send updates

This commit is contained in:
fmtabbara
2021-09-07 21:53:05 +01:00
parent 45a56a7088
commit b9389f1235
7 changed files with 225 additions and 184 deletions
@@ -1,26 +1,20 @@
import React, { useEffect, useState } from 'react'
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 { SendError } from './SendError'
import { TFormData } from './SendWizard'
export const SendConfirmation = ({
amount,
recipient,
onFinish,
data,
error,
isLoading,
}: {
amount: string
recipient: string
onFinish: () => void
data?: Pick<TFormData, 'to' | 'amount'>
error?: string
isLoading: boolean
}) => {
const theme: Theme = useTheme()
const [isLoading, setIsLoading] = useState(true)
useEffect(() => {
setTimeout(() => {
setIsLoading(false)
onFinish()
}, 3000)
}, [])
return (
<div
@@ -32,9 +26,9 @@ export const SendConfirmation = ({
width: '100%',
}}
>
{isLoading ? (
<CircularProgress size={48} />
) : (
{isLoading && <CircularProgress size={48} />}
{!isLoading && !!error && <SendError message={error} />}
{!isLoading && data && (
<>
<div
style={{
@@ -66,7 +60,7 @@ export const SendConfirmation = ({
</Typography>
</div>
<div style={{ wordBreak: 'break-all' }}>
<Typography>{recipient}</Typography>
<Typography>{data.to}</Typography>
</div>
</div>
<div style={{ display: 'flex' }}>
@@ -76,7 +70,7 @@ export const SendConfirmation = ({
</Typography>
</div>
<div>
<Typography>{amount + ' punks'}</Typography>
<Typography>{data.amount + ' punks'}</Typography>
</div>
</div>
</Card>
+27 -37
View File
@@ -1,19 +1,11 @@
import { Card, CircularProgress, Theme, Typography } from '@material-ui/core'
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 React, { useEffect, useState } from 'react'
export const SendError = ({ onFinish }: { onFinish: () => void }) => {
export const SendError = ({ message }: { message?: string }) => {
const theme: Theme = useTheme()
const [isLoading, setIsLoading] = useState(true)
useEffect(() => {
setTimeout(() => {
setIsLoading(false)
onFinish()
}, 3000)
}, [])
return (
<div
@@ -25,33 +17,31 @@ export const SendError = ({ onFinish }: { onFinish: () => void }) => {
width: '100%',
}}
>
{isLoading ? (
<CircularProgress size={48} />
) : (
<>
<div
style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
marginBottom: theme.spacing(4),
}}
>
<ErrorOutline
style={{ fontSize: 50, color: theme.palette.error.main }}
/>
<Typography>Transaction failed</Typography>
</div>
<>
<div
style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
marginBottom: theme.spacing(4),
}}
>
<ErrorOutline
style={{ fontSize: 50, color: theme.palette.error.main }}
/>
<Typography>Transaction failed</Typography>
</div>
<Card
variant="outlined"
style={{ width: '100%', padding: theme.spacing(2) }}
>
<Alert severity="error">An error occured during the request</Alert>
</Card>
</>
)}
<Card
variant="outlined"
style={{ width: '100%', padding: theme.spacing(2) }}
>
<Alert severity="error">
An error occured during the request {message}
</Alert>
</Card>
</>
</div>
)
}
+20 -27
View File
@@ -1,63 +1,56 @@
import React, { useContext } from 'react'
import React from 'react'
import { Grid, InputAdornment, TextField } from '@material-ui/core'
import { ClientContext } from '../../context/main'
import { useFormContext } from 'react-hook-form'
export const SendForm = ({
formData,
updateRecipAddress,
updateAmount,
}: {
formData: { toAddress: string; sendAmount: string }
updateRecipAddress: (address: string) => void
updateAmount: (amount: string) => void
}) => {
const { clientDetails } = useContext(ClientContext)
export const SendForm = () => {
const {
register,
formState: { errors },
} = useFormContext()
return (
<Grid container spacing={3}>
<Grid item xs={12}>
<TextField
{...register('from')}
required
variant="outlined"
id="sender"
name="sender"
label="Sender address"
id="from"
name="from"
label="From"
fullWidth
value={clientDetails?.client_address}
disabled={true}
/>
</Grid>
<Grid item xs={12}>
<TextField
{...register('to')}
required
variant="outlined"
id="recipient"
name="recipient"
label="Recipient address"
id="to"
name="to"
label="To"
fullWidth
autoFocus
value={formData.toAddress}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
updateRecipAddress(e.target.value)
}
error={!!errors.recipient}
helperText={errors.recipient?.message}
/>
</Grid>
<Grid item xs={12} sm={6}>
<TextField
{...register('amount')}
required
variant="outlined"
id="amount"
name="amount"
label="Amount"
fullWidth
error={!!errors.amount}
helperText={errors.amount?.message}
InputProps={{
endAdornment: <InputAdornment position="end">punks</InputAdornment>,
}}
value={formData.sendAmount}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
updateAmount(e.target.value)
}
/>
</Grid>
</Grid>
+12 -16
View File
@@ -1,17 +1,16 @@
import React from 'react'
import { Card, Divider, Grid, Theme, Typography } from '@material-ui/core'
import { useTheme } from '@material-ui/styles'
import React, { useContext } from 'react'
import { ClientContext } from '../../context/main'
import { useFormContext } from 'react-hook-form'
import { TFormData } from './SendWizard'
export const SendReview = () => {
const { getValues } = useFormContext()
const values: TFormData = getValues()
export const SendReview = ({
recipientAddress,
amount,
}: {
recipientAddress: string
amount: string
}) => {
const { clientDetails } = useContext(ClientContext)
const theme: Theme = useTheme()
return (
<Card
variant="outlined"
@@ -19,22 +18,19 @@ export const SendReview = ({
>
<Grid container spacing={2}>
<Grid item xs={12}>
<SendReviewField
title="From"
subtitle={clientDetails?.client_address!}
/>
<SendReviewField title="From" subtitle={values.from} />
</Grid>
<Grid item xs={12}>
<Divider light />
</Grid>
<Grid item xs={12}>
<SendReviewField title="To" subtitle={recipientAddress} />
<SendReviewField title="To" subtitle={values.to} />
</Grid>
<Grid item xs={12}>
<Divider light />
</Grid>
<Grid item xs={12}>
<SendReviewField title="Amount" subtitle={amount} />
<SendReviewField title="Amount" subtitle={values.amount} />
</Grid>
</Grid>
</Card>
+140 -83
View File
@@ -1,107 +1,164 @@
import React, { useState } from 'react'
import React, { useContext, useEffect, 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 { SendForm } from './SendForm'
import { SendReview } from './SendReview'
import { SendConfirmation } from './SendConfirmation'
import { SendError } from './SendError'
import { ClientContext } from '../../context/main'
import { validationSchema } from './validationSchema'
import { invoke } from '@tauri-apps/api'
const defaultValues = {
amount: '',
memo: '',
to: '',
}
export type TFormData = {
amount: string
memo: string
to: string
from: string
}
export const SendWizard = () => {
const [activeStep, setActiveStep] = useState(0)
const [toAddress, setToAddress] = useState('')
const [sendAmount, setSendAmount] = useState('')
const [isLoading, setIsLoading] = useState(false)
const [requestError, setRequestError] = useState<string>()
const [confirmedData, setConfirmedData] =
useState<{ amount: string; recipient: string }>()
const steps = ['Enter address', 'Review and send', 'Await confirmation']
const { clientDetails } = useContext(ClientContext)
const methods = useForm<TFormData>({
defaultValues: {
...defaultValues,
from: clientDetails?.client_address!,
},
resolver: yupResolver(validationSchema),
})
const theme: Theme = useTheme()
const handleNextStep = () =>
setActiveStep((s) => (s + 1 < steps.length ? s + 1 : s))
const handleNextStep = methods.handleSubmit(() => setActiveStep((s) => s + 1))
const handlePreviousStep = () =>
setActiveStep((s) => (s - 1 >= 0 ? s - 1 : s))
const handlePreviousStep = () => setActiveStep((s) => s - 1)
const handleFinish = () => {
methods.reset()
setActiveStep(0)
setSendAmount('')
setToAddress('')
}
const handleSend = () => {
setIsLoading(true)
setActiveStep((s) => s + 1)
const formState = methods.getValues()
invoke('send', {
address: formState.to,
amount: { denom: 'punk', amount: formState.amount },
memo: formState.memo,
})
.then((res) => {
console.log(res)
setActiveStep((s) => s + 1)
setConfirmedData({
amount: formState.amount,
recipient: formState.to,
})
setIsLoading(false)
})
.catch((e) => {
setRequestError(e)
setIsLoading(false)
console.log(e)
})
}
return (
<div style={{ paddingTop: theme.spacing(3) }}>
<Stepper
activeStep={activeStep}
style={{
background: theme.palette.grey[50],
paddingBottom: 0,
paddingTop: 0,
}}
>
{steps.map((s, i) => (
<Step key={i}>
<StepLabel>{s}</StepLabel>
</Step>
))}
</Stepper>
<div
style={{
minHeight: 300,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
padding: theme.spacing(0, 3),
}}
>
{activeStep === 0 ? (
<SendForm
updateRecipAddress={(address) => setToAddress(address)}
updateAmount={(amount) => setSendAmount(amount)}
formData={{ sendAmount, toAddress }}
/>
) : activeStep === 1 ? (
<SendReview recipientAddress={toAddress} amount={sendAmount} />
) : sendAmount === 'fail' ? (
<SendError onFinish={() => setActiveStep((s) => s + 1)} />
) : (
<SendConfirmation
amount={sendAmount}
recipient={toAddress}
onFinish={() => setActiveStep((s) => s + 1)}
/>
)}
</div>
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-end',
borderTop: `1px solid ${theme.palette.grey[200]}`,
background: theme.palette.grey[100],
padding: theme.spacing(2),
}}
>
{activeStep === 1 && (
<Button
disableElevation
style={{ marginRight: theme.spacing(1) }}
onClick={handlePreviousStep}
>
Back
</Button>
)}
<Button
variant={activeStep > 0 ? 'contained' : 'text'}
color={activeStep > 0 ? 'primary' : 'default'}
disableElevation
onClick={activeStep === 3 ? handleFinish : handleNextStep}
disabled={!(toAddress.length > 0 && sendAmount.length > 0)}
<FormProvider {...methods}>
<div style={{ paddingTop: theme.spacing(3) }}>
<Stepper
activeStep={activeStep}
style={{
background: theme.palette.grey[50],
paddingBottom: 0,
paddingTop: 0,
}}
>
{activeStep === 1
? 'Send'
: activeStep === steps.length
? 'Finish'
: 'Next'}
</Button>
{steps.map((s, i) => (
<Step key={i}>
<StepLabel>{s}</StepLabel>
</Step>
))}
</Stepper>
<div
style={{
minHeight: 300,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
padding: theme.spacing(0, 3),
}}
>
{activeStep === 0 ? (
<SendForm />
) : activeStep === 1 ? (
<SendReview />
) : (
<SendConfirmation
data={confirmedData}
isLoading={isLoading}
error={requestError}
/>
)}
</div>
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-end',
borderTop: `1px solid ${theme.palette.grey[200]}`,
background: theme.palette.grey[100],
padding: theme.spacing(2),
}}
>
{activeStep === 1 && (
<Button
disableElevation
style={{ marginRight: theme.spacing(1) }}
onClick={handlePreviousStep}
>
Back
</Button>
)}
<Button
variant={activeStep > 0 ? 'contained' : 'text'}
color={activeStep > 0 ? 'primary' : 'default'}
disableElevation
onClick={
activeStep === 0
? handleNextStep
: activeStep === 1
? handleSend
: handleFinish
}
disabled={
!!(
methods.formState.errors.amount ||
methods.formState.errors.to ||
isLoading
)
}
>
{activeStep === 0 ? 'Next' : activeStep === 1 ? 'Send' : 'Finish'}
</Button>
</div>
</div>
</div>
</FormProvider>
)
}
+1 -1
View File
@@ -1,4 +1,4 @@
import React from 'react'
import React, { useState } from 'react'
import { NymCard } from '../../components'
import { SendWizard } from './SendWizard'
import { Layout } from '../../layouts'
@@ -0,0 +1,11 @@
import * as Yup from 'yup'
import { validateAmount, validateKey } from '../../utils'
export const validationSchema = Yup.object().shape({
to: Yup.string().required(),
amount: Yup.string()
.required()
.test('valid-amount', 'A valid amount is required', (amount) => {
return validateAmount(amount || '0', '1')
}),
})