diff --git a/packages/stores/src/mockStore.ts b/packages/stores/src/mockStore.ts index ba6909f56437..d3af16d6d9ba 100644 --- a/packages/stores/src/mockStore.ts +++ b/packages/stores/src/mockStore.ts @@ -695,7 +695,7 @@ const mock = (): TStores & { is_mock: boolean } => { cancellation_range_list: [], clearContractPurchaseToastBox: jest.fn(), clearPurchaseInfo: jest.fn(), - clearWheelPickerInitialValues: jest.fn(), + clearV2ParamsInitialValues: jest.fn(), contract_expiry_type: '', contract_purchase_toast_box: {}, contract_start_type: '', @@ -753,7 +753,7 @@ const mock = (): TStores & { is_mock: boolean } => { setHoveredBarrier: jest.fn(), setIsTradeParamsExpanded: jest.fn(), setTradeTypeTab: jest.fn(), - setWheelPickerInitialValues: jest.fn(), + setV2ParamsInitialValues: jest.fn(), stake_boundary: {}, start_date: 0, stop_loss: 0, @@ -767,8 +767,9 @@ const mock = (): TStores & { is_mock: boolean } => { trade_type_tab: '', trade_types: {}, validation_errors: {}, + validation_params: {}, vanilla_trade_type: '', - wheel_picker_initial_values: {}, + v2_params_initial_values: {}, }, }, feature_flags: { diff --git a/packages/trader/src/AppV2/Components/PurchaseButton/purchase-button-content.tsx b/packages/trader/src/AppV2/Components/PurchaseButton/purchase-button-content.tsx index 8400ae8e4a03..08a91d571e2e 100644 --- a/packages/trader/src/AppV2/Components/PurchaseButton/purchase-button-content.tsx +++ b/packages/trader/src/AppV2/Components/PurchaseButton/purchase-button-content.tsx @@ -7,6 +7,7 @@ import { Money } from '@deriv/components'; type TPurchaseButtonContent = { current_stake?: number | null; + error?: React.ReactNode; info: ReturnType['proposal_info'][0] | Record; is_reverse?: boolean; is_high_low?: boolean; @@ -25,6 +26,7 @@ type TPurchaseButtonContent = { const PurchaseButtonContent = ({ currency, current_stake, + error, has_open_accu_contract, info, is_accumulator, @@ -68,26 +70,28 @@ const PurchaseButtonContent = ({ )} data-testid='dt_purchase_button_wrapper' > - {!is_content_empty && ( + {(!is_content_empty || error) && ( - {text_basis} + {!error && text_basis} - + {error || ( + + )} )} diff --git a/packages/trader/src/AppV2/Components/PurchaseButton/purchase-button.scss b/packages/trader/src/AppV2/Components/PurchaseButton/purchase-button.scss index a2ad8dc8cdf5..f8718237bb2a 100644 --- a/packages/trader/src/AppV2/Components/PurchaseButton/purchase-button.scss +++ b/packages/trader/src/AppV2/Components/PurchaseButton/purchase-button.scss @@ -5,7 +5,7 @@ height: var(--core-size-2800); gap: 0px; - &:nth-child(2):not(.purchase-button--loading) { + &:not(:first-child):not(.purchase-button--loading) { align-items: flex-start; } &--single { @@ -54,13 +54,18 @@ position: absolute; background-color: var(--core-color-solid-slate-50); top: 0; - left: var(--core-spacing-400); - width: calc(100% - var(--core-spacing-800)); + inset-inline-start: var(--core-spacing-400); + width: calc((100% - var(--core-spacing-800) - var(--core-spacing-400)) / 2); height: calc(100% - var(--core-spacing-400)); border-radius: var(--component-button-border-radius-lg); z-index: -1; - } - &:disabled { - opacity: var(--core-opacity-300); // disabled button opacity in quill-ui is 0.48 (var(--core-opacity-600)) + + &:last-child:not(.single) { + inset-inline-start: unset; + inset-inline-end: var(--core-spacing-400); + } + &.single { + width: calc(100% - var(--core-spacing-800)); + } } } diff --git a/packages/trader/src/AppV2/Components/PurchaseButton/purchase-button.tsx b/packages/trader/src/AppV2/Components/PurchaseButton/purchase-button.tsx index 4139127b516e..0440d9474729 100644 --- a/packages/trader/src/AppV2/Components/PurchaseButton/purchase-button.tsx +++ b/packages/trader/src/AppV2/Components/PurchaseButton/purchase-button.tsx @@ -1,6 +1,7 @@ import React from 'react'; import clsx from 'clsx'; import { observer } from 'mobx-react'; +import { Localize } from '@deriv/translations'; import { useStore } from '@deriv/stores'; import { useTraderStore } from 'Stores/useTraderStores'; import { Button, useNotifications } from '@deriv-com/quill-ui'; @@ -17,6 +18,7 @@ import { import PurchaseButtonContent from './purchase-button-content'; import { getTradeTypeTabsList } from 'AppV2/Utils/trade-params-utils'; import { StandaloneStopwatchRegularIcon } from '@deriv/quill-icons'; +import { getDisplayedContractTypes } from 'AppV2/Utils/trade-types-utils'; const PurchaseButton = observer(() => { const [loading_button_index, setLoadingButtonIndex] = React.useState(null); @@ -62,9 +64,7 @@ const PurchaseButton = observer(() => { is_vanilla_fx, is_vanilla, }; - const trade_types_array = Object.keys(trade_types).filter( - type => !getTradeTypeTabsList(contract_type).length || type === trade_type_tab - ); + const contract_types = getDisplayedContractTypes(trade_types, contract_type, trade_type_tab); const active_accu_contract = is_accumulator ? all_positions.find( ({ contract_info, type }) => @@ -119,18 +119,24 @@ const PurchaseButton = observer(() => { disabled={is_accu_sell_disabled} onClick={() => onClickSell(active_accu_contract?.contract_info.contract_id)} /> - {is_accu_sell_disabled &&
} + {is_accu_sell_disabled &&
}
); } return (
- {trade_types_array.map((trade_type, index) => { + {contract_types.map((trade_type, index) => { const info = proposal_info?.[trade_type] || {}; - const is_single_button = trade_types_array.length === 1; + const is_single_button = contract_types.length === 1; const is_loading = loading_button_index === index; - const is_disabled = !is_trade_enabled_v2; + const is_disabled = !is_trade_enabled_v2 || info.has_error; + /* TODO: stop using error text for is_max_payout_exceeded after validation_params are added to proposal API (both success & error response): + E.g., for is_max_payout_exceeded, we have to temporarily check the error text: Max payout error always contains 3 numbers, the check will work for any languages: */ + const float_number_search_regex = /\d+(\.\d+)?/g; + const is_max_payout_exceeded = + info.has_error && info.message?.match(float_number_search_regex)?.length === 3; + const error_message = is_max_payout_exceeded ? : ''; return ( @@ -154,13 +160,16 @@ const PurchaseButton = observer(() => { {!is_loading && !is_accumulator && ( )} {is_disabled && !is_loading && ( -
+
)} ); diff --git a/packages/trader/src/AppV2/Components/TradeParameters/GrowthRate/growth-rate-picker.tsx b/packages/trader/src/AppV2/Components/TradeParameters/GrowthRate/growth-rate-picker.tsx index 9cfca3aa0874..a697d28cb176 100644 --- a/packages/trader/src/AppV2/Components/TradeParameters/GrowthRate/growth-rate-picker.tsx +++ b/packages/trader/src/AppV2/Components/TradeParameters/GrowthRate/growth-rate-picker.tsx @@ -4,20 +4,14 @@ import { ActionSheet, Text, WheelPicker } from '@deriv-com/quill-ui'; import { localize, Localize } from '@deriv/translations'; import { getGrowthRatePercentage } from '@deriv/shared'; import { Skeleton } from '@deriv/components'; -import type { TWheelPickerInitialValues } from 'Stores/Modules/Trading/trade-store'; +import type { TV2ParamsInitialValues } from 'Stores/Modules/Trading/trade-store'; type TGrowthRatePickerProps = { accumulator_range_list?: number[]; growth_rate: number; maximum_ticks: number; setGrowthRate: (growth_rate: number) => void; - setWheelPickerInitialValues: ({ - value, - name, - }: { - value: number | string; - name: keyof TWheelPickerInitialValues; - }) => void; + setV2ParamsInitialValues: ({ value, name }: { value: number | string; name: keyof TV2ParamsInitialValues }) => void; should_show_details?: boolean; tick_size_barrier_percentage: string; }; @@ -31,7 +25,7 @@ const GrowthRatePicker = ({ growth_rate, maximum_ticks, setGrowthRate, - setWheelPickerInitialValues, + setV2ParamsInitialValues, should_show_details, tick_size_barrier_percentage, }: TGrowthRatePickerProps) => { @@ -52,7 +46,7 @@ const GrowthRatePicker = ({ React.useEffect(() => { if (!initial_growth_rate.current && growth_rate) { initial_growth_rate.current = growth_rate; - setWheelPickerInitialValues({ value: growth_rate, name: 'growth_rate' }); + setV2ParamsInitialValues({ value: growth_rate, name: 'growth_rate' }); } return () => { if (initial_growth_rate.current && initial_growth_rate.current !== selected_growth_rate.current) { @@ -65,7 +59,7 @@ const GrowthRatePicker = ({ const handleSave = () => { initial_growth_rate.current = selected_growth_rate.current; - setWheelPickerInitialValues({ value: selected_growth_rate.current, name: 'growth_rate' }); + setV2ParamsInitialValues({ value: selected_growth_rate.current, name: 'growth_rate' }); }; const handlePickerValuesChange = (value: string | number) => { diff --git a/packages/trader/src/AppV2/Components/TradeParameters/GrowthRate/growth-rate.tsx b/packages/trader/src/AppV2/Components/TradeParameters/GrowthRate/growth-rate.tsx index 7860e0165bab..bc4f68012236 100644 --- a/packages/trader/src/AppV2/Components/TradeParameters/GrowthRate/growth-rate.tsx +++ b/packages/trader/src/AppV2/Components/TradeParameters/GrowthRate/growth-rate.tsx @@ -25,9 +25,9 @@ const GrowthRate = observer(({ is_minimized }: TGrowthRateProps) => { maximum_ticks, onChange, proposal_info, - setWheelPickerInitialValues, + setV2ParamsInitialValues, tick_size_barrier_percentage, - wheel_picker_initial_values, + v2_params_initial_values, } = useTraderStore(); const [is_open, setIsOpen] = React.useState(false); @@ -53,7 +53,7 @@ const GrowthRate = observer(({ is_minimized }: TGrowthRateProps) => { maximum_ticks={maximum_ticks} growth_rate={growth_rate} setGrowthRate={handleGrowthRateChange} - setWheelPickerInitialValues={setWheelPickerInitialValues} + setV2ParamsInitialValues={setV2ParamsInitialValues} should_show_details={is_proposal_data_available} tick_size_barrier_percentage={tick_size_barrier_percentage} /> @@ -78,7 +78,7 @@ const GrowthRate = observer(({ is_minimized }: TGrowthRateProps) => { ]; React.useEffect(() => { - const initial_growth_rate = wheel_picker_initial_values?.growth_rate; + const initial_growth_rate = v2_params_initial_values?.growth_rate; if (initial_growth_rate && growth_rate !== initial_growth_rate) handleGrowthRateChange(initial_growth_rate); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); diff --git a/packages/trader/src/AppV2/Components/TradeParameters/Stake/__tests__/stake.spec.tsx b/packages/trader/src/AppV2/Components/TradeParameters/Stake/__tests__/stake.spec.tsx new file mode 100644 index 000000000000..8aa6b082504f --- /dev/null +++ b/packages/trader/src/AppV2/Components/TradeParameters/Stake/__tests__/stake.spec.tsx @@ -0,0 +1,287 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { CONTRACT_TYPES, TRADE_TYPES } from '@deriv/shared'; +import { mockStore } from '@deriv/stores'; +import ModulesProvider from 'Stores/Providers/modules-providers'; +import TraderProviders from '../../../../../trader-providers'; +import Stake from '../stake'; + +const stake_param_label = 'Stake'; +const input_placeholder = 'Amount'; +const save_button_label = 'Save'; + +describe('Stake', () => { + let default_mock_store: ReturnType; + + beforeEach( + () => + (default_mock_store = mockStore({ + modules: { + trade: { + ...mockStore({}), + amount: 10, + basis: 'stake', + contract_type: TRADE_TYPES.RISE_FALL, + currency: 'USD', + proposal_info: { + [CONTRACT_TYPES.CALL]: { + id: '53e8cb91-8c13-60a3-289f-778e8386367c', + has_error: false, + message: + 'Win payout if Volatility 100 (1s) Index is strictly higher than entry spot at 5 minutes after contract start time.', + payout: 19.55, + }, + [CONTRACT_TYPES.PUT]: { + id: '2b5dd806-7505-8af7-1bbb-5e24ac48bbbc', + has_error: false, + message: + 'Win payout if Volatility 100 (1s) Index is strictly lower than entry spot at 5 minutes after contract start time.', + payout: 19.51, + }, + }, + trade_types: { + [CONTRACT_TYPES.CALL]: 'Higher', + [CONTRACT_TYPES.PUT]: 'Lower', + }, + validation_errors: { amount: [] }, + validation_params: { + [CONTRACT_TYPES.CALL]: { max_payout: '50000.00' }, + [CONTRACT_TYPES.PUT]: { max_payout: '50000.00' }, + }, + v2_params_initial_values: { + stake: 10, + }, + }, + }, + })) + ); + + const MockedStake = ({ store = default_mock_store }: { store?: ReturnType }) => ( + + + + + + ); + + it('should switch basis to stake if it is different', () => { + default_mock_store.modules.trade.basis = 'payout'; + render(); + + expect(default_mock_store.modules.trade.onChange).toHaveBeenCalledWith({ + target: { name: 'basis', value: 'stake' }, + }); + }); + + it('should render trade param with "Stake" label and input with a value equal to the current stake amount value', () => { + render(); + const { amount, currency } = default_mock_store.modules.trade; + expect(screen.getByText(stake_param_label)).toBeInTheDocument(); + expect(screen.getByRole('textbox')).toHaveValue(`${amount} ${currency}`); + }); + + it('should open ActionSheet with input, details and "Save" button if user clicks on "Stake" trade param', () => { + render(); + + expect(screen.queryByTestId('dt-actionsheet-overlay')).not.toBeInTheDocument(); + + userEvent.click(screen.getByText(stake_param_label)); + + expect(screen.getByTestId('dt-actionsheet-overlay')).toBeInTheDocument(); + expect(screen.getByPlaceholderText(input_placeholder)).toBeInTheDocument(); + expect(screen.getAllByText(/payout/i)).toHaveLength(3); + expect(screen.getByRole('button', { name: save_button_label })).toBeInTheDocument(); + }); + + it('should call onChange when stake input changes', () => { + render(); + userEvent.click(screen.getByText(stake_param_label)); + userEvent.type(screen.getByPlaceholderText(input_placeholder), '0'); + expect(default_mock_store.modules.trade.onChange).toHaveBeenCalledWith({ + target: { name: 'amount', value: '100' }, + }); + }); + + it('should not render payout details for Accumulators', () => { + default_mock_store.modules.trade.is_accumulator = true; + render(); + userEvent.click(screen.getByText(stake_param_label)); + expect(screen.queryByText(/payout/i)).not.toBeInTheDocument(); + }); + + it('should not render payout details for Turbos', () => { + default_mock_store.modules.trade.is_turbos = true; + render(); + userEvent.click(screen.getByText(stake_param_label)); + expect(screen.queryByText(/payout/i)).not.toBeInTheDocument(); + }); + + it('should not render payout details for Vanillas', () => { + default_mock_store.modules.trade.is_vanilla = true; + render(); + userEvent.click(screen.getByText(stake_param_label)); + expect(screen.queryByText(/payout/i)).not.toBeInTheDocument(); + }); + + it('should render Stop out and Comission details instead of payout details for Multipliers', () => { + render( + + ); + + userEvent.click(screen.getByText(stake_param_label)); + expect(screen.getByText('Acceptable range: 1.00 to 2,000.00 USD')).toBeInTheDocument(); + expect(screen.getByText('Stop out')).toBeInTheDocument(); + expect(screen.getByText('Commission')).toBeInTheDocument(); + }); + + it('should call setV2ParamsInitialValues if v2_params_initial_values.stake !== amount on mount and on Save button click if no error', () => { + default_mock_store.modules.trade.amount = '30'; + render(); + userEvent.click(screen.getByText(stake_param_label)); + userEvent.type(screen.getByPlaceholderText(input_placeholder), '0'); + + expect(default_mock_store.modules.trade.setV2ParamsInitialValues).toHaveBeenCalledTimes(1); + + userEvent.click(screen.getByRole('button', { name: save_button_label })); + expect(default_mock_store.modules.trade.setV2ParamsInitialValues).toHaveBeenCalledTimes(2); + }); + + it('should call onChange on component mount if v2_params_initial_values.stake is not equal to amount', () => { + default_mock_store.modules.trade.amount = '30'; + render(); + expect(default_mock_store.modules.trade.onChange).toHaveBeenCalledWith({ + target: { name: 'amount', value: 10 }, + }); + }); + + it('should show error in case of a validation error if input is non-empty', () => { + const error_text = "Please enter a stake amount that's at least 0.35."; + default_mock_store.modules.trade.proposal_info = { + PUT: { id: '', has_error: true, message: error_text }, + CALL: { id: '', has_error: true, message: error_text }, + }; + default_mock_store.modules.trade.validation_errors.amount = [error_text]; + default_mock_store.modules.trade.amount = 0; + + render(); + userEvent.click(screen.getByText(stake_param_label)); + expect(screen.getByText(error_text)).toBeInTheDocument(); + expect(screen.getAllByText('- USD')).toHaveLength(2); + }); + + it('should not show error in case of a validation error if input is empty, and show it only after Save button is clicked', () => { + const { rerender } = render(); + userEvent.click(screen.getByText(stake_param_label)); + userEvent.type(screen.getByPlaceholderText(input_placeholder), '{backspace}{backspace}'); + + const error_text = 'Amount is a required field.'; + rerender( + + ); + expect(screen.queryByText(error_text)).not.toBeInTheDocument(); + + userEvent.click(screen.getByRole('button', { name: save_button_label })); + expect(screen.getByText(error_text)).toBeInTheDocument(); + expect(screen.getAllByText('- USD')).toHaveLength(2); + }); + + it('should show max payout error with the least current payout when both of the 2 contract types exceed max payout', () => { + const error_text_rise = 'Minimum stake of 0.35 and maximum payout of 50000.00. Current payout is 50631.97.'; + const error_text_fall = 'Minimum stake of 0.35 and maximum payout of 50000.00. Current payout is 50513.21.'; + default_mock_store.modules.trade.proposal_info = { + CALL: { id: '', has_error: true, message: error_text_rise }, + PUT: { id: '', has_error: true, message: error_text_fall }, + }; + default_mock_store.modules.trade.validation_errors.amount = [error_text_fall]; + default_mock_store.modules.trade.amount = '26500'; + + render(); + userEvent.click(screen.getByText(stake_param_label)); + + expect(screen.getByText(error_text_fall)).toBeInTheDocument(); + expect(screen.queryByText('- USD')).not.toBeInTheDocument(); + }); + + it('should not show max payout error if one of the 2 contract types satisfies max payout', () => { + const error_text_rise = 'Minimum stake of 0.35 and maximum payout of 50000.00. Current payout is 50058.77.'; + const success_text_fall = + 'Win payout if Volatility 100 (1s) Index is strictly lower than entry spot at 5 minutes after contract start time.'; + default_mock_store.modules.trade.proposal_info = { + CALL: { id: '', has_error: true, message: error_text_rise }, + PUT: { + id: 'b608baf2-ba5d-00e0-8035-9af5c0769664', + has_error: false, + message: success_text_fall, + payout: 49942.7, + }, + }; + default_mock_store.modules.trade.amount = '26200'; + + render(); + userEvent.click(screen.getByText(stake_param_label)); + + expect(screen.queryByText(error_text_rise)).not.toBeInTheDocument(); + }); +}); diff --git a/packages/trader/src/AppV2/Components/TradeParameters/Stake/index.ts b/packages/trader/src/AppV2/Components/TradeParameters/Stake/index.ts index 918d2dcb512b..3d5500b3fde5 100644 --- a/packages/trader/src/AppV2/Components/TradeParameters/Stake/index.ts +++ b/packages/trader/src/AppV2/Components/TradeParameters/Stake/index.ts @@ -1,3 +1,4 @@ import Stake from './stake'; +import './stake.scss'; export default Stake; diff --git a/packages/trader/src/AppV2/Components/TradeParameters/Stake/stake-details.tsx b/packages/trader/src/AppV2/Components/TradeParameters/Stake/stake-details.tsx new file mode 100644 index 000000000000..c5497cc2300e --- /dev/null +++ b/packages/trader/src/AppV2/Components/TradeParameters/Stake/stake-details.tsx @@ -0,0 +1,141 @@ +import React from 'react'; +import clsx from 'clsx'; +import { Text } from '@deriv-com/quill-ui'; +import { FormatUtils } from '@deriv-com/utils'; +import { getCurrencyDisplayCode, getTradeTypeName, TRADE_TYPES } from '@deriv/shared'; +import { Localize } from '@deriv/translations'; +import { TTradeStore } from 'Types'; + +type TStakeDetailsProps = Pick< + TTradeStore, + 'commission' | 'contract_type' | 'currency' | 'has_stop_loss' | 'is_multiplier' | 'stop_out' +> & { + contract_types: string[]; + details: { + first_contract_payout: number; + max_payout: string | number; + max_stake: string | number; + min_stake: string | number; + second_contract_payout: number; + }; + is_loading_proposal: boolean; + is_max_payout_exceeded: boolean; + should_show_payout_details: boolean; + stake_error: string; +}; + +const StakeDetails = ({ + commission, + contract_type, + contract_types, + currency, + details, + has_stop_loss, + is_loading_proposal, + is_multiplier, + is_max_payout_exceeded, + should_show_payout_details, + stake_error, + stop_out, +}: TStakeDetailsProps) => { + const [displayed_values, setDisplayedValues] = React.useState({ + commission: '', + first_contract_payout: '', + second_contract_payout: '', + stop_out: '', + }); + + React.useEffect(() => { + const getDisplayedValue = (new_value?: number | string, current_value?: string) => { + return ((current_value === '-' && is_loading_proposal) || stake_error) && !is_max_payout_exceeded + ? '-' + : FormatUtils.formatMoney(Number(new_value)); + }; + + const { + commission: commission_value, + first_contract_payout, + second_contract_payout, + stop_out: stop_out_value, + } = displayed_values; + const new_commission = getDisplayedValue(Math.abs(Number(commission)), commission_value); + const new_payout_1 = getDisplayedValue(details.first_contract_payout, first_contract_payout); + const new_payout_2 = getDisplayedValue(details.second_contract_payout, second_contract_payout); + const new_stop_out = getDisplayedValue(Math.abs(Number(stop_out)), stop_out_value); + + if ( + commission_value !== new_commission || + first_contract_payout !== new_payout_1 || + second_contract_payout !== new_payout_2 || + stop_out_value !== new_stop_out + ) { + setDisplayedValues({ + commission: new_commission, + first_contract_payout: new_payout_1, + second_contract_payout: new_payout_2, + stop_out: new_stop_out, + }); + } + }, [commission, details, displayed_values, is_loading_proposal, is_max_payout_exceeded, stake_error, stop_out]); + + const payout_title = ; + const content = [ + { + is_displayed: !has_stop_loss && is_multiplier && !should_show_payout_details, + label: , + value: displayed_values.stop_out, + }, + { + is_displayed: is_multiplier && !should_show_payout_details, + label: , + value: displayed_values.commission, + }, + { + is_displayed: !!details.max_payout && should_show_payout_details, + label: , + value: FormatUtils.formatMoney(+details.max_payout), + }, + { + contract_type: getTradeTypeName(contract_types[0], { + isHighLow: contract_type === TRADE_TYPES.HIGH_LOW, + }), + is_displayed: !!contract_types.length && should_show_payout_details, + label: payout_title, + has_error: details.first_contract_payout > +details.max_payout && is_max_payout_exceeded, + value: displayed_values.first_contract_payout, + }, + { + contract_type: getTradeTypeName(contract_types[1], { + isHighLow: contract_type === TRADE_TYPES.HIGH_LOW, + }), + is_displayed: contract_types.length > 1 && should_show_payout_details, + label: payout_title, + has_error: details.second_contract_payout > +details.max_payout && is_max_payout_exceeded, + value: displayed_values.second_contract_payout, + }, + ]; + + return ( +
+ {content.map( + ({ contract_type, is_displayed, label, has_error, value }, idx) => + is_displayed && ( +
+ + {label} + {contract_type && ` (${contract_type})`} + + + {value} {getCurrencyDisplayCode(currency)} + +
+ ) + )} +
+ ); +}; + +export default StakeDetails; diff --git a/packages/trader/src/AppV2/Components/TradeParameters/Stake/stake.scss b/packages/trader/src/AppV2/Components/TradeParameters/Stake/stake.scss new file mode 100644 index 000000000000..565565aaea89 --- /dev/null +++ b/packages/trader/src/AppV2/Components/TradeParameters/Stake/stake.scss @@ -0,0 +1,27 @@ +.stake-content { + height: 23.6rem; + padding: 0 var(--component-actionSheet-spacing-padding-md); + display: flex; + flex-direction: column; + justify-content: space-between; + + &__details { + display: flex; + flex-direction: column; + gap: var(--core-spacing-400); + + &-row { + display: flex; + justify-content: space-between; + + p:first-child { + color: var(--component-textIcon-normal-subtle); + } + &.error { + p:last-child { + color: var(--component-textIcon-statusNormal-danger); + } + } + } + } +} diff --git a/packages/trader/src/AppV2/Components/TradeParameters/Stake/stake.tsx b/packages/trader/src/AppV2/Components/TradeParameters/Stake/stake.tsx index ba58c9764db4..7ca1280637c9 100644 --- a/packages/trader/src/AppV2/Components/TradeParameters/Stake/stake.tsx +++ b/packages/trader/src/AppV2/Components/TradeParameters/Stake/stake.tsx @@ -1,37 +1,234 @@ import React from 'react'; import clsx from 'clsx'; import { observer } from 'mobx-react'; -import { TextField } from '@deriv-com/quill-ui'; -import { Localize } from '@deriv/translations'; +import { ActionSheet, TextField, TextFieldWithSteppers } from '@deriv-com/quill-ui'; +import { localize, Localize } from '@deriv/translations'; +import { getCurrencyDisplayCode, getDecimalPlaces } from '@deriv/shared'; +import { FormatUtils } from '@deriv-com/utils'; import { useTraderStore } from 'Stores/useTraderStores'; -import { getCurrencyDisplayCode } from '@deriv/shared'; +import { getDisplayedContractTypes } from 'AppV2/Utils/trade-types-utils'; +import StakeDetails from './stake-details'; type TStakeProps = { is_minimized?: boolean; }; -const BASIS = { - PAYOUT: 'payout', - STAKE: 'stake', -}; - const Stake = observer(({ is_minimized }: TStakeProps) => { - const { amount, basis, currency, onChange } = useTraderStore(); + const { + amount, + basis, + commission, + contract_type, + currency, + has_stop_loss, + is_accumulator, + is_multiplier, + is_turbos, + is_vanilla, + onChange, + proposal_info, + setV2ParamsInitialValues, + stop_out, + trade_type_tab, + trade_types, + validation_errors, + validation_params, + v2_params_initial_values, + } = useTraderStore(); + const [is_open, setIsOpen] = React.useState(false); + const [should_show_error, setShouldShowError] = React.useState(true); + const contract_types = getDisplayedContractTypes(trade_types, contract_type, trade_type_tab); + // first contract type data: + const { + has_error: has_error_1, + id: id_1, + message: message_1 = '', + payout: payout_1 = 0, + } = proposal_info[contract_types[0]] ?? {}; + // second contract type data: + const { + has_error: has_error_2, + id: id_2, + message: message_2 = '', + payout: payout_2 = 0, + } = proposal_info[contract_types[1]] ?? {}; + const is_loading_proposal = !id_1 || (!!contract_types[1] && !id_2); + const proposal_error_message_1 = has_error_1 ? message_1 : ''; + const proposal_error_message_2 = has_error_2 ? message_2 : ''; + const proposal_error_message = proposal_error_message_1 || proposal_error_message_2 || validation_errors?.amount[0]; + /* TODO: stop using Max payout from error text as a default max payout and stop using error text for is_max_payout_exceeded after validation_params are added to proposal API (both success & error response): + E.g., for is_max_payout_exceeded, we have to temporarily check the error text: Max payout error always contains 3 numbers, the check will work for any languages: */ + const float_number_search_regex = /\d+(\.\d+)?/g; + const is_max_payout_exceeded = + proposal_error_message_1.match(float_number_search_regex)?.length === 3 || + proposal_error_message_2.match(float_number_search_regex)?.length === 3; + const error_max_payout = + is_max_payout_exceeded && proposal_error_message + ? Number(proposal_error_message.match(float_number_search_regex)?.[1]) + : 0; + const { max_payout = error_max_payout, stake } = validation_params[contract_types[0]] ?? {}; + const { max: max_stake = 0, min: min_stake = 0 } = stake ?? {}; + const error_payout_1 = proposal_error_message_1 + ? Number(proposal_error_message_1.match(float_number_search_regex)?.[2]) + : 0; + const error_payout_2 = proposal_error_message_2 + ? Number(proposal_error_message_2.match(float_number_search_regex)?.[2]) + : 0; + const first_contract_payout = payout_1 || error_payout_1; + const second_contract_payout = payout_2 || error_payout_2; + const validation_error_text = contract_types[1] ? validation_errors?.amount[0] : proposal_error_message; + const main_error_message = + (validation_error_text && error_payout_1 > error_payout_2 + ? proposal_error_message_2 + : proposal_error_message_1) || validation_error_text; + const has_both_errors = has_error_1 && has_error_2; + const two_contracts_error = has_both_errors || amount.toString() === '' ? main_error_message : ''; + const stake_error = contract_types[1] ? two_contracts_error : validation_error_text; + + const [details, setDetails] = React.useState({ + first_contract_payout, + max_payout, + max_stake, + min_stake, + second_contract_payout, + }); React.useEffect(() => { - if (basis === BASIS.PAYOUT) onChange({ target: { name: 'basis', value: BASIS.STAKE } }); + const initial_stake = v2_params_initial_values?.stake; + if (initial_stake && amount !== initial_stake) { + onChange({ target: { name: 'amount', value: initial_stake } }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + React.useEffect(() => { + if (is_open && v2_params_initial_values.stake !== amount) { + setV2ParamsInitialValues({ value: amount, name: 'stake' }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [is_open]); + React.useEffect(() => { + if (basis !== 'stake') onChange({ target: { name: 'basis', value: 'stake' } }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [basis]); + React.useEffect(() => { + if (is_open) { + if ( + (details.first_contract_payout !== first_contract_payout && first_contract_payout) || + (details.max_payout !== max_payout && max_payout) || + (details.max_stake !== max_stake && max_stake) || + (details.min_stake !== min_stake && min_stake) || + (details.second_contract_payout !== second_contract_payout && second_contract_payout) + ) { + setDetails({ + first_contract_payout, + max_payout, + max_stake, + min_stake, + second_contract_payout, + }); + } + } + }, [details, is_open, max_payout, max_stake, min_stake, first_contract_payout, second_contract_payout]); + + const getInputMessage = () => + (should_show_error && stake_error) || + (!!details.min_stake && !!details.max_stake && ( + + )); + + const handleOnChange = (e: { target: { name: string; value: string } }) => { + setShouldShowError(e.target.value !== ''); + // TODO: this value modifications will no longer be needed after quill-ui TextFieldWithSteppers is improved to accept and replace commas with dots: + let value = e.target.value.replaceAll(',', '.').replaceAll(/\.{2,}/g, '.'); + if (Number(value.match(/\./g)?.length) > 1) { + value = parseFloat(value).toString(); + } + onChange({ target: { name: 'amount', value } }); + }; + + const onClose = (is_saved = false) => { + if (is_open) { + if (!is_saved) { + onChange({ target: { name: 'amount', value: v2_params_initial_values.stake } }); + } + if (v2_params_initial_values.stake !== amount) { + setV2ParamsInitialValues({ value: amount, name: 'stake' }); + } + setIsOpen(false); + } + }; - if (basis === BASIS.PAYOUT) return null; return ( - } - value={`${amount} ${getCurrencyDisplayCode(currency)}`} - className={clsx('trade-params__option', is_minimized && 'trade-params__option--minimized')} - /> + <> + } + onClick={() => setIsOpen(true)} + value={`${v2_params_initial_values?.stake ?? amount} ${getCurrencyDisplayCode(currency)}`} + className={clsx('trade-params__option', is_minimized && 'trade-params__option--minimized')} + /> + onClose(false)} position='left' expandable={false}> + + } /> + + + + + , + onAction: () => { + if (!stake_error && !is_loading_proposal) { + onClose(true); + } else { + setShouldShowError(true); + } + }, + }} + /> + + + ); }); diff --git a/packages/trader/src/AppV2/Components/TradeParameters/Strike/strike-wheel.tsx b/packages/trader/src/AppV2/Components/TradeParameters/Strike/strike-wheel.tsx index ca07d290ad7a..3cb727919878 100644 --- a/packages/trader/src/AppV2/Components/TradeParameters/Strike/strike-wheel.tsx +++ b/packages/trader/src/AppV2/Components/TradeParameters/Strike/strike-wheel.tsx @@ -3,7 +3,7 @@ import debounce from 'lodash.debounce'; import { ActionSheet, Text, WheelPicker } from '@deriv-com/quill-ui'; import { Skeleton } from '@deriv/components'; import { Localize } from '@deriv/translations'; -import type { TWheelPickerInitialValues } from 'Stores/Modules/Trading/trade-store'; +import type { TV2ParamsInitialValues } from 'Stores/Modules/Trading/trade-store'; type TStrikeWheelProps = { current_strike: string; @@ -13,13 +13,7 @@ type TStrikeWheelProps = { strike_price_list: { value: string; }[]; - setWheelPickerInitialValues: ({ - value, - name, - }: { - value: number | string; - name: keyof TWheelPickerInitialValues; - }) => void; + setV2ParamsInitialValues: ({ value, name }: { value: number | string; name: keyof TV2ParamsInitialValues }) => void; }; const onWheelPickerScrollDebounced = debounce( @@ -33,20 +27,20 @@ const StrikeWheel = ({ onStrikePriceSelect, payout_per_point, strike_price_list, - setWheelPickerInitialValues, + setV2ParamsInitialValues, }: TStrikeWheelProps) => { const initial_value_ref = React.useRef(); const selected_value_ref = React.useRef(current_strike); const onSave = () => { initial_value_ref.current = selected_value_ref.current; - setWheelPickerInitialValues({ value: selected_value_ref.current, name: 'strike' }); + setV2ParamsInitialValues({ value: selected_value_ref.current, name: 'strike' }); }; React.useEffect(() => { if (!initial_value_ref.current && current_strike) { initial_value_ref.current = current_strike; - setWheelPickerInitialValues({ value: current_strike, name: 'strike' }); + setV2ParamsInitialValues({ value: current_strike, name: 'strike' }); } return () => { diff --git a/packages/trader/src/AppV2/Components/TradeParameters/Strike/strike.tsx b/packages/trader/src/AppV2/Components/TradeParameters/Strike/strike.tsx index abd503d9dd57..76acef55d7bb 100644 --- a/packages/trader/src/AppV2/Components/TradeParameters/Strike/strike.tsx +++ b/packages/trader/src/AppV2/Components/TradeParameters/Strike/strike.tsx @@ -24,8 +24,8 @@ const Strike = observer(({ is_minimized }: TStrikeProps) => { currency, onChange, proposal_info, - setWheelPickerInitialValues, - wheel_picker_initial_values, + setV2ParamsInitialValues, + v2_params_initial_values, } = useTraderStore(); const is_small_screen = window.innerHeight <= 640; @@ -47,7 +47,7 @@ const Strike = observer(({ is_minimized }: TStrikeProps) => { onStrikePriceSelect={handleStrikeChange} payout_per_point={payout_per_point} strike_price_list={strike_price_list} - setWheelPickerInitialValues={setWheelPickerInitialValues} + setV2ParamsInitialValues={setV2ParamsInitialValues} /> ), }, @@ -59,7 +59,7 @@ const Strike = observer(({ is_minimized }: TStrikeProps) => { const classname = clsx('trade-params__option', is_minimized && 'trade-params__option--minimized'); React.useEffect(() => { - const initial_strike = wheel_picker_initial_values?.strike; + const initial_strike = v2_params_initial_values?.strike; if (initial_strike && barrier_1 !== initial_strike) { handleStrikeChange(initial_strike); } diff --git a/packages/trader/src/AppV2/Components/TradeParameters/TakeProfit/__tests__/take-profit.spec.tsx b/packages/trader/src/AppV2/Components/TradeParameters/TakeProfit/__tests__/take-profit.spec.tsx index 63470e7c5bdc..f87c393f1f1d 100644 --- a/packages/trader/src/AppV2/Components/TradeParameters/TakeProfit/__tests__/take-profit.spec.tsx +++ b/packages/trader/src/AppV2/Components/TradeParameters/TakeProfit/__tests__/take-profit.spec.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import { CONTRACT_TYPES, TRADE_TYPES } from '@deriv/shared'; import { mockStore } from '@deriv/stores'; import ModulesProvider from 'Stores/Providers/modules-providers'; import TraderProviders from '../../../../../trader-providers'; @@ -11,8 +12,36 @@ const data_testid = 'dt_input_with_steppers'; describe('TakeProfit', () => { let default_mock_store: ReturnType; - - beforeEach(() => (default_mock_store = mockStore({}))); + const validation_params = { + [CONTRACT_TYPES.MULTIPLIER.UP]: { + take_profit: { + min: '0.01', + max: '100', + }, + }, + [CONTRACT_TYPES.MULTIPLIER.DOWN]: { + take_profit: { + min: '0.01', + max: '100', + }, + }, + }; + + beforeEach( + () => + (default_mock_store = mockStore({ + modules: { + trade: { + ...mockStore({}), + contract_type: TRADE_TYPES.MULTIPLIER, + trade_types: { + [CONTRACT_TYPES.MULTIPLIER.UP]: 'Multiply Up', + [CONTRACT_TYPES.MULTIPLIER.DOWN]: 'Multiply Down', + }, + }, + }, + })) + ); afterEach(() => jest.clearAllMocks()); @@ -86,12 +115,7 @@ describe('TakeProfit', () => { }); it('should validate values, that user typed, and show error text if they are out of acceptable range. If values are wrong, when user clicks on "Save" button onChangeMultiple and onChange will not be called', () => { - default_mock_store.modules.trade.validation_params = { - take_profit: { - min: '0.01', - max: '100', - }, - }; + default_mock_store.modules.trade.validation_params = validation_params; default_mock_store.modules.trade.take_profit = ''; mockTakeProfit(); @@ -116,12 +140,7 @@ describe('TakeProfit', () => { }); it('should validate values, that user typed. In case if values are correct, when user clicks on "Save" button onChangeMultiple and onChange will be called', () => { - default_mock_store.modules.trade.validation_params = { - take_profit: { - min: '0.01', - max: '100', - }, - }; + default_mock_store.modules.trade.validation_params = validation_params; mockTakeProfit(); userEvent.click(screen.getByText(take_profit_trade_param)); diff --git a/packages/trader/src/AppV2/Components/TradeParameters/TakeProfit/take-profit.tsx b/packages/trader/src/AppV2/Components/TradeParameters/TakeProfit/take-profit.tsx index 3a404a5d01ee..d760a8e2b3ed 100644 --- a/packages/trader/src/AppV2/Components/TradeParameters/TakeProfit/take-profit.tsx +++ b/packages/trader/src/AppV2/Components/TradeParameters/TakeProfit/take-profit.tsx @@ -10,6 +10,7 @@ import Carousel from 'AppV2/Components/Carousel'; import CarouselHeader from 'AppV2/Components/Carousel/carousel-header'; import TakeProfitInput from './take-profit-input'; import TradeParamDefinition from 'AppV2/Components/TradeParamDefinition'; +import { getDisplayedContractTypes } from 'AppV2/Utils/trade-types-utils'; type TTakeProfitProps = { is_minimized?: boolean; @@ -17,11 +18,14 @@ type TTakeProfitProps = { const TakeProfit = observer(({ is_minimized }: TTakeProfitProps) => { const { + contract_type, currency, has_open_accu_contract, has_take_profit, is_accumulator, take_profit, + trade_types, + trade_type_tab, onChangeMultiple, onChange, validation_params, @@ -36,8 +40,9 @@ const TakeProfit = observer(({ is_minimized }: TTakeProfitProps) => { const focused_input_ref = React.useRef(null); const focus_timeout = React.useRef>(); - const min_take_profit = validation_params?.take_profit?.min; - const max_take_profit = validation_params?.take_profit?.max; + const contract_types = getDisplayedContractTypes(trade_types, contract_type, trade_type_tab); + const min_take_profit = validation_params[contract_types[0]]?.take_profit?.min; + const max_take_profit = validation_params[contract_types[0]]?.take_profit?.max; const decimals = getDecimalPlaces(currency); const getInputMessage = () => diff --git a/packages/trader/src/AppV2/Utils/trade-types-utils.tsx b/packages/trader/src/AppV2/Utils/trade-types-utils.tsx index 13d482e76088..d066ed7682ef 100644 --- a/packages/trader/src/AppV2/Utils/trade-types-utils.tsx +++ b/packages/trader/src/AppV2/Utils/trade-types-utils.tsx @@ -1,8 +1,17 @@ import React from 'react'; import { Localize } from '@deriv/translations'; import { getAvailableContractTypes, getCategoriesSortedByKey } from 'Modules/Trading/Helpers/contract-type'; -import { TRADE_TYPES, unsupported_contract_types_list } from '@deriv/shared'; +import { + getContractTypePosition, + getSupportedContracts, + TRADE_TYPES, + unsupported_contract_types_list, +} from '@deriv/shared'; import { useTraderStore } from 'Stores/useTraderStores'; +import { getTradeTypeTabsList } from './trade-params-utils'; + +const getSortedIndex = (type: string) => + getContractTypePosition(type as keyof ReturnType) === 'bottom' ? 1 : 0; export const CONTRACT_LIST = { ACCUMULATORS: 'Accumulators', @@ -47,3 +56,13 @@ export const getTradeTypesList = (contract_types_list: ReturnType['trade_types'], + contract_type: string, + trade_type_tab: string +) => + Object.keys(trade_types) + .filter(type => !getTradeTypeTabsList(contract_type).length || type === trade_type_tab) + .sort((a, b) => getSortedIndex(a) - getSortedIndex(b)); diff --git a/packages/trader/src/Stores/Modules/Trading/Helpers/__tests__/contract-type.spec.ts b/packages/trader/src/Stores/Modules/Trading/Helpers/__tests__/contract-type.spec.ts index c9081987d741..d66fcb4d7ed6 100644 --- a/packages/trader/src/Stores/Modules/Trading/Helpers/__tests__/contract-type.spec.ts +++ b/packages/trader/src/Stores/Modules/Trading/Helpers/__tests__/contract-type.spec.ts @@ -299,10 +299,10 @@ describe('ContractType.getContractValues', () => { has_cancellation: false, }); }); - it('should use strike value from wheel_picker_initial_values for Vanillas contract as barrier_1 if isDTraderV2 === true', async () => { + it('should use strike value from v2_params_initial_values for Vanillas contract as barrier_1 if isDTraderV2 === true', async () => { const symbol = '1HZ100V'; trade_store.contract_type = 'vanillalongcall'; - trade_store.wheel_picker_initial_values = { strike: '+1.80' }; + trade_store.v2_params_initial_values = { strike: '+1.80' }; await ContractType.buildContractTypesConfig(symbol); const result = ContractType.getContractValues(trade_store); diff --git a/packages/trader/src/Stores/Modules/Trading/Helpers/contract-type.ts b/packages/trader/src/Stores/Modules/Trading/Helpers/contract-type.ts index a77f848e826c..db5f2a8c79ea 100644 --- a/packages/trader/src/Stores/Modules/Trading/Helpers/contract-type.ts +++ b/packages/trader/src/Stores/Modules/Trading/Helpers/contract-type.ts @@ -195,7 +195,7 @@ export const ContractType = (() => { short_barriers, long_barriers, strike_price_choices, - wheel_picker_initial_values, + v2_params_initial_values, } = store; if (!contract_type) return {}; @@ -211,10 +211,10 @@ export const ContractType = (() => { case 'Call': case 'Put': stored_barriers_data = - wheel_picker_initial_values?.strike && isDTraderV2() + v2_params_initial_values?.strike && isDTraderV2() ? ({ ...strike_price_choices, - barrier: wheel_picker_initial_values.strike, + barrier: v2_params_initial_values.strike, } as TTradeStore['strike_price_choices']) : strike_price_choices; break; diff --git a/packages/trader/src/Stores/Modules/Trading/__tests__/trade-store.spec.ts b/packages/trader/src/Stores/Modules/Trading/__tests__/trade-store.spec.ts index 05f7fa0f037d..dccb4d72fd74 100644 --- a/packages/trader/src/Stores/Modules/Trading/__tests__/trade-store.spec.ts +++ b/packages/trader/src/Stores/Modules/Trading/__tests__/trade-store.spec.ts @@ -370,34 +370,34 @@ describe('TradeStore', () => { expect(mockedTradeStore.trade_type_tab).toEqual(''); }); }); - describe('setWheelPickerInitialValues', () => { + describe('setV2ParamsInitialValues', () => { beforeEach(() => { - mockedTradeStore.clearWheelPickerInitialValues(); + mockedTradeStore.clearV2ParamsInitialValues(); }); - it('should set growth rate into a wheel_picker_initial_values', () => { - expect(mockedTradeStore.wheel_picker_initial_values).toEqual({}); + it('should set growth rate into a v2_params_initial_values', () => { + expect(mockedTradeStore.v2_params_initial_values).toEqual({}); - mockedTradeStore.setWheelPickerInitialValues({ name: 'growth_rate', value: 0.03 }); + mockedTradeStore.setV2ParamsInitialValues({ name: 'growth_rate', value: 0.03 }); - expect(mockedTradeStore.wheel_picker_initial_values.growth_rate).toEqual(0.03); + expect(mockedTradeStore.v2_params_initial_values.growth_rate).toEqual(0.03); }); - it('should set strike into a wheel_picker_initial_values', () => { - expect(mockedTradeStore.wheel_picker_initial_values).toEqual({}); + it('should set strike into a v2_params_initial_values', () => { + expect(mockedTradeStore.v2_params_initial_values).toEqual({}); - mockedTradeStore.setWheelPickerInitialValues({ name: 'strike', value: '+1.30' }); + mockedTradeStore.setV2ParamsInitialValues({ name: 'strike', value: '+1.30' }); - expect(mockedTradeStore.wheel_picker_initial_values.strike).toEqual('+1.30'); + expect(mockedTradeStore.v2_params_initial_values.strike).toEqual('+1.30'); }); - it('should clear all values when clearWheelPickerInitialValues is called', () => { - mockedTradeStore.setWheelPickerInitialValues({ name: 'strike', value: '+1.00' }); - mockedTradeStore.setWheelPickerInitialValues({ name: 'growth_rate', value: 0.05 }); + it('should clear all values when clearV2ParamsInitialValues is called', () => { + mockedTradeStore.setV2ParamsInitialValues({ name: 'strike', value: '+1.00' }); + mockedTradeStore.setV2ParamsInitialValues({ name: 'growth_rate', value: 0.05 }); - expect(mockedTradeStore.wheel_picker_initial_values.strike).toEqual('+1.00'); - expect(mockedTradeStore.wheel_picker_initial_values.growth_rate).toEqual(0.05); + expect(mockedTradeStore.v2_params_initial_values.strike).toEqual('+1.00'); + expect(mockedTradeStore.v2_params_initial_values.growth_rate).toEqual(0.05); - mockedTradeStore.clearWheelPickerInitialValues(); + mockedTradeStore.clearV2ParamsInitialValues(); - expect(mockedTradeStore.wheel_picker_initial_values).toEqual({}); + expect(mockedTradeStore.v2_params_initial_values).toEqual({}); }); }); }); diff --git a/packages/trader/src/Stores/Modules/Trading/trade-store.ts b/packages/trader/src/Stores/Modules/Trading/trade-store.ts index 29c2df019bca..00d07da311f2 100644 --- a/packages/trader/src/Stores/Modules/Trading/trade-store.ts +++ b/packages/trader/src/Stores/Modules/Trading/trade-store.ts @@ -140,8 +140,9 @@ export type TChartStateChangeOption = { symbol_category?: string; time_interval_name?: string; }; -export type TWheelPickerInitialValues = { +export type TV2ParamsInitialValues = { growth_rate?: number; + stake?: string | number; strike?: string | number; multiplier?: number; }; @@ -259,7 +260,9 @@ export default class TradeStore extends BaseStore { * */ market_close_times: string[] = []; - validation_params?: TValidationParams | Record = {}; + validation_params: { + [key: string]: TValidationParams | Record; + } = {}; // Last Digit digit_stats: number[] = []; @@ -315,7 +318,7 @@ export default class TradeStore extends BaseStore { // Mobile is_trade_params_expanded = true; - wheel_picker_initial_values: TWheelPickerInitialValues = {}; + v2_params_initial_values: TV2ParamsInitialValues = {}; debouncedSendTradeParamsAnalytics = debounce((payload: TEvents['ce_contracts_set_up_form']) => { if (payload.action === 'change_parameter_value') { @@ -365,7 +368,7 @@ export default class TradeStore extends BaseStore { 'stop_loss', 'take_profit', 'is_trade_params_expanded', - 'wheel_picker_initial_values', + 'v2_params_initial_values', ]; const session_storage_properties = ['contract_type', 'symbol']; @@ -469,7 +472,7 @@ export default class TradeStore extends BaseStore { ticks_history_stats: observable, trade_type_tab: observable, trade_types: observable, - wheel_picker_initial_values: observable, + v2_params_initial_values: observable, accountSwitcherListener: action.bound, barrier_pipsize: computed, barriers_flattened: computed, @@ -479,7 +482,7 @@ export default class TradeStore extends BaseStore { clearLimitOrderBarriers: action.bound, clearPurchaseInfo: action.bound, clientInitListener: action.bound, - clearWheelPickerInitialValues: action.bound, + clearV2ParamsInitialValues: action.bound, enablePurchase: action.bound, exportLayout: action.bound, forgetAllProposal: action.bound, @@ -531,7 +534,7 @@ export default class TradeStore extends BaseStore { setStakeBoundary: action.bound, setTradeTypeTab: action.bound, setTradeStatus: action.bound, - setWheelPickerInitialValues: action.bound, + setV2ParamsInitialValues: action.bound, show_digits_stats: computed, updateStore: action.bound, updateSymbol: action.bound, @@ -644,12 +647,12 @@ export default class TradeStore extends BaseStore { } } - setWheelPickerInitialValues({ value, name }: { value: number | string; name: keyof TWheelPickerInitialValues }) { - this.wheel_picker_initial_values = { ...this.wheel_picker_initial_values, ...{ [name]: value } }; + setV2ParamsInitialValues({ value, name }: { value: number | string; name: keyof TV2ParamsInitialValues }) { + this.v2_params_initial_values = { ...this.v2_params_initial_values, ...{ [name]: value } }; } - clearWheelPickerInitialValues() { - this.wheel_picker_initial_values = {}; + clearV2ParamsInitialValues() { + this.v2_params_initial_values = {}; } setDefaultGrowthRate() { @@ -1451,6 +1454,7 @@ export default class TradeStore extends BaseStore { ...this.proposal_info, [contract_type]: getProposalInfo(this, response, obj_prev_contract_basis), }; + this.validation_params[contract_type] = this.proposal_info[contract_type].validation_params; if (this.is_multiplier && this.proposal_info && this.proposal_info.MULTUP) { const { commission, cancellation, limit_order } = this.proposal_info.MULTUP; @@ -1464,15 +1468,6 @@ export default class TradeStore extends BaseStore { this.stop_out = limit_order?.stop_out?.order_amount; } - if (this.is_turbos && (this.proposal_info?.TURBOSSHORT || this.proposal_info?.TURBOSLONG)) { - if (this.proposal_info?.TURBOSSHORT) { - this.validation_params = this.proposal_info.TURBOSSHORT.validation_params; - } - if (this.proposal_info?.TURBOSLONG) { - this.validation_params = this.proposal_info.TURBOSLONG.validation_params; - } - } - if (this.is_accumulator && this.proposal_info?.ACCU) { const { barrier_spot_distance, @@ -1484,14 +1479,12 @@ export default class TradeStore extends BaseStore { high_barrier, low_barrier, spot_time, - validation_params, } = this.proposal_info.ACCU; this.ticks_history_stats = getUpdatedTicksHistoryStats({ previous_ticks_history_stats: this.ticks_history_stats, new_ticks_history_stats: ticks_stayed_in, last_tick_epoch, }); - this.validation_params = validation_params; this.maximum_ticks = maximum_ticks; this.maximum_payout = maximum_payout; this.tick_size_barrier_percentage = tick_size_barrier_percentage; @@ -1756,7 +1749,7 @@ export default class TradeStore extends BaseStore { this.disposeNetworkStatusChange(); this.disposeThemeChange(); this.is_trade_component_mounted = false; - this.clearWheelPickerInitialValues(); + this.clearV2ParamsInitialValues(); // TODO: Find a more elegant solution to unmount contract-trade-store this.root_store.contract_trade.onUnmount(); this.refresh();