Skip to content

Commit

Permalink
Maryia/positions-redesign/Contract cards improvements + fetching Open…
Browse files Browse the repository at this point in the history
… positions + formatProfitTableTransactions TS migration (#58)

* refactor: contract-card-list and card

* feat: buttons demo + animation improvements

* feat: finilize Duration component for the card

* chore: ts migration for closed positions

* fix: console error with remaining time & showing empty message only when empty

* feat: connect real open postions + style and filter fixes
  • Loading branch information
maryia-deriv committed May 23, 2024
1 parent 1437e77 commit 6c5106b
Show file tree
Hide file tree
Showing 14 changed files with 287 additions and 450 deletions.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { formatMoney, toMoment, getSymbolDisplayName, getMarketInformation } from '@deriv/shared';
import { ActiveSymbols, ProfitTable } from '@deriv/api-types';

export const formatProfitTableTransactions = (
transaction: NonNullable<ProfitTable['transactions']>[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,
},
};
};
5 changes: 3 additions & 2 deletions packages/reports/src/Stores/useReportsStores.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<ProfitStores, 'data' | 'date_from' | 'totals'> & {
date_from: number;
data: { [key: string]: string }[];
data: ReturnType<typeof formatProfitTableTransactions>[];
totals: { [key: string]: unknown };
};

Expand Down Expand Up @@ -39,7 +40,7 @@ type TOverrideStatementStore = Omit<
suffix_icon: string;
};

type TReportsStore = {
export type TReportsStore = {
profit_table: TOverrideProfitStore;
statement: TOverrideStatementStore;
};
Expand Down
1 change: 1 addition & 0 deletions packages/stores/src/mockStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions packages/stores/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<TPortfolioPosition['contract_info'], 'expiry_time' | 'tick_count'> & {
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 <Localize i18n_default_text='Ongoing' />;
if (tick_count && currentTick) {
return `${currentTick}/${tick_count} ${getCardLabels().TICKS.toLowerCase()}`;
}
return <RemainingTime end_time={expiry_time} getCardLabels={getCardLabels} start_time={server_time} />;
};

if (!expiry_time) return null;
return (
// TODO: when Tag is exported from quill-ui, use <Tag
// className='contract-card-duration'
// icon={<LabelPairedStopwatchCaptionRegularIcon />}
// label={isMultiplier ? <Localize i18n_default_text='Ongoing' /> : 'Duration'}
// />
// TODO: when <Tag /> is exported from quill-ui, use it instead
<div className='contract-card-duration'>
<LabelPairedStopwatchCaptionRegularIcon />
<CaptionText className='duration'>
{/* in progress */}
{isMultiplier ? <Localize i18n_default_text='Ongoing' /> : 'Duration'}
<CaptionText as='div' className='duration'>
{getDisplayedDuration()}
</CaptionText>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -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<React.SetStateAction<boolean>>;
};

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<Array<ReturnType<typeof setTimeout>>>([]);
const ContractCardList = ({
currency,
hasButtonsDemo,
onClickCancel,
onClickSell,
positions = [],
setHasButtonsDemo,
}: TContractCardListProps) => {
const closedCardsTimeouts = React.useRef<Array<ReturnType<typeof setTimeout>>>([]);

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 (
<div className='contract-card-list'>
{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;
<div
className={classNames('contract-card-list', {
'contract-card-list--has-buttons-demo': hasButtonsDemo && !positions[0].contract_info.sell_time,
})}
>
{positions.map(position => {
const { contract_id: id } = position.contract_info;
return (
<ContractCard
key={id ?? contract_info.contract_id}
{...contract_info}
{...rest}
currentTick={currentTick}
isMultiplier={isMultiplier}
isValidToCancel={validToCancel}
isValidToSell={validToSell}
key={id}
contractInfo={position.contract_info}
currency={currency}
id={id}
isSellRequested={(position as TPortfolioPosition).is_sell_requested}
onCancel={() => id && handleClose?.(id, true)}
onClose={() => id && handleClose?.(id)}
redirectTo={getContractPath(id)}
symbolName={display_name}
totalProfit={totalProfit}
tradeTypeName={tradeTypeName}
redirectTo={id ? getContractPath(id) : undefined}
/>
);
})}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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;
Expand Down Expand Up @@ -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 {
Expand All @@ -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));
}
}
Expand All @@ -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));
}
}
Loading

0 comments on commit 6c5106b

Please sign in to comment.