feat: Implement featured agents section within the CMS features management interface.

This commit is contained in:
pradeepkumar
2026-03-23 11:25:23 +05:30
parent ddf0cc5517
commit 6b5256978e
3 changed files with 91 additions and 20 deletions

View File

@@ -11,6 +11,7 @@ import {
HeroContent,
FeaturesContent,
FeatureItem,
FeaturedAgentItem,
TopProfessionalsContent,
ProfessionalItem,
TestimonialsContent,
@@ -74,7 +75,11 @@ function defaultHero(): HeroContent {
}
function defaultFeatures(): FeaturesContent {
return { title: '', subtitle: '', features: [] };
return { title: '', subtitle: '', features: [], featuredAgents: [] };
}
function emptyFeaturedAgent(): FeaturedAgentItem {
return { name: '', role: '', rating: '', location: '', experience: '', imageUrl: '' };
}
function defaultTopProfessionals(): TopProfessionalsContent {
@@ -158,6 +163,7 @@ export default function CmsPage() {
const [isSubmitting, setIsSubmitting] = useState(false);
const [modalError, setModalError] = useState('');
const [professionalsTab, setProfessionalsTab] = useState<'agents' | 'lenders'>('agents');
const [featuresTab, setFeaturesTab] = useState<'features' | 'agents'>('features');
const [isUploadingImage, setIsUploadingImage] = useState(false);
// Agent search
@@ -389,26 +395,80 @@ export default function CmsPage() {
<input type="text" className={inputCls} value={data.subtitle} onChange={(e) => update({ subtitle: e.target.value })} placeholder="Section subtitle" />
</div>
<div>
<div className="flex items-center justify-between mb-2">
<label className="block text-sm font-medium text-[#00293d]">Features ({data.features.length})</label>
<button type="button" onClick={addFeature} className="px-3 py-1 text-xs bg-[#f5a623] hover:bg-[#e09620] text-white rounded-lg transition-colors">+ Add Feature</button>
</div>
{data.features.length === 0 && <p className="text-sm text-[#666666] italic">No features added yet.</p>}
<div className="space-y-3">
{data.features.map((feat, idx) => (
<div key={idx} className="p-3 border border-[#e5e7eb] rounded-lg bg-[#f9fafb] space-y-2">
<div className="flex items-center justify-between">
<span className="text-xs font-semibold text-[#666666]">Feature {idx + 1}</span>
<button type="button" onClick={() => removeFeature(idx)} className="text-red-500 hover:text-red-700 text-xs font-medium">Remove</button>
</div>
{renderImageUploadField('Icon', feat.iconPath, (url) => updateFeature(idx, { iconPath: url }))}
<input type="text" className={inputCls} value={feat.title} onChange={(e) => updateFeature(idx, { title: e.target.value })} placeholder="Feature title" />
<textarea rows={2} className={textareaCls} value={feat.description} onChange={(e) => updateFeature(idx, { description: e.target.value })} placeholder="Feature description" />
</div>
))}
</div>
{/* Tabs */}
<div className="border-b border-[#e5e7eb]">
<nav className="flex -mb-px">
<button
type="button"
onClick={() => setFeaturesTab('features')}
className={`px-4 py-2 text-sm font-medium border-b-2 transition-colors ${featuresTab === 'features' ? 'border-[#f5a623] text-[#f5a623]' : 'border-transparent text-[#666666] hover:text-[#00293d]'}`}
>
Features ({data.features.length})
</button>
<button
type="button"
onClick={() => setFeaturesTab('agents')}
className={`px-4 py-2 text-sm font-medium border-b-2 transition-colors ${featuresTab === 'agents' ? 'border-[#f5a623] text-[#f5a623]' : 'border-transparent text-[#666666] hover:text-[#00293d]'}`}
>
Featured Agents ({(data.featuredAgents || []).length})
</button>
</nav>
</div>
{/* Features Tab */}
{featuresTab === 'features' && (
<div>
<div className="flex items-center justify-between mb-2">
<label className="block text-sm font-medium text-[#00293d]">Features ({data.features.length})</label>
<button type="button" onClick={addFeature} className="px-3 py-1 text-xs bg-[#f5a623] hover:bg-[#e09620] text-white rounded-lg transition-colors">+ Add Feature</button>
</div>
{data.features.length === 0 && <p className="text-sm text-[#666666] italic">No features added yet.</p>}
<div className="space-y-3">
{data.features.map((feat, idx) => (
<div key={idx} className="p-3 border border-[#e5e7eb] rounded-lg bg-[#f9fafb] space-y-2">
<div className="flex items-center justify-between">
<span className="text-xs font-semibold text-[#666666]">Feature {idx + 1}</span>
<button type="button" onClick={() => removeFeature(idx)} className="text-red-500 hover:text-red-700 text-xs font-medium">Remove</button>
</div>
{renderImageUploadField('Icon', feat.iconPath, (url) => updateFeature(idx, { iconPath: url }))}
<input type="text" className={inputCls} value={feat.title} onChange={(e) => updateFeature(idx, { title: e.target.value })} placeholder="Feature title" />
<textarea rows={2} className={textareaCls} value={feat.description} onChange={(e) => updateFeature(idx, { description: e.target.value })} placeholder="Feature description" />
</div>
))}
</div>
</div>
)}
{/* Featured Agents Tab */}
{featuresTab === 'agents' && (
<div>
<div className="flex items-center justify-between mb-2">
<label className="block text-sm font-medium text-[#00293d]">Featured Agent Cards ({(data.featuredAgents || []).length}/3)</label>
{(data.featuredAgents || []).length < 3 && (
<button type="button" onClick={() => update({ featuredAgents: [...(data.featuredAgents || []), emptyFeaturedAgent()] })} className="px-3 py-1 text-xs bg-[#f5a623] hover:bg-[#e09620] text-white rounded-lg transition-colors">+ Add Agent</button>
)}
</div>
{(data.featuredAgents || []).length >= 3 && <p className="text-xs text-[#666666] mb-2">Maximum of 3 agents reached.</p>}
<div className="space-y-3">
{(data.featuredAgents || []).map((agent, idx) => (
<div key={idx} className="p-3 border border-[#e5e7eb] rounded-lg bg-[#f9fafb] space-y-2">
<div className="flex items-center justify-between">
<span className="text-xs font-semibold text-[#666666]">Agent {idx + 1}</span>
<button type="button" onClick={() => update({ featuredAgents: (data.featuredAgents || []).filter((_, i) => i !== idx) })} className="text-red-500 hover:text-red-700 text-xs font-medium">Remove</button>
</div>
<div className="grid grid-cols-2 gap-2">
<input type="text" className={inputCls} value={agent.name} onChange={(e) => { const agents = [...(data.featuredAgents || [])]; agents[idx] = { ...agents[idx], name: e.target.value }; update({ featuredAgents: agents }); }} placeholder="Name" />
<input type="text" className={inputCls} value={agent.role} onChange={(e) => { const agents = [...(data.featuredAgents || [])]; agents[idx] = { ...agents[idx], role: e.target.value }; update({ featuredAgents: agents }); }} placeholder="Role (e.g. Lender)" />
<input type="text" className={inputCls} value={agent.location} onChange={(e) => { const agents = [...(data.featuredAgents || [])]; agents[idx] = { ...agents[idx], location: e.target.value }; update({ featuredAgents: agents }); }} placeholder="Location" />
<input type="text" className={inputCls} value={agent.rating} onChange={(e) => { const agents = [...(data.featuredAgents || [])]; agents[idx] = { ...agents[idx], rating: e.target.value }; update({ featuredAgents: agents }); }} placeholder="Rating (e.g. 4.9)" />
</div>
<input type="text" className={inputCls} value={agent.experience} onChange={(e) => { const agents = [...(data.featuredAgents || [])]; agents[idx] = { ...agents[idx], experience: e.target.value }; update({ featuredAgents: agents }); }} placeholder="Experience (e.g. 7+ years...)" />
{renderImageUploadField('Photo', agent.imageUrl, (url) => { const agents = [...(data.featuredAgents || [])]; agents[idx] = { ...agents[idx], imageUrl: url }; update({ featuredAgents: agents }); })}
</div>
))}
</div>
</div>
)}
</div>
);
}

View File

@@ -14,10 +14,20 @@ export interface FeatureItem {
description: string;
}
export interface FeaturedAgentItem {
name: string;
role: string;
rating: string;
location: string;
experience: string;
imageUrl: string;
}
export interface FeaturesContent {
title: string;
subtitle: string;
features: FeatureItem[];
featuredAgents?: FeaturedAgentItem[];
}
export interface ProfessionalItem {

View File

@@ -68,6 +68,7 @@ export type {
HeroContent,
FeaturesContent,
FeatureItem,
FeaturedAgentItem,
TopProfessionalsContent,
ProfessionalItem,
TestimonialsContent,