Merge pull request #704 from nymtech/feature/receive-coins-page
Feature/receive coins page + UI tweaks
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
import React from 'react'
|
||||
import { Grid, Theme, useTheme } from '@material-ui/core'
|
||||
|
||||
export const Layout = ({ children }: { children: React.ReactElement }) => {
|
||||
const theme: Theme = useTheme()
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
padding: theme.spacing(5),
|
||||
}}
|
||||
>
|
||||
<Grid container justify="center">
|
||||
<Grid item xs={12} md={8} xl={6}>
|
||||
{children}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -15,19 +15,20 @@ import {
|
||||
import React, { useContext } from 'react'
|
||||
import Link from 'next/link'
|
||||
import VpnKeyIcon from '@material-ui/icons/VpnKey'
|
||||
import {
|
||||
ExitToApp,
|
||||
AccountBalanceWallet,
|
||||
MonetizationOn,
|
||||
AttachMoney,
|
||||
MoneyOff,
|
||||
Menu,
|
||||
HowToVote,
|
||||
Cancel,
|
||||
Pageview,
|
||||
} from '@material-ui/icons'
|
||||
import AccountBalanceWalletIcon from '@material-ui/icons/AccountBalanceWallet'
|
||||
import { ValidatorClientContext } from '../contexts/ValidatorClient'
|
||||
import { ADMIN_ADDRESS } from '../pages/_app'
|
||||
import {
|
||||
Menu,
|
||||
AttachMoney,
|
||||
MoneyOff,
|
||||
HowToVote,
|
||||
Cancel,
|
||||
ArrowForward,
|
||||
ArrowBack,
|
||||
ExitToApp,
|
||||
} from '@material-ui/icons'
|
||||
|
||||
import { makeBasicStyle } from '../common/helpers'
|
||||
import { theme } from '../lib/theme'
|
||||
|
||||
@@ -53,7 +54,7 @@ export default function MainNav() {
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<>
|
||||
<AppBar position="absolute" color="default" className={classes.appBar}>
|
||||
<Toolbar>
|
||||
<IconButton
|
||||
@@ -83,7 +84,7 @@ export default function MainNav() {
|
||||
|
||||
<ListItem button>
|
||||
<ListItemIcon>
|
||||
<AccountBalanceWallet />
|
||||
<AccountBalanceWalletIcon />
|
||||
</ListItemIcon>
|
||||
<Link href="/balanceCheck">
|
||||
<ListItemText primary="Check Balance" />
|
||||
@@ -92,13 +93,22 @@ export default function MainNav() {
|
||||
|
||||
<ListItem button>
|
||||
<ListItemIcon>
|
||||
<MonetizationOn />
|
||||
<ArrowForward />
|
||||
</ListItemIcon>
|
||||
<Link href="/send">
|
||||
<ListItemText primary="Send coins" />
|
||||
</Link>
|
||||
</ListItem>
|
||||
|
||||
<ListItem button>
|
||||
<ListItemIcon>
|
||||
<ArrowBack />
|
||||
</ListItemIcon>
|
||||
<Link href="/receive">
|
||||
<ListItemText primary="Receive coins" />
|
||||
</Link>
|
||||
</ListItem>
|
||||
|
||||
<Divider />
|
||||
|
||||
{/*<ListItem>*/}
|
||||
@@ -132,15 +142,6 @@ export default function MainNav() {
|
||||
</Link>
|
||||
</ListItem>
|
||||
|
||||
<ListItem button>
|
||||
<ListItemIcon>
|
||||
<Pageview />
|
||||
</ListItemIcon>
|
||||
<Link href="/checkDelegation">
|
||||
<ListItemText primary="Check current delegation" />
|
||||
</Link>
|
||||
</ListItem>
|
||||
|
||||
<ListItem button>
|
||||
<ListItemIcon>
|
||||
<Cancel />
|
||||
@@ -158,7 +159,7 @@ export default function MainNav() {
|
||||
</ListItem>
|
||||
|
||||
{adminPageDisplayed && (
|
||||
<React.Fragment>
|
||||
<>
|
||||
<Divider />
|
||||
<ListItem button>
|
||||
<ListItemIcon>
|
||||
@@ -169,7 +170,7 @@ export default function MainNav() {
|
||||
<ListItemText primary="Admin" />
|
||||
</Link>
|
||||
</ListItem>
|
||||
</React.Fragment>
|
||||
</>
|
||||
)}
|
||||
</List>
|
||||
</div>
|
||||
@@ -185,6 +186,6 @@ export default function MainNav() {
|
||||
</div>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
</React.Fragment>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import React from 'react'
|
||||
import { Card, CardContent, CardHeader, useTheme } from '@material-ui/core'
|
||||
|
||||
export const NymCard = ({
|
||||
title,
|
||||
subheader,
|
||||
children,
|
||||
}: {
|
||||
title: string
|
||||
subheader?: string
|
||||
children: React.ReactElement
|
||||
}) => {
|
||||
const theme = useTheme()
|
||||
return (
|
||||
<Card variant="outlined">
|
||||
<CardHeader
|
||||
title={title}
|
||||
subheader={subheader}
|
||||
titleTypographyProps={{ variant: 'h5' }}
|
||||
subheaderTypographyProps={{ variant: 'subtitle1' }}
|
||||
style={{
|
||||
padding: theme.spacing(2.5),
|
||||
borderBottom: `1px solid ${theme.palette.grey[200]}`,
|
||||
}}
|
||||
/>
|
||||
<CardContent style={{ background: theme.palette.grey[50] }}>
|
||||
{children}
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -2,13 +2,14 @@ import React, { useEffect, useState, ChangeEvent } from 'react'
|
||||
import { printableBalanceToNative } from '@nymproject/nym-validator-client/dist/currency'
|
||||
import { Coin, nativeToPrintable } from '@nymproject/nym-validator-client'
|
||||
import { Alert } from '@material-ui/lab'
|
||||
import Grid from '@material-ui/core/Grid'
|
||||
import TextField from '@material-ui/core/TextField'
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
FormControlLabel,
|
||||
InputAdornment,
|
||||
Grid,
|
||||
TextField,
|
||||
useMediaQuery,
|
||||
} from '@material-ui/core'
|
||||
import bs58 from 'bs58'
|
||||
import semver from 'semver'
|
||||
@@ -81,6 +82,8 @@ export default function BondNodeForm(props: TBondNodeFormProps) {
|
||||
getBalance()
|
||||
}, [getBalance])
|
||||
|
||||
const matches = useMediaQuery('(min-width:768px)')
|
||||
|
||||
const handleCheckboxToggle = () => {
|
||||
setAdvancedShown((prevSet) => !prevSet)
|
||||
}
|
||||
@@ -310,11 +313,13 @@ export default function BondNodeForm(props: TBondNodeFormProps) {
|
||||
<Grid item xs={12} sm={8}>
|
||||
<TextField
|
||||
required
|
||||
id='amount'
|
||||
name='amount'
|
||||
label={`Amount to bond (minimum ${nativeToPrintable(
|
||||
minimumBond.amount
|
||||
)} ${minimumBond.denom})`}
|
||||
id="amount"
|
||||
name="amount"
|
||||
label={`Amount to bond ${
|
||||
matches
|
||||
? '(minimum ' + nativeToPrintable(minimumBond.amount) + ')'
|
||||
: ''
|
||||
}`}
|
||||
error={!validateAmount}
|
||||
helperText={
|
||||
!validateAmount
|
||||
@@ -326,7 +331,7 @@ export default function BondNodeForm(props: TBondNodeFormProps) {
|
||||
fullWidth
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position='end'>{DENOM}</InputAdornment>
|
||||
<InputAdornment position="end">{DENOM}</InputAdornment>
|
||||
),
|
||||
}}
|
||||
onChange={handleAmountChange}
|
||||
@@ -343,9 +348,9 @@ export default function BondNodeForm(props: TBondNodeFormProps) {
|
||||
<TextField
|
||||
error={!validity.validIdentityKey}
|
||||
required
|
||||
id='identity'
|
||||
name='identity'
|
||||
label='Identity key'
|
||||
id="identity"
|
||||
name="identity"
|
||||
label="Identity key"
|
||||
fullWidth
|
||||
/>
|
||||
</Grid>
|
||||
@@ -353,9 +358,9 @@ export default function BondNodeForm(props: TBondNodeFormProps) {
|
||||
<TextField
|
||||
error={!validity.validSphinxKey}
|
||||
required
|
||||
id='sphinxKey'
|
||||
name='sphinxKey'
|
||||
label='Sphinx key'
|
||||
id="sphinxKey"
|
||||
name="sphinxKey"
|
||||
label="Sphinx key"
|
||||
fullWidth
|
||||
{...(!validity.validSphinxKey
|
||||
? { helperText: 'Enter a valid sphinx key' }
|
||||
@@ -366,9 +371,9 @@ export default function BondNodeForm(props: TBondNodeFormProps) {
|
||||
<TextField
|
||||
error={!validity.validHost}
|
||||
required
|
||||
id='host'
|
||||
name='host'
|
||||
label='Host'
|
||||
id="host"
|
||||
name="host"
|
||||
label="Host"
|
||||
fullWidth
|
||||
{...(!validity.validHost
|
||||
? { helperText: 'Enter a valid IP or a hostname (without port)' }
|
||||
@@ -382,9 +387,9 @@ export default function BondNodeForm(props: TBondNodeFormProps) {
|
||||
<TextField
|
||||
error={!validity.validLocation}
|
||||
required
|
||||
id='location'
|
||||
name='location'
|
||||
label='Location'
|
||||
id="location"
|
||||
name="location"
|
||||
label="Location"
|
||||
fullWidth
|
||||
{...(!validity.validLocation
|
||||
? { helperText: 'Enter a valid location of your node' }
|
||||
@@ -397,9 +402,9 @@ export default function BondNodeForm(props: TBondNodeFormProps) {
|
||||
<TextField
|
||||
error={!validity.validVersion}
|
||||
required
|
||||
id='version'
|
||||
name='version'
|
||||
label='Version'
|
||||
id="version"
|
||||
name="version"
|
||||
label="Version"
|
||||
fullWidth
|
||||
{...(!validity.validVersion
|
||||
? {
|
||||
@@ -418,7 +423,7 @@ export default function BondNodeForm(props: TBondNodeFormProps) {
|
||||
onChange={handleCheckboxToggle}
|
||||
/>
|
||||
}
|
||||
label='Show advanced options'
|
||||
label="Show advanced options"
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
@@ -427,10 +432,10 @@ export default function BondNodeForm(props: TBondNodeFormProps) {
|
||||
<Grid item xs={12} sm={4}>
|
||||
<TextField
|
||||
error={!validity.validMixPort}
|
||||
variant='outlined'
|
||||
id='mixPort'
|
||||
name='mixPort'
|
||||
label='Mix Port'
|
||||
variant="outlined"
|
||||
id="mixPort"
|
||||
name="mixPort"
|
||||
label="Mix Port"
|
||||
fullWidth
|
||||
defaultValue={DEFAULT_MIX_PORT}
|
||||
{...(!validity.validMixPort
|
||||
@@ -445,10 +450,10 @@ export default function BondNodeForm(props: TBondNodeFormProps) {
|
||||
<Grid item xs={12} sm={4}>
|
||||
<TextField
|
||||
error={!validity.validVerlocPort}
|
||||
variant='outlined'
|
||||
id='verlocPort'
|
||||
name='verlocPort'
|
||||
label='Verloc Port'
|
||||
variant="outlined"
|
||||
id="verlocPort"
|
||||
name="verlocPort"
|
||||
label="Verloc Port"
|
||||
fullWidth
|
||||
defaultValue={DEFAULT_VERLOC_PORT}
|
||||
/>
|
||||
@@ -457,10 +462,10 @@ export default function BondNodeForm(props: TBondNodeFormProps) {
|
||||
<Grid item xs={12} sm={4}>
|
||||
<TextField
|
||||
error={!validity.validHttpApiPort}
|
||||
variant='outlined'
|
||||
id='httpApiPort'
|
||||
name='httpApiPort'
|
||||
label='HTTP API Port'
|
||||
variant="outlined"
|
||||
id="httpApiPort"
|
||||
name="httpApiPort"
|
||||
label="HTTP API Port"
|
||||
fullWidth
|
||||
defaultValue={DEFAULT_HTTP_API_PORT}
|
||||
/>
|
||||
@@ -470,10 +475,10 @@ export default function BondNodeForm(props: TBondNodeFormProps) {
|
||||
<Grid item xs={12} sm={4}>
|
||||
<TextField
|
||||
error={!validity.validClientsPort}
|
||||
variant='outlined'
|
||||
id='clientsPort'
|
||||
name='clientsPort'
|
||||
label='client WS API Port'
|
||||
variant="outlined"
|
||||
id="clientsPort"
|
||||
name="clientsPort"
|
||||
label="client WS API Port"
|
||||
fullWidth
|
||||
defaultValue={DEFAULT_CLIENTS_PORT}
|
||||
/>
|
||||
@@ -485,9 +490,9 @@ export default function BondNodeForm(props: TBondNodeFormProps) {
|
||||
|
||||
<div className={classes.buttons}>
|
||||
<Button
|
||||
variant='contained'
|
||||
color='primary'
|
||||
type='submit'
|
||||
variant="contained"
|
||||
color="primary"
|
||||
type="submit"
|
||||
className={classes.button}
|
||||
disabled={!isValidAmount}
|
||||
>
|
||||
|
||||
@@ -2,6 +2,8 @@ import React, { useContext, useEffect } from 'react'
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
import { Grid, LinearProgress, Paper } from '@material-ui/core'
|
||||
import { Gateway, MixNode } from '@nymproject/nym-validator-client/dist/types'
|
||||
import { coin } from '@nymproject/nym-validator-client'
|
||||
import { printableBalanceToNative } from '@nymproject/nym-validator-client/dist/currency'
|
||||
import Confirmation from '../Confirmation'
|
||||
import { ValidatorClientContext } from '../../contexts/ValidatorClient'
|
||||
import NoClientError from '../NoClientError'
|
||||
@@ -13,8 +15,6 @@ import { theme } from '../../lib/theme'
|
||||
import { checkNodesOwnership, makeBasicStyle } from '../../common/helpers'
|
||||
import NodeTypeChooser from '../NodeTypeChooser'
|
||||
import ExecFeeNotice from '../ExecFeeNotice'
|
||||
import { printableBalanceToNative } from '@nymproject/nym-validator-client/dist/currency'
|
||||
import { coin } from '@nymproject/nym-validator-client'
|
||||
import { UDENOM } from '../../pages/_app'
|
||||
|
||||
export type BondingInformation = {
|
||||
@@ -161,22 +161,14 @@ const BondNode = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<main className={classes.layout}>
|
||||
<Paper className={classes.paper}>
|
||||
<ExecFeeNotice name="bonding" />
|
||||
<Typography
|
||||
component="h1"
|
||||
variant="h4"
|
||||
align="center"
|
||||
className={classes.wrapper}
|
||||
>
|
||||
Bond a {nodeType}
|
||||
</Typography>
|
||||
{getBondContent()}
|
||||
</Paper>
|
||||
</main>
|
||||
</>
|
||||
<Grid container spacing={2} direction="column">
|
||||
<Grid item>
|
||||
<ExecFeeNotice name="bonding" />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Paper style={{ padding: theme.spacing(3) }}>{getBondContent()}</Paper>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -83,13 +83,13 @@ export default function DelegateForm(props: DelegateFormProps) {
|
||||
|
||||
return (
|
||||
<form onSubmit={submitForm}>
|
||||
<Grid container spacing={3}>
|
||||
<Grid container spacing={3} direction="column">
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
required
|
||||
id='identity'
|
||||
name='identity'
|
||||
label='Node identity'
|
||||
id="identity"
|
||||
name="identity"
|
||||
label="Node identity"
|
||||
error={!validIdentity}
|
||||
helperText={
|
||||
validIdentity
|
||||
@@ -100,25 +100,25 @@ export default function DelegateForm(props: DelegateFormProps) {
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Grid item xs={12} lg={6}>
|
||||
<TextField
|
||||
required
|
||||
id='amount'
|
||||
name='amount'
|
||||
label='Amount to delegate'
|
||||
id="amount"
|
||||
name="amount"
|
||||
label="Amount to delegate"
|
||||
error={!isValidAmount}
|
||||
helperText={isValidAmount ? '' : 'Please enter a valid amount'}
|
||||
onChange={handleAmountChange}
|
||||
fullWidth
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position='end'>{DENOM}</InputAdornment>
|
||||
<InputAdornment position="end">{DENOM}</InputAdornment>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
{allocationWarning && (
|
||||
<Grid item>
|
||||
<Grid item xs={12} lg={6}>
|
||||
<Alert severity={!isValidAmount ? 'error' : 'info'}>
|
||||
{allocationWarning}
|
||||
</Alert>
|
||||
@@ -140,9 +140,9 @@ export default function DelegateForm(props: DelegateFormProps) {
|
||||
</Grid>
|
||||
<div className={classes.buttons}>
|
||||
<Button
|
||||
variant='contained'
|
||||
color='primary'
|
||||
type='submit'
|
||||
variant="contained"
|
||||
color="primary"
|
||||
type="submit"
|
||||
className={classes.button}
|
||||
disabled={!isValidAmount}
|
||||
>
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import React, { useContext, useEffect } from 'react'
|
||||
import { Paper } from '@material-ui/core'
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
import { Grid, Paper } from '@material-ui/core'
|
||||
import { coin, printableCoin } from '@nymproject/nym-validator-client'
|
||||
import { useRouter } from 'next/router'
|
||||
import { printableBalanceToNative } from '@nymproject/nym-validator-client/dist/currency'
|
||||
import { ValidatorClientContext } from '../../contexts/ValidatorClient'
|
||||
import { NodeType } from '../../common/node'
|
||||
import NoClientError from '../NoClientError'
|
||||
import Confirmation from '../Confirmation'
|
||||
import DelegateForm from './DelegateForm'
|
||||
import { printableBalanceToNative } from '@nymproject/nym-validator-client/dist/currency'
|
||||
import { Coin } from '@cosmjs/launchpad'
|
||||
import { coin, printableCoin } from '@nymproject/nym-validator-client'
|
||||
import { UDENOM } from '../../pages/_app'
|
||||
import { theme } from '../../lib/theme'
|
||||
import { makeBasicStyle } from '../../common/helpers'
|
||||
@@ -101,22 +100,16 @@ const DelegateToNode = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<main className={classes.layout}>
|
||||
<Paper className={classes.paper}>
|
||||
<ExecFeeNotice name={'delegating stake'} />
|
||||
<Typography
|
||||
component="h1"
|
||||
variant="h4"
|
||||
align="center"
|
||||
className={classes.wrapper}
|
||||
>
|
||||
Delegate to {nodeType}
|
||||
</Typography>
|
||||
<Grid container spacing={2} direction="column">
|
||||
<Grid item>
|
||||
<ExecFeeNotice name={'delegating stake'} />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Paper style={{ padding: theme.spacing(3) }}>
|
||||
{getDelegationContent()}
|
||||
</Paper>
|
||||
</main>
|
||||
</>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React, { useContext, useEffect } from 'react'
|
||||
import { printableCoin } from '@nymproject/nym-validator-client'
|
||||
import { Paper } from '@material-ui/core'
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
import { printableCoin } from '@nymproject/nym-validator-client'
|
||||
import { useRouter } from 'next/router'
|
||||
import { ValidatorClientContext } from '../../contexts/ValidatorClient'
|
||||
import { NodeType } from '../../common/node'
|
||||
@@ -107,21 +106,9 @@ const DelegationCheck = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<main className={classes.layout}>
|
||||
<Paper className={classes.paper}>
|
||||
<Typography
|
||||
component="h1"
|
||||
variant="h4"
|
||||
align="center"
|
||||
className={classes.wrapper}
|
||||
>
|
||||
Check your stake on a {nodeType}
|
||||
</Typography>
|
||||
{getDelegationCheckContent()}
|
||||
</Paper>
|
||||
</main>
|
||||
</>
|
||||
<Paper style={{ padding: theme.spacing(3) }}>
|
||||
{getDelegationCheckContent()}
|
||||
</Paper>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './NymCard'
|
||||
export * from './Layout'
|
||||
@@ -1,81 +1,78 @@
|
||||
import React from 'react';
|
||||
import Grid from '@material-ui/core/Grid';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import TextField from '@material-ui/core/TextField';
|
||||
import { DENOM } from '../../pages/_app';
|
||||
import { InputAdornment } from "@material-ui/core";
|
||||
import React from 'react'
|
||||
import Grid from '@material-ui/core/Grid'
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
import TextField from '@material-ui/core/TextField'
|
||||
import { DENOM } from '../../pages/_app'
|
||||
import { InputAdornment } from '@material-ui/core'
|
||||
|
||||
type SendNymFormProps = {
|
||||
address: string,
|
||||
setFormStatus: (nonEmpty: boolean) => void,
|
||||
address: string
|
||||
setFormStatus: (nonEmpty: boolean) => void
|
||||
}
|
||||
|
||||
export default function SendNymForm({ address, setFormStatus }: SendNymFormProps) {
|
||||
const [recipientHasValue, setRecipientHasValue] = React.useState(false)
|
||||
const [amountHasValue, setAmountHasValue] = React.useState(false)
|
||||
const [validAmount, setValidAmount] = React.useState(true)
|
||||
|
||||
const handleInputData = (element) => (event) => {
|
||||
if (element === "recipient") {
|
||||
let nonZeroRecipient = event.target.value.length > 0
|
||||
setRecipientHasValue(nonZeroRecipient)
|
||||
setFormStatus(nonZeroRecipient && amountHasValue)
|
||||
} else if (element === "amount") {
|
||||
let nonZeroAmount = event.target.value.length > 0
|
||||
setAmountHasValue(nonZeroAmount)
|
||||
setFormStatus(recipientHasValue && nonZeroAmount)
|
||||
if (nonZeroAmount) {
|
||||
// don't ask me about that. javascript works in mysterious ways
|
||||
// and this is apparently a good way of checking if string
|
||||
// is purely made of numeric characters
|
||||
let parsed = +event.target.value
|
||||
if (isNaN(parsed)) {
|
||||
setValidAmount(false)
|
||||
} else {
|
||||
setValidAmount(true)
|
||||
}
|
||||
}
|
||||
export default function SendNymForm({
|
||||
address,
|
||||
setFormStatus,
|
||||
}: SendNymFormProps) {
|
||||
const [recipientHasValue, setRecipientHasValue] = React.useState(false)
|
||||
const [amountHasValue, setAmountHasValue] = React.useState(false)
|
||||
const [validAmount, setValidAmount] = React.useState(true)
|
||||
|
||||
const handleInputData = (element) => (event) => {
|
||||
if (element === 'recipient') {
|
||||
let nonZeroRecipient = event.target.value.length > 0
|
||||
setRecipientHasValue(nonZeroRecipient)
|
||||
setFormStatus(nonZeroRecipient && amountHasValue)
|
||||
} else if (element === 'amount') {
|
||||
let nonZeroAmount = event.target.value.length > 0
|
||||
setAmountHasValue(nonZeroAmount)
|
||||
setFormStatus(recipientHasValue && nonZeroAmount)
|
||||
if (nonZeroAmount) {
|
||||
// don't ask me about that. javascript works in mysterious ways
|
||||
// and this is apparently a good way of checking if string
|
||||
// is purely made of numeric characters
|
||||
let parsed = +event.target.value
|
||||
if (isNaN(parsed)) {
|
||||
setValidAmount(false)
|
||||
} else {
|
||||
setValidAmount(true)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Enter recipient address and the amount
|
||||
</Typography>
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="h6">Sending from {address}</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
required
|
||||
id="recipient"
|
||||
name="recipient"
|
||||
label="Recipient address"
|
||||
onChange={handleInputData("recipient")}
|
||||
fullWidth
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<TextField
|
||||
required
|
||||
id="amount"
|
||||
name="amount"
|
||||
label="Amount"
|
||||
error={!validAmount}
|
||||
helperText={validAmount ? "" : "Please enter a valid amount"}
|
||||
onChange={handleInputData("amount")}
|
||||
fullWidth
|
||||
InputProps={{
|
||||
endAdornment:
|
||||
<InputAdornment position="end">{DENOM}</InputAdornment>
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
);
|
||||
return (
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="subtitle1">Sending from {address}</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
required
|
||||
id="recipient"
|
||||
name="recipient"
|
||||
label="Recipient address"
|
||||
onChange={handleInputData('recipient')}
|
||||
fullWidth
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<TextField
|
||||
required
|
||||
id="amount"
|
||||
name="amount"
|
||||
label="Amount"
|
||||
error={!validAmount}
|
||||
helperText={validAmount ? '' : 'Please enter a valid amount'}
|
||||
onChange={handleInputData('amount')}
|
||||
fullWidth
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">{DENOM}</InputAdornment>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useContext, useEffect } from 'react'
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
import { Grid, LinearProgress, Paper } from '@material-ui/core'
|
||||
import { Grid, LinearProgress, Paper, Typography } from '@material-ui/core'
|
||||
import { Alert } from '@material-ui/lab'
|
||||
import { useRouter } from 'next/router'
|
||||
import { NodeType } from '../../common/node'
|
||||
import { ValidatorClientContext } from '../../contexts/ValidatorClient'
|
||||
@@ -95,9 +95,9 @@ const UnbondNode = () => {
|
||||
return (
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12}>
|
||||
<Typography gutterBottom>
|
||||
<Alert severity="info">
|
||||
You do not currently have a mixnode or a gateway bonded.
|
||||
</Typography>
|
||||
</Alert>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
@@ -126,22 +126,16 @@ const UnbondNode = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<main className={classes.layout}>
|
||||
<Paper className={classes.paper}>
|
||||
<ExecFeeNotice name={'unbonding'} />
|
||||
<Typography
|
||||
component="h1"
|
||||
variant="h4"
|
||||
align="center"
|
||||
className={classes.wrapper}
|
||||
>
|
||||
Unbond a {headerText}
|
||||
</Typography>
|
||||
<Grid container spacing={2} direction="column">
|
||||
<Grid item>
|
||||
<ExecFeeNotice name={'unbonding'} />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Paper style={{ padding: theme.spacing(3) }}>
|
||||
{getUnbondContent()}
|
||||
</Paper>
|
||||
</main>
|
||||
</>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React, { useContext, useEffect, useState } from 'react'
|
||||
import { Paper } from '@material-ui/core'
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
import React, { useContext, useEffect } from 'react'
|
||||
import { Grid, Paper } from '@material-ui/core'
|
||||
import { useRouter } from 'next/router'
|
||||
import { ValidatorClientContext } from '../../contexts/ValidatorClient'
|
||||
import { NodeType } from '../../common/node'
|
||||
@@ -90,22 +89,16 @@ const UndelegateFromNode = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<main className={classes.layout}>
|
||||
<Paper className={classes.paper}>
|
||||
<ExecFeeNotice name={'undelegating stake'} />
|
||||
<Typography
|
||||
component="h1"
|
||||
variant="h4"
|
||||
align="center"
|
||||
className={classes.wrapper}
|
||||
>
|
||||
Undelegate stake from {nodeType}
|
||||
</Typography>
|
||||
<Grid container spacing={2} direction="column">
|
||||
<Grid item>
|
||||
<ExecFeeNotice name={'undelegating stake'} />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Paper style={{ padding: theme.spacing(3) }}>
|
||||
{getUndelegationContent()}
|
||||
</Paper>
|
||||
</main>
|
||||
</>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,52 +1,15 @@
|
||||
import React, { useContext, useEffect } from 'react'
|
||||
import Paper from '@material-ui/core/Paper'
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
import Button from '@material-ui/core/Button'
|
||||
import { Grid, Button } from '@material-ui/core'
|
||||
import RefreshIcon from '@material-ui/icons/Refresh'
|
||||
import { makeStyles } from '@material-ui/core/styles'
|
||||
import { useRouter } from 'next/router'
|
||||
import { ValidatorClientContext } from '../contexts/ValidatorClient'
|
||||
import MainNav from '../components/MainNav'
|
||||
import Confirmation from '../components/Confirmation'
|
||||
import NoClientError from '../components/NoClientError'
|
||||
import { useGetBalance } from '../hooks/useGetBalance'
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
appBar: {
|
||||
position: 'relative',
|
||||
},
|
||||
layout: {
|
||||
width: 'auto',
|
||||
marginLeft: theme.spacing(2),
|
||||
marginRight: theme.spacing(2),
|
||||
[theme.breakpoints.up(600 + theme.spacing(2) * 2)]: {
|
||||
width: 600,
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto',
|
||||
},
|
||||
},
|
||||
paper: {
|
||||
marginTop: theme.spacing(3),
|
||||
marginBottom: theme.spacing(3),
|
||||
padding: theme.spacing(2),
|
||||
[theme.breakpoints.up(600 + theme.spacing(3) * 2)]: {
|
||||
marginTop: theme.spacing(6),
|
||||
marginBottom: theme.spacing(6),
|
||||
padding: theme.spacing(3),
|
||||
},
|
||||
},
|
||||
buttons: {
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
},
|
||||
button: {
|
||||
marginTop: theme.spacing(3),
|
||||
marginLeft: theme.spacing(1),
|
||||
},
|
||||
}))
|
||||
import { Layout, NymCard } from '../components'
|
||||
|
||||
export default function CheckBalance() {
|
||||
const classes = useStyles()
|
||||
const router = useRouter()
|
||||
|
||||
const { client } = useContext(ValidatorClientContext)
|
||||
@@ -69,40 +32,39 @@ export default function CheckBalance() {
|
||||
return (
|
||||
<>
|
||||
<MainNav />
|
||||
<main className={classes.layout}>
|
||||
<Paper className={classes.paper}>
|
||||
<Typography component='h1' variant='h4' align='center'>
|
||||
Check Balance
|
||||
</Typography>
|
||||
|
||||
<Layout>
|
||||
<NymCard title="Check Balance">
|
||||
{client === null ? (
|
||||
<NoClientError />
|
||||
) : (
|
||||
<>
|
||||
<Confirmation
|
||||
isLoading={isBalanceLoading}
|
||||
error={balanceCheckError}
|
||||
progressMessage='Checking balance...'
|
||||
successMessage={balanceMessage}
|
||||
failureMessage='Failed to check the account balance!'
|
||||
/>
|
||||
<div className={classes.buttons}>
|
||||
<Button
|
||||
variant='contained'
|
||||
color='primary'
|
||||
type='submit'
|
||||
onClick={getBalance}
|
||||
disabled={isBalanceLoading}
|
||||
className={classes.button}
|
||||
startIcon={<RefreshIcon />}
|
||||
>
|
||||
Refresh
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
<Grid container direction="column" spacing={2}>
|
||||
<Grid item>
|
||||
<Confirmation
|
||||
isLoading={isBalanceLoading}
|
||||
error={balanceCheckError}
|
||||
progressMessage="Checking balance..."
|
||||
successMessage={balanceMessage}
|
||||
failureMessage="Failed to check the account balance!"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
type="submit"
|
||||
onClick={getBalance}
|
||||
disabled={isBalanceLoading}
|
||||
startIcon={<RefreshIcon />}
|
||||
>
|
||||
Refresh
|
||||
</Button>
|
||||
</div>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)}
|
||||
</Paper>
|
||||
</main>
|
||||
</NymCard>
|
||||
</Layout>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
+15
-10
@@ -1,14 +1,19 @@
|
||||
import React from "react";
|
||||
import MainNav from "../components/MainNav";
|
||||
import BondNode from "../components/bond/NodeBond";
|
||||
import React from 'react'
|
||||
import MainNav from '../components/MainNav'
|
||||
import BondNode from '../components/bond/NodeBond'
|
||||
import { Layout, NymCard } from '../components'
|
||||
|
||||
const Bond = () => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<MainNav />
|
||||
<BondNode/>
|
||||
</React.Fragment>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<MainNav />
|
||||
<Layout>
|
||||
<NymCard title="Bond" subheader="Bond a mixnode or gateway">
|
||||
<BondNode />
|
||||
</NymCard>
|
||||
</Layout>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Bond
|
||||
export default Bond
|
||||
|
||||
@@ -1,14 +1,22 @@
|
||||
import React from "react";
|
||||
import MainNav from "../components/MainNav";
|
||||
import DelegationCheck from "../components/delegation-check/DelegationCheck";
|
||||
import React from 'react'
|
||||
import MainNav from '../components/MainNav'
|
||||
import DelegationCheck from '../components/delegation-check/DelegationCheck'
|
||||
import { Layout, NymCard } from '../components'
|
||||
|
||||
const CheckDelegation = () => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<MainNav />
|
||||
<DelegationCheck />
|
||||
</React.Fragment>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<MainNav />
|
||||
<Layout>
|
||||
<NymCard
|
||||
title="Check Stake"
|
||||
subheader="Check your stake on a mixnode or gateway"
|
||||
>
|
||||
<DelegationCheck />
|
||||
</NymCard>
|
||||
</Layout>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default CheckDelegation
|
||||
export default CheckDelegation
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
import React from "react";
|
||||
import MainNav from "../components/MainNav";
|
||||
import NodeDelegation from "../components/delegate/NodeDelegation";
|
||||
import React from 'react'
|
||||
import MainNav from '../components/MainNav'
|
||||
import NodeDelegation from '../components/delegate/NodeDelegation'
|
||||
import { Layout, NymCard } from '../components'
|
||||
|
||||
const DelegateStake = () => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<MainNav />
|
||||
<NodeDelegation />
|
||||
</React.Fragment>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<MainNav />
|
||||
<Layout>
|
||||
<NymCard title="Delegate" subheader="Delegate to Mixnode or Gateway">
|
||||
<NodeDelegation />
|
||||
</NymCard>
|
||||
</Layout>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default DelegateStake
|
||||
export default DelegateStake
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import { useMediaQuery } from '@material-ui/core'
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
Grid,
|
||||
Typography,
|
||||
} from '@material-ui/core'
|
||||
import React from 'react'
|
||||
import { useContext } from 'react'
|
||||
import { Layout, NymCard } from '../components'
|
||||
import MainNav from '../components/MainNav'
|
||||
import NoClientError from '../components/NoClientError'
|
||||
import { ValidatorClientContext } from '../contexts/ValidatorClient'
|
||||
|
||||
const Receive = () => {
|
||||
const { client } = useContext(ValidatorClientContext)
|
||||
const matches = useMediaQuery('(min-width:769px)')
|
||||
|
||||
return (
|
||||
<>
|
||||
<MainNav />
|
||||
<Layout>
|
||||
<NymCard title="Receive Nym">
|
||||
{!client ? (
|
||||
<NoClientError />
|
||||
) : (
|
||||
<Grid container direction="column" spacing={1}>
|
||||
<Grid item>
|
||||
<Typography variant="subtitle1" noWrap={false}>
|
||||
You can receive tokens by providing this address to the sender
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Typography
|
||||
variant={matches ? 'h5' : 'subtitle1'}
|
||||
style={{ wordBreak: 'break-word' }}
|
||||
>
|
||||
{client?.address}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)}
|
||||
</NymCard>
|
||||
</Layout>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Receive
|
||||
+27
-63
@@ -1,57 +1,23 @@
|
||||
import React, { useContext } from 'react'
|
||||
import { makeStyles } from '@material-ui/core/styles'
|
||||
import Paper from '@material-ui/core/Paper'
|
||||
import Stepper from '@material-ui/core/Stepper'
|
||||
import Step from '@material-ui/core/Step'
|
||||
import StepLabel from '@material-ui/core/StepLabel'
|
||||
import Button from '@material-ui/core/Button'
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
import { printableBalanceToNative } from '@nymproject/nym-validator-client/dist/currency'
|
||||
import { coin, Coin, printableCoin } from '@nymproject/nym-validator-client'
|
||||
import {
|
||||
Paper,
|
||||
Stepper,
|
||||
Step,
|
||||
StepLabel,
|
||||
Button,
|
||||
Typography,
|
||||
} from '@material-ui/core'
|
||||
import { theme } from '../lib/theme'
|
||||
import { Layout, NymCard } from '../components'
|
||||
import { Review } from '../components/send-funds/Review'
|
||||
import SendNymForm from '../components/send-funds/SendNymForm'
|
||||
import { coin, Coin, printableCoin } from '@nymproject/nym-validator-client'
|
||||
import Confirmation from '../components/Confirmation'
|
||||
import MainNav from '../components/MainNav'
|
||||
import { ValidatorClientContext } from '../contexts/ValidatorClient'
|
||||
import NoClientError from '../components/NoClientError'
|
||||
import { UDENOM } from './_app'
|
||||
import { printableBalanceToNative } from '@nymproject/nym-validator-client/dist/currency'
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
appBar: {
|
||||
position: 'relative',
|
||||
},
|
||||
layout: {
|
||||
width: 'auto',
|
||||
marginLeft: theme.spacing(2),
|
||||
marginRight: theme.spacing(2),
|
||||
[theme.breakpoints.up(600 + theme.spacing(2) * 2)]: {
|
||||
width: 600,
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto',
|
||||
},
|
||||
},
|
||||
paper: {
|
||||
marginTop: theme.spacing(3),
|
||||
marginBottom: theme.spacing(3),
|
||||
padding: theme.spacing(2),
|
||||
[theme.breakpoints.up(600 + theme.spacing(3) * 2)]: {
|
||||
marginTop: theme.spacing(6),
|
||||
marginBottom: theme.spacing(6),
|
||||
padding: theme.spacing(3),
|
||||
},
|
||||
},
|
||||
stepper: {
|
||||
padding: theme.spacing(3, 0, 5),
|
||||
},
|
||||
buttons: {
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
},
|
||||
button: {
|
||||
marginTop: theme.spacing(3),
|
||||
marginLeft: theme.spacing(1),
|
||||
},
|
||||
}))
|
||||
|
||||
const steps = ['Enter addresses', 'Review & send', 'Await confirmation']
|
||||
|
||||
@@ -90,8 +56,6 @@ export default function SendFunds() {
|
||||
throw new Error('Unknown step')
|
||||
}
|
||||
}
|
||||
|
||||
const classes = useStyles()
|
||||
const { client } = useContext(ValidatorClientContext)
|
||||
|
||||
console.log('client in send', client)
|
||||
@@ -189,8 +153,8 @@ export default function SendFunds() {
|
||||
|
||||
const getStepperContent = () => {
|
||||
return (
|
||||
<>
|
||||
<Stepper activeStep={activeStep} className={classes.stepper}>
|
||||
<Paper style={{ padding: theme.spacing(3) }}>
|
||||
<Stepper activeStep={activeStep} style={{ paddingLeft: 0 }}>
|
||||
{steps.map((label) => (
|
||||
<Step key={label}>
|
||||
<StepLabel>{label}</StepLabel>
|
||||
@@ -217,18 +181,21 @@ export default function SendFunds() {
|
||||
<>
|
||||
<form onSubmit={handleNext}>
|
||||
{getStepContent(activeStep)}
|
||||
<div className={classes.buttons}>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
padding: theme.spacing(1, 0),
|
||||
}}
|
||||
>
|
||||
{activeStep !== 0 && (
|
||||
<Button onClick={handleBack} className={classes.button}>
|
||||
Back
|
||||
</Button>
|
||||
<Button onClick={handleBack}>Back</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
type="submit"
|
||||
disabled={checkButtonDisabled()}
|
||||
className={classes.button}
|
||||
>
|
||||
{activeStep === 1
|
||||
? 'Send'
|
||||
@@ -241,21 +208,18 @@ export default function SendFunds() {
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
</>
|
||||
</Paper>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<MainNav />
|
||||
<main className={classes.layout}>
|
||||
<Paper className={classes.paper}>
|
||||
<Typography component="h1" variant="h4" align="center">
|
||||
Send Nym
|
||||
</Typography>
|
||||
<Layout>
|
||||
<NymCard title="Send Nym">
|
||||
{client === null ? <NoClientError /> : getStepperContent()}
|
||||
</Paper>
|
||||
</main>
|
||||
</NymCard>
|
||||
</Layout>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
+15
-10
@@ -1,14 +1,19 @@
|
||||
import React from "react";
|
||||
import MainNav from "../components/MainNav";
|
||||
import UnbondNode from "../components/unbond/UnbondNode";
|
||||
import React from 'react'
|
||||
import { Layout, NymCard } from '../components'
|
||||
import MainNav from '../components/MainNav'
|
||||
import UnbondNode from '../components/unbond/UnbondNode'
|
||||
|
||||
const Unbond = () => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<MainNav/>
|
||||
<UnbondNode />
|
||||
</React.Fragment>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<MainNav />
|
||||
<Layout>
|
||||
<NymCard title="Unbond" subheader="Unbond a Mixnode or Gateway">
|
||||
<UnbondNode />
|
||||
</NymCard>
|
||||
</Layout>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Unbond
|
||||
export default Unbond
|
||||
|
||||
@@ -1,14 +1,22 @@
|
||||
import React from "react";
|
||||
import MainNav from "../components/MainNav";
|
||||
import NodeUndelegation from "../components/undelegate/NodeUndelegation";
|
||||
import React from 'react'
|
||||
import { Layout, NymCard } from '../components'
|
||||
import MainNav from '../components/MainNav'
|
||||
import NodeUndelegation from '../components/undelegate/NodeUndelegation'
|
||||
|
||||
const UndelegateStake = () => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<MainNav />
|
||||
<NodeUndelegation />
|
||||
</React.Fragment>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<MainNav />
|
||||
<Layout>
|
||||
<NymCard
|
||||
title="Undelegate"
|
||||
subheader="Undelegate from a Mixnode or Gateway"
|
||||
>
|
||||
<NodeUndelegation />
|
||||
</NymCard>
|
||||
</Layout>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default UndelegateStake
|
||||
export default UndelegateStake
|
||||
|
||||
Reference in New Issue
Block a user