88 lines
4.6 KiB
TypeScript
88 lines
4.6 KiB
TypeScript
"use client";
|
|
|
|
import Link from "next/link";
|
|
import { useRouter } from "next/navigation";
|
|
import { useRef, useState, type FormEvent } from "react";
|
|
import { REGEXP_ONLY_DIGITS } from "input-otp";
|
|
import { Alert, AlertDescription } from "@/components/ui/alert";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
import { Field, FieldGroup, FieldLabel } from "@/components/ui/field";
|
|
import { Input } from "@/components/ui/input";
|
|
import { InputOTP, InputOTPGroup, InputOTPSlot } from "@/components/ui/input-otp";
|
|
import { authClient } from "@/lib/auth/client";
|
|
|
|
export function RegistrationOtpForm({ email, nextPath, setup }: {
|
|
email: string; nextPath: string; setup: boolean;
|
|
}) {
|
|
const router = useRouter();
|
|
const emailRef = useRef<HTMLInputElement>(null);
|
|
const [busy, setBusy] = useState(false);
|
|
const [sending, setSending] = useState(false);
|
|
const [error, setError] = useState("");
|
|
const [sent, setSent] = useState(setup);
|
|
|
|
async function verify(event: FormEvent<HTMLFormElement>) {
|
|
event.preventDefault();
|
|
if (busy) return;
|
|
const form = new FormData(event.currentTarget);
|
|
const address = String(form.get("email") ?? "").trim();
|
|
const otp = String(form.get("otp") ?? "").trim();
|
|
setBusy(true);
|
|
setError("");
|
|
try {
|
|
const result = await authClient.emailOtp.verifyEmail({ email: address, otp });
|
|
if (result.error) throw new Error("รหัสไม่ถูกต้องหรือหมดอายุ กรุณาลองอีกครั้ง");
|
|
const returnPath = setup ? `/profile?setup=1&next=${encodeURIComponent(nextPath)}` : nextPath;
|
|
router.replace(returnPath);
|
|
router.refresh();
|
|
} catch (cause) {
|
|
setError(cause instanceof Error ? cause.message : "ยืนยันอีเมลไม่สำเร็จ");
|
|
} finally { setBusy(false); }
|
|
}
|
|
|
|
async function resend() {
|
|
if (sending || busy) return;
|
|
const address = emailRef.current?.value.trim() ?? "";
|
|
if (!address) return;
|
|
setSending(true);
|
|
setError("");
|
|
try {
|
|
const result = await authClient.emailOtp.sendVerificationOtp({ email: address, type: "email-verification" });
|
|
if (result.error) throw new Error("ส่งรหัสใหม่ไม่สำเร็จ กรุณาลองอีกครั้ง");
|
|
setSent(true);
|
|
} catch (cause) {
|
|
setError(cause instanceof Error ? cause.message : "ส่งรหัสใหม่ไม่สำเร็จ");
|
|
} finally { setSending(false); }
|
|
}
|
|
|
|
return <Card className="w-full max-w-sm">
|
|
<CardHeader><CardTitle>ยืนยันอีเมล</CardTitle></CardHeader>
|
|
<CardContent className="flex flex-col gap-4">
|
|
{sent && <Alert><AlertDescription>กรอกรหัส 6 หลักที่ส่งไปยังอีเมลของคุณ รหัสหมดอายุใน 5 นาที</AlertDescription></Alert>}
|
|
{!sent && <p className="text-sm text-muted-foreground">กรอกรหัสจากอีเมล หรือกดส่งรหัสใหม่หากยังไม่มีรหัส</p>}
|
|
{error && <Alert variant="destructive"><AlertDescription>{error}</AlertDescription></Alert>}
|
|
<form onSubmit={verify}>
|
|
<FieldGroup>
|
|
<Field><FieldLabel htmlFor="otp-email">อีเมล</FieldLabel>
|
|
<Input ref={emailRef} id="otp-email" name="email" type="email" defaultValue={email} autoComplete="email" required disabled={busy || sending} />
|
|
</Field>
|
|
<Field><FieldLabel htmlFor="otp-code">รหัส OTP</FieldLabel>
|
|
<InputOTP id="otp-code" name="otp" containerClassName="w-full" inputMode="numeric" autoComplete="one-time-code"
|
|
pattern={REGEXP_ONLY_DIGITS} minLength={6} maxLength={6} required disabled={busy || sending}>
|
|
<InputOTPGroup className="w-full">
|
|
{Array.from({ length: 6 }, (_, index) => <InputOTPSlot key={index} index={index} className="flex-1" />)}
|
|
</InputOTPGroup>
|
|
</InputOTP>
|
|
</Field>
|
|
<Button type="submit" disabled={busy || sending}>{busy ? "กำลังยืนยัน..." : "ยืนยันอีเมล"}</Button>
|
|
</FieldGroup>
|
|
</form>
|
|
<Button variant="link" onClick={resend} disabled={busy || sending}>
|
|
{sending ? "กำลังส่ง..." : "ส่งรหัสใหม่"}
|
|
</Button>
|
|
<Button variant="link" nativeButton={false} render={<Link href="/auth/login" />}>กลับไปเข้าสู่ระบบ</Button>
|
|
</CardContent>
|
|
</Card>;
|
|
}
|