'use client'; import { useState, useEffect } from 'react'; import { useSession } from 'next-auth/react'; import Image from 'next/image'; import Link from 'next/link'; // Import shared components import { InfoCard, ExperienceSection, ProfileCard, SpecializationSection, TestimonialsSection, StatusButtons, ContactInfo, PhotoUploadModal, } from '@/components/profile'; import { agentsService, AgentProfile, FieldValueResponse } from '@/services/agents.service'; import { connectionRequestsService } from '@/services/connection-requests.service'; import { uploadService } from '@/services/upload.service'; import { testimonialsService, Testimonial } from '@/services/testimonials.service'; import { mapFieldValuesToExperience, mapFieldValuesToProfileCard, mapFieldValuesToAvailability, mapFieldValuesToSpecializationFields, mapFieldValuesToContactInfo, ExperienceData, ProfileCardData, AvailabilityData, SpecializationFieldsData, ContactInfoData } from '@/utils/profileDataMapper'; // Mock data for sections not yet available from API const mockData = { preferredWorkEnvironment: 'Preferred work environment includes on-site property visits, in-depth market research, client consultations, property tours, and active involvement in negotiations to ensure the best outcomes', }; // Default experience data when no field values are available const defaultExperience: ExperienceData = { years: '-', contracts: '-', licensingAreas: [], expertiseYears: [], certifications: [], }; // Default profile card data when no field values are available const defaultProfileCardData: ProfileCardData = { bio: '', expertise: [], serviceAreas: [], city: null, state: null, }; // Default availability data when no field values are available const defaultAvailabilityData: AvailabilityData = { type: '', schedule: [], }; // Default specialization fields data when no field values are available const defaultSpecializationFieldsData: SpecializationFieldsData = { fields: [], }; // Default contact info data when no field values are available const defaultContactInfoData: ContactInfoData = { email: null, phone: null, }; export default function AgentDashboard() { const { data: session, status: sessionStatus } = useSession(); const [agentProfile, setAgentProfile] = useState(null); const [fieldValues, setFieldValues] = useState([]); const [experienceData, setExperienceData] = useState(defaultExperience); const [profileCardData, setProfileCardData] = useState(defaultProfileCardData); const [availabilityData, setAvailabilityData] = useState(defaultAvailabilityData); const [specializationFieldsData, setSpecializationFieldsData] = useState(defaultSpecializationFieldsData); const [contactInfoData, setContactInfoData] = useState(defaultContactInfoData); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [isPhotoModalOpen, setIsPhotoModalOpen] = useState(false); const [imageError, setImageError] = useState(false); const [avatarUrl, setAvatarUrl] = useState(null); const [isAvailable, setIsAvailable] = useState(true); const [isTogglingAvailability, setIsTogglingAvailability] = useState(false); const [pendingRequestCount, setPendingRequestCount] = useState(0); const [testimonials, setTestimonials] = useState<{ id: string; text: string; author: string; role: string; rating: number }[]>([]); const defaultImage = '/assets/demo_images/8f017153a7b4a239a4f0691234ef97dd55092282.jpg'; // Helper to check if avatar is an S3 key (not a full URL or local path) const isS3Key = (avatar: string | null | undefined): boolean => { if (!avatar) return false; // S3 keys don't start with http or / return !avatar.startsWith('http') && !avatar.startsWith('/'); }; // Fetch agent profile and field values when session is ready useEffect(() => { // Wait until session is authenticated and tokens are available if (sessionStatus !== 'authenticated') return; const token = typeof window !== 'undefined' ? localStorage.getItem('accessToken') : null; if (!token) return; const fetchData = async () => { try { setLoading(true); setError(null); // Fetch profile, field values, pending request count, and testimonials in parallel const [profile, fieldValuesResponse, requestCounts, testimonialsData] = await Promise.all([ agentsService.getMyProfile(), agentsService.getFieldValues(), connectionRequestsService.getRequestCounts().catch(() => null), testimonialsService.getMyTestimonials('latest').catch(() => [] as Testimonial[]), ]); setAgentProfile(profile); setFieldValues(fieldValuesResponse.fieldValues); setIsAvailable(profile.isAvailable ?? true); if (requestCounts) { setPendingRequestCount(requestCounts.pending); } // Map testimonials for TestimonialsSection (latest 3) setTestimonials( testimonialsData.slice(0, 3).map((t) => ({ id: t.id, text: t.text, author: t.authorName, role: t.authorRole, rating: t.rating, })) ); // Map field values to experience data structure const mappedExperience = mapFieldValuesToExperience(fieldValuesResponse.fieldValues); setExperienceData(mappedExperience); // Map field values to profile card data (bio, expertise) const mappedProfileCard = mapFieldValuesToProfileCard(fieldValuesResponse.fieldValues); setProfileCardData(mappedProfileCard); // Map field values to availability data const mappedAvailability = mapFieldValuesToAvailability(fieldValuesResponse.fieldValues); setAvailabilityData(mappedAvailability); // Map field values to specialization fields data (only fields from "Specialization" section) const mappedSpecializationFields = mapFieldValuesToSpecializationFields(fieldValuesResponse.fieldValues); setSpecializationFieldsData(mappedSpecializationFields); // Map field values to contact info data (email, phone) const mappedContactInfo = mapFieldValuesToContactInfo(fieldValuesResponse.fieldValues); setContactInfoData(mappedContactInfo); // If avatar is an S3 key, fetch presigned URL if (profile.avatar && isS3Key(profile.avatar)) { try { const presignedUrl = await uploadService.getPresignedDownloadUrl(profile.avatar); setAvatarUrl(presignedUrl); } catch (avatarErr) { console.error('Failed to get avatar URL:', avatarErr); // Don't fail the whole page, just use default image } } else if (profile.avatar) { // It's already a full URL or local path setAvatarUrl(profile.avatar); } } catch (err) { console.error('Failed to fetch profile:', err); setError('Failed to load profile data'); } finally { setLoading(false); } }; fetchData(); }, [sessionStatus]); // Handle availability toggle const handleToggleAvailability = async () => { if (isTogglingAvailability) return; try { setIsTogglingAvailability(true); const newAvailability = !isAvailable; // Update on server const updatedProfile = await agentsService.updateProfile({ isAvailable: newAvailability }); // Update local state setIsAvailable(newAvailability); setAgentProfile(updatedProfile); } catch (err) { console.error('Failed to update availability:', err); // Revert on error - state stays the same } finally { setIsTogglingAvailability(false); } }; const handlePhotoUpload = async (file: File) => { // Upload avatar to S3 (uses agent ID as filename for auto-replacement) const uploadedFile = await uploadService.uploadAvatar(file); // Update agent profile with the S3 key const updatedProfile = await agentsService.updateProfile({ avatar: uploadedFile.url }); setImageError(false); // Reset error state for new image setAgentProfile(updatedProfile); // Fetch new presigned URL for the uploaded avatar if (uploadedFile.url && isS3Key(uploadedFile.url)) { try { const presignedUrl = await uploadService.getPresignedDownloadUrl(uploadedFile.url); setAvatarUrl(presignedUrl); } catch (err) { console.error('Failed to get presigned URL for new avatar:', err); } } // Notify header and other components to refresh avatar window.dispatchEvent(new Event('profile-updated')); }; const getProfileImageUrl = () => { // If image failed to load, return default if (imageError) { return defaultImage; } // If we have a presigned URL from S3, use it if (avatarUrl) { return avatarUrl; } // Check for null, undefined, or empty string if (!agentProfile?.avatar || agentProfile.avatar.trim() === '') { return defaultImage; } // For relative paths (local assets), return as-is if (agentProfile.avatar.startsWith('/')) { return agentProfile.avatar; } // Default fallback return defaultImage; }; if (loading) { return (

Loading profile...

); } if (error || !agentProfile) { return (

Unable to Load Profile

{error || 'Profile not found'}

); } // Format member since date const formatMemberSince = (dateString?: string) => { if (!dateString) return 'Member'; try { const date = new Date(dateString); return date.toLocaleDateString('en-US', { month: 'long', year: 'numeric' }); } catch { return 'Member'; } }; return (
{/* Verification Status Banner */} {agentProfile?.verificationStatus === 'REJECTED' && (
Warning

Profile Verification Rejected

{agentProfile.verificationNote && (

Reason: {agentProfile.verificationNote}

)}

Please update your profile and documents, then resubmit for verification.

Update & Resubmit
)} {agentProfile?.verificationStatus === 'PENDING_REVIEW' && (

Profile verification is under review. You will be notified once approved.

)} {agentProfile?.verificationStatus === 'NONE' && (
Info

Complete your profile and upload verification documents to get verified.

Complete Profile
)} {/* Main Layout - Responsive: Column on mobile, Row on desktop */}
{/* Left Sidebar - Status & Contact */}
{/* Profile Image */}
{/* Initials base layer */}
{agentProfile?.firstName?.[0]?.toUpperCase() || '?'}
{/* eslint-disable-next-line @next/next/no-img-element */} Profile { (e.target as HTMLImageElement).style.display = 'none'; }} /> {/* Gradient Overlay */}
{/* Edit Icon - Top Right Outside */}
{/* Status Buttons - Toggle for agent to set availability */} {/* Contact Info */}
{/* Right Content - Profile Info + Experience + All Sections */}
{/* Profile Card */} {/* Experience Section - Dynamic data from profile fields */} {/* Info Cards Section */}
} content={
{availabilityData.type && (

{availabilityData.type}

)}
{availabilityData.schedule.length > 0 ? ( availabilityData.schedule.map((item, index) => (

{item}

)) ) : (

Not specified

)}
} /> } content={

{mockData.preferredWorkEnvironment}

} /> {testimonials.length > 0 && ( } content={

“{testimonials[0].text.length > 150 ? testimonials[0].text.substring(0, 150) + '...' : testimonials[0].text}”

} /> )}
{/* Specialization Section */} {/* Testimonials Section */}
{/* Photo Upload Modal */} setIsPhotoModalOpen(false)} onUpload={handlePhotoUpload} currentPhoto={agentProfile.avatar} currentPhotoUrl={avatarUrl} />
); }