flnxi-Wu/app/register/page.tsx

227 lines
7.8 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 } from 'next/navigation'
import Image from 'next/image'
import Link from 'next/link'
import { PASSWORD_POLICY_DESCRIPTION, validatePasswordStrength } from '@/lib/password-policy'
const DEFAULT_LOGIN_LOGO = process.env.NEXT_PUBLIC_DEFAULT_LOGIN_LOGO_URL || ''
export default function RegisterPage() {
const router = useRouter()
const [email, setEmail] = useState('')
const [username, setUsername] = useState('')
const [department, setDepartment] = useState('')
const [departments, setDepartments] = useState<Array<{ id: string; name: string }>>([])
const [password, setPassword] = useState('')
const [confirmPassword, setConfirmPassword] = useState('')
const [loading, setLoading] = useState(false)
const [error, setError] = useState('')
const [logoError, setLogoError] = useState(false)
const pageClass = 'min-h-screen flex items-center justify-center bg-slate-50 dark:bg-slate-950 p-4'
const panelClass = 'w-full max-w-md rounded-xl border border-slate-200 dark:border-slate-800 bg-white dark:bg-slate-900 shadow-sm p-6'
const inputClass = 'w-full px-4 py-3 rounded-xl border border-slate-300 dark:border-slate-700 bg-white dark:bg-slate-800 text-slate-900 dark:text-white placeholder:text-slate-400 dark:placeholder:text-slate-500 focus:border-slate-900 dark:focus:border-slate-500 focus:ring-2 focus:ring-slate-200 dark:focus:ring-slate-700 outline-none transition-colors'
const buttonClass = 'w-full py-3 rounded-xl bg-slate-900 dark:bg-slate-100 text-white dark:text-slate-900 hover:bg-slate-800 dark:hover:bg-white font-medium transition-colors disabled:opacity-60'
useEffect(() => {
fetchDepartments()
}, [])
const fetchDepartments = async () => {
try {
const response = await fetch('/api/departments')
if (response.ok) {
const data = await response.json()
setDepartments(data)
}
} catch (error) {
console.error('Error fetching departments:', error)
}
}
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setError('')
if (password !== confirmPassword) {
setError('两次输入的密码不一致')
return
}
const passwordValidation = validatePasswordStrength(password)
if (!passwordValidation.valid) {
setError(passwordValidation.error || '密码不符合安全要求')
return
}
setLoading(true)
try {
const response = await fetch('/api/auth/register', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ email, username, department, password }),
})
const data = await response.json()
if (response.ok) {
router.push('/login?registered=true')
} else {
setError(data.error || '注册失败')
}
} catch (error) {
console.error('Registration error:', error)
setError('注册失败,请稍后再试')
} finally {
setLoading(false)
}
}
return (
<div className={pageClass}>
<div className="max-w-md w-full">
{/* Logo */}
<div className="text-center mb-8">
<div className="flex justify-center mb-4">
{DEFAULT_LOGIN_LOGO && !logoError ? (
<Image
src={DEFAULT_LOGIN_LOGO}
alt="FLNXI素材库"
width={80}
height={80}
unoptimized
className="rounded-xl border border-slate-200 dark:border-slate-700 shadow-sm"
onError={() => setLogoError(true)}
/>
) : (
<div className="w-20 h-20 rounded-xl border border-slate-200 dark:border-slate-700 shadow-sm bg-slate-900 dark:bg-slate-700 flex items-center justify-center">
<span className="text-3xl font-bold text-white">F</span>
</div>
)}
</div>
<h1 className="text-2xl font-semibold text-slate-900 dark:text-slate-100">FLNXI素材库</h1>
<p className="text-slate-600 dark:text-slate-400 mt-2">申请加入素材库</p>
</div>
{/* Register Form */}
<div className={panelClass}>
<form onSubmit={handleSubmit} className="space-y-6">
{error && (
<div className="bg-red-50 border border-red-200 text-red-600 px-4 py-3 rounded-xl text-sm">
{error}
</div>
)}
<div>
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
邮箱
</label>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className={inputClass}
placeholder="your@email.com"
required
/>
</div>
<div>
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
用户名
</label>
<input
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
className={inputClass}
placeholder="选择一个用户名"
required
/>
</div>
<div>
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
部门
</label>
<select
value={department}
onChange={(e) => setDepartment(e.target.value)}
className={inputClass}
>
<option value="">请选择部门</option>
{departments.map((dept) => (
<option key={dept.id} value={dept.name}>
{dept.name}
</option>
))}
</select>
{departments.length === 0 && (
<p className="text-sm text-slate-500 dark:text-slate-400 mt-1">
暂无可选部门,请联系管理员添加
</p>
)}
</div>
<div>
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
密码
</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className={inputClass}
placeholder={PASSWORD_POLICY_DESCRIPTION}
required
minLength={10}
/>
</div>
<div>
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
确认密码
</label>
<input
type="password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
className={inputClass}
placeholder="••••••••"
required
minLength={10}
/>
</div>
<button
type="submit"
className={buttonClass}
disabled={loading}
>
{loading ? '注册中...' : '注册'}
</button>
</form>
<div className="mt-6 text-center">
<p className="text-sm text-slate-600 dark:text-slate-400">
已有账号?{' '}
<Link
href="/login"
className="text-slate-900 dark:text-slate-100 hover:text-slate-700 dark:hover:text-slate-300 font-medium underline"
>
立即登录
</Link>
</p>
</div>
</div>
</div>
</div>
)
}