diff --git a/packages/components/src/components/contract-card/contract-card-items/contract-card-sell.jsx b/packages/components/src/components/contract-card/contract-card-items/contract-card-sell.jsx
index e18c107d2fd4..71f191054346 100644
--- a/packages/components/src/components/contract-card/contract-card-items/contract-card-sell.jsx
+++ b/packages/components/src/components/contract-card/contract-card-items/contract-card-sell.jsx
@@ -14,24 +14,19 @@ const ContractCardSell = ({ contract_info, getCardLabels, is_sell_requested, onC
ev.preventDefault();
};
+ if (!should_show_sell) return null;
+ if (!is_valid_to_sell)
+ return
{getCardLabels().RESALE_NOT_OFFERED}
;
return (
- should_show_sell && (
-
- {is_valid_to_sell ? (
-
- ) : (
- {getCardLabels().RESALE_NOT_OFFERED}
- )}
-
- )
+
);
};
diff --git a/packages/reports/src/Containers/index.js b/packages/reports/src/Containers/index.js
index 0523dc131e09..8489caed23b4 100644
--- a/packages/reports/src/Containers/index.js
+++ b/packages/reports/src/Containers/index.js
@@ -1,7 +1,7 @@
-import OpenPositions from './open-positions.jsx';
-import ProfitTable from './profit-table.jsx';
-import Statement from './statement.jsx';
-import Reports from './reports.jsx';
+import OpenPositions from './open-positions';
+import ProfitTable from './profit-table';
+import Statement from './statement';
+import Reports from './reports';
export default {
OpenPositions,
diff --git a/packages/reports/src/Containers/open-positions.jsx b/packages/reports/src/Containers/open-positions.tsx
similarity index 65%
rename from packages/reports/src/Containers/open-positions.jsx
rename to packages/reports/src/Containers/open-positions.tsx
index a87fe3ba6334..8b4808749035 100644
--- a/packages/reports/src/Containers/open-positions.jsx
+++ b/packages/reports/src/Containers/open-positions.tsx
@@ -1,5 +1,3 @@
-import { PropTypes as MobxPropTypes } from 'mobx-react';
-import PropTypes from 'prop-types';
import React from 'react';
import { withRouter } from 'react-router-dom';
import {
@@ -21,6 +19,7 @@ import {
website_name,
getTotalProfit,
getContractPath,
+ formatPortfolioPosition,
} from '@deriv/shared';
import { localize, Localize } from '@deriv/translations';
import { ReportsTableRowLoader } from '../Components/Elements/ContentLoader';
@@ -31,25 +30,133 @@ import {
getOpenPositionsColumnsTemplate,
getMultiplierOpenPositionsColumnsTemplate,
} from 'Constants/data-table-constants';
-import PlaceholderComponent from '../Components/placeholder-component.jsx';
+import PlaceholderComponent from '../Components/placeholder-component';
import { getCardLabels } from '_common/contract';
import { connect } from 'Stores/connect';
-
-const EmptyPlaceholderWrapper = props => (
+import type { TRootStore } from 'Stores/index';
+import { TContractInfo } from '@deriv/shared/src/utils/contract/contract-types';
+
+type TRangeFloatZeroToOne = React.ComponentProps['value'];
+type TFormatPortfolioPosition = ReturnType;
+type TGetMultiplierOpenPositionsColumnsTemplate = ReturnType;
+type TGetOpenPositionsColumnsTemplate = ReturnType;
+type TColumnsMap = TGetMultiplierOpenPositionsColumnsTemplate | TGetOpenPositionsColumnsTemplate;
+type TColumnsMapElement = TColumnsMap[number];
+type TColIndex =
+ | 'type'
+ | 'reference'
+ | 'currency'
+ | 'purchase'
+ | 'payout'
+ | 'profit'
+ | 'indicative'
+ | 'id'
+ | 'multiplier'
+ | 'buy_price'
+ | 'cancellation'
+ | 'limit_order'
+ | 'bid_price'
+ | 'action';
+
+type TEmptyPlaceholderWrapper = React.PropsWithChildren<{
+ is_empty: boolean;
+ component_icon: string;
+}>;
+
+const EmptyPlaceholderWrapper = ({ is_empty, component_icon, children }: TEmptyPlaceholderWrapper) => (
- {props.is_empty ? (
+ {is_empty ? (
) : (
- props.children
+ children
)}
);
+type TMobileRowRenderer = {
+ row: TFormatPortfolioPosition & { is_sell_requested: boolean };
+ is_footer: boolean;
+ columns_map: Record;
+ server_time: moment.Moment;
+ onClickCancel: () => void;
+ onClickSell: () => void;
+ measure: () => void;
+};
+
+type TOpenPositionsTable = {
+ className: string;
+ columns: Record[];
+ component_icon: string;
+ currency: string;
+ active_positions: TFormatPortfolioPosition[];
+ is_loading: boolean;
+ getRowAction: (row_obj: TRowObj) =>
+ | string
+ | {
+ component: JSX.Element;
+ };
+ mobileRowRenderer: (args: TMobileRowRenderer) => JSX.Element;
+ preloaderCheck: (item: { purchase: number }) => boolean;
+ row_size: number;
+ totals: TTotals;
+};
+
+type TRowObj = {
+ is_unsupported: false;
+ id: number;
+};
+
+type TTotals = {
+ contract_info?: {
+ profit?: number;
+ buy_price?: number;
+ bid_price?: number;
+ cancellation?: {
+ ask_price?: number;
+ };
+ };
+ indicative?: number;
+ purchase?: number;
+ profit_loss?: number;
+ payout?: number;
+};
+
+type TAddToastProps = {
+ key: string;
+ content: string;
+ type: string;
+};
+
+type TOpenPositions = {
+ active_positions: TFormatPortfolioPosition[];
+ component_icon: string;
+ currency: string;
+ error: string;
+ getPositionById: (id: number) => TFormatPortfolioPosition;
+ is_loading: boolean;
+ is_multiplier: boolean;
+ NotificationMessages: () => JSX.Element;
+ onClickCancel: () => void;
+ onClickSell: () => void;
+ onMount: () => void;
+ server_time: moment.Moment;
+ addToast: (obj: TAddToastProps) => void;
+ current_focus: string;
+ onClickRemove: () => void;
+ getContractById: (id: number) => TContractInfo;
+ is_mobile: boolean;
+ removeToast: () => void;
+ setCurrentFocus: () => void;
+ should_show_cancellation_warning: boolean;
+ toggleCancellationWarning: () => void;
+ toggleUnsupportedContractModal: () => void;
+};
+
const MobileRowRenderer = ({
row,
is_footer,
@@ -59,7 +166,7 @@ const MobileRowRenderer = ({
onClickSell,
measure,
...props
-}) => {
+}: TMobileRowRenderer) => {
React.useEffect(() => {
if (!is_footer) {
measure();
@@ -90,15 +197,15 @@ const MobileRowRenderer = ({
const { contract_info, contract_update, type, is_sell_requested } = row;
const { currency, status, date_expiry, date_start } = contract_info;
const duration_type = getContractDurationType(contract_info.longcode);
- const progress_value = getTimePercentage(server_time, date_start, date_expiry) / 100;
+ const progress_value = (getTimePercentage(server_time, date_start ?? 0, date_expiry ?? 0) /
+ 100) as TRangeFloatZeroToOne;
- if (isMultiplierContract(type)) {
+ if (isMultiplierContract(type ?? '')) {
return (
(
+}: TOpenPositionsTable) => (
{is_loading ? (
);
-const getRowAction = row_obj =>
+const getRowAction = (row_obj: TRowObj) =>
row_obj.is_unsupported
? {
component: (
@@ -234,10 +341,13 @@ const getRowAction = row_obj =>
* purchase property in contract positions object is somehow NaN or undefined in the first few responses.
* So we set it to true in these cases to show a preloader for the data-table-row until the correct value is set.
*/
-const isPurchaseReceived = item => isNaN(item.purchase) || !item.purchase;
+const isPurchaseReceived = (item: { purchase: number }) => isNaN(item.purchase) || !item.purchase;
-const getOpenPositionsTotals = (active_positions_filtered, is_multiplier_selected) => {
- let totals;
+const getOpenPositionsTotals = (
+ active_positions_filtered: TFormatPortfolioPosition[],
+ is_multiplier_selected: boolean
+) => {
+ let totals: TTotals;
if (is_multiplier_selected) {
let ask_price = 0;
@@ -247,11 +357,15 @@ const getOpenPositionsTotals = (active_positions_filtered, is_multiplier_selecte
let purchase = 0;
active_positions_filtered.forEach(portfolio_pos => {
- buy_price += +portfolio_pos.contract_info.buy_price;
- bid_price += +portfolio_pos.contract_info.bid_price;
- purchase += +portfolio_pos.purchase;
+ buy_price += Number(portfolio_pos.contract_info.buy_price);
+ bid_price += Number(portfolio_pos.contract_info.bid_price);
+ purchase += Number(portfolio_pos.purchase);
if (portfolio_pos.contract_info) {
- profit += getTotalProfit(portfolio_pos.contract_info);
+ const prices = {
+ bid_price: portfolio_pos.contract_info.bid_price ?? 0,
+ buy_price: portfolio_pos.contract_info.buy_price ?? 0,
+ };
+ profit += getTotalProfit(prices);
if (portfolio_pos.contract_info.cancellation) {
ask_price += portfolio_pos.contract_info.cancellation.ask_price || 0;
@@ -268,9 +382,10 @@ const getOpenPositionsTotals = (active_positions_filtered, is_multiplier_selecte
};
if (ask_price > 0) {
- totals.contract_info.cancellation = {
- ask_price,
- };
+ if (totals.contract_info)
+ totals.contract_info.cancellation = {
+ ask_price,
+ };
}
} else {
let indicative = 0;
@@ -280,9 +395,9 @@ const getOpenPositionsTotals = (active_positions_filtered, is_multiplier_selecte
active_positions_filtered?.forEach(portfolio_pos => {
indicative += +portfolio_pos.indicative;
- purchase += +portfolio_pos.purchase;
- profit_loss += portfolio_pos.profit_loss;
- payout += portfolio_pos.payout;
+ purchase += Number(portfolio_pos.purchase);
+ profit_loss += Number(portfolio_pos.profit_loss);
+ payout += Number(portfolio_pos.payout);
});
totals = {
indicative,
@@ -308,7 +423,7 @@ const OpenPositions = ({
onMount,
server_time,
...props
-}) => {
+}: TOpenPositions) => {
const [active_index, setActiveIndex] = React.useState(is_multiplier ? 1 : 0);
// Tabs should be visible only when there is at least one active multiplier contract
const [has_multiplier_contract, setMultiplierContract] = React.useState(false);
@@ -331,13 +446,15 @@ const OpenPositions = ({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [previous_active_positions]);
- const checkForMultiplierContract = (prev_active_positions = []) => {
+ const checkForMultiplierContract = (prev_active_positions: TFormatPortfolioPosition[] = []) => {
if (!has_multiplier_contract && active_positions !== prev_active_positions) {
- setMultiplierContract(active_positions.some(p => isMultiplierContract(p.contract_info?.contract_type)));
+ setMultiplierContract(
+ active_positions.some(p => isMultiplierContract(p.contract_info?.contract_type ?? ''))
+ );
}
};
- const setActiveTabIndex = index => setActiveIndex(index);
+ const setActiveTabIndex = (index: number) => setActiveIndex(index);
if (error) return {error}
;
@@ -345,8 +462,8 @@ const OpenPositions = ({
const active_positions_filtered = active_positions?.filter(p => {
if (p.contract_info) {
return is_multiplier_selected
- ? isMultiplierContract(p.contract_info.contract_type)
- : !isMultiplierContract(p.contract_info.contract_type);
+ ? isMultiplierContract(p.contract_info.contract_type ?? '')
+ : !isMultiplierContract(p.contract_info.contract_type ?? '');
}
return true;
});
@@ -363,12 +480,12 @@ const OpenPositions = ({
})
: getOpenPositionsColumnsTemplate(currency);
- const columns_map = columns.reduce((map, item) => {
- map[item.col_index] = item;
- return map;
- }, {});
+ const columns_map = {} as Record;
+ columns.forEach(e => {
+ columns_map[e.col_index] = e;
+ });
- const mobileRowRenderer = args => (
+ const mobileRowRenderer = (args: TMobileRowRenderer) => (
-
+
-
+
({
- active_positions: portfolio.active_positions,
- currency: client.currency,
- error: portfolio.error,
- getPositionById: portfolio.getPositionById,
- 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,
- is_mobile: ui.is_mobile,
- removeToast: ui.removeToast,
- setCurrentFocus: ui.setCurrentFocus,
- should_show_cancellation_warning: ui.should_show_cancellation_warning,
- toggleCancellationWarning: ui.toggleCancellationWarning,
- toggleUnsupportedContractModal: ui.toggleUnsupportedContractModal,
-}))(withRouter(OpenPositions));
+export default withRouter(
+ connect(({ client, common, ui, portfolio, contract_trade }: TRootStore) => ({
+ active_positions: portfolio.active_positions,
+ currency: client.currency,
+ error: portfolio.error,
+ getPositionById: portfolio.getPositionById,
+ 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,
+ is_mobile: ui.is_mobile,
+ removeToast: ui.removeToast,
+ setCurrentFocus: ui.setCurrentFocus,
+ should_show_cancellation_warning: ui.should_show_cancellation_warning,
+ toggleCancellationWarning: ui.toggleCancellationWarning,
+ toggleUnsupportedContractModal: ui.toggleUnsupportedContractModal,
+ }))(OpenPositions)
+);
diff --git a/packages/reports/src/Stores/index.js b/packages/reports/src/Stores/index.ts
similarity index 62%
rename from packages/reports/src/Stores/index.js
rename to packages/reports/src/Stores/index.ts
index f0495d2fb985..008866af1948 100644
--- a/packages/reports/src/Stores/index.js
+++ b/packages/reports/src/Stores/index.ts
@@ -1,10 +1,20 @@
import ModulesStore from './Modules';
+import ProfitTableStore from './Modules/Profit/profit-store';
+import StatementStore from './Modules/Statement/statement-store';
+import type { TStores } from '@deriv/stores';
+
+export type TRootStore = TStores & {
+ modules: {
+ profit_table: ProfitTableStore;
+ statement: StatementStore;
+ };
+};
export default class RootStore {
- constructor(core_store) {
+ constructor(core_store: TStores) {
this.client = core_store.client;
this.common = core_store.common;
- this.modules = new ModulesStore(this, core_store);
+ this.modules = new ModulesStore(this);
this.ui = core_store.ui;
this.gtm = core_store.gtm;
this.rudderstack = core_store.rudderstack;
diff --git a/packages/shared/src/utils/helpers/format-response.ts b/packages/shared/src/utils/helpers/format-response.ts
index 5cb8f2baac36..ad9f1afa074d 100644
--- a/packages/shared/src/utils/helpers/format-response.ts
+++ b/packages/shared/src/utils/helpers/format-response.ts
@@ -1,25 +1,8 @@
+import { ProposalOpenContract } from '@deriv/api-types';
import { getUnsupportedContracts } from '../constants';
import { getSymbolDisplayName, TActiveSymbols } from './active-symbols';
import { getMarketInformation } from './market-underlying';
-type TPortfolioPos = {
- buy_price: number;
- contract_id?: number;
- contract_type?: string;
- longcode: string;
- payout: number;
- shortcode: string;
- transaction_id?: number;
- transaction_ids?: {
- buy: number;
- sell: number;
- };
- limit_order?: {
- stop_loss?: null | number;
- take_profit?: null | number;
- };
-};
-
type TIsUnSupportedContract = {
contract_type?: string;
is_forward_starting?: 0 | 1;
@@ -30,24 +13,27 @@ const isUnSupportedContract = (portfolio_pos: TIsUnSupportedContract) =>
!!portfolio_pos.is_forward_starting; // for forward start contracts
export const formatPortfolioPosition = (
- portfolio_pos: TPortfolioPos,
+ portfolio_pos: ProposalOpenContract,
active_symbols: TActiveSymbols = [],
indicative?: number
) => {
const purchase = portfolio_pos.buy_price;
const payout = portfolio_pos.payout;
- const display_name = getSymbolDisplayName(active_symbols, getMarketInformation(portfolio_pos.shortcode).underlying);
- const transaction_id =
- portfolio_pos.transaction_id || (portfolio_pos.transaction_ids && portfolio_pos.transaction_ids.buy);
+ const display_name = getSymbolDisplayName(
+ active_symbols,
+ getMarketInformation(portfolio_pos.shortcode ?? '').underlying
+ );
+ const transaction_id = portfolio_pos.transaction_ids && portfolio_pos.transaction_ids.buy;
return {
contract_info: portfolio_pos,
- details: portfolio_pos.longcode.replace(/\n/g, '
'),
+ details: portfolio_pos.longcode?.replace(/\n/g, '
'),
display_name,
id: portfolio_pos.contract_id,
indicative: (indicative && isNaN(indicative)) || !indicative ? 0 : indicative,
payout,
purchase,
+ profit_loss: portfolio_pos.profit,
reference: Number(transaction_id),
type: portfolio_pos.contract_type,
is_unsupported: isUnSupportedContract(portfolio_pos),
diff --git a/packages/stores/package.json b/packages/stores/package.json
index cdcceeef167c..883a46ff883f 100644
--- a/packages/stores/package.json
+++ b/packages/stores/package.json
@@ -13,6 +13,7 @@
"@deriv/api-types": "^1.0.54",
"@testing-library/react": "^12.0.0",
"typescript": "^4.6.3",
- "react-router": "^5.2.0"
+ "react-router": "^5.2.0",
+ "moment": "^2.29.2"
}
}
diff --git a/packages/stores/types.ts b/packages/stores/types.ts
index 9f1bb7625411..fe554ddee3d3 100644
--- a/packages/stores/types.ts
+++ b/packages/stores/types.ts
@@ -1,4 +1,5 @@
-import type { GetAccountStatus, Authorize, DetailsOfEachMT5Loginid } from '@deriv/api-types';
+import type { Moment } from 'moment';
+import type { GetAccountStatus, Authorize, DetailsOfEachMT5Loginid, ProposalOpenContract } from '@deriv/api-types';
import type { RouteComponentProps } from 'react-router';
type TAccount = NonNullable[0];
@@ -85,6 +86,7 @@ type TCommonStore = {
platform: string;
routeBackInApp: (history: Pick, additional_platform_path?: string[]) => void;
routeTo: (pathname: string) => void;
+ server_time: Moment;
};
type TUiStore = {
@@ -97,6 +99,30 @@ type TUiStore = {
setCurrentFocus: (value: string) => void;
toggleAccountsDialog: () => void;
toggleCashier: () => void;
+ notification_messages_ui: string;
+ addToast: (obj: Record) => void;
+ removeToast: (name: string) => void;
+ should_show_cancellation_warning: boolean;
+ toggleCancellationWarning: () => void;
+ toggleUnsupportedContractModal: () => void;
+};
+
+type TPortfolioStore = {
+ getContractById: (id: number) => ProposalOpenContract;
+ active_positions: ProposalOpenContract[];
+ error: TCommonStoreError;
+ getPositionById: (id: number) => ProposalOpenContract;
+ is_loading: boolean;
+ is_multiplier: boolean;
+ onClickCancel: () => void;
+ onClickSell: () => void;
+ onMount: () => void;
+ onClickRemove: () => void;
+ removePositionById: (id: number) => void;
+};
+
+type TContractStore = {
+ getContractById: (id: number) => ProposalOpenContract;
};
export type TRootStore = {
@@ -104,4 +130,6 @@ export type TRootStore = {
common: TCommonStore;
ui: TUiStore;
modules: Record;
+ portfolio: TPortfolioStore;
+ contract_trade: TContractStore;
};