feat(wallet-bonding): add node table component
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { TransactionExecuteResult } from '@nymproject/types';
|
||||
import { MajorCurrencyAmount, TransactionExecuteResult } from '@nymproject/types';
|
||||
import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import type { Network } from 'src/types';
|
||||
import { TBondGatewayArgs, TBondMixNodeArgs } from 'src/types';
|
||||
@@ -15,12 +15,12 @@ import {
|
||||
export interface BondedMixnode {
|
||||
key: string;
|
||||
ip: string;
|
||||
stake: number;
|
||||
bond: number;
|
||||
stake: MajorCurrencyAmount;
|
||||
bond: MajorCurrencyAmount;
|
||||
stakeSaturation: number;
|
||||
profitMargin: number;
|
||||
nodeRewards: number;
|
||||
operatorRewards: number;
|
||||
nodeRewards: MajorCurrencyAmount;
|
||||
operatorRewards: MajorCurrencyAmount;
|
||||
delegators: number;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ export interface BondedMixnode {
|
||||
export interface BondedGateway {
|
||||
key: string;
|
||||
ip: string;
|
||||
bond: number;
|
||||
bond: MajorCurrencyAmount;
|
||||
location?: string; // TODO not yet available, only available in Network Explorer API
|
||||
}
|
||||
|
||||
@@ -89,7 +89,17 @@ export const BondingContextProvider = ({
|
||||
};
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
const bounded = null;
|
||||
const bounded: BondedMixnode = {
|
||||
bond: { denom: 'NYM', amount: '1234' },
|
||||
key: 'B2Xx4haarLWMajX8w259oHjtRZsC7nHwagbWrJNiA3QC',
|
||||
delegators: 123,
|
||||
ip: '1.2.34.5',
|
||||
nodeRewards: { denom: 'NYM', amount: '123' },
|
||||
operatorRewards: { denom: 'NYM', amount: '12' },
|
||||
profitMargin: 10,
|
||||
stake: { denom: 'NYM', amount: '99' },
|
||||
stakeSaturation: 99,
|
||||
};
|
||||
// TODO fetch bondedMixnode and bondedGatway via tauri dedicated requests
|
||||
/* try {
|
||||
bounded = await fetchBondingData();
|
||||
@@ -181,6 +191,7 @@ export const BondingContextProvider = ({
|
||||
isLoading,
|
||||
error,
|
||||
bondMixnode,
|
||||
bondedMixnode,
|
||||
bondGateway,
|
||||
unbondMixnode,
|
||||
unbondGateway,
|
||||
|
||||
@@ -6,22 +6,22 @@ import { mockSleep } from './utils';
|
||||
|
||||
const SLEEP_MS = 1000;
|
||||
|
||||
const bondedMixnodeMock = {
|
||||
const bondedMixnodeMock: BondedMixnode = {
|
||||
key: '7mjM2fYbtN6kxMwp1TrmQ4VwPks3URR5pBgWPWhzT98F',
|
||||
ip: '112.43.234.56',
|
||||
stake: 35847.221,
|
||||
bond: 12576.32745,
|
||||
stake: { denom: 'NYM', amount: '1234' },
|
||||
bond: { denom: 'NYM', amount: '1234' },
|
||||
stakeSaturation: 95,
|
||||
profitMargin: 15,
|
||||
nodeRewards: 12576.32745,
|
||||
operatorRewards: 12576.32,
|
||||
nodeRewards: { denom: 'NYM', amount: '1234' },
|
||||
operatorRewards: { denom: 'NYM', amount: '1234' },
|
||||
delegators: 5423,
|
||||
};
|
||||
|
||||
const bondedGatewayMock = {
|
||||
const bondedGatewayMock: BondedGateway = {
|
||||
key: 'WayM2fYbtN6kxMwp1TrmQ4VwPks3URR5pBgWPWhzT98F',
|
||||
ip: '112.43.234.57',
|
||||
bond: 12576.32745,
|
||||
bond: { denom: 'NYM', amount: '1234' },
|
||||
};
|
||||
|
||||
const TxResultMock: TransactionExecuteResult = {
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import React, { useContext, useEffect, useReducer } from 'react';
|
||||
import { Box, Button, Typography } from '@mui/material';
|
||||
import { Link } from '@nymproject/react/link/Link';
|
||||
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 { AppContext, urls } from '../../context';
|
||||
import { SummaryModal } from './SummaryModal';
|
||||
import { bond, vestingBond } from '../../requests';
|
||||
import { TBondArgs } from '../../types';
|
||||
import { SimpleModal } from '../../components/Modals/SimpleModal';
|
||||
import { Console } from '../../utils/console';
|
||||
import { SimpleDialog } from './SimpleDialog';
|
||||
|
||||
const initialState: BondState = {
|
||||
showModal: false,
|
||||
@@ -51,7 +53,7 @@ export const BondingCard = () => {
|
||||
const { formStep, showModal } = state;
|
||||
console.log(state);
|
||||
|
||||
const { userBalance, clientDetails } = useContext(AppContext);
|
||||
const { userBalance, clientDetails, network } = useContext(AppContext);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch({ type: 'reset' });
|
||||
@@ -101,11 +103,17 @@ export const BondingCard = () => {
|
||||
dispatch({ type: 'set_tx', payload: tx });
|
||||
dispatch({ type: 'next_step' });
|
||||
})
|
||||
.catch(() => {
|
||||
.catch((e: any) => {
|
||||
Console.error('Failed to bond', e);
|
||||
// TODO do something
|
||||
});
|
||||
};
|
||||
|
||||
const onConfirm = () => {
|
||||
dispatch({ type: 'close_modal' });
|
||||
dispatch({ type: 'reset' });
|
||||
};
|
||||
|
||||
return (
|
||||
<NymCard title="Bonding">
|
||||
<Box
|
||||
@@ -167,17 +175,16 @@ export const BondingCard = () => {
|
||||
/>
|
||||
)}
|
||||
{formStep === 4 && showModal && (
|
||||
<SimpleModal
|
||||
<SimpleDialog
|
||||
open={formStep === 4 && showModal}
|
||||
onOk={() => {
|
||||
dispatch({ type: 'close_modal' });
|
||||
dispatch({ type: 'reset' });
|
||||
}}
|
||||
header="Bonding successful"
|
||||
okLabel="Done"
|
||||
onClose={onConfirm}
|
||||
title="Bonding successful"
|
||||
confirmText="Done"
|
||||
>
|
||||
Link to transaction on network explorer
|
||||
</SimpleModal>
|
||||
<Link href={`${urls(network).blockExplorer}/transaction/${state.tx?.transaction_hash}`} noIcon>
|
||||
View on blockchain
|
||||
</Link>
|
||||
</SimpleDialog>
|
||||
)}
|
||||
</NymCard>
|
||||
);
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
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>
|
||||
);
|
||||
+1
-1
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import { Grid } from '@mui/material';
|
||||
import { NymCard } from '../../components';
|
||||
|
||||
export const BondedGatewayCard = () => (
|
||||
export const GatewayCard = () => (
|
||||
<NymCard title="Balance">
|
||||
<Grid container direction="column" spacing={2}>
|
||||
<Grid item>bonded gateway data table</Grid>
|
||||
@@ -0,0 +1,110 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { IconButton, Typography } from '@mui/material';
|
||||
import { MoreVert } from '@mui/icons-material';
|
||||
import { Link } from '@nymproject/react/link/Link';
|
||||
import { NymCard } from '../../components';
|
||||
import { BondedMixnode } from '../../context';
|
||||
import { Cell, Header, NodeTable } from './NodeTable';
|
||||
|
||||
const headers: Header[] = [
|
||||
{
|
||||
header: 'Stake',
|
||||
id: 'stake',
|
||||
sx: { pl: 0 },
|
||||
},
|
||||
{
|
||||
header: 'Bond',
|
||||
id: 'bond',
|
||||
},
|
||||
{
|
||||
header: 'Stake saturation',
|
||||
id: 'stake-saturation',
|
||||
tooltip: 'TODO',
|
||||
},
|
||||
{
|
||||
header: 'PM',
|
||||
id: 'profit-margin',
|
||||
tooltip:
|
||||
'The percentage of the node rewards that you as the node operator will take before the rest of the reward is shared between you and the delegators.',
|
||||
},
|
||||
{
|
||||
header: 'Node rewards',
|
||||
id: 'node-rewards',
|
||||
tooltip: 'This is the total rewards for this node in this epoch including delegates and the operators share.',
|
||||
},
|
||||
{
|
||||
header: 'Operator rewards',
|
||||
id: 'operator-rewards',
|
||||
tooltip:
|
||||
'This is your (operator) new rewards including the PM and cost. You can compound your rewards manually every epoch or unbond your node to redeem them.',
|
||||
},
|
||||
{
|
||||
header: 'No. delegators',
|
||||
id: 'delegators',
|
||||
},
|
||||
{
|
||||
id: 'menu-button',
|
||||
size: 'small',
|
||||
sx: { pr: 0 },
|
||||
},
|
||||
];
|
||||
|
||||
export const MixnodeCard = ({ mixnode }: { mixnode: BondedMixnode }) => {
|
||||
const { stake, bond, stakeSaturation, profitMargin, nodeRewards, operatorRewards, delegators } = mixnode;
|
||||
const cells: Cell[] = useMemo(
|
||||
() => [
|
||||
{
|
||||
cell: `${stake.amount} ${stake.denom}`,
|
||||
id: 'stake-cell',
|
||||
sx: { pl: 0 },
|
||||
},
|
||||
{
|
||||
cell: `${bond.amount} ${bond.denom}`,
|
||||
id: 'bond-cell',
|
||||
},
|
||||
{
|
||||
cell: `${stakeSaturation}%`,
|
||||
id: 'stake-saturation-cell',
|
||||
},
|
||||
{
|
||||
cell: `${profitMargin}%`,
|
||||
id: 'pm-cell',
|
||||
},
|
||||
{
|
||||
cell: `${nodeRewards.amount} ${nodeRewards.denom}`,
|
||||
id: 'node-rewards-cell',
|
||||
},
|
||||
{
|
||||
cell: `${operatorRewards.amount} ${operatorRewards.denom}`,
|
||||
id: 'operator-rewards-cell',
|
||||
},
|
||||
{
|
||||
cell: delegators,
|
||||
id: 'delegators-cell',
|
||||
},
|
||||
{
|
||||
cell: (
|
||||
<IconButton sx={{ fontSize: '1rem', padding: 0 }}>
|
||||
<MoreVert fontSize="inherit" sx={{ color: 'text.primary' }} />
|
||||
</IconButton>
|
||||
),
|
||||
id: 'menu-button-cell',
|
||||
align: 'center',
|
||||
size: 'small',
|
||||
sx: { pr: 0 },
|
||||
},
|
||||
],
|
||||
[mixnode],
|
||||
);
|
||||
return (
|
||||
<NymCard title="Monster node">
|
||||
<NodeTable headers={headers} cells={cells} />
|
||||
<Typography sx={{ mt: 2 }}>
|
||||
Check more stats of your node on the{' '}
|
||||
<Link href="url" target="_blank">
|
||||
explorer
|
||||
</Link>
|
||||
</Typography>
|
||||
</NymCard>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,79 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Box,
|
||||
Stack,
|
||||
SxProps,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell as MUITableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Tooltip,
|
||||
Typography,
|
||||
} from '@mui/material';
|
||||
import { InfoOutlined } from '@mui/icons-material';
|
||||
|
||||
export interface TableCell {
|
||||
children: React.ReactNode;
|
||||
color?: string;
|
||||
align?: 'center' | 'inherit' | 'justify' | 'left' | 'right';
|
||||
size?: 'small' | 'medium';
|
||||
sx?: SxProps;
|
||||
}
|
||||
|
||||
export type TableHeader = TableCell & { tooltip?: React.ReactNode };
|
||||
|
||||
const CellHeader = ({ children, tooltip, sx, size, align, color }: TableHeader) => (
|
||||
<MUITableCell sx={{ py: 1.2, ...sx }} size={size} align={align} color={color}>
|
||||
{tooltip ? (
|
||||
<Tooltip title={tooltip} arrow placement="top-start">
|
||||
<Stack direction="row" alignItems="center" fontSize="0.8rem">
|
||||
<InfoOutlined fontSize="inherit" sx={{ mr: 0.5 }} />
|
||||
<Typography>{children}</Typography>
|
||||
</Stack>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Typography>{children}</Typography>
|
||||
)}
|
||||
</MUITableCell>
|
||||
);
|
||||
|
||||
const CellValue = ({ children, align, size, color, sx }: TableCell) => (
|
||||
<MUITableCell component="th" scope="row" sx={{ py: 1, ...sx }} align={align} size={size} color={color}>
|
||||
{children}
|
||||
</MUITableCell>
|
||||
);
|
||||
|
||||
export type Header = Omit<TableHeader, 'children'> & { header?: React.ReactNode; id: string };
|
||||
export type Cell = Omit<TableCell, 'children'> & { cell: React.ReactNode; id: string };
|
||||
|
||||
export interface TableProps {
|
||||
headers: Header[];
|
||||
cells: Cell[];
|
||||
}
|
||||
|
||||
export const NodeTable = ({ headers, cells }: TableProps) => (
|
||||
<TableContainer component={Box}>
|
||||
<Table sx={{ minWidth: 650 }} aria-label="node-table">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
{headers.map(({ header, id, tooltip, sx }) => (
|
||||
<CellHeader tooltip={tooltip} key={id} sx={sx}>
|
||||
{header}
|
||||
</CellHeader>
|
||||
))}
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
<TableRow key="node-data">
|
||||
{cells.map(({ cell, id, align, size, color, sx }) => (
|
||||
<CellValue align={align} size={size} key={id} sx={sx} color={color}>
|
||||
{cell}
|
||||
</CellValue>
|
||||
))}
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
);
|
||||
@@ -0,0 +1,28 @@
|
||||
import React from 'react';
|
||||
import { Button, Dialog, DialogActions, DialogContent, DialogTitle } from '@mui/material';
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
children?: React.ReactNode;
|
||||
title: string;
|
||||
confirmText: string;
|
||||
}
|
||||
|
||||
export const SimpleDialog = ({ open, onClose, children, title, confirmText }: Props) => (
|
||||
<Dialog
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
aria-labelledby="responsive-dialog-title"
|
||||
maxWidth="sm"
|
||||
sx={{ textAlign: 'center' }}
|
||||
>
|
||||
<DialogTitle id="responsive-dialog-title">{title}</DialogTitle>
|
||||
<DialogContent>{children}</DialogContent>
|
||||
<DialogActions sx={{ px: 2, pb: 2 }}>
|
||||
<Button onClick={onClose} variant="contained" autoFocus fullWidth>
|
||||
{confirmText}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
@@ -34,18 +34,18 @@ export const SummaryModal = ({ open, onClose, onSubmit, header, buttonText, iden
|
||||
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>
|
||||
<Typography>Identity Key</Typography>
|
||||
<Typography>{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 direction="row" justifyContent="space-between">
|
||||
<Typography>Amount</Typography>
|
||||
<Typography>{`${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 direction="row" justifyContent="space-between" mb={1}>
|
||||
<Typography>Fee for this operation</Typography>
|
||||
<Typography>{fee}</Typography>
|
||||
</Stack>
|
||||
</SimpleModal>
|
||||
);
|
||||
|
||||
@@ -4,8 +4,8 @@ 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 { MixnodeCard } from './MixnodeCard';
|
||||
import { GatewayCard } from './GatewayCard';
|
||||
import { EnumRequestStatus } from '../../components';
|
||||
import { useCheckOwnership } from '../../hooks/useCheckOwnership';
|
||||
|
||||
@@ -26,13 +26,7 @@ const Bonding = () => {
|
||||
return (
|
||||
<PageLayout>
|
||||
<Box display="flex" flexDirection="column" gap={2}>
|
||||
{!bondedMixnode &&
|
||||
!bondedGateway &&
|
||||
status === EnumRequestStatus.initial &&
|
||||
!ownership.hasOwnership &&
|
||||
!isLoading && <BondingCard />}
|
||||
{bondedMixnode && <BondedMixnodeCard />}
|
||||
{bondedGateway && <BondedGatewayCard />}
|
||||
{bondedMixnode && <MixnodeCard mixnode={bondedMixnode} />}
|
||||
</Box>
|
||||
</PageLayout>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user