diff --git a/packages/components/src/components/collapsible/collapsible.tsx b/packages/components/src/components/collapsible/collapsible.tsx index 341f927cbf0f..65bbdd93901a 100644 --- a/packages/components/src/components/collapsible/collapsible.tsx +++ b/packages/components/src/components/collapsible/collapsible.tsx @@ -4,11 +4,11 @@ import { useSwipeable } from 'react-swipeable'; import ArrowButton from './arrow-button'; type TCollapsible = { - as: React.ElementType; + as?: React.ElementType; is_collapsed?: boolean; position?: 'top' | 'bottom'; onClick: (state: boolean) => void; - title: string; + title?: string; }; const swipe_config = { diff --git a/packages/components/src/components/dialog/dialog.tsx b/packages/components/src/components/dialog/dialog.tsx index 1acf950382f8..e45ceba656d4 100644 --- a/packages/components/src/components/dialog/dialog.tsx +++ b/packages/components/src/components/dialog/dialog.tsx @@ -98,7 +98,7 @@ const Dialog = ({ } }; - const validateClickOutside = () => dismissable || !!(has_close_icon && is_visible && is_closed_on_cancel); + const validateClickOutside = () => !!(dismissable || (has_close_icon && is_visible && is_closed_on_cancel)); useOnClickOutside(wrapper_ref, handleClose, validateClickOutside); diff --git a/packages/shared/src/utils/helpers/__tests__/start-date.ts b/packages/shared/src/utils/helpers/__tests__/start-date.ts index 504c4b36eb34..30c20b80ab0c 100644 --- a/packages/shared/src/utils/helpers/__tests__/start-date.ts +++ b/packages/shared/src/utils/helpers/__tests__/start-date.ts @@ -1,9 +1,10 @@ +import { ContractsFor } from '@deriv/api-types'; import { buildForwardStartingConfig } from '../start-date'; describe('start_date', () => { describe('buildForwardStartingConfig', () => { it('Returns empty object when forward_starting_options and forward_starting_dates are both empties', () => { - const contract = { + const contract: ContractsFor['available'][number] = { barrier_category: 'euro_atm', barriers: 0, contract_category: 'callput', @@ -19,7 +20,8 @@ describe('start_date', () => { start_type: 'spot', submarket: 'major_pairs', underlying_symbol: 'frxAUDJPY', - forward_starting_options: [], + forward_starting_options: + [] as unknown as ContractsFor['available'][number]['forward_starting_options'], }; /* eslint-disable no-unused-expressions */ expect(buildForwardStartingConfig(contract, [])).toHaveLength(0); diff --git a/packages/shared/src/utils/helpers/start-date.ts b/packages/shared/src/utils/helpers/start-date.ts index 250d4337b988..a96a694f4290 100644 --- a/packages/shared/src/utils/helpers/start-date.ts +++ b/packages/shared/src/utils/helpers/start-date.ts @@ -1,16 +1,6 @@ import moment from 'moment'; import { toMoment } from '../date'; - -type TForwardStartingDates = { - blackouts?: unknown[]; - close?: string; - date: string; - open?: string; -}; - -type TContract = { - forward_starting_options: TForwardStartingDates[]; -}; +import { ContractsFor } from '@deriv/api-types'; type TConfig = { text: string; @@ -21,19 +11,22 @@ type TConfig = { }[]; }[]; -export const buildForwardStartingConfig = (contract: TContract, forward_starting_dates: TForwardStartingDates[]) => { +export const buildForwardStartingConfig = ( + contract: ContractsFor['available'][number], + forward_starting_dates?: TConfig +) => { const forward_starting_config: TConfig = []; if ((contract.forward_starting_options || []).length) { - contract.forward_starting_options.forEach(option => { - const duplicated_option = forward_starting_config.find(opt => opt.value === parseInt(option.date)); + (contract.forward_starting_options ?? []).forEach(option => { + const duplicated_option = forward_starting_config.find(opt => opt.value === parseInt(option.date ?? '')); const current_session = { open: toMoment(option.open), close: toMoment(option.close) }; if (duplicated_option) { duplicated_option.sessions.push(current_session); } else { forward_starting_config.push({ text: toMoment(option.date).format('ddd - DD MMM, YYYY'), - value: parseInt(option.date), + value: parseInt(option.date ?? ''), sessions: [current_session], }); } diff --git a/packages/stores/src/mockStore.ts b/packages/stores/src/mockStore.ts index 5f2ac3b5acdc..69a4afa8ea74 100644 --- a/packages/stores/src/mockStore.ts +++ b/packages/stores/src/mockStore.ts @@ -328,9 +328,9 @@ const mock = (): TStores & { is_mock: boolean } => { setPurchaseState: jest.fn(), shouldNavigateAfterChooseCrypto: jest.fn(), toggleLanguageSettingsModal: jest.fn(), + toggleServicesErrorModal: jest.fn(), toggleLinkExpiredModal: jest.fn(), toggleSetCurrencyModal: jest.fn(), - toggleServicesErrorModal: jest.fn(), addToast: jest.fn(), removeToast: jest.fn(), reports_route_tab_index: 1, @@ -513,6 +513,10 @@ const mock = (): TStores & { is_mock: boolean } => { eventHandler: jest.fn(), setLoginFlag: jest.fn(), }, + pushwoosh: {}, + contract_replay: {}, + chart_barrier_store: {}, + active_symbols: {}, }; }; diff --git a/packages/stores/types.ts b/packages/stores/types.ts index f85af2ec59cb..f120f8a25402 100644 --- a/packages/stores/types.ts +++ b/packages/stores/types.ts @@ -423,6 +423,11 @@ type TCommonStoreError = { should_show_refresh: boolean; type?: string; }; +type TCommonStoreServicesError = { + code?: string; + message?: string; + type?: string; +}; type TCommonStore = { isCurrentLanguage(language_code: string): boolean; @@ -438,8 +443,8 @@ type TCommonStore = { changeSelectedLanguage: (key: string) => void; current_language: string; is_language_changing: boolean; + services_error: TCommonStoreServicesError; is_socket_opened: boolean; - services_error: { code: string; message: string; type: string } | Record; setAppstorePlatform: (value: string) => void; app_routing_history: TAppRoutingHistory[]; getExchangeRate: (from_currency: string, to_currency: string) => Promise; @@ -734,6 +739,10 @@ export type TCoreStores = { notifications: TNotificationStore; traders_hub: TTradersHubStore; gtm: TGtmStore; + pushwoosh: Record; + contract_replay: Record; + chart_barrier_store: Record; + active_symbols: Record; }; export type TStores = TCoreStores & { diff --git a/packages/trader/src/App/Components/Elements/Modals/MarketUnavailableModal/index.js b/packages/trader/src/App/Components/Elements/Modals/MarketUnavailableModal/index.js deleted file mode 100644 index 19a00063933a..000000000000 --- a/packages/trader/src/App/Components/Elements/Modals/MarketUnavailableModal/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import MarketUnavailableModal from './market-unavailable.jsx'; - -export default MarketUnavailableModal; diff --git a/packages/trader/src/App/Components/Elements/Modals/MarketUnavailableModal/index.ts b/packages/trader/src/App/Components/Elements/Modals/MarketUnavailableModal/index.ts new file mode 100644 index 000000000000..62c77323d14e --- /dev/null +++ b/packages/trader/src/App/Components/Elements/Modals/MarketUnavailableModal/index.ts @@ -0,0 +1,3 @@ +import MarketUnavailableModal from './market-unavailable'; + +export default MarketUnavailableModal; diff --git a/packages/trader/src/App/Components/Elements/Modals/MarketUnavailableModal/market-unavailable.jsx b/packages/trader/src/App/Components/Elements/Modals/MarketUnavailableModal/market-unavailable.tsx similarity index 82% rename from packages/trader/src/App/Components/Elements/Modals/MarketUnavailableModal/market-unavailable.jsx rename to packages/trader/src/App/Components/Elements/Modals/MarketUnavailableModal/market-unavailable.tsx index 7fbe8ba71f49..9764dad5faea 100644 --- a/packages/trader/src/App/Components/Elements/Modals/MarketUnavailableModal/market-unavailable.jsx +++ b/packages/trader/src/App/Components/Elements/Modals/MarketUnavailableModal/market-unavailable.tsx @@ -1,13 +1,18 @@ -import PropTypes from 'prop-types'; import React from 'react'; import { Dialog } from '@deriv/components'; import { getPlatformSettings } from '@deriv/shared'; import { localize, Localize } from '@deriv/translations'; import { observer, useStore } from '@deriv/stores'; -const MarketUnavailableModal = observer(({ onCancel, onConfirm }) => { +type TMarketUnavailableModalProps = { + is_loading?: boolean; + onCancel: () => void; + onConfirm: () => void; +}; + +const MarketUnavailableModal = observer(({ is_loading, onCancel, onConfirm }: TMarketUnavailableModalProps) => { const { ui } = useStore(); - const { disableApp, enableApp, is_loading, has_only_forward_starting_contracts: is_visible } = ui; + const { disableApp, enableApp, has_only_forward_starting_contracts: is_visible } = ui; return ( { ); }); -MarketUnavailableModal.propTypes = { - onCancel: PropTypes.func, - onConfirm: PropTypes.func, -}; - export default MarketUnavailableModal; diff --git a/packages/trader/src/App/Components/Elements/Modals/ServicesErrorModal/constants.ts b/packages/trader/src/App/Components/Elements/Modals/ServicesErrorModal/constants.ts index a049a53b662d..3f0d29a4ece5 100644 --- a/packages/trader/src/App/Components/Elements/Modals/ServicesErrorModal/constants.ts +++ b/packages/trader/src/App/Components/Elements/Modals/ServicesErrorModal/constants.ts @@ -1,6 +1,6 @@ import { localize } from '@deriv/translations'; -export const getTitle = (type: string) => { +export const getTitle = (type?: string) => { switch (type) { case 'buy': return localize('Purchase Error'); diff --git a/packages/trader/src/App/Components/Elements/Modals/ServicesErrorModal/services-error-modal.tsx b/packages/trader/src/App/Components/Elements/Modals/ServicesErrorModal/services-error-modal.tsx index 1b501c666ef3..392e961c86b1 100644 --- a/packages/trader/src/App/Components/Elements/Modals/ServicesErrorModal/services-error-modal.tsx +++ b/packages/trader/src/App/Components/Elements/Modals/ServicesErrorModal/services-error-modal.tsx @@ -8,9 +8,9 @@ import CompanyWideLimitExceededModal from './company-wide-limit-exceeded-modal'; import AccountVerificationRequiredModal from './account-verification-required-modal'; type TServicesError = { - code: string; - message: string; - type: string; + code?: string; + message?: string; + type?: string; }; type TPropServicesErrorModel = { diff --git a/packages/trader/src/App/Components/Elements/Modals/UnsupportedContractModal/index.js b/packages/trader/src/App/Components/Elements/Modals/UnsupportedContractModal/index.ts similarity index 92% rename from packages/trader/src/App/Components/Elements/Modals/UnsupportedContractModal/index.js rename to packages/trader/src/App/Components/Elements/Modals/UnsupportedContractModal/index.ts index dbddc5419714..f001335a990e 100644 --- a/packages/trader/src/App/Components/Elements/Modals/UnsupportedContractModal/index.js +++ b/packages/trader/src/App/Components/Elements/Modals/UnsupportedContractModal/index.ts @@ -1,3 +1,3 @@ -import UnsupportedContractModal from './unsupported-contract-modal.jsx'; +import UnsupportedContractModal from './unsupported-contract-modal'; export default UnsupportedContractModal; diff --git a/packages/trader/src/App/Components/Elements/Modals/UnsupportedContractModal/unsupported-contract-modal.jsx b/packages/trader/src/App/Components/Elements/Modals/UnsupportedContractModal/unsupported-contract-modal.jsx deleted file mode 100644 index 3cd1b28434ff..000000000000 --- a/packages/trader/src/App/Components/Elements/Modals/UnsupportedContractModal/unsupported-contract-modal.jsx +++ /dev/null @@ -1,35 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import { Dialog } from '@deriv/components'; -import { localize, Localize } from '@deriv/translations'; -import { website_name } from '@deriv/shared'; -import { observer, useStore } from '@deriv/stores'; - -const UnsupportedContractModal = observer(({ onConfirm, onClose }) => { - const { ui } = useStore(); - const { disableApp, enableApp, is_loading, is_unsupported_contract_modal_visible: is_visible } = ui; - - return ( - - - - ); -}); - -UnsupportedContractModal.propTypes = { - onClose: PropTypes.func, - onConfirm: PropTypes.func, -}; - -export default UnsupportedContractModal; diff --git a/packages/trader/src/App/Components/Elements/Modals/UnsupportedContractModal/unsupported-contract-modal.tsx b/packages/trader/src/App/Components/Elements/Modals/UnsupportedContractModal/unsupported-contract-modal.tsx new file mode 100644 index 000000000000..d9e7b9423509 --- /dev/null +++ b/packages/trader/src/App/Components/Elements/Modals/UnsupportedContractModal/unsupported-contract-modal.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { Dialog } from '@deriv/components'; +import { localize, Localize } from '@deriv/translations'; +import { website_name } from '@deriv/shared'; +import { observer, useStore } from '@deriv/stores'; + +type TUnsupportedContractModalProps = { + is_loading?: boolean; + is_visible?: boolean; + onClose: () => void; + onConfirm: () => void; +}; + +const UnsupportedContractModal = observer( + ({ is_loading, is_visible: is_modal_visible, onConfirm, onClose }: TUnsupportedContractModalProps) => { + const { ui } = useStore(); + const { disableApp, enableApp, is_unsupported_contract_modal_visible } = ui; + const is_visible = !!(is_unsupported_contract_modal_visible || is_modal_visible); + + return ( + + + + ); + } +); + +export default UnsupportedContractModal; diff --git a/packages/trader/src/App/Containers/Modals/index.js b/packages/trader/src/App/Containers/Modals/index.js deleted file mode 100644 index 4f99455bb36c..000000000000 --- a/packages/trader/src/App/Containers/Modals/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import TradeModals from './trade-modals.jsx'; - -export default TradeModals; diff --git a/packages/trader/src/App/Containers/Modals/index.ts b/packages/trader/src/App/Containers/Modals/index.ts new file mode 100644 index 000000000000..af8b1307fab3 --- /dev/null +++ b/packages/trader/src/App/Containers/Modals/index.ts @@ -0,0 +1,3 @@ +import TradeModals from './trade-modals'; + +export default TradeModals; diff --git a/packages/trader/src/App/Containers/Modals/trade-modals.jsx b/packages/trader/src/App/Containers/Modals/trade-modals.tsx similarity index 98% rename from packages/trader/src/App/Containers/Modals/trade-modals.jsx rename to packages/trader/src/App/Containers/Modals/trade-modals.tsx index fee15a59ea4b..b70f8241e701 100644 --- a/packages/trader/src/App/Containers/Modals/trade-modals.jsx +++ b/packages/trader/src/App/Containers/Modals/trade-modals.tsx @@ -46,7 +46,7 @@ const TradeModals = observer(() => { const unsupportedContractOnClose = () => { const portfoliows_url = urlFor('user/portfoliows', { legacy: true }); window.open(portfoliows_url, '_blank'); - unsupportedContractOnConfirm(false); + unsupportedContractOnConfirm(); }; return ( 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 2bfad9727298..9dd0c883dfb5 100644 --- a/packages/trader/src/Modules/Trading/Components/Elements/purchase-fieldset.tsx +++ b/packages/trader/src/Modules/Trading/Components/Elements/purchase-fieldset.tsx @@ -19,7 +19,7 @@ type TPurchaseFieldset = { is_disabled: boolean; is_high_low: boolean; is_loading: boolean; - is_market_closed: boolean; + is_market_closed?: boolean; is_multiplier: boolean; is_proposal_empty: boolean; is_proposal_error: boolean; diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Accumulator/accumulators-amount-mobile.tsx b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Accumulator/accumulators-amount-mobile.tsx index f9fc43ebce0f..74113f3e875f 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Accumulator/accumulators-amount-mobile.tsx +++ b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Accumulator/accumulators-amount-mobile.tsx @@ -8,7 +8,7 @@ import { observer, useStore } from '@deriv/stores'; import { useTraderStore } from 'Stores/useTraderStores'; type TAccumulatorsAmountMobile = { - is_nativepicker: boolean; + is_nativepicker?: boolean; }; const AccumulatorsAmountMobile = observer(({ is_nativepicker }: TAccumulatorsAmountMobile) => { diff --git a/packages/trader/src/Modules/Trading/Components/Form/form-layout.tsx b/packages/trader/src/Modules/Trading/Components/Form/form-layout.tsx index ee41fa37ba8e..6786d9fbb34d 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/form-layout.tsx +++ b/packages/trader/src/Modules/Trading/Components/Form/form-layout.tsx @@ -10,7 +10,7 @@ type TFormLayout = { const Screen = Loadable({ loader: () => isMobile() - ? import(/* webpackChunkName: "screen-small" */ './screen-small.jsx') + ? import(/* webpackChunkName: "screen-small" */ './screen-small') : import(/* webpackChunkName: "screen-large" */ './screen-large'), loading: () => null, render(loaded, props) { diff --git a/packages/trader/src/Modules/Trading/Components/Form/screen-small.jsx b/packages/trader/src/Modules/Trading/Components/Form/screen-small.tsx similarity index 88% rename from packages/trader/src/Modules/Trading/Components/Form/screen-small.jsx rename to packages/trader/src/Modules/Trading/Components/Form/screen-small.tsx index a322cf443648..7181dd35f9b4 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/screen-small.jsx +++ b/packages/trader/src/Modules/Trading/Components/Form/screen-small.tsx @@ -1,4 +1,3 @@ -import PropTypes from 'prop-types'; import React from 'react'; import { Collapsible } from '@deriv/components'; import { TradeParamsLoader } from 'App/Components/Elements/ContentLoader'; @@ -30,6 +29,24 @@ import TradeTypeTabs from 'Modules/Trading/Components/Form/TradeParams/trade-typ import { observer } from '@deriv/stores'; import { useTraderStore } from 'Stores/useTraderStores'; +type TCollapsibleTradeParams = Pick< + ReturnType, + | 'form_components' + | 'has_take_profit' + | 'previous_symbol' + | 'is_accumulator' + | 'is_trade_params_expanded' + | 'is_multiplier' + | 'is_vanilla' + | 'is_turbos' + | 'onChange' + | 'take_profit' + | 'setIsTradeParamsExpanded' +> & { + has_allow_equals: boolean; + is_allow_equal: boolean; +}; + const CollapsibleTradeParams = ({ form_components, has_allow_equals, @@ -44,7 +61,7 @@ const CollapsibleTradeParams = ({ onChange, take_profit, setIsTradeParamsExpanded, -}) => { +}: TCollapsibleTradeParams) => { React.useEffect(() => { if (previous_symbol && is_allow_equal && has_allow_equals) setIsTradeParamsExpanded(true); // eslint-disable-next-line react-hooks/exhaustive-deps @@ -52,7 +69,7 @@ const CollapsibleTradeParams = ({ const is_collapsed = !is_trade_params_expanded; - const onClick = e => { + const onClick = (e: boolean) => { setIsTradeParamsExpanded(e); }; @@ -60,7 +77,7 @@ const CollapsibleTradeParams = ({ setIsTradeParamsExpanded(true); }; - const isVisible = component => form_components.includes(component); + const isVisible = (component: string) => form_components.includes(component); return ( @@ -91,7 +108,8 @@ const CollapsibleTradeParams = ({ )} - + {/* + // @ts-expect-error Observer wrapped component needs to be ts migrated before props can be detected */} {!is_accumulator && } {has_allow_equals && (
@@ -111,6 +129,7 @@ const CollapsibleTradeParams = ({ className={classNames('take-profit', 'mobile-widget')} > { +const ScreenSmall = observer(({ is_trade_enabled }: { is_trade_enabled: boolean }) => { const trade_store = useTraderStore(); const { is_accumulator, @@ -190,8 +209,4 @@ const ScreenSmall = observer(({ is_trade_enabled }) => { ); }); -ScreenSmall.propTypes = { - is_trade_enabled: PropTypes.bool, -}; - export default ScreenSmall; diff --git a/packages/trader/src/Modules/Trading/Containers/purchase.tsx b/packages/trader/src/Modules/Trading/Containers/purchase.tsx index bd8005f43760..f7e078770dd3 100644 --- a/packages/trader/src/Modules/Trading/Containers/purchase.tsx +++ b/packages/trader/src/Modules/Trading/Containers/purchase.tsx @@ -27,7 +27,7 @@ const getSortedIndex = (type: string, index: number) => { } }; -const Purchase = observer(({ is_market_closed }: { is_market_closed: boolean }) => { +const Purchase = observer(({ is_market_closed }: { is_market_closed?: boolean }) => { const { portfolio: { all_positions, onClickSell }, ui: { purchase_states: purchased_states_arr, is_mobile, setPurchaseState }, diff --git a/packages/trader/src/Stores/Modules/Trading/Actions/contract-type.ts b/packages/trader/src/Stores/Modules/Trading/Actions/contract-type.ts index c5552ff614bb..cbd0c8f89579 100644 --- a/packages/trader/src/Stores/Modules/Trading/Actions/contract-type.ts +++ b/packages/trader/src/Stores/Modules/Trading/Actions/contract-type.ts @@ -1,34 +1,10 @@ import { ContractType } from 'Stores/Modules/Trading/Helpers/contract-type'; import { TTradeStore } from 'Types'; -type TOnChangeContractTypeList = (store: TTradeStore) => { - contract_type: string; -}; - -type TContractValues = Pick< - TTradeStore, - | 'form_components' - | 'basis_list' - | 'basis' - | 'trade_types' - | 'start_date' - | 'start_dates_list' - | 'contract_start_type' - | 'barrier_count' - | 'barrier_1' - | 'barrier_2' - | 'duration_unit' - | 'duration_units_list' - | 'duration_min_max' - | 'expiry_type' - | 'accumulator_range_list' - | 'multiplier_range_list' - | 'cancellation_range_list' ->; - -export const onChangeContractTypeList: TOnChangeContractTypeList = ({ contract_types_list, contract_type }) => { +export const onChangeContractTypeList = ({ contract_types_list, contract_type }: TTradeStore) => { return ContractType.getContractType(contract_types_list, contract_type); }; -export const onChangeContractType = (store: TTradeStore): TContractValues => { + +export const onChangeContractType = (store: TTradeStore) => { return ContractType.getContractValues(store); }; 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..9be02fc3e422 100644 --- a/packages/trader/src/Stores/Modules/Trading/Actions/start-date.ts +++ b/packages/trader/src/Stores/Modules/Trading/Actions/start-date.ts @@ -1,32 +1,12 @@ import { ContractType } from 'Stores/Modules/Trading/Helpers/contract-type'; import { TTradeStore } from 'Types'; -type TOnChangeStartDate = ( - store: TTradeStore -) => Promise< - Pick< - TTradeStore, - | 'contract_start_type' - | 'duration_units_list' - | 'duration_min_max' - | 'duration_unit' - | 'start_time' - | 'expiry_date' - | 'expiry_type' - > & { sessions?: TTradeStore['sessions'] } ->; -type TOnChangeExpiry = (store: TTradeStore) => Promise<{ - expiry_time: TTradeStore['expiry_time']; - market_open_times?: TTradeStore['market_open_times']; - market_close_times?: TTradeStore['market_close_times']; -}>; - -export const onChangeStartDate: TOnChangeStartDate = async store => { +export const onChangeStartDate = async (store: TTradeStore) => { const { contract_type, duration_unit, start_date } = store; - const server_time = store.root_store.common.server_time; + const server_time = store.root_store?.common.server_time; let { start_time, expiry_type } = store; - start_time = start_time || server_time.clone().add(6, 'minute').format('HH:mm'); // when there is not a default value for start_time, it should be set more than 5 min after server_time + start_time = start_time || server_time?.clone().add(6, 'minute').format('HH:mm'); // when there is not a default value for start_time, it should be set more than 5 min after server_time const obj_contract_start_type = ContractType.getStartType(start_date); const contract_start_type = obj_contract_start_type.contract_start_type; @@ -56,7 +36,7 @@ export const onChangeStartDate: TOnChangeStartDate = async store => { }; }; -export const onChangeExpiry: TOnChangeExpiry = async store => { +export const onChangeExpiry = async (store: TTradeStore) => { const { start_time, expiry_date, expiry_type, expiry_time, start_date, symbol, sessions } = store; const trading_times = await ContractType.getTradingTimes(expiry_date, symbol); 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..189936dfe2c9 100644 --- a/packages/trader/src/Stores/Modules/Trading/Helpers/allow-equals.ts +++ b/packages/trader/src/Stores/Modules/Trading/Helpers/allow-equals.ts @@ -5,7 +5,7 @@ import { TTradeStore } from 'Types'; type THasDurationForCallPutEqual = { contract_type_list: TTradeStore['contract_types_list']; - duration_unit: PriceProposalRequest['duration_unit']; + duration_unit: string; contract_start_type: string; }; @@ -13,7 +13,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' ); }; @@ -27,7 +27,7 @@ export const hasDurationForCallPutEqual = ( 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/contract-type.js b/packages/trader/src/Stores/Modules/Trading/Helpers/contract-type.ts similarity index 63% rename from packages/trader/src/Stores/Modules/Trading/Helpers/contract-type.js rename to packages/trader/src/Stores/Modules/Trading/Helpers/contract-type.ts index 99b04c99f481..667e1e93c98b 100644 --- a/packages/trader/src/Stores/Modules/Trading/Helpers/contract-type.js +++ b/packages/trader/src/Stores/Modules/Trading/Helpers/contract-type.ts @@ -20,17 +20,76 @@ import { import ServerTime from '_common/base/server_time'; import { localize } from '@deriv/translations'; import { isSessionAvailable } from './start-date'; +import { ContractsFor, ContractsForSymbolResponse, TradingTimes, TradingTimesResponse } from '@deriv/api-types'; +import { TTradeStore } from '../../../../Types/common-prop.type'; + +type TAvailableCategories = { + [key: string]: { + name: string; + categories: Array; + }; +}; +type TBarriers = Record< + keyof TTradeStore['duration_min_max'], + { + barrier?: string; + high_barrier?: string; + low_barrier?: string; + } +> & { + count: number; +}; +type TConfig = ReturnType[string]['config'] & { + has_spot?: boolean; + durations?: ReturnType; + trade_types?: { [key: string]: string }; + barriers?: ReturnType; + forward_starting_dates?: ReturnType; + growth_rate_range?: number[]; + multiplier_range?: number[]; + cancellation_range?: string[]; + barrier_choices?: string[]; +}; +type TTextValueStrings = { + text: string; + value: string; +}; +type TTimes = { + open: string[]; + close: string[]; +}; +type TEvents = + | { + dates: string; + descrip: string; + }[] + | []; export const ContractType = (() => { - let available_contract_types = {}; - let available_categories = {}; - let contract_types; - const trading_events = {}; - const trading_times = {}; + type TContractValues = ReturnType & + ReturnType & + ReturnType & + ReturnType & + ReturnType & + ReturnType & + ReturnType & + ReturnType & + ReturnType & + ReturnType & + ReturnType & + ReturnType & + ReturnType & + ReturnType; + + let available_contract_types: ReturnType = {}; + let available_categories: TAvailableCategories = {}; + let contract_types: ReturnType; + const trading_events: { [key: string]: Record } = {}; + const trading_times: { [key: string]: Record } = {}; let has_only_forward_starting_contracts = false; - const buildContractTypesConfig = symbol => - WS.storage.contractsFor(symbol).then(r => { + const buildContractTypesConfig = (symbol: string): Promise => + WS.storage.contractsFor(symbol).then((r: Required) => { const has_contracts = getPropertyValue(r, ['contracts_for']); has_only_forward_starting_contracts = has_contracts && !r.contracts_for.available.find(contract => contract.start_type !== 'forward'); @@ -44,7 +103,7 @@ export const ContractType = (() => { key => contract_types[key].trade_types.indexOf(contract.contract_type) !== -1 && (typeof contract_types[key].barrier_count === 'undefined' || - +contract_types[key].barrier_count === contract.barriers) // To distinguish betweeen Rise/Fall & Higher/Lower + Number(contract_types[key].barrier_count) === contract.barriers) // To distinguish betweeen Rise/Fall & Higher/Lower ); if (!type) return; // ignore unsupported contract types @@ -55,28 +114,28 @@ export const ContractType = (() => { available_categories[ Object.keys(available_categories).find( key => available_categories[key].categories.indexOf(type) !== -1 - ) + ) ?? '' ].categories; if (!sub_cats) return; - sub_cats[sub_cats.indexOf(type)] = { value: type, text: contract_types[type].title }; + sub_cats[(sub_cats as string[]).indexOf(type)] = { value: type, text: contract_types[type].title }; // populate available contract types available_contract_types[type] = cloneObject(contract_types[type]); } - const config = available_contract_types[type].config || {}; + const config: TConfig = available_contract_types[type].config || {}; // set config values config.has_spot = config.has_spot || contract.start_type === 'spot'; - config.durations = !config.hide_duration && buildDurationConfig(contract, config.durations); + config.durations = config.hide_duration ? undefined : buildDurationConfig(contract, config.durations); config.trade_types = buildTradeTypesConfig(contract, config.trade_types); config.barriers = buildBarriersConfig(contract, config.barriers); - config.barrier_choices = contract.barrier_choices; + config.barrier_choices = contract.barrier_choices as TConfig['barrier_choices']; config.forward_starting_dates = buildForwardStartingConfig(contract, config.forward_starting_dates); - config.growth_rate_range = contract.growth_rate_range; - config.multiplier_range = contract.multiplier_range; - config.cancellation_range = contract.cancellation_range; + config.growth_rate_range = contract.growth_rate_range as TConfig['growth_rate_range']; + config.multiplier_range = contract.multiplier_range as TConfig['multiplier_range']; + config.cancellation_range = contract.cancellation_range as TConfig['cancellation_range']; available_contract_types[type].config = config; }); @@ -92,15 +151,18 @@ export const ContractType = (() => { }); }); - const buildTradeTypesConfig = (contract, trade_types = {}) => { - trade_types[contract.contract_type] = contract.contract_display; + const buildTradeTypesConfig = ( + contract: ContractsFor['available'][number], + trade_types: { [key: string]: string } = {} + ) => { + trade_types[contract.contract_type] = contract.contract_display ?? ''; return trade_types; }; - const getArrayDefaultValue = (arr_new_values, value) => + const getArrayDefaultValue = (arr_new_values: Array, value: string | number) => arr_new_values.indexOf(value) !== -1 ? value : arr_new_values[0]; - const getContractValues = store => { + const getContractValues = (store: TTradeStore): TContractValues | Record => { const { contract_expiry_type, contract_type, @@ -117,7 +179,7 @@ export const ContractType = (() => { if (!contract_type) return {}; - let stored_barriers_data = {}; + let stored_barriers_data: TTradeStore['short_barriers' | 'long_barriers'] = {}; if (getContractSubtype(contract_type) === 'Short') { stored_barriers_data = short_barriers; } else if (getContractSubtype(contract_type) === 'Long') { @@ -129,16 +191,16 @@ export const ContractType = (() => { const obj_trade_types = getTradeTypes(contract_type); const obj_start_dates = getStartDates(contract_type, start_date); const obj_start_type = getStartType(obj_start_dates.start_date); - const obj_barrier = getBarriers(contract_type, contract_expiry_type, stored_barriers_data.barrier); + const obj_barrier = getBarriers(contract_type, contract_expiry_type, stored_barriers_data?.barrier); const obj_duration_unit = getDurationUnit(duration_unit, contract_type, obj_start_type.contract_start_type); const obj_duration_units_list = getDurationUnitsList(contract_type, obj_start_type.contract_start_type); const obj_duration_units_min_max = getDurationMinMax(contract_type, obj_start_type.contract_start_type); const obj_accumulator_range_list = getAccumulatorRange(contract_type); - const obj_barrier_choices = getBarrierChoices(contract_type, stored_barriers_data.barrier_choices); + const obj_barrier_choices = getBarrierChoices(contract_type, stored_barriers_data?.barrier_choices); const obj_multiplier_range_list = getMultiplierRange(contract_type, multiplier); const obj_cancellation = getCancellation(contract_type, cancellation_duration, symbol); - const obj_expiry_type = getExpiryType(obj_duration_units_list, expiry_type); + const obj_expiry_type = getExpiryType(obj_duration_units_list.duration_units_list, expiry_type); const obj_equal = getEqualProps(contract_type); return { @@ -160,9 +222,9 @@ export const ContractType = (() => { }; }; - const getContractType = (list, contract_type) => { - const arr_list = Object.keys(list || {}) - .reduce((k, l) => [...k, ...list[l].categories.map(ct => ct.value)], []) + const getContractType = (list: TAvailableCategories, contract_type: string) => { + const arr_list: string[] = Object.keys(list || {}) + .reduce((k, l) => [...k, ...(list[l].categories as TTextValueStrings[]).map(ct => ct.value)], []) .filter(type => unsupported_contract_types_list.indexOf(type) === -1) .sort((a, b) => (a === 'multiplier' || b === 'multiplier' ? -1 : 0)); @@ -171,7 +233,7 @@ export const ContractType = (() => { }; }; - const getComponents = c_type => { + const getComponents = (c_type: string) => { let check = []; if (contract_types[c_type]?.config?.should_override) { check = [...contract_types[c_type].components]; @@ -181,7 +243,7 @@ export const ContractType = (() => { !( component === 'duration' && contract_types[c_type].config && - contract_types[c_type].config.hide_duration + (contract_types[c_type].config as TConfig).hide_duration ) ); } @@ -192,29 +254,29 @@ export const ContractType = (() => { ); }; - const getDurationUnitsList = (contract_type, contract_start_type) => { + const getDurationUnitsList = (contract_type: string, contract_start_type: string) => { return { duration_units_list: - getPropertyValue(available_contract_types, [ + (getPropertyValue(available_contract_types, [ contract_type, 'config', 'durations', 'units_display', contract_start_type, - ]) || [], + ]) as TTextValueStrings[]) || [], }; }; - const getDurationUnit = (duration_unit, contract_type, contract_start_type) => { + const getDurationUnit = (duration_unit: string, contract_type: string, contract_start_type: string) => { const duration_units = - getPropertyValue(available_contract_types, [ + (getPropertyValue(available_contract_types, [ contract_type, 'config', 'durations', 'units_display', contract_start_type, - ]) || []; - const arr_units = []; + ]) as TTextValueStrings[]) || []; + const arr_units: string[] = []; duration_units.forEach(obj => { arr_units.push(obj.value); }); @@ -224,8 +286,8 @@ export const ContractType = (() => { }; }; - const getDurationMinMax = (contract_type, contract_start_type, contract_expiry_type) => { - let duration_min_max = + 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] = getPropertyValue(available_contract_types, [ contract_type, 'config', @@ -235,7 +297,7 @@ export const ContractType = (() => { ]) || {}; if (contract_expiry_type) { - duration_min_max = duration_min_max[contract_expiry_type] || {}; + duration_min_max = 'contract_expiry_type' in duration_min_max ? duration_min_max[contract_expiry_type] : {}; } return { duration_min_max }; @@ -243,13 +305,13 @@ export const ContractType = (() => { const getFullContractTypes = () => available_contract_types; - const getStartType = start_date => ({ + const getStartType = (start_date: number) => ({ // Number(0) refers to 'now' contract_start_type: start_date === Number(0) ? 'spot' : 'forward', }); - const getStartDates = (contract_type, current_start_date) => { - const config = getPropertyValue(available_contract_types, [contract_type, 'config']); + const getStartDates = (contract_type: string, current_start_date: number) => { + const config: TConfig = getPropertyValue(available_contract_types, [contract_type, 'config']); const start_dates_list = []; if (config?.has_spot) { @@ -267,70 +329,85 @@ export const ContractType = (() => { return { start_date, start_dates_list }; }; - const getSessions = (contract_type, start_date) => { - const config = getPropertyValue(available_contract_types, [contract_type, 'config']) || {}; - const sessions = ((config.forward_starting_dates || []).find(option => option.value === start_date) || {}) - .sessions; + const getSessions = (contract_type: string, start_date: number) => { + const config: TConfig = getPropertyValue(available_contract_types, [contract_type, 'config']) || {}; + const sessions = config.forward_starting_dates?.find(option => option.value === start_date)?.sessions; return { sessions }; }; const hours = [...Array(24).keys()].map(a => `0${a}`.slice(-2)); const minutes = [...Array(12).keys()].map(a => `0${a * 5}`.slice(-2)); - const getValidTime = (sessions, compare_moment, start_moment) => { + const getValidTime = ( + sessions: ReturnType['sessions'], + compare_moment: moment.Moment, + start_moment?: moment.Moment + ) => { if (sessions && !isSessionAvailable(sessions, compare_moment)) { // first see if changing the minute brings it to the right session compare_moment.minute( - minutes.find(m => isSessionAvailable(sessions, compare_moment.minute(m))) || compare_moment.format('mm') + Number( + minutes.find(m => isSessionAvailable(sessions, compare_moment.minute(+m))) || + compare_moment.format('mm') + ) ); // if not, also change the hour if (!isSessionAvailable(sessions, compare_moment)) { compare_moment.minute(0); compare_moment.hour( - hours.find(h => isSessionAvailable(sessions, compare_moment.hour(h), start_moment, true)) || - compare_moment.format('HH') + Number( + hours.find(h => isSessionAvailable(sessions, compare_moment.hour(+h), start_moment, true)) || + compare_moment.format('HH') + ) ); compare_moment.minute( - minutes.find(m => isSessionAvailable(sessions, compare_moment.minute(m))) || - compare_moment.format('mm') + Number( + minutes.find(m => isSessionAvailable(sessions, compare_moment.minute(+m))) || + compare_moment.format('mm') + ) ); } } return compare_moment.format('HH:mm'); }; - const buildMoment = (date, time) => { - const [hour, minute] = isTimeValid(time) ? time.split(':') : [0, 0]; + const buildMoment = (date: string | number | null, time?: string | null) => { + const [hour, minute] = isTimeValid(time ?? '') ? time?.split(':') ?? [] : [0, 0]; return toMoment(date || ServerTime.get()) - .hour(hour) - .minute(minute); + .hour(+hour) + .minute(+minute); }; - const getStartTime = (sessions, start_date, start_time) => ({ + const getStartTime = ( + sessions: ReturnType['sessions'], + start_date: number, + start_time?: string | null + ) => ({ start_time: start_date ? getValidTime(sessions, buildMoment(start_date, start_time)) : null, }); - const getTradingEvents = async (date, underlying = null) => { + const getTradingEvents = async (date: string, underlying: string | null = null) => { if (!date) { return []; } if (!(date in trading_events)) { - const trading_times_response = await WS.tradingTimes(date); - + const trading_times_response: TradingTimesResponse = await WS.tradingTimes(date); + const trading_times_data = trading_times_response.trading_times as TradingTimes; if (getPropertyValue(trading_times_response, ['trading_times', 'markets'])) { - for (let i = 0; i < trading_times_response.trading_times.markets.length; i++) { - const submarkets = trading_times_response.trading_times.markets[i].submarkets; + for (let i = 0; i < trading_times_data.markets.length; i++) { + const submarkets = trading_times_data.markets[i].submarkets; if (submarkets) { for (let j = 0; j < submarkets.length; j++) { const symbols = submarkets[j].symbols; if (symbols) { for (let k = 0; k < symbols.length; k++) { const symbol = symbols[k]; - if (!trading_events[trading_times_response.echo_req.trading_times]) { - trading_events[trading_times_response.echo_req.trading_times] = {}; + if (!trading_events[trading_times_response.echo_req.trading_times as string]) { + trading_events[trading_times_response.echo_req.trading_times as string] = {}; } - trading_events[trading_times_response.echo_req.trading_times][symbol.symbol] = - symbol.events; + trading_events[trading_times_response.echo_req.trading_times as string][ + symbol.symbol + ] = symbol.events as TEvents; } } } @@ -339,32 +416,37 @@ export const ContractType = (() => { } } - return trading_events[date][underlying]; + return trading_events[date][underlying as string]; }; - const getTradingTimes = async (date, underlying = null) => { + const getTradingTimes = async ( + date: string | null, + underlying: string | null = null + ): Promise | TTimes | Record> => { if (!date) { - return []; + return {}; } if (!(date in trading_times)) { - const trading_times_response = await WS.tradingTimes(date); - + const trading_times_response: TradingTimesResponse = await WS.tradingTimes(date); + const trading_times_data = trading_times_response.trading_times as TradingTimes; if (getPropertyValue(trading_times_response, ['trading_times', 'markets'])) { - for (let i = 0; i < trading_times_response.trading_times.markets.length; i++) { - const submarkets = trading_times_response.trading_times.markets[i].submarkets; + for (let i = 0; i < trading_times_data.markets.length; i++) { + const submarkets = trading_times_data.markets[i].submarkets; if (submarkets) { for (let j = 0; j < submarkets.length; j++) { const symbols = submarkets[j].symbols; if (symbols) { for (let k = 0; k < symbols.length; k++) { const symbol = symbols[k]; - if (!trading_times[trading_times_response.echo_req.trading_times]) { - trading_times[trading_times_response.echo_req.trading_times] = {}; + if (!trading_times[trading_times_response.echo_req.trading_times as string]) { + trading_times[trading_times_response.echo_req.trading_times as string] = {}; } - trading_times[trading_times_response.echo_req.trading_times][symbol.symbol] = { - open: symbol.times.open, - close: symbol.times.close, + trading_times[trading_times_response.echo_req.trading_times as string][ + symbol.symbol + ] = { + open: (symbol.times as TTimes).open, + close: (symbol.times as TTimes).close, }; } } @@ -377,7 +459,10 @@ export const ContractType = (() => { return underlying ? trading_times[date][underlying] : trading_times[date]; }; - const getExpiryType = (duration_units_list, expiry_type) => { + const getExpiryType = ( + duration_units_list: ReturnType['duration_units_list'], + expiry_type: string | null + ) => { if (duration_units_list) { if ( (!expiry_type && duration_units_list.length > 0) || @@ -395,7 +480,12 @@ export const ContractType = (() => { return { expiry_type }; }; - const getExpiryDate = (duration_units_list, expiry_date, expiry_type, start_date) => { + const getExpiryDate = ( + duration_units_list: ReturnType['duration_units_list'], + expiry_date: string | null, + expiry_type: string | null, + start_date: number + ) => { let proper_expiry_date = null; if (expiry_type === 'endtime') { @@ -423,20 +513,20 @@ export const ContractType = (() => { // first check if end time is within available sessions // then confirm that end time is at least 5 minute after start time const getExpiryTime = ( - expiry_date, - expiry_time, - expiry_type, - market_close_times, - sessions, - start_date, - start_time + expiry_date: string | null, + expiry_time: string | null, + expiry_type: string | null, + market_close_times: string[] | undefined | TTimes, + sessions: TTradeStore['sessions'], + start_date: number, + start_time?: string | null ) => { - let end_time = null; + let end_time: moment.Moment | string | null = null; if (expiry_type === 'endtime') { let market_close_time = '23:59:59'; - if (market_close_times && market_close_times.length && market_close_times[0] !== '--') { + if (Array.isArray(market_close_times) && market_close_times?.length && market_close_times[0] !== '--') { // Some of underlyings (e.g. Australian Index) have two close time during a day so we always select the further one as the end time of the contract. market_close_time = market_close_times.slice(-1)[0]; } @@ -469,7 +559,7 @@ export const ContractType = (() => { sessions && !isSessionAvailable(sessions, start_moment.clone().add(5, 'minutes')); end_time = start_moment.clone().add(is_end_of_day || is_end_of_session ? 0 : 5, 'minutes'); // Set the end_time to be multiple of 5 to be equal as the SELECTED_TIME that shown to the client. - end_time = setMinuteMultipleByFive(end_time).format('HH:mm'); + end_time = setMinuteMultipleByFive(end_time as moment.Moment).format('HH:mm'); } // Set the expiry_time to 5 minute less than start_time for forwading contracts when the expiry_time is null and the expiry_date is tomorrow. if (end_time === '00:00' && start_moment.isBefore(end_moment, 'day')) { @@ -480,14 +570,16 @@ export const ContractType = (() => { return { expiry_time: end_time }; }; - const setMinuteMultipleByFive = moment_obj => moment_obj.minute(Math.ceil(moment_obj.minute() / 5) * 5); + const setMinuteMultipleByFive = (moment_obj: moment.Moment) => + moment_obj.minute(Math.ceil(moment_obj.minute() / 5) * 5); - const getTradeTypes = contract_type => ({ - trade_types: getPropertyValue(available_contract_types, [contract_type, 'config', 'trade_types']), + const getTradeTypes = (contract_type: string) => ({ + trade_types: getPropertyValue(available_contract_types, [contract_type, 'config', 'trade_types']) as string[], }); - const getBarriers = (contract_type, expiry_type, stored_barrier_value) => { - const barriers = getPropertyValue(available_contract_types, [contract_type, 'config', 'barriers']) || {}; + const getBarriers = (contract_type: string, expiry_type: string, stored_barrier_value?: string) => { + const barriers = + (getPropertyValue(available_contract_types, [contract_type, 'config', 'barriers']) as TBarriers) || {}; const barrier_values = barriers[expiry_type] || {}; const barrier_1 = barrier_values.barrier || barrier_values.high_barrier || ''; const barrier_2 = barrier_values.low_barrier || ''; @@ -498,10 +590,13 @@ export const ContractType = (() => { }; }; - const getBasis = (contract_type, basis) => { - const arr_basis = getPropertyValue(available_contract_types, [contract_type, 'basis']) || []; + const getBasis = (contract_type: string, basis: string) => { + const arr_basis: string[] = getPropertyValue(available_contract_types, [contract_type, 'basis']) || []; const localized_basis = getLocalizedBasis(); - const basis_list = arr_basis.reduce((cur, bas) => [...cur, { text: localized_basis[bas], value: bas }], []); + const basis_list = arr_basis.reduce( + (cur, bas) => [...cur, { text: localized_basis[bas as keyof typeof localized_basis], value: bas }], + [] + ); return { basis_list, @@ -509,19 +604,20 @@ export const ContractType = (() => { }; }; - const getAccumulatorRange = contract_type => ({ + const getAccumulatorRange = (contract_type: string) => ({ accumulator_range_list: - getPropertyValue(available_contract_types, [contract_type, 'config', 'growth_rate_range']) || [], + (getPropertyValue(available_contract_types, [contract_type, 'config', 'growth_rate_range']) as number[]) || + [], }); - const getBarrierChoices = (contract_type, stored_barrier_choices = []) => ({ + const getBarrierChoices = (contract_type: string, stored_barrier_choices = [] as string[]) => ({ barrier_choices: stored_barrier_choices.length ? stored_barrier_choices : getPropertyValue(available_contract_types, [contract_type, 'config', 'barrier_choices']) || [], }); - const getMultiplierRange = (contract_type, multiplier) => { - const arr_multiplier = + const getMultiplierRange = (contract_type: string, multiplier: number) => { + const arr_multiplier: number[] = getPropertyValue(available_contract_types, [contract_type, 'config', 'multiplier_range']) || []; return { @@ -530,15 +626,17 @@ export const ContractType = (() => { }; }; - const getCancellation = (contract_type, cancellation_duration, symbol) => { - const arr_cancellation_range = + const getCancellation = (contract_type: string, cancellation_duration: string, symbol: string) => { + const arr_cancellation_range: string[] = getPropertyValue(available_contract_types, [contract_type, 'config', 'cancellation_range']) || []; - const regex = new RegExp('^([0-9]+)|([a-zA-Z]+)$', 'g'); - const getText = str => { - const [duration, unit] = str.match(regex); + const regex = /(^(?:\d){1,})|((?:[a-zA-Z]){1,}$)/g; + const getText = (str: string) => { + const [duration, unit] = str.match(regex) ?? []; const unit_map = getUnitMap(); - return `${duration} ${unit_map[unit].name_plural}`; + const unit_names = unit_map[unit as keyof typeof unit_map]; + const name = 'name_plural' in unit_names ? unit_names.name_plural : unit_names.name; + return `${duration} ${name}`; }; const should_show_cancellation = shouldShowCancellation(symbol); @@ -550,7 +648,7 @@ export const ContractType = (() => { }; }; - const getEqualProps = contract_type => { + const getEqualProps = (contract_type: string) => { const base_contract_type = /^(.*)_equal$/.exec(contract_type)?.[1]; if (base_contract_type && !available_contract_types[base_contract_type]) { diff --git a/packages/trader/src/Stores/base-store.js b/packages/trader/src/Stores/base-store.ts similarity index 73% rename from packages/trader/src/Stores/base-store.js rename to packages/trader/src/Stores/base-store.ts index f2b26b9f8d0b..704efb2175f2 100644 --- a/packages/trader/src/Stores/base-store.js +++ b/packages/trader/src/Stores/base-store.ts @@ -1,7 +1,18 @@ import { action, intercept, observable, reaction, toJS, when, makeObservable } from 'mobx'; import { isProduction, isEmptyObject } from '@deriv/shared'; - +import { TCoreStores } from '@deriv/stores/types'; import Validator from 'Utils/Validator'; +import { getValidationRules } from './Modules/Trading/Constants/validation-rules'; + +type TValidationRules = ReturnType | Record; + +type TBaseStoreOptions = { + root_store?: TCoreStores; + local_storage_properties?: string[]; + session_storage_properties?: string[]; + validation_rules?: TValidationRules; + store_name?: string; +}; /** * BaseStore class is the base class for all defined stores in the application. It handles some stuff such as: @@ -16,34 +27,27 @@ export default class BaseStore { LOCAL_STORAGE: Symbol('LOCAL_STORAGE'), SESSION_STORAGE: Symbol('SESSION_STORAGE'), }); - - validation_errors = {}; - - validation_rules = {}; - - preSwitchAccountDisposer = null; - pre_switch_account_listener = null; - - switchAccountDisposer = null; - switch_account_listener = null; - - logoutDisposer = null; - logout_listener = null; - - clientInitDisposer = null; - client_init_listener = null; - - networkStatusChangeDisposer = null; - network_status_change_listener = null; - - themeChangeDisposer = null; - theme_change_listener = null; - - realAccountSignupEndedDisposer = null; - real_account_signup_ended_listener = null; - + clientInitDisposer: null | (() => void) = null; + client_init_listener: null | (() => Promise) = null; + logoutDisposer: null | (() => void) = null; + logout_listener: null | (() => Promise) = null; + local_storage_properties: string[]; + networkStatusChangeDisposer: null | (() => 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; + 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; + validation_errors: { [key: string]: string[] } = {}; + validation_rules: TValidationRules = {}; /** * Constructor of the base class that gets properties' name of child which should be saved in storages * @@ -54,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 = {}) { + constructor(options: TBaseStoreOptions = {}) { makeObservable(this, { validation_errors: observable, validation_rules: observable, @@ -99,9 +103,7 @@ export default class BaseStore { writable: true, }); - const has_local_or_session_storage = - (local_storage_properties && local_storage_properties.length) || - (session_storage_properties && session_storage_properties.length); + const has_local_or_session_storage = local_storage_properties?.length || session_storage_properties?.length; if (has_local_or_session_storage) { if (!store_name) { @@ -135,15 +137,18 @@ export default class BaseStore { * * @return {Object} Returns a cloned object of the store. */ - getSnapshot(properties) { + getSnapshot(properties: string[]): object { let snapshot = toJS(this); if (!isEmptyObject(this.root_store)) { snapshot.root_store = this.root_store; } - if (properties && properties.length) { - snapshot = properties.reduce((result, p) => Object.assign(result, { [p]: snapshot[p] }), {}); + if (properties?.length) { + snapshot = properties.reduce( + (result, p) => Object.assign(result, { [p]: snapshot[p as keyof this] }), + {} as this + ); } return snapshot; @@ -157,7 +162,7 @@ export default class BaseStore { setupReactionForLocalStorage() { if (this.local_storage_properties.length) { reaction( - () => this.local_storage_properties.map(i => this[i]), + () => this.local_storage_properties.map(i => this[i as keyof this]), () => this.saveToStorage(this.local_storage_properties, BaseStore.STORAGES.LOCAL_STORAGE) ); } @@ -171,7 +176,7 @@ export default class BaseStore { setupReactionForSessionStorage() { if (this.session_storage_properties.length) { reaction( - () => this.session_storage_properties.map(i => this[i]), + () => this.session_storage_properties.map(i => this[i as keyof this]), () => this.saveToStorage(this.session_storage_properties, BaseStore.STORAGES.SESSION_STORAGE) ); } @@ -184,7 +189,7 @@ export default class BaseStore { * @param {Symbol} storage - A symbol object that defines the storage which the snapshot should be stored in it. * */ - saveToStorage(properties, storage) { + saveToStorage(properties: string[] = [], storage = Symbol('')) { const snapshot = JSON.stringify(this.getSnapshot(properties), (key, value) => { if (value !== null) return value; return undefined; @@ -202,12 +207,12 @@ export default class BaseStore { * */ retrieveFromStorage() { - const local_storage_snapshot = JSON.parse(localStorage.getItem(this.store_name, {})); - const session_storage_snapshot = JSON.parse(sessionStorage.getItem(this.store_name, {})); + const local_storage_snapshot = JSON.parse(String(localStorage.getItem(this.store_name))); + const session_storage_snapshot = JSON.parse(String(sessionStorage.getItem(this.store_name))); const snapshot = { ...local_storage_snapshot, ...session_storage_snapshot }; - Object.keys(snapshot).forEach(k => (this[k] = snapshot[k])); + Object.keys(snapshot).forEach(k => (this[k as keyof this] = snapshot[k])); } /** @@ -217,7 +222,7 @@ export default class BaseStore { * @param [{String}] messages - An array of strings that contains validation error messages for the particular property. * */ - setValidationErrorMessages(propertyName, messages) { + setValidationErrorMessages(propertyName: string, messages: string[]) { const is_different = () => !!this.validation_errors[propertyName] .filter(x => !messages.includes(x)) @@ -233,9 +238,9 @@ export default class BaseStore { * @param {object} rules * */ - setValidationRules(rules = {}) { + setValidationRules(rules: TValidationRules = {}): void { Object.keys(rules).forEach(key => { - this.addRule(key, rules[key]); + this.addRule(key as keyof TValidationRules, rules[key as keyof TValidationRules]); }); } @@ -246,10 +251,10 @@ export default class BaseStore { * @param {String} rules * */ - addRule(property, rules) { + addRule(property: T, rules: TValidationRules[T]) { this.validation_rules[property] = rules; - intercept(this, property, change => { + intercept(this, property as unknown as keyof this, change => { this.validateProperty(property, change.newValue); return change; }); @@ -262,16 +267,21 @@ export default class BaseStore { * @param {object} value - The value of the property, it can be undefined. * */ - validateProperty(property, value) { - const trigger = this.validation_rules[property].trigger; - const inputs = { [property]: value !== undefined ? value : this[property] }; - const validation_rules = { [property]: this.validation_rules[property].rules || [] }; + validateProperty(property: string, value: T[keyof T]) { + const validation_rules_for_property = this.validation_rules[property as keyof TValidationRules]; + 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 = { + [property]: 'rules' in validation_rules_for_property ? validation_rules_for_property.rules : [], + }; if (!!trigger && Object.hasOwnProperty.call(this, trigger)) { - inputs[trigger] = this[trigger]; - validation_rules[trigger] = this.validation_rules[trigger].rules || []; + const validation_rules_for_trigger = this.validation_rules[trigger as keyof TValidationRules]; + inputs[trigger] = this[trigger as keyof this]; + validation_rules[trigger] = + 'rules' in validation_rules_for_trigger ? validation_rules_for_trigger.rules : []; } - + // @ts-expect-error TODO: remove this comment when Validator migrated to TS is merged to master const validator = new Validator(inputs, validation_rules, this); validator.isPassed(); @@ -289,7 +299,7 @@ export default class BaseStore { const validation_rules = Object.keys(this.validation_rules); const validation_errors = Object.keys(this.validation_errors); validation_rules.forEach(p => { - this.validateProperty(p, this[p]); + this.validateProperty(p, this[p as keyof this]); }); // Remove keys that are present in error, but not in rules: @@ -300,18 +310,18 @@ export default class BaseStore { }); } - onSwitchAccount(listener) { + onSwitchAccount(listener: null | (() => Promise)): void { if (listener) { 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 && result.then && typeof result.then === 'function') { + 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 { @@ -329,17 +339,17 @@ export default class BaseStore { } } - onPreSwitchAccount(listener) { + onPreSwitchAccount(listener: null | (() => Promise)): void { 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 && result.then && typeof result.then === 'function') { + 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 { @@ -357,15 +367,15 @@ export default class BaseStore { } } - onLogout(listener) { + 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 && result.then && typeof result.then === 'function') { + 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 { @@ -383,15 +393,15 @@ export default class BaseStore { this.logout_listener = listener; } - onClientInit(listener) { + 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 && result.then && typeof result.then === 'function') { + 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 { @@ -409,12 +419,12 @@ export default class BaseStore { this.client_init_listener = listener; } - onNetworkStatusChange(listener) { + 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); + this.network_status_change_listener?.(is_online); } catch (error) { // there is no listener currently active. so we can just ignore the error raised from treating // a null object as a function. Although, in development mode, we throw a console error. @@ -428,12 +438,12 @@ export default class BaseStore { this.network_status_change_listener = listener; } - onThemeChange(listener) { + 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); + this.theme_change_listener?.(is_dark_mode_on); } catch (error) { // there is no listener currently active. so we can just ignore the error raised from treating // a null object as a function. Although, in development mode, we throw a console error. @@ -447,15 +457,15 @@ export default class BaseStore { this.theme_change_listener = listener; } - onRealAccountSignupEnd(listener) { + 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 && result.then && typeof result.then === 'function') { + 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 { @@ -533,11 +543,11 @@ export default class BaseStore { this.disposeRealAccountSignupEnd(); } - assertHasValidCache(loginid, ...reactions) { + 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 = false; + this.partial_fetch_time = 0; } } } diff --git a/packages/trader/src/Stores/index.js b/packages/trader/src/Stores/index.ts similarity index 53% rename from packages/trader/src/Stores/index.js rename to packages/trader/src/Stores/index.ts index 611e7d194e61..24bca29111bd 100644 --- a/packages/trader/src/Stores/index.js +++ b/packages/trader/src/Stores/index.ts @@ -1,7 +1,21 @@ +import { TCoreStores } from '@deriv/stores/types'; import ModulesStore from './Modules'; export default class RootStore { - constructor(core_store) { + client: TCoreStores['client']; + common: TCoreStores['common']; + modules: ModulesStore; + ui: TCoreStores['ui']; + gtm: TCoreStores['gtm']; + pushwoosh: TCoreStores['pushwoosh']; + notifications: TCoreStores['notifications']; + contract_replay: TCoreStores['contract_replay']; + contract_trade: TCoreStores['contract_trade']; + portfolio: TCoreStores['portfolio']; + chart_barrier_store: TCoreStores['chart_barrier_store']; + active_symbols: TCoreStores['active_symbols']; + + constructor(core_store: TCoreStores) { this.client = core_store.client; this.common = core_store.common; this.modules = new ModulesStore(this, core_store); diff --git a/packages/trader/src/Stores/useTraderStores.tsx b/packages/trader/src/Stores/useTraderStores.tsx index 36bb72f5aad4..3e689c9ee72e 100644 --- a/packages/trader/src/Stores/useTraderStores.tsx +++ b/packages/trader/src/Stores/useTraderStores.tsx @@ -9,10 +9,8 @@ type TTextValueStrings = { }; type TContractTypesList = { - [key: string]: { - name: string; - categories: TTextValueStrings[]; - }; + name: string; + categories: TTextValueStrings[]; }; type TContractCategoriesList = { @@ -63,9 +61,13 @@ type TOverrideTradeStore = Omit< | 'market_close_times' | 'market_open_times' | 'multiplier_range_list' + | 'multiplier' | 'sessions' + | 'setIsTradeParamsExpanded' | 'start_dates_list' | 'start_time' + | 'symbol' + | 'take_profit' | 'proposal_info' | 'trade_types' | 'ticks_history_stats' @@ -85,8 +87,10 @@ type TOverrideTradeStore = Omit< expiry_time: string | null; expiry_type: string | null; form_components: string[]; + long_barriers: Record | { barrier: string; barrier_choices: string[] }; market_open_times: string[]; market_close_times: string[]; + multiplier: number; multiplier_range_list: number[]; proposal_info: { [key: string]: { @@ -104,8 +108,12 @@ type TOverrideTradeStore = Omit< }; }; sessions: Array<{ open: moment.Moment; close: moment.Moment }>; + setIsTradeParamsExpanded: (value: boolean) => void; + short_barriers: Record | { barrier: string; barrier_choices: string[] }; start_dates_list: Array<{ text: string; value: number }>; - start_time: string | null; + start_time?: string | null; + symbol: string; + take_profit?: string; ticks_history_stats: { ticks_stayed_in?: number[]; last_tick_epoch?: number;