@dreamtree-org/landing-kit 0.7.2 → 0.9.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 +55 -0
- package/PLATFORM-API.md +213 -0
- package/README.md +96 -0
- package/ai-skills/landing-kit.md +97 -0
- package/dist/index.cjs +323 -15
- package/dist/index.js +323 -15
- package/dist/platform.cjs +2469 -0
- package/dist/platform.js +2450 -0
- package/dist/schema.cjs +324 -3
- package/dist/schema.js +304 -3
- package/index.d.ts +15 -1
- package/package.json +26 -4
- package/platform.d.ts +342 -0
- package/schema.d.ts +50 -0
package/dist/schema.js
CHANGED
|
@@ -267,8 +267,8 @@ var MAX_SECTIONS = 20;
|
|
|
267
267
|
function sectionDefaults(type) {
|
|
268
268
|
const def = SECTION_TYPES[type];
|
|
269
269
|
if (!def) throw new Error(`Unknown section type "${type}"`);
|
|
270
|
-
const
|
|
271
|
-
return
|
|
270
|
+
const fill2 = (fields2) => Object.fromEntries(fields2.map((f) => [f.key, f.kind === "list" ? (f.default || []).map((item) => ({ ...fill2(f.of), ...item })) : f.default]));
|
|
271
|
+
return fill2(def.fields);
|
|
272
272
|
}
|
|
273
273
|
var seq = 0;
|
|
274
274
|
function newSectionId(type) {
|
|
@@ -298,7 +298,7 @@ var decode = (s) => s.replace(/&(amp|quot|#39|lt|gt|#x[0-9a-f]+|#\d+);/gi, (m, e
|
|
|
298
298
|
});
|
|
299
299
|
function safeHref(raw) {
|
|
300
300
|
const v = decode(String(raw || "")).replace(/[\u0000- \u007f-\u009f]/g, "");
|
|
301
|
-
if (!v) return null;
|
|
301
|
+
if (!v || v.includes("\\")) return null;
|
|
302
302
|
if (/^(https?:\/\/|mailto:|tel:)/i.test(v)) return v;
|
|
303
303
|
if (/^\/(?!\/)/.test(v) || v.startsWith("#")) return v;
|
|
304
304
|
return null;
|
|
@@ -422,6 +422,9 @@ function field(f, value, path, errors) {
|
|
|
422
422
|
throw new Error(`Unknown field kind "${f.kind}" (${path})`);
|
|
423
423
|
}
|
|
424
424
|
}
|
|
425
|
+
function validateFields(defs, obj, path, errors) {
|
|
426
|
+
return fields(defs, obj, path, errors);
|
|
427
|
+
}
|
|
425
428
|
function fields(defs, obj, path, errors) {
|
|
426
429
|
const src = obj && typeof obj === "object" && !Array.isArray(obj) ? obj : {};
|
|
427
430
|
if (obj != null && src !== obj) errors.push({ path, message: "must be an object" });
|
|
@@ -645,24 +648,318 @@ function themeVars(input) {
|
|
|
645
648
|
}
|
|
646
649
|
};
|
|
647
650
|
}
|
|
651
|
+
|
|
652
|
+
// src/schema/store.js
|
|
653
|
+
var STORE_VERSION = 2;
|
|
654
|
+
var MAX_BLOCKS = 12;
|
|
655
|
+
var ID_RE2 = /^[a-z0-9][a-z0-9-]{0,39}$/;
|
|
656
|
+
var opt = (...pairs) => pairs.map(([value, label]) => ({ value, label }));
|
|
657
|
+
var sel = (key, label, options, d) => ({ key, kind: "select", label, options, default: d });
|
|
658
|
+
var bool = (key, label, d) => ({ key, kind: "bool", label, default: d });
|
|
659
|
+
var heading = (d, max = 90) => ({ key: "title", kind: "text", label: "Heading", max, default: d });
|
|
660
|
+
var CONTENT_BLOCKS = ["promo_banner", "usp_strip", "trust_badges", "cta_cards", "faq", "rich_text", "testimonials", "brand_strip", "gallery"];
|
|
661
|
+
var LISTING_SLOTS = {
|
|
662
|
+
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)] },
|
|
663
|
+
toolbar: { label: "Toolbar and sort", description: "Result count, sort and the filters in use.", fields: [] },
|
|
664
|
+
filters: { label: "Filters", description: "Price, brand and spec filters.", required: true, fields: [] },
|
|
665
|
+
results: { label: "Results grid", description: "The products, with paging.", required: true, fields: [] }
|
|
666
|
+
};
|
|
667
|
+
var PRODUCT_SLOTS = {
|
|
668
|
+
gallery: { label: "Gallery", description: "Photos of the product.", required: true, fields: [] },
|
|
669
|
+
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)] },
|
|
670
|
+
highlights: { label: "Highlights", description: "The product's headline numbers and selling points.", fields: [heading("Highlights")] },
|
|
671
|
+
delivery: { label: "Delivery check", description: "PIN code delivery estimate.", fields: [] },
|
|
672
|
+
seller: { label: "Seller", description: "Who sells and services the product.", fields: [] },
|
|
673
|
+
inspection: { label: "Inspection report", description: "Checks, grade and condition notes.", fields: [heading("Inspection report"), bool("expanded", "Open by default", false)] },
|
|
674
|
+
battery: { label: "Battery", description: "Battery health, where the product has one.", fields: [] },
|
|
675
|
+
included: { label: "In the box", description: "What ships with the product.", fields: [] },
|
|
676
|
+
specs: { label: "Specifications", description: "The full specification table.", fields: [heading("Specifications")] },
|
|
677
|
+
policies: { label: "Warranty and returns", description: "The store's warranty and return terms.", fields: [heading("Warranty and returns")] },
|
|
678
|
+
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")] }
|
|
679
|
+
};
|
|
680
|
+
var SORTS = opt(["relevance", "Recommended"], ["newest", "Newest first"], ["price_asc", "Price: low to high"], ["price_desc", "Price: high to low"], ["discount", "Biggest discount"]);
|
|
681
|
+
var STORE_PAGES = {
|
|
682
|
+
listing: {
|
|
683
|
+
label: "Listing page",
|
|
684
|
+
slots: LISTING_SLOTS,
|
|
685
|
+
settings: [
|
|
686
|
+
sel("columns", "Columns on desktop", opt(["3", "3"], ["4", "4"]), "3"),
|
|
687
|
+
sel("columns_mobile", "Columns on phones", opt(["1", "1"], ["2", "2"]), "2"),
|
|
688
|
+
sel("page_size", "Products per page", opt(["12", "12"], ["24", "24"], ["36", "36"]), "12"),
|
|
689
|
+
sel("default_sort", "Default sort", SORTS, "relevance"),
|
|
690
|
+
sel("filters", "Filters show as", opt(["sidebar", "Sidebar"], ["topbar", "Bar above the results"], ["drawer", "Button that opens a panel"]), "sidebar"),
|
|
691
|
+
bool("active_chips", "Show the filters in use as chips", true),
|
|
692
|
+
sel("paging", "Paging", opt(["pages", "Page numbers"], ["load_more", "Load more button"]), "pages")
|
|
693
|
+
],
|
|
694
|
+
templates: {
|
|
695
|
+
classic: { label: "Classic", description: "Filters in a sidebar beside a product grid.", settings: {}, blocks: ["collection_header", "toolbar", "filters", "results"] },
|
|
696
|
+
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"] },
|
|
697
|
+
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"] }
|
|
698
|
+
},
|
|
699
|
+
default: "classic"
|
|
700
|
+
},
|
|
701
|
+
product: {
|
|
702
|
+
label: "Product page",
|
|
703
|
+
slots: PRODUCT_SLOTS,
|
|
704
|
+
settings: [
|
|
705
|
+
sel("gallery", "Gallery", opt(["thumbs_side", "Thumbnails beside the photo"], ["thumbs_below", "Thumbnails below the photo"], ["mosaic", "Full-width mosaic"]), "thumbs_side"),
|
|
706
|
+
bool("sticky_buy_bar", "Sticky buy bar on phones", true),
|
|
707
|
+
sel("spec_tiles", "Key spec tiles", opt(["0", "None"], ["4", "4"], ["6", "6"]), "4"),
|
|
708
|
+
bool("breadcrumbs", "Show breadcrumbs", true)
|
|
709
|
+
],
|
|
710
|
+
templates: {
|
|
711
|
+
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"] },
|
|
712
|
+
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"] },
|
|
713
|
+
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"] }
|
|
714
|
+
},
|
|
715
|
+
default: "classic"
|
|
716
|
+
}
|
|
717
|
+
};
|
|
718
|
+
var STORE_PAGE_KEYS = Object.keys(STORE_PAGES);
|
|
719
|
+
var link = [
|
|
720
|
+
{ key: "label", kind: "text", label: "Label", max: 30, required: true, default: "" },
|
|
721
|
+
{ key: "href", kind: "url", label: "Link", required: true, default: "" }
|
|
722
|
+
];
|
|
723
|
+
var CHROME_FIELDS = [
|
|
724
|
+
{
|
|
725
|
+
key: "announcement",
|
|
726
|
+
kind: "group",
|
|
727
|
+
label: "Announcement bar",
|
|
728
|
+
fields: [
|
|
729
|
+
bool("enabled", "Show the announcement bar", false),
|
|
730
|
+
{ key: "text", kind: "text", label: "Message", max: 120, default: "" },
|
|
731
|
+
{ key: "href", kind: "url", label: "Link", default: "" },
|
|
732
|
+
sel("tone", "Colour", opt(["brand", "Brand"], ["accent", "Accent"], ["dark", "Dark"]), "brand")
|
|
733
|
+
]
|
|
734
|
+
},
|
|
735
|
+
{
|
|
736
|
+
key: "header",
|
|
737
|
+
kind: "group",
|
|
738
|
+
label: "Header",
|
|
739
|
+
fields: [
|
|
740
|
+
{ key: "utility_text", kind: "text", label: "Strip above the header", max: 80, default: "" },
|
|
741
|
+
bool("show_search", "Search box", true),
|
|
742
|
+
bool("category_nav", "Menu of the categories you sell", true),
|
|
743
|
+
{ key: "nav_links", kind: "list", label: "Extra menu links", max: 8, itemLabel: "label", of: link, default: [] },
|
|
744
|
+
bool("sticky", "Keep the header on screen while scrolling", true)
|
|
745
|
+
]
|
|
746
|
+
},
|
|
747
|
+
{
|
|
748
|
+
key: "footer",
|
|
749
|
+
kind: "group",
|
|
750
|
+
label: "Footer",
|
|
751
|
+
fields: [
|
|
752
|
+
{ key: "about", kind: "textarea", label: "About the store", max: 240, default: "" },
|
|
753
|
+
{
|
|
754
|
+
key: "columns",
|
|
755
|
+
kind: "list",
|
|
756
|
+
label: "Link columns",
|
|
757
|
+
max: 4,
|
|
758
|
+
itemLabel: "title",
|
|
759
|
+
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: [] }],
|
|
760
|
+
default: []
|
|
761
|
+
},
|
|
762
|
+
{
|
|
763
|
+
key: "socials",
|
|
764
|
+
kind: "list",
|
|
765
|
+
label: "Social profiles",
|
|
766
|
+
max: 6,
|
|
767
|
+
itemLabel: "network",
|
|
768
|
+
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: "" }],
|
|
769
|
+
default: []
|
|
770
|
+
},
|
|
771
|
+
{ key: "note", kind: "text", label: "Small print (GSTIN, address)", max: 160, default: "" }
|
|
772
|
+
]
|
|
773
|
+
}
|
|
774
|
+
];
|
|
775
|
+
var CARD_FIELDS = [
|
|
776
|
+
sel("image_ratio", "Photo shape", opt(["4:3", "Landscape 4:3"], ["1:1", "Square"], ["3:4", "Portrait 3:4"]), "4:3"),
|
|
777
|
+
bool("badges", "Condition and discount badges", true),
|
|
778
|
+
bool("spec_line", "Key specs line", true),
|
|
779
|
+
bool("compare", "Compare checkbox", true),
|
|
780
|
+
bool("quick_add", "Add to cart button", true),
|
|
781
|
+
bool("mrp", "Show the MRP struck through", true),
|
|
782
|
+
sel("hover", "On hover", opt(["none", "Nothing"], ["zoom", "Zoom the photo"], ["second_image", "Show the second photo"]), "zoom")
|
|
783
|
+
];
|
|
784
|
+
function validateGroups(defs, obj, path, errors) {
|
|
785
|
+
const src = obj && typeof obj === "object" && !Array.isArray(obj) ? obj : {};
|
|
786
|
+
if (obj != null && src !== obj) errors.push({ path, message: "must be an object" });
|
|
787
|
+
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]]));
|
|
788
|
+
}
|
|
789
|
+
var fill = (defs) => Object.fromEntries(defs.map((f) => [f.key, f.kind === "group" ? fill(f.fields) : f.kind === "list" ? [...f.default || []] : f.default]));
|
|
790
|
+
var defaultChrome = () => fill(CHROME_FIELDS);
|
|
791
|
+
var defaultCard = () => fill(CARD_FIELDS);
|
|
792
|
+
function blockDef(pageKey, type) {
|
|
793
|
+
const page = own(STORE_PAGES, pageKey) ? STORE_PAGES[pageKey] : null;
|
|
794
|
+
if (!page) return null;
|
|
795
|
+
if (own(page.slots, type)) return { ...page.slots[type], slot: true };
|
|
796
|
+
if (CONTENT_BLOCKS.includes(type) && own(SECTION_TYPES, type)) return { ...SECTION_TYPES[type], slot: false };
|
|
797
|
+
return null;
|
|
798
|
+
}
|
|
799
|
+
var own = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key);
|
|
800
|
+
var blockId = (type) => type.replace(/_/g, "-");
|
|
801
|
+
var blockDefaults = (pageKey, type) => {
|
|
802
|
+
const def = blockDef(pageKey, type);
|
|
803
|
+
return def.slot ? fill(def.fields) : sectionDefaults(type);
|
|
804
|
+
};
|
|
805
|
+
function newBlock(pageKey, type) {
|
|
806
|
+
const def = blockDef(pageKey, type);
|
|
807
|
+
if (!def) throw new Error(`Unknown block "${type}" on the ${pageKey} page`);
|
|
808
|
+
return { id: def.slot ? blockId(type) : newSectionId(type), type, enabled: true, props: blockDefaults(pageKey, type) };
|
|
809
|
+
}
|
|
810
|
+
function templatePage(pageKey, template) {
|
|
811
|
+
const page = STORE_PAGES[pageKey];
|
|
812
|
+
const key = own(page.templates, template) ? template : page.default;
|
|
813
|
+
const tpl = page.templates[key];
|
|
814
|
+
const settings = { ...fill(page.settings), ...tpl.settings };
|
|
815
|
+
const blocks = tpl.blocks.map((type) => ({ id: blockId(type), type, enabled: true, props: blockDefaults(pageKey, type) }));
|
|
816
|
+
return { template: key, settings, blocks };
|
|
817
|
+
}
|
|
818
|
+
function validateTemplatePage(pageKey, input, path, errors) {
|
|
819
|
+
var _a, _b;
|
|
820
|
+
const page = STORE_PAGES[pageKey];
|
|
821
|
+
const src = input && typeof input === "object" && !Array.isArray(input) ? input : {};
|
|
822
|
+
let template = (_a = src.template) != null ? _a : page.default;
|
|
823
|
+
if (!own(page.templates, template)) {
|
|
824
|
+
errors.push({ path: `${path}.template`, message: `must be one of ${Object.keys(page.templates).join(", ")}` });
|
|
825
|
+
template = page.default;
|
|
826
|
+
}
|
|
827
|
+
const tpl = page.templates[template];
|
|
828
|
+
const settingDefs = page.settings.map((f) => tpl.settings[f.key] === void 0 ? f : { ...f, default: tpl.settings[f.key] });
|
|
829
|
+
const settings = validateFields(settingDefs, (_b = src.settings) != null ? _b : {}, `${path}.settings`, errors);
|
|
830
|
+
const list = Array.isArray(src.blocks) ? src.blocks : null;
|
|
831
|
+
if (src.blocks != null && !list) errors.push({ path: `${path}.blocks`, message: "must be a list" });
|
|
832
|
+
const raw = list != null ? list : templatePage(pageKey, template).blocks;
|
|
833
|
+
if (raw.length > MAX_BLOCKS) errors.push({ path: `${path}.blocks`, message: `can have at most ${MAX_BLOCKS}` });
|
|
834
|
+
const ids = /* @__PURE__ */ new Set();
|
|
835
|
+
const slotsSeen = /* @__PURE__ */ new Set();
|
|
836
|
+
const blocks = [];
|
|
837
|
+
raw.slice(0, MAX_BLOCKS).forEach((b, i) => {
|
|
838
|
+
var _a2, _b2;
|
|
839
|
+
const at = `${path}.blocks[${i}]`;
|
|
840
|
+
if (!b || typeof b !== "object") {
|
|
841
|
+
errors.push({ path: at, message: "must be an object" });
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
const def = blockDef(pageKey, b.type);
|
|
845
|
+
if (!def) {
|
|
846
|
+
errors.push({ path: `${at}.type`, message: `unknown block "${b.type}" on the ${page.label.toLowerCase()}` });
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
const id = String((_a2 = b.id) != null ? _a2 : "");
|
|
850
|
+
if (!ID_RE2.test(id)) errors.push({ path: `${at}.id`, message: "must be 1\u201340 lowercase letters, digits or dashes" });
|
|
851
|
+
else if (ids.has(id)) errors.push({ path: `${at}.id`, message: `duplicate id "${id}"` });
|
|
852
|
+
ids.add(id);
|
|
853
|
+
if (def.slot) {
|
|
854
|
+
if (slotsSeen.has(b.type)) {
|
|
855
|
+
errors.push({ path: `${at}.type`, message: `"${def.label}" can appear only once` });
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
858
|
+
slotsSeen.add(b.type);
|
|
859
|
+
}
|
|
860
|
+
let enabled = b.enabled === void 0 ? true : b.enabled;
|
|
861
|
+
if (typeof enabled !== "boolean") {
|
|
862
|
+
errors.push({ path: `${at}.enabled`, message: "must be true or false" });
|
|
863
|
+
enabled = true;
|
|
864
|
+
}
|
|
865
|
+
if (def.required && !enabled) {
|
|
866
|
+
errors.push({ path: `${at}.enabled`, message: `"${def.label}" is required and cannot be hidden` });
|
|
867
|
+
enabled = true;
|
|
868
|
+
}
|
|
869
|
+
blocks.push({ id, type: b.type, enabled, props: validateFields(def.fields, (_b2 = b.props) != null ? _b2 : {}, `${at}.props`, errors) });
|
|
870
|
+
});
|
|
871
|
+
for (const [type, def] of Object.entries(page.slots)) {
|
|
872
|
+
if (def.required && !slotsSeen.has(type)) {
|
|
873
|
+
errors.push({ path: `${path}.blocks`, message: `"${def.label}" is required` });
|
|
874
|
+
blocks.push({ id: ids.has(blockId(type)) ? `${blockId(type)}-1` : blockId(type), type, enabled: true, props: blockDefaults(pageKey, type) });
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
return { template, settings, blocks };
|
|
878
|
+
}
|
|
879
|
+
function migrateStore(input) {
|
|
880
|
+
const home = input && typeof input === "object" && Array.isArray(input.sections) ? input : null;
|
|
881
|
+
return {
|
|
882
|
+
version: STORE_VERSION,
|
|
883
|
+
pages: { home: home ? { version: 1, sections: home.sections } : defaultPage(), listing: templatePage("listing"), product: templatePage("product") },
|
|
884
|
+
chrome: defaultChrome(),
|
|
885
|
+
card: defaultCard()
|
|
886
|
+
};
|
|
887
|
+
}
|
|
888
|
+
var isLegacyPage = (doc) => !!doc && typeof doc === "object" && doc.version !== STORE_VERSION && Array.isArray(doc.sections);
|
|
889
|
+
function validateStore(input) {
|
|
890
|
+
var _a, _b, _c;
|
|
891
|
+
const errors = [];
|
|
892
|
+
const src0 = isLegacyPage(input) ? migrateStore(input) : input;
|
|
893
|
+
const src = src0 && typeof src0 === "object" && !Array.isArray(src0) ? src0 : null;
|
|
894
|
+
if (!src) {
|
|
895
|
+
const doc2 = migrateStore(null);
|
|
896
|
+
return { ok: false, errors: [{ path: "doc", message: "must be an object" }], doc: doc2 };
|
|
897
|
+
}
|
|
898
|
+
if (src.version != null && src.version !== STORE_VERSION) errors.push({ path: "version", message: `must be ${STORE_VERSION}` });
|
|
899
|
+
const pages = src.pages && typeof src.pages === "object" ? src.pages : {};
|
|
900
|
+
const home = validatePage((_a = pages.home) != null ? _a : defaultPage());
|
|
901
|
+
home.errors.forEach((e) => errors.push({ path: `pages.home.${e.path}`, message: e.message }));
|
|
902
|
+
const doc = {
|
|
903
|
+
version: STORE_VERSION,
|
|
904
|
+
pages: {
|
|
905
|
+
home: home.page,
|
|
906
|
+
...Object.fromEntries(STORE_PAGE_KEYS.map((k) => [k, validateTemplatePage(k, pages[k], `pages.${k}`, errors)]))
|
|
907
|
+
},
|
|
908
|
+
chrome: validateGroups(CHROME_FIELDS, (_b = src.chrome) != null ? _b : {}, "chrome", errors),
|
|
909
|
+
card: validateFields(CARD_FIELDS, (_c = src.card) != null ? _c : {}, "card", errors)
|
|
910
|
+
};
|
|
911
|
+
if (doc.chrome.announcement.enabled && !doc.chrome.announcement.text) errors.push({ path: "chrome.announcement.text", message: "is required when the bar is shown" });
|
|
912
|
+
return { ok: errors.length === 0, errors, doc };
|
|
913
|
+
}
|
|
914
|
+
function defaultStore({ name, tagline } = {}) {
|
|
915
|
+
return { ...migrateStore(defaultPage({ name, tagline })) };
|
|
916
|
+
}
|
|
917
|
+
function storeMediaRefs(doc) {
|
|
918
|
+
const out = [];
|
|
919
|
+
const walk = (v) => {
|
|
920
|
+
if (Array.isArray(v)) v.forEach(walk);
|
|
921
|
+
else if (v && typeof v === "object") {
|
|
922
|
+
if (v.media_id != null && Object.keys(v).every((k) => k === "media_id" || k === "alt")) out.push(v);
|
|
923
|
+
else Object.values(v).forEach(walk);
|
|
924
|
+
}
|
|
925
|
+
};
|
|
926
|
+
walk(doc);
|
|
927
|
+
return out;
|
|
928
|
+
}
|
|
648
929
|
export {
|
|
930
|
+
CARD_FIELDS,
|
|
931
|
+
CHROME_FIELDS,
|
|
932
|
+
CONTENT_BLOCKS,
|
|
649
933
|
DEFAULT_THEME,
|
|
650
934
|
FONT_PAIRS,
|
|
651
935
|
ICON_NAMES,
|
|
936
|
+
LISTING_SLOTS,
|
|
937
|
+
MAX_BLOCKS,
|
|
652
938
|
MAX_SECTIONS,
|
|
653
939
|
MODES,
|
|
654
940
|
PAGE_VERSION,
|
|
655
941
|
PALETTE_STEPS,
|
|
942
|
+
PRODUCT_SLOTS,
|
|
656
943
|
RADII,
|
|
657
944
|
SECTION_TYPES,
|
|
658
945
|
SECTION_TYPE_KEYS,
|
|
946
|
+
STORE_PAGES,
|
|
947
|
+
STORE_PAGE_KEYS,
|
|
948
|
+
STORE_VERSION,
|
|
659
949
|
THEME_PRESETS,
|
|
950
|
+
blockDef,
|
|
660
951
|
brandPalette,
|
|
661
952
|
contrastRatio,
|
|
953
|
+
defaultCard,
|
|
954
|
+
defaultChrome,
|
|
662
955
|
defaultPage,
|
|
956
|
+
defaultStore,
|
|
663
957
|
hexToOklch,
|
|
664
958
|
isHex,
|
|
959
|
+
isLegacyPage,
|
|
665
960
|
luminance,
|
|
961
|
+
migrateStore,
|
|
962
|
+
newBlock,
|
|
666
963
|
newSection,
|
|
667
964
|
newSectionId,
|
|
668
965
|
oklchToHex,
|
|
@@ -670,7 +967,11 @@ export {
|
|
|
670
967
|
safeHref,
|
|
671
968
|
sanitizeRichText,
|
|
672
969
|
sectionDefaults,
|
|
970
|
+
storeMediaRefs,
|
|
971
|
+
templatePage,
|
|
673
972
|
themeVars,
|
|
973
|
+
validateFields,
|
|
674
974
|
validatePage,
|
|
975
|
+
validateStore,
|
|
675
976
|
validateTheme
|
|
676
977
|
};
|
package/index.d.ts
CHANGED
|
@@ -299,7 +299,7 @@ export declare function createDemoIsland(props: Omit<DemoDialogProps, 'onClose'>
|
|
|
299
299
|
// ---------------------------------------------------------------------------
|
|
300
300
|
// Tenant pages: a store's own landing page as data. Validate and build pages
|
|
301
301
|
// with `@dreamtree-org/landing-kit/schema`; render them with TenantPage.
|
|
302
|
-
import type { PageDoc, Theme, MediaRef, SectionType, ValidationError, Palette, FontPair } from './schema';
|
|
302
|
+
import type { PageDoc, Theme, MediaRef, SectionType, ValidationError, Palette, FontPair, StoreDoc, TemplateBlock } from './schema';
|
|
303
303
|
|
|
304
304
|
export interface TenantPageProps {
|
|
305
305
|
/** A page document (validated again here; invalid sections are dropped). */
|
|
@@ -321,6 +321,20 @@ export function TenantPage(props: TenantPageProps): JSX.Element;
|
|
|
321
321
|
export const TENANT_BLOCKS: Record<SectionType, (props: any) => JSX.Element | null>;
|
|
322
322
|
export function HeroSlider(props: Record<string, unknown>): JSX.Element | null;
|
|
323
323
|
export function NamedIcon(props: { name: string; size?: number; className?: string }): JSX.Element | null;
|
|
324
|
+
export interface TemplateRegionProps {
|
|
325
|
+
/** The blocks of a template page (StoreDoc.pages.listing/product.blocks), in order; hidden ones are skipped. */
|
|
326
|
+
blocks: TemplateBlock[];
|
|
327
|
+
/** App components for commerce slots, by block type; return null to place a slot yourself. */
|
|
328
|
+
slots?: Record<string, ComponentType<{ block: TemplateBlock; props: Record<string, unknown> }>>;
|
|
329
|
+
theme?: Partial<Theme> | null;
|
|
330
|
+
resolveMedia?: (ref: MediaRef, variant: string) => string | null;
|
|
331
|
+
linkComponent?: ComponentType<any> | null;
|
|
332
|
+
storeName?: string;
|
|
333
|
+
className?: string;
|
|
334
|
+
}
|
|
335
|
+
/** A listing or product page's blocks: app slots plus content sections rendered like TenantPage. */
|
|
336
|
+
export function TemplateRegion(props: TemplateRegionProps): JSX.Element;
|
|
337
|
+
export function validateStore(doc: unknown): { ok: boolean; errors: ValidationError[]; doc: StoreDoc };
|
|
324
338
|
export function validatePage(doc: unknown): { ok: boolean; errors: ValidationError[]; page: PageDoc };
|
|
325
339
|
export function validateTheme(theme: unknown): { ok: boolean; errors: ValidationError[]; warnings: ValidationError[]; theme: Theme };
|
|
326
340
|
export function themeVars(theme: unknown): { theme: Theme; font: FontPair; palette: { primary: Palette; accent: Palette }; style: Record<string, string> };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dreamtree-org/landing-kit",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "The shared marketing site
|
|
3
|
+
"version": "0.9.0",
|
|
4
|
+
"description": "The shared marketing site, auth frame (AuthShell + brands) and Platform admin console for every Dreamtree product: content-driven landing pages, login/signup shell, platform console client, Express routes, prerender and SEO files.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Partha Preetham Krishna",
|
|
7
7
|
"email": "preetham.krishna.dev@gmail.com",
|
|
@@ -56,6 +56,11 @@
|
|
|
56
56
|
"import": "./dist/enhance.js",
|
|
57
57
|
"require": "./dist/enhance.cjs"
|
|
58
58
|
},
|
|
59
|
+
"./platform": {
|
|
60
|
+
"types": "./platform.d.ts",
|
|
61
|
+
"import": "./dist/platform.js",
|
|
62
|
+
"require": "./dist/platform.cjs"
|
|
63
|
+
},
|
|
59
64
|
"./styles.css": "./styles.css",
|
|
60
65
|
"./auth.css": "./auth.css"
|
|
61
66
|
},
|
|
@@ -73,6 +78,9 @@
|
|
|
73
78
|
"schema.d.ts",
|
|
74
79
|
"demo-server.d.ts",
|
|
75
80
|
"enhance.d.ts",
|
|
81
|
+
"platform.d.ts",
|
|
82
|
+
"PLATFORM-API.md",
|
|
83
|
+
"ai-skills",
|
|
76
84
|
"README.md",
|
|
77
85
|
"MIGRATION.md",
|
|
78
86
|
"LICENSE"
|
|
@@ -85,9 +93,16 @@
|
|
|
85
93
|
},
|
|
86
94
|
"peerDependencies": {
|
|
87
95
|
"react": ">=18",
|
|
88
|
-
"react-dom": ">=18"
|
|
96
|
+
"react-dom": ">=18",
|
|
97
|
+
"@dreamtree-org/twreact-ui": "^2.0.2"
|
|
98
|
+
},
|
|
99
|
+
"peerDependenciesMeta": {
|
|
100
|
+
"@dreamtree-org/twreact-ui": {
|
|
101
|
+
"optional": true
|
|
102
|
+
}
|
|
89
103
|
},
|
|
90
104
|
"devDependencies": {
|
|
105
|
+
"@dreamtree-org/twreact-ui": "^2.0.2",
|
|
91
106
|
"esbuild": "^0.25.0",
|
|
92
107
|
"express": "^4.22.3",
|
|
93
108
|
"react": "^18.3.1",
|
|
@@ -96,6 +111,12 @@
|
|
|
96
111
|
"engines": {
|
|
97
112
|
"node": ">=18"
|
|
98
113
|
},
|
|
114
|
+
"dreamtree": {
|
|
115
|
+
"ai": {
|
|
116
|
+
"skill": "ai-skills/landing-kit.md",
|
|
117
|
+
"name": "landing-kit"
|
|
118
|
+
}
|
|
119
|
+
},
|
|
99
120
|
"keywords": [
|
|
100
121
|
"react",
|
|
101
122
|
"landing-page",
|
|
@@ -106,7 +127,8 @@
|
|
|
106
127
|
"express",
|
|
107
128
|
"prerender",
|
|
108
129
|
"dreamtree",
|
|
109
|
-
"multi-tenant"
|
|
130
|
+
"multi-tenant",
|
|
131
|
+
"admin-console"
|
|
110
132
|
],
|
|
111
133
|
"publishConfig": {
|
|
112
134
|
"access": "public"
|