This commit is contained in:
pradeepkumar
2026-01-24 22:19:30 +05:30
parent 7d6ef356e2
commit a0559a17f1
6 changed files with 454 additions and 40 deletions

View File

@@ -1,5 +1,6 @@
'use client';
import { useState, useEffect } from 'react';
import { useSession } from 'next-auth/react';
import Image from 'next/image';
@@ -12,20 +13,13 @@ import {
TestimonialsSection,
StatusButtons,
ContactInfo,
PhotoUploadModal,
} from '@/components/profile';
import { agentsService, AgentProfile } from '@/services/agents.service';
import { uploadService } from '@/services/upload.service';
// Mock agent data - in production, this would come from API
const agentData = {
name: 'Brian Neeland',
isVerified: true,
title: 'Licensed Real Estate Agent',
location: 'Colorado',
memberSince: 'March 2020',
bio: 'Brian brings eight years of hands-on experience helping clients confidently buy, sell, and invest in real estate. He combines strong analytical skills with deep market knowledge to identify the right opportunities and guide clients through every step of the process. With a data-driven approach and clear communication, Brian ensures smooth transactions and informed decisions tailored to each client\'s goals.',
expertise: ['Buyers', 'Sellers', 'Divorce', 'Luxury', 'Retirement', 'Historical', 'condo', 'Rural', 'FHA', 'Conventional', 'USDA', 'Retirement'],
email: 'brian@gmail.com',
phone: '+91*******7493',
profileImage: '/assets/demo_images/8f017153a7b4a239a4f0691234ef97dd55092282.jpg',
// Mock data for sections not yet available from API
const mockData = {
experience: {
years: '10+ years',
contracts: '10+ Contracts',
@@ -81,7 +75,106 @@ const agentData = {
export default function AgentDashboard() {
const { data: session } = useSession();
const [firstName, lastName] = agentData.name.split(' ');
const [agentProfile, setAgentProfile] = useState<AgentProfile | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [isPhotoModalOpen, setIsPhotoModalOpen] = useState(false);
const [imageError, setImageError] = useState(false);
const defaultImage = '/assets/demo_images/8f017153a7b4a239a4f0691234ef97dd55092282.jpg';
// Fetch agent profile on mount
useEffect(() => {
const fetchProfile = async () => {
try {
setLoading(true);
setError(null);
const profile = await agentsService.getMyProfile();
setAgentProfile(profile);
} catch (err) {
console.error('Failed to fetch profile:', err);
setError('Failed to load profile data');
} finally {
setLoading(false);
}
};
fetchProfile();
}, []);
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 URL
const updatedProfile = await agentsService.updateProfile({ avatar: uploadedFile.url });
setImageError(false); // Reset error state for new image
setAgentProfile(updatedProfile);
};
const getProfileImageUrl = () => {
// If image failed to load, return default
if (imageError) {
return defaultImage;
}
// Check for null, undefined, or empty string
if (!agentProfile?.avatar || agentProfile.avatar.trim() === '') {
return defaultImage;
}
// S3 URLs are full URLs starting with http
if (agentProfile.avatar.startsWith('http')) {
return agentProfile.avatar;
}
// For relative paths (local assets), return as-is
return agentProfile.avatar;
};
if (loading) {
return (
<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>
);
}
if (error || !agentProfile) {
return (
<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>
);
}
// 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 (
<div className="space-y-6">
@@ -92,18 +185,24 @@ export default function AgentDashboard() {
{/* Profile Image */}
<div className="relative w-[200px] lg:w-[260px]">
<div className="w-[200px] h-[200px] lg:w-[260px] lg:h-[260px] rounded-[15px] overflow-hidden">
<Image
src={agentData.profileImage}
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={getProfileImageUrl()}
alt="Profile"
width={260}
height={260}
className="w-full h-full object-cover"
onError={(e) => {
const target = e.target as HTMLImageElement;
target.src = defaultImage;
}}
/>
{/* Gradient Overlay */}
<div className="absolute inset-0 bg-gradient-to-t from-black/50 via-black/20 to-transparent pointer-events-none" />
</div>
{/* Edit Icon - Top Right Outside */}
<button className="absolute -top-3 -right-3 w-9 h-9 bg-white rounded-[10px] shadow-md flex items-center justify-center hover:bg-gray-50 transition-colors">
<button
onClick={() => setIsPhotoModalOpen(true)}
className="absolute -top-3 -right-3 w-9 h-9 bg-white rounded-[10px] shadow-md flex items-center justify-center hover:bg-gray-50 transition-colors"
>
<Image
src="/assets/icons/edit-icon.svg"
alt="Edit profile image"
@@ -117,21 +216,24 @@ export default function AgentDashboard() {
<StatusButtons />
{/* Contact Info */}
<ContactInfo email={agentData.email} phone={agentData.phone} />
<ContactInfo
email={agentProfile.email || agentProfile.user?.email || ''}
phone={agentProfile.phone || ''}
/>
</div>
{/* Right Content - Profile Info + Experience + All Sections */}
<div className="flex-1 space-y-4">
{/* Profile Card */}
<ProfileCard
firstName={firstName}
lastName={lastName}
isVerified={agentData.isVerified}
title={agentData.title}
location={agentData.location}
memberSince={agentData.memberSince}
bio={agentData.bio}
expertise={agentData.expertise}
firstName={agentProfile.firstName}
lastName={agentProfile.lastName}
isVerified={agentProfile.isVerified}
title={agentProfile.agentType?.name || 'Real Estate Agent'}
location={agentProfile.serviceAreas?.[0] || 'United States'}
memberSince={formatMemberSince((agentProfile as unknown as { createdAt?: string }).createdAt)}
bio={agentProfile.bio || ''}
expertise={agentProfile.specializations || []}
showEditButton={true}
editHref="/agent/edit"
messageHref="/agent/message"
@@ -139,7 +241,7 @@ export default function AgentDashboard() {
/>
{/* Experience Section */}
<ExperienceSection experience={agentData.experience} />
<ExperienceSection experience={mockData.experience} />
{/* Info Cards Section */}
<div className="flex flex-col lg:grid lg:grid-cols-3 gap-6">
@@ -155,9 +257,9 @@ export default function AgentDashboard() {
}
content={
<div>
<p className="font-semibold font-serif text-[14px] leading-[19px] text-[#00293D] mb-2">{agentData.availability.type}</p>
<p className="font-semibold font-serif text-[14px] leading-[19px] text-[#00293D] mb-2">{mockData.availability.type}</p>
<div className="space-y-1">
{agentData.availability.days.map((day) => (
{mockData.availability.days.map((day) => (
<p key={day} className="font-normal font-serif text-[14px] leading-[19px] text-[#00293D]">{day}</p>
))}
</div>
@@ -174,7 +276,7 @@ export default function AgentDashboard() {
height={28}
/>
}
content={<p className="font-serif font-semibold text-[14px] leading-[19px] text-[#00293D]">{agentData.preferredWorkEnvironment}</p>}
content={<p className="font-serif font-semibold text-[14px] leading-[19px] text-[#00293D]">{mockData.preferredWorkEnvironment}</p>}
/>
<InfoCard
title=""
@@ -186,17 +288,25 @@ export default function AgentDashboard() {
height={28}
/>
}
content={<p className="text-[14px] font-semibold font-serif leading-[19px] text-center text-[#00293D]">&ldquo;{agentData.testimonialHighlight}&rdquo;</p>}
content={<p className="text-[14px] font-semibold font-serif leading-[19px] text-center text-[#00293D]">&ldquo;{mockData.testimonialHighlight}&rdquo;</p>}
/>
</div>
{/* Specialization Section */}
<SpecializationSection specialization={agentData.specialization} />
<SpecializationSection specialization={mockData.specialization} />
{/* Testimonials Section */}
<TestimonialsSection testimonials={agentData.testimonials} />
<TestimonialsSection testimonials={mockData.testimonials} />
</div>
</div>
{/* Photo Upload Modal */}
<PhotoUploadModal
isOpen={isPhotoModalOpen}
onClose={() => setIsPhotoModalOpen(false)}
onUpload={handlePhotoUpload}
currentPhoto={agentProfile.avatar}
/>
</div>
);
}