Files
ai-showcase-platform/app/register/page.tsx

452 lines
18 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 type React from "react"
import { useState, useEffect } from "react"
import { useRouter, useSearchParams } from "next/navigation"
import { useAuth } from "@/contexts/auth-context"
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 { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Badge } from "@/components/ui/badge"
import { Alert, AlertDescription } from "@/components/ui/alert"
import { Separator } from "@/components/ui/separator"
import { Brain, User, Mail, Building, Lock, Loader2, CheckCircle, AlertTriangle, Shield, Code, Eye, EyeOff } from "lucide-react"
export default function RegisterPage() {
const router = useRouter()
const searchParams = useSearchParams()
const { register, isLoading } = useAuth()
const [formData, setFormData] = useState({
name: "",
email: "",
password: "",
confirmPassword: "",
department: "",
})
const [error, setError] = useState("")
const [success, setSuccess] = useState("")
const [isSubmitting, setIsSubmitting] = useState(false)
const [showPassword, setShowPassword] = useState(false)
const [showConfirmPassword, setShowConfirmPassword] = useState(false)
// 從 URL 參數獲取邀請資訊
const invitationToken = searchParams.get("token")
const invitedEmail = searchParams.get("email")
const invitedRole = searchParams.get("role") || "user"
const mode = searchParams.get("mode") // "reset" 表示密碼重設模式
const invitedName = searchParams.get("name")
const invitedDepartment = searchParams.get("department")
const isInvitedUser = !!(invitationToken && invitedEmail)
const isResetMode = mode === "reset"
// 在重設模式下,使用從資料庫獲取的正確角色
const displayRole = isResetMode ? invitedRole : invitedRole
useEffect(() => {
if (isInvitedUser) {
setFormData((prev) => ({
...prev,
email: decodeURIComponent(invitedEmail),
name: isResetMode && invitedName ? decodeURIComponent(invitedName) : prev.name,
department: isResetMode && invitedDepartment ? decodeURIComponent(invitedDepartment) : prev.department,
}))
}
}, [isInvitedUser, invitedEmail, isResetMode, invitedName, invitedDepartment])
const handleInputChange = (field: string, value: string) => {
setFormData((prev) => ({ ...prev, [field]: value }))
setError("")
}
const getRoleDescription = (role: string) => {
switch (role) {
case "admin":
return "可以訪問管理後台,管理用戶和審核應用"
case "developer":
return "可以提交 AI 應用申請,參與平台建設"
case "user":
return "可以瀏覽和收藏應用,參與評價互動"
default:
return ""
}
}
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setError("")
setIsSubmitting(true)
// 表單驗證
if (!formData.name || !formData.email || !formData.password || !formData.department) {
setError("請填寫所有必填欄位")
setIsSubmitting(false)
return
}
if (formData.password !== formData.confirmPassword) {
setError("密碼確認不一致")
setIsSubmitting(false)
return
}
if (formData.password.length < 6) {
setError("密碼長度至少需要 6 個字符")
setIsSubmitting(false)
return
}
try {
if (isResetMode) {
// 密碼重設模式
const response = await fetch('/api/auth/reset-password', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
token: invitationToken,
password: formData.password
}),
})
const data = await response.json()
if (data.success) {
setSuccess("密碼重設成功!正在跳轉...")
setTimeout(() => {
router.push("/")
}, 2000)
} else {
setError(data.error || "密碼重設失敗")
}
} else {
// 正常註冊模式
const success = await register({
name: formData.name,
email: formData.email,
password: formData.password,
department: formData.department,
role: isInvitedUser ? invitedRole : 'user', // 如果是邀請用戶,使用邀請的角色
})
if (success) {
setSuccess("註冊成功!正在跳轉...")
setTimeout(() => {
router.push("/")
}, 2000)
} else {
setError("註冊失敗,請檢查資料或聯繫管理員")
}
}
} catch (err) {
setError(isResetMode ? "密碼重設過程中發生錯誤,請稍後再試" : "註冊過程中發生錯誤,請稍後再試")
}
setIsSubmitting(false)
}
if (success) {
return (
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-white to-blue-50 flex items-center justify-center p-4">
<Card className="w-full max-w-md">
<CardContent className="text-center py-8">
<div className="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-4">
<CheckCircle className="w-8 h-8 text-green-600" />
</div>
<h3 className="text-xl font-semibold text-gray-900 mb-2">
{isResetMode ? "密碼重設成功!" : "註冊成功!"}
</h3>
<p className="text-gray-600 mb-4">
{isResetMode ? "您的密碼已成功重設" : "歡迎加入強茂集團 AI 展示平台"}
</p>
<p className="text-sm text-gray-500">...</p>
</CardContent>
</Card>
</div>
)
}
return (
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-white to-blue-50 flex items-center justify-center p-4">
<div className="w-full max-w-md">
{/* Header */}
<div className="text-center mb-8">
<div className="flex items-center justify-center space-x-2 mb-4">
<div className="w-10 h-10 bg-gradient-to-r from-blue-600 to-purple-600 rounded-lg flex items-center justify-center">
<Brain className="w-6 h-6 text-white" />
</div>
<div>
<h1 className="text-2xl font-bold text-gray-900"> AI </h1>
</div>
</div>
{isResetMode ? (
<div>
<h2 className="text-xl font-semibold text-gray-900 mb-2"></h2>
<p className="text-gray-600"></p>
</div>
) : isInvitedUser ? (
<div>
<h2 className="text-xl font-semibold text-gray-900 mb-2"></h2>
<p className="text-gray-600"></p>
</div>
) : (
<div>
<h2 className="text-xl font-semibold text-gray-900 mb-2"></h2>
<p className="text-gray-600"> AI </p>
</div>
)}
</div>
<Card>
<CardHeader>
<CardTitle>{isResetMode ? "密碼重設" : "註冊資訊"}</CardTitle>
<CardDescription>
{isResetMode
? "請設定您的新密碼"
: isInvitedUser
? "請填寫您的個人資訊完成註冊"
: "請填寫以下資訊建立您的帳戶"
}
</CardDescription>
</CardHeader>
<CardContent>
{/* Invitation Info */}
{isInvitedUser && (
<div className="mb-6">
<div className="bg-blue-50 rounded-lg p-4">
<div className="flex items-start space-x-3">
<CheckCircle className="w-5 h-5 text-blue-600 mt-0.5" />
<div className="flex-1">
<h4 className="font-medium text-blue-900 mb-2"></h4>
<div className="space-y-2">
<div className="flex items-center justify-between">
<span className="text-sm text-blue-700"></span>
<span className="text-sm font-medium text-blue-900">{invitedEmail}</span>
</div>
<div className="flex items-center justify-between">
<span className="text-sm text-blue-700"></span>
<Badge variant="secondary" className="text-xs">
{displayRole === "admin" && (
<><Shield className="w-3 h-3 mr-1" /></>
)}
{displayRole === "developer" && (
<><Code className="w-3 h-3 mr-1" /></>
)}
{displayRole === "user" && (
<><User className="w-3 h-3 mr-1" /></>
)}
</Badge>
</div>
<div className="text-xs text-blue-600 mt-2">
{getRoleDescription(displayRole)}
</div>
</div>
</div>
</div>
</div>
</div>
)}
{error && (
<Alert variant="destructive" className="mb-4">
<AlertTriangle className="h-4 w-4" />
<AlertDescription>{error}</AlertDescription>
</Alert>
)}
<form onSubmit={handleSubmit} className="space-y-4">
{!isResetMode && (
<div className="space-y-2">
<Label htmlFor="name"> *</Label>
<div className="relative">
<User className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
<Input
id="name"
type="text"
value={formData.name}
onChange={(e) => handleInputChange("name", e.target.value)}
placeholder="請輸入您的姓名"
className="pl-10"
disabled={isSubmitting}
/>
</div>
</div>
)}
<div className="space-y-2">
<Label htmlFor="email"> *</Label>
<div className="relative">
<Mail className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
<Input
id="email"
type="email"
value={formData.email}
onChange={(e) => handleInputChange("email", e.target.value)}
placeholder="請輸入電子郵件"
className="pl-10"
disabled={isSubmitting || isInvitedUser || isResetMode}
readOnly={isInvitedUser || isResetMode}
/>
</div>
{(isInvitedUser || isResetMode) && <p className="text-xs text-gray-500"></p>}
</div>
{!isResetMode && (
<div className="space-y-2">
<Label htmlFor="department"> *</Label>
<div className="relative">
<Building className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4 z-10" />
<Select
value={formData.department}
onValueChange={(value) => handleInputChange("department", value)}
disabled={isSubmitting}
>
<SelectTrigger className="pl-10">
<SelectValue placeholder="請選擇您的部門" />
</SelectTrigger>
<SelectContent>
<SelectItem value="HQBU">HQBU</SelectItem>
<SelectItem value="ITBU">ITBU</SelectItem>
<SelectItem value="RDBU">RDBU</SelectItem>
<SelectItem value="MDBU">MDBU</SelectItem>
<SelectItem value="PGBU">PGBU</SelectItem>
<SelectItem value="SGBU">SGBU</SelectItem>
<SelectItem value="TGBU">TGBU</SelectItem>
<SelectItem value="WGBU">WGBU</SelectItem>
<SelectItem value="Other"></SelectItem>
</SelectContent>
</Select>
</div>
</div>
)}
<div className="space-y-2">
<Label htmlFor="password"> *</Label>
<div className="relative">
<Lock className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
<Input
id="password"
type={showPassword ? "text" : "password"}
value={formData.password}
onChange={(e) => handleInputChange("password", e.target.value)}
placeholder={isResetMode ? "請輸入新密碼" : "請輸入密碼"}
className="pl-10 pr-10"
disabled={isSubmitting}
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600"
disabled={isSubmitting}
>
{showPassword ? (
<EyeOff className="w-4 h-4" />
) : (
<Eye className="w-4 h-4" />
)}
</button>
</div>
<p className="text-xs text-gray-500"> 6 </p>
</div>
<div className="space-y-2">
<Label htmlFor="confirmPassword"> *</Label>
<div className="relative">
<Lock className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
<Input
id="confirmPassword"
type={showConfirmPassword ? "text" : "password"}
value={formData.confirmPassword}
onChange={(e) => handleInputChange("confirmPassword", e.target.value)}
placeholder="請再次輸入密碼"
className="pl-10 pr-10"
disabled={isSubmitting}
/>
<button
type="button"
onClick={() => setShowConfirmPassword(!showConfirmPassword)}
className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600"
disabled={isSubmitting}
>
{showConfirmPassword ? (
<EyeOff className="w-4 h-4" />
) : (
<Eye className="w-4 h-4" />
)}
</button>
</div>
</div>
<Button
type="submit"
className="w-full bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700"
disabled={isSubmitting || isLoading}
>
{isSubmitting || isLoading ? (
<>
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
{isResetMode ? "重設中..." : "註冊中..."}
</>
) : (
isResetMode ? "重設密碼" : "完成註冊"
)}
</Button>
</form>
<div className="mt-6 text-center">
<p className="text-sm text-gray-600">
{" "}
<Button
variant="link"
className="p-0 h-auto font-normal text-blue-600 hover:text-blue-700"
onClick={() => router.push("/")}
>
</Button>
</p>
</div>
</CardContent>
</Card>
{/* Role Information */}
{!isInvitedUser && !isResetMode && (
<Card className="mt-6">
<CardHeader>
<CardTitle className="text-lg"></CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid gap-3">
<div className="flex items-start space-x-3 p-3 bg-blue-50 rounded-lg">
<User className="w-5 h-5 text-blue-600 mt-0.5" />
<div>
<h4 className="font-medium text-blue-900"></h4>
<p className="text-sm text-blue-700"></p>
</div>
</div>
<div className="flex items-start space-x-3 p-3 bg-green-50 rounded-lg">
<Code className="w-5 h-5 text-green-600 mt-0.5" />
<div>
<h4 className="font-medium text-green-900"></h4>
<p className="text-sm text-green-700"> AI </p>
</div>
</div>
<div className="flex items-start space-x-3 p-3 bg-purple-50 rounded-lg">
<Shield className="w-5 h-5 text-purple-600 mt-0.5" />
<div>
<h4 className="font-medium text-purple-900"></h4>
<p className="text-sm text-purple-700"></p>
</div>
</div>
</div>
</CardContent>
</Card>
)}
</div>
</div>
)
}