dramaling-vocab-learning/frontend/app/register/page.tsx

243 lines
8.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 } from 'react'
import Link from 'next/link'
import { useRouter } from 'next/navigation'
import { useAuth } from '@/contexts/AuthContext'
export default function RegisterPage() {
const router = useRouter()
const { register } = useAuth()
const [formData, setFormData] = useState({
username: '',
email: '',
password: '',
confirmPassword: ''
})
const [loading, setLoading] = useState(false)
const [errors, setErrors] = useState<Record<string, string>>({})
const [apiError, setApiError] = useState('')
const validatePassword = (password: string) => {
if (password.length < 8) return '密碼至少需要 8 個字元'
if (!/[A-Z]/.test(password)) return '密碼需包含大寫字母'
if (!/[a-z]/.test(password)) return '密碼需包含小寫字母'
if (!/[0-9]/.test(password)) return '密碼需包含數字'
return ''
}
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target
setFormData(prev => ({ ...prev, [name]: value }))
// Clear error when user starts typing
if (errors[name]) {
setErrors(prev => ({ ...prev, [name]: '' }))
}
}
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setApiError('')
const newErrors: Record<string, string> = {}
// Validate form
if (formData.username.length < 3) {
newErrors.username = '用戶名至少需要 3 個字元'
}
const passwordError = validatePassword(formData.password)
if (passwordError) {
newErrors.password = passwordError
}
if (formData.password !== formData.confirmPassword) {
newErrors.confirmPassword = '密碼不一致'
}
if (Object.keys(newErrors).length > 0) {
setErrors(newErrors)
return
}
setLoading(true)
// 調試:打印要發送的數據
console.log('Submitting registration with data:', {
username: formData.username,
email: formData.email,
password: formData.password ? '[hidden]' : 'empty'
})
const result = await register(formData.username, formData.email, formData.password)
if (result.success) {
router.push('/dashboard')
} else {
setApiError(result.error || '註冊失敗')
}
setLoading(false)
}
const handleGoogleSignup = () => {
setLoading(true)
setTimeout(() => {
router.push('/dashboard')
}, 1000)
}
return (
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 flex items-center justify-center px-4 py-8">
<div className="max-w-md w-full bg-white rounded-2xl shadow-xl p-8">
<div className="text-center mb-8">
<h1 className="text-3xl font-bold text-gray-900"></h1>
<p className="text-gray-600 mt-2"></p>
</div>
<form onSubmit={handleSubmit} className="space-y-5">
{apiError && (
<div className="bg-red-50 border border-red-200 rounded-lg p-4">
<p className="text-red-600 text-sm">{apiError}</p>
</div>
)}
<div>
<label htmlFor="username" className="block text-sm font-medium text-gray-700 mb-2">
</label>
<input
id="username"
name="username"
type="text"
required
value={formData.username}
onChange={handleChange}
className={`w-full px-4 py-3 border rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent outline-none transition ${
errors.username ? 'border-red-500' : 'border-gray-300'
}`}
placeholder="選擇一個獨特的用戶名"
/>
{errors.username && (
<p className="mt-1 text-sm text-red-600">{errors.username}</p>
)}
</div>
<div>
<label htmlFor="email" className="block text-sm font-medium text-gray-700 mb-2">
Email
</label>
<input
id="email"
name="email"
type="email"
required
value={formData.email}
onChange={handleChange}
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent outline-none transition"
placeholder="your@email.com"
/>
</div>
<div>
<label htmlFor="password" className="block text-sm font-medium text-gray-700 mb-2">
</label>
<input
id="password"
name="password"
type="password"
required
value={formData.password}
onChange={handleChange}
className={`w-full px-4 py-3 border rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent outline-none transition ${
errors.password ? 'border-red-500' : 'border-gray-300'
}`}
placeholder="至少8位包含大小寫及數字"
/>
{errors.password && (
<p className="mt-1 text-sm text-red-600">{errors.password}</p>
)}
</div>
<div>
<label htmlFor="confirmPassword" className="block text-sm font-medium text-gray-700 mb-2">
</label>
<input
id="confirmPassword"
name="confirmPassword"
type="password"
required
value={formData.confirmPassword}
onChange={handleChange}
className={`w-full px-4 py-3 border rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent outline-none transition ${
errors.confirmPassword ? 'border-red-500' : 'border-gray-300'
}`}
placeholder="再次輸入密碼"
/>
{errors.confirmPassword && (
<p className="mt-1 text-sm text-red-600">{errors.confirmPassword}</p>
)}
</div>
<div className="flex items-start">
<input
id="terms"
type="checkbox"
required
className="h-4 w-4 text-primary focus:ring-primary border-gray-300 rounded mt-0.5"
/>
<label htmlFor="terms" className="ml-2 text-sm text-gray-600">
DramaLing
<Link href="/terms" className="text-primary hover:text-primary-hover"></Link>
<Link href="/privacy" className="text-primary hover:text-primary-hover"></Link>
</label>
</div>
<button
type="submit"
disabled={loading}
className="w-full bg-primary text-white py-3 rounded-lg font-semibold hover:bg-primary-hover transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
{loading ? '創建中...' : '創建帳號'}
</button>
</form>
{/* <div className="mt-6">
<div className="relative">
<div className="absolute inset-0 flex items-center">
<div className="w-full border-t border-gray-300"></div>
</div>
<div className="relative flex justify-center text-sm">
<span className="px-2 bg-white text-gray-500">或</span>
</div>
</div>
<button
onClick={handleGoogleSignup}
disabled={loading}
className="mt-4 w-full flex items-center justify-center px-4 py-3 border border-gray-300 rounded-lg shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
<svg className="w-5 h-5 mr-2" viewBox="0 0 24 24">
<path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/>
<path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/>
<path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/>
<path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/>
</svg>
使用 Google 註冊
</button>
</div> */}
<p className="mt-8 text-center text-sm text-gray-600">
{' '}
<Link href="/login" className="font-medium text-primary hover:text-primary-hover">
</Link>
</p>
</div>
</div>
)
}