bstein-dev-home/frontend/src/onboarding/onboardingGuides.js

58 lines
2.0 KiB
JavaScript

/**
* Parse onboarding media manifests into guide groups.
*
* WHY: keeping manifest shaping outside the view makes guide behavior
* testable without mounting the whole onboarding page.
*
* @param {string[]} files - Manifest file paths relative to the onboarding media root.
* @returns {object} Guide groups keyed by service, step, and variant.
*/
export function parseManifest(files) {
const grouped = {};
for (const path of files) {
if (typeof path !== "string") continue;
const cleaned = path.replace(/^\/+/, "").replace(/\\/g, "/");
const parts = cleaned.split("/");
if (parts.length < 3) continue;
const service = parts[0];
const step = parts[1];
const rest = parts.slice(2);
let variant = "default";
let filename = rest.join("/");
if (rest.length > 1) {
variant = rest[0];
filename = rest.slice(1).join("/");
}
const order = guideOrder(filename);
const label = guideLabel(filename);
const url = `/media/onboarding/${cleaned}`;
grouped[service] = grouped[service] || {};
grouped[service][step] = grouped[service][step] || {};
grouped[service][step][variant] = grouped[service][step][variant] || { id: variant, title: variant === "default" ? "" : variant, shots: [] };
grouped[service][step][variant].shots.push({ url, order, label, file: filename });
}
Object.values(grouped).forEach((serviceSteps) => {
Object.values(serviceSteps).forEach((variants) => {
Object.values(variants).forEach((group) => {
group.shots.sort((a, b) => (a.order - b.order) || a.file.localeCompare(b.file));
});
});
});
return grouped;
}
function guideOrder(filename) {
const prefix = filename.match(/^(\d{1,3})/);
if (prefix) return Number(prefix[1]);
const step = filename.match(/step[-_ ]?(\d{1,3})/i);
if (step) return Number(step[1]);
return Number.MAX_SAFE_INTEGER;
}
function guideLabel(filename) {
const base = filename.replace(/\.(png|jpe?g|webp)$/i, "");
return base.replace(/^\d+[-_]?/, "").replace(/[-_]/g, " ").trim();
}