Files
Tống Thành Đạt a39c336dd4 feat(cms): enhance content editors with dynamic UI configs and validation
Implement a more flexible, configuration-driven approach for CMS editors across accreditation, admissions, and partnerships modules.
- Move UI labels, help texts, and field limits from hardcoded views to `editorUi` configurations in config files.
- Add server-side and client-side validation to prevent duplicate category tabs in accreditation and partnerships editors.
- Refactor partnership and admission views to dynamically render tabs and fields based on the provided configuration.
- Update field length constraints and default values across multiple content editors to better align with frontend requirements.
- Improve the admissions calculator editor with dynamic field configurations and default value fallbacks.
2026-04-22 18:06:03 +07:00

157 lines
5.0 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: "Manage the content for the partnerships page",
routeBase: "/admin/partnerships",
apiPath: "/api/partnerships",
previewPath: "/about/partnerships",
dataFile: "partnerships",
imageType: "partnerships",
editorUi: {
directory: {
tabsFrontendHint: "The frontend shows up to 3 direct tabs. Additional tabs move into a More dropdown.",
partnersHelpText: "Each partner card keeps its own open or closed state automatically.",
},
inquiryForm: {
fieldsHelpText: "Manage labels, placeholders, type, width, and dropdown options.",
},
},
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: "Add and reorder the category tabs shown on the page.",
}),
objectList(
"partners",
"Partners",
[
text("name", "Partner name", { maxLength: 90 }),
combobox("category", "Category", {
maxLength: 30,
optionsPath: "directory.tabs",
}),
textarea("summary", "Card summary", {
maxLength: 130,
rows: 3,
helpText: "Keep this short. Around 130 characters works best in the card layout.",
}),
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: 35 }),
objectList(
"fields",
"Form fields",
[
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.",
},
),
]),
},
],
};