Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fda9bc65fc | |||
| 3c476560d2 | |||
| bcd5531f40 | |||
| fc747503cf | |||
| f3b7f7874e | |||
| 36ec0e180e | |||
| b2ce0a8300 | |||
| 727a8305a3 | |||
| 2978d4ea6b | |||
| c31235b3f9 | |||
| 5c264d79bf | |||
| 6bda9ae5f7 | |||
| 9e932defb4 | |||
| f249e7b497 | |||
| 05a9c24437 | |||
| eb6ecc7241 | |||
| f841242ce2 | |||
| c3ba7e5ff2 | |||
| 15ab890932 |
@@ -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"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": [
|
||||
"@nymproject/eslint-config-react-typescript"
|
||||
],
|
||||
"parserOptions": {
|
||||
"project": "./tsconfig.eslint.json"
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,5 @@
|
||||
"trailingComma": "all",
|
||||
"singleQuote": true,
|
||||
"printWidth": 120,
|
||||
"tabWidth": 2,
|
||||
"semi": false
|
||||
"tabWidth": 2
|
||||
}
|
||||
|
||||
+45
-4
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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]} />;
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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
@@ -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>;
|
||||
};
|
||||
|
||||
@@ -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 };
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
Vendored
-3
@@ -1,3 +0,0 @@
|
||||
declare module '*.jpg'
|
||||
declare module '*.png'
|
||||
declare module '*.svg'
|
||||
+35
-38
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
export * from './AppLayout'
|
||||
export * from './PageLayout'
|
||||
export * from './AppLayout';
|
||||
export * from './PageLayout';
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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)),
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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)),
|
||||
});
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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')),
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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(),
|
||||
})
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -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[];
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
Vendored
+21
-21
@@ -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
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user