diff --git a/libs/deal-ticket/src/components/constants.ts b/libs/deal-ticket/src/components/constants.ts index b8c812d44..4192763bb 100644 --- a/libs/deal-ticket/src/components/constants.ts +++ b/libs/deal-ticket/src/components/constants.ts @@ -28,3 +28,5 @@ export const DEAL_TICKET_SECTION = { EXPIRY: 'sec-expiry', SUMMARY: 'sec-summary', }; + +export const ERROR_SIZE_DECIMAL = 'step'; diff --git a/libs/deal-ticket/src/components/deal-ticket-validation/use-order-margin-validation.tsx b/libs/deal-ticket/src/components/deal-ticket-validation/use-order-margin-validation.tsx index d51b9bfc5..f83198bc6 100644 --- a/libs/deal-ticket/src/components/deal-ticket-validation/use-order-margin-validation.tsx +++ b/libs/deal-ticket/src/components/deal-ticket-validation/use-order-margin-validation.tsx @@ -1,3 +1,4 @@ +import { useMemo } from 'react'; import { useVegaWallet } from '@vegaprotocol/wallet'; import { AccountType } from '@vegaprotocol/types'; import { toBigNum } from '@vegaprotocol/react-helpers'; @@ -33,15 +34,20 @@ export const useOrderMarginValidation = ({ market, estMargin }: Props) => { const margin = toBigNum(estMargin?.margin || 0, 0); const { id, symbol, decimals } = market.tradableInstrument.instrument.product.settlementAsset; - if (balance.isZero() || balance.isLessThan(margin)) { + const balanceString = balance.toString(); + const marginString = margin.toString(); + const memoizedValue = useMemo(() => { return { - balance: balance.toString(), - margin: margin.toString(), + balance: balanceString, + margin: marginString, id, symbol, decimals, }; - } + }, [balanceString, marginString, id, symbol, decimals]); + if (balance.isZero() || balance.isLessThan(margin)) { + return memoizedValue; + } return false; }; diff --git a/libs/deal-ticket/src/components/deal-ticket-validation/use-order-validation.spec.tsx b/libs/deal-ticket/src/components/deal-ticket-validation/use-order-validation.spec.tsx index 755f9fb18..25477680d 100644 --- a/libs/deal-ticket/src/components/deal-ticket-validation/use-order-validation.spec.tsx +++ b/libs/deal-ticket/src/components/deal-ticket-validation/use-order-validation.spec.tsx @@ -11,10 +11,10 @@ import { } from '@vegaprotocol/types'; import type { ValidationProps } from './use-order-validation'; import { marketTranslations, useOrderValidation } from './use-order-validation'; -import { ERROR_SIZE_DECIMAL } from './validate-size'; import type { DealTicketMarketFragment } from '../deal-ticket/__generated___/DealTicket'; import * as OrderMarginValidation from './use-order-margin-validation'; import { ValidateMargin } from './validate-margin'; +import { ERROR_SIZE_DECIMAL } from '../constants'; jest.mock('@vegaprotocol/wallet'); @@ -174,12 +174,17 @@ describe('useOrderValidation', () => { `( 'Returns an error message for market state suspended or pending', ({ state }) => { + jest + .spyOn(OrderMarginValidation, 'useOrderMarginValidation') + .mockReturnValue(false); const { result } = setup({ market: { ...defaultOrder.market, state, tradingMode: MarketTradingMode.TRADING_MODE_BATCH_AUCTION, }, + orderType: Schema.OrderType.TYPE_LIMIT, + orderTimeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTT, }); expect(result.current).toStrictEqual({ isDisabled: false, @@ -302,4 +307,28 @@ describe('useOrderValidation', () => { testElement.type ); }); + + it.each` + state + ${MarketState.STATE_PENDING} + ${MarketState.STATE_PROPOSED} + `( + 'Returns error when market state is pending and size is wrong', + ({ state }) => { + const { result } = setup({ + fieldErrors: { + size: { type: `validate`, message: ERROR_SIZE_DECIMAL }, + }, + market: { + ...market, + state, + }, + }); + expect(result.current).toStrictEqual({ + isDisabled: true, + message: ERROR.FIELD_PRICE_STEP_DECIMAL, + section: 'sec-size', + }); + } + ); }); diff --git a/libs/deal-ticket/src/components/deal-ticket-validation/use-order-validation.tsx b/libs/deal-ticket/src/components/deal-ticket-validation/use-order-validation.tsx index 41e42bba1..8cf5ac444 100644 --- a/libs/deal-ticket/src/components/deal-ticket-validation/use-order-validation.tsx +++ b/libs/deal-ticket/src/components/deal-ticket-validation/use-order-validation.tsx @@ -12,14 +12,13 @@ import { } from '@vegaprotocol/types'; import type { OrderSubmissionBody } from '@vegaprotocol/wallet'; import { Tooltip } from '@vegaprotocol/ui-toolkit'; -import { ERROR_SIZE_DECIMAL } from './validate-size'; import { MarketDataGrid } from '../trading-mode-tooltip'; import { compileGridData } from '../trading-mode-tooltip/compile-grid-data'; import type { DealTicketMarketFragment } from '../deal-ticket/__generated___/DealTicket'; import { ValidateMargin } from './validate-margin'; import type { OrderMargin } from '../../hooks/use-order-margin'; import { useOrderMarginValidation } from './use-order-margin-validation'; -import { DEAL_TICKET_SECTION } from '../constants'; +import { DEAL_TICKET_SECTION, ERROR_SIZE_DECIMAL } from '../constants'; export const isMarketInAuction = (market: DealTicketMarketFragment) => { return [ @@ -53,7 +52,7 @@ export type DealTicketSection = export const useOrderValidation = ({ market, - fieldErrors = {}, + fieldErrors, orderType, orderTimeInForce, estMargin, @@ -66,6 +65,80 @@ export const useOrderValidation = ({ const minSize = toDecimal(market.positionDecimalPlaces); const isInvalidOrderMargin = useOrderMarginValidation({ market, estMargin }); + const fieldErrorChecking = useMemo<{ + message: ReactNode | string; + isDisabled: boolean; + section: DealTicketSection; + } | null>(() => { + if (fieldErrors?.size?.type || fieldErrors?.price?.type) { + if (fieldErrors?.size?.type === 'required') { + return { + isDisabled: true, + message: t('You need to provide a size'), + section: DEAL_TICKET_SECTION.SIZE, + }; + } + + if (fieldErrors?.size?.type === 'min') { + return { + isDisabled: true, + message: t(`Size cannot be lower than "${minSize}"`), + section: DEAL_TICKET_SECTION.SIZE, + }; + } + + if ( + fieldErrors?.price?.type === 'required' && + orderType !== Schema.OrderType.TYPE_MARKET + ) { + return { + isDisabled: true, + message: t('You need to provide a price'), + section: DEAL_TICKET_SECTION.PRICE, + }; + } + + if ( + fieldErrors?.price?.type === 'min' && + orderType !== Schema.OrderType.TYPE_MARKET + ) { + return { + isDisabled: true, + message: t(`The price cannot be negative`), + section: DEAL_TICKET_SECTION.PRICE, + }; + } + + if ( + fieldErrors?.size?.type === 'validate' && + fieldErrors?.size?.message === ERROR_SIZE_DECIMAL + ) { + if (market.positionDecimalPlaces === 0) { + return { + isDisabled: true, + message: t('Order sizes must be in whole numbers for this market'), + section: DEAL_TICKET_SECTION.SIZE, + }; + } + return { + isDisabled: true, + message: t( + `The size field accepts up to ${market.positionDecimalPlaces} decimal places` + ), + section: DEAL_TICKET_SECTION.SIZE, + }; + } + } + return null; + }, [ + fieldErrors?.size?.type, + fieldErrors?.size?.message, + fieldErrors?.price?.type, + minSize, + orderType, + market.positionDecimalPlaces, + ]); + const { message, isDisabled, section } = useMemo<{ message: ReactNode | string; isDisabled: boolean; @@ -104,6 +177,9 @@ export const useOrderValidation = ({ market.state ) ) { + if (fieldErrorChecking) { + return fieldErrorChecking; + } return { isDisabled: false, message: t( @@ -243,62 +319,8 @@ export const useOrderValidation = ({ } } - if (fieldErrors?.size?.type === 'required') { - return { - isDisabled: true, - message: t('You need to provide a size'), - section: DEAL_TICKET_SECTION.SIZE, - }; - } - - if (fieldErrors?.size?.type === 'min') { - return { - isDisabled: true, - message: t(`Size cannot be lower than "${minSize}"`), - section: DEAL_TICKET_SECTION.SIZE, - }; - } - - if ( - fieldErrors?.price?.type === 'required' && - orderType !== Schema.OrderType.TYPE_MARKET - ) { - return { - isDisabled: true, - message: t('You need to provide a price'), - section: DEAL_TICKET_SECTION.PRICE, - }; - } - - if ( - fieldErrors?.price?.type === 'min' && - orderType !== Schema.OrderType.TYPE_MARKET - ) { - return { - isDisabled: true, - message: t(`The price cannot be negative`), - section: DEAL_TICKET_SECTION.PRICE, - }; - } - - if ( - fieldErrors?.size?.type === 'validate' && - fieldErrors?.size?.message === ERROR_SIZE_DECIMAL - ) { - if (market.positionDecimalPlaces === 0) { - return { - isDisabled: true, - message: t('Order sizes must be in whole numbers for this market'), - section: DEAL_TICKET_SECTION.SIZE, - }; - } - return { - isDisabled: true, - message: t( - `The size field accepts up to ${market.positionDecimalPlaces} decimal places` - ), - section: DEAL_TICKET_SECTION.SIZE, - }; + if (fieldErrorChecking) { + return fieldErrorChecking; } if (isInvalidOrderMargin) { @@ -331,12 +353,9 @@ export const useOrderValidation = ({ section: '', }; }, [ - minSize, pubKey, market, - fieldErrors?.size?.type, - fieldErrors?.size?.message, - fieldErrors?.price?.type, + fieldErrorChecking, orderType, orderTimeInForce, isInvalidOrderMargin, diff --git a/libs/deal-ticket/src/components/deal-ticket-validation/validate-size.ts b/libs/deal-ticket/src/components/deal-ticket-validation/validate-size.ts index 1d078c5f7..c57d27640 100644 --- a/libs/deal-ticket/src/components/deal-ticket-validation/validate-size.ts +++ b/libs/deal-ticket/src/components/deal-ticket-validation/validate-size.ts @@ -1,4 +1,4 @@ -export const ERROR_SIZE_DECIMAL = 'step'; +import { ERROR_SIZE_DECIMAL } from '../constants'; export const validateSize = (step: number) => { const [, stepDecimals = ''] = String(step).split('.');