start form validation

This commit is contained in:
fmtabbara
2021-09-01 19:47:41 +01:00
parent c69d7fa46f
commit 1d2a1b2635
5 changed files with 6498 additions and 5967 deletions
+7 -2
View File
@@ -10,6 +10,7 @@
},
"dependencies": {
"@babel/preset-typescript": "^7.15.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",
@@ -19,7 +20,10 @@
"clsx": "^1.1.1",
"react": "^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": {
"@babel/core": "^7.15.0",
@@ -30,6 +34,7 @@
"@tauri-apps/cli": "^1.0.0-beta.9",
"@types/bs58": "^4.0.1",
"@types/react-router-dom": "^5.1.8",
"@types/semver": "^7.3.8",
"babel-loader": "^8.2.2",
"css-loader": "^6.2.0",
"favicons-webpack-plugin": "^5.0.2",
@@ -39,6 +44,6 @@
"url-loader": "^4.1.1",
"webpack": "^5.50.0",
"webpack-cli": "^4.8.0",
"webpack-dev-server": "^3.11.2"
"webpack-dev-server": "^4.1.0"
}
}
+29 -27
View File
@@ -69,33 +69,35 @@ export const AddressCard = () => {
noPadding
Action={
<Tooltip title={!copyState ? 'Copy address' : 'Copied'}>
<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>
<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>
}
>
+81 -1
View File
@@ -8,20 +8,84 @@ import {
TextField,
Theme,
} from '@material-ui/core'
import { EnumNodeType } from '../../types/global'
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 {
isValidHostname,
validateAmount,
validateKey,
validateVersion,
} from '../../utils'
type TBondNodeFormProps = {
// minimumBond: Coin
// 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 = () => {
const [advancedShown, setAdvancedShown] = React.useState(false)
const [nodeType, setNodeType] = useState(EnumNodeType.Mixnode)
const {
register,
handleSubmit,
formState: { errors },
} = useForm<TBondFormFields>({ resolver: yupResolver(validationSchema) })
const theme: Theme = useTheme()
console.log(errors)
const onSubmit = (data: TBondFormFields) => console.log(data)
return (
<form>
@@ -35,32 +99,41 @@ export const BondNodeForm = () => {
</Grid>
<Grid item xs={12}>
<TextField
{...register('identityKey')}
variant="outlined"
required
id="identityKey"
name="identityKey"
label="Identity key"
fullWidth
error={!!errors.identityKey}
helperText={errors.identityKey?.message}
/>
</Grid>
<Grid item xs={12}>
<TextField
{...register('sphinxKey')}
variant="outlined"
required
id="sphinxKey"
name="sphinxKey"
label="Sphinx key"
error={!!errors.sphinxKey}
helperText={errors.sphinxKey?.message}
fullWidth
/>
</Grid>
<Grid item xs={12} sm={9}>
<TextField
{...register('amount')}
variant="outlined"
required
id="amount"
name="amount"
label="Amount to bond"
fullWidth
error={!!errors.amount}
helperText={errors.amount?.message}
InputProps={{
endAdornment: (
<InputAdornment position="end">punks</InputAdornment>
@@ -71,12 +144,15 @@ export const BondNodeForm = () => {
<Grid item xs={12} sm={6}>
<TextField
{...register('host')}
variant="outlined"
required
id="host"
name="host"
label="Host"
fullWidth
error={!!errors.host}
helperText={errors.host?.message}
/>
</Grid>
@@ -96,12 +172,15 @@ export const BondNodeForm = () => {
<Grid item xs={12} sm={6}>
<TextField
{...register('version')}
variant="outlined"
required
id="version"
name="version"
label="Version"
fullWidth
error={!!errors.version}
helperText={errors.version?.message}
/>
</Grid>
@@ -185,6 +264,7 @@ export const BondNodeForm = () => {
type="submit"
size="large"
disableElevation
onClick={handleSubmit(onSubmit)}
>
Bond
</Button>
+80
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff