feat: Implement featured agents section within the CMS features management interface.
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -68,6 +68,7 @@ export type {
|
||||
HeroContent,
|
||||
FeaturesContent,
|
||||
FeatureItem,
|
||||
FeaturedAgentItem,
|
||||
TopProfessionalsContent,
|
||||
ProfessionalItem,
|
||||
TestimonialsContent,
|
||||
|
||||
Reference in New Issue
Block a user