feat(deal-ticket): update deal ticket submit buttons
This commit is contained in:
@@ -1,25 +0,0 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Side } from '@vegaprotocol/types';
|
||||
import classNames from 'classnames';
|
||||
|
||||
interface Props {
|
||||
side: Side;
|
||||
label?: string;
|
||||
}
|
||||
|
||||
export const DealTicketButton = ({ side, label }: Props) => {
|
||||
const buttonClasses = classNames(
|
||||
'px-10 py-2 uppercase rounded-md text-white w-full',
|
||||
{
|
||||
'bg-market-red': side === Side.SIDE_SELL,
|
||||
'bg-market-green-550': side === Side.SIDE_BUY,
|
||||
}
|
||||
);
|
||||
return (
|
||||
<div className="mb-2">
|
||||
<button type="submit" data-testid="place-order" className={buttonClasses}>
|
||||
{label || t('Place order')}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,7 +1,4 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { Tooltip } from '@vegaprotocol/ui-toolkit';
|
||||
import classnames from 'classnames';
|
||||
import type { ReactNode } from 'react';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { FeesBreakdown } from '@vegaprotocol/markets';
|
||||
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
|
||||
@@ -16,7 +13,6 @@ import { marketMarginDataProvider } from '@vegaprotocol/accounts';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
|
||||
import {
|
||||
NOTIONAL_SIZE_TOOLTIP_TEXT,
|
||||
MARGIN_DIFF_TOOLTIP_TEXT,
|
||||
DEDUCTION_FROM_COLLATERAL_TOOLTIP_TEXT,
|
||||
TOTAL_MARGIN_AVAILABLE,
|
||||
@@ -25,114 +21,54 @@ import {
|
||||
MARGIN_ACCOUNT_TOOLTIP_TEXT,
|
||||
} from '../../constants';
|
||||
import { useEstimateFees } from '../../hooks';
|
||||
import { KeyValue } from './key-value';
|
||||
|
||||
const emptyValue = '-';
|
||||
|
||||
export interface DealTicketFeeDetailPros {
|
||||
label: string;
|
||||
value?: string | null | undefined;
|
||||
symbol: string;
|
||||
indent?: boolean | undefined;
|
||||
labelDescription?: ReactNode;
|
||||
formattedValue?: string;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
export const DealTicketFeeDetail = ({
|
||||
label,
|
||||
value,
|
||||
labelDescription,
|
||||
symbol,
|
||||
indent,
|
||||
onClick,
|
||||
formattedValue,
|
||||
}: DealTicketFeeDetailPros) => {
|
||||
const displayValue = `${formattedValue ?? '-'} ${symbol || ''}`;
|
||||
const valueElement = onClick ? (
|
||||
<button onClick={onClick} className="text-muted">
|
||||
{displayValue}
|
||||
</button>
|
||||
) : (
|
||||
<div className="text-muted">{displayValue}</div>
|
||||
);
|
||||
return (
|
||||
<div
|
||||
data-testid={
|
||||
'deal-ticket-fee-' + label.toLocaleLowerCase().replace(/\s/g, '-')
|
||||
}
|
||||
key={typeof label === 'string' ? label : 'value-dropdown'}
|
||||
className={classnames(
|
||||
'text-xs mt-2 flex justify-between items-center gap-4 flex-wrap',
|
||||
{ 'ml-2': indent }
|
||||
)}
|
||||
>
|
||||
<Tooltip description={labelDescription}>
|
||||
<div>{label}</div>
|
||||
</Tooltip>
|
||||
<Tooltip description={`${value ?? '-'} ${symbol || ''}`}>
|
||||
{valueElement}
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export interface DealTicketFeeDetailsProps {
|
||||
assetSymbol: string;
|
||||
order: OrderSubmissionBody['orderSubmission'];
|
||||
market: Market;
|
||||
notionalSize: string | null;
|
||||
}
|
||||
|
||||
export const DealTicketFeeDetails = ({
|
||||
assetSymbol,
|
||||
order,
|
||||
market,
|
||||
notionalSize,
|
||||
}: DealTicketFeeDetailsProps) => {
|
||||
const feeEstimate = useEstimateFees(order);
|
||||
const { settlementAsset: asset } =
|
||||
market.tradableInstrument.instrument.product;
|
||||
const { decimals: assetDecimals, quantum } = asset;
|
||||
const marketDecimals = market.decimalPlaces;
|
||||
const quoteName = market.tradableInstrument.instrument.product.quoteName;
|
||||
|
||||
return (
|
||||
<>
|
||||
<DealTicketFeeDetail
|
||||
label={t('Notional')}
|
||||
value={formatValue(notionalSize, marketDecimals)}
|
||||
formattedValue={formatValue(notionalSize, marketDecimals)}
|
||||
symbol={quoteName}
|
||||
labelDescription={NOTIONAL_SIZE_TOOLTIP_TEXT(quoteName)}
|
||||
/>
|
||||
<DealTicketFeeDetail
|
||||
label={t('Fees')}
|
||||
value={
|
||||
feeEstimate?.totalFeeAmount &&
|
||||
`~${formatValue(feeEstimate?.totalFeeAmount, assetDecimals)}`
|
||||
}
|
||||
formattedValue={
|
||||
feeEstimate?.totalFeeAmount &&
|
||||
`~${formatValue(feeEstimate?.totalFeeAmount, assetDecimals, quantum)}`
|
||||
}
|
||||
labelDescription={
|
||||
<>
|
||||
<span>
|
||||
{t(
|
||||
`An estimate of the most you would be expected to pay in fees, in the market's settlement asset ${assetSymbol}.`
|
||||
)}
|
||||
</span>
|
||||
<FeesBreakdown
|
||||
fees={feeEstimate?.fees}
|
||||
feeFactors={market.fees.factors}
|
||||
symbol={assetSymbol}
|
||||
decimals={assetDecimals}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
symbol={assetSymbol}
|
||||
/>
|
||||
</>
|
||||
<KeyValue
|
||||
label={t('Fees')}
|
||||
value={
|
||||
feeEstimate?.totalFeeAmount &&
|
||||
`~${formatValue(feeEstimate?.totalFeeAmount, assetDecimals)}`
|
||||
}
|
||||
formattedValue={
|
||||
feeEstimate?.totalFeeAmount &&
|
||||
`~${formatValue(feeEstimate?.totalFeeAmount, assetDecimals, quantum)}`
|
||||
}
|
||||
labelDescription={
|
||||
<>
|
||||
<span>
|
||||
{t(
|
||||
`An estimate of the most you would be expected to pay in fees, in the market's settlement asset ${assetSymbol}.`
|
||||
)}
|
||||
</span>
|
||||
<FeesBreakdown
|
||||
fees={feeEstimate?.fees}
|
||||
feeFactors={market.fees.factors}
|
||||
symbol={assetSymbol}
|
||||
decimals={assetDecimals}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
symbol={assetSymbol}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -209,7 +145,7 @@ export const DealTicketMarginDetails = ({
|
||||
BigInt(marginAccountBalance);
|
||||
|
||||
deductionFromCollateral = (
|
||||
<DealTicketFeeDetail
|
||||
<KeyValue
|
||||
indent
|
||||
label={t('Deduction from collateral')}
|
||||
value={formatRange(
|
||||
@@ -236,7 +172,7 @@ export const DealTicketMarginDetails = ({
|
||||
/>
|
||||
);
|
||||
projectedMargin = (
|
||||
<DealTicketFeeDetail
|
||||
<KeyValue
|
||||
label={t('Projected margin')}
|
||||
value={formatRange(
|
||||
marginEstimate?.bestCase.initialLevel,
|
||||
@@ -308,7 +244,7 @@ export const DealTicketMarginDetails = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<DealTicketFeeDetail
|
||||
<KeyValue
|
||||
label={t('Margin required')}
|
||||
value={formatRange(
|
||||
marginRequiredBestCase,
|
||||
@@ -324,7 +260,7 @@ export const DealTicketMarginDetails = ({
|
||||
labelDescription={MARGIN_DIFF_TOOLTIP_TEXT(assetSymbol)}
|
||||
symbol={assetSymbol}
|
||||
/>
|
||||
<DealTicketFeeDetail
|
||||
<KeyValue
|
||||
label={t('Total margin available')}
|
||||
indent
|
||||
value={formatValue(totalMarginAvailable, assetDecimals)}
|
||||
@@ -342,7 +278,7 @@ export const DealTicketMarginDetails = ({
|
||||
)}
|
||||
/>
|
||||
{deductionFromCollateral}
|
||||
<DealTicketFeeDetail
|
||||
<KeyValue
|
||||
label={t('Current margin allocation')}
|
||||
indent
|
||||
onClick={
|
||||
@@ -358,7 +294,7 @@ export const DealTicketMarginDetails = ({
|
||||
)}
|
||||
/>
|
||||
{projectedMargin}
|
||||
<DealTicketFeeDetail
|
||||
<KeyValue
|
||||
label={t('Liquidation price estimate')}
|
||||
value={liquidationPriceEstimate}
|
||||
formattedValue={liquidationPriceEstimate}
|
||||
|
||||
@@ -115,6 +115,32 @@ describe('StopOrder', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('calculate notional for market limit', async () => {
|
||||
render(generateJsx());
|
||||
await userEvent.type(screen.getByTestId(sizeInput), '10');
|
||||
await userEvent.type(screen.getByTestId(priceInput), '10');
|
||||
expect(screen.getByTestId('deal-ticket-fee-notional')).toHaveTextContent(
|
||||
'Notional100.00 BTC'
|
||||
);
|
||||
});
|
||||
|
||||
it('calculates notional for limit order', async () => {
|
||||
render(generateJsx());
|
||||
await userEvent.click(screen.getByTestId(orderTypeTrigger));
|
||||
await userEvent.click(screen.getByTestId(orderTypeMarket));
|
||||
await userEvent.type(screen.getByTestId(sizeInput), '10');
|
||||
// price trigger is selected but it's empty, calculate base on size and marketPrice prop
|
||||
expect(screen.getByTestId('deal-ticket-fee-notional')).toHaveTextContent(
|
||||
'Notional20.00 BTC'
|
||||
);
|
||||
|
||||
await userEvent.type(screen.getByTestId(triggerPriceInput), '3');
|
||||
// calculate base on size and price trigger
|
||||
expect(screen.getByTestId('deal-ticket-fee-notional')).toHaveTextContent(
|
||||
'Notional30.00 BTC'
|
||||
);
|
||||
});
|
||||
|
||||
it('should use local storage state for initial values', async () => {
|
||||
const values: Partial<StopOrderFormValues> = {
|
||||
type: Schema.OrderType.TYPE_LIMIT,
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import type { StopOrdersSubmission } from '@vegaprotocol/wallet';
|
||||
import {
|
||||
formatForInput,
|
||||
formatValue,
|
||||
removeDecimal,
|
||||
toDecimal,
|
||||
validateAmount,
|
||||
@@ -19,6 +20,9 @@ import {
|
||||
TradingInputError as InputError,
|
||||
TradingSelect as Select,
|
||||
Tooltip,
|
||||
TradingButton,
|
||||
Intent,
|
||||
Pill,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { getDerivedPrice } from '@vegaprotocol/markets';
|
||||
import type { Market } from '@vegaprotocol/markets';
|
||||
@@ -41,9 +45,10 @@ import {
|
||||
} from '../../hooks/use-form-values';
|
||||
import type { StopOrderFormValues } from '../../hooks/use-form-values';
|
||||
import { mapFormValuesToStopOrdersSubmission } from '../../utils/map-form-values-to-submission';
|
||||
import { DealTicketButton } from './deal-ticket-button';
|
||||
import { DealTicketFeeDetails } from './deal-ticket-fee-details';
|
||||
import { validateExpiration } from '../../utils';
|
||||
import { NOTIONAL_SIZE_TOOLTIP_TEXT } from '../../constants';
|
||||
import { KeyValue } from './key-value';
|
||||
|
||||
export interface StopOrderProps {
|
||||
market: Market;
|
||||
@@ -78,7 +83,7 @@ const Trigger = ({
|
||||
control,
|
||||
watch,
|
||||
priceStep,
|
||||
assetSymbol,
|
||||
quoteName,
|
||||
oco,
|
||||
marketPrice,
|
||||
decimalPlaces,
|
||||
@@ -86,7 +91,7 @@ const Trigger = ({
|
||||
control: Control<StopOrderFormValues>;
|
||||
watch: UseFormWatch<StopOrderFormValues>;
|
||||
priceStep: string;
|
||||
assetSymbol: string;
|
||||
quoteName: string;
|
||||
oco?: boolean;
|
||||
marketPrice?: string | null;
|
||||
decimalPlaces: number;
|
||||
@@ -181,7 +186,7 @@ const Trigger = ({
|
||||
data-testid={`triggerPrice${oco ? '-oco' : ''}`}
|
||||
type="number"
|
||||
step={priceStep}
|
||||
appendElement={assetSymbol}
|
||||
appendElement={<Pill size="xs">{quoteName}</Pill>}
|
||||
value={value || ''}
|
||||
hasError={!!fieldState.error}
|
||||
{...props}
|
||||
@@ -249,7 +254,7 @@ const Trigger = ({
|
||||
<Input
|
||||
type="number"
|
||||
step={trailingPercentOffsetStep}
|
||||
appendElement="%"
|
||||
appendElement={<Pill size="xs">%</Pill>}
|
||||
data-testid={`triggerTrailingPercentOffset${
|
||||
oco ? '-oco' : ''
|
||||
}`}
|
||||
@@ -311,10 +316,12 @@ const Size = ({
|
||||
control,
|
||||
sizeStep,
|
||||
oco,
|
||||
isLimitType,
|
||||
}: {
|
||||
control: Control<StopOrderFormValues>;
|
||||
sizeStep: string;
|
||||
oco?: boolean;
|
||||
isLimitType: boolean;
|
||||
}) => {
|
||||
return (
|
||||
<Controller
|
||||
@@ -332,7 +339,7 @@ const Size = ({
|
||||
const { value, ...props } = field;
|
||||
const id = `order-size${oco ? '-oco' : ''}`;
|
||||
return (
|
||||
<div className="mb-4">
|
||||
<div className={`mb-${isLimitType ? '4' : '2'}`}>
|
||||
<FormGroup labelFor={id} label={t(`Size`)} compact>
|
||||
<Input
|
||||
id={id}
|
||||
@@ -394,12 +401,8 @@ const Price = ({
|
||||
const { value, ...props } = field;
|
||||
const id = `order-price${oco ? '-oco' : ''}`;
|
||||
return (
|
||||
<div className="mb-4">
|
||||
<FormGroup
|
||||
labelFor={id}
|
||||
label={t(`Price (${quoteName})`)}
|
||||
compact={true}
|
||||
>
|
||||
<div className="mb-2">
|
||||
<FormGroup labelFor={id} label={t('Price')} compact={true}>
|
||||
<Input
|
||||
id={id}
|
||||
className="w-full"
|
||||
@@ -409,6 +412,7 @@ const Price = ({
|
||||
onWheel={(e) => e.currentTarget.blur()}
|
||||
value={value || ''}
|
||||
hasError={!!fieldState.error}
|
||||
appendElement={<Pill size="xs">{quoteName}</Pill>}
|
||||
{...props}
|
||||
/>
|
||||
</FormGroup>
|
||||
@@ -530,6 +534,9 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
||||
const rawSize = watch('size');
|
||||
const oco = watch('oco');
|
||||
const expiresAt = watch('expiresAt');
|
||||
const ocoPrice = watch('ocoPrice');
|
||||
const ocoSize = watch('ocoSize');
|
||||
const ocoType = watch('ocoType');
|
||||
|
||||
useEffect(() => {
|
||||
const size = storedFormValues?.[dealTicketType]?.size;
|
||||
@@ -566,6 +573,13 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
||||
market.positionDecimalPlaces
|
||||
);
|
||||
|
||||
const notionalSizeOco = getNotionalSize(
|
||||
ocoPrice,
|
||||
ocoSize,
|
||||
market.decimalPlaces,
|
||||
market.positionDecimalPlaces
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const subscription = watch((value, { name, type }) => {
|
||||
updateStoredFormValues(market.id, value);
|
||||
@@ -620,18 +634,31 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
||||
control={control}
|
||||
watch={watch}
|
||||
priceStep={priceStep}
|
||||
assetSymbol={asset.symbol}
|
||||
quoteName={quoteName}
|
||||
marketPrice={marketPrice}
|
||||
decimalPlaces={market.decimalPlaces}
|
||||
/>
|
||||
<hr className="mb-4 border-vega-clight-500 dark:border-vega-cdark-500" />
|
||||
<Size
|
||||
control={control}
|
||||
sizeStep={sizeStep}
|
||||
isLimitType={type === Schema.OrderType.TYPE_LIMIT}
|
||||
/>
|
||||
<Price
|
||||
control={control}
|
||||
watch={watch}
|
||||
priceStep={priceStep}
|
||||
quoteName={quoteName}
|
||||
/>
|
||||
<Size control={control} sizeStep={sizeStep} />
|
||||
<div className="mb-4">
|
||||
<KeyValue
|
||||
label={t('Notional')}
|
||||
value={formatValue(notionalSize, market.decimalPlaces)}
|
||||
formattedValue={formatValue(notionalSize, market.decimalPlaces)}
|
||||
symbol={quoteName}
|
||||
labelDescription={NOTIONAL_SIZE_TOOLTIP_TEXT(quoteName)}
|
||||
/>
|
||||
</div>
|
||||
<TimeInForce control={control} />
|
||||
<div className="flex justify-end pb-3 gap-2">
|
||||
<ReduceOnly />
|
||||
@@ -699,12 +726,18 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
||||
control={control}
|
||||
watch={watch}
|
||||
priceStep={priceStep}
|
||||
assetSymbol={asset.symbol}
|
||||
quoteName={quoteName}
|
||||
marketPrice={marketPrice}
|
||||
decimalPlaces={market.decimalPlaces}
|
||||
oco
|
||||
/>
|
||||
<hr className="mb-2 border-vega-clight-500 dark:border-vega-cdark-500" />
|
||||
<Size
|
||||
control={control}
|
||||
sizeStep={sizeStep}
|
||||
oco
|
||||
isLimitType={ocoType === Schema.OrderType.TYPE_LIMIT}
|
||||
/>
|
||||
<Price
|
||||
control={control}
|
||||
watch={watch}
|
||||
@@ -712,7 +745,18 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
||||
quoteName={quoteName}
|
||||
oco
|
||||
/>
|
||||
<Size control={control} sizeStep={sizeStep} oco />
|
||||
<div className="mb-4">
|
||||
<KeyValue
|
||||
label={t('Notional')}
|
||||
value={formatValue(notionalSizeOco, market.decimalPlaces)}
|
||||
formattedValue={formatValue(
|
||||
notionalSizeOco,
|
||||
market.decimalPlaces
|
||||
)}
|
||||
symbol={quoteName}
|
||||
labelDescription={NOTIONAL_SIZE_TOOLTIP_TEXT(quoteName)}
|
||||
/>
|
||||
</div>
|
||||
<TimeInForce control={control} oco />
|
||||
<div className="flex justify-end mb-2 gap-2">
|
||||
<ReduceOnly />
|
||||
@@ -803,7 +847,16 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
||||
</>
|
||||
)}
|
||||
<NoWalletWarning isReadOnly={isReadOnly} />
|
||||
<DealTicketButton side={side} label={t('Submit Stop Order')} />
|
||||
<TradingButton
|
||||
data-testid="place-order"
|
||||
className="w-full"
|
||||
intent={side === Schema.Side.SIDE_BUY ? Intent.Success : Intent.Danger}
|
||||
subLabel={`${formatValue(notionalSize, market.decimalPlaces)} ${
|
||||
market.tradableInstrument.instrument.product.quoteName
|
||||
}`}
|
||||
>
|
||||
{t('Place order')}
|
||||
</TradingButton>
|
||||
<DealTicketFeeDetails
|
||||
order={{
|
||||
marketId: market.id,
|
||||
@@ -813,7 +866,6 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
||||
timeInForce,
|
||||
type,
|
||||
}}
|
||||
notionalSize={notionalSize}
|
||||
assetSymbol={asset.symbol}
|
||||
market={market}
|
||||
/>
|
||||
|
||||
@@ -3,7 +3,6 @@ import * as Schema from '@vegaprotocol/types';
|
||||
import type { FormEventHandler } from 'react';
|
||||
import { memo, useCallback, useEffect, useRef, useMemo } from 'react';
|
||||
import { Controller, useController, useForm } from 'react-hook-form';
|
||||
import { DealTicketButton } from './deal-ticket-button';
|
||||
import {
|
||||
DealTicketFeeDetails,
|
||||
DealTicketMarginDetails,
|
||||
@@ -23,6 +22,8 @@ import {
|
||||
Intent,
|
||||
Notification,
|
||||
Tooltip,
|
||||
TradingButton,
|
||||
Pill,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
import {
|
||||
@@ -35,6 +36,7 @@ import {
|
||||
validateAmount,
|
||||
toDecimal,
|
||||
formatForInput,
|
||||
formatValue,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { activeOrdersProvider } from '@vegaprotocol/orders';
|
||||
import { getDerivedPrice } from '@vegaprotocol/markets';
|
||||
@@ -46,7 +48,10 @@ import {
|
||||
validateType,
|
||||
} from '../../utils';
|
||||
import { ZeroBalanceError } from '../deal-ticket-validation/zero-balance-error';
|
||||
import { SummaryValidationType } from '../../constants';
|
||||
import {
|
||||
NOTIONAL_SIZE_TOOLTIP_TEXT,
|
||||
SummaryValidationType,
|
||||
} from '../../constants';
|
||||
import type {
|
||||
Market,
|
||||
MarketData,
|
||||
@@ -68,6 +73,7 @@ import { useDealTicketFormValues } from '../../hooks/use-form-values';
|
||||
import { DealTicketSizeIceberg } from './deal-ticket-size-iceberg';
|
||||
import noop from 'lodash/noop';
|
||||
import { isNonPersistentOrder } from '../../utils/time-in-force-persistance';
|
||||
import { KeyValue } from './key-value';
|
||||
|
||||
export const REDUCE_ONLY_TOOLTIP =
|
||||
'"Reduce only" will ensure that this order will not increase the size of an open position. When the order is matched, it will only trade enough volume to bring your open volume towards 0 but never change the direction of your position. If applied to a limit order that is not instantly filled, the order will be stopped.';
|
||||
@@ -338,6 +344,7 @@ export const DealTicket = ({
|
||||
const priceStep = toDecimal(market?.decimalPlaces);
|
||||
const sizeStep = toDecimal(market?.positionDecimalPlaces);
|
||||
const quoteName = market.tradableInstrument.instrument.product.quoteName;
|
||||
const isLimitType = type === Schema.OrderType.TYPE_LIMIT;
|
||||
|
||||
return (
|
||||
<form
|
||||
@@ -386,7 +393,7 @@ export const DealTicket = ({
|
||||
validate: validateAmount(sizeStep, 'Size'),
|
||||
}}
|
||||
render={({ field, fieldState }) => (
|
||||
<div className="mb-4">
|
||||
<div className={`mb-${isLimitType ? '4' : '2'}`}>
|
||||
<FormGroup
|
||||
label={t('Size')}
|
||||
labelFor="input-order-size-limit"
|
||||
@@ -411,7 +418,7 @@ export const DealTicket = ({
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
{type === Schema.OrderType.TYPE_LIMIT && (
|
||||
{isLimitType && (
|
||||
<Controller
|
||||
name="price"
|
||||
control={control}
|
||||
@@ -424,14 +431,15 @@ export const DealTicket = ({
|
||||
validate: validateAmount(priceStep, 'Price'),
|
||||
}}
|
||||
render={({ field, fieldState }) => (
|
||||
<div className="mb-4">
|
||||
<div className="mb-2">
|
||||
<FormGroup
|
||||
labelFor="input-price-quote"
|
||||
label={t(`Price (${quoteName})`)}
|
||||
label={t('Price')}
|
||||
compact
|
||||
>
|
||||
<Input
|
||||
id="input-price-quote"
|
||||
appendElement={<Pill size="xs">{quoteName}</Pill>}
|
||||
className="w-full"
|
||||
type="number"
|
||||
step={priceStep}
|
||||
@@ -449,6 +457,15 @@ export const DealTicket = ({
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<div className="mb-4">
|
||||
<KeyValue
|
||||
label={t('Notional')}
|
||||
value={formatValue(notionalSize, market.decimalPlaces)}
|
||||
formattedValue={formatValue(notionalSize, market.decimalPlaces)}
|
||||
symbol={quoteName}
|
||||
labelDescription={NOTIONAL_SIZE_TOOLTIP_TEXT(quoteName)}
|
||||
/>
|
||||
</div>
|
||||
<Controller
|
||||
name="timeInForce"
|
||||
control={control}
|
||||
@@ -475,7 +492,7 @@ export const DealTicket = ({
|
||||
}
|
||||
|
||||
// iceberg orders must be persistent orders, so if user
|
||||
// switches to to a non persisten tif value, remove iceberg selection
|
||||
// switches to a non persistent tif value, remove iceberg selection
|
||||
if (iceberg && isNonPersistentOrder(value)) {
|
||||
setValue('iceberg', false);
|
||||
}
|
||||
@@ -487,7 +504,7 @@ export const DealTicket = ({
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{type === Schema.OrderType.TYPE_LIMIT &&
|
||||
{isLimitType &&
|
||||
timeInForce === Schema.OrderTimeInForce.TIME_IN_FORCE_GTT && (
|
||||
<Controller
|
||||
name="expiresAt"
|
||||
@@ -569,7 +586,7 @@ export const DealTicket = ({
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
{type === Schema.OrderType.TYPE_LIMIT && (
|
||||
{isLimitType && (
|
||||
<>
|
||||
<div className="flex justify-between pb-2 gap-2">
|
||||
<Controller
|
||||
@@ -624,12 +641,20 @@ export const DealTicket = ({
|
||||
pubKey={pubKey}
|
||||
onDeposit={onDeposit}
|
||||
/>
|
||||
<DealTicketButton side={side} />
|
||||
<TradingButton
|
||||
data-testid="place-order"
|
||||
className="w-full"
|
||||
intent={side === Schema.Side.SIDE_BUY ? Intent.Success : Intent.Danger}
|
||||
subLabel={`${formatValue(notionalSize, market.decimalPlaces)} ${
|
||||
market.tradableInstrument.instrument.product.quoteName
|
||||
}`}
|
||||
>
|
||||
{t('Place order')}
|
||||
</TradingButton>
|
||||
<DealTicketFeeDetails
|
||||
order={
|
||||
normalizedOrder && { ...normalizedOrder, price: price || undefined }
|
||||
}
|
||||
notionalSize={notionalSize}
|
||||
assetSymbol={assetSymbol}
|
||||
market={market}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import { Tooltip } from '@vegaprotocol/ui-toolkit';
|
||||
import classnames from 'classnames';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
export interface KeyValuePros {
|
||||
label: string;
|
||||
value?: string | null | undefined;
|
||||
symbol: string;
|
||||
indent?: boolean | undefined;
|
||||
labelDescription?: ReactNode;
|
||||
formattedValue?: string;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
export const KeyValue = ({
|
||||
label,
|
||||
value,
|
||||
labelDescription,
|
||||
symbol,
|
||||
indent,
|
||||
onClick,
|
||||
formattedValue,
|
||||
}: KeyValuePros) => {
|
||||
const displayValue = `${formattedValue ?? '-'} ${symbol || ''}`;
|
||||
const valueElement = onClick ? (
|
||||
<button onClick={onClick} className="text-muted">
|
||||
{displayValue}
|
||||
</button>
|
||||
) : (
|
||||
<div className="text-muted">{displayValue}</div>
|
||||
);
|
||||
return (
|
||||
<div
|
||||
data-testid={
|
||||
'deal-ticket-fee-' + label.toLocaleLowerCase().replace(/\s/g, '-')
|
||||
}
|
||||
key={typeof label === 'string' ? label : 'value-dropdown'}
|
||||
className={classnames(
|
||||
'text-xs mt-2 flex justify-between items-center gap-4 flex-wrap',
|
||||
{ 'ml-2': indent }
|
||||
)}
|
||||
>
|
||||
<Tooltip description={labelDescription}>
|
||||
<div>{label}</div>
|
||||
</Tooltip>
|
||||
<Tooltip description={`${value ?? '-'} ${symbol || ''}`}>
|
||||
{valueElement}
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user