mirror of
https://github.com/Hestia-Homes/assessment-model.git
synced 2026-06-08 11:37:25 +00:00
Drop useEffect from VerifyCodeForm; replace tick-based countdown
The per-second countdown was the only thing pulling useEffect into this component, in violation of the project rule (CLAUDE.md: avoid useEffect, prefer event handlers). The hook was driving a tick purely so we could show "Resend in 28s" / "27s" / ... — none of which is load-bearing. Replace it with a single setTimeout fired from the resend event handler that flips resendStatus from "cooldown" back to "idle" after 30 seconds. The button stays disabled with "Code sent — wait a moment" instead of showing a live countdown. Same blocking signal, no hook needed. While here, split the single status enum into verifyStatus + resendStatus so the verify button no longer wrongly disables during a resend cooldown (latent bug from the previous shape). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
ee506425fd
commit
47efd1bd5a
1 changed files with 28 additions and 30 deletions
|
|
@ -1,36 +1,33 @@
|
|||
"use client";
|
||||
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useRef, useState } from "react";
|
||||
import { signIn } from "next-auth/react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Button } from "@/app/shadcn_components/ui/button";
|
||||
import { Input } from "@/app/shadcn_components/ui/input";
|
||||
|
||||
const RESEND_COOLDOWN_SECONDS = 30;
|
||||
const RESEND_COOLDOWN_MS = 30_000;
|
||||
|
||||
type Status = "idle" | "verifying" | "resending";
|
||||
type VerifyStatus = "idle" | "verifying";
|
||||
type ResendStatus = "idle" | "resending" | "cooldown";
|
||||
|
||||
export default function VerifyCodeForm({ email }: { email: string }) {
|
||||
const router = useRouter();
|
||||
const [code, setCode] = useState("");
|
||||
const [status, setStatus] = useState<Status>("idle");
|
||||
const [verifyStatus, setVerifyStatus] = useState<VerifyStatus>("idle");
|
||||
const [resendStatus, setResendStatus] = useState<ResendStatus>("idle");
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [resendCountdown, setResendCountdown] = useState(0);
|
||||
const [resendNotice, setResendNotice] = useState<string | null>(null);
|
||||
const inFlightRef = useRef(false);
|
||||
|
||||
// Resend cooldown timer
|
||||
useEffect(() => {
|
||||
if (resendCountdown <= 0) return;
|
||||
const id = setTimeout(() => setResendCountdown((s) => s - 1), 1000);
|
||||
return () => clearTimeout(id);
|
||||
}, [resendCountdown]);
|
||||
const isWorking =
|
||||
verifyStatus === "verifying" || resendStatus === "resending";
|
||||
|
||||
async function submitCode(value: string) {
|
||||
if (inFlightRef.current) return;
|
||||
if (!/^\d{6}$/.test(value)) return;
|
||||
inFlightRef.current = true;
|
||||
setStatus("verifying");
|
||||
setVerifyStatus("verifying");
|
||||
setError(null);
|
||||
|
||||
const res = await signIn("email-code", {
|
||||
|
|
@ -46,7 +43,7 @@ export default function VerifyCodeForm({ email }: { email: string }) {
|
|||
return;
|
||||
}
|
||||
|
||||
setStatus("idle");
|
||||
setVerifyStatus("idle");
|
||||
setCode("");
|
||||
setError(
|
||||
"That code didn't match. Check the latest email — older codes stop working as soon as you request a new one.",
|
||||
|
|
@ -62,25 +59,33 @@ export default function VerifyCodeForm({ email }: { email: string }) {
|
|||
}
|
||||
|
||||
async function handleResend() {
|
||||
if (resendCountdown > 0 || status === "resending") return;
|
||||
if (!email) return;
|
||||
setStatus("resending");
|
||||
if (isWorking || resendStatus === "cooldown" || !email) return;
|
||||
setResendStatus("resending");
|
||||
setError(null);
|
||||
setResendNotice(null);
|
||||
|
||||
const res = await signIn("email", { email, redirect: false });
|
||||
|
||||
setStatus("idle");
|
||||
if (res?.error) {
|
||||
setResendStatus("idle");
|
||||
setError(
|
||||
"We couldn't send a new code right now. Wait a minute and try again.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
setResendNotice("A new code is on its way. Older codes stop working.");
|
||||
setResendCountdown(RESEND_COOLDOWN_SECONDS);
|
||||
setResendStatus("cooldown");
|
||||
setTimeout(() => setResendStatus("idle"), RESEND_COOLDOWN_MS);
|
||||
}
|
||||
|
||||
const resendLabel =
|
||||
resendStatus === "resending"
|
||||
? "Sending…"
|
||||
: resendStatus === "cooldown"
|
||||
? "Code sent — wait a moment"
|
||||
: "Resend code";
|
||||
|
||||
return (
|
||||
<div className="space-y-4 text-left">
|
||||
<div>
|
||||
|
|
@ -98,7 +103,7 @@ export default function VerifyCodeForm({ email }: { email: string }) {
|
|||
onChange={(e) => handleChange(e.target.value)}
|
||||
placeholder="••••••"
|
||||
className="h-12 text-center text-2xl tracking-[0.5em] font-mono"
|
||||
disabled={status === "verifying"}
|
||||
disabled={verifyStatus === "verifying"}
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -106,10 +111,10 @@ export default function VerifyCodeForm({ email }: { email: string }) {
|
|||
<Button
|
||||
type="button"
|
||||
onClick={() => submitCode(code)}
|
||||
disabled={status !== "idle" || code.length !== 6}
|
||||
disabled={isWorking || code.length !== 6}
|
||||
className="bg-brandbrown hover:bg-hoverblue w-full text-base py-3"
|
||||
>
|
||||
{status === "verifying" ? "Verifying…" : "Sign in"}
|
||||
{verifyStatus === "verifying" ? "Verifying…" : "Sign in"}
|
||||
</Button>
|
||||
|
||||
<div className="text-center text-sm space-y-2">
|
||||
|
|
@ -120,19 +125,12 @@ export default function VerifyCodeForm({ email }: { email: string }) {
|
|||
<button
|
||||
type="button"
|
||||
onClick={handleResend}
|
||||
disabled={
|
||||
resendCountdown > 0 || status === "resending" || !email
|
||||
}
|
||||
disabled={isWorking || resendStatus === "cooldown" || !email}
|
||||
className="text-brandblue hover:underline disabled:text-gray-400 disabled:no-underline"
|
||||
>
|
||||
{status === "resending"
|
||||
? "Sending…"
|
||||
: resendCountdown > 0
|
||||
? `Resend code in ${resendCountdown}s`
|
||||
: "Resend code"}
|
||||
{resendLabel}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue