feat: Implement dynamic agent profile editing by introducing new services and dynamic UI components.
This commit is contained in:
140
src/app/(agent)/agent/edit/components/DynamicSection.tsx
Normal file
140
src/app/(agent)/agent/edit/components/DynamicSection.tsx
Normal file
@@ -0,0 +1,140 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import Image from 'next/image';
|
||||
import { ProfileSection } from '@/services/profile-sections.service';
|
||||
import DynamicField from './DynamicField';
|
||||
|
||||
// Map section slugs to icon paths
|
||||
const sectionIcons: Record<string, string> = {
|
||||
'basic-information': '/assets/icons/basic-information-icon.svg',
|
||||
'contact-information': '/assets/icons/email-orange-icon.svg',
|
||||
'location': '/assets/icons/location-orange-icon.svg',
|
||||
'professional-info': '/assets/icons/certification-icon.svg',
|
||||
'specialization': '/assets/icons/home-icon.svg',
|
||||
'experience': '/assets/icons/star-icon.svg',
|
||||
'availability': '/assets/icons/availability-clock-icon.svg',
|
||||
'biography': '/assets/icons/bio-icon.svg',
|
||||
'attached-documents': '/assets/icons/attachment-icon.svg',
|
||||
'hobbies': '/assets/icons/heart-icon.svg',
|
||||
'testimonials': '/assets/icons/quote-icon.svg',
|
||||
'certification': '/assets/icons/certification-icon.svg',
|
||||
};
|
||||
|
||||
// Map icon names (from database) to icon paths
|
||||
const iconNameToPath: Record<string, string> = {
|
||||
'star': '/assets/icons/star-icon.svg',
|
||||
'home': '/assets/icons/home-icon.svg',
|
||||
'location': '/assets/icons/location-orange-icon.svg',
|
||||
'heart': '/assets/icons/heart-icon.svg',
|
||||
'clock': '/assets/icons/availability-clock-icon.svg',
|
||||
'user': '/assets/icons/basic-information-icon.svg',
|
||||
'email': '/assets/icons/email-orange-icon.svg',
|
||||
'phone': '/assets/icons/phone-orange-icon.svg',
|
||||
'document': '/assets/icons/attachment-icon.svg',
|
||||
'certificate': '/assets/icons/certification-icon.svg',
|
||||
'certification': '/assets/icons/certification-icon.svg',
|
||||
'quote': '/assets/icons/quote-icon.svg',
|
||||
'bio': '/assets/icons/bio-icon.svg',
|
||||
'professional': '/assets/icons/professional-icon.svg',
|
||||
'wallet': '/assets/icons/wallet-icon.svg',
|
||||
'chart': '/assets/icons/chart-icon.svg',
|
||||
'dollar': '/assets/icons/dollar-icon.svg',
|
||||
'calendar': '/assets/icons/calendar-icon.svg',
|
||||
};
|
||||
|
||||
// Default icon for sections without a specific icon
|
||||
const defaultIcon = '/assets/icons/professional-icon.svg';
|
||||
|
||||
// Helper to check if a string is an emoji
|
||||
const isEmojiString = (str: string): boolean => {
|
||||
// Emoji regex pattern - matches most common emojis
|
||||
const emojiRegex = /^[\u{1F300}-\u{1F9FF}]|[\u{2600}-\u{26FF}]|[\u{2700}-\u{27BF}]|[\u{1F600}-\u{1F64F}]|[\u{1F680}-\u{1F6FF}]|[\u{1F1E0}-\u{1F1FF}]/u;
|
||||
return emojiRegex.test(str);
|
||||
};
|
||||
|
||||
interface DynamicSectionProps {
|
||||
section: ProfileSection;
|
||||
formData: Record<string, unknown>;
|
||||
onChange: (fieldSlug: string, value: unknown) => void;
|
||||
errors?: Record<string, string>;
|
||||
}
|
||||
|
||||
export default function DynamicSection({ section, formData, onChange, errors }: DynamicSectionProps) {
|
||||
// Determine icon source
|
||||
let iconPath: string = defaultIcon;
|
||||
let isEmoji = false;
|
||||
|
||||
if (section.icon) {
|
||||
if (section.icon.startsWith('/') || section.icon.startsWith('http')) {
|
||||
// It's a valid URL path
|
||||
iconPath = section.icon;
|
||||
} else if (isEmojiString(section.icon)) {
|
||||
// It's an emoji
|
||||
isEmoji = true;
|
||||
} else if (iconNameToPath[section.icon.toLowerCase()]) {
|
||||
// It's an icon name like "star", "home", etc.
|
||||
iconPath = iconNameToPath[section.icon.toLowerCase()];
|
||||
} else {
|
||||
// Fallback to slug mapping or default
|
||||
iconPath = sectionIcons[section.slug] || defaultIcon;
|
||||
}
|
||||
} else {
|
||||
// No icon set, use slug mapping or default
|
||||
iconPath = sectionIcons[section.slug] || defaultIcon;
|
||||
}
|
||||
|
||||
// Sort fields by sortOrder
|
||||
const sortedFields = [...(section.fields || [])].sort((a, b) => a.sortOrder - b.sortOrder);
|
||||
const activeFields = sortedFields.filter(f => f.isActive && !f.isSearchableOnly);
|
||||
|
||||
if (activeFields.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
id={section.slug}
|
||||
className="bg-white rounded-[20px] p-6 shadow-[0px_10px_20px_rgba(217,217,217,0.5)] scroll-mt-[100px]"
|
||||
>
|
||||
{/* Section Header */}
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<div className="w-[40px] h-[40px] flex items-center justify-center">
|
||||
{isEmoji ? (
|
||||
<span className="text-2xl">{section.icon}</span>
|
||||
) : (
|
||||
<Image
|
||||
src={iconPath}
|
||||
alt={section.name}
|
||||
width={24}
|
||||
height={24}
|
||||
className="object-contain"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-[18px] font-bold font-serif text-[#00293D]">
|
||||
{section.name}
|
||||
{section.isRequired && <span className="text-red-500 ml-1">*</span>}
|
||||
</h2>
|
||||
{section.description && (
|
||||
<p className="text-[12px] text-gray-500">{section.description}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Section Fields */}
|
||||
<div className="space-y-4">
|
||||
{activeFields.map((field) => (
|
||||
<DynamicField
|
||||
key={field.id}
|
||||
field={field}
|
||||
value={formData[field.slug]}
|
||||
onChange={onChange}
|
||||
error={errors?.[field.slug]}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user