Merge pull request #925 from nymtech/feature/ui-enhancements
Feature/UI enhancements for Desktop Wallet
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"trailingComma": "all",
|
||||
"singleQuote": true,
|
||||
"printWidth": 120,
|
||||
"tabWidth": 2,
|
||||
"semi": false
|
||||
}
|
||||
Generated
+17
@@ -2786,6 +2786,16 @@ version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
||||
|
||||
[[package]]
|
||||
name = "open"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b46b233de7d83bc167fe43ae2dda3b5b84e80e09cceba581e4decb958a4896bf"
|
||||
dependencies = [
|
||||
"pathdiff",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.38"
|
||||
@@ -2899,6 +2909,12 @@ version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0744126afe1a6dd7f394cb50a716dbe086cb06e255e53d8d0185d82828358fb5"
|
||||
|
||||
[[package]]
|
||||
name = "pathdiff"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
|
||||
|
||||
[[package]]
|
||||
name = "pbkdf2"
|
||||
version = "0.8.0"
|
||||
@@ -4524,6 +4540,7 @@ dependencies = [
|
||||
"http",
|
||||
"ignore",
|
||||
"once_cell",
|
||||
"open",
|
||||
"percent-encoding",
|
||||
"rand 0.8.4",
|
||||
"raw-window-handle",
|
||||
|
||||
@@ -13,11 +13,12 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/preset-typescript": "^7.15.0",
|
||||
"@emotion/react": "^11.7.0",
|
||||
"@emotion/styled": "^11.6.0",
|
||||
"@hookform/resolvers": "^2.8.0",
|
||||
"@material-ui/core": "^4.12.3",
|
||||
"@material-ui/icons": "^4.11.2",
|
||||
"@material-ui/lab": "^4.0.0-alpha.60",
|
||||
"@material-ui/styles": "^4.11.4",
|
||||
"@mui/icons-material": "^5.2.0",
|
||||
"@mui/material": "^5.2.2",
|
||||
"@mui/styles": "^5.2.2",
|
||||
"@types/react-dom": "^17.0.9",
|
||||
"bs58": "^4.0.1",
|
||||
"clsx": "^1.1.1",
|
||||
@@ -48,8 +49,8 @@
|
||||
"html-webpack-plugin": "^5.3.2",
|
||||
"style-loader": "^3.2.1",
|
||||
"url-loader": "^4.1.1",
|
||||
"webpack": "^5.50.0",
|
||||
"webpack": "^5.64.3",
|
||||
"webpack-cli": "^4.8.0",
|
||||
"webpack-dev-server": "^4.1.0"
|
||||
"webpack-dev-server": "^4.5.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Open+Sans&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<title>Nym Wallet</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -18,7 +18,7 @@ tauri-build = { version = "1.0.0-beta.4" }
|
||||
[dependencies]
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
tauri = { version = "1.0.0-beta.8", features = [] }
|
||||
tauri = { version = "1.0.0-beta.8", features = ["shell-open"] }
|
||||
tokio = { version = "1.10", features = ["sync"] }
|
||||
dirs = "4.0"
|
||||
bip39 = "1.0"
|
||||
|
||||
@@ -48,14 +48,17 @@
|
||||
"updater": {
|
||||
"active": false
|
||||
},
|
||||
"allowlist": {},
|
||||
"allowlist": {
|
||||
"shell": {
|
||||
"open": true
|
||||
}
|
||||
},
|
||||
"windows": [
|
||||
{
|
||||
"title": "nym-wallet",
|
||||
"width": 1268,
|
||||
"height": 768,
|
||||
"resizable": true,
|
||||
"fullscreen": false
|
||||
"resizable": true
|
||||
}
|
||||
],
|
||||
"security": {
|
||||
|
||||
@@ -10,9 +10,8 @@ import {
|
||||
Paper,
|
||||
Slide,
|
||||
TextField,
|
||||
Theme,
|
||||
} from '@material-ui/core'
|
||||
import { useTheme } from '@material-ui/styles'
|
||||
} from '@mui/material'
|
||||
|
||||
import { ClientContext } from '../context/main'
|
||||
import { NymCard } from '.'
|
||||
import { getContractParams, setContractParams } from '../requests'
|
||||
@@ -77,13 +76,9 @@ const AdminForm: React.FC<{
|
||||
onCancel()
|
||||
}
|
||||
|
||||
const theme: Theme = useTheme()
|
||||
|
||||
return (
|
||||
<FormControl fullWidth>
|
||||
<div
|
||||
style={{ padding: theme.spacing(3, 5), maxWidth: 700, minWidth: 400 }}
|
||||
>
|
||||
<Box sx={{ padding: [3, 5], maxWidth: 700, minWidth: 400 }}>
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
@@ -190,15 +185,15 @@ const AdminForm: React.FC<{
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</div>
|
||||
</Box>
|
||||
<Grid
|
||||
container
|
||||
spacing={1}
|
||||
justifyContent="flex-end"
|
||||
style={{
|
||||
borderTop: `1px solid ${theme.palette.grey[200]}`,
|
||||
background: theme.palette.grey[100],
|
||||
padding: theme.spacing(2),
|
||||
sx={{
|
||||
borderTop: (theme) => `1px solid ${theme.palette.grey[200]}`,
|
||||
background: (theme) => theme.palette.grey[100],
|
||||
padding: 2,
|
||||
}}
|
||||
>
|
||||
<Grid item>
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
import React, { useContext, useEffect } from 'react'
|
||||
import {
|
||||
AppBar as MuiAppBar,
|
||||
Divider,
|
||||
Grid,
|
||||
IconButton,
|
||||
Toolbar,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
} from '@mui/material'
|
||||
import { Box } from '@mui/system'
|
||||
import { Logout } from '@mui/icons-material'
|
||||
import { ClientContext } from '../context/main'
|
||||
import { CopyToClipboard } from '.'
|
||||
|
||||
export const AppBar = () => {
|
||||
const { userBalance, logOut, clientDetails } = useContext(ClientContext)
|
||||
const matches = useMediaQuery('(min-width: 769px)')
|
||||
|
||||
return (
|
||||
<MuiAppBar
|
||||
position="sticky"
|
||||
sx={{ boxShadow: 'none', bgcolor: 'nym.background.light' }}
|
||||
>
|
||||
<Toolbar>
|
||||
<Grid
|
||||
container
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
flexWrap="nowrap"
|
||||
>
|
||||
<Grid container item alignItems="center">
|
||||
<Grid item>
|
||||
<AppBarItem
|
||||
primaryText="Balance"
|
||||
secondaryText={userBalance.balance?.printable_balance}
|
||||
/>
|
||||
</Grid>
|
||||
{matches && (
|
||||
<>
|
||||
<Divider
|
||||
orientation="vertical"
|
||||
variant="middle"
|
||||
flexItem
|
||||
sx={{ mr: 1 }}
|
||||
/>
|
||||
|
||||
<Grid item>
|
||||
<AppBarItem
|
||||
primaryText="Address"
|
||||
secondaryText={clientDetails?.client_address}
|
||||
Action={
|
||||
<CopyToClipboard
|
||||
text={clientDetails?.client_address}
|
||||
iconButton
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Grid>
|
||||
</>
|
||||
)}
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<IconButton onClick={logOut} sx={{ color: 'nym.background.dark' }}>
|
||||
<Logout />
|
||||
</IconButton>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Toolbar>
|
||||
</MuiAppBar>
|
||||
)
|
||||
}
|
||||
|
||||
const AppBarItem: React.FC<{
|
||||
primaryText: string
|
||||
secondaryText?: string
|
||||
Action?: React.ReactNode
|
||||
}> = ({ primaryText, secondaryText = '', Action }) => {
|
||||
return (
|
||||
<Box sx={{ p: 1, mr: 1 }}>
|
||||
<Typography variant="body2" component="span" sx={{ color: 'grey.600' }}>
|
||||
{primaryText}:
|
||||
</Typography>{' '}
|
||||
<Typography
|
||||
variant="body2"
|
||||
component="span"
|
||||
color="nym.background.dark"
|
||||
sx={{ mr: 1 }}
|
||||
>
|
||||
{secondaryText}
|
||||
</Typography>
|
||||
{Action}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
import React from 'react'
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
import Grid from '@material-ui/core/Grid'
|
||||
import { CircularProgress } from '@material-ui/core'
|
||||
import { Alert, AlertTitle } from '@material-ui/lab'
|
||||
import {
|
||||
Alert,
|
||||
AlertTitle,
|
||||
CircularProgress,
|
||||
Grid,
|
||||
Typography,
|
||||
} from '@mui/material'
|
||||
|
||||
type ConfirmationProps = {
|
||||
isLoading: boolean
|
||||
|
||||
@@ -1,51 +1,82 @@
|
||||
import React, { useState } from 'react'
|
||||
import { Button } from '@material-ui/core'
|
||||
import { Check } from '@material-ui/icons'
|
||||
import { green } from '@material-ui/core/colors'
|
||||
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'
|
||||
|
||||
const copy = (text: string): Promise<{ success: boolean; value: string }> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
clipboard
|
||||
.writeText(text)
|
||||
.then(() => resolve({ success: true, value: text }))
|
||||
.catch((e) => reject({ success: false, value: 'Failed to copy: ' + e }))
|
||||
})
|
||||
}
|
||||
|
||||
export const handleCopy = async ({
|
||||
text,
|
||||
cb,
|
||||
export const CopyToClipboard = ({
|
||||
text = '',
|
||||
light,
|
||||
iconButton,
|
||||
}: {
|
||||
text: string
|
||||
cb: (success: boolean) => void
|
||||
text?: string
|
||||
light?: boolean
|
||||
iconButton?: boolean
|
||||
}) => {
|
||||
const res = await copy(text)
|
||||
if (res.success) {
|
||||
setTimeout(() => {
|
||||
cb(true)
|
||||
}, 750)
|
||||
} else {
|
||||
console.log(res.value)
|
||||
}
|
||||
}
|
||||
|
||||
export const CopyToClipboard = ({ text }: { text: string }) => {
|
||||
const [copied, setCopied] = useState(false)
|
||||
|
||||
const updateCopyStatus = (isCopied: boolean) => setCopied(isCopied)
|
||||
const handleCopy = async (text: string) => {
|
||||
try {
|
||||
await clipboard.writeText(text)
|
||||
setCopied(true)
|
||||
} catch (e) {
|
||||
console.log('failed to copy: ' + e)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
let timer: NodeJS.Timeout
|
||||
if (copied) {
|
||||
timer = setTimeout(() => {
|
||||
setCopied(false)
|
||||
}, 2000)
|
||||
}
|
||||
return () => clearTimeout(timer)
|
||||
}, [copied])
|
||||
|
||||
if (iconButton)
|
||||
return (
|
||||
<Tooltip title={!copied ? 'Copy' : 'Copied!'} leaveDelay={500}>
|
||||
<IconButton
|
||||
onClick={() => handleCopy(text)}
|
||||
size="small"
|
||||
sx={{
|
||||
color: (theme) =>
|
||||
light
|
||||
? theme.palette.common.white
|
||||
: theme.palette.nym.background.dark,
|
||||
}}
|
||||
>
|
||||
{!copied ? (
|
||||
<ContentCopy fontSize="small" />
|
||||
) : (
|
||||
<Check color="success" />
|
||||
)}
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
)
|
||||
|
||||
return (
|
||||
<Button
|
||||
size="small"
|
||||
variant={copied ? 'text' : 'outlined'}
|
||||
aria-label="save"
|
||||
data-testid="copy-button"
|
||||
onClick={() => handleCopy({ text, cb: updateCopyStatus })}
|
||||
endIcon={copied && <Check />}
|
||||
style={copied ? { background: green[500], color: 'white' } : {}}
|
||||
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,
|
||||
}}
|
||||
onClick={() => handleCopy(text)}
|
||||
endIcon={
|
||||
copied && (
|
||||
<Check sx={{ color: (theme) => theme.palette.success.light }} />
|
||||
)
|
||||
}
|
||||
>
|
||||
{copied ? 'Copied' : 'Copy'}
|
||||
{!copied ? 'Copy' : 'Copied'}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React from 'react'
|
||||
import { FallbackProps } from 'react-error-boundary'
|
||||
import { Alert, AlertTitle } from '@material-ui/lab'
|
||||
import { Button } from '@material-ui/core'
|
||||
import { Alert, AlertTitle, Button } from '@mui/material'
|
||||
|
||||
export const ErrorFallback = ({ error, resetErrorBoundary }: FallbackProps) => {
|
||||
return (
|
||||
|
||||
@@ -1,79 +1,59 @@
|
||||
import React, { useContext, useEffect } from 'react'
|
||||
import { Link, useLocation } from 'react-router-dom'
|
||||
import { List, ListItem, ListItemIcon, ListItemText } from '@mui/material'
|
||||
import {
|
||||
List,
|
||||
ListItem,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
Theme,
|
||||
} from '@material-ui/core'
|
||||
import {
|
||||
AccountBalanceWalletRounded,
|
||||
AccountBalanceWalletOutlined,
|
||||
ArrowBack,
|
||||
ArrowForward,
|
||||
AttachMoney,
|
||||
Cancel,
|
||||
ExitToApp,
|
||||
HowToVote,
|
||||
CancelOutlined,
|
||||
HowToVoteOutlined,
|
||||
MoneyOff,
|
||||
Description,
|
||||
Settings,
|
||||
} from '@material-ui/icons'
|
||||
import { makeStyles } from '@material-ui/styles'
|
||||
import clsx from 'clsx'
|
||||
} from '@mui/icons-material'
|
||||
import { ADMIN_ADDRESS, ClientContext } from '../context/main'
|
||||
|
||||
let routesSchema = [
|
||||
{
|
||||
label: 'Balance',
|
||||
route: '/balance',
|
||||
Icon: <AccountBalanceWalletRounded />,
|
||||
Icon: AccountBalanceWalletOutlined,
|
||||
},
|
||||
{
|
||||
label: 'Send',
|
||||
route: '/send',
|
||||
Icon: <ArrowForward />,
|
||||
Icon: ArrowForward,
|
||||
},
|
||||
{
|
||||
label: 'Receive',
|
||||
route: '/receive',
|
||||
Icon: <ArrowBack />,
|
||||
Icon: ArrowBack,
|
||||
},
|
||||
{
|
||||
label: 'Bond',
|
||||
route: '/bond',
|
||||
Icon: <AttachMoney />,
|
||||
Icon: AttachMoney,
|
||||
},
|
||||
{
|
||||
label: 'Unbond',
|
||||
route: '/unbond',
|
||||
Icon: <MoneyOff />,
|
||||
Icon: MoneyOff,
|
||||
},
|
||||
{
|
||||
label: 'Delegate',
|
||||
route: '/delegate',
|
||||
Icon: <HowToVote />,
|
||||
Icon: HowToVoteOutlined,
|
||||
},
|
||||
{
|
||||
label: 'Undelegate',
|
||||
route: '/undelegate',
|
||||
Icon: <Cancel />,
|
||||
Icon: CancelOutlined,
|
||||
},
|
||||
]
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) => ({
|
||||
navItem: {
|
||||
color: '#fff',
|
||||
fontSize: 20,
|
||||
},
|
||||
selected: {
|
||||
color: theme.palette.primary.light,
|
||||
},
|
||||
}))
|
||||
|
||||
export const Nav = () => {
|
||||
const classes = useStyles()
|
||||
const { clientDetails, handleShowAdmin, logOut } = useContext(ClientContext)
|
||||
const { clientDetails, handleShowAdmin } = useContext(ClientContext)
|
||||
const location = useLocation()
|
||||
|
||||
useEffect(() => {
|
||||
@@ -81,7 +61,7 @@ export const Nav = () => {
|
||||
routesSchema.push({
|
||||
label: 'Docs',
|
||||
route: '/docs',
|
||||
Icon: <Description />,
|
||||
Icon: Description,
|
||||
})
|
||||
}
|
||||
}, [])
|
||||
@@ -91,56 +71,42 @@ export const Nav = () => {
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
justifyContent: 'flex-start',
|
||||
}}
|
||||
>
|
||||
<List>
|
||||
{routesSchema.map((r, i) => (
|
||||
<ListItem button component={Link} to={r.route} key={i}>
|
||||
<List disablePadding>
|
||||
{routesSchema.map(({ Icon, route, label }, i) => (
|
||||
<ListItem disableGutters component={Link} to={route} key={i}>
|
||||
<ListItemIcon
|
||||
className={clsx([
|
||||
classes.navItem,
|
||||
location.pathname === r.route ? classes.selected : undefined,
|
||||
])}
|
||||
sx={{
|
||||
minWidth: 30,
|
||||
color:
|
||||
location.pathname === route ? 'primary.main' : 'common.white',
|
||||
}}
|
||||
>
|
||||
{r.Icon}
|
||||
<Icon sx={{ fontSize: 20 }} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={r.label}
|
||||
primaryTypographyProps={{
|
||||
className: clsx([
|
||||
classes.navItem,
|
||||
location.pathname === r.route ? classes.selected : undefined,
|
||||
]),
|
||||
sx={{
|
||||
color:
|
||||
location.pathname === route ? 'primary.main' : 'common.white',
|
||||
}}
|
||||
primary={label}
|
||||
/>
|
||||
</ListItem>
|
||||
))}
|
||||
{clientDetails?.client_address === ADMIN_ADDRESS && (
|
||||
<ListItem button onClick={handleShowAdmin}>
|
||||
<ListItemIcon className={classes.navItem}>
|
||||
<Settings />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary="Admin"
|
||||
primaryTypographyProps={{
|
||||
className: classes.navItem,
|
||||
<ListItem disableGutters onClick={handleShowAdmin}>
|
||||
<ListItemIcon
|
||||
sx={{
|
||||
minWidth: 30,
|
||||
}}
|
||||
/>
|
||||
>
|
||||
<Settings sx={{ fontSize: 20, color: 'white' }} />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Admin" sx={{ color: 'common.white' }} />
|
||||
</ListItem>
|
||||
)}
|
||||
|
||||
<ListItem button onClick={logOut}>
|
||||
<ListItemIcon data-testid="log-out" className={classes.navItem}>
|
||||
<ExitToApp />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary="Log out"
|
||||
primaryTypographyProps={{
|
||||
className: classes.navItem,
|
||||
}}
|
||||
/>
|
||||
</ListItem>
|
||||
</List>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
import React, { useContext, useEffect, useState } from 'react'
|
||||
import {
|
||||
CardContent,
|
||||
CircularProgress,
|
||||
IconButton,
|
||||
Tooltip,
|
||||
Typography,
|
||||
useTheme,
|
||||
} from '@material-ui/core'
|
||||
import { ClientContext } from '../context/main'
|
||||
import { CheckCircleOutline, FileCopy, Refresh } from '@material-ui/icons'
|
||||
import { NymCard } from './NymCard'
|
||||
import { Alert } from '@material-ui/lab'
|
||||
import { handleCopy } from './CopyToClipboard'
|
||||
import { truncate } from '../utils'
|
||||
|
||||
export const BalanceCard = () => {
|
||||
const { getBalance } = useContext(ClientContext)
|
||||
const theme = useTheme()
|
||||
|
||||
useEffect(getBalance.fetchBalance, [])
|
||||
|
||||
return (
|
||||
<div style={{ margin: theme.spacing(3) }}>
|
||||
<NymCard
|
||||
title="Balance"
|
||||
subheader="Current wallet balance"
|
||||
noPadding
|
||||
Action={
|
||||
<Tooltip title="Refresh balance">
|
||||
<IconButton data-testid="refresh-balance" onClick={getBalance.fetchBalance} size="small">
|
||||
<Refresh />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
}
|
||||
>
|
||||
<CardContent>
|
||||
<div style={{ display: 'flex', justifyContent: 'center' }}>
|
||||
{getBalance.isLoading ? (
|
||||
<CircularProgress size={24} />
|
||||
) : getBalance.error ? (
|
||||
<Alert severity="error" style={{ width: '100%' }}>
|
||||
{getBalance.error}
|
||||
</Alert>
|
||||
) : (
|
||||
<Typography variant="h6" data-testid="account-balance">
|
||||
{getBalance.balance?.printable_balance}
|
||||
</Typography>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</NymCard>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
enum EnumCopyState {
|
||||
copying,
|
||||
copySuccess,
|
||||
}
|
||||
|
||||
export const AddressCard = () => {
|
||||
const { clientDetails } = useContext(ClientContext)
|
||||
|
||||
const [copyState, setCopyState] = useState<EnumCopyState>()
|
||||
|
||||
const theme = useTheme()
|
||||
|
||||
return (
|
||||
<div style={{ margin: theme.spacing(3) }}>
|
||||
<NymCard
|
||||
title="Address"
|
||||
subheader="Wallet payments address"
|
||||
noPadding
|
||||
data-testid="wallet-address-header"
|
||||
Action={
|
||||
<Tooltip title={!copyState ? 'Copy address' : 'Copied'}>
|
||||
<span>
|
||||
<IconButton
|
||||
disabled={!!copyState}
|
||||
onClick={async () => {
|
||||
setCopyState(EnumCopyState.copying)
|
||||
await handleCopy({
|
||||
text: clientDetails?.client_address || '',
|
||||
cb: (isCopied) => {
|
||||
if (isCopied) {
|
||||
setCopyState(EnumCopyState.copySuccess)
|
||||
setTimeout(() => {
|
||||
setCopyState(undefined)
|
||||
}, 2500)
|
||||
}
|
||||
},
|
||||
})
|
||||
}}
|
||||
>
|
||||
{copyState === EnumCopyState.copying ? (
|
||||
<CircularProgress size={24} />
|
||||
) : copyState === EnumCopyState.copySuccess ? (
|
||||
<CheckCircleOutline
|
||||
style={{ color: theme.palette.success.main }}
|
||||
/>
|
||||
) : (
|
||||
<FileCopy />
|
||||
)}
|
||||
</IconButton>
|
||||
</span>
|
||||
</Tooltip>
|
||||
}
|
||||
>
|
||||
<CardContent>
|
||||
<Typography data-testid="wallet-address"
|
||||
style={{ fontWeight: theme.typography.fontWeightRegular }}
|
||||
>
|
||||
{truncate(clientDetails?.client_address!, 35)}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</NymCard>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { Alert, AlertTitle } from '@material-ui/lab'
|
||||
import { Alert, AlertTitle } from '@mui/material'
|
||||
|
||||
export const NoClientError = () => {
|
||||
return (
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import React from 'react'
|
||||
import {
|
||||
FormControl,
|
||||
FormControlLabel,
|
||||
FormLabel,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
} from '@material-ui/core'
|
||||
import React from 'react'
|
||||
} from '@mui/material'
|
||||
import { EnumNodeType } from '../types/global'
|
||||
|
||||
export const NodeTypeSelector = ({
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from 'react'
|
||||
import { Card, CardContent, CardHeader, useTheme } from '@material-ui/core'
|
||||
import { Card, CardContent, CardHeader } from '@mui/material'
|
||||
import { styled } from '@mui/styles'
|
||||
|
||||
export const NymCard: React.FC<{
|
||||
title: string
|
||||
@@ -7,7 +8,6 @@ export const NymCard: React.FC<{
|
||||
Action?: React.ReactNode
|
||||
noPadding?: boolean
|
||||
}> = ({ title, subheader, Action, noPadding, children }) => {
|
||||
const theme = useTheme()
|
||||
return (
|
||||
<Card variant="outlined">
|
||||
<CardHeader
|
||||
@@ -17,19 +17,24 @@ export const NymCard: React.FC<{
|
||||
titleTypographyProps={{ variant: 'h5' }}
|
||||
subheaderTypographyProps={{ variant: 'subtitle1' }}
|
||||
action={Action}
|
||||
style={{
|
||||
padding: theme.spacing(2.5),
|
||||
borderBottom: `1px solid ${theme.palette.grey[200]}`,
|
||||
sx={{
|
||||
color: 'nym.background.dark',
|
||||
padding: 2.5,
|
||||
borderBottom: (theme) => `1px solid ${theme.palette.grey[100]}`,
|
||||
}}
|
||||
/>
|
||||
<CardContent
|
||||
style={{
|
||||
background: theme.palette.grey[50],
|
||||
padding: noPadding ? 0 : theme.spacing(2, 5),
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</CardContent>
|
||||
{noPadding ? (
|
||||
<CardContentNoPadding>{children}</CardContentNoPadding>
|
||||
) : (
|
||||
<CardContent>{children}</CardContent>
|
||||
)}
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
const CardContentNoPadding = styled(CardContent)({
|
||||
padding: 0,
|
||||
'&:last-child': {
|
||||
paddingBottom: 0,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React from 'react'
|
||||
import { CircularProgress, Theme } from '@material-ui/core'
|
||||
import { useTheme } from '@material-ui/styles'
|
||||
import { CircularProgress, Box } from '@mui/material'
|
||||
|
||||
export enum EnumRequestStatus {
|
||||
initial = 'initial',
|
||||
@@ -18,16 +17,15 @@ export const RequestStatus = ({
|
||||
Success: React.ReactNode
|
||||
Error: React.ReactNode
|
||||
}) => {
|
||||
const theme: Theme = useTheme()
|
||||
return (
|
||||
<div style={{ padding: theme.spacing(3, 5) }}>
|
||||
<Box sx={{ padding: [3, 5] }}>
|
||||
{status === EnumRequestStatus.loading && (
|
||||
<div style={{ display: 'flex', justifyContent: 'center' }}>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'center' }}>
|
||||
<CircularProgress size={48} />
|
||||
</div>
|
||||
</Box>
|
||||
)}
|
||||
{status === EnumRequestStatus.success && Success}
|
||||
{status === EnumRequestStatus.error && Error}
|
||||
</div>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ export * from './Confirmation'
|
||||
export * from './CopyToClipboard'
|
||||
export * from './NymCard'
|
||||
export * from './Nav'
|
||||
export * from './NavigationCards'
|
||||
export * from './NodeTypeSelector'
|
||||
export * from './RequestStatus'
|
||||
export * from './NoClientError'
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
import React, { createContext, useEffect, useState } from 'react'
|
||||
import { useHistory } from 'react-router-dom'
|
||||
import { TClientDetails, TSignInWithMnemonic } from '../types'
|
||||
import { TUseGetBalance, useGetBalance } from '../hooks/useGetBalance'
|
||||
import { TUseuserBalance, useGetBalance } from '../hooks/useGetBalance'
|
||||
|
||||
export const ADMIN_ADDRESS = 'punk1h3w4nj7kny5dfyjw2le4vm74z03v9vd4dstpu0'
|
||||
export const urls = {
|
||||
blockExplorer: 'https://testnet-milhon-blocks.nymtech.net',
|
||||
}
|
||||
|
||||
type TClientContext = {
|
||||
clientDetails?: TClientDetails
|
||||
getBalance: TUseGetBalance
|
||||
userBalance: TUseuserBalance
|
||||
showAdmin: boolean
|
||||
mode: 'light' | 'dark'
|
||||
handleShowAdmin: () => void
|
||||
logIn: (clientDetails: TSignInWithMnemonic) => void
|
||||
logOut: () => void
|
||||
@@ -23,18 +27,27 @@ export const ClientContextProvider = ({
|
||||
}) => {
|
||||
const [clientDetails, setClientDetails] = useState<TClientDetails>()
|
||||
const [showAdmin, setShowAdmin] = useState(false)
|
||||
const [mode, setMode] = useState<'light' | 'dark'>('light')
|
||||
|
||||
const history = useHistory()
|
||||
const getBalance = useGetBalance()
|
||||
const userBalance = useGetBalance()
|
||||
|
||||
useEffect(() => {
|
||||
!clientDetails ? history.push('/signin') : history.push('/balance')
|
||||
}, [clientDetails])
|
||||
if (!clientDetails) {
|
||||
history.push('/signin')
|
||||
} else {
|
||||
userBalance.fetchBalance()
|
||||
history.push('/balance')
|
||||
}
|
||||
}, [clientDetails, userBalance.fetchBalance])
|
||||
|
||||
const logIn = async (clientDetails: TSignInWithMnemonic) =>
|
||||
setClientDetails(clientDetails)
|
||||
|
||||
const logOut = () => setClientDetails(undefined)
|
||||
const logOut = () => {
|
||||
setClientDetails(undefined)
|
||||
userBalance.clearBalance()
|
||||
}
|
||||
|
||||
const handleShowAdmin = () => setShowAdmin((show) => !show)
|
||||
|
||||
@@ -42,8 +55,9 @@ export const ClientContextProvider = ({
|
||||
<ClientContext.Provider
|
||||
value={{
|
||||
clientDetails,
|
||||
getBalance,
|
||||
userBalance,
|
||||
showAdmin,
|
||||
mode,
|
||||
handleShowAdmin,
|
||||
logIn,
|
||||
logOut,
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
import { useState } from 'react'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { invoke } from '@tauri-apps/api'
|
||||
import { Balance } from '../types'
|
||||
|
||||
export type TUseGetBalance = {
|
||||
export type TUseuserBalance = {
|
||||
error?: string
|
||||
balance?: Balance
|
||||
isLoading: boolean
|
||||
fetchBalance: () => void
|
||||
clearBalance: () => void
|
||||
}
|
||||
|
||||
export const useGetBalance = (): TUseGetBalance => {
|
||||
export const useGetBalance = (): TUseuserBalance => {
|
||||
const [balance, setBalance] = useState<Balance>()
|
||||
const [error, setError] = useState<string>()
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
|
||||
const fetchBalance = () => {
|
||||
const fetchBalance = useCallback(() => {
|
||||
setIsLoading(true)
|
||||
setError(undefined)
|
||||
invoke('get_balance')
|
||||
@@ -25,12 +26,15 @@ export const useGetBalance = (): TUseGetBalance => {
|
||||
setTimeout(() => {
|
||||
setIsLoading(false)
|
||||
}, 1000)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const clearBalance = () => setBalance(undefined)
|
||||
|
||||
return {
|
||||
error,
|
||||
isLoading,
|
||||
balance,
|
||||
fetchBalance,
|
||||
clearBalance,
|
||||
}
|
||||
}
|
||||
|
||||
+10
-14
@@ -1,40 +1,36 @@
|
||||
import React, { useContext } from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import { ErrorBoundary } from 'react-error-boundary'
|
||||
import { CssBaseline, ThemeProvider } from '@material-ui/core'
|
||||
import { BrowserRouter as Router } from 'react-router-dom'
|
||||
import { Routes } from './routes'
|
||||
import { theme } from './theme'
|
||||
import { ClientContext, ClientContextProvider } from './context/main'
|
||||
import { ApplicationLayout } from './layouts'
|
||||
import { SignIn } from './routes/sign-in'
|
||||
import { SignIn } from './routes/sign-in/'
|
||||
import { Admin, ErrorFallback } from './components'
|
||||
import { NymWalletTheme } from './theme'
|
||||
|
||||
const AppWrapper = () => {
|
||||
const App = () => {
|
||||
const { clientDetails } = useContext(ClientContext)
|
||||
return (
|
||||
<ThemeProvider theme={theme}>
|
||||
<CssBaseline />
|
||||
<NymWalletTheme>
|
||||
{!clientDetails ? (
|
||||
<SignIn />
|
||||
) : (
|
||||
<ApplicationLayout>
|
||||
<>
|
||||
<Admin />
|
||||
<Routes />
|
||||
</>
|
||||
<Admin />
|
||||
<Routes />
|
||||
</ApplicationLayout>
|
||||
)}
|
||||
</ThemeProvider>
|
||||
</NymWalletTheme>
|
||||
)
|
||||
}
|
||||
|
||||
const App = () => {
|
||||
const AppWrapper = () => {
|
||||
return (
|
||||
<ErrorBoundary FallbackComponent={ErrorFallback}>
|
||||
<Router>
|
||||
<ClientContextProvider>
|
||||
<AppWrapper />
|
||||
<App />
|
||||
</ClientContextProvider>
|
||||
</Router>
|
||||
</ErrorBoundary>
|
||||
@@ -43,4 +39,4 @@ const App = () => {
|
||||
|
||||
const root = document.getElementById('root')
|
||||
|
||||
ReactDOM.render(<App />, root)
|
||||
ReactDOM.render(<AppWrapper />, root)
|
||||
|
||||
@@ -1,71 +1,45 @@
|
||||
import React from 'react'
|
||||
import { Divider } from '@material-ui/core'
|
||||
import { AddressCard, BalanceCard, Nav } from '../components'
|
||||
import { Box } from '@mui/material'
|
||||
import { Nav } from '../components'
|
||||
import Logo from '../images/logo-background.svg'
|
||||
import { theme } from '../theme'
|
||||
import { AppBar } from '../components/AppBar'
|
||||
|
||||
export const ApplicationLayout = ({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactElement
|
||||
}) => {
|
||||
export const ApplicationLayout: React.FC = ({ children }) => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
<Box
|
||||
sx={{
|
||||
height: '100vh',
|
||||
width: '100vw',
|
||||
display: 'grid',
|
||||
gridTemplateColumns: '400px auto',
|
||||
gridTemplateColumns: '240px auto',
|
||||
gridTemplateRows: '100%',
|
||||
gridColumnGap: '8px',
|
||||
gridRowGap: '0px',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
gridArea: '1 / 1 / 2 / 2',
|
||||
<Box
|
||||
sx={{
|
||||
background: '#121726',
|
||||
overflow: 'auto',
|
||||
p: [4, 5],
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
marginTop: theme.spacing(6),
|
||||
}}
|
||||
>
|
||||
<img src={Logo} style={{ width: 75 }} />
|
||||
</div>
|
||||
<Divider
|
||||
light
|
||||
variant="middle"
|
||||
style={{
|
||||
background: theme.palette.grey[100],
|
||||
marginTop: theme.spacing(6),
|
||||
}}
|
||||
/>
|
||||
<div style={{ marginTop: theme.spacing(10) }}>
|
||||
<BalanceCard />
|
||||
<AddressCard />
|
||||
</div>
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<img src={Logo} style={{ width: 45 }} />
|
||||
</Box>
|
||||
|
||||
<div style={{ marginTop: theme.spacing(7) }}>
|
||||
<Nav />
|
||||
</div>
|
||||
<div />
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
gridArea: '1 / 2 / 2 / 3',
|
||||
<Nav />
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
bgcolor: 'nym.background.light',
|
||||
overflow: 'auto',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
>
|
||||
<AppBar />
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
import React from 'react'
|
||||
import { Grid, Theme, useTheme } from '@material-ui/core'
|
||||
import { Box, Grid } from '@mui/material'
|
||||
|
||||
export const Layout = ({ children }: { children: React.ReactElement }) => {
|
||||
const theme: Theme = useTheme()
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
padding: theme.spacing(5),
|
||||
<Box
|
||||
sx={{
|
||||
padding: 5,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
overflow: 'auto',
|
||||
}}
|
||||
>
|
||||
<Grid container justifyContent="center" style={{ margin: 'auto' }}>
|
||||
@@ -21,6 +18,6 @@ export const Layout = ({ children }: { children: React.ReactElement }) => {
|
||||
{children}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</div>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ export const createAccount = async (): Promise<TCreateAccount> =>
|
||||
await invoke('create_new_account')
|
||||
|
||||
export const signInWithMnemonic = async (
|
||||
mnemonic: string
|
||||
mnemonic: string,
|
||||
): Promise<TSignInWithMnemonic> =>
|
||||
await invoke('connect_with_mnemonic', { mnemonic })
|
||||
|
||||
@@ -75,15 +75,17 @@ export const bond = async ({
|
||||
export const unbond = async (type: EnumNodeType) =>
|
||||
await invoke(`unbond_${type}`)
|
||||
|
||||
export const getBalance = async (): Promise<Balance> =>
|
||||
export const userBalance = async (): Promise<Balance> =>
|
||||
await invoke('get_balance')
|
||||
|
||||
export const getContractParams = async (): Promise<TauriContractSettingsParams> =>
|
||||
await invoke('get_contract_settings')
|
||||
export const getContractParams =
|
||||
async (): Promise<TauriContractSettingsParams> =>
|
||||
await invoke('get_contract_settings')
|
||||
|
||||
export const setContractParams = async (
|
||||
params: TauriContractSettingsParams
|
||||
): Promise<TauriContractSettingsParams> => await invoke('update_contract_settings', { params })
|
||||
params: TauriContractSettingsParams,
|
||||
): Promise<TauriContractSettingsParams> =>
|
||||
await invoke('update_contract_settings', { params })
|
||||
|
||||
export const getReverseMixDelegations = async (): Promise<TDelegation> =>
|
||||
await invoke('get_reverse_mix_delegations_paged')
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from 'react'
|
||||
import React from 'react'
|
||||
import { Layout } from '../layouts'
|
||||
|
||||
export const NotFound = () => {
|
||||
|
||||
@@ -1,61 +1,41 @@
|
||||
import React, { useEffect } from 'react'
|
||||
import { Button, CircularProgress, Grid } from '@material-ui/core'
|
||||
import { Alert } from '@material-ui/lab'
|
||||
import { Refresh } from '@material-ui/icons'
|
||||
import React, { useContext } from 'react'
|
||||
import { Alert, Button, Grid, Link } from '@mui/material'
|
||||
import { Box } from '@mui/system'
|
||||
import { OpenInNew } from '@mui/icons-material'
|
||||
import { NymCard } from '../components'
|
||||
import { Layout } from '../layouts'
|
||||
import { theme } from '../theme'
|
||||
import { useGetBalance } from '../hooks/useGetBalance'
|
||||
|
||||
import { ClientContext, urls } from '../context/main'
|
||||
|
||||
export const Balance = () => {
|
||||
const { balance, isLoading, error, fetchBalance } = useGetBalance()
|
||||
|
||||
useEffect(fetchBalance, [])
|
||||
|
||||
const RefreshAction = () => (
|
||||
<Button
|
||||
variant="contained"
|
||||
size="small"
|
||||
color="primary"
|
||||
type="submit"
|
||||
data-testid="refresh-button"
|
||||
onClick={fetchBalance}
|
||||
disabled={isLoading}
|
||||
disableElevation
|
||||
startIcon={<Refresh />}
|
||||
endIcon={isLoading && <CircularProgress size={20} />}
|
||||
style={{ marginRight: theme.spacing(2) }}
|
||||
>
|
||||
Refresh
|
||||
</Button>
|
||||
)
|
||||
const { userBalance, clientDetails } = useContext(ClientContext)
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<NymCard title="Check Balance" data-testid="check-balance">
|
||||
<NymCard title="Balance" data-testid="check-balance">
|
||||
<Grid container direction="column" spacing={2}>
|
||||
<Grid item>
|
||||
{error && (
|
||||
<Alert
|
||||
severity="error"
|
||||
data-testid="error-refresh"
|
||||
action={<RefreshAction />}
|
||||
style={{ padding: theme.spacing(2) }}
|
||||
>
|
||||
{error}
|
||||
{userBalance.error && (
|
||||
<Alert severity="error" data-testid="error-refresh" sx={{ p: 2 }}>
|
||||
{userBalance.error}
|
||||
</Alert>
|
||||
)}
|
||||
{!error && (
|
||||
<Alert
|
||||
severity="success"
|
||||
data-testid="refresh-success"
|
||||
style={{ padding: theme.spacing(2, 3) }}
|
||||
action={<RefreshAction />}
|
||||
>
|
||||
{'The current balance is ' + balance?.printable_balance}
|
||||
</Alert>
|
||||
{!userBalance.error && (
|
||||
<Box data-testid="refresh-success" sx={{ p: 2 }}>
|
||||
{'The current balance is ' +
|
||||
userBalance.balance?.printable_balance}
|
||||
</Box>
|
||||
)}
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Link
|
||||
sx={{ pl: 1 }}
|
||||
href={`${urls.blockExplorer}/account/${clientDetails?.client_address}`}
|
||||
target="_blank"
|
||||
>
|
||||
<Button endIcon={<OpenInNew />}>Last transactions</Button>
|
||||
</Link>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</NymCard>
|
||||
</Layout>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import React, { useContext } from 'react'
|
||||
import {
|
||||
Alert,
|
||||
Box,
|
||||
Button,
|
||||
Checkbox,
|
||||
CircularProgress,
|
||||
@@ -8,10 +10,7 @@ import {
|
||||
Grid,
|
||||
InputAdornment,
|
||||
TextField,
|
||||
Theme,
|
||||
} from '@material-ui/core'
|
||||
import { useTheme } from '@material-ui/styles'
|
||||
import { Alert } from '@material-ui/lab'
|
||||
} from '@mui/material'
|
||||
import { yupResolver } from '@hookform/resolvers/yup'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { EnumNodeType } from '../../types/global'
|
||||
@@ -79,7 +78,7 @@ export const BondForm = ({
|
||||
onSuccess,
|
||||
}: {
|
||||
disabled: boolean
|
||||
fees?: { [key in EnumNodeType]: Coin }
|
||||
fees?: { [EnumNodeType.mixnode]: Coin; [EnumNodeType.gateway]?: Coin }
|
||||
onError: (message?: string) => void
|
||||
onSuccess: (message?: string) => void
|
||||
}) => {
|
||||
@@ -95,12 +94,12 @@ export const BondForm = ({
|
||||
defaultValues,
|
||||
})
|
||||
|
||||
const { getBalance } = useContext(ClientContext)
|
||||
const { userBalance } = useContext(ClientContext)
|
||||
|
||||
const watchNodeType = watch('nodeType', defaultValues.nodeType)
|
||||
const watchAdvancedOptions = watch(
|
||||
'withAdvancedOptions',
|
||||
defaultValues.withAdvancedOptions
|
||||
defaultValues.withAdvancedOptions,
|
||||
)
|
||||
|
||||
const onSubmit = async (data: TBondFormFields) => {
|
||||
@@ -114,7 +113,7 @@ export const BondForm = ({
|
||||
|
||||
await bond({ type: data.nodeType, data: formattedData, amount })
|
||||
.then(() => {
|
||||
getBalance.fetchBalance()
|
||||
userBalance.fetchBalance()
|
||||
onSuccess(`Successfully bonded to ${data.identityKey}`)
|
||||
})
|
||||
.catch((e) => {
|
||||
@@ -122,11 +121,9 @@ export const BondForm = ({
|
||||
})
|
||||
}
|
||||
|
||||
const theme: Theme = useTheme()
|
||||
|
||||
return (
|
||||
<FormControl fullWidth>
|
||||
<div style={{ padding: theme.spacing(3, 5) }}>
|
||||
<Box sx={{ padding: [3, 5] }}>
|
||||
<Grid container spacing={3}>
|
||||
<Grid container item justifyContent="space-between">
|
||||
<Grid item>
|
||||
@@ -146,7 +143,7 @@ export const BondForm = ({
|
||||
{`A fee of ${
|
||||
watchNodeType === EnumNodeType.mixnode
|
||||
? fees.mixnode.amount
|
||||
: fees.gateway.amount
|
||||
: fees?.gateway?.amount
|
||||
} PUNK will apply to this transaction`}
|
||||
</Alert>
|
||||
</Grid>
|
||||
@@ -352,15 +349,15 @@ export const BondForm = ({
|
||||
</>
|
||||
)}
|
||||
</Grid>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-end',
|
||||
borderTop: `1px solid ${theme.palette.grey[200]}`,
|
||||
background: theme.palette.grey[100],
|
||||
padding: theme.spacing(2),
|
||||
borderTop: (theme) => `1px solid ${theme.palette.grey[200]}`,
|
||||
bgcolor: (theme) => theme.palette.grey[50],
|
||||
padding: 2,
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
@@ -368,7 +365,6 @@ export const BondForm = ({
|
||||
variant="contained"
|
||||
color="primary"
|
||||
type="submit"
|
||||
size="large"
|
||||
data-testid="submit-button"
|
||||
disableElevation
|
||||
onClick={handleSubmit(onSubmit)}
|
||||
@@ -376,7 +372,7 @@ export const BondForm = ({
|
||||
>
|
||||
Bond
|
||||
</Button>
|
||||
</div>
|
||||
</Box>
|
||||
</FormControl>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import React, { useContext, useEffect, useState } from 'react'
|
||||
import { Box, Button, CircularProgress, Theme } from '@material-ui/core'
|
||||
import { Alert } from '@material-ui/lab'
|
||||
import { useTheme } from '@material-ui/styles'
|
||||
import { Alert, Box, Button, CircularProgress } from '@mui/material'
|
||||
import { BondForm } from './BondForm'
|
||||
import { NymCard } from '../../components'
|
||||
import {
|
||||
@@ -20,9 +18,7 @@ export const Bond = () => {
|
||||
const [fees, setFees] = useState<TFee>()
|
||||
|
||||
const { checkOwnership, ownership } = useCheckOwnership()
|
||||
const { getBalance } = useContext(ClientContext)
|
||||
|
||||
const theme: Theme = useTheme()
|
||||
const { userBalance } = useContext(ClientContext)
|
||||
|
||||
useEffect(() => {
|
||||
if (status === EnumRequestStatus.initial) {
|
||||
@@ -43,14 +39,14 @@ export const Bond = () => {
|
||||
<NymCard title="Bond" subheader="Bond a node or gateway" noPadding>
|
||||
{ownership?.hasOwnership && (
|
||||
<Alert
|
||||
severity="warning"
|
||||
severity="warning"
|
||||
action={
|
||||
<Button
|
||||
disabled={status === EnumRequestStatus.loading}
|
||||
onClick={async () => {
|
||||
setStatus(EnumRequestStatus.loading)
|
||||
await unbond(ownership.nodeType!)
|
||||
getBalance.fetchBalance()
|
||||
userBalance.fetchBalance()
|
||||
setStatus(EnumRequestStatus.initial)
|
||||
}}
|
||||
data-testid="unBond"
|
||||
@@ -58,17 +54,17 @@ export const Bond = () => {
|
||||
Unbond
|
||||
</Button>
|
||||
}
|
||||
style={{ margin: theme.spacing(2) }}
|
||||
style={{ margin: 2 }}
|
||||
>
|
||||
{`Looks like you already have a ${ownership.nodeType} bonded.`}
|
||||
</Alert>
|
||||
)}
|
||||
{status === EnumRequestStatus.loading && (
|
||||
<Box
|
||||
style={{
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
padding: theme.spacing(3),
|
||||
padding: 3,
|
||||
}}
|
||||
>
|
||||
<CircularProgress size={48} />
|
||||
@@ -94,7 +90,9 @@ export const Bond = () => {
|
||||
<RequestStatus
|
||||
status={status}
|
||||
Success={
|
||||
<Alert severity="success" data-testid="bond-success">Successfully bonded node</Alert>
|
||||
<Alert severity="success" data-testid="bond-success">
|
||||
Successfully bonded node
|
||||
</Alert>
|
||||
}
|
||||
Error={
|
||||
<Alert severity="error" data-testid="bond-error">
|
||||
@@ -102,14 +100,14 @@ export const Bond = () => {
|
||||
</Alert>
|
||||
}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-end',
|
||||
borderTop: `1px solid ${theme.palette.grey[200]}`,
|
||||
background: theme.palette.grey[100],
|
||||
padding: theme.spacing(2),
|
||||
borderTop: (theme) => `1px solid ${theme.palette.grey[200]}`,
|
||||
bgcolor: (theme) => theme.palette.grey[50],
|
||||
padding: 2,
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
@@ -120,7 +118,7 @@ export const Bond = () => {
|
||||
>
|
||||
Again?
|
||||
</Button>
|
||||
</div>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</NymCard>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import React, { useContext } from 'react'
|
||||
import {
|
||||
Alert,
|
||||
Box,
|
||||
Button,
|
||||
CircularProgress,
|
||||
FormControl,
|
||||
@@ -8,12 +10,11 @@ import {
|
||||
TextField,
|
||||
Theme,
|
||||
useTheme,
|
||||
} from '@material-ui/core'
|
||||
} from '@mui/material'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { EnumNodeType, TFee } from '../../types'
|
||||
import { yupResolver } from '@hookform/resolvers/yup'
|
||||
import { validationSchema } from './validationSchema'
|
||||
import { Alert } from '@material-ui/lab'
|
||||
import { ClientContext } from '../../context/main'
|
||||
import { delegate, majorToMinor } from '../../requests'
|
||||
import { checkHasEnoughFunds } from '../../utils'
|
||||
@@ -39,7 +40,6 @@ export const DelegateForm = ({
|
||||
onError: (message?: string) => void
|
||||
onSuccess: (message?: string) => void
|
||||
}) => {
|
||||
const theme = useTheme<Theme>()
|
||||
const {
|
||||
register,
|
||||
watch,
|
||||
@@ -53,7 +53,7 @@ export const DelegateForm = ({
|
||||
|
||||
const watchNodeType = watch('nodeType', defaultValues.nodeType)
|
||||
|
||||
const { getBalance } = useContext(ClientContext)
|
||||
const { userBalance } = useContext(ClientContext)
|
||||
|
||||
const onSubmit = async (data: TDelegateForm) => {
|
||||
const hasEnoughFunds = await checkHasEnoughFunds(data.amount)
|
||||
@@ -72,9 +72,9 @@ export const DelegateForm = ({
|
||||
})
|
||||
.then((res) => {
|
||||
onSuccess(
|
||||
`Successfully delegated ${data.amount} punk to ${res.target_address}`
|
||||
`Successfully delegated ${data.amount} punk to ${res.target_address}`,
|
||||
)
|
||||
getBalance.fetchBalance()
|
||||
userBalance.fetchBalance()
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e)
|
||||
@@ -84,7 +84,7 @@ export const DelegateForm = ({
|
||||
|
||||
return (
|
||||
<FormControl fullWidth>
|
||||
<div style={{ padding: theme.spacing(3, 5) }}>
|
||||
<Box sx={{ padding: [3, 5] }}>
|
||||
<Grid container spacing={3}>
|
||||
<Grid container item xs={12} justifyContent="space-between">
|
||||
<Grid item>
|
||||
@@ -126,15 +126,15 @@ export const DelegateForm = ({
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-end',
|
||||
borderTop: `1px solid ${theme.palette.grey[200]}`,
|
||||
background: theme.palette.grey[100],
|
||||
padding: theme.spacing(2),
|
||||
borderTop: (theme) => `1px solid ${theme.palette.grey[200]}`,
|
||||
background: (theme) => theme.palette.grey[50],
|
||||
padding: 2,
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
@@ -149,7 +149,7 @@ export const DelegateForm = ({
|
||||
>
|
||||
Delegate stake
|
||||
</Button>
|
||||
</div>
|
||||
</Box>
|
||||
</FormControl>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { Box, Button, CircularProgress, Theme } from '@material-ui/core'
|
||||
import { useTheme } from '@material-ui/styles'
|
||||
import { Alert, AlertTitle } from '@material-ui/lab'
|
||||
import { Alert, AlertTitle, Box, Button, CircularProgress } from '@mui/material'
|
||||
import { DelegateForm } from './DelegateForm'
|
||||
import { Layout } from '../../layouts'
|
||||
import { NymCard } from '../../components'
|
||||
@@ -14,7 +12,7 @@ import { getGasFee } from '../../requests'
|
||||
|
||||
export const Delegate = () => {
|
||||
const [status, setStatus] = useState<EnumRequestStatus>(
|
||||
EnumRequestStatus.initial
|
||||
EnumRequestStatus.initial,
|
||||
)
|
||||
const [message, setMessage] = useState<string>()
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
@@ -34,7 +32,6 @@ export const Delegate = () => {
|
||||
getFees()
|
||||
}, [])
|
||||
|
||||
const theme: Theme = useTheme()
|
||||
return (
|
||||
<Layout>
|
||||
<NymCard
|
||||
@@ -45,10 +42,10 @@ export const Delegate = () => {
|
||||
>
|
||||
{isLoading && (
|
||||
<Box
|
||||
style={{
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
padding: theme.spacing(3),
|
||||
p: 3,
|
||||
}}
|
||||
>
|
||||
<CircularProgress size={48} />
|
||||
@@ -76,7 +73,7 @@ export const Delegate = () => {
|
||||
<Alert severity="error" data-testid="delegate-error">
|
||||
<AlertTitle>Delegation failed</AlertTitle>
|
||||
An error occurred with the request:
|
||||
<Box style={{ wordBreak: 'break-word' }}>{message}</Box>
|
||||
<Box sx={{ wordBreak: 'break-word' }}>{message}</Box>
|
||||
</Alert>
|
||||
}
|
||||
Success={
|
||||
@@ -86,14 +83,14 @@ export const Delegate = () => {
|
||||
</Alert>
|
||||
}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-end',
|
||||
borderTop: `1px solid ${theme.palette.grey[200]}`,
|
||||
background: theme.palette.grey[100],
|
||||
padding: theme.spacing(2),
|
||||
borderTop: (theme) => `1px solid ${theme.palette.grey[200]}`,
|
||||
background: (theme) => theme.palette.grey[50],
|
||||
p: 2,
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
@@ -104,7 +101,7 @@ export const Delegate = () => {
|
||||
>
|
||||
Finish
|
||||
</Button>
|
||||
</div>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -6,16 +6,12 @@ import { Bond } from './bond'
|
||||
import { Delegate } from './delegate'
|
||||
import { Receive } from './receive'
|
||||
import { Send } from './send'
|
||||
import { SignIn } from './sign-in'
|
||||
import { Unbond } from './unbond'
|
||||
import { Undelegate } from './undelegate'
|
||||
import { InternalDocs } from './internal-docs'
|
||||
|
||||
export const Routes = () => (
|
||||
<Switch>
|
||||
<Route path="/signin">
|
||||
<SignIn />
|
||||
</Route>
|
||||
<Route path="/balance">
|
||||
<Balance />
|
||||
</Route>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react'
|
||||
import { List, ListItem } from '@material-ui/core'
|
||||
import { List, ListItem } from '@mui/material'
|
||||
import { DocEntry } from './DocEntry'
|
||||
|
||||
export const ApiList = () => {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React from 'react'
|
||||
import { Button, Card, TextField } from '@material-ui/core'
|
||||
import { Button, Card, CardContent, TextField } from '@mui/material'
|
||||
import { invoke } from '@tauri-apps/api'
|
||||
import CardContent from '@material-ui/core/CardContent'
|
||||
|
||||
interface DocEntryProps {
|
||||
function: FunctionDef
|
||||
@@ -24,7 +23,7 @@ function collectArgs(functionName: string, args: ArgDef[]) {
|
||||
|
||||
args.forEach((arg) => {
|
||||
let elem: HTMLElement | null = document.getElementById(
|
||||
argKey(functionName, arg.name)
|
||||
argKey(functionName, arg.name),
|
||||
)
|
||||
|
||||
if (arg.type === 'object') {
|
||||
@@ -44,21 +43,21 @@ export const DocEntry = (props: DocEntryProps) => {
|
||||
const onClick = () => {
|
||||
invoke(
|
||||
props.function.name,
|
||||
collectArgs(props.function.name, props.function.args)
|
||||
collectArgs(props.function.name, props.function.args),
|
||||
)
|
||||
.then((result) => {
|
||||
setCard(
|
||||
<Card>
|
||||
<CardContent>{JSON.stringify(result, null, 4)}</CardContent>
|
||||
</Card>
|
||||
</Card>,
|
||||
)
|
||||
})
|
||||
.catch((e) =>
|
||||
setCard(
|
||||
<Card>
|
||||
<CardContent>{e}</CardContent>
|
||||
</Card>
|
||||
)
|
||||
</Card>,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useContext, useState } from 'react'
|
||||
import React, { useContext } from 'react'
|
||||
import { NymCard } from '../../components'
|
||||
import { ApiList } from './ApiList'
|
||||
import { Layout } from '../../layouts'
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import React, { useContext } from 'react'
|
||||
import QRCode from 'qrcode.react'
|
||||
import { Box, Card, Grid, Typography } from '@material-ui/core'
|
||||
import { Alert } from '@material-ui/lab'
|
||||
import { useMediaQuery } from '@material-ui/core'
|
||||
import {
|
||||
Alert,
|
||||
Box,
|
||||
Card,
|
||||
Grid,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
} from '@mui/material'
|
||||
import { CopyToClipboard, NymCard } from '../components'
|
||||
import { Layout } from '../layouts'
|
||||
import { theme } from '../theme'
|
||||
import { ClientContext } from '../context/main'
|
||||
|
||||
export const Receive = () => {
|
||||
@@ -23,12 +27,12 @@ export const Receive = () => {
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Card
|
||||
style={{
|
||||
margin: theme.spacing(1, 0),
|
||||
sx={{
|
||||
m: [1, 0],
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
flexWrap: 'wrap',
|
||||
padding: theme.spacing(3),
|
||||
p: 3,
|
||||
}}
|
||||
variant="outlined"
|
||||
>
|
||||
@@ -42,9 +46,9 @@ export const Receive = () => {
|
||||
<Typography
|
||||
data-testid="client-address"
|
||||
variant={matches ? 'h5' : 'subtitle1'}
|
||||
style={{
|
||||
sx={{
|
||||
wordBreak: 'break-word',
|
||||
marginRight: theme.spacing(1),
|
||||
mr: 1,
|
||||
}}
|
||||
component="span"
|
||||
>
|
||||
@@ -54,15 +58,18 @@ export const Receive = () => {
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Box
|
||||
style={{
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
marginBottom: theme.spacing(2),
|
||||
mb: 2,
|
||||
}}
|
||||
component="div"
|
||||
>
|
||||
{clientDetails && (
|
||||
<QRCode data-testid="qr-code" value={clientDetails.client_address} />
|
||||
<QRCode
|
||||
data-testid="qr-code"
|
||||
value={clientDetails.client_address}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Grid>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React from 'react'
|
||||
import { Card, CircularProgress, Theme, Typography } from '@material-ui/core'
|
||||
import { CheckCircleOutline } from '@material-ui/icons'
|
||||
import { useTheme } from '@material-ui/styles'
|
||||
import { Box, Card, CircularProgress, Typography } from '@mui/material'
|
||||
import { CheckCircleOutline } from '@mui/icons-material'
|
||||
import { SendError } from './SendError'
|
||||
import { TauriTxResult } from '../../types/rust/tauritxresult'
|
||||
|
||||
@@ -14,11 +13,9 @@ export const SendConfirmation = ({
|
||||
error?: string
|
||||
isLoading: boolean
|
||||
}) => {
|
||||
const theme: Theme = useTheme()
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
@@ -30,52 +27,55 @@ export const SendConfirmation = ({
|
||||
{!isLoading && !!error && <SendError message={error} />}
|
||||
{!isLoading && data && (
|
||||
<>
|
||||
<div
|
||||
style={{
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginBottom: theme.spacing(4),
|
||||
mb: 4,
|
||||
}}
|
||||
>
|
||||
<CheckCircleOutline
|
||||
style={{
|
||||
sx={{
|
||||
fontSize: 50,
|
||||
color: theme.palette.success.main,
|
||||
marginBottom: theme.spacing(1),
|
||||
color: 'success.main',
|
||||
mb: 1,
|
||||
}}
|
||||
/>
|
||||
<Typography data-testid="transaction-complete">Transaction complete</Typography>
|
||||
</div>
|
||||
<Typography data-testid="transaction-complete">
|
||||
Transaction complete
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Card
|
||||
variant="outlined"
|
||||
style={{ width: '100%', padding: theme.spacing(2) }}
|
||||
>
|
||||
<div style={{ display: 'flex', marginBottom: theme.spacing(2) }}>
|
||||
<div style={{ width: '33%' }}>
|
||||
<Typography style={{ color: theme.palette.grey[600] }}>
|
||||
<Card variant="outlined" sx={{ width: '100%', p: 2 }}>
|
||||
<Box sx={{ display: 'flex', mb: 2 }}>
|
||||
<Box sx={{ width: '1/3' }}>
|
||||
<Typography sx={{ color: (theme) => theme.palette.grey[600] }}>
|
||||
Recipient
|
||||
</Typography>
|
||||
</div>
|
||||
<div style={{ wordBreak: 'break-all' }}>
|
||||
<Typography data-testid="to-address">{data.to_address}</Typography>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ display: 'flex' }}>
|
||||
<div style={{ width: '33%' }}>
|
||||
<Typography style={{ color: theme.palette.grey[600] }}>
|
||||
</Box>
|
||||
<Box sx={{ wordBreak: 'break-all' }}>
|
||||
<Typography data-testid="to-address">
|
||||
{data.to_address}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex' }}>
|
||||
<Box sx={{ width: '33%' }}>
|
||||
<Typography sx={{ color: (theme) => theme.palette.grey[600] }}>
|
||||
Amount
|
||||
</Typography>
|
||||
</div>
|
||||
<div>
|
||||
<Typography data-testid="send-amount">{data.amount.amount + ' punks'}</Typography>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography data-testid="send-amount">
|
||||
{data.amount.amount + ' punks'}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</Card>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
import React from 'react'
|
||||
import { Card, Theme, Typography } from '@material-ui/core'
|
||||
import { ErrorOutline } from '@material-ui/icons'
|
||||
import { Alert } from '@material-ui/lab'
|
||||
import { useTheme } from '@material-ui/styles'
|
||||
import { Alert, Box, Card, Typography } from '@mui/material'
|
||||
import { ErrorOutline } from '@mui/icons-material'
|
||||
|
||||
export const SendError = ({ message }: { message?: string }) => {
|
||||
const theme: Theme = useTheme()
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
@@ -18,30 +14,25 @@ export const SendError = ({ message }: { message?: string }) => {
|
||||
}}
|
||||
>
|
||||
<>
|
||||
<div
|
||||
style={{
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginBottom: theme.spacing(4),
|
||||
mb: 4,
|
||||
}}
|
||||
>
|
||||
<ErrorOutline
|
||||
style={{ fontSize: 50, color: theme.palette.error.main }}
|
||||
/>
|
||||
<ErrorOutline sx={{ fontSize: 50, color: 'error.main' }} />
|
||||
<Typography>Transaction failed</Typography>
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
<Card
|
||||
variant="outlined"
|
||||
style={{ width: '100%', padding: theme.spacing(2) }}
|
||||
>
|
||||
<Card variant="outlined" sx={{ width: '100%', p: 2 }}>
|
||||
<Alert severity="error" data-testid="transaction-error">
|
||||
An error occured during the request {message}
|
||||
</Alert>
|
||||
</Card>
|
||||
</>
|
||||
</div>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react'
|
||||
import { Grid, InputAdornment, TextField } from '@material-ui/core'
|
||||
import { Grid, InputAdornment, TextField } from '@mui/material'
|
||||
import { useFormContext } from 'react-hook-form'
|
||||
|
||||
export const SendForm = () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useContext, useEffect, useState } from 'react'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import {
|
||||
Card,
|
||||
CircularProgress,
|
||||
@@ -6,10 +6,8 @@ import {
|
||||
Grid,
|
||||
Theme,
|
||||
Typography,
|
||||
} from '@material-ui/core'
|
||||
import { useTheme } from '@material-ui/styles'
|
||||
} from '@mui/material'
|
||||
import { useFormContext } from 'react-hook-form'
|
||||
import { ClientContext } from '../../context/main'
|
||||
import { getGasFee } from '../../requests'
|
||||
|
||||
export const SendReview = () => {
|
||||
@@ -19,8 +17,6 @@ export const SendReview = () => {
|
||||
|
||||
const values = getValues()
|
||||
|
||||
const theme: Theme = useTheme()
|
||||
|
||||
useEffect(() => {
|
||||
const getFee = async () => {
|
||||
const fee = await getGasFee('Send')
|
||||
@@ -33,10 +29,10 @@ export const SendReview = () => {
|
||||
return (
|
||||
<Card
|
||||
variant="outlined"
|
||||
style={{
|
||||
sx={{
|
||||
width: '100%',
|
||||
padding: theme.spacing(2),
|
||||
margin: theme.spacing(3, 0),
|
||||
p: 2,
|
||||
m: [3, 0],
|
||||
}}
|
||||
>
|
||||
{isLoadingFee ? (
|
||||
@@ -44,7 +40,7 @@ export const SendReview = () => {
|
||||
) : (
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<SendReviewField title="From" subtitle={values.from}/>
|
||||
<SendReviewField title="From" subtitle={values.from} />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Divider light />
|
||||
@@ -80,13 +76,12 @@ export const SendReviewField = ({
|
||||
title: string
|
||||
subtitle?: string
|
||||
}) => {
|
||||
const theme: Theme = useTheme()
|
||||
return (
|
||||
<>
|
||||
<Typography style={{ color: theme.palette.grey[600] }}>
|
||||
{title}
|
||||
<Typography style={{ color: 'grey[600]' }}>{title}</Typography>
|
||||
<Typography data-testid={title} style={{ wordBreak: 'break-all' }}>
|
||||
{subtitle}
|
||||
</Typography>
|
||||
<Typography data-testid={title} style={{ wordBreak: 'break-all' }}>{subtitle}</Typography>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import React, { useContext, useEffect, useState } from 'react'
|
||||
import React, { useContext, useState } from 'react'
|
||||
import { useForm, FormProvider } from 'react-hook-form'
|
||||
import { yupResolver } from '@hookform/resolvers/yup'
|
||||
import { Button, Step, StepLabel, Stepper, Theme } from '@material-ui/core'
|
||||
import { useTheme } from '@material-ui/styles'
|
||||
import { Box, Button, Step, StepLabel, Stepper } from '@mui/material'
|
||||
import { SendForm } from './SendForm'
|
||||
import { SendReview } from './SendReview'
|
||||
import { SendConfirmation } from './SendConfirmation'
|
||||
@@ -31,7 +30,7 @@ export const SendWizard = () => {
|
||||
const [requestError, setRequestError] = useState<string>()
|
||||
const [confirmedData, setConfirmedData] = useState<TauriTxResult['details']>()
|
||||
|
||||
const { getBalance } = useContext(ClientContext)
|
||||
const { userBalance } = useContext(ClientContext)
|
||||
|
||||
const steps = ['Enter address', 'Review and send', 'Await confirmation']
|
||||
|
||||
@@ -44,8 +43,6 @@ export const SendWizard = () => {
|
||||
resolver: yupResolver(validationSchema),
|
||||
})
|
||||
|
||||
const theme: Theme = useTheme()
|
||||
|
||||
const handleNextStep = methods.handleSubmit(() => setActiveStep((s) => s + 1))
|
||||
|
||||
const handlePreviousStep = () => setActiveStep((s) => s - 1)
|
||||
@@ -85,7 +82,7 @@ export const SendWizard = () => {
|
||||
amount: { denom: 'Major', amount: formState.amount },
|
||||
})
|
||||
setIsLoading(false)
|
||||
getBalance.fetchBalance()
|
||||
userBalance.fetchBalance()
|
||||
})
|
||||
.catch((e) => {
|
||||
setRequestError(e)
|
||||
@@ -97,13 +94,13 @@ export const SendWizard = () => {
|
||||
|
||||
return (
|
||||
<FormProvider {...methods}>
|
||||
<div style={{ paddingTop: theme.spacing(3) }}>
|
||||
<Box sx={{ pt: 3 }}>
|
||||
<Stepper
|
||||
activeStep={activeStep}
|
||||
style={{
|
||||
background: theme.palette.grey[50],
|
||||
paddingBottom: 0,
|
||||
paddingTop: 0,
|
||||
sx={{
|
||||
bgcolor: 'grey[50]',
|
||||
pb: 0,
|
||||
pt: 0,
|
||||
}}
|
||||
>
|
||||
{steps.map((s, i) => (
|
||||
@@ -112,13 +109,13 @@ export const SendWizard = () => {
|
||||
</Step>
|
||||
))}
|
||||
</Stepper>
|
||||
<div
|
||||
style={{
|
||||
<Box
|
||||
sx={{
|
||||
minHeight: 300,
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
padding: theme.spacing(0, 3),
|
||||
p: [0, 3],
|
||||
}}
|
||||
>
|
||||
{activeStep === 0 ? (
|
||||
@@ -132,21 +129,21 @@ export const SendWizard = () => {
|
||||
error={requestError}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-end',
|
||||
borderTop: `1px solid ${theme.palette.grey[200]}`,
|
||||
background: theme.palette.grey[100],
|
||||
padding: theme.spacing(2),
|
||||
borderTop: (theme) => `1px solid ${theme.palette.grey[200]}`,
|
||||
background: (theme) => theme.palette.grey[50],
|
||||
p: 2,
|
||||
}}
|
||||
>
|
||||
{activeStep === 1 && (
|
||||
<Button
|
||||
disableElevation
|
||||
style={{ marginRight: theme.spacing(1) }}
|
||||
sx={{ mr: 1 }}
|
||||
onClick={handlePreviousStep}
|
||||
data-testid="back-button"
|
||||
>
|
||||
@@ -155,7 +152,7 @@ export const SendWizard = () => {
|
||||
)}
|
||||
<Button
|
||||
variant={activeStep > 0 ? 'contained' : 'text'}
|
||||
color={activeStep > 0 ? 'primary' : 'default'}
|
||||
color={activeStep > 0 ? 'primary' : 'inherit'}
|
||||
disableElevation
|
||||
data-testid="button"
|
||||
onClick={
|
||||
@@ -175,8 +172,8 @@ export const SendWizard = () => {
|
||||
>
|
||||
{activeStep === 0 ? 'Next' : activeStep === 1 ? 'Send' : 'Finish'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
</Box>
|
||||
</FormProvider>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,317 +0,0 @@
|
||||
import React, { useContext, useState } from 'react'
|
||||
import {
|
||||
TextField,
|
||||
CircularProgress,
|
||||
Button,
|
||||
Typography,
|
||||
Grid,
|
||||
Link,
|
||||
Theme,
|
||||
Card,
|
||||
Divider,
|
||||
} from '@material-ui/core'
|
||||
import { Alert } from '@material-ui/lab'
|
||||
import { useTheme } from '@material-ui/styles'
|
||||
import { ArrowBack, CheckCircleOutline } from '@material-ui/icons'
|
||||
import logo from '../images/logo-background.svg'
|
||||
import logo_alt from '../images/logo.png'
|
||||
import { ClientContext } from '../context/main'
|
||||
import { theme } from '../theme'
|
||||
import { createAccount, signInWithMnemonic } from '../requests'
|
||||
import { TCreateAccount } from '../types'
|
||||
import { CopyToClipboard } from '../components'
|
||||
|
||||
export const SignIn = () => {
|
||||
const theme: Theme = useTheme()
|
||||
const [showCreateAccount, setShowCreateAccount] = useState(false)
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
height: '100vh',
|
||||
width: '100vw',
|
||||
display: 'grid',
|
||||
gridTemplateColumns: '400px auto',
|
||||
gridTemplateRows: '100%',
|
||||
gridColumnGap: '0px',
|
||||
gridRowGap: '0px',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
gridArea: '1 / 1 / 2 / 2',
|
||||
background: '#121726',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<img src={logo} style={{ width: 100 }} />
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
gridArea: '1 / 2 / 2 / 3',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
background: theme.palette.grey[100],
|
||||
}}
|
||||
>
|
||||
{showCreateAccount ? (
|
||||
<CreateAccountContent
|
||||
showSignIn={() => setShowCreateAccount(false)}
|
||||
/>
|
||||
) : (
|
||||
<SignInContent showCreateAccount={() => setShowCreateAccount(true)} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const SignInContent = ({
|
||||
showCreateAccount,
|
||||
}: {
|
||||
showCreateAccount: () => void
|
||||
}) => {
|
||||
const [mnemonic, setMnemonic] = useState<string>('')
|
||||
const [inputError, setInputError] = useState<string>()
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
|
||||
const { logIn } = useContext(ClientContext)
|
||||
|
||||
const theme: Theme = useTheme()
|
||||
|
||||
const handleSignIn = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault()
|
||||
|
||||
setIsLoading(true)
|
||||
setInputError(undefined)
|
||||
|
||||
try {
|
||||
const res = await signInWithMnemonic(mnemonic || '')
|
||||
setIsLoading(false)
|
||||
logIn(res)
|
||||
} catch (e: any) {
|
||||
setIsLoading(false)
|
||||
setInputError(e)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<SignInCard>
|
||||
<>
|
||||
<Typography variant="h4" data-testid="sign-in">Sign in</Typography>
|
||||
<form noValidate onSubmit={handleSignIn}>
|
||||
<Grid container direction="column" spacing={1}>
|
||||
<Grid item>
|
||||
<TextField
|
||||
style={{ background: 'white' }}
|
||||
value={mnemonic}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setMnemonic(e.target.value)
|
||||
}
|
||||
size="medium"
|
||||
variant="outlined"
|
||||
margin="normal"
|
||||
required
|
||||
fullWidth
|
||||
id="mnemonic"
|
||||
label="BIP-39 Mnemonic"
|
||||
name="mnemonic"
|
||||
autoComplete="mnemonic"
|
||||
autoFocus
|
||||
disabled={isLoading}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Button
|
||||
fullWidth
|
||||
variant="contained"
|
||||
color="primary"
|
||||
type="submit"
|
||||
disabled={isLoading}
|
||||
endIcon={isLoading && <CircularProgress size={20} />}
|
||||
disableElevation
|
||||
>
|
||||
{!isLoading ? 'Sign In' : 'Signing in'}
|
||||
</Button>
|
||||
</Grid>
|
||||
{inputError && (
|
||||
<Grid item style={{ marginTop: theme.spacing(1) }}>
|
||||
<Alert severity="error">{inputError}</Alert>
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item style={{ marginTop: theme.spacing(1) }}>
|
||||
<Typography variant="body2" component="span">
|
||||
Don't have an account?
|
||||
</Typography>{' '}
|
||||
<Link href="#" onClick={showCreateAccount}>
|
||||
Create one
|
||||
</Link>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</form>
|
||||
</>
|
||||
</SignInCard>
|
||||
)
|
||||
}
|
||||
|
||||
const SignInCard: React.FC = ({ children }) => {
|
||||
const theme: Theme = useTheme()
|
||||
return (
|
||||
<>
|
||||
<Card
|
||||
style={{
|
||||
width: 600,
|
||||
padding: theme.spacing(6, 10),
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
position: 'relative',
|
||||
minHeight: 350,
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={logo_alt}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
width: 425,
|
||||
filter: 'grayscale(100%)',
|
||||
opacity: 0.1,
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(0%, -50%)',
|
||||
}}
|
||||
/>
|
||||
{children}
|
||||
</Card>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const CreateAccountContent = ({ showSignIn }: { showSignIn: () => void }) => {
|
||||
const [accountDetails, setAccountDetails] = useState<TCreateAccount>()
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [error, setError] = useState<Error>()
|
||||
|
||||
const handleCreateAccount = async () => {
|
||||
setIsLoading(true)
|
||||
setError(undefined)
|
||||
try {
|
||||
const res = await createAccount()
|
||||
setTimeout(() => {
|
||||
setAccountDetails(res)
|
||||
setIsLoading(false)
|
||||
}, 2500)
|
||||
} catch (e: any) {
|
||||
setError(e)
|
||||
}
|
||||
}
|
||||
return (
|
||||
<SignInCard>
|
||||
<Typography variant="h4">Create wallet</Typography>
|
||||
<Typography color="textSecondary">
|
||||
Create a new wallet to start using the Nym network
|
||||
</Typography>
|
||||
<Grid
|
||||
container
|
||||
direction="column"
|
||||
spacing={3}
|
||||
style={{ marginTop: theme.spacing(3) }}
|
||||
>
|
||||
<Grid item container justifyContent="center">
|
||||
{isLoading && <CircularProgress size={48} />}
|
||||
{!isLoading && accountDetails && (
|
||||
<>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginBottom: theme.spacing(4),
|
||||
}}
|
||||
>
|
||||
<CheckCircleOutline
|
||||
style={{
|
||||
fontSize: 50,
|
||||
color: theme.palette.success.main,
|
||||
marginBottom: theme.spacing(1),
|
||||
}}
|
||||
/>
|
||||
<Typography>Wallet setup complete</Typography>
|
||||
</div>
|
||||
<Alert severity="info" style={{ marginBottom: theme.spacing(2) }} data-testid="mnemonic-warning">
|
||||
Please store your <strong>mnemonic</strong> in a safe place.
|
||||
You'll need it to access your wallet
|
||||
</Alert>
|
||||
<Card
|
||||
variant="outlined"
|
||||
style={{
|
||||
width: '100%',
|
||||
padding: theme.spacing(2),
|
||||
}}
|
||||
>
|
||||
<Grid container direction="column" spacing={1}>
|
||||
<Grid item>
|
||||
<Typography style={{ color: theme.palette.grey[600] }}>
|
||||
Mnemonic
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Typography data-testid="mnemonic-phrase">{accountDetails.mnemonic}</Typography>
|
||||
<div
|
||||
style={{ display: 'flex', justifyContent: 'flex-end' }}
|
||||
>
|
||||
<CopyToClipboard text={accountDetails.mnemonic} />
|
||||
</div>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Divider light />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Typography style={{ color: theme.palette.grey[600] }}>
|
||||
Address
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Typography data-testid="wallet-address">{accountDetails.client_address}</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Card>
|
||||
</>
|
||||
)}
|
||||
</Grid>
|
||||
{error && (
|
||||
<Grid item style={{ marginTop: theme.spacing(1) }}>
|
||||
<Alert severity="error" data-testid="error">{error}</Alert>
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item>
|
||||
{!accountDetails && (
|
||||
<Button
|
||||
onClick={handleCreateAccount}
|
||||
fullWidth
|
||||
variant="contained"
|
||||
color="primary"
|
||||
type="submit"
|
||||
data-testid="create-button"
|
||||
disableElevation
|
||||
style={{ marginBottom: theme.spacing(1) }}
|
||||
disabled={isLoading}
|
||||
>
|
||||
Create
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
fullWidth
|
||||
variant="text"
|
||||
onClick={showSignIn}
|
||||
data-testid="sign-in-button"
|
||||
startIcon={<ArrowBack />}
|
||||
>
|
||||
Sign in
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</SignInCard>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import {
|
||||
Alert,
|
||||
Button,
|
||||
Card,
|
||||
CardActions,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
CircularProgress,
|
||||
Stack,
|
||||
Typography,
|
||||
} from '@mui/material'
|
||||
import { Box } from '@mui/system'
|
||||
import { ArrowBack } from '@mui/icons-material'
|
||||
import { createAccount } from '../../requests'
|
||||
import { TCreateAccount } from '../../types'
|
||||
import logo from '../../images/logo-background.svg'
|
||||
import { CopyToClipboard } from '../../components'
|
||||
|
||||
export const CreateAccountContent: React.FC<{ showSignIn: () => void }> = ({
|
||||
showSignIn,
|
||||
}) => {
|
||||
const [accountDetails, setAccountDetails] = useState<TCreateAccount>()
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [error, setError] = useState<Error>()
|
||||
|
||||
const handleCreateAccount = async () => {
|
||||
setIsLoading(true)
|
||||
setError(undefined)
|
||||
try {
|
||||
const res = await createAccount()
|
||||
setTimeout(() => {
|
||||
setAccountDetails(res)
|
||||
setIsLoading(false)
|
||||
}, 2500)
|
||||
} catch (e: any) {
|
||||
setError(e)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
handleCreateAccount()
|
||||
}, [])
|
||||
|
||||
if (isLoading) return <CircularProgress size={70} />
|
||||
|
||||
return (
|
||||
<Stack spacing={4} alignItems="center">
|
||||
<img src={logo} width={80} />
|
||||
<Typography sx={{ color: 'common.white' }} variant="h4">
|
||||
Congratulations
|
||||
</Typography>
|
||||
<Typography sx={{ color: 'common.white' }} variant="h6">
|
||||
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>
|
||||
</Alert>
|
||||
<Card
|
||||
variant="outlined"
|
||||
sx={{ bgcolor: 'transparent', p: 2, borderColor: 'common.white' }}
|
||||
>
|
||||
<CardHeader sx={{ color: 'common.white' }} title="Mnemonic" />
|
||||
<CardContent
|
||||
sx={{ color: 'common.white' }}
|
||||
data-testid="mnemonic-phrase"
|
||||
>
|
||||
{accountDetails?.mnemonic}
|
||||
</CardContent>
|
||||
<CardActions sx={{ justifyContent: 'flex-end' }}>
|
||||
<CopyToClipboard text={accountDetails?.mnemonic || ''} light />
|
||||
</CardActions>
|
||||
</Card>
|
||||
<Box sx={{ textAlign: 'center' }}>
|
||||
<Typography sx={{ color: 'common.white' }}>Address:</Typography>
|
||||
<Typography sx={{ color: 'common.white' }} data-testid="wallet-address">
|
||||
{accountDetails?.client_address}
|
||||
</Typography>
|
||||
</Box>
|
||||
{error && (
|
||||
<Alert severity="error" variant="outlined">
|
||||
{error}
|
||||
</Alert>
|
||||
)}
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={showSignIn}
|
||||
data-testid="sign-in-button"
|
||||
startIcon={<ArrowBack />}
|
||||
size="large"
|
||||
sx={{ width: 360 }}
|
||||
>
|
||||
Back to Sign in
|
||||
</Button>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import React, { useContext, useState } from 'react'
|
||||
import { Box } from '@mui/system'
|
||||
import { SignInContent } from './sign-in'
|
||||
import { CreateAccountContent } from './create-account'
|
||||
|
||||
export const SignIn = () => {
|
||||
const [showCreateAccount, setShowCreateAccount] = useState(false)
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
height: '100vh',
|
||||
width: '100vw',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
overflow: 'auto',
|
||||
bgcolor: (theme) => theme.palette.nym.background.dark,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: 500,
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
margin: 'auto',
|
||||
}}
|
||||
>
|
||||
{showCreateAccount ? (
|
||||
<CreateAccountContent
|
||||
showSignIn={() => setShowCreateAccount(false)}
|
||||
/>
|
||||
) : (
|
||||
<SignInContent showCreateAccount={() => setShowCreateAccount(true)} />
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
import React, { useContext, useState } from 'react'
|
||||
import {
|
||||
Button,
|
||||
CircularProgress,
|
||||
Grid,
|
||||
Stack,
|
||||
Link,
|
||||
TextField,
|
||||
Typography,
|
||||
Alert,
|
||||
} from '@mui/material'
|
||||
import { styled } from '@mui/styles'
|
||||
import logo from '../../images/logo-background.svg'
|
||||
import { signInWithMnemonic } from '../../requests'
|
||||
import { ClientContext } from '../../context/main'
|
||||
|
||||
export const SignInContent: React.FC<{ showCreateAccount: () => void }> = ({
|
||||
showCreateAccount,
|
||||
}) => {
|
||||
const [mnemonic, setMnemonic] = useState<string>('')
|
||||
const [inputError, setInputError] = useState<string>()
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
|
||||
const { logIn } = useContext(ClientContext)
|
||||
|
||||
const handleSignIn = async (e: React.MouseEvent<HTMLElement>) => {
|
||||
e.preventDefault()
|
||||
|
||||
setIsLoading(true)
|
||||
setInputError(undefined)
|
||||
|
||||
try {
|
||||
const res = await signInWithMnemonic(mnemonic || '')
|
||||
setIsLoading(false)
|
||||
logIn(res)
|
||||
} catch (e: any) {
|
||||
setIsLoading(false)
|
||||
setInputError(e)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack spacing={3} alignItems="center" sx={{ width: '80%' }}>
|
||||
<img src={logo} style={{ width: 80 }} />
|
||||
<Typography sx={{ color: 'common.white' }}>
|
||||
Enter Mnemonic and sign in
|
||||
</Typography>
|
||||
<Grid container direction="column" spacing={3}>
|
||||
<Grid item style={{ paddingTop: 0 }}>
|
||||
<StyledInput
|
||||
value={mnemonic}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setMnemonic(e.target.value)
|
||||
}
|
||||
size="medium"
|
||||
variant="outlined"
|
||||
margin="normal"
|
||||
required
|
||||
fullWidth
|
||||
id="mnemonic"
|
||||
label="BIP-39 Mnemonic"
|
||||
name="mnemonic"
|
||||
autoComplete="mnemonic"
|
||||
autoFocus
|
||||
disabled={isLoading}
|
||||
sx={{ m: 0 }}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Button
|
||||
fullWidth
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={isLoading}
|
||||
endIcon={isLoading && <CircularProgress size={20} />}
|
||||
disableElevation
|
||||
size="large"
|
||||
onClick={handleSignIn}
|
||||
>
|
||||
{!isLoading ? 'Sign In' : 'Signing in'}
|
||||
</Button>
|
||||
</Grid>
|
||||
{inputError && (
|
||||
<Grid item sx={{ mt: 1 }}>
|
||||
<Alert
|
||||
severity="error"
|
||||
variant="outlined"
|
||||
data-testid="error"
|
||||
sx={{ color: 'error.light' }}
|
||||
>
|
||||
{inputError}
|
||||
</Alert>
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
<div>
|
||||
<Typography sx={{ color: 'common.white' }} component="span">
|
||||
Don't have an account?
|
||||
</Typography>{' '}
|
||||
<Link href="#" onClick={showCreateAccount}>
|
||||
Create one now
|
||||
</Link>
|
||||
</div>
|
||||
</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,20 +1,15 @@
|
||||
import React, { useContext, useEffect, useState } from 'react'
|
||||
import { Alert, Box, Button, CircularProgress, Theme } from '@mui/material'
|
||||
import { NymCard } from '../../components'
|
||||
import { UnbondForm } from './UnbondForm'
|
||||
import { Layout } from '../../layouts'
|
||||
import { useCheckOwnership } from '../../hooks/useCheckOwnership'
|
||||
import { Alert } from '@material-ui/lab'
|
||||
import { Box, Button, CircularProgress, Theme } from '@material-ui/core'
|
||||
import { ClientContext } from '../../context/main'
|
||||
import { unbond } from '../../requests'
|
||||
import { useTheme } from '@material-ui/styles'
|
||||
|
||||
export const Unbond = () => {
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const { checkOwnership, ownership } = useCheckOwnership()
|
||||
const { getBalance } = useContext(ClientContext)
|
||||
|
||||
const theme: Theme = useTheme()
|
||||
const { userBalance } = useContext(ClientContext)
|
||||
|
||||
useEffect(() => {
|
||||
const initialiseForm = async () => {
|
||||
@@ -37,29 +32,29 @@ export const Unbond = () => {
|
||||
onClick={async () => {
|
||||
setIsLoading(true)
|
||||
await unbond(ownership.nodeType!)
|
||||
getBalance.fetchBalance()
|
||||
userBalance.fetchBalance()
|
||||
setIsLoading(false)
|
||||
}}
|
||||
>
|
||||
Unbond
|
||||
</Button>
|
||||
}
|
||||
style={{ margin: theme.spacing(2) }}
|
||||
sx={{ m: 2 }}
|
||||
>
|
||||
{`Looks like you already have a ${ownership.nodeType} bonded.`}
|
||||
</Alert>
|
||||
)}
|
||||
{!ownership.hasOwnership && (
|
||||
<Alert severity="info" style={{ margin: theme.spacing(3) }} data-testid="no-bond">
|
||||
<Alert severity="info" sx={{ m: 3 }} data-testid="no-bond">
|
||||
You don't currently have a bonded node
|
||||
</Alert>
|
||||
)}
|
||||
{isLoading && (
|
||||
<Box
|
||||
style={{
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
padding: theme.spacing(3),
|
||||
p: 3,
|
||||
}}
|
||||
>
|
||||
<CircularProgress size={48} />
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
import React, { useContext, useEffect } from 'react'
|
||||
import { useForm, Controller } from 'react-hook-form'
|
||||
import {
|
||||
Box,
|
||||
Alert,
|
||||
Autocomplete,
|
||||
Button,
|
||||
CircularProgress,
|
||||
FormControl,
|
||||
Grid,
|
||||
TextField,
|
||||
Theme,
|
||||
} from '@material-ui/core'
|
||||
import { Alert, Autocomplete } from '@material-ui/lab'
|
||||
import { useTheme } from '@material-ui/styles'
|
||||
} from '@mui/material'
|
||||
import { yupResolver } from '@hookform/resolvers/yup'
|
||||
import { validationSchema } from './validationSchema'
|
||||
import { NodeTypeSelector } from '../../components/NodeTypeSelector'
|
||||
import { EnumNodeType, TFee } from '../../types'
|
||||
import { ClientContext } from '../../context/main'
|
||||
import { undelegate } from '../../requests'
|
||||
@@ -55,9 +54,7 @@ export const UndelegateForm = ({
|
||||
setValue('identity', '')
|
||||
}, [watchNodeType])
|
||||
|
||||
const { getBalance } = useContext(ClientContext)
|
||||
|
||||
const theme: Theme = useTheme()
|
||||
const { userBalance } = useContext(ClientContext)
|
||||
|
||||
const onSubmit = async (data: TFormData) => {
|
||||
await undelegate({
|
||||
@@ -66,14 +63,14 @@ export const UndelegateForm = ({
|
||||
})
|
||||
.then(async (res) => {
|
||||
onSuccess(`Successfully undelegated from ${res.target_address}`)
|
||||
getBalance.fetchBalance()
|
||||
userBalance.fetchBalance()
|
||||
})
|
||||
.catch((e) => onError(e))
|
||||
}
|
||||
|
||||
return (
|
||||
<FormControl fullWidth>
|
||||
<div style={{ padding: theme.spacing(3, 5) }}>
|
||||
<Box sx={{ p: [3, 5] }}>
|
||||
<Grid container spacing={3} direction="column">
|
||||
<Grid container item xs={12} justifyContent="space-between">
|
||||
<Grid item>
|
||||
@@ -113,15 +110,15 @@ export const UndelegateForm = ({
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-end',
|
||||
borderTop: `1px solid ${theme.palette.grey[200]}`,
|
||||
background: theme.palette.grey[100],
|
||||
padding: theme.spacing(2),
|
||||
borderTop: (theme) => `1px solid ${theme.palette.grey[200]}`,
|
||||
background: (theme) => theme.palette.grey[50],
|
||||
p: 2,
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
@@ -136,7 +133,7 @@ export const UndelegateForm = ({
|
||||
>
|
||||
Undelegate stake
|
||||
</Button>
|
||||
</div>
|
||||
</Box>
|
||||
</FormControl>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { Alert, AlertTitle } from '@material-ui/lab'
|
||||
import { useTheme } from '@material-ui/styles'
|
||||
import { NymCard } from '../../components'
|
||||
import { UndelegateForm } from './UndelegateForm'
|
||||
import { Layout } from '../../layouts'
|
||||
@@ -8,12 +6,8 @@ import {
|
||||
EnumRequestStatus,
|
||||
RequestStatus,
|
||||
} from '../../components/RequestStatus'
|
||||
import { Box, Button, CircularProgress, Theme } from '@material-ui/core'
|
||||
import {
|
||||
getGasFee,
|
||||
getReverseGatewayDelegations,
|
||||
getReverseMixDelegations,
|
||||
} from '../../requests'
|
||||
import { Alert, AlertTitle, Box, Button, CircularProgress } from '@mui/material'
|
||||
import { getGasFee, getReverseMixDelegations } from '../../requests'
|
||||
import { TFee, TDelegation } from '../../types'
|
||||
|
||||
export type TDelegations = {
|
||||
@@ -23,7 +17,7 @@ export type TDelegations = {
|
||||
export const Undelegate = () => {
|
||||
const [message, setMessage] = useState<string>()
|
||||
const [status, setStatus] = useState<EnumRequestStatus>(
|
||||
EnumRequestStatus.initial
|
||||
EnumRequestStatus.initial,
|
||||
)
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [fees, setFees] = useState<TFee>()
|
||||
@@ -57,8 +51,6 @@ export const Undelegate = () => {
|
||||
setIsLoading(false)
|
||||
}
|
||||
|
||||
const theme: Theme = useTheme()
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<NymCard
|
||||
@@ -68,10 +60,10 @@ export const Undelegate = () => {
|
||||
>
|
||||
{isLoading && (
|
||||
<Box
|
||||
style={{
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
padding: theme.spacing(3),
|
||||
p: 3,
|
||||
}}
|
||||
>
|
||||
<CircularProgress size={48} />
|
||||
@@ -111,14 +103,14 @@ export const Undelegate = () => {
|
||||
</Alert>
|
||||
}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-end',
|
||||
borderTop: `1px solid ${theme.palette.grey[200]}`,
|
||||
background: theme.palette.grey[100],
|
||||
padding: theme.spacing(2),
|
||||
borderTop: '1px solid grey[200]',
|
||||
bgcolor: 'grey[50]',
|
||||
p: 2,
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
@@ -130,7 +122,7 @@ export const Undelegate = () => {
|
||||
>
|
||||
Finish
|
||||
</Button>
|
||||
</div>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
import { createTheme } from '@material-ui/core'
|
||||
|
||||
const nymPalette = {
|
||||
primary: {
|
||||
main: '#F4731B',
|
||||
},
|
||||
secondary: {
|
||||
main: '#009FA8',
|
||||
},
|
||||
background: {
|
||||
main: '#121726',
|
||||
},
|
||||
}
|
||||
|
||||
export const theme = createTheme({
|
||||
palette: {
|
||||
primary: {
|
||||
main: nymPalette.primary.main,
|
||||
},
|
||||
secondary: {
|
||||
main: nymPalette.secondary.main,
|
||||
},
|
||||
},
|
||||
shape: {
|
||||
borderRadius: 24,
|
||||
},
|
||||
overrides: {
|
||||
MuiButton: {
|
||||
root: {
|
||||
padding: '12px 24px',
|
||||
},
|
||||
containedPrimary: {
|
||||
color: 'white',
|
||||
},
|
||||
text: {
|
||||
padding: '12px 24px',
|
||||
},
|
||||
outlined: {
|
||||
padding: '12px 24px',
|
||||
},
|
||||
textSizeSmall: {
|
||||
padding: '6px 12px',
|
||||
},
|
||||
outlinedSizeSmall: {
|
||||
padding: '8px 12px',
|
||||
},
|
||||
containedSizeSmall: {
|
||||
padding: '8px 12px',
|
||||
},
|
||||
},
|
||||
|
||||
MuiStepIcon: {
|
||||
text: {
|
||||
fill: '#fff',
|
||||
},
|
||||
},
|
||||
|
||||
MuiTooltip: {
|
||||
tooltipPlacementBottom: {
|
||||
background: nymPalette.background.main,
|
||||
padding: '8px 12px',
|
||||
fontSize: 12,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,19 @@
|
||||
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])
|
||||
return (
|
||||
<ThemeProvider theme={theme}>
|
||||
<CssBaseline />
|
||||
{children}
|
||||
</ThemeProvider>
|
||||
)
|
||||
}
|
||||
Vendored
+86
@@ -0,0 +1,86 @@
|
||||
/* 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'
|
||||
|
||||
/**
|
||||
* If you are unfamiliar with Material UI theming, please read the following first:
|
||||
* - https://mui.com/customization/theming/
|
||||
* - https://mui.com/customization/palette/
|
||||
* - https://mui.com/customization/dark-mode/#dark-mode-with-custom-palette
|
||||
*
|
||||
* This file adds typings to the theme using Typescript's module augmentation.
|
||||
*
|
||||
* Read the following if you are unfamiliar with module augmentation and declaration merging. Then
|
||||
* look at the recommendations from Material UI docs for implementation:
|
||||
* - https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation
|
||||
* - https://www.typescriptlang.org/docs/handbook/declaration-merging.html#merging-interfaces
|
||||
* - https://mui.com/customization/palette/#adding-new-colors
|
||||
*
|
||||
*
|
||||
* IMPORTANT:
|
||||
*
|
||||
* The type augmentation must match MUI's definitions. So, notice the use of `interface` rather than
|
||||
* `type Foo = { ... }` - this is necessary to merge the definitions.
|
||||
*/
|
||||
|
||||
declare module '@mui/material/styles' {
|
||||
/**
|
||||
* This interface defines a palette used across Nym for branding
|
||||
*/
|
||||
interface NymPalette {
|
||||
highlight: string
|
||||
background: { light: string; dark: string }
|
||||
text: {
|
||||
light: string
|
||||
}
|
||||
}
|
||||
|
||||
interface NymPaletteVariant {
|
||||
mode: PaletteMode
|
||||
background: {
|
||||
main: string
|
||||
paper: string
|
||||
}
|
||||
text: {
|
||||
main: string
|
||||
}
|
||||
topNav: {
|
||||
background: string
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A palette definition only for the Nym Wallet that extends the Nym palette
|
||||
*/
|
||||
interface NymWalletPalette {
|
||||
nymWallet: {}
|
||||
}
|
||||
|
||||
interface NymPaletteAndNymWalletPalette {
|
||||
nym: NymPalette & NymWalletPalette
|
||||
}
|
||||
|
||||
type NymPaletteAndNymWalletPaletteOptions =
|
||||
Partial<NymPaletteAndNymWalletPalette>
|
||||
|
||||
/**
|
||||
* Add anything not palette related to the theme here
|
||||
*/
|
||||
interface NymTheme {}
|
||||
|
||||
/**
|
||||
* This augments the definitions of the MUI Theme with the Nym theme, as well as
|
||||
* a partial `ThemeOptions` type used by `createTheme`
|
||||
*
|
||||
* IMPORTANT: only add extensions to the interfaces above, do not modify the lines below
|
||||
*/
|
||||
interface Theme extends NymTheme {}
|
||||
interface ThemeOptions extends Partial<NymTheme> {}
|
||||
interface Palette extends NymPaletteAndNymWalletPalette {}
|
||||
interface PaletteOptions extends NymPaletteAndNymWalletPaletteOptions {}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
import { PaletteMode } from '@mui/material'
|
||||
import {
|
||||
PaletteOptions,
|
||||
NymPalette,
|
||||
NymWalletPalette,
|
||||
ThemeOptions,
|
||||
createTheme,
|
||||
NymPaletteVariant,
|
||||
} from '@mui/material/styles'
|
||||
|
||||
//-----------------------------------------------------------------------------------------------
|
||||
// Nym palette type definitions
|
||||
//
|
||||
|
||||
/**
|
||||
* The Nym palette.
|
||||
*
|
||||
* IMPORTANT: do not export this constant, always use the MUI `useTheme` hook to get the correct
|
||||
* colours for dark/light mode.
|
||||
*/
|
||||
const nymPalette: NymPalette = {
|
||||
/** emphasises important elements */
|
||||
highlight: '#FB6E4E',
|
||||
background: { light: '#F4F6F8', dark: '#121726' },
|
||||
text: {
|
||||
light: '#F2F2F2',
|
||||
},
|
||||
}
|
||||
|
||||
const darkMode: NymPaletteVariant = {
|
||||
mode: 'dark',
|
||||
background: {
|
||||
main: '#121726',
|
||||
paper: '#242C3D',
|
||||
},
|
||||
text: {
|
||||
main: '#F2F2F2',
|
||||
},
|
||||
topNav: {
|
||||
background: '#111826',
|
||||
},
|
||||
}
|
||||
|
||||
const lightMode: NymPaletteVariant = {
|
||||
mode: 'light',
|
||||
background: {
|
||||
main: '#F2F2F2',
|
||||
paper: '#FFFFFF',
|
||||
},
|
||||
text: {
|
||||
main: '#666666',
|
||||
},
|
||||
topNav: {
|
||||
background: '#111826',
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Nym palette specific to the Nym Wallet
|
||||
*
|
||||
* 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 => ({
|
||||
nymWallet: {},
|
||||
})
|
||||
|
||||
//-----------------------------------------------------------------------------------------------
|
||||
// Nym palettes for light and dark mode
|
||||
//
|
||||
|
||||
/**
|
||||
* Map a Nym palette variant onto the MUI palette
|
||||
*/
|
||||
const variantToMUIPalette = (variant: NymPaletteVariant): PaletteOptions => ({
|
||||
text: {
|
||||
primary: variant.text.main,
|
||||
},
|
||||
primary: {
|
||||
main: nymPalette.highlight,
|
||||
contrastText: '#fff',
|
||||
},
|
||||
background: {
|
||||
default: variant.background.main,
|
||||
paper: variant.background.paper,
|
||||
},
|
||||
})
|
||||
|
||||
/**
|
||||
* Returns the Network Explorer palette for light mode.
|
||||
*/
|
||||
const createLightModePalette = (): PaletteOptions => ({
|
||||
nym: {
|
||||
...nymPalette,
|
||||
...nymWalletPalette(lightMode),
|
||||
},
|
||||
...variantToMUIPalette(lightMode),
|
||||
})
|
||||
|
||||
/**
|
||||
* Returns the Network Explorer palette for dark mode.
|
||||
*/
|
||||
const createDarkModePalette = (): PaletteOptions => ({
|
||||
nym: {
|
||||
...nymPalette,
|
||||
...nymWalletPalette(darkMode),
|
||||
},
|
||||
...variantToMUIPalette(darkMode),
|
||||
})
|
||||
|
||||
/**
|
||||
* IMPORANT: if you need to get the default MUI theme, use the following
|
||||
*
|
||||
* import { createTheme as systemCreateTheme } from '@mui/system';
|
||||
*
|
||||
* // get the MUI system defaults for light mode
|
||||
* const systemTheme = systemCreateTheme({ palette: { mode: 'light' } });
|
||||
*
|
||||
*
|
||||
* return {
|
||||
* // change `primary` to default MUI `success`
|
||||
* primary: {
|
||||
* main: systemTheme.palette.success.main,
|
||||
* },
|
||||
* nym: {
|
||||
* ...nymPalette,
|
||||
* ...nymWalletPalette,
|
||||
* },
|
||||
* };
|
||||
*/
|
||||
|
||||
//-----------------------------------------------------------------------------------------------
|
||||
// Nym theme overrides
|
||||
//
|
||||
|
||||
/**
|
||||
* Gets the theme options to be passed to `createTheme`.
|
||||
*
|
||||
* Based on pattern from https://mui.com/customization/dark-mode/#dark-mode-with-custom-palette.
|
||||
*
|
||||
* @param mode The theme mode: 'light' or 'dark'
|
||||
*/
|
||||
export const getDesignTokens = (mode: PaletteMode): ThemeOptions => {
|
||||
// first, create the palette from user's choice of light or dark mode
|
||||
const { palette } = createTheme({
|
||||
palette: {
|
||||
mode,
|
||||
...(mode === 'light'
|
||||
? createLightModePalette()
|
||||
: createDarkModePalette()),
|
||||
},
|
||||
})
|
||||
|
||||
// then customise theme and components
|
||||
return {
|
||||
typography: {
|
||||
fontFamily: [
|
||||
'Open Sans',
|
||||
'sans-serif',
|
||||
'BlinkMacSystemFont',
|
||||
'Roboto',
|
||||
'Oxygen',
|
||||
'Ubuntu',
|
||||
'Helvetica Neue',
|
||||
].join(','),
|
||||
fontSize: 14,
|
||||
fontWeightRegular: 500,
|
||||
button: {
|
||||
textTransform: 'none',
|
||||
fontWeight: '600',
|
||||
},
|
||||
},
|
||||
shape: {
|
||||
borderRadius: 8,
|
||||
},
|
||||
transitions: {
|
||||
duration: {
|
||||
shortest: 150,
|
||||
shorter: 200,
|
||||
short: 250,
|
||||
standard: 300,
|
||||
complex: 375,
|
||||
enteringScreen: 225,
|
||||
leavingScreen: 195,
|
||||
},
|
||||
easing: {
|
||||
easeIn: 'cubic-bezier(0.4, 0, 1, 1)',
|
||||
},
|
||||
},
|
||||
components: {
|
||||
MuiButton: {
|
||||
styleOverrides: {
|
||||
sizeLarge: {
|
||||
height: 55,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
palette,
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,10 @@ export type TCreateAccount = {
|
||||
mnemonic: string
|
||||
} & TSignInWithMnemonic
|
||||
|
||||
export type TFee = { [EnumNodeType.mixnode]: Coin }
|
||||
export type TFee = {
|
||||
[EnumNodeType.mixnode]: Coin
|
||||
[EnumNodeType.gateway]?: Coin
|
||||
}
|
||||
|
||||
export type TDelegation = {
|
||||
delegated_nodes: string[]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { invoke } from '@tauri-apps/api'
|
||||
import bs58 from 'bs58'
|
||||
import { minor, valid } from 'semver'
|
||||
import { getBalance, majorToMinor } from '../requests'
|
||||
import { userBalance, majorToMinor } from '../requests'
|
||||
import { Coin } from '../types'
|
||||
|
||||
export const validateKey = (key: string): boolean => {
|
||||
@@ -18,7 +18,7 @@ export const validateKey = (key: string): boolean => {
|
||||
|
||||
export const validateAmount = async (
|
||||
amount: string,
|
||||
minimum: string
|
||||
minimum: string,
|
||||
): Promise<boolean> => {
|
||||
// tests basic coin value requirements, like no more than 6 decimal places, value lower than total supply, etc
|
||||
if (!Number(amount)) {
|
||||
@@ -93,6 +93,6 @@ export const truncate = (text: string, trim: number) =>
|
||||
|
||||
export const checkHasEnoughFunds = async (allocationValue: string) => {
|
||||
const minorValue = await majorToMinor(allocationValue)
|
||||
const walletValue = await getBalance()
|
||||
const walletValue = await userBalance()
|
||||
return !(+walletValue.coin.amount - +minorValue.amount < 0)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"allowSyntheticDefaultImports": true,
|
||||
/* Visit https://aka.ms/tsconfig.json to read more about this file */
|
||||
|
||||
/* Basic Options */
|
||||
|
||||
@@ -10,6 +10,9 @@ module.exports = {
|
||||
compress: true,
|
||||
historyApiFallback: true,
|
||||
hot: true,
|
||||
client: {
|
||||
overlay: false,
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
new HtmlWebpackPlugin({
|
||||
|
||||
+2108
-1940
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user