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 9ccf910a4274..2f72de4633be 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 @@ -26,7 +26,7 @@ export type TGeneralContractCardBodyProps = { current_focus?: string | null; error_message_alignment?: string; getCardLabels: TGetCardLables; - getContractById: (contract_id?: number) => TContractStore; + getContractById: (contract_id: number) => TContractStore; should_show_cancellation_warning: boolean; has_progress_slider: boolean; is_mobile: boolean; diff --git a/packages/components/src/components/contract-card/contract-card-items/toggle-card-dialog.tsx b/packages/components/src/components/contract-card/contract-card-items/toggle-card-dialog.tsx index 2d3fbea6e2bb..ce87b3690213 100644 --- a/packages/components/src/components/contract-card/contract-card-items/toggle-card-dialog.tsx +++ b/packages/components/src/components/contract-card/contract-card-items/toggle-card-dialog.tsx @@ -60,7 +60,7 @@ const ToggleCardDialog = ({ const toggle_ref = React.useRef(null); const dialog_ref = React.useRef(null); - const contract = getContractById(contract_id); + const contract = getContractById(Number(contract_id)); React.useEffect(() => { ContractUpdateFormWrapper = connectWithContractUpdate?.(ContractUpdateForm) || ContractUpdateForm; diff --git a/packages/components/src/components/contract-card/contract-card.tsx b/packages/components/src/components/contract-card/contract-card.tsx index 2213afe58b48..77a54f5f9ecb 100644 --- a/packages/components/src/components/contract-card/contract-card.tsx +++ b/packages/components/src/components/contract-card/contract-card.tsx @@ -18,7 +18,7 @@ type TContractCardProps = { is_multiplier: boolean; is_positions: boolean; is_unsupported: boolean; - onClickRemove: (contract_id?: number) => void; + onClickRemove: (contract_id: number) => void; profit_loss: number; result: string; should_show_result_overlay: boolean; 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 0762e3eff3aa..25be6f8cf87a 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 @@ -14,7 +14,7 @@ type TResultOverlayProps = { 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(contract_id)} + onClick={() => onClickRemove(Number(contract_id))} /> )} {getContractPath && ( diff --git a/packages/components/src/components/data-list/data-list-cell.tsx b/packages/components/src/components/data-list/data-list-cell.tsx index 27e5dabc9f90..241e0d046b4b 100644 --- a/packages/components/src/components/data-list/data-list-cell.tsx +++ b/packages/components/src/components/data-list/data-list-cell.tsx @@ -1,7 +1,7 @@ import React from 'react'; import classNames from 'classnames'; import { isTurbosContract, isVanillaContract } from '@deriv/shared'; -import { TPassThrough, TRow } from './data-list'; +import { TPassThrough, TRow } from '../types/common.types'; export type TColIndex = | 'type' @@ -36,22 +36,24 @@ export type TDataListCell = { className?: string; column?: { key?: string; - title?: React.ReactNode; - col_index: TColIndex; + title?: string; + col_index?: TColIndex; renderCellContent?: (props: TRenderCellContent) => React.ReactNode; - renderHeader?: (props: THeaderProps) => React.ReactNode; + renderHeader?: (prop: renderHeaderType) => React.ReactNode; }; is_footer?: boolean; passthrough?: TPassThrough; - row: TRow; + row?: TRow; }; +type renderHeaderType = { title?: string; is_vanilla?: boolean }; + const DataListCell = ({ className, column, is_footer, passthrough, row }: TDataListCell) => { if (!column) return null; const { col_index, title } = column; - const cell_value = row[col_index]; - const is_turbos = isTurbosContract(row.contract_info?.contract_type); - const is_vanilla = isVanillaContract(row.contract_info?.contract_type); + const cell_value = row?.[col_index as TColIndex]; + const is_turbos = isTurbosContract(row?.contract_info?.contract_type); + const is_vanilla = isVanillaContract(row?.contract_info?.contract_type); return (
@@ -66,7 +68,7 @@ const DataListCell = ({ className, column, is_footer, passthrough, row }: TDataL cell_value, is_footer, passthrough, - row_obj: row, + row_obj: row as TRow, is_vanilla, is_turbos, }) diff --git a/packages/components/src/components/data-list/data-list-row.tsx b/packages/components/src/components/data-list/data-list-row.tsx index ed96acdd15fe..37189cc6c5d1 100644 --- a/packages/components/src/components/data-list/data-list-row.tsx +++ b/packages/components/src/components/data-list/data-list-row.tsx @@ -2,7 +2,9 @@ import classNames from 'classnames'; import React from 'react'; import { NavLink } from 'react-router-dom'; import { useIsMounted } from '@deriv/shared'; -import { TPassThrough, TRow, TRowRenderer } from './data-list'; +import { TRowRenderer } from './data-list'; +import { TPassThrough } from '../types/common.types'; +import { TSource } from '../data-table/data-table'; type TDataListRow = { action_desc?: { @@ -17,7 +19,7 @@ type TDataListRow = { is_new_row: boolean; is_scrolling: boolean; passthrough?: TPassThrough; - row: TRow; + row: TSource; }; const DataListRow = ({ diff --git a/packages/components/src/components/data-list/data-list.tsx b/packages/components/src/components/data-list/data-list.tsx index 79c606efbc8e..01d829796f3b 100644 --- a/packages/components/src/components/data-list/data-list.tsx +++ b/packages/components/src/components/data-list/data-list.tsx @@ -17,6 +17,12 @@ import DataListCell, { TColIndex, TDataListCell } from './data-list-cell'; import DataListRow from './data-list-row'; import ThemedScrollbars from '../themed-scrollbars'; import { MeasuredCellParent } from 'react-virtualized/dist/es/CellMeasurer'; +import { TTableRowItem, TPassThrough, TRow } from '../types/common.types'; + +const List = _List as unknown as React.FC; +const AutoSizer = _AutoSizer as unknown as React.FC; +const CellMeasurer = _CellMeasurer as unknown as React.FC; +export type TRowRenderer = (params: Partial) => React.ReactNode; type TMobileRowRenderer = { row?: TRow; @@ -26,19 +32,14 @@ type TMobileRowRenderer = { onClickCancel: (contract_id?: number) => void; onClickSell: (contract_id?: number) => void; measure?: () => void; + passthrough?: TPassThrough; }; -const List = _List as unknown as React.FC; -const AutoSizer = _AutoSizer as unknown as React.FC; -const CellMeasurer = _CellMeasurer as unknown as React.FC; -export type TRowRenderer = (params: Partial) => React.ReactNode; -export type TPassThrough = { isTopUp: (item: TRow) => boolean }; -export type TRow = { [key: string]: any }; export type TDataList = { className?: string; data_source: TRow[]; footer?: TRow; - getRowAction?: (row: TRow) => { component: JSX.Element } | string; + getRowAction?: (row: TRow) => TTableRowItem; getRowSize?: (params: { index: number }) => number; keyMapper?: (row: TRow) => number | string; onRowsRendered?: (params: IndexRange) => void; @@ -121,6 +122,7 @@ const DataList = React.memo( const getContent = ({ measure }: GetContentType = {}) => ( TTableRowItem[]; getRowSize?: ((params: { index: number }) => number) | number; measure?: () => void; - getRowAction?: (row: Record) => { component: JSX.Element } | string; + getRowAction?: (item: TSource) => TTableRowItem; onScroll?: React.UIEventHandler; id?: number; - passthrough?: (item: TSource) => boolean; + passthrough?: React.ComponentProps['passthrough']; autoHide?: boolean; - footer: Record; - preloaderCheck: (param: TSource) => boolean; - data_source: TSource[]; + footer?: Record | React.ReactNode; + preloaderCheck?: (param: TSource) => boolean; + data_source?: TSource[]; keyMapper?: (row: TSource) => number | string; }; @@ -82,7 +82,8 @@ const DataTable = ({ cache_ref.current = new CellMeasurerCache({ fixedWidth: true, keyMapper: row_index => { - if (row_index < data_source.length) return keyMapper?.(data_source[row_index]) || row_index; + if (data_source && row_index < data_source.length) + return keyMapper?.(data_source[row_index]) || row_index; return row_index; }, }); @@ -101,10 +102,13 @@ const DataTable = ({ }; const rowRenderer = ({ style, index, key, parent }: TRowRenderer) => { - const item = data_source[index]; - const action = getRowAction && getRowAction(item); - const contract_id = item.contract_id || item.id; - const row_key = keyMapper?.(item) || key; + const item = data_source?.[index]; + const contract_id = (item?.contract_id || item?.id) as string; + let action: TTableRowItem | undefined, row_key; + if (item) { + action = getRowAction && getRowAction(item); + row_key = keyMapper?.(item) || key; + } // If row content is complex, consider rendering a light-weight placeholder while scrolling. const getContent = ({ measure }: TMeasure) => ( @@ -119,7 +123,7 @@ const DataTable = ({ passthrough={passthrough} replace={typeof action === 'object' ? action : undefined} row_obj={item} - show_preloader={typeof preloaderCheck === 'function' ? preloaderCheck(item) : false} + show_preloader={typeof preloaderCheck === 'function' && item ? preloaderCheck(item) : false} to={typeof action === 'string' ? action : undefined} is_dynamic_height={is_dynamic_height} is_footer={false} @@ -185,7 +189,7 @@ const DataTable = ({ height={height} overscanRowCount={1} ref={(ref: Grid) => (list_ref.current = ref)} - rowCount={data_source.length} + rowCount={data_source?.length || 0} rowHeight={ is_dynamic_height && cache_ref?.current?.rowHeight ? cache_ref?.current?.rowHeight diff --git a/packages/components/src/components/data-table/table-row-info.tsx b/packages/components/src/components/data-table/table-row-info.tsx index 33c45309eba5..79f94638821e 100644 --- a/packages/components/src/components/data-table/table-row-info.tsx +++ b/packages/components/src/components/data-table/table-row-info.tsx @@ -31,7 +31,7 @@ const TableRowInfo = ({ replace, is_footer, cells, className, is_dynamic_height, onClick={is_footer || !replace ? undefined : toggleDetails} className={classNames(className, { 'statement__row--detail': show_details })} > - {show_details ?
{replace?.component}
: cells} + {show_details && typeof replace === 'object' ?
{replace?.component}
: cells}
); } @@ -40,7 +40,7 @@ const TableRowInfo = ({ replace, is_footer, cells, className, is_dynamic_height, onClick={is_footer || !replace ? undefined : toggleDetails} className={classNames(className, { 'statement__row--detail': show_details })} > - {show_details ? ( + {show_details && typeof replace === 'object' ? (
{replace?.component}
diff --git a/packages/components/src/components/data-table/table-row.tsx b/packages/components/src/components/data-table/table-row.tsx index f69acab953db..07a723467826 100644 --- a/packages/components/src/components/data-table/table-row.tsx +++ b/packages/components/src/components/data-table/table-row.tsx @@ -11,7 +11,7 @@ type TTableRow = { id?: string; is_footer: boolean; is_header?: boolean; - passthrough?: (item: TSource) => boolean; + passthrough?: { isTopUp: (item: TSource) => boolean }; replace?: TTableRowItem; to?: string; show_preloader?: boolean; diff --git a/packages/components/src/components/dialog/dialog.tsx b/packages/components/src/components/dialog/dialog.tsx index 06295f4e5496..e45ceba656d4 100644 --- a/packages/components/src/components/dialog/dialog.tsx +++ b/packages/components/src/components/dialog/dialog.tsx @@ -26,7 +26,7 @@ type TDialog = { onConfirm: () => void; onEscapeButtonCancel?: () => void; portal_element_id?: string; - title?: string; + title?: string | JSX.Element; }; const Dialog = ({ diff --git a/packages/components/src/components/filter-dropdown/filter-dropdown.tsx b/packages/components/src/components/filter-dropdown/filter-dropdown.tsx index be1c618f5601..a73265a9aa5e 100644 --- a/packages/components/src/components/filter-dropdown/filter-dropdown.tsx +++ b/packages/components/src/components/filter-dropdown/filter-dropdown.tsx @@ -13,14 +13,14 @@ type TListItem = { }; type TFilterDropdown = { - dropdown_className: string; + dropdown_className?: string; dropdown_display_className: string; filter_list: Array; handleFilterChange: (e: string) => void; - initial_filter: string; + initial_filter?: string; initial_selected_filter: string; - label: string; - hide_top_placeholder: boolean; + label?: string; + hide_top_placeholder?: boolean; }; const FilterDropdown = ({ diff --git a/packages/components/src/components/infinite-data-list/infinite-data-list.tsx b/packages/components/src/components/infinite-data-list/infinite-data-list.tsx index 23f625d67ea1..883115edc59b 100644 --- a/packages/components/src/components/infinite-data-list/infinite-data-list.tsx +++ b/packages/components/src/components/infinite-data-list/infinite-data-list.tsx @@ -1,20 +1,20 @@ import React from 'react'; import { InfiniteLoader as _InfiniteLoader, InfiniteLoaderProps, Index, IndexRange } from 'react-virtualized'; -import DataList, { TRow, TRowRenderer } from '../data-list/data-list'; +import DataList from '../data-list/data-list'; const InfiniteLoader = _InfiniteLoader as unknown as React.FC; -type TInfiniteDatalist = { + +type TInfiniteDatalist = Pick< + React.ComponentProps, + 'getRowSize' | 'onRowsRendered' | 'onScroll' | 'overscanRowCount' | 'rowRenderer' +> & { className: string; data_list_className: string; has_more_items_to_load: boolean; - items: TRow[]; - keyMapperFn?: (row: TRow) => number | string; - loadMoreRowsFn: (params: IndexRange) => Promise; - onScroll: () => void; - rowRenderer: TRowRenderer; has_filler: boolean; - overscanRowCount: number; - getRowSize?: (params: { index: number }) => number; + items: React.ComponentProps['data_source']; + keyMapperFn: React.ComponentProps['keyMapper']; + loadMoreRowsFn: (params: IndexRange) => Promise; }; const InfiniteDataList = ({ diff --git a/packages/components/src/components/input-field/input-field.tsx b/packages/components/src/components/input-field/input-field.tsx index a7b60b333d7c..67859d60be00 100644 --- a/packages/components/src/components/input-field/input-field.tsx +++ b/packages/components/src/components/input-field/input-field.tsx @@ -23,7 +23,7 @@ type TInputField = { classNamePrefix?: string; classNameWrapper?: string; // CSS class for the component wrapper currency: string; - current_focus: string; + current_focus?: string | null; data_testid?: string; data_tip?: string; data_value?: string; diff --git a/packages/components/src/components/input-field/input.tsx b/packages/components/src/components/input-field/input.tsx index 126a164b16ca..3547ddef0aa9 100644 --- a/packages/components/src/components/input-field/input.tsx +++ b/packages/components/src/components/input-field/input.tsx @@ -13,7 +13,7 @@ type TInputProps = { className?: string; classNameDynamicSuffix?: string; classNameInlinePrefix?: string; - current_focus: string; + current_focus?: string | null; data_testid?: string; data_tip?: string; data_value?: number | string; 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 a929e246887a..fd9ed4667948 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 @@ -23,9 +23,9 @@ type TPositionsDrawerCardProps = { contract_info: TContractInfo; contract_update?: TContractInfo['contract_update']; currency: string; - current_focus: string; + current_focus: string | null; display_name?: string; - getContractById: (contract_id?: number) => TContractStore; + getContractById: (contract_id: number) => TContractStore; is_mobile?: boolean; is_sell_requested?: boolean; is_unsupported?: boolean; @@ -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/types/common.types.ts b/packages/components/src/components/types/common.types.ts index 8b459a97081b..65048a670689 100644 --- a/packages/components/src/components/types/common.types.ts +++ b/packages/components/src/components/types/common.types.ts @@ -13,4 +13,13 @@ export type TItem = { value: Array | string; }; -export type TTableRowItem = { component: React.ReactNode }; +export type TTableRowItem = + | { + message?: string; + component?: React.ReactElement; + } + | string; + +export type TRow = { [key: string]: any }; + +export type TPassThrough = { isTopUp: (item: TRow) => boolean }; diff --git a/packages/components/src/components/types/contract.types.ts b/packages/components/src/components/types/contract.types.ts index 212a926b300b..090e2607aa33 100644 --- a/packages/components/src/components/types/contract.types.ts +++ b/packages/components/src/components/types/contract.types.ts @@ -1,11 +1,11 @@ export type TGetContractPath = (contract_id?: number) => string; export type TToastConfig = { - key?: string; - content: string; - timeout?: number; + key: string; + content: string | React.ReactNode; is_bottom?: boolean; - type?: string; + timeout?: number; + type: string; }; export type TErrorMessages = Readonly<{ diff --git a/packages/components/stories/contract-card/statics/contract.js b/packages/components/stories/contract-card/statics/contract.js index 7e671ff54a61..67e040c8a65a 100644 --- a/packages/components/stories/contract-card/statics/contract.js +++ b/packages/components/stories/contract-card/statics/contract.js @@ -128,14 +128,6 @@ export const getUnsupportedContracts = () => ({ name: 'Low Tick', position: 'bottom', }, - ASIANU: { - name: 'Asian Up', - position: 'top', - }, - ASIAND: { - name: 'Asian Down', - position: 'bottom', - }, LBFLOATCALL: { name: 'Close-to-Low', position: 'top', @@ -254,6 +246,14 @@ export const getSupportedContracts = is_high_low => ({ name: 'Goes Outside', position: 'bottom', }, + ASIANU: { + name: 'Asian Up', + position: 'top', + }, + ASIAND: { + name: 'Asian Down', + position: 'bottom', + }, }); export const getContractConfig = is_high_low => ({ diff --git a/packages/core/src/App/Containers/Redirect/redirect.jsx b/packages/core/src/App/Containers/Redirect/redirect.jsx index 530bf682a9e6..1cb2d9c80f07 100644 --- a/packages/core/src/App/Containers/Redirect/redirect.jsx +++ b/packages/core/src/App/Containers/Redirect/redirect.jsx @@ -10,7 +10,6 @@ const Redirect = ({ currency, setVerificationCode, verification_code, - hasAnyRealAccount, openRealAccountSignup, setResetTradingPasswordModalOpen, toggleAccountSignupModal, @@ -24,7 +23,12 @@ const Redirect = ({ let redirected_to_route = false; const action_param = url_params.get('action'); const code_param = url_params.get('code') || verification_code[action_param]; + const ext_platform_url = url_params.get('ext_platform_url'); + const redirectToExternalPlatform = url => { + history.push(`${routes.root}?ext_platform_url=${url}`); + redirected_to_route = true; + }; setVerificationCode(code_param, action_param); setNewEmail(url_params.get('email'), action_param); @@ -119,14 +123,9 @@ const Redirect = ({ case 'add_account': { WS.wait('get_account_status').then(() => { if (!currency) return openRealAccountSignup('set_currency'); - if (hasAnyRealAccount()) return openRealAccountSignup('manage'); return openRealAccountSignup('svg'); }); - const ext_platform_url = url_params.get('ext_platform_url'); - if (ext_platform_url) { - history.push(`${routes.root}?ext_platform_url=${ext_platform_url}`); - redirected_to_route = true; - } + if (ext_platform_url) redirectToExternalPlatform(ext_platform_url); break; } case 'add_account_multiplier': { @@ -134,11 +133,14 @@ const Redirect = ({ if (!currency) return openRealAccountSignup('set_currency'); return openRealAccountSignup('maltainvest'); }); - const ext_platform_url = url_params.get('ext_platform_url'); - if (ext_platform_url) { - history.push(`${routes.root}?ext_platform_url=${ext_platform_url}`); - redirected_to_route = true; - } + if (ext_platform_url) redirectToExternalPlatform(ext_platform_url); + break; + } + case 'manage_account': { + WS.wait('get_account_status').then(() => { + return openRealAccountSignup('manage'); + }); + if (ext_platform_url) redirectToExternalPlatform(ext_platform_url); break; } case 'verification': { @@ -184,7 +186,6 @@ Redirect.propTypes = { currency: PropTypes.string, loginid: PropTypes.string, getServerTime: PropTypes.object, - hasAnyRealAccount: PropTypes.bool, history: PropTypes.object, openRealAccountSignup: PropTypes.func, setResetTradingPasswordModalOpen: PropTypes.func, @@ -204,7 +205,6 @@ export default withRouter( setVerificationCode: client.setVerificationCode, verification_code: client.verification_code, fetchResidenceList: client.fetchResidenceList, - hasAnyRealAccount: client.hasAnyRealAccount, openRealAccountSignup: ui.openRealAccountSignup, setResetTradingPasswordModalOpen: ui.setResetTradingPasswordModalOpen, toggleAccountSignupModal: ui.toggleAccountSignupModal, diff --git a/packages/reports/build/webpack.config.js b/packages/reports/build/webpack.config.js index abe0e97d799b..8837014558a2 100644 --- a/packages/reports/build/webpack.config.js +++ b/packages/reports/build/webpack.config.js @@ -40,7 +40,6 @@ module.exports = function (env) { 'react-router-dom': 'react-router-dom', 'react-router': 'react-router', mobx: 'mobx', - 'mobx-react': 'mobx-react', '@deriv/shared': '@deriv/shared', '@deriv/components': '@deriv/components', '@deriv/translations': '@deriv/translations', diff --git a/packages/reports/package.json b/packages/reports/package.json index 1f1e18eb1b98..8dc811b193b6 100644 --- a/packages/reports/package.json +++ b/packages/reports/package.json @@ -76,7 +76,6 @@ }, "dependencies": { "@deriv/components": "^1.0.0", - "@deriv/deriv-api": "^1.0.11", "@deriv/shared": "^1.0.0", "@deriv/stores": "^1.0.0", "@deriv/translations": "^1.0.0", @@ -95,8 +94,6 @@ "lodash.debounce": "^4.0.8", "lodash.throttle": "^4.1.1", "mobx": "^6.6.1", - "mobx-react": "^7.5.1", - "mobx-utils": "^6.0.5", "moment": "^2.29.2", "null-loader": "^4.0.1", "object.fromentries": "^2.0.0", diff --git a/packages/reports/src/Components/Form/CompositeCalendar/composite-calendar-mobile.tsx b/packages/reports/src/Components/Form/CompositeCalendar/composite-calendar-mobile.tsx index 3e53511c6bc8..d059be4877e1 100644 --- a/packages/reports/src/Components/Form/CompositeCalendar/composite-calendar-mobile.tsx +++ b/packages/reports/src/Components/Form/CompositeCalendar/composite-calendar-mobile.tsx @@ -44,14 +44,14 @@ export const RadioButton = ({ id, className, selected_value, value, label, onCha const CUSTOM_KEY = 'custom'; type TCompositeCalendarMobile = { - input_date_range: TInputDateRange; - current_focus: string; - duration_list: Array; + input_date_range?: TInputDateRange; + current_focus?: string; + duration_list?: Array; onChange: ( value: { from?: moment.Moment; to?: moment.Moment; is_batch?: boolean }, extra_data?: { date_range: TInputDateRange } ) => void; - setCurrentFocus: (focus: string) => void; + setCurrentFocus?: (focus: string) => void; from: number; to: number; }; @@ -66,7 +66,7 @@ const CompositeCalendarMobile = React.memo( from, to, }: TCompositeCalendarMobile) => { - const date_range = input_date_range || duration_list.find(range => range.value === 'all_time'); + const date_range = input_date_range || duration_list?.find(range => range.value === 'all_time'); const [from_date, setFrom] = React.useState(from ? toMoment(from).format('YYYY-MM-DD') : undefined); const [to_date, setTo] = React.useState(to ? toMoment(to).format('YYYY-MM-DD') : undefined); @@ -97,7 +97,7 @@ const CompositeCalendarMobile = React.memo( const new_from = from_date || to_date || today; const new_to = to_date || today; - const new_date_range = Object.assign(selected_date_range, { + const new_date_range = Object.assign(selected_date_range as TInputDateRange, { label: `${toMoment(new_from).format('DD MMM YYYY')} - ${toMoment(new_to).format('DD MMM YYYY')}`, }); @@ -114,9 +114,9 @@ const CompositeCalendarMobile = React.memo( }; const applyDateRange = () => { - if (selected_date_range.onClick) { + if (selected_date_range?.onClick) { selectDateRange(selected_date_range); - } else if (selected_date_range.value === CUSTOM_KEY) { + } else if (selected_date_range?.value === CUSTOM_KEY) { selectCustomDateRange(); } setAppliedDateRange(selected_date_range); @@ -166,7 +166,7 @@ const CompositeCalendarMobile = React.memo( const onDateRangeChange = (_date_range: TInputDateRange) => { setSelectedDateRange( - duration_list.find(range => _date_range && range.value === _date_range.value) || _date_range + duration_list?.find(range => _date_range && range.value === _date_range.value) || _date_range ); }; @@ -185,7 +185,7 @@ const CompositeCalendarMobile = React.memo( icon={() => } onClick={openDialog} setCurrentFocus={setCurrentFocus} - value={applied_date_range.label} + value={applied_date_range?.label} />
- {duration_list.map(duration => ( + {duration_list?.map(duration => ( ))} @@ -216,7 +216,7 @@ const CompositeCalendarMobile = React.memo( className='composite-calendar-modal__custom-radio' value={CUSTOM_KEY} label={localize('Custom')} - selected_value={selected_date_range.value} + selected_value={selected_date_range?.value} onChange={onDateRangeChange} /> diff --git a/packages/reports/src/Components/Form/CompositeCalendar/composite-calendar.tsx b/packages/reports/src/Components/Form/CompositeCalendar/composite-calendar.tsx index 8523b9917dee..0a2611a578b1 100644 --- a/packages/reports/src/Components/Form/CompositeCalendar/composite-calendar.tsx +++ b/packages/reports/src/Components/Form/CompositeCalendar/composite-calendar.tsx @@ -3,18 +3,15 @@ import Loadable from 'react-loadable'; import { DesktopWrapper, InputField, MobileWrapper, useOnClickOutside } from '@deriv/components'; import { localize } from '@deriv/translations'; import { daysFromTodayTo, toMoment } from '@deriv/shared'; -import { connect } from 'Stores/connect'; -import type { TCoreStores } from '@deriv/stores/types'; import CompositeCalendarMobile from './composite-calendar-mobile'; import SideList from './side-list'; import CalendarIcon from './calendar-icon'; import TwoMonthPicker from './two-month-picker'; import moment from 'moment'; +import { observer, useStore } from '@deriv/stores'; type TCompositeCalendar = { - current_focus: string; onChange: (values: { to?: moment.Moment; from?: moment.Moment; is_batch?: boolean }) => void; - setCurrentFocus: () => void; to: number; from: number; }; @@ -34,8 +31,10 @@ const TwoMonthPickerLoadable = Loadable = props => { - const { current_focus, onChange, setCurrentFocus, to, from } = props; +const CompositeCalendar = observer((props: TCompositeCalendar) => { + const { ui } = useStore(); + const { current_focus, setCurrentFocus } = ui; + const { onChange, to, from } = props; const [show_to, setShowTo] = React.useState(false); const [show_from, setShowFrom] = React.useState(false); @@ -111,7 +110,7 @@ const CompositeCalendar: React.FC = props => { useOnClickOutside( wrapper_ref, - event => { + (event: React.MouseEvent) => { event?.stopPropagation(); event?.preventDefault(); hideCalendar(); @@ -181,13 +180,8 @@ const CompositeCalendar: React.FC = props => { ); -}; +}); CompositeCalendar.displayName = 'CompositeCalendar'; -export default React.memo( - connect(({ ui }: TCoreStores) => ({ - current_focus: ui.current_focus, - setCurrentFocus: ui.setCurrentFocus, - }))(CompositeCalendar) -); +export default React.memo(CompositeCalendar); diff --git a/packages/reports/src/Components/account-statistics.tsx b/packages/reports/src/Components/account-statistics.tsx index 2a1b605c4e51..f731df75264c 100644 --- a/packages/reports/src/Components/account-statistics.tsx +++ b/packages/reports/src/Components/account-statistics.tsx @@ -1,17 +1,15 @@ import React from 'react'; import { MobileWrapper, Money, Text } from '@deriv/components'; import { localize } from '@deriv/translations'; -import { connect } from 'Stores/connect'; +import { observer, useStore } from '@deriv/stores'; +import { useReportsStore } from 'Stores/useReportsStores'; -type TAccountStatistics = { - account_statistics: { - total_withdrawals: number; - total_deposits: number; - }; - currency: string; -}; +const AccountStatistics = observer(() => { + const { client } = useStore(); + const { statement } = useReportsStore(); + const { currency } = client; + const { account_statistics } = statement; -const AccountStatistics = ({ account_statistics, currency }: TAccountStatistics) => { return (
@@ -64,10 +62,6 @@ const AccountStatistics = ({ account_statistics, currency }: TAccountStatistics)
); -}; +}); -// TODO: implement reports store TRootStore in types.ts -export default connect(({ modules, client }: any) => ({ - account_statistics: modules.statement.account_statistics, - currency: client.currency, -}))(AccountStatistics); +export default AccountStatistics; diff --git a/packages/reports/src/Components/filter-component.tsx b/packages/reports/src/Components/filter-component.tsx index 608c1e6de178..3910805d547d 100644 --- a/packages/reports/src/Components/filter-component.tsx +++ b/packages/reports/src/Components/filter-component.tsx @@ -1,33 +1,14 @@ import React from 'react'; import { FilterDropdown } from '@deriv/components'; import { localize } from '@deriv/translations'; -import { connect } from 'Stores/connect'; import CompositeCalendar from './Form/CompositeCalendar'; -import { TRootStore } from 'Stores/index'; +import { observer } from '@deriv/stores'; +import { useReportsStore } from 'Stores/useReportsStores'; -type TFilterComponent = { - action_type: string; - date_from: number; - date_to: number; - filtered_date_range: { - duration: number; - label: string; - onClick?: () => void; - value?: string; - }; - handleDateChange: () => void; - handleFilterChange: () => void; - suffix_icon: string; -}; +const FilterComponent = observer(() => { + const { statement } = useReportsStore(); + const { action_type, date_from, date_to, handleFilterChange, handleDateChange } = statement; -const FilterComponent = ({ - action_type, - date_from, - date_to, - handleFilterChange, - handleDateChange, - filtered_date_range, -}: TFilterComponent) => { const filter_list = [ { text: localize('All transactions'), @@ -57,12 +38,7 @@ const FilterComponent = ({ return ( - + ); -}; +}); -export default connect(({ modules }: TRootStore) => ({ - action_type: modules.statement.action_type, - data: modules.statement.data, - date_from: modules.statement.date_from, - date_to: modules.statement.date_to, - filtered_date_range: modules.statement.filtered_date_range, - handleDateChange: modules.statement.handleDateChange, - handleFilterChange: modules.statement.handleFilterChange, -}))(FilterComponent); +export default FilterComponent; diff --git a/packages/reports/src/Components/indicative-cell.tsx b/packages/reports/src/Components/indicative-cell.tsx index fcf20ab2ccdf..dc422d878a53 100644 --- a/packages/reports/src/Components/indicative-cell.tsx +++ b/packages/reports/src/Components/indicative-cell.tsx @@ -1,27 +1,21 @@ import React from 'react'; import { Icon, Money, DesktopWrapper, ContractCard } from '@deriv/components'; import { getCardLabels, TContractInfo } from '@deriv/shared'; -import { connect } from 'Stores/connect'; -import { TRootStore } from 'Stores/index'; +import { observer, useStore } from '@deriv/stores'; type TIndicativeCell = { amount: number; contract_info: TContractInfo; currency: string; - status: string; + status?: string; is_footer: boolean; is_sell_requested: boolean; - onClickSell: () => void; }; -const IndicativeCell = ({ - amount, - currency, - contract_info, - is_footer, - onClickSell, - is_sell_requested, -}: TIndicativeCell) => { +const IndicativeCell = observer((props: TIndicativeCell) => { + const { amount, contract_info, currency, is_footer, is_sell_requested, status } = props; + const { portfolio } = useStore(); + const { onClickSell } = portfolio; const [movement, setMovement] = React.useState(null); const [amount_state, setAmountState] = React.useState(0); @@ -49,14 +43,14 @@ const IndicativeCell = ({ contract_info={contract_info} is_sell_requested={is_sell_requested} getCardLabels={getCardLabels} - onClickSell={onClickSell} + onClickSell={contract_id => { + if (contract_id) onClickSell(contract_id); + }} /> )}
); -}; +}); -export default connect(({ portfolio }: TRootStore) => ({ - onClickSell: portfolio.onClickSell, -}))(IndicativeCell); +export default IndicativeCell; diff --git a/packages/reports/src/Constants/data-table-constants.tsx b/packages/reports/src/Constants/data-table-constants.tsx index 5bba1b5f59ee..75d732a41309 100644 --- a/packages/reports/src/Constants/data-table-constants.tsx +++ b/packages/reports/src/Constants/data-table-constants.tsx @@ -18,6 +18,7 @@ import MarketSymbolIconRow from '../Components/market-symbol-icon-row'; import ProfitLossCell from '../Components/profit_loss_cell'; import CurrencyWrapper from '../Components/currency-wrapper'; import { useStore } from '@deriv/stores'; +import moment from 'moment'; type TPortfolioStore = ReturnType['portfolio']; @@ -41,6 +42,7 @@ type TAccumulatorOpenPositionstemplateProps = Omit< TMultiplierOpenPositionstemplateProps, 'onClickCancel' | 'server_time' >; + type TMultiplierOpenPositionstemplateProps = Pick< TPortfolioStore, 'getPositionById' | 'onClickCancel' | 'onClickSell' @@ -565,4 +567,3 @@ export const getAccumulatorOpenPositionsColumnsTemplate = ({ }, }, ]; -/* eslint-enable react/display-name, react/prop-types */ diff --git a/packages/reports/src/Containers/open-positions.tsx b/packages/reports/src/Containers/open-positions.tsx index 1c70d2d59706..e5bf812a8a8d 100644 --- a/packages/reports/src/Containers/open-positions.tsx +++ b/packages/reports/src/Containers/open-positions.tsx @@ -29,7 +29,6 @@ import { getGrowthRatePercentage, getCardLabels, toMoment, - TContractStore, } from '@deriv/shared'; import { localize, Localize } from '@deriv/translations'; import { ReportsTableRowLoader } from '../Components/Elements/ContentLoader'; @@ -42,52 +41,52 @@ import { getMultiplierOpenPositionsColumnsTemplate, } from 'Constants/data-table-constants'; import PlaceholderComponent from '../Components/placeholder-component'; -import { connect } from 'Stores/connect'; -import type { TRootStore } from 'Stores/index'; -import { TColIndex } from 'Types'; +import { observer, useStore } from '@deriv/stores'; +import { TColIndex, TUnsupportedContractType } from 'Types'; import moment from 'moment'; -type TPortfolioStore = TRootStore['portfolio']; +type TRangeFloatZeroToOne = React.ComponentProps['value']; +type TPortfolioStore = ReturnType['portfolio']; type TDataList = React.ComponentProps; type TDataListCell = React.ComponentProps; type TRowRenderer = TDataList['rowRenderer']; -type TMobileRowRenderer = { - row?: TDataList['data_source'][number]; - is_footer?: boolean; - columns_map?: Record; - server_time?: moment.Moment; - onClickCancel: (contract_id?: number) => void; - onClickSell: (contract_id?: number) => void; - measure?: () => void; -}; -type TRangeFloatZeroToOne = React.ComponentProps['value']; + type TEmptyPlaceholderWrapper = React.PropsWithChildren<{ is_empty: boolean; component_icon: string; }>; -const EmptyPlaceholderWrapper = ({ is_empty, component_icon, children }: TEmptyPlaceholderWrapper) => ( - - {is_empty ? ( - - ) : ( - children - )} - -); +type TUiStore = Pick< + ReturnType['ui'], + | 'addToast' + | 'current_focus' + | 'removeToast' + | 'setCurrentFocus' + | 'should_show_cancellation_warning' + | 'toggleCancellationWarning' + | 'toggleUnsupportedContractModal' +>; -type TOpenPositionsTable = Pick & { +type TMobileRowRenderer = TUiStore & { + row?: TDataList['data_source'][0]; + is_footer?: boolean; + columns_map: Record; + getContractById: ReturnType['contract_trade']['getContractById']; + server_time: moment.Moment; + onClickCancel: (contract_id?: number) => void; + onClickRemove: TPortfolioStore['removePositionById']; + onClickSell: (contract_id?: number) => void; + measure?: () => void; +}; + +type TOpenPositionsTable = { className: string; columns: Record[]; component_icon: string; currency: string; active_positions: TPortfolioStore['active_positions']; is_loading: boolean; + getRowAction: TDataList['getRowAction']; mobileRowRenderer: TRowRenderer; preloaderCheck: (item: TTotals) => boolean; row_size: number; @@ -115,57 +114,24 @@ type TTotals = { payout?: number; }; -type TAddToastProps = { - key?: string; - content: string; - timeout?: number; - is_bottom?: boolean; - type?: string; -}; - -type TOpenPositions = Pick< - TPortfolioStore, - | 'active_positions' - | 'error' - | 'getPositionById' - | 'is_loading' - | 'is_multiplier' - | 'onClickCancel' - | 'onClickSell' - | 'onMount' -> & { +type TOpenPositions = { component_icon: string; - currency: string; - is_accumulator: boolean; - is_eu: boolean; - NotificationMessages: () => JSX.Element; - server_time: moment.Moment; - addToast: (obj: TAddToastProps) => void; - current_focus: string; - onClickRemove: () => void; - getContractById: (contract_id?: number) => TContractStore; - removeToast: () => void; - setCurrentFocus: () => void; - should_show_cancellation_warning: boolean; - toggleCancellationWarning: () => void; - toggleUnsupportedContractModal: () => void; }; -type TMobileRowRendererProps = Pick< - TOpenPositions, - | 'addToast' - | 'current_focus' - | 'getContractById' - | 'onClickRemove' - | 'removeToast' - | 'setCurrentFocus' - | 'should_show_cancellation_warning' - | 'toggleCancellationWarning' - | 'toggleUnsupportedContractModal' -> & - Omit & { - columns_map: { [key: TColIndex]: undefined | TDataListCell['column'] }; - }; +const EmptyPlaceholderWrapper = ({ is_empty, component_icon, children }: TEmptyPlaceholderWrapper) => ( + + {is_empty ? ( + + ) : ( + children + )} + +); const MobileRowRenderer = ({ row = {}, @@ -176,7 +142,7 @@ const MobileRowRenderer = ({ onClickSell, measure, ...props -}: TMobileRowRendererProps) => { +}: TMobileRowRenderer) => { React.useEffect(() => { if (!is_footer) { measure?.(); @@ -204,8 +170,7 @@ const MobileRowRenderer = ({ ); } - const { contract_info, contract_update, type, is_sell_requested } = - row as TPortfolioStore['active_positions'][number]; + const { contract_info, contract_update, type, is_sell_requested } = row as TPortfolioStore['active_positions'][0]; const { currency, status, date_expiry, date_start, tick_count, purchase_time } = contract_info; const current_tick = tick_count ? getCurrentTick(contract_info) : null; const turbos_duration_unit = tick_count ? 'ticks' : getDurationUnitText(getDurationPeriod(contract_info), true); @@ -215,7 +180,7 @@ const MobileRowRenderer = ({ const progress_value = (getTimePercentage(server_time, date_start ?? 0, date_expiry ?? 0) / 100) as TRangeFloatZeroToOne; - if (isMultiplierContract(type ?? '') || isAccumulatorContract(type)) { + if (isMultiplierContract(type) || isAccumulatorContract(type)) { return ( ) : ( - + )}
@@ -344,10 +309,7 @@ const getRowAction: TDataList['getRowAction'] = row_obj => - ]?.name, + trade_type_name: getUnsupportedContracts()[row_obj.type as TUnsupportedContractType]?.name, }} /> ), @@ -414,9 +376,10 @@ const getOpenPositionsTotals = ( let profit = 0; active_positions_filtered?.forEach(({ contract_info }) => { - buy_price += +(contract_info.buy_price ?? 0); - bid_price += +(contract_info.bid_price ?? 0); - take_profit += contract_info.limit_order?.take_profit?.order_amount ?? 0; + buy_price += Number(contract_info.buy_price); + bid_price += Number(contract_info.bid_price); + if (contract_info.limit_order?.take_profit?.order_amount) + take_profit += contract_info.limit_order.take_profit.order_amount; if (contract_info) { profit += getTotalProfit(contract_info); } @@ -456,23 +419,49 @@ const getOpenPositionsTotals = ( return totals; }; -const OpenPositions = ({ - active_positions, - component_icon, - currency, - error, - getPositionById, - is_accumulator, - is_eu: hide_accu_in_dropdown, - is_loading, - is_multiplier, - NotificationMessages, - onClickCancel, - onClickSell, - onMount, - server_time, - ...props -}: TOpenPositions) => { +const OpenPositions = observer(({ component_icon, ...props }: TOpenPositions) => { + const { portfolio, client, ui, common, contract_trade } = useStore(); + const { + active_positions, + error, + getPositionById, + is_accumulator, + is_loading, + is_multiplier, + onClickCancel, + onClickSell, + onMount, + removePositionById: onClickRemove, + } = portfolio; + const { currency, is_eu: hide_accu_in_dropdown } = client; + const { + notification_messages_ui: NotificationMessages, + addToast, + current_focus, + is_mobile, + removeToast, + setCurrentFocus, + should_show_cancellation_warning, + toggleCancellationWarning, + toggleUnsupportedContractModal, + } = ui; + const { server_time } = common; + const { getContractById } = contract_trade; + + const store_props = { + onClickRemove, + NotificationMessages, + addToast, + current_focus, + is_mobile, + removeToast, + setCurrentFocus, + should_show_cancellation_warning, + toggleCancellationWarning, + toggleUnsupportedContractModal, + getContractById, + }; + const [has_accumulator_contract, setHasAccumulatorContract] = React.useState(false); const [has_multiplier_contract, setHasMultiplierContract] = React.useState(false); const previous_active_positions = usePrevious(active_positions); @@ -498,7 +487,7 @@ const OpenPositions = ({ if (is_accumulator_selected) return ( isAccumulatorContract(contract_info.contract_type) && - (`${getGrowthRatePercentage(contract_info.growth_rate || 0)}%` === accumulator_rate || + (`${getGrowthRatePercentage(Number(contract_info.growth_rate))}%` === accumulator_rate || !accumulator_rate.includes('%')) ); return ( @@ -547,7 +536,7 @@ const OpenPositions = ({ if (error) return

{error}

; const getColumns = () => { - if (is_multiplier_selected) { + if (is_multiplier_selected && server_time) { return getMultiplierOpenPositionsColumnsTemplate({ currency, onClickCancel, @@ -570,17 +559,18 @@ const OpenPositions = ({ const columns_map = {} as Record; columns.forEach(e => { - columns_map[e.col_index] = e as TDataListCell['column']; + columns_map[e.col_index as TColIndex] = e as TDataListCell['column']; }); const mobileRowRenderer: TRowRenderer = args => ( ); @@ -688,31 +678,6 @@ const OpenPositions = ({ {getOpenPositionsTable()} ); -}; +}); -export default withRouter( - connect(({ client, common, ui, portfolio, contract_trade }: TRootStore) => ({ - active_positions: portfolio.active_positions, - currency: client.currency, - is_eu: client.is_eu, - error: portfolio.error, - getPositionById: portfolio.getPositionById, - is_accumulator: portfolio.is_accumulator, - is_loading: portfolio.is_loading, - is_multiplier: portfolio.is_multiplier, - NotificationMessages: ui.notification_messages_ui, - onClickCancel: portfolio.onClickCancel, - onClickSell: portfolio.onClickSell, - onMount: portfolio.onMount, - server_time: common.server_time, - addToast: ui.addToast, - current_focus: ui.current_focus, - onClickRemove: portfolio.removePositionById, - getContractById: contract_trade.getContractById, - removeToast: ui.removeToast, - setCurrentFocus: ui.setCurrentFocus, - should_show_cancellation_warning: ui.should_show_cancellation_warning, - toggleCancellationWarning: ui.toggleCancellationWarning, - toggleUnsupportedContractModal: ui.toggleUnsupportedContractModal, - }))(OpenPositions) -); +export default withRouter(OpenPositions); diff --git a/packages/reports/src/Containers/profit-table.tsx b/packages/reports/src/Containers/profit-table.tsx index 5e1d6a626160..d250abb9555f 100644 --- a/packages/reports/src/Containers/profit-table.tsx +++ b/packages/reports/src/Containers/profit-table.tsx @@ -12,46 +12,29 @@ import { import { localize, Localize } from '@deriv/translations'; import { ReportsTableRowLoader } from '../Components/Elements/ContentLoader'; import CompositeCalendar from '../Components/Form/CompositeCalendar'; -import { - TInputDateRange, - TColIndex, - TColumnTemplateType, - TSupportedContractType, - TUnsupportedContractType, -} from 'Types'; - -import { connect } from 'Stores/connect'; +import { TSupportedContractType, TUnsupportedContractType } from 'Types'; import EmptyTradeHistoryMessage from '../Components/empty-trade-history-message'; import PlaceholderComponent from '../Components/placeholder-component'; import { ReportsMeta } from '../Components/reports-meta'; import { getProfitTableColumnsTemplate } from 'Constants/data-table-constants'; -import { TRootStore } from 'Stores/index'; -import moment from 'moment/moment'; +import { observer, useStore } from '@deriv/stores'; +import { useReportsStore } from 'Stores/useReportsStores'; type TProfitTable = { component_icon: string; - currency: string; - data: Array<{ [key: string]: string }>; - date_from: number; - date_to: number; - error: string; - filtered_date_range: TInputDateRange; - is_empty: boolean; - is_loading: boolean; - is_switching: boolean; - handleDateChange: (values: { [key: string]: moment.Moment }) => void; - handleScroll: (ev: React.UIEvent) => void; - has_selected_date: boolean; - onMount: VoidFunction; - onUnmount: VoidFunction; - totals: React.ReactNode; }; -const getRowAction = (row_obj: { [key: string]: string }) => { - const contract_type = extractInfoFromShortcode(row_obj?.shortcode)?.category?.toString().toUpperCase(); +type TDataListCell = React.ComponentProps; + +type TGetProfitTableColumnsTemplate = ReturnType; + +const getRowAction = (row_obj: { [key: string]: unknown }) => { + const contract_type = extractInfoFromShortcode(row_obj?.shortcode as string) + ?.category?.toString() + .toUpperCase(); return getSupportedContracts()[contract_type as TSupportedContractType] && - !isForwardStarting(row_obj.shortcode, +row_obj.purchase_time_unix) - ? getContractPath(+row_obj.contract_id) + !isForwardStarting(row_obj.shortcode as string, Number(row_obj.purchase_time_unix)) + ? getContractPath(Number(row_obj.contract_id)) : { component: ( { }; }; -const ProfitTable = ({ - component_icon, - currency, - data, - date_from, - date_to, - error, - filtered_date_range, - is_empty, - is_loading, - is_switching, - handleDateChange, - handleScroll, - has_selected_date, - onMount, - onUnmount, - totals, -}: TProfitTable) => { +const ProfitTable = observer(({ component_icon }: TProfitTable) => { + const { client } = useStore(); + const { profit_table } = useReportsStore(); + const { currency, is_switching } = client; + const { + data, + date_from, + date_to, + error, + is_empty, + is_loading, + handleDateChange, + handleScroll, + has_selected_date, + onMount, + onUnmount, + totals, + } = profit_table; + React.useEffect(() => { onMount(); return () => { @@ -92,33 +76,31 @@ const ProfitTable = ({ if (error) return

{error}

; - const filter_component = ( - - ); + const filter_component = ; - const columns = getProfitTableColumnsTemplate(currency, data.length); + const columns: TGetProfitTableColumnsTemplate = getProfitTableColumnsTemplate(currency, data.length); - const columns_map = Object.fromEntries(columns.map(column => [column.col_index, column])) as { - [key in TColIndex]: TColumnTemplateType; - }; + const columns_map = Object.fromEntries(columns.map(column => [column.col_index, column])) as Record< + TGetProfitTableColumnsTemplate[number]['col_index'], + TGetProfitTableColumnsTemplate[number] + >; - const mobileRowRenderer = ({ row, is_footer }: { row: any; is_footer?: boolean }) => { - const duration_type = /^(MULTUP|MULTDOWN)/.test(row.shortcode) ? '' : row.duration_type; + const mobileRowRenderer: React.ComponentProps['rowRenderer'] = ({ row, is_footer }) => { + const duration_type = /^(MULTUP|MULTDOWN)/.test(row?.shortcode) ? '' : row?.duration_type; const duration_classname = duration_type ? `duration-type__${duration_type.toLowerCase()}` : ''; if (is_footer) { return (
- +
@@ -128,26 +110,38 @@ const ProfitTable = ({ return ( <>
- +
{localize(duration_type)}
- - + +
- - + +
- - + +
- +
); @@ -181,7 +175,6 @@ const ProfitTable = ({ columns={columns} onScroll={handleScroll} footer={totals} - is_empty={is_empty} getRowAction={getRowAction} getRowSize={() => 63} content_loader={ReportsTableRowLoader} @@ -208,22 +201,6 @@ const ProfitTable = ({ )} ); -}; +}); -export default connect(({ modules, client }: TRootStore) => ({ - currency: client.currency, - data: modules.profit_table.data, - date_from: modules.profit_table.date_from, - date_to: modules.profit_table.date_to, - error: modules.profit_table.error, - filtered_date_range: modules.profit_table.filtered_date_range, - is_empty: modules.profit_table.is_empty, - is_loading: modules.profit_table.is_loading, - is_switching: client.is_switching, - handleDateChange: modules.profit_table.handleDateChange, - handleScroll: modules.profit_table.handleScroll, - has_selected_date: modules.profit_table.has_selected_date, - onMount: modules.profit_table.onMount, - onUnmount: modules.profit_table.onUnmount, - totals: modules.profit_table.totals, -}))(withRouter(ProfitTable)); +export default withRouter(ProfitTable); diff --git a/packages/reports/src/Containers/progress-slider-stream.tsx b/packages/reports/src/Containers/progress-slider-stream.tsx index 226c30f5e5cf..bfc6ed70f615 100644 --- a/packages/reports/src/Containers/progress-slider-stream.tsx +++ b/packages/reports/src/Containers/progress-slider-stream.tsx @@ -1,22 +1,24 @@ import React from 'react'; import { ProgressSlider } from '@deriv/components'; import { getCurrentTick, TContractInfo, getCardLabels } from '@deriv/shared'; -import { connect } from 'Stores/connect'; -import moment from 'moment'; -import { TRootStore } from 'Stores/index'; +import { observer, useStore } from '@deriv/stores'; type TProgressSliderStream = { contract_info: Required; - is_loading: boolean; - server_time: moment.Moment; }; -const ProgressSliderStream = ({ contract_info, is_loading, server_time }: TProgressSliderStream) => { +const ProgressSliderStream = observer(({ contract_info }: TProgressSliderStream) => { + 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); + if (!server_time) return null; + return ( ); -}; +}); -export default connect(({ common, portfolio }: TRootStore) => ({ - is_loading: portfolio.is_loading, - server_time: common.server_time, -}))(ProgressSliderStream); +export default ProgressSliderStream; diff --git a/packages/reports/src/Containers/routes.tsx b/packages/reports/src/Containers/routes.tsx index ab4b8d04d70b..61c0b535cc17 100644 --- a/packages/reports/src/Containers/routes.tsx +++ b/packages/reports/src/Containers/routes.tsx @@ -1,26 +1,19 @@ -import React from 'react'; +import { observer, useStore } from '@deriv/stores'; import { withRouter } from 'react-router'; +import React from 'react'; import BinaryRoutes from 'Components/Routes'; -import { connect } from 'Stores/connect'; import ErrorComponent from 'Components/Errors'; -import { TRootStore } from 'Stores/index'; import { TRoutes } from 'Types'; -const Routes = ({ error, has_error, is_logged_in, is_logging_in, passthrough }: TRoutes) => { +const Routes = observer(({ passthrough }: TRoutes) => { + const { client, common } = useStore(); + const { is_logged_in, is_logging_in } = client; + const { error, has_error } = common; if (has_error) { return ; } return ; -}; +}); -// need to wrap withRouter around connect -// to prevent updates on from being blocked -export default withRouter( - connect(({ client, common }: TRootStore) => ({ - is_logged_in: client.is_logged_in, - is_logging_in: client.is_logging_in, - error: common.error, - has_error: common.has_error, - }))(Routes) -); +export default withRouter(Routes); diff --git a/packages/reports/src/Containers/statement.tsx b/packages/reports/src/Containers/statement.tsx index 5e0c2de1ed54..481c01bcb523 100644 --- a/packages/reports/src/Containers/statement.tsx +++ b/packages/reports/src/Containers/statement.tsx @@ -10,37 +10,21 @@ import { } from '@deriv/shared'; import { localize, Localize } from '@deriv/translations'; import { ReportsTableRowLoader } from '../Components/Elements/ContentLoader'; -import { connect } from 'Stores/connect'; import { getStatementTableColumnsTemplate } from '../Constants/data-table-constants'; import PlaceholderComponent from '../Components/placeholder-component'; import AccountStatistics from '../Components/account-statistics'; import FilterComponent from '../Components/filter-component'; import { ReportsMeta } from '../Components/reports-meta'; import EmptyTradeHistoryMessage from '../Components/empty-trade-history-message'; -import { TRootStore } from 'Stores/index'; +import { observer, useStore } from '@deriv/stores'; +import { useReportsStore } from 'Stores/useReportsStores'; +import { TSupportedContractType, TUnsupportedContractType } from 'Types'; +import { TSource } from '@deriv/components/src/components/data-table/data-table'; +import { TRow } from '@deriv/components/src/components/types/common.types'; type TGetStatementTableColumnsTemplate = ReturnType; type TColIndex = 'icon' | 'refid' | 'currency' | 'date' | 'action_type' | 'amount' | 'balance'; -type TFormatStatementTransaction = { - action: string; - date: string; - display_name: string; - refid: number; - payout: string; - amount: string; - balance: string; - desc: string; - id: number; - app_id: number; - shortcode: string; - action_type: string; - purchase_time: number; - transaction_time: number; - withdrawal_details: string; - longcode: string; -}; - type TAction = | { message?: string; @@ -49,26 +33,7 @@ type TAction = | string; type TStatement = { - action_type: string; - account_statistics: React.ComponentProps['account_statistics']; component_icon: string; - currency: string; - data: TFormatStatementTransaction[]; - date_from: number | null; - date_to: number | null; - error: string; - filtered_date_range: React.ComponentProps['filtered_date_range']; - handleDateChange: () => void; - handleFilterChange: () => void; - handleScroll: () => void; - has_selected_date: boolean; - is_empty: boolean; - is_loading: boolean; - is_mx_mlt: boolean; - is_switching: boolean; - is_virtual: boolean; - onMount: () => void; - onUnmount: () => void; }; type TDetailsComponent = { @@ -76,6 +41,9 @@ type TDetailsComponent = { action_type: string; }; +type TDataList = React.ComponentProps; +type TDataListCell = React.ComponentProps; + const DetailsComponent = ({ message = '', action_type = '' }: TDetailsComponent) => { const address_hash_match = /:\s([0-9a-zA-Z]+.{25,28})/gm.exec(message.split(/,\s/)[0]); const address_hash = address_hash_match?.[1]; @@ -113,12 +81,14 @@ const DetailsComponent = ({ message = '', action_type = '' }: TDetailsComponent) ); }; -const getRowAction = (row_obj: TFormatStatementTransaction) => { +type TGetRowAction = TDataList['getRowAction'] | React.ComponentProps['getRowAction']; + +const getRowAction: TGetRowAction = (row_obj: TSource | TRow) => { let action: TAction = {}; if (row_obj.id && ['buy', 'sell'].includes(row_obj.action_type)) { - const contract_type = extractInfoFromShortcode(row_obj.shortcode).category.toUpperCase(); + const contract_type = extractInfoFromShortcode(row_obj.shortcode)?.category?.toUpperCase(); action = - getSupportedContracts()[contract_type] && + getSupportedContracts()[contract_type as TSupportedContractType] && !isForwardStarting(row_obj.shortcode, row_obj.purchase_time || row_obj.transaction_time) ? getContractPath(row_obj.id) : { @@ -127,7 +97,8 @@ const getRowAction = (row_obj: TFormatStatementTransaction) => { ), @@ -156,28 +127,13 @@ const getRowAction = (row_obj: TFormatStatementTransaction) => { return action; }; -const Statement = ({ - account_statistics, - action_type, - component_icon, - currency, - data, - date_from, - date_to, - error, - filtered_date_range, - handleDateChange, - handleFilterChange, - handleScroll, - has_selected_date, - is_empty, - is_loading, - is_mx_mlt, - is_switching, - is_virtual, - onMount, - onUnmount, -}: TStatement) => { +const Statement = observer(({ component_icon }: TStatement) => { + const { client } = useStore(); + const { statement } = useReportsStore(); + const { currency, standpoint, is_switching, is_virtual } = client; + const { data, error, handleScroll, has_selected_date, is_empty, is_loading, onMount, onUnmount } = statement; + const is_mx_mlt = standpoint.iom || standpoint.malta; + React.useEffect(() => { onMount(); return () => { @@ -195,22 +151,41 @@ const Statement = ({ }, {} as Record); // TODO: Export type instead of any from 'DataList' component when it migrates to tsx - const mobileRowRenderer = ({ row, passthrough }: any) => ( + const mobileRowRenderer = ({ + row, + passthrough, + }: Pick[0], 'row' | 'passthrough'>) => (
- - + +
- - + +
- - + +
- +
); @@ -219,27 +194,15 @@ const Statement = ({ - } + filter_component={} is_statement - optional_component={ - !is_switching && - is_mx_mlt && - } + optional_component={!is_switching && is_mx_mlt && } /> {is_switching ? ( ) : ( - {data.length === 0 || is_empty ? ( + {data?.length === 0 || is_empty ? ( getRowAction(row)} + getRowAction={getRowAction} onScroll={handleScroll} passthrough={{ - isTopUp: (item: TFormatStatementTransaction) => - is_virtual && item.action === 'Deposit', + isTopUp: (item: { action?: string }) => is_virtual && item.action === 'Deposit', }} > @@ -278,8 +240,7 @@ const Statement = ({ rowRenderer={mobileRowRenderer} row_gap={8} passthrough={{ - isTopUp: (item: TFormatStatementTransaction) => - is_virtual && item.action === 'Deposit', + isTopUp: (item: { action?: string }) => is_virtual && item.action === 'Deposit', }} > @@ -291,28 +252,6 @@ const Statement = ({ )} ); -}; +}); -export default withRouter( - connect(({ modules, client }: TRootStore) => ({ - action_type: modules.statement.action_type, - account_statistics: modules.statement.account_statistics, - currency: client.currency, - data: modules.statement.data, - date_from: modules.statement.date_from, - date_to: modules.statement.date_to, - error: modules.statement.error, - filtered_date_range: modules.statement.filtered_date_range, - handleDateChange: modules.statement.handleDateChange, - handleFilterChange: modules.statement.handleFilterChange, - handleScroll: modules.statement.handleScroll, - has_selected_date: modules.statement.has_selected_date, - is_empty: modules.statement.is_empty, - is_loading: modules.statement.is_loading, - is_mx_mlt: client.standpoint.iom || client.standpoint.malta, - is_switching: client.is_switching, - is_virtual: client.is_virtual, - onMount: modules.statement.onMount, - onUnmount: modules.statement.onUnmount, - }))(Statement) -); +export default withRouter(Statement); diff --git a/packages/reports/src/Stores/connect.js b/packages/reports/src/Stores/connect.js deleted file mode 100644 index 4ef42c8d18b6..000000000000 --- a/packages/reports/src/Stores/connect.js +++ /dev/null @@ -1,31 +0,0 @@ -import { useObserver } from 'mobx-react'; -import React from 'react'; - -const isClassComponent = Component => - !!(typeof Component === 'function' && Component.prototype && Component.prototype.isReactComponent); - -export const MobxContent = React.createContext(null); - -function injectStorePropsToComponent(propsToSelectFn, BaseComponent) { - const Component = own_props => { - const store = React.useContext(MobxContent); - - let ObservedComponent = BaseComponent; - - if (isClassComponent(BaseComponent)) { - const FunctionalWrapperComponent = props => ; - ObservedComponent = FunctionalWrapperComponent; - } - - return useObserver(() => ObservedComponent({ ...own_props, ...propsToSelectFn(store, own_props) })); - }; - - Component.displayName = BaseComponent.name; - return Component; -} - -export const MobxContentProvider = ({ store, children }) => { - return {children}; -}; - -export const connect = propsToSelectFn => Component => injectStorePropsToComponent(propsToSelectFn, Component); diff --git a/packages/reports/src/Stores/index.ts b/packages/reports/src/Stores/index.ts deleted file mode 100644 index 08806f1df4e5..000000000000 --- a/packages/reports/src/Stores/index.ts +++ /dev/null @@ -1,41 +0,0 @@ -import ModulesStore from './Modules'; -import ProfitTableStore from './Modules/Profit/profit-store'; -import StatementStore from './Modules/Statement/statement-store'; -import type { TCoreStores } from '@deriv/stores/types'; - -export type TRootStore = TCoreStores & { - modules: { - profit_table: ProfitTableStore; - statement: StatementStore; - }; -}; - -export default class RootStore { - client: TCoreStores['client']; - common: TCoreStores['common']; - modules: ModulesStore; - ui: TCoreStores['ui']; - gtm: unknown; - pushwoosh: unknown; - notifications: TCoreStores['notifications']; - contract_replay: unknown; - contract_trade: TCoreStores['contract_trade']; - portfolio: TCoreStores['portfolio']; - chart_barrier_store: unknown; - active_symbols: unknown; - - constructor(core_store: TCoreStores) { - this.client = core_store.client; - this.common = core_store.common; - this.modules = new ModulesStore(this); - this.ui = core_store.ui; - this.gtm = core_store.gtm; - this.pushwoosh = core_store.pushwoosh; - this.notifications = core_store.notifications; - this.contract_replay = core_store.contract_replay; - this.contract_trade = core_store.contract_trade; - this.portfolio = core_store.portfolio; - this.chart_barrier_store = core_store.chart_barrier_store; - this.active_symbols = core_store.active_symbols; - } -} diff --git a/packages/reports/src/Stores/useReportsStores.tsx b/packages/reports/src/Stores/useReportsStores.tsx new file mode 100644 index 000000000000..1e4e6bf76950 --- /dev/null +++ b/packages/reports/src/Stores/useReportsStores.tsx @@ -0,0 +1,69 @@ +import React from 'react'; +import { useStore } from '@deriv/stores'; +import ProfitStores from './Modules/Profit/profit-store'; +import StatementStores from './Modules/Statement/statement-store'; + +type TOverrideProfitStore = Omit & { + date_from: number; + data: { [key: string]: string }[]; + totals: { [key: string]: unknown }; +}; + +type TOverrideStatementStore = Omit< + StatementStores, + | 'account_statistics' + | 'action_type' + | 'data' + | 'date_from' + | 'date_to' + | 'filtered_date_range' + | 'handleDateChange' + | 'handleFilterChange' + | 'handleScroll' + | 'suffix_icon' +> & { + account_statistics: { total_deposits: number; total_withdrawals: number }; + action_type: string; + data: { [key: string]: string }[]; + date_from: number; + date_to: number; + filtered_date_range: { + duration: number; + label: string; + onClick?: () => void; + value?: string; + }; + handleDateChange: () => void; + handleFilterChange: () => void; + handleScroll: React.UIEventHandler; + suffix_icon: string; +}; + +type TReportsStore = { + profit_table: TOverrideProfitStore; + statement: TOverrideStatementStore; +}; + +const ReportsStoreContext = React.createContext(null); + +export const ReportsStoreProvider = ({ children }: React.PropsWithChildren) => { + const root_store = useStore(); + const memoizedValue = React.useMemo(() => { + return { + profit_table: new ProfitStores({ root_store }), + statement: new StatementStores({ root_store }), + } as unknown as TReportsStore; + }, [root_store]); + + return {children}; +}; + +export const useReportsStore = () => { + const store = React.useContext(ReportsStoreContext); + + if (!store) { + throw new Error('useReportsStore must be used within ReportsStoreProvider'); + } + + return store; +}; diff --git a/packages/reports/src/Types/common-prop.type.ts b/packages/reports/src/Types/common-prop.type.ts index 7327d126599c..9fd30234e7ed 100644 --- a/packages/reports/src/Types/common-prop.type.ts +++ b/packages/reports/src/Types/common-prop.type.ts @@ -1,6 +1,6 @@ import React from 'react'; import { Redirect } from 'react-router-dom'; -import { TRootStore } from 'Stores/index'; +import { TCoreStores } from '@deriv/stores/types'; import { getMultiplierOpenPositionsColumnsTemplate, getOpenPositionsColumnsTemplate, @@ -11,7 +11,7 @@ import { import { getSupportedContracts, getUnsupportedContracts } from '@deriv/shared'; export type TPassthrough = { - root_store: TRootStore; + root_store: TCoreStores; WS: Record; }; @@ -34,11 +34,11 @@ export type TRoute = { }; export type TErrorComponent = { - header: string; + header: string | JSX.Element; is_dialog: boolean; message: React.ReactElement | string; redirect_label: string; - redirectOnClick: () => void; + redirectOnClick: (() => void) | null; should_show_refresh: boolean; type: string; }; diff --git a/packages/reports/src/_common/base/server_time.js b/packages/reports/src/_common/base/server_time.js deleted file mode 100644 index b639380b27a5..000000000000 --- a/packages/reports/src/_common/base/server_time.js +++ /dev/null @@ -1,25 +0,0 @@ -const PromiseClass = require('../utility').PromiseClass; - -const ServerTime = (() => { - let clock_started = false; - const pending = new PromiseClass(); - let common_store; - - const init = store => { - if (!clock_started) { - common_store = store; - pending.resolve(common_store.server_time); - clock_started = true; - } - }; - - const get = () => (clock_started && common_store.server_time ? common_store.server_time.clone() : undefined); - - return { - init, - get, - timePromise: () => (clock_started ? Promise.resolve(common_store.server_time) : pending.promise), - }; -})(); - -module.exports = ServerTime; diff --git a/packages/reports/src/_common/utility.js b/packages/reports/src/_common/utility.js index 5c20dc5c33e0..17103900f516 100644 --- a/packages/reports/src/_common/utility.js +++ b/packages/reports/src/_common/utility.js @@ -6,47 +6,6 @@ const template = (string, content) => { return string.replace(/\[_(\d+)]/g, (s, index) => to_replace[+index - 1]); }; -/** - * Creates a DOM element and adds any attributes to it. - * - * @param {String} tag_name: the tag to create, e.g. 'div', 'a', etc - * @param {Object} attributes: all the attributes to assign, e.g. { id: '...', class: '...', html: '...', ... } - * @return the created DOM element - */ -const createElement = (tag_name, attributes = {}) => { - const el = document.createElement(tag_name); - Object.keys(attributes).forEach(attr => { - const value = attributes[attr]; - if (attr === 'text') { - el.textContent = value; - } else if (attr === 'html') { - el.html(value); - } else { - el.setAttribute(attr, value); - } - }); - return el; -}; - -let static_hash; -const getStaticHash = () => { - static_hash = - static_hash || (document.querySelector('script[src*="main"]').getAttribute('src') || '').split('.')[1]; - return static_hash; -}; - -class PromiseClass { - constructor() { - this.promise = new Promise((resolve, reject) => { - this.reject = reject; - this.resolve = resolve; - }); - } -} - module.exports = { template, - createElement, - getStaticHash, - PromiseClass, }; diff --git a/packages/reports/src/app.tsx b/packages/reports/src/app.tsx index 3d3298d62311..fa9f31db13d0 100644 --- a/packages/reports/src/app.tsx +++ b/packages/reports/src/app.tsx @@ -1,27 +1,20 @@ import React from 'react'; import Routes from 'Containers/routes'; -import { MobxContentProvider } from 'Stores/connect'; -import { StoreProvider } from '@deriv/stores'; +import ReportsProviders from './reports-providers'; import 'Sass/app.scss'; -import initStore from './init-store'; import type { TCoreStores } from '@deriv/stores/types'; type TAppProps = { passthrough: { root_store: TCoreStores; - WS: unknown; }; }; const App = ({ passthrough }: TAppProps) => { - const root_store = initStore(passthrough.root_store, passthrough.WS); - return ( - - - - - + + + ); }; diff --git a/packages/reports/src/init-store.js b/packages/reports/src/init-store.js deleted file mode 100644 index f6ad9f411595..000000000000 --- a/packages/reports/src/init-store.js +++ /dev/null @@ -1,20 +0,0 @@ -import { configure } from 'mobx'; -import RootStore from 'Stores'; -import { setWebsocket } from '@deriv/shared'; -import ServerTime from '_common/base/server_time'; - -configure({ enforceActions: 'observed' }); - -let root_store; - -const initStore = (core_store, websocket) => { - if (root_store) return root_store; - - ServerTime.init(core_store.common); - setWebsocket(websocket); - root_store = new RootStore(core_store); - - return root_store; -}; - -export default initStore; diff --git a/packages/reports/src/reports-providers.tsx b/packages/reports/src/reports-providers.tsx new file mode 100644 index 000000000000..cb5dc0488479 --- /dev/null +++ b/packages/reports/src/reports-providers.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import { StoreProvider } from '@deriv/stores'; +import { ReportsStoreProvider } from 'Stores/useReportsStores'; +import type { TCoreStores } from '@deriv/stores/types'; + +export const ReportsProviders = ({ children, store }: React.PropsWithChildren<{ store: TCoreStores }>) => { + return ( + + {children} + + ); +}; + +export default ReportsProviders; diff --git a/packages/shared/src/utils/constants/contract.ts b/packages/shared/src/utils/constants/contract.ts index 33c318cb9c0e..44dee5aa36a7 100644 --- a/packages/shared/src/utils/constants/contract.ts +++ b/packages/shared/src/utils/constants/contract.ts @@ -358,14 +358,6 @@ export const getUnsupportedContracts = () => name: localize('Low Tick'), position: 'bottom', }, - ASIANU: { - name: localize('Asian Up'), - position: 'top', - }, - ASIAND: { - name: localize('Asian Down'), - position: 'bottom', - }, LBFLOATCALL: { name: localize('Close-to-Low'), position: 'top', @@ -493,6 +485,14 @@ export const getSupportedContracts = (is_high_low?: boolean) => name: localize('Goes Outside'), position: 'bottom', }, + ASIANU: { + name: localize('Asian Up'), + position: 'top', + }, + ASIAND: { + name: localize('Asian Down'), + position: 'bottom', + }, } as const); export const getContractConfig = (is_high_low?: boolean) => ({ diff --git a/packages/shared/src/utils/contract/contract.ts b/packages/shared/src/utils/contract/contract.ts index aaf7bcbc7543..5a2012e69b51 100644 --- a/packages/shared/src/utils/contract/contract.ts +++ b/packages/shared/src/utils/contract/contract.ts @@ -71,7 +71,9 @@ export const isTurbosContract = (contract_type = '') => /TURBOS/i.test(contract_ export const isVanillaContract = (contract_type = '') => /VANILLA/i.test(contract_type); -export const isSmartTraderContract = (contract_type = '') => /RUN|EXPIRY|RANGE|UPORDOWN/i.test(contract_type); +export const isSmartTraderContract = (contract_type = '') => /RUN|EXPIRY|RANGE|UPORDOWN|ASIAN/i.test(contract_type); + +export const isAsiansContract = (contract_type = '') => /ASIAN/i.test(contract_type); export const isCryptoContract = (underlying = '') => underlying.startsWith('cry'); @@ -103,7 +105,10 @@ export const getAccuBarriersForContractDetails = (contract_info: TContractInfo) export const getCurrentTick = (contract_info: TContractInfo) => { const tick_stream = unique(contract_info.tick_stream || [], 'epoch'); - const current_tick = isDigitContract(contract_info.contract_type) ? tick_stream.length : tick_stream.length - 1; + const current_tick = + isDigitContract(contract_info.contract_type) || isAsiansContract(contract_info.contract_type) + ? tick_stream.length + : tick_stream.length - 1; return !current_tick || current_tick < 0 ? 0 : current_tick; }; diff --git a/packages/shared/src/utils/helpers/__tests__/format-response.ts b/packages/shared/src/utils/helpers/__tests__/format-response.ts index c1b54cf27d52..2b0638def931 100644 --- a/packages/shared/src/utils/helpers/__tests__/format-response.ts +++ b/packages/shared/src/utils/helpers/__tests__/format-response.ts @@ -95,7 +95,7 @@ describe('format-response', () => { display_name: 'Volatility 25 Index', id: 1234, indicative: 0, - is_unsupported: true, + is_unsupported: false, payout: 3500.1, contract_update: undefined, purchase: 2500.5, diff --git a/packages/stores/src/mockStore.ts b/packages/stores/src/mockStore.ts index c0001c3097ea..69a4afa8ea74 100644 --- a/packages/stores/src/mockStore.ts +++ b/packages/stores/src/mockStore.ts @@ -14,7 +14,6 @@ const mock = (): TStores & { is_mock: boolean } => { redirectOnClick: jest.fn(), setError: jest.fn(), }; - const services_error = { code: '', message: '', type: '' }; return { is_mock: true, client: { @@ -118,6 +117,7 @@ const mock = (): TStores & { is_mock: boolean } => { is_united_kingdom: false, }, currency: '', + currencies_list: [{ text: '', value: '', has_tool_tip: false }], current_currency_type: '', current_fiat_currency: '', cfd_score: 0, @@ -279,9 +279,9 @@ const mock = (): TStores & { is_mock: boolean } => { changeCurrentLanguage: jest.fn(), changeSelectedLanguage: jest.fn(), is_network_online: false, + services_error: {}, server_time: undefined, is_language_changing: false, - services_error: {}, is_socket_opened: false, setAppstorePlatform: jest.fn(), app_routing_history: [], @@ -370,6 +370,7 @@ const mock = (): TStores & { is_mock: boolean } => { toggleShouldShowRealAccountsList: jest.fn(), is_reset_trading_password_modal_visible: false, setResetTradingPasswordModalOpen: jest.fn(), + vanilla_trade_type: 'VANILLALONGCALL', }, traders_hub: { getAccount: jest.fn(), diff --git a/packages/stores/types.ts b/packages/stores/types.ts index 964c39e45093..f120f8a25402 100644 --- a/packages/stores/types.ts +++ b/packages/stores/types.ts @@ -2,6 +2,7 @@ import type { AccountLimitsResponse, Authorize, ContractUpdate, + ContractUpdateHistory, DetailsOfEachMT5Loginid, GetAccountStatus, GetLimits, @@ -84,25 +85,6 @@ type TPopulateSettingsExtensionsMenuItem = { value: (props: T) => JSX.Element; }; -type TPortfolioPosition = { - contract_info: ProposalOpenContract & - Portfolio1 & { - contract_update?: ContractUpdate; - }; - details?: string; - display_name: string; - id?: number; - indicative: number; - payout?: number; - purchase?: number; - reference: number; - type?: string; - is_unsupported: boolean; - contract_update: ProposalOpenContract['limit_order']; - is_sell_requested: boolean; - profit_loss: number; -}; - type TAppRoutingHistory = { action: string; hash: string; @@ -195,7 +177,9 @@ type TMenuItem = { type TAddToastProps = { key: string; - content: string; + content: string | React.ReactNode; + is_bottom?: boolean; + timeout?: number; type: string; }; @@ -286,6 +270,7 @@ type TClientStore = { setCFDScore: (score: number) => void; country_standpoint: TCountryStandpoint; currency: string; + currencies_list: { text: string; value: string; has_tool_tip?: boolean }[]; current_currency_type?: string; current_fiat_currency?: string; has_any_real_account: boolean; @@ -475,11 +460,11 @@ type TUiStore = { enableApp: () => void; has_only_forward_starting_contracts: boolean; has_real_account_signup_ended: boolean; + header_extension: JSX.Element | null; is_account_settings_visible: boolean; is_loading: boolean; is_cashier_visible: boolean; is_closing_create_real_account_modal: boolean; - header_extension: JSX.Element | null; is_dark_mode_on: boolean; is_reports_visible: boolean; is_route_modal_on: boolean; @@ -494,13 +479,25 @@ type TUiStore = { value: 'maltainvest' | 'svg' | 'add_crypto' | 'choose' | 'add_fiat' | 'set_currency' | 'manage' ) => void; notification_messages_ui: React.ElementType; - setCurrentFocus: (value: string) => void; + populateFooterExtensions: ( + footer_extensions: + | [ + { + position?: string; + Component?: React.FunctionComponent; + has_right_separator?: boolean; + } + ] + | [] + ) => 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; setReportsTabIndex: (value: number) => void; setIsClosingCreateRealAccountModal: (value: boolean) => void; setRealAccountSignupEnd: (status: boolean) => void; setPurchaseState: (index: number) => void; - setHasOnlyForwardingContracts: (has_only_forward_starting_contracts: boolean) => void; sub_section_index: number; setSubSectionIndex: (index: number) => void; shouldNavigateAfterChooseCrypto: (value: Omit | TRoutes) => void; @@ -519,7 +516,7 @@ type TUiStore = { is_ready_to_deposit_modal_visible: boolean; reports_route_tab_index: number; should_show_cancellation_warning: boolean; - toggleCancellationWarning: (state_change: boolean) => void; + toggleCancellationWarning: (state_change?: boolean) => void; toggleUnsupportedContractModal: (state_change: boolean) => void; toggleReports: (is_visible: boolean) => void; is_real_acc_signup_on: boolean; @@ -546,18 +543,26 @@ type TUiStore = { populateSettingsExtensions: (menu_items: Array | null) => void; purchase_states: boolean[]; setShouldShowCooldownModal: (value: boolean) => void; - setAppContentsScrollRef: (ref: React.MutableRefObject) => void; - populateFooterExtensions: ( - footer_extensions: - | [ - { - position?: string; - Component?: React.FunctionComponent; - has_right_separator?: boolean; - } - ] - | [] - ) => void; + vanilla_trade_type: 'VANILLALONGCALL' | 'VANILLALONGPUT'; +}; + +type TPortfolioPosition = { + contract_info: ProposalOpenContract & + Portfolio1 & { + contract_update?: ContractUpdate; + }; + details?: string; + display_name: string; + id?: number; + indicative: number; + payout?: number; + purchase?: number; + reference: number; + type?: string; + is_unsupported: boolean; + contract_update: ProposalOpenContract['limit_order']; + is_sell_requested: boolean; + profit_loss: number; }; type TPortfolioStore = { @@ -576,15 +581,32 @@ type TPortfolioStore = { removePositionById: (id: number) => void; }; -type TContractStore = { - getContractById: (id: number) => ProposalOpenContract; +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; }; +type TContractStore = { + clearContractUpdateConfigValues: () => void; + contract_info: TPortfolioPosition['contract_info']; + contract_update_history: ContractUpdateHistory; + contract_update_take_profit: number | string; + contract_update_stop_loss: number | string; + digits_info: { [key: number]: { digit: number; spot: string } }; + 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[] }; +}; + type TMenuStore = { attach: (item: TMenuItem) => void; update: (menu: TMenuItem, index: number) => void; @@ -710,7 +732,7 @@ export type TCoreStores = { menu: TMenuStore; ui: TUiStore; portfolio: TPortfolioStore; - contract_trade: TContractStore; + contract_trade: TContractTradeStore; // This should be `any` as this property will be handled in each package. // eslint-disable-next-line @typescript-eslint/no-explicit-any modules: Record; diff --git a/packages/trader/src/App/Components/Elements/ContractAudit/contract-details.jsx b/packages/trader/src/App/Components/Elements/ContractAudit/contract-details.jsx index 204e2a72f939..51e4083bdccf 100644 --- a/packages/trader/src/App/Components/Elements/ContractAudit/contract-details.jsx +++ b/packages/trader/src/App/Components/Elements/ContractAudit/contract-details.jsx @@ -6,11 +6,12 @@ import { epochToMoment, getCancellationPrice, getCurrencyDisplayCode, + hasTwoBarriers, isAccumulatorContract, isMobile, isMultiplierContract, isSmartTraderContract, - hasTwoBarriers, + isAsiansContract, isTurbosContract, isUserSold, isEndedBeforeCancellationExpired, @@ -49,6 +50,7 @@ const ContractDetails = ({ contract_end_time, contract_info, duration, duration_ const show_barrier = !is_vanilla && !isAccumulatorContract(contract_type) && !isSmartTraderContract(contract_type); const show_duration = !isAccumulatorContract(contract_type) || !isNaN(contract_end_time); const show_payout_per_point = isTurbosContract(contract_type) || is_vanilla; + 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')}`; @@ -98,7 +100,7 @@ const ContractDetails = ({ contract_end_time, contract_info, duration, duration_ value={tick_count > 0 ? ticks_duration_text : `${duration} ${duration_unit}`} /> )} - {is_vanilla && ( + {show_strike_barrier && ( } @@ -120,6 +122,23 @@ const ContractDetails = ({ contract_end_time, contract_info, duration, duration_ value={getBarrierValue(contract_info) || ' - '} /> )} + {hasTwoBarriers(contract_type) && ( + + {[high_barrier, low_barrier].map((barrier, index) => ( + } + key={barrier} + label={ + high_barrier === barrier + ? localize('High barrier') + : localize('Low barrier') + } + value={barrier} + /> + ))} + + )} {show_payout_per_point && ( )} - {hasTwoBarriers(contract_type) && ( - - {[high_barrier, low_barrier].map((barrier, index) => ( - } - key={barrier} - label={high_barrier === barrier ? localize('High barrier') : localize('Low barrier')} - value={barrier} - /> - ))} - - )} } diff --git a/packages/trader/src/App/Components/Elements/Media/__tests__/media-description.spec.tsx b/packages/trader/src/App/Components/Elements/Media/__tests__/media-description.spec.tsx new file mode 100644 index 000000000000..ccad8a4317c1 --- /dev/null +++ b/packages/trader/src/App/Components/Elements/Media/__tests__/media-description.spec.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import { MediaDescription } from 'App/Components/Elements/Media'; + +const test_children = 'Test Children'; + +describe('MediaDescription', () => { + it('should render children inside of proper MediaDescription div container with className', () => { + render({test_children}); + const test_props_children = screen.getByText(test_children); + + expect(test_props_children).toBeInTheDocument(); + expect(test_props_children).toHaveClass('media__description'); + }); +}); diff --git a/packages/trader/src/App/Components/Elements/Media/__tests__/media-heading.spec.tsx b/packages/trader/src/App/Components/Elements/Media/__tests__/media-heading.spec.tsx new file mode 100644 index 000000000000..acb7f14dab68 --- /dev/null +++ b/packages/trader/src/App/Components/Elements/Media/__tests__/media-heading.spec.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import { MediaHeading } from 'App/Components/Elements/Media'; + +const test_children = 'Test Children'; + +describe('MediaHeading', () => { + it('should render children inside of proper MediaHeading div container with className', () => { + render({test_children}); + const test_props_children = screen.getByText(test_children); + + expect(test_props_children).toBeInTheDocument(); + expect(test_props_children).toHaveClass('media__heading'); + }); +}); diff --git a/packages/trader/src/App/Components/Elements/Media/__tests__/media-icon.spec.tsx b/packages/trader/src/App/Components/Elements/Media/__tests__/media-icon.spec.tsx new file mode 100644 index 000000000000..d87bdfd29a27 --- /dev/null +++ b/packages/trader/src/App/Components/Elements/Media/__tests__/media-icon.spec.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import { MediaIcon } from 'App/Components/Elements/Media'; + +const test_disabled = 'Interval Duration Disabled Light Icon'; +const test_enabled = 'Interval Duration Enabled Light Icon'; +const mock_props = { + disabled: jest.fn(() =>
{test_disabled}
), + enabled: jest.fn(() =>
{test_enabled}
), + id: 'test_id', + is_enabled: true, +}; + +describe('MediaIcon', () => { + it('should render MediaIcon component with enabled SVG if is_enabled === true', () => { + render(); + + expect(screen.getByText(test_enabled)).toBeInTheDocument(); + }); + it('should render MediaIcon component with disabled SVG if is_enabled === false', () => { + render(); + + expect(screen.getByText(test_disabled)).toBeInTheDocument(); + }); +}); diff --git a/packages/trader/src/App/Components/Elements/Media/__tests__/media-item.spec.tsx b/packages/trader/src/App/Components/Elements/Media/__tests__/media-item.spec.tsx new file mode 100644 index 000000000000..2770cd33fad3 --- /dev/null +++ b/packages/trader/src/App/Components/Elements/Media/__tests__/media-item.spec.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import MediaItem from '../media-item'; + +const test_children = 'Test Children'; + +describe('MediaItem', () => { + it('should render children inside of proper MediaItem div container with className', () => { + render({test_children}); + const test_props_children = screen.getByText(test_children); + + expect(test_props_children).toBeInTheDocument(); + expect(test_props_children).toHaveClass('media'); + }); +}); diff --git a/packages/trader/src/App/Components/Elements/Media/index.js b/packages/trader/src/App/Components/Elements/Media/index.js deleted file mode 100644 index ce221026b9e0..000000000000 --- a/packages/trader/src/App/Components/Elements/Media/index.js +++ /dev/null @@ -1,6 +0,0 @@ -import MediaItem from './media-item.jsx'; - -export * from './media-description.jsx'; -export * from './media-heading.jsx'; -export * from './media-icon.jsx'; -export default MediaItem; diff --git a/packages/trader/src/App/Components/Elements/Media/index.ts b/packages/trader/src/App/Components/Elements/Media/index.ts new file mode 100644 index 000000000000..16f58a9fb22e --- /dev/null +++ b/packages/trader/src/App/Components/Elements/Media/index.ts @@ -0,0 +1,6 @@ +import MediaItem from './media-item'; + +export * from './media-description'; +export * from './media-heading'; +export * from './media-icon'; +export default MediaItem; diff --git a/packages/trader/src/App/Components/Elements/Media/media-description.jsx b/packages/trader/src/App/Components/Elements/Media/media-description.jsx deleted file mode 100644 index 36c190dd4dd4..000000000000 --- a/packages/trader/src/App/Components/Elements/Media/media-description.jsx +++ /dev/null @@ -1,5 +0,0 @@ -import React from 'react'; - -const MediaDescription = props =>
{props.children}
; - -export { MediaDescription }; diff --git a/packages/trader/src/App/Components/Elements/Media/media-description.tsx b/packages/trader/src/App/Components/Elements/Media/media-description.tsx new file mode 100644 index 000000000000..41b4b6a66644 --- /dev/null +++ b/packages/trader/src/App/Components/Elements/Media/media-description.tsx @@ -0,0 +1,7 @@ +import React from 'react'; + +const MediaDescription = ({ children }: React.PropsWithChildren) => ( +
{children}
+); + +export { MediaDescription }; diff --git a/packages/trader/src/App/Components/Elements/Media/media-heading.jsx b/packages/trader/src/App/Components/Elements/Media/media-heading.jsx deleted file mode 100644 index 68e63deb8f4d..000000000000 --- a/packages/trader/src/App/Components/Elements/Media/media-heading.jsx +++ /dev/null @@ -1,5 +0,0 @@ -import React from 'react'; - -const MediaHeading = props =>
{props.children}
; - -export { MediaHeading }; diff --git a/packages/trader/src/App/Components/Elements/Media/media-heading.tsx b/packages/trader/src/App/Components/Elements/Media/media-heading.tsx new file mode 100644 index 000000000000..5105497b3588 --- /dev/null +++ b/packages/trader/src/App/Components/Elements/Media/media-heading.tsx @@ -0,0 +1,5 @@ +import React from 'react'; + +const MediaHeading = ({ children }: React.PropsWithChildren) =>
{children}
; + +export { MediaHeading }; diff --git a/packages/trader/src/App/Components/Elements/Media/media-icon.jsx b/packages/trader/src/App/Components/Elements/Media/media-icon.jsx deleted file mode 100644 index 645eeb88256a..000000000000 --- a/packages/trader/src/App/Components/Elements/Media/media-icon.jsx +++ /dev/null @@ -1,16 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; - -const MediaIcon = ({ id, is_enabled, enabled, disabled }) => { - const Icon = is_enabled ? enabled : disabled; - return ; -}; - -MediaIcon.propTypes = { - disabled: PropTypes.func, - enabled: PropTypes.func, - id: PropTypes.string, - is_enabled: PropTypes.bool, -}; - -export { MediaIcon }; diff --git a/packages/trader/src/App/Components/Elements/Media/media-icon.tsx b/packages/trader/src/App/Components/Elements/Media/media-icon.tsx new file mode 100644 index 000000000000..a4d7c5c0b4ac --- /dev/null +++ b/packages/trader/src/App/Components/Elements/Media/media-icon.tsx @@ -0,0 +1,14 @@ +import React from 'react'; + +type TMediaIcon = { + disabled: React.ComponentType>; + enabled: React.ComponentType>; + id: string; + is_enabled: boolean; +}; +const MediaIcon = ({ id, is_enabled, enabled, disabled }: TMediaIcon) => { + const Icon = is_enabled ? enabled : disabled; + return ; +}; + +export { MediaIcon }; diff --git a/packages/trader/src/App/Components/Elements/Media/media-item.jsx b/packages/trader/src/App/Components/Elements/Media/media-item.jsx deleted file mode 100644 index 6347c10d720e..000000000000 --- a/packages/trader/src/App/Components/Elements/Media/media-item.jsx +++ /dev/null @@ -1,5 +0,0 @@ -import React from 'react'; - -const MediaItem = props =>
{props.children}
; - -export default MediaItem; diff --git a/packages/trader/src/App/Components/Elements/Media/media-item.tsx b/packages/trader/src/App/Components/Elements/Media/media-item.tsx new file mode 100644 index 000000000000..0d0ded93016b --- /dev/null +++ b/packages/trader/src/App/Components/Elements/Media/media-item.tsx @@ -0,0 +1,5 @@ +import React from 'react'; + +const MediaItem = ({ children }: React.PropsWithChildren) =>
{children}
; + +export default MediaItem; diff --git a/packages/trader/src/Modules/Contract/Components/InfoBox/info-box.jsx b/packages/trader/src/Modules/Contract/Components/InfoBox/info-box.jsx index e0c3607add99..9631a004c4c4 100644 --- a/packages/trader/src/Modules/Contract/Components/InfoBox/info-box.jsx +++ b/packages/trader/src/Modules/Contract/Components/InfoBox/info-box.jsx @@ -2,7 +2,7 @@ import PropTypes from 'prop-types'; import React from 'react'; import { SlideIn } from 'App/Components/Animations'; import InfoBoxLongcode from './info-box-longcode.jsx'; -import ContractError from '../contract-error.jsx'; +import ContractError from '../contract-error'; const InfoBox = ({ contract_info, error_message, removeError }) => { const is_ready = !!contract_info.longcode; diff --git a/packages/trader/src/Modules/Contract/Components/Sell/sell-button.jsx b/packages/trader/src/Modules/Contract/Components/Sell/sell-button.jsx deleted file mode 100644 index ca255bdb2be4..000000000000 --- a/packages/trader/src/Modules/Contract/Components/Sell/sell-button.jsx +++ /dev/null @@ -1,34 +0,0 @@ -import { observer } from 'mobx-react'; -import PropTypes from 'prop-types'; -import React from 'react'; -import { Button, Popover } from '@deriv/components'; -import { localize } from '@deriv/translations'; - -const SellButton = ({ contract_info, is_sell_requested, is_valid_to_sell, onClickSell }) => { - const sell_message = is_valid_to_sell - ? localize( - 'Contract will be sold at the prevailing market price when the request is received by our servers. This price may differ from the indicated price.' - ) - : contract_info.validation_error; - - return ( - - -