VerifyForm.tsx 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. import { Formik, FormikHelpers } from "formik";
  2. import { t } from "i18next";
  3. import { useRef, useState } from "react";
  4. import OtpInput from "react-otp-input";
  5. import InvalidInputMessage from "@ente/accounts/components/two-factor/InvalidInputMessage";
  6. import {
  7. CenteredFlex,
  8. VerticallyCentered,
  9. } from "@ente/shared/components/Container";
  10. import SubmitButton from "@ente/shared/components/SubmitButton";
  11. import { sleep } from "@ente/shared/utils";
  12. import { Box, Typography } from "@mui/material";
  13. interface formValues {
  14. otp: string;
  15. }
  16. interface Props {
  17. onSubmit: VerifyTwoFactorCallback;
  18. buttonText: string;
  19. }
  20. export type VerifyTwoFactorCallback = (
  21. otp: string,
  22. markSuccessful: () => Promise<void>,
  23. ) => Promise<void>;
  24. export default function VerifyTwoFactor(props: Props) {
  25. const [waiting, setWaiting] = useState(false);
  26. const otpInputRef = useRef(null);
  27. const [success, setSuccess] = useState(false);
  28. const markSuccessful = async () => {
  29. setWaiting(false);
  30. setSuccess(true);
  31. await sleep(1000);
  32. };
  33. const submitForm = async (
  34. { otp }: formValues,
  35. { setFieldError, resetForm }: FormikHelpers<formValues>,
  36. ) => {
  37. try {
  38. setWaiting(true);
  39. await props.onSubmit(otp, markSuccessful);
  40. } catch (e) {
  41. resetForm();
  42. for (let i = 0; i < 6; i++) {
  43. otpInputRef.current?.focusPrevInput();
  44. }
  45. setFieldError("otp", `${t("UNKNOWN_ERROR")} ${e.message}`);
  46. }
  47. setWaiting(false);
  48. };
  49. const onChange =
  50. (callback: Function, triggerSubmit: Function) => (otp: string) => {
  51. callback(otp);
  52. if (otp.length === 6) {
  53. triggerSubmit(otp);
  54. }
  55. };
  56. return (
  57. <Formik<formValues>
  58. initialValues={{ otp: "" }}
  59. validateOnChange={false}
  60. validateOnBlur={false}
  61. onSubmit={submitForm}
  62. >
  63. {({ values, errors, handleChange, handleSubmit, submitForm }) => (
  64. <VerticallyCentered>
  65. <form noValidate onSubmit={handleSubmit}>
  66. <Typography mb={2} variant="small" color="text.muted">
  67. {t("ENTER_TWO_FACTOR_OTP")}
  68. </Typography>
  69. <Box my={2}>
  70. <OtpInput
  71. ref={otpInputRef}
  72. shouldAutoFocus
  73. value={values.otp}
  74. onChange={onChange(
  75. handleChange("otp"),
  76. submitForm,
  77. )}
  78. numInputs={6}
  79. separator={"-"}
  80. isInputNum
  81. className={"otp-input"}
  82. />
  83. {errors.otp && (
  84. <CenteredFlex sx={{ mt: 1 }}>
  85. <InvalidInputMessage>
  86. {t("INCORRECT_CODE")}
  87. </InvalidInputMessage>
  88. </CenteredFlex>
  89. )}
  90. </Box>
  91. <SubmitButton
  92. buttonText={props.buttonText}
  93. loading={waiting}
  94. success={success}
  95. disabled={values.otp.length < 6}
  96. />
  97. </form>
  98. </VerticallyCentered>
  99. )}
  100. </Formik>
  101. );
  102. }