s3 fix
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import Image from 'next/image';
|
||||
import { useParams } from 'next/navigation';
|
||||
|
||||
@@ -13,35 +14,12 @@ import {
|
||||
StatusButtons,
|
||||
ContactInfo,
|
||||
} from '@/components/profile';
|
||||
import { agentsService, AgentProfile, FieldValueResponse } from '@/services/agents.service';
|
||||
import { getProxyImageUrl } from '@/lib/imageProxy';
|
||||
import { mapFieldValuesToExperience, ExperienceData } from '@/utils/profileDataMapper';
|
||||
|
||||
// Mock agent data - in production, this would come from API based on id
|
||||
const getAgentData = (id: string) => ({
|
||||
id,
|
||||
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',
|
||||
experience: {
|
||||
years: '10+ years',
|
||||
contracts: '10+ Contracts',
|
||||
licensingAreas: ['Colorado Springs', 'Monument', 'Falcon', 'Castle Rock', 'Fountain', 'Momentum'],
|
||||
expertiseYears: [
|
||||
{ area: 'Colorado', years: '10 yrs' },
|
||||
{ area: 'Falcon', years: '10 yrs' },
|
||||
{ area: 'Colorado', years: '10 yrs' },
|
||||
{ area: 'Colorado', years: '10 yrs' },
|
||||
],
|
||||
certifications: [
|
||||
{ name: 'Certified Residential Specialist (CRS)', org: 'RESIDENTIAL REAL ESTATE COUNCIL' },
|
||||
{ name: 'Certified Residential Specialist (CRS)', org: 'RESIDENTIAL REAL ESTATE COUNCIL' },
|
||||
],
|
||||
},
|
||||
// Mock data for sections not yet available from API
|
||||
const mockData = {
|
||||
availability: {
|
||||
type: 'Full-time',
|
||||
days: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
|
||||
@@ -78,15 +56,129 @@ const getAgentData = (id: string) => ({
|
||||
rating: 5,
|
||||
},
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
// Default experience data when no field values are available
|
||||
const defaultExperience: ExperienceData = {
|
||||
years: '-',
|
||||
contracts: '-',
|
||||
licensingAreas: [],
|
||||
expertiseYears: [],
|
||||
certifications: [],
|
||||
};
|
||||
|
||||
export default function AgentProfileView() {
|
||||
const params = useParams();
|
||||
const id = params.id as string;
|
||||
|
||||
// In production, fetch agent data based on id
|
||||
const agentData = getAgentData(id);
|
||||
const [firstName, lastName] = agentData.name.split(' ');
|
||||
const [agentProfile, setAgentProfile] = useState<AgentProfile | null>(null);
|
||||
const [fieldValues, setFieldValues] = useState<FieldValueResponse[]>([]);
|
||||
const [experienceData, setExperienceData] = useState<ExperienceData>(defaultExperience);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [imageError, setImageError] = useState(false);
|
||||
|
||||
const defaultImage = '/assets/demo_images/8f017153a7b4a239a4f0691234ef97dd55092282.jpg';
|
||||
|
||||
// Fetch agent profile and field values on mount
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
if (!id) return;
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
// Fetch profile and field values in parallel
|
||||
const [profile, fieldValuesResponse] = await Promise.all([
|
||||
agentsService.getAgentById(id),
|
||||
agentsService.getFieldValuesByAgentId(id),
|
||||
]);
|
||||
|
||||
setAgentProfile(profile);
|
||||
setFieldValues(fieldValuesResponse.fieldValues);
|
||||
|
||||
// Map field values to experience data structure
|
||||
const mappedExperience = mapFieldValuesToExperience(fieldValuesResponse.fieldValues);
|
||||
setExperienceData(mappedExperience);
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch profile:', err);
|
||||
setError('Failed to load profile data');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchData();
|
||||
}, [id]);
|
||||
|
||||
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 need to go through the backend proxy
|
||||
if (agentProfile.avatar.startsWith('http')) {
|
||||
return getProxyImageUrl(agentProfile.avatar);
|
||||
}
|
||||
|
||||
// For relative paths (local assets), return as-is
|
||||
return agentProfile.avatar;
|
||||
};
|
||||
|
||||
// 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>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="max-w-7xl mx-auto px-4 lg:px-8 py-6 space-y-6">
|
||||
@@ -97,12 +189,16 @@ export default function AgentProfileView() {
|
||||
{/* 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;
|
||||
setImageError(true);
|
||||
}}
|
||||
/>
|
||||
{/* Gradient Overlay */}
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/50 via-black/20 to-transparent pointer-events-none" />
|
||||
@@ -113,27 +209,30 @@ export default function AgentProfileView() {
|
||||
<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 - No edit button for user view */}
|
||||
<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={false}
|
||||
messageHref="/user/message"
|
||||
/>
|
||||
|
||||
{/* Experience Section */}
|
||||
<ExperienceSection experience={agentData.experience} />
|
||||
{/* Experience Section - Dynamic data from profile fields */}
|
||||
<ExperienceSection experience={experienceData} />
|
||||
|
||||
{/* Info Cards Section */}
|
||||
<div className="flex flex-col lg:grid lg:grid-cols-3 gap-6">
|
||||
@@ -149,9 +248,9 @@ export default function AgentProfileView() {
|
||||
}
|
||||
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>
|
||||
@@ -168,7 +267,7 @@ export default function AgentProfileView() {
|
||||
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=""
|
||||
@@ -180,15 +279,15 @@ export default function AgentProfileView() {
|
||||
height={28}
|
||||
/>
|
||||
}
|
||||
content={<p className="text-[14px] font-semibold font-serif leading-[19px] text-center text-[#00293D]">“{agentData.testimonialHighlight}”</p>}
|
||||
content={<p className="text-[14px] font-semibold font-serif leading-[19px] text-center text-[#00293D]">“{mockData.testimonialHighlight}”</p>}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Specialization Section */}
|
||||
<SpecializationSection specialization={agentData.specialization} />
|
||||
<SpecializationSection specialization={mockData.specialization} />
|
||||
|
||||
{/* Testimonials Section */}
|
||||
<TestimonialsSection testimonials={agentData.testimonials} />
|
||||
<TestimonialsSection testimonials={mockData.testimonials} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user