Feature #11712 ยป NexZen E-commerce FAQ Module Redmine Tasks.md
NexZen E-commerce: FAQ Module, Redmine Tasks
Module: E-commerce storefront and admin | Stack: React, NestJS, MongoDB (separate DB per tenant), Redis
How to use: create the parent task first, then add the two subtasks below (Backend and Frontend) under it. Example URLs use the /api prefix and a { "data": ... } response envelope. Adjust both to match your existing API format.
Parent task
Subject: [FAQ] Dynamic FAQ management and storefront FAQ page Tracker: Feature | Priority: Normal
Description: Each company manages its own FAQ categories and questions from the admin panel. The storefront shows them on a FAQ page with category filter, search, "Was this helpful?" votes and a help form on the right side. Data is stored in the tenant's own database, so no tenantId field is needed. Collections: faq_categories, faqs, faq_enquiries. New tenants get default FAQs (US or India template) when they are provisioned.
Done when:
- Admin can create, edit, reorder and publish FAQs and categories.
- Customer sees only published FAQs on the storefront and can search and vote.
- Help form submissions reach the admin inbox and email notification.
- A newly onboarded company already has default FAQs.
API summary
| Method | Endpoint | Auth | Used by (frontend) |
|---|---|---|---|
| GET, POST | /api/admin/faq-categories | Admin, Manager | Admin categories screen |
| PATCH, DELETE | /api/admin/faq-categories/:id | Admin, Manager | Admin categories screen |
| GET, POST | /api/admin/faqs | Admin, Manager | Admin FAQ list and editor |
| GET, PATCH, DELETE | /api/admin/faqs/:id | Admin, Manager | Admin FAQ list and editor |
| PATCH | /api/admin/faqs/reorder | Admin, Manager | Admin FAQ list (drag to reorder) |
| PATCH | /api/admin/faqs/:id/publish | Admin, Manager | Admin FAQ list (publish toggle) |
| GET | /api/faqs | Public | Storefront FAQ page and product, checkout, order widgets |
| POST | /api/faqs/:id/feedback | Public | Helpful votes |
| POST | /api/faq-enquiries | Public | Help form |
| GET, PATCH | /api/admin/faq-enquiries | Admin, Manager | Enquiries inbox |
Subtask 1: Backend
Subject: [FAQ][BE] FAQ backend: schemas, admin and public APIs, enquiries, seeder, cache Tracker: Task | Priority: Normal | Parent: [FAQ] Dynamic FAQ management and storefront FAQ page
Description: Build all server-side FAQ functionality in the NestJS app on the tenant DB connection, in nine parts. Each part has its own examples and acceptance criteria. All admin routes allow only Admin and Manager roles.
Checklist:
- [ ] 1. Schemas and indexes
- [ ] 2. Admin category APIs
- [ ] 3. Admin FAQ APIs: CRUD, reorder, publish
- [ ] 4. Public FAQ APIs: list, search, feedback
- [ ] 5. Enquiry (help form) APIs and notifications
- [ ] 6. Default FAQ seeder for new tenants
- [ ] 7. Redis cache for the public API
- [ ] 8. Validation, RBAC, sanitization and rate limiting
- [ ] 9. Unit and e2e tests
1. Schemas and indexes in the tenant DB
Create three Mongoose schemas and register them on the tenant connection provider, not the master DB.
faq_categories: name (required), slug (unique), order, isActive, timestampsfaqs: categoryId, question, answer (HTML), order, status (draftorpublished), placements (faq_page,product,checkout,order), region (ALL,US,IN), helpfulYes, helpfulNo, createdBy, updatedBy, timestampsfaq_enquiries: name, email, phone, message, orderNumber, status (new,in_progress,closed), assignedTo, timestamps
Indexes: faqs on { status, categoryId, order } and a text index on { question, answer }; faq_categories on { slug } unique; faq_enquiries on { status, createdAt }.
Acceptance criteria:
- Collections are created in the tenant DB only; nothing is written to the master DB.
- Two tenants can have the same category name without conflict.
- Text search returns results for a word in the question or answer.
2. Admin category APIs
CRUD for categories. Slug is generated from the name. A category that has FAQs cannot be deleted.
Create POST /api/admin/faq-categories
Request: { "name": "Shipping", "order": 1, "isActive": true }
Response 201: { "data": { "_id": "6650a1", "name": "Shipping", "slug": "shipping", "order": 1, "isActive": true } }
List GET /api/admin/faq-categories
Response 200: { "data": [ { "_id": "6650a1", "name": "Shipping", "order": 1, "isActive": true, "faqCount": 12 } ] }
Update PATCH /api/admin/faq-categories/:id with { "name": "Shipping and Delivery", "isActive": false }
Delete DELETE /api/admin/faq-categories/:id
Response 409: { "message": "Category has 12 FAQs. Move or delete them first." }
Acceptance criteria:
- Duplicate name returns 409.
- Empty name or name over 80 characters returns 400.
- Inactive category hides all its FAQs from the public API.
3. Admin FAQ APIs (CRUD, reorder, publish)
Manage FAQ entries. The answer HTML is sanitized before saving. Every create, update and delete is written to the action history, following the existing pattern.
Create POST /api/admin/faqs
Request:
{ "categoryId": "6650a1", "question": "How long will delivery take?",
"answer": "<p>Orders ship in 1-2 business days via FedEx.</p>",
"order": 1, "status": "draft", "placements": ["faq_page", "product"], "region": "ALL" }
Response 201: { "data": { "_id": "6650b7", "status": "draft" } }
List GET /api/admin/faqs?categoryId=6650a1&status=draft®ion=US&q=delivery&page=1&limit=20
Response 200: { "data": [ { "_id": "6650b7", "question": "...", "categoryName": "Shipping", "status": "draft" } ],
"meta": { "page": 1, "limit": 20, "total": 34 } }
Get, update, delete: GET, PATCH, DELETE /api/admin/faqs/:id
Reorder PATCH /api/admin/faqs/reorder (single bulk write)
Request: { "items": [ { "id": "6650b7", "order": 1 }, { "id": "6650b8", "order": 2 } ] }
Response 200: { "data": { "updated": 2 } }
Publish or unpublish PATCH /api/admin/faqs/:id/publish
Request: { "status": "published" }
Acceptance criteria:
- Script tags and inline event handlers in
answerare removed on save. - Unknown
categoryIdreturns 404. - Reorder with an id that does not exist returns 400 with the bad id and changes nothing.
- Cache is invalidated after every write (part 7).
4. Public FAQ APIs (list, search, feedback)
Storefront endpoints. They return only published FAQs in active categories, sorted by category order then FAQ order. region returns FAQs tagged for that region plus those tagged ALL.
List GET /api/faqs?category=shipping&q=refund&placement=faq_page®ion=US
Response 200:
{ "data": [ { "category": { "name": "Shipping", "slug": "shipping" },
"faqs": [ { "_id": "6650b7", "question": "How long will delivery take?",
"answer": "<p>Orders ship in 1-2 business days.</p>" } ] } ] }
Feedback POST /api/faqs/:id/feedback
Request: { "helpful": true }
Response 200: { "data": { "helpfulYes": 13, "helpfulNo": 2 } }
Acceptance criteria:
- Draft FAQs never appear in public responses.
placement=productreturns only FAQs that includeproductinplacements.- Search matches question and answer text.
- One vote per visitor per FAQ (client key in cookie or header); a repeat vote returns 429 or is ignored.
5. Enquiry (help form) APIs and notifications
Public endpoint for the help form beside the FAQ list, plus an admin inbox.
Submit POST /api/faq-enquiries
Request: { "name": "Asha", "email": "asha@example.com", "phone": "+91 98765 43210",
"message": "My refund has not arrived.", "orderNumber": "ORD-1042", "website": "" }
Response 201: { "data": { "id": "6650c2", "message": "Thanks, we will get back to you soon." } }
website is a honeypot field; a non-empty value is silently discarded.
Admin list GET /api/admin/faq-enquiries?status=new&page=1&limit=20
Admin update PATCH /api/admin/faq-enquiries/:id
Request: { "status": "in_progress", "assignedTo": "64ab12" }
Notifications: send a confirmation email to the customer, and notify admin users who enabled it in Manage Notifications (email or SMS).
Acceptance criteria:
- Invalid email or message under 10 characters returns 400.
- Maximum 5 submissions per hour per IP.
- New enquiry appears in the admin list with status
new.
6. Default FAQ seeder for new tenants
Keep US and India template sets as JSON in the codebase. Run the seeder at the end of tenant provisioning (after the primary warehouse is created). Seeding must be idempotent: running it twice creates no duplicates. Tenants own the entries afterwards.
Sample template entry
{ "category": "Shipping", "region": "US", "order": 1,
"question": "How long will delivery take?",
"answer": "<p>Orders ship within 1-2 business days via FedEx.</p>", "templateKey": "us-shipping-1" }
Template categories
- US: Returns and Refunds, Shipping, Sales Tax, Payments, Warranty, Privacy, Subscriptions, Gift Cards
- India: Cash on Delivery, UPI and Payments, EMI, Pincode Delivery, GST Invoice, Returns and Replacement, Authenticity, Offers, Support
Acceptance criteria:
- New tenant has default categories and FAQs after onboarding.
- Re-running the seeder does not overwrite tenant edits (match on
templateKey). - Seeder failure does not block tenant creation; it is logged and can be retried.
7. Redis cache for the public API
Cache public list responses. Each tenant has its own DB, so the key must include the tenant identifier.
- Key:
{tenantKey}:faq:public:{region}:{placement}:{category} - TTL: 10 minutes
- Invalidate all
{tenantKey}:faq:public:*keys on any admin create, update, delete, reorder or publish.
Acceptance criteria:
- A second identical request is served from cache.
- An admin change is visible on the storefront immediately.
- No cache key is shared between tenants.
8. Validation, RBAC, sanitization and rate limiting
- DTOs with class-validator on every endpoint.
- Admin routes allow only Admin and Manager roles.
- HTML sanitizer allow-list for
answer: p, br, ul, ol, li, strong, em, a (http/https only), h3, h4. - Rate limits: enquiry 5/hour/IP, feedback 30/hour/IP.
9. Unit and e2e tests
Cover: tenant isolation (tenant A cannot read tenant B FAQs), draft hidden from public, reorder, sanitization, region filter, idempotent seeder, cache invalidation, enquiry rate limit.
Subtask done when: all nine checklist items are ticked, tests pass, and the frontend can run against the APIs above.
Subtask 2: Frontend
Subject: [FAQ][FE] FAQ frontend: storefront FAQ page, help form, admin screens Tracker: Task | Priority: Normal | Parent: [FAQ] Dynamic FAQ management and storefront FAQ page | Depends on: Backend subtask
Description: Build all React screens for the FAQ module: the storefront FAQ page with the help form, the reusable FAQ widget for product, checkout and order pages, and the admin screens for categories, FAQs and enquiries. Work in nine parts. Each screen can start with mock data copied from the Backend subtask examples and switch to the real API when it is ready.
Checklist:
- [ ] 1. Storefront FAQ page: list, categories, search
- [ ] 2. Help form beside the FAQ list
- [ ] 3. Helpful votes
- [ ] 4. Contextual FAQ widget on product, checkout and order pages
- [ ] 5. Admin: FAQ categories screen
- [ ] 6. Admin: FAQ list and editor
- [ ] 7. Admin: enquiries inbox
- [ ] 8. SEO: FAQPage structured data
- [ ] 9. QA, responsive and accessibility pass
1. Storefront FAQ page: list, categories, search
Build the Frequently Asked Questions page. Left side: numbered accordion list grouped by category, with a category filter and search box on top. Right side: the help form (part 2). On mobile the form moves below the list.
Calls: GET /api/faqs?category=shipping&q=refund®ion=US
Acceptance criteria:
- Search is debounced (300 ms) and shows an empty state message when nothing matches.
- Only one answer is open at a time; an open item can be shared with a URL hash such as
#faq-6650b7. - Loading skeleton while fetching; error state with a retry button.
- Answer HTML is rendered safely.
2. Help form beside the FAQ list
Sticky form on the right with Name, Email, Phone (optional), Order number (optional), Message and a blue Submit button. Includes the hidden honeypot field website.
Calls: POST /api/faq-enquiries
Body: { "name": "Asha", "email": "asha@example.com", "message": "My refund has not arrived.", "website": "" }
Acceptance criteria:
- Inline validation messages; the button is disabled while submitting.
- Success message on 201 and the form is cleared.
- A 429 response shows "Too many requests, please try again later."
3. Helpful votes
Show "Was this helpful? Yes / No" under each answer. Calls POST /api/faqs/:id/feedback with { "helpful": true }. After voting, show "Thanks for your feedback" and remember the vote in localStorage so the buttons stay disabled.
4. Contextual FAQ widget on product, checkout and order pages
Reusable component, for example <FaqList placement="product" />, with a "View all FAQs" link.
- Product page: shipping and return questions
- Checkout: payment and tax questions
- Order details: tracking and return questions
Calls: GET /api/faqs?placement=product®ion=US
5. Admin: FAQ categories screen
Table with name, order, FAQ count and an active toggle. Add and edit in a modal. Delete is blocked and shows the server message when the category has FAQs. Drag to reorder updates order.
Calls: GET, POST /api/admin/faq-categories, PATCH, DELETE /api/admin/faq-categories/:id
6. Admin: FAQ list and editor
- List with filters: category, status, region, search; pagination.
- Editor: category, question, rich-text answer, region, placements (multi-select), status.
- Drag to reorder within a category.
- Publish and unpublish toggle per row.
- Preview button showing how the FAQ looks on the storefront.
- "Import defaults" button to load the US or India template again (uses the seeder logic from the Backend subtask).
Calls: GET, POST /api/admin/faqs, GET, PATCH, DELETE /api/admin/faqs/:id, PATCH /api/admin/faqs/reorder, PATCH /api/admin/faqs/:id/publish
Acceptance criteria: unsaved-changes warning, API validation messages shown on the field, success toast after save.
7. Admin: enquiries inbox
List with status filter (New, In progress, Closed), a detail drawer showing the message and order number, assign to a user, and change status. Show a badge with the count of new enquiries on the menu item.
Calls: GET /api/admin/faq-enquiries?status=new, PATCH /api/admin/faq-enquiries/:id
8. SEO: FAQPage structured data
Inject JSON-LD on the public FAQ page, built from the published FAQs.
{ "@context": "https://schema.org", "@type": "FAQPage",
"mainEntity": [ { "@type": "Question", "name": "How long will delivery take?",
"acceptedAnswer": { "@type": "Answer", "text": "Orders ship in 1-2 business days via FedEx." } } ] }
9. QA, responsive and accessibility pass
Test at mobile, tablet and desktop widths; keyboard navigation for the accordion; labels and error text on form fields; test with two tenants to confirm each sees only its own FAQs.
Subtask done when: all nine checklist items are ticked, an admin can create and publish a FAQ and see it on the storefront, and a help form submission appears in the enquiries inbox.