import { useState, useEffect } from 'react'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; import { useAuthStore } from '@/stores/auth'; import { useTranslation } from '@/hooks/useTranslation'; import { apiClient } from '@/services/api/client'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Textarea } from '@/components/ui/textarea'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Progress } from '@/components/ui/progress'; import { useToast } from '@/hooks/useToast'; import { usernameSchema, emailSchema } from '@/schemas/validation'; import { calculateProfileCompletion, type ProfileCompletion, type UpdateProfileRequest, } from '@/features/profile/services/profileService'; import { Twitter, Instagram, Facebook, Youtube, Link as LinkIcon, AlertCircle, CheckCircle2, } from 'lucide-react'; import { Alert, AlertDescription } from '@/components/ui/alert'; // FE-PAGE-003: Complete Profile page implementation // Define schema with social links const profileSchema = z.object({ username: usernameSchema, email: emailSchema, first_name: z.string().optional(), last_name: z.string().optional(), bio: z.string().max(500, 'Bio must be less than 500 characters').optional(), location: z.string().max(100).optional(), social_links: z .object({ twitter: z.string().url('Invalid Twitter URL').optional().or(z.literal('')), instagram: z.string().url('Invalid Instagram URL').optional().or(z.literal('')), facebook: z.string().url('Invalid Facebook URL').optional().or(z.literal('')), youtube: z.string().url('Invalid YouTube URL').optional().or(z.literal('')), website: z.string().url('Invalid website URL').optional().or(z.literal('')), }) .optional(), }); type ProfileFormData = z.infer; export function ProfileForm() { const { user, refreshUser } = useAuthStore(); const { t } = useTranslation(); const toast = useToast(); const [isEditing, setIsEditing] = useState(false); const [isLoading, setIsLoading] = useState(false); const [completion, setCompletion] = useState(null); // FE-PAGE-003: Load profile completion useEffect(() => { if (user?.id) { calculateProfileCompletion(user.id) .then(setCompletion) .catch((err) => { console.error('Failed to load profile completion:', err); }); } }, [user?.id]); // FE-PAGE-003: Initialize form with social links const form = useForm({ resolver: zodResolver(profileSchema), defaultValues: { username: user?.username || '', email: user?.email || '', first_name: user?.first_name || '', last_name: user?.last_name || '', bio: user?.bio || '', location: user?.location || '', social_links: { twitter: (user as any)?.social_links?.twitter || '', instagram: (user as any)?.social_links?.instagram || '', facebook: (user as any)?.social_links?.facebook || '', youtube: (user as any)?.social_links?.youtube || '', website: (user as any)?.social_links?.website || '', }, }, mode: 'onBlur', }); const { register, handleSubmit, reset, watch, formState: { errors }, } = form; // FE-PAGE-003: Watch form values to update completion const watchedValues = watch(); useEffect(() => { if (user?.id && !isEditing) { // Reset form when user changes reset({ username: user?.username || '', email: user?.email || '', first_name: user?.first_name || '', last_name: user?.last_name || '', bio: user?.bio || '', location: user?.location || '', social_links: { twitter: (user as any)?.social_links?.twitter || '', instagram: (user as any)?.social_links?.instagram || '', facebook: (user as any)?.social_links?.facebook || '', youtube: (user as any)?.social_links?.youtube || '', website: (user as any)?.social_links?.website || '', }, }); } }, [user, reset, isEditing]); const onSubmit = async (data: ProfileFormData) => { if (!user) return; setIsLoading(true); try { const userId = user.id; // FE-PAGE-003: Prepare update request with social links const updateData: UpdateProfileRequest & { social_links?: Record } = { username: data.username, first_name: data.first_name || undefined, last_name: data.last_name || undefined, bio: data.bio || undefined, location: data.location || undefined, }; // Add social links if provided if (data.social_links) { const socialLinks: Record = {}; if (data.social_links.twitter) socialLinks.twitter = data.social_links.twitter; if (data.social_links.instagram) socialLinks.instagram = data.social_links.instagram; if (data.social_links.facebook) socialLinks.facebook = data.social_links.facebook; if (data.social_links.youtube) socialLinks.youtube = data.social_links.youtube; if (data.social_links.website) socialLinks.website = data.social_links.website; if (Object.keys(socialLinks).length > 0) { updateData.social_links = socialLinks; } } await apiClient.put(`/users/${userId}`, updateData); await refreshUser(); // Refresh completion if (user.id) { const newCompletion = await calculateProfileCompletion(user.id); setCompletion(newCompletion); } toast.success(t('profile.success') || 'Profile updated successfully'); setIsEditing(false); } catch (err: any) { toast.error(err.message || t('profile.error') || 'Failed to update profile'); } finally { setIsLoading(false); } }; const handleCancel = () => { reset({ username: user?.username || '', email: user?.email || '', first_name: user?.first_name || '', last_name: user?.last_name || '', bio: user?.bio || '', location: user?.location || '', social_links: { twitter: (user as any)?.social_links?.twitter || '', instagram: (user as any)?.social_links?.instagram || '', facebook: (user as any)?.social_links?.facebook || '', youtube: (user as any)?.social_links?.youtube || '', website: (user as any)?.social_links?.website || '', }, }); setIsEditing(false); }; if (!user) { return (

Chargement du profil...

); } return (
{/* FE-PAGE-003: Profile Completion Indicator */} {completion && ( {completion.percentage === 100 ? ( ) : ( )} Profile Completion
{completion.percentage}% Complete {completion.percentage === 100 ? 'Profile Complete!' : `${completion.missing.length} field(s) missing`}
{completion.missing.length > 0 && (

Complete your profile:

    {completion.missing.map((field, index) => (
  • {field}
  • ))}
)}
)} {t('profile.title') || 'Profile'} {!isEditing && ( )}

{t('profile.personalInfo') || 'Personal Information'}

{/* Avatar Section */} {isEditing && (
)}
{errors.username && (

{errors.username.message}

)}
{errors.email && (

{errors.email.message}

)}
{errors.first_name && (

{errors.first_name.message}

)}
{errors.last_name && (

{errors.last_name.message}

)}
{errors.location && (

{errors.location.message}

)}
{/* FE-PAGE-003: Bio with Textarea */}