@xiwebdesign/xicms-core 5.3.0 → 5.5.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.
Files changed (135) hide show
  1. package/dist/XiCMSProvider.d.ts +11 -2
  2. package/dist/XiCMSProvider.js +10 -5
  3. package/dist/XiCMSProvider.js.map +1 -1
  4. package/dist/admin/blockEditors/GenericEditor.js +18 -8
  5. package/dist/admin/blockEditors/GenericEditor.js.map +1 -1
  6. package/dist/admin/customBlocks/CustomBlocksPanel.js +12 -2
  7. package/dist/admin/customBlocks/CustomBlocksPanel.js.map +1 -1
  8. package/dist/admin/lib/pagesClient.js +4 -1
  9. package/dist/admin/lib/pagesClient.js.map +1 -1
  10. package/dist/admin/lib/upload.js +14 -7
  11. package/dist/admin/lib/upload.js.map +1 -1
  12. package/dist/admin/media/MediaManager.js +60 -28
  13. package/dist/admin/media/MediaManager.js.map +1 -1
  14. package/dist/admin/media/MediaPicker.js +25 -15
  15. package/dist/admin/media/MediaPicker.js.map +1 -1
  16. package/dist/admin/pages/PageEditor.d.ts +2 -1
  17. package/dist/admin/pages/PageEditor.js +16 -2
  18. package/dist/admin/pages/PageEditor.js.map +1 -1
  19. package/dist/admin/pages/PagesPanel.js +9 -5
  20. package/dist/admin/pages/PagesPanel.js.map +1 -1
  21. package/dist/admin/panelRegistry.js +3 -0
  22. package/dist/admin/panelRegistry.js.map +1 -1
  23. package/dist/admin/shop/CategoriesTab.d.ts +3 -0
  24. package/dist/admin/shop/CategoriesTab.js +140 -0
  25. package/dist/admin/shop/CategoriesTab.js.map +1 -0
  26. package/dist/admin/shop/ConnectionsTab.d.ts +9 -0
  27. package/dist/admin/shop/ConnectionsTab.js +158 -0
  28. package/dist/admin/shop/ConnectionsTab.js.map +1 -0
  29. package/dist/admin/shop/OrdersTab.d.ts +3 -0
  30. package/dist/admin/shop/OrdersTab.js +236 -0
  31. package/dist/admin/shop/OrdersTab.js.map +1 -0
  32. package/dist/admin/shop/ProductsTab.d.ts +3 -0
  33. package/dist/admin/shop/ProductsTab.js +397 -0
  34. package/dist/admin/shop/ProductsTab.js.map +1 -0
  35. package/dist/admin/shop/ShopPanel.d.ts +4 -0
  36. package/dist/admin/shop/ShopPanel.js +25 -0
  37. package/dist/admin/shop/ShopPanel.js.map +1 -0
  38. package/dist/admin/shop/lib.d.ts +16 -0
  39. package/dist/admin/shop/lib.js +46 -0
  40. package/dist/admin/shop/lib.js.map +1 -0
  41. package/dist/admin/visual/VisualEditor.js +11 -2
  42. package/dist/admin/visual/VisualEditor.js.map +1 -1
  43. package/dist/admin.css +1 -1
  44. package/dist/agentFiles/files.d.ts +15 -3
  45. package/dist/agentFiles/files.js +57 -0
  46. package/dist/agentFiles/files.js.map +1 -1
  47. package/dist/auth/AuthProvider.js +3 -3
  48. package/dist/auth/AuthProvider.js.map +1 -1
  49. package/dist/blocks/builtins/Columns.js +1 -1
  50. package/dist/blocks/builtins/Columns.js.map +1 -1
  51. package/dist/blocks/builtins/Gallery.js +2 -2
  52. package/dist/blocks/builtins/Gallery.js.map +1 -1
  53. package/dist/blocks/builtins/Hero.js +1 -1
  54. package/dist/blocks/builtins/Hero.js.map +1 -1
  55. package/dist/blocks/builtins/SymbolRef.js.map +1 -1
  56. package/dist/components/PageView.js +4 -3
  57. package/dist/components/PageView.js.map +1 -1
  58. package/dist/demo/index.d.ts +7 -0
  59. package/dist/demo/index.js +23 -0
  60. package/dist/demo/index.js.map +1 -0
  61. package/dist/index.d.ts +2 -4
  62. package/dist/index.js +0 -20
  63. package/dist/index.js.map +1 -1
  64. package/dist/modules/builtins.d.ts +1 -0
  65. package/dist/modules/builtins.js +4 -0
  66. package/dist/modules/builtins.js.map +1 -1
  67. package/dist/modules/index.d.ts +4 -0
  68. package/dist/modules/index.js +12 -0
  69. package/dist/modules/index.js.map +1 -1
  70. package/dist/modules/registry.d.ts +3 -4
  71. package/dist/modules/registry.js +2 -3
  72. package/dist/modules/registry.js.map +1 -1
  73. package/dist/modules/shop/blocks/Cart.d.ts +2 -0
  74. package/dist/modules/shop/blocks/Cart.js +91 -0
  75. package/dist/modules/shop/blocks/Cart.js.map +1 -0
  76. package/dist/modules/shop/blocks/Checkout.d.ts +2 -0
  77. package/dist/modules/shop/blocks/Checkout.js +126 -0
  78. package/dist/modules/shop/blocks/Checkout.js.map +1 -0
  79. package/dist/modules/shop/blocks/Donation.d.ts +2 -0
  80. package/dist/modules/shop/blocks/Donation.js +145 -0
  81. package/dist/modules/shop/blocks/Donation.js.map +1 -0
  82. package/dist/modules/shop/blocks/OrderStatus.d.ts +2 -0
  83. package/dist/modules/shop/blocks/OrderStatus.js +40 -0
  84. package/dist/modules/shop/blocks/OrderStatus.js.map +1 -0
  85. package/dist/modules/shop/blocks/ProductDetail.d.ts +2 -0
  86. package/dist/modules/shop/blocks/ProductDetail.js +168 -0
  87. package/dist/modules/shop/blocks/ProductDetail.js.map +1 -0
  88. package/dist/modules/shop/blocks/ProductGrid.d.ts +8 -0
  89. package/dist/modules/shop/blocks/ProductGrid.js +136 -0
  90. package/dist/modules/shop/blocks/ProductGrid.js.map +1 -0
  91. package/dist/modules/shop/blocks/ShopCategories.d.ts +2 -0
  92. package/dist/modules/shop/blocks/ShopCategories.js +69 -0
  93. package/dist/modules/shop/blocks/ShopCategories.js.map +1 -0
  94. package/dist/modules/shop/cartStore.d.ts +22 -0
  95. package/dist/modules/shop/cartStore.js +85 -0
  96. package/dist/modules/shop/cartStore.js.map +1 -0
  97. package/dist/modules/shop/lib.d.ts +25 -0
  98. package/dist/modules/shop/lib.js +61 -0
  99. package/dist/modules/shop/lib.js.map +1 -0
  100. package/dist/modules/shop/module.d.ts +1 -0
  101. package/dist/modules/shop/module.js +39 -0
  102. package/dist/modules/shop/module.js.map +1 -0
  103. package/dist/modules/shop/settings.d.ts +41 -0
  104. package/dist/modules/shop/settings.js +18 -0
  105. package/dist/modules/shop/settings.js.map +1 -0
  106. package/dist/schema/blocks.d.ts +27 -12
  107. package/dist/schema/blocks.js +3 -0
  108. package/dist/schema/blocks.js.map +1 -1
  109. package/dist/schema/index.d.ts +1 -0
  110. package/dist/schema/index.js +1 -0
  111. package/dist/schema/index.js.map +1 -1
  112. package/dist/schema/settings.d.ts +1 -34
  113. package/dist/schema/settings.js +2 -8
  114. package/dist/schema/settings.js.map +1 -1
  115. package/dist/schema/shop.d.ts +939 -0
  116. package/dist/schema/shop.js +209 -0
  117. package/dist/schema/shop.js.map +1 -0
  118. package/dist/schema/siteConfig.d.ts +3 -71
  119. package/dist/schema/siteConfig.js +3 -8
  120. package/dist/schema/siteConfig.js.map +1 -1
  121. package/dist/schema/symbols.d.ts +2 -2
  122. package/dist/schema/symbols.js.map +1 -1
  123. package/dist/server/index.d.ts +1 -1
  124. package/dist/server/index.js +2 -0
  125. package/dist/server/index.js.map +1 -1
  126. package/dist/server/pages.d.ts +23 -3
  127. package/dist/server/pages.js +37 -3
  128. package/dist/server/pages.js.map +1 -1
  129. package/dist/server/seo.js +5 -3
  130. package/dist/server/seo.js.map +1 -1
  131. package/dist/settings/useSiteSettings.d.ts +15 -3
  132. package/dist/settings/useSiteSettings.js +26 -5
  133. package/dist/settings/useSiteSettings.js.map +1 -1
  134. package/dist/styles.css +14 -0
  135. package/package.json +6 -2
@@ -0,0 +1,69 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useState } from "react";
4
+ import { ShopCategoriesBlockSchema } from "../../../schema/shop";
5
+ import { defineBlock } from "../../../blocks/registry";
6
+ import { Card, Heading, Text } from "../../../blocks/ui";
7
+ import { fetchShopCategories, useShopSettings } from "../lib";
8
+ function ShopCategoriesRender({ block, siteId }) {
9
+ const settings = useShopSettings();
10
+ const [cats, setCats] = useState(null);
11
+ useEffect(() => {
12
+ void fetchShopCategories(siteId).then(setCats).catch(() => setCats([]));
13
+ }, [siteId]);
14
+ if (cats === null) return null;
15
+ if (cats.length === 0) {
16
+ return /* @__PURE__ */ jsx(Card, { padding: 4, variant: "muted", children: /* @__PURE__ */ jsx(Text, { children: "Nog geen categorie\xEBn." }) });
17
+ }
18
+ return /* @__PURE__ */ jsxs("div", { className: "xicms-shop-categories", children: [
19
+ block.heading && /* @__PURE__ */ jsx(Heading, { level: 2, "data-xicms-edit": "heading", children: block.heading }),
20
+ /* @__PURE__ */ jsx(
21
+ "div",
22
+ {
23
+ className: "xicms-shop-categories__grid",
24
+ style: {
25
+ display: "grid",
26
+ gridTemplateColumns: `repeat(auto-fill, minmax(min(260px, calc(100%/${block.columns} - 1rem)), 1fr))`,
27
+ gap: "1rem",
28
+ marginTop: block.heading ? "1rem" : 0
29
+ },
30
+ children: cats.map((c) => /* @__PURE__ */ jsx(
31
+ "a",
32
+ {
33
+ className: "xicms-shop-categories__item",
34
+ href: `${settings.shopPath}?cat=${encodeURIComponent(c.slug)}`,
35
+ style: { textDecoration: "none", color: "inherit" },
36
+ children: /* @__PURE__ */ jsxs(Card, { padding: 0, children: [
37
+ c.imageUrl && /* @__PURE__ */ jsx(
38
+ "img",
39
+ {
40
+ src: c.imageUrl,
41
+ alt: c.name,
42
+ loading: "lazy",
43
+ style: { width: "100%", aspectRatio: "4/3", objectFit: "cover", display: "block", borderRadius: "inherit" }
44
+ }
45
+ ),
46
+ /* @__PURE__ */ jsxs("div", { style: { padding: "0.9rem 1rem" }, children: [
47
+ /* @__PURE__ */ jsx(Text, { weight: "bold", children: c.name }),
48
+ c.description && /* @__PURE__ */ jsx(Text, { size: "sm", style: { opacity: 0.75, marginTop: 4 }, children: c.description })
49
+ ] })
50
+ ] })
51
+ },
52
+ c.id
53
+ ))
54
+ }
55
+ )
56
+ ] });
57
+ }
58
+ const shopCategoriesBlock = defineBlock({
59
+ type: "shopCategories",
60
+ schema: ShopCategoriesBlockSchema,
61
+ Render: ShopCategoriesRender,
62
+ label: "Winkel-categorie\xEBn",
63
+ icon: "\u{1F5C2}\uFE0F",
64
+ defaults: () => ({ columns: 3 })
65
+ });
66
+ export {
67
+ shopCategoriesBlock
68
+ };
69
+ //# sourceMappingURL=ShopCategories.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/modules/shop/blocks/ShopCategories.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport type { z } from \"zod\";\nimport { ShopCategoriesBlockSchema } from \"../../../schema/shop\";\nimport { defineBlock, type BlockRenderProps } from \"../../../blocks/registry\";\nimport { Card, Heading, Text } from \"../../../blocks/ui\";\nimport { fetchShopCategories, useShopSettings, type StoredShopCategory } from \"../lib\";\n\ntype Blk = z.infer<typeof ShopCategoriesBlockSchema>;\n\n/** Categorie-tegels — linkt naar de winkelpagina met `?cat=<slug>` (productGrid filtert daarop). */\nfunction ShopCategoriesRender({ block, siteId }: BlockRenderProps<Blk>) {\n const settings = useShopSettings();\n const [cats, setCats] = useState<StoredShopCategory[] | null>(null);\n\n useEffect(() => {\n void fetchShopCategories(siteId).then(setCats).catch(() => setCats([]));\n }, [siteId]);\n\n if (cats === null) return null;\n if (cats.length === 0) {\n return (\n <Card padding={4} variant=\"muted\">\n <Text>Nog geen categorieën.</Text>\n </Card>\n );\n }\n\n return (\n <div className=\"xicms-shop-categories\">\n {block.heading && (\n <Heading level={2} data-xicms-edit=\"heading\">{block.heading}</Heading>\n )}\n <div\n className=\"xicms-shop-categories__grid\"\n style={{\n display: \"grid\",\n gridTemplateColumns: `repeat(auto-fill, minmax(min(260px, calc(100%/${block.columns} - 1rem)), 1fr))`,\n gap: \"1rem\",\n marginTop: block.heading ? \"1rem\" : 0,\n }}\n >\n {cats.map((c) => (\n <a\n key={c.id}\n className=\"xicms-shop-categories__item\"\n href={`${settings.shopPath}?cat=${encodeURIComponent(c.slug)}`}\n style={{ textDecoration: \"none\", color: \"inherit\" }}\n >\n <Card padding={0}>\n {c.imageUrl && (\n <img\n src={c.imageUrl}\n alt={c.name}\n loading=\"lazy\"\n style={{ width: \"100%\", aspectRatio: \"4/3\", objectFit: \"cover\", display: \"block\", borderRadius: \"inherit\" }}\n />\n )}\n <div style={{ padding: \"0.9rem 1rem\" }}>\n <Text weight=\"bold\">{c.name}</Text>\n {c.description && (\n <Text size=\"sm\" style={{ opacity: 0.75, marginTop: 4 }}>{c.description}</Text>\n )}\n </div>\n </Card>\n </a>\n ))}\n </div>\n </div>\n );\n}\n\nexport const shopCategoriesBlock = defineBlock({\n type: \"shopCategories\",\n schema: ShopCategoriesBlockSchema,\n Render: ShopCategoriesRender,\n label: \"Winkel-categorieën\",\n icon: \"🗂️\",\n defaults: () => ({ columns: 3 }),\n});\n"],"mappings":";AAwBQ,cAmCM,YAnCN;AAtBR,SAAS,WAAW,gBAAgB;AAEpC,SAAS,iCAAiC;AAC1C,SAAS,mBAA0C;AACnD,SAAS,MAAM,SAAS,YAAY;AACpC,SAAS,qBAAqB,uBAAgD;AAK9E,SAAS,qBAAqB,EAAE,OAAO,OAAO,GAA0B;AACtE,QAAM,WAAW,gBAAgB;AACjC,QAAM,CAAC,MAAM,OAAO,IAAI,SAAsC,IAAI;AAElE,YAAU,MAAM;AACd,SAAK,oBAAoB,MAAM,EAAE,KAAK,OAAO,EAAE,MAAM,MAAM,QAAQ,CAAC,CAAC,CAAC;AAAA,EACxE,GAAG,CAAC,MAAM,CAAC;AAEX,MAAI,SAAS,KAAM,QAAO;AAC1B,MAAI,KAAK,WAAW,GAAG;AACrB,WACE,oBAAC,QAAK,SAAS,GAAG,SAAQ,SACxB,8BAAC,QAAK,sCAAqB,GAC7B;AAAA,EAEJ;AAEA,SACE,qBAAC,SAAI,WAAU,yBACZ;AAAA,UAAM,WACL,oBAAC,WAAQ,OAAO,GAAG,mBAAgB,WAAW,gBAAM,SAAQ;AAAA,IAE9D;AAAA,MAAC;AAAA;AAAA,QACC,WAAU;AAAA,QACV,OAAO;AAAA,UACL,SAAS;AAAA,UACT,qBAAqB,iDAAiD,MAAM,OAAO;AAAA,UACnF,KAAK;AAAA,UACL,WAAW,MAAM,UAAU,SAAS;AAAA,QACtC;AAAA,QAEC,eAAK,IAAI,CAAC,MACT;AAAA,UAAC;AAAA;AAAA,YAEC,WAAU;AAAA,YACV,MAAM,GAAG,SAAS,QAAQ,QAAQ,mBAAmB,EAAE,IAAI,CAAC;AAAA,YAC5D,OAAO,EAAE,gBAAgB,QAAQ,OAAO,UAAU;AAAA,YAElD,+BAAC,QAAK,SAAS,GACZ;AAAA,gBAAE,YACD;AAAA,gBAAC;AAAA;AAAA,kBACC,KAAK,EAAE;AAAA,kBACP,KAAK,EAAE;AAAA,kBACP,SAAQ;AAAA,kBACR,OAAO,EAAE,OAAO,QAAQ,aAAa,OAAO,WAAW,SAAS,SAAS,SAAS,cAAc,UAAU;AAAA;AAAA,cAC5G;AAAA,cAEF,qBAAC,SAAI,OAAO,EAAE,SAAS,cAAc,GACnC;AAAA,oCAAC,QAAK,QAAO,QAAQ,YAAE,MAAK;AAAA,gBAC3B,EAAE,eACD,oBAAC,QAAK,MAAK,MAAK,OAAO,EAAE,SAAS,MAAM,WAAW,EAAE,GAAI,YAAE,aAAY;AAAA,iBAE3E;AAAA,eACF;AAAA;AAAA,UApBK,EAAE;AAAA,QAqBT,CACD;AAAA;AAAA,IACH;AAAA,KACF;AAEJ;AAEO,MAAM,sBAAsB,YAAY;AAAA,EAC7C,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,MAAM;AAAA,EACN,UAAU,OAAO,EAAE,SAAS,EAAE;AAChC,CAAC;","names":[]}
@@ -0,0 +1,22 @@
1
+ export interface CartLine {
2
+ productId: string;
3
+ slug: string;
4
+ name: string;
5
+ /** Variant-key ("68|Roze") + leesbaar label ("Maat 68, Roze"). */
6
+ variantKey?: string;
7
+ variantLabel?: string;
8
+ unitPriceCents: number;
9
+ qty: number;
10
+ imageUrl?: string;
11
+ }
12
+ export declare function cartAdd(siteId: string, line: Omit<CartLine, "qty">, qty?: number): void;
13
+ export declare function cartSetQty(siteId: string, productId: string, variantKey: string | undefined, qty: number): void;
14
+ export declare function cartRemove(siteId: string, productId: string, variantKey: string | undefined): void;
15
+ export declare function cartClear(siteId: string): void;
16
+ export interface CartState {
17
+ lines: CartLine[];
18
+ count: number;
19
+ itemsCents: number;
20
+ }
21
+ /** Reactieve cart-state voor blocks/klantcomponenten (bv. een cart-badge in de header). */
22
+ export declare function useCart(siteId: string): CartState;
@@ -0,0 +1,85 @@
1
+ "use client";
2
+ import { useSyncExternalStore } from "react";
3
+ const listeners = /* @__PURE__ */ new Set();
4
+ const cache = /* @__PURE__ */ new Map();
5
+ function storageKey(siteId) {
6
+ return `xicms:cart:${siteId}`;
7
+ }
8
+ function readLines(siteId) {
9
+ if (typeof window === "undefined") return [];
10
+ let raw = "";
11
+ try {
12
+ raw = window.localStorage.getItem(storageKey(siteId)) ?? "[]";
13
+ } catch {
14
+ raw = "[]";
15
+ }
16
+ const cached = cache.get(siteId);
17
+ if (cached && cached.raw === raw) return cached.lines;
18
+ let lines = [];
19
+ try {
20
+ const parsed = JSON.parse(raw);
21
+ if (Array.isArray(parsed)) lines = parsed.filter((l) => l && typeof l === "object" && typeof l.productId === "string");
22
+ } catch {
23
+ lines = [];
24
+ }
25
+ cache.set(siteId, { raw, lines });
26
+ return lines;
27
+ }
28
+ function writeLines(siteId, lines) {
29
+ try {
30
+ window.localStorage.setItem(storageKey(siteId), JSON.stringify(lines));
31
+ } catch {
32
+ cache.set(siteId, { raw: `mem:${Date.now()}`, lines });
33
+ }
34
+ emit();
35
+ }
36
+ function emit() {
37
+ for (const l of listeners) l();
38
+ }
39
+ function subscribe(cb) {
40
+ listeners.add(cb);
41
+ const onStorage = (e) => {
42
+ if (e.key?.startsWith("xicms:cart:")) cb();
43
+ };
44
+ window.addEventListener("storage", onStorage);
45
+ return () => {
46
+ listeners.delete(cb);
47
+ window.removeEventListener("storage", onStorage);
48
+ };
49
+ }
50
+ function lineId(l) {
51
+ return `${l.productId}::${l.variantKey ?? ""}`;
52
+ }
53
+ function cartAdd(siteId, line, qty = 1) {
54
+ const lines = [...readLines(siteId)];
55
+ const idx = lines.findIndex((l) => lineId(l) === lineId(line));
56
+ if (idx >= 0) lines[idx] = { ...lines[idx], qty: Math.min(999, lines[idx].qty + qty) };
57
+ else lines.push({ ...line, qty: Math.min(999, Math.max(1, qty)) });
58
+ writeLines(siteId, lines);
59
+ }
60
+ function cartSetQty(siteId, productId, variantKey, qty) {
61
+ const lines = readLines(siteId).map((l) => lineId(l) === lineId({ productId, variantKey }) ? { ...l, qty: Math.min(999, qty) } : l).filter((l) => l.qty > 0);
62
+ writeLines(siteId, lines);
63
+ }
64
+ function cartRemove(siteId, productId, variantKey) {
65
+ writeLines(siteId, readLines(siteId).filter((l) => lineId(l) !== lineId({ productId, variantKey })));
66
+ }
67
+ function cartClear(siteId) {
68
+ writeLines(siteId, []);
69
+ }
70
+ function useCart(siteId) {
71
+ const lines = useSyncExternalStore(subscribe, () => readLines(siteId), () => []);
72
+ return {
73
+ lines,
74
+ count: lines.reduce((n, l) => n + l.qty, 0),
75
+ itemsCents: lines.reduce((n, l) => n + l.unitPriceCents * l.qty, 0)
76
+ };
77
+ }
78
+ export {
79
+ cartAdd,
80
+ cartClear,
81
+ cartRemove,
82
+ cartSetQty,
83
+ useCart
84
+ };
85
+ //# sourceMappingURL=cartStore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/modules/shop/cartStore.ts"],"sourcesContent":["\"use client\";\n\n/**\n * Winkelwagen-store — puur client-side (localStorage), geen Firestore-writes vóór checkout.\n * Extern store + `useSyncExternalStore` zodat élk shop-block (grid, cart-badge, checkout) dezelfde\n * state ziet zonder dat er een provider gemount hoeft te worden. Cross-tab sync via `storage`.\n *\n * Prijzen in de cart zijn indicatief voor weergave; bij `createOrder` herrekent de server alles\n * tegen de opgeslagen product-docs (client wordt nooit vertrouwd).\n */\nimport { useSyncExternalStore } from \"react\";\n\nexport interface CartLine {\n productId: string;\n slug: string;\n name: string;\n /** Variant-key (\"68|Roze\") + leesbaar label (\"Maat 68, Roze\"). */\n variantKey?: string;\n variantLabel?: string;\n unitPriceCents: number;\n qty: number;\n imageUrl?: string;\n}\n\nconst listeners = new Set<() => void>();\n/** Cache per siteId zodat getSnapshot een stabiele referentie teruggeeft (useSyncExternalStore-eis). */\nconst cache = new Map<string, { raw: string; lines: CartLine[] }>();\n\nfunction storageKey(siteId: string): string {\n return `xicms:cart:${siteId}`;\n}\n\nfunction readLines(siteId: string): CartLine[] {\n if (typeof window === \"undefined\") return [];\n let raw = \"\";\n try {\n raw = window.localStorage.getItem(storageKey(siteId)) ?? \"[]\";\n } catch {\n raw = \"[]\";\n }\n const cached = cache.get(siteId);\n if (cached && cached.raw === raw) return cached.lines;\n let lines: CartLine[] = [];\n try {\n const parsed = JSON.parse(raw);\n if (Array.isArray(parsed)) lines = parsed.filter((l) => l && typeof l === \"object\" && typeof l.productId === \"string\");\n } catch {\n lines = [];\n }\n cache.set(siteId, { raw, lines });\n return lines;\n}\n\nfunction writeLines(siteId: string, lines: CartLine[]): void {\n try {\n window.localStorage.setItem(storageKey(siteId), JSON.stringify(lines));\n } catch {\n // localStorage vol/geblokkeerd: cart werkt dan alleen in-memory via de cache.\n cache.set(siteId, { raw: `mem:${Date.now()}`, lines });\n }\n emit();\n}\n\nfunction emit(): void {\n for (const l of listeners) l();\n}\n\nfunction subscribe(cb: () => void): () => void {\n listeners.add(cb);\n // Cross-tab: een andere tab die de cart wijzigt triggert hier een re-render.\n const onStorage = (e: StorageEvent) => {\n if (e.key?.startsWith(\"xicms:cart:\")) cb();\n };\n window.addEventListener(\"storage\", onStorage);\n return () => {\n listeners.delete(cb);\n window.removeEventListener(\"storage\", onStorage);\n };\n}\n\nfunction lineId(l: Pick<CartLine, \"productId\" | \"variantKey\">): string {\n return `${l.productId}::${l.variantKey ?? \"\"}`;\n}\n\nexport function cartAdd(siteId: string, line: Omit<CartLine, \"qty\">, qty = 1): void {\n const lines = [...readLines(siteId)];\n const idx = lines.findIndex((l) => lineId(l) === lineId(line));\n if (idx >= 0) lines[idx] = { ...lines[idx], qty: Math.min(999, lines[idx].qty + qty) };\n else lines.push({ ...line, qty: Math.min(999, Math.max(1, qty)) });\n writeLines(siteId, lines);\n}\n\nexport function cartSetQty(siteId: string, productId: string, variantKey: string | undefined, qty: number): void {\n const lines = readLines(siteId)\n .map((l) => (lineId(l) === lineId({ productId, variantKey }) ? { ...l, qty: Math.min(999, qty) } : l))\n .filter((l) => l.qty > 0);\n writeLines(siteId, lines);\n}\n\nexport function cartRemove(siteId: string, productId: string, variantKey: string | undefined): void {\n writeLines(siteId, readLines(siteId).filter((l) => lineId(l) !== lineId({ productId, variantKey })));\n}\n\nexport function cartClear(siteId: string): void {\n writeLines(siteId, []);\n}\n\nexport interface CartState {\n lines: CartLine[];\n count: number;\n itemsCents: number;\n}\n\n/** Reactieve cart-state voor blocks/klantcomponenten (bv. een cart-badge in de header). */\nexport function useCart(siteId: string): CartState {\n const lines = useSyncExternalStore(subscribe, () => readLines(siteId), () => [] as CartLine[]);\n return {\n lines,\n count: lines.reduce((n, l) => n + l.qty, 0),\n itemsCents: lines.reduce((n, l) => n + l.unitPriceCents * l.qty, 0),\n };\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,25 @@
1
+ import type { CreateOrderInput, Product, ShopCategory } from "../../schema/shop";
2
+ import { type ShopSettings } from "./settings";
3
+ /** Publieke shop-instellingen met defaults (paden, verzendkosten, valuta). */
4
+ export declare function useShopSettings(): ShopSettings;
5
+ export type StoredProduct = Product & {
6
+ id: string;
7
+ };
8
+ export type StoredShopCategory = ShopCategory & {
9
+ id: string;
10
+ };
11
+ export declare function fetchActiveProducts(siteId: string): Promise<StoredProduct[]>;
12
+ export declare function fetchProductBySlug(siteId: string, slug: string): Promise<StoredProduct | null>;
13
+ export declare function fetchShopCategories(siteId: string): Promise<StoredShopCategory[]>;
14
+ /** Start de checkout: order + Stripe-sessie server-side; geeft de redirect-URL terug. */
15
+ export declare function createOrderCall(input: CreateOrderInput): Promise<{
16
+ url: string;
17
+ orderNumber: string;
18
+ }>;
19
+ /** Leesbaar variantlabel: opties ["Maat","Kleur"] + key "68|Roze" → "Maat 68 · Roze". */
20
+ export declare function variantLabelFor(options: {
21
+ name: string;
22
+ values: string[];
23
+ }[], key: string): string;
24
+ /** Querystring-parameter client-side lezen (blocks zijn client components). */
25
+ export declare function readQueryParam(name: string): string | null;
@@ -0,0 +1,61 @@
1
+ "use client";
2
+ import { collection, getDocs, limit as fsLimit, query, where } from "firebase/firestore";
3
+ import { getFunctions, httpsCallable } from "firebase/functions";
4
+ import { getDb, getFirebaseApp } from "../../firebase/client";
5
+ import { functionsRegion } from "../../firebase/region";
6
+ import { useModuleSettings } from "../context";
7
+ import { defaultShopSettings } from "./settings";
8
+ function useShopSettings() {
9
+ const raw = useModuleSettings("shop");
10
+ return { ...defaultShopSettings, ...raw };
11
+ }
12
+ async function fetchActiveProducts(siteId) {
13
+ const db = getDb();
14
+ if (!db) return [];
15
+ const snap = await getDocs(
16
+ query(collection(db, "sites", siteId, "products"), where("status", "==", "active"), fsLimit(500))
17
+ );
18
+ return snap.docs.map((d) => ({ id: d.id, ...d.data() }));
19
+ }
20
+ async function fetchProductBySlug(siteId, slug) {
21
+ const db = getDb();
22
+ if (!db || !slug) return null;
23
+ const snap = await getDocs(
24
+ query(collection(db, "sites", siteId, "products"), where("slug", "==", slug), fsLimit(1))
25
+ );
26
+ const doc = snap.docs[0];
27
+ if (!doc) return null;
28
+ const product = { id: doc.id, ...doc.data() };
29
+ return product.status === "active" ? product : null;
30
+ }
31
+ async function fetchShopCategories(siteId) {
32
+ const db = getDb();
33
+ if (!db) return [];
34
+ const snap = await getDocs(query(collection(db, "sites", siteId, "shopCategories"), fsLimit(100)));
35
+ return snap.docs.map((d) => ({ id: d.id, ...d.data() })).filter((c) => c.visible !== false).sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
36
+ }
37
+ async function createOrderCall(input) {
38
+ const app = getFirebaseApp();
39
+ if (!app) throw new Error("Webwinkel niet beschikbaar.");
40
+ const fn = httpsCallable(getFunctions(app, functionsRegion()), "createOrder");
41
+ const res = await fn(input);
42
+ return res.data;
43
+ }
44
+ function variantLabelFor(options, key) {
45
+ const parts = key.split("|");
46
+ return options.map((o, i) => `${o.name} ${parts[i] ?? "?"}`).join(" \xB7 ");
47
+ }
48
+ function readQueryParam(name) {
49
+ if (typeof window === "undefined") return null;
50
+ return new URLSearchParams(window.location.search).get(name);
51
+ }
52
+ export {
53
+ createOrderCall,
54
+ fetchActiveProducts,
55
+ fetchProductBySlug,
56
+ fetchShopCategories,
57
+ readQueryParam,
58
+ useShopSettings,
59
+ variantLabelFor
60
+ };
61
+ //# sourceMappingURL=lib.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/modules/shop/lib.ts"],"sourcesContent":["\"use client\";\n\n/**\n * Client-helpers voor de shop-blocks: producten/categorieën lezen (publieke rules) en de\n * `createOrder`-callable aanroepen. Queries bewust simpel gehouden (één `where` op status,\n * filteren/sorteren client-side) zodat er géén composite-indexes nodig zijn — assortimenten\n * van XiCMS-sites zijn klein (tientallen tot honderden producten).\n */\nimport { collection, getDocs, limit as fsLimit, query, where } from \"firebase/firestore\";\nimport { getFunctions, httpsCallable } from \"firebase/functions\";\nimport { getDb, getFirebaseApp } from \"../../firebase/client\";\nimport { functionsRegion } from \"../../firebase/region\";\nimport type { CreateOrderInput, Product, ShopCategory } from \"../../schema/shop\";\nimport { useModuleSettings } from \"../context\";\nimport { defaultShopSettings, type ShopSettings } from \"./settings\";\n\n/** Publieke shop-instellingen met defaults (paden, verzendkosten, valuta). */\nexport function useShopSettings(): ShopSettings {\n const raw = useModuleSettings<Partial<ShopSettings>>(\"shop\");\n return { ...defaultShopSettings, ...raw };\n}\n\nexport type StoredProduct = Product & { id: string };\nexport type StoredShopCategory = ShopCategory & { id: string };\n\nexport async function fetchActiveProducts(siteId: string): Promise<StoredProduct[]> {\n const db = getDb();\n if (!db) return [];\n const snap = await getDocs(\n query(collection(db, \"sites\", siteId, \"products\"), where(\"status\", \"==\", \"active\"), fsLimit(500)),\n );\n return snap.docs.map((d) => ({ id: d.id, ...(d.data() as Product) }));\n}\n\nexport async function fetchProductBySlug(siteId: string, slug: string): Promise<StoredProduct | null> {\n const db = getDb();\n if (!db || !slug) return null;\n const snap = await getDocs(\n query(collection(db, \"sites\", siteId, \"products\"), where(\"slug\", \"==\", slug), fsLimit(1)),\n );\n const doc = snap.docs[0];\n if (!doc) return null;\n const product = { id: doc.id, ...(doc.data() as Product) };\n return product.status === \"active\" ? product : null;\n}\n\nexport async function fetchShopCategories(siteId: string): Promise<StoredShopCategory[]> {\n const db = getDb();\n if (!db) return [];\n const snap = await getDocs(query(collection(db, \"sites\", siteId, \"shopCategories\"), fsLimit(100)));\n return snap.docs\n .map((d) => ({ id: d.id, ...(d.data() as ShopCategory) }))\n .filter((c) => c.visible !== false)\n .sort((a, b) => (a.order ?? 0) - (b.order ?? 0));\n}\n\n/** Start de checkout: order + Stripe-sessie server-side; geeft de redirect-URL terug. */\nexport async function createOrderCall(input: CreateOrderInput): Promise<{ url: string; orderNumber: string }> {\n const app = getFirebaseApp();\n if (!app) throw new Error(\"Webwinkel niet beschikbaar.\");\n const fn = httpsCallable(getFunctions(app, functionsRegion()), \"createOrder\");\n const res = await fn(input);\n return res.data as { url: string; orderNumber: string };\n}\n\n/** Leesbaar variantlabel: opties [\"Maat\",\"Kleur\"] + key \"68|Roze\" → \"Maat 68 · Roze\". */\nexport function variantLabelFor(options: { name: string; values: string[] }[], key: string): string {\n const parts = key.split(\"|\");\n return options.map((o, i) => `${o.name} ${parts[i] ?? \"?\"}`).join(\" · \");\n}\n\n/** Querystring-parameter client-side lezen (blocks zijn client components). */\nexport function readQueryParam(name: string): string | null {\n if (typeof window === \"undefined\") return null;\n return new URLSearchParams(window.location.search).get(name);\n}\n"],"mappings":";AAQA,SAAS,YAAY,SAAS,SAAS,SAAS,OAAO,aAAa;AACpE,SAAS,cAAc,qBAAqB;AAC5C,SAAS,OAAO,sBAAsB;AACtC,SAAS,uBAAuB;AAEhC,SAAS,yBAAyB;AAClC,SAAS,2BAA8C;AAGhD,SAAS,kBAAgC;AAC9C,QAAM,MAAM,kBAAyC,MAAM;AAC3D,SAAO,EAAE,GAAG,qBAAqB,GAAG,IAAI;AAC1C;AAKA,eAAsB,oBAAoB,QAA0C;AAClF,QAAM,KAAK,MAAM;AACjB,MAAI,CAAC,GAAI,QAAO,CAAC;AACjB,QAAM,OAAO,MAAM;AAAA,IACjB,MAAM,WAAW,IAAI,SAAS,QAAQ,UAAU,GAAG,MAAM,UAAU,MAAM,QAAQ,GAAG,QAAQ,GAAG,CAAC;AAAA,EAClG;AACA,SAAO,KAAK,KAAK,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,GAAI,EAAE,KAAK,EAAc,EAAE;AACtE;AAEA,eAAsB,mBAAmB,QAAgB,MAA6C;AACpG,QAAM,KAAK,MAAM;AACjB,MAAI,CAAC,MAAM,CAAC,KAAM,QAAO;AACzB,QAAM,OAAO,MAAM;AAAA,IACjB,MAAM,WAAW,IAAI,SAAS,QAAQ,UAAU,GAAG,MAAM,QAAQ,MAAM,IAAI,GAAG,QAAQ,CAAC,CAAC;AAAA,EAC1F;AACA,QAAM,MAAM,KAAK,KAAK,CAAC;AACvB,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,UAAU,EAAE,IAAI,IAAI,IAAI,GAAI,IAAI,KAAK,EAAc;AACzD,SAAO,QAAQ,WAAW,WAAW,UAAU;AACjD;AAEA,eAAsB,oBAAoB,QAA+C;AACvF,QAAM,KAAK,MAAM;AACjB,MAAI,CAAC,GAAI,QAAO,CAAC;AACjB,QAAM,OAAO,MAAM,QAAQ,MAAM,WAAW,IAAI,SAAS,QAAQ,gBAAgB,GAAG,QAAQ,GAAG,CAAC,CAAC;AACjG,SAAO,KAAK,KACT,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,GAAI,EAAE,KAAK,EAAmB,EAAE,EACxD,OAAO,CAAC,MAAM,EAAE,YAAY,KAAK,EACjC,KAAK,CAAC,GAAG,OAAO,EAAE,SAAS,MAAM,EAAE,SAAS,EAAE;AACnD;AAGA,eAAsB,gBAAgB,OAAwE;AAC5G,QAAM,MAAM,eAAe;AAC3B,MAAI,CAAC,IAAK,OAAM,IAAI,MAAM,6BAA6B;AACvD,QAAM,KAAK,cAAc,aAAa,KAAK,gBAAgB,CAAC,GAAG,aAAa;AAC5E,QAAM,MAAM,MAAM,GAAG,KAAK;AAC1B,SAAO,IAAI;AACb;AAGO,SAAS,gBAAgB,SAA+C,KAAqB;AAClG,QAAM,QAAQ,IAAI,MAAM,GAAG;AAC3B,SAAO,QAAQ,IAAI,CAAC,GAAG,MAAM,GAAG,EAAE,IAAI,IAAI,MAAM,CAAC,KAAK,GAAG,EAAE,EAAE,KAAK,QAAK;AACzE;AAGO,SAAS,eAAe,MAA6B;AAC1D,MAAI,OAAO,WAAW,YAAa,QAAO;AAC1C,SAAO,IAAI,gBAAgB,OAAO,SAAS,MAAM,EAAE,IAAI,IAAI;AAC7D;","names":[]}
@@ -0,0 +1 @@
1
+ export declare const shopModule: import("..").ModuleDefinition;
@@ -0,0 +1,39 @@
1
+ import { defineModule } from "../defineModule";
2
+ import { ShopSettingsSchema, defaultShopSettings } from "./settings";
3
+ import { shopCategoriesBlock } from "./blocks/ShopCategories";
4
+ import { productGridBlock } from "./blocks/ProductGrid";
5
+ import { productDetailBlock } from "./blocks/ProductDetail";
6
+ import { cartBlock } from "./blocks/Cart";
7
+ import { checkoutBlock } from "./blocks/Checkout";
8
+ import { donationBlock } from "./blocks/Donation";
9
+ import { orderStatusBlock } from "./blocks/OrderStatus";
10
+ const shopModule = defineModule({
11
+ id: "shop",
12
+ version: "1.0.0",
13
+ label: "Webwinkel",
14
+ description: "Webwinkel + donaties: producten met varianten, winkelwagen, afrekenen via Stripe (eigen account van de klant) en bestelbevestiging per e-mail via de eigen SMTP-mailbox.",
15
+ defaultEnabled: false,
16
+ blocks: [
17
+ shopCategoriesBlock,
18
+ productGridBlock,
19
+ productDetailBlock,
20
+ cartBlock,
21
+ checkoutBlock,
22
+ donationBlock,
23
+ orderStatusBlock
24
+ ],
25
+ settingsSchema: ShopSettingsSchema,
26
+ defaultSettings: defaultShopSettings,
27
+ requires: [
28
+ { kind: "function", name: "createOrder" },
29
+ { kind: "function", name: "shopStripeWebhook" },
30
+ { kind: "function", name: "saveShopSecrets" },
31
+ { kind: "firestore", collection: "products" },
32
+ { kind: "firestore", collection: "shopCategories" },
33
+ { kind: "firestore", collection: "orders" }
34
+ ]
35
+ });
36
+ export {
37
+ shopModule
38
+ };
39
+ //# sourceMappingURL=module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/modules/shop/module.ts"],"sourcesContent":["/**\n * Shop-module (5.5) — webwinkel + donaties op het eigen Stripe-account van de klant.\n *\n * - Producten/categorieën: `sites/{siteId}/products` + `shopCategories` (editor-write, rules).\n * - Checkout: `createOrder`-callable herrekent bedragen server-side en start een hosted\n * Stripe Checkout-sessie (iDEAL/kaart); de `shopStripeWebhook` zet de order op betaald en\n * verstuurt de bevestigingsmail (eigen SMTP van de klant, bv. Gmail — zie functions/lib/mail).\n * - Donaties: het donatie-block gebruikt dezelfde order-flow (kind:\"donation\", geen verzending).\n * - Beheer: het \"Webwinkel\"-paneel (producten/categorieën/bestellingen/koppelingen) hangt via\n * de paneel-registry aan deze module-id.\n *\n * Server-safe: geen \"use client\" hier — de blocks zelf zijn client components.\n */\nimport { defineModule } from \"../defineModule\";\nimport { ShopSettingsSchema, defaultShopSettings } from \"./settings\";\nimport { shopCategoriesBlock } from \"./blocks/ShopCategories\";\nimport { productGridBlock } from \"./blocks/ProductGrid\";\nimport { productDetailBlock } from \"./blocks/ProductDetail\";\nimport { cartBlock } from \"./blocks/Cart\";\nimport { checkoutBlock } from \"./blocks/Checkout\";\nimport { donationBlock } from \"./blocks/Donation\";\nimport { orderStatusBlock } from \"./blocks/OrderStatus\";\n\nexport const shopModule = defineModule({\n id: \"shop\",\n version: \"1.0.0\",\n label: \"Webwinkel\",\n description:\n \"Webwinkel + donaties: producten met varianten, winkelwagen, afrekenen via Stripe (eigen account van de klant) en bestelbevestiging per e-mail via de eigen SMTP-mailbox.\",\n defaultEnabled: false,\n blocks: [\n shopCategoriesBlock,\n productGridBlock,\n productDetailBlock,\n cartBlock,\n checkoutBlock,\n donationBlock,\n orderStatusBlock,\n ],\n settingsSchema: ShopSettingsSchema,\n defaultSettings: defaultShopSettings,\n requires: [\n { kind: \"function\", name: \"createOrder\" },\n { kind: \"function\", name: \"shopStripeWebhook\" },\n { kind: \"function\", name: \"saveShopSecrets\" },\n { kind: \"firestore\", collection: \"products\" },\n { kind: \"firestore\", collection: \"shopCategories\" },\n { kind: \"firestore\", collection: \"orders\" },\n ],\n});\n"],"mappings":"AAaA,SAAS,oBAAoB;AAC7B,SAAS,oBAAoB,2BAA2B;AACxD,SAAS,2BAA2B;AACpC,SAAS,wBAAwB;AACjC,SAAS,0BAA0B;AACnC,SAAS,iBAAiB;AAC1B,SAAS,qBAAqB;AAC9B,SAAS,qBAAqB;AAC9B,SAAS,wBAAwB;AAE1B,MAAM,aAAa,aAAa;AAAA,EACrC,IAAI;AAAA,EACJ,SAAS;AAAA,EACT,OAAO;AAAA,EACP,aACE;AAAA,EACF,gBAAgB;AAAA,EAChB,QAAQ;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA,gBAAgB;AAAA,EAChB,iBAAiB;AAAA,EACjB,UAAU;AAAA,IACR,EAAE,MAAM,YAAY,MAAM,cAAc;AAAA,IACxC,EAAE,MAAM,YAAY,MAAM,oBAAoB;AAAA,IAC9C,EAAE,MAAM,YAAY,MAAM,kBAAkB;AAAA,IAC5C,EAAE,MAAM,aAAa,YAAY,WAAW;AAAA,IAC5C,EAAE,MAAM,aAAa,YAAY,iBAAiB;AAAA,IAClD,EAAE,MAAM,aAAa,YAAY,SAAS;AAAA,EAC5C;AACF,CAAC;","names":[]}
@@ -0,0 +1,41 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Publieke shop-instellingen (per site, via `settings/public.modules.shop.config` of
4
+ * `SiteConfig.modules.shop.config`). Server-safe: alleen Zod. De SettingsPanel rendert hier
5
+ * automatisch een formulier voor (moduleSettingsForm); de Functions lezen dezelfde config
6
+ * server-side. Geheimen (Stripe-key, SMTP) horen hier NIET — die staan in
7
+ * `sites/{siteId}/private/shop` en lopen via callables.
8
+ */
9
+ export declare const ShopSettingsSchema: z.ZodObject<{
10
+ shopPath: z.ZodDefault<z.ZodString>;
11
+ detailPath: z.ZodDefault<z.ZodString>;
12
+ cartPath: z.ZodDefault<z.ZodString>;
13
+ checkoutPath: z.ZodDefault<z.ZodString>;
14
+ successPath: z.ZodDefault<z.ZodString>;
15
+ currency: z.ZodDefault<z.ZodString>;
16
+ shippingFlatCents: z.ZodDefault<z.ZodNumber>;
17
+ shippingFreeFromCents: z.ZodDefault<z.ZodNumber>;
18
+ orderPrefix: z.ZodDefault<z.ZodString>;
19
+ }, "strip", z.ZodTypeAny, {
20
+ currency: string;
21
+ shopPath: string;
22
+ detailPath: string;
23
+ cartPath: string;
24
+ checkoutPath: string;
25
+ successPath: string;
26
+ shippingFlatCents: number;
27
+ shippingFreeFromCents: number;
28
+ orderPrefix: string;
29
+ }, {
30
+ currency?: string | undefined;
31
+ shopPath?: string | undefined;
32
+ detailPath?: string | undefined;
33
+ cartPath?: string | undefined;
34
+ checkoutPath?: string | undefined;
35
+ successPath?: string | undefined;
36
+ shippingFlatCents?: number | undefined;
37
+ shippingFreeFromCents?: number | undefined;
38
+ orderPrefix?: string | undefined;
39
+ }>;
40
+ export type ShopSettings = z.infer<typeof ShopSettingsSchema>;
41
+ export declare const defaultShopSettings: ShopSettings;
@@ -0,0 +1,18 @@
1
+ import { z } from "zod";
2
+ const ShopSettingsSchema = z.object({
3
+ shopPath: z.string().max(200).default("/webwinkel").describe("Pad van de winkelpagina"),
4
+ detailPath: z.string().max(200).default("/product").describe("Pad van de productpagina"),
5
+ cartPath: z.string().max(200).default("/winkelwagen").describe("Pad van de winkelwagen"),
6
+ checkoutPath: z.string().max(200).default("/afrekenen").describe("Pad van de afrekenpagina"),
7
+ successPath: z.string().max(200).default("/bedankt").describe("Pad van de bedankt-pagina"),
8
+ currency: z.string().min(3).max(3).default("EUR").describe("Valuta (ISO-code)"),
9
+ shippingFlatCents: z.number().int().min(0).max(1e5).default(495).describe("Verzendkosten (centen)"),
10
+ shippingFreeFromCents: z.number().int().min(0).max(1e8).default(0).describe("Gratis verzending vanaf (centen, 0 = nooit)"),
11
+ orderPrefix: z.string().max(12).default("ORDER").describe("Ordernummer-prefix")
12
+ });
13
+ const defaultShopSettings = ShopSettingsSchema.parse({});
14
+ export {
15
+ ShopSettingsSchema,
16
+ defaultShopSettings
17
+ };
18
+ //# sourceMappingURL=settings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/modules/shop/settings.ts"],"sourcesContent":["import { z } from \"zod\";\n\n/**\n * Publieke shop-instellingen (per site, via `settings/public.modules.shop.config` of\n * `SiteConfig.modules.shop.config`). Server-safe: alleen Zod. De SettingsPanel rendert hier\n * automatisch een formulier voor (moduleSettingsForm); de Functions lezen dezelfde config\n * server-side. Geheimen (Stripe-key, SMTP) horen hier NIET — die staan in\n * `sites/{siteId}/private/shop` en lopen via callables.\n */\nexport const ShopSettingsSchema = z.object({\n shopPath: z.string().max(200).default(\"/webwinkel\").describe(\"Pad van de winkelpagina\"),\n detailPath: z.string().max(200).default(\"/product\").describe(\"Pad van de productpagina\"),\n cartPath: z.string().max(200).default(\"/winkelwagen\").describe(\"Pad van de winkelwagen\"),\n checkoutPath: z.string().max(200).default(\"/afrekenen\").describe(\"Pad van de afrekenpagina\"),\n successPath: z.string().max(200).default(\"/bedankt\").describe(\"Pad van de bedankt-pagina\"),\n currency: z.string().min(3).max(3).default(\"EUR\").describe(\"Valuta (ISO-code)\"),\n shippingFlatCents: z.number().int().min(0).max(100_000).default(495).describe(\"Verzendkosten (centen)\"),\n shippingFreeFromCents: z\n .number()\n .int()\n .min(0)\n .max(1_000_000_00)\n .default(0)\n .describe(\"Gratis verzending vanaf (centen, 0 = nooit)\"),\n orderPrefix: z.string().max(12).default(\"ORDER\").describe(\"Ordernummer-prefix\"),\n});\nexport type ShopSettings = z.infer<typeof ShopSettingsSchema>;\n\nexport const defaultShopSettings: ShopSettings = ShopSettingsSchema.parse({});\n"],"mappings":"AAAA,SAAS,SAAS;AASX,MAAM,qBAAqB,EAAE,OAAO;AAAA,EACzC,UAAU,EAAE,OAAO,EAAE,IAAI,GAAG,EAAE,QAAQ,YAAY,EAAE,SAAS,yBAAyB;AAAA,EACtF,YAAY,EAAE,OAAO,EAAE,IAAI,GAAG,EAAE,QAAQ,UAAU,EAAE,SAAS,0BAA0B;AAAA,EACvF,UAAU,EAAE,OAAO,EAAE,IAAI,GAAG,EAAE,QAAQ,cAAc,EAAE,SAAS,wBAAwB;AAAA,EACvF,cAAc,EAAE,OAAO,EAAE,IAAI,GAAG,EAAE,QAAQ,YAAY,EAAE,SAAS,0BAA0B;AAAA,EAC3F,aAAa,EAAE,OAAO,EAAE,IAAI,GAAG,EAAE,QAAQ,UAAU,EAAE,SAAS,2BAA2B;AAAA,EACzF,UAAU,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,EAAE,QAAQ,KAAK,EAAE,SAAS,mBAAmB;AAAA,EAC9E,mBAAmB,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,GAAO,EAAE,QAAQ,GAAG,EAAE,SAAS,wBAAwB;AAAA,EACtG,uBAAuB,EACpB,OAAO,EACP,IAAI,EACJ,IAAI,CAAC,EACL,IAAI,GAAY,EAChB,QAAQ,CAAC,EACT,SAAS,6CAA6C;AAAA,EACzD,aAAa,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,QAAQ,OAAO,EAAE,SAAS,oBAAoB;AAChF,CAAC;AAGM,MAAM,sBAAoC,mBAAmB,MAAM,CAAC,CAAC;","names":[]}
@@ -156,6 +156,9 @@ export declare const HeroBlockSchema: z.ZodObject<{
156
156
  imageUrl: z.ZodOptional<z.ZodString>;
157
157
  /** Videoachtergrond (mp4/webm); overschrijft de afbeelding als gezet. */
158
158
  videoUrl: z.ZodOptional<z.ZodString>;
159
+ /** Kopniveau: "1" voor dé hoofdkop; "2" voor een tweede hero op dezelfde pagina
160
+ * (twee h1's = slechte SEO/a11y). */
161
+ headingLevel: z.ZodDefault<z.ZodEnum<["1", "2"]>>;
159
162
  align: z.ZodDefault<z.ZodEnum<["left", "center"]>>;
160
163
  /** Verticale uitlijning van de inhoud binnen de hero. */
161
164
  vAlign: z.ZodDefault<z.ZodEnum<["top", "center", "bottom"]>>;
@@ -210,6 +213,7 @@ export declare const HeroBlockSchema: z.ZodObject<{
210
213
  _type: "hero";
211
214
  align: "left" | "center";
212
215
  _key: string;
216
+ headingLevel: "1" | "2";
213
217
  vAlign: "center" | "top" | "bottom";
214
218
  minHeight: "sm" | "md" | "lg" | "auto" | "full";
215
219
  overlayOpacity: number;
@@ -249,6 +253,7 @@ export declare const HeroBlockSchema: z.ZodObject<{
249
253
  subtitle?: string | undefined;
250
254
  imageUrl?: string | undefined;
251
255
  videoUrl?: string | undefined;
256
+ headingLevel?: "1" | "2" | undefined;
252
257
  vAlign?: "center" | "top" | "bottom" | undefined;
253
258
  minHeight?: "sm" | "md" | "lg" | "auto" | "full" | undefined;
254
259
  overlayColor?: string | undefined;
@@ -748,16 +753,16 @@ export declare const PricingBlockSchema: z.ZodObject<{
748
753
  label: string;
749
754
  price: string;
750
755
  highlighted: boolean;
751
- features?: string[] | undefined;
752
756
  detail?: string | undefined;
757
+ features?: string[] | undefined;
753
758
  ctaLabel?: string | undefined;
754
759
  ctaHref?: string | undefined;
755
760
  billingPeriod?: string | undefined;
756
761
  }, {
757
762
  label?: string | undefined;
758
- features?: string[] | undefined;
759
763
  price?: string | undefined;
760
764
  detail?: string | undefined;
765
+ features?: string[] | undefined;
761
766
  ctaLabel?: string | undefined;
762
767
  ctaHref?: string | undefined;
763
768
  highlighted?: boolean | undefined;
@@ -800,8 +805,8 @@ export declare const PricingBlockSchema: z.ZodObject<{
800
805
  label: string;
801
806
  price: string;
802
807
  highlighted: boolean;
803
- features?: string[] | undefined;
804
808
  detail?: string | undefined;
809
+ features?: string[] | undefined;
805
810
  ctaLabel?: string | undefined;
806
811
  ctaHref?: string | undefined;
807
812
  billingPeriod?: string | undefined;
@@ -832,9 +837,9 @@ export declare const PricingBlockSchema: z.ZodObject<{
832
837
  } | undefined;
833
838
  items?: {
834
839
  label?: string | undefined;
835
- features?: string[] | undefined;
836
840
  price?: string | undefined;
837
841
  detail?: string | undefined;
842
+ features?: string[] | undefined;
838
843
  ctaLabel?: string | undefined;
839
844
  ctaHref?: string | undefined;
840
845
  highlighted?: boolean | undefined;
@@ -5191,6 +5196,9 @@ export declare const BuiltinBlockSchema: z.ZodDiscriminatedUnion<"_type", [z.Zod
5191
5196
  imageUrl: z.ZodOptional<z.ZodString>;
5192
5197
  /** Videoachtergrond (mp4/webm); overschrijft de afbeelding als gezet. */
5193
5198
  videoUrl: z.ZodOptional<z.ZodString>;
5199
+ /** Kopniveau: "1" voor dé hoofdkop; "2" voor een tweede hero op dezelfde pagina
5200
+ * (twee h1's = slechte SEO/a11y). */
5201
+ headingLevel: z.ZodDefault<z.ZodEnum<["1", "2"]>>;
5194
5202
  align: z.ZodDefault<z.ZodEnum<["left", "center"]>>;
5195
5203
  /** Verticale uitlijning van de inhoud binnen de hero. */
5196
5204
  vAlign: z.ZodDefault<z.ZodEnum<["top", "center", "bottom"]>>;
@@ -5245,6 +5253,7 @@ export declare const BuiltinBlockSchema: z.ZodDiscriminatedUnion<"_type", [z.Zod
5245
5253
  _type: "hero";
5246
5254
  align: "left" | "center";
5247
5255
  _key: string;
5256
+ headingLevel: "1" | "2";
5248
5257
  vAlign: "center" | "top" | "bottom";
5249
5258
  minHeight: "sm" | "md" | "lg" | "auto" | "full";
5250
5259
  overlayOpacity: number;
@@ -5284,6 +5293,7 @@ export declare const BuiltinBlockSchema: z.ZodDiscriminatedUnion<"_type", [z.Zod
5284
5293
  subtitle?: string | undefined;
5285
5294
  imageUrl?: string | undefined;
5286
5295
  videoUrl?: string | undefined;
5296
+ headingLevel?: "1" | "2" | undefined;
5287
5297
  vAlign?: "center" | "top" | "bottom" | undefined;
5288
5298
  minHeight?: "sm" | "md" | "lg" | "auto" | "full" | undefined;
5289
5299
  overlayColor?: string | undefined;
@@ -5776,16 +5786,16 @@ export declare const BuiltinBlockSchema: z.ZodDiscriminatedUnion<"_type", [z.Zod
5776
5786
  label: string;
5777
5787
  price: string;
5778
5788
  highlighted: boolean;
5779
- features?: string[] | undefined;
5780
5789
  detail?: string | undefined;
5790
+ features?: string[] | undefined;
5781
5791
  ctaLabel?: string | undefined;
5782
5792
  ctaHref?: string | undefined;
5783
5793
  billingPeriod?: string | undefined;
5784
5794
  }, {
5785
5795
  label?: string | undefined;
5786
- features?: string[] | undefined;
5787
5796
  price?: string | undefined;
5788
5797
  detail?: string | undefined;
5798
+ features?: string[] | undefined;
5789
5799
  ctaLabel?: string | undefined;
5790
5800
  ctaHref?: string | undefined;
5791
5801
  highlighted?: boolean | undefined;
@@ -5828,8 +5838,8 @@ export declare const BuiltinBlockSchema: z.ZodDiscriminatedUnion<"_type", [z.Zod
5828
5838
  label: string;
5829
5839
  price: string;
5830
5840
  highlighted: boolean;
5831
- features?: string[] | undefined;
5832
5841
  detail?: string | undefined;
5842
+ features?: string[] | undefined;
5833
5843
  ctaLabel?: string | undefined;
5834
5844
  ctaHref?: string | undefined;
5835
5845
  billingPeriod?: string | undefined;
@@ -5860,9 +5870,9 @@ export declare const BuiltinBlockSchema: z.ZodDiscriminatedUnion<"_type", [z.Zod
5860
5870
  } | undefined;
5861
5871
  items?: {
5862
5872
  label?: string | undefined;
5863
- features?: string[] | undefined;
5864
5873
  price?: string | undefined;
5865
5874
  detail?: string | undefined;
5875
+ features?: string[] | undefined;
5866
5876
  ctaLabel?: string | undefined;
5867
5877
  ctaHref?: string | undefined;
5868
5878
  highlighted?: boolean | undefined;
@@ -10136,6 +10146,9 @@ export declare const AnyBlockSchema: z.ZodUnion<[z.ZodDiscriminatedUnion<"_type"
10136
10146
  imageUrl: z.ZodOptional<z.ZodString>;
10137
10147
  /** Videoachtergrond (mp4/webm); overschrijft de afbeelding als gezet. */
10138
10148
  videoUrl: z.ZodOptional<z.ZodString>;
10149
+ /** Kopniveau: "1" voor dé hoofdkop; "2" voor een tweede hero op dezelfde pagina
10150
+ * (twee h1's = slechte SEO/a11y). */
10151
+ headingLevel: z.ZodDefault<z.ZodEnum<["1", "2"]>>;
10139
10152
  align: z.ZodDefault<z.ZodEnum<["left", "center"]>>;
10140
10153
  /** Verticale uitlijning van de inhoud binnen de hero. */
10141
10154
  vAlign: z.ZodDefault<z.ZodEnum<["top", "center", "bottom"]>>;
@@ -10190,6 +10203,7 @@ export declare const AnyBlockSchema: z.ZodUnion<[z.ZodDiscriminatedUnion<"_type"
10190
10203
  _type: "hero";
10191
10204
  align: "left" | "center";
10192
10205
  _key: string;
10206
+ headingLevel: "1" | "2";
10193
10207
  vAlign: "center" | "top" | "bottom";
10194
10208
  minHeight: "sm" | "md" | "lg" | "auto" | "full";
10195
10209
  overlayOpacity: number;
@@ -10229,6 +10243,7 @@ export declare const AnyBlockSchema: z.ZodUnion<[z.ZodDiscriminatedUnion<"_type"
10229
10243
  subtitle?: string | undefined;
10230
10244
  imageUrl?: string | undefined;
10231
10245
  videoUrl?: string | undefined;
10246
+ headingLevel?: "1" | "2" | undefined;
10232
10247
  vAlign?: "center" | "top" | "bottom" | undefined;
10233
10248
  minHeight?: "sm" | "md" | "lg" | "auto" | "full" | undefined;
10234
10249
  overlayColor?: string | undefined;
@@ -10721,16 +10736,16 @@ export declare const AnyBlockSchema: z.ZodUnion<[z.ZodDiscriminatedUnion<"_type"
10721
10736
  label: string;
10722
10737
  price: string;
10723
10738
  highlighted: boolean;
10724
- features?: string[] | undefined;
10725
10739
  detail?: string | undefined;
10740
+ features?: string[] | undefined;
10726
10741
  ctaLabel?: string | undefined;
10727
10742
  ctaHref?: string | undefined;
10728
10743
  billingPeriod?: string | undefined;
10729
10744
  }, {
10730
10745
  label?: string | undefined;
10731
- features?: string[] | undefined;
10732
10746
  price?: string | undefined;
10733
10747
  detail?: string | undefined;
10748
+ features?: string[] | undefined;
10734
10749
  ctaLabel?: string | undefined;
10735
10750
  ctaHref?: string | undefined;
10736
10751
  highlighted?: boolean | undefined;
@@ -10773,8 +10788,8 @@ export declare const AnyBlockSchema: z.ZodUnion<[z.ZodDiscriminatedUnion<"_type"
10773
10788
  label: string;
10774
10789
  price: string;
10775
10790
  highlighted: boolean;
10776
- features?: string[] | undefined;
10777
10791
  detail?: string | undefined;
10792
+ features?: string[] | undefined;
10778
10793
  ctaLabel?: string | undefined;
10779
10794
  ctaHref?: string | undefined;
10780
10795
  billingPeriod?: string | undefined;
@@ -10805,9 +10820,9 @@ export declare const AnyBlockSchema: z.ZodUnion<[z.ZodDiscriminatedUnion<"_type"
10805
10820
  } | undefined;
10806
10821
  items?: {
10807
10822
  label?: string | undefined;
10808
- features?: string[] | undefined;
10809
10823
  price?: string | undefined;
10810
10824
  detail?: string | undefined;
10825
+ features?: string[] | undefined;
10811
10826
  ctaLabel?: string | undefined;
10812
10827
  ctaHref?: string | undefined;
10813
10828
  highlighted?: boolean | undefined;
@@ -32,6 +32,9 @@ const HeroBlockSchema = z.object({
32
32
  imageUrl: z.string().max(2e3).optional(),
33
33
  /** Videoachtergrond (mp4/webm); overschrijft de afbeelding als gezet. */
34
34
  videoUrl: z.string().max(2e3).optional(),
35
+ /** Kopniveau: "1" voor dé hoofdkop; "2" voor een tweede hero op dezelfde pagina
36
+ * (twee h1's = slechte SEO/a11y). */
37
+ headingLevel: z.enum(["1", "2"]).default("1").describe("Kopniveau (h1/h2)"),
35
38
  align: z.enum(["left", "center"]).default("left"),
36
39
  /** Verticale uitlijning van de inhoud binnen de hero. */
37
40
  vAlign: z.enum(["top", "center", "bottom"]).default("center"),