added resent email functionality

This commit is contained in:
Myzel394 2022-10-15 10:03:09 +02:00
parent c8c774de54
commit 457ef7bc8c
13 changed files with 191 additions and 6 deletions

View File

@ -16,6 +16,7 @@
"@tanstack/react-query": "^4.12.0",
"axios": "^1.1.2",
"crypto-js": "^4.1.1",
"date-fns": "^2.29.3",
"formik": "^2.2.9",
"in-seconds": "^1.2.0",
"openpgp": "^5.5.0",
@ -28,6 +29,7 @@
"yup": "^0.32.11"
},
"devDependencies": {
"@types/date-fns": "^2.6.0",
"@types/openpgp": "^4.4.18",
"@types/react": "^18.0.17",
"@types/react-dom": "^18.0.6",

View File

@ -6,3 +6,5 @@ export * from "./signup"
export {default as signup} from "./signup"
export * from "./validate-email"
export {default as validateEmail} from "./validate-email"
export * from "./resend-email-verification-code"
export {default as resendEmailVerificationCode} from "./resend-email-verification-code"

View File

@ -0,0 +1,12 @@
import axios from "axios"
export default function resendEmailVerificationCode(
email: string,
): Promise<void> {
return axios.post(
`${import.meta.env.VITE_SERVER_BASE_URL}/auth/resend-email`,
{
email,
},
)
}

View File

@ -0,0 +1,78 @@
import {AxiosError} from "axios"
import {ReactElement, useEffect, useState} from "react"
import {UseMutationResult} from "@tanstack/react-query"
import {Alert, Snackbar} from "@mui/material"
import {FastAPIError} from "~/utils"
import getErrorMessage from "~/utils/get-error-message"
export interface MutationStatusSnackbarProps<
TData = unknown,
TError = unknown,
TVariables = unknown,
TContext = unknown,
> {
mutation: UseMutationResult<TData, TError, TVariables, TContext>
successMessage?: string
errorMessage?: string
}
export default function MutationStatusSnackbar<
TData extends {data: {detail?: string}} = {data: {detail?: string}},
TError extends AxiosError = AxiosError<FastAPIError>,
TVariables = unknown,
TContext = unknown,
>({
mutation,
successMessage,
errorMessage,
}: MutationStatusSnackbarProps<
TData,
TError,
TVariables,
TContext
>): ReactElement {
const [open, setOpen] = useState<boolean>(false)
const severity = (() => {
if (mutation.isError) {
return "error"
}
if (mutation.isSuccess) {
return "success"
}
return "info"
})()
const message = (() => {
if (mutation.isError) {
// @ts-ignore
return errorMessage ?? getErrorMessage(mutation.error)
}
if (mutation.isSuccess) {
return successMessage ?? mutation.data?.data?.detail ?? "Success!"
}
})()
useEffect(() => {
if (mutation.isSuccess || mutation.isError) {
setOpen(true)
}
}, [mutation.isSuccess, mutation.isError])
return (
<Snackbar
open={open}
onClose={() => setOpen(false)}
autoHideDuration={5000}
>
<Alert severity={severity} variant="filled">
{message}
</Alert>
</Snackbar>
)
}

View File

@ -8,3 +8,5 @@ export * from "./PasswordField"
export {default as PasswordField} from "./PasswordField"
export * from "./SimpleForm"
export {default as SimpleForm} from "./SimpleForm"
export * from "./MutationStatusSnackbar"
export {default as MutationStatusSnackbar} from "./MutationStatusSnackbar"

2
src/hooks/index.ts Normal file
View File

@ -0,0 +1,2 @@
export * from "./use-interval-update"
export {default as useIntervalUpdate} from "./use-interval-update"

View File

@ -0,0 +1,18 @@
import {useEffect, useState} from "react"
export default function useIntervalUpdate(
updateIntervalInMilliSeconds = 1000,
): [Date, () => void] {
const [, setForceUpdateValue] = useState<number>(0)
const [startDate, setStartDate] = useState(() => new Date())
useEffect(() => {
const interval = setInterval(() => {
setForceUpdateValue(value => value + 1)
}, updateIntervalInMilliSeconds)
return () => clearInterval(interval)
}, [])
return [startDate, () => setStartDate(new Date())]
}

View File

@ -0,0 +1,52 @@
import {MdEmail} from "react-icons/md"
import {AxiosError} from "axios"
import React, {ReactElement} from "react"
import differenceInSeconds from "date-fns/differenceInSeconds"
import {useMutation} from "@tanstack/react-query"
import {LoadingButton} from "@mui/lab"
import {useIntervalUpdate} from "~/hooks"
import {resendEmailVerificationCode} from "~/apis"
import {isDev} from "~/constants/development"
import {MutationStatusSnackbar} from "~/components"
export interface ResendMailButtonProps {
email: string
}
const RESEND_INTERVAL = isDev ? 3 : 60
export default function ResendMailButton({
email,
}: ResendMailButtonProps): ReactElement {
const [startDate, resetInterval] = useIntervalUpdate(1000)
const secondsPassed = differenceInSeconds(new Date(), startDate)
const secondsLeft = RESEND_INTERVAL - secondsPassed
const mutation = useMutation<void, AxiosError, string>(
resendEmailVerificationCode,
{
onSettled: resetInterval,
},
)
const {mutate: resendEmail, isLoading} = mutation
return (
<>
<LoadingButton
variant="contained"
startIcon={<MdEmail />}
onClick={() => resendEmail(email)}
loading={isLoading}
disabled={secondsLeft > 0 || isLoading}
>
<span>
<span>Resend Email</span>
{secondsLeft > 0 && <span> ({secondsLeft})</span>}
</span>
</LoadingButton>
<MutationStatusSnackbar mutation={mutation} />
</>
)
}

View File

@ -3,12 +3,15 @@ import React, {ReactElement} from "react"
import {Grid, Typography} from "@mui/material"
import {MultiStepFormElement, OpenMailButton} from "~/components"
import ResendMailButton from "~/route-widgets/root/YouGotMail/ResendMailButton"
export interface YouGotMailProps {
domain: string
email: string
}
export default function YouGotMail({domain}: YouGotMailProps): ReactElement {
export default function YouGotMail({email}: YouGotMailProps): ReactElement {
const domain = email.split("@")[1]
return (
<MultiStepFormElement>
<Grid
@ -35,6 +38,9 @@ export default function YouGotMail({domain}: YouGotMailProps): ReactElement {
<Grid item>
<OpenMailButton domain={domain} />
</Grid>
<Grid item>
<ResendMailButton email={email} />
</Grid>
</Grid>
</MultiStepFormElement>
)

View File

@ -0,0 +1 @@
export {default} from "./YouGotMail"

View File

@ -24,10 +24,7 @@ export default function SignupRoute(): ReactElement {
onSignUp={setEmail}
key="email"
/>,
<YouGotMail
domain={(email || "").split("@")[1]}
key="you_got_mail"
/>,
<YouGotMail email={email || ""} key="you_got_mail" />,
]}
index={index}
/>

View File

@ -0,0 +1,13 @@
import {AxiosError} from "axios"
import {FastAPIError} from "~/utils/parse-fastapi-error"
export default function getErrorMessage(
error: AxiosError<FastAPIError>,
): string {
if (typeof error.response?.data?.detail === "string") {
return error.response.data.detail
}
return "There was an error."
}