Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a87a4a1af5 | ||
|
|
ddc62e6913 | ||
|
|
19b95832c4 | ||
|
|
48e4ab53a1 | ||
|
|
cefcff040f | ||
|
|
c3ef639daf |
@@ -8,7 +8,7 @@ import {
|
||||
doesValueEquateToParam,
|
||||
} from '@vegaprotocol/proposals';
|
||||
import { useEnvironment, DocsLinks } from '@vegaprotocol/environment';
|
||||
import { validateJson } from '@vegaprotocol/utils';
|
||||
import { useValidateJson } from '@vegaprotocol/utils';
|
||||
import {
|
||||
NetworkParams,
|
||||
useNetworkParams,
|
||||
@@ -41,6 +41,7 @@ export interface NewAssetProposalFormFields {
|
||||
const DOCS_LINK = '/new-asset-proposal';
|
||||
|
||||
export const ProposeNewAsset = () => {
|
||||
const validateJson = useValidateJson();
|
||||
const {
|
||||
params,
|
||||
loading: networkParamsLoading,
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
doesValueEquateToParam,
|
||||
} from '@vegaprotocol/proposals';
|
||||
import { useEnvironment, DocsLinks } from '@vegaprotocol/environment';
|
||||
import { validateJson } from '@vegaprotocol/utils';
|
||||
import { useValidateJson } from '@vegaprotocol/utils';
|
||||
import {
|
||||
NetworkParams,
|
||||
useNetworkParams,
|
||||
@@ -39,6 +39,7 @@ export interface NewMarketProposalFormFields {
|
||||
const DOCS_LINK = '/new-market-proposal';
|
||||
|
||||
export const ProposeNewMarket = () => {
|
||||
const validateJson = useValidateJson();
|
||||
const {
|
||||
params,
|
||||
loading: networkParamsLoading,
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
RoundedWrapper,
|
||||
TextArea,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { validateJson } from '@vegaprotocol/utils';
|
||||
import { useValidateJson } from '@vegaprotocol/utils';
|
||||
import {
|
||||
NetworkParams,
|
||||
useNetworkParams,
|
||||
@@ -31,6 +31,7 @@ export interface RawProposalFormFields {
|
||||
}
|
||||
|
||||
export const ProposeRaw = () => {
|
||||
const validateJson = useValidateJson();
|
||||
const {
|
||||
params,
|
||||
loading: networkParamsLoading,
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
doesValueEquateToParam,
|
||||
} from '@vegaprotocol/proposals';
|
||||
import { useEnvironment, DocsLinks } from '@vegaprotocol/environment';
|
||||
import { validateJson } from '@vegaprotocol/utils';
|
||||
import { useValidateJson } from '@vegaprotocol/utils';
|
||||
import {
|
||||
NetworkParams,
|
||||
useNetworkParams,
|
||||
@@ -39,6 +39,7 @@ export interface UpdateAssetProposalFormFields {
|
||||
const DOCS_LINK = '/update-asset-proposal';
|
||||
|
||||
export const ProposeUpdateAsset = () => {
|
||||
const validateJson = useValidateJson();
|
||||
const {
|
||||
params,
|
||||
loading: networkParamsLoading,
|
||||
|
||||
+3
-2
@@ -8,7 +8,7 @@ import {
|
||||
useProposalSubmit,
|
||||
} from '@vegaprotocol/proposals';
|
||||
import { useEnvironment, DocsLinks } from '@vegaprotocol/environment';
|
||||
import { validateJson } from '@vegaprotocol/utils';
|
||||
import { useValidateJson } from '@vegaprotocol/utils';
|
||||
import {
|
||||
NetworkParams,
|
||||
useNetworkParams,
|
||||
@@ -53,6 +53,7 @@ export interface UpdateMarketProposalFormFields {
|
||||
const DOCS_LINK = '/update-market-proposal';
|
||||
|
||||
export const ProposeUpdateMarket = () => {
|
||||
const validateJson = useValidateJson();
|
||||
const {
|
||||
params,
|
||||
loading: networkParamsLoading,
|
||||
@@ -260,7 +261,7 @@ export const ProposeUpdateMarket = () => {
|
||||
</FormGroup>
|
||||
|
||||
{selectedMarket && (
|
||||
<div className="mt-[-20px] mb-6">
|
||||
<div className="mb-6 mt-[-20px]">
|
||||
<KeyValueTable data-testid="update-market-details">
|
||||
<KeyValueTableRow>
|
||||
{t('MarketName')}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import sortBy from 'lodash/sortBy';
|
||||
import {
|
||||
maxSafe,
|
||||
required,
|
||||
vegaPublicKey,
|
||||
useMaxSafe,
|
||||
useRequired,
|
||||
useVegaPublicKey,
|
||||
addDecimal,
|
||||
formatNumber,
|
||||
addDecimalsFormatNumber,
|
||||
@@ -67,6 +67,9 @@ export const TransferForm = ({
|
||||
minQuantumMultiple,
|
||||
}: TransferFormProps) => {
|
||||
const t = useT();
|
||||
const maxSafe = useMaxSafe();
|
||||
const required = useRequired();
|
||||
const vegaPublicKey = useVegaPublicKey();
|
||||
const {
|
||||
control,
|
||||
register,
|
||||
@@ -415,7 +418,7 @@ export const TransferForm = ({
|
||||
{accountBalance && (
|
||||
<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={() =>
|
||||
setValue('amount', parseFloat(accountBalance).toString(), {
|
||||
shouldValidate: true,
|
||||
@@ -491,7 +494,7 @@ export const TransferFee = ({
|
||||
const totalValue = new BigNumber(transferAmount).plus(fee).toString();
|
||||
|
||||
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">
|
||||
<Tooltip
|
||||
description={t(
|
||||
@@ -560,7 +563,7 @@ export const AddressField = ({
|
||||
<button
|
||||
type="button"
|
||||
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')}
|
||||
</button>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Controller, type Control } from 'react-hook-form';
|
||||
import type { Market } from '@vegaprotocol/markets';
|
||||
import type { OrderFormValues } from '../../hooks/use-form-values';
|
||||
import { toDecimal, validateAmount } from '@vegaprotocol/utils';
|
||||
import { toDecimal, useValidateAmount } from '@vegaprotocol/utils';
|
||||
import {
|
||||
TradingFormGroup,
|
||||
TradingInput,
|
||||
@@ -28,6 +28,7 @@ export const DealTicketSizeIceberg = ({
|
||||
peakSize,
|
||||
}: DealTicketSizeIcebergProps) => {
|
||||
const t = useT();
|
||||
const validateAmount = useValidateAmount();
|
||||
const sizeStep = toDecimal(market?.positionDecimalPlaces);
|
||||
|
||||
const renderPeakSizeError = () => {
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
formatValue,
|
||||
removeDecimal,
|
||||
toDecimal,
|
||||
validateAmount,
|
||||
useValidateAmount,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { type Control, type UseFormWatch } from 'react-hook-form';
|
||||
import { useForm, Controller, useController } from 'react-hook-form';
|
||||
@@ -109,6 +109,7 @@ const Trigger = ({
|
||||
decimalPlaces: number;
|
||||
}) => {
|
||||
const t = useT();
|
||||
const validateAmount = useValidateAmount();
|
||||
const triggerType = watch(oco ? 'ocoTriggerType' : 'triggerType');
|
||||
const triggerDirection = watch('triggerDirection');
|
||||
const isPriceTrigger = triggerType === 'price';
|
||||
@@ -341,6 +342,7 @@ const Size = ({
|
||||
assetUnit?: string;
|
||||
}) => {
|
||||
const t = useT();
|
||||
const validateAmount = useValidateAmount();
|
||||
return (
|
||||
<Controller
|
||||
name={oco ? 'ocoSize' : 'size'}
|
||||
@@ -401,6 +403,7 @@ const Price = ({
|
||||
oco?: boolean;
|
||||
}) => {
|
||||
const t = useT();
|
||||
const validateAmount = useValidateAmount();
|
||||
if (watch(oco ? 'ocoType' : 'type') === Schema.OrderType.TYPE_MARKET) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ import { useOpenVolume } from '@vegaprotocol/positions';
|
||||
import {
|
||||
toBigNum,
|
||||
removeDecimal,
|
||||
validateAmount,
|
||||
useValidateAmount,
|
||||
toDecimal,
|
||||
formatForInput,
|
||||
formatValue,
|
||||
@@ -140,6 +140,7 @@ export const DealTicket = ({
|
||||
onDeposit,
|
||||
}: DealTicketProps) => {
|
||||
const t = useT();
|
||||
const validateAmount = useValidateAmount();
|
||||
const { pubKey, isReadOnly } = useVegaWallet();
|
||||
const setType = useDealTicketFormValues((state) => state.setType);
|
||||
const storedFormValues = useDealTicketFormValues(
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import type { Asset, AssetFieldsFragment } from '@vegaprotocol/assets';
|
||||
import { AssetOption } from '@vegaprotocol/assets';
|
||||
import {
|
||||
ethereumAddress,
|
||||
required,
|
||||
vegaPublicKey,
|
||||
minSafe,
|
||||
maxSafe,
|
||||
useEthereumAddress,
|
||||
useRequired,
|
||||
useVegaPublicKey,
|
||||
useMinSafe,
|
||||
useMaxSafe,
|
||||
addDecimal,
|
||||
isAssetTypeERC20,
|
||||
formatNumber,
|
||||
@@ -85,6 +85,11 @@ export const DepositForm = ({
|
||||
isFaucetable,
|
||||
}: DepositFormProps) => {
|
||||
const t = useT();
|
||||
const ethereumAddress = useEthereumAddress();
|
||||
const required = useRequired();
|
||||
const vegaPublicKey = useVegaPublicKey();
|
||||
const minSafe = useMinSafe();
|
||||
const maxSafe = useMaxSafe();
|
||||
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
|
||||
const openDialog = useWeb3ConnectStore((store) => store.open);
|
||||
const { isActive, account } = useWeb3React();
|
||||
@@ -459,7 +464,7 @@ const UseButton = (props: UseButtonProps) => {
|
||||
<button
|
||||
{...props}
|
||||
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);
|
||||
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"
|
||||
>
|
||||
{isInput ? t('Select from wallet') : t('Enter manually')}
|
||||
|
||||
@@ -10,8 +10,10 @@ import en_fills from './locales/en/fills.json';
|
||||
import en_funding_payments from './locales/en/funding-payments.json';
|
||||
import en_governance from './locales/en/governance.json';
|
||||
import en_trading from './locales/en/trading.json';
|
||||
|
||||
import en_markets from './locales/en/markets.json';
|
||||
import en_web3 from './locales/en/web3.json';
|
||||
|
||||
import en_positions from './locales/en/positions.json';
|
||||
export const locales = {
|
||||
en: {
|
||||
accounts: en_accounts,
|
||||
@@ -26,5 +28,7 @@ export const locales = {
|
||||
governance: en_governance,
|
||||
trading: en_trading,
|
||||
markets: en_markets,
|
||||
web3: en_web3,
|
||||
positions: en_positions,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"Last traded price": "Last traded price",
|
||||
"No open orders": "No open orders",
|
||||
"Spread": "Spread"
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"Best case": "Best case",
|
||||
"Close position": "Close position",
|
||||
"Entry / Mark": "Entry / Mark",
|
||||
"Lifetime loss socialisation deductions: {{losses}}": "Lifetime loss socialisation deductions: {{losses}}",
|
||||
"Maintained by network": "Maintained by network",
|
||||
"Margin / Leverage": "Margin / Leverage",
|
||||
"Market": "Market",
|
||||
"No positions": "No positions",
|
||||
"Profit or loss is realised whenever your position is reduced to zero and the margin is released back to your collateral balance. P&L excludes any fees paid.": "Profit or loss is realised whenever your position is reduced to zero and the margin is released back to your collateral balance. P&L excludes any fees paid.",
|
||||
"Read more about loss socialisation": "Read more about loss socialisation",
|
||||
"Read more about position resolution": "Read more about position resolution",
|
||||
"Realised PNL": "Realised PNL",
|
||||
"Realised PNL: {{value}}": "Realised PNL: {{value}}",
|
||||
"Size / Notional": "Size / Notional",
|
||||
"Status: {{status}}": "Status: {{status}}",
|
||||
"The position was distressed, but could not be closed out - orders were removed from the book, and the open volume will be closed out once there is sufficient volume on the book.": "The position was distressed, but could not be closed out - orders were removed from the book, and the open volume will be closed out once there is sufficient volume on the book.",
|
||||
"The position was distressed, but removing open orders from the book brought the margin level back to a point where the open position could be maintained.": "The position was distressed, but removing open orders from the book brought the margin level back to a point where the open position could be maintained.",
|
||||
"Unrealised PNL": "Unrealised PNL",
|
||||
"Unrealised profit is the current profit on your open position. Margin is still allocated to your position.": "Unrealised profit is the current profit on your open position. Margin is still allocated to your position.",
|
||||
"Vega key": "Vega key",
|
||||
"View settlement asset details": "View settlement asset details",
|
||||
"Worst case": "Worst case",
|
||||
"Worst case liquidation price": "Worst case liquidation price",
|
||||
"You did not have enough {{assetSymbol}} collateral to meet the maintenance margin requirements for your position, so it was closed by the network.": "You did not have enough {{assetSymbol}} collateral to meet the maintenance margin requirements for your position, so it was closed by the network.",
|
||||
"You received less {{assetSymbol}} in gains that you should have when the market moved in your favour. This occurred because one or more other trader(s) were closed out and did not have enough funds to cover their losses, and the market's insurance pool was empty.": "You received less {{assetSymbol}} in gains that you should have when the market moved in your favour. This occurred because one or more other trader(s) were closed out and did not have enough funds to cover their losses, and the market's insurance pool was empty.",
|
||||
"Your open orders were cancelled.": "Your open orders were cancelled.",
|
||||
"Your position is distressed.": "Your position is distressed.",
|
||||
"Your position was closed.": "Your position was closed."
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
{
|
||||
"{{title}} complete": "{{title}} complete",
|
||||
"{{title}} failed": "{{title}} failed",
|
||||
"{{title}} pending": "{{title}} pending",
|
||||
"Action required": "Action required",
|
||||
"Approved": "Approved",
|
||||
"Await Ethereum transaction": "Await Ethereum transaction",
|
||||
"Awaiting confirmation": "Awaiting confirmation",
|
||||
"Awaiting confirmations {{confirmations}}/{[requiredConfirmations}}": "Awaiting confirmations {{confirmations}}/{[requiredConfirmations}}",
|
||||
"Awaiting Ethereum transaction {{confirmations}}/{{requiredConfirmations}} confirmations...": "Awaiting Ethereum transaction {{confirmations}}/{{requiredConfirmations}} confirmations...",
|
||||
"Batch market instruction": "Batch market instruction",
|
||||
"Cancel all orders": "Cancel all orders",
|
||||
"Cancel all orders for <strong>{{marketName}}</strong>": "Cancel all orders for <strong>{{marketName}}</strong>",
|
||||
"Cancel all stop orders": "Cancel all stop orders",
|
||||
"Cancel all stop orders for <strong>{{marketName}}</strong>": "Cancel all stop orders for <strong>{{marketName}}</strong>",
|
||||
"Cancel order": "Cancel order",
|
||||
"Cancel order - {{status}}": "Cancel order - {{status}}",
|
||||
"Cancel stop order": "Cancel stop order",
|
||||
"Cannot be completed until {{time}}": "Cannot be completed until {{time}}",
|
||||
"Change network": "Change network",
|
||||
"Close": "Close",
|
||||
"Close position for <strong>{{marketName}}</strong>": "Close position for <strong>{{marketName}}</strong>",
|
||||
"Coinbase": "Coinbase",
|
||||
"Complete withdrawal": "Complete withdrawal",
|
||||
"Confirm transaction": "Confirm transaction",
|
||||
"Confirm transaction in wallet": "Confirm transaction in wallet",
|
||||
"Confirmed": "Confirmed",
|
||||
"Confirmed in wallet": "Confirmed in wallet",
|
||||
"Connect to your Ethereum wallet": "Connect to your Ethereum wallet",
|
||||
"Connect wallet": "Connect wallet",
|
||||
"Connect wallet to withdraw": "Connect wallet to withdraw",
|
||||
"Copy": "Copy",
|
||||
"Delayed": "Delayed",
|
||||
"Deposit": "Deposit",
|
||||
"Edit order": "Edit order",
|
||||
"Edit order - {{status}}": "Edit order - {{status}}",
|
||||
"Error": "Error",
|
||||
"Error occurred": "Error occurred",
|
||||
"Error: {{errorMessage}}": "Error: {{errorMessage}}",
|
||||
"Ethereum transaction complete": "Ethereum transaction complete",
|
||||
"Filled": "Filled",
|
||||
"Funds unlocked": "Funds unlocked",
|
||||
"Go to your Ethereum wallet and connect to the network {{networkName}}": "Go to your Ethereum wallet and connect to the network {{networkName}}",
|
||||
"If the network is reset or has an outage, records of your withdrawal may be lost. It is recommended that you save these details in a safe place so you can still complete your withdrawal.": "If the network is reset or has an outage, records of your withdrawal may be lost. It is recommended that you save these details in a safe place so you can still complete your withdrawal.",
|
||||
"Invalid asset source: {{source}}": "Invalid asset source: {{source}}",
|
||||
"Loading": "Loading",
|
||||
"MetaMask": "MetaMask",
|
||||
"MetaMask, Brave or other injected web wallet": "MetaMask, Brave or other injected web wallet",
|
||||
"No data": "No data",
|
||||
"Order cancelled'": "Order cancelled'",
|
||||
"Order expired'": "Order expired'",
|
||||
"Order filled": "Order filled",
|
||||
"Order parked": "Order parked",
|
||||
"Order partially filled": "Order partially filled",
|
||||
"Order rejected": "Order rejected",
|
||||
"Order stopped": "Order stopped",
|
||||
"Order submitted": "Order submitted",
|
||||
"Pending approval": "Pending approval",
|
||||
"Please go to your Vega wallet application and approve or reject the transaction.": "Please go to your Vega wallet application and approve or reject the transaction.",
|
||||
"Please go to your wallet application and approve or reject the transaction.": "Please go to your wallet application and approve or reject the transaction.",
|
||||
"Please wait for your transaction to be confirmed": "Please wait for your transaction to be confirmed",
|
||||
"Please wait for your transaction to be confirmed.": "Please wait for your transaction to be confirmed.",
|
||||
"Processing": "Processing",
|
||||
"Processing deposit": "Processing deposit",
|
||||
"Return": "Return",
|
||||
"Save withdrawal details": "Save withdrawal details",
|
||||
"save your withdrawal details": "save your withdrawal details",
|
||||
"Something went wrong": "Something went wrong",
|
||||
"Submission failed": "Submission failed",
|
||||
"Submit order": "Submit order",
|
||||
"Submit order - {{status}}": "Submit order - {{status}}",
|
||||
"Submit stop order": "Submit stop order",
|
||||
"The amount you're withdrawing has triggered a time delay": "The amount you're withdrawing has triggered a time delay",
|
||||
"The connection to your Vega Wallet has been lost.": "The connection to your Vega Wallet has been lost.",
|
||||
"The withdrawal has been approved.": "The withdrawal has been approved.",
|
||||
"To {{address}}": "To {{address}}",
|
||||
"To complete this withdrawal, connect the Ethereum wallet {{receiverAddress}}": "To complete this withdrawal, connect the Ethereum wallet {{receiverAddress}}",
|
||||
"Transaction confirmed": "Transaction confirmed",
|
||||
"Transfer": "Transfer",
|
||||
"Transfer complete": "Transfer complete",
|
||||
"Unknown": "Unknown",
|
||||
"Vega confirmation": "Vega confirmation",
|
||||
"Vega is confirming your transaction...": "Vega is confirming your transaction...",
|
||||
"Verifying withdrawal approval": "Verifying withdrawal approval",
|
||||
"Verifying...": "Verifying...",
|
||||
"View in block explorer": "View in block explorer",
|
||||
"View on Etherscan": "View on Etherscan",
|
||||
"View transaction on Etherscan": "View transaction on Etherscan",
|
||||
"Waiting for deposit confirmation.": "Waiting for deposit confirmation.",
|
||||
"Wallet disconnected": "Wallet disconnected",
|
||||
"WalletConnect": "WalletConnect",
|
||||
"WalletConnect Legacy": "WalletConnect Legacy",
|
||||
"WalletConnect v1": "WalletConnect v1",
|
||||
"WalletConnect v2": "WalletConnect v2",
|
||||
"Withdraw": "Withdraw",
|
||||
"Withdraw {{amount}} {{symbol}}": "Withdraw {{amount}} {{symbol}}",
|
||||
"Withdraw dependencies not met.": "Withdraw dependencies not met.",
|
||||
"Withdraw failure": "Withdraw failure",
|
||||
"Your {{timeInForce}} order was not filled and it has been stopped": "Your {{timeInForce}} order was not filled and it has been stopped",
|
||||
"Your Ethereum wallet is connected to the wrong network.": "Your Ethereum wallet is connected to the wrong network.",
|
||||
"Your funds have been unlocked for withdrawal.": "Your funds have been unlocked for withdrawal.",
|
||||
"Your order has been rejected": "Your order has been rejected",
|
||||
"Your order has been rejected because: {{rejectionReason}}": "Your order has been rejected because: {{rejectionReason}}",
|
||||
"Your order has been stopped": "Your order has been stopped",
|
||||
"Your order has been stopped because: {{rejectionReason}}": "Your order has been stopped because: {{rejectionReason}}",
|
||||
"Your order was rejected.": "Your order was rejected.",
|
||||
"Your transaction has been completed.": "Your transaction has been completed.",
|
||||
"Your transaction has been confirmed": "Your transaction has been confirmed",
|
||||
"Your transaction has been confirmed.": "Your transaction has been confirmed."
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
export const useTranslation = () => ({
|
||||
t: (label: string, replacements?: Record<string, string>) => {
|
||||
const replace =
|
||||
replacements?.replace && typeof replacements === 'object'
|
||||
? replacements?.replace
|
||||
: replacements;
|
||||
let translatedLabel = replacements?.defaultValue || label;
|
||||
if (typeof replace === 'object' && replace !== null) {
|
||||
Object.keys(replace).forEach((key) => {
|
||||
translatedLabel = translatedLabel.replace(`{{${key}}}`, replace[key]);
|
||||
});
|
||||
}
|
||||
return translatedLabel;
|
||||
},
|
||||
});
|
||||
@@ -2,7 +2,6 @@ import { DepthChart } from 'pennant';
|
||||
import throttle from 'lodash/throttle';
|
||||
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||
import { addDecimal, getNumberFormat } from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useThemeSwitcher } from '@vegaprotocol/react-helpers';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { marketDepthProvider } from './market-depth-provider';
|
||||
@@ -16,6 +15,7 @@ import {
|
||||
} from './__generated__/MarketDepth';
|
||||
import { type DepthChartProps } from 'pennant';
|
||||
import { parseLevel, updateLevels } from './depth-chart-utils';
|
||||
import { useT } from './use-t';
|
||||
|
||||
interface DepthChartManagerProps {
|
||||
marketId: string;
|
||||
@@ -39,6 +39,7 @@ const getMidPrice = (
|
||||
type DepthData = Pick<DepthChartProps, 'data' | 'midPrice'>;
|
||||
|
||||
export const DepthChartContainer = ({ marketId }: DepthChartManagerProps) => {
|
||||
const t = useT();
|
||||
const { theme } = useThemeSwitcher();
|
||||
const variables = useMemo(() => ({ marketId }), [marketId]);
|
||||
const [depthData, setDepthData] = useState<DepthData | null>(null);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useMemo, useRef, useState } from 'react';
|
||||
import ReactVirtualizedAutoSizer from 'react-virtualized-auto-sizer';
|
||||
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { usePrevious } from '@vegaprotocol/react-helpers';
|
||||
import { OrderbookRow } from './orderbook-row';
|
||||
import type { OrderbookRowData } from './orderbook-data';
|
||||
@@ -10,6 +9,7 @@ import { Splash, VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
|
||||
import classNames from 'classnames';
|
||||
import type { PriceLevelFieldsFragment } from './__generated__/MarketDepth';
|
||||
import { OrderbookControls } from './orderbook-controls';
|
||||
import { useT } from './use-t';
|
||||
|
||||
// Sets row height, will be used to calculate number of rows that can be
|
||||
// displayed each side of the book without overflow
|
||||
@@ -85,6 +85,7 @@ export const OrderbookMid = ({
|
||||
bestAskPrice: string;
|
||||
bestBidPrice: string;
|
||||
}) => {
|
||||
const t = useT();
|
||||
const previousLastTradedPrice = usePrevious(lastTradedPrice);
|
||||
const priceChangeRef = useRef<'up' | 'down' | 'none'>('none');
|
||||
const spread = (BigInt(bestAskPrice) - BigInt(bestBidPrice)).toString();
|
||||
@@ -153,6 +154,7 @@ export const Orderbook = ({
|
||||
bids,
|
||||
assetSymbol,
|
||||
}: OrderbookProps) => {
|
||||
const t = useT();
|
||||
const [resolution, setResolution] = useState(1);
|
||||
|
||||
const groupedAsks = useMemo(() => {
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const useT = () => useTranslation('market-depth').t;
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
getDateTimeFormat,
|
||||
addDecimal,
|
||||
addDecimalsFormatNumber,
|
||||
validateAmount,
|
||||
useValidateAmount,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { Size } from '@vegaprotocol/datagrid';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
@@ -39,6 +39,7 @@ export const OrderEditDialog = ({
|
||||
onSubmit,
|
||||
}: OrderEditDialogProps) => {
|
||||
const t = useT();
|
||||
const validateAmount = useValidateAmount();
|
||||
const headerClassName = 'text-xs font-bold text-black dark:text-white';
|
||||
const {
|
||||
register,
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
getDateTimeFormat,
|
||||
isNumeric,
|
||||
toBigNum,
|
||||
formatTrigger,
|
||||
useFormatTrigger,
|
||||
} from '@vegaprotocol/utils';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import {
|
||||
@@ -52,6 +52,7 @@ export type StopOrdersTableProps = TypedDataAgGrid<StopOrder> & {
|
||||
export const StopOrdersTable = memo(
|
||||
({ onCancel, onMarketClick, onView, ...props }: StopOrdersTableProps) => {
|
||||
const t = useT();
|
||||
const formatTrigger = useFormatTrigger();
|
||||
const showAllActions = !props.isReadOnly;
|
||||
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 (
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Tooltip } from '@vegaprotocol/ui-toolkit';
|
||||
import { useEstimatePositionQuery } from './__generated__/Positions';
|
||||
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useT } from '../use-t';
|
||||
|
||||
export const LiquidationPrice = ({
|
||||
marketId,
|
||||
@@ -14,6 +14,7 @@ export const LiquidationPrice = ({
|
||||
collateralAvailable: string;
|
||||
decimalPlaces: number;
|
||||
}) => {
|
||||
const t = useT();
|
||||
const { data: currentData, previousData } = useEstimatePositionQuery({
|
||||
variables: {
|
||||
marketId,
|
||||
@@ -43,11 +44,11 @@ export const LiquidationPrice = ({
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>{t('Worst case')}</th>
|
||||
<td className="pl-2 font-mono text-right">{worstCase}</td>
|
||||
<td className="pl-2 text-right font-mono">{worstCase}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{t('Best case')}</th>
|
||||
<td className="pl-2 font-mono text-right">{bestCase}</td>
|
||||
<td className="pl-2 text-right font-mono">{bestCase}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import {
|
||||
ActionsDropdown,
|
||||
TradingDropdownItem,
|
||||
@@ -6,8 +5,10 @@ import {
|
||||
VegaIconNames,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
|
||||
import { useT } from '../use-t';
|
||||
|
||||
export const PositionActionsDropdown = ({ assetId }: { assetId: string }) => {
|
||||
const t = useT();
|
||||
const open = useAssetDetailsDialogStore((store) => store.open);
|
||||
|
||||
return (
|
||||
|
||||
@@ -3,7 +3,6 @@ import { PositionsTable } from './positions-table';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import { useVegaTransactionStore } from '@vegaprotocol/web3';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import {
|
||||
positionsMetricsProvider,
|
||||
@@ -11,6 +10,7 @@ import {
|
||||
} from './positions-data-providers';
|
||||
import type { useDataGridEvents } from '@vegaprotocol/datagrid';
|
||||
import { MAXGOINT64 } from '@vegaprotocol/utils';
|
||||
import { useT } from '../use-t';
|
||||
|
||||
interface PositionsManagerProps {
|
||||
partyIds: string[];
|
||||
@@ -27,6 +27,7 @@ export const PositionsManager = ({
|
||||
gridProps,
|
||||
showClosed = false,
|
||||
}: PositionsManagerProps) => {
|
||||
const t = useT();
|
||||
const { pubKeys, pubKey } = useVegaWallet();
|
||||
const create = useVegaTransactionStore((store) => store.create);
|
||||
const onClose = useCallback(
|
||||
|
||||
@@ -28,7 +28,6 @@ import {
|
||||
addDecimalsFormatNumber,
|
||||
addDecimalsFormatNumberQuantum,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { type Position } from './positions-data-providers';
|
||||
import {
|
||||
MarketTradingMode,
|
||||
@@ -38,6 +37,7 @@ import {
|
||||
import { DocsLinks } from '@vegaprotocol/environment';
|
||||
import { PositionActionsDropdown } from './position-actions-dropdown';
|
||||
import { LiquidationPrice } from './liquidation-price';
|
||||
import { useT } from '../use-t';
|
||||
|
||||
interface Props extends TypedDataAgGrid<Position> {
|
||||
onClose?: (data: Position) => void;
|
||||
@@ -81,6 +81,7 @@ export const PositionsTable = ({
|
||||
pubKey,
|
||||
...props
|
||||
}: Props) => {
|
||||
const t = useT();
|
||||
return (
|
||||
<AgGrid
|
||||
overlayNoRowsTemplate={t('No positions')}
|
||||
@@ -193,8 +194,8 @@ export const PositionsTable = ({
|
||||
switch (args.data.status) {
|
||||
case PositionStatus.POSITION_STATUS_CLOSED_OUT:
|
||||
secondaryTooltip = t(
|
||||
`You did not have enough %s collateral to meet the maintenance margin requirements for your position, so it was closed by the network.`,
|
||||
args.data.assetSymbol
|
||||
`You did not have enough {{assetSymbol}} collateral to meet the maintenance margin requirements for your position, so it was closed by the network.`,
|
||||
{ assetSymbol: args.data.assetSymbol }
|
||||
);
|
||||
break;
|
||||
case PositionStatus.POSITION_STATUS_ORDERS_CLOSED:
|
||||
@@ -218,10 +219,12 @@ export const PositionsTable = ({
|
||||
<p className="mb-2">{primaryTooltip}</p>
|
||||
<p className="mb-2">{secondaryTooltip}</p>
|
||||
<p className="mb-2">
|
||||
{t(
|
||||
'Status: %s',
|
||||
PositionStatusMapping[args.data.status]
|
||||
)}
|
||||
{t('Status: {{status}}', {
|
||||
nsSeparator: '*',
|
||||
replace: {
|
||||
status: PositionStatusMapping[args.data.status],
|
||||
},
|
||||
})}
|
||||
</p>
|
||||
{POSITION_RESOLUTION_LINK && (
|
||||
<ExternalLink href={POSITION_RESOLUTION_LINK}>
|
||||
@@ -386,18 +389,26 @@ export const PositionsTable = ({
|
||||
value={
|
||||
<>
|
||||
<p className="mb-2">
|
||||
{t('Realised PNL: %s', args.value)}
|
||||
{t('Realised PNL: {{value}}', {
|
||||
nsSeparator: '*',
|
||||
replace: { value: args.value },
|
||||
})}
|
||||
</p>
|
||||
<p className="mb-2">
|
||||
{t(
|
||||
'Lifetime loss socialisation deductions: %s',
|
||||
lossesFormatted
|
||||
'Lifetime loss socialisation deductions: {{losses}}',
|
||||
{
|
||||
nsSeparator: '*',
|
||||
replace: {
|
||||
losses: lossesFormatted,
|
||||
},
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
<p className="mb-2">
|
||||
{t(
|
||||
`You received less %s in gains that you should have when the market moved in your favour. This occurred because one or more other trader(s) were closed out and did not have enough funds to cover their losses, and the market's insurance pool was empty.`,
|
||||
args.data.assetSymbol
|
||||
`You received less {{assetSymbol}} in gains that you should have when the market moved in your favour. This occurred because one or more other trader(s) were closed out and did not have enough funds to cover their losses, and the market's insurance pool was empty.`,
|
||||
{ assetSymbol: args.data.assetSymbol }
|
||||
)}
|
||||
</p>
|
||||
{LOSS_SOCIALIZATION_LINK && (
|
||||
@@ -481,7 +492,15 @@ export const PositionsTable = ({
|
||||
return columnDefs.filter<ColDef>(
|
||||
(colDef: ColDef | null): colDef is ColDef => colDef !== null
|
||||
);
|
||||
}, [isReadOnly, multipleKeys, onClose, onMarketClick, pubKey, pubKeys])}
|
||||
}, [
|
||||
isReadOnly,
|
||||
multipleKeys,
|
||||
onClose,
|
||||
onMarketClick,
|
||||
pubKey,
|
||||
pubKeys,
|
||||
t,
|
||||
])}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,17 @@
|
||||
import '@testing-library/jest-dom';
|
||||
import ResizeObserver from 'resize-observer-polyfill';
|
||||
import { locales } from '@vegaprotocol/i18n';
|
||||
import i18n from 'i18next';
|
||||
import { initReactI18next } from 'react-i18next';
|
||||
|
||||
// Set up i18n instance so that components have the correct default
|
||||
// en translations
|
||||
i18n.use(initReactI18next).init({
|
||||
// we init with resources
|
||||
resources: locales,
|
||||
fallbackLng: 'en',
|
||||
ns: ['positions'],
|
||||
defaultNS: 'positions',
|
||||
});
|
||||
|
||||
global.ResizeObserver = ResizeObserver;
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
export const ns = 'positions';
|
||||
export const useT = () => useTranslation(ns).t;
|
||||
@@ -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;
|
||||
},
|
||||
});
|
||||
@@ -1,27 +1,37 @@
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { addDecimalsFormatNumber } from './number';
|
||||
import { useCallback } from 'react';
|
||||
import { useT } from '../use-t';
|
||||
|
||||
export const formatTrigger = (
|
||||
data: Pick<Schema.StopOrder, 'trigger' | 'triggerDirection'> | undefined,
|
||||
marketDecimalPlaces: number,
|
||||
defaultValue = '-'
|
||||
) => {
|
||||
if (data && data?.trigger?.__typename === 'StopOrderPrice') {
|
||||
return `${t('Mark')} ${
|
||||
data?.triggerDirection ===
|
||||
Schema.StopOrderTriggerDirection.TRIGGER_DIRECTION_FALLS_BELOW
|
||||
? '<'
|
||||
: '>'
|
||||
} ${addDecimalsFormatNumber(data.trigger.price, marketDecimalPlaces)}`;
|
||||
}
|
||||
if (data && data?.trigger?.__typename === 'StopOrderTrailingPercentOffset') {
|
||||
return `${t('Mark')} ${
|
||||
data?.triggerDirection ===
|
||||
Schema.StopOrderTriggerDirection.TRIGGER_DIRECTION_FALLS_BELOW
|
||||
? '+'
|
||||
: '-'
|
||||
}${(Number(data?.trigger.trailingPercentOffset) * 100).toFixed(1)}%`;
|
||||
}
|
||||
return defaultValue;
|
||||
export const useFormatTrigger = () => {
|
||||
const t = useT();
|
||||
return useCallback(
|
||||
(
|
||||
data: Pick<Schema.StopOrder, 'trigger' | 'triggerDirection'> | undefined,
|
||||
marketDecimalPlaces: number,
|
||||
defaultValue = '-'
|
||||
) => {
|
||||
if (data && data?.trigger?.__typename === 'StopOrderPrice') {
|
||||
return `${t('Mark')} ${
|
||||
data?.triggerDirection ===
|
||||
Schema.StopOrderTriggerDirection.TRIGGER_DIRECTION_FALLS_BELOW
|
||||
? '<'
|
||||
: '>'
|
||||
} ${addDecimalsFormatNumber(data.trigger.price, marketDecimalPlaces)}`;
|
||||
}
|
||||
if (
|
||||
data &&
|
||||
data?.trigger?.__typename === 'StopOrderTrailingPercentOffset'
|
||||
) {
|
||||
return `${t('Mark')} ${
|
||||
data?.triggerDirection ===
|
||||
Schema.StopOrderTriggerDirection.TRIGGER_DIRECTION_FALLS_BELOW
|
||||
? '+'
|
||||
: '-'
|
||||
}${(Number(data?.trigger.trailingPercentOffset) * 100).toFixed(1)}%`;
|
||||
}
|
||||
return defaultValue;
|
||||
},
|
||||
[t]
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { MarketState } from '@vegaprotocol/types';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { isValid, parseISO } from 'date-fns';
|
||||
import { getDateTimeFormat } from './format';
|
||||
import { useT } from './use-t';
|
||||
|
||||
export const getMarketExpiryDate = (
|
||||
tags?: ReadonlyArray<string> | null
|
||||
@@ -40,12 +40,15 @@ export const getExpiryDate = (
|
||||
close: string | null,
|
||||
state: MarketState
|
||||
): string => {
|
||||
const t = useT();
|
||||
const metadataExpiryDate = getMarketExpiryDate(tags);
|
||||
const marketTimestampCloseDate = close && new Date(close);
|
||||
let content = null;
|
||||
if (!metadataExpiryDate) {
|
||||
content = marketTimestampCloseDate
|
||||
? `Expired on ${getDateTimeFormat().format(marketTimestampCloseDate)}`
|
||||
? t('Expired on {{date}}', {
|
||||
date: getDateTimeFormat().format(marketTimestampCloseDate),
|
||||
})
|
||||
: t('Not time-based');
|
||||
} else {
|
||||
const isExpired =
|
||||
@@ -54,7 +57,9 @@ export const getExpiryDate = (
|
||||
state === MarketState.STATE_SETTLED);
|
||||
if (isExpired) {
|
||||
content = marketTimestampCloseDate
|
||||
? `Expired on ${getDateTimeFormat().format(marketTimestampCloseDate)}`
|
||||
? t('Expired on {{date}}', {
|
||||
date: getDateTimeFormat().format(marketTimestampCloseDate),
|
||||
})
|
||||
: t('Expired');
|
||||
} else {
|
||||
content = getDateTimeFormat().format(metadataExpiryDate);
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
export const ns = 'utils';
|
||||
export const useT = () => useTranslation(ns).t;
|
||||
@@ -1,6 +1,10 @@
|
||||
import { ethereumAddress, vegaPublicKey } from './common';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { useEthereumAddress, useVegaPublicKey } from './common';
|
||||
|
||||
it('ethereumAddress', () => {
|
||||
const result = renderHook(useEthereumAddress);
|
||||
const ethereumAddress = result.result.current;
|
||||
|
||||
const errorMessage = 'Invalid Ethereum address';
|
||||
|
||||
const validAddress = '0x72c22822A19D20DE7e426fB84aa047399Ddd8853';
|
||||
@@ -17,6 +21,9 @@ it('ethereumAddress', () => {
|
||||
});
|
||||
|
||||
it('vegaPublicKey', () => {
|
||||
const result = renderHook(useVegaPublicKey);
|
||||
const vegaPublicKey = result.result.current;
|
||||
|
||||
const errorMessage = 'Invalid Vega key';
|
||||
|
||||
const validKey =
|
||||
|
||||
@@ -1,40 +1,71 @@
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useT } from '../use-t';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
export const required = (value: string) => {
|
||||
if (value === null || value === undefined || value === '') {
|
||||
return t('Required');
|
||||
}
|
||||
return true;
|
||||
export const useRequired = () => {
|
||||
const t = useT();
|
||||
return useCallback(
|
||||
(value: string) => {
|
||||
if (value === null || value === undefined || value === '') {
|
||||
return t('Required');
|
||||
}
|
||||
return true;
|
||||
},
|
||||
[t]
|
||||
);
|
||||
};
|
||||
|
||||
export const ethereumAddress = (value: string) => {
|
||||
if (!/^0x[0-9a-fA-F]{40}$/i.test(value)) {
|
||||
return t('Invalid Ethereum address');
|
||||
}
|
||||
return true;
|
||||
export const useEthereumAddress = () => {
|
||||
const t = useT();
|
||||
return useCallback(
|
||||
(value: string) => {
|
||||
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 vegaPublicKey = (value: string) => {
|
||||
if (!VEGA_ID_REGEX.test(value)) {
|
||||
return t('Invalid Vega key');
|
||||
}
|
||||
return true;
|
||||
export const useVegaPublicKey = () => {
|
||||
const t = useT();
|
||||
return useCallback(
|
||||
(value: string) => {
|
||||
if (!VEGA_ID_REGEX.test(value)) {
|
||||
return t('Invalid Vega key');
|
||||
}
|
||||
return true;
|
||||
},
|
||||
[t]
|
||||
);
|
||||
};
|
||||
|
||||
export const minSafe = (min: BigNumber) => (value: string) => {
|
||||
if (new BigNumber(value).isLessThan(min)) {
|
||||
return t('Value is below minimum');
|
||||
}
|
||||
return true;
|
||||
export const useMinSafe = () => {
|
||||
const t = useT();
|
||||
return useCallback(
|
||||
(min: BigNumber) => (value: string) => {
|
||||
if (new BigNumber(value).isLessThan(min)) {
|
||||
return t('Value is below minimum');
|
||||
}
|
||||
return true;
|
||||
},
|
||||
[t]
|
||||
);
|
||||
};
|
||||
|
||||
export const maxSafe = (max: BigNumber) => (value: string) => {
|
||||
if (new BigNumber(value).isGreaterThan(max)) {
|
||||
return t('Value is above maximum');
|
||||
}
|
||||
return true;
|
||||
export const useMaxSafe = () => {
|
||||
const t = useT();
|
||||
return useCallback(
|
||||
(max: BigNumber) => (value: string) => {
|
||||
if (new BigNumber(value).isGreaterThan(max)) {
|
||||
return t('Value is above maximum');
|
||||
}
|
||||
return true;
|
||||
},
|
||||
[t]
|
||||
);
|
||||
};
|
||||
|
||||
export const suitableForSyntaxHighlighter = (str: string) => {
|
||||
@@ -46,11 +77,17 @@ export const suitableForSyntaxHighlighter = (str: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const validateJson = (value: string) => {
|
||||
try {
|
||||
JSON.parse(value);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return t('Must be valid JSON');
|
||||
}
|
||||
export const useValidateJson = () => {
|
||||
const t = useT();
|
||||
return useCallback(
|
||||
(value: string) => {
|
||||
try {
|
||||
JSON.parse(value);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return t('Must be valid JSON');
|
||||
}
|
||||
},
|
||||
[t]
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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) => {
|
||||
const [, stepDecimals = ''] = String(step).split('.');
|
||||
export const useValidateAmount = () => {
|
||||
const t = useT();
|
||||
return useCallback(
|
||||
(step: number | string, field: string) => {
|
||||
const [, stepDecimals = ''] = String(step).split('.');
|
||||
|
||||
return (value?: string) => {
|
||||
if (Number(step) > 1) {
|
||||
if (Number(value) % Number(step) > 0) {
|
||||
return t(`${field} must be a multiple of ${step} for this market`);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
const [, valueDecimals = ''] = (value || '').split('.');
|
||||
if (stepDecimals.length < valueDecimals.length) {
|
||||
if (stepDecimals === '') {
|
||||
return t(`${field} must be whole numbers for this market`);
|
||||
}
|
||||
return t(`${field} accepts up to ${stepDecimals.length} decimal places`);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
return (value?: string) => {
|
||||
if (Number(step) > 1) {
|
||||
if (Number(value) % Number(step) > 0) {
|
||||
return t(
|
||||
'{{field}} must be a multiple of {{step}} for this market',
|
||||
{
|
||||
field,
|
||||
step,
|
||||
}
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
const [, valueDecimals = ''] = (value || '').split('.');
|
||||
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]
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Link } from '@vegaprotocol/ui-toolkit';
|
||||
import { EtherscanLink, useEnvironment } from '@vegaprotocol/environment';
|
||||
import { EthTxStatus } from '../use-ethereum-transaction';
|
||||
import { useT } from '../use-t';
|
||||
|
||||
const ACTIVE_CLASSES = 'text-black dark:text-white';
|
||||
|
||||
export const ConfirmRow = ({ status }: { status: EthTxStatus }) => {
|
||||
const t = useT();
|
||||
if (status === EthTxStatus.Requested) {
|
||||
return (
|
||||
<p className="text-black dark:text-white">
|
||||
@@ -32,6 +33,7 @@ export const TxRow = ({
|
||||
requiredConfirmations,
|
||||
highlightComplete = true,
|
||||
}: TxRowProps) => {
|
||||
const t = useT();
|
||||
const { ETHERSCAN_URL } = useEnvironment();
|
||||
|
||||
if (status === EthTxStatus.Pending) {
|
||||
@@ -39,7 +41,8 @@ export const TxRow = ({
|
||||
<p className={`flex justify-between ${ACTIVE_CLASSES}`}>
|
||||
<span>
|
||||
{t(
|
||||
`Awaiting Ethereum transaction ${confirmations}/${requiredConfirmations} confirmations...`
|
||||
`Awaiting Ethereum transaction {{confirmations}}/{{requiredConfirmations}} confirmations...`,
|
||||
{ confirmations, requiredConfirmations }
|
||||
)}
|
||||
</span>
|
||||
<Link
|
||||
@@ -82,6 +85,7 @@ interface ConfirmationEventRowProps {
|
||||
}
|
||||
|
||||
export const ConfirmationEventRow = ({ status }: ConfirmationEventRowProps) => {
|
||||
const t = useT();
|
||||
if (status !== EthTxStatus.Complete && status !== EthTxStatus.Confirmed) {
|
||||
return <p>{t('Vega confirmation')}</p>;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Button, Dialog, Icon, Intent, Loader } from '@vegaprotocol/ui-toolkit';
|
||||
import { isEthereumError } from '../ethereum-error';
|
||||
import type { EthTxState, TxError } from '../use-ethereum-transaction';
|
||||
import { EthTxStatus } from '../use-ethereum-transaction';
|
||||
import { ConfirmRow, TxRow, ConfirmationEventRow } from './dialog-rows';
|
||||
import { useT } from '../use-t';
|
||||
|
||||
export interface EthereumTransactionDialogProps {
|
||||
title: string;
|
||||
@@ -20,13 +20,14 @@ export const EthereumTransactionDialog = ({
|
||||
onChange,
|
||||
requiredConfirmations = 1,
|
||||
}: EthereumTransactionDialogProps) => {
|
||||
const t = useT();
|
||||
const { status, error, confirmations, txHash } = transaction;
|
||||
return (
|
||||
<Dialog
|
||||
open={transaction.dialogOpen}
|
||||
onChange={onChange}
|
||||
size="small"
|
||||
{...getWrapperProps(title, status)}
|
||||
{...getWrapperProps(title, status, t)}
|
||||
>
|
||||
<TransactionContent
|
||||
status={status}
|
||||
@@ -44,11 +45,13 @@ export const getTransactionContent = ({
|
||||
transaction,
|
||||
requiredConfirmations,
|
||||
reset,
|
||||
t,
|
||||
}: {
|
||||
title: string;
|
||||
transaction: EthTxState;
|
||||
requiredConfirmations?: number;
|
||||
reset: () => void;
|
||||
t: ReturnType<typeof useT>;
|
||||
}) => {
|
||||
const { status, error, confirmations, txHash } = transaction;
|
||||
const content = ({ returnLabel }: { returnLabel?: string }) => (
|
||||
@@ -75,7 +78,7 @@ export const getTransactionContent = ({
|
||||
</>
|
||||
);
|
||||
return {
|
||||
...getWrapperProps(title, status),
|
||||
...getWrapperProps(title, status, t),
|
||||
status,
|
||||
Content: content,
|
||||
};
|
||||
@@ -94,6 +97,7 @@ export const TransactionContent = ({
|
||||
confirmations: number;
|
||||
requiredConfirmations?: number;
|
||||
}) => {
|
||||
const t = useT();
|
||||
if (status === EthTxStatus.Error) {
|
||||
let errorMessage = '';
|
||||
|
||||
@@ -105,7 +109,10 @@ export const TransactionContent = ({
|
||||
|
||||
return (
|
||||
<p className="break-all">
|
||||
{t('Error')}: {errorMessage}
|
||||
{t('Error: {{errorMessage}}', {
|
||||
nsSeparator: '*',
|
||||
replace: { errorMessage },
|
||||
})}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
@@ -128,7 +135,8 @@ export const TransactionContent = ({
|
||||
type WrapperProps = { title: string; icon?: JSX.Element; intent?: Intent };
|
||||
export const getWrapperProps = (
|
||||
title: string,
|
||||
status: EthTxStatus
|
||||
status: EthTxStatus,
|
||||
t: ReturnType<typeof useT>
|
||||
): WrapperProps => {
|
||||
const propsMap = {
|
||||
[EthTxStatus.Default]: {
|
||||
@@ -137,17 +145,17 @@ export const getWrapperProps = (
|
||||
intent: undefined,
|
||||
},
|
||||
[EthTxStatus.Error]: {
|
||||
title: t(`${title} failed`),
|
||||
title: t(`{{title}} failed`, { title }),
|
||||
icon: <Icon name="warning-sign" />,
|
||||
intent: Intent.Danger,
|
||||
},
|
||||
[EthTxStatus.Requested]: {
|
||||
title: t('Confirm transaction'),
|
||||
title: t('Confirm transaction', { title }),
|
||||
icon: <Icon name="hand-up" />,
|
||||
intent: Intent.Warning,
|
||||
},
|
||||
[EthTxStatus.Pending]: {
|
||||
title: t(`${title} pending`),
|
||||
title: t(`{{title}} pending`, { title }),
|
||||
icon: (
|
||||
<span className="mt-1">
|
||||
<Loader size="small" />
|
||||
@@ -156,12 +164,12 @@ export const getWrapperProps = (
|
||||
intent: Intent.None,
|
||||
},
|
||||
[EthTxStatus.Complete]: {
|
||||
title: t(`${title} pending`),
|
||||
title: t(`{{title}} pending`, { title }),
|
||||
icon: <Loader size="small" />,
|
||||
intent: Intent.None,
|
||||
},
|
||||
[EthTxStatus.Confirmed]: {
|
||||
title: t(`${title} complete`),
|
||||
title: t(`{{title}} complete`, { title }),
|
||||
icon: <Icon name="tick" />,
|
||||
intent: Intent.Success,
|
||||
},
|
||||
|
||||
@@ -3,7 +3,6 @@ import { useEffect } from 'react';
|
||||
import { useAssetsDataProvider } from '@vegaprotocol/assets';
|
||||
import { EtherscanLink } from '@vegaprotocol/environment';
|
||||
import { formatNumber, toBigNum } from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import type { Toast, ToastContent } from '@vegaprotocol/ui-toolkit';
|
||||
import { ToastHeading } from '@vegaprotocol/ui-toolkit';
|
||||
import { Panel } from '@vegaprotocol/ui-toolkit';
|
||||
@@ -17,6 +16,7 @@ import { EthTxStatus } from './use-ethereum-transaction';
|
||||
import { isEthereumError } from './ethereum-error';
|
||||
import { TransactionContent } from './ethereum-transaction-dialog';
|
||||
import { useEthTransactionStore } from './use-ethereum-transaction-store';
|
||||
import { useT } from './use-t';
|
||||
|
||||
const intentMap: { [s in EthTxStatus]: Intent } = {
|
||||
Default: Intent.Primary,
|
||||
@@ -34,6 +34,7 @@ const isDepositTransaction = (tx: EthStoredTxState) =>
|
||||
tx.methodName === 'deposit_asset';
|
||||
|
||||
const EthTransactionDetails = ({ tx }: { tx: EthStoredTxState }) => {
|
||||
const t = useT();
|
||||
const { data: assets } = useAssetsDataProvider();
|
||||
if (!assets) return null;
|
||||
|
||||
@@ -64,8 +65,10 @@ const EthTransactionDetails = ({ tx }: { tx: EthStoredTxState }) => {
|
||||
{tx.status === EthTxStatus.Pending && (
|
||||
<>
|
||||
<p className="mt-[2px]">
|
||||
{t('Awaiting confirmations')}{' '}
|
||||
{`(${tx.confirmations}/${tx.requiredConfirmations})`}
|
||||
{t(
|
||||
'Awaiting confirmations {{confirmations}}/{[requiredConfirmations}}',
|
||||
tx
|
||||
)}
|
||||
</p>
|
||||
<ProgressBar
|
||||
value={(tx.confirmations / tx.requiredConfirmations) * 100}
|
||||
@@ -84,6 +87,7 @@ type EthTxToastContentProps = {
|
||||
};
|
||||
|
||||
const EthTxRequestedToastContent = ({ tx }: EthTxToastContentProps) => {
|
||||
const t = useT();
|
||||
return (
|
||||
<>
|
||||
<ToastHeading>{t('Action required')}</ToastHeading>
|
||||
@@ -98,6 +102,7 @@ const EthTxRequestedToastContent = ({ tx }: EthTxToastContentProps) => {
|
||||
};
|
||||
|
||||
const EthTxPendingToastContent = ({ tx }: EthTxToastContentProps) => {
|
||||
const t = useT();
|
||||
return (
|
||||
<>
|
||||
<ToastHeading>{t('Awaiting confirmation')}</ToastHeading>
|
||||
@@ -111,6 +116,7 @@ const EthTxPendingToastContent = ({ tx }: EthTxToastContentProps) => {
|
||||
};
|
||||
|
||||
const EthTxErrorToastContent = ({ tx }: EthTxToastContentProps) => {
|
||||
const t = useT();
|
||||
let errorMessage = '';
|
||||
|
||||
if (isEthereumError(tx.error)) {
|
||||
@@ -128,6 +134,7 @@ const EthTxErrorToastContent = ({ tx }: EthTxToastContentProps) => {
|
||||
};
|
||||
|
||||
const EthTxConfirmedToastContent = ({ tx }: EthTxToastContentProps) => {
|
||||
const t = useT();
|
||||
return (
|
||||
<>
|
||||
<ToastHeading>{t('Transaction confirmed')}</ToastHeading>
|
||||
@@ -141,11 +148,12 @@ const EthTxConfirmedToastContent = ({ tx }: EthTxToastContentProps) => {
|
||||
};
|
||||
|
||||
const EthTxCompletedToastContent = ({ tx }: EthTxToastContentProps) => {
|
||||
const t = useT();
|
||||
const isDeposit = isDepositTransaction(tx);
|
||||
return (
|
||||
<>
|
||||
<ToastHeading>
|
||||
{t('Processing')} {isDeposit && t('deposit')}
|
||||
{isDeposit ? t('Processing deposit') : t('Processing')}
|
||||
</ToastHeading>
|
||||
<p>
|
||||
{t('Your transaction has been completed.')}{' '}
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
EthereumTransactionDialog,
|
||||
getTransactionContent,
|
||||
} from './ethereum-transaction-dialog';
|
||||
import { useT } from './use-t';
|
||||
|
||||
export enum EthTxStatus {
|
||||
Default = 'Default',
|
||||
@@ -51,6 +52,7 @@ export const useEthereumTransaction = <
|
||||
requiredConfirmations = 1,
|
||||
requiresConfirmation = false
|
||||
) => {
|
||||
const t = useT();
|
||||
const [transaction, _setTransaction] = useState<EthTxState>(initialState);
|
||||
|
||||
const setTransaction = useCallback((update: Partial<EthTxState>) => {
|
||||
@@ -173,8 +175,9 @@ export const useEthereumTransaction = <
|
||||
transaction,
|
||||
requiredConfirmations,
|
||||
reset,
|
||||
t,
|
||||
}),
|
||||
[methodName, requiredConfirmations, reset, transaction]
|
||||
[methodName, requiredConfirmations, reset, transaction, t]
|
||||
);
|
||||
|
||||
return { perform, transaction, reset, setConfirmed, Dialog, TxContent };
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { formatNumber, toBigNum } from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import type { Toast } from '@vegaprotocol/ui-toolkit';
|
||||
import { ToastHeading } from '@vegaprotocol/ui-toolkit';
|
||||
import { Panel } from '@vegaprotocol/ui-toolkit';
|
||||
@@ -15,6 +14,7 @@ import {
|
||||
} from './use-ethereum-withdraw-approvals-store';
|
||||
import { ApprovalStatus } from './use-ethereum-withdraw-approvals-store';
|
||||
import { VerificationStatus } from './withdrawal-approval-status';
|
||||
import { useT } from './use-t';
|
||||
|
||||
const intentMap: { [s in ApprovalStatus]: Intent } = {
|
||||
Pending: Intent.Warning,
|
||||
@@ -29,6 +29,7 @@ const EthWithdrawalApprovalToastContent = ({
|
||||
}: {
|
||||
tx: EthWithdrawalApprovalState;
|
||||
}) => {
|
||||
const t = useT();
|
||||
const isConnectionFailure =
|
||||
tx.failureReason &&
|
||||
[
|
||||
@@ -54,14 +55,17 @@ const EthWithdrawalApprovalToastContent = ({
|
||||
title = t('Approved');
|
||||
}
|
||||
|
||||
const num = formatNumber(
|
||||
const amount = formatNumber(
|
||||
toBigNum(tx.withdrawal.amount, tx.withdrawal.asset.decimals),
|
||||
tx.withdrawal.asset.decimals
|
||||
);
|
||||
const details = isConnectionFailure ? null : (
|
||||
<Panel>
|
||||
<strong>
|
||||
{t('Withdraw')} {num} {tx.withdrawal.asset.symbol}
|
||||
{t('Withdraw {{amount}} {{symbol}}', {
|
||||
amount,
|
||||
symbol: tx.withdrawal.asset.symbol,
|
||||
})}
|
||||
</strong>
|
||||
</Panel>
|
||||
);
|
||||
|
||||
@@ -4,7 +4,6 @@ import { useEffect, useRef } from 'react';
|
||||
import { addDecimal } from '@vegaprotocol/utils';
|
||||
import { useGetWithdrawThreshold } from './use-get-withdraw-threshold';
|
||||
import { useGetWithdrawDelay } from './use-get-withdraw-delay';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { localLoggerFactory } from '@vegaprotocol/logger';
|
||||
|
||||
import { CollateralBridge } from '@vegaprotocol/smart-contracts';
|
||||
@@ -21,8 +20,10 @@ import {
|
||||
useEthWithdrawApprovalsStore,
|
||||
WithdrawalFailure,
|
||||
} from './use-ethereum-withdraw-approvals-store';
|
||||
import { useT } from './use-t';
|
||||
|
||||
export const useEthWithdrawApprovalsManager = () => {
|
||||
const t = useT();
|
||||
const getThreshold = useGetWithdrawThreshold();
|
||||
const getDelay = useGetWithdrawDelay();
|
||||
const { query } = useApolloClient();
|
||||
@@ -49,9 +50,12 @@ export const useEthWithdrawApprovalsManager = () => {
|
||||
if (withdrawal.asset.source.__typename !== 'ERC20') {
|
||||
update(transaction.id, {
|
||||
status: ApprovalStatus.Error,
|
||||
message: t(
|
||||
`Invalid asset source: ${withdrawal.asset.source.__typename}`
|
||||
),
|
||||
message: t(`Invalid asset source: {{source}}`, {
|
||||
nsSeparator: '*',
|
||||
replace: {
|
||||
source: withdrawal.asset.source.__typename,
|
||||
},
|
||||
}),
|
||||
failureReason: WithdrawalFailure.InvalidAsset,
|
||||
});
|
||||
return;
|
||||
@@ -162,5 +166,6 @@ export const useEthWithdrawApprovalsManager = () => {
|
||||
transaction,
|
||||
update,
|
||||
chainId,
|
||||
t,
|
||||
]);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
export const ns = 'web3';
|
||||
export const useT = () => useTranslation(ns).t;
|
||||
@@ -418,32 +418,33 @@ describe('getVegaTransactionContentIntent', () => {
|
||||
});
|
||||
});
|
||||
describe('getOrderToastTitle', () => {
|
||||
const t = (v: string) => v;
|
||||
it('should return the correct title', () => {
|
||||
expect(getOrderToastTitle(Types.OrderStatus.STATUS_ACTIVE)).toBe(
|
||||
expect(getOrderToastTitle(Types.OrderStatus.STATUS_ACTIVE, t)).toBe(
|
||||
'Order submitted'
|
||||
);
|
||||
expect(getOrderToastTitle(Types.OrderStatus.STATUS_FILLED)).toBe(
|
||||
expect(getOrderToastTitle(Types.OrderStatus.STATUS_FILLED, t)).toBe(
|
||||
'Order filled'
|
||||
);
|
||||
expect(getOrderToastTitle(Types.OrderStatus.STATUS_PARTIALLY_FILLED)).toBe(
|
||||
'Order partially filled'
|
||||
);
|
||||
expect(getOrderToastTitle(Types.OrderStatus.STATUS_PARKED)).toBe(
|
||||
expect(
|
||||
getOrderToastTitle(Types.OrderStatus.STATUS_PARTIALLY_FILLED, t)
|
||||
).toBe('Order partially filled');
|
||||
expect(getOrderToastTitle(Types.OrderStatus.STATUS_PARKED, t)).toBe(
|
||||
'Order parked'
|
||||
);
|
||||
expect(getOrderToastTitle(Types.OrderStatus.STATUS_STOPPED)).toBe(
|
||||
expect(getOrderToastTitle(Types.OrderStatus.STATUS_STOPPED, t)).toBe(
|
||||
'Order stopped'
|
||||
);
|
||||
expect(getOrderToastTitle(Types.OrderStatus.STATUS_CANCELLED)).toBe(
|
||||
expect(getOrderToastTitle(Types.OrderStatus.STATUS_CANCELLED, t)).toBe(
|
||||
'Order cancelled'
|
||||
);
|
||||
expect(getOrderToastTitle(Types.OrderStatus.STATUS_EXPIRED)).toBe(
|
||||
expect(getOrderToastTitle(Types.OrderStatus.STATUS_EXPIRED, t)).toBe(
|
||||
'Order expired'
|
||||
);
|
||||
expect(getOrderToastTitle(Types.OrderStatus.STATUS_REJECTED)).toBe(
|
||||
expect(getOrderToastTitle(Types.OrderStatus.STATUS_REJECTED, t)).toBe(
|
||||
'Order rejected'
|
||||
);
|
||||
expect(getOrderToastTitle(undefined)).toBe(undefined);
|
||||
expect(getOrderToastTitle(undefined, t)).toBe(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -480,38 +481,42 @@ describe('getOrderToastIntent', () => {
|
||||
describe('getRejectionReason', () => {
|
||||
it('should return the correct rejection reason for insufficient asset balance', () => {
|
||||
expect(
|
||||
getRejectionReason({
|
||||
rejectionReason:
|
||||
Types.OrderRejectionReason.ORDER_ERROR_INSUFFICIENT_ASSET_BALANCE,
|
||||
status: Types.OrderStatus.STATUS_REJECTED,
|
||||
id: '',
|
||||
createdAt: undefined,
|
||||
size: '',
|
||||
price: '',
|
||||
timeInForce: Types.OrderTimeInForce.TIME_IN_FORCE_FOK,
|
||||
side: Types.Side.SIDE_BUY,
|
||||
marketId: '',
|
||||
remaining: '',
|
||||
})
|
||||
getRejectionReason(
|
||||
{
|
||||
rejectionReason:
|
||||
Types.OrderRejectionReason.ORDER_ERROR_INSUFFICIENT_ASSET_BALANCE,
|
||||
status: Types.OrderStatus.STATUS_REJECTED,
|
||||
id: '',
|
||||
createdAt: undefined,
|
||||
size: '',
|
||||
price: '',
|
||||
timeInForce: Types.OrderTimeInForce.TIME_IN_FORCE_FOK,
|
||||
side: Types.Side.SIDE_BUY,
|
||||
marketId: '',
|
||||
remaining: '',
|
||||
},
|
||||
(v) => v
|
||||
)
|
||||
).toBe('Insufficient asset balance');
|
||||
});
|
||||
|
||||
it('should return the correct rejection reason when order is stopped', () => {
|
||||
expect(
|
||||
getRejectionReason({
|
||||
rejectionReason: null,
|
||||
status: Types.OrderStatus.STATUS_STOPPED,
|
||||
id: '',
|
||||
createdAt: undefined,
|
||||
size: '',
|
||||
price: '',
|
||||
timeInForce: Types.OrderTimeInForce.TIME_IN_FORCE_FOK,
|
||||
side: Types.Side.SIDE_BUY,
|
||||
marketId: '',
|
||||
remaining: '',
|
||||
})
|
||||
).toBe(
|
||||
'Your Fill or Kill (FOK) order was not filled and it has been stopped'
|
||||
);
|
||||
getRejectionReason(
|
||||
{
|
||||
rejectionReason: null,
|
||||
status: Types.OrderStatus.STATUS_STOPPED,
|
||||
id: '',
|
||||
createdAt: undefined,
|
||||
size: '',
|
||||
price: '',
|
||||
timeInForce: Types.OrderTimeInForce.TIME_IN_FORCE_FOK,
|
||||
side: Types.Side.SIDE_BUY,
|
||||
marketId: '',
|
||||
remaining: '',
|
||||
},
|
||||
(v) => v
|
||||
)
|
||||
).toBe('Your {{timeInForce}} order was not filled and it has been stopped');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -40,10 +40,9 @@ import {
|
||||
formatNumber,
|
||||
toBigNum,
|
||||
truncateByChars,
|
||||
formatTrigger,
|
||||
useFormatTrigger,
|
||||
MAXGOINT64,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useAssetsMapProvider } from '@vegaprotocol/assets';
|
||||
import { useEthWithdrawApprovalsStore } from './use-ethereum-withdraw-approvals-store';
|
||||
import { DApp, EXPLORER_TX, useLinks } from '@vegaprotocol/environment';
|
||||
@@ -58,26 +57,31 @@ import { OrderStatusMapping } from '@vegaprotocol/types';
|
||||
import { Size } from '@vegaprotocol/datagrid';
|
||||
import { useWithdrawalApprovalDialog } from './withdrawal-approval-dialog';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import { Trans } from 'react-i18next';
|
||||
import { useT } from './use-t';
|
||||
|
||||
export const getRejectionReason = (
|
||||
order: OrderTxUpdateFieldsFragment
|
||||
order: OrderTxUpdateFieldsFragment,
|
||||
t: ReturnType<typeof useT>
|
||||
): string | null => {
|
||||
switch (order.status) {
|
||||
case Schema.OrderStatus.STATUS_STOPPED:
|
||||
return t(
|
||||
`Your ${
|
||||
Schema.OrderTimeInForceMapping[order.timeInForce]
|
||||
} order was not filled and it has been stopped`
|
||||
`Your {{timeInForce}} order was not filled and it has been stopped`,
|
||||
{
|
||||
timeInForce: Schema.OrderTimeInForceMapping[order.timeInForce],
|
||||
}
|
||||
);
|
||||
default:
|
||||
return order.rejectionReason
|
||||
? t(Schema.OrderRejectionReasonMapping[order.rejectionReason])
|
||||
? Schema.OrderRejectionReasonMapping[order.rejectionReason]
|
||||
: '';
|
||||
}
|
||||
};
|
||||
|
||||
export const getOrderToastTitle = (
|
||||
status?: Schema.OrderStatus
|
||||
status: Schema.OrderStatus | undefined,
|
||||
t: ReturnType<typeof useT>
|
||||
): string | undefined => {
|
||||
if (!status) {
|
||||
return;
|
||||
@@ -212,6 +216,7 @@ const SubmitOrderDetails = ({
|
||||
data: OrderSubmission;
|
||||
order?: OrderTxUpdateFieldsFragment;
|
||||
}) => {
|
||||
const t = useT();
|
||||
const { data: markets } = useMarketsMapProvider();
|
||||
const market = markets?.[order?.marketId || ''];
|
||||
if (!market) return null;
|
||||
@@ -224,9 +229,9 @@ const SubmitOrderDetails = ({
|
||||
<Panel>
|
||||
<h4>
|
||||
{order
|
||||
? t(
|
||||
`Submit order - ${OrderStatusMapping[order.status].toLowerCase()}`
|
||||
)
|
||||
? t(`Submit order - {{status}}`, {
|
||||
status: OrderStatusMapping[order.status].toLowerCase(),
|
||||
})
|
||||
: t('Submit order')}
|
||||
</h4>
|
||||
<p>{market?.tradableInstrument.instrument.code}</p>
|
||||
@@ -255,6 +260,7 @@ const SubmitStopOrderSetup = ({
|
||||
triggerDirection: Schema.StopOrderTriggerDirection;
|
||||
market: Market;
|
||||
}) => {
|
||||
const formatTrigger = useFormatTrigger();
|
||||
if (!market || !stopOrderSetup) return null;
|
||||
|
||||
const { price, size, side } = stopOrderSetup.orderSubmission;
|
||||
@@ -294,6 +300,7 @@ const SubmitStopOrderSetup = ({
|
||||
};
|
||||
|
||||
const SubmitStopOrderDetails = ({ data }: { data: StopOrdersSubmission }) => {
|
||||
const t = useT();
|
||||
const { data: markets } = useMarketsMapProvider();
|
||||
const marketId =
|
||||
data.fallsBelow?.orderSubmission.marketId ||
|
||||
@@ -335,6 +342,7 @@ const EditOrderDetails = ({
|
||||
data: OrderAmendment;
|
||||
order?: OrderTxUpdateFieldsFragment;
|
||||
}) => {
|
||||
const t = useT();
|
||||
const { data: orderById } = useOrderByIdQuery({
|
||||
variables: { orderId: data.orderId },
|
||||
fetchPolicy: 'no-cache',
|
||||
@@ -376,7 +384,9 @@ const EditOrderDetails = ({
|
||||
<Panel title={data.orderId}>
|
||||
<h4>
|
||||
{order
|
||||
? t(`Edit order - ${OrderStatusMapping[order.status].toLowerCase()}`)
|
||||
? t(`Edit order - {{status}}`, {
|
||||
status: OrderStatusMapping[order.status].toLowerCase(),
|
||||
})
|
||||
: t('Edit order')}
|
||||
</h4>
|
||||
<p>{market?.tradableInstrument.instrument.code}</p>
|
||||
@@ -395,6 +405,7 @@ const CancelOrderDetails = ({
|
||||
orderId: string;
|
||||
order?: OrderTxUpdateFieldsFragment;
|
||||
}) => {
|
||||
const t = useT();
|
||||
const { data: orderById } = useOrderByIdQuery({
|
||||
variables: { orderId },
|
||||
});
|
||||
@@ -421,9 +432,9 @@ const CancelOrderDetails = ({
|
||||
<Panel title={orderId}>
|
||||
<h4>
|
||||
{order
|
||||
? t(
|
||||
`Cancel order - ${OrderStatusMapping[order.status].toLowerCase()}`
|
||||
)
|
||||
? t(`Cancel order - {{status}}`, {
|
||||
status: OrderStatusMapping[order.status].toLowerCase(),
|
||||
})
|
||||
: t('Cancel order')}
|
||||
</h4>
|
||||
<p>{market?.tradableInstrument.instrument.code}</p>
|
||||
@@ -435,6 +446,8 @@ const CancelOrderDetails = ({
|
||||
};
|
||||
|
||||
const CancelStopOrderDetails = ({ stopOrderId }: { stopOrderId: string }) => {
|
||||
const t = useT();
|
||||
const formatTrigger = useFormatTrigger();
|
||||
const { data: orderById } = useStopOrderByIdQuery({
|
||||
variables: { stopOrderId },
|
||||
});
|
||||
@@ -473,6 +486,7 @@ const CancelStopOrderDetails = ({ stopOrderId }: { stopOrderId: string }) => {
|
||||
};
|
||||
|
||||
export const VegaTransactionDetails = ({ tx }: { tx: VegaStoredTxState }) => {
|
||||
const t = useT();
|
||||
const { data: assets } = useAssetsMapProvider();
|
||||
const { data: markets } = useMarketsMapProvider();
|
||||
|
||||
@@ -480,14 +494,17 @@ export const VegaTransactionDetails = ({ tx }: { tx: VegaStoredTxState }) => {
|
||||
const transactionDetails = tx.body;
|
||||
const asset = assets?.[transactionDetails.withdrawSubmission.asset];
|
||||
if (asset) {
|
||||
const num = formatNumber(
|
||||
const amount = formatNumber(
|
||||
toBigNum(transactionDetails.withdrawSubmission.amount, asset.decimals),
|
||||
asset.decimals
|
||||
);
|
||||
return (
|
||||
<Panel>
|
||||
<strong>
|
||||
{t('Withdraw')} {num} {asset.symbol}
|
||||
{t('Withdraw {{amount}} {{symbol}}', {
|
||||
amount,
|
||||
symbol: asset.symbol,
|
||||
})}
|
||||
</strong>
|
||||
</Panel>
|
||||
);
|
||||
@@ -526,7 +543,10 @@ export const VegaTransactionDetails = ({ tx }: { tx: VegaStoredTxState }) => {
|
||||
if (marketName) {
|
||||
return (
|
||||
<Panel>
|
||||
{t('Cancel all orders for')} <strong>{marketName}</strong>
|
||||
<Trans
|
||||
defaults="Cancel all orders for <strong>{{marketName}}</strong>"
|
||||
values={{ marketName }}
|
||||
/>
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
@@ -556,7 +576,10 @@ export const VegaTransactionDetails = ({ tx }: { tx: VegaStoredTxState }) => {
|
||||
if (marketName) {
|
||||
return (
|
||||
<Panel>
|
||||
{t('Cancel all stop orders for')} <strong>{marketName}</strong>
|
||||
<Trans
|
||||
defaults="Cancel all stop orders for <strong>{{marketName}}</strong>"
|
||||
values={{ marketName }}
|
||||
/>
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
@@ -584,8 +607,12 @@ export const VegaTransactionDetails = ({ tx }: { tx: VegaStoredTxState }) => {
|
||||
if (market) {
|
||||
return (
|
||||
<Panel>
|
||||
{t('Close position for')}{' '}
|
||||
<strong>{market.tradableInstrument.instrument.code}</strong>
|
||||
<Trans
|
||||
defaults="Close position for <strong>{{instrumentCode}}</strong>"
|
||||
values={{
|
||||
instrumentCode: market.tradableInstrument.instrument.code,
|
||||
}}
|
||||
/>
|
||||
{tx.order?.remaining && (
|
||||
<p>
|
||||
{t('Filled')}{' '}
|
||||
@@ -621,9 +648,7 @@ export const VegaTransactionDetails = ({ tx }: { tx: VegaStoredTxState }) => {
|
||||
return (
|
||||
<Panel>
|
||||
<h4>{t('Transfer')}</h4>
|
||||
<p>
|
||||
{t('To')} {truncateByChars(to)}
|
||||
</p>
|
||||
<p>{t('To {{address}}', { address: truncateByChars(to) })}</p>
|
||||
<p>
|
||||
{value} {transferAsset.symbol}
|
||||
</p>
|
||||
@@ -637,19 +662,25 @@ export const VegaTransactionDetails = ({ tx }: { tx: VegaStoredTxState }) => {
|
||||
|
||||
type VegaTxToastContentProps = { tx: VegaStoredTxState };
|
||||
|
||||
const VegaTxRequestedToastContent = ({ tx }: VegaTxToastContentProps) => (
|
||||
<>
|
||||
<ToastHeading>{t('Action required')}</ToastHeading>
|
||||
<p>
|
||||
{t(
|
||||
'Please go to your Vega wallet application and approve or reject the transaction.'
|
||||
)}
|
||||
</p>
|
||||
<VegaTransactionDetails tx={tx} />
|
||||
</>
|
||||
);
|
||||
const VegaTxRequestedToastContent = ({ tx }: VegaTxToastContentProps) => {
|
||||
const t = useT();
|
||||
return (
|
||||
<>
|
||||
<ToastHeading>{t('Action required')}</ToastHeading>
|
||||
<p>
|
||||
{t(
|
||||
'Please go to your Vega wallet application and approve or reject the transaction.'
|
||||
)}
|
||||
</p>
|
||||
<VegaTransactionDetails tx={tx} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const VegaTxPendingToastContentProps = ({ tx }: VegaTxToastContentProps) => {
|
||||
const VegaTxPendingToastContentProps = (
|
||||
{ tx }: VegaTxToastContentProps,
|
||||
t: ReturnType<typeof useT>
|
||||
) => {
|
||||
const explorerLink = useLinks(DApp.Explorer);
|
||||
return (
|
||||
<>
|
||||
@@ -671,6 +702,7 @@ const VegaTxPendingToastContentProps = ({ tx }: VegaTxToastContentProps) => {
|
||||
};
|
||||
|
||||
const VegaTxCompleteToastsContent = ({ tx }: VegaTxToastContentProps) => {
|
||||
const t = useT();
|
||||
const { createEthWithdrawalApproval } = useEthWithdrawApprovalsStore(
|
||||
(state) => ({
|
||||
createEthWithdrawalApproval: state.create,
|
||||
@@ -696,31 +728,13 @@ const VegaTxCompleteToastsContent = ({ tx }: VegaTxToastContentProps) => {
|
||||
</p>
|
||||
);
|
||||
|
||||
const dialogTrigger = (
|
||||
// It has to stay as <a> due to the word breaking issue
|
||||
// eslint-disable-next-line jsx-a11y/anchor-is-valid
|
||||
<a
|
||||
href="#"
|
||||
className="inline underline underline-offset-4 cursor-pointer text-inherit break-words"
|
||||
data-testid="toast-withdrawal-details"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
if (tx.withdrawal?.id) {
|
||||
useWithdrawalApprovalDialog.getState().open(tx.withdrawal?.id);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{t('save your withdrawal details')}
|
||||
</a>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ToastHeading>{t('Funds unlocked')}</ToastHeading>
|
||||
<p>{t('Your funds have been unlocked for withdrawal.')}</p>
|
||||
{tx.txHash && (
|
||||
<ExternalLink
|
||||
className="block mb-[5px] break-all"
|
||||
className="mb-[5px] block break-all"
|
||||
href={explorerLink(EXPLORER_TX.replace(':hash', tx.txHash))}
|
||||
rel="noreferrer"
|
||||
>
|
||||
@@ -729,7 +743,28 @@ const VegaTxCompleteToastsContent = ({ tx }: VegaTxToastContentProps) => {
|
||||
)}
|
||||
{/* TODO: Delay message - This withdrawal is subject to a delay. Come back in 5 days to complete the withdrawal. */}
|
||||
<p className="break-words">
|
||||
{t('You can')} {dialogTrigger} {t('for extra security.')}
|
||||
<Trans
|
||||
defaults="You can <0>save your withdrawal details</0> for extra security."
|
||||
components={[
|
||||
// It has to stay as <a> due to the word breaking issue
|
||||
// eslint-disable-next-line jsx-a11y/anchor-is-valid
|
||||
<a
|
||||
href="#"
|
||||
className="inline underline underline-offset-4 cursor-pointer text-inherit break-words"
|
||||
data-testid="toast-withdrawal-details"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
if (tx.withdrawal?.id) {
|
||||
useWithdrawalApprovalDialog
|
||||
.getState()
|
||||
.open(tx.withdrawal?.id);
|
||||
}
|
||||
}}
|
||||
>
|
||||
save your withdrawal details
|
||||
</a>,
|
||||
]}
|
||||
/>
|
||||
</p>
|
||||
<VegaTransactionDetails tx={tx} />
|
||||
{completeWithdrawalButton}
|
||||
@@ -738,26 +773,31 @@ const VegaTxCompleteToastsContent = ({ tx }: VegaTxToastContentProps) => {
|
||||
}
|
||||
|
||||
if (tx.order && tx.order.rejectionReason) {
|
||||
const rejectionReason = getRejectionReason(tx.order);
|
||||
const rejectionReason = getRejectionReason(tx.order, t);
|
||||
return (
|
||||
<>
|
||||
<ToastHeading>{getOrderToastTitle(tx.order.status)}</ToastHeading>
|
||||
<ToastHeading>{getOrderToastTitle(tx.order.status, t)}</ToastHeading>
|
||||
{rejectionReason ? (
|
||||
<p>
|
||||
{t('Your order has been %s because: %s', [
|
||||
tx.order.status === Schema.OrderStatus.STATUS_STOPPED
|
||||
? 'stopped'
|
||||
: 'rejected',
|
||||
rejectionReason,
|
||||
])}
|
||||
{tx.order.status === Schema.OrderStatus.STATUS_STOPPED
|
||||
? t('Your order has been stopped because: {{rejectionReason}}', {
|
||||
nsSeparator: '*',
|
||||
replace: {
|
||||
rejectionReason,
|
||||
},
|
||||
})
|
||||
: t('Your order has been rejected because: {{rejectionReason}}', {
|
||||
nsSeparator: '*',
|
||||
replace: {
|
||||
rejectionReason,
|
||||
},
|
||||
})}
|
||||
</p>
|
||||
) : (
|
||||
<p>
|
||||
{t('Your order has been %s.', [
|
||||
tx.order.status === Schema.OrderStatus.STATUS_STOPPED
|
||||
? 'stopped'
|
||||
: 'rejected',
|
||||
])}
|
||||
{tx.order.status === Schema.OrderStatus.STATUS_STOPPED
|
||||
? t('Your order has been stopped')
|
||||
: t('Your order has been rejected')}
|
||||
</p>
|
||||
)}
|
||||
{tx.txHash && (
|
||||
@@ -778,7 +818,7 @@ const VegaTxCompleteToastsContent = ({ tx }: VegaTxToastContentProps) => {
|
||||
if (isOrderSubmissionTransaction(tx.body) && tx.order?.rejectionReason) {
|
||||
return (
|
||||
<div>
|
||||
<h3 className="font-bold">{getOrderToastTitle(tx.order.status)}</h3>
|
||||
<h3 className="font-bold">{getOrderToastTitle(tx.order.status, t)}</h3>
|
||||
<p>{t('Your order was rejected.')}</p>
|
||||
{tx.txHash && (
|
||||
<p className="break-all">
|
||||
@@ -799,7 +839,7 @@ const VegaTxCompleteToastsContent = ({ tx }: VegaTxToastContentProps) => {
|
||||
return (
|
||||
<div>
|
||||
<h3 className="font-bold">{t('Transfer complete')}</h3>
|
||||
<p>{t('Your transaction has been confirmed ')}</p>
|
||||
<p>{t('Your transaction has been confirmed')}</p>
|
||||
{tx.txHash && (
|
||||
<p className="break-all">
|
||||
<ExternalLink
|
||||
@@ -819,10 +859,10 @@ const VegaTxCompleteToastsContent = ({ tx }: VegaTxToastContentProps) => {
|
||||
<>
|
||||
<ToastHeading>
|
||||
{tx.order?.status
|
||||
? getOrderToastTitle(tx.order.status)
|
||||
? getOrderToastTitle(tx.order.status, t)
|
||||
: t('Confirmed')}
|
||||
</ToastHeading>
|
||||
<p>{t('Your transaction has been confirmed ')}</p>
|
||||
<p>{t('Your transaction has been confirmed')}</p>
|
||||
{tx.txHash && (
|
||||
<p className="break-all">
|
||||
<ExternalLink
|
||||
@@ -839,6 +879,7 @@ const VegaTxCompleteToastsContent = ({ tx }: VegaTxToastContentProps) => {
|
||||
};
|
||||
|
||||
const VegaTxErrorToastContent = ({ tx }: VegaTxToastContentProps) => {
|
||||
const t = useT();
|
||||
let label = t('Error occurred');
|
||||
let errorMessage =
|
||||
tx.error instanceof WalletError
|
||||
@@ -847,7 +888,7 @@ const VegaTxErrorToastContent = ({ tx }: VegaTxToastContentProps) => {
|
||||
|
||||
const reconnectVegaWallet = useReconnectVegaWallet();
|
||||
|
||||
const orderRejection = tx.order && getRejectionReason(tx.order);
|
||||
const orderRejection = tx.order && getRejectionReason(tx.order, t);
|
||||
const walletNoConnectionCodes = [
|
||||
ClientErrors.NO_SERVICE.code,
|
||||
ClientErrors.NO_CLIENT.code,
|
||||
@@ -857,10 +898,13 @@ const VegaTxErrorToastContent = ({ tx }: VegaTxToastContentProps) => {
|
||||
walletNoConnectionCodes.includes(tx.error.code);
|
||||
|
||||
if (orderRejection) {
|
||||
label = getOrderToastTitle(tx.order?.status) || t('Order rejected');
|
||||
errorMessage = t('Your order has been rejected because: %s', [
|
||||
orderRejection || tx.order?.rejectionReason || ' ',
|
||||
]);
|
||||
label = getOrderToastTitle(tx.order?.status, t) || t('Order rejected');
|
||||
errorMessage = t(
|
||||
'Your order has been rejected because: {{rejectionReason}}',
|
||||
{
|
||||
rejectionReason: orderRejection || tx.order?.rejectionReason || ' ',
|
||||
}
|
||||
);
|
||||
}
|
||||
if (walletError) {
|
||||
label = t('Wallet disconnected');
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useLocalStorage } from '@vegaprotocol/react-helpers';
|
||||
import {
|
||||
Dialog,
|
||||
@@ -16,6 +15,7 @@ import type { Web3ReactHooks } from '@web3-react/core';
|
||||
import { useWeb3ConnectStore } from './web3-connect-store';
|
||||
import { theme } from '@vegaprotocol/tailwindcss-config';
|
||||
import classNames from 'classnames';
|
||||
import { useT } from './use-t';
|
||||
|
||||
interface Web3ConnectDialogProps {
|
||||
dialogOpen: boolean;
|
||||
@@ -29,33 +29,36 @@ export const Web3ConnectDialog = ({
|
||||
setDialogOpen,
|
||||
connectors,
|
||||
desiredChainId,
|
||||
}: Web3ConnectDialogProps) => (
|
||||
<Dialog
|
||||
open={dialogOpen}
|
||||
onChange={setDialogOpen}
|
||||
onInteractOutside={(e) => {
|
||||
// do not close dialog when clicked outside (wallet connect modal)
|
||||
e.preventDefault();
|
||||
}}
|
||||
intent={Intent.None}
|
||||
title={t('Connect to your Ethereum wallet')}
|
||||
size="small"
|
||||
>
|
||||
<ul className="grid grid-cols-2 gap-2" data-testid="web3-connector-list">
|
||||
{connectors.map((connector, i) => (
|
||||
<li key={i} className="mb-2 last:mb-0">
|
||||
<ConnectButton
|
||||
connector={connector}
|
||||
desiredChainId={desiredChainId}
|
||||
onClick={() => {
|
||||
setDialogOpen(false);
|
||||
}}
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</Dialog>
|
||||
);
|
||||
}: Web3ConnectDialogProps) => {
|
||||
const t = useT();
|
||||
return (
|
||||
<Dialog
|
||||
open={dialogOpen}
|
||||
onChange={setDialogOpen}
|
||||
onInteractOutside={(e) => {
|
||||
// do not close dialog when clicked outside (wallet connect modal)
|
||||
e.preventDefault();
|
||||
}}
|
||||
intent={Intent.None}
|
||||
title={t('Connect to your Ethereum wallet')}
|
||||
size="small"
|
||||
>
|
||||
<ul className="grid grid-cols-2 gap-2" data-testid="web3-connector-list">
|
||||
{connectors.map((connector, i) => (
|
||||
<li key={i} className="mb-2 last:mb-0">
|
||||
<ConnectButton
|
||||
connector={connector}
|
||||
desiredChainId={desiredChainId}
|
||||
onClick={() => {
|
||||
setDialogOpen(false);
|
||||
}}
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
const ConnectButton = ({
|
||||
connector,
|
||||
@@ -66,9 +69,10 @@ const ConnectButton = ({
|
||||
desiredChainId?: number;
|
||||
onClick?: () => void;
|
||||
}) => {
|
||||
const t = useT();
|
||||
const [connectorInstance, { useIsActivating }] = connector;
|
||||
const isActivating = useIsActivating();
|
||||
const info = getConnectorInfo(connectorInstance);
|
||||
const info = getConnectorInfo(connectorInstance, t);
|
||||
const [, setEagerConnector] = useLocalStorage(ETHEREUM_EAGER_CONNECT);
|
||||
return (
|
||||
<button
|
||||
@@ -120,7 +124,7 @@ export const Web3ConnectUncontrolledDialog = () => {
|
||||
);
|
||||
};
|
||||
|
||||
function getConnectorInfo(connector: Connector) {
|
||||
function getConnectorInfo(connector: Connector, t: ReturnType<typeof useT>) {
|
||||
if (connector instanceof MetaMask) {
|
||||
return {
|
||||
icon: <VegaIcon name={VegaIconNames.METAMASK} size={32} />,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import {
|
||||
Button,
|
||||
CopyWithTooltip,
|
||||
@@ -13,6 +12,7 @@ import {
|
||||
import { useWithdrawalApprovalQuery } from './__generated__/WithdrawalApproval';
|
||||
import omit from 'lodash/omit';
|
||||
import { create } from 'zustand';
|
||||
import { useT } from './use-t';
|
||||
|
||||
type WithdrawalApprovalDialogProps = {
|
||||
withdrawalId: string | undefined;
|
||||
@@ -28,6 +28,7 @@ export const WithdrawalApprovalDialog = ({
|
||||
onChange,
|
||||
asJson,
|
||||
}: WithdrawalApprovalDialogProps) => {
|
||||
const t = useT();
|
||||
return (
|
||||
<Dialog
|
||||
title={t('Save withdrawal details')}
|
||||
@@ -48,8 +49,7 @@ export const WithdrawalApprovalDialog = ({
|
||||
<div className="pr-8">
|
||||
<p>
|
||||
{t(
|
||||
`If the network is reset or has an outage, records of your withdrawal
|
||||
may be lost. It is recommended that you save these details in a safe place so you can still complete your withdrawal.`
|
||||
`If the network is reset or has an outage, records of your withdrawal may be lost. It is recommended that you save these details in a safe place so you can still complete your withdrawal.`
|
||||
)}
|
||||
</p>
|
||||
{withdrawalId ? (
|
||||
@@ -80,16 +80,20 @@ type WithdrawalApprovalDialogContentProps = {
|
||||
asJson: boolean;
|
||||
};
|
||||
|
||||
const NoDataContent = ({ msg = t('No data') }) => (
|
||||
<div className="py-12" data-testid="splash">
|
||||
<Splash>{msg}</Splash>
|
||||
</div>
|
||||
);
|
||||
const NoDataContent = ({ msg }: { msg?: string }) => {
|
||||
const t = useT();
|
||||
return (
|
||||
<div className="py-12" data-testid="splash">
|
||||
<Splash>{msg || t('No data')}</Splash>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const WithdrawalApprovalDialogContent = ({
|
||||
withdrawalId,
|
||||
asJson,
|
||||
}: WithdrawalApprovalDialogContentProps) => {
|
||||
const t = useT();
|
||||
const { data, loading } = useWithdrawalApprovalQuery({
|
||||
variables: {
|
||||
withdrawalId,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import {
|
||||
getDateTimeFormat,
|
||||
resolveNetworkName,
|
||||
@@ -13,12 +12,14 @@ import {
|
||||
import { useEthereumConfig } from './use-ethereum-config';
|
||||
import { Button, useToasts } from '@vegaprotocol/ui-toolkit';
|
||||
import { useWeb3ConnectStore } from './web3-connect-store';
|
||||
import { useT } from './use-t';
|
||||
|
||||
export const VerificationStatus = ({
|
||||
state,
|
||||
}: {
|
||||
state: EthWithdrawalApprovalState;
|
||||
}) => {
|
||||
const t = useT();
|
||||
const { config } = useEthereumConfig();
|
||||
const openDialog = useWeb3ConnectStore((state) => state.open);
|
||||
const remove = useToasts((state) => state.remove);
|
||||
@@ -38,9 +39,14 @@ export const VerificationStatus = ({
|
||||
return state.failureReason === WithdrawalFailure.NoConnection ? (
|
||||
<>
|
||||
<p>
|
||||
{t('To complete this withdrawal, connect the Ethereum wallet %s', [
|
||||
truncateByChars(state.withdrawal.details?.receiverAddress || ' '),
|
||||
])}
|
||||
{t(
|
||||
'To complete this withdrawal, connect the Ethereum wallet {{receiverAddress}}',
|
||||
{
|
||||
receiverAddress: truncateByChars(
|
||||
state.withdrawal.details?.receiverAddress || ' '
|
||||
),
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
<Button
|
||||
onClick={() => {
|
||||
@@ -56,9 +62,12 @@ export const VerificationStatus = ({
|
||||
<>
|
||||
<p>{t('Your Ethereum wallet is connected to the wrong network.')}</p>
|
||||
<p className="mt-2">
|
||||
{t('Go to your Ethereum wallet and connect to the network %s', [
|
||||
resolveNetworkName(config?.chain_id),
|
||||
])}
|
||||
{t(
|
||||
'Go to your Ethereum wallet and connect to the network {{networkName}}',
|
||||
{
|
||||
networkName: resolveNetworkName(config?.chain_id),
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
@@ -75,7 +84,9 @@ export const VerificationStatus = ({
|
||||
return (
|
||||
<>
|
||||
<p>{t("The amount you're withdrawing has triggered a time delay")}</p>
|
||||
<p>{t(`Cannot be completed until ${formattedTime}`)}</p>
|
||||
<p>
|
||||
{t(`Cannot be completed until {{time}}`, { time: formattedTime })}
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1 +1,15 @@
|
||||
import '@testing-library/jest-dom';
|
||||
|
||||
import { locales } from '@vegaprotocol/i18n';
|
||||
import i18n from 'i18next';
|
||||
import { initReactI18next } from 'react-i18next';
|
||||
|
||||
// Set up i18n instance so that components have the correct default
|
||||
// en translations
|
||||
i18n.use(initReactI18next).init({
|
||||
// we init with resources
|
||||
resources: locales,
|
||||
fallbackLng: 'en',
|
||||
ns: ['web3'],
|
||||
defaultNS: 'web3',
|
||||
});
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { Asset } from '@vegaprotocol/assets';
|
||||
import { AssetOption } from '@vegaprotocol/assets';
|
||||
import {
|
||||
ethereumAddress,
|
||||
minSafe,
|
||||
useEthereumAddress,
|
||||
useRequired,
|
||||
useMinSafe,
|
||||
removeDecimal,
|
||||
required,
|
||||
isAssetTypeERC20,
|
||||
formatNumber,
|
||||
} from '@vegaprotocol/utils';
|
||||
@@ -23,7 +23,6 @@ import {
|
||||
import { useWeb3React } from '@web3-react/core';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { useEffect, type ButtonHTMLAttributes } from 'react';
|
||||
import type { ControllerRenderProps } from 'react-hook-form';
|
||||
import { formatDistanceToNow } from 'date-fns';
|
||||
import { useForm, Controller, useWatch } from 'react-hook-form';
|
||||
import { WithdrawLimits } from './withdraw-limits';
|
||||
@@ -112,6 +111,10 @@ export const WithdrawForm = ({
|
||||
onSelectAsset,
|
||||
submitWithdraw,
|
||||
}: WithdrawFormProps) => {
|
||||
const ethereumAddress = useEthereumAddress();
|
||||
const required = useRequired();
|
||||
const minSafe = useMinSafe();
|
||||
|
||||
const { account: address } = useWeb3React();
|
||||
const {
|
||||
register,
|
||||
@@ -150,36 +153,6 @@ export const WithdrawForm = ({
|
||||
trigger('to');
|
||||
}, [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 =
|
||||
Boolean(delay) &&
|
||||
Boolean(selectedAsset) &&
|
||||
@@ -189,7 +162,7 @@ export const WithdrawForm = ({
|
||||
<>
|
||||
<div className="mb-4 text-sm">
|
||||
<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 2 - Transfer funds to your Ethereum wallet')}</li>
|
||||
</ol>
|
||||
@@ -208,7 +181,29 @@ export const WithdrawForm = ({
|
||||
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 && (
|
||||
<TradingInputError intent="danger">
|
||||
@@ -314,7 +309,7 @@ const UseButton = (props: UseButtonProps) => {
|
||||
<button
|
||||
{...props}
|
||||
type="button"
|
||||
className="absolute top-0 right-0 ml-auto text-sm underline"
|
||||
className="absolute right-0 top-0 ml-auto text-sm underline"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user