diff --git a/packages/components/src/components/dialog/dialog.tsx b/packages/components/src/components/dialog/dialog.tsx index cea840c7eed0..d2f7209ca4fd 100644 --- a/packages/components/src/components/dialog/dialog.tsx +++ b/packages/components/src/components/dialog/dialog.tsx @@ -14,7 +14,7 @@ type TDialog = { dismissable?: boolean; disableApp?: () => void; enableApp?: () => void; - has_close_icon: boolean; + has_close_icon?: boolean; is_closed_on_cancel?: boolean; is_closed_on_confirm?: boolean; is_content_centered?: boolean; @@ -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/core/src/Constants/contract.js b/packages/core/src/Constants/contract.js deleted file mode 100644 index e43b0b52d357..000000000000 --- a/packages/core/src/Constants/contract.js +++ /dev/null @@ -1,248 +0,0 @@ -import { localize } from '@deriv/translations'; - -export const getMarketNamesMap = () => ({ - FRXAUDCAD: localize('AUD/CAD'), - FRXAUDCHF: localize('AUD/CHF'), - FRXAUDJPY: localize('AUD/JPY'), - FRXAUDNZD: localize('AUD/NZD'), - FRXAUDPLN: localize('AUD/PLN'), - FRXAUDUSD: localize('AUD/USD'), - FRXBROUSD: localize('Oil/USD'), - FRXEURAUD: localize('EUR/AUD'), - FRXEURCAD: localize('EUR/CAD'), - FRXEURCHF: localize('EUR/CHF'), - FRXEURGBP: localize('EUR/GBP'), - FRXEURJPY: localize('EUR/JPY'), - FRXEURNZD: localize('EUR/NZD'), - FRXEURUSD: localize('EUR/USD'), - FRXGBPAUD: localize('GBP/AUD'), - FRXGBPCAD: localize('GBP/CAD'), - FRXGBPCHF: localize('GBP/CHF'), - FRXGBPJPY: localize('GBP/JPY'), - FRXGBPNOK: localize('GBP/NOK'), - FRXGBPUSD: localize('GBP/USD'), - FRXNZDJPY: localize('NZD/JPY'), - FRXNZDUSD: localize('NZD/USD'), - FRXUSDCAD: localize('USD/CAD'), - FRXUSDCHF: localize('USD/CHF'), - FRXUSDJPY: localize('USD/JPY'), - FRXUSDNOK: localize('USD/NOK'), - FRXUSDPLN: localize('USD/PLN'), - FRXUSDSEK: localize('USD/SEK'), - FRXXAGUSD: localize('Silver/USD'), - FRXXAUUSD: localize('Gold/USD'), - FRXXPDUSD: localize('Palladium/USD'), - FRXXPTUSD: localize('Platinum/USD'), - OTC_AEX: localize('Netherlands 25'), - OTC_AS51: localize('Australia 200'), - OTC_DJI: localize('Wall Street 30'), - OTC_FCHI: localize('France 40'), - OTC_FTSE: localize('UK 100'), - OTC_GDAXI: localize('Germany 40'), - OTC_HSI: localize('Hong Kong 50'), - OTC_IBEX35: localize('Spanish Index'), - OTC_N225: localize('Japan 225'), - OTC_NDX: localize('US Tech 100'), - OTC_SPC: localize('US 500'), - OTC_SSMI: localize('Swiss 20'), - OTC_SX5E: localize('Euro 50'), - R_10: localize('Volatility 10 Index'), - R_25: localize('Volatility 25 Index'), - R_50: localize('Volatility 50 Index'), - R_75: localize('Volatility 75 Index'), - R_100: localize('Volatility 100 Index'), - BOOM300N: localize('Boom 300 Index'), - BOOM500: localize('Boom 500 Index'), - BOOM1000: localize('Boom 1000 Index'), - CRASH300N: localize('Crash 300 Index'), - CRASH500: localize('Crash 500 Index'), - CRASH1000: localize('Crash 1000 Index'), - RDBEAR: localize('Bear Market Index'), - RDBULL: localize('Bull Market Index'), - STPRNG: localize('Step Index'), - WLDAUD: localize('AUD Basket'), - WLDEUR: localize('EUR Basket'), - WLDGBP: localize('GBP Basket'), - WLDXAU: localize('Gold Basket'), - WLDUSD: localize('USD Basket'), - '1HZ10V': localize('Volatility 10 (1s) Index'), - '1HZ100V': localize('Volatility 100 (1s) Index'), - '1HZ150V': localize('Volatility 150 (1s) Index'), - '1HZ200V': localize('Volatility 200 (1s) Index'), - '1HZ250V': localize('Volatility 250 (1s) Index'), - '1HZ300V': localize('Volatility 300 (1s) Index'), - JD10: localize('Jump 10 Index'), - JD25: localize('Jump 25 Index'), - JD50: localize('Jump 50 Index'), - JD75: localize('Jump 75 Index'), - JD100: localize('Jump 100 Index'), - JD150: localize('Jump 150 Index'), - JD200: localize('Jump 200 Index'), - CRYBCHUSD: localize('BCH/USD'), - CRYBNBUSD: localize('BNB/USD'), - CRYBTCLTC: localize('BTC/LTC'), - CRYIOTUSD: localize('IOT/USD'), - CRYNEOUSD: localize('NEO/USD'), - CRYOMGUSD: localize('OMG/USD'), - CRYTRXUSD: localize('TRX/USD'), - CRYBTCETH: localize('BTC/ETH'), - CRYZECUSD: localize('ZEC/USD'), - CRYXMRUSD: localize('ZMR/USD'), - CRYXMLUSD: localize('XLM/USD'), - CRYXRPUSD: localize('XRP/USD'), - CRYBTCUSD: localize('BTC/USD'), - CRYDSHUSD: localize('DSH/USD'), - CRYETHUSD: localize('ETH/USD'), - CRYEOSUSD: localize('EOS/USD'), - CRYLTCUSD: localize('LTC/USD'), -}); - -export const getUnsupportedContracts = () => ({ - RESETCALL: { - name: localize('Reset Call'), - position: 'top', - }, - RESETPUT: { - name: localize('Reset Put'), - position: 'bottom', - }, - TICKHIGH: { - name: localize('High Tick'), - position: 'top', - }, - TICKLOW: { - name: localize('Low Tick'), - position: 'bottom', - }, - ASIANU: { - name: localize('Asian Up'), - position: 'top', - }, - ASIAND: { - name: localize('Asian Down'), - position: 'bottom', - }, - LBFLOATCALL: { - name: localize('Close-Low'), - position: 'top', - }, - LBFLOATPUT: { - name: localize('High-Close'), - position: 'top', - }, - LBHIGHLOW: { - name: localize('High-Low'), - position: 'top', - }, - CALLSPREAD: { - name: localize('Call Spread'), - position: 'top', - }, - PUTSPREAD: { - name: localize('Put Spread'), - position: 'bottom', - }, -}); - -export const getSupportedContracts = is_high_low => ({ - ACCU: { - button_name: localize('Buy'), - name: localize('Accumulators'), - position: 'top', - }, - CALL: { - name: is_high_low ? localize('Higher') : localize('Rise'), - position: 'top', - }, - PUT: { - name: is_high_low ? localize('Lower') : localize('Fall'), - position: 'bottom', - }, - CALLE: { - name: localize('Rise'), - position: 'top', - }, - PUTE: { - name: localize('Fall'), - position: 'bottom', - }, - DIGITMATCH: { - name: localize('Matches'), - position: 'top', - }, - DIGITDIFF: { - name: localize('Differs'), - position: 'bottom', - }, - DIGITEVEN: { - name: localize('Even'), - position: 'top', - }, - DIGITODD: { - name: localize('Odd'), - position: 'bottom', - }, - DIGITOVER: { - name: localize('Over'), - position: 'top', - }, - DIGITUNDER: { - name: localize('Under'), - position: 'bottom', - }, - ONETOUCH: { - name: localize('Touch'), - position: 'top', - }, - NOTOUCH: { - name: localize('No Touch'), - position: 'bottom', - }, - TURBOSLONG: { - button_name: localize('Long'), - name: 'Turbos', - position: 'top', - }, - TURBOSSHORT: { - button_name: localize('Short'), - name: 'Turbos', - position: 'bottom', - }, - RUNHIGH: { - name: localize('Only Ups'), - position: 'top', - }, - RUNLOW: { - name: localize('Only Downs'), - position: 'bottom', - }, - EXPIRYMISS: { - name: localize('Ends Outside'), - position: 'top', - }, - EXPIRYRANGE: { - name: localize('Ends Between'), - position: 'bottom', - }, - RANGE: { - name: localize('Stays Between'), - position: 'top', - }, - UPORDOWN: { - name: localize('Goes Outside'), - position: 'bottom', - }, -}); - -export const getContractConfig = is_high_low => ({ - ...getSupportedContracts(is_high_low), - ...getUnsupportedContracts(), -}); - -export const getContractTypeDisplay = (type, is_high_low = false, show_button_name = false) => { - const contract_config = getContractConfig(is_high_low)[type]; - return (show_button_name && contract_config.button_name) || contract_config.name || ''; -}; - -export const getContractTypePosition = (type, is_high_low = false) => - getContractConfig(is_high_low)[type] ? getContractConfig(is_high_low)[type.toUpperCase()].position : 'top'; diff --git a/packages/core/src/Constants/index.js b/packages/core/src/Constants/index.js index 9ea4413f730b..5ecdd1f3443d 100644 --- a/packages/core/src/Constants/index.js +++ b/packages/core/src/Constants/index.js @@ -1,2 +1 @@ -export * from './contract'; export * from './ui'; diff --git a/packages/shared/src/utils/constants/__tests__/contract.spec.ts b/packages/shared/src/utils/constants/__tests__/contract.spec.ts new file mode 100644 index 000000000000..60e9cbf7a5b4 --- /dev/null +++ b/packages/shared/src/utils/constants/__tests__/contract.spec.ts @@ -0,0 +1,91 @@ +import { localize } from '@deriv/translations'; +import { + getCardLabels, + getMarketNamesMap, + getUnsupportedContracts, + getSupportedContracts, + getContractConfig, + getContractTypeDisplay, + getContractTypePosition, +} from '../contract'; + +type TGetSupportedContractsKey = keyof ReturnType; +const card_label = localize('Apply'); +const markets_name = localize('AUD/CAD'); +const unsupported_contract = { + name: localize('Spread Up'), + position: 'top', +}; +const supported_high_low = { + name: localize('Higher'), + position: 'top', +}; +const not_supported_high_low = { + name: localize('Rise'), + position: 'top', +}; + +describe('getCardLabels', () => { + it('should return an object with card labels, e.g. such as Apply', () => { + expect(getCardLabels().APPLY).toEqual(card_label); + }); +}); + +describe('getMarketNamesMap', () => { + it('should return an object with markets names, e.g. such as AUD/CAD', () => { + expect(getMarketNamesMap().FRXAUDCAD).toEqual(markets_name); + }); +}); + +describe('getUnsupportedContracts', () => { + it('should return an object with unsupported contracts, e.g. such as Spread Up', () => { + expect(getUnsupportedContracts().CALLSPREAD).toEqual(unsupported_contract); + }); +}); + +describe('getSupportedContracts', () => { + it('should return an object with specific supported contracts if is_high_low === true', () => { + expect(getSupportedContracts(true).CALL).toEqual(supported_high_low); + }); + + it('should return an object with specific supported contracts if is_high_low === false', () => { + expect(getSupportedContracts(false).CALL).toEqual(not_supported_high_low); + }); +}); + +describe('getContractConfig', () => { + it('should return an object with specific contracts if is_high_low === true', () => { + expect(getContractConfig(true).CALL).toEqual(supported_high_low); + }); + + it('should return object with specific contracts if is_high_low === false', () => { + expect(getContractConfig(false).CALL).toEqual(not_supported_high_low); + }); +}); + +describe('getContractTypeDisplay', () => { + it('should return a specific button name if show_button_name === true and contract_config has a button_name field', () => { + expect(getContractTypeDisplay('ACCU', false, true)).toEqual(localize('Buy')); + }); + it('should return a specific contract name if show_button_name === false but contract_config has a button_name field', () => { + expect(getContractTypeDisplay('ACCU')).toEqual(localize('Accumulators')); + }); + it('should return a specific contract name if show_button_name === true but contract_config has no button_name field', () => { + expect(getContractTypeDisplay('MULTDOWN', true, true)).toEqual(localize('Down')); + }); + it('should return an empty string if show_button_name === false and contract_config has no name field', () => { + expect(getContractTypeDisplay('TEST', true, false)).toBe(''); + }); + it('should return an empty string if show_button_name === true and contract_config has no name field and no button_name', () => { + expect(getContractTypeDisplay('TEST', true, true)).toBe(''); + }); +}); + +describe('getContractTypePosition', () => { + it('should return a specific button position if such type exist', () => { + expect(getContractTypePosition('NOTOUCH')).toBe('bottom'); + }); + it('should return a top position if such type does not exist', () => { + expect(getContractTypePosition('TEST' as TGetSupportedContractsKey)).toBe('top'); + }); +}); diff --git a/packages/shared/src/utils/constants/contract.tsx b/packages/shared/src/utils/constants/contract.ts similarity index 51% rename from packages/shared/src/utils/constants/contract.tsx rename to packages/shared/src/utils/constants/contract.ts index ffb0d6ae8483..33c318cb9c0e 100644 --- a/packages/shared/src/utils/constants/contract.tsx +++ b/packages/shared/src/utils/constants/contract.ts @@ -1,15 +1,16 @@ import React from 'react'; -import { localize, Localize } from '@deriv/translations'; +import { localize } from '@deriv/translations'; import { shouldShowCancellation, shouldShowExpiration, TURBOS } from '../contract'; -export const getLocalizedBasis = () => ({ - accumulator: localize('Accumulators'), - payout: localize('Payout'), - payout_per_point: localize('Payout per point'), - stake: localize('Stake'), - multiplier: localize('Multiplier'), - turbos: localize('Turbos'), -}); +export const getLocalizedBasis = () => + ({ + accumulator: localize('Accumulators'), + payout: localize('Payout'), + payout_per_point: localize('Payout per point'), + stake: localize('Stake'), + multiplier: localize('Multiplier'), + turbos: localize('Turbos'), + } as const); /** * components can be undef or an array containing any of: 'start_date', 'barrier', 'last_digit' @@ -26,6 +27,12 @@ type TContractTypesConfig = { type TGetContractTypesConfig = (symbol: string) => Record; +type TContractConfig = { + button_name?: React.ReactNode; + name: React.ReactNode; + position: string; +}; + type TGetSupportedContracts = keyof ReturnType; export const getContractTypesConfig: TGetContractTypesConfig = symbol => ({ @@ -165,20 +172,21 @@ export const getContractTypesConfig: TGetContractTypesConfig = symbol => ({ }); // Config for rendering trade options -export const getContractCategoriesConfig = () => ({ - Turbos: { name: localize('Turbos'), categories: [TURBOS.LONG, TURBOS.SHORT] }, - Multipliers: { name: localize('Multipliers'), categories: ['multiplier'] }, - 'Ups & Downs': { - name: localize('Ups & Downs'), - categories: ['rise_fall', 'rise_fall_equal', 'run_high_low', 'reset', 'asian', 'callputspread'], - }, - 'Highs & Lows': { name: localize('Highs & Lows'), categories: ['high_low', 'touch', 'tick_high_low'] }, - 'Ins & Outs': { name: localize('Ins & Outs'), categories: ['end', 'stay'] }, - 'Look Backs': { name: localize('Look Backs'), categories: ['lb_high_low', 'lb_put', 'lb_call'] }, - Digits: { name: localize('Digits'), categories: ['match_diff', 'even_odd', 'over_under'] }, - Vanillas: { name: localize('Vanillas'), categories: ['vanilla'] }, - Accumulators: { name: localize('Accumulators'), categories: ['accumulator'] }, -}); +export const getContractCategoriesConfig = () => + ({ + Turbos: { name: localize('Turbos'), categories: [TURBOS.LONG, TURBOS.SHORT] }, + Multipliers: { name: localize('Multipliers'), categories: ['multiplier'] }, + 'Ups & Downs': { + name: localize('Ups & Downs'), + categories: ['rise_fall', 'rise_fall_equal', 'run_high_low', 'reset', 'asian', 'callputspread'], + }, + 'Highs & Lows': { name: localize('Highs & Lows'), categories: ['high_low', 'touch', 'tick_high_low'] }, + 'Ins & Outs': { name: localize('Ins & Outs'), categories: ['end', 'stay'] }, + 'Look Backs': { name: localize('Look Backs'), categories: ['lb_high_low', 'lb_put', 'lb_call'] }, + Digits: { name: localize('Digits'), categories: ['match_diff', 'even_odd', 'over_under'] }, + Vanillas: { name: localize('Vanillas'), categories: ['vanilla'] }, + Accumulators: { name: localize('Accumulators'), categories: ['accumulator'] }, + } as const); export const unsupported_contract_types_list = [ // TODO: remove these once all contract types are supported @@ -194,141 +202,143 @@ export const unsupported_contract_types_list = [ 'lb_high_low', ]; -export const getCardLabels = () => ({ - APPLY: localize('Apply'), - BARRIER: localize('Barrier:'), - BUY_PRICE: localize('Buy price:'), - CANCEL: localize('Cancel'), - CLOSE: localize('Close'), - CONTRACT_VALUE: localize('Contract value:'), - CURRENT_STAKE: localize('Current stake:'), - DAY: localize('day'), - DAYS: localize('days'), - DEAL_CANCEL_FEE: localize('Deal cancel. fee:'), - DECREMENT_VALUE: localize('Decrement value'), - DONT_SHOW_THIS_AGAIN: localize("Don't show this again"), - ENTRY_SPOT: localize('Entry spot:'), - INCREMENT_VALUE: localize('Increment value'), - NOT_AVAILABLE: localize('N/A'), - RESALE_NOT_OFFERED: localize('Resale not offered'), - SELL: localize('Sell'), - STAKE: localize('Stake:'), - STOP_LOSS: localize('Stop loss:'), - STRIKE: localize('Strike:'), - TICK: localize('Tick '), - TICKS: localize('Ticks'), - TOTAL_PROFIT_LOSS: localize('Total profit/loss:'), - PROFIT_LOSS: localize('Profit/Loss:'), - POTENTIAL_PROFIT_LOSS: localize('Potential profit/loss:'), - INDICATIVE_PRICE: localize('Indicative price:'), - INITIAL_STAKE: localize('Initial stake:'), - LOST: localize('Lost'), - PAYOUT: localize('Sell price:'), - PURCHASE_PRICE: localize('Buy price:'), - POTENTIAL_PAYOUT: localize('Payout limit:'), - TAKE_PROFIT: localize('Take profit:'), - TAKE_PROFIT_LOSS_NOT_AVAILABLE: localize( - 'Take profit and/or stop loss are not available while deal cancellation is active.' - ), - WON: localize('Won'), -}); +export const getCardLabels = () => + ({ + APPLY: localize('Apply'), + BARRIER: localize('Barrier:'), + BUY_PRICE: localize('Buy price:'), + CANCEL: localize('Cancel'), + CLOSE: localize('Close'), + CONTRACT_VALUE: localize('Contract value:'), + CURRENT_STAKE: localize('Current stake:'), + DAY: localize('day'), + DAYS: localize('days'), + DEAL_CANCEL_FEE: localize('Deal cancel. fee:'), + DECREMENT_VALUE: localize('Decrement value'), + DONT_SHOW_THIS_AGAIN: localize("Don't show this again"), + ENTRY_SPOT: localize('Entry spot:'), + INCREMENT_VALUE: localize('Increment value'), + NOT_AVAILABLE: localize('N/A'), + RESALE_NOT_OFFERED: localize('Resale not offered'), + SELL: localize('Sell'), + STAKE: localize('Stake:'), + STOP_LOSS: localize('Stop loss:'), + STRIKE: localize('Strike:'), + TICK: localize('Tick '), + TICKS: localize('Ticks'), + TOTAL_PROFIT_LOSS: localize('Total profit/loss:'), + PROFIT_LOSS: localize('Profit/Loss:'), + POTENTIAL_PROFIT_LOSS: localize('Potential profit/loss:'), + INDICATIVE_PRICE: localize('Indicative price:'), + INITIAL_STAKE: localize('Initial stake:'), + LOST: localize('Lost'), + PAYOUT: localize('Sell price:'), + PURCHASE_PRICE: localize('Buy price:'), + POTENTIAL_PAYOUT: localize('Payout limit:'), + TAKE_PROFIT: localize('Take profit:'), + TAKE_PROFIT_LOSS_NOT_AVAILABLE: localize( + 'Take profit and/or stop loss are not available while deal cancellation is active.' + ), + WON: localize('Won'), + } as const); -export const getMarketNamesMap = () => ({ - FRXAUDCAD: localize('AUD/CAD'), - FRXAUDCHF: localize('AUD/CHF'), - FRXAUDJPY: localize('AUD/JPY'), - FRXAUDNZD: localize('AUD/NZD'), - FRXAUDPLN: localize('AUD/PLN'), - FRXAUDUSD: localize('AUD/USD'), - FRXBROUSD: localize('Oil/USD'), - FRXEURAUD: localize('EUR/AUD'), - FRXEURCAD: localize('EUR/CAD'), - FRXEURCHF: localize('EUR/CHF'), - FRXEURGBP: localize('EUR/GBP'), - FRXEURJPY: localize('EUR/JPY'), - FRXEURNZD: localize('EUR/NZD'), - FRXEURUSD: localize('EUR/USD'), - FRXGBPAUD: localize('GBP/AUD'), - FRXGBPCAD: localize('GBP/CAD'), - FRXGBPCHF: localize('GBP/CHF'), - FRXGBPJPY: localize('GBP/JPY'), - FRXGBPNOK: localize('GBP/NOK'), - FRXGBPUSD: localize('GBP/USD'), - FRXNZDJPY: localize('NZD/JPY'), - FRXNZDUSD: localize('NZD/USD'), - FRXUSDCAD: localize('USD/CAD'), - FRXUSDCHF: localize('USD/CHF'), - FRXUSDJPY: localize('USD/JPY'), - FRXUSDNOK: localize('USD/NOK'), - FRXUSDPLN: localize('USD/PLN'), - FRXUSDSEK: localize('USD/SEK'), - FRXXAGUSD: localize('Silver/USD'), - FRXXAUUSD: localize('Gold/USD'), - FRXXPDUSD: localize('Palladium/USD'), - FRXXPTUSD: localize('Platinum/USD'), - OTC_AEX: localize('Netherlands 25'), - OTC_AS51: localize('Australia 200'), - OTC_DJI: localize('Wall Street 30'), - OTC_FCHI: localize('France 40'), - OTC_FTSE: localize('UK 100'), - OTC_GDAXI: localize('Germany 40'), - OTC_HSI: localize('Hong Kong 50'), - OTC_IBEX35: localize('Spanish Index'), - OTC_N225: localize('Japan 225'), - OTC_NDX: localize('US Tech 100'), - OTC_SPC: localize('US 500'), - OTC_SSMI: localize('Swiss 20'), - OTC_SX5E: localize('Euro 50'), - R_10: localize('Volatility 10 Index'), - R_25: localize('Volatility 25 Index'), - R_50: localize('Volatility 50 Index'), - R_75: localize('Volatility 75 Index'), - R_100: localize('Volatility 100 Index'), - BOOM300N: localize('Boom 300 Index'), - BOOM500: localize('Boom 500 Index'), - BOOM1000: localize('Boom 1000 Index'), - CRASH300N: localize('Crash 300 Index'), - CRASH500: localize('Crash 500 Index'), - CRASH1000: localize('Crash 1000 Index'), - RDBEAR: localize('Bear Market Index'), - RDBULL: localize('Bull Market Index'), - STPRNG: localize('Step Index'), - WLDAUD: localize('AUD Basket'), - WLDEUR: localize('EUR Basket'), - WLDGBP: localize('GBP Basket'), - WLDXAU: localize('Gold Basket'), - WLDUSD: localize('USD Basket'), - '1HZ10V': localize('Volatility 10 (1s) Index'), - '1HZ100V': localize('Volatility 100 (1s) Index'), - '1HZ150V': localize('Volatility 150 (1s) Index'), - '1HZ200V': localize('Volatility 200 (1s) Index'), - '1HZ250V': localize('Volatility 250 (1s) Index'), - '1HZ300V': localize('Volatility 300 (1s) Index'), - JD10: localize('Jump 10 Index'), - JD25: localize('Jump 25 Index'), - JD50: localize('Jump 50 Index'), - JD75: localize('Jump 75 Index'), - JD100: localize('Jump 100 Index'), - JD150: localize('Jump 150 Index'), - JD200: localize('Jump 200 Index'), - CRYBCHUSD: localize('BCH/USD'), - CRYBNBUSD: localize('BNB/USD'), - CRYBTCLTC: localize('BTC/LTC'), - CRYIOTUSD: localize('IOT/USD'), - CRYNEOUSD: localize('NEO/USD'), - CRYOMGUSD: localize('OMG/USD'), - CRYTRXUSD: localize('TRX/USD'), - CRYBTCETH: localize('BTC/ETH'), - CRYZECUSD: localize('ZEC/USD'), - CRYXMRUSD: localize('ZMR/USD'), - CRYXMLUSD: localize('XLM/USD'), - CRYXRPUSD: localize('XRP/USD'), - CRYBTCUSD: localize('BTC/USD'), - CRYDSHUSD: localize('DSH/USD'), - CRYETHUSD: localize('ETH/USD'), - CRYEOSUSD: localize('EOS/USD'), - CRYLTCUSD: localize('LTC/USD'), -}); +export const getMarketNamesMap = () => + ({ + FRXAUDCAD: localize('AUD/CAD'), + FRXAUDCHF: localize('AUD/CHF'), + FRXAUDJPY: localize('AUD/JPY'), + FRXAUDNZD: localize('AUD/NZD'), + FRXAUDPLN: localize('AUD/PLN'), + FRXAUDUSD: localize('AUD/USD'), + FRXBROUSD: localize('Oil/USD'), + FRXEURAUD: localize('EUR/AUD'), + FRXEURCAD: localize('EUR/CAD'), + FRXEURCHF: localize('EUR/CHF'), + FRXEURGBP: localize('EUR/GBP'), + FRXEURJPY: localize('EUR/JPY'), + FRXEURNZD: localize('EUR/NZD'), + FRXEURUSD: localize('EUR/USD'), + FRXGBPAUD: localize('GBP/AUD'), + FRXGBPCAD: localize('GBP/CAD'), + FRXGBPCHF: localize('GBP/CHF'), + FRXGBPJPY: localize('GBP/JPY'), + FRXGBPNOK: localize('GBP/NOK'), + FRXGBPUSD: localize('GBP/USD'), + FRXNZDJPY: localize('NZD/JPY'), + FRXNZDUSD: localize('NZD/USD'), + FRXUSDCAD: localize('USD/CAD'), + FRXUSDCHF: localize('USD/CHF'), + FRXUSDJPY: localize('USD/JPY'), + FRXUSDNOK: localize('USD/NOK'), + FRXUSDPLN: localize('USD/PLN'), + FRXUSDSEK: localize('USD/SEK'), + FRXXAGUSD: localize('Silver/USD'), + FRXXAUUSD: localize('Gold/USD'), + FRXXPDUSD: localize('Palladium/USD'), + FRXXPTUSD: localize('Platinum/USD'), + OTC_AEX: localize('Netherlands 25'), + OTC_AS51: localize('Australia 200'), + OTC_DJI: localize('Wall Street 30'), + OTC_FCHI: localize('France 40'), + OTC_FTSE: localize('UK 100'), + OTC_GDAXI: localize('Germany 40'), + OTC_HSI: localize('Hong Kong 50'), + OTC_IBEX35: localize('Spanish Index'), + OTC_N225: localize('Japan 225'), + OTC_NDX: localize('US Tech 100'), + OTC_SPC: localize('US 500'), + OTC_SSMI: localize('Swiss 20'), + OTC_SX5E: localize('Euro 50'), + R_10: localize('Volatility 10 Index'), + R_25: localize('Volatility 25 Index'), + R_50: localize('Volatility 50 Index'), + R_75: localize('Volatility 75 Index'), + R_100: localize('Volatility 100 Index'), + BOOM300N: localize('Boom 300 Index'), + BOOM500: localize('Boom 500 Index'), + BOOM1000: localize('Boom 1000 Index'), + CRASH300N: localize('Crash 300 Index'), + CRASH500: localize('Crash 500 Index'), + CRASH1000: localize('Crash 1000 Index'), + RDBEAR: localize('Bear Market Index'), + RDBULL: localize('Bull Market Index'), + STPRNG: localize('Step Index'), + WLDAUD: localize('AUD Basket'), + WLDEUR: localize('EUR Basket'), + WLDGBP: localize('GBP Basket'), + WLDXAU: localize('Gold Basket'), + WLDUSD: localize('USD Basket'), + '1HZ10V': localize('Volatility 10 (1s) Index'), + '1HZ100V': localize('Volatility 100 (1s) Index'), + '1HZ150V': localize('Volatility 150 (1s) Index'), + '1HZ200V': localize('Volatility 200 (1s) Index'), + '1HZ250V': localize('Volatility 250 (1s) Index'), + '1HZ300V': localize('Volatility 300 (1s) Index'), + JD10: localize('Jump 10 Index'), + JD25: localize('Jump 25 Index'), + JD50: localize('Jump 50 Index'), + JD75: localize('Jump 75 Index'), + JD100: localize('Jump 100 Index'), + JD150: localize('Jump 150 Index'), + JD200: localize('Jump 200 Index'), + CRYBCHUSD: localize('BCH/USD'), + CRYBNBUSD: localize('BNB/USD'), + CRYBTCLTC: localize('BTC/LTC'), + CRYIOTUSD: localize('IOT/USD'), + CRYNEOUSD: localize('NEO/USD'), + CRYOMGUSD: localize('OMG/USD'), + CRYTRXUSD: localize('TRX/USD'), + CRYBTCETH: localize('BTC/ETH'), + CRYZECUSD: localize('ZEC/USD'), + CRYXMRUSD: localize('ZMR/USD'), + CRYXMLUSD: localize('XLM/USD'), + CRYXRPUSD: localize('XRP/USD'), + CRYBTCUSD: localize('BTC/USD'), + CRYDSHUSD: localize('DSH/USD'), + CRYETHUSD: localize('ETH/USD'), + CRYEOSUSD: localize('EOS/USD'), + CRYLTCUSD: localize('LTC/USD'), + } as const); export const getUnsupportedContracts = () => ({ @@ -381,106 +391,106 @@ export const getUnsupportedContracts = () => export const getSupportedContracts = (is_high_low?: boolean) => ({ ACCU: { - button_name: , - name: , + button_name: localize('Buy'), + name: localize('Accumulators'), position: 'top', }, CALL: { - name: is_high_low ? : , + name: is_high_low ? localize('Higher') : localize('Rise'), position: 'top', }, PUT: { - name: is_high_low ? : , + name: is_high_low ? localize('Lower') : localize('Fall'), position: 'bottom', }, CALLE: { - name: , + name: localize('Rise'), position: 'top', }, PUTE: { - name: , + name: localize('Fall'), position: 'bottom', }, DIGITMATCH: { - name: , + name: localize('Matches'), position: 'top', }, DIGITDIFF: { - name: , + name: localize('Differs'), position: 'bottom', }, DIGITEVEN: { - name: , + name: localize('Even'), position: 'top', }, DIGITODD: { - name: , + name: localize('Odd'), position: 'bottom', }, DIGITOVER: { - name: , + name: localize('Over'), position: 'top', }, DIGITUNDER: { - name: , + name: localize('Under'), position: 'bottom', }, ONETOUCH: { - name: , + name: localize('Touch'), position: 'top', }, NOTOUCH: { - name: , + name: localize('No Touch'), position: 'bottom', }, MULTUP: { - name: , + name: localize('Up'), position: 'top', }, MULTDOWN: { - name: , + name: localize('Down'), position: 'bottom', }, TURBOSLONG: { - name: , - button_name: , + name: localize('Turbos'), + button_name: localize('Long'), position: 'top', }, TURBOSSHORT: { - name: , - button_name: , + name: localize('Turbos'), + button_name: localize('Short'), position: 'bottom', }, VANILLALONGCALL: { - name: , + name: localize('Call'), position: 'top', }, VANILLALONGPUT: { - name: , + name: localize('Put'), position: 'bottom', }, RUNHIGH: { - name: , + name: localize('Only Ups'), position: 'top', }, RUNLOW: { - name: , + name: localize('Only Downs'), position: 'bottom', }, EXPIRYMISS: { - name: , + name: localize('Ends Outside'), position: 'top', }, EXPIRYRANGE: { - name: , + name: localize('Ends Between'), position: 'bottom', }, RANGE: { - name: , + name: localize('Stays Between'), position: 'top', }, UPORDOWN: { - name: , + name: localize('Goes Outside'), position: 'bottom', }, } as const); @@ -494,18 +504,13 @@ export const getContractConfig = (is_high_low?: boolean) => ({ // TODO we can combine getContractTypeDisplay and getContractTypePosition functions. the difference between these two functions is just the property they return. (name/position) */ -export const getContractTypeDisplay = ( - type: TGetSupportedContracts | string, - is_high_low = false, - show_button_name = false -) => { - const contract_config = getContractConfig(is_high_low)[type as TGetSupportedContracts]; - if (show_button_name && 'button_name' in contract_config) return contract_config.button_name; - return contract_config.name || ''; +export const getContractTypeDisplay = (type: string, is_high_low = false, show_button_name = false) => { + const contract_config = getContractConfig(is_high_low)[type as TGetSupportedContracts] as TContractConfig; + return (show_button_name && contract_config?.button_name) || contract_config?.name || ''; }; export const getContractTypePosition = (type: TGetSupportedContracts, is_high_low = false) => - getContractConfig(is_high_low)[type].position || 'top'; + getContractConfig(is_high_low)?.[type]?.position || 'top'; export const isCallPut = (trade_type: 'rise_fall' | 'rise_fall_equal' | 'high_low'): boolean => trade_type === 'rise_fall' || trade_type === 'rise_fall_equal' || trade_type === 'high_low'; diff --git a/packages/stores/src/mockStore.ts b/packages/stores/src/mockStore.ts index fd6f46283e2f..d51ec38ebb9d 100644 --- a/packages/stores/src/mockStore.ts +++ b/packages/stores/src/mockStore.ts @@ -305,6 +305,7 @@ const mock = (): TStores & { is_mock: boolean } => { openRealAccountSignup: jest.fn(), setIsClosingCreateRealAccountModal: jest.fn(), setRealAccountSignupEnd: jest.fn(), + setPurchaseState: jest.fn(), setHasOnlyForwardingContracts: jest.fn(), shouldNavigateAfterChooseCrypto: jest.fn(), toggleLanguageSettingsModal: jest.fn(), @@ -341,6 +342,7 @@ const mock = (): TStores & { is_mock: boolean } => { openDerivRealAccountNeededModal: jest.fn(), populateHeaderExtensions: jest.fn(), populateSettingsExtensions: jest.fn(), + purchase_states: [], setShouldShowCooldownModal: jest.fn(), populateFooterExtensions: jest.fn(), openAccountNeededModal: jest.fn(), @@ -431,8 +433,8 @@ const mock = (): TStores & { is_mock: boolean } => { setP2PRedirectTo: jest.fn(), }, portfolio: { - positions: [], active_positions: [], + all_positions: [], error: '', getPositionById: jest.fn(), is_loading: false, @@ -442,9 +444,15 @@ const mock = (): TStores & { is_mock: boolean } => { onClickCancel: jest.fn(), onClickSell: jest.fn(), onMount: jest.fn(), + positions: [], removePositionById: jest.fn(), }, contract_trade: { + contract_info: {}, + contract_update_stop_loss: '', + contract_update_take_profit: '', + has_contract_update_stop_loss: false, + has_contract_update_take_profit: false, getContractById: jest.fn(), }, modules: {}, diff --git a/packages/stores/types.ts b/packages/stores/types.ts index 350883a6e094..a2ee3ff20b5c 100644 --- a/packages/stores/types.ts +++ b/packages/stores/types.ts @@ -5,10 +5,10 @@ import type { DetailsOfEachMT5Loginid, GetAccountStatus, GetLimits, + Portfolio1, GetSettings, LandingCompany, LogOutResponse, - Portfolio1, ProposalOpenContract, ResidenceList, SetFinancialAssessmentRequest, @@ -468,6 +468,7 @@ type TUiStore = { setReportsTabIndex: (value: number) => void; setIsClosingCreateRealAccountModal: (value: boolean) => void; setRealAccountSignupEnd: (status: boolean) => void; + setPurchaseState: (index: number) => void; setHasOnlyForwardingContracts: (has_only_forward_starting_contracts: boolean) => void; sub_section_index: number; setSubSectionIndex: (index: number) => void; @@ -511,6 +512,7 @@ type TUiStore = { setResetTradingPasswordModalOpen: () => void; populateHeaderExtensions: (header_items: JSX.Element | null) => void; populateSettingsExtensions: (menu_items: Array | null) => void; + purchase_states: boolean[]; setShouldShowCooldownModal: (value: boolean) => void; setAppContentsScrollRef: (ref: React.MutableRefObject) => void; populateFooterExtensions: ( @@ -528,11 +530,12 @@ type TUiStore = { type TPortfolioStore = { active_positions: TPortfolioPosition[]; + all_positions: TPortfolioPosition[]; error: string; getPositionById: (id: number) => TPortfolioPosition; - is_accumulator: boolean; is_loading: boolean; is_multiplier: boolean; + is_accumulator: boolean; is_turbos: boolean; onClickCancel: (contract_id?: number) => void; onClickSell: (contract_id?: number) => void; @@ -543,6 +546,11 @@ type TPortfolioStore = { type TContractStore = { getContractById: (id: number) => ProposalOpenContract; + contract_info: TPortfolioPosition['contract_info']; + contract_update_stop_loss: string; + contract_update_take_profit: string; + has_contract_update_stop_loss: boolean; + has_contract_update_take_profit: boolean; }; type TMenuStore = { diff --git a/packages/trader/@deriv-stores.d.ts b/packages/trader/@deriv-stores.d.ts deleted file mode 100644 index 4be7c2be78e2..000000000000 --- a/packages/trader/@deriv-stores.d.ts +++ /dev/null @@ -1,33 +0,0 @@ -import type { TRootStore } from '@deriv/stores/types'; -import type TradeStore from './src/Stores/Modules/Trading/trade-store'; - -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 & { - clearContractPurchaseToastBox: () => void; - contract_purchase_toast_box: TToastBoxObject; -}; - -declare module '@deriv/stores' { - export function useStore(): TRootStore & { - modules: { - trade: TOverrideTradeStore; - }; - }; -} diff --git a/packages/trader/package.json b/packages/trader/package.json index 168480ff2bc8..32726a2bf57e 100644 --- a/packages/trader/package.json +++ b/packages/trader/package.json @@ -35,6 +35,7 @@ "devDependencies": { "@babel/eslint-parser": "^7.17.0", "@babel/preset-react": "^7.16.7", + "@types/lodash.debounce": "^4.0.7", "@testing-library/jest-dom": "^5.12.0", "@testing-library/react": "^12.0.0", "@testing-library/react-hooks": "^7.0.2", diff --git a/packages/trader/src/App/Components/Elements/ContractDrawer/contract-drawer-card.jsx b/packages/trader/src/App/Components/Elements/ContractDrawer/contract-drawer-card.jsx index a464a0d1e015..3bac2237f157 100644 --- a/packages/trader/src/App/Components/Elements/ContractDrawer/contract-drawer-card.jsx +++ b/packages/trader/src/App/Components/Elements/ContractDrawer/contract-drawer-card.jsx @@ -2,8 +2,14 @@ import classNames from 'classnames'; import PropTypes from 'prop-types'; import React from 'react'; import { DesktopWrapper, MobileWrapper, Collapsible, ContractCard, useHover } from '@deriv/components'; -import { isCryptoContract, isDesktop, getEndTime, getSymbolDisplayName } from '@deriv/shared'; -import { getCardLabels, getContractTypeDisplay } from 'Constants/contract'; +import { + getEndTime, + getSymbolDisplayName, + getCardLabels, + getContractTypeDisplay, + isCryptoContract, + isDesktop, +} from '@deriv/shared'; import { getMarketInformation } from 'Utils/Helpers/market-underlying'; import { SwipeableContractDrawer } from './swipeable-components.jsx'; import MarketClosedContractOverlay from './market-closed-contract-overlay.jsx'; diff --git a/packages/trader/src/App/Components/Elements/EmptyPortfolioMessage/empty-portfolio-message.jsx b/packages/trader/src/App/Components/Elements/EmptyPortfolioMessage/empty-portfolio-message.tsx similarity index 88% rename from packages/trader/src/App/Components/Elements/EmptyPortfolioMessage/empty-portfolio-message.jsx rename to packages/trader/src/App/Components/Elements/EmptyPortfolioMessage/empty-portfolio-message.tsx index 5f26070b2c88..9a36c46f6d14 100644 --- a/packages/trader/src/App/Components/Elements/EmptyPortfolioMessage/empty-portfolio-message.jsx +++ b/packages/trader/src/App/Components/Elements/EmptyPortfolioMessage/empty-portfolio-message.tsx @@ -2,7 +2,11 @@ import React from 'react'; import { Icon, Text } from '@deriv/components'; import { localize } from '@deriv/translations'; -const EmptyPortfolioMessage = ({ error }) => ( +type TEmptyPortfolioMessage = { + error: string; +}; + +const EmptyPortfolioMessage = ({ error }: TEmptyPortfolioMessage) => (
{error ? ( diff --git a/packages/trader/src/App/Components/Elements/EmptyPortfolioMessage/index.js b/packages/trader/src/App/Components/Elements/EmptyPortfolioMessage/index.js index d498bb957778..f6aad7b8b061 100644 --- a/packages/trader/src/App/Components/Elements/EmptyPortfolioMessage/index.js +++ b/packages/trader/src/App/Components/Elements/EmptyPortfolioMessage/index.js @@ -1,3 +1,3 @@ -import EmptyPortfolioMessage from './empty-portfolio-message.jsx'; +import EmptyPortfolioMessage from './empty-portfolio-message'; export default EmptyPortfolioMessage; diff --git a/packages/trader/src/App/Components/Elements/Errors/error-component.jsx b/packages/trader/src/App/Components/Elements/Errors/error-component.tsx similarity index 68% rename from packages/trader/src/App/Components/Elements/Errors/error-component.jsx rename to packages/trader/src/App/Components/Elements/Errors/error-component.tsx index e7ba28a9a7da..b5485b0791c9 100644 --- a/packages/trader/src/App/Components/Elements/Errors/error-component.jsx +++ b/packages/trader/src/App/Components/Elements/Errors/error-component.tsx @@ -1,10 +1,18 @@ -import PropTypes from 'prop-types'; import React from 'react'; import { Dialog, PageErrorContainer } from '@deriv/components'; import { routes } from '@deriv/shared'; import { localize } from '@deriv/translations'; -const ErrorComponent = ({ +type TErrorComponent = { + header: string; + message: React.ReactNode; + is_dialog: boolean; + redirect_label: string; + redirectOnClick: () => void; + should_show_refresh: boolean; +}; + +const ErrorComponent: React.FC> = ({ header, message, is_dialog, @@ -12,13 +20,13 @@ const ErrorComponent = ({ redirectOnClick, should_show_refresh = true, }) => { - const refresh_message = should_show_refresh ? localize('Please refresh this page to continue.') : ''; + const refresh_message: string = should_show_refresh ? localize('Please refresh this page to continue.') : ''; if (is_dialog) { return ( location.reload())} > @@ -37,14 +45,4 @@ const ErrorComponent = ({ ); }; -ErrorComponent.propTypes = { - header: PropTypes.string, - is_dialog: PropTypes.bool, - message: PropTypes.oneOfType([PropTypes.node, PropTypes.string, PropTypes.object]), - redirect_label: PropTypes.string, - redirectOnClick: PropTypes.func, - should_show_refresh: PropTypes.bool, - type: PropTypes.string, -}; - export default ErrorComponent; diff --git a/packages/trader/src/App/Components/Elements/Errors/index.js b/packages/trader/src/App/Components/Elements/Errors/index.js index 7673d0bd01f2..8585809410a1 100644 --- a/packages/trader/src/App/Components/Elements/Errors/index.js +++ b/packages/trader/src/App/Components/Elements/Errors/index.js @@ -1,3 +1,3 @@ -import ErrorComponent from './error-component.jsx'; +import ErrorComponent from './error-component'; export default ErrorComponent; diff --git a/packages/trader/src/App/Components/Elements/PositionsDrawer/positions-modal-card.jsx b/packages/trader/src/App/Components/Elements/PositionsDrawer/positions-modal-card.jsx index 25639322b58f..69a44e1b602d 100644 --- a/packages/trader/src/App/Components/Elements/PositionsDrawer/positions-modal-card.jsx +++ b/packages/trader/src/App/Components/Elements/PositionsDrawer/positions-modal-card.jsx @@ -6,23 +6,24 @@ import { CSSTransition } from 'react-transition-group'; import { ContractCard, CurrencyBadge, Icon, Money, ProgressSliderMobile, Text } from '@deriv/components'; import { getContractPath, + getContractTypeDisplay, + getCardLabels, + getSymbolDisplayName, + getEndTime, + getTotalProfit, + hasContractEntered, isAccumulatorContract, isCryptoContract, isMultiplierContract, isTurbosContract, isHighLow, isCryptocurrency, - hasContractEntered, isOpen, - getSymbolDisplayName, - getEndTime, - getTotalProfit, isVanillaContract, } from '@deriv/shared'; import { localize } from '@deriv/translations'; import { BinaryLink } from 'App/Components/Routes'; import { PositionsCardLoader } from 'App/Components/Elements/ContentLoader'; -import { getContractTypeDisplay, getCardLabels } from 'Constants/contract'; import { getMarketInformation } from 'Utils/Helpers/market-underlying'; import ResultMobile from './result-mobile.jsx'; import { observer, useStore } from '@deriv/stores'; diff --git a/packages/trader/src/App/Components/Elements/__tests__/chart-loader.spec.jsx b/packages/trader/src/App/Components/Elements/__tests__/chart-loader.spec.tsx similarity index 59% rename from packages/trader/src/App/Components/Elements/__tests__/chart-loader.spec.jsx rename to packages/trader/src/App/Components/Elements/__tests__/chart-loader.spec.tsx index 5287d957128c..8e8b1750b6f6 100644 --- a/packages/trader/src/App/Components/Elements/__tests__/chart-loader.spec.jsx +++ b/packages/trader/src/App/Components/Elements/__tests__/chart-loader.spec.tsx @@ -2,17 +2,23 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; import ChartLoader from '../chart-loader'; +const default_props = { + is_dark: true, + is_visible: false, +}; + describe('ChartLoader', () => { const test_id = 'dt_barspinner'; - it('should render ChartLoader component if is_visible === true', () => { - render(); + it('should not render ChartLoader component if is_visible === false', () => { + render(); - expect(screen.getByTestId(test_id)).toBeInTheDocument(); + expect(screen.queryByTestId(test_id)).not.toBeInTheDocument(); }); - it('should render ChartLoader component if is_visible === false', () => { - render(); + it('should render ChartLoader component if is_visible === true', () => { + default_props.is_visible = true; + render(); - expect(screen.queryByTestId(test_id)).not.toBeInTheDocument(); + expect(screen.getByTestId(test_id)).toBeInTheDocument(); }); }); diff --git a/packages/trader/src/App/Components/Elements/chart-loader.jsx b/packages/trader/src/App/Components/Elements/chart-loader.tsx similarity index 63% rename from packages/trader/src/App/Components/Elements/chart-loader.jsx rename to packages/trader/src/App/Components/Elements/chart-loader.tsx index ae33ae48d9d0..06343df6a417 100644 --- a/packages/trader/src/App/Components/Elements/chart-loader.jsx +++ b/packages/trader/src/App/Components/Elements/chart-loader.tsx @@ -1,17 +1,16 @@ -import PropTypes from 'prop-types'; import React from 'react'; import Loading from '_common/components/loading'; -const ChartLoader = ({ is_dark, is_visible }) => +type TChartLoader = { + is_dark: boolean; + is_visible: boolean; +}; + +const ChartLoader = ({ is_dark, is_visible }: TChartLoader) => is_visible ? (
) : null; -ChartLoader.propTypes = { - is_dark: PropTypes.bool, - is_visible: PropTypes.bool, -}; - export default ChartLoader; diff --git a/packages/trader/src/App/Containers/ProgressSliderStream/progress-slider-stream.jsx b/packages/trader/src/App/Containers/ProgressSliderStream/progress-slider-stream.jsx index 487cbc036604..34409af133c9 100644 --- a/packages/trader/src/App/Containers/ProgressSliderStream/progress-slider-stream.jsx +++ b/packages/trader/src/App/Containers/ProgressSliderStream/progress-slider-stream.jsx @@ -1,8 +1,7 @@ import PropTypes from 'prop-types'; import React from 'react'; import { ProgressSlider } from '@deriv/components'; -import { getCurrentTick } from '@deriv/shared'; -import { getCardLabels } from 'Constants/contract'; +import { getCardLabels, getCurrentTick } from '@deriv/shared'; import { observer, useStore } from '@deriv/stores'; const ProgressSliderStream = observer(({ contract_info }) => { diff --git a/packages/trader/src/Constants/contract.js b/packages/trader/src/Constants/contract.js deleted file mode 100644 index f953a1e7fc00..000000000000 --- a/packages/trader/src/Constants/contract.js +++ /dev/null @@ -1,305 +0,0 @@ -import React from 'react'; -import { localize, Localize } from '@deriv/translations'; - -export const getCardLabels = () => ({ - APPLY: localize('Apply'), - BARRIER: localize('Barrier:'), - BUY_PRICE: localize('Buy price:'), - CANCEL: localize('Cancel'), - CLOSE: localize('Close'), - CONTRACT_VALUE: localize('Contract value:'), - CURRENT_STAKE: localize('Current stake:'), - DAY: localize('day'), - DAYS: localize('days'), - DEAL_CANCEL_FEE: localize('Deal cancel. fee:'), - DECREMENT_VALUE: localize('Decrement value'), - DONT_SHOW_THIS_AGAIN: localize("Don't show this again"), - ENTRY_SPOT: localize('Entry spot:'), - INCREMENT_VALUE: localize('Increment value'), - NOT_AVAILABLE: localize('N/A'), - RESALE_NOT_OFFERED: localize('Resale not offered'), - SELL: localize('Sell'), - STAKE: localize('Stake:'), - STOP_LOSS: localize('Stop loss:'), - STRIKE: localize('Strike:'), - TICK: localize('Tick '), - TICKS: localize('Ticks'), - TOTAL_PROFIT_LOSS: localize('Total profit/loss:'), - PROFIT_LOSS: localize('Profit/Loss:'), - POTENTIAL_PROFIT_LOSS: localize('Potential profit/loss:'), - INDICATIVE_PRICE: localize('Indicative price:'), - INITIAL_STAKE: localize('Initial stake:'), - LOST: localize('Lost'), - PAYOUT: localize('Sell price:'), - PURCHASE_PRICE: localize('Buy price:'), - POTENTIAL_PAYOUT: localize('Payout limit:'), - TAKE_PROFIT: localize('Take profit:'), - TAKE_PROFIT_LOSS_NOT_AVAILABLE: localize( - 'Take profit and/or stop loss are not available while deal cancellation is active.' - ), - WON: localize('Won'), -}); - -export const getMarketNamesMap = () => ({ - FRXAUDCAD: localize('AUD/CAD'), - FRXAUDCHF: localize('AUD/CHF'), - FRXAUDJPY: localize('AUD/JPY'), - FRXAUDNZD: localize('AUD/NZD'), - FRXAUDPLN: localize('AUD/PLN'), - FRXAUDUSD: localize('AUD/USD'), - FRXBROUSD: localize('Oil/USD'), - FRXEURAUD: localize('EUR/AUD'), - FRXEURCAD: localize('EUR/CAD'), - FRXEURCHF: localize('EUR/CHF'), - FRXEURGBP: localize('EUR/GBP'), - FRXEURJPY: localize('EUR/JPY'), - FRXEURNZD: localize('EUR/NZD'), - FRXEURUSD: localize('EUR/USD'), - FRXGBPAUD: localize('GBP/AUD'), - FRXGBPCAD: localize('GBP/CAD'), - FRXGBPCHF: localize('GBP/CHF'), - FRXGBPJPY: localize('GBP/JPY'), - FRXGBPNOK: localize('GBP/NOK'), - FRXGBPUSD: localize('GBP/USD'), - FRXNZDJPY: localize('NZD/JPY'), - FRXNZDUSD: localize('NZD/USD'), - FRXUSDCAD: localize('USD/CAD'), - FRXUSDCHF: localize('USD/CHF'), - FRXUSDJPY: localize('USD/JPY'), - FRXUSDNOK: localize('USD/NOK'), - FRXUSDPLN: localize('USD/PLN'), - FRXUSDSEK: localize('USD/SEK'), - FRXXAGUSD: localize('Silver/USD'), - FRXXAUUSD: localize('Gold/USD'), - FRXXPDUSD: localize('Palladium/USD'), - FRXXPTUSD: localize('Platinum/USD'), - OTC_AEX: localize('Netherlands 25'), - OTC_AS51: localize('Australia 200'), - OTC_DJI: localize('Wall Street 30'), - OTC_FCHI: localize('France 40'), - OTC_FTSE: localize('UK 100'), - OTC_GDAXI: localize('Germany 40'), - OTC_HSI: localize('Hong Kong 50'), - OTC_IBEX35: localize('Spanish Index'), - OTC_N225: localize('Japan 225'), - OTC_NDX: localize('US Tech 100'), - OTC_SPC: localize('US 500'), - OTC_SSMI: localize('Swiss 20'), - OTC_SX5E: localize('Euro 50'), - R_10: localize('Volatility 10 Index'), - R_25: localize('Volatility 25 Index'), - R_50: localize('Volatility 50 Index'), - R_75: localize('Volatility 75 Index'), - R_100: localize('Volatility 100 Index'), - BOOM300N: localize('Boom 300 Index'), - BOOM500: localize('Boom 500 Index'), - BOOM1000: localize('Boom 1000 Index'), - CRASH300N: localize('Crash 300 Index'), - CRASH500: localize('Crash 500 Index'), - CRASH1000: localize('Crash 1000 Index'), - RDBEAR: localize('Bear Market Index'), - RDBULL: localize('Bull Market Index'), - STPRNG: localize('Step Index'), - WLDAUD: localize('AUD Basket'), - WLDEUR: localize('EUR Basket'), - WLDGBP: localize('GBP Basket'), - WLDXAU: localize('Gold Basket'), - WLDUSD: localize('USD Basket'), - '1HZ10V': localize('Volatility 10 (1s) Index'), - '1HZ100V': localize('Volatility 100 (1s) Index'), - '1HZ150V': localize('Volatility 150 (1s) Index'), - '1HZ200V': localize('Volatility 200 (1s) Index'), - '1HZ250V': localize('Volatility 250 (1s) Index'), - '1HZ300V': localize('Volatility 300 (1s) Index'), - JD10: localize('Jump 10 Index'), - JD25: localize('Jump 25 Index'), - JD50: localize('Jump 50 Index'), - JD75: localize('Jump 75 Index'), - JD100: localize('Jump 100 Index'), - JD150: localize('Jump 150 Index'), - JD200: localize('Jump 200 Index'), - CRYBCHUSD: localize('BCH/USD'), - CRYBNBUSD: localize('BNB/USD'), - CRYBTCLTC: localize('BTC/LTC'), - CRYIOTUSD: localize('IOT/USD'), - CRYNEOUSD: localize('NEO/USD'), - CRYOMGUSD: localize('OMG/USD'), - CRYTRXUSD: localize('TRX/USD'), - CRYBTCETH: localize('BTC/ETH'), - CRYZECUSD: localize('ZEC/USD'), - CRYXMRUSD: localize('ZMR/USD'), - CRYXMLUSD: localize('XLM/USD'), - CRYXRPUSD: localize('XRP/USD'), - CRYBTCUSD: localize('BTC/USD'), - CRYDSHUSD: localize('DSH/USD'), - CRYETHUSD: localize('ETH/USD'), - CRYEOSUSD: localize('EOS/USD'), - CRYLTCUSD: localize('LTC/USD'), -}); - -export const getUnsupportedContracts = () => ({ - RESETCALL: { - name: , - position: 'top', - }, - RESETPUT: { - name: , - position: 'bottom', - }, - TICKHIGH: { - name: , - position: 'top', - }, - TICKLOW: { - name: , - position: 'bottom', - }, - ASIANU: { - name: , - position: 'top', - }, - ASIAND: { - name: , - position: 'bottom', - }, - LBFLOATCALL: { - name: , - position: 'top', - }, - LBFLOATPUT: { - name: , - position: 'top', - }, - LBHIGHLOW: { - name: , - position: 'top', - }, - CALLSPREAD: { - name: , - position: 'top', - }, - PUTSPREAD: { - name: , - position: 'bottom', - }, -}); - -// Config to display trade button and their position -export const getSupportedContracts = is_high_low => ({ - ACCU: { - button_name: , - name: , - position: 'top', - }, - CALL: { - name: is_high_low ? : , - position: 'top', - }, - PUT: { - name: is_high_low ? : , - position: 'bottom', - }, - CALLE: { - name: , - position: 'top', - }, - PUTE: { - name: , - position: 'bottom', - }, - DIGITMATCH: { - name: , - position: 'top', - }, - DIGITDIFF: { - name: , - position: 'bottom', - }, - DIGITEVEN: { - name: , - position: 'top', - }, - DIGITODD: { - name: , - position: 'bottom', - }, - DIGITOVER: { - name: , - position: 'top', - }, - DIGITUNDER: { - name: , - position: 'bottom', - }, - ONETOUCH: { - name: , - position: 'top', - }, - NOTOUCH: { - name: , - position: 'bottom', - }, - MULTUP: { - name: , - position: 'top', - }, - MULTDOWN: { - name: , - position: 'bottom', - }, - TURBOSLONG: { - button_name: , - name: , - position: 'top', - }, - TURBOSSHORT: { - button_name: , - name: , - position: 'bottom', - }, - VANILLALONGCALL: { - name: , - position: 'top', - }, - VANILLALONGPUT: { - name: , - position: 'bottom', - }, - RUNHIGH: { - name: , - position: 'top', - }, - RUNLOW: { - name: , - position: 'bottom', - }, - EXPIRYMISS: { - name: , - position: 'top', - }, - EXPIRYRANGE: { - name: , - position: 'bottom', - }, - RANGE: { - name: , - position: 'top', - }, - UPORDOWN: { - name: , - position: 'bottom', - }, -}); - -export const getContractConfig = is_high_low => ({ - ...getSupportedContracts(is_high_low), - ...getUnsupportedContracts(), -}); - -export const getContractTypeDisplay = (type, is_high_low = false, show_button_name = false) => { - const contract_config = getContractConfig(is_high_low)[type]; - return (show_button_name && contract_config.button_name) || contract_config.name || ''; -}; - -export const getContractTypePosition = (type, is_high_low = false) => - getContractConfig(is_high_low)[type] ? getContractConfig(is_high_low)[type.toUpperCase()].position : 'top'; diff --git a/packages/trader/src/Constants/index.js b/packages/trader/src/Constants/index.js deleted file mode 100644 index 9ea4413f730b..000000000000 --- a/packages/trader/src/Constants/index.js +++ /dev/null @@ -1,2 +0,0 @@ -export * from './contract'; -export * from './ui'; diff --git a/packages/trader/src/Constants/ui.js b/packages/trader/src/Constants/ui.js deleted file mode 100644 index 0865c8c5efa8..000000000000 --- a/packages/trader/src/Constants/ui.js +++ /dev/null @@ -1,2 +0,0 @@ -export const MAX_MOBILE_WIDTH = 767; -export const MAX_TABLET_WIDTH = 1024; diff --git a/packages/trader/src/Modules/Contract/Components/Digits/digits.jsx b/packages/trader/src/Modules/Contract/Components/Digits/digits.jsx index 5c8a14d768a5..8436d533dde2 100644 --- a/packages/trader/src/Modules/Contract/Components/Digits/digits.jsx +++ b/packages/trader/src/Modules/Contract/Components/Digits/digits.jsx @@ -3,10 +3,9 @@ import PropTypes from 'prop-types'; import React from 'react'; import { toJS } from 'mobx'; import { DesktopWrapper, MobileWrapper, Popover, Text } from '@deriv/components'; -import { isMobile, useIsMounted, isContractElapsed } from '@deriv/shared'; +import { getMarketNamesMap, isMobile, useIsMounted, isContractElapsed } from '@deriv/shared'; import { localize, Localize } from '@deriv/translations'; import { Bounce, SlideIn } from 'App/Components/Animations'; -import { getMarketNamesMap } from '../../../../Constants'; import { DigitSpot, LastDigitPrediction } from '../LastDigitPrediction'; import 'Sass/app/modules/contract/digits.scss'; diff --git a/packages/trader/src/Modules/Contract/Containers/contract-replay.jsx b/packages/trader/src/Modules/Contract/Containers/contract-replay.jsx index 371d4f330c80..ed1dd388ff47 100644 --- a/packages/trader/src/Modules/Contract/Containers/contract-replay.jsx +++ b/packages/trader/src/Modules/Contract/Containers/contract-replay.jsx @@ -26,7 +26,7 @@ import { urlFor, } from '@deriv/shared'; import { localize } from '@deriv/translations'; -import ChartLoader from 'App/Components/Elements/chart-loader.jsx'; +import ChartLoader from 'App/Components/Elements/chart-loader'; import ContractDrawer from 'App/Components/Elements/ContractDrawer'; import UnsupportedContractModal from 'App/Components/Elements/Modals/UnsupportedContractModal'; import { SmartChart } from 'Modules/SmartChart'; diff --git a/packages/trader/src/Modules/Page404/Components/Page404.jsx b/packages/trader/src/Modules/Page404/Components/Page404.tsx similarity index 99% rename from packages/trader/src/Modules/Page404/Components/Page404.jsx rename to packages/trader/src/Modules/Page404/Components/Page404.tsx index 981431ce85c8..30100185bcf1 100644 --- a/packages/trader/src/Modules/Page404/Components/Page404.jsx +++ b/packages/trader/src/Modules/Page404/Components/Page404.tsx @@ -1,7 +1,6 @@ import React from 'react'; import { PageError } from '@deriv/components'; import { routes, getUrlBase } from '@deriv/shared'; - import { localize } from '@deriv/translations'; const Page404 = () => ( diff --git a/packages/trader/src/Modules/Page404/index.js b/packages/trader/src/Modules/Page404/index.js index 5bd19e4b89a2..04dbb70feef6 100644 --- a/packages/trader/src/Modules/Page404/index.js +++ b/packages/trader/src/Modules/Page404/index.js @@ -1,3 +1,3 @@ -import Page404 from './Components/Page404.jsx'; +import Page404 from './Components/Page404'; export default Page404; diff --git a/packages/trader/src/Modules/Trading/Components/Elements/purchase-button.jsx b/packages/trader/src/Modules/Trading/Components/Elements/purchase-button.tsx similarity index 82% rename from packages/trader/src/Modules/Trading/Components/Elements/purchase-button.jsx rename to packages/trader/src/Modules/Trading/Components/Elements/purchase-button.tsx index 3b43b46a2f3c..3e886115ad5c 100644 --- a/packages/trader/src/Modules/Trading/Components/Elements/purchase-button.jsx +++ b/packages/trader/src/Modules/Trading/Components/Elements/purchase-button.tsx @@ -1,13 +1,42 @@ import classNames from 'classnames'; -import PropTypes from 'prop-types'; import React from 'react'; import { DesktopWrapper, MobileWrapper, Money, IconTradeTypes, Text } from '@deriv/components'; -import { getContractTypeDisplay } from 'Constants/contract'; -import ContractInfo from 'Modules/Trading/Components/Form/Purchase/contract-info.jsx'; -import { getGrowthRatePercentage } from '@deriv/shared'; +import ContractInfo from 'Modules/Trading/Components/Form/Purchase/contract-info'; +import { getContractTypeDisplay, getGrowthRatePercentage } from '@deriv/shared'; +import { TProposalTypeInfo } from 'Types'; + +type TPurchaseButton = { + basis: string; + buy_info: { error?: string }; + currency: string; + growth_rate: number; + has_deal_cancellation: boolean; + index: number; + info: TProposalTypeInfo; + is_accumulator: boolean; + is_disabled: boolean; + is_high_low: boolean; + is_loading: boolean; + is_multiplier: boolean; + is_proposal_empty: boolean; + is_vanilla: boolean; + is_turbos: boolean; + onClickPurchase: (proposal_id: string, price: string | number, type: string) => void; + purchased_states_arr: boolean[]; + should_fade: boolean; + setPurchaseState: (index: number) => void; + type: string; +}; + +type TButtonTextWrapper = { + should_fade: boolean; + is_loading: boolean; + type: string; + is_high_low: boolean; +}; // TODO [lazy-loading-required] Responsive related components -const ButtonTextWrapper = ({ should_fade, is_loading, type, is_high_low }) => { +const ButtonTextWrapper = ({ should_fade, is_loading, type, is_high_low }: TButtonTextWrapper) => { return (
@@ -17,7 +46,7 @@ const ButtonTextWrapper = ({ should_fade, is_loading, type, is_high_low }) => { ); }; -const IconComponentWrapper = ({ type }) => ( +const IconComponentWrapper = ({ type }: { type: string }) => (
@@ -44,14 +73,15 @@ const PurchaseButton = ({ should_fade, onClickPurchase, type, -}) => { +}: TPurchaseButton) => { const getIconType = () => { if (!should_fade && is_loading) return ''; return is_high_low ? `${type.toLowerCase()}_barrier` : type.toLowerCase(); }; const { has_increased } = info; const is_button_disabled = (is_disabled && !is_loading) || is_proposal_empty; - const non_multiplier_info_right = is_accumulator ? `${getGrowthRatePercentage(info.growth_rate)}%` : info.returns; + const non_multiplier_info_right = + is_accumulator && info.growth_rate ? `${getGrowthRatePercentage(info.growth_rate)}%` : info.returns; let button_value; @@ -147,27 +177,4 @@ const PurchaseButton = ({ ); }; -PurchaseButton.propTypes = { - basis: PropTypes.string, - buy_info: PropTypes.object, - currency: PropTypes.string, - growth_rate: PropTypes.number, - has_deal_cancellation: PropTypes.bool, - index: PropTypes.number, - info: PropTypes.object, - is_accumulator: PropTypes.bool, - is_disabled: PropTypes.bool, - is_high_low: PropTypes.bool, - is_loading: PropTypes.bool, - is_multiplier: PropTypes.bool, - is_proposal_empty: PropTypes.bool, - is_turbos: PropTypes.bool, - is_vanilla: PropTypes.bool, - onClickPurchase: PropTypes.func, - purchased_states_arr: PropTypes.array, - should_fade: PropTypes.bool, - setPurchaseState: PropTypes.func, - type: PropTypes.string, -}; - export default PurchaseButton; diff --git a/packages/trader/src/Modules/Trading/Components/Elements/purchase-fieldset.jsx b/packages/trader/src/Modules/Trading/Components/Elements/purchase-fieldset.tsx similarity index 85% rename from packages/trader/src/Modules/Trading/Components/Elements/purchase-fieldset.jsx rename to packages/trader/src/Modules/Trading/Components/Elements/purchase-fieldset.tsx index fd06434b7f23..cbce355c475b 100644 --- a/packages/trader/src/Modules/Trading/Components/Elements/purchase-fieldset.jsx +++ b/packages/trader/src/Modules/Trading/Components/Elements/purchase-fieldset.tsx @@ -1,11 +1,36 @@ import classNames from 'classnames'; import React from 'react'; -import PropTypes from 'prop-types'; import { DesktopWrapper, MobileWrapper, Popover } from '@deriv/components'; import Fieldset from 'App/Components/Form/fieldset.jsx'; -import ContractInfo from 'Modules/Trading/Components/Form/Purchase/contract-info.jsx'; -import PurchaseButton from 'Modules/Trading/Components/Elements/purchase-button.jsx'; -import CancelDealInfo from '../Form/Purchase/cancel-deal-info.jsx'; +import ContractInfo from 'Modules/Trading/Components/Form/Purchase/contract-info'; +import PurchaseButton from 'Modules/Trading/Components/Elements/purchase-button'; +import CancelDealInfo from '../Form/Purchase/cancel-deal-info'; +import { TProposalTypeInfo } from 'Types'; + +type TPurchaseFieldset = { + basis: string; + buy_info: { error?: string }; + currency: string; + growth_rate: number; + has_cancellation: boolean; + index: number; + info: TProposalTypeInfo; + is_accumulator: boolean; + is_disabled: boolean; + is_high_low: boolean; + is_loading: boolean; + is_market_closed: boolean; + is_multiplier: boolean; + is_proposal_empty: boolean; + is_proposal_error: boolean; + is_vanilla: boolean; + is_turbos: boolean; + onClickPurchase: (proposal_id: string, price: string | number, type: string) => void; + onHoverPurchase: (is_over: boolean, contract_type: string) => void; + purchased_states_arr: boolean[]; + setPurchaseState: (index: number) => void; + type: string; +}; const PurchaseFieldset = ({ basis, @@ -30,7 +55,7 @@ const PurchaseFieldset = ({ onHoverPurchase, setPurchaseState, type, -}) => { +}: TPurchaseFieldset) => { const [should_fade, setShouldFade] = React.useState(false); React.useEffect(() => { @@ -107,7 +132,7 @@ const PurchaseFieldset = ({ }} onMouseLeave={() => { if (!is_disabled) { - onHoverPurchase(false); + onHoverPurchase(false, type); } }} > @@ -151,29 +176,4 @@ const PurchaseFieldset = ({ ); }; -PurchaseFieldset.propTypes = { - basis: PropTypes.string, - buy_info: PropTypes.object, - currency: PropTypes.string, - growth_rate: PropTypes.number, - has_cancellation: PropTypes.bool, - index: PropTypes.number, - info: PropTypes.object, - is_accumulator: PropTypes.bool, - is_disabled: PropTypes.bool, - is_high_low: PropTypes.bool, - is_loading: PropTypes.bool, - is_market_closed: PropTypes.bool, - is_multiplier: PropTypes.bool, - is_proposal_empty: PropTypes.bool, - is_proposal_error: PropTypes.bool, - is_turbos: PropTypes.bool, - is_vanilla: PropTypes.bool, - onClickPurchase: PropTypes.func, - onHoverPurchase: PropTypes.func, - purchased_states_arr: PropTypes.array, - setPurchaseState: PropTypes.func, - type: PropTypes.string, -}; - export default React.memo(PurchaseFieldset); diff --git a/packages/trader/src/Modules/Trading/Components/Form/Purchase/cancel-deal-info.jsx b/packages/trader/src/Modules/Trading/Components/Form/Purchase/cancel-deal-info.tsx similarity index 82% rename from packages/trader/src/Modules/Trading/Components/Form/Purchase/cancel-deal-info.jsx rename to packages/trader/src/Modules/Trading/Components/Form/Purchase/cancel-deal-info.tsx index cca795272fd7..febf2f6cc8de 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/Purchase/cancel-deal-info.jsx +++ b/packages/trader/src/Modules/Trading/Components/Form/Purchase/cancel-deal-info.tsx @@ -5,19 +5,23 @@ import { isDesktop, isMobile, getDecimalPlaces } from '@deriv/shared'; import { localize } from '@deriv/translations'; import { observer } from '@deriv/stores'; import { useTraderStore } from 'Stores/useTraderStores'; +import { TProposalTypeInfo } from 'Types'; -const CancelDealInfo = observer(({ proposal_info }) => { +const CancelDealInfo = observer(({ proposal_info }: { proposal_info: TProposalTypeInfo }) => { const { currency, has_cancellation } = useTraderStore(); const { id, cancellation, has_error } = proposal_info; - const error = has_error || !id; + const error = has_error ?? !id; const [is_row_layout, setIsRowLayout] = React.useState(false); - const ref = React.useRef(null); + const ref = React.useRef(null); React.useEffect(() => { if (ref.current) { const el_height = ref.current.parentElement?.clientHeight; - if ((el_height > 21 && isDesktop()) || ((el_height > 21 || getDecimalPlaces(currency) > 2) && isMobile())) { + if ( + el_height && + ((el_height > 21 && isDesktop()) || ((el_height > 21 || getDecimalPlaces(currency) > 2) && isMobile())) + ) { setIsRowLayout(true); } else { setIsRowLayout(false); diff --git a/packages/trader/src/Modules/Trading/Components/Form/Purchase/contract-info.jsx b/packages/trader/src/Modules/Trading/Components/Form/Purchase/contract-info.jsx index 56eb0af097a3..209a7d98a52e 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/Purchase/contract-info.jsx +++ b/packages/trader/src/Modules/Trading/Components/Form/Purchase/contract-info.jsx @@ -4,7 +4,7 @@ import React from 'react'; import { DesktopWrapper, Icon, MobileWrapper, Money, Popover, Text } from '@deriv/components'; import { Localize, localize } from '@deriv/translations'; import { getContractSubtype, getCurrencyDisplayCode, getLocalizedBasis, getGrowthRatePercentage } from '@deriv/shared'; -import CancelDealInfo from './cancel-deal-info.jsx'; +import CancelDealInfo from './cancel-deal-info'; export const ValueMovement = ({ has_error_or_not_loaded, diff --git a/packages/trader/src/Modules/Trading/Components/Form/form-layout.jsx b/packages/trader/src/Modules/Trading/Components/Form/form-layout.tsx similarity index 75% rename from packages/trader/src/Modules/Trading/Components/Form/form-layout.jsx rename to packages/trader/src/Modules/Trading/Components/Form/form-layout.tsx index dccd9a3e0e1d..ee41fa37ba8e 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/form-layout.jsx +++ b/packages/trader/src/Modules/Trading/Components/Form/form-layout.tsx @@ -1,13 +1,17 @@ -import PropTypes from 'prop-types'; import React from 'react'; import Loadable from 'react-loadable'; import { isMobile } from '@deriv/shared'; +type TFormLayout = { + is_market_closed: boolean; + is_trade_enabled: boolean; +}; + const Screen = Loadable({ loader: () => isMobile() ? import(/* webpackChunkName: "screen-small" */ './screen-small.jsx') - : import(/* webpackChunkName: "screen-large" */ './screen-large.jsx'), + : import(/* webpackChunkName: "screen-large" */ './screen-large'), loading: () => null, render(loaded, props) { const Component = loaded.default; @@ -15,15 +19,10 @@ const Screen = Loadable({ }, }); -const FormLayout = ({ is_market_closed, is_trade_enabled }) => ( +const FormLayout = ({ is_market_closed, is_trade_enabled }: TFormLayout) => ( ); -FormLayout.propTypes = { - is_market_closed: PropTypes.bool, - is_trade_enabled: PropTypes.bool, -}; - export default React.memo(FormLayout); diff --git a/packages/trader/src/Modules/Trading/Components/Form/screen-large.jsx b/packages/trader/src/Modules/Trading/Components/Form/screen-large.tsx similarity index 79% rename from packages/trader/src/Modules/Trading/Components/Form/screen-large.jsx rename to packages/trader/src/Modules/Trading/Components/Form/screen-large.tsx index 2172fb8d55ad..0f481553b57c 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/screen-large.jsx +++ b/packages/trader/src/Modules/Trading/Components/Form/screen-large.tsx @@ -1,13 +1,16 @@ import classNames from 'classnames'; -import PropTypes from 'prop-types'; import React from 'react'; import { TradeParamsLoader } from 'App/Components/Elements/ContentLoader'; import Fieldset from 'App/Components/Form/fieldset.jsx'; import ContractType from '../../Containers/contract-type.jsx'; -import Purchase from '../../Containers/purchase.jsx'; +import Purchase from '../../Containers/purchase'; import TradeParams from '../../Containers/trade-params.jsx'; -const ScreenLarge = ({ is_market_closed, is_trade_enabled }) => ( +type TScreenLarge = { + is_market_closed?: boolean; + is_trade_enabled: boolean; +}; +const ScreenLarge = ({ is_market_closed = false, is_trade_enabled }: TScreenLarge) => (
(
); -ScreenLarge.propTypes = { - is_market_closed: PropTypes.bool, - is_trade_enabled: PropTypes.bool, -}; - export default ScreenLarge; diff --git a/packages/trader/src/Modules/Trading/Components/Form/screen-small.jsx b/packages/trader/src/Modules/Trading/Components/Form/screen-small.jsx index 8a2dc1843ed1..2fa26e0a46b4 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/screen-small.jsx +++ b/packages/trader/src/Modules/Trading/Components/Form/screen-small.jsx @@ -17,7 +17,7 @@ import AccumulatorsInfoDisplay from 'Modules/Trading/Components/Form/TradeParams import { BarrierMobile, LastDigitMobile } from 'Modules/Trading/Containers/trade-params-mobile.jsx'; import ContractType from 'Modules/Trading/Containers/contract-type.jsx'; import MobileWidget from 'Modules/Trading/Components/Elements/mobile-widget.jsx'; -import Purchase from 'Modules/Trading/Containers/purchase.jsx'; +import Purchase from 'Modules/Trading/Containers/purchase'; import RiskManagementInfo from 'Modules/Trading/Components/Elements/Multiplier/risk-management-info.jsx'; import TakeProfit from 'Modules/Trading/Components/Form/TradeParams/Multiplier/take-profit.jsx'; import 'Sass/app/_common/mobile-widget.scss'; diff --git a/packages/trader/src/Modules/Trading/Containers/contract-type.jsx b/packages/trader/src/Modules/Trading/Containers/contract-type.jsx index 5859840db86b..50138aeb9c6a 100644 --- a/packages/trader/src/Modules/Trading/Containers/contract-type.jsx +++ b/packages/trader/src/Modules/Trading/Containers/contract-type.jsx @@ -1,10 +1,9 @@ import React from 'react'; import { MobileWrapper, usePrevious } from '@deriv/components'; -import { unsupported_contract_types_list } from '@deriv/shared'; +import { getMarketNamesMap, unsupported_contract_types_list } from '@deriv/shared'; import { isDigitTradeType } from 'Modules/Trading/Helpers/digits'; import { localize } from '@deriv/translations'; import { ToastPopup } from 'Modules/Trading/Containers/toast-popup.jsx'; -import { getMarketNamesMap } from '../../../Constants'; import ContractTypeWidget from '../Components/Form/ContractType'; import { getAvailableContractTypes } from '../Helpers/contract-type'; import { useTraderStore } from 'Stores/useTraderStores'; diff --git a/packages/trader/src/Modules/Trading/Containers/purchase.jsx b/packages/trader/src/Modules/Trading/Containers/purchase.tsx similarity index 66% rename from packages/trader/src/Modules/Trading/Containers/purchase.jsx rename to packages/trader/src/Modules/Trading/Containers/purchase.tsx index bbbf1d2aee09..91da6c5bed3a 100644 --- a/packages/trader/src/Modules/Trading/Containers/purchase.jsx +++ b/packages/trader/src/Modules/Trading/Containers/purchase.tsx @@ -1,13 +1,26 @@ import React from 'react'; -import { isAccumulatorContract, isEmptyObject } from '@deriv/shared'; +import { getContractTypePosition, getSupportedContracts, isAccumulatorContract, isEmptyObject } from '@deriv/shared'; import { localize } from '@deriv/translations'; import PurchaseButtonsOverlay from 'Modules/Trading/Components/Elements/purchase-buttons-overlay.jsx'; -import PurchaseFieldset from 'Modules/Trading/Components/Elements/purchase-fieldset.jsx'; -import { getContractTypePosition } from 'Constants/contract'; +import PurchaseFieldset from 'Modules/Trading/Components/Elements/purchase-fieldset'; import { useTraderStore } from 'Stores/useTraderStores'; import { observer, useStore } from '@deriv/stores'; +import { TProposalTypeInfo } from 'Types'; -const Purchase = observer(({ is_market_closed }) => { +type TGetSupportedContractsKey = keyof ReturnType; + +const getSortedIndex = (type: string, index: number) => { + switch (getContractTypePosition(type as TGetSupportedContractsKey)) { + case 'top': + return 0; + case 'bottom': + return 1; + default: + return index; + } +}; + +const Purchase = observer(({ is_market_closed }: { is_market_closed: boolean }) => { const { portfolio: { active_positions }, ui: { purchase_states: purchased_states_arr, is_mobile, setPurchaseState }, @@ -33,23 +46,21 @@ const Purchase = observer(({ is_market_closed }) => { trade_types, is_trade_enabled, } = useTraderStore(); + const is_high_low = /^high_low$/.test(contract_type.toLowerCase()); - const isLoading = info => { + const isLoading = (info: TProposalTypeInfo | Record) => { const has_validation_error = Object.values(validation_errors).some(e => e.length); - return !has_validation_error && !info.has_error && !info.id; + return !has_validation_error && !info?.has_error && !info.id; }; const is_proposal_empty = isEmptyObject(proposal_info); const components = []; - Object.keys(trade_types).map((type, index) => { - const getSortedIndex = () => { - if (getContractTypePosition(type) === 'top') return 0; - if (getContractTypePosition(type) === 'bottom') return 1; - return index; - }; - const info = proposal_info[type] || {}; + + Object.keys(trade_types).forEach((type, index) => { + const info = proposal_info?.[type] || {}; const is_disabled = !is_trade_enabled || !info.id || !is_purchase_enabled; + const is_accum_or_mult_error = info?.has_error && !!info?.message; const is_proposal_error = - is_multiplier || (is_accumulator && !is_mobile) ? info.has_error && !!info.message : info.has_error; + is_multiplier || (is_accumulator && !is_mobile) ? is_accum_or_mult_error : info?.has_error; const purchase_fieldset = ( { currency={currency} growth_rate={growth_rate} info={info} - key={index} - index={getSortedIndex(index, type)} + key={type} + index={getSortedIndex(type, index)} has_cancellation={has_cancellation} is_accumulator={is_accumulator} is_disabled={is_disabled} is_high_low={is_high_low} is_loading={isLoading(info)} is_market_closed={is_market_closed} - is_mobile={is_mobile} is_multiplier={is_multiplier} is_turbos={is_turbos} is_vanilla={is_vanilla} is_proposal_empty={is_proposal_empty} - is_proposal_error={is_proposal_error} + is_proposal_error={!!is_proposal_error} purchased_states_arr={purchased_states_arr} onHoverPurchase={onHoverPurchase} onClickPurchase={onClickPurchase} @@ -79,19 +89,12 @@ const Purchase = observer(({ is_market_closed }) => { /> ); - if (!is_vanilla) { - switch (getContractTypePosition(type)) { - case 'top': - components.unshift(purchase_fieldset); - break; - case 'bottom': - components.push(purchase_fieldset); - break; - default: - components.push(purchase_fieldset); - break; - } - } else if (vanilla_trade_type === type) { + if (!is_vanilla && getContractTypePosition(type as TGetSupportedContractsKey) === 'top') { + components.unshift(purchase_fieldset); + } else if ( + (!is_vanilla && getContractTypePosition(type as TGetSupportedContractsKey) !== 'top') || + vanilla_trade_type === type + ) { components.push(purchase_fieldset); } }); @@ -111,7 +114,8 @@ const Purchase = observer(({ is_market_closed }) => { /> ); } - return components; + + return components as unknown as JSX.Element; }); export default Purchase; diff --git a/packages/trader/src/Modules/Trading/Containers/trade.jsx b/packages/trader/src/Modules/Trading/Containers/trade.jsx index a65dcbddf672..96af7ddf148e 100644 --- a/packages/trader/src/Modules/Trading/Containers/trade.jsx +++ b/packages/trader/src/Modules/Trading/Containers/trade.jsx @@ -2,12 +2,12 @@ import React from 'react'; import classNames from 'classnames'; import { DesktopWrapper, Div100vhContainer, MobileWrapper, SwipeableWrapper } from '@deriv/components'; import { getDecimalPlaces, isDesktop, isMobile } from '@deriv/shared'; -import ChartLoader from 'App/Components/Elements/chart-loader.jsx'; +import ChartLoader from 'App/Components/Elements/chart-loader'; import PositionsDrawer from 'App/Components/Elements/PositionsDrawer'; import MarketIsClosedOverlay from 'App/Components/Elements/market-is-closed-overlay.jsx'; import Test from './test.jsx'; import { ChartBottomWidgets, ChartTopWidgets, DigitsWidget } from './chart-widgets.jsx'; -import FormLayout from '../Components/Form/form-layout.jsx'; +import FormLayout from '../Components/Form/form-layout'; import AllMarkers from '../../SmartChart/Components/all-markers.jsx'; import AccumulatorsChartElements from '../../SmartChart/Components/Markers/accumulators-chart-elements.jsx'; import ToolbarWidgets from '../../SmartChart/Components/toolbar-widgets.jsx'; diff --git a/packages/trader/src/Stores/Modules/SmartChart/Constants/markers.js b/packages/trader/src/Stores/Modules/SmartChart/Constants/markers.js deleted file mode 100644 index 987f95e37c48..000000000000 --- a/packages/trader/src/Stores/Modules/SmartChart/Constants/markers.js +++ /dev/null @@ -1,59 +0,0 @@ -import { localize } from '@deriv/translations'; -import MarkerLine from 'Modules/SmartChart/Components/Markers/marker-line.jsx'; -import MarkerSpotLabel from 'Modules/SmartChart/Components/Markers/marker-spot-label.jsx'; -import MarkerSpot from 'Modules/SmartChart/Components/Markers/marker-spot.jsx'; - -export const MARKER_TYPES_CONFIG = { - LINE_END: { - type: 'LINE_END', - marker_config: { - ContentComponent: MarkerLine, - className: 'chart-marker-line', - }, - content_config: { line_style: 'dash', label: localize('End Time') }, - }, - LINE_PURCHASE: { - type: 'LINE_PURCHASE', - marker_config: { - ContentComponent: MarkerLine, - className: 'chart-marker-line', - }, - content_config: { line_style: 'solid', label: localize('Purchase Time') }, - }, - LINE_START: { - type: 'LINE_START', - marker_config: { - ContentComponent: MarkerLine, - className: 'chart-marker-line', - }, - content_config: { line_style: 'solid', label: localize('Start Time') }, - }, - SPOT_ENTRY: { - type: 'SPOT_ENTRY', - marker_config: { - ContentComponent: MarkerSpot, - }, - content_config: { className: 'chart-spot__entry' }, - }, - SPOT_SELL: { - type: 'SPOT_SELL', - marker_config: { - ContentComponent: MarkerSpot, - }, - content_config: { className: 'chart-spot__sell' }, - }, - SPOT_EXIT: { - type: 'SPOT_EXIT', - marker_config: { - ContentComponent: MarkerSpotLabel, - }, - content_config: { spot_className: 'chart-spot__spot' }, - }, - SPOT_MIDDLE: { - type: 'SPOT_MIDDLE', - marker_config: { - ContentComponent: MarkerSpotLabel, - }, - content_config: { spot_className: 'chart-spot__spot' }, - }, -}; diff --git a/packages/trader/src/Stores/Modules/SmartChart/Helpers/__tests__/barriers.js b/packages/trader/src/Stores/Modules/SmartChart/Helpers/__tests__/barriers.js deleted file mode 100644 index 10fa8830af0d..000000000000 --- a/packages/trader/src/Stores/Modules/SmartChart/Helpers/__tests__/barriers.js +++ /dev/null @@ -1,58 +0,0 @@ -import React from 'react'; -import * as Barriers from '../barriers'; - -describe('Barriers', () => { - describe('isBarrierSupported', () => { - it('should return false when barrier is not in CONTRACT_SHADES', () => { - expect(Barriers.isBarrierSupported('SomeThinMadeUp')).toEqual(false); - }); - it('should return true when barrier is in CONTRACT_SHADES', () => { - expect(Barriers.isBarrierSupported('CALL')).toEqual(true); - }); - }); - - describe('barriersToString', () => { - it('should convert non-zero barriers which do not have +/- to string consisting of them without +/- while is_relative is false', () => { - expect(Barriers.barriersToString(false, 10, 15)).toEqual(['10', '15']); - }); - it('should convert values without +/- and zero to string consisting of them without +/- while is_relative is false', () => { - expect(Barriers.barriersToString(false, 0, 15)).toEqual(['0', '15']); - }); - it('should convert barriers which have +/- to string consisting of them without +/- while is_relative is false', () => { - expect(Barriers.barriersToString(false, +11, 15)).toEqual(['11', '15']); - }); - it('should convert barriers which have +/- to string consisting of them with +/- while is_relative is true', () => { - expect(Barriers.barriersToString(true, +11, +15)).toEqual(['+11', '+15']); - }); - }); - - describe('barriersObjectToArray', () => { - const main = { - color: 'green', - draggable: false, - }; - it('should return an array from values in barriers object', () => { - const barriers = { - main, - }; - expect(Barriers.barriersObjectToArray(barriers, [])).toEqual([ - { - color: 'green', - draggable: false, - }, - ]); - }); - it('should return an array from values in barriers object (empty values should be filtered out)', () => { - const barriers = { - main, - somethingEmpty: {}, - }; - expect(Barriers.barriersObjectToArray(barriers, [])).toEqual([ - { - color: 'green', - draggable: false, - }, - ]); - }); - }); -}); diff --git a/packages/trader/src/Stores/Modules/SmartChart/Helpers/__tests__/barriers.ts b/packages/trader/src/Stores/Modules/SmartChart/Helpers/__tests__/barriers.ts new file mode 100644 index 000000000000..ec9371f9b2ad --- /dev/null +++ b/packages/trader/src/Stores/Modules/SmartChart/Helpers/__tests__/barriers.ts @@ -0,0 +1,50 @@ +import * as Barriers from '../barriers'; + +describe('Barriers', () => { + describe('barriersToString', () => { + it('should convert non-zero barriers which do not have +/- to string consisting of them without +/- while is_relative is false', () => { + expect(Barriers.barriersToString(false, 10, 15)).toEqual(['10', '15']); + }); + it('should convert values without +/- and zero to string consisting of them without +/- while is_relative is false', () => { + expect(Barriers.barriersToString(false, 0, 15)).toEqual(['0', '15']); + }); + it('should convert barriers which have +/- to string consisting of them without +/- while is_relative is false', () => { + expect(Barriers.barriersToString(false, +11, 15)).toEqual(['11', '15']); + }); + it('should convert barriers which have +/- to string consisting of them with +/- while is_relative is true', () => { + expect(Barriers.barriersToString(true, +11, +15)).toEqual(['+11', '+15']); + }); + }); + describe('removeBarrier', () => { + let barriers: Barriers.TBarrier[]; + const BARRIERS_KEYS = { + PURCHASE_SPOT_BARRIER: 'PURCHASE_SPOT_BARRIER', + TAKE_PROFIT: 'take_profit', + STOP_LOSS: 'stop_loss', + STOP_OUT: 'stop_out', + }; + beforeEach(() => { + barriers = [ + { key: BARRIERS_KEYS.PURCHASE_SPOT_BARRIER, high: '1111.11' }, + { key: BARRIERS_KEYS.TAKE_PROFIT, high: '2222.22' }, + { key: BARRIERS_KEYS.STOP_OUT, high: '3333.33' }, + ] as Barriers.TBarrier[]; + }); + it('should remove the barrier with a specified key from initial barriers array', () => { + const key_to_remove = BARRIERS_KEYS.TAKE_PROFIT; + Barriers.removeBarrier(barriers, key_to_remove); + expect(barriers.find(barrier => barrier.key === key_to_remove)).toBeUndefined(); + expect(barriers.length).toEqual(2); + }); + it('should not remove any barriers if the key is not found', () => { + Barriers.removeBarrier(barriers, BARRIERS_KEYS.STOP_LOSS); + expect(barriers.length).toEqual(3); + }); + it('should not modify the barriers array if it is empty', () => { + const key_to_remove = BARRIERS_KEYS.STOP_OUT; + const empty_barriers = [] as Barriers.TBarrier[]; + Barriers.removeBarrier(empty_barriers, key_to_remove); + expect(empty_barriers.length).toEqual(0); + }); + }); +}); diff --git a/packages/trader/src/Stores/Modules/SmartChart/Helpers/barriers.js b/packages/trader/src/Stores/Modules/SmartChart/Helpers/barriers.js deleted file mode 100644 index ca001aee0023..000000000000 --- a/packages/trader/src/Stores/Modules/SmartChart/Helpers/barriers.js +++ /dev/null @@ -1,27 +0,0 @@ -import { toJS } from 'mobx'; -import { isEmptyObject, CONTRACT_SHADES } from '@deriv/shared'; - -export const isBarrierSupported = contract_type => contract_type in CONTRACT_SHADES; - -export const barriersToString = (is_relative, ...barriers_list) => - barriers_list - .filter(barrier => barrier !== undefined && barrier !== null) - .map(barrier => `${is_relative && !/^[+-]/.test(barrier) ? '+' : ''}${barrier}`); - -export const barriersObjectToArray = (barriers, reference_array) => { - Object.keys(barriers).forEach(barrier => { - const js_object = toJS(barriers[barrier]); - if (!isEmptyObject(js_object)) { - reference_array.push(js_object); - } - }); - - return reference_array; -}; - -export const removeBarrier = (barriers, key) => { - const index = barriers.findIndex(b => b.key === key); - if (index > -1) { - barriers.splice(index, 1); - } -}; diff --git a/packages/trader/src/Stores/Modules/SmartChart/Helpers/barriers.ts b/packages/trader/src/Stores/Modules/SmartChart/Helpers/barriers.ts new file mode 100644 index 000000000000..61c5371b8b28 --- /dev/null +++ b/packages/trader/src/Stores/Modules/SmartChart/Helpers/barriers.ts @@ -0,0 +1,18 @@ +import type { ChartBarrierStore } from '../chart-barrier-store'; + +export type TBarrier = ChartBarrierStore & { key?: string }; + +export const barriersToString = ( + is_relative: boolean, + ...barriers_list: Array +): Array => + barriers_list + .filter(barrier => barrier !== undefined && barrier !== null) + .map(barrier => `${is_relative && !/^[+-]/.test(barrier?.toString() ?? '') ? '+' : ''}${barrier}`); + +export const removeBarrier = (barriers: TBarrier[], key: string) => { + const index = barriers.findIndex(b => b.key === key); + if (index > -1) { + barriers.splice(index, 1); + } +}; diff --git a/packages/trader/src/Stores/Modules/SmartChart/chart-barrier-store.js b/packages/trader/src/Stores/Modules/SmartChart/chart-barrier-store.ts similarity index 52% rename from packages/trader/src/Stores/Modules/SmartChart/chart-barrier-store.js rename to packages/trader/src/Stores/Modules/SmartChart/chart-barrier-store.ts index 718086c6ee9c..493dfe14a2b4 100644 --- a/packages/trader/src/Stores/Modules/SmartChart/chart-barrier-store.js +++ b/packages/trader/src/Stores/Modules/SmartChart/chart-barrier-store.ts @@ -2,26 +2,38 @@ import { action, computed, observable, makeObservable } from 'mobx'; import { BARRIER_COLORS, BARRIER_LINE_STYLES, CONTRACT_SHADES, DEFAULT_SHADES } from '@deriv/shared'; import { barriersToString } from './Helpers/barriers'; -export class ChartBarrierStore { - color; - lineStyle; - shade; - shadeColor; - - high; - low; - - relative; - draggable; +type TOnChartBarrierChange = null | ((barrier_1: string, barrier_2?: string) => void); +type TOnChangeParams = { high: string | number; low?: string | number }; +type TChartBarrierStoreOptions = + | { + color: string; + line_style?: string; + not_draggable?: boolean; + } + | Record; - hidePriceLines; - hideBarrierLine; - hideOffscreenLine; - title; - - onChartBarrierChange; - - constructor(high_barrier, low_barrier, onChartBarrierChange = null, { color, line_style, not_draggable } = {}) { +export class ChartBarrierStore { + color: string; + lineStyle: string; + shade?: string; + shadeColor?: string; + high?: string | number; + low?: string | number; + onChange: (barriers: TOnChangeParams) => void; + relative: boolean; + draggable: boolean; + hidePriceLines: boolean; + hideBarrierLine?: boolean; + hideOffscreenLine?: boolean; + title?: string; + onChartBarrierChange: TOnChartBarrierChange | null; + + constructor( + high_barrier: string | number, + low_barrier?: string | number, + onChartBarrierChange: TOnChartBarrierChange = null, + { color, line_style, not_draggable }: TChartBarrierStoreOptions = {} + ) { makeObservable(this, { color: observable, lineStyle: observable, @@ -49,7 +61,7 @@ export class ChartBarrierStore { // trade_store's action to process new barriers on dragged this.onChartBarrierChange = - typeof onChartBarrierChange === 'function' ? onChartBarrierChange.bind(this) : () => {}; + typeof onChartBarrierChange === 'function' ? onChartBarrierChange.bind(this) : () => undefined; this.high = high_barrier || 0; // 0 to follow the price if (low_barrier) { @@ -59,37 +71,38 @@ export class ChartBarrierStore { this.shade = this.default_shade; const has_barrier = !!high_barrier; - this.relative = !has_barrier || /^[+-]/.test(high_barrier); + this.relative = !has_barrier || /^[+-]/.test(high_barrier.toString()); this.draggable = !not_draggable && has_barrier; this.hidePriceLines = !has_barrier; } - updateBarriers(high, low, isFromChart = false) { + updateBarriers(high: string | number, low?: string | number, isFromChart = false) { if (!isFromChart) { - this.relative = /^[+-]/.test(high); + this.relative = /^[+-]/.test(high.toString()); } this.high = high || undefined; this.low = low || undefined; } - updateBarrierShade(should_display, contract_type) { - this.shade = (should_display && CONTRACT_SHADES[contract_type]) || this.default_shade; + updateBarrierShade(should_display: boolean, contract_type: string) { + this.shade = + (should_display && CONTRACT_SHADES[contract_type as keyof typeof CONTRACT_SHADES]) || this.default_shade; } - onBarrierChange({ high, low }) { + onBarrierChange({ high, low }: TOnChangeParams) { this.updateBarriers(high, low, true); - this.onChartBarrierChange(...barriersToString(this.relative, high, low)); + this.onChartBarrierChange?.(...(barriersToString(this.relative, high, low) as [string, string | undefined])); } - updateBarrierColor(is_dark_mode) { + updateBarrierColor(is_dark_mode: boolean) { this.color = is_dark_mode ? BARRIER_COLORS.DARK_GRAY : BARRIER_COLORS.GRAY; } - get barrier_count() { - return (typeof this.high !== 'undefined') + (typeof this.low !== 'undefined'); + get barrier_count(): number { + return +(typeof this.high !== 'undefined') + +(typeof this.low !== 'undefined'); } get default_shade() { - return DEFAULT_SHADES[this.barrier_count]; + return DEFAULT_SHADES[this.barrier_count as keyof typeof DEFAULT_SHADES]; } } diff --git a/packages/trader/src/Stores/Modules/SmartChart/chart-marker-store.js b/packages/trader/src/Stores/Modules/SmartChart/chart-marker-store.js deleted file mode 100644 index c501b8e827fe..000000000000 --- a/packages/trader/src/Stores/Modules/SmartChart/chart-marker-store.js +++ /dev/null @@ -1,16 +0,0 @@ -import { observable, makeObservable } from 'mobx'; - -export class ChartMarkerStore { - marker_config = observable.object({}); - content_config = observable.object({}); - - constructor(marker_config, content_config) { - makeObservable(this, { - marker_config: observable, - content_config: observable, - }); - - this.marker_config = marker_config; - this.content_config = content_config; - } -} diff --git a/packages/trader/src/Stores/Modules/Trading/Actions/barrier.js b/packages/trader/src/Stores/Modules/Trading/Actions/barrier.js deleted file mode 100644 index 16f4d3b9d165..000000000000 --- a/packages/trader/src/Stores/Modules/Trading/Actions/barrier.js +++ /dev/null @@ -1,4 +0,0 @@ -export const getBarrierValues = ({ barrier_values = {} }) => ({ - barrier_1: barrier_values.barrier || barrier_values.high_barrier || '', - barrier_2: barrier_values.low_barrier || '', -}); diff --git a/packages/trader/src/Stores/Modules/Trading/Actions/contract-type.js b/packages/trader/src/Stores/Modules/Trading/Actions/contract-type.js deleted file mode 100644 index 762929a981e8..000000000000 --- a/packages/trader/src/Stores/Modules/Trading/Actions/contract-type.js +++ /dev/null @@ -1,6 +0,0 @@ -import { ContractType } from 'Stores/Modules/Trading/Helpers/contract-type'; - -export const onChangeContractTypeList = ({ contract_types_list, contract_type }) => { - return ContractType.getContractType(contract_types_list, contract_type); -}; -export const onChangeContractType = store => ContractType.getContractValues(store); diff --git a/packages/trader/src/Stores/Modules/Trading/Actions/contract-type.ts b/packages/trader/src/Stores/Modules/Trading/Actions/contract-type.ts new file mode 100644 index 000000000000..c5552ff614bb --- /dev/null +++ b/packages/trader/src/Stores/Modules/Trading/Actions/contract-type.ts @@ -0,0 +1,34 @@ +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 }) => { + return ContractType.getContractType(contract_types_list, contract_type); +}; +export const onChangeContractType = (store: TTradeStore): TContractValues => { + return ContractType.getContractValues(store); +}; diff --git a/packages/trader/src/Stores/Modules/Trading/Actions/currency.js b/packages/trader/src/Stores/Modules/Trading/Actions/currency.js deleted file mode 100644 index e08d6e359142..000000000000 --- a/packages/trader/src/Stores/Modules/Trading/Actions/currency.js +++ /dev/null @@ -1,16 +0,0 @@ -import { WS } from '@deriv/shared'; -import { buildCurrenciesList, getDefaultCurrency } from '../Helpers/currency'; - -export const getCurrenciesAsync = async currency => { - const response = await WS.authorized.storage.payoutCurrencies(); - - const currencies_list = buildCurrenciesList( - Array.isArray(response?.payout_currencies) ? response.payout_currencies : [] - ); - const default_currency = getDefaultCurrency(currencies_list, currency); - - return { - currencies_list, - ...default_currency, - }; -}; diff --git a/packages/trader/src/Stores/Modules/Trading/Actions/duration.js b/packages/trader/src/Stores/Modules/Trading/Actions/duration.ts similarity index 55% rename from packages/trader/src/Stores/Modules/Trading/Actions/duration.js rename to packages/trader/src/Stores/Modules/Trading/Actions/duration.ts index 070d9d7c5555..025251f3ea06 100644 --- a/packages/trader/src/Stores/Modules/Trading/Actions/duration.js +++ b/packages/trader/src/Stores/Modules/Trading/Actions/duration.ts @@ -1,7 +1,18 @@ import { getExpiryType, getDurationMinMaxValues } from '@deriv/shared'; import { ContractType } from 'Stores/Modules/Trading/Helpers/contract-type'; +import { TTradeStore } from 'Types'; -export const onChangeExpiry = store => { +type TOnChangeExpiry = (store: TTradeStore) => { + contract_expiry_type: string; + barrier_count?: number; + barrier_1?: string; + barrier_2?: string; +}; +type TAssertDurationParams = Partial< + Pick +>; + +export const onChangeExpiry: TOnChangeExpiry = store => { const contract_expiry_type = getExpiryType(store); // TODO: there will be no barrier available if contract is only daily but client chooses intraday endtime. we should find a way to handle this. @@ -15,7 +26,7 @@ export const onChangeExpiry = store => { }; }; -export const onChangeContractType = store => { +export const onChangeContractType = (store: TTradeStore) => { const contract_expiry_type = getExpiryType(store); const { duration, duration_min_max, duration_unit } = store; @@ -27,13 +38,18 @@ export const onChangeContractType = store => { }; }; -const assertDuration = ({ contract_expiry_type, duration, duration_min_max, duration_unit } = {}) => { - const [min, max] = getDurationMinMaxValues(duration_min_max, contract_expiry_type, duration_unit); +const assertDuration = ({ + contract_expiry_type, + duration, + duration_min_max, + duration_unit, +}: TAssertDurationParams = {}) => { + const [min, max] = getDurationMinMaxValues(duration_min_max ?? {}, contract_expiry_type ?? '', duration_unit ?? ''); - if (duration < min) { + if (duration && min && duration < min) { return { duration: min }; } - if (duration > max) { + if (duration && max && duration > max) { return { duration: max }; } return {}; diff --git a/packages/trader/src/Stores/Modules/Trading/Actions/purchase.js b/packages/trader/src/Stores/Modules/Trading/Actions/purchase.js deleted file mode 100644 index 3f3cee01270a..000000000000 --- a/packages/trader/src/Stores/Modules/Trading/Actions/purchase.js +++ /dev/null @@ -1,8 +0,0 @@ -import { WS } from '@deriv/shared'; - -export const processPurchase = async (proposal_id, price, passthrough) => - WS.buy({ - proposal_id, - price, - ...(passthrough && { passthrough }), - }); diff --git a/packages/trader/src/Stores/Modules/Trading/Actions/purchase.ts b/packages/trader/src/Stores/Modules/Trading/Actions/purchase.ts new file mode 100644 index 000000000000..f2a8062a8ebc --- /dev/null +++ b/packages/trader/src/Stores/Modules/Trading/Actions/purchase.ts @@ -0,0 +1,13 @@ +import { Buy, BuyContractRequest } from '@deriv/api-types'; +import { WS } from '@deriv/shared'; + +export const processPurchase = async ( + proposal_id: string, + price: BuyContractRequest['price'], + passthrough: BuyContractRequest['passthrough'] +): Promise => + WS.buy({ + proposal_id, + price, + ...(passthrough && { passthrough }), + }); diff --git a/packages/trader/src/Stores/Modules/Trading/Actions/start-date.js b/packages/trader/src/Stores/Modules/Trading/Actions/start-date.ts similarity index 74% rename from packages/trader/src/Stores/Modules/Trading/Actions/start-date.js rename to packages/trader/src/Stores/Modules/Trading/Actions/start-date.ts index 4f4f0f8ff351..2740abf35023 100644 --- a/packages/trader/src/Stores/Modules/Trading/Actions/start-date.js +++ b/packages/trader/src/Stores/Modules/Trading/Actions/start-date.ts @@ -1,9 +1,30 @@ import { ContractType } from 'Stores/Modules/Trading/Helpers/contract-type'; +import { TTradeStore } from 'Types'; -export const onChangeStartDate = async store => { +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 => { const { contract_type, duration_unit, start_date } = store; const server_time = store.root_store.common.server_time; - let { start_time, expiry_date, expiry_type } = store; + 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 @@ -12,7 +33,6 @@ export const onChangeStartDate = async store => { const obj_sessions = ContractType.getSessions(contract_type, start_date); const sessions = obj_sessions.sessions; const obj_start_time = ContractType.getStartTime(sessions, start_date, start_time); - start_time = obj_start_time.start_time; const obj_duration_units_list = ContractType.getDurationUnitsList(contract_type, contract_start_type); const duration_units_list = obj_duration_units_list.duration_units_list; @@ -20,8 +40,7 @@ export const onChangeStartDate = async store => { const obj_expiry_type = ContractType.getExpiryType(duration_units_list, expiry_type); expiry_type = obj_expiry_type.expiry_type; - const obj_expiry_date = ContractType.getExpiryDate(duration_units_list, expiry_date, expiry_type, start_date); - expiry_date = obj_expiry_date.expiry_date; + const obj_expiry_date = ContractType.getExpiryDate(duration_units_list, store.expiry_date, expiry_type, start_date); const obj_duration_min_max = ContractType.getDurationMinMax(contract_type, contract_start_type); @@ -37,7 +56,7 @@ export const onChangeStartDate = async store => { }; }; -export const onChangeExpiry = async store => { +export const onChangeExpiry: TOnChangeExpiry = async store => { 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/Actions/symbol.js b/packages/trader/src/Stores/Modules/Trading/Actions/symbol.ts similarity index 63% rename from packages/trader/src/Stores/Modules/Trading/Actions/symbol.js rename to packages/trader/src/Stores/Modules/Trading/Actions/symbol.ts index 95af64de0647..1887dc2b2c9f 100644 --- a/packages/trader/src/Stores/Modules/Trading/Actions/symbol.js +++ b/packages/trader/src/Stores/Modules/Trading/Actions/symbol.ts @@ -1,5 +1,5 @@ import { ContractType } from 'Stores/Modules/Trading/Helpers/contract-type'; -export const onChangeSymbolAsync = async symbol => { +export const onChangeSymbolAsync = async (symbol: string): Promise => { await ContractType.buildContractTypesConfig(symbol); }; diff --git a/packages/trader/src/Stores/Modules/Trading/Actions/test.js b/packages/trader/src/Stores/Modules/Trading/Actions/test.js deleted file mode 100644 index 6726e3bf70cc..000000000000 --- a/packages/trader/src/Stores/Modules/Trading/Actions/test.js +++ /dev/null @@ -1,20 +0,0 @@ -import { WS } from '@deriv/shared'; - -/* This action does not modify state directlly. - * The payload will be the callback that get's called for each tick - */ -let cb; -const ticksCallback = response => { - const data = response.error - ? response.error.message - : `${new Date(response.tick.epoch * 1000).toUTCString()}: ${response.tick.quote}`; - cb(data); -}; - -export const getTicks = function ({ symbol }, callback) { - cb = callback; - WS.forgetAll('ticks').then(() => { - WS.subscribeTicks(symbol, ticksCallback); - }); - return {}; -}; diff --git a/packages/trader/src/Stores/Modules/Trading/Actions/test.ts b/packages/trader/src/Stores/Modules/Trading/Actions/test.ts new file mode 100644 index 000000000000..b055e1b8f1ae --- /dev/null +++ b/packages/trader/src/Stores/Modules/Trading/Actions/test.ts @@ -0,0 +1,27 @@ +import { TicksStreamResponse } from '@deriv/api-types'; +import { WS } from '@deriv/shared'; + +type TCallback = (data: string) => void; +/* This action does not modify state directlly. + * The payload will be the callback that get's called for each tick + */ +let cb: TCallback; +const ticksCallback = (response: TicksStreamResponse) => { + if ( + response.error && + typeof response.error === 'object' && + 'message' in response.error && + typeof response.error.message === 'string' + ) { + return cb(response.error.message); + } + return cb(`${new Date(Number(response.tick?.epoch) * 1000).toUTCString()}: ${response.tick?.quote}`); +}; + +export const getTicks = function ({ symbol }: { symbol: string }, callback: TCallback) { + cb = callback; + WS.forgetAll('ticks').then(() => { + WS.subscribeTicks(symbol, ticksCallback); + }); + return {}; +}; diff --git a/packages/trader/src/Stores/Modules/Trading/Constants/ui.js b/packages/trader/src/Stores/Modules/Trading/Constants/ui.js deleted file mode 100644 index 325fcda6e315..000000000000 --- a/packages/trader/src/Stores/Modules/Trading/Constants/ui.js +++ /dev/null @@ -1,11 +0,0 @@ -import Amount from 'Modules/Trading/Components/Form/TradeParams/amount.jsx'; -import Barrier from 'Modules/Trading/Components/Form/TradeParams/barrier.jsx'; -import Duration from 'Modules/Trading/Components/Form/TradeParams/Duration'; -import LastDigit from 'Modules/Trading/Components/Form/TradeParams/last-digit.jsx'; - -export const form_components = [ - { name: 'duration', Component: Duration }, - { name: 'barrier', Component: Barrier }, - { name: 'last_digit', Component: LastDigit }, - { name: 'amount', Component: Amount }, -]; diff --git a/packages/trader/src/Stores/Modules/Trading/Constants/validation-rules.js b/packages/trader/src/Stores/Modules/Trading/Constants/validation-rules.js deleted file mode 100644 index 3b50b9ad2d90..000000000000 --- a/packages/trader/src/Stores/Modules/Trading/Constants/validation-rules.js +++ /dev/null @@ -1,187 +0,0 @@ -import { localize } from '@deriv/translations'; -import { isHourValid, isMinuteValid, isTimeValid, toMoment } from '@deriv/shared'; -import { isSessionAvailable } from '../Helpers/start-date'; - -const tradeSpecificBarrierCheck = (is_vanilla, input) => is_vanilla || input !== 0; - -export const getValidationRules = () => ({ - amount: { - rules: [ - ['req', { message: localize('Amount is a required field.') }], - ['number', { min: 0, type: 'float' }], - ], - }, - barrier_1: { - rules: [ - [ - 'req', - { - condition: store => store.barrier_count && store.form_components.indexOf('barrier') > -1, - message: localize('Barrier is a required field.'), - }, - ], - ['barrier', { condition: store => store.barrier_count }], - [ - 'custom', - { - func: (value, options, store, inputs) => - store.barrier_count > 1 ? +value > +inputs.barrier_2 : true, - message: localize('Higher barrier must be higher than lower barrier.'), - }, - ], - [ - 'custom', - { - func: (value, options, store, inputs) => - /^[+-]/.test(inputs.barrier_1) - ? tradeSpecificBarrierCheck(store.is_vanilla, +inputs.barrier_1) - : true, - message: localize('Barrier cannot be zero.'), - }, - ], - ], - trigger: 'barrier_2', - }, - barrier_2: { - rules: [ - [ - 'req', - { - condition: store => store.barrier_count > 1 && store.form_components.indexOf('barrier') > -1, - message: localize('Barrier is a required field.'), - }, - ], - ['barrier', { condition: store => store.barrier_count }], - [ - 'custom', - { - func: (value, options, store, inputs) => - (/^[+-]/g.test(inputs.barrier_1) && /^[+-]/g.test(value)) || - (/^(?![+-])/g.test(inputs.barrier_1) && /^(?![+-])/g.test(value)), - message: localize('Both barriers should be relative or absolute'), - }, - ], - [ - 'custom', - { - func: (value, options, store, inputs) => +inputs.barrier_1 > +value, - message: localize('Lower barrier must be lower than higher barrier.'), - }, - ], - ], - trigger: 'barrier_1', - }, - duration: { - rules: [['req', { message: localize('Duration is a required field.') }]], - }, - start_date: { - trigger: 'start_time', - }, - expiry_date: { - trigger: 'expiry_time', - }, - start_time: { - rules: [ - [ - 'custom', - { - func: (value, options, store) => store.contract_start_type === 'spot' || isTimeValid(value), - message: localize('Please enter the start time in the format "HH:MM".'), - }, - ], - [ - 'custom', - { - func: (value, options, store) => store.contract_start_type === 'spot' || isHourValid(value), - message: localize('Hour must be between 0 and 23.'), - }, - ], - [ - 'custom', - { - func: (value, options, store) => store.contract_start_type === 'spot' || isMinuteValid(value), - message: localize('Minute must be between 0 and 59.'), - }, - ], - [ - 'custom', - { - func: (value, options, store) => { - if (store.contract_start_type === 'spot') return true; - if (!isTimeValid(value)) return false; - const start_moment = toMoment(store.start_date); - const start_moment_clone = start_moment.clone(); - const [h, m] = value.split(':'); - return isSessionAvailable(store.sessions, start_moment_clone.hour(h).minute(m), start_moment); - }, - message: localize('Start time cannot be in the past.'), - }, - ], - ], - }, - expiry_time: { - rules: [ - [ - 'custom', - { - func: (value, options, store) => store.contract_start_type === 'spot' || isTimeValid(value), - message: localize('Please enter the start time in the format "HH:MM".'), - }, - ], - [ - 'custom', - { - func: (value, options, store) => store.contract_start_type === 'spot' || isHourValid(value), - message: localize('Hour must be between 0 and 23.'), - }, - ], - [ - 'custom', - { - func: (value, options, store) => store.contract_start_type === 'spot' || isMinuteValid(value), - message: localize('Minute must be between 0 and 59.'), - }, - ], - [ - 'custom', - { - func: (value, options, store) => { - if (store.contract_start_type === 'spot') return true; - if (!isTimeValid(value)) return false; - const start_moment = toMoment(store.start_date); - const start_moment_clone = start_moment.clone(); - const [h, m] = value.split(':'); - return isSessionAvailable(store.sessions, start_moment_clone.hour(h).minute(m), start_moment); - }, - message: localize('Expiry time cannot be in the past.'), - }, - ], - ], - }, - ...getMultiplierValidationRules(), -}); - -export const getMultiplierValidationRules = () => ({ - stop_loss: { - rules: [ - [ - 'req', - { - condition: store => store.has_stop_loss && !store.stop_loss, - message: localize('Please enter a stop loss amount.'), - }, - ], - ], - }, - take_profit: { - rules: [ - [ - 'req', - { - condition: store => store.has_take_profit && !store.take_profit, - message: localize('Please enter a take profit amount.'), - }, - ], - ], - }, -}); diff --git a/packages/trader/src/Stores/Modules/Trading/Constants/validation-rules.ts b/packages/trader/src/Stores/Modules/Trading/Constants/validation-rules.ts new file mode 100644 index 000000000000..6911efae718e --- /dev/null +++ b/packages/trader/src/Stores/Modules/Trading/Constants/validation-rules.ts @@ -0,0 +1,234 @@ +import { localize } from '@deriv/translations'; +import { isHourValid, isMinuteValid, isTimeValid, toMoment } from '@deriv/shared'; +import { isSessionAvailable } from '../Helpers/start-date'; +import { TTradeStore } from 'Types'; +import type { TRuleOptions } from 'Utils/Validator/validator'; + +const tradeSpecificBarrierCheck = (is_vanilla: boolean, input: number) => is_vanilla || input !== 0; + +export const getValidationRules = () => + ({ + amount: { + rules: [ + ['req', { message: localize('Amount is a required field.') }], + ['number', { min: 0, type: 'float' }], + ], + }, + barrier_1: { + rules: [ + [ + 'req', + { + condition: (store: TTradeStore) => + store.barrier_count && store.form_components.indexOf('barrier') > -1, + message: localize('Barrier is a required field.'), + }, + ], + ['barrier', { condition: (store: TTradeStore) => store.barrier_count }], + [ + 'custom', + { + func: ( + value: TTradeStore['barrier_1'], + options: Partial, + store: TTradeStore, + inputs: Pick + ) => (store.barrier_count > 1 ? +value > +inputs.barrier_2 : true), + message: localize('Higher barrier must be higher than lower barrier.'), + }, + ], + [ + 'custom', + { + func: ( + value: TTradeStore['barrier_1'], + options: Partial, + store: TTradeStore, + inputs: Pick + ) => + /^[+-]/.test(inputs.barrier_1) + ? tradeSpecificBarrierCheck(store.is_vanilla, +inputs.barrier_1) + : true, + message: localize('Barrier cannot be zero.'), + }, + ], + ], + trigger: 'barrier_2', + }, + barrier_2: { + rules: [ + [ + 'req', + { + condition: (store: TTradeStore) => + store.barrier_count > 1 && store.form_components.indexOf('barrier') > -1, + message: localize('Barrier is a required field.'), + }, + ], + ['barrier', { condition: (store: TTradeStore) => store.barrier_count }], + [ + 'custom', + { + func: ( + value: TTradeStore['barrier_2'], + options: Partial, + store: TTradeStore, + inputs: Pick + ) => + (/^[+-]/g.test(inputs.barrier_1) && /^[+-]/g.test(value)) || + (/^(?![+-])/g.test(inputs.barrier_1) && /^(?![+-])/g.test(value)), + message: localize('Both barriers should be relative or absolute'), + }, + ], + [ + 'custom', + { + func: ( + value: TTradeStore['barrier_2'], + options: Partial, + store: TTradeStore, + inputs: Pick + ) => +inputs.barrier_1 > +value, + message: localize('Lower barrier must be lower than higher barrier.'), + }, + ], + ], + trigger: 'barrier_1', + }, + duration: { + rules: [['req', { message: localize('Duration is a required field.') }]], + }, + start_date: { + trigger: 'start_time', + }, + expiry_date: { + trigger: 'expiry_time', + }, + start_time: { + rules: [ + [ + 'custom', + { + func: (value: TTradeStore['start_time'], options: Partial, store: TTradeStore) => + store.contract_start_type === 'spot' || isTimeValid(value ?? ''), + message: localize('Please enter the start time in the format "HH:MM".'), + }, + ], + [ + 'custom', + { + func: (value: TTradeStore['start_time'], options: Partial, store: TTradeStore) => + store.contract_start_type === 'spot' || isHourValid(value ?? ''), + message: localize('Hour must be between 0 and 23.'), + }, + ], + [ + 'custom', + { + func: (value: TTradeStore['start_time'], options: Partial, store: TTradeStore) => + store.contract_start_type === 'spot' || isMinuteValid(value ?? ''), + message: localize('Minute must be between 0 and 59.'), + }, + ], + [ + 'custom', + { + func: ( + value: TTradeStore['start_time'], + options: Partial, + store: TTradeStore + ) => { + if (store.contract_start_type === 'spot') return true; + if (!isTimeValid(value ?? '')) return false; + const start_moment = toMoment(store.start_date); + const start_moment_clone = start_moment.clone(); + const [h, m] = value?.split(':') ?? []; + return isSessionAvailable( + store.sessions, + start_moment_clone.hour(+h).minute(+m), + start_moment + ); + }, + message: localize('Start time cannot be in the past.'), + }, + ], + ], + }, + expiry_time: { + rules: [ + [ + 'custom', + { + func: (value: TTradeStore['expiry_time'], options: Partial, store: TTradeStore) => + store.contract_start_type === 'spot' || isTimeValid(value ?? ''), + message: localize('Please enter the start time in the format "HH:MM".'), + }, + ], + [ + 'custom', + { + func: (value: TTradeStore['expiry_time'], options: Partial, store: TTradeStore) => + store.contract_start_type === 'spot' || isHourValid(value ?? ''), + message: localize('Hour must be between 0 and 23.'), + }, + ], + [ + 'custom', + { + func: (value: TTradeStore['expiry_time'], options: Partial, store: TTradeStore) => + store.contract_start_type === 'spot' || isMinuteValid(value ?? ''), + message: localize('Minute must be between 0 and 59.'), + }, + ], + [ + 'custom', + { + func: ( + value: TTradeStore['expiry_time'], + options: Partial, + store: TTradeStore + ) => { + if (store.contract_start_type === 'spot') return true; + if (!isTimeValid(value ?? '')) return false; + const start_moment = toMoment(store.start_date); + const start_moment_clone = start_moment.clone(); + const [h, m] = value?.split(':') ?? []; + return isSessionAvailable( + store.sessions, + start_moment_clone.hour(+h).minute(+m), + start_moment + ); + }, + message: localize('Expiry time cannot be in the past.'), + }, + ], + ], + }, + ...getMultiplierValidationRules(), + } as const); + +export const getMultiplierValidationRules = () => + ({ + stop_loss: { + rules: [ + [ + 'req', + { + condition: (store: TTradeStore) => store.has_stop_loss && !store.stop_loss, + message: localize('Please enter a stop loss amount.'), + }, + ], + ], + }, + take_profit: { + rules: [ + [ + 'req', + { + condition: (store: TTradeStore) => store.has_take_profit && !store.take_profit, + message: localize('Please enter a take profit amount.'), + }, + ], + ], + }, + } as const); diff --git a/packages/trader/src/Stores/Modules/Trading/Helpers/__tests__/accumulator.spec.js b/packages/trader/src/Stores/Modules/Trading/Helpers/__tests__/accumulator.spec.ts similarity index 100% rename from packages/trader/src/Stores/Modules/Trading/Helpers/__tests__/accumulator.spec.js rename to packages/trader/src/Stores/Modules/Trading/Helpers/__tests__/accumulator.spec.ts diff --git a/packages/trader/src/Stores/Modules/Trading/Helpers/__tests__/currency.js b/packages/trader/src/Stores/Modules/Trading/Helpers/__tests__/currency.spec.ts similarity index 98% rename from packages/trader/src/Stores/Modules/Trading/Helpers/__tests__/currency.js rename to packages/trader/src/Stores/Modules/Trading/Helpers/__tests__/currency.spec.ts index 3759356ef03d..498001862b80 100644 --- a/packages/trader/src/Stores/Modules/Trading/Helpers/__tests__/currency.js +++ b/packages/trader/src/Stores/Modules/Trading/Helpers/__tests__/currency.spec.ts @@ -1,4 +1,3 @@ -import React from 'react'; import { buildCurrenciesList, getDefaultCurrency } from '../currency'; describe('buildCurrenciesList', () => { diff --git a/packages/trader/src/Stores/Modules/Trading/Helpers/__tests__/proposal.js b/packages/trader/src/Stores/Modules/Trading/Helpers/__tests__/proposal.js index cae8a94fcfa9..e0b273a91d9f 100644 --- a/packages/trader/src/Stores/Modules/Trading/Helpers/__tests__/proposal.js +++ b/packages/trader/src/Stores/Modules/Trading/Helpers/__tests__/proposal.js @@ -30,6 +30,7 @@ describe('Proposal', () => { commission: undefined, error_code: undefined, error_field: undefined, + growth_rate: undefined, limit_order: undefined, id: '', message: 'This is error', @@ -40,6 +41,7 @@ describe('Proposal', () => { text: 'Stake', value: '', }, + spot: undefined, }); }); diff --git a/packages/trader/src/Stores/Modules/Trading/Helpers/accumulator.js b/packages/trader/src/Stores/Modules/Trading/Helpers/accumulator.ts similarity index 82% rename from packages/trader/src/Stores/Modules/Trading/Helpers/accumulator.js rename to packages/trader/src/Stores/Modules/Trading/Helpers/accumulator.ts index 31a1c8656a07..b9ae542dbeab 100644 --- a/packages/trader/src/Stores/Modules/Trading/Helpers/accumulator.js +++ b/packages/trader/src/Stores/Modules/Trading/Helpers/accumulator.ts @@ -5,15 +5,24 @@ * @param {number} last_tick_epoch - an epoch of the latest tick counted by the latest (last) ticks counter in new_ticks_stayed_in array; * @returns an object of the same type as previous_ticks_history_stats. */ + +import { TTradeStore } from 'Types'; + +type TGetUpdatedTicksHistoryStats = { + previous_ticks_history_stats: TTradeStore['ticks_history_stats']; + new_ticks_history_stats?: TTradeStore['ticks_history_stats']['ticks_stayed_in']; + last_tick_epoch: TTradeStore['ticks_history_stats']['last_tick_epoch']; +}; + export const getUpdatedTicksHistoryStats = ({ previous_ticks_history_stats = {}, new_ticks_history_stats = [], last_tick_epoch, -}) => { +}: TGetUpdatedTicksHistoryStats) => { // we anticipate that the latest counter value will be the last one in the received new_ticks_stayed_in array: let ticks_stayed_in = []; const previous_history = previous_ticks_history_stats.ticks_stayed_in || []; - const previous_epoch = previous_ticks_history_stats.last_tick_epoch; + const previous_epoch = previous_ticks_history_stats.last_tick_epoch ?? 0; if (!new_ticks_history_stats.length || !last_tick_epoch) return previous_ticks_history_stats; if (new_ticks_history_stats.length > 1) { ticks_stayed_in = [...new_ticks_history_stats].reverse(); diff --git a/packages/trader/src/Stores/Modules/Trading/Helpers/allow-equals.js b/packages/trader/src/Stores/Modules/Trading/Helpers/allow-equals.js deleted file mode 100644 index ab09aba5b08f..000000000000 --- a/packages/trader/src/Stores/Modules/Trading/Helpers/allow-equals.js +++ /dev/null @@ -1,41 +0,0 @@ -import { isEmptyObject, getPropertyValue } from '@deriv/shared'; -import { ContractType } from 'Stores/Modules/Trading/Helpers/contract-type'; - -export const hasCallPutEqual = contract_type_list => { - if (isEmptyObject(contract_type_list)) return false; - - return ((getPropertyValue(contract_type_list, 'Ups & Downs') || {}).categories || []).some( - contract => contract.value === 'rise_fall_equal' - ); -}; - -export const hasDurationForCallPutEqual = (contract_type_list, duration_unit, contract_start_type) => { - if (!contract_type_list || !duration_unit || !contract_start_type) return false; - - const contract_list = Object.keys(contract_type_list || {}).reduce( - (key, list) => [...key, ...contract_type_list[list].categories.map(contract => contract.value)], - [] - ); - - const contract_duration_list = contract_list.map(list => ({ - [list]: getPropertyValue(ContractType.getFullContractTypes(), [ - list, - 'config', - 'durations', - 'units_display', - contract_start_type, - ]), - })); - - // Check whether rise fall equal is exists and has the current store duration unit - if (hasCallPutEqual(contract_type_list)) { - const found = contract_duration_list.filter(contract => contract && contract.rise_fall_equal); - if (found.length > 0) { - return found[0].rise_fall_equal.some(duration => duration.value === duration_unit); - } - } - - return false; -}; - -export const isRiseFallEqual = contract_type => /^(rise_fall|rise_fall_equal)$/.test(contract_type); diff --git a/packages/trader/src/Stores/Modules/Trading/Helpers/allow-equals.ts b/packages/trader/src/Stores/Modules/Trading/Helpers/allow-equals.ts new file mode 100644 index 000000000000..4b0a7c7f9fae --- /dev/null +++ b/packages/trader/src/Stores/Modules/Trading/Helpers/allow-equals.ts @@ -0,0 +1,55 @@ +import { isEmptyObject, getPropertyValue } from '@deriv/shared'; +import { ContractType } from 'Stores/Modules/Trading/Helpers/contract-type'; +import { PriceProposalRequest } from '@deriv/api-types'; +import { TTradeStore } from 'Types'; + +type THasDurationForCallPutEqual = { + contract_type_list: TTradeStore['contract_types_list']; + duration_unit: PriceProposalRequest['duration_unit']; + contract_start_type: string; +}; + +export const hasCallPutEqual = (contract_type_list: THasDurationForCallPutEqual['contract_type_list']) => { + 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.value === 'rise_fall_equal' + ); +}; + +export const hasDurationForCallPutEqual = ( + contract_type_list: THasDurationForCallPutEqual['contract_type_list'], + duration_unit: THasDurationForCallPutEqual['duration_unit'], + contract_start_type: THasDurationForCallPutEqual['contract_start_type'] +) => { + if (!contract_type_list || !duration_unit || !contract_start_type) return false; + + const contract_list = Object.keys(contract_type_list || {}).reduce((key, list) => { + // @ts-expect-error the key always exists in the object, hence can ignore the TS error. + const item: THasDurationForCallPutEqual['contract_type_list']['Ups & Downs'][string] = contract_type_list[list]; + return [...key, ...item.categories.map(contract => contract.value)]; + }, []); + + const contract_duration_list = contract_list.map(list => ({ + [list]: getPropertyValue(ContractType.getFullContractTypes(), [ + list, + 'config', + 'durations', + 'units_display', + contract_start_type, + ]), + })); + + // Check whether rise fall equal is exists and has the current store duration unit + if (hasCallPutEqual(contract_type_list)) { + const found = contract_duration_list.filter(contract => contract?.rise_fall_equal); + if (found.length > 0) { + return found[0].rise_fall_equal.some((duration: { value: string }) => duration.value === duration_unit); + } + } + + return false; +}; + +export const isRiseFallEqual = (contract_type: string) => /^(rise_fall|rise_fall_equal)$/.test(contract_type); diff --git a/packages/trader/src/Stores/Modules/Trading/Helpers/chart.js b/packages/trader/src/Stores/Modules/Trading/Helpers/chart.js deleted file mode 100644 index 0fa3aa4272d2..000000000000 --- a/packages/trader/src/Stores/Modules/Trading/Helpers/chart.js +++ /dev/null @@ -1,4 +0,0 @@ -export const setChartBarrier = (SmartChartStore, proposal_response, onBarrierChange, barrier_config) => { - const { barrier, barrier2, contract_type } = proposal_response.echo_req; - SmartChartStore.createBarriers(contract_type, barrier, barrier2, onBarrierChange, barrier_config); -}; diff --git a/packages/trader/src/Stores/Modules/Trading/Helpers/currency.js b/packages/trader/src/Stores/Modules/Trading/Helpers/currency.ts similarity index 66% rename from packages/trader/src/Stores/Modules/Trading/Helpers/currency.js rename to packages/trader/src/Stores/Modules/Trading/Helpers/currency.ts index bb8e707b1793..77a08274e265 100644 --- a/packages/trader/src/Stores/Modules/Trading/Helpers/currency.js +++ b/packages/trader/src/Stores/Modules/Trading/Helpers/currency.ts @@ -1,9 +1,15 @@ import { isCryptocurrency } from '@deriv/shared'; import { localize } from '@deriv/translations'; -export const buildCurrenciesList = payout_currencies => { - const fiat = []; - const crypto = []; +type TCurrencyList = { + text: string; + value: string; + has_tooltip: boolean; +}[]; + +export const buildCurrenciesList = (payout_currencies: string[]) => { + const fiat: TCurrencyList = []; + const crypto: TCurrencyList = []; payout_currencies.forEach(cur => { const isCrypto = isCryptocurrency(cur); @@ -16,7 +22,7 @@ export const buildCurrenciesList = payout_currencies => { }; }; -export const getDefaultCurrency = (currencies_list, currency = '') => { +export const getDefaultCurrency = (currencies_list: Record, currency = '') => { const supported_currencies = Object.values(currencies_list).reduce((a, b) => [...a, ...b], []); const default_currency = supported_currencies.find(c => c.value === currency) ? currency diff --git a/packages/trader/src/Stores/Modules/Trading/Helpers/end-time.js b/packages/trader/src/Stores/Modules/Trading/Helpers/end-time.js deleted file mode 100644 index 9f64b6419bfc..000000000000 --- a/packages/trader/src/Stores/Modules/Trading/Helpers/end-time.js +++ /dev/null @@ -1,30 +0,0 @@ -const getClosestTime = (time, interval) => time.minute(Math.ceil(time.minute() / interval) * interval); - -export const getSelectedTime = (server_time, selected_time, market_open_times, market_close_times) => { - for (let i = 0; i < market_open_times.length; i++) { - if (selected_time.isAfter(market_open_times[i]) && selected_time.isBefore(market_close_times[i])) { - return getClosestTime(selected_time, 5).format('HH:mm'); - } - } - - for (let i = 0; i < market_open_times.length; i++) { - if (market_open_times[i].isAfter(server_time)) { - return getClosestTime(market_open_times[i], 5).format('HH:mm'); - } - } - - return getClosestTime(server_time, 5).format('HH:mm'); -}; - -export const getBoundaries = (server_time, market_open_times, market_close_times) => { - const boundaries = { - start: market_open_times.map(open_time => (server_time.isBefore(open_time) ? open_time.clone() : server_time)), - end: market_close_times, - }; - - if (boundaries.start.length > 0) { - boundaries.start[0] = getClosestTime(boundaries.start[0], 5); - } - - return boundaries; -}; diff --git a/packages/trader/src/Stores/Modules/Trading/Helpers/end-time.ts b/packages/trader/src/Stores/Modules/Trading/Helpers/end-time.ts new file mode 100644 index 000000000000..c51b6b94325b --- /dev/null +++ b/packages/trader/src/Stores/Modules/Trading/Helpers/end-time.ts @@ -0,0 +1,56 @@ +import { useStore } from '@deriv/stores'; +import { TTradeStore } from 'Types'; +import moment from 'moment'; + +type TTime = { + server_time: NonNullable['common']['server_time']>; + selected_time: moment.Moment; + market_open_times: TTradeStore['market_open_times']; + market_close_times: TTradeStore['market_close_times']; +}; + +const getClosestTime = (time: moment.Moment | string, interval: number): moment.Moment => { + const moment_time = moment(time); // Convert time to a moment object if it's a string + return moment_time.minute(Math.ceil(moment_time.minute() / interval) * interval); +}; + +export const getSelectedTime = ( + server_time: TTime['server_time'], + selected_time: TTime['selected_time'], + market_open_times: TTime['market_open_times'], + market_close_times: TTime['market_close_times'] +) => { + for (let i = 0; i < market_open_times.length; i++) { + if (selected_time.isAfter(market_open_times[i]) && selected_time.isBefore(market_close_times[i])) { + return getClosestTime(selected_time, 5).format('HH:mm'); + } + } + + for (let i = 0; i < market_open_times.length; i++) { + const moment_market_open_time = moment(market_open_times[i]); // Convert market open time to a moment object + if (moment_market_open_time.isAfter(server_time)) { + return getClosestTime(moment_market_open_time, 5).format('HH:mm'); + } + } + + return getClosestTime(server_time, 5).format('HH:mm'); +}; + +export const getBoundaries = ( + server_time: TTime['server_time'], + market_open_times: TTime['market_open_times'], + market_close_times: TTime['market_close_times'] +) => { + const boundaries = { + start: market_open_times.map(open_time => + server_time.isBefore(open_time) ? moment(open_time).clone() : server_time + ), + end: market_close_times, + }; + + if (boundaries.start.length > 0) { + boundaries.start[0] = getClosestTime(boundaries.start[0], 5); + } + + return boundaries; +}; diff --git a/packages/trader/src/Stores/Modules/Trading/Helpers/limit-orders.js b/packages/trader/src/Stores/Modules/Trading/Helpers/limit-orders.ts similarity index 64% rename from packages/trader/src/Stores/Modules/Trading/Helpers/limit-orders.js rename to packages/trader/src/Stores/Modules/Trading/Helpers/limit-orders.ts index 201f45f199f0..2020d20c37bb 100644 --- a/packages/trader/src/Stores/Modules/Trading/Helpers/limit-orders.js +++ b/packages/trader/src/Stores/Modules/Trading/Helpers/limit-orders.ts @@ -1,25 +1,43 @@ import { isMultiplierContract, BARRIER_COLORS, BARRIER_LINE_STYLES } from '@deriv/shared'; import { ChartBarrierStore } from '../../SmartChart/chart-barrier-store'; import { removeBarrier } from '../../SmartChart/Helpers/barriers'; +import { useStore } from '@deriv/stores'; -const isLimitOrderBarrierSupported = (contract_type, contract_info) => - isMultiplierContract(contract_type) && contract_info.limit_order; +const isLimitOrderBarrierSupported = ( + contract_type: string, + contract_info: ReturnType['portfolio']['all_positions'][0]['contract_info'] +) => isMultiplierContract(contract_type) && contract_info.limit_order; export const LIMIT_ORDER_TYPES = { STOP_OUT: 'stop_out', TAKE_PROFIT: 'take_profit', STOP_LOSS: 'stop_loss', -}; +} as const; + +type TBarrier = ChartBarrierStore & { key?: string }; -export const setLimitOrderBarriers = ({ barriers, contract_type, contract_info = {}, is_over }) => { +type TSetLimitOrderBarriers = { + barriers: TBarrier[]; + contract_type: string; + contract_info: Parameters[1]; + is_over: boolean; +}; +export const setLimitOrderBarriers = ({ + barriers, + contract_type, + contract_info = {}, + is_over, +}: TSetLimitOrderBarriers) => { if (is_over && isLimitOrderBarrierSupported(contract_type, contract_info)) { const limit_orders = Object.values(LIMIT_ORDER_TYPES); - const has_stop_loss = Object.keys(contract_info.limit_order).some( - k => k === LIMIT_ORDER_TYPES.STOP_LOSS && contract_info.limit_order[k].value - ); + const has_stop_loss = + contract_info.limit_order !== undefined && + Object.keys(contract_info.limit_order).some( + k => k === LIMIT_ORDER_TYPES.STOP_LOSS && contract_info?.limit_order?.[k]?.value + ); limit_orders.forEach(key => { - const obj_limit_order = contract_info.limit_order[key]; + const obj_limit_order = contract_info.limit_order?.[key]; if (!obj_limit_order || !obj_limit_order.value) { removeBarrier(barriers, key); @@ -35,6 +53,7 @@ export const setLimitOrderBarriers = ({ barriers, contract_type, contract_info = barrier.onChange({ high: obj_limit_order.value, + low: undefined, //TODO: wait until ChartBarrierStore is ts migrated and 'low' can be an optional parameter }); } else { const obj_barrier = { @@ -66,7 +85,16 @@ export const setLimitOrderBarriers = ({ barriers, contract_type, contract_info = * Get limit_order for contract_update API * @param {object} contract_update - contract_update input & checkbox values */ -export const getLimitOrder = contract_update => { +export const getLimitOrder = ( + contract_update: Pick< + ReturnType['contract_trade'], + | 'has_contract_update_stop_loss' + | 'has_contract_update_take_profit' + | 'contract_update_stop_loss' + | 'contract_update_take_profit' + | 'contract_info' + > +) => { const { has_contract_update_stop_loss, has_contract_update_take_profit, @@ -75,11 +103,11 @@ export const getLimitOrder = contract_update => { contract_info, } = contract_update; - const limit_order = {}; + const limit_order: { take_profit?: number | null; stop_loss?: number | null } = {}; const new_take_profit = has_contract_update_take_profit ? +contract_update_take_profit : null; const has_take_profit_changed = - Math.abs(contract_info.limit_order?.take_profit?.order_amount) !== Math.abs(new_take_profit); + Math.abs(contract_info.limit_order?.take_profit?.order_amount ?? 0) !== Math.abs(new_take_profit ?? 0); if (has_take_profit_changed) { // send positive take_profit to update or null cancel @@ -88,7 +116,7 @@ export const getLimitOrder = contract_update => { const new_stop_loss = has_contract_update_stop_loss ? +contract_update_stop_loss : null; const has_stop_loss_changed = - Math.abs(contract_info.limit_order?.stop_loss?.order_amount) !== Math.abs(new_stop_loss); + Math.abs(contract_info?.limit_order?.stop_loss?.order_amount ?? 0) !== Math.abs(new_stop_loss ?? 0); if (has_stop_loss_changed) { // send positive stop_loss to update or null to cancel diff --git a/packages/trader/src/Stores/Modules/Trading/Helpers/logic.js b/packages/trader/src/Stores/Modules/Trading/Helpers/logic.js deleted file mode 100644 index 837683fe659f..000000000000 --- a/packages/trader/src/Stores/Modules/Trading/Helpers/logic.js +++ /dev/null @@ -1,4 +0,0 @@ -import ServerTime from '_common/base/server_time'; - -export const isCancellationExpired = contract_info => - !!(contract_info.cancellation.date_expiry < ServerTime.get().unix()); diff --git a/packages/trader/src/Stores/Modules/Trading/Helpers/logic.ts b/packages/trader/src/Stores/Modules/Trading/Helpers/logic.ts new file mode 100644 index 000000000000..b586a0425592 --- /dev/null +++ b/packages/trader/src/Stores/Modules/Trading/Helpers/logic.ts @@ -0,0 +1,5 @@ +import { TTradeStore } from 'Types'; +import ServerTime from '_common/base/server_time'; + +export const isCancellationExpired = (contract_info: TTradeStore['proposal_info'][string]) => + !!contract_info.cancellation?.date_expiry && contract_info.cancellation.date_expiry < ServerTime.get().unix(); diff --git a/packages/trader/src/Stores/Modules/Trading/Helpers/multiplier.js b/packages/trader/src/Stores/Modules/Trading/Helpers/multiplier.ts similarity index 59% rename from packages/trader/src/Stores/Modules/Trading/Helpers/multiplier.js rename to packages/trader/src/Stores/Modules/Trading/Helpers/multiplier.ts index 8376eb452063..1e7fb6ad79d6 100644 --- a/packages/trader/src/Stores/Modules/Trading/Helpers/multiplier.js +++ b/packages/trader/src/Stores/Modules/Trading/Helpers/multiplier.ts @@ -1,4 +1,23 @@ -export const onToggleCancellation = ({ has_cancellation, onChangeMultiple }) => { +import { TTradeStore } from 'Types'; + +type TData = Partial< + Pick< + TTradeStore, + 'cancellation_duration' | 'cancellation_price' | 'has_cancellation' | 'has_stop_loss' | 'has_take_profit' + > +>; + +type TOnToggleCancellation = { + has_cancellation: TTradeStore['has_cancellation']; + onChangeMultiple: (data: TData) => void; +}; + +type TOnChangeCancellationDuration = { + event: React.ChangeEvent; + onChangeMultiple: (data: TData) => void; +}; + +export const onToggleCancellation = ({ has_cancellation, onChangeMultiple }: TOnToggleCancellation) => { // e.target.checked is not reliable, we have to toggle its previous value const new_val = !has_cancellation; onChangeMultiple({ @@ -16,7 +35,7 @@ export const onToggleCancellation = ({ has_cancellation, onChangeMultiple }) => }); }; -export const onChangeCancellationDuration = ({ event, onChangeMultiple }) => { +export const onChangeCancellationDuration = ({ event, onChangeMultiple }: TOnChangeCancellationDuration) => { const { value } = event.target; onChangeMultiple({ has_cancellation: true, diff --git a/packages/trader/src/Stores/Modules/Trading/Helpers/preview-proposal.js b/packages/trader/src/Stores/Modules/Trading/Helpers/preview-proposal.js deleted file mode 100644 index 3e70008aa87b..000000000000 --- a/packages/trader/src/Stores/Modules/Trading/Helpers/preview-proposal.js +++ /dev/null @@ -1,31 +0,0 @@ -import debounce from 'lodash.debounce'; -import { isEmptyObject, WS } from '@deriv/shared'; -import { createProposalRequests } from './proposal'; - -export const requestPreviewProposal = debounce((store, override = {}, onProposalResponse) => { - const new_store = { ...store, ...override }; - const requests = createProposalRequests(new_store); - const subscription_map = {}; - - const onResponse = response => { - if (response.error) return; - - subscription_map[response.subscription.id] = true; - onProposalResponse(response); - }; - - if (!isEmptyObject(requests)) { - const proposal_requests = requests; - - Object.keys(proposal_requests).forEach(type => { - WS.subscribeProposal(proposal_requests[type], onResponse); - }); - } - - return () => { - Object.keys(subscription_map).forEach(id => { - WS.forget(id); - delete subscription_map[id]; - }); - }; -}, 700); diff --git a/packages/trader/src/Stores/Modules/Trading/Helpers/preview-proposal.ts b/packages/trader/src/Stores/Modules/Trading/Helpers/preview-proposal.ts new file mode 100644 index 000000000000..38618549d597 --- /dev/null +++ b/packages/trader/src/Stores/Modules/Trading/Helpers/preview-proposal.ts @@ -0,0 +1,36 @@ +import debounce from 'lodash.debounce'; +import { isEmptyObject, WS } from '@deriv/shared'; +import { createProposalRequests } from './proposal'; +import { PriceProposalResponse } from '@deriv/api-types'; +import { TTradeStore } from 'Types'; + +export const requestPreviewProposal = debounce( + (store: TTradeStore, override = {}, onProposalResponse: (response: PriceProposalResponse) => void) => { + const new_store = { ...store, ...override }; + const requests = createProposalRequests(new_store); + const subscription_map: { [key: string]: boolean } = {}; + + const onResponse = (response: PriceProposalResponse) => { + if (response.error || !response.subscription) return; + + subscription_map[response.subscription.id] = true; + onProposalResponse(response); + }; + + if (!isEmptyObject(requests)) { + const proposal_requests = requests; + + Object.keys(proposal_requests).forEach(type => { + WS.subscribeProposal(proposal_requests[type], onResponse); + }); + } + + return () => { + Object.keys(subscription_map).forEach(id => { + WS.forget(id); + delete subscription_map[id]; + }); + }; + }, + 700 +); diff --git a/packages/trader/src/Stores/Modules/Trading/Helpers/process.js b/packages/trader/src/Stores/Modules/Trading/Helpers/process.js deleted file mode 100644 index b03c5545b74a..000000000000 --- a/packages/trader/src/Stores/Modules/Trading/Helpers/process.js +++ /dev/null @@ -1,44 +0,0 @@ -import { ContractType as ContractTypeHelper } from 'Stores/Modules/Trading/Helpers/contract-type'; -import * as ContractType from '../Actions/contract-type'; -import * as Duration from '../Actions/duration'; -import * as StartDate from '../Actions/start-date'; - -const processInSequence = async (store, functions) => { - const snapshot = store.getSnapshot(); - // To make sure that every function is invoked and affects the snapshot respectively, we have to use for instead of forEach - for (let i = 0; i < functions.length; i++) { - // Shallow copy with Object.assign is good enough to extend the snapshot with new state - // we don't need deep extension here, since each function in functions array composes a property of the store completely - Object.assign(snapshot, await functions[i](snapshot)); // eslint-disable-line no-await-in-loop - } - store.updateStore({ - ...snapshot, - }); -}; - -export const processTradeParams = async (store, new_state) => { - const functions = getMethodsList(store, new_state); - await processInSequence(store, functions); - - const duration_functions = getExpiryMethodsList(); - await processInSequence(store, duration_functions); - - store.updateStore({ - is_trade_enabled: true, - }); -}; - -const getMethodsList = (store, new_state) => [ - ContractTypeHelper.getContractCategories, - ContractType.onChangeContractTypeList, - ...(/\b(symbol|contract_type|is_equal)\b/.test(Object.keys(new_state)) || !store.contract_type // symbol/contract_type changed or contract_type not set yet - ? [ContractType.onChangeContractType] - : []), - StartDate.onChangeStartDate, - Duration.onChangeExpiry, // it should be always after StartDate.onChangeStartDate - ...(/\b(symbol|contract_type|is_equal)\b/.test(Object.keys(new_state)) || !store.contract_type // symbol/contract_type changed or contract_type not set yet - ? [Duration.onChangeContractType] - : []), -]; - -const getExpiryMethodsList = () => [StartDate.onChangeExpiry]; diff --git a/packages/trader/src/Stores/Modules/Trading/Helpers/process.ts b/packages/trader/src/Stores/Modules/Trading/Helpers/process.ts new file mode 100644 index 000000000000..9c4afbcc970d --- /dev/null +++ b/packages/trader/src/Stores/Modules/Trading/Helpers/process.ts @@ -0,0 +1,57 @@ +import { ContractType as ContractTypeHelper } from 'Stores/Modules/Trading/Helpers/contract-type'; +import * as ContractType from '../Actions/contract-type'; +import * as Duration from '../Actions/duration'; +import * as StartDate from '../Actions/start-date'; +import { TTradeStore } from 'Types'; + +// remove local TGetSnapshot type and assertion after TS migration for trader package is complete +type TGetSnapshot = (properties?: string[]) => TTradeStore; + +const processInSequence = async ( + store: TTradeStore, + functions: ReturnType | ReturnType +) => { + const snapshot = (store.getSnapshot as TGetSnapshot)(); + // To make sure that every function is invoked and affects the snapshot respectively, we have to use for instead of forEach + for (let i = 0; i < functions.length; i++) { + // Shallow copy with Object.assign is good enough to extend the snapshot with new state + // we don't need deep extension here, since each function in functions array composes a property of the store completely + Object.assign(snapshot, await functions[i](snapshot)); // eslint-disable-line no-await-in-loop + } + store.updateStore({ + ...snapshot, + }); +}; + +export const processTradeParams = async (store: TTradeStore, new_state: DeepPartial) => { + const functions = getMethodsList(store, new_state); + await processInSequence(store, functions); + + const duration_functions = getExpiryMethodsList(); + await processInSequence(store, duration_functions); + + store.updateStore({ + is_trade_enabled: true, + }); +}; + +const getMethodsList = ( + store: Parameters[0], + new_state: Parameters[1] +) => { + const filtered_keys = Object.keys(new_state).filter(key => /\b(symbol|contract_type|is_equal)\b/.test(key)); + return [ + ContractTypeHelper.getContractCategories, + ContractType.onChangeContractTypeList, + ...(filtered_keys.length > 0 || !store.contract_type // symbol/contract_type changed or contract_type not set yet + ? [ContractType.onChangeContractType] + : []), + StartDate.onChangeStartDate, + Duration.onChangeExpiry, // it should be always after StartDate.onChangeStartDate + ...(filtered_keys.length || !store.contract_type // symbol/contract_type changed or contract_type not set yet + ? [Duration.onChangeContractType] + : []), + ]; +}; + +const getExpiryMethodsList = () => [StartDate.onChangeExpiry]; diff --git a/packages/trader/src/Stores/Modules/Trading/Helpers/proposal.js b/packages/trader/src/Stores/Modules/Trading/Helpers/proposal.ts similarity index 59% rename from packages/trader/src/Stores/Modules/Trading/Helpers/proposal.js rename to packages/trader/src/Stores/Modules/Trading/Helpers/proposal.ts index cade6d663338..0ae9ff9fde8c 100644 --- a/packages/trader/src/Stores/Modules/Trading/Helpers/proposal.js +++ b/packages/trader/src/Stores/Modules/Trading/Helpers/proposal.ts @@ -1,3 +1,4 @@ +import { PriceProposalResponse, Proposal } from '@deriv/api-types'; import { convertToUnix, getDecimalPlaces, @@ -7,17 +8,43 @@ import { isTurbosContract, toMoment, } from '@deriv/shared'; +import { TError, TTradeStore } from 'Types'; -const isVisible = elem => !(!elem || (elem.offsetWidth === 0 && elem.offsetHeight === 0)); +type TObjContractBasis = { + text: string; + value: string; +}; + +type TObjMultiplier = { + cancellation?: string; + limit_order?: { + take_profit?: number; + stop_loss?: number; + }; + multiplier?: number; +}; + +type TObjAccum = { + growth_rate?: number; + limit_order?: { + take_profit?: number; + }; +}; -const map_error_field = { +type TObjExpiry = { + date_expiry?: number; +}; + +const isVisible = (elem: HTMLElement) => !(!elem || (elem.offsetWidth === 0 && elem.offsetHeight === 0)); + +const map_error_field: { [key: string]: string } = { barrier: 'barrier_1', barrier2: 'barrier_2', date_expiry: 'expiry_date', }; -export const getProposalErrorField = response => { - const error_field = getPropertyValue(response, ['error', 'details', 'field']); +export const getProposalErrorField = (response: PriceProposalResponse) => { + const error_field: string = getPropertyValue(response, ['error', 'details', 'field']); if (!error_field) { return null; } @@ -26,28 +53,37 @@ export const getProposalErrorField = response => { return el_error && isVisible(el_error) ? error_id : null; }; -export const getProposalInfo = (store, response, obj_prev_contract_basis) => { - const proposal = response.proposal || {}; - const profit = proposal.payout - proposal.ask_price || 0; +export const getProposalInfo = ( + store: TTradeStore, + response: PriceProposalResponse & TError, + obj_prev_contract_basis: TObjContractBasis +) => { + const proposal = response.proposal || ({} as Proposal); + const profit = (proposal.payout || 0) - (proposal.ask_price || 0); const returns = (profit * 100) / (proposal.ask_price || 1); const stake = proposal.display_value; const basis_list = store.basis_list; - const contract_basis = + const contract_basis: TObjContractBasis | undefined = store.is_vanilla || store.is_turbos ? { text: getLocalizedBasis().payout_per_point, value: 'display_number_of_contracts' } - : basis_list.find(o => o.value !== store.basis) || {}; + : basis_list.find(o => o.value !== store.basis) || ({} as TObjContractBasis); - const is_stake = contract_basis.value === 'stake'; - const price = is_stake ? stake : proposal[contract_basis.value]; - let has_increased = price > obj_prev_contract_basis.value; + const is_stake = contract_basis?.value === 'stake'; + + const price = is_stake ? stake : proposal[contract_basis?.value as keyof Proposal]; + let has_increased = false; + + if (price !== undefined && price !== null) { + has_increased = price > obj_prev_contract_basis.value; + } if (!obj_prev_contract_basis.value || price === obj_prev_contract_basis.value) { - has_increased = null; + has_increased = !!null; } const obj_contract_basis = { - text: contract_basis.text || '', + text: contract_basis?.text || '', value: price || '', }; @@ -69,7 +105,7 @@ export const getProposalInfo = (store, response, obj_prev_contract_basis) => { error_field: response?.error?.details?.field, has_increased, limit_order: proposal.limit_order, - message: proposal.longcode || response.error.message, + message: proposal.longcode || response?.error?.message, obj_contract_basis, payout: proposal.payout, profit: profit.toFixed(getDecimalPlaces(store.currency)), @@ -80,8 +116,8 @@ export const getProposalInfo = (store, response, obj_prev_contract_basis) => { }; }; -export const createProposalRequests = store => { - const requests = {}; +export const createProposalRequests = (store: TTradeStore) => { + const requests = {} as Record>; Object.keys(store.trade_types).forEach(type => { const new_req = createProposalRequestForContract(store, type); @@ -91,38 +127,38 @@ export const createProposalRequests = store => { return requests; }; -const setProposalMultiplier = (store, obj_multiplier) => { +const setProposalMultiplier = (store: TTradeStore, obj_multiplier: TObjMultiplier) => { obj_multiplier.multiplier = store.multiplier; obj_multiplier.cancellation = store.has_cancellation ? store.cancellation_duration : undefined; obj_multiplier.limit_order = store.has_take_profit || store.has_stop_loss ? {} : undefined; - if (store.has_take_profit && store.take_profit) { + if (store.has_take_profit && store.take_profit && obj_multiplier.limit_order) { obj_multiplier.limit_order.take_profit = +store.take_profit || 0; // send positive take_profit to API } - if (store.has_stop_loss && store.stop_loss) { + if (store.has_stop_loss && store.stop_loss && obj_multiplier.limit_order) { obj_multiplier.limit_order.stop_loss = +store.stop_loss || 0; // send positive stop_loss to API } }; -const setProposalAccumulator = (store, obj_accumulator) => { +const setProposalAccumulator = (store: TTradeStore, obj_accumulator: TObjAccum) => { obj_accumulator.growth_rate = store.growth_rate; obj_accumulator.limit_order = store.has_take_profit ? {} : undefined; - if (store.has_take_profit && store.take_profit) { + if (store.has_take_profit && store.take_profit && obj_accumulator.limit_order) { obj_accumulator.limit_order.take_profit = +store.take_profit || 0; // send positive take_profit to API } }; -const createProposalRequestForContract = (store, type_of_contract) => { - const obj_accumulator = {}; - const obj_expiry = {}; - const obj_multiplier = {}; +const createProposalRequestForContract = (store: TTradeStore, type_of_contract: string) => { + const obj_accumulator: TObjAccum = {}; + const obj_expiry: TObjExpiry = {}; + const obj_multiplier: TObjMultiplier = {}; let limit_order; - if (store.expiry_type === 'endtime') { + if (store.expiry_type === 'endtime' && store.expiry_time) { const expiry_date = toMoment(store.expiry_date); obj_expiry.date_expiry = convertToUnix(expiry_date.unix(), store.expiry_time); } @@ -142,15 +178,15 @@ const createProposalRequestForContract = (store, type_of_contract) => { return { proposal: 1, subscribe: 1, - amount: parseFloat(store.amount) || 0, + amount: parseFloat(store.amount.toString()) || 0, basis: store.basis, contract_type: type_of_contract, currency: store.currency, symbol: store.symbol, - ...(store.start_date && { date_start: convertToUnix(store.start_date, store.start_time) }), + ...(store.start_date && store.start_time && { date_start: convertToUnix(store.start_date, store.start_time) }), ...(store.expiry_type === 'duration' ? { - duration: parseInt(store.duration), + duration: parseInt(store.duration.toString()), duration_unit: store.duration_unit, } : obj_expiry), diff --git a/packages/trader/src/Stores/Modules/Trading/Helpers/start-date.js b/packages/trader/src/Stores/Modules/Trading/Helpers/start-date.ts similarity index 66% rename from packages/trader/src/Stores/Modules/Trading/Helpers/start-date.js rename to packages/trader/src/Stores/Modules/Trading/Helpers/start-date.ts index d380db266c55..be82edf14f50 100644 --- a/packages/trader/src/Stores/Modules/Trading/Helpers/start-date.js +++ b/packages/trader/src/Stores/Modules/Trading/Helpers/start-date.ts @@ -1,7 +1,8 @@ import ServerTime from '_common/base/server_time'; import { toMoment } from '@deriv/shared'; +import { useTraderStore } from 'Stores/useTraderStores'; -const isBeforeDate = (compare_moment, start_moment, should_only_check_hour) => { +const isBeforeDate = (compare_moment: moment.Moment, start_moment: moment.Moment, should_only_check_hour: boolean) => { const now_moment = toMoment(start_moment); if (should_only_check_hour) { now_moment.minute(0).second(0); @@ -10,9 +11,9 @@ const isBeforeDate = (compare_moment, start_moment, should_only_check_hour) => { }; export const isSessionAvailable = ( - sessions = [], - compare_moment = toMoment(ServerTime.get()), - start_moment = toMoment(ServerTime.get()), + sessions: ReturnType['sessions'] = [], + compare_moment: moment.Moment = toMoment(ServerTime.get()), + start_moment: moment.Moment = toMoment(ServerTime.get()), should_only_check_hour = false ) => !isBeforeDate(compare_moment, ServerTime.get(), should_only_check_hour) && diff --git a/packages/trader/src/Stores/useTraderStores.tsx b/packages/trader/src/Stores/useTraderStores.tsx index 5e4db0cd77c2..36bb72f5aad4 100644 --- a/packages/trader/src/Stores/useTraderStores.tsx +++ b/packages/trader/src/Stores/useTraderStores.tsx @@ -1,8 +1,119 @@ import React from 'react'; import { useStore } from '@deriv/stores'; import TradeStore from './Modules/Trading/trade-store'; +import moment from 'moment'; -const TraderStoreContext = React.createContext(null); +type TTextValueStrings = { + text: string; + value: string; +}; + +type TContractTypesList = { + [key: string]: { + name: string; + categories: TTextValueStrings[]; + }; +}; + +type TContractCategoriesList = { + Multipliers: TContractTypesList; + 'Ups & Downs': TContractTypesList; + 'Highs & Lows': TContractTypesList; + 'Ins & Outs': TContractTypesList; + 'Look Backs': TContractTypesList; + Digits: TContractTypesList; + Vanillas: TContractTypesList; + Accumulators: TContractTypesList; +}; + +type TToastBoxListItem = { + contract_category: string; + contract_types: [ + { + text: string; + value: string; + } + ]; +}; + +type TToastBoxObject = { + key?: boolean; + buy_price?: number; + currency?: string; + contract_type?: string; + list?: TToastBoxListItem[]; +}; + +type TOverrideTradeStore = Omit< + TradeStore, + | 'accumulator_range_list' + | 'barriers' + | 'basis_list' + | 'cancellation_price' + | 'cancellation_range_list' + | 'clearContractPurchaseToastBox' + | 'contract_purchase_toast_box' + | 'contract_types_list' + | 'duration_min_max' + | 'duration_units_list' + | 'expiry_date' + | 'expiry_time' + | 'expiry_type' + | 'form_components' + | 'market_close_times' + | 'market_open_times' + | 'multiplier_range_list' + | 'sessions' + | 'start_dates_list' + | 'start_time' + | 'proposal_info' + | 'trade_types' + | 'ticks_history_stats' +> & { + accumulator_range_list?: number[]; + basis_list: Array; + cancellation_price?: number; + cancellation_range_list: Array; + clearContractPurchaseToastBox: () => void; + contract_purchase_toast_box: TToastBoxObject; + contract_types_list: TContractCategoriesList; + duration_min_max: { + [key: string]: { min: number; max: number }; + }; + duration_units_list: Array; + expiry_date: string | null; + expiry_time: string | null; + expiry_type: string | null; + form_components: string[]; + market_open_times: string[]; + market_close_times: string[]; + multiplier_range_list: number[]; + proposal_info: { + [key: string]: { + has_error?: boolean; + id: string; + has_increased?: boolean; + message?: string; + cancellation?: { + ask_price: number; + date_expiry: number; + }; + growth_rate?: number; + returns?: string; + stake: string; + }; + }; + sessions: Array<{ open: moment.Moment; close: moment.Moment }>; + start_dates_list: Array<{ text: string; value: number }>; + start_time: string | null; + ticks_history_stats: { + ticks_stayed_in?: number[]; + last_tick_epoch?: number; + }; + trade_types: { [key: string]: string }; +}; + +const TraderStoreContext = React.createContext(null); export const TraderStoreProvider = ({ children }: React.PropsWithChildren) => { const { modules } = useStore(); diff --git a/packages/trader/src/Types/common-prop.type.ts b/packages/trader/src/Types/common-prop.type.ts new file mode 100644 index 000000000000..bc38b04c55e2 --- /dev/null +++ b/packages/trader/src/Types/common-prop.type.ts @@ -0,0 +1,27 @@ +import { useTraderStore } from 'Stores/useTraderStores'; + +export type TProposalTypeInfo = { + has_error?: boolean; + id: string; + has_increased?: boolean; + message?: string; + cancellation?: { + ask_price: number; + date_expiry: number; + }; + growth_rate?: number; + returns?: string; + stake: string; +}; + +export type TError = { + error?: { + code?: string; + details?: { + field?: string; + }; + message?: string; + }; +}; + +export type TTradeStore = ReturnType; diff --git a/packages/trader/src/Types/index.ts b/packages/trader/src/Types/index.ts new file mode 100644 index 000000000000..989319119dbe --- /dev/null +++ b/packages/trader/src/Types/index.ts @@ -0,0 +1 @@ +export * from './common-prop.type'; diff --git a/packages/trader/src/Utils/Helpers/market-underlying.js b/packages/trader/src/Utils/Helpers/market-underlying.js deleted file mode 100644 index f63669febc31..000000000000 --- a/packages/trader/src/Utils/Helpers/market-underlying.js +++ /dev/null @@ -1,30 +0,0 @@ -import { getMarketNamesMap, getContractConfig } from 'Constants/contract'; - -/** - * Fetch market information from shortcode - * @param shortcode: string - * @returns {{underlying: string, category: string}} - */ - -// TODO: Combine with extractInfoFromShortcode function in shared, both are currently used -export const getMarketInformation = shortcode => { - const market_info = { - category: '', - underlying: '', - }; - - const pattern = new RegExp( - '^([A-Z]+)_((1HZ[0-9-V]+)|((CRASH|BOOM)[0-9\\d]+[A-Z]?)|(OTC_[A-Z0-9]+)|R_[\\d]{2,3}|[A-Z]+)' - ); - const extracted = pattern.exec(shortcode); - if (extracted !== null) { - market_info.category = extracted[1].toLowerCase(); - market_info.underlying = extracted[2]; - } - - return market_info; -}; - -export const getMarketName = underlying => (underlying ? getMarketNamesMap()[underlying.toUpperCase()] : null); - -export const getTradeTypeName = category => (category ? getContractConfig()[category.toUpperCase()]?.name : null); diff --git a/packages/trader/src/Utils/Helpers/market-underlying.ts b/packages/trader/src/Utils/Helpers/market-underlying.ts new file mode 100644 index 000000000000..96922da25d0a --- /dev/null +++ b/packages/trader/src/Utils/Helpers/market-underlying.ts @@ -0,0 +1,44 @@ +import { getContractConfig, getMarketNamesMap } from '@deriv/shared'; + +type TMarketInfo = { + category: string; + underlying: string; +}; + +type TTradeConfig = { + name: JSX.Element; + position: string; +}; + +/** + * Fetch market information from shortcode + * @param shortcode: string + * @returns {{underlying: string, category: string}} + */ + +// TODO: Combine with extractInfoFromShortcode function in shared, both are currently used +export const getMarketInformation = (shortcode: string): TMarketInfo => { + const market_info: TMarketInfo = { + category: '', + underlying: '', + }; + + const pattern = new RegExp( + '^([A-Z]+)_((1HZ[0-9-V]+)|((CRASH|BOOM)[0-9\\d]+[A-Z]?)|(OTC_[A-Z0-9]+)|R_[\\d]{2,3}|[A-Z]+)' + ); + const extracted = pattern.exec(shortcode); + if (extracted !== null) { + market_info.category = extracted[1].toLowerCase(); + market_info.underlying = extracted[2]; + } + + return market_info; +}; + +export const getMarketName = (underlying: string) => + underlying ? getMarketNamesMap()[underlying.toUpperCase() as keyof typeof getMarketNamesMap] : null; + +export const getTradeTypeName = (category: string) => + category + ? (getContractConfig()[category.toUpperCase() as keyof typeof getContractConfig] as TTradeConfig)?.name + : null; diff --git a/packages/trader/src/Utils/Validator/__tests__/error.spec.js b/packages/trader/src/Utils/Validator/__tests__/error.spec.ts similarity index 67% rename from packages/trader/src/Utils/Validator/__tests__/error.spec.js rename to packages/trader/src/Utils/Validator/__tests__/error.spec.ts index f97dae65582c..ffe7d027f625 100644 --- a/packages/trader/src/Utils/Validator/__tests__/error.spec.js +++ b/packages/trader/src/Utils/Validator/__tests__/error.spec.ts @@ -1,33 +1,33 @@ import Errors from '../errors'; describe('Errors', () => { - let errors; + let errors: Errors; beforeEach(() => { errors = new Errors(); - errors.add('Error', 100); + errors.add('Error', '100'); }); it('should add a new error to the errors', () => { - errors.add('Error', 101); - expect(errors.errors['Error']).toHaveLength(2); + errors.add('Error', '101'); + expect(errors.errors.Error).toHaveLength(2); }); it('should not add an error if already existed', () => { - errors.add('Error', 100); - expect(errors.errors['Error']).toHaveLength(1); + errors.add('Error', '100'); + expect(errors.errors.Error).toHaveLength(1); }); it('should return all errors', () => { - expect(errors.all()).toEqual({ Error: [100] }); + expect(errors.all()).toEqual({ Error: ['100'] }); }); it('should return first error if attribute exists', () => { - expect(errors.first('Error')).toEqual(100); + expect(errors.first('Error')).toEqual('100'); }); it('should return data if attribute exists', () => { - expect(errors.get('Error')).toEqual([100]); + expect(errors.get('Error')).toEqual(['100']); }); it('should return an empty array if attribute does not exist', () => { diff --git a/packages/trader/src/Utils/Validator/errors.js b/packages/trader/src/Utils/Validator/errors.ts similarity index 79% rename from packages/trader/src/Utils/Validator/errors.js rename to packages/trader/src/Utils/Validator/errors.ts index 8c3307765b64..23bd74efad9e 100644 --- a/packages/trader/src/Utils/Validator/errors.js +++ b/packages/trader/src/Utils/Validator/errors.ts @@ -1,9 +1,11 @@ class Errors { + errors: { [key: string]: string[] }; + constructor() { this.errors = {}; } - add(attribute, message) { + add(attribute: string, message: string) { if (!this.has(attribute)) { this.errors[attribute] = []; } @@ -17,14 +19,14 @@ class Errors { return this.errors; } - first(attribute) { + first(attribute: string) { if (this.has(attribute)) { return this.errors[attribute][0]; } return null; } - get(attribute) { + get(attribute: string) { if (this.has(attribute)) { return this.errors[attribute]; } @@ -32,7 +34,7 @@ class Errors { return []; } - has(attribute) { + has(attribute: string) { return Object.prototype.hasOwnProperty.call(this.errors, attribute); } } diff --git a/packages/trader/src/Utils/Validator/index.js b/packages/trader/src/Utils/Validator/index.ts similarity index 100% rename from packages/trader/src/Utils/Validator/index.js rename to packages/trader/src/Utils/Validator/index.ts diff --git a/packages/trader/src/Utils/Validator/validator.js b/packages/trader/src/Utils/Validator/validator.js deleted file mode 100644 index c8aa2ce59dff..000000000000 --- a/packages/trader/src/Utils/Validator/validator.js +++ /dev/null @@ -1,112 +0,0 @@ -import { template } from '_common/utility'; -import { getPreBuildDVRs } from '@deriv/shared'; -import Error from './errors'; - -class Validator { - constructor(input, rules, store = null) { - this.input = input; - this.rules = rules; - this.store = store; - this.errors = new Error(); - - this.error_count = 0; - } - - /** - * Add failure and error message for given rule - * - * @param {string} attribute - * @param {object} rule - */ - addFailure(attribute, rule, error_message) { - let message = error_message || rule.options.message || getPreBuildDVRs()[rule.name].message(); - if (rule.name === 'length') { - message = template(message, [ - rule.options.min === rule.options.max ? rule.options.min : `${rule.options.min}-${rule.options.max}`, - ]); - } else if (rule.name === 'min') { - message = template(message, [rule.options.min]); - } else if (rule.name === 'not_equal') { - message = template(message, [rule.options.name1, rule.options.name2]); - } - this.errors.add(attribute, message); - this.error_count++; - } - - /** - * Runs validator - * - * @return {boolean} Whether it passes; true = passes, false = fails - */ - check() { - Object.keys(this.input).forEach(attribute => { - if (!Object.prototype.hasOwnProperty.call(this.rules, attribute)) { - return; - } - - this.rules[attribute].forEach(rule => { - const ruleObject = Validator.getRuleObject(rule); - - if (!ruleObject.validator && typeof ruleObject.validator !== 'function') { - return; - } - - if (ruleObject.options.condition && !ruleObject.options.condition(this.store)) { - return; - } - - if (this.input[attribute] === '' && ruleObject.name !== 'req') { - return; - } - - let is_valid, error_message; - if (ruleObject.name === 'number') { - const { is_ok, message } = ruleObject.validator( - this.input[attribute], - ruleObject.options, - this.store, - this.input - ); - is_valid = is_ok; - error_message = message; - } else { - is_valid = ruleObject.validator(this.input[attribute], ruleObject.options, this.store, this.input); - } - - if (!is_valid) { - this.addFailure(attribute, ruleObject, error_message); - } - }); - }); - return !this.error_count; - } - - /** - * Determine if validation passes - * - * @return {boolean} - */ - isPassed() { - return this.check(); - } - - /** - * Converts the rule array to an object - * - * @param {array} rule - * @return {object} - */ - static getRuleObject(rule) { - const is_rule_string = typeof rule === 'string'; - const rule_object = { - name: is_rule_string ? rule : rule[0], - options: is_rule_string ? {} : rule[1] || {}, - }; - - rule_object.validator = rule_object.name === 'custom' ? rule[1].func : getPreBuildDVRs()[rule_object.name].func; - - return rule_object; - } -} - -export default Validator; diff --git a/packages/trader/src/Utils/Validator/validator.ts b/packages/trader/src/Utils/Validator/validator.ts new file mode 100644 index 000000000000..60f258a7fcad --- /dev/null +++ b/packages/trader/src/Utils/Validator/validator.ts @@ -0,0 +1,170 @@ +import { template } from '_common/utility'; +import { getPreBuildDVRs } from '@deriv/shared'; +import Error from './errors'; +import { getValidationRules } from 'Stores/Modules/Trading/Constants/validation-rules'; +import { TTradeStore } from 'Types'; + +type TOptions = { + [key: string]: unknown; + decimals?: string | number; + is_required?: boolean; + max?: number | string; + min?: number | string; + name1?: string; + name2?: string; + regex?: RegExp; + type?: string; +}; + +type TInitPreBuildDVRs = ReturnType; + +export type TRuleOptions = { + func: (value: string | number, options?: TOptions, store?: TTradeStore, inputs?: unknown) => boolean; + condition: (store: TTradeStore) => boolean; + message: string; +} & TOptions; + +type TRule = string | Array; + +type TValidationResult = { + is_ok: boolean; + message: string; +}; + +class Validator { + input: Partial; + rules: Partial; + store: TTradeStore; + errors: Error; + error_count: number; + + constructor(input: Partial, rules: Partial, store: TTradeStore) { + this.input = input; + this.rules = rules; + this.store = store; + this.errors = new Error(); + + this.error_count = 0; + } + + /** + * Add failure and error message for given rule + * + * @param {string} attribute + * @param {object} rule + */ + addFailure(attribute: string, rule: { name: string; options: TRuleOptions }, error_message?: string) { + let message = + error_message || + rule.options.message || + (getPreBuildDVRs() as unknown as { [key: string]: { message: () => string } })[rule.name].message(); + if (rule.name === 'length') { + message = template(message, [ + rule.options.min === rule.options.max + ? rule.options.min?.toString() + : `${rule.options.min}-${rule.options.max}`, + ]); + } else if (rule.name === 'min') { + message = template(message, [rule.options.min?.toString()]); + } else if (rule.name === 'not_equal') { + message = template(message, [rule.options.name1, rule.options.name2]); + } + this.errors.add(attribute, message); + this.error_count++; + } + + /** + * Runs validator + * + * @return {boolean} Whether it passes; true = passes, false = fails + */ + check() { + Object.keys(this.input).forEach(attribute => { + if (!Object.prototype.hasOwnProperty.call(this.rules, attribute)) { + return; + } + + (this.rules as unknown as { [key: string]: Array })[attribute].forEach((rule: TRule) => { + const ruleObject = Validator.getRuleObject(rule); + + if (!ruleObject.validator && typeof ruleObject.validator !== 'function') { + return; + } + + if (ruleObject.options.condition && !ruleObject.options.condition(this.store)) { + return; + } + + if (this.input[attribute as keyof TTradeStore] === '' && ruleObject.name !== 'req') { + return; + } + + let is_valid, error_message; + if (ruleObject.name === 'number') { + const { is_ok, message }: TValidationResult = ruleObject.validator( + this.input[attribute as keyof TTradeStore], + ruleObject.options, + this.store, + this.input + ) as TValidationResult; + is_valid = is_ok; + error_message = message; + } else { + is_valid = ruleObject.validator( + this.input[attribute as keyof TTradeStore], + ruleObject.options, + this.store, + this.input + ); + } + + if (!is_valid) { + this.addFailure(attribute, ruleObject, error_message); + } + }); + }); + return !this.error_count; + } + + /** + * Determine if validation passes + * + * @return {boolean} + */ + isPassed() { + return this.check(); + } + + /** + * Converts the rule array to an object + * + * @param {array} rule + * @return {object} + */ + static getRuleObject(rule: TRule) { + const is_rule_string = typeof rule === 'string'; + const rule_object_name = (is_rule_string ? rule : rule[0]) as string; + const rule_object_options = (is_rule_string ? {} : rule[1] || {}) as TRuleOptions; + return { + name: rule_object_name, + options: rule_object_options, + validator: + rule_object_name === 'custom' + ? rule_object_options.func + : ( + getPreBuildDVRs() as unknown as { + [key: string]: { + func: ( + value: string | number, + options?: TRuleOptions, + store?: TTradeStore, + inputs?: unknown + ) => boolean | { is_ok: boolean; message: string }; + }; + } + )[rule_object_name].func, + }; + } +} + +export default Validator; diff --git a/packages/trader/tsconfig.json b/packages/trader/tsconfig.json index f980e4bcac9b..0bc1c7a7b7e9 100644 --- a/packages/trader/tsconfig.json +++ b/packages/trader/tsconfig.json @@ -13,11 +13,12 @@ "Services/*": ["src/Services/*"], "Stores/*": ["src/Stores/*"], "Translations/*": ["src/public/translations/*"], + "Types": ["src/Types"], "Utils/*": ["src/Utils/*"], "@deriv/*": ["../*/src"] }, "outDir": "./dist", "baseUrl": "./" }, - "include": ["src", "../../globals.d.ts", "../../utils.d.ts", "@deriv-stores.d.ts"] + "include": ["src", "../../globals.d.ts", "../../utils.d.ts"] }