From d09c5eb5d0fb7a8a0d91e431c6e08661448b67bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20G=C5=82ownia?= Date: Mon, 12 Jun 2023 15:12:53 +0200 Subject: [PATCH] feat: open account breakdown on deal ticket current margin allocation click --- .../trading-deal-ticket-order.cy.ts | 32 +++++-- .../client-pages/market/trade-grid.tsx | 2 + libs/accounts/src/lib/accounts-manager.tsx | 58 ++++++++---- libs/accounts/src/lib/breakdown-table.tsx | 2 - libs/accounts/src/lib/margin-health-chart.tsx | 17 ++-- libs/accounts/src/lib/use-account-balance.tsx | 16 ++-- .../src/lib/use-market-account-balance.tsx | 16 ++-- .../deal-ticket/deal-ticket-container.tsx | 3 + .../deal-ticket/deal-ticket-fee-details.tsx | 88 +++++++++++++------ .../components/deal-ticket/deal-ticket.tsx | 8 +- 10 files changed, 166 insertions(+), 76 deletions(-) diff --git a/apps/trading-e2e/src/integration/trading-deal-ticket-order.cy.ts b/apps/trading-e2e/src/integration/trading-deal-ticket-order.cy.ts index 8de406c00..9969ba2c9 100644 --- a/apps/trading-e2e/src/integration/trading-deal-ticket-order.cy.ts +++ b/apps/trading-e2e/src/integration/trading-deal-ticket-order.cy.ts @@ -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(); }); }); }); diff --git a/apps/trading/client-pages/market/trade-grid.tsx b/apps/trading/client-pages/market/trade-grid.tsx index 0078ca0ba..cabffc238 100644 --- a/apps/trading/client-pages/market/trade-grid.tsx +++ b/apps/trading/client-pages/market/trade-grid.tsx @@ -251,6 +251,7 @@ const MainGrid = memo( const [sizesMiddle, handleOnMiddleLayoutChange] = usePaneLayout({ id: 'middle-1', }); + const onMarketClick = useMarketClickHandler(true); return ( @@ -269,6 +270,7 @@ const MainGrid = memo( navigate('/portfolio')} /> diff --git a/libs/accounts/src/lib/accounts-manager.tsx b/libs/accounts/src/lib/accounts-manager.tsx index 600f33fe9..5c61bb7c6 100644 --- a/libs/accounts/src/lib/accounts-manager.tsx +++ b/libs/accounts/src/lib/accounts-manager.tsx @@ -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 ( + { + if (!isOpen) { + onClose(); + } + }} + > + {assetId && ( + + )} + + ); + } +); + interface AccountManagerProps { partyId: string; onClickAsset: (assetId: string) => void; @@ -152,23 +187,12 @@ export const AccountManager = ({ {...bottomPlaceholderProps} overlayNoRowsTemplate={error ? error.message : t('No accounts')} /> - { - if (!isOpen) { - setBreakdownAssetId(undefined); - } - }} - > - {breakdownAssetId && ( - - )} - + setBreakdownAssetId(undefined), [])} + onMarketClick={onMarketClick ? onMarketClickInternal : undefined} + /> ); }; diff --git a/libs/accounts/src/lib/breakdown-table.tsx b/libs/accounts/src/lib/breakdown-table.tsx index e25dba47b..e07d2a270 100644 --- a/libs/accounts/src/lib/breakdown-table.tsx +++ b/libs/accounts/src/lib/breakdown-table.tsx @@ -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, }; }; diff --git a/libs/accounts/src/lib/margin-health-chart.tsx b/libs/accounts/src/lib/margin-health-chart.tsx index b49a86b5f..dea5e6e31 100644 --- a/libs/accounts/src/lib/margin-health-chart.tsx +++ b/libs/accounts/src/lib/margin-health-chart.tsx @@ -20,7 +20,7 @@ const TooltipContentRow = ({ href?: string; }) => ( <> -
+
{href ? ( {label} @@ -29,7 +29,7 @@ const TooltipContentRow = ({ label )}
-
+
{addDecimalsFormatNumber(value, decimals)}
@@ -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 = [ , , , , ]; - if (rawGeneralAccountBalance) { + if (rawMarginAccountBalance) { const balance = ( ); diff --git a/libs/accounts/src/lib/use-account-balance.tsx b/libs/accounts/src/lib/use-account-balance.tsx index 2b50182bf..80fc7faa7 100644 --- a/libs/accounts/src/lib/use-account-balance.tsx +++ b/libs/accounts/src/lib/use-account-balance.tsx @@ -7,8 +7,12 @@ import { getSettlementAccount } from './get-settlement-account'; export const useAccountBalance = (assetId?: string) => { const { pubKey } = useVegaWallet(); - const [accountBalance, setAccountBalance] = useState(''); - const [accountDecimals, setAccountDecimals] = useState(null); + const [accountBalance, setAccountBalance] = useState( + undefined + ); + const [accountDecimals, setAccountDecimals] = useState( + 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] ); diff --git a/libs/accounts/src/lib/use-market-account-balance.tsx b/libs/accounts/src/lib/use-market-account-balance.tsx index 8491598a2..e8862086d 100644 --- a/libs/accounts/src/lib/use-market-account-balance.tsx +++ b/libs/accounts/src/lib/use-market-account-balance.tsx @@ -7,16 +7,20 @@ import { getMarketAccount } from './get-market-account'; export const useMarketAccountBalance = (marketId: string) => { const { pubKey } = useVegaWallet(); - const [accountBalance, setAccountBalance] = useState(''); - const [accountDecimals, setAccountDecimals] = useState(null); + const [accountBalance, setAccountBalance] = useState( + undefined + ); + const [accountDecimals, setAccountDecimals] = useState( + 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] ); diff --git a/libs/deal-ticket/src/components/deal-ticket/deal-ticket-container.tsx b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-container.tsx index c106469b5..6786fd2c6 100644 --- a/libs/deal-ticket/src/components/deal-ticket/deal-ticket-container.tsx +++ b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-container.tsx @@ -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} /> ) : ( diff --git a/libs/deal-ticket/src/components/deal-ticket/deal-ticket-fee-details.tsx b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-fee-details.tsx index ae81837bd..df0f9f40e 100644 --- a/libs/deal-ticket/src/components/deal-ticket/deal-ticket-fee-details.tsx +++ b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-fee-details.tsx @@ -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) => ( -
-
+}: DealTicketFeeDetailPros) => { + const displayValue = `${formattedValue ?? '-'} ${symbol || ''}`; + const valueElement = onClick ? ( + + ) : ( +
{displayValue}
+ ); + return ( +
{label}
+ + {valueElement} +
- -
{`${ - formattedValue ?? '-' - } ${symbol || ''}`}
-
-
-); + ); +}; 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 = ( setBreakdownDialog(false), + [] + ); + return (
{deductionFromCollateral} - {projectedMargin} setBreakdownDialog(true) : undefined + } value={formatValue(marginAccountBalance, assetDecimals)} symbol={assetSymbol} labelDescription={MARGIN_ACCOUNT_TOOLTIP_TEXT} @@ -350,6 +373,7 @@ export const DealTicketFeeDetails = ({ quantum )} /> + {projectedMargin} + {partyId && ( + + )}
); }; diff --git a/libs/deal-ticket/src/components/deal-ticket/deal-ticket.tsx b/libs/deal-ticket/src/components/deal-ticket/deal-ticket.tsx index 9d72a017b..f52343b9a 100644 --- a/libs/deal-ticket/src/components/deal-ticket/deal-ticket.tsx +++ b/libs/deal-ticket/src/components/deal-ticket/deal-ticket.tsx @@ -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 = ({ } />