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);