'use client'; import React, { useState, useEffect } from 'react'; import { Box, TextField, Button, Typography, Paper, InputAdornment, IconButton, Alert, CircularProgress, Link as MuiLink, Checkbox, FormControlLabel, } from '@mui/material'; import { Visibility, VisibilityOff } from '@mui/icons-material'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { motion } from 'framer-motion'; import * as z from 'zod'; import { useAuth } from '@/lib/auth/AuthContext'; import Link from 'next/link'; import { useTheme } from '@mui/material/styles'; import apiClient from '@/lib/api/client'; // Create a function to generate schema dynamically based on requireInviteCode const createRegisterSchema = (requireInviteCode: boolean) => z.object({ name: z.string().min(2, 'Name must be at least 2 characters'), email: z.string().email('Invalid email address'), password: z.string() .min(8, 'Password must be at least 8 characters') .regex(/[A-Z]/, 'Password must contain at least one uppercase letter') .regex(/[a-z]/, 'Password must contain at least one lowercase letter') .regex(/[0-9]/, 'Password must contain at least one number'), confirmPassword: z.string(), inviteCode: requireInviteCode ? z.string().min(1, 'Invite code is required') : z.string().optional(), dateOfBirth: z.string().min(1, 'Date of birth is required'), parentalEmail: z.string().email('Invalid email address').optional().or(z.literal('')), agreeToTerms: z.boolean().refine(val => val === true, { message: 'You must agree to the Terms of Service', }), agreeToPrivacy: z.boolean().refine(val => val === true, { message: 'You must agree to the Privacy Policy', }), coppaConsent: z.boolean().optional(), }).refine((data) => data.password === data.confirmPassword, { message: 'Passwords do not match', path: ['confirmPassword'], }).refine((data) => { // Check if user is under 18 and requires parental consent const birthDate = new Date(data.dateOfBirth); const today = new Date(); const age = today.getFullYear() - birthDate.getFullYear() - (today.getMonth() < birthDate.getMonth() || (today.getMonth() === birthDate.getMonth() && today.getDate() < birthDate.getDate()) ? 1 : 0); if (age < 13) { return false; // Users under 13 cannot register (COPPA compliance) } if (age >= 13 && age < 18) { // Users 13-17 need parental email and consent return !!data.parentalEmail && data.parentalEmail.length > 0 && data.coppaConsent === true; } return true; }, { message: 'Users under 13 cannot create an account. Users 13-17 require parental consent and email.', path: ['dateOfBirth'], }); type RegisterFormData = z.infer>; export default function RegisterPage() { const theme = useTheme(); const [showPassword, setShowPassword] = useState(false); const [showConfirmPassword, setShowConfirmPassword] = useState(false); const [error, setError] = useState(null); const [isLoading, setIsLoading] = useState(false); const [userAge, setUserAge] = useState(null); const [requiresParentalConsent, setRequiresParentalConsent] = useState(false); const [requireInviteCode, setRequireInviteCode] = useState(false); const [loadingConfig, setLoadingConfig] = useState(true); const { register: registerUser } = useAuth(); const { register, handleSubmit, watch, formState: { errors }, setValue, trigger, } = useForm({ resolver: zodResolver(createRegisterSchema(requireInviteCode)), defaultValues: { agreeToTerms: false, agreeToPrivacy: false, coppaConsent: false, }, }); // Fetch registration configuration on mount useEffect(() => { const fetchRegistrationConfig = async () => { try { const response = await apiClient.get('/api/v1/auth/registration/config'); if (response.data?.success && response.data?.data) { setRequireInviteCode(response.data.data.requireInviteCode); } } catch (error) { console.error('Failed to fetch registration config:', error); // Default to not requiring invite code if fetch fails setRequireInviteCode(false); } finally { setLoadingConfig(false); } }; fetchRegistrationConfig(); }, []); // Watch date of birth to calculate age and show parental consent if needed const dateOfBirth = watch('dateOfBirth'); // Calculate age when date of birth changes const calculateAge = (dob: string): number | null => { if (!dob) return null; const birthDate = new Date(dob); const today = new Date(); const age = today.getFullYear() - birthDate.getFullYear() - (today.getMonth() < birthDate.getMonth() || (today.getMonth() === birthDate.getMonth() && today.getDate() < birthDate.getDate()) ? 1 : 0); return age; }; // Update age and parental consent requirement when DOB changes React.useEffect(() => { if (dateOfBirth) { const age = calculateAge(dateOfBirth); setUserAge(age); setRequiresParentalConsent(age !== null && age >= 13 && age < 18); } else { setUserAge(null); setRequiresParentalConsent(false); } }, [dateOfBirth]); const onSubmit = async (data: RegisterFormData) => { setError(null); // Validate invite code if required if (requireInviteCode && (!data.inviteCode || data.inviteCode.trim() === '')) { setError('Invite code is required to register'); return; } setIsLoading(true); try { await registerUser({ name: data.name, email: data.email, password: data.password, inviteCode: data.inviteCode || undefined, dateOfBirth: data.dateOfBirth, parentalEmail: data.parentalEmail || undefined, coppaConsentGiven: data.coppaConsent || false, }); // Navigation to onboarding is handled in the register function } catch (err: any) { setError(err.message || 'Failed to register. Please try again.'); } finally { setIsLoading(false); } }; return ( Create Account ✨ Start your journey to organized parenting {error && ( {error} )} {requireInviteCode && ( )} setShowPassword(!showPassword)} edge="end" disabled={isLoading} aria-label={showPassword ? 'Hide password' : 'Show password'} > {showPassword ? : } ), }} FormHelperTextProps={{ id: errors.password ? 'password-error' : undefined, role: errors.password ? 'alert' : undefined, }} /> setShowConfirmPassword(!showConfirmPassword)} edge="end" disabled={isLoading} aria-label={showConfirmPassword ? 'Hide password' : 'Show password'} > {showConfirmPassword ? : } ), }} FormHelperTextProps={{ id: errors.confirmPassword ? 'confirm-password-error' : undefined, role: errors.confirmPassword ? 'alert' : undefined, }} /> {userAge !== null && userAge < 13 && ( Users under 13 years old cannot create an account per COPPA regulations. )} {requiresParentalConsent && ( As you are under 18, parental consent is required to create an account. } label={ I confirm that I have my parent/guardian's permission to create this account } sx={{ mt: 1 }} /> {errors.coppaConsent && ( Parental consent is required for users under 18 )} )} } label={ I agree to the{' '} Terms of Service {' '}* } sx={{ alignItems: 'flex-start', mb: 1 }} /> {errors.agreeToTerms && ( {errors.agreeToTerms.message} )} } label={ I agree to the{' '} Privacy Policy {' '}* } sx={{ alignItems: 'flex-start', mb: 1 }} /> {errors.agreeToPrivacy && ( {errors.agreeToPrivacy.message} )} Already have an account?{' '} Sign in ); }