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