Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Maryia/positions-redesign/finilise contract card + add total profit loss + initiate pagination in closed positions #70

Merged
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ import { formatDuration, getDiffDuration } from '@deriv/shared';
import { TGetCardLables } from '../types';

type TRemainingTimeProps = {
as?: React.ElementType;
end_time?: number;
start_time: moment.Moment;
format?: string;
getCardLabels: TGetCardLables;
};

const RemainingTime = ({ end_time, format, getCardLabels, start_time }: TRemainingTimeProps) => {
const RemainingTime = ({ as = 'div', end_time, format, getCardLabels, start_time }: TRemainingTimeProps) => {
const Tag = as;
if (!end_time || start_time.unix() > +end_time) {
return <React.Fragment>{''}</React.Fragment>;
}
Expand All @@ -24,7 +26,7 @@ const RemainingTime = ({ end_time, format, getCardLabels, start_time }: TRemaini
}
const is_zeroes = /^00:00$/.test(remaining_time);

return <React.Fragment>{!is_zeroes && <div className='dc-remaining-time'>{remaining_time}</div>}</React.Fragment>;
return <React.Fragment>{!is_zeroes && <Tag className='dc-remaining-time'>{remaining_time}</Tag>}</React.Fragment>;
};

export default RemainingTime;
3 changes: 1 addition & 2 deletions packages/reports/src/Containers/open-positions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -655,8 +655,7 @@ const OpenPositions = observer(({ component_icon, ...props }: TOpenPositions) =>
/>
);
};
// TODO: Uncomment and update this when DTrader 2.0 development starts:
// if (useFeatureFlags().is_dtrader_v2_enabled) return <Text size='l'>I am Open positions for DTrader 2.0.</Text>;

return (
<React.Fragment>
<NotificationMessages />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const ContractCard = 'Contract Card';
jest.mock('../contract-card', () => jest.fn(() => <div>{ContractCard}</div>));

const mockProps = {
onScroll: jest.fn(),
positions: [
{
contract_info: {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import { TPortfolioPosition } from '@deriv/stores/types';
import { Localize } from '@deriv/translations';
import { CaptionText } from '@deriv-com/quill-ui';
import { Tag } from '@deriv-com/quill-ui';
import { LabelPairedStopwatchCaptionRegularIcon } from '@deriv/quill-icons';
import { getCardLabels } from '@deriv/shared';
import { RemainingTime } from '@deriv/components';
Expand Down Expand Up @@ -29,20 +29,24 @@ export const ContractCardStatusTimer = ({
}
return (
<RemainingTime
as='span'
end_time={date_expiry}
getCardLabels={getCardLabels}
start_time={serverTime as moment.Moment}
/>
);
};
if (!date_expiry || (serverTime as moment.Moment).unix() > +date_expiry || isSold) {
return <CaptionText className='status'>{getCardLabels().CLOSED}</CaptionText>;
return <Tag className='status' label={getCardLabels().CLOSED} variant='custom' color='custom' size='sm' />;
}
return (
// TODO: when <Tag /> is exported from quill-ui, use it instead
<div className='timer'>
<LabelPairedStopwatchCaptionRegularIcon />
<CaptionText as='div'>{getDisplayedDuration()}</CaptionText>
</div>
<Tag
className='timer'
icon={LabelPairedStopwatchCaptionRegularIcon}
label={getDisplayedDuration()}
variant='custom'
color='custom'
size='sm'
/>
);
};
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// TODO: Utilize tokens from quill and logical css properties;
.contract-card {
position: relative;
display: flex;
Expand All @@ -11,41 +10,33 @@
transform: translateX(0);
transition: transform var(--core-motion-duration-200) var(--motion-easing-inandout);

.icon,
.dc-icon {
position: relative;
display: flex;
flex-shrink: 0;
justify-content: center;
align-items: center;
width: var(--core-size-1600);
height: var(--core-size-1600);
cursor: pointer;
}
.body {
&__body {
display: flex;
flex-direction: column;
gap: var(--semantic-spacing-gap-md);
}
.body,
.title {
&__body,
&__title {
min-width: 0;
flex-grow: 1;
}
.details,
.status-and-profit,
.status,
.timer {
&__details,
.status-and-profit {
display: flex;
gap: var(--core-spacing-400);
align-items: center;
}
.status,
.timer {
background-color: var(--core-color-opacity-black-75);
height: var(--core-size-1200);
padding: 0 var(--core-spacing-400);
border-radius: var(--semantic-borderRadius-sm);

.dc-remaining-time {
font-size: unset;
Expand All @@ -54,7 +45,7 @@
.status-and-profit {
justify-content: space-between;
}
.title {
&__title {
display: flex;
flex-direction: column;
}
Expand All @@ -64,7 +55,7 @@
overflow: hidden;
text-overflow: ellipsis;

&__icon {
&-icon {
padding: var(--core-size-200);
}
}
Expand All @@ -87,18 +78,24 @@
inset-inline-end: 0;
inset-block-end: 0;
transform: translateX(100%);
opacity: var(--core-opacity-50);
transition: opacity var(--core-motion-duration-200) var(--motion-easing-inandout);

button {
display: flex;
justify-content: center;
align-items: center;
width: var(--core-size-3600);
height: 100%;
cursor: pointer;

div {
.label {
color: var(--core-color-solid-slate-50);
}
&:disabled:not(.loading) {
background-color: var(--core-color-opacity-black-200);

div {
.label {
color: var(--component-textIcon-normal-disabled);
}
}
Expand Down Expand Up @@ -136,7 +133,7 @@
}
}
&.lost {
.total-profit {
.profit {
color: var(--core-color-solid-red-600);
}
button:not(:disabled),
Expand All @@ -149,7 +146,7 @@
}
}
&.won {
.total-profit {
.profit {
color: var(--core-color-solid-green-600);
}
button:not(:disabled),
Expand All @@ -172,14 +169,15 @@
position: relative;
width: inherit;
overflow: hidden;
min-height: 10.4rem; // equal to __item's height of 104px, must be an exact value for item deletion transition to work
flex-shrink: 0;
max-height: 10.4rem; // Update carefully: max-height in exact units is needed for transition below to work
box-shadow: var(--core-elevation-shadow-130);
border-radius: var(--semantic-borderRadius-md);

&.deleted {
opacity: var(--core-opacity-50);
max-height: 0;
transition: max-height 0.3s, opacity 0.3s;
transition: max-height 0.3s, opacity 0.1s;
transition-timing-function: var(--motion-easing-out);
}
}
Expand Down Expand Up @@ -224,6 +222,12 @@
}
}
}
&.show-buttons,
&-list--has-buttons-demo {
.buttons {
opacity: var(--core-opacity-1300);
}
}
}

.contract-cards {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
getCardLabels,
getCurrentTick,
getMarketName,
getTotalProfit,
getTradeTypeName,
isEnded,
isHighLow,
Expand All @@ -20,6 +19,7 @@ import { ContractCardStatusTimer, TContractCardStatusTimerProps } from './contra
import { BinaryLink } from 'App/Components/Routes';
import { TClosedPosition } from 'AppV2/Containers/Positions/positions-content';
import { TRootStore } from 'Types';
import { getProfit } from 'AppV2/Utils/positions-utils';

type TContractCardProps = TContractCardStatusTimerProps & {
className?: string;
Expand All @@ -45,7 +45,7 @@ const swipeConfig = {
};

const ContractCard = ({
className,
className = 'contract-card',
contractInfo,
currency,
hasActionButtons,
Expand All @@ -57,6 +57,8 @@ const ContractCard = ({
serverTime,
}: TContractCardProps) => {
const [isDeleted, setIsDeleted] = React.useState(false);
const [isClosing, setIsClosing] = React.useState(false);
const [isCanceling, setIsCanceling] = 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 ?? '', {
Expand All @@ -70,12 +72,8 @@ const ContractCard = ({
const symbolName =
'underlying_symbol' in contractInfo ? getMarketName(contractInfo.underlying_symbol ?? '') : display_name;
const isMultiplier = isMultiplierContract(contract_type);
const isSold = isEnded(contractInfo as TContractInfo);
const totalProfit =
(contractInfo as TClosedPosition['contract_info']).profit_loss ??
(isMultiplierContract(contract_type)
? getTotalProfit(contractInfo as TContractInfo)
: (contractInfo as TContractInfo).profit);
const isSold = !!sell_time || isEnded(contractInfo as TContractInfo);
const totalProfit = getProfit(contractInfo);
const validToCancel = isValidToCancel(contractInfo as TContractInfo);
const validToSell = isValidToSell(contractInfo as TContractInfo) && !isSellRequested;

Expand All @@ -93,7 +91,13 @@ const ContractCard = ({
const handleClose = (e: React.MouseEvent<HTMLButtonElement>, shouldCancel?: boolean) => {
e.preventDefault();
e.stopPropagation();
shouldCancel ? onCancel?.(e) : onClose?.(e);
if (shouldCancel) {
onCancel?.(e);
setIsCanceling(true);
} else {
onClose?.(e);
setIsClosing(true);
}
};

React.useEffect(() => {
Expand All @@ -104,10 +108,10 @@ const ContractCard = ({

if (!contract_type) return null;
return (
<div className={classNames('contract-card-wrapper', { deleted: isDeleted })}>
<div className={classNames(`${className}-wrapper`, { deleted: isDeleted })}>
<BinaryLink
{...(hasActionButtons ? swipeHandlers : {})}
className={classNames('contract-card', className, {
className={classNames(className, {
'show-buttons': shouldShowButtons,
'has-cancel-button': validToCancel,
lost: Number(totalProfit) < 0,
Expand All @@ -117,10 +121,10 @@ const ContractCard = ({
onDragStart={e => e.preventDefault()}
to={redirectTo}
>
<div className='body'>
<div className='details'>
<IconTradeTypes className='trade-type__icon' type={contract_type ?? ''} size={32} />
<div className='title'>
<div className={`${className}__body`}>
<div className={`${className}__details`}>
<IconTradeTypes className='trade-type-icon' type={contract_type ?? ''} size={32} />
<div className={`${className}__title`}>
<Text className='trade-type' size='sm'>
{tradeTypeName}
</Text>
Expand All @@ -136,11 +140,11 @@ const ContractCard = ({
<ContractCardStatusTimer
currentTick={currentTick}
hasNoAutoExpiry={isMultiplier}
isSold={!!sell_time || isSold}
isSold={isSold}
serverTime={serverTime}
{...contractInfo}
/>
<Text className='total-profit' size='sm'>
<Text className='profit' size='sm'>
<Money amount={totalProfit} currency={currency} has_sign show_currency />
</Text>
</div>
Expand All @@ -149,25 +153,31 @@ const ContractCard = ({
<div className='buttons'>
{validToCancel && (
<button
className={classNames('icon', 'cancel', { loading: isSellRequested })}
aria-label='cancel'
disabled={Number(totalProfit) >= 0}
className={classNames({ loading: isSellRequested && isCanceling })}
disabled={Number((contractInfo as TContractInfo).profit) >= 0}
onClick={e => handleClose(e, true)}
>
<CaptionText bold as='div'>
{isSellRequested ? <div className='circle-loader' /> : getCardLabels().CANCEL}
</CaptionText>
{isSellRequested && isCanceling ? (
<div className='circle-loader' />
) : (
<CaptionText bold as='div' className='label'>
{getCardLabels().CANCEL}
</CaptionText>
)}
</button>
)}
<button
className={classNames('icon', 'close', { loading: isSellRequested })}
aria-label='close'
className={classNames({ loading: isSellRequested && isClosing })}
disabled={!validToSell}
onClick={handleClose}
>
<CaptionText bold as='div'>
{isSellRequested ? <div className='circle-loader' /> : getCardLabels().CLOSE}
</CaptionText>
{isSellRequested && isClosing ? (
<div className='circle-loader' />
) : (
<CaptionText bold as='div' className='label'>
{getCardLabels().CLOSE}
</CaptionText>
)}
</button>
</div>
)}
Expand Down
Loading
Loading