feat: Add FAQ content management page with functionality to manage categories and items.
This commit is contained in:
@@ -24,6 +24,9 @@ import {
|
||||
AboutTeamContent,
|
||||
AboutTeamMember,
|
||||
AboutCtaContent,
|
||||
FaqContent,
|
||||
FaqCategoryItem,
|
||||
FaqItem,
|
||||
User,
|
||||
} from '@/services';
|
||||
|
||||
@@ -53,6 +56,10 @@ const ABOUT_SECTIONS: SectionMeta[] = [
|
||||
{ pageSlug: 'about', sectionKey: 'cta', label: 'Call to Action', description: 'Bottom CTA section' },
|
||||
];
|
||||
|
||||
const FAQ_SECTIONS: SectionMeta[] = [
|
||||
{ pageSlug: 'faq', sectionKey: 'faqContent', label: 'FAQ Content', description: 'Categories, questions and answers' },
|
||||
];
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Default content factories
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -83,6 +90,11 @@ function defaultContentFor(pageSlug: string, sectionKey: string): unknown {
|
||||
case 'cta': return { title: '', description: '', buttonText: '' } as AboutCtaContent;
|
||||
}
|
||||
}
|
||||
if (pageSlug === 'faq') {
|
||||
switch (sectionKey) {
|
||||
case 'faqContent': return { pageTitle: '', categories: [], faqs: [], supportTitle: '', supportDescription: '' } as FaqContent;
|
||||
}
|
||||
}
|
||||
switch (sectionKey) {
|
||||
case 'hero': return defaultHero();
|
||||
case 'features': return defaultFeatures();
|
||||
@@ -120,9 +132,10 @@ const textareaCls = inputCls;
|
||||
|
||||
export default function CmsPage() {
|
||||
const router = useRouter();
|
||||
const [activePage, setActivePage] = useState<'landing' | 'about'>('landing');
|
||||
const [activePage, setActivePage] = useState<'landing' | 'about' | 'faq'>('landing');
|
||||
const [records, setRecords] = useState<CmsContentRecord[]>([]);
|
||||
const [aboutRecords, setAboutRecords] = useState<CmsContentRecord[]>([]);
|
||||
const [faqRecords, setFaqRecords] = useState<CmsContentRecord[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [error, setError] = useState('');
|
||||
const [notification, setNotification] = useState<{ type: 'success' | 'error'; message: string } | null>(null);
|
||||
@@ -148,12 +161,14 @@ export default function CmsPage() {
|
||||
setIsLoading(true);
|
||||
setError('');
|
||||
try {
|
||||
const [landingData, aboutData] = await Promise.all([
|
||||
const [landingData, aboutData, faqData] = await Promise.all([
|
||||
cmsService.getByPage('landing'),
|
||||
cmsService.getByPage('about'),
|
||||
cmsService.getByPage('faq'),
|
||||
]);
|
||||
setRecords(landingData);
|
||||
setAboutRecords(aboutData);
|
||||
setFaqRecords(faqData);
|
||||
} catch (err) {
|
||||
const msg = getErrorMessage(err);
|
||||
setError(msg);
|
||||
@@ -230,7 +245,7 @@ export default function CmsPage() {
|
||||
|
||||
// Helpers
|
||||
function recordFor(pageSlug: string, sectionKey: string): CmsContentRecord | undefined {
|
||||
const source = pageSlug === 'about' ? aboutRecords : records;
|
||||
const source = pageSlug === 'about' ? aboutRecords : pageSlug === 'faq' ? faqRecords : records;
|
||||
return source.find((r) => r.sectionKey === sectionKey);
|
||||
}
|
||||
|
||||
@@ -825,6 +840,123 @@ export default function CmsPage() {
|
||||
);
|
||||
}
|
||||
|
||||
function renderFaqForm() {
|
||||
const data = editContent as FaqContent;
|
||||
const update = (patch: Partial<FaqContent>) => setEditContent({ ...data, ...patch });
|
||||
|
||||
function addCategory() {
|
||||
const newCat: FaqCategoryItem = { id: '', label: '' };
|
||||
update({ categories: [...data.categories, newCat] });
|
||||
}
|
||||
function updateCategory(index: number, patch: Partial<FaqCategoryItem>) {
|
||||
const categories = [...data.categories];
|
||||
categories[index] = { ...categories[index], ...patch };
|
||||
update({ categories });
|
||||
}
|
||||
function removeCategory(index: number) {
|
||||
update({ categories: data.categories.filter((_, i) => i !== index) });
|
||||
}
|
||||
|
||||
function addFaq() {
|
||||
const newFaq: FaqItem = { id: Date.now(), icon: '', question: '', answer: '', category: '' };
|
||||
update({ faqs: [...data.faqs, newFaq] });
|
||||
}
|
||||
function updateFaq(index: number, patch: Partial<FaqItem>) {
|
||||
const faqs = [...data.faqs];
|
||||
faqs[index] = { ...faqs[index], ...patch };
|
||||
update({ faqs });
|
||||
}
|
||||
function removeFaq(index: number) {
|
||||
update({ faqs: data.faqs.filter((_, i) => i !== index) });
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Page Title */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-[#00293d] mb-1">Page Title</label>
|
||||
<input type="text" className={inputCls} value={data.pageTitle} onChange={(e) => update({ pageTitle: e.target.value })} placeholder="e.g. Frequently Asked Questions ?" />
|
||||
</div>
|
||||
|
||||
{/* Support Title */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-[#00293d] mb-1">Support Title</label>
|
||||
<input type="text" className={inputCls} value={data.supportTitle} onChange={(e) => update({ supportTitle: e.target.value })} placeholder="e.g. Still Need Help ?" />
|
||||
</div>
|
||||
|
||||
{/* Support Description */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-[#00293d] mb-1">Support Description</label>
|
||||
<textarea rows={3} className={textareaCls} value={data.supportDescription} onChange={(e) => update({ supportDescription: e.target.value })} placeholder="e.g. Our Support Team Is Available Mon-Fri, 9am-6pm IST" />
|
||||
</div>
|
||||
|
||||
{/* Categories */}
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<label className="block text-sm font-medium text-[#00293d]">Categories ({data.categories.length})</label>
|
||||
<button type="button" onClick={addCategory} className="px-3 py-1 text-xs bg-[#f5a623] hover:bg-[#e09620] text-white rounded-lg transition-colors">+ Add Category</button>
|
||||
</div>
|
||||
{data.categories.length === 0 && <p className="text-sm text-[#666666] italic">No categories added yet.</p>}
|
||||
<div className="space-y-3">
|
||||
{data.categories.map((cat, 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]">Category {idx + 1}</span>
|
||||
<button type="button" onClick={() => removeCategory(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={cat.id} onChange={(e) => updateCategory(idx, { id: e.target.value })} placeholder="ID (e.g. buying)" />
|
||||
<input type="text" className={inputCls} value={cat.label} onChange={(e) => updateCategory(idx, { label: e.target.value })} placeholder="Label (e.g. Buying a Home)" />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* FAQ Items */}
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<label className="block text-sm font-medium text-[#00293d]">FAQ Items ({data.faqs.length})</label>
|
||||
<button type="button" onClick={addFaq} className="px-3 py-1 text-xs bg-[#f5a623] hover:bg-[#e09620] text-white rounded-lg transition-colors">+ Add FAQ</button>
|
||||
</div>
|
||||
{data.faqs.length === 0 && <p className="text-sm text-[#666666] italic">No FAQ items added yet.</p>}
|
||||
<div className="space-y-3">
|
||||
{data.faqs.map((faq, 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]">FAQ {idx + 1}</span>
|
||||
<button type="button" onClick={() => removeFaq(idx)} className="text-red-500 hover:text-red-700 text-xs font-medium">Remove</button>
|
||||
</div>
|
||||
{renderImageUploadField('Icon', faq.icon, (url) => updateFaq(idx, { icon: url }))}
|
||||
<div>
|
||||
<label className="block text-xs text-[#666666] mb-0.5">Question</label>
|
||||
<input type="text" className={inputCls} value={faq.question} onChange={(e) => updateFaq(idx, { question: e.target.value })} placeholder="Enter the question" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs text-[#666666] mb-0.5">Answer</label>
|
||||
<textarea rows={3} className={textareaCls} value={faq.answer} onChange={(e) => updateFaq(idx, { answer: e.target.value })} placeholder="Enter the answer" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs text-[#666666] mb-0.5">Category</label>
|
||||
<select
|
||||
className={inputCls}
|
||||
value={faq.category}
|
||||
onChange={(e) => updateFaq(idx, { category: e.target.value })}
|
||||
>
|
||||
<option value="">Select a category...</option>
|
||||
{data.categories.map((cat) => (
|
||||
<option key={cat.id} value={cat.id}>{cat.label}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function renderModalContent() {
|
||||
if (!editingSection || editContent == null) return null;
|
||||
const key = editingSection.sectionKey;
|
||||
@@ -838,6 +970,11 @@ export default function CmsPage() {
|
||||
case 'cta': return renderAboutCtaForm();
|
||||
}
|
||||
}
|
||||
if (page === 'faq') {
|
||||
switch (key) {
|
||||
case 'faqContent': return renderFaqForm();
|
||||
}
|
||||
}
|
||||
switch (key) {
|
||||
case 'hero': return renderHeroForm();
|
||||
case 'features': return renderFeaturesForm();
|
||||
@@ -851,8 +988,8 @@ export default function CmsPage() {
|
||||
// Render
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const currentSections = activePage === 'landing' ? LANDING_SECTIONS : ABOUT_SECTIONS;
|
||||
const currentRecords = activePage === 'landing' ? records : aboutRecords;
|
||||
const currentSections = activePage === 'landing' ? LANDING_SECTIONS : activePage === 'about' ? ABOUT_SECTIONS : FAQ_SECTIONS;
|
||||
const currentRecords = activePage === 'landing' ? records : activePage === 'about' ? aboutRecords : faqRecords;
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -879,6 +1016,13 @@ export default function CmsPage() {
|
||||
>
|
||||
About Us
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setActivePage('faq')}
|
||||
className={`px-5 py-3 text-sm font-medium border-b-2 transition-colors ${activePage === 'faq' ? 'border-[#f5a623] text-[#f5a623]' : 'border-transparent text-[#666666] hover:text-[#00293d]'}`}
|
||||
>
|
||||
FAQ
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user