@dreamtree-org/landing-kit 0.8.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MIGRATION.md +30 -0
- package/PLATFORM-API.md +17 -13
- package/README.md +27 -1
- package/ai-skills/landing-kit.md +7 -6
- package/dist/index.cjs +323 -15
- package/dist/index.js +323 -15
- package/dist/platform.cjs +1094 -398
- package/dist/platform.js +1092 -396
- package/dist/schema.cjs +324 -3
- package/dist/schema.js +304 -3
- package/index.d.ts +15 -1
- package/package.json +1 -1
- package/platform.d.ts +37 -3
- package/schema.d.ts +50 -0
package/dist/index.js
CHANGED
|
@@ -276,9 +276,9 @@ function Wordmark({ name }) {
|
|
|
276
276
|
/* @__PURE__ */ jsx5("span", { className: "dt-lp-wordmark-hi", children: m[2] })
|
|
277
277
|
] });
|
|
278
278
|
}
|
|
279
|
-
function BrandLockup({ brand:
|
|
279
|
+
function BrandLockup({ brand: own2, byline = "by Dreamtree-Org", href = "/" }) {
|
|
280
280
|
const ctx = useLanding();
|
|
281
|
-
const brand =
|
|
281
|
+
const brand = own2 || ctx.brand;
|
|
282
282
|
if (!brand) return null;
|
|
283
283
|
return /* @__PURE__ */ jsxs4("a", { href, className: "dt-lp-brand", "aria-label": `${brand.name}${byline ? ` ${byline}` : ""}, home`, children: [
|
|
284
284
|
/* @__PURE__ */ jsx5(BrandMark2, { brand }),
|
|
@@ -405,10 +405,10 @@ function DreamtreeFooter({
|
|
|
405
405
|
tagline,
|
|
406
406
|
legal = [],
|
|
407
407
|
year = (/* @__PURE__ */ new Date()).getFullYear(),
|
|
408
|
-
brand:
|
|
408
|
+
brand: own2
|
|
409
409
|
}) {
|
|
410
410
|
const ctx = useLanding();
|
|
411
|
-
const brand =
|
|
411
|
+
const brand = own2 || ctx.brand;
|
|
412
412
|
return /* @__PURE__ */ jsxs7("footer", { className: "dt-lp-footer", children: [
|
|
413
413
|
/* @__PURE__ */ jsxs7(Wrap, { className: `dt-lp-footer-grid dt-lp-footer-grid--${Math.min(columns.length, 3)}`, children: [
|
|
414
414
|
/* @__PURE__ */ jsxs7("div", { className: "dt-lp-footer-about", children: [
|
|
@@ -575,7 +575,7 @@ function FeatureSection(props) {
|
|
|
575
575
|
] })
|
|
576
576
|
);
|
|
577
577
|
case "intro": {
|
|
578
|
-
const { subtitle, detail, note, glance = [], glanceTitle = "At a glance", link } = props;
|
|
578
|
+
const { subtitle, detail, note, glance = [], glanceTitle = "At a glance", link: link2 } = props;
|
|
579
579
|
return shell(
|
|
580
580
|
/* @__PURE__ */ jsxs10("div", { className: "dt-lp-intro-grid", children: [
|
|
581
581
|
/* @__PURE__ */ jsxs10("div", { children: [
|
|
@@ -589,7 +589,7 @@ function FeatureSection(props) {
|
|
|
589
589
|
/* @__PURE__ */ jsx12("p", { id: `${hid2}-glance`, className: "dt-lp-eyebrow", children: glanceTitle }),
|
|
590
590
|
/* @__PURE__ */ jsx12(Checklist, { items: glance }),
|
|
591
591
|
props.glanceNote ? /* @__PURE__ */ jsx12("p", { className: "dt-lp-note", children: props.glanceNote }) : null,
|
|
592
|
-
|
|
592
|
+
link2 ? /* @__PURE__ */ jsx12(TextLink, { href: link2.href, className: "dt-lp-mt", children: link2.label }) : null
|
|
593
593
|
] }) : null
|
|
594
594
|
] })
|
|
595
595
|
);
|
|
@@ -750,7 +750,7 @@ function Faq({ id = "faq", eyebrow, title: title2 = "Frequently asked questions"
|
|
|
750
750
|
|
|
751
751
|
// src/DemoCta.jsx
|
|
752
752
|
import { jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
753
|
-
function DemoCta({ id = "demo", eyebrow, title: title2, body, label = "Book a demo", chips, chipsLabel = "Business types", note, link }) {
|
|
753
|
+
function DemoCta({ id = "demo", eyebrow, title: title2, body, label = "Book a demo", chips, chipsLabel = "Business types", note, link: link2 }) {
|
|
754
754
|
return /* @__PURE__ */ jsx16("section", { id, "aria-labelledby": `${id}-title`, className: "dt-lp-section dt-lp-cta", children: /* @__PURE__ */ jsx16(Wrap, { children: /* @__PURE__ */ jsxs14("div", { className: "dt-lp-cta-panel", children: [
|
|
755
755
|
eyebrow ? /* @__PURE__ */ jsx16("p", { className: "dt-lp-eyebrow dt-lp-eyebrow--inverse", children: eyebrow }) : null,
|
|
756
756
|
/* @__PURE__ */ jsx16("h2", { id: `${id}-title`, className: "dt-lp-h2 dt-lp-h2--cta", children: title2 }),
|
|
@@ -761,7 +761,7 @@ function DemoCta({ id = "demo", eyebrow, title: title2, body, label = "Book a de
|
|
|
761
761
|
note.title ? /* @__PURE__ */ jsx16("h3", { className: "dt-lp-h3", children: note.title }) : null,
|
|
762
762
|
note.body ? /* @__PURE__ */ jsx16("p", { children: note.body }) : null
|
|
763
763
|
] }) : null,
|
|
764
|
-
|
|
764
|
+
link2 ? /* @__PURE__ */ jsx16("p", { className: "dt-lp-cta-link", children: /* @__PURE__ */ jsx16(SmartLink, { href: link2.href, reload: link2.reload, children: link2.label }) }) : null
|
|
765
765
|
] }) }) });
|
|
766
766
|
}
|
|
767
767
|
|
|
@@ -824,17 +824,17 @@ function LandingPage({ brand, content, preview = null, slots = {}, linkComponent
|
|
|
824
824
|
import { jsx as jsx18, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
825
825
|
function PreviewFrame({
|
|
826
826
|
id = "product",
|
|
827
|
-
heading,
|
|
827
|
+
heading: heading2,
|
|
828
828
|
caption,
|
|
829
829
|
meta,
|
|
830
830
|
badge = "Demo data",
|
|
831
|
-
brand:
|
|
831
|
+
brand: own2,
|
|
832
832
|
children
|
|
833
833
|
}) {
|
|
834
834
|
const ctx = useLanding();
|
|
835
|
-
const brand =
|
|
835
|
+
const brand = own2 || ctx.brand;
|
|
836
836
|
return /* @__PURE__ */ jsxs16("section", { id, "aria-labelledby": `${id}-title`, className: "dt-lp-preview", children: [
|
|
837
|
-
/* @__PURE__ */ jsx18("h2", { id: `${id}-title`, className: "dt-lp-sr-only", children:
|
|
837
|
+
/* @__PURE__ */ jsx18("h2", { id: `${id}-title`, className: "dt-lp-sr-only", children: heading2 || `${brand ? brand.name : "Product"} overview` }),
|
|
838
838
|
/* @__PURE__ */ jsx18(Wrap, { children: /* @__PURE__ */ jsxs16("figure", { className: "dt-lp-preview-frame", children: [
|
|
839
839
|
/* @__PURE__ */ jsxs16("div", { className: "dt-lp-preview-bar", children: [
|
|
840
840
|
/* @__PURE__ */ jsxs16("div", { className: "dt-lp-preview-title", children: [
|
|
@@ -1484,6 +1484,12 @@ var SECTION_TYPES = {
|
|
|
1484
1484
|
var SECTION_TYPE_KEYS = Object.keys(SECTION_TYPES);
|
|
1485
1485
|
var PAGE_VERSION = 1;
|
|
1486
1486
|
var MAX_SECTIONS = 20;
|
|
1487
|
+
function sectionDefaults(type) {
|
|
1488
|
+
const def = SECTION_TYPES[type];
|
|
1489
|
+
if (!def) throw new Error(`Unknown section type "${type}"`);
|
|
1490
|
+
const fill2 = (fields2) => Object.fromEntries(fields2.map((f) => [f.key, f.kind === "list" ? (f.default || []).map((item) => ({ ...fill2(f.of), ...item })) : f.default]));
|
|
1491
|
+
return fill2(def.fields);
|
|
1492
|
+
}
|
|
1487
1493
|
|
|
1488
1494
|
// src/schema/richtext.js
|
|
1489
1495
|
var ALLOWED = /* @__PURE__ */ new Set(["p", "br", "h2", "h3", "h4", "strong", "b", "em", "i", "u", "ul", "ol", "li", "a", "blockquote"]);
|
|
@@ -1504,7 +1510,7 @@ var decode = (s) => s.replace(/&(amp|quot|#39|lt|gt|#x[0-9a-f]+|#\d+);/gi, (m, e
|
|
|
1504
1510
|
});
|
|
1505
1511
|
function safeHref(raw) {
|
|
1506
1512
|
const v = decode(String(raw || "")).replace(/[\u0000- \u007f-\u009f]/g, "");
|
|
1507
|
-
if (!v) return null;
|
|
1513
|
+
if (!v || v.includes("\\")) return null;
|
|
1508
1514
|
if (/^(https?:\/\/|mailto:|tel:)/i.test(v)) return v;
|
|
1509
1515
|
if (/^\/(?!\/)/.test(v) || v.startsWith("#")) return v;
|
|
1510
1516
|
return null;
|
|
@@ -1628,6 +1634,9 @@ function field(f, value, path, errors) {
|
|
|
1628
1634
|
throw new Error(`Unknown field kind "${f.kind}" (${path})`);
|
|
1629
1635
|
}
|
|
1630
1636
|
}
|
|
1637
|
+
function validateFields(defs, obj, path, errors) {
|
|
1638
|
+
return fields(defs, obj, path, errors);
|
|
1639
|
+
}
|
|
1631
1640
|
function fields(defs, obj, path, errors) {
|
|
1632
1641
|
const src = obj && typeof obj === "object" && !Array.isArray(obj) ? obj : {};
|
|
1633
1642
|
if (obj != null && src !== obj) errors.push({ path, message: "must be an object" });
|
|
@@ -1665,6 +1674,18 @@ function validatePage(input) {
|
|
|
1665
1674
|
});
|
|
1666
1675
|
return { ok: errors.length === 0, errors, page: { version: PAGE_VERSION, sections } };
|
|
1667
1676
|
}
|
|
1677
|
+
function defaultPage({ name = "Our store", tagline = "" } = {}) {
|
|
1678
|
+
const hero = sectionDefaults("hero_slider");
|
|
1679
|
+
hero.slides = [{ ...hero.slides[0], heading: String(name).slice(0, 90), text: String(tagline || "").slice(0, 220), cta_label: "Shop now", cta_href: "/" }];
|
|
1680
|
+
return validatePage({
|
|
1681
|
+
version: PAGE_VERSION,
|
|
1682
|
+
sections: [
|
|
1683
|
+
{ id: "hero", type: "hero_slider", props: { ...hero, autoplay: false } },
|
|
1684
|
+
{ id: "featured", type: "featured", props: sectionDefaults("featured") },
|
|
1685
|
+
{ id: "categories", type: "categories", props: sectionDefaults("categories") }
|
|
1686
|
+
]
|
|
1687
|
+
}).page;
|
|
1688
|
+
}
|
|
1668
1689
|
|
|
1669
1690
|
// src/schema/color.js
|
|
1670
1691
|
var HEX_RE = /^#([0-9a-f]{6})$/i;
|
|
@@ -2039,12 +2060,12 @@ function SlotSection({ id, title: title2, intro: intro2, tone: tone2, slot, ...p
|
|
|
2039
2060
|
/* @__PURE__ */ jsx24("div", { className: "dt-lp-slot-body", children: body })
|
|
2040
2061
|
] });
|
|
2041
2062
|
}
|
|
2042
|
-
function PromoBanner({ id, eyebrow, heading, text, cta_label: label, cta_href: href, image, style = "brand", media }) {
|
|
2063
|
+
function PromoBanner({ id, eyebrow, heading: heading2, text, cta_label: label, cta_href: href, image, style = "brand", media }) {
|
|
2043
2064
|
const src = media(image, "wide");
|
|
2044
2065
|
return /* @__PURE__ */ jsx24("section", { id, "aria-labelledby": hid(id), className: "dt-lp-section dt-lp-promo-wrap", children: /* @__PURE__ */ jsx24("div", { className: "dt-lp-wrap", children: /* @__PURE__ */ jsxs21("div", { className: `dt-lp-promo dt-lp-promo--${style}${src ? " dt-lp-promo--image" : ""}`, children: [
|
|
2045
2066
|
/* @__PURE__ */ jsxs21("div", { className: "dt-lp-promo-copy", children: [
|
|
2046
2067
|
eyebrow ? /* @__PURE__ */ jsx24("p", { className: "dt-lp-eyebrow dt-lp-eyebrow--inverse", children: eyebrow }) : null,
|
|
2047
|
-
/* @__PURE__ */ jsx24("h2", { id: hid(id), className: "dt-lp-h2 dt-lp-promo-heading", children:
|
|
2068
|
+
/* @__PURE__ */ jsx24("h2", { id: hid(id), className: "dt-lp-h2 dt-lp-promo-heading", children: heading2 }),
|
|
2048
2069
|
text ? /* @__PURE__ */ jsx24("p", { className: "dt-lp-promo-text", children: text }) : null,
|
|
2049
2070
|
label && href ? /* @__PURE__ */ jsx24(SmartLink, { href, className: "dt-lp-btn dt-lp-btn--lg dt-lp-btn--inverse", children: label }) : null
|
|
2050
2071
|
] }),
|
|
@@ -2220,6 +2241,291 @@ function TenantPage({
|
|
|
2220
2241
|
return /* @__PURE__ */ jsx25(SectionBoundary, { children: /* @__PURE__ */ jsx25(Block, { ...s.props, id: `s-${s.id}`, first: i === 0 && headingStart === 1, media, slot: slots[s.type], onSubscribe }) }, s.id);
|
|
2221
2242
|
}) }) });
|
|
2222
2243
|
}
|
|
2244
|
+
|
|
2245
|
+
// src/tenant/TemplateRegion.jsx
|
|
2246
|
+
import { Fragment as Fragment7, useMemo as useMemo2 } from "react";
|
|
2247
|
+
import { jsx as jsx26 } from "react/jsx-runtime";
|
|
2248
|
+
var noMedia2 = () => null;
|
|
2249
|
+
function TemplateRegion({
|
|
2250
|
+
blocks = [],
|
|
2251
|
+
slots = {},
|
|
2252
|
+
theme,
|
|
2253
|
+
resolveMedia = noMedia2,
|
|
2254
|
+
linkComponent = null,
|
|
2255
|
+
storeName = "",
|
|
2256
|
+
className = ""
|
|
2257
|
+
}) {
|
|
2258
|
+
const vars = useMemo2(() => themeVars(theme), [theme]);
|
|
2259
|
+
const ctx = useMemo2(() => ({ brand: { id: "tenant", name: storeName, primary: vars.theme.primary }, linkComponent, onBookDemo: null, home: false }), [storeName, vars.theme.primary, linkComponent]);
|
|
2260
|
+
const media = (ref, variant) => ref && ref.media_id != null ? resolveMedia(ref, variant) : null;
|
|
2261
|
+
return /* @__PURE__ */ jsx26(Fragment7, { children: blocks.filter((b) => b && b.enabled !== false).map((b) => {
|
|
2262
|
+
const Slot = slots[b.type];
|
|
2263
|
+
if (Slot) {
|
|
2264
|
+
return /* @__PURE__ */ jsx26(SectionBoundary, { children: /* @__PURE__ */ jsx26(Slot, { block: b, props: b.props || {} }) }, b.id);
|
|
2265
|
+
}
|
|
2266
|
+
const Block = TENANT_BLOCKS[b.type];
|
|
2267
|
+
if (!Block || Block === TENANT_BLOCKS.featured) return null;
|
|
2268
|
+
return /* @__PURE__ */ jsx26(SectionBoundary, { children: /* @__PURE__ */ jsx26(LandingProvider, { value: ctx, children: /* @__PURE__ */ jsx26("div", { className: `dt-landing dt-lp-tenant dt-lp-region ${className}`.trim(), style: vars.style, "data-block": b.type, children: /* @__PURE__ */ jsx26(Block, { ...b.props, id: `b-${b.id}`, first: false, media, slot: null, onSubscribe: null }) }) }) }, b.id);
|
|
2269
|
+
}) });
|
|
2270
|
+
}
|
|
2271
|
+
|
|
2272
|
+
// src/schema/store.js
|
|
2273
|
+
var STORE_VERSION = 2;
|
|
2274
|
+
var MAX_BLOCKS = 12;
|
|
2275
|
+
var ID_RE2 = /^[a-z0-9][a-z0-9-]{0,39}$/;
|
|
2276
|
+
var opt = (...pairs) => pairs.map(([value, label]) => ({ value, label }));
|
|
2277
|
+
var sel = (key, label, options, d) => ({ key, kind: "select", label, options, default: d });
|
|
2278
|
+
var bool = (key, label, d) => ({ key, kind: "bool", label, default: d });
|
|
2279
|
+
var heading = (d, max = 90) => ({ key: "title", kind: "text", label: "Heading", max, default: d });
|
|
2280
|
+
var CONTENT_BLOCKS = ["promo_banner", "usp_strip", "trust_badges", "cta_cards", "faq", "rich_text", "testimonials", "brand_strip", "gallery"];
|
|
2281
|
+
var LISTING_SLOTS = {
|
|
2282
|
+
collection_header: { label: "Collection header", description: "Title, description and quick links for the category or search.", fields: [bool("show_description", "Show the description", true), bool("show_quick_links", "Show quick links", true)] },
|
|
2283
|
+
toolbar: { label: "Toolbar and sort", description: "Result count, sort and the filters in use.", fields: [] },
|
|
2284
|
+
filters: { label: "Filters", description: "Price, brand and spec filters.", required: true, fields: [] },
|
|
2285
|
+
results: { label: "Results grid", description: "The products, with paging.", required: true, fields: [] }
|
|
2286
|
+
};
|
|
2287
|
+
var PRODUCT_SLOTS = {
|
|
2288
|
+
gallery: { label: "Gallery", description: "Photos of the product.", required: true, fields: [] },
|
|
2289
|
+
buy_box: { label: "Buy box", description: "Title, price, key specs and the buy buttons.", required: true, fields: [bool("show_compare", "Show Add to compare", true), bool("show_quote", "Show bulk quote link", true)] },
|
|
2290
|
+
highlights: { label: "Highlights", description: "The product's headline numbers and selling points.", fields: [heading("Highlights")] },
|
|
2291
|
+
delivery: { label: "Delivery check", description: "PIN code delivery estimate.", fields: [] },
|
|
2292
|
+
seller: { label: "Seller", description: "Who sells and services the product.", fields: [] },
|
|
2293
|
+
inspection: { label: "Inspection report", description: "Checks, grade and condition notes.", fields: [heading("Inspection report"), bool("expanded", "Open by default", false)] },
|
|
2294
|
+
battery: { label: "Battery", description: "Battery health, where the product has one.", fields: [] },
|
|
2295
|
+
included: { label: "In the box", description: "What ships with the product.", fields: [] },
|
|
2296
|
+
specs: { label: "Specifications", description: "The full specification table.", fields: [heading("Specifications")] },
|
|
2297
|
+
policies: { label: "Warranty and returns", description: "The store's warranty and return terms.", fields: [heading("Warranty and returns")] },
|
|
2298
|
+
similar: { label: "Similar products", description: "More products like this one.", fields: [heading("Similar products"), sel("limit", "How many", opt(["3", "3"], ["4", "4"], ["6", "6"], ["8", "8"]), "4")] }
|
|
2299
|
+
};
|
|
2300
|
+
var SORTS = opt(["relevance", "Recommended"], ["newest", "Newest first"], ["price_asc", "Price: low to high"], ["price_desc", "Price: high to low"], ["discount", "Biggest discount"]);
|
|
2301
|
+
var STORE_PAGES = {
|
|
2302
|
+
listing: {
|
|
2303
|
+
label: "Listing page",
|
|
2304
|
+
slots: LISTING_SLOTS,
|
|
2305
|
+
settings: [
|
|
2306
|
+
sel("columns", "Columns on desktop", opt(["3", "3"], ["4", "4"]), "3"),
|
|
2307
|
+
sel("columns_mobile", "Columns on phones", opt(["1", "1"], ["2", "2"]), "2"),
|
|
2308
|
+
sel("page_size", "Products per page", opt(["12", "12"], ["24", "24"], ["36", "36"]), "12"),
|
|
2309
|
+
sel("default_sort", "Default sort", SORTS, "relevance"),
|
|
2310
|
+
sel("filters", "Filters show as", opt(["sidebar", "Sidebar"], ["topbar", "Bar above the results"], ["drawer", "Button that opens a panel"]), "sidebar"),
|
|
2311
|
+
bool("active_chips", "Show the filters in use as chips", true),
|
|
2312
|
+
sel("paging", "Paging", opt(["pages", "Page numbers"], ["load_more", "Load more button"]), "pages")
|
|
2313
|
+
],
|
|
2314
|
+
templates: {
|
|
2315
|
+
classic: { label: "Classic", description: "Filters in a sidebar beside a product grid.", settings: {}, blocks: ["collection_header", "toolbar", "filters", "results"] },
|
|
2316
|
+
topbar: { label: "Top bar", description: "Filter chips above a wide grid, more room for products.", settings: { columns: "4", filters: "topbar", paging: "load_more" }, blocks: ["collection_header", "filters", "toolbar", "results"] },
|
|
2317
|
+
editorial: { label: "Editorial", description: "A story-led collection header, then the grid behind a filter button.", settings: { columns: "4", filters: "drawer", active_chips: false }, blocks: ["collection_header", "rich_text", "toolbar", "filters", "results"] }
|
|
2318
|
+
},
|
|
2319
|
+
default: "classic"
|
|
2320
|
+
},
|
|
2321
|
+
product: {
|
|
2322
|
+
label: "Product page",
|
|
2323
|
+
slots: PRODUCT_SLOTS,
|
|
2324
|
+
settings: [
|
|
2325
|
+
sel("gallery", "Gallery", opt(["thumbs_side", "Thumbnails beside the photo"], ["thumbs_below", "Thumbnails below the photo"], ["mosaic", "Full-width mosaic"]), "thumbs_side"),
|
|
2326
|
+
bool("sticky_buy_bar", "Sticky buy bar on phones", true),
|
|
2327
|
+
sel("spec_tiles", "Key spec tiles", opt(["0", "None"], ["4", "4"], ["6", "6"]), "4"),
|
|
2328
|
+
bool("breadcrumbs", "Show breadcrumbs", true)
|
|
2329
|
+
],
|
|
2330
|
+
templates: {
|
|
2331
|
+
classic: { label: "Classic", description: "Photos beside the buy box, details below.", settings: { gallery: "thumbs_side" }, blocks: ["gallery", "buy_box", "delivery", "seller", "inspection", "battery", "included", "specs", "policies", "similar"] },
|
|
2332
|
+
immersive: { label: "Immersive", description: "A full-width photo mosaic first, a floating buy panel, the story below.", settings: { gallery: "mosaic" }, blocks: ["gallery", "buy_box", "highlights", "inspection", "specs", "policies", "similar"] },
|
|
2333
|
+
compact: { label: "Compact", description: "One column, phone first, with a sticky buy bar.", settings: { gallery: "thumbs_below", spec_tiles: "0", breadcrumbs: false }, blocks: ["gallery", "buy_box", "delivery", "inspection", "specs", "policies", "similar"] }
|
|
2334
|
+
},
|
|
2335
|
+
default: "classic"
|
|
2336
|
+
}
|
|
2337
|
+
};
|
|
2338
|
+
var STORE_PAGE_KEYS = Object.keys(STORE_PAGES);
|
|
2339
|
+
var link = [
|
|
2340
|
+
{ key: "label", kind: "text", label: "Label", max: 30, required: true, default: "" },
|
|
2341
|
+
{ key: "href", kind: "url", label: "Link", required: true, default: "" }
|
|
2342
|
+
];
|
|
2343
|
+
var CHROME_FIELDS = [
|
|
2344
|
+
{
|
|
2345
|
+
key: "announcement",
|
|
2346
|
+
kind: "group",
|
|
2347
|
+
label: "Announcement bar",
|
|
2348
|
+
fields: [
|
|
2349
|
+
bool("enabled", "Show the announcement bar", false),
|
|
2350
|
+
{ key: "text", kind: "text", label: "Message", max: 120, default: "" },
|
|
2351
|
+
{ key: "href", kind: "url", label: "Link", default: "" },
|
|
2352
|
+
sel("tone", "Colour", opt(["brand", "Brand"], ["accent", "Accent"], ["dark", "Dark"]), "brand")
|
|
2353
|
+
]
|
|
2354
|
+
},
|
|
2355
|
+
{
|
|
2356
|
+
key: "header",
|
|
2357
|
+
kind: "group",
|
|
2358
|
+
label: "Header",
|
|
2359
|
+
fields: [
|
|
2360
|
+
{ key: "utility_text", kind: "text", label: "Strip above the header", max: 80, default: "" },
|
|
2361
|
+
bool("show_search", "Search box", true),
|
|
2362
|
+
bool("category_nav", "Menu of the categories you sell", true),
|
|
2363
|
+
{ key: "nav_links", kind: "list", label: "Extra menu links", max: 8, itemLabel: "label", of: link, default: [] },
|
|
2364
|
+
bool("sticky", "Keep the header on screen while scrolling", true)
|
|
2365
|
+
]
|
|
2366
|
+
},
|
|
2367
|
+
{
|
|
2368
|
+
key: "footer",
|
|
2369
|
+
kind: "group",
|
|
2370
|
+
label: "Footer",
|
|
2371
|
+
fields: [
|
|
2372
|
+
{ key: "about", kind: "textarea", label: "About the store", max: 240, default: "" },
|
|
2373
|
+
{
|
|
2374
|
+
key: "columns",
|
|
2375
|
+
kind: "list",
|
|
2376
|
+
label: "Link columns",
|
|
2377
|
+
max: 4,
|
|
2378
|
+
itemLabel: "title",
|
|
2379
|
+
of: [{ key: "title", kind: "text", label: "Column heading", max: 40, required: true, default: "" }, { key: "links", kind: "list", label: "Links", max: 8, itemLabel: "label", of: link, default: [] }],
|
|
2380
|
+
default: []
|
|
2381
|
+
},
|
|
2382
|
+
{
|
|
2383
|
+
key: "socials",
|
|
2384
|
+
kind: "list",
|
|
2385
|
+
label: "Social profiles",
|
|
2386
|
+
max: 6,
|
|
2387
|
+
itemLabel: "network",
|
|
2388
|
+
of: [sel("network", "Network", opt(["instagram", "Instagram"], ["facebook", "Facebook"], ["youtube", "YouTube"], ["x", "X"], ["linkedin", "LinkedIn"], ["whatsapp", "WhatsApp"]), "instagram"), { key: "href", kind: "url", label: "Profile link", required: true, default: "" }],
|
|
2389
|
+
default: []
|
|
2390
|
+
},
|
|
2391
|
+
{ key: "note", kind: "text", label: "Small print (GSTIN, address)", max: 160, default: "" }
|
|
2392
|
+
]
|
|
2393
|
+
}
|
|
2394
|
+
];
|
|
2395
|
+
var CARD_FIELDS = [
|
|
2396
|
+
sel("image_ratio", "Photo shape", opt(["4:3", "Landscape 4:3"], ["1:1", "Square"], ["3:4", "Portrait 3:4"]), "4:3"),
|
|
2397
|
+
bool("badges", "Condition and discount badges", true),
|
|
2398
|
+
bool("spec_line", "Key specs line", true),
|
|
2399
|
+
bool("compare", "Compare checkbox", true),
|
|
2400
|
+
bool("quick_add", "Add to cart button", true),
|
|
2401
|
+
bool("mrp", "Show the MRP struck through", true),
|
|
2402
|
+
sel("hover", "On hover", opt(["none", "Nothing"], ["zoom", "Zoom the photo"], ["second_image", "Show the second photo"]), "zoom")
|
|
2403
|
+
];
|
|
2404
|
+
function validateGroups(defs, obj, path, errors) {
|
|
2405
|
+
const src = obj && typeof obj === "object" && !Array.isArray(obj) ? obj : {};
|
|
2406
|
+
if (obj != null && src !== obj) errors.push({ path, message: "must be an object" });
|
|
2407
|
+
return Object.fromEntries(defs.map((g) => [g.key, g.kind === "group" ? validateFields(g.fields, src[g.key], `${path}.${g.key}`, errors) : validateFields([g], src, path, errors)[g.key]]));
|
|
2408
|
+
}
|
|
2409
|
+
var fill = (defs) => Object.fromEntries(defs.map((f) => [f.key, f.kind === "group" ? fill(f.fields) : f.kind === "list" ? [...f.default || []] : f.default]));
|
|
2410
|
+
var defaultChrome = () => fill(CHROME_FIELDS);
|
|
2411
|
+
var defaultCard = () => fill(CARD_FIELDS);
|
|
2412
|
+
function blockDef(pageKey, type) {
|
|
2413
|
+
const page = own(STORE_PAGES, pageKey) ? STORE_PAGES[pageKey] : null;
|
|
2414
|
+
if (!page) return null;
|
|
2415
|
+
if (own(page.slots, type)) return { ...page.slots[type], slot: true };
|
|
2416
|
+
if (CONTENT_BLOCKS.includes(type) && own(SECTION_TYPES, type)) return { ...SECTION_TYPES[type], slot: false };
|
|
2417
|
+
return null;
|
|
2418
|
+
}
|
|
2419
|
+
var own = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key);
|
|
2420
|
+
var blockId = (type) => type.replace(/_/g, "-");
|
|
2421
|
+
var blockDefaults = (pageKey, type) => {
|
|
2422
|
+
const def = blockDef(pageKey, type);
|
|
2423
|
+
return def.slot ? fill(def.fields) : sectionDefaults(type);
|
|
2424
|
+
};
|
|
2425
|
+
function templatePage(pageKey, template) {
|
|
2426
|
+
const page = STORE_PAGES[pageKey];
|
|
2427
|
+
const key = own(page.templates, template) ? template : page.default;
|
|
2428
|
+
const tpl = page.templates[key];
|
|
2429
|
+
const settings = { ...fill(page.settings), ...tpl.settings };
|
|
2430
|
+
const blocks = tpl.blocks.map((type) => ({ id: blockId(type), type, enabled: true, props: blockDefaults(pageKey, type) }));
|
|
2431
|
+
return { template: key, settings, blocks };
|
|
2432
|
+
}
|
|
2433
|
+
function validateTemplatePage(pageKey, input, path, errors) {
|
|
2434
|
+
var _a, _b;
|
|
2435
|
+
const page = STORE_PAGES[pageKey];
|
|
2436
|
+
const src = input && typeof input === "object" && !Array.isArray(input) ? input : {};
|
|
2437
|
+
let template = (_a = src.template) != null ? _a : page.default;
|
|
2438
|
+
if (!own(page.templates, template)) {
|
|
2439
|
+
errors.push({ path: `${path}.template`, message: `must be one of ${Object.keys(page.templates).join(", ")}` });
|
|
2440
|
+
template = page.default;
|
|
2441
|
+
}
|
|
2442
|
+
const tpl = page.templates[template];
|
|
2443
|
+
const settingDefs = page.settings.map((f) => tpl.settings[f.key] === void 0 ? f : { ...f, default: tpl.settings[f.key] });
|
|
2444
|
+
const settings = validateFields(settingDefs, (_b = src.settings) != null ? _b : {}, `${path}.settings`, errors);
|
|
2445
|
+
const list = Array.isArray(src.blocks) ? src.blocks : null;
|
|
2446
|
+
if (src.blocks != null && !list) errors.push({ path: `${path}.blocks`, message: "must be a list" });
|
|
2447
|
+
const raw = list != null ? list : templatePage(pageKey, template).blocks;
|
|
2448
|
+
if (raw.length > MAX_BLOCKS) errors.push({ path: `${path}.blocks`, message: `can have at most ${MAX_BLOCKS}` });
|
|
2449
|
+
const ids = /* @__PURE__ */ new Set();
|
|
2450
|
+
const slotsSeen = /* @__PURE__ */ new Set();
|
|
2451
|
+
const blocks = [];
|
|
2452
|
+
raw.slice(0, MAX_BLOCKS).forEach((b, i) => {
|
|
2453
|
+
var _a2, _b2;
|
|
2454
|
+
const at = `${path}.blocks[${i}]`;
|
|
2455
|
+
if (!b || typeof b !== "object") {
|
|
2456
|
+
errors.push({ path: at, message: "must be an object" });
|
|
2457
|
+
return;
|
|
2458
|
+
}
|
|
2459
|
+
const def = blockDef(pageKey, b.type);
|
|
2460
|
+
if (!def) {
|
|
2461
|
+
errors.push({ path: `${at}.type`, message: `unknown block "${b.type}" on the ${page.label.toLowerCase()}` });
|
|
2462
|
+
return;
|
|
2463
|
+
}
|
|
2464
|
+
const id = String((_a2 = b.id) != null ? _a2 : "");
|
|
2465
|
+
if (!ID_RE2.test(id)) errors.push({ path: `${at}.id`, message: "must be 1\u201340 lowercase letters, digits or dashes" });
|
|
2466
|
+
else if (ids.has(id)) errors.push({ path: `${at}.id`, message: `duplicate id "${id}"` });
|
|
2467
|
+
ids.add(id);
|
|
2468
|
+
if (def.slot) {
|
|
2469
|
+
if (slotsSeen.has(b.type)) {
|
|
2470
|
+
errors.push({ path: `${at}.type`, message: `"${def.label}" can appear only once` });
|
|
2471
|
+
return;
|
|
2472
|
+
}
|
|
2473
|
+
slotsSeen.add(b.type);
|
|
2474
|
+
}
|
|
2475
|
+
let enabled = b.enabled === void 0 ? true : b.enabled;
|
|
2476
|
+
if (typeof enabled !== "boolean") {
|
|
2477
|
+
errors.push({ path: `${at}.enabled`, message: "must be true or false" });
|
|
2478
|
+
enabled = true;
|
|
2479
|
+
}
|
|
2480
|
+
if (def.required && !enabled) {
|
|
2481
|
+
errors.push({ path: `${at}.enabled`, message: `"${def.label}" is required and cannot be hidden` });
|
|
2482
|
+
enabled = true;
|
|
2483
|
+
}
|
|
2484
|
+
blocks.push({ id, type: b.type, enabled, props: validateFields(def.fields, (_b2 = b.props) != null ? _b2 : {}, `${at}.props`, errors) });
|
|
2485
|
+
});
|
|
2486
|
+
for (const [type, def] of Object.entries(page.slots)) {
|
|
2487
|
+
if (def.required && !slotsSeen.has(type)) {
|
|
2488
|
+
errors.push({ path: `${path}.blocks`, message: `"${def.label}" is required` });
|
|
2489
|
+
blocks.push({ id: ids.has(blockId(type)) ? `${blockId(type)}-1` : blockId(type), type, enabled: true, props: blockDefaults(pageKey, type) });
|
|
2490
|
+
}
|
|
2491
|
+
}
|
|
2492
|
+
return { template, settings, blocks };
|
|
2493
|
+
}
|
|
2494
|
+
function migrateStore(input) {
|
|
2495
|
+
const home = input && typeof input === "object" && Array.isArray(input.sections) ? input : null;
|
|
2496
|
+
return {
|
|
2497
|
+
version: STORE_VERSION,
|
|
2498
|
+
pages: { home: home ? { version: 1, sections: home.sections } : defaultPage(), listing: templatePage("listing"), product: templatePage("product") },
|
|
2499
|
+
chrome: defaultChrome(),
|
|
2500
|
+
card: defaultCard()
|
|
2501
|
+
};
|
|
2502
|
+
}
|
|
2503
|
+
var isLegacyPage = (doc) => !!doc && typeof doc === "object" && doc.version !== STORE_VERSION && Array.isArray(doc.sections);
|
|
2504
|
+
function validateStore(input) {
|
|
2505
|
+
var _a, _b, _c;
|
|
2506
|
+
const errors = [];
|
|
2507
|
+
const src0 = isLegacyPage(input) ? migrateStore(input) : input;
|
|
2508
|
+
const src = src0 && typeof src0 === "object" && !Array.isArray(src0) ? src0 : null;
|
|
2509
|
+
if (!src) {
|
|
2510
|
+
const doc2 = migrateStore(null);
|
|
2511
|
+
return { ok: false, errors: [{ path: "doc", message: "must be an object" }], doc: doc2 };
|
|
2512
|
+
}
|
|
2513
|
+
if (src.version != null && src.version !== STORE_VERSION) errors.push({ path: "version", message: `must be ${STORE_VERSION}` });
|
|
2514
|
+
const pages = src.pages && typeof src.pages === "object" ? src.pages : {};
|
|
2515
|
+
const home = validatePage((_a = pages.home) != null ? _a : defaultPage());
|
|
2516
|
+
home.errors.forEach((e) => errors.push({ path: `pages.home.${e.path}`, message: e.message }));
|
|
2517
|
+
const doc = {
|
|
2518
|
+
version: STORE_VERSION,
|
|
2519
|
+
pages: {
|
|
2520
|
+
home: home.page,
|
|
2521
|
+
...Object.fromEntries(STORE_PAGE_KEYS.map((k) => [k, validateTemplatePage(k, pages[k], `pages.${k}`, errors)]))
|
|
2522
|
+
},
|
|
2523
|
+
chrome: validateGroups(CHROME_FIELDS, (_b = src.chrome) != null ? _b : {}, "chrome", errors),
|
|
2524
|
+
card: validateFields(CARD_FIELDS, (_c = src.card) != null ? _c : {}, "card", errors)
|
|
2525
|
+
};
|
|
2526
|
+
if (doc.chrome.announcement.enabled && !doc.chrome.announcement.text) errors.push({ path: "chrome.announcement.text", message: "is required when the bar is shown" });
|
|
2527
|
+
return { ok: errors.length === 0, errors, doc };
|
|
2528
|
+
}
|
|
2223
2529
|
export {
|
|
2224
2530
|
Audiences,
|
|
2225
2531
|
AuthShell,
|
|
@@ -2252,6 +2558,7 @@ export {
|
|
|
2252
2558
|
SectionIntro,
|
|
2253
2559
|
SmartLink,
|
|
2254
2560
|
TENANT_BLOCKS,
|
|
2561
|
+
TemplateRegion,
|
|
2255
2562
|
TenantPage,
|
|
2256
2563
|
TextLink,
|
|
2257
2564
|
Wordmark,
|
|
@@ -2270,5 +2577,6 @@ export {
|
|
|
2270
2577
|
useLanding,
|
|
2271
2578
|
useTapReveal,
|
|
2272
2579
|
validatePage,
|
|
2580
|
+
validateStore,
|
|
2273
2581
|
validateTheme
|
|
2274
2582
|
};
|