start form validation
This commit is contained in:
@@ -10,6 +10,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/preset-typescript": "^7.15.0",
|
"@babel/preset-typescript": "^7.15.0",
|
||||||
|
"@hookform/resolvers": "^2.8.0",
|
||||||
"@material-ui/core": "^4.12.3",
|
"@material-ui/core": "^4.12.3",
|
||||||
"@material-ui/icons": "^4.11.2",
|
"@material-ui/icons": "^4.11.2",
|
||||||
"@material-ui/lab": "^4.0.0-alpha.60",
|
"@material-ui/lab": "^4.0.0-alpha.60",
|
||||||
@@ -19,7 +20,10 @@
|
|||||||
"clsx": "^1.1.1",
|
"clsx": "^1.1.1",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-router-dom": "^5.2.0"
|
"react-hook-form": "^7.14.2",
|
||||||
|
"react-router-dom": "^5.2.0",
|
||||||
|
"semver": "^6.3.0",
|
||||||
|
"yup": "^0.32.9"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.15.0",
|
"@babel/core": "^7.15.0",
|
||||||
@@ -30,6 +34,7 @@
|
|||||||
"@tauri-apps/cli": "^1.0.0-beta.9",
|
"@tauri-apps/cli": "^1.0.0-beta.9",
|
||||||
"@types/bs58": "^4.0.1",
|
"@types/bs58": "^4.0.1",
|
||||||
"@types/react-router-dom": "^5.1.8",
|
"@types/react-router-dom": "^5.1.8",
|
||||||
|
"@types/semver": "^7.3.8",
|
||||||
"babel-loader": "^8.2.2",
|
"babel-loader": "^8.2.2",
|
||||||
"css-loader": "^6.2.0",
|
"css-loader": "^6.2.0",
|
||||||
"favicons-webpack-plugin": "^5.0.2",
|
"favicons-webpack-plugin": "^5.0.2",
|
||||||
@@ -39,6 +44,6 @@
|
|||||||
"url-loader": "^4.1.1",
|
"url-loader": "^4.1.1",
|
||||||
"webpack": "^5.50.0",
|
"webpack": "^5.50.0",
|
||||||
"webpack-cli": "^4.8.0",
|
"webpack-cli": "^4.8.0",
|
||||||
"webpack-dev-server": "^3.11.2"
|
"webpack-dev-server": "^4.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,33 +69,35 @@ export const AddressCard = () => {
|
|||||||
noPadding
|
noPadding
|
||||||
Action={
|
Action={
|
||||||
<Tooltip title={!copyState ? 'Copy address' : 'Copied'}>
|
<Tooltip title={!copyState ? 'Copy address' : 'Copied'}>
|
||||||
<IconButton
|
<span>
|
||||||
disabled={!!copyState}
|
<IconButton
|
||||||
onClick={async () => {
|
disabled={!!copyState}
|
||||||
setCopyState(EnumCopyState.copying)
|
onClick={async () => {
|
||||||
await handleCopy({
|
setCopyState(EnumCopyState.copying)
|
||||||
text: clientDetails?.client_address || '',
|
await handleCopy({
|
||||||
cb: (isCopied) => {
|
text: clientDetails?.client_address || '',
|
||||||
if (isCopied) {
|
cb: (isCopied) => {
|
||||||
setCopyState(EnumCopyState.copySuccess)
|
if (isCopied) {
|
||||||
setTimeout(() => {
|
setCopyState(EnumCopyState.copySuccess)
|
||||||
setCopyState(undefined)
|
setTimeout(() => {
|
||||||
}, 2500)
|
setCopyState(undefined)
|
||||||
}
|
}, 2500)
|
||||||
},
|
}
|
||||||
})
|
},
|
||||||
}}
|
})
|
||||||
>
|
}}
|
||||||
{copyState === EnumCopyState.copying ? (
|
>
|
||||||
<CircularProgress size={24} />
|
{copyState === EnumCopyState.copying ? (
|
||||||
) : copyState === EnumCopyState.copySuccess ? (
|
<CircularProgress size={24} />
|
||||||
<CheckCircleOutline
|
) : copyState === EnumCopyState.copySuccess ? (
|
||||||
style={{ color: theme.palette.success.main }}
|
<CheckCircleOutline
|
||||||
/>
|
style={{ color: theme.palette.success.main }}
|
||||||
) : (
|
/>
|
||||||
<FileCopy />
|
) : (
|
||||||
)}
|
<FileCopy />
|
||||||
</IconButton>
|
)}
|
||||||
|
</IconButton>
|
||||||
|
</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -8,20 +8,84 @@ import {
|
|||||||
TextField,
|
TextField,
|
||||||
Theme,
|
Theme,
|
||||||
} from '@material-ui/core'
|
} from '@material-ui/core'
|
||||||
import { EnumNodeType } from '../../types/global'
|
|
||||||
import { useTheme } from '@material-ui/styles'
|
import { useTheme } from '@material-ui/styles'
|
||||||
|
import { useForm } from 'react-hook-form'
|
||||||
|
import { yupResolver } from '@hookform/resolvers/yup'
|
||||||
|
import * as Yup from 'yup'
|
||||||
|
import { EnumNodeType } from '../../types/global'
|
||||||
import { NodeTypeSelector } from '../../components/NodeTypeSelector'
|
import { NodeTypeSelector } from '../../components/NodeTypeSelector'
|
||||||
|
import {
|
||||||
|
isValidHostname,
|
||||||
|
validateAmount,
|
||||||
|
validateKey,
|
||||||
|
validateVersion,
|
||||||
|
} from '../../utils'
|
||||||
|
|
||||||
type TBondNodeFormProps = {
|
type TBondNodeFormProps = {
|
||||||
// minimumBond: Coin
|
// minimumBond: Coin
|
||||||
// onSubmit: (values: BondingInformation) => void
|
// onSubmit: (values: BondingInformation) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TBondFormFields = {
|
||||||
|
identityKey: string
|
||||||
|
sphinxKey: string
|
||||||
|
amount: string
|
||||||
|
host: string
|
||||||
|
version: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const validationSchema = Yup.object().shape({
|
||||||
|
identityKey: Yup.string()
|
||||||
|
.required('An indentity key is required')
|
||||||
|
.test('valid-id-key', 'A valid identity key is required', function (value) {
|
||||||
|
return validateKey(value || '')
|
||||||
|
}),
|
||||||
|
sphinxKey: Yup.string()
|
||||||
|
.required('A sphinx key is required')
|
||||||
|
.test(
|
||||||
|
'valid-sphinx-key',
|
||||||
|
'A valid sphinx key is required',
|
||||||
|
function (value) {
|
||||||
|
return validateKey(value || '')
|
||||||
|
}
|
||||||
|
),
|
||||||
|
amount: Yup.string()
|
||||||
|
.required('An amount is required')
|
||||||
|
.test(
|
||||||
|
'valid-amount',
|
||||||
|
'A valid amount is required (min 100 punks)',
|
||||||
|
function (value) {
|
||||||
|
return validateAmount(value || '', '100000000')
|
||||||
|
// minimum amount needs to come from the backend - replace when available
|
||||||
|
}
|
||||||
|
),
|
||||||
|
|
||||||
|
host: Yup.string()
|
||||||
|
.required('A host is required')
|
||||||
|
.test('valid-amount', 'A valid host is required', function (value) {
|
||||||
|
return !!value ? isValidHostname(value) : false
|
||||||
|
}),
|
||||||
|
version: Yup.string()
|
||||||
|
.required('A version is required')
|
||||||
|
.test('valid-version', 'A valid version is required', function (value) {
|
||||||
|
return !!value ? validateVersion(value) : false
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
export const BondNodeForm = () => {
|
export const BondNodeForm = () => {
|
||||||
const [advancedShown, setAdvancedShown] = React.useState(false)
|
const [advancedShown, setAdvancedShown] = React.useState(false)
|
||||||
const [nodeType, setNodeType] = useState(EnumNodeType.Mixnode)
|
const [nodeType, setNodeType] = useState(EnumNodeType.Mixnode)
|
||||||
|
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
handleSubmit,
|
||||||
|
formState: { errors },
|
||||||
|
} = useForm<TBondFormFields>({ resolver: yupResolver(validationSchema) })
|
||||||
|
|
||||||
const theme: Theme = useTheme()
|
const theme: Theme = useTheme()
|
||||||
|
console.log(errors)
|
||||||
|
|
||||||
|
const onSubmit = (data: TBondFormFields) => console.log(data)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form>
|
<form>
|
||||||
@@ -35,32 +99,41 @@ export const BondNodeForm = () => {
|
|||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<TextField
|
<TextField
|
||||||
|
{...register('identityKey')}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
required
|
required
|
||||||
id="identityKey"
|
id="identityKey"
|
||||||
name="identityKey"
|
name="identityKey"
|
||||||
label="Identity key"
|
label="Identity key"
|
||||||
fullWidth
|
fullWidth
|
||||||
|
error={!!errors.identityKey}
|
||||||
|
helperText={errors.identityKey?.message}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<TextField
|
<TextField
|
||||||
|
{...register('sphinxKey')}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
required
|
required
|
||||||
id="sphinxKey"
|
id="sphinxKey"
|
||||||
name="sphinxKey"
|
name="sphinxKey"
|
||||||
label="Sphinx key"
|
label="Sphinx key"
|
||||||
|
error={!!errors.sphinxKey}
|
||||||
|
helperText={errors.sphinxKey?.message}
|
||||||
fullWidth
|
fullWidth
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12} sm={9}>
|
<Grid item xs={12} sm={9}>
|
||||||
<TextField
|
<TextField
|
||||||
|
{...register('amount')}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
required
|
required
|
||||||
id="amount"
|
id="amount"
|
||||||
name="amount"
|
name="amount"
|
||||||
label="Amount to bond"
|
label="Amount to bond"
|
||||||
fullWidth
|
fullWidth
|
||||||
|
error={!!errors.amount}
|
||||||
|
helperText={errors.amount?.message}
|
||||||
InputProps={{
|
InputProps={{
|
||||||
endAdornment: (
|
endAdornment: (
|
||||||
<InputAdornment position="end">punks</InputAdornment>
|
<InputAdornment position="end">punks</InputAdornment>
|
||||||
@@ -71,12 +144,15 @@ export const BondNodeForm = () => {
|
|||||||
|
|
||||||
<Grid item xs={12} sm={6}>
|
<Grid item xs={12} sm={6}>
|
||||||
<TextField
|
<TextField
|
||||||
|
{...register('host')}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
required
|
required
|
||||||
id="host"
|
id="host"
|
||||||
name="host"
|
name="host"
|
||||||
label="Host"
|
label="Host"
|
||||||
fullWidth
|
fullWidth
|
||||||
|
error={!!errors.host}
|
||||||
|
helperText={errors.host?.message}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
@@ -96,12 +172,15 @@ export const BondNodeForm = () => {
|
|||||||
|
|
||||||
<Grid item xs={12} sm={6}>
|
<Grid item xs={12} sm={6}>
|
||||||
<TextField
|
<TextField
|
||||||
|
{...register('version')}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
required
|
required
|
||||||
id="version"
|
id="version"
|
||||||
name="version"
|
name="version"
|
||||||
label="Version"
|
label="Version"
|
||||||
fullWidth
|
fullWidth
|
||||||
|
error={!!errors.version}
|
||||||
|
helperText={errors.version?.message}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
@@ -185,6 +264,7 @@ export const BondNodeForm = () => {
|
|||||||
type="submit"
|
type="submit"
|
||||||
size="large"
|
size="large"
|
||||||
disableElevation
|
disableElevation
|
||||||
|
onClick={handleSubmit(onSubmit)}
|
||||||
>
|
>
|
||||||
Bond
|
Bond
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
import bs58 from 'bs58'
|
||||||
|
import { minor, valid } from 'semver'
|
||||||
|
import { invoke } from '@tauri-apps/api'
|
||||||
|
|
||||||
|
export const validateKey = (key: string): boolean => {
|
||||||
|
// it must be a valid base58 key
|
||||||
|
try {
|
||||||
|
const bytes = bs58.decode(key)
|
||||||
|
// of length 32
|
||||||
|
return bytes.length === 32
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const validateAmount = async (
|
||||||
|
rawValue: string,
|
||||||
|
minimum: string
|
||||||
|
): Promise<boolean> => {
|
||||||
|
// tests basic coin value requirements, like no more than 6 decimal places, value lower than total supply, etc
|
||||||
|
if (!basicRawCoinValueValidation(rawValue)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const nativeValueString: string = await invoke(
|
||||||
|
'printable_balance_to_native',
|
||||||
|
{ amount: rawValue }
|
||||||
|
)
|
||||||
|
|
||||||
|
let nativeValue = parseInt(nativeValueString)
|
||||||
|
|
||||||
|
return nativeValue >= parseInt(minimum)
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// this conversion seems really iffy but I'm not sure how to better approach it
|
||||||
|
}
|
||||||
|
|
||||||
|
export const basicRawCoinValueValidation = (rawAmount: string): boolean => {
|
||||||
|
let amountFloat = parseFloat(rawAmount)
|
||||||
|
if (isNaN(amountFloat)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// it cannot have more than 6 decimal places
|
||||||
|
if (amountFloat != parseFloat(amountFloat.toFixed(6))) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// it cannot be larger than the total supply
|
||||||
|
if (amountFloat > 1_000_000_000_000_000) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// it can't be lower than one micro coin
|
||||||
|
return amountFloat >= 0.000001
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isValidHostname = (value: string) => {
|
||||||
|
// regex for ipv4 and ipv6 and hhostname- source http://jsfiddle.net/DanielD/8S4nq/
|
||||||
|
const hostnameRegex =
|
||||||
|
/((^\s*((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\s*$)|(^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$))|(^\s*((?=.{1,255}$)(?=.*[A-Za-z].*)[0-9A-Za-z](?:(?:[0-9A-Za-z]|\b-){0,61}[0-9A-Za-z])?(?:\.[0-9A-Za-z](?:(?:[0-9A-Za-z]|\b-){0,61}[0-9A-Za-z])?)*)\s*$)/
|
||||||
|
|
||||||
|
return hostnameRegex.test(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const validateVersion = (version: string): boolean => {
|
||||||
|
try {
|
||||||
|
const minorVersion = minor(version)
|
||||||
|
const validVersion = valid(version)
|
||||||
|
return validVersion !== null && minorVersion >= 11
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
+6301
-5937
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user