feat(utils): use i18next (#5269)

Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
This commit is contained in:
Bartłomiej Głownia 2023-11-19 23:00:45 +01:00 committed by GitHub
parent 5827b87f89
commit bb47747501
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 277 additions and 143 deletions

View File

@ -8,7 +8,7 @@ import {
doesValueEquateToParam, doesValueEquateToParam,
} from '@vegaprotocol/proposals'; } from '@vegaprotocol/proposals';
import { useEnvironment, DocsLinks } from '@vegaprotocol/environment'; import { useEnvironment, DocsLinks } from '@vegaprotocol/environment';
import { validateJson } from '@vegaprotocol/utils'; import { useValidateJson } from '@vegaprotocol/utils';
import { import {
NetworkParams, NetworkParams,
useNetworkParams, useNetworkParams,
@ -41,6 +41,7 @@ export interface NewAssetProposalFormFields {
const DOCS_LINK = '/new-asset-proposal'; const DOCS_LINK = '/new-asset-proposal';
export const ProposeNewAsset = () => { export const ProposeNewAsset = () => {
const validateJson = useValidateJson();
const { const {
params, params,
loading: networkParamsLoading, loading: networkParamsLoading,

View File

@ -7,7 +7,7 @@ import {
doesValueEquateToParam, doesValueEquateToParam,
} from '@vegaprotocol/proposals'; } from '@vegaprotocol/proposals';
import { useEnvironment, DocsLinks } from '@vegaprotocol/environment'; import { useEnvironment, DocsLinks } from '@vegaprotocol/environment';
import { validateJson } from '@vegaprotocol/utils'; import { useValidateJson } from '@vegaprotocol/utils';
import { import {
NetworkParams, NetworkParams,
useNetworkParams, useNetworkParams,
@ -39,6 +39,7 @@ export interface NewMarketProposalFormFields {
const DOCS_LINK = '/new-market-proposal'; const DOCS_LINK = '/new-market-proposal';
export const ProposeNewMarket = () => { export const ProposeNewMarket = () => {
const validateJson = useValidateJson();
const { const {
params, params,
loading: networkParamsLoading, loading: networkParamsLoading,

View File

@ -14,7 +14,7 @@ import {
RoundedWrapper, RoundedWrapper,
TextArea, TextArea,
} from '@vegaprotocol/ui-toolkit'; } from '@vegaprotocol/ui-toolkit';
import { validateJson } from '@vegaprotocol/utils'; import { useValidateJson } from '@vegaprotocol/utils';
import { import {
NetworkParams, NetworkParams,
useNetworkParams, useNetworkParams,
@ -31,6 +31,7 @@ export interface RawProposalFormFields {
} }
export const ProposeRaw = () => { export const ProposeRaw = () => {
const validateJson = useValidateJson();
const { const {
params, params,
loading: networkParamsLoading, loading: networkParamsLoading,

View File

@ -7,7 +7,7 @@ import {
doesValueEquateToParam, doesValueEquateToParam,
} from '@vegaprotocol/proposals'; } from '@vegaprotocol/proposals';
import { useEnvironment, DocsLinks } from '@vegaprotocol/environment'; import { useEnvironment, DocsLinks } from '@vegaprotocol/environment';
import { validateJson } from '@vegaprotocol/utils'; import { useValidateJson } from '@vegaprotocol/utils';
import { import {
NetworkParams, NetworkParams,
useNetworkParams, useNetworkParams,
@ -39,6 +39,7 @@ export interface UpdateAssetProposalFormFields {
const DOCS_LINK = '/update-asset-proposal'; const DOCS_LINK = '/update-asset-proposal';
export const ProposeUpdateAsset = () => { export const ProposeUpdateAsset = () => {
const validateJson = useValidateJson();
const { const {
params, params,
loading: networkParamsLoading, loading: networkParamsLoading,

View File

@ -8,7 +8,7 @@ import {
useProposalSubmit, useProposalSubmit,
} from '@vegaprotocol/proposals'; } from '@vegaprotocol/proposals';
import { useEnvironment, DocsLinks } from '@vegaprotocol/environment'; import { useEnvironment, DocsLinks } from '@vegaprotocol/environment';
import { validateJson } from '@vegaprotocol/utils'; import { useValidateJson } from '@vegaprotocol/utils';
import { import {
NetworkParams, NetworkParams,
useNetworkParams, useNetworkParams,
@ -53,6 +53,7 @@ export interface UpdateMarketProposalFormFields {
const DOCS_LINK = '/update-market-proposal'; const DOCS_LINK = '/update-market-proposal';
export const ProposeUpdateMarket = () => { export const ProposeUpdateMarket = () => {
const validateJson = useValidateJson();
const { const {
params, params,
loading: networkParamsLoading, loading: networkParamsLoading,
@ -260,7 +261,7 @@ export const ProposeUpdateMarket = () => {
</FormGroup> </FormGroup>
{selectedMarket && ( {selectedMarket && (
<div className="mt-[-20px] mb-6"> <div className="mb-6 mt-[-20px]">
<KeyValueTable data-testid="update-market-details"> <KeyValueTable data-testid="update-market-details">
<KeyValueTableRow> <KeyValueTableRow>
{t('MarketName')} {t('MarketName')}

View File

@ -1,8 +1,8 @@
import sortBy from 'lodash/sortBy'; import sortBy from 'lodash/sortBy';
import { import {
maxSafe, useMaxSafe,
required, useRequired,
vegaPublicKey, useVegaPublicKey,
addDecimal, addDecimal,
formatNumber, formatNumber,
addDecimalsFormatNumber, addDecimalsFormatNumber,
@ -67,6 +67,9 @@ export const TransferForm = ({
minQuantumMultiple, minQuantumMultiple,
}: TransferFormProps) => { }: TransferFormProps) => {
const t = useT(); const t = useT();
const maxSafe = useMaxSafe();
const required = useRequired();
const vegaPublicKey = useVegaPublicKey();
const { const {
control, control,
register, register,
@ -415,7 +418,7 @@ export const TransferForm = ({
{accountBalance && ( {accountBalance && (
<button <button
type="button" type="button"
className="absolute top-0 right-0 ml-auto text-xs underline" className="absolute right-0 top-0 ml-auto text-xs underline"
onClick={() => onClick={() =>
setValue('amount', parseFloat(accountBalance).toString(), { setValue('amount', parseFloat(accountBalance).toString(), {
shouldValidate: true, shouldValidate: true,
@ -491,7 +494,7 @@ export const TransferFee = ({
const totalValue = new BigNumber(transferAmount).plus(fee).toString(); const totalValue = new BigNumber(transferAmount).plus(fee).toString();
return ( return (
<div className="flex flex-col mb-4 text-xs gap-2"> <div className="mb-4 flex flex-col gap-2 text-xs">
<div className="flex flex-wrap items-center justify-between gap-1"> <div className="flex flex-wrap items-center justify-between gap-1">
<Tooltip <Tooltip
description={t( description={t(
@ -560,7 +563,7 @@ export const AddressField = ({
<button <button
type="button" type="button"
onClick={onChange} onClick={onChange}
className="absolute top-0 right-0 ml-auto text-xs underline" className="absolute right-0 top-0 ml-auto text-xs underline"
> >
{isInput ? t('Select from wallet') : t('Enter manually')} {isInput ? t('Select from wallet') : t('Enter manually')}
</button> </button>

View File

@ -1,7 +1,7 @@
import { Controller, type Control } from 'react-hook-form'; import { Controller, type Control } from 'react-hook-form';
import type { Market } from '@vegaprotocol/markets'; import type { Market } from '@vegaprotocol/markets';
import type { OrderFormValues } from '../../hooks/use-form-values'; import type { OrderFormValues } from '../../hooks/use-form-values';
import { toDecimal, validateAmount } from '@vegaprotocol/utils'; import { toDecimal, useValidateAmount } from '@vegaprotocol/utils';
import { import {
TradingFormGroup, TradingFormGroup,
TradingInput, TradingInput,
@ -28,6 +28,7 @@ export const DealTicketSizeIceberg = ({
peakSize, peakSize,
}: DealTicketSizeIcebergProps) => { }: DealTicketSizeIcebergProps) => {
const t = useT(); const t = useT();
const validateAmount = useValidateAmount();
const sizeStep = toDecimal(market?.positionDecimalPlaces); const sizeStep = toDecimal(market?.positionDecimalPlaces);
const renderPeakSizeError = () => { const renderPeakSizeError = () => {

View File

@ -9,7 +9,7 @@ import {
formatValue, formatValue,
removeDecimal, removeDecimal,
toDecimal, toDecimal,
validateAmount, useValidateAmount,
} from '@vegaprotocol/utils'; } from '@vegaprotocol/utils';
import { type Control, type UseFormWatch } from 'react-hook-form'; import { type Control, type UseFormWatch } from 'react-hook-form';
import { useForm, Controller, useController } from 'react-hook-form'; import { useForm, Controller, useController } from 'react-hook-form';
@ -109,6 +109,7 @@ const Trigger = ({
decimalPlaces: number; decimalPlaces: number;
}) => { }) => {
const t = useT(); const t = useT();
const validateAmount = useValidateAmount();
const triggerType = watch(oco ? 'ocoTriggerType' : 'triggerType'); const triggerType = watch(oco ? 'ocoTriggerType' : 'triggerType');
const triggerDirection = watch('triggerDirection'); const triggerDirection = watch('triggerDirection');
const isPriceTrigger = triggerType === 'price'; const isPriceTrigger = triggerType === 'price';
@ -341,6 +342,7 @@ const Size = ({
assetUnit?: string; assetUnit?: string;
}) => { }) => {
const t = useT(); const t = useT();
const validateAmount = useValidateAmount();
return ( return (
<Controller <Controller
name={oco ? 'ocoSize' : 'size'} name={oco ? 'ocoSize' : 'size'}
@ -401,6 +403,7 @@ const Price = ({
oco?: boolean; oco?: boolean;
}) => { }) => {
const t = useT(); const t = useT();
const validateAmount = useValidateAmount();
if (watch(oco ? 'ocoType' : 'type') === Schema.OrderType.TYPE_MARKET) { if (watch(oco ? 'ocoType' : 'type') === Schema.OrderType.TYPE_MARKET) {
return null; return null;
} }

View File

@ -28,7 +28,7 @@ import { useOpenVolume } from '@vegaprotocol/positions';
import { import {
toBigNum, toBigNum,
removeDecimal, removeDecimal,
validateAmount, useValidateAmount,
toDecimal, toDecimal,
formatForInput, formatForInput,
formatValue, formatValue,
@ -140,6 +140,7 @@ export const DealTicket = ({
onDeposit, onDeposit,
}: DealTicketProps) => { }: DealTicketProps) => {
const t = useT(); const t = useT();
const validateAmount = useValidateAmount();
const { pubKey, isReadOnly } = useVegaWallet(); const { pubKey, isReadOnly } = useVegaWallet();
const setType = useDealTicketFormValues((state) => state.setType); const setType = useDealTicketFormValues((state) => state.setType);
const storedFormValues = useDealTicketFormValues( const storedFormValues = useDealTicketFormValues(

View File

@ -1,11 +1,11 @@
import type { Asset, AssetFieldsFragment } from '@vegaprotocol/assets'; import type { Asset, AssetFieldsFragment } from '@vegaprotocol/assets';
import { AssetOption } from '@vegaprotocol/assets'; import { AssetOption } from '@vegaprotocol/assets';
import { import {
ethereumAddress, useEthereumAddress,
required, useRequired,
vegaPublicKey, useVegaPublicKey,
minSafe, useMinSafe,
maxSafe, useMaxSafe,
addDecimal, addDecimal,
isAssetTypeERC20, isAssetTypeERC20,
formatNumber, formatNumber,
@ -85,6 +85,11 @@ export const DepositForm = ({
isFaucetable, isFaucetable,
}: DepositFormProps) => { }: DepositFormProps) => {
const t = useT(); const t = useT();
const ethereumAddress = useEthereumAddress();
const required = useRequired();
const vegaPublicKey = useVegaPublicKey();
const minSafe = useMinSafe();
const maxSafe = useMaxSafe();
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore(); const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
const openDialog = useWeb3ConnectStore((store) => store.open); const openDialog = useWeb3ConnectStore((store) => store.open);
const { isActive, account } = useWeb3React(); const { isActive, account } = useWeb3React();
@ -459,7 +464,7 @@ const UseButton = (props: UseButtonProps) => {
<button <button
{...props} {...props}
type="button" type="button"
className="absolute top-0 right-0 ml-auto text-sm underline" className="absolute right-0 top-0 ml-auto text-sm underline"
/> />
); );
}; };
@ -519,7 +524,7 @@ export const AddressField = ({
setIsInput((curr) => !curr); setIsInput((curr) => !curr);
onChange(); onChange();
}} }}
className="absolute top-0 right-0 ml-auto text-sm underline" className="absolute right-0 top-0 ml-auto text-sm underline"
data-testid="enter-pubkey-manually" data-testid="enter-pubkey-manually"
> >
{isInput ? t('Select from wallet') : t('Enter manually')} {isInput ? t('Select from wallet') : t('Enter manually')}

View File

@ -0,0 +1,15 @@
{
"Expired on {{date}}": "Expired on {{date}}",
"Not time-based": "Not time-based",
"Expired": "Expired",
"Mark": "Mark",
"Required": "Required",
"Invalid Ethereum address": "Invalid Ethereum address",
"Invalid Vega key": "Invalid Vega key",
"Value is below minimum": "Value is below minimum",
"Value is above maximum": "Value is above maximum",
"Must be valid JSON": "Must be valid JSON",
"{{field}} must be a multiple of {{step}} for this market": "{{field}} must be a multiple of {{step}} for this market",
"{{field}} must be whole numbers for this market": "{{field}} must be whole numbers for this market",
"{{field}} accepts up to {{decimals}} decimal places": "{{field}} accepts up to {{decimals}} decimal places"
}

View File

@ -3,7 +3,7 @@ import {
getDateTimeFormat, getDateTimeFormat,
addDecimal, addDecimal,
addDecimalsFormatNumber, addDecimalsFormatNumber,
validateAmount, useValidateAmount,
} from '@vegaprotocol/utils'; } from '@vegaprotocol/utils';
import { Size } from '@vegaprotocol/datagrid'; import { Size } from '@vegaprotocol/datagrid';
import * as Schema from '@vegaprotocol/types'; import * as Schema from '@vegaprotocol/types';
@ -39,6 +39,7 @@ export const OrderEditDialog = ({
onSubmit, onSubmit,
}: OrderEditDialogProps) => { }: OrderEditDialogProps) => {
const t = useT(); const t = useT();
const validateAmount = useValidateAmount();
const headerClassName = 'text-xs font-bold text-black dark:text-white'; const headerClassName = 'text-xs font-bold text-black dark:text-white';
const { const {
register, register,

View File

@ -3,7 +3,7 @@ import {
getDateTimeFormat, getDateTimeFormat,
isNumeric, isNumeric,
toBigNum, toBigNum,
formatTrigger, useFormatTrigger,
} from '@vegaprotocol/utils'; } from '@vegaprotocol/utils';
import * as Schema from '@vegaprotocol/types'; import * as Schema from '@vegaprotocol/types';
import { import {
@ -52,6 +52,7 @@ export type StopOrdersTableProps = TypedDataAgGrid<StopOrder> & {
export const StopOrdersTable = memo( export const StopOrdersTable = memo(
({ onCancel, onMarketClick, onView, ...props }: StopOrdersTableProps) => { ({ onCancel, onMarketClick, onView, ...props }: StopOrdersTableProps) => {
const t = useT(); const t = useT();
const formatTrigger = useFormatTrigger();
const showAllActions = !props.isReadOnly; const showAllActions = !props.isReadOnly;
const columnDefs: ColDef[] = useMemo( const columnDefs: ColDef[] = useMemo(
() => [ () => [
@ -282,7 +283,15 @@ export const StopOrdersTable = memo(
}, },
}, },
], ],
[onCancel, onMarketClick, onView, props.isReadOnly, showAllActions, t] [
onCancel,
onMarketClick,
onView,
props.isReadOnly,
showAllActions,
t,
formatTrigger,
]
); );
return ( return (

View File

@ -0,0 +1,14 @@
export const useTranslation = () => ({
t: (label: string, replacements?: Record<string, string>) => {
let translatedLabel = label;
if (typeof replacements === 'object' && replacements !== null) {
Object.keys(replacements).forEach((key) => {
translatedLabel = translatedLabel.replace(
`{{${key}}}`,
replacements[key]
);
});
}
return translatedLabel;
},
});

View File

@ -1,27 +1,37 @@
import * as Schema from '@vegaprotocol/types'; import * as Schema from '@vegaprotocol/types';
import { t } from '@vegaprotocol/i18n';
import { addDecimalsFormatNumber } from './number'; import { addDecimalsFormatNumber } from './number';
import { useCallback } from 'react';
import { useT } from '../use-t';
export const formatTrigger = ( export const useFormatTrigger = () => {
data: Pick<Schema.StopOrder, 'trigger' | 'triggerDirection'> | undefined, const t = useT();
marketDecimalPlaces: number, return useCallback(
defaultValue = '-' (
) => { data: Pick<Schema.StopOrder, 'trigger' | 'triggerDirection'> | undefined,
if (data && data?.trigger?.__typename === 'StopOrderPrice') { marketDecimalPlaces: number,
return `${t('Mark')} ${ defaultValue = '-'
data?.triggerDirection === ) => {
Schema.StopOrderTriggerDirection.TRIGGER_DIRECTION_FALLS_BELOW if (data && data?.trigger?.__typename === 'StopOrderPrice') {
? '<' return `${t('Mark')} ${
: '>' data?.triggerDirection ===
} ${addDecimalsFormatNumber(data.trigger.price, marketDecimalPlaces)}`; Schema.StopOrderTriggerDirection.TRIGGER_DIRECTION_FALLS_BELOW
} ? '<'
if (data && data?.trigger?.__typename === 'StopOrderTrailingPercentOffset') { : '>'
return `${t('Mark')} ${ } ${addDecimalsFormatNumber(data.trigger.price, marketDecimalPlaces)}`;
data?.triggerDirection === }
Schema.StopOrderTriggerDirection.TRIGGER_DIRECTION_FALLS_BELOW if (
? '+' data &&
: '-' data?.trigger?.__typename === 'StopOrderTrailingPercentOffset'
}${(Number(data?.trigger.trailingPercentOffset) * 100).toFixed(1)}%`; ) {
} return `${t('Mark')} ${
return defaultValue; data?.triggerDirection ===
Schema.StopOrderTriggerDirection.TRIGGER_DIRECTION_FALLS_BELOW
? '+'
: '-'
}${(Number(data?.trigger.trailingPercentOffset) * 100).toFixed(1)}%`;
}
return defaultValue;
},
[t]
);
}; };

View File

@ -1,7 +1,7 @@
import { MarketState } from '@vegaprotocol/types'; import { MarketState } from '@vegaprotocol/types';
import { t } from '@vegaprotocol/i18n';
import { isValid, parseISO } from 'date-fns'; import { isValid, parseISO } from 'date-fns';
import { getDateTimeFormat } from './format'; import { getDateTimeFormat } from './format';
import { useT } from './use-t';
export const getMarketExpiryDate = ( export const getMarketExpiryDate = (
tags?: ReadonlyArray<string> | null tags?: ReadonlyArray<string> | null
@ -40,12 +40,15 @@ export const getExpiryDate = (
close: string | null, close: string | null,
state: MarketState state: MarketState
): string => { ): string => {
const t = useT();
const metadataExpiryDate = getMarketExpiryDate(tags); const metadataExpiryDate = getMarketExpiryDate(tags);
const marketTimestampCloseDate = close && new Date(close); const marketTimestampCloseDate = close && new Date(close);
let content = null; let content = null;
if (!metadataExpiryDate) { if (!metadataExpiryDate) {
content = marketTimestampCloseDate content = marketTimestampCloseDate
? `Expired on ${getDateTimeFormat().format(marketTimestampCloseDate)}` ? t('Expired on {{date}}', {
date: getDateTimeFormat().format(marketTimestampCloseDate),
})
: t('Not time-based'); : t('Not time-based');
} else { } else {
const isExpired = const isExpired =
@ -54,7 +57,9 @@ export const getExpiryDate = (
state === MarketState.STATE_SETTLED); state === MarketState.STATE_SETTLED);
if (isExpired) { if (isExpired) {
content = marketTimestampCloseDate content = marketTimestampCloseDate
? `Expired on ${getDateTimeFormat().format(marketTimestampCloseDate)}` ? t('Expired on {{date}}', {
date: getDateTimeFormat().format(marketTimestampCloseDate),
})
: t('Expired'); : t('Expired');
} else { } else {
content = getDateTimeFormat().format(metadataExpiryDate); content = getDateTimeFormat().format(metadataExpiryDate);

View File

@ -0,0 +1,3 @@
import { useTranslation } from 'react-i18next';
export const ns = 'utils';
export const useT = () => useTranslation(ns).t;

View File

@ -1,6 +1,10 @@
import { ethereumAddress, vegaPublicKey } from './common'; import { renderHook } from '@testing-library/react';
import { useEthereumAddress, useVegaPublicKey } from './common';
it('ethereumAddress', () => { it('ethereumAddress', () => {
const result = renderHook(useEthereumAddress);
const ethereumAddress = result.result.current;
const errorMessage = 'Invalid Ethereum address'; const errorMessage = 'Invalid Ethereum address';
const validAddress = '0x72c22822A19D20DE7e426fB84aa047399Ddd8853'; const validAddress = '0x72c22822A19D20DE7e426fB84aa047399Ddd8853';
@ -17,6 +21,9 @@ it('ethereumAddress', () => {
}); });
it('vegaPublicKey', () => { it('vegaPublicKey', () => {
const result = renderHook(useVegaPublicKey);
const vegaPublicKey = result.result.current;
const errorMessage = 'Invalid Vega key'; const errorMessage = 'Invalid Vega key';
const validKey = const validKey =

View File

@ -1,40 +1,71 @@
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import { t } from '@vegaprotocol/i18n'; import { useT } from '../use-t';
import { useCallback } from 'react';
export const required = (value: string) => { export const useRequired = () => {
if (value === null || value === undefined || value === '') { const t = useT();
return t('Required'); return useCallback(
} (value: string) => {
return true; if (value === null || value === undefined || value === '') {
return t('Required');
}
return true;
},
[t]
);
}; };
export const ethereumAddress = (value: string) => { export const useEthereumAddress = () => {
if (!/^0x[0-9a-fA-F]{40}$/i.test(value)) { const t = useT();
return t('Invalid Ethereum address'); return useCallback(
} (value: string) => {
return true; if (!/^0x[0-9a-fA-F]{40}$/i.test(value)) {
return t('Invalid Ethereum address');
}
return true;
},
[t]
);
}; };
export const VEGA_ID_REGEX = /^[A-Fa-f0-9]{64}$/i; export const VEGA_ID_REGEX = /^[A-Fa-f0-9]{64}$/i;
export const vegaPublicKey = (value: string) => { export const useVegaPublicKey = () => {
if (!VEGA_ID_REGEX.test(value)) { const t = useT();
return t('Invalid Vega key'); return useCallback(
} (value: string) => {
return true; if (!VEGA_ID_REGEX.test(value)) {
return t('Invalid Vega key');
}
return true;
},
[t]
);
}; };
export const minSafe = (min: BigNumber) => (value: string) => { export const useMinSafe = () => {
if (new BigNumber(value).isLessThan(min)) { const t = useT();
return t('Value is below minimum'); return useCallback(
} (min: BigNumber) => (value: string) => {
return true; if (new BigNumber(value).isLessThan(min)) {
return t('Value is below minimum');
}
return true;
},
[t]
);
}; };
export const maxSafe = (max: BigNumber) => (value: string) => { export const useMaxSafe = () => {
if (new BigNumber(value).isGreaterThan(max)) { const t = useT();
return t('Value is above maximum'); return useCallback(
} (max: BigNumber) => (value: string) => {
return true; if (new BigNumber(value).isGreaterThan(max)) {
return t('Value is above maximum');
}
return true;
},
[t]
);
}; };
export const suitableForSyntaxHighlighter = (str: string) => { export const suitableForSyntaxHighlighter = (str: string) => {
@ -46,11 +77,17 @@ export const suitableForSyntaxHighlighter = (str: string) => {
} }
}; };
export const validateJson = (value: string) => { export const useValidateJson = () => {
try { const t = useT();
JSON.parse(value); return useCallback(
return true; (value: string) => {
} catch (e) { try {
return t('Must be valid JSON'); JSON.parse(value);
} return true;
} catch (e) {
return t('Must be valid JSON');
}
},
[t]
);
}; };

View File

@ -1,22 +1,40 @@
import { t } from '@vegaprotocol/i18n'; import { useCallback } from 'react';
import { useT } from '../use-t';
export const validateAmount = (step: number | string, field: string) => { export const useValidateAmount = () => {
const [, stepDecimals = ''] = String(step).split('.'); const t = useT();
return useCallback(
(step: number | string, field: string) => {
const [, stepDecimals = ''] = String(step).split('.');
return (value?: string) => { return (value?: string) => {
if (Number(step) > 1) { if (Number(step) > 1) {
if (Number(value) % Number(step) > 0) { if (Number(value) % Number(step) > 0) {
return t(`${field} must be a multiple of ${step} for this market`); return t(
} '{{field}} must be a multiple of {{step}} for this market',
return true; {
} field,
const [, valueDecimals = ''] = (value || '').split('.'); step,
if (stepDecimals.length < valueDecimals.length) { }
if (stepDecimals === '') { );
return t(`${field} must be whole numbers for this market`); }
} return true;
return t(`${field} accepts up to ${stepDecimals.length} decimal places`); }
} const [, valueDecimals = ''] = (value || '').split('.');
return true; if (stepDecimals.length < valueDecimals.length) {
}; if (stepDecimals === '') {
return t('{{field}} must be whole numbers for this market', {
field,
});
}
return t('{{field}} accepts up to {{decimals}} decimal places', {
field,
decimals: stepDecimals.length,
});
}
return true;
};
},
[t]
);
}; };

View File

@ -40,7 +40,7 @@ import {
formatNumber, formatNumber,
toBigNum, toBigNum,
truncateByChars, truncateByChars,
formatTrigger, useFormatTrigger,
MAXGOINT64, MAXGOINT64,
} from '@vegaprotocol/utils'; } from '@vegaprotocol/utils';
import { useAssetsMapProvider } from '@vegaprotocol/assets'; import { useAssetsMapProvider } from '@vegaprotocol/assets';
@ -260,6 +260,7 @@ const SubmitStopOrderSetup = ({
triggerDirection: Schema.StopOrderTriggerDirection; triggerDirection: Schema.StopOrderTriggerDirection;
market: Market; market: Market;
}) => { }) => {
const formatTrigger = useFormatTrigger();
if (!market || !stopOrderSetup) return null; if (!market || !stopOrderSetup) return null;
const { price, size, side } = stopOrderSetup.orderSubmission; const { price, size, side } = stopOrderSetup.orderSubmission;
@ -446,6 +447,7 @@ const CancelOrderDetails = ({
const CancelStopOrderDetails = ({ stopOrderId }: { stopOrderId: string }) => { const CancelStopOrderDetails = ({ stopOrderId }: { stopOrderId: string }) => {
const t = useT(); const t = useT();
const formatTrigger = useFormatTrigger();
const { data: orderById } = useStopOrderByIdQuery({ const { data: orderById } = useStopOrderByIdQuery({
variables: { stopOrderId }, variables: { stopOrderId },
}); });
@ -732,7 +734,7 @@ const VegaTxCompleteToastsContent = ({ tx }: VegaTxToastContentProps) => {
<p>{t('Your funds have been unlocked for withdrawal.')}</p> <p>{t('Your funds have been unlocked for withdrawal.')}</p>
{tx.txHash && ( {tx.txHash && (
<ExternalLink <ExternalLink
className="block mb-[5px] break-all" className="mb-[5px] block break-all"
href={explorerLink(EXPLORER_TX.replace(':hash', tx.txHash))} href={explorerLink(EXPLORER_TX.replace(':hash', tx.txHash))}
rel="noreferrer" rel="noreferrer"
> >

View File

@ -1,10 +1,10 @@
import type { Asset } from '@vegaprotocol/assets'; import type { Asset } from '@vegaprotocol/assets';
import { AssetOption } from '@vegaprotocol/assets'; import { AssetOption } from '@vegaprotocol/assets';
import { import {
ethereumAddress, useEthereumAddress,
minSafe, useRequired,
useMinSafe,
removeDecimal, removeDecimal,
required,
isAssetTypeERC20, isAssetTypeERC20,
formatNumber, formatNumber,
} from '@vegaprotocol/utils'; } from '@vegaprotocol/utils';
@ -23,7 +23,6 @@ import {
import { useWeb3React } from '@web3-react/core'; import { useWeb3React } from '@web3-react/core';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import { useEffect, type ButtonHTMLAttributes } from 'react'; import { useEffect, type ButtonHTMLAttributes } from 'react';
import type { ControllerRenderProps } from 'react-hook-form';
import { formatDistanceToNow } from 'date-fns'; import { formatDistanceToNow } from 'date-fns';
import { useForm, Controller, useWatch } from 'react-hook-form'; import { useForm, Controller, useWatch } from 'react-hook-form';
import { WithdrawLimits } from './withdraw-limits'; import { WithdrawLimits } from './withdraw-limits';
@ -112,6 +111,10 @@ export const WithdrawForm = ({
onSelectAsset, onSelectAsset,
submitWithdraw, submitWithdraw,
}: WithdrawFormProps) => { }: WithdrawFormProps) => {
const ethereumAddress = useEthereumAddress();
const required = useRequired();
const minSafe = useMinSafe();
const { account: address } = useWeb3React(); const { account: address } = useWeb3React();
const { const {
register, register,
@ -150,36 +153,6 @@ export const WithdrawForm = ({
trigger('to'); trigger('to');
}, [address, setValue, trigger]); }, [address, setValue, trigger]);
const renderAssetsSelector = ({
field,
}: {
field: ControllerRenderProps<FormFields, 'asset'>;
}) => {
return (
<TradingRichSelect
data-testid="select-asset"
id="asset"
name="asset"
required
onValueChange={(value) => {
onSelectAsset(value);
field.onChange(value);
}}
placeholder={t('Please select an asset')}
value={selectedAsset?.id}
hasError={Boolean(errors.asset?.message)}
>
{assets.filter(isAssetTypeERC20).map((a) => (
<AssetOption
key={a.id}
asset={a}
balance={<AssetBalance asset={a} />}
/>
))}
</TradingRichSelect>
);
};
const showWithdrawDelayNotification = const showWithdrawDelayNotification =
Boolean(delay) && Boolean(delay) &&
Boolean(selectedAsset) && Boolean(selectedAsset) &&
@ -189,7 +162,7 @@ export const WithdrawForm = ({
<> <>
<div className="mb-4 text-sm"> <div className="mb-4 text-sm">
<p>{t('There are two steps required to make a withdrawal')}</p> <p>{t('There are two steps required to make a withdrawal')}</p>
<ol className="pl-4 list-disc"> <ol className="list-disc pl-4">
<li>{t('Step 1 - Release funds from Vega')}</li> <li>{t('Step 1 - Release funds from Vega')}</li>
<li>{t('Step 2 - Transfer funds to your Ethereum wallet')}</li> <li>{t('Step 2 - Transfer funds to your Ethereum wallet')}</li>
</ol> </ol>
@ -208,7 +181,29 @@ export const WithdrawForm = ({
required: (value) => !!selectedAsset || required(value), required: (value) => !!selectedAsset || required(value),
}, },
}} }}
render={renderAssetsSelector} render={({ field }) => (
<TradingRichSelect
data-testid="select-asset"
id="asset"
name="asset"
required
onValueChange={(value) => {
onSelectAsset(value);
field.onChange(value);
}}
placeholder={t('Please select an asset')}
value={selectedAsset?.id}
hasError={Boolean(errors.asset?.message)}
>
{assets.filter(isAssetTypeERC20).map((a) => (
<AssetOption
key={a.id}
asset={a}
balance={<AssetBalance asset={a} />}
/>
))}
</TradingRichSelect>
)}
/> />
{errors.asset?.message && ( {errors.asset?.message && (
<TradingInputError intent="danger"> <TradingInputError intent="danger">
@ -314,7 +309,7 @@ const UseButton = (props: UseButtonProps) => {
<button <button
{...props} {...props}
type="button" type="button"
className="absolute top-0 right-0 ml-auto text-sm underline" className="absolute right-0 top-0 ml-auto text-sm underline"
/> />
); );
}; };