feat(trading): deposit flow deal ticket (#2874)
This commit is contained in:
parent
7017e24adf
commit
55d6dd4dce
@ -17,6 +17,7 @@ const itemValue = 'item-value';
|
|||||||
|
|
||||||
describe('Market proposal notification', { tags: '@smoke' }, () => {
|
describe('Market proposal notification', { tags: '@smoke' }, () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
|
cy.setVegaWallet();
|
||||||
cy.mockTradingPage(
|
cy.mockTradingPage(
|
||||||
Schema.MarketState.STATE_ACTIVE,
|
Schema.MarketState.STATE_ACTIVE,
|
||||||
Schema.MarketTradingMode.TRADING_MODE_MONITORING_AUCTION,
|
Schema.MarketTradingMode.TRADING_MODE_MONITORING_AUCTION,
|
||||||
@ -239,7 +240,6 @@ describe('market states not accepting orders', { tags: '@smoke' }, function () {
|
|||||||
cy.visit('/#/markets/market-0');
|
cy.visit('/#/markets/market-0');
|
||||||
});
|
});
|
||||||
it('must display that market is not accepting orders', function () {
|
it('must display that market is not accepting orders', function () {
|
||||||
cy.getByTestId('place-order').click();
|
|
||||||
cy.getByTestId('dealticket-error-message-summary').should(
|
cy.getByTestId('dealticket-error-message-summary').should(
|
||||||
'have.text',
|
'have.text',
|
||||||
`This market is ${marketState
|
`This market is ${marketState
|
||||||
|
@ -13,7 +13,6 @@ const toggleShort = 'order-side-SIDE_SELL';
|
|||||||
const toggleLong = 'order-side-SIDE_BUY';
|
const toggleLong = 'order-side-SIDE_BUY';
|
||||||
const toggleLimit = 'order-type-TYPE_LIMIT';
|
const toggleLimit = 'order-type-TYPE_LIMIT';
|
||||||
const toggleMarket = 'order-type-TYPE_MARKET';
|
const toggleMarket = 'order-type-TYPE_MARKET';
|
||||||
const errorMessage = 'dealticket-error-message';
|
|
||||||
|
|
||||||
const TIFlist = Object.values(Schema.OrderTimeInForce).map((value) => {
|
const TIFlist = Object.values(Schema.OrderTimeInForce).map((value) => {
|
||||||
return {
|
return {
|
||||||
@ -363,11 +362,11 @@ describe('deal ticket validation', { tags: '@smoke' }, () => {
|
|||||||
cy.wait('@Markets');
|
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('connect-vega-wallet'); // Not connected
|
||||||
cy.getByTestId('order-connect-wallet').should('exist');
|
cy.getByTestId('order-connect-wallet').should('exist');
|
||||||
cy.getByTestId(placeOrderBtn).should('not.exist');
|
cy.getByTestId(placeOrderBtn).should('exist');
|
||||||
cy.getByTestId(errorMessage).should('not.exist');
|
cy.getByTestId('deal-ticket-connect-wallet').should('exist');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('must be able to select order direction - long/short', function () {
|
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', () => {
|
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');
|
cy.getByTestId('place-order').should('be.disabled');
|
||||||
//7002-SORD-003
|
//7002-SORD-003
|
||||||
cy.getByTestId('dealticket-error-message-zero-balance').should(
|
cy.getByTestId('dealticket-error-message-zero-balance').should(
|
||||||
'have.text',
|
'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');
|
cy.getByTestId('deal-ticket-deposit-dialog-button').should('exist');
|
||||||
});
|
});
|
||||||
@ -708,7 +707,7 @@ describe('account validation', { tags: '@regression' }, () => {
|
|||||||
);
|
);
|
||||||
cy.getByTestId('dealticket-warning-margin').should(
|
cy.getByTestId('dealticket-warning-margin').should(
|
||||||
'contain.text',
|
'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('deal-ticket-deposit-dialog-button').click();
|
||||||
cy.getByTestId('dialog-content')
|
cy.getByTestId('dialog-content')
|
||||||
|
@ -98,8 +98,14 @@ export const MarketPage = () => {
|
|||||||
if (w > 960) {
|
if (w > 960) {
|
||||||
return <TradeGrid market={data} onSelect={onSelect} />;
|
return <TradeGrid market={data} onSelect={onSelect} />;
|
||||||
}
|
}
|
||||||
return <TradePanels market={data} onSelect={onSelect} />;
|
return (
|
||||||
}, [w, data, onSelect]);
|
<TradePanels
|
||||||
|
market={data}
|
||||||
|
onSelect={onSelect}
|
||||||
|
onClickCollateral={() => navigate('/portfolio')}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}, [w, data, onSelect, navigate]);
|
||||||
if (!data && marketId) {
|
if (!data && marketId) {
|
||||||
return (
|
return (
|
||||||
<Splash>
|
<Splash>
|
||||||
|
@ -111,7 +111,10 @@ const MainGrid = ({
|
|||||||
<TradeGridChild>
|
<TradeGridChild>
|
||||||
<Tabs>
|
<Tabs>
|
||||||
<Tab id="ticket" name={t('Ticket')}>
|
<Tab id="ticket" name={t('Ticket')}>
|
||||||
<TradingViews.Ticket marketId={marketId} />
|
<TradingViews.Ticket
|
||||||
|
marketId={marketId}
|
||||||
|
onClickCollateral={() => navigate('/portfolio')}
|
||||||
|
/>
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab id="info" name={t('Info')}>
|
<Tab id="info" name={t('Info')}>
|
||||||
<TradingViews.Info
|
<TradingViews.Info
|
||||||
@ -210,15 +213,21 @@ interface TradePanelsProps {
|
|||||||
market: Market | null;
|
market: Market | null;
|
||||||
onSelect: (marketId: string) => void;
|
onSelect: (marketId: string) => void;
|
||||||
onMarketClick?: (marketId: string) => void;
|
onMarketClick?: (marketId: string) => void;
|
||||||
|
onClickCollateral: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TradePanels = ({ market, onSelect }: TradePanelsProps) => {
|
export const TradePanels = ({
|
||||||
|
market,
|
||||||
|
onSelect,
|
||||||
|
onClickCollateral,
|
||||||
|
}: TradePanelsProps) => {
|
||||||
const [view, setView] = useState<TradingView>('Candles');
|
const [view, setView] = useState<TradingView>('Candles');
|
||||||
const renderView = () => {
|
const renderView = () => {
|
||||||
const Component = memo<{
|
const Component = memo<{
|
||||||
marketId: string;
|
marketId: string;
|
||||||
onSelect: (marketId: string) => void;
|
onSelect: (marketId: string) => void;
|
||||||
onMarketClick?: (marketId: string) => void;
|
onMarketClick?: (marketId: string) => void;
|
||||||
|
onClickCollateral: () => void;
|
||||||
}>(TradingViews[view]);
|
}>(TradingViews[view]);
|
||||||
|
|
||||||
if (!Component) {
|
if (!Component) {
|
||||||
@ -227,7 +236,13 @@ export const TradePanels = ({ market, onSelect }: TradePanelsProps) => {
|
|||||||
|
|
||||||
if (!market) return <Splash>{NO_MARKET}</Splash>;
|
if (!market) return <Splash>{NO_MARKET}</Splash>;
|
||||||
|
|
||||||
return <Component marketId={market?.id} onSelect={onSelect} />;
|
return (
|
||||||
|
<Component
|
||||||
|
marketId={market?.id}
|
||||||
|
onSelect={onSelect}
|
||||||
|
onClickCollateral={onClickCollateral}
|
||||||
|
/>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -42,7 +42,7 @@ describe('Footer', () => {
|
|||||||
|
|
||||||
describe('NodeHealth', () => {
|
describe('NodeHealth', () => {
|
||||||
const cases = [
|
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: 5, classname: 'bg-warning', text: '5 Blocks behind' },
|
||||||
{ diff: -1, classname: 'bg-danger', text: 'Non operational' },
|
{ diff: -1, classname: 'bg-danger', text: 'Non operational' },
|
||||||
];
|
];
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { formatNumber, t } from '@vegaprotocol/react-helpers';
|
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';
|
import { DepositDialog, useDepositDialog } from '@vegaprotocol/deposits';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@ -16,27 +16,23 @@ export const MarginWarning = ({ margin, balance, asset }: Props) => {
|
|||||||
const openDepositDialog = useDepositDialog((state) => state.open);
|
const openDepositDialog = useDepositDialog((state) => state.open);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
<Notification
|
||||||
className="text-xs text-warning mb-4"
|
intent={Intent.Warning}
|
||||||
data-testid="dealticket-warning-margin"
|
testId="dealticket-warning-margin"
|
||||||
>
|
message={`You may not have enough margin available to open this position. ${formatNumber(
|
||||||
<p className="mb-2">
|
margin,
|
||||||
{t('You may not have enough margin available to open this position.')}{' '}
|
asset.decimals
|
||||||
<ButtonLink
|
)} ${asset.symbol} ${t(
|
||||||
data-testid="deal-ticket-deposit-dialog-button"
|
'is currently required. You have only'
|
||||||
onClick={() => openDepositDialog(asset.id)}
|
)} ${formatNumber(balance, asset.decimals)} ${asset.symbol} ${t(
|
||||||
>
|
'available.'
|
||||||
{t(`Deposit ${asset.symbol}`)}
|
)}`}
|
||||||
</ButtonLink>
|
buttonProps={{
|
||||||
</p>
|
text: t(`Deposit ${asset.symbol}`),
|
||||||
<p>
|
action: () => openDepositDialog(asset.id),
|
||||||
{`${formatNumber(margin, asset.decimals)} ${asset.symbol} ${t(
|
dataTestId: 'deal-ticket-deposit-dialog-button',
|
||||||
'currently required'
|
}}
|
||||||
)}, ${formatNumber(balance, asset.decimals)} ${asset.symbol} ${t(
|
/>
|
||||||
'available'
|
|
||||||
)}`}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<DepositDialog />
|
<DepositDialog />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -1,26 +1,36 @@
|
|||||||
import { t } from '@vegaprotocol/react-helpers';
|
import { Intent, Notification, Link } from '@vegaprotocol/ui-toolkit';
|
||||||
import { ButtonLink, InputError } from '@vegaprotocol/ui-toolkit';
|
|
||||||
import { useDepositDialog } from '@vegaprotocol/deposits';
|
import { useDepositDialog } from '@vegaprotocol/deposits';
|
||||||
|
import { t } from '@vegaprotocol/react-helpers';
|
||||||
|
|
||||||
interface ZeroBalanceErrorProps {
|
interface ZeroBalanceErrorProps {
|
||||||
asset: {
|
asset: {
|
||||||
id: string;
|
id: string;
|
||||||
symbol: string;
|
symbol: string;
|
||||||
};
|
};
|
||||||
|
onClickCollateral: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ZeroBalanceError = ({ asset }: ZeroBalanceErrorProps) => {
|
export const ZeroBalanceError = ({
|
||||||
|
asset,
|
||||||
|
onClickCollateral,
|
||||||
|
}: ZeroBalanceErrorProps) => {
|
||||||
const openDepositDialog = useDepositDialog((state) => state.open);
|
const openDepositDialog = useDepositDialog((state) => state.open);
|
||||||
return (
|
return (
|
||||||
<InputError data-testid="dealticket-error-message-zero-balance">
|
<Notification
|
||||||
<p className="mb-2">
|
intent={Intent.Warning}
|
||||||
{t('Insufficient balance. ')}
|
testId="dealticket-error-message-zero-balance"
|
||||||
<ButtonLink
|
message={
|
||||||
data-testid="deal-ticket-deposit-dialog-button"
|
<>
|
||||||
onClick={() => openDepositDialog(asset.id)}
|
You need {asset.symbol} in your wallet to trade in this market. See
|
||||||
>
|
all your <Link onClick={onClickCollateral}>collateral</Link>.
|
||||||
{t(`Deposit ${asset.symbol}`)}
|
</>
|
||||||
</ButtonLink>
|
}
|
||||||
</p>
|
buttonProps={{
|
||||||
</InputError>
|
text: t(`Make a deposit`),
|
||||||
|
action: () => openDepositDialog(asset.id),
|
||||||
|
dataTestId: 'deal-ticket-deposit-dialog-button',
|
||||||
|
size: 'md',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { t } from '@vegaprotocol/react-helpers';
|
import { t } from '@vegaprotocol/react-helpers';
|
||||||
import type { ButtonVariant } from '@vegaprotocol/ui-toolkit';
|
import type { ButtonVariant } from '@vegaprotocol/ui-toolkit';
|
||||||
import { Button } from '@vegaprotocol/ui-toolkit';
|
import { Button } from '@vegaprotocol/ui-toolkit';
|
||||||
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
|
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
@ -9,32 +9,19 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const DealTicketButton = ({ disabled, variant }: Props) => {
|
export const DealTicketButton = ({ disabled, variant }: Props) => {
|
||||||
const { pubKey } = useVegaWallet();
|
const { pubKey, isReadOnly } = useVegaWallet();
|
||||||
const openVegaWalletDialog = useVegaWalletDialogStore(
|
const isDisabled = !pubKey || isReadOnly || disabled;
|
||||||
(store) => store.openVegaWalletDialog
|
return (
|
||||||
);
|
|
||||||
return pubKey ? (
|
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<Button
|
<Button
|
||||||
variant={variant}
|
variant={variant}
|
||||||
fill
|
fill
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={disabled}
|
disabled={isDisabled}
|
||||||
data-testid="place-order"
|
data-testid="place-order"
|
||||||
>
|
>
|
||||||
{t('Place order')}
|
{t('Place order')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
|
||||||
<Button
|
|
||||||
variant="default"
|
|
||||||
fill
|
|
||||||
type="button"
|
|
||||||
data-testid="order-connect-wallet"
|
|
||||||
onClick={openVegaWalletDialog}
|
|
||||||
className="mb-6"
|
|
||||||
>
|
|
||||||
{t('Connect wallet')}
|
|
||||||
</Button>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -8,9 +8,13 @@ import { DealTicket } from './deal-ticket';
|
|||||||
|
|
||||||
export interface DealTicketContainerProps {
|
export interface DealTicketContainerProps {
|
||||||
marketId: string;
|
marketId: string;
|
||||||
|
onClickCollateral?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DealTicketContainer = ({ marketId }: DealTicketContainerProps) => {
|
export const DealTicketContainer = ({
|
||||||
|
marketId,
|
||||||
|
onClickCollateral,
|
||||||
|
}: DealTicketContainerProps) => {
|
||||||
const {
|
const {
|
||||||
data: market,
|
data: market,
|
||||||
error: marketError,
|
error: marketError,
|
||||||
@ -41,6 +45,7 @@ export const DealTicketContainer = ({ marketId }: DealTicketContainerProps) => {
|
|||||||
market={market}
|
market={market}
|
||||||
marketData={marketData}
|
marketData={marketData}
|
||||||
submit={(orderSubmission) => create({ orderSubmission })}
|
submit={(orderSubmission) => create({ orderSubmission })}
|
||||||
|
onClickCollateral={onClickCollateral || (() => null)}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Splash>
|
<Splash>
|
||||||
|
@ -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 { t, toDecimal, validateAmount } from '@vegaprotocol/react-helpers';
|
||||||
import type { DealTicketAmountProps } from './deal-ticket-amount';
|
import type { DealTicketAmountProps } from './deal-ticket-amount';
|
||||||
|
|
||||||
@ -20,17 +20,17 @@ export const DealTicketLimitAmount = ({
|
|||||||
const renderError = () => {
|
const renderError = () => {
|
||||||
if (sizeError) {
|
if (sizeError) {
|
||||||
return (
|
return (
|
||||||
<InputError data-testid="dealticket-error-message-size-limit">
|
<NotificationError testId="dealticket-error-message-size-limit">
|
||||||
{sizeError}
|
{sizeError}
|
||||||
</InputError>
|
</NotificationError>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (priceError) {
|
if (priceError) {
|
||||||
return (
|
return (
|
||||||
<InputError data-testid="dealticket-error-message-price-limit">
|
<NotificationError testId="dealticket-error-message-price-limit">
|
||||||
{priceError}
|
{priceError}
|
||||||
</InputError>
|
</NotificationError>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ import {
|
|||||||
toDecimal,
|
toDecimal,
|
||||||
validateAmount,
|
validateAmount,
|
||||||
} from '@vegaprotocol/react-helpers';
|
} 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 { isMarketInAuction } from '../../utils';
|
||||||
import type { DealTicketAmountProps } from './deal-ticket-amount';
|
import type { DealTicketAmountProps } from './deal-ticket-amount';
|
||||||
import { getMarketPrice } from '../../utils/get-price';
|
import { getMarketPrice } from '../../utils/get-price';
|
||||||
@ -77,12 +77,12 @@ export const DealTicketMarketAmount = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{sizeError && (
|
{sizeError && (
|
||||||
<InputError
|
<NotificationError
|
||||||
intent="danger"
|
intent="danger"
|
||||||
data-testid="dealticket-error-message-size-market"
|
testId="dealticket-error-message-size-market"
|
||||||
>
|
>
|
||||||
{sizeError}
|
{sizeError}
|
||||||
</InputError>
|
</NotificationError>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1,12 +1,6 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||||
import {
|
import { fireEvent, render, screen, act } from '@testing-library/react';
|
||||||
fireEvent,
|
|
||||||
render,
|
|
||||||
screen,
|
|
||||||
act,
|
|
||||||
waitFor,
|
|
||||||
} from '@testing-library/react';
|
|
||||||
import { generateMarket, generateMarketData } from '../../test-helpers';
|
import { generateMarket, generateMarketData } from '../../test-helpers';
|
||||||
import { DealTicket } from './deal-ticket';
|
import { DealTicket } from './deal-ticket';
|
||||||
import * as Schema from '@vegaprotocol/types';
|
import * as Schema from '@vegaprotocol/types';
|
||||||
@ -15,14 +9,6 @@ import type { MockedResponse } from '@apollo/client/testing';
|
|||||||
import { MockedProvider } from '@apollo/client/testing';
|
import { MockedProvider } from '@apollo/client/testing';
|
||||||
import type { ChainIdQuery } from '@vegaprotocol/react-helpers';
|
import type { ChainIdQuery } from '@vegaprotocol/react-helpers';
|
||||||
import { ChainIdDocument, addDecimal } 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 market = generateMarket();
|
||||||
const marketData = generateMarketData();
|
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 () => {
|
it('can edit deal ticket', async () => {
|
||||||
render(generateJsx());
|
render(generateJsx());
|
||||||
|
|
||||||
|
@ -10,9 +10,15 @@ import { SideSelector } from './side-selector';
|
|||||||
import { TimeInForceSelector } from './time-in-force-selector';
|
import { TimeInForceSelector } from './time-in-force-selector';
|
||||||
import { TypeSelector } from './type-selector';
|
import { TypeSelector } from './type-selector';
|
||||||
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
|
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
|
||||||
|
import { useVegaWalletDialogStore } from '@vegaprotocol/wallet';
|
||||||
import { normalizeOrderSubmission } from '@vegaprotocol/wallet';
|
import { normalizeOrderSubmission } from '@vegaprotocol/wallet';
|
||||||
import { useVegaWallet } 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 { useOrderMarginValidation } from '../../hooks/use-order-margin-validation';
|
||||||
import { MarginWarning } from '../deal-ticket-validation/margin-warning';
|
import { MarginWarning } from '../deal-ticket-validation/margin-warning';
|
||||||
import {
|
import {
|
||||||
@ -37,6 +43,7 @@ export interface DealTicketProps {
|
|||||||
market: Market;
|
market: Market;
|
||||||
marketData: MarketData;
|
marketData: MarketData;
|
||||||
submit: (order: OrderSubmissionBody['orderSubmission']) => void;
|
submit: (order: OrderSubmissionBody['orderSubmission']) => void;
|
||||||
|
onClickCollateral?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DealTicketFormFields = OrderSubmissionBody['orderSubmission'] & {
|
export type DealTicketFormFields = OrderSubmissionBody['orderSubmission'] & {
|
||||||
@ -45,7 +52,12 @@ export type DealTicketFormFields = OrderSubmissionBody['orderSubmission'] & {
|
|||||||
summary: string;
|
summary: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DealTicket = ({ market, marketData, submit }: DealTicketProps) => {
|
export const DealTicket = ({
|
||||||
|
market,
|
||||||
|
marketData,
|
||||||
|
submit,
|
||||||
|
onClickCollateral,
|
||||||
|
}: DealTicketProps) => {
|
||||||
const { pubKey, isReadOnly } = useVegaWallet();
|
const { pubKey, isReadOnly } = useVegaWallet();
|
||||||
const { getPersistedOrder, setPersistedOrder } = usePersistedOrderStore(
|
const { getPersistedOrder, setPersistedOrder } = usePersistedOrderStore(
|
||||||
(store) => ({
|
(store) => ({
|
||||||
@ -87,6 +99,44 @@ export const DealTicket = ({ market, marketData, submit }: DealTicketProps) => {
|
|||||||
const marketTradingModeError = validateMarketTradingMode(
|
const marketTradingModeError = validateMarketTradingMode(
|
||||||
marketData.marketTradingMode
|
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(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
(!hasNoBalance &&
|
(!hasNoBalance &&
|
||||||
@ -98,6 +148,7 @@ export const DealTicket = ({ market, marketData, submit }: DealTicketProps) => {
|
|||||||
) {
|
) {
|
||||||
clearErrors('summary');
|
clearErrors('summary');
|
||||||
}
|
}
|
||||||
|
checkForErrors();
|
||||||
}, [
|
}, [
|
||||||
hasNoBalance,
|
hasNoBalance,
|
||||||
marketStateError,
|
marketStateError,
|
||||||
@ -105,39 +156,12 @@ export const DealTicket = ({ market, marketData, submit }: DealTicketProps) => {
|
|||||||
clearErrors,
|
clearErrors,
|
||||||
errors.summary?.message,
|
errors.summary?.message,
|
||||||
errors.summary?.type,
|
errors.summary?.type,
|
||||||
|
checkForErrors,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const onSubmit = useCallback(
|
const onSubmit = useCallback(
|
||||||
(order: OrderSubmissionBody['orderSubmission']) => {
|
(order: OrderSubmissionBody['orderSubmission']) => {
|
||||||
if (!pubKey) {
|
checkForErrors();
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
submit(
|
submit(
|
||||||
normalizeOrderSubmission(
|
normalizeOrderSubmission(
|
||||||
order,
|
order,
|
||||||
@ -146,16 +170,7 @@ export const DealTicket = ({ market, marketData, submit }: DealTicketProps) => {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[
|
[checkForErrors, submit, market.decimalPlaces, market.positionDecimalPlaces]
|
||||||
submit,
|
|
||||||
pubKey,
|
|
||||||
hasNoBalance,
|
|
||||||
market.positionDecimalPlaces,
|
|
||||||
market.decimalPlaces,
|
|
||||||
marketStateError,
|
|
||||||
marketTradingModeError,
|
|
||||||
setError,
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -233,16 +248,18 @@ export const DealTicket = ({ market, marketData, submit }: DealTicketProps) => {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<DealTicketButton
|
|
||||||
disabled={Object.keys(errors).length >= 1 || isReadOnly}
|
|
||||||
variant={order.side === Schema.Side.SIDE_BUY ? 'ternary' : 'secondary'}
|
|
||||||
/>
|
|
||||||
<SummaryMessage
|
<SummaryMessage
|
||||||
errorMessage={errors.summary?.message}
|
errorMessage={errors.summary?.message}
|
||||||
market={market}
|
market={market}
|
||||||
marketData={marketData}
|
marketData={marketData}
|
||||||
order={order}
|
order={order}
|
||||||
isReadOnly={isReadOnly}
|
isReadOnly={isReadOnly}
|
||||||
|
pubKey={pubKey}
|
||||||
|
onClickCollateral={onClickCollateral || (() => null)}
|
||||||
|
/>
|
||||||
|
<DealTicketButton
|
||||||
|
disabled={Object.keys(errors).length >= 1 || isReadOnly}
|
||||||
|
variant={order.side === Schema.Side.SIDE_BUY ? 'ternary' : 'secondary'}
|
||||||
/>
|
/>
|
||||||
<DealTicketFeeDetails
|
<DealTicketFeeDetails
|
||||||
order={order}
|
order={order}
|
||||||
@ -263,6 +280,8 @@ interface SummaryMessageProps {
|
|||||||
marketData: MarketData;
|
marketData: MarketData;
|
||||||
order: OrderSubmissionBody['orderSubmission'];
|
order: OrderSubmissionBody['orderSubmission'];
|
||||||
isReadOnly: boolean;
|
isReadOnly: boolean;
|
||||||
|
pubKey: string | null;
|
||||||
|
onClickCollateral: () => void;
|
||||||
}
|
}
|
||||||
const SummaryMessage = memo(
|
const SummaryMessage = memo(
|
||||||
({
|
({
|
||||||
@ -271,30 +290,60 @@ const SummaryMessage = memo(
|
|||||||
marketData,
|
marketData,
|
||||||
order,
|
order,
|
||||||
isReadOnly,
|
isReadOnly,
|
||||||
|
pubKey,
|
||||||
|
onClickCollateral,
|
||||||
}: SummaryMessageProps) => {
|
}: SummaryMessageProps) => {
|
||||||
// Specific error UI for if balance is so we can
|
// Specific error UI for if balance is so we can
|
||||||
// render a deposit dialog
|
// render a deposit dialog
|
||||||
const asset = market.tradableInstrument.instrument.product.settlementAsset;
|
const asset = market.tradableInstrument.instrument.product.settlementAsset;
|
||||||
|
const assetSymbol = asset.symbol;
|
||||||
const { balanceError, balance, margin } = useOrderMarginValidation({
|
const { balanceError, balance, margin } = useOrderMarginValidation({
|
||||||
market,
|
market,
|
||||||
marketData,
|
marketData,
|
||||||
order,
|
order,
|
||||||
});
|
});
|
||||||
|
const openVegaWalletDialog = useVegaWalletDialogStore(
|
||||||
|
(store) => store.openVegaWalletDialog
|
||||||
|
);
|
||||||
if (isReadOnly) {
|
if (isReadOnly) {
|
||||||
return (
|
return (
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<InputError data-testid="dealticket-error-message-summary">
|
<NotificationError testId="dealticket-error-message-summary">
|
||||||
{
|
{
|
||||||
'You need to connect your own wallet to start trading on this market'
|
'You need to connect your own wallet to start trading on this market'
|
||||||
}
|
}
|
||||||
</InputError>
|
</NotificationError>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (!pubKey) {
|
||||||
|
return (
|
||||||
|
<Notification
|
||||||
|
testId={'deal-ticket-connect-wallet'}
|
||||||
|
intent={Intent.Warning}
|
||||||
|
message={
|
||||||
|
<p className="text-sm pb-2">
|
||||||
|
You need a{' '}
|
||||||
|
<ExternalLink href="https://vega.xyz/wallet">
|
||||||
|
Vega wallet
|
||||||
|
</ExternalLink>{' '}
|
||||||
|
with {assetSymbol} to start trading in this market.
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
buttonProps={{
|
||||||
|
text: t('Connect wallet'),
|
||||||
|
action: openVegaWalletDialog,
|
||||||
|
dataTestId: 'order-connect-wallet',
|
||||||
|
size: 'md',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
if (errorMessage === SummaryValidationType.NoCollateral) {
|
if (errorMessage === SummaryValidationType.NoCollateral) {
|
||||||
return (
|
return (
|
||||||
<ZeroBalanceError
|
<ZeroBalanceError
|
||||||
asset={market.tradableInstrument.instrument.product.settlementAsset}
|
asset={market.tradableInstrument.instrument.product.settlementAsset}
|
||||||
|
onClickCollateral={onClickCollateral}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -304,9 +353,9 @@ const SummaryMessage = memo(
|
|||||||
if (errorMessage) {
|
if (errorMessage) {
|
||||||
return (
|
return (
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<InputError data-testid="dealticket-error-message-summary">
|
<NotificationError testId="dealticket-error-message-summary">
|
||||||
{errorMessage}
|
{errorMessage}
|
||||||
</InputError>
|
</NotificationError>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -326,14 +375,13 @@ const SummaryMessage = memo(
|
|||||||
].includes(marketData.marketTradingMode)
|
].includes(marketData.marketTradingMode)
|
||||||
) {
|
) {
|
||||||
return (
|
return (
|
||||||
<div
|
<Notification
|
||||||
className="text-sm text-warning mb-4"
|
intent={Intent.Warning}
|
||||||
data-testid="dealticket-warning-auction"
|
testId={'dealticket-warning-auction'}
|
||||||
>
|
message={t(
|
||||||
<p>
|
'Any orders placed now will not trade until the auction ends'
|
||||||
{t('Any orders placed now will not trade until the auction ends')}
|
)}
|
||||||
</p>
|
/>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 { formatForInput } from '@vegaprotocol/react-helpers';
|
||||||
import { t } from '@vegaprotocol/react-helpers';
|
import { t } from '@vegaprotocol/react-helpers';
|
||||||
import type { UseFormRegister } from 'react-hook-form';
|
import type { UseFormRegister } from 'react-hook-form';
|
||||||
@ -35,9 +35,9 @@ export const ExpirySelector = ({
|
|||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
{errorMessage && (
|
{errorMessage && (
|
||||||
<InputError data-testid="dealticket-error-message-expiry">
|
<NotificationError testId="dealticket-error-message-expiry">
|
||||||
{errorMessage}
|
{errorMessage}
|
||||||
</InputError>
|
</NotificationError>
|
||||||
)}
|
)}
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
);
|
);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
FormGroup,
|
FormGroup,
|
||||||
InputError,
|
NotificationError,
|
||||||
Select,
|
Select,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
} from '@vegaprotocol/ui-toolkit';
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
@ -139,9 +139,9 @@ export const TimeInForceSelector = ({
|
|||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
{errorMessage && (
|
{errorMessage && (
|
||||||
<InputError data-testid="dealticket-error-message-tif">
|
<NotificationError testId="dealticket-error-message-tif">
|
||||||
{renderError(errorMessage)}
|
{renderError(errorMessage)}
|
||||||
</InputError>
|
</NotificationError>
|
||||||
)}
|
)}
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
);
|
);
|
||||||
|
@ -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 { DataGrid, t } from '@vegaprotocol/react-helpers';
|
||||||
import * as Schema from '@vegaprotocol/types';
|
import * as Schema from '@vegaprotocol/types';
|
||||||
import { Toggle } from '@vegaprotocol/ui-toolkit';
|
import { Toggle } from '@vegaprotocol/ui-toolkit';
|
||||||
@ -78,9 +82,9 @@ export const TypeSelector = ({
|
|||||||
onChange={(e) => onSelect(e.target.value as Schema.OrderType)}
|
onChange={(e) => onSelect(e.target.value as Schema.OrderType)}
|
||||||
/>
|
/>
|
||||||
{errorMessage && (
|
{errorMessage && (
|
||||||
<InputError data-testid="dealticket-error-message-type">
|
<NotificationError testId="dealticket-error-message-type">
|
||||||
{renderError(errorMessage as MarketModeValidationType)}
|
{renderError(errorMessage as MarketModeValidationType)}
|
||||||
</InputError>
|
</NotificationError>
|
||||||
)}
|
)}
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
);
|
);
|
||||||
|
@ -103,7 +103,7 @@ describe('FillsTable', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const amountCell = cells.find((c) => c.getAttribute('col-id') === 'size');
|
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 () => {
|
it('should format cells correctly for seller fill', async () => {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
export * from './__generated__/MarketDepth';
|
export * from './__generated__/MarketDepth';
|
||||||
export * from './depth-chart';
|
export * from './depth-chart';
|
||||||
export { marketDepthProvider } from './market-depth-provider';
|
export * from './market-depth-provider';
|
||||||
export * from './orderbook-container';
|
export * from './orderbook-container';
|
||||||
export * from './orderbook-data';
|
export * from './orderbook-data';
|
||||||
export * from './orderbook-manager';
|
export * from './orderbook-manager';
|
||||||
|
@ -334,7 +334,7 @@ export const Orderbook = ({
|
|||||||
BigInt(maxPriceLevel) -
|
BigInt(maxPriceLevel) -
|
||||||
BigInt(offsetTop) * BigInt(resolution)
|
BigInt(offsetTop) * BigInt(resolution)
|
||||||
).toString()
|
).toString()
|
||||||
: rows?.[Math.min(offsetTop, rows.length - 1)].price.toString();
|
: rows?.[Math.min(offsetTop, rows.length - 1)]?.price?.toString();
|
||||||
if (lockOnMidPrice) {
|
if (lockOnMidPrice) {
|
||||||
setLockOnMidPrice(false);
|
setLockOnMidPrice(false);
|
||||||
}
|
}
|
||||||
|
@ -92,7 +92,7 @@ it('add color and sign to amount, displays positive notional value', async () =>
|
|||||||
});
|
});
|
||||||
let cells = screen.getAllByRole('gridcell');
|
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].classList.contains('text-vega-pink')).toBeFalsy();
|
||||||
expect(cells[2].textContent).toEqual('+100');
|
expect(cells[2].textContent).toEqual('+100');
|
||||||
expect(cells[1].textContent).toEqual('1,230.0');
|
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');
|
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].classList.contains('text-vega-pink')).toBeTruthy();
|
||||||
expect(cells[2].textContent?.startsWith('-100')).toBeTruthy();
|
expect(cells[2].textContent?.startsWith('-100')).toBeTruthy();
|
||||||
expect(cells[1].textContent).toEqual('1,230.0');
|
expect(cells[1].textContent).toEqual('1,230.0');
|
||||||
|
@ -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';
|
export const negativeClassNames = 'text-vega-pink dark:text-vega-pink';
|
||||||
|
|
||||||
const isPositive = ({ value }: { value: string | bigint | number }) =>
|
const isPositive = ({ value }: { value: string | bigint | number }) =>
|
||||||
|
@ -116,7 +116,10 @@ export const TradesTable = forwardRef<AgGridReact, Props>((props, ref) => {
|
|||||||
if (!data?.market) {
|
if (!data?.market) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return addDecimal(value, data.market.positionDecimalPlaces);
|
return addDecimalsFormatNumber(
|
||||||
|
value,
|
||||||
|
data.market.positionDecimalPlaces
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<AgGridColumn
|
<AgGridColumn
|
||||||
|
@ -9,7 +9,7 @@ import { Icon } from '../icon';
|
|||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
|
|
||||||
export type ButtonVariant = 'default' | 'primary' | 'secondary' | 'ternary';
|
export type ButtonVariant = 'default' | 'primary' | 'secondary' | 'ternary';
|
||||||
type ButtonSize = 'lg' | 'md' | 'sm' | 'xs';
|
export type ButtonSize = 'lg' | 'md' | 'sm' | 'xs';
|
||||||
|
|
||||||
const base = 'inline-block uppercase border rounded-md disabled:opacity-60';
|
const base = 'inline-block uppercase border rounded-md disabled:opacity-60';
|
||||||
const xs = 'px-2 py-0 text-sm';
|
const xs = 'px-2 py-0 text-sm';
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import type { HTMLAttributes } from 'react';
|
import type { HTMLAttributes } from 'react';
|
||||||
|
import { Intent } from '../../utils/intent';
|
||||||
|
import { Notification } from '../notification';
|
||||||
|
|
||||||
interface InputErrorProps extends HTMLAttributes<HTMLDivElement> {
|
interface InputErrorProps extends HTMLAttributes<HTMLDivElement> {
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
@ -7,6 +9,40 @@ interface InputErrorProps extends HTMLAttributes<HTMLDivElement> {
|
|||||||
forInput?: string;
|
forInput?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface NotificationErrorProps extends HTMLAttributes<HTMLDivElement> {
|
||||||
|
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 (
|
||||||
|
<Notification
|
||||||
|
intent={getIntent(intent)}
|
||||||
|
testId={testId || 'input-error-text'}
|
||||||
|
message={<div className="role">{children}</div>}
|
||||||
|
aria-describedby={forInput}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const InputError = ({
|
export const InputError = ({
|
||||||
intent = 'danger',
|
intent = 'danger',
|
||||||
children,
|
children,
|
||||||
|
@ -4,13 +4,20 @@ import classNames from 'classnames';
|
|||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import { Intent } from '../../utils/intent';
|
import { Intent } from '../../utils/intent';
|
||||||
import { Icon } from '../icon';
|
import { Icon } from '../icon';
|
||||||
|
import type { ButtonSize } from '../button';
|
||||||
import { Button } from '../button';
|
import { Button } from '../button';
|
||||||
|
|
||||||
type NotificationProps = {
|
type NotificationProps = {
|
||||||
intent: Intent;
|
intent: Intent;
|
||||||
message: ReactNode | string;
|
message: ReactNode | string;
|
||||||
title?: string;
|
title?: string;
|
||||||
buttonProps?: { text: string; action: () => void; className?: string };
|
buttonProps?: {
|
||||||
|
text: string;
|
||||||
|
action: () => void;
|
||||||
|
className?: string;
|
||||||
|
dataTestId?: string;
|
||||||
|
size?: ButtonSize;
|
||||||
|
};
|
||||||
testId?: string;
|
testId?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
};
|
};
|
||||||
@ -41,11 +48,20 @@ export const Notification = ({
|
|||||||
{
|
{
|
||||||
'border-gray-700 dark:border-gray-300': intent === Intent.None,
|
'border-gray-700 dark:border-gray-300': intent === Intent.None,
|
||||||
'border-vega-blue': intent === Intent.Primary,
|
'border-vega-blue': intent === Intent.Primary,
|
||||||
'border-vega-green dark:border-vega-green': intent === Intent.Success,
|
'border-vega-green-550 dark:border-vega-green':
|
||||||
'border-yellow-500': intent === Intent.Warning,
|
intent === Intent.Success,
|
||||||
|
'border-vega-orange': intent === Intent.Warning,
|
||||||
'border-vega-pink': intent === Intent.Danger,
|
'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
|
className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@ -55,7 +71,7 @@ export const Notification = ({
|
|||||||
'text-gray-700 dark:text-gray-300': intent === Intent.None,
|
'text-gray-700 dark:text-gray-300': intent === Intent.None,
|
||||||
'text-vega-blue': intent === Intent.Primary,
|
'text-vega-blue': intent === Intent.Primary,
|
||||||
'text-vega-green dark:text-vega-green': intent === Intent.Success,
|
'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,
|
'text-vega-pink': intent === Intent.Danger,
|
||||||
},
|
},
|
||||||
'flex items-start mt-1'
|
'flex items-start mt-1'
|
||||||
@ -63,18 +79,19 @@ export const Notification = ({
|
|||||||
>
|
>
|
||||||
<Icon size={4} name={getIcon(intent)} />
|
<Icon size={4} name={getIcon(intent)} />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col flex-grow items-start gap-1.5 text-base">
|
<div className="flex flex-col flex-grow items-start gap-1.5">
|
||||||
{title && (
|
{title && (
|
||||||
<div className="whitespace-nowrap overflow-hidden text-ellipsis uppercase text-sm leading-6">
|
<div className="whitespace-nowrap overflow-hidden text-ellipsis uppercase leading-6">
|
||||||
{title}
|
{title}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div>{message}</div>
|
<div className="text-sm">{message}</div>
|
||||||
{buttonProps && (
|
{buttonProps && (
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size={buttonProps.size || 'sm'}
|
||||||
onClick={buttonProps.action}
|
onClick={buttonProps.action}
|
||||||
className={classNames('mt-2 px-6 py-3', buttonProps.className)}
|
className={classNames(buttonProps.className)}
|
||||||
|
data-testid={buttonProps.dataTestId}
|
||||||
>
|
>
|
||||||
{buttonProps.text}
|
{buttonProps.text}
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -23,7 +23,8 @@ export const getIntentBackground = (intent?: Intent) => {
|
|||||||
'bg-vega-pink dark:bg-vega-yellow': intent === Intent.Primary,
|
'bg-vega-pink dark:bg-vega-yellow': intent === Intent.Primary,
|
||||||
'bg-danger': intent === Intent.Danger,
|
'bg-danger': intent === Intent.Danger,
|
||||||
'bg-warning': intent === Intent.Warning,
|
'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,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user