From bae55f77afee63ad9982261d9fc371379252c44d Mon Sep 17 00:00:00 2001 From: Henry Hein Date: Tue, 25 Jul 2023 09:35:42 +0800 Subject: [PATCH 01/55] fix: ts-migrate trade-params --- .../components/input-field/input-field.tsx | 20 +-- .../src/components/input-field/input.tsx | 14 +- .../src/components/numpad/numpad.tsx | 10 +- packages/stores/src/mockStore.ts | 3 + packages/stores/types.ts | 8 +- .../{allow-equals.jsx => allow-equals.tsx} | 38 ++--- .../{amount-mobile.jsx => amount-mobile.tsx} | 54 +++++-- .../TradeParams/{amount.jsx => amount.tsx} | 35 ++--- .../TradeParams/{barrier.jsx => barrier.tsx} | 27 ++-- .../{last-digit.jsx => last-digit.tsx} | 13 +- .../Multiplier/multiplier-amount-modal.jsx | 2 +- .../Containers/trade-params-mobile.jsx | 6 +- .../Trading/Containers/trade-params.jsx | 6 +- .../trader/src/Stores/useTraderStores.tsx | 136 +++++++++++++++++- packages/trader/src/Types/common-prop.type.ts | 27 ++++ packages/trader/src/Types/index.ts | 1 + 16 files changed, 302 insertions(+), 98 deletions(-) rename packages/trader/src/Modules/Trading/Components/Form/TradeParams/{allow-equals.jsx => allow-equals.tsx} (72%) rename packages/trader/src/Modules/Trading/Components/Form/TradeParams/{amount-mobile.jsx => amount-mobile.tsx} (84%) rename packages/trader/src/Modules/Trading/Components/Form/TradeParams/{amount.jsx => amount.tsx} (90%) rename packages/trader/src/Modules/Trading/Components/Form/TradeParams/{barrier.jsx => barrier.tsx} (93%) rename packages/trader/src/Modules/Trading/Components/Form/TradeParams/{last-digit.jsx => last-digit.tsx} (80%) create mode 100644 packages/trader/src/Types/common-prop.type.ts create mode 100644 packages/trader/src/Types/index.ts diff --git a/packages/components/src/components/input-field/input-field.tsx b/packages/components/src/components/input-field/input-field.tsx index 94296b7c1663..37cc6c2dbec3 100644 --- a/packages/components/src/components/input-field/input-field.tsx +++ b/packages/components/src/components/input-field/input-field.tsx @@ -14,7 +14,7 @@ export type TButtonType = 'button' | 'submit' | 'reset'; // supports more than two different types of 'value' as a prop. // Quick Solution - Pass two different props to input field. type TInputField = { - ariaLabel: string; + ariaLabel?: string; checked?: boolean; className?: string; classNameDynamicSuffix?: string; @@ -22,8 +22,8 @@ type TInputField = { classNameInput?: string; classNamePrefix?: string; classNameWrapper?: string; // CSS class for the component wrapper - currency: string; - current_focus: string; + currency?: string; + current_focus: string | null; data_testid?: string; data_tip?: string; data_value?: string; @@ -31,26 +31,26 @@ type TInputField = { error_message_alignment?: string; error_messages?: string[]; format?: (new_value?: string) => string; - fractional_digits: number; + fractional_digits?: number; helper?: string; icon?: React.ElementType; id?: string; increment_button_type?: TButtonType; inline_prefix?: string; inputmode?: TInputMode; - is_autocomplete_disabled: boolean; + is_autocomplete_disabled?: boolean; is_disabled?: boolean; - is_error_tooltip_hidden: boolean; + is_error_tooltip_hidden?: boolean; is_float: boolean; - is_hj_whitelisted: boolean; + is_hj_whitelisted?: boolean; is_incrementable_on_long_press?: boolean; - is_incrementable: boolean; - is_negative_disabled: boolean; + is_incrementable?: boolean; + is_negative_disabled?: boolean; is_read_only?: boolean; is_signed?: boolean; is_unit_at_right?: boolean; label?: string; - max_length: number; + max_length?: number; max_value?: number; min_value?: number; name: string; diff --git a/packages/components/src/components/input-field/input.tsx b/packages/components/src/components/input-field/input.tsx index 126a164b16ca..8c5df9dd49e3 100644 --- a/packages/components/src/components/input-field/input.tsx +++ b/packages/components/src/components/input-field/input.tsx @@ -4,7 +4,7 @@ import { getCurrencyDisplayCode } from '@deriv/shared'; import { TInputMode } from './input-field'; type TInputProps = { - ariaLabel: string; + ariaLabel?: string; changeValue: ( e: React.ChangeEvent, callback?: (evt: React.ChangeEvent) => void @@ -13,22 +13,22 @@ type TInputProps = { className?: string; classNameDynamicSuffix?: string; classNameInlinePrefix?: string; - current_focus: string; + current_focus: string | null; data_testid?: string; data_tip?: string; data_value?: number | string; display_value: number | string; - fractional_digits: number; + fractional_digits?: number; has_error?: boolean; id?: string; inline_prefix?: string; inputmode?: TInputMode; - is_autocomplete_disabled: boolean; + is_autocomplete_disabled?: boolean; is_disabled?: boolean; is_hj_whitelisted: boolean; - is_incrementable: boolean; + is_incrementable?: boolean; is_read_only: boolean; - max_length: number; + max_length?: number; name: string; onBlur?: React.FocusEventHandler; onClick?: React.MouseEventHandler; @@ -128,7 +128,7 @@ const Input = ({ data-value={data_value} disabled={!!is_disabled} id={id} - maxLength={fractional_digits ? max_length + fractional_digits + 1 : max_length} + maxLength={fractional_digits && max_length ? max_length + fractional_digits + 1 : max_length} name={name} onBlur={onBlurHandler} onChange={onChange} diff --git a/packages/components/src/components/numpad/numpad.tsx b/packages/components/src/components/numpad/numpad.tsx index 662f840aaa33..9a92b24c744f 100644 --- a/packages/components/src/components/numpad/numpad.tsx +++ b/packages/components/src/components/numpad/numpad.tsx @@ -12,20 +12,20 @@ type TNumpad = { is_regular?: boolean; is_currency?: boolean; is_submit_disabled?: boolean; - label: string; + label?: string; reset_press_interval: number; reset_value: string; - max: number; - min: number; + max?: number; + min?: number; pip_size: number; onSubmit: (param: number | string) => void; - v: string; + v?: string; render?: (props: { value: string; className: string }) => React.ReactNode; submit_label: string; value: string; format: (v: string) => number; onValueChange: (val: number | string) => void; - onValidate: (default_value: number | string) => string | undefined; + onValidate: (default_value: number | string) => boolean | 'error'; }; const concatenate = (number: string | number, default_value: string | number) => diff --git a/packages/stores/src/mockStore.ts b/packages/stores/src/mockStore.ts index 18590524abc6..e36084201881 100644 --- a/packages/stores/src/mockStore.ts +++ b/packages/stores/src/mockStore.ts @@ -96,6 +96,7 @@ const mock = (): TStores & { is_mock: boolean } => { balance: '', can_change_fiat_currency: false, currency: '', + currencies_list: [{ text: '', value: '', has_tool_tip: false }], current_currency_type: '', current_fiat_currency: '', cfd_score: 0, @@ -120,6 +121,7 @@ const mock = (): TStores & { is_mock: boolean } => { is_logged_in: false, is_logging_in: false, is_pending_proof_of_ownership: false, + is_single_currency: false, is_switching: false, is_tnc_needed: false, is_trading_experience_incomplete: false, @@ -329,6 +331,7 @@ const mock = (): TStores & { is_mock: boolean } => { toggleShouldShowRealAccountsList: jest.fn(), is_reset_trading_password_modal_visible: false, setResetTradingPasswordModalOpen: jest.fn(), + vanilla_trade_type: 'VANILLALONGCALL', }, traders_hub: { closeModal: jest.fn(), diff --git a/packages/stores/types.ts b/packages/stores/types.ts index 59fd8aba745f..dcdcc708c10e 100644 --- a/packages/stores/types.ts +++ b/packages/stores/types.ts @@ -158,7 +158,8 @@ type TMenuItem = { type TAddToastProps = { key: string; - content: string; + content: string | React.ReactNode; + timeout?: number; type: string; }; @@ -237,6 +238,7 @@ type TClientStore = { cfd_score: number; setCFDScore: (score: number) => void; currency: string; + currencies_list: { text: string; value: string; has_tool_tip?: boolean }[]; current_currency_type?: string; current_fiat_currency?: string; has_any_real_account: boolean; @@ -251,6 +253,7 @@ type TClientStore = { is_eu_country: boolean; is_eu: boolean; is_uk: boolean; + is_single_currency: boolean; is_social_signup: boolean; has_residence: boolean; is_authorize: boolean; @@ -422,7 +425,7 @@ type TUiStore = { value: 'maltainvest' | 'svg' | 'add_crypto' | 'choose' | 'add_fiat' | 'set_currency' | 'manage' ) => void; notification_messages_ui: React.ElementType; - setCurrentFocus: (value: string) => void; + setCurrentFocus: (value: string | null) => void; setDarkMode: (is_dark_mode_on: boolean) => boolean; setReportsTabIndex: (value: number) => void; setIsClosingCreateRealAccountModal: (value: boolean) => void; @@ -464,6 +467,7 @@ type TUiStore = { populateHeaderExtensions: (header_items: JSX.Element | null) => void; populateSettingsExtensions: (menu_items: Array | null) => void; setShouldShowCooldownModal: (value: boolean) => void; + vanilla_trade_type: 'VANILLALONGCALL' | 'VANILLALONGPUT'; }; type TPortfolioStore = { diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/allow-equals.jsx b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/allow-equals.tsx similarity index 72% rename from packages/trader/src/Modules/Trading/Components/Form/TradeParams/allow-equals.jsx rename to packages/trader/src/Modules/Trading/Components/Form/TradeParams/allow-equals.tsx index 0d3ad0614154..448e3419ba38 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/allow-equals.jsx +++ b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/allow-equals.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import PropTypes from 'prop-types'; import { Popover, Checkbox } from '@deriv/components'; import { localize } from '@deriv/translations'; import { @@ -7,6 +6,22 @@ import { hasDurationForCallPutEqual, isRiseFallEqual, } from 'Stores/Modules/Trading/Helpers/allow-equals'; +import { useTraderStore } from 'Stores/useTraderStores'; + +type TTradeStore = Pick< + ReturnType, + | 'contract_start_type' + | 'contract_type' + | 'contract_types_list' + | 'duration_unit' + | 'expiry_type' + | 'has_equals_only' +>; + +type TAllowEquals = TTradeStore & { + onChange: (e: { target: { name: string; value: number } }) => Promise; + value: number; +}; const AllowEquals = ({ contract_start_type, @@ -17,7 +32,7 @@ const AllowEquals = ({ onChange, value, has_equals_only, -}) => { +}: TAllowEquals) => { const has_callputequal_duration = hasDurationForCallPutEqual( contract_types_list, duration_unit, @@ -28,10 +43,12 @@ const AllowEquals = ({ const has_allow_equals = isRiseFallEqual(contract_type) && (has_callputequal_duration || expiry_type === 'endtime') && has_callputequal; - const changeValue = e => { + const changeValue: React.ComponentProps['onChange'] = e => { e.persist(); - const { name, checked } = e.target; - onChange({ target: { name, value: Number(checked) } }); + if ('checked' in e.target) { + const { name, checked } = e.target; + onChange({ target: { name, value: Number(checked) } }); + } }; return ( @@ -61,15 +78,4 @@ const AllowEquals = ({ ); }; -AllowEquals.propTypes = { - contract_start_type: PropTypes.string, - contract_type: PropTypes.string, - contract_types_list: PropTypes.object, - duration_unit: PropTypes.string, - expiry_type: PropTypes.string, - has_equals_only: PropTypes.bool, - onChange: PropTypes.func, - value: PropTypes.number, -}; - export default AllowEquals; diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount-mobile.jsx b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount-mobile.tsx similarity index 84% rename from packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount-mobile.jsx rename to packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount-mobile.tsx index d588f1377351..76b8a1a50a5e 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount-mobile.jsx +++ b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount-mobile.tsx @@ -7,6 +7,24 @@ import classNames from 'classnames'; import { observer, useStore } from '@deriv/stores'; import { useTraderStore } from 'Stores/useTraderStores'; +type TBasis = { + basis: string; + duration_unit: string; + duration_value: number; + toggleModal: () => void; + has_duration_error: boolean; + selected_basis: string; + setSelectedAmount: (basis: string, num: string | number) => void; + setAmountError: (has_error: boolean) => void; +}; + +type TObject = { + duration_unit: string; + duration: number; + basis: string; + amount: string | number; +}; + const Basis = observer( ({ basis, @@ -17,28 +35,31 @@ const Basis = observer( selected_basis, setSelectedAmount, setAmountError, - }) => { + }: TBasis) => { const { ui, client } = useStore(); const { addToast, vanilla_trade_type } = ui; const { currency } = client; const { onChangeMultiple, - trade_amount, - trade_basis, - trade_duration_unit, - trade_duration, + amount: trade_amount, + basis: trade_basis, + duration_unit: trade_duration_unit, + duration: trade_duration, contract_type, stake_boundary, } = useTraderStore(); + const user_currency_decimal_places = getDecimalPlaces(currency); - const onNumberChange = num => { + const onNumberChange = (num: number | string) => { setSelectedAmount(basis, num); validateAmount(num); }; - const formatAmount = value => - !isNaN(value) && value !== '' ? Number(value).toFixed(user_currency_decimal_places) : value; - const setBasisAndAmount = amount => { - const on_change_obj = {}; + const formatAmount = (value: number | string): number => { + const numericValue = typeof value === 'string' ? Number(value) : value; + return !isNaN(numericValue) ? parseFloat(numericValue.toFixed(user_currency_decimal_places)) : numericValue; + }; + const setBasisAndAmount = (amount: number | string) => { + const on_change_obj = {} as TObject; // Check for any duration changes in Duration trade params Tab before sending onChange object if (duration_unit !== trade_duration_unit && !has_duration_error) @@ -56,7 +77,7 @@ const Basis = observer( const zero_decimals = Number('0').toFixed(getDecimalPlaces(currency)); const min_amount = parseFloat(zero_decimals.toString().replace(/.$/, '1')); - const validateAmount = value => { + const validateAmount = (value: number | string) => { const localized_message = ; const selected_value = parseFloat(value.toString()); @@ -125,6 +146,13 @@ const Basis = observer( } ); +type TAmountMobile = React.ComponentProps & { + amount_tab_idx: number; + setAmountTabIdx: React.ComponentProps['onTabItemClick']; + stake_value: string; + payout_value: string; +}; + const Amount = observer( ({ toggleModal, @@ -137,7 +165,7 @@ const Amount = observer( setSelectedAmount, stake_value, payout_value, - }) => { + }: TAmountMobile) => { const { basis, basis_list } = useTraderStore(); const has_selected_tab_idx = typeof amount_tab_idx !== 'undefined'; const active_index = has_selected_tab_idx ? amount_tab_idx : basis_list.findIndex(b => b.value === basis); @@ -159,6 +187,8 @@ const Amount = observer( return (
+ {/* + // @ts-expect-error We explicitly defined children as React.ReactElement[] here we only return a single JSX.Element or null. Dont know how to fix without breaking Tabs it's used in 27 files*/} {basis_list.map(basis_option => { switch (basis_option.value) { diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount.jsx b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount.tsx similarity index 90% rename from packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount.jsx rename to packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount.tsx index 978d26394d8d..34902374e22a 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount.jsx +++ b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount.tsx @@ -2,26 +2,34 @@ import { AMOUNT_MAX_LENGTH, addComma, getDecimalPlaces } from '@deriv/shared'; import { ButtonToggle, Dropdown, InputField, Text } from '@deriv/components'; import { Localize, localize } from '@deriv/translations'; -import AllowEquals from './allow-equals.jsx'; +import AllowEquals from './allow-equals'; import Fieldset from 'App/Components/Form/fieldset.jsx'; import Multiplier from './Multiplier/multiplier.jsx'; import MultipliersInfo from './Multiplier/info.jsx'; -import PropTypes from 'prop-types'; import React from 'react'; import classNames from 'classnames'; import { useTraderStore } from 'Stores/useTraderStores'; import { observer, useStore } from '@deriv/stores'; +type TInput = { + amount: string | number; + currency: string; + current_focus: string | null; + error_messages?: string[]; + is_single_currency: boolean; + onChange: (e: { target: { name: string; value: number | string } }) => void; + setCurrentFocus: (name: string | null) => void; +}; + export const Input = ({ amount, currency, current_focus, error_messages, - is_nativepicker, is_single_currency, onChange, setCurrentFocus, -}) => ( +}: TInput) => ( ); -const Amount = observer(({ is_minimized, is_nativepicker }) => { +const Amount = observer(({ is_minimized }: { is_minimized: boolean }) => { const { ui, client } = useStore(); const { currencies_list, is_single_currency } = client; const { setCurrentFocus, vanilla_trade_type, current_focus } = ui; @@ -90,7 +97,7 @@ const Amount = observer(({ is_minimized, is_nativepicker }) => { ); } - const error_messages = validation_errors.amount; + const error_messages = validation_errors?.amount; const getBasisList = () => basis_list.map(item => ({ text: item.text, value: item.value })); @@ -132,12 +139,10 @@ const Amount = observer(({ is_minimized, is_nativepicker }) => { current_focus={current_focus} error_messages={error_messages} is_single_currency={is_single_currency} - is_nativepicker={is_nativepicker} onChange={onChange} setCurrentFocus={setCurrentFocus} /> { current_focus={current_focus} error_messages={error_messages} is_single_currency={is_single_currency} - is_nativepicker={is_nativepicker} onChange={onChange} setCurrentFocus={setCurrentFocus} /> @@ -168,13 +172,15 @@ const Amount = observer(({ is_minimized, is_nativepicker }) => { duration_unit={duration_unit} expiry_type={expiry_type} onChange={onChange} - value={parseInt(is_equal)} + value={Number(is_equal)} has_equals_only={has_equals_only} /> {is_multiplier && ( { ); }); -Amount.propTypes = { - is_minimized: PropTypes.bool, - is_nativepicker: PropTypes.bool, -}; - export default Amount; diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/barrier.jsx b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/barrier.tsx similarity index 93% rename from packages/trader/src/Modules/Trading/Components/Form/TradeParams/barrier.jsx rename to packages/trader/src/Modules/Trading/Components/Form/TradeParams/barrier.tsx index 4855881b8b1a..e2ca0280cf90 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/barrier.jsx +++ b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/barrier.tsx @@ -1,5 +1,4 @@ import classNames from 'classnames'; -import PropTypes from 'prop-types'; import React from 'react'; import { DesktopWrapper, Icon, InputField, MobileWrapper, Modal, Text, usePrevious } from '@deriv/components'; import Fieldset from 'App/Components/Form/fieldset.jsx'; @@ -9,7 +8,12 @@ import { useTraderStore } from 'Stores/useTraderStores'; import { localize } from '@deriv/translations'; import LabeledQuantityInputMobile from '../LabeledQuantityInputMobile'; -const Barrier = observer(({ is_minimized, is_absolute_only }) => { +type TBarrier = { + is_minimized?: boolean; + is_absolute_only?: boolean; +}; + +const Barrier = observer(({ is_minimized, is_absolute_only }: TBarrier) => { const { ui } = useStore(); const { current_focus, setCurrentFocus } = ui; const { @@ -25,11 +29,12 @@ const Barrier = observer(({ is_minimized, is_absolute_only }) => { } = useTraderStore(); const [show_modal, setShowModal] = React.useState(false); const type_with_current_spot = Object.keys(trade_types).find(type => proposal_info?.[type]?.spot); - const contract_info = proposal_info?.[type_with_current_spot]; + let contract_info, has_spot_increased; + if (type_with_current_spot) contract_info = proposal_info?.[type_with_current_spot]; const current_spot = contract_info?.spot || ''; const current_barrier_price = contract_info?.barrier || ''; const previous_spot = usePrevious(current_spot); - const has_spot_increased = current_spot > previous_spot; + if (previous_spot) has_spot_increased = current_spot > previous_spot; const barrier_title = barrier_count === 1 ? localize('Barrier') : localize('Barriers'); const has_error_or_not_loaded = contract_info?.has_error || !contract_info?.id; @@ -49,7 +54,7 @@ const Barrier = observer(({ is_minimized, is_absolute_only }) => { // TODO: Some contracts yet to be implemented in app.deriv.com allow only absolute barrier, hence the prop const is_absolute_barrier = is_day_duration || is_absolute_only; - const format = value => { + const format = (value: string) => { const float_value = parseFloat(value); let final_value; if (Math.sign(float_value) === -1) { @@ -86,7 +91,7 @@ const Barrier = observer(({ is_minimized, is_absolute_only }) => { )} current_focus={current_focus} onChange={onChange} - error_messages={validation_errors.barrier_1 || []} + error_messages={validation_errors?.barrier_1 || []} is_float is_signed setCurrentFocus={setCurrentFocus} @@ -103,7 +108,7 @@ const Barrier = observer(({ is_minimized, is_absolute_only }) => { classNameInput='trade-container__input' current_focus={current_focus} onChange={onChange} - error_messages={validation_errors.barrier_2} + error_messages={validation_errors?.barrier_2} is_float is_signed setCurrentFocus={setCurrentFocus} @@ -131,6 +136,7 @@ const Barrier = observer(({ is_minimized, is_absolute_only }) => { {localize('Current Price')} {current_spot && ( + //@ts-expect-error until this component is typescript migrated and some props optional { current_focus={current_focus} onChange={onChange} error_messages={ - (barrier_count === 1 ? validation_errors.barrier_1 : validation_errors.barrier_2) || [] + (barrier_count === 1 ? validation_errors?.barrier_1 : validation_errors?.barrier_2) || [] } error_message_alignment='top' is_float @@ -219,9 +225,4 @@ const Barrier = observer(({ is_minimized, is_absolute_only }) => { ); }); -Barrier.propTypes = { - is_absolute_only: PropTypes.bool, - is_minimized: PropTypes.bool, -}; - export default Barrier; diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/last-digit.jsx b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/last-digit.tsx similarity index 80% rename from packages/trader/src/Modules/Trading/Components/Form/TradeParams/last-digit.jsx rename to packages/trader/src/Modules/Trading/Components/Form/TradeParams/last-digit.tsx index 87c16bb7da3c..51e8377621e9 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/last-digit.jsx +++ b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/last-digit.tsx @@ -1,4 +1,3 @@ -import PropTypes from 'prop-types'; import React from 'react'; import { isDesktop } from '@deriv/shared'; import { localize } from '@deriv/translations'; @@ -7,7 +6,11 @@ import Fieldset from 'App/Components/Form/fieldset.jsx'; import { observer } from '@deriv/stores'; import { useTraderStore } from 'Stores/useTraderStores'; -const LastDigit = observer(({ is_minimized }) => { +type TLastDigit = { + is_minimized?: boolean; +}; + +const LastDigit = observer(({ is_minimized }: TLastDigit) => { const { onChange, last_digit } = useTraderStore(); if (is_minimized) { return
{`${localize('Last Digit')}: ${last_digit}`}
; @@ -29,10 +32,4 @@ const LastDigit = observer(({ is_minimized }) => { ); }); -LastDigit.propTypes = { - is_minimized: PropTypes.bool, - last_digit: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - onChange: PropTypes.func, -}; - export default LastDigit; diff --git a/packages/trader/src/Modules/Trading/Containers/Multiplier/multiplier-amount-modal.jsx b/packages/trader/src/Modules/Trading/Containers/Multiplier/multiplier-amount-modal.jsx index 56cc5eb875a0..3032438b7d46 100644 --- a/packages/trader/src/Modules/Trading/Containers/Multiplier/multiplier-amount-modal.jsx +++ b/packages/trader/src/Modules/Trading/Containers/Multiplier/multiplier-amount-modal.jsx @@ -3,7 +3,7 @@ import { Div100vhContainer, Modal, Money, Popover, usePreventIOSZoom } from '@de import { useIsMounted, WS } from '@deriv/shared'; import { localize, Localize } from '@deriv/translations'; import { requestPreviewProposal } from 'Stores/Modules/Trading/Helpers/preview-proposal'; -import AmountMobile from 'Modules/Trading/Components/Form/TradeParams/amount-mobile.jsx'; +import AmountMobile from 'Modules/Trading/Components/Form/TradeParams/amount-mobile'; import MultipliersInfo from 'Modules/Trading/Components/Form/TradeParams/Multiplier/info.jsx'; import { observer, useStore } from '@deriv/stores'; import { useTraderStore } from 'Stores/useTraderStores'; diff --git a/packages/trader/src/Modules/Trading/Containers/trade-params-mobile.jsx b/packages/trader/src/Modules/Trading/Containers/trade-params-mobile.jsx index 8a5873c816a9..7177907a4fa9 100644 --- a/packages/trader/src/Modules/Trading/Containers/trade-params-mobile.jsx +++ b/packages/trader/src/Modules/Trading/Containers/trade-params-mobile.jsx @@ -2,10 +2,10 @@ import 'Sass/app/modules/trading-mobile.scss'; import { Div100vhContainer, Modal, Money, Tabs, ThemedScrollbars, usePreventIOSZoom } from '@deriv/components'; -import AmountMobile from 'Modules/Trading/Components/Form/TradeParams/amount-mobile.jsx'; -import Barrier from 'Modules/Trading/Components/Form/TradeParams/barrier.jsx'; +import AmountMobile from 'Modules/Trading/Components/Form/TradeParams/amount-mobile'; +import Barrier from 'Modules/Trading/Components/Form/TradeParams/barrier'; import DurationMobile from 'Modules/Trading/Components/Form/TradeParams/Duration/duration-mobile.jsx'; -import LastDigit from 'Modules/Trading/Components/Form/TradeParams/last-digit.jsx'; +import LastDigit from 'Modules/Trading/Components/Form/TradeParams/last-digit'; import { observer, useStore } from '@deriv/stores'; import { useTraderStore } from 'Stores/useTraderStores'; import React from 'react'; diff --git a/packages/trader/src/Modules/Trading/Containers/trade-params.jsx b/packages/trader/src/Modules/Trading/Containers/trade-params.jsx index cd34a3ce49a9..8777117ac7fe 100644 --- a/packages/trader/src/Modules/Trading/Containers/trade-params.jsx +++ b/packages/trader/src/Modules/Trading/Containers/trade-params.jsx @@ -1,10 +1,10 @@ import PropTypes from 'prop-types'; import React from 'react'; import classNames from 'classnames'; -import Amount from 'Modules/Trading/Components/Form/TradeParams/amount.jsx'; -import Barrier from 'Modules/Trading/Components/Form/TradeParams/barrier.jsx'; +import Amount from 'Modules/Trading/Components/Form/TradeParams/amount'; +import Barrier from 'Modules/Trading/Components/Form/TradeParams/barrier'; import Duration from 'Modules/Trading/Components/Form/TradeParams/Duration'; -import LastDigit from 'Modules/Trading/Components/Form/TradeParams/last-digit.jsx'; +import LastDigit from 'Modules/Trading/Components/Form/TradeParams/last-digit'; import CancelDeal from 'Modules/Trading/Components/Form/TradeParams/Multiplier/cancel-deal.jsx'; import Accumulator from 'Modules/Trading/Components/Form/TradeParams/Accumulator/accumulator.jsx'; import StopLoss from 'Modules/Trading/Components/Form/TradeParams/Multiplier/stop-loss.jsx'; diff --git a/packages/trader/src/Stores/useTraderStores.tsx b/packages/trader/src/Stores/useTraderStores.tsx index 5e4db0cd77c2..028893d4c853 100644 --- a/packages/trader/src/Stores/useTraderStores.tsx +++ b/packages/trader/src/Stores/useTraderStores.tsx @@ -1,8 +1,142 @@ import React from 'react'; import { useStore } from '@deriv/stores'; import TradeStore from './Modules/Trading/trade-store'; +import moment from 'moment'; -const TraderStoreContext = React.createContext(null); +type TTextValueStrings = { + text: string; + value: string; +}; + +type TContractTypesList = { + [key: string]: { + name: string; + categories: TTextValueStrings[]; + }; +}; + +type TContractCategoriesList = { + Multipliers: TContractTypesList; + 'Ups & Downs': TContractTypesList; + 'Highs & Lows': TContractTypesList; + 'Ins & Outs': TContractTypesList; + 'Look Backs': TContractTypesList; + Digits: TContractTypesList; + Vanillas: TContractTypesList; + Accumulators: TContractTypesList; +}; + +type TToastBoxListItem = { + contract_category: string; + contract_types: [ + { + text: string; + value: string; + } + ]; +}; + +type TToastBoxObject = { + key?: boolean; + buy_price?: number; + currency?: string; + contract_type?: string; + list?: TToastBoxListItem[]; +}; + +type TOverrideTradeStore = Omit< + TradeStore, + | 'accumulator_range_list' + | 'barriers' + | 'basis_list' + | 'cancellation_price' + | 'cancellation_range_list' + | 'clearContractPurchaseToastBox' + | 'contract_purchase_toast_box' + | 'contract_types_list' + | 'duration_min_max' + | 'duration_units_list' + | 'expiry_date' + | 'expiry_time' + | 'expiry_type' + | 'form_components' + | 'market_close_times' + | 'market_open_times' + | 'multiplier_range_list' + | 'sessions' + | 'setIsTradeParamsExpanded' + | 'stake_boundary' + | 'start_dates_list' + | 'start_time' + | 'take_profit' + | 'proposal_info' + | 'trade_types' + | 'ticks_history_stats' + | 'validation_errors' +> & { + accumulator_range_list?: number[]; + basis_list: Array; + cancellation_price?: number; + cancellation_range_list: Array; + clearContractPurchaseToastBox: () => void; + contract_purchase_toast_box: TToastBoxObject; + contract_types_list: TContractCategoriesList; + duration_min_max: { + [key: string]: { min: number; max: number }; + }; + duration_units_list: Array; + expiry_date: string | null; + expiry_time: string | null; + expiry_type: string | null; + form_components: string[]; + market_open_times: string[]; + market_close_times: string[]; + multiplier_range_list: number[]; + proposal_info: { + [key: string]: { + barrier?: string; + has_error?: boolean; + id: string; + has_increased?: boolean; + message?: string; + cancellation?: { + ask_price: number; + date_expiry: number; + }; + growth_rate?: number; + returns?: string; + stake: string; + spot?: string; + }; + }; + sessions: Array<{ open: moment.Moment; close: moment.Moment }>; + setIsTradeParamsExpanded: (value: boolean) => void; + stake_boundary: { + VANILLALONGCALL: { + min_stake: string; + max_stake: string; + }; + VANILLALONGPUT: { + min_stake: string; + max_stake: string; + }; + }; + start_dates_list: Array<{ text: string; value: number }>; + start_time: string | null; + take_profit?: string; + ticks_history_stats: { + ticks_stayed_in?: number[]; + last_tick_epoch?: number; + }; + trade_types: { [key: string]: string }; + validation_errors?: { + amount?: string[]; + barrier_1?: string[]; + barrier_2?: string[]; + }; +}; + +const TraderStoreContext = React.createContext(null); export const TraderStoreProvider = ({ children }: React.PropsWithChildren) => { const { modules } = useStore(); diff --git a/packages/trader/src/Types/common-prop.type.ts b/packages/trader/src/Types/common-prop.type.ts new file mode 100644 index 000000000000..bc38b04c55e2 --- /dev/null +++ b/packages/trader/src/Types/common-prop.type.ts @@ -0,0 +1,27 @@ +import { useTraderStore } from 'Stores/useTraderStores'; + +export type TProposalTypeInfo = { + has_error?: boolean; + id: string; + has_increased?: boolean; + message?: string; + cancellation?: { + ask_price: number; + date_expiry: number; + }; + growth_rate?: number; + returns?: string; + stake: string; +}; + +export type TError = { + error?: { + code?: string; + details?: { + field?: string; + }; + message?: string; + }; +}; + +export type TTradeStore = ReturnType; diff --git a/packages/trader/src/Types/index.ts b/packages/trader/src/Types/index.ts new file mode 100644 index 000000000000..989319119dbe --- /dev/null +++ b/packages/trader/src/Types/index.ts @@ -0,0 +1 @@ +export * from './common-prop.type'; From 4ac6bb79f6b3e9d43a2fb7172dde473e90f7f13e Mon Sep 17 00:00:00 2001 From: Henry Hein Date: Tue, 25 Jul 2023 09:44:20 +0800 Subject: [PATCH 02/55] fix: sonarcloud --- .../src/Modules/Trading/Components/Form/TradeParams/amount.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount.tsx b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount.tsx index 34902374e22a..53470a4ca2f9 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount.tsx +++ b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount.tsx @@ -39,7 +39,7 @@ export const Input = ({ error_messages={error_messages} fractional_digits={getDecimalPlaces(currency)} id='dt_amount_input' - inline_prefix={is_single_currency ? currency : null || undefined} + inline_prefix={is_single_currency ? currency : undefined} is_autocomplete_disabled is_float is_hj_whitelisted From 7130bc720d43c611bbc8fe25b39ba99062c8c5ae Mon Sep 17 00:00:00 2001 From: Henry Hein Date: Wed, 26 Jul 2023 09:14:12 +0800 Subject: [PATCH 03/55] fix: pull in changes from tech debt 2 package --- packages/stores/src/mockStore.ts | 59 +++++++++++-------- packages/stores/types.ts | 59 +++++++++++++++++-- .../trader/src/Stores/useTraderStores.tsx | 4 ++ packages/trader/tsconfig.json | 3 +- 4 files changed, 94 insertions(+), 31 deletions(-) diff --git a/packages/stores/src/mockStore.ts b/packages/stores/src/mockStore.ts index e36084201881..4a139e1ee1a4 100644 --- a/packages/stores/src/mockStore.ts +++ b/packages/stores/src/mockStore.ts @@ -2,6 +2,19 @@ import merge from 'lodash.merge'; import type { TStores } from '../types'; const mock = (): TStores & { is_mock: boolean } => { + const common_store_error = { + app_routing_history: [], + header: '', + message: '', + type: '', + redirect_label: '', + redirect_to: '', + should_clear_error_on_click: false, + should_show_refresh: false, + redirectOnClick: jest.fn(), + setError: jest.fn(), + }; + const services_error = { code: '', message: '', type: '' }; return { is_mock: true, client: { @@ -242,18 +255,7 @@ const mock = (): TStores & { is_mock: boolean } => { setPrevAccountType: jest.fn(), }, common: { - error: { - app_routing_history: [], - header: '', - message: '', - type: '', - redirect_label: '', - redirect_to: '', - should_clear_error_on_click: false, - should_show_refresh: false, - redirectOnClick: jest.fn(), - setError: jest.fn(), - }, + error: common_store_error, current_language: 'EN', isCurrentLanguage: jest.fn(), is_from_derivgo: false, @@ -264,6 +266,7 @@ const mock = (): TStores & { is_mock: boolean } => { changeCurrentLanguage: jest.fn(), changeSelectedLanguage: jest.fn(), is_network_online: false, + services_error, server_time: undefined, is_language_changing: false, setAppstorePlatform: jest.fn(), @@ -281,6 +284,8 @@ const mock = (): TStores & { is_mock: boolean } => { is_language_settings_modal_on: false, is_mobile: false, is_reports_visible: false, + is_services_error_visible: false, + is_unsupported_contract_modal_visible: false, disableApp: jest.fn(), enableApp: jest.fn(), setCurrentFocus: jest.fn(), @@ -288,13 +293,17 @@ const mock = (): TStores & { is_mock: boolean } => { toggleCashier: jest.fn(), setDarkMode: jest.fn(), setReportsTabIndex: jest.fn(), + has_only_forward_starting_contracts: false, has_real_account_signup_ended: false, notification_messages_ui: jest.fn(), openRealAccountSignup: jest.fn(), + setHasOnlyForwardingContracts: jest.fn(), setIsClosingCreateRealAccountModal: jest.fn(), setRealAccountSignupEnd: jest.fn(), + setPurchaseState: jest.fn(), shouldNavigateAfterChooseCrypto: jest.fn(), toggleLanguageSettingsModal: jest.fn(), + toggleServicesErrorModal: jest.fn(), toggleSetCurrencyModal: jest.fn(), addToast: jest.fn(), removeToast: jest.fn(), @@ -324,6 +333,7 @@ const mock = (): TStores & { is_mock: boolean } => { openDerivRealAccountNeededModal: jest.fn(), populateHeaderExtensions: jest.fn(), populateSettingsExtensions: jest.fn(), + purchase_states: [], setShouldShowCooldownModal: jest.fn(), openAccountNeededModal: jest.fn(), is_accounts_switcher_on: false, @@ -397,18 +407,8 @@ const mock = (): TStores & { is_mock: boolean } => { }, portfolio: { active_positions: [], - error: { - header: '', - message: '', - type: '', - redirect_label: '', - redirect_to: '', - should_clear_error_on_click: false, - should_show_refresh: false, - redirectOnClick: jest.fn(), - setError: jest.fn(), - app_routing_history: [], - }, + all_positions: [], + error: '', getPositionById: jest.fn(), is_loading: false, is_accumulator: false, @@ -416,9 +416,15 @@ const mock = (): TStores & { is_mock: boolean } => { onClickCancel: jest.fn(), onClickSell: jest.fn(), onMount: jest.fn(), + positions: [], removePositionById: jest.fn(), }, contract_trade: { + contract_info: {}, + contract_update_stop_loss: '', + contract_update_take_profit: '', + has_contract_update_stop_loss: false, + has_contract_update_take_profit: false, getContractById: jest.fn(), }, modules: {}, @@ -432,6 +438,11 @@ const mock = (): TStores & { is_mock: boolean } => { update: jest.fn(), unmount: jest.fn(), }, + gtm: {}, + pushwoosh: {}, + contract_replay: {}, + chart_barrier_store: {}, + active_symbols: {}, }; }; diff --git a/packages/stores/types.ts b/packages/stores/types.ts index dcdcc708c10e..fc514f7ac870 100644 --- a/packages/stores/types.ts +++ b/packages/stores/types.ts @@ -1,9 +1,11 @@ import type { AccountLimitsResponse, Authorize, + ContractUpdate, DetailsOfEachMT5Loginid, GetAccountStatus, GetLimits, + Portfolio1, GetSettings, LandingCompany, LogOutResponse, @@ -387,6 +389,12 @@ type TCommonStoreError = { type?: string; }; +type TCommonStoreServicesError = { + code: string; + message: string; + type?: string; +}; + type TCommonStore = { isCurrentLanguage(language_code: string): boolean; error: TCommonStoreError; @@ -401,6 +409,7 @@ type TCommonStore = { changeSelectedLanguage: (key: string) => void; current_language: string; is_language_changing: boolean; + services_error: TCommonStoreServicesError; setAppstorePlatform: (value: string) => void; app_routing_history: TAppRoutingHistory[]; getExchangeRate: (from_currency: string, to_currency: string) => Promise; @@ -412,6 +421,7 @@ type TUiStore = { current_focus: string | null; disableApp: () => void; enableApp: () => void; + has_only_forward_starting_contracts: boolean; has_real_account_signup_ended: boolean; is_cashier_visible: boolean; is_closing_create_real_account_modal: boolean; @@ -419,7 +429,8 @@ type TUiStore = { is_reports_visible: boolean; is_language_settings_modal_on: boolean; is_mobile: boolean; - sub_section_index: number; + is_services_error_visible: boolean; + is_unsupported_contract_modal_visible: boolean; toggleShouldShowRealAccountsList: (value: boolean) => void; openRealAccountSignup: ( value: 'maltainvest' | 'svg' | 'add_crypto' | 'choose' | 'add_fiat' | 'set_currency' | 'manage' @@ -427,15 +438,19 @@ type TUiStore = { notification_messages_ui: React.ElementType; setCurrentFocus: (value: string | null) => void; setDarkMode: (is_dark_mode_on: boolean) => boolean; + setHasOnlyForwardingContracts: (has_only_forward_starting_contracts: boolean) => void; setReportsTabIndex: (value: number) => void; setIsClosingCreateRealAccountModal: (value: boolean) => void; setRealAccountSignupEnd: (status: boolean) => void; + setPurchaseState: (index: number) => void; + sub_section_index: number; setSubSectionIndex: (index: number) => void; shouldNavigateAfterChooseCrypto: (value: Omit | TRoutes) => void; toggleAccountsDialog: () => void; toggleCashier: () => void; toggleLanguageSettingsModal: () => void; toggleReadyToDepositModal: () => void; + toggleServicesErrorModal: (is_visible: boolean) => void; toggleSetCurrencyModal: () => void; is_tablet: boolean; removeToast: (key: string) => void; @@ -466,25 +481,52 @@ type TUiStore = { setResetTradingPasswordModalOpen: () => void; populateHeaderExtensions: (header_items: JSX.Element | null) => void; populateSettingsExtensions: (menu_items: Array | null) => void; + purchase_states: boolean[]; setShouldShowCooldownModal: (value: boolean) => void; vanilla_trade_type: 'VANILLALONGCALL' | 'VANILLALONGPUT'; }; +type TPortfolioPosition = { + contract_info: ProposalOpenContract & + Portfolio1 & { + contract_update?: ContractUpdate; + }; + details?: string; + display_name: string; + id?: number; + indicative: number; + payout?: number; + purchase?: number; + reference: number; + type?: string; + is_unsupported: boolean; + contract_update: ProposalOpenContract['limit_order']; + is_sell_requested: boolean; + profit_loss: number; +}; + type TPortfolioStore = { - active_positions: ProposalOpenContract[]; - error: TCommonStoreError; - getPositionById: (id: number) => ProposalOpenContract; + active_positions: TPortfolioPosition[]; + all_positions: TPortfolioPosition[]; + error: string; + getPositionById: (id: number) => TPortfolioPosition; is_loading: boolean; is_multiplier: boolean; is_accumulator: boolean; - onClickCancel: (contract_id: number) => void; - onClickSell: (contract_id: number) => void; + onClickCancel: (contract_id?: number) => void; + onClickSell: (contract_id?: number) => void; onMount: () => void; + positions: TPortfolioPosition[]; removePositionById: (id: number) => void; }; type TContractStore = { getContractById: (id: number) => ProposalOpenContract; + contract_info: TPortfolioPosition['contract_info']; + contract_update_stop_loss: string; + contract_update_take_profit: string; + has_contract_update_stop_loss: boolean; + has_contract_update_take_profit: boolean; }; type TMenuStore = { @@ -566,6 +608,11 @@ export type TCoreStores = { modules: Record; notifications: TNotificationStore; traders_hub: TTradersHubStore; + gtm: Record; + pushwoosh: Record; + contract_replay: Record; + chart_barrier_store: Record; + active_symbols: Record; }; export type TStores = TCoreStores & { diff --git a/packages/trader/src/Stores/useTraderStores.tsx b/packages/trader/src/Stores/useTraderStores.tsx index 028893d4c853..26d831268da3 100644 --- a/packages/trader/src/Stores/useTraderStores.tsx +++ b/packages/trader/src/Stores/useTraderStores.tsx @@ -63,11 +63,13 @@ type TOverrideTradeStore = Omit< | 'market_close_times' | 'market_open_times' | 'multiplier_range_list' + | 'multiplier' | 'sessions' | 'setIsTradeParamsExpanded' | 'stake_boundary' | 'start_dates_list' | 'start_time' + | 'symbol' | 'take_profit' | 'proposal_info' | 'trade_types' @@ -91,6 +93,7 @@ type TOverrideTradeStore = Omit< form_components: string[]; market_open_times: string[]; market_close_times: string[]; + multiplier: number; multiplier_range_list: number[]; proposal_info: { [key: string]: { @@ -123,6 +126,7 @@ type TOverrideTradeStore = Omit< }; start_dates_list: Array<{ text: string; value: number }>; start_time: string | null; + symbol: string; take_profit?: string; ticks_history_stats: { ticks_stayed_in?: number[]; diff --git a/packages/trader/tsconfig.json b/packages/trader/tsconfig.json index f980e4bcac9b..0bc1c7a7b7e9 100644 --- a/packages/trader/tsconfig.json +++ b/packages/trader/tsconfig.json @@ -13,11 +13,12 @@ "Services/*": ["src/Services/*"], "Stores/*": ["src/Stores/*"], "Translations/*": ["src/public/translations/*"], + "Types": ["src/Types"], "Utils/*": ["src/Utils/*"], "@deriv/*": ["../*/src"] }, "outDir": "./dist", "baseUrl": "./" }, - "include": ["src", "../../globals.d.ts", "../../utils.d.ts", "@deriv-stores.d.ts"] + "include": ["src", "../../globals.d.ts", "../../utils.d.ts"] } From 0b9147dfdb4eb29ffe48f1866645026d72959a7f Mon Sep 17 00:00:00 2001 From: Henry Hein Date: Thu, 27 Jul 2023 16:37:17 +0800 Subject: [PATCH 04/55] fix: resolve comments --- packages/components/src/components/numpad/numpad.tsx | 2 +- .../Trading/Components/Form/TradeParams/amount-mobile.tsx | 2 +- .../src/Modules/Trading/Components/Form/TradeParams/amount.tsx | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/components/src/components/numpad/numpad.tsx b/packages/components/src/components/numpad/numpad.tsx index 9a92b24c744f..67ed71a70445 100644 --- a/packages/components/src/components/numpad/numpad.tsx +++ b/packages/components/src/components/numpad/numpad.tsx @@ -23,7 +23,7 @@ type TNumpad = { render?: (props: { value: string; className: string }) => React.ReactNode; submit_label: string; value: string; - format: (v: string) => number; + format?: (v: string) => number | string; onValueChange: (val: number | string) => void; onValidate: (default_value: number | string) => boolean | 'error'; }; diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount-mobile.tsx b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount-mobile.tsx index 76b8a1a50a5e..12321e506e9c 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount-mobile.tsx +++ b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount-mobile.tsx @@ -54,7 +54,7 @@ const Basis = observer( setSelectedAmount(basis, num); validateAmount(num); }; - const formatAmount = (value: number | string): number => { + const formatAmount = (value: number | string) => { const numericValue = typeof value === 'string' ? Number(value) : value; return !isNaN(numericValue) ? parseFloat(numericValue.toFixed(user_currency_decimal_places)) : numericValue; }; diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount.tsx b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount.tsx index 53470a4ca2f9..df57e4f40279 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount.tsx +++ b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount.tsx @@ -179,8 +179,7 @@ const Amount = observer(({ is_minimized }: { is_minimized: boolean }) => { Date: Mon, 31 Jul 2023 13:43:37 +0800 Subject: [PATCH 05/55] fix: move type to common prop types --- packages/trader/src/Stores/useTraderStores.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/trader/src/Stores/useTraderStores.tsx b/packages/trader/src/Stores/useTraderStores.tsx index 26d831268da3..360914c8924a 100644 --- a/packages/trader/src/Stores/useTraderStores.tsx +++ b/packages/trader/src/Stores/useTraderStores.tsx @@ -2,11 +2,7 @@ import React from 'react'; import { useStore } from '@deriv/stores'; import TradeStore from './Modules/Trading/trade-store'; import moment from 'moment'; - -type TTextValueStrings = { - text: string; - value: string; -}; +import { TTextValueStrings } from '../../../components/src/components/types/common.types'; type TContractTypesList = { [key: string]: { From 951754617e990a40400eea82e068ae0e8299f22e Mon Sep 17 00:00:00 2001 From: Henry Hein Date: Mon, 31 Jul 2023 13:43:58 +0800 Subject: [PATCH 06/55] fix: move type to common prop types --- packages/components/src/components/types/common.types.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/components/src/components/types/common.types.ts b/packages/components/src/components/types/common.types.ts index 8b459a97081b..d62d6f643b72 100644 --- a/packages/components/src/components/types/common.types.ts +++ b/packages/components/src/components/types/common.types.ts @@ -14,3 +14,8 @@ export type TItem = { }; export type TTableRowItem = { component: React.ReactNode }; + +export type TTextValueStrings = { + text: string; + value: string; +}; From 9bf6a53d9e014e71530c0764ca889b6e3110967e Mon Sep 17 00:00:00 2001 From: Henry Hein Date: Mon, 31 Jul 2023 13:48:01 +0800 Subject: [PATCH 07/55] fix: move type to common prop types file --- packages/components/src/components/types/common.types.ts | 5 ----- packages/trader/src/Stores/useTraderStores.tsx | 2 +- packages/trader/src/Types/common-prop.type.ts | 5 +++++ 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/components/src/components/types/common.types.ts b/packages/components/src/components/types/common.types.ts index d62d6f643b72..8b459a97081b 100644 --- a/packages/components/src/components/types/common.types.ts +++ b/packages/components/src/components/types/common.types.ts @@ -14,8 +14,3 @@ export type TItem = { }; export type TTableRowItem = { component: React.ReactNode }; - -export type TTextValueStrings = { - text: string; - value: string; -}; diff --git a/packages/trader/src/Stores/useTraderStores.tsx b/packages/trader/src/Stores/useTraderStores.tsx index 360914c8924a..c745965f2f67 100644 --- a/packages/trader/src/Stores/useTraderStores.tsx +++ b/packages/trader/src/Stores/useTraderStores.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { useStore } from '@deriv/stores'; import TradeStore from './Modules/Trading/trade-store'; import moment from 'moment'; -import { TTextValueStrings } from '../../../components/src/components/types/common.types'; +import { TTextValueStrings } from '../Types/common-prop.type'; type TContractTypesList = { [key: string]: { diff --git a/packages/trader/src/Types/common-prop.type.ts b/packages/trader/src/Types/common-prop.type.ts index bc38b04c55e2..12f9f2551630 100644 --- a/packages/trader/src/Types/common-prop.type.ts +++ b/packages/trader/src/Types/common-prop.type.ts @@ -1,5 +1,10 @@ import { useTraderStore } from 'Stores/useTraderStores'; +export type TTextValueStrings = { + text: string; + value: string; +}; + export type TProposalTypeInfo = { has_error?: boolean; id: string; From 50d5a865688af82d64cae13c68830ccc27e5f1c7 Mon Sep 17 00:00:00 2001 From: Henry Hein Date: Mon, 31 Jul 2023 14:05:26 +0800 Subject: [PATCH 08/55] fix: resolve comments --- .../Trading/Components/Form/TradeParams/amount-mobile.tsx | 8 ++------ .../Trading/Components/Form/TradeParams/amount.tsx | 6 ++---- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount-mobile.tsx b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount-mobile.tsx index 12321e506e9c..5f1513e56212 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount-mobile.tsx +++ b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount-mobile.tsx @@ -54,10 +54,8 @@ const Basis = observer( setSelectedAmount(basis, num); validateAmount(num); }; - const formatAmount = (value: number | string) => { - const numericValue = typeof value === 'string' ? Number(value) : value; - return !isNaN(numericValue) ? parseFloat(numericValue.toFixed(user_currency_decimal_places)) : numericValue; - }; + const formatAmount = (value: number | string) => + !isNaN(+value) && value !== '' ? Number(value).toFixed(user_currency_decimal_places) : value; const setBasisAndAmount = (amount: number | string) => { const on_change_obj = {} as TObject; @@ -187,8 +185,6 @@ const Amount = observer( return (
- {/* - // @ts-expect-error We explicitly defined children as React.ReactElement[] here we only return a single JSX.Element or null. Dont know how to fix without breaking Tabs it's used in 27 files*/} {basis_list.map(basis_option => { switch (basis_option.value) { diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount.tsx b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount.tsx index df57e4f40279..53d8d084f9d4 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount.tsx +++ b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount.tsx @@ -81,9 +81,7 @@ const Amount = observer(({ is_minimized }: { is_minimized: boolean }) => { if (is_minimized) { return (
- - {(basis_list.find(o => o.value === basis) || {}).text} - + {basis_list.find(o => o.value === basis)?.text}   { duration_unit={duration_unit} expiry_type={expiry_type} onChange={onChange} - value={Number(is_equal)} + value={parseInt(is_equal)} has_equals_only={has_equals_only} /> {is_multiplier && ( From 7e10d2f863c63a3c99144fb8727fa4693fd6cd21 Mon Sep 17 00:00:00 2001 From: kate-deriv <121025168+kate-deriv@users.noreply.github.com> Date: Mon, 31 Jul 2023 10:24:25 +0300 Subject: [PATCH 09/55] Kate / DTRA-321 / TS migration of trade-params and trade-params-mobile (#6) * refactor: migrate trade params and started mobile version * refactor: ts migration of trade params mobile * chore: add nessasary prop * refactor: apply suggestions * chore: change todo text * refactor: add import --- .../div100vh-container/div100vh-container.tsx | 4 +- .../src/components/numpad/numpad.tsx | 4 +- .../components/src/components/tabs/tab.tsx | 20 ++-- .../components/src/components/tabs/tabs.tsx | 48 ++++---- .../Components/Elements/mobile-widget.jsx | 2 +- .../Form/TradeParams/amount-mobile.tsx | 8 +- .../Trading/Components/Form/screen-large.jsx | 2 +- .../Trading/Components/Form/screen-small.jsx | 2 +- ...ams-mobile.jsx => trade-params-mobile.tsx} | 106 +++++++++++++----- .../{trade-params.jsx => trade-params.tsx} | 17 +-- 10 files changed, 134 insertions(+), 79 deletions(-) rename packages/trader/src/Modules/Trading/Containers/{trade-params-mobile.jsx => trade-params-mobile.tsx} (75%) rename packages/trader/src/Modules/Trading/Containers/{trade-params.jsx => trade-params.tsx} (86%) diff --git a/packages/components/src/components/div100vh-container/div100vh-container.tsx b/packages/components/src/components/div100vh-container/div100vh-container.tsx index d33c67384078..f6736e66a0ed 100644 --- a/packages/components/src/components/div100vh-container/div100vh-container.tsx +++ b/packages/components/src/components/div100vh-container/div100vh-container.tsx @@ -16,7 +16,7 @@ import Div100vh from 'react-div-100vh'; type TDiv100vhContainer = { id?: string; - height_offset: string; + height_offset?: string; is_bypassed?: boolean; is_disabled?: boolean; max_height_offset?: string; @@ -30,7 +30,7 @@ const Div100vhContainer = ({ is_bypassed = false, is_disabled = false, id, - height_offset, + height_offset = '', max_autoheight_offset, }: React.PropsWithChildren) => { const height_rule = height_offset ? `calc(100rvh - ${height_offset})` : 'calc(100rvh)'; diff --git a/packages/components/src/components/numpad/numpad.tsx b/packages/components/src/components/numpad/numpad.tsx index 67ed71a70445..e7f6c2e4dfd5 100644 --- a/packages/components/src/components/numpad/numpad.tsx +++ b/packages/components/src/components/numpad/numpad.tsx @@ -22,7 +22,7 @@ type TNumpad = { v?: string; render?: (props: { value: string; className: string }) => React.ReactNode; submit_label: string; - value: string; + value: string | number; format?: (v: string) => number | string; onValueChange: (val: number | string) => void; onValidate: (default_value: number | string) => boolean | 'error'; @@ -71,7 +71,7 @@ const Numpad = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [default_value, value]); - const updateValue = (val: string) => { + const updateValue = (val: string | number) => { setValue(val); if (onValueChange) onValueChange(val); }; diff --git a/packages/components/src/components/tabs/tab.tsx b/packages/components/src/components/tabs/tab.tsx index eba22fac25bc..5c26046094d6 100644 --- a/packages/components/src/components/tabs/tab.tsx +++ b/packages/components/src/components/tabs/tab.tsx @@ -4,15 +4,15 @@ import Counter from '../counter'; import Icon from '../icon'; type TTabProps = { - active_icon_color: string; + active_icon_color?: string; active_tab_ref?: React.RefObject | null; bottom?: boolean; className?: string; count: number; header_content: React.ReactElement; header_fit_content?: boolean; - icon_color: string; - icon_size: number; + icon_color?: string; + icon_size?: number; icon: string; id?: string; is_active: boolean; @@ -25,17 +25,17 @@ type TTabProps = { }; const Tab = ({ - active_icon_color, + active_icon_color = '', active_tab_ref, - bottom, - className, + bottom = false, + className = '', count, header_content, - header_fit_content, - icon_color, - icon_size, + header_fit_content = false, + icon_color = '', + icon_size = 0, icon, - id, + id = '', is_active, is_label_hidden, is_scrollable, diff --git a/packages/components/src/components/tabs/tabs.tsx b/packages/components/src/components/tabs/tabs.tsx index c7d8f5483e77..99817c188937 100644 --- a/packages/components/src/components/tabs/tabs.tsx +++ b/packages/components/src/components/tabs/tabs.tsx @@ -14,20 +14,20 @@ declare module 'react' { } type TTabsProps = RouteComponentProps & { - active_icon_color: string; + active_icon_color?: string; active_index?: number; - background_color: string; + background_color?: string; bottom?: boolean; - center: boolean; - children: React.ReactElement[]; + center?: boolean; + children: (React.ReactElement | null)[]; className?: string; - fit_content: boolean; + fit_content?: boolean; has_active_line?: boolean; has_bottom_line?: boolean; header_fit_content?: boolean; history: History; - icon_color: string; - icon_size: number; + icon_color?: string; + icon_size?: number; is_100vw?: boolean; is_full_width?: boolean; is_overflow_hidden?: boolean; @@ -39,27 +39,27 @@ type TTabsProps = RouteComponentProps & { }; const Tabs = ({ - active_icon_color, + active_icon_color = '', active_index = 0, - background_color, - bottom, - center, + background_color = '', + bottom = false, + center = false, children, - className, - fit_content, + className = '', + fit_content = false, has_active_line = true, has_bottom_line = true, - header_fit_content, + header_fit_content = false, history, - icon_color, - icon_size, - is_100vw, - is_full_width, - is_overflow_hidden, - is_scrollable, + icon_color = '', + icon_size = 0, + is_100vw = false, + is_full_width = false, + is_overflow_hidden = false, + is_scrollable = false, onTabItemClick, - should_update_hash, - single_tab_has_no_label, + should_update_hash = false, + single_tab_has_no_label = false, top, }: TTabsProps) => { const [active_line_style, updateActiveLineStyle] = React.useState({}); @@ -99,7 +99,7 @@ const Tabs = ({ initial_index_to_show = hash_index; } else { // if no hash is in url but component has passed hash prop, set hash of the tab shown - const child_props = children[initial_index_to_show].props; + const child_props = children[initial_index_to_show]?.props; const current_id = child_props && child_props.hash; if (current_id) { pushHash(current_id); @@ -128,7 +128,7 @@ const Tabs = ({ const onClickTabItem = (index: number) => { if (should_update_hash) { - const hash = children[index].props['data-hash']; + const hash = children[index]?.props['data-hash']; pushHash(hash); } setActiveTabIndex(index); diff --git a/packages/trader/src/Modules/Trading/Components/Elements/mobile-widget.jsx b/packages/trader/src/Modules/Trading/Components/Elements/mobile-widget.jsx index 0d336ef5589f..c1bbf2cb75a4 100644 --- a/packages/trader/src/Modules/Trading/Components/Elements/mobile-widget.jsx +++ b/packages/trader/src/Modules/Trading/Components/Elements/mobile-widget.jsx @@ -3,7 +3,7 @@ import { Money } from '@deriv/components'; import { localize, Localize } from '@deriv/translations'; import { getExpiryType, getDurationMinMaxValues, getLocalizedBasis } from '@deriv/shared'; import { MultiplierAmountWidget } from 'Modules/Trading/Components/Form/TradeParams/Multiplier/widgets.jsx'; -import TradeParamsModal from '../../Containers/trade-params-mobile.jsx'; +import TradeParamsModal from '../../Containers/trade-params-mobile'; import { observer, useStore } from '@deriv/stores'; import { useTraderStore } from 'Stores/useTraderStores'; diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount-mobile.tsx b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount-mobile.tsx index 5f1513e56212..bdb0dc51ad31 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount-mobile.tsx +++ b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount-mobile.tsx @@ -13,7 +13,7 @@ type TBasis = { duration_value: number; toggleModal: () => void; has_duration_error: boolean; - selected_basis: string; + selected_basis: string | number; setSelectedAmount: (basis: string, num: string | number) => void; setAmountError: (has_error: boolean) => void; }; @@ -145,10 +145,10 @@ const Basis = observer( ); type TAmountMobile = React.ComponentProps & { - amount_tab_idx: number; + amount_tab_idx?: number; setAmountTabIdx: React.ComponentProps['onTabItemClick']; - stake_value: string; - payout_value: string; + stake_value: string | number; + payout_value: string | number; }; const Amount = observer( diff --git a/packages/trader/src/Modules/Trading/Components/Form/screen-large.jsx b/packages/trader/src/Modules/Trading/Components/Form/screen-large.jsx index 2172fb8d55ad..59773075af8a 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/screen-large.jsx +++ b/packages/trader/src/Modules/Trading/Components/Form/screen-large.jsx @@ -5,7 +5,7 @@ import { TradeParamsLoader } from 'App/Components/Elements/ContentLoader'; import Fieldset from 'App/Components/Form/fieldset.jsx'; import ContractType from '../../Containers/contract-type.jsx'; import Purchase from '../../Containers/purchase.jsx'; -import TradeParams from '../../Containers/trade-params.jsx'; +import TradeParams from '../../Containers/trade-params'; const ScreenLarge = ({ is_market_closed, is_trade_enabled }) => (
void; +}; + +type TTradeParamsMobile = { + currency: string; + toggleModal: () => void; + isVisible: (component_key: string) => boolean; + setAmountTabIdx: (amount_tab_idx?: number) => void; + amount_tab_idx?: number; + setTradeParamTabIdx: (trade_param_tab_idx: number) => void; + trade_param_tab_idx: number; + setDurationTabIdx: (duration_tab_idx?: number) => void; + duration_unit: string; + duration_units_list: TTextValueStrings[]; + duration_value: number; + duration_tab_idx?: number; + has_amount_error: boolean; + has_duration_error: boolean; + // amount + setAmountError: (has_error: boolean) => void; + setSelectedAmount: (basis: string, selected_basis_value: string | number) => void; + stake_value: number; + payout_value: number; + // duration + setDurationError: (has_error: boolean) => void; + setSelectedDuration: (selected_duration_unit: string, selected_duration: number) => void; + t_duration: number; + s_duration: number; + m_duration: number; + h_duration: number; + d_duration: number; +}; + +type TReducer = Pick< + TTradeParamsMobile, + | 'trade_param_tab_idx' + | 'duration_tab_idx' + | 'amount_tab_idx' + | 'has_amount_error' + | 'has_duration_error' + | 't_duration' + | 's_duration' + | 'm_duration' + | 'h_duration' + | 'd_duration' + | 'stake_value' + | 'payout_value' +> & { curr_duration_unit: string; curr_duration_value: number }; + const DEFAULT_DURATION = Object.freeze({ t: 5, s: 15, @@ -20,20 +70,21 @@ const DEFAULT_DURATION = Object.freeze({ d: 1, }); -const reducer = (state, payload) => { +const reducer = (state: TReducer, payload: Partial) => { return { ...state, ...payload, }; }; -const makeGetDefaultDuration = (trade_duration, trade_duration_unit) => duration_unit => - trade_duration_unit === duration_unit ? trade_duration : DEFAULT_DURATION[duration_unit]; +const makeGetDefaultDuration = (trade_duration: number, trade_duration_unit: string) => (duration_unit: string) => + trade_duration_unit === duration_unit + ? trade_duration + : DEFAULT_DURATION[duration_unit as keyof typeof DEFAULT_DURATION]; -const TradeParamsModal = observer(({ is_open, toggleModal }) => { - const { client, ui } = useStore(); +const TradeParamsModal = observer(({ is_open, toggleModal }: TTradeParamsModal) => { + const { client } = useStore(); const { currency } = client; - const { enableApp, disableApp } = ui; const { amount, form_components, duration, duration_unit, duration_units_list } = useTraderStore(); // eslint-disable-next-line react-hooks/exhaustive-deps @@ -66,15 +117,16 @@ const TradeParamsModal = observer(({ is_open, toggleModal }) => { // duration and duration_unit can be changed in trade-store when contract type is changed }, [duration, duration_unit]); - const setTradeParamTabIdx = trade_param_tab_idx => dispatch({ trade_param_tab_idx }); + const setTradeParamTabIdx = (trade_param_tab_idx: number) => dispatch({ trade_param_tab_idx }); - const setDurationTabIdx = duration_tab_idx => dispatch({ duration_tab_idx }); + const setDurationTabIdx = (duration_tab_idx?: number) => dispatch({ duration_tab_idx }); - const setAmountTabIdx = amount_tab_idx => dispatch({ amount_tab_idx }); + const setAmountTabIdx = (amount_tab_idx?: number) => dispatch({ amount_tab_idx }); - const setSelectedAmount = (basis, selected_basis_value) => dispatch({ [`${basis}_value`]: selected_basis_value }); + const setSelectedAmount = (basis: string, selected_basis_value: string | number) => + dispatch({ [`${basis}_value`]: selected_basis_value }); - const setSelectedDuration = (selected_duration_unit, selected_duration) => { + const setSelectedDuration = (selected_duration_unit: string, selected_duration: number) => { dispatch({ [`${selected_duration_unit}_duration`]: selected_duration, curr_duration_unit: selected_duration_unit, @@ -82,24 +134,21 @@ const TradeParamsModal = observer(({ is_open, toggleModal }) => { }); }; - const setAmountError = has_error => { + const setAmountError = (has_error: boolean) => { dispatch({ has_amount_error: has_error }); }; - const setDurationError = has_error => { + const setDurationError = (has_error: boolean) => { dispatch({ has_duration_error: has_error }); }; - const isVisible = component_key => form_components.includes(component_key); - + const isVisible = (component_key: string): boolean => form_components.includes(component_key); return ( } - disableApp={disableApp} toggleModal={toggleModal} height='auto' width='calc(100vw - 32px)' @@ -173,7 +222,7 @@ const TradeParamsMobile = observer( m_duration, h_duration, d_duration, - }) => { + }: TTradeParamsMobile) => { const { basis_list, basis, is_vanilla, expiry_epoch } = useTraderStore(); const getDurationText = () => { const duration = duration_units_list.find(d => d.value === duration_unit); @@ -189,7 +238,7 @@ const TradeParamsMobile = observer( return ; }; - const getHeaderContent = tab_key => { + const getHeaderContent = (tab_key: string) => { switch (tab_key) { case 'duration': return ( @@ -230,9 +279,10 @@ const TradeParamsMobile = observer( onTabItemClick={setTradeParamTabIdx} top > - {isVisible('duration') && ( + {isVisible('duration') ? (
is migrated to TS toggleModal={toggleModal} amount_tab_idx={amount_tab_idx} duration_tab_idx={duration_tab_idx} @@ -251,8 +301,8 @@ const TradeParamsMobile = observer( expiry_epoch={expiry_epoch} />
- )} - {isVisible('amount') && ( + ) : null} + {isVisible('amount') ? (
- )} + ) : null} ); } @@ -275,10 +327,10 @@ const TradeParamsMobile = observer( export const LastDigitMobile = observer(() => { const { form_components } = useTraderStore(); - return form_components.includes('last_digit') && ; + return form_components.includes('last_digit') ? : null; }); export const BarrierMobile = observer(() => { const { form_components } = useTraderStore(); - return form_components.includes('barrier') && ; + return form_components.includes('barrier') ? : null; }); diff --git a/packages/trader/src/Modules/Trading/Containers/trade-params.jsx b/packages/trader/src/Modules/Trading/Containers/trade-params.tsx similarity index 86% rename from packages/trader/src/Modules/Trading/Containers/trade-params.jsx rename to packages/trader/src/Modules/Trading/Containers/trade-params.tsx index 8777117ac7fe..cff70d605bbd 100644 --- a/packages/trader/src/Modules/Trading/Containers/trade-params.jsx +++ b/packages/trader/src/Modules/Trading/Containers/trade-params.tsx @@ -1,4 +1,3 @@ -import PropTypes from 'prop-types'; import React from 'react'; import classNames from 'classnames'; import Amount from 'Modules/Trading/Components/Form/TradeParams/amount'; @@ -17,14 +16,21 @@ import { observer } from '@deriv/stores'; import { useTraderStore } from 'Stores/useTraderStores'; import Fieldset from 'App/Components/Form/fieldset.jsx'; -const TradeParams = observer(({ is_minimized }) => { +type TTradeParams = { + is_minimized: boolean; +}; + +const TradeParams = observer(({ is_minimized }: TTradeParams) => { const { form_components } = useTraderStore(); - const isVisible = component_key => { + const isVisible = (component_key: string) => { return form_components.includes(component_key); }; return ( - {isVisible('duration') && } + {isVisible('duration') && ( + // @ts-expect-error: TODO: check if TS error is gone after is migrated to TS + + )} {isVisible('barrier') && } {isVisible('last_digit') && } {isVisible('accumulator') && } @@ -41,8 +47,5 @@ const TradeParams = observer(({ is_minimized }) => { ); }); -TradeParams.propTypes = { - is_minimized: PropTypes.bool, -}; export default TradeParams; From 399417bcd96edf8d342a4d2e7525266de0ddc9e3 Mon Sep 17 00:00:00 2001 From: Henry Hein Date: Mon, 31 Jul 2023 15:44:29 +0800 Subject: [PATCH 10/55] fix: circleCI error --- .../src/Modules/Trading/Components/Form/TradeParams/amount.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount.tsx b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount.tsx index 53d8d084f9d4..d2e8582fbdea 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount.tsx +++ b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount.tsx @@ -170,7 +170,7 @@ const Amount = observer(({ is_minimized }: { is_minimized: boolean }) => { duration_unit={duration_unit} expiry_type={expiry_type} onChange={onChange} - value={parseInt(is_equal)} + value={Number(is_equal)} has_equals_only={has_equals_only} /> {is_multiplier && ( From 08a31bbafd423f756d6bdaf4976fa010ea575194 Mon Sep 17 00:00:00 2001 From: Henry Hein Date: Thu, 3 Aug 2023 11:55:37 +0800 Subject: [PATCH 11/55] fix: ts migrate trade-footer-extensions.jsx --- packages/stores/src/mockStore.ts | 3 +++ packages/stores/types.ts | 3 +++ ...e-footer-extensions.jsx => trade-footer-extensions.tsx} | 7 +------ 3 files changed, 7 insertions(+), 6 deletions(-) rename packages/trader/src/App/Containers/{trade-footer-extensions.jsx => trade-footer-extensions.tsx} (88%) diff --git a/packages/stores/src/mockStore.ts b/packages/stores/src/mockStore.ts index 567f2aad3b39..5e049812c467 100644 --- a/packages/stores/src/mockStore.ts +++ b/packages/stores/src/mockStore.ts @@ -283,6 +283,7 @@ const mock = (): TStores & { is_mock: boolean } => { is_dark_mode_on: false, is_language_settings_modal_on: false, is_mobile: false, + is_positions_drawer_on: false, is_reports_visible: false, is_services_error_visible: false, is_unsupported_contract_modal_visible: false, @@ -303,6 +304,7 @@ const mock = (): TStores & { is_mock: boolean } => { setPurchaseState: jest.fn(), shouldNavigateAfterChooseCrypto: jest.fn(), toggleLanguageSettingsModal: jest.fn(), + togglePositionsDrawer: jest.fn(), toggleServicesErrorModal: jest.fn(), toggleSetCurrencyModal: jest.fn(), addToast: jest.fn(), @@ -416,6 +418,7 @@ const mock = (): TStores & { is_mock: boolean } => { }, portfolio: { active_positions: [], + active_positions_count: 0, all_positions: [], error: '', getPositionById: jest.fn(), diff --git a/packages/stores/types.ts b/packages/stores/types.ts index 2f450ff614df..758f394ab4df 100644 --- a/packages/stores/types.ts +++ b/packages/stores/types.ts @@ -430,6 +430,7 @@ type TUiStore = { is_reports_visible: boolean; is_language_settings_modal_on: boolean; is_mobile: boolean; + is_positions_drawer_on: boolean; is_services_error_visible: boolean; is_unsupported_contract_modal_visible: boolean; toggleShouldShowRealAccountsList: (value: boolean) => void; @@ -450,6 +451,7 @@ type TUiStore = { toggleAccountsDialog: () => void; toggleCashier: () => void; toggleLanguageSettingsModal: () => void; + togglePositionsDrawer: () => void; toggleReadyToDepositModal: () => void; toggleServicesErrorModal: (is_visible: boolean) => void; toggleSetCurrencyModal: () => void; @@ -520,6 +522,7 @@ type TPortfolioPosition = { type TPortfolioStore = { active_positions: TPortfolioPosition[]; + active_positions_count: number; all_positions: TPortfolioPosition[]; error: string; getPositionById: (id: number) => TPortfolioPosition; diff --git a/packages/trader/src/App/Containers/trade-footer-extensions.jsx b/packages/trader/src/App/Containers/trade-footer-extensions.tsx similarity index 88% rename from packages/trader/src/App/Containers/trade-footer-extensions.jsx rename to packages/trader/src/App/Containers/trade-footer-extensions.tsx index 702950f5ebca..832d6b2161a1 100644 --- a/packages/trader/src/App/Containers/trade-footer-extensions.jsx +++ b/packages/trader/src/App/Containers/trade-footer-extensions.tsx @@ -1,8 +1,7 @@ -import PropTypes from 'prop-types'; import React from 'react'; import { withRouter } from 'react-router-dom'; import { routes } from '@deriv/shared'; -import TogglePositions from 'App/Components/Elements/TogglePositions'; +import TogglePositions from '../Components/Elements/TogglePositions/toggle-positions'; import { observer, useStore } from '@deriv/stores'; const TradeFooterExtensions = observer(() => { @@ -40,8 +39,4 @@ const TradeFooterExtensions = observer(() => { return null; }); -TradeFooterExtensions.propTypes = { - location: PropTypes.object, -}; - export default withRouter(TradeFooterExtensions); From 028df014259e59b9802cc1d1962a927b50f5e586 Mon Sep 17 00:00:00 2001 From: Henry Hein Date: Thu, 3 Aug 2023 12:19:54 +0800 Subject: [PATCH 12/55] fix: fix import --- packages/trader/src/App/app.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/trader/src/App/app.tsx b/packages/trader/src/App/app.tsx index e1c53b91a711..ba551b3d2037 100644 --- a/packages/trader/src/App/app.tsx +++ b/packages/trader/src/App/app.tsx @@ -2,7 +2,7 @@ import React from 'react'; import Loadable from 'react-loadable'; import Routes from 'App/Containers/Routes/routes.jsx'; import TradeHeaderExtensions from 'App/Containers/trade-header-extensions'; -import TradeFooterExtensions from 'App/Containers/trade-footer-extensions.jsx'; +import TradeFooterExtensions from 'App/Containers/trade-footer-extensions'; import TradeSettingsExtensions from 'App/Containers/trade-settings-extensions'; import { NetworkStatusToastErrorPopup } from 'Modules/Trading/Containers/toast-popup.jsx'; import initStore from './init-store'; From c89ec2ddd5017a899e80546ae4bbd990877ee36f Mon Sep 17 00:00:00 2001 From: Henry Hein Date: Thu, 3 Aug 2023 14:11:00 +0800 Subject: [PATCH 13/55] fix: remove progress-slider-stream since its not used --- .../Containers/ProgressSliderStream/index.js | 1 - .../progress-slider-stream.jsx | 35 ------------------- 2 files changed, 36 deletions(-) delete mode 100644 packages/trader/src/App/Containers/ProgressSliderStream/index.js delete mode 100644 packages/trader/src/App/Containers/ProgressSliderStream/progress-slider-stream.jsx diff --git a/packages/trader/src/App/Containers/ProgressSliderStream/index.js b/packages/trader/src/App/Containers/ProgressSliderStream/index.js deleted file mode 100644 index 657c6ba7065a..000000000000 --- a/packages/trader/src/App/Containers/ProgressSliderStream/index.js +++ /dev/null @@ -1 +0,0 @@ -export default from './progress-slider-stream.jsx'; diff --git a/packages/trader/src/App/Containers/ProgressSliderStream/progress-slider-stream.jsx b/packages/trader/src/App/Containers/ProgressSliderStream/progress-slider-stream.jsx deleted file mode 100644 index 487cbc036604..000000000000 --- a/packages/trader/src/App/Containers/ProgressSliderStream/progress-slider-stream.jsx +++ /dev/null @@ -1,35 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import { ProgressSlider } from '@deriv/components'; -import { getCurrentTick } from '@deriv/shared'; -import { getCardLabels } from 'Constants/contract'; -import { observer, useStore } from '@deriv/stores'; - -const ProgressSliderStream = observer(({ contract_info }) => { - const { common, portfolio } = useStore(); - const { server_time } = common; - const { is_loading } = portfolio; - - if (!contract_info) { - return
; - } - const current_tick = contract_info.tick_count && getCurrentTick(contract_info); - - return ( - - ); -}); - -ProgressSliderStream.propTypes = { - contract_info: PropTypes.object, -}; - -export default ProgressSliderStream; From 2c2d5b42a0395969961b8d068841130d473a994d Mon Sep 17 00:00:00 2001 From: Henry Hein Date: Thu, 3 Aug 2023 14:29:25 +0800 Subject: [PATCH 14/55] fix: resolve comments --- .../src/Modules/Trading/Components/Form/TradeParams/amount.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount.tsx b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount.tsx index d2e8582fbdea..1072c899586e 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount.tsx +++ b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount.tsx @@ -1,7 +1,6 @@ import { AMOUNT_MAX_LENGTH, addComma, getDecimalPlaces } from '@deriv/shared'; import { ButtonToggle, Dropdown, InputField, Text } from '@deriv/components'; import { Localize, localize } from '@deriv/translations'; - import AllowEquals from './allow-equals'; import Fieldset from 'App/Components/Form/fieldset.jsx'; import Multiplier from './Multiplier/multiplier.jsx'; From ef2fa81670919122f8d39653786e83ba7f39dcb6 Mon Sep 17 00:00:00 2001 From: Henry Hein Date: Thu, 3 Aug 2023 14:50:45 +0800 Subject: [PATCH 15/55] fix: reset with master From 8f9b5e2b40188b20d458f61ce158bff795da2d39 Mon Sep 17 00:00:00 2001 From: Henry Hein Date: Thu, 3 Aug 2023 15:00:05 +0800 Subject: [PATCH 16/55] fix: reset with master From bb34a5efc999ac37d53b7dc62bbeb22987a117e3 Mon Sep 17 00:00:00 2001 From: Henry Hein Date: Thu, 3 Aug 2023 15:07:54 +0800 Subject: [PATCH 17/55] fix: reset with master --- .../src/components/div100vh-container/div100vh-container.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/components/src/components/div100vh-container/div100vh-container.tsx b/packages/components/src/components/div100vh-container/div100vh-container.tsx index f6736e66a0ed..d33c67384078 100644 --- a/packages/components/src/components/div100vh-container/div100vh-container.tsx +++ b/packages/components/src/components/div100vh-container/div100vh-container.tsx @@ -16,7 +16,7 @@ import Div100vh from 'react-div-100vh'; type TDiv100vhContainer = { id?: string; - height_offset?: string; + height_offset: string; is_bypassed?: boolean; is_disabled?: boolean; max_height_offset?: string; @@ -30,7 +30,7 @@ const Div100vhContainer = ({ is_bypassed = false, is_disabled = false, id, - height_offset = '', + height_offset, max_autoheight_offset, }: React.PropsWithChildren) => { const height_rule = height_offset ? `calc(100rvh - ${height_offset})` : 'calc(100rvh)'; From 8526837d8ae5d1b74dfe9d269f879d577662f282 Mon Sep 17 00:00:00 2001 From: Henry Hein Date: Thu, 3 Aug 2023 15:16:45 +0800 Subject: [PATCH 18/55] fix: reset with master --- .../bot-web-ui/src/components/dashboard/bot-notification.tsx | 2 +- .../dashboard-component/load-bot-preview/recent-footer.tsx | 4 ++-- .../src/components/dashboard/tutorial-tab/faq-content.tsx | 4 ++-- packages/bot-web-ui/src/components/download/download.tsx | 2 +- .../bot-web-ui/src/components/load-modal/recent-footer.tsx | 2 +- .../components/floating-rate/__test__/floating-rate.spec.js | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/bot-web-ui/src/components/dashboard/bot-notification.tsx b/packages/bot-web-ui/src/components/dashboard/bot-notification.tsx index 6069b63a1815..681418d8463c 100644 --- a/packages/bot-web-ui/src/components/dashboard/bot-notification.tsx +++ b/packages/bot-web-ui/src/components/dashboard/bot-notification.tsx @@ -1,8 +1,8 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { Toast } from '@deriv/components'; -import { observer } from '@deriv/stores'; import { localize } from '@deriv/translations'; +import { observer } from '@deriv/stores'; import { useDBotStore } from 'Stores/useDBotStore'; const BotNotification = observer(() => { diff --git a/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/recent-footer.tsx b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/recent-footer.tsx index f760df68f747..b916a5e66a72 100644 --- a/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/recent-footer.tsx +++ b/packages/bot-web-ui/src/components/dashboard/dashboard-component/load-bot-preview/recent-footer.tsx @@ -1,9 +1,9 @@ import React from 'react'; -import { observer } from 'mobx-react'; import { Button } from '@deriv/components'; import { localize } from '@deriv/translations'; -import { useDBotStore } from 'Stores/useDBotStore'; import './index.scss'; +import { observer } from 'mobx-react'; +import { useDBotStore } from 'Stores/useDBotStore'; const RecentFooter = observer(() => { const { load_modal } = useDBotStore(); diff --git a/packages/bot-web-ui/src/components/dashboard/tutorial-tab/faq-content.tsx b/packages/bot-web-ui/src/components/dashboard/tutorial-tab/faq-content.tsx index 0f4560471f2e..d830656c95aa 100644 --- a/packages/bot-web-ui/src/components/dashboard/tutorial-tab/faq-content.tsx +++ b/packages/bot-web-ui/src/components/dashboard/tutorial-tab/faq-content.tsx @@ -1,8 +1,8 @@ import React from 'react'; -import { Accordion, Text } from '@deriv/components'; +import { Text, Accordion } from '@deriv/components'; import { isMobile } from '@deriv/shared'; -import { observer } from '@deriv/stores'; import { localize } from '@deriv/translations'; +import { observer } from '@deriv/stores'; import { useDBotStore } from 'Stores/useDBotStore'; import { TDescription } from './tutorial-content'; diff --git a/packages/bot-web-ui/src/components/download/download.tsx b/packages/bot-web-ui/src/components/download/download.tsx index a58e87cb1519..5290186132be 100644 --- a/packages/bot-web-ui/src/components/download/download.tsx +++ b/packages/bot-web-ui/src/components/download/download.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { Button, Icon, Popover } from '@deriv/components'; -import { observer } from '@deriv/stores'; import { localize } from '@deriv/translations'; +import { observer } from '@deriv/stores'; import { useDBotStore } from 'Stores/useDBotStore'; type TDownloadProps = { diff --git a/packages/bot-web-ui/src/components/load-modal/recent-footer.tsx b/packages/bot-web-ui/src/components/load-modal/recent-footer.tsx index 4271297342ab..f41830c5cb22 100644 --- a/packages/bot-web-ui/src/components/load-modal/recent-footer.tsx +++ b/packages/bot-web-ui/src/components/load-modal/recent-footer.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { Button } from '@deriv/components'; -import { observer } from '@deriv/stores'; import { localize } from '@deriv/translations'; +import { observer } from '@deriv/stores'; import { useDBotStore } from 'Stores/useDBotStore'; const RecentFooter = observer(() => { diff --git a/packages/p2p/src/components/floating-rate/__test__/floating-rate.spec.js b/packages/p2p/src/components/floating-rate/__test__/floating-rate.spec.js index a840345989ab..e7555771f0ca 100644 --- a/packages/p2p/src/components/floating-rate/__test__/floating-rate.spec.js +++ b/packages/p2p/src/components/floating-rate/__test__/floating-rate.spec.js @@ -37,7 +37,7 @@ describe('', () => { }); expect(screen.getByText('of the market rate')).toBeInTheDocument(); - expect(screen.getAllByRole('button')).toHaveLength(2); + expect(screen.getAllByRole('button').length).toBe(2); }); it('should display error messages when error is passed as props', () => { From f926720f3f90c36d7697e6f9a8e4a3860b1038cd Mon Sep 17 00:00:00 2001 From: Henry Hein Date: Thu, 3 Aug 2023 15:33:59 +0800 Subject: [PATCH 19/55] fix: circleCI --- .../src/components/div100vh-container/div100vh-container.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/src/components/div100vh-container/div100vh-container.tsx b/packages/components/src/components/div100vh-container/div100vh-container.tsx index d33c67384078..6bcf1a60361a 100644 --- a/packages/components/src/components/div100vh-container/div100vh-container.tsx +++ b/packages/components/src/components/div100vh-container/div100vh-container.tsx @@ -16,7 +16,7 @@ import Div100vh from 'react-div-100vh'; type TDiv100vhContainer = { id?: string; - height_offset: string; + height_offset?: string; is_bypassed?: boolean; is_disabled?: boolean; max_height_offset?: string; From bc9cb56a7c41d6e74aa2ba49ac36a495c9998e42 Mon Sep 17 00:00:00 2001 From: Henry Hein Date: Fri, 4 Aug 2023 10:44:05 +0800 Subject: [PATCH 20/55] fix: togglePositions folder --- .../__tests__/toggle-positions.spec.tsx | 16 ++++++++---- .../Elements/TogglePositions/index.js | 2 +- ...mobile.jsx => toggle-positions-mobile.tsx} | 25 +++++++++++-------- ...gle-positions.jsx => toggle-positions.tsx} | 17 ++++++------- .../src/App/Containers/populate-header.jsx | 9 ++----- 5 files changed, 36 insertions(+), 33 deletions(-) rename packages/trader/src/App/Components/Elements/TogglePositions/{toggle-positions-mobile.jsx => toggle-positions-mobile.tsx} (86%) rename packages/trader/src/App/Components/Elements/TogglePositions/{toggle-positions.jsx => toggle-positions.tsx} (74%) diff --git a/packages/trader/src/App/Components/Elements/TogglePositions/__tests__/toggle-positions.spec.tsx b/packages/trader/src/App/Components/Elements/TogglePositions/__tests__/toggle-positions.spec.tsx index 19770e5e8bf3..20e13be8dd7a 100644 --- a/packages/trader/src/App/Components/Elements/TogglePositions/__tests__/toggle-positions.spec.tsx +++ b/packages/trader/src/App/Components/Elements/TogglePositions/__tests__/toggle-positions.spec.tsx @@ -1,29 +1,35 @@ import React from 'react'; import userEvent from '@testing-library/user-event'; import { render, screen } from '@testing-library/react'; -import TogglePositions from '../toggle-positions.jsx'; +import TogglePositions from '../toggle-positions'; + +const default_props = { + is_open: false, + positions_count: 0, + togglePositions: jest.fn(), +}; describe('TogglePositions component', () => { it('should have "positions-toggle--active" class when "is_open" is "true"', () => { - render(); + render(); expect(screen.getByTestId('dt_positions_toggle')).toHaveClass('positions-toggle--active'); }); it('should have "positions-toggle--has-count" class when "positions_count > 0"', () => { - render(); + render(); expect(screen.getByTestId('dt_positions_toggle')).toHaveClass('positions-toggle--has-count'); }); it('should call "togglePositions" when the user clicked on the link', () => { const mockTogglePositions = jest.fn(); - render(); + render(); const link = screen.getByTestId('dt_positions_toggle'); userEvent.click(link); expect(mockTogglePositions).toHaveBeenCalledTimes(1); }); it('should render "IcPortfolio" icon', () => { - render(); + render(); expect(screen.getByTestId('dt_icon')).toBeVisible(); }); }); diff --git a/packages/trader/src/App/Components/Elements/TogglePositions/index.js b/packages/trader/src/App/Components/Elements/TogglePositions/index.js index 117ed97b03fd..c97185e5654a 100644 --- a/packages/trader/src/App/Components/Elements/TogglePositions/index.js +++ b/packages/trader/src/App/Components/Elements/TogglePositions/index.js @@ -1,3 +1,3 @@ -import TogglePositions from './toggle-positions.jsx'; +import TogglePositions from './toggle-positions'; export default TogglePositions; diff --git a/packages/trader/src/App/Components/Elements/TogglePositions/toggle-positions-mobile.jsx b/packages/trader/src/App/Components/Elements/TogglePositions/toggle-positions-mobile.tsx similarity index 86% rename from packages/trader/src/App/Components/Elements/TogglePositions/toggle-positions-mobile.jsx rename to packages/trader/src/App/Components/Elements/TogglePositions/toggle-positions-mobile.tsx index ceaa48693b70..5d66cd6682c6 100644 --- a/packages/trader/src/App/Components/Elements/TogglePositions/toggle-positions-mobile.jsx +++ b/packages/trader/src/App/Components/Elements/TogglePositions/toggle-positions-mobile.tsx @@ -7,34 +7,39 @@ import { NavLink } from 'react-router-dom'; import EmptyPortfolioMessage from '../EmptyPortfolioMessage'; import PositionsModalCard from 'App/Components/Elements/PositionsDrawer/positions-modal-card.jsx'; import { filterByContractType } from 'App/Components/Elements/PositionsDrawer/helpers'; -import TogglePositions from './toggle-positions.jsx'; +import TogglePositions from './toggle-positions'; import { useTraderStore } from 'Stores/useTraderStores'; import { observer, useStore } from '@deriv/stores'; +type TTogglePositionsMobile = Pick< + ReturnType['portfolio'], + 'active_positions_count' | 'all_positions' | 'error' | 'onClickSell' | 'onClickCancel' +> & { + currency: ReturnType['client']['currency']; + is_empty: number; +}; + const TogglePositionsMobile = observer( ({ active_positions_count, all_positions, currency, - disableApp, - enableApp, error, is_empty, onClickSell, onClickCancel, - toggleUnsupportedContractModal, - }) => { + }: TTogglePositionsMobile) => { const { portfolio, ui } = useStore(); const { symbol, contract_type: trade_contract_type } = useTraderStore(); const { removePositionById: onClickRemove } = portfolio; - const { togglePositionsDrawer, is_positions_drawer_on } = ui; - let filtered_positions = []; + const { togglePositionsDrawer, toggleUnsupportedContractModal, is_positions_drawer_on } = ui; + let filtered_positions: TTogglePositionsMobile['all_positions'] = []; const closeModal = () => { filtered_positions.slice(0, 5).map(position => { const { contract_info } = position; if (contract_info?.is_sold) { - onClickRemove(contract_info.contract_id); + onClickRemove(contract_info.contract_id as number); } }); togglePositionsDrawer(); @@ -44,6 +49,7 @@ const TogglePositionsMobile = observer( p => p.contract_info && symbol === p.contract_info.underlying && + //@ts-expect-error filterByContractType function needs to be in typescript filterByContractType(p.contract_info, trade_contract_type) ); @@ -66,6 +72,7 @@ const TogglePositionsMobile = observer( unmountOnExit > diff --git a/packages/trader/src/App/Components/Elements/TogglePositions/toggle-positions.jsx b/packages/trader/src/App/Components/Elements/TogglePositions/toggle-positions.tsx similarity index 74% rename from packages/trader/src/App/Components/Elements/TogglePositions/toggle-positions.jsx rename to packages/trader/src/App/Components/Elements/TogglePositions/toggle-positions.tsx index c37fb05d11f0..7e7691372e11 100644 --- a/packages/trader/src/App/Components/Elements/TogglePositions/toggle-positions.jsx +++ b/packages/trader/src/App/Components/Elements/TogglePositions/toggle-positions.tsx @@ -1,10 +1,15 @@ import classNames from 'classnames'; -import PropTypes from 'prop-types'; import React from 'react'; import { Icon } from '@deriv/components'; import 'Sass/app/_common/components/positions-toggle.scss'; -const TogglePositions = ({ positions_count, is_open, togglePositions }) => { +type TTogglePositions = { + positions_count: number; + is_open: boolean; + togglePositions: () => void; +}; + +const TogglePositions = ({ positions_count, is_open, togglePositions }: TTogglePositions) => { const positions_toggle_class = classNames('positions-toggle', { 'positions-toggle--active': is_open, 'positions-toggle--has-count': positions_count > 0, @@ -22,12 +27,4 @@ const TogglePositions = ({ positions_count, is_open, togglePositions }) => { ); }; -TogglePositions.propTypes = { - is_open: PropTypes.bool, - is_positions_drawer_on: PropTypes.bool, - positions_count: PropTypes.number, - togglePositions: PropTypes.func, - togglePositionsDrawer: PropTypes.func, -}; - export default TogglePositions; diff --git a/packages/trader/src/App/Containers/populate-header.jsx b/packages/trader/src/App/Containers/populate-header.jsx index 1690f023037a..85f58563ab2c 100644 --- a/packages/trader/src/App/Containers/populate-header.jsx +++ b/packages/trader/src/App/Containers/populate-header.jsx @@ -1,20 +1,18 @@ import React from 'react'; -import TogglePositionsMobile from 'App/Components/Elements/TogglePositions/toggle-positions-mobile.jsx'; +import TogglePositionsMobile from 'App/Components/Elements/TogglePositions/toggle-positions-mobile'; import { filterByContractType } from 'App/Components/Elements/PositionsDrawer/helpers'; import { useTraderStore } from 'Stores/useTraderStores'; import { observer, useStore } from '@deriv/stores'; const PopulateHeader = observer(() => { - const { portfolio, ui, client } = useStore(); + const { portfolio, client } = useStore(); const { symbol, contract_type: trade_contract_type } = useTraderStore(); const { currency: positions_currency } = client; - const { disableApp, enableApp } = ui; const { active_positions_count, all_positions: positions, error: positions_error, onClickSell: onPositionsSell, - removePositionById: onPositionsRemove, onClickCancel: onPositionsCancel, } = portfolio; @@ -30,12 +28,9 @@ const PopulateHeader = observer(() => { active_positions_count={active_positions_count} all_positions={positions} currency={positions_currency} - disableApp={disableApp} is_empty={!symbol_positions.length} - enableApp={enableApp} error={positions_error} onClickSell={onPositionsSell} - onClickRemove={onPositionsRemove} onClickCancel={onPositionsCancel} /> ); From cd89b3889dd42073d79f8a06cedb2f836c802e49 Mon Sep 17 00:00:00 2001 From: Henry Hein Date: Fri, 4 Aug 2023 15:42:47 +0800 Subject: [PATCH 21/55] fix: dtra-346 marketisclosed and marketcountdowntimer migration --- .../Elements/market-countdown-timer.jsx | 209 ---------------- .../Elements/market-countdown-timer.tsx | 224 ++++++++++++++++++ ...erlay.jsx => market-is-closed-overlay.tsx} | 31 ++- .../src/Modules/Trading/Containers/trade.jsx | 2 +- 4 files changed, 244 insertions(+), 222 deletions(-) delete mode 100644 packages/trader/src/App/Components/Elements/market-countdown-timer.jsx create mode 100644 packages/trader/src/App/Components/Elements/market-countdown-timer.tsx rename packages/trader/src/App/Components/Elements/{market-is-closed-overlay.jsx => market-is-closed-overlay.tsx} (68%) diff --git a/packages/trader/src/App/Components/Elements/market-countdown-timer.jsx b/packages/trader/src/App/Components/Elements/market-countdown-timer.jsx deleted file mode 100644 index 833af29df84f..000000000000 --- a/packages/trader/src/App/Components/Elements/market-countdown-timer.jsx +++ /dev/null @@ -1,209 +0,0 @@ -import classNames from 'classnames'; -import React from 'react'; -import PropTypes from 'prop-types'; -import moment from 'moment'; -import { Text } from '@deriv/components'; -import { useIsMounted, WS, convertTimeFormat, isMarketClosed } from '@deriv/shared'; -import { Localize } from '@deriv/translations'; -import { observer } from '@deriv/stores'; -import { useTraderStore } from 'Stores/useTraderStores'; - -// check market in coming 7 days -const days_to_check_before_exit = 7; - -const getTradingTimes = async target_time => { - const data = await WS.tradingTimes(target_time); - if (data.error) { - return { api_initial_load_error: data.error.message }; - } - return data; -}; -// eslint-disable-next-line consistent-return -const getSymbol = (target_symbol, trading_times) => { - let symbol; - const { markets } = trading_times; - for (let i = 0; i < markets.length; i++) { - const { submarkets } = markets[i]; - for (let j = 0; j < submarkets.length; j++) { - const { symbols } = submarkets[j]; - symbol = symbols.find(item => item.symbol === target_symbol); - if (symbol !== undefined) return symbol; - } - } -}; - -const calculateTimeLeft = remaining_time_to_open => { - const difference = remaining_time_to_open - Date.now(); - return difference > 0 - ? { - days: Math.floor(difference / (1000 * 60 * 60 * 24)), - hours: Math.floor((difference / (1000 * 60 * 60)) % 24), - minutes: Math.floor((difference / 1000 / 60) % 60), - seconds: Math.floor((difference / 1000) % 60), - } - : {}; -}; - -const MarketCountdownTimer = observer(({ is_main_page, setIsTimerLoading, onMarketOpen, symbol }) => { - const { active_symbols } = useTraderStore(); - const isMounted = useIsMounted(); - const [when_market_opens, setWhenMarketOpens] = React.useState({}); - const [time_left, setTimeLeft] = React.useState(calculateTimeLeft(when_market_opens?.remaining_time_to_open)); - const [is_loading, setLoading] = React.useState(true); - - React.useEffect(() => { - if (!is_main_page || (is_main_page && isMarketClosed(active_symbols, symbol))) { - setLoading(true); - // eslint-disable-next-line consistent-return - const whenMarketOpens = async (days_offset, target_symbol) => { - // days_offset is 0 for today, 1 for tomorrow, etc. - if (days_offset > days_to_check_before_exit) return {}; - let remaining_time_to_open; - const target_date = moment(new Date()).add(days_offset, 'days'); - const api_response = await getTradingTimes(target_date.format('YYYY-MM-DD')); - if (!api_response.api_initial_load_error) { - const { times } = getSymbol(target_symbol, api_response.trading_times); - const { open, close } = times; - const is_closed_all_day = open?.length === 1 && open[0] === '--' && close[0] === '--'; - if (is_closed_all_day) { - // check tomorrow trading times - return whenMarketOpens(days_offset + 1, target_symbol); - } - const date_str = target_date.toISOString().substring(0, 11); - const getUTCDate = hour => new Date(`${date_str}${hour}Z`); - for (let i = 0; i < open?.length; i++) { - const diff = +getUTCDate(open[i]) - Date.now(); - if (diff > 0) { - remaining_time_to_open = +getUTCDate(open[i]); - if (isMounted() && target_symbol === symbol) { - return setWhenMarketOpens({ - days_offset, - opening_time: open[i], - remaining_time_to_open, - }); - } - } - } - whenMarketOpens(days_offset + 1, target_symbol); - } - }; - - whenMarketOpens(0, symbol); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [symbol]); - - React.useEffect(() => { - let timer; - if (when_market_opens?.remaining_time_to_open) { - timer = setTimeout(() => { - setTimeLeft(calculateTimeLeft(when_market_opens.remaining_time_to_open)); - if (new Date(when_market_opens.remaining_time_to_open) - +new Date() < 1000) { - setLoading(true); - if (is_main_page) onMarketOpen(false); - } - }, 1000); - } - return () => { - if (timer) { - clearTimeout(timer); - } - }; - }, [time_left, when_market_opens, onMarketOpen, is_main_page]); - - React.useEffect(() => { - if (!is_loading) setIsTimerLoading(false); - }, [is_loading, setIsTimerLoading]); - - let timer_components = ''; - - if (Object.keys(time_left).length) { - const hours = (time_left.days * 24 + time_left.hours).toString().padStart(2, '0'); - const minutes = time_left.minutes.toString().padStart(2, '0'); - const seconds = time_left.seconds.toString().padStart(2, '0'); - timer_components = `${hours}:${minutes}:${seconds}`; - } - - if (!(when_market_opens && timer_components)) return null; - - const { opening_time, days_offset } = when_market_opens; - let opening_time_banner = null; - if (opening_time) { - const formatted_opening_time = convertTimeFormat(opening_time); - const target_date = moment(new Date()).add(days_offset, 'days'); - const opening_date = target_date.format('DD MMM YYYY'); - const opening_day = target_date.format('dddd'); - opening_time_banner = ( - - ]} - values={{ - formatted_opening_time, - opening_day, - opening_date, - }} - /> - - ); - } - - if (is_loading) setLoading(false); - - return ( - - - - - {opening_time_banner} - - - - - {timer_components} - -
- - ); -}); - -MarketCountdownTimer.propTypes = { - is_main_page: PropTypes.bool, - setIsTimerLoading: PropTypes.func, - onMarketOpen: PropTypes.func, - symbol: PropTypes.string.isRequired, -}; - -export default MarketCountdownTimer; diff --git a/packages/trader/src/App/Components/Elements/market-countdown-timer.tsx b/packages/trader/src/App/Components/Elements/market-countdown-timer.tsx new file mode 100644 index 000000000000..799fd054712b --- /dev/null +++ b/packages/trader/src/App/Components/Elements/market-countdown-timer.tsx @@ -0,0 +1,224 @@ +import classNames from 'classnames'; +import React from 'react'; +import moment from 'moment'; +import { Text } from '@deriv/components'; +import { useIsMounted, WS, convertTimeFormat, isMarketClosed } from '@deriv/shared'; +import { Localize } from '@deriv/translations'; +import { observer } from '@deriv/stores'; +import { useTraderStore } from 'Stores/useTraderStores'; +import { TradingTimesRequest, TradingTimesResponse } from '@deriv/api-types'; + +type TMarketCountDownTimer = { + is_main_page: boolean; + setIsTimerLoading: React.Dispatch>; + onMarketOpen: ReturnType['prepareTradeStore']; + symbol: ReturnType['symbol']; +}; + +type TWhenMarketOpens = { + days_offset: number; + opening_time: string; + remaining_time_to_open: number; +}; + +// check market in coming 7 days +const days_to_check_before_exit = 7; + +const getTradingTimes = async (target_time: TradingTimesRequest['trading_times']) => { + const data = await WS.tradingTimes(target_time); + if (data.error) { + return { api_initial_load_error: data.error.message }; + } + return data; +}; +// eslint-disable-next-line consistent-return +const getSymbol = ( + target_symbol: string, + trading_times: NonNullable> +) => { + let symbol; + const { markets } = trading_times; + for (let i = 0; i < markets.length; i++) { + const { submarkets } = markets[i]; + if (submarkets) { + for (let j = 0; j < submarkets.length; j++) { + const { symbols } = submarkets[j]; + symbol = symbols?.find(item => item.symbol === target_symbol); + if (symbol !== undefined) return symbol; + } + } + } +}; + +const calculateTimeLeft = (remaining_time_to_open: number) => { + const difference = remaining_time_to_open - Date.now(); + return difference > 0 + ? { + days: Math.floor(difference / (1000 * 60 * 60 * 24)), + hours: Math.floor((difference / (1000 * 60 * 60)) % 24), + minutes: Math.floor((difference / 1000 / 60) % 60), + seconds: Math.floor((difference / 1000) % 60), + } + : {}; +}; + +const MarketCountdownTimer = observer( + ({ is_main_page, setIsTimerLoading, onMarketOpen, symbol }: TMarketCountDownTimer) => { + const { active_symbols } = useTraderStore(); + const isMounted = useIsMounted(); + const [when_market_opens, setWhenMarketOpens] = React.useState({} as TWhenMarketOpens); + const [time_left, setTimeLeft] = React.useState(calculateTimeLeft(when_market_opens?.remaining_time_to_open)); + const [is_loading, setLoading] = React.useState(true); + + React.useEffect(() => { + if (!is_main_page || (is_main_page && isMarketClosed(active_symbols, symbol))) { + setLoading(true); + // eslint-disable-next-line consistent-return + // @ts-expect-error there is no explict return type because of if statements + const whenMarketOpens = async (days_offset: number, target_symbol: string) => { + // days_offset is 0 for today, 1 for tomorrow, etc. + if (days_offset > days_to_check_before_exit) return {}; + let remaining_time_to_open; + const target_date = moment(new Date()).add(days_offset, 'days'); + const api_response = await getTradingTimes(target_date.format('YYYY-MM-DD')); + if (!api_response.api_initial_load_error) { + const returned_symbol = getSymbol(target_symbol, api_response.trading_times); + const open = returned_symbol?.times.open as string[]; + const close = returned_symbol?.times.close as string[]; + const is_closed_all_day = open?.length === 1 && open[0] === '--' && close[0] === '--'; + if (is_closed_all_day) { + // check tomorrow trading times + return whenMarketOpens(days_offset + 1, target_symbol); + } + const date_str = target_date.toISOString().substring(0, 11); + const getUTCDate = (hour: string) => new Date(`${date_str}${hour}Z`); + for (let i = 0; i < open?.length; i++) { + const diff = +getUTCDate(open[i]) - Date.now(); + if (diff > 0) { + remaining_time_to_open = +getUTCDate(open[i]); + if (isMounted() && target_symbol === symbol) { + return setWhenMarketOpens({ + days_offset, + opening_time: open[i], + remaining_time_to_open, + }); + } + } + } + whenMarketOpens(days_offset + 1, target_symbol); + } + }; + + whenMarketOpens(0, symbol); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [symbol]); + + React.useEffect(() => { + let timer: ReturnType; + if (when_market_opens?.remaining_time_to_open) { + timer = setTimeout(() => { + setTimeLeft(calculateTimeLeft(when_market_opens.remaining_time_to_open)); + if (+new Date(when_market_opens.remaining_time_to_open) - +new Date() < 1000) { + setLoading(true); + if (is_main_page) onMarketOpen(false); + } + }, 1000); + } + return () => { + if (timer) { + clearTimeout(timer); + } + }; + }, [time_left, when_market_opens, onMarketOpen, is_main_page]); + + React.useEffect(() => { + if (!is_loading) setIsTimerLoading(false); + }, [is_loading, setIsTimerLoading]); + + let timer_components = ''; + + if (Object.keys(time_left).length && time_left.days) { + const hours = (time_left.days * 24 + time_left.hours).toString().padStart(2, '0'); + const minutes = time_left.minutes.toString().padStart(2, '0'); + const seconds = time_left.seconds.toString().padStart(2, '0'); + timer_components = `${hours}:${minutes}:${seconds}`; + } + + if (!(when_market_opens && timer_components)) return null; + + const { opening_time, days_offset } = when_market_opens; + let opening_time_banner = null; + if (opening_time) { + const formatted_opening_time = convertTimeFormat(opening_time); + const target_date = moment(new Date()).add(days_offset, 'days'); + const opening_date = target_date.format('DD MMM YYYY'); + const opening_day = target_date.format('dddd'); + opening_time_banner = ( + + ]} + values={{ + formatted_opening_time, + opening_day, + opening_date, + }} + /> + + ); + } + + if (is_loading) setLoading(false); + + return ( + + + + + {opening_time_banner} + + + + + {timer_components} + +
+ + ); + } +); + +export default MarketCountdownTimer; diff --git a/packages/trader/src/App/Components/Elements/market-is-closed-overlay.jsx b/packages/trader/src/App/Components/Elements/market-is-closed-overlay.tsx similarity index 68% rename from packages/trader/src/App/Components/Elements/market-is-closed-overlay.jsx rename to packages/trader/src/App/Components/Elements/market-is-closed-overlay.tsx index a68ab239d67c..e2c58ab7180b 100644 --- a/packages/trader/src/App/Components/Elements/market-is-closed-overlay.jsx +++ b/packages/trader/src/App/Components/Elements/market-is-closed-overlay.tsx @@ -1,14 +1,29 @@ import classNames from 'classnames'; import React from 'react'; -import PropTypes from 'prop-types'; import { Button, Text } from '@deriv/components'; import { localize, Localize } from '@deriv/translations'; -import MarketCountdownTimer from './market-countdown-timer.jsx'; +import MarketCountdownTimer from './market-countdown-timer'; +import { useStore } from '@deriv/stores'; +import { useTraderStore } from 'Stores/useTraderStores.js'; -const MarketIsClosedOverlay = ({ is_eu, is_synthetics_trading_market_available, onClick, onMarketOpen, symbol }) => { +type TMarketIsClosedOverlay = { + is_eu: ReturnType['client']['is_eu']; + is_synthetics_trading_market_available: ReturnType['is_synthetics_trading_market_available']; + onClick: () => void; + onMarketOpen: React.ComponentProps['onMarketOpen']; + symbol: ReturnType['symbol']; +}; + +const MarketIsClosedOverlay = ({ + is_eu, + is_synthetics_trading_market_available, + onClick, + onMarketOpen, + symbol, +}: TMarketIsClosedOverlay) => { const [is_timer_loading, setIsTimerLoading] = React.useState(true); - let message = ( + let message: JSX.Element | null = ( ); let btn_lbl = localize('Try Synthetic Indices'); @@ -45,12 +60,4 @@ const MarketIsClosedOverlay = ({ is_eu, is_synthetics_trading_market_available, ); }; -MarketIsClosedOverlay.propTypes = { - is_eu: PropTypes.bool, - is_synthetics_trading_market_available: PropTypes.bool, - onClick: PropTypes.func, - onMarketOpen: PropTypes.func, - symbol: PropTypes.string, -}; - export default MarketIsClosedOverlay; diff --git a/packages/trader/src/Modules/Trading/Containers/trade.jsx b/packages/trader/src/Modules/Trading/Containers/trade.jsx index 5a18ce1abc37..049c78217273 100644 --- a/packages/trader/src/Modules/Trading/Containers/trade.jsx +++ b/packages/trader/src/Modules/Trading/Containers/trade.jsx @@ -4,7 +4,7 @@ import { DesktopWrapper, Div100vhContainer, MobileWrapper, SwipeableWrapper } fr import { getDecimalPlaces, isDesktop, isMobile } from '@deriv/shared'; import ChartLoader from 'App/Components/Elements/chart-loader.jsx'; import PositionsDrawer from 'App/Components/Elements/PositionsDrawer'; -import MarketIsClosedOverlay from 'App/Components/Elements/market-is-closed-overlay.jsx'; +import MarketIsClosedOverlay from 'App/Components/Elements/market-is-closed-overlay'; import Test from './test.jsx'; import { ChartBottomWidgets, ChartTopWidgets, DigitsWidget } from './chart-widgets.jsx'; import FormLayout from '../Components/Form/form-layout.jsx'; From 8a31dd407167738aefc11783294196b232e22543 Mon Sep 17 00:00:00 2001 From: Henry Hein Date: Mon, 7 Aug 2023 11:44:06 +0800 Subject: [PATCH 22/55] fix: bug --- .../App/Components/Elements/market-countdown-timer.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/trader/src/App/Components/Elements/market-countdown-timer.tsx b/packages/trader/src/App/Components/Elements/market-countdown-timer.tsx index 799fd054712b..8a816afc4255 100644 --- a/packages/trader/src/App/Components/Elements/market-countdown-timer.tsx +++ b/packages/trader/src/App/Components/Elements/market-countdown-timer.tsx @@ -138,10 +138,10 @@ const MarketCountdownTimer = observer( let timer_components = ''; - if (Object.keys(time_left).length && time_left.days) { - const hours = (time_left.days * 24 + time_left.hours).toString().padStart(2, '0'); - const minutes = time_left.minutes.toString().padStart(2, '0'); - const seconds = time_left.seconds.toString().padStart(2, '0'); + if (Object.keys(time_left).length) { + const hours = ((time_left.days ?? 0) * 24 + (time_left.hours ?? 0)).toString().padStart(2, '0'); + const minutes = (time_left.minutes ?? 0).toString().padStart(2, '0'); + const seconds = (time_left.seconds ?? 0).toString().padStart(2, '0'); timer_components = `${hours}:${minutes}:${seconds}`; } From 5e341bdcc7d8fb22a6b3a2bc61a9d788b0360e32 Mon Sep 17 00:00:00 2001 From: Maryia <103177211+maryia-deriv@users.noreply.github.com> Date: Wed, 9 Aug 2023 10:02:35 +0300 Subject: [PATCH 23/55] Maryia/dtra-270/TS migration: trade-store (#5) * chore: prepare for migration * chore: add more types to trade-store * chore: add more types to trade-store * chore: add more types to trade-store * chore: add more types to trade-store * chore: migrated trade-store to ts * chore: improve types in trade-store * fix: type * revert: hooks package installation * refactor: address review --- package-lock.json | 17 + .../utils/digital-options/digital-options.ts | 6 +- packages/shared/src/utils/helpers/duration.ts | 4 +- packages/trader/package.json | 1 + .../Form/TradeParams/amount-mobile.tsx | 11 +- .../src/Modules/Trading/Containers/trade.jsx | 2 - .../{trade-store.js => trade-store.ts} | 463 +++++++++++------- .../trader/src/Stores/useTraderStores.tsx | 138 +----- 8 files changed, 310 insertions(+), 332 deletions(-) rename packages/trader/src/Stores/Modules/Trading/{trade-store.js => trade-store.ts} (82%) diff --git a/package-lock.json b/package-lock.json index 72419937639c..9debdbccfd60 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37,6 +37,7 @@ "@types/js-cookie": "^3.0.1", "@types/jsdom": "^20.0.0", "@types/loadjs": "^4.0.1", + "@types/lodash.debounce": "^4.0.7", "@types/lodash.merge": "^4.6.7", "@types/lodash.throttle": "^4.1.7", "@types/object.fromentries": "^2.0.0", @@ -15933,6 +15934,14 @@ "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.196.tgz", "integrity": "sha512-22y3o88f4a94mKljsZcanlNWPzO0uBsBdzLAngf2tp533LzZcQzb6+eZPJ+vCTt+bqF2XnvT9gejTLsAcJAJyQ==" }, + "node_modules/@types/lodash.debounce": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@types/lodash.debounce/-/lodash.debounce-4.0.7.tgz", + "integrity": "sha512-X1T4wMZ+gT000M2/91SYj0d/7JfeNZ9PeeOldSNoE/lunLeQXKvkmIumI29IaKMotU/ln/McOIvgzZcQ/3TrSA==", + "dependencies": { + "@types/lodash": "*" + } + }, "node_modules/@types/lodash.merge": { "version": "4.6.7", "resolved": "https://registry.npmjs.org/@types/lodash.merge/-/lodash.merge-4.6.7.tgz", @@ -60842,6 +60851,14 @@ "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.196.tgz", "integrity": "sha512-22y3o88f4a94mKljsZcanlNWPzO0uBsBdzLAngf2tp533LzZcQzb6+eZPJ+vCTt+bqF2XnvT9gejTLsAcJAJyQ==" }, + "@types/lodash.debounce": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@types/lodash.debounce/-/lodash.debounce-4.0.7.tgz", + "integrity": "sha512-X1T4wMZ+gT000M2/91SYj0d/7JfeNZ9PeeOldSNoE/lunLeQXKvkmIumI29IaKMotU/ln/McOIvgzZcQ/3TrSA==", + "requires": { + "@types/lodash": "*" + } + }, "@types/lodash.merge": { "version": "4.6.7", "resolved": "https://registry.npmjs.org/@types/lodash.merge/-/lodash.merge-4.6.7.tgz", diff --git a/packages/shared/src/utils/digital-options/digital-options.ts b/packages/shared/src/utils/digital-options/digital-options.ts index 882b3f7037eb..a1a43602bf27 100644 --- a/packages/shared/src/utils/digital-options/digital-options.ts +++ b/packages/shared/src/utils/digital-options/digital-options.ts @@ -10,7 +10,7 @@ type TShowError = { message: string; header: string; redirect_label: string; - redirectOnClick: (() => void) | null; + redirectOnClick?: (() => void) | null; should_show_refresh: boolean; redirect_to: string; should_clear_error_on_click: boolean; @@ -25,8 +25,8 @@ type TAccounts = { export const showDigitalOptionsUnavailableError = ( showError: (t: TShowError) => void, message: TMessage, - redirectOnClick: (() => void) | null, - should_redirect: boolean, + redirectOnClick?: (() => void) | null, + should_redirect = false, should_clear_error_on_click = true ) => { const { title, text, link } = message; diff --git a/packages/shared/src/utils/helpers/duration.ts b/packages/shared/src/utils/helpers/duration.ts index 84d47c169c9a..be2579641b3f 100644 --- a/packages/shared/src/utils/helpers/duration.ts +++ b/packages/shared/src/utils/helpers/duration.ts @@ -163,10 +163,10 @@ export const hasIntradayDurationUnit = (duration_units_list: TUnit[]) => { * On switching symbols, end_time value of volatility indices should be set to today * * @param {String} symbol - * @param {String} expiry_type + * @param {String | null} expiry_type * @returns {*} */ -export const resetEndTimeOnVolatilityIndices = (symbol: string, expiry_type: string) => +export const resetEndTimeOnVolatilityIndices = (symbol: string, expiry_type: string | null) => /^R_/.test(symbol) && expiry_type === 'endtime' ? toMoment(null).format('DD MMM YYYY') : null; export const getDurationMinMaxValues = ( diff --git a/packages/trader/package.json b/packages/trader/package.json index 612d662a0b07..26d5892a4932 100644 --- a/packages/trader/package.json +++ b/packages/trader/package.json @@ -35,6 +35,7 @@ "devDependencies": { "@babel/eslint-parser": "^7.17.0", "@babel/preset-react": "^7.16.7", + "@types/lodash.debounce": "^4.0.7", "@types/react": "^18.0.7", "@types/react-dom": "^18.0.0", "babel-loader": "^8.1.0", diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount-mobile.tsx b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount-mobile.tsx index bdb0dc51ad31..f18ca1a63434 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount-mobile.tsx +++ b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount-mobile.tsx @@ -18,13 +18,6 @@ type TBasis = { setAmountError: (has_error: boolean) => void; }; -type TObject = { - duration_unit: string; - duration: number; - basis: string; - amount: string | number; -}; - const Basis = observer( ({ basis, @@ -57,7 +50,7 @@ const Basis = observer( const formatAmount = (value: number | string) => !isNaN(+value) && value !== '' ? Number(value).toFixed(user_currency_decimal_places) : value; const setBasisAndAmount = (amount: number | string) => { - const on_change_obj = {} as TObject; + const on_change_obj: Partial> = {}; // Check for any duration changes in Duration trade params Tab before sending onChange object if (duration_unit !== trade_duration_unit && !has_duration_error) @@ -66,7 +59,7 @@ const Basis = observer( if (amount !== trade_amount || basis !== trade_basis) { on_change_obj.basis = basis; - on_change_obj.amount = amount; + on_change_obj.amount = +amount; } if (!isEmptyObject(on_change_obj)) onChangeMultiple(on_change_obj); diff --git a/packages/trader/src/Modules/Trading/Containers/trade.jsx b/packages/trader/src/Modules/Trading/Containers/trade.jsx index 049c78217273..4b5b1a807d9e 100644 --- a/packages/trader/src/Modules/Trading/Containers/trade.jsx +++ b/packages/trader/src/Modules/Trading/Containers/trade.jsx @@ -294,7 +294,6 @@ const ChartTrade = observer(props => { wsSubscribe, active_symbols, has_alternative_source, - refToAddTick, } = useTraderStore(); const settings = { @@ -384,7 +383,6 @@ const ChartTrade = observer(props => { onExportLayout={exportLayout} shouldFetchTradingTimes={!end_epoch} hasAlternativeSource={has_alternative_source} - refToAddTick={refToAddTick} getMarketsOrder={getMarketsOrder} yAxisMargin={{ top: isMobile() ? 76 : 106, diff --git a/packages/trader/src/Stores/Modules/Trading/trade-store.js b/packages/trader/src/Stores/Modules/Trading/trade-store.ts similarity index 82% rename from packages/trader/src/Stores/Modules/Trading/trade-store.js rename to packages/trader/src/Stores/Modules/Trading/trade-store.ts index a402a1662617..15499f3116d3 100644 --- a/packages/trader/src/Stores/Modules/Trading/trade-store.js +++ b/packages/trader/src/Stores/Modules/Trading/trade-store.ts @@ -17,7 +17,6 @@ import { isMarketClosed, isMobile, pickDefaultSymbol, - removeBarrier, resetEndTimeOnVolatilityIndices, showDigitalOptionsUnavailableError, showMxMltUnavailableError, @@ -39,12 +38,146 @@ import { action, computed, makeObservable, observable, override, reaction, runIn import { createProposalRequests, getProposalErrorField, getProposalInfo } from './Helpers/proposal'; import { BARRIER_COLORS } from '../SmartChart/Constants/barriers'; import BaseStore from '../../base-store'; +import { TTextValueStrings } from '../../../Types/common-prop.type'; import { ChartBarrierStore } from '../SmartChart/chart-barrier-store'; import debounce from 'lodash.debounce'; import { setLimitOrderBarriers } from './Helpers/limit-orders'; +import type { TCoreStores } from '@deriv/stores/types'; +import { + ActiveSymbols, + ActiveSymbolsRequest, + Buy, + BuyContractResponse, + History, + PriceProposalRequest, + PriceProposalResponse, + ServerTimeRequest, + TickSpotData, + TicksHistoryRequest, + TicksHistoryResponse, + TicksStreamResponse, + TradingTimesRequest, +} from '@deriv/api-types'; + +type TBarriers = Array< + ChartBarrierStore & { + hideOffscreenBarrier?: boolean; + isSingleBarrier?: boolean; + } +>; +type TChartLayout = { + adj: boolean; + aggregationType: string; + animation?: boolean; + candleWidth: number; + chartScale: string; + chartType: string; + crosshair: number; + extended: boolean; + flipped: boolean; + interval: number; + marketSessions: Partial>; + outliers: boolean; + panels: { + chart: { + chartName: string; + display: string; + index: number; + percent: number; + yAxis: { + name: string; + position: null; + }; + yaxisLHS: string[]; + yaxisRHS: string[]; + }; + }; + periodicity: number; + previousMaxTicks?: number; + range: Partial>; + setSpan: Partial>; + studies?: Partial>; + symbols: [ + { + interval: number; + periodicity: number; + setSpan: Partial>; + symbol: string; + symbolObject: ActiveSymbols[number]; + timeUnit: string; + } + ]; + timeUnit: string; + volumeUnderlay: boolean; +}; +type TChartStateChangeOption = { symbol: string | undefined; isClosed: boolean }; +type TContractDataForGTM = PriceProposalRequest & + ReturnType & { + buy_price: number; + }; +type TPrevChartLayout = + | (TChartLayout & { + isDone?: VoidFunction; + is_used?: boolean; + }) + | null; +type TContractTypesList = { + [key: string]: { + name: string; + categories: TTextValueStrings[]; + }; +}; +type TDurationMinMax = { + [key: string]: { min: number; max: number }; +}; +type TResponse = Res & { + echo_req: Req; + error?: { + code: string; + message: string; + details?: Res[K] & { field: string }; + }; +}; +type TProposalInfo = { + [key: string]: ReturnType; +}; +type TStakeBoundary = Record< + string, + { + min_stake?: number; + max_stake?: number; + } +>; +type TTicksHistoryResponse = TicksHistoryResponse | TicksStreamResponse; +type TToastBoxListItem = { + component: JSX.Element | null; + contract_types: TTextValueStrings[]; + icon: string; + key: string; + label: string; +}; +type TToastBoxObject = { + key?: boolean; + buy_price?: string; + currency?: string; + contract_type?: string; + list?: Array; +}; +export type TValidationErrors = { [key: string]: string[] }; +export type TValidationRules = Omit>, 'duration'> & { + duration: { + rules: [ + string, + { + min: number | null; + max: number | null; + } + ][]; + }; +}; const store_name = 'trade_store'; -const g_subscribers_map = {}; // blame amin.m +const g_subscribers_map: Partial>> = {}; // blame amin.m export default class TradeStore extends BaseStore { // Control values @@ -55,52 +188,52 @@ export default class TradeStore extends BaseStore { has_equals_only = false; // Underlying - symbol; + symbol = ''; is_market_closed = false; previous_symbol = ''; - active_symbols = []; + active_symbols: ActiveSymbols = []; - form_components = []; + form_components: string[] = []; // Contract Type contract_expiry_type = ''; contract_start_type = ''; contract_type = ''; - contract_types_list = {}; - trade_types = {}; + contract_types_list: TContractTypesList = {}; + trade_types: { [key: string]: string } = {}; // Amount amount = 10; basis = ''; - basis_list = []; + basis_list: Array = []; currency = ''; - stake_boundary = { VANILLALONGCALL: {}, VANILLALONGPUT: {} }; + stake_boundary: TStakeBoundary = { VANILLALONGCALL: {}, VANILLALONGPUT: {} }; // Duration duration = 5; - duration_min_max = {}; + duration_min_max: TDurationMinMax = {}; duration_unit = ''; - duration_units_list = []; - expiry_date = ''; - expiry_epoch = ''; - expiry_time = ''; - expiry_type = 'duration'; + duration_units_list: Array = []; + expiry_date: string | null = ''; + expiry_epoch: number | string = ''; + expiry_time: string | null = ''; + expiry_type: string | null = 'duration'; // Barrier barrier_1 = ''; barrier_2 = ''; barrier_count = 0; - main_barrier = null; - barriers = []; - strike_price_choices = []; + main_barrier: ChartBarrierStore | null = null; + barriers: TBarriers = []; + strike_price_choices: string[] = []; // Start Time - start_date = Number(0); // Number(0) refers to 'now' - start_dates_list = []; - start_time = null; - sessions = []; + start_date = 0; // 0 refers to 'now' + start_dates_list: Array<{ text: string; value: number }> = []; + start_time: string | null = null; + sessions: Array<{ open: moment.Moment; close: moment.Moment }> = []; - market_open_times = []; + market_open_times: string[] = []; // End Date Time /** * An array that contains market closing time. @@ -108,43 +241,46 @@ export default class TradeStore extends BaseStore { * e.g. ["04:00:00", "08:00:00"] * */ - market_close_times = []; + market_close_times: string[] = []; // Last Digit last_digit = 5; is_mobile_digit_view_selected = false; // Purchase - proposal_info = {}; - purchase_info = {}; + proposal_info: TProposalInfo = {}; + purchase_info: Partial = {}; // Chart loader observables - is_chart_loading; + is_chart_loading?: boolean; should_show_active_symbols_loading = false; // Accumulator trade params - accumulator_range_list = []; + accumulator_range_list: number[] = []; growth_rate = 0.03; maximum_payout = 0; maximum_ticks = 0; - ticks_history_stats = {}; + ticks_history_stats: { + ticks_stayed_in?: number[]; + last_tick_epoch?: number; + } = {}; tick_size_barrier = 0; // Multiplier trade params - multiplier; - multiplier_range_list = []; - stop_loss; - take_profit; + multiplier = 0; + multiplier_range_list: number[] = []; + stop_loss?: string; + take_profit?: string; has_stop_loss = false; has_take_profit = false; has_cancellation = false; - commission; - cancellation_price; - stop_out; - expiration; - hovered_contract_type; + commission?: string | number; + cancellation_price?: number; + stop_out?: number; + expiration?: number; + hovered_contract_type?: string | null; cancellation_duration = '60m'; - cancellation_range_list = []; + cancellation_range_list: Array = []; // Vanilla trade params vanilla_trade_type = 'VANILLALONGCALL'; @@ -153,19 +289,18 @@ export default class TradeStore extends BaseStore { is_trade_params_expanded = true; //Toastbox - contract_purchase_toast_box; + contract_purchase_toast_box?: TToastBoxObject; - addTickByProposal = () => null; debouncedProposal = debounce(this.requestProposal, 500); - proposal_requests = {}; + proposal_requests: Partial> = {}; is_purchasing_contract = false; - initial_barriers; + initial_barriers?: { barrier_1: string; barrier_2: string }; is_initial_barrier_applied = false; should_skip_prepost_lifecycle = false; - constructor({ root_store }) { + constructor({ root_store }: { root_store: TCoreStores }) { const local_storage_properties = [ 'amount', 'currency', @@ -313,7 +448,6 @@ export default class TradeStore extends BaseStore { resetErrorServices: action.bound, resetPreviousSymbol: action.bound, setActiveSymbols: action.bound, - setAllowEqual: action.bound, setChartStatus: action.bound, setContractTypes: action.bound, setDefaultSymbol: action.bound, @@ -321,7 +455,6 @@ export default class TradeStore extends BaseStore { setMarketStatus: action.bound, setMobileDigitView: action.bound, setPreviousSymbol: action.bound, - setPurchaseSpotBarrier: action.bound, setSkipPrePostLifecycle: action.bound, setStakeBoundary: action.bound, setStrikeChoices: action.bound, @@ -329,7 +462,6 @@ export default class TradeStore extends BaseStore { show_digits_stats: computed, themeChangeListener: action.bound, updateBarrierColor: action.bound, - updateLimitOrderBarriers: action.bound, updateStore: action.bound, updateSymbol: action.bound, contract_purchase_toast_box: observable, @@ -371,10 +503,10 @@ export default class TradeStore extends BaseStore { () => [this.has_stop_loss, this.has_take_profit], () => { if (!this.has_stop_loss) { - this.validation_errors.stop_loss = []; + (this.validation_errors as TValidationErrors).stop_loss = []; } if (!this.has_take_profit) { - this.validation_errors.take_profit = []; + (this.validation_errors as TValidationErrors).take_profit = []; } } ); @@ -388,8 +520,8 @@ export default class TradeStore extends BaseStore { } else { // we need to remove these two validation rules on contract_type change // to be able to remove any existing Stop loss / Take profit validation errors - delete this.validation_rules.stop_loss; - delete this.validation_rules.take_profit; + delete (this.validation_rules as TValidationRules).stop_loss; + delete (this.validation_rules as TValidationRules).take_profit; } this.resetAccumulatorData(); } @@ -405,7 +537,7 @@ export default class TradeStore extends BaseStore { } ); when( - () => this.accumulator_range_list.length, + () => !!this.accumulator_range_list.length, () => this.setDefaultGrowthRate() ); } @@ -432,14 +564,14 @@ export default class TradeStore extends BaseStore { } } - setSkipPrePostLifecycle(should_skip) { + setSkipPrePostLifecycle(should_skip: boolean) { if (!!should_skip !== !!this.should_skip_prepost_lifecycle) { // to skip assignment if no change is made this.should_skip_prepost_lifecycle = should_skip; } } - setTradeStatus(status) { + setTradeStatus(status: boolean) { this.is_trade_enabled = status; } @@ -484,7 +616,7 @@ export default class TradeStore extends BaseStore { async setActiveSymbols() { const is_on_mf_account = this.root_store.client.landing_company_shortcode === 'maltainvest'; const hide_close_mx_mlt_storage_flag = !!parseInt( - localStorage.getItem('hide_close_mx_mlt_account_notification') + localStorage.getItem('hide_close_mx_mlt_account_notification') ?? '' ); const is_logged_in = this.root_store.client.is_logged_in; const clients_country = this.root_store.client.clients_country; @@ -552,7 +684,9 @@ export default class TradeStore extends BaseStore { runInAction(() => { const contract_categories = ContractType.getContractCategories(); this.processNewValuesAsync({ - ...contract_categories, + ...(contract_categories as Pick & { + has_only_forward_starting_contracts: boolean; + }), ...ContractType.getContractType(contract_categories.contract_types_list, this.contract_type), }); this.processNewValuesAsync(ContractType.getContractValues(this)); @@ -591,7 +725,7 @@ export default class TradeStore extends BaseStore { ); } - async onChangeMultiple(values) { + async onChangeMultiple(values: Partial) { Object.keys(values).forEach(name => { if (!(name in this)) { throw new Error(`Invalid Argument: ${name}`); @@ -602,7 +736,7 @@ export default class TradeStore extends BaseStore { this.validateAllProperties(); // then run validation before sending proposal } - async onChange(e) { + async onChange(e: { target: { name: string; value: unknown } }) { const { name, value } = e.target; if (name === 'symbol' && value) { // set trade params skeleton and chart loader to true until processNewValuesAsync resolves @@ -633,15 +767,11 @@ export default class TradeStore extends BaseStore { this.root_store.common.setSelectedContractType(this.contract_type); } - setPreviousSymbol(symbol) { + setPreviousSymbol(symbol: string) { if (this.previous_symbol !== symbol) this.previous_symbol = symbol; } - setAllowEqual(is_equal) { - this.is_equal = is_equal; - } - - setIsTradeParamsExpanded(value) { + setIsTradeParamsExpanded(value: boolean) { this.is_trade_params_expanded = value; } @@ -657,13 +787,13 @@ export default class TradeStore extends BaseStore { }); } - updateBarrierColor(is_dark_mode) { + updateBarrierColor(is_dark_mode: boolean) { if (this.main_barrier) { this.main_barrier.updateBarrierColor(is_dark_mode); } } - onHoverPurchase(is_over, contract_type) { + onHoverPurchase(is_over: boolean, contract_type?: string) { if (this.is_accumulator) return; if (this.is_purchase_enabled && this.main_barrier && !this.is_multiplier) { this.main_barrier.updateBarrierShade(is_over, contract_type); @@ -676,50 +806,14 @@ export default class TradeStore extends BaseStore { barriers: this.root_store.portfolio.barriers, is_over, contract_type, - contract_info: this.proposal_info[contract_type], - }); - } - - setPurchaseSpotBarrier(is_over, position) { - const key = 'PURCHASE_SPOT_BARRIER'; - if (!is_over) { - removeBarrier(this.root_store.portfolio.barriers, key); - return; - } - - let purchase_spot_barrier = this.root_store.portfolio.barriers.find(b => b.key === key); - if (purchase_spot_barrier) { - if (purchase_spot_barrier.high !== +position.contract_info.entry_spot) { - purchase_spot_barrier.onChange({ - high: position.contract_info.entry_spot, - }); - } - } else { - purchase_spot_barrier = new ChartBarrierStore(position.contract_info.entry_spot); - purchase_spot_barrier.key = key; - purchase_spot_barrier.draggable = false; - purchase_spot_barrier.hideOffscreenBarrier = true; - purchase_spot_barrier.isSingleBarrier = true; - purchase_spot_barrier.updateBarrierColor(this.root_store.ui.is_dark_mode_on); - this.barriers.push(purchase_spot_barrier); - this.root_store.portfolio.barriers.push(purchase_spot_barrier); - } - } - - updateLimitOrderBarriers(is_over, position) { - const contract_info = position.contract_info; - const { barriers } = this; - setLimitOrderBarriers({ - barriers, - contract_info, - contract_type: contract_info.contract_type, - is_over, + contract_info: this.proposal_info[contract_type ?? ''], }); } clearLimitOrderBarriers() { this.hovered_contract_type = null; const { barriers } = this; + //@ts-expect-error: TODO: check if type error is gone after limit-orders.js is migrated to ts setLimitOrderBarriers({ barriers, is_over: false, @@ -739,16 +833,16 @@ export default class TradeStore extends BaseStore { return this.root_store.portfolio.barriers && toJS(this.root_store.portfolio.barriers); } - setMainBarrier = proposal_info => { + setMainBarrier = (proposal_info: PriceProposalRequest) => { if (!proposal_info) { return; } - const { contract_type, barrier, high_barrier, low_barrier } = proposal_info; - + const { contract_type, barrier, barrier2 } = proposal_info; if (isBarrierSupported(contract_type)) { const color = this.root_store.ui.is_dark_mode_on ? BARRIER_COLORS.DARK_GRAY : BARRIER_COLORS.GRAY; // create barrier only when it's available in response - this.main_barrier = new ChartBarrierStore(barrier || high_barrier, low_barrier, this.onChartBarrierChange, { + //@ts-expect-error: TODO: check if type error is gone after ChartBarrierStore is migrated to typescript + this.main_barrier = new ChartBarrierStore(barrier, barrier2, this.onChartBarrierChange, { color, not_draggable: this.is_vanilla, }); @@ -760,14 +854,14 @@ export default class TradeStore extends BaseStore { onPurchase = debounce(this.processPurchase, 300); - processPurchase(proposal_id, price, type) { + processPurchase(proposal_id: string, price: string, type: string) { if (!this.is_purchase_enabled) return; if (proposal_id) { this.is_purchase_enabled = false; this.is_purchasing_contract = true; const is_tick_contract = this.duration_unit === 't'; processPurchase(proposal_id, price).then( - action(response => { + action((response: TResponse) => { if (!this.is_trade_component_mounted) { this.enablePurchase(); this.is_purchasing_contract = false; @@ -797,7 +891,7 @@ export default class TradeStore extends BaseStore { if (this.proposal_info[type] && this.proposal_info[type].id !== proposal_id) { throw new Error('Proposal ID does not match.'); } - const contract_data = { + const contract_data: TContractDataForGTM = { ...this.proposal_requests[type], ...this.proposal_info[type], buy_price: response.buy.buy_price, @@ -808,8 +902,8 @@ export default class TradeStore extends BaseStore { if (contract_id) { const shortcode = response.buy.shortcode; const { category, underlying } = extractInfoFromShortcode(shortcode); - const is_digit_contract = isDigitContractType(category.toUpperCase()); - const contract_type = category.toUpperCase(); + const is_digit_contract = isDigitContractType(category?.toUpperCase() ?? ''); + const contract_type = category?.toUpperCase(); this.root_store.contract_trade.addContract({ contract_id, start_time, @@ -868,10 +962,10 @@ export default class TradeStore extends BaseStore { const el_purchase_value = document.getElementsByClassName('trade-container__price-info'); const el_purchase_buttons = document.getElementsByClassName('btn-purchase'); [].forEach.bind(el_purchase_buttons, el => { - el.classList.add('btn-purchase--disabled'); + (el as HTMLButtonElement).classList.add('btn-purchase--disabled'); })(); [].forEach.bind(el_purchase_value, el => { - el.classList.add('trade-container__price-info--fade'); + (el as HTMLDivElement).classList.add('trade-container__price-info--fade'); })(); }; @@ -880,11 +974,11 @@ export default class TradeStore extends BaseStore { * @param {Object} new_state - new values to update the store with * @return {Object} returns the object having only those values that are updated */ - updateStore(new_state) { + updateStore(new_state: Partial) { Object.keys(cloneObject(new_state) || {}).forEach(key => { if (key === 'root_store' || ['validation_rules', 'validation_errors', 'currency'].indexOf(key) > -1) return; - if (JSON.stringify(this[key]) === JSON.stringify(new_state[key])) { - delete new_state[key]; + if (JSON.stringify(this[key as keyof this]) === JSON.stringify(new_state[key as keyof TradeStore])) { + delete new_state[key as keyof TradeStore]; } else { if (key === 'symbol') { this.is_purchase_enabled = false; @@ -895,7 +989,7 @@ export default class TradeStore extends BaseStore { new_state.start_date = parseInt(new_state.start_date); } - this[key] = new_state[key]; + this[key as keyof this] = new_state[key as keyof TradeStore]; // validation is done in mobx intercept (base_store.js) // when barrier_1 is set, it is compared with store.barrier_2 (which is not updated yet) @@ -908,9 +1002,9 @@ export default class TradeStore extends BaseStore { } async processNewValuesAsync( - obj_new_values = {}, + obj_new_values: Partial = {}, is_changed_by_user = false, - obj_old_values = {}, + obj_old_values: Partial | null = {}, should_forget_first = true ) { // To switch to rise_fall_equal contract type when allow equal is checked on first page refresh or @@ -944,7 +1038,7 @@ export default class TradeStore extends BaseStore { savePreviousChartMode('', null); } - if (/\bduration\b/.test(Object.keys(obj_new_values))) { + if (/\bduration\b/.test(Object.keys(obj_new_values) as unknown as string)) { // TODO: fix this in input-field.jsx if (typeof obj_new_values.duration === 'string') { obj_new_values.duration = +obj_new_values.duration; @@ -956,19 +1050,19 @@ export default class TradeStore extends BaseStore { this.forgetAllProposal(); this.proposal_requests = {}; } - if (is_changed_by_user && /\bcurrency\b/.test(Object.keys(obj_new_values))) { + if (is_changed_by_user && /\bcurrency\b/.test(Object.keys(obj_new_values) as unknown as string)) { const prev_currency = obj_old_values?.currency || this.currency; const has_currency_changed = obj_new_values.currency !== prev_currency; const should_reset_stake = - isCryptocurrency(obj_new_values.currency) || + isCryptocurrency(obj_new_values.currency ?? '') || // For switch between fiat and crypto and vice versa - isCryptocurrency(obj_new_values.currency) !== isCryptocurrency(prev_currency); + isCryptocurrency(obj_new_values.currency ?? '') !== isCryptocurrency(prev_currency); if (has_currency_changed && should_reset_stake) { - obj_new_values.amount = obj_new_values.amount || getMinPayout(obj_new_values.currency); + obj_new_values.amount = obj_new_values.amount || getMinPayout(obj_new_values.currency ?? ''); } - this.currency = obj_new_values.currency; + this.currency = obj_new_values.currency ?? ''; } let has_only_forward_starting_contracts; @@ -976,7 +1070,7 @@ export default class TradeStore extends BaseStore { if (Object.keys(obj_new_values).includes('symbol')) { this.setPreviousSymbol(this.symbol); await Symbol.onChangeSymbolAsync(obj_new_values.symbol); - this.setMarketStatus(isMarketClosed(this.active_symbols, obj_new_values.symbol)); + this.setMarketStatus(isMarketClosed(this.active_symbols, obj_new_values.symbol ?? '')); has_only_forward_starting_contracts = ContractType.getContractCategories().has_only_forward_starting_contracts; } @@ -987,7 +1081,10 @@ export default class TradeStore extends BaseStore { const new_state = this.updateStore(cloneObject(obj_new_values)); - if (is_changed_by_user || /\b(symbol|contract_types_list)\b/.test(Object.keys(new_state))) { + if ( + is_changed_by_user || + /\b(symbol|contract_types_list)\b/.test(Object.keys(new_state) as unknown as string) + ) { this.updateStore({ // disable purchase button(s), clear contract info is_purchase_enabled: false, @@ -1012,7 +1109,7 @@ export default class TradeStore extends BaseStore { ...(!this.is_initial_barrier_applied ? this.initial_barriers : {}), }); this.is_initial_barrier_applied = true; - if (/\b(contract_type|currency)\b/.test(Object.keys(new_state))) { + if (/\b(contract_type|currency)\b/.test(Object.keys(new_state) as unknown as string)) { this.validateAllProperties(); } this.debouncedProposal(); @@ -1033,11 +1130,11 @@ export default class TradeStore extends BaseStore { return isDigitTradeType(this.contract_type); } - setMobileDigitView(bool) { + setMobileDigitView(bool: boolean) { this.is_mobile_digit_view_selected = bool; } - pushPurchaseDataToGtm(contract_data) { + pushPurchaseDataToGtm(contract_data: TContractDataForGTM) { const data = { event: 'buy_contract', bom_ui: 'new', @@ -1101,11 +1198,11 @@ export default class TradeStore extends BaseStore { if (length > 0) WS.forgetAll('proposal'); } - setMarketStatus(status) { + setMarketStatus(status: boolean) { this.is_market_closed = status; } - onProposalResponse(response) { + onProposalResponse(response: TResponse) { const { contract_type } = response.echo_req; const prev_proposal_info = getPropertyValue(this.proposal_info, contract_type) || {}; const obj_prev_contract_basis = getPropertyValue(prev_proposal_info, 'obj_contract_basis') || {}; @@ -1171,10 +1268,9 @@ export default class TradeStore extends BaseStore { } if (this.hovered_contract_type === contract_type) { - this.addTickByProposal(response); setLimitOrderBarriers({ barriers: this.root_store.portfolio.barriers, - contract_info: this.proposal_info[this.hovered_contract_type], + contract_info: this.proposal_info[this.hovered_contract_type ?? ''], contract_type, is_over: true, }); @@ -1210,9 +1306,9 @@ export default class TradeStore extends BaseStore { // Sometimes the initial barrier doesn't match with current barrier choices received from API. // When this happens we want to populate the list of barrier choices to choose from since the value cannot be specified manually if (this.is_vanilla) { - const { barrier_choices, max_stake, min_stake } = response.error.details; + const { barrier_choices, max_stake, min_stake } = response.error.details ?? {}; this.setStakeBoundary(contract_type, min_stake, max_stake); - this.setStrikeChoices(barrier_choices); + this.setStrikeChoices(barrier_choices as string[]); if (!this.strike_price_choices.includes(this.barrier_1)) { // Since on change of duration `proposal` API call is made which returns a new set of barrier values. // The new list is set and the mid value is assigned @@ -1242,8 +1338,8 @@ export default class TradeStore extends BaseStore { } else { this.validateAllProperties(); if (this.is_vanilla) { - const { max_stake, min_stake, barrier_choices } = response.proposal; - this.setStrikeChoices(barrier_choices); + const { max_stake, min_stake, barrier_choices } = response.proposal ?? {}; + this.setStrikeChoices(barrier_choices as string[]); this.setStakeBoundary(contract_type, min_stake, max_stake); } } @@ -1253,15 +1349,15 @@ export default class TradeStore extends BaseStore { } } - onChartBarrierChange(barrier_1, barrier_2) { + onChartBarrierChange(barrier_1: string, barrier_2: string) { this.processNewValuesAsync({ barrier_1, barrier_2 }, true); } onAllowEqualsChange() { - this.processNewValuesAsync({ contract_type: parseInt(this.is_equal) ? 'rise_fall_equal' : 'rise_fall' }, true); + this.processNewValuesAsync({ contract_type: this.is_equal ? 'rise_fall_equal' : 'rise_fall' }, true); } - updateSymbol(underlying) { + updateSymbol(underlying: string) { if (!underlying) return; this.onChange({ target: { @@ -1273,13 +1369,15 @@ export default class TradeStore extends BaseStore { changeDurationValidationRules() { if (this.expiry_type === 'endtime') { - this.validation_errors.duration = []; + (this.validation_errors as TValidationErrors).duration = []; return; } - if (!this.validation_rules.duration) return; + if (!(this.validation_rules as TValidationRules).duration) return; - const index = this.validation_rules.duration.rules.findIndex(item => item[0] === 'number'); + const index = (this.validation_rules as TValidationRules).duration.rules.findIndex( + item => item[0] === 'number' + ); const limits = this.duration_min_max[this.contract_expiry_type] || false; if (limits) { @@ -1288,11 +1386,12 @@ export default class TradeStore extends BaseStore { max: convertDurationLimit(+limits.max, this.duration_unit), }; - if (index > -1) { - this.validation_rules.duration.rules[index][1] = duration_options; + if (Number(index) > -1) { + (this.validation_rules as TValidationRules).duration.rules[Number(index)][1] = duration_options; } else { - this.validation_rules.duration.rules.push(['number', duration_options]); + (this.validation_rules as TValidationRules).duration.rules.push(['number', duration_options]); } + //@ts-expect-error: TODO: check if TS error is gone after base-store.ts from shared package is used here instead of base-store.js this.validateProperty('duration', this.duration); } } @@ -1344,11 +1443,11 @@ export default class TradeStore extends BaseStore { return Promise.resolve(); } - networkStatusChangeListener(is_online) { + networkStatusChangeListener(is_online: boolean) { this.setTradeStatus(is_online); } - themeChangeListener(is_dark_mode_on) { + themeChangeListener(is_dark_mode_on: boolean) { this.updateBarrierColor(is_dark_mode_on); } @@ -1380,7 +1479,7 @@ export default class TradeStore extends BaseStore { manageMxMltRemovalNotification() { const { addNotificationMessage, client_notifications, notification_messages, unmarkNotificationMessage } = this.root_store.notifications; - const get_notification_messages = JSON.parse(localStorage.getItem('notification_messages')); + const get_notification_messages = JSON.parse(localStorage.getItem('notification_messages') ?? ''); const { has_iom_account, has_malta_account, is_logged_in } = this.root_store.client; unmarkNotificationMessage({ key: 'close_mx_mlt_account' }); if (get_notification_messages !== null && is_logged_in && (has_iom_account || has_malta_account)) { @@ -1388,7 +1487,7 @@ export default class TradeStore extends BaseStore { () => is_logged_in && notification_messages.length === 0, () => { const hidden_close_account_notification = - parseInt(localStorage.getItem('hide_close_mx_mlt_account_notification')) === 1; + parseInt(localStorage.getItem('hide_close_mx_mlt_account_notification') ?? '') === 1; const should_retain_notification = (has_iom_account || has_malta_account) && !hidden_close_account_notification; if (should_retain_notification) { @@ -1399,11 +1498,11 @@ export default class TradeStore extends BaseStore { } } - setChartStatus(status) { + setChartStatus(status: boolean) { this.is_chart_loading = status; } - async initAccountCurrency(new_currency) { + async initAccountCurrency(new_currency: string) { if (this.currency === new_currency) return; await this.processNewValuesAsync({ currency: new_currency }, true, { currency: this.currency }, false); @@ -1435,7 +1534,7 @@ export default class TradeStore extends BaseStore { this.resetAccumulatorData(); } - prev_chart_layout = null; + prev_chart_layout: TPrevChartLayout = null; get chart_layout() { let layout = null; @@ -1449,35 +1548,37 @@ export default class TradeStore extends BaseStore { return this.contract_type === 'multiplier' && /^cry/.test(this.symbol); } - exportLayout(layout) { + exportLayout(layout: TChartLayout) { delete layout.previousMaxTicks; // TODO: fix it in smartcharts this.prev_chart_layout = layout; - this.prev_chart_layout.isDone = () => { - this.prev_chart_layout.is_used = true; - this.setChartStatus(false); - }; + if (this.prev_chart_layout) { + this.prev_chart_layout.isDone = () => { + if (this.prev_chart_layout) this.prev_chart_layout.is_used = true; + this.setChartStatus(false); + }; + } } // ---------- WS ---------- - wsSubscribe = (req, callback) => { - const passthrough_callback = (...args) => { + wsSubscribe = (req: TicksHistoryRequest, callback: (response: TTicksHistoryResponse) => void) => { + const passthrough_callback = (...args: [TTicksHistoryResponse]) => { callback(...args); if (this.is_accumulator) { let current_spot_data = {}; if ('tick' in args[0]) { - const { epoch, quote, symbol } = args[0].tick; + const { epoch, quote, symbol } = args[0].tick as TickSpotData; if (this.symbol !== symbol) return; current_spot_data = { current_spot: quote, current_spot_time: epoch, }; } else if ('history' in args[0]) { - const { prices, times } = args[0].history; + const { prices, times } = args[0].history as History; const symbol = args[0].echo_req.ticks_history; if (this.symbol !== symbol) return; current_spot_data = { - current_spot: prices[prices.length - 1], - current_spot_time: times[times.length - 1], + current_spot: prices?.[prices?.length - 1], + current_spot_time: times?.[times?.length - 1], }; } else { return; @@ -1492,21 +1593,21 @@ export default class TradeStore extends BaseStore { } }; - wsForget = req => { + wsForget = (req: TicksHistoryRequest) => { const key = JSON.stringify(req); if (g_subscribers_map[key]) { - g_subscribers_map[key].unsubscribe(); + g_subscribers_map[key]?.unsubscribe(); delete g_subscribers_map[key]; } // WS.forget('ticks_history', callback, match); }; - wsForgetStream = stream_id => { + wsForgetStream = (stream_id: string) => { WS.forgetStream(stream_id); }; - wsSendRequest = req => { - if (req.time) { + wsSendRequest = (req: TradingTimesRequest | ActiveSymbolsRequest | ServerTimeRequest) => { + if ('time' in req) { return ServerTime.timePromise().then(server_time => { if (server_time) { return { @@ -1517,16 +1618,16 @@ export default class TradeStore extends BaseStore { return WS.time(); }); } - if (req.active_symbols) { + if ('active_symbols' in req) { return WS.activeSymbols('brief'); } - if (req.trading_times) { + if ('trading_times' in req) { return WS.tradingTimes(req.trading_times); } return WS.storage.send(req); }; - chartStateChange(state, option) { + chartStateChange(state: string, option?: TChartStateChangeOption) { const market_close_prop = 'isClosed'; switch (state) { case 'MARKET_STATE_CHANGE': @@ -1539,10 +1640,6 @@ export default class TradeStore extends BaseStore { } } - refToAddTick = ref => { - this.addTickByProposal = ref; - }; - get has_alternative_source() { return this.is_multiplier && !!this.hovered_contract_type; } @@ -1559,7 +1656,7 @@ export default class TradeStore extends BaseStore { return this.contract_type === 'vanilla'; } - setContractPurchaseToastbox(response) { + setContractPurchaseToastbox(response: Buy) { const list = getAvailableContractTypes(this.contract_types_list, unsupported_contract_types_list); return (this.contract_purchase_toast_box = { @@ -1575,7 +1672,7 @@ export default class TradeStore extends BaseStore { this.contract_purchase_toast_box = undefined; } - async getFirstOpenMarket(markets_to_search) { + async getFirstOpenMarket(markets_to_search: string[]) { if (this.active_symbols?.length) { return findFirstOpenMarket(this.active_symbols, markets_to_search); } @@ -1587,11 +1684,11 @@ export default class TradeStore extends BaseStore { return findFirstOpenMarket(active_symbols, markets_to_search); } - setStrikeChoices(strike_prices) { + setStrikeChoices(strike_prices: string[]) { this.strike_price_choices = strike_prices ?? []; } - setStakeBoundary(type, min_stake, max_stake) { + setStakeBoundary(type: string, min_stake?: number, max_stake?: number) { this.stake_boundary[type] = { min_stake, max_stake }; } } diff --git a/packages/trader/src/Stores/useTraderStores.tsx b/packages/trader/src/Stores/useTraderStores.tsx index c745965f2f67..25ea546c7b08 100644 --- a/packages/trader/src/Stores/useTraderStores.tsx +++ b/packages/trader/src/Stores/useTraderStores.tsx @@ -1,139 +1,11 @@ import React from 'react'; import { useStore } from '@deriv/stores'; -import TradeStore from './Modules/Trading/trade-store'; -import moment from 'moment'; -import { TTextValueStrings } from '../Types/common-prop.type'; +import TradeStore, { TValidationErrors, TValidationRules } from './Modules/Trading/trade-store'; -type TContractTypesList = { - [key: string]: { - name: string; - categories: TTextValueStrings[]; - }; -}; - -type TContractCategoriesList = { - Multipliers: TContractTypesList; - 'Ups & Downs': TContractTypesList; - 'Highs & Lows': TContractTypesList; - 'Ins & Outs': TContractTypesList; - 'Look Backs': TContractTypesList; - Digits: TContractTypesList; - Vanillas: TContractTypesList; - Accumulators: TContractTypesList; -}; - -type TToastBoxListItem = { - contract_category: string; - contract_types: [ - { - text: string; - value: string; - } - ]; -}; - -type TToastBoxObject = { - key?: boolean; - buy_price?: number; - currency?: string; - contract_type?: string; - list?: TToastBoxListItem[]; -}; - -type TOverrideTradeStore = Omit< - TradeStore, - | 'accumulator_range_list' - | 'barriers' - | 'basis_list' - | 'cancellation_price' - | 'cancellation_range_list' - | 'clearContractPurchaseToastBox' - | 'contract_purchase_toast_box' - | 'contract_types_list' - | 'duration_min_max' - | 'duration_units_list' - | 'expiry_date' - | 'expiry_time' - | 'expiry_type' - | 'form_components' - | 'market_close_times' - | 'market_open_times' - | 'multiplier_range_list' - | 'multiplier' - | 'sessions' - | 'setIsTradeParamsExpanded' - | 'stake_boundary' - | 'start_dates_list' - | 'start_time' - | 'symbol' - | 'take_profit' - | 'proposal_info' - | 'trade_types' - | 'ticks_history_stats' - | 'validation_errors' -> & { - accumulator_range_list?: number[]; - basis_list: Array; - cancellation_price?: number; - cancellation_range_list: Array; - clearContractPurchaseToastBox: () => void; - contract_purchase_toast_box: TToastBoxObject; - contract_types_list: TContractCategoriesList; - duration_min_max: { - [key: string]: { min: number; max: number }; - }; - duration_units_list: Array; - expiry_date: string | null; - expiry_time: string | null; - expiry_type: string | null; - form_components: string[]; - market_open_times: string[]; - market_close_times: string[]; - multiplier: number; - multiplier_range_list: number[]; - proposal_info: { - [key: string]: { - barrier?: string; - has_error?: boolean; - id: string; - has_increased?: boolean; - message?: string; - cancellation?: { - ask_price: number; - date_expiry: number; - }; - growth_rate?: number; - returns?: string; - stake: string; - spot?: string; - }; - }; - sessions: Array<{ open: moment.Moment; close: moment.Moment }>; - setIsTradeParamsExpanded: (value: boolean) => void; - stake_boundary: { - VANILLALONGCALL: { - min_stake: string; - max_stake: string; - }; - VANILLALONGPUT: { - min_stake: string; - max_stake: string; - }; - }; - start_dates_list: Array<{ text: string; value: number }>; - start_time: string | null; - symbol: string; - take_profit?: string; - ticks_history_stats: { - ticks_stayed_in?: number[]; - last_tick_epoch?: number; - }; - trade_types: { [key: string]: string }; - validation_errors?: { - amount?: string[]; - barrier_1?: string[]; - barrier_2?: string[]; - }; +type TOverrideTradeStore = Omit & { + //TODO: these types can be removed from here and trade-store after base-store is migrated to TS + validation_errors?: TValidationErrors; + validation_rules: TValidationRules; }; const TraderStoreContext = React.createContext(null); From 8c7a4b02a5f8f5841b3e7621f66bcf323902c335 Mon Sep 17 00:00:00 2001 From: Henry Hein Date: Thu, 10 Aug 2023 11:51:00 +0800 Subject: [PATCH 24/55] fix: resolve comments --- .../Elements/market-countdown-timer.tsx | 6 ++-- .../Containers/trade-footer-extensions.tsx | 5 ++-- .../src/Stores/Modules/Trading/trade-store.ts | 29 ++----------------- 3 files changed, 8 insertions(+), 32 deletions(-) diff --git a/packages/trader/src/App/Components/Elements/market-countdown-timer.tsx b/packages/trader/src/App/Components/Elements/market-countdown-timer.tsx index 8a816afc4255..03ec30b9ec2a 100644 --- a/packages/trader/src/App/Components/Elements/market-countdown-timer.tsx +++ b/packages/trader/src/App/Components/Elements/market-countdown-timer.tsx @@ -139,9 +139,9 @@ const MarketCountdownTimer = observer( let timer_components = ''; if (Object.keys(time_left).length) { - const hours = ((time_left.days ?? 0) * 24 + (time_left.hours ?? 0)).toString().padStart(2, '0'); - const minutes = (time_left.minutes ?? 0).toString().padStart(2, '0'); - const seconds = (time_left.seconds ?? 0).toString().padStart(2, '0'); + const hours = (Number(time_left.days) * 24 + Number(time_left.hours)).toString().padStart(2, '0'); + const minutes = Number(time_left.minutes).toString().padStart(2, '0'); + const seconds = Number(time_left.seconds).toString().padStart(2, '0'); timer_components = `${hours}:${minutes}:${seconds}`; } diff --git a/packages/trader/src/App/Containers/trade-footer-extensions.tsx b/packages/trader/src/App/Containers/trade-footer-extensions.tsx index 832d6b2161a1..8643c94f5987 100644 --- a/packages/trader/src/App/Containers/trade-footer-extensions.tsx +++ b/packages/trader/src/App/Containers/trade-footer-extensions.tsx @@ -1,10 +1,11 @@ import React from 'react'; -import { withRouter } from 'react-router-dom'; +import { RouteComponentProps, withRouter } from 'react-router-dom'; import { routes } from '@deriv/shared'; import TogglePositions from '../Components/Elements/TogglePositions/toggle-positions'; import { observer, useStore } from '@deriv/stores'; -const TradeFooterExtensions = observer(() => { +const TradeFooterExtensions = observer((props: RouteComponentProps) => { + const { location } = props; const { client, portfolio, ui } = useStore(); const { is_logged_in } = client; const { active_positions_count } = portfolio; diff --git a/packages/trader/src/Stores/Modules/Trading/trade-store.ts b/packages/trader/src/Stores/Modules/Trading/trade-store.ts index 7504045e2a76..d8b98a18e809 100644 --- a/packages/trader/src/Stores/Modules/Trading/trade-store.ts +++ b/packages/trader/src/Stores/Modules/Trading/trade-store.ts @@ -210,7 +210,7 @@ export default class TradeStore extends BaseStore { basis = ''; basis_list: Array = []; currency = ''; - stake_boundary: TStakeBoundary = { VANILLALONGCALL: {}, VANILLALONGPUT: {} }; + stake_boundary: Partial = {}; // Duration duration = 5; @@ -228,7 +228,6 @@ export default class TradeStore extends BaseStore { barrier_count = 0; main_barrier: ChartBarrierStore | null = null; barriers: TBarriers = []; - strike_price_choices: string[] = []; hovered_barrier = ''; barrier_choices: string[] = []; @@ -865,7 +864,7 @@ export default class TradeStore extends BaseStore { // create barrier only when it's available in response this.main_barrier = new ChartBarrierStore( - this.hovered_barrier || barrier || barrier, + this.hovered_barrier || barrier, barrier2, //@ts-expect-error need to typescript migrate chart-barrier-store.js first this.onChartBarrierChange, @@ -1317,26 +1316,6 @@ export default class TradeStore extends BaseStore { } if (this.is_accumulator) this.resetAccumulatorData(); - // Sometimes the initial barrier doesn't match with current barrier choices received from API. - // When this happens we want to populate the list of barrier choices to choose from since the value cannot be specified manually - if (this.is_vanilla) { - const { barrier_choices, max_stake, min_stake } = response.error.details ?? {}; - this.setStakeBoundary(contract_type, min_stake, max_stake); - this.setStrikeChoices(barrier_choices as string[]); - if (!this.strike_price_choices.includes(this.barrier_1)) { - // Since on change of duration `proposal` API call is made which returns a new set of barrier values. - // The new list is set and the mid value is assigned - const index = Math.floor(this.strike_price_choices.length / 2); - this.barrier_1 = this.strike_price_choices[index]; - this.onChange({ - target: { - name: 'barrier_1', - value: this.barrier_1, - }, - }); - } - } - // Sometimes when we navigate fast, `forget_all` proposal is called immediately after proposal subscription calls. // But, in the BE, `forget_all` proposal call is processed before the proposal subscriptions are registered. In this case, `forget_all` proposal doesn't forget the new subscriptions. // So when we send new proposal subscription requests, we get `AlreadySubscribed` error. @@ -1722,10 +1701,6 @@ export default class TradeStore extends BaseStore { return findFirstOpenMarket(active_symbols, markets_to_search); } - setStrikeChoices(strike_prices: string[]) { - this.strike_price_choices = strike_prices ?? []; - } - setStakeBoundary(type: string, min_stake?: number, max_stake?: number) { this.stake_boundary[type] = { min_stake, max_stake }; } From cf55989b45f9b56aca44fe89c58146af9897199c Mon Sep 17 00:00:00 2001 From: kate-deriv Date: Thu, 10 Aug 2023 12:48:30 +0300 Subject: [PATCH 25/55] feat: add ts migartion of store --- packages/stores/src/mockStore.ts | 70 ++++----- packages/stores/types.ts | 107 +++++++------- .../trader/src/Stores/useTraderStores.tsx | 136 +++++++++++++++++- packages/trader/src/Types/common-prop.type.ts | 32 +++++ packages/trader/src/Types/index.ts | 1 + packages/trader/tsconfig.json | 3 +- 6 files changed, 260 insertions(+), 89 deletions(-) create mode 100644 packages/trader/src/Types/common-prop.type.ts create mode 100644 packages/trader/src/Types/index.ts diff --git a/packages/stores/src/mockStore.ts b/packages/stores/src/mockStore.ts index ab6fd4c5366c..4a139e1ee1a4 100644 --- a/packages/stores/src/mockStore.ts +++ b/packages/stores/src/mockStore.ts @@ -2,6 +2,19 @@ import merge from 'lodash.merge'; import type { TStores } from '../types'; const mock = (): TStores & { is_mock: boolean } => { + const common_store_error = { + app_routing_history: [], + header: '', + message: '', + type: '', + redirect_label: '', + redirect_to: '', + should_clear_error_on_click: false, + should_show_refresh: false, + redirectOnClick: jest.fn(), + setError: jest.fn(), + }; + const services_error = { code: '', message: '', type: '' }; return { is_mock: true, client: { @@ -96,6 +109,7 @@ const mock = (): TStores & { is_mock: boolean } => { balance: '', can_change_fiat_currency: false, currency: '', + currencies_list: [{ text: '', value: '', has_tool_tip: false }], current_currency_type: '', current_fiat_currency: '', cfd_score: 0, @@ -120,14 +134,13 @@ const mock = (): TStores & { is_mock: boolean } => { is_logged_in: false, is_logging_in: false, is_pending_proof_of_ownership: false, + is_single_currency: false, is_switching: false, is_tnc_needed: false, is_trading_experience_incomplete: false, is_virtual: false, is_withdrawal_lock: false, is_populating_account_list: false, - is_language_loaded: false, - prev_account_type: '', landing_company_shortcode: '', local_currency_config: { currency: '', @@ -242,18 +255,7 @@ const mock = (): TStores & { is_mock: boolean } => { setPrevAccountType: jest.fn(), }, common: { - error: { - app_routing_history: [], - header: '', - message: '', - type: '', - redirect_label: '', - redirect_to: '', - should_clear_error_on_click: false, - should_show_refresh: false, - redirectOnClick: jest.fn(), - setError: jest.fn(), - }, + error: common_store_error, current_language: 'EN', isCurrentLanguage: jest.fn(), is_from_derivgo: false, @@ -264,13 +266,12 @@ const mock = (): TStores & { is_mock: boolean } => { changeCurrentLanguage: jest.fn(), changeSelectedLanguage: jest.fn(), is_network_online: false, + services_error, server_time: undefined, is_language_changing: false, - is_socket_opened: false, setAppstorePlatform: jest.fn(), app_routing_history: [], getExchangeRate: jest.fn(), - network_status: {}, }, ui: { app_contents_scroll_ref: { @@ -278,13 +279,13 @@ const mock = (): TStores & { is_mock: boolean } => { }, current_focus: null, is_cashier_visible: false, - is_app_disabled: false, is_closing_create_real_account_modal: false, is_dark_mode_on: false, is_language_settings_modal_on: false, - is_link_expired_modal_visible: false, is_mobile: false, is_reports_visible: false, + is_services_error_visible: false, + is_unsupported_contract_modal_visible: false, disableApp: jest.fn(), enableApp: jest.fn(), setCurrentFocus: jest.fn(), @@ -292,14 +293,17 @@ const mock = (): TStores & { is_mock: boolean } => { toggleCashier: jest.fn(), setDarkMode: jest.fn(), setReportsTabIndex: jest.fn(), + has_only_forward_starting_contracts: false, has_real_account_signup_ended: false, notification_messages_ui: jest.fn(), openRealAccountSignup: jest.fn(), + setHasOnlyForwardingContracts: jest.fn(), setIsClosingCreateRealAccountModal: jest.fn(), setRealAccountSignupEnd: jest.fn(), + setPurchaseState: jest.fn(), shouldNavigateAfterChooseCrypto: jest.fn(), toggleLanguageSettingsModal: jest.fn(), - toggleLinkExpiredModal: jest.fn(), + toggleServicesErrorModal: jest.fn(), toggleSetCurrencyModal: jest.fn(), addToast: jest.fn(), removeToast: jest.fn(), @@ -315,10 +319,8 @@ const mock = (): TStores & { is_mock: boolean } => { is_ready_to_deposit_modal_visible: false, is_real_acc_signup_on: false, is_need_real_account_for_cashier_modal_visible: false, - is_chart_layout_default: false, toggleNeedRealAccountForCashierModal: jest.fn(), setIsAcuityModalOpen: jest.fn(), - setAppContentsScrollRef: jest.fn(), is_switch_to_deriv_account_modal_visible: false, openSwitchToRealAccountModal: jest.fn(), is_top_up_virtual_open: false, @@ -331,14 +333,15 @@ const mock = (): TStores & { is_mock: boolean } => { openDerivRealAccountNeededModal: jest.fn(), populateHeaderExtensions: jest.fn(), populateSettingsExtensions: jest.fn(), + purchase_states: [], setShouldShowCooldownModal: jest.fn(), - populateFooterExtensions: jest.fn(), openAccountNeededModal: jest.fn(), is_accounts_switcher_on: false, openTopUpModal: jest.fn(), toggleShouldShowRealAccountsList: jest.fn(), is_reset_trading_password_modal_visible: false, setResetTradingPasswordModalOpen: jest.fn(), + vanilla_trade_type: 'VANILLALONGCALL', }, traders_hub: { closeModal: jest.fn(), @@ -350,12 +353,8 @@ const mock = (): TStores & { is_mock: boolean } => { login: '', account_id: '', }, - handleTabItemClick: jest.fn(), - is_account_transfer_modal_open: false, is_eu_user: false, is_real: false, - is_regulators_compare_modal_visible: false, - is_tour_open: false, selectRegion: jest.fn(), setSelectedAccount: jest.fn(), is_low_risk_cr_eu_real: false, @@ -382,7 +381,6 @@ const mock = (): TStores & { is_mock: boolean } => { is_demo: false, financial_restricted_countries: false, selected_account_type: 'real', - selected_platform_type: 'options', no_CR_account: false, no_MF_account: false, multipliers_account_status: '', @@ -390,10 +388,6 @@ const mock = (): TStores & { is_mock: boolean } => { setTogglePlatformType: jest.fn(), toggleAccountTransferModal: jest.fn(), selectAccountType: jest.fn(), - toggleIsTourOpen: jest.fn(), - is_demo_low_risk: false, - is_mt5_notification_modal_visible: false, - setMT5NotificationModal: jest.fn(), }, menu: { attach: jest.fn(), @@ -412,20 +406,25 @@ const mock = (): TStores & { is_mock: boolean } => { setP2PRedirectTo: jest.fn(), }, portfolio: { - positions: [], active_positions: [], + all_positions: [], error: '', getPositionById: jest.fn(), is_loading: false, is_accumulator: false, is_multiplier: false, - is_turbos: false, onClickCancel: jest.fn(), onClickSell: jest.fn(), onMount: jest.fn(), + positions: [], removePositionById: jest.fn(), }, contract_trade: { + contract_info: {}, + contract_update_stop_loss: '', + contract_update_take_profit: '', + has_contract_update_stop_loss: false, + has_contract_update_take_profit: false, getContractById: jest.fn(), }, modules: {}, @@ -439,6 +438,11 @@ const mock = (): TStores & { is_mock: boolean } => { update: jest.fn(), unmount: jest.fn(), }, + gtm: {}, + pushwoosh: {}, + contract_replay: {}, + chart_barrier_store: {}, + active_symbols: {}, }; }; diff --git a/packages/stores/types.ts b/packages/stores/types.ts index 08a3ccf2a657..fc514f7ac870 100644 --- a/packages/stores/types.ts +++ b/packages/stores/types.ts @@ -5,10 +5,10 @@ import type { DetailsOfEachMT5Loginid, GetAccountStatus, GetLimits, + Portfolio1, GetSettings, LandingCompany, LogOutResponse, - Portfolio1, ProposalOpenContract, ResidenceList, SetFinancialAssessmentRequest, @@ -83,25 +83,6 @@ type TPopulateSettingsExtensionsMenuItem = { value: (props: T) => JSX.Element; }; -type TPortfolioPosition = { - contract_info: ProposalOpenContract & - Portfolio1 & { - contract_update?: ContractUpdate; - }; - details?: string; - display_name: string; - id?: number; - indicative: number; - payout?: number; - purchase?: number; - reference: number; - type?: string; - is_unsupported: boolean; - contract_update: ProposalOpenContract['limit_order']; - is_sell_requested: boolean; - profit_loss: number; -}; - type TAppRoutingHistory = { action: string; hash: string; @@ -179,7 +160,8 @@ type TMenuItem = { type TAddToastProps = { key: string; - content: string; + content: string | React.ReactNode; + timeout?: number; type: string; }; @@ -258,6 +240,7 @@ type TClientStore = { cfd_score: number; setCFDScore: (score: number) => void; currency: string; + currencies_list: { text: string; value: string; has_tool_tip?: boolean }[]; current_currency_type?: string; current_fiat_currency?: string; has_any_real_account: boolean; @@ -272,6 +255,7 @@ type TClientStore = { is_eu_country: boolean; is_eu: boolean; is_uk: boolean; + is_single_currency: boolean; is_social_signup: boolean; has_residence: boolean; is_authorize: boolean; @@ -290,7 +274,6 @@ type TClientStore = { is_withdrawal_lock: boolean; landing_company_shortcode: string; is_populating_account_list: boolean; - is_language_loaded: boolean; local_currency_config: { currency: string; decimal_places?: number; @@ -391,7 +374,6 @@ type TClientStore = { setFinancialAndTradingAssessment: ( payload: SetFinancialAssessmentRequest ) => Promise; - prev_account_type: string; }; type TCommonStoreError = { @@ -407,6 +389,12 @@ type TCommonStoreError = { type?: string; }; +type TCommonStoreServicesError = { + code: string; + message: string; + type?: string; +}; + type TCommonStore = { isCurrentLanguage(language_code: string): boolean; error: TCommonStoreError; @@ -421,11 +409,10 @@ type TCommonStore = { changeSelectedLanguage: (key: string) => void; current_language: string; is_language_changing: boolean; - is_socket_opened: boolean; + services_error: TCommonStoreServicesError; setAppstorePlatform: (value: string) => void; app_routing_history: TAppRoutingHistory[]; getExchangeRate: (from_currency: string, to_currency: string) => Promise; - network_status: Record | { [key: string]: string }; }; type TUiStore = { @@ -434,33 +421,36 @@ type TUiStore = { current_focus: string | null; disableApp: () => void; enableApp: () => void; + has_only_forward_starting_contracts: boolean; has_real_account_signup_ended: boolean; is_cashier_visible: boolean; is_closing_create_real_account_modal: boolean; is_dark_mode_on: boolean; is_reports_visible: boolean; is_language_settings_modal_on: boolean; - is_app_disabled: boolean; - is_link_expired_modal_visible: boolean; is_mobile: boolean; - sub_section_index: number; + is_services_error_visible: boolean; + is_unsupported_contract_modal_visible: boolean; toggleShouldShowRealAccountsList: (value: boolean) => void; openRealAccountSignup: ( value: 'maltainvest' | 'svg' | 'add_crypto' | 'choose' | 'add_fiat' | 'set_currency' | 'manage' ) => void; notification_messages_ui: React.ElementType; - setCurrentFocus: (value: string) => void; + setCurrentFocus: (value: string | null) => void; setDarkMode: (is_dark_mode_on: boolean) => boolean; + setHasOnlyForwardingContracts: (has_only_forward_starting_contracts: boolean) => void; setReportsTabIndex: (value: number) => void; setIsClosingCreateRealAccountModal: (value: boolean) => void; setRealAccountSignupEnd: (status: boolean) => void; + setPurchaseState: (index: number) => void; + sub_section_index: number; setSubSectionIndex: (index: number) => void; shouldNavigateAfterChooseCrypto: (value: Omit | TRoutes) => void; toggleAccountsDialog: () => void; toggleCashier: () => void; toggleLanguageSettingsModal: () => void; - toggleLinkExpiredModal: (state_change: boolean) => void; toggleReadyToDepositModal: () => void; + toggleServicesErrorModal: (is_visible: boolean) => void; toggleSetCurrencyModal: () => void; is_tablet: boolean; removeToast: (key: string) => void; @@ -472,7 +462,6 @@ type TUiStore = { toggleReports: (is_visible: boolean) => void; is_real_acc_signup_on: boolean; is_need_real_account_for_cashier_modal_visible: boolean; - is_chart_layout_default: boolean; toggleNeedRealAccountForCashierModal: () => void; setIsAcuityModalOpen: (value: boolean) => void; is_switch_to_deriv_account_modal_visible: boolean; @@ -492,29 +481,38 @@ type TUiStore = { setResetTradingPasswordModalOpen: () => void; populateHeaderExtensions: (header_items: JSX.Element | null) => void; populateSettingsExtensions: (menu_items: Array | null) => void; + purchase_states: boolean[]; setShouldShowCooldownModal: (value: boolean) => void; - setAppContentsScrollRef: (ref: React.MutableRefObject) => void; - populateFooterExtensions: ( - footer_extensions: - | [ - { - position?: string; - Component?: React.FunctionComponent; - has_right_separator?: boolean; - } - ] - | [] - ) => void; + vanilla_trade_type: 'VANILLALONGCALL' | 'VANILLALONGPUT'; +}; + +type TPortfolioPosition = { + contract_info: ProposalOpenContract & + Portfolio1 & { + contract_update?: ContractUpdate; + }; + details?: string; + display_name: string; + id?: number; + indicative: number; + payout?: number; + purchase?: number; + reference: number; + type?: string; + is_unsupported: boolean; + contract_update: ProposalOpenContract['limit_order']; + is_sell_requested: boolean; + profit_loss: number; }; type TPortfolioStore = { active_positions: TPortfolioPosition[]; + all_positions: TPortfolioPosition[]; error: string; getPositionById: (id: number) => TPortfolioPosition; - is_accumulator: boolean; is_loading: boolean; is_multiplier: boolean; - is_turbos: boolean; + is_accumulator: boolean; onClickCancel: (contract_id?: number) => void; onClickSell: (contract_id?: number) => void; onMount: () => void; @@ -524,6 +522,11 @@ type TPortfolioStore = { type TContractStore = { getContractById: (id: number) => ProposalOpenContract; + contract_info: TPortfolioPosition['contract_info']; + contract_update_stop_loss: string; + contract_update_take_profit: string; + has_contract_update_stop_loss: boolean; + has_contract_update_take_profit: boolean; }; type TMenuStore = { @@ -564,15 +567,11 @@ type TTradersHubStore = { login: string; account_id: string; }; - handleTabItemClick: (idx: number) => void; - is_account_transfer_modal_open: boolean; is_low_risk_cr_eu_real: boolean; is_eu_user: boolean; show_eu_related_content: boolean; setTogglePlatformType: (platform_type: string) => void; is_real: boolean; - is_regulators_compare_modal_visible: boolean; - is_tour_open: boolean; selectRegion: (region: string) => void; closeAccountTransferModal: () => void; toggleRegulatorsCompareModal: () => void; @@ -581,7 +580,6 @@ type TTradersHubStore = { multipliers_account_status: string; financial_restricted_countries: boolean; selected_account_type: string; - selected_platform_type: string; setSelectedAccount: (account: { login?: string; account_id?: string }) => void; no_CR_account: boolean; no_MF_account: boolean; @@ -593,10 +591,6 @@ type TTradersHubStore = { platform_demo_balance: TBalance; cfd_real_balance: TBalance; selectAccountType: (account_type: string) => void; - toggleIsTourOpen: (is_tour_open: boolean) => void; - is_demo_low_risk: boolean; - is_mt5_notification_modal_visible: boolean; - setMT5NotificationModal: (value: boolean) => void; }; /** @@ -614,6 +608,11 @@ export type TCoreStores = { modules: Record; notifications: TNotificationStore; traders_hub: TTradersHubStore; + gtm: Record; + pushwoosh: Record; + contract_replay: Record; + chart_barrier_store: Record; + active_symbols: Record; }; export type TStores = TCoreStores & { diff --git a/packages/trader/src/Stores/useTraderStores.tsx b/packages/trader/src/Stores/useTraderStores.tsx index 5e4db0cd77c2..c745965f2f67 100644 --- a/packages/trader/src/Stores/useTraderStores.tsx +++ b/packages/trader/src/Stores/useTraderStores.tsx @@ -1,8 +1,142 @@ import React from 'react'; import { useStore } from '@deriv/stores'; import TradeStore from './Modules/Trading/trade-store'; +import moment from 'moment'; +import { TTextValueStrings } from '../Types/common-prop.type'; -const TraderStoreContext = React.createContext(null); +type TContractTypesList = { + [key: string]: { + name: string; + categories: TTextValueStrings[]; + }; +}; + +type TContractCategoriesList = { + Multipliers: TContractTypesList; + 'Ups & Downs': TContractTypesList; + 'Highs & Lows': TContractTypesList; + 'Ins & Outs': TContractTypesList; + 'Look Backs': TContractTypesList; + Digits: TContractTypesList; + Vanillas: TContractTypesList; + Accumulators: TContractTypesList; +}; + +type TToastBoxListItem = { + contract_category: string; + contract_types: [ + { + text: string; + value: string; + } + ]; +}; + +type TToastBoxObject = { + key?: boolean; + buy_price?: number; + currency?: string; + contract_type?: string; + list?: TToastBoxListItem[]; +}; + +type TOverrideTradeStore = Omit< + TradeStore, + | 'accumulator_range_list' + | 'barriers' + | 'basis_list' + | 'cancellation_price' + | 'cancellation_range_list' + | 'clearContractPurchaseToastBox' + | 'contract_purchase_toast_box' + | 'contract_types_list' + | 'duration_min_max' + | 'duration_units_list' + | 'expiry_date' + | 'expiry_time' + | 'expiry_type' + | 'form_components' + | 'market_close_times' + | 'market_open_times' + | 'multiplier_range_list' + | 'multiplier' + | 'sessions' + | 'setIsTradeParamsExpanded' + | 'stake_boundary' + | 'start_dates_list' + | 'start_time' + | 'symbol' + | 'take_profit' + | 'proposal_info' + | 'trade_types' + | 'ticks_history_stats' + | 'validation_errors' +> & { + accumulator_range_list?: number[]; + basis_list: Array; + cancellation_price?: number; + cancellation_range_list: Array; + clearContractPurchaseToastBox: () => void; + contract_purchase_toast_box: TToastBoxObject; + contract_types_list: TContractCategoriesList; + duration_min_max: { + [key: string]: { min: number; max: number }; + }; + duration_units_list: Array; + expiry_date: string | null; + expiry_time: string | null; + expiry_type: string | null; + form_components: string[]; + market_open_times: string[]; + market_close_times: string[]; + multiplier: number; + multiplier_range_list: number[]; + proposal_info: { + [key: string]: { + barrier?: string; + has_error?: boolean; + id: string; + has_increased?: boolean; + message?: string; + cancellation?: { + ask_price: number; + date_expiry: number; + }; + growth_rate?: number; + returns?: string; + stake: string; + spot?: string; + }; + }; + sessions: Array<{ open: moment.Moment; close: moment.Moment }>; + setIsTradeParamsExpanded: (value: boolean) => void; + stake_boundary: { + VANILLALONGCALL: { + min_stake: string; + max_stake: string; + }; + VANILLALONGPUT: { + min_stake: string; + max_stake: string; + }; + }; + start_dates_list: Array<{ text: string; value: number }>; + start_time: string | null; + symbol: string; + take_profit?: string; + ticks_history_stats: { + ticks_stayed_in?: number[]; + last_tick_epoch?: number; + }; + trade_types: { [key: string]: string }; + validation_errors?: { + amount?: string[]; + barrier_1?: string[]; + barrier_2?: string[]; + }; +}; + +const TraderStoreContext = React.createContext(null); export const TraderStoreProvider = ({ children }: React.PropsWithChildren) => { const { modules } = useStore(); diff --git a/packages/trader/src/Types/common-prop.type.ts b/packages/trader/src/Types/common-prop.type.ts new file mode 100644 index 000000000000..12f9f2551630 --- /dev/null +++ b/packages/trader/src/Types/common-prop.type.ts @@ -0,0 +1,32 @@ +import { useTraderStore } from 'Stores/useTraderStores'; + +export type TTextValueStrings = { + text: string; + value: string; +}; + +export type TProposalTypeInfo = { + has_error?: boolean; + id: string; + has_increased?: boolean; + message?: string; + cancellation?: { + ask_price: number; + date_expiry: number; + }; + growth_rate?: number; + returns?: string; + stake: string; +}; + +export type TError = { + error?: { + code?: string; + details?: { + field?: string; + }; + message?: string; + }; +}; + +export type TTradeStore = ReturnType; diff --git a/packages/trader/src/Types/index.ts b/packages/trader/src/Types/index.ts new file mode 100644 index 000000000000..989319119dbe --- /dev/null +++ b/packages/trader/src/Types/index.ts @@ -0,0 +1 @@ +export * from './common-prop.type'; diff --git a/packages/trader/tsconfig.json b/packages/trader/tsconfig.json index f980e4bcac9b..0bc1c7a7b7e9 100644 --- a/packages/trader/tsconfig.json +++ b/packages/trader/tsconfig.json @@ -13,11 +13,12 @@ "Services/*": ["src/Services/*"], "Stores/*": ["src/Stores/*"], "Translations/*": ["src/public/translations/*"], + "Types": ["src/Types"], "Utils/*": ["src/Utils/*"], "@deriv/*": ["../*/src"] }, "outDir": "./dist", "baseUrl": "./" }, - "include": ["src", "../../globals.d.ts", "../../utils.d.ts", "@deriv-stores.d.ts"] + "include": ["src", "../../globals.d.ts", "../../utils.d.ts"] } From cd5556bc3aab951e7709fc16798e9d056a433b01 Mon Sep 17 00:00:00 2001 From: kate-deriv Date: Thu, 10 Aug 2023 15:37:07 +0300 Subject: [PATCH 26/55] refactor: add prev changes --- package-lock.json | 17 + .../div100vh-container/div100vh-container.tsx | 2 +- .../components/input-field/input-field.tsx | 20 +- .../src/components/input-field/input.tsx | 14 +- .../src/components/numpad/numpad.tsx | 16 +- .../components/src/components/tabs/tab.tsx | 20 +- .../components/src/components/tabs/tabs.tsx | 48 +- .../utils/digital-options/digital-options.ts | 6 +- packages/shared/src/utils/helpers/duration.ts | 4 +- packages/stores/src/mockStore.ts | 7 +- packages/stores/types.ts | 21 +- packages/trader/package.json | 1 + .../__tests__/toggle-positions.spec.tsx | 16 +- .../Elements/TogglePositions/index.js | 2 +- ...mobile.jsx => toggle-positions-mobile.tsx} | 37 +- ...gle-positions.jsx => toggle-positions.tsx} | 17 +- .../Elements/market-countdown-timer.jsx | 209 -------- .../Elements/market-countdown-timer.tsx | 224 +++++++++ ...erlay.jsx => market-is-closed-overlay.tsx} | 31 +- .../Containers/ProgressSliderStream/index.js | 1 - .../progress-slider-stream.jsx | 35 -- .../src/App/Containers/populate-header.jsx | 9 +- ...nsions.jsx => trade-footer-extensions.tsx} | 12 +- packages/trader/src/App/app.tsx | 2 +- .../Components/Elements/mobile-widget.jsx | 2 +- .../{allow-equals.jsx => allow-equals.tsx} | 38 +- .../{amount-mobile.jsx => amount-mobile.tsx} | 45 +- .../TradeParams/{amount.jsx => amount.tsx} | 39 +- .../TradeParams/{barrier.jsx => barrier.tsx} | 27 +- .../{last-digit.jsx => last-digit.tsx} | 13 +- .../Trading/Components/Form/screen-large.jsx | 2 +- .../Trading/Components/Form/screen-small.jsx | 2 +- .../Multiplier/multiplier-amount-modal.jsx | 2 +- ...ams-mobile.jsx => trade-params-mobile.tsx} | 112 +++-- .../{trade-params.jsx => trade-params.tsx} | 23 +- .../src/Modules/Trading/Containers/trade.jsx | 4 +- .../{trade-store.js => trade-store.ts} | 472 +++++++++++------- .../trader/src/Stores/useTraderStores.tsx | 138 +---- 38 files changed, 883 insertions(+), 807 deletions(-) rename packages/trader/src/App/Components/Elements/TogglePositions/{toggle-positions-mobile.jsx => toggle-positions-mobile.tsx} (78%) rename packages/trader/src/App/Components/Elements/TogglePositions/{toggle-positions.jsx => toggle-positions.tsx} (74%) delete mode 100644 packages/trader/src/App/Components/Elements/market-countdown-timer.jsx create mode 100644 packages/trader/src/App/Components/Elements/market-countdown-timer.tsx rename packages/trader/src/App/Components/Elements/{market-is-closed-overlay.jsx => market-is-closed-overlay.tsx} (68%) delete mode 100644 packages/trader/src/App/Containers/ProgressSliderStream/index.js delete mode 100644 packages/trader/src/App/Containers/ProgressSliderStream/progress-slider-stream.jsx rename packages/trader/src/App/Containers/{trade-footer-extensions.jsx => trade-footer-extensions.tsx} (82%) rename packages/trader/src/Modules/Trading/Components/Form/TradeParams/{allow-equals.jsx => allow-equals.tsx} (72%) rename packages/trader/src/Modules/Trading/Components/Form/TradeParams/{amount-mobile.jsx => amount-mobile.tsx} (86%) rename packages/trader/src/Modules/Trading/Components/Form/TradeParams/{amount.jsx => amount.tsx} (87%) rename packages/trader/src/Modules/Trading/Components/Form/TradeParams/{barrier.jsx => barrier.tsx} (93%) rename packages/trader/src/Modules/Trading/Components/Form/TradeParams/{last-digit.jsx => last-digit.tsx} (80%) rename packages/trader/src/Modules/Trading/Containers/{trade-params-mobile.jsx => trade-params-mobile.tsx} (74%) rename packages/trader/src/Modules/Trading/Containers/{trade-params.jsx => trade-params.tsx} (86%) rename packages/trader/src/Stores/Modules/Trading/{trade-store.js => trade-store.ts} (82%) diff --git a/package-lock.json b/package-lock.json index 60d6071a6059..83785e3db50f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37,6 +37,7 @@ "@types/js-cookie": "^3.0.1", "@types/jsdom": "^20.0.0", "@types/loadjs": "^4.0.1", + "@types/lodash.debounce": "^4.0.7", "@types/lodash.merge": "^4.6.7", "@types/lodash.throttle": "^4.1.7", "@types/object.fromentries": "^2.0.0", @@ -15933,6 +15934,14 @@ "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.196.tgz", "integrity": "sha512-22y3o88f4a94mKljsZcanlNWPzO0uBsBdzLAngf2tp533LzZcQzb6+eZPJ+vCTt+bqF2XnvT9gejTLsAcJAJyQ==" }, + "node_modules/@types/lodash.debounce": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@types/lodash.debounce/-/lodash.debounce-4.0.7.tgz", + "integrity": "sha512-X1T4wMZ+gT000M2/91SYj0d/7JfeNZ9PeeOldSNoE/lunLeQXKvkmIumI29IaKMotU/ln/McOIvgzZcQ/3TrSA==", + "dependencies": { + "@types/lodash": "*" + } + }, "node_modules/@types/lodash.merge": { "version": "4.6.7", "resolved": "https://registry.npmjs.org/@types/lodash.merge/-/lodash.merge-4.6.7.tgz", @@ -60842,6 +60851,14 @@ "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.196.tgz", "integrity": "sha512-22y3o88f4a94mKljsZcanlNWPzO0uBsBdzLAngf2tp533LzZcQzb6+eZPJ+vCTt+bqF2XnvT9gejTLsAcJAJyQ==" }, + "@types/lodash.debounce": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@types/lodash.debounce/-/lodash.debounce-4.0.7.tgz", + "integrity": "sha512-X1T4wMZ+gT000M2/91SYj0d/7JfeNZ9PeeOldSNoE/lunLeQXKvkmIumI29IaKMotU/ln/McOIvgzZcQ/3TrSA==", + "requires": { + "@types/lodash": "*" + } + }, "@types/lodash.merge": { "version": "4.6.7", "resolved": "https://registry.npmjs.org/@types/lodash.merge/-/lodash.merge-4.6.7.tgz", diff --git a/packages/components/src/components/div100vh-container/div100vh-container.tsx b/packages/components/src/components/div100vh-container/div100vh-container.tsx index d33c67384078..6bcf1a60361a 100644 --- a/packages/components/src/components/div100vh-container/div100vh-container.tsx +++ b/packages/components/src/components/div100vh-container/div100vh-container.tsx @@ -16,7 +16,7 @@ import Div100vh from 'react-div-100vh'; type TDiv100vhContainer = { id?: string; - height_offset: string; + height_offset?: string; is_bypassed?: boolean; is_disabled?: boolean; max_height_offset?: string; diff --git a/packages/components/src/components/input-field/input-field.tsx b/packages/components/src/components/input-field/input-field.tsx index 94296b7c1663..37cc6c2dbec3 100644 --- a/packages/components/src/components/input-field/input-field.tsx +++ b/packages/components/src/components/input-field/input-field.tsx @@ -14,7 +14,7 @@ export type TButtonType = 'button' | 'submit' | 'reset'; // supports more than two different types of 'value' as a prop. // Quick Solution - Pass two different props to input field. type TInputField = { - ariaLabel: string; + ariaLabel?: string; checked?: boolean; className?: string; classNameDynamicSuffix?: string; @@ -22,8 +22,8 @@ type TInputField = { classNameInput?: string; classNamePrefix?: string; classNameWrapper?: string; // CSS class for the component wrapper - currency: string; - current_focus: string; + currency?: string; + current_focus: string | null; data_testid?: string; data_tip?: string; data_value?: string; @@ -31,26 +31,26 @@ type TInputField = { error_message_alignment?: string; error_messages?: string[]; format?: (new_value?: string) => string; - fractional_digits: number; + fractional_digits?: number; helper?: string; icon?: React.ElementType; id?: string; increment_button_type?: TButtonType; inline_prefix?: string; inputmode?: TInputMode; - is_autocomplete_disabled: boolean; + is_autocomplete_disabled?: boolean; is_disabled?: boolean; - is_error_tooltip_hidden: boolean; + is_error_tooltip_hidden?: boolean; is_float: boolean; - is_hj_whitelisted: boolean; + is_hj_whitelisted?: boolean; is_incrementable_on_long_press?: boolean; - is_incrementable: boolean; - is_negative_disabled: boolean; + is_incrementable?: boolean; + is_negative_disabled?: boolean; is_read_only?: boolean; is_signed?: boolean; is_unit_at_right?: boolean; label?: string; - max_length: number; + max_length?: number; max_value?: number; min_value?: number; name: string; diff --git a/packages/components/src/components/input-field/input.tsx b/packages/components/src/components/input-field/input.tsx index 126a164b16ca..8c5df9dd49e3 100644 --- a/packages/components/src/components/input-field/input.tsx +++ b/packages/components/src/components/input-field/input.tsx @@ -4,7 +4,7 @@ import { getCurrencyDisplayCode } from '@deriv/shared'; import { TInputMode } from './input-field'; type TInputProps = { - ariaLabel: string; + ariaLabel?: string; changeValue: ( e: React.ChangeEvent, callback?: (evt: React.ChangeEvent) => void @@ -13,22 +13,22 @@ type TInputProps = { className?: string; classNameDynamicSuffix?: string; classNameInlinePrefix?: string; - current_focus: string; + current_focus: string | null; data_testid?: string; data_tip?: string; data_value?: number | string; display_value: number | string; - fractional_digits: number; + fractional_digits?: number; has_error?: boolean; id?: string; inline_prefix?: string; inputmode?: TInputMode; - is_autocomplete_disabled: boolean; + is_autocomplete_disabled?: boolean; is_disabled?: boolean; is_hj_whitelisted: boolean; - is_incrementable: boolean; + is_incrementable?: boolean; is_read_only: boolean; - max_length: number; + max_length?: number; name: string; onBlur?: React.FocusEventHandler; onClick?: React.MouseEventHandler; @@ -128,7 +128,7 @@ const Input = ({ data-value={data_value} disabled={!!is_disabled} id={id} - maxLength={fractional_digits ? max_length + fractional_digits + 1 : max_length} + maxLength={fractional_digits && max_length ? max_length + fractional_digits + 1 : max_length} name={name} onBlur={onBlurHandler} onChange={onChange} diff --git a/packages/components/src/components/numpad/numpad.tsx b/packages/components/src/components/numpad/numpad.tsx index 662f840aaa33..e7f6c2e4dfd5 100644 --- a/packages/components/src/components/numpad/numpad.tsx +++ b/packages/components/src/components/numpad/numpad.tsx @@ -12,20 +12,20 @@ type TNumpad = { is_regular?: boolean; is_currency?: boolean; is_submit_disabled?: boolean; - label: string; + label?: string; reset_press_interval: number; reset_value: string; - max: number; - min: number; + max?: number; + min?: number; pip_size: number; onSubmit: (param: number | string) => void; - v: string; + v?: string; render?: (props: { value: string; className: string }) => React.ReactNode; submit_label: string; - value: string; - format: (v: string) => number; + value: string | number; + format?: (v: string) => number | string; onValueChange: (val: number | string) => void; - onValidate: (default_value: number | string) => string | undefined; + onValidate: (default_value: number | string) => boolean | 'error'; }; const concatenate = (number: string | number, default_value: string | number) => @@ -71,7 +71,7 @@ const Numpad = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [default_value, value]); - const updateValue = (val: string) => { + const updateValue = (val: string | number) => { setValue(val); if (onValueChange) onValueChange(val); }; diff --git a/packages/components/src/components/tabs/tab.tsx b/packages/components/src/components/tabs/tab.tsx index eba22fac25bc..5c26046094d6 100644 --- a/packages/components/src/components/tabs/tab.tsx +++ b/packages/components/src/components/tabs/tab.tsx @@ -4,15 +4,15 @@ import Counter from '../counter'; import Icon from '../icon'; type TTabProps = { - active_icon_color: string; + active_icon_color?: string; active_tab_ref?: React.RefObject | null; bottom?: boolean; className?: string; count: number; header_content: React.ReactElement; header_fit_content?: boolean; - icon_color: string; - icon_size: number; + icon_color?: string; + icon_size?: number; icon: string; id?: string; is_active: boolean; @@ -25,17 +25,17 @@ type TTabProps = { }; const Tab = ({ - active_icon_color, + active_icon_color = '', active_tab_ref, - bottom, - className, + bottom = false, + className = '', count, header_content, - header_fit_content, - icon_color, - icon_size, + header_fit_content = false, + icon_color = '', + icon_size = 0, icon, - id, + id = '', is_active, is_label_hidden, is_scrollable, diff --git a/packages/components/src/components/tabs/tabs.tsx b/packages/components/src/components/tabs/tabs.tsx index c7d8f5483e77..99817c188937 100644 --- a/packages/components/src/components/tabs/tabs.tsx +++ b/packages/components/src/components/tabs/tabs.tsx @@ -14,20 +14,20 @@ declare module 'react' { } type TTabsProps = RouteComponentProps & { - active_icon_color: string; + active_icon_color?: string; active_index?: number; - background_color: string; + background_color?: string; bottom?: boolean; - center: boolean; - children: React.ReactElement[]; + center?: boolean; + children: (React.ReactElement | null)[]; className?: string; - fit_content: boolean; + fit_content?: boolean; has_active_line?: boolean; has_bottom_line?: boolean; header_fit_content?: boolean; history: History; - icon_color: string; - icon_size: number; + icon_color?: string; + icon_size?: number; is_100vw?: boolean; is_full_width?: boolean; is_overflow_hidden?: boolean; @@ -39,27 +39,27 @@ type TTabsProps = RouteComponentProps & { }; const Tabs = ({ - active_icon_color, + active_icon_color = '', active_index = 0, - background_color, - bottom, - center, + background_color = '', + bottom = false, + center = false, children, - className, - fit_content, + className = '', + fit_content = false, has_active_line = true, has_bottom_line = true, - header_fit_content, + header_fit_content = false, history, - icon_color, - icon_size, - is_100vw, - is_full_width, - is_overflow_hidden, - is_scrollable, + icon_color = '', + icon_size = 0, + is_100vw = false, + is_full_width = false, + is_overflow_hidden = false, + is_scrollable = false, onTabItemClick, - should_update_hash, - single_tab_has_no_label, + should_update_hash = false, + single_tab_has_no_label = false, top, }: TTabsProps) => { const [active_line_style, updateActiveLineStyle] = React.useState({}); @@ -99,7 +99,7 @@ const Tabs = ({ initial_index_to_show = hash_index; } else { // if no hash is in url but component has passed hash prop, set hash of the tab shown - const child_props = children[initial_index_to_show].props; + const child_props = children[initial_index_to_show]?.props; const current_id = child_props && child_props.hash; if (current_id) { pushHash(current_id); @@ -128,7 +128,7 @@ const Tabs = ({ const onClickTabItem = (index: number) => { if (should_update_hash) { - const hash = children[index].props['data-hash']; + const hash = children[index]?.props['data-hash']; pushHash(hash); } setActiveTabIndex(index); diff --git a/packages/shared/src/utils/digital-options/digital-options.ts b/packages/shared/src/utils/digital-options/digital-options.ts index 882b3f7037eb..a1a43602bf27 100644 --- a/packages/shared/src/utils/digital-options/digital-options.ts +++ b/packages/shared/src/utils/digital-options/digital-options.ts @@ -10,7 +10,7 @@ type TShowError = { message: string; header: string; redirect_label: string; - redirectOnClick: (() => void) | null; + redirectOnClick?: (() => void) | null; should_show_refresh: boolean; redirect_to: string; should_clear_error_on_click: boolean; @@ -25,8 +25,8 @@ type TAccounts = { export const showDigitalOptionsUnavailableError = ( showError: (t: TShowError) => void, message: TMessage, - redirectOnClick: (() => void) | null, - should_redirect: boolean, + redirectOnClick?: (() => void) | null, + should_redirect = false, should_clear_error_on_click = true ) => { const { title, text, link } = message; diff --git a/packages/shared/src/utils/helpers/duration.ts b/packages/shared/src/utils/helpers/duration.ts index 84d47c169c9a..be2579641b3f 100644 --- a/packages/shared/src/utils/helpers/duration.ts +++ b/packages/shared/src/utils/helpers/duration.ts @@ -163,10 +163,10 @@ export const hasIntradayDurationUnit = (duration_units_list: TUnit[]) => { * On switching symbols, end_time value of volatility indices should be set to today * * @param {String} symbol - * @param {String} expiry_type + * @param {String | null} expiry_type * @returns {*} */ -export const resetEndTimeOnVolatilityIndices = (symbol: string, expiry_type: string) => +export const resetEndTimeOnVolatilityIndices = (symbol: string, expiry_type: string | null) => /^R_/.test(symbol) && expiry_type === 'endtime' ? toMoment(null).format('DD MMM YYYY') : null; export const getDurationMinMaxValues = ( diff --git a/packages/stores/src/mockStore.ts b/packages/stores/src/mockStore.ts index 4a139e1ee1a4..02c6ff4ed370 100644 --- a/packages/stores/src/mockStore.ts +++ b/packages/stores/src/mockStore.ts @@ -14,7 +14,6 @@ const mock = (): TStores & { is_mock: boolean } => { redirectOnClick: jest.fn(), setError: jest.fn(), }; - const services_error = { code: '', message: '', type: '' }; return { is_mock: true, client: { @@ -266,7 +265,7 @@ const mock = (): TStores & { is_mock: boolean } => { changeCurrentLanguage: jest.fn(), changeSelectedLanguage: jest.fn(), is_network_online: false, - services_error, + services_error: {}, server_time: undefined, is_language_changing: false, setAppstorePlatform: jest.fn(), @@ -283,6 +282,7 @@ const mock = (): TStores & { is_mock: boolean } => { is_dark_mode_on: false, is_language_settings_modal_on: false, is_mobile: false, + is_positions_drawer_on: false, is_reports_visible: false, is_services_error_visible: false, is_unsupported_contract_modal_visible: false, @@ -303,7 +303,9 @@ const mock = (): TStores & { is_mock: boolean } => { setPurchaseState: jest.fn(), shouldNavigateAfterChooseCrypto: jest.fn(), toggleLanguageSettingsModal: jest.fn(), + togglePositionsDrawer: jest.fn(), toggleServicesErrorModal: jest.fn(), + toggleLinkExpiredModal: jest.fn(), toggleSetCurrencyModal: jest.fn(), addToast: jest.fn(), removeToast: jest.fn(), @@ -407,6 +409,7 @@ const mock = (): TStores & { is_mock: boolean } => { }, portfolio: { active_positions: [], + active_positions_count: 0, all_positions: [], error: '', getPositionById: jest.fn(), diff --git a/packages/stores/types.ts b/packages/stores/types.ts index fc514f7ac870..ceaf4a754e32 100644 --- a/packages/stores/types.ts +++ b/packages/stores/types.ts @@ -390,8 +390,8 @@ type TCommonStoreError = { }; type TCommonStoreServicesError = { - code: string; - message: string; + code?: string; + message?: string; type?: string; }; @@ -409,6 +409,7 @@ type TCommonStore = { changeSelectedLanguage: (key: string) => void; current_language: string; is_language_changing: boolean; + is_socket_opened: boolean; services_error: TCommonStoreServicesError; setAppstorePlatform: (value: string) => void; app_routing_history: TAppRoutingHistory[]; @@ -429,6 +430,7 @@ type TUiStore = { is_reports_visible: boolean; is_language_settings_modal_on: boolean; is_mobile: boolean; + is_positions_drawer_on: boolean; is_services_error_visible: boolean; is_unsupported_contract_modal_visible: boolean; toggleShouldShowRealAccountsList: (value: boolean) => void; @@ -449,6 +451,8 @@ type TUiStore = { toggleAccountsDialog: () => void; toggleCashier: () => void; toggleLanguageSettingsModal: () => void; + togglePositionsDrawer: () => void; + toggleLinkExpiredModal: (state_change: boolean) => void; toggleReadyToDepositModal: () => void; toggleServicesErrorModal: (is_visible: boolean) => void; toggleSetCurrencyModal: () => void; @@ -483,6 +487,18 @@ type TUiStore = { populateSettingsExtensions: (menu_items: Array | null) => void; purchase_states: boolean[]; setShouldShowCooldownModal: (value: boolean) => void; + setAppContentsScrollRef: (ref: React.MutableRefObject) => void; + populateFooterExtensions: ( + footer_extensions: + | [ + { + position?: string; + Component?: React.FunctionComponent; + has_right_separator?: boolean; + } + ] + | [] + ) => void; vanilla_trade_type: 'VANILLALONGCALL' | 'VANILLALONGPUT'; }; @@ -507,6 +523,7 @@ type TPortfolioPosition = { type TPortfolioStore = { active_positions: TPortfolioPosition[]; + active_positions_count: number; all_positions: TPortfolioPosition[]; error: string; getPositionById: (id: number) => TPortfolioPosition; diff --git a/packages/trader/package.json b/packages/trader/package.json index ea9e649d4930..a4c48e7a53aa 100644 --- a/packages/trader/package.json +++ b/packages/trader/package.json @@ -35,6 +35,7 @@ "devDependencies": { "@babel/eslint-parser": "^7.17.0", "@babel/preset-react": "^7.16.7", + "@types/lodash.debounce": "^4.0.7", "@testing-library/jest-dom": "^5.12.0", "@testing-library/react": "^12.0.0", "@testing-library/react-hooks": "^7.0.2", diff --git a/packages/trader/src/App/Components/Elements/TogglePositions/__tests__/toggle-positions.spec.tsx b/packages/trader/src/App/Components/Elements/TogglePositions/__tests__/toggle-positions.spec.tsx index 19770e5e8bf3..20e13be8dd7a 100644 --- a/packages/trader/src/App/Components/Elements/TogglePositions/__tests__/toggle-positions.spec.tsx +++ b/packages/trader/src/App/Components/Elements/TogglePositions/__tests__/toggle-positions.spec.tsx @@ -1,29 +1,35 @@ import React from 'react'; import userEvent from '@testing-library/user-event'; import { render, screen } from '@testing-library/react'; -import TogglePositions from '../toggle-positions.jsx'; +import TogglePositions from '../toggle-positions'; + +const default_props = { + is_open: false, + positions_count: 0, + togglePositions: jest.fn(), +}; describe('TogglePositions component', () => { it('should have "positions-toggle--active" class when "is_open" is "true"', () => { - render(); + render(); expect(screen.getByTestId('dt_positions_toggle')).toHaveClass('positions-toggle--active'); }); it('should have "positions-toggle--has-count" class when "positions_count > 0"', () => { - render(); + render(); expect(screen.getByTestId('dt_positions_toggle')).toHaveClass('positions-toggle--has-count'); }); it('should call "togglePositions" when the user clicked on the link', () => { const mockTogglePositions = jest.fn(); - render(); + render(); const link = screen.getByTestId('dt_positions_toggle'); userEvent.click(link); expect(mockTogglePositions).toHaveBeenCalledTimes(1); }); it('should render "IcPortfolio" icon', () => { - render(); + render(); expect(screen.getByTestId('dt_icon')).toBeVisible(); }); }); diff --git a/packages/trader/src/App/Components/Elements/TogglePositions/index.js b/packages/trader/src/App/Components/Elements/TogglePositions/index.js index 117ed97b03fd..c97185e5654a 100644 --- a/packages/trader/src/App/Components/Elements/TogglePositions/index.js +++ b/packages/trader/src/App/Components/Elements/TogglePositions/index.js @@ -1,3 +1,3 @@ -import TogglePositions from './toggle-positions.jsx'; +import TogglePositions from './toggle-positions'; export default TogglePositions; diff --git a/packages/trader/src/App/Components/Elements/TogglePositions/toggle-positions-mobile.jsx b/packages/trader/src/App/Components/Elements/TogglePositions/toggle-positions-mobile.tsx similarity index 78% rename from packages/trader/src/App/Components/Elements/TogglePositions/toggle-positions-mobile.jsx rename to packages/trader/src/App/Components/Elements/TogglePositions/toggle-positions-mobile.tsx index 692620e0d419..5d66cd6682c6 100644 --- a/packages/trader/src/App/Components/Elements/TogglePositions/toggle-positions-mobile.jsx +++ b/packages/trader/src/App/Components/Elements/TogglePositions/toggle-positions-mobile.tsx @@ -6,35 +6,53 @@ import { localize } from '@deriv/translations'; import { NavLink } from 'react-router-dom'; import EmptyPortfolioMessage from '../EmptyPortfolioMessage'; import PositionsModalCard from 'App/Components/Elements/PositionsDrawer/positions-modal-card.jsx'; -import TogglePositions from './toggle-positions.jsx'; +import { filterByContractType } from 'App/Components/Elements/PositionsDrawer/helpers'; +import TogglePositions from './toggle-positions'; +import { useTraderStore } from 'Stores/useTraderStores'; import { observer, useStore } from '@deriv/stores'; +type TTogglePositionsMobile = Pick< + ReturnType['portfolio'], + 'active_positions_count' | 'all_positions' | 'error' | 'onClickSell' | 'onClickCancel' +> & { + currency: ReturnType['client']['currency']; + is_empty: number; +}; + const TogglePositionsMobile = observer( ({ active_positions_count, + all_positions, currency, - disableApp, - enableApp, error, - filtered_positions, is_empty, onClickSell, onClickCancel, - toggleUnsupportedContractModal, - }) => { + }: TTogglePositionsMobile) => { const { portfolio, ui } = useStore(); + const { symbol, contract_type: trade_contract_type } = useTraderStore(); const { removePositionById: onClickRemove } = portfolio; - const { togglePositionsDrawer, is_positions_drawer_on } = ui; + const { togglePositionsDrawer, toggleUnsupportedContractModal, is_positions_drawer_on } = ui; + let filtered_positions: TTogglePositionsMobile['all_positions'] = []; + const closeModal = () => { filtered_positions.slice(0, 5).map(position => { const { contract_info } = position; if (contract_info?.is_sold) { - onClickRemove(contract_info.contract_id); + onClickRemove(contract_info.contract_id as number); } }); togglePositionsDrawer(); }; + filtered_positions = all_positions.filter( + p => + p.contract_info && + symbol === p.contract_info.underlying && + //@ts-expect-error filterByContractType function needs to be in typescript + filterByContractType(p.contract_info, trade_contract_type) + ); + // Show only 5 most recent open contracts const body_content = ( @@ -54,6 +72,7 @@ const TogglePositionsMobile = observer( unmountOnExit > diff --git a/packages/trader/src/App/Components/Elements/TogglePositions/toggle-positions.jsx b/packages/trader/src/App/Components/Elements/TogglePositions/toggle-positions.tsx similarity index 74% rename from packages/trader/src/App/Components/Elements/TogglePositions/toggle-positions.jsx rename to packages/trader/src/App/Components/Elements/TogglePositions/toggle-positions.tsx index c37fb05d11f0..7e7691372e11 100644 --- a/packages/trader/src/App/Components/Elements/TogglePositions/toggle-positions.jsx +++ b/packages/trader/src/App/Components/Elements/TogglePositions/toggle-positions.tsx @@ -1,10 +1,15 @@ import classNames from 'classnames'; -import PropTypes from 'prop-types'; import React from 'react'; import { Icon } from '@deriv/components'; import 'Sass/app/_common/components/positions-toggle.scss'; -const TogglePositions = ({ positions_count, is_open, togglePositions }) => { +type TTogglePositions = { + positions_count: number; + is_open: boolean; + togglePositions: () => void; +}; + +const TogglePositions = ({ positions_count, is_open, togglePositions }: TTogglePositions) => { const positions_toggle_class = classNames('positions-toggle', { 'positions-toggle--active': is_open, 'positions-toggle--has-count': positions_count > 0, @@ -22,12 +27,4 @@ const TogglePositions = ({ positions_count, is_open, togglePositions }) => { ); }; -TogglePositions.propTypes = { - is_open: PropTypes.bool, - is_positions_drawer_on: PropTypes.bool, - positions_count: PropTypes.number, - togglePositions: PropTypes.func, - togglePositionsDrawer: PropTypes.func, -}; - export default TogglePositions; diff --git a/packages/trader/src/App/Components/Elements/market-countdown-timer.jsx b/packages/trader/src/App/Components/Elements/market-countdown-timer.jsx deleted file mode 100644 index 833af29df84f..000000000000 --- a/packages/trader/src/App/Components/Elements/market-countdown-timer.jsx +++ /dev/null @@ -1,209 +0,0 @@ -import classNames from 'classnames'; -import React from 'react'; -import PropTypes from 'prop-types'; -import moment from 'moment'; -import { Text } from '@deriv/components'; -import { useIsMounted, WS, convertTimeFormat, isMarketClosed } from '@deriv/shared'; -import { Localize } from '@deriv/translations'; -import { observer } from '@deriv/stores'; -import { useTraderStore } from 'Stores/useTraderStores'; - -// check market in coming 7 days -const days_to_check_before_exit = 7; - -const getTradingTimes = async target_time => { - const data = await WS.tradingTimes(target_time); - if (data.error) { - return { api_initial_load_error: data.error.message }; - } - return data; -}; -// eslint-disable-next-line consistent-return -const getSymbol = (target_symbol, trading_times) => { - let symbol; - const { markets } = trading_times; - for (let i = 0; i < markets.length; i++) { - const { submarkets } = markets[i]; - for (let j = 0; j < submarkets.length; j++) { - const { symbols } = submarkets[j]; - symbol = symbols.find(item => item.symbol === target_symbol); - if (symbol !== undefined) return symbol; - } - } -}; - -const calculateTimeLeft = remaining_time_to_open => { - const difference = remaining_time_to_open - Date.now(); - return difference > 0 - ? { - days: Math.floor(difference / (1000 * 60 * 60 * 24)), - hours: Math.floor((difference / (1000 * 60 * 60)) % 24), - minutes: Math.floor((difference / 1000 / 60) % 60), - seconds: Math.floor((difference / 1000) % 60), - } - : {}; -}; - -const MarketCountdownTimer = observer(({ is_main_page, setIsTimerLoading, onMarketOpen, symbol }) => { - const { active_symbols } = useTraderStore(); - const isMounted = useIsMounted(); - const [when_market_opens, setWhenMarketOpens] = React.useState({}); - const [time_left, setTimeLeft] = React.useState(calculateTimeLeft(when_market_opens?.remaining_time_to_open)); - const [is_loading, setLoading] = React.useState(true); - - React.useEffect(() => { - if (!is_main_page || (is_main_page && isMarketClosed(active_symbols, symbol))) { - setLoading(true); - // eslint-disable-next-line consistent-return - const whenMarketOpens = async (days_offset, target_symbol) => { - // days_offset is 0 for today, 1 for tomorrow, etc. - if (days_offset > days_to_check_before_exit) return {}; - let remaining_time_to_open; - const target_date = moment(new Date()).add(days_offset, 'days'); - const api_response = await getTradingTimes(target_date.format('YYYY-MM-DD')); - if (!api_response.api_initial_load_error) { - const { times } = getSymbol(target_symbol, api_response.trading_times); - const { open, close } = times; - const is_closed_all_day = open?.length === 1 && open[0] === '--' && close[0] === '--'; - if (is_closed_all_day) { - // check tomorrow trading times - return whenMarketOpens(days_offset + 1, target_symbol); - } - const date_str = target_date.toISOString().substring(0, 11); - const getUTCDate = hour => new Date(`${date_str}${hour}Z`); - for (let i = 0; i < open?.length; i++) { - const diff = +getUTCDate(open[i]) - Date.now(); - if (diff > 0) { - remaining_time_to_open = +getUTCDate(open[i]); - if (isMounted() && target_symbol === symbol) { - return setWhenMarketOpens({ - days_offset, - opening_time: open[i], - remaining_time_to_open, - }); - } - } - } - whenMarketOpens(days_offset + 1, target_symbol); - } - }; - - whenMarketOpens(0, symbol); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [symbol]); - - React.useEffect(() => { - let timer; - if (when_market_opens?.remaining_time_to_open) { - timer = setTimeout(() => { - setTimeLeft(calculateTimeLeft(when_market_opens.remaining_time_to_open)); - if (new Date(when_market_opens.remaining_time_to_open) - +new Date() < 1000) { - setLoading(true); - if (is_main_page) onMarketOpen(false); - } - }, 1000); - } - return () => { - if (timer) { - clearTimeout(timer); - } - }; - }, [time_left, when_market_opens, onMarketOpen, is_main_page]); - - React.useEffect(() => { - if (!is_loading) setIsTimerLoading(false); - }, [is_loading, setIsTimerLoading]); - - let timer_components = ''; - - if (Object.keys(time_left).length) { - const hours = (time_left.days * 24 + time_left.hours).toString().padStart(2, '0'); - const minutes = time_left.minutes.toString().padStart(2, '0'); - const seconds = time_left.seconds.toString().padStart(2, '0'); - timer_components = `${hours}:${minutes}:${seconds}`; - } - - if (!(when_market_opens && timer_components)) return null; - - const { opening_time, days_offset } = when_market_opens; - let opening_time_banner = null; - if (opening_time) { - const formatted_opening_time = convertTimeFormat(opening_time); - const target_date = moment(new Date()).add(days_offset, 'days'); - const opening_date = target_date.format('DD MMM YYYY'); - const opening_day = target_date.format('dddd'); - opening_time_banner = ( - - ]} - values={{ - formatted_opening_time, - opening_day, - opening_date, - }} - /> - - ); - } - - if (is_loading) setLoading(false); - - return ( - - - - - {opening_time_banner} - - - - - {timer_components} - -
- - ); -}); - -MarketCountdownTimer.propTypes = { - is_main_page: PropTypes.bool, - setIsTimerLoading: PropTypes.func, - onMarketOpen: PropTypes.func, - symbol: PropTypes.string.isRequired, -}; - -export default MarketCountdownTimer; diff --git a/packages/trader/src/App/Components/Elements/market-countdown-timer.tsx b/packages/trader/src/App/Components/Elements/market-countdown-timer.tsx new file mode 100644 index 000000000000..03ec30b9ec2a --- /dev/null +++ b/packages/trader/src/App/Components/Elements/market-countdown-timer.tsx @@ -0,0 +1,224 @@ +import classNames from 'classnames'; +import React from 'react'; +import moment from 'moment'; +import { Text } from '@deriv/components'; +import { useIsMounted, WS, convertTimeFormat, isMarketClosed } from '@deriv/shared'; +import { Localize } from '@deriv/translations'; +import { observer } from '@deriv/stores'; +import { useTraderStore } from 'Stores/useTraderStores'; +import { TradingTimesRequest, TradingTimesResponse } from '@deriv/api-types'; + +type TMarketCountDownTimer = { + is_main_page: boolean; + setIsTimerLoading: React.Dispatch>; + onMarketOpen: ReturnType['prepareTradeStore']; + symbol: ReturnType['symbol']; +}; + +type TWhenMarketOpens = { + days_offset: number; + opening_time: string; + remaining_time_to_open: number; +}; + +// check market in coming 7 days +const days_to_check_before_exit = 7; + +const getTradingTimes = async (target_time: TradingTimesRequest['trading_times']) => { + const data = await WS.tradingTimes(target_time); + if (data.error) { + return { api_initial_load_error: data.error.message }; + } + return data; +}; +// eslint-disable-next-line consistent-return +const getSymbol = ( + target_symbol: string, + trading_times: NonNullable> +) => { + let symbol; + const { markets } = trading_times; + for (let i = 0; i < markets.length; i++) { + const { submarkets } = markets[i]; + if (submarkets) { + for (let j = 0; j < submarkets.length; j++) { + const { symbols } = submarkets[j]; + symbol = symbols?.find(item => item.symbol === target_symbol); + if (symbol !== undefined) return symbol; + } + } + } +}; + +const calculateTimeLeft = (remaining_time_to_open: number) => { + const difference = remaining_time_to_open - Date.now(); + return difference > 0 + ? { + days: Math.floor(difference / (1000 * 60 * 60 * 24)), + hours: Math.floor((difference / (1000 * 60 * 60)) % 24), + minutes: Math.floor((difference / 1000 / 60) % 60), + seconds: Math.floor((difference / 1000) % 60), + } + : {}; +}; + +const MarketCountdownTimer = observer( + ({ is_main_page, setIsTimerLoading, onMarketOpen, symbol }: TMarketCountDownTimer) => { + const { active_symbols } = useTraderStore(); + const isMounted = useIsMounted(); + const [when_market_opens, setWhenMarketOpens] = React.useState({} as TWhenMarketOpens); + const [time_left, setTimeLeft] = React.useState(calculateTimeLeft(when_market_opens?.remaining_time_to_open)); + const [is_loading, setLoading] = React.useState(true); + + React.useEffect(() => { + if (!is_main_page || (is_main_page && isMarketClosed(active_symbols, symbol))) { + setLoading(true); + // eslint-disable-next-line consistent-return + // @ts-expect-error there is no explict return type because of if statements + const whenMarketOpens = async (days_offset: number, target_symbol: string) => { + // days_offset is 0 for today, 1 for tomorrow, etc. + if (days_offset > days_to_check_before_exit) return {}; + let remaining_time_to_open; + const target_date = moment(new Date()).add(days_offset, 'days'); + const api_response = await getTradingTimes(target_date.format('YYYY-MM-DD')); + if (!api_response.api_initial_load_error) { + const returned_symbol = getSymbol(target_symbol, api_response.trading_times); + const open = returned_symbol?.times.open as string[]; + const close = returned_symbol?.times.close as string[]; + const is_closed_all_day = open?.length === 1 && open[0] === '--' && close[0] === '--'; + if (is_closed_all_day) { + // check tomorrow trading times + return whenMarketOpens(days_offset + 1, target_symbol); + } + const date_str = target_date.toISOString().substring(0, 11); + const getUTCDate = (hour: string) => new Date(`${date_str}${hour}Z`); + for (let i = 0; i < open?.length; i++) { + const diff = +getUTCDate(open[i]) - Date.now(); + if (diff > 0) { + remaining_time_to_open = +getUTCDate(open[i]); + if (isMounted() && target_symbol === symbol) { + return setWhenMarketOpens({ + days_offset, + opening_time: open[i], + remaining_time_to_open, + }); + } + } + } + whenMarketOpens(days_offset + 1, target_symbol); + } + }; + + whenMarketOpens(0, symbol); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [symbol]); + + React.useEffect(() => { + let timer: ReturnType; + if (when_market_opens?.remaining_time_to_open) { + timer = setTimeout(() => { + setTimeLeft(calculateTimeLeft(when_market_opens.remaining_time_to_open)); + if (+new Date(when_market_opens.remaining_time_to_open) - +new Date() < 1000) { + setLoading(true); + if (is_main_page) onMarketOpen(false); + } + }, 1000); + } + return () => { + if (timer) { + clearTimeout(timer); + } + }; + }, [time_left, when_market_opens, onMarketOpen, is_main_page]); + + React.useEffect(() => { + if (!is_loading) setIsTimerLoading(false); + }, [is_loading, setIsTimerLoading]); + + let timer_components = ''; + + if (Object.keys(time_left).length) { + const hours = (Number(time_left.days) * 24 + Number(time_left.hours)).toString().padStart(2, '0'); + const minutes = Number(time_left.minutes).toString().padStart(2, '0'); + const seconds = Number(time_left.seconds).toString().padStart(2, '0'); + timer_components = `${hours}:${minutes}:${seconds}`; + } + + if (!(when_market_opens && timer_components)) return null; + + const { opening_time, days_offset } = when_market_opens; + let opening_time_banner = null; + if (opening_time) { + const formatted_opening_time = convertTimeFormat(opening_time); + const target_date = moment(new Date()).add(days_offset, 'days'); + const opening_date = target_date.format('DD MMM YYYY'); + const opening_day = target_date.format('dddd'); + opening_time_banner = ( + + ]} + values={{ + formatted_opening_time, + opening_day, + opening_date, + }} + /> + + ); + } + + if (is_loading) setLoading(false); + + return ( + + + + + {opening_time_banner} + + + + + {timer_components} + +
+ + ); + } +); + +export default MarketCountdownTimer; diff --git a/packages/trader/src/App/Components/Elements/market-is-closed-overlay.jsx b/packages/trader/src/App/Components/Elements/market-is-closed-overlay.tsx similarity index 68% rename from packages/trader/src/App/Components/Elements/market-is-closed-overlay.jsx rename to packages/trader/src/App/Components/Elements/market-is-closed-overlay.tsx index a68ab239d67c..e2c58ab7180b 100644 --- a/packages/trader/src/App/Components/Elements/market-is-closed-overlay.jsx +++ b/packages/trader/src/App/Components/Elements/market-is-closed-overlay.tsx @@ -1,14 +1,29 @@ import classNames from 'classnames'; import React from 'react'; -import PropTypes from 'prop-types'; import { Button, Text } from '@deriv/components'; import { localize, Localize } from '@deriv/translations'; -import MarketCountdownTimer from './market-countdown-timer.jsx'; +import MarketCountdownTimer from './market-countdown-timer'; +import { useStore } from '@deriv/stores'; +import { useTraderStore } from 'Stores/useTraderStores.js'; -const MarketIsClosedOverlay = ({ is_eu, is_synthetics_trading_market_available, onClick, onMarketOpen, symbol }) => { +type TMarketIsClosedOverlay = { + is_eu: ReturnType['client']['is_eu']; + is_synthetics_trading_market_available: ReturnType['is_synthetics_trading_market_available']; + onClick: () => void; + onMarketOpen: React.ComponentProps['onMarketOpen']; + symbol: ReturnType['symbol']; +}; + +const MarketIsClosedOverlay = ({ + is_eu, + is_synthetics_trading_market_available, + onClick, + onMarketOpen, + symbol, +}: TMarketIsClosedOverlay) => { const [is_timer_loading, setIsTimerLoading] = React.useState(true); - let message = ( + let message: JSX.Element | null = ( ); let btn_lbl = localize('Try Synthetic Indices'); @@ -45,12 +60,4 @@ const MarketIsClosedOverlay = ({ is_eu, is_synthetics_trading_market_available, ); }; -MarketIsClosedOverlay.propTypes = { - is_eu: PropTypes.bool, - is_synthetics_trading_market_available: PropTypes.bool, - onClick: PropTypes.func, - onMarketOpen: PropTypes.func, - symbol: PropTypes.string, -}; - export default MarketIsClosedOverlay; diff --git a/packages/trader/src/App/Containers/ProgressSliderStream/index.js b/packages/trader/src/App/Containers/ProgressSliderStream/index.js deleted file mode 100644 index 657c6ba7065a..000000000000 --- a/packages/trader/src/App/Containers/ProgressSliderStream/index.js +++ /dev/null @@ -1 +0,0 @@ -export default from './progress-slider-stream.jsx'; diff --git a/packages/trader/src/App/Containers/ProgressSliderStream/progress-slider-stream.jsx b/packages/trader/src/App/Containers/ProgressSliderStream/progress-slider-stream.jsx deleted file mode 100644 index 487cbc036604..000000000000 --- a/packages/trader/src/App/Containers/ProgressSliderStream/progress-slider-stream.jsx +++ /dev/null @@ -1,35 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import { ProgressSlider } from '@deriv/components'; -import { getCurrentTick } from '@deriv/shared'; -import { getCardLabels } from 'Constants/contract'; -import { observer, useStore } from '@deriv/stores'; - -const ProgressSliderStream = observer(({ contract_info }) => { - const { common, portfolio } = useStore(); - const { server_time } = common; - const { is_loading } = portfolio; - - if (!contract_info) { - return
; - } - const current_tick = contract_info.tick_count && getCurrentTick(contract_info); - - return ( - - ); -}); - -ProgressSliderStream.propTypes = { - contract_info: PropTypes.object, -}; - -export default ProgressSliderStream; diff --git a/packages/trader/src/App/Containers/populate-header.jsx b/packages/trader/src/App/Containers/populate-header.jsx index b6831ac6f4d1..105b097a46c3 100644 --- a/packages/trader/src/App/Containers/populate-header.jsx +++ b/packages/trader/src/App/Containers/populate-header.jsx @@ -1,21 +1,19 @@ import React from 'react'; -import TogglePositionsMobile from 'App/Components/Elements/TogglePositions/toggle-positions-mobile.jsx'; +import TogglePositionsMobile from 'App/Components/Elements/TogglePositions/toggle-positions-mobile'; import { filterByContractType } from 'App/Components/Elements/PositionsDrawer/helpers'; import { useTraderStore } from 'Stores/useTraderStores'; import { observer, useStore } from '@deriv/stores'; import { TURBOS } from '@deriv/shared'; const PopulateHeader = observer(() => { - const { portfolio, ui, client } = useStore(); + const { portfolio, client } = useStore(); const { symbol, contract_type: trade_contract_type } = useTraderStore(); const { currency: positions_currency } = client; - const { disableApp, enableApp } = ui; const { active_positions_count, all_positions: positions, error: positions_error, onClickSell: onPositionsSell, - removePositionById: onPositionsRemove, onClickCancel: onPositionsCancel, } = portfolio; @@ -34,12 +32,9 @@ const PopulateHeader = observer(() => { active_positions_count={active_positions_count} filtered_positions={filtered_positions} currency={positions_currency} - disableApp={disableApp} is_empty={!filtered_positions.length} - enableApp={enableApp} error={positions_error} onClickSell={onPositionsSell} - onClickRemove={onPositionsRemove} onClickCancel={onPositionsCancel} /> ); diff --git a/packages/trader/src/App/Containers/trade-footer-extensions.jsx b/packages/trader/src/App/Containers/trade-footer-extensions.tsx similarity index 82% rename from packages/trader/src/App/Containers/trade-footer-extensions.jsx rename to packages/trader/src/App/Containers/trade-footer-extensions.tsx index 702950f5ebca..8643c94f5987 100644 --- a/packages/trader/src/App/Containers/trade-footer-extensions.jsx +++ b/packages/trader/src/App/Containers/trade-footer-extensions.tsx @@ -1,11 +1,11 @@ -import PropTypes from 'prop-types'; import React from 'react'; -import { withRouter } from 'react-router-dom'; +import { RouteComponentProps, withRouter } from 'react-router-dom'; import { routes } from '@deriv/shared'; -import TogglePositions from 'App/Components/Elements/TogglePositions'; +import TogglePositions from '../Components/Elements/TogglePositions/toggle-positions'; import { observer, useStore } from '@deriv/stores'; -const TradeFooterExtensions = observer(() => { +const TradeFooterExtensions = observer((props: RouteComponentProps) => { + const { location } = props; const { client, portfolio, ui } = useStore(); const { is_logged_in } = client; const { active_positions_count } = portfolio; @@ -40,8 +40,4 @@ const TradeFooterExtensions = observer(() => { return null; }); -TradeFooterExtensions.propTypes = { - location: PropTypes.object, -}; - export default withRouter(TradeFooterExtensions); diff --git a/packages/trader/src/App/app.tsx b/packages/trader/src/App/app.tsx index e1c53b91a711..ba551b3d2037 100644 --- a/packages/trader/src/App/app.tsx +++ b/packages/trader/src/App/app.tsx @@ -2,7 +2,7 @@ import React from 'react'; import Loadable from 'react-loadable'; import Routes from 'App/Containers/Routes/routes.jsx'; import TradeHeaderExtensions from 'App/Containers/trade-header-extensions'; -import TradeFooterExtensions from 'App/Containers/trade-footer-extensions.jsx'; +import TradeFooterExtensions from 'App/Containers/trade-footer-extensions'; import TradeSettingsExtensions from 'App/Containers/trade-settings-extensions'; import { NetworkStatusToastErrorPopup } from 'Modules/Trading/Containers/toast-popup.jsx'; import initStore from './init-store'; diff --git a/packages/trader/src/Modules/Trading/Components/Elements/mobile-widget.jsx b/packages/trader/src/Modules/Trading/Components/Elements/mobile-widget.jsx index 0d336ef5589f..c1bbf2cb75a4 100644 --- a/packages/trader/src/Modules/Trading/Components/Elements/mobile-widget.jsx +++ b/packages/trader/src/Modules/Trading/Components/Elements/mobile-widget.jsx @@ -3,7 +3,7 @@ import { Money } from '@deriv/components'; import { localize, Localize } from '@deriv/translations'; import { getExpiryType, getDurationMinMaxValues, getLocalizedBasis } from '@deriv/shared'; import { MultiplierAmountWidget } from 'Modules/Trading/Components/Form/TradeParams/Multiplier/widgets.jsx'; -import TradeParamsModal from '../../Containers/trade-params-mobile.jsx'; +import TradeParamsModal from '../../Containers/trade-params-mobile'; import { observer, useStore } from '@deriv/stores'; import { useTraderStore } from 'Stores/useTraderStores'; diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/allow-equals.jsx b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/allow-equals.tsx similarity index 72% rename from packages/trader/src/Modules/Trading/Components/Form/TradeParams/allow-equals.jsx rename to packages/trader/src/Modules/Trading/Components/Form/TradeParams/allow-equals.tsx index 0d3ad0614154..448e3419ba38 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/allow-equals.jsx +++ b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/allow-equals.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import PropTypes from 'prop-types'; import { Popover, Checkbox } from '@deriv/components'; import { localize } from '@deriv/translations'; import { @@ -7,6 +6,22 @@ import { hasDurationForCallPutEqual, isRiseFallEqual, } from 'Stores/Modules/Trading/Helpers/allow-equals'; +import { useTraderStore } from 'Stores/useTraderStores'; + +type TTradeStore = Pick< + ReturnType, + | 'contract_start_type' + | 'contract_type' + | 'contract_types_list' + | 'duration_unit' + | 'expiry_type' + | 'has_equals_only' +>; + +type TAllowEquals = TTradeStore & { + onChange: (e: { target: { name: string; value: number } }) => Promise; + value: number; +}; const AllowEquals = ({ contract_start_type, @@ -17,7 +32,7 @@ const AllowEquals = ({ onChange, value, has_equals_only, -}) => { +}: TAllowEquals) => { const has_callputequal_duration = hasDurationForCallPutEqual( contract_types_list, duration_unit, @@ -28,10 +43,12 @@ const AllowEquals = ({ const has_allow_equals = isRiseFallEqual(contract_type) && (has_callputequal_duration || expiry_type === 'endtime') && has_callputequal; - const changeValue = e => { + const changeValue: React.ComponentProps['onChange'] = e => { e.persist(); - const { name, checked } = e.target; - onChange({ target: { name, value: Number(checked) } }); + if ('checked' in e.target) { + const { name, checked } = e.target; + onChange({ target: { name, value: Number(checked) } }); + } }; return ( @@ -61,15 +78,4 @@ const AllowEquals = ({ ); }; -AllowEquals.propTypes = { - contract_start_type: PropTypes.string, - contract_type: PropTypes.string, - contract_types_list: PropTypes.object, - duration_unit: PropTypes.string, - expiry_type: PropTypes.string, - has_equals_only: PropTypes.bool, - onChange: PropTypes.func, - value: PropTypes.number, -}; - export default AllowEquals; diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount-mobile.jsx b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount-mobile.tsx similarity index 86% rename from packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount-mobile.jsx rename to packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount-mobile.tsx index e78e2f9303a7..6adba4cfd29d 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount-mobile.jsx +++ b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount-mobile.tsx @@ -7,6 +7,17 @@ import { Money, Numpad, Tabs } from '@deriv/components'; import { getDecimalPlaces, isEmptyObject } from '@deriv/shared'; import MinMaxStakeInfo from './min-max-stake-info'; +type TBasis = { + basis: string; + duration_unit: string; + duration_value: number; + toggleModal: () => void; + has_duration_error: boolean; + selected_basis: string | number; + setSelectedAmount: (basis: string, num: string | number) => void; + setAmountError: (has_error: boolean) => void; +}; + const Basis = observer( ({ basis, @@ -17,7 +28,7 @@ const Basis = observer( selected_basis, setSelectedAmount, setAmountError, - }) => { + }: TBasis) => { const { ui, client } = useStore(); const { addToast } = ui; const { currency } = client; @@ -25,21 +36,22 @@ const Basis = observer( is_turbos, is_vanilla, onChangeMultiple, - trade_amount, - trade_basis, - trade_duration_unit, - trade_duration, + amount: trade_amount, + basis: trade_basis, + duration_unit: trade_duration_unit, + duration: trade_duration, contract_type, } = useTraderStore(); + const user_currency_decimal_places = getDecimalPlaces(currency); - const onNumberChange = num => { + const onNumberChange = (num: number | string) => { setSelectedAmount(basis, num); validateAmount(num); }; - const formatAmount = value => - !isNaN(value) && value !== '' ? Number(value).toFixed(user_currency_decimal_places) : value; - const setBasisAndAmount = amount => { - const on_change_obj = {}; + const formatAmount = (value: number | string) => + !isNaN(+value) && value !== '' ? Number(value).toFixed(user_currency_decimal_places) : value; + const setBasisAndAmount = (amount: number | string) => { + const on_change_obj: Partial> = {}; // Check for any duration changes in Duration trade params Tab before sending onChange object if (duration_unit !== trade_duration_unit && !has_duration_error) @@ -48,7 +60,7 @@ const Basis = observer( if (amount !== trade_amount || basis !== trade_basis) { on_change_obj.basis = basis; - on_change_obj.amount = amount; + on_change_obj.amount = +amount; } if (!isEmptyObject(on_change_obj)) onChangeMultiple(on_change_obj); @@ -57,7 +69,7 @@ const Basis = observer( const zero_decimals = Number('0').toFixed(getDecimalPlaces(currency)); const min_amount = parseFloat(zero_decimals.toString().replace(/.$/, '1')); - const validateAmount = value => { + const validateAmount = (value: number | string) => { const localized_message = ; const selected_value = parseFloat(value.toString()); @@ -115,6 +127,13 @@ const Basis = observer( } ); +type TAmountMobile = React.ComponentProps & { + amount_tab_idx?: number; + setAmountTabIdx: React.ComponentProps['onTabItemClick']; + stake_value: string | number; + payout_value: string | number; +}; + const Amount = observer( ({ toggleModal, @@ -127,7 +146,7 @@ const Amount = observer( setSelectedAmount, stake_value, payout_value, - }) => { + }: TAmountMobile) => { const { basis, basis_list } = useTraderStore(); const has_selected_tab_idx = typeof amount_tab_idx !== 'undefined'; const active_index = has_selected_tab_idx ? amount_tab_idx : basis_list.findIndex(b => b.value === basis); diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount.jsx b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount.tsx similarity index 87% rename from packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount.jsx rename to packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount.tsx index 0e9ac8b72281..b6f44f018bab 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount.jsx +++ b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount.tsx @@ -1,28 +1,35 @@ import { AMOUNT_MAX_LENGTH, addComma, getDecimalPlaces } from '@deriv/shared'; import { ButtonToggle, Dropdown, InputField } from '@deriv/components'; import { Localize, localize } from '@deriv/translations'; - -import AllowEquals from './allow-equals.jsx'; +import AllowEquals from './allow-equals'; import Fieldset from 'App/Components/Form/fieldset.jsx'; import Multiplier from './Multiplier/multiplier.jsx'; import MultipliersInfo from './Multiplier/info.jsx'; import MinMaxStakeInfo from './min-max-stake-info'; -import PropTypes from 'prop-types'; import React from 'react'; import classNames from 'classnames'; import { useTraderStore } from 'Stores/useTraderStores'; import { observer, useStore } from '@deriv/stores'; +type TInput = { + amount: string | number; + currency: string; + current_focus: string | null; + error_messages?: string[]; + is_single_currency: boolean; + onChange: (e: { target: { name: string; value: number | string } }) => void; + setCurrentFocus: (name: string | null) => void; +}; + export const Input = ({ amount, currency, current_focus, error_messages, - is_nativepicker, is_single_currency, onChange, setCurrentFocus, -}) => ( +}: TInput) => ( ); -const Amount = observer(({ is_minimized, is_nativepicker }) => { +const Amount = observer(({ is_minimized }: { is_minimized: boolean }) => { const { ui, client } = useStore(); const { currencies_list, is_single_currency } = client; const { setCurrentFocus, current_focus } = ui; @@ -76,9 +82,7 @@ const Amount = observer(({ is_minimized, is_nativepicker }) => { if (is_minimized) { return (
- - {(basis_list.find(o => o.value === basis) || {}).text} - + {basis_list.find(o => o.value === basis)?.text}   { ); } - const error_messages = validation_errors.amount; + const error_messages = validation_errors?.amount; const getBasisList = () => basis_list.map(item => ({ text: item.text, value: item.value })); @@ -134,12 +138,10 @@ const Amount = observer(({ is_minimized, is_nativepicker }) => { current_focus={current_focus} error_messages={error_messages} is_single_currency={is_single_currency} - is_nativepicker={is_nativepicker} onChange={onChange} setCurrentFocus={setCurrentFocus} /> { current_focus={current_focus} error_messages={error_messages} is_single_currency={is_single_currency} - is_nativepicker={is_nativepicker} onChange={onChange} setCurrentFocus={setCurrentFocus} /> @@ -170,13 +171,14 @@ const Amount = observer(({ is_minimized, is_nativepicker }) => { duration_unit={duration_unit} expiry_type={expiry_type} onChange={onChange} - value={parseInt(is_equal)} + value={Number(is_equal)} has_equals_only={has_equals_only} /> {is_multiplier && ( { ); }); -Amount.propTypes = { - is_minimized: PropTypes.bool, - is_nativepicker: PropTypes.bool, -}; - export default Amount; diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/barrier.jsx b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/barrier.tsx similarity index 93% rename from packages/trader/src/Modules/Trading/Components/Form/TradeParams/barrier.jsx rename to packages/trader/src/Modules/Trading/Components/Form/TradeParams/barrier.tsx index 4855881b8b1a..e2ca0280cf90 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/barrier.jsx +++ b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/barrier.tsx @@ -1,5 +1,4 @@ import classNames from 'classnames'; -import PropTypes from 'prop-types'; import React from 'react'; import { DesktopWrapper, Icon, InputField, MobileWrapper, Modal, Text, usePrevious } from '@deriv/components'; import Fieldset from 'App/Components/Form/fieldset.jsx'; @@ -9,7 +8,12 @@ import { useTraderStore } from 'Stores/useTraderStores'; import { localize } from '@deriv/translations'; import LabeledQuantityInputMobile from '../LabeledQuantityInputMobile'; -const Barrier = observer(({ is_minimized, is_absolute_only }) => { +type TBarrier = { + is_minimized?: boolean; + is_absolute_only?: boolean; +}; + +const Barrier = observer(({ is_minimized, is_absolute_only }: TBarrier) => { const { ui } = useStore(); const { current_focus, setCurrentFocus } = ui; const { @@ -25,11 +29,12 @@ const Barrier = observer(({ is_minimized, is_absolute_only }) => { } = useTraderStore(); const [show_modal, setShowModal] = React.useState(false); const type_with_current_spot = Object.keys(trade_types).find(type => proposal_info?.[type]?.spot); - const contract_info = proposal_info?.[type_with_current_spot]; + let contract_info, has_spot_increased; + if (type_with_current_spot) contract_info = proposal_info?.[type_with_current_spot]; const current_spot = contract_info?.spot || ''; const current_barrier_price = contract_info?.barrier || ''; const previous_spot = usePrevious(current_spot); - const has_spot_increased = current_spot > previous_spot; + if (previous_spot) has_spot_increased = current_spot > previous_spot; const barrier_title = barrier_count === 1 ? localize('Barrier') : localize('Barriers'); const has_error_or_not_loaded = contract_info?.has_error || !contract_info?.id; @@ -49,7 +54,7 @@ const Barrier = observer(({ is_minimized, is_absolute_only }) => { // TODO: Some contracts yet to be implemented in app.deriv.com allow only absolute barrier, hence the prop const is_absolute_barrier = is_day_duration || is_absolute_only; - const format = value => { + const format = (value: string) => { const float_value = parseFloat(value); let final_value; if (Math.sign(float_value) === -1) { @@ -86,7 +91,7 @@ const Barrier = observer(({ is_minimized, is_absolute_only }) => { )} current_focus={current_focus} onChange={onChange} - error_messages={validation_errors.barrier_1 || []} + error_messages={validation_errors?.barrier_1 || []} is_float is_signed setCurrentFocus={setCurrentFocus} @@ -103,7 +108,7 @@ const Barrier = observer(({ is_minimized, is_absolute_only }) => { classNameInput='trade-container__input' current_focus={current_focus} onChange={onChange} - error_messages={validation_errors.barrier_2} + error_messages={validation_errors?.barrier_2} is_float is_signed setCurrentFocus={setCurrentFocus} @@ -131,6 +136,7 @@ const Barrier = observer(({ is_minimized, is_absolute_only }) => { {localize('Current Price')} {current_spot && ( + //@ts-expect-error until this component is typescript migrated and some props optional { current_focus={current_focus} onChange={onChange} error_messages={ - (barrier_count === 1 ? validation_errors.barrier_1 : validation_errors.barrier_2) || [] + (barrier_count === 1 ? validation_errors?.barrier_1 : validation_errors?.barrier_2) || [] } error_message_alignment='top' is_float @@ -219,9 +225,4 @@ const Barrier = observer(({ is_minimized, is_absolute_only }) => { ); }); -Barrier.propTypes = { - is_absolute_only: PropTypes.bool, - is_minimized: PropTypes.bool, -}; - export default Barrier; diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/last-digit.jsx b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/last-digit.tsx similarity index 80% rename from packages/trader/src/Modules/Trading/Components/Form/TradeParams/last-digit.jsx rename to packages/trader/src/Modules/Trading/Components/Form/TradeParams/last-digit.tsx index 87c16bb7da3c..51e8377621e9 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/last-digit.jsx +++ b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/last-digit.tsx @@ -1,4 +1,3 @@ -import PropTypes from 'prop-types'; import React from 'react'; import { isDesktop } from '@deriv/shared'; import { localize } from '@deriv/translations'; @@ -7,7 +6,11 @@ import Fieldset from 'App/Components/Form/fieldset.jsx'; import { observer } from '@deriv/stores'; import { useTraderStore } from 'Stores/useTraderStores'; -const LastDigit = observer(({ is_minimized }) => { +type TLastDigit = { + is_minimized?: boolean; +}; + +const LastDigit = observer(({ is_minimized }: TLastDigit) => { const { onChange, last_digit } = useTraderStore(); if (is_minimized) { return
{`${localize('Last Digit')}: ${last_digit}`}
; @@ -29,10 +32,4 @@ const LastDigit = observer(({ is_minimized }) => { ); }); -LastDigit.propTypes = { - is_minimized: PropTypes.bool, - last_digit: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - onChange: PropTypes.func, -}; - export default LastDigit; diff --git a/packages/trader/src/Modules/Trading/Components/Form/screen-large.jsx b/packages/trader/src/Modules/Trading/Components/Form/screen-large.jsx index 2172fb8d55ad..59773075af8a 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/screen-large.jsx +++ b/packages/trader/src/Modules/Trading/Components/Form/screen-large.jsx @@ -5,7 +5,7 @@ import { TradeParamsLoader } from 'App/Components/Elements/ContentLoader'; import Fieldset from 'App/Components/Form/fieldset.jsx'; import ContractType from '../../Containers/contract-type.jsx'; import Purchase from '../../Containers/purchase.jsx'; -import TradeParams from '../../Containers/trade-params.jsx'; +import TradeParams from '../../Containers/trade-params'; const ScreenLarge = ({ is_market_closed, is_trade_enabled }) => (
void; +}; + +type TTradeParamsMobile = { + currency: string; + toggleModal: () => void; + isVisible: (component_key: string) => boolean; + setAmountTabIdx: (amount_tab_idx?: number) => void; + amount_tab_idx?: number; + setTradeParamTabIdx: (trade_param_tab_idx: number) => void; + trade_param_tab_idx: number; + setDurationTabIdx: (duration_tab_idx?: number) => void; + duration_unit: string; + duration_units_list: TTextValueStrings[]; + duration_value: number; + duration_tab_idx?: number; + has_amount_error: boolean; + has_duration_error: boolean; + // amount + setAmountError: (has_error: boolean) => void; + setSelectedAmount: (basis: string, selected_basis_value: string | number) => void; + stake_value: number; + payout_value: number; + // duration + setDurationError: (has_error: boolean) => void; + setSelectedDuration: (selected_duration_unit: string, selected_duration: number) => void; + t_duration: number; + s_duration: number; + m_duration: number; + h_duration: number; + d_duration: number; +}; + +type TReducer = Pick< + TTradeParamsMobile, + | 'trade_param_tab_idx' + | 'duration_tab_idx' + | 'amount_tab_idx' + | 'has_amount_error' + | 'has_duration_error' + | 't_duration' + | 's_duration' + | 'm_duration' + | 'h_duration' + | 'd_duration' + | 'stake_value' + | 'payout_value' +> & { curr_duration_unit: string; curr_duration_value: number }; + const DEFAULT_DURATION = Object.freeze({ t: 5, s: 15, @@ -20,20 +70,21 @@ const DEFAULT_DURATION = Object.freeze({ d: 1, }); -const reducer = (state, payload) => { +const reducer = (state: TReducer, payload: Partial) => { return { ...state, ...payload, }; }; -const makeGetDefaultDuration = (trade_duration, trade_duration_unit) => duration_unit => - trade_duration_unit === duration_unit ? trade_duration : DEFAULT_DURATION[duration_unit]; +const makeGetDefaultDuration = (trade_duration: number, trade_duration_unit: string) => (duration_unit: string) => + trade_duration_unit === duration_unit + ? trade_duration + : DEFAULT_DURATION[duration_unit as keyof typeof DEFAULT_DURATION]; -const TradeParamsModal = observer(({ is_open, toggleModal }) => { - const { client, ui } = useStore(); +const TradeParamsModal = observer(({ is_open, toggleModal }: TTradeParamsModal) => { + const { client } = useStore(); const { currency } = client; - const { enableApp, disableApp } = ui; const { amount, form_components, duration, duration_unit, duration_units_list } = useTraderStore(); // eslint-disable-next-line react-hooks/exhaustive-deps @@ -66,15 +117,16 @@ const TradeParamsModal = observer(({ is_open, toggleModal }) => { // duration and duration_unit can be changed in trade-store when contract type is changed }, [duration, duration_unit]); - const setTradeParamTabIdx = trade_param_tab_idx => dispatch({ trade_param_tab_idx }); + const setTradeParamTabIdx = (trade_param_tab_idx: number) => dispatch({ trade_param_tab_idx }); - const setDurationTabIdx = duration_tab_idx => dispatch({ duration_tab_idx }); + const setDurationTabIdx = (duration_tab_idx?: number) => dispatch({ duration_tab_idx }); - const setAmountTabIdx = amount_tab_idx => dispatch({ amount_tab_idx }); + const setAmountTabIdx = (amount_tab_idx?: number) => dispatch({ amount_tab_idx }); - const setSelectedAmount = (basis, selected_basis_value) => dispatch({ [`${basis}_value`]: selected_basis_value }); + const setSelectedAmount = (basis: string, selected_basis_value: string | number) => + dispatch({ [`${basis}_value`]: selected_basis_value }); - const setSelectedDuration = (selected_duration_unit, selected_duration) => { + const setSelectedDuration = (selected_duration_unit: string, selected_duration: number) => { dispatch({ [`${selected_duration_unit}_duration`]: selected_duration, curr_duration_unit: selected_duration_unit, @@ -82,24 +134,21 @@ const TradeParamsModal = observer(({ is_open, toggleModal }) => { }); }; - const setAmountError = has_error => { + const setAmountError = (has_error: boolean) => { dispatch({ has_amount_error: has_error }); }; - const setDurationError = has_error => { + const setDurationError = (has_error: boolean) => { dispatch({ has_duration_error: has_error }); }; - const isVisible = component_key => form_components.includes(component_key); - + const isVisible = (component_key: string): boolean => form_components.includes(component_key); return ( } - disableApp={disableApp} toggleModal={toggleModal} height='auto' width='calc(100vw - 32px)' @@ -173,7 +222,7 @@ const TradeParamsMobile = observer( m_duration, h_duration, d_duration, - }) => { + }: TTradeParamsMobile) => { const { basis_list, basis, expiry_epoch, is_turbos, is_vanilla } = useTraderStore(); const getDurationText = () => { const duration = duration_units_list.find(d => d.value === duration_unit); @@ -189,7 +238,7 @@ const TradeParamsMobile = observer( return ; }; - const getHeaderContent = tab_key => { + const getHeaderContent = (tab_key: string) => { switch (tab_key) { case 'duration': return ( @@ -230,9 +279,10 @@ const TradeParamsMobile = observer( onTabItemClick={setTradeParamTabIdx} top > - {isVisible('duration') && ( + {isVisible('duration') ? (
is migrated to TS toggleModal={toggleModal} amount_tab_idx={amount_tab_idx} duration_tab_idx={duration_tab_idx} @@ -250,8 +300,8 @@ const TradeParamsMobile = observer( expiry_epoch={expiry_epoch} />
- )} - {isVisible('amount') && ( + ) : null} + {isVisible('amount') ? (
- )} + ) : null} ); } @@ -274,10 +326,10 @@ const TradeParamsMobile = observer( export const LastDigitMobile = observer(() => { const { form_components } = useTraderStore(); - return form_components.includes('last_digit') && ; + return form_components.includes('last_digit') ? : null; }); export const BarrierMobile = observer(() => { const { form_components } = useTraderStore(); - return form_components.includes('barrier') && ; + return form_components.includes('barrier') ? : null; }); diff --git a/packages/trader/src/Modules/Trading/Containers/trade-params.jsx b/packages/trader/src/Modules/Trading/Containers/trade-params.tsx similarity index 86% rename from packages/trader/src/Modules/Trading/Containers/trade-params.jsx rename to packages/trader/src/Modules/Trading/Containers/trade-params.tsx index 344f8f55bceb..6b4e4ef76516 100644 --- a/packages/trader/src/Modules/Trading/Containers/trade-params.jsx +++ b/packages/trader/src/Modules/Trading/Containers/trade-params.tsx @@ -1,11 +1,10 @@ -import PropTypes from 'prop-types'; import React from 'react'; import classNames from 'classnames'; -import Amount from 'Modules/Trading/Components/Form/TradeParams/amount.jsx'; -import Barrier from 'Modules/Trading/Components/Form/TradeParams/barrier.jsx'; +import Amount from 'Modules/Trading/Components/Form/TradeParams/amount'; +import Barrier from 'Modules/Trading/Components/Form/TradeParams/barrier'; import BarrierSelector from 'Modules/Trading/Components/Form/TradeParams/Turbos/barrier-selector'; import Duration from 'Modules/Trading/Components/Form/TradeParams/Duration'; -import LastDigit from 'Modules/Trading/Components/Form/TradeParams/last-digit.jsx'; +import LastDigit from 'Modules/Trading/Components/Form/TradeParams/last-digit'; import CancelDeal from 'Modules/Trading/Components/Form/TradeParams/Multiplier/cancel-deal.jsx'; import Accumulator from 'Modules/Trading/Components/Form/TradeParams/Accumulator/accumulator.jsx'; import StopLoss from 'Modules/Trading/Components/Form/TradeParams/Multiplier/stop-loss.jsx'; @@ -18,15 +17,22 @@ import { observer } from '@deriv/stores'; import { useTraderStore } from 'Stores/useTraderStores'; import Fieldset from 'App/Components/Form/fieldset.jsx'; -const TradeParams = observer(({ is_minimized }) => { +type TTradeParams = { + is_minimized: boolean; +}; + +const TradeParams = observer(({ is_minimized }: TTradeParams) => { const { form_components } = useTraderStore(); - const isVisible = component_key => { + const isVisible = (component_key: string) => { return form_components.includes(component_key); }; return ( - {isVisible('duration') && } + {isVisible('duration') && ( + // @ts-expect-error: TODO: check if TS error is gone after is migrated to TS + + )} {isVisible('barrier') && } {isVisible('last_digit') && } {isVisible('accumulator') && } @@ -46,8 +52,5 @@ const TradeParams = observer(({ is_minimized }) => { ); }); -TradeParams.propTypes = { - is_minimized: PropTypes.bool, -}; export default TradeParams; diff --git a/packages/trader/src/Modules/Trading/Containers/trade.jsx b/packages/trader/src/Modules/Trading/Containers/trade.jsx index a65dcbddf672..6df27136e61d 100644 --- a/packages/trader/src/Modules/Trading/Containers/trade.jsx +++ b/packages/trader/src/Modules/Trading/Containers/trade.jsx @@ -4,7 +4,7 @@ import { DesktopWrapper, Div100vhContainer, MobileWrapper, SwipeableWrapper } fr import { getDecimalPlaces, isDesktop, isMobile } from '@deriv/shared'; import ChartLoader from 'App/Components/Elements/chart-loader.jsx'; import PositionsDrawer from 'App/Components/Elements/PositionsDrawer'; -import MarketIsClosedOverlay from 'App/Components/Elements/market-is-closed-overlay.jsx'; +import MarketIsClosedOverlay from 'App/Components/Elements/market-is-closed-overlay'; import Test from './test.jsx'; import { ChartBottomWidgets, ChartTopWidgets, DigitsWidget } from './chart-widgets.jsx'; import FormLayout from '../Components/Form/form-layout.jsx'; @@ -300,7 +300,6 @@ const ChartTrade = observer(props => { wsSubscribe, active_symbols, has_alternative_source, - refToAddTick, } = useTraderStore(); const settings = { @@ -390,7 +389,6 @@ const ChartTrade = observer(props => { onExportLayout={exportLayout} shouldFetchTradingTimes={!end_epoch} hasAlternativeSource={has_alternative_source} - refToAddTick={refToAddTick} getMarketsOrder={getMarketsOrder} yAxisMargin={{ top: isMobile() ? 76 : 106, diff --git a/packages/trader/src/Stores/Modules/Trading/trade-store.js b/packages/trader/src/Stores/Modules/Trading/trade-store.ts similarity index 82% rename from packages/trader/src/Stores/Modules/Trading/trade-store.js rename to packages/trader/src/Stores/Modules/Trading/trade-store.ts index 7ada3c3902f2..d8b98a18e809 100644 --- a/packages/trader/src/Stores/Modules/Trading/trade-store.js +++ b/packages/trader/src/Stores/Modules/Trading/trade-store.ts @@ -1,5 +1,4 @@ import * as Symbol from './Actions/symbol'; - import { WS, cloneObject, @@ -19,7 +18,6 @@ import { isMobile, isTurbosContract, pickDefaultSymbol, - removeBarrier, resetEndTimeOnVolatilityIndices, showDigitalOptionsUnavailableError, showMxMltUnavailableError, @@ -43,12 +41,146 @@ import { action, computed, makeObservable, observable, override, reaction, runIn import { createProposalRequests, getProposalErrorField, getProposalInfo } from './Helpers/proposal'; import { getHoveredColor } from './Helpers/barrier-utils'; import BaseStore from '../../base-store'; +import { TTextValueStrings } from '../../../Types/common-prop.type'; import { ChartBarrierStore } from '../SmartChart/chart-barrier-store'; import debounce from 'lodash.debounce'; import { setLimitOrderBarriers } from './Helpers/limit-orders'; +import type { TCoreStores } from '@deriv/stores/types'; +import { + ActiveSymbols, + ActiveSymbolsRequest, + Buy, + BuyContractResponse, + History, + PriceProposalRequest, + PriceProposalResponse, + ServerTimeRequest, + TickSpotData, + TicksHistoryRequest, + TicksHistoryResponse, + TicksStreamResponse, + TradingTimesRequest, +} from '@deriv/api-types'; + +type TBarriers = Array< + ChartBarrierStore & { + hideOffscreenBarrier?: boolean; + isSingleBarrier?: boolean; + } +>; +type TChartLayout = { + adj: boolean; + aggregationType: string; + animation?: boolean; + candleWidth: number; + chartScale: string; + chartType: string; + crosshair: number; + extended: boolean; + flipped: boolean; + interval: number; + marketSessions: Partial>; + outliers: boolean; + panels: { + chart: { + chartName: string; + display: string; + index: number; + percent: number; + yAxis: { + name: string; + position: null; + }; + yaxisLHS: string[]; + yaxisRHS: string[]; + }; + }; + periodicity: number; + previousMaxTicks?: number; + range: Partial>; + setSpan: Partial>; + studies?: Partial>; + symbols: [ + { + interval: number; + periodicity: number; + setSpan: Partial>; + symbol: string; + symbolObject: ActiveSymbols[number]; + timeUnit: string; + } + ]; + timeUnit: string; + volumeUnderlay: boolean; +}; +type TChartStateChangeOption = { symbol: string | undefined; isClosed: boolean }; +type TContractDataForGTM = PriceProposalRequest & + ReturnType & { + buy_price: number; + }; +type TPrevChartLayout = + | (TChartLayout & { + isDone?: VoidFunction; + is_used?: boolean; + }) + | null; +type TContractTypesList = { + [key: string]: { + name: string; + categories: TTextValueStrings[]; + }; +}; +type TDurationMinMax = { + [key: string]: { min: number; max: number }; +}; +type TResponse = Res & { + echo_req: Req; + error?: { + code: string; + message: string; + details?: Res[K] & { field: string }; + }; +}; +type TProposalInfo = { + [key: string]: ReturnType; +}; +type TStakeBoundary = Record< + string, + { + min_stake?: number; + max_stake?: number; + } +>; +type TTicksHistoryResponse = TicksHistoryResponse | TicksStreamResponse; +type TToastBoxListItem = { + component: JSX.Element | null; + contract_types: TTextValueStrings[]; + icon: string; + key: string; + label: string; +}; +type TToastBoxObject = { + key?: boolean; + buy_price?: string; + currency?: string; + contract_type?: string; + list?: Array; +}; +export type TValidationErrors = { [key: string]: string[] }; +export type TValidationRules = Omit>, 'duration'> & { + duration: { + rules: [ + string, + { + min: number | null; + max: number | null; + } + ][]; + }; +}; const store_name = 'trade_store'; -const g_subscribers_map = {}; // blame amin.m +const g_subscribers_map: Partial>> = {}; // blame amin.m export default class TradeStore extends BaseStore { // Control values @@ -59,53 +191,53 @@ export default class TradeStore extends BaseStore { has_equals_only = false; // Underlying - symbol; + symbol = ''; is_market_closed = false; previous_symbol = ''; - active_symbols = []; + active_symbols: ActiveSymbols = []; - form_components = []; + form_components: string[] = []; // Contract Type contract_expiry_type = ''; contract_start_type = ''; contract_type = ''; - contract_types_list = {}; - trade_types = {}; + contract_types_list: TContractTypesList = {}; + trade_types: { [key: string]: string } = {}; // Amount amount = 10; basis = ''; - basis_list = []; + basis_list: Array = []; currency = ''; - stake_boundary = {}; + stake_boundary: Partial = {}; // Duration duration = 5; - duration_min_max = {}; + duration_min_max: TDurationMinMax = {}; duration_unit = ''; - duration_units_list = []; - expiry_date = ''; - expiry_epoch = ''; - expiry_time = ''; - expiry_type = 'duration'; + duration_units_list: Array = []; + expiry_date: string | null = ''; + expiry_epoch: number | string = ''; + expiry_time: string | null = ''; + expiry_type: string | null = 'duration'; // Barrier barrier_1 = ''; barrier_2 = ''; barrier_count = 0; - barriers = []; + main_barrier: ChartBarrierStore | null = null; + barriers: TBarriers = []; hovered_barrier = ''; - main_barrier = null; - barrier_choices = []; + barrier_choices: string[] = []; // Start Time - start_date = Number(0); // Number(0) refers to 'now' - start_dates_list = []; - start_time = null; - sessions = []; + start_date = 0; // 0 refers to 'now' + start_dates_list: Array<{ text: string; value: number }> = []; + start_time: string | null = null; + sessions: Array<{ open: moment.Moment; close: moment.Moment }> = []; - market_open_times = []; + market_open_times: string[] = []; // End Date Time /** * An array that contains market closing time. @@ -113,43 +245,46 @@ export default class TradeStore extends BaseStore { * e.g. ["04:00:00", "08:00:00"] * */ - market_close_times = []; + market_close_times: string[] = []; // Last Digit last_digit = 5; is_mobile_digit_view_selected = false; // Purchase - proposal_info = {}; - purchase_info = {}; + proposal_info: TProposalInfo = {}; + purchase_info: Partial = {}; // Chart loader observables - is_chart_loading; + is_chart_loading?: boolean; should_show_active_symbols_loading = false; // Accumulator trade params - accumulator_range_list = []; + accumulator_range_list: number[] = []; growth_rate = 0.03; maximum_payout = 0; maximum_ticks = 0; - ticks_history_stats = {}; + ticks_history_stats: { + ticks_stayed_in?: number[]; + last_tick_epoch?: number; + } = {}; tick_size_barrier = 0; // Multiplier trade params - multiplier; - multiplier_range_list = []; - stop_loss; - take_profit; + multiplier = 0; + multiplier_range_list: number[] = []; + stop_loss?: string; + take_profit?: string; has_stop_loss = false; has_take_profit = false; has_cancellation = false; - commission; - cancellation_price; - stop_out; - expiration; - hovered_contract_type; + commission?: string | number; + cancellation_price?: number; + stop_out?: number; + expiration?: number; + hovered_contract_type?: string | null; cancellation_duration = '60m'; - cancellation_range_list = []; + cancellation_range_list: Array = []; // Turbos trade params long_barriers = {}; @@ -162,19 +297,18 @@ export default class TradeStore extends BaseStore { is_trade_params_expanded = true; //Toastbox - contract_purchase_toast_box; + contract_purchase_toast_box?: TToastBoxObject; - addTickByProposal = () => null; debouncedProposal = debounce(this.requestProposal, 500); - proposal_requests = {}; + proposal_requests: Partial> = {}; is_purchasing_contract = false; - initial_barriers; + initial_barriers?: { barrier_1: string; barrier_2: string }; is_initial_barrier_applied = false; should_skip_prepost_lifecycle = false; - constructor({ root_store }) { + constructor({ root_store }: { root_store: TCoreStores }) { const local_storage_properties = [ 'amount', 'currency', @@ -332,7 +466,6 @@ export default class TradeStore extends BaseStore { resetErrorServices: action.bound, resetPreviousSymbol: action.bound, setActiveSymbols: action.bound, - setAllowEqual: action.bound, setBarrierChoices: action.bound, setChartStatus: action.bound, setContractPurchaseToastbox: action.bound, @@ -342,14 +475,12 @@ export default class TradeStore extends BaseStore { setMarketStatus: action.bound, setMobileDigitView: action.bound, setPreviousSymbol: action.bound, - setPurchaseSpotBarrier: action.bound, setSkipPrePostLifecycle: action.bound, setStakeBoundary: action.bound, setTradeStatus: action.bound, show_digits_stats: computed, themeChangeListener: action.bound, updateBarrierColor: action.bound, - updateLimitOrderBarriers: action.bound, updateStore: action.bound, updateSymbol: action.bound, vanilla_trade_type: observable, @@ -389,10 +520,10 @@ export default class TradeStore extends BaseStore { () => [this.has_stop_loss, this.has_take_profit], () => { if (!this.has_stop_loss) { - this.validation_errors.stop_loss = []; + (this.validation_errors as TValidationErrors).stop_loss = []; } if (!this.has_take_profit) { - this.validation_errors.take_profit = []; + (this.validation_errors as TValidationErrors).take_profit = []; } } ); @@ -406,8 +537,8 @@ export default class TradeStore extends BaseStore { } else { // we need to remove these two validation rules on contract_type change // to be able to remove any existing Stop loss / Take profit validation errors - delete this.validation_rules.stop_loss; - delete this.validation_rules.take_profit; + delete (this.validation_rules as TValidationRules).stop_loss; + delete (this.validation_rules as TValidationRules).take_profit; } this.resetAccumulatorData(); } @@ -423,7 +554,7 @@ export default class TradeStore extends BaseStore { } ); when( - () => this.accumulator_range_list.length, + () => !!this.accumulator_range_list.length, () => this.setDefaultGrowthRate() ); } @@ -450,14 +581,14 @@ export default class TradeStore extends BaseStore { } } - setSkipPrePostLifecycle(should_skip) { + setSkipPrePostLifecycle(should_skip: boolean) { if (!!should_skip !== !!this.should_skip_prepost_lifecycle) { // to skip assignment if no change is made this.should_skip_prepost_lifecycle = should_skip; } } - setTradeStatus(status) { + setTradeStatus(status: boolean) { this.is_trade_enabled = status; } @@ -502,7 +633,7 @@ export default class TradeStore extends BaseStore { async setActiveSymbols() { const is_on_mf_account = this.root_store.client.landing_company_shortcode === 'maltainvest'; const hide_close_mx_mlt_storage_flag = !!parseInt( - localStorage.getItem('hide_close_mx_mlt_account_notification') + localStorage.getItem('hide_close_mx_mlt_account_notification') ?? '' ); const is_logged_in = this.root_store.client.is_logged_in; const clients_country = this.root_store.client.clients_country; @@ -570,7 +701,9 @@ export default class TradeStore extends BaseStore { runInAction(() => { const contract_categories = ContractType.getContractCategories(); this.processNewValuesAsync({ - ...contract_categories, + ...(contract_categories as Pick & { + has_only_forward_starting_contracts: boolean; + }), ...ContractType.getContractType(contract_categories.contract_types_list, this.contract_type), }); this.processNewValuesAsync(ContractType.getContractValues(this)); @@ -609,7 +742,7 @@ export default class TradeStore extends BaseStore { ); } - async onChangeMultiple(values) { + async onChangeMultiple(values: Partial) { Object.keys(values).forEach(name => { if (!(name in this)) { throw new Error(`Invalid Argument: ${name}`); @@ -620,7 +753,7 @@ export default class TradeStore extends BaseStore { this.validateAllProperties(); // then run validation before sending proposal } - async onChange(e) { + async onChange(e: { target: { name: string; value: unknown } }) { const { name, value } = e.target; if (name === 'symbol' && value) { // set trade params skeleton and chart loader to true until processNewValuesAsync resolves @@ -651,19 +784,15 @@ export default class TradeStore extends BaseStore { this.root_store.common.setSelectedContractType(this.contract_type); } - setHoveredBarrier(hovered_value) { + setHoveredBarrier(hovered_value: string) { this.hovered_barrier = hovered_value; } - setPreviousSymbol(symbol) { + setPreviousSymbol(symbol: string) { if (this.previous_symbol !== symbol) this.previous_symbol = symbol; } - setAllowEqual(is_equal) { - this.is_equal = is_equal; - } - - setIsTradeParamsExpanded(value) { + setIsTradeParamsExpanded(value: boolean) { this.is_trade_params_expanded = value; } @@ -679,13 +808,13 @@ export default class TradeStore extends BaseStore { }); } - updateBarrierColor(is_dark_mode) { + updateBarrierColor(is_dark_mode: boolean) { if (this.main_barrier) { this.main_barrier.updateBarrierColor(is_dark_mode); } } - onHoverPurchase(is_over, contract_type) { + onHoverPurchase(is_over: boolean, contract_type?: string) { if (this.is_accumulator) return; if (this.is_purchase_enabled && this.main_barrier && !this.is_multiplier) { this.main_barrier.updateBarrierShade(is_over, contract_type); @@ -698,50 +827,14 @@ export default class TradeStore extends BaseStore { barriers: this.root_store.portfolio.barriers, is_over, contract_type, - contract_info: this.proposal_info[contract_type], - }); - } - - setPurchaseSpotBarrier(is_over, position) { - const key = 'PURCHASE_SPOT_BARRIER'; - if (!is_over) { - removeBarrier(this.root_store.portfolio.barriers, key); - return; - } - - let purchase_spot_barrier = this.root_store.portfolio.barriers.find(b => b.key === key); - if (purchase_spot_barrier) { - if (purchase_spot_barrier.high !== +position.contract_info.entry_spot) { - purchase_spot_barrier.onChange({ - high: position.contract_info.entry_spot, - }); - } - } else { - purchase_spot_barrier = new ChartBarrierStore(position.contract_info.entry_spot); - purchase_spot_barrier.key = key; - purchase_spot_barrier.draggable = false; - purchase_spot_barrier.hideOffscreenBarrier = true; - purchase_spot_barrier.isSingleBarrier = true; - purchase_spot_barrier.updateBarrierColor(this.root_store.ui.is_dark_mode_on); - this.barriers.push(purchase_spot_barrier); - this.root_store.portfolio.barriers.push(purchase_spot_barrier); - } - } - - updateLimitOrderBarriers(is_over, position) { - const contract_info = position.contract_info; - const { barriers } = this; - setLimitOrderBarriers({ - barriers, - contract_info, - contract_type: contract_info.contract_type, - is_over, + contract_info: this.proposal_info[contract_type ?? ''], }); } clearLimitOrderBarriers() { this.hovered_contract_type = null; const { barriers } = this; + //@ts-expect-error: TODO: check if type error is gone after limit-orders.js is migrated to ts setLimitOrderBarriers({ barriers, is_over: false, @@ -761,19 +854,19 @@ export default class TradeStore extends BaseStore { return this.root_store.portfolio.barriers && toJS(this.root_store.portfolio.barriers); } - setMainBarrier = proposal_info => { + setMainBarrier = (proposal_info: PriceProposalRequest) => { if (!proposal_info) { return; } - const { barrier, contract_type, high_barrier, low_barrier } = proposal_info; - + const { contract_type, barrier, barrier2 } = proposal_info; if (isBarrierSupported(contract_type)) { const color = this.root_store.ui.is_dark_mode_on ? BARRIER_COLORS.DARK_GRAY : BARRIER_COLORS.GRAY; // create barrier only when it's available in response this.main_barrier = new ChartBarrierStore( - this.hovered_barrier || barrier || high_barrier, - low_barrier, + this.hovered_barrier || barrier, + barrier2, + //@ts-expect-error need to typescript migrate chart-barrier-store.js first this.onChartBarrierChange, { color: this.hovered_barrier ? getHoveredColor(contract_type) : color, @@ -789,14 +882,14 @@ export default class TradeStore extends BaseStore { onPurchase = debounce(this.processPurchase, 300); - processPurchase(proposal_id, price, type) { + processPurchase(proposal_id: string, price: string, type: string) { if (!this.is_purchase_enabled) return; if (proposal_id) { this.is_purchase_enabled = false; this.is_purchasing_contract = true; const is_tick_contract = this.duration_unit === 't'; processPurchase(proposal_id, price).then( - action(response => { + action((response: TResponse) => { if (!this.is_trade_component_mounted) { this.enablePurchase(); this.is_purchasing_contract = false; @@ -826,7 +919,7 @@ export default class TradeStore extends BaseStore { if (this.proposal_info[type] && this.proposal_info[type].id !== proposal_id) { throw new Error('Proposal ID does not match.'); } - const contract_data = { + const contract_data: TContractDataForGTM = { ...this.proposal_requests[type], ...this.proposal_info[type], buy_price: response.buy.buy_price, @@ -837,8 +930,8 @@ export default class TradeStore extends BaseStore { if (contract_id) { const shortcode = response.buy.shortcode; const { category, underlying } = extractInfoFromShortcode(shortcode); - const is_digit_contract = isDigitContractType(category.toUpperCase()); - const contract_type = category.toUpperCase(); + const is_digit_contract = isDigitContractType(category?.toUpperCase() ?? ''); + const contract_type = category?.toUpperCase(); this.root_store.contract_trade.addContract({ contract_id, start_time, @@ -892,10 +985,10 @@ export default class TradeStore extends BaseStore { const el_purchase_value = document.getElementsByClassName('trade-container__price-info'); const el_purchase_buttons = document.getElementsByClassName('btn-purchase'); [].forEach.bind(el_purchase_buttons, el => { - el.classList.add('btn-purchase--disabled'); + (el as HTMLButtonElement).classList.add('btn-purchase--disabled'); })(); [].forEach.bind(el_purchase_value, el => { - el.classList.add('trade-container__price-info--fade'); + (el as HTMLDivElement).classList.add('trade-container__price-info--fade'); })(); }; @@ -904,11 +997,11 @@ export default class TradeStore extends BaseStore { * @param {Object} new_state - new values to update the store with * @return {Object} returns the object having only those values that are updated */ - updateStore(new_state) { + updateStore(new_state: Partial) { Object.keys(cloneObject(new_state) || {}).forEach(key => { if (key === 'root_store' || ['validation_rules', 'validation_errors', 'currency'].indexOf(key) > -1) return; - if (JSON.stringify(this[key]) === JSON.stringify(new_state[key])) { - delete new_state[key]; + if (JSON.stringify(this[key as keyof this]) === JSON.stringify(new_state[key as keyof TradeStore])) { + delete new_state[key as keyof TradeStore]; } else { if (key === 'symbol') { this.is_purchase_enabled = false; @@ -919,7 +1012,7 @@ export default class TradeStore extends BaseStore { new_state.start_date = parseInt(new_state.start_date); } - this[key] = new_state[key]; + this[key as keyof this] = new_state[key as keyof TradeStore]; // validation is done in mobx intercept (base_store.js) // when barrier_1 is set, it is compared with store.barrier_2 (which is not updated yet) @@ -932,9 +1025,9 @@ export default class TradeStore extends BaseStore { } async processNewValuesAsync( - obj_new_values = {}, + obj_new_values: Partial = {}, is_changed_by_user = false, - obj_old_values = {}, + obj_old_values: Partial | null = {}, should_forget_first = true ) { // To switch to rise_fall_equal contract type when allow equal is checked on first page refresh or @@ -968,7 +1061,7 @@ export default class TradeStore extends BaseStore { savePreviousChartMode('', null); } - if (/\bduration\b/.test(Object.keys(obj_new_values))) { + if (/\bduration\b/.test(Object.keys(obj_new_values) as unknown as string)) { // TODO: fix this in input-field.jsx if (typeof obj_new_values.duration === 'string') { obj_new_values.duration = +obj_new_values.duration; @@ -980,19 +1073,19 @@ export default class TradeStore extends BaseStore { this.forgetAllProposal(); this.proposal_requests = {}; } - if (is_changed_by_user && /\bcurrency\b/.test(Object.keys(obj_new_values))) { + if (is_changed_by_user && /\bcurrency\b/.test(Object.keys(obj_new_values) as unknown as string)) { const prev_currency = obj_old_values?.currency || this.currency; const has_currency_changed = obj_new_values.currency !== prev_currency; const should_reset_stake = - isCryptocurrency(obj_new_values.currency) || + isCryptocurrency(obj_new_values.currency ?? '') || // For switch between fiat and crypto and vice versa - isCryptocurrency(obj_new_values.currency) !== isCryptocurrency(prev_currency); + isCryptocurrency(obj_new_values.currency ?? '') !== isCryptocurrency(prev_currency); if (has_currency_changed && should_reset_stake) { - obj_new_values.amount = obj_new_values.amount || getMinPayout(obj_new_values.currency); + obj_new_values.amount = obj_new_values.amount || getMinPayout(obj_new_values.currency ?? ''); } - this.currency = obj_new_values.currency; + this.currency = obj_new_values.currency ?? ''; } let has_only_forward_starting_contracts; @@ -1000,7 +1093,7 @@ export default class TradeStore extends BaseStore { if (Object.keys(obj_new_values).includes('symbol')) { this.setPreviousSymbol(this.symbol); await Symbol.onChangeSymbolAsync(obj_new_values.symbol); - this.setMarketStatus(isMarketClosed(this.active_symbols, obj_new_values.symbol)); + this.setMarketStatus(isMarketClosed(this.active_symbols, obj_new_values.symbol ?? '')); has_only_forward_starting_contracts = ContractType.getContractCategories().has_only_forward_starting_contracts; } @@ -1011,7 +1104,10 @@ export default class TradeStore extends BaseStore { const new_state = this.updateStore(cloneObject(obj_new_values)); - if (is_changed_by_user || /\b(symbol|contract_types_list)\b/.test(Object.keys(new_state))) { + if ( + is_changed_by_user || + /\b(symbol|contract_types_list)\b/.test(Object.keys(new_state) as unknown as string) + ) { this.updateStore({ // disable purchase button(s), clear contract info is_purchase_enabled: false, @@ -1036,7 +1132,7 @@ export default class TradeStore extends BaseStore { ...(!this.is_initial_barrier_applied ? this.initial_barriers : {}), }); this.is_initial_barrier_applied = true; - if (/\b(contract_type|currency)\b/.test(Object.keys(new_state))) { + if (/\b(contract_type|currency)\b/.test(Object.keys(new_state) as unknown as string)) { this.validateAllProperties(); } this.debouncedProposal(); @@ -1057,11 +1153,11 @@ export default class TradeStore extends BaseStore { return isDigitTradeType(this.contract_type); } - setMobileDigitView(bool) { + setMobileDigitView(bool: boolean) { this.is_mobile_digit_view_selected = bool; } - pushPurchaseDataToGtm(contract_data) { + pushPurchaseDataToGtm(contract_data: TContractDataForGTM) { const data = { event: 'buy_contract', bom_ui: 'new', @@ -1125,11 +1221,11 @@ export default class TradeStore extends BaseStore { if (length > 0) WS.forgetAll('proposal'); } - setMarketStatus(status) { + setMarketStatus(status: boolean) { this.is_market_closed = status; } - onProposalResponse(response) { + onProposalResponse(response: TResponse) { const { contract_type } = response.echo_req; const prev_proposal_info = getPropertyValue(this.proposal_info, contract_type) || {}; const obj_prev_contract_basis = getPropertyValue(prev_proposal_info, 'obj_contract_basis') || {}; @@ -1195,10 +1291,9 @@ export default class TradeStore extends BaseStore { } if (this.hovered_contract_type === contract_type) { - this.addTickByProposal(response); setLimitOrderBarriers({ barriers: this.root_store.portfolio.barriers, - contract_info: this.proposal_info[this.hovered_contract_type], + contract_info: this.proposal_info[this.hovered_contract_type ?? ''], contract_type, is_over: true, }); @@ -1239,7 +1334,7 @@ export default class TradeStore extends BaseStore { if ((this.is_turbos || this.is_vanilla) && response.error.details?.barrier_choices) { const { barrier_choices, max_stake, min_stake } = response.error.details; this.setStakeBoundary(contract_type, min_stake, max_stake); - this.setBarrierChoices(barrier_choices); + this.setBarrierChoices(barrier_choices as string[]); if (!this.barrier_choices.includes(this.barrier_1)) { // Since on change of duration `proposal` API call is made which returns a new set of barrier values. // The new list is set and the mid value is assigned @@ -1256,8 +1351,8 @@ export default class TradeStore extends BaseStore { } else { this.validateAllProperties(); if (this.is_turbos || this.is_vanilla) { - const { max_stake, min_stake, barrier_choices } = response.proposal; - this.setBarrierChoices(barrier_choices); + const { max_stake, min_stake, barrier_choices } = response.proposal ?? {}; + this.setBarrierChoices(barrier_choices as string[]); this.setStakeBoundary(contract_type, min_stake, max_stake); } } @@ -1267,15 +1362,15 @@ export default class TradeStore extends BaseStore { } } - onChartBarrierChange(barrier_1, barrier_2) { + onChartBarrierChange(barrier_1: string, barrier_2: string) { this.processNewValuesAsync({ barrier_1, barrier_2 }, true); } onAllowEqualsChange() { - this.processNewValuesAsync({ contract_type: parseInt(this.is_equal) ? 'rise_fall_equal' : 'rise_fall' }, true); + this.processNewValuesAsync({ contract_type: this.is_equal ? 'rise_fall_equal' : 'rise_fall' }, true); } - updateSymbol(underlying) { + updateSymbol(underlying: string) { if (!underlying) return; this.onChange({ target: { @@ -1287,13 +1382,15 @@ export default class TradeStore extends BaseStore { changeDurationValidationRules() { if (this.expiry_type === 'endtime') { - this.validation_errors.duration = []; + (this.validation_errors as TValidationErrors).duration = []; return; } - if (!this.validation_rules.duration) return; + if (!(this.validation_rules as TValidationRules).duration) return; - const index = this.validation_rules.duration.rules.findIndex(item => item[0] === 'number'); + const index = (this.validation_rules as TValidationRules).duration.rules.findIndex( + item => item[0] === 'number' + ); const limits = this.duration_min_max[this.contract_expiry_type] || false; if (limits) { @@ -1302,11 +1399,12 @@ export default class TradeStore extends BaseStore { max: convertDurationLimit(+limits.max, this.duration_unit), }; - if (index > -1) { - this.validation_rules.duration.rules[index][1] = duration_options; + if (Number(index) > -1) { + (this.validation_rules as TValidationRules).duration.rules[Number(index)][1] = duration_options; } else { - this.validation_rules.duration.rules.push(['number', duration_options]); + (this.validation_rules as TValidationRules).duration.rules.push(['number', duration_options]); } + //@ts-expect-error: TODO: check if TS error is gone after base-store.ts from shared package is used here instead of base-store.js this.validateProperty('duration', this.duration); } } @@ -1358,11 +1456,11 @@ export default class TradeStore extends BaseStore { return Promise.resolve(); } - networkStatusChangeListener(is_online) { + networkStatusChangeListener(is_online: boolean) { this.setTradeStatus(is_online); } - themeChangeListener(is_dark_mode_on) { + themeChangeListener(is_dark_mode_on: boolean) { this.updateBarrierColor(is_dark_mode_on); } @@ -1394,7 +1492,7 @@ export default class TradeStore extends BaseStore { manageMxMltRemovalNotification() { const { addNotificationMessage, client_notifications, notification_messages, unmarkNotificationMessage } = this.root_store.notifications; - const get_notification_messages = JSON.parse(localStorage.getItem('notification_messages')); + const get_notification_messages = JSON.parse(localStorage.getItem('notification_messages') ?? ''); const { has_iom_account, has_malta_account, is_logged_in } = this.root_store.client; unmarkNotificationMessage({ key: 'close_mx_mlt_account' }); if (get_notification_messages !== null && is_logged_in && (has_iom_account || has_malta_account)) { @@ -1402,7 +1500,7 @@ export default class TradeStore extends BaseStore { () => is_logged_in && notification_messages.length === 0, () => { const hidden_close_account_notification = - parseInt(localStorage.getItem('hide_close_mx_mlt_account_notification')) === 1; + parseInt(localStorage.getItem('hide_close_mx_mlt_account_notification') ?? '') === 1; const should_retain_notification = (has_iom_account || has_malta_account) && !hidden_close_account_notification; if (should_retain_notification) { @@ -1413,11 +1511,11 @@ export default class TradeStore extends BaseStore { } } - setChartStatus(status) { + setChartStatus(status: boolean) { this.is_chart_loading = status; } - async initAccountCurrency(new_currency) { + async initAccountCurrency(new_currency: string) { if (this.currency === new_currency) return; await this.processNewValuesAsync({ currency: new_currency }, true, { currency: this.currency }, false); @@ -1449,7 +1547,7 @@ export default class TradeStore extends BaseStore { this.resetAccumulatorData(); } - prev_chart_layout = null; + prev_chart_layout: TPrevChartLayout = null; get chart_layout() { let layout = null; @@ -1463,35 +1561,37 @@ export default class TradeStore extends BaseStore { return this.contract_type === 'multiplier' && /^cry/.test(this.symbol); } - exportLayout(layout) { + exportLayout(layout: TChartLayout) { delete layout.previousMaxTicks; // TODO: fix it in smartcharts this.prev_chart_layout = layout; - this.prev_chart_layout.isDone = () => { - this.prev_chart_layout.is_used = true; - this.setChartStatus(false); - }; + if (this.prev_chart_layout) { + this.prev_chart_layout.isDone = () => { + if (this.prev_chart_layout) this.prev_chart_layout.is_used = true; + this.setChartStatus(false); + }; + } } // ---------- WS ---------- - wsSubscribe = (req, callback) => { - const passthrough_callback = (...args) => { + wsSubscribe = (req: TicksHistoryRequest, callback: (response: TTicksHistoryResponse) => void) => { + const passthrough_callback = (...args: [TTicksHistoryResponse]) => { callback(...args); if (this.is_accumulator) { let current_spot_data = {}; if ('tick' in args[0]) { - const { epoch, quote, symbol } = args[0].tick; + const { epoch, quote, symbol } = args[0].tick as TickSpotData; if (this.symbol !== symbol) return; current_spot_data = { current_spot: quote, current_spot_time: epoch, }; } else if ('history' in args[0]) { - const { prices, times } = args[0].history; + const { prices, times } = args[0].history as History; const symbol = args[0].echo_req.ticks_history; if (this.symbol !== symbol) return; current_spot_data = { - current_spot: prices[prices.length - 1], - current_spot_time: times[times.length - 1], + current_spot: prices?.[prices?.length - 1], + current_spot_time: times?.[times?.length - 1], }; } else { return; @@ -1506,21 +1606,21 @@ export default class TradeStore extends BaseStore { } }; - wsForget = req => { + wsForget = (req: TicksHistoryRequest) => { const key = JSON.stringify(req); if (g_subscribers_map[key]) { - g_subscribers_map[key].unsubscribe(); + g_subscribers_map[key]?.unsubscribe(); delete g_subscribers_map[key]; } // WS.forget('ticks_history', callback, match); }; - wsForgetStream = stream_id => { + wsForgetStream = (stream_id: string) => { WS.forgetStream(stream_id); }; - wsSendRequest = req => { - if (req.time) { + wsSendRequest = (req: TradingTimesRequest | ActiveSymbolsRequest | ServerTimeRequest) => { + if ('time' in req) { return ServerTime.timePromise().then(server_time => { if (server_time) { return { @@ -1531,16 +1631,16 @@ export default class TradeStore extends BaseStore { return WS.time(); }); } - if (req.active_symbols) { + if ('active_symbols' in req) { return WS.activeSymbols('brief'); } - if (req.trading_times) { + if ('trading_times' in req) { return WS.tradingTimes(req.trading_times); } return WS.storage.send(req); }; - chartStateChange(state, option) { + chartStateChange(state: string, option?: TChartStateChangeOption) { const market_close_prop = 'isClosed'; switch (state) { case 'MARKET_STATE_CHANGE': @@ -1553,10 +1653,6 @@ export default class TradeStore extends BaseStore { } } - refToAddTick = ref => { - this.addTickByProposal = ref; - }; - get has_alternative_source() { return this.is_multiplier && !!this.hovered_contract_type; } @@ -1577,7 +1673,7 @@ export default class TradeStore extends BaseStore { return this.contract_type === 'vanilla'; } - setContractPurchaseToastbox(response) { + setContractPurchaseToastbox(response: Buy) { const list = getAvailableContractTypes(this.contract_types_list, unsupported_contract_types_list); return (this.contract_purchase_toast_box = { @@ -1593,7 +1689,7 @@ export default class TradeStore extends BaseStore { this.contract_purchase_toast_box = undefined; } - async getFirstOpenMarket(markets_to_search) { + async getFirstOpenMarket(markets_to_search: string[]) { if (this.active_symbols?.length) { return findFirstOpenMarket(this.active_symbols, markets_to_search); } @@ -1605,7 +1701,11 @@ export default class TradeStore extends BaseStore { return findFirstOpenMarket(active_symbols, markets_to_search); } - setBarrierChoices(barrier_choices) { + setStakeBoundary(type: string, min_stake?: number, max_stake?: number) { + this.stake_boundary[type] = { min_stake, max_stake }; + } + + setBarrierChoices(barrier_choices: string[]) { this.barrier_choices = barrier_choices ?? []; if (this.is_turbos) { const stored_barriers_data = { barrier: this.barrier_1, barrier_choices }; @@ -1616,8 +1716,4 @@ export default class TradeStore extends BaseStore { } } } - - setStakeBoundary(type, min_stake, max_stake) { - if (min_stake && max_stake) this.stake_boundary[type] = { min_stake, max_stake }; - } } diff --git a/packages/trader/src/Stores/useTraderStores.tsx b/packages/trader/src/Stores/useTraderStores.tsx index c745965f2f67..25ea546c7b08 100644 --- a/packages/trader/src/Stores/useTraderStores.tsx +++ b/packages/trader/src/Stores/useTraderStores.tsx @@ -1,139 +1,11 @@ import React from 'react'; import { useStore } from '@deriv/stores'; -import TradeStore from './Modules/Trading/trade-store'; -import moment from 'moment'; -import { TTextValueStrings } from '../Types/common-prop.type'; +import TradeStore, { TValidationErrors, TValidationRules } from './Modules/Trading/trade-store'; -type TContractTypesList = { - [key: string]: { - name: string; - categories: TTextValueStrings[]; - }; -}; - -type TContractCategoriesList = { - Multipliers: TContractTypesList; - 'Ups & Downs': TContractTypesList; - 'Highs & Lows': TContractTypesList; - 'Ins & Outs': TContractTypesList; - 'Look Backs': TContractTypesList; - Digits: TContractTypesList; - Vanillas: TContractTypesList; - Accumulators: TContractTypesList; -}; - -type TToastBoxListItem = { - contract_category: string; - contract_types: [ - { - text: string; - value: string; - } - ]; -}; - -type TToastBoxObject = { - key?: boolean; - buy_price?: number; - currency?: string; - contract_type?: string; - list?: TToastBoxListItem[]; -}; - -type TOverrideTradeStore = Omit< - TradeStore, - | 'accumulator_range_list' - | 'barriers' - | 'basis_list' - | 'cancellation_price' - | 'cancellation_range_list' - | 'clearContractPurchaseToastBox' - | 'contract_purchase_toast_box' - | 'contract_types_list' - | 'duration_min_max' - | 'duration_units_list' - | 'expiry_date' - | 'expiry_time' - | 'expiry_type' - | 'form_components' - | 'market_close_times' - | 'market_open_times' - | 'multiplier_range_list' - | 'multiplier' - | 'sessions' - | 'setIsTradeParamsExpanded' - | 'stake_boundary' - | 'start_dates_list' - | 'start_time' - | 'symbol' - | 'take_profit' - | 'proposal_info' - | 'trade_types' - | 'ticks_history_stats' - | 'validation_errors' -> & { - accumulator_range_list?: number[]; - basis_list: Array; - cancellation_price?: number; - cancellation_range_list: Array; - clearContractPurchaseToastBox: () => void; - contract_purchase_toast_box: TToastBoxObject; - contract_types_list: TContractCategoriesList; - duration_min_max: { - [key: string]: { min: number; max: number }; - }; - duration_units_list: Array; - expiry_date: string | null; - expiry_time: string | null; - expiry_type: string | null; - form_components: string[]; - market_open_times: string[]; - market_close_times: string[]; - multiplier: number; - multiplier_range_list: number[]; - proposal_info: { - [key: string]: { - barrier?: string; - has_error?: boolean; - id: string; - has_increased?: boolean; - message?: string; - cancellation?: { - ask_price: number; - date_expiry: number; - }; - growth_rate?: number; - returns?: string; - stake: string; - spot?: string; - }; - }; - sessions: Array<{ open: moment.Moment; close: moment.Moment }>; - setIsTradeParamsExpanded: (value: boolean) => void; - stake_boundary: { - VANILLALONGCALL: { - min_stake: string; - max_stake: string; - }; - VANILLALONGPUT: { - min_stake: string; - max_stake: string; - }; - }; - start_dates_list: Array<{ text: string; value: number }>; - start_time: string | null; - symbol: string; - take_profit?: string; - ticks_history_stats: { - ticks_stayed_in?: number[]; - last_tick_epoch?: number; - }; - trade_types: { [key: string]: string }; - validation_errors?: { - amount?: string[]; - barrier_1?: string[]; - barrier_2?: string[]; - }; +type TOverrideTradeStore = Omit & { + //TODO: these types can be removed from here and trade-store after base-store is migrated to TS + validation_errors?: TValidationErrors; + validation_rules: TValidationRules; }; const TraderStoreContext = React.createContext(null); From 0d29044bd70594fdf4e52c8a5ba99a0825efca7a Mon Sep 17 00:00:00 2001 From: kate-deriv Date: Thu, 10 Aug 2023 17:20:12 +0300 Subject: [PATCH 27/55] chore: empty commit From 781447cc36fe5948b8b402a201a54589f720b702 Mon Sep 17 00:00:00 2001 From: kate-deriv Date: Thu, 10 Aug 2023 18:01:30 +0300 Subject: [PATCH 28/55] fix: add lost mocked --- packages/stores/src/mockStore.ts | 1 + packages/stores/types.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/stores/src/mockStore.ts b/packages/stores/src/mockStore.ts index 82fd95b4d7a1..a1c9bc8e9ca3 100644 --- a/packages/stores/src/mockStore.ts +++ b/packages/stores/src/mockStore.ts @@ -266,6 +266,7 @@ const mock = (): TStores & { is_mock: boolean } => { changeCurrentLanguage: jest.fn(), changeSelectedLanguage: jest.fn(), is_network_online: false, + network_status: {}, services_error: {}, server_time: undefined, is_language_changing: false, diff --git a/packages/stores/types.ts b/packages/stores/types.ts index 3333c5a94af7..750176c7f306 100644 --- a/packages/stores/types.ts +++ b/packages/stores/types.ts @@ -414,6 +414,7 @@ type TCommonStore = { setAppstorePlatform: (value: string) => void; app_routing_history: TAppRoutingHistory[]; getExchangeRate: (from_currency: string, to_currency: string) => Promise; + network_status: Record | { [key: string]: string }; }; type TUiStore = { From a243f5cc5ca10a12288b422ccc5f631725388c0c Mon Sep 17 00:00:00 2001 From: Henry Hein Date: Fri, 11 Aug 2023 11:14:28 +0800 Subject: [PATCH 29/55] fix: resolve comments --- packages/stores/src/mockStore.ts | 5 ----- packages/stores/types.ts | 5 ----- packages/trader/build/constants.js | 1 + .../src/Modules/Trading/Containers/trade-params-mobile.tsx | 2 +- packages/trader/src/Stores/Modules/Trading/trade-store.ts | 4 ++-- 5 files changed, 4 insertions(+), 13 deletions(-) diff --git a/packages/stores/src/mockStore.ts b/packages/stores/src/mockStore.ts index 6ed892f2d973..35f313ccbe1b 100644 --- a/packages/stores/src/mockStore.ts +++ b/packages/stores/src/mockStore.ts @@ -459,11 +459,6 @@ const mock = (): TStores & { is_mock: boolean } => { update: jest.fn(), unmount: jest.fn(), }, - gtm: {}, - pushwoosh: {}, - contract_replay: {}, - chart_barrier_store: {}, - active_symbols: {}, }; }; diff --git a/packages/stores/types.ts b/packages/stores/types.ts index 179f9953c239..b9c0c97af204 100644 --- a/packages/stores/types.ts +++ b/packages/stores/types.ts @@ -640,11 +640,6 @@ export type TCoreStores = { modules: Record; notifications: TNotificationStore; traders_hub: TTradersHubStore; - gtm: Record; - pushwoosh: Record; - contract_replay: Record; - chart_barrier_store: Record; - active_symbols: Record; }; export type TStores = TCoreStores & { diff --git a/packages/trader/build/constants.js b/packages/trader/build/constants.js index 1a8cab525725..2560c6ae2b60 100644 --- a/packages/trader/build/constants.js +++ b/packages/trader/build/constants.js @@ -42,6 +42,7 @@ const ALIASES = { Services: path.resolve(__dirname, '../src/Services'), Stores: path.resolve(__dirname, '../src/Stores'), Translations: path.resolve(__dirname, '../src/public/translations'), + Types: path.resolve(__dirname, '../src/Types'), Utils: path.resolve(__dirname, '../src/Utils'), }; diff --git a/packages/trader/src/Modules/Trading/Containers/trade-params-mobile.tsx b/packages/trader/src/Modules/Trading/Containers/trade-params-mobile.tsx index cec34207adcd..5fe6b07ef006 100644 --- a/packages/trader/src/Modules/Trading/Containers/trade-params-mobile.tsx +++ b/packages/trader/src/Modules/Trading/Containers/trade-params-mobile.tsx @@ -4,7 +4,7 @@ import AmountMobile from 'Modules/Trading/Components/Form/TradeParams/amount-mob import Barrier from 'Modules/Trading/Components/Form/TradeParams/barrier'; import DurationMobile from 'Modules/Trading/Components/Form/TradeParams/Duration/duration-mobile.jsx'; import LastDigit from 'Modules/Trading/Components/Form/TradeParams/last-digit'; -import { TTextValueStrings } from '../../../Types/common-prop.type'; +import { TTextValueStrings } from 'Types'; import { observer, useStore } from '@deriv/stores'; import { useTraderStore } from 'Stores/useTraderStores'; import React from 'react'; diff --git a/packages/trader/src/Stores/Modules/Trading/trade-store.ts b/packages/trader/src/Stores/Modules/Trading/trade-store.ts index d8b98a18e809..d90c3630da9f 100644 --- a/packages/trader/src/Stores/Modules/Trading/trade-store.ts +++ b/packages/trader/src/Stores/Modules/Trading/trade-store.ts @@ -41,7 +41,7 @@ import { action, computed, makeObservable, observable, override, reaction, runIn import { createProposalRequests, getProposalErrorField, getProposalInfo } from './Helpers/proposal'; import { getHoveredColor } from './Helpers/barrier-utils'; import BaseStore from '../../base-store'; -import { TTextValueStrings } from '../../../Types/common-prop.type'; +import { TTextValueStrings } from 'Types'; import { ChartBarrierStore } from '../SmartChart/chart-barrier-store'; import debounce from 'lodash.debounce'; import { setLimitOrderBarriers } from './Helpers/limit-orders'; @@ -1702,7 +1702,7 @@ export default class TradeStore extends BaseStore { } setStakeBoundary(type: string, min_stake?: number, max_stake?: number) { - this.stake_boundary[type] = { min_stake, max_stake }; + if (min_stake && max_stake) this.stake_boundary[type] = { min_stake, max_stake }; } setBarrierChoices(barrier_choices: string[]) { From 778d74d54af3f80a386c3e99e6cfa9c1b44c8f3e Mon Sep 17 00:00:00 2001 From: kate-deriv <121025168+kate-deriv@users.noreply.github.com> Date: Mon, 14 Aug 2023 09:22:00 +0300 Subject: [PATCH 30/55] Kate / DTRA-354 / Components/Form/Purchase files in Trader package (#21) * refactor: ts migartion of purchase files * refactor: remove duplicated types * refactor: apply suggestions --- .../Elements/payout-per-point-mobile.tsx | 16 +--- .../Components/Elements/purchase-button.jsx | 2 +- .../Components/Elements/purchase-fieldset.jsx | 4 +- ...cel-deal-info.jsx => cancel-deal-info.tsx} | 7 +- .../{contract-info.jsx => contract-info.tsx} | 73 +++++-------------- .../Form/Purchase/value-movement.tsx | 51 +++++++++++++ .../Components/Form/TradeParams/barrier.tsx | 3 +- packages/trader/src/Types/common-prop.type.ts | 1 + 8 files changed, 82 insertions(+), 75 deletions(-) rename packages/trader/src/Modules/Trading/Components/Form/Purchase/{cancel-deal-info.jsx => cancel-deal-info.tsx} (87%) rename packages/trader/src/Modules/Trading/Components/Form/Purchase/{contract-info.jsx => contract-info.tsx} (77%) create mode 100644 packages/trader/src/Modules/Trading/Components/Form/Purchase/value-movement.tsx diff --git a/packages/trader/src/Modules/Trading/Components/Elements/payout-per-point-mobile.tsx b/packages/trader/src/Modules/Trading/Components/Elements/payout-per-point-mobile.tsx index 169dab63fea0..864a811171cf 100644 --- a/packages/trader/src/Modules/Trading/Components/Elements/payout-per-point-mobile.tsx +++ b/packages/trader/src/Modules/Trading/Components/Elements/payout-per-point-mobile.tsx @@ -5,22 +5,10 @@ import Fieldset from 'App/Components/Form/fieldset.jsx'; import { observer } from '@deriv/stores'; import { getContractSubtype, isVanillaContract } from '@deriv/shared'; import { useTraderStore } from 'Stores/useTraderStores'; +import { TProposalTypeInfo } from 'Types'; type TProposalInfo = { - [key: string]: { - has_error?: boolean; - id: string; - has_increased?: boolean; - message?: string; - cancellation?: { - ask_price: number; - date_expiry: number; - }; - growth_rate?: number; - obj_contract_basis?: Record<'text' | 'value', string>; - returns?: string; - stake: string; - }; + [key: string]: TProposalTypeInfo; }; const PayoutPerPointMobile = observer(() => { diff --git a/packages/trader/src/Modules/Trading/Components/Elements/purchase-button.jsx b/packages/trader/src/Modules/Trading/Components/Elements/purchase-button.jsx index 3b43b46a2f3c..0e2026501b99 100644 --- a/packages/trader/src/Modules/Trading/Components/Elements/purchase-button.jsx +++ b/packages/trader/src/Modules/Trading/Components/Elements/purchase-button.jsx @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import React from 'react'; import { DesktopWrapper, MobileWrapper, Money, IconTradeTypes, Text } from '@deriv/components'; import { getContractTypeDisplay } from 'Constants/contract'; -import ContractInfo from 'Modules/Trading/Components/Form/Purchase/contract-info.jsx'; +import ContractInfo from 'Modules/Trading/Components/Form/Purchase/contract-info'; import { getGrowthRatePercentage } from '@deriv/shared'; // TODO [lazy-loading-required] Responsive related components diff --git a/packages/trader/src/Modules/Trading/Components/Elements/purchase-fieldset.jsx b/packages/trader/src/Modules/Trading/Components/Elements/purchase-fieldset.jsx index fd06434b7f23..05b8a7af9e68 100644 --- a/packages/trader/src/Modules/Trading/Components/Elements/purchase-fieldset.jsx +++ b/packages/trader/src/Modules/Trading/Components/Elements/purchase-fieldset.jsx @@ -3,9 +3,9 @@ import React from 'react'; import PropTypes from 'prop-types'; import { DesktopWrapper, MobileWrapper, Popover } from '@deriv/components'; import Fieldset from 'App/Components/Form/fieldset.jsx'; -import ContractInfo from 'Modules/Trading/Components/Form/Purchase/contract-info.jsx'; +import ContractInfo from 'Modules/Trading/Components/Form/Purchase/contract-info'; import PurchaseButton from 'Modules/Trading/Components/Elements/purchase-button.jsx'; -import CancelDealInfo from '../Form/Purchase/cancel-deal-info.jsx'; +import CancelDealInfo from '../Form/Purchase/cancel-deal-info'; const PurchaseFieldset = ({ basis, diff --git a/packages/trader/src/Modules/Trading/Components/Form/Purchase/cancel-deal-info.jsx b/packages/trader/src/Modules/Trading/Components/Form/Purchase/cancel-deal-info.tsx similarity index 87% rename from packages/trader/src/Modules/Trading/Components/Form/Purchase/cancel-deal-info.jsx rename to packages/trader/src/Modules/Trading/Components/Form/Purchase/cancel-deal-info.tsx index cca795272fd7..88a7a3c15e44 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/Purchase/cancel-deal-info.jsx +++ b/packages/trader/src/Modules/Trading/Components/Form/Purchase/cancel-deal-info.tsx @@ -5,18 +5,19 @@ import { isDesktop, isMobile, getDecimalPlaces } from '@deriv/shared'; import { localize } from '@deriv/translations'; import { observer } from '@deriv/stores'; import { useTraderStore } from 'Stores/useTraderStores'; +import { TProposalTypeInfo } from 'Types'; -const CancelDealInfo = observer(({ proposal_info }) => { +const CancelDealInfo = observer(({ proposal_info }: { proposal_info: TProposalTypeInfo }) => { const { currency, has_cancellation } = useTraderStore(); const { id, cancellation, has_error } = proposal_info; const error = has_error || !id; const [is_row_layout, setIsRowLayout] = React.useState(false); - const ref = React.useRef(null); + const ref = React.useRef(null); React.useEffect(() => { if (ref.current) { - const el_height = ref.current.parentElement?.clientHeight; + const el_height = Number(ref.current.parentElement?.clientHeight); if ((el_height > 21 && isDesktop()) || ((el_height > 21 || getDecimalPlaces(currency) > 2) && isMobile())) { setIsRowLayout(true); } else { diff --git a/packages/trader/src/Modules/Trading/Components/Form/Purchase/contract-info.jsx b/packages/trader/src/Modules/Trading/Components/Form/Purchase/contract-info.tsx similarity index 77% rename from packages/trader/src/Modules/Trading/Components/Form/Purchase/contract-info.jsx rename to packages/trader/src/Modules/Trading/Components/Form/Purchase/contract-info.tsx index 56eb0af097a3..8abd3e1b61ec 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/Purchase/contract-info.jsx +++ b/packages/trader/src/Modules/Trading/Components/Form/Purchase/contract-info.tsx @@ -1,44 +1,26 @@ import classNames from 'classnames'; -import PropTypes from 'prop-types'; import React from 'react'; -import { DesktopWrapper, Icon, MobileWrapper, Money, Popover, Text } from '@deriv/components'; +import { DesktopWrapper, MobileWrapper, Money, Popover, Text } from '@deriv/components'; import { Localize, localize } from '@deriv/translations'; import { getContractSubtype, getCurrencyDisplayCode, getLocalizedBasis, getGrowthRatePercentage } from '@deriv/shared'; -import CancelDealInfo from './cancel-deal-info.jsx'; +import CancelDealInfo from './cancel-deal-info'; +import ValueMovement from './value-movement'; +import { TProposalTypeInfo } from 'Types'; -export const ValueMovement = ({ - has_error_or_not_loaded, - proposal_info, - currency, - has_increased, - is_turbos, - is_vanilla, - value, - show_currency = true, -}) => ( -
-
- {!has_error_or_not_loaded && ( - - )} -
-
- {!has_error_or_not_loaded && has_increased !== null && has_increased ? ( - - ) : ( - - )} -
-
-); +type TContractInfo = { + basis: string; + currency: string; + growth_rate: number; + has_increased: boolean; + is_accumulator: boolean; + is_multiplier: boolean; + is_turbos: boolean; + is_vanilla: boolean; + is_loading: boolean; + proposal_info: TProposalTypeInfo; + should_fade: boolean; + type: string; +}; const ContractInfo = ({ basis, @@ -53,7 +35,7 @@ const ContractInfo = ({ should_fade, proposal_info, type, -}) => { +}: TContractInfo) => { const localized_basis = getLocalizedBasis(); const stakeOrPayout = () => { switch (basis) { @@ -72,7 +54,7 @@ const ContractInfo = ({ }; const has_error_or_not_loaded = proposal_info.has_error || !proposal_info.id; - const basis_text = has_error_or_not_loaded ? stakeOrPayout() : proposal_info.obj_contract_basis.text; + const basis_text = has_error_or_not_loaded ? stakeOrPayout() : proposal_info?.obj_contract_basis?.text || ''; const { message, obj_contract_basis, stake } = proposal_info; const setHintMessage = () => { @@ -182,19 +164,4 @@ const ContractInfo = ({ ); }; -ContractInfo.propTypes = { - basis: PropTypes.string, - currency: PropTypes.string, - growth_rate: PropTypes.number, - has_increased: PropTypes.bool, - is_accumulator: PropTypes.bool, - is_multiplier: PropTypes.bool, - is_turbos: PropTypes.bool, - is_vanilla: PropTypes.bool, - is_loading: PropTypes.bool, - proposal_info: PropTypes.object, - should_fade: PropTypes.bool, - type: PropTypes.string, -}; - export default ContractInfo; diff --git a/packages/trader/src/Modules/Trading/Components/Form/Purchase/value-movement.tsx b/packages/trader/src/Modules/Trading/Components/Form/Purchase/value-movement.tsx new file mode 100644 index 000000000000..928e8cf957e0 --- /dev/null +++ b/packages/trader/src/Modules/Trading/Components/Form/Purchase/value-movement.tsx @@ -0,0 +1,51 @@ +import classNames from 'classnames'; +import React from 'react'; +import { Icon, Money } from '@deriv/components'; +import { TProposalTypeInfo } from 'Types'; + +type TValueMovement = { + has_error_or_not_loaded: boolean; + proposal_info?: TProposalTypeInfo; + currency?: string; + has_increased?: boolean; + is_turbos?: boolean; + is_vanilla?: boolean; + value?: number | string; + show_currency?: boolean; +}; + +const ValueMovement = ({ + has_error_or_not_loaded, + proposal_info, + currency, + has_increased, + is_turbos = false, + is_vanilla = false, + value, + show_currency = true, +}: TValueMovement) => ( +
+
+ {!has_error_or_not_loaded && ( + + )} +
+
+ {!has_error_or_not_loaded && has_increased !== null && has_increased ? ( + + ) : ( + + )} +
+
+); + +export default ValueMovement; diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/barrier.tsx b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/barrier.tsx index e2ca0280cf90..4a5087d7482f 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/barrier.tsx +++ b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/barrier.tsx @@ -2,7 +2,7 @@ import classNames from 'classnames'; import React from 'react'; import { DesktopWrapper, Icon, InputField, MobileWrapper, Modal, Text, usePrevious } from '@deriv/components'; import Fieldset from 'App/Components/Form/fieldset.jsx'; -import { ValueMovement } from '../Purchase/contract-info'; +import ValueMovement from '../Purchase/value-movement'; import { observer, useStore } from '@deriv/stores'; import { useTraderStore } from 'Stores/useTraderStores'; import { localize } from '@deriv/translations'; @@ -136,7 +136,6 @@ const Barrier = observer(({ is_minimized, is_absolute_only }: TBarrier) => { {localize('Current Price')} {current_spot && ( - //@ts-expect-error until this component is typescript migrated and some props optional ; returns?: string; stake: string; }; From 79bf3c122d306ef8b6d5343112cab9c5e969a2c7 Mon Sep 17 00:00:00 2001 From: Maryia <103177211+maryia-deriv@users.noreply.github.com> Date: Tue, 15 Aug 2023 03:35:49 +0300 Subject: [PATCH 31/55] maryia/fix: sonarcloud warnings (#7) * fix: sonarcloud warnings * fix: Unexpected end of JSON input --- .../Containers/trade-params-mobile.tsx | 2 +- .../src/Stores/Modules/Trading/trade-store.ts | 21 ++++++++++--------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/packages/trader/src/Modules/Trading/Containers/trade-params-mobile.tsx b/packages/trader/src/Modules/Trading/Containers/trade-params-mobile.tsx index 5fe6b07ef006..c25294e21a6d 100644 --- a/packages/trader/src/Modules/Trading/Containers/trade-params-mobile.tsx +++ b/packages/trader/src/Modules/Trading/Containers/trade-params-mobile.tsx @@ -113,7 +113,7 @@ const TradeParamsModal = observer(({ is_open, toggleModal }: TTradeParamsModal) React.useEffect(() => { setSelectedDuration(duration_unit, duration); - setDurationTabIdx(undefined); + setDurationTabIdx(); // duration and duration_unit can be changed in trade-store when contract type is changed }, [duration, duration_unit]); diff --git a/packages/trader/src/Stores/Modules/Trading/trade-store.ts b/packages/trader/src/Stores/Modules/Trading/trade-store.ts index d90c3630da9f..6976466b403c 100644 --- a/packages/trader/src/Stores/Modules/Trading/trade-store.ts +++ b/packages/trader/src/Stores/Modules/Trading/trade-store.ts @@ -166,6 +166,7 @@ type TToastBoxObject = { contract_type?: string; list?: Array; }; +type TTurbosBarriersData = Record | { barrier: string; barrier_choices: string[] }; export type TValidationErrors = { [key: string]: string[] }; export type TValidationRules = Omit>, 'duration'> & { duration: { @@ -287,8 +288,8 @@ export default class TradeStore extends BaseStore { cancellation_range_list: Array = []; // Turbos trade params - long_barriers = {}; - short_barriers = {}; + long_barriers: TTurbosBarriersData = {}; + short_barriers: TTurbosBarriersData = {}; // Vanilla trade params vanilla_trade_type = 'VANILLALONGCALL'; @@ -633,7 +634,7 @@ export default class TradeStore extends BaseStore { async setActiveSymbols() { const is_on_mf_account = this.root_store.client.landing_company_shortcode === 'maltainvest'; const hide_close_mx_mlt_storage_flag = !!parseInt( - localStorage.getItem('hide_close_mx_mlt_account_notification') ?? '' + localStorage.getItem('hide_close_mx_mlt_account_notification') as string ); const is_logged_in = this.root_store.client.is_logged_in; const clients_country = this.root_store.client.clients_country; @@ -652,7 +653,7 @@ export default class TradeStore extends BaseStore { return; } - if (!active_symbols || !active_symbols.length) { + if (!active_symbols?.length) { await WS.wait('get_settings'); /* * This logic is related to EU country checks @@ -800,7 +801,7 @@ export default class TradeStore extends BaseStore { this.setMarketStatus(isMarketClosed(this.active_symbols, this.previous_symbol)); await Symbol.onChangeSymbolAsync(this.previous_symbol); - await this.updateSymbol(this.symbol); + this.updateSymbol(this.symbol); this.setChartStatus(false); runInAction(() => { @@ -1492,7 +1493,7 @@ export default class TradeStore extends BaseStore { manageMxMltRemovalNotification() { const { addNotificationMessage, client_notifications, notification_messages, unmarkNotificationMessage } = this.root_store.notifications; - const get_notification_messages = JSON.parse(localStorage.getItem('notification_messages') ?? ''); + const get_notification_messages = JSON.parse(localStorage.getItem('notification_messages') as string); const { has_iom_account, has_malta_account, is_logged_in } = this.root_store.client; unmarkNotificationMessage({ key: 'close_mx_mlt_account' }); if (get_notification_messages !== null && is_logged_in && (has_iom_account || has_malta_account)) { @@ -1500,7 +1501,7 @@ export default class TradeStore extends BaseStore { () => is_logged_in && notification_messages.length === 0, () => { const hidden_close_account_notification = - parseInt(localStorage.getItem('hide_close_mx_mlt_account_notification') ?? '') === 1; + parseInt(localStorage.getItem('hide_close_mx_mlt_account_notification') as string) === 1; const should_retain_notification = (has_iom_account || has_malta_account) && !hidden_close_account_notification; if (should_retain_notification) { @@ -1558,7 +1559,7 @@ export default class TradeStore extends BaseStore { } get is_crypto_multiplier() { - return this.contract_type === 'multiplier' && /^cry/.test(this.symbol); + return this.contract_type === 'multiplier' && this.symbol.startsWith('cry'); } exportLayout(layout: TChartLayout) { @@ -1676,13 +1677,13 @@ export default class TradeStore extends BaseStore { setContractPurchaseToastbox(response: Buy) { const list = getAvailableContractTypes(this.contract_types_list, unsupported_contract_types_list); - return (this.contract_purchase_toast_box = { + this.contract_purchase_toast_box = { key: true, buy_price: formatMoney(this.root_store.client.currency, response.buy_price, true, 0, 0), contract_type: this.contract_type, currency: getCurrencyDisplayCode(this.root_store.client.currency), list, - }); + }; } clearContractPurchaseToastBox() { From eb28c12a06e0023c3813e77ee9386c299efc408f Mon Sep 17 00:00:00 2001 From: Henry Hein Date: Wed, 16 Aug 2023 15:19:23 +0800 Subject: [PATCH 32/55] fix: bug --- .../Elements/TogglePositions/toggle-positions-mobile.tsx | 6 +++--- packages/trader/src/App/Containers/populate-header.jsx | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/trader/src/App/Components/Elements/TogglePositions/toggle-positions-mobile.tsx b/packages/trader/src/App/Components/Elements/TogglePositions/toggle-positions-mobile.tsx index 5d66cd6682c6..facdaaa30b9f 100644 --- a/packages/trader/src/App/Components/Elements/TogglePositions/toggle-positions-mobile.tsx +++ b/packages/trader/src/App/Components/Elements/TogglePositions/toggle-positions-mobile.tsx @@ -36,7 +36,7 @@ const TogglePositionsMobile = observer( let filtered_positions: TTogglePositionsMobile['all_positions'] = []; const closeModal = () => { - filtered_positions.slice(0, 5).map(position => { + filtered_positions?.slice(0, 5).map(position => { const { contract_info } = position; if (contract_info?.is_sold) { onClickRemove(contract_info.contract_id as number); @@ -45,7 +45,7 @@ const TogglePositionsMobile = observer( togglePositionsDrawer(); }; - filtered_positions = all_positions.filter( + filtered_positions = all_positions?.filter( p => p.contract_info && symbol === p.contract_info.underlying && @@ -57,7 +57,7 @@ const TogglePositionsMobile = observer( const body_content = ( - {filtered_positions.slice(0, 5).map(portfolio_position => ( + {filtered_positions?.slice(0, 5).map(portfolio_position => ( { return ( Date: Wed, 16 Aug 2023 16:11:37 +0800 Subject: [PATCH 33/55] fix: sonarcloud --- packages/stores/src/mockStore.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/stores/src/mockStore.ts b/packages/stores/src/mockStore.ts index 1171b81d0035..eec1e458992f 100644 --- a/packages/stores/src/mockStore.ts +++ b/packages/stores/src/mockStore.ts @@ -299,7 +299,6 @@ const mock = (): TStores & { is_mock: boolean } => { setCurrentFocus: jest.fn(), toggleAccountsDialog: jest.fn(), toggleCashier: jest.fn(), - togglePositionsDrawer: jest.fn(), setDarkMode: jest.fn(), setReportsTabIndex: jest.fn(), has_only_forward_starting_contracts: false, From 0160b2bc13d1767a7ec2d9c665fedc0e190ce749 Mon Sep 17 00:00:00 2001 From: Henry Hein Date: Wed, 16 Aug 2023 16:30:01 +0800 Subject: [PATCH 34/55] fix: reorder props --- .../Elements/TogglePositions/toggle-positions-mobile.tsx | 2 +- packages/trader/src/App/Containers/populate-header.jsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/trader/src/App/Components/Elements/TogglePositions/toggle-positions-mobile.tsx b/packages/trader/src/App/Components/Elements/TogglePositions/toggle-positions-mobile.tsx index c40be8aec3e1..022180ee093f 100644 --- a/packages/trader/src/App/Components/Elements/TogglePositions/toggle-positions-mobile.tsx +++ b/packages/trader/src/App/Components/Elements/TogglePositions/toggle-positions-mobile.tsx @@ -23,9 +23,9 @@ type THiddenPositionsId = TTogglePositionsMobile['filtered_positions'][0]['id']; const TogglePositionsMobile = observer( ({ active_positions_count, - filtered_positions, currency, error, + filtered_positions, is_empty, onClickSell, onClickCancel, diff --git a/packages/trader/src/App/Containers/populate-header.jsx b/packages/trader/src/App/Containers/populate-header.jsx index 105b097a46c3..0ad90e143629 100644 --- a/packages/trader/src/App/Containers/populate-header.jsx +++ b/packages/trader/src/App/Containers/populate-header.jsx @@ -30,10 +30,10 @@ const PopulateHeader = observer(() => { return ( From 159efaa3ea4298cd9d538216dca68af1d9a3dfe9 Mon Sep 17 00:00:00 2001 From: Henry Hein Date: Wed, 16 Aug 2023 20:38:21 +0800 Subject: [PATCH 35/55] fix: test cases --- .../toggle-positions-mobile.spec.tsx | 110 +++++++++++++++--- .../toggle-positions-mobile.tsx | 2 +- 2 files changed, 97 insertions(+), 15 deletions(-) diff --git a/packages/trader/src/App/Components/Elements/TogglePositions/__tests__/toggle-positions-mobile.spec.tsx b/packages/trader/src/App/Components/Elements/TogglePositions/__tests__/toggle-positions-mobile.spec.tsx index cd2855ab77f7..025a84d9b56a 100644 --- a/packages/trader/src/App/Components/Elements/TogglePositions/__tests__/toggle-positions-mobile.spec.tsx +++ b/packages/trader/src/App/Components/Elements/TogglePositions/__tests__/toggle-positions-mobile.spec.tsx @@ -7,21 +7,53 @@ import { mockStore } from '@deriv/stores'; import { TCoreStores } from '@deriv/stores/types'; import { BrowserRouter } from 'react-router-dom'; -const default_mocked_props = { +const default_mocked_props: React.ComponentProps = { active_positions_count: 0, - filtered_positions: [], currency: 'USD', - disableApp: jest.fn(), - enableApp: jest.fn(), error: '', + filtered_positions: [ + { + contract_info: { + contract_id: 215925907928, + contract_type: 'CALL', + is_sold: 0, + shortcode: 'CALL_1HZ100V_76.33_1692187938_1692188838_S0P_0', + underlying: '1HZ100V', + }, + contract_update: {}, + display_name: 'Volatility 100 (1s) Index', + indicative: 29.55, + reference: 430851089968, + is_sell_requested: false, + is_unsupported: false, + profit_loss: -9.45, + }, + { + contract_info: { + contract_id: 2, + contract_type: 'PUT', + is_sold: 0, + shortcode: 'PUT_R_10_19.53_1691443887_1691444787_S0P_0', + underlying: 'R_100', + }, + contract_update: {}, + display_name: 'Volatility 100 (1s) Index', + indicative: 29.55, + reference: 430851089968, + is_sell_requested: false, + is_unsupported: false, + profit_loss: -9.45, + }, + ], is_empty: true, onClickSell: jest.fn(), onClickCancel: jest.fn(), - toggleUnsupportedContractModal: jest.fn(), }; + const default_mock_store = { ui: { togglePositionsDrawer: jest.fn(), + toggleUnsupportedContractModal: jest.fn(), is_positions_drawer_on: false, }, }; @@ -68,29 +100,47 @@ describe('TogglePositionsMobile component', () => { expect(screen.getByText(/You have no open positions for this asset./i)).toBeInTheDocument(); }); it('should display 2 positions when is_positions_drawer_on === true, is_empty === false, and has 2 active positions', () => { - const new_mocked_props = { + const new_mocked_props: React.ComponentProps = { active_positions_count: 2, + currency: 'USD', + error: '', filtered_positions: [ { contract_info: { - contract_id: '1', + contract_id: 1, contract_type: 'CALL', is_sold: 0, shortcode: 'CALL_R_10_19.54_1691443851_1691444751_S0P_0', underlying: 'R_100', }, + contract_update: {}, + display_name: 'Volatility 100 (1s) Index', + indicative: 29.55, + reference: 430851089968, + is_sell_requested: false, + is_unsupported: false, + profit_loss: -9.45, }, { contract_info: { - contract_id: '2', + contract_id: 2, contract_type: 'PUT', is_sold: 0, shortcode: 'PUT_R_10_19.53_1691443887_1691444787_S0P_0', underlying: 'R_100', }, + contract_update: {}, + display_name: 'Volatility 100 (1s) Index', + indicative: 29.55, + reference: 430851089968, + is_sell_requested: false, + is_unsupported: false, + profit_loss: -9.45, }, ], is_empty: false, + onClickSell: jest.fn(), + onClickCancel: jest.fn(), }; const mock_root_store = mockStore({ ...default_mock_store, @@ -101,29 +151,47 @@ describe('TogglePositionsMobile component', () => { expect(screen.getAllByText(/PositionsModalCard/i)).toHaveLength(2); }); it('should display 1 of 2 positions after closing the modal if one of the 2 positions is sold', async () => { - const new_mocked_props = { + const new_mocked_props: React.ComponentProps = { active_positions_count: 1, + currency: 'USD', + error: '', filtered_positions: [ { contract_info: { - contract_id: '1', + contract_id: 1, contract_type: 'CALL', is_sold: 0, shortcode: 'CALL_R_10_19.54_1691443851_1691444751_S0P_0', underlying: 'R_100', }, + contract_update: {}, + display_name: 'Volatility 100 (1s) Index', + indicative: 29.55, + reference: 430851089968, + is_sell_requested: false, + is_unsupported: false, + profit_loss: -9.45, }, { contract_info: { - contract_id: '2', + contract_id: 2, contract_type: 'PUT', is_sold: 1, shortcode: 'PUT_R_10_19.53_1691443887_1691444787_S0P_0', underlying: 'R_100', }, + contract_update: {}, + display_name: 'Volatility 100 (1s) Index', + indicative: 29.55, + reference: 430851089968, + is_sell_requested: false, + is_unsupported: false, + profit_loss: -9.45, }, ], is_empty: false, + onClickSell: jest.fn(), + onClickCancel: jest.fn(), }; const mock_root_store = mockStore({ ...default_mock_store, @@ -140,24 +208,38 @@ describe('TogglePositionsMobile component', () => { }); }); it('should display no more than 5 recent positions', () => { - const positions_pair = [ + const positions_pair: React.ComponentProps['filtered_positions'] = [ { contract_info: { - contract_id: '1', + contract_id: 1, contract_type: 'CALL', is_sold: 0, shortcode: 'CALL_R_10_19.54_1691443851_1691444751_S0P_0', underlying: 'R_100', }, + contract_update: {}, + display_name: 'Volatility 100 (1s) Index', + indicative: 29.55, + reference: 430851089968, + is_sell_requested: false, + is_unsupported: false, + profit_loss: -9.45, }, { contract_info: { - contract_id: '2', + contract_id: 2, contract_type: 'PUT', is_sold: 0, shortcode: 'PUT_R_10_19.53_1691443887_1691444787_S0P_0', underlying: 'R_100', }, + contract_update: {}, + display_name: 'Volatility 100 (1s) Index', + indicative: 29.55, + reference: 430851089968, + is_sell_requested: false, + is_unsupported: false, + profit_loss: -9.45, }, ]; const new_mocked_props = { diff --git a/packages/trader/src/App/Components/Elements/TogglePositions/toggle-positions-mobile.tsx b/packages/trader/src/App/Components/Elements/TogglePositions/toggle-positions-mobile.tsx index 022180ee093f..eef96fdde3b9 100644 --- a/packages/trader/src/App/Components/Elements/TogglePositions/toggle-positions-mobile.tsx +++ b/packages/trader/src/App/Components/Elements/TogglePositions/toggle-positions-mobile.tsx @@ -15,7 +15,7 @@ type TTogglePositionsMobile = Pick< > & { currency: ReturnType['client']['currency']; filtered_positions: ReturnType['portfolio']['all_positions']; - is_empty: number; + is_empty: boolean; }; type THiddenPositionsId = TTogglePositionsMobile['filtered_positions'][0]['id']; From c9d1156bc58b79dd43794a7947d147a681983fe8 Mon Sep 17 00:00:00 2001 From: Henry Hein Date: Thu, 17 Aug 2023 08:47:40 +0800 Subject: [PATCH 36/55] fix: coveralls --- packages/trader/src/Modules/Trading/Helpers/contract-type.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/trader/src/Modules/Trading/Helpers/contract-type.tsx b/packages/trader/src/Modules/Trading/Helpers/contract-type.tsx index 49eff170b6b8..d81f315b8198 100644 --- a/packages/trader/src/Modules/Trading/Helpers/contract-type.tsx +++ b/packages/trader/src/Modules/Trading/Helpers/contract-type.tsx @@ -55,7 +55,7 @@ export const getAvailableContractTypes = (contract_types_list: TcontractTypesLis return Object.keys(contract_types_list) .map(key => { const contract_types = contract_types_list[key].categories; - const contract_name = contract_types_list[key].name; + const contract_name = contract_types_list[key].name || ''; const available_contract_types = contract_types.filter(type => type.value && // TODO: remove this check once all contract types are supported From 4daf7b153a25847313525ca0e3e6d07062508816 Mon Sep 17 00:00:00 2001 From: Henry Hein Date: Thu, 17 Aug 2023 09:10:32 +0800 Subject: [PATCH 37/55] fix: coveralls --- packages/trader/src/Modules/Trading/Helpers/contract-type.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/trader/src/Modules/Trading/Helpers/contract-type.tsx b/packages/trader/src/Modules/Trading/Helpers/contract-type.tsx index d81f315b8198..4cd1d6d10f47 100644 --- a/packages/trader/src/Modules/Trading/Helpers/contract-type.tsx +++ b/packages/trader/src/Modules/Trading/Helpers/contract-type.tsx @@ -3,7 +3,7 @@ import { localize } from '@deriv/translations'; type TCategory = { value: string; text: string }; type TContractType = { - name: string; + name?: string; categories: TCategory[]; }; type TcontractTypesList = { From 98f5e4552e40b52f4205531711ecfcec424293ac Mon Sep 17 00:00:00 2001 From: Henry Hein Date: Thu, 17 Aug 2023 10:18:23 +0800 Subject: [PATCH 38/55] fix: this component doesnt exist anymore, hence test was also removed --- .../__tests__/progress-slider-stream.spec.jsx | 44 ------------------- 1 file changed, 44 deletions(-) delete mode 100644 packages/trader/src/App/Containers/ProgressSliderStream/__tests__/progress-slider-stream.spec.jsx diff --git a/packages/trader/src/App/Containers/ProgressSliderStream/__tests__/progress-slider-stream.spec.jsx b/packages/trader/src/App/Containers/ProgressSliderStream/__tests__/progress-slider-stream.spec.jsx deleted file mode 100644 index 76db96aadf64..000000000000 --- a/packages/trader/src/App/Containers/ProgressSliderStream/__tests__/progress-slider-stream.spec.jsx +++ /dev/null @@ -1,44 +0,0 @@ -import React from 'react'; -import { screen, render } from '@testing-library/react'; -import { mockStore } from '@deriv/stores'; -import TraderProviders from '../../../../trader-providers'; -import ProgressSliderStream from '../progress-slider-stream'; - -jest.mock('@deriv/components', () => ({ - ...jest.requireActual('@deriv/components'), - ProgressSlider: () =>
Mocked Progress Slider
, -})); - -const contract_info = { - contract_type: 'TEST', - date_expiry: 1222222224, - date_start: 1222222222, - tick_count: 2, - tick_stream: [ - { epoch: 1, tick: 1, tick_display_value: '300' }, - { epoch: 2, tick: 2, tick_display_value: '302' }, - ], -}; - -describe('', () => { - const mockProgressSliderStream = (mocked_store, contract_info = null) => { - return ( - - - - ); - }; - - it('should not render if contract_info is falsy', () => { - const mock_root_store = mockStore({}); - render(mockProgressSliderStream(mock_root_store)); - - expect(screen.queryByText('Mocked Progress Slider')).not.toBeInTheDocument(); - }); - it('should render if contract_info was passed in props', () => { - const mock_root_store = mockStore({}); - render(mockProgressSliderStream(mock_root_store, contract_info)); - - expect(screen.getByText('Mocked Progress Slider')).toBeInTheDocument(); - }); -}); From bca1185fd952814dcab9a20249f05a2264b37421 Mon Sep 17 00:00:00 2001 From: henry-deriv <118344354+henry-deriv@users.noreply.github.com> Date: Tue, 22 Aug 2023 16:14:10 +0800 Subject: [PATCH 39/55] henry/dtra-356/fix: ts-migration-digitsJSX (#24) * fix: initial commit * fix: ts migrate digits JSX * fix: small type change * fix: comment --- packages/shared/src/utils/helpers/logic.ts | 21 ++--- packages/stores/src/mockStore.ts | 17 +++- packages/stores/types.ts | 19 +++- .../Digits/{digits.jsx => digits.tsx} | 91 +++++++++++++------ .../Contract/Components/Digits/index.js | 2 +- 5 files changed, 106 insertions(+), 44 deletions(-) rename packages/trader/src/Modules/Contract/Components/Digits/{digits.jsx => digits.tsx} (72%) diff --git a/packages/shared/src/utils/helpers/logic.ts b/packages/shared/src/utils/helpers/logic.ts index 691cef6f72b8..8f95d62ef54a 100644 --- a/packages/shared/src/utils/helpers/logic.ts +++ b/packages/shared/src/utils/helpers/logic.ts @@ -2,16 +2,15 @@ import moment from 'moment'; import { isEmptyObject } from '../object'; import { isAccumulatorContract, isOpen, isUserSold } from '../contract'; import { TContractInfo, TContractStore } from '../contract/contract-types'; +import { TickSpotData } from '@deriv/api-types'; -type TTick = { - ask?: number; - bid?: number; - epoch?: number; - id?: string; - pip_size: number; - quote?: number; - symbol?: string; -}; +type TTick = + | (Omit & { + ask: TickSpotData['ask'] | null; + bid: TickSpotData['bid'] | null; + pip_size?: TickSpotData['pip_size']; + }) + | null; type TIsEndedBeforeCancellationExpired = TGetEndTime & { cancellation: { @@ -38,10 +37,10 @@ type TGetEndTime = Pick< > & Required>; -export const isContractElapsed = (contract_info: TGetEndTime, tick: TTick) => { +export const isContractElapsed = (contract_info: TContractInfo, tick?: TTick) => { if (isEmptyObject(tick) || isEmptyObject(contract_info)) return false; const end_time = getEndTime(contract_info) || 0; - if (end_time && tick.epoch) { + if (end_time && tick && tick.epoch) { const seconds = moment.duration(moment.unix(tick.epoch).diff(moment.unix(end_time))).asSeconds(); return seconds >= 2; } diff --git a/packages/stores/src/mockStore.ts b/packages/stores/src/mockStore.ts index c4bd3794c94b..3afaa0679918 100644 --- a/packages/stores/src/mockStore.ts +++ b/packages/stores/src/mockStore.ts @@ -449,6 +449,13 @@ const mock = (): TStores & { is_mock: boolean } => { has_contract_update_stop_loss: false, has_contract_update_take_profit: false, getContractById: jest.fn(), + last_contract: { + contract_info: {}, + digits_info: {}, + display_status: '', + is_digit_contract: false, + is_ended: false, + }, }, modules: {}, exchange_rates: { @@ -463,7 +470,15 @@ const mock = (): TStores & { is_mock: boolean } => { }, gtm: {}, pushwoosh: {}, - contract_replay: {}, + contract_replay: { + contract_store: { + contract_info: {}, + digits_info: {}, + display_status: '', + is_digit_contract: false, + is_ended: false, + }, + }, chart_barrier_store: {}, active_symbols: {}, }; diff --git a/packages/stores/types.ts b/packages/stores/types.ts index 6a31e78a8235..bdafcfa5e887 100644 --- a/packages/stores/types.ts +++ b/packages/stores/types.ts @@ -548,6 +548,13 @@ type TContractStore = { contract_update_take_profit: string; has_contract_update_stop_loss: boolean; has_contract_update_take_profit: boolean; + last_contract: { + contract_info: TPortfolioPosition['contract_info']; + digits_info: { [key: number]: { digit: number; spot: string } }; + display_status: string; + is_digit_contract: boolean; + is_ended: boolean; + }; }; type TMenuStore = { @@ -627,6 +634,16 @@ type TTradersHubStore = { showTopUpModal: () => void; }; +type TContractReplay = { + contract_store: { + contract_info: TPortfolioPosition['contract_info']; + digits_info: { [key: number]: { digit: number; spot: string } }; + display_status: string; + is_digit_contract: boolean; + is_ended: boolean; + }; +}; + /** * This is the type that contains all the `core` package stores */ @@ -644,7 +661,7 @@ export type TCoreStores = { traders_hub: TTradersHubStore; gtm: Record; pushwoosh: Record; - contract_replay: Record; + contract_replay: TContractReplay; chart_barrier_store: Record; active_symbols: Record; }; diff --git a/packages/trader/src/Modules/Contract/Components/Digits/digits.jsx b/packages/trader/src/Modules/Contract/Components/Digits/digits.tsx similarity index 72% rename from packages/trader/src/Modules/Contract/Components/Digits/digits.jsx rename to packages/trader/src/Modules/Contract/Components/Digits/digits.tsx index 5c8a14d768a5..610c50b3f67e 100644 --- a/packages/trader/src/Modules/Contract/Components/Digits/digits.jsx +++ b/packages/trader/src/Modules/Contract/Components/Digits/digits.tsx @@ -1,5 +1,4 @@ import classNames from 'classnames'; -import PropTypes from 'prop-types'; import React from 'react'; import { toJS } from 'mobx'; import { DesktopWrapper, MobileWrapper, Popover, Text } from '@deriv/components'; @@ -9,6 +8,52 @@ import { Bounce, SlideIn } from 'App/Components/Animations'; import { getMarketNamesMap } from '../../../../Constants'; import { DigitSpot, LastDigitPrediction } from '../LastDigitPrediction'; import 'Sass/app/modules/contract/digits.scss'; +import { useStore } from '@deriv/stores'; +import { useTraderStore } from 'Stores/useTraderStores'; + +type TTraderStore = ReturnType; +type TOnChangeStatus = { status: string | null; current_tick: number | null }; +type TOnLastDigitSpot = { + spot: string; + is_lost: boolean; + is_selected_winning: boolean; + is_latest: boolean; + is_won: boolean; +}; +type TContractStores = + | ReturnType['contract_trade']['last_contract'] + | ReturnType['contract_replay']['contract_store']; + +type TDigitsWrapper = TContractStores & { + digits_array?: string[]; + is_trade_page?: boolean; + onDigitChange?: TTraderStore['onChange']; + selected_digit?: TTraderStore['last_digit']; + trade_type?: TTraderStore['contract_type']; + onChangeStatus?: (params: TOnChangeStatus) => void; + onLastDigitSpot?: (params: TOnLastDigitSpot) => void; + tick?: + | (Parameters[1] & { + current_tick: number; + }) + | null; +}; + +type TDigits = Pick< + TDigitsWrapper, + | 'contract_info' + | 'digits_array' + | 'digits_info' + | 'display_status' + | 'is_digit_contract' + | 'is_ended' + | 'is_trade_page' + | 'trade_type' + | 'onDigitChange' + | 'selected_digit' +> & { + underlying: ReturnType['symbol']; +}; const DigitsWrapper = ({ contract_info, @@ -23,7 +68,7 @@ const DigitsWrapper = ({ trade_type, onChangeStatus, ...props -}) => { +}: TDigitsWrapper) => { const has_contract = contract_info.date_start; let tick = props.tick; @@ -42,7 +87,7 @@ const DigitsWrapper = ({ ask: t.tick, bid: t.tick, epoch: t.epoch, - pip_size: t.tick_display_value.split('.')[1].length, + pip_size: t.tick_display_value?.split('.')[1].length, current_tick: tick_stream.length, }; } @@ -60,7 +105,7 @@ const DigitsWrapper = ({ // i.e - 40px + 6px left and 6px right padding/margin = 52 dimension={isMobile() ? 64 : 52} has_entry_spot={!!contract_info.entry_tick} - barrier={!is_contract_elapsed && is_tick_ready ? +contract_info.barrier : null} + barrier={!is_contract_elapsed && is_tick_ready ? Number(contract_info.barrier) : null} contract_type={!is_contract_elapsed && is_tick_ready ? contract_info.contract_type : null} digits={digits_array} digits_info={!is_contract_elapsed && is_tick_ready ? digits_info : {}} @@ -77,24 +122,24 @@ const DigitsWrapper = ({ ); }; -const Digits = React.memo(props => { - const [status, setStatus] = React.useState(); - const [current_tick, setCurrentTick] = React.useState(); - const [spot, setSpot] = React.useState(); - const [is_selected_winning, setIsSelectedWinning] = React.useState(); - const [is_latest, setIsLatest] = React.useState(); - const [is_won, setIsWon] = React.useState(); - const [is_lost, setIsLost] = React.useState(); +const Digits = React.memo((props: TDigits) => { + const [status, setStatus] = React.useState(); + const [current_tick, setCurrentTick] = React.useState(); + const [spot, setSpot] = React.useState(); + const [is_selected_winning, setIsSelectedWinning] = React.useState(); + const [is_latest, setIsLatest] = React.useState(); + const [is_won, setIsWon] = React.useState(); + const [is_lost, setIsLost] = React.useState(); const isMounted = useIsMounted(); const { contract_info, digits_array, is_digit_contract, is_trade_page, underlying } = props; - const onChangeStatus = params => { + const onChangeStatus = (params: TOnChangeStatus) => { setStatus(params.status); setCurrentTick(params.current_tick); }; - const onLastDigitSpot = params => { + const onLastDigitSpot = (params: TOnLastDigitSpot) => { setSpot(params.spot); setIsLost(params.is_lost); setIsSelectedWinning(params.is_selected_winning); @@ -106,7 +151,8 @@ const Digits = React.memo(props => { const underlying_name = is_trade_page ? underlying : contract_info.underlying; return localize('Last digit stats for latest 1000 ticks for {{underlying_name}}', { - underlying_name: getMarketNamesMap()[underlying_name.toUpperCase()], + underlying_name: + getMarketNamesMap()[underlying_name?.toUpperCase() as keyof ReturnType], }); }; @@ -152,7 +198,6 @@ const Digits = React.memo(props => { current_spot={spot} is_lost={is_lost} is_selected_winning={is_selected_winning} - is_visible={!!(is_latest && spot)} is_won={is_won} /> @@ -165,18 +210,4 @@ const Digits = React.memo(props => { Digits.displayName = 'Digits'; -Digits.propTypes = { - contract_info: PropTypes.object, - digits_array: PropTypes.array, - digits_info: PropTypes.object, - display_status: PropTypes.string, - is_digit_contract: PropTypes.bool, - is_ended: PropTypes.bool, - is_trade_page: PropTypes.bool, - trade_type: PropTypes.string, - onDigitChange: PropTypes.func, - selected_digit: PropTypes.number, - underlying: PropTypes.string, -}; - export default Digits; diff --git a/packages/trader/src/Modules/Contract/Components/Digits/index.js b/packages/trader/src/Modules/Contract/Components/Digits/index.js index d46e4208466c..5a177415c03a 100644 --- a/packages/trader/src/Modules/Contract/Components/Digits/index.js +++ b/packages/trader/src/Modules/Contract/Components/Digits/index.js @@ -1,3 +1,3 @@ -import Digits from './digits.jsx'; +import Digits from './digits'; export default Digits; From 5477ae5c309f161c85d58f874a9d4d569228a153 Mon Sep 17 00:00:00 2001 From: kate-deriv Date: Tue, 22 Aug 2023 12:40:05 +0300 Subject: [PATCH 40/55] chore: removed unused state --- .../trader/src/Modules/Contract/Components/Digits/digits.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/trader/src/Modules/Contract/Components/Digits/digits.tsx b/packages/trader/src/Modules/Contract/Components/Digits/digits.tsx index 610c50b3f67e..8ce0d2d4d90e 100644 --- a/packages/trader/src/Modules/Contract/Components/Digits/digits.tsx +++ b/packages/trader/src/Modules/Contract/Components/Digits/digits.tsx @@ -127,7 +127,6 @@ const Digits = React.memo((props: TDigits) => { const [current_tick, setCurrentTick] = React.useState(); const [spot, setSpot] = React.useState(); const [is_selected_winning, setIsSelectedWinning] = React.useState(); - const [is_latest, setIsLatest] = React.useState(); const [is_won, setIsWon] = React.useState(); const [is_lost, setIsLost] = React.useState(); const isMounted = useIsMounted(); @@ -143,7 +142,6 @@ const Digits = React.memo((props: TDigits) => { setSpot(params.spot); setIsLost(params.is_lost); setIsSelectedWinning(params.is_selected_winning); - setIsLatest(params.is_latest); setIsWon(params.is_won); }; From 2b82010671938e9cede4a1434e08f2dc97d809b8 Mon Sep 17 00:00:00 2001 From: Maryia <103177211+maryia-deriv@users.noreply.github.com> Date: Wed, 23 Aug 2023 10:38:59 +0300 Subject: [PATCH 41/55] Maryia/dtra-355/Migrate ContractDrawer files to TS (#22) * feat: migrated swipeable-components to TS * feat: migrated market-closed-contract-overlay & index to TS * chore: migrated contract-drawer-card.tsx to ts * build: fix type * chore: migrated contract-drawer to ts * chore: fixed existing types in digits and logic * chore: sort types properties in alphabetical order --- .../components/collapsible/arrow-button.tsx | 2 +- .../contract-card-body.tsx | 4 +- .../contract-card-footer.tsx | 6 +- .../contract-update-form.tsx | 2 +- .../contract-card/contract-card.tsx | 14 +- .../result-overlay/result-overlay.tsx | 10 +- .../div100vh-container/div100vh-container.tsx | 2 +- .../src/components/tooltip/tooltip.tsx | 2 +- packages/components/src/hooks/use-hover.ts | 2 +- .../reports/src/Containers/open-positions.tsx | 2 +- .../src/utils/contract/contract-types.ts | 19 ++- packages/shared/src/utils/helpers/logic.ts | 10 +- packages/stores/src/mockStore.ts | 37 ++++-- packages/stores/types.ts | 123 +++++++++++++++--- ...awer-card.jsx => contract-drawer-card.tsx} | 61 +++++---- ...ontract-drawer.jsx => contract-drawer.tsx} | 49 ++++--- .../Elements/ContractDrawer/index.js | 3 - .../Elements/ContractDrawer/index.ts | 3 + ...jsx => market-closed-contract-overlay.tsx} | 7 +- ...omponents.jsx => swipeable-components.tsx} | 26 ++-- .../Contract/Components/Digits/digits.tsx | 61 ++++----- 21 files changed, 282 insertions(+), 163 deletions(-) rename packages/trader/src/App/Components/Elements/ContractDrawer/{contract-drawer-card.jsx => contract-drawer-card.tsx} (81%) rename packages/trader/src/App/Components/Elements/ContractDrawer/{contract-drawer.jsx => contract-drawer.tsx} (85%) delete mode 100644 packages/trader/src/App/Components/Elements/ContractDrawer/index.js create mode 100644 packages/trader/src/App/Components/Elements/ContractDrawer/index.ts rename packages/trader/src/App/Components/Elements/ContractDrawer/{market-closed-contract-overlay.jsx => market-closed-contract-overlay.tsx} (64%) rename packages/trader/src/App/Components/Elements/ContractDrawer/{swipeable-components.jsx => swipeable-components.tsx} (75%) diff --git a/packages/components/src/components/collapsible/arrow-button.tsx b/packages/components/src/components/collapsible/arrow-button.tsx index 42654e49280d..ad616e8cacf0 100644 --- a/packages/components/src/components/collapsible/arrow-button.tsx +++ b/packages/components/src/components/collapsible/arrow-button.tsx @@ -13,7 +13,7 @@ type TArrowButton = { is_open?: boolean; onClick: () => void; title?: string; - position: 'top' | 'bottom'; + position?: 'top' | 'bottom'; }; const IconArrow = ({ className }: { className?: string }) => ( diff --git a/packages/components/src/components/contract-card/contract-card-items/contract-card-body.tsx b/packages/components/src/components/contract-card/contract-card-items/contract-card-body.tsx index f9430a530f10..a7cd83e5a2b4 100644 --- a/packages/components/src/components/contract-card/contract-card-items/contract-card-body.tsx +++ b/packages/components/src/components/contract-card/contract-card-items/contract-card-body.tsx @@ -31,11 +31,11 @@ export type TGeneralContractCardBodyProps = { has_progress_slider: boolean; is_mobile: boolean; is_sold: boolean; - onMouseLeave: () => void; + onMouseLeave?: () => void; removeToast: (toast_id: string) => void; setCurrentFocus: (name: string) => void; status?: string; - toggleCancellationWarning: () => void; + toggleCancellationWarning: (state_change?: boolean) => void; progress_slider?: React.ReactNode; is_positions?: boolean; }; diff --git a/packages/components/src/components/contract-card/contract-card-items/contract-card-footer.tsx b/packages/components/src/components/contract-card/contract-card-items/contract-card-footer.tsx index d599eb7b1081..db1988ca776d 100644 --- a/packages/components/src/components/contract-card/contract-card-items/contract-card-footer.tsx +++ b/packages/components/src/components/contract-card/contract-card-items/contract-card-footer.tsx @@ -11,13 +11,13 @@ export type TCardFooterPropTypes = { contract_info: TContractInfo; getCardLabels: TGetCardLables; is_multiplier?: boolean; - is_positions: boolean; + is_positions?: boolean; is_sell_requested: boolean; onClickCancel: (contract_id?: number) => void; onClickSell: (contract_id?: number) => void; onFooterEntered?: () => void; server_time: moment.Moment; - should_show_transition: boolean; + should_show_transition?: boolean; }; const CardFooter = ({ @@ -32,7 +32,7 @@ const CardFooter = ({ server_time, should_show_transition, }: TCardFooterPropTypes) => { - const { in_prop } = useNewRowTransition(should_show_transition); + const { in_prop } = useNewRowTransition(!!should_show_transition); const is_valid_to_cancel = isValidToCancel(contract_info); diff --git a/packages/components/src/components/contract-card/contract-card-items/contract-update-form.tsx b/packages/components/src/components/contract-card/contract-card-items/contract-update-form.tsx index c423695f5972..ccc917c799f0 100644 --- a/packages/components/src/components/contract-card/contract-card-items/contract-update-form.tsx +++ b/packages/components/src/components/contract-card/contract-card-items/contract-update-form.tsx @@ -34,7 +34,7 @@ export type TContractUpdateFormProps = Pick< current_focus?: string; error_message_alignment: string; getCardLabels: TGetCardLables; - onMouseLeave: () => void; + onMouseLeave?: () => void; removeToast: (toast_id: string) => void; setCurrentFocus: (name: string | null) => void; status: string; diff --git a/packages/components/src/components/contract-card/contract-card.tsx b/packages/components/src/components/contract-card/contract-card.tsx index 2213afe58b48..94993077b32e 100644 --- a/packages/components/src/components/contract-card/contract-card.tsx +++ b/packages/components/src/components/contract-card/contract-card.tsx @@ -14,15 +14,15 @@ import { TGetCardLables, TGetContractPath } from '../types'; type TContractCardProps = { contract_info: TContractInfo; getCardLabels: TGetCardLables; - getContractPath: TGetContractPath; + getContractPath?: TGetContractPath; is_multiplier: boolean; - is_positions: boolean; - is_unsupported: boolean; - onClickRemove: (contract_id?: number) => void; + is_positions?: boolean; + is_unsupported?: boolean; + onClickRemove?: (contract_id?: number) => void; profit_loss: number; - result: string; + result?: string; should_show_result_overlay: boolean; - toggleUnsupportedContractModal: (is_unsupported_contract_modal_visible: boolean) => void; + toggleUnsupportedContractModal?: (is_unsupported_contract_modal_visible: boolean) => void; }; const ContractCard = ({ @@ -53,7 +53,7 @@ const ContractCard = ({ is_multiplier={is_multiplier} is_visible={!!contract_info.is_sold} onClickRemove={onClickRemove} - onClick={() => toggleUnsupportedContractModal(true)} + onClick={() => toggleUnsupportedContractModal?.(true)} result={result || fallback_result} is_positions={is_positions} /> diff --git a/packages/components/src/components/contract-card/result-overlay/result-overlay.tsx b/packages/components/src/components/contract-card/result-overlay/result-overlay.tsx index 17f3f4dee848..5f4c54d8a839 100644 --- a/packages/components/src/components/contract-card/result-overlay/result-overlay.tsx +++ b/packages/components/src/components/contract-card/result-overlay/result-overlay.tsx @@ -8,13 +8,13 @@ import { TGetCardLables, TGetContractPath } from '../../types'; type TResultOverlayProps = { contract_id?: number; getCardLabels: TGetCardLables; - getContractPath: TGetContractPath; + getContractPath?: TGetContractPath; is_multiplier?: boolean; - is_positions: boolean; - is_unsupported: boolean; + is_positions?: boolean; + is_unsupported?: boolean; is_visible: boolean; onClick: () => void; - onClickRemove: (contract_id?: number) => void; + onClickRemove?: (contract_id?: number) => void; result: string; }; @@ -88,7 +88,7 @@ const ResultOverlay = ({ onClickRemove(contract_id)} + onClick={() => onClickRemove?.(contract_id)} /> )} {getContractPath && ( diff --git a/packages/components/src/components/div100vh-container/div100vh-container.tsx b/packages/components/src/components/div100vh-container/div100vh-container.tsx index 6bcf1a60361a..619466ffa4ca 100644 --- a/packages/components/src/components/div100vh-container/div100vh-container.tsx +++ b/packages/components/src/components/div100vh-container/div100vh-container.tsx @@ -20,7 +20,7 @@ type TDiv100vhContainer = { is_bypassed?: boolean; is_disabled?: boolean; max_height_offset?: string; - className: string; + className?: string; max_autoheight_offset?: string; }; diff --git a/packages/components/src/components/tooltip/tooltip.tsx b/packages/components/src/components/tooltip/tooltip.tsx index 5f5cbac0c599..8d1203ff18a6 100644 --- a/packages/components/src/components/tooltip/tooltip.tsx +++ b/packages/components/src/components/tooltip/tooltip.tsx @@ -21,7 +21,7 @@ const Tooltip = ({ icon, // only question, info and dot accepted message, }: React.PropsWithChildren) => { - const [hover_ref, show_tooltip_balloon_icon_on_hover] = useHover(); + const [hover_ref, show_tooltip_balloon_icon_on_hover] = useHover(); const icon_class = classNames(classNameIcon, icon); diff --git a/packages/components/src/hooks/use-hover.ts b/packages/components/src/hooks/use-hover.ts index 6ebe6782472e..e5dbf16b6c0e 100644 --- a/packages/components/src/hooks/use-hover.ts +++ b/packages/components/src/hooks/use-hover.ts @@ -1,6 +1,6 @@ import React, { RefObject } from 'react'; -export const useHover = ( +export const useHover = ( refSetter?: RefObject | null, should_prevent_bubbling?: boolean ) => { diff --git a/packages/reports/src/Containers/open-positions.tsx b/packages/reports/src/Containers/open-positions.tsx index 9e117c3a0f63..400e66a1e084 100644 --- a/packages/reports/src/Containers/open-positions.tsx +++ b/packages/reports/src/Containers/open-positions.tsx @@ -117,7 +117,7 @@ type TTotals = { type TAddToastProps = { key?: string; - content: string; + content: React.ReactNode; timeout?: number; is_bottom?: boolean; type?: string; diff --git a/packages/shared/src/utils/contract/contract-types.ts b/packages/shared/src/utils/contract/contract-types.ts index 0442d8ffeeb6..0e5be1ce6746 100644 --- a/packages/shared/src/utils/contract/contract-types.ts +++ b/packages/shared/src/utils/contract/contract-types.ts @@ -1,15 +1,26 @@ -import { ContractUpdate, Portfolio1, ProposalOpenContract } from '@deriv/api-types'; +import { + ContractUpdate, + ContractUpdateHistory, + Portfolio1, + ProposalOpenContract, + TickSpotData, +} from '@deriv/api-types'; export type TContractStore = { + clearContractUpdateConfigValues: () => void; contract_info: TContractInfo; + contract_update_history: ContractUpdateHistory; contract_update_take_profit: number | string; contract_update_stop_loss: number | string; - clearContractUpdateConfigValues: () => void; + digits_info: TDigitsInfo; + display_status: string; has_contract_update_take_profit: boolean; has_contract_update_stop_loss: boolean; + is_digit_contract: boolean; + is_ended: boolean; + onChange: (param: { name: string; value: string | number | boolean }) => void; updateLimitOrder: () => void; validation_errors: { contract_update_stop_loss: string[]; contract_update_take_profit: string[] }; - onChange: (param: { name: string; value: string | number | boolean }) => void; }; export type TContractInfo = ProposalOpenContract & @@ -33,3 +44,5 @@ type TLimitProperty = { }; export type TLimitOrder = Partial>; + +export type TTickSpotData = TickSpotData; diff --git a/packages/shared/src/utils/helpers/logic.ts b/packages/shared/src/utils/helpers/logic.ts index 8f95d62ef54a..22998efd1162 100644 --- a/packages/shared/src/utils/helpers/logic.ts +++ b/packages/shared/src/utils/helpers/logic.ts @@ -4,14 +4,6 @@ import { isAccumulatorContract, isOpen, isUserSold } from '../contract'; import { TContractInfo, TContractStore } from '../contract/contract-types'; import { TickSpotData } from '@deriv/api-types'; -type TTick = - | (Omit & { - ask: TickSpotData['ask'] | null; - bid: TickSpotData['bid'] | null; - pip_size?: TickSpotData['pip_size']; - }) - | null; - type TIsEndedBeforeCancellationExpired = TGetEndTime & { cancellation: { ask_price: number; @@ -37,7 +29,7 @@ type TGetEndTime = Pick< > & Required>; -export const isContractElapsed = (contract_info: TContractInfo, tick?: TTick) => { +export const isContractElapsed = (contract_info: TContractInfo, tick?: TickSpotData) => { if (isEmptyObject(tick) || isEmptyObject(contract_info)) return false; const end_time = getEndTime(contract_info) || 0; if (end_time && tick && tick.epoch) { diff --git a/packages/stores/src/mockStore.ts b/packages/stores/src/mockStore.ts index 3afaa0679918..474cf8de6524 100644 --- a/packages/stores/src/mockStore.ts +++ b/packages/stores/src/mockStore.ts @@ -443,19 +443,32 @@ const mock = (): TStores & { is_mock: boolean } => { removePositionById: jest.fn(), }, contract_trade: { - contract_info: {}, - contract_update_stop_loss: '', - contract_update_take_profit: '', - has_contract_update_stop_loss: false, - has_contract_update_take_profit: false, + accountSwitchListener: jest.fn(), + accu_barriers_timeout_id: null, + accumulator_barriers_data: {}, + accumulator_contract_barriers_data: {}, + addContract: jest.fn(), + chart_type: '', + clearAccumulatorBarriersData: jest.fn(), + clearError: jest.fn(), + contracts: [], + error_message: '', getContractById: jest.fn(), - last_contract: { - contract_info: {}, - digits_info: {}, - display_status: '', - is_digit_contract: false, - is_ended: false, - }, + granularity: 0, + has_crossed_accu_barriers: false, + has_error: false, + last_contract: {}, + markers_array: [], + onUnmount: jest.fn(), + prev_chart_type: '', + prev_granularity: null, + removeContract: jest.fn(), + savePreviousChartMode: jest.fn(), + setNewAccumulatorBarriersData: jest.fn(), + updateAccumulatorBarriersData: jest.fn(), + updateChartType: jest.fn(), + updateGranularity: jest.fn(), + updateProposal: jest.fn(), }, modules: {}, exchange_rates: { diff --git a/packages/stores/types.ts b/packages/stores/types.ts index bdafcfa5e887..2df33d39b516 100644 --- a/packages/stores/types.ts +++ b/packages/stores/types.ts @@ -14,6 +14,7 @@ import type { SetFinancialAssessmentRequest, SetFinancialAssessmentResponse, StatesList, + ContractUpdateHistory, } from '@deriv/api-types'; import type { Moment } from 'moment'; import type { RouteComponentProps } from 'react-router'; @@ -159,10 +160,11 @@ type TMenuItem = { }; type TAddToastProps = { - key: string; - content: string | React.ReactNode; + key?: string; + content: React.ReactNode; timeout?: number; - type: string; + is_bottom?: boolean; + type?: string; }; type TButtonProps = { @@ -465,7 +467,7 @@ type TUiStore = { is_ready_to_deposit_modal_visible: boolean; reports_route_tab_index: number; should_show_cancellation_warning: boolean; - toggleCancellationWarning: (state_change: boolean) => void; + toggleCancellationWarning: (state_change?: boolean) => void; toggleUnsupportedContractModal: (state_change: boolean) => void; toggleReports: (is_visible: boolean) => void; is_real_acc_signup_on: boolean; @@ -541,20 +543,109 @@ type TPortfolioStore = { removePositionById: (id: number) => void; }; +type TAccumulatorBarriersData = { + current_spot: number; + current_spot_time: number; + tick_update_timestamp: number; + accumulators_high_barrier: string; + accumulators_low_barrier: string; + barrier_spot_distance: string; + previous_spot_time: number; +}; +type TAccumulatorContractBarriersData = TAccumulatorBarriersData & { + should_update_contract_barriers: boolean; +}; +type TAddContractParams = { + barrier: string; + contract_id: string; + contract_type: string; + start_time: number; + longcode: string; + underlying: string; + is_tick_contract: boolean; + limit_order: ProposalOpenContract['limit_order']; +}; + +type TContractTradeStore = { + accountSwitchListener: () => Promise; + accu_barriers_timeout_id: NodeJS.Timeout | null; + accumulator_barriers_data: Partial; + accumulator_contract_barriers_data: Partial; + addContract: ({ + barrier, + contract_id, + contract_type, + start_time, + longcode, + underlying, + is_tick_contract, + limit_order, + }: TAddContractParams) => void; + chart_type: string; + clearAccumulatorBarriersData: (should_clear_contract_data_only?: boolean, should_clear_timeout?: boolean) => void; + clearError: () => void; + contracts: TContractStore[]; + error_message: string; + getContractById: (contract_id?: number) => TContractStore; + granularity: string | number; + has_crossed_accu_barriers: boolean; + has_error: boolean; + last_contract: TContractStore | Record; + markers_array: Array<{ + type: string; + contract_info: { + accu_barriers_difference: + | boolean + | { + top: string; + bottom: string; + font: string; + }; + has_crossed_accu_barriers: boolean; + is_accumulator_trade_without_contract: boolean; + }; + key: string; + price_array: [string, string]; + epoch_array: [number]; + }>; + onUnmount: () => void; + prev_chart_type: string; + prev_granularity: string | number | null; + removeContract: (data: { contract_id: string }) => void; + savePreviousChartMode: (chart_type: string, granularity: string | number | null) => void; + setNewAccumulatorBarriersData: ( + new_barriers_data: TAccumulatorBarriersData, + should_update_contract_barriers?: boolean + ) => void; + updateAccumulatorBarriersData: ({ + accumulators_high_barrier, + accumulators_low_barrier, + barrier_spot_distance, + current_spot, + current_spot_time, + should_update_contract_barriers, + underlying, + }: Partial) => void; + updateChartType: (type: string) => void; + updateGranularity: (granularity: number) => void; + updateProposal: (response: ProposalOpenContract) => void; +}; + type TContractStore = { - getContractById: (id: number) => ProposalOpenContract; + clearContractUpdateConfigValues: () => void; contract_info: TPortfolioPosition['contract_info']; - contract_update_stop_loss: string; - contract_update_take_profit: string; - has_contract_update_stop_loss: boolean; + contract_update_history: ContractUpdateHistory; + contract_update_take_profit: number | string; + contract_update_stop_loss: number | string; + digits_info: { [key: number]: { digit: number; spot: string } }; + display_status: string; has_contract_update_take_profit: boolean; - last_contract: { - contract_info: TPortfolioPosition['contract_info']; - digits_info: { [key: number]: { digit: number; spot: string } }; - display_status: string; - is_digit_contract: boolean; - is_ended: boolean; - }; + has_contract_update_stop_loss: boolean; + is_digit_contract: boolean; + is_ended: boolean; + onChange: (param: { name: string; value: string | number | boolean }) => void; + updateLimitOrder: () => void; + validation_errors: { contract_update_stop_loss: string[]; contract_update_take_profit: string[] }; }; type TMenuStore = { @@ -653,7 +744,7 @@ export type TCoreStores = { menu: TMenuStore; ui: TUiStore; portfolio: TPortfolioStore; - contract_trade: TContractStore; + contract_trade: TContractTradeStore; // This should be `any` as this property will be handled in each package. // eslint-disable-next-line @typescript-eslint/no-explicit-any modules: Record; diff --git a/packages/trader/src/App/Components/Elements/ContractDrawer/contract-drawer-card.jsx b/packages/trader/src/App/Components/Elements/ContractDrawer/contract-drawer-card.tsx similarity index 81% rename from packages/trader/src/App/Components/Elements/ContractDrawer/contract-drawer-card.jsx rename to packages/trader/src/App/Components/Elements/ContractDrawer/contract-drawer-card.tsx index a464a0d1e015..3313929b27c4 100644 --- a/packages/trader/src/App/Components/Elements/ContractDrawer/contract-drawer-card.jsx +++ b/packages/trader/src/App/Components/Elements/ContractDrawer/contract-drawer-card.tsx @@ -1,20 +1,45 @@ import classNames from 'classnames'; -import PropTypes from 'prop-types'; import React from 'react'; import { DesktopWrapper, MobileWrapper, Collapsible, ContractCard, useHover } from '@deriv/components'; -import { isCryptoContract, isDesktop, getEndTime, getSymbolDisplayName } from '@deriv/shared'; +import { isCryptoContract, isDesktop, getEndTime, getSymbolDisplayName, toMoment } from '@deriv/shared'; import { getCardLabels, getContractTypeDisplay } from 'Constants/contract'; import { getMarketInformation } from 'Utils/Helpers/market-underlying'; -import { SwipeableContractDrawer } from './swipeable-components.jsx'; -import MarketClosedContractOverlay from './market-closed-contract-overlay.jsx'; +import { SwipeableContractDrawer } from './swipeable-components'; +import MarketClosedContractOverlay from './market-closed-contract-overlay'; import { useTraderStore } from 'Stores/useTraderStores'; import { observer, useStore } from '@deriv/stores'; +type TContractCardBodyProps = React.ComponentProps; +type TContractCardFooterProps = React.ComponentProps; +type TSwipeableContractDrawerProps = React.ComponentProps; + +type TContractDrawerCardProps = { + currency?: string; + is_collapsed: boolean; + is_market_closed: boolean; + is_smarttrader_contract: boolean; + result?: string; + server_time?: moment.Moment; + toggleContractAuditDrawer: () => void; +} & Pick< + TContractCardBodyProps, + | 'contract_info' + | 'contract_update' + | 'is_accumulator' + | 'is_mobile' + | 'is_multiplier' + | 'is_turbos' + | 'is_vanilla' + | 'status' +> & + Pick & + Pick; + const ContractDrawerCard = observer( ({ contract_info, contract_update, - currency, + currency = '', is_accumulator, is_collapsed, is_market_closed, @@ -29,10 +54,10 @@ const ContractDrawerCard = observer( onSwipedUp, onSwipedDown, result, - server_time, + server_time = toMoment(), status, toggleContractAuditDrawer, - }) => { + }: TContractDrawerCardProps) => { const { ui, contract_trade } = useStore(); const { active_symbols } = useTraderStore(); const { @@ -44,7 +69,7 @@ const ContractDrawerCard = observer( toggleCancellationWarning, } = ui; const { getContractById } = contract_trade; - const [hover_ref, should_hide_closed_overlay] = useHover(); + const [hover_ref, should_hide_closed_overlay] = useHover(); const { profit, validation_error } = contract_info; const is_sold = !!getEndTime(contract_info); @@ -77,7 +102,7 @@ const ContractDrawerCard = observer( contract_info={contract_info} contract_update={contract_update} currency={currency} - current_focus={current_focus} + current_focus={current_focus ?? ''} getCardLabels={getCardLabels} getContractById={getContractById} is_accumulator={is_accumulator} @@ -91,7 +116,7 @@ const ContractDrawerCard = observer( server_time={server_time} setCurrentFocus={setCurrentFocus} should_show_cancellation_warning={should_show_cancellation_warning} - status={status} + status={status ?? ''} toggleCancellationWarning={toggleCancellationWarning} /> ); @@ -105,7 +130,6 @@ const ContractDrawerCard = observer( onClickCancel={onClickCancel} onClickSell={onClickSell} server_time={server_time} - status={status} /> ); @@ -121,13 +145,13 @@ const ContractDrawerCard = observer( contract_info={contract_info} getCardLabels={getCardLabels} is_multiplier={is_multiplier} - profit_loss={profit} + profit_loss={Number(profit)} should_show_result_overlay={false} >
0 && !result, - 'dc-contract-card--red': profit < 0 && !result, + 'dc-contract-card--green': Number(profit) > 0 && !result, + 'dc-contract-card--red': Number(profit) < 0 && !result, 'contract-card__market-closed--disabled': is_market_closed && should_hide_closed_overlay, })} ref={hover_ref} @@ -169,13 +193,4 @@ const ContractDrawerCard = observer( } ); -ContractDrawerCard.propTypes = { - currency: PropTypes.string, - is_accumulator: PropTypes.bool, - is_smarttrader_contract: PropTypes.bool, - is_collapsed: PropTypes.bool, - is_turbos: PropTypes.bool, - onClickCancel: PropTypes.func, - onClickSell: PropTypes.func, -}; export default ContractDrawerCard; diff --git a/packages/trader/src/App/Components/Elements/ContractDrawer/contract-drawer.jsx b/packages/trader/src/App/Components/Elements/ContractDrawer/contract-drawer.tsx similarity index 85% rename from packages/trader/src/App/Components/Elements/ContractDrawer/contract-drawer.jsx rename to packages/trader/src/App/Components/Elements/ContractDrawer/contract-drawer.tsx index 0d51eae66590..8b5dbd1496f1 100644 --- a/packages/trader/src/App/Components/Elements/ContractDrawer/contract-drawer.jsx +++ b/packages/trader/src/App/Components/Elements/ContractDrawer/contract-drawer.tsx @@ -1,7 +1,6 @@ import classNames from 'classnames'; -import PropTypes from 'prop-types'; import React from 'react'; -import { withRouter } from 'react-router'; +import { RouteComponentProps, withRouter } from 'react-router'; import { CSSTransition } from 'react-transition-group'; import { DesktopWrapper, MobileWrapper, Div100vhContainer } from '@deriv/components'; import { @@ -11,13 +10,35 @@ import { getDurationTime, getDurationUnitText, getEndTime, + TContractStore, } from '@deriv/shared'; import ContractAudit from 'App/Components/Elements/ContractAudit'; import { PositionsCardLoader } from 'App/Components/Elements/ContentLoader'; import ContractDrawerCard from './contract-drawer-card'; -import { SwipeableContractAudit } from './swipeable-components.jsx'; +import { SwipeableContractAudit } from './swipeable-components'; import { observer, useStore } from '@deriv/stores'; +type TContractDrawerCardProps = React.ComponentProps; +type TContractDrawerProps = RouteComponentProps & { + contract_update_history: TContractStore['contract_update_history']; + is_dark_theme: boolean; + toggleHistoryTab: (state_change?: boolean) => void; +} & Pick< + TContractDrawerCardProps, + | 'contract_info' + | 'contract_update' + | 'is_accumulator' + | 'is_market_closed' + | 'is_multiplier' + | 'is_sell_requested' + | 'is_smarttrader_contract' + | 'is_turbos' + | 'is_vanilla' + | 'onClickCancel' + | 'onClickSell' + | 'status' + >; + const ContractDrawer = observer( ({ contract_info, @@ -35,13 +56,13 @@ const ContractDrawer = observer( onClickSell, status, toggleHistoryTab, - }) => { + }: TContractDrawerProps) => { const { common, ui } = useStore(); const { server_time } = common; const { is_mobile } = ui; const { currency, exit_tick_display_value, is_sold } = contract_info; - const contract_drawer_ref = React.useRef(); - const contract_drawer_card_ref = React.useRef(); + const contract_drawer_ref = React.useRef(null); + const contract_drawer_card_ref = React.useRef(null); const [should_show_contract_audit, setShouldShowContractAudit] = React.useState(false); const exit_spot = isUserSold(contract_info) && !is_accumulator && !is_multiplier && !is_turbos @@ -102,7 +123,7 @@ const ContractDrawer = observer( ) : (
- +
); @@ -119,11 +140,10 @@ const ContractDrawer = observer( 'contract-drawer--is-multiplier-sold': is_multiplier && isMobile() && getEndTime(contract_info), })} style={{ - transform: - should_show_contract_audit && + transform: (should_show_contract_audit && contract_drawer_ref.current && contract_drawer_card_ref.current && - `translateY(calc(${contract_drawer_card_ref.current.clientHeight}px - ${contract_drawer_ref.current.clientHeight}px))`, + `translateY(calc(${contract_drawer_card_ref.current.clientHeight}px - ${contract_drawer_ref.current.clientHeight}px))`) as React.CSSProperties['transform'], }} ref={contract_drawer_ref} > @@ -162,13 +182,4 @@ const ContractDrawer = observer( } ); -ContractDrawer.propTypes = { - is_accumulator: PropTypes.bool, - is_multiplier: PropTypes.bool, - is_vanilla: PropTypes.bool, - is_smarttrader_contract: PropTypes.bool, - is_turbos: PropTypes.bool, - toggleHistoryTab: PropTypes.func, -}; - export default withRouter(ContractDrawer); diff --git a/packages/trader/src/App/Components/Elements/ContractDrawer/index.js b/packages/trader/src/App/Components/Elements/ContractDrawer/index.js deleted file mode 100644 index 34ed63b1b74e..000000000000 --- a/packages/trader/src/App/Components/Elements/ContractDrawer/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import ContractDrawer from './contract-drawer.jsx'; - -export default ContractDrawer; diff --git a/packages/trader/src/App/Components/Elements/ContractDrawer/index.ts b/packages/trader/src/App/Components/Elements/ContractDrawer/index.ts new file mode 100644 index 000000000000..563d551f3b95 --- /dev/null +++ b/packages/trader/src/App/Components/Elements/ContractDrawer/index.ts @@ -0,0 +1,3 @@ +import ContractDrawer from './contract-drawer'; + +export default ContractDrawer; diff --git a/packages/trader/src/App/Components/Elements/ContractDrawer/market-closed-contract-overlay.jsx b/packages/trader/src/App/Components/Elements/ContractDrawer/market-closed-contract-overlay.tsx similarity index 64% rename from packages/trader/src/App/Components/Elements/ContractDrawer/market-closed-contract-overlay.jsx rename to packages/trader/src/App/Components/Elements/ContractDrawer/market-closed-contract-overlay.tsx index 8d3d47aeb479..8538cd1de2c6 100644 --- a/packages/trader/src/App/Components/Elements/ContractDrawer/market-closed-contract-overlay.jsx +++ b/packages/trader/src/App/Components/Elements/ContractDrawer/market-closed-contract-overlay.tsx @@ -1,8 +1,7 @@ import React from 'react'; -import PropTypes from 'prop-types'; import { Text } from '@deriv/components'; -const MarketClosedContractOverlay = ({ validation_error }) => ( +const MarketClosedContractOverlay = ({ validation_error }: { validation_error?: string }) => (
{validation_error} @@ -10,8 +9,4 @@ const MarketClosedContractOverlay = ({ validation_error }) => (
); -MarketClosedContractOverlay.propTypes = { - symbol: PropTypes.string, -}; - export default MarketClosedContractOverlay; diff --git a/packages/trader/src/App/Components/Elements/ContractDrawer/swipeable-components.jsx b/packages/trader/src/App/Components/Elements/ContractDrawer/swipeable-components.tsx similarity index 75% rename from packages/trader/src/App/Components/Elements/ContractDrawer/swipeable-components.jsx rename to packages/trader/src/App/Components/Elements/ContractDrawer/swipeable-components.tsx index 9b9e917c28b3..55aefd7f33e7 100644 --- a/packages/trader/src/App/Components/Elements/ContractDrawer/swipeable-components.jsx +++ b/packages/trader/src/App/Components/Elements/ContractDrawer/swipeable-components.tsx @@ -1,13 +1,21 @@ import classNames from 'classnames'; -import PropTypes from 'prop-types'; import React from 'react'; import ReactDOM from 'react-dom'; import { SwipeableWrapper } from '@deriv/components'; +type TSwipeableContractAuditProps = React.PropsWithChildren<{ + is_multiplier?: boolean; + onSwipedDown?: () => void; +}>; +type TSwipeableContractDrawerProps = React.PropsWithChildren<{ + onSwipedDown?: () => void; + onSwipedUp?: () => void; +}>; + /** * Swipeable components */ -export const SwipeableContractAudit = ({ is_multiplier, children, onSwipedDown }) => { +export const SwipeableContractAudit = ({ is_multiplier, children, onSwipedDown }: TSwipeableContractAuditProps) => { const swipe_handlers = SwipeableWrapper.useSwipeable({ onSwipedDown, }); @@ -31,13 +39,7 @@ export const SwipeableContractAudit = ({ is_multiplier, children, onSwipedDown } ); }; -SwipeableContractAudit.propTypes = { - is_multiplier: PropTypes.bool, - children: PropTypes.node, - onSwipedDown: PropTypes.func, -}; - -export const SwipeableContractDrawer = ({ children, onSwipedDown, onSwipedUp }) => { +export const SwipeableContractDrawer = ({ children, onSwipedDown, onSwipedUp }: TSwipeableContractDrawerProps) => { const swipe_handlers = SwipeableWrapper.useSwipeable({ onSwipedDown, onSwipedUp, @@ -45,9 +47,3 @@ export const SwipeableContractDrawer = ({ children, onSwipedDown, onSwipedUp }) return
{children}
; }; - -SwipeableContractDrawer.propTypes = { - children: PropTypes.node, - onSwipedDown: PropTypes.func, - onSwipedUp: PropTypes.func, -}; diff --git a/packages/trader/src/Modules/Contract/Components/Digits/digits.tsx b/packages/trader/src/Modules/Contract/Components/Digits/digits.tsx index 8ce0d2d4d90e..f8dc050914b3 100644 --- a/packages/trader/src/Modules/Contract/Components/Digits/digits.tsx +++ b/packages/trader/src/Modules/Contract/Components/Digits/digits.tsx @@ -2,17 +2,16 @@ import classNames from 'classnames'; import React from 'react'; import { toJS } from 'mobx'; import { DesktopWrapper, MobileWrapper, Popover, Text } from '@deriv/components'; -import { isMobile, useIsMounted, isContractElapsed } from '@deriv/shared'; +import { isMobile, useIsMounted, isContractElapsed, TContractStore, TTickSpotData } from '@deriv/shared'; import { localize, Localize } from '@deriv/translations'; import { Bounce, SlideIn } from 'App/Components/Animations'; import { getMarketNamesMap } from '../../../../Constants'; import { DigitSpot, LastDigitPrediction } from '../LastDigitPrediction'; import 'Sass/app/modules/contract/digits.scss'; -import { useStore } from '@deriv/stores'; import { useTraderStore } from 'Stores/useTraderStores'; type TTraderStore = ReturnType; -type TOnChangeStatus = { status: string | null; current_tick: number | null }; +type TOnChangeStatus = { status: string | null | undefined; current_tick: number | null }; type TOnLastDigitSpot = { spot: string; is_lost: boolean; @@ -20,40 +19,34 @@ type TOnLastDigitSpot = { is_latest: boolean; is_won: boolean; }; -type TContractStores = - | ReturnType['contract_trade']['last_contract'] - | ReturnType['contract_replay']['contract_store']; - -type TDigitsWrapper = TContractStores & { - digits_array?: string[]; +type TDigitsWrapper = TDigits & { + onChangeStatus?: (params: TOnChangeStatus) => void; + onLastDigitSpot?: (params: TOnLastDigitSpot) => void; +}; +type TDigits = Pick & { + digits_array?: number[]; + display_status?: TContractStore['display_status']; + is_digit_contract?: TContractStore['is_digit_contract']; + is_ended?: TContractStore['is_ended']; is_trade_page?: boolean; onDigitChange?: TTraderStore['onChange']; selected_digit?: TTraderStore['last_digit']; trade_type?: TTraderStore['contract_type']; - onChangeStatus?: (params: TOnChangeStatus) => void; - onLastDigitSpot?: (params: TOnLastDigitSpot) => void; - tick?: - | (Parameters[1] & { - current_tick: number; - }) - | null; -}; - -type TDigits = Pick< - TDigitsWrapper, - | 'contract_info' - | 'digits_array' - | 'digits_info' - | 'display_status' - | 'is_digit_contract' - | 'is_ended' - | 'is_trade_page' - | 'trade_type' - | 'onDigitChange' - | 'selected_digit' -> & { - underlying: ReturnType['symbol']; + tick?: TTickSpotData; + underlying: TTraderStore['symbol']; }; +type TTickStream = NonNullable[number]; +type TTickData = + | TTickSpotData + | null + | undefined + | { + ask: TTickStream['tick']; + bid: TTickStream['tick']; + current_tick: number; + epoch: TTickStream['epoch']; + pip_size?: number; + }; const DigitsWrapper = ({ contract_info, @@ -70,7 +63,7 @@ const DigitsWrapper = ({ ...props }: TDigitsWrapper) => { const has_contract = contract_info.date_start; - let tick = props.tick; + let tick: TTickData = props.tick; const is_tick_ready = is_trade_page ? !!tick : true; const is_contract_elapsed = is_trade_page ? isContractElapsed(contract_info, tick) : false; @@ -95,7 +88,7 @@ const DigitsWrapper = ({ React.useEffect(() => { if (onChangeStatus) { - onChangeStatus({ status, current_tick: tick ? tick.current_tick : null }); + onChangeStatus({ status, current_tick: tick && 'current_tick' in tick ? tick.current_tick : null }); } }, [tick, is_trade_page, display_status, onChangeStatus, status]); From 070a003cacc6521490d3cbd8468a14f14e795cfc Mon Sep 17 00:00:00 2001 From: kate-deriv <121025168+kate-deriv@users.noreply.github.com> Date: Thu, 24 Aug 2023 08:56:50 +0300 Subject: [PATCH 42/55] Kate/dtra 357/ts contract audit files (#23) * refactor: apply suggestion from prev pr * refactor: start ts migration of contract audit * chore: change comment * refactor: ts of contract audit item * refactor: ts migration of contract details * refactor: ts migration of contract history * refactor: add preprepared types * refactor: tests * chore: apply nit * refactor: apply suggestions * refactor: apply suggestions --- .../components/src/components/money/money.tsx | 4 +- packages/shared/src/utils/helpers/logic.ts | 29 ++-------- ...ails.spec.js => contract-details.spec.tsx} | 17 +++--- ...audit-item.jsx => contract-audit-item.tsx} | 21 ++++--- ...{contract-audit.jsx => contract-audit.tsx} | 52 +++++++++++------- ...tract-details.jsx => contract-details.tsx} | 55 ++++++++++--------- ...tract-history.jsx => contract-history.tsx} | 23 ++++---- .../Elements/ContractAudit/index.js | 2 +- .../ContractDrawer/contract-drawer.tsx | 1 - .../Form/Purchase/contract-info.tsx | 15 ++--- .../Form/Purchase/value-movement.tsx | 15 +++-- 11 files changed, 112 insertions(+), 122 deletions(-) rename packages/trader/src/App/Components/Elements/ContractAudit/__tests__/{contract-details.spec.js => contract-details.spec.tsx} (85%) rename packages/trader/src/App/Components/Elements/ContractAudit/{contract-audit-item.jsx => contract-audit-item.tsx} (80%) rename packages/trader/src/App/Components/Elements/ContractAudit/{contract-audit.jsx => contract-audit.tsx} (55%) rename packages/trader/src/App/Components/Elements/ContractAudit/{contract-details.jsx => contract-details.tsx} (84%) rename packages/trader/src/App/Components/Elements/ContractAudit/{contract-history.jsx => contract-history.tsx} (77%) diff --git a/packages/components/src/components/money/money.tsx b/packages/components/src/components/money/money.tsx index d9556a181846..954ec009856c 100644 --- a/packages/components/src/components/money/money.tsx +++ b/packages/components/src/components/money/money.tsx @@ -19,8 +19,8 @@ const Money = ({ show_currency = false, }: Partial) => { let sign = ''; - if (+amount && (amount < 0 || has_sign)) { - sign = amount > 0 ? '+' : '-'; + if (Number(amount) && (Number(amount) < 0 || has_sign)) { + sign = Number(amount) > 0 ? '+' : '-'; } // if it's formatted already then don't make any changes unless we should remove extra -/+ signs diff --git a/packages/shared/src/utils/helpers/logic.ts b/packages/shared/src/utils/helpers/logic.ts index 22998efd1162..8fb64ec0b5b2 100644 --- a/packages/shared/src/utils/helpers/logic.ts +++ b/packages/shared/src/utils/helpers/logic.ts @@ -4,31 +4,10 @@ import { isAccumulatorContract, isOpen, isUserSold } from '../contract'; import { TContractInfo, TContractStore } from '../contract/contract-types'; import { TickSpotData } from '@deriv/api-types'; -type TIsEndedBeforeCancellationExpired = TGetEndTime & { - cancellation: { - ask_price: number; - date_expiry: number; - }; -}; - type TIsSoldBeforeStart = Required>; type TIsStarted = Required>; -type TGetEndTime = Pick< - TContractInfo, - | 'is_expired' - | 'sell_time' - | 'status' - | 'tick_count' - | 'bid_price' - | 'buy_price' - | 'contract_id' - | 'is_valid_to_sell' - | 'profit' -> & - Required>; - export const isContractElapsed = (contract_info: TContractInfo, tick?: TickSpotData) => { if (isEmptyObject(tick) || isEmptyObject(contract_info)) return false; const end_time = getEndTime(contract_info) || 0; @@ -39,9 +18,13 @@ export const isContractElapsed = (contract_info: TContractInfo, tick?: TickSpotD return false; }; -export const isEndedBeforeCancellationExpired = (contract_info: TIsEndedBeforeCancellationExpired) => { +export const isEndedBeforeCancellationExpired = (contract_info: TContractInfo) => { const end_time = getEndTime(contract_info) || 0; - return !!(contract_info.cancellation && end_time < contract_info.cancellation.date_expiry); + return !!( + contract_info.cancellation && + contract_info.cancellation.date_expiry && + end_time < contract_info.cancellation.date_expiry + ); }; export const isSoldBeforeStart = (contract_info: TIsSoldBeforeStart) => diff --git a/packages/trader/src/App/Components/Elements/ContractAudit/__tests__/contract-details.spec.js b/packages/trader/src/App/Components/Elements/ContractAudit/__tests__/contract-details.spec.tsx similarity index 85% rename from packages/trader/src/App/Components/Elements/ContractAudit/__tests__/contract-details.spec.js rename to packages/trader/src/App/Components/Elements/ContractAudit/__tests__/contract-details.spec.tsx index edea635534de..9f1b66141a40 100644 --- a/packages/trader/src/App/Components/Elements/ContractAudit/__tests__/contract-details.spec.js +++ b/packages/trader/src/App/Components/Elements/ContractAudit/__tests__/contract-details.spec.tsx @@ -1,5 +1,6 @@ import React from 'react'; import { screen, render } from '@testing-library/react'; +import { TContractInfo } from '@deriv/shared'; import ContractDetails from '../contract-details'; describe('ContractDetails', () => { @@ -46,27 +47,27 @@ describe('ContractDetails', () => { }; it('renders the ContractAuditItems specific to Vanilla component when is_vanilla is true', () => { - const wrapper = render( + render( ); - expect(wrapper.queryAllByTestId('dt_bt_label')).toHaveLength(2); + expect(screen.queryAllByTestId('dt_bt_label')).toHaveLength(2); }); it('renders the Payout per point label when is_vanilla is true', () => { render( ); diff --git a/packages/trader/src/App/Components/Elements/ContractAudit/contract-audit-item.jsx b/packages/trader/src/App/Components/Elements/ContractAudit/contract-audit-item.tsx similarity index 80% rename from packages/trader/src/App/Components/Elements/ContractAudit/contract-audit-item.jsx rename to packages/trader/src/App/Components/Elements/ContractAudit/contract-audit-item.tsx index efd400b90269..9fbee190778f 100644 --- a/packages/trader/src/App/Components/Elements/ContractAudit/contract-audit-item.jsx +++ b/packages/trader/src/App/Components/Elements/ContractAudit/contract-audit-item.tsx @@ -1,9 +1,17 @@ -import PropTypes from 'prop-types'; import React from 'react'; import { formatDate, formatTime } from '@deriv/shared'; import { Text } from '@deriv/components'; -const ContractAuditItem = ({ icon, id, label, timestamp, value, value2 }) => ( +type TContractAuditItem = { + icon?: React.ReactNode; + id: string; + label?: string; + timestamp?: number; + value: React.ReactNode; + value2?: React.ReactNode; +}; + +const ContractAuditItem = ({ icon, id, label, timestamp, value, value2 }: TContractAuditItem) => (
{icon &&
{icon}
}
@@ -34,13 +42,4 @@ const ContractAuditItem = ({ icon, id, label, timestamp, value, value2 }) => (
); -ContractAuditItem.propTypes = { - icon: PropTypes.node, - id: PropTypes.string, - label: PropTypes.string, - timestamp: PropTypes.string, - value: PropTypes.PropTypes.oneOfType([PropTypes.number, PropTypes.string, PropTypes.node]), - value2: PropTypes.PropTypes.oneOfType([PropTypes.number, PropTypes.string]), -}; - export default ContractAuditItem; diff --git a/packages/trader/src/App/Components/Elements/ContractAudit/contract-audit.jsx b/packages/trader/src/App/Components/Elements/ContractAudit/contract-audit.tsx similarity index 55% rename from packages/trader/src/App/Components/Elements/ContractAudit/contract-audit.jsx rename to packages/trader/src/App/Components/Elements/ContractAudit/contract-audit.tsx index 24daae7534b5..aaee80ca2f55 100644 --- a/packages/trader/src/App/Components/Elements/ContractAudit/contract-audit.jsx +++ b/packages/trader/src/App/Components/Elements/ContractAudit/contract-audit.tsx @@ -1,35 +1,56 @@ -import PropTypes from 'prop-types'; import React from 'react'; import { Tabs } from '@deriv/components'; import { localize } from '@deriv/translations'; -import { WS } from '@deriv/shared'; +import { WS, TContractStore, TContractInfo } from '@deriv/shared'; +import { useTraderStore } from 'Stores/useTraderStores'; import ContractDetails from './contract-details'; -import ContractHistory from './contract-history.jsx'; +import ContractHistory from './contract-history'; + +type TContractUpdateHistory = TContractStore['contract_update_history']; + +type TContractAudit = Partial< + Pick, 'is_accumulator' | 'is_turbos' | 'is_multiplier' | 'is_vanilla'> +> & { + contract_update_history: TContractUpdateHistory; + contract_end_time: number | undefined; + contract_info: TContractInfo; + duration: string | number; + duration_unit: string; + exit_spot: string | undefined; + has_result: boolean; + is_dark_theme: boolean; + is_open: boolean; + toggleHistoryTab: (state_change?: boolean) => void; +}; + +type TResponse = { + contract_update_history: TContractUpdateHistory; +}; const ContractAudit = ({ contract_update_history, has_result, is_accumulator, is_multiplier, - is_smarttrader_contract, is_turbos, toggleHistoryTab, ...props -}) => { +}: TContractAudit) => { const { contract_id, currency } = props.contract_info; - const [update_history, setUpdateHistory] = React.useState([]); + const [update_history, setUpdateHistory] = React.useState([]); - const getSortedUpdateHistory = history => history.sort((a, b) => b.order_date - a.order_date); + const getSortedUpdateHistory = (history: TContractUpdateHistory) => + history.sort((a, b) => Number(b?.order_date) - Number(a?.order_date)); React.useEffect(() => { if (!!contract_update_history.length && contract_update_history.length > update_history.length) setUpdateHistory(getSortedUpdateHistory(contract_update_history)); }, [contract_update_history, update_history]); - const onTabItemClick = tab_index => { - toggleHistoryTab(tab_index); + const onTabItemClick = (tab_index: number) => { + toggleHistoryTab(!!tab_index); if (tab_index) { - WS.contractUpdateHistory(contract_id).then(response => { + WS.contractUpdateHistory(contract_id).then((response: TResponse) => { setUpdateHistory(getSortedUpdateHistory(response.contract_update_history)); }); } @@ -58,15 +79,4 @@ const ContractAudit = ({ ); }; -ContractAudit.propTypes = { - contract_info: PropTypes.object, - contract_update_history: PropTypes.array, - has_result: PropTypes.bool, - is_accumulator: PropTypes.bool, - is_multiplier: PropTypes.bool, - is_smarttrader_contract: PropTypes.bool, - is_turbos: PropTypes.bool, - toggleHistoryTab: PropTypes.func, -}; - export default ContractAudit; diff --git a/packages/trader/src/App/Components/Elements/ContractAudit/contract-details.jsx b/packages/trader/src/App/Components/Elements/ContractAudit/contract-details.tsx similarity index 84% rename from packages/trader/src/App/Components/Elements/ContractAudit/contract-details.jsx rename to packages/trader/src/App/Components/Elements/ContractAudit/contract-details.tsx index 204e2a72f939..3d8bcb3c3cf3 100644 --- a/packages/trader/src/App/Components/Elements/ContractAudit/contract-details.jsx +++ b/packages/trader/src/App/Components/Elements/ContractAudit/contract-details.tsx @@ -1,4 +1,3 @@ -import PropTypes from 'prop-types'; import React from 'react'; import { Money, Icon, ThemedScrollbars } from '@deriv/components'; import { localize } from '@deriv/translations'; @@ -16,6 +15,7 @@ import { isEndedBeforeCancellationExpired, isUserCancelled, toGMTFormat, + TContractInfo, } from '@deriv/shared'; import { addCommaToNumber, @@ -23,10 +23,26 @@ import { getBarrierValue, isDigitType, } from 'App/Components/Elements/PositionsDrawer/helpers'; -import ContractAuditItem from './contract-audit-item.jsx'; +import ContractAuditItem from './contract-audit-item'; import { isCancellationExpired } from 'Stores/Modules/Trading/Helpers/logic'; -const ContractDetails = ({ contract_end_time, contract_info, duration, duration_unit, exit_spot, is_vanilla }) => { +type TContractDetails = { + contract_end_time?: number; + contract_info: TContractInfo; + duration: number | string; + duration_unit: string; + exit_spot?: string; + is_vanilla?: boolean; +}; + +const ContractDetails = ({ + contract_end_time, + contract_info, + duration, + duration_unit, + exit_spot, + is_vanilla, +}: TContractDetails) => { const { commission, contract_type, @@ -44,14 +60,14 @@ const ContractDetails = ({ contract_end_time, contract_info, duration, duration_ display_number_of_contracts, } = contract_info; - const is_profit = profit >= 0; + const is_profit = Number(profit) >= 0; const cancellation_price = getCancellationPrice(contract_info); const show_barrier = !is_vanilla && !isAccumulatorContract(contract_type) && !isSmartTraderContract(contract_type); - const show_duration = !isAccumulatorContract(contract_type) || !isNaN(contract_end_time); + const show_duration = !isAccumulatorContract(contract_type) || !isNaN(Number(contract_end_time)); const show_payout_per_point = isTurbosContract(contract_type) || is_vanilla; const ticks_duration_text = isAccumulatorContract(contract_type) ? `${tick_passed}/${tick_count} ${localize('ticks')}` - : `${tick_count} ${tick_count < 2 ? localize('tick') : localize('ticks')}`; + : `${tick_count} ${Number(tick_count) < 2 ? localize('tick') : localize('ticks')}`; const getLabel = () => { if (isUserSold(contract_info) && isEndedBeforeCancellationExpired(contract_info)) @@ -60,7 +76,6 @@ const ContractDetails = ({ contract_end_time, contract_info, duration, duration_ if (isCancellationExpired(contract_info)) return localize('Deal cancellation (expired)'); return localize('Deal cancellation (active)'); }; - return (
@@ -77,7 +92,7 @@ const ContractDetails = ({ contract_end_time, contract_info, duration, duration_ id='dt_commission_label' icon={} label={localize('Commission')} - value={} + value={} /> {!!cancellation_price && ( } label={localize('Duration')} - value={tick_count > 0 ? ticks_duration_text : `${duration} ${duration_unit}`} + value={Number(tick_count) > 0 ? ticks_duration_text : `${duration} ${duration_unit}`} /> )} {is_vanilla && ( @@ -147,7 +162,7 @@ const ContractDetails = ({ contract_end_time, contract_info, duration, duration_ id='dt_start_time_label' icon={} label={localize('Start time')} - value={toGMTFormat(epochToMoment(date_start)) || ' - '} + value={toGMTFormat(epochToMoment(Number(date_start))) || ' - '} /> {!isDigitType(contract_type) && ( } label={localize('Entry spot')} value={addCommaToNumber(entry_spot_display_value) || ' - '} - value2={toGMTFormat(epochToMoment(entry_tick_time)) || ' - '} + value2={toGMTFormat(epochToMoment(Number(entry_tick_time))) || ' - '} /> )} - {!isNaN(exit_spot) && ( + {!isNaN(Number(exit_spot)) && ( } label={localize('Exit spot')} value={addCommaToNumber(exit_spot) || ' - '} - value2={toGMTFormat(epochToMoment(exit_tick_time)) || ' - '} + value2={toGMTFormat(epochToMoment(Number(exit_tick_time))) || ' - '} /> )} - {!isNaN(contract_end_time) && ( + {!isNaN(Number(contract_end_time)) && ( } label={localize('Exit time')} - value={toGMTFormat(epochToMoment(contract_end_time)) || ' - '} + value={toGMTFormat(epochToMoment(Number(contract_end_time))) || ' - '} /> )}
@@ -180,14 +195,4 @@ const ContractDetails = ({ contract_end_time, contract_info, duration, duration_ ); }; -ContractDetails.propTypes = { - contract_end_time: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - contract_info: PropTypes.object, - date_start: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - duration: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - duration_unit: PropTypes.string, - exit_spot: PropTypes.string, - is_vanilla: PropTypes.bool, -}; - export default ContractDetails; diff --git a/packages/trader/src/App/Components/Elements/ContractAudit/contract-history.jsx b/packages/trader/src/App/Components/Elements/ContractAudit/contract-history.tsx similarity index 77% rename from packages/trader/src/App/Components/Elements/ContractAudit/contract-history.jsx rename to packages/trader/src/App/Components/Elements/ContractAudit/contract-history.tsx index 239c8ee4d8fc..7323084209ac 100644 --- a/packages/trader/src/App/Components/Elements/ContractAudit/contract-history.jsx +++ b/packages/trader/src/App/Components/Elements/ContractAudit/contract-history.tsx @@ -1,12 +1,14 @@ -import PropTypes from 'prop-types'; import React from 'react'; import { Icon, Money, ThemedScrollbars, Text } from '@deriv/components'; -import { isMobile } from '@deriv/shared'; - +import { isMobile, TContractStore } from '@deriv/shared'; import { localize } from '@deriv/translations'; -import ContractAuditItem from './contract-audit-item.jsx'; +import ContractAuditItem from './contract-audit-item'; -const ContractHistory = ({ currency, history = [] }) => { +type TContractHistory = { + currency?: string; + history: [] | TContractStore['contract_update_history']; +}; +const ContractHistory = ({ currency, history = [] }: TContractHistory) => { if (!history.length) { return (
@@ -26,11 +28,11 @@ const ContractHistory = ({ currency, history = [] }) => { key={key} id={`dt_history_label_${key}`} label={item.display_name} - timestamp={+item.order_date} + timestamp={Number(item?.order_date)} value={ - Math.abs(+item.order_amount) !== 0 ? ( + Math.abs(Number(item.order_amount)) !== 0 ? ( - {+item.order_amount < 0 && -} + {Number(item.order_amount) < 0 && -} {item.value && ( @@ -50,9 +52,4 @@ const ContractHistory = ({ currency, history = [] }) => { ); }; -ContractHistory.propTypes = { - currency: PropTypes.string, - history: PropTypes.array, -}; - export default ContractHistory; diff --git a/packages/trader/src/App/Components/Elements/ContractAudit/index.js b/packages/trader/src/App/Components/Elements/ContractAudit/index.js index 1cf766ffd8e6..5051cc70b69f 100644 --- a/packages/trader/src/App/Components/Elements/ContractAudit/index.js +++ b/packages/trader/src/App/Components/Elements/ContractAudit/index.js @@ -1,3 +1,3 @@ -import ContractAudit from './contract-audit.jsx'; +import ContractAudit from './contract-audit'; export default ContractAudit; diff --git a/packages/trader/src/App/Components/Elements/ContractDrawer/contract-drawer.tsx b/packages/trader/src/App/Components/Elements/ContractDrawer/contract-drawer.tsx index 8b5dbd1496f1..69045cc10ab8 100644 --- a/packages/trader/src/App/Components/Elements/ContractDrawer/contract-drawer.tsx +++ b/packages/trader/src/App/Components/Elements/ContractDrawer/contract-drawer.tsx @@ -77,7 +77,6 @@ const ContractDrawer = observer( is_accumulator={is_accumulator} is_dark_theme={is_dark_theme} is_multiplier={is_multiplier} - is_smarttrader_contract={is_smarttrader_contract} is_open is_turbos={is_turbos} duration={getDurationTime(contract_info)} diff --git a/packages/trader/src/Modules/Trading/Components/Form/Purchase/contract-info.tsx b/packages/trader/src/Modules/Trading/Components/Form/Purchase/contract-info.tsx index 8abd3e1b61ec..6c3a53f41e0e 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/Purchase/contract-info.tsx +++ b/packages/trader/src/Modules/Trading/Components/Form/Purchase/contract-info.tsx @@ -3,19 +3,16 @@ import React from 'react'; import { DesktopWrapper, MobileWrapper, Money, Popover, Text } from '@deriv/components'; import { Localize, localize } from '@deriv/translations'; import { getContractSubtype, getCurrencyDisplayCode, getLocalizedBasis, getGrowthRatePercentage } from '@deriv/shared'; +import { useTraderStore } from 'Stores/useTraderStores'; import CancelDealInfo from './cancel-deal-info'; import ValueMovement from './value-movement'; import { TProposalTypeInfo } from 'Types'; -type TContractInfo = { - basis: string; - currency: string; - growth_rate: number; - has_increased: boolean; - is_accumulator: boolean; - is_multiplier: boolean; - is_turbos: boolean; - is_vanilla: boolean; +type TContractInfo = Pick< + ReturnType, + 'basis' | 'growth_rate' | 'is_accumulator' | 'is_turbos' | 'is_vanilla' | 'is_multiplier' | 'currency' +> & { + has_increased?: boolean | null; is_loading: boolean; proposal_info: TProposalTypeInfo; should_fade: boolean; diff --git a/packages/trader/src/Modules/Trading/Components/Form/Purchase/value-movement.tsx b/packages/trader/src/Modules/Trading/Components/Form/Purchase/value-movement.tsx index 928e8cf957e0..ff65e52dfc75 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/Purchase/value-movement.tsx +++ b/packages/trader/src/Modules/Trading/Components/Form/Purchase/value-movement.tsx @@ -1,19 +1,18 @@ import classNames from 'classnames'; import React from 'react'; import { Icon, Money } from '@deriv/components'; -import { TProposalTypeInfo } from 'Types'; +import ContractInfo from './contract-info'; -type TValueMovement = { +type TValueMovement = Partial< + Pick< + React.ComponentProps, + 'is_turbos' | 'is_vanilla' | 'currency' | 'has_increased' | 'proposal_info' + > +> & { has_error_or_not_loaded: boolean; - proposal_info?: TProposalTypeInfo; - currency?: string; - has_increased?: boolean; - is_turbos?: boolean; - is_vanilla?: boolean; value?: number | string; show_currency?: boolean; }; - const ValueMovement = ({ has_error_or_not_loaded, proposal_info, From 7147560a0b85dde2e8a40e45c8e481ddde6abfdc Mon Sep 17 00:00:00 2001 From: kate-deriv Date: Thu, 24 Aug 2023 10:03:04 +0300 Subject: [PATCH 43/55] chore: fix of sonar cloud --- .../Components/Elements/ContractAudit/contract-details.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/trader/src/App/Components/Elements/ContractAudit/contract-details.tsx b/packages/trader/src/App/Components/Elements/ContractAudit/contract-details.tsx index 3d8bcb3c3cf3..c4df22dd8ab9 100644 --- a/packages/trader/src/App/Components/Elements/ContractAudit/contract-details.tsx +++ b/packages/trader/src/App/Components/Elements/ContractAudit/contract-details.tsx @@ -140,7 +140,11 @@ const ContractDetails = ({ id='dt_bt_label' icon={} label={localize('Payout per point')} - value={`${display_number_of_contracts} ${getCurrencyDisplayCode(currency)}` || ' - '} + value={ + display_number_of_contracts + ? `${display_number_of_contracts} ${getCurrencyDisplayCode(currency)}` + : ' - ' + } /> )} From 2cf925bb9c37fa83bae6240a2dceca5e5da1e48d Mon Sep 17 00:00:00 2001 From: Maryia <103177211+maryia-deriv@users.noreply.github.com> Date: Thu, 24 Aug 2023 10:04:36 +0300 Subject: [PATCH 44/55] Maryia/dtra-373/remove localize from ContractCardHeader component (#25) * refactor: remove localize from contract-card-header * refactor: use Localize component instead of localize helper * build: install RTL deps in shared package --- package-lock.json | 2 +- .../contract-card-header.tsx | 6 ++---- .../contract-card-items/contract-type-cell.tsx | 2 +- packages/shared/package.json | 4 ++++ .../utils/contract/__tests__/contract.spec.ts | 18 ++++++++++++++++++ .../contract/{contract.ts => contract.tsx} | 13 ++++++++++++- 6 files changed, 38 insertions(+), 7 deletions(-) rename packages/shared/src/utils/contract/{contract.ts => contract.tsx} (95%) diff --git a/package-lock.json b/package-lock.json index e24c0d483403..f457923e7541 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "@contentpass/zxcvbn": "^4.4.3", "@datadog/browser-logs": "^4.36.0", "@datadog/browser-rum": "^4.37.0", - "@deriv/api-types": "^1.0.94", + "@deriv/api-types": "^1.0.116", "@deriv/deriv-api": "^1.0.11", "@deriv/deriv-charts": "1.3.2", "@deriv/js-interpreter": "^3.0.0", diff --git a/packages/components/src/components/contract-card/contract-card-items/contract-card-header.tsx b/packages/components/src/components/contract-card/contract-card-items/contract-card-header.tsx index 4079df1bb026..1ce4c8a311d1 100644 --- a/packages/components/src/components/contract-card/contract-card-items/contract-card-header.tsx +++ b/packages/components/src/components/contract-card/contract-card-items/contract-card-header.tsx @@ -1,17 +1,16 @@ import React from 'react'; import classNames from 'classnames'; import { CSSTransition } from 'react-transition-group'; -import { localize } from '@deriv/translations'; import { isHighLow, getCurrentTick, getGrowthRatePercentage, - getContractSubtype, isAccumulatorContract, isSmartTraderContract, isBot, isMobile, isTurbosContract, + getLocalizedTurbosSubtype, } from '@deriv/shared'; import ContractTypeCell from './contract-type-cell'; import Button from '../../button'; @@ -83,8 +82,7 @@ const ContractCardHeader = ({ }, { is_param_displayed: is_turbos, - displayed_param: - getContractSubtype(contract_type || '') === 'Long' ? localize('Long') : localize('Short'), + displayed_param: getLocalizedTurbosSubtype(contract_type), }, ], [multiplier, growth_rate, is_accumulator, is_turbos, contract_type] diff --git a/packages/components/src/components/contract-card/contract-card-items/contract-type-cell.tsx b/packages/components/src/components/contract-card/contract-card-items/contract-type-cell.tsx index ef5d594ac828..41629909cfd9 100644 --- a/packages/components/src/components/contract-card/contract-card-items/contract-type-cell.tsx +++ b/packages/components/src/components/contract-card/contract-card-items/contract-type-cell.tsx @@ -9,7 +9,7 @@ export type TContractTypeCellProps = { is_high_low: boolean; multiplier?: number; type?: string; - displayed_trade_param?: string; + displayed_trade_param?: React.ReactNode; }; const ContractTypeCell = ({ diff --git a/packages/shared/package.json b/packages/shared/package.json index 5fabf7e9ca21..fa5d8314f434 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -32,6 +32,10 @@ "devDependencies": { "@babel/eslint-parser": "^7.17.0", "@babel/preset-react": "^7.16.7", + "@testing-library/jest-dom": "^5.12.0", + "@testing-library/react": "^12.0.0", + "@testing-library/react-hooks": "^7.0.2", + "@testing-library/user-event": "^13.5.0", "@types/jsdom": "^20.0.0", "@types/react": "^18.0.7", "@types/react-dom": "^18.0.0", diff --git a/packages/shared/src/utils/contract/__tests__/contract.spec.ts b/packages/shared/src/utils/contract/__tests__/contract.spec.ts index b2fd4147641d..4923e95520cf 100644 --- a/packages/shared/src/utils/contract/__tests__/contract.spec.ts +++ b/packages/shared/src/utils/contract/__tests__/contract.spec.ts @@ -1,3 +1,4 @@ +import { screen, render } from '@testing-library/react'; import * as ContractUtils from '../contract'; import { TContractInfo, TDigitsInfo, TTickItem } from '../contract-types'; @@ -574,3 +575,20 @@ describe('getContractStatus', () => { ).toBe('won'); }); }); + +describe('getLocalizedTurbosSubtype', () => { + it('should return an empty string for non-turbos contracts', () => { + render(ContractUtils.getLocalizedTurbosSubtype('CALL') as JSX.Element); + expect(screen.queryByText('Long')).not.toBeInTheDocument(); + expect(screen.queryByText('Short')).not.toBeInTheDocument(); + expect(ContractUtils.getLocalizedTurbosSubtype('CALL')).toBe(''); + }); + it('should render "Long" for TURBOSLONG contract', () => { + render(ContractUtils.getLocalizedTurbosSubtype('TURBOSLONG') as JSX.Element); + expect(screen.getByText('Long')).toBeInTheDocument(); + }); + it('should render "Short" for TURBOSSHORT contract', () => { + render(ContractUtils.getLocalizedTurbosSubtype('TURBOSSHORT') as JSX.Element); + expect(screen.getByText('Short')).toBeInTheDocument(); + }); +}); diff --git a/packages/shared/src/utils/contract/contract.ts b/packages/shared/src/utils/contract/contract.tsx similarity index 95% rename from packages/shared/src/utils/contract/contract.ts rename to packages/shared/src/utils/contract/contract.tsx index 0931087f4128..96d4acca13cd 100644 --- a/packages/shared/src/utils/contract/contract.ts +++ b/packages/shared/src/utils/contract/contract.tsx @@ -1,4 +1,6 @@ import moment from 'moment'; +import React from 'react'; +import { Localize } from '@deriv/translations'; import { unique } from '../object'; import { capitalizeFirstLetter } from '../string/string_util'; import { TContractInfo, TDigitsInfo, TLimitOrder, TTickItem } from './contract-types'; @@ -207,7 +209,16 @@ export const shouldShowExpiration = (symbol = '') => symbol.startsWith('cry'); export const shouldShowCancellation = (symbol = '') => !/^(cry|CRASH|BOOM|stpRNG|WLD|JD)/.test(symbol); -export const getContractSubtype = (type: string) => +export const getContractSubtype = (type = '') => /(VANILLALONG|TURBOS)/i.test(type) ? capitalizeFirstLetter(type.replace(/(VANILLALONG|TURBOS)/i, '').toLowerCase()) : ''; + +export const getLocalizedTurbosSubtype = (contract_type = '') => { + if (!isTurbosContract(contract_type)) return ''; + return getContractSubtype(contract_type) === 'Long' ? ( + + ) : ( + + ); +}; From 915ea638fe64bc361366672e3a566863c69a1233 Mon Sep 17 00:00:00 2001 From: kate-deriv Date: Thu, 24 Aug 2023 10:52:34 +0300 Subject: [PATCH 45/55] fix: sonar cloud codesmells --- packages/shared/src/utils/helpers/logic.ts | 6 +----- .../Components/Elements/ContractAudit/contract-details.tsx | 3 ++- .../Components/Elements/ContractAudit/contract-history.tsx | 2 +- .../src/Modules/Contract/Components/Digits/digits.tsx | 2 +- 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/packages/shared/src/utils/helpers/logic.ts b/packages/shared/src/utils/helpers/logic.ts index 8fb64ec0b5b2..435d9c5a6a34 100644 --- a/packages/shared/src/utils/helpers/logic.ts +++ b/packages/shared/src/utils/helpers/logic.ts @@ -20,11 +20,7 @@ export const isContractElapsed = (contract_info: TContractInfo, tick?: TickSpotD export const isEndedBeforeCancellationExpired = (contract_info: TContractInfo) => { const end_time = getEndTime(contract_info) || 0; - return !!( - contract_info.cancellation && - contract_info.cancellation.date_expiry && - end_time < contract_info.cancellation.date_expiry - ); + return !!(contract_info.cancellation?.date_expiry && end_time < contract_info.cancellation.date_expiry); }; export const isSoldBeforeStart = (contract_info: TIsSoldBeforeStart) => diff --git a/packages/trader/src/App/Components/Elements/ContractAudit/contract-details.tsx b/packages/trader/src/App/Components/Elements/ContractAudit/contract-details.tsx index c4df22dd8ab9..adb8472cb0f6 100644 --- a/packages/trader/src/App/Components/Elements/ContractAudit/contract-details.tsx +++ b/packages/trader/src/App/Components/Elements/ContractAudit/contract-details.tsx @@ -65,9 +65,10 @@ const ContractDetails = ({ const show_barrier = !is_vanilla && !isAccumulatorContract(contract_type) && !isSmartTraderContract(contract_type); const show_duration = !isAccumulatorContract(contract_type) || !isNaN(Number(contract_end_time)); const show_payout_per_point = isTurbosContract(contract_type) || is_vanilla; + const ticks_label = Number(tick_count) < 2 ? localize('tick') : localize('ticks'); const ticks_duration_text = isAccumulatorContract(contract_type) ? `${tick_passed}/${tick_count} ${localize('ticks')}` - : `${tick_count} ${Number(tick_count) < 2 ? localize('tick') : localize('ticks')}`; + : `${tick_count} ${ticks_label}`; const getLabel = () => { if (isUserSold(contract_info) && isEndedBeforeCancellationExpired(contract_info)) diff --git a/packages/trader/src/App/Components/Elements/ContractAudit/contract-history.tsx b/packages/trader/src/App/Components/Elements/ContractAudit/contract-history.tsx index 7323084209ac..af17015ee0a6 100644 --- a/packages/trader/src/App/Components/Elements/ContractAudit/contract-history.tsx +++ b/packages/trader/src/App/Components/Elements/ContractAudit/contract-history.tsx @@ -25,7 +25,7 @@ const ContractHistory = ({ currency, history = [] }: TContractHistory) => {
{history.map((item, key) => ( Date: Thu, 7 Sep 2023 18:49:22 +0300 Subject: [PATCH 46/55] fix: build TS errors (#32) --- .../Components/Elements/purchase-button.tsx | 4 +- .../Components/Elements/purchase-fieldset.tsx | 4 +- .../Modules/Trading/Containers/purchase.tsx | 6 +- .../Modules/SmartChart/chart-barrier-store.ts | 2 +- .../Modules/Trading/Actions/purchase.ts | 17 +- .../Modules/Trading/Actions/start-date.ts | 2 +- .../Trading/Constants/validation-rules.ts | 428 +++++++++--------- .../Modules/Trading/Helpers/allow-equals.ts | 8 +- .../Modules/Trading/Helpers/limit-orders.ts | 18 +- .../Stores/Modules/Trading/Helpers/logic.ts | 4 +- .../Modules/Trading/Helpers/proposal.ts | 2 +- .../src/Stores/Modules/Trading/trade-store.ts | 27 +- .../trader/src/Stores/useTraderStores.tsx | 4 +- packages/trader/src/Types/common-prop.type.ts | 15 +- 14 files changed, 258 insertions(+), 283 deletions(-) diff --git a/packages/trader/src/Modules/Trading/Components/Elements/purchase-button.tsx b/packages/trader/src/Modules/Trading/Components/Elements/purchase-button.tsx index 09ac715286e0..3590eb73ddd1 100644 --- a/packages/trader/src/Modules/Trading/Components/Elements/purchase-button.tsx +++ b/packages/trader/src/Modules/Trading/Components/Elements/purchase-button.tsx @@ -3,11 +3,11 @@ import React from 'react'; import { DesktopWrapper, MobileWrapper, Money, IconTradeTypes, Text } from '@deriv/components'; import ContractInfo from 'Modules/Trading/Components/Form/Purchase/contract-info'; import { getContractTypeDisplay, getGrowthRatePercentage } from '@deriv/shared'; -import { TProposalTypeInfo } from 'Types'; +import { TProposalTypeInfo, TTradeStore } from 'Types'; type TPurchaseButton = { basis: string; - buy_info: { error?: string }; + buy_info: TTradeStore['purchase_info']; currency: string; growth_rate: number; has_deal_cancellation: boolean; diff --git a/packages/trader/src/Modules/Trading/Components/Elements/purchase-fieldset.tsx b/packages/trader/src/Modules/Trading/Components/Elements/purchase-fieldset.tsx index 4c8475760dcc..7524f8b4e9ab 100644 --- a/packages/trader/src/Modules/Trading/Components/Elements/purchase-fieldset.tsx +++ b/packages/trader/src/Modules/Trading/Components/Elements/purchase-fieldset.tsx @@ -5,11 +5,11 @@ import Fieldset from 'App/Components/Form/fieldset.jsx'; import ContractInfo from 'Modules/Trading/Components/Form/Purchase/contract-info'; import PurchaseButton from 'Modules/Trading/Components/Elements/purchase-button'; import CancelDealInfo from '../Form/Purchase/cancel-deal-info'; -import { TProposalTypeInfo } from 'Types'; +import { TProposalTypeInfo, TTradeStore } from 'Types'; type TPurchaseFieldset = { basis: string; - buy_info: { error?: string }; + buy_info: TTradeStore['purchase_info']; currency: string; growth_rate: number; has_cancellation: boolean; diff --git a/packages/trader/src/Modules/Trading/Containers/purchase.tsx b/packages/trader/src/Modules/Trading/Containers/purchase.tsx index 91da6c5bed3a..9e94221e4bbf 100644 --- a/packages/trader/src/Modules/Trading/Containers/purchase.tsx +++ b/packages/trader/src/Modules/Trading/Containers/purchase.tsx @@ -5,7 +5,7 @@ import PurchaseButtonsOverlay from 'Modules/Trading/Components/Elements/purchase import PurchaseFieldset from 'Modules/Trading/Components/Elements/purchase-fieldset'; import { useTraderStore } from 'Stores/useTraderStores'; import { observer, useStore } from '@deriv/stores'; -import { TProposalTypeInfo } from 'Types'; +import { TTradeStore } from 'Types'; type TGetSupportedContractsKey = keyof ReturnType; @@ -41,14 +41,14 @@ const Purchase = observer(({ is_market_closed }: { is_market_closed: boolean }) proposal_info, purchase_info, symbol, - validation_errors, + validation_errors = {}, vanilla_trade_type, trade_types, is_trade_enabled, } = useTraderStore(); const is_high_low = /^high_low$/.test(contract_type.toLowerCase()); - const isLoading = (info: TProposalTypeInfo | Record) => { + const isLoading = (info: TTradeStore['proposal_info'][string] | Record) => { const has_validation_error = Object.values(validation_errors).some(e => e.length); return !has_validation_error && !info?.has_error && !info.id; }; diff --git a/packages/trader/src/Stores/Modules/SmartChart/chart-barrier-store.ts b/packages/trader/src/Stores/Modules/SmartChart/chart-barrier-store.ts index 493dfe14a2b4..7f3e2adb9f0f 100644 --- a/packages/trader/src/Stores/Modules/SmartChart/chart-barrier-store.ts +++ b/packages/trader/src/Stores/Modules/SmartChart/chart-barrier-store.ts @@ -29,7 +29,7 @@ export class ChartBarrierStore { onChartBarrierChange: TOnChartBarrierChange | null; constructor( - high_barrier: string | number, + high_barrier?: string | number, low_barrier?: string | number, onChartBarrierChange: TOnChartBarrierChange = null, { color, line_style, not_draggable }: TChartBarrierStoreOptions = {} diff --git a/packages/trader/src/Stores/Modules/Trading/Actions/purchase.ts b/packages/trader/src/Stores/Modules/Trading/Actions/purchase.ts index f2a8062a8ebc..cc2b463fede0 100644 --- a/packages/trader/src/Stores/Modules/Trading/Actions/purchase.ts +++ b/packages/trader/src/Stores/Modules/Trading/Actions/purchase.ts @@ -1,11 +1,20 @@ -import { Buy, BuyContractRequest } from '@deriv/api-types'; +import { Buy, BuyContractResponse, BuyContractRequest } from '@deriv/api-types'; import { WS } from '@deriv/shared'; +type TResponse = BuyContractResponse & { + echo_req: Buy; + error?: { + code: string; + message: string; + details?: BuyContractResponse['buy'] & { field: string }; + }; +}; + export const processPurchase = async ( proposal_id: string, - price: BuyContractRequest['price'], - passthrough: BuyContractRequest['passthrough'] -): Promise => + price: string | number, + passthrough?: BuyContractRequest['passthrough'] +): Promise => WS.buy({ proposal_id, price, diff --git a/packages/trader/src/Stores/Modules/Trading/Actions/start-date.ts b/packages/trader/src/Stores/Modules/Trading/Actions/start-date.ts index 2740abf35023..6bc9b28998e9 100644 --- a/packages/trader/src/Stores/Modules/Trading/Actions/start-date.ts +++ b/packages/trader/src/Stores/Modules/Trading/Actions/start-date.ts @@ -58,7 +58,7 @@ export const onChangeStartDate: TOnChangeStartDate = async store => { export const onChangeExpiry: TOnChangeExpiry = async store => { const { start_time, expiry_date, expiry_type, expiry_time, start_date, symbol, sessions } = store; - + // @ts-expect-error TODO: check if TS error is gone after ContractType is migrated to TS const trading_times = await ContractType.getTradingTimes(expiry_date, symbol); const obj_market_open_times = { market_open_times: trading_times.open }; const obj_market_close_times = { market_close_times: trading_times.close }; diff --git a/packages/trader/src/Stores/Modules/Trading/Constants/validation-rules.ts b/packages/trader/src/Stores/Modules/Trading/Constants/validation-rules.ts index 6911efae718e..150a4385f927 100644 --- a/packages/trader/src/Stores/Modules/Trading/Constants/validation-rules.ts +++ b/packages/trader/src/Stores/Modules/Trading/Constants/validation-rules.ts @@ -6,229 +6,211 @@ import type { TRuleOptions } from 'Utils/Validator/validator'; const tradeSpecificBarrierCheck = (is_vanilla: boolean, input: number) => is_vanilla || input !== 0; -export const getValidationRules = () => - ({ - amount: { - rules: [ - ['req', { message: localize('Amount is a required field.') }], - ['number', { min: 0, type: 'float' }], - ], - }, - barrier_1: { - rules: [ - [ - 'req', - { - condition: (store: TTradeStore) => - store.barrier_count && store.form_components.indexOf('barrier') > -1, - message: localize('Barrier is a required field.'), - }, - ], - ['barrier', { condition: (store: TTradeStore) => store.barrier_count }], - [ - 'custom', - { - func: ( - value: TTradeStore['barrier_1'], - options: Partial, - store: TTradeStore, - inputs: Pick - ) => (store.barrier_count > 1 ? +value > +inputs.barrier_2 : true), - message: localize('Higher barrier must be higher than lower barrier.'), - }, - ], - [ - 'custom', - { - func: ( - value: TTradeStore['barrier_1'], - options: Partial, - store: TTradeStore, - inputs: Pick - ) => - /^[+-]/.test(inputs.barrier_1) - ? tradeSpecificBarrierCheck(store.is_vanilla, +inputs.barrier_1) - : true, - message: localize('Barrier cannot be zero.'), - }, - ], - ], - trigger: 'barrier_2', - }, - barrier_2: { - rules: [ - [ - 'req', - { - condition: (store: TTradeStore) => - store.barrier_count > 1 && store.form_components.indexOf('barrier') > -1, - message: localize('Barrier is a required field.'), - }, - ], - ['barrier', { condition: (store: TTradeStore) => store.barrier_count }], - [ - 'custom', - { - func: ( - value: TTradeStore['barrier_2'], - options: Partial, - store: TTradeStore, - inputs: Pick - ) => - (/^[+-]/g.test(inputs.barrier_1) && /^[+-]/g.test(value)) || - (/^(?![+-])/g.test(inputs.barrier_1) && /^(?![+-])/g.test(value)), - message: localize('Both barriers should be relative or absolute'), - }, - ], - [ - 'custom', - { - func: ( - value: TTradeStore['barrier_2'], - options: Partial, - store: TTradeStore, - inputs: Pick - ) => +inputs.barrier_1 > +value, - message: localize('Lower barrier must be lower than higher barrier.'), - }, - ], - ], - trigger: 'barrier_1', - }, - duration: { - rules: [['req', { message: localize('Duration is a required field.') }]], - }, - start_date: { - trigger: 'start_time', - }, - expiry_date: { - trigger: 'expiry_time', - }, - start_time: { - rules: [ - [ - 'custom', - { - func: (value: TTradeStore['start_time'], options: Partial, store: TTradeStore) => - store.contract_start_type === 'spot' || isTimeValid(value ?? ''), - message: localize('Please enter the start time in the format "HH:MM".'), - }, - ], - [ - 'custom', - { - func: (value: TTradeStore['start_time'], options: Partial, store: TTradeStore) => - store.contract_start_type === 'spot' || isHourValid(value ?? ''), - message: localize('Hour must be between 0 and 23.'), - }, - ], - [ - 'custom', - { - func: (value: TTradeStore['start_time'], options: Partial, store: TTradeStore) => - store.contract_start_type === 'spot' || isMinuteValid(value ?? ''), - message: localize('Minute must be between 0 and 59.'), - }, - ], - [ - 'custom', - { - func: ( - value: TTradeStore['start_time'], - options: Partial, - store: TTradeStore - ) => { - if (store.contract_start_type === 'spot') return true; - if (!isTimeValid(value ?? '')) return false; - const start_moment = toMoment(store.start_date); - const start_moment_clone = start_moment.clone(); - const [h, m] = value?.split(':') ?? []; - return isSessionAvailable( - store.sessions, - start_moment_clone.hour(+h).minute(+m), - start_moment - ); - }, - message: localize('Start time cannot be in the past.'), - }, - ], - ], - }, - expiry_time: { - rules: [ - [ - 'custom', - { - func: (value: TTradeStore['expiry_time'], options: Partial, store: TTradeStore) => - store.contract_start_type === 'spot' || isTimeValid(value ?? ''), - message: localize('Please enter the start time in the format "HH:MM".'), - }, - ], - [ - 'custom', - { - func: (value: TTradeStore['expiry_time'], options: Partial, store: TTradeStore) => - store.contract_start_type === 'spot' || isHourValid(value ?? ''), - message: localize('Hour must be between 0 and 23.'), - }, - ], - [ - 'custom', - { - func: (value: TTradeStore['expiry_time'], options: Partial, store: TTradeStore) => - store.contract_start_type === 'spot' || isMinuteValid(value ?? ''), - message: localize('Minute must be between 0 and 59.'), - }, - ], - [ - 'custom', - { - func: ( - value: TTradeStore['expiry_time'], - options: Partial, - store: TTradeStore - ) => { - if (store.contract_start_type === 'spot') return true; - if (!isTimeValid(value ?? '')) return false; - const start_moment = toMoment(store.start_date); - const start_moment_clone = start_moment.clone(); - const [h, m] = value?.split(':') ?? []; - return isSessionAvailable( - store.sessions, - start_moment_clone.hour(+h).minute(+m), - start_moment - ); - }, - message: localize('Expiry time cannot be in the past.'), - }, - ], +export const getValidationRules = () => ({ + amount: { + rules: [ + ['req', { message: localize('Amount is a required field.') }], + ['number', { min: 0, type: 'float' }], + ], + }, + barrier_1: { + rules: [ + [ + 'req', + { + condition: (store: TTradeStore) => + store.barrier_count && store.form_components.indexOf('barrier') > -1, + message: localize('Barrier is a required field.'), + }, + ], + ['barrier', { condition: (store: TTradeStore) => store.barrier_count }], + [ + 'custom', + { + func: ( + value: TTradeStore['barrier_1'], + options: Partial, + store: TTradeStore, + inputs: Pick + ) => (store.barrier_count > 1 ? +value > +inputs.barrier_2 : true), + message: localize('Higher barrier must be higher than lower barrier.'), + }, ], - }, - ...getMultiplierValidationRules(), - } as const); + [ + 'custom', + { + func: ( + value: TTradeStore['barrier_1'], + options: Partial, + store: TTradeStore, + inputs: Pick + ) => + /^[+-]/.test(inputs.barrier_1) + ? tradeSpecificBarrierCheck(store.is_vanilla, +inputs.barrier_1) + : true, + message: localize('Barrier cannot be zero.'), + }, + ], + ], + trigger: 'barrier_2', + }, + barrier_2: { + rules: [ + [ + 'req', + { + condition: (store: TTradeStore) => + store.barrier_count > 1 && store.form_components.indexOf('barrier') > -1, + message: localize('Barrier is a required field.'), + }, + ], + ['barrier', { condition: (store: TTradeStore) => store.barrier_count }], + [ + 'custom', + { + func: ( + value: TTradeStore['barrier_2'], + options: Partial, + store: TTradeStore, + inputs: Pick + ) => + (/^[+-]/g.test(inputs.barrier_1) && /^[+-]/g.test(value)) || + (/^(?![+-])/g.test(inputs.barrier_1) && /^(?![+-])/g.test(value)), + message: localize('Both barriers should be relative or absolute'), + }, + ], + [ + 'custom', + { + func: ( + value: TTradeStore['barrier_2'], + options: Partial, + store: TTradeStore, + inputs: Pick + ) => +inputs.barrier_1 > +value, + message: localize('Lower barrier must be lower than higher barrier.'), + }, + ], + ], + trigger: 'barrier_1', + }, + duration: { + rules: [['req', { message: localize('Duration is a required field.') }]], + }, + start_date: { + trigger: 'start_time', + }, + expiry_date: { + trigger: 'expiry_time', + }, + start_time: { + rules: [ + [ + 'custom', + { + func: (value: TTradeStore['start_time'], options: Partial, store: TTradeStore) => + store.contract_start_type === 'spot' || isTimeValid(value ?? ''), + message: localize('Please enter the start time in the format "HH:MM".'), + }, + ], + [ + 'custom', + { + func: (value: TTradeStore['start_time'], options: Partial, store: TTradeStore) => + store.contract_start_type === 'spot' || isHourValid(value ?? ''), + message: localize('Hour must be between 0 and 23.'), + }, + ], + [ + 'custom', + { + func: (value: TTradeStore['start_time'], options: Partial, store: TTradeStore) => + store.contract_start_type === 'spot' || isMinuteValid(value ?? ''), + message: localize('Minute must be between 0 and 59.'), + }, + ], + [ + 'custom', + { + func: (value: TTradeStore['start_time'], options: Partial, store: TTradeStore) => { + if (store.contract_start_type === 'spot') return true; + if (!isTimeValid(value ?? '')) return false; + const start_moment = toMoment(store.start_date); + const start_moment_clone = start_moment.clone(); + const [h, m] = value?.split(':') ?? []; + return isSessionAvailable(store.sessions, start_moment_clone.hour(+h).minute(+m), start_moment); + }, + message: localize('Start time cannot be in the past.'), + }, + ], + ], + }, + expiry_time: { + rules: [ + [ + 'custom', + { + func: (value: TTradeStore['expiry_time'], options: Partial, store: TTradeStore) => + store.contract_start_type === 'spot' || isTimeValid(value ?? ''), + message: localize('Please enter the start time in the format "HH:MM".'), + }, + ], + [ + 'custom', + { + func: (value: TTradeStore['expiry_time'], options: Partial, store: TTradeStore) => + store.contract_start_type === 'spot' || isHourValid(value ?? ''), + message: localize('Hour must be between 0 and 23.'), + }, + ], + [ + 'custom', + { + func: (value: TTradeStore['expiry_time'], options: Partial, store: TTradeStore) => + store.contract_start_type === 'spot' || isMinuteValid(value ?? ''), + message: localize('Minute must be between 0 and 59.'), + }, + ], + [ + 'custom', + { + func: (value: TTradeStore['expiry_time'], options: Partial, store: TTradeStore) => { + if (store.contract_start_type === 'spot') return true; + if (!isTimeValid(value ?? '')) return false; + const start_moment = toMoment(store.start_date); + const start_moment_clone = start_moment.clone(); + const [h, m] = value?.split(':') ?? []; + return isSessionAvailable(store.sessions, start_moment_clone.hour(+h).minute(+m), start_moment); + }, + message: localize('Expiry time cannot be in the past.'), + }, + ], + ], + }, + ...getMultiplierValidationRules(), +}); -export const getMultiplierValidationRules = () => - ({ - stop_loss: { - rules: [ - [ - 'req', - { - condition: (store: TTradeStore) => store.has_stop_loss && !store.stop_loss, - message: localize('Please enter a stop loss amount.'), - }, - ], - ], - }, - take_profit: { - rules: [ - [ - 'req', - { - condition: (store: TTradeStore) => store.has_take_profit && !store.take_profit, - message: localize('Please enter a take profit amount.'), - }, - ], +export const getMultiplierValidationRules = () => ({ + stop_loss: { + rules: [ + [ + 'req', + { + condition: (store: TTradeStore) => store.has_stop_loss && !store.stop_loss, + message: localize('Please enter a stop loss amount.'), + }, + ], + ], + }, + take_profit: { + rules: [ + [ + 'req', + { + condition: (store: TTradeStore) => store.has_take_profit && !store.take_profit, + message: localize('Please enter a take profit amount.'), + }, ], - }, - } as const); + ], + }, +}); diff --git a/packages/trader/src/Stores/Modules/Trading/Helpers/allow-equals.ts b/packages/trader/src/Stores/Modules/Trading/Helpers/allow-equals.ts index 4b0a7c7f9fae..8d2eb0bdbdbf 100644 --- a/packages/trader/src/Stores/Modules/Trading/Helpers/allow-equals.ts +++ b/packages/trader/src/Stores/Modules/Trading/Helpers/allow-equals.ts @@ -1,11 +1,10 @@ import { isEmptyObject, getPropertyValue } from '@deriv/shared'; import { ContractType } from 'Stores/Modules/Trading/Helpers/contract-type'; -import { PriceProposalRequest } from '@deriv/api-types'; import { TTradeStore } from 'Types'; type THasDurationForCallPutEqual = { contract_type_list: TTradeStore['contract_types_list']; - duration_unit: PriceProposalRequest['duration_unit']; + duration_unit: TTradeStore['duration_unit']; contract_start_type: string; }; @@ -13,7 +12,7 @@ export const hasCallPutEqual = (contract_type_list: THasDurationForCallPutEqual[ if (isEmptyObject(contract_type_list)) return false; return !!getPropertyValue(contract_type_list, 'Ups & Downs')?.categories?.some( - (contract: THasDurationForCallPutEqual['contract_type_list']['Ups & Downs'][string]['categories'][0]) => + (contract: THasDurationForCallPutEqual['contract_type_list']['Ups & Downs']['categories'][0]) => contract.value === 'rise_fall_equal' ); }; @@ -26,8 +25,7 @@ export const hasDurationForCallPutEqual = ( if (!contract_type_list || !duration_unit || !contract_start_type) return false; const contract_list = Object.keys(contract_type_list || {}).reduce((key, list) => { - // @ts-expect-error the key always exists in the object, hence can ignore the TS error. - const item: THasDurationForCallPutEqual['contract_type_list']['Ups & Downs'][string] = contract_type_list[list]; + const item: THasDurationForCallPutEqual['contract_type_list']['Ups & Downs'] = contract_type_list[list]; return [...key, ...item.categories.map(contract => contract.value)]; }, []); diff --git a/packages/trader/src/Stores/Modules/Trading/Helpers/limit-orders.ts b/packages/trader/src/Stores/Modules/Trading/Helpers/limit-orders.ts index 2020d20c37bb..a7a0183a7638 100644 --- a/packages/trader/src/Stores/Modules/Trading/Helpers/limit-orders.ts +++ b/packages/trader/src/Stores/Modules/Trading/Helpers/limit-orders.ts @@ -2,11 +2,10 @@ import { isMultiplierContract, BARRIER_COLORS, BARRIER_LINE_STYLES } from '@deri import { ChartBarrierStore } from '../../SmartChart/chart-barrier-store'; import { removeBarrier } from '../../SmartChart/Helpers/barriers'; import { useStore } from '@deriv/stores'; +import { getProposalInfo } from './proposal'; -const isLimitOrderBarrierSupported = ( - contract_type: string, - contract_info: ReturnType['portfolio']['all_positions'][0]['contract_info'] -) => isMultiplierContract(contract_type) && contract_info.limit_order; +const isLimitOrderBarrierSupported = (contract_type: string, contract_info: ReturnType) => + isMultiplierContract(contract_type) && contract_info.limit_order; export const LIMIT_ORDER_TYPES = { STOP_OUT: 'stop_out', @@ -18,14 +17,14 @@ type TBarrier = ChartBarrierStore & { key?: string }; type TSetLimitOrderBarriers = { barriers: TBarrier[]; - contract_type: string; - contract_info: Parameters[1]; + contract_type?: string; + contract_info?: ReturnType; is_over: boolean; }; export const setLimitOrderBarriers = ({ barriers, - contract_type, - contract_info = {}, + contract_type = '', + contract_info = {} as ReturnType, is_over, }: TSetLimitOrderBarriers) => { if (is_over && isLimitOrderBarrierSupported(contract_type, contract_info)) { @@ -53,7 +52,6 @@ export const setLimitOrderBarriers = ({ barrier.onChange({ high: obj_limit_order.value, - low: undefined, //TODO: wait until ChartBarrierStore is ts migrated and 'low' can be an optional parameter }); } else { const obj_barrier = { @@ -87,7 +85,7 @@ export const setLimitOrderBarriers = ({ */ export const getLimitOrder = ( contract_update: Pick< - ReturnType['contract_trade'], + ReturnType['contract_trade']['contracts'][number], | 'has_contract_update_stop_loss' | 'has_contract_update_take_profit' | 'contract_update_stop_loss' diff --git a/packages/trader/src/Stores/Modules/Trading/Helpers/logic.ts b/packages/trader/src/Stores/Modules/Trading/Helpers/logic.ts index b586a0425592..e1cd547ee251 100644 --- a/packages/trader/src/Stores/Modules/Trading/Helpers/logic.ts +++ b/packages/trader/src/Stores/Modules/Trading/Helpers/logic.ts @@ -1,5 +1,5 @@ -import { TTradeStore } from 'Types'; +import { TContractInfo } from '@deriv/shared'; import ServerTime from '_common/base/server_time'; -export const isCancellationExpired = (contract_info: TTradeStore['proposal_info'][string]) => +export const isCancellationExpired = (contract_info: TContractInfo) => !!contract_info.cancellation?.date_expiry && contract_info.cancellation.date_expiry < ServerTime.get().unix(); diff --git a/packages/trader/src/Stores/Modules/Trading/Helpers/proposal.ts b/packages/trader/src/Stores/Modules/Trading/Helpers/proposal.ts index 0ae9ff9fde8c..7231a2f97d9f 100644 --- a/packages/trader/src/Stores/Modules/Trading/Helpers/proposal.ts +++ b/packages/trader/src/Stores/Modules/Trading/Helpers/proposal.ts @@ -71,7 +71,7 @@ export const getProposalInfo = ( const is_stake = contract_basis?.value === 'stake'; - const price = is_stake ? stake : proposal[contract_basis?.value as keyof Proposal]; + const price = is_stake ? stake : (proposal[contract_basis?.value as keyof Proposal] as string | number); let has_increased = false; if (price !== undefined && price !== null) { diff --git a/packages/trader/src/Stores/Modules/Trading/trade-store.ts b/packages/trader/src/Stores/Modules/Trading/trade-store.ts index 6976466b403c..140bb95aa559 100644 --- a/packages/trader/src/Stores/Modules/Trading/trade-store.ts +++ b/packages/trader/src/Stores/Modules/Trading/trade-store.ts @@ -114,7 +114,7 @@ type TChartLayout = { volumeUnderlay: boolean; }; type TChartStateChangeOption = { symbol: string | undefined; isClosed: boolean }; -type TContractDataForGTM = PriceProposalRequest & +type TContractDataForGTM = Omit, 'cancellation' | 'limit_order'> & ReturnType & { buy_price: number; }; @@ -301,7 +301,7 @@ export default class TradeStore extends BaseStore { contract_purchase_toast_box?: TToastBoxObject; debouncedProposal = debounce(this.requestProposal, 500); - proposal_requests: Partial> = {}; + proposal_requests: Record> = {}; is_purchasing_contract = false; initial_barriers?: { barrier_1: string; barrier_2: string }; @@ -818,9 +818,9 @@ export default class TradeStore extends BaseStore { onHoverPurchase(is_over: boolean, contract_type?: string) { if (this.is_accumulator) return; if (this.is_purchase_enabled && this.main_barrier && !this.is_multiplier) { - this.main_barrier.updateBarrierShade(is_over, contract_type); + this.main_barrier.updateBarrierShade(is_over, contract_type ?? ''); } else if (!is_over && this.main_barrier && !this.is_multiplier) { - this.main_barrier.updateBarrierShade(false, contract_type); + this.main_barrier.updateBarrierShade(false, contract_type ?? ''); } this.hovered_contract_type = is_over ? contract_type : null; @@ -835,7 +835,6 @@ export default class TradeStore extends BaseStore { clearLimitOrderBarriers() { this.hovered_contract_type = null; const { barriers } = this; - //@ts-expect-error: TODO: check if type error is gone after limit-orders.js is migrated to ts setLimitOrderBarriers({ barriers, is_over: false, @@ -867,7 +866,6 @@ export default class TradeStore extends BaseStore { this.main_barrier = new ChartBarrierStore( this.hovered_barrier || barrier, barrier2, - //@ts-expect-error need to typescript migrate chart-barrier-store.js first this.onChartBarrierChange, { color: this.hovered_barrier ? getHoveredColor(contract_type) : color, @@ -883,7 +881,7 @@ export default class TradeStore extends BaseStore { onPurchase = debounce(this.processPurchase, 300); - processPurchase(proposal_id: string, price: string, type: string) { + processPurchase(proposal_id: string, price: string | number, type: string) { if (!this.is_purchase_enabled) return; if (proposal_id) { this.is_purchase_enabled = false; @@ -1093,7 +1091,7 @@ export default class TradeStore extends BaseStore { if (Object.keys(obj_new_values).includes('symbol')) { this.setPreviousSymbol(this.symbol); - await Symbol.onChangeSymbolAsync(obj_new_values.symbol); + await Symbol.onChangeSymbolAsync(obj_new_values.symbol ?? ''); this.setMarketStatus(isMarketClosed(this.active_symbols, obj_new_values.symbol ?? '')); has_only_forward_starting_contracts = ContractType.getContractCategories().has_only_forward_starting_contracts; @@ -1127,6 +1125,7 @@ export default class TradeStore extends BaseStore { // TODO: handle barrier updates on proposal api // const is_barrier_changed = 'barrier_1' in new_state || 'barrier_2' in new_state; + // @ts-expect-error TODO: check if TS error is gone after TOverrideTradeStore is removed await processTradeParams(this, new_state); this.updateStore({ @@ -1198,6 +1197,7 @@ export default class TradeStore extends BaseStore { } requestProposal() { + // @ts-expect-error TODO: check that TS error is gone after TOverrideTradeStore is removed const requests = createProposalRequests(this); if (Object.values(this.validation_errors).some(e => e.length)) { this.proposal_info = {}; @@ -1208,7 +1208,7 @@ export default class TradeStore extends BaseStore { } if (!isEmptyObject(requests)) { - this.proposal_requests = requests; + this.proposal_requests = requests as Record>; this.purchase_info = {}; Object.keys(this.proposal_requests).forEach(type => { WS.subscribeProposal(this.proposal_requests[type], this.onProposalResponse); @@ -1241,6 +1241,7 @@ export default class TradeStore extends BaseStore { this.proposal_info = { ...this.proposal_info, + // @ts-expect-error TODO: check that TS error is gone after TOverrideTradeStore is removed [contract_type]: getProposalInfo(this, response, obj_prev_contract_basis), }; @@ -1258,11 +1259,11 @@ export default class TradeStore extends BaseStore { if (this.is_accumulator && this.proposal_info?.ACCU) { const { barrier_spot_distance, - maximum_ticks, + maximum_ticks = 0, ticks_stayed_in, - tick_size_barrier, + tick_size_barrier = 0, last_tick_epoch, - maximum_payout, + maximum_payout = 0, high_barrier, low_barrier, spot_time, @@ -1363,7 +1364,7 @@ export default class TradeStore extends BaseStore { } } - onChartBarrierChange(barrier_1: string, barrier_2: string) { + onChartBarrierChange(barrier_1: string, barrier_2?: string) { this.processNewValuesAsync({ barrier_1, barrier_2 }, true); } diff --git a/packages/trader/src/Stores/useTraderStores.tsx b/packages/trader/src/Stores/useTraderStores.tsx index 25ea546c7b08..2e3a930191ef 100644 --- a/packages/trader/src/Stores/useTraderStores.tsx +++ b/packages/trader/src/Stores/useTraderStores.tsx @@ -1,11 +1,11 @@ import React from 'react'; import { useStore } from '@deriv/stores'; -import TradeStore, { TValidationErrors, TValidationRules } from './Modules/Trading/trade-store'; +import TradeStore, { TValidationErrors } from './Modules/Trading/trade-store'; type TOverrideTradeStore = Omit & { //TODO: these types can be removed from here and trade-store after base-store is migrated to TS validation_errors?: TValidationErrors; - validation_rules: TValidationRules; + validation_rules: TradeStore['validation_rules']; }; const TraderStoreContext = React.createContext(null); diff --git a/packages/trader/src/Types/common-prop.type.ts b/packages/trader/src/Types/common-prop.type.ts index e7fa4323b4bb..f08fae2d9a4c 100644 --- a/packages/trader/src/Types/common-prop.type.ts +++ b/packages/trader/src/Types/common-prop.type.ts @@ -5,20 +5,7 @@ export type TTextValueStrings = { value: string; }; -export type TProposalTypeInfo = { - has_error?: boolean; - id: string; - has_increased?: boolean; - message?: string; - cancellation?: { - ask_price: number; - date_expiry: number; - }; - growth_rate?: number; - obj_contract_basis?: Record<'text' | 'value', string>; - returns?: string; - stake: string; -}; +export type TProposalTypeInfo = TTradeStore['proposal_info'][string]; export type TError = { error?: { From 16a0bcddb4228ca00d50ff30ce7d35a7d88ef61c Mon Sep 17 00:00:00 2001 From: kate-deriv Date: Tue, 19 Sep 2023 11:58:43 +0300 Subject: [PATCH 47/55] refactor: tests --- .../hooks/src/__tests__/useCFDAllAccounts.spec.tsx | 10 +++++----- .../recommend-user/__test__/recommend-user.spec.tsx | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/hooks/src/__tests__/useCFDAllAccounts.spec.tsx b/packages/hooks/src/__tests__/useCFDAllAccounts.spec.tsx index 06479e81c4d1..1006de314ade 100644 --- a/packages/hooks/src/__tests__/useCFDAllAccounts.spec.tsx +++ b/packages/hooks/src/__tests__/useCFDAllAccounts.spec.tsx @@ -12,7 +12,7 @@ describe('useCFDAllAccounts', () => { ); const { result } = renderHook(() => useCFDAllAccounts(), { wrapper }); - expect(result.current).toHaveLength(0); + expect(result.current.length).toBe(0); }); test('should return proper data when client has MT5 accounts', async () => { @@ -33,7 +33,7 @@ describe('useCFDAllAccounts', () => { ); const { result } = renderHook(() => useCFDAllAccounts(), { wrapper }); - expect(result.current).toHaveLength(1); + expect(result.current.length).toBe(1); }); test('should return proper data when client has dxtrade accounts', async () => { @@ -54,7 +54,7 @@ describe('useCFDAllAccounts', () => { ); const { result } = renderHook(() => useCFDAllAccounts(), { wrapper }); - expect(result.current).toHaveLength(1); + expect(result.current.length).toBe(1); }); test('should return proper data when client has ctrader accounts', async () => { @@ -75,7 +75,7 @@ describe('useCFDAllAccounts', () => { ); const { result } = renderHook(() => useCFDAllAccounts(), { wrapper }); - expect(result.current).toHaveLength(1); + expect(result.current.length).toBe(1); }); test('should return proper data when client has MT5, ctrader and dxtrade accounts', async () => { @@ -110,6 +110,6 @@ describe('useCFDAllAccounts', () => { ); const { result } = renderHook(() => useCFDAllAccounts(), { wrapper }); - expect(result.current).toHaveLength(3); + expect(result.current.length).toBe(3); }); }); diff --git a/packages/p2p/src/components/recommend-user/__test__/recommend-user.spec.tsx b/packages/p2p/src/components/recommend-user/__test__/recommend-user.spec.tsx index a36613049591..0516a166167d 100644 --- a/packages/p2p/src/components/recommend-user/__test__/recommend-user.spec.tsx +++ b/packages/p2p/src/components/recommend-user/__test__/recommend-user.spec.tsx @@ -16,14 +16,14 @@ describe('', () => { render(); expect(screen.getByText('Would you recommend this buyer?')).toBeInTheDocument(); - expect(screen.getAllByRole('button')).toHaveLength(2); + expect(screen.getAllByRole('button').length).toBe(2); }); it('should render the component with correct message if it is a buy order for the user and both buttons', () => { render(); expect(screen.getByText('Would you recommend this seller?')).toBeInTheDocument(); - expect(screen.getAllByRole('button')).toHaveLength(2); + expect(screen.getAllByRole('button').length).toBe(2); }); it('should auto select the Yes button if the user was previously recommended', () => { From 1b7580090c3adc16e4016e569cf50dca10f30298 Mon Sep 17 00:00:00 2001 From: kate-deriv Date: Wed, 20 Sep 2023 10:09:34 +0300 Subject: [PATCH 48/55] fix: more conflicts --- packages/stores/src/mockStore.ts | 1 - packages/stores/types.ts | 1 - packages/trader/src/Stores/Modules/Trading/trade-store.ts | 4 +++- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/stores/src/mockStore.ts b/packages/stores/src/mockStore.ts index 53d19865738c..c7aab9712b09 100644 --- a/packages/stores/src/mockStore.ts +++ b/packages/stores/src/mockStore.ts @@ -138,7 +138,6 @@ const mock = (): TStores & { is_mock: boolean } => { is_pending_proof_of_ownership: false, is_single_currency: false, is_switching: false, - is_single_currency: false, is_tnc_needed: false, is_trading_experience_incomplete: false, is_virtual: false, diff --git a/packages/stores/types.ts b/packages/stores/types.ts index 7b612f5baa99..f03339ea6569 100644 --- a/packages/stores/types.ts +++ b/packages/stores/types.ts @@ -287,7 +287,6 @@ type TClientStore = { is_low_risk: boolean; is_pending_proof_of_ownership: boolean; is_switching: boolean; - is_single_currency: boolean; is_tnc_needed: boolean; is_trading_experience_incomplete: boolean; is_virtual: boolean; diff --git a/packages/trader/src/Stores/Modules/Trading/trade-store.ts b/packages/trader/src/Stores/Modules/Trading/trade-store.ts index 01a30fded781..5b6b16a4eaf8 100644 --- a/packages/trader/src/Stores/Modules/Trading/trade-store.ts +++ b/packages/trader/src/Stores/Modules/Trading/trade-store.ts @@ -27,6 +27,7 @@ import { unsupported_contract_types_list, BARRIER_COLORS, BARRIER_LINE_STYLES, + TContractInfo, } from '@deriv/shared'; import { localize } from '@deriv/translations'; import { getValidationRules, getMultiplierValidationRules } from 'Stores/Modules/Trading/Constants/validation-rules'; @@ -571,7 +572,8 @@ export default class TradeStore extends BaseStore { this.is_accumulator && !!this.root_store.portfolio.open_accu_contract && !!this.root_store.portfolio.active_positions.find( - ({ contract_info, type }) => isAccumulatorContract(type) && contract_info.underlying === this.symbol + ({ contract_info, type }: { contract_info: TContractInfo; type: string }) => + isAccumulatorContract(type) && contract_info.underlying === this.symbol ) ); } From bcbf91f99fcf5aac40b0451c2c79dc99503c5328 Mon Sep 17 00:00:00 2001 From: kate-deriv Date: Wed, 20 Sep 2023 16:21:27 +0300 Subject: [PATCH 49/55] chore: empty commit From e14156c95bf400999b82e74bad507873c6b9006e Mon Sep 17 00:00:00 2001 From: Maryia <103177211+maryia-deriv@users.noreply.github.com> Date: Thu, 28 Sep 2023 09:11:27 +0300 Subject: [PATCH 50/55] maryia/fix: type issues on package 4 (#41) * fix: type issues * chore: file change from package 3 * chore: keep small changes from package 3 * chore: removed unnecessary todo comment --- package-lock.json | 172 +++++++++++++++++- .../__tests__/error-component.spec.tsx | 1 - .../error-component/error-component.tsx | 8 +- .../shared/src/utils/shortcode/shortcode.ts | 24 ++- .../declarative-validation-rules.ts | 4 +- packages/stores/src/mockStore.ts | 26 ++- packages/stores/types.ts | 84 +++++++-- packages/trader/package.json | 1 + .../TradeParams/Duration/duration-wrapper.jsx | 16 -- .../Components/Form/TradeParams/amount.tsx | 4 +- .../Containers/trade-params-mobile.tsx | 2 +- .../Modules/Trading/Actions/start-date.ts | 1 - .../Trading/Constants/validation-rules.ts | 105 +++++------ .../Modules/Trading/Helpers/contract-type.ts | 13 +- .../Stores/Modules/Trading/Helpers/process.ts | 2 +- .../src/Stores/Modules/Trading/trade-store.ts | 50 ++--- packages/trader/src/Stores/base-store.ts | 44 ++--- .../trader/src/Stores/useTraderStores.tsx | 10 +- packages/trader/src/Types/common-prop.type.ts | 4 + .../trader/src/Utils/Validator/validator.ts | 32 ++-- 20 files changed, 401 insertions(+), 202 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2e4e2659a25c..4f870b953253 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,16 +6,181 @@ "": { "name": "root", "dependencies": { + "@babel/plugin-proposal-private-property-in-object": "^7.12.1", + "@babel/polyfill": "^7.4.4", "@babel/preset-typescript": "^7.16.5", + "@binary-com/binary-document-uploader": "^2.4.8", + "@contentpass/zxcvbn": "^4.4.3", + "@datadog/browser-logs": "^4.36.0", + "@datadog/browser-rum": "^4.37.0", + "@deriv/api-types": "^1.0.118", + "@deriv/deriv-api": "^1.0.11", + "@deriv/deriv-charts": "1.3.6", + "@deriv/js-interpreter": "^3.0.0", + "@deriv/ui": "^0.6.0", + "@livechat/customer-sdk": "^2.0.4", "@sendbird/chat": "^4.9.7", + "@storybook/addon-actions": "^6.5.10", + "@storybook/addon-docs": "^6.5.10", + "@storybook/addon-essentials": "^6.5.10", + "@storybook/addon-info": "^5.3.21", + "@storybook/addon-interactions": "^6.5.10", + "@storybook/addon-knobs": "^6.4.0", + "@storybook/addon-links": "^6.5.10", + "@storybook/builder-webpack5": "^6.5.10", + "@storybook/manager-webpack5": "^6.5.10", + "@storybook/react": "^6.5.10", + "@storybook/testing-library": "^0.0.13", + "@tanstack/react-query": "^4.28.0", + "@tanstack/react-query-devtools": "^4.28.0", + "@types/classnames": "^2.2.11", + "@types/js-cookie": "^3.0.1", + "@types/jsdom": "^20.0.0", + "@types/loadjs": "^4.0.1", + "@types/lodash.debounce": "^4.0.7", + "@types/lodash.merge": "^4.6.7", + "@types/lodash.throttle": "^4.1.7", + "@types/object.fromentries": "^2.0.0", + "@types/qrcode.react": "^1.0.2", + "@types/react-loadable": "^5.5.6", "@types/react-transition-group": "^4.4.4", + "@types/ws": "^8.5.5", + "@typescript-eslint/eslint-plugin": "5.45.0", + "@typescript-eslint/parser": "5.45.0", + "@xmldom/xmldom": "^0.8.4", + "acorn": "^6.1.1", + "babel-core": "^6.26.3", + "babel-eslint": "^10.1.0", "babel-jest": "^27.3.1", + "babel-loader": "^8.1.0", + "binary-utils": "^4.23.0", + "blockly": "3.20191014.4", + "bowser": "^2.9.0", + "canvas-toBlob": "^1.0.0", + "circular-dependency-plugin": "^5.2.2", + "classnames": "^2.2.6", + "clean-webpack-plugin": "^3.0.0", + "commander": "^3.0.2", + "concurrently": "^5.3.0", + "copy-webpack-plugin": "^9.0.1", + "copy-webpack-plugin-v6": "npm:copy-webpack-plugin@6", + "crc-32": "^1.2.0", + "cross-env": "^5.2.0", + "css-hot-loader": "^1.4.4", + "css-loader": "^5.0.1", + "css-minimizer-webpack-plugin": "^3.0.1", + "deep-diff": "^1.0.2", "dotenv": "^8.2.0", + "embla-carousel-react": "^8.0.0-rc12", + "eslint-config-airbnb-base": "^14.2.1", + "eslint-config-binary": "^1.0.2", + "eslint-config-prettier": "^7.2.0", + "eslint-formatter-pretty": "^4.0.0", + "eslint-import-resolver-webpack": "^0.13.0", + "eslint-plugin-import": "^2.23.4", + "eslint-plugin-local-rules": "2.0.0", + "eslint-plugin-prettier": "^3.3.1", + "eslint-plugin-react": "^7.22.0", + "eslint-plugin-react-hooks": "^4.2.0", + "eslint-plugin-simple-import-sort": "^10.0.0", + "eslint-plugin-sort-destructure-keys": "^1.5.0", + "eslint-plugin-typescript-sort-keys": "^2.3.0", + "extend": "^3.0.2", + "file-loader": "^6.2.0", + "file-saver": "^2.0.2", + "fork-ts-checker-webpack-plugin": "^6.5.0", + "formik": "^2.1.4", + "framer-motion": "^6.5.1", + "gh-pages": "^2.1.1", + "git-revision-webpack-plugin": "^5.0.0", + "glob": "^7.1.5", + "history": "^5.0.0", + "html-loader": "^1.3.2", + "html-webpack-plugin": "^5.0.0-beta.5", + "html-webpack-tags-plugin": "^2.0.17", + "i18n-iso-countries": "^6.8.0", + "i18next": "^22.4.6", + "ignore-styles": "^5.0.1", + "immutable": "^3.8.2", + "js-cookie": "^2.2.1", + "jsdom": "^21.1.1", + "jsdom-global": "^2.1.1", + "loader-utils": "^1.1.0", + "loadjs": "^4.2.0", + "localforage": "^1.9.0", + "lodash.debounce": "^4.0.8", + "lodash.merge": "^4.6.2", + "lodash.throttle": "^4.1.1", + "lz-string": "^1.4.4", + "mini-css-extract-plugin": "^1.3.4", + "mobx": "^6.6.1", + "mobx-persist-store": "1.1.2", + "mobx-react": "^7.5.1", + "mobx-react-lite": "^3.4.0", + "mobx-utils": "^6.0.5", + "mock-local-storage": "^1.1.8", + "moment": "^2.29.2", + "null-loader": "^4.0.1", + "object.fromentries": "^2.0.0", + "onfido-sdk-ui": "^11.0.0", + "pako": "^1.0.11", + "postcss-loader": "^6.2.1", + "postcss-preset-env": "^7.4.3", + "postcss-scss": "^4.0.6", + "preload-webpack-plugin": "^3.0.0-beta.4", + "promise-polyfill": "^8.1.3", + "prop-types": "^15.7.2", + "qrcode.react": "^1.0.0", + "raw-loader": "^4.0.0", "react": "^17.0.2", + "react-content-loader": "^6.2.0", + "react-div-100vh": "^0.3.8", "react-dom": "^17.0.2", + "react-dropzone": "11.0.1", + "react-i18next": "^11.11.0", + "react-joyride": "^2.5.3", + "react-loadable": "^5.5.0", + "react-qrcode": "^0.3.5", + "react-rnd": "^10.4.1", + "react-router": "^5.2.0", + "react-router-dom": "^5.2.0", + "react-simple-star-rating": "4.0.4", + "react-svg-loader": "^3.0.3", + "react-swipeable": "^6.2.1", + "react-tiny-popover": "^7.0.1", + "react-transition-group": "4.4.2", + "react-virtualized": "npm:@enykeev/react-virtualized@^9.22.4-mirror.1", + "react-window": "^1.8.5", + "redux": "^4.0.1", + "redux-thunk": "^2.2.0", + "resolve-url-loader": "^3.1.2", + "rimraf": "^3.0.2", + "rudder-sdk-js": "^2.35.0", + "sass": "^1.62.1", + "sass-loader": "^12.6.0", + "sass-resources-loader": "^2.1.1", + "scratch-blocks": "0.1.0-prerelease.20200917235131", "selfsigned": "^2.1.1", + "shx": "^0.3.2", + "source-map-loader": "^1.1.2", + "style-loader": "^1.2.1", + "svg-sprite-loader": "^6.0.11", + "svgo": "^2.8.0", + "svgo-loader": "^3.0.0", + "terser-webpack-plugin": "^5.1.1", "typescript": "^4.6.3", - "ws": "^8.13.0" + "usehooks-ts": "^2.7.0", + "web-push-notifications": "^3.33.0", + "webpack": "^5.81.0", + "webpack-bundle-analyzer": "^4.3.0", + "webpack-cli": "^4.7.2", + "webpack-dev-middleware": "^5.0.0", + "webpack-dev-server": "^3.11.2", + "webpack-manifest-plugin": "^4.0.2", + "webpack-node-externals": "^2.5.2", + "workbox-webpack-plugin": "^6.0.2", + "ws": "^8.13.0", + "yup": "^0.32.11" }, "devDependencies": { "@babel/core": "^7.12.10", @@ -25036,6 +25201,11 @@ "eslint-plugin-playwright": "^0.9.0" } }, + "node_modules/eslint-plugin-local-rules": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-local-rules/-/eslint-plugin-local-rules-2.0.0.tgz", + "integrity": "sha512-sWueme0kUcP0JC1+6OBDQ9edBDVFJR92WJHSRbhiRExlenMEuUisdaVBPR+ItFBFXo2Pdw6FD2UfGZWkz8e93g==" + }, "node_modules/eslint-plugin-playwright": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/eslint-plugin-playwright/-/eslint-plugin-playwright-0.9.0.tgz", diff --git a/packages/cashier/src/containers/routes/error-component/__tests__/error-component.spec.tsx b/packages/cashier/src/containers/routes/error-component/__tests__/error-component.spec.tsx index 1e26400526ee..8805f7e2a32e 100644 --- a/packages/cashier/src/containers/routes/error-component/__tests__/error-component.spec.tsx +++ b/packages/cashier/src/containers/routes/error-component/__tests__/error-component.spec.tsx @@ -27,7 +27,6 @@ describe('', () => { redirect_label: 'test_label', should_clear_error_on_click: true, should_show_refresh: true, - app_routing_history: [{ pathname: '/cashier' }], redirectOnClick: jest.fn(), setError: jest.fn(), }; diff --git a/packages/cashier/src/containers/routes/error-component/error-component.tsx b/packages/cashier/src/containers/routes/error-component/error-component.tsx index c826db55784d..0b6355258712 100644 --- a/packages/cashier/src/containers/routes/error-component/error-component.tsx +++ b/packages/cashier/src/containers/routes/error-component/error-component.tsx @@ -2,8 +2,12 @@ import React from 'react'; import { useHistory } from 'react-router-dom'; import { PageErrorContainer } from '@deriv/components'; import { routes } from '@deriv/shared'; -import { TRootStore } from '../../../types'; import { localize, Localize } from '@deriv/translations'; +import { TCoreStores } from '@deriv/stores/types'; + +type TErrorComponentProps = TCoreStores['common']['error'] & { + setError?: (has_error: boolean, error: React.ReactNode | null) => void; +}; const ErrorComponent = ({ header, @@ -14,7 +18,7 @@ const ErrorComponent = ({ setError, should_clear_error_on_click, should_show_refresh = true, -}: TRootStore['common']['error']) => { +}: TErrorComponentProps) => { const history = useHistory(); React.useEffect(() => { diff --git a/packages/shared/src/utils/shortcode/shortcode.ts b/packages/shared/src/utils/shortcode/shortcode.ts index 6517450d2bd0..954a1da76487 100644 --- a/packages/shared/src/utils/shortcode/shortcode.ts +++ b/packages/shared/src/utils/shortcode/shortcode.ts @@ -9,19 +9,17 @@ type TIsHighLow = { }; }; -type TInfoFromShortcode = Partial< - Record< - | 'category' - | 'underlying' - | 'barrier_1' - | 'multiplier' - | 'start_time' - | 'payout_tick' - | 'growth_rate' - | 'growth_frequency' - | 'tick_size_barrier', - string - > +type TInfoFromShortcode = Record< + | 'category' + | 'underlying' + | 'barrier_1' + | 'multiplier' + | 'start_time' + | 'payout_tick' + | 'growth_rate' + | 'growth_frequency' + | 'tick_size_barrier', + string >; // category_underlying_amount diff --git a/packages/shared/src/utils/validation/declarative-validation-rules.ts b/packages/shared/src/utils/validation/declarative-validation-rules.ts index 96fc564926d6..29578f216009 100644 --- a/packages/shared/src/utils/validation/declarative-validation-rules.ts +++ b/packages/shared/src/utils/validation/declarative-validation-rules.ts @@ -47,7 +47,7 @@ export const validPhone = (value: string) => /^\+?([0-9-]+\s)*[0-9-]+$/.test(val export const validLetterSymbol = (value: string) => /^[A-Za-z]+([a-zA-Z.' -])*[a-zA-Z.' -]+$/.test(value); export const validName = (value: string) => /^(?!.*\s{2,})[\p{L}\s'.-]{2,50}$/u.test(value); export const validLength = (value = '', options: TOptions) => - (options.min ? value.length >= options.min : true) && (options.max ? value.length <= options.max : true); + (options.min ? value.length >= options.min : true) && (options.max ? value.length <= Number(options.max) : true); export const validPassword = (value: string) => /^(?=.*[a-z])(?=.*\d)(?=.*[A-Z])[!-~]{8,25}$/.test(value); export const validEmail = (value: string) => /^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,63}$/.test(value); const validBarrier = (value: string) => /^[+-]?\d+\.?\d*$/.test(value); @@ -60,7 +60,7 @@ const validEmailToken = (value: string) => value.trim().length === 8; let pre_build_dvrs: TInitPreBuildDVRs, form_error_messages: TFormErrorMessagesTypes; const isMoreThanMax = (value: number, options: TOptions) => - options.type === 'float' ? +value > +options.max! : compareBigUnsignedInt(value, options.max!) === 1; + options.type === 'float' ? +value > Number(options.max) : compareBigUnsignedInt(value, options.max) === 1; export const validNumber = (value: string, opts: TOptions) => { const options = cloneObject(opts); diff --git a/packages/stores/src/mockStore.ts b/packages/stores/src/mockStore.ts index 1174b164511d..407b46575361 100644 --- a/packages/stores/src/mockStore.ts +++ b/packages/stores/src/mockStore.ts @@ -1,5 +1,5 @@ import merge from 'lodash.merge'; -import type { TStores } from '../types'; +import type { TCoreStores, TStores } from '../types'; const mock = (): TStores & { is_mock: boolean } => { const common_store_error = { @@ -12,7 +12,6 @@ const mock = (): TStores & { is_mock: boolean } => { should_clear_error_on_click: false, should_show_refresh: false, redirectOnClick: jest.fn(), - setError: jest.fn(), }; return { is_mock: true, @@ -108,6 +107,7 @@ const mock = (): TStores & { is_mock: boolean } => { }, balance: '', can_change_fiat_currency: false, + clients_country: '', country_standpoint: { is_belgium: false, is_france: false, @@ -149,6 +149,7 @@ const mock = (): TStores & { is_mock: boolean } => { is_switching: false, is_tnc_needed: false, is_trading_experience_incomplete: false, + is_unwelcome: false, is_virtual: false, is_withdrawal_lock: false, is_populating_account_list: false, @@ -197,6 +198,7 @@ const mock = (): TStores & { is_mock: boolean } => { active_accounts: [], account_list: [], available_crypto_currencies: [], + selectCurrency: jest.fn(), setAccountStatus: jest.fn(), setBalanceOtherAccounts: jest.fn(), setInitialized: jest.fn(), @@ -280,7 +282,11 @@ const mock = (): TStores & { is_mock: boolean } => { is_network_online: false, network_status: {}, services_error: {}, - server_time: undefined, + server_time: new Date() as unknown as TCoreStores['common']['server_time'], + setError: jest.fn(), + setSelectedContractType: jest.fn(), + setServicesError: jest.fn(), + showError: jest.fn(), is_language_changing: false, setAppstorePlatform: jest.fn(), app_routing_history: [], @@ -293,8 +299,10 @@ const mock = (): TStores & { is_mock: boolean } => { }, current_focus: null, is_account_settings_visible: false, + is_advanced_duration: false, is_loading: false, is_cashier_visible: false, + is_chart_layout_default: false, is_closing_create_real_account_modal: false, is_dark_mode_on: false, is_language_settings_modal_on: false, @@ -317,6 +325,7 @@ const mock = (): TStores & { is_mock: boolean } => { has_only_forward_starting_contracts: false, has_real_account_signup_ended: false, notification_messages_ui: jest.fn(), + openPositionsDrawer: jest.fn(), openRealAccountSignup: jest.fn(), setHasOnlyForwardingContracts: jest.fn(), setIsClosingCreateRealAccountModal: jest.fn(), @@ -332,6 +341,7 @@ const mock = (): TStores & { is_mock: boolean } => { addToast: jest.fn(), removeToast: jest.fn(), reports_route_tab_index: 1, + resetPurchaseStates: jest.fn(), should_show_cancellation_warning: false, toggleCancellationWarning: jest.fn(), toggleUnsupportedContractModal: jest.fn(), @@ -453,22 +463,27 @@ const mock = (): TStores & { is_mock: boolean } => { setP2POrderProps: jest.fn(), showAccountSwitchToRealNotification: jest.fn(), setP2PRedirectTo: jest.fn(), + setShouldShowPopups: jest.fn(), toggleNotificationsModal: jest.fn(), }, portfolio: { active_positions: [], active_positions_count: 0, all_positions: [], + barriers: [], error: '', getPositionById: jest.fn(), is_loading: false, is_accumulator: false, is_multiplier: false, + onBuyResponse: jest.fn(), onClickCancel: jest.fn(), onClickSell: jest.fn(), onMount: jest.fn(), + open_accu_contract: null, positions: [], removePositionById: jest.fn(), + setContractType: jest.fn(), }, contract_trade: { accountSwitchListener: jest.fn(), @@ -535,7 +550,10 @@ const mock = (): TStores & { is_mock: boolean } => { }, }, chart_barrier_store: {}, - active_symbols: {}, + active_symbols: { + active_symbols: [], + setActiveSymbols: jest.fn(), + }, }; }; diff --git a/packages/stores/types.ts b/packages/stores/types.ts index 7843d55ccdbf..b35272fca3a4 100644 --- a/packages/stores/types.ts +++ b/packages/stores/types.ts @@ -16,6 +16,7 @@ import type { StatesList, ContractUpdateHistory, Transaction, + ActiveSymbols, } from '@deriv/api-types'; import type { Moment } from 'moment'; import type { RouteComponentProps } from 'react-router'; @@ -266,6 +267,7 @@ type TClientStore = { available_crypto_currencies: string[]; balance?: string | number; can_change_fiat_currency: boolean; + clients_country: string; cfd_score: number; setCFDScore: (score: number) => void; country_standpoint: TCountryStandpoint; @@ -286,6 +288,7 @@ type TClientStore = { is_eu_country: boolean; is_eu: boolean; is_uk: boolean; + is_unwelcome: boolean; is_single_currency: boolean; is_social_signup: boolean; has_residence: boolean; @@ -326,6 +329,7 @@ type TClientStore = { standpoint: TStandPoint; setAccountStatus: (status?: GetAccountStatus) => void; setBalanceOtherAccounts: (balance: number) => void; + selectCurrency: (currency: string) => void; setInitialized: (status?: boolean) => void; setLogout: (status?: boolean) => void; setVisibilityRealityCheck: (value: boolean) => void; @@ -410,15 +414,14 @@ type TClientStore = { }; type TCommonStoreError = { - app_routing_history?: TAppRoutingHistory[]; - header: string | JSX.Element; + header?: string | JSX.Element; message: string | JSX.Element; - redirect_label: string; + redirect_label?: string; redirect_to?: string; - redirectOnClick: (() => void) | null; - setError?: (has_error: boolean, error: React.ReactNode | null) => void; + redirectOnClick?: (() => void) | null; should_clear_error_on_click?: boolean; - should_show_refresh: boolean; + should_redirect?: boolean; + should_show_refresh?: boolean; type?: string; }; type TCommonStoreServicesError = { @@ -436,7 +439,7 @@ type TCommonStore = { platform: 'dxtrade' | 'derivez' | 'mt5' | 'ctrader' | ''; routeBackInApp: (history: Pick, additional_platform_path?: string[]) => void; routeTo: (pathname: string) => void; - server_time?: Moment; + server_time: Moment; changeCurrentLanguage: (new_language: string) => void; changeSelectedLanguage: (key: string) => void; current_language: string; @@ -444,6 +447,10 @@ type TCommonStore = { services_error: TCommonStoreServicesError; is_socket_opened: boolean; setAppstorePlatform: (value: string) => void; + setError?: (has_error: boolean, error: TCommonStoreError) => void; + setSelectedContractType: (contract_type: string) => void; + setServicesError: (error: TCommonStoreServicesError) => void; + showError: (error: TCommonStoreError) => void; app_routing_history: TAppRoutingHistory[]; getExchangeRate: (from_currency: string, to_currency: string) => Promise; network_status: Record | { [key: string]: string }; @@ -460,10 +467,13 @@ type TUiStore = { has_real_account_signup_ended: boolean; header_extension: JSX.Element | null; is_account_settings_visible: boolean; - is_loading: boolean; + is_advanced_duration: boolean; is_cashier_visible: boolean; + is_chart_asset_info_visible?: boolean; + is_chart_layout_default: boolean; is_closing_create_real_account_modal: boolean; is_dark_mode_on: boolean; + is_loading: boolean; is_reports_visible: boolean; is_route_modal_on: boolean; is_language_settings_modal_on: boolean; @@ -472,6 +482,7 @@ type TUiStore = { is_positions_drawer_on: boolean; is_services_error_visible: boolean; is_unsupported_contract_modal_visible: boolean; + openPositionsDrawer: () => void; openRealAccountSignup: ( value: 'maltainvest' | 'svg' | 'add_crypto' | 'choose' | 'add_fiat' | 'set_currency' | 'manage' ) => void; @@ -487,10 +498,11 @@ type TUiStore = { ] | [] ) => void; + resetPurchaseStates: () => void; setAppContentsScrollRef: (ref: React.MutableRefObject) => void; setCurrentFocus: (value: string | null) => void; setDarkMode: (is_dark_mode_on: boolean) => boolean; - setHasOnlyForwardingContracts: (has_only_forward_starting_contracts: boolean) => void; + setHasOnlyForwardingContracts: (has_only_forward_starting_contracts?: boolean) => void; setReportsTabIndex: (value: number) => void; setIsClosingCreateRealAccountModal: (value: boolean) => void; setRealAccountSignupEnd: (status: boolean) => void; @@ -565,16 +577,20 @@ type TPortfolioStore = { active_positions: TPortfolioPosition[]; active_positions_count: number; all_positions: TPortfolioPosition[]; + barriers: TBarriers; error: string; getPositionById: (id: number) => TPortfolioPosition; is_loading: boolean; is_multiplier: boolean; is_accumulator: boolean; + onBuyResponse: (contract_info: { contract_id: number; longcode: string; contract_type: string }) => void; onClickCancel: (contract_id?: number) => void; onClickSell: (contract_id?: number) => void; onMount: () => void; + open_accu_contract: TPortfolioPosition | null; positions: TPortfolioPosition[]; removePositionById: (id: number) => void; + setContractType: (contract_type: string) => void; }; type TAccumulatorBarriersData = { @@ -590,16 +606,42 @@ type TAccumulatorContractBarriersData = TAccumulatorBarriersData & { should_update_contract_barriers: boolean; }; type TAddContractParams = { - barrier: string; - contract_id: string; + barrier: number | null; + contract_id: number; contract_type: string; start_time: number; longcode: string; underlying: string; is_tick_contract: boolean; - limit_order: ProposalOpenContract['limit_order']; -}; - + limit_order?: ProposalOpenContract['limit_order']; +}; +type TOnChartBarrierChange = null | ((barrier_1: string, barrier_2?: string) => void); +type TOnChangeParams = { high: string | number; low?: string | number }; +type TBarriers = Array<{ + color: string; + lineStyle: string; + shade?: string; + shadeColor?: string; + high?: string | number; + low?: string | number; + onChange: (barriers: TOnChangeParams) => void; + relative: boolean; + draggable: boolean; + hidePriceLines: boolean; + hideBarrierLine?: boolean; + hideOffscreenLine?: boolean; + title?: string; + onChartBarrierChange: TOnChartBarrierChange | null; + key?: string; + hideOffscreenBarrier?: boolean; + isSingleBarrier?: boolean; + onBarrierChange: (barriers: TOnChangeParams) => void; + updateBarrierColor: (is_dark_mode: boolean) => void; + updateBarriers: (high: string | number, low?: string | number, isFromChart?: boolean) => void; + updateBarrierShade: (should_display: boolean, contract_type: string) => void; + barrier_count: number; + default_shade: string; +}>; type TContractTradeStore = { accountSwitchListener: () => Promise; accu_barriers_timeout_id: NodeJS.Timeout | null; @@ -621,7 +663,7 @@ type TContractTradeStore = { contracts: TContractStore[]; error_message: string; getContractById: (contract_id?: number) => TContractStore; - granularity: string | number; + granularity: null | number; has_crossed_accu_barriers: boolean; has_error: boolean; last_contract: TContractStore | Record; @@ -644,9 +686,9 @@ type TContractTradeStore = { }>; onUnmount: () => void; prev_chart_type: string; - prev_granularity: string | number | null; + prev_granularity: number | null; removeContract: (data: { contract_id: string }) => void; - savePreviousChartMode: (chart_type: string, granularity: string | number | null) => void; + savePreviousChartMode: (chart_type: string, granularity: number | null) => void; setNewAccumulatorBarriersData: ( new_barriers_data: TAccumulatorBarriersData, should_update_contract_barriers?: boolean @@ -703,9 +745,15 @@ type TNotificationStore = { setP2POrderProps: () => void; showAccountSwitchToRealNotification: (loginid: string, currency: string) => void; setP2PRedirectTo: () => void; + setShouldShowPopups: (should_show_popups: boolean) => void; toggleNotificationsModal: () => void; }; +type TActiveSymbolsStore = { + active_symbols: ActiveSymbols; + setActiveSymbols: () => Promise; +}; + type TBalance = { currency: string; balance: number; @@ -821,7 +869,7 @@ export type TCoreStores = { pushwoosh: Record; contract_replay: TContractReplay; chart_barrier_store: Record; - active_symbols: Record; + active_symbols: TActiveSymbolsStore; }; export type TStores = TCoreStores & { diff --git a/packages/trader/package.json b/packages/trader/package.json index 9d802416308b..0cc781502849 100644 --- a/packages/trader/package.json +++ b/packages/trader/package.json @@ -87,6 +87,7 @@ "@deriv/analytics": "^1.0.0", "@deriv/components": "^1.0.0", "@deriv/deriv-api": "^1.0.11", + "@deriv/api-types": "^1.0.118", "@deriv/deriv-charts": "1.3.6", "@deriv/reports": "^1.0.0", "@deriv/shared": "^1.0.0", diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Duration/duration-wrapper.jsx b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Duration/duration-wrapper.jsx index 8ef7596439f6..eb20274ae761 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Duration/duration-wrapper.jsx +++ b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Duration/duration-wrapper.jsx @@ -1,6 +1,4 @@ -import PropTypes from 'prop-types'; import React from 'react'; -import { PropTypes as MobxPropTypes } from 'mobx-react'; import { getDurationMinMaxValues } from '@deriv/shared'; import Duration from './duration.jsx'; import { observer, useStore } from '@deriv/stores'; @@ -195,18 +193,4 @@ const DurationWrapper = observer(() => { ); }); -DurationWrapper.propTypes = { - duration_d: PropTypes.number, - duration_h: PropTypes.number, - duration_m: PropTypes.number, - duration_s: PropTypes.number, - duration_unit: PropTypes.string, - duration_units_list: MobxPropTypes.arrayOrObservableArray, - getDurationFromUnit: PropTypes.func, - is_minimized: PropTypes.bool, - sessions: MobxPropTypes.arrayOrObservableArray, - start_time: PropTypes.string, - symbol: PropTypes.string, -}; - export default DurationWrapper; diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount.tsx b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount.tsx index c797bdf1c611..37b268971f87 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount.tsx +++ b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount.tsx @@ -16,8 +16,8 @@ type TInput = { currency: string; current_focus: string | null; error_messages?: string[]; - is_single_currency: boolean; is_disabled?: boolean; + is_single_currency: boolean; onChange: (e: { target: { name: string; value: number | string } }) => void; setCurrentFocus: (name: string | null) => void; }; @@ -58,7 +58,7 @@ export const Input = ({ /> ); -const Amount = observer(({ is_minimized }: { is_minimized: boolean }) => { +const Amount = observer(({ is_minimized = false }: { is_minimized?: boolean }) => { const { ui, client } = useStore(); const { currencies_list, is_single_currency } = client; const { setCurrentFocus, current_focus } = ui; diff --git a/packages/trader/src/Modules/Trading/Containers/trade-params-mobile.tsx b/packages/trader/src/Modules/Trading/Containers/trade-params-mobile.tsx index 75c506a3b597..8cb17c6ffe72 100644 --- a/packages/trader/src/Modules/Trading/Containers/trade-params-mobile.tsx +++ b/packages/trader/src/Modules/Trading/Containers/trade-params-mobile.tsx @@ -13,8 +13,8 @@ import { localize } from '@deriv/translations'; type TTradeParamsModal = { is_open: boolean; - toggleModal: () => void; tab_index: number; + toggleModal: () => void; }; type TTradeParamsMobile = { diff --git a/packages/trader/src/Stores/Modules/Trading/Actions/start-date.ts b/packages/trader/src/Stores/Modules/Trading/Actions/start-date.ts index a713212e746a..62d9950f5e04 100644 --- a/packages/trader/src/Stores/Modules/Trading/Actions/start-date.ts +++ b/packages/trader/src/Stores/Modules/Trading/Actions/start-date.ts @@ -38,7 +38,6 @@ export const onChangeStartDate = async (store: TTradeStore) => { export const onChangeExpiry = async (store: TTradeStore) => { const { start_time, expiry_date, expiry_type, expiry_time, start_date, symbol, sessions } = store; - // @ts-expect-error TODO: check if TS error is gone after ContractType is migrated to TS const trading_times = await ContractType.getTradingTimes(expiry_date, symbol); const obj_market_open_times = { market_open_times: trading_times.open }; const obj_market_close_times = { market_close_times: trading_times.close }; diff --git a/packages/trader/src/Stores/Modules/Trading/Constants/validation-rules.ts b/packages/trader/src/Stores/Modules/Trading/Constants/validation-rules.ts index 150a4385f927..cbd1835ba444 100644 --- a/packages/trader/src/Stores/Modules/Trading/Constants/validation-rules.ts +++ b/packages/trader/src/Stores/Modules/Trading/Constants/validation-rules.ts @@ -4,9 +4,16 @@ import { isSessionAvailable } from '../Helpers/start-date'; import { TTradeStore } from 'Types'; import type { TRuleOptions } from 'Utils/Validator/validator'; +type TValidationRules = { + [key: string]: { + rules?: Array[]; + trigger?: string; + }; +}; + const tradeSpecificBarrierCheck = (is_vanilla: boolean, input: number) => is_vanilla || input !== 0; -export const getValidationRules = () => ({ +export const getValidationRules = (): TValidationRules => ({ amount: { rules: [ ['req', { message: localize('Amount is a required field.') }], @@ -18,35 +25,25 @@ export const getValidationRules = () => ({ [ 'req', { - condition: (store: TTradeStore) => - store.barrier_count && store.form_components.indexOf('barrier') > -1, + condition: store => !!store.barrier_count && store.form_components.indexOf('barrier') > -1, message: localize('Barrier is a required field.'), }, ], - ['barrier', { condition: (store: TTradeStore) => store.barrier_count }], + ['barrier', { condition: (store: TTradeStore) => !!store.barrier_count }], [ 'custom', { - func: ( - value: TTradeStore['barrier_1'], - options: Partial, - store: TTradeStore, - inputs: Pick - ) => (store.barrier_count > 1 ? +value > +inputs.barrier_2 : true), + func: (value: TTradeStore['barrier_1'], options, store, inputs) => + Number(store?.barrier_count) > 1 ? +value > Number(inputs?.barrier_2) : true, message: localize('Higher barrier must be higher than lower barrier.'), }, ], [ 'custom', { - func: ( - value: TTradeStore['barrier_1'], - options: Partial, - store: TTradeStore, - inputs: Pick - ) => - /^[+-]/.test(inputs.barrier_1) - ? tradeSpecificBarrierCheck(store.is_vanilla, +inputs.barrier_1) + func: (value: TTradeStore['barrier_1'], options, store, inputs) => + /^[+-]/.test(inputs?.barrier_1 ?? '') + ? tradeSpecificBarrierCheck(!!store?.is_vanilla, Number(inputs?.barrier_1)) : true, message: localize('Barrier cannot be zero.'), }, @@ -59,35 +56,25 @@ export const getValidationRules = () => ({ [ 'req', { - condition: (store: TTradeStore) => - store.barrier_count > 1 && store.form_components.indexOf('barrier') > -1, + condition: store => store.barrier_count > 1 && store.form_components.indexOf('barrier') > -1, message: localize('Barrier is a required field.'), }, ], - ['barrier', { condition: (store: TTradeStore) => store.barrier_count }], + ['barrier', { condition: (store: TTradeStore) => !!store.barrier_count }], [ 'custom', { - func: ( - value: TTradeStore['barrier_2'], - options: Partial, - store: TTradeStore, - inputs: Pick - ) => - (/^[+-]/g.test(inputs.barrier_1) && /^[+-]/g.test(value)) || - (/^(?![+-])/g.test(inputs.barrier_1) && /^(?![+-])/g.test(value)), + func: (value: TTradeStore['barrier_2'], options, store, inputs) => + (/^[+-]/g.test(inputs?.barrier_1 ?? '') && /^[+-]/g.test(value)) || + (/^(?![+-])/g.test(inputs?.barrier_1 ?? '') && /^(?![+-])/g.test(value)), message: localize('Both barriers should be relative or absolute'), }, ], [ 'custom', { - func: ( - value: TTradeStore['barrier_2'], - options: Partial, - store: TTradeStore, - inputs: Pick - ) => +inputs.barrier_1 > +value, + func: (value: TTradeStore['barrier_2'], options, store, inputs) => + Number(inputs?.barrier_1) > +value, message: localize('Lower barrier must be lower than higher barrier.'), }, ], @@ -108,37 +95,41 @@ export const getValidationRules = () => ({ [ 'custom', { - func: (value: TTradeStore['start_time'], options: Partial, store: TTradeStore) => - store.contract_start_type === 'spot' || isTimeValid(value ?? ''), + func: (value: TTradeStore['start_time'], options, store) => + store?.contract_start_type === 'spot' || isTimeValid(value ?? ''), message: localize('Please enter the start time in the format "HH:MM".'), }, ], [ 'custom', { - func: (value: TTradeStore['start_time'], options: Partial, store: TTradeStore) => - store.contract_start_type === 'spot' || isHourValid(value ?? ''), + func: (value: TTradeStore['start_time'], options, store) => + store?.contract_start_type === 'spot' || isHourValid(value ?? ''), message: localize('Hour must be between 0 and 23.'), }, ], [ 'custom', { - func: (value: TTradeStore['start_time'], options: Partial, store: TTradeStore) => - store.contract_start_type === 'spot' || isMinuteValid(value ?? ''), + func: (value: TTradeStore['start_time'], options, store) => + store?.contract_start_type === 'spot' || isMinuteValid(value ?? ''), message: localize('Minute must be between 0 and 59.'), }, ], [ 'custom', { - func: (value: TTradeStore['start_time'], options: Partial, store: TTradeStore) => { - if (store.contract_start_type === 'spot') return true; + func: (value: TTradeStore['start_time'], options, store) => { + if (store?.contract_start_type === 'spot') return true; if (!isTimeValid(value ?? '')) return false; - const start_moment = toMoment(store.start_date); + const start_moment = toMoment(store?.start_date); const start_moment_clone = start_moment.clone(); const [h, m] = value?.split(':') ?? []; - return isSessionAvailable(store.sessions, start_moment_clone.hour(+h).minute(+m), start_moment); + return isSessionAvailable( + store?.sessions, + start_moment_clone.hour(+h).minute(+m), + start_moment + ); }, message: localize('Start time cannot be in the past.'), }, @@ -150,37 +141,41 @@ export const getValidationRules = () => ({ [ 'custom', { - func: (value: TTradeStore['expiry_time'], options: Partial, store: TTradeStore) => - store.contract_start_type === 'spot' || isTimeValid(value ?? ''), + func: (value: TTradeStore['expiry_time'], options, store) => + store?.contract_start_type === 'spot' || isTimeValid(value ?? ''), message: localize('Please enter the start time in the format "HH:MM".'), }, ], [ 'custom', { - func: (value: TTradeStore['expiry_time'], options: Partial, store: TTradeStore) => - store.contract_start_type === 'spot' || isHourValid(value ?? ''), + func: (value: TTradeStore['expiry_time'], options, store) => + store?.contract_start_type === 'spot' || isHourValid(value ?? ''), message: localize('Hour must be between 0 and 23.'), }, ], [ 'custom', { - func: (value: TTradeStore['expiry_time'], options: Partial, store: TTradeStore) => - store.contract_start_type === 'spot' || isMinuteValid(value ?? ''), + func: (value: TTradeStore['expiry_time'], options, store) => + store?.contract_start_type === 'spot' || isMinuteValid(value ?? ''), message: localize('Minute must be between 0 and 59.'), }, ], [ 'custom', { - func: (value: TTradeStore['expiry_time'], options: Partial, store: TTradeStore) => { - if (store.contract_start_type === 'spot') return true; + func: (value: TTradeStore['expiry_time'], options, store) => { + if (store?.contract_start_type === 'spot') return true; if (!isTimeValid(value ?? '')) return false; - const start_moment = toMoment(store.start_date); + const start_moment = toMoment(store?.start_date); const start_moment_clone = start_moment.clone(); const [h, m] = value?.split(':') ?? []; - return isSessionAvailable(store.sessions, start_moment_clone.hour(+h).minute(+m), start_moment); + return isSessionAvailable( + store?.sessions, + start_moment_clone.hour(+h).minute(+m), + start_moment + ); }, message: localize('Expiry time cannot be in the past.'), }, diff --git a/packages/trader/src/Stores/Modules/Trading/Helpers/contract-type.ts b/packages/trader/src/Stores/Modules/Trading/Helpers/contract-type.ts index 667e1e93c98b..7cbe52293bfe 100644 --- a/packages/trader/src/Stores/Modules/Trading/Helpers/contract-type.ts +++ b/packages/trader/src/Stores/Modules/Trading/Helpers/contract-type.ts @@ -159,7 +159,7 @@ export const ContractType = (() => { return trade_types; }; - const getArrayDefaultValue = (arr_new_values: Array, value: string | number) => + const getArrayDefaultValue = (arr_new_values: Array, value: T): T => arr_new_values.indexOf(value) !== -1 ? value : arr_new_values[0]; const getContractValues = (store: TTradeStore): TContractValues | Record => { @@ -287,7 +287,7 @@ export const ContractType = (() => { }; const getDurationMinMax = (contract_type: string, contract_start_type: string, contract_expiry_type?: string) => { - let duration_min_max: TTradeStore['duration_min_max'] | TTradeStore['duration_min_max'][string] = + let duration_min_max: TTradeStore['duration_min_max'] = getPropertyValue(available_contract_types, [ contract_type, 'config', @@ -297,7 +297,12 @@ export const ContractType = (() => { ]) || {}; if (contract_expiry_type) { - duration_min_max = 'contract_expiry_type' in duration_min_max ? duration_min_max[contract_expiry_type] : {}; + duration_min_max = + 'contract_expiry_type' in duration_min_max + ? (duration_min_max as unknown as { [key: string]: TTradeStore['duration_min_max'] })[ + contract_expiry_type + ] + : {}; } return { duration_min_max }; @@ -574,7 +579,7 @@ export const ContractType = (() => { moment_obj.minute(Math.ceil(moment_obj.minute() / 5) * 5); const getTradeTypes = (contract_type: string) => ({ - trade_types: getPropertyValue(available_contract_types, [contract_type, 'config', 'trade_types']) as string[], + trade_types: getPropertyValue(available_contract_types, [contract_type, 'config', 'trade_types']), }); const getBarriers = (contract_type: string, expiry_type: string, stored_barrier_value?: string) => { diff --git a/packages/trader/src/Stores/Modules/Trading/Helpers/process.ts b/packages/trader/src/Stores/Modules/Trading/Helpers/process.ts index 9c4afbcc970d..bab8b78af2da 100644 --- a/packages/trader/src/Stores/Modules/Trading/Helpers/process.ts +++ b/packages/trader/src/Stores/Modules/Trading/Helpers/process.ts @@ -23,7 +23,7 @@ const processInSequence = async ( }); }; -export const processTradeParams = async (store: TTradeStore, new_state: DeepPartial) => { +export const processTradeParams = async (store: TTradeStore, new_state: Partial) => { const functions = getMethodsList(store, new_state); await processInSequence(store, functions); diff --git a/packages/trader/src/Stores/Modules/Trading/trade-store.ts b/packages/trader/src/Stores/Modules/Trading/trade-store.ts index 815ea39fba38..b2d13c05f93c 100644 --- a/packages/trader/src/Stores/Modules/Trading/trade-store.ts +++ b/packages/trader/src/Stores/Modules/Trading/trade-store.ts @@ -27,7 +27,6 @@ import { unsupported_contract_types_list, BARRIER_COLORS, BARRIER_LINE_STYLES, - TContractInfo, } from '@deriv/shared'; import { RudderStack } from '@deriv/analytics'; import type { TEvents } from '@deriv/analytics'; @@ -44,7 +43,7 @@ import { action, computed, makeObservable, observable, override, reaction, runIn import { createProposalRequests, getProposalErrorField, getProposalInfo } from './Helpers/proposal'; import { getHoveredColor } from './Helpers/barrier-utils'; import BaseStore from '../../base-store'; -import { TTextValueStrings } from 'Types'; +import { TTextValueNumber, TTextValueStrings } from 'Types'; import { ChartBarrierStore } from '../SmartChart/chart-barrier-store'; import debounce from 'lodash.debounce'; import { setLimitOrderBarriers } from './Helpers/limit-orders'; @@ -171,18 +170,6 @@ type TToastBoxObject = { list?: Array; }; type TTurbosBarriersData = Record | { barrier: string; barrier_choices: string[] }; -export type TValidationErrors = { [key: string]: string[] }; -export type TValidationRules = Omit>, 'duration'> & { - duration: { - rules: [ - string, - { - min: number | null; - max: number | null; - } - ][]; - }; -}; const store_name = 'trade_store'; const g_subscribers_map: Partial>> = {}; // blame amin.m @@ -277,7 +264,7 @@ export default class TradeStore extends BaseStore { // Multiplier trade params multiplier = 0; - multiplier_range_list: number[] = []; + multiplier_range_list: TTextValueNumber[] = []; stop_loss?: string; take_profit?: string; has_stop_loss = false; @@ -525,10 +512,10 @@ export default class TradeStore extends BaseStore { () => [this.has_stop_loss, this.has_take_profit], () => { if (!this.has_stop_loss) { - (this.validation_errors as TValidationErrors).stop_loss = []; + this.validation_errors.stop_loss = []; } if (!this.has_take_profit) { - (this.validation_errors as TValidationErrors).take_profit = []; + this.validation_errors.take_profit = []; } } ); @@ -542,8 +529,8 @@ export default class TradeStore extends BaseStore { } else { // we need to remove these two validation rules on contract_type change // to be able to remove any existing Stop loss / Take profit validation errors - delete (this.validation_rules as TValidationRules).stop_loss; - delete (this.validation_rules as TValidationRules).take_profit; + delete this.validation_rules.stop_loss; + delete this.validation_rules.take_profit; } this.resetAccumulatorData(); } @@ -575,8 +562,7 @@ export default class TradeStore extends BaseStore { this.is_accumulator && !!this.root_store.portfolio.open_accu_contract && !!this.root_store.portfolio.active_positions.find( - ({ contract_info, type }: { contract_info: TContractInfo; type: string }) => - isAccumulatorContract(type) && contract_info.underlying === this.symbol + ({ contract_info, type }) => isAccumulatorContract(type) && contract_info.underlying === this.symbol ) ); } @@ -762,7 +748,7 @@ export default class TradeStore extends BaseStore { } else if (name === 'currency') { // Only allow the currency dropdown change if client is not logged in if (!this.root_store.client.is_logged_in) { - this.root_store.client.selectCurrency(value); + this.root_store.client.selectCurrency(value as string); } } else if (name === 'expiry_date') { this.expiry_time = null; @@ -1006,7 +992,7 @@ export default class TradeStore extends BaseStore { new_state.start_date = parseInt(new_state.start_date); } - this[key as keyof this] = new_state[key as keyof TradeStore]; + this[key as 'currency'] = new_state[key as keyof TradeStore] as TradeStore['currency']; // validation is done in mobx intercept (base_store.js) // when barrier_1 is set, it is compared with store.barrier_2 (which is not updated yet) @@ -1120,7 +1106,6 @@ export default class TradeStore extends BaseStore { // TODO: handle barrier updates on proposal api // const is_barrier_changed = 'barrier_1' in new_state || 'barrier_2' in new_state; - // @ts-expect-error TODO: check if TS error is gone after TOverrideTradeStore is removed await processTradeParams(this, new_state); this.updateStore({ @@ -1192,7 +1177,6 @@ export default class TradeStore extends BaseStore { } requestProposal() { - // @ts-expect-error TODO: check that TS error is gone after TOverrideTradeStore is removed const requests = createProposalRequests(this); if (Object.values(this.validation_errors).some(e => e.length)) { this.proposal_info = {}; @@ -1236,7 +1220,6 @@ export default class TradeStore extends BaseStore { this.proposal_info = { ...this.proposal_info, - // @ts-expect-error TODO: check that TS error is gone after TOverrideTradeStore is removed [contract_type]: getProposalInfo(this, response, obj_prev_contract_basis), }; @@ -1379,15 +1362,13 @@ export default class TradeStore extends BaseStore { changeDurationValidationRules() { if (this.expiry_type === 'endtime') { - (this.validation_errors as TValidationErrors).duration = []; + this.validation_errors.duration = []; return; } - if (!(this.validation_rules as TValidationRules).duration) return; + if (!this.validation_rules.duration) return; - const index = (this.validation_rules as TValidationRules).duration.rules.findIndex( - item => item[0] === 'number' - ); + const index = this.validation_rules.duration.rules?.findIndex(item => item[0] === 'number'); const limits = this.duration_min_max[this.contract_expiry_type] || false; if (limits) { @@ -1396,12 +1377,11 @@ export default class TradeStore extends BaseStore { max: convertDurationLimit(+limits.max, this.duration_unit), }; - if (Number(index) > -1) { - (this.validation_rules as TValidationRules).duration.rules[Number(index)][1] = duration_options; + if (Number(index) > -1 && this.validation_rules.duration.rules) { + this.validation_rules.duration.rules[Number(index)][1] = duration_options; } else { - (this.validation_rules as TValidationRules).duration.rules.push(['number', duration_options]); + this.validation_rules.duration.rules?.push(['number', duration_options]); } - //@ts-expect-error: TODO: check if TS error is gone after base-store.ts from shared package is used here instead of base-store.js this.validateProperty('duration', this.duration); } } diff --git a/packages/trader/src/Stores/base-store.ts b/packages/trader/src/Stores/base-store.ts index 704efb2175f2..c66f6937d334 100644 --- a/packages/trader/src/Stores/base-store.ts +++ b/packages/trader/src/Stores/base-store.ts @@ -7,7 +7,7 @@ import { getValidationRules } from './Modules/Trading/Constants/validation-rules type TValidationRules = ReturnType | Record; type TBaseStoreOptions = { - root_store?: TCoreStores; + root_store: TCoreStores; local_storage_properties?: string[]; session_storage_properties?: string[]; validation_rules?: TValidationRules; @@ -33,21 +33,21 @@ export default class BaseStore { logout_listener: null | (() => Promise) = null; local_storage_properties: string[]; networkStatusChangeDisposer: null | (() => void) = null; - network_status_change_listener: null | ((is_online?: boolean) => void) = null; + network_status_change_listener: null | ((is_online: boolean) => void) = null; partial_fetch_time = 0; preSwitchAccountDisposer: null | (() => void) = null; pre_switch_account_listener: null | (() => Promise) = null; realAccountSignupEndedDisposer: null | (() => void) = null; real_account_signup_ended_listener: null | (() => Promise) = null; - root_store?: TCoreStores; + root_store: TCoreStores; session_storage_properties: string[]; store_name = ''; switchAccountDisposer: null | (() => void) = null; switch_account_listener: null | (() => Promise) = null; themeChangeDisposer: null | (() => void) = null; - theme_change_listener: null | ((is_dark_mode_on?: boolean) => void) = null; + theme_change_listener: null | ((is_dark_mode_on: boolean) => void) = null; validation_errors: { [key: string]: string[] } = {}; - validation_rules: TValidationRules = {}; + validation_rules: TValidationRules | Record = {}; /** * Constructor of the base class that gets properties' name of child which should be saved in storages * @@ -58,7 +58,7 @@ export default class BaseStore { * @property {Object} validation_rules - An object that contains the validation rules for each property of the store. * @property {String} store_name - Explicit store name for browser application storage (to bypass minification) */ - constructor(options: TBaseStoreOptions = {}) { + constructor(options = {} as TBaseStoreOptions) { makeObservable(this, { validation_errors: observable, validation_rules: observable, @@ -268,7 +268,7 @@ export default class BaseStore { * */ validateProperty(property: string, value: T[keyof T]) { - const validation_rules_for_property = this.validation_rules[property as keyof TValidationRules]; + const validation_rules_for_property = this.validation_rules[property] ?? {}; const trigger = 'trigger' in validation_rules_for_property ? validation_rules_for_property.trigger : undefined; const inputs = { [property]: value ?? this[property as keyof this] }; const validation_rules = { @@ -315,13 +315,13 @@ export default class BaseStore { this.switch_account_listener = listener; this.switchAccountDisposer = when( - () => !!this.root_store?.client.switch_broadcast, + () => !!this.root_store.client.switch_broadcast, () => { try { const result = this.switch_account_listener?.(); if (result?.then && typeof result.then === 'function') { result.then(() => { - this.root_store?.client.switchEndSignal(); + this.root_store.client.switchEndSignal(); this.onSwitchAccount(this.switch_account_listener); }); } else { @@ -343,13 +343,13 @@ export default class BaseStore { if (listener) { this.pre_switch_account_listener = listener; this.preSwitchAccountDisposer = when( - () => !!this.root_store?.client.pre_switch_broadcast, + () => !!this.root_store.client.pre_switch_broadcast, () => { try { const result = this.pre_switch_account_listener?.(); if (result?.then && typeof result.then === 'function') { result.then(() => { - this.root_store?.client.setPreSwitchAccount(false); + this.root_store.client.setPreSwitchAccount(false); this.onPreSwitchAccount(this.pre_switch_account_listener); }); } else { @@ -369,13 +369,13 @@ export default class BaseStore { onLogout(listener: null | (() => Promise)): void { this.logoutDisposer = when( - () => !!this.root_store?.client.has_logged_out, + () => !!this.root_store.client.has_logged_out, async () => { try { const result = this.logout_listener?.(); if (result?.then && typeof result.then === 'function') { result.then(() => { - this.root_store?.client.setLogout(false); + this.root_store.client.setLogout(false); this.onLogout(this.logout_listener); }); } else { @@ -395,13 +395,13 @@ export default class BaseStore { onClientInit(listener: null | (() => Promise)): void { this.clientInitDisposer = when( - () => !!this.root_store?.client.initialized_broadcast, + () => !!this.root_store.client.initialized_broadcast, async () => { try { const result = this.client_init_listener?.(); if (result?.then && typeof result.then === 'function') { result.then(() => { - this.root_store?.client.setInitialized(false); + this.root_store.client.setInitialized(false); this.onClientInit(this.client_init_listener); }); } else { @@ -419,9 +419,9 @@ export default class BaseStore { this.client_init_listener = listener; } - onNetworkStatusChange(listener: null | ((is_online?: boolean) => void)): void { + onNetworkStatusChange(listener: null | ((is_online: boolean) => void)): void { this.networkStatusChangeDisposer = reaction( - () => this.root_store?.common.is_network_online, + () => this.root_store.common.is_network_online, is_online => { try { this.network_status_change_listener?.(is_online); @@ -438,9 +438,9 @@ export default class BaseStore { this.network_status_change_listener = listener; } - onThemeChange(listener: null | ((is_dark_mode_on?: boolean) => void)): void { + onThemeChange(listener: null | ((is_dark_mode_on: boolean) => void)): void { this.themeChangeDisposer = reaction( - () => this.root_store?.ui.is_dark_mode_on, + () => this.root_store.ui.is_dark_mode_on, is_dark_mode_on => { try { this.theme_change_listener?.(is_dark_mode_on); @@ -459,13 +459,13 @@ export default class BaseStore { onRealAccountSignupEnd(listener: null | (() => Promise)): void { this.realAccountSignupEndedDisposer = when( - () => !!this.root_store?.ui.has_real_account_signup_ended, + () => !!this.root_store.ui.has_real_account_signup_ended, () => { try { const result = this.real_account_signup_ended_listener?.(); if (result?.then && typeof result.then === 'function') { result.then(() => { - this.root_store?.ui.setRealAccountSignupEnd(false); + this.root_store.ui.setRealAccountSignupEnd(false); this.onRealAccountSignupEnd(this.real_account_signup_ended_listener); }); } else { @@ -545,7 +545,7 @@ export default class BaseStore { assertHasValidCache(loginid: string, ...reactions: VoidFunction[]): void { // account was changed when this was unmounted. - if (this.root_store?.client.loginid !== loginid) { + if (this.root_store.client.loginid !== loginid) { reactions.forEach(act => act()); this.partial_fetch_time = 0; } diff --git a/packages/trader/src/Stores/useTraderStores.tsx b/packages/trader/src/Stores/useTraderStores.tsx index 2e3a930191ef..5e4db0cd77c2 100644 --- a/packages/trader/src/Stores/useTraderStores.tsx +++ b/packages/trader/src/Stores/useTraderStores.tsx @@ -1,14 +1,8 @@ import React from 'react'; import { useStore } from '@deriv/stores'; -import TradeStore, { TValidationErrors } from './Modules/Trading/trade-store'; +import TradeStore from './Modules/Trading/trade-store'; -type TOverrideTradeStore = Omit & { - //TODO: these types can be removed from here and trade-store after base-store is migrated to TS - validation_errors?: TValidationErrors; - validation_rules: TradeStore['validation_rules']; -}; - -const TraderStoreContext = React.createContext(null); +const TraderStoreContext = React.createContext(null); export const TraderStoreProvider = ({ children }: React.PropsWithChildren) => { const { modules } = useStore(); diff --git a/packages/trader/src/Types/common-prop.type.ts b/packages/trader/src/Types/common-prop.type.ts index f08fae2d9a4c..d2252c3012d8 100644 --- a/packages/trader/src/Types/common-prop.type.ts +++ b/packages/trader/src/Types/common-prop.type.ts @@ -4,6 +4,10 @@ export type TTextValueStrings = { text: string; value: string; }; +export type TTextValueNumber = { + text: string; + value: number; +}; export type TProposalTypeInfo = TTradeStore['proposal_info'][string]; diff --git a/packages/trader/src/Utils/Validator/validator.ts b/packages/trader/src/Utils/Validator/validator.ts index 60f258a7fcad..2519c64f96c2 100644 --- a/packages/trader/src/Utils/Validator/validator.ts +++ b/packages/trader/src/Utils/Validator/validator.ts @@ -8,8 +8,8 @@ type TOptions = { [key: string]: unknown; decimals?: string | number; is_required?: boolean; - max?: number | string; - min?: number | string; + max?: number | string | null; + min?: number | string | null; name1?: string; name2?: string; regex?: RegExp; @@ -19,9 +19,14 @@ type TOptions = { type TInitPreBuildDVRs = ReturnType; export type TRuleOptions = { - func: (value: string | number, options?: TOptions, store?: TTradeStore, inputs?: unknown) => boolean; - condition: (store: TTradeStore) => boolean; - message: string; + func?: ( + value: T, + options?: TOptions, + store?: TTradeStore, + inputs?: Pick + ) => boolean | { is_ok: boolean; message: string }; + condition?: (store: TTradeStore) => boolean; + message?: string; } & TOptions; type TRule = string | Array; @@ -32,13 +37,13 @@ type TValidationResult = { }; class Validator { - input: Partial; + input: Pick; rules: Partial; store: TTradeStore; errors: Error; error_count: number; - constructor(input: Partial, rules: Partial, store: TTradeStore) { + constructor(input: Pick, rules: Partial, store: TTradeStore) { this.input = input; this.rules = rules; this.store = store; @@ -101,8 +106,8 @@ class Validator { let is_valid, error_message; if (ruleObject.name === 'number') { - const { is_ok, message }: TValidationResult = ruleObject.validator( - this.input[attribute as keyof TTradeStore], + const { is_ok, message } = ruleObject.validator( + this.input[attribute as keyof TTradeStore] as string, ruleObject.options, this.store, this.input @@ -111,7 +116,7 @@ class Validator { error_message = message; } else { is_valid = ruleObject.validator( - this.input[attribute as keyof TTradeStore], + this.input[attribute as keyof TTradeStore] as string, ruleObject.options, this.store, this.input @@ -154,12 +159,7 @@ class Validator { : ( getPreBuildDVRs() as unknown as { [key: string]: { - func: ( - value: string | number, - options?: TRuleOptions, - store?: TTradeStore, - inputs?: unknown - ) => boolean | { is_ok: boolean; message: string }; + func: TRuleOptions['func']; }; } )[rule_object_name].func, From 6e61e4d636b0f92bc571e95feb688289ed691f47 Mon Sep 17 00:00:00 2001 From: kate-deriv Date: Thu, 28 Sep 2023 10:48:40 +0300 Subject: [PATCH 51/55] refactor: apply suggestions --- .../Modules/Contract/Components/Digits/digits.tsx | 2 +- .../Components/Form/TradeParams/amount-mobile.tsx | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/trader/src/Modules/Contract/Components/Digits/digits.tsx b/packages/trader/src/Modules/Contract/Components/Digits/digits.tsx index 45a593833561..e963a30c2ee7 100644 --- a/packages/trader/src/Modules/Contract/Components/Digits/digits.tsx +++ b/packages/trader/src/Modules/Contract/Components/Digits/digits.tsx @@ -28,7 +28,7 @@ type TOnLastDigitSpot = { type TDigitsWrapper = TDigits & { onChangeStatus?: (params: TOnChangeStatus) => void; - onLastDigitSpot?: (paams: TOnLastDigitSpot) => void; + onLastDigitSpot?: (params: TOnLastDigitSpot) => void; }; type TDigits = Pick & { digits_array?: number[]; diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount-mobile.tsx b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount-mobile.tsx index edd47c673244..b968f272ee63 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount-mobile.tsx +++ b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount-mobile.tsx @@ -7,6 +7,13 @@ import { Money, Numpad, Tabs } from '@deriv/components'; import { getDecimalPlaces, isEmptyObject } from '@deriv/shared'; import MinMaxStakeInfo from './min-max-stake-info'; +type TAmountMobile = React.ComponentProps & { + amount_tab_idx?: number; + setAmountTabIdx: React.ComponentProps['onTabItemClick']; + stake_value: string | number; + payout_value: string | number; +}; + type TBasis = { basis: string; duration_unit: string; @@ -141,13 +148,6 @@ const Basis = observer( } ); -type TAmountMobile = React.ComponentProps & { - amount_tab_idx?: number; - setAmountTabIdx: React.ComponentProps['onTabItemClick']; - stake_value: string | number; - payout_value: string | number; -}; - const Amount = observer( ({ toggleModal, From a969cb5f0e3184f53e2b51d5fa3b418fca8011b4 Mon Sep 17 00:00:00 2001 From: kate-deriv Date: Mon, 2 Oct 2023 15:41:46 +0300 Subject: [PATCH 52/55] fix: types in tests --- .../Components/Elements/__tests__/purchase-button.spec.tsx | 2 +- .../Components/Elements/__tests__/purchase-fieldset.spec.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/trader/src/Modules/Trading/Components/Elements/__tests__/purchase-button.spec.tsx b/packages/trader/src/Modules/Trading/Components/Elements/__tests__/purchase-button.spec.tsx index 6fbdf17615dd..f8d1f5e18593 100644 --- a/packages/trader/src/Modules/Trading/Components/Elements/__tests__/purchase-button.spec.tsx +++ b/packages/trader/src/Modules/Trading/Components/Elements/__tests__/purchase-button.spec.tsx @@ -31,7 +31,7 @@ const default_mocked_props = { should_fade: false, setPurchaseState: jest.fn(), type: 'VANILLALONGCALL', -}; +} as unknown as React.ComponentProps; jest.mock('@deriv/shared', () => ({ ...jest.requireActual('@deriv/shared'), diff --git a/packages/trader/src/Modules/Trading/Components/Elements/__tests__/purchase-fieldset.spec.tsx b/packages/trader/src/Modules/Trading/Components/Elements/__tests__/purchase-fieldset.spec.tsx index aad213c17fc1..f48114e67eb9 100644 --- a/packages/trader/src/Modules/Trading/Components/Elements/__tests__/purchase-fieldset.spec.tsx +++ b/packages/trader/src/Modules/Trading/Components/Elements/__tests__/purchase-fieldset.spec.tsx @@ -33,7 +33,7 @@ const default_mocked_props = { purchased_states_arr: [true, false], setPurchaseState: jest.fn(), type: '', -}; +} as unknown as React.ComponentProps; jest.mock('@deriv/shared', () => ({ ...jest.requireActual('@deriv/shared'), From b11012fb9c6ae677090e55bd7c0e1373a947dafe Mon Sep 17 00:00:00 2001 From: kate-deriv Date: Mon, 2 Oct 2023 16:03:47 +0300 Subject: [PATCH 53/55] fix: test extention --- .../Trading/Components/Form/__tests__/screen-large.spec.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/trader/src/Modules/Trading/Components/Form/__tests__/screen-large.spec.tsx b/packages/trader/src/Modules/Trading/Components/Form/__tests__/screen-large.spec.tsx index e55433a5e35b..f824ccd64cf6 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/__tests__/screen-large.spec.tsx +++ b/packages/trader/src/Modules/Trading/Components/Form/__tests__/screen-large.spec.tsx @@ -8,7 +8,7 @@ jest.mock('App/Components/Elements/ContentLoader', () => ({ })); jest.mock('../../../Containers/contract-type', () => jest.fn(() => 'MockedContractType')); jest.mock('../../../Containers/purchase', () => jest.fn(() => 'MockedPurchase')); -jest.mock('../../../Containers/trade-params.jsx', () => jest.fn(() => 'MockedTradeParams')); +jest.mock('../../../Containers/trade-params', () => jest.fn(() => 'MockedTradeParams')); const mock_props = { is_market_closed: false, From 61ef36f22c5facffd9f13309e31d3e70086d89f6 Mon Sep 17 00:00:00 2001 From: kate-deriv Date: Tue, 3 Oct 2023 10:12:13 +0300 Subject: [PATCH 54/55] fix: wallet file --- .../src/components/WalletTransactions/WalletTransactions.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/wallets/src/components/WalletTransactions/WalletTransactions.tsx b/packages/wallets/src/components/WalletTransactions/WalletTransactions.tsx index bdb02fb8afd1..27b482a8dc87 100644 --- a/packages/wallets/src/components/WalletTransactions/WalletTransactions.tsx +++ b/packages/wallets/src/components/WalletTransactions/WalletTransactions.tsx @@ -5,8 +5,8 @@ import './WalletTransactions.scss'; const WalletTransactions = () => { const [filterValue, setFilterValue] = - useState['filter']>(undefined); - const [isPendingActive, setIsPendingActive] = useState(true); + React.useState['filter']>(undefined); + const [isPendingActive, setIsPendingActive] = React.useState(true); return (
From 9f6924ab8a0b4ad79d1d8f4abe9a80435785b3b3 Mon Sep 17 00:00:00 2001 From: kate-deriv Date: Wed, 4 Oct 2023 15:55:34 +0300 Subject: [PATCH 55/55] fix: add turbos to types --- packages/stores/src/mockStore.ts | 1 + packages/stores/types.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/stores/src/mockStore.ts b/packages/stores/src/mockStore.ts index ba20d2f31c87..be3884422813 100644 --- a/packages/stores/src/mockStore.ts +++ b/packages/stores/src/mockStore.ts @@ -478,6 +478,7 @@ const mock = (): TStores & { is_mock: boolean } => { is_loading: false, is_accumulator: false, is_multiplier: false, + is_turbos: false, onBuyResponse: jest.fn(), onClickCancel: jest.fn(), onClickSell: jest.fn(), diff --git a/packages/stores/types.ts b/packages/stores/types.ts index 5ad12bb601b0..7d3d8555799d 100644 --- a/packages/stores/types.ts +++ b/packages/stores/types.ts @@ -585,6 +585,7 @@ type TPortfolioStore = { is_loading: boolean; is_multiplier: boolean; is_accumulator: boolean; + is_turbos: boolean; onBuyResponse: (contract_info: { contract_id: number; longcode: string; contract_type: string }) => void; onClickCancel: (contract_id?: number) => void; onClickSell: (contract_id?: number) => void;