/** * PasswordResetConfirmForm Component * Handles password reset with token from email * Integrates with backend API password reset confirm flow */ 'use client'; import { useState } from 'react'; import Link from 'next/link'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Alert } from '@/components/ui/alert'; import { usePasswordResetConfirm } from '@/lib/api/hooks/useAuth'; import { getGeneralError, getFieldErrors } from '@/lib/api/errors'; import type { APIError } from '@/lib/api/errors'; // ============================================================================ // Validation Schema // ============================================================================ const resetConfirmSchema = z .object({ token: z.string().min(1, 'Reset token is required'), new_password: z .string() .min(1, 'New password is required') .min(8, 'Password must be at least 8 characters') .regex(/[0-9]/, 'Password must contain at least one number') .regex(/[A-Z]/, 'Password must contain at least one uppercase letter'), confirm_password: z.string().min(1, 'Please confirm your password'), }) .refine((data) => data.new_password === data.confirm_password, { message: 'Passwords do not match', path: ['confirm_password'], }); type ResetConfirmFormData = z.infer; // ============================================================================ // Helper Functions // ============================================================================ /** * Calculate password strength based on requirements */ function calculatePasswordStrength(password: string): { hasMinLength: boolean; hasNumber: boolean; hasUppercase: boolean; strength: number; } { const hasMinLength = password.length >= 8; const hasNumber = /[0-9]/.test(password); const hasUppercase = /[A-Z]/.test(password); const strength = (hasMinLength ? 33 : 0) + (hasNumber ? 33 : 0) + (hasUppercase ? 34 : 0); return { hasMinLength, hasNumber, hasUppercase, strength }; } // ============================================================================ // Component // ============================================================================ interface PasswordResetConfirmFormProps { /** Reset token from URL query parameter */ token: string; /** Optional callback after successful reset */ onSuccess?: () => void; /** Show login link */ showLoginLink?: boolean; /** Custom className for form container */ className?: string; } /** * PasswordResetConfirmForm - Reset password with token * * Features: * - Token validation * - New password validation with strength indicator * - Password confirmation matching * - Loading states * - Server error display * - Success message * - Link back to login * * @example * ```tsx * router.push('/login')} * /> * ``` */ export function PasswordResetConfirmForm({ token, onSuccess, showLoginLink = true, className, }: PasswordResetConfirmFormProps) { const [serverError, setServerError] = useState(null); const [successMessage, setSuccessMessage] = useState(null); const resetMutation = usePasswordResetConfirm(); const form = useForm({ resolver: zodResolver(resetConfirmSchema), defaultValues: { token, new_password: '', confirm_password: '', }, }); const watchPassword = form.watch('new_password'); const passwordStrength = calculatePasswordStrength(watchPassword); const onSubmit = async (data: ResetConfirmFormData) => { try { // Clear previous errors and success message setServerError(null); setSuccessMessage(null); form.clearErrors(); // Confirm password reset await resetMutation.mutateAsync({ token: data.token, new_password: data.new_password, }); // Show success message setSuccessMessage( 'Your password has been successfully reset. You can now log in with your new password.' ); // Reset form form.reset({ token, new_password: '', confirm_password: '' }); // Success callback onSuccess?.(); } catch (error) { // Handle API errors const errors = error as APIError[]; // Set general error message const generalError = getGeneralError(errors); if (generalError) { setServerError(generalError); } // Set field-specific errors const fieldErrors = getFieldErrors(errors); Object.entries(fieldErrors).forEach(([field, message]) => { if (field === 'token' || field === 'new_password') { form.setError(field, { message }); } }); } }; const isSubmitting = form.formState.isSubmitting || resetMutation.isPending; return (
{/* Success Alert */} {successMessage && (

{successMessage}

)} {/* Server Error Alert */} {serverError && (

{serverError}

)} {/* Instructions */}

Enter your new password below. Make sure it meets all security requirements.

{/* Hidden Token Field (for form submission) */} {/* New Password Field */}
{form.formState.errors.new_password && (

{form.formState.errors.new_password.message}

)} {/* Password Strength Indicator */} {watchPassword && (
= 66 ? 'bg-yellow-500' : 'bg-red-500' }`} style={{ width: `${passwordStrength.strength}%` }} />
  • {passwordStrength.hasMinLength ? '✓' : '○'} At least 8 characters
  • {passwordStrength.hasNumber ? '✓' : '○'} Contains a number
  • {passwordStrength.hasUppercase ? '✓' : '○'} Contains an uppercase letter
)}
{/* Confirm Password Field */}
{form.formState.errors.confirm_password && (

{form.formState.errors.confirm_password.message}

)}
{/* Submit Button */} {/* Login Link */} {showLoginLink && (

Remember your password?{' '} Back to login

)}
); }