feat: open account breakdown on deal ticket current margin allocation click

This commit is contained in:
Bartłomiej Głownia
2023-06-12 16:29:53 +02:00
parent 59785bdc78
commit d09c5eb5d0
10 changed files with 166 additions and 76 deletions
@@ -96,15 +96,29 @@ describe('deal ticker order validation', { tags: '@smoke' }, () => {
it('must have total margin available', () => {
// 7001-COLL-011
cy.getByTestId('tab-ticket')
.find('.text-xs')
.eq(5)
.within(() => {
cy.get('[data-state="closed"]').should(
'have.text',
'Total margin available100,000.01 tDAI'
);
});
cy.getByTestId('deal-ticket-fee-total-margin-available').within(() => {
cy.get('[data-state="closed"]').should(
'have.text',
'Total margin available100,000.01 tDAI'
);
});
});
it('must have current margin allocation', () => {
cy.getByTestId('deal-ticket-fee-current-margin-allocation').within(() => {
cy.get('[data-state="closed"]:first').should(
'have.text',
'Current margin allocation'
);
});
});
it('should open usage breakdown dialog when clicked on current margin allocation', () => {
cy.getByTestId('deal-ticket-fee-current-margin-allocation').within(() => {
cy.get('button').click();
});
cy.getByTestId('usage-breakdown').should('exist');
cy.getByTestId('dialog-close').click();
});
});
});
@@ -251,6 +251,7 @@ const MainGrid = memo(
const [sizesMiddle, handleOnMiddleLayoutChange] = usePaneLayout({
id: 'middle-1',
});
const onMarketClick = useMarketClickHandler(true);
return (
<ResizableGrid vertical onChange={handleOnLayoutChange}>
@@ -269,6 +270,7 @@ const MainGrid = memo(
<Tab id="ticket" name={t('Ticket')}>
<TradingViews.ticket.component
marketId={marketId}
onMarketClick={onMarketClick}
onClickCollateral={() => navigate('/portfolio')}
/>
</Tab>
+41 -17
View File
@@ -63,6 +63,41 @@ const AccountBreakdown = ({
);
};
export const AccountBreakdownDialog = memo(
({
assetId,
partyId,
onClose,
onMarketClick,
}: {
assetId?: string;
partyId: string;
onClose: () => void;
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
}) => {
console.log('render');
return (
<Dialog
size="medium"
open={Boolean(assetId)}
onChange={(isOpen) => {
if (!isOpen) {
onClose();
}
}}
>
{assetId && (
<AccountBreakdown
assetId={assetId}
partyId={partyId}
onMarketClick={onMarketClick}
/>
)}
</Dialog>
);
}
);
interface AccountManagerProps {
partyId: string;
onClickAsset: (assetId: string) => void;
@@ -152,23 +187,12 @@ export const AccountManager = ({
{...bottomPlaceholderProps}
overlayNoRowsTemplate={error ? error.message : t('No accounts')}
/>
<Dialog
size="medium"
open={Boolean(breakdownAssetId)}
onChange={(isOpen) => {
if (!isOpen) {
setBreakdownAssetId(undefined);
}
}}
>
{breakdownAssetId && (
<AccountBreakdown
assetId={breakdownAssetId}
partyId={partyId}
onMarketClick={onMarketClick ? onMarketClickInternal : undefined}
/>
)}
</Dialog>
<AccountBreakdownDialog
assetId={breakdownAssetId}
partyId={partyId}
onClose={useCallback(() => setBreakdownAssetId(undefined), [])}
onMarketClick={onMarketClick ? onMarketClickInternal : undefined}
/>
</div>
);
};
@@ -1,7 +1,6 @@
import { forwardRef } from 'react';
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { Intent } from '@vegaprotocol/ui-toolkit';
import { AgGridColumn } from 'ag-grid-react';
import type { AgGridReact, AgGridReactProps } from 'ag-grid-react';
import type { AccountFields } from './accounts-data-provider';
@@ -34,7 +33,6 @@ export const progressBarValueFormatter = ({
low: addDecimalsFormatNumber(min.toString(), data.asset.decimals),
high: addDecimalsFormatNumber(mid.toString(), data.asset.decimals),
value: range ? Number((min * BigInt(100)) / range) : 0,
intent: Intent.Warning,
};
};
+11 -6
View File
@@ -20,7 +20,7 @@ const TooltipContentRow = ({
href?: string;
}) => (
<>
<div className="float-left clear-left">
<div className="float-left clear-left" key="label">
{href ? (
<ExternalLink href={href} target="_blank">
{label}
@@ -29,7 +29,7 @@ const TooltipContentRow = ({
label
)}
</div>
<div className="float-right">
<div className="float-right" key="value">
{addDecimalsFormatNumber(value, decimals)}
</div>
</>
@@ -63,8 +63,8 @@ export const MarginHealthChart = ({
const initialLevel = Number(data.initialLevel);
const maintenanceLevel = Number(data.maintenanceLevel);
const searchLevel = Number(data.searchLevel);
const marginAccountBalance = Number(rawMarginAccountBalance);
const generalAccountBalance = Number(rawGeneralAccountBalance);
const marginAccountBalance = Number(rawMarginAccountBalance || '0');
const generalAccountBalance = Number(rawGeneralAccountBalance || '0');
const max = Math.max(
marginAccountBalance + generalAccountBalance,
collateralReleaseLevel
@@ -78,24 +78,28 @@ export const MarginHealthChart = ({
const tooltipContent = [
<TooltipContentRow
key={'maintenance'}
label={t('maintenance level')}
href="https://docs.vega.xyz/testnet/concepts/trading-on-vega/positions-margin#margin-level-maintenance"
value={data.maintenanceLevel}
decimals={decimals}
/>,
<TooltipContentRow
key={'search'}
label={t('search level')}
href="https://docs.vega.xyz/testnet/concepts/trading-on-vega/positions-margin#margin-level-searching-for-collateral"
value={data.searchLevel}
decimals={decimals}
/>,
<TooltipContentRow
key={'initial'}
label={t('initial level')}
href="https://docs.vega.xyz/testnet/concepts/trading-on-vega/positions-margin#margin-level-initial"
value={data.initialLevel}
decimals={decimals}
/>,
<TooltipContentRow
key={'release'}
label={t('release level')}
href="https://docs.vega.xyz/testnet/concepts/trading-on-vega/positions-margin#margin-level-releasing-collateral"
value={data.collateralReleaseLevel}
@@ -103,11 +107,12 @@ export const MarginHealthChart = ({
/>,
];
if (rawGeneralAccountBalance) {
if (rawMarginAccountBalance) {
const balance = (
<TooltipContentRow
key={'balance'}
label={t('balance')}
value={rawGeneralAccountBalance}
value={rawMarginAccountBalance}
decimals={decimals}
/>
);
+10 -6
View File
@@ -7,8 +7,12 @@ import { getSettlementAccount } from './get-settlement-account';
export const useAccountBalance = (assetId?: string) => {
const { pubKey } = useVegaWallet();
const [accountBalance, setAccountBalance] = useState<string>('');
const [accountDecimals, setAccountDecimals] = useState<number | null>(null);
const [accountBalance, setAccountBalance] = useState<string | undefined>(
undefined
);
const [accountDecimals, setAccountDecimals] = useState<number | undefined>(
undefined
);
const variables = useMemo(() => {
return { partyId: pubKey || '' };
}, [pubKey]);
@@ -17,8 +21,8 @@ export const useAccountBalance = (assetId?: string) => {
const account = assetId
? getSettlementAccount({ accounts: data, assetId })
: undefined;
setAccountBalance(account?.balance || '');
setAccountDecimals(account?.asset.decimals || null);
setAccountBalance(account?.balance);
setAccountDecimals(account?.asset.decimals);
return true;
},
[assetId]
@@ -33,8 +37,8 @@ export const useAccountBalance = (assetId?: string) => {
return useMemo(
() => ({
accountBalance: pubKey ? accountBalance : '',
accountDecimals: pubKey ? accountDecimals : null,
accountBalance: pubKey ? accountBalance : undefined,
accountDecimals: pubKey ? accountDecimals : undefined,
}),
[accountBalance, accountDecimals, pubKey]
);
@@ -7,16 +7,20 @@ import { getMarketAccount } from './get-market-account';
export const useMarketAccountBalance = (marketId: string) => {
const { pubKey } = useVegaWallet();
const [accountBalance, setAccountBalance] = useState<string>('');
const [accountDecimals, setAccountDecimals] = useState<number | null>(null);
const [accountBalance, setAccountBalance] = useState<string | undefined>(
undefined
);
const [accountDecimals, setAccountDecimals] = useState<number | undefined>(
undefined
);
const update = useCallback(
({ data }: { data: Account[] | null }) => {
const account = getMarketAccount({ accounts: data, marketId });
if (account?.balance) {
setAccountBalance(account?.balance || '');
setAccountBalance(account?.balance);
}
if (account?.asset.decimals) {
setAccountDecimals(account?.asset.decimals || null);
setAccountDecimals(account?.asset.decimals);
}
return true;
},
@@ -32,8 +36,8 @@ export const useMarketAccountBalance = (marketId: string) => {
return useMemo(
() => ({
accountBalance: pubKey ? accountBalance : '',
accountDecimals: pubKey ? accountDecimals : null,
accountBalance: pubKey ? accountBalance : undefined,
accountDecimals: pubKey ? accountDecimals : undefined,
}),
[accountBalance, accountDecimals, pubKey]
);
@@ -7,11 +7,13 @@ import { DealTicket } from './deal-ticket';
export interface DealTicketContainerProps {
marketId: string;
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
onClickCollateral?: () => void;
}
export const DealTicketContainer = ({
marketId,
onMarketClick,
onClickCollateral,
}: DealTicketContainerProps) => {
const {
@@ -47,6 +49,7 @@ export const DealTicketContainer = ({
marketData={marketData}
submit={(orderSubmission) => create({ orderSubmission })}
onClickCollateral={onClickCollateral}
onMarketClick={onMarketClick}
/>
) : (
<Splash>
@@ -1,3 +1,4 @@
import { useCallback, useState } from 'react';
import { Tooltip } from '@vegaprotocol/ui-toolkit';
import classnames from 'classnames';
import type { ReactNode } from 'react';
@@ -8,6 +9,7 @@ import { useVegaWallet } from '@vegaprotocol/wallet';
import type { Market } from '@vegaprotocol/markets';
import type { EstimatePositionQuery } from '@vegaprotocol/positions';
import type { EstimateFeesQuery } from '../../hooks/__generated__/EstimateOrder';
import { AccountBreakdownDialog } from '@vegaprotocol/accounts';
import {
addDecimalsFormatNumber,
@@ -62,6 +64,7 @@ export interface DealTicketFeeDetailPros {
indent?: boolean | undefined;
labelDescription?: ReactNode;
formattedValue?: string;
onClick?: () => void;
}
export const DealTicketFeeDetail = ({
@@ -70,35 +73,46 @@ export const DealTicketFeeDetail = ({
labelDescription,
symbol,
indent,
onClick,
formattedValue,
}: DealTicketFeeDetailPros) => (
<div
data-testid={
'deal-ticket-fee-' + label.toLocaleLowerCase().replace(/\s/g, '-')
}
key={typeof label === 'string' ? label : 'value-dropdown'}
className={classnames(
'text-xs mt-2 flex justify-between items-center gap-4 flex-wrap',
{ 'ml-2': indent }
)}
>
<div>
}: DealTicketFeeDetailPros) => {
const displayValue = `${formattedValue ?? '-'} ${symbol || ''}`;
const valueElement = onClick ? (
<button
onClick={onClick}
className="text-neutral-500 dark:text-neutral-300"
>
{displayValue}
</button>
) : (
<div className="text-neutral-500 dark:text-neutral-300">{displayValue}</div>
);
return (
<div
data-testid={
'deal-ticket-fee-' + label.toLocaleLowerCase().replace(/\s/g, '-')
}
key={typeof label === 'string' ? label : 'value-dropdown'}
className={classnames(
'text-xs mt-2 flex justify-between items-center gap-4 flex-wrap',
{ 'ml-2': indent }
)}
>
<Tooltip description={labelDescription}>
<div>{label}</div>
</Tooltip>
<Tooltip description={`${value ?? '-'} ${symbol || ''}`}>
{valueElement}
</Tooltip>
</div>
<Tooltip description={`${value ?? '-'} ${symbol || ''}`}>
<div className="text-neutral-500 dark:text-neutral-300">{`${
formattedValue ?? '-'
} ${symbol || ''}`}</div>
</Tooltip>
</div>
);
);
};
export interface DealTicketFeeDetailsProps {
generalAccountBalance?: string;
marginAccountBalance?: string;
market: Market;
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
assetSymbol: string;
notionalSize: string | null;
feeEstimate: EstimateFeesQuery['estimateFees'] | undefined;
@@ -111,23 +125,24 @@ export const DealTicketFeeDetails = ({
assetSymbol,
feeEstimate,
market,
onMarketClick,
notionalSize,
positionEstimate,
}: DealTicketFeeDetailsProps) => {
const { pubKey } = useVegaWallet();
const [breakdownDialog, setBreakdownDialog] = useState(false);
const { pubKey: partyId } = useVegaWallet();
const { data: currentMargins } = useDataProvider({
dataProvider: marketMarginDataProvider,
variables: { marketId: market.id, partyId: pubKey || '' },
skip: !pubKey,
variables: { marketId: market.id, partyId: partyId || '' },
skip: !partyId,
});
const liquidationEstimate = positionEstimate?.liquidation;
const marginEstimate = positionEstimate?.margin;
const totalBalance =
BigInt(generalAccountBalance || '0') + BigInt(marginAccountBalance || '0');
const assetDecimals =
market.tradableInstrument.instrument.product.settlementAsset.decimals;
const quantum =
market.tradableInstrument.instrument.product.settlementAsset.quantum;
const { settlementAsset: asset } =
market.tradableInstrument.instrument.product;
const { decimals: assetDecimals, quantum } = asset;
let marginRequiredBestCase: string | undefined = undefined;
let marginRequiredWorstCase: string | undefined = undefined;
if (marginEstimate) {
@@ -198,7 +213,6 @@ export const DealTicketFeeDetails = ({
);
projectedMargin = (
<DealTicketFeeDetail
indent
label={t('Projected margin')}
value={formatRange(
marginEstimate?.bestCase.initialLevel,
@@ -269,6 +283,11 @@ export const DealTicketFeeDetails = ({
);
}
const onAccountBreakdownDialogClose = useCallback(
() => setBreakdownDialog(false),
[]
);
return (
<div>
<DealTicketFeeDetail
@@ -323,6 +342,7 @@ export const DealTicketFeeDetails = ({
/>
<DealTicketFeeDetail
label={t('Total margin available')}
indent
value={formatValue(totalMarginAvailable, assetDecimals)}
formattedValue={formatValue(
totalMarginAvailable,
@@ -338,9 +358,12 @@ export const DealTicketFeeDetails = ({
)}
/>
{deductionFromCollateral}
{projectedMargin}
<DealTicketFeeDetail
label={t('Current margin allocation')}
indent
onClick={
generalAccountBalance ? () => setBreakdownDialog(true) : undefined
}
value={formatValue(marginAccountBalance, assetDecimals)}
symbol={assetSymbol}
labelDescription={MARGIN_ACCOUNT_TOOLTIP_TEXT}
@@ -350,6 +373,7 @@ export const DealTicketFeeDetails = ({
quantum
)}
/>
{projectedMargin}
<DealTicketFeeDetail
label={t('Liquidation price estimate')}
value={liquidationPriceEstimate}
@@ -357,6 +381,14 @@ export const DealTicketFeeDetails = ({
symbol={assetSymbol}
labelDescription={LIQUIDATION_PRICE_ESTIMATE_TOOLTIP_TEXT}
/>
{partyId && (
<AccountBreakdownDialog
assetId={breakdownDialog ? asset.id : undefined}
partyId={partyId}
onMarketClick={onMarketClick}
onClose={onAccountBreakdownDialogClose}
/>
)}
</div>
);
};
@@ -58,12 +58,14 @@ import { useDataProvider } from '@vegaprotocol/data-provider';
export interface DealTicketProps {
market: Market;
marketData: MarketData;
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
submit: (order: OrderSubmission) => void;
onClickCollateral?: () => void;
}
export const DealTicket = ({
market,
onMarketClick,
marketData,
submit,
onClickCollateral,
@@ -98,7 +100,7 @@ export const DealTicket = ({
const { accountBalance: generalAccountBalance } = useAccountBalance(asset.id);
const balance = (
BigInt(marginAccountBalance) + BigInt(generalAccountBalance)
BigInt(marginAccountBalance || '0') + BigInt(generalAccountBalance || '0')
).toString();
const { marketState, marketTradingMode } = marketData;
@@ -192,7 +194,8 @@ export const DealTicket = ({
return;
}
const hasNoBalance = !BigInt(generalAccountBalance);
const hasNoBalance =
!generalAccountBalance || !BigInt(generalAccountBalance);
if (hasNoBalance) {
setError('summary', {
message: SummaryValidationType.NoCollateral,
@@ -479,6 +482,7 @@ export const DealTicket = ({
}
/>
<DealTicketFeeDetails
onMarketClick={onMarketClick}
feeEstimate={feeEstimate}
notionalSize={notionalSize}
assetSymbol={assetSymbol}