2026-01-20 12:17:47 +05:30
'use client' ;
2026-01-24 23:10:02 +05:30
import { useState , useEffect } from 'react' ;
2026-01-20 12:17:47 +05:30
import Image from 'next/image' ;
import { useParams } from 'next/navigation' ;
2026-02-02 09:59:08 +05:30
import { useSession } from 'next-auth/react' ;
2026-01-20 12:17:47 +05:30
// Import shared components
import {
InfoCard ,
ExperienceSection ,
ProfileCard ,
SpecializationSection ,
TestimonialsSection ,
StatusButtons ,
ContactInfo ,
} from '@/components/profile' ;
2026-02-02 09:59:08 +05:30
import { ConnectRequestModal } from '@/components/modals' ;
2026-01-24 23:10:02 +05:30
import { agentsService , AgentProfile , FieldValueResponse } from '@/services/agents.service' ;
2026-02-02 09:59:08 +05:30
import { connectionRequestsService , ConnectionStatus } from '@/services/connection-requests.service' ;
import { uploadService } from '@/services/upload.service' ;
2026-03-05 05:36:10 +05:30
import { testimonialsService , Testimonial } from '@/services/testimonials.service' ;
2026-02-02 10:19:55 +05:30
import { mapFieldValuesToExperience , mapFieldValuesToSpecializationFields , mapFieldValuesToProfileCard , mapFieldValuesToContactInfo , mapFieldValuesToAvailability , ExperienceData , SpecializationFieldsData , ProfileCardData , ContactInfoData , AvailabilityData } from '@/utils/profileDataMapper' ;
2026-01-20 12:17:47 +05:30
2026-01-24 23:10:02 +05:30
// Mock data for sections not yet available from API
const mockData = {
2026-01-20 12:17:47 +05:30
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' ,
2026-01-24 23:10:02 +05:30
} ;
// Default experience data when no field values are available
const defaultExperience : ExperienceData = {
years : '-' ,
contracts : '-' ,
licensingAreas : [ ] ,
expertiseYears : [ ] ,
certifications : [ ] ,
} ;
2026-01-20 12:17:47 +05:30
2026-01-25 01:15:02 +05:30
// Default specialization fields data when no field values are available
const defaultSpecializationFieldsData : SpecializationFieldsData = {
fields : [ ] ,
} ;
2026-02-02 09:59:08 +05:30
// Default profile card data when no field values are available
const defaultProfileCardData : ProfileCardData = {
bio : '' ,
expertise : [ ] ,
serviceAreas : [ ] ,
city : null ,
state : null ,
} ;
2026-02-02 10:19:55 +05:30
// Default contact info data when no field values are available
const defaultContactInfoData : ContactInfoData = {
email : null ,
phone : null ,
} ;
// Default availability data when no field values are available
const defaultAvailabilityData : AvailabilityData = {
type : '' ,
schedule : [ ] ,
} ;
2026-01-20 12:17:47 +05:30
export default function AgentProfileView() {
const params = useParams ( ) ;
const id = params . id as string ;
2026-02-02 09:59:08 +05:30
const { data : session } = useSession ( ) ;
2026-01-20 12:17:47 +05:30
2026-01-24 23:10:02 +05:30
const [ agentProfile , setAgentProfile ] = useState < AgentProfile | null > ( null ) ;
const [ fieldValues , setFieldValues ] = useState < FieldValueResponse [ ] > ( [ ] ) ;
const [ experienceData , setExperienceData ] = useState < ExperienceData > ( defaultExperience ) ;
2026-01-25 01:15:02 +05:30
const [ specializationFieldsData , setSpecializationFieldsData ] = useState < SpecializationFieldsData > ( defaultSpecializationFieldsData ) ;
2026-02-02 09:59:08 +05:30
const [ profileCardData , setProfileCardData ] = useState < ProfileCardData > ( defaultProfileCardData ) ;
2026-02-02 10:19:55 +05:30
const [ contactInfoData , setContactInfoData ] = useState < ContactInfoData > ( defaultContactInfoData ) ;
const [ availabilityData , setAvailabilityData ] = useState < AvailabilityData > ( defaultAvailabilityData ) ;
2026-01-24 23:10:02 +05:30
const [ loading , setLoading ] = useState ( true ) ;
const [ error , setError ] = useState < string | null > ( null ) ;
const [ imageError , setImageError ] = useState ( false ) ;
2026-02-02 09:59:08 +05:30
const [ avatarUrl , setAvatarUrl ] = useState < string | null > ( null ) ;
2026-03-05 05:36:10 +05:30
const [ testimonials , setTestimonials ] = useState < { id : string ; text : string ; author : string ; role : string ; rating : number } [ ] > ( [ ] ) ;
2026-02-02 09:59:08 +05:30
// Connect modal state
const [ showConnectModal , setShowConnectModal ] = useState ( false ) ;
const [ connectionStatus , setConnectionStatus ] = useState < ConnectionStatus | null > ( null ) ;
const [ connectionRequestId , setConnectionRequestId ] = useState < string | null > ( null ) ;
2026-01-24 23:10:02 +05:30
2026-02-02 09:59:08 +05:30
// 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 ( '/' ) ;
} ;
2026-01-24 23:10:02 +05:30
// Fetch agent profile and field values on mount
useEffect ( ( ) = > {
const fetchData = async ( ) = > {
if ( ! id ) return ;
try {
setLoading ( true ) ;
setError ( null ) ;
2026-03-05 05:36:10 +05:30
// Fetch profile, field values, and testimonials in parallel
const [ profile , fieldValuesResponse , testimonialsData ] = await Promise . all ( [
2026-01-24 23:10:02 +05:30
agentsService . getAgentById ( id ) ,
agentsService . getFieldValuesByAgentId ( id ) ,
2026-03-05 05:36:10 +05:30
testimonialsService . getAgentTestimonials ( id ) . catch ( ( ) = > [ ] as Testimonial [ ] ) ,
2026-01-24 23:10:02 +05:30
] ) ;
setAgentProfile ( profile ) ;
setFieldValues ( fieldValuesResponse . fieldValues ) ;
// Map field values to experience data structure
const mappedExperience = mapFieldValuesToExperience ( fieldValuesResponse . fieldValues ) ;
setExperienceData ( mappedExperience ) ;
2026-01-25 01:15:02 +05:30
// Map field values to specialization fields data (only fields from "Specialization" section)
const mappedSpecializationFields = mapFieldValuesToSpecializationFields ( fieldValuesResponse . fieldValues ) ;
setSpecializationFieldsData ( mappedSpecializationFields ) ;
2026-02-02 09:59:08 +05:30
// Map field values to profile card data (bio, expertise, location)
const mappedProfileCard = mapFieldValuesToProfileCard ( fieldValuesResponse . fieldValues ) ;
setProfileCardData ( mappedProfileCard ) ;
2026-02-02 10:19:55 +05:30
// Map field values to contact info data (email, phone)
const mappedContactInfo = mapFieldValuesToContactInfo ( fieldValuesResponse . fieldValues ) ;
setContactInfoData ( mappedContactInfo ) ;
// Map field values to availability data
const mappedAvailability = mapFieldValuesToAvailability ( fieldValuesResponse . fieldValues ) ;
setAvailabilityData ( mappedAvailability ) ;
2026-03-05 05:36:10 +05:30
// Map testimonials for TestimonialsSection
setTestimonials (
testimonialsData . map ( ( t ) = > ( {
id : t.id ,
text : t.text ,
author : t.authorName ,
role : t.authorRole ,
rating : t.rating ,
} ) )
) ;
2026-02-02 09:59:08 +05:30
// If avatar is an S3 key, fetch presigned URL
if ( profile . avatar && isS3Key ( profile . avatar ) ) {
try {
const presignedUrl = await uploadService . getPresignedDownloadUrl ( profile . avatar ) ;
2026-03-07 22:19:10 +05:30
setAvatarUrl ( presignedUrl ) ;
2026-02-02 09:59:08 +05:30
} catch ( avatarErr ) {
console . error ( 'Failed to get avatar URL:' , avatarErr ) ;
}
} else if ( profile . avatar ) {
setAvatarUrl ( profile . avatar ) ;
}
2026-01-24 23:10:02 +05:30
} catch ( err ) {
console . error ( 'Failed to fetch profile:' , err ) ;
setError ( 'Failed to load profile data' ) ;
} finally {
setLoading ( false ) ;
}
} ;
fetchData ( ) ;
} , [ id ] ) ;
2026-02-02 09:59:08 +05:30
// Fetch connection status when user is logged in
useEffect ( ( ) = > {
const fetchConnectionStatus = async ( ) = > {
if ( ! id || ! session ) return ;
try {
const statusResponse = await connectionRequestsService . getConnectionStatus ( id ) ;
setConnectionStatus ( statusResponse ? . status || null ) ;
setConnectionRequestId ( statusResponse ? . id || null ) ;
} catch ( err ) {
console . error ( 'Failed to fetch connection status:' , err ) ;
// Non-critical error, don't show to user
}
} ;
fetchConnectionStatus ( ) ;
} , [ id , session ] ) ;
// Handle connection request success
const handleConnectionSuccess = ( ) = > {
setConnectionStatus ( 'PENDING' ) ;
} ;
// Handle unlink/disconnect
const handleUnlink = async ( ) = > {
if ( ! connectionRequestId ) return ;
try {
await connectionRequestsService . cancelRequest ( connectionRequestId ) ;
setConnectionStatus ( null ) ;
setConnectionRequestId ( null ) ;
} catch ( err ) {
console . error ( 'Failed to unlink connection:' , err ) ;
}
} ;
2026-01-24 23:10:02 +05:30
const getProfileImageUrl = ( ) = > {
2026-03-07 22:19:10 +05:30
// If image failed to load, return null
2026-01-24 23:10:02 +05:30
if ( imageError ) {
2026-03-07 22:19:10 +05:30
return null ;
2026-01-24 23:10:02 +05:30
}
2026-02-02 09:59:08 +05:30
// If we have a presigned URL from S3, use it
if ( avatarUrl ) {
return avatarUrl ;
}
2026-01-24 23:10:02 +05:30
// Check for null, undefined, or empty string
if ( ! agentProfile ? . avatar || agentProfile . avatar . trim ( ) === '' ) {
2026-03-07 22:19:10 +05:30
return null ;
2026-01-24 23:10:02 +05:30
}
2026-02-02 09:59:08 +05:30
// For relative paths (local assets), return as-is
if ( agentProfile . avatar . startsWith ( '/' ) ) {
return agentProfile . avatar ;
2026-01-24 23:10:02 +05:30
}
2026-03-07 22:19:10 +05:30
// No image available
return null ;
2026-01-24 23:10:02 +05:30
} ;
// 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' ;
}
} ;
if ( loading ) {
return (
< div className = "max-w-7xl mx-auto px-4 lg:px-8 py-6" >
< div className = "flex items-center justify-center h-[calc(100vh-180px)]" >
< div className = "flex flex-col items-center gap-4" >
< div className = "animate-spin rounded-full h-12 w-12 border-b-2 border-[#E58625]" > < / div >
< p className = "text-[14px] font-serif text-[#00293D]/70" > Loading profile . . . < / p >
< / div >
< / div >
< / div >
) ;
}
if ( error || ! agentProfile ) {
return (
< div className = "max-w-7xl mx-auto px-4 lg:px-8 py-6" >
< div className = "flex items-center justify-center h-[calc(100vh-180px)]" >
< div className = "bg-white rounded-[20px] p-8 shadow-[0px_10px_20px_rgba(217,217,217,0.5)] max-w-md text-center" >
< div className = "w-16 h-16 bg-red-100 rounded-full flex items-center justify-center mx-auto mb-4" >
< svg className = "w-8 h-8 text-red-500" fill = "none" viewBox = "0 0 24 24" stroke = "currentColor" >
< path strokeLinecap = "round" strokeLinejoin = "round" strokeWidth = { 2 } d = "M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" / >
< / svg >
< / div >
< h2 className = "text-[18px] font-bold font-serif text-[#00293D] mb-2" > Unable to Load Profile < / h2 >
< p className = "text-[14px] font-serif text-[#00293D]/70 mb-4" > { error || 'Profile not found' } < / p >
< button
onClick = { ( ) = > window . location . reload ( ) }
className = "px-6 py-2 bg-[#E58625] rounded-full text-[14px] font-semibold font-serif text-white hover:bg-[#E58625]/90 transition-colors"
>
Try Again
< / button >
< / div >
< / div >
< / div >
) ;
}
2026-01-20 12:17:47 +05:30
return (
< div className = "max-w-7xl mx-auto px-4 lg:px-8 py-6 space-y-6" >
{ /* Main Layout - Responsive: Column on mobile, Row on desktop */ }
< div className = "flex flex-col lg:flex-row gap-6 lg:items-stretch" >
{ /* Left Sidebar - Status & Contact */ }
< div className = "w-full lg:w-[280px] flex-shrink-0 space-y-4 flex flex-col items-center lg:items-start" >
{ /* Profile Image */ }
< div className = "relative w-[200px] lg:w-[260px]" >
2026-03-07 22:19:10 +05:30
< div className = "w-[200px] h-[200px] lg:w-[260px] lg:h-[260px] rounded-[15px] overflow-hidden bg-[#e8e8e8]" >
{ getProfileImageUrl ( ) ? (
/* eslint-disable-next-line @next/next/no-img-element */
< img
src = { getProfileImageUrl ( ) ! }
alt = "Profile"
className = "w-full h-full object-cover"
onError = { ( ) = > setImageError ( true ) }
/ >
) : (
< div className = "w-full h-full flex items-center justify-center" >
< Image
src = "/assets/icons/user-placeholder-icon.svg"
alt = "Profile"
width = { 80 }
height = { 80 }
className = "opacity-40"
/ >
< / div >
) }
2026-01-20 12:17:47 +05:30
{ /* Gradient Overlay */ }
< div className = "absolute inset-0 bg-gradient-to-t from-black/50 via-black/20 to-transparent pointer-events-none" / >
< / div >
< / div >
2026-02-02 09:59:08 +05:30
{ /* Status Buttons - Public view shows availability status with Connect/Pending/Unlink button */ }
< StatusButtons
isAvailable = { agentProfile . isAvailable ? ? true }
connectionStatus = { connectionStatus }
onConnectClick = { ( ) = > setShowConnectModal ( true ) }
onUnlinkClick = { handleUnlink }
/ >
2026-01-20 12:17:47 +05:30
{ /* Contact Info */ }
2026-01-24 23:10:02 +05:30
< ContactInfo
2026-02-02 10:19:55 +05:30
email = { agentProfile . email || agentProfile . user ? . email || contactInfoData . email || '' }
phone = { agentProfile . phone || contactInfoData . phone || '' }
2026-01-24 23:10:02 +05:30
/ >
2026-01-20 12:17:47 +05:30
< / div >
{ /* Right Content - Profile Info + Experience + All Sections */ }
< div className = "flex-1 space-y-4" >
{ /* Profile Card - No edit button for user view */ }
< ProfileCard
2026-01-24 23:10:02 +05:30
firstName = { agentProfile . firstName }
lastName = { agentProfile . lastName }
isVerified = { agentProfile . isVerified }
title = { agentProfile . agentType ? . name || 'Real Estate Agent' }
2026-02-02 09:59:08 +05:30
location = { profileCardData . city && profileCardData . state
? ` ${ profileCardData . city } , ${ profileCardData . state } `
: profileCardData . city || profileCardData . state || agentProfile . serviceAreas ? . [ 0 ] || '-' }
2026-01-24 23:10:02 +05:30
memberSince = { formatMemberSince ( ( agentProfile as unknown as { createdAt? : string } ) . createdAt ) }
2026-02-02 09:59:08 +05:30
bio = { profileCardData . bio || agentProfile . bio || '' }
expertise = { profileCardData . expertise . length > 0 ? profileCardData . expertise : ( agentProfile . specializations || [ ] ) }
2026-01-20 12:17:47 +05:30
showEditButton = { false }
2026-01-20 12:26:27 +05:30
messageHref = "/user/message"
2026-02-02 09:59:08 +05:30
connectionStatus = { connectionStatus }
onConnectClick = { ( ) = > setShowConnectModal ( true ) }
onUnlinkClick = { handleUnlink }
2026-01-20 12:17:47 +05:30
/ >
2026-01-24 23:10:02 +05:30
{ /* Experience Section - Dynamic data from profile fields */ }
< ExperienceSection experience = { experienceData } / >
2026-01-20 12:17:47 +05:30
{ /* Info Cards Section */ }
< div className = "flex flex-col lg:grid lg:grid-cols-3 gap-6" >
< InfoCard
title = "Availability"
icon = {
< Image
src = "/assets/icons/availability-clock-icon.svg"
alt = "Availability"
width = { 28 }
height = { 31 }
/ >
}
content = {
< div >
2026-02-02 10:19:55 +05:30
{ availabilityData . type && (
< p className = "font-semibold font-serif text-[14px] leading-[19px] text-[#00293D] mb-2" > { availabilityData . type } < / p >
) }
2026-01-20 12:17:47 +05:30
< div className = "space-y-1" >
2026-02-02 10:19:55 +05:30
{ availabilityData . schedule . length > 0 ? (
availabilityData . schedule . map ( ( item , index ) = > (
< p key = { index } className = "font-normal font-serif text-[14px] leading-[19px] text-[#00293D]" > { item } < / p >
) )
) : (
< p className = "font-normal font-serif text-[14px] leading-[19px] text-[#00293D]/60" > Not specified < / p >
) }
2026-01-20 12:17:47 +05:30
< / div >
< / div >
}
/ >
< InfoCard
title = ""
icon = {
< Image
src = "/assets/icons/work-environment-icon.svg"
alt = "Work environment"
width = { 28 }
height = { 28 }
/ >
}
2026-01-24 23:10:02 +05:30
content = { < p className = "font-serif font-semibold text-[14px] leading-[19px] text-[#00293D]" > { mockData . preferredWorkEnvironment } < / p > }
2026-01-20 12:17:47 +05:30
/ >
2026-03-05 05:36:10 +05:30
{ testimonials . length > 0 && (
< InfoCard
title = ""
icon = {
< Image
src = "/assets/icons/testimonial-star-icon.svg"
alt = "Testimonial"
width = { 28 }
height = { 28 }
/ >
}
content = { < p className = "text-[14px] font-semibold font-serif leading-[19px] text-center text-[#00293D]" > & ldquo ; { testimonials [ 0 ] . text . length > 150 ? testimonials [ 0 ] . text . substring ( 0 , 150 ) + '...' : testimonials [ 0 ] . text } & rdquo ; < / p > }
/ >
) }
2026-01-20 12:17:47 +05:30
< / div >
{ /* Specialization Section */ }
2026-01-25 01:15:02 +05:30
< SpecializationSection fieldsData = { specializationFieldsData } / >
2026-01-20 12:17:47 +05:30
{ /* Testimonials Section */ }
2026-03-05 05:36:10 +05:30
< TestimonialsSection testimonials = { testimonials } / >
2026-01-20 12:17:47 +05:30
< / div >
< / div >
2026-02-02 09:59:08 +05:30
{ /* Connect Request Modal */ }
< ConnectRequestModal
isOpen = { showConnectModal }
onClose = { ( ) = > setShowConnectModal ( false ) }
agentProfileId = { id }
agentName = { ` ${ agentProfile . firstName || '' } ${ agentProfile . lastName || '' } ` . trim ( ) || 'Agent' }
existingStatus = { connectionStatus }
onSuccess = { handleConnectionSuccess }
/ >
2026-01-20 12:17:47 +05:30
< / div >
) ;
}