feat: add personal tagline support and improve snake_case string formatting in profile data mapping
This commit is contained in:
@@ -21,7 +21,7 @@ import { connectionRequestsService } from '@/services/connection-requests.servic
|
|||||||
import { uploadService } from '@/services/upload.service';
|
import { uploadService } from '@/services/upload.service';
|
||||||
import { usersService } from '@/services/users.service';
|
import { usersService } from '@/services/users.service';
|
||||||
import { testimonialsService, Testimonial } from '@/services/testimonials.service';
|
import { testimonialsService, Testimonial } from '@/services/testimonials.service';
|
||||||
import { mapFieldValuesToExperience, mapFieldValuesToProfileCard, mapFieldValuesToAvailability, mapFieldValuesToSpecializationFields, mapFieldValuesToContactInfo, mapFieldValuesToWorkEnvironment, ExperienceData, ProfileCardData, AvailabilityData, SpecializationFieldsData, ContactInfoData, WorkEnvironmentData } from '@/utils/profileDataMapper';
|
import { mapFieldValuesToExperience, mapFieldValuesToProfileCard, mapFieldValuesToAvailability, mapFieldValuesToSpecializationFields, mapFieldValuesToContactInfo, mapFieldValuesToWorkEnvironment, mapFieldValuesToPersonalTagline, ExperienceData, ProfileCardData, AvailabilityData, SpecializationFieldsData, ContactInfoData, WorkEnvironmentData, PersonalTaglineData } from '@/utils/profileDataMapper';
|
||||||
|
|
||||||
// Default work environment data
|
// Default work environment data
|
||||||
const defaultWorkEnvironmentData: WorkEnvironmentData = {
|
const defaultWorkEnvironmentData: WorkEnvironmentData = {
|
||||||
@@ -29,6 +29,12 @@ const defaultWorkEnvironmentData: WorkEnvironmentData = {
|
|||||||
content: '',
|
content: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Default personal tagline data
|
||||||
|
const defaultPersonalTaglineData: PersonalTaglineData = {
|
||||||
|
label: 'Personal Tagline',
|
||||||
|
content: '',
|
||||||
|
};
|
||||||
|
|
||||||
// Default experience data when no field values are available
|
// Default experience data when no field values are available
|
||||||
const defaultExperience: ExperienceData = {
|
const defaultExperience: ExperienceData = {
|
||||||
years: '-',
|
years: '-',
|
||||||
@@ -82,6 +88,7 @@ export default function AgentDashboard() {
|
|||||||
const [profileCardData, setProfileCardData] = useState<ProfileCardData>(defaultProfileCardData);
|
const [profileCardData, setProfileCardData] = useState<ProfileCardData>(defaultProfileCardData);
|
||||||
const [availabilityData, setAvailabilityData] = useState<AvailabilityData>(defaultAvailabilityData);
|
const [availabilityData, setAvailabilityData] = useState<AvailabilityData>(defaultAvailabilityData);
|
||||||
const [workEnvironmentData, setWorkEnvironmentData] = useState<WorkEnvironmentData>(defaultWorkEnvironmentData);
|
const [workEnvironmentData, setWorkEnvironmentData] = useState<WorkEnvironmentData>(defaultWorkEnvironmentData);
|
||||||
|
const [personalTaglineData, setPersonalTaglineData] = useState<PersonalTaglineData>(defaultPersonalTaglineData);
|
||||||
const [specializationFieldsData, setSpecializationFieldsData] = useState<SpecializationFieldsData>(defaultSpecializationFieldsData);
|
const [specializationFieldsData, setSpecializationFieldsData] = useState<SpecializationFieldsData>(defaultSpecializationFieldsData);
|
||||||
const [contactInfoData, setContactInfoData] = useState<ContactInfoData>(defaultContactInfoData);
|
const [contactInfoData, setContactInfoData] = useState<ContactInfoData>(defaultContactInfoData);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
@@ -157,6 +164,10 @@ export default function AgentDashboard() {
|
|||||||
const mappedWorkEnv = mapFieldValuesToWorkEnvironment(fieldValuesResponse.fieldValues);
|
const mappedWorkEnv = mapFieldValuesToWorkEnvironment(fieldValuesResponse.fieldValues);
|
||||||
setWorkEnvironmentData(mappedWorkEnv);
|
setWorkEnvironmentData(mappedWorkEnv);
|
||||||
|
|
||||||
|
// Map field values to personal tagline data
|
||||||
|
const mappedTagline = mapFieldValuesToPersonalTagline(fieldValuesResponse.fieldValues);
|
||||||
|
setPersonalTaglineData(mappedTagline);
|
||||||
|
|
||||||
// Map field values to specialization fields data (only fields from "Specialization" section)
|
// Map field values to specialization fields data (only fields from "Specialization" section)
|
||||||
const mappedSpecializationFields = mapFieldValuesToSpecializationFields(fieldValuesResponse.fieldValues);
|
const mappedSpecializationFields = mapFieldValuesToSpecializationFields(fieldValuesResponse.fieldValues);
|
||||||
setSpecializationFieldsData(mappedSpecializationFields);
|
setSpecializationFieldsData(mappedSpecializationFields);
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import { agentsService, AgentProfile, FieldValueResponse } from '@/services/agen
|
|||||||
import { connectionRequestsService, ConnectionStatus } from '@/services/connection-requests.service';
|
import { connectionRequestsService, ConnectionStatus } from '@/services/connection-requests.service';
|
||||||
import { uploadService } from '@/services/upload.service';
|
import { uploadService } from '@/services/upload.service';
|
||||||
import { testimonialsService, Testimonial } from '@/services/testimonials.service';
|
import { testimonialsService, Testimonial } from '@/services/testimonials.service';
|
||||||
import { mapFieldValuesToExperience, mapFieldValuesToSpecializationFields, mapFieldValuesToProfileCard, mapFieldValuesToContactInfo, mapFieldValuesToAvailability, mapFieldValuesToWorkEnvironment, ExperienceData, SpecializationFieldsData, ProfileCardData, ContactInfoData, AvailabilityData, WorkEnvironmentData } from '@/utils/profileDataMapper';
|
import { mapFieldValuesToExperience, mapFieldValuesToSpecializationFields, mapFieldValuesToProfileCard, mapFieldValuesToContactInfo, mapFieldValuesToAvailability, mapFieldValuesToWorkEnvironment, mapFieldValuesToPersonalTagline, ExperienceData, SpecializationFieldsData, ProfileCardData, ContactInfoData, AvailabilityData, WorkEnvironmentData, PersonalTaglineData } from '@/utils/profileDataMapper';
|
||||||
|
|
||||||
// Default work environment data
|
// Default work environment data
|
||||||
const defaultWorkEnvironmentData: WorkEnvironmentData = {
|
const defaultWorkEnvironmentData: WorkEnvironmentData = {
|
||||||
@@ -29,6 +29,12 @@ const defaultWorkEnvironmentData: WorkEnvironmentData = {
|
|||||||
content: '',
|
content: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Default personal tagline data
|
||||||
|
const defaultPersonalTaglineData: PersonalTaglineData = {
|
||||||
|
label: 'Personal Tagline',
|
||||||
|
content: '',
|
||||||
|
};
|
||||||
|
|
||||||
// Default experience data when no field values are available
|
// Default experience data when no field values are available
|
||||||
const defaultExperience: ExperienceData = {
|
const defaultExperience: ExperienceData = {
|
||||||
years: '-',
|
years: '-',
|
||||||
@@ -87,6 +93,7 @@ export default function AgentProfileView() {
|
|||||||
const [contactInfoData, setContactInfoData] = useState<ContactInfoData>(defaultContactInfoData);
|
const [contactInfoData, setContactInfoData] = useState<ContactInfoData>(defaultContactInfoData);
|
||||||
const [availabilityData, setAvailabilityData] = useState<AvailabilityData>(defaultAvailabilityData);
|
const [availabilityData, setAvailabilityData] = useState<AvailabilityData>(defaultAvailabilityData);
|
||||||
const [workEnvironmentData, setWorkEnvironmentData] = useState<WorkEnvironmentData>(defaultWorkEnvironmentData);
|
const [workEnvironmentData, setWorkEnvironmentData] = useState<WorkEnvironmentData>(defaultWorkEnvironmentData);
|
||||||
|
const [personalTaglineData, setPersonalTaglineData] = useState<PersonalTaglineData>(defaultPersonalTaglineData);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [imageError, setImageError] = useState(false);
|
const [imageError, setImageError] = useState(false);
|
||||||
@@ -150,6 +157,10 @@ export default function AgentProfileView() {
|
|||||||
const mappedWorkEnv = mapFieldValuesToWorkEnvironment(fieldValuesResponse.fieldValues);
|
const mappedWorkEnv = mapFieldValuesToWorkEnvironment(fieldValuesResponse.fieldValues);
|
||||||
setWorkEnvironmentData(mappedWorkEnv);
|
setWorkEnvironmentData(mappedWorkEnv);
|
||||||
|
|
||||||
|
// Map field values to personal tagline data
|
||||||
|
const mappedTagline = mapFieldValuesToPersonalTagline(fieldValuesResponse.fieldValues);
|
||||||
|
setPersonalTaglineData(mappedTagline);
|
||||||
|
|
||||||
// Map testimonials for TestimonialsSection
|
// Map testimonials for TestimonialsSection
|
||||||
setTestimonials(
|
setTestimonials(
|
||||||
testimonialsData.map((t) => ({
|
testimonialsData.map((t) => ({
|
||||||
@@ -427,18 +438,18 @@ export default function AgentProfileView() {
|
|||||||
content={<p className="font-serif font-semibold text-[14px] leading-[19px] text-[#00293D]">{workEnvironmentData.content}</p>}
|
content={<p className="font-serif font-semibold text-[14px] leading-[19px] text-[#00293D]">{workEnvironmentData.content}</p>}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{testimonials.length > 0 && (
|
{personalTaglineData.content && (
|
||||||
<InfoCard
|
<InfoCard
|
||||||
title=""
|
title={personalTaglineData.label}
|
||||||
icon={
|
icon={
|
||||||
<Image
|
<Image
|
||||||
src="/assets/icons/testimonial-star-icon.svg"
|
src="/assets/icons/testimonial-star-icon.svg"
|
||||||
alt="Testimonial"
|
alt={personalTaglineData.label}
|
||||||
width={28}
|
width={28}
|
||||||
height={28}
|
height={28}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
content={<p className="text-[14px] font-semibold font-serif leading-[19px] text-center text-[#00293D]">“{testimonials[0].text.length > 150 ? testimonials[0].text.substring(0, 150) + '...' : testimonials[0].text}”</p>}
|
content={<p className="text-[14px] font-semibold font-serif leading-[19px] text-center text-[#00293D]">“{personalTaglineData.content.length > 150 ? personalTaglineData.content.substring(0, 150) + '...' : personalTaglineData.content}”</p>}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -17,12 +17,8 @@ export function ExperienceSection({ experience }: ExperienceSectionProps) {
|
|||||||
const [showAllExpertise, setShowAllExpertise] = useState(false);
|
const [showAllExpertise, setShowAllExpertise] = useState(false);
|
||||||
const [showAllCertifications, setShowAllCertifications] = useState(false);
|
const [showAllCertifications, setShowAllCertifications] = useState(false);
|
||||||
|
|
||||||
// Build agency display — append designation if flag is set
|
// Show agency block whenever name OR designation is filled
|
||||||
const agencyDisplay = experience.agencyName
|
const hasAgency = !!(experience.agencyName || experience.agencyDesignation);
|
||||||
? (experience.agencyShowDesignation && experience.agencyDesignation
|
|
||||||
? `${experience.agencyName} - ${experience.agencyDesignation}`
|
|
||||||
: experience.agencyName)
|
|
||||||
: '';
|
|
||||||
|
|
||||||
// Calculate how many more items are hidden
|
// Calculate how many more items are hidden
|
||||||
const licensingRemaining = experience.licensingAreas.length - INITIAL_DISPLAY_COUNT;
|
const licensingRemaining = experience.licensingAreas.length - INITIAL_DISPLAY_COUNT;
|
||||||
@@ -46,15 +42,20 @@ export function ExperienceSection({ experience }: ExperienceSectionProps) {
|
|||||||
<div className="flex flex-col lg:flex-row gap-4 lg:gap-8">
|
<div className="flex flex-col lg:flex-row gap-4 lg:gap-8">
|
||||||
{/* Left Column */}
|
{/* Left Column */}
|
||||||
<div className="flex-1 space-y-4">
|
<div className="flex-1 space-y-4">
|
||||||
{agencyDisplay && (
|
{hasAgency && (
|
||||||
<>
|
<>
|
||||||
<div>
|
<div>
|
||||||
<p className="font-fractul font-bold text-[14px] leading-[17px] text-[#00293D] mb-2">{experience.agencySectionLabel}</p>
|
<p className="font-fractul font-bold text-[14px] leading-[17px] text-[#00293D] mb-2">{experience.agencySectionLabel}</p>
|
||||||
<ul className="list-disc list-inside">
|
{experience.agencyName && (
|
||||||
<li className="font-serif font-medium text-[15px] leading-[21px] text-[#00293D]">
|
<p className="font-serif font-medium text-[15px] leading-[21px] text-[#00293D]">
|
||||||
{agencyDisplay}
|
{experience.agencyName}
|
||||||
</li>
|
</p>
|
||||||
</ul>
|
)}
|
||||||
|
{experience.agencyDesignation && (
|
||||||
|
<p className="font-serif font-normal text-[14px] leading-[21px] text-[#00293D]/70">
|
||||||
|
{experience.agencyDesignation}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{/* Horizontal divider */}
|
{/* Horizontal divider */}
|
||||||
<div className="w-full h-0 border-t-[0.5px] border-solid border-[#00293D]/20" />
|
<div className="w-full h-0 border-t-[0.5px] border-solid border-[#00293D]/20" />
|
||||||
|
|||||||
@@ -239,6 +239,23 @@ export function mapFieldValuesToWorkEnvironment(fieldValues: FieldValueResponse[
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Personal tagline data structure
|
||||||
|
export interface PersonalTaglineData {
|
||||||
|
label: string;
|
||||||
|
content: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps field values to personal tagline data (label + content)
|
||||||
|
*/
|
||||||
|
export function mapFieldValuesToPersonalTagline(fieldValues: FieldValueResponse[]): PersonalTaglineData {
|
||||||
|
const field = fieldValues.find(f => f.fieldSlug === 'personal_tagline');
|
||||||
|
return {
|
||||||
|
label: field?.fieldName || 'Personal Tagline',
|
||||||
|
content: (field?.value as string | undefined)?.trim() || '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Type-determining values (business hours → Full-time, etc.)
|
// Type-determining values (business hours → Full-time, etc.)
|
||||||
const fullTimeKeywords = ['full_time', 'mf_9_5', 'mf_8_6', 'full time', '9-5', '9 am', '9am'];
|
const fullTimeKeywords = ['full_time', 'mf_9_5', 'mf_8_6', 'full time', '9-5', '9 am', '9am'];
|
||||||
const partTimeKeywords = ['part_time', 'part time'];
|
const partTimeKeywords = ['part_time', 'part time'];
|
||||||
@@ -264,13 +281,23 @@ export function mapFieldValuesToAvailability(fieldValues: FieldValueResponse[]):
|
|||||||
return { type: '', schedule: [], label };
|
return { type: '', schedule: [], label };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper to format snake_case strings to Title Case (for unresolved raw values)
|
||||||
|
const formatIfSnakeCase = (s: string): string => {
|
||||||
|
const trimmed = s.trim();
|
||||||
|
// If it looks like a snake_case key (e.g. "mf_9_5"), format it
|
||||||
|
if (/^[a-z0-9]+(_[a-z0-9]+)+$/i.test(trimmed)) {
|
||||||
|
return trimmed.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
|
||||||
|
}
|
||||||
|
return trimmed;
|
||||||
|
};
|
||||||
|
|
||||||
// Prefer backend-resolved labels, fall back to raw value with snake_case formatting
|
// Prefer backend-resolved labels, fall back to raw value with snake_case formatting
|
||||||
const resolvedLabels = rawValues.map((val, i) => {
|
const resolvedLabels = rawValues.map((val, i) => {
|
||||||
const fromBackend = labelValues[i];
|
const fromBackend = labelValues[i];
|
||||||
if (fromBackend && typeof fromBackend === 'string' && fromBackend.trim()) {
|
if (fromBackend && typeof fromBackend === 'string' && fromBackend.trim()) {
|
||||||
return fromBackend.trim();
|
return formatIfSnakeCase(fromBackend);
|
||||||
}
|
}
|
||||||
return val.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
|
return formatIfSnakeCase(val);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Deduplicate by normalized comparison (case/whitespace insensitive)
|
// Deduplicate by normalized comparison (case/whitespace insensitive)
|
||||||
|
|||||||
Reference in New Issue
Block a user