forked from UKSOURCE/cms.lams
feat(admin): implement unified submission and newsletter management
Introduce a centralized system to manage all website form submissions and newsletter subscriptions. - Add `Submission` and `NewsletterSubscription` models with MongoDB schema validation - Implement `submissionController` and `newsletterSubscriptionController` for CRUD operations and filtering - Create a unified admin UI for reviewing submissions across different sources (home, request, contact, partnership, newsletter) - Add database migration scripts for creating collections and indexes - Refactor partnership inquiry forms to use a fixed field structure - Update admin navigation and server CORS settings to support PATCH requests
This commit is contained in:
@@ -4,6 +4,14 @@ const partnershipsConfig = require("../utils/contentEditors/partnershipsConfig")
|
||||
const createPageContentController = require("./_createPageContentController");
|
||||
const { ensureUniqueIds } = require("../utils/contentEditorIds");
|
||||
|
||||
const FIXED_INQUIRY_FIELDS = [
|
||||
{ id: "firstName", type: "text", width: "half", label: "First name", placeholder: "First name", required: true },
|
||||
{ id: "lastName", type: "text", width: "half", label: "Last name", placeholder: "Last name", required: true },
|
||||
{ id: "organization", type: "text", width: "full", label: "Organization name", placeholder: "Company or Institution", required: true },
|
||||
{ id: "partnershipType", type: "select", width: "full", label: "Partnership type", placeholder: "Select partnership type", required: true },
|
||||
{ id: "message", type: "textarea", width: "full", label: "Message", placeholder: "Tell us how you'd like to collaborate...", required: true },
|
||||
];
|
||||
|
||||
function getTabConfig(tabKey) {
|
||||
return (partnershipsConfig.tabs || []).find((tab) => tab.key === tabKey) || {};
|
||||
}
|
||||
@@ -85,7 +93,25 @@ function normalizeInquiryForm(data) {
|
||||
}
|
||||
|
||||
if (Array.isArray(data.inquiryForm.fields)) {
|
||||
return data;
|
||||
const fieldsById = new Map(data.inquiryForm.fields.map((field) => [field.id, field]));
|
||||
return {
|
||||
...data,
|
||||
inquiryForm: {
|
||||
...data.inquiryForm,
|
||||
fields: FIXED_INQUIRY_FIELDS.map((fixedField) => {
|
||||
const field = fieldsById.get(fixedField.id) || {};
|
||||
return {
|
||||
id: fixedField.id,
|
||||
label: field.label || fixedField.label,
|
||||
placeholder: field.placeholder || fixedField.placeholder,
|
||||
type: fixedField.type,
|
||||
width: ["half", "full"].includes(field.width) ? field.width : fixedField.width,
|
||||
required: field.required === undefined ? fixedField.required : Boolean(field.required),
|
||||
options: Array.isArray(field.options) ? field.options.filter(Boolean) : [],
|
||||
};
|
||||
}),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const legacyFields = data.inquiryForm.fields || {};
|
||||
@@ -110,7 +136,7 @@ function normalizeInquiryForm(data) {
|
||||
};
|
||||
}
|
||||
|
||||
function prepareInquiryPayload(payload) {
|
||||
function prepareInquiryPayload(payload, context = {}) {
|
||||
const normalized = normalizeInquiryForm(payload);
|
||||
const fields = Array.isArray(normalized?.inquiryForm?.fields)
|
||||
? normalized.inquiryForm.fields
|
||||
@@ -144,6 +170,14 @@ function prepareInquiryPayload(payload) {
|
||||
);
|
||||
}
|
||||
|
||||
const fieldsById = new Map(fields.map((field) => [field.id, field]));
|
||||
const beforeData = context.beforeData || {};
|
||||
const beforeNormalized = normalizeInquiryForm(beforeData || {});
|
||||
const beforeFields = Array.isArray(beforeNormalized?.inquiryForm?.fields)
|
||||
? beforeNormalized.inquiryForm.fields
|
||||
: [];
|
||||
const beforeFieldsById = new Map(beforeFields.map((field) => [field.id, field]));
|
||||
|
||||
return {
|
||||
...normalized,
|
||||
directory: {
|
||||
@@ -157,20 +191,18 @@ function prepareInquiryPayload(payload) {
|
||||
},
|
||||
inquiryForm: {
|
||||
...normalized.inquiryForm,
|
||||
fields: ensureUniqueIds(
|
||||
fields,
|
||||
(field) => field.id,
|
||||
(field, index) => field.label || field.placeholder || `field-${index + 1}`,
|
||||
"field",
|
||||
).map((field) => ({
|
||||
id: field.id,
|
||||
label: field.label || "",
|
||||
placeholder: field.placeholder || "",
|
||||
type: field.type || "text",
|
||||
width: field.width || "full",
|
||||
required: Boolean(field.required),
|
||||
options: Array.isArray(field.options) ? field.options.filter(Boolean) : [],
|
||||
})),
|
||||
fields: FIXED_INQUIRY_FIELDS.map((fixedField) => {
|
||||
const field = fieldsById.get(fixedField.id) || beforeFieldsById.get(fixedField.id) || {};
|
||||
return {
|
||||
id: fixedField.id,
|
||||
label: field.label || fixedField.label,
|
||||
placeholder: field.placeholder || fixedField.placeholder,
|
||||
type: fixedField.type,
|
||||
width: ["half", "full"].includes(field.width) ? field.width : fixedField.width,
|
||||
required: field.required === undefined ? fixedField.required : Boolean(field.required),
|
||||
options: Array.isArray(field.options) ? field.options.filter(Boolean) : [],
|
||||
};
|
||||
}),
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -195,6 +227,9 @@ controller.index = async function index(req, res) {
|
||||
process.env.BACKEND_URL ?? `${req.protocol}://${req.get("host")}`;
|
||||
const defaultTab = partnershipsConfig.tabs[0]?.key;
|
||||
const requestedTab = req.query.tab;
|
||||
if (requestedTab === "submissions") {
|
||||
return res.redirect("/admin/submissions?tab=partnership");
|
||||
}
|
||||
const activeTab = partnershipsConfig.tabs.some((tab) => tab.key === requestedTab)
|
||||
? requestedTab
|
||||
: defaultTab;
|
||||
|
||||
Reference in New Issue
Block a user