geo/frontend/app/(auth)/verify-email/page.tsx

155 lines
4.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import { useState, useEffect, Suspense } from "react";
import { useSearchParams, useRouter } from "next/navigation";
import Link from "next/link";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { api } from "@/lib/api";
function VerifyEmailForm() {
const router = useRouter();
const searchParams = useSearchParams();
const email = searchParams.get("email") || "";
const [code, setCode] = useState("");
const [error, setError] = useState("");
const [loading, setLoading] = useState(false);
const [success, setSuccess] = useState(false);
const [resendCountdown, setResendCountdown] = useState(0);
useEffect(() => {
if (resendCountdown > 0) {
const timer = setTimeout(() => setResendCountdown((c) => c - 1), 1000);
return () => clearTimeout(timer);
}
}, [resendCountdown]);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!email) {
setError("邮箱地址无效");
return;
}
setLoading(true);
setError("");
try {
await api.auth.verifyEmail(email, code);
setSuccess(true);
setTimeout(() => {
router.push("/login");
}, 1500);
} catch (err: unknown) {
const message = err instanceof Error ? err.message : "验证失败";
setError(message);
} finally {
setLoading(false);
}
};
const handleResend = async () => {
if (!email || resendCountdown > 0) return;
setError("");
try {
await api.auth.resendVerification(email);
setResendCountdown(60);
} catch (err: unknown) {
const message = err instanceof Error ? err.message : "发送失败";
setError(message);
}
};
if (success) {
return (
<Card className="w-full max-w-md">
<CardHeader className="space-y-1">
<CardTitle className="text-2xl"></CardTitle>
<CardDescription>...</CardDescription>
</CardHeader>
<CardFooter>
<Link href="/login" className="text-sm text-primary hover:underline">
</Link>
</CardFooter>
</Card>
);
}
return (
<Card className="w-full max-w-md">
<CardHeader className="space-y-1">
<CardTitle className="text-2xl"></CardTitle>
<CardDescription>
<span className="font-medium">{email || "您的邮箱"}</span>
</CardDescription>
</CardHeader>
<form onSubmit={handleSubmit}>
<CardContent className="space-y-4">
{error && (
<p className="text-sm text-destructive">{error}</p>
)}
<div className="space-y-2">
<Label htmlFor="code"></Label>
<Input
id="code"
type="text"
placeholder="请输入6位验证码"
maxLength={6}
value={code}
onChange={(e) => setCode(e.target.value)}
required
/>
</div>
<div className="flex items-center justify-between">
<Button
type="button"
variant="outline"
size="sm"
onClick={handleResend}
disabled={resendCountdown > 0 || !email}
>
{resendCountdown > 0
? `${resendCountdown} 秒后重新发送`
: "重新发送验证码"}
</Button>
</div>
</CardContent>
<CardFooter className="flex flex-col space-y-4">
<Button type="submit" className="w-full" disabled={loading}>
{loading ? "验证中..." : "验证"}
</Button>
<Link href="/login" className="text-sm text-primary hover:underline">
</Link>
</CardFooter>
</form>
</Card>
);
}
export default function VerifyEmailPage() {
return (
<Suspense
fallback={
<Card className="w-full max-w-md">
<CardHeader className="space-y-1">
<CardTitle className="text-2xl"></CardTitle>
<CardDescription>...</CardDescription>
</CardHeader>
</Card>
}
>
<VerifyEmailForm />
</Suspense>
);
}