Files
cms.techvanguard.vn/utils/contentEditors/partnershipsConfig.js
T
Tống Thành Đạt 7df3a6f6bf feat(cms): refactor page content management and implement detailed editors
Refactor the CMS page content system to use a more modular configuration-driven approach. This replaces the monolithic `pageContentConfig.js` with individual configuration files for each page and introduces a shared field utility to standardize editor components.
Key changes:
- Implement a generic `_renderSingletonPageView` helper to reduce duplication across controllers.
- Enhance `_createPageContentController` with `normalizeForEditor`, `normalizeForApi`, and `preparePayload` hooks for custom data transformation.
- Create dedicated configuration and view structures for Partnerships, History, Accreditation, Admissions, and Policies pages.
- Add a specialized section editor for Policies to manage complex nested content.
- Improve the frontend `page-content-editor.js` with support for visibility logic, auto-sequencing for arrays, and URL synchronization for active tabs.
- Update data JSON files to align with the new schema.
2026-04-20 19:43:28 +07:00

161 lines
5.1 KiB
JavaScript

const {
text,
textarea,
image,
checkbox,
select,
combobox,
object,
stringList,
objectList,
} = require("./sharedFields");
const inquiryFieldOptions = [
{ value: "text", label: "Single line text" },
{ value: "textarea", label: "Paragraph" },
{ value: "select", label: "Dropdown" },
];
const widthOptions = [
{ value: "half", label: "Half width" },
{ value: "full", label: "Full width" },
];
module.exports = {
key: "partnerships",
title: "Partnerships Management",
subtitle: "Edit content displayed on the partnerships page",
routeBase: "/admin/partnerships",
apiPath: "/api/partnerships",
previewPath: "/about/partnerships",
dataFile: "partnerships",
imageType: "partnerships",
tabs: [
{
key: "hero",
label: "Hero",
icon: "fas fa-image",
schema: object("hero", "Hero", [
text("badge", "Eyebrow label", { maxLength: 40 }),
text("title", "Headline", { maxLength: 90 }),
textarea("description", "Supporting text", { maxLength: 220, rows: 4 }),
text("linkLabel", "Scroll link label", { maxLength: 40 }),
image("image", "Hero image", {
imageHint: "Recommended 720x630 px",
helpText: "Upload the image shown in the right hero panel.",
}),
text("imageAlt", "Hero image alt text", { maxLength: 120 }),
]),
},
{
key: "directory",
label: "Partner Directory",
icon: "fas fa-handshake",
schema: object("directory", "Partner directory", [
text("heading", "Section heading", { maxLength: 70 }),
textarea("description", "Section description", {
maxLength: 180,
rows: 3,
}),
stringList("tabs", "Category tabs", {
itemLabel: "Category tab",
maxLength: 30,
placeholder: "Industry",
sortable: true,
helpText:
"The frontend shows up to 3 direct tabs. Additional tabs move into a More dropdown.",
}),
text("loadMoreLabel", "Load more button label", { maxLength: 40 }),
objectList(
"partners",
"Partners",
[
text("id", "Partner key", {
maxLength: 50,
helpText:
"Use a short unique key. It keeps each card state separate on the frontend.",
}),
text("name", "Partner name", { maxLength: 90 }),
combobox("category", "Category", {
maxLength: 30,
optionsPath: "directory.tabs",
}),
textarea("summary", "Card summary", {
maxLength: 130,
rows: 3,
helpText: "The card preview is capped at 130 characters.",
}),
image("logo", "Partner logo", {
imageHint: "Recommended 105x80 px minimum visible ratio",
helpText: "Use a clean logo with transparent or simple background.",
}),
text("logoAlt", "Logo alt text", { maxLength: 120 }),
textarea("about", "About text", { maxLength: 600, rows: 5 }),
text("collabType", "Collaboration type", { maxLength: 40 }),
textarea("benefits", "Benefits", { maxLength: 240, rows: 4 }),
],
{
itemLabel: "Partner",
sortable: true,
itemTitleKey: "name",
itemSubtitleKey: "category",
emptyText: "No partners yet.",
},
),
]),
},
{
key: "cta",
label: "Call To Action",
icon: "fas fa-bullhorn",
schema: object("cta", "Call to action", [
text("heading", "Headline", { maxLength: 80 }),
textarea("description", "Supporting text", {
maxLength: 220,
rows: 4,
}),
text("buttonLabel", "Button label", { maxLength: 40 }),
]),
},
{
key: "inquiryForm",
label: "Inquiry Form",
icon: "fas fa-envelope",
schema: object("inquiryForm", "Inquiry form", [
text("title", "Modal title", { maxLength: 60 }),
objectList(
"fields",
"Form fields",
[
text("id", "Field key", {
maxLength: 40,
helpText:
"Use a short unique key such as firstName or organization.",
}),
text("label", "Field label", { maxLength: 40 }),
text("placeholder", "Placeholder text", { maxLength: 80 }),
select("type", "Field type", inquiryFieldOptions),
select("width", "Field width", widthOptions),
stringList("options", "Dropdown options", {
itemLabel: "Option",
maxLength: 50,
sortable: true,
helpText: "Only used when the field type is Dropdown.",
visibleWhen: { path: "type", equals: "select" },
}),
checkbox("required", "Required field"),
],
{
itemLabel: "Field",
sortable: true,
itemTitleKey: "label",
itemSubtitleKey: "type",
emptyText: "No inquiry fields yet.",
},
),
text("submitLabel", "Submit button label", { maxLength: 40 }),
]),
},
],
};