feat: replace team members with cards in CMS, add date validation fields, and remove contact info section from user profile
This commit is contained in:
@@ -24,7 +24,7 @@ import {
|
||||
AboutFeaturesContent,
|
||||
AboutFeatureItem,
|
||||
AboutTeamContent,
|
||||
AboutTeamMember,
|
||||
AboutTeamCard,
|
||||
AboutCtaContent,
|
||||
FaqContent,
|
||||
FaqCategoryItem,
|
||||
@@ -97,7 +97,7 @@ function defaultContentFor(pageSlug: string, sectionKey: string): unknown {
|
||||
case 'hero': return { badge: '', headline: '', description: '', ctaButtonText: '', bannerImageUrl: '', bannerOverlayText: '' } as AboutHeroContent;
|
||||
case 'stats': return { stats: [] } as AboutStatsContent;
|
||||
case 'features': return { badge: '', features: [] } as AboutFeaturesContent;
|
||||
case 'team': return { title: '', subtitle: '', members: [] } as AboutTeamContent;
|
||||
case 'team': return { title: '', subtitle: '', intro: '', cards: [] } as AboutTeamContent;
|
||||
case 'cta': return { title: '', description: '', buttonText: '' } as AboutCtaContent;
|
||||
}
|
||||
}
|
||||
@@ -1132,40 +1132,48 @@ export default function CmsPage() {
|
||||
}
|
||||
|
||||
function renderAboutTeamForm() {
|
||||
const data = editContent as AboutTeamContent;
|
||||
const dataRaw = editContent as AboutTeamContent & { members?: unknown };
|
||||
const data: AboutTeamContent = {
|
||||
title: dataRaw.title ?? '',
|
||||
subtitle: dataRaw.subtitle ?? '',
|
||||
intro: dataRaw.intro ?? '',
|
||||
cards: Array.isArray(dataRaw.cards) ? dataRaw.cards : [],
|
||||
};
|
||||
const update = (patch: Partial<AboutTeamContent>) => setEditContent({ ...data, ...patch });
|
||||
function updateMember(index: number, patch: Partial<AboutTeamMember>) {
|
||||
const members = [...data.members];
|
||||
members[index] = { ...members[index], ...patch };
|
||||
update({ members });
|
||||
function updateCard(index: number, patch: Partial<AboutTeamCard>) {
|
||||
const cards = [...data.cards];
|
||||
cards[index] = { ...cards[index], ...patch };
|
||||
update({ cards });
|
||||
}
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-[#00293d] mb-1">Section Title</label>
|
||||
<input type="text" className={inputCls} value={data.title} onChange={(e) => update({ title: e.target.value })} placeholder="e.g. Meet the minds behind the platform." />
|
||||
<input type="text" className={inputCls} value={data.title} onChange={(e) => update({ title: e.target.value })} placeholder="e.g. For Real Estate Professionals." />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-[#00293d] mb-1">Section Subtitle</label>
|
||||
<textarea rows={2} className={textareaCls} value={data.subtitle} onChange={(e) => update({ subtitle: e.target.value })} placeholder="Section description" />
|
||||
<textarea rows={2} className={textareaCls} value={data.subtitle} onChange={(e) => update({ subtitle: e.target.value })} placeholder="A place where your skills, not your wallet, stands out." />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-[#00293d] mb-1">Intro Paragraph</label>
|
||||
<textarea rows={4} className={textareaCls} value={data.intro} onChange={(e) => update({ intro: e.target.value })} placeholder="RE-Quest is a unique platform..." />
|
||||
</div>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<label className="block text-sm font-medium text-[#00293d]">Team Members ({data.members.length})</label>
|
||||
<button type="button" onClick={() => update({ members: [...data.members, { name: '', role: '', imageUrl: '' }] })} className="px-3 py-1 text-xs bg-[#f5a623] hover:bg-[#e09620] text-white rounded-lg transition-colors">+ Add Member</button>
|
||||
<label className="block text-sm font-medium text-[#00293d]">Cards ({data.cards.length})</label>
|
||||
<button type="button" onClick={() => update({ cards: [...data.cards, { title: '', description: '', iconPath: '' }] })} className="px-3 py-1 text-xs bg-[#f5a623] hover:bg-[#e09620] text-white rounded-lg transition-colors">+ Add Card</button>
|
||||
</div>
|
||||
{data.members.length === 0 && <p className="text-sm text-[#666666] italic">No team members added yet.</p>}
|
||||
{data.cards.length === 0 && <p className="text-sm text-[#666666] italic">No cards added yet.</p>}
|
||||
<div className="space-y-3">
|
||||
{data.members.map((member, idx) => (
|
||||
{data.cards.map((card, 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]">Member {idx + 1}</span>
|
||||
<button type="button" onClick={() => update({ members: data.members.filter((_, i) => i !== idx) })} className="text-red-500 hover:text-red-700 text-xs font-medium">Remove</button>
|
||||
<span className="text-xs font-semibold text-[#666666]">Card {idx + 1}</span>
|
||||
<button type="button" onClick={() => update({ cards: data.cards.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={member.name} onChange={(e) => updateMember(idx, { name: e.target.value })} placeholder="Name" />
|
||||
<input type="text" className={inputCls} value={member.role} onChange={(e) => updateMember(idx, { role: e.target.value })} placeholder="Role" />
|
||||
</div>
|
||||
{renderImageUploadField(`Photo`, member.imageUrl, (url) => updateMember(idx, { imageUrl: url }))}
|
||||
{renderImageUploadField('Icon', card.iconPath, (url) => updateCard(idx, { iconPath: url }))}
|
||||
<input type="text" className={inputCls} value={card.title} onChange={(e) => updateCard(idx, { title: e.target.value })} placeholder="Card title" />
|
||||
<textarea rows={3} className={textareaCls} value={card.description} onChange={(e) => updateCard(idx, { description: e.target.value })} placeholder="Card description" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -974,6 +974,28 @@ export default function SectionDetailPage() {
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{fieldForm.fieldType === 'DATE' && (
|
||||
<>
|
||||
<div>
|
||||
<label className="block text-xs text-[#666666] mb-1">Min Date</label>
|
||||
<input
|
||||
type="date"
|
||||
value={fieldForm.validation?.minDate ?? ''}
|
||||
onChange={(e) => updateValidation('minDate', e.target.value)}
|
||||
className="w-full px-3 py-2 border border-[#e5e7eb] rounded-xl focus:outline-none focus:border-[#f5a623] text-sm text-[#00293d] bg-white placeholder:text-[#666666]"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs text-[#666666] mb-1">Max Date</label>
|
||||
<input
|
||||
type="date"
|
||||
value={fieldForm.validation?.maxDate ?? ''}
|
||||
onChange={(e) => updateValidation('maxDate', e.target.value)}
|
||||
className="w-full px-3 py-2 border border-[#e5e7eb] rounded-xl focus:outline-none focus:border-[#f5a623] text-sm text-[#00293d] bg-white placeholder:text-[#666666]"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div className="col-span-2">
|
||||
<label className="block text-xs text-[#666666] mb-1">Pattern (regex)</label>
|
||||
<input
|
||||
|
||||
@@ -464,41 +464,6 @@ export default function UserDetailPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Contact Information */}
|
||||
<div className="bg-white rounded-xl shadow-sm border border-[#e5e7eb] mb-6">
|
||||
<div className="px-6 py-4 border-b border-[#e5e7eb]">
|
||||
<h2 className="text-lg font-semibold text-[#00293d] font-fractul">Contact Information</h2>
|
||||
</div>
|
||||
<div className="p-6">
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-6">
|
||||
<div>
|
||||
<p className="text-sm text-[#666666]">Phone</p>
|
||||
<p className="text-sm font-medium text-[#00293d]">
|
||||
{user.profile?.phone || 'Not provided'}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-[#666666]">City</p>
|
||||
<p className="text-sm font-medium text-[#00293d]">
|
||||
{user.profile?.city || 'Not provided'}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-[#666666]">State</p>
|
||||
<p className="text-sm font-medium text-[#00293d]">
|
||||
{user.profile?.state || 'Not provided'}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-[#666666]">Country</p>
|
||||
<p className="text-sm font-medium text-[#00293d]">
|
||||
{user.profile?.country || 'Not provided'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Dynamic Profile Data (only for AGENT role) */}
|
||||
{user.role === 'AGENT' && (
|
||||
<div className="bg-white rounded-xl shadow-sm border border-[#e5e7eb] mb-6">
|
||||
|
||||
@@ -98,16 +98,17 @@ export interface AboutFeaturesContent {
|
||||
features: AboutFeatureItem[];
|
||||
}
|
||||
|
||||
export interface AboutTeamMember {
|
||||
name: string;
|
||||
role: string;
|
||||
imageUrl: string;
|
||||
export interface AboutTeamCard {
|
||||
title: string;
|
||||
description: string;
|
||||
iconPath: string;
|
||||
}
|
||||
|
||||
export interface AboutTeamContent {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
members: AboutTeamMember[];
|
||||
intro: string;
|
||||
cards: AboutTeamCard[];
|
||||
}
|
||||
|
||||
export interface AboutCtaContent {
|
||||
|
||||
@@ -80,7 +80,7 @@ export type {
|
||||
AboutStatsContent,
|
||||
AboutFeatureItem,
|
||||
AboutFeaturesContent,
|
||||
AboutTeamMember,
|
||||
AboutTeamCard,
|
||||
AboutTeamContent,
|
||||
AboutCtaContent,
|
||||
FaqCategoryItem,
|
||||
|
||||
@@ -47,6 +47,8 @@ export interface FieldValidation {
|
||||
minLength?: number;
|
||||
maxLength?: number;
|
||||
pattern?: string;
|
||||
minDate?: string; // ISO date (YYYY-MM-DD) for DATE fields
|
||||
maxDate?: string; // ISO date (YYYY-MM-DD) for DATE fields
|
||||
}
|
||||
|
||||
export interface FieldUiConfig {
|
||||
|
||||
Reference in New Issue
Block a user