diff --git a/web/packages/accounts/components/two-factor/VerifyTwoFactor.tsx b/web/packages/accounts/components/two-factor/VerifyTwoFactor.tsx deleted file mode 100644 index 0633a35060..0000000000 --- a/web/packages/accounts/components/two-factor/VerifyTwoFactor.tsx +++ /dev/null @@ -1,140 +0,0 @@ -import { LoadingButton } from "@/base/components/mui/LoadingButton"; -import { - CenteredFlex, - VerticallyCentered, -} from "@ente/shared/components/Container"; -import { Box, Typography, styled } from "@mui/material"; -import { Formik, type FormikHelpers } from "formik"; -import { t } from "i18next"; -import React, { useState } from "react"; -import OtpInput from "react-otp-input"; - -interface formValues { - otp: string; -} -interface Props { - onSubmit: VerifyTwoFactorCallback; - buttonText: string; -} - -export type VerifyTwoFactorCallback = ( - otp: string, - markSuccessful: () => void, -) => Promise; - -export function VerifyTwoFactor(props: Props) { - const [waiting, setWaiting] = useState(false); - const [shouldAutoFocus, setShouldAutoFocus] = useState(true); - - const markSuccessful = () => setWaiting(false); - - const submitForm = async ( - { otp }: formValues, - { setFieldError, resetForm }: FormikHelpers, - ) => { - try { - setWaiting(true); - await props.onSubmit(otp, markSuccessful); - } catch (e) { - resetForm(); - const message = e instanceof Error ? e.message : ""; - setFieldError("otp", `${t("generic_error_retry")} (${message})`); - // Workaround (toggling shouldAutoFocus) to reset the focus back to - // the first input field in case of errors. - // https://github.com/devfolioco/react-otp-input/issues/420 - setShouldAutoFocus(false); - setTimeout(() => setShouldAutoFocus(true), 100); - } - setWaiting(false); - }; - - const onChange = - // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type - (callback: Function, triggerSubmit: Function) => (otp: string) => { - callback(otp); - if (otp.length === 6) { - triggerSubmit(otp); - } - }; - return ( - - initialValues={{ otp: "" }} - validateOnChange={false} - validateOnBlur={false} - onSubmit={submitForm} - > - {({ values, errors, handleChange, handleSubmit, submitForm }) => ( - -
- - {t("enter_two_factor_otp")} - - - -} - renderInput={(props) => ( - - )} - /> - {errors.otp && ( - - - {t("incorrect_code")} - - - )} - - - {props.buttonText} - -
-
- )} - - ); -} - -const IndividualInput = styled("input")( - ({ theme }) => ` - font-size: 1.5rem; - padding: 4px; - width: 40px !important; - aspect-ratio: 1; - margin-inline: 6px; - border: 1px solid ${theme.vars.palette.accent.main}; - border-radius: 1px; - outline-color: ${theme.vars.palette.accent.light}; - transition: 0.5s; - ${theme.breakpoints.down("sm")} { - font-size: 1rem; - padding: 4px; - width: 32px !important; - } -`, -); - -const InvalidInputMessage: React.FC = ({ - children, -}) => ( - - {children} - -); diff --git a/web/packages/accounts/pages/two-factor/setup.tsx b/web/packages/accounts/pages/two-factor/setup.tsx index fe23e30b8d..d41aaa0a6f 100644 --- a/web/packages/accounts/pages/two-factor/setup.tsx +++ b/web/packages/accounts/pages/two-factor/setup.tsx @@ -1,19 +1,22 @@ -import { - VerifyTwoFactor, - type VerifyTwoFactorCallback, -} from "@/accounts/components/two-factor/VerifyTwoFactor"; import { appHomeRoute } from "@/accounts/services/redirect"; import type { TwoFactorSecret } from "@/accounts/services/user"; import { enableTwoFactor, setupTwoFactor } from "@/accounts/services/user"; import { CenteredFill } from "@/base/components/containers"; import { FocusVisibleButton } from "@/base/components/mui/FocusVisibleButton"; +import { LoadingButton } from "@/base/components/mui/LoadingButton"; import log from "@/base/log"; +import { + CenteredFlex, + VerticallyCentered, +} from "@ente/shared/components/Container"; import { encryptWithRecoveryKey } from "@ente/shared/crypto/helpers"; import { getData, LS_KEYS, setLSUser } from "@ente/shared/storage/localStorage"; -import { Paper, Stack, styled, Typography } from "@mui/material"; +import { Box, Paper, Stack, styled, Typography } from "@mui/material"; +import { Formik, type FormikHelpers } from "formik"; import { t } from "i18next"; import { useRouter } from "next/router"; -import { useEffect, useState } from "react"; +import React, { useEffect, useState } from "react"; +import OtpInput from "react-otp-input"; import { TwoFactorSetup } from "../../components/two-factor/TwoFactorSetup"; export type SetupMode = "qrCode" | "manualCode"; @@ -84,6 +87,8 @@ const Page: React.FC = () => { ); }; +export default Page; + const ContentsPaper = styled(Paper)(({ theme }) => ({ marginBlock: theme.spacing(2), padding: theme.spacing(4, 2), @@ -94,4 +99,132 @@ const ContentsPaper = styled(Paper)(({ theme }) => ({ gap: theme.spacing(4), })); -export default Page; +interface formValues { + otp: string; +} +interface VerifyTwoFactorProps { + onSubmit: VerifyTwoFactorCallback; + buttonText: string; +} + +export type VerifyTwoFactorCallback = ( + otp: string, + markSuccessful: () => void, +) => Promise; + +const VerifyTwoFactor: React.FC = (props) => { + const [waiting, setWaiting] = useState(false); + const [shouldAutoFocus, setShouldAutoFocus] = useState(true); + + const markSuccessful = () => setWaiting(false); + + const submitForm = async ( + { otp }: formValues, + { setFieldError, resetForm }: FormikHelpers, + ) => { + try { + setWaiting(true); + await props.onSubmit(otp, markSuccessful); + } catch (e) { + resetForm(); + const message = e instanceof Error ? e.message : ""; + setFieldError("otp", `${t("generic_error_retry")} (${message})`); + // Workaround (toggling shouldAutoFocus) to reset the focus back to + // the first input field in case of errors. + // https://github.com/devfolioco/react-otp-input/issues/420 + setShouldAutoFocus(false); + setTimeout(() => setShouldAutoFocus(true), 100); + } + setWaiting(false); + }; + + const onChange = + // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type + (callback: Function, triggerSubmit: Function) => (otp: string) => { + callback(otp); + if (otp.length === 6) { + triggerSubmit(otp); + } + }; + return ( + + initialValues={{ otp: "" }} + validateOnChange={false} + validateOnBlur={false} + onSubmit={submitForm} + > + {({ values, errors, handleChange, handleSubmit, submitForm }) => ( + +
+ + {t("enter_two_factor_otp")} + + + -} + renderInput={(props) => ( + + )} + /> + {errors.otp && ( + + + {t("incorrect_code")} + + + )} + + + {props.buttonText} + +
+
+ )} + + ); +}; + +const IndividualInput = styled("input")( + ({ theme }) => ` + font-size: 1.5rem; + padding: 4px; + width: 40px !important; + aspect-ratio: 1; + margin-inline: 6px; + border: 1px solid ${theme.vars.palette.accent.main}; + border-radius: 1px; + outline-color: ${theme.vars.palette.accent.light}; + transition: 0.5s; + ${theme.breakpoints.down("sm")} { + font-size: 1rem; + padding: 4px; + width: 32px !important; + } +`, +); + +const InvalidInputMessage: React.FC = ({ + children, +}) => ( + + {children} + +);