[web] General refactoring - Update the OTP input component package (#3884)
This commit is contained in:
@@ -27,7 +27,6 @@
|
||||
"piexifjs": "^1.0.6",
|
||||
"pure-react-carousel": "^1.30.1",
|
||||
"react-dropzone": "^14.2",
|
||||
"react-otp-input": "^2.3.1",
|
||||
"react-select": "^5.8.0",
|
||||
"react-top-loading-bar": "^2.3.1",
|
||||
"react-virtualized-auto-sizer": "^1.0",
|
||||
|
||||
@@ -124,37 +124,3 @@ body {
|
||||
.pswp__caption--empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.carousel-inner {
|
||||
padding-bottom: 50px !important;
|
||||
}
|
||||
|
||||
.carousel-indicators li {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.carousel-indicators .active {
|
||||
background-color: #51cd7c;
|
||||
}
|
||||
|
||||
div.otp-input input {
|
||||
width: 36px !important;
|
||||
height: 36px;
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
div.otp-input input::placeholder {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
div.otp-input input:not(:placeholder-shown),
|
||||
div.otp-input input:focus {
|
||||
border: 2px solid #51cd7c;
|
||||
border-radius: 1px;
|
||||
-webkit-transition: 0.5s;
|
||||
transition: 0.5s;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ with Next.js.
|
||||
|
||||
For more details, see [translations.md](translations.md).
|
||||
|
||||
### Others
|
||||
### Other UI components
|
||||
|
||||
- [formik](https://github.com/jaredpalmer/formik) provides an easier to use
|
||||
abstraction for dealing with form state, validation and submission states
|
||||
@@ -140,6 +140,9 @@ For more details, see [translations.md](translations.md).
|
||||
|
||||
- [react-select](https://react-select.com/) is used for search dropdowns.
|
||||
|
||||
- [react-otp-input](https://github.com/devfolioco/react-otp-input) is used to
|
||||
render a segmented OTP input field for 2FA authentication.
|
||||
|
||||
## Utilities
|
||||
|
||||
- [comlink](https://github.com/GoogleChromeLabs/comlink) provides a minimal
|
||||
@@ -191,15 +194,20 @@ For more details, see [translations.md](translations.md).
|
||||
- [chrono-node](https://github.com/wanasit/chrono) is used for parsing natural
|
||||
language queries into dates for showing search results.
|
||||
|
||||
- [matrix](https://github.com/mljs/matrix) is mathematical matrix abstraction
|
||||
by the machine learning code. It is used alongwith
|
||||
[similarity-transformation](https://github.com/shaileshpandit/similarity-transformation-js)
|
||||
during face alignment.
|
||||
|
||||
### UI
|
||||
|
||||
- [react-top-loading-bar](https://github.com/klendi/react-top-loading-bar) is
|
||||
used for showing a progress indicator for global actions (This shouldn't be
|
||||
used always, it is only meant as a fallback when there isn't an otherwise
|
||||
suitable place for showing a local activity indicator).
|
||||
|
||||
- [matrix](https://github.com/mljs/matrix) is mathematical matrix abstraction
|
||||
by the machine learning code. It is used alongwith
|
||||
[similarity-transformation](https://github.com/shaileshpandit/similarity-transformation-js)
|
||||
during face alignment.
|
||||
- [pure-react-carousel](https://github.com/express-labs/pure-react-carousel)
|
||||
is used for the feature carousel on the welcome (login / signup) screen.
|
||||
|
||||
## Auth app specific
|
||||
|
||||
|
||||
@@ -4,10 +4,10 @@ import {
|
||||
CenteredFlex,
|
||||
VerticallyCentered,
|
||||
} from "@ente/shared/components/Container";
|
||||
import { Box, Typography } from "@mui/material";
|
||||
import { Box, Typography, styled } from "@mui/material";
|
||||
import { Formik, type FormikHelpers } from "formik";
|
||||
import { t } from "i18next";
|
||||
import { useRef, useState } from "react";
|
||||
import { useState } from "react";
|
||||
import OtpInput from "react-otp-input";
|
||||
|
||||
interface formValues {
|
||||
@@ -25,7 +25,7 @@ export type VerifyTwoFactorCallback = (
|
||||
|
||||
export default function VerifyTwoFactor(props: Props) {
|
||||
const [waiting, setWaiting] = useState(false);
|
||||
const otpInputRef = useRef<OtpInput>(null);
|
||||
const [shouldAutoFocus, setShouldAutoFocus] = useState(true);
|
||||
|
||||
const markSuccessful = async () => {
|
||||
setWaiting(false);
|
||||
@@ -40,11 +40,13 @@ export default function VerifyTwoFactor(props: Props) {
|
||||
await props.onSubmit(otp, markSuccessful);
|
||||
} catch (e) {
|
||||
resetForm();
|
||||
for (let i = 0; i < 6; i++) {
|
||||
otpInputRef.current?.focusPrevInput();
|
||||
}
|
||||
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);
|
||||
};
|
||||
@@ -71,17 +73,17 @@ export default function VerifyTwoFactor(props: Props) {
|
||||
</Typography>
|
||||
<Box my={2}>
|
||||
<OtpInput
|
||||
ref={otpInputRef}
|
||||
shouldAutoFocus
|
||||
shouldAutoFocus={shouldAutoFocus}
|
||||
value={values.otp}
|
||||
onChange={onChange(
|
||||
handleChange("otp"),
|
||||
submitForm,
|
||||
)}
|
||||
numInputs={6}
|
||||
separator={"-"}
|
||||
isInputNum
|
||||
className={"otp-input"}
|
||||
renderSeparator={<span>-</span>}
|
||||
renderInput={(props) => (
|
||||
<IndividualInput {...props} />
|
||||
)}
|
||||
/>
|
||||
{errors.otp && (
|
||||
<CenteredFlex sx={{ mt: 1 }}>
|
||||
@@ -107,3 +109,23 @@ export default function VerifyTwoFactor(props: Props) {
|
||||
</Formik>
|
||||
);
|
||||
}
|
||||
|
||||
const IndividualInput = styled("input")(
|
||||
({ theme }) => `
|
||||
font-size: 1.5rem;
|
||||
padding: 4px;
|
||||
width: 40px !important;
|
||||
aspect-ratio: 1;
|
||||
margin-inline: 8px;
|
||||
border: 1px solid ${theme.colors.accent.A700};
|
||||
border-radius: 1px;
|
||||
outline-color: ${theme.colors.accent.A300};
|
||||
transition: 0.5s;
|
||||
|
||||
${theme.breakpoints.down("sm")} {
|
||||
font-size: 1rem;
|
||||
padding: 4px;
|
||||
width: 32px !important;
|
||||
}
|
||||
`,
|
||||
);
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"dependencies": {
|
||||
"@/base": "*",
|
||||
"@ente/eslint-config": "*",
|
||||
"@ente/shared": "*"
|
||||
"@ente/shared": "*",
|
||||
"react-otp-input": "^3.1.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3743,10 +3743,10 @@ react-is@^18.3.1:
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e"
|
||||
integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==
|
||||
|
||||
react-otp-input@^2.3.1:
|
||||
version "2.4.0"
|
||||
resolved "https://registry.yarnpkg.com/react-otp-input/-/react-otp-input-2.4.0.tgz#0f0a3de1d8c8d564e2e4fbe5d6b7b56e29e3a6e6"
|
||||
integrity sha512-AIgl7u4sS9BTNCxX1xlaS5fPWay/Zml8Ho5LszXZKXrH1C/TiFsTQGmtl13UecQYO3mSF3HUzG2rrDf0sjEFmg==
|
||||
react-otp-input@^3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/react-otp-input/-/react-otp-input-3.1.1.tgz#910169629812c40a614e6c175cc2c5f36102bb61"
|
||||
integrity sha512-bjPavgJ0/Zmf/AYi4onj8FbH93IjeD+e8pWwxIJreDEWsU1ILR5fs8jEJmMGWSBe/yyvPP6X/W6Mk9UkOCkTPw==
|
||||
|
||||
react-refresh@^0.14.2:
|
||||
version "0.14.2"
|
||||
|
||||
Reference in New Issue
Block a user