Compare commits

...

22 Commits

Author SHA1 Message Date
Gala ebd986fae2 Merge branch 'feat/2130-tables-update' of github.com:nymtech/nym into feat/2130-tables-update 2022-12-21 11:28:32 +01:00
Gala 82b3db8c3b cleaning 2022-12-21 11:28:12 +01:00
Gala 498aedf15a refactor from PR request 2022-12-21 11:28:12 +01:00
Gala d1bac9a1f3 refactor 2022-12-21 11:28:12 +01:00
Gala c9b790d3f6 refactor 2022-12-21 11:28:12 +01:00
Gala 791425014a delegations layout and tooltip when delegate with vesting 2022-12-21 11:28:11 +01:00
Gala a7c98733ff delegations table changes wip 2022-12-21 11:28:11 +01:00
Gala f737cb3bb4 tidying up 2022-12-21 11:28:11 +01:00
Gala f6931d1500 change date order to dd/mm/yy 2022-12-21 11:28:11 +01:00
Gala 7391463cae removing epoch on settings and fixing divider position 2022-12-21 11:28:11 +01:00
Gala 30ce58e4b1 bond table changes 2022-12-21 11:28:11 +01:00
Gala efd300f8fe Merge branch 'release/v1.1.5' into feat/2130-tables-update 2022-12-21 11:12:49 +01:00
Gala 07edf1a626 cleaning 2022-12-19 15:27:35 +01:00
Gala 2b2b04f147 refactor from PR request 2022-12-19 15:23:39 +01:00
Gala 094b2db7f7 refactor 2022-12-19 10:52:29 +01:00
Gala 8ed312fe1c refactor 2022-12-19 10:37:04 +01:00
Gala 6c6e5d98b7 delegations layout and tooltip when delegate with vesting 2022-12-16 08:04:33 +01:00
Gala 4fe3ac7da5 delegations table changes wip 2022-12-15 17:29:48 +01:00
Gala d7e4969a3e tidying up 2022-12-15 16:45:49 +01:00
Gala 415bfd66aa change date order to dd/mm/yy 2022-12-15 15:32:50 +01:00
Gala 9af6f824d4 removing epoch on settings and fixing divider position 2022-12-15 15:22:44 +01:00
Gala ccfb98bdae bond table changes 2022-12-15 14:18:01 +01:00
6 changed files with 153 additions and 97 deletions
@@ -1,4 +1,4 @@
import React from 'react'; import React, { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { Box, Button, Chip, Stack, Tooltip, Typography } from '@mui/material'; import { Box, Button, Chip, Stack, Tooltip, Typography } from '@mui/material';
import { Link } from '@nymproject/react/link/Link'; import { Link } from '@nymproject/react/link/Link';
@@ -11,6 +11,9 @@ import { Node as NodeIcon } from '../../svg-icons/node';
import { Cell, Header, NodeTable } from './NodeTable'; import { Cell, Header, NodeTable } from './NodeTable';
import { BondedMixnodeActions, TBondedMixnodeActions } from './BondedMixnodeActions'; import { BondedMixnodeActions, TBondedMixnodeActions } from './BondedMixnodeActions';
import { NodeStats } from './NodeStats'; import { NodeStats } from './NodeStats';
import { getIntervalAsDate } from 'src/utils';
const textWhenNotName = 'This node has not yet set a name';
const headers: Header[] = [ const headers: Header[] = [
{ {
@@ -63,6 +66,7 @@ export const BondedMixnode = ({
network?: Network; network?: Network;
onActionSelect: (action: TBondedMixnodeActions) => void; onActionSelect: (action: TBondedMixnodeActions) => void;
}) => { }) => {
const [nextEpoch, setNextEpoch] = useState<string | Error>();
const navigate = useNavigate(); const navigate = useNavigate();
const { const {
name, name,
@@ -78,6 +82,15 @@ export const BondedMixnode = ({
identityKey, identityKey,
host, host,
} = mixnode; } = mixnode;
const getNextInterval = async () => {
try {
const { nextEpoch } = await getIntervalAsDate();
setNextEpoch(nextEpoch);
} catch {
setNextEpoch(Error());
}
};
const cells: Cell[] = [ const cells: Cell[] = [
{ {
cell: `${stake.amount} ${stake.denom}`, cell: `${stake.amount} ${stake.denom}`,
@@ -121,6 +134,10 @@ export const BondedMixnode = ({
}, },
]; ];
useEffect(() => {
getNextInterval();
}, []);
return ( return (
<Stack gap={2}> <Stack gap={2}>
<NymCard <NymCard
@@ -133,19 +150,24 @@ export const BondedMixnode = ({
</Typography> </Typography>
<NodeStatus status={status} /> <NodeStatus status={status} />
</Box> </Box>
{name && ( {name?.includes(textWhenNotName) ? null : (
<Tooltip title={host} arrow>
<Typography fontWeight="regular" variant="h6" width="fit-content"> <Typography fontWeight="regular" variant="h6" width="fit-content">
{name} {name}
</Typography> </Typography>
</Tooltip>
)} )}
<Tooltip title={host} placement="top" arrow>
<Box width="fit-content">
<IdentityKey identityKey={identityKey} /> <IdentityKey identityKey={identityKey} />
</Box>
</Tooltip>
</Stack> </Stack>
} }
Action={ Action={
isMixnode(mixnode) && ( <Box display="flex" flexDirection="column" alignItems="flex-end" justifyContent="space-between" height={70}>
<Tooltip title={mixnode.isUnbonding ? 'You have a pending unbond event. Node settings are disabled.' : ''}> {isMixnode(mixnode) && (
<Tooltip
title={mixnode.isUnbonding ? 'You have a pending unbond event. Node settings are disabled.' : ''}
>
<Box> <Box>
<Button <Button
variant="text" variant="text"
@@ -158,7 +180,13 @@ export const BondedMixnode = ({
</Button> </Button>
</Box> </Box>
</Tooltip> </Tooltip>
) )}
{nextEpoch instanceof Error ? null : (
<Typography fontSize={14} marginRight={1}>
Next epoch starts at <b>{nextEpoch}</b>
</Typography>
)}
</Box>
} }
> >
<NodeTable headers={headers} cells={cells} /> <NodeTable headers={headers} cells={cells} />
@@ -29,15 +29,18 @@ export const DelegationItem = ({
}) => { }) => {
const operatingCost = isDelegation(item) && item.cost_params?.interval_operating_cost; const operatingCost = isDelegation(item) && item.cost_params?.interval_operating_cost;
return ( const tooltipText = () => {
<Tooltip if (nodeIsUnbonded) {
arrow return 'This node has unbonded and it does not exist anymore. You need to undelegate from it to get your stake and outstanding rewards (if any) back.';
title={ } else if (item.uses_vesting_contract_tokens) {
nodeIsUnbonded return 'Delegation made with locked tockens';
? 'This node has unbonded and it does not exist anymore. You need to undelegate from it to get your stake and outstanding rewards (if any) back.' } else {
: '' return '';
} }
> };
return (
<Tooltip arrow title={tooltipText()}>
<TableRow key={item.node_identity} sx={{ color: !item.node_identity ? 'error.main' : 'inherit' }}> <TableRow key={item.node_identity} sx={{ color: !item.node_identity ? 'error.main' : 'inherit' }}>
<TableCell sx={{ color: 'inherit', pr: 1 }} padding="normal"> <TableCell sx={{ color: 'inherit', pr: 1 }} padding="normal">
{nodeIsUnbonded ? ( {nodeIsUnbonded ? (
@@ -1,24 +1,12 @@
import React, { useContext, useEffect, useState } from 'react'; import React, { useContext, useEffect, useState } from 'react';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { import { Button, Divider, Typography, TextField, InputAdornment, Grid, Box, FormHelperText } from '@mui/material';
Button,
Divider,
Typography,
TextField,
InputAdornment,
Grid,
CircularProgress,
Box,
FormHelperText,
} from '@mui/material';
import { useTheme } from '@mui/material/styles'; import { useTheme } from '@mui/material/styles';
import { CurrencyDenom, MixNodeCostParams } from '@nymproject/types'; import { CurrencyDenom, MixNodeCostParams } from '@nymproject/types';
import { CurrencyFormField } from '@nymproject/react/currency/CurrencyFormField'; import { CurrencyFormField } from '@nymproject/react/currency/CurrencyFormField';
import { add, format, fromUnixTime } from 'date-fns';
import { isMixnode } from 'src/types'; import { isMixnode } from 'src/types';
import { import {
getCurrentInterval,
getPendingIntervalEvents, getPendingIntervalEvents,
simulateUpdateMixnodeCostParams, simulateUpdateMixnodeCostParams,
simulateVestingUpdateMixnodeCostParams, simulateVestingUpdateMixnodeCostParams,
@@ -29,6 +17,7 @@ import { TBondedMixnode } from 'src/context/bonding';
import { SimpleModal } from 'src/components/Modals/SimpleModal'; import { SimpleModal } from 'src/components/Modals/SimpleModal';
import { bondedNodeParametersValidationSchema } from 'src/components/Bonding/forms/mixnodeValidationSchema'; import { bondedNodeParametersValidationSchema } from 'src/components/Bonding/forms/mixnodeValidationSchema';
import { Console } from 'src/utils/console'; import { Console } from 'src/utils/console';
import { getIntervalAsDate } from 'src/utils';
import { Alert } from 'src/components/Alert'; import { Alert } from 'src/components/Alert';
import { ChangeMixCostParams } from 'src/pages/bonding/types'; import { ChangeMixCostParams } from 'src/pages/bonding/types';
import { AppContext } from 'src/context'; import { AppContext } from 'src/context';
@@ -39,7 +28,6 @@ import { LoadingModal } from 'src/components/Modals/LoadingModal';
export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode }): JSX.Element => { export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode }): JSX.Element => {
const [openConfirmationModal, setOpenConfirmationModal] = useState<boolean>(false); const [openConfirmationModal, setOpenConfirmationModal] = useState<boolean>(false);
const [intervalTime, setIntervalTime] = useState<string>(); const [intervalTime, setIntervalTime] = useState<string>();
const [nextEpoch, setNextEpoch] = useState<string>();
const [pendingUpdates, setPendingUpdates] = useState<MixNodeCostParams>(); const [pendingUpdates, setPendingUpdates] = useState<MixNodeCostParams>();
const { clientDetails } = useContext(AppContext); const { clientDetails } = useContext(AppContext);
const theme = useTheme(); const theme = useTheme();
@@ -63,27 +51,13 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode
defaultValues, defaultValues,
}); });
const getIntervalAsDate = async () => { const getNextInterval = async () => {
const interval = await getCurrentInterval(); try {
const secondsToNextInterval = const { intervalTime } = await getIntervalAsDate();
Number(interval.epochs_in_interval - interval.current_epoch_id) * Number(interval.epoch_length_seconds); setIntervalTime(intervalTime);
} catch {
setIntervalTime( console.log('cant retrieve next interval');
format( }
add(new Date(), {
seconds: secondsToNextInterval,
}),
'MM/dd/yyyy HH:mm',
),
);
setNextEpoch(
format(
add(fromUnixTime(Number(interval.current_epoch_start_unix)), {
seconds: Number(interval.epoch_length_seconds),
}),
'HH:mm',
),
);
}; };
const getPendingEvents = async () => { const getPendingEvents = async () => {
@@ -107,7 +81,7 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode
}; };
useEffect(() => { useEffect(() => {
getIntervalAsDate(); getNextInterval();
getPendingEvents(); getPendingEvents();
}, []); }, []);
@@ -137,7 +111,16 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode
}; };
return ( return (
<Grid container xs item> <Grid
container
xs
item
sx={{
'& .MuiGrid-item': {
pl: 0,
},
}}
>
{fee && ( {fee && (
<ConfirmTx <ConfirmTx
open open
@@ -152,9 +135,6 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode
<Alert <Alert
title={ title={
<> <>
<Box component="span" sx={{ fontWeight: 600, mr: 2 }}>
{`Next epoch ${nextEpoch}`}
</Box>
<Box component="span" sx={{ fontWeight: 600 }}>{`Next interval: ${intervalTime}`}</Box> <Box component="span" sx={{ fontWeight: 600 }}>{`Next interval: ${intervalTime}`}</Box>
</> </>
} }
@@ -206,7 +186,7 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode
</Grid> </Grid>
)} )}
</Grid> </Grid>
<Divider flexItem /> <Divider flexItem sx={{ position: 'relative', left: '-24px', width: 'calc(100% + 24px)' }} />
<Grid item container direction="row" alignItems="left" justifyContent="space-between" padding={3} spacing={1}> <Grid item container direction="row" alignItems="left" justifyContent="space-between" padding={3} spacing={1}>
<Grid item> <Grid item>
<Typography variant="body1" sx={{ fontWeight: 600, mb: 1 }}> <Typography variant="body1" sx={{ fontWeight: 600, mb: 1 }}>
@@ -249,7 +229,7 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode
</Grid> </Grid>
</Grid> </Grid>
</Grid> </Grid>
<Divider flexItem /> <Divider flexItem sx={{ position: 'relative', left: '-24px', width: 'calc(100% + 24px)' }} />
<Grid container justifyContent="end"> <Grid container justifyContent="end">
<Button <Button
size="large" size="large"
+32 -12
View File
@@ -18,7 +18,7 @@ import { DelegationListItemActions } from '../../components/Delegation/Delegatio
import { RedeemModal } from '../../components/Rewards/RedeemModal'; import { RedeemModal } from '../../components/Rewards/RedeemModal';
import { DelegationModal, DelegationModalProps } from '../../components/Delegation/DelegationModal'; import { DelegationModal, DelegationModalProps } from '../../components/Delegation/DelegationModal';
import { backDropStyles, modalStyles } from '../../../.storybook/storiesStyles'; import { backDropStyles, modalStyles } from '../../../.storybook/storiesStyles';
import { toPercentIntegerString } from '../../utils'; import { toPercentIntegerString, getIntervalAsDate } from 'src/utils';
const storybookStyles = (theme: Theme, isStorybook?: boolean, backdropProps?: object) => const storybookStyles = (theme: Theme, isStorybook?: boolean, backdropProps?: object) =>
isStorybook isStorybook
@@ -36,9 +36,9 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => {
const [confirmationModalProps, setConfirmationModalProps] = useState<DelegationModalProps | undefined>(); const [confirmationModalProps, setConfirmationModalProps] = useState<DelegationModalProps | undefined>();
const [currentDelegationListActionItem, setCurrentDelegationListActionItem] = useState<DelegationWithEverything>(); const [currentDelegationListActionItem, setCurrentDelegationListActionItem] = useState<DelegationWithEverything>();
const [saturationError, setSaturationError] = useState<{ action: 'compound' | 'delegate'; saturation: string }>(); const [saturationError, setSaturationError] = useState<{ action: 'compound' | 'delegate'; saturation: string }>();
const [nextEpoch, setNextEpoch] = useState<string | Error>();
const theme = useTheme(); const theme = useTheme();
const { const {
clientDetails, clientDetails,
network, network,
@@ -75,6 +75,15 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => {
}; };
}; };
const getNextInterval = async () => {
try {
const { nextEpoch } = await getIntervalAsDate();
setNextEpoch(nextEpoch);
} catch {
setNextEpoch(Error());
}
};
// Refresh the rewards and delegations periodically when page is mounted // Refresh the rewards and delegations periodically when page is mounted
useEffect(() => { useEffect(() => {
const timer = setInterval(refresh, 1 * 60 * 1000); // every 1 minute const timer = setInterval(refresh, 1 * 60 * 1000); // every 1 minute
@@ -83,6 +92,7 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => {
useEffect(() => { useEffect(() => {
refresh(); refresh();
getNextInterval();
}, [clientDetails, confirmationModalProps]); }, [clientDetails, confirmationModalProps]);
const handleDelegationItemActionClick = (item: DelegationWithEverything, action: DelegationListItemActions) => { const handleDelegationItemActionClick = (item: DelegationWithEverything, action: DelegationListItemActions) => {
@@ -332,7 +342,9 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => {
<> <>
<Paper elevation={0} sx={{ p: 3, mt: 4 }}> <Paper elevation={0} sx={{ p: 3, mt: 4 }}>
<Stack spacing={3}> <Stack spacing={3}>
<Box display="flex" justifyContent="space-between" alignItems="center"> <Box display="flex" justifyContent="space-between">
{' '}
<Box display="flex" flexDirection="column">
<Typography variant="h6" lineHeight={1.334} fontWeight={600}> <Typography variant="h6" lineHeight={1.334} fontWeight={600}>
Delegations Delegations
</Typography> </Typography>
@@ -345,22 +357,30 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => {
fontSize={14} fontSize={14}
fontWeight={theme.palette.mode === 'light' ? 400 : 600} fontWeight={theme.palette.mode === 'light' ? 400 : 600}
noIcon noIcon
marginTop={1.5}
/> />
)} )}
</Box> </Box>
{!!delegations?.length && (
<Button
variant="contained"
disableElevation
onClick={() => setShowNewDelegationModal(true)}
sx={{ py: 1.5, px: 5, color: 'primary.contrastText', height: 'fit-content' }}
>
Delegate
</Button>
)}
</Box>
{!!delegations?.length && ( {!!delegations?.length && (
<Box display="flex" justifyContent="space-between" alignItems="end"> <Box display="flex" justifyContent="space-between" alignItems="end">
<RewardsSummary isLoading={isLoading} totalDelegation={totalDelegations} totalRewards={totalRewards} /> <RewardsSummary isLoading={isLoading} totalDelegation={totalDelegations} totalRewards={totalRewards} />
{nextEpoch instanceof Error ? null : (
<Button <Typography fontSize={14}>
variant="contained" Next epoch starts at <b>{nextEpoch}</b>
disableElevation </Typography>
onClick={() => setShowNewDelegationModal(true)} )}
sx={{ py: 1.5, px: 5, color: 'primary.contrastText' }}
>
Delegate
</Button>
</Box> </Box>
)} )}
{delegationsComponent(delegations)} {delegationsComponent(delegations)}
+2
View File
@@ -7,6 +7,8 @@ import { TPoolOption } from 'src/components';
import { getDefaultMixnodeCostParams, getLockedCoins, getSpendableCoins, userBalance } from '../requests'; import { getDefaultMixnodeCostParams, getLockedCoins, getSpendableCoins, userBalance } from '../requests';
import { Console } from './console'; import { Console } from './console';
export * from './nextEpoch';
export const validateKey = (key: string, bytesLength: number): boolean => { export const validateKey = (key: string, bytesLength: number): boolean => {
// it must be a valid base58 key // it must be a valid base58 key
try { try {
+23
View File
@@ -0,0 +1,23 @@
import { getCurrentInterval } from 'src/requests';
import { add, format, fromUnixTime } from 'date-fns';
export const getIntervalAsDate = async () => {
const interval = await getCurrentInterval();
const secondsToNextInterval =
Number(interval.epochs_in_interval - interval.current_epoch_id) * Number(interval.epoch_length_seconds);
const intervalTime = format(
add(new Date(), {
seconds: secondsToNextInterval,
}),
'dd/MM/yyyy, HH:mm',
);
const nextEpoch = format(
add(fromUnixTime(Number(interval.current_epoch_start_unix)), {
seconds: Number(interval.epoch_length_seconds),
}),
'HH:mm',
);
return { intervalTime, nextEpoch };
};