feat(wallet-bonding): bonding page, new bond form wip
This commit is contained in:
@@ -49,7 +49,7 @@
|
||||
"string-to-color": "^2.2.2",
|
||||
"use-clipboard-copy": "^0.2.0",
|
||||
"uuid": "^8.3.2",
|
||||
"yup": "^0.32.9"
|
||||
"yup": "^1.0.0-beta.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.15.0",
|
||||
|
||||
@@ -3,7 +3,57 @@ import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import { List, ListItem, ListItemIcon, ListItemText } from '@mui/material';
|
||||
import { AccountBalanceWalletOutlined, ArrowBack, ArrowForward, Description, Settings } from '@mui/icons-material';
|
||||
import { AppContext } from '../context/main';
|
||||
import { Bond, Delegate, Unbond } from '../svg-icons';
|
||||
import { Bond, Delegate, Unbond, Bonding } from '../svg-icons';
|
||||
|
||||
const routesSchema = [
|
||||
{
|
||||
label: 'Balance',
|
||||
route: '/balance',
|
||||
Icon: AccountBalanceWalletOutlined,
|
||||
},
|
||||
{
|
||||
label: 'Send',
|
||||
route: '/send',
|
||||
Icon: ArrowForward,
|
||||
},
|
||||
{
|
||||
label: 'Receive',
|
||||
route: '/receive',
|
||||
Icon: ArrowBack,
|
||||
},
|
||||
{
|
||||
label: 'Bond',
|
||||
route: '/bond',
|
||||
Icon: Bond,
|
||||
},
|
||||
{
|
||||
label: 'Bonding',
|
||||
route: '/bonding',
|
||||
Icon: Bonding,
|
||||
},
|
||||
{
|
||||
label: 'Unbond',
|
||||
route: '/unbond',
|
||||
Icon: Unbond,
|
||||
},
|
||||
{
|
||||
label: 'Delegation',
|
||||
route: '/delegation',
|
||||
Icon: Delegate,
|
||||
},
|
||||
{
|
||||
label: 'Docs',
|
||||
route: '/docs',
|
||||
Icon: Description,
|
||||
mode: 'dev',
|
||||
},
|
||||
{
|
||||
label: 'Admin',
|
||||
route: '/admin',
|
||||
Icon: Settings,
|
||||
mode: 'admin',
|
||||
},
|
||||
];
|
||||
|
||||
export const Nav = () => {
|
||||
const location = useLocation();
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
export * from './main';
|
||||
export * from './auth';
|
||||
export * from './accounts';
|
||||
export * from './bonding';
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ const TxResultMock: TransactionExecuteResult = {
|
||||
fee: { amount: '1', denom: 'NYM' },
|
||||
};
|
||||
|
||||
export const BondingContextProvider = ({
|
||||
export const MockBondingContextProvider = ({
|
||||
network,
|
||||
children,
|
||||
}: {
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { Divider, Stack, Typography } from '@mui/material';
|
||||
import { SimpleModal } from '../../components/Modals/SimpleModal';
|
||||
import { AmountData, NodeType } from './types';
|
||||
import { CurrencyInput } from './bond-form/CurrencyInput';
|
||||
import { AppContext } from '../../context';
|
||||
import { amountSchema } from './amountSchema';
|
||||
import { TokenPoolSelector } from '../../components';
|
||||
import { TextFieldInput } from './bond-form';
|
||||
import { checkHasEnoughFunds, checkHasEnoughLockedTokens } from '../../utils';
|
||||
|
||||
export interface Props {
|
||||
nodeType: NodeType;
|
||||
open: boolean;
|
||||
onClose?: () => void;
|
||||
onSubmit: (data: AmountData) => Promise<void>;
|
||||
header?: string;
|
||||
buttonText?: string;
|
||||
}
|
||||
|
||||
export const AmountModal = ({ open, onClose, onSubmit, header, buttonText, nodeType }: Props) => {
|
||||
const {
|
||||
control,
|
||||
setValue,
|
||||
setError,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
} = useForm<AmountData>({
|
||||
resolver: yupResolver(amountSchema),
|
||||
defaultValues: {
|
||||
tokenPool: 'balance',
|
||||
profitMargin: 10,
|
||||
},
|
||||
});
|
||||
|
||||
const { userBalance, clientDetails } = useContext(AppContext);
|
||||
|
||||
const onSubmitForm = async (data: AmountData) => {
|
||||
if (data.tokenPool === 'balance' && !(await checkHasEnoughFunds(data.amount.amount || ''))) {
|
||||
return setError('amount.amount', { message: 'Not enough funds in wallet' });
|
||||
}
|
||||
|
||||
if (data.tokenPool === 'locked' && !(await checkHasEnoughLockedTokens(data.amount.amount || ''))) {
|
||||
return setError('amount.amount', { message: 'Not enough locked tokens' });
|
||||
}
|
||||
return onSubmit(data);
|
||||
};
|
||||
|
||||
return (
|
||||
<SimpleModal
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
onOk={handleSubmit(onSubmitForm)}
|
||||
header={header || 'Bond'}
|
||||
subHeader="Step 2/2"
|
||||
okLabel={buttonText || 'Next'}
|
||||
>
|
||||
<form>
|
||||
{nodeType === 'mixnode' && (
|
||||
<TextFieldInput
|
||||
name="profitMargin"
|
||||
control={control}
|
||||
defaultValue=""
|
||||
label="Profit Margin"
|
||||
placeholder="Profit Margin"
|
||||
error={Boolean(errors.profitMargin)}
|
||||
helperText={errors.profitMargin ? errors.profitMargin.message : 'Default is 10%'}
|
||||
required
|
||||
muiTextFieldProps={{ fullWidth: true }}
|
||||
sx={{ mb: 2.5 }}
|
||||
/>
|
||||
)}
|
||||
<Stack direction="row" spacing={2}>
|
||||
{userBalance.originalVesting && (
|
||||
<TokenPoolSelector onSelect={(pool) => setValue('tokenPool', pool)} disabled={false} />
|
||||
)}
|
||||
<CurrencyInput
|
||||
control={control}
|
||||
required
|
||||
fullWidth
|
||||
label="Amount"
|
||||
name="amount"
|
||||
currencyDenom={clientDetails?.denom}
|
||||
errorMessage={errors.amount?.amount?.message}
|
||||
/>
|
||||
</Stack>
|
||||
</form>
|
||||
<Stack direction="row" justifyContent="space-between" mt={3}>
|
||||
<Typography fontWeight={600}>Account balance</Typography>
|
||||
<Typography fontWeight={600}>{userBalance.balance?.printable_balance || 0}</Typography>
|
||||
</Stack>
|
||||
<Divider sx={{ my: 1 }} />
|
||||
<Typography fontWeight={400}>Est. fee for this transaction will be cauculated in the next page</Typography>
|
||||
</SimpleModal>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,184 @@
|
||||
import React, { useContext, useEffect, useReducer } from 'react';
|
||||
import { Box, Button, Typography } from '@mui/material';
|
||||
import { Gateway, MajorCurrencyAmount, MixNode } from '@nymproject/types';
|
||||
import { NymCard } from '../../components';
|
||||
import { NodeIdentityModal } from './NodeIdentityModal';
|
||||
import { ACTIONTYPE, AmountData, BondState, FormStep, NodeData, NodeType } from './types';
|
||||
import { AmountModal } from './AmountModal';
|
||||
import { AppContext } from '../../context';
|
||||
import { SummaryModal } from './SummaryModal';
|
||||
import { bond, vestingBond } from '../../requests';
|
||||
import { TBondArgs } from '../../types';
|
||||
import { SimpleModal } from '../../components/Modals/SimpleModal';
|
||||
|
||||
const initialState: BondState = {
|
||||
showModal: false,
|
||||
formStep: 1,
|
||||
};
|
||||
|
||||
function reducer(state: BondState, action: ACTIONTYPE) {
|
||||
let step;
|
||||
switch (action.type) {
|
||||
case 'change_bond_type':
|
||||
return { ...state, type: action.payload };
|
||||
case 'set_node_data':
|
||||
return { ...state, nodeData: action.payload };
|
||||
case 'set_amount_data':
|
||||
return { ...state, amountData: action.payload };
|
||||
case 'set_step':
|
||||
return { ...state, formStep: action.payload };
|
||||
case 'set_tx':
|
||||
return { ...state, tx: action.payload };
|
||||
case 'next_step':
|
||||
step = state.formStep + 1;
|
||||
return { ...state, formStep: step <= 4 ? (step as FormStep) : 4 };
|
||||
case 'previous_step':
|
||||
step = state.formStep - 1;
|
||||
return { ...state, formStep: step >= 1 ? (step as FormStep) : 1 };
|
||||
case 'show_modal':
|
||||
return { ...state, showModal: true };
|
||||
case 'close_modal':
|
||||
return { ...state, showModal: false };
|
||||
case 'reset':
|
||||
return initialState;
|
||||
default:
|
||||
throw new Error();
|
||||
}
|
||||
}
|
||||
|
||||
export const BondingCard = () => {
|
||||
const [state, dispatch] = useReducer(reducer, initialState);
|
||||
const { formStep, showModal } = state;
|
||||
console.log(state);
|
||||
|
||||
const { userBalance, clientDetails } = useContext(AppContext);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch({ type: 'reset' });
|
||||
}, [clientDetails]);
|
||||
|
||||
const formatData = (nodeType: NodeType, nodeData: NodeData, amountData: AmountData): MixNode | Gateway =>
|
||||
nodeType === 'mixnode'
|
||||
? {
|
||||
host: nodeData.host,
|
||||
mix_port: nodeData.mixPort,
|
||||
verloc_port: nodeData.verlocPort,
|
||||
http_api_port: nodeData.httpApiPort,
|
||||
sphinx_key: nodeData.sphinxKey,
|
||||
identity_key: nodeData.identityKey,
|
||||
version: nodeData.version,
|
||||
profit_margin_percent: amountData.profitMargin as number,
|
||||
}
|
||||
: {
|
||||
host: nodeData.host,
|
||||
mix_port: nodeData.mixPort,
|
||||
clients_port: nodeData.clientsPort,
|
||||
location: nodeData.location as string,
|
||||
sphinx_key: nodeData.sphinxKey,
|
||||
identity_key: nodeData.identityKey,
|
||||
version: nodeData.version,
|
||||
};
|
||||
|
||||
const onSubmit = async () => {
|
||||
const { nodeData, amountData } = state;
|
||||
if (!nodeData || !amountData) {
|
||||
throw new Error('');
|
||||
}
|
||||
const request = amountData.tokenPool === 'balance' ? bond : vestingBond;
|
||||
dispatch({ type: 'next_step' });
|
||||
return request({
|
||||
type: nodeData.nodeType,
|
||||
ownerSignature: nodeData.signature,
|
||||
[nodeData.nodeType]: formatData(nodeData.nodeType, nodeData, amountData),
|
||||
pledge: amountData.amount,
|
||||
} as TBondArgs)
|
||||
.then(async (tx) => {
|
||||
if (amountData.tokenPool === 'balance') {
|
||||
await userBalance.fetchBalance();
|
||||
} else {
|
||||
await userBalance.fetchTokenAllocation();
|
||||
}
|
||||
dispatch({ type: 'set_tx', payload: tx });
|
||||
dispatch({ type: 'next_step' });
|
||||
})
|
||||
.catch(() => {
|
||||
// TODO do something
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<NymCard title="Bonding">
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
pt: 0,
|
||||
}}
|
||||
>
|
||||
<Typography>Bond a node or a gateway</Typography>
|
||||
<Button
|
||||
disabled={false}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
type="button"
|
||||
disableElevation
|
||||
onClick={() => dispatch({ type: 'show_modal' })}
|
||||
sx={{ py: 1.5, px: 3 }}
|
||||
>
|
||||
Bond
|
||||
</Button>
|
||||
</Box>
|
||||
{formStep === 1 && showModal && (
|
||||
<NodeIdentityModal
|
||||
open={formStep === 1 && showModal}
|
||||
onClose={() => dispatch({ type: 'reset' })}
|
||||
onSubmit={async (data) => {
|
||||
dispatch({ type: 'set_node_data', payload: data });
|
||||
dispatch({ type: 'next_step' });
|
||||
}}
|
||||
header="Bond"
|
||||
buttonText="Next"
|
||||
/>
|
||||
)}
|
||||
{formStep === 2 && showModal && (
|
||||
<AmountModal
|
||||
open={formStep === 2 && showModal}
|
||||
onClose={() => dispatch({ type: 'reset' })}
|
||||
onSubmit={async (data) => {
|
||||
dispatch({ type: 'set_amount_data', payload: data });
|
||||
dispatch({ type: 'next_step' });
|
||||
}}
|
||||
header="Bond"
|
||||
buttonText="Next"
|
||||
nodeType={state.nodeData?.nodeType || 'mixnode'}
|
||||
/>
|
||||
)}
|
||||
{formStep === 3 && showModal && (
|
||||
<SummaryModal
|
||||
open={formStep === 3 && showModal}
|
||||
onClose={() => dispatch({ type: 'reset' })}
|
||||
onSubmit={onSubmit}
|
||||
header="Bond details"
|
||||
buttonText="Confirm"
|
||||
nodeType={state.nodeData?.nodeType as NodeType}
|
||||
identityKey={state.nodeData?.identityKey as string}
|
||||
amount={state.amountData?.amount as MajorCurrencyAmount}
|
||||
/>
|
||||
)}
|
||||
{true && (
|
||||
<SimpleModal
|
||||
open={true}
|
||||
onOk={() => {
|
||||
dispatch({ type: 'close_modal' });
|
||||
dispatch({ type: 'reset' });
|
||||
}}
|
||||
header="Bonding successful"
|
||||
okLabel="Done"
|
||||
>
|
||||
Link to transaction on network explorer
|
||||
</SimpleModal>
|
||||
)}
|
||||
</NymCard>
|
||||
);
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import { BondingPage } from './index';
|
||||
import { MockBondingContextProvider } from '../../context/mocks/delegations';
|
||||
import { MockBondingContextProvider } from '../../context/mocks/bonding';
|
||||
|
||||
export default {
|
||||
title: 'Bonding/Flows/Mock',
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import React from 'react';
|
||||
import { Grid } from '@mui/material';
|
||||
import { NymCard } from '../../components';
|
||||
|
||||
export const BondedGatewayCard = () => (
|
||||
<NymCard title="Balance">
|
||||
<Grid container direction="column" spacing={2}>
|
||||
<Grid item>bonded gateway data table</Grid>
|
||||
</Grid>
|
||||
</NymCard>
|
||||
);
|
||||
@@ -0,0 +1,11 @@
|
||||
import React from 'react';
|
||||
import { Alert, Button, Grid, Link, Typography } from '@mui/material';
|
||||
import { NymCard } from '../../components';
|
||||
|
||||
export const BondedMixnodeCard = () => (
|
||||
<NymCard title="Balance">
|
||||
<Grid container direction="column" spacing={2}>
|
||||
<Grid item>bonded mixnode data table</Grid>
|
||||
</Grid>
|
||||
</NymCard>
|
||||
);
|
||||
@@ -0,0 +1,211 @@
|
||||
import React from 'react';
|
||||
import { useForm, useWatch } from 'react-hook-form';
|
||||
import { Stack } from '@mui/material';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { SimpleModal } from '../../components/Modals/SimpleModal';
|
||||
import { NodeData, NodeType } from './types';
|
||||
import { RadioInput, TextFieldInput, CheckboxInput } from './bond-form';
|
||||
import { nodeSchema } from './nodeSchema';
|
||||
|
||||
export interface Props {
|
||||
open: boolean;
|
||||
onClose?: () => void;
|
||||
onSubmit: (data: NodeData) => Promise<void>;
|
||||
header?: string;
|
||||
buttonText?: string;
|
||||
}
|
||||
|
||||
const radioOptions: { label: string; value: NodeType }[] = [
|
||||
{
|
||||
label: 'Mixnode',
|
||||
value: 'mixnode',
|
||||
},
|
||||
{
|
||||
label: 'Gateway',
|
||||
value: 'gateway',
|
||||
},
|
||||
];
|
||||
|
||||
export const NodeIdentityModal = ({ open, onClose, onSubmit, header, buttonText }: Props) => {
|
||||
const {
|
||||
control,
|
||||
getValues,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
} = useForm<NodeData>({
|
||||
defaultValues: {
|
||||
nodeType: radioOptions[0].value,
|
||||
advancedOpt: false,
|
||||
mixPort: 1789,
|
||||
verlocPort: 1790,
|
||||
httpApiPort: 8000,
|
||||
clientsPort: 9000,
|
||||
},
|
||||
resolver: yupResolver(nodeSchema),
|
||||
});
|
||||
|
||||
const nodeType = useWatch({ name: 'nodeType', control });
|
||||
const advancedOpt = useWatch({ name: 'advancedOpt', control });
|
||||
|
||||
const onSubmitForm = (data: NodeData) => {
|
||||
onSubmit(data);
|
||||
};
|
||||
|
||||
return (
|
||||
<SimpleModal
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
onOk={handleSubmit(onSubmitForm)}
|
||||
header={header || 'Bond'}
|
||||
subHeader="Step 1/2"
|
||||
okLabel={buttonText || 'Next'}
|
||||
>
|
||||
<form>
|
||||
<RadioInput
|
||||
name="nodeType"
|
||||
label="Select node type"
|
||||
options={radioOptions}
|
||||
control={control}
|
||||
defaultValue={getValues('nodeType')}
|
||||
muiRadioGroupProps={{ row: true }}
|
||||
/>
|
||||
<TextFieldInput
|
||||
name="identityKey"
|
||||
control={control}
|
||||
defaultValue=""
|
||||
label="Identity Key"
|
||||
placeholder="Identity Key"
|
||||
error={Boolean(errors.identityKey)}
|
||||
helperText={errors.identityKey?.message}
|
||||
required
|
||||
muiTextFieldProps={{ fullWidth: true }}
|
||||
sx={{ mb: 2.5, mt: 1 }}
|
||||
/>
|
||||
<TextFieldInput
|
||||
name="sphinxKey"
|
||||
control={control}
|
||||
defaultValue=""
|
||||
label="Sphinx Key"
|
||||
placeholder="Sphinx Key"
|
||||
error={Boolean(errors.sphinxKey)}
|
||||
helperText={errors.sphinxKey?.message}
|
||||
required
|
||||
muiTextFieldProps={{ fullWidth: true }}
|
||||
sx={{ mb: 2.5 }}
|
||||
/>
|
||||
<TextFieldInput
|
||||
name="signature"
|
||||
control={control}
|
||||
defaultValue=""
|
||||
label="Signature"
|
||||
placeholder="Signature"
|
||||
error={Boolean(errors.signature)}
|
||||
helperText={errors.signature?.message}
|
||||
required
|
||||
muiTextFieldProps={{ fullWidth: true }}
|
||||
sx={{ mb: 2.5 }}
|
||||
/>
|
||||
{nodeType === 'gateway' && (
|
||||
<TextFieldInput
|
||||
name="location"
|
||||
control={control}
|
||||
defaultValue=""
|
||||
label="Location"
|
||||
placeholder="Location"
|
||||
error={Boolean(errors.location)}
|
||||
helperText={errors.location?.message}
|
||||
required
|
||||
muiTextFieldProps={{ fullWidth: true }}
|
||||
sx={{ mb: 2.5 }}
|
||||
/>
|
||||
)}
|
||||
<Stack direction="row" spacing={2}>
|
||||
<TextFieldInput
|
||||
name="host"
|
||||
control={control}
|
||||
defaultValue=""
|
||||
label="Host"
|
||||
placeholder="Host"
|
||||
error={Boolean(errors.host)}
|
||||
helperText={errors.host?.message}
|
||||
required
|
||||
muiTextFieldProps={{ fullWidth: true }}
|
||||
sx={{ mb: 2.5 }}
|
||||
/>
|
||||
<TextFieldInput
|
||||
name="version"
|
||||
control={control}
|
||||
defaultValue=""
|
||||
label="Version"
|
||||
placeholder="Version"
|
||||
error={Boolean(errors.version)}
|
||||
helperText={errors.version?.message}
|
||||
required
|
||||
muiTextFieldProps={{ fullWidth: true }}
|
||||
sx={{ mb: 2.5 }}
|
||||
/>
|
||||
</Stack>
|
||||
<CheckboxInput
|
||||
name="advancedOpt"
|
||||
label="Use advanced options"
|
||||
control={control}
|
||||
defaultValue={false}
|
||||
sx={{ mb: 2.5 }}
|
||||
/>
|
||||
{advancedOpt && (
|
||||
<Stack direction="row" spacing={1.5}>
|
||||
<TextFieldInput
|
||||
name="mixPort"
|
||||
control={control}
|
||||
label="Mix Port"
|
||||
placeholder="Mix Port"
|
||||
error={Boolean(errors.mixPort)}
|
||||
helperText={errors.mixPort?.message && 'A valid port value is required'}
|
||||
required
|
||||
registerOptions={{ valueAsNumber: true }}
|
||||
sx={{ mb: 2.5 }}
|
||||
/>
|
||||
{nodeType === 'mixnode' ? (
|
||||
<>
|
||||
<TextFieldInput
|
||||
name="verlocPort"
|
||||
control={control}
|
||||
label="Verloc Port"
|
||||
placeholder="Verloc Port"
|
||||
error={Boolean(errors.verlocPort)}
|
||||
helperText={errors.verlocPort?.message && 'A valid port value is required'}
|
||||
required
|
||||
registerOptions={{ valueAsNumber: true }}
|
||||
sx={{ mb: 2.5 }}
|
||||
/>
|
||||
<TextFieldInput
|
||||
name="httpApiPort"
|
||||
control={control}
|
||||
label="HTTP API Port"
|
||||
placeholder="HTTP API Port"
|
||||
error={Boolean(errors.httpApiPort)}
|
||||
helperText={errors.httpApiPort?.message && 'A valid port value is required'}
|
||||
required
|
||||
registerOptions={{ valueAsNumber: true }}
|
||||
sx={{ mb: 2.5 }}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<TextFieldInput
|
||||
name="clientsPort"
|
||||
control={control}
|
||||
label="client WS API Port"
|
||||
placeholder="client WS API Port"
|
||||
error={Boolean(errors.clientsPort)}
|
||||
helperText={errors.clientsPort?.message && 'A valid port value is required'}
|
||||
required
|
||||
registerOptions={{ valueAsNumber: true }}
|
||||
sx={{ mb: 2.5 }}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
)}
|
||||
</form>
|
||||
</SimpleModal>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,52 @@
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { Divider, Stack, Typography } from '@mui/material';
|
||||
import { MajorCurrencyAmount } from '@nymproject/types';
|
||||
import { SimpleModal } from '../../components/Modals/SimpleModal';
|
||||
import { getGasFee } from '../../requests';
|
||||
import { NodeType } from './types';
|
||||
import { AppContext } from '../../context';
|
||||
|
||||
export interface Props {
|
||||
open: boolean;
|
||||
onClose?: () => void;
|
||||
onSubmit: () => Promise<void>;
|
||||
header: string;
|
||||
buttonText: string;
|
||||
identityKey: string;
|
||||
nodeType: NodeType;
|
||||
amount: MajorCurrencyAmount;
|
||||
}
|
||||
|
||||
export const SummaryModal = ({ open, onClose, onSubmit, header, buttonText, identityKey, nodeType, amount }: Props) => {
|
||||
const onConfirm = async () => onSubmit();
|
||||
const [fee, setFee] = useState<string>('-');
|
||||
const { clientDetails } = useContext(AppContext);
|
||||
|
||||
const getFee = async (op: 'BondMixnode' | 'BondGateway') => {
|
||||
const res = await getGasFee(op);
|
||||
setFee(`${res.amount} ${clientDetails?.denom}`);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getFee(nodeType === 'mixnode' ? 'BondMixnode' : 'BondGateway');
|
||||
}, [clientDetails, nodeType]);
|
||||
|
||||
return (
|
||||
<SimpleModal open={open} onClose={onClose} onOk={onConfirm} header={header} okLabel={buttonText}>
|
||||
<Stack direction="row" justifyContent="space-between" mt={3}>
|
||||
<Typography fontWeight={600}>Identity Key</Typography>
|
||||
<Typography fontWeight={600}>{identityKey}</Typography>
|
||||
</Stack>
|
||||
<Divider sx={{ my: 1 }} />
|
||||
<Stack direction="row" justifyContent="space-between" mt={3}>
|
||||
<Typography fontWeight={600}>Amount</Typography>
|
||||
<Typography fontWeight={600}>{`${amount.amount} ${amount.denom}`}</Typography>
|
||||
</Stack>
|
||||
<Divider sx={{ my: 1 }} />
|
||||
<Stack direction="row" justifyContent="space-between" mt={3}>
|
||||
<Typography fontWeight={600}>Fee for this operation</Typography>
|
||||
<Typography fontWeight={600}>{fee}</Typography>
|
||||
</Stack>
|
||||
</SimpleModal>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
import { number, object, string } from 'yup';
|
||||
import { validateAmount } from '../../utils';
|
||||
|
||||
export const amountSchema = object().shape({
|
||||
amount: object().shape({
|
||||
amount: string()
|
||||
.required('An amount is required')
|
||||
.test('valid-amount', 'Pledge error', async function isValidAmount(this, value) {
|
||||
const isValid = await validateAmount(value || '', '100');
|
||||
if (!isValid) {
|
||||
return this.createError({ message: 'A valid amount is required (min 100)' });
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
}),
|
||||
profitMargin: number().required('Profit Percentage is required').min(0).max(100),
|
||||
});
|
||||
@@ -0,0 +1,49 @@
|
||||
import * as React from 'react';
|
||||
import { Control, useController } from 'react-hook-form';
|
||||
import { Checkbox, CheckboxProps, FormControlLabel, FormControlLabelProps, FormGroup, SxProps } from '@mui/material';
|
||||
|
||||
interface Props {
|
||||
name: string;
|
||||
label: string;
|
||||
control: Control<any>;
|
||||
defaultValue: boolean;
|
||||
muiCheckboxProps?: CheckboxProps;
|
||||
muiFormControlLabelProps?: FormControlLabelProps;
|
||||
sx?: SxProps;
|
||||
}
|
||||
|
||||
export const CheckboxInput = ({
|
||||
name,
|
||||
control,
|
||||
defaultValue,
|
||||
label,
|
||||
muiCheckboxProps,
|
||||
muiFormControlLabelProps,
|
||||
sx,
|
||||
}: Props) => {
|
||||
const {
|
||||
field: { onChange, onBlur, value, ref },
|
||||
} = useController({
|
||||
name,
|
||||
control,
|
||||
defaultValue,
|
||||
});
|
||||
return (
|
||||
<FormGroup sx={sx}>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
onBlur={onBlur}
|
||||
onChange={onChange}
|
||||
checked={value}
|
||||
inputRef={ref}
|
||||
name={name}
|
||||
{...muiCheckboxProps}
|
||||
/>
|
||||
}
|
||||
label={label}
|
||||
{...muiFormControlLabelProps}
|
||||
/>
|
||||
</FormGroup>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
import * as React from 'react';
|
||||
import { Control, useController } from 'react-hook-form';
|
||||
import { CurrencyFormField } from '@nymproject/react/currency/CurrencyFormField';
|
||||
import { CurrencyDenom } from '@nymproject/types';
|
||||
|
||||
interface Props {
|
||||
name: string;
|
||||
label: string;
|
||||
control: Control<any>;
|
||||
required?: boolean;
|
||||
fullWidth?: boolean;
|
||||
errorMessage?: string;
|
||||
currencyDenom?: CurrencyDenom;
|
||||
}
|
||||
|
||||
export const CurrencyInput = ({ name, label, control, errorMessage, currencyDenom, required, fullWidth }: Props) => {
|
||||
const {
|
||||
field: { onChange },
|
||||
} = useController({
|
||||
name,
|
||||
control,
|
||||
});
|
||||
return (
|
||||
<CurrencyFormField
|
||||
showCoinMark
|
||||
required={required}
|
||||
fullWidth={fullWidth}
|
||||
label={label}
|
||||
onChanged={onChange}
|
||||
denom={currencyDenom}
|
||||
validationError={errorMessage}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,70 @@
|
||||
import * as React from 'react';
|
||||
import { Control, useController } from 'react-hook-form';
|
||||
import {
|
||||
FormControl,
|
||||
FormControlLabel,
|
||||
FormLabel,
|
||||
FormLabelProps,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
RadioGroupProps,
|
||||
RadioProps,
|
||||
} from '@mui/material';
|
||||
|
||||
interface Props {
|
||||
name: string;
|
||||
label: string;
|
||||
control: Control<any>;
|
||||
options: { label: string; value: any }[];
|
||||
defaultValue: any;
|
||||
muiRadioGroupProps?: RadioGroupProps;
|
||||
muiRadioProps?: RadioProps;
|
||||
muiFormLabelProps?: FormLabelProps;
|
||||
}
|
||||
|
||||
export const RadioInput = ({
|
||||
label,
|
||||
control,
|
||||
options,
|
||||
defaultValue,
|
||||
name,
|
||||
muiRadioGroupProps,
|
||||
muiRadioProps,
|
||||
muiFormLabelProps,
|
||||
}: Props) => {
|
||||
const {
|
||||
field: { onChange, value, ref },
|
||||
} = useController({
|
||||
name,
|
||||
control,
|
||||
rules: { required: true },
|
||||
defaultValue,
|
||||
});
|
||||
return (
|
||||
<FormControl ref={ref}>
|
||||
<FormLabel
|
||||
id={`radio-group-label-${name}`}
|
||||
sx={{
|
||||
color: 'text.main',
|
||||
}}
|
||||
{...muiFormLabelProps}
|
||||
>
|
||||
{label}
|
||||
</FormLabel>
|
||||
<RadioGroup
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
aria-labelledby={`radio-group-label-${name}`}
|
||||
name={name}
|
||||
sx={{
|
||||
color: 'text.main',
|
||||
}}
|
||||
{...muiRadioGroupProps}
|
||||
>
|
||||
{options.map(({ value: v, label: l }) => (
|
||||
<FormControlLabel value={v} control={<Radio color="default" {...muiRadioProps} />} label={l} />
|
||||
))}
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,59 @@
|
||||
import * as React from 'react';
|
||||
import { Control, useController } from 'react-hook-form';
|
||||
import { SxProps, TextField, TextFieldProps } from '@mui/material';
|
||||
import { RegisterOptions } from 'react-hook-form/dist/types/validator';
|
||||
|
||||
interface Props {
|
||||
name: string;
|
||||
label: string;
|
||||
placeholder?: string;
|
||||
control: Control<any>;
|
||||
defaultValue?: string;
|
||||
required?: boolean;
|
||||
error?: boolean;
|
||||
muiTextFieldProps?: TextFieldProps;
|
||||
helperText?: string;
|
||||
sx?: SxProps;
|
||||
registerOptions?: RegisterOptions;
|
||||
}
|
||||
|
||||
export const TextFieldInput = ({
|
||||
name,
|
||||
label,
|
||||
control,
|
||||
defaultValue,
|
||||
placeholder,
|
||||
muiTextFieldProps,
|
||||
required,
|
||||
error,
|
||||
helperText,
|
||||
registerOptions,
|
||||
sx,
|
||||
}: Props) => {
|
||||
const {
|
||||
field: { onChange, onBlur, value, ref },
|
||||
} = useController({
|
||||
name,
|
||||
control,
|
||||
defaultValue,
|
||||
rules: registerOptions,
|
||||
});
|
||||
return (
|
||||
<TextField
|
||||
onChange={onChange}
|
||||
onBlur={onBlur}
|
||||
value={value}
|
||||
name={name}
|
||||
id={name}
|
||||
label={label}
|
||||
variant="outlined"
|
||||
placeholder={placeholder}
|
||||
required={required}
|
||||
inputRef={ref}
|
||||
error={error}
|
||||
helperText={helperText}
|
||||
{...muiTextFieldProps}
|
||||
sx={sx}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './CheckboxInput';
|
||||
export * from './RadioInput';
|
||||
export * from './TextFieldInput';
|
||||
@@ -1 +1,48 @@
|
||||
// TODO
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { AppContext } from 'src/context/main';
|
||||
import { Box } from '@mui/material';
|
||||
import { useBondingContext, BondingContextProvider } from '../../context';
|
||||
import { PageLayout } from '../../layouts';
|
||||
import { BondingCard } from './BondingCard';
|
||||
import { BondedMixnodeCard } from './BoundedMixnodeCard';
|
||||
import { BondedGatewayCard } from './BoundedGatewayCard';
|
||||
import { EnumRequestStatus } from '../../components';
|
||||
import { useCheckOwnership } from '../../hooks/useCheckOwnership';
|
||||
|
||||
const Bonding = () => {
|
||||
const [status] = useState(EnumRequestStatus.initial);
|
||||
const { bondedMixnode, bondedGateway } = useBondingContext();
|
||||
const { checkOwnership, ownership, isLoading } = useCheckOwnership();
|
||||
|
||||
useEffect(() => {
|
||||
if (status === EnumRequestStatus.initial) {
|
||||
const initialiseForm = async () => {
|
||||
await checkOwnership();
|
||||
};
|
||||
initialiseForm();
|
||||
}
|
||||
}, [status, checkOwnership]);
|
||||
|
||||
return (
|
||||
<PageLayout>
|
||||
<Box display="flex" flexDirection="column" gap={2}>
|
||||
{!bondedMixnode &&
|
||||
!bondedGateway &&
|
||||
status === EnumRequestStatus.initial &&
|
||||
!ownership.hasOwnership &&
|
||||
!isLoading && <BondingCard />}
|
||||
{bondedMixnode && <BondedMixnodeCard />}
|
||||
{bondedGateway && <BondedGatewayCard />}
|
||||
</Box>
|
||||
</PageLayout>
|
||||
);
|
||||
};
|
||||
|
||||
export const BondingPage = () => {
|
||||
const { network } = useContext(AppContext);
|
||||
return (
|
||||
<BondingContextProvider network={network}>
|
||||
<Bonding />
|
||||
</BondingContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import { boolean, lazy, mixed, number, object, string } from 'yup';
|
||||
import { isValidHostname, validateKey, validateLocation, validateRawPort, validateVersion } from '../../utils';
|
||||
import { NodeType } from './types';
|
||||
|
||||
export const nodeSchema = object().shape({
|
||||
nodeType: string<NodeType>().required(),
|
||||
identityKey: string()
|
||||
.required('An indentity key is required')
|
||||
.test('valid-id-key', 'A valid identity key is required', (value) => validateKey(value || '', 32)),
|
||||
|
||||
sphinxKey: string()
|
||||
.required('A sphinx key is required')
|
||||
.test('valid-sphinx-key', 'A valid sphinx key is required', (value) => validateKey(value || '', 32)),
|
||||
|
||||
signature: string()
|
||||
.required('Signature is required')
|
||||
.test('valid-signature', 'A valid signature is required', (value) => validateKey(value || '', 64)),
|
||||
|
||||
host: string()
|
||||
.required('A host is required')
|
||||
.test('valid-host', 'A valid host is required', (value) => (value ? isValidHostname(value) : false)),
|
||||
|
||||
version: string()
|
||||
.required('A version is required')
|
||||
.test('valid-version', 'A valid version is required', (value) => (value ? validateVersion(value) : false)),
|
||||
|
||||
advancedOpt: boolean().required(),
|
||||
|
||||
location: lazy((value) => {
|
||||
if (value) {
|
||||
return string()
|
||||
.required('A location is required')
|
||||
.test('valid-location', 'A valid version is required', (locationValueTest) =>
|
||||
locationValueTest ? validateLocation(locationValueTest) : false,
|
||||
);
|
||||
}
|
||||
return mixed().notRequired();
|
||||
}),
|
||||
|
||||
mixPort: number()
|
||||
.required('A mixport is required')
|
||||
.test('valid-mixport', 'A valid mixport is required', (value) => (value ? validateRawPort(value) : false)),
|
||||
|
||||
verlocPort: number()
|
||||
.required('A verloc port is required')
|
||||
.test('valid-verloc', 'A valid verloc port is required', (value) => (value ? validateRawPort(value) : false)),
|
||||
|
||||
httpApiPort: number()
|
||||
.required('A http-api port is required')
|
||||
.test('valid-http', 'A valid http-api port is required', (value) => (value ? validateRawPort(value) : false)),
|
||||
|
||||
clientsPort: number()
|
||||
.required('A clients port is required')
|
||||
.test('valid-clients', 'A valid clients port is required', (value) => (value ? validateRawPort(value) : false)),
|
||||
});
|
||||
@@ -0,0 +1,45 @@
|
||||
import { MajorCurrencyAmount, TransactionExecuteResult } from '@nymproject/types';
|
||||
|
||||
export type FormStep = 1 | 2 | 3 | 4;
|
||||
export type NodeType = 'mixnode' | 'gateway';
|
||||
|
||||
export type ACTIONTYPE =
|
||||
| { type: 'change_bond_type'; payload: NodeType }
|
||||
| { type: 'set_node_data'; payload: NodeData }
|
||||
| { type: 'set_amount_data'; payload: AmountData }
|
||||
| { type: 'set_step'; payload: FormStep }
|
||||
| { type: 'set_tx'; payload: TransactionExecuteResult }
|
||||
| { type: 'next_step' }
|
||||
| { type: 'previous_step' }
|
||||
| { type: 'show_modal' }
|
||||
| { type: 'close_modal' }
|
||||
| { type: 'reset' };
|
||||
|
||||
export interface NodeData {
|
||||
nodeType: NodeType;
|
||||
identityKey: string;
|
||||
sphinxKey: string;
|
||||
signature: string;
|
||||
host: string;
|
||||
location?: string;
|
||||
version: string;
|
||||
advancedOpt: boolean;
|
||||
mixPort: number;
|
||||
verlocPort: number;
|
||||
clientsPort: number;
|
||||
httpApiPort: number;
|
||||
}
|
||||
|
||||
export interface AmountData {
|
||||
amount: MajorCurrencyAmount;
|
||||
tokenPool: string;
|
||||
profitMargin?: number;
|
||||
}
|
||||
|
||||
export interface BondState {
|
||||
showModal: boolean;
|
||||
formStep: FormStep;
|
||||
nodeData?: NodeData;
|
||||
amountData?: AmountData;
|
||||
tx?: TransactionExecuteResult;
|
||||
}
|
||||
@@ -7,3 +7,5 @@ export * from './auth';
|
||||
export * from './settings';
|
||||
export * from './unbond';
|
||||
export * from './delegation';
|
||||
export * from './bonding';
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Route, Routes } from 'react-router-dom';
|
||||
import { ApplicationLayout } from 'src/layouts';
|
||||
import { Terminal } from 'src/pages/terminal';
|
||||
import { Send } from 'src/components/Send';
|
||||
import { Bond, Balance, InternalDocs, Receive, Unbond, DelegationPage, Admin, Settings } from '../pages';
|
||||
import { Bond, Balance, InternalDocs, Receive, Unbond, DelegationPage, Admin, Settings, BondingPage } from '../pages';
|
||||
|
||||
export const AppRoutes = () => (
|
||||
<ApplicationLayout>
|
||||
@@ -14,6 +14,7 @@ export const AppRoutes = () => (
|
||||
<Route path="/balance" element={<Balance />} />
|
||||
<Route path="/receive" element={<Receive />} />
|
||||
<Route path="/bond" element={<Bond />} />
|
||||
<Route path="/bonding" element={<BondingPage />} />
|
||||
<Route path="/unbond" element={<Unbond />} />
|
||||
<Route path="/delegation" element={<DelegationPage />} />
|
||||
<Route path="/docs" element={<InternalDocs />} />
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import React from 'react';
|
||||
import { SvgIcon, SvgIconProps } from '@mui/material';
|
||||
|
||||
export const Bonding = (props: SvgIconProps) => (
|
||||
<SvgIcon {...props}>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M16 17C14.8954 17 14 16.1046 14 15L14 9C14 7.89543 14.8954 7 16 7L22 7C23.1046 7 24 7.89543 24 9L24 15C24 16.1046 23.1046 17 22 17L16 17ZM16 9L16 15L22 15L22 9L16 9Z"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M2 17C0.89543 17 -1.00647e-07 16.1046 -2.24801e-07 15L-8.99206e-07 9C-1.02336e-06 7.89543 0.895429 7 2 7L8 7C9.10457 7 10 7.89543 10 9L10 15C10 16.1046 9.10457 17 8 17L2 17ZM2 9L2 15L8 15L8 9L2 9Z"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M6 12C6 11.4477 6.44772 11 7 11H17C17.5523 11 18 11.4477 18 12C18 12.5523 17.5523 13 17 13H7C6.44772 13 6 12.5523 6 12Z"
|
||||
/>
|
||||
</SvgIcon>
|
||||
);
|
||||
@@ -2,3 +2,4 @@ export * from './delegate';
|
||||
export * from './undelegate';
|
||||
export * from './bond';
|
||||
export * from './unbond';
|
||||
export * from './bonding';
|
||||
|
||||
@@ -1157,7 +1157,7 @@
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.2"
|
||||
|
||||
"@babel/runtime@^7.0.0", "@babel/runtime@^7.10.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.15.4", "@babel/runtime@^7.16.3", "@babel/runtime@^7.16.7", "@babel/runtime@^7.17.2", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
|
||||
"@babel/runtime@^7.0.0", "@babel/runtime@^7.10.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.16.3", "@babel/runtime@^7.16.7", "@babel/runtime@^7.17.2", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
|
||||
version "7.17.2"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.2.tgz#66f68591605e59da47523c631416b18508779941"
|
||||
integrity sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw==
|
||||
@@ -5279,11 +5279,6 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.182.tgz#05301a4d5e62963227eaafe0ce04dd77c54ea5c2"
|
||||
integrity sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q==
|
||||
|
||||
"@types/lodash@^4.14.175":
|
||||
version "4.14.179"
|
||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.179.tgz#490ec3288088c91295780237d2497a3aa9dfb5c5"
|
||||
integrity sha512-uwc1x90yCKqGcIOAT6DwOSuxnrAbpkdPsUOZtwrXb4D/6wZs+6qG7QnIawDuZWg0sWpxl+ltIKCaLoMlna678w==
|
||||
|
||||
"@types/long@^4.0.1":
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.2.tgz#b74129719fc8d11c01868010082d483b7545591a"
|
||||
@@ -13188,11 +13183,6 @@ locate-path@^6.0.0:
|
||||
dependencies:
|
||||
p-locate "^5.0.0"
|
||||
|
||||
lodash-es@^4.17.21:
|
||||
version "4.17.21"
|
||||
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee"
|
||||
integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==
|
||||
|
||||
lodash._reinterpolate@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
|
||||
@@ -18101,12 +18091,12 @@ timsort@^0.3.0:
|
||||
resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4"
|
||||
integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=
|
||||
|
||||
tiny-invariant@^1.0.2:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.2.0.tgz#a1141f86b672a9148c72e978a19a73b9b94a15a9"
|
||||
integrity sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg==
|
||||
tiny-case@^1.0.2:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/tiny-case/-/tiny-case-1.0.3.tgz#d980d66bc72b5d5a9ca86fb7c9ffdb9c898ddd03"
|
||||
integrity sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==
|
||||
|
||||
tiny-warning@^1.0.0, tiny-warning@^1.0.2, tiny-warning@^1.0.3:
|
||||
tiny-warning@^1.0.2:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"
|
||||
integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==
|
||||
@@ -19542,17 +19532,13 @@ yocto-queue@^0.1.0:
|
||||
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
|
||||
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
|
||||
|
||||
yup@^0.32.9:
|
||||
version "0.32.11"
|
||||
resolved "https://registry.yarnpkg.com/yup/-/yup-0.32.11.tgz#d67fb83eefa4698607982e63f7ca4c5ed3cf18c5"
|
||||
integrity sha512-Z2Fe1bn+eLstG8DRR6FTavGD+MeAwyfmouhHsIUgaADz8jvFKbO/fXc2trJKZg+5EBjh4gGm3iU/t3onKlXHIg==
|
||||
yup@^1.0.0-beta.4:
|
||||
version "1.0.0-beta.4"
|
||||
resolved "https://registry.yarnpkg.com/yup/-/yup-1.0.0-beta.4.tgz#df10f42df6aa0212c22aab58d6d026b1610ff8a3"
|
||||
integrity sha512-g5uuQH2rN+0Z4L/ix8KoYIS6v7vnrykRzM/u7ExSA0WA33vrw2YOUKShBhaueh9N95oz54slgqBQTHx7E6EHwg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.15.4"
|
||||
"@types/lodash" "^4.14.175"
|
||||
lodash "^4.17.21"
|
||||
lodash-es "^4.17.21"
|
||||
nanoclone "^0.2.1"
|
||||
property-expr "^2.0.4"
|
||||
tiny-case "^1.0.2"
|
||||
toposort "^2.0.2"
|
||||
|
||||
zwitch@^1.0.0:
|
||||
|
||||
Reference in New Issue
Block a user