mirror of
https://github.com/Myzel394/kleckrelay-website.git
synced 2025-06-19 15:55:26 +02:00
added resent email functionality
This commit is contained in:
parent
c8c774de54
commit
457ef7bc8c
@ -16,6 +16,7 @@
|
|||||||
"@tanstack/react-query": "^4.12.0",
|
"@tanstack/react-query": "^4.12.0",
|
||||||
"axios": "^1.1.2",
|
"axios": "^1.1.2",
|
||||||
"crypto-js": "^4.1.1",
|
"crypto-js": "^4.1.1",
|
||||||
|
"date-fns": "^2.29.3",
|
||||||
"formik": "^2.2.9",
|
"formik": "^2.2.9",
|
||||||
"in-seconds": "^1.2.0",
|
"in-seconds": "^1.2.0",
|
||||||
"openpgp": "^5.5.0",
|
"openpgp": "^5.5.0",
|
||||||
@ -28,6 +29,7 @@
|
|||||||
"yup": "^0.32.11"
|
"yup": "^0.32.11"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/date-fns": "^2.6.0",
|
||||||
"@types/openpgp": "^4.4.18",
|
"@types/openpgp": "^4.4.18",
|
||||||
"@types/react": "^18.0.17",
|
"@types/react": "^18.0.17",
|
||||||
"@types/react-dom": "^18.0.6",
|
"@types/react-dom": "^18.0.6",
|
||||||
|
@ -6,3 +6,5 @@ export * from "./signup"
|
|||||||
export {default as signup} from "./signup"
|
export {default as signup} from "./signup"
|
||||||
export * from "./validate-email"
|
export * from "./validate-email"
|
||||||
export {default as validateEmail} 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"
|
||||||
|
12
src/apis/resend-email-verification-code.ts
Normal file
12
src/apis/resend-email-verification-code.ts
Normal 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,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
78
src/components/MutationStatusSnackbar.tsx
Normal file
78
src/components/MutationStatusSnackbar.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
@ -8,3 +8,5 @@ export * from "./PasswordField"
|
|||||||
export {default as PasswordField} from "./PasswordField"
|
export {default as PasswordField} from "./PasswordField"
|
||||||
export * from "./SimpleForm"
|
export * from "./SimpleForm"
|
||||||
export {default as SimpleForm} from "./SimpleForm"
|
export {default as SimpleForm} from "./SimpleForm"
|
||||||
|
export * from "./MutationStatusSnackbar"
|
||||||
|
export {default as MutationStatusSnackbar} from "./MutationStatusSnackbar"
|
||||||
|
2
src/hooks/index.ts
Normal file
2
src/hooks/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from "./use-interval-update"
|
||||||
|
export {default as useIntervalUpdate} from "./use-interval-update"
|
18
src/hooks/use-interval-update.ts
Normal file
18
src/hooks/use-interval-update.ts
Normal 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())]
|
||||||
|
}
|
52
src/route-widgets/root/YouGotMail/ResendMailButton.tsx
Normal file
52
src/route-widgets/root/YouGotMail/ResendMailButton.tsx
Normal 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} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
@ -3,12 +3,15 @@ import React, {ReactElement} from "react"
|
|||||||
import {Grid, Typography} from "@mui/material"
|
import {Grid, Typography} from "@mui/material"
|
||||||
|
|
||||||
import {MultiStepFormElement, OpenMailButton} from "~/components"
|
import {MultiStepFormElement, OpenMailButton} from "~/components"
|
||||||
|
import ResendMailButton from "~/route-widgets/root/YouGotMail/ResendMailButton"
|
||||||
|
|
||||||
export interface YouGotMailProps {
|
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 (
|
return (
|
||||||
<MultiStepFormElement>
|
<MultiStepFormElement>
|
||||||
<Grid
|
<Grid
|
||||||
@ -35,6 +38,9 @@ export default function YouGotMail({domain}: YouGotMailProps): ReactElement {
|
|||||||
<Grid item>
|
<Grid item>
|
||||||
<OpenMailButton domain={domain} />
|
<OpenMailButton domain={domain} />
|
||||||
</Grid>
|
</Grid>
|
||||||
|
<Grid item>
|
||||||
|
<ResendMailButton email={email} />
|
||||||
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</MultiStepFormElement>
|
</MultiStepFormElement>
|
||||||
)
|
)
|
1
src/route-widgets/root/YouGotMail/index.ts
Normal file
1
src/route-widgets/root/YouGotMail/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export {default} from "./YouGotMail"
|
@ -24,10 +24,7 @@ export default function SignupRoute(): ReactElement {
|
|||||||
onSignUp={setEmail}
|
onSignUp={setEmail}
|
||||||
key="email"
|
key="email"
|
||||||
/>,
|
/>,
|
||||||
<YouGotMail
|
<YouGotMail email={email || ""} key="you_got_mail" />,
|
||||||
domain={(email || "").split("@")[1]}
|
|
||||||
key="you_got_mail"
|
|
||||||
/>,
|
|
||||||
]}
|
]}
|
||||||
index={index}
|
index={index}
|
||||||
/>
|
/>
|
||||||
|
0
src/types.d.ts → src/server-types.d.ts
vendored
0
src/types.d.ts → src/server-types.d.ts
vendored
13
src/utils/get-error-message.ts
Normal file
13
src/utils/get-error-message.ts
Normal 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."
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user