refactor: di chuyển các trường nhập liệu sang sử dụng Controller của React Hook Form.

This commit is contained in:
Ho Ngoc Hai
2026-01-05 10:03:42 +07:00
parent 29de865509
commit b0df44f3ea

View File

@@ -1,6 +1,6 @@
'use client';
import { useForm } from 'react-hook-form';
import { useForm, Controller } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { useRouter } from 'next/navigation';
@@ -12,6 +12,7 @@ import { Input } from '@/features/shared/components/ui/input';
import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from '@/features/shared/components/ui/card';
import { useTranslations } from 'next-intl';
import { AuthControls } from '@/features/shared/components/layout/auth-controls';
import { cn } from '@/shared/utils';
/**
* EN: Create register schema with translated messages
@@ -151,10 +152,9 @@ export default function RegisterPage() {
// EN: React Hook Form setup with Zod resolver
// VI: Setup React Hook Form với Zod resolver
const {
register,
control,
handleSubmit,
watch,
setValue,
formState: { errors, isSubmitting },
} = useForm<RegisterFormData>({
resolver: zodResolver(registerSchema),
@@ -291,52 +291,64 @@ export default function RegisterPage() {
<div className="space-y-4">
{/* EN: Full name input field / VI: Trường nhập họ tên */}
<Input
type="text"
label={t('auth.register.fullName')}
placeholder="John Doe"
isInvalid={!!errors.fullName}
errorMessage={errors.fullName?.message}
{...register('fullName')}
onChange={(e) => {
register('fullName').onChange(e);
setValue('fullName', e.target.value);
}}
autoComplete="name"
aria-required="true"
<Controller
name="fullName"
control={control}
render={({ field }) => (
<Input
type="text"
label={t('auth.register.fullName')}
placeholder="John Doe"
isInvalid={!!errors.fullName}
errorMessage={errors.fullName?.message}
value={field.value}
onChange={field.onChange}
onBlur={field.onBlur}
autoComplete="name"
aria-required="true"
/>
)}
/>
{/* EN: Email input field / VI: Trường nhập email */}
<Input
type="email"
label={t('auth.register.email')}
placeholder="you@example.com"
isInvalid={!!errors.email}
errorMessage={errors.email?.message}
{...register('email')}
onChange={(e) => {
register('email').onChange(e);
setValue('email', e.target.value);
}}
autoComplete="email"
aria-required="true"
<Controller
name="email"
control={control}
render={({ field }) => (
<Input
type="email"
label={t('auth.register.email')}
placeholder="you@example.com"
isInvalid={!!errors.email}
errorMessage={errors.email?.message}
value={field.value}
onChange={field.onChange}
onBlur={field.onBlur}
autoComplete="email"
aria-required="true"
/>
)}
/>
{/* EN: Password input field / VI: Trường nhập mật khẩu */}
<div className="space-y-2">
<Input
type="password"
label={t('auth.register.password')}
placeholder={t('auth.register.createStrongPassword')}
isInvalid={!!errors.password}
errorMessage={errors.password?.message}
{...register('password')}
onChange={(e) => {
register('password').onChange(e);
setValue('password', e.target.value);
}}
autoComplete="new-password"
aria-required="true"
<Controller
name="password"
control={control}
render={({ field }) => (
<Input
type="password"
label={t('auth.register.password')}
placeholder={t('auth.register.createStrongPassword')}
isInvalid={!!errors.password}
errorMessage={errors.password?.message}
value={field.value}
onChange={field.onChange}
onBlur={field.onBlur}
autoComplete="new-password"
aria-required="true"
/>
)}
/>
{/* EN: Password strength indicator / VI: Chỉ báo độ mạnh mật khẩu */}
@@ -372,52 +384,64 @@ export default function RegisterPage() {
</div>
{/* EN: Confirm password input field / VI: Trường xác nhận mật khẩu */}
<Input
type="password"
label={t('auth.register.confirmPassword')}
placeholder={t('auth.register.reEnterPassword')}
isInvalid={!!errors.confirmPassword}
errorMessage={errors.confirmPassword?.message}
{...register('confirmPassword')}
onChange={(e) => {
register('confirmPassword').onChange(e);
setValue('confirmPassword', e.target.value);
}}
autoComplete="new-password"
aria-required="true"
<Controller
name="confirmPassword"
control={control}
render={({ field }) => (
<Input
type="password"
label={t('auth.register.confirmPassword')}
placeholder={t('auth.register.reEnterPassword')}
isInvalid={!!errors.confirmPassword}
errorMessage={errors.confirmPassword?.message}
value={field.value}
onChange={field.onChange}
onBlur={field.onBlur}
autoComplete="new-password"
aria-required="true"
/>
)}
/>
{/* EN: Terms and conditions checkbox / VI: Checkbox điều khoản và điều kiện */}
<div className="space-y-2 pt-2">
<label className="flex items-start gap-2 cursor-pointer group">
<input
type="checkbox"
{...register('terms')}
className="mt-1 w-4 h-4 rounded border-glass bg-glass-subtle text-accent-primary focus:ring-2 focus:ring-accent-primary focus:ring-offset-2 focus:ring-offset-bg-primary cursor-pointer flex-shrink-0 transition-all"
aria-required="true"
aria-invalid={errors.terms ? 'true' : 'false'}
/>
<span className="text-sm text-text-secondary group-hover:text-text-primary transition-colors">
{t('auth.register.agreeToTerms')}{' '}
<Link
href="/terms"
className="text-accent-primary hover:text-accent-primary-hover font-medium transition-colors"
target="_blank"
rel="noopener noreferrer"
>
{t('auth.register.termsAndConditions')}
</Link>
</span>
</label>
{errors.terms && (
<p
className="text-sm text-accent-error flex items-center gap-1 ml-6 animate-in fade-in duration-quick"
role="alert"
>
<span>{errors.terms.message}</span>
</p>
<Controller
name="terms"
control={control}
render={({ field }) => (
<div className="space-y-2 pt-2">
<label className="flex items-start gap-2 cursor-pointer group">
<input
type="checkbox"
checked={field.value}
onChange={field.onChange}
onBlur={field.onBlur}
className="mt-1 w-4 h-4 rounded border-glass bg-glass-subtle text-accent-primary focus:ring-2 focus:ring-accent-primary focus:ring-offset-2 focus:ring-offset-bg-primary cursor-pointer flex-shrink-0 transition-all"
aria-required="true"
aria-invalid={errors.terms ? 'true' : 'false'}
/>
<span className="text-sm text-text-secondary group-hover:text-text-primary transition-colors">
{t('auth.register.agreeToTerms')}{' '}
<Link
href="/terms"
className="text-accent-primary hover:text-accent-primary-hover font-medium transition-colors"
target="_blank"
rel="noopener noreferrer"
>
{t('auth.register.termsAndConditions')}
</Link>
</span>
</label>
{errors.terms && (
<p
className="text-sm text-accent-error flex items-center gap-1 ml-6 animate-in fade-in duration-quick"
role="alert"
>
<span>{errors.terms.message}</span>
</p>
)}
</div>
)}
</div>
/>
</div>
{/* EN: Submit button with loading state / VI: Nút submit với trạng thái loading */}