Files
course-agent-od/web/src/app/(auth)/register/page.tsx
T
pengxiao ddbb79b9f6 Initial commit: 国土空间规划课程智能体 v1.0
单容器 Docker 架构的国土空间规划课程智能问答系统,集成 FastAPI 后端与 Next.js 前端。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 09:40:18 +08:00

266 lines
9.3 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 } from "react";
import { useRouter } from "next/navigation";
import Link from "next/link";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { useAuthStore } from "@/store/auth";
import { BookOpen, Eye, EyeOff, Loader2, CheckCircle } from "lucide-react";
import { FlickeringGrid } from "@/components/magicui/flickering-grid";
import { ThemeToggle } from "@/components/ui/theme-toggle";
const registerSchema = z.object({
username: z.string().min(3, "用户名至少3个字符").max(20, "用户名最多20个字符"),
email: z.string().email("请输入有效的邮箱地址"),
password: z.string().min(6, "密码至少6个字符"),
confirmPassword: z.string(),
full_name: z.string().optional(),
}).refine((data) => data.password === data.confirmPassword, {
message: "密码不匹配",
path: ["confirmPassword"],
});
type RegisterForm = z.infer<typeof registerSchema>;
export default function RegisterPage() {
const [showPassword, setShowPassword] = useState(false);
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const router = useRouter();
const { register: registerUser, error, clearError } = useAuthStore();
const {
register,
handleSubmit,
formState: { errors },
} = useForm<RegisterForm>({
resolver: zodResolver(registerSchema),
});
const onSubmit = async (data: RegisterForm) => {
setIsLoading(true);
clearError();
const success = await registerUser({
username: data.username,
email: data.email,
password: data.password,
full_name: data.full_name,
});
if (success) {
router.push("/chat");
}
setIsLoading(false);
};
return (
<div className="min-h-screen bg-app relative overflow-hidden">
{/* 背景动画 */}
<FlickeringGrid
className="absolute inset-0 z-0 [mask-image:radial-gradient(800px_circle_at_center,white,transparent)]"
squareSize={4}
gridGap={4}
color="#60A5FA"
maxOpacity={0.1}
flickerChance={0.05}
/>
{/* 主题切换按钮 */}
<div className="absolute top-4 right-4 z-10">
<ThemeToggle />
</div>
<div className="relative z-10 flex items-center justify-center min-h-screen p-4">
<div className="w-full max-w-md">
{/* Logo和标题 */}
<div className="text-center mb-8">
<Link
href="/"
className="flex items-center justify-center space-x-3 mb-6 group cursor-pointer transition-opacity hover:opacity-80"
>
<div className="w-12 h-12 bg-gradient-to-r from-blue-500 to-purple-600 rounded-xl flex items-center justify-center shadow-lg group-hover:shadow-xl transition-shadow">
<BookOpen className="w-7 h-7 text-white" />
</div>
<span className="text-2xl font-bold">
</span>
</Link>
<p className="text-muted-foreground text-lg"></p>
</div>
{/* 注册表单 */}
<Card className="backdrop-blur-sm bg-card/80 border-border/50 shadow-xl">
<CardHeader>
<CardTitle className="text-2xl"></CardTitle>
<CardDescription>
</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
{/* 错误提示 */}
{error && (
<Alert variant="destructive">
<AlertDescription>{error}</AlertDescription>
</Alert>
)}
{/* 用户名输入 */}
<div className="space-y-2">
<Label htmlFor="username"> *</Label>
<Input
id="username"
type="text"
placeholder="请输入用户名"
{...register("username")}
disabled={isLoading}
/>
{errors.username && (
<p className="text-sm text-red-600">{errors.username.message}</p>
)}
</div>
{/* 邮箱输入 */}
<div className="space-y-2">
<Label htmlFor="email"> *</Label>
<Input
id="email"
type="email"
placeholder="请输入邮箱地址"
{...register("email")}
disabled={isLoading}
/>
{errors.email && (
<p className="text-sm text-red-600">{errors.email.message}</p>
)}
</div>
{/* 姓名输入 */}
<div className="space-y-2">
<Label htmlFor="full_name"></Label>
<Input
id="full_name"
type="text"
placeholder="请输入您的姓名"
{...register("full_name")}
disabled={isLoading}
/>
{errors.full_name && (
<p className="text-sm text-red-600">{errors.full_name.message}</p>
)}
</div>
{/* 密码输入 */}
<div className="space-y-2">
<Label htmlFor="password"> *</Label>
<div className="relative">
<Input
id="password"
type={showPassword ? "text" : "password"}
placeholder="请输入密码"
{...register("password")}
disabled={isLoading}
/>
<button
type="button"
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500 hover:text-gray-700"
onClick={() => setShowPassword(!showPassword)}
disabled={isLoading}
>
{showPassword ? (
<EyeOff className="w-4 h-4" />
) : (
<Eye className="w-4 h-4" />
)}
</button>
</div>
{errors.password && (
<p className="text-sm text-red-600">{errors.password.message}</p>
)}
</div>
{/* 确认密码输入 */}
<div className="space-y-2">
<Label htmlFor="confirmPassword"> *</Label>
<div className="relative">
<Input
id="confirmPassword"
type={showConfirmPassword ? "text" : "password"}
placeholder="请再次输入密码"
{...register("confirmPassword")}
disabled={isLoading}
/>
<button
type="button"
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500 hover:text-gray-700"
onClick={() => setShowConfirmPassword(!showConfirmPassword)}
disabled={isLoading}
>
{showConfirmPassword ? (
<EyeOff className="w-4 h-4" />
) : (
<Eye className="w-4 h-4" />
)}
</button>
</div>
{errors.confirmPassword && (
<p className="text-sm text-red-600">{errors.confirmPassword.message}</p>
)}
</div>
{/* 注册按钮 */}
<Button
type="submit"
className="w-full"
disabled={isLoading}
>
{isLoading ? (
<>
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
...
</>
) : (
<>
<CheckCircle className="w-4 h-4 mr-2" />
</>
)}
</Button>
</form>
{/* 登录链接 */}
<div className="mt-6 text-center">
<p className="text-sm text-gray-600">
{" "}
<Link
href="/login"
className="text-blue-600 hover:text-blue-800 font-medium"
>
</Link>
</p>
</div>
</CardContent>
</Card>
{/* 注册提示 */}
<div className="mt-6 text-center">
<p className="text-xs text-muted-foreground">
</p>
</div>
</div>
</div>
</div>
);
}