feat: Add full CMS support for the 'Contact' page, including sections, default content factories, data fetching, and editing forms.
This commit is contained in:
@@ -60,6 +60,11 @@ const FAQ_SECTIONS: SectionMeta[] = [
|
||||
{ pageSlug: 'faq', sectionKey: 'faqContent', label: 'FAQ Content', description: 'Categories, questions and answers' },
|
||||
];
|
||||
|
||||
const CONTACT_SECTIONS: SectionMeta[] = [
|
||||
{ pageSlug: 'contact', sectionKey: 'contactDetails', label: 'Contact Details', description: 'Email, phone, office address and map' },
|
||||
{ pageSlug: 'contact', sectionKey: 'cta', label: 'Call to Action', description: 'Bottom CTA section text and button' },
|
||||
];
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Default content factories
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -95,6 +100,12 @@ function defaultContentFor(pageSlug: string, sectionKey: string): unknown {
|
||||
case 'faqContent': return { pageTitle: '', categories: [], faqs: [], supportTitle: '', supportDescription: '' } as FaqContent;
|
||||
}
|
||||
}
|
||||
if (pageSlug === 'contact') {
|
||||
switch (sectionKey) {
|
||||
case 'contactDetails': return { title: '', description: '', email: '', phone: '', phoneHours: '', officeAddress: '', officeCity: '', mapUrl: '', directionsUrl: '' };
|
||||
case 'cta': return { title: '', description: '', buttonText: '', buttonLink: '' };
|
||||
}
|
||||
}
|
||||
switch (sectionKey) {
|
||||
case 'hero': return defaultHero();
|
||||
case 'features': return defaultFeatures();
|
||||
@@ -132,10 +143,11 @@ const textareaCls = inputCls;
|
||||
|
||||
export default function CmsPage() {
|
||||
const router = useRouter();
|
||||
const [activePage, setActivePage] = useState<'landing' | 'about' | 'faq'>('landing');
|
||||
const [activePage, setActivePage] = useState<'landing' | 'about' | 'faq' | 'contact'>('landing');
|
||||
const [records, setRecords] = useState<CmsContentRecord[]>([]);
|
||||
const [aboutRecords, setAboutRecords] = useState<CmsContentRecord[]>([]);
|
||||
const [faqRecords, setFaqRecords] = useState<CmsContentRecord[]>([]);
|
||||
const [contactRecords, setContactRecords] = useState<CmsContentRecord[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [error, setError] = useState('');
|
||||
const [notification, setNotification] = useState<{ type: 'success' | 'error'; message: string } | null>(null);
|
||||
@@ -161,14 +173,16 @@ export default function CmsPage() {
|
||||
setIsLoading(true);
|
||||
setError('');
|
||||
try {
|
||||
const [landingData, aboutData, faqData] = await Promise.all([
|
||||
const [landingData, aboutData, faqData, contactData] = await Promise.all([
|
||||
cmsService.getByPage('landing'),
|
||||
cmsService.getByPage('about'),
|
||||
cmsService.getByPage('faq'),
|
||||
cmsService.getByPage('contact'),
|
||||
]);
|
||||
setRecords(landingData);
|
||||
setAboutRecords(aboutData);
|
||||
setFaqRecords(faqData);
|
||||
setContactRecords(contactData);
|
||||
} catch (err) {
|
||||
const msg = getErrorMessage(err);
|
||||
setError(msg);
|
||||
@@ -986,6 +1000,80 @@ export default function CmsPage() {
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Contact page editors
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function renderContactDetailsForm() {
|
||||
const data = editContent as Record<string, string>;
|
||||
const update = (patch: Record<string, string>) => setEditContent({ ...data, ...patch });
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-[#00293d] mb-1">Page Title</label>
|
||||
<input className={inputCls} value={data.title || ''} onChange={e => update({ title: e.target.value })} placeholder="Get In Touch" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-[#00293d] mb-1">Description</label>
|
||||
<textarea className={textareaCls} rows={2} value={data.description || ''} onChange={e => update({ description: e.target.value })} placeholder="Page description" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-[#00293d] mb-1">Support Email</label>
|
||||
<input className={inputCls} value={data.email || ''} onChange={e => update({ email: e.target.value })} placeholder="support@example.com" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-[#00293d] mb-1">Phone Number</label>
|
||||
<input className={inputCls} value={data.phone || ''} onChange={e => update({ phone: e.target.value })} placeholder="1234567890" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-[#00293d] mb-1">Phone Hours</label>
|
||||
<input className={inputCls} value={data.phoneHours || ''} onChange={e => update({ phoneHours: e.target.value })} placeholder="Mon-Fri 9am-6pm" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-[#00293d] mb-1">Office Address (Line 1)</label>
|
||||
<input className={inputCls} value={data.officeAddress || ''} onChange={e => update({ officeAddress: e.target.value })} placeholder="123 Market Street" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-[#00293d] mb-1">Office Address (Line 2)</label>
|
||||
<input className={inputCls} value={data.officeCity || ''} onChange={e => update({ officeCity: e.target.value })} placeholder="New York CA 234737" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-[#00293d] mb-1">Google Maps Embed URL (optional)</label>
|
||||
<input className={inputCls} value={data.mapUrl || ''} onChange={e => update({ mapUrl: e.target.value })} placeholder="https://maps.google.com/..." />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-[#00293d] mb-1">Directions URL (optional)</label>
|
||||
<input className={inputCls} value={data.directionsUrl || ''} onChange={e => update({ directionsUrl: e.target.value })} placeholder="https://maps.google.com/..." />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function renderContactCtaForm() {
|
||||
const data = editContent as Record<string, string>;
|
||||
const update = (patch: Record<string, string>) => setEditContent({ ...data, ...patch });
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-[#00293d] mb-1">Title</label>
|
||||
<input className={inputCls} value={data.title || ''} onChange={e => update({ title: e.target.value })} placeholder="Ready to find an agent?" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-[#00293d] mb-1">Description</label>
|
||||
<textarea className={textareaCls} rows={2} value={data.description || ''} onChange={e => update({ description: e.target.value })} placeholder="CTA description" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-[#00293d] mb-1">Button Text</label>
|
||||
<input className={inputCls} value={data.buttonText || ''} onChange={e => update({ buttonText: e.target.value })} placeholder="Start Your Search" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-[#00293d] mb-1">Button Link</label>
|
||||
<input className={inputCls} value={data.buttonLink || ''} onChange={e => update({ buttonLink: e.target.value })} placeholder="/user/profiles" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function renderModalContent() {
|
||||
if (!editingSection || editContent == null) return null;
|
||||
const key = editingSection.sectionKey;
|
||||
@@ -1004,6 +1092,12 @@ export default function CmsPage() {
|
||||
case 'faqContent': return renderFaqForm();
|
||||
}
|
||||
}
|
||||
if (page === 'contact') {
|
||||
switch (key) {
|
||||
case 'contactDetails': return renderContactDetailsForm();
|
||||
case 'cta': return renderContactCtaForm();
|
||||
}
|
||||
}
|
||||
switch (key) {
|
||||
case 'hero': return renderHeroForm();
|
||||
case 'features': return renderFeaturesForm();
|
||||
@@ -1017,8 +1111,8 @@ export default function CmsPage() {
|
||||
// Render
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const currentSections = activePage === 'landing' ? LANDING_SECTIONS : activePage === 'about' ? ABOUT_SECTIONS : FAQ_SECTIONS;
|
||||
const currentRecords = activePage === 'landing' ? records : activePage === 'about' ? aboutRecords : faqRecords;
|
||||
const currentSections = activePage === 'landing' ? LANDING_SECTIONS : activePage === 'about' ? ABOUT_SECTIONS : activePage === 'contact' ? CONTACT_SECTIONS : FAQ_SECTIONS;
|
||||
const currentRecords = activePage === 'landing' ? records : activePage === 'about' ? aboutRecords : activePage === 'contact' ? contactRecords : faqRecords;
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -1052,6 +1146,13 @@ export default function CmsPage() {
|
||||
>
|
||||
FAQ
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setActivePage('contact')}
|
||||
className={`px-5 py-3 text-sm font-medium border-b-2 transition-colors ${activePage === 'contact' ? 'border-[#f5a623] text-[#f5a623]' : 'border-transparent text-[#666666] hover:text-[#00293d]'}`}
|
||||
>
|
||||
Contact
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user