feat: Add FAQ content management page with functionality to manage categories and items.

This commit is contained in:
pradeepkumar
2026-02-24 03:53:56 +05:30
parent 9bc70078c2
commit a264c24f26
3 changed files with 173 additions and 5 deletions

View File

@@ -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>