@gov-cy/govcy-express-services
Version:
An Express-based system that dynamically renders services using @gov-cy/govcy-frontend-renderer and posts data to a submission API.
303 lines (262 loc) • 12 kB
JavaScript
import * as govcyResources from "../resources/govcyResources.mjs";
const ALLOWED_TASK_STATUSES = new Set(["NOT_STARTED", "IN_PROGRESS", "COMPLETED"]);
function normalizeTaskStatus(statusKey = "NOT_STARTED") {
if (typeof statusKey !== "string") return "NOT_STARTED";
const normalized = statusKey.toUpperCase();
return ALLOWED_TASK_STATUSES.has(normalized) ? normalized : "NOT_STARTED";
}
/**
* Defines custom pages for a given siteId and pageUrl.
* This function initializes the custom pages in the store and sets up the necessary data structure.
* @param {object} store The session store (usually req.app)
* @param {string} siteId The site identifier (e.g., `"cso"`)
* @param {string} pageUrl The URL of the custom page (e.g., `"/cso/custom"`)
* @param {string} pageTitle The title of the custom page (e.g., `{ en: "My custom section", el: "Προσαρμοσμένη ενότητα" }`)
* @param {string} insertAfterPageUrl The page URL to insert the custom page after (e.g., `"qualifications"`)
* @param {array} errors An array of error objects (e.g., `[{ id: "error1", text: { en: "Error 1", el: "Error 1"} }, { id: "error2", text: { en: "Error 2", el: "Error 2"} }]`)
* @param {array} summaryElements An array of summary element objects (e.g., `{ key: { en: "Extra value", el: "Πρόσθετη τιμή" }, value: [] }` )
* @param {boolean} summaryHtml Optional HTML content for the summary (e.g., `{ en: "<strong>Summary HTML</strong>", el: "<strong>Περίληψη HTML</strong>" }`)
* @param {string} taskStatus Optional default task status for task lists (NOT_STARTED/IN_PROGRESS/COMPLETED)
* @param {object} [extraProps={}] Optional extra metadata (e.g., `{ nextPage: "confirmation", requiresOTP: true }`)
*/
export function defineCustomPages(
store,
siteId,
pageUrl,
pageTitle,
insertAfterPageUrl,
errors,
summaryElements,
summaryHtml = false,
taskStatus = "NOT_STARTED",
extraProps = {}
) {
// Initialize custom pages in session if not already set
store.siteData ??= {};
store.siteData[siteId] ??= {};
store.siteData[siteId].customPagesDefinition ??= {}; // Initialize custom pages definition
store.siteData[siteId].customPagesDefinition[pageUrl] ??= {}; // Initialize specific page definition
// ✅ Normalize error objects: add pageUrl if missing
let normalizedErrors = [];
if (Array.isArray(errors)) {
normalizedErrors = errors.map(err => ({
...err,
// pageUrl replace first `/` with empty string to ensure it is relative
pageUrl: err.pageUrl || pageUrl.replace(/^\//, "") // ← ensure pageUrl is set
}));
}
// Initialize the custom summary actions
let summaryActions = [];
// Base definition
const definition = {
pageTitle,
insertAfterPageUrl,
summaryElements: summaryElements || [],
errors: normalizedErrors,
summaryActions: summaryActions,
taskStatus: normalizeTaskStatus(taskStatus),
...(summaryHtml ? { summaryHtml } : {}),
...extraProps // ✅ Merge any additional developer-defined properties
};
// Construct default summaryActions
if (pageTitle && pageUrl) {
definition.summaryActions = [
{
text: govcyResources.staticResources.text.change,
classes: "govcy-d-print-none",
href: `${pageUrl}?route=review`,
visuallyHiddenText: pageTitle
}
];
}
// Save definition
store.siteData[siteId].customPagesDefinition[pageUrl] = definition;
}
/**
* Retrieves the custom page definition for a given siteId and pageUrl.
*
* @param {object} store The custom pages configuration store. Should be req.app
* @param {string} siteId The site id
* @param {string} pageUrl The URL of the custom page
* @returns {object} The custom page definition or an empty object if not found.
*/
export function getCustomPageDefinition(store, siteId, pageUrl) {
return store.siteData?.[siteId]?.customPagesDefinition?.[pageUrl] || {};
}
/**
* Resets the custom pages for a given siteId.
* This will deep copy the customPagesDefinition to customPages.
* This is useful for initializing or resetting the custom pages.
*
* @param {object} configStore The custom pages configuration store. Should be req.app
* @param {object} store The data layer store. Should be the req.session
* @param {string} siteId The site id
*/
export function resetCustomPages(configStore, store, siteId) {
if (!configStore?.siteData?.[siteId]?.customPagesDefinition) return;
// Reset custom pages for the given siteId based on the customPagesDefinition
store.siteData[siteId].customPages = JSON.parse(JSON.stringify(configStore.siteData[siteId].customPagesDefinition))
}
// ==========================================================
// 🧰 Custom Page Error Helpers
// ==========================================================
/**
* Sets a custom property on a given custom page definition or instance.
*
* @param {object} store - The store containing siteData.
* @param {string} siteId - The site identifier.
* @param {string} pageUrl - The custom page URL.
* @param {string} property - The property name to set.
* @param {*} value - The value to assign.
* @param {boolean} [isDefinition=false] - Whether to modify the global definition instead of session data.
*/
export function setCustomPageProperty(store, siteId, pageUrl, property, value, isDefinition = false) {
const container = isDefinition
? store.siteData?.[siteId]?.customPagesDefinition
: store.siteData?.[siteId]?.customPages;
if (!container || !container[pageUrl]) {
console.warn(`⚠️ setCustomPageProperty: page '${pageUrl}' not found for site '${siteId}'`);
return;
}
container[pageUrl][property] = value;
}
/**
* Gets a custom property from a given custom page definition or instance.
*
* @param {object} store - The store containing siteData.
* @param {string} siteId - The site identifier.
* @param {string} pageUrl - The custom page URL.
* @param {string} property - The property name to get.
* @param {boolean} [isDefinition=false] - Whether to read from the global definition instead of session data.
* @returns {*} The value of the property, or undefined if not found.
*/
export function getCustomPageProperty(store, siteId, pageUrl, property, isDefinition = false) {
const container = isDefinition
? store.siteData?.[siteId]?.customPagesDefinition
: store.siteData?.[siteId]?.customPages;
return container?.[pageUrl]?.[property];
}
/**
* Sets the data object for a given custom page.
* Overwrites the existing data (does not merge).
*
* @param {Object} store - The store containing siteData.
* @param {string} siteId - The site identifier.
* @param {string} pageUrl - The URL of the custom page.
* @param {Object} data - The data to store.
*/
export function setCustomPageData(store, siteId, pageUrl, data) {
const target = store.siteData?.[siteId]?.customPages?.[pageUrl];
if (!target) {
console.warn(`⚠️ setCustomPageData: page '${pageUrl}' not found for site '${siteId}'`);
return;
}
target.data = data; // overwrite existing data
}
/**
* Sets the email array with dsf-email-templates objects for a given custom page.
* Overwrites the existing data (does not merge).
*
* @param {Object} store - The store containing siteData.
* @param {string} siteId - The site identifier.
* @param {string} pageUrl - The URL of the custom page.
* @param {Array<Object>} arrayOfEmailObjects - Array of dsf-email-templates objects.
*/
export function setCustomPageEmail(store, siteId, pageUrl, arrayOfEmailObjects) {
const target = store.siteData?.[siteId]?.customPages?.[pageUrl];
if (!target) {
console.warn(`⚠️ setCustomPageData: page '${pageUrl}' not found for site '${siteId}'`);
return;
}
if (!Array.isArray(arrayOfEmailObjects)) {
console.warn(`⚠️ setCustomPageEmail: expected array for custom page '${normalizedPageUrl}', got`, typeof arrayOfEmailObjects);
return;
}
target.email = arrayOfEmailObjects; // overwrite existing data
}
/**
* Sets the summaryElements array for a given custom page.
* Replaces the existing summaryElements array.
*
* @param {Object} store - The store containing siteData.
* @param {string} siteId - The site identifier.
* @param {string} pageUrl - The URL of the custom page.
* @param {Array} summaryElements - Array of summary element definitions.
*/
export function setCustomPageSummaryElements(store, siteId, pageUrl, summaryElements) {
const target = store.siteData?.[siteId]?.customPages?.[pageUrl];
if (!target) {
console.warn(`⚠️ setCustomPageSummaryElements: page '${pageUrl}' not found for site '${siteId}'`);
return;
}
target.summaryElements = Array.isArray(summaryElements) ? summaryElements : [];
}
/**
* Clears all errors for a given custom page.
* Works on either customPages or customPagesDefinition.
*
* @param {Object} store - The store containing siteData.
* @param {string} siteId - The site identifier.
* @param {string} pageUrl - The URL of the custom page.
*/
export function clearCustomPageErrors(store, siteId, pageUrl) {
const data = store.siteData?.[siteId]?.customPages?.[pageUrl];
if (data) data.errors = [];
}
/**
* Adds an error to a given custom page (definition or data).
* Auto-fills pageUrl and ensures array exists.
*
* @param {Object} store - The store containing siteData.
* @param {string} siteId - The site identifier.
* @param {string} pageUrl - The URL of the custom page.
* @param {string} errorId - Unique identifier for the error.
* @param {Object} errorTextObject - Multilingual Object containing localized error texts.
*/
export function addCustomPageError(store, siteId, pageUrl, errorId, errorTextObject) {
const normalizedPageUrl = pageUrl.replace(/^\/+/, "");
const target =
store.siteData?.[siteId]?.customPages?.[pageUrl]
if (!target) {
console.warn(`⚠️ addCustomPageError: page '${pageUrl}' not found for site '${siteId}'`);
return;
}
target.errors ??= [];
// ✅ Prevent duplicate errors by ID
if (target.errors.some(err => err.id === errorId)) return;
// Normalize and push error
target.errors.push({
id: errorId,
text: errorTextObject,
pageUrl: normalizedPageUrl,
});
}
/**
* Sets the task status for a custom page so task-list pages can show progress.
*
* @param {object} store Session store (req.session)
* @param {string} siteId Current site id
* @param {string} pageUrl Custom page url
* @param {string} statusKey One of NOT_STARTED/IN_PROGRESS/COMPLETED
*/
export function setCustomPageTaskStatus(store, siteId, pageUrl, statusKey = "NOT_STARTED") {
const target = store?.siteData?.[siteId]?.customPages?.[pageUrl];
if (!target) {
console.warn(`⚠️ setCustomPageTaskStatus: page '${pageUrl}' not found for site '${siteId}'`);
return;
}
target.taskStatus = normalizeTaskStatus(statusKey);
}
/**
* Retrieves the stored task status for a custom page.
*
* @param {object} store Session store (req.session)
* @param {string} siteId Current site id
* @param {string} pageUrl Custom page url
* @returns {string} Task status key
*/
export function getCustomPageTaskStatus(store, siteId, pageUrl) {
const target = store?.siteData?.[siteId]?.customPages?.[pageUrl];
if (!target) return "NOT_STARTED";
return normalizeTaskStatus(target.taskStatus);
}