OTP Gen

PHOTO EMBED

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;
content_copyCOPY