'use client'; import { useState, useCallback } from 'react'; import Link from 'next/link'; import { useRouter, useSearchParams } from 'next/navigation'; import { useI18n } from '@/lib/i18n'; import { Eye, EyeOff, Mail, Lock, ArrowRight, Loader2, CheckCircle, AlertTriangle, UserPlus, Languages, User, } from 'lucide-react'; import { GoogleLogin } from '@react-oauth/google'; 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 { useNotification } from '@/components/ui/notification'; import { apiClient } from '@/lib/apiClient'; import { useRegister } from './useRegister'; import { cn } from '@/lib/utils'; import type { GoogleAuthResponse } from '../login/types'; function validateEmail(email: string) { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); } function validatePassword(password: string) { return password.length >= 8 && /[A-Z]/.test(password) && /[a-z]/.test(password) && /[0-9]/.test(password); } function getPasswordStrength(password: string) { if (password.length === 0) return { score: 0, labelKey: '', color: '' }; let score = 0; if (password.length >= 8) score++; if (password.length >= 12) score++; if (/[A-Z]/.test(password)) score++; if (/[a-z]/.test(password)) score++; if (/[0-9]/.test(password)) score++; if (/[^A-Za-z0-9]/.test(password)) score++; if (score <= 2) return { score, labelKey: 'register.password.strength.weak', color: 'bg-destructive' }; if (score <= 4) return { score, labelKey: 'register.password.strength.medium', color: 'bg-yellow-500' }; return { score, labelKey: 'register.password.strength.strong', color: 'bg-green-500' }; } function PasswordToggleIcon({ visible, onToggle, label }: { visible: boolean; onToggle: () => void; label: string }) { return ( ); } export function RegisterForm() { const [name, setName] = useState(''); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [confirmPassword, setConfirmPassword] = useState(''); const [showPassword, setShowPassword] = useState(false); const [showConfirm, setShowConfirm] = useState(false); const [googleLoading, setGoogleLoading] = useState(false); const [touched, setTouched] = useState({ name: false, email: false, password: false, confirmPassword: false }); const registerMutation = useRegister(); const { notify } = useNotification(); const { t } = useI18n(); const router = useRouter(); const searchParams = useSearchParams(); const redirect = searchParams.get('redirect') || '/dashboard'; const googleClientId = process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID || ''; const nameError = touched.name && name.length > 0 && name.length < 2 ? t('register.name.error') : undefined; const emailError = touched.email && email.length > 0 && !validateEmail(email) ? t('register.email.error') : undefined; const passwordError = touched.password && password.length > 0 && !validatePassword(password) ? t('register.password.error') : undefined; const confirmError = touched.confirmPassword && confirmPassword.length > 0 && password !== confirmPassword ? t('register.confirmPassword.error') : undefined; const passwordStrength = getPasswordStrength(password); const isFormValid = name.length >= 2 && validateEmail(email) && validatePassword(password) && password === confirmPassword; const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); setTouched({ name: true, email: true, password: true, confirmPassword: true }); if (!isFormValid) return; registerMutation.mutate({ name, email, password }); }; const getConfirmRightIcon = () => { if (touched.confirmPassword && confirmPassword.length > 0 && password === confirmPassword) { return ; } return ( setShowConfirm(!showConfirm)} label={showConfirm ? t('register.confirmPassword.hide') : t('register.confirmPassword.show')} /> ); }; const handleGoogleSuccess = useCallback(async (credentialResponse: { credential?: string }) => { if (!credentialResponse.credential) return; setGoogleLoading(true); try { const response = await apiClient.post<{ data: GoogleAuthResponse }>( '/api/v1/auth/google', { credential: credentialResponse.credential }, ); const { access_token, refresh_token } = response.data; localStorage.setItem('token', access_token); localStorage.setItem('refresh_token', refresh_token); router.push(redirect); } catch { notify({ title: t('login.google.errorGeneric'), description: t('login.google.errorFailed'), variant: 'destructive', }); } finally { setGoogleLoading(false); } }, [redirect, router, notify, t]); const handleGoogleError = useCallback(() => { notify({ title: t('login.google.errorGeneric'), description: t('login.google.errorFailed'), variant: 'destructive', }); }, [notify, t]); return (
Wordly {t('register.title')} {t('register.subtitle')}
{googleClientId && ( <>
{googleLoading ? ( ) : ( )}
{t('login.orContinueWith')}
)} {registerMutation.isError && (

{registerMutation.error?.message || t('register.error.failed')}

)}
setName(e.target.value)} onBlur={() => setTouched((t) => ({ ...t, name: true }))} leftIcon={} rightIcon={ touched.name && name.length > 0 ? name.length >= 2 ? : : undefined } error={nameError} required autoComplete="name" />
setEmail(e.target.value)} onBlur={() => setTouched((t) => ({ ...t, email: true }))} leftIcon={} rightIcon={ touched.email && email.length > 0 ? validateEmail(email) ? : : undefined } error={emailError} required autoComplete="email" />
setPassword(e.target.value)} onBlur={() => setTouched((t) => ({ ...t, password: true }))} leftIcon={} rightIcon={ setShowPassword(!showPassword)} label={showPassword ? t('register.password.hide') : t('register.password.show')} /> } error={passwordError} required minLength={8} autoComplete="new-password" /> {password.length > 0 && (
{[1, 2, 3, 4].map((level) => (
))}

{t('register.password.strengthLabel')} {passwordStrength.labelKey ? t(passwordStrength.labelKey) : ''}

)}
setConfirmPassword(e.target.value)} onBlur={() => setTouched((t) => ({ ...t, confirmPassword: true }))} leftIcon={} rightIcon={getConfirmRightIcon()} error={confirmError} required autoComplete="new-password" />

{t('register.hasAccount')}{' '} {t('register.login')}

{t('register.terms.prefix')}{' '} {t('register.terms.link')} .

); }