diff --git a/nym-wallet/src/components/Bonding/BondedMixnode.tsx b/nym-wallet/src/components/Bonding/BondedMixnode.tsx index 3faee0e4b9..27ecf34df3 100644 --- a/nym-wallet/src/components/Bonding/BondedMixnode.tsx +++ b/nym-wallet/src/components/Bonding/BondedMixnode.tsx @@ -1,6 +1,8 @@ import React from 'react'; +import { useNavigate } from 'react-router-dom'; import { Box, Button, Stack, Typography } from '@mui/material'; import { Link } from '@nymproject/react/link/Link'; +import { isMixnode } from 'src/types'; import { TBondedMixnode, urls } from 'src/context'; import { NymCard } from 'src/components'; import { Network } from 'src/types'; @@ -60,6 +62,7 @@ export const BondedMixnode = ({ network?: Network; onActionSelect: (action: TBondedMixnodeActions) => void; }) => { + const navigate = useNavigate(); const { name, stake, @@ -133,14 +136,16 @@ export const BondedMixnode = ({ } Action={ - + isMixnode(mixnode) && ( + + ) } > diff --git a/nym-wallet/src/components/Bonding/forms/mixnodeValidationSchema.ts b/nym-wallet/src/components/Bonding/forms/mixnodeValidationSchema.ts index aa9e66db1c..01ff72bdff 100644 --- a/nym-wallet/src/components/Bonding/forms/mixnodeValidationSchema.ts +++ b/nym-wallet/src/components/Bonding/forms/mixnodeValidationSchema.ts @@ -49,3 +49,31 @@ export const amountSchema = Yup.object().shape({ }), profitMargin: Yup.number().required('Profit Percentage is required').min(0).max(100), }); + +export const bondedInfoParametersValidationSchema = Yup.object().shape({ + host: Yup.string() + .required('A host is required') + .test('valid-host', 'A valid host is required', (value) => (value ? isValidHostname(value) : false)), + + version: Yup.string() + .required('A version is required') + .test('valid-version', 'A valid version is required', (value) => (value ? validateVersion(value) : false)), + + mixPort: Yup.number() + .required('A mixport is required') + .test('valid-mixport', 'A valid mixport is required', (value) => (value ? validateRawPort(value) : false)), + + verlocPort: Yup.number() + .required('A verloc port is required') + .test('valid-verloc', 'A valid verloc port is required', (value) => (value ? validateRawPort(value) : false)), + + httpApiPort: Yup.number() + .required('A http-api port is required') + .test('valid-http', 'A valid http-api port is required', (value) => (value ? validateRawPort(value) : false)), +}); + +export const bondedNodeParametersValidationSchema = Yup.object().shape({ + profitMargin: Yup.number().required('Profit Percentage is required').min(0).max(100), + + operatorCost: Yup.number().required('Operator cost is required'), +}) diff --git a/nym-wallet/src/components/Bonding/modals/NodeSettingsModal.tsx b/nym-wallet/src/components/Bonding/modals/NodeSettingsModal.tsx index ac8cf7fc36..34b35e7a3f 100644 --- a/nym-wallet/src/components/Bonding/modals/NodeSettingsModal.tsx +++ b/nym-wallet/src/components/Bonding/modals/NodeSettingsModal.tsx @@ -12,6 +12,7 @@ import { simulateUpdateMixnodeCostParams, simulateVestingUpdateMixnodeCostParams import { LoadingModal } from 'src/components/Modals/LoadingModal'; import { FeeDetails } from '@nymproject/types'; +//Now we are using the node setting page instead of this modal export const NodeSettings = ({ currentPm, isVesting, @@ -19,13 +20,13 @@ export const NodeSettings = ({ onClose, onError, }: { - currentPm: TBondedMixnode['profitMargin']; - isVesting: boolean; + currentPm?: TBondedMixnode['profitMargin']; + isVesting?: boolean; onConfirm: (profitMargin: string, fee?: FeeDetails) => Promise; onClose: () => void; onError: (err: string) => void; }) => { - const [pm, setPm] = useState(currentPm.toString()); + const [pm, setPm] = useState(currentPm?.toString()); const [error, setError] = useState(false); const { fee, getFee, resetFeeState, isFeeLoading, feeError } = useGetFee(); @@ -52,13 +53,15 @@ export const NodeSettings = ({ return; } - // TODO: this will have to be updated with allowing users to provide their operating cost in the form - const defaultCostParams = await attachDefaultOperatingCost(toPercentFloatString(pm)); + if (pm) { + // TODO: this will have to be updated with allowing users to provide their operating cost in the form + const defaultCostParams = await attachDefaultOperatingCost(toPercentFloatString(pm)); - if (isVesting) { - await getFee(simulateVestingUpdateMixnodeCostParams, defaultCostParams); - } else { - await getFee(simulateUpdateMixnodeCostParams, defaultCostParams); + if (isVesting) { + await getFee(simulateVestingUpdateMixnodeCostParams, defaultCostParams); + } else { + await getFee(simulateUpdateMixnodeCostParams, defaultCostParams); + } } }; @@ -74,7 +77,7 @@ export const NodeSettings = ({ if (isFeeLoading) return ; - if (fee) + if (fee && pm) return ( - + Set profit margin diff --git a/nym-wallet/src/components/Bonding/modals/UnbondModal.tsx b/nym-wallet/src/components/Bonding/modals/UnbondModal.tsx index 07b1e90b0e..15b46b517b 100644 --- a/nym-wallet/src/components/Bonding/modals/UnbondModal.tsx +++ b/nym-wallet/src/components/Bonding/modals/UnbondModal.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { Box, TextField, Typography } from '@mui/material'; +import { Typography } from '@mui/material'; import { useEffect, useState } from 'react'; import { TBondedGateway, TBondedMixnode } from 'src/context'; import { useGetFee } from 'src/hooks/useGetFee'; @@ -13,8 +13,6 @@ import { simulateVestingUnbondGateway, simulateVestingUnbondMixnode, } from '../../../requests'; -import { ConfirmationModal } from '../../Modals/ConfirmationModal'; -import { Error } from '../../Error'; interface Props { node: TBondedMixnode | TBondedGateway; @@ -25,9 +23,6 @@ interface Props { export const UnbondModal = ({ node, onConfirm, onClose, onError }: Props) => { const { fee, isFeeLoading, getFee, feeError } = useGetFee(); - const [isConfirmed, setIsConfirmed] = useState(false); - const [showConfirmModal, setShowConfirmModal] = useState(true); - const [confirmField, setConfirmField] = useState(''); useEffect(() => { if (feeError) { @@ -53,63 +48,18 @@ export const UnbondModal = ({ node, onConfirm, onClose, onError }: Props) => { } }, [node]); - if (showConfirmModal) { - return ( - { - setIsConfirmed(true); - setShowConfirmModal(false); - }} - onClose={onClose} - disabled={confirmField !== 'UNBOND'} - > - - If you unbond your node you will loose all your delegators! - - - - To unbond, type{' '} - t.palette.nym.highlight }}> - UNBOND - {' '} - in the field below and click UNBOND button - - setConfirmField(e.target.value)} /> - - ); - } - - if (isConfirmed) { - return ( - - - {isMixnode(node) && ( - - )} - - Tokens will be transferred to the account you are logged in with now - - ); - } - return ; + return ( + + + + Tokens will be transferred to the account you are logged in with now + + ); }; diff --git a/nym-wallet/src/components/Modals/SimpleModal.tsx b/nym-wallet/src/components/Modals/SimpleModal.tsx index 62673b9a63..782a83ecc1 100644 --- a/nym-wallet/src/components/Modals/SimpleModal.tsx +++ b/nym-wallet/src/components/Modals/SimpleModal.tsx @@ -2,6 +2,7 @@ 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 InfoOutlinedIcon from '@mui/icons-material/InfoOutlined'; import { StyledBackButton } from 'src/components/StyledBackButton'; import { modalStyle } from './styles'; @@ -9,8 +10,10 @@ export const SimpleModal: React.FC<{ open: boolean; hideCloseIcon?: boolean; displayErrorIcon?: boolean; + displayInfoIcon?: boolean; headerStyles?: SxProps; subHeaderStyles?: SxProps; + buttonFullWidth?: boolean; onClose?: () => void; onOk?: () => Promise; onBack?: () => void; @@ -24,8 +27,10 @@ export const SimpleModal: React.FC<{ open, hideCloseIcon, displayErrorIcon, + displayInfoIcon, headerStyles, subHeaderStyles, + buttonFullWidth, onClose, okDisabled, onOk, @@ -40,6 +45,7 @@ export const SimpleModal: React.FC<{ `1px solid ${t.palette.nym.nymWallet.modal.border}`, ...modalStyle, ...sx }}> {displayErrorIcon && } + {displayInfoIcon && theme.palette.nym.nymWallet.text.blue }} />} {typeof header === 'string' ? ( @@ -64,8 +70,8 @@ export const SimpleModal: React.FC<{ {children} {(onOk || onBack) && ( - - {onBack && } + + {onBack && } {onOk && ( + + + + {isConfirmed && ( + setIsConfirmed(false)} onError={onError} /> + )} + + ); +}; diff --git a/nym-wallet/src/pages/bonding/node-settings/settings-pages/general-settings/InfoSettings.tsx b/nym-wallet/src/pages/bonding/node-settings/settings-pages/general-settings/InfoSettings.tsx new file mode 100644 index 0000000000..fedc986d8c --- /dev/null +++ b/nym-wallet/src/pages/bonding/node-settings/settings-pages/general-settings/InfoSettings.tsx @@ -0,0 +1,247 @@ +import { useEffect, useState } from 'react'; +import { useForm } from 'react-hook-form'; +import { yupResolver } from '@hookform/resolvers/yup'; +import { Button, Divider, Typography, TextField, Grid, Alert, IconButton, CircularProgress, Box } from '@mui/material'; +import { useTheme } from '@mui/material/styles'; +import CloseIcon from '@mui/icons-material/Close'; +import { isMixnode } from 'src/types'; +import { updateMixnodeConfig } from 'src/requests'; +import { TBondedMixnode, TBondedGateway } from 'src/context/bonding'; +import { SimpleModal } from 'src/components/Modals/SimpleModal'; +import { bondedInfoParametersValidationSchema } from 'src/components/Bonding/forms/mixnodeValidationSchema'; +import { Console } from 'src/utils/console'; + +export const InfoSettings = ({ bondedNode }: { bondedNode: TBondedMixnode | TBondedGateway }) => { + const [open, setOpen] = useState(true); + const [openConfirmationModal, setOpenConfirmationModal] = useState(false); + + const theme = useTheme(); + + const { + register, + handleSubmit, + formState: { errors, isSubmitting, isDirty, isValid }, + } = useForm({ + resolver: yupResolver(bondedInfoParametersValidationSchema), + mode: 'onChange', + defaultValues: isMixnode(bondedNode) ? bondedNode : {}, + }); + + const onSubmit = async (data: { + host?: string; + version?: string; + mixPort?: number; + verlocPort?: number; + httpApiPort?: number; + }) => { + const { host, version, mixPort, verlocPort, httpApiPort } = data; + if (host && version && mixPort && verlocPort && httpApiPort) { + const MixNodeConfigParams = { + host, + mix_port: mixPort, + verloc_port: verlocPort, + http_api_port: httpApiPort, + version, + }; + try { + await updateMixnodeConfig(MixNodeConfigParams); + setOpenConfirmationModal(true); + } catch (error) { + Console.error(error); + } + } + }; + + return ( + + {open && ( + { + setOpen(false); + }} + > + + + } + sx={{ + px: 2, + borderRadius: 0, + bgcolor: 'background.default', + color: (theme) => theme.palette.nym.nymWallet.text.blue, + '& .MuiAlert-icon': { color: (theme) => theme.palette.nym.nymWallet.text.blue, mr: 1 }, + }} + > + Your changes will be ONLY saved on the display. Remember to change the + values on your node’s config file too. + + )} + + + + + Port + + (t.palette.mode === 'light' ? t.palette.nym.text.muted : 'text.primary'), + }} + > + Change profit margin of your node + + + + + + + + + + + + + + + + + + + Host + + (t.palette.mode === 'light' ? t.palette.nym.text.muted : 'text.primary'), + }} + > + Lock wallet after certain time + + + + + + + + + + + + + Version + + (t.palette.mode === 'light' ? t.palette.nym.text.muted : 'text.primary'), + }} + > + Lock wallet after certain time + + + + + + + + + + + + + + { + await setOpenConfirmationModal(false); + }} + buttonFullWidth + sx={{ + width: '450px', + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + }} + headerStyles={{ + width: '100%', + mb: 1, + textAlign: 'center', + color: theme.palette.nym.nymWallet.text.blue, + fontSize: 16, + textTransform: 'capitalize', + }} + subHeaderStyles={{ + width: '100%', + mb: 1, + textAlign: 'center', + color: 'main', + fontSize: 14, + textTransform: 'capitalize', + }} + /> + + ); +}; diff --git a/nym-wallet/src/pages/bonding/node-settings/settings-pages/general-settings/ParametersSettings.tsx b/nym-wallet/src/pages/bonding/node-settings/settings-pages/general-settings/ParametersSettings.tsx new file mode 100644 index 0000000000..c0c7ebff6c --- /dev/null +++ b/nym-wallet/src/pages/bonding/node-settings/settings-pages/general-settings/ParametersSettings.tsx @@ -0,0 +1,218 @@ +import { useState } from 'react'; +import { useForm } from 'react-hook-form'; +import { yupResolver } from '@hookform/resolvers/yup'; +import { + Button, + Divider, + Typography, + TextField, + InputAdornment, + Grid, + Alert, + IconButton, + CircularProgress, + Box, +} from '@mui/material'; +import { useTheme } from '@mui/material/styles'; +import CloseIcon from '@mui/icons-material/Close'; +import { isMixnode } from 'src/types'; +import { updateMixnodeCostParams } from 'src/requests'; +import { TBondedMixnode, TBondedGateway } from 'src/context/bonding'; +import { SimpleModal } from 'src/components/Modals/SimpleModal'; +import { bondedNodeParametersValidationSchema } from 'src/components/Bonding/forms/mixnodeValidationSchema'; +import { Console } from 'src/utils/console'; + +export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode | TBondedGateway }): JSX.Element => { + const [open, setOpen] = useState(true); + const [openConfirmationModal, setOpenConfirmationModal] = useState(false); + + const theme = useTheme(); + + const { + register, + handleSubmit, + formState: { errors, isSubmitting, isDirty, isValid }, + } = useForm({ + resolver: yupResolver(bondedNodeParametersValidationSchema), + mode: 'onChange', + defaultValues: isMixnode(bondedNode) + ? { + operatorCost: bondedNode.bond.amount, + profitMargin: bondedNode.profitMargin, + } + : {}, + }); + + const onSubmit = async (data: { operatorCost?: string; profitMargin?: string }) => { + if (data.operatorCost && data.profitMargin) { + const MixNodeCostParams = { + profit_margin_percent: data.profitMargin.toString(), + interval_operating_cost: { + denom: bondedNode.bond.denom, + amount: data.operatorCost.toString(), + }, + }; + try { + await updateMixnodeCostParams(MixNodeCostParams); + setOpenConfirmationModal(true); + } catch (error) { + Console.error(error); + } + } + }; + + return ( + + {open && ( + { + setOpen(false); + }} + > + + + } + sx={{ + width: 1, + px: 2, + borderRadius: 0, + bgcolor: 'background.default', + color: (theme) => theme.palette.nym.nymWallet.text.blue, + '& .MuiAlert-icon': { color: (theme) => theme.palette.nym.nymWallet.text.blue, mr: 1 }, + }} + > + + Profit margin can be changed once a month, your changes will be applied in the next interval + + + )} + + + + + Profit Margin + + (t.palette.mode === 'light' ? t.palette.nym.text.muted : 'text.primary'), + }} + > + Profit margin can be changed once a month + + + + {isMixnode(bondedNode) && ( + + + % + + ), + }} + /> + + )} + + + + + + + Operator cost + + (t.palette.mode === 'light' ? t.palette.nym.text.muted : 'text.primary'), + }} + > + Lock Wallet after a certain time + + + + + + {bondedNode.bond.denom.toUpperCase()} + + ), + }} + /> + + + + + + + + + { + await setOpenConfirmationModal(false); + }} + buttonFullWidth + sx={{ + width: '320px', + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + }} + headerStyles={{ + width: '100%', + mb: 1, + textAlign: 'center', + color: theme.palette.nym.nymWallet.text.blue, + fontSize: 16, + textTransform: 'capitalize', + }} + subHeaderStyles={{ + m: 0, + }} + /> + + ); +}; diff --git a/nym-wallet/src/pages/bonding/node-settings/settings-pages/general-settings/index.tsx b/nym-wallet/src/pages/bonding/node-settings/settings-pages/general-settings/index.tsx new file mode 100644 index 0000000000..05f8193447 --- /dev/null +++ b/nym-wallet/src/pages/bonding/node-settings/settings-pages/general-settings/index.tsx @@ -0,0 +1,41 @@ +import React, { useContext, useEffect, useState } from 'react'; +import { Box, Button, Divider, Grid } from '@mui/material'; +import { TBondedMixnode, TBondedGateway } from '../../../../../context/bonding'; +import { InfoSettings } from './InfoSettings'; +import { ParametersSettings } from './ParametersSettings'; + +const nodeGeneralNav = ['Info', 'Parameters']; + +export const NodeGeneralSettings = ({ bondedNode }: { bondedNode: TBondedMixnode | TBondedGateway }) => { + const [settingsCard, setSettingsCard] = useState(nodeGeneralNav[0]); + //TODO: Check what happens with a gateway + return ( + + + + {nodeGeneralNav.map((item) => ( + + ))} + + + {settingsCard === nodeGeneralNav[0] && } + {settingsCard === nodeGeneralNav[1] && } + + + ); +}; diff --git a/nym-wallet/src/pages/terminal/index.tsx b/nym-wallet/src/pages/terminal/index.tsx index 2fbb609c23..3bd322318e 100644 --- a/nym-wallet/src/pages/terminal/index.tsx +++ b/nym-wallet/src/pages/terminal/index.tsx @@ -110,11 +110,11 @@ const TerminalInner: React.FC = () => { {status ? ( } sx={{ mb: 2 }}> - {status} + {status} ) : ( - Data loading complete + Data loading complete )} diff --git a/nym-wallet/src/routes/app.tsx b/nym-wallet/src/routes/app.tsx index d5366bb420..0fbc9edc6c 100644 --- a/nym-wallet/src/routes/app.tsx +++ b/nym-wallet/src/routes/app.tsx @@ -4,7 +4,7 @@ import { ApplicationLayout } from 'src/layouts'; import { Terminal } from 'src/pages/terminal'; import { Send } from 'src/components/Send'; import { Receive } from '../components/Receive'; -import { Balance, InternalDocs, DelegationPage, Admin, BondingPage } from '../pages'; +import { Balance, InternalDocs, DelegationPage, Admin, BondingPage, NodeSettingsPage } from '../pages'; export const AppRoutes = () => ( @@ -14,6 +14,7 @@ export const AppRoutes = () => ( } /> } /> + } /> } /> } /> } /> diff --git a/nym-wallet/src/theme/mui-theme.d.ts b/nym-wallet/src/theme/mui-theme.d.ts index b0868079f7..2b703db05a 100644 --- a/nym-wallet/src/theme/mui-theme.d.ts +++ b/nym-wallet/src/theme/mui-theme.d.ts @@ -31,6 +31,7 @@ declare module '@mui/material/styles' { highlight: string; success: string; info: string; + red: string; fee: string; background: { light: string; dark: string }; text: { @@ -58,6 +59,7 @@ declare module '@mui/material/styles' { warn: string; contrast: string; grey: string; + blue: string; }; topNav: { background: string; diff --git a/nym-wallet/src/theme/theme.tsx b/nym-wallet/src/theme/theme.tsx index ef6c9dc4e6..569ad028b2 100644 --- a/nym-wallet/src/theme/theme.tsx +++ b/nym-wallet/src/theme/theme.tsx @@ -23,6 +23,7 @@ const nymPalette: NymPalette = { highlight: '#FB6E4E', success: '#21D073', info: '#60D7EF', + red: '#DA465B', fee: '#967FF0', background: { light: '#F4F6F8', dark: '#1D2125' }, text: { @@ -52,6 +53,7 @@ const darkMode: NymPaletteVariant = { warn: '#FFE600', contrast: '#1D2125', grey: '#5B6174', + blue: '#60D7EF', }, topNav: { background: '#111826', @@ -82,6 +84,7 @@ const lightMode: NymPaletteVariant = { warn: '#FFE600', contrast: '#FFFFFF', grey: '#3A4053', + blue: '#514EFB', }, topNav: { background: '#111826', @@ -288,6 +291,16 @@ export const getDesignTokens = (mode: PaletteMode): ThemeOptions => { }, }, }, + MuiToolbar: { + styleOverrides: { + root: { + minWidth: 0, + '@media (min-width: 0px)': { + minHeight: 'fit-content', + }, + }, + }, + }, MuiMenu: { styleOverrides: { list: ({ _, theme }) => ({