fix
This commit is contained in:
@@ -17,7 +17,7 @@ import {
|
||||
} from '@/components/profile';
|
||||
import { agentsService, AgentProfile, FieldValueResponse } from '@/services/agents.service';
|
||||
import { uploadService } from '@/services/upload.service';
|
||||
import { mapFieldValuesToExperience, mapFieldValuesToProfileCard, mapFieldValuesToAvailability, ExperienceData, ProfileCardData, AvailabilityData } from '@/utils/profileDataMapper';
|
||||
import { mapFieldValuesToExperience, mapFieldValuesToProfileCard, mapFieldValuesToAvailability, mapFieldValuesToSpecializationFields, ExperienceData, ProfileCardData, AvailabilityData, SpecializationFieldsData } from '@/utils/profileDataMapper';
|
||||
|
||||
// Mock data for sections not yet available from API
|
||||
const mockData = {
|
||||
@@ -42,13 +42,6 @@ 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',
|
||||
testimonialHighlight: "The most amazing experience I've had as a real estate professional is helping a family secure their dream home and seeing their happiness when they received the keys.",
|
||||
specialization: {
|
||||
types: ['Buyers', 'Sellers', 'First time Buyers', 'First time Sellers', 'Divorce'],
|
||||
loanTypes: ['Conventional', 'FHA', 'VA', 'USDA'],
|
||||
propertyTypes: ['SFR', 'Town home', 'Rural', 'Luxury', 'Condo'],
|
||||
hobbies: ['Boating', 'Horses', 'RV', 'Automotive', 'Aviation'],
|
||||
pricePoints: ['Under $100K', '$100K – $300K', '$300K – $500K', '$500K – $1M', '$1M and above'],
|
||||
},
|
||||
testimonials: [
|
||||
{
|
||||
id: 1,
|
||||
@@ -96,6 +89,11 @@ const defaultAvailabilityData: AvailabilityData = {
|
||||
schedule: [],
|
||||
};
|
||||
|
||||
// Default specialization fields data when no field values are available
|
||||
const defaultSpecializationFieldsData: SpecializationFieldsData = {
|
||||
fields: [],
|
||||
};
|
||||
|
||||
export default function AgentDashboard() {
|
||||
const { data: session } = useSession();
|
||||
const [agentProfile, setAgentProfile] = useState<AgentProfile | null>(null);
|
||||
@@ -103,6 +101,7 @@ export default function AgentDashboard() {
|
||||
const [experienceData, setExperienceData] = useState<ExperienceData>(defaultExperience);
|
||||
const [profileCardData, setProfileCardData] = useState<ProfileCardData>(defaultProfileCardData);
|
||||
const [availabilityData, setAvailabilityData] = useState<AvailabilityData>(defaultAvailabilityData);
|
||||
const [specializationFieldsData, setSpecializationFieldsData] = useState<SpecializationFieldsData>(defaultSpecializationFieldsData);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [isPhotoModalOpen, setIsPhotoModalOpen] = useState(false);
|
||||
@@ -146,6 +145,10 @@ export default function AgentDashboard() {
|
||||
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);
|
||||
|
||||
// If avatar is an S3 key, fetch presigned URL
|
||||
if (profile.avatar && isS3Key(profile.avatar)) {
|
||||
try {
|
||||
@@ -382,7 +385,7 @@ export default function AgentDashboard() {
|
||||
</div>
|
||||
|
||||
{/* Specialization Section */}
|
||||
<SpecializationSection specialization={mockData.specialization} />
|
||||
<SpecializationSection fieldsData={specializationFieldsData} />
|
||||
|
||||
{/* Testimonials Section */}
|
||||
<TestimonialsSection testimonials={mockData.testimonials} />
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
} from '@/components/profile';
|
||||
import { agentsService, AgentProfile, FieldValueResponse } from '@/services/agents.service';
|
||||
import { getProxyImageUrl } from '@/lib/imageProxy';
|
||||
import { mapFieldValuesToExperience, ExperienceData } from '@/utils/profileDataMapper';
|
||||
import { mapFieldValuesToExperience, mapFieldValuesToSpecializationFields, ExperienceData, SpecializationFieldsData } from '@/utils/profileDataMapper';
|
||||
|
||||
// Mock data for sections not yet available from API
|
||||
const mockData = {
|
||||
@@ -26,13 +26,6 @@ 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',
|
||||
testimonialHighlight: "The most amazing experience I've had as a real estate professional is helping a family secure their dream home and seeing their happiness when they received the keys.",
|
||||
specialization: {
|
||||
types: ['Buyers', 'Sellers', 'First time Buyers', 'First time Sellers', 'Divorce'],
|
||||
loanTypes: ['Conventional', 'FHA', 'VA', 'USDA'],
|
||||
propertyTypes: ['SFR', 'Town home', 'Rural', 'Luxury', 'Condo'],
|
||||
hobbies: ['Boating', 'Horses', 'RV', 'Automotive', 'Aviation'],
|
||||
pricePoints: ['Under $100K', '$100K – $300K', '$300K – $500K', '$500K – $1M', '$1M and above'],
|
||||
},
|
||||
testimonials: [
|
||||
{
|
||||
id: 1,
|
||||
@@ -67,6 +60,11 @@ const defaultExperience: ExperienceData = {
|
||||
certifications: [],
|
||||
};
|
||||
|
||||
// Default specialization fields data when no field values are available
|
||||
const defaultSpecializationFieldsData: SpecializationFieldsData = {
|
||||
fields: [],
|
||||
};
|
||||
|
||||
export default function AgentProfileView() {
|
||||
const params = useParams();
|
||||
const id = params.id as string;
|
||||
@@ -74,6 +72,7 @@ export default function AgentProfileView() {
|
||||
const [agentProfile, setAgentProfile] = useState<AgentProfile | null>(null);
|
||||
const [fieldValues, setFieldValues] = useState<FieldValueResponse[]>([]);
|
||||
const [experienceData, setExperienceData] = useState<ExperienceData>(defaultExperience);
|
||||
const [specializationFieldsData, setSpecializationFieldsData] = useState<SpecializationFieldsData>(defaultSpecializationFieldsData);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [imageError, setImageError] = useState(false);
|
||||
@@ -101,6 +100,10 @@ export default function AgentProfileView() {
|
||||
// Map field values to experience data structure
|
||||
const mappedExperience = mapFieldValuesToExperience(fieldValuesResponse.fieldValues);
|
||||
setExperienceData(mappedExperience);
|
||||
|
||||
// Map field values to specialization fields data (only fields from "Specialization" section)
|
||||
const mappedSpecializationFields = mapFieldValuesToSpecializationFields(fieldValuesResponse.fieldValues);
|
||||
setSpecializationFieldsData(mappedSpecializationFields);
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch profile:', err);
|
||||
setError('Failed to load profile data');
|
||||
@@ -284,7 +287,7 @@ export default function AgentProfileView() {
|
||||
</div>
|
||||
|
||||
{/* Specialization Section */}
|
||||
<SpecializationSection specialization={mockData.specialization} />
|
||||
<SpecializationSection fieldsData={specializationFieldsData} />
|
||||
|
||||
{/* Testimonials Section */}
|
||||
<TestimonialsSection testimonials={mockData.testimonials} />
|
||||
|
||||
@@ -2,90 +2,80 @@
|
||||
|
||||
import Image from 'next/image';
|
||||
import { SpecializationCard } from './SpecializationCard';
|
||||
|
||||
interface SpecializationData {
|
||||
types: string[];
|
||||
loanTypes: string[];
|
||||
propertyTypes: string[];
|
||||
hobbies: string[];
|
||||
pricePoints: string[];
|
||||
}
|
||||
import { SpecializationFieldsData } from '@/utils/profileDataMapper';
|
||||
|
||||
interface SpecializationSectionProps {
|
||||
specialization: SpecializationData;
|
||||
fieldsData: SpecializationFieldsData;
|
||||
}
|
||||
|
||||
export function SpecializationSection({ specialization }: SpecializationSectionProps) {
|
||||
export function SpecializationSection({ fieldsData }: SpecializationSectionProps) {
|
||||
const { fields } = fieldsData;
|
||||
|
||||
// Don't render if no fields with data
|
||||
if (!fields || fields.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Split fields for layout: first 3 in top row, rest in bottom row
|
||||
const topRowFields = fields.slice(0, 3);
|
||||
const bottomRowFields = fields.slice(3);
|
||||
|
||||
return (
|
||||
<div className="bg-[#e8e8e8] rounded-[20px] p-8">
|
||||
<div className="text-center mb-8">
|
||||
<h2 className="text-[20px] font-bold text-[#00293D] font-fractul leading-[24px]">Specialization</h2>
|
||||
<p className="text-[14px] font-medium text-[#00293D] font-fractul leading-[17px]">Area Of Expertise and Focus</p>
|
||||
</div>
|
||||
<div className="flex flex-col lg:grid lg:grid-cols-3 gap-6 mb-6">
|
||||
<SpecializationCard
|
||||
title="Specialization"
|
||||
items={specialization.types}
|
||||
icon={
|
||||
<Image
|
||||
src="/assets/icons/home-icon.svg"
|
||||
alt="Specialization"
|
||||
width={24}
|
||||
height={24}
|
||||
|
||||
{/* Top row - up to 3 cards */}
|
||||
{topRowFields.length > 0 && (
|
||||
<div className={`flex flex-col lg:grid gap-6 mb-6 ${
|
||||
topRowFields.length === 1 ? 'lg:grid-cols-1 lg:max-w-md lg:mx-auto' :
|
||||
topRowFields.length === 2 ? 'lg:grid-cols-2 lg:max-w-2xl lg:mx-auto' :
|
||||
'lg:grid-cols-3'
|
||||
}`}>
|
||||
{topRowFields.map((field) => (
|
||||
<SpecializationCard
|
||||
key={field.fieldSlug}
|
||||
title={field.fieldName}
|
||||
items={field.values}
|
||||
icon={
|
||||
<Image
|
||||
src={field.icon || '/assets/icons/home-icon.svg'}
|
||||
alt={field.fieldName}
|
||||
width={24}
|
||||
height={24}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<SpecializationCard
|
||||
title="Loan Type"
|
||||
items={specialization.loanTypes}
|
||||
icon={
|
||||
<Image
|
||||
src="/assets/icons/wallet-icon.svg"
|
||||
alt="Loan Type"
|
||||
width={24}
|
||||
height={24}
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Bottom row - remaining cards */}
|
||||
{bottomRowFields.length > 0 && (
|
||||
<div className={`flex flex-col lg:grid gap-6 ${
|
||||
bottomRowFields.length === 1 ? 'lg:grid-cols-1 lg:max-w-md lg:mx-auto' :
|
||||
bottomRowFields.length === 2 ? 'lg:grid-cols-2 lg:max-w-2xl lg:mx-auto' :
|
||||
'lg:grid-cols-3'
|
||||
}`}>
|
||||
{bottomRowFields.map((field) => (
|
||||
<SpecializationCard
|
||||
key={field.fieldSlug}
|
||||
title={field.fieldName}
|
||||
items={field.values}
|
||||
icon={
|
||||
<Image
|
||||
src={field.icon || '/assets/icons/home-icon.svg'}
|
||||
alt={field.fieldName}
|
||||
width={24}
|
||||
height={24}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<SpecializationCard
|
||||
title="Property Type"
|
||||
items={specialization.propertyTypes}
|
||||
icon={
|
||||
<Image
|
||||
src="/assets/icons/chart-icon.svg"
|
||||
alt="Property Type"
|
||||
width={24}
|
||||
height={24}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col lg:grid lg:grid-cols-2 gap-6 lg:max-w-2xl lg:mx-auto">
|
||||
<SpecializationCard
|
||||
title="Hobbies & Interests"
|
||||
items={specialization.hobbies}
|
||||
icon={
|
||||
<Image
|
||||
src="/assets/icons/heart-icon.svg"
|
||||
alt="Hobbies"
|
||||
width={24}
|
||||
height={24}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<SpecializationCard
|
||||
title="Price Point"
|
||||
items={specialization.pricePoints}
|
||||
icon={
|
||||
<Image
|
||||
src="/assets/icons/wallet-icon.svg"
|
||||
alt="Price Point"
|
||||
width={24}
|
||||
height={24}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -132,6 +132,19 @@ export interface ProfileCardData {
|
||||
serviceAreas: string[];
|
||||
}
|
||||
|
||||
// Dynamic field data structure for SpecializationSection component
|
||||
// Each field within the "Specialization" section becomes a card
|
||||
export interface SpecializationFieldItem {
|
||||
fieldSlug: string;
|
||||
fieldName: string;
|
||||
icon?: string;
|
||||
values: string[];
|
||||
}
|
||||
|
||||
export interface SpecializationFieldsData {
|
||||
fields: SpecializationFieldItem[];
|
||||
}
|
||||
|
||||
// Availability data structure
|
||||
export interface AvailabilityData {
|
||||
type: string;
|
||||
@@ -219,3 +232,102 @@ export function mapFieldValuesToProfileCard(fieldValues: FieldValueResponse[]):
|
||||
serviceAreas: serviceAreas || licensedAreas || coverageAreas || [],
|
||||
};
|
||||
}
|
||||
|
||||
// Field slug to icon mapping for specialization fields
|
||||
const fieldIconMapping: Record<string, string> = {
|
||||
'client_specialization': '/assets/icons/home-icon.svg',
|
||||
'property_type': '/assets/icons/chart-icon.svg',
|
||||
'loan_type': '/assets/icons/wallet-icon.svg',
|
||||
'areas_expertise_years': '/assets/icons/certification-icon.svg',
|
||||
'licensing_areas': '/assets/icons/location-icon.svg',
|
||||
'hobbies': '/assets/icons/heart-icon.svg',
|
||||
'hobbies_interests': '/assets/icons/heart-icon.svg',
|
||||
'price_points': '/assets/icons/wallet-icon.svg',
|
||||
'price_point': '/assets/icons/wallet-icon.svg',
|
||||
'default': '/assets/icons/home-icon.svg',
|
||||
};
|
||||
|
||||
/**
|
||||
* Get icon for a field based on its slug
|
||||
*/
|
||||
function getFieldIcon(fieldSlug: string): string {
|
||||
const normalizedSlug = fieldSlug.toLowerCase();
|
||||
|
||||
// Check for exact match first
|
||||
if (fieldIconMapping[normalizedSlug]) {
|
||||
return fieldIconMapping[normalizedSlug];
|
||||
}
|
||||
|
||||
// Check for partial matches
|
||||
if (normalizedSlug.includes('loan')) return fieldIconMapping['loan_type'];
|
||||
if (normalizedSlug.includes('property')) return fieldIconMapping['property_type'];
|
||||
if (normalizedSlug.includes('hobby') || normalizedSlug.includes('interest')) return fieldIconMapping['hobbies'];
|
||||
if (normalizedSlug.includes('price')) return fieldIconMapping['price_points'];
|
||||
if (normalizedSlug.includes('client') || normalizedSlug.includes('special')) return fieldIconMapping['client_specialization'];
|
||||
if (normalizedSlug.includes('license') || normalizedSlug.includes('area')) return fieldIconMapping['licensing_areas'];
|
||||
if (normalizedSlug.includes('expertise') || normalizedSlug.includes('certification')) return fieldIconMapping['areas_expertise_years'];
|
||||
|
||||
return fieldIconMapping['default'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert field value to string array for display
|
||||
*/
|
||||
function valueToStringArray(value: unknown): string[] {
|
||||
if (!value) return [];
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(v => String(v)).filter(v => v.trim() !== '');
|
||||
}
|
||||
|
||||
if (typeof value === 'string' && value.trim()) {
|
||||
return [value];
|
||||
}
|
||||
|
||||
if (typeof value === 'number' || typeof value === 'boolean') {
|
||||
return [String(value)];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps field values from the "Specialization" section to individual field cards
|
||||
* Each field within the Specialization section becomes its own card
|
||||
*/
|
||||
export function mapFieldValuesToSpecializationFields(fieldValues: FieldValueResponse[]): SpecializationFieldsData {
|
||||
// Filter only fields from the "Specialization" section
|
||||
const specializationFields = fieldValues.filter(field => {
|
||||
const sectionSlug = field.sectionSlug?.toLowerCase() || '';
|
||||
return sectionSlug === 'specialization' || sectionSlug.includes('specialization');
|
||||
});
|
||||
|
||||
// Create a card for each field
|
||||
const fields: SpecializationFieldItem[] = [];
|
||||
|
||||
for (const field of specializationFields) {
|
||||
const { fieldSlug, fieldName, value } = field;
|
||||
|
||||
// Skip if no field info or no value
|
||||
if (!fieldSlug || !fieldName) continue;
|
||||
|
||||
const values = valueToStringArray(value);
|
||||
if (values.length === 0) continue;
|
||||
|
||||
fields.push({
|
||||
fieldSlug,
|
||||
fieldName,
|
||||
icon: getFieldIcon(fieldSlug),
|
||||
values,
|
||||
});
|
||||
}
|
||||
|
||||
return { fields };
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if specialization fields data has any meaningful content
|
||||
*/
|
||||
export function hasSpecializationFieldsData(data: SpecializationFieldsData): boolean {
|
||||
return data.fields.length > 0 && data.fields.some(f => f.values.length > 0);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user