diff --git a/nym-wallet/src/components/Delegation/DelegateBlocker.tsx b/nym-wallet/src/components/Delegation/DelegateBlocker.tsx new file mode 100644 index 0000000000..2071226111 --- /dev/null +++ b/nym-wallet/src/components/Delegation/DelegateBlocker.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import { SimpleModal } from '../Modals/SimpleModal'; + +export const OverSaturatedBlockerModal: React.FC<{ + open: boolean; + onClose?: () => void; + identityKey?: string; + header?: string; + subHeader?: string; + buttonText?: string; +}> = ({ open, onClose, header, subHeader, buttonText }) => ( + +); diff --git a/nym-wallet/src/components/Delegation/DelegateModal.tsx b/nym-wallet/src/components/Delegation/DelegateModal.tsx index 1da4668c64..c9a50a383f 100644 --- a/nym-wallet/src/components/Delegation/DelegateModal.tsx +++ b/nym-wallet/src/components/Delegation/DelegateModal.tsx @@ -1,13 +1,14 @@ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { Box, Stack, Typography } from '@mui/material'; import { IdentityKeyFormField } from '@nymproject/react/mixnodes/IdentityKeyFormField'; import { CurrencyFormField } from '@nymproject/react/currency/CurrencyFormField'; import { CurrencyDenom, MajorCurrencyAmount } from '@nymproject/types'; import { getGasFee } from 'src/requests'; +import { Console } from 'src/utils/console'; import { SimpleModal } from '../Modals/SimpleModal'; import { ModalListItem } from './ModalListItem'; -import { validateKey } from '../../utils'; import { TokenPoolSelector, TPoolOption } from '../TokenPoolSelector'; +import { getMixnodeStakeSaturation } from '../../requests'; const MIN_AMOUNT_TO_DELEGATE = 10; @@ -50,10 +51,12 @@ export const DelegateModal: React.FC<{ }) => { const [identityKey, setIdentityKey] = useState(initialIdentityKey); const [amount, setAmount] = useState(initialAmount); - const [isValidated, setValidated] = useState(false); - const [errorAmount, setErrorAmount] = useState(); - const [tokenPool, setTokenPool] = useState('balance'); const [fee, setFee] = useState(); + const [tokenPool, setTokenPool] = useState('balance'); + const [isValidated, setValidated] = useState(false); + const [errorAmount, setErrorAmount] = useState(); + const [errorNodeSaturation, setErrorNodeSaturation] = useState(); + const [errorIdentityKey, setErrorIdentityKey] = useState(); const getFee = async () => { if (feeOverride) setFee(feeOverride); @@ -63,18 +66,41 @@ export const DelegateModal: React.FC<{ } }; - const validate = () => { - let newValidatedValue = true; - if (!identityKey || !validateKey(identityKey, 32)) { - newValidatedValue = false; + const handleCheckStakeSaturation = async (identity: string) => { + setErrorNodeSaturation(undefined); + + try { + const newSaturation = await getMixnodeStakeSaturation(identity); + if (newSaturation && newSaturation.saturation > 1) { + const saturationPercentage = Math.round(newSaturation.saturation * 100); + setErrorNodeSaturation(`This node is over saturated (${saturationPercentage}%), please select another node`); + } + } catch (e) { + Console.error('Error fetching the saturation, error:', e); + setErrorNodeSaturation(undefined); } - if (amount && Number(amount) < MIN_AMOUNT_TO_DELEGATE) { - setErrorAmount(`Min. delegation amount: ${MIN_AMOUNT_TO_DELEGATE} ${currency}`); - newValidatedValue = false; + }; + + const validateIdentityKey = async (isValid: boolean) => { + if (!isValid) { + setErrorIdentityKey('Identity key is invalid'); + setErrorNodeSaturation(undefined); } else { - setErrorAmount(undefined); + setErrorIdentityKey(undefined); + await handleCheckStakeSaturation(identityKey!); + } + }; + + const validateAmount = (newValue: MajorCurrencyAmount) => { + setErrorAmount(undefined); + + if (newValue.amount && Number(newValue.amount) < MIN_AMOUNT_TO_DELEGATE) { + setErrorAmount(`Min. delegation amount: ${MIN_AMOUNT_TO_DELEGATE} ${currency}`); + } + + if (!newValue.amount) { + setErrorAmount('Amount required'); } - setValidated(newValidatedValue); }; const handleOk = () => { @@ -83,8 +109,9 @@ export const DelegateModal: React.FC<{ } }; - const handleIdentityKeyChanged = (newIdentityKey: string) => { + const handleIdentityKeyChanged = async (newIdentityKey: string) => { setIdentityKey(newIdentityKey); + if (onIdentityKeyChanged) { onIdentityKeyChanged(newIdentityKey); } @@ -92,19 +119,22 @@ export const DelegateModal: React.FC<{ const handleAmountChanged = (newAmount: MajorCurrencyAmount) => { setAmount(newAmount.amount); + validateAmount(newAmount); + if (onAmountChanged) { onAmountChanged(newAmount.amount); } }; - React.useEffect(() => { - validate(); - }, [amount, identityKey]); - - React.useEffect(() => { + useEffect(() => { getFee(); }, []); + useEffect(() => { + if (!!errorIdentityKey || !!errorAmount || errorNodeSaturation) setValidated(false); + else setValidated(true); + }, [errorIdentityKey, errorAmount, errorNodeSaturation]); + return ( + + {errorNodeSaturation} + {hasVestingContract && setTokenPool(pool)} />} - + {errorAmount} diff --git a/nym-wallet/src/components/Delegation/DelegationActions.tsx b/nym-wallet/src/components/Delegation/DelegationActions.tsx index 2833a2ecde..4c36949f53 100644 --- a/nym-wallet/src/components/Delegation/DelegationActions.tsx +++ b/nym-wallet/src/components/Delegation/DelegationActions.tsx @@ -99,7 +99,8 @@ export const DelegationsActionsMenu: React.FC<{ isPending?: DelegationEventKind; disableRedeemingRewards?: boolean; disableDelegateMore?: boolean; -}> = ({ disableRedeemingRewards, disableDelegateMore, onActionClick, isPending }) => { + disableCompoundRewards?: boolean; +}> = ({ disableRedeemingRewards, disableDelegateMore, disableCompoundRewards, onActionClick, isPending }) => { const [anchorEl, setAnchorEl] = React.useState(null); const open = Boolean(anchorEl); const handleClick = (event: React.MouseEvent) => { @@ -135,7 +136,7 @@ export const DelegationsActionsMenu: React.FC<{ title="Delegate more" Icon={} onClick={() => handleActionSelect?.('delegate')} - disabled={disableDelegateMore} + disabled={disableDelegateMore || disableCompoundRewards} /> (onItemActionClick ? onItemActionClick(item, action) : undefined)} disableRedeemingRewards={!item.accumulated_rewards || item.accumulated_rewards.amount === '0'} - disableDelegateMore={(item?.stake_saturation || 0) > 100} + disableDelegateMore={(item?.stake_saturation || 0) > 1} + disableCompoundRewards={(item?.stake_saturation || 0) > 1} /> ) : ( ; +const BasePage: React.FC<{ children: React.ReactElement; handleClick: () => void }> = ({ + children, + handleClick, +}) => ( + <> + +

Lorem ipsum

+ +

+ Veniam dolor laborum labore sit reprehenderit enim mollit magna nulla adipisicing fugiat. Est ex irure quis sunt + velit elit do minim mollit non duis reprehenderit. Eiusmod dolore adipisicing ex nostrud consectetur culpa + exercitation do. Ad elit esse ipsum aliqua labore irure laborum qui culpa. +

+

+ Occaecat commodo excepteur anim ut officia dolor laboris dolore id occaecat enim qui eiusmod occaecat aliquip ad + tempor. Labore amet laborum magna amet consequat dolor cupidatat in consequat sunt aliquip magna laboris tempor + culpa est magna. Sit tempor cillum culpa sint ipsum nostrud ullamco voluptate exercitation dolore magna elit ut + mollit. +

+

+ Labore voluptate elit amet ipsum qui officia duis in et occaecat culpa ex do non labore mollit. Cillum cupidatat + duis ea dolore laboris laboris sunt duis anim consectetur cupidatat nulla ad minim sunt ea. Aliqua amet commodo + est irure sint magna sunt. Pariatur dolore commodo labore quis incididunt proident duis voluptate exercitation + in duis. Occaecat aliqua laboris reprehenderit nostrud est aute pariatur fugiat anim. Dolore sunt cillum ea + aliquip consectetur laborum ipsum qui veniam Lorem consectetur adipisicing velit magna aute. Amet tempor quis + excepteur minim culpa velit Lorem enim ad. +

+

+ Mollit laborum exercitation excepteur laboris adipisicing ipsum veniam cillum mollit voluptate do. Amet et anim + Lorem mollit minim duis cupidatat non. Consectetur sit deserunt nisi nisi non excepteur dolor eiusmod aute aute + irure anim dolore ipsum et veniam. +

+
+ {children} + +); + export const Default = () => { const [open, setOpen] = React.useState(true); + const handleClick = () => setOpen(true); + return ( - <> - -

Lorem ipsum

- -

- Veniam dolor laborum labore sit reprehenderit enim mollit magna nulla adipisicing fugiat. Est ex irure quis - sunt velit elit do minim mollit non duis reprehenderit. Eiusmod dolore adipisicing ex nostrud consectetur - culpa exercitation do. Ad elit esse ipsum aliqua labore irure laborum qui culpa. -

-

- Occaecat commodo excepteur anim ut officia dolor laboris dolore id occaecat enim qui eiusmod occaecat aliquip - ad tempor. Labore amet laborum magna amet consequat dolor cupidatat in consequat sunt aliquip magna laboris - tempor culpa est magna. Sit tempor cillum culpa sint ipsum nostrud ullamco voluptate exercitation dolore magna - elit ut mollit. -

-

- Labore voluptate elit amet ipsum qui officia duis in et occaecat culpa ex do non labore mollit. Cillum - cupidatat duis ea dolore laboris laboris sunt duis anim consectetur cupidatat nulla ad minim sunt ea. Aliqua - amet commodo est irure sint magna sunt. Pariatur dolore commodo labore quis incididunt proident duis voluptate - exercitation in duis. Occaecat aliqua laboris reprehenderit nostrud est aute pariatur fugiat anim. Dolore sunt - cillum ea aliquip consectetur laborum ipsum qui veniam Lorem consectetur adipisicing velit magna aute. Amet - tempor quis excepteur minim culpa velit Lorem enim ad. -

-

- Mollit laborum exercitation excepteur laboris adipisicing ipsum veniam cillum mollit voluptate do. Amet et - anim Lorem mollit minim duis cupidatat non. Consectetur sit deserunt nisi nisi non excepteur dolor eiusmod - aute aute irure anim dolore ipsum et veniam. -

-
+ setOpen(false)} @@ -63,44 +74,16 @@ export const Default = () => { elit ut mollit.

- +
); }; export const NoSubheader = () => { const [open, setOpen] = React.useState(true); + const handleClick = () => setOpen(true); + return ( - <> - -

Lorem ipsum

- -

- Veniam dolor laborum labore sit reprehenderit enim mollit magna nulla adipisicing fugiat. Est ex irure quis - sunt velit elit do minim mollit non duis reprehenderit. Eiusmod dolore adipisicing ex nostrud consectetur - culpa exercitation do. Ad elit esse ipsum aliqua labore irure laborum qui culpa. -

-

- Occaecat commodo excepteur anim ut officia dolor laboris dolore id occaecat enim qui eiusmod occaecat aliquip - ad tempor. Labore amet laborum magna amet consequat dolor cupidatat in consequat sunt aliquip magna laboris - tempor culpa est magna. Sit tempor cillum culpa sint ipsum nostrud ullamco voluptate exercitation dolore magna - elit ut mollit. -

-

- Labore voluptate elit amet ipsum qui officia duis in et occaecat culpa ex do non labore mollit. Cillum - cupidatat duis ea dolore laboris laboris sunt duis anim consectetur cupidatat nulla ad minim sunt ea. Aliqua - amet commodo est irure sint magna sunt. Pariatur dolore commodo labore quis incididunt proident duis voluptate - exercitation in duis. Occaecat aliqua laboris reprehenderit nostrud est aute pariatur fugiat anim. Dolore sunt - cillum ea aliquip consectetur laborum ipsum qui veniam Lorem consectetur adipisicing velit magna aute. Amet - tempor quis excepteur minim culpa velit Lorem enim ad. -

-

- Mollit laborum exercitation excepteur laboris adipisicing ipsum veniam cillum mollit voluptate do. Amet et - anim Lorem mollit minim duis cupidatat non. Consectetur sit deserunt nisi nisi non excepteur dolor eiusmod - aute aute irure anim dolore ipsum et veniam. -

-
+ setOpen(false)} @@ -117,6 +100,69 @@ export const NoSubheader = () => { Veniam dolor laborum labore sit reprehenderit enim mollit magna nulla adipisicing fugiat. Est ex irure quis.

- +
+ ); +}; + +export const hideCloseIcon = () => { + const [open, setOpen] = React.useState(true); + const handleClick = () => setOpen(true); + + return ( + + setOpen(false)} + onOk={() => setOpen(false)} + header="This is a modal" + okLabel="Kaplow!" + > +

+ Tempor culpa est magna. Sit tempor cillum culpa sint ipsum nostrud ullamco voluptate exercitation dolore magna + elit ut mollit. +

+ +

+ Veniam dolor laborum labore sit reprehenderit enim mollit magna nulla adipisicing fugiat. Est ex irure quis. +

+
+
+ ); +}; + +export const hideCloseIconAndDisplayErrorIcon = () => { + const [open, setOpen] = React.useState(true); + const handleClick = () => setOpen(true); + + return ( + + setOpen(false)} + onOk={() => setOpen(false)} + header="This modal announces an error !" + okLabel="Kaplow!" + sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center' }} + headerStyles={{ + width: '100%', + mb: 3, + textAlign: 'center', + color: 'error.main', + }} + subHeaderStyles={{ textAlign: 'center', color: 'text.primary', fontSize: 14, fontWeight: 400 }} + > +

+ Tempor culpa est magna. Sit tempor cillum culpa sint ipsum nostrud ullamco voluptate exercitation dolore magna + elit ut mollit. +

+ +

+ Veniam dolor laborum labore sit reprehenderit enim mollit magna nulla adipisicing fugiat. Est ex irure quis. +

+
+
); }; diff --git a/nym-wallet/src/components/Modals/SimpleModal.tsx b/nym-wallet/src/components/Modals/SimpleModal.tsx index 53e7c79d5a..0ef8fad222 100644 --- a/nym-wallet/src/components/Modals/SimpleModal.tsx +++ b/nym-wallet/src/components/Modals/SimpleModal.tsx @@ -1,10 +1,15 @@ import React from 'react'; import { Box, Button, Modal, Stack, SxProps, Typography } from '@mui/material'; import CloseIcon from '@mui/icons-material/Close'; +import ErrorOutline from '@mui/icons-material/ErrorOutline'; import { modalStyle } from './styles'; export const SimpleModal: React.FC<{ open: boolean; + hideCloseIcon?: boolean; + displayErrorIcon?: boolean; + headerStyles?: SxProps; + subHeaderStyles?: SxProps; onClose?: () => void; onOk?: () => void; header: string; @@ -12,17 +17,38 @@ export const SimpleModal: React.FC<{ okLabel: string; okDisabled?: boolean; sx?: SxProps; -}> = ({ open, onClose, okDisabled, onOk, header, subHeader, okLabel, sx, children }) => ( +}> = ({ + open, + hideCloseIcon, + displayErrorIcon, + headerStyles, + subHeaderStyles, + onClose, + okDisabled, + onOk, + header, + subHeader, + okLabel, + sx, + children, +}) => ( + {displayErrorIcon && } - + {header} - + {!hideCloseIcon && } {subHeader && ( - theme.palette.text.secondary}> + theme.palette.text.secondary} + sx={{ ...subHeaderStyles }} + > {subHeader} )} diff --git a/ts-packages/react-components/src/components/mixnodes/IdentityKeyFormField.tsx b/ts-packages/react-components/src/components/mixnodes/IdentityKeyFormField.tsx index e440b888ad..d718cf0b59 100644 --- a/ts-packages/react-components/src/components/mixnodes/IdentityKeyFormField.tsx +++ b/ts-packages/react-components/src/components/mixnodes/IdentityKeyFormField.tsx @@ -62,9 +62,10 @@ export const IdentityKeyFormField: React.FC<{ if (doValidation(newValue)) { setValue(newValue); - if (onChanged) { - onChanged(newValue); - } + } + + if (onChanged) { + onChanged(newValue); } };