OTP Gen
Wed Feb 08 2023 01:27:47 GMT+0000 (Coordinated Universal Time)
Saved by @anirudhnair42 #typescriptreact
import { EPatroclusStatus } from "@lendtable/common/dtos/PatroclusDto"; import { ErrorMessage } from "components-v2/validation/ErrorMessage"; import React, { FC, useEffect, useRef, useState } from "react"; import Button from "./Button"; import Header from "./Header"; import LinkIcons from "./LinkIcons"; let currentOtpIndex: number = 0; const TwoFactorAuth: FC<{ onContinue: (otp: string) => void; onClose: () => void; loading: boolean; status: EPatroclusStatus.MfaTokenRequired | EPatroclusStatus.MfaTokenInvalid; tokenLength: number; }> = ({ onContinue, onClose, status, loading, tokenLength }) => { const [otp, setOtp] = useState<string[]>(new Array(tokenLength).fill("")); const [activeOtpIndex, setActiveOtpIndex] = useState<number>(0); const [invalid, setInvalid] = useState(false); const inputRef = useRef<HTMLInputElement>(null); const handleOnChange = ({ target, }: React.ChangeEvent<HTMLInputElement>): void => { const { value } = target; const newOtp: string[] = [...otp]; newOtp[currentOtpIndex] = value.substring(value.length - 1); if (!value) setActiveOtpIndex(currentOtpIndex - 1); else setActiveOtpIndex(currentOtpIndex + 1); setOtp(newOtp); }; const handlePaste = (e: React.ClipboardEvent<HTMLInputElement>) => { e.preventDefault(); const pastedString = e.clipboardData.getData("text"); const newOtp: string[] = []; for (let i = 0; i < tokenLength; i++) { newOtp.push(pastedString[i] || ""); } setActiveOtpIndex(Math.min(pastedString.length, 5)); setOtp(newOtp); }; const handleOnKeyDown = ( { key }: React.KeyboardEvent<HTMLInputElement>, index: number ) => { currentOtpIndex = index; if (key === "Backspace") { setActiveOtpIndex(currentOtpIndex - 1); } }; const onSubmit = (e?: React.FormEvent<HTMLFormElement>) => { e && e.preventDefault(); if (loading) { return; } if (otp.join("").length < tokenLength) { inputRef.current?.focus(); setInvalid(true); setTimeout(() => { setInvalid(false); }, 820); return; } onContinue(otp.join("")); }; useEffect(() => { inputRef.current?.focus(); }, [activeOtpIndex]); const resetOtp = () => { const newOtp: string[] = new Array(tokenLength).fill(""); setOtp(newOtp); setActiveOtpIndex(0); }; const currentNumberOfCharacters = otp.filter( (character) => character !== "" ).length; useEffect(() => { if ( status === EPatroclusStatus.MfaTokenInvalid && currentNumberOfCharacters > 0 ) { resetOtp(); } }, [status]); return ( <> <Header onClose={onClose}> <LinkIcons /> </Header> <div> <div className="mx-2"> <div className="mb-2 text-2xl font-semibold">Enter your 2FA Code</div> <div className="mb-6 text-xs font-light"> Enter the 2fa code SMS verification received on your phone or email address: </div> </div> <form className={ (invalid ? "animate-shake " : "") + "flex items-center justify-center space-x-2" } id="two-factor-auth" onSubmit={(e) => onSubmit(e)} > {otp.map((_, index) => { return ( <React.Fragment key={index}> <input ref={index === activeOtpIndex ? inputRef : null} type="text" className="spin-button-none border-1 h-14 w-10 rounded-lg border-black bg-transparent text-center text-xl font-semibold text-gray-400 outline-none transition focus:border-gray-700 focus:text-gray-700" onChange={handleOnChange} onKeyDown={(e) => handleOnKeyDown(e, index)} onPaste={(e) => handlePaste(e)} value={otp[index]} disabled={loading} inputMode="numeric" pattern="[0-9]*" autoComplete="one-time-code" /> </React.Fragment> ); })} </form> {status === EPatroclusStatus.MfaTokenInvalid && ( <ErrorMessage className="mt-2" message="Your 2FA code was incorrect." /> )} </div> <Button form="two-factor-auth" label="Submit" submitting={loading} disabled={currentNumberOfCharacters < tokenLength} ></Button> </> ); }; export default TwoFactorAuth;
Comments