// ===== Live Spiro bundle sync ===== // Fetches the live bundle list from the Spiro public API on mount and // exposes a Map via the useSpiroBundles hook. // Failure modes (network, non-2xx, bad shape) fall through silently to a // console.warn; consumers must keep their own hardcoded fallback values. const SPIRO_BUNDLES_URL = "https://order-api.spiro.media/api/bundle/GetBundles?tenantShortCode=veradoma&orderPageCode=veradoma-order-page"; // Parse Spiro's "+ Foo\n+ Bar, \n+ Baz." into ["Foo", "Bar", "Baz"]. const parseSpiroFeatures = (desc) => { if (!desc || typeof desc !== "string") return []; return desc .split(/\r?\n/) .map(line => line.replace(/^\s*\+\s*/, "").trim()) .map(line => line.replace(/[,.\s]+$/, "").trim()) .filter(line => line.length > 0); }; // Format the price. variablePricing → "from $225", flat → "$225". const formatSpiroPrice = (displayPrice, variablePricing) => { if (typeof displayPrice !== "number" || !isFinite(displayPrice)) return null; const rounded = Math.round(displayPrice); return variablePricing ? `from $${rounded}` : `$${rounded}`; }; // React hook. Starts fetch on mount; returns the Map once available, or null // while loading / on error. Aborts cleanly on unmount. const useSpiroBundles = () => { const [byBundleId, setByBundleId] = React.useState(null); React.useEffect(() => { let cancelled = false; fetch(SPIRO_BUNDLES_URL, { cache: "no-store" }) .then(r => { if (!r.ok) throw new Error(`Spiro API ${r.status}`); return r.json(); }) .then(bundles => { if (cancelled || !Array.isArray(bundles)) return; const map = new Map(); for (const b of bundles) { if (!b || !b.bundleID) continue; const price = formatSpiroPrice(b.displayPrice, b.variablePricing); const features = parseSpiroFeatures(b.description); if (price || features.length) map.set(b.bundleID, { price, features }); } setByBundleId(map); }) .catch(err => { if (!cancelled) console.warn("Spiro live sync failed; using hardcoded fallback.", err); }); return () => { cancelled = true; }; }, []); return byBundleId; }; Object.assign(window, { parseSpiroFeatures, formatSpiroPrice, useSpiroBundles });