feat: 5 digit code ui

This commit is contained in:
Vivian Phung 2024-06-21 20:37:03 -04:00 committed by Vivian Phung
parent b261e7e436
commit 0b82fa915e
4 changed files with 138 additions and 12 deletions

View File

@ -5,10 +5,13 @@ import { authenticateUser, createUser } from '../turnkey-backend';
const router = Router(); const router = Router();
//
// Access Code
//
router.post('/accesscode', async (req, res) => { router.post('/accesscode', async (req, res) => {
console.log('Access Code', req.body); console.log('Access Code', req.body);
const { accesscode } = req.body; const { accesscode } = req.body;
if (accesscode === '444444') { if (accesscode === '44444') {
return res.send({ isValid: true }); return res.send({ isValid: true });
} else { } else {
return res.sendStatus(204); return res.sendStatus(204);

View File

@ -0,0 +1,117 @@
import { useEffect, useRef } from 'react';
import { Input } from '../Input';
export interface VerifyCodeInputProps {
code: string;
setCode: (code: string) => void;
submitCode: () => void;
loading: boolean;
}
export const VerifyCodeInput = ({
code,
setCode,
submitCode,
loading,
}: VerifyCodeInputProps) => {
const inputRefs = useRef<(HTMLInputElement | null)[]>([]);
const handlePaste = (
e: React.ClipboardEvent<HTMLInputElement>,
i: number,
) => {
e.preventDefault();
const pasteData = e.clipboardData.getData('text').replace(/\D/g, ''); // Only digits
if (pasteData.length > 0) {
let newCodeArray = code.split('');
for (let j = 0; j < pasteData.length && i + j < 6; j++) {
newCodeArray[i + j] = pasteData[j];
}
const newCode = newCodeArray.join('');
setCode(newCode);
const nextIndex = Math.min(i + pasteData.length, 5);
const nextInput = inputRefs.current[nextIndex];
if (nextInput) nextInput.focus();
if (!newCode.includes(' ')) {
submitCode();
}
}
};
const handleKeyDown = (
e: React.KeyboardEvent<HTMLInputElement>,
i: number,
) => {
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) return;
if (e.key === 'Backspace') {
e.preventDefault();
const isEmpty = code[i] === ' ';
const newCode = !isEmpty
? code.slice(0, i) + ' ' + code.slice(i + 1, 6)
: code.slice(0, i - 1) + ' ' + code.slice(i, 6);
setCode(newCode.slice(0, 6));
if (i === 0 || !isEmpty) return;
const prev = inputRefs.current[i - 1];
if (prev) prev.focus();
return;
}
if (!e.key.match(/[0-9]/)) return;
e.preventDefault(); // Prevent the default event to avoid duplicate input
const newCode = code.slice(0, i) + e.key + code.slice(i + 1, 6);
setCode(newCode);
if (i === 5) {
submitCode();
return;
}
const next = inputRefs.current[i + 1];
if (next) next.focus();
};
const handleChange = (e: React.ChangeEvent<HTMLInputElement>, i: number) => {
const value = e.target.value.slice(-1);
if (!value.match(/[0-9]/)) return;
const newCode = code.slice(0, i) + value + code.slice(i + 1, 6);
setCode(newCode);
if (i < 5) {
const next = inputRefs.current[i + 1];
if (next) next.focus();
}
if (!newCode.includes(' ')) {
submitCode();
}
};
useEffect(() => {
if (inputRefs.current[0]) {
inputRefs.current[0].focus();
}
}, []);
return (
<div className="flex gap-2">
{code.split('').map((char, i) => (
<Input
key={i}
value={char === ' ' ? '' : char}
ref={(el) => (inputRefs.current[i] = el)}
onChange={(e) => handleChange(e, i)}
onPaste={(e) => handlePaste(e, i)}
onKeyDown={(e) => handleKeyDown(e, i)}
disabled={!!loading}
style={{ textAlign: 'center' }} // Add this line to center text
/>
))}
</div>
);
};

View File

@ -0,0 +1 @@
export { VerifyCodeInput } from './VerifyCodeInput';

View File

@ -6,7 +6,7 @@ import {
LoaderIcon, LoaderIcon,
} from 'components/shared/CustomIcon'; } from 'components/shared/CustomIcon';
import { WavyBorder } from 'components/shared/WavyBorder'; import { WavyBorder } from 'components/shared/WavyBorder';
import { Input } from 'components/shared/Input'; import { VerifyCodeInput } from 'components/shared/VerifyCodeInput';
import { verifyAccessCode } from 'utils/accessCode'; import { verifyAccessCode } from 'utils/accessCode';
type AccessMethod = 'accesscode' | 'passkey'; type AccessMethod = 'accesscode' | 'passkey';
@ -20,7 +20,7 @@ type AccessCodeProps = {
export const AccessCode: React.FC<AccessCodeProps> = ({ export const AccessCode: React.FC<AccessCodeProps> = ({
onCorrectAccessCode, onCorrectAccessCode,
}) => { }) => {
const [accessCode, setAccessCode] = useState(''); const [accessCode, setAccessCode] = useState(' ');
const [error, setError] = useState<Err | null>(); const [error, setError] = useState<Err | null>();
const [accessMethod, setAccessMethod] = useState<AccessMethod | false>(false); const [accessMethod, setAccessMethod] = useState<AccessMethod | false>(false);
@ -28,6 +28,9 @@ export const AccessCode: React.FC<AccessCodeProps> = ({
setAccessMethod('accesscode'); setAccessMethod('accesscode');
try { try {
const isValidAccessCode = await verifyAccessCode(accessCode); const isValidAccessCode = await verifyAccessCode(accessCode);
// add a pause for ux
await new Promise((resolve) => setTimeout(resolve, 250));
if (isValidAccessCode) { if (isValidAccessCode) {
localStorage.setItem('accessCode', accessCode); localStorage.setItem('accessCode', accessCode);
onCorrectAccessCode(); onCorrectAccessCode();
@ -43,7 +46,7 @@ export const AccessCode: React.FC<AccessCodeProps> = ({
} }
const loading = accessMethod; const loading = accessMethod;
const isValidAccessCodeLength = accessCode.length === 6; const isValidAccessCodeLength = accessCode.trim().length === 5;
return ( return (
<div> <div>
@ -56,10 +59,11 @@ export const AccessCode: React.FC<AccessCodeProps> = ({
<div className="self-stretch p-4 xs:p-6 flex-col justify-center items-center gap-8 flex"> <div className="self-stretch p-4 xs:p-6 flex-col justify-center items-center gap-8 flex">
<div className="self-stretch flex-col gap-8 flex"> <div className="self-stretch flex-col gap-8 flex">
<div className="flex-col justify-start items-start gap-2 inline-flex"> <div className="flex-col justify-start items-start gap-2 inline-flex">
<Input <VerifyCodeInput
value={accessCode} loading={!!loading}
onChange={(e) => setAccessCode(e.target.value)} code={accessCode}
disabled={!!loading} setCode={setAccessCode}
submitCode={validateAccessCode}
/> />
</div> </div>
<Button <Button
@ -70,9 +74,7 @@ export const AccessCode: React.FC<AccessCodeProps> = ({
<ArrowRightCircleFilledIcon height="16" /> <ArrowRightCircleFilledIcon height="16" />
) )
} }
onClick={() => { onClick={validateAccessCode}
validateAccessCode();
}}
variant={'secondary'} variant={'secondary'}
disabled={!accessCode || !isValidAccessCodeLength || !!loading} disabled={!accessCode || !isValidAccessCodeLength || !!loading}
> >
@ -82,7 +84,10 @@ export const AccessCode: React.FC<AccessCodeProps> = ({
<div className="flex flex-col gap-3"> <div className="flex flex-col gap-3">
<div className="justify-center items-center gap-2 inline-flex"> <div className="justify-center items-center gap-2 inline-flex">
<div className="text-red-500 text-sm"> <div className="text-red-500 text-sm">
Error: {error.message} Error: {error.message}.{' '}
<a href="/signup" className="underline">
Try again?
</a>
</div> </div>
</div> </div>
</div> </div>