'use client'; import { useState, useRef, useEffect } from 'react'; import { useRouter } from 'next/navigation'; import { QuickLinks } from './components'; import DynamicSection from './components/DynamicSection'; import RepeatableSection, { RepeatableEntryData } from './components/RepeatableSection'; import { profileSectionsService, ProfileSection, AgentTypeSectionsResponse } from '@/services/profile-sections.service'; import { agentsService, AgentProfile, FieldValueInput } from '@/services/agents.service'; import { MobileBackButton } from '@/components/layout/MobileBackButton'; export default function EditProfilePage() { const router = useRouter(); const scrollContainerRef = useRef(null); // State for agent profile and dynamic sections const [agentProfile, setAgentProfile] = useState(null); const [sections, setSections] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); // Form data keyed by field slug const [formData, setFormData] = useState>({}); // Repeatable sections data keyed by section slug const [repeatableData, setRepeatableData] = useState>({}); const [formErrors, setFormErrors] = useState>({}); const [repeatableErrors, setRepeatableErrors] = useState>>>({}); const [isSaving, setIsSaving] = useState(false); // Fetch agent profile and sections on mount useEffect(() => { const fetchData = async () => { try { setLoading(true); setError(null); // First, fetch the agent's profile to get their agentTypeId const profile = await agentsService.getMyProfile(); setAgentProfile(profile); if (!profile.agentTypeId) { setError('Your profile does not have an agent type assigned. Please contact support.'); setLoading(false); return; } // Now fetch the sections for this agent type const response: AgentTypeSectionsResponse = await profileSectionsService.getSectionsForAgentType(profile.agentTypeId); // Sort sections by effectiveSortOrder const sortedSections = [...response.sections].sort( (a, b) => (a.effectiveSortOrder || a.sortOrder) - (b.effectiveSortOrder || b.sortOrder) ); setSections(sortedSections); // Debug: Log all sections and their isRepeatable status console.log('All sections:', sortedSections.map(s => ({ slug: s.slug, name: s.name, isRepeatable: s.isRepeatable }))); // Initialize form data with existing profile data + default values from fields const initialData: Record = {}; // Pre-fill with existing profile data if (profile.firstName) initialData['first-name'] = profile.firstName; if (profile.lastName) initialData['last-name'] = profile.lastName; if (profile.email) initialData['email'] = profile.email; // Note: phone is populated from dynamic field values (slug: phone_number) // Don't pre-populate 'phone' here as it creates a stale shadow entry // that conflicts with the dynamic field during save if (profile.bio) initialData['bio'] = profile.bio; if (profile.licenseNumber) initialData['license-number'] = profile.licenseNumber; if (profile.experience) initialData['experience'] = profile.experience; if (profile.specializations?.length) initialData['specializations'] = profile.specializations; if (profile.languages?.length) initialData['languages'] = profile.languages; if (profile.serviceAreas?.length) initialData['service-areas'] = profile.serviceAreas; // Then apply field default values where no existing data sortedSections.forEach(section => { const sectionFields = Array.isArray(section.fields) ? section.fields : []; sectionFields.forEach(field => { if (!field || !field.slug) return; // Skip malformed fields if (field.defaultValue && initialData[field.slug] === undefined) { // Parse default value based on field type switch (field.fieldType) { case 'CHECKBOX': initialData[field.slug] = field.defaultValue === 'true'; break; case 'NUMBER': case 'RANGE': initialData[field.slug] = parseFloat(field.defaultValue) || 0; break; case 'CHECKBOX_GROUP': case 'MULTI_SELECT': case 'TAG_INPUT': try { initialData[field.slug] = JSON.parse(field.defaultValue); } catch { initialData[field.slug] = []; } break; default: initialData[field.slug] = field.defaultValue; } } }); }); // Fetch existing field values from the API try { const fieldValuesResponse = await agentsService.getFieldValues(); if (fieldValuesResponse.fieldValues) { fieldValuesResponse.fieldValues.forEach(fv => { // Only set if value exists and is not null/undefined if (fv.value !== null && fv.value !== undefined) { initialData[fv.fieldSlug] = fv.value; } }); } } catch (fieldValueErr) { // If field values don't exist yet, that's OK - just use defaults console.log('No existing field values found, using defaults'); } setFormData(initialData); // Initialize repeatable sections data const initialRepeatableData: Record = {}; sortedSections.forEach(section => { if (section.isRepeatable) { // Check if there's existing data for this section (stored as `${sectionSlug}_entries`) const entriesKey = `${section.slug}_entries`; const existingEntries = initialData[entriesKey]; if (Array.isArray(existingEntries) && existingEntries.length > 0) { initialRepeatableData[section.slug] = existingEntries as RepeatableEntryData[]; } else { // Initialize with one empty entry initialRepeatableData[section.slug] = [{}]; } } }); // Also check for any _entries fields that might not match a repeatable section // This handles cases where data was saved but section isn't marked as repeatable Object.keys(initialData).forEach(key => { if (key.endsWith('_entries') && Array.isArray(initialData[key])) { const sectionSlug = key.replace('_entries', ''); if (!initialRepeatableData[sectionSlug]) { // Find the section by slug const section = sortedSections.find(s => s.slug === sectionSlug); if (section) { initialRepeatableData[sectionSlug] = initialData[key] as RepeatableEntryData[]; } } } }); setRepeatableData(initialRepeatableData); } catch (err: unknown) { console.error('Failed to fetch data:', err); // Check for specific error types const error = err as { response?: { status?: number } }; if (error.response?.status === 401) { setError('You are not logged in or your session has expired. Please log in again.'); } else if (error.response?.status === 404) { setError('Agent profile not found. Please complete your registration first.'); } else { setError('Failed to load profile. Please try again.'); } } finally { setLoading(false); } }; fetchData(); }, []); const handleCancel = () => { router.push('/agent/dashboard'); }; const handleSave = async () => { try { setIsSaving(true); setFormErrors({}); setRepeatableErrors({}); // Validate required fields for regular sections const errors: Record = {}; const repErrors: Record>> = {}; sections.forEach(section => { const sectionFields = Array.isArray(section.fields) ? section.fields : []; if (section.isRepeatable) { // Validate repeatable section entries const entries = repeatableData[section.slug] || [{}]; entries.forEach((entry, entryIndex) => { sectionFields.forEach(field => { if (field && field.isRequired && field.isActive && !field.isSearchableOnly) { const value = entry[field.slug]; const isEmpty = value === undefined || value === null || value === '' || (Array.isArray(value) && value.length === 0); if (isEmpty) { if (!repErrors[section.slug]) repErrors[section.slug] = {}; if (!repErrors[section.slug][entryIndex]) repErrors[section.slug][entryIndex] = {}; repErrors[section.slug][entryIndex][field.slug] = `${field.name} is required`; } } }); }); } else { // Validate regular section fields sectionFields.forEach(field => { if (field && field.isRequired && field.isActive && !field.isSearchableOnly) { const value = formData[field.slug]; const isEmpty = value === undefined || value === null || value === '' || (Array.isArray(value) && value.length === 0); if (isEmpty) { errors[field.slug] = `${field.name} is required`; } } }); } }); const hasErrors = Object.keys(errors).length > 0; const hasRepeatableErrors = Object.keys(repErrors).length > 0; if (hasErrors || hasRepeatableErrors) { setFormErrors(errors); setRepeatableErrors(repErrors); // Scroll to first error if (hasErrors) { const firstErrorField = Object.keys(errors)[0]; const errorElement = document.querySelector(`[data-field-slug="${firstErrorField}"]`); errorElement?.scrollIntoView({ behavior: 'smooth', block: 'center' }); } else if (hasRepeatableErrors) { const firstSection = Object.keys(repErrors)[0]; const sectionElement = document.getElementById(firstSection); sectionElement?.scrollIntoView({ behavior: 'smooth', block: 'center' }); } return; } // Convert formData to field values array const fieldValues: FieldValueInput[] = Object.entries(formData).map(([fieldSlug, value]) => ({ fieldSlug, value: value as string | number | boolean | string[] | Record | null, })); // Add repeatable section data as a special field value Object.entries(repeatableData).forEach(([sectionSlug, entries]) => { // Filter out empty entries (entries with no filled fields) const nonEmptyEntries = entries.filter(entry => Object.values(entry).some(v => v !== undefined && v !== null && v !== '') ); if (nonEmptyEntries.length > 0) { fieldValues.push({ fieldSlug: `${sectionSlug}_entries`, value: nonEmptyEntries, }); } }); // Save field values to API await agentsService.saveFieldValues(fieldValues); // Also update main profile fields (phone, email) to keep them in sync // This ensures that when the view page checks agentProfile.phone first, // it gets the correct (possibly empty) value const profileUpdates: Partial<{ phone: string | null; email: string | null }> = {}; // Sync phone field - check dynamic field slugs FIRST, then legacy 'phone' // Dynamic fields (phone_number, etc.) are the source of truth from the form // 'phone' is a legacy pre-populated key and should only be used as fallback const phoneFieldSlugs = ['phone_number', 'cell_number', 'office_number', 'phone']; for (const slug of phoneFieldSlugs) { const phoneValue = formData[slug]; if (phoneValue !== undefined) { // Set to null if empty, otherwise use the value profileUpdates.phone = phoneValue === '' ? null : (phoneValue as string); break; // Use the first found phone field } } // Only update if there are changes to sync if (Object.keys(profileUpdates).length > 0) { await agentsService.updateProfile(profileUpdates); } router.push('/agent/dashboard'); } catch (err) { console.error('Failed to save:', err); setError('Failed to save profile. Please try again.'); } finally { setIsSaving(false); } }; const handleFieldChange = (fieldSlug: string, value: unknown) => { setFormData(prev => ({ ...prev, [fieldSlug]: value })); // Clear error when field is modified if (formErrors[fieldSlug]) { setFormErrors(prev => { const newErrors = { ...prev }; delete newErrors[fieldSlug]; return newErrors; }); } }; const handleRepeatableChange = (sectionSlug: string, entries: RepeatableEntryData[]) => { setRepeatableData(prev => ({ ...prev, [sectionSlug]: entries })); // Clear errors for this section when modified if (repeatableErrors[sectionSlug]) { setRepeatableErrors(prev => { const newErrors = { ...prev }; delete newErrors[sectionSlug]; return newErrors; }); } }; if (loading) { return (

Loading profile sections...

); } if (error && sections.length === 0) { return (

Unable to Load Profile

{error}

); } return (
{/* Left Sidebar - Quick Links */}
{/* Main Content - Scrollable */}
{/* Inner container with padding for shadow visibility */}
{/* Error Banner */} {error && (

{error}

)} {/* Dynamic Sections */} {sections.map((section) => section.isRepeatable ? ( ) : ( ) )} {/* Empty State */} {sections.length === 0 && !loading && (

No Profile Sections Available

Profile sections have not been configured for your agent type yet.

)} {/* Action Buttons */} {sections.length > 0 && (
)}
); }