diff --git a/apps/trading-e2e/src/integration/market-summary.cy.ts b/apps/trading-e2e/src/integration/market-summary.cy.ts index eb490e5a8..1a89dc16b 100644 --- a/apps/trading-e2e/src/integration/market-summary.cy.ts +++ b/apps/trading-e2e/src/integration/market-summary.cy.ts @@ -17,6 +17,7 @@ const itemValue = 'item-value'; describe('Market proposal notification', { tags: '@smoke' }, () => { before(() => { + cy.setVegaWallet(); cy.mockTradingPage( Schema.MarketState.STATE_ACTIVE, Schema.MarketTradingMode.TRADING_MODE_MONITORING_AUCTION, @@ -239,7 +240,6 @@ describe('market states not accepting orders', { tags: '@smoke' }, function () { cy.visit('/#/markets/market-0'); }); it('must display that market is not accepting orders', function () { - cy.getByTestId('place-order').click(); cy.getByTestId('dealticket-error-message-summary').should( 'have.text', `This market is ${marketState diff --git a/apps/trading-e2e/src/integration/trading-deal-ticket.cy.ts b/apps/trading-e2e/src/integration/trading-deal-ticket.cy.ts index 77805cc8a..83536a1c8 100644 --- a/apps/trading-e2e/src/integration/trading-deal-ticket.cy.ts +++ b/apps/trading-e2e/src/integration/trading-deal-ticket.cy.ts @@ -13,7 +13,6 @@ const toggleShort = 'order-side-SIDE_SELL'; const toggleLong = 'order-side-SIDE_BUY'; const toggleLimit = 'order-type-TYPE_LIMIT'; const toggleMarket = 'order-type-TYPE_MARKET'; -const errorMessage = 'dealticket-error-message'; const TIFlist = Object.values(Schema.OrderTimeInForce).map((value) => { return { @@ -363,11 +362,11 @@ describe('deal ticket validation', { tags: '@smoke' }, () => { cy.wait('@Markets'); }); - it('must not place an order if wallet is not connected', () => { + it('must show place order button and connect wallet if wallet is not connected', () => { cy.getByTestId('connect-vega-wallet'); // Not connected cy.getByTestId('order-connect-wallet').should('exist'); - cy.getByTestId(placeOrderBtn).should('not.exist'); - cy.getByTestId(errorMessage).should('not.exist'); + cy.getByTestId(placeOrderBtn).should('exist'); + cy.getByTestId('deal-ticket-connect-wallet').should('exist'); }); it('must be able to select order direction - long/short', function () { @@ -664,13 +663,13 @@ describe('account validation', { tags: '@regression' }, () => { }); it('should show an error if your balance is zero', () => { - cy.getByTestId('place-order').should('not.be.disabled'); - cy.getByTestId('place-order').click(); cy.getByTestId('place-order').should('be.disabled'); //7002-SORD-003 cy.getByTestId('dealticket-error-message-zero-balance').should( 'have.text', - 'Insufficient balance. Deposit ' + 'tDAI' + 'You need ' + + 'tDAI' + + ' in your wallet to trade in this market. See all your collateral.Make a deposit' ); cy.getByTestId('deal-ticket-deposit-dialog-button').should('exist'); }); @@ -708,7 +707,7 @@ describe('account validation', { tags: '@regression' }, () => { ); cy.getByTestId('dealticket-warning-margin').should( 'contain.text', - '9,999.99 tDAI currently required, 1,000.00 tDAI available' + '9,999.99 tDAI is currently required. You have only 1,000.00 tDAI available.Deposit tDAI' ); cy.getByTestId('deal-ticket-deposit-dialog-button').click(); cy.getByTestId('dialog-content') diff --git a/apps/trading/client-pages/market/market.tsx b/apps/trading/client-pages/market/market.tsx index 6de4c56c0..99e8054ae 100644 --- a/apps/trading/client-pages/market/market.tsx +++ b/apps/trading/client-pages/market/market.tsx @@ -98,8 +98,14 @@ export const MarketPage = () => { if (w > 960) { return ; } - return ; - }, [w, data, onSelect]); + return ( + navigate('/portfolio')} + /> + ); + }, [w, data, onSelect, navigate]); if (!data && marketId) { return ( diff --git a/apps/trading/client-pages/market/trade-grid.tsx b/apps/trading/client-pages/market/trade-grid.tsx index 7fc16117e..98d1ca110 100644 --- a/apps/trading/client-pages/market/trade-grid.tsx +++ b/apps/trading/client-pages/market/trade-grid.tsx @@ -111,7 +111,10 @@ const MainGrid = ({ - + navigate('/portfolio')} + /> void; onMarketClick?: (marketId: string) => void; + onClickCollateral: () => void; } -export const TradePanels = ({ market, onSelect }: TradePanelsProps) => { +export const TradePanels = ({ + market, + onSelect, + onClickCollateral, +}: TradePanelsProps) => { const [view, setView] = useState('Candles'); const renderView = () => { const Component = memo<{ marketId: string; onSelect: (marketId: string) => void; onMarketClick?: (marketId: string) => void; + onClickCollateral: () => void; }>(TradingViews[view]); if (!Component) { @@ -227,7 +236,13 @@ export const TradePanels = ({ market, onSelect }: TradePanelsProps) => { if (!market) return {NO_MARKET}; - return ; + return ( + + ); }; return ( diff --git a/apps/trading/components/footer/footer.spec.tsx b/apps/trading/components/footer/footer.spec.tsx index b8c465a31..687a3fbe6 100644 --- a/apps/trading/components/footer/footer.spec.tsx +++ b/apps/trading/components/footer/footer.spec.tsx @@ -42,7 +42,7 @@ describe('Footer', () => { describe('NodeHealth', () => { const cases = [ - { diff: 0, classname: 'bg-success', text: 'Operational' }, + { diff: 0, classname: 'bg-vega-green-550', text: 'Operational' }, { diff: 5, classname: 'bg-warning', text: '5 Blocks behind' }, { diff: -1, classname: 'bg-danger', text: 'Non operational' }, ]; diff --git a/libs/deal-ticket/src/components/deal-ticket-validation/margin-warning.tsx b/libs/deal-ticket/src/components/deal-ticket-validation/margin-warning.tsx index eed9a539c..260b4290a 100644 --- a/libs/deal-ticket/src/components/deal-ticket-validation/margin-warning.tsx +++ b/libs/deal-ticket/src/components/deal-ticket-validation/margin-warning.tsx @@ -1,5 +1,5 @@ import { formatNumber, t } from '@vegaprotocol/react-helpers'; -import { ButtonLink } from '@vegaprotocol/ui-toolkit'; +import { Notification, Intent } from '@vegaprotocol/ui-toolkit'; import { DepositDialog, useDepositDialog } from '@vegaprotocol/deposits'; interface Props { @@ -16,27 +16,23 @@ export const MarginWarning = ({ margin, balance, asset }: Props) => { const openDepositDialog = useDepositDialog((state) => state.open); return ( <> -
-

- {t('You may not have enough margin available to open this position.')}{' '} - openDepositDialog(asset.id)} - > - {t(`Deposit ${asset.symbol}`)} - -

-

- {`${formatNumber(margin, asset.decimals)} ${asset.symbol} ${t( - 'currently required' - )}, ${formatNumber(balance, asset.decimals)} ${asset.symbol} ${t( - 'available' - )}`} -

-
+ openDepositDialog(asset.id), + dataTestId: 'deal-ticket-deposit-dialog-button', + }} + /> ); diff --git a/libs/deal-ticket/src/components/deal-ticket-validation/zero-balance-error.tsx b/libs/deal-ticket/src/components/deal-ticket-validation/zero-balance-error.tsx index b92b9034c..d89e9b0fa 100644 --- a/libs/deal-ticket/src/components/deal-ticket-validation/zero-balance-error.tsx +++ b/libs/deal-ticket/src/components/deal-ticket-validation/zero-balance-error.tsx @@ -1,26 +1,36 @@ -import { t } from '@vegaprotocol/react-helpers'; -import { ButtonLink, InputError } from '@vegaprotocol/ui-toolkit'; +import { Intent, Notification, Link } from '@vegaprotocol/ui-toolkit'; import { useDepositDialog } from '@vegaprotocol/deposits'; +import { t } from '@vegaprotocol/react-helpers'; + interface ZeroBalanceErrorProps { asset: { id: string; symbol: string; }; + onClickCollateral: () => void; } -export const ZeroBalanceError = ({ asset }: ZeroBalanceErrorProps) => { +export const ZeroBalanceError = ({ + asset, + onClickCollateral, +}: ZeroBalanceErrorProps) => { const openDepositDialog = useDepositDialog((state) => state.open); return ( - -

- {t('Insufficient balance. ')} - openDepositDialog(asset.id)} - > - {t(`Deposit ${asset.symbol}`)} - -

-
+ + You need {asset.symbol} in your wallet to trade in this market. See + all your collateral. + + } + buttonProps={{ + text: t(`Make a deposit`), + action: () => openDepositDialog(asset.id), + dataTestId: 'deal-ticket-deposit-dialog-button', + size: 'md', + }} + /> ); }; diff --git a/libs/deal-ticket/src/components/deal-ticket/deal-ticket-button.tsx b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-button.tsx index 66fb2239d..5960612bd 100644 --- a/libs/deal-ticket/src/components/deal-ticket/deal-ticket-button.tsx +++ b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-button.tsx @@ -1,7 +1,7 @@ import { t } from '@vegaprotocol/react-helpers'; import type { ButtonVariant } from '@vegaprotocol/ui-toolkit'; import { Button } from '@vegaprotocol/ui-toolkit'; -import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet'; +import { useVegaWallet } from '@vegaprotocol/wallet'; interface Props { disabled: boolean; @@ -9,32 +9,19 @@ interface Props { } export const DealTicketButton = ({ disabled, variant }: Props) => { - const { pubKey } = useVegaWallet(); - const openVegaWalletDialog = useVegaWalletDialogStore( - (store) => store.openVegaWalletDialog - ); - return pubKey ? ( + const { pubKey, isReadOnly } = useVegaWallet(); + const isDisabled = !pubKey || isReadOnly || disabled; + return (
- ) : ( - ); }; 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 e9ce6dd54..b51db8229 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 @@ -8,9 +8,13 @@ import { DealTicket } from './deal-ticket'; export interface DealTicketContainerProps { marketId: string; + onClickCollateral?: () => void; } -export const DealTicketContainer = ({ marketId }: DealTicketContainerProps) => { +export const DealTicketContainer = ({ + marketId, + onClickCollateral, +}: DealTicketContainerProps) => { const { data: market, error: marketError, @@ -41,6 +45,7 @@ export const DealTicketContainer = ({ marketId }: DealTicketContainerProps) => { market={market} marketData={marketData} submit={(orderSubmission) => create({ orderSubmission })} + onClickCollateral={onClickCollateral || (() => null)} /> ) : ( diff --git a/libs/deal-ticket/src/components/deal-ticket/deal-ticket-limit-amount.tsx b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-limit-amount.tsx index 59280c3d9..54c219401 100644 --- a/libs/deal-ticket/src/components/deal-ticket/deal-ticket-limit-amount.tsx +++ b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-limit-amount.tsx @@ -1,4 +1,4 @@ -import { FormGroup, Input, InputError } from '@vegaprotocol/ui-toolkit'; +import { FormGroup, Input, NotificationError } from '@vegaprotocol/ui-toolkit'; import { t, toDecimal, validateAmount } from '@vegaprotocol/react-helpers'; import type { DealTicketAmountProps } from './deal-ticket-amount'; @@ -20,17 +20,17 @@ export const DealTicketLimitAmount = ({ const renderError = () => { if (sizeError) { return ( - + {sizeError} - + ); } if (priceError) { return ( - + {priceError} - + ); } diff --git a/libs/deal-ticket/src/components/deal-ticket/deal-ticket-market-amount.tsx b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-market-amount.tsx index 0bf9878ad..39018fdfa 100644 --- a/libs/deal-ticket/src/components/deal-ticket/deal-ticket-market-amount.tsx +++ b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-market-amount.tsx @@ -4,7 +4,7 @@ import { toDecimal, validateAmount, } from '@vegaprotocol/react-helpers'; -import { Input, InputError, Tooltip } from '@vegaprotocol/ui-toolkit'; +import { Input, NotificationError, Tooltip } from '@vegaprotocol/ui-toolkit'; import { isMarketInAuction } from '../../utils'; import type { DealTicketAmountProps } from './deal-ticket-amount'; import { getMarketPrice } from '../../utils/get-price'; @@ -77,12 +77,12 @@ export const DealTicketMarketAmount = ({ {sizeError && ( - {sizeError} - + )} ); diff --git a/libs/deal-ticket/src/components/deal-ticket/deal-ticket.spec.tsx b/libs/deal-ticket/src/components/deal-ticket/deal-ticket.spec.tsx index 27b580051..b0fe2483b 100644 --- a/libs/deal-ticket/src/components/deal-ticket/deal-ticket.spec.tsx +++ b/libs/deal-ticket/src/components/deal-ticket/deal-ticket.spec.tsx @@ -1,12 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { VegaWalletContext } from '@vegaprotocol/wallet'; -import { - fireEvent, - render, - screen, - act, - waitFor, -} from '@testing-library/react'; +import { fireEvent, render, screen, act } from '@testing-library/react'; import { generateMarket, generateMarketData } from '../../test-helpers'; import { DealTicket } from './deal-ticket'; import * as Schema from '@vegaprotocol/types'; @@ -15,14 +9,6 @@ import type { MockedResponse } from '@apollo/client/testing'; import { MockedProvider } from '@apollo/client/testing'; import type { ChainIdQuery } from '@vegaprotocol/react-helpers'; import { ChainIdDocument, addDecimal } from '@vegaprotocol/react-helpers'; -import * as utils from '../../utils'; - -let mockHasNoBalance = false; -jest.mock('../../hooks/use-has-no-balance', () => { - return { - useHasNoBalance: () => mockHasNoBalance, - }; -}); const market = generateMarket(); const marketData = generateMarketData(); @@ -148,50 +134,6 @@ describe('DealTicket', () => { ); }); - it('validation should be reset', async () => { - mockHasNoBalance = true; - jest.spyOn(utils, 'validateMarketState').mockReturnValue('Wrong state'); - jest - .spyOn(utils, 'validateMarketTradingMode') - .mockReturnValue('Wrong trading mode'); - const { rerender } = render(generateJsx()); - - await act(async () => { - fireEvent.click(screen.getByTestId('place-order')); - }); - await waitFor(async () => { - expect( - await screen.getByTestId('dealticket-error-message-summary') - ).toHaveTextContent('Wrong state'); - }); - - jest.spyOn(utils, 'validateMarketState').mockReturnValue(true); - await act(async () => { - rerender(generateJsx()); - }); - await act(async () => { - fireEvent.click(screen.getByTestId('place-order')); - }); - await waitFor(async () => { - expect( - await screen.getByTestId('dealticket-error-message-zero-balance') - ).toHaveTextContent('Insufficient balance.'); - }); - - mockHasNoBalance = false; - await act(async () => { - rerender(generateJsx()); - }); - await act(async () => { - fireEvent.click(screen.getByTestId('place-order')); - }); - await waitFor(async () => { - expect( - await screen.getByTestId('dealticket-error-message-summary') - ).toHaveTextContent('Wrong trading mode'); - }); - }); - it('can edit deal ticket', async () => { render(generateJsx()); 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 20f3280cc..8d9891529 100644 --- a/libs/deal-ticket/src/components/deal-ticket/deal-ticket.tsx +++ b/libs/deal-ticket/src/components/deal-ticket/deal-ticket.tsx @@ -10,9 +10,15 @@ import { SideSelector } from './side-selector'; import { TimeInForceSelector } from './time-in-force-selector'; import { TypeSelector } from './type-selector'; import type { OrderSubmissionBody } from '@vegaprotocol/wallet'; +import { useVegaWalletDialogStore } from '@vegaprotocol/wallet'; import { normalizeOrderSubmission } from '@vegaprotocol/wallet'; import { useVegaWallet } from '@vegaprotocol/wallet'; -import { InputError } from '@vegaprotocol/ui-toolkit'; +import { + ExternalLink, + NotificationError, + Intent, + Notification, +} from '@vegaprotocol/ui-toolkit'; import { useOrderMarginValidation } from '../../hooks/use-order-margin-validation'; import { MarginWarning } from '../deal-ticket-validation/margin-warning'; import { @@ -37,6 +43,7 @@ export interface DealTicketProps { market: Market; marketData: MarketData; submit: (order: OrderSubmissionBody['orderSubmission']) => void; + onClickCollateral?: () => void; } export type DealTicketFormFields = OrderSubmissionBody['orderSubmission'] & { @@ -45,7 +52,12 @@ export type DealTicketFormFields = OrderSubmissionBody['orderSubmission'] & { summary: string; }; -export const DealTicket = ({ market, marketData, submit }: DealTicketProps) => { +export const DealTicket = ({ + market, + marketData, + submit, + onClickCollateral, +}: DealTicketProps) => { const { pubKey, isReadOnly } = useVegaWallet(); const { getPersistedOrder, setPersistedOrder } = usePersistedOrderStore( (store) => ({ @@ -87,6 +99,44 @@ export const DealTicket = ({ market, marketData, submit }: DealTicketProps) => { const marketTradingModeError = validateMarketTradingMode( marketData.marketTradingMode ); + + const checkForErrors = useCallback(() => { + if (!pubKey) { + setError('summary', { message: t('No public key selected') }); + return; + } + + if (marketStateError !== true) { + setError('summary', { + message: marketStateError, + type: SummaryValidationType.MarketState, + }); + return; + } + + if (hasNoBalance) { + setError('summary', { + message: SummaryValidationType.NoCollateral, + type: SummaryValidationType.NoCollateral, + }); + return; + } + + if (marketTradingModeError !== true) { + setError('summary', { + message: marketTradingModeError, + type: SummaryValidationType.TradingMode, + }); + return; + } + }, [ + hasNoBalance, + marketStateError, + marketTradingModeError, + pubKey, + setError, + ]); + useEffect(() => { if ( (!hasNoBalance && @@ -98,6 +148,7 @@ export const DealTicket = ({ market, marketData, submit }: DealTicketProps) => { ) { clearErrors('summary'); } + checkForErrors(); }, [ hasNoBalance, marketStateError, @@ -105,39 +156,12 @@ export const DealTicket = ({ market, marketData, submit }: DealTicketProps) => { clearErrors, errors.summary?.message, errors.summary?.type, + checkForErrors, ]); const onSubmit = useCallback( (order: OrderSubmissionBody['orderSubmission']) => { - if (!pubKey) { - setError('summary', { message: t('No public key selected') }); - return; - } - - if (marketStateError !== true) { - setError('summary', { - message: marketStateError, - type: SummaryValidationType.MarketState, - }); - return; - } - - if (hasNoBalance) { - setError('summary', { - message: SummaryValidationType.NoCollateral, - type: SummaryValidationType.NoCollateral, - }); - return; - } - - if (marketTradingModeError !== true) { - setError('summary', { - message: marketTradingModeError, - type: SummaryValidationType.TradingMode, - }); - return; - } - + checkForErrors(); submit( normalizeOrderSubmission( order, @@ -146,16 +170,7 @@ export const DealTicket = ({ market, marketData, submit }: DealTicketProps) => { ) ); }, - [ - submit, - pubKey, - hasNoBalance, - market.positionDecimalPlaces, - market.decimalPlaces, - marketStateError, - marketTradingModeError, - setError, - ] + [checkForErrors, submit, market.decimalPlaces, market.positionDecimalPlaces] ); return ( @@ -233,16 +248,18 @@ export const DealTicket = ({ market, marketData, submit }: DealTicketProps) => { )} /> )} - = 1 || isReadOnly} - variant={order.side === Schema.Side.SIDE_BUY ? 'ternary' : 'secondary'} - /> null)} + /> + = 1 || isReadOnly} + variant={order.side === Schema.Side.SIDE_BUY ? 'ternary' : 'secondary'} /> void; } const SummaryMessage = memo( ({ @@ -271,30 +290,60 @@ const SummaryMessage = memo( marketData, order, isReadOnly, + pubKey, + onClickCollateral, }: SummaryMessageProps) => { // Specific error UI for if balance is so we can // render a deposit dialog const asset = market.tradableInstrument.instrument.product.settlementAsset; + const assetSymbol = asset.symbol; const { balanceError, balance, margin } = useOrderMarginValidation({ market, marketData, order, }); + const openVegaWalletDialog = useVegaWalletDialogStore( + (store) => store.openVegaWalletDialog + ); if (isReadOnly) { return (
- + { 'You need to connect your own wallet to start trading on this market' } - +
); } + if (!pubKey) { + return ( + + You need a{' '} + + Vega wallet + {' '} + with {assetSymbol} to start trading in this market. +

+ } + buttonProps={{ + text: t('Connect wallet'), + action: openVegaWalletDialog, + dataTestId: 'order-connect-wallet', + size: 'md', + }} + /> + ); + } if (errorMessage === SummaryValidationType.NoCollateral) { return ( ); } @@ -304,9 +353,9 @@ const SummaryMessage = memo( if (errorMessage) { return (
- + {errorMessage} - +
); } @@ -326,14 +375,13 @@ const SummaryMessage = memo( ].includes(marketData.marketTradingMode) ) { return ( -
-

- {t('Any orders placed now will not trade until the auction ends')} -

-
+ ); } diff --git a/libs/deal-ticket/src/components/deal-ticket/expiry-selector.tsx b/libs/deal-ticket/src/components/deal-ticket/expiry-selector.tsx index dcebc4325..744aa63c8 100644 --- a/libs/deal-ticket/src/components/deal-ticket/expiry-selector.tsx +++ b/libs/deal-ticket/src/components/deal-ticket/expiry-selector.tsx @@ -1,4 +1,4 @@ -import { FormGroup, Input, InputError } from '@vegaprotocol/ui-toolkit'; +import { FormGroup, Input, NotificationError } from '@vegaprotocol/ui-toolkit'; import { formatForInput } from '@vegaprotocol/react-helpers'; import { t } from '@vegaprotocol/react-helpers'; import type { UseFormRegister } from 'react-hook-form'; @@ -35,9 +35,9 @@ export const ExpirySelector = ({ })} /> {errorMessage && ( - + {errorMessage} - + )} ); diff --git a/libs/deal-ticket/src/components/deal-ticket/time-in-force-selector.tsx b/libs/deal-ticket/src/components/deal-ticket/time-in-force-selector.tsx index b69707aaf..3c43751a2 100644 --- a/libs/deal-ticket/src/components/deal-ticket/time-in-force-selector.tsx +++ b/libs/deal-ticket/src/components/deal-ticket/time-in-force-selector.tsx @@ -1,7 +1,7 @@ import { useEffect, useState } from 'react'; import { FormGroup, - InputError, + NotificationError, Select, Tooltip, } from '@vegaprotocol/ui-toolkit'; @@ -139,9 +139,9 @@ export const TimeInForceSelector = ({ ))} {errorMessage && ( - + {renderError(errorMessage)} - + )} ); diff --git a/libs/deal-ticket/src/components/deal-ticket/type-selector.tsx b/libs/deal-ticket/src/components/deal-ticket/type-selector.tsx index 7e247dd2f..454072b05 100644 --- a/libs/deal-ticket/src/components/deal-ticket/type-selector.tsx +++ b/libs/deal-ticket/src/components/deal-ticket/type-selector.tsx @@ -1,4 +1,8 @@ -import { FormGroup, InputError, Tooltip } from '@vegaprotocol/ui-toolkit'; +import { + FormGroup, + NotificationError, + Tooltip, +} from '@vegaprotocol/ui-toolkit'; import { DataGrid, t } from '@vegaprotocol/react-helpers'; import * as Schema from '@vegaprotocol/types'; import { Toggle } from '@vegaprotocol/ui-toolkit'; @@ -78,9 +82,9 @@ export const TypeSelector = ({ onChange={(e) => onSelect(e.target.value as Schema.OrderType)} /> {errorMessage && ( - + {renderError(errorMessage as MarketModeValidationType)} - + )} ); diff --git a/libs/fills/src/lib/fills-table.spec.tsx b/libs/fills/src/lib/fills-table.spec.tsx index 98d533112..756dc3206 100644 --- a/libs/fills/src/lib/fills-table.spec.tsx +++ b/libs/fills/src/lib/fills-table.spec.tsx @@ -103,7 +103,7 @@ describe('FillsTable', () => { }); const amountCell = cells.find((c) => c.getAttribute('col-id') === 'size'); - expect(amountCell).toHaveClass('text-vega-green'); + expect(amountCell).toHaveClass('text-vega-green-550'); }); it('should format cells correctly for seller fill', async () => { diff --git a/libs/market-depth/src/lib/index.ts b/libs/market-depth/src/lib/index.ts index 7815b351f..a06577b78 100644 --- a/libs/market-depth/src/lib/index.ts +++ b/libs/market-depth/src/lib/index.ts @@ -1,6 +1,6 @@ export * from './__generated__/MarketDepth'; export * from './depth-chart'; -export { marketDepthProvider } from './market-depth-provider'; +export * from './market-depth-provider'; export * from './orderbook-container'; export * from './orderbook-data'; export * from './orderbook-manager'; diff --git a/libs/market-depth/src/lib/orderbook.tsx b/libs/market-depth/src/lib/orderbook.tsx index 855b53212..85208a1b5 100644 --- a/libs/market-depth/src/lib/orderbook.tsx +++ b/libs/market-depth/src/lib/orderbook.tsx @@ -334,7 +334,7 @@ export const Orderbook = ({ BigInt(maxPriceLevel) - BigInt(offsetTop) * BigInt(resolution) ).toString() - : rows?.[Math.min(offsetTop, rows.length - 1)].price.toString(); + : rows?.[Math.min(offsetTop, rows.length - 1)]?.price?.toString(); if (lockOnMidPrice) { setLockOnMidPrice(false); } diff --git a/libs/positions/src/lib/positions-table.spec.tsx b/libs/positions/src/lib/positions-table.spec.tsx index a935141a0..f4f093bc6 100644 --- a/libs/positions/src/lib/positions-table.spec.tsx +++ b/libs/positions/src/lib/positions-table.spec.tsx @@ -92,7 +92,7 @@ it('add color and sign to amount, displays positive notional value', async () => }); let cells = screen.getAllByRole('gridcell'); - expect(cells[2].classList.contains('text-vega-green')).toBeTruthy(); + expect(cells[2].classList.contains('text-vega-green-550')).toBeTruthy(); expect(cells[2].classList.contains('text-vega-pink')).toBeFalsy(); expect(cells[2].textContent).toEqual('+100'); expect(cells[1].textContent).toEqual('1,230.0'); @@ -105,7 +105,7 @@ it('add color and sign to amount, displays positive notional value', async () => ); }); cells = screen.getAllByRole('gridcell'); - expect(cells[2].classList.contains('text-vega-green')).toBeFalsy(); + expect(cells[2].classList.contains('text-vega-green-550')).toBeFalsy(); expect(cells[2].classList.contains('text-vega-pink')).toBeTruthy(); expect(cells[2].textContent?.startsWith('-100')).toBeTruthy(); expect(cells[1].textContent).toEqual('1,230.0'); diff --git a/libs/react-helpers/src/lib/grid/cell-class-rules.ts b/libs/react-helpers/src/lib/grid/cell-class-rules.ts index fe28f3e42..b05a1de66 100644 --- a/libs/react-helpers/src/lib/grid/cell-class-rules.ts +++ b/libs/react-helpers/src/lib/grid/cell-class-rules.ts @@ -1,4 +1,4 @@ -export const positiveClassNames = 'text-vega-green dark:text-vega-green'; +export const positiveClassNames = 'text-vega-green-550 dark:text-vega-green'; export const negativeClassNames = 'text-vega-pink dark:text-vega-pink'; const isPositive = ({ value }: { value: string | bigint | number }) => diff --git a/libs/trades/src/lib/trades-table.tsx b/libs/trades/src/lib/trades-table.tsx index 97a61ad1a..2a40ee8e9 100644 --- a/libs/trades/src/lib/trades-table.tsx +++ b/libs/trades/src/lib/trades-table.tsx @@ -116,7 +116,10 @@ export const TradesTable = forwardRef((props, ref) => { if (!data?.market) { return null; } - return addDecimal(value, data.market.positionDecimalPlaces); + return addDecimalsFormatNumber( + value, + data.market.positionDecimalPlaces + ); }} /> { children?: React.ReactNode; @@ -7,6 +9,40 @@ interface InputErrorProps extends HTMLAttributes { forInput?: string; } +interface NotificationErrorProps extends HTMLAttributes { + children?: React.ReactNode; + intent?: Intent | 'danger' | 'warning'; + forInput?: string; + testId?: string; +} + +const getIntent = (intent: Intent | 'danger' | 'warning') => { + switch (intent) { + case 'danger': + return Intent.Danger; + case 'warning': + return Intent.Warning; + default: + return intent; + } +}; + +export const NotificationError = ({ + intent = Intent.Danger, + children, + forInput, + testId, +}: NotificationErrorProps) => { + return ( + {children}} + aria-describedby={forInput} + /> + ); +}; + export const InputError = ({ intent = 'danger', children, diff --git a/libs/ui-toolkit/src/components/notification/notification.tsx b/libs/ui-toolkit/src/components/notification/notification.tsx index 397b35482..5a6b0750c 100644 --- a/libs/ui-toolkit/src/components/notification/notification.tsx +++ b/libs/ui-toolkit/src/components/notification/notification.tsx @@ -4,13 +4,20 @@ import classNames from 'classnames'; import type { ReactNode } from 'react'; import { Intent } from '../../utils/intent'; import { Icon } from '../icon'; +import type { ButtonSize } from '../button'; import { Button } from '../button'; type NotificationProps = { intent: Intent; message: ReactNode | string; title?: string; - buttonProps?: { text: string; action: () => void; className?: string }; + buttonProps?: { + text: string; + action: () => void; + className?: string; + dataTestId?: string; + size?: ButtonSize; + }; testId?: string; className?: string; }; @@ -41,11 +48,20 @@ export const Notification = ({ { 'border-gray-700 dark:border-gray-300': intent === Intent.None, 'border-vega-blue': intent === Intent.Primary, - 'border-vega-green dark:border-vega-green': intent === Intent.Success, - 'border-yellow-500': intent === Intent.Warning, + 'border-vega-green-550 dark:border-vega-green': + intent === Intent.Success, + 'border-vega-orange': intent === Intent.Warning, 'border-vega-pink': intent === Intent.Danger, }, - 'border rounded text-xs p-4 flex items-start gap-2.5 bg-neutral-100 dark:bg-neutral-900', + { + 'bg-vega-light-100 dark:bg-vega-dark-100 ': intent === Intent.None, + 'bg-vega-blue-300 dark:bg-vega-blue-700': intent === Intent.Primary, + 'bg-vega-green-300 dark:bg-vega-green-700': intent === Intent.Success, + 'bg-vega-orange-300 dark:bg-vega-orange-650': + intent === Intent.Warning, + 'bg-vega-pink-300 dark:bg-vega-pink-650': intent === Intent.Danger, + }, + 'border rounded p-2 flex items-start gap-2.5 my-4', className )} > @@ -55,7 +71,7 @@ export const Notification = ({ 'text-gray-700 dark:text-gray-300': intent === Intent.None, 'text-vega-blue': intent === Intent.Primary, 'text-vega-green dark:text-vega-green': intent === Intent.Success, - 'text-yellow-600 dark:text-yellow-500': intent === Intent.Warning, + 'text-yellow-600 dark:text-yellow': intent === Intent.Warning, 'text-vega-pink': intent === Intent.Danger, }, 'flex items-start mt-1' @@ -63,18 +79,19 @@ export const Notification = ({ > -
+
{title && ( -
+
{title}
)} -
{message}
+
{message}
{buttonProps && ( diff --git a/libs/ui-toolkit/src/utils/intent.tsx b/libs/ui-toolkit/src/utils/intent.tsx index dfcdcc788..8c40c1d08 100644 --- a/libs/ui-toolkit/src/utils/intent.tsx +++ b/libs/ui-toolkit/src/utils/intent.tsx @@ -23,7 +23,8 @@ export const getIntentBackground = (intent?: Intent) => { 'bg-vega-pink dark:bg-vega-yellow': intent === Intent.Primary, 'bg-danger': intent === Intent.Danger, 'bg-warning': intent === Intent.Warning, - 'bg-success': intent === Intent.Success, + // contrast issues with light mode + 'bg-vega-green-550 dark:bg-vega-green': intent === Intent.Success, }; };