Files
ai-showcase-platform/app/reset-password/page.tsx

240 lines
7.9 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 } from "react"
import { useRouter, useSearchParams } from "next/navigation"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Alert, AlertDescription } from "@/components/ui/alert"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { CheckCircle, AlertTriangle, Lock, Eye, EyeOff } from "lucide-react"
export default function ResetPasswordPage() {
const router = useRouter()
const searchParams = useSearchParams()
const token = searchParams.get('token')
const [password, setPassword] = useState("")
const [confirmPassword, setConfirmPassword] = useState("")
const [showPassword, setShowPassword] = useState(false)
const [showConfirmPassword, setShowConfirmPassword] = useState(false)
const [isLoading, setIsLoading] = useState(false)
const [isValidating, setIsValidating] = useState(true)
const [isValidToken, setIsValidToken] = useState(false)
const [isSuccess, setIsSuccess] = useState(false)
const [error, setError] = useState("")
// 驗證 token 是否有效
useEffect(() => {
if (!token) {
setError("缺少重設 token")
setIsValidating(false)
return
}
const validateToken = async () => {
try {
const response = await fetch(`/api/auth/reset-password?token=${token}`)
const data = await response.json()
if (data.success && data.valid) {
setIsValidToken(true)
} else {
setError(data.error || "無效或已過期的重設 token")
}
} catch (err) {
setError("驗證 token 時發生錯誤")
} finally {
setIsValidating(false)
}
}
validateToken()
}, [token])
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setError("")
if (!password || !confirmPassword) {
setError("請填寫所有欄位")
return
}
if (password.length < 6) {
setError("密碼長度至少需要 6 個字符")
return
}
if (password !== confirmPassword) {
setError("密碼確認不一致")
return
}
setIsLoading(true)
try {
const response = await fetch('/api/auth/reset-password', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
token,
password
}),
})
const data = await response.json()
if (data.success) {
setIsSuccess(true)
setTimeout(() => {
router.push('/')
}, 3000)
} else {
setError(data.error || "重設密碼失敗")
}
} catch (err) {
setError("重設密碼時發生錯誤")
} finally {
setIsLoading(false)
}
}
if (isValidating) {
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50">
<Card className="w-full max-w-md">
<CardContent className="pt-6">
<div className="text-center">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600 mx-auto mb-4"></div>
<p className="text-gray-600">...</p>
</div>
</CardContent>
</Card>
</div>
)
}
if (!isValidToken) {
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50">
<Card className="w-full max-w-md">
<CardHeader>
<CardTitle className="text-center text-red-600"></CardTitle>
<CardDescription className="text-center">
</CardDescription>
</CardHeader>
<CardContent>
<Alert variant="destructive">
<AlertTriangle className="h-4 w-4" />
<AlertDescription>{error}</AlertDescription>
</Alert>
<div className="mt-4 text-center">
<Button onClick={() => router.push('/')} variant="outline">
</Button>
</div>
</CardContent>
</Card>
</div>
)
}
if (isSuccess) {
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50">
<Card className="w-full max-w-md">
<CardContent className="pt-6">
<div className="text-center">
<CheckCircle className="h-16 w-16 text-green-600 mx-auto mb-4" />
<h2 className="text-2xl font-bold text-green-600 mb-2"></h2>
<p className="text-gray-600 mb-4">
3
</p>
<Button onClick={() => router.push('/')} className="w-full">
</Button>
</div>
</CardContent>
</Card>
</div>
)
}
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50">
<Card className="w-full max-w-md">
<CardHeader>
<CardTitle className="text-center"></CardTitle>
<CardDescription className="text-center">
</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={handleSubmit} className="space-y-4">
<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"}
placeholder="請輸入新密碼"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="pl-10 pr-10"
required
/>
<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"
>
{showPassword ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
</button>
</div>
</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"}
placeholder="請再次輸入新密碼"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
className="pl-10 pr-10"
required
/>
<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"
>
{showConfirmPassword ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
</button>
</div>
</div>
{error && (
<Alert variant="destructive">
<AlertTriangle className="h-4 w-4" />
<AlertDescription>{error}</AlertDescription>
</Alert>
)}
<Button type="submit" className="w-full" disabled={isLoading}>
{isLoading ? "重設中..." : "重設密碼"}
</Button>
</form>
</CardContent>
</Card>
</div>
)
}