forked from UKSOURCE/cms.lams
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.
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
const {
|
||||
text,
|
||||
textarea,
|
||||
image,
|
||||
icon,
|
||||
url,
|
||||
select,
|
||||
combobox,
|
||||
object,
|
||||
stringList,
|
||||
objectList,
|
||||
} = require("./sharedFields");
|
||||
|
||||
const statusOptions = [
|
||||
{ value: "Active", label: "Active" },
|
||||
{ value: "Inactive", label: "Inactive" },
|
||||
];
|
||||
|
||||
module.exports = {
|
||||
key: "accreditation",
|
||||
title: "Accreditation Management",
|
||||
subtitle: "Edit content displayed on the accreditation page",
|
||||
routeBase: "/admin/accreditation",
|
||||
apiPath: "/api/accreditation",
|
||||
previewPath: "/about/accreditation",
|
||||
dataFile: "accreditation",
|
||||
imageType: "accreditation",
|
||||
tabs: [
|
||||
{
|
||||
key: "trustBanner",
|
||||
label: "Trust Banner",
|
||||
icon: "fas fa-shield-check",
|
||||
schema: object("trustBanner", "Trust banner", [
|
||||
icon("icon", "Banner icon"),
|
||||
text("text", "Banner message", {
|
||||
maxLength: 120,
|
||||
helpText:
|
||||
"This is the slim credibility strip shown above the accreditation content.",
|
||||
}),
|
||||
objectList(
|
||||
"links",
|
||||
"Banner links",
|
||||
[
|
||||
text("label", "Link label", { maxLength: 40 }),
|
||||
url("href", "Link URL", { maxLength: 255 }),
|
||||
icon("icon", "Link icon"),
|
||||
],
|
||||
{
|
||||
itemLabel: "Link",
|
||||
sortable: true,
|
||||
emptyText: "No trust banner links yet.",
|
||||
},
|
||||
),
|
||||
]),
|
||||
},
|
||||
{
|
||||
key: "hero",
|
||||
label: "Hero",
|
||||
icon: "fas fa-image",
|
||||
schema: object("hero", "Hero", [
|
||||
text("badge", "Eyebrow label", { maxLength: 40 }),
|
||||
text("title", "Headline", { maxLength: 60 }),
|
||||
textarea("description", "Supporting text", { maxLength: 420, rows: 5 }),
|
||||
]),
|
||||
},
|
||||
{
|
||||
key: "grid",
|
||||
label: "Accreditation Grid",
|
||||
icon: "fas fa-table-cells-large",
|
||||
schema: object("grid", "Accreditation grid", [
|
||||
stringList("tabs", "Category tabs", {
|
||||
itemLabel: "Tab",
|
||||
maxLength: 30,
|
||||
sortable: true,
|
||||
helpText:
|
||||
"The frontend shows up to 3 direct tabs. Additional tabs move into a More dropdown.",
|
||||
}),
|
||||
objectList(
|
||||
"items",
|
||||
"Accreditation cards",
|
||||
[
|
||||
icon("icon", "Fallback icon"),
|
||||
image("image", "Card image", {
|
||||
imageHint: "Recommended 118x58 px minimum visible ratio",
|
||||
helpText: "Logo or badge used at the top of the card.",
|
||||
}),
|
||||
select("status", "Status", statusOptions),
|
||||
combobox("category", "Category", {
|
||||
maxLength: 30,
|
||||
optionsPath: "grid.tabs",
|
||||
}),
|
||||
text("title", "Card title", {
|
||||
maxLength: 17,
|
||||
helpText: "Frontend title space is capped at 17 characters.",
|
||||
}),
|
||||
textarea("description", "Card description", {
|
||||
maxLength: 300,
|
||||
rows: 5,
|
||||
helpText: "Frontend description preview is capped at 300 characters.",
|
||||
}),
|
||||
text("scopeLabel", "Scope label", { maxLength: 20 }),
|
||||
text("scope", "Scope text", { maxLength: 60 }),
|
||||
text("validUntilLabel", "Validity label", { maxLength: 24 }),
|
||||
text("validUntil", "Validity text", { maxLength: 40 }),
|
||||
text("buttonLabel", "Certificate button label", { maxLength: 30 }),
|
||||
url("certificateHref", "Certificate URL", { maxLength: 255 }),
|
||||
],
|
||||
{
|
||||
itemLabel: "Accreditation",
|
||||
sortable: true,
|
||||
itemTitleKey: "title",
|
||||
itemSubtitleKey: "category",
|
||||
emptyText: "No accreditation cards yet.",
|
||||
},
|
||||
),
|
||||
]),
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,221 @@
|
||||
const {
|
||||
text,
|
||||
hidden,
|
||||
textarea,
|
||||
image,
|
||||
icon,
|
||||
checkbox,
|
||||
object,
|
||||
stringList,
|
||||
objectList,
|
||||
linkFields,
|
||||
} = require("./sharedFields");
|
||||
|
||||
module.exports = {
|
||||
key: "admissions",
|
||||
title: "Admissions Management",
|
||||
subtitle: "Edit content displayed on the admissions page",
|
||||
routeBase: "/admin/admissions",
|
||||
apiPath: "/api/admissions",
|
||||
previewPath: "/admissions",
|
||||
dataFile: "admissions",
|
||||
imageType: "admissions",
|
||||
tabs: [
|
||||
{
|
||||
key: "hero",
|
||||
label: "Hero",
|
||||
icon: "fas fa-image",
|
||||
schema: object("hero", "Hero", [
|
||||
text("badge", "Eyebrow label", { maxLength: 40 }),
|
||||
text("title", "Headline", { maxLength: 80 }),
|
||||
textarea("description", "Supporting text", { maxLength: 240, rows: 4 }),
|
||||
object("primaryCta", "Primary button", linkFields("Primary button")),
|
||||
object("secondaryCta", "Secondary button", linkFields("Secondary button")),
|
||||
image("image", "Hero image", {
|
||||
imageHint: "Recommended 720x646 px",
|
||||
helpText: "Large image rendered in the right hero panel.",
|
||||
}),
|
||||
text("imageAlt", "Hero image alt text", { maxLength: 120 }),
|
||||
]),
|
||||
},
|
||||
{
|
||||
key: "process",
|
||||
label: "Admissions Process",
|
||||
icon: "fas fa-list-ol",
|
||||
schema: object("process", "Admissions process", [
|
||||
text("id", "Section key", { maxLength: 40 }),
|
||||
text("title", "Section title", { maxLength: 60 }),
|
||||
textarea("description", "Section description", {
|
||||
maxLength: 180,
|
||||
rows: 3,
|
||||
}),
|
||||
objectList(
|
||||
"steps",
|
||||
"Steps",
|
||||
[
|
||||
hidden("number", { autoSequence: { padLength: 2 } }),
|
||||
text("title", "Step title", { maxLength: 50 }),
|
||||
textarea("description", "Step description", {
|
||||
maxLength: 180,
|
||||
rows: 3,
|
||||
}),
|
||||
checkbox("active", "Highlight this step"),
|
||||
],
|
||||
{
|
||||
itemLabel: "Step",
|
||||
sortable: true,
|
||||
itemTitleKey: "title",
|
||||
itemSubtitleKey: "number",
|
||||
emptyText: "No process steps yet.",
|
||||
},
|
||||
),
|
||||
]),
|
||||
},
|
||||
{
|
||||
key: "eligibility",
|
||||
label: "Eligibility",
|
||||
icon: "fas fa-check-circle",
|
||||
schema: object("eligibility", "Eligibility", [
|
||||
text("id", "Section key", { maxLength: 40 }),
|
||||
text("title", "Section title", { maxLength: 60 }),
|
||||
objectList(
|
||||
"cards",
|
||||
"Eligibility cards",
|
||||
[
|
||||
text("title", "Card title", { maxLength: 50 }),
|
||||
icon("icon", "Card icon"),
|
||||
stringList("items", "Checklist items", {
|
||||
itemLabel: "Checklist item",
|
||||
maxLength: 120,
|
||||
sortable: true,
|
||||
}),
|
||||
],
|
||||
{
|
||||
itemLabel: "Card",
|
||||
sortable: true,
|
||||
itemTitleKey: "title",
|
||||
emptyText: "No eligibility cards yet.",
|
||||
},
|
||||
),
|
||||
]),
|
||||
},
|
||||
{
|
||||
key: "tuition",
|
||||
label: "Tuition",
|
||||
icon: "fas fa-chart-column",
|
||||
schema: object("tuition", "Tuition", [
|
||||
text("id", "Section key", { maxLength: 40 }),
|
||||
text("title", "Section title", { maxLength: 60 }),
|
||||
text("chartTitle", "Chart title", { maxLength: 60 }),
|
||||
textarea("chartDescription", "Chart description", {
|
||||
maxLength: 140,
|
||||
rows: 3,
|
||||
}),
|
||||
objectList(
|
||||
"series",
|
||||
"Chart series",
|
||||
[
|
||||
text("label", "Series label", { maxLength: 40 }),
|
||||
{ key: "color", label: "Series color", type: "color" },
|
||||
stringList("values", "Data points", {
|
||||
itemLabel: "Point",
|
||||
fieldType: "number",
|
||||
sortable: true,
|
||||
}),
|
||||
],
|
||||
{
|
||||
itemLabel: "Series",
|
||||
sortable: true,
|
||||
itemTitleKey: "label",
|
||||
emptyText: "No chart series yet.",
|
||||
},
|
||||
),
|
||||
]),
|
||||
},
|
||||
{
|
||||
key: "keyDates",
|
||||
label: "Key Dates",
|
||||
icon: "fas fa-calendar-days",
|
||||
schema: object("keyDates", "Key dates", [
|
||||
text("id", "Section key", { maxLength: 40 }),
|
||||
text("title", "Section title", { maxLength: 60 }),
|
||||
stringList("columns", "Table columns", {
|
||||
itemLabel: "Column",
|
||||
maxLength: 40,
|
||||
sortable: true,
|
||||
}),
|
||||
objectList(
|
||||
"rows",
|
||||
"Table rows",
|
||||
[
|
||||
text("term", "Term", { maxLength: 40 }),
|
||||
text("applicationDeadline", "Application deadline", {
|
||||
maxLength: 40,
|
||||
}),
|
||||
text("classesStart", "Classes start", { maxLength: 40 }),
|
||||
],
|
||||
{
|
||||
itemLabel: "Row",
|
||||
sortable: true,
|
||||
itemTitleKey: "term",
|
||||
emptyText: "No key date rows yet.",
|
||||
},
|
||||
),
|
||||
]),
|
||||
},
|
||||
{
|
||||
key: "calculator",
|
||||
label: "Calculator",
|
||||
icon: "fas fa-calculator",
|
||||
schema: object("calculator", "Calculator", [
|
||||
text("title", "Card title", { maxLength: 60 }),
|
||||
textarea("description", "Card description", {
|
||||
maxLength: 120,
|
||||
rows: 3,
|
||||
}),
|
||||
stringList("modelOptions", "Model options", {
|
||||
itemLabel: "Option",
|
||||
maxLength: 30,
|
||||
sortable: true,
|
||||
}),
|
||||
text("paceLabel", "Pace label", { maxLength: 30 }),
|
||||
text("minPaceLabel", "Minimum pace label", { maxLength: 20 }),
|
||||
text("maxPaceLabel", "Maximum pace label", { maxLength: 20 }),
|
||||
text("resultLabel", "Result label", { maxLength: 40 }),
|
||||
text("monthlyAmount", "Monthly amount", { maxLength: 20 }),
|
||||
text("monthlySuffix", "Monthly suffix", { maxLength: 10 }),
|
||||
icon("noteIcon", "Note icon"),
|
||||
text("note", "Note text", { maxLength: 60 }),
|
||||
object("cta", "Button", linkFields("Button")),
|
||||
]),
|
||||
},
|
||||
{
|
||||
key: "scholarships",
|
||||
label: "Scholarships",
|
||||
icon: "fas fa-award",
|
||||
schema: object("scholarships", "Scholarships", [
|
||||
text("title", "Card title", { maxLength: 50 }),
|
||||
icon("icon", "Card icon"),
|
||||
objectList(
|
||||
"items",
|
||||
"Scholarship items",
|
||||
[
|
||||
text("title", "Title", { maxLength: 50 }),
|
||||
text("amount", "Amount", { maxLength: 24 }),
|
||||
textarea("description", "Description", {
|
||||
maxLength: 160,
|
||||
rows: 3,
|
||||
}),
|
||||
],
|
||||
{
|
||||
itemLabel: "Scholarship item",
|
||||
sortable: true,
|
||||
itemTitleKey: "title",
|
||||
itemSubtitleKey: "amount",
|
||||
emptyText: "No scholarship items yet.",
|
||||
},
|
||||
),
|
||||
]),
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,123 @@
|
||||
const {
|
||||
text,
|
||||
textarea,
|
||||
image,
|
||||
icon,
|
||||
checkbox,
|
||||
url,
|
||||
combobox,
|
||||
object,
|
||||
stringList,
|
||||
objectList,
|
||||
} = require("./sharedFields");
|
||||
|
||||
module.exports = {
|
||||
key: "history",
|
||||
title: "History Management",
|
||||
subtitle: "Edit content displayed on the history page",
|
||||
routeBase: "/admin/history",
|
||||
apiPath: "/api/history",
|
||||
previewPath: "/about/history",
|
||||
dataFile: "history",
|
||||
imageType: "history",
|
||||
tabs: [
|
||||
{
|
||||
key: "highlight",
|
||||
label: "Highlight Bar",
|
||||
icon: "fas fa-star",
|
||||
schema: object("highlight", "Highlight bar", [
|
||||
icon("icon", "Highlight icon"),
|
||||
text("text", "Message", { maxLength: 110 }),
|
||||
text("linkLabel", "Link label", { maxLength: 30 }),
|
||||
url("href", "Link URL", { maxLength: 255 }),
|
||||
]),
|
||||
},
|
||||
{
|
||||
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 }),
|
||||
]),
|
||||
},
|
||||
{
|
||||
key: "filters",
|
||||
label: "Filter Controls",
|
||||
icon: "fas fa-filter",
|
||||
schema: object("filters", "Filter controls", [
|
||||
text("yearLabel", "Year filter label", { maxLength: 30 }),
|
||||
text("categoryLabel", "Category filter label", { maxLength: 30 }),
|
||||
text("buttonLabel", "Apply button label", { maxLength: 30 }),
|
||||
stringList("yearOptions", "Year options", {
|
||||
itemLabel: "Year option",
|
||||
maxLength: 30,
|
||||
sortable: true,
|
||||
helpText: "Reorder to control the dropdown order shown to users.",
|
||||
}),
|
||||
stringList("categoryOptions", "Category options", {
|
||||
itemLabel: "Category option",
|
||||
maxLength: 40,
|
||||
sortable: true,
|
||||
}),
|
||||
]),
|
||||
},
|
||||
{
|
||||
key: "timeline",
|
||||
label: "Timeline",
|
||||
icon: "fas fa-clock-rotate-left",
|
||||
schema: object("timeline", "Timeline", [
|
||||
text("loadMoreLabel", "Load more button label", { maxLength: 40 }),
|
||||
objectList(
|
||||
"items",
|
||||
"Milestones",
|
||||
[
|
||||
text("id", "Milestone key", { maxLength: 60 }),
|
||||
text("year", "Year", { maxLength: 10 }),
|
||||
combobox("yearRange", "Year range", {
|
||||
maxLength: 30,
|
||||
optionsPath: "filters.yearOptions",
|
||||
}),
|
||||
combobox("category", "Category", {
|
||||
maxLength: 40,
|
||||
optionsPath: "filters.categoryOptions",
|
||||
}),
|
||||
text("categoryLabel", "Category badge label", { maxLength: 30 }),
|
||||
text("title", "Milestone title", { maxLength: 90 }),
|
||||
textarea("description", "Description", {
|
||||
maxLength: 260,
|
||||
rows: 4,
|
||||
}),
|
||||
image("image", "Milestone image", {
|
||||
imageHint: "Recommended 436x190 px",
|
||||
helpText: "Wide image used inside the milestone card.",
|
||||
}),
|
||||
text("imageAlt", "Image alt text", { maxLength: 120 }),
|
||||
objectList(
|
||||
"stats",
|
||||
"Stats",
|
||||
[
|
||||
text("value", "Value", { maxLength: 24 }),
|
||||
text("label", "Label", { maxLength: 50 }),
|
||||
],
|
||||
{
|
||||
itemLabel: "Stat",
|
||||
sortable: true,
|
||||
emptyText: "No stats yet.",
|
||||
},
|
||||
),
|
||||
checkbox("featured", "Featured milestone"),
|
||||
],
|
||||
{
|
||||
itemLabel: "Milestone",
|
||||
sortable: true,
|
||||
itemTitleKey: "title",
|
||||
itemSubtitleKey: "year",
|
||||
emptyText: "No milestones yet.",
|
||||
},
|
||||
),
|
||||
]),
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,160 @@
|
||||
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 }),
|
||||
]),
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,208 @@
|
||||
const {
|
||||
text,
|
||||
textarea,
|
||||
icon,
|
||||
url,
|
||||
combobox,
|
||||
object,
|
||||
objectList,
|
||||
stringList,
|
||||
variantList,
|
||||
} = require("./sharedFields");
|
||||
|
||||
const baseConfig = {
|
||||
key: "policies",
|
||||
title: "Policies Management",
|
||||
subtitle: "Edit content displayed on the policies page",
|
||||
routeBase: "/admin/policies",
|
||||
apiPath: "/api/policies",
|
||||
previewPath: "/policies",
|
||||
dataFile: "policies",
|
||||
imageType: "policies",
|
||||
tabs: [
|
||||
{
|
||||
key: "hero",
|
||||
label: "Hero",
|
||||
icon: "fas fa-scale-balanced",
|
||||
schema: object("hero", "Hero", [
|
||||
text("badge", "Eyebrow label", { maxLength: 40 }),
|
||||
icon("icon", "Hero icon", {
|
||||
helpText: "Policies uses icon-only controls and does not require image upload.",
|
||||
}),
|
||||
text("titlePrefix", "Headline prefix", { maxLength: 50 }),
|
||||
text("titleHighlight", "Headline highlight", { maxLength: 40 }),
|
||||
textarea("description", "Supporting text", {
|
||||
maxLength: 220,
|
||||
rows: 4,
|
||||
}),
|
||||
]),
|
||||
},
|
||||
{
|
||||
key: "sidebar",
|
||||
label: "Sidebar",
|
||||
icon: "fas fa-bars",
|
||||
schema: object("sidebar", "Sidebar", [
|
||||
text("heading", "Sidebar heading", { maxLength: 30 }),
|
||||
text("helperText", "Helper text", { maxLength: 60 }),
|
||||
text("contactLabel", "Contact link label", { maxLength: 40 }),
|
||||
url("contactHref", "Contact URL", { maxLength: 255 }),
|
||||
]),
|
||||
},
|
||||
{
|
||||
key: "policies",
|
||||
label: "Policies",
|
||||
icon: "fas fa-file-lines",
|
||||
schema: objectList(
|
||||
"policies",
|
||||
"Policies",
|
||||
[
|
||||
text("id", "Policy key", { maxLength: 40 }),
|
||||
text("navLabel", "Sidebar label", { maxLength: 40 }),
|
||||
text("title", "Policy title", { maxLength: 70 }),
|
||||
text("effectiveDate", "Effective date", { maxLength: 50 }),
|
||||
textarea("intro", "Intro text", { maxLength: 260, rows: 4 }),
|
||||
],
|
||||
{
|
||||
itemLabel: "Policy",
|
||||
sortable: true,
|
||||
itemTitleKey: "title",
|
||||
itemSubtitleKey: "effectiveDate",
|
||||
emptyText: "No policies yet.",
|
||||
itemActions: [
|
||||
{
|
||||
label: "Edit Sections",
|
||||
icon: "fas fa-pen-to-square",
|
||||
hrefTemplate: "/admin/policies/{id}/section",
|
||||
className: "btn btn-outline-primary btn-sm",
|
||||
},
|
||||
],
|
||||
},
|
||||
),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
function createPoliciesSectionEditorConfig(policy, allPolicies = []) {
|
||||
const policyOptions = allPolicies
|
||||
.filter((item) => item.id && item.id !== policy.id)
|
||||
.map((item) => ({
|
||||
value: item.id,
|
||||
label: item.title || item.navLabel || item.id,
|
||||
}));
|
||||
|
||||
return {
|
||||
key: "policySections",
|
||||
title: `Policy Sections: ${policy.title || policy.id}`,
|
||||
subtitle: "Manage section content and paragraph order for this policy",
|
||||
routeBase: `/admin/policies/${policy.id}/section`,
|
||||
previewPath: "/policies",
|
||||
imageType: "policies",
|
||||
tabs: [
|
||||
{
|
||||
key: "sections",
|
||||
label: "Sections",
|
||||
icon: "fas fa-file-lines",
|
||||
schema: variantList(
|
||||
"sections",
|
||||
"Sections",
|
||||
{
|
||||
text: {
|
||||
label: "Text section",
|
||||
schema: object("section", "Text section", [
|
||||
text("heading", "Section heading", { maxLength: 60 }),
|
||||
objectList(
|
||||
"paragraphs",
|
||||
"Paragraphs",
|
||||
[
|
||||
textarea("text", "Paragraph text", {
|
||||
maxLength: 500,
|
||||
rows: 4,
|
||||
}),
|
||||
objectList(
|
||||
"links",
|
||||
"Inline links",
|
||||
[
|
||||
text("label", "Link label", { maxLength: 50 }),
|
||||
url("href", "Link URL", { maxLength: 255 }),
|
||||
combobox("tabId", "Switch to policy", {
|
||||
maxLength: 40,
|
||||
options: policyOptions,
|
||||
helpText:
|
||||
"Optional. Choose another policy to switch tabs on the frontend.",
|
||||
}),
|
||||
],
|
||||
{
|
||||
itemLabel: "Link",
|
||||
sortable: true,
|
||||
emptyText: "No inline links yet.",
|
||||
},
|
||||
),
|
||||
],
|
||||
{
|
||||
itemLabel: "Paragraph",
|
||||
sortable: true,
|
||||
emptyText: "No paragraphs yet.",
|
||||
},
|
||||
),
|
||||
]),
|
||||
},
|
||||
list: {
|
||||
label: "List section",
|
||||
schema: object("section", "List section", [
|
||||
text("heading", "Section heading", { maxLength: 60 }),
|
||||
textarea("intro", "Intro text", { maxLength: 220, rows: 3 }),
|
||||
stringList("items", "List items", {
|
||||
itemLabel: "List item",
|
||||
maxLength: 180,
|
||||
fieldType: "textarea",
|
||||
sortable: true,
|
||||
emptyText: "No list items yet.",
|
||||
}),
|
||||
]),
|
||||
},
|
||||
cards: {
|
||||
label: "Card section",
|
||||
schema: object("section", "Card section", [
|
||||
objectList(
|
||||
"cards",
|
||||
"Cards",
|
||||
[
|
||||
icon("icon", "Card icon"),
|
||||
text("title", "Card title", { maxLength: 50 }),
|
||||
textarea("description", "Card description", {
|
||||
maxLength: 220,
|
||||
rows: 4,
|
||||
}),
|
||||
object("link", "Card link", [
|
||||
text("label", "Link label", { maxLength: 50 }),
|
||||
url("href", "Link URL", { maxLength: 255 }),
|
||||
combobox("tabId", "Switch to policy", {
|
||||
maxLength: 40,
|
||||
options: policyOptions,
|
||||
}),
|
||||
]),
|
||||
],
|
||||
{
|
||||
itemLabel: "Card",
|
||||
sortable: true,
|
||||
emptyText: "No cards yet.",
|
||||
},
|
||||
),
|
||||
]),
|
||||
},
|
||||
},
|
||||
{
|
||||
itemLabel: "Section",
|
||||
sortable: true,
|
||||
emptyText: "No sections yet.",
|
||||
},
|
||||
),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
baseConfig,
|
||||
createPoliciesSectionEditorConfig,
|
||||
};
|
||||
@@ -0,0 +1,182 @@
|
||||
const ICON_OPTIONS = [
|
||||
"fa-scale-balanced",
|
||||
"fa-shield-check",
|
||||
"fa-magnifying-glass",
|
||||
"fa-graduation-cap",
|
||||
"fa-check-double",
|
||||
"fa-building-columns",
|
||||
"fa-award",
|
||||
"fa-trophy",
|
||||
"fa-arrow-down",
|
||||
"fa-arrow-right",
|
||||
"fa-check-circle",
|
||||
"fa-exchange-alt",
|
||||
"fa-bolt",
|
||||
"fa-book-open",
|
||||
"fa-credit-card",
|
||||
"fa-calendar-days",
|
||||
"fa-envelope",
|
||||
"fa-file-lines",
|
||||
"fa-globe",
|
||||
"fa-briefcase",
|
||||
"fa-handshake",
|
||||
"fa-circle-info",
|
||||
"fa-star",
|
||||
"fa-list-check",
|
||||
];
|
||||
|
||||
const text = (key, label, options = {}) => ({
|
||||
key,
|
||||
label,
|
||||
type: "text",
|
||||
...options,
|
||||
});
|
||||
|
||||
const hidden = (key, options = {}) => ({
|
||||
key,
|
||||
type: "hidden",
|
||||
...options,
|
||||
});
|
||||
|
||||
const textarea = (key, label, options = {}) => ({
|
||||
key,
|
||||
label,
|
||||
type: "textarea",
|
||||
rows: options.rows || 4,
|
||||
...options,
|
||||
});
|
||||
|
||||
const image = (key, label, options = {}) => ({
|
||||
key,
|
||||
label,
|
||||
type: "image",
|
||||
...options,
|
||||
});
|
||||
|
||||
const icon = (key, label, options = {}) => ({
|
||||
key,
|
||||
label,
|
||||
type: "icon",
|
||||
options: options.options || ICON_OPTIONS,
|
||||
...options,
|
||||
});
|
||||
|
||||
const checkbox = (key, label, options = {}) => ({
|
||||
key,
|
||||
label,
|
||||
type: "checkbox",
|
||||
...options,
|
||||
});
|
||||
|
||||
const url = (key, label, options = {}) => ({
|
||||
key,
|
||||
label,
|
||||
type: "url",
|
||||
...options,
|
||||
});
|
||||
|
||||
const select = (key, label, options = [], extra = {}) => ({
|
||||
key,
|
||||
label,
|
||||
type: "select",
|
||||
options,
|
||||
...extra,
|
||||
});
|
||||
|
||||
const combobox = (key, label, options = {}) => ({
|
||||
key,
|
||||
label,
|
||||
type: "combobox",
|
||||
...options,
|
||||
});
|
||||
|
||||
const object = (key, label, fields, options = {}) => ({
|
||||
key,
|
||||
label,
|
||||
type: "object",
|
||||
fields,
|
||||
...options,
|
||||
});
|
||||
|
||||
const stringList = (key, label, options = {}) => ({
|
||||
key,
|
||||
label,
|
||||
type: "array",
|
||||
itemLabel: options.itemLabel || "Item",
|
||||
sortable: options.sortable,
|
||||
addLabel: options.addLabel,
|
||||
helpText: options.helpText,
|
||||
emptyText: options.emptyText,
|
||||
itemSchema: {
|
||||
type: "primitive",
|
||||
fieldType: options.fieldType || "text",
|
||||
label: options.itemLabel || "Item",
|
||||
maxLength: options.maxLength,
|
||||
placeholder: options.placeholder,
|
||||
helpText: options.itemHelpText,
|
||||
},
|
||||
...options,
|
||||
});
|
||||
|
||||
const objectList = (key, label, fields, options = {}) => ({
|
||||
key,
|
||||
label,
|
||||
type: "array",
|
||||
itemLabel: options.itemLabel || "Item",
|
||||
sortable: options.sortable,
|
||||
addLabel: options.addLabel,
|
||||
helpText: options.helpText,
|
||||
emptyText: options.emptyText,
|
||||
itemTitleKey: options.itemTitleKey,
|
||||
itemSubtitleKey: options.itemSubtitleKey,
|
||||
itemActions: options.itemActions,
|
||||
itemSchema: {
|
||||
type: "object",
|
||||
fields,
|
||||
},
|
||||
...options,
|
||||
});
|
||||
|
||||
const variantList = (key, label, variants, options = {}) => ({
|
||||
key,
|
||||
label,
|
||||
type: "array",
|
||||
itemLabel: options.itemLabel || "Item",
|
||||
sortable: options.sortable,
|
||||
addLabel: options.addLabel,
|
||||
helpText: options.helpText,
|
||||
emptyText: options.emptyText,
|
||||
itemSchema: {
|
||||
type: "variant",
|
||||
discriminator: "type",
|
||||
options: Object.keys(variants).map((value) => ({
|
||||
value,
|
||||
label: variants[value].label,
|
||||
})),
|
||||
variants,
|
||||
},
|
||||
...options,
|
||||
});
|
||||
|
||||
const linkFields = (prefix = "Link") => [
|
||||
text("label", `${prefix} label`, { maxLength: 60 }),
|
||||
url("href", `${prefix} URL`, { maxLength: 255 }),
|
||||
];
|
||||
|
||||
module.exports = {
|
||||
ICON_OPTIONS,
|
||||
text,
|
||||
hidden,
|
||||
textarea,
|
||||
image,
|
||||
icon,
|
||||
checkbox,
|
||||
url,
|
||||
select,
|
||||
combobox,
|
||||
object,
|
||||
stringList,
|
||||
objectList,
|
||||
variantList,
|
||||
linkFields,
|
||||
};
|
||||
@@ -1,703 +0,0 @@
|
||||
const ICON_OPTIONS = [
|
||||
"fa-scale-balanced",
|
||||
"fa-shield-check",
|
||||
"fa-magnifying-glass",
|
||||
"fa-graduation-cap",
|
||||
"fa-check-double",
|
||||
"fa-building-columns",
|
||||
"fa-award",
|
||||
"fa-trophy",
|
||||
"fa-arrow-down",
|
||||
"fa-arrow-right",
|
||||
"fa-check-circle",
|
||||
"fa-exchange-alt",
|
||||
"fa-bolt",
|
||||
"fa-book-open",
|
||||
"fa-credit-card",
|
||||
];
|
||||
|
||||
const text = (key, label, options = {}) => ({
|
||||
key,
|
||||
label,
|
||||
type: "text",
|
||||
...options,
|
||||
});
|
||||
|
||||
const textarea = (key, label, options = {}) => ({
|
||||
key,
|
||||
label,
|
||||
type: "textarea",
|
||||
rows: options.rows || 4,
|
||||
...options,
|
||||
});
|
||||
|
||||
const image = (key, label, options = {}) => ({
|
||||
key,
|
||||
label,
|
||||
type: "image",
|
||||
...options,
|
||||
});
|
||||
|
||||
const icon = (key, label, options = {}) => ({
|
||||
key,
|
||||
label,
|
||||
type: "icon",
|
||||
options: options.options || ICON_OPTIONS,
|
||||
...options,
|
||||
});
|
||||
|
||||
const checkbox = (key, label, options = {}) => ({
|
||||
key,
|
||||
label,
|
||||
type: "checkbox",
|
||||
...options,
|
||||
});
|
||||
|
||||
const url = (key, label, options = {}) => ({
|
||||
key,
|
||||
label,
|
||||
type: "url",
|
||||
...options,
|
||||
});
|
||||
|
||||
const object = (key, label, fields, options = {}) => ({
|
||||
key,
|
||||
label,
|
||||
type: "object",
|
||||
fields,
|
||||
...options,
|
||||
});
|
||||
|
||||
const stringList = (key, label, options = {}) => ({
|
||||
key,
|
||||
label,
|
||||
type: "array",
|
||||
itemLabel: options.itemLabel || "Item",
|
||||
itemSchema: {
|
||||
type: "primitive",
|
||||
fieldType: options.fieldType || "text",
|
||||
label: options.itemLabel || "Item",
|
||||
maxLength: options.maxLength,
|
||||
placeholder: options.placeholder,
|
||||
helpText: options.itemHelpText,
|
||||
},
|
||||
...options,
|
||||
});
|
||||
|
||||
const objectList = (key, label, fields, options = {}) => ({
|
||||
key,
|
||||
label,
|
||||
type: "array",
|
||||
itemLabel: options.itemLabel || "Item",
|
||||
itemSchema: {
|
||||
type: "object",
|
||||
fields,
|
||||
},
|
||||
...options,
|
||||
});
|
||||
|
||||
const variantList = (key, label, variants, options = {}) => ({
|
||||
key,
|
||||
label,
|
||||
type: "array",
|
||||
itemLabel: options.itemLabel || "Item",
|
||||
itemSchema: {
|
||||
type: "variant",
|
||||
discriminator: "type",
|
||||
options: Object.keys(variants).map((value) => ({
|
||||
value,
|
||||
label: variants[value].label,
|
||||
})),
|
||||
variants,
|
||||
},
|
||||
...options,
|
||||
});
|
||||
|
||||
const linkFields = (prefix = "Link") => [
|
||||
text("label", `${prefix} Label`, { maxLength: 60 }),
|
||||
url("href", `${prefix} URL`, { maxLength: 255 }),
|
||||
];
|
||||
|
||||
module.exports = {
|
||||
partnerships: {
|
||||
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", "Badge", { maxLength: 40 }),
|
||||
text("title", "Title", { maxLength: 90 }),
|
||||
textarea("description", "Description", { maxLength: 220, rows: 4 }),
|
||||
text("linkLabel", "Link Label", { maxLength: 40 }),
|
||||
image("image", "Hero Image", {
|
||||
imageHint: "Recommended 720x630 px",
|
||||
helpText: "Upload a landscape hero image for the right panel.",
|
||||
}),
|
||||
text("imageAlt", "Hero Image Alt Text", { maxLength: 120 }),
|
||||
]),
|
||||
},
|
||||
{
|
||||
key: "directory",
|
||||
label: "Directory",
|
||||
icon: "fas fa-handshake",
|
||||
schema: object("directory", "Directory", [
|
||||
text("heading", "Heading", { maxLength: 70 }),
|
||||
textarea("description", "Description", { maxLength: 180, rows: 3 }),
|
||||
stringList("tabs", "Tabs", {
|
||||
itemLabel: "Tab",
|
||||
maxLength: 30,
|
||||
placeholder: "Industry",
|
||||
}),
|
||||
text("loadMoreLabel", "Load More Label", { maxLength: 40 }),
|
||||
objectList(
|
||||
"partners",
|
||||
"Partners",
|
||||
[
|
||||
text("id", "Partner ID", {
|
||||
maxLength: 50,
|
||||
helpText: "Stable ID used by the frontend filtering state.",
|
||||
}),
|
||||
text("name", "Name", { maxLength: 90 }),
|
||||
text("category", "Category", { maxLength: 30 }),
|
||||
textarea("summary", "Summary", {
|
||||
maxLength: 130,
|
||||
rows: 3,
|
||||
helpText:
|
||||
"The card preview uses a 130 character cap in the frontend.",
|
||||
}),
|
||||
image("logo", "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", { maxLength: 600, rows: 5 }),
|
||||
text("collabType", "Collaboration Type", { maxLength: 40 }),
|
||||
textarea("benefits", "Benefits", { maxLength: 240, rows: 4 }),
|
||||
],
|
||||
{ itemLabel: "Partner" },
|
||||
),
|
||||
]),
|
||||
},
|
||||
{
|
||||
key: "cta",
|
||||
label: "CTA",
|
||||
icon: "fas fa-bullhorn",
|
||||
schema: object("cta", "CTA", [
|
||||
text("heading", "Heading", { maxLength: 80 }),
|
||||
textarea("description", "Description", { 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", "Title", { maxLength: 60 }),
|
||||
object("fields", "Fields", [
|
||||
object("firstName", "First Name Field", [
|
||||
text("label", "Label", { maxLength: 30 }),
|
||||
text("placeholder", "Placeholder", { maxLength: 40 }),
|
||||
]),
|
||||
object("lastName", "Last Name Field", [
|
||||
text("label", "Label", { maxLength: 30 }),
|
||||
text("placeholder", "Placeholder", { maxLength: 40 }),
|
||||
]),
|
||||
object("organization", "Organization Field", [
|
||||
text("label", "Label", { maxLength: 40 }),
|
||||
text("placeholder", "Placeholder", { maxLength: 60 }),
|
||||
]),
|
||||
object("partnershipType", "Partnership Type Field", [
|
||||
text("label", "Label", { maxLength: 40 }),
|
||||
stringList("options", "Options", {
|
||||
itemLabel: "Option",
|
||||
maxLength: 50,
|
||||
}),
|
||||
]),
|
||||
object("message", "Message Field", [
|
||||
text("label", "Label", { maxLength: 30 }),
|
||||
text("placeholder", "Placeholder", { maxLength: 80 }),
|
||||
]),
|
||||
]),
|
||||
text("submitLabel", "Submit Label", { maxLength: 40 }),
|
||||
]),
|
||||
},
|
||||
],
|
||||
},
|
||||
history: {
|
||||
key: "history",
|
||||
title: "History Management",
|
||||
subtitle: "Edit content displayed on the history page",
|
||||
routeBase: "/admin/history",
|
||||
apiPath: "/api/history",
|
||||
previewPath: "/about/history",
|
||||
dataFile: "history",
|
||||
imageType: "history",
|
||||
tabs: [
|
||||
{
|
||||
key: "highlight",
|
||||
label: "Highlight",
|
||||
icon: "fas fa-star",
|
||||
schema: object("highlight", "Highlight Banner", [
|
||||
icon("icon", "Icon"),
|
||||
text("text", "Text", { maxLength: 110 }),
|
||||
text("linkLabel", "Link Label", { maxLength: 30 }),
|
||||
url("href", "Link URL", { maxLength: 255 }),
|
||||
]),
|
||||
},
|
||||
{
|
||||
key: "hero",
|
||||
label: "Hero",
|
||||
icon: "fas fa-image",
|
||||
schema: object("hero", "Hero", [
|
||||
text("badge", "Badge", { maxLength: 40 }),
|
||||
text("title", "Title", { maxLength: 90 }),
|
||||
textarea("description", "Description", { maxLength: 220, rows: 4 }),
|
||||
]),
|
||||
},
|
||||
{
|
||||
key: "filters",
|
||||
label: "Filters",
|
||||
icon: "fas fa-filter",
|
||||
schema: object("filters", "Filters", [
|
||||
text("yearLabel", "Year Label", { maxLength: 30 }),
|
||||
text("categoryLabel", "Category Label", { maxLength: 30 }),
|
||||
text("buttonLabel", "Button Label", { maxLength: 30 }),
|
||||
stringList("yearOptions", "Year Options", {
|
||||
itemLabel: "Year Option",
|
||||
maxLength: 30,
|
||||
}),
|
||||
stringList("categoryOptions", "Category Options", {
|
||||
itemLabel: "Category Option",
|
||||
maxLength: 40,
|
||||
}),
|
||||
]),
|
||||
},
|
||||
{
|
||||
key: "timeline",
|
||||
label: "Timeline",
|
||||
icon: "fas fa-clock-rotate-left",
|
||||
schema: object("timeline", "Timeline", [
|
||||
text("loadMoreLabel", "Load More Label", { maxLength: 40 }),
|
||||
objectList(
|
||||
"items",
|
||||
"Milestones",
|
||||
[
|
||||
text("id", "Milestone ID", { maxLength: 60 }),
|
||||
text("year", "Year", { maxLength: 10 }),
|
||||
text("yearRange", "Year Range", { maxLength: 30 }),
|
||||
text("category", "Category", { maxLength: 40 }),
|
||||
text("categoryLabel", "Category Label", { maxLength: 30 }),
|
||||
text("title", "Title", { maxLength: 90 }),
|
||||
textarea("description", "Description", {
|
||||
maxLength: 260,
|
||||
rows: 4,
|
||||
}),
|
||||
image("image", "Milestone Image", {
|
||||
imageHint: "Recommended 436x190 px",
|
||||
helpText: "Wide image used inside the milestone card.",
|
||||
}),
|
||||
text("imageAlt", "Image Alt Text", { maxLength: 120 }),
|
||||
objectList(
|
||||
"stats",
|
||||
"Stats",
|
||||
[
|
||||
text("value", "Value", { maxLength: 24 }),
|
||||
text("label", "Label", { maxLength: 50 }),
|
||||
],
|
||||
{ itemLabel: "Stat" },
|
||||
),
|
||||
checkbox("featured", "Featured"),
|
||||
],
|
||||
{ itemLabel: "Milestone" },
|
||||
),
|
||||
]),
|
||||
},
|
||||
],
|
||||
},
|
||||
accreditation: {
|
||||
key: "accreditation",
|
||||
title: "Accreditation Management",
|
||||
subtitle: "Edit content displayed on the accreditation page",
|
||||
routeBase: "/admin/accreditation",
|
||||
apiPath: "/api/accreditation",
|
||||
previewPath: "/about/accreditation",
|
||||
dataFile: "accreditation",
|
||||
imageType: "accreditation",
|
||||
tabs: [
|
||||
{
|
||||
key: "trustBanner",
|
||||
label: "Trust Banner",
|
||||
icon: "fas fa-shield-check",
|
||||
schema: object("trustBanner", "Trust Banner", [
|
||||
icon("icon", "Icon"),
|
||||
text("text", "Text", { maxLength: 120 }),
|
||||
objectList(
|
||||
"links",
|
||||
"Links",
|
||||
[
|
||||
text("label", "Label", { maxLength: 40 }),
|
||||
url("href", "URL", { maxLength: 255 }),
|
||||
icon("icon", "Icon"),
|
||||
],
|
||||
{ itemLabel: "Link" },
|
||||
),
|
||||
]),
|
||||
},
|
||||
{
|
||||
key: "hero",
|
||||
label: "Hero",
|
||||
icon: "fas fa-image",
|
||||
schema: object("hero", "Hero", [
|
||||
text("badge", "Badge", { maxLength: 40 }),
|
||||
text("title", "Title", { maxLength: 60 }),
|
||||
textarea("description", "Description", { maxLength: 420, rows: 5 }),
|
||||
]),
|
||||
},
|
||||
{
|
||||
key: "grid",
|
||||
label: "Grid",
|
||||
icon: "fas fa-table-cells-large",
|
||||
schema: object("grid", "Grid", [
|
||||
stringList("tabs", "Tabs", {
|
||||
itemLabel: "Tab",
|
||||
maxLength: 30,
|
||||
}),
|
||||
objectList(
|
||||
"items",
|
||||
"Items",
|
||||
[
|
||||
text("id", "Item ID", { maxLength: 50 }),
|
||||
icon("icon", "Icon"),
|
||||
image("image", "Image", {
|
||||
imageHint: "Recommended 118x58 px minimum visible ratio",
|
||||
helpText: "Logo or badge used at the top of the card.",
|
||||
}),
|
||||
text("status", "Status", { maxLength: 20 }),
|
||||
text("category", "Category", { maxLength: 30 }),
|
||||
text("title", "Title", {
|
||||
maxLength: 17,
|
||||
helpText: "Frontend title space is capped at 17 characters.",
|
||||
}),
|
||||
textarea("description", "Description", {
|
||||
maxLength: 300,
|
||||
rows: 5,
|
||||
helpText:
|
||||
"Frontend description preview is capped at 300 characters.",
|
||||
}),
|
||||
text("scopeLabel", "Scope Label", { maxLength: 20 }),
|
||||
text("scope", "Scope", { maxLength: 60 }),
|
||||
text("validUntilLabel", "Valid Until Label", { maxLength: 24 }),
|
||||
text("validUntil", "Valid Until", { maxLength: 40 }),
|
||||
text("buttonLabel", "Button Label", { maxLength: 30 }),
|
||||
url("certificateHref", "Certificate URL", { maxLength: 255 }),
|
||||
],
|
||||
{ itemLabel: "Accreditation Item" },
|
||||
),
|
||||
]),
|
||||
},
|
||||
],
|
||||
},
|
||||
admissions: {
|
||||
key: "admissions",
|
||||
title: "Admissions Management",
|
||||
subtitle: "Edit content displayed on the admissions page",
|
||||
routeBase: "/admin/admissions",
|
||||
apiPath: "/api/admissions",
|
||||
previewPath: "/admissions",
|
||||
dataFile: "admissions",
|
||||
imageType: "admissions",
|
||||
tabs: [
|
||||
{
|
||||
key: "hero",
|
||||
label: "Hero",
|
||||
icon: "fas fa-image",
|
||||
schema: object("hero", "Hero", [
|
||||
text("badge", "Badge", { maxLength: 40 }),
|
||||
text("title", "Title", { maxLength: 80 }),
|
||||
textarea("description", "Description", { maxLength: 240, rows: 4 }),
|
||||
object("primaryCta", "Primary CTA", linkFields("Primary CTA")),
|
||||
object("secondaryCta", "Secondary CTA", linkFields("Secondary CTA")),
|
||||
image("image", "Hero Image", {
|
||||
imageHint: "Recommended 720x646 px",
|
||||
helpText: "Large image rendered in the right hero panel.",
|
||||
}),
|
||||
text("imageAlt", "Hero Image Alt Text", { maxLength: 120 }),
|
||||
]),
|
||||
},
|
||||
{
|
||||
key: "process",
|
||||
label: "Process",
|
||||
icon: "fas fa-list-ol",
|
||||
schema: object("process", "Process", [
|
||||
text("id", "Section ID", { maxLength: 40 }),
|
||||
text("title", "Title", { maxLength: 60 }),
|
||||
textarea("description", "Description", { maxLength: 180, rows: 3 }),
|
||||
objectList(
|
||||
"steps",
|
||||
"Steps",
|
||||
[
|
||||
text("number", "Number", { maxLength: 8 }),
|
||||
text("title", "Title", { maxLength: 50 }),
|
||||
textarea("description", "Description", { maxLength: 180, rows: 3 }),
|
||||
checkbox("active", "Active"),
|
||||
],
|
||||
{ itemLabel: "Step" },
|
||||
),
|
||||
]),
|
||||
},
|
||||
{
|
||||
key: "eligibility",
|
||||
label: "Eligibility",
|
||||
icon: "fas fa-check-circle",
|
||||
schema: object("eligibility", "Eligibility", [
|
||||
text("id", "Section ID", { maxLength: 40 }),
|
||||
text("title", "Title", { maxLength: 60 }),
|
||||
objectList(
|
||||
"cards",
|
||||
"Cards",
|
||||
[
|
||||
text("title", "Title", { maxLength: 50 }),
|
||||
icon("icon", "Icon"),
|
||||
stringList("items", "Items", {
|
||||
itemLabel: "Bullet Item",
|
||||
maxLength: 120,
|
||||
}),
|
||||
],
|
||||
{ itemLabel: "Card" },
|
||||
),
|
||||
]),
|
||||
},
|
||||
{
|
||||
key: "tuition",
|
||||
label: "Tuition",
|
||||
icon: "fas fa-chart-column",
|
||||
schema: object("tuition", "Tuition", [
|
||||
text("id", "Section ID", { maxLength: 40 }),
|
||||
text("title", "Title", { maxLength: 60 }),
|
||||
text("chartTitle", "Chart Title", { maxLength: 60 }),
|
||||
textarea("chartDescription", "Chart Description", {
|
||||
maxLength: 140,
|
||||
rows: 3,
|
||||
}),
|
||||
objectList(
|
||||
"series",
|
||||
"Chart Series",
|
||||
[
|
||||
text("label", "Label", { maxLength: 40 }),
|
||||
{ key: "color", label: "Color", type: "color" },
|
||||
stringList("values", "Values", {
|
||||
itemLabel: "Point",
|
||||
fieldType: "number",
|
||||
}),
|
||||
],
|
||||
{ itemLabel: "Series" },
|
||||
),
|
||||
]),
|
||||
},
|
||||
{
|
||||
key: "keyDates",
|
||||
label: "Key Dates",
|
||||
icon: "fas fa-calendar-days",
|
||||
schema: object("keyDates", "Key Dates", [
|
||||
text("id", "Section ID", { maxLength: 40 }),
|
||||
text("title", "Title", { maxLength: 60 }),
|
||||
stringList("columns", "Columns", {
|
||||
itemLabel: "Column",
|
||||
maxLength: 40,
|
||||
}),
|
||||
objectList(
|
||||
"rows",
|
||||
"Rows",
|
||||
[
|
||||
text("term", "Term", { maxLength: 40 }),
|
||||
text("applicationDeadline", "Application Deadline", {
|
||||
maxLength: 40,
|
||||
}),
|
||||
text("classesStart", "Classes Start", { maxLength: 40 }),
|
||||
],
|
||||
{ itemLabel: "Row" },
|
||||
),
|
||||
]),
|
||||
},
|
||||
{
|
||||
key: "calculator",
|
||||
label: "Calculator",
|
||||
icon: "fas fa-calculator",
|
||||
schema: object("calculator", "Calculator", [
|
||||
text("title", "Title", { maxLength: 60 }),
|
||||
textarea("description", "Description", { maxLength: 120, rows: 3 }),
|
||||
stringList("modelOptions", "Model Options", {
|
||||
itemLabel: "Option",
|
||||
maxLength: 30,
|
||||
}),
|
||||
text("paceLabel", "Pace Label", { maxLength: 30 }),
|
||||
text("minPaceLabel", "Min Pace Label", { maxLength: 20 }),
|
||||
text("maxPaceLabel", "Max Pace Label", { maxLength: 20 }),
|
||||
text("resultLabel", "Result Label", { maxLength: 40 }),
|
||||
text("monthlyAmount", "Monthly Amount", { maxLength: 20 }),
|
||||
text("monthlySuffix", "Monthly Suffix", { maxLength: 10 }),
|
||||
icon("noteIcon", "Note Icon"),
|
||||
text("note", "Note", { maxLength: 60 }),
|
||||
object("cta", "CTA", linkFields("CTA")),
|
||||
]),
|
||||
},
|
||||
{
|
||||
key: "scholarships",
|
||||
label: "Scholarships",
|
||||
icon: "fas fa-award",
|
||||
schema: object("scholarships", "Scholarships", [
|
||||
text("title", "Title", { maxLength: 50 }),
|
||||
icon("icon", "Icon"),
|
||||
objectList(
|
||||
"items",
|
||||
"Scholarship Items",
|
||||
[
|
||||
text("title", "Title", { maxLength: 50 }),
|
||||
text("amount", "Amount", { maxLength: 24 }),
|
||||
textarea("description", "Description", { maxLength: 160, rows: 3 }),
|
||||
],
|
||||
{ itemLabel: "Scholarship Item" },
|
||||
),
|
||||
]),
|
||||
},
|
||||
],
|
||||
},
|
||||
policies: {
|
||||
key: "policies",
|
||||
title: "Policies Management",
|
||||
subtitle: "Edit content displayed on the policies page",
|
||||
routeBase: "/admin/policies",
|
||||
apiPath: "/api/policies",
|
||||
previewPath: "/policies",
|
||||
dataFile: "policies",
|
||||
imageType: "policies",
|
||||
tabs: [
|
||||
{
|
||||
key: "hero",
|
||||
label: "Hero",
|
||||
icon: "fas fa-scale-balanced",
|
||||
schema: object("hero", "Hero", [
|
||||
text("badge", "Badge", { maxLength: 40 }),
|
||||
icon("icon", "Icon", {
|
||||
helpText: "Policies uses icon-only controls and does not require image upload.",
|
||||
}),
|
||||
text("titlePrefix", "Title Prefix", { maxLength: 50 }),
|
||||
text("titleHighlight", "Title Highlight", { maxLength: 40 }),
|
||||
textarea("description", "Description", { maxLength: 220, rows: 4 }),
|
||||
text("lastUpdated", "Last Updated Label", { maxLength: 50 }),
|
||||
]),
|
||||
},
|
||||
{
|
||||
key: "sidebar",
|
||||
label: "Sidebar",
|
||||
icon: "fas fa-bars",
|
||||
schema: object("sidebar", "Sidebar", [
|
||||
text("heading", "Heading", { maxLength: 30 }),
|
||||
text("helperText", "Helper Text", { maxLength: 60 }),
|
||||
text("contactLabel", "Contact Label", { maxLength: 40 }),
|
||||
url("contactHref", "Contact URL", { maxLength: 255 }),
|
||||
]),
|
||||
},
|
||||
{
|
||||
key: "policies",
|
||||
label: "Policies",
|
||||
icon: "fas fa-file-lines",
|
||||
schema: objectList(
|
||||
"policies",
|
||||
"Policies",
|
||||
[
|
||||
text("id", "Policy ID", { maxLength: 40 }),
|
||||
text("navLabel", "Navigation Label", { maxLength: 40 }),
|
||||
text("title", "Title", { maxLength: 70 }),
|
||||
text("effectiveDate", "Effective Date", { maxLength: 50 }),
|
||||
textarea("intro", "Intro", { maxLength: 260, rows: 4 }),
|
||||
variantList(
|
||||
"sections",
|
||||
"Sections",
|
||||
{
|
||||
text: {
|
||||
label: "Text Section",
|
||||
schema: object("section", "Text Section", [
|
||||
text("heading", "Heading", { maxLength: 60 }),
|
||||
objectList(
|
||||
"paragraphs",
|
||||
"Paragraphs",
|
||||
[
|
||||
textarea("text", "Text", {
|
||||
maxLength: 500,
|
||||
rows: 4,
|
||||
}),
|
||||
objectList(
|
||||
"links",
|
||||
"Links",
|
||||
[
|
||||
text("label", "Label", { maxLength: 50 }),
|
||||
url("href", "URL", { maxLength: 255 }),
|
||||
text("tabId", "Target Policy ID", {
|
||||
maxLength: 40,
|
||||
helpText:
|
||||
"Use this when the link should switch to another policy tab.",
|
||||
}),
|
||||
],
|
||||
{ itemLabel: "Link" },
|
||||
),
|
||||
],
|
||||
{ itemLabel: "Paragraph" },
|
||||
),
|
||||
]),
|
||||
},
|
||||
list: {
|
||||
label: "List Section",
|
||||
schema: object("section", "List Section", [
|
||||
text("heading", "Heading", { maxLength: 60 }),
|
||||
textarea("intro", "Intro", { maxLength: 220, rows: 3 }),
|
||||
stringList("items", "Items", {
|
||||
itemLabel: "List Item",
|
||||
maxLength: 180,
|
||||
fieldType: "textarea",
|
||||
}),
|
||||
]),
|
||||
},
|
||||
cards: {
|
||||
label: "Cards Section",
|
||||
schema: object("section", "Cards Section", [
|
||||
objectList(
|
||||
"cards",
|
||||
"Cards",
|
||||
[
|
||||
icon("icon", "Icon"),
|
||||
text("title", "Title", { maxLength: 50 }),
|
||||
textarea("description", "Description", {
|
||||
maxLength: 220,
|
||||
rows: 4,
|
||||
}),
|
||||
object("link", "Link", [
|
||||
text("label", "Label", { maxLength: 50 }),
|
||||
url("href", "URL", { maxLength: 255 }),
|
||||
text("tabId", "Target Policy ID", { maxLength: 40 }),
|
||||
]),
|
||||
],
|
||||
{ itemLabel: "Card" },
|
||||
),
|
||||
]),
|
||||
},
|
||||
},
|
||||
{ itemLabel: "Section" },
|
||||
),
|
||||
],
|
||||
{ itemLabel: "Policy" },
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user