send updates
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,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')
|
||||
}),
|
||||
})
|
||||
Reference in New Issue
Block a user