From 457ef7bc8c9e2035a2530cb1d178496629481cb0 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Sat, 15 Oct 2022 10:03:09 +0200 Subject: [PATCH] added resent email functionality --- package.json | 2 + src/apis/index.ts | 2 + src/apis/resend-email-verification-code.ts | 12 +++ src/components/MutationStatusSnackbar.tsx | 78 +++++++++++++++++++ src/components/index.ts | 2 + src/hooks/index.ts | 2 + src/hooks/use-interval-update.ts | 18 +++++ .../root/YouGotMail/ResendMailButton.tsx | 52 +++++++++++++ .../root/{ => YouGotMail}/YouGotMail.tsx | 10 ++- src/route-widgets/root/YouGotMail/index.ts | 1 + src/routes/SignupRoute.tsx | 5 +- src/{types.d.ts => server-types.d.ts} | 0 src/utils/get-error-message.ts | 13 ++++ 13 files changed, 191 insertions(+), 6 deletions(-) create mode 100644 src/apis/resend-email-verification-code.ts create mode 100644 src/components/MutationStatusSnackbar.tsx create mode 100644 src/hooks/index.ts create mode 100644 src/hooks/use-interval-update.ts create mode 100644 src/route-widgets/root/YouGotMail/ResendMailButton.tsx rename src/route-widgets/root/{ => YouGotMail}/YouGotMail.tsx (75%) create mode 100644 src/route-widgets/root/YouGotMail/index.ts rename src/{types.d.ts => server-types.d.ts} (100%) create mode 100644 src/utils/get-error-message.ts diff --git a/package.json b/package.json index 738ddaa..971d6be 100755 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/apis/index.ts b/src/apis/index.ts index 8bba37a..4b7beea 100644 --- a/src/apis/index.ts +++ b/src/apis/index.ts @@ -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" diff --git a/src/apis/resend-email-verification-code.ts b/src/apis/resend-email-verification-code.ts new file mode 100644 index 0000000..cf90f11 --- /dev/null +++ b/src/apis/resend-email-verification-code.ts @@ -0,0 +1,12 @@ +import axios from "axios" + +export default function resendEmailVerificationCode( + email: string, +): Promise { + return axios.post( + `${import.meta.env.VITE_SERVER_BASE_URL}/auth/resend-email`, + { + email, + }, + ) +} diff --git a/src/components/MutationStatusSnackbar.tsx b/src/components/MutationStatusSnackbar.tsx new file mode 100644 index 0000000..2542996 --- /dev/null +++ b/src/components/MutationStatusSnackbar.tsx @@ -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 + + successMessage?: string + errorMessage?: string +} + +export default function MutationStatusSnackbar< + TData extends {data: {detail?: string}} = {data: {detail?: string}}, + TError extends AxiosError = AxiosError, + TVariables = unknown, + TContext = unknown, +>({ + mutation, + successMessage, + errorMessage, +}: MutationStatusSnackbarProps< + TData, + TError, + TVariables, + TContext +>): ReactElement { + const [open, setOpen] = useState(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 ( + setOpen(false)} + autoHideDuration={5000} + > + + {message} + + + ) +} diff --git a/src/components/index.ts b/src/components/index.ts index f8b5312..702f9f5 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -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" diff --git a/src/hooks/index.ts b/src/hooks/index.ts new file mode 100644 index 0000000..a71313c --- /dev/null +++ b/src/hooks/index.ts @@ -0,0 +1,2 @@ +export * from "./use-interval-update" +export {default as useIntervalUpdate} from "./use-interval-update" diff --git a/src/hooks/use-interval-update.ts b/src/hooks/use-interval-update.ts new file mode 100644 index 0000000..7f70f8f --- /dev/null +++ b/src/hooks/use-interval-update.ts @@ -0,0 +1,18 @@ +import {useEffect, useState} from "react" + +export default function useIntervalUpdate( + updateIntervalInMilliSeconds = 1000, +): [Date, () => void] { + const [, setForceUpdateValue] = useState(0) + const [startDate, setStartDate] = useState(() => new Date()) + + useEffect(() => { + const interval = setInterval(() => { + setForceUpdateValue(value => value + 1) + }, updateIntervalInMilliSeconds) + + return () => clearInterval(interval) + }, []) + + return [startDate, () => setStartDate(new Date())] +} diff --git a/src/route-widgets/root/YouGotMail/ResendMailButton.tsx b/src/route-widgets/root/YouGotMail/ResendMailButton.tsx new file mode 100644 index 0000000..61ef97a --- /dev/null +++ b/src/route-widgets/root/YouGotMail/ResendMailButton.tsx @@ -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( + resendEmailVerificationCode, + { + onSettled: resetInterval, + }, + ) + const {mutate: resendEmail, isLoading} = mutation + + return ( + <> + } + onClick={() => resendEmail(email)} + loading={isLoading} + disabled={secondsLeft > 0 || isLoading} + > + + Resend Email + {secondsLeft > 0 && ({secondsLeft})} + + + + + ) +} diff --git a/src/route-widgets/root/YouGotMail.tsx b/src/route-widgets/root/YouGotMail/YouGotMail.tsx similarity index 75% rename from src/route-widgets/root/YouGotMail.tsx rename to src/route-widgets/root/YouGotMail/YouGotMail.tsx index 78d6e1d..d2c8729 100644 --- a/src/route-widgets/root/YouGotMail.tsx +++ b/src/route-widgets/root/YouGotMail/YouGotMail.tsx @@ -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 ( + + + ) diff --git a/src/route-widgets/root/YouGotMail/index.ts b/src/route-widgets/root/YouGotMail/index.ts new file mode 100644 index 0000000..93b6657 --- /dev/null +++ b/src/route-widgets/root/YouGotMail/index.ts @@ -0,0 +1 @@ +export {default} from "./YouGotMail" diff --git a/src/routes/SignupRoute.tsx b/src/routes/SignupRoute.tsx index 7808767..17ef45a 100644 --- a/src/routes/SignupRoute.tsx +++ b/src/routes/SignupRoute.tsx @@ -24,10 +24,7 @@ export default function SignupRoute(): ReactElement { onSignUp={setEmail} key="email" />, - , + , ]} index={index} /> diff --git a/src/types.d.ts b/src/server-types.d.ts similarity index 100% rename from src/types.d.ts rename to src/server-types.d.ts diff --git a/src/utils/get-error-message.ts b/src/utils/get-error-message.ts new file mode 100644 index 0000000..bc27353 --- /dev/null +++ b/src/utils/get-error-message.ts @@ -0,0 +1,13 @@ +import {AxiosError} from "axios" + +import {FastAPIError} from "~/utils/parse-fastapi-error" + +export default function getErrorMessage( + error: AxiosError, +): string { + if (typeof error.response?.data?.detail === "string") { + return error.response.data.detail + } + + return "There was an error." +}