diff --git a/packages/reports/src/Stores/Modules/Profit/Helpers/format-response.js b/packages/reports/src/Stores/Modules/Profit/Helpers/format-response.js deleted file mode 100644 index f691a0a7b596..000000000000 --- a/packages/reports/src/Stores/Modules/Profit/Helpers/format-response.js +++ /dev/null @@ -1,27 +0,0 @@ -import { formatMoney, toMoment, getSymbolDisplayName, getMarketInformation } from '@deriv/shared'; - -export const formatProfitTableTransactions = (transaction, currency, active_symbols = []) => { - const format_string = 'DD MMM YYYY HH:mm:ss'; - const purchase_time = `${toMoment(+transaction.purchase_time).format(format_string)}`; - const purchase_time_unix = transaction.purchase_time; - const sell_time = `${toMoment(+transaction.sell_time).format(format_string)}`; - const payout = parseFloat(transaction.payout); - const sell_price = parseFloat(transaction.sell_price); - const buy_price = parseFloat(transaction.buy_price); - const profit_loss = formatMoney(currency, Number(sell_price - buy_price), true); - const display_name = getSymbolDisplayName(active_symbols, getMarketInformation(transaction.shortcode).underlying); - - return { - ...transaction, - ...{ - payout, - sell_price, - buy_price, - profit_loss, - sell_time, - purchase_time, - display_name, - purchase_time_unix, - }, - }; -}; diff --git a/packages/reports/src/Stores/Modules/Profit/Helpers/format-response.ts b/packages/reports/src/Stores/Modules/Profit/Helpers/format-response.ts new file mode 100644 index 000000000000..957d9d28231f --- /dev/null +++ b/packages/reports/src/Stores/Modules/Profit/Helpers/format-response.ts @@ -0,0 +1,35 @@ +import { formatMoney, toMoment, getSymbolDisplayName, getMarketInformation } from '@deriv/shared'; +import { ActiveSymbols, ProfitTable } from '@deriv/api-types'; + +export const formatProfitTableTransactions = ( + transaction: NonNullable[number], + currency: string, + active_symbols: ActiveSymbols = [] +) => { + const format_string = 'DD MMM YYYY HH:mm:ss'; + const purchase_time = `${toMoment(Number(transaction.purchase_time)).format(format_string)}`; + const purchase_time_unix = transaction.purchase_time; + const sell_time = `${toMoment(Number(transaction.sell_time)).format(format_string)}`; + const payout = transaction.payout ?? NaN; + const sell_price = transaction.sell_price ?? NaN; + const buy_price = transaction.buy_price ?? NaN; + const profit_loss = formatMoney(currency, Number(sell_price - buy_price), true); + const display_name = getSymbolDisplayName( + active_symbols, + getMarketInformation(transaction.shortcode ?? '').underlying + ); + + return { + ...transaction, + ...{ + payout, + sell_price, + buy_price, + profit_loss, + sell_time, + purchase_time, + display_name, + purchase_time_unix, + }, + }; +}; diff --git a/packages/reports/src/Stores/useReportsStores.tsx b/packages/reports/src/Stores/useReportsStores.tsx index 1e4e6bf76950..dd3b45c1a315 100644 --- a/packages/reports/src/Stores/useReportsStores.tsx +++ b/packages/reports/src/Stores/useReportsStores.tsx @@ -2,10 +2,11 @@ import React from 'react'; import { useStore } from '@deriv/stores'; import ProfitStores from './Modules/Profit/profit-store'; import StatementStores from './Modules/Statement/statement-store'; +import { formatProfitTableTransactions } from './Modules/Profit/Helpers/format-response'; type TOverrideProfitStore = Omit & { date_from: number; - data: { [key: string]: string }[]; + data: ReturnType[]; totals: { [key: string]: unknown }; }; @@ -39,7 +40,7 @@ type TOverrideStatementStore = Omit< suffix_icon: string; }; -type TReportsStore = { +export type TReportsStore = { profit_table: TOverrideProfitStore; statement: TOverrideStatementStore; }; diff --git a/packages/stores/src/mockStore.ts b/packages/stores/src/mockStore.ts index 21fd87a0b7ee..a7e54b80a804 100644 --- a/packages/stores/src/mockStore.ts +++ b/packages/stores/src/mockStore.ts @@ -580,6 +580,7 @@ const mock = (): TStores & { is_mock: boolean } => { barriers: [], error: '', getPositionById: jest.fn(), + is_active_empty: false, is_loading: false, is_accumulator: false, is_multiplier: false, diff --git a/packages/stores/types.ts b/packages/stores/types.ts index e610b510ef87..f5660fc85167 100644 --- a/packages/stores/types.ts +++ b/packages/stores/types.ts @@ -802,6 +802,7 @@ type TPortfolioStore = { barriers: TBarriers; error: string; getPositionById: (id: number) => TPortfolioPosition; + is_active_empty: boolean; is_loading: boolean; is_multiplier: boolean; is_accumulator: boolean; diff --git a/packages/trader/src/AppV2/Components/ContractCard/contract-card-duration.tsx b/packages/trader/src/AppV2/Components/ContractCard/contract-card-duration.tsx index 8c4fbd41ce0b..701db2212746 100644 --- a/packages/trader/src/AppV2/Components/ContractCard/contract-card-duration.tsx +++ b/packages/trader/src/AppV2/Components/ContractCard/contract-card-duration.tsx @@ -5,29 +5,33 @@ import { CaptionText } from '@deriv-com/quill-ui'; import { LabelPairedStopwatchCaptionRegularIcon } from '@deriv/quill-icons'; import { useStore } from '@deriv/stores'; import { observer } from 'mobx-react'; +import { getCardLabels } from '@deriv/shared'; +import { RemainingTime } from '@deriv/components'; -export type TContractCardDurationProps = Pick< - TPortfolioPosition['contract_info'], - 'expiry_time' | 'purchase_time' | 'tick_count' -> & { - currentTick: number | null; - isMultiplier?: boolean; +export type TContractCardDurationProps = Pick & { + currentTick?: number | null; + hasNoAutoExpiry?: boolean; }; export const ContractCardDuration = observer( - ({ currentTick, expiry_time, isMultiplier, purchase_time, tick_count }: TContractCardDurationProps) => { + ({ currentTick, expiry_time, hasNoAutoExpiry, tick_count }: TContractCardDurationProps) => { const { server_time } = useStore().common; + + const getDisplayedDuration = () => { + if (hasNoAutoExpiry) return ; + if (tick_count && currentTick) { + return `${currentTick}/${tick_count} ${getCardLabels().TICKS.toLowerCase()}`; + } + return ; + }; + + if (!expiry_time) return null; return ( - // TODO: when Tag is exported from quill-ui, use } - // label={isMultiplier ? : 'Duration'} - // /> + // TODO: when is exported from quill-ui, use it instead
- - {/* in progress */} - {isMultiplier ? : 'Duration'} + + {getDisplayedDuration()}
); diff --git a/packages/trader/src/AppV2/Components/ContractCard/contract-card-list.tsx b/packages/trader/src/AppV2/Components/ContractCard/contract-card-list.tsx index cf736dc4f49a..5e030ff31623 100644 --- a/packages/trader/src/AppV2/Components/ContractCard/contract-card-list.tsx +++ b/packages/trader/src/AppV2/Components/ContractCard/contract-card-list.tsx @@ -1,76 +1,67 @@ -import { - getContractPath, - getCurrentTick, - getTotalProfit, - getTradeTypeName, - isHighLow, - isMultiplierContract, - isValidToCancel, - isValidToSell, -} from '@deriv/shared'; +import { getContractPath } from '@deriv/shared'; import { TPortfolioPosition } from '@deriv/stores/types'; import React from 'react'; import ContractCard from './contract-card'; +import classNames from 'classnames'; +import { TClosedPosition } from 'AppV2/Containers/Positions/positions-content'; export type TContractCardListProps = { currency?: string; + hasButtonsDemo?: boolean; onClickCancel?: (contractId: number) => void; onClickSell?: (contractId: number) => void; - positions?: TPortfolioPosition[]; + positions?: (TPortfolioPosition | TClosedPosition)[]; + setHasButtonsDemo?: React.Dispatch>; }; -const ContractCardList = ({ onClickCancel, onClickSell, positions = [], ...rest }: TContractCardListProps) => { - // TODO: make it work not only with an open position data but also with a profit_table transaction data - const timeoutIds = React.useRef>>([]); +const ContractCardList = ({ + currency, + hasButtonsDemo, + onClickCancel, + onClickSell, + positions = [], + setHasButtonsDemo, +}: TContractCardListProps) => { + const closedCardsTimeouts = React.useRef>>([]); React.useEffect(() => { - const timers = timeoutIds.current; + const timers = closedCardsTimeouts.current; + const demoTimeout = setTimeout(() => setHasButtonsDemo?.(false), 720); return () => { if (timers.length) { timers.forEach(id => clearTimeout(id)); } + if (demoTimeout) clearTimeout(demoTimeout); }; + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const handleClose = (id: number, shouldCancel?: boolean) => { const timeoutId = setTimeout(() => { shouldCancel ? onClickCancel?.(id) : onClickSell?.(id); }, 160); - timeoutIds.current.push(timeoutId); + closedCardsTimeouts.current.push(timeoutId); }; if (!positions.length) return null; return ( -
- {positions.map(({ id, is_sell_requested, contract_info }) => { - const { contract_type, display_name, profit, shortcode } = contract_info; - const contract_main_title = getTradeTypeName(contract_type ?? '', { - isHighLow: isHighLow({ shortcode }), - showMainTitle: true, - }); - const currentTick = contract_info.tick_count ? getCurrentTick(contract_info) : null; - const tradeTypeName = `${contract_main_title} ${getTradeTypeName(contract_type ?? '', { - isHighLow: isHighLow({ shortcode }), - })}`.trim(); - const isMultiplier = isMultiplierContract(contract_type); - const validToCancel = isValidToCancel(contract_info); - const validToSell = isValidToSell(contract_info) && !is_sell_requested; - const totalProfit = isMultiplierContract(contract_type) ? getTotalProfit(contract_info) : profit; +
+ {positions.map(position => { + const { contract_id: id } = position.contract_info; return ( id && handleClose?.(id, true)} onClose={() => id && handleClose?.(id)} - redirectTo={getContractPath(id)} - symbolName={display_name} - totalProfit={totalProfit} - tradeTypeName={tradeTypeName} + redirectTo={id ? getContractPath(id) : undefined} /> ); })} diff --git a/packages/trader/src/AppV2/Components/ContractCard/contract-card.scss b/packages/trader/src/AppV2/Components/ContractCard/contract-card.scss index fba9210e30e5..63719c3b0c7f 100644 --- a/packages/trader/src/AppV2/Components/ContractCard/contract-card.scss +++ b/packages/trader/src/AppV2/Components/ContractCard/contract-card.scss @@ -9,7 +9,7 @@ justify-content: space-between; height: 10.4rem; transform: translateX(0); - transition: transform var(--motion-duration-snappy) var(--motion-easing-inandout); + transition: transform var(--motion-duration-snappy) var(--motion-easing-linear); .icon, .dc-icon { @@ -34,16 +34,22 @@ } .details, .status-and-profit, + .status, &-duration { display: flex; gap: 0.8rem; align-items: center; } + .status, &-duration { - background-color: color-mix(in sRGB, var(--component-textIcon-normal-default) 4%, transparent); + background-color: var(--core-color-opacity-black-75); height: 2.4rem; padding: 0 0.8rem; border-radius: var(--semantic-borderRadius-sm); + + .dc-remaining-time { + font-size: unset; + } } .status-and-profit { justify-content: space-between; @@ -106,6 +112,10 @@ button:not(:disabled) { background-color: var(--core-color-solid-cherry-700); } + .status { + color: var(--core-color-solid-red-900); + background-color: var(--core-color-opacity-red-100); + } } &.won { .total-profit { @@ -114,11 +124,15 @@ button:not(:disabled) { background-color: var(--core-color-solid-emerald-700); } + .status { + color: var(--core-color-solid-green-900); + background-color: var(--core-color-opacity-green-100); + } } &.show-buttons { transform: translateX(calc(var(--core-size-3600) * -1)); - &--has-cancel-button { + &.has-cancel-button { transform: translateX(calc(var(--core-size-3600) * -2)); } } @@ -133,16 +147,51 @@ &.deleted { opacity: var(--core-opacity-50); max-height: 0; - transition: max-height var(--core-motion-duration-200), opacity var(--core-motion-duration-200); + transition: max-height var(--motion-duration-snappy), opacity var(--motion-duration-snappy); transition-timing-function: var(--motion-easing-inandout); } } &-list { display: flex; + flex-grow: 1; flex-direction: column; gap: 0.8rem; width: inherit; padding: 0.8rem; overflow: hidden; + + &--has-buttons-demo { + .contract-card-wrapper:first-child { + .contract-card { + animation: var(--motion-duration-relax) var(--motion-easing-inandout) bounce-one-button; + + &.has-cancel-button { + animation: var(--motion-duration-relax) var(--motion-easing-inandout) bounce-two-buttons; + } + } + } + } + } +} + +@keyframes bounce-one-button { + 0%, + 100% { + transform: translateX(0); + } + 30%, + 70% { + transform: translateX(calc(var(--core-size-3600) * -1)); + } +} + +@keyframes bounce-two-buttons { + 0%, + 100% { + transform: translateX(0); + } + 30%, + 70% { + transform: translateX(calc(var(--core-size-3600) * -2)); } } diff --git a/packages/trader/src/AppV2/Components/ContractCard/contract-card.tsx b/packages/trader/src/AppV2/Components/ContractCard/contract-card.tsx index 02e8652d9d73..9a1ac60f08f0 100644 --- a/packages/trader/src/AppV2/Components/ContractCard/contract-card.tsx +++ b/packages/trader/src/AppV2/Components/ContractCard/contract-card.tsx @@ -3,28 +3,34 @@ import classNames from 'classnames'; import { CaptionText, Text } from '@deriv-com/quill-ui'; import { useSwipeable } from 'react-swipeable'; import { IconTradeTypes, Money } from '@deriv/components'; -import { getCardLabels } from '@deriv/shared'; -import { TPortfolioPosition } from '@deriv/stores/types'; +import { + TContractInfo, + getCardLabels, + getCurrentTick, + getMarketName, + getTotalProfit, + getTradeTypeName, + isHighLow, + isMultiplierContract, + isValidToCancel, + isValidToSell, +} from '@deriv/shared'; import { ContractCardDuration, TContractCardDurationProps } from './contract-card-duration'; import { BinaryLink } from 'App/Components/Routes'; +import { TClosedPosition } from 'AppV2/Containers/Positions/positions-content'; -type TContractCardProps = Pick< - TPortfolioPosition['contract_info'], - 'buy_price' | 'contract_type' | 'sell_time' | 'profit' -> & - TContractCardDurationProps & { - className?: string; - currency?: string; - isValidToCancel?: boolean; - isValidToSell?: boolean; - onClick?: (e?: React.MouseEvent) => void; - onCancel?: (e?: React.MouseEvent) => void; - onClose?: (e?: React.MouseEvent) => void; - redirectTo?: string; - symbolName?: string; - totalProfit?: string | number; - tradeTypeName?: string; - }; +type TContractCardProps = TContractCardDurationProps & { + className?: string; + contractInfo: TContractInfo | TClosedPosition['contract_info']; + currency?: string; + hasActionButtons?: boolean; + id?: number | null; + isSellRequested?: boolean; + onClick?: (e?: React.MouseEvent) => void; + onCancel?: (e?: React.MouseEvent) => void; + onClose?: (e?: React.MouseEvent) => void; + redirectTo?: string; +}; const DIRECTION = { LEFT: 'left', @@ -38,24 +44,34 @@ const swipeConfig = { const ContractCard = ({ className, - contract_type, + contractInfo, currency, - buy_price, - isValidToCancel, - isValidToSell, + hasActionButtons = true, + isSellRequested, onCancel, onClick, onClose, - profit, redirectTo, - sell_time, - symbolName, - totalProfit, - tradeTypeName, - ...rest }: TContractCardProps) => { const [isDeleted, setIsDeleted] = React.useState(false); const [shouldShowButtons, setShouldShowButtons] = React.useState(false); + const { buy_price, contract_type, display_name, sell_time, shortcode } = contractInfo; + const contract_main_title = getTradeTypeName(contract_type ?? '', { + isHighLow: isHighLow({ shortcode }), + showMainTitle: true, + }); + const currentTick = 'tick_count' in contractInfo && contractInfo.tick_count ? getCurrentTick(contractInfo) : null; + const tradeTypeName = `${contract_main_title} ${getTradeTypeName(contract_type ?? '', { + isHighLow: isHighLow({ shortcode }), + })}`.trim(); + const symbolName = + 'underlying_symbol' in contractInfo ? getMarketName(contractInfo.underlying_symbol ?? '') : display_name; + const isMultiplier = isMultiplierContract(contract_type); + const totalProfit = isMultiplierContract(contract_type) + ? getTotalProfit(contractInfo as TContractInfo) + : (contractInfo as TContractInfo).profit ?? (contractInfo as TClosedPosition['contract_info']).profit_loss; + const validToCancel = isValidToCancel(contractInfo as TContractInfo); + const validToSell = isValidToSell(contractInfo as TContractInfo) && !isSellRequested; const handleSwipe = (direction: string) => { const isLeft = direction === DIRECTION.LEFT; @@ -79,14 +95,15 @@ const ContractCard = ({
0, })} - to={redirectTo} + onClick={onClick} onDragStart={e => e.preventDefault()} + to={redirectTo} >
@@ -105,24 +122,26 @@ const ContractCard = ({
{sell_time ? ( - - {getCardLabels().CLOSED} - + {getCardLabels().CLOSED} ) : ( - + )}
- {!sell_time && ( + {!sell_time && hasActionButtons && (
- {isValidToCancel && ( + {validToCancel && (