diff --git a/packages/analytics/src/index.ts b/packages/analytics/src/index.ts index c338fac2bec1..43de7ed9c23e 100644 --- a/packages/analytics/src/index.ts +++ b/packages/analytics/src/index.ts @@ -1 +1,2 @@ export { default as RudderStack } from './rudderstack'; +export type { TEvents } from './rudderstack'; diff --git a/packages/analytics/src/rudderstack.ts b/packages/analytics/src/rudderstack.ts index 153e919b2c3a..489db83520cb 100644 --- a/packages/analytics/src/rudderstack.ts +++ b/packages/analytics/src/rudderstack.ts @@ -133,7 +133,7 @@ type IdentifyAction = { language: string; }; -type TEvents = { +export type TEvents = { ce_chart_types_form: ChartTypesFormAction; ce_indicators_types_form: IndicatorsTypesFormAction; ce_market_types_form: MarketTypesFormAction; diff --git a/packages/cashier/src/containers/routes/error-component/__tests__/error-component.spec.tsx b/packages/cashier/src/containers/routes/error-component/__tests__/error-component.spec.tsx index 1e26400526ee..8805f7e2a32e 100644 --- a/packages/cashier/src/containers/routes/error-component/__tests__/error-component.spec.tsx +++ b/packages/cashier/src/containers/routes/error-component/__tests__/error-component.spec.tsx @@ -27,7 +27,6 @@ describe('', () => { redirect_label: 'test_label', should_clear_error_on_click: true, should_show_refresh: true, - app_routing_history: [{ pathname: '/cashier' }], redirectOnClick: jest.fn(), setError: jest.fn(), }; diff --git a/packages/cashier/src/containers/routes/error-component/error-component.tsx b/packages/cashier/src/containers/routes/error-component/error-component.tsx index c826db55784d..0b6355258712 100644 --- a/packages/cashier/src/containers/routes/error-component/error-component.tsx +++ b/packages/cashier/src/containers/routes/error-component/error-component.tsx @@ -2,8 +2,12 @@ import React from 'react'; import { useHistory } from 'react-router-dom'; import { PageErrorContainer } from '@deriv/components'; import { routes } from '@deriv/shared'; -import { TRootStore } from '../../../types'; import { localize, Localize } from '@deriv/translations'; +import { TCoreStores } from '@deriv/stores/types'; + +type TErrorComponentProps = TCoreStores['common']['error'] & { + setError?: (has_error: boolean, error: React.ReactNode | null) => void; +}; const ErrorComponent = ({ header, @@ -14,7 +18,7 @@ const ErrorComponent = ({ setError, should_clear_error_on_click, should_show_refresh = true, -}: TRootStore['common']['error']) => { +}: TErrorComponentProps) => { const history = useHistory(); React.useEffect(() => { diff --git a/packages/components/src/components/collapsible/arrow-button.tsx b/packages/components/src/components/collapsible/arrow-button.tsx index 42654e49280d..ad616e8cacf0 100644 --- a/packages/components/src/components/collapsible/arrow-button.tsx +++ b/packages/components/src/components/collapsible/arrow-button.tsx @@ -13,7 +13,7 @@ type TArrowButton = { is_open?: boolean; onClick: () => void; title?: string; - position: 'top' | 'bottom'; + position?: 'top' | 'bottom'; }; const IconArrow = ({ className }: { className?: string }) => ( diff --git a/packages/components/src/components/contract-card/contract-card-items/accumulator-card-body.tsx b/packages/components/src/components/contract-card/contract-card-items/accumulator-card-body.tsx index 7c0153eee830..54aa0019758a 100644 --- a/packages/components/src/components/contract-card/contract-card-items/accumulator-card-body.tsx +++ b/packages/components/src/components/contract-card/contract-card-items/accumulator-card-body.tsx @@ -24,7 +24,7 @@ type TAccumulatorCardBody = { getContractById: React.ComponentProps['getContractById']; indicative?: number; is_sold: boolean; - onMouseLeave: () => void; + onMouseLeave?: () => void; removeToast: (toast_id: string) => void; setCurrentFocus: (value: string) => void; status?: string; diff --git a/packages/components/src/components/contract-card/contract-card-items/contract-card-body.tsx b/packages/components/src/components/contract-card/contract-card-items/contract-card-body.tsx index 2f72de4633be..f9fe7c825133 100644 --- a/packages/components/src/components/contract-card/contract-card-items/contract-card-body.tsx +++ b/packages/components/src/components/contract-card/contract-card-items/contract-card-body.tsx @@ -31,11 +31,11 @@ export type TGeneralContractCardBodyProps = { has_progress_slider: boolean; is_mobile: boolean; is_sold: boolean; - onMouseLeave: () => void; + onMouseLeave?: () => void; removeToast: (toast_id: string) => void; setCurrentFocus: (name: string) => void; status?: string; - toggleCancellationWarning: () => void; + toggleCancellationWarning: (state_change?: boolean) => void; progress_slider?: React.ReactNode; is_positions?: boolean; }; diff --git a/packages/components/src/components/contract-card/contract-card-items/contract-card-footer.tsx b/packages/components/src/components/contract-card/contract-card-items/contract-card-footer.tsx index d599eb7b1081..db1988ca776d 100644 --- a/packages/components/src/components/contract-card/contract-card-items/contract-card-footer.tsx +++ b/packages/components/src/components/contract-card/contract-card-items/contract-card-footer.tsx @@ -11,13 +11,13 @@ export type TCardFooterPropTypes = { contract_info: TContractInfo; getCardLabels: TGetCardLables; is_multiplier?: boolean; - is_positions: boolean; + is_positions?: boolean; is_sell_requested: boolean; onClickCancel: (contract_id?: number) => void; onClickSell: (contract_id?: number) => void; onFooterEntered?: () => void; server_time: moment.Moment; - should_show_transition: boolean; + should_show_transition?: boolean; }; const CardFooter = ({ @@ -32,7 +32,7 @@ const CardFooter = ({ server_time, should_show_transition, }: TCardFooterPropTypes) => { - const { in_prop } = useNewRowTransition(should_show_transition); + const { in_prop } = useNewRowTransition(!!should_show_transition); const is_valid_to_cancel = isValidToCancel(contract_info); diff --git a/packages/components/src/components/contract-card/contract-card-items/contract-card-header.tsx b/packages/components/src/components/contract-card/contract-card-items/contract-card-header.tsx index 4079df1bb026..1ce4c8a311d1 100644 --- a/packages/components/src/components/contract-card/contract-card-items/contract-card-header.tsx +++ b/packages/components/src/components/contract-card/contract-card-items/contract-card-header.tsx @@ -1,17 +1,16 @@ import React from 'react'; import classNames from 'classnames'; import { CSSTransition } from 'react-transition-group'; -import { localize } from '@deriv/translations'; import { isHighLow, getCurrentTick, getGrowthRatePercentage, - getContractSubtype, isAccumulatorContract, isSmartTraderContract, isBot, isMobile, isTurbosContract, + getLocalizedTurbosSubtype, } from '@deriv/shared'; import ContractTypeCell from './contract-type-cell'; import Button from '../../button'; @@ -83,8 +82,7 @@ const ContractCardHeader = ({ }, { is_param_displayed: is_turbos, - displayed_param: - getContractSubtype(contract_type || '') === 'Long' ? localize('Long') : localize('Short'), + displayed_param: getLocalizedTurbosSubtype(contract_type), }, ], [multiplier, growth_rate, is_accumulator, is_turbos, contract_type] diff --git a/packages/components/src/components/contract-card/contract-card-items/contract-type-cell.tsx b/packages/components/src/components/contract-card/contract-card-items/contract-type-cell.tsx index ef5d594ac828..41629909cfd9 100644 --- a/packages/components/src/components/contract-card/contract-card-items/contract-type-cell.tsx +++ b/packages/components/src/components/contract-card/contract-card-items/contract-type-cell.tsx @@ -9,7 +9,7 @@ export type TContractTypeCellProps = { is_high_low: boolean; multiplier?: number; type?: string; - displayed_trade_param?: string; + displayed_trade_param?: React.ReactNode; }; const ContractTypeCell = ({ diff --git a/packages/components/src/components/contract-card/contract-card-items/contract-update-form.tsx b/packages/components/src/components/contract-card/contract-card-items/contract-update-form.tsx index 1e116c67e966..a337de1179df 100644 --- a/packages/components/src/components/contract-card/contract-card-items/contract-update-form.tsx +++ b/packages/components/src/components/contract-card/contract-card-items/contract-update-form.tsx @@ -33,7 +33,7 @@ export type TContractUpdateFormProps = Pick< contract: TContractStore; error_message_alignment?: string; getCardLabels: TGetCardLables; - onMouseLeave: () => void; + onMouseLeave?: () => void; removeToast: (toast_id: string) => void; setCurrentFocus: (name: string | null) => void; status: string; diff --git a/packages/components/src/components/contract-card/contract-card.tsx b/packages/components/src/components/contract-card/contract-card.tsx index 77a54f5f9ecb..94993077b32e 100644 --- a/packages/components/src/components/contract-card/contract-card.tsx +++ b/packages/components/src/components/contract-card/contract-card.tsx @@ -14,15 +14,15 @@ import { TGetCardLables, TGetContractPath } from '../types'; type TContractCardProps = { contract_info: TContractInfo; getCardLabels: TGetCardLables; - getContractPath: TGetContractPath; + getContractPath?: TGetContractPath; is_multiplier: boolean; - is_positions: boolean; - is_unsupported: boolean; - onClickRemove: (contract_id: number) => void; + is_positions?: boolean; + is_unsupported?: boolean; + onClickRemove?: (contract_id?: number) => void; profit_loss: number; - result: string; + result?: string; should_show_result_overlay: boolean; - toggleUnsupportedContractModal: (is_unsupported_contract_modal_visible: boolean) => void; + toggleUnsupportedContractModal?: (is_unsupported_contract_modal_visible: boolean) => void; }; const ContractCard = ({ @@ -53,7 +53,7 @@ const ContractCard = ({ is_multiplier={is_multiplier} is_visible={!!contract_info.is_sold} onClickRemove={onClickRemove} - onClick={() => toggleUnsupportedContractModal(true)} + onClick={() => toggleUnsupportedContractModal?.(true)} result={result || fallback_result} is_positions={is_positions} /> diff --git a/packages/components/src/components/contract-card/result-overlay/result-overlay.tsx b/packages/components/src/components/contract-card/result-overlay/result-overlay.tsx index 25be6f8cf87a..5186ce2b47e9 100644 --- a/packages/components/src/components/contract-card/result-overlay/result-overlay.tsx +++ b/packages/components/src/components/contract-card/result-overlay/result-overlay.tsx @@ -8,13 +8,13 @@ import { TGetCardLables, TGetContractPath } from '../../types'; type TResultOverlayProps = { contract_id?: number; getCardLabels: TGetCardLables; - getContractPath: TGetContractPath; + getContractPath?: TGetContractPath; is_multiplier?: boolean; - is_positions: boolean; - is_unsupported: boolean; + is_positions?: boolean; + is_unsupported?: boolean; is_visible: boolean; onClick: () => void; - onClickRemove: (contract_id: number) => void; + onClickRemove?: (contract_id?: number) => void; result: string; }; @@ -88,7 +88,7 @@ const ResultOverlay = ({ onClickRemove(Number(contract_id))} + onClick={() => onClickRemove?.(contract_id)} /> )} {getContractPath && ( diff --git a/packages/components/src/components/div100vh-container/div100vh-container.tsx b/packages/components/src/components/div100vh-container/div100vh-container.tsx index 6bcf1a60361a..619466ffa4ca 100644 --- a/packages/components/src/components/div100vh-container/div100vh-container.tsx +++ b/packages/components/src/components/div100vh-container/div100vh-container.tsx @@ -20,7 +20,7 @@ type TDiv100vhContainer = { is_bypassed?: boolean; is_disabled?: boolean; max_height_offset?: string; - className: string; + className?: string; max_autoheight_offset?: string; }; diff --git a/packages/components/src/components/input-field/input-field.tsx b/packages/components/src/components/input-field/input-field.tsx index 67859d60be00..4a9e5f2f91ea 100644 --- a/packages/components/src/components/input-field/input-field.tsx +++ b/packages/components/src/components/input-field/input-field.tsx @@ -14,7 +14,7 @@ export type TButtonType = 'button' | 'submit' | 'reset'; // supports more than two different types of 'value' as a prop. // Quick Solution - Pass two different props to input field. type TInputField = { - ariaLabel: string; + ariaLabel?: string; checked?: boolean; className?: string; classNameDynamicSuffix?: string; @@ -22,8 +22,8 @@ type TInputField = { classNameInput?: string; classNamePrefix?: string; classNameWrapper?: string; // CSS class for the component wrapper - currency: string; - current_focus?: string | null; + currency?: string; + current_focus: string | null; data_testid?: string; data_tip?: string; data_value?: string; @@ -31,26 +31,26 @@ type TInputField = { error_message_alignment?: string; error_messages?: string[]; format?: (new_value?: string) => string; - fractional_digits: number; + fractional_digits?: number; helper?: string; icon?: React.ElementType; id?: string; increment_button_type?: TButtonType; inline_prefix?: string; inputmode?: TInputMode; - is_autocomplete_disabled: boolean; + is_autocomplete_disabled?: boolean; is_disabled?: boolean; - is_error_tooltip_hidden: boolean; + is_error_tooltip_hidden?: boolean; is_float: boolean; - is_hj_whitelisted: boolean; + is_hj_whitelisted?: boolean; is_incrementable_on_long_press?: boolean; - is_incrementable: boolean; - is_negative_disabled: boolean; + is_incrementable?: boolean; + is_negative_disabled?: boolean; is_read_only?: boolean; is_signed?: boolean; is_unit_at_right?: boolean; label?: string; - max_length: number; + max_length?: number; max_value?: number; min_value?: number; name: string; diff --git a/packages/components/src/components/input-field/input.tsx b/packages/components/src/components/input-field/input.tsx index 3547ddef0aa9..8c5df9dd49e3 100644 --- a/packages/components/src/components/input-field/input.tsx +++ b/packages/components/src/components/input-field/input.tsx @@ -4,7 +4,7 @@ import { getCurrencyDisplayCode } from '@deriv/shared'; import { TInputMode } from './input-field'; type TInputProps = { - ariaLabel: string; + ariaLabel?: string; changeValue: ( e: React.ChangeEvent, callback?: (evt: React.ChangeEvent) => void @@ -13,22 +13,22 @@ type TInputProps = { className?: string; classNameDynamicSuffix?: string; classNameInlinePrefix?: string; - current_focus?: string | null; + current_focus: string | null; data_testid?: string; data_tip?: string; data_value?: number | string; display_value: number | string; - fractional_digits: number; + fractional_digits?: number; has_error?: boolean; id?: string; inline_prefix?: string; inputmode?: TInputMode; - is_autocomplete_disabled: boolean; + is_autocomplete_disabled?: boolean; is_disabled?: boolean; is_hj_whitelisted: boolean; - is_incrementable: boolean; + is_incrementable?: boolean; is_read_only: boolean; - max_length: number; + max_length?: number; name: string; onBlur?: React.FocusEventHandler; onClick?: React.MouseEventHandler; @@ -128,7 +128,7 @@ const Input = ({ data-value={data_value} disabled={!!is_disabled} id={id} - maxLength={fractional_digits ? max_length + fractional_digits + 1 : max_length} + maxLength={fractional_digits && max_length ? max_length + fractional_digits + 1 : max_length} name={name} onBlur={onBlurHandler} onChange={onChange} diff --git a/packages/components/src/components/money/money.tsx b/packages/components/src/components/money/money.tsx index d9556a181846..954ec009856c 100644 --- a/packages/components/src/components/money/money.tsx +++ b/packages/components/src/components/money/money.tsx @@ -19,8 +19,8 @@ const Money = ({ show_currency = false, }: Partial) => { let sign = ''; - if (+amount && (amount < 0 || has_sign)) { - sign = amount > 0 ? '+' : '-'; + if (Number(amount) && (Number(amount) < 0 || has_sign)) { + sign = Number(amount) > 0 ? '+' : '-'; } // if it's formatted already then don't make any changes unless we should remove extra -/+ signs diff --git a/packages/components/src/components/numpad/numpad.tsx b/packages/components/src/components/numpad/numpad.tsx index 662f840aaa33..e7f6c2e4dfd5 100644 --- a/packages/components/src/components/numpad/numpad.tsx +++ b/packages/components/src/components/numpad/numpad.tsx @@ -12,20 +12,20 @@ type TNumpad = { is_regular?: boolean; is_currency?: boolean; is_submit_disabled?: boolean; - label: string; + label?: string; reset_press_interval: number; reset_value: string; - max: number; - min: number; + max?: number; + min?: number; pip_size: number; onSubmit: (param: number | string) => void; - v: string; + v?: string; render?: (props: { value: string; className: string }) => React.ReactNode; submit_label: string; - value: string; - format: (v: string) => number; + value: string | number; + format?: (v: string) => number | string; onValueChange: (val: number | string) => void; - onValidate: (default_value: number | string) => string | undefined; + onValidate: (default_value: number | string) => boolean | 'error'; }; const concatenate = (number: string | number, default_value: string | number) => @@ -71,7 +71,7 @@ const Numpad = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [default_value, value]); - const updateValue = (val: string) => { + const updateValue = (val: string | number) => { setValue(val); if (onValueChange) onValueChange(val); }; diff --git a/packages/components/src/components/positions-drawer-card/positions-drawer-card.tsx b/packages/components/src/components/positions-drawer-card/positions-drawer-card.tsx index fd9ed4667948..089a3e6123cc 100644 --- a/packages/components/src/components/positions-drawer-card/positions-drawer-card.tsx +++ b/packages/components/src/components/positions-drawer-card/positions-drawer-card.tsx @@ -33,7 +33,7 @@ type TPositionsDrawerCardProps = { profit_loss?: number; onClickCancel: (contract_id?: number) => void; onClickSell: (contract_id?: number) => void; - onClickRemove: (contract_id: number) => void; + onClickRemove: (contract_id?: number) => void; onFooterEntered?: () => void; onMouseEnter?: () => void; onMouseLeave?: () => void; diff --git a/packages/components/src/components/tabs/tab.tsx b/packages/components/src/components/tabs/tab.tsx index eba22fac25bc..5c26046094d6 100644 --- a/packages/components/src/components/tabs/tab.tsx +++ b/packages/components/src/components/tabs/tab.tsx @@ -4,15 +4,15 @@ import Counter from '../counter'; import Icon from '../icon'; type TTabProps = { - active_icon_color: string; + active_icon_color?: string; active_tab_ref?: React.RefObject | null; bottom?: boolean; className?: string; count: number; header_content: React.ReactElement; header_fit_content?: boolean; - icon_color: string; - icon_size: number; + icon_color?: string; + icon_size?: number; icon: string; id?: string; is_active: boolean; @@ -25,17 +25,17 @@ type TTabProps = { }; const Tab = ({ - active_icon_color, + active_icon_color = '', active_tab_ref, - bottom, - className, + bottom = false, + className = '', count, header_content, - header_fit_content, - icon_color, - icon_size, + header_fit_content = false, + icon_color = '', + icon_size = 0, icon, - id, + id = '', is_active, is_label_hidden, is_scrollable, diff --git a/packages/components/src/components/tabs/tabs.tsx b/packages/components/src/components/tabs/tabs.tsx index c7d8f5483e77..99817c188937 100644 --- a/packages/components/src/components/tabs/tabs.tsx +++ b/packages/components/src/components/tabs/tabs.tsx @@ -14,20 +14,20 @@ declare module 'react' { } type TTabsProps = RouteComponentProps & { - active_icon_color: string; + active_icon_color?: string; active_index?: number; - background_color: string; + background_color?: string; bottom?: boolean; - center: boolean; - children: React.ReactElement[]; + center?: boolean; + children: (React.ReactElement | null)[]; className?: string; - fit_content: boolean; + fit_content?: boolean; has_active_line?: boolean; has_bottom_line?: boolean; header_fit_content?: boolean; history: History; - icon_color: string; - icon_size: number; + icon_color?: string; + icon_size?: number; is_100vw?: boolean; is_full_width?: boolean; is_overflow_hidden?: boolean; @@ -39,27 +39,27 @@ type TTabsProps = RouteComponentProps & { }; const Tabs = ({ - active_icon_color, + active_icon_color = '', active_index = 0, - background_color, - bottom, - center, + background_color = '', + bottom = false, + center = false, children, - className, - fit_content, + className = '', + fit_content = false, has_active_line = true, has_bottom_line = true, - header_fit_content, + header_fit_content = false, history, - icon_color, - icon_size, - is_100vw, - is_full_width, - is_overflow_hidden, - is_scrollable, + icon_color = '', + icon_size = 0, + is_100vw = false, + is_full_width = false, + is_overflow_hidden = false, + is_scrollable = false, onTabItemClick, - should_update_hash, - single_tab_has_no_label, + should_update_hash = false, + single_tab_has_no_label = false, top, }: TTabsProps) => { const [active_line_style, updateActiveLineStyle] = React.useState({}); @@ -99,7 +99,7 @@ const Tabs = ({ initial_index_to_show = hash_index; } else { // if no hash is in url but component has passed hash prop, set hash of the tab shown - const child_props = children[initial_index_to_show].props; + const child_props = children[initial_index_to_show]?.props; const current_id = child_props && child_props.hash; if (current_id) { pushHash(current_id); @@ -128,7 +128,7 @@ const Tabs = ({ const onClickTabItem = (index: number) => { if (should_update_hash) { - const hash = children[index].props['data-hash']; + const hash = children[index]?.props['data-hash']; pushHash(hash); } setActiveTabIndex(index); diff --git a/packages/components/src/components/tooltip/tooltip.tsx b/packages/components/src/components/tooltip/tooltip.tsx index 5f5cbac0c599..8d1203ff18a6 100644 --- a/packages/components/src/components/tooltip/tooltip.tsx +++ b/packages/components/src/components/tooltip/tooltip.tsx @@ -21,7 +21,7 @@ const Tooltip = ({ icon, // only question, info and dot accepted message, }: React.PropsWithChildren) => { - const [hover_ref, show_tooltip_balloon_icon_on_hover] = useHover(); + const [hover_ref, show_tooltip_balloon_icon_on_hover] = useHover(); const icon_class = classNames(classNameIcon, icon); diff --git a/packages/components/src/hooks/use-hover.ts b/packages/components/src/hooks/use-hover.ts index e28bc981f48a..c56fa0c4e94b 100644 --- a/packages/components/src/hooks/use-hover.ts +++ b/packages/components/src/hooks/use-hover.ts @@ -1,7 +1,7 @@ import React, { RefObject } from 'react'; import { isMobileOs } from '@deriv/shared'; -export const useHover = ( +export const useHover = ( refSetter?: RefObject | null, should_prevent_bubbling?: boolean ) => { diff --git a/packages/reports/src/Containers/open-positions.tsx b/packages/reports/src/Containers/open-positions.tsx index e5bf812a8a8d..c367cf972c24 100644 --- a/packages/reports/src/Containers/open-positions.tsx +++ b/packages/reports/src/Containers/open-positions.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { withRouter } from 'react-router-dom'; +import { RouteComponentProps, withRouter } from 'react-router-dom'; import { DesktopWrapper, MobileWrapper, @@ -114,7 +114,7 @@ type TTotals = { payout?: number; }; -type TOpenPositions = { +type TOpenPositions = RouteComponentProps & { component_icon: string; }; diff --git a/packages/shared/package.json b/packages/shared/package.json index 45a8fcd869c3..2821fef759c9 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -32,6 +32,10 @@ "devDependencies": { "@babel/eslint-parser": "^7.17.0", "@babel/preset-react": "^7.16.7", + "@testing-library/jest-dom": "^5.12.0", + "@testing-library/react": "^12.0.0", + "@testing-library/react-hooks": "^7.0.2", + "@testing-library/user-event": "^13.5.0", "@types/jsdom": "^20.0.0", "@types/react": "^18.0.7", "@types/react-dom": "^18.0.0", diff --git a/packages/shared/src/utils/contract/__tests__/contract.spec.ts b/packages/shared/src/utils/contract/__tests__/contract.spec.ts index 50c20e064ba1..c98bec1ab498 100644 --- a/packages/shared/src/utils/contract/__tests__/contract.spec.ts +++ b/packages/shared/src/utils/contract/__tests__/contract.spec.ts @@ -1,3 +1,4 @@ +import { screen, render } from '@testing-library/react'; import * as ContractUtils from '../contract'; import { TContractInfo, TDigitsInfo, TTickItem } from '../contract-types'; @@ -585,3 +586,20 @@ describe('getContractStatus', () => { ).toBe('won'); }); }); + +describe('getLocalizedTurbosSubtype', () => { + it('should return an empty string for non-turbos contracts', () => { + render(ContractUtils.getLocalizedTurbosSubtype('CALL') as JSX.Element); + expect(screen.queryByText('Long')).not.toBeInTheDocument(); + expect(screen.queryByText('Short')).not.toBeInTheDocument(); + expect(ContractUtils.getLocalizedTurbosSubtype('CALL')).toBe(''); + }); + it('should render "Long" for TURBOSLONG contract', () => { + render(ContractUtils.getLocalizedTurbosSubtype('TURBOSLONG') as JSX.Element); + expect(screen.getByText('Long')).toBeInTheDocument(); + }); + it('should render "Short" for TURBOSSHORT contract', () => { + render(ContractUtils.getLocalizedTurbosSubtype('TURBOSSHORT') as JSX.Element); + expect(screen.getByText('Short')).toBeInTheDocument(); + }); +}); diff --git a/packages/shared/src/utils/contract/contract-types.ts b/packages/shared/src/utils/contract/contract-types.ts index 0442d8ffeeb6..0e5be1ce6746 100644 --- a/packages/shared/src/utils/contract/contract-types.ts +++ b/packages/shared/src/utils/contract/contract-types.ts @@ -1,15 +1,26 @@ -import { ContractUpdate, Portfolio1, ProposalOpenContract } from '@deriv/api-types'; +import { + ContractUpdate, + ContractUpdateHistory, + Portfolio1, + ProposalOpenContract, + TickSpotData, +} from '@deriv/api-types'; export type TContractStore = { + clearContractUpdateConfigValues: () => void; contract_info: TContractInfo; + contract_update_history: ContractUpdateHistory; contract_update_take_profit: number | string; contract_update_stop_loss: number | string; - clearContractUpdateConfigValues: () => void; + digits_info: TDigitsInfo; + display_status: string; has_contract_update_take_profit: boolean; has_contract_update_stop_loss: boolean; + is_digit_contract: boolean; + is_ended: boolean; + onChange: (param: { name: string; value: string | number | boolean }) => void; updateLimitOrder: () => void; validation_errors: { contract_update_stop_loss: string[]; contract_update_take_profit: string[] }; - onChange: (param: { name: string; value: string | number | boolean }) => void; }; export type TContractInfo = ProposalOpenContract & @@ -33,3 +44,5 @@ type TLimitProperty = { }; export type TLimitOrder = Partial>; + +export type TTickSpotData = TickSpotData; diff --git a/packages/shared/src/utils/contract/contract.ts b/packages/shared/src/utils/contract/contract.tsx similarity index 95% rename from packages/shared/src/utils/contract/contract.ts rename to packages/shared/src/utils/contract/contract.tsx index 006e872ef84a..4f9ad4bb5f4a 100644 --- a/packages/shared/src/utils/contract/contract.ts +++ b/packages/shared/src/utils/contract/contract.tsx @@ -1,4 +1,6 @@ import moment from 'moment'; +import React from 'react'; +import { Localize } from '@deriv/translations'; import { unique } from '../object'; import { capitalizeFirstLetter } from '../string/string_util'; import { TContractInfo, TDigitsInfo, TLimitOrder, TTickItem } from './contract-types'; @@ -214,7 +216,16 @@ export const shouldShowExpiration = (symbol = '') => symbol.startsWith('cry'); export const shouldShowCancellation = (symbol = '') => !/^(cry|CRASH|BOOM|stpRNG|WLD|JD)/.test(symbol); -export const getContractSubtype = (type: string) => +export const getContractSubtype = (type = '') => /(VANILLALONG|TURBOS)/i.test(type) ? capitalizeFirstLetter(type.replace(/(VANILLALONG|TURBOS)/i, '').toLowerCase()) : ''; + +export const getLocalizedTurbosSubtype = (contract_type = '') => { + if (!isTurbosContract(contract_type)) return ''; + return getContractSubtype(contract_type) === 'Long' ? ( + + ) : ( + + ); +}; diff --git a/packages/shared/src/utils/digital-options/digital-options.ts b/packages/shared/src/utils/digital-options/digital-options.ts index 5ee382320bf5..b619b0af2c35 100644 --- a/packages/shared/src/utils/digital-options/digital-options.ts +++ b/packages/shared/src/utils/digital-options/digital-options.ts @@ -10,7 +10,7 @@ type TShowError = { message: string; header: string; redirect_label: string; - redirectOnClick: (() => void) | null; + redirectOnClick?: (() => void) | null; should_show_refresh: boolean; redirect_to: string; should_clear_error_on_click: boolean; @@ -25,8 +25,8 @@ type TAccounts = { export const showDigitalOptionsUnavailableError = ( showError: (t: TShowError) => void, message: TMessage, - redirectOnClick: (() => void) | null, - should_redirect: boolean, + redirectOnClick?: (() => void) | null, + should_redirect = false, should_clear_error_on_click = true ) => { const { title, text, link } = message; diff --git a/packages/shared/src/utils/helpers/duration.ts b/packages/shared/src/utils/helpers/duration.ts index 84d47c169c9a..be2579641b3f 100644 --- a/packages/shared/src/utils/helpers/duration.ts +++ b/packages/shared/src/utils/helpers/duration.ts @@ -163,10 +163,10 @@ export const hasIntradayDurationUnit = (duration_units_list: TUnit[]) => { * On switching symbols, end_time value of volatility indices should be set to today * * @param {String} symbol - * @param {String} expiry_type + * @param {String | null} expiry_type * @returns {*} */ -export const resetEndTimeOnVolatilityIndices = (symbol: string, expiry_type: string) => +export const resetEndTimeOnVolatilityIndices = (symbol: string, expiry_type: string | null) => /^R_/.test(symbol) && expiry_type === 'endtime' ? toMoment(null).format('DD MMM YYYY') : null; export const getDurationMinMaxValues = ( diff --git a/packages/shared/src/utils/helpers/logic.ts b/packages/shared/src/utils/helpers/logic.ts index 691cef6f72b8..435d9c5a6a34 100644 --- a/packages/shared/src/utils/helpers/logic.ts +++ b/packages/shared/src/utils/helpers/logic.ts @@ -2,55 +2,25 @@ import moment from 'moment'; import { isEmptyObject } from '../object'; import { isAccumulatorContract, isOpen, isUserSold } from '../contract'; import { TContractInfo, TContractStore } from '../contract/contract-types'; - -type TTick = { - ask?: number; - bid?: number; - epoch?: number; - id?: string; - pip_size: number; - quote?: number; - symbol?: string; -}; - -type TIsEndedBeforeCancellationExpired = TGetEndTime & { - cancellation: { - ask_price: number; - date_expiry: number; - }; -}; +import { TickSpotData } from '@deriv/api-types'; type TIsSoldBeforeStart = Required>; type TIsStarted = Required>; -type TGetEndTime = Pick< - TContractInfo, - | 'is_expired' - | 'sell_time' - | 'status' - | 'tick_count' - | 'bid_price' - | 'buy_price' - | 'contract_id' - | 'is_valid_to_sell' - | 'profit' -> & - Required>; - -export const isContractElapsed = (contract_info: TGetEndTime, tick: TTick) => { +export const isContractElapsed = (contract_info: TContractInfo, tick?: TickSpotData) => { if (isEmptyObject(tick) || isEmptyObject(contract_info)) return false; const end_time = getEndTime(contract_info) || 0; - if (end_time && tick.epoch) { + if (end_time && tick && tick.epoch) { const seconds = moment.duration(moment.unix(tick.epoch).diff(moment.unix(end_time))).asSeconds(); return seconds >= 2; } return false; }; -export const isEndedBeforeCancellationExpired = (contract_info: TIsEndedBeforeCancellationExpired) => { +export const isEndedBeforeCancellationExpired = (contract_info: TContractInfo) => { const end_time = getEndTime(contract_info) || 0; - return !!(contract_info.cancellation && end_time < contract_info.cancellation.date_expiry); + return !!(contract_info.cancellation?.date_expiry && end_time < contract_info.cancellation.date_expiry); }; export const isSoldBeforeStart = (contract_info: TIsSoldBeforeStart) => diff --git a/packages/shared/src/utils/shortcode/shortcode.ts b/packages/shared/src/utils/shortcode/shortcode.ts index 6517450d2bd0..954a1da76487 100644 --- a/packages/shared/src/utils/shortcode/shortcode.ts +++ b/packages/shared/src/utils/shortcode/shortcode.ts @@ -9,19 +9,17 @@ type TIsHighLow = { }; }; -type TInfoFromShortcode = Partial< - Record< - | 'category' - | 'underlying' - | 'barrier_1' - | 'multiplier' - | 'start_time' - | 'payout_tick' - | 'growth_rate' - | 'growth_frequency' - | 'tick_size_barrier', - string - > +type TInfoFromShortcode = Record< + | 'category' + | 'underlying' + | 'barrier_1' + | 'multiplier' + | 'start_time' + | 'payout_tick' + | 'growth_rate' + | 'growth_frequency' + | 'tick_size_barrier', + string >; // category_underlying_amount diff --git a/packages/shared/src/utils/validation/declarative-validation-rules.ts b/packages/shared/src/utils/validation/declarative-validation-rules.ts index 96fc564926d6..29578f216009 100644 --- a/packages/shared/src/utils/validation/declarative-validation-rules.ts +++ b/packages/shared/src/utils/validation/declarative-validation-rules.ts @@ -47,7 +47,7 @@ export const validPhone = (value: string) => /^\+?([0-9-]+\s)*[0-9-]+$/.test(val export const validLetterSymbol = (value: string) => /^[A-Za-z]+([a-zA-Z.' -])*[a-zA-Z.' -]+$/.test(value); export const validName = (value: string) => /^(?!.*\s{2,})[\p{L}\s'.-]{2,50}$/u.test(value); export const validLength = (value = '', options: TOptions) => - (options.min ? value.length >= options.min : true) && (options.max ? value.length <= options.max : true); + (options.min ? value.length >= options.min : true) && (options.max ? value.length <= Number(options.max) : true); export const validPassword = (value: string) => /^(?=.*[a-z])(?=.*\d)(?=.*[A-Z])[!-~]{8,25}$/.test(value); export const validEmail = (value: string) => /^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,63}$/.test(value); const validBarrier = (value: string) => /^[+-]?\d+\.?\d*$/.test(value); @@ -60,7 +60,7 @@ const validEmailToken = (value: string) => value.trim().length === 8; let pre_build_dvrs: TInitPreBuildDVRs, form_error_messages: TFormErrorMessagesTypes; const isMoreThanMax = (value: number, options: TOptions) => - options.type === 'float' ? +value > +options.max! : compareBigUnsignedInt(value, options.max!) === 1; + options.type === 'float' ? +value > Number(options.max) : compareBigUnsignedInt(value, options.max) === 1; export const validNumber = (value: string, opts: TOptions) => { const options = cloneObject(opts); diff --git a/packages/stores/src/mockStore.ts b/packages/stores/src/mockStore.ts index c3eb4c0d2416..be3884422813 100644 --- a/packages/stores/src/mockStore.ts +++ b/packages/stores/src/mockStore.ts @@ -1,5 +1,5 @@ import merge from 'lodash.merge'; -import type { TStores } from '../types'; +import type { TCoreStores, TStores } from '../types'; const mock = (): TStores & { is_mock: boolean } => { const common_store_error = { @@ -12,7 +12,6 @@ const mock = (): TStores & { is_mock: boolean } => { should_clear_error_on_click: false, should_show_refresh: false, redirectOnClick: jest.fn(), - setError: jest.fn(), }; return { is_mock: true, @@ -108,6 +107,7 @@ const mock = (): TStores & { is_mock: boolean } => { }, balance: '', can_change_fiat_currency: false, + clients_country: '', country_standpoint: { is_belgium: false, is_france: false, @@ -149,11 +149,10 @@ const mock = (): TStores & { is_mock: boolean } => { is_switching: false, is_tnc_needed: false, is_trading_experience_incomplete: false, + is_unwelcome: false, is_virtual: false, is_withdrawal_lock: false, is_populating_account_list: false, - is_language_loaded: false, - prev_account_type: '', landing_company_shortcode: '', local_currency_config: { currency: '', @@ -199,6 +198,7 @@ const mock = (): TStores & { is_mock: boolean } => { active_accounts: [], account_list: [], available_crypto_currencies: [], + selectCurrency: jest.fn(), setAccountStatus: jest.fn(), setBalanceOtherAccounts: jest.fn(), setInitialized: jest.fn(), @@ -272,6 +272,7 @@ const mock = (): TStores & { is_mock: boolean } => { current_language: 'EN', isCurrentLanguage: jest.fn(), is_from_derivgo: false, + is_socket_opened: false, has_error: false, platform: '', routeBackInApp: jest.fn(), @@ -279,14 +280,17 @@ const mock = (): TStores & { is_mock: boolean } => { changeCurrentLanguage: jest.fn(), changeSelectedLanguage: jest.fn(), is_network_online: false, + network_status: {}, services_error: {}, - server_time: undefined, + server_time: new Date() as unknown as TCoreStores['common']['server_time'], + setError: jest.fn(), + setSelectedContractType: jest.fn(), + setServicesError: jest.fn(), + showError: jest.fn(), is_language_changing: false, - is_socket_opened: false, setAppstorePlatform: jest.fn(), app_routing_history: [], getExchangeRate: jest.fn(), - network_status: {}, }, ui: { advanced_duration_unit: 't', @@ -296,9 +300,10 @@ const mock = (): TStores & { is_mock: boolean } => { }, current_focus: null, is_account_settings_visible: false, + is_advanced_duration: false, is_loading: false, is_cashier_visible: false, - is_app_disabled: false, + is_chart_layout_default: false, is_closing_create_real_account_modal: false, is_dark_mode_on: false, is_language_settings_modal_on: false, @@ -317,26 +322,29 @@ const mock = (): TStores & { is_mock: boolean } => { toggleAccountsDialog: jest.fn(), toggleAccountSettings: jest.fn(), toggleCashier: jest.fn(), - togglePositionsDrawer: jest.fn(), setDarkMode: jest.fn(), setReportsTabIndex: jest.fn(), has_only_forward_starting_contracts: false, has_real_account_signup_ended: false, notification_messages_ui: jest.fn(), + openPositionsDrawer: jest.fn(), openRealAccountSignup: jest.fn(), setHasOnlyForwardingContracts: jest.fn(), setIsClosingCreateRealAccountModal: jest.fn(), setMobileLanguageMenuOpen: jest.fn(), setRealAccountSignupEnd: jest.fn(), setPurchaseState: jest.fn(), + setAppContentsScrollRef: jest.fn(), shouldNavigateAfterChooseCrypto: jest.fn(), toggleLanguageSettingsModal: jest.fn(), - toggleServicesErrorModal: jest.fn(), + togglePositionsDrawer: jest.fn(), toggleLinkExpiredModal: jest.fn(), + toggleServicesErrorModal: jest.fn(), toggleSetCurrencyModal: jest.fn(), addToast: jest.fn(), removeToast: jest.fn(), reports_route_tab_index: 1, + resetPurchaseStates: jest.fn(), should_show_cancellation_warning: false, toggleCancellationWarning: jest.fn(), toggleUnsupportedContractModal: jest.fn(), @@ -348,9 +356,7 @@ const mock = (): TStores & { is_mock: boolean } => { is_ready_to_deposit_modal_visible: false, is_real_acc_signup_on: false, is_need_real_account_for_cashier_modal_visible: false, - is_chart_layout_default: false, toggleNeedRealAccountForCashierModal: jest.fn(), - setAppContentsScrollRef: jest.fn(), is_switch_to_deriv_account_modal_visible: false, openSwitchToRealAccountModal: jest.fn(), is_top_up_virtual_open: false, @@ -386,13 +392,9 @@ const mock = (): TStores & { is_mock: boolean } => { login: '', account_id: '', }, - handleTabItemClick: jest.fn(), - is_account_transfer_modal_open: false, is_eu_user: false, setIsOnboardingVisited: jest.fn(), is_real: false, - is_regulators_compare_modal_visible: false, - is_tour_open: false, selectRegion: jest.fn(), setSelectedAccount: jest.fn(), is_low_risk_cr_eu_real: false, @@ -419,7 +421,6 @@ const mock = (): TStores & { is_mock: boolean } => { is_demo: false, financial_restricted_countries: false, selected_account_type: 'real', - selected_platform_type: 'options', no_CR_account: false, no_MF_account: false, modal_data: { @@ -464,30 +465,56 @@ const mock = (): TStores & { is_mock: boolean } => { setP2POrderProps: jest.fn(), showAccountSwitchToRealNotification: jest.fn(), setP2PRedirectTo: jest.fn(), + setShouldShowPopups: jest.fn(), toggleNotificationsModal: jest.fn(), }, portfolio: { active_positions: [], + active_positions_count: 0, all_positions: [], + barriers: [], error: '', getPositionById: jest.fn(), is_loading: false, is_accumulator: false, is_multiplier: false, is_turbos: false, + onBuyResponse: jest.fn(), onClickCancel: jest.fn(), onClickSell: jest.fn(), onMount: jest.fn(), + open_accu_contract: null, positions: [], removePositionById: jest.fn(), + setContractType: jest.fn(), }, contract_trade: { - contract_info: {}, - contract_update_stop_loss: '', - contract_update_take_profit: '', - has_contract_update_stop_loss: false, - has_contract_update_take_profit: false, + accountSwitchListener: jest.fn(), + accu_barriers_timeout_id: null, + accumulator_barriers_data: {}, + accumulator_contract_barriers_data: {}, + addContract: jest.fn(), + chart_type: '', + clearAccumulatorBarriersData: jest.fn(), + clearError: jest.fn(), + contracts: [], + error_message: '', getContractById: jest.fn(), + granularity: 0, + has_crossed_accu_barriers: false, + has_error: false, + last_contract: {}, + markers_array: [], + onUnmount: jest.fn(), + prev_chart_type: '', + prev_granularity: null, + removeContract: jest.fn(), + savePreviousChartMode: jest.fn(), + setNewAccumulatorBarriersData: jest.fn(), + updateAccumulatorBarriersData: jest.fn(), + updateChartType: jest.fn(), + updateGranularity: jest.fn(), + updateProposal: jest.fn(), }, modules: {}, exchange_rates: { @@ -516,9 +543,20 @@ const mock = (): TStores & { is_mock: boolean } => { setLoginFlag: jest.fn(), }, pushwoosh: {}, - contract_replay: {}, + contract_replay: { + contract_store: { + contract_info: {}, + digits_info: {}, + display_status: '', + is_digit_contract: false, + is_ended: false, + }, + }, chart_barrier_store: {}, - active_symbols: {}, + active_symbols: { + active_symbols: [], + setActiveSymbols: jest.fn(), + }, }; }; diff --git a/packages/stores/types.ts b/packages/stores/types.ts index 13c55dcb4282..7d3d8555799d 100644 --- a/packages/stores/types.ts +++ b/packages/stores/types.ts @@ -2,7 +2,6 @@ import type { AccountLimitsResponse, Authorize, ContractUpdate, - ContractUpdateHistory, DetailsOfEachMT5Loginid, GetAccountStatus, GetLimits, @@ -15,7 +14,9 @@ import type { SetFinancialAssessmentRequest, SetFinancialAssessmentResponse, StatesList, + ContractUpdateHistory, Transaction, + ActiveSymbols, } from '@deriv/api-types'; import type { Moment } from 'moment'; import type { RouteComponentProps } from 'react-router'; @@ -176,11 +177,11 @@ type TMenuItem = { }; type TAddToastProps = { - key: string; + key?: string; content: string | React.ReactNode; - is_bottom?: boolean; timeout?: number; - type: string; + is_bottom?: boolean; + type?: string; }; type TButtonProps = { @@ -266,6 +267,7 @@ type TClientStore = { available_crypto_currencies: string[]; balance?: string | number; can_change_fiat_currency: boolean; + clients_country: string; cfd_score: number; setCFDScore: (score: number) => void; country_standpoint: TCountryStandpoint; @@ -286,6 +288,8 @@ type TClientStore = { is_eu_country: boolean; is_eu: boolean; is_uk: boolean; + is_unwelcome: boolean; + is_single_currency: boolean; is_social_signup: boolean; has_residence: boolean; is_authorize: boolean; @@ -298,7 +302,6 @@ type TClientStore = { is_logging_in: boolean; is_low_risk: boolean; is_pending_proof_of_ownership: boolean; - is_single_currency: boolean; is_switching: boolean; is_tnc_needed: boolean; is_trading_experience_incomplete: boolean; @@ -306,7 +309,6 @@ type TClientStore = { is_withdrawal_lock: boolean; landing_company_shortcode: string; is_populating_account_list: boolean; - is_language_loaded: boolean; local_currency_config: { currency: string; decimal_places?: number; @@ -327,6 +329,7 @@ type TClientStore = { standpoint: TStandPoint; setAccountStatus: (status?: GetAccountStatus) => void; setBalanceOtherAccounts: (balance: number) => void; + selectCurrency: (currency: string) => void; setInitialized: (status?: boolean) => void; setLogout: (status?: boolean) => void; setVisibilityRealityCheck: (value: boolean) => void; @@ -408,19 +411,17 @@ type TClientStore = { setFinancialAndTradingAssessment: ( payload: SetFinancialAssessmentRequest ) => Promise; - prev_account_type: string; }; type TCommonStoreError = { - app_routing_history?: TAppRoutingHistory[]; - header: string | JSX.Element; + header?: string | JSX.Element; message: string | JSX.Element; - redirect_label: string; + redirect_label?: string; redirect_to?: string; - redirectOnClick: (() => void) | null; - setError?: (has_error: boolean, error: React.ReactNode | null) => void; + redirectOnClick?: (() => void) | null; should_clear_error_on_click?: boolean; - should_show_refresh: boolean; + should_redirect?: boolean; + should_show_refresh?: boolean; type?: string; }; type TCommonStoreServicesError = { @@ -438,7 +439,7 @@ type TCommonStore = { platform: 'dxtrade' | 'derivez' | 'mt5' | 'ctrader' | ''; routeBackInApp: (history: Pick, additional_platform_path?: string[]) => void; routeTo: (pathname: string) => void; - server_time?: Moment; + server_time: Moment; changeCurrentLanguage: (new_language: string) => void; changeSelectedLanguage: (key: string) => void; current_language: string; @@ -446,6 +447,10 @@ type TCommonStore = { services_error: TCommonStoreServicesError; is_socket_opened: boolean; setAppstorePlatform: (value: string) => void; + setError?: (has_error: boolean, error: TCommonStoreError) => void; + setSelectedContractType: (contract_type: string) => void; + setServicesError: (error: TCommonStoreServicesError) => void; + showError: (error: TCommonStoreError) => void; app_routing_history: TAppRoutingHistory[]; getExchangeRate: (from_currency: string, to_currency: string) => Promise; network_status: Record | { [key: string]: string }; @@ -463,20 +468,23 @@ type TUiStore = { has_real_account_signup_ended: boolean; header_extension: JSX.Element | null; is_account_settings_visible: boolean; - is_loading: boolean; + is_advanced_duration: boolean; is_cashier_visible: boolean; + is_chart_asset_info_visible?: boolean; + is_chart_layout_default: boolean; is_closing_create_real_account_modal: boolean; is_dark_mode_on: boolean; + is_loading: boolean; is_reports_visible: boolean; is_route_modal_on: boolean; is_language_settings_modal_on: boolean; - is_app_disabled: boolean; is_link_expired_modal_visible: boolean; is_mobile: boolean; is_mobile_language_menu_open: boolean; is_positions_drawer_on: boolean; is_services_error_visible: boolean; is_unsupported_contract_modal_visible: boolean; + openPositionsDrawer: () => void; openRealAccountSignup: ( value: 'maltainvest' | 'svg' | 'add_crypto' | 'choose' | 'add_fiat' | 'set_currency' | 'manage' ) => void; @@ -492,10 +500,11 @@ type TUiStore = { ] | [] ) => void; + resetPurchaseStates: () => void; setAppContentsScrollRef: (ref: React.MutableRefObject) => void; setCurrentFocus: (value: string | null) => void; setDarkMode: (is_dark_mode_on: boolean) => boolean; - setHasOnlyForwardingContracts: (has_only_forward_starting_contracts: boolean) => void; + setHasOnlyForwardingContracts: (has_only_forward_starting_contracts?: boolean) => void; setMobileLanguageMenuOpen: (is_mobile_language_menu_open: boolean) => void; setReportsTabIndex: (value: number) => void; setIsClosingCreateRealAccountModal: (value: boolean) => void; @@ -524,7 +533,6 @@ type TUiStore = { toggleReports: (is_visible: boolean) => void; is_real_acc_signup_on: boolean; is_need_real_account_for_cashier_modal_visible: boolean; - is_chart_layout_default: boolean; toggleNeedRealAccountForCashierModal: () => void; is_switch_to_deriv_account_modal_visible: boolean; openSwitchToRealAccountModal: () => void; @@ -569,27 +577,137 @@ type TPortfolioPosition = { type TPortfolioStore = { active_positions: TPortfolioPosition[]; + active_positions_count: number; all_positions: TPortfolioPosition[]; + barriers: TBarriers; error: string; getPositionById: (id: number) => TPortfolioPosition; is_loading: boolean; is_multiplier: boolean; is_accumulator: boolean; is_turbos: boolean; + onBuyResponse: (contract_info: { contract_id: number; longcode: string; contract_type: string }) => void; onClickCancel: (contract_id?: number) => void; onClickSell: (contract_id?: number) => void; onMount: () => void; + open_accu_contract: TPortfolioPosition | null; positions: TPortfolioPosition[]; removePositionById: (id: number) => void; -}; - + setContractType: (contract_type: string) => void; +}; + +type TAccumulatorBarriersData = { + current_spot: number; + current_spot_time: number; + tick_update_timestamp: number; + accumulators_high_barrier: string; + accumulators_low_barrier: string; + barrier_spot_distance: string; + previous_spot_time: number; +}; +type TAccumulatorContractBarriersData = TAccumulatorBarriersData & { + should_update_contract_barriers: boolean; +}; +type TAddContractParams = { + barrier: number | null; + contract_id: number; + contract_type: string; + start_time: number; + longcode: string; + underlying: string; + is_tick_contract: boolean; + limit_order?: ProposalOpenContract['limit_order']; +}; +type TOnChartBarrierChange = null | ((barrier_1: string, barrier_2?: string) => void); +type TOnChangeParams = { high: string | number; low?: string | number }; +type TBarriers = Array<{ + color: string; + lineStyle: string; + shade?: string; + shadeColor?: string; + high?: string | number; + low?: string | number; + onChange: (barriers: TOnChangeParams) => void; + relative: boolean; + draggable: boolean; + hidePriceLines: boolean; + hideBarrierLine?: boolean; + hideOffscreenLine?: boolean; + title?: string; + onChartBarrierChange: TOnChartBarrierChange | null; + key?: string; + hideOffscreenBarrier?: boolean; + isSingleBarrier?: boolean; + onBarrierChange: (barriers: TOnChangeParams) => void; + updateBarrierColor: (is_dark_mode: boolean) => void; + updateBarriers: (high: string | number, low?: string | number, isFromChart?: boolean) => void; + updateBarrierShade: (should_display: boolean, contract_type: string) => void; + barrier_count: number; + default_shade: string; +}>; type TContractTradeStore = { - contract_info: TPortfolioPosition['contract_info']; - contract_update_stop_loss: string; - contract_update_take_profit: string; - getContractById: (id: number) => TContractStore; - has_contract_update_stop_loss: boolean; - has_contract_update_take_profit: boolean; + accountSwitchListener: () => Promise; + accu_barriers_timeout_id: NodeJS.Timeout | null; + accumulator_barriers_data: Partial; + accumulator_contract_barriers_data: Partial; + addContract: ({ + barrier, + contract_id, + contract_type, + start_time, + longcode, + underlying, + is_tick_contract, + limit_order, + }: TAddContractParams) => void; + chart_type: string; + clearAccumulatorBarriersData: (should_clear_contract_data_only?: boolean, should_clear_timeout?: boolean) => void; + clearError: () => void; + contracts: TContractStore[]; + error_message: string; + getContractById: (contract_id?: number) => TContractStore; + granularity: null | number; + has_crossed_accu_barriers: boolean; + has_error: boolean; + last_contract: TContractStore | Record; + markers_array: Array<{ + type: string; + contract_info: { + accu_barriers_difference: + | boolean + | { + top: string; + bottom: string; + font: string; + }; + has_crossed_accu_barriers: boolean; + is_accumulator_trade_without_contract: boolean; + }; + key: string; + price_array: [string, string]; + epoch_array: [number]; + }>; + onUnmount: () => void; + prev_chart_type: string; + prev_granularity: number | null; + removeContract: (data: { contract_id: string }) => void; + savePreviousChartMode: (chart_type: string, granularity: number | null) => void; + setNewAccumulatorBarriersData: ( + new_barriers_data: TAccumulatorBarriersData, + should_update_contract_barriers?: boolean + ) => void; + updateAccumulatorBarriersData: ({ + accumulators_high_barrier, + accumulators_low_barrier, + barrier_spot_distance, + current_spot, + current_spot_time, + should_update_contract_barriers, + underlying, + }: Partial) => void; + updateChartType: (type: string) => void; + updateGranularity: (granularity: number) => void; + updateProposal: (response: ProposalOpenContract) => void; }; type TContractStore = { @@ -630,9 +748,15 @@ type TNotificationStore = { setP2POrderProps: () => void; showAccountSwitchToRealNotification: (loginid: string, currency: string) => void; setP2PRedirectTo: () => void; + setShouldShowPopups: (should_show_popups: boolean) => void; toggleNotificationsModal: () => void; }; +type TActiveSymbolsStore = { + active_symbols: ActiveSymbols; + setActiveSymbols: () => Promise; +}; + type TBalance = { currency: string; balance: number; @@ -658,16 +782,12 @@ type TTradersHubStore = { login: string; account_id: string; }; - handleTabItemClick: (idx: number) => void; - is_account_transfer_modal_open: boolean; is_low_risk_cr_eu_real: boolean; is_eu_user: boolean; setIsOnboardingVisited: (is_visited: boolean) => void; show_eu_related_content: boolean; setTogglePlatformType: (platform_type: string) => void; is_real: boolean; - is_regulators_compare_modal_visible: boolean; - is_tour_open: boolean; selectRegion: (region: string) => void; closeAccountTransferModal: () => void; toggleRegulatorsCompareModal: () => void; @@ -677,7 +797,6 @@ type TTradersHubStore = { multipliers_account_status: string; financial_restricted_countries: boolean; selected_account_type: string; - selected_platform_type: string; setSelectedAccount: (account: { login?: string; account_id?: string }) => void; no_CR_account: boolean; no_MF_account: boolean; @@ -705,6 +824,15 @@ type TTradersHubStore = { showTopUpModal: () => void; }; +type TContractReplay = { + contract_store: { + contract_info: TPortfolioPosition['contract_info']; + digits_info: { [key: number]: { digit: number; spot: string } }; + display_status: string; + is_digit_contract: boolean; + is_ended: boolean; + }; +}; type TGtmStore = { is_gtm_applicable: boolean; visitorId: Readonly; @@ -742,9 +870,9 @@ export type TCoreStores = { traders_hub: TTradersHubStore; gtm: TGtmStore; pushwoosh: Record; - contract_replay: Record; + contract_replay: TContractReplay; chart_barrier_store: Record; - active_symbols: Record; + active_symbols: TActiveSymbolsStore; }; export type TStores = TCoreStores & { diff --git a/packages/trader/build/constants.js b/packages/trader/build/constants.js index 1a8cab525725..2560c6ae2b60 100644 --- a/packages/trader/build/constants.js +++ b/packages/trader/build/constants.js @@ -42,6 +42,7 @@ const ALIASES = { Services: path.resolve(__dirname, '../src/Services'), Stores: path.resolve(__dirname, '../src/Stores'), Translations: path.resolve(__dirname, '../src/public/translations'), + Types: path.resolve(__dirname, '../src/Types'), Utils: path.resolve(__dirname, '../src/Utils'), }; diff --git a/packages/trader/package.json b/packages/trader/package.json index 9d802416308b..0cc781502849 100644 --- a/packages/trader/package.json +++ b/packages/trader/package.json @@ -87,6 +87,7 @@ "@deriv/analytics": "^1.0.0", "@deriv/components": "^1.0.0", "@deriv/deriv-api": "^1.0.11", + "@deriv/api-types": "^1.0.118", "@deriv/deriv-charts": "1.3.6", "@deriv/reports": "^1.0.0", "@deriv/shared": "^1.0.0", diff --git a/packages/trader/src/App/Components/Elements/ContractAudit/__tests__/contract-details.spec.js b/packages/trader/src/App/Components/Elements/ContractAudit/__tests__/contract-details.spec.tsx similarity index 85% rename from packages/trader/src/App/Components/Elements/ContractAudit/__tests__/contract-details.spec.js rename to packages/trader/src/App/Components/Elements/ContractAudit/__tests__/contract-details.spec.tsx index edea635534de..9f1b66141a40 100644 --- a/packages/trader/src/App/Components/Elements/ContractAudit/__tests__/contract-details.spec.js +++ b/packages/trader/src/App/Components/Elements/ContractAudit/__tests__/contract-details.spec.tsx @@ -1,5 +1,6 @@ import React from 'react'; import { screen, render } from '@testing-library/react'; +import { TContractInfo } from '@deriv/shared'; import ContractDetails from '../contract-details'; describe('ContractDetails', () => { @@ -46,27 +47,27 @@ describe('ContractDetails', () => { }; it('renders the ContractAuditItems specific to Vanilla component when is_vanilla is true', () => { - const wrapper = render( + render( ); - expect(wrapper.queryAllByTestId('dt_bt_label')).toHaveLength(2); + expect(screen.queryAllByTestId('dt_bt_label')).toHaveLength(2); }); it('renders the Payout per point label when is_vanilla is true', () => { render( ); diff --git a/packages/trader/src/App/Components/Elements/ContractAudit/contract-audit-item.jsx b/packages/trader/src/App/Components/Elements/ContractAudit/contract-audit-item.tsx similarity index 80% rename from packages/trader/src/App/Components/Elements/ContractAudit/contract-audit-item.jsx rename to packages/trader/src/App/Components/Elements/ContractAudit/contract-audit-item.tsx index efd400b90269..9fbee190778f 100644 --- a/packages/trader/src/App/Components/Elements/ContractAudit/contract-audit-item.jsx +++ b/packages/trader/src/App/Components/Elements/ContractAudit/contract-audit-item.tsx @@ -1,9 +1,17 @@ -import PropTypes from 'prop-types'; import React from 'react'; import { formatDate, formatTime } from '@deriv/shared'; import { Text } from '@deriv/components'; -const ContractAuditItem = ({ icon, id, label, timestamp, value, value2 }) => ( +type TContractAuditItem = { + icon?: React.ReactNode; + id: string; + label?: string; + timestamp?: number; + value: React.ReactNode; + value2?: React.ReactNode; +}; + +const ContractAuditItem = ({ icon, id, label, timestamp, value, value2 }: TContractAuditItem) => (
{icon &&
{icon}
}
@@ -34,13 +42,4 @@ const ContractAuditItem = ({ icon, id, label, timestamp, value, value2 }) => (
); -ContractAuditItem.propTypes = { - icon: PropTypes.node, - id: PropTypes.string, - label: PropTypes.string, - timestamp: PropTypes.string, - value: PropTypes.PropTypes.oneOfType([PropTypes.number, PropTypes.string, PropTypes.node]), - value2: PropTypes.PropTypes.oneOfType([PropTypes.number, PropTypes.string]), -}; - export default ContractAuditItem; diff --git a/packages/trader/src/App/Components/Elements/ContractAudit/contract-audit.jsx b/packages/trader/src/App/Components/Elements/ContractAudit/contract-audit.tsx similarity index 55% rename from packages/trader/src/App/Components/Elements/ContractAudit/contract-audit.jsx rename to packages/trader/src/App/Components/Elements/ContractAudit/contract-audit.tsx index 24daae7534b5..aaee80ca2f55 100644 --- a/packages/trader/src/App/Components/Elements/ContractAudit/contract-audit.jsx +++ b/packages/trader/src/App/Components/Elements/ContractAudit/contract-audit.tsx @@ -1,35 +1,56 @@ -import PropTypes from 'prop-types'; import React from 'react'; import { Tabs } from '@deriv/components'; import { localize } from '@deriv/translations'; -import { WS } from '@deriv/shared'; +import { WS, TContractStore, TContractInfo } from '@deriv/shared'; +import { useTraderStore } from 'Stores/useTraderStores'; import ContractDetails from './contract-details'; -import ContractHistory from './contract-history.jsx'; +import ContractHistory from './contract-history'; + +type TContractUpdateHistory = TContractStore['contract_update_history']; + +type TContractAudit = Partial< + Pick, 'is_accumulator' | 'is_turbos' | 'is_multiplier' | 'is_vanilla'> +> & { + contract_update_history: TContractUpdateHistory; + contract_end_time: number | undefined; + contract_info: TContractInfo; + duration: string | number; + duration_unit: string; + exit_spot: string | undefined; + has_result: boolean; + is_dark_theme: boolean; + is_open: boolean; + toggleHistoryTab: (state_change?: boolean) => void; +}; + +type TResponse = { + contract_update_history: TContractUpdateHistory; +}; const ContractAudit = ({ contract_update_history, has_result, is_accumulator, is_multiplier, - is_smarttrader_contract, is_turbos, toggleHistoryTab, ...props -}) => { +}: TContractAudit) => { const { contract_id, currency } = props.contract_info; - const [update_history, setUpdateHistory] = React.useState([]); + const [update_history, setUpdateHistory] = React.useState([]); - const getSortedUpdateHistory = history => history.sort((a, b) => b.order_date - a.order_date); + const getSortedUpdateHistory = (history: TContractUpdateHistory) => + history.sort((a, b) => Number(b?.order_date) - Number(a?.order_date)); React.useEffect(() => { if (!!contract_update_history.length && contract_update_history.length > update_history.length) setUpdateHistory(getSortedUpdateHistory(contract_update_history)); }, [contract_update_history, update_history]); - const onTabItemClick = tab_index => { - toggleHistoryTab(tab_index); + const onTabItemClick = (tab_index: number) => { + toggleHistoryTab(!!tab_index); if (tab_index) { - WS.contractUpdateHistory(contract_id).then(response => { + WS.contractUpdateHistory(contract_id).then((response: TResponse) => { setUpdateHistory(getSortedUpdateHistory(response.contract_update_history)); }); } @@ -58,15 +79,4 @@ const ContractAudit = ({ ); }; -ContractAudit.propTypes = { - contract_info: PropTypes.object, - contract_update_history: PropTypes.array, - has_result: PropTypes.bool, - is_accumulator: PropTypes.bool, - is_multiplier: PropTypes.bool, - is_smarttrader_contract: PropTypes.bool, - is_turbos: PropTypes.bool, - toggleHistoryTab: PropTypes.func, -}; - export default ContractAudit; diff --git a/packages/trader/src/App/Components/Elements/ContractAudit/contract-details.jsx b/packages/trader/src/App/Components/Elements/ContractAudit/contract-details.tsx similarity index 82% rename from packages/trader/src/App/Components/Elements/ContractAudit/contract-details.jsx rename to packages/trader/src/App/Components/Elements/ContractAudit/contract-details.tsx index 51e4083bdccf..236fd691cc62 100644 --- a/packages/trader/src/App/Components/Elements/ContractAudit/contract-details.jsx +++ b/packages/trader/src/App/Components/Elements/ContractAudit/contract-details.tsx @@ -1,4 +1,3 @@ -import PropTypes from 'prop-types'; import React from 'react'; import { Money, Icon, ThemedScrollbars } from '@deriv/components'; import { localize } from '@deriv/translations'; @@ -17,6 +16,7 @@ import { isEndedBeforeCancellationExpired, isUserCancelled, toGMTFormat, + TContractInfo, } from '@deriv/shared'; import { addCommaToNumber, @@ -24,10 +24,26 @@ import { getBarrierValue, isDigitType, } from 'App/Components/Elements/PositionsDrawer/helpers'; -import ContractAuditItem from './contract-audit-item.jsx'; +import ContractAuditItem from './contract-audit-item'; import { isCancellationExpired } from 'Stores/Modules/Trading/Helpers/logic'; -const ContractDetails = ({ contract_end_time, contract_info, duration, duration_unit, exit_spot, is_vanilla }) => { +type TContractDetails = { + contract_end_time?: number; + contract_info: TContractInfo; + duration: number | string; + duration_unit: string; + exit_spot?: string; + is_vanilla?: boolean; +}; + +const ContractDetails = ({ + contract_end_time, + contract_info, + duration, + duration_unit, + exit_spot, + is_vanilla, +}: TContractDetails) => { const { commission, contract_type, @@ -45,15 +61,16 @@ const ContractDetails = ({ contract_end_time, contract_info, duration, duration_ display_number_of_contracts, } = contract_info; - const is_profit = profit >= 0; + const is_profit = Number(profit) >= 0; const cancellation_price = getCancellationPrice(contract_info); const show_barrier = !is_vanilla && !isAccumulatorContract(contract_type) && !isSmartTraderContract(contract_type); - const show_duration = !isAccumulatorContract(contract_type) || !isNaN(contract_end_time); + const show_duration = !isAccumulatorContract(contract_type) || !isNaN(Number(contract_end_time)); const show_payout_per_point = isTurbosContract(contract_type) || is_vanilla; + const ticks_label = Number(tick_count) < 2 ? localize('tick') : localize('ticks'); const show_strike_barrier = is_vanilla || isAsiansContract(contract_type); const ticks_duration_text = isAccumulatorContract(contract_type) ? `${tick_passed}/${tick_count} ${localize('ticks')}` - : `${tick_count} ${tick_count < 2 ? localize('tick') : localize('ticks')}`; + : `${tick_count} ${ticks_label}`; const getLabel = () => { if (isUserSold(contract_info) && isEndedBeforeCancellationExpired(contract_info)) @@ -62,7 +79,6 @@ const ContractDetails = ({ contract_end_time, contract_info, duration, duration_ if (isCancellationExpired(contract_info)) return localize('Deal cancellation (expired)'); return localize('Deal cancellation (active)'); }; - return (
@@ -79,7 +95,7 @@ const ContractDetails = ({ contract_end_time, contract_info, duration, duration_ id='dt_commission_label' icon={} label={localize('Commission')} - value={} + value={} /> {!!cancellation_price && ( } label={localize('Duration')} - value={tick_count > 0 ? ticks_duration_text : `${duration} ${duration_unit}`} + value={Number(tick_count) > 0 ? ticks_duration_text : `${duration} ${duration_unit}`} /> )} {show_strike_barrier && ( @@ -144,7 +160,11 @@ const ContractDetails = ({ contract_end_time, contract_info, duration, duration_ id='dt_bt_label' icon={} label={localize('Payout per point')} - value={`${display_number_of_contracts} ${getCurrencyDisplayCode(currency)}` || ' - '} + value={ + display_number_of_contracts + ? `${display_number_of_contracts} ${getCurrencyDisplayCode(currency)}` + : ' - ' + } /> )} @@ -153,7 +173,7 @@ const ContractDetails = ({ contract_end_time, contract_info, duration, duration_ id='dt_start_time_label' icon={} label={localize('Start time')} - value={toGMTFormat(epochToMoment(date_start)) || ' - '} + value={toGMTFormat(epochToMoment(Number(date_start))) || ' - '} /> {!isDigitType(contract_type) && ( } label={localize('Entry spot')} value={addCommaToNumber(entry_spot_display_value) || ' - '} - value2={toGMTFormat(epochToMoment(entry_tick_time)) || ' - '} + value2={toGMTFormat(epochToMoment(Number(entry_tick_time))) || ' - '} /> )} - {!isNaN(exit_spot) && ( + {!isNaN(Number(exit_spot)) && ( } label={localize('Exit spot')} value={addCommaToNumber(exit_spot) || ' - '} - value2={toGMTFormat(epochToMoment(exit_tick_time)) || ' - '} + value2={toGMTFormat(epochToMoment(Number(exit_tick_time))) || ' - '} /> )} - {!isNaN(contract_end_time) && ( + {!isNaN(Number(contract_end_time)) && ( } label={localize('Exit time')} - value={toGMTFormat(epochToMoment(contract_end_time)) || ' - '} + value={toGMTFormat(epochToMoment(Number(contract_end_time))) || ' - '} /> )}
@@ -186,14 +206,4 @@ const ContractDetails = ({ contract_end_time, contract_info, duration, duration_ ); }; -ContractDetails.propTypes = { - contract_end_time: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - contract_info: PropTypes.object, - date_start: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - duration: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - duration_unit: PropTypes.string, - exit_spot: PropTypes.string, - is_vanilla: PropTypes.bool, -}; - export default ContractDetails; diff --git a/packages/trader/src/App/Components/Elements/ContractAudit/contract-history.jsx b/packages/trader/src/App/Components/Elements/ContractAudit/contract-history.tsx similarity index 75% rename from packages/trader/src/App/Components/Elements/ContractAudit/contract-history.jsx rename to packages/trader/src/App/Components/Elements/ContractAudit/contract-history.tsx index 239c8ee4d8fc..af17015ee0a6 100644 --- a/packages/trader/src/App/Components/Elements/ContractAudit/contract-history.jsx +++ b/packages/trader/src/App/Components/Elements/ContractAudit/contract-history.tsx @@ -1,12 +1,14 @@ -import PropTypes from 'prop-types'; import React from 'react'; import { Icon, Money, ThemedScrollbars, Text } from '@deriv/components'; -import { isMobile } from '@deriv/shared'; - +import { isMobile, TContractStore } from '@deriv/shared'; import { localize } from '@deriv/translations'; -import ContractAuditItem from './contract-audit-item.jsx'; +import ContractAuditItem from './contract-audit-item'; -const ContractHistory = ({ currency, history = [] }) => { +type TContractHistory = { + currency?: string; + history: [] | TContractStore['contract_update_history']; +}; +const ContractHistory = ({ currency, history = [] }: TContractHistory) => { if (!history.length) { return (
@@ -23,14 +25,14 @@ const ContractHistory = ({ currency, history = [] }) => {
{history.map((item, key) => ( - {+item.order_amount < 0 && -} + {Number(item.order_amount) < 0 && -} {item.value && ( @@ -50,9 +52,4 @@ const ContractHistory = ({ currency, history = [] }) => { ); }; -ContractHistory.propTypes = { - currency: PropTypes.string, - history: PropTypes.array, -}; - export default ContractHistory; diff --git a/packages/trader/src/App/Components/Elements/ContractAudit/index.js b/packages/trader/src/App/Components/Elements/ContractAudit/index.js index 1cf766ffd8e6..5051cc70b69f 100644 --- a/packages/trader/src/App/Components/Elements/ContractAudit/index.js +++ b/packages/trader/src/App/Components/Elements/ContractAudit/index.js @@ -1,3 +1,3 @@ -import ContractAudit from './contract-audit.jsx'; +import ContractAudit from './contract-audit'; export default ContractAudit; diff --git a/packages/trader/src/App/Components/Elements/ContractDrawer/contract-drawer-card.jsx b/packages/trader/src/App/Components/Elements/ContractDrawer/contract-drawer-card.tsx similarity index 80% rename from packages/trader/src/App/Components/Elements/ContractDrawer/contract-drawer-card.jsx rename to packages/trader/src/App/Components/Elements/ContractDrawer/contract-drawer-card.tsx index 3bac2237f157..9b8709b0b4a2 100644 --- a/packages/trader/src/App/Components/Elements/ContractDrawer/contract-drawer-card.jsx +++ b/packages/trader/src/App/Components/Elements/ContractDrawer/contract-drawer-card.tsx @@ -1,5 +1,4 @@ import classNames from 'classnames'; -import PropTypes from 'prop-types'; import React from 'react'; import { DesktopWrapper, MobileWrapper, Collapsible, ContractCard, useHover } from '@deriv/components'; import { @@ -9,18 +8,45 @@ import { getContractTypeDisplay, isCryptoContract, isDesktop, + toMoment, } from '@deriv/shared'; import { getMarketInformation } from 'Utils/Helpers/market-underlying'; -import { SwipeableContractDrawer } from './swipeable-components.jsx'; -import MarketClosedContractOverlay from './market-closed-contract-overlay.jsx'; +import { SwipeableContractDrawer } from './swipeable-components'; +import MarketClosedContractOverlay from './market-closed-contract-overlay'; import { useTraderStore } from 'Stores/useTraderStores'; import { observer, useStore } from '@deriv/stores'; +type TContractCardBodyProps = React.ComponentProps; +type TContractCardFooterProps = React.ComponentProps; +type TSwipeableContractDrawerProps = React.ComponentProps; + +type TContractDrawerCardProps = { + currency?: string; + is_collapsed: boolean; + is_market_closed: boolean; + is_smarttrader_contract: boolean; + result?: string; + server_time?: moment.Moment; + toggleContractAuditDrawer: () => void; +} & Pick< + TContractCardBodyProps, + | 'contract_info' + | 'contract_update' + | 'is_accumulator' + | 'is_mobile' + | 'is_multiplier' + | 'is_turbos' + | 'is_vanilla' + | 'status' +> & + Pick & + Pick; + const ContractDrawerCard = observer( ({ contract_info, contract_update, - currency, + currency = '', is_accumulator, is_collapsed, is_market_closed, @@ -35,10 +61,10 @@ const ContractDrawerCard = observer( onSwipedUp, onSwipedDown, result, - server_time, + server_time = toMoment(), status, toggleContractAuditDrawer, - }) => { + }: TContractDrawerCardProps) => { const { ui, contract_trade } = useStore(); const { active_symbols } = useTraderStore(); const { @@ -50,13 +76,13 @@ const ContractDrawerCard = observer( toggleCancellationWarning, } = ui; const { getContractById } = contract_trade; - const [hover_ref, should_hide_closed_overlay] = useHover(); + const [hover_ref, should_hide_closed_overlay] = useHover(); const { profit, validation_error } = contract_info; const is_sold = !!getEndTime(contract_info); const display_name = getSymbolDisplayName( active_symbols, - getMarketInformation(contract_info.shortcode).underlying + getMarketInformation(contract_info.shortcode || '').underlying ); const is_crypto = isCryptoContract(contract_info.underlying); @@ -83,7 +109,7 @@ const ContractDrawerCard = observer( contract_info={contract_info} contract_update={contract_update} currency={currency} - current_focus={current_focus} + current_focus={current_focus ?? ''} getCardLabels={getCardLabels} getContractById={getContractById} is_accumulator={is_accumulator} @@ -97,7 +123,7 @@ const ContractDrawerCard = observer( server_time={server_time} setCurrentFocus={setCurrentFocus} should_show_cancellation_warning={should_show_cancellation_warning} - status={status} + status={status ?? ''} toggleCancellationWarning={toggleCancellationWarning} /> ); @@ -111,7 +137,6 @@ const ContractDrawerCard = observer( onClickCancel={onClickCancel} onClickSell={onClickSell} server_time={server_time} - status={status} /> ); @@ -127,13 +152,13 @@ const ContractDrawerCard = observer( contract_info={contract_info} getCardLabels={getCardLabels} is_multiplier={is_multiplier} - profit_loss={profit} + profit_loss={Number(profit)} should_show_result_overlay={false} >
0 && !result, - 'dc-contract-card--red': profit < 0 && !result, + 'dc-contract-card--green': Number(profit) > 0 && !result, + 'dc-contract-card--red': Number(profit) < 0 && !result, 'contract-card__market-closed--disabled': is_market_closed && should_hide_closed_overlay, })} ref={hover_ref} @@ -175,13 +200,4 @@ const ContractDrawerCard = observer( } ); -ContractDrawerCard.propTypes = { - currency: PropTypes.string, - is_accumulator: PropTypes.bool, - is_smarttrader_contract: PropTypes.bool, - is_collapsed: PropTypes.bool, - is_turbos: PropTypes.bool, - onClickCancel: PropTypes.func, - onClickSell: PropTypes.func, -}; export default ContractDrawerCard; diff --git a/packages/trader/src/App/Components/Elements/ContractDrawer/contract-drawer.jsx b/packages/trader/src/App/Components/Elements/ContractDrawer/contract-drawer.tsx similarity index 84% rename from packages/trader/src/App/Components/Elements/ContractDrawer/contract-drawer.jsx rename to packages/trader/src/App/Components/Elements/ContractDrawer/contract-drawer.tsx index 0d51eae66590..69045cc10ab8 100644 --- a/packages/trader/src/App/Components/Elements/ContractDrawer/contract-drawer.jsx +++ b/packages/trader/src/App/Components/Elements/ContractDrawer/contract-drawer.tsx @@ -1,7 +1,6 @@ import classNames from 'classnames'; -import PropTypes from 'prop-types'; import React from 'react'; -import { withRouter } from 'react-router'; +import { RouteComponentProps, withRouter } from 'react-router'; import { CSSTransition } from 'react-transition-group'; import { DesktopWrapper, MobileWrapper, Div100vhContainer } from '@deriv/components'; import { @@ -11,13 +10,35 @@ import { getDurationTime, getDurationUnitText, getEndTime, + TContractStore, } from '@deriv/shared'; import ContractAudit from 'App/Components/Elements/ContractAudit'; import { PositionsCardLoader } from 'App/Components/Elements/ContentLoader'; import ContractDrawerCard from './contract-drawer-card'; -import { SwipeableContractAudit } from './swipeable-components.jsx'; +import { SwipeableContractAudit } from './swipeable-components'; import { observer, useStore } from '@deriv/stores'; +type TContractDrawerCardProps = React.ComponentProps; +type TContractDrawerProps = RouteComponentProps & { + contract_update_history: TContractStore['contract_update_history']; + is_dark_theme: boolean; + toggleHistoryTab: (state_change?: boolean) => void; +} & Pick< + TContractDrawerCardProps, + | 'contract_info' + | 'contract_update' + | 'is_accumulator' + | 'is_market_closed' + | 'is_multiplier' + | 'is_sell_requested' + | 'is_smarttrader_contract' + | 'is_turbos' + | 'is_vanilla' + | 'onClickCancel' + | 'onClickSell' + | 'status' + >; + const ContractDrawer = observer( ({ contract_info, @@ -35,13 +56,13 @@ const ContractDrawer = observer( onClickSell, status, toggleHistoryTab, - }) => { + }: TContractDrawerProps) => { const { common, ui } = useStore(); const { server_time } = common; const { is_mobile } = ui; const { currency, exit_tick_display_value, is_sold } = contract_info; - const contract_drawer_ref = React.useRef(); - const contract_drawer_card_ref = React.useRef(); + const contract_drawer_ref = React.useRef(null); + const contract_drawer_card_ref = React.useRef(null); const [should_show_contract_audit, setShouldShowContractAudit] = React.useState(false); const exit_spot = isUserSold(contract_info) && !is_accumulator && !is_multiplier && !is_turbos @@ -56,7 +77,6 @@ const ContractDrawer = observer( is_accumulator={is_accumulator} is_dark_theme={is_dark_theme} is_multiplier={is_multiplier} - is_smarttrader_contract={is_smarttrader_contract} is_open is_turbos={is_turbos} duration={getDurationTime(contract_info)} @@ -102,7 +122,7 @@ const ContractDrawer = observer( ) : (
- +
); @@ -119,11 +139,10 @@ const ContractDrawer = observer( 'contract-drawer--is-multiplier-sold': is_multiplier && isMobile() && getEndTime(contract_info), })} style={{ - transform: - should_show_contract_audit && + transform: (should_show_contract_audit && contract_drawer_ref.current && contract_drawer_card_ref.current && - `translateY(calc(${contract_drawer_card_ref.current.clientHeight}px - ${contract_drawer_ref.current.clientHeight}px))`, + `translateY(calc(${contract_drawer_card_ref.current.clientHeight}px - ${contract_drawer_ref.current.clientHeight}px))`) as React.CSSProperties['transform'], }} ref={contract_drawer_ref} > @@ -162,13 +181,4 @@ const ContractDrawer = observer( } ); -ContractDrawer.propTypes = { - is_accumulator: PropTypes.bool, - is_multiplier: PropTypes.bool, - is_vanilla: PropTypes.bool, - is_smarttrader_contract: PropTypes.bool, - is_turbos: PropTypes.bool, - toggleHistoryTab: PropTypes.func, -}; - export default withRouter(ContractDrawer); diff --git a/packages/trader/src/App/Components/Elements/ContractDrawer/index.js b/packages/trader/src/App/Components/Elements/ContractDrawer/index.js deleted file mode 100644 index 34ed63b1b74e..000000000000 --- a/packages/trader/src/App/Components/Elements/ContractDrawer/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import ContractDrawer from './contract-drawer.jsx'; - -export default ContractDrawer; diff --git a/packages/trader/src/App/Components/Elements/ContractDrawer/index.ts b/packages/trader/src/App/Components/Elements/ContractDrawer/index.ts new file mode 100644 index 000000000000..563d551f3b95 --- /dev/null +++ b/packages/trader/src/App/Components/Elements/ContractDrawer/index.ts @@ -0,0 +1,3 @@ +import ContractDrawer from './contract-drawer'; + +export default ContractDrawer; diff --git a/packages/trader/src/App/Components/Elements/ContractDrawer/market-closed-contract-overlay.jsx b/packages/trader/src/App/Components/Elements/ContractDrawer/market-closed-contract-overlay.tsx similarity index 64% rename from packages/trader/src/App/Components/Elements/ContractDrawer/market-closed-contract-overlay.jsx rename to packages/trader/src/App/Components/Elements/ContractDrawer/market-closed-contract-overlay.tsx index 8d3d47aeb479..8538cd1de2c6 100644 --- a/packages/trader/src/App/Components/Elements/ContractDrawer/market-closed-contract-overlay.jsx +++ b/packages/trader/src/App/Components/Elements/ContractDrawer/market-closed-contract-overlay.tsx @@ -1,8 +1,7 @@ import React from 'react'; -import PropTypes from 'prop-types'; import { Text } from '@deriv/components'; -const MarketClosedContractOverlay = ({ validation_error }) => ( +const MarketClosedContractOverlay = ({ validation_error }: { validation_error?: string }) => (
{validation_error} @@ -10,8 +9,4 @@ const MarketClosedContractOverlay = ({ validation_error }) => (
); -MarketClosedContractOverlay.propTypes = { - symbol: PropTypes.string, -}; - export default MarketClosedContractOverlay; diff --git a/packages/trader/src/App/Components/Elements/ContractDrawer/swipeable-components.jsx b/packages/trader/src/App/Components/Elements/ContractDrawer/swipeable-components.tsx similarity index 75% rename from packages/trader/src/App/Components/Elements/ContractDrawer/swipeable-components.jsx rename to packages/trader/src/App/Components/Elements/ContractDrawer/swipeable-components.tsx index 9b9e917c28b3..55aefd7f33e7 100644 --- a/packages/trader/src/App/Components/Elements/ContractDrawer/swipeable-components.jsx +++ b/packages/trader/src/App/Components/Elements/ContractDrawer/swipeable-components.tsx @@ -1,13 +1,21 @@ import classNames from 'classnames'; -import PropTypes from 'prop-types'; import React from 'react'; import ReactDOM from 'react-dom'; import { SwipeableWrapper } from '@deriv/components'; +type TSwipeableContractAuditProps = React.PropsWithChildren<{ + is_multiplier?: boolean; + onSwipedDown?: () => void; +}>; +type TSwipeableContractDrawerProps = React.PropsWithChildren<{ + onSwipedDown?: () => void; + onSwipedUp?: () => void; +}>; + /** * Swipeable components */ -export const SwipeableContractAudit = ({ is_multiplier, children, onSwipedDown }) => { +export const SwipeableContractAudit = ({ is_multiplier, children, onSwipedDown }: TSwipeableContractAuditProps) => { const swipe_handlers = SwipeableWrapper.useSwipeable({ onSwipedDown, }); @@ -31,13 +39,7 @@ export const SwipeableContractAudit = ({ is_multiplier, children, onSwipedDown } ); }; -SwipeableContractAudit.propTypes = { - is_multiplier: PropTypes.bool, - children: PropTypes.node, - onSwipedDown: PropTypes.func, -}; - -export const SwipeableContractDrawer = ({ children, onSwipedDown, onSwipedUp }) => { +export const SwipeableContractDrawer = ({ children, onSwipedDown, onSwipedUp }: TSwipeableContractDrawerProps) => { const swipe_handlers = SwipeableWrapper.useSwipeable({ onSwipedDown, onSwipedUp, @@ -45,9 +47,3 @@ export const SwipeableContractDrawer = ({ children, onSwipedDown, onSwipedUp }) return
{children}
; }; - -SwipeableContractDrawer.propTypes = { - children: PropTypes.node, - onSwipedDown: PropTypes.func, - onSwipedUp: PropTypes.func, -}; diff --git a/packages/trader/src/App/Components/Elements/TogglePositions/__tests__/toggle-positions-mobile.spec.tsx b/packages/trader/src/App/Components/Elements/TogglePositions/__tests__/toggle-positions-mobile.spec.tsx index cd2855ab77f7..025a84d9b56a 100644 --- a/packages/trader/src/App/Components/Elements/TogglePositions/__tests__/toggle-positions-mobile.spec.tsx +++ b/packages/trader/src/App/Components/Elements/TogglePositions/__tests__/toggle-positions-mobile.spec.tsx @@ -7,21 +7,53 @@ import { mockStore } from '@deriv/stores'; import { TCoreStores } from '@deriv/stores/types'; import { BrowserRouter } from 'react-router-dom'; -const default_mocked_props = { +const default_mocked_props: React.ComponentProps = { active_positions_count: 0, - filtered_positions: [], currency: 'USD', - disableApp: jest.fn(), - enableApp: jest.fn(), error: '', + filtered_positions: [ + { + contract_info: { + contract_id: 215925907928, + contract_type: 'CALL', + is_sold: 0, + shortcode: 'CALL_1HZ100V_76.33_1692187938_1692188838_S0P_0', + underlying: '1HZ100V', + }, + contract_update: {}, + display_name: 'Volatility 100 (1s) Index', + indicative: 29.55, + reference: 430851089968, + is_sell_requested: false, + is_unsupported: false, + profit_loss: -9.45, + }, + { + contract_info: { + contract_id: 2, + contract_type: 'PUT', + is_sold: 0, + shortcode: 'PUT_R_10_19.53_1691443887_1691444787_S0P_0', + underlying: 'R_100', + }, + contract_update: {}, + display_name: 'Volatility 100 (1s) Index', + indicative: 29.55, + reference: 430851089968, + is_sell_requested: false, + is_unsupported: false, + profit_loss: -9.45, + }, + ], is_empty: true, onClickSell: jest.fn(), onClickCancel: jest.fn(), - toggleUnsupportedContractModal: jest.fn(), }; + const default_mock_store = { ui: { togglePositionsDrawer: jest.fn(), + toggleUnsupportedContractModal: jest.fn(), is_positions_drawer_on: false, }, }; @@ -68,29 +100,47 @@ describe('TogglePositionsMobile component', () => { expect(screen.getByText(/You have no open positions for this asset./i)).toBeInTheDocument(); }); it('should display 2 positions when is_positions_drawer_on === true, is_empty === false, and has 2 active positions', () => { - const new_mocked_props = { + const new_mocked_props: React.ComponentProps = { active_positions_count: 2, + currency: 'USD', + error: '', filtered_positions: [ { contract_info: { - contract_id: '1', + contract_id: 1, contract_type: 'CALL', is_sold: 0, shortcode: 'CALL_R_10_19.54_1691443851_1691444751_S0P_0', underlying: 'R_100', }, + contract_update: {}, + display_name: 'Volatility 100 (1s) Index', + indicative: 29.55, + reference: 430851089968, + is_sell_requested: false, + is_unsupported: false, + profit_loss: -9.45, }, { contract_info: { - contract_id: '2', + contract_id: 2, contract_type: 'PUT', is_sold: 0, shortcode: 'PUT_R_10_19.53_1691443887_1691444787_S0P_0', underlying: 'R_100', }, + contract_update: {}, + display_name: 'Volatility 100 (1s) Index', + indicative: 29.55, + reference: 430851089968, + is_sell_requested: false, + is_unsupported: false, + profit_loss: -9.45, }, ], is_empty: false, + onClickSell: jest.fn(), + onClickCancel: jest.fn(), }; const mock_root_store = mockStore({ ...default_mock_store, @@ -101,29 +151,47 @@ describe('TogglePositionsMobile component', () => { expect(screen.getAllByText(/PositionsModalCard/i)).toHaveLength(2); }); it('should display 1 of 2 positions after closing the modal if one of the 2 positions is sold', async () => { - const new_mocked_props = { + const new_mocked_props: React.ComponentProps = { active_positions_count: 1, + currency: 'USD', + error: '', filtered_positions: [ { contract_info: { - contract_id: '1', + contract_id: 1, contract_type: 'CALL', is_sold: 0, shortcode: 'CALL_R_10_19.54_1691443851_1691444751_S0P_0', underlying: 'R_100', }, + contract_update: {}, + display_name: 'Volatility 100 (1s) Index', + indicative: 29.55, + reference: 430851089968, + is_sell_requested: false, + is_unsupported: false, + profit_loss: -9.45, }, { contract_info: { - contract_id: '2', + contract_id: 2, contract_type: 'PUT', is_sold: 1, shortcode: 'PUT_R_10_19.53_1691443887_1691444787_S0P_0', underlying: 'R_100', }, + contract_update: {}, + display_name: 'Volatility 100 (1s) Index', + indicative: 29.55, + reference: 430851089968, + is_sell_requested: false, + is_unsupported: false, + profit_loss: -9.45, }, ], is_empty: false, + onClickSell: jest.fn(), + onClickCancel: jest.fn(), }; const mock_root_store = mockStore({ ...default_mock_store, @@ -140,24 +208,38 @@ describe('TogglePositionsMobile component', () => { }); }); it('should display no more than 5 recent positions', () => { - const positions_pair = [ + const positions_pair: React.ComponentProps['filtered_positions'] = [ { contract_info: { - contract_id: '1', + contract_id: 1, contract_type: 'CALL', is_sold: 0, shortcode: 'CALL_R_10_19.54_1691443851_1691444751_S0P_0', underlying: 'R_100', }, + contract_update: {}, + display_name: 'Volatility 100 (1s) Index', + indicative: 29.55, + reference: 430851089968, + is_sell_requested: false, + is_unsupported: false, + profit_loss: -9.45, }, { contract_info: { - contract_id: '2', + contract_id: 2, contract_type: 'PUT', is_sold: 0, shortcode: 'PUT_R_10_19.53_1691443887_1691444787_S0P_0', underlying: 'R_100', }, + contract_update: {}, + display_name: 'Volatility 100 (1s) Index', + indicative: 29.55, + reference: 430851089968, + is_sell_requested: false, + is_unsupported: false, + profit_loss: -9.45, }, ]; const new_mocked_props = { diff --git a/packages/trader/src/App/Components/Elements/TogglePositions/__tests__/toggle-positions.spec.tsx b/packages/trader/src/App/Components/Elements/TogglePositions/__tests__/toggle-positions.spec.tsx index 19770e5e8bf3..20e13be8dd7a 100644 --- a/packages/trader/src/App/Components/Elements/TogglePositions/__tests__/toggle-positions.spec.tsx +++ b/packages/trader/src/App/Components/Elements/TogglePositions/__tests__/toggle-positions.spec.tsx @@ -1,29 +1,35 @@ import React from 'react'; import userEvent from '@testing-library/user-event'; import { render, screen } from '@testing-library/react'; -import TogglePositions from '../toggle-positions.jsx'; +import TogglePositions from '../toggle-positions'; + +const default_props = { + is_open: false, + positions_count: 0, + togglePositions: jest.fn(), +}; describe('TogglePositions component', () => { it('should have "positions-toggle--active" class when "is_open" is "true"', () => { - render(); + render(); expect(screen.getByTestId('dt_positions_toggle')).toHaveClass('positions-toggle--active'); }); it('should have "positions-toggle--has-count" class when "positions_count > 0"', () => { - render(); + render(); expect(screen.getByTestId('dt_positions_toggle')).toHaveClass('positions-toggle--has-count'); }); it('should call "togglePositions" when the user clicked on the link', () => { const mockTogglePositions = jest.fn(); - render(); + render(); const link = screen.getByTestId('dt_positions_toggle'); userEvent.click(link); expect(mockTogglePositions).toHaveBeenCalledTimes(1); }); it('should render "IcPortfolio" icon', () => { - render(); + render(); expect(screen.getByTestId('dt_icon')).toBeVisible(); }); }); diff --git a/packages/trader/src/App/Components/Elements/TogglePositions/index.js b/packages/trader/src/App/Components/Elements/TogglePositions/index.js index 117ed97b03fd..c97185e5654a 100644 --- a/packages/trader/src/App/Components/Elements/TogglePositions/index.js +++ b/packages/trader/src/App/Components/Elements/TogglePositions/index.js @@ -1,3 +1,3 @@ -import TogglePositions from './toggle-positions.jsx'; +import TogglePositions from './toggle-positions'; export default TogglePositions; diff --git a/packages/trader/src/App/Components/Elements/TogglePositions/toggle-positions-mobile.jsx b/packages/trader/src/App/Components/Elements/TogglePositions/toggle-positions-mobile.tsx similarity index 87% rename from packages/trader/src/App/Components/Elements/TogglePositions/toggle-positions-mobile.jsx rename to packages/trader/src/App/Components/Elements/TogglePositions/toggle-positions-mobile.tsx index d17e8ca51eed..eef96fdde3b9 100644 --- a/packages/trader/src/App/Components/Elements/TogglePositions/toggle-positions-mobile.jsx +++ b/packages/trader/src/App/Components/Elements/TogglePositions/toggle-positions-mobile.tsx @@ -6,24 +6,32 @@ import { localize } from '@deriv/translations'; import { NavLink } from 'react-router-dom'; import EmptyPortfolioMessage from '../EmptyPortfolioMessage'; import PositionsModalCard from 'App/Components/Elements/PositionsDrawer/positions-modal-card.jsx'; -import TogglePositions from './toggle-positions.jsx'; +import TogglePositions from './toggle-positions'; import { observer, useStore } from '@deriv/stores'; +type TTogglePositionsMobile = Pick< + ReturnType['portfolio'], + 'active_positions_count' | 'error' | 'onClickSell' | 'onClickCancel' +> & { + currency: ReturnType['client']['currency']; + filtered_positions: ReturnType['portfolio']['all_positions']; + is_empty: boolean; +}; + +type THiddenPositionsId = TTogglePositionsMobile['filtered_positions'][0]['id']; + const TogglePositionsMobile = observer( ({ active_positions_count, currency, - disableApp, - enableApp, error, filtered_positions, is_empty, onClickSell, onClickCancel, - toggleUnsupportedContractModal, - }) => { - const { togglePositionsDrawer, is_positions_drawer_on } = useStore().ui; - const [hidden_positions_ids, setHiddenPositionsIds] = React.useState([]); + }: TTogglePositionsMobile) => { + const { togglePositionsDrawer, toggleUnsupportedContractModal, is_positions_drawer_on } = useStore().ui; + const [hidden_positions_ids, setHiddenPositionsIds] = React.useState([]); const displayed_positions = filtered_positions .filter(p => hidden_positions_ids.every(hidden_position_id => hidden_position_id !== p.contract_info.contract_id) @@ -57,6 +65,7 @@ const TogglePositionsMobile = observer( unmountOnExit > diff --git a/packages/trader/src/App/Components/Elements/TogglePositions/toggle-positions.jsx b/packages/trader/src/App/Components/Elements/TogglePositions/toggle-positions.tsx similarity index 74% rename from packages/trader/src/App/Components/Elements/TogglePositions/toggle-positions.jsx rename to packages/trader/src/App/Components/Elements/TogglePositions/toggle-positions.tsx index c37fb05d11f0..7e7691372e11 100644 --- a/packages/trader/src/App/Components/Elements/TogglePositions/toggle-positions.jsx +++ b/packages/trader/src/App/Components/Elements/TogglePositions/toggle-positions.tsx @@ -1,10 +1,15 @@ import classNames from 'classnames'; -import PropTypes from 'prop-types'; import React from 'react'; import { Icon } from '@deriv/components'; import 'Sass/app/_common/components/positions-toggle.scss'; -const TogglePositions = ({ positions_count, is_open, togglePositions }) => { +type TTogglePositions = { + positions_count: number; + is_open: boolean; + togglePositions: () => void; +}; + +const TogglePositions = ({ positions_count, is_open, togglePositions }: TTogglePositions) => { const positions_toggle_class = classNames('positions-toggle', { 'positions-toggle--active': is_open, 'positions-toggle--has-count': positions_count > 0, @@ -22,12 +27,4 @@ const TogglePositions = ({ positions_count, is_open, togglePositions }) => { ); }; -TogglePositions.propTypes = { - is_open: PropTypes.bool, - is_positions_drawer_on: PropTypes.bool, - positions_count: PropTypes.number, - togglePositions: PropTypes.func, - togglePositionsDrawer: PropTypes.func, -}; - export default TogglePositions; diff --git a/packages/trader/src/App/Components/Elements/market-countdown-timer.jsx b/packages/trader/src/App/Components/Elements/market-countdown-timer.jsx deleted file mode 100644 index 833af29df84f..000000000000 --- a/packages/trader/src/App/Components/Elements/market-countdown-timer.jsx +++ /dev/null @@ -1,209 +0,0 @@ -import classNames from 'classnames'; -import React from 'react'; -import PropTypes from 'prop-types'; -import moment from 'moment'; -import { Text } from '@deriv/components'; -import { useIsMounted, WS, convertTimeFormat, isMarketClosed } from '@deriv/shared'; -import { Localize } from '@deriv/translations'; -import { observer } from '@deriv/stores'; -import { useTraderStore } from 'Stores/useTraderStores'; - -// check market in coming 7 days -const days_to_check_before_exit = 7; - -const getTradingTimes = async target_time => { - const data = await WS.tradingTimes(target_time); - if (data.error) { - return { api_initial_load_error: data.error.message }; - } - return data; -}; -// eslint-disable-next-line consistent-return -const getSymbol = (target_symbol, trading_times) => { - let symbol; - const { markets } = trading_times; - for (let i = 0; i < markets.length; i++) { - const { submarkets } = markets[i]; - for (let j = 0; j < submarkets.length; j++) { - const { symbols } = submarkets[j]; - symbol = symbols.find(item => item.symbol === target_symbol); - if (symbol !== undefined) return symbol; - } - } -}; - -const calculateTimeLeft = remaining_time_to_open => { - const difference = remaining_time_to_open - Date.now(); - return difference > 0 - ? { - days: Math.floor(difference / (1000 * 60 * 60 * 24)), - hours: Math.floor((difference / (1000 * 60 * 60)) % 24), - minutes: Math.floor((difference / 1000 / 60) % 60), - seconds: Math.floor((difference / 1000) % 60), - } - : {}; -}; - -const MarketCountdownTimer = observer(({ is_main_page, setIsTimerLoading, onMarketOpen, symbol }) => { - const { active_symbols } = useTraderStore(); - const isMounted = useIsMounted(); - const [when_market_opens, setWhenMarketOpens] = React.useState({}); - const [time_left, setTimeLeft] = React.useState(calculateTimeLeft(when_market_opens?.remaining_time_to_open)); - const [is_loading, setLoading] = React.useState(true); - - React.useEffect(() => { - if (!is_main_page || (is_main_page && isMarketClosed(active_symbols, symbol))) { - setLoading(true); - // eslint-disable-next-line consistent-return - const whenMarketOpens = async (days_offset, target_symbol) => { - // days_offset is 0 for today, 1 for tomorrow, etc. - if (days_offset > days_to_check_before_exit) return {}; - let remaining_time_to_open; - const target_date = moment(new Date()).add(days_offset, 'days'); - const api_response = await getTradingTimes(target_date.format('YYYY-MM-DD')); - if (!api_response.api_initial_load_error) { - const { times } = getSymbol(target_symbol, api_response.trading_times); - const { open, close } = times; - const is_closed_all_day = open?.length === 1 && open[0] === '--' && close[0] === '--'; - if (is_closed_all_day) { - // check tomorrow trading times - return whenMarketOpens(days_offset + 1, target_symbol); - } - const date_str = target_date.toISOString().substring(0, 11); - const getUTCDate = hour => new Date(`${date_str}${hour}Z`); - for (let i = 0; i < open?.length; i++) { - const diff = +getUTCDate(open[i]) - Date.now(); - if (diff > 0) { - remaining_time_to_open = +getUTCDate(open[i]); - if (isMounted() && target_symbol === symbol) { - return setWhenMarketOpens({ - days_offset, - opening_time: open[i], - remaining_time_to_open, - }); - } - } - } - whenMarketOpens(days_offset + 1, target_symbol); - } - }; - - whenMarketOpens(0, symbol); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [symbol]); - - React.useEffect(() => { - let timer; - if (when_market_opens?.remaining_time_to_open) { - timer = setTimeout(() => { - setTimeLeft(calculateTimeLeft(when_market_opens.remaining_time_to_open)); - if (new Date(when_market_opens.remaining_time_to_open) - +new Date() < 1000) { - setLoading(true); - if (is_main_page) onMarketOpen(false); - } - }, 1000); - } - return () => { - if (timer) { - clearTimeout(timer); - } - }; - }, [time_left, when_market_opens, onMarketOpen, is_main_page]); - - React.useEffect(() => { - if (!is_loading) setIsTimerLoading(false); - }, [is_loading, setIsTimerLoading]); - - let timer_components = ''; - - if (Object.keys(time_left).length) { - const hours = (time_left.days * 24 + time_left.hours).toString().padStart(2, '0'); - const minutes = time_left.minutes.toString().padStart(2, '0'); - const seconds = time_left.seconds.toString().padStart(2, '0'); - timer_components = `${hours}:${minutes}:${seconds}`; - } - - if (!(when_market_opens && timer_components)) return null; - - const { opening_time, days_offset } = when_market_opens; - let opening_time_banner = null; - if (opening_time) { - const formatted_opening_time = convertTimeFormat(opening_time); - const target_date = moment(new Date()).add(days_offset, 'days'); - const opening_date = target_date.format('DD MMM YYYY'); - const opening_day = target_date.format('dddd'); - opening_time_banner = ( - - ]} - values={{ - formatted_opening_time, - opening_day, - opening_date, - }} - /> - - ); - } - - if (is_loading) setLoading(false); - - return ( - - - - - {opening_time_banner} - - - - - {timer_components} - -
- - ); -}); - -MarketCountdownTimer.propTypes = { - is_main_page: PropTypes.bool, - setIsTimerLoading: PropTypes.func, - onMarketOpen: PropTypes.func, - symbol: PropTypes.string.isRequired, -}; - -export default MarketCountdownTimer; diff --git a/packages/trader/src/App/Components/Elements/market-countdown-timer.tsx b/packages/trader/src/App/Components/Elements/market-countdown-timer.tsx new file mode 100644 index 000000000000..03ec30b9ec2a --- /dev/null +++ b/packages/trader/src/App/Components/Elements/market-countdown-timer.tsx @@ -0,0 +1,224 @@ +import classNames from 'classnames'; +import React from 'react'; +import moment from 'moment'; +import { Text } from '@deriv/components'; +import { useIsMounted, WS, convertTimeFormat, isMarketClosed } from '@deriv/shared'; +import { Localize } from '@deriv/translations'; +import { observer } from '@deriv/stores'; +import { useTraderStore } from 'Stores/useTraderStores'; +import { TradingTimesRequest, TradingTimesResponse } from '@deriv/api-types'; + +type TMarketCountDownTimer = { + is_main_page: boolean; + setIsTimerLoading: React.Dispatch>; + onMarketOpen: ReturnType['prepareTradeStore']; + symbol: ReturnType['symbol']; +}; + +type TWhenMarketOpens = { + days_offset: number; + opening_time: string; + remaining_time_to_open: number; +}; + +// check market in coming 7 days +const days_to_check_before_exit = 7; + +const getTradingTimes = async (target_time: TradingTimesRequest['trading_times']) => { + const data = await WS.tradingTimes(target_time); + if (data.error) { + return { api_initial_load_error: data.error.message }; + } + return data; +}; +// eslint-disable-next-line consistent-return +const getSymbol = ( + target_symbol: string, + trading_times: NonNullable> +) => { + let symbol; + const { markets } = trading_times; + for (let i = 0; i < markets.length; i++) { + const { submarkets } = markets[i]; + if (submarkets) { + for (let j = 0; j < submarkets.length; j++) { + const { symbols } = submarkets[j]; + symbol = symbols?.find(item => item.symbol === target_symbol); + if (symbol !== undefined) return symbol; + } + } + } +}; + +const calculateTimeLeft = (remaining_time_to_open: number) => { + const difference = remaining_time_to_open - Date.now(); + return difference > 0 + ? { + days: Math.floor(difference / (1000 * 60 * 60 * 24)), + hours: Math.floor((difference / (1000 * 60 * 60)) % 24), + minutes: Math.floor((difference / 1000 / 60) % 60), + seconds: Math.floor((difference / 1000) % 60), + } + : {}; +}; + +const MarketCountdownTimer = observer( + ({ is_main_page, setIsTimerLoading, onMarketOpen, symbol }: TMarketCountDownTimer) => { + const { active_symbols } = useTraderStore(); + const isMounted = useIsMounted(); + const [when_market_opens, setWhenMarketOpens] = React.useState({} as TWhenMarketOpens); + const [time_left, setTimeLeft] = React.useState(calculateTimeLeft(when_market_opens?.remaining_time_to_open)); + const [is_loading, setLoading] = React.useState(true); + + React.useEffect(() => { + if (!is_main_page || (is_main_page && isMarketClosed(active_symbols, symbol))) { + setLoading(true); + // eslint-disable-next-line consistent-return + // @ts-expect-error there is no explict return type because of if statements + const whenMarketOpens = async (days_offset: number, target_symbol: string) => { + // days_offset is 0 for today, 1 for tomorrow, etc. + if (days_offset > days_to_check_before_exit) return {}; + let remaining_time_to_open; + const target_date = moment(new Date()).add(days_offset, 'days'); + const api_response = await getTradingTimes(target_date.format('YYYY-MM-DD')); + if (!api_response.api_initial_load_error) { + const returned_symbol = getSymbol(target_symbol, api_response.trading_times); + const open = returned_symbol?.times.open as string[]; + const close = returned_symbol?.times.close as string[]; + const is_closed_all_day = open?.length === 1 && open[0] === '--' && close[0] === '--'; + if (is_closed_all_day) { + // check tomorrow trading times + return whenMarketOpens(days_offset + 1, target_symbol); + } + const date_str = target_date.toISOString().substring(0, 11); + const getUTCDate = (hour: string) => new Date(`${date_str}${hour}Z`); + for (let i = 0; i < open?.length; i++) { + const diff = +getUTCDate(open[i]) - Date.now(); + if (diff > 0) { + remaining_time_to_open = +getUTCDate(open[i]); + if (isMounted() && target_symbol === symbol) { + return setWhenMarketOpens({ + days_offset, + opening_time: open[i], + remaining_time_to_open, + }); + } + } + } + whenMarketOpens(days_offset + 1, target_symbol); + } + }; + + whenMarketOpens(0, symbol); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [symbol]); + + React.useEffect(() => { + let timer: ReturnType; + if (when_market_opens?.remaining_time_to_open) { + timer = setTimeout(() => { + setTimeLeft(calculateTimeLeft(when_market_opens.remaining_time_to_open)); + if (+new Date(when_market_opens.remaining_time_to_open) - +new Date() < 1000) { + setLoading(true); + if (is_main_page) onMarketOpen(false); + } + }, 1000); + } + return () => { + if (timer) { + clearTimeout(timer); + } + }; + }, [time_left, when_market_opens, onMarketOpen, is_main_page]); + + React.useEffect(() => { + if (!is_loading) setIsTimerLoading(false); + }, [is_loading, setIsTimerLoading]); + + let timer_components = ''; + + if (Object.keys(time_left).length) { + const hours = (Number(time_left.days) * 24 + Number(time_left.hours)).toString().padStart(2, '0'); + const minutes = Number(time_left.minutes).toString().padStart(2, '0'); + const seconds = Number(time_left.seconds).toString().padStart(2, '0'); + timer_components = `${hours}:${minutes}:${seconds}`; + } + + if (!(when_market_opens && timer_components)) return null; + + const { opening_time, days_offset } = when_market_opens; + let opening_time_banner = null; + if (opening_time) { + const formatted_opening_time = convertTimeFormat(opening_time); + const target_date = moment(new Date()).add(days_offset, 'days'); + const opening_date = target_date.format('DD MMM YYYY'); + const opening_day = target_date.format('dddd'); + opening_time_banner = ( + + ]} + values={{ + formatted_opening_time, + opening_day, + opening_date, + }} + /> + + ); + } + + if (is_loading) setLoading(false); + + return ( + + + + + {opening_time_banner} + + + + + {timer_components} + +
+ + ); + } +); + +export default MarketCountdownTimer; diff --git a/packages/trader/src/App/Components/Elements/market-is-closed-overlay.jsx b/packages/trader/src/App/Components/Elements/market-is-closed-overlay.tsx similarity index 68% rename from packages/trader/src/App/Components/Elements/market-is-closed-overlay.jsx rename to packages/trader/src/App/Components/Elements/market-is-closed-overlay.tsx index a68ab239d67c..e2c58ab7180b 100644 --- a/packages/trader/src/App/Components/Elements/market-is-closed-overlay.jsx +++ b/packages/trader/src/App/Components/Elements/market-is-closed-overlay.tsx @@ -1,14 +1,29 @@ import classNames from 'classnames'; import React from 'react'; -import PropTypes from 'prop-types'; import { Button, Text } from '@deriv/components'; import { localize, Localize } from '@deriv/translations'; -import MarketCountdownTimer from './market-countdown-timer.jsx'; +import MarketCountdownTimer from './market-countdown-timer'; +import { useStore } from '@deriv/stores'; +import { useTraderStore } from 'Stores/useTraderStores.js'; -const MarketIsClosedOverlay = ({ is_eu, is_synthetics_trading_market_available, onClick, onMarketOpen, symbol }) => { +type TMarketIsClosedOverlay = { + is_eu: ReturnType['client']['is_eu']; + is_synthetics_trading_market_available: ReturnType['is_synthetics_trading_market_available']; + onClick: () => void; + onMarketOpen: React.ComponentProps['onMarketOpen']; + symbol: ReturnType['symbol']; +}; + +const MarketIsClosedOverlay = ({ + is_eu, + is_synthetics_trading_market_available, + onClick, + onMarketOpen, + symbol, +}: TMarketIsClosedOverlay) => { const [is_timer_loading, setIsTimerLoading] = React.useState(true); - let message = ( + let message: JSX.Element | null = ( ); let btn_lbl = localize('Try Synthetic Indices'); @@ -45,12 +60,4 @@ const MarketIsClosedOverlay = ({ is_eu, is_synthetics_trading_market_available, ); }; -MarketIsClosedOverlay.propTypes = { - is_eu: PropTypes.bool, - is_synthetics_trading_market_available: PropTypes.bool, - onClick: PropTypes.func, - onMarketOpen: PropTypes.func, - symbol: PropTypes.string, -}; - export default MarketIsClosedOverlay; diff --git a/packages/trader/src/App/Containers/ProgressSliderStream/__tests__/progress-slider-stream.spec.jsx b/packages/trader/src/App/Containers/ProgressSliderStream/__tests__/progress-slider-stream.spec.jsx deleted file mode 100644 index 76db96aadf64..000000000000 --- a/packages/trader/src/App/Containers/ProgressSliderStream/__tests__/progress-slider-stream.spec.jsx +++ /dev/null @@ -1,44 +0,0 @@ -import React from 'react'; -import { screen, render } from '@testing-library/react'; -import { mockStore } from '@deriv/stores'; -import TraderProviders from '../../../../trader-providers'; -import ProgressSliderStream from '../progress-slider-stream'; - -jest.mock('@deriv/components', () => ({ - ...jest.requireActual('@deriv/components'), - ProgressSlider: () =>
Mocked Progress Slider
, -})); - -const contract_info = { - contract_type: 'TEST', - date_expiry: 1222222224, - date_start: 1222222222, - tick_count: 2, - tick_stream: [ - { epoch: 1, tick: 1, tick_display_value: '300' }, - { epoch: 2, tick: 2, tick_display_value: '302' }, - ], -}; - -describe('', () => { - const mockProgressSliderStream = (mocked_store, contract_info = null) => { - return ( - - - - ); - }; - - it('should not render if contract_info is falsy', () => { - const mock_root_store = mockStore({}); - render(mockProgressSliderStream(mock_root_store)); - - expect(screen.queryByText('Mocked Progress Slider')).not.toBeInTheDocument(); - }); - it('should render if contract_info was passed in props', () => { - const mock_root_store = mockStore({}); - render(mockProgressSliderStream(mock_root_store, contract_info)); - - expect(screen.getByText('Mocked Progress Slider')).toBeInTheDocument(); - }); -}); diff --git a/packages/trader/src/App/Containers/ProgressSliderStream/index.js b/packages/trader/src/App/Containers/ProgressSliderStream/index.js deleted file mode 100644 index 657c6ba7065a..000000000000 --- a/packages/trader/src/App/Containers/ProgressSliderStream/index.js +++ /dev/null @@ -1 +0,0 @@ -export default from './progress-slider-stream.jsx'; diff --git a/packages/trader/src/App/Containers/ProgressSliderStream/progress-slider-stream.jsx b/packages/trader/src/App/Containers/ProgressSliderStream/progress-slider-stream.jsx deleted file mode 100644 index 34409af133c9..000000000000 --- a/packages/trader/src/App/Containers/ProgressSliderStream/progress-slider-stream.jsx +++ /dev/null @@ -1,34 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import { ProgressSlider } from '@deriv/components'; -import { getCardLabels, getCurrentTick } from '@deriv/shared'; -import { observer, useStore } from '@deriv/stores'; - -const ProgressSliderStream = observer(({ contract_info }) => { - const { common, portfolio } = useStore(); - const { server_time } = common; - const { is_loading } = portfolio; - - if (!contract_info) { - return
; - } - const current_tick = contract_info.tick_count && getCurrentTick(contract_info); - - return ( - - ); -}); - -ProgressSliderStream.propTypes = { - contract_info: PropTypes.object, -}; - -export default ProgressSliderStream; diff --git a/packages/trader/src/App/Containers/populate-header.jsx b/packages/trader/src/App/Containers/populate-header.jsx index 74127c7c9c33..6cb78ddd14cc 100644 --- a/packages/trader/src/App/Containers/populate-header.jsx +++ b/packages/trader/src/App/Containers/populate-header.jsx @@ -1,15 +1,14 @@ import React from 'react'; -import TogglePositionsMobile from 'App/Components/Elements/TogglePositions/toggle-positions-mobile.jsx'; +import TogglePositionsMobile from 'App/Components/Elements/TogglePositions/toggle-positions-mobile'; import { filterByContractType } from 'App/Components/Elements/PositionsDrawer/helpers'; import { useTraderStore } from 'Stores/useTraderStores'; import { observer, useStore } from '@deriv/stores'; import { TURBOS, VANILLALONG, isTurbosContract, isVanillaContract } from '@deriv/shared'; const PopulateHeader = observer(() => { - const { portfolio, ui, client } = useStore(); + const { portfolio, client } = useStore(); const { symbol, contract_type: trade_contract_type } = useTraderStore(); const { currency: positions_currency } = client; - const { disableApp, enableApp } = ui; const { active_positions_count, all_positions: positions, @@ -37,12 +36,10 @@ const PopulateHeader = observer(() => { return ( diff --git a/packages/trader/src/App/Containers/trade-footer-extensions.jsx b/packages/trader/src/App/Containers/trade-footer-extensions.tsx similarity index 82% rename from packages/trader/src/App/Containers/trade-footer-extensions.jsx rename to packages/trader/src/App/Containers/trade-footer-extensions.tsx index 702950f5ebca..8643c94f5987 100644 --- a/packages/trader/src/App/Containers/trade-footer-extensions.jsx +++ b/packages/trader/src/App/Containers/trade-footer-extensions.tsx @@ -1,11 +1,11 @@ -import PropTypes from 'prop-types'; import React from 'react'; -import { withRouter } from 'react-router-dom'; +import { RouteComponentProps, withRouter } from 'react-router-dom'; import { routes } from '@deriv/shared'; -import TogglePositions from 'App/Components/Elements/TogglePositions'; +import TogglePositions from '../Components/Elements/TogglePositions/toggle-positions'; import { observer, useStore } from '@deriv/stores'; -const TradeFooterExtensions = observer(() => { +const TradeFooterExtensions = observer((props: RouteComponentProps) => { + const { location } = props; const { client, portfolio, ui } = useStore(); const { is_logged_in } = client; const { active_positions_count } = portfolio; @@ -40,8 +40,4 @@ const TradeFooterExtensions = observer(() => { return null; }); -TradeFooterExtensions.propTypes = { - location: PropTypes.object, -}; - export default withRouter(TradeFooterExtensions); diff --git a/packages/trader/src/App/app.tsx b/packages/trader/src/App/app.tsx index a2bfedb085fb..bbafd8fa512f 100644 --- a/packages/trader/src/App/app.tsx +++ b/packages/trader/src/App/app.tsx @@ -2,7 +2,7 @@ import React from 'react'; import Loadable from 'react-loadable'; import Routes from 'App/Containers/Routes/routes.jsx'; import TradeHeaderExtensions from 'App/Containers/trade-header-extensions'; -import TradeFooterExtensions from 'App/Containers/trade-footer-extensions.jsx'; +import TradeFooterExtensions from 'App/Containers/trade-footer-extensions'; import TradeSettingsExtensions from 'App/Containers/trade-settings-extensions'; import { NetworkStatusToastErrorPopup } from 'Modules/Trading/Containers/toast-popup'; import initStore from './init-store'; diff --git a/packages/trader/src/Modules/Contract/Components/Digits/digits.jsx b/packages/trader/src/Modules/Contract/Components/Digits/digits.tsx similarity index 66% rename from packages/trader/src/Modules/Contract/Components/Digits/digits.jsx rename to packages/trader/src/Modules/Contract/Components/Digits/digits.tsx index 8436d533dde2..e963a30c2ee7 100644 --- a/packages/trader/src/Modules/Contract/Components/Digits/digits.jsx +++ b/packages/trader/src/Modules/Contract/Components/Digits/digits.tsx @@ -1,13 +1,59 @@ import classNames from 'classnames'; -import PropTypes from 'prop-types'; import React from 'react'; import { toJS } from 'mobx'; import { DesktopWrapper, MobileWrapper, Popover, Text } from '@deriv/components'; -import { getMarketNamesMap, isMobile, useIsMounted, isContractElapsed } from '@deriv/shared'; +import { + getMarketNamesMap, + isMobile, + useIsMounted, + isContractElapsed, + TContractStore, + TTickSpotData, +} from '@deriv/shared'; import { localize, Localize } from '@deriv/translations'; import { Bounce, SlideIn } from 'App/Components/Animations'; import { DigitSpot, LastDigitPrediction } from '../LastDigitPrediction'; import 'Sass/app/modules/contract/digits.scss'; +import { useTraderStore } from 'Stores/useTraderStores'; + +type TTraderStore = ReturnType; +type TOnChangeStatus = { status: string | null | undefined; current_tick: number | null }; +type TOnLastDigitSpot = { + spot: string | null; + is_lost?: boolean; + is_selected_winning: boolean; + is_latest: boolean; + is_won?: boolean; +}; + +type TDigitsWrapper = TDigits & { + onChangeStatus?: (params: TOnChangeStatus) => void; + onLastDigitSpot?: (params: TOnLastDigitSpot) => void; +}; +type TDigits = Pick & { + digits_array?: number[]; + display_status?: TContractStore['display_status']; + is_digit_contract?: TContractStore['is_digit_contract']; + is_ended?: TContractStore['is_ended']; + is_trade_page?: boolean; + onDigitChange?: TTraderStore['onChange']; + selected_digit?: TTraderStore['last_digit']; + trade_type?: TTraderStore['contract_type']; + tick?: TTickSpotData; + underlying: TTraderStore['symbol']; +}; +type TTickStream = NonNullable[number]; +type TTickData = + | TTickSpotData + | null + | undefined + | { + ask: TTickStream['tick']; + bid: TTickStream['tick']; + current_tick: number; + epoch: TTickStream['epoch']; + pip_size?: number; + }; const DigitsWrapper = ({ contract_info, @@ -22,9 +68,9 @@ const DigitsWrapper = ({ trade_type, onChangeStatus, ...props -}) => { +}: TDigitsWrapper) => { const has_contract = contract_info.date_start; - let tick = props.tick; + let tick: TTickData = props.tick; const is_tick_ready = is_trade_page ? !!tick : true; const is_contract_elapsed = is_trade_page ? isContractElapsed(contract_info, tick) : false; @@ -35,13 +81,13 @@ const DigitsWrapper = ({ if (has_contract && !is_contract_elapsed) { tick = null; const tick_stream = contract_info.tick_stream; - if (tick_stream && tick_stream.length) { + if (tick_stream?.length) { const t = toJS(tick_stream.slice(-1)[0]); tick = { ask: t.tick, bid: t.tick, epoch: t.epoch, - pip_size: t.tick_display_value.split('.')[1].length, + pip_size: t.tick_display_value?.split('.')[1].length, current_tick: tick_stream.length, }; } @@ -49,7 +95,7 @@ const DigitsWrapper = ({ React.useEffect(() => { if (onChangeStatus) { - onChangeStatus({ status, current_tick: tick ? tick.current_tick : null }); + onChangeStatus({ status, current_tick: tick && 'current_tick' in tick ? tick.current_tick : null }); } }, [tick, is_trade_page, display_status, onChangeStatus, status]); @@ -59,15 +105,15 @@ const DigitsWrapper = ({ // i.e - 40px + 6px left and 6px right padding/margin = 52 dimension={isMobile() ? 64 : 52} has_entry_spot={!!contract_info.entry_tick} - barrier={!is_contract_elapsed && is_tick_ready ? +contract_info.barrier : null} - contract_type={!is_contract_elapsed && is_tick_ready ? contract_info.contract_type : null} + barrier={!is_contract_elapsed && is_tick_ready ? Number(contract_info.barrier) : null} + contract_type={!is_contract_elapsed && is_tick_ready ? contract_info.contract_type : ''} digits={digits_array} digits_info={!is_contract_elapsed && is_tick_ready ? digits_info : {}} is_digit_contract={is_digit_contract} is_ended={is_ended} is_trade_page={is_trade_page} - status={status} - tick={tick} + status={status as React.ComponentProps['status']} + tick={tick as React.ComponentProps['tick']} trade_type={trade_type} onDigitChange={onDigitChange} selected_digit={selected_digit} @@ -76,28 +122,26 @@ const DigitsWrapper = ({ ); }; -const Digits = React.memo(props => { - const [status, setStatus] = React.useState(); - const [current_tick, setCurrentTick] = React.useState(); - const [spot, setSpot] = React.useState(); - const [is_selected_winning, setIsSelectedWinning] = React.useState(); - const [is_latest, setIsLatest] = React.useState(); - const [is_won, setIsWon] = React.useState(); - const [is_lost, setIsLost] = React.useState(); +const Digits = React.memo((props: TDigits) => { + const [status, setStatus] = React.useState(); + const [current_tick, setCurrentTick] = React.useState(); + const [spot, setSpot] = React.useState(); + const [is_selected_winning, setIsSelectedWinning] = React.useState(); + const [is_won, setIsWon] = React.useState(); + const [is_lost, setIsLost] = React.useState(); const isMounted = useIsMounted(); const { contract_info, digits_array, is_digit_contract, is_trade_page, underlying } = props; - const onChangeStatus = params => { + const onChangeStatus = (params: TOnChangeStatus) => { setStatus(params.status); setCurrentTick(params.current_tick); }; - const onLastDigitSpot = params => { + const onLastDigitSpot = (params: TOnLastDigitSpot) => { setSpot(params.spot); setIsLost(params.is_lost); setIsSelectedWinning(params.is_selected_winning); - setIsLatest(params.is_latest); setIsWon(params.is_won); }; @@ -105,7 +149,8 @@ const Digits = React.memo(props => { const underlying_name = is_trade_page ? underlying : contract_info.underlying; return localize('Last digit stats for latest 1000 ticks for {{underlying_name}}', { - underlying_name: getMarketNamesMap()[underlying_name.toUpperCase()], + underlying_name: + getMarketNamesMap()[underlying_name?.toUpperCase() as keyof ReturnType], }); }; @@ -151,7 +196,6 @@ const Digits = React.memo(props => { current_spot={spot} is_lost={is_lost} is_selected_winning={is_selected_winning} - is_visible={!!(is_latest && spot)} is_won={is_won} /> @@ -164,18 +208,4 @@ const Digits = React.memo(props => { Digits.displayName = 'Digits'; -Digits.propTypes = { - contract_info: PropTypes.object, - digits_array: PropTypes.array, - digits_info: PropTypes.object, - display_status: PropTypes.string, - is_digit_contract: PropTypes.bool, - is_ended: PropTypes.bool, - is_trade_page: PropTypes.bool, - trade_type: PropTypes.string, - onDigitChange: PropTypes.func, - selected_digit: PropTypes.number, - underlying: PropTypes.string, -}; - export default Digits; diff --git a/packages/trader/src/Modules/Contract/Components/Digits/index.js b/packages/trader/src/Modules/Contract/Components/Digits/index.js index d46e4208466c..5a177415c03a 100644 --- a/packages/trader/src/Modules/Contract/Components/Digits/index.js +++ b/packages/trader/src/Modules/Contract/Components/Digits/index.js @@ -1,3 +1,3 @@ -import Digits from './digits.jsx'; +import Digits from './digits'; export default Digits; diff --git a/packages/trader/src/Modules/Contract/Components/LastDigitPrediction/digit-display.tsx b/packages/trader/src/Modules/Contract/Components/LastDigitPrediction/digit-display.tsx index 856cfe2e4aec..482d22dc692f 100644 --- a/packages/trader/src/Modules/Contract/Components/LastDigitPrediction/digit-display.tsx +++ b/packages/trader/src/Modules/Contract/Components/LastDigitPrediction/digit-display.tsx @@ -20,7 +20,7 @@ type TDigitDisplay = Pick, 'is_lost' | 'i digit: number | null; spot: string | null; }; - selected_digit: number | boolean; + selected_digit?: number; status: ProposalOpenContract['status']; stats?: number | null; value: number; diff --git a/packages/trader/src/Modules/Contract/Components/LastDigitPrediction/digit-spot.tsx b/packages/trader/src/Modules/Contract/Components/LastDigitPrediction/digit-spot.tsx index 18ee62ff7b95..0f8720f4314f 100644 --- a/packages/trader/src/Modules/Contract/Components/LastDigitPrediction/digit-spot.tsx +++ b/packages/trader/src/Modules/Contract/Components/LastDigitPrediction/digit-spot.tsx @@ -3,7 +3,7 @@ import React from 'react'; import { Text } from '@deriv/components'; type TDigitSpot = { - current_spot: string | null; + current_spot?: string | null; is_selected_winning?: boolean; is_lost?: boolean; is_won?: boolean; diff --git a/packages/trader/src/Modules/Contract/Components/LastDigitPrediction/last-digit-prediction.tsx b/packages/trader/src/Modules/Contract/Components/LastDigitPrediction/last-digit-prediction.tsx index b8dbabe9bbb2..bca5ba854ec4 100644 --- a/packages/trader/src/Modules/Contract/Components/LastDigitPrediction/last-digit-prediction.tsx +++ b/packages/trader/src/Modules/Contract/Components/LastDigitPrediction/last-digit-prediction.tsx @@ -11,13 +11,13 @@ type TLastDigitPrediction = Pick< 'barrier' | 'is_digit_contract' | 'has_entry_spot' | 'onLastDigitSpot' > & { contract_type?: string; - digits: number[]; + digits?: number[]; digits_info: { [key: string]: { digit: number; spot: string } }; dimension: number; is_ended?: boolean; - is_trade_page: boolean; - onDigitChange: (event: { target: { name: string; value: number } }) => void; - selected_digit: number | boolean; + is_trade_page?: boolean; + onDigitChange?: (event: { target: { name: string; value: number } }) => void; + selected_digit?: number; status?: ProposalOpenContract['status']; tick?: TicksStreamResponse['tick']; trade_type?: string; @@ -138,7 +138,7 @@ const LastDigitPrediction = ({ value={idx} onLastDigitSpot={onLastDigitSpot} onSelect={isSelectableDigitType() ? handleSelect : null} - selected_digit={isSelectableDigitType() ? selected_digit : false} + selected_digit={isSelectableDigitType() ? selected_digit : undefined} /> ))} diff --git a/packages/trader/src/Modules/Trading/Components/Elements/__tests__/purchase-button.spec.tsx b/packages/trader/src/Modules/Trading/Components/Elements/__tests__/purchase-button.spec.tsx index 6fbdf17615dd..f8d1f5e18593 100644 --- a/packages/trader/src/Modules/Trading/Components/Elements/__tests__/purchase-button.spec.tsx +++ b/packages/trader/src/Modules/Trading/Components/Elements/__tests__/purchase-button.spec.tsx @@ -31,7 +31,7 @@ const default_mocked_props = { should_fade: false, setPurchaseState: jest.fn(), type: 'VANILLALONGCALL', -}; +} as unknown as React.ComponentProps; jest.mock('@deriv/shared', () => ({ ...jest.requireActual('@deriv/shared'), diff --git a/packages/trader/src/Modules/Trading/Components/Elements/__tests__/purchase-fieldset.spec.tsx b/packages/trader/src/Modules/Trading/Components/Elements/__tests__/purchase-fieldset.spec.tsx index aad213c17fc1..f48114e67eb9 100644 --- a/packages/trader/src/Modules/Trading/Components/Elements/__tests__/purchase-fieldset.spec.tsx +++ b/packages/trader/src/Modules/Trading/Components/Elements/__tests__/purchase-fieldset.spec.tsx @@ -33,7 +33,7 @@ const default_mocked_props = { purchased_states_arr: [true, false], setPurchaseState: jest.fn(), type: '', -}; +} as unknown as React.ComponentProps; jest.mock('@deriv/shared', () => ({ ...jest.requireActual('@deriv/shared'), diff --git a/packages/trader/src/Modules/Trading/Components/Elements/mobile-widget.jsx b/packages/trader/src/Modules/Trading/Components/Elements/mobile-widget.jsx index 972fddcaaeb1..3cc03e313cbc 100644 --- a/packages/trader/src/Modules/Trading/Components/Elements/mobile-widget.jsx +++ b/packages/trader/src/Modules/Trading/Components/Elements/mobile-widget.jsx @@ -3,7 +3,7 @@ import { Money } from '@deriv/components'; import { localize, Localize } from '@deriv/translations'; import { getExpiryType, getDurationMinMaxValues, getLocalizedBasis } from '@deriv/shared'; import { MultiplierAmountWidget } from 'Modules/Trading/Components/Form/TradeParams/Multiplier/widgets.jsx'; -import TradeParamsModal from '../../Containers/trade-params-mobile.jsx'; +import TradeParamsModal from '../../Containers/trade-params-mobile'; import { observer, useStore } from '@deriv/stores'; import { useTraderStore } from 'Stores/useTraderStores'; diff --git a/packages/trader/src/Modules/Trading/Components/Elements/payout-per-point-mobile.tsx b/packages/trader/src/Modules/Trading/Components/Elements/payout-per-point-mobile.tsx index f2a409658087..fd24be61089d 100644 --- a/packages/trader/src/Modules/Trading/Components/Elements/payout-per-point-mobile.tsx +++ b/packages/trader/src/Modules/Trading/Components/Elements/payout-per-point-mobile.tsx @@ -5,22 +5,10 @@ import Fieldset from 'App/Components/Form/fieldset'; import { observer } from '@deriv/stores'; import { getContractSubtype, isVanillaContract } from '@deriv/shared'; import { useTraderStore } from 'Stores/useTraderStores'; +import { TProposalTypeInfo } from 'Types'; type TProposalInfo = { - [key: string]: { - has_error?: boolean; - id: string; - has_increased?: boolean; - message?: string; - cancellation?: { - ask_price: number; - date_expiry: number; - }; - growth_rate?: number; - obj_contract_basis?: Record<'text' | 'value', string>; - returns?: string; - stake: string; - }; + [key: string]: TProposalTypeInfo; }; const PayoutPerPointMobile = observer(() => { diff --git a/packages/trader/src/Modules/Trading/Components/Elements/purchase-button.tsx b/packages/trader/src/Modules/Trading/Components/Elements/purchase-button.tsx index 2d39b67f94e8..5f24ead4faeb 100644 --- a/packages/trader/src/Modules/Trading/Components/Elements/purchase-button.tsx +++ b/packages/trader/src/Modules/Trading/Components/Elements/purchase-button.tsx @@ -3,11 +3,11 @@ import React from 'react'; import { DesktopWrapper, MobileWrapper, Money, IconTradeTypes, Text } from '@deriv/components'; import ContractInfo from 'Modules/Trading/Components/Form/Purchase/contract-info'; import { getContractTypeDisplay } from '@deriv/shared'; -import { TProposalTypeInfo } from 'Types'; +import { TProposalTypeInfo, TTradeStore } from 'Types'; type TPurchaseButton = { basis: string; - buy_info: { error?: string }; + buy_info: TTradeStore['purchase_info']; currency: string; growth_rate: number; has_deal_cancellation: boolean; @@ -164,6 +164,7 @@ const PurchaseButton = ({ is_loading={is_loading} is_multiplier={is_multiplier} is_turbos={is_turbos} + is_vanilla={is_vanilla} should_fade={should_fade} proposal_info={info} type={type} diff --git a/packages/trader/src/Modules/Trading/Components/Elements/purchase-fieldset.tsx b/packages/trader/src/Modules/Trading/Components/Elements/purchase-fieldset.tsx index 9dd0c883dfb5..b45321d42b1f 100644 --- a/packages/trader/src/Modules/Trading/Components/Elements/purchase-fieldset.tsx +++ b/packages/trader/src/Modules/Trading/Components/Elements/purchase-fieldset.tsx @@ -5,11 +5,11 @@ import Fieldset from 'App/Components/Form/fieldset'; import ContractInfo from 'Modules/Trading/Components/Form/Purchase/contract-info'; import PurchaseButton from 'Modules/Trading/Components/Elements/purchase-button'; import CancelDealInfo from '../Form/Purchase/cancel-deal-info'; -import { TProposalTypeInfo } from 'Types'; +import { TProposalTypeInfo, TTradeStore } from 'Types'; type TPurchaseFieldset = { basis: string; - buy_info: { error?: string }; + buy_info: TTradeStore['purchase_info']; currency: string; growth_rate: number; has_cancellation: boolean; @@ -119,6 +119,8 @@ const PurchaseFieldset = ({ is_vanilla={is_vanilla} should_fade={should_fade} type={type} + is_accumulator={is_accumulator} + growth_rate={growth_rate} /> )}
{ }); it('should render component when click on ', () => { - render(); + render(); const dt_contract_dropdown = screen.getByTestId('dt_contract_dropdown'); fireEvent.click(dt_contract_dropdown); diff --git a/packages/trader/src/Modules/Trading/Components/Form/ContractType/__tests__/contract-type-widget.spec.tsx b/packages/trader/src/Modules/Trading/Components/Form/ContractType/__tests__/contract-type-widget.spec.tsx index 4e81e7dc06b2..a0d0f9bb48b7 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/ContractType/__tests__/contract-type-widget.spec.tsx +++ b/packages/trader/src/Modules/Trading/Components/Form/ContractType/__tests__/contract-type-widget.spec.tsx @@ -72,7 +72,7 @@ describe('', () => { }; it('should render component when click on ', () => { - render(); + render(); expect(screen.getByTestId('dt_contract_widget')).toBeInTheDocument(); }); }); diff --git a/packages/trader/src/Modules/Trading/Components/Form/ContractType/contract-type-widget.tsx b/packages/trader/src/Modules/Trading/Components/Form/ContractType/contract-type-widget.tsx index 7f5224568e3a..ec166eee8c31 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/ContractType/contract-type-widget.tsx +++ b/packages/trader/src/Modules/Trading/Components/Form/ContractType/contract-type-widget.tsx @@ -7,10 +7,15 @@ import { getContractTypeCategoryIcons, findContractCategory } from '../../../Hel import { TContractCategory, TContractType, TList } from './types'; type TContractTypeWidget = { - name?: string; + name: string; value: TContractType['value']; list: TContractCategory[]; - onChange: (event: DeepPartial>) => void; + onChange: (e: { + target: { + name: string; + value: unknown; + }; + }) => Promise; languageChanged?: boolean; }; diff --git a/packages/trader/src/Modules/Trading/Components/Form/Purchase/cancel-deal-info.tsx b/packages/trader/src/Modules/Trading/Components/Form/Purchase/cancel-deal-info.tsx index febf2f6cc8de..3010d38f6a34 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/Purchase/cancel-deal-info.tsx +++ b/packages/trader/src/Modules/Trading/Components/Form/Purchase/cancel-deal-info.tsx @@ -13,15 +13,12 @@ const CancelDealInfo = observer(({ proposal_info }: { proposal_info: TProposalTy 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 && - ((el_height > 21 && isDesktop()) || ((el_height > 21 || getDecimalPlaces(currency) > 2) && isMobile())) - ) { + const el_height = Number(ref.current.parentElement?.clientHeight); + if ((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.tsx similarity index 77% rename from packages/trader/src/Modules/Trading/Components/Form/Purchase/contract-info.jsx rename to packages/trader/src/Modules/Trading/Components/Form/Purchase/contract-info.tsx index 209a7d98a52e..6c3a53f41e0e 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/Purchase/contract-info.jsx +++ b/packages/trader/src/Modules/Trading/Components/Form/Purchase/contract-info.tsx @@ -1,44 +1,23 @@ import classNames from 'classnames'; -import PropTypes from 'prop-types'; import React from 'react'; -import { DesktopWrapper, Icon, MobileWrapper, Money, Popover, Text } from '@deriv/components'; +import { DesktopWrapper, MobileWrapper, Money, Popover, Text } from '@deriv/components'; import { Localize, localize } from '@deriv/translations'; import { getContractSubtype, getCurrencyDisplayCode, getLocalizedBasis, getGrowthRatePercentage } from '@deriv/shared'; +import { useTraderStore } from 'Stores/useTraderStores'; import CancelDealInfo from './cancel-deal-info'; +import ValueMovement from './value-movement'; +import { TProposalTypeInfo } from 'Types'; -export const ValueMovement = ({ - has_error_or_not_loaded, - proposal_info, - currency, - has_increased, - is_turbos, - is_vanilla, - value, - show_currency = true, -}) => ( -
-
- {!has_error_or_not_loaded && ( - - )} -
-
- {!has_error_or_not_loaded && has_increased !== null && has_increased ? ( - - ) : ( - - )} -
-
-); +type TContractInfo = Pick< + ReturnType, + 'basis' | 'growth_rate' | 'is_accumulator' | 'is_turbos' | 'is_vanilla' | 'is_multiplier' | 'currency' +> & { + has_increased?: boolean | null; + is_loading: boolean; + proposal_info: TProposalTypeInfo; + should_fade: boolean; + type: string; +}; const ContractInfo = ({ basis, @@ -53,7 +32,7 @@ const ContractInfo = ({ should_fade, proposal_info, type, -}) => { +}: TContractInfo) => { const localized_basis = getLocalizedBasis(); const stakeOrPayout = () => { switch (basis) { @@ -72,7 +51,7 @@ const ContractInfo = ({ }; const has_error_or_not_loaded = proposal_info.has_error || !proposal_info.id; - const basis_text = has_error_or_not_loaded ? stakeOrPayout() : proposal_info.obj_contract_basis.text; + const basis_text = has_error_or_not_loaded ? stakeOrPayout() : proposal_info?.obj_contract_basis?.text || ''; const { message, obj_contract_basis, stake } = proposal_info; const setHintMessage = () => { @@ -182,19 +161,4 @@ const ContractInfo = ({ ); }; -ContractInfo.propTypes = { - basis: PropTypes.string, - currency: PropTypes.string, - growth_rate: PropTypes.number, - has_increased: PropTypes.bool, - is_accumulator: PropTypes.bool, - is_multiplier: PropTypes.bool, - is_turbos: PropTypes.bool, - is_vanilla: PropTypes.bool, - is_loading: PropTypes.bool, - proposal_info: PropTypes.object, - should_fade: PropTypes.bool, - type: PropTypes.string, -}; - export default ContractInfo; diff --git a/packages/trader/src/Modules/Trading/Components/Form/Purchase/value-movement.tsx b/packages/trader/src/Modules/Trading/Components/Form/Purchase/value-movement.tsx new file mode 100644 index 000000000000..ff65e52dfc75 --- /dev/null +++ b/packages/trader/src/Modules/Trading/Components/Form/Purchase/value-movement.tsx @@ -0,0 +1,50 @@ +import classNames from 'classnames'; +import React from 'react'; +import { Icon, Money } from '@deriv/components'; +import ContractInfo from './contract-info'; + +type TValueMovement = Partial< + Pick< + React.ComponentProps, + 'is_turbos' | 'is_vanilla' | 'currency' | 'has_increased' | 'proposal_info' + > +> & { + has_error_or_not_loaded: boolean; + value?: number | string; + show_currency?: boolean; +}; +const ValueMovement = ({ + has_error_or_not_loaded, + proposal_info, + currency, + has_increased, + is_turbos = false, + is_vanilla = false, + value, + show_currency = true, +}: TValueMovement) => ( +
+
+ {!has_error_or_not_loaded && ( + + )} +
+
+ {!has_error_or_not_loaded && has_increased !== null && has_increased ? ( + + ) : ( + + )} +
+
+); + +export default ValueMovement; diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Duration/duration-wrapper.jsx b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Duration/duration-wrapper.jsx index 8ef7596439f6..eb20274ae761 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Duration/duration-wrapper.jsx +++ b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Duration/duration-wrapper.jsx @@ -1,6 +1,4 @@ -import PropTypes from 'prop-types'; import React from 'react'; -import { PropTypes as MobxPropTypes } from 'mobx-react'; import { getDurationMinMaxValues } from '@deriv/shared'; import Duration from './duration.jsx'; import { observer, useStore } from '@deriv/stores'; @@ -195,18 +193,4 @@ const DurationWrapper = observer(() => { ); }); -DurationWrapper.propTypes = { - duration_d: PropTypes.number, - duration_h: PropTypes.number, - duration_m: PropTypes.number, - duration_s: PropTypes.number, - duration_unit: PropTypes.string, - duration_units_list: MobxPropTypes.arrayOrObservableArray, - getDurationFromUnit: PropTypes.func, - is_minimized: PropTypes.bool, - sessions: MobxPropTypes.arrayOrObservableArray, - start_time: PropTypes.string, - symbol: PropTypes.string, -}; - export default DurationWrapper; diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/allow-equals.jsx b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/allow-equals.tsx similarity index 72% rename from packages/trader/src/Modules/Trading/Components/Form/TradeParams/allow-equals.jsx rename to packages/trader/src/Modules/Trading/Components/Form/TradeParams/allow-equals.tsx index 0d3ad0614154..448e3419ba38 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/allow-equals.jsx +++ b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/allow-equals.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import PropTypes from 'prop-types'; import { Popover, Checkbox } from '@deriv/components'; import { localize } from '@deriv/translations'; import { @@ -7,6 +6,22 @@ import { hasDurationForCallPutEqual, isRiseFallEqual, } from 'Stores/Modules/Trading/Helpers/allow-equals'; +import { useTraderStore } from 'Stores/useTraderStores'; + +type TTradeStore = Pick< + ReturnType, + | 'contract_start_type' + | 'contract_type' + | 'contract_types_list' + | 'duration_unit' + | 'expiry_type' + | 'has_equals_only' +>; + +type TAllowEquals = TTradeStore & { + onChange: (e: { target: { name: string; value: number } }) => Promise; + value: number; +}; const AllowEquals = ({ contract_start_type, @@ -17,7 +32,7 @@ const AllowEquals = ({ onChange, value, has_equals_only, -}) => { +}: TAllowEquals) => { const has_callputequal_duration = hasDurationForCallPutEqual( contract_types_list, duration_unit, @@ -28,10 +43,12 @@ const AllowEquals = ({ const has_allow_equals = isRiseFallEqual(contract_type) && (has_callputequal_duration || expiry_type === 'endtime') && has_callputequal; - const changeValue = e => { + const changeValue: React.ComponentProps['onChange'] = e => { e.persist(); - const { name, checked } = e.target; - onChange({ target: { name, value: Number(checked) } }); + if ('checked' in e.target) { + const { name, checked } = e.target; + onChange({ target: { name, value: Number(checked) } }); + } }; return ( @@ -61,15 +78,4 @@ const AllowEquals = ({ ); }; -AllowEquals.propTypes = { - contract_start_type: PropTypes.string, - contract_type: PropTypes.string, - contract_types_list: PropTypes.object, - duration_unit: PropTypes.string, - expiry_type: PropTypes.string, - has_equals_only: PropTypes.bool, - onChange: PropTypes.func, - value: PropTypes.number, -}; - export default AllowEquals; diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount-mobile.jsx b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount-mobile.tsx similarity index 86% rename from packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount-mobile.jsx rename to packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount-mobile.tsx index 2e5439c3aff1..b968f272ee63 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount-mobile.jsx +++ b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount-mobile.tsx @@ -7,6 +7,24 @@ import { Money, Numpad, Tabs } from '@deriv/components'; import { getDecimalPlaces, isEmptyObject } from '@deriv/shared'; import MinMaxStakeInfo from './min-max-stake-info'; +type TAmountMobile = React.ComponentProps & { + amount_tab_idx?: number; + setAmountTabIdx: React.ComponentProps['onTabItemClick']; + stake_value: string | number; + payout_value: string | number; +}; + +type TBasis = { + basis: string; + duration_unit: string; + duration_value: number; + toggleModal: () => void; + has_duration_error: boolean; + selected_basis: string | number; + setSelectedAmount: (basis: string, num: string | number) => void; + setAmountError: (has_error: boolean) => void; +}; + const Basis = observer( ({ basis, @@ -17,7 +35,7 @@ const Basis = observer( selected_basis, setSelectedAmount, setAmountError, - }) => { + }: TBasis) => { const { ui, client } = useStore(); const { addToast } = ui; const { currency } = client; @@ -27,21 +45,21 @@ const Basis = observer( is_vanilla, onChangeMultiple, stake_boundary, - trade_amount, - trade_basis, - trade_duration_unit, - trade_duration, + amount: trade_amount, + basis: trade_basis, + duration_unit: trade_duration_unit, + duration: trade_duration, } = useTraderStore(); const { min_stake, max_stake } = stake_boundary[contract_type.toUpperCase()] || {}; const user_currency_decimal_places = getDecimalPlaces(currency); - const onNumberChange = num => { + const onNumberChange = (num: number | string) => { setSelectedAmount(basis, num); validateAmount(num); }; - const formatAmount = value => - !isNaN(value) && value !== '' ? Number(value).toFixed(user_currency_decimal_places) : value; - const setBasisAndAmount = amount => { - const on_change_obj = {}; + const formatAmount = (value: number | string) => + !isNaN(+value) && value !== '' ? Number(value).toFixed(user_currency_decimal_places) : value; + const setBasisAndAmount = (amount: number | string) => { + const on_change_obj: Partial> = {}; // Check for any duration changes in Duration trade params Tab before sending onChange object if (duration_unit !== trade_duration_unit && !has_duration_error) @@ -50,7 +68,7 @@ const Basis = observer( if (amount !== trade_amount || basis !== trade_basis) { on_change_obj.basis = basis; - on_change_obj.amount = amount; + on_change_obj.amount = +amount; } if (!isEmptyObject(on_change_obj)) onChangeMultiple(on_change_obj); @@ -59,7 +77,7 @@ const Basis = observer( const zero_decimals = Number('0').toFixed(getDecimalPlaces(currency)); const min_amount = parseFloat(zero_decimals.toString().replace(/.$/, '1')); - const validateAmount = value => { + const validateAmount = (value: number | string) => { const localized_message = ; const min_max_stake_message = ( { + }: TAmountMobile) => { const { basis, basis_list } = useTraderStore(); const has_selected_tab_idx = typeof amount_tab_idx !== 'undefined'; const active_index = has_selected_tab_idx ? amount_tab_idx : basis_list.findIndex(b => b.value === basis); diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount.jsx b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount.tsx similarity index 88% rename from packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount.jsx rename to packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount.tsx index 9352e4b03e24..c33cf5e80ab1 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount.jsx +++ b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/amount.tsx @@ -1,29 +1,37 @@ import { AMOUNT_MAX_LENGTH, addComma, getDecimalPlaces } from '@deriv/shared'; import { ButtonToggle, Dropdown, InputField } from '@deriv/components'; import { Localize, localize } from '@deriv/translations'; - -import AllowEquals from './allow-equals.jsx'; +import AllowEquals from './allow-equals'; import Fieldset from 'App/Components/Form/fieldset'; import Multiplier from './Multiplier/multiplier.jsx'; import MultipliersInfo from './Multiplier/info.jsx'; import MinMaxStakeInfo from './min-max-stake-info'; -import PropTypes from 'prop-types'; import React from 'react'; import classNames from 'classnames'; import { useTraderStore } from 'Stores/useTraderStores'; import { observer, useStore } from '@deriv/stores'; +type TInput = { + amount: string | number; + currency: string; + current_focus: string | null; + error_messages?: string[]; + is_disabled?: boolean; + is_single_currency: boolean; + onChange: (e: { target: { name: string; value: number | string } }) => void; + setCurrentFocus: (name: string | null) => void; +}; + export const Input = ({ amount, currency, current_focus, error_messages, - is_nativepicker, is_single_currency, is_disabled, onChange, setCurrentFocus, -}) => ( +}: TInput) => ( ); -const Amount = observer(({ is_minimized, is_nativepicker }) => { +const Amount = observer(({ is_minimized = false }: { is_minimized?: boolean }) => { const { ui, client } = useStore(); const { currencies_list, is_single_currency } = client; const { setCurrentFocus, current_focus } = ui; @@ -82,9 +89,7 @@ const Amount = observer(({ is_minimized, is_nativepicker }) => { if (is_minimized) { return (
- - {(basis_list.find(o => o.value === basis) || {}).text} - + {basis_list.find(o => o.value === basis)?.text}   { ); } - const error_messages = validation_errors.amount; + const error_messages = validation_errors?.amount; const getBasisList = () => basis_list.map(item => ({ text: item.text, value: item.value })); @@ -140,12 +145,10 @@ const Amount = observer(({ is_minimized, is_nativepicker }) => { current_focus={current_focus} error_messages={error_messages} is_single_currency={is_single_currency} - is_nativepicker={is_nativepicker} onChange={onChange} setCurrentFocus={setCurrentFocus} /> { current_focus={current_focus} error_messages={error_messages} is_single_currency={is_single_currency} - is_nativepicker={is_nativepicker} is_disabled={has_open_accu_contract} onChange={onChange} setCurrentFocus={setCurrentFocus} @@ -177,13 +179,14 @@ const Amount = observer(({ is_minimized, is_nativepicker }) => { duration_unit={duration_unit} expiry_type={expiry_type} onChange={onChange} - value={parseInt(is_equal)} + value={Number(is_equal)} has_equals_only={has_equals_only} /> {is_multiplier && ( { ); }); -Amount.propTypes = { - is_minimized: PropTypes.bool, - is_nativepicker: PropTypes.bool, -}; - export default Amount; diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/barrier.jsx b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/barrier.tsx similarity index 94% rename from packages/trader/src/Modules/Trading/Components/Form/TradeParams/barrier.jsx rename to packages/trader/src/Modules/Trading/Components/Form/TradeParams/barrier.tsx index 7ca8412e989f..e8f19de41855 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/barrier.jsx +++ b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/barrier.tsx @@ -1,15 +1,19 @@ import classNames from 'classnames'; -import PropTypes from 'prop-types'; import React from 'react'; import { DesktopWrapper, Icon, InputField, MobileWrapper, Modal, Text, usePrevious } from '@deriv/components'; import Fieldset from 'App/Components/Form/fieldset'; -import { ValueMovement } from '../Purchase/contract-info'; +import ValueMovement from '../Purchase/value-movement'; import { observer, useStore } from '@deriv/stores'; import { useTraderStore } from 'Stores/useTraderStores'; import { localize } from '@deriv/translations'; import LabeledQuantityInputMobile from '../LabeledQuantityInputMobile'; -const Barrier = observer(({ is_minimized, is_absolute_only }) => { +type TBarrier = { + is_minimized?: boolean; + is_absolute_only?: boolean; +}; + +const Barrier = observer(({ is_minimized, is_absolute_only }: TBarrier) => { const { ui } = useStore(); const { current_focus, setCurrentFocus } = ui; const { @@ -25,11 +29,12 @@ const Barrier = observer(({ is_minimized, is_absolute_only }) => { } = useTraderStore(); const [show_modal, setShowModal] = React.useState(false); const type_with_current_spot = Object.keys(trade_types).find(type => proposal_info?.[type]?.spot); - const contract_info = proposal_info?.[type_with_current_spot]; + let contract_info, has_spot_increased; + if (type_with_current_spot) contract_info = proposal_info?.[type_with_current_spot]; const current_spot = contract_info?.spot || ''; const current_barrier_price = contract_info?.barrier || ''; const previous_spot = usePrevious(current_spot); - const has_spot_increased = current_spot > previous_spot; + if (previous_spot) has_spot_increased = Number(current_spot) > previous_spot; const barrier_title = barrier_count === 1 ? localize('Barrier') : localize('Barriers'); const has_error_or_not_loaded = contract_info?.has_error || !contract_info?.id; @@ -49,7 +54,7 @@ const Barrier = observer(({ is_minimized, is_absolute_only }) => { // TODO: Some contracts yet to be implemented in app.deriv.com allow only absolute barrier, hence the prop const is_absolute_barrier = is_day_duration || is_absolute_only; - const format = value => { + const format = (value: string) => { const float_value = parseFloat(value); let final_value; if (Math.sign(float_value) === -1) { @@ -86,7 +91,7 @@ const Barrier = observer(({ is_minimized, is_absolute_only }) => { )} current_focus={current_focus} onChange={onChange} - error_messages={validation_errors.barrier_1 || []} + error_messages={validation_errors?.barrier_1 || []} is_float is_signed setCurrentFocus={setCurrentFocus} @@ -103,7 +108,7 @@ const Barrier = observer(({ is_minimized, is_absolute_only }) => { classNameInput='trade-container__input' current_focus={current_focus} onChange={onChange} - error_messages={validation_errors.barrier_2} + error_messages={validation_errors?.barrier_2} is_float is_signed setCurrentFocus={setCurrentFocus} @@ -155,7 +160,7 @@ const Barrier = observer(({ is_minimized, is_absolute_only }) => { current_focus={current_focus} onChange={onChange} error_messages={ - (barrier_count === 1 ? validation_errors.barrier_1 : validation_errors.barrier_2) || [] + (barrier_count === 1 ? validation_errors?.barrier_1 : validation_errors?.barrier_2) || [] } error_message_alignment='top' is_float @@ -219,9 +224,4 @@ const Barrier = observer(({ is_minimized, is_absolute_only }) => { ); }); -Barrier.propTypes = { - is_absolute_only: PropTypes.bool, - is_minimized: PropTypes.bool, -}; - export default Barrier; diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/last-digit.jsx b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/last-digit.tsx similarity index 80% rename from packages/trader/src/Modules/Trading/Components/Form/TradeParams/last-digit.jsx rename to packages/trader/src/Modules/Trading/Components/Form/TradeParams/last-digit.tsx index 46edec68c99a..58930d65d309 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/last-digit.jsx +++ b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/last-digit.tsx @@ -1,4 +1,3 @@ -import PropTypes from 'prop-types'; import React from 'react'; import { isDesktop } from '@deriv/shared'; import { localize } from '@deriv/translations'; @@ -7,7 +6,11 @@ import Fieldset from 'App/Components/Form/fieldset'; import { observer } from '@deriv/stores'; import { useTraderStore } from 'Stores/useTraderStores'; -const LastDigit = observer(({ is_minimized }) => { +type TLastDigit = { + is_minimized?: boolean; +}; + +const LastDigit = observer(({ is_minimized }: TLastDigit) => { const { onChange, last_digit } = useTraderStore(); if (is_minimized) { return
{`${localize('Last Digit')}: ${last_digit}`}
; @@ -29,10 +32,4 @@ const LastDigit = observer(({ is_minimized }) => { ); }); -LastDigit.propTypes = { - is_minimized: PropTypes.bool, - last_digit: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - onChange: PropTypes.func, -}; - export default LastDigit; diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/min-max-stake-info.tsx b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/min-max-stake-info.tsx index 8a842efeb757..6c74f6de5cce 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/min-max-stake-info.tsx +++ b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/min-max-stake-info.tsx @@ -6,16 +6,16 @@ import { isMobile } from '@deriv/shared'; type TMinMaxStakeInfo = { className?: string; - min_stake: number; - max_stake: number; - currency: string; + min_stake?: number; + max_stake?: number; + currency?: string; }; const MinMaxStakeInfo = ({ className, currency, max_stake, min_stake }: TMinMaxStakeInfo) => { return (
- {!isNaN(min_stake) && - !isNaN(max_stake) && + {!isNaN(Number(min_stake)) && + !isNaN(Number(max_stake)) && ['Min', 'Max'].map(text => ( ({ })); jest.mock('../../../Containers/contract-type', () => jest.fn(() => 'MockedContractType')); jest.mock('../../../Containers/purchase', () => jest.fn(() => 'MockedPurchase')); -jest.mock('../../../Containers/trade-params.jsx', () => jest.fn(() => 'MockedTradeParams')); +jest.mock('../../../Containers/trade-params', () => jest.fn(() => 'MockedTradeParams')); const mock_props = { is_market_closed: false, diff --git a/packages/trader/src/Modules/Trading/Components/Form/screen-large.tsx b/packages/trader/src/Modules/Trading/Components/Form/screen-large.tsx index 6916fd8d0c97..cd917012fd3a 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/screen-large.tsx +++ b/packages/trader/src/Modules/Trading/Components/Form/screen-large.tsx @@ -4,7 +4,7 @@ import { TradeParamsLoader } from 'App/Components/Elements/ContentLoader'; import Fieldset from 'App/Components/Form/fieldset'; import ContractType from '../../Containers/contract-type'; import Purchase from '../../Containers/purchase'; -import TradeParams from '../../Containers/trade-params.jsx'; +import TradeParams from '../../Containers/trade-params'; type TScreenLarge = { is_market_closed?: boolean; diff --git a/packages/trader/src/Modules/Trading/Components/Form/screen-small.tsx b/packages/trader/src/Modules/Trading/Components/Form/screen-small.tsx index 7181dd35f9b4..aef43f88b21b 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/screen-small.tsx +++ b/packages/trader/src/Modules/Trading/Components/Form/screen-small.tsx @@ -13,7 +13,7 @@ import { } from 'Modules/Trading/Components/Form/TradeParams/Multiplier/widgets.jsx'; import AccumulatorsAmountMobile from 'Modules/Trading/Components/Form/TradeParams/Accumulator/accumulators-amount-mobile'; import AccumulatorsInfoDisplay from 'Modules/Trading/Components/Form/TradeParams/Accumulator/accumulators-info-display'; -import { BarrierMobile, LastDigitMobile } from 'Modules/Trading/Containers/trade-params-mobile.jsx'; +import { BarrierMobile, LastDigitMobile } from 'Modules/Trading/Containers/trade-params-mobile'; import ContractType from 'Modules/Trading/Containers/contract-type'; import MobileWidget from 'Modules/Trading/Components/Elements/mobile-widget.jsx'; import Purchase from 'Modules/Trading/Containers/purchase'; diff --git a/packages/trader/src/Modules/Trading/Containers/Multiplier/multiplier-amount-modal.jsx b/packages/trader/src/Modules/Trading/Containers/Multiplier/multiplier-amount-modal.jsx index 7818ffd8b8fa..ff8a61763e63 100644 --- a/packages/trader/src/Modules/Trading/Containers/Multiplier/multiplier-amount-modal.jsx +++ b/packages/trader/src/Modules/Trading/Containers/Multiplier/multiplier-amount-modal.jsx @@ -3,7 +3,7 @@ import { Div100vhContainer, Modal, Money, Popover, usePreventIOSZoom } from '@de import { useIsMounted, WS } from '@deriv/shared'; import { localize, Localize } from '@deriv/translations'; import { requestPreviewProposal } from 'Stores/Modules/Trading/Helpers/preview-proposal'; -import AmountMobile from 'Modules/Trading/Components/Form/TradeParams/amount-mobile.jsx'; +import AmountMobile from 'Modules/Trading/Components/Form/TradeParams/amount-mobile'; import MultipliersInfo from 'Modules/Trading/Components/Form/TradeParams/Multiplier/info.jsx'; import { observer, useStore } from '@deriv/stores'; import { useTraderStore } from 'Stores/useTraderStores'; diff --git a/packages/trader/src/Modules/Trading/Containers/purchase.tsx b/packages/trader/src/Modules/Trading/Containers/purchase.tsx index e6ae38d7edc7..20d4af5ab044 100644 --- a/packages/trader/src/Modules/Trading/Containers/purchase.tsx +++ b/packages/trader/src/Modules/Trading/Containers/purchase.tsx @@ -12,7 +12,7 @@ import AccumulatorsSellButton from '../Components/Form/TradeParams/Accumulator/a import PurchaseFieldset from 'Modules/Trading/Components/Elements/purchase-fieldset'; import { useTraderStore } from 'Stores/useTraderStores'; import { observer, useStore } from '@deriv/stores'; -import { TProposalTypeInfo } from 'Types'; +import { TTradeStore } from 'Types'; type TGetSupportedContractsKey = keyof ReturnType; @@ -48,14 +48,14 @@ const Purchase = observer(({ is_market_closed }: { is_market_closed?: boolean }) proposal_info, purchase_info, symbol, - validation_errors, + validation_errors = {}, trade_types, is_trade_enabled, has_open_accu_contract, } = useTraderStore(); const is_high_low = /^high_low$/.test(contract_type.toLowerCase()); - const isLoading = (info: TProposalTypeInfo | Record) => { + const isLoading = (info: TTradeStore['proposal_info'][string] | Record) => { const has_validation_error = Object.values(validation_errors).some(e => e.length); return !has_validation_error && !info?.has_error && !info.id; }; diff --git a/packages/trader/src/Modules/Trading/Containers/trade-params-mobile.jsx b/packages/trader/src/Modules/Trading/Containers/trade-params-mobile.tsx similarity index 75% rename from packages/trader/src/Modules/Trading/Containers/trade-params-mobile.jsx rename to packages/trader/src/Modules/Trading/Containers/trade-params-mobile.tsx index fe9cd8e69bbe..8cb17c6ffe72 100644 --- a/packages/trader/src/Modules/Trading/Containers/trade-params-mobile.jsx +++ b/packages/trader/src/Modules/Trading/Containers/trade-params-mobile.tsx @@ -1,17 +1,68 @@ import 'Sass/app/modules/trading-mobile.scss'; - import { Div100vhContainer, Modal, Money, Tabs, ThemedScrollbars, usePreventIOSZoom } from '@deriv/components'; - -import AmountMobile from 'Modules/Trading/Components/Form/TradeParams/amount-mobile.jsx'; -import Barrier from 'Modules/Trading/Components/Form/TradeParams/barrier.jsx'; +import AmountMobile from 'Modules/Trading/Components/Form/TradeParams/amount-mobile'; +import Barrier from 'Modules/Trading/Components/Form/TradeParams/barrier'; import DurationMobile from 'Modules/Trading/Components/Form/TradeParams/Duration/duration-mobile.jsx'; -import LastDigit from 'Modules/Trading/Components/Form/TradeParams/last-digit.jsx'; +import LastDigit from 'Modules/Trading/Components/Form/TradeParams/last-digit'; +import { TTextValueStrings } from 'Types'; import { observer, useStore } from '@deriv/stores'; import { useTraderStore } from 'Stores/useTraderStores'; import React from 'react'; import classNames from 'classnames'; import { localize } from '@deriv/translations'; +type TTradeParamsModal = { + is_open: boolean; + tab_index: number; + toggleModal: () => void; +}; + +type TTradeParamsMobile = { + currency: string; + toggleModal: () => void; + isVisible: (component_key: string) => boolean; + setAmountTabIdx: (amount_tab_idx?: number) => void; + amount_tab_idx?: number; + setTradeParamTabIdx: (trade_param_tab_idx: number) => void; + trade_param_tab_idx: number; + setDurationTabIdx: (duration_tab_idx?: number) => void; + duration_unit: string; + duration_units_list: TTextValueStrings[]; + duration_value: number; + duration_tab_idx?: number; + has_amount_error: boolean; + has_duration_error: boolean; + // amount + setAmountError: (has_error: boolean) => void; + setSelectedAmount: (basis: string, selected_basis_value: string | number) => void; + stake_value: number; + payout_value: number; + // duration + setDurationError: (has_error: boolean) => void; + setSelectedDuration: (selected_duration_unit: string, selected_duration: number) => void; + t_duration: number; + s_duration: number; + m_duration: number; + h_duration: number; + d_duration: number; +}; + +type TReducer = Pick< + TTradeParamsMobile, + | 'trade_param_tab_idx' + | 'duration_tab_idx' + | 'amount_tab_idx' + | 'has_amount_error' + | 'has_duration_error' + | 't_duration' + | 's_duration' + | 'm_duration' + | 'h_duration' + | 'd_duration' + | 'stake_value' + | 'payout_value' +> & { curr_duration_unit: string; curr_duration_value: number }; + const DEFAULT_DURATION = Object.freeze({ t: 5, s: 15, @@ -20,20 +71,21 @@ const DEFAULT_DURATION = Object.freeze({ d: 1, }); -const reducer = (state, payload) => { +const reducer = (state: TReducer, payload: Partial) => { return { ...state, ...payload, }; }; -const makeGetDefaultDuration = (trade_duration, trade_duration_unit) => duration_unit => - trade_duration_unit === duration_unit ? trade_duration : DEFAULT_DURATION[duration_unit]; +const makeGetDefaultDuration = (trade_duration: number, trade_duration_unit: string) => (duration_unit: string) => + trade_duration_unit === duration_unit + ? trade_duration + : DEFAULT_DURATION[duration_unit as keyof typeof DEFAULT_DURATION]; -const TradeParamsModal = observer(({ is_open, toggleModal, tab_index }) => { - const { client, ui } = useStore(); +const TradeParamsModal = observer(({ is_open, toggleModal, tab_index }: TTradeParamsModal) => { + const { client } = useStore(); const { currency } = client; - const { enableApp, disableApp } = ui; const { amount, form_components, duration, duration_unit, duration_units_list } = useTraderStore(); // eslint-disable-next-line react-hooks/exhaustive-deps const getDefaultDuration = React.useCallback(makeGetDefaultDuration(duration, duration_unit), []); @@ -61,19 +113,20 @@ const TradeParamsModal = observer(({ is_open, toggleModal, tab_index }) => { React.useEffect(() => { setSelectedDuration(duration_unit, duration); - setDurationTabIdx(undefined); + setDurationTabIdx(); // duration and duration_unit can be changed in trade-store when contract type is changed }, [duration, duration_unit]); - const setTradeParamTabIdx = trade_param_tab_idx => dispatch({ trade_param_tab_idx }); + const setTradeParamTabIdx = (trade_param_tab_idx: number) => dispatch({ trade_param_tab_idx }); - const setDurationTabIdx = duration_tab_idx => dispatch({ duration_tab_idx }); + const setDurationTabIdx = (duration_tab_idx?: number) => dispatch({ duration_tab_idx }); - const setAmountTabIdx = amount_tab_idx => dispatch({ amount_tab_idx }); + const setAmountTabIdx = (amount_tab_idx?: number) => dispatch({ amount_tab_idx }); - const setSelectedAmount = (basis, selected_basis_value) => dispatch({ [`${basis}_value`]: selected_basis_value }); + const setSelectedAmount = (basis: string, selected_basis_value: string | number) => + dispatch({ [`${basis}_value`]: selected_basis_value }); - const setSelectedDuration = (selected_duration_unit, selected_duration) => { + const setSelectedDuration = (selected_duration_unit: string, selected_duration: number) => { dispatch({ [`${selected_duration_unit}_duration`]: selected_duration, curr_duration_unit: selected_duration_unit, @@ -81,24 +134,21 @@ const TradeParamsModal = observer(({ is_open, toggleModal, tab_index }) => { }); }; - const setAmountError = has_error => { + const setAmountError = (has_error: boolean) => { dispatch({ has_amount_error: has_error }); }; - const setDurationError = has_error => { + const setDurationError = (has_error: boolean) => { dispatch({ has_duration_error: has_error }); }; - const isVisible = component_key => form_components.includes(component_key); - + const isVisible = (component_key: string): boolean => form_components.includes(component_key); return ( } - disableApp={disableApp} toggleModal={toggleModal} height='auto' width='calc(100vw - 32px)' @@ -172,7 +222,7 @@ const TradeParamsMobile = observer( m_duration, h_duration, d_duration, - }) => { + }: TTradeParamsMobile) => { const { basis_list, basis, expiry_epoch, is_turbos, is_vanilla } = useTraderStore(); const getDurationText = () => { const duration = duration_units_list.find(d => d.value === duration_unit); @@ -188,7 +238,7 @@ const TradeParamsMobile = observer( return ; }; - const getHeaderContent = tab_key => { + const getHeaderContent = (tab_key: string) => { switch (tab_key) { case 'duration': return ( @@ -229,9 +279,10 @@ const TradeParamsMobile = observer( onTabItemClick={setTradeParamTabIdx} top > - {isVisible('duration') && ( + {isVisible('duration') ? (
is migrated to TS toggleModal={toggleModal} amount_tab_idx={amount_tab_idx} duration_tab_idx={duration_tab_idx} @@ -249,8 +300,8 @@ const TradeParamsMobile = observer( expiry_epoch={expiry_epoch} />
- )} - {isVisible('amount') && ( + ) : null} + {isVisible('amount') ? (
- )} + ) : null} ); } @@ -273,10 +326,10 @@ const TradeParamsMobile = observer( export const LastDigitMobile = observer(() => { const { form_components } = useTraderStore(); - return form_components.includes('last_digit') && ; + return form_components.includes('last_digit') ? : null; }); export const BarrierMobile = observer(() => { const { form_components } = useTraderStore(); - return form_components.includes('barrier') && ; + return form_components.includes('barrier') ? : null; }); diff --git a/packages/trader/src/Modules/Trading/Containers/trade-params.jsx b/packages/trader/src/Modules/Trading/Containers/trade-params.tsx similarity index 86% rename from packages/trader/src/Modules/Trading/Containers/trade-params.jsx rename to packages/trader/src/Modules/Trading/Containers/trade-params.tsx index 6941ca692129..a00d1e44f459 100644 --- a/packages/trader/src/Modules/Trading/Containers/trade-params.jsx +++ b/packages/trader/src/Modules/Trading/Containers/trade-params.tsx @@ -1,11 +1,10 @@ -import PropTypes from 'prop-types'; import React from 'react'; import classNames from 'classnames'; -import Amount from 'Modules/Trading/Components/Form/TradeParams/amount.jsx'; -import Barrier from 'Modules/Trading/Components/Form/TradeParams/barrier.jsx'; +import Amount from 'Modules/Trading/Components/Form/TradeParams/amount'; +import Barrier from 'Modules/Trading/Components/Form/TradeParams/barrier'; import BarrierSelector from 'Modules/Trading/Components/Form/TradeParams/Turbos/barrier-selector'; import Duration from 'Modules/Trading/Components/Form/TradeParams/Duration'; -import LastDigit from 'Modules/Trading/Components/Form/TradeParams/last-digit.jsx'; +import LastDigit from 'Modules/Trading/Components/Form/TradeParams/last-digit'; import CancelDeal from 'Modules/Trading/Components/Form/TradeParams/Multiplier/cancel-deal.jsx'; import Accumulator from 'Modules/Trading/Components/Form/TradeParams/Accumulator/accumulator'; import StopLoss from 'Modules/Trading/Components/Form/TradeParams/Multiplier/stop-loss.jsx'; @@ -18,15 +17,22 @@ import { observer } from '@deriv/stores'; import { useTraderStore } from 'Stores/useTraderStores'; import Fieldset from 'App/Components/Form/fieldset'; -const TradeParams = observer(({ is_minimized }) => { +type TTradeParams = { + is_minimized?: boolean; +}; + +const TradeParams = observer(({ is_minimized = false }: TTradeParams) => { const { form_components } = useTraderStore(); - const isVisible = component_key => { + const isVisible = (component_key: string) => { return form_components.includes(component_key); }; return ( - {isVisible('duration') && } + {isVisible('duration') && ( + // @ts-expect-error: TODO: check if TS error is gone after is migrated to TS + + )} {isVisible('barrier') && } {isVisible('last_digit') && } {isVisible('accumulator') && } @@ -46,8 +52,5 @@ const TradeParams = observer(({ is_minimized }) => { ); }); -TradeParams.propTypes = { - is_minimized: PropTypes.bool, -}; export default TradeParams; diff --git a/packages/trader/src/Modules/Trading/Containers/trade.jsx b/packages/trader/src/Modules/Trading/Containers/trade.jsx index c01c0eed96ed..9b57197c5570 100644 --- a/packages/trader/src/Modules/Trading/Containers/trade.jsx +++ b/packages/trader/src/Modules/Trading/Containers/trade.jsx @@ -4,7 +4,7 @@ import { DesktopWrapper, Div100vhContainer, MobileWrapper, SwipeableWrapper } fr import { getDecimalPlaces, isDesktop, isMobile } from '@deriv/shared'; import ChartLoader from 'App/Components/Elements/chart-loader'; import PositionsDrawer from 'App/Components/Elements/PositionsDrawer'; -import MarketIsClosedOverlay from 'App/Components/Elements/market-is-closed-overlay.jsx'; +import MarketIsClosedOverlay from 'App/Components/Elements/market-is-closed-overlay'; import Test from './test.jsx'; import { ChartBottomWidgets, ChartTopWidgets, DigitsWidget } from './chart-widgets.jsx'; import FormLayout from '../Components/Form/form-layout'; @@ -300,7 +300,6 @@ const ChartTrade = observer(props => { wsSubscribe, active_symbols, has_alternative_source, - refToAddTick, } = useTraderStore(); const settings = { @@ -391,7 +390,6 @@ const ChartTrade = observer(props => { onExportLayout={exportLayout} shouldFetchTradingTimes={!end_epoch} hasAlternativeSource={has_alternative_source} - refToAddTick={refToAddTick} getMarketsOrder={getMarketsOrder} should_zoom_out_on_yaxis={is_accumulator} yAxisMargin={{ diff --git a/packages/trader/src/Modules/Trading/Helpers/contract-type.tsx b/packages/trader/src/Modules/Trading/Helpers/contract-type.tsx index aba9e38fee31..36dd8cf923f8 100644 --- a/packages/trader/src/Modules/Trading/Helpers/contract-type.tsx +++ b/packages/trader/src/Modules/Trading/Helpers/contract-type.tsx @@ -5,7 +5,7 @@ import { TContractType, TContractCategory, TList } from '../Components/Form/Cont type TContractTypesList = { [key: string]: { name: string; - categories: TContractType[]; + categories: DeepRequired; }; }; diff --git a/packages/trader/src/Stores/Modules/SmartChart/chart-barrier-store.ts b/packages/trader/src/Stores/Modules/SmartChart/chart-barrier-store.ts index 493dfe14a2b4..7f3e2adb9f0f 100644 --- a/packages/trader/src/Stores/Modules/SmartChart/chart-barrier-store.ts +++ b/packages/trader/src/Stores/Modules/SmartChart/chart-barrier-store.ts @@ -29,7 +29,7 @@ export class ChartBarrierStore { onChartBarrierChange: TOnChartBarrierChange | null; constructor( - high_barrier: string | number, + high_barrier?: string | number, low_barrier?: string | number, onChartBarrierChange: TOnChartBarrierChange = null, { color, line_style, not_draggable }: TChartBarrierStoreOptions = {} diff --git a/packages/trader/src/Stores/Modules/Trading/Actions/purchase.ts b/packages/trader/src/Stores/Modules/Trading/Actions/purchase.ts index f2a8062a8ebc..cc2b463fede0 100644 --- a/packages/trader/src/Stores/Modules/Trading/Actions/purchase.ts +++ b/packages/trader/src/Stores/Modules/Trading/Actions/purchase.ts @@ -1,11 +1,20 @@ -import { Buy, BuyContractRequest } from '@deriv/api-types'; +import { Buy, BuyContractResponse, BuyContractRequest } from '@deriv/api-types'; import { WS } from '@deriv/shared'; +type TResponse = BuyContractResponse & { + echo_req: Buy; + error?: { + code: string; + message: string; + details?: BuyContractResponse['buy'] & { field: string }; + }; +}; + export const processPurchase = async ( proposal_id: string, - price: BuyContractRequest['price'], - passthrough: BuyContractRequest['passthrough'] -): Promise => + price: string | number, + passthrough?: BuyContractRequest['passthrough'] +): Promise => WS.buy({ proposal_id, price, diff --git a/packages/trader/src/Stores/Modules/Trading/Actions/start-date.ts b/packages/trader/src/Stores/Modules/Trading/Actions/start-date.ts index 9be02fc3e422..62d9950f5e04 100644 --- a/packages/trader/src/Stores/Modules/Trading/Actions/start-date.ts +++ b/packages/trader/src/Stores/Modules/Trading/Actions/start-date.ts @@ -38,7 +38,6 @@ export const onChangeStartDate = async (store: TTradeStore) => { export const onChangeExpiry = async (store: TTradeStore) => { const { start_time, expiry_date, expiry_type, expiry_time, start_date, symbol, sessions } = store; - const trading_times = await ContractType.getTradingTimes(expiry_date, symbol); const obj_market_open_times = { market_open_times: trading_times.open }; const obj_market_close_times = { market_close_times: trading_times.close }; diff --git a/packages/trader/src/Stores/Modules/Trading/Constants/validation-rules.ts b/packages/trader/src/Stores/Modules/Trading/Constants/validation-rules.ts index 6911efae718e..cbd1835ba444 100644 --- a/packages/trader/src/Stores/Modules/Trading/Constants/validation-rules.ts +++ b/packages/trader/src/Stores/Modules/Trading/Constants/validation-rules.ts @@ -4,231 +4,208 @@ import { isSessionAvailable } from '../Helpers/start-date'; import { TTradeStore } from 'Types'; import type { TRuleOptions } from 'Utils/Validator/validator'; +type TValidationRules = { + [key: string]: { + rules?: Array[]; + trigger?: string; + }; +}; + const tradeSpecificBarrierCheck = (is_vanilla: boolean, input: number) => is_vanilla || input !== 0; -export const getValidationRules = () => - ({ - amount: { - rules: [ - ['req', { message: localize('Amount is a required field.') }], - ['number', { min: 0, type: 'float' }], - ], - }, - barrier_1: { - rules: [ - [ - 'req', - { - condition: (store: TTradeStore) => - store.barrier_count && store.form_components.indexOf('barrier') > -1, - message: localize('Barrier is a required field.'), - }, - ], - ['barrier', { condition: (store: TTradeStore) => store.barrier_count }], - [ - 'custom', - { - func: ( - value: TTradeStore['barrier_1'], - options: Partial, - store: TTradeStore, - inputs: Pick - ) => (store.barrier_count > 1 ? +value > +inputs.barrier_2 : true), - message: localize('Higher barrier must be higher than lower barrier.'), - }, - ], - [ - 'custom', - { - func: ( - value: TTradeStore['barrier_1'], - options: Partial, - store: TTradeStore, - inputs: Pick - ) => - /^[+-]/.test(inputs.barrier_1) - ? tradeSpecificBarrierCheck(store.is_vanilla, +inputs.barrier_1) - : true, - message: localize('Barrier cannot be zero.'), - }, - ], - ], - trigger: 'barrier_2', - }, - barrier_2: { - rules: [ - [ - 'req', - { - condition: (store: TTradeStore) => - store.barrier_count > 1 && store.form_components.indexOf('barrier') > -1, - message: localize('Barrier is a required field.'), - }, - ], - ['barrier', { condition: (store: TTradeStore) => store.barrier_count }], - [ - 'custom', - { - func: ( - value: TTradeStore['barrier_2'], - options: Partial, - store: TTradeStore, - inputs: Pick - ) => - (/^[+-]/g.test(inputs.barrier_1) && /^[+-]/g.test(value)) || - (/^(?![+-])/g.test(inputs.barrier_1) && /^(?![+-])/g.test(value)), - message: localize('Both barriers should be relative or absolute'), - }, - ], - [ - 'custom', - { - func: ( - value: TTradeStore['barrier_2'], - options: Partial, - store: TTradeStore, - inputs: Pick - ) => +inputs.barrier_1 > +value, - message: localize('Lower barrier must be lower than higher barrier.'), - }, - ], - ], - trigger: 'barrier_1', - }, - duration: { - rules: [['req', { message: localize('Duration is a required field.') }]], - }, - start_date: { - trigger: 'start_time', - }, - expiry_date: { - trigger: 'expiry_time', - }, - start_time: { - rules: [ - [ - 'custom', - { - func: (value: TTradeStore['start_time'], options: Partial, store: TTradeStore) => - store.contract_start_type === 'spot' || isTimeValid(value ?? ''), - message: localize('Please enter the start time in the format "HH:MM".'), - }, - ], - [ - 'custom', - { - func: (value: TTradeStore['start_time'], options: Partial, store: TTradeStore) => - store.contract_start_type === 'spot' || isHourValid(value ?? ''), - message: localize('Hour must be between 0 and 23.'), - }, - ], - [ - 'custom', - { - func: (value: TTradeStore['start_time'], options: Partial, store: TTradeStore) => - store.contract_start_type === 'spot' || isMinuteValid(value ?? ''), - message: localize('Minute must be between 0 and 59.'), - }, - ], - [ - 'custom', - { - func: ( - value: TTradeStore['start_time'], - options: Partial, - store: TTradeStore - ) => { - if (store.contract_start_type === 'spot') return true; - if (!isTimeValid(value ?? '')) return false; - const start_moment = toMoment(store.start_date); - const start_moment_clone = start_moment.clone(); - const [h, m] = value?.split(':') ?? []; - return isSessionAvailable( - store.sessions, - start_moment_clone.hour(+h).minute(+m), - start_moment - ); - }, - message: localize('Start time cannot be in the past.'), - }, - ], - ], - }, - expiry_time: { - rules: [ - [ - 'custom', - { - func: (value: TTradeStore['expiry_time'], options: Partial, store: TTradeStore) => - store.contract_start_type === 'spot' || isTimeValid(value ?? ''), - message: localize('Please enter the start time in the format "HH:MM".'), - }, - ], - [ - 'custom', - { - func: (value: TTradeStore['expiry_time'], options: Partial, store: TTradeStore) => - store.contract_start_type === 'spot' || isHourValid(value ?? ''), - message: localize('Hour must be between 0 and 23.'), - }, - ], - [ - 'custom', - { - func: (value: TTradeStore['expiry_time'], options: Partial, store: TTradeStore) => - store.contract_start_type === 'spot' || isMinuteValid(value ?? ''), - message: localize('Minute must be between 0 and 59.'), - }, - ], - [ - 'custom', - { - func: ( - value: TTradeStore['expiry_time'], - options: Partial, - store: TTradeStore - ) => { - if (store.contract_start_type === 'spot') return true; - if (!isTimeValid(value ?? '')) return false; - const start_moment = toMoment(store.start_date); - const start_moment_clone = start_moment.clone(); - const [h, m] = value?.split(':') ?? []; - return isSessionAvailable( - store.sessions, - start_moment_clone.hour(+h).minute(+m), - start_moment - ); - }, - message: localize('Expiry time cannot be in the past.'), - }, - ], +export const getValidationRules = (): TValidationRules => ({ + 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: TTradeStore) => !!store.barrier_count }], + [ + 'custom', + { + func: (value: TTradeStore['barrier_1'], options, store, inputs) => + Number(store?.barrier_count) > 1 ? +value > Number(inputs?.barrier_2) : true, + message: localize('Higher barrier must be higher than lower barrier.'), + }, ], - }, - ...getMultiplierValidationRules(), - } as const); + [ + 'custom', + { + func: (value: TTradeStore['barrier_1'], options, store, inputs) => + /^[+-]/.test(inputs?.barrier_1 ?? '') + ? tradeSpecificBarrierCheck(!!store?.is_vanilla, Number(inputs?.barrier_1)) + : true, + message: localize('Barrier cannot be zero.'), + }, + ], + ], + 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: TTradeStore) => !!store.barrier_count }], + [ + 'custom', + { + func: (value: TTradeStore['barrier_2'], options, store, inputs) => + (/^[+-]/g.test(inputs?.barrier_1 ?? '') && /^[+-]/g.test(value)) || + (/^(?![+-])/g.test(inputs?.barrier_1 ?? '') && /^(?![+-])/g.test(value)), + message: localize('Both barriers should be relative or absolute'), + }, + ], + [ + 'custom', + { + func: (value: TTradeStore['barrier_2'], options, store, inputs) => + Number(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, store) => + store?.contract_start_type === 'spot' || isTimeValid(value ?? ''), + message: localize('Please enter the start time in the format "HH:MM".'), + }, + ], + [ + 'custom', + { + func: (value: TTradeStore['start_time'], options, store) => + store?.contract_start_type === 'spot' || isHourValid(value ?? ''), + message: localize('Hour must be between 0 and 23.'), + }, + ], + [ + 'custom', + { + func: (value: TTradeStore['start_time'], options, store) => + store?.contract_start_type === 'spot' || isMinuteValid(value ?? ''), + message: localize('Minute must be between 0 and 59.'), + }, + ], + [ + 'custom', + { + func: (value: TTradeStore['start_time'], options, 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: TTradeStore['expiry_time'], options, store) => + store?.contract_start_type === 'spot' || isTimeValid(value ?? ''), + message: localize('Please enter the start time in the format "HH:MM".'), + }, + ], + [ + 'custom', + { + func: (value: TTradeStore['expiry_time'], options, store) => + store?.contract_start_type === 'spot' || isHourValid(value ?? ''), + message: localize('Hour must be between 0 and 23.'), + }, + ], + [ + 'custom', + { + func: (value: TTradeStore['expiry_time'], options, store) => + store?.contract_start_type === 'spot' || isMinuteValid(value ?? ''), + message: localize('Minute must be between 0 and 59.'), + }, + ], + [ + 'custom', + { + func: (value: TTradeStore['expiry_time'], options, 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: TTradeStore) => store.has_stop_loss && !store.stop_loss, - message: localize('Please enter a stop loss amount.'), - }, - ], - ], - }, - take_profit: { - rules: [ - [ - 'req', - { - condition: (store: TTradeStore) => store.has_take_profit && !store.take_profit, - message: localize('Please enter a take profit amount.'), - }, - ], +export const getMultiplierValidationRules = () => ({ + stop_loss: { + rules: [ + [ + 'req', + { + condition: (store: TTradeStore) => store.has_stop_loss && !store.stop_loss, + message: localize('Please enter a stop loss amount.'), + }, + ], + ], + }, + take_profit: { + rules: [ + [ + 'req', + { + condition: (store: TTradeStore) => store.has_take_profit && !store.take_profit, + message: localize('Please enter a take profit amount.'), + }, ], - }, - } as const); + ], + }, +}); diff --git a/packages/trader/src/Stores/Modules/Trading/Helpers/allow-equals.ts b/packages/trader/src/Stores/Modules/Trading/Helpers/allow-equals.ts index 189936dfe2c9..8d2eb0bdbdbf 100644 --- a/packages/trader/src/Stores/Modules/Trading/Helpers/allow-equals.ts +++ b/packages/trader/src/Stores/Modules/Trading/Helpers/allow-equals.ts @@ -1,11 +1,10 @@ import { isEmptyObject, getPropertyValue } from '@deriv/shared'; import { ContractType } from 'Stores/Modules/Trading/Helpers/contract-type'; -import { PriceProposalRequest } from '@deriv/api-types'; import { TTradeStore } from 'Types'; type THasDurationForCallPutEqual = { contract_type_list: TTradeStore['contract_types_list']; - duration_unit: string; + duration_unit: TTradeStore['duration_unit']; contract_start_type: string; }; @@ -26,7 +25,6 @@ export const hasDurationForCallPutEqual = ( if (!contract_type_list || !duration_unit || !contract_start_type) return false; const contract_list = Object.keys(contract_type_list || {}).reduce((key, list) => { - // @ts-expect-error the key always exists in the object, hence can ignore the TS error. const item: THasDurationForCallPutEqual['contract_type_list']['Ups & Downs'] = contract_type_list[list]; return [...key, ...item.categories.map(contract => contract.value)]; }, []); diff --git a/packages/trader/src/Stores/Modules/Trading/Helpers/chart.ts b/packages/trader/src/Stores/Modules/Trading/Helpers/chart.ts index c06061a0ab86..9f9d97eec88b 100644 --- a/packages/trader/src/Stores/Modules/Trading/Helpers/chart.ts +++ b/packages/trader/src/Stores/Modules/Trading/Helpers/chart.ts @@ -1,16 +1,11 @@ -type TPayload = { - data?: { - action: string; - chart_type_name?: string; - indicator_type_name?: string; - indicators_category_name?: string; - market_type_name?: string; - search_string?: string; - subform_name?: string; - tab_market_name?: string; - time_interval_name?: string; - }; - event_type: string; +import type { TEvents } from '@deriv/analytics'; + +export type TPayload = { + data: Omit< + Partial, + 'action' + > & { action: string }; + event_type: 'ce_chart_types_form' | 'ce_market_types_form' | 'ce_indicators_types_form'; }; type TStateChangeOption = { @@ -75,7 +70,7 @@ export const SUBFORM_NAME = { const getChartTypeFormAnalyticsData = (state: keyof typeof STATE_TYPES, option: TStateChangeOption = {}) => { const { chart_type_name = '', is_open, time_interval_name } = option; const chart_event_type = 'ce_chart_types_form'; - const payload = { + const payload: TPayload = { data: { action: '', chart_type_name, @@ -107,9 +102,9 @@ const getIndicatorTypeFormAnalyticsData = (state: keyof typeof STATE_TYPES, opti const indicators_subform = is_info_open ? SUBFORM_NAME.INDICATORS_INFO : SUBFORM_NAME.INDICATORS_TYPE; const info_open_close_action = is_info_open ? ACTION.INFO_OPEN : ACTION.INFO_CLOSE; const open_close_action = is_open ? ACTION.OPEN : ACTION.CLOSE; - const payload: TPayload = { + const payload = { event_type: indicators_event_type, - }; + } as TPayload; if ( (state === STATE_TYPES.INDICATOR_SEARCH && !option.search_string) || ((state === STATE_TYPES.INDICATOR_ADDED || @@ -183,9 +178,9 @@ const getMarketTypeFormAnalyticsData = (state: keyof typeof STATE_TYPES, option: const market_event_type = 'ce_market_types_form'; const favorites_action = is_favorite ? ACTION.ADD_TO_FAVORITES : ACTION.DELETE_FROM_FAVORITES; const open_close_action = is_open ? ACTION.OPEN : ACTION.CLOSE; - const payload: TPayload = { + const payload = { event_type: market_event_type, - }; + } as TPayload; if ( (state === STATE_TYPES.MARKET_SEARCH && !option.search_string) || (state === STATE_TYPES.FAVORITE_MARKETS_TOGGLE && !market_type_name) 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 35f9b6c9210f..f49fb573e61a 100644 --- a/packages/trader/src/Stores/Modules/Trading/Helpers/contract-type.ts +++ b/packages/trader/src/Stores/Modules/Trading/Helpers/contract-type.ts @@ -159,7 +159,7 @@ export const ContractType = (() => { return trade_types; }; - const getArrayDefaultValue = (arr_new_values: Array, value: string | number) => + const getArrayDefaultValue = (arr_new_values: Array, value: T): T => arr_new_values.indexOf(value) !== -1 ? value : arr_new_values[0]; const getContractValues = (store: TTradeStore): TContractValues | Record => { @@ -292,7 +292,7 @@ export const ContractType = (() => { }; const getDurationMinMax = (contract_type: string, contract_start_type: string, contract_expiry_type?: string) => { - let duration_min_max: TTradeStore['duration_min_max'] | TTradeStore['duration_min_max'][string] = + let duration_min_max: TTradeStore['duration_min_max'] = getPropertyValue(available_contract_types, [ contract_type, 'config', @@ -302,7 +302,12 @@ export const ContractType = (() => { ]) || {}; if (contract_expiry_type) { - duration_min_max = 'contract_expiry_type' in duration_min_max ? duration_min_max[contract_expiry_type] : {}; + duration_min_max = + 'contract_expiry_type' in duration_min_max + ? (duration_min_max as unknown as { [key: string]: TTradeStore['duration_min_max'] })[ + contract_expiry_type + ] + : {}; } return { duration_min_max }; @@ -579,7 +584,7 @@ export const ContractType = (() => { moment_obj.minute(Math.ceil(moment_obj.minute() / 5) * 5); const getTradeTypes = (contract_type: string) => ({ - trade_types: getPropertyValue(available_contract_types, [contract_type, 'config', 'trade_types']) as string[], + trade_types: getPropertyValue(available_contract_types, [contract_type, 'config', 'trade_types']), }); const getBarriers = (contract_type: string, expiry_type: string, stored_barrier_value?: string) => { diff --git a/packages/trader/src/Stores/Modules/Trading/Helpers/limit-orders.ts b/packages/trader/src/Stores/Modules/Trading/Helpers/limit-orders.ts index 2020d20c37bb..a7a0183a7638 100644 --- a/packages/trader/src/Stores/Modules/Trading/Helpers/limit-orders.ts +++ b/packages/trader/src/Stores/Modules/Trading/Helpers/limit-orders.ts @@ -2,11 +2,10 @@ import { isMultiplierContract, BARRIER_COLORS, BARRIER_LINE_STYLES } from '@deri import { ChartBarrierStore } from '../../SmartChart/chart-barrier-store'; import { removeBarrier } from '../../SmartChart/Helpers/barriers'; import { useStore } from '@deriv/stores'; +import { getProposalInfo } from './proposal'; -const isLimitOrderBarrierSupported = ( - contract_type: string, - contract_info: ReturnType['portfolio']['all_positions'][0]['contract_info'] -) => isMultiplierContract(contract_type) && contract_info.limit_order; +const isLimitOrderBarrierSupported = (contract_type: string, contract_info: ReturnType) => + isMultiplierContract(contract_type) && contract_info.limit_order; export const LIMIT_ORDER_TYPES = { STOP_OUT: 'stop_out', @@ -18,14 +17,14 @@ type TBarrier = ChartBarrierStore & { key?: string }; type TSetLimitOrderBarriers = { barriers: TBarrier[]; - contract_type: string; - contract_info: Parameters[1]; + contract_type?: string; + contract_info?: ReturnType; is_over: boolean; }; export const setLimitOrderBarriers = ({ barriers, - contract_type, - contract_info = {}, + contract_type = '', + contract_info = {} as ReturnType, is_over, }: TSetLimitOrderBarriers) => { if (is_over && isLimitOrderBarrierSupported(contract_type, contract_info)) { @@ -53,7 +52,6 @@ export const setLimitOrderBarriers = ({ barrier.onChange({ high: obj_limit_order.value, - low: undefined, //TODO: wait until ChartBarrierStore is ts migrated and 'low' can be an optional parameter }); } else { const obj_barrier = { @@ -87,7 +85,7 @@ export const setLimitOrderBarriers = ({ */ export const getLimitOrder = ( contract_update: Pick< - ReturnType['contract_trade'], + ReturnType['contract_trade']['contracts'][number], | 'has_contract_update_stop_loss' | 'has_contract_update_take_profit' | 'contract_update_stop_loss' diff --git a/packages/trader/src/Stores/Modules/Trading/Helpers/logic.ts b/packages/trader/src/Stores/Modules/Trading/Helpers/logic.ts index b586a0425592..e1cd547ee251 100644 --- a/packages/trader/src/Stores/Modules/Trading/Helpers/logic.ts +++ b/packages/trader/src/Stores/Modules/Trading/Helpers/logic.ts @@ -1,5 +1,5 @@ -import { TTradeStore } from 'Types'; +import { TContractInfo } from '@deriv/shared'; import ServerTime from '_common/base/server_time'; -export const isCancellationExpired = (contract_info: TTradeStore['proposal_info'][string]) => +export const isCancellationExpired = (contract_info: TContractInfo) => !!contract_info.cancellation?.date_expiry && contract_info.cancellation.date_expiry < ServerTime.get().unix(); diff --git a/packages/trader/src/Stores/Modules/Trading/Helpers/process.ts b/packages/trader/src/Stores/Modules/Trading/Helpers/process.ts index 9c4afbcc970d..bab8b78af2da 100644 --- a/packages/trader/src/Stores/Modules/Trading/Helpers/process.ts +++ b/packages/trader/src/Stores/Modules/Trading/Helpers/process.ts @@ -23,7 +23,7 @@ const processInSequence = async ( }); }; -export const processTradeParams = async (store: TTradeStore, new_state: DeepPartial) => { +export const processTradeParams = async (store: TTradeStore, new_state: Partial) => { const functions = getMethodsList(store, new_state); await processInSequence(store, functions); diff --git a/packages/trader/src/Stores/Modules/Trading/Helpers/proposal.ts b/packages/trader/src/Stores/Modules/Trading/Helpers/proposal.ts index 0ae9ff9fde8c..7231a2f97d9f 100644 --- a/packages/trader/src/Stores/Modules/Trading/Helpers/proposal.ts +++ b/packages/trader/src/Stores/Modules/Trading/Helpers/proposal.ts @@ -71,7 +71,7 @@ export const getProposalInfo = ( const is_stake = contract_basis?.value === 'stake'; - const price = is_stake ? stake : proposal[contract_basis?.value as keyof Proposal]; + const price = is_stake ? stake : (proposal[contract_basis?.value as keyof Proposal] as string | number); let has_increased = false; if (price !== undefined && price !== null) { diff --git a/packages/trader/src/Stores/Modules/Trading/trade-store.js b/packages/trader/src/Stores/Modules/Trading/trade-store.ts similarity index 82% rename from packages/trader/src/Stores/Modules/Trading/trade-store.js rename to packages/trader/src/Stores/Modules/Trading/trade-store.ts index d20856b83796..0e304c394dc4 100644 --- a/packages/trader/src/Stores/Modules/Trading/trade-store.js +++ b/packages/trader/src/Stores/Modules/Trading/trade-store.ts @@ -1,5 +1,4 @@ import * as Symbol from './Actions/symbol'; - import { WS, cloneObject, @@ -21,7 +20,6 @@ import { isTurbosContract, isVanillaContract, pickDefaultSymbol, - removeBarrier, resetEndTimeOnVolatilityIndices, showDigitalOptionsUnavailableError, showUnavailableLocationError, @@ -32,6 +30,7 @@ import { BARRIER_LINE_STYLES, } from '@deriv/shared'; import { RudderStack } from '@deriv/analytics'; +import type { TEvents } from '@deriv/analytics'; import { localize } from '@deriv/translations'; import { getValidationRules, getMultiplierValidationRules } from 'Stores/Modules/Trading/Constants/validation-rules'; import { ContractType } from 'Stores/Modules/Trading/Helpers/contract-type'; @@ -45,13 +44,136 @@ import { action, computed, makeObservable, observable, override, reaction, runIn import { createProposalRequests, getProposalErrorField, getProposalInfo } from './Helpers/proposal'; import { getHoveredColor } from './Helpers/barrier-utils'; import BaseStore from '../../base-store'; +import { TTextValueNumber, TTextValueStrings } from 'Types'; import { ChartBarrierStore } from '../SmartChart/chart-barrier-store'; import debounce from 'lodash.debounce'; import { setLimitOrderBarriers } from './Helpers/limit-orders'; -import { STATE_TYPES, getChartAnalyticsData } from './Helpers/chart'; +import type { TCoreStores } from '@deriv/stores/types'; +import { + ActiveSymbols, + ActiveSymbolsRequest, + Buy, + BuyContractResponse, + History, + PriceProposalRequest, + PriceProposalResponse, + ServerTimeRequest, + TickSpotData, + TicksHistoryRequest, + TicksHistoryResponse, + TicksStreamResponse, + TradingTimesRequest, +} from '@deriv/api-types'; +import { STATE_TYPES, TPayload, getChartAnalyticsData } from './Helpers/chart'; + +type TBarriers = Array< + ChartBarrierStore & { + hideOffscreenBarrier?: boolean; + isSingleBarrier?: boolean; + } +>; +type TChartLayout = { + adj: boolean; + aggregationType: string; + animation?: boolean; + candleWidth: number; + chartScale: string; + chartType: string; + crosshair: number; + extended: boolean; + flipped: boolean; + interval: number; + marketSessions: Partial>; + outliers: boolean; + panels: { + chart: { + chartName: string; + display: string; + index: number; + percent: number; + yAxis: { + name: string; + position: null; + }; + yaxisLHS: string[]; + yaxisRHS: string[]; + }; + }; + periodicity: number; + previousMaxTicks?: number; + range: Partial>; + setSpan: Partial>; + studies?: Partial>; + symbols: [ + { + interval: number; + periodicity: number; + setSpan: Partial>; + symbol: string; + symbolObject: ActiveSymbols[number]; + timeUnit: string; + } + ]; + timeUnit: string; + volumeUnderlay: boolean; +}; +type TChartStateChangeOption = { symbol: string | undefined; isClosed: boolean }; +type TContractDataForGTM = Omit, 'cancellation' | 'limit_order'> & + ReturnType & { + buy_price: number; + }; +type TPrevChartLayout = + | (TChartLayout & { + isDone?: VoidFunction; + is_used?: boolean; + }) + | null; +type TContractTypesList = { + [key: string]: { + name: string; + categories: TTextValueStrings[]; + }; +}; +type TDurationMinMax = { + [key: string]: { min: number; max: number }; +}; +type TResponse = Res & { + echo_req: Req; + error?: { + code: string; + message: string; + details?: Res[K] & { field: string }; + }; +}; +type TProposalInfo = { + [key: string]: ReturnType; +}; +type TStakeBoundary = Record< + string, + { + min_stake?: number; + max_stake?: number; + } +>; +type TTicksHistoryResponse = TicksHistoryResponse | TicksStreamResponse; +type TToastBoxListItem = { + component: JSX.Element | null; + contract_types: TTextValueStrings[]; + icon: string; + key: string; + label: string; +}; +type TToastBoxObject = { + key?: boolean; + buy_price?: string; + currency?: string; + contract_type?: string; + list?: Array; +}; +type TBarriersData = Record | { barrier: string; barrier_choices: string[] }; const store_name = 'trade_store'; -const g_subscribers_map = {}; // blame amin.m +const g_subscribers_map: Partial>> = {}; // blame amin.m export default class TradeStore extends BaseStore { // Control values @@ -62,53 +184,53 @@ export default class TradeStore extends BaseStore { has_equals_only = false; // Underlying - symbol; + symbol = ''; is_market_closed = false; previous_symbol = ''; - active_symbols = []; + active_symbols: ActiveSymbols = []; - form_components = []; + form_components: string[] = []; // Contract Type contract_expiry_type = ''; contract_start_type = ''; contract_type = ''; - contract_types_list = {}; - trade_types = {}; + contract_types_list: TContractTypesList = {}; + trade_types: { [key: string]: string } = {}; // Amount amount = 10; basis = ''; - basis_list = []; + basis_list: Array = []; currency = ''; - stake_boundary = {}; + stake_boundary: Partial = {}; // Duration duration = 5; - duration_min_max = {}; + duration_min_max: TDurationMinMax = {}; duration_unit = ''; - duration_units_list = []; - expiry_date = ''; - expiry_epoch = ''; - expiry_time = ''; - expiry_type = 'duration'; + duration_units_list: Array = []; + expiry_date: string | null = ''; + expiry_epoch: number | string = ''; + expiry_time: string | null = ''; + expiry_type: string | null = 'duration'; // Barrier barrier_1 = ''; barrier_2 = ''; barrier_count = 0; - barriers = []; + main_barrier: ChartBarrierStore | null = null; + barriers: TBarriers = []; hovered_barrier = ''; - main_barrier = null; - barrier_choices = []; + barrier_choices: string[] = []; // Start Time - start_date = Number(0); // Number(0) refers to 'now' - start_dates_list = []; - start_time = null; - sessions = []; + start_date = 0; // 0 refers to 'now' + start_dates_list: Array<{ text: string; value: number }> = []; + start_time: string | null = null; + sessions: Array<{ open: moment.Moment; close: moment.Moment }> = []; - market_open_times = []; + market_open_times: string[] = []; // End Date Time /** * An array that contains market closing time. @@ -116,68 +238,70 @@ export default class TradeStore extends BaseStore { * e.g. ["04:00:00", "08:00:00"] * */ - market_close_times = []; + market_close_times: string[] = []; // Last Digit last_digit = 5; is_mobile_digit_view_selected = false; // Purchase - proposal_info = {}; - purchase_info = {}; + proposal_info: TProposalInfo = {}; + purchase_info: Partial = {}; // Chart loader observables - is_chart_loading; + is_chart_loading?: boolean; should_show_active_symbols_loading = false; // Accumulator trade params - accumulator_range_list = []; + accumulator_range_list: number[] = []; growth_rate = 0.03; maximum_payout = 0; maximum_ticks = 0; - ticks_history_stats = {}; + ticks_history_stats: { + ticks_stayed_in?: number[]; + last_tick_epoch?: number; + } = {}; tick_size_barrier = 0; // Multiplier trade params - multiplier; - multiplier_range_list = []; - stop_loss; - take_profit; + multiplier = 0; + multiplier_range_list: TTextValueNumber[] = []; + stop_loss?: string; + take_profit?: string; has_stop_loss = false; has_take_profit = false; has_cancellation = false; - commission; - cancellation_price; - stop_out; - expiration; - hovered_contract_type; + commission?: string | number; + cancellation_price?: number; + stop_out?: number; + expiration?: number; + hovered_contract_type?: string | null; cancellation_duration = '60m'; - cancellation_range_list = []; + cancellation_range_list: Array = []; // Turbos trade params - long_barriers = {}; - short_barriers = {}; + long_barriers: TBarriersData = {}; + short_barriers: TBarriersData = {}; // Vanilla trade params - strike_price_choices = {}; + strike_price_choices: TBarriersData = {}; // Mobile is_trade_params_expanded = true; //Toastbox - contract_purchase_toast_box; + contract_purchase_toast_box?: TToastBoxObject; - addTickByProposal = () => null; debouncedProposal = debounce(this.requestProposal, 500); - proposal_requests = {}; + proposal_requests: Record> = {}; is_purchasing_contract = false; - initial_barriers; + initial_barriers?: { barrier_1: string; barrier_2: string }; is_initial_barrier_applied = false; should_skip_prepost_lifecycle = false; - constructor({ root_store }) { + constructor({ root_store }: { root_store: TCoreStores }) { const local_storage_properties = [ 'amount', 'currency', @@ -337,7 +461,6 @@ export default class TradeStore extends BaseStore { resetErrorServices: action.bound, resetPreviousSymbol: action.bound, setActiveSymbols: action.bound, - setAllowEqual: action.bound, setBarrierChoices: action.bound, setChartStatus: action.bound, setContractPurchaseToastbox: action.bound, @@ -347,14 +470,12 @@ export default class TradeStore extends BaseStore { setMarketStatus: action.bound, setMobileDigitView: action.bound, setPreviousSymbol: action.bound, - setPurchaseSpotBarrier: action.bound, setSkipPrePostLifecycle: action.bound, setStakeBoundary: action.bound, setTradeStatus: action.bound, show_digits_stats: computed, themeChangeListener: action.bound, updateBarrierColor: action.bound, - updateLimitOrderBarriers: action.bound, updateStore: action.bound, updateSymbol: action.bound, }); @@ -427,7 +548,7 @@ export default class TradeStore extends BaseStore { } ); when( - () => this.accumulator_range_list.length, + () => !!this.accumulator_range_list.length, () => this.setDefaultGrowthRate() ); } @@ -464,14 +585,14 @@ export default class TradeStore extends BaseStore { } } - setSkipPrePostLifecycle(should_skip) { + setSkipPrePostLifecycle(should_skip: boolean) { if (!!should_skip !== !!this.should_skip_prepost_lifecycle) { // to skip assignment if no change is made this.should_skip_prepost_lifecycle = should_skip; } } - setTradeStatus(status) { + setTradeStatus(status: boolean) { this.is_trade_enabled = status; } @@ -531,7 +652,7 @@ export default class TradeStore extends BaseStore { return; } - if (!active_symbols || !active_symbols.length) { + if (!active_symbols?.length) { await WS.wait('get_settings'); /* * This logic is related to EU country checks @@ -564,7 +685,9 @@ export default class TradeStore extends BaseStore { runInAction(() => { const contract_categories = ContractType.getContractCategories(); this.processNewValuesAsync({ - ...contract_categories, + ...(contract_categories as Pick & { + has_only_forward_starting_contracts: boolean; + }), ...ContractType.getContractType(contract_categories.contract_types_list, this.contract_type), }); this.processNewValuesAsync(ContractType.getContractValues(this)); @@ -603,7 +726,7 @@ export default class TradeStore extends BaseStore { ); } - async onChangeMultiple(values) { + async onChangeMultiple(values: Partial) { Object.keys(values).forEach(name => { if (!(name in this)) { throw new Error(`Invalid Argument: ${name}`); @@ -614,7 +737,7 @@ export default class TradeStore extends BaseStore { this.validateAllProperties(); // then run validation before sending proposal } - async onChange(e) { + async onChange(e: { target: { name: string; value: unknown } }) { const { name, value } = e.target; if (name === 'symbol' && value) { // set trade params skeleton and chart loader to true until processNewValuesAsync resolves @@ -627,7 +750,7 @@ export default class TradeStore extends BaseStore { } else if (name === 'currency') { // Only allow the currency dropdown change if client is not logged in if (!this.root_store.client.is_logged_in) { - this.root_store.client.selectCurrency(value); + this.root_store.client.selectCurrency(value as string); } } else if (name === 'expiry_date') { this.expiry_time = null; @@ -645,19 +768,15 @@ export default class TradeStore extends BaseStore { this.root_store.common.setSelectedContractType(this.contract_type); } - setHoveredBarrier(hovered_value) { + setHoveredBarrier(hovered_value: string) { this.hovered_barrier = hovered_value; } - setPreviousSymbol(symbol) { + setPreviousSymbol(symbol: string) { if (this.previous_symbol !== symbol) this.previous_symbol = symbol; } - setAllowEqual(is_equal) { - this.is_equal = is_equal; - } - - setIsTradeParamsExpanded(value) { + setIsTradeParamsExpanded(value: boolean) { this.is_trade_params_expanded = value; } @@ -665,7 +784,7 @@ export default class TradeStore extends BaseStore { this.setMarketStatus(isMarketClosed(this.active_symbols, this.previous_symbol)); await Symbol.onChangeSymbolAsync(this.previous_symbol); - await this.updateSymbol(this.symbol); + this.updateSymbol(this.symbol); this.setChartStatus(false); runInAction(() => { @@ -673,18 +792,18 @@ export default class TradeStore extends BaseStore { }); } - updateBarrierColor(is_dark_mode) { + updateBarrierColor(is_dark_mode: boolean) { if (this.main_barrier) { this.main_barrier.updateBarrierColor(is_dark_mode); } } - onHoverPurchase(is_over, contract_type) { + onHoverPurchase(is_over: boolean, contract_type?: string) { if (this.is_accumulator) return; if (this.is_purchase_enabled && this.main_barrier && !this.is_multiplier) { - this.main_barrier.updateBarrierShade(is_over, contract_type); + this.main_barrier.updateBarrierShade(is_over, contract_type ?? ''); } else if (!is_over && this.main_barrier && !this.is_multiplier) { - this.main_barrier.updateBarrierShade(false, contract_type); + this.main_barrier.updateBarrierShade(false, contract_type ?? ''); } this.hovered_contract_type = is_over ? contract_type : null; @@ -692,44 +811,7 @@ export default class TradeStore extends BaseStore { barriers: this.root_store.portfolio.barriers, is_over, contract_type, - contract_info: this.proposal_info[contract_type], - }); - } - - setPurchaseSpotBarrier(is_over, position) { - const key = 'PURCHASE_SPOT_BARRIER'; - if (!is_over) { - removeBarrier(this.root_store.portfolio.barriers, key); - return; - } - - let purchase_spot_barrier = this.root_store.portfolio.barriers.find(b => b.key === key); - if (purchase_spot_barrier) { - if (purchase_spot_barrier.high !== +position.contract_info.entry_spot) { - purchase_spot_barrier.onChange({ - high: position.contract_info.entry_spot, - }); - } - } else { - purchase_spot_barrier = new ChartBarrierStore(position.contract_info.entry_spot); - purchase_spot_barrier.key = key; - purchase_spot_barrier.draggable = false; - purchase_spot_barrier.hideOffscreenBarrier = true; - purchase_spot_barrier.isSingleBarrier = true; - purchase_spot_barrier.updateBarrierColor(this.root_store.ui.is_dark_mode_on); - this.barriers.push(purchase_spot_barrier); - this.root_store.portfolio.barriers.push(purchase_spot_barrier); - } - } - - updateLimitOrderBarriers(is_over, position) { - const contract_info = position.contract_info; - const { barriers } = this; - setLimitOrderBarriers({ - barriers, - contract_info, - contract_type: contract_info.contract_type, - is_over, + contract_info: this.proposal_info[contract_type ?? ''], }); } @@ -755,19 +837,18 @@ export default class TradeStore extends BaseStore { return this.root_store.portfolio.barriers && toJS(this.root_store.portfolio.barriers); } - setMainBarrier = proposal_info => { + setMainBarrier = (proposal_info: PriceProposalRequest) => { if (!proposal_info) { return; } - const { barrier, contract_type, high_barrier, low_barrier } = proposal_info; - + const { contract_type, barrier, barrier2 } = proposal_info; if (isBarrierSupported(contract_type)) { const color = this.root_store.ui.is_dark_mode_on ? BARRIER_COLORS.DARK_GRAY : BARRIER_COLORS.GRAY; // create barrier only when it's available in response this.main_barrier = new ChartBarrierStore( - this.hovered_barrier || barrier || high_barrier, - low_barrier, + this.hovered_barrier || barrier, + barrier2, this.onChartBarrierChange, { color: this.hovered_barrier ? getHoveredColor(contract_type) : color, @@ -783,14 +864,14 @@ export default class TradeStore extends BaseStore { onPurchase = debounce(this.processPurchase, 300); - processPurchase(proposal_id, price, type) { + processPurchase(proposal_id: string, price: string | number, type: string) { if (!this.is_purchase_enabled) return; if (proposal_id) { this.is_purchase_enabled = false; this.is_purchasing_contract = true; const is_tick_contract = this.duration_unit === 't'; processPurchase(proposal_id, price).then( - action(response => { + action((response: TResponse) => { if (!this.is_trade_component_mounted) { this.enablePurchase(); this.is_purchasing_contract = false; @@ -820,7 +901,7 @@ export default class TradeStore extends BaseStore { if (this.proposal_info[type] && this.proposal_info[type].id !== proposal_id) { throw new Error('Proposal ID does not match.'); } - const contract_data = { + const contract_data: TContractDataForGTM = { ...this.proposal_requests[type], ...this.proposal_info[type], buy_price: response.buy.buy_price, @@ -831,8 +912,8 @@ export default class TradeStore extends BaseStore { if (contract_id) { const shortcode = response.buy.shortcode; const { category, underlying } = extractInfoFromShortcode(shortcode); - const is_digit_contract = isDigitContractType(category.toUpperCase()); - const contract_type = category.toUpperCase(); + const is_digit_contract = isDigitContractType(category?.toUpperCase() ?? ''); + const contract_type = category?.toUpperCase(); this.root_store.contract_trade.addContract({ contract_id, start_time, @@ -886,10 +967,10 @@ export default class TradeStore extends BaseStore { const el_purchase_value = document.getElementsByClassName('trade-container__price-info'); const el_purchase_buttons = document.getElementsByClassName('btn-purchase'); [].forEach.bind(el_purchase_buttons, el => { - el.classList.add('btn-purchase--disabled'); + (el as HTMLButtonElement).classList.add('btn-purchase--disabled'); })(); [].forEach.bind(el_purchase_value, el => { - el.classList.add('trade-container__price-info--fade'); + (el as HTMLDivElement).classList.add('trade-container__price-info--fade'); })(); }; @@ -898,11 +979,11 @@ export default class TradeStore extends BaseStore { * @param {Object} new_state - new values to update the store with * @return {Object} returns the object having only those values that are updated */ - updateStore(new_state) { + updateStore(new_state: Partial) { Object.keys(cloneObject(new_state) || {}).forEach(key => { if (key === 'root_store' || ['validation_rules', 'validation_errors', 'currency'].indexOf(key) > -1) return; - if (JSON.stringify(this[key]) === JSON.stringify(new_state[key])) { - delete new_state[key]; + if (JSON.stringify(this[key as keyof this]) === JSON.stringify(new_state[key as keyof TradeStore])) { + delete new_state[key as keyof TradeStore]; } else { if (key === 'symbol') { this.is_purchase_enabled = false; @@ -913,7 +994,7 @@ export default class TradeStore extends BaseStore { new_state.start_date = parseInt(new_state.start_date); } - this[key] = new_state[key]; + this[key as 'currency'] = new_state[key as keyof TradeStore] as TradeStore['currency']; // validation is done in mobx intercept (base_store.js) // when barrier_1 is set, it is compared with store.barrier_2 (which is not updated yet) @@ -926,9 +1007,9 @@ export default class TradeStore extends BaseStore { } async processNewValuesAsync( - obj_new_values = {}, + obj_new_values: Partial = {}, is_changed_by_user = false, - obj_old_values = {}, + obj_old_values: Partial | null = {}, should_forget_first = true ) { // To switch to rise_fall_equal contract type when allow equal is checked on first page refresh or @@ -962,7 +1043,7 @@ export default class TradeStore extends BaseStore { savePreviousChartMode('', null); } - if (/\bduration\b/.test(Object.keys(obj_new_values))) { + if (/\bduration\b/.test(Object.keys(obj_new_values) as unknown as string)) { // TODO: fix this in input-field.jsx if (typeof obj_new_values.duration === 'string') { obj_new_values.duration = +obj_new_values.duration; @@ -974,27 +1055,27 @@ export default class TradeStore extends BaseStore { this.forgetAllProposal(); this.proposal_requests = {}; } - if (is_changed_by_user && /\bcurrency\b/.test(Object.keys(obj_new_values))) { + if (is_changed_by_user && /\bcurrency\b/.test(Object.keys(obj_new_values) as unknown as string)) { const prev_currency = obj_old_values?.currency || this.currency; const has_currency_changed = obj_new_values.currency !== prev_currency; const should_reset_stake = - isCryptocurrency(obj_new_values.currency) || + isCryptocurrency(obj_new_values.currency ?? '') || // For switch between fiat and crypto and vice versa - isCryptocurrency(obj_new_values.currency) !== isCryptocurrency(prev_currency); + isCryptocurrency(obj_new_values.currency ?? '') !== isCryptocurrency(prev_currency); if (has_currency_changed && should_reset_stake) { - obj_new_values.amount = obj_new_values.amount || getMinPayout(obj_new_values.currency); + obj_new_values.amount = obj_new_values.amount || getMinPayout(obj_new_values.currency ?? ''); } - this.currency = obj_new_values.currency; + this.currency = obj_new_values.currency ?? ''; } let has_only_forward_starting_contracts; if (Object.keys(obj_new_values).includes('symbol')) { this.setPreviousSymbol(this.symbol); - await Symbol.onChangeSymbolAsync(obj_new_values.symbol); - this.setMarketStatus(isMarketClosed(this.active_symbols, obj_new_values.symbol)); + await Symbol.onChangeSymbolAsync(obj_new_values.symbol ?? ''); + this.setMarketStatus(isMarketClosed(this.active_symbols, obj_new_values.symbol ?? '')); has_only_forward_starting_contracts = ContractType.getContractCategories().has_only_forward_starting_contracts; } @@ -1005,7 +1086,10 @@ export default class TradeStore extends BaseStore { const new_state = this.updateStore(cloneObject(obj_new_values)); - if (is_changed_by_user || /\b(symbol|contract_types_list)\b/.test(Object.keys(new_state))) { + if ( + is_changed_by_user || + /\b(symbol|contract_types_list)\b/.test(Object.keys(new_state) as unknown as string) + ) { this.updateStore({ // disable purchase button(s), clear contract info is_purchase_enabled: false, @@ -1030,7 +1114,7 @@ export default class TradeStore extends BaseStore { ...(!this.is_initial_barrier_applied ? this.initial_barriers : {}), }); this.is_initial_barrier_applied = true; - if (/\b(contract_type|currency)\b/.test(Object.keys(new_state))) { + if (/\b(contract_type|currency)\b/.test(Object.keys(new_state) as unknown as string)) { this.validateAllProperties(); } this.debouncedProposal(); @@ -1051,11 +1135,11 @@ export default class TradeStore extends BaseStore { return isDigitTradeType(this.contract_type); } - setMobileDigitView(bool) { + setMobileDigitView(bool: boolean) { this.is_mobile_digit_view_selected = bool; } - pushPurchaseDataToGtm(contract_data) { + pushPurchaseDataToGtm(contract_data: TContractDataForGTM) { const data = { event: 'buy_contract', bom_ui: 'new', @@ -1105,7 +1189,7 @@ export default class TradeStore extends BaseStore { } if (!isEmptyObject(requests)) { - this.proposal_requests = requests; + this.proposal_requests = requests as Record>; this.purchase_info = {}; Object.keys(this.proposal_requests).forEach(type => { WS.subscribeProposal(this.proposal_requests[type], this.onProposalResponse); @@ -1119,11 +1203,11 @@ export default class TradeStore extends BaseStore { if (length > 0) WS.forgetAll('proposal'); } - setMarketStatus(status) { + setMarketStatus(status: boolean) { this.is_market_closed = status; } - onProposalResponse(response) { + onProposalResponse(response: TResponse) { const { contract_type } = response.echo_req; const prev_proposal_info = getPropertyValue(this.proposal_info, contract_type) || {}; const obj_prev_contract_basis = getPropertyValue(prev_proposal_info, 'obj_contract_basis') || {}; @@ -1155,11 +1239,11 @@ export default class TradeStore extends BaseStore { if (this.is_accumulator && this.proposal_info?.ACCU) { const { barrier_spot_distance, - maximum_ticks, + maximum_ticks = 0, ticks_stayed_in, - tick_size_barrier, + tick_size_barrier = 0, last_tick_epoch, - maximum_payout, + maximum_payout = 0, high_barrier, low_barrier, spot_time, @@ -1189,10 +1273,9 @@ export default class TradeStore extends BaseStore { } if (this.hovered_contract_type === contract_type) { - this.addTickByProposal(response); setLimitOrderBarriers({ barriers: this.root_store.portfolio.barriers, - contract_info: this.proposal_info[this.hovered_contract_type], + contract_info: this.proposal_info[this.hovered_contract_type ?? ''], contract_type, is_over: true, }); @@ -1233,7 +1316,7 @@ export default class TradeStore extends BaseStore { if ((this.is_turbos || this.is_vanilla) && response.error.details?.barrier_choices) { const { barrier_choices, max_stake, min_stake } = response.error.details; this.setStakeBoundary(contract_type, min_stake, max_stake); - this.setBarrierChoices(barrier_choices); + this.setBarrierChoices(barrier_choices as string[]); if (!this.barrier_choices.includes(this.barrier_1)) { // Since on change of duration `proposal` API call is made which returns a new set of barrier values. // The new list is set and the mid value is assigned @@ -1250,8 +1333,8 @@ export default class TradeStore extends BaseStore { } else { this.validateAllProperties(); if (this.is_turbos || this.is_vanilla) { - const { max_stake, min_stake, barrier_choices } = response.proposal; - this.setBarrierChoices(barrier_choices); + const { max_stake, min_stake, barrier_choices } = response.proposal ?? {}; + this.setBarrierChoices(barrier_choices as string[]); this.setStakeBoundary(contract_type, min_stake, max_stake); } } @@ -1261,15 +1344,15 @@ export default class TradeStore extends BaseStore { } } - onChartBarrierChange(barrier_1, barrier_2) { + onChartBarrierChange(barrier_1: string, barrier_2?: string) { this.processNewValuesAsync({ barrier_1, barrier_2 }, true); } onAllowEqualsChange() { - this.processNewValuesAsync({ contract_type: parseInt(this.is_equal) ? 'rise_fall_equal' : 'rise_fall' }, true); + this.processNewValuesAsync({ contract_type: this.is_equal ? 'rise_fall_equal' : 'rise_fall' }, true); } - updateSymbol(underlying) { + updateSymbol(underlying: string) { if (!underlying) return; this.onChange({ target: { @@ -1287,7 +1370,7 @@ export default class TradeStore extends BaseStore { if (!this.validation_rules.duration) return; - const index = this.validation_rules.duration.rules.findIndex(item => item[0] === 'number'); + const index = this.validation_rules.duration.rules?.findIndex(item => item[0] === 'number'); const limits = this.duration_min_max[this.contract_expiry_type] || false; if (limits) { @@ -1296,10 +1379,10 @@ export default class TradeStore extends BaseStore { max: convertDurationLimit(+limits.max, this.duration_unit), }; - if (index > -1) { - this.validation_rules.duration.rules[index][1] = duration_options; + if (Number(index) > -1 && this.validation_rules.duration.rules) { + this.validation_rules.duration.rules[Number(index)][1] = duration_options; } else { - this.validation_rules.duration.rules.push(['number', duration_options]); + this.validation_rules.duration.rules?.push(['number', duration_options]); } this.validateProperty('duration', this.duration); } @@ -1352,11 +1435,11 @@ export default class TradeStore extends BaseStore { return Promise.resolve(); } - networkStatusChangeListener(is_online) { + networkStatusChangeListener(is_online: boolean) { this.setTradeStatus(is_online); } - themeChangeListener(is_dark_mode_on) { + themeChangeListener(is_dark_mode_on: boolean) { this.updateBarrierColor(is_dark_mode_on); } @@ -1383,11 +1466,11 @@ export default class TradeStore extends BaseStore { }); } - setChartStatus(status) { + setChartStatus(status: boolean) { this.is_chart_loading = status; } - async initAccountCurrency(new_currency) { + async initAccountCurrency(new_currency: string) { if (this.currency === new_currency) return; await this.processNewValuesAsync({ currency: new_currency }, true, { currency: this.currency }, false); @@ -1422,7 +1505,7 @@ export default class TradeStore extends BaseStore { } } - prev_chart_layout = null; + prev_chart_layout: TPrevChartLayout = null; get chart_layout() { let layout = null; @@ -1433,38 +1516,40 @@ export default class TradeStore extends BaseStore { } get is_crypto_multiplier() { - return this.contract_type === 'multiplier' && /^cry/.test(this.symbol); + return this.contract_type === 'multiplier' && this.symbol.startsWith('cry'); } - exportLayout(layout) { + exportLayout(layout: TChartLayout) { delete layout.previousMaxTicks; // TODO: fix it in smartcharts this.prev_chart_layout = layout; - this.prev_chart_layout.isDone = () => { - this.prev_chart_layout.is_used = true; - this.setChartStatus(false); - }; + if (this.prev_chart_layout) { + this.prev_chart_layout.isDone = () => { + if (this.prev_chart_layout) this.prev_chart_layout.is_used = true; + this.setChartStatus(false); + }; + } } // ---------- WS ---------- - wsSubscribe = (req, callback) => { - const passthrough_callback = (...args) => { + wsSubscribe = (req: TicksHistoryRequest, callback: (response: TTicksHistoryResponse) => void) => { + const passthrough_callback = (...args: [TTicksHistoryResponse]) => { callback(...args); if (this.is_accumulator) { let current_spot_data = {}; if ('tick' in args[0]) { - const { epoch, quote, symbol } = args[0].tick; + const { epoch, quote, symbol } = args[0].tick as TickSpotData; if (this.symbol !== symbol) return; current_spot_data = { current_spot: quote, current_spot_time: epoch, }; } else if ('history' in args[0]) { - const { prices, times } = args[0].history; + const { prices, times } = args[0].history as History; const symbol = args[0].echo_req.ticks_history; if (this.symbol !== symbol) return; current_spot_data = { - current_spot: prices[prices.length - 1], - current_spot_time: times[times.length - 1], + current_spot: prices?.[prices?.length - 1], + current_spot_time: times?.[times?.length - 1], }; } else { return; @@ -1479,21 +1564,21 @@ export default class TradeStore extends BaseStore { } }; - wsForget = req => { + wsForget = (req: TicksHistoryRequest) => { const key = JSON.stringify(req); if (g_subscribers_map[key]) { - g_subscribers_map[key].unsubscribe(); + g_subscribers_map[key]?.unsubscribe(); delete g_subscribers_map[key]; } // WS.forget('ticks_history', callback, match); }; - wsForgetStream = stream_id => { + wsForgetStream = (stream_id: string) => { WS.forgetStream(stream_id); }; - wsSendRequest = req => { - if (req.time) { + wsSendRequest = (req: TradingTimesRequest | ActiveSymbolsRequest | ServerTimeRequest) => { + if ('time' in req) { return ServerTime.timePromise().then(server_time => { if (server_time) { return { @@ -1504,16 +1589,16 @@ export default class TradeStore extends BaseStore { return WS.time(); }); } - if (req.active_symbols) { + if ('active_symbols' in req) { return WS.activeSymbols('brief'); } - if (req.trading_times) { + if ('trading_times' in req) { return WS.tradingTimes(req.trading_times); } return WS.storage.send(req); }; - chartStateChange(state, option) { + chartStateChange(state: string, option?: TChartStateChangeOption) { if ( state === STATE_TYPES.MARKET_STATE_CHANGE && this.is_trade_component_mounted && @@ -1522,20 +1607,16 @@ export default class TradeStore extends BaseStore { ) { this.prepareTradeStore(false); } - const { data, event_type } = getChartAnalyticsData(state, option); + const { data, event_type } = getChartAnalyticsData(state as keyof typeof STATE_TYPES, option) as TPayload; if (data) { RudderStack.track(event_type, { ...data, device_type: isMobile() ? 'mobile' : 'desktop', form_name: 'default', - }); + } as TEvents['ce_chart_types_form']); } } - refToAddTick = ref => { - this.addTickByProposal = ref; - }; - get has_alternative_source() { return this.is_multiplier && !!this.hovered_contract_type; } @@ -1556,23 +1637,26 @@ export default class TradeStore extends BaseStore { return isVanillaContract(this.contract_type); } - setContractPurchaseToastbox(response) { - const list = getAvailableContractTypes(this.contract_types_list, unsupported_contract_types_list); + setContractPurchaseToastbox(response: Buy) { + const list = getAvailableContractTypes( + this.contract_types_list, + unsupported_contract_types_list + ) as Array; - return (this.contract_purchase_toast_box = { + this.contract_purchase_toast_box = { key: true, buy_price: formatMoney(this.root_store.client.currency, response.buy_price, true, 0, 0), contract_type: this.contract_type, currency: getCurrencyDisplayCode(this.root_store.client.currency), list, - }); + }; } clearContractPurchaseToastBox() { this.contract_purchase_toast_box = undefined; } - async getFirstOpenMarket(markets_to_search) { + async getFirstOpenMarket(markets_to_search: string[]) { if (this.active_symbols?.length) { return findFirstOpenMarket(this.active_symbols, markets_to_search); } @@ -1584,7 +1668,11 @@ export default class TradeStore extends BaseStore { return findFirstOpenMarket(active_symbols, markets_to_search); } - setBarrierChoices(barrier_choices) { + setStakeBoundary(type: string, min_stake?: number, max_stake?: number) { + if (min_stake && max_stake) this.stake_boundary[type] = { min_stake, max_stake }; + } + + setBarrierChoices(barrier_choices: string[]) { this.barrier_choices = barrier_choices ?? []; if (this.is_turbos) { const stored_barriers_data = { barrier: this.barrier_1, barrier_choices }; @@ -1598,8 +1686,4 @@ export default class TradeStore extends BaseStore { this.strike_price_choices = { barrier: this.barrier_1, barrier_choices }; } } - - setStakeBoundary(type, min_stake, max_stake) { - if (min_stake && max_stake) this.stake_boundary[type] = { min_stake, max_stake }; - } } diff --git a/packages/trader/src/Stores/base-store.ts b/packages/trader/src/Stores/base-store.ts index 704efb2175f2..c66f6937d334 100644 --- a/packages/trader/src/Stores/base-store.ts +++ b/packages/trader/src/Stores/base-store.ts @@ -7,7 +7,7 @@ import { getValidationRules } from './Modules/Trading/Constants/validation-rules type TValidationRules = ReturnType | Record; type TBaseStoreOptions = { - root_store?: TCoreStores; + root_store: TCoreStores; local_storage_properties?: string[]; session_storage_properties?: string[]; validation_rules?: TValidationRules; @@ -33,21 +33,21 @@ export default class BaseStore { logout_listener: null | (() => Promise) = null; local_storage_properties: string[]; networkStatusChangeDisposer: null | (() => void) = null; - network_status_change_listener: null | ((is_online?: boolean) => void) = null; + network_status_change_listener: null | ((is_online: boolean) => void) = null; partial_fetch_time = 0; preSwitchAccountDisposer: null | (() => void) = null; pre_switch_account_listener: null | (() => Promise) = null; realAccountSignupEndedDisposer: null | (() => void) = null; real_account_signup_ended_listener: null | (() => Promise) = null; - root_store?: TCoreStores; + root_store: TCoreStores; session_storage_properties: string[]; store_name = ''; switchAccountDisposer: null | (() => void) = null; switch_account_listener: null | (() => Promise) = null; themeChangeDisposer: null | (() => void) = null; - theme_change_listener: null | ((is_dark_mode_on?: boolean) => void) = null; + theme_change_listener: null | ((is_dark_mode_on: boolean) => void) = null; validation_errors: { [key: string]: string[] } = {}; - validation_rules: TValidationRules = {}; + validation_rules: TValidationRules | Record = {}; /** * Constructor of the base class that gets properties' name of child which should be saved in storages * @@ -58,7 +58,7 @@ export default class BaseStore { * @property {Object} validation_rules - An object that contains the validation rules for each property of the store. * @property {String} store_name - Explicit store name for browser application storage (to bypass minification) */ - constructor(options: TBaseStoreOptions = {}) { + constructor(options = {} as TBaseStoreOptions) { makeObservable(this, { validation_errors: observable, validation_rules: observable, @@ -268,7 +268,7 @@ export default class BaseStore { * */ validateProperty(property: string, value: T[keyof T]) { - const validation_rules_for_property = this.validation_rules[property as keyof TValidationRules]; + const validation_rules_for_property = this.validation_rules[property] ?? {}; const trigger = 'trigger' in validation_rules_for_property ? validation_rules_for_property.trigger : undefined; const inputs = { [property]: value ?? this[property as keyof this] }; const validation_rules = { @@ -315,13 +315,13 @@ export default class BaseStore { this.switch_account_listener = listener; this.switchAccountDisposer = when( - () => !!this.root_store?.client.switch_broadcast, + () => !!this.root_store.client.switch_broadcast, () => { try { const result = this.switch_account_listener?.(); if (result?.then && typeof result.then === 'function') { result.then(() => { - this.root_store?.client.switchEndSignal(); + this.root_store.client.switchEndSignal(); this.onSwitchAccount(this.switch_account_listener); }); } else { @@ -343,13 +343,13 @@ export default class BaseStore { if (listener) { this.pre_switch_account_listener = listener; this.preSwitchAccountDisposer = when( - () => !!this.root_store?.client.pre_switch_broadcast, + () => !!this.root_store.client.pre_switch_broadcast, () => { try { const result = this.pre_switch_account_listener?.(); if (result?.then && typeof result.then === 'function') { result.then(() => { - this.root_store?.client.setPreSwitchAccount(false); + this.root_store.client.setPreSwitchAccount(false); this.onPreSwitchAccount(this.pre_switch_account_listener); }); } else { @@ -369,13 +369,13 @@ export default class BaseStore { onLogout(listener: null | (() => Promise)): void { this.logoutDisposer = when( - () => !!this.root_store?.client.has_logged_out, + () => !!this.root_store.client.has_logged_out, async () => { try { const result = this.logout_listener?.(); if (result?.then && typeof result.then === 'function') { result.then(() => { - this.root_store?.client.setLogout(false); + this.root_store.client.setLogout(false); this.onLogout(this.logout_listener); }); } else { @@ -395,13 +395,13 @@ export default class BaseStore { onClientInit(listener: null | (() => Promise)): void { this.clientInitDisposer = when( - () => !!this.root_store?.client.initialized_broadcast, + () => !!this.root_store.client.initialized_broadcast, async () => { try { const result = this.client_init_listener?.(); if (result?.then && typeof result.then === 'function') { result.then(() => { - this.root_store?.client.setInitialized(false); + this.root_store.client.setInitialized(false); this.onClientInit(this.client_init_listener); }); } else { @@ -419,9 +419,9 @@ export default class BaseStore { this.client_init_listener = listener; } - onNetworkStatusChange(listener: null | ((is_online?: boolean) => void)): void { + onNetworkStatusChange(listener: null | ((is_online: boolean) => void)): void { this.networkStatusChangeDisposer = reaction( - () => this.root_store?.common.is_network_online, + () => this.root_store.common.is_network_online, is_online => { try { this.network_status_change_listener?.(is_online); @@ -438,9 +438,9 @@ export default class BaseStore { this.network_status_change_listener = listener; } - onThemeChange(listener: null | ((is_dark_mode_on?: boolean) => void)): void { + onThemeChange(listener: null | ((is_dark_mode_on: boolean) => void)): void { this.themeChangeDisposer = reaction( - () => this.root_store?.ui.is_dark_mode_on, + () => this.root_store.ui.is_dark_mode_on, is_dark_mode_on => { try { this.theme_change_listener?.(is_dark_mode_on); @@ -459,13 +459,13 @@ export default class BaseStore { onRealAccountSignupEnd(listener: null | (() => Promise)): void { this.realAccountSignupEndedDisposer = when( - () => !!this.root_store?.ui.has_real_account_signup_ended, + () => !!this.root_store.ui.has_real_account_signup_ended, () => { try { const result = this.real_account_signup_ended_listener?.(); if (result?.then && typeof result.then === 'function') { result.then(() => { - this.root_store?.ui.setRealAccountSignupEnd(false); + this.root_store.ui.setRealAccountSignupEnd(false); this.onRealAccountSignupEnd(this.real_account_signup_ended_listener); }); } else { @@ -545,7 +545,7 @@ export default class BaseStore { assertHasValidCache(loginid: string, ...reactions: VoidFunction[]): void { // account was changed when this was unmounted. - if (this.root_store?.client.loginid !== loginid) { + if (this.root_store.client.loginid !== loginid) { reactions.forEach(act => act()); this.partial_fetch_time = 0; } diff --git a/packages/trader/src/Stores/useTraderStores.tsx b/packages/trader/src/Stores/useTraderStores.tsx index ff5ab4b5becd..5e4db0cd77c2 100644 --- a/packages/trader/src/Stores/useTraderStores.tsx +++ b/packages/trader/src/Stores/useTraderStores.tsx @@ -1,128 +1,8 @@ import React from 'react'; import { useStore } from '@deriv/stores'; import TradeStore from './Modules/Trading/trade-store'; -import moment from 'moment'; -type TTextValueStrings = { - text: string; - value: string; -}; - -type TContractTypesList = { - name: string; - categories: TTextValueStrings[]; -}; - -type TContractCategoriesList = { - Multipliers: TContractTypesList; - 'Ups & Downs': TContractTypesList; - 'Highs & Lows': TContractTypesList; - 'Ins & Outs': TContractTypesList; - 'Look Backs': TContractTypesList; - Digits: TContractTypesList; - Vanillas: TContractTypesList; - Accumulators: TContractTypesList; -}; - -type TToastBoxListItem = { - contract_category: string; - contract_types: [ - { - text: string; - value: string; - } - ]; -}; - -type TToastBoxObject = { - key?: boolean; - buy_price?: number; - currency?: string; - contract_type?: string; - list?: TToastBoxListItem[]; -}; - -type TOverrideTradeStore = Omit< - TradeStore, - | 'accumulator_range_list' - | 'barriers' - | 'basis_list' - | 'cancellation_price' - | 'cancellation_range_list' - | 'clearContractPurchaseToastBox' - | 'contract_purchase_toast_box' - | 'contract_types_list' - | 'duration_min_max' - | 'duration_units_list' - | 'expiry_date' - | 'expiry_time' - | 'expiry_type' - | 'form_components' - | 'market_close_times' - | 'market_open_times' - | 'multiplier_range_list' - | 'multiplier' - | 'sessions' - | 'setIsTradeParamsExpanded' - | 'start_dates_list' - | 'start_time' - | 'symbol' - | 'take_profit' - | '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[]; - long_barriers: Record | { barrier: string; barrier_choices: string[] }; - market_open_times: string[]; - market_close_times: string[]; - multiplier: number; - multiplier_range_list: number[]; - proposal_info: { - [key: string]: { - 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 }>; - setIsTradeParamsExpanded: (value: boolean) => void; - short_barriers: Record | { barrier: string; barrier_choices: string[] }; - strike_price_choices: Record | { barrier: string; barrier_choices: string[] }; - start_dates_list: Array<{ text: string; value: number }>; - start_time?: string | null; - symbol: string; - take_profit?: string; - ticks_history_stats: { - ticks_stayed_in?: number[]; - last_tick_epoch?: number; - }; - trade_types: { [key: string]: string }; -}; - -const TraderStoreContext = React.createContext(null); +const TraderStoreContext = React.createContext(null); export const TraderStoreProvider = ({ children }: React.PropsWithChildren) => { const { modules } = useStore(); diff --git a/packages/trader/src/Types/common-prop.type.ts b/packages/trader/src/Types/common-prop.type.ts index bc38b04c55e2..d2252c3012d8 100644 --- a/packages/trader/src/Types/common-prop.type.ts +++ b/packages/trader/src/Types/common-prop.type.ts @@ -1,19 +1,16 @@ 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 TTextValueStrings = { + text: string; + value: string; +}; +export type TTextValueNumber = { + text: string; + value: number; }; +export type TProposalTypeInfo = TTradeStore['proposal_info'][string]; + export type TError = { error?: { code?: string; diff --git a/packages/trader/src/Utils/Validator/validator.ts b/packages/trader/src/Utils/Validator/validator.ts index c0a840093d41..c51f76b38287 100644 --- a/packages/trader/src/Utils/Validator/validator.ts +++ b/packages/trader/src/Utils/Validator/validator.ts @@ -6,8 +6,8 @@ type TOptions = { [key: string]: unknown; decimals?: string | number; is_required?: boolean; - max?: number | string; - min?: number | string; + max?: number | string | null; + min?: number | string | null; name1?: string; name2?: string; regex?: RegExp; @@ -17,9 +17,14 @@ type TOptions = { type TInitPreBuildDVRs = ReturnType; export type TRuleOptions = { - func: (value: string | number, options?: TOptions, store?: TTradeStore, inputs?: unknown) => boolean; - condition: (store: TTradeStore) => boolean; - message: string; + func?: ( + value: T, + options?: TOptions, + store?: TTradeStore, + inputs?: Pick + ) => boolean | { is_ok: boolean; message: string }; + condition?: (store: TTradeStore) => boolean; + message?: string; } & TOptions; type TRule = string | Array; @@ -30,13 +35,13 @@ type TValidationResult = { }; class Validator { - input: Partial; + input: Pick; rules: Partial; store: TTradeStore; errors: Errors; error_count: number; - constructor(input: Partial, rules: Partial, store: TTradeStore) { + constructor(input: Pick, rules: Partial, store: TTradeStore) { this.input = input; this.rules = rules; this.store = store; @@ -99,8 +104,8 @@ class Validator { let is_valid, error_message; if (ruleObject.name === 'number') { - const { is_ok, message }: TValidationResult = ruleObject.validator( - this.input[attribute as keyof TTradeStore], + const { is_ok, message } = ruleObject.validator( + this.input[attribute as keyof TTradeStore] as string, ruleObject.options, this.store, this.input @@ -109,7 +114,7 @@ class Validator { error_message = message; } else { is_valid = ruleObject.validator( - this.input[attribute as keyof TTradeStore], + this.input[attribute as keyof TTradeStore] as string, ruleObject.options, this.store, this.input @@ -152,12 +157,7 @@ class Validator { : ( getPreBuildDVRs() as unknown as { [key: string]: { - func: ( - value: string | number, - options?: TRuleOptions, - store?: TTradeStore, - inputs?: unknown - ) => boolean | { is_ok: boolean; message: string }; + func: TRuleOptions['func']; }; } )[rule_object_name].func,