Compare commits

...

19 Commits

Author SHA1 Message Date
fmtabbara fda9bc65fc [ci skip] Generate TS types 2022-03-10 19:15:26 +00:00
fmtabbara 3c476560d2 fix ts and es errors 2022-03-10 19:14:00 +00:00
fmtabbara bcd5531f40 [ci skip] Generate TS types 2022-03-10 15:36:02 +00:00
fmtabbara fc747503cf Merge branch 'feature/update-wallet-dependencies' of https://github.com/nymtech/nym into feature/update-wallet-dependencies 2022-03-10 15:34:36 +00:00
fmtabbara f3b7f7874e ts and es fixes 2022-03-10 15:34:28 +00:00
fmtabbara 36ec0e180e [ci skip] Generate TS types 2022-03-10 15:26:04 +00:00
fmtabbara b2ce0a8300 fix ts errors 2022-03-10 15:24:43 +00:00
Mark Sinclair 727a8305a3 Fix up typings for image and json modules
Add tsconfig for eslint to process webpack config
2022-03-10 14:30:54 +00:00
mmsinclair 2978d4ea6b [ci skip] Generate TS types 2022-03-10 13:28:46 +00:00
fmtabbara c31235b3f9 fix breaking change 2022-03-10 13:24:00 +00:00
fmtabbara 5c264d79bf fix sign in pages 2022-03-10 12:35:16 +00:00
Mark Sinclair 6bda9ae5f7 lint fixes 2022-03-10 12:20:26 +00:00
fmtabbara 9e932defb4 lint fixes 2022-03-10 11:59:07 +00:00
Mark Sinclair f249e7b497 linting fixes 2022-03-10 11:48:57 +00:00
fmtabbara 05a9c24437 linting fixes 2022-03-10 11:48:26 +00:00
Mark Sinclair eb6ecc7241 Formatting: fix linting errors for dependencies 2022-03-10 11:08:57 +00:00
Mark Sinclair f841242ce2 Formatting: run eslint --fix on all files 2022-03-10 11:05:52 +00:00
Mark Sinclair c3ba7e5ff2 Add eslint rules 2022-03-10 11:04:10 +00:00
Mark Sinclair 15ab890932 Use shared ts-packages in wallet 2022-03-10 10:55:21 +00:00
123 changed files with 2431 additions and 9216 deletions
-109
View File
@@ -1,109 +0,0 @@
{
"env": {
"browser": true,
"es6": true,
"node": true,
"jest": true
},
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": 2019,
"sourceType": "module"
},
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"plugins": ["react", "react-hooks", "jsx-a11y", "prettier", "jest"],
"extends": [
"plugin:react/recommended",
"airbnb",
"prettier",
"plugin:jest/recommended",
"plugin:jest/style"
],
"rules": {
"jest/prefer-strict-equal": "error",
"jest/prefer-to-have-length": "warn",
"prettier/prettier": "error",
"import/prefer-default-export": "off",
"react/prop-types": "off",
"react/jsx-filename-extension": "off",
"react/jsx-props-no-spreading": "off",
"import/no-extraneous-dependencies": [
"error",
{
"devDependencies": [
"**/*.test.[jt]s",
"**/*.spec.[jt]s",
"**/*.test.[jt]sx",
"**/*.spec.[jt]sx"
]
}
],
"import/extensions": [
"error",
"ignorePackages",
{
"ts": "never",
"tsx": "never",
"js": "never",
"jsx": "never"
}
]
},
"overrides": [
{
"files": "**/*.+(ts|tsx)",
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.json"
},
"plugins": ["@typescript-eslint/eslint-plugin"],
"extends": [
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"prettier"
],
"rules": {
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-var-requires": "off",
"no-use-before-define": [0],
"@typescript-eslint/no-use-before-define": [1],
"import/no-unresolved": 0,
"import/no-extraneous-dependencies": [
"error",
{
"devDependencies": [
"**/*.test.ts",
"**/*.spec.ts",
"**/*.test.tsx",
"**/*.spec.tsx"
]
}
],
"quotes": "off",
"@typescript-eslint/quotes": [
2,
"single",
{
"avoidEscape": true
}
],
"@typescript-eslint/no-unused-vars": [2, { "argsIgnorePattern": "^_" }]
}
}
],
"settings": {
"import/resolver": {
"root-import": {
"rootPathPrefix": "@",
"rootPathSuffix": "src",
"extensions": [".js", ".ts", ".tsx", ".jsx", ".mdx"]
}
}
}
}
+8
View File
@@ -0,0 +1,8 @@
{
"extends": [
"@nymproject/eslint-config-react-typescript"
],
"parserOptions": {
"project": "./tsconfig.eslint.json"
}
}
+1 -2
View File
@@ -2,6 +2,5 @@
"trailingComma": "all",
"singleQuote": true,
"printWidth": 120,
"tabWidth": 2,
"semi": false
"tabWidth": 2
}
+45 -4
View File
@@ -1,5 +1,5 @@
{
"name": "tauri-app",
"name": "@nymproject/nym-wallet-app",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
@@ -8,8 +8,11 @@
"webpack:prod": "yarn webpack --progress --config webpack.prod.js",
"tauri:dev": "yarn tauri dev",
"tauri:build": "yarn tauri build",
"tsc": "tsc --noEmit true",
"dev": "yarn run webpack:dev & yarn run tauri:dev",
"build": "run-s webpack:prod tauri:build"
"build": "run-s webpack:prod tauri:build",
"lint": "eslint src",
"lint:fix": "eslint src --fix"
},
"dependencies": {
"@babel/preset-typescript": "^7.15.0",
@@ -19,7 +22,9 @@
"@mui/icons-material": "^5.2.0",
"@mui/material": "^5.2.2",
"@mui/styles": "^5.2.2",
"@types/react-dom": "^17.0.9",
"@nymproject/mui-theme": "^1.0.0",
"@nymproject/react": "^1.0.0",
"@tauri-apps/api": "^1.0.0-rc.1",
"bs58": "^4.0.1",
"clsx": "^1.1.1",
"date-fns": "^2.28.0",
@@ -38,26 +43,62 @@
"@babel/plugin-transform-async-to-generator": "^7.14.5",
"@babel/preset-env": "^7.15.0",
"@babel/preset-react": "^7.14.5",
"@nymproject/eslint-config-react-typescript": "^1.0.0",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.4",
"@svgr/webpack": "^6.1.1",
"@tauri-apps/api": "^1.0.0-rc.1",
"@tauri-apps/cli": "^1.0.0-rc.5",
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^12.0.0",
"@types/bs58": "^4.0.1",
"@types/jest": "^27.0.1",
"@types/node": "^16.7.13",
"@types/qrcode.react": "^1.0.2",
"@types/react": "^17.0.34",
"@types/react-dom": "^17.0.9",
"@types/react-router": "^5.1.18",
"@types/react-router-dom": "^5.1.8",
"@types/semver": "^7.3.8",
"@typescript-eslint/eslint-plugin": "^5.13.0",
"@typescript-eslint/parser": "^5.13.0",
"babel-loader": "^8.2.2",
"babel-plugin-root-import": "^6.6.0",
"clean-webpack-plugin": "^4.0.0",
"css-loader": "^6.2.0",
"css-minimizer-webpack-plugin": "^3.0.2",
"dotenv-webpack": "^7.0.3",
"eslint": "^8.10.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-airbnb-typescript": "^16.1.0",
"eslint-config-prettier": "^8.5.0",
"eslint-import-resolver-root-import": "^1.0.4",
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-jest": "^26.1.1",
"eslint-plugin-jsx-a11y": "^6.5.1",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-react": "^7.29.2",
"eslint-plugin-react-hooks": "^4.3.0",
"eslint-plugin-storybook": "^0.5.7",
"favicons": "^6.2.2",
"favicons-webpack-plugin": "^5.0.2",
"file-loader": "^6.2.0",
"fork-ts-checker-webpack-plugin": "^7.2.1",
"html-webpack-plugin": "^5.3.2",
"jest": "^27.1.0",
"mini-css-extract-plugin": "^2.2.2",
"npm-run-all": "^4.1.5",
"prettier": "2.3.2",
"react-refresh": "^0.10.0",
"react-refresh-typescript": "^2.0.2",
"style-loader": "^3.2.1",
"ts-jest": "^27.0.5",
"ts-loader": "^9.2.5",
"tsconfig-paths-webpack-plugin": "^3.5.2",
"typescript": "^4.6.2",
"url-loader": "^4.1.1",
"webpack": "^5.64.3",
"webpack-cli": "^4.8.0",
"webpack-dev-server": "^4.5.0",
"webpack-favicons": "^1.3.8",
"webpack-merge": "^5.8.0"
}
}
+9 -9
View File
@@ -1,12 +1,12 @@
import React, { useContext } from 'react'
import { AppBar as MuiAppBar, Grid, IconButton, Toolbar, Typography } from '@mui/material'
import { Logout } from '@mui/icons-material'
import { ClientContext } from '../context/main'
import { NetworkSelector } from '.'
import { Node as NodeIcon } from '../svg-icons/node'
import React, { useContext } from 'react';
import { AppBar as MuiAppBar, Grid, IconButton, Toolbar } from '@mui/material';
import { Logout } from '@mui/icons-material';
import { ClientContext } from '../context/main';
import { NetworkSelector } from './NetworkSelector';
import { Node as NodeIcon } from '../svg-icons/node';
export const AppBar = () => {
const { showSettings, logOut, handleShowSettings } = useContext(ClientContext)
const { showSettings, logOut, handleShowSettings } = useContext(ClientContext);
return (
<MuiAppBar position="sticky" sx={{ boxShadow: 'none', bgcolor: 'transparent' }}>
@@ -34,5 +34,5 @@ export const AppBar = () => {
</Grid>
</Toolbar>
</MuiAppBar>
)
}
);
};
+8 -9
View File
@@ -1,12 +1,11 @@
import React, { useContext } from 'react'
import { Typography } from '@mui/material'
import { Box } from '@mui/system'
import { ClientContext } from '../context/main'
import { CopyToClipboard } from '../components'
import { splice } from '../utils'
import React, { useContext } from 'react';
import { Box, Typography } from '@mui/material';
import { ClientContext } from '../context/main';
import { CopyToClipboard } from './CopyToClipboard';
import { splice } from '../utils';
export const ClientAddress = ({ withCopy }: { withCopy?: boolean }) => {
const { clientDetails } = useContext(ClientContext)
const { clientDetails } = useContext(ClientContext);
return (
<Box>
<Typography variant="body2" component="span" sx={{ color: 'grey.600' }}>
@@ -17,5 +16,5 @@ export const ClientAddress = ({ withCopy }: { withCopy?: boolean }) => {
</Typography>
{withCopy && <CopyToClipboard text={clientDetails?.client_address} iconButton />}
</Box>
)
}
);
};
+26 -43
View File
@@ -1,37 +1,37 @@
import React, { useEffect, useState } from 'react'
import { Button, IconButton, Tooltip } from '@mui/material'
import { Check, ContentCopy } from '@mui/icons-material'
import { clipboard } from '@tauri-apps/api'
import React, { useEffect, useState } from 'react';
import { Button, IconButton, Tooltip } from '@mui/material';
import { Check, ContentCopy } from '@mui/icons-material';
import { clipboard } from '@tauri-apps/api';
export const CopyToClipboard = ({
text = '',
light,
iconButton,
}: {
text?: string
light?: boolean
iconButton?: boolean
text?: string;
light?: boolean;
iconButton?: boolean;
}) => {
const [copied, setCopied] = useState(false)
const [copied, setCopied] = useState(false);
const handleCopy = async (text: string) => {
const handleCopy = async (_text: string) => {
try {
await clipboard.writeText(text)
setCopied(true)
await clipboard.writeText(_text);
setCopied(true);
} catch (e) {
console.log('failed to copy: ' + e)
console.log(`failed to copy: ${e}`);
}
}
};
useEffect(() => {
let timer: NodeJS.Timeout
let timer: NodeJS.Timeout;
if (copied) {
timer = setTimeout(() => {
setCopied(false)
}, 2000)
setCopied(false);
}, 2000);
}
return () => clearTimeout(timer)
}, [copied])
return () => clearTimeout(timer);
}, [copied]);
if (iconButton)
return (
@@ -40,43 +40,26 @@ export const CopyToClipboard = ({
onClick={() => handleCopy(text)}
size="small"
sx={{
color: (theme) =>
light
? theme.palette.common.white
: theme.palette.nym.background.dark,
color: (theme) => (light ? theme.palette.common.white : theme.palette.nym.background.dark),
}}
>
{!copied ? (
<ContentCopy fontSize="small" />
) : (
<Check color="success" />
)}
{!copied ? <ContentCopy fontSize="small" /> : <Check color="success" />}
</IconButton>
</Tooltip>
)
);
return (
<Button
variant="outlined"
color="inherit"
sx={{
color: (theme) =>
light
? theme.palette.common.white
: theme.palette.nym.background.dark,
borderColor: (theme) =>
light
? theme.palette.common.white
: theme.palette.nym.background.dark,
color: (theme) => (light ? theme.palette.common.white : theme.palette.nym.background.dark),
borderColor: (theme) => (light ? theme.palette.common.white : theme.palette.nym.background.dark),
}}
onClick={() => handleCopy(text)}
endIcon={
copied && (
<Check sx={{ color: (theme) => theme.palette.success.light }} />
)
}
endIcon={copied && <Check sx={{ color: (theme) => theme.palette.success.light }} />}
>
{!copied ? 'Copy' : 'Copied'}
</Button>
)
}
);
};
+16 -18
View File
@@ -1,19 +1,17 @@
import React from 'react'
import { FallbackProps } from 'react-error-boundary'
import { Alert, AlertTitle, Button } from '@mui/material'
import React from 'react';
import { FallbackProps } from 'react-error-boundary';
import { Alert, AlertTitle, Button } from '@mui/material';
export const ErrorFallback = ({ error, resetErrorBoundary }: FallbackProps) => {
return (
<div>
<Alert severity="error" data-testid="error-message">
<AlertTitle>{error.name}</AlertTitle>
{error.message}
</Alert>
<Alert severity="error" data-testid="stack-trace">
<AlertTitle>Stack trace</AlertTitle>
{error.stack}
</Alert>
<Button onClick={resetErrorBoundary}>Back to safety</Button>
</div>
)
}
export const ErrorFallback = ({ error, resetErrorBoundary }: FallbackProps) => (
<div>
<Alert severity="error" data-testid="error-message">
<AlertTitle>{error.name}</AlertTitle>
{error.message}
</Alert>
<Alert severity="error" data-testid="stack-trace">
<AlertTitle>Stack trace</AlertTitle>
{error.stack}
</Alert>
<Button onClick={resetErrorBoundary}>Back to safety</Button>
</div>
);
+15 -15
View File
@@ -1,29 +1,29 @@
import React, { useState, useEffect, useContext } from 'react'
import { Typography } from '@mui/material'
import { Operation } from '../types'
import { getGasFee } from '../requests'
import { ClientContext } from '../context/main'
import React, { useState, useEffect, useContext } from 'react';
import { Typography } from '@mui/material';
import { Operation } from '../types';
import { getGasFee } from '../requests';
import { ClientContext } from '../context/main';
export const Fee = ({ feeType }: { feeType: Operation }) => {
const [fee, setFee] = useState<string>()
const {currency} = useContext(ClientContext)
const [fee, setFee] = useState<string>();
const { currency } = useContext(ClientContext);
const getFee = async () => {
const fee = await getGasFee(feeType)
setFee(fee.amount)
}
const res = await getGasFee(feeType);
setFee(res.amount);
};
useEffect(() => {
getFee()
}, [])
getFee();
}, []);
if (fee) {
return (
<Typography sx={{ color: 'nym.fee', fontWeight: 600 }}>
Fee for this transaction: {`${fee} ${currency?.major}`}{' '}
</Typography>
)
);
}
return null
}
return null;
};
+8 -8
View File
@@ -1,6 +1,6 @@
import React from 'react'
import { InfoOutlined } from '@mui/icons-material'
import { Tooltip, TooltipProps } from '@mui/material'
import React from 'react';
import { InfoOutlined } from '@mui/icons-material';
import { Tooltip, TooltipProps } from '@mui/material';
export const InfoTooltip = ({
title,
@@ -8,12 +8,12 @@ export const InfoTooltip = ({
light,
size = 'small',
}: {
title: string
tooltipPlacement?: TooltipProps['placement']
light?: boolean
size?: 'small' | 'medium' | 'large'
title: string;
tooltipPlacement?: TooltipProps['placement'];
light?: boolean;
size?: 'small' | 'medium' | 'large';
}) => (
<Tooltip title={title} arrow placement={tooltipPlacement}>
<InfoOutlined fontSize={size} sx={{ color: light ? 'grey.500' : undefined }} />
</Tooltip>
)
);
+16 -16
View File
@@ -1,11 +1,11 @@
import React, { useContext, useEffect } from 'react'
import { Link, useLocation } from 'react-router-dom'
import { List, ListItem, ListItemIcon, ListItemText } from '@mui/material'
import { AccountBalanceWalletOutlined, ArrowBack, ArrowForward, Description, Settings } from '@mui/icons-material'
import { ADMIN_ADDRESS, ClientContext } from '../context/main'
import { Bond, Delegate, Unbond, Undelegate } from '../svg-icons'
import React, { useContext, useEffect } from 'react';
import { Link, useLocation } from 'react-router-dom';
import { List, ListItem, ListItemIcon, ListItemText } from '@mui/material';
import { AccountBalanceWalletOutlined, ArrowBack, ArrowForward, Description, Settings } from '@mui/icons-material';
import { ADMIN_ADDRESS, ClientContext } from '../context/main';
import { Bond, Delegate, Unbond, Undelegate } from '../svg-icons';
let routesSchema = [
const routesSchema = [
{
label: 'Balance',
route: '/balance',
@@ -41,11 +41,11 @@ let routesSchema = [
route: '/undelegate',
Icon: Undelegate,
},
]
];
export const Nav = () => {
const { clientDetails, handleShowAdmin } = useContext(ClientContext)
const location = useLocation()
const { clientDetails, handleShowAdmin } = useContext(ClientContext);
const location = useLocation();
useEffect(() => {
if (clientDetails?.client_address === ADMIN_ADDRESS) {
@@ -53,9 +53,9 @@ export const Nav = () => {
label: 'Docs',
route: '/docs',
Icon: Description,
})
});
}
}, [])
}, []);
return (
<div
@@ -66,8 +66,8 @@ export const Nav = () => {
}}
>
<List disablePadding>
{routesSchema.map(({ Icon, route, label }, i) => (
<ListItem disableGutters component={Link} to={route} key={i}>
{routesSchema.map(({ Icon, route, label }) => (
<ListItem disableGutters component={Link} to={route} key={label}>
<ListItemIcon
sx={{
minWidth: 30,
@@ -98,5 +98,5 @@ export const Nav = () => {
)}
</List>
</div>
)
}
);
};
+29 -29
View File
@@ -1,27 +1,38 @@
import React, { useState, useContext } from 'react'
import { Button, List, ListItem, ListItemIcon, ListItemText, ListSubheader, Popover } from '@mui/material'
import { ArrowDropDown, CheckSharp } from '@mui/icons-material'
import { ClientContext, IS_DEV_MODE } from '../context/main'
import { Network } from '../types'
import React, { useState, useContext } from 'react';
import { Button, List, ListItem, ListItemIcon, ListItemText, ListSubheader, Popover } from '@mui/material';
import { ArrowDropDown, CheckSharp } from '@mui/icons-material';
import { ClientContext, IS_DEV_MODE } from '../context/main';
import { Network } from '../types';
const networks: { networkName: Network; name: string }[] = [
{ networkName: 'MAINNET', name: 'Nym Mainnet' },
{ networkName: 'SANDBOX', name: 'Testnet Sandbox' },
{ networkName: 'QA', name: 'QA' },
]
];
export function NetworkSelector() {
const { network, switchNetwork } = useContext(ClientContext)
const NetworkItem: React.FC<{ title: string; isSelected: boolean; onSelect: () => void }> = ({
title,
isSelected,
onSelect,
}) => (
<ListItem button onClick={onSelect}>
<ListItemIcon>{isSelected && <CheckSharp color="success" />}</ListItemIcon>
<ListItemText>{title}</ListItemText>
</ListItem>
);
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null)
export const NetworkSelector = () => {
const { network, switchNetwork } = useContext(ClientContext);
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget)
}
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null)
}
setAnchorEl(null);
};
return (
<>
@@ -47,31 +58,20 @@ export function NetworkSelector() {
<List>
<ListSubheader>Network selection</ListSubheader>
{networks
.filter((network) => !(!IS_DEV_MODE && network.networkName === 'QA'))
.filter(({ networkName }) => !(!IS_DEV_MODE && networkName === 'QA'))
.map(({ name, networkName }) => (
<NetworkItem
key={networkName}
title={name}
isSelected={networkName === network}
onSelect={() => {
handleClose()
switchNetwork(networkName)
handleClose();
switchNetwork(networkName);
}}
/>
))}
</List>
</Popover>
</>
)
}
const NetworkItem: React.FC<{ title: string; isSelected: boolean; onSelect: () => void }> = ({
title,
isSelected,
onSelect,
}) => (
<ListItem button onClick={onSelect}>
<ListItemIcon>{isSelected && <CheckSharp color="success" />}</ListItemIcon>
<ListItemText>{title}</ListItemText>
</ListItem>
)
);
};
+9 -12
View File
@@ -1,13 +1,10 @@
import React from 'react'
import { Link } from 'react-router-dom'
import { Alert, AlertTitle } from '@mui/material'
import React from 'react';
import { Link } from 'react-router-dom';
import { Alert, AlertTitle } from '@mui/material';
export const NoClientError = () => {
return (
<Alert severity="error">
<AlertTitle data-testid="client-error">No client detected</AlertTitle>
Have you signed in? Try to go back to{' '}
<Link to="/signin">the main page</Link> and try again
</Alert>
)
}
export const NoClientError = () => (
<Alert severity="error">
<AlertTitle data-testid="client-error">No client detected</AlertTitle>
Have you signed in? Try to go back to <Link to="/signin">the main page</Link> and try again
</Alert>
);
+21 -21
View File
@@ -1,36 +1,36 @@
import React from 'react'
import { Typography } from '@mui/material'
import { CircleOutlined, PauseCircleOutlined, CheckCircleOutline } from '@mui/icons-material'
import { MixnodeStatus } from '../types'
export const NodeStatus = ({ status }: { status: MixnodeStatus }) => {
switch (status) {
case 'active':
return <Active />
case 'inactive':
return <Inactive />
case 'standby':
return <Standby />
default:
null
}
return null
}
import React from 'react';
import { Typography } from '@mui/material';
import { CircleOutlined, PauseCircleOutlined, CheckCircleOutline } from '@mui/icons-material';
import { MixnodeStatus } from '../types';
const Active = () => (
<Typography sx={{ color: 'success.main', display: 'flex', alignItems: 'center' }}>
<CheckCircleOutline fontSize="small" color="success" sx={{ mr: 1 }} /> Active
</Typography>
)
);
const Inactive = () => (
<Typography sx={{ color: 'nym.text.dark', display: 'flex', alignItems: 'center' }}>
<CircleOutlined fontSize="small" sx={{ color: 'nym.text.dark', mr: 1 }} /> Inactive
</Typography>
)
);
const Standby = () => (
<Typography sx={{ color: 'info.main', display: 'flex', alignItems: 'center' }}>
<PauseCircleOutlined fontSize="small" color="info" sx={{ mr: 1 }} /> Standby
</Typography>
)
);
export const NodeStatus = ({ status }: { status: MixnodeStatus }) => {
switch (status) {
case 'active':
return <Active />;
case 'inactive':
return <Inactive />;
case 'standby':
return <Standby />;
default:
return null;
}
return null;
};
@@ -1,17 +1,17 @@
import React from 'react'
import { FormControl, FormControlLabel, FormLabel, Radio, RadioGroup } from '@mui/material'
import { EnumNodeType } from '../types/global'
import React from 'react';
import { FormControl, FormControlLabel, FormLabel, Radio, RadioGroup } from '@mui/material';
import { EnumNodeType } from '../types/global';
export const NodeTypeSelector = ({
disabled,
nodeType,
setNodeType,
}: {
disabled: boolean
nodeType: EnumNodeType
setNodeType: (nodeType: EnumNodeType) => void
disabled: boolean;
nodeType: EnumNodeType;
setNodeType: (nodeType: EnumNodeType) => void;
}) => {
const handleNodeTypeChange = (e: React.ChangeEvent<HTMLInputElement>) => setNodeType(e.target.value as EnumNodeType)
const handleNodeTypeChange = (e: React.ChangeEvent<HTMLInputElement>) => setNodeType(e.target.value as EnumNodeType);
return (
<FormControl component="fieldset">
@@ -39,5 +39,5 @@ export const NodeTypeSelector = ({
/>
</RadioGroup>
</FormControl>
)
}
);
};
+30 -32
View File
@@ -1,37 +1,35 @@
import React from 'react'
import { Box, Card, CardContent, CardHeader } from '@mui/material'
import { styled } from '@mui/material/styles'
import { Title } from './Title'
import React from 'react';
import { Box, Card, CardContent, CardHeader } from '@mui/material';
import { styled } from '@mui/material/styles';
import { Title } from './Title';
export const NymCard: React.FC<{
title: string | React.ReactElement
subheader?: string
Action?: React.ReactNode
Icon?: any
noPadding?: boolean
}> = ({ title, subheader, Action, Icon, noPadding, children }) => {
return (
<Card variant="outlined" sx={{ overflow: 'auto' }}>
<CardHeader
sx={{ p: 3, color: 'nym.background.dark' }}
title={<Title title={title} Icon={Icon} />}
subheader={subheader}
data-testid={title}
subheaderTypographyProps={{ variant: 'subtitle1' }}
action={<Box sx={{ mt: 1, mr: 1 }}>{Action}</Box>}
/>
{noPadding ? (
<CardContentNoPadding>{children}</CardContentNoPadding>
) : (
<CardContent sx={{ p: 3 }}>{children}</CardContent>
)}
</Card>
)
}
const CardContentNoPadding = styled(CardContent)(({ theme }) => ({
const CardContentNoPadding = styled(CardContent)(() => ({
padding: 0,
'&:last-child': {
paddingBottom: 0,
},
}))
}));
export const NymCard: React.FC<{
title: string | React.ReactElement;
subheader?: string;
Action?: React.ReactNode;
Icon?: any;
noPadding?: boolean;
}> = ({ title, subheader, Action, Icon, noPadding, children }) => (
<Card variant="outlined" sx={{ overflow: 'auto' }}>
<CardHeader
sx={{ p: 3, color: 'nym.background.dark' }}
title={<Title title={title} Icon={Icon} />}
subheader={subheader}
data-testid={title}
subheaderTypographyProps={{ variant: 'subtitle1' }}
action={<Box sx={{ mt: 1, mr: 1 }}>{Action}</Box>}
/>
{noPadding ? (
<CardContentNoPadding>{children}</CardContentNoPadding>
) : (
<CardContent sx={{ p: 3 }}>{children}</CardContent>
)}
</Card>
);
+7 -7
View File
@@ -1,10 +1,10 @@
import React from 'react'
import Logo from '../images/logo-background.svg'
import React from 'react';
import Logo from '../images/logo-background.svg';
const imgSize = {
['small']: 40,
['medium']: 80,
['large']: 120,
}
small: 40,
medium: 80,
large: 120,
};
export const NymLogo = ({ size = 'medium' }: { size?: 'small' | 'medium' | 'large' }) => <Logo width={imgSize[size]} />
export const NymLogo = ({ size = 'medium' }: { size?: 'small' | 'medium' | 'large' }) => <Logo width={imgSize[size]} />;
+16 -18
View File
@@ -1,5 +1,5 @@
import React from 'react'
import { CircularProgress, Box } from '@mui/material'
import React from 'react';
import { CircularProgress, Box } from '@mui/material';
export enum EnumRequestStatus {
initial = 'initial',
@@ -13,19 +13,17 @@ export const RequestStatus = ({
Success,
Error,
}: {
status: EnumRequestStatus
Success: React.ReactNode
Error: React.ReactNode
}) => {
return (
<Box sx={{ padding: [3, 5] }}>
{status === EnumRequestStatus.loading && (
<Box sx={{ display: 'flex', justifyContent: 'center' }}>
<CircularProgress size={48} />
</Box>
)}
{status === EnumRequestStatus.success && Success}
{status === EnumRequestStatus.error && Error}
</Box>
)
}
status: EnumRequestStatus;
Success: React.ReactNode;
Error: React.ReactNode;
}) => (
<Box sx={{ padding: [3, 5] }}>
{status === EnumRequestStatus.loading && (
<Box sx={{ display: 'flex', justifyContent: 'center' }}>
<CircularProgress size={48} />
</Box>
)}
{status === EnumRequestStatus.success && Success}
{status === EnumRequestStatus.error && Error}
</Box>
);
+14 -16
View File
@@ -1,18 +1,16 @@
import React from 'react'
import { Stack, Typography } from '@mui/material'
import React from 'react';
import { Stack, Typography } from '@mui/material';
export const SuccessReponse: React.FC<{
title: string
subtitle: string | React.ReactNode
caption: string | React.ReactNode
}> = ({ title, subtitle, caption }) => {
return (
<Stack spacing={3} alignItems="center" sx={{ mb: 5 }}>
<Typography variant="h5" fontWeight="600" data-testid="transaction-complete" color="success.main">
{title}
</Typography>
<Typography fontWeight="600">{subtitle}</Typography>
<Typography>{caption}</Typography>
</Stack>
)
}
title: string;
subtitle: string | React.ReactNode;
caption: string | React.ReactNode;
}> = ({ title, subtitle, caption }) => (
<Stack spacing={3} alignItems="center" sx={{ mb: 5 }}>
<Typography variant="h5" fontWeight="600" data-testid="transaction-complete" color="success.main">
{title}
</Typography>
<Typography fontWeight="600">{subtitle}</Typography>
<Typography>{caption}</Typography>
</Stack>
);
+3 -3
View File
@@ -1,5 +1,5 @@
import React from 'react'
import { Box, Typography } from '@mui/material'
import React from 'react';
import { Box, Typography } from '@mui/material';
export const Title: React.FC<{ title: string | React.ReactNode; Icon: any }> = ({ title, Icon }) => (
<Box display="flex" alignItems="center">
@@ -8,4 +8,4 @@ export const Title: React.FC<{ title: string | React.ReactNode; Icon: any }> = (
{title}
</Typography>
</Box>
)
);
+12 -14
View File
@@ -1,18 +1,16 @@
import { ListItem, ListItemText, Select } from '@mui/material'
import React, { useState } from 'react'
import { ListItem, ListItemText, Select } from '@mui/material';
import React, { useState } from 'react';
type TPool = 'balance' | 'locked'
type TPool = 'balance' | 'locked';
export const TokenPoolSelector: React.FC<{ onSelect: (pool: TPool) => void }> = ({ onSelect }) => {
const [value, setValue] = useState<TPool>()
export const TokenPoolSelector: React.FC = () => {
const [value] = useState<TPool>();
return (
<>
<Select label="Token Pool" value={value}>
<ListItem>
<ListItemText primary="Balance" secondary="123 nymt" />
</ListItem>
</Select>
</>
)
}
<Select label="Token Pool" value={value}>
<ListItem>
<ListItemText primary="Balance" secondary="123 nymt" />
</ListItem>
</Select>
);
};
@@ -1,23 +1,19 @@
import React from 'react'
import { Card, Grid, Typography } from '@mui/material'
import React from 'react';
import { Card, Grid, Typography } from '@mui/material';
export type TTransactionDetails = Array<{ primary: string; secondary: string }>
export type TTransactionDetails = Array<{ primary: string; secondary: string }>;
export const TransactionDetails: React.FC<{ details: TTransactionDetails }> = ({ details }) => {
return (
<Card variant="outlined" sx={{ width: '100%', p: 2 }}>
{details.map(({ primary, secondary }, i) => {
return (
<Grid container sx={{ mt: i !== 0 ? 1 : 0 }} key={i}>
<Grid item sm={4} md={3} lg={2}>
<Typography sx={{ color: (theme) => theme.palette.grey[600] }}>{primary}</Typography>
</Grid>
<Grid item>
<Typography data-testid="to-address">{secondary}</Typography>
</Grid>
</Grid>
)
})}
</Card>
)
}
export const TransactionDetails: React.FC<{ details: TTransactionDetails }> = ({ details }) => (
<Card variant="outlined" sx={{ width: '100%', p: 2 }}>
{details.map(({ primary, secondary }, i) => (
<Grid container sx={{ mt: i !== 0 ? 1 : 0 }} key={primary}>
<Grid item sm={4} md={3} lg={2}>
<Typography sx={{ color: (theme) => theme.palette.grey[600] }}>{primary}</Typography>
</Grid>
<Grid item>
<Typography data-testid="to-address">{secondary}</Typography>
</Grid>
</Grid>
))}
</Card>
);
+16 -17
View File
@@ -1,17 +1,16 @@
export * from './Error'
export * from './CopyToClipboard'
export * from './NymCard'
export * from './Nav'
export * from './NodeTypeSelector'
export * from './RequestStatus'
export * from './NoClientError'
export * from './SuccessResponse'
export * from './TransactionDetails'
export * from './NymLogo'
export * from './Fee'
export * from './AppBar'
export * from './NetworkSelector'
export * from './ClientAddress'
export * from './InfoToolTip'
export * from './Nav'
export * from './Title'
export * from './Error';
export * from './CopyToClipboard';
export * from './NymCard';
export * from './Nav';
export * from './NodeTypeSelector';
export * from './RequestStatus';
export * from './NoClientError';
export * from './SuccessResponse';
export * from './TransactionDetails';
export * from './NymLogo';
export * from './Fee';
export * from './AppBar';
export * from './NetworkSelector';
export * from './ClientAddress';
export * from './InfoToolTip';
export * from './Title';
+110 -111
View File
@@ -1,139 +1,138 @@
import React, { createContext, useEffect, useState } from 'react'
import { Account, Network, TCurrency, TMixnodeBondDetails } from '../types'
import { TUseuserBalance, useGetBalance } from '../hooks/useGetBalance'
import { config } from '../../config'
import { getMixnodeBondDetails, selectNetwork, signInWithMnemonic, signOut } from '../requests'
import { currencyMap } from '../utils'
import { useHistory } from 'react-router-dom'
import React, { createContext, useEffect, useMemo, useState } from 'react';
import { useHistory } from 'react-router-dom';
import { Account, Network, TCurrency, TMixnodeBondDetails } from '../types';
import { TUseuserBalance, useGetBalance } from '../hooks/useGetBalance';
import { config } from '../../config';
import { getMixnodeBondDetails, selectNetwork, signInWithMnemonic, signOut } from '../requests';
import { currencyMap } from '../utils';
export const { ADMIN_ADDRESS, IS_DEV_MODE } = config
export const { ADMIN_ADDRESS, IS_DEV_MODE } = config;
export const urls = (network?: Network) =>
network === 'MAINNET'
export const urls = (networkName?: Network) =>
networkName === 'MAINNET'
? {
blockExplorer: 'https://blocks.nymtech.net',
networkExplorer: 'https://explorer.nymtech.net',
}
: {
blockExplorer: `https://${network}-blocks.nymtech.net`,
networkExplorer: `https://${network}-explorer.nymtech.net`,
}
blockExplorer: `https://${networkName}-blocks.nymtech.net`,
networkExplorer: `https://${networkName}-explorer.nymtech.net`,
};
type TClientContext = {
mode: 'light' | 'dark'
clientDetails?: Account
mixnodeDetails?: TMixnodeBondDetails | null
userBalance: TUseuserBalance
showAdmin: boolean
showSettings: boolean
network?: Network
currency?: TCurrency
isLoading: boolean
error?: string
switchNetwork: (network: Network) => void
getBondDetails: () => Promise<void>
handleShowSettings: () => void
handleShowAdmin: () => void
logIn: (mnemonic: string) => void
logOut: () => void
}
mode: 'light' | 'dark';
clientDetails?: Account;
mixnodeDetails?: TMixnodeBondDetails | null;
userBalance: TUseuserBalance;
showAdmin: boolean;
showSettings: boolean;
network?: Network;
currency?: TCurrency;
isLoading: boolean;
error?: string;
switchNetwork: (network: Network) => void;
getBondDetails: () => Promise<void>;
handleShowSettings: () => void;
handleShowAdmin: () => void;
logIn: (mnemonic: string) => void;
logOut: () => void;
};
export const ClientContext = createContext({} as TClientContext)
export const ClientContext = createContext({} as TClientContext);
export const ClientContextProvider = ({ children }: { children: React.ReactNode }) => {
const [clientDetails, setClientDetails] = useState<Account>()
const [mixnodeDetails, setMixnodeDetails] = useState<TMixnodeBondDetails | null>()
const [network, setNetwork] = useState<Network | undefined>()
const [currency, setCurrency] = useState<TCurrency>()
const [showAdmin, setShowAdmin] = useState(false)
const [showSettings, setShowSettings] = useState(false)
const [mode, setMode] = useState<'light' | 'dark'>('light')
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState<string>()
const [clientDetails, setClientDetails] = useState<Account>();
const [mixnodeDetails, setMixnodeDetails] = useState<TMixnodeBondDetails | null>();
const [network, setNetwork] = useState<Network | undefined>();
const [currency, setCurrency] = useState<TCurrency>();
const [showAdmin, setShowAdmin] = useState(false);
const [showSettings, setShowSettings] = useState(false);
const [mode] = useState<'light' | 'dark'>('light');
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string>();
const userBalance = useGetBalance(clientDetails?.client_address)
const history = useHistory()
const userBalance = useGetBalance(clientDetails?.client_address);
const history = useHistory();
const loadAccount = async (n: Network) => {
try {
const client = await selectNetwork(n);
setClientDetails(client);
} catch (e) {
console.error(e);
} finally {
setCurrency(currencyMap(n));
}
};
const getBondDetails = async () => {
setMixnodeDetails(undefined);
try {
const mixnode = await getMixnodeBondDetails();
setMixnodeDetails(mixnode);
} catch (e) {
console.log(e);
}
};
useEffect(() => {
const refreshAccount = async () => {
if (network) {
await loadAccount(network)
await getBondDetails()
userBalance.fetchBalance()
await loadAccount(network);
await getBondDetails();
userBalance.fetchBalance();
}
}
refreshAccount()
}, [network])
};
refreshAccount();
}, [network]);
const logIn = async (mnemonic: string) => {
try {
setIsLoading(true)
await signInWithMnemonic(mnemonic || '')
await getBondDetails()
setNetwork('MAINNET')
history.push('/balance')
setIsLoading(true);
await signInWithMnemonic(mnemonic || '');
await getBondDetails();
setNetwork('MAINNET');
history.push('/balance');
} catch (e) {
setIsLoading(false)
setError(e as string)
setIsLoading(false);
setError(e as string);
}
}
const loadAccount = async (network: Network) => {
try {
const clientDetails = await selectNetwork(network)
setClientDetails(clientDetails)
} catch (e) {
} finally {
setCurrency(currencyMap(network))
}
}
};
const logOut = async () => {
setClientDetails(undefined)
setNetwork(undefined)
setError(undefined)
setIsLoading(false)
userBalance.clearAll()
await signOut()
}
setClientDetails(undefined);
setNetwork(undefined);
setError(undefined);
setIsLoading(false);
userBalance.clearAll();
await signOut();
};
const handleShowAdmin = () => setShowAdmin((show) => !show)
const handleShowSettings = () => setShowSettings((show) => !show)
const handleShowAdmin = () => setShowAdmin((show) => !show);
const handleShowSettings = () => setShowSettings((show) => !show);
const switchNetwork = (_network: Network) => setNetwork(_network);
const getBondDetails = async () => {
setMixnodeDetails(undefined)
try {
const mixnodeDetails = await getMixnodeBondDetails()
setMixnodeDetails(mixnodeDetails)
} catch (e) {
console.log(e)
}
}
const memoizedValue = useMemo(
() => ({
mode,
isLoading,
error,
clientDetails,
mixnodeDetails,
userBalance,
showAdmin,
showSettings,
network,
currency,
switchNetwork,
getBondDetails,
handleShowSettings,
handleShowAdmin,
logIn,
logOut,
}),
[mode, isLoading, error, clientDetails, mixnodeDetails, userBalance, showAdmin, showSettings, network, currency],
);
const switchNetwork = (network: Network) => setNetwork(network)
return (
<ClientContext.Provider
value={{
mode,
isLoading,
error,
clientDetails,
mixnodeDetails,
userBalance,
showAdmin,
showSettings,
network,
currency,
switchNetwork,
getBondDetails,
handleShowSettings,
handleShowAdmin,
logIn,
logOut,
}}
>
{children}
</ClientContext.Provider>
)
}
return <ClientContext.Provider value={memoizedValue}>{children}</ClientContext.Provider>;
};
+26 -26
View File
@@ -1,49 +1,49 @@
import { useCallback, useContext, useEffect, useState } from 'react'
import { ClientContext } from '../context/main'
import { checkGatewayOwnership, checkMixnodeOwnership } from '../requests'
import { EnumNodeType, TNodeOwnership } from '../types'
import { useCallback, useContext, useEffect, useState } from 'react';
import { ClientContext } from '../context/main';
import { checkGatewayOwnership, checkMixnodeOwnership } from '../requests';
import { EnumNodeType, TNodeOwnership } from '../types';
const initial = {
hasOwnership: false,
nodeType: undefined,
}
};
export const useCheckOwnership = () => {
const { clientDetails } = useContext(ClientContext)
const [ownership, setOwnership] = useState<TNodeOwnership>(initial)
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState<string>()
const { clientDetails } = useContext(ClientContext);
const [ownership, setOwnership] = useState<TNodeOwnership>(initial);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string>();
const checkOwnership = useCallback(async () => {
const status = {} as TNodeOwnership
const status = {} as TNodeOwnership;
setIsLoading(true)
setIsLoading(true);
try {
const ownsMixnode = await checkMixnodeOwnership()
const ownsGateway = await checkGatewayOwnership()
const ownsMixnode = await checkMixnodeOwnership();
const ownsGateway = await checkGatewayOwnership();
if (ownsMixnode) {
status.hasOwnership = true
status.nodeType = EnumNodeType.mixnode
status.hasOwnership = true;
status.nodeType = EnumNodeType.mixnode;
}
if (ownsGateway) {
status.hasOwnership = true
status.nodeType = EnumNodeType.gateway
status.hasOwnership = true;
status.nodeType = EnumNodeType.gateway;
}
setOwnership(status)
setOwnership(status);
} catch (e) {
setError(e as string)
setIsLoading(false)
setOwnership(initial)
setError(e as string);
setIsLoading(false);
setOwnership(initial);
}
}, [])
}, []);
useEffect(() => {
checkOwnership()
}, [clientDetails])
checkOwnership();
}, [clientDetails]);
return { isLoading, error, ownership, checkOwnership }
}
return { isLoading, error, ownership, checkOwnership };
};
+73 -73
View File
@@ -1,6 +1,7 @@
import { useCallback, useEffect, useState } from 'react'
import { invoke } from '@tauri-apps/api'
import { Balance, Coin, OriginalVestingResponse, Period } from '../types'
import { useCallback, useEffect, useState } from 'react';
import { invoke } from '@tauri-apps/api';
import { VestingAccountInfo } from 'src/types/rust/vestingaccountinfo';
import { Balance, Coin, OriginalVestingResponse, Period } from '../types';
import {
getVestingCoins,
getVestedCoins,
@@ -9,53 +10,41 @@ import {
getOriginalVesting,
getCurrentVestingPeriod,
getVestingAccountInfo,
} from '../requests'
import { VestingAccountInfo } from 'src/types/rust/vestingaccountinfo'
} from '../requests';
type TTokenAllocation = {
[key in 'vesting' | 'vested' | 'locked' | 'spendable']: Coin['amount']
}
[key in 'vesting' | 'vested' | 'locked' | 'spendable']: Coin['amount'];
};
export type TUseuserBalance = {
error?: string
balance?: Balance
tokenAllocation?: TTokenAllocation
originalVesting?: OriginalVestingResponse
currentVestingPeriod?: Period
vestingAccountInfo?: VestingAccountInfo
isLoading: boolean
fetchBalance: () => void
clearBalance: () => void
clearAll: () => void
fetchTokenAllocation: () => void
}
error?: string;
balance?: Balance;
tokenAllocation?: TTokenAllocation;
originalVesting?: OriginalVestingResponse;
currentVestingPeriod?: Period;
vestingAccountInfo?: VestingAccountInfo;
isLoading: boolean;
fetchBalance: () => void;
clearBalance: () => void;
clearAll: () => void;
fetchTokenAllocation: () => void;
};
export const useGetBalance = (address?: string): TUseuserBalance => {
const [balance, setBalance] = useState<Balance>()
const [error, setError] = useState<string>()
const [tokenAllocation, setTokenAllocation] = useState<TTokenAllocation>()
const [originalVesting, setOriginalVesting] = useState<OriginalVestingResponse>()
const [currentVestingPeriod, setCurrentVestingPeriod] = useState<Period>()
const [vestingAccountInfo, setVestingAccountInfo] = useState<VestingAccountInfo>()
const [isLoading, setIsLoading] = useState(false)
const [balance, setBalance] = useState<Balance>();
const [error, setError] = useState<string>();
const [tokenAllocation, setTokenAllocation] = useState<TTokenAllocation>();
const [originalVesting, setOriginalVesting] = useState<OriginalVestingResponse>();
const [currentVestingPeriod, setCurrentVestingPeriod] = useState<Period>();
const [vestingAccountInfo, setVestingAccountInfo] = useState<VestingAccountInfo>();
const [isLoading, setIsLoading] = useState(false);
const fetchBalance = useCallback(async () => {
setIsLoading(true)
setError(undefined)
try {
const balance = await invoke('get_balance')
setBalance(balance as Balance)
} catch (error) {
setError(error as string)
} finally {
setTimeout(() => {
setIsLoading(false)
}, 1000)
}
}, [])
const clearBalance = () => setBalance(undefined);
const clearTokenAllocation = () => setTokenAllocation(undefined);
const clearOriginalVesting = () => setOriginalVesting(undefined);
const fetchTokenAllocation = async () => {
setIsLoading(true)
setIsLoading(true);
if (address) {
try {
const [
@@ -64,8 +53,8 @@ export const useGetBalance = (address?: string): TUseuserBalance => {
vestedCoins,
lockedCoins,
spendableCoins,
currentVestingPeriod,
vestingAccountInfo,
currentVestingPer,
vestingAccountDetail,
] = await Promise.all([
getOriginalVesting(address),
getVestingCoins(address),
@@ -74,47 +63,58 @@ export const useGetBalance = (address?: string): TUseuserBalance => {
getSpendableCoins(address),
getCurrentVestingPeriod(address),
getVestingAccountInfo(address),
])
setOriginalVesting(originalVestingValue)
setCurrentVestingPeriod(currentVestingPeriod)
]);
setOriginalVesting(originalVestingValue);
setCurrentVestingPeriod(currentVestingPer);
setTokenAllocation({
vesting: vestingCoins.amount,
vested: vestedCoins.amount,
locked: lockedCoins.amount,
spendable: spendableCoins.amount,
})
setVestingAccountInfo(vestingAccountInfo)
});
setVestingAccountInfo(vestingAccountDetail);
} catch (e) {
clearTokenAllocation()
clearOriginalVesting()
console.error(e)
clearTokenAllocation();
clearOriginalVesting();
console.error(e);
}
}
setIsLoading(false)
}
setIsLoading(false);
};
const clearBalance = () => setBalance(undefined)
const clearTokenAllocation = () => setTokenAllocation(undefined)
const clearOriginalVesting = () => setOriginalVesting(undefined)
const fetchBalance = useCallback(async () => {
setIsLoading(true);
setError(undefined);
try {
const bal = await invoke('get_balance');
setBalance(bal as Balance);
} catch (err) {
setError(err as string);
} finally {
setTimeout(() => {
setIsLoading(false);
}, 1000);
}
}, []);
const clearAll = () => {
clearBalance()
clearTokenAllocation()
clearOriginalVesting()
}
clearBalance();
clearTokenAllocation();
clearOriginalVesting();
};
const handleRefresh = (addr?: string) => {
if (addr) {
fetchBalance();
fetchTokenAllocation();
} else {
clearAll();
}
};
useEffect(() => {
handleRefresh(address)
}, [address])
const handleRefresh = (address?: string) => {
if (address) {
fetchBalance()
fetchTokenAllocation()
} else {
clearAll()
}
}
handleRefresh(address);
}, [address]);
return {
error,
@@ -128,5 +128,5 @@ export const useGetBalance = (address?: string): TUseuserBalance => {
clearBalance,
clearAll,
fetchTokenAllocation,
}
}
};
};
-3
View File
@@ -1,3 +0,0 @@
declare module '*.jpg'
declare module '*.png'
declare module '*.svg'
+35 -38
View File
@@ -1,23 +1,22 @@
import React, { useContext, useEffect, useLayoutEffect } from 'react'
import ReactDOM from 'react-dom'
import { ErrorBoundary } from 'react-error-boundary'
import { BrowserRouter as Router } from 'react-router-dom'
import { SnackbarProvider } from 'notistack'
import { Routes } from './routes'
import { ClientContext, ClientContextProvider } from './context/main'
import { ApplicationLayout } from './layouts'
import { Admin, Welcome } from './pages'
import { ErrorFallback } from './components'
import { NymWalletTheme, WelcomeTheme } from './theme'
import { Settings } from './pages'
import { maximizeWindow } from './utils'
import React, { useContext, useLayoutEffect } from 'react';
import ReactDOM from 'react-dom';
import { ErrorBoundary } from 'react-error-boundary';
import { BrowserRouter as Router } from 'react-router-dom';
import { SnackbarProvider } from 'notistack';
import { Routes } from './routes';
import { ClientContext, ClientContextProvider } from './context/main';
import { ApplicationLayout } from './layouts';
import { Admin, Welcome, Settings } from './pages';
import { ErrorFallback } from './components';
import { NymWalletTheme, WelcomeTheme } from './theme';
import { maximizeWindow } from './utils';
const App = () => {
const { clientDetails } = useContext(ClientContext)
const { clientDetails } = useContext(ClientContext);
useLayoutEffect(() => {
maximizeWindow()
}, [])
maximizeWindow();
}, []);
return !clientDetails ? (
<WelcomeTheme>
@@ -31,28 +30,26 @@ const App = () => {
<Routes />
</ApplicationLayout>
</NymWalletTheme>
)
}
);
};
const AppWrapper = () => {
return (
<ErrorBoundary FallbackComponent={ErrorFallback}>
<Router>
<SnackbarProvider
anchorOrigin={{
vertical: 'bottom',
horizontal: 'right',
}}
>
<ClientContextProvider>
<App />
</ClientContextProvider>
</SnackbarProvider>
</Router>
</ErrorBoundary>
)
}
const AppWrapper = () => (
<ErrorBoundary FallbackComponent={ErrorFallback}>
<Router>
<SnackbarProvider
anchorOrigin={{
vertical: 'bottom',
horizontal: 'right',
}}
>
<ClientContextProvider>
<App />
</ClientContextProvider>
</SnackbarProvider>
</Router>
</ErrorBoundary>
);
const root = document.getElementById('root')
const root = document.getElementById('root');
ReactDOM.render(<AppWrapper />, root)
ReactDOM.render(<AppWrapper />, root);
+32 -35
View File
@@ -1,43 +1,40 @@
import React from 'react'
import { Box, Container } from '@mui/material'
import Logo from '../images/logo-background.svg'
import { AppBar, Nav } from '../components'
import { PageLayout } from '.'
import React from 'react';
import { Box, Container } from '@mui/material';
import Logo from '../images/logo-background.svg';
import { AppBar, Nav } from '../components';
export const ApplicationLayout: React.FC = ({ children }) => {
return (
export const ApplicationLayout: React.FC = ({ children }) => (
<Box
sx={{
height: '100vh',
width: '100vw',
display: 'grid',
gridTemplateColumns: '240px auto',
gridTemplateRows: '100%',
overflow: 'hidden',
}}
>
<Box
sx={{
height: '100vh',
width: '100vw',
display: 'grid',
gridTemplateColumns: '240px auto',
gridTemplateRows: '100%',
overflow: 'hidden',
background: '#121726',
overflow: 'auto',
py: 4,
px: 5,
}}
display="flex"
flexDirection="column"
justifyContent="space-between"
>
<Box
sx={{
background: '#121726',
overflow: 'auto',
py: 4,
px: 5,
}}
display="flex"
flexDirection="column"
justifyContent="space-between"
>
<Box>
<Box sx={{ mb: 3 }}>
<Logo width={45} />
</Box>
<Nav />
<Box>
<Box sx={{ mb: 3 }}>
<Logo width={45} />
</Box>
<Nav />
</Box>
<Container>
<AppBar />
{children}
</Container>
</Box>
)
}
<Container>
<AppBar />
{children}
</Container>
</Box>
);
+15 -17
View File
@@ -1,19 +1,17 @@
import React from 'react'
import { Box } from '@mui/material'
import React from 'react';
import { Box } from '@mui/material';
export const PageLayout: React.FC<{ position?: 'flex-start' | 'flex-end' }> = ({ position, children }) => {
return (
<Box
sx={{
height: 'calc(100% - 65px)',
display: 'flex',
alignItems: position || 'center',
overflow: 'auto',
}}
>
<Box width="100%" margin="auto">
{children}
</Box>
export const PageLayout: React.FC<{ position?: 'flex-start' | 'flex-end' }> = ({ position, children }) => (
<Box
sx={{
height: 'calc(100% - 65px)',
display: 'flex',
alignItems: position || 'center',
overflow: 'auto',
}}
>
<Box width="100%" margin="auto">
{children}
</Box>
)
}
</Box>
);
+2 -2
View File
@@ -1,2 +1,2 @@
export * from './AppLayout'
export * from './PageLayout'
export * from './AppLayout';
export * from './PageLayout';
+63 -128
View File
@@ -1,68 +1,27 @@
import React, { useContext, useEffect, useState } from 'react'
import { useForm } from 'react-hook-form'
import { Backdrop, Box, Button, CircularProgress, FormControl, Grid, Paper, Slide, TextField } from '@mui/material'
import React, { useContext, useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { Backdrop, Box, Button, CircularProgress, FormControl, Grid, Paper, Slide, TextField } from '@mui/material';
import { ClientContext } from '../../context/main'
import { NymCard } from '../../components'
import { getContractParams, setContractParams } from '../../requests'
import { TauriContractStateParams } from '../../types'
export const Admin: React.FC = () => {
const { showAdmin, handleShowAdmin } = useContext(ClientContext)
const [isLoading, setIsLoading] = useState(false)
const [params, setParams] = useState<TauriContractStateParams>()
const onCancel = () => {
setParams(undefined)
setIsLoading(false)
handleShowAdmin()
}
useEffect(() => {
const requestContractParams = async () => {
if (showAdmin) {
setIsLoading(true)
const params = await getContractParams()
setParams(params)
setIsLoading(false)
}
}
requestContractParams()
}, [showAdmin])
return (
<Backdrop open={showAdmin} style={{ zIndex: 2, overflow: 'auto' }}>
<Slide in={showAdmin}>
<Paper style={{ margin: 'auto' }}>
<NymCard title="Admin" subheader="Contract administration" noPadding>
{isLoading && (
<Box style={{ display: 'flex', justifyContent: 'center' }}>
<CircularProgress size={48} />
</Box>
)}
{!isLoading && params && <AdminForm onCancel={onCancel} params={params} />}
</NymCard>
</Paper>
</Slide>
</Backdrop>
)
}
import { ClientContext } from '../../context/main';
import { NymCard } from '../../components';
import { getContractParams, setContractParams } from '../../requests';
import { TauriContractStateParams } from '../../types';
const AdminForm: React.FC<{
params: TauriContractStateParams
onCancel: () => void
params: TauriContractStateParams;
onCancel: () => void;
}> = ({ params, onCancel }) => {
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
} = useForm({ defaultValues: { ...params } })
} = useForm({ defaultValues: { ...params } });
const onSubmit = async (data: TauriContractStateParams) => {
await setContractParams(data)
console.log(data)
onCancel()
}
await setContractParams(data);
console.log(data);
onCancel();
};
return (
<FormControl fullWidth>
@@ -70,93 +29,28 @@ const AdminForm: React.FC<{
<Grid container spacing={3}>
<Grid item xs={12}>
<TextField
{...register('minimum_mixnode_bond')}
{...register('minimum_mixnode_pledge')}
required
variant="outlined"
id="minimum_mixnode_bond"
name="minimum_mixnode_bond"
label="Minumum mixnode bond"
fullWidth
error={!!errors.minimum_mixnode_bond}
helperText={errors?.minimum_mixnode_bond?.message}
error={!!errors.minimum_mixnode_pledge}
helperText={errors?.minimum_mixnode_pledge?.message}
/>
</Grid>
<Grid item xs={12}>
<TextField
{...register('minimum_gateway_bond')}
{...register('minimum_gateway_pledge')}
required
variant="outlined"
id="minimum_gateway_bond"
name="minimum_gateway_bond"
label="Minumum gateway bond"
fullWidth
error={!!errors.minimum_gateway_bond}
helperText={errors?.minimum_gateway_bond?.message}
/>
</Grid>
<Grid item xs={12}>
<TextField
{...register('mixnode_bond_reward_rate')}
required
variant="outlined"
id="mixnode_bond_reward_rate"
name="mixnode_bond_reward_rate"
label="Mixnode bond reward rate"
fullWidth
error={!!errors.mixnode_bond_reward_rate}
helperText={errors?.mixnode_bond_reward_rate?.message}
/>
</Grid>
<Grid item xs={12}>
<TextField
{...register('gateway_bond_reward_rate')}
required
variant="outlined"
id="gateway_bond_reward_rate"
name="gateway_bond_reward_rate"
label="Gateway bond reward rate"
fullWidth
error={!!errors.gateway_bond_reward_rate}
helperText={errors?.gateway_bond_reward_rate?.message}
/>
</Grid>
<Grid item xs={12}>
<TextField
{...register('mixnode_delegation_reward_rate')}
required
variant="outlined"
id="mixnode_delegation_reward_rate"
name="mixnode_delegation_reward_rate"
label="Mixnode Delegation Reward Rate"
fullWidth
error={!!errors.mixnode_delegation_reward_rate}
helperText={errors?.mixnode_delegation_reward_rate?.message}
/>
</Grid>
<Grid item xs={12}>
<TextField
{...register('gateway_delegation_reward_rate')}
required
variant="outlined"
id="gateway_delegation_reward_rate"
name="gateway_delegation_reward_rate"
label="Gateway Delegation Reward Rate"
fullWidth
error={!!errors.gateway_delegation_reward_rate}
helperText={errors?.gateway_delegation_reward_rate?.message}
/>
</Grid>
<Grid item xs={12}>
<TextField
{...register('epoch_length')}
required
variant="outlined"
id="epochLength"
name="epochLength"
label="Epoch length (hours)"
fullWidth
error={!!errors.epoch_length}
helperText={errors?.epoch_length?.message}
error={!!errors.minimum_gateway_pledge}
helperText={errors?.minimum_gateway_pledge?.message}
/>
</Grid>
<Grid item xs={12}>
@@ -202,5 +96,46 @@ const AdminForm: React.FC<{
</Grid>
</Grid>
</FormControl>
)
}
);
};
export const Admin: React.FC = () => {
const { showAdmin, handleShowAdmin } = useContext(ClientContext);
const [isLoading, setIsLoading] = useState(false);
const [params, setParams] = useState<TauriContractStateParams>();
const onCancel = () => {
setParams(undefined);
setIsLoading(false);
handleShowAdmin();
};
useEffect(() => {
const requestContractParams = async () => {
if (showAdmin) {
setIsLoading(true);
const prms = await getContractParams();
setParams(prms);
setIsLoading(false);
}
};
requestContractParams();
}, [showAdmin]);
return (
<Backdrop open={showAdmin} style={{ zIndex: 2, overflow: 'auto' }}>
<Slide in={showAdmin}>
<Paper style={{ margin: 'auto' }}>
<NymCard title="Admin" subheader="Contract administration" noPadding>
{isLoading && (
<Box style={{ display: 'flex', justifyContent: 'center' }}>
<CircularProgress size={48} />
</Box>
)}
{!isLoading && params && <AdminForm onCancel={onCancel} params={params} />}
</NymCard>
</Paper>
</Slide>
</Backdrop>
);
};
+10 -10
View File
@@ -1,15 +1,15 @@
import React, { useContext, useEffect } from 'react'
import { Alert, Button, Grid, Link, Typography } from '@mui/material'
import { OpenInNew } from '@mui/icons-material'
import { NymCard, ClientAddress } from '../../components'
import { ClientContext, urls } from '../../context/main'
import React, { useContext, useEffect } from 'react';
import { Alert, Button, Grid, Link, Typography } from '@mui/material';
import { OpenInNew } from '@mui/icons-material';
import { NymCard, ClientAddress } from '../../components';
import { ClientContext, urls } from '../../context/main';
export const BalanceCard = () => {
const { userBalance, clientDetails, network } = useContext(ClientContext)
const { userBalance, clientDetails, network } = useContext(ClientContext);
useEffect(() => {
userBalance.fetchBalance()
}, [])
userBalance.fetchBalance();
}, []);
return (
<NymCard title="Balance" data-testid="check-balance" Action={<ClientAddress withCopy />}>
@@ -40,5 +40,5 @@ export const BalanceCard = () => {
)}
</Grid>
</NymCard>
)
}
);
};
@@ -1,19 +1,30 @@
import React, { useContext } from 'react'
import { Box, Tooltip, Typography } from '@mui/material'
import { format } from 'date-fns'
import { ClientContext } from '../../../context/main'
/* eslint-disable react/no-array-index-key */
import React, { useContext } from 'react';
import { Box, Tooltip, Typography } from '@mui/material';
import { format } from 'date-fns';
import { ClientContext } from '../../../context/main';
const calculateMarkerPosition = (arrLength: number, index: number) => (1 / arrLength) * 100 * index
const calculateMarkerPosition = (arrLength: number, index: number) => (1 / arrLength) * 100 * index;
const Marker: React.FC<{ tooltipText: string; color: string; position: string }> = ({
tooltipText,
color,
position,
}) => (
<Tooltip title={tooltipText}>
<rect x={position} width="4" height="12" rx="1" fill={color} style={{ cursor: 'pointer' }} />
</Tooltip>
);
export const VestingTimeline: React.FC<{ percentageComplete: number }> = ({ percentageComplete }) => {
const {
userBalance: { currentVestingPeriod, vestingAccountInfo },
} = useContext(ClientContext)
} = useContext(ClientContext);
const nextPeriod =
typeof currentVestingPeriod === 'object' && !!vestingAccountInfo?.periods
? Number(vestingAccountInfo?.periods[currentVestingPeriod.In + 1]?.start_time)
: undefined
: undefined;
return (
<Box display="flex" flexDirection="column" gap={1} position="relative" width="100%">
@@ -40,15 +51,5 @@ export const VestingTimeline: React.FC<{ percentageComplete: number }> = ({ perc
</Typography>
)}
</Box>
)
}
const Marker: React.FC<{ tooltipText: string; color: string; position: string }> = ({
tooltipText,
color,
position,
}) => (
<Tooltip title={tooltipText}>
<rect x={position} width="4" height="12" rx="1" fill={color} style={{ cursor: 'pointer' }} />
</Tooltip>
)
);
};
+12 -11
View File
@@ -1,15 +1,16 @@
import React, { useContext, useEffect } from 'react'
import { Box } from '@mui/material'
import { BalanceCard } from './balance'
import { VestingCard } from './vesting'
import { ClientContext } from '../../context/main'
import { PageLayout } from '../../layouts'
import React, { useContext, useEffect } from 'react';
import { Box } from '@mui/material';
import { BalanceCard } from './balance';
import { VestingCard } from './vesting';
import { ClientContext } from '../../context/main';
import { PageLayout } from '../../layouts';
export const Balance = () => {
const { userBalance } = useContext(ClientContext)
const { userBalance } = useContext(ClientContext);
useEffect(() => {
userBalance.fetchBalance()
}, [])
userBalance.fetchBalance();
}, []);
return (
<PageLayout>
@@ -18,5 +19,5 @@ export const Balance = () => {
{userBalance.originalVesting && <VestingCard />}
</Box>
</PageLayout>
)
}
);
};
+113 -117
View File
@@ -1,120 +1,59 @@
import React, { useEffect, useContext, useState } from 'react'
import React, { useCallback, useContext, useEffect, useState } from 'react';
import {
IconButton,
Box,
Button,
CircularProgress,
LinearProgress,
Grid,
IconButton,
Table,
TableCell,
TableCellProps,
TableContainer,
TableHead,
TableRow,
Typography,
Box,
Button,
TableCellProps,
Grid,
} from '@mui/material'
import { InfoOutlined, Refresh } from '@mui/icons-material'
import { useSnackbar } from 'notistack'
import { NymCard, InfoTooltip, Title, Fee } from '../../components'
import { ClientContext } from '../../context/main'
import { withdrawVestedCoins } from '../../requests'
import { Period } from '../../types'
import { VestingTimeline } from './components/vesting-timeline'
export const VestingCard = () => {
const { userBalance } = useContext(ClientContext)
const [isLoading, setIsLoading] = useState(false)
const { enqueueSnackbar, closeSnackbar } = useSnackbar()
const refreshBalances = async () => {
await userBalance.fetchBalance()
await userBalance.fetchTokenAllocation()
}
useEffect(() => {
return () => closeSnackbar()
}, [])
return (
<NymCard
title="Vesting Schedule"
data-testid="check-unvested-tokens"
Icon={InfoOutlined}
Action={
<IconButton
onClick={async () => {
await refreshBalances()
enqueueSnackbar('Balances updated', { variant: 'success', preventDuplicate: true })
}}
>
<Refresh />
</IconButton>
}
>
<VestingSchedule />
<TokenTransfer />
<Box display="flex" justifyContent="space-between" alignItems="center">
{userBalance.tokenAllocation?.spendable !== '0' ? <Fee feeType="Send" /> : <div />}
<Button
size="large"
variant="contained"
onClick={async () => {
setIsLoading(true)
try {
await withdrawVestedCoins(userBalance.tokenAllocation?.spendable!)
await refreshBalances()
enqueueSnackbar('Token transfer succeeded', {
variant: 'success',
preventDuplicate: true,
})
} catch (e) {
console.log(e)
enqueueSnackbar('Token transfer failed. You may not have any transferable tokens at this time', {
variant: 'error',
preventDuplicate: true,
})
} finally {
setIsLoading(false)
}
}}
endIcon={isLoading && <CircularProgress size={16} color="inherit" />}
disabled={isLoading || userBalance.tokenAllocation?.spendable === '0'}
disableElevation
>
Transfer
</Button>
</Box>
</NymCard>
)
}
} from '@mui/material';
import { InfoOutlined, Refresh } from '@mui/icons-material';
import { useSnackbar } from 'notistack';
import { Fee, InfoTooltip, NymCard, Title } from '../../components';
import { ClientContext } from '../../context/main';
import { withdrawVestedCoins } from '../../requests';
import { Period } from '../../types';
import { VestingTimeline } from './components/vesting-timeline';
const columnsHeaders: Array<{ title: string; align: TableCellProps['align'] }> = [
{ title: 'Locked', align: 'left' },
{ title: 'Period', align: 'left' },
{ title: 'Percentage Vested', align: 'left' },
{ title: 'Unlocked', align: 'right' },
]
];
const vestingPeriod = (current?: Period, original?: number) => {
if (current === 'After') return 'Complete';
if (typeof current === 'object' && typeof original === 'number') return `${current.In + 1}/${original}`;
return 'N/A';
};
const VestingSchedule = () => {
const { userBalance, currency } = useContext(ClientContext)
const [vestedPercentage, setVestedPercentage] = useState(0)
const { userBalance, currency } = useContext(ClientContext);
const [vestedPercentage, setVestedPercentage] = useState(0);
const calculatePercentage = () => {
const { tokenAllocation, originalVesting } = userBalance
const { tokenAllocation, originalVesting } = userBalance;
if (tokenAllocation?.vesting && tokenAllocation.vested && tokenAllocation.vested !== '0' && originalVesting) {
const percentage = (+tokenAllocation.vested / +originalVesting?.amount.amount) * 100
const rounded = percentage.toFixed(2)
setVestedPercentage(+rounded)
const percentage = (+tokenAllocation.vested / +originalVesting.amount.amount) * 100;
const rounded = percentage.toFixed(2);
setVestedPercentage(+rounded);
} else {
setVestedPercentage(0)
setVestedPercentage(0);
}
}
};
useEffect(() => {
calculatePercentage()
}, [userBalance.tokenAllocation, calculatePercentage])
calculatePercentage();
}, [userBalance.tokenAllocation, calculatePercentage]);
return (
<TableContainer>
@@ -149,32 +88,23 @@ const VestingSchedule = () => {
</TableHead>
</Table>
</TableContainer>
)
}
const vestingPeriod = (current?: Period, original?: number) => {
if (current === 'After') return 'Complete'
if (typeof current === 'object' && typeof original === 'number') return `${current.In + 1}/${original}`
return 'N/A'
}
);
};
const TokenTransfer = () => {
const { userBalance, currency } = useContext(ClientContext)
const { userBalance, currency } = useContext(ClientContext);
const icon = useCallback(
() => (
<Box sx={{ display: 'flex', mr: 1 }}>
<InfoTooltip title="Unlocked tokens that are available to transfer to your balance" size="medium" />
</Box>
),
[],
);
return (
<Grid container sx={{ my: 2 }} direction="column" spacing={2}>
<Grid item>
<Title
title="Transfer unlocked tokens"
Icon={() => {
return (
<Box sx={{ display: 'flex', mr: 1 }}>
<InfoTooltip title="Unlocked tokens that are available to transfer to your balance" size="medium" />
</Box>
)
}}
/>
<Title title="Transfer unlocked tokens" Icon={icon} />
</Grid>
<Grid item>
<Typography variant="subtitle2" sx={{ color: 'grey.500', mt: 2 }}>
@@ -186,5 +116,71 @@ const TokenTransfer = () => {
</Typography>
</Grid>
</Grid>
)
}
);
};
export const VestingCard = () => {
const { userBalance } = useContext(ClientContext);
const [isLoading, setIsLoading] = useState(false);
const { enqueueSnackbar, closeSnackbar } = useSnackbar();
const refreshBalances = async () => {
await userBalance.fetchBalance();
await userBalance.fetchTokenAllocation();
};
useEffect(() => () => closeSnackbar(), []);
return (
<NymCard
title="Vesting Schedule"
data-testid="check-unvested-tokens"
Icon={InfoOutlined}
Action={
<IconButton
onClick={async () => {
await refreshBalances();
enqueueSnackbar('Balances updated', { variant: 'success', preventDuplicate: true });
}}
>
<Refresh />
</IconButton>
}
>
<VestingSchedule />
<TokenTransfer />
<Box display="flex" justifyContent="space-between" alignItems="center">
{userBalance.tokenAllocation?.spendable !== '0' ? <Fee feeType="Send" /> : <div />}
<Button
size="large"
variant="contained"
onClick={async () => {
setIsLoading(true);
try {
await withdrawVestedCoins(userBalance.tokenAllocation?.spendable!);
await refreshBalances();
enqueueSnackbar('Token transfer succeeded', {
variant: 'success',
preventDuplicate: true,
});
} catch (e) {
console.log(e);
enqueueSnackbar('Token transfer failed. You may not have any transferable tokens at this time', {
variant: 'error',
preventDuplicate: true,
});
} finally {
setIsLoading(false);
}
}}
endIcon={isLoading && <CircularProgress size={16} color="inherit" />}
disabled={isLoading || userBalance.tokenAllocation?.spendable === '0'}
disableElevation
>
Transfer
</Button>
</Box>
</NymCard>
);
};
+60 -63
View File
@@ -1,4 +1,4 @@
import React, { useContext } from 'react'
import React, { useContext } from 'react';
import {
Box,
Button,
@@ -9,34 +9,33 @@ import {
Grid,
InputAdornment,
TextField,
Typography,
} from '@mui/material'
import { yupResolver } from '@hookform/resolvers/yup'
import { useForm } from 'react-hook-form'
import { EnumNodeType } from '../../types/global'
import { NodeTypeSelector } from '../../components/NodeTypeSelector'
import { bond, majorToMinor } from '../../requests'
import { validationSchema } from './validationSchema'
import { Gateway, MixNode } from '../../types'
import { ClientContext } from '../../context/main'
import { Fee } from '../../components'
} from '@mui/material';
import { yupResolver } from '@hookform/resolvers/yup';
import { useForm } from 'react-hook-form';
import { EnumNodeType } from '../../types/global';
import { NodeTypeSelector } from '../../components/NodeTypeSelector';
import { bond, majorToMinor } from '../../requests';
import { validationSchema } from './validationSchema';
import { Gateway, MixNode } from '../../types';
import { ClientContext } from '../../context/main';
import { Fee } from '../../components';
type TBondFormFields = {
withAdvancedOptions: boolean
nodeType: EnumNodeType
ownerSignature: string
identityKey: string
sphinxKey: string
profitMarginPercent: number
amount: string
host: string
version: string
location?: string
mixPort: number
verlocPort: number
clientsPort: number
httpApiPort: number
}
withAdvancedOptions: boolean;
nodeType: EnumNodeType;
ownerSignature: string;
identityKey: string;
sphinxKey: string;
profitMarginPercent: number;
amount: string;
host: string;
version: string;
location?: string;
mixPort: number;
verlocPort: number;
clientsPort: number;
httpApiPort: number;
};
const defaultValues = {
withAdvancedOptions: false,
@@ -53,7 +52,7 @@ const defaultValues = {
verlocPort: 1790,
httpApiPort: 8000,
clientsPort: 9000,
}
};
const formatData = (data: TBondFormFields) => {
const payload: { [key: string]: any } = {
@@ -63,27 +62,26 @@ const formatData = (data: TBondFormFields) => {
version: data.version,
mix_port: data.mixPort,
profit_margin_percent: data.profitMarginPercent,
}
};
if (data.nodeType === EnumNodeType.mixnode) {
payload.verloc_port = data.verlocPort
payload.http_api_port = data.httpApiPort
return payload as MixNode
} else {
payload.clients_port = data.clientsPort
payload.location = data.location
return payload as Gateway
payload.verloc_port = data.verlocPort;
payload.http_api_port = data.httpApiPort;
return payload as MixNode;
}
}
payload.clients_port = data.clientsPort;
payload.location = data.location;
return payload as Gateway;
};
export const BondForm = ({
disabled,
onError,
onSuccess,
}: {
disabled: boolean
onError: (message?: string) => void
onSuccess: (details: { address: string; amount: string }) => void
disabled: boolean;
onError: (message?: string) => void;
onSuccess: (details: { address: string; amount: string }) => void;
}) => {
const {
register,
@@ -94,27 +92,27 @@ export const BondForm = ({
} = useForm<TBondFormFields>({
resolver: yupResolver(validationSchema),
defaultValues,
})
});
const { userBalance, currency, getBondDetails } = useContext(ClientContext)
const { userBalance, currency, getBondDetails } = useContext(ClientContext);
const watchNodeType = watch('nodeType', defaultValues.nodeType)
const watchAdvancedOptions = watch('withAdvancedOptions', defaultValues.withAdvancedOptions)
const watchNodeType = watch('nodeType', defaultValues.nodeType);
const watchAdvancedOptions = watch('withAdvancedOptions', defaultValues.withAdvancedOptions);
const onSubmit = async (data: TBondFormFields) => {
const formattedData = formatData(data)
const pledge = await majorToMinor(data.amount)
const formattedData = formatData(data);
const pledge = await majorToMinor(data.amount);
await bond({ type: data.nodeType, ownerSignature: data.ownerSignature, data: formattedData, pledge })
.then(async () => {
await getBondDetails()
userBalance.fetchBalance()
onSuccess({ address: data.identityKey, amount: data.amount })
await getBondDetails();
userBalance.fetchBalance();
onSuccess({ address: data.identityKey, amount: data.amount });
})
.catch((e) => {
onError(e)
})
}
onError(e);
});
};
return (
<FormControl fullWidth>
@@ -125,8 +123,8 @@ export const BondForm = ({
<NodeTypeSelector
nodeType={watchNodeType}
setNodeType={(nodeType) => {
setValue('nodeType', nodeType)
if (nodeType === EnumNodeType.mixnode) setValue('location', undefined)
setValue('nodeType', nodeType);
if (nodeType === EnumNodeType.mixnode) setValue('location', undefined);
}}
disabled={disabled}
/>
@@ -268,20 +266,19 @@ export const BondForm = ({
if (watchAdvancedOptions) {
setValue('mixPort', defaultValues.mixPort, {
shouldValidate: true,
})
});
setValue('clientsPort', defaultValues.clientsPort, {
shouldValidate: true,
})
});
setValue('verlocPort', defaultValues.verlocPort, {
shouldValidate: true,
})
});
setValue('httpApiPort', defaultValues.httpApiPort, {
shouldValidate: true,
})
setValue('withAdvancedOptions', false)
resizeTo
});
setValue('withAdvancedOptions', false);
} else {
setValue('withAdvancedOptions', true)
setValue('withAdvancedOptions', true);
}
}}
/>
@@ -380,5 +377,5 @@ export const BondForm = ({
</Button>
</Box>
</FormControl>
)
}
);
};
+7 -7
View File
@@ -1,10 +1,10 @@
import React, { useContext } from 'react'
import { Box } from '@mui/system'
import { SuccessReponse, TransactionDetails } from '../../components'
import { ClientContext } from '../../context/main'
import React, { useContext } from 'react';
import { Box } from '@mui/material';
import { SuccessReponse, TransactionDetails } from '../../components';
import { ClientContext } from '../../context/main';
export const SuccessView: React.FC<{ details?: { amount: string; address: string } }> = ({ details }) => {
const { userBalance, currency } = useContext(ClientContext)
const { userBalance, currency } = useContext(ClientContext);
return (
<>
<SuccessReponse
@@ -23,5 +23,5 @@ export const SuccessView: React.FC<{ details?: { amount: string; address: string
</Box>
)}
</>
)
}
);
};
+33 -33
View File
@@ -1,31 +1,31 @@
import React, { useContext, useEffect, useState } from 'react'
import { Alert, Box, Button, CircularProgress } from '@mui/material'
import { BondForm } from './BondForm'
import { SuccessView } from './SuccessView'
import { NymCard } from '../../components'
import { EnumRequestStatus, RequestStatus } from '../../components/RequestStatus'
import { unbond } from '../../requests'
import { useCheckOwnership } from '../../hooks/useCheckOwnership'
import { ClientContext } from '../../context/main'
import { PageLayout } from '../../layouts'
import React, { useContext, useEffect, useState } from 'react';
import { Alert, Box, Button, CircularProgress } from '@mui/material';
import { BondForm } from './BondForm';
import { SuccessView } from './SuccessView';
import { NymCard } from '../../components';
import { EnumRequestStatus, RequestStatus } from '../../components/RequestStatus';
import { unbond } from '../../requests';
import { useCheckOwnership } from '../../hooks/useCheckOwnership';
import { ClientContext } from '../../context/main';
import { PageLayout } from '../../layouts';
export const Bond = () => {
const [status, setStatus] = useState(EnumRequestStatus.initial)
const [error, setError] = useState<string>()
const [successDetails, setSuccessDetails] = useState<{ amount: string; address: string }>()
const [status, setStatus] = useState(EnumRequestStatus.initial);
const [error, setError] = useState<string>();
const [successDetails, setSuccessDetails] = useState<{ amount: string; address: string }>();
const { checkOwnership, ownership } = useCheckOwnership()
const { userBalance, getBondDetails } = useContext(ClientContext)
const { checkOwnership, ownership } = useCheckOwnership();
const { userBalance, getBondDetails } = useContext(ClientContext);
useEffect(() => {
if (status === EnumRequestStatus.initial) {
const initialiseForm = async () => {
await checkOwnership()
setStatus(EnumRequestStatus.initial)
}
initialiseForm()
await checkOwnership();
setStatus(EnumRequestStatus.initial);
};
initialiseForm();
}
}, [status])
}, [status]);
return (
<PageLayout>
@@ -43,11 +43,11 @@ export const Bond = () => {
<Button
disabled={status === EnumRequestStatus.loading}
onClick={async () => {
setStatus(EnumRequestStatus.loading)
await unbond(ownership.nodeType!)
await getBondDetails()
await userBalance.fetchBalance()
setStatus(EnumRequestStatus.initial)
setStatus(EnumRequestStatus.loading);
await unbond(ownership.nodeType!);
await getBondDetails();
await userBalance.fetchBalance();
setStatus(EnumRequestStatus.initial);
}}
data-testid="unBond"
color="inherit"
@@ -74,12 +74,12 @@ export const Bond = () => {
{status === EnumRequestStatus.initial && (
<BondForm
onError={(e?: string) => {
setError(e)
setStatus(EnumRequestStatus.error)
setError(e);
setStatus(EnumRequestStatus.error);
}}
onSuccess={(details) => {
setSuccessDetails(details)
setStatus(EnumRequestStatus.success)
setSuccessDetails(details);
setStatus(EnumRequestStatus.success);
}}
disabled={ownership?.hasOwnership}
/>
@@ -106,8 +106,8 @@ export const Bond = () => {
>
<Button
onClick={() => {
setStatus(EnumRequestStatus.initial)
checkOwnership()
setStatus(EnumRequestStatus.initial);
checkOwnership();
}}
>
{status === EnumRequestStatus.error ? 'Again?' : 'Finish'}
@@ -117,5 +117,5 @@ export const Bond = () => {
)}
</NymCard>
</PageLayout>
)
}
);
};
+27 -45
View File
@@ -1,4 +1,4 @@
import * as Yup from 'yup'
import * as Yup from 'yup';
import {
checkHasEnoughFunds,
isValidHostname,
@@ -7,78 +7,60 @@ import {
validateLocation,
validateRawPort,
validateVersion,
} from '../../utils'
} from '../../utils';
export const validationSchema = Yup.object().shape({
identityKey: Yup.string()
.required('An indentity key is required')
.test('valid-id-key', 'A valid identity key is required', function (value) {
return validateKey(value || '', 32)
}),
.test('valid-id-key', 'A valid identity key is required', (value) => validateKey(value || '', 32)),
sphinxKey: Yup.string()
.required('A sphinx key is required')
.test('valid-sphinx-key', 'A valid sphinx key is required', function (value) {
return validateKey(value || '', 32)
}),
.test('valid-sphinx-key', 'A valid sphinx key is required', (value) => validateKey(value || '', 32)),
ownerSignature: Yup.string()
.required('Signature is required')
.test('valid-signature', 'A valid signature is required', function (value) {
return validateKey(value || '', 64)
}),
.test('valid-signature', 'A valid signature is required', (value) => validateKey(value || '', 64)),
profitMarginPercent: Yup.number().required('Profit Percentage is required').min(0).max(100),
amount: Yup.string()
.required('An amount is required')
.test('valid-amount', `Pledge error`, async function (value) {
const isValid = await validateAmount(value || '', '100000000')
.test('valid-amount', 'Pledge error', async function (value) {
const isValid = await validateAmount(value || '', '100000000');
if (!isValid) {
return this.createError({ message: `A valid amount is required (min 100)` })
} else {
const hasEnough = await checkHasEnoughFunds(value || '')
if (!hasEnough) {
return this.createError({ message: 'Not enough funds in wallet' })
}
return this.createError({ message: 'A valid amount is required (min 100)' });
}
return true
const hasEnough = await checkHasEnoughFunds(value || '');
if (!hasEnough) {
return this.createError({ message: 'Not enough funds in wallet' });
}
return true;
}),
host: Yup.string()
.required('A host is required')
.test('valid-host', 'A valid host is required', function (value) {
return !!value ? isValidHostname(value) : false
}),
.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', function (value) {
return !!value ? validateVersion(value) : false
}),
location: Yup.lazy((value) => {
if (!!value) {
.test('valid-version', 'A valid version is required', (value) => (value ? validateVersion(value) : false)),
location: Yup.lazy((locationValue) => {
if (locationValue) {
return Yup.string()
.required('A location is required')
.test('valid-location', 'A valid version is required', function (value) {
return !!value ? validateLocation(value) : false
})
.test('valid-location', 'A valid version is required', (locationValueTest) =>
locationValueTest ? validateLocation(locationValueTest) : false,
);
}
return Yup.mixed().notRequired()
return Yup.mixed().notRequired();
}),
mixPort: Yup.number()
.required('A mixport is required')
.test('valid-mixport', 'A valid mixport is required', function (value) {
return !!value ? validateRawPort(value) : false
}),
.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', function (value) {
return !!value ? validateRawPort(value) : false
}),
.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', function (value) {
return !!value ? validateRawPort(value) : false
}),
.test('valid-http', 'A valid http-api port is required', (value) => (value ? validateRawPort(value) : false)),
clientsPort: Yup.number()
.required('A clients port is required')
.test('valid-clients', 'A valid clients port is required', function (value) {
return !!value ? validateRawPort(value) : false
}),
})
.test('valid-clients', 'A valid clients port is required', (value) => (value ? validateRawPort(value) : false)),
});
+32 -34
View File
@@ -1,57 +1,55 @@
import React, { useContext } from 'react'
import { Box, Button, CircularProgress, FormControl, Grid, InputAdornment, TextField, Typography } from '@mui/material'
import { yupResolver } from '@hookform/resolvers/yup'
import { useForm } from 'react-hook-form'
import { EnumNodeType } from '../../types'
import { validationSchema } from './validationSchema'
import { ClientContext } from '../../context/main'
import { delegate, majorToMinor } from '../../requests'
import { checkHasEnoughFunds } from '../../utils'
import { Fee } from '../../components'
import React, { useContext } from 'react';
import { Box, Button, CircularProgress, FormControl, Grid, InputAdornment, TextField } from '@mui/material';
import { yupResolver } from '@hookform/resolvers/yup';
import { useForm } from 'react-hook-form';
import { EnumNodeType } from '../../types';
import { validationSchema } from './validationSchema';
import { ClientContext } from '../../context/main';
import { delegate, majorToMinor } from '../../requests';
import { checkHasEnoughFunds } from '../../utils';
import { Fee } from '../../components';
type TDelegateForm = {
nodeType: EnumNodeType
identity: string
amount: string
}
nodeType: EnumNodeType;
identity: string;
amount: string;
};
const defaultValues: TDelegateForm = {
nodeType: EnumNodeType.mixnode,
identity: '',
amount: '',
}
};
export const DelegateForm = ({
onError,
onSuccess,
}: {
onError: (message?: string) => void
onSuccess: (details: { amount: string; address: string }) => void
onError: (message?: string) => void;
onSuccess: (details: { amount: string; address: string }) => void;
}) => {
const {
register,
watch,
handleSubmit,
setError,
formState: { errors, isSubmitting },
} = useForm<TDelegateForm>({
defaultValues,
resolver: yupResolver(validationSchema),
})
});
const watchNodeType = watch('nodeType', defaultValues.nodeType)
const { userBalance, currency } = useContext(ClientContext)
const { userBalance, currency } = useContext(ClientContext);
const onSubmit = async (data: TDelegateForm) => {
const hasEnoughFunds = await checkHasEnoughFunds(data.amount)
const hasEnoughFunds = await checkHasEnoughFunds(data.amount);
if (!hasEnoughFunds) {
return setError('amount', {
setError('amount', {
message: 'Not enough funds in wallet',
})
});
return;
}
const amount = await majorToMinor(data.amount)
const amount = await majorToMinor(data.amount);
await delegate({
type: data.nodeType,
@@ -59,14 +57,14 @@ export const DelegateForm = ({
amount,
})
.then((res) => {
onSuccess({ amount: data.amount, address: res.target_address })
userBalance.fetchBalance()
onSuccess({ amount: data.amount, address: res.target_address });
userBalance.fetchBalance();
})
.catch((e) => {
console.log(e)
onError(e)
})
}
console.log(e);
onError(e);
});
};
return (
<FormControl fullWidth>
@@ -131,5 +129,5 @@ export const DelegateForm = ({
</Button>
</Box>
</FormControl>
)
}
);
};
@@ -1,10 +1,10 @@
import React, { useContext } from 'react'
import { Box } from '@mui/system'
import { SuccessReponse, TransactionDetails } from '../../components'
import { ClientContext } from '../../context/main'
import React, { useContext } from 'react';
import { Box } from '@mui/material';
import { SuccessReponse, TransactionDetails } from '../../components';
import { ClientContext } from '../../context/main';
export const SuccessView: React.FC<{ details?: { amount: string; address: string } }> = ({ details }) => {
const { userBalance, currency } = useContext(ClientContext)
const { userBalance, currency } = useContext(ClientContext);
return (
<>
<SuccessReponse
@@ -23,5 +23,5 @@ export const SuccessView: React.FC<{ details?: { amount: string; address: string
</Box>
)}
</>
)
}
);
};
+19 -19
View File
@@ -1,18 +1,18 @@
import React, { useContext, useState } from 'react'
import { Alert, AlertTitle, Box, Button, Link, Typography } from '@mui/material'
import { DelegateForm } from './DelegateForm'
import { NymCard } from '../../components'
import { EnumRequestStatus, RequestStatus } from '../../components/RequestStatus'
import { SuccessView } from './SuccessView'
import { urls, ClientContext } from '../../context/main'
import { PageLayout } from '../../layouts'
import React, { useContext, useState } from 'react';
import { Alert, AlertTitle, Box, Button, Link, Typography } from '@mui/material';
import { DelegateForm } from './DelegateForm';
import { NymCard } from '../../components';
import { EnumRequestStatus, RequestStatus } from '../../components/RequestStatus';
import { SuccessView } from './SuccessView';
import { urls, ClientContext } from '../../context/main';
import { PageLayout } from '../../layouts';
export const Delegate = () => {
const [status, setStatus] = useState<EnumRequestStatus>(EnumRequestStatus.initial)
const [error, setError] = useState<string>()
const [successDetails, setSuccessDetails] = useState<{ amount: string; address: string }>()
const [status, setStatus] = useState<EnumRequestStatus>(EnumRequestStatus.initial);
const [error, setError] = useState<string>();
const [successDetails, setSuccessDetails] = useState<{ amount: string; address: string }>();
const { network } = useContext(ClientContext)
const { network } = useContext(ClientContext);
return (
<PageLayout>
@@ -26,12 +26,12 @@ export const Delegate = () => {
{status === EnumRequestStatus.initial && (
<DelegateForm
onError={(message?: string) => {
setStatus(EnumRequestStatus.error)
setError(message)
setStatus(EnumRequestStatus.error);
setError(message);
}}
onSuccess={(details) => {
setStatus(EnumRequestStatus.success)
setSuccessDetails(details)
setStatus(EnumRequestStatus.success);
setSuccessDetails(details);
}}
/>
)}
@@ -62,7 +62,7 @@ export const Delegate = () => {
<Button
data-testid="finish-button"
onClick={() => {
setStatus(EnumRequestStatus.initial)
setStatus(EnumRequestStatus.initial);
}}
>
Finish
@@ -80,5 +80,5 @@ export const Delegate = () => {
for uptime and performances to help make delegation decisions
</Typography>
</PageLayout>
)
}
);
};
@@ -1,5 +1,5 @@
import * as Yup from 'yup'
import { validateAmount, validateKey } from '../../utils'
import * as Yup from 'yup';
import { validateAmount, validateKey } from '../../utils';
export const validationSchema = Yup.object().shape({
identity: Yup.string()
@@ -7,9 +7,9 @@ export const validationSchema = Yup.object().shape({
.test(
'valid-id-key',
'A valid identity key is required e.g. 824WyExLUWvLE2mpSHBatN4AoByuLzfnHFeHWiBYzg4z',
(value) => (!!value ? validateKey(value, 32) : false),
(value) => (value ? validateKey(value, 32) : false),
),
amount: Yup.string()
.required()
.test('valid-amount-key', 'A valid amount is required', (value) => (!!value ? validateAmount(value, '0') : false)),
})
.test('valid-amount-key', 'A valid amount is required', (value) => (value ? validateAmount(value, '0') : false)),
});
+11 -11
View File
@@ -1,11 +1,11 @@
export * from './Admin'
export * from './balance'
export * from './bond'
export * from './delegate'
export * from './internal-docs'
export * from './receive'
export * from './send'
export * from './welcome'
export * from './settings'
export * from './unbond'
export * from './undelegate'
export * from './Admin';
export * from './balance';
export * from './bond';
export * from './delegate';
export * from './internal-docs';
export * from './receive';
export * from './send';
export * from './welcome';
export * from './settings';
export * from './unbond';
export * from './undelegate';
+162 -164
View File
@@ -1,165 +1,163 @@
import React from 'react'
import { List, ListItem } from '@mui/material'
import { DocEntry } from './DocEntry'
import React from 'react';
import { List, ListItem } from '@mui/material';
import { DocEntry } from './DocEntry';
export const ApiList = () => {
return (
<List>
<ListItem>
<DocEntry
function={{
name: 'connect_with_mnemonic',
args: [{ name: 'mnemonic', type: 'str' }],
}}
/>
</ListItem>
<ListItem>
<DocEntry function={{ name: 'get_balance', args: [] }} />
</ListItem>
<ListItem>
<DocEntry
function={{
name: 'minor_to_major',
args: [{ name: 'amount', type: 'str' }],
}}
/>
</ListItem>
<ListItem>
<DocEntry
function={{
name: 'major_to_minor',
args: [{ name: 'amount', type: 'str' }],
}}
/>
</ListItem>
<ListItem>
<DocEntry
function={{
name: 'owns_mixnode',
args: [],
}}
/>
</ListItem>
<ListItem>
<DocEntry
function={{
name: 'owns_gateway',
args: [],
}}
/>
</ListItem>
<ListItem>
<DocEntry
function={{
name: 'bond_mixnode',
args: [
{ name: 'mixnode', type: 'object' },
{ name: 'bond', type: 'object' },
],
}}
/>
</ListItem>
<ListItem>
<DocEntry
function={{
name: 'unbond_mixnode',
args: [],
}}
/>
</ListItem>
<ListItem>
<DocEntry
function={{
name: 'bond_gateway',
args: [
{ name: 'gateway', type: 'object' },
{ name: 'bond', type: 'object' },
],
}}
/>
</ListItem>
<ListItem>
<DocEntry
function={{
name: 'unbond_gateway',
args: [],
}}
/>
</ListItem>
<ListItem>
<DocEntry
function={{
name: 'delegate_to_mixnode',
args: [
{ name: 'identity', type: 'str' },
{ name: 'amount', type: 'object' },
],
}}
/>
</ListItem>
<ListItem>
<DocEntry
function={{
name: 'undelegate_from_mixnode',
args: [{ name: 'identity', type: 'str' }],
}}
/>
</ListItem>
<ListItem>
<DocEntry
function={{
name: 'delegate_to_gateway',
args: [
{ name: 'identity', type: 'str' },
{ name: 'amount', type: 'object' },
],
}}
/>
</ListItem>
<ListItem>
<DocEntry
function={{
name: 'undelegate_from_gateway',
args: [{ name: 'identity', type: 'str' }],
}}
/>
</ListItem>
<ListItem>
<DocEntry
function={{
name: 'send',
args: [
{ name: 'address', type: 'str' },
{ name: 'amount', type: 'object' },
{ name: 'memo', type: 'str' },
],
}}
/>
</ListItem>
<ListItem>
<DocEntry
function={{
name: 'outdated_get_approximate_fee',
args: [{ name: 'operation', type: 'str' }],
}}
/>
</ListItem>
<ListItem>
<DocEntry
function={{
name: 'create_new_account',
args: [],
}}
/>
</ListItem>
<ListItem>
<DocEntry
function={{
name: 'connect_with_mnemonic',
args: [{ name: 'mnemonic', type: 'str' }],
}}
/>
</ListItem>
</List>
)
}
export const ApiList = () => (
<List>
<ListItem>
<DocEntry
function={{
name: 'connect_with_mnemonic',
args: [{ name: 'mnemonic', type: 'str' }],
}}
/>
</ListItem>
<ListItem>
<DocEntry function={{ name: 'get_balance', args: [] }} />
</ListItem>
<ListItem>
<DocEntry
function={{
name: 'minor_to_major',
args: [{ name: 'amount', type: 'str' }],
}}
/>
</ListItem>
<ListItem>
<DocEntry
function={{
name: 'major_to_minor',
args: [{ name: 'amount', type: 'str' }],
}}
/>
</ListItem>
<ListItem>
<DocEntry
function={{
name: 'owns_mixnode',
args: [],
}}
/>
</ListItem>
<ListItem>
<DocEntry
function={{
name: 'owns_gateway',
args: [],
}}
/>
</ListItem>
<ListItem>
<DocEntry
function={{
name: 'bond_mixnode',
args: [
{ name: 'mixnode', type: 'object' },
{ name: 'bond', type: 'object' },
],
}}
/>
</ListItem>
<ListItem>
<DocEntry
function={{
name: 'unbond_mixnode',
args: [],
}}
/>
</ListItem>
<ListItem>
<DocEntry
function={{
name: 'bond_gateway',
args: [
{ name: 'gateway', type: 'object' },
{ name: 'bond', type: 'object' },
],
}}
/>
</ListItem>
<ListItem>
<DocEntry
function={{
name: 'unbond_gateway',
args: [],
}}
/>
</ListItem>
<ListItem>
<DocEntry
function={{
name: 'delegate_to_mixnode',
args: [
{ name: 'identity', type: 'str' },
{ name: 'amount', type: 'object' },
],
}}
/>
</ListItem>
<ListItem>
<DocEntry
function={{
name: 'undelegate_from_mixnode',
args: [{ name: 'identity', type: 'str' }],
}}
/>
</ListItem>
<ListItem>
<DocEntry
function={{
name: 'delegate_to_gateway',
args: [
{ name: 'identity', type: 'str' },
{ name: 'amount', type: 'object' },
],
}}
/>
</ListItem>
<ListItem>
<DocEntry
function={{
name: 'undelegate_from_gateway',
args: [{ name: 'identity', type: 'str' }],
}}
/>
</ListItem>
<ListItem>
<DocEntry
function={{
name: 'send',
args: [
{ name: 'address', type: 'str' },
{ name: 'amount', type: 'object' },
{ name: 'memo', type: 'str' },
],
}}
/>
</ListItem>
<ListItem>
<DocEntry
function={{
name: 'outdated_get_approximate_fee',
args: [{ name: 'operation', type: 'str' }],
}}
/>
</ListItem>
<ListItem>
<DocEntry
function={{
name: 'create_new_account',
args: [],
}}
/>
</ListItem>
<ListItem>
<DocEntry
function={{
name: 'connect_with_mnemonic',
args: [{ name: 'mnemonic', type: 'str' }],
}}
/>
</ListItem>
</List>
);
+26 -43
View File
@@ -1,56 +1,50 @@
import React from 'react'
import { Button, Card, CardContent, TextField } from '@mui/material'
import { invoke } from '@tauri-apps/api'
/* eslint-disable react/destructuring-assignment */
import React from 'react';
import { Button, Card, CardContent, TextField } from '@mui/material';
import { invoke } from '@tauri-apps/api';
interface DocEntryProps {
function: FunctionDef
function: FunctionDef;
}
interface FunctionDef {
name: string
args: ArgDef[]
name: string;
args: ArgDef[];
}
interface ArgDef {
name: string
type: string
name: string;
type: string;
}
const argKey = (functionName: string, arg: string) => `${functionName}_${arg}`
const argKey = (functionName: string, arg: string) => `${functionName}_${arg}`;
function collectArgs(functionName: string, args: ArgDef[]) {
let invokeArgs: { [key: string]: string } = {}
const invokeArgs: { [key: string]: string } = {};
args.forEach((arg) => {
let elem: HTMLElement | null = document.getElementById(
argKey(functionName, arg.name),
)
const elem: HTMLElement | null = document.getElementById(argKey(functionName, arg.name));
if (arg.type === 'object') {
console.log(arg)
invokeArgs[arg.name] = JSON.parse((elem as HTMLInputElement).value)
invokeArgs[arg.name] = JSON.parse((elem as HTMLInputElement).value);
} else {
invokeArgs[arg.name] = (elem as HTMLInputElement).value || ''
invokeArgs[arg.name] = (elem as HTMLInputElement).value || '';
}
})
console.log(invokeArgs)
return invokeArgs
});
return invokeArgs;
}
export const DocEntry = (props: DocEntryProps) => {
const [card, setCard] = React.useState(<Card />)
export const DocEntry: React.FC<DocEntryProps> = (props) => {
const [card, setCard] = React.useState(<Card />);
const onClick = () => {
invoke(
props.function.name,
collectArgs(props.function.name, props.function.args),
)
invoke(props.function.name, collectArgs(props.function.name, props.function.args))
.then((result) => {
setCard(
<Card>
<CardContent>{JSON.stringify(result, null, 4)}</CardContent>
</Card>,
)
);
})
.catch((e) =>
setCard(
@@ -58,26 +52,15 @@ export const DocEntry = (props: DocEntryProps) => {
<CardContent>{e}</CardContent>
</Card>,
),
)
}
);
};
return (
<div>
<Button
variant="contained"
color="primary"
size="small"
disableElevation
onClick={onClick}
>
<Button variant="contained" color="primary" size="small" disableElevation onClick={onClick}>
{props.function.name}
</Button>
<Button
variant="contained"
size="small"
disableElevation
onClick={() => setCard(<Card />)}
>
<Button variant="contained" size="small" disableElevation onClick={() => setCard(<Card />)}>
X
</Button>
<div>
@@ -92,5 +75,5 @@ export const DocEntry = (props: DocEntryProps) => {
<br />
{card}
</div>
)
}
);
};
+8 -8
View File
@@ -1,18 +1,18 @@
import React, { useContext } from 'react'
import { NymCard } from '../../components'
import { ApiList } from './ApiList'
import React, { useContext } from 'react';
import { NymCard } from '../../components';
import { ApiList } from './ApiList';
import { ADMIN_ADDRESS, ClientContext } from '../../context/main'
import { ADMIN_ADDRESS, ClientContext } from '../../context/main';
export const InternalDocs = () => {
const { clientDetails } = useContext(ClientContext)
const { clientDetails } = useContext(ClientContext);
if (clientDetails?.client_address === ADMIN_ADDRESS) {
return (
<NymCard title="Docs" subheader="Internal API docs">
<ApiList />
</NymCard>
)
);
}
return null
}
return null;
};
+9 -9
View File
@@ -1,12 +1,12 @@
import React, { useContext } from 'react'
import QRCode from 'qrcode.react'
import { Alert, Box, Stack } from '@mui/material'
import { ClientAddress, NymCard } from '../../components'
import { ClientContext } from '../../context/main'
import { PageLayout } from '../../layouts'
import React, { useContext } from 'react';
import QRCode from 'qrcode.react';
import { Alert, Box, Stack } from '@mui/material';
import { ClientAddress, NymCard } from '../../components';
import { ClientContext } from '../../context/main';
import { PageLayout } from '../../layouts';
export const Receive = () => {
const { clientDetails, currency } = useContext(ClientContext)
const { clientDetails, currency } = useContext(ClientContext);
return (
<PageLayout>
@@ -22,5 +22,5 @@ export const Receive = () => {
</Stack>
</NymCard>
</PageLayout>
)
}
);
};
+13 -13
View File
@@ -1,21 +1,21 @@
import React, { useContext } from 'react'
import { Box, CircularProgress, Link, Typography } from '@mui/material'
import { SendError } from './SendError'
import { ClientContext, urls } from '../../context/main'
import { SuccessReponse } from '../../components'
import { TransactionDetails } from '../../components/TransactionDetails'
import { TransactionDetails as TTransactionDetails } from '../../types'
import React, { useContext } from 'react';
import { Box, CircularProgress, Link, Typography } from '@mui/material';
import { SendError } from './SendError';
import { ClientContext, urls } from '../../context/main';
import { SuccessReponse } from '../../components';
import { TransactionDetails } from '../../components/TransactionDetails';
import { TransactionDetails as TTransactionDetails } from '../../types';
export const SendConfirmation = ({
data,
error,
isLoading,
}: {
data?: TTransactionDetails & { tx_hash: string }
error?: string
isLoading: boolean
data?: TTransactionDetails & { tx_hash: string };
error?: string;
isLoading: boolean;
}) => {
const { userBalance, currency, network } = useContext(ClientContext)
const { userBalance, currency, network } = useContext(ClientContext);
return (
<Box
sx={{
@@ -55,5 +55,5 @@ export const SendConfirmation = ({
</>
)}
</Box>
)
}
);
};
+34 -36
View File
@@ -1,38 +1,36 @@
import React from 'react'
import { Alert, Box, Card, Typography } from '@mui/material'
import { ErrorOutline } from '@mui/icons-material'
import React from 'react';
import { Alert, Box, Card, Typography } from '@mui/material';
import { ErrorOutline } from '@mui/icons-material';
export const SendError = ({ message }: { message?: string }) => {
return (
<Box
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
width: '100%',
}}
>
<>
<Box
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
mb: 4,
}}
>
<ErrorOutline sx={{ fontSize: 50, color: 'error.main' }} />
<Typography>Transaction failed</Typography>
</Box>
export const SendError = ({ message }: { message?: string }) => (
<Box
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
width: '100%',
}}
>
<>
<Box
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
mb: 4,
}}
>
<ErrorOutline sx={{ fontSize: 50, color: 'error.main' }} />
<Typography>Transaction failed</Typography>
</Box>
<Card variant="outlined" sx={{ width: '100%', p: 2 }}>
<Alert severity="error" data-testid="transaction-error">
An error occured during the request {message}
</Alert>
</Card>
</>
</Box>
)
}
<Card variant="outlined" sx={{ width: '100%', p: 2 }}>
<Alert severity="error" data-testid="transaction-error">
An error occured during the request {message}
</Alert>
</Card>
</>
</Box>
);
+9 -9
View File
@@ -1,15 +1,15 @@
import React, { useContext } from 'react'
import { Grid, InputAdornment, TextField } from '@mui/material'
import { useFormContext } from 'react-hook-form'
import { ClientContext } from '../../context/main'
import { Fee, ClientAddress } from '../../components'
import React, { useContext } from 'react';
import { Grid, InputAdornment, TextField } from '@mui/material';
import { useFormContext } from 'react-hook-form';
import { ClientContext } from '../../context/main';
import { Fee, ClientAddress } from '../../components';
export const SendForm = () => {
const {
register,
formState: { errors },
} = useFormContext()
const { currency } = useContext(ClientContext)
} = useFormContext();
const { currency } = useContext(ClientContext);
return (
<Grid container spacing={3}>
@@ -51,5 +51,5 @@ export const SendForm = () => {
<Fee feeType="Send" />
</Grid>
</Grid>
)
}
);
};
+18 -20
View File
@@ -1,13 +1,22 @@
import React, { useContext } from 'react'
import { Card, Divider, Grid, Typography } from '@mui/material'
import { useFormContext } from 'react-hook-form'
import { ClientContext } from '../../context/main'
import React, { useContext } from 'react';
import { Card, Divider, Grid, Typography } from '@mui/material';
import { useFormContext } from 'react-hook-form';
import { ClientContext } from '../../context/main';
const SendReviewField = ({ title, subtitle, info }: { title: string; subtitle?: string; info?: boolean }) => (
<>
<Typography sx={{ color: info ? 'nym.fee' : '' }}>{title}</Typography>
<Typography data-testid={title} sx={{ color: info ? 'nym.fee' : '', wordBreak: 'break-all' }}>
{subtitle}
</Typography>
</>
);
export const SendReview = ({ transferFee }: { transferFee?: string }) => {
const { getValues } = useFormContext()
const { clientDetails, currency } = useContext(ClientContext)
const { getValues } = useFormContext();
const { clientDetails, currency } = useContext(ClientContext);
const values = getValues()
const values = getValues();
return (
<Card
@@ -44,16 +53,5 @@ export const SendReview = ({ transferFee }: { transferFee?: string }) => {
</Grid>
</Grid>
</Card>
)
}
export const SendReviewField = ({ title, subtitle, info }: { title: string; subtitle?: string; info?: boolean }) => {
return (
<>
<Typography sx={{ color: info ? 'nym.fee' : '' }}>{title}</Typography>
<Typography data-testid={title} sx={{ color: info ? 'nym.fee' : '', wordBreak: 'break-all' }}>
{subtitle}
</Typography>
</>
)
}
);
};
+76 -75
View File
@@ -1,104 +1,105 @@
import React, { useEffect, useContext, useState } from 'react'
import { useForm, FormProvider } from 'react-hook-form'
import { yupResolver } from '@hookform/resolvers/yup'
import { Box, Button, Step, StepLabel, Stepper } from '@mui/material'
import { SendForm } from './SendForm'
import { SendReview } from './SendReview'
import { SendConfirmation } from './SendConfirmation'
import { ClientContext } from '../../context/main'
import { validationSchema } from './validationSchema'
import { TauriTxResult, TransactionDetails } from '../../types'
import { getGasFee, majorToMinor, send } from '../../requests'
import { checkHasEnoughFunds } from '../../utils'
import React, { useEffect, useContext, useState } from 'react';
import { useForm, FormProvider } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';
import { Box, Button, Step, StepLabel, Stepper } from '@mui/material';
import { SendForm } from './SendForm';
import { SendReview } from './SendReview';
import { SendConfirmation } from './SendConfirmation';
import { ClientContext } from '../../context/main';
import { validationSchema } from './validationSchema';
import { TauriTxResult, TransactionDetails } from '../../types';
import { getGasFee, majorToMinor, send } from '../../requests';
import { checkHasEnoughFunds } from '../../utils';
const defaultValues = {
amount: '',
memo: '',
to: '',
}
};
export type TFormData = {
amount: string
memo: string
to: string
}
amount: string;
memo: string;
to: string;
};
export const SendWizard = () => {
const [activeStep, setActiveStep] = useState(0)
const [isLoading, setIsLoading] = useState(false)
const [requestError, setRequestError] = useState<string>()
const [transferFee, setTransferFee] = useState<string>()
const [confirmedData, setConfirmedData] = useState<TransactionDetails & { tx_hash: string }>()
const [activeStep, setActiveStep] = useState(0);
const [isLoading, setIsLoading] = useState(false);
const [requestError, setRequestError] = useState<string>();
const [transferFee, setTransferFee] = useState<string>();
const [confirmedData, setConfirmedData] = useState<TransactionDetails & { tx_hash: string }>();
const { userBalance } = useContext(ClientContext)
const { userBalance } = useContext(ClientContext);
useEffect(() => {
const getFee = async () => {
const fee = await getGasFee('Send')
setTransferFee(fee.amount)
}
getFee()
}, [])
const fee = await getGasFee('Send');
setTransferFee(fee.amount);
};
getFee();
}, []);
const steps = ['Enter address', 'Review and send', 'Await confirmation']
const steps = ['Enter address', 'Review and send', 'Await confirmation'];
const methods = useForm<TFormData>({
defaultValues: {
...defaultValues,
},
resolver: yupResolver(validationSchema),
})
});
const handleNextStep = methods.handleSubmit(() => setActiveStep((s) => s + 1))
const handleNextStep = methods.handleSubmit(() => setActiveStep((s) => s + 1));
const handlePreviousStep = () => setActiveStep((s) => s - 1)
const handlePreviousStep = () => setActiveStep((s) => s - 1);
const handleFinish = () => {
methods.reset()
setIsLoading(false)
setRequestError(undefined)
setConfirmedData(undefined)
setActiveStep(0)
}
methods.reset();
setIsLoading(false);
setRequestError(undefined);
setConfirmedData(undefined);
setActiveStep(0);
};
const handleSend = async () => {
const formState = methods.getValues()
const formState = methods.getValues();
const hasEnoughFunds = await checkHasEnoughFunds(formState.amount)
const hasEnoughFunds = await checkHasEnoughFunds(formState.amount);
if (!hasEnoughFunds) {
methods.setError('amount', {
message: 'Not enough funds in wallet',
})
return handlePreviousStep()
} else {
setIsLoading(true)
setActiveStep((s) => s + 1)
const amount = await majorToMinor(formState.amount)
send({
amount,
address: formState.to,
memo: formState.memo,
})
.then((res: TauriTxResult) => {
const { details, tx_hash } = res
setActiveStep((s) => s + 1)
setConfirmedData({
...details,
amount: { denom: 'Major', amount: formState.amount },
tx_hash,
})
setIsLoading(false)
userBalance.fetchBalance()
})
.catch((e) => {
setRequestError(e)
setIsLoading(false)
console.log(e)
})
});
handlePreviousStep();
return;
}
}
setIsLoading(true);
setActiveStep((s) => s + 1);
const amount = await majorToMinor(formState.amount);
send({
amount,
address: formState.to,
memo: formState.memo,
})
.then((res: TauriTxResult) => {
// eslint-disable-next-line @typescript-eslint/naming-convention
const { details, tx_hash } = res;
setActiveStep((s) => s + 1);
setConfirmedData({
...details,
amount: { denom: 'Major', amount: formState.amount },
tx_hash,
});
setIsLoading(false);
userBalance.fetchBalance();
})
.catch((e) => {
setRequestError(e);
setIsLoading(false);
console.error(e);
});
};
return (
<FormProvider {...methods}>
@@ -109,8 +110,8 @@ export const SendWizard = () => {
p: 2,
}}
>
{steps.map((s, i) => (
<Step key={i}>
{steps.map((s) => (
<Step key={s}>
<StepLabel>{s}</StepLabel>
</Step>
))}
@@ -126,7 +127,7 @@ export const SendWizard = () => {
}}
>
{activeStep === 0 ? (
<SendForm transferFee={transferFee} />
<SendForm />
) : activeStep === 1 ? (
<SendReview transferFee={transferFee} />
) : (
@@ -160,5 +161,5 @@ export const SendWizard = () => {
</Box>
</Box>
</FormProvider>
)
}
);
};
+8 -8
View File
@@ -1,16 +1,16 @@
import React, { useContext } from 'react'
import { NymCard } from '../../components'
import { SendWizard } from './SendWizard'
import { ClientContext } from '../../context/main'
import { PageLayout } from '../../layouts'
import React, { useContext } from 'react';
import { NymCard } from '../../components';
import { SendWizard } from './SendWizard';
import { ClientContext } from '../../context/main';
import { PageLayout } from '../../layouts';
export const Send = () => {
const { currency } = useContext(ClientContext)
const { currency } = useContext(ClientContext);
return (
<PageLayout>
<NymCard title={`Send ${currency?.major}`} noPadding>
<SendWizard />
</NymCard>
</PageLayout>
)
}
);
};
@@ -1,11 +1,9 @@
import * as Yup from 'yup'
import { validateAmount } from '../../utils'
import * as Yup from 'yup';
import { validateAmount } from '../../utils';
export const validationSchema = Yup.object().shape({
to: Yup.string().strict().trim('Cannot have leading space').required(),
amount: Yup.string()
.required()
.test('valid-amount', 'A valid amount is required', (amount) => {
return validateAmount(amount || '0', '0')
}),
})
.test('valid-amount', 'A valid amount is required', (amount) => validateAmount(amount || '0', '0')),
});
+20 -20
View File
@@ -1,27 +1,27 @@
import React, { useContext, useState } from 'react'
import { Alert, Box, Dialog } from '@mui/material'
import { NymCard } from '../../components'
import { ClientContext } from '../../context/main'
import { Tabs } from './tabs'
import { Profile } from './profile'
import { SystemVariables } from './system-variables'
import { NodeStats } from './node-stats'
import { useSettingsState } from './useSettingsState'
import { NodeStatus } from '../../components/NodeStatus'
import { Node as NodeIcon } from '../../svg-icons/node'
import React, { useContext, useState } from 'react';
import { Alert, Box, Dialog } from '@mui/material';
import { NymCard } from '../../components';
import { ClientContext } from '../../context/main';
import { Tabs } from './tabs';
import { Profile } from './profile';
import { SystemVariables } from './system-variables';
import { NodeStats } from './node-stats';
import { useSettingsState } from './useSettingsState';
import { NodeStatus } from '../../components/NodeStatus';
import { Node as NodeIcon } from '../../svg-icons/node';
const tabs = ['Profile', 'System variables', 'Node stats']
const tabs = ['Profile', 'System variables', 'Node stats'];
export const Settings = () => {
const [selectedTab, setSelectedTab] = useState(0)
const [selectedTab, setSelectedTab] = useState(0);
const { mixnodeDetails, showSettings, handleShowSettings, getBondDetails } = useContext(ClientContext)
const { status, saturation, rewardEstimation, inclusionProbability } = useSettingsState(showSettings)
const { mixnodeDetails, showSettings, handleShowSettings, getBondDetails } = useContext(ClientContext);
const { status, saturation, rewardEstimation, inclusionProbability } = useSettingsState(showSettings);
const handleTabChange = (_: React.SyntheticEvent, newTab: number) => setSelectedTab(newTab)
const handleTabChange = (_: React.SyntheticEvent, newTab: number) => setSelectedTab(newTab);
return showSettings ? (
<Dialog open={true} onClose={handleShowSettings} maxWidth="md" fullWidth>
<Dialog open onClose={handleShowSettings} maxWidth="md" fullWidth>
<NymCard
title={
<Box display="flex" alignItems="center">
@@ -36,7 +36,7 @@ export const Settings = () => {
<Tabs tabs={tabs} selectedTab={selectedTab} onChange={handleTabChange} disabled={!mixnodeDetails} />
{!mixnodeDetails && (
<Alert severity="info" sx={{ m: 4 }}>
You don't currently have a node running
You do not currently have a node running
</Alert>
)}
{selectedTab === 0 && mixnodeDetails && <Profile />}
@@ -53,5 +53,5 @@ export const Settings = () => {
</>
</NymCard>
</Dialog>
) : null
}
) : null;
};
+7 -7
View File
@@ -1,10 +1,10 @@
import React, { useContext } from 'react'
import { OpenInNew } from '@mui/icons-material'
import { Button, Link, Stack, Typography } from '@mui/material'
import { urls, ClientContext} from '../../context/main'
import React, { useContext } from 'react';
import { OpenInNew } from '@mui/icons-material';
import { Button, Link, Stack, Typography } from '@mui/material';
import { urls, ClientContext } from '../../context/main';
export const NodeStats = ({ mixnodeId }: { mixnodeId?: string }) => {
const {network} = useContext(ClientContext)
const { network } = useContext(ClientContext);
return (
<Stack spacing={2} sx={{ p: 4 }}>
<Typography>All your node stats are available on the link below</Typography>
@@ -12,5 +12,5 @@ export const NodeStats = ({ mixnodeId }: { mixnodeId?: string }) => {
<Button endIcon={<OpenInNew />}>Network Explorer</Button>
</Link>
</Stack>
)
}
);
};
+6 -7
View File
@@ -1,10 +1,9 @@
import React, { useContext } from 'react'
import { Button, Divider, Stack, TextField, Typography } from '@mui/material'
import { Box } from '@mui/system'
import { ClientContext } from '../../context/main'
import React, { useContext } from 'react';
import { Box, Button, Divider, Stack, TextField, Typography } from '@mui/material';
import { ClientContext } from '../../context/main';
export const Profile = () => {
const { mixnodeDetails } = useContext(ClientContext)
const { mixnodeDetails } = useContext(ClientContext);
return (
<>
<Box sx={{ p: 3 }}>
@@ -31,5 +30,5 @@ export const Profile = () => {
</Button>
</Box>
</>
)
}
);
};
@@ -1,18 +1,52 @@
import React, { useContext, useState } from 'react'
import { Box, Button, CircularProgress, Grid, LinearProgress, Stack, TextField, Typography } from '@mui/material'
import { PercentOutlined } from '@mui/icons-material'
import { yupResolver } from '@hookform/resolvers/yup'
import { useForm } from 'react-hook-form'
import { InfoTooltip } from '../../components/InfoToolTip'
import { InclusionProbabilityResponse, TMixnodeBondDetails } from '../../types'
import { validationSchema } from './validationSchema'
import { updateMixnode } from '../../requests'
import { ClientContext } from '../../context/main'
import { Fee } from '../../components'
import React, { useContext, useState } from 'react';
import { Box, Button, CircularProgress, Grid, LinearProgress, Stack, TextField, Typography } from '@mui/material';
import { PercentOutlined } from '@mui/icons-material';
import { yupResolver } from '@hookform/resolvers/yup';
import { useForm } from 'react-hook-form';
import { Fee, InfoTooltip } from '../../components';
import { InclusionProbabilityResponse, TMixnodeBondDetails } from '../../types';
import { validationSchema } from './validationSchema';
import { updateMixnode } from '../../requests';
import { ClientContext } from '../../context/main';
type TFormData = {
profitMarginPercent: number
}
profitMarginPercent: string;
};
const DataField = ({ title, info, Indicator }: { title: string; info: string; Indicator: React.ReactElement }) => (
<Grid container justifyContent="space-between">
<Grid item xs={12} md={6}>
<Box display="flex" alignItems="center">
<InfoTooltip title={info} tooltipPlacement="right" />
<Typography sx={{ ml: 1 }}>{title}</Typography>
</Box>
</Grid>
<Grid item xs={12} md={6}>
<Box display="flex" justifyContent="flex-end">
{Indicator}
</Box>
</Grid>
</Grid>
);
const PercentIndicator = ({ value, warning }: { value: number; warning?: boolean }) => (
<Grid container alignItems="center">
<Grid item xs={2}>
<Typography component="span" sx={{ color: warning ? 'error.main' : 'nym.fee', fontWeight: 600 }}>
{value}%
</Typography>
</Grid>
<Grid item xs={10}>
<LinearProgress
color="inherit"
sx={{ color: warning ? 'error.main' : 'nym.fee' }}
variant="determinate"
value={value < 100 ? value : 100}
/>
</Grid>
</Grid>
);
export const SystemVariables = ({
mixnodeDetails,
@@ -21,13 +55,13 @@ export const SystemVariables = ({
inclusionProbability,
onUpdate,
}: {
mixnodeDetails: TMixnodeBondDetails['mix_node']
saturation: number
rewardEstimation: number
inclusionProbability: InclusionProbabilityResponse
onUpdate: () => void
mixnodeDetails: TMixnodeBondDetails['mix_node'];
saturation: number;
rewardEstimation: number;
inclusionProbability: InclusionProbabilityResponse;
onUpdate: () => void;
}) => {
const [nodeUpdateResponse, setNodeUpdateResponse] = useState<'success' | 'failed'>()
const [nodeUpdateResponse, setNodeUpdateResponse] = useState<'success' | 'failed'>();
const {
register,
@@ -36,21 +70,21 @@ export const SystemVariables = ({
} = useForm({
resolver: yupResolver(validationSchema),
defaultValues: { profitMarginPercent: mixnodeDetails.profit_margin_percent.toString() },
})
});
const { userBalance, currency } = useContext(ClientContext)
const { userBalance, currency } = useContext(ClientContext);
const onSubmit = async (data: TFormData) => {
try {
await updateMixnode({ profitMarginPercent: data.profitMarginPercent })
await userBalance.fetchBalance()
onUpdate()
setNodeUpdateResponse('success')
await updateMixnode({ profitMarginPercent: +data.profitMarginPercent });
await userBalance.fetchBalance();
onUpdate();
setNodeUpdateResponse('success');
} catch (e) {
setNodeUpdateResponse('failed')
console.log(e)
setNodeUpdateResponse('failed');
console.error(e);
}
}
};
return (
<>
@@ -60,7 +94,7 @@ export const SystemVariables = ({
{...register('profitMarginPercent', { valueAsNumber: true })}
label="Profit margin"
helperText={
!!errors.profitMarginPercent
errors.profitMarginPercent
? errors.profitMarginPercent.message
: "The percentage of your delegators' rewards that you as the node operator will take"
}
@@ -125,42 +159,5 @@ export const SystemVariables = ({
</Button>
</Box>
</>
)
}
const DataField = ({ title, info, Indicator }: { title: string; info: string; Indicator: React.ReactElement }) => (
<Grid container justifyContent="space-between">
<Grid item xs={12} md={6}>
<Box display="flex" alignItems="center">
<InfoTooltip title={info} tooltipPlacement="right" />
<Typography sx={{ ml: 1 }}>{title}</Typography>
</Box>
</Grid>
<Grid item xs={12} md={6}>
<Box display="flex" justifyContent="flex-end">
{Indicator}
</Box>
</Grid>
</Grid>
)
const PercentIndicator = ({ value, warning }: { value: number; warning?: boolean }) => {
return (
<Grid container alignItems="center">
<Grid item xs={2}>
<Typography component="span" sx={{ color: warning ? 'error.main' : 'nym.fee', fontWeight: 600 }}>
{value}%
</Typography>
</Grid>
<Grid item xs={10}>
<LinearProgress
color="inherit"
sx={{ color: warning ? 'error.main' : 'nym.fee' }}
variant="determinate"
value={value < 100 ? value : 100}
/>
</Grid>
</Grid>
)
}
);
};
+10 -11
View File
@@ -1,12 +1,11 @@
import React from 'react'
import { Box } from '@mui/system'
import { Tab, Tabs as MuiTabs } from '@mui/material'
import React from 'react';
import { Tab, Tabs as MuiTabs, Box } from '@mui/material';
export const Tabs: React.FC<{
tabs: string[]
selectedTab: number
disabled: boolean
onChange: (event: React.SyntheticEvent, tab: number) => void
tabs: string[];
selectedTab: number;
disabled: boolean;
onChange: (event: React.SyntheticEvent, tab: number) => void;
}> = ({ tabs, selectedTab, disabled, onChange }) => (
<MuiTabs
value={selectedTab}
@@ -14,10 +13,10 @@ export const Tabs: React.FC<{
sx={{ bgcolor: 'grey.200', borderTop: '1px solid', borderBottom: '1px solid', borderColor: 'grey.300' }}
textColor="inherit"
>
{tabs.map((tabName, index) => (
<Tab key={index} label={tabName} sx={{ textTransform: 'capitalize' }} disabled={disabled} />
{tabs.map((tabName) => (
<Tab key={tabName} label={tabName} sx={{ textTransform: 'capitalize' }} disabled={disabled} />
))}
</MuiTabs>
)
);
export const TabPanel: React.FC = ({ children }) => <Box sx={{ p: 4 }}>{children}</Box>
export const TabPanel: React.FC = ({ children }) => <Box sx={{ p: 4 }}>{children}</Box>;
@@ -1,77 +1,79 @@
import { useContext, useEffect, useState } from 'react'
import { ClientContext } from '../../context/main'
import { useContext, useEffect, useState } from 'react';
import { ClientContext } from '../../context/main';
import {
getMixnodeRewardEstimation,
getMixnodeStakeSaturation,
getMixnodeStatus,
minorToMajor,
getInclusionProbability,
} from '../../requests'
import { MixnodeStatus, InclusionProbabilityResponse } from '../../types'
} from '../../requests';
import { MixnodeStatus, InclusionProbabilityResponse } from '../../types';
export const useSettingsState = (shouldUpdate: boolean) => {
const [status, setStatus] = useState<MixnodeStatus>('not_found')
const [saturation, setSaturation] = useState<number>(0)
const [rewardEstimation, setRewardEstimation] = useState<number>(0)
const [status, setStatus] = useState<MixnodeStatus>('not_found');
const [saturation, setSaturation] = useState<number>(0);
const [rewardEstimation, setRewardEstimation] = useState<number>(0);
const [inclusionProbability, setInclusionProbability] = useState<InclusionProbabilityResponse>({
in_active: 0,
in_reserve: 0,
})
});
const { mixnodeDetails } = useContext(ClientContext)
const { mixnodeDetails } = useContext(ClientContext);
const getStatus = async (mixnodeKey: string) => {
const status = await getMixnodeStatus(mixnodeKey)
setStatus(status.status)
}
const newStatus = await getMixnodeStatus(mixnodeKey);
setStatus(newStatus.status);
};
const getStakeSaturation = async (mixnodeKey: string) => {
const saturation = await getMixnodeStakeSaturation(mixnodeKey)
const newSaturation = await getMixnodeStakeSaturation(mixnodeKey);
if (saturation) {
setSaturation(Math.round(saturation.saturation * 100))
if (newSaturation) {
setSaturation(Math.round(newSaturation.saturation * 100));
}
}
};
const getRewardEstimation = async (mixnodeKey: string) => {
const rewardEstimation = await getMixnodeRewardEstimation(mixnodeKey)
if (rewardEstimation) {
const toMajor = await minorToMajor(rewardEstimation.estimated_total_node_reward.toString())
setRewardEstimation(parseInt(toMajor.amount))
const newRewardEstimation = await getMixnodeRewardEstimation(mixnodeKey);
if (newRewardEstimation) {
const toMajor = await minorToMajor(newRewardEstimation.estimated_total_node_reward.toString());
setRewardEstimation(parseInt(toMajor.amount, Number(10)));
}
}
};
const getMixnodeInclusionProbability = async (mixnodeKey: string) => {
const probability = await getInclusionProbability(mixnodeKey)
const probability = await getInclusionProbability(mixnodeKey);
if (probability) {
const in_active = Math.round(probability.in_active * 100)
const in_reserve = Math.round(probability.in_reserve * 100)
setInclusionProbability({ in_active, in_reserve })
// eslint-disable-next-line @typescript-eslint/naming-convention
const in_active = Math.round(probability.in_active * 100);
// eslint-disable-next-line @typescript-eslint/naming-convention
const in_reserve = Math.round(probability.in_reserve * 100);
setInclusionProbability({ in_active, in_reserve });
}
}
};
const reset = () => {
setStatus('not_found')
setSaturation(0)
setRewardEstimation(0)
setInclusionProbability({ in_active: 0, in_reserve: 0 })
}
setStatus('not_found');
setSaturation(0);
setRewardEstimation(0);
setInclusionProbability({ in_active: 0, in_reserve: 0 });
};
useEffect(() => {
if (shouldUpdate && mixnodeDetails?.mix_node.identity_key) {
getStatus(mixnodeDetails?.mix_node.identity_key)
getStakeSaturation(mixnodeDetails?.mix_node.identity_key)
getRewardEstimation(mixnodeDetails?.mix_node.identity_key)
getMixnodeInclusionProbability(mixnodeDetails?.mix_node.identity_key)
getStatus(mixnodeDetails?.mix_node.identity_key);
getStakeSaturation(mixnodeDetails?.mix_node.identity_key);
getRewardEstimation(mixnodeDetails?.mix_node.identity_key);
getMixnodeInclusionProbability(mixnodeDetails?.mix_node.identity_key);
} else {
reset()
reset();
}
}, [shouldUpdate])
}, [shouldUpdate]);
return {
status,
saturation,
rewardEstimation,
inclusionProbability,
}
}
};
};
@@ -1,5 +1,5 @@
import * as Yup from 'yup'
import * as Yup from 'yup';
export const validationSchema = Yup.object({
profitMarginPercent: Yup.number().typeError('profit margin percent must be a number').min(0).max(100).required(),
})
});
+23 -23
View File
@@ -1,22 +1,22 @@
import React, { useContext, useEffect, useState } from 'react'
import { Alert, Box, Button, CircularProgress } from '@mui/material'
import { Fee, NymCard } from '../../components'
import { useCheckOwnership } from '../../hooks/useCheckOwnership'
import { ClientContext } from '../../context/main'
import { unbond } from '../../requests'
import { PageLayout } from '../../layouts'
import React, { useContext, useEffect, useState } from 'react';
import { Alert, Box, Button, CircularProgress } from '@mui/material';
import { Fee, NymCard } from '../../components';
import { useCheckOwnership } from '../../hooks/useCheckOwnership';
import { ClientContext } from '../../context/main';
import { unbond } from '../../requests';
import { PageLayout } from '../../layouts';
export const Unbond = () => {
const [isLoading, setIsLoading] = useState(false)
const { checkOwnership, ownership } = useCheckOwnership()
const { userBalance, getBondDetails } = useContext(ClientContext)
const [isLoading, setIsLoading] = useState(false);
const { checkOwnership, ownership } = useCheckOwnership();
const { userBalance, getBondDetails } = useContext(ClientContext);
useEffect(() => {
const initialiseForm = async () => {
await checkOwnership()
}
initialiseForm()
}, [ownership.hasOwnership, checkOwnership])
await checkOwnership();
};
initialiseForm();
}, [ownership.hasOwnership, checkOwnership]);
return (
<PageLayout>
@@ -31,12 +31,12 @@ export const Unbond = () => {
data-testid="un-bond"
disabled={isLoading}
onClick={async () => {
setIsLoading(true)
await unbond(ownership.nodeType!)
await userBalance.fetchBalance()
await getBondDetails()
await checkOwnership()
setIsLoading(false)
setIsLoading(true);
await unbond(ownership.nodeType!);
await userBalance.fetchBalance();
await getBondDetails();
await checkOwnership();
setIsLoading(false);
}}
color="inherit"
>
@@ -54,7 +54,7 @@ export const Unbond = () => {
</>
) : (
<Alert severity="info" sx={{ m: 3 }} data-testid="no-bond">
You don't currently have a bonded node
You do not currently have a bonded node
</Alert>
)}
{isLoading && (
@@ -71,5 +71,5 @@ export const Unbond = () => {
)}
</NymCard>
</PageLayout>
)
}
);
};
@@ -1,33 +1,32 @@
import React, { useContext, useEffect } from 'react'
import { useForm, Controller } from 'react-hook-form'
import { Box, Autocomplete, Button, CircularProgress, FormControl, Grid, TextField, Typography } from '@mui/material'
import { yupResolver } from '@hookform/resolvers/yup'
import { validationSchema } from './validationSchema'
import { EnumNodeType, TDelegation, TFee } from '../../types'
import { ClientContext } from '../../context/main'
import { undelegate } from '../../requests'
import { Fee } from '../../components'
import React, { useContext, useEffect } from 'react';
import { Controller, useForm } from 'react-hook-form';
import { Autocomplete, Box, Button, CircularProgress, FormControl, Grid, TextField } from '@mui/material';
import { yupResolver } from '@hookform/resolvers/yup';
import { validationSchema } from './validationSchema';
import { EnumNodeType, TDelegation, TFee } from '../../types';
import { ClientContext } from '../../context/main';
import { undelegate } from '../../requests';
import { Fee } from '../../components';
type TFormData = {
nodeType: EnumNodeType
identity: string
}
nodeType: EnumNodeType;
identity: string;
};
const defaultValues = {
nodeType: EnumNodeType.mixnode,
identity: '',
}
};
export const UndelegateForm = ({
fees,
delegations,
onError,
onSuccess,
}: {
fees: TFee
delegations?: TDelegation[]
onError: (message?: string) => void
onSuccess: (message?: string) => void
fees: TFee;
delegations?: TDelegation[];
onError: (message?: string) => void;
onSuccess: (message?: string) => void;
}) => {
const {
control,
@@ -38,14 +37,14 @@ export const UndelegateForm = ({
} = useForm<TFormData>({
defaultValues,
resolver: yupResolver(validationSchema),
})
const watchNodeType = watch('nodeType')
});
const watchNodeType = watch('nodeType');
useEffect(() => {
setValue('identity', '')
}, [watchNodeType])
setValue('identity', '');
}, [watchNodeType]);
const { userBalance } = useContext(ClientContext)
const { userBalance } = useContext(ClientContext);
const onSubmit = async (data: TFormData) => {
await undelegate({
@@ -53,11 +52,11 @@ export const UndelegateForm = ({
identity: data.identity,
})
.then(async (res) => {
onSuccess(`Successfully undelegated from ${res.target_address}`)
userBalance.fetchBalance()
onSuccess(`Successfully undelegated from ${res.target_address}`);
userBalance.fetchBalance();
})
.catch((e) => onError(e))
}
.catch((e) => onError(e));
};
return (
<FormControl fullWidth>
@@ -118,5 +117,5 @@ export const UndelegateForm = ({
</Button>
</Box>
</FormControl>
)
}
);
};
+37 -38
View File
@@ -1,48 +1,47 @@
import React, { useContext, useEffect, useState } from 'react'
import { Alert, AlertTitle, Box, Button, CircularProgress } from '@mui/material'
import { NymCard } from '../../components'
import { UndelegateForm } from './UndelegateForm'
import { EnumRequestStatus, RequestStatus } from '../../components/RequestStatus'
import { getGasFee, getReverseMixDelegations } from '../../requests'
import { TFee, TPagedDelegations } from '../../types'
import { ClientContext } from '../../context/main'
import { PageLayout } from '../../layouts'
import React, { useContext, useEffect, useState } from 'react';
import { Alert, AlertTitle, Box, Button, CircularProgress } from '@mui/material';
import { EnumRequestStatus, NymCard, RequestStatus } from '../../components';
import { UndelegateForm } from './UndelegateForm';
import { getGasFee, getReverseMixDelegations } from '../../requests';
import { TFee, TPagedDelegations } from '../../types';
import { ClientContext } from '../../context/main';
import { PageLayout } from '../../layouts';
export const Undelegate = () => {
const [message, setMessage] = useState<string>()
const [status, setStatus] = useState<EnumRequestStatus>(EnumRequestStatus.initial)
const [isLoading, setIsLoading] = useState(true)
const [fees, setFees] = useState<TFee>()
const [pagedDelegations, setPagesDelegations] = useState<TPagedDelegations>()
const [message, setMessage] = useState<string>();
const [status, setStatus] = useState<EnumRequestStatus>(EnumRequestStatus.initial);
const [isLoading, setIsLoading] = useState(true);
const [fees, setFees] = useState<TFee>();
const [pagedDelegations, setPagesDelegations] = useState<TPagedDelegations>();
const { clientDetails } = useContext(ClientContext)
useEffect(() => {
initialize()
}, [clientDetails])
const { clientDetails } = useContext(ClientContext);
const initialize = async () => {
setStatus(EnumRequestStatus.initial)
setIsLoading(true)
setStatus(EnumRequestStatus.initial);
setIsLoading(true);
try {
const [mixnodeFee, mixnodeDelegations] = await Promise.all([
getGasFee('UndelegateFromMixnode'),
getReverseMixDelegations(),
])
]);
setFees({
mixnode: mixnodeFee,
})
});
setPagesDelegations(mixnodeDelegations)
setPagesDelegations(mixnodeDelegations);
} catch (e) {
setStatus(EnumRequestStatus.error)
setMessage(e as string)
setStatus(EnumRequestStatus.error);
setMessage(e as string);
}
setIsLoading(false)
}
setIsLoading(false);
};
useEffect(() => {
initialize();
}, [clientDetails]);
return (
<PageLayout>
@@ -63,13 +62,13 @@ export const Undelegate = () => {
<UndelegateForm
fees={fees}
delegations={pagedDelegations?.delegations}
onError={(message) => {
setMessage(message)
setStatus(EnumRequestStatus.error)
onError={(m) => {
setMessage(m);
setStatus(EnumRequestStatus.error);
}}
onSuccess={(message) => {
setMessage(message)
setStatus(EnumRequestStatus.success)
onSuccess={(m) => {
setMessage(m);
setStatus(EnumRequestStatus.success);
}}
/>
)}
@@ -104,8 +103,8 @@ export const Undelegate = () => {
variant="contained"
disableElevation
onClick={() => {
setStatus(EnumRequestStatus.initial)
initialize()
setStatus(EnumRequestStatus.initial);
initialize();
}}
size="large"
>
@@ -117,5 +116,5 @@ export const Undelegate = () => {
</>
</NymCard>
</PageLayout>
)
}
);
};
@@ -1,10 +1,8 @@
import * as Yup from 'yup'
import { validateKey } from '../../utils'
import * as Yup from 'yup';
import { validateKey } from '../../utils';
export const validationSchema = Yup.object().shape({
identity: Yup.string()
.required()
.test('valid-id-key', 'A valid identity key is required', function (value) {
return validateKey(value || '', 32)
}),
})
.test('valid-id-key', 'A valid identity key is required', (value) => validateKey(value || '', 32)),
});
@@ -1,28 +1,28 @@
import React, { useEffect, useState } from 'react'
import { Alert, Button, Card, CardActions, CardContent, CardHeader, Stack, Typography } from '@mui/material'
import { createAccount } from '../../requests'
import { TCreateAccount } from '../../types'
import { CopyToClipboard } from '../../components'
/* eslint-disable react/no-unused-prop-types */
import React, { useEffect, useState } from 'react';
import { Alert, Button, Card, CardActions, CardContent, CardHeader, Stack, Typography } from '@mui/material';
import { createAccount } from '../../requests';
import { TCreateAccount } from '../../types';
import { CopyToClipboard } from '../../components';
import { TPages } from './types';
export const CreateAccountContent: React.FC<{ page: 'legacy create account'; showSignIn: () => void }> = ({
showSignIn,
}) => {
const [accountDetails, setAccountDetails] = useState<TCreateAccount>()
const [error, setError] = useState<Error>()
export const CreateAccountContent: React.FC<{ page: TPages; showSignIn: () => void }> = ({ page, showSignIn }) => {
const [accountDetails, setAccountDetails] = useState<TCreateAccount>();
const [error, setError] = useState<Error>();
const handleCreateAccount = async () => {
setError(undefined)
setError(undefined);
try {
const account = await createAccount()
setAccountDetails(account)
const account = await createAccount();
setAccountDetails(account);
} catch (e: any) {
setError(e)
setError(e);
}
}
};
useEffect(() => {
handleCreateAccount()
}, [])
handleCreateAccount();
}, []);
return (
<Stack spacing={4} alignItems="center" sx={{ width: 700 }}>
@@ -33,7 +33,7 @@ export const CreateAccountContent: React.FC<{ page: 'legacy create account'; sho
Account setup complete!
</Typography>
<Alert severity="info" variant="outlined" sx={{ color: 'info.light' }} data-testid="mnemonic-warning">
<Typography>Please store your mnemonic in a safe place. You'll need it to access your account!</Typography>
<Typography>Please store your mnemonic in a safe place. You will need it to access your account!</Typography>
</Alert>
<Card variant="outlined" sx={{ bgcolor: 'transparent', p: 2, borderColor: 'common.white' }}>
<CardHeader sx={{ color: 'common.white' }} title="Mnemonic" />
@@ -53,5 +53,5 @@ export const CreateAccountContent: React.FC<{ page: 'legacy create account'; sho
Sign in
</Button>
</Stack>
)
}
);
};
@@ -1,32 +1,29 @@
import React, { useContext, useState } from 'react'
import { Button, CircularProgress, Grid, Stack, TextField, Typography, Alert } from '@mui/material'
import { styled } from '@mui/material/styles'
import { signInWithMnemonic } from '../../requests'
import { ClientContext } from '../../context/main'
import { NymLogo } from '../../components'
import React, { useContext, useState } from 'react';
import { Alert, Button, CircularProgress, Grid, Stack, Typography } from '@mui/material';
import { ClientContext } from '../../context/main';
import { NymLogo } from '../../components';
export const SignInContent: React.FC<{ showCreateAccount: () => void }> = ({ showCreateAccount }) => {
const [mnemonic, setMnemonic] = useState<string>('')
const [inputError, setInputError] = useState<string>()
const [isLoading, setIsLoading] = useState(false)
export const SignInContent: React.FC = () => {
const [mnemonic] = useState<string>('');
const [inputError, setInputError] = useState<string>();
const [isLoading, setIsLoading] = useState(false);
const { logIn } = useContext(ClientContext)
const { logIn } = useContext(ClientContext);
const handleSignIn = async (e: React.MouseEvent<HTMLElement>) => {
e.preventDefault()
e.preventDefault();
setIsLoading(true)
setInputError(undefined)
setIsLoading(true);
setInputError(undefined);
try {
await signInWithMnemonic(mnemonic || '')
setIsLoading(false)
logIn()
} catch (e: any) {
setIsLoading(false)
setInputError(e)
await logIn(mnemonic || '');
setIsLoading(false);
} catch (error: any) {
setIsLoading(false);
setInputError(error);
}
}
};
return (
<Stack spacing={3} alignItems="center" sx={{ width: '80%' }}>
@@ -65,25 +62,5 @@ export const SignInContent: React.FC<{ showCreateAccount: () => void }> = ({ sho
)}
</Grid>
</Stack>
)
}
const StyledInput = styled((props) => <TextField {...props} />)(({ theme }) => ({
'& input': {
color: theme.palette.nym.text.light,
},
'& label': {
color: theme.palette.nym.text.light,
},
'& label.Mui-focused': {
color: theme.palette.primary.main,
},
'& .MuiOutlinedInput-root': {
'& fieldset': {
borderColor: theme.palette.common.white,
},
'&:hover fieldset': {
borderColor: theme.palette.primary.main,
},
},
}))
);
};
@@ -1,16 +1,16 @@
import React from 'react'
import { Typography } from '@mui/material'
import React from 'react';
import { Typography } from '@mui/material';
export const Title = ({ title }: { title: string }) => (
<Typography sx={{ color: 'common.white', fontWeight: 600 }}>{title}</Typography>
)
);
export const Subtitle = ({ subtitle }: { subtitle: string }) => (
<Typography sx={{ color: 'common.white', textAlign: 'center', maxWidth: 400 }}>{subtitle}</Typography>
)
);
export const SubtitleSlick = ({ subtitle }: { subtitle: string }) => (
<Typography variant="caption" sx={{ color: 'grey.600', textTransform: 'uppercase', letterSpacing: 4 }}>
{subtitle}
</Typography>
)
);
@@ -1,4 +1,4 @@
export * from './heading'
export * from './word-tiles'
export * from './render-page'
export * from './password-strength'
export * from './heading';
export * from './word-tiles';
export * from './render-page';
export * from './password-strength';
@@ -1,64 +1,65 @@
import React, { useEffect, useState } from 'react'
import { LockOutlined } from '@mui/icons-material'
import { LinearProgress, Stack, Typography } from '@mui/material'
import { Box } from '@mui/system'
import React, { useEffect, useState } from 'react';
import { LockOutlined } from '@mui/icons-material';
import { LinearProgress, Stack, Typography, Box } from '@mui/material';
type TStrength = 'weak' | 'medium' | 'strong' | 'init'
type TStrength = 'weak' | 'medium' | 'strong' | 'init';
const strong = /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#\$%\^&\*])(?=.{8,})/
const medium = /^(((?=.*[a-z])(?=.*[A-Z]))|((?=.*[a-z])(?=.*[0-9]))|((?=.*[A-Z])(?=.*[0-9])))(?=.{6,})/
const strong = /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*])(?=.{8,})/;
const medium = /^(((?=.*[a-z])(?=.*[A-Z]))|((?=.*[a-z])(?=.*[0-9]))|((?=.*[A-Z])(?=.*[0-9])))(?=.{6,})/;
const colorMap = {
init: "inherit" as "inherit",
init: 'inherit' as 'inherit',
weak: 'error' as 'error',
medium: 'warning' as 'warning',
strong: 'success' as 'success',
}
};
const getText = (strength: TStrength) => {
switch (strength) {
case 'strong':
return 'Strong password'
return 'Strong password';
case 'medium':
return 'Medium strength password'
return 'Medium strength password';
case 'weak':
return 'Weak password'
return 'Weak password';
default:
return 'Password strength'
return 'Password strength';
}
}
};
const getTextColor = (strength: TStrength) => {
switch (strength) {
case 'strong':
return 'success.main'
return 'success.main';
case 'medium':
return 'warning.main'
return 'warning.main';
case 'weak':
return 'error.main'
return 'error.main';
default:
return 'grey.500'
return 'grey.500';
}
}
};
export const PasswordStrength = ({ password }: { password: string }) => {
const [strength, setStrength] = useState<TStrength>('init')
const [strength, setStrength] = useState<TStrength>('init');
useEffect(() => {
if (password.length === 0) {
return setStrength('init')
setStrength('init');
return;
}
if (password.match(strong)) {
return setStrength('strong')
setStrength('strong');
return;
}
if (password.match(medium)) {
return setStrength('medium')
setStrength('medium');
return;
}
setStrength('weak')
}, [password])
setStrength('weak');
}, [password]);
return (
<Stack spacing={0.5}>
@@ -68,11 +69,11 @@ export const PasswordStrength = ({ password }: { password: string }) => {
value={strength === 'strong' ? 100 : strength === 'medium' ? 50 : 0}
/>
<Box display="flex" alignItems="center">
<LockOutlined sx={{ fontSize: 15, color: getTextColor(strength) }} />
<LockOutlined sx={{ fontSize: 15, color: getTextColor(strength) }} />
<Typography variant="caption" sx={{ ml: 0.5, color: getTextColor(strength) }}>
{getText(strength)}
</Typography>
</Box>
</Stack>
)
}
);
};
@@ -1,11 +1,11 @@
import React from 'react'
import { TPages } from '../types'
import React from 'react';
import { TPages } from '../types';
export const RenderPage = ({ children, page }: { children: React.ReactElement[]; page: TPages }) => (
<>
{React.Children.map(children, (Child) => {
if (page === Child?.props.page) return Child
return null
if (page === Child?.props.page) return Child;
return null;
})}
</>
)
);
@@ -1,30 +1,6 @@
import React from 'react'
import { Box, Card, CardHeader, Grid, Typography, Stack, Fade } from '@mui/material'
import { THiddenMnemonicWord, THiddenMnemonicWords, TMnemonicWords } from '../types'
export const WordTiles = ({
mnemonicWords,
showIndex,
onClick,
}: {
mnemonicWords?: TMnemonicWords
showIndex?: boolean
onClick?: ({ name, index }: { name: string; index: number }) => void
}) => {
if (mnemonicWords) {
return (
<Grid container spacing={3} justifyContent="center">
{mnemonicWords.map(({ name, index, disabled }) => (
<Grid item xs={2} key={index} onClick={() => onClick?.({ name, index })}>
<WordTile mnemonicWord={name} index={showIndex ? index : undefined} onClick={!!onClick} disabled={disabled}/>
</Grid>
))}
</Grid>
)
}
return null
}
import React from 'react';
import { Box, Card, CardHeader, Grid, Typography, Stack, Fade } from '@mui/material';
import { THiddenMnemonicWord, THiddenMnemonicWords, TMnemonicWords } from '../types';
export const WordTile = ({
mnemonicWord,
@@ -32,10 +8,10 @@ export const WordTile = ({
disabled,
onClick,
}: {
mnemonicWord: string
index?: number
disabled?: boolean
onClick?: boolean
mnemonicWord: string;
index?: number;
disabled?: boolean;
onClick?: boolean;
}) => (
<Card
variant="outlined"
@@ -51,14 +27,56 @@ export const WordTile = ({
titleTypographyProps={{ sx: { fontWeight: 700 }, variant: 'body1', textAlign: index ? 'left' : 'center' }}
avatar={
index && (
<Typography variant="caption" color={'#3A4053'}>
<Typography variant="caption" color="#3A4053">
{index}
</Typography>
)
}
/>
</Card>
)
);
export const WordTiles = ({
mnemonicWords,
showIndex,
onClick,
}: {
mnemonicWords?: TMnemonicWords;
showIndex?: boolean;
onClick?: ({ name, index }: { name: string; index: number }) => void;
}) => {
if (mnemonicWords) {
return (
<Grid container spacing={3} justifyContent="center">
{mnemonicWords.map(({ name, index, disabled }) => (
<Grid item xs={2} key={index} onClick={() => onClick?.({ name, index })}>
<WordTile
mnemonicWord={name}
index={showIndex ? index : undefined}
onClick={!!onClick}
disabled={disabled}
/>
</Grid>
))}
</Grid>
);
}
return null;
};
const HiddenWord = ({ mnemonicWord }: { mnemonicWord: THiddenMnemonicWord }) => (
<Stack spacing={2} alignItems="center">
<Box borderBottom="1px solid #3A4053" sx={{ p: 2, width: '100%' }}>
<Fade in={!mnemonicWord.hidden}>
<Box>
<WordTile mnemonicWord={mnemonicWord.name} />
</Box>
</Fade>
</Box>
<Typography>{mnemonicWord.index}.</Typography>
</Stack>
);
export const HiddenWords = ({ mnemonicWords }: { mnemonicWords?: THiddenMnemonicWords }) => {
if (mnemonicWords) {
@@ -70,22 +88,7 @@ export const HiddenWords = ({ mnemonicWords }: { mnemonicWords?: THiddenMnemonic
</Grid>
))}
</Grid>
)
);
}
return null
}
const HiddenWord = ({ mnemonicWord }: { mnemonicWord: THiddenMnemonicWord }) => {
return (
<Stack spacing={2} alignItems="center">
<Box borderBottom="1px solid #3A4053" sx={{ p: 2, width: '100%' }}>
<Fade in={!mnemonicWord.hidden}>
<Box>
<WordTile mnemonicWord={mnemonicWord.name} />
</Box>
</Fade>
</Box>
<Typography>{mnemonicWord.index}.</Typography>
</Stack>
)
}
return null;
};
+21 -22
View File
@@ -1,26 +1,25 @@
import React, { useContext, useState } from 'react'
import { Box } from '@mui/system'
import { CircularProgress, Stack } from '@mui/material'
import { WelcomeContent, VerifyMnemonic, MnemonicWords, CreatePassword, ExistingAccount } from './pages'
import { NymLogo } from '../../components'
import { TMnemonicWords, TPages } from './types'
import { RenderPage } from './components'
import { CreateAccountContent } from './_legacy_create-account'
import { ClientContext } from '../../context/main'
import React, { useContext, useState } from 'react';
import { CircularProgress, Stack, Box } from '@mui/material';
import { ExistingAccount, WelcomeContent } from './pages';
import { NymLogo } from '../../components';
import { TPages } from './types';
import { RenderPage } from './components';
import { CreateAccountContent } from './_legacy_create-account';
import { ClientContext } from '../../context/main';
const testMnemonic =
'futuristic big receptive caption saw hug odd spoon internal dime bike rake helpless left distribution gusty eyes beg enormous word influence trashy pets curl'
const mnemonicToArray = (mnemonic: string): TMnemonicWords =>
mnemonic
.split(' ')
.reduce((a, c: string, index) => [...a, { name: c, index: index + 1, disabled: false }], [] as TMnemonicWords)
// const testMnemonic =
// 'futuristic big receptive caption saw hug odd spoon internal dime bike rake helpless left distribution gusty eyes beg enormous word influence trashy pets curl';
//
// const mnemonicToArray = (mnemonic: string): TMnemonicWords =>
// mnemonic
// .split(' ')
// .reduce((a, c: string, index) => [...a, { name: c, index: index + 1, disabled: false }], [] as TMnemonicWords);
export const Welcome = () => {
const [page, setPage] = useState<TPages>('welcome')
const [mnemonicWords, setMnemonicWords] = useState<TMnemonicWords>()
const [page, setPage] = useState<TPages>('welcome');
// const [mnemonicWords, setMnemonicWords] = useState<TMnemonicWords>();
const { isLoading } = useContext(ClientContext)
const { isLoading } = useContext(ClientContext);
// useEffect(() => {
// const mnemonicArray = mnemonicToArray(testMnemonic)
@@ -72,11 +71,11 @@ export const Welcome = () => {
page="verify mnemonic"
/>
<CreatePassword page="create password" /> */}
<ExistingAccount page="existing account" onPrev={() => setPage('welcome')} />
<ExistingAccount onPrev={() => setPage('welcome')} page="existing account" />
</RenderPage>
</Stack>
)}
</Box>
</Box>
)
}
);
};
@@ -1,13 +1,13 @@
import React, { useState } from 'react'
import { Button, FormControl, Grid, IconButton, Stack, TextField } from '@mui/material'
import { VisibilityOff, Visibility } from '@mui/icons-material'
import { Subtitle, Title, PasswordStrength } from '../components'
import React, { useState } from 'react';
import { Button, FormControl, Grid, IconButton, Stack, TextField } from '@mui/material';
import { VisibilityOff, Visibility } from '@mui/icons-material';
import { Subtitle, Title, PasswordStrength } from '../components';
export const CreatePassword = ({}: { page: 'create password' }) => {
const [password, setPassword] = useState<string>('')
const [confirmedPassword, setConfirmedPassword] = useState<string>()
const [showPassword, setShowPassword] = useState(false)
const [showConfirmedPassword, setShowConfirmedPassword] = useState(false)
export const CreatePassword = () => {
const [password, setPassword] = useState<string>('');
const [confirmedPassword, setConfirmedPassword] = useState<string>();
const [showPassword, setShowPassword] = useState(false);
const [showConfirmedPassword, setShowConfirmedPassword] = useState(false);
return (
<>
@@ -56,5 +56,5 @@ export const CreatePassword = ({}: { page: 'create password' }) => {
</Grid>
</Grid>
</>
)
}
);
};
@@ -1,16 +1,18 @@
import React, { useContext, useState } from 'react'
import { Alert, Button, Stack, TextField } from '@mui/material'
import { Subtitle } from '../components'
import { ClientContext } from '../../../context/main'
/* eslint-disable react/no-unused-prop-types */
import React, { useContext, useState } from 'react';
import { Alert, Button, Stack, TextField } from '@mui/material';
import { Subtitle } from '../components';
import { ClientContext } from '../../../context/main';
import { TPages } from '../types';
export const ExistingAccount: React.FC<{ page: 'existing account'; onPrev: () => void }> = ({ onPrev }) => {
const [mnemonic, setMnemonic] = useState<string>('')
export const ExistingAccount: React.FC<{ page: TPages; onPrev: () => void }> = ({ onPrev }) => {
const [mnemonic, setMnemonic] = useState<string>('');
const { logIn, error } = useContext(ClientContext)
const { logIn, error } = useContext(ClientContext);
const handleSignIn = async (e: React.MouseEvent<HTMLElement>) => {
e.preventDefault()
logIn(mnemonic)
}
e.preventDefault();
logIn(mnemonic);
};
return (
<Stack spacing={2} sx={{ width: 400 }} alignItems="center">
@@ -36,5 +38,5 @@ export const ExistingAccount: React.FC<{ page: 'existing account'; onPrev: () =>
Back
</Button>
</Stack>
)
}
);
};
+5 -5
View File
@@ -1,5 +1,5 @@
export * from './welcome-content'
export * from './mnemonic-words'
export * from './verify-mnemonic'
export * from './create-password'
export * from './existing-account'
export * from './welcome-content';
export * from './mnemonic-words';
export * from './verify-mnemonic';
export * from './create-password';
export * from './existing-account';
@@ -1,37 +1,34 @@
import React from 'react'
import { Alert, Button, Typography } from '@mui/material'
import { WordTiles } from '../components/word-tiles'
import { TMnemonicWords } from '../types'
import React from 'react';
import { Alert, Button, Typography } from '@mui/material';
import { WordTiles } from '../components';
import { TMnemonicWords } from '../types';
export const MnemonicWords = ({
mnemonicWords,
onNext,
onPrev,
}: {
mnemonicWords?: TMnemonicWords
page: 'create account'
onNext: () => void
onPrev: () => void
}) => {
return (
<>
<Typography sx={{ color: 'common.white', fontWeight: 600 }}>Write down your mnemonic</Typography>
<Alert icon={false} severity="info" sx={{ bgcolor: '#18263B', color: '#50ABFF', width: 625 }}>
Please store your mnemonic in a safe place. This is the only way to access your wallet!
</Alert>
<WordTiles mnemonicWords={mnemonicWords} showIndex />
<Button variant="contained" color="primary" disableElevation size="large" onClick={onNext} sx={{ width: 250 }}>
Verify mnemonic
</Button>
<Button
variant="outlined"
disableElevation
size="large"
onClick={onPrev}
sx={{ color: 'common.white', border: '1px solid white', '&:hover': { border: '1px solid white' }, width: 250 }}
>
Back
</Button>
</>
)
}
mnemonicWords?: TMnemonicWords;
onNext: () => void;
onPrev: () => void;
}) => (
<>
<Typography sx={{ color: 'common.white', fontWeight: 600 }}>Write down your mnemonic</Typography>
<Alert icon={false} severity="info" sx={{ bgcolor: '#18263B', color: '#50ABFF', width: 625 }}>
Please store your mnemonic in a safe place. This is the only way to access your wallet!
</Alert>
<WordTiles mnemonicWords={mnemonicWords} showIndex />
<Button variant="contained" color="primary" disableElevation size="large" onClick={onNext} sx={{ width: 250 }}>
Verify mnemonic
</Button>
<Button
variant="outlined"
disableElevation
size="large"
onClick={onPrev}
sx={{ color: 'common.white', border: '1px solid white', '&:hover': { border: '1px solid white' }, width: 250 }}
>
Back
</Button>
</>
);
@@ -1,45 +1,43 @@
import React, { useEffect, useState } from 'react'
import { Button } from '@mui/material'
import { WordTiles, HiddenWords } from '../components/word-tiles'
import { THiddenMnemonicWords, THiddenMnemonicWord, TMnemonicWord, TMnemonicWords } from '../types'
import { randomNumberBetween } from '../../../utils'
import { Title, Subtitle } from '../components'
import React, { useEffect, useState } from 'react';
import { Button } from '@mui/material';
import { HiddenWords, Subtitle, Title, WordTiles } from '../components';
import { THiddenMnemonicWord, THiddenMnemonicWords, TMnemonicWord, TMnemonicWords } from '../types';
import { randomNumberBetween } from '../../../utils';
const numberOfRandomWords = 4
const numberOfRandomWords = 4;
export const VerifyMnemonic = ({
mnemonicWords,
onComplete,
}: {
page: 'verify mnemonic'
mnemonicWords?: TMnemonicWords
onComplete: () => void
mnemonicWords?: TMnemonicWords;
onComplete: () => void;
}) => {
const [randomWords, setRandomWords] = useState<TMnemonicWords>()
const [hiddenRandomWords, setHiddenRandomWords] = useState<THiddenMnemonicWords>()
const [currentSelection, setCurrentSelection] = useState(0)
const [randomWords, setRandomWords] = useState<TMnemonicWords>();
const [hiddenRandomWords, setHiddenRandomWords] = useState<THiddenMnemonicWords>();
const [currentSelection, setCurrentSelection] = useState(0);
useEffect(() => {
if (mnemonicWords) {
const randomWords = getRandomEntriesFromArray<TMnemonicWord>(mnemonicWords, numberOfRandomWords)
const withHiddenProperty = randomWords.map((word) => ({ ...word, hidden: true }))
const shuffled = getRandomEntriesFromArray<THiddenMnemonicWord>(withHiddenProperty, numberOfRandomWords)
setRandomWords(randomWords)
setHiddenRandomWords(shuffled)
const newRandomWords = getRandomEntriesFromArray<TMnemonicWord>(mnemonicWords, numberOfRandomWords);
const withHiddenProperty = newRandomWords.map((word) => ({ ...word, hidden: true }));
const shuffled = getRandomEntriesFromArray<THiddenMnemonicWord>(withHiddenProperty, numberOfRandomWords);
setRandomWords(newRandomWords);
setHiddenRandomWords(shuffled);
}
}, [mnemonicWords])
}, [mnemonicWords]);
const revealWord = ({ name }: { name: string }) => {
if (name === hiddenRandomWords![currentSelection].name) {
setHiddenRandomWords((hiddenWords) =>
hiddenWords?.map((word) => (word.name === name ? { ...word, hidden: false } : word)),
)
setRandomWords((randomWords) =>
randomWords?.map((word) => (word.name === name ? { ...word, disabled: true } : word)),
)
setCurrentSelection((current) => current + 1)
);
setRandomWords((argRandomWords) =>
argRandomWords?.map((word) => (word.name === name ? { ...word, disabled: true } : word)),
);
setCurrentSelection((current) => current + 1);
}
}
};
if (randomWords && hiddenRandomWords) {
return (
@@ -61,20 +59,20 @@ export const VerifyMnemonic = ({
Next
</Button>
</>
)
);
}
return null
}
return null;
};
function getRandomEntriesFromArray<T>(arr: T[], numberOfEntries: number) {
const init = [...arr]
let randomEntries: T[] = []
const init = [...arr];
const randomEntries: T[] = [];
while (randomEntries.length !== numberOfEntries) {
const rand = randomNumberBetween(0, init.length - 1)
randomEntries.push(init[rand])
init.splice(rand, 1)
const rand = randomNumberBetween(0, init.length - 1);
randomEntries.push(init[rand]);
init.splice(rand, 1);
}
return randomEntries
return randomEntries;
}
@@ -1,42 +1,42 @@
import React from 'react'
import { Button, Stack } from '@mui/material'
import { SubtitleSlick, Title } from '../components'
/* eslint-disable react/no-unused-prop-types */
import React from 'react';
import { Button, Stack } from '@mui/material';
import { SubtitleSlick, Title } from '../components';
import { TPages } from '../types';
export const WelcomeContent: React.FC<{
page: 'welcome'
onUseExisting: () => void
onCreateAccountComplete: () => void
}> = ({ onUseExisting, onCreateAccountComplete }) => {
return (
<>
<Title title="Welcome to NYM" />
<SubtitleSlick subtitle="Next generation of privacy" />
<Stack spacing={3} sx={{ width: 300 }}>
<Button
fullWidth
variant="contained"
color="primary"
disableElevation
size="large"
onClick={onCreateAccountComplete}
>
Create Account
</Button>
<Button
fullWidth
variant="outlined"
size="large"
sx={{
color: 'common.white',
border: '1px solid white',
'&:hover': { border: '1px solid white', '&:hover': { background: 'none' } },
}}
onClick={onUseExisting}
disableRipple
>
Sign in
</Button>
</Stack>
</>
)
}
page: TPages;
onUseExisting: () => void;
onCreateAccountComplete: () => void;
}> = ({ onUseExisting, onCreateAccountComplete }) => (
<>
<Title title="Welcome to NYM" />
<SubtitleSlick subtitle="Next generation of privacy" />
<Stack spacing={3} sx={{ width: 300 }}>
<Button
fullWidth
variant="contained"
color="primary"
disableElevation
size="large"
onClick={onCreateAccountComplete}
>
Create Account
</Button>
<Button
fullWidth
variant="outlined"
size="large"
sx={{
color: 'common.white',
border: '1px solid white',
'&:hover': { border: '1px solid white', '&:hover': { background: 'none' } },
}}
onClick={onUseExisting}
disableRipple
>
Sign in
</Button>
</Stack>
</>
);
+8 -8
View File
@@ -5,15 +5,15 @@ export type TPages =
| 'create password'
| 'existing account'
| 'select network'
| 'legacy create account'
| 'legacy create account';
export type TMnemonicWord = {
name: string
index: number
disabled: boolean
}
export type TMnemonicWords = TMnemonicWord[]
name: string;
index: number;
disabled: boolean;
};
export type TMnemonicWords = TMnemonicWord[];
export type THiddenMnemonicWord = { hidden: boolean } & TMnemonicWord
export type THiddenMnemonicWord = { hidden: boolean } & TMnemonicWord;
export type THiddenMnemonicWords = THiddenMnemonicWord[]
export type THiddenMnemonicWords = THiddenMnemonicWord[];
+5 -5
View File
@@ -1,9 +1,9 @@
import { invoke } from '@tauri-apps/api'
import { Account, TCreateAccount } from '../types'
import { invoke } from '@tauri-apps/api';
import { Account, TCreateAccount } from '../types';
export const createAccount = async (): Promise<TCreateAccount> => await invoke('create_new_account')
export const createAccount = async (): Promise<TCreateAccount> => invoke('create_new_account');
export const signInWithMnemonic = async (mnemonic: string): Promise<Account> =>
await invoke('connect_with_mnemonic', { mnemonic })
invoke('connect_with_mnemonic', { mnemonic });
export const signOut = async () => await invoke('logout')
export const signOut = async () => invoke('logout');
+17 -17
View File
@@ -1,5 +1,5 @@
import { invoke } from '@tauri-apps/api'
import { Coin, DelegationResult, EnumNodeType, Gateway, MixNode, TauriTxResult } from '../types'
import { invoke } from '@tauri-apps/api';
import { Coin, DelegationResult, EnumNodeType, Gateway, MixNode, TauriTxResult } from '../types';
export const bond = async ({
type,
@@ -7,34 +7,34 @@ export const bond = async ({
pledge,
ownerSignature,
}: {
type: EnumNodeType
data: MixNode | Gateway
pledge: Coin
ownerSignature: string
}): Promise<any> => await invoke(`bond_${type}`, { [type]: data, ownerSignature, pledge })
type: EnumNodeType;
data: MixNode | Gateway;
pledge: Coin;
ownerSignature: string;
}): Promise<any> => invoke(`bond_${type}`, { [type]: data, ownerSignature, pledge });
export const unbond = async (type: EnumNodeType) => await invoke(`unbond_${type}`)
export const unbond = async (type: EnumNodeType) => invoke(`unbond_${type}`);
export const delegate = async ({
type,
identity,
amount,
}: {
type: EnumNodeType
identity: string
amount: Coin
}): Promise<DelegationResult> => await invoke(`delegate_to_${type}`, { identity, amount })
type: EnumNodeType;
identity: string;
amount: Coin;
}): Promise<DelegationResult> => invoke(`delegate_to_${type}`, { identity, amount });
export const undelegate = async ({
type,
identity,
}: {
type: EnumNodeType
identity: string
}): Promise<DelegationResult> => await invoke(`undelegate_from_${type}`, { identity })
type: EnumNodeType;
identity: string;
}): Promise<DelegationResult> => invoke(`undelegate_from_${type}`, { identity });
export const send = async (args: { amount: Coin; address: string; memo: string }): Promise<TauriTxResult> =>
await invoke('send', args)
invoke('send', args);
export const updateMixnode = async ({ profitMarginPercent }: { profitMarginPercent: number }) =>
await invoke('update_mixnode', { profitMarginPercent })
invoke('update_mixnode', { profitMarginPercent });
+4 -4
View File
@@ -1,6 +1,6 @@
import { invoke } from '@tauri-apps/api'
import { Coin } from '../types'
import { invoke } from '@tauri-apps/api';
import { Coin } from '../types';
export const minorToMajor = async (amount: string): Promise<Coin> => await invoke('minor_to_major', { amount })
export const minorToMajor = async (amount: string): Promise<Coin> => invoke('minor_to_major', { amount });
export const majorToMinor = async (amount: string): Promise<Coin> => await invoke('major_to_minor', { amount })
export const majorToMinor = async (amount: string): Promise<Coin> => invoke('major_to_minor', { amount });
+4 -4
View File
@@ -1,7 +1,7 @@
import { invoke } from '@tauri-apps/api'
import { TauriContractStateParams } from '../types'
import { invoke } from '@tauri-apps/api';
import { TauriContractStateParams } from '../types';
export const getContractParams = async (): Promise<TauriContractStateParams> => await invoke('get_contract_settings')
export const getContractParams = async (): Promise<TauriContractStateParams> => invoke('get_contract_settings');
export const setContractParams = async (params: TauriContractStateParams): Promise<TauriContractStateParams> =>
await invoke('update_contract_settings', { params })
invoke('update_contract_settings', { params });
+7 -7
View File
@@ -1,7 +1,7 @@
export * from './account'
export * from './actions'
export * from './coin'
export * from './contract'
export * from './vesting'
export * from './network'
export * from './queries'
export * from './account';
export * from './actions';
export * from './coin';
export * from './contract';
export * from './vesting';
export * from './network';
export * from './queries';
+3 -3
View File
@@ -1,4 +1,4 @@
import { invoke } from '@tauri-apps/api'
import { Account, Network } from '../types'
import { invoke } from '@tauri-apps/api';
import { Account, Network } from '../types';
export const selectNetwork = async (network: Network): Promise<Account> => await invoke('switch_network', { network })
export const selectNetwork = async (network: Network): Promise<Account> => invoke('switch_network', { network });
+13 -14
View File
@@ -1,4 +1,4 @@
import { invoke } from '@tauri-apps/api'
import { invoke } from '@tauri-apps/api';
import {
Balance,
Coin,
@@ -9,36 +9,35 @@ import {
StakeSaturationResponse,
TMixnodeBondDetails,
TPagedDelegations,
} from '../types'
} from '../types';
export const getReverseMixDelegations = async (): Promise<TPagedDelegations> =>
await invoke('get_reverse_mix_delegations_paged')
invoke('get_reverse_mix_delegations_paged');
export const getReverseGatewayDelegations = async (): Promise<TPagedDelegations> =>
await invoke('get_reverse_gateway_delegations_paged')
invoke('get_reverse_gateway_delegations_paged');
export const getMixnodeBondDetails = async (): Promise<TMixnodeBondDetails | null> =>
await invoke('mixnode_bond_details')
export const getMixnodeBondDetails = async (): Promise<TMixnodeBondDetails | null> => invoke('mixnode_bond_details');
export const getMixnodeStakeSaturation = async (identity: string): Promise<StakeSaturationResponse> =>
await invoke('mixnode_stake_saturation', { identity })
invoke('mixnode_stake_saturation', { identity });
export const getMixnodeRewardEstimation = async (identity: string): Promise<RewardEstimationResponse> =>
await invoke('mixnode_reward_estimation', { identity })
invoke('mixnode_reward_estimation', { identity });
export const getMixnodeStatus = async (identity: string): Promise<MixnodeStatusResponse> =>
await invoke('mixnode_status', { identity })
invoke('mixnode_status', { identity });
export const checkMixnodeOwnership = async (): Promise<boolean> => await invoke('owns_mixnode')
export const checkMixnodeOwnership = async (): Promise<boolean> => invoke('owns_mixnode');
export const checkGatewayOwnership = async (): Promise<boolean> => await invoke('owns_gateway')
export const checkGatewayOwnership = async (): Promise<boolean> => invoke('owns_gateway');
// NOTE: this uses OUTDATED defaults that might have no resemblance with the reality
// as for the actual transaction, the gas cost is being simulated beforehand
export const getGasFee = async (operation: Operation): Promise<Coin> =>
await invoke('outdated_get_approximate_fee', { operation })
invoke('outdated_get_approximate_fee', { operation });
export const getInclusionProbability = async (identity: string): Promise<InclusionProbabilityResponse> =>
await invoke('mixnode_inclusion_probability', { identity })
invoke('mixnode_inclusion_probability', { identity });
export const userBalance = async (): Promise<Balance> => await invoke('get_balance')
export const userBalance = async (): Promise<Balance> => invoke('get_balance');
+29 -29
View File
@@ -1,51 +1,51 @@
import { invoke } from '@tauri-apps/api'
import { VestingAccountInfo } from 'src/types/rust/vestingaccountinfo'
import { majorToMinor, minorToMajor } from '.'
import { Coin, DelegationResult, OriginalVestingResponse, Period } from '../types'
import { invoke } from '@tauri-apps/api';
import { VestingAccountInfo } from 'src/types/rust/vestingaccountinfo';
import { majorToMinor, minorToMajor } from './coin';
import { Coin, DelegationResult, OriginalVestingResponse, Period } from '../types';
export const getLockedCoins = async (address: string): Promise<Coin> => {
const res: Coin = await invoke('locked_coins', { address })
return await minorToMajor(res.amount)
}
const res: Coin = await invoke('locked_coins', { address });
return minorToMajor(res.amount);
};
export const getSpendableCoins = async (vestingAccountAddress?: string): Promise<Coin> => {
const res: Coin = await invoke('spendable_coins', { vestingAccountAddress })
return await minorToMajor(res.amount)
}
const res: Coin = await invoke('spendable_coins', { vestingAccountAddress });
return minorToMajor(res.amount);
};
export const getVestingCoins = async (vestingAccountAddress: string): Promise<Coin> => {
const res: Coin = await invoke('vesting_coins', { vestingAccountAddress })
return await minorToMajor(res.amount)
}
const res: Coin = await invoke('vesting_coins', { vestingAccountAddress });
return minorToMajor(res.amount);
};
export const getVestedCoins = async (vestingAccountAddress: string): Promise<Coin> => {
const res: Coin = await invoke('vested_coins', { vestingAccountAddress })
return await minorToMajor(res.amount)
}
const res: Coin = await invoke('vested_coins', { vestingAccountAddress });
return minorToMajor(res.amount);
};
export const getOriginalVesting = async (vestingAccountAddress: string): Promise<OriginalVestingResponse> => {
const res: OriginalVestingResponse = await invoke('original_vesting', { vestingAccountAddress })
const majorValue = await minorToMajor(res.amount.amount)
return { ...res, amount: majorValue }
}
const res: OriginalVestingResponse = await invoke('original_vesting', { vestingAccountAddress });
const majorValue = await minorToMajor(res.amount.amount);
return { ...res, amount: majorValue };
};
export const withdrawVestedCoins = async (amount: string) => {
const minor = await majorToMinor(amount)
await invoke('withdraw_vested_coins', { amount: { amount: minor.amount, denom: 'Minor' } })
}
const minor = await majorToMinor(amount);
await invoke('withdraw_vested_coins', { amount: { amount: minor.amount, denom: 'Minor' } });
};
export const getCurrentVestingPeriod = async (address: string): Promise<Period> =>
await invoke('get_current_vesting_period', { address })
invoke('get_current_vesting_period', { address });
export const vestingDelegateToMixnode = async ({
identity,
amount,
}: {
identity: string
amount: Coin
}): Promise<DelegationResult> => await invoke('vesting_delegate_to_mixnode', { identity, amount })
identity: string;
amount: Coin;
}): Promise<DelegationResult> => invoke('vesting_delegate_to_mixnode', { identity, amount });
export const vestingUnelegateFromMixnode = async (identity: string): Promise<DelegationResult> =>
await invoke('vesting_delegate_to_mixnode', { identity })
invoke('vesting_delegate_to_mixnode', { identity });
export const getVestingAccountInfo = async (address: string): Promise<VestingAccountInfo> =>
await invoke('get_account_info', { address })
invoke('get_account_info', { address });
+5 -5
View File
@@ -1,7 +1,7 @@
import React from 'react'
import { Switch, Route } from 'react-router-dom'
import { Balance } from '../pages/balance'
import { Bond, Delegate, InternalDocs, Receive, Send, Unbond, Undelegate } from '../pages'
import React from 'react';
import { Switch, Route } from 'react-router-dom';
import { Balance } from '../pages/balance';
import { Bond, Delegate, InternalDocs, Receive, Send, Unbond, Undelegate } from '../pages';
export const Routes = () => (
<Switch>
@@ -30,4 +30,4 @@ export const Routes = () => (
<InternalDocs />
</Route>
</Switch>
)
);
+21 -23
View File
@@ -1,24 +1,22 @@
import React from 'react'
import { SvgIcon, SvgIconProps } from '@mui/material'
import React from 'react';
import { SvgIcon, SvgIconProps } from '@mui/material';
export const Bond = (props: SvgIconProps) => {
return (
<SvgIcon {...props}>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M16 17C14.8954 17 14 16.1046 14 15L14 9C14 7.89543 14.8954 7 16 7L22 7C23.1046 7 24 7.89543 24 9L24 15C24 16.1046 23.1046 17 22 17L16 17ZM16 9L16 15L22 15L22 9L16 9Z"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M2 17C0.89543 17 -1.00647e-07 16.1046 -2.24801e-07 15L-8.99206e-07 9C-1.02336e-06 7.89543 0.895429 7 2 7L8 7C9.10457 7 10 7.89543 10 9L10 15C10 16.1046 9.10457 17 8 17L2 17ZM2 9L2 15L8 15L8 9L2 9Z"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M6 12C6 11.4477 6.44772 11 7 11H17C17.5523 11 18 11.4477 18 12C18 12.5523 17.5523 13 17 13H7C6.44772 13 6 12.5523 6 12Z"
/>
</SvgIcon>
)
}
export const Bond = (props: SvgIconProps) => (
<SvgIcon {...props}>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M16 17C14.8954 17 14 16.1046 14 15L14 9C14 7.89543 14.8954 7 16 7L22 7C23.1046 7 24 7.89543 24 9L24 15C24 16.1046 23.1046 17 22 17L16 17ZM16 9L16 15L22 15L22 9L16 9Z"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M2 17C0.89543 17 -1.00647e-07 16.1046 -2.24801e-07 15L-8.99206e-07 9C-1.02336e-06 7.89543 0.895429 7 2 7L8 7C9.10457 7 10 7.89543 10 9L10 15C10 16.1046 9.10457 17 8 17L2 17ZM2 9L2 15L8 15L8 9L2 9Z"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M6 12C6 11.4477 6.44772 11 7 11H17C17.5523 11 18 11.4477 18 12C18 12.5523 17.5523 13 17 13H7C6.44772 13 6 12.5523 6 12Z"
/>
</SvgIcon>
);
+11 -13
View File
@@ -1,14 +1,12 @@
import React from 'react'
import { SvgIcon, SvgIconProps } from '@mui/material'
import React from 'react';
import { SvgIcon, SvgIconProps } from '@mui/material';
export const Delegate = (props: SvgIconProps) => {
return (
<SvgIcon {...props}>
<path d="M4 12V15H6V12H4ZM16 7L14.59 5.59L13 7.17V2H11V7.19L9.39 5.61L8 7L12 11L16 7ZM4 17H20V15H4V17Z" />
<path d="M20 21C20 21.5523 19.5523 22 19 22H5C4.44772 22 4 21.5523 4 21V20H20V21Z" />
<rect x="18" y="12" width="2" height="3" />
<rect x="18" y="17" width="2" height="3" />
<rect x="4" y="17" width="2" height="3" />
</SvgIcon>
)
}
export const Delegate = (props: SvgIconProps) => (
<SvgIcon {...props}>
<path d="M4 12V15H6V12H4ZM16 7L14.59 5.59L13 7.17V2H11V7.19L9.39 5.61L8 7L12 11L16 7ZM4 17H20V15H4V17Z" />
<path d="M20 21C20 21.5523 19.5523 22 19 22H5C4.44772 22 4 21.5523 4 21V20H20V21Z" />
<rect x="18" y="12" width="2" height="3" />
<rect x="18" y="17" width="2" height="3" />
<rect x="4" y="17" width="2" height="3" />
</SvgIcon>
);
+4 -4
View File
@@ -1,4 +1,4 @@
export * from './delegate'
export * from './undelegate'
export * from './bond'
export * from './unbond'
export * from './delegate';
export * from './undelegate';
export * from './bond';
export * from './unbond';
+12 -14
View File
@@ -1,15 +1,13 @@
import React from 'react'
import { SvgIcon, SvgIconProps } from '@mui/material'
import React from 'react';
import { SvgIcon, SvgIconProps } from '@mui/material';
export const Node = (props: SvgIconProps) => {
return (
<SvgIcon {...props}>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M18.9536 6.99307L12 3.05788L5.04641 6.99304L11.5144 10.5864C11.8164 10.7542 12.1837 10.7542 12.4857 10.5864L18.9536 6.99307ZM19.9005 8.75493L13.457 12.3347C13.3093 12.4167 13.1564 12.4854 13 12.5407V20.3762L19.9005 16.4711V8.75493ZM11 12.5407C10.8436 12.4854 10.6908 12.4167 10.5431 12.3347L4.09946 8.75488V16.4711L11 20.3762V12.5407ZM13.0497 1.2757C12.4002 0.908099 11.5998 0.908099 10.9503 1.2757L3.04973 5.74676C2.40015 6.11437 2 6.79373 2 7.52894V16.4711C2 17.2063 2.40015 17.8856 3.04973 18.2532L10.9503 22.7243C11.5998 23.0919 12.4002 23.0919 13.0497 22.7243L20.9503 18.2532C21.5998 17.8856 22 17.2063 22 16.4711V7.52894C22 6.79373 21.5998 6.11437 20.9503 5.74676L13.0497 1.2757Z"
fill={props.color}
/>
</SvgIcon>
)
}
export const Node: React.FC<SvgIconProps> = ({ color, ...props }) => (
<SvgIcon {...props}>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M18.9536 6.99307L12 3.05788L5.04641 6.99304L11.5144 10.5864C11.8164 10.7542 12.1837 10.7542 12.4857 10.5864L18.9536 6.99307ZM19.9005 8.75493L13.457 12.3347C13.3093 12.4167 13.1564 12.4854 13 12.5407V20.3762L19.9005 16.4711V8.75493ZM11 12.5407C10.8436 12.4854 10.6908 12.4167 10.5431 12.3347L4.09946 8.75488V16.4711L11 20.3762V12.5407ZM13.0497 1.2757C12.4002 0.908099 11.5998 0.908099 10.9503 1.2757L3.04973 5.74676C2.40015 6.11437 2 6.79373 2 7.52894V16.4711C2 17.2063 2.40015 17.8856 3.04973 18.2532L10.9503 22.7243C11.5998 23.0919 12.4002 23.0919 13.0497 22.7243L20.9503 18.2532C21.5998 17.8856 22 17.2063 22 16.4711V7.52894C22 6.79373 21.5998 6.11437 20.9503 5.74676L13.0497 1.2757Z"
fill={color}
/>
</SvgIcon>
);
+16 -18
View File
@@ -1,19 +1,17 @@
import React from 'react'
import { SvgIcon, SvgIconProps } from '@mui/material'
import React from 'react';
import { SvgIcon, SvgIconProps } from '@mui/material';
export const Unbond = (props: SvgIconProps) => {
return (
<SvgIcon {...props}>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M16 17C14.8954 17 14 16.1046 14 15L14 9C14 7.89543 14.8954 7 16 7L22 7C23.1046 7 24 7.89543 24 9L24 15C24 16.1046 23.1046 17 22 17L16 17ZM16 9L16 15L22 15L22 9L16 9Z"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M2 17C0.89543 17 -1.00647e-07 16.1046 -2.24801e-07 15L-8.99206e-07 9C-1.02336e-06 7.89543 0.895429 7 2 7L8 7C9.10457 7 10 7.89543 10 9L10 15C10 16.1046 9.10457 17 8 17L2 17ZM2 9L2 15L8 15L8 9L2 9Z"
/>
</SvgIcon>
)
}
export const Unbond = (props: SvgIconProps) => (
<SvgIcon {...props}>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M16 17C14.8954 17 14 16.1046 14 15L14 9C14 7.89543 14.8954 7 16 7L22 7C23.1046 7 24 7.89543 24 9L24 15C24 16.1046 23.1046 17 22 17L16 17ZM16 9L16 15L22 15L22 9L16 9Z"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M2 17C0.89543 17 -1.00647e-07 16.1046 -2.24801e-07 15L-8.99206e-07 9C-1.02336e-06 7.89543 0.895429 7 2 7L8 7C9.10457 7 10 7.89543 10 9L10 15C10 16.1046 9.10457 17 8 17L2 17ZM2 9L2 15L8 15L8 9L2 9Z"
/>
</SvgIcon>
);
+12 -14
View File
@@ -1,15 +1,13 @@
import React from 'react'
import { SvgIcon, SvgIconProps } from '@mui/material'
import React from 'react';
import { SvgIcon, SvgIconProps } from '@mui/material';
export const Undelegate = (props: SvgIconProps) => {
return (
<SvgIcon {...props}>
<path d="M4 12V15H6V12H4ZM4 17H20V15H4V17Z" />
<path d="M20 21C20 21.5523 19.5523 22 19 22H5C4.44772 22 4 21.5523 4 21V20H20V21Z" />
<rect x="18" y="12" width="2" height="3" />
<rect x="18" y="17" width="2" height="3" />
<rect x="4" y="17" width="2" height="3" />
<path d="M9.41 7.41L8 6L12 2L16 6L14.61 7.39L13 5.81L13 11L11 11L11 5.83L9.41 7.41Z" />
</SvgIcon>
)
}
export const Undelegate = (props: SvgIconProps) => (
<SvgIcon {...props}>
<path d="M4 12V15H6V12H4ZM4 17H20V15H4V17Z" />
<path d="M20 21C20 21.5523 19.5523 22 19 22H5C4.44772 22 4 21.5523 4 21V20H20V21Z" />
<rect x="18" y="12" width="2" height="3" />
<rect x="18" y="17" width="2" height="3" />
<rect x="4" y="17" width="2" height="3" />
<path d="M9.41 7.41L8 6L12 2L16 6L14.61 7.39L13 5.81L13 11L11 11L11 5.83L9.41 7.41Z" />
</SvgIcon>
);
+12 -12
View File
@@ -1,29 +1,29 @@
import React, { useContext } from 'react'
import { createTheme, ThemeProvider } from '@mui/material/styles'
import { CssBaseline } from '@mui/material'
import { getDesignTokens } from './theme'
import { ClientContext } from '../context/main'
import React, { useContext } from 'react';
import { createTheme, ThemeProvider } from '@mui/material/styles';
import { CssBaseline } from '@mui/material';
import { getDesignTokens } from './theme';
import { ClientContext } from '../context/main';
/**
* Provides the theme for the Network Explorer by reacting to the light/dark mode choice stored in the app context.
*/
export const NymWalletTheme: React.FC = ({ children }) => {
const { mode } = useContext(ClientContext)
const theme = React.useMemo(() => createTheme(getDesignTokens(mode)), [mode])
const { mode } = useContext(ClientContext);
const theme = React.useMemo(() => createTheme(getDesignTokens(mode)), [mode]);
return (
<ThemeProvider theme={theme}>
<CssBaseline />
{children}
</ThemeProvider>
)
}
);
};
export const WelcomeTheme: React.FC = ({ children }) => {
const theme = createTheme(getDesignTokens('dark'))
const theme = createTheme(getDesignTokens('dark'));
return (
<ThemeProvider theme={theme}>
<CssBaseline />
{children}
</ThemeProvider>
)
}
);
};
+21 -21
View File
@@ -1,6 +1,6 @@
/* eslint-disable no-shadow,@typescript-eslint/no-unused-vars,@typescript-eslint/no-empty-interface */
import { Theme, ThemeOptions, Palette, PaletteOptions } from '@mui/material/styles'
import { PaletteMode } from '@mui/material'
import { Theme, ThemeOptions, Palette, PaletteOptions } from '@mui/material/styles';
import { PaletteMode } from '@mui/material';
/**
* If you are unfamiliar with Material UI theming, please read the following first:
@@ -28,43 +28,43 @@ declare module '@mui/material/styles' {
* This interface defines a palette used across Nym for branding
*/
interface NymPalette {
highlight: string
success: string
info: string
fee: string
background: { light: string; dark: string }
highlight: string;
success: string;
info: string;
fee: string;
background: { light: string; dark: string };
text: {
light: string
dark: string
}
light: string;
dark: string;
};
}
interface NymPaletteVariant {
mode: PaletteMode
mode: PaletteMode;
background: {
main: string
paper: string
}
main: string;
paper: string;
};
text: {
main: string
}
main: string;
};
topNav: {
background: string
}
background: string;
};
}
/**
* A palette definition only for the Nym Wallet that extends the Nym palette
*/
interface NymWalletPalette {
nymWallet: {}
nymWallet: {};
}
interface NymPaletteAndNymWalletPalette {
nym: NymPalette & NymWalletPalette
nym: NymPalette & NymWalletPalette;
}
type NymPaletteAndNymWalletPaletteOptions = Partial<NymPaletteAndNymWalletPalette>
type NymPaletteAndNymWalletPaletteOptions = Partial<NymPaletteAndNymWalletPalette>;
/**
* Add anything not palette related to the theme here
+13 -13
View File
@@ -1,4 +1,4 @@
import { PaletteMode } from '@mui/material'
import { PaletteMode } from '@mui/material';
import {
PaletteOptions,
NymPalette,
@@ -6,7 +6,7 @@ import {
ThemeOptions,
createTheme,
NymPaletteVariant,
} from '@mui/material/styles'
} from '@mui/material/styles';
//-----------------------------------------------------------------------------------------------
// Nym palette type definitions
@@ -29,7 +29,7 @@ const nymPalette: NymPalette = {
light: '#F2F2F2',
dark: '#121726',
},
}
};
const darkMode: NymPaletteVariant = {
mode: 'dark',
@@ -43,7 +43,7 @@ const darkMode: NymPaletteVariant = {
topNav: {
background: '#111826',
},
}
};
const lightMode: NymPaletteVariant = {
mode: 'light',
@@ -57,7 +57,7 @@ const lightMode: NymPaletteVariant = {
topNav: {
background: '#111826',
},
}
};
/**
* Nym palette specific to the Nym Wallet
@@ -65,9 +65,9 @@ const lightMode: NymPaletteVariant = {
* IMPORTANT: do not export this constant, always use the MUI `useTheme` hook to get the correct
* colours for dark/light mode.
*/
const nymWalletPalette = (variant: NymPaletteVariant): NymWalletPalette => ({
const nymWalletPalette = (_variant: NymPaletteVariant): NymWalletPalette => ({
nymWallet: {},
})
});
//-----------------------------------------------------------------------------------------------
// Nym palettes for light and dark mode
@@ -94,7 +94,7 @@ const variantToMUIPalette = (variant: NymPaletteVariant): PaletteOptions => ({
default: variant.background.main,
paper: variant.background.paper,
},
})
});
/**
* Returns the Network Explorer palette for light mode.
@@ -105,7 +105,7 @@ const createLightModePalette = (): PaletteOptions => ({
...nymWalletPalette(lightMode),
},
...variantToMUIPalette(lightMode),
})
});
/**
* Returns the Network Explorer palette for dark mode.
@@ -116,7 +116,7 @@ const createDarkModePalette = (): PaletteOptions => ({
...nymWalletPalette(darkMode),
},
...variantToMUIPalette(darkMode),
})
});
/**
* IMPORANT: if you need to get the default MUI theme, use the following
@@ -157,7 +157,7 @@ export const getDesignTokens = (mode: PaletteMode): ThemeOptions => {
mode,
...(mode === 'light' ? createLightModePalette() : createDarkModePalette()),
},
})
});
// then customise theme and components
return {
@@ -217,5 +217,5 @@ export const getDesignTokens = (mode: PaletteMode): ThemeOptions => {
},
},
palette,
}
}
};
};

Some files were not shown because too many files have changed in this diff Show More