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} );