feat(wallet-bonding): node settings flow
This commit is contained in:
@@ -177,6 +177,7 @@ const BondingCard = () => {
|
||||
confirmButton="Done"
|
||||
maxWidth="xs"
|
||||
fullWidth
|
||||
sx={{ textAlign: 'center' }}
|
||||
>
|
||||
<Link href={`${urls(network).blockExplorer}/transaction/${state.tx?.transaction_hash}`} noIcon>
|
||||
View on blockchain
|
||||
|
||||
@@ -33,3 +33,9 @@ Default.args = {
|
||||
cancelButton: false,
|
||||
disabled: false,
|
||||
};
|
||||
|
||||
export const CenteredText = Template.bind({});
|
||||
CenteredText.args = {
|
||||
...Default.args,
|
||||
sx: { textAlign: 'center' },
|
||||
};
|
||||
|
||||
@@ -130,7 +130,7 @@ const MixnodeCard = ({ mixnode }: { mixnode: BondedMixnode }) => {
|
||||
explorer
|
||||
</Link>
|
||||
</Typography>
|
||||
<NodeSettings mixnode={mixnode} open={showNodeSettings} />
|
||||
<NodeSettings mixnode={mixnode} show={showNodeSettings} onClose={() => setShowNodeSettings(false)} />
|
||||
</BondedNodeCard>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,35 +1,82 @@
|
||||
import * as React from 'react';
|
||||
import { useState } from 'react';
|
||||
import { MajorCurrencyAmount } from '@nymproject/types';
|
||||
import { useContext, useEffect, useState } from 'react';
|
||||
import { MajorCurrencyAmount, TransactionExecuteResult } from '@nymproject/types';
|
||||
import { Link } from '@nymproject/react/link/Link';
|
||||
import { Typography } from '@mui/material';
|
||||
import ProfitMarginModal from './ProfitMarginModal';
|
||||
import { BondedMixnode } from '../../../../context';
|
||||
import { AppContext, BondedMixnode, urls } from '../../../../context';
|
||||
import SummaryModal from './SummaryModal';
|
||||
import { SimpleDialog } from '../../components';
|
||||
|
||||
interface Props {
|
||||
mixnode: BondedMixnode;
|
||||
open: boolean;
|
||||
show: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
// TODO fetch real estimated operator reward for 10% PM
|
||||
const MOCK_ESTIMATED_OP_REWARD: MajorCurrencyAmount = { amount: '42', denom: 'NYM' };
|
||||
|
||||
const NodeSettings = ({ open, mixnode }: Props) => {
|
||||
const NodeSettings = ({ mixnode, show, onClose }: Props) => {
|
||||
const [profitMargin, setProfitMargin] = useState<number>();
|
||||
const [pmModalOpen, setPmModalOpen] = useState<boolean>(true);
|
||||
const [fee, setFee] = useState<MajorCurrencyAmount>({ amount: '0', denom: 'NYM' });
|
||||
const [step, setStep] = useState<1 | 2 | 3>(1);
|
||||
const [tx, setTx] = useState<TransactionExecuteResult>();
|
||||
|
||||
if (!open) return null;
|
||||
const { network } = useContext(AppContext);
|
||||
|
||||
useEffect(() => {
|
||||
setFee({ amount: '42', denom: 'NYM' }); // TODO fetch real fee amount
|
||||
}, [profitMargin]);
|
||||
|
||||
const submit = () => {
|
||||
// TODO send request to update profit margin
|
||||
setStep(3); // on success
|
||||
// setTx(requestResult)
|
||||
};
|
||||
|
||||
const reset = () => {
|
||||
setProfitMargin(0);
|
||||
setStep(1);
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<ProfitMarginModal
|
||||
open={pmModalOpen}
|
||||
onClose={() => {
|
||||
setPmModalOpen(false);
|
||||
}}
|
||||
onSubmit={async (pm) => {
|
||||
setProfitMargin(pm);
|
||||
setPmModalOpen(false);
|
||||
}}
|
||||
estimatedOpReward={MOCK_ESTIMATED_OP_REWARD}
|
||||
currentPm={mixnode.profitMargin}
|
||||
/>
|
||||
<>
|
||||
<ProfitMarginModal
|
||||
open={show && step === 1}
|
||||
onClose={onClose}
|
||||
onConfirm={async (pm) => {
|
||||
setProfitMargin(pm);
|
||||
setStep(2);
|
||||
}}
|
||||
estimatedOpReward={MOCK_ESTIMATED_OP_REWARD}
|
||||
currentPm={mixnode.profitMargin}
|
||||
/>
|
||||
<SummaryModal
|
||||
open={show && step === 2}
|
||||
onClose={reset}
|
||||
onConfirm={submit}
|
||||
onCancel={() => setStep(1)}
|
||||
currentPm={mixnode.profitMargin}
|
||||
newPm={profitMargin as number}
|
||||
fee={fee as MajorCurrencyAmount}
|
||||
/>
|
||||
<SimpleDialog
|
||||
open={show && step === 3}
|
||||
onClose={reset}
|
||||
onConfirm={reset}
|
||||
title="Operation successful"
|
||||
confirmButton="Done"
|
||||
maxWidth="xs"
|
||||
sx={{ textAlign: 'center' }}
|
||||
>
|
||||
<Typography sx={{ mb: 2 }}>This operation can take up to one hour to process</Typography>
|
||||
<Link href={`${urls(network).blockExplorer}/transaction/${tx?.transaction_hash}`} noIcon>
|
||||
View on blockchain
|
||||
</Link>
|
||||
</SimpleDialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import * as React from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { Divider, Stack, Typography } from '@mui/material';
|
||||
import { Box, Divider, Stack, Typography } from '@mui/material';
|
||||
import { MajorCurrencyAmount } from '@nymproject/types';
|
||||
import { SimpleDialog, TextFieldInput } from '../../components';
|
||||
import schema from './schema';
|
||||
import { Node as NodeIcon } from '../../../../svg-icons/node';
|
||||
import getSchema from './schema';
|
||||
|
||||
export interface Props {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
onSubmit: (pm: number) => Promise<void>;
|
||||
onConfirm: (pm: number) => void;
|
||||
estimatedOpReward: MajorCurrencyAmount;
|
||||
currentPm: number;
|
||||
}
|
||||
@@ -18,15 +19,14 @@ interface FormData {
|
||||
profitMargin: number;
|
||||
}
|
||||
|
||||
const NodeSettingsModal = ({ open, onClose, onSubmit, estimatedOpReward, currentPm }: Props) => {
|
||||
const NodeSettingsModal = ({ open, onClose, onConfirm, estimatedOpReward, currentPm }: Props) => {
|
||||
const {
|
||||
control,
|
||||
setValue,
|
||||
setError,
|
||||
handleSubmit,
|
||||
reset,
|
||||
formState: { errors },
|
||||
} = useForm<FormData>({
|
||||
resolver: yupResolver(schema),
|
||||
resolver: yupResolver(getSchema(currentPm)),
|
||||
defaultValues: {
|
||||
profitMargin: currentPm,
|
||||
},
|
||||
@@ -35,37 +35,48 @@ const NodeSettingsModal = ({ open, onClose, onSubmit, estimatedOpReward, current
|
||||
return (
|
||||
<SimpleDialog
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
onConfirm={handleSubmit(async (data) => onSubmit(data.profitMargin))}
|
||||
title="Node Settings"
|
||||
onClose={() => {
|
||||
reset();
|
||||
onClose();
|
||||
}}
|
||||
onConfirm={handleSubmit(async (data) => onConfirm(data.profitMargin))}
|
||||
title={
|
||||
<Stack direction="row" alignItems="center">
|
||||
<NodeIcon sx={{ mr: 1, fontSize: 14 }} />
|
||||
Node Settings
|
||||
</Stack>
|
||||
}
|
||||
subTitle="System Variables"
|
||||
confirmButton="Next"
|
||||
closeButton
|
||||
disabled={Boolean(errors?.profitMargin)}
|
||||
>
|
||||
<form>
|
||||
<TextFieldInput
|
||||
name="profitMargin"
|
||||
control={control}
|
||||
defaultValue=""
|
||||
label="Set profit margin"
|
||||
placeholder="Profit Margin"
|
||||
error={Boolean(errors.profitMargin)}
|
||||
helperText={
|
||||
errors.profitMargin
|
||||
? errors.profitMargin.message
|
||||
: 'Your new profit margin will be applied in the next epoch'
|
||||
}
|
||||
required
|
||||
muiTextFieldProps={{ fullWidth: true }}
|
||||
sx={{ mb: 2.5 }}
|
||||
/>
|
||||
</form>
|
||||
<Stack direction="row" justifyContent="space-between" mt={3}>
|
||||
<Typography fontWeight={400}>Estimated operator reward for 10% PM</Typography>
|
||||
<Typography fontWeight={400}>{`~${estimatedOpReward.amount} ${estimatedOpReward.denom}`}</Typography>
|
||||
</Stack>
|
||||
<Divider sx={{ my: 1 }} />
|
||||
<Typography fontWeight={400}>Est. fee for this transaction will be cauculated in the next page</Typography>
|
||||
<Box sx={{ mt: 1 }}>
|
||||
<form>
|
||||
<TextFieldInput
|
||||
name="profitMargin"
|
||||
control={control}
|
||||
defaultValue=""
|
||||
label="Set profit margin"
|
||||
placeholder="Profit Margin"
|
||||
error={Boolean(errors.profitMargin)}
|
||||
helperText={
|
||||
errors.profitMargin
|
||||
? errors.profitMargin.message
|
||||
: 'Your new profit margin will be applied in the next epoch'
|
||||
}
|
||||
required
|
||||
muiTextFieldProps={{ fullWidth: true }}
|
||||
sx={{ mb: 2.5 }}
|
||||
/>
|
||||
</form>
|
||||
<Stack direction="row" justifyContent="space-between" mt={3}>
|
||||
<Typography fontWeight={400}>Estimated operator reward for 10% PM</Typography>
|
||||
<Typography fontWeight={400}>{`~${estimatedOpReward.amount} ${estimatedOpReward.denom}`}</Typography>
|
||||
</Stack>
|
||||
<Divider sx={{ my: 1 }} />
|
||||
<Typography fontWeight={400}>Est. fee for this transaction will be cauculated in the next page</Typography>
|
||||
</Box>
|
||||
</SimpleDialog>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import * as React from 'react';
|
||||
import { Divider, Stack, Typography } from '@mui/material';
|
||||
import { MajorCurrencyAmount } from '@nymproject/types';
|
||||
import { SimpleDialog } from '../../components';
|
||||
|
||||
export interface Props {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
onConfirm: () => void;
|
||||
onCancel: () => void;
|
||||
currentPm: number;
|
||||
newPm: number;
|
||||
fee: MajorCurrencyAmount;
|
||||
}
|
||||
|
||||
const SummaryModal = ({ open, onClose, onConfirm, onCancel, currentPm, newPm, fee }: Props) => (
|
||||
<SimpleDialog
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
onConfirm={onConfirm}
|
||||
onCancel={onCancel}
|
||||
title="Profit margin change"
|
||||
subTitle="System Variables"
|
||||
confirmButton="Confirm"
|
||||
closeButton
|
||||
cancelButton
|
||||
maxWidth="xs"
|
||||
fullWidth
|
||||
>
|
||||
<Stack direction="row" justifyContent="space-between">
|
||||
<Typography fontWeight={400}>Current profit margin</Typography>
|
||||
<Typography fontWeight={400}>{`${currentPm}%`}</Typography>
|
||||
</Stack>
|
||||
<Divider sx={{ my: 1 }} />
|
||||
<Stack direction="row" justifyContent="space-between">
|
||||
<Typography fontWeight={400}>New profit margin</Typography>
|
||||
<Typography fontWeight={400}>{`${newPm}%`}</Typography>
|
||||
</Stack>
|
||||
<Divider sx={{ my: 1 }} />
|
||||
<Stack direction="row" justifyContent="space-between">
|
||||
<Typography fontWeight={400}>Fee for this operation</Typography>
|
||||
<Typography fontWeight={400}>{`${fee.amount} ${fee.denom}`}</Typography>
|
||||
</Stack>
|
||||
</SimpleDialog>
|
||||
);
|
||||
|
||||
export default SummaryModal;
|
||||
@@ -1,7 +1,8 @@
|
||||
import { number, object } from 'yup';
|
||||
|
||||
const schema = object().shape({
|
||||
profitMargin: number().required('Profit Percentage is required').min(0).max(100),
|
||||
});
|
||||
const getSchema = (currentPm: number) =>
|
||||
object().shape({
|
||||
profitMargin: number().required('Profit Percentage is required').min(0).max(100).notOneOf([currentPm]),
|
||||
});
|
||||
|
||||
export default schema;
|
||||
export default getSchema;
|
||||
|
||||
Reference in New Issue
Block a user