@xiwebdesign/xicms-core 7.13.0 → 7.14.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 (44) hide show
  1. package/dist/admin/lib/mediaUsage.d.ts +17 -0
  2. package/dist/admin/lib/mediaUsage.js +32 -1
  3. package/dist/admin/lib/mediaUsage.js.map +1 -1
  4. package/dist/admin/media/MediaManager.js +74 -17
  5. package/dist/admin/media/MediaManager.js.map +1 -1
  6. package/dist/admin/visual/VisualEditor.js +37 -3
  7. package/dist/admin/visual/VisualEditor.js.map +1 -1
  8. package/dist/blocks/BlockErrorBoundary.d.ts +1 -0
  9. package/dist/blocks/BlockErrorBoundary.js +4 -0
  10. package/dist/blocks/BlockErrorBoundary.js.map +1 -1
  11. package/dist/blocks/BlockRenderer.js +1 -1
  12. package/dist/blocks/BlockRenderer.js.map +1 -1
  13. package/dist/blocks/reportRenderFout.d.ts +13 -0
  14. package/dist/blocks/reportRenderFout.js +31 -0
  15. package/dist/blocks/reportRenderFout.js.map +1 -0
  16. package/dist/modules/shop/blocks/Cart.js +1 -2
  17. package/dist/modules/shop/blocks/Cart.js.map +1 -1
  18. package/dist/modules/shop/blocks/Checkout.js +1 -2
  19. package/dist/modules/shop/blocks/Checkout.js.map +1 -1
  20. package/dist/modules/shop/blocks/Donation.js +1 -2
  21. package/dist/modules/shop/blocks/Donation.js.map +1 -1
  22. package/dist/modules/shop/blocks/OrderStatus.js +0 -2
  23. package/dist/modules/shop/blocks/OrderStatus.js.map +1 -1
  24. package/dist/modules/shop/blocks/ProductDetail.js +1 -7
  25. package/dist/modules/shop/blocks/ProductDetail.js.map +1 -1
  26. package/dist/modules/shop/blocks/ProductGrid.js +1 -2
  27. package/dist/modules/shop/blocks/ProductGrid.js.map +1 -1
  28. package/dist/modules/shop/blocks/ShopCategories.js +0 -2
  29. package/dist/modules/shop/blocks/ShopCategories.js.map +1 -1
  30. package/dist/modules/shop/module.js +1 -0
  31. package/dist/modules/shop/module.js.map +1 -1
  32. package/dist/modules/shop/schemas.d.ts +1 -0
  33. package/dist/modules/shop/schemas.js +32 -0
  34. package/dist/modules/shop/schemas.js.map +1 -0
  35. package/dist/modules/types.d.ts +6 -0
  36. package/dist/schema/shop.d.ts +1 -11
  37. package/dist/schema/shop.js +1 -20
  38. package/dist/schema/shop.js.map +1 -1
  39. package/dist/schema/shopGeld.d.ts +18 -0
  40. package/dist/schema/shopGeld.js +27 -0
  41. package/dist/schema/shopGeld.js.map +1 -0
  42. package/dist/styles.css +17 -0
  43. package/dist/version.js +1 -1
  44. package/package.json +1 -1
@@ -17,6 +17,7 @@ interface Props {
17
17
  mode: "view" | "preview";
18
18
  type: string;
19
19
  blockKey: string;
20
+ siteId?: string;
20
21
  children: ReactNode;
21
22
  }
22
23
  interface State {
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { Component } from "react";
4
+ import { reportRenderFout } from "./reportRenderFout.js";
4
5
  class BlockErrorBoundary extends Component {
5
6
  state = { error: null };
6
7
  static getDerivedStateFromError(error) {
@@ -10,6 +11,9 @@ class BlockErrorBoundary extends Component {
10
11
  if (typeof console !== "undefined") {
11
12
  console.error(`[xicms] block "${this.props.type}" gaf een renderfout:`, error, info);
12
13
  }
14
+ if (this.props.mode === "view" && this.props.siteId) {
15
+ reportRenderFout(this.props.siteId, this.props.type, error?.message ?? String(error));
16
+ }
13
17
  }
14
18
  componentDidUpdate(prev) {
15
19
  if (this.state.error && (prev.blockKey !== this.props.blockKey || prev.type !== this.props.type)) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/blocks/BlockErrorBoundary.tsx"],"sourcesContent":["\"use client\";\n\nimport { Component, type ErrorInfo, type ReactNode } from \"react\";\n\n/**\n * Vangt een fout in één block-Render op zodat één kapot block nooit de hele pagina sloopt\n * (foolproof: een editor kan geen content maken die de live site breekt).\n *\n * - `mode=\"view\"` (publiek): faalt stil — rendert niets, layout blijft intact.\n * - `mode=\"preview\"` (editor): toont een zichtbare foutkaart zodat de bewerker de breuk ziet.\n *\n * Let op de RSC-grens: een error-boundary is per definitie client (`componentDidCatch`). Daarom is\n * dit een client-leaf die per block om de Render heen wordt gezet; `BlockRenderer` zelf blijft\n * server-importeerbaar. Een fout die al tijdens server-rendering wordt gegooid valt buiten een\n * client-boundary — maar `validateBlocks` haalt schema-ongeldige content er server-side al uit, en\n * de meeste runtime-render-fouten (kapotte URL, client-hook) gebeuren client-side en worden hier\n * dus wél gevangen.\n */\ninterface Props {\n mode: \"view\" | \"preview\";\n type: string;\n blockKey: string;\n children: ReactNode;\n}\ninterface State {\n error: Error | null;\n}\n\nexport class BlockErrorBoundary extends Component<Props, State> {\n state: State = { error: null };\n\n static getDerivedStateFromError(error: Error): State {\n return { error };\n }\n\n componentDidCatch(error: Error, info: ErrorInfo): void {\n if (typeof console !== \"undefined\") {\n console.error(`[xicms] block \"${this.props.type}\" gaf een renderfout:`, error, info);\n }\n }\n\n componentDidUpdate(prev: Props): void {\n // Reset na een fix (ander block of ander type) zodat het block opnieuw probeert te renderen.\n if (this.state.error && (prev.blockKey !== this.props.blockKey || prev.type !== this.props.type)) {\n this.setState({ error: null });\n }\n }\n\n render(): ReactNode {\n if (this.state.error) {\n if (this.props.mode === \"preview\") {\n return (\n <div className=\"xicms-block-error\" role=\"alert\">\n <strong>Blok “{this.props.type}” kon niet worden weergegeven.</strong>\n <span>{this.state.error.message}</span>\n </div>\n );\n }\n // Publiek: stil falen, geen layout-verstoring.\n return null;\n }\n return this.props.children;\n }\n}\n"],"mappings":";AAqDY,SACA,KADA;AAnDZ,SAAS,iBAAiD;AA0BnD,MAAM,2BAA2B,UAAwB;AAAA,EAC9D,QAAe,EAAE,OAAO,KAAK;AAAA,EAE7B,OAAO,yBAAyB,OAAqB;AACnD,WAAO,EAAE,MAAM;AAAA,EACjB;AAAA,EAEA,kBAAkB,OAAc,MAAuB;AACrD,QAAI,OAAO,YAAY,aAAa;AAClC,cAAQ,MAAM,kBAAkB,KAAK,MAAM,IAAI,yBAAyB,OAAO,IAAI;AAAA,IACrF;AAAA,EACF;AAAA,EAEA,mBAAmB,MAAmB;AAEpC,QAAI,KAAK,MAAM,UAAU,KAAK,aAAa,KAAK,MAAM,YAAY,KAAK,SAAS,KAAK,MAAM,OAAO;AAChG,WAAK,SAAS,EAAE,OAAO,KAAK,CAAC;AAAA,IAC/B;AAAA,EACF;AAAA,EAEA,SAAoB;AAClB,QAAI,KAAK,MAAM,OAAO;AACpB,UAAI,KAAK,MAAM,SAAS,WAAW;AACjC,eACE,qBAAC,SAAI,WAAU,qBAAoB,MAAK,SACtC;AAAA,+BAAC,YAAO;AAAA;AAAA,YAAO,KAAK,MAAM;AAAA,YAAK;AAAA,aAA8B;AAAA,UAC7D,oBAAC,UAAM,eAAK,MAAM,MAAM,SAAQ;AAAA,WAClC;AAAA,MAEJ;AAEA,aAAO;AAAA,IACT;AACA,WAAO,KAAK,MAAM;AAAA,EACpB;AACF;","names":[]}
1
+ {"version":3,"sources":["../../src/blocks/BlockErrorBoundary.tsx"],"sourcesContent":["\"use client\";\n\nimport { Component, type ErrorInfo, type ReactNode } from \"react\";\nimport { reportRenderFout } from \"./reportRenderFout\";\n\n/**\n * Vangt een fout in één block-Render op zodat één kapot block nooit de hele pagina sloopt\n * (foolproof: een editor kan geen content maken die de live site breekt).\n *\n * - `mode=\"view\"` (publiek): faalt stil — rendert niets, layout blijft intact.\n * - `mode=\"preview\"` (editor): toont een zichtbare foutkaart zodat de bewerker de breuk ziet.\n *\n * Let op de RSC-grens: een error-boundary is per definitie client (`componentDidCatch`). Daarom is\n * dit een client-leaf die per block om de Render heen wordt gezet; `BlockRenderer` zelf blijft\n * server-importeerbaar. Een fout die al tijdens server-rendering wordt gegooid valt buiten een\n * client-boundary — maar `validateBlocks` haalt schema-ongeldige content er server-side al uit, en\n * de meeste runtime-render-fouten (kapotte URL, client-hook) gebeuren client-side en worden hier\n * dus wél gevangen.\n */\ninterface Props {\n mode: \"view\" | \"preview\";\n type: string;\n blockKey: string;\n siteId?: string;\n children: ReactNode;\n}\ninterface State {\n error: Error | null;\n}\n\nexport class BlockErrorBoundary extends Component<Props, State> {\n state: State = { error: null };\n\n static getDerivedStateFromError(error: Error): State {\n return { error };\n }\n\n componentDidCatch(error: Error, info: ErrorInfo): void {\n if (typeof console !== \"undefined\") {\n console.error(`[xicms] block \"${this.props.type}\" gaf een renderfout:`, error, info);\n }\n // Publiek (mode=\"view\"): meld de fout aan de backend — de bezoeker ziet niets (dat is de\n // belofte van deze boundary), maar wij horen het via Cloud Logging/Monitoring. In de editor\n // niet: daar stáát de bewerker naar de foutkaart te kijken.\n if (this.props.mode === \"view\" && this.props.siteId) {\n reportRenderFout(this.props.siteId, this.props.type, error?.message ?? String(error));\n }\n }\n\n componentDidUpdate(prev: Props): void {\n // Reset na een fix (ander block of ander type) zodat het block opnieuw probeert te renderen.\n if (this.state.error && (prev.blockKey !== this.props.blockKey || prev.type !== this.props.type)) {\n this.setState({ error: null });\n }\n }\n\n render(): ReactNode {\n if (this.state.error) {\n if (this.props.mode === \"preview\") {\n return (\n <div className=\"xicms-block-error\" role=\"alert\">\n <strong>Blok “{this.props.type}” kon niet worden weergegeven.</strong>\n <span>{this.state.error.message}</span>\n </div>\n );\n }\n // Publiek: stil falen, geen layout-verstoring.\n return null;\n }\n return this.props.children;\n }\n}\n"],"mappings":";AA6DY,SACA,KADA;AA3DZ,SAAS,iBAAiD;AAC1D,SAAS,wBAAwB;AA2B1B,MAAM,2BAA2B,UAAwB;AAAA,EAC9D,QAAe,EAAE,OAAO,KAAK;AAAA,EAE7B,OAAO,yBAAyB,OAAqB;AACnD,WAAO,EAAE,MAAM;AAAA,EACjB;AAAA,EAEA,kBAAkB,OAAc,MAAuB;AACrD,QAAI,OAAO,YAAY,aAAa;AAClC,cAAQ,MAAM,kBAAkB,KAAK,MAAM,IAAI,yBAAyB,OAAO,IAAI;AAAA,IACrF;AAIA,QAAI,KAAK,MAAM,SAAS,UAAU,KAAK,MAAM,QAAQ;AACnD,uBAAiB,KAAK,MAAM,QAAQ,KAAK,MAAM,MAAM,OAAO,WAAW,OAAO,KAAK,CAAC;AAAA,IACtF;AAAA,EACF;AAAA,EAEA,mBAAmB,MAAmB;AAEpC,QAAI,KAAK,MAAM,UAAU,KAAK,aAAa,KAAK,MAAM,YAAY,KAAK,SAAS,KAAK,MAAM,OAAO;AAChG,WAAK,SAAS,EAAE,OAAO,KAAK,CAAC;AAAA,IAC/B;AAAA,EACF;AAAA,EAEA,SAAoB;AAClB,QAAI,KAAK,MAAM,OAAO;AACpB,UAAI,KAAK,MAAM,SAAS,WAAW;AACjC,eACE,qBAAC,SAAI,WAAU,qBAAoB,MAAK,SACtC;AAAA,+BAAC,YAAO;AAAA;AAAA,YAAO,KAAK,MAAM;AAAA,YAAK;AAAA,aAA8B;AAAA,UAC7D,oBAAC,UAAM,eAAK,MAAM,MAAM,SAAQ;AAAA,WAClC;AAAA,MAEJ;AAEA,aAAO;AAAA,IACT;AACA,WAAO,KAAK,MAAM;AAAA,EACpB;AACF;","names":[]}
@@ -15,7 +15,7 @@ function BlockRenderer({
15
15
  const Render = def.Render;
16
16
  const attrs = blockStyleAttrs(b._style);
17
17
  const rendered = /* @__PURE__ */ jsx(Render, { block: b, siteId, mode });
18
- return /* @__PURE__ */ jsx(BlockErrorBoundary, { mode, type: b._type, blockKey: b._key, children: attrs ? /* @__PURE__ */ jsx(
18
+ return /* @__PURE__ */ jsx(BlockErrorBoundary, { mode, type: b._type, blockKey: b._key, siteId, children: attrs ? /* @__PURE__ */ jsx(
19
19
  "div",
20
20
  {
21
21
  id: attrs.id,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/blocks/BlockRenderer.tsx"],"sourcesContent":["import { validateBlocks, type BlockRegistry } from \"./registry\";\nimport { BlockErrorBoundary } from \"./BlockErrorBoundary\";\nimport { blockStyleAttrs } from \"./blockStyle\";\nimport type { BlockStyle } from \"../schema/blocks\";\n\n/**\n * Rendert een blocks-array via de registry. Server-safe (geen \"use client\"): bruikbaar in\n * React Server Components én client. Geef de registry expliciet mee (zo werkt server-rendering);\n * client-componenten kunnen `useRegistry()` gebruiken om hem te verkrijgen.\n *\n * Elk block wordt in een `BlockErrorBoundary` gewikkeld zodat één kapot block nooit de hele\n * pagina sloopt (foolproof).\n */\nexport function BlockRenderer({\n blocks,\n registry,\n siteId,\n mode = \"view\",\n}: {\n blocks: unknown[] | undefined;\n registry: BlockRegistry;\n siteId: string;\n mode?: \"view\" | \"preview\";\n}) {\n const valid = validateBlocks(blocks, registry);\n return (\n <>\n {valid.map((b) => {\n const def = registry.get(b._type);\n if (!def) return null;\n const Render = def.Render;\n // Gedeelde blokstijl (ruimte/achtergrond/breedte/uitlijning/anker) centraal toepassen.\n const attrs = blockStyleAttrs((b as { _style?: BlockStyle })._style);\n const rendered = <Render block={b} siteId={siteId} mode={mode} />;\n // Altijd een DOM-wrapper met een stabiele block-identifier, zodat het feedback-widget\n // (en andere tooling) een onderdeel op de pagina kan aanwijzen via\n // `closest('[data-xicms-block]')`. Zonder eigen blokstijl gebruiken we `display:contents`\n // zodat de wrapper layout-neutraal is (hij traverseert wél mee in `closest()`).\n return (\n <BlockErrorBoundary key={b._key} mode={mode} type={b._type} blockKey={b._key}>\n {attrs ? (\n <div\n id={attrs.id}\n className={attrs.className}\n style={attrs.style}\n data-xicms-animate={attrs.dataAnimate}\n data-xicms-block={b._key}\n data-xicms-block-type={b._type}\n >\n {rendered}\n </div>\n ) : (\n <div style={{ display: \"contents\" }} data-xicms-block={b._key} data-xicms-block-type={b._type}>\n {rendered}\n </div>\n )}\n </BlockErrorBoundary>\n );\n })}\n </>\n );\n}\n"],"mappings":"AA0BI,mBAOqB,WAPrB;AA1BJ,SAAS,sBAA0C;AACnD,SAAS,0BAA0B;AACnC,SAAS,uBAAuB;AAWzB,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA,OAAO;AACT,GAKG;AACD,QAAM,QAAQ,eAAe,QAAQ,QAAQ;AAC7C,SACE,gCACG,gBAAM,IAAI,CAAC,MAAM;AAChB,UAAM,MAAM,SAAS,IAAI,EAAE,KAAK;AAChC,QAAI,CAAC,IAAK,QAAO;AACjB,UAAM,SAAS,IAAI;AAEnB,UAAM,QAAQ,gBAAiB,EAA8B,MAAM;AACnE,UAAM,WAAW,oBAAC,UAAO,OAAO,GAAG,QAAgB,MAAY;AAK/D,WACE,oBAAC,sBAAgC,MAAY,MAAM,EAAE,OAAO,UAAU,EAAE,MACrE,kBACC;AAAA,MAAC;AAAA;AAAA,QACC,IAAI,MAAM;AAAA,QACV,WAAW,MAAM;AAAA,QACjB,OAAO,MAAM;AAAA,QACb,sBAAoB,MAAM;AAAA,QAC1B,oBAAkB,EAAE;AAAA,QACpB,yBAAuB,EAAE;AAAA,QAExB;AAAA;AAAA,IACH,IAEA,oBAAC,SAAI,OAAO,EAAE,SAAS,WAAW,GAAG,oBAAkB,EAAE,MAAM,yBAAuB,EAAE,OACrF,oBACH,KAfqB,EAAE,IAiB3B;AAAA,EAEJ,CAAC,GACH;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../src/blocks/BlockRenderer.tsx"],"sourcesContent":["import { validateBlocks, type BlockRegistry } from \"./registry\";\nimport { BlockErrorBoundary } from \"./BlockErrorBoundary\";\nimport { blockStyleAttrs } from \"./blockStyle\";\nimport type { BlockStyle } from \"../schema/blocks\";\n\n/**\n * Rendert een blocks-array via de registry. Server-safe (geen \"use client\"): bruikbaar in\n * React Server Components én client. Geef de registry expliciet mee (zo werkt server-rendering);\n * client-componenten kunnen `useRegistry()` gebruiken om hem te verkrijgen.\n *\n * Elk block wordt in een `BlockErrorBoundary` gewikkeld zodat één kapot block nooit de hele\n * pagina sloopt (foolproof).\n */\nexport function BlockRenderer({\n blocks,\n registry,\n siteId,\n mode = \"view\",\n}: {\n blocks: unknown[] | undefined;\n registry: BlockRegistry;\n siteId: string;\n mode?: \"view\" | \"preview\";\n}) {\n const valid = validateBlocks(blocks, registry);\n return (\n <>\n {valid.map((b) => {\n const def = registry.get(b._type);\n if (!def) return null;\n const Render = def.Render;\n // Gedeelde blokstijl (ruimte/achtergrond/breedte/uitlijning/anker) centraal toepassen.\n const attrs = blockStyleAttrs((b as { _style?: BlockStyle })._style);\n const rendered = <Render block={b} siteId={siteId} mode={mode} />;\n // Altijd een DOM-wrapper met een stabiele block-identifier, zodat het feedback-widget\n // (en andere tooling) een onderdeel op de pagina kan aanwijzen via\n // `closest('[data-xicms-block]')`. Zonder eigen blokstijl gebruiken we `display:contents`\n // zodat de wrapper layout-neutraal is (hij traverseert wél mee in `closest()`).\n return (\n <BlockErrorBoundary key={b._key} mode={mode} type={b._type} blockKey={b._key} siteId={siteId}>\n {attrs ? (\n <div\n id={attrs.id}\n className={attrs.className}\n style={attrs.style}\n data-xicms-animate={attrs.dataAnimate}\n data-xicms-block={b._key}\n data-xicms-block-type={b._type}\n >\n {rendered}\n </div>\n ) : (\n <div style={{ display: \"contents\" }} data-xicms-block={b._key} data-xicms-block-type={b._type}>\n {rendered}\n </div>\n )}\n </BlockErrorBoundary>\n );\n })}\n </>\n );\n}\n"],"mappings":"AA0BI,mBAOqB,WAPrB;AA1BJ,SAAS,sBAA0C;AACnD,SAAS,0BAA0B;AACnC,SAAS,uBAAuB;AAWzB,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA,OAAO;AACT,GAKG;AACD,QAAM,QAAQ,eAAe,QAAQ,QAAQ;AAC7C,SACE,gCACG,gBAAM,IAAI,CAAC,MAAM;AAChB,UAAM,MAAM,SAAS,IAAI,EAAE,KAAK;AAChC,QAAI,CAAC,IAAK,QAAO;AACjB,UAAM,SAAS,IAAI;AAEnB,UAAM,QAAQ,gBAAiB,EAA8B,MAAM;AACnE,UAAM,WAAW,oBAAC,UAAO,OAAO,GAAG,QAAgB,MAAY;AAK/D,WACE,oBAAC,sBAAgC,MAAY,MAAM,EAAE,OAAO,UAAU,EAAE,MAAM,QAC3E,kBACC;AAAA,MAAC;AAAA;AAAA,QACC,IAAI,MAAM;AAAA,QACV,WAAW,MAAM;AAAA,QACjB,OAAO,MAAM;AAAA,QACb,sBAAoB,MAAM;AAAA,QAC1B,oBAAkB,EAAE;AAAA,QACpB,yBAAuB,EAAE;AAAA,QAExB;AAAA;AAAA,IACH,IAEA,oBAAC,SAAI,OAAO,EAAE,SAAS,WAAW,GAAG,oBAAkB,EAAE,MAAM,yBAAuB,EAAE,OACrF,oBACH,KAfqB,EAAE,IAiB3B;AAAA,EAEJ,CAAC,GACH;AAEJ;","names":[]}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Meldt een blok-renderfout op de LIVE site aan de backend (7.14) — zie functions/clientErrors.ts
3
+ * voor het waarom. Dit bestand bewaakt vooral wat er NIET gebeurt:
4
+ *
5
+ * - nooit op een pagina zonder Firebase, nooit in dev/preview (daar zie je de fout zelf);
6
+ * - maximaal één melding per bloktype per sessie (sessionStorage-dedupe) — een lijst met twintig
7
+ * kapotte kaarten wordt één melding, geen twintig;
8
+ * - volledig fire-and-forget en zelf in try/catch: het meldpunt mag nooit een tweede fout aan de
9
+ * eerste toevoegen;
10
+ * - de Firebase-SDK wordt dynamisch geladen, net als overal op het publieke pad — een pagina
11
+ * zonder kapot blok betaalt hier nul bytes voor.
12
+ */
13
+ export declare function reportRenderFout(siteId: string, blockType: string, message: string): void;
@@ -0,0 +1,31 @@
1
+ "use client";
2
+ import { CORE_VERSION } from "../version.js";
3
+ function reportRenderFout(siteId, blockType, message) {
4
+ try {
5
+ if (process.env.NODE_ENV !== "production") return;
6
+ const sleutel = `xicms-renderfout:${blockType}`;
7
+ if (sessionStorage.getItem(sleutel)) return;
8
+ sessionStorage.setItem(sleutel, "1");
9
+ void (async () => {
10
+ const [{ getFirebaseApp }, { callable }] = await Promise.all([
11
+ import("../firebase/client.js"),
12
+ import("../firebase/functionsClient.js")
13
+ ]);
14
+ const app = getFirebaseApp();
15
+ if (!app) return;
16
+ await callable(app, "reportClientError")({
17
+ siteId,
18
+ blockType,
19
+ path: window.location.pathname.slice(0, 200),
20
+ message: message.slice(0, 500),
21
+ coreVersion: CORE_VERSION
22
+ });
23
+ })().catch(() => {
24
+ });
25
+ } catch {
26
+ }
27
+ }
28
+ export {
29
+ reportRenderFout
30
+ };
31
+ //# sourceMappingURL=reportRenderFout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/blocks/reportRenderFout.ts"],"sourcesContent":["\"use client\";\n\nimport { CORE_VERSION } from \"../version\";\n\n/**\n * Meldt een blok-renderfout op de LIVE site aan de backend (7.14) — zie functions/clientErrors.ts\n * voor het waarom. Dit bestand bewaakt vooral wat er NIET gebeurt:\n *\n * - nooit op een pagina zonder Firebase, nooit in dev/preview (daar zie je de fout zelf);\n * - maximaal één melding per bloktype per sessie (sessionStorage-dedupe) — een lijst met twintig\n * kapotte kaarten wordt één melding, geen twintig;\n * - volledig fire-and-forget en zelf in try/catch: het meldpunt mag nooit een tweede fout aan de\n * eerste toevoegen;\n * - de Firebase-SDK wordt dynamisch geladen, net als overal op het publieke pad — een pagina\n * zonder kapot blok betaalt hier nul bytes voor.\n */\nexport function reportRenderFout(siteId: string, blockType: string, message: string): void {\n try {\n if (process.env.NODE_ENV !== \"production\") return;\n const sleutel = `xicms-renderfout:${blockType}`;\n if (sessionStorage.getItem(sleutel)) return;\n sessionStorage.setItem(sleutel, \"1\");\n void (async () => {\n const [{ getFirebaseApp }, { callable }] = await Promise.all([\n import(\"../firebase/client\"),\n import(\"../firebase/functionsClient\"),\n ]);\n const app = getFirebaseApp();\n if (!app) return;\n await callable(app, \"reportClientError\")({\n siteId,\n blockType,\n path: window.location.pathname.slice(0, 200),\n message: message.slice(0, 500),\n coreVersion: CORE_VERSION,\n });\n })().catch(() => {});\n } catch {\n /* opslag/omgeving geblokkeerd: dan geen melding — nooit een fout om een melding */\n }\n}\n"],"mappings":";AAEA,SAAS,oBAAoB;AActB,SAAS,iBAAiB,QAAgB,WAAmB,SAAuB;AACzF,MAAI;AACF,QAAI,QAAQ,IAAI,aAAa,aAAc;AAC3C,UAAM,UAAU,oBAAoB,SAAS;AAC7C,QAAI,eAAe,QAAQ,OAAO,EAAG;AACrC,mBAAe,QAAQ,SAAS,GAAG;AACnC,UAAM,YAAY;AAChB,YAAM,CAAC,EAAE,eAAe,GAAG,EAAE,SAAS,CAAC,IAAI,MAAM,QAAQ,IAAI;AAAA,QAC3D,OAAO,oBAAoB;AAAA,QAC3B,OAAO,6BAA6B;AAAA,MACtC,CAAC;AACD,YAAM,MAAM,eAAe;AAC3B,UAAI,CAAC,IAAK;AACV,YAAM,SAAS,KAAK,mBAAmB,EAAE;AAAA,QACvC;AAAA,QACA;AAAA,QACA,MAAM,OAAO,SAAS,SAAS,MAAM,GAAG,GAAG;AAAA,QAC3C,SAAS,QAAQ,MAAM,GAAG,GAAG;AAAA,QAC7B,aAAa;AAAA,MACf,CAAC;AAAA,IACH,GAAG,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EACrB,QAAQ;AAAA,EAER;AACF;","names":[]}
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
- import { CartBlockSchema, formatCents, shippingCentsFor } from "../../../schema/shop.js";
3
+ import { formatCents, shippingCentsFor } from "../../../schema/shopGeld.js";
4
4
  import { defineBlock } from "../../../blocks/registry.js";
5
5
  import { Card, Heading, Text } from "../../../blocks/ui/index.js";
6
6
  import { cartLineKey, cartRemove, cartSetQty, useCart } from "../cartStore.js";
@@ -87,7 +87,6 @@ function CartRender({ block, siteId }) {
87
87
  }
88
88
  const cartBlock = defineBlock({
89
89
  type: "cart",
90
- schema: CartBlockSchema,
91
90
  Render: CartRender,
92
91
  label: "Winkelwagen",
93
92
  icon: "\u{1F6D2}",
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/modules/shop/blocks/Cart.tsx"],"sourcesContent":["\"use client\";\n\nimport type { z } from \"zod\";\nimport { CartBlockSchema, formatCents, shippingCentsFor } from \"../../../schema/shop\";\nimport { defineBlock, type BlockRenderProps } from \"../../../blocks/registry\";\nimport { Card, Heading, Text } from \"../../../blocks/ui\";\nimport { cartLineKey, cartRemove, cartSetQty, useCart } from \"../cartStore\";\nimport { useShopStrings } from \"../strings\";\nimport { useShopSettings } from \"../lib\";\n\ntype Blk = z.infer<typeof CartBlockSchema>;\n\nfunction CartRender({ block, siteId }: BlockRenderProps<Blk>) {\n const settings = useShopSettings();\n const t = useShopStrings();\n const { lines, itemsCents } = useCart(siteId);\n const shippingCents = shippingCentsFor(\n itemsCents,\n { shippingFlatCents: settings.shippingFlatCents, shippingFreeFromCents: settings.shippingFreeFromCents || undefined },\n lines.length > 0,\n );\n\n return (\n <div className=\"xicms-shop-cart\">\n {block.heading && <Heading level={1} data-xicms-edit=\"heading\">{block.heading}</Heading>}\n\n {lines.length === 0 ? (\n <Card padding={4} variant=\"muted\" style={{ marginTop: \"1rem\" }}>\n <Text>\n {t(\"cartEmpty\")} <a href={settings.shopPath}>{t(\"toShop\")}</a>\n </Text>\n </Card>\n ) : (\n <div style={{ display: \"grid\", gap: \"1rem\", marginTop: \"1rem\" }}>\n {lines.map((l) => (\n <Card key={cartLineKey(l)} padding={3}>\n <div style={{ display: \"flex\", gap: \"1rem\", alignItems: \"center\", flexWrap: \"wrap\" }}>\n {l.imageUrl && (\n <img src={l.imageUrl} alt=\"\" style={{ width: 72, height: 72, objectFit: \"cover\", borderRadius: 8 }} />\n )}\n <div style={{ flex: \"1 1 200px\" }}>\n <a href={`${settings.detailPath}?p=${encodeURIComponent(l.slug)}`} style={{ textDecoration: \"none\", color: \"inherit\" }}>\n <Text weight=\"bold\" display=\"block\">{l.name}</Text>\n </a>\n {l.variantLabel && <Text size=\"sm\" display=\"block\" style={{ opacity: 0.75 }}>{l.variantLabel}</Text>}\n {l.personalizationLabel && (\n <Text size=\"sm\" display=\"block\" style={{ opacity: 0.75 }}>{l.personalizationLabel}</Text>\n )}\n <Text size=\"sm\" display=\"block\" style={{ marginTop: 2 }}>\n {formatCents(l.unitPriceCents, settings.currency)} {t(\"perPiece\")}\n </Text>\n </div>\n <div style={{ display: \"flex\", alignItems: \"center\", gap: 8 }}>\n <input\n className=\"xicms-input\"\n type=\"number\"\n min={0}\n max={999}\n value={l.qty}\n aria-label={`${t(\"qty\")} ${l.name}`}\n onChange={(e) => cartSetQty(siteId, l, Math.max(0, Number(e.target.value) || 0))}\n style={{ width: 72 }}\n />\n <Text weight=\"bold\" style={{ minWidth: 80, textAlign: \"right\" }}>\n {formatCents(l.unitPriceCents * l.qty, settings.currency)}\n </Text>\n <button\n type=\"button\"\n aria-label={`Verwijder ${l.name}`}\n onClick={() => cartRemove(siteId, l)}\n style={{ background: \"none\", border: \"none\", cursor: \"pointer\", fontSize: \"1.1rem\", opacity: 0.6 }}\n >\n ✕\n </button>\n </div>\n </div>\n </Card>\n ))}\n\n <Card padding={3} variant=\"muted\">\n <div style={{ display: \"grid\", gap: 4, justifyContent: \"end\", textAlign: \"right\" }}>\n <Text size=\"sm\">{t(\"subtotal\")}: {formatCents(itemsCents, settings.currency)}</Text>\n <Text size=\"sm\">\n {t(\"shippingCosts\")}: {shippingCents === 0 ? t(\"free\") : formatCents(shippingCents, settings.currency)}\n </Text>\n <Text weight=\"bold\">{t(\"total\")}: {formatCents(itemsCents + shippingCents, settings.currency)}</Text>\n </div>\n </Card>\n\n <div style={{ display: \"flex\", gap: 12, flexWrap: \"wrap\" }}>\n <a className=\"xicms-btn xicms-btn--primary\" href={settings.checkoutPath}>{t(\"checkout\")}</a>\n <a className=\"xicms-btn xicms-btn--secondary\" href={settings.shopPath}>{t(\"continueShopping\")}</a>\n </div>\n </div>\n )}\n </div>\n );\n}\n\nexport const cartBlock = defineBlock({\n type: \"cart\",\n schema: CartBlockSchema,\n Render: CartRender,\n label: \"Winkelwagen\",\n icon: \"🛒\",\n defaults: () => ({ heading: \"Winkelwagen\" }),\n});\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../../src/modules/shop/blocks/Cart.tsx"],"sourcesContent":["\"use client\";\n\nimport type { z } from \"zod\";\nimport { formatCents, shippingCentsFor } from \"../../../schema/shopGeld\";\nimport type { CartBlockSchema } from \"../../../schema/shop\";\nimport { defineBlock, type BlockRenderProps } from \"../../../blocks/registry\";\nimport { Card, Heading, Text } from \"../../../blocks/ui\";\nimport { cartLineKey, cartRemove, cartSetQty, useCart } from \"../cartStore\";\nimport { useShopStrings } from \"../strings\";\nimport { useShopSettings } from \"../lib\";\n\ntype Blk = z.infer<typeof CartBlockSchema>;\n\nfunction CartRender({ block, siteId }: BlockRenderProps<Blk>) {\n const settings = useShopSettings();\n const t = useShopStrings();\n const { lines, itemsCents } = useCart(siteId);\n const shippingCents = shippingCentsFor(\n itemsCents,\n { shippingFlatCents: settings.shippingFlatCents, shippingFreeFromCents: settings.shippingFreeFromCents || undefined },\n lines.length > 0,\n );\n\n return (\n <div className=\"xicms-shop-cart\">\n {block.heading && <Heading level={1} data-xicms-edit=\"heading\">{block.heading}</Heading>}\n\n {lines.length === 0 ? (\n <Card padding={4} variant=\"muted\" style={{ marginTop: \"1rem\" }}>\n <Text>\n {t(\"cartEmpty\")} <a href={settings.shopPath}>{t(\"toShop\")}</a>\n </Text>\n </Card>\n ) : (\n <div style={{ display: \"grid\", gap: \"1rem\", marginTop: \"1rem\" }}>\n {lines.map((l) => (\n <Card key={cartLineKey(l)} padding={3}>\n <div style={{ display: \"flex\", gap: \"1rem\", alignItems: \"center\", flexWrap: \"wrap\" }}>\n {l.imageUrl && (\n <img src={l.imageUrl} alt=\"\" style={{ width: 72, height: 72, objectFit: \"cover\", borderRadius: 8 }} />\n )}\n <div style={{ flex: \"1 1 200px\" }}>\n <a href={`${settings.detailPath}?p=${encodeURIComponent(l.slug)}`} style={{ textDecoration: \"none\", color: \"inherit\" }}>\n <Text weight=\"bold\" display=\"block\">{l.name}</Text>\n </a>\n {l.variantLabel && <Text size=\"sm\" display=\"block\" style={{ opacity: 0.75 }}>{l.variantLabel}</Text>}\n {l.personalizationLabel && (\n <Text size=\"sm\" display=\"block\" style={{ opacity: 0.75 }}>{l.personalizationLabel}</Text>\n )}\n <Text size=\"sm\" display=\"block\" style={{ marginTop: 2 }}>\n {formatCents(l.unitPriceCents, settings.currency)} {t(\"perPiece\")}\n </Text>\n </div>\n <div style={{ display: \"flex\", alignItems: \"center\", gap: 8 }}>\n <input\n className=\"xicms-input\"\n type=\"number\"\n min={0}\n max={999}\n value={l.qty}\n aria-label={`${t(\"qty\")} ${l.name}`}\n onChange={(e) => cartSetQty(siteId, l, Math.max(0, Number(e.target.value) || 0))}\n style={{ width: 72 }}\n />\n <Text weight=\"bold\" style={{ minWidth: 80, textAlign: \"right\" }}>\n {formatCents(l.unitPriceCents * l.qty, settings.currency)}\n </Text>\n <button\n type=\"button\"\n aria-label={`Verwijder ${l.name}`}\n onClick={() => cartRemove(siteId, l)}\n style={{ background: \"none\", border: \"none\", cursor: \"pointer\", fontSize: \"1.1rem\", opacity: 0.6 }}\n >\n ✕\n </button>\n </div>\n </div>\n </Card>\n ))}\n\n <Card padding={3} variant=\"muted\">\n <div style={{ display: \"grid\", gap: 4, justifyContent: \"end\", textAlign: \"right\" }}>\n <Text size=\"sm\">{t(\"subtotal\")}: {formatCents(itemsCents, settings.currency)}</Text>\n <Text size=\"sm\">\n {t(\"shippingCosts\")}: {shippingCents === 0 ? t(\"free\") : formatCents(shippingCents, settings.currency)}\n </Text>\n <Text weight=\"bold\">{t(\"total\")}: {formatCents(itemsCents + shippingCents, settings.currency)}</Text>\n </div>\n </Card>\n\n <div style={{ display: \"flex\", gap: 12, flexWrap: \"wrap\" }}>\n <a className=\"xicms-btn xicms-btn--primary\" href={settings.checkoutPath}>{t(\"checkout\")}</a>\n <a className=\"xicms-btn xicms-btn--secondary\" href={settings.shopPath}>{t(\"continueShopping\")}</a>\n </div>\n </div>\n )}\n </div>\n );\n}\n\nexport const cartBlock = defineBlock({\n type: \"cart\",\n Render: CartRender,\n label: \"Winkelwagen\",\n icon: \"🛒\",\n defaults: () => ({ heading: \"Winkelwagen\" }),\n});\n"],"mappings":"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","names":[]}
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useState } from "react";
4
- import { CheckoutBlockSchema, formatCents, shippingCentsFor } from "../../../schema/shop.js";
4
+ import { formatCents, shippingCentsFor } from "../../../schema/shopGeld.js";
5
5
  import { defineBlock } from "../../../blocks/registry.js";
6
6
  import { Card, Heading, Text } from "../../../blocks/ui/index.js";
7
7
  import { TextInput } from "@astryxdesign/core/TextInput";
@@ -166,7 +166,6 @@ function CheckoutRender({ block, siteId, mode }) {
166
166
  }
167
167
  const checkoutBlock = defineBlock({
168
168
  type: "checkout",
169
- schema: CheckoutBlockSchema,
170
169
  Render: CheckoutRender,
171
170
  label: "Afrekenen",
172
171
  icon: "\u{1F4B3}",
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/modules/shop/blocks/Checkout.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState, type FormEvent } from \"react\";\nimport type { z } from \"zod\";\nimport { CheckoutBlockSchema, formatCents, shippingCentsFor } from \"../../../schema/shop\";\nimport { defineBlock, type BlockRenderProps } from \"../../../blocks/registry\";\nimport { Card, Heading, Text } from \"../../../blocks/ui\";\nimport { TextInput } from \"@astryxdesign/core/TextInput\";\nimport { TextArea } from \"@astryxdesign/core/TextArea\";\nimport { cartLineKey, useCart } from \"../cartStore\";\nimport { useShopStrings } from \"../strings\";\nimport { createOrderCall, useShopSettings, validateDiscountCall, type DiscountPreview } from \"../lib\";\n\ntype Blk = z.infer<typeof CheckoutBlockSchema>;\n\n/**\n * Afrekenen — klantgegevens + overzicht + kortingscode; `createOrder` herrekent server-side\n * (incl. korting en personalisatie-meerprijs) en geeft de Stripe Checkout-URL terug. De cart\n * wordt pas geleegd op de bedankt-pagina, zodat een geannuleerde betaling niets kwijtraakt.\n * NB: verplichte velden dragen een \" *\" in het label (de Engelse Astryx-chip blijft uit).\n */\nfunction CheckoutRender({ block, siteId, mode }: BlockRenderProps<Blk>) {\n const settings = useShopSettings();\n const t = useShopStrings();\n const { lines, itemsCents } = useCart(siteId);\n const [v, setV] = useState({ name: \"\", email: \"\", phone: \"\", address: \"\", zip: \"\", city: \"\", note: \"\" });\n const [hp, setHp] = useState(\"\");\n const [busy, setBusy] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [codeInput, setCodeInput] = useState(\"\");\n const [codeBusy, setCodeBusy] = useState(false);\n const [discount, setDiscount] = useState<DiscountPreview | null>(null);\n\n const baseShipping = shippingCentsFor(\n itemsCents,\n { shippingFlatCents: settings.shippingFlatCents, shippingFreeFromCents: settings.shippingFreeFromCents || undefined },\n lines.length > 0,\n );\n const shippingCents = discount?.valid && discount.freeShipping ? 0 : baseShipping;\n const discountCents = discount?.valid ? Math.min(discount.discountCents ?? 0, itemsCents) : 0;\n const totalCents = Math.max(0, itemsCents - discountCents) + shippingCents;\n\n async function applyCode() {\n const code = codeInput.trim().toUpperCase();\n if (!code || codeBusy) return;\n setCodeBusy(true);\n try {\n const res = await validateDiscountCall(siteId, code, itemsCents);\n setDiscount(res.valid ? { ...res, code } : { valid: false });\n } catch {\n setDiscount({ valid: false });\n } finally {\n setCodeBusy(false);\n }\n }\n\n async function submit(e: FormEvent) {\n e.preventDefault();\n if (mode === \"preview\" || busy) return;\n setBusy(true);\n setError(null);\n try {\n const { url } = await createOrderCall({\n siteId,\n items: lines.map((l) => ({\n productId: l.productId,\n variantKey: l.variantKey,\n qty: l.qty,\n personalization: l.personalization,\n })),\n customer: { email: v.email, name: v.name, phone: v.phone || undefined },\n shipping: { address: v.address, zip: v.zip, city: v.city, country: \"NL\" },\n note: v.note || undefined,\n discountCode: discount?.valid ? discount.code : undefined,\n successPath: settings.successPath,\n cancelPath: settings.cartPath,\n _hp: hp || undefined,\n });\n window.location.assign(url);\n } catch (err) {\n console.error(\"[xicms] checkout mislukt:\", err);\n setError(t(\"checkoutFailed\"));\n setBusy(false);\n }\n }\n\n if (lines.length === 0) {\n return (\n <Card padding={4} variant=\"muted\">\n <Text>\n {t(\"cartEmpty\")} <a href={settings.shopPath}>{t(\"toShop\")}</a>\n </Text>\n </Card>\n );\n }\n\n return (\n <div className=\"xicms-shop-checkout\" style={{ display: \"grid\", gap: \"2rem\", gridTemplateColumns: \"repeat(auto-fit, minmax(min(320px, 100%), 1fr))\" }}>\n <form onSubmit={submit} style={{ display: \"grid\", gap: \"0.9rem\", alignContent: \"start\" }}>\n {block.heading && <Heading level={1} data-xicms-edit=\"heading\">{block.heading}</Heading>}\n {block.introText && <Text data-xicms-edit=\"introText\">{block.introText}</Text>}\n\n <TextInput label={`${t(\"fieldName\")} *`} value={v.name} onChange={(x) => setV((p) => ({ ...p, name: x }))} />\n <TextInput label={`${t(\"fieldEmail\")} *`} type=\"email\" value={v.email} onChange={(x) => setV((p) => ({ ...p, email: x }))} />\n <TextInput label={t(\"fieldPhone\")} value={v.phone} onChange={(x) => setV((p) => ({ ...p, phone: x }))} />\n <TextInput label={`${t(\"fieldAddress\")} *`} value={v.address} onChange={(x) => setV((p) => ({ ...p, address: x }))} />\n <div style={{ display: \"grid\", gap: \"0.9rem\", gridTemplateColumns: \"140px 1fr\" }}>\n <TextInput label={`${t(\"fieldZip\")} *`} value={v.zip} onChange={(x) => setV((p) => ({ ...p, zip: x }))} />\n <TextInput label={`${t(\"fieldCity\")} *`} value={v.city} onChange={(x) => setV((p) => ({ ...p, city: x }))} />\n </div>\n <TextArea label={t(\"fieldNote\")} rows={3} value={v.note} onChange={(x) => setV((p) => ({ ...p, note: x }))} />\n\n {/* Honeypot — verborgen voor mensen, bots vullen 'm vaak wel in. */}\n <input\n type=\"text\"\n tabIndex={-1}\n autoComplete=\"off\"\n value={hp}\n onChange={(e) => setHp(e.target.value)}\n style={{ position: \"absolute\", left: \"-9999px\", width: 1, height: 1 }}\n aria-hidden\n />\n\n {error && <Text role=\"alert\" style={{ color: \"var(--color-error, #c0392b)\" }}>{error}</Text>}\n\n <button\n type=\"submit\"\n className=\"xicms-btn xicms-btn--primary\"\n disabled={busy}\n style={{ cursor: busy ? \"wait\" : \"pointer\", width: \"fit-content\" }}\n >\n {busy ? t(\"busy\") : t(\"pay\", { bedrag: formatCents(totalCents, settings.currency) })}\n </button>\n <Text size=\"sm\" style={{ opacity: 0.7 }}>{t(\"secureNote\")}</Text>\n </form>\n\n <Card padding={3} variant=\"muted\" style={{ alignSelf: \"start\" }}>\n <Heading level={2}>{t(\"yourOrder\")}</Heading>\n <div style={{ display: \"grid\", gap: 8, marginTop: \"0.75rem\" }}>\n {lines.map((l) => (\n <div key={cartLineKey(l)} style={{ display: \"flex\", justifyContent: \"space-between\", gap: 12 }}>\n <Text size=\"sm\">\n {l.qty}× {l.name}\n {l.variantLabel ? ` (${l.variantLabel})` : \"\"}\n {l.personalizationLabel ? ` — ${l.personalizationLabel}` : \"\"}\n </Text>\n <Text size=\"sm\">{formatCents(l.unitPriceCents * l.qty, settings.currency)}</Text>\n </div>\n ))}\n\n {/* Kortingscode */}\n <div className=\"xicms-shop-checkout__discount\" style={{ display: \"flex\", gap: 8, marginTop: 4 }}>\n <input\n className=\"xicms-input\"\n type=\"text\"\n placeholder={t(\"discountCode\")}\n value={codeInput}\n onChange={(e) => setCodeInput(e.target.value.toUpperCase())}\n style={{ flex: 1, textTransform: \"uppercase\" }}\n />\n <button type=\"button\" className=\"xicms-btn xicms-btn--secondary\" disabled={codeBusy || !codeInput.trim()} onClick={applyCode}>\n {codeBusy ? t(\"busy\") : t(\"discountApply\")}\n </button>\n </div>\n {discount && !discount.valid && (\n <Text size=\"sm\" role=\"alert\" style={{ color: \"var(--color-error, #c0392b)\" }}>{t(\"discountInvalid\")}</Text>\n )}\n {discount?.valid && (\n <Text size=\"sm\" style={{ color: \"var(--xicms-primary, #4d7fb0)\", fontWeight: 600 }}>\n {t(\"discountApplied\", { code: discount.code ?? \"\" })}\n </Text>\n )}\n\n <hr style={{ border: \"none\", borderTop: \"1px solid rgba(0,0,0,0.1)\" }} />\n {discountCents > 0 && (\n <div style={{ display: \"flex\", justifyContent: \"space-between\" }}>\n <Text size=\"sm\">{t(\"discount\")}</Text>\n <Text size=\"sm\">−{formatCents(discountCents, settings.currency)}</Text>\n </div>\n )}\n <div style={{ display: \"flex\", justifyContent: \"space-between\" }}>\n <Text size=\"sm\">{t(\"shippingCosts\")}</Text>\n <Text size=\"sm\">{shippingCents === 0 ? t(\"free\") : formatCents(shippingCents, settings.currency)}</Text>\n </div>\n <div style={{ display: \"flex\", justifyContent: \"space-between\" }}>\n <Text weight=\"bold\">{t(\"total\")}</Text>\n <Text weight=\"bold\">{formatCents(totalCents, settings.currency)}</Text>\n </div>\n </div>\n </Card>\n </div>\n );\n}\n\nexport const checkoutBlock = defineBlock({\n type: \"checkout\",\n schema: CheckoutBlockSchema,\n Render: CheckoutRender,\n label: \"Afrekenen\",\n icon: \"💳\",\n defaults: () => ({ heading: \"Afrekenen\" }),\n});\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../../src/modules/shop/blocks/Checkout.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState, type FormEvent } from \"react\";\nimport type { z } from \"zod\";\nimport { formatCents, shippingCentsFor } from \"../../../schema/shopGeld\";\nimport type { CheckoutBlockSchema } from \"../../../schema/shop\";\nimport { defineBlock, type BlockRenderProps } from \"../../../blocks/registry\";\nimport { Card, Heading, Text } from \"../../../blocks/ui\";\nimport { TextInput } from \"@astryxdesign/core/TextInput\";\nimport { TextArea } from \"@astryxdesign/core/TextArea\";\nimport { cartLineKey, useCart } from \"../cartStore\";\nimport { useShopStrings } from \"../strings\";\nimport { createOrderCall, useShopSettings, validateDiscountCall, type DiscountPreview } from \"../lib\";\n\ntype Blk = z.infer<typeof CheckoutBlockSchema>;\n\n/**\n * Afrekenen — klantgegevens + overzicht + kortingscode; `createOrder` herrekent server-side\n * (incl. korting en personalisatie-meerprijs) en geeft de Stripe Checkout-URL terug. De cart\n * wordt pas geleegd op de bedankt-pagina, zodat een geannuleerde betaling niets kwijtraakt.\n * NB: verplichte velden dragen een \" *\" in het label (de Engelse Astryx-chip blijft uit).\n */\nfunction CheckoutRender({ block, siteId, mode }: BlockRenderProps<Blk>) {\n const settings = useShopSettings();\n const t = useShopStrings();\n const { lines, itemsCents } = useCart(siteId);\n const [v, setV] = useState({ name: \"\", email: \"\", phone: \"\", address: \"\", zip: \"\", city: \"\", note: \"\" });\n const [hp, setHp] = useState(\"\");\n const [busy, setBusy] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [codeInput, setCodeInput] = useState(\"\");\n const [codeBusy, setCodeBusy] = useState(false);\n const [discount, setDiscount] = useState<DiscountPreview | null>(null);\n\n const baseShipping = shippingCentsFor(\n itemsCents,\n { shippingFlatCents: settings.shippingFlatCents, shippingFreeFromCents: settings.shippingFreeFromCents || undefined },\n lines.length > 0,\n );\n const shippingCents = discount?.valid && discount.freeShipping ? 0 : baseShipping;\n const discountCents = discount?.valid ? Math.min(discount.discountCents ?? 0, itemsCents) : 0;\n const totalCents = Math.max(0, itemsCents - discountCents) + shippingCents;\n\n async function applyCode() {\n const code = codeInput.trim().toUpperCase();\n if (!code || codeBusy) return;\n setCodeBusy(true);\n try {\n const res = await validateDiscountCall(siteId, code, itemsCents);\n setDiscount(res.valid ? { ...res, code } : { valid: false });\n } catch {\n setDiscount({ valid: false });\n } finally {\n setCodeBusy(false);\n }\n }\n\n async function submit(e: FormEvent) {\n e.preventDefault();\n if (mode === \"preview\" || busy) return;\n setBusy(true);\n setError(null);\n try {\n const { url } = await createOrderCall({\n siteId,\n items: lines.map((l) => ({\n productId: l.productId,\n variantKey: l.variantKey,\n qty: l.qty,\n personalization: l.personalization,\n })),\n customer: { email: v.email, name: v.name, phone: v.phone || undefined },\n shipping: { address: v.address, zip: v.zip, city: v.city, country: \"NL\" },\n note: v.note || undefined,\n discountCode: discount?.valid ? discount.code : undefined,\n successPath: settings.successPath,\n cancelPath: settings.cartPath,\n _hp: hp || undefined,\n });\n window.location.assign(url);\n } catch (err) {\n console.error(\"[xicms] checkout mislukt:\", err);\n setError(t(\"checkoutFailed\"));\n setBusy(false);\n }\n }\n\n if (lines.length === 0) {\n return (\n <Card padding={4} variant=\"muted\">\n <Text>\n {t(\"cartEmpty\")} <a href={settings.shopPath}>{t(\"toShop\")}</a>\n </Text>\n </Card>\n );\n }\n\n return (\n <div className=\"xicms-shop-checkout\" style={{ display: \"grid\", gap: \"2rem\", gridTemplateColumns: \"repeat(auto-fit, minmax(min(320px, 100%), 1fr))\" }}>\n <form onSubmit={submit} style={{ display: \"grid\", gap: \"0.9rem\", alignContent: \"start\" }}>\n {block.heading && <Heading level={1} data-xicms-edit=\"heading\">{block.heading}</Heading>}\n {block.introText && <Text data-xicms-edit=\"introText\">{block.introText}</Text>}\n\n <TextInput label={`${t(\"fieldName\")} *`} value={v.name} onChange={(x) => setV((p) => ({ ...p, name: x }))} />\n <TextInput label={`${t(\"fieldEmail\")} *`} type=\"email\" value={v.email} onChange={(x) => setV((p) => ({ ...p, email: x }))} />\n <TextInput label={t(\"fieldPhone\")} value={v.phone} onChange={(x) => setV((p) => ({ ...p, phone: x }))} />\n <TextInput label={`${t(\"fieldAddress\")} *`} value={v.address} onChange={(x) => setV((p) => ({ ...p, address: x }))} />\n <div style={{ display: \"grid\", gap: \"0.9rem\", gridTemplateColumns: \"140px 1fr\" }}>\n <TextInput label={`${t(\"fieldZip\")} *`} value={v.zip} onChange={(x) => setV((p) => ({ ...p, zip: x }))} />\n <TextInput label={`${t(\"fieldCity\")} *`} value={v.city} onChange={(x) => setV((p) => ({ ...p, city: x }))} />\n </div>\n <TextArea label={t(\"fieldNote\")} rows={3} value={v.note} onChange={(x) => setV((p) => ({ ...p, note: x }))} />\n\n {/* Honeypot — verborgen voor mensen, bots vullen 'm vaak wel in. */}\n <input\n type=\"text\"\n tabIndex={-1}\n autoComplete=\"off\"\n value={hp}\n onChange={(e) => setHp(e.target.value)}\n style={{ position: \"absolute\", left: \"-9999px\", width: 1, height: 1 }}\n aria-hidden\n />\n\n {error && <Text role=\"alert\" style={{ color: \"var(--color-error, #c0392b)\" }}>{error}</Text>}\n\n <button\n type=\"submit\"\n className=\"xicms-btn xicms-btn--primary\"\n disabled={busy}\n style={{ cursor: busy ? \"wait\" : \"pointer\", width: \"fit-content\" }}\n >\n {busy ? t(\"busy\") : t(\"pay\", { bedrag: formatCents(totalCents, settings.currency) })}\n </button>\n <Text size=\"sm\" style={{ opacity: 0.7 }}>{t(\"secureNote\")}</Text>\n </form>\n\n <Card padding={3} variant=\"muted\" style={{ alignSelf: \"start\" }}>\n <Heading level={2}>{t(\"yourOrder\")}</Heading>\n <div style={{ display: \"grid\", gap: 8, marginTop: \"0.75rem\" }}>\n {lines.map((l) => (\n <div key={cartLineKey(l)} style={{ display: \"flex\", justifyContent: \"space-between\", gap: 12 }}>\n <Text size=\"sm\">\n {l.qty}× {l.name}\n {l.variantLabel ? ` (${l.variantLabel})` : \"\"}\n {l.personalizationLabel ? ` — ${l.personalizationLabel}` : \"\"}\n </Text>\n <Text size=\"sm\">{formatCents(l.unitPriceCents * l.qty, settings.currency)}</Text>\n </div>\n ))}\n\n {/* Kortingscode */}\n <div className=\"xicms-shop-checkout__discount\" style={{ display: \"flex\", gap: 8, marginTop: 4 }}>\n <input\n className=\"xicms-input\"\n type=\"text\"\n placeholder={t(\"discountCode\")}\n value={codeInput}\n onChange={(e) => setCodeInput(e.target.value.toUpperCase())}\n style={{ flex: 1, textTransform: \"uppercase\" }}\n />\n <button type=\"button\" className=\"xicms-btn xicms-btn--secondary\" disabled={codeBusy || !codeInput.trim()} onClick={applyCode}>\n {codeBusy ? t(\"busy\") : t(\"discountApply\")}\n </button>\n </div>\n {discount && !discount.valid && (\n <Text size=\"sm\" role=\"alert\" style={{ color: \"var(--color-error, #c0392b)\" }}>{t(\"discountInvalid\")}</Text>\n )}\n {discount?.valid && (\n <Text size=\"sm\" style={{ color: \"var(--xicms-primary, #4d7fb0)\", fontWeight: 600 }}>\n {t(\"discountApplied\", { code: discount.code ?? \"\" })}\n </Text>\n )}\n\n <hr style={{ border: \"none\", borderTop: \"1px solid rgba(0,0,0,0.1)\" }} />\n {discountCents > 0 && (\n <div style={{ display: \"flex\", justifyContent: \"space-between\" }}>\n <Text size=\"sm\">{t(\"discount\")}</Text>\n <Text size=\"sm\">−{formatCents(discountCents, settings.currency)}</Text>\n </div>\n )}\n <div style={{ display: \"flex\", justifyContent: \"space-between\" }}>\n <Text size=\"sm\">{t(\"shippingCosts\")}</Text>\n <Text size=\"sm\">{shippingCents === 0 ? t(\"free\") : formatCents(shippingCents, settings.currency)}</Text>\n </div>\n <div style={{ display: \"flex\", justifyContent: \"space-between\" }}>\n <Text weight=\"bold\">{t(\"total\")}</Text>\n <Text weight=\"bold\">{formatCents(totalCents, settings.currency)}</Text>\n </div>\n </div>\n </Card>\n </div>\n );\n}\n\nexport const checkoutBlock = defineBlock({\n type: \"checkout\",\n Render: CheckoutRender,\n label: \"Afrekenen\",\n icon: \"💳\",\n defaults: () => ({ heading: \"Afrekenen\" }),\n});\n"],"mappings":"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","names":[]}
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useState } from "react";
4
- import { DonationBlockSchema, formatCents } from "../../../schema/shop.js";
4
+ import { formatCents } from "../../../schema/shopGeld.js";
5
5
  import { defineBlock } from "../../../blocks/registry.js";
6
6
  import { Card, Heading, Text } from "../../../blocks/ui/index.js";
7
7
  import { TextInput } from "@astryxdesign/core/TextInput";
@@ -127,7 +127,6 @@ function DonationRender({ block, siteId, mode }) {
127
127
  }
128
128
  const donationBlock = defineBlock({
129
129
  type: "donation",
130
- schema: DonationBlockSchema,
131
130
  Render: DonationRender,
132
131
  label: "Donatie",
133
132
  icon: "\u{1F49B}",
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/modules/shop/blocks/Donation.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState, type FormEvent } from \"react\";\nimport type { z } from \"zod\";\nimport { DonationBlockSchema, formatCents } from \"../../../schema/shop\";\nimport { defineBlock, type BlockRenderProps } from \"../../../blocks/registry\";\nimport { Card, Heading, Text } from \"../../../blocks/ui\";\nimport { TextInput } from \"@astryxdesign/core/TextInput\";\nimport { createOrderCall, useShopSettings } from \"../lib\";\nimport { useShopStrings } from \"../strings\";\n\ntype Blk = z.infer<typeof DonationBlockSchema>;\n\n/**\n * Donatie — bedragknoppen + vrij bedrag, naam/e-mail, betaling via dezelfde `createOrder`-flow\n * (orderregel kind:\"donation\", geen verzendadres/verzendkosten). Het gekozen bedrag is de enige\n * client-invoer die de server accepteert — binnen de min/max van dit block.\n */\nfunction DonationRender({ block, siteId, mode }: BlockRenderProps<Blk>) {\n const settings = useShopSettings();\n const t = useShopStrings();\n const [amountCents, setAmountCents] = useState<number | null>(block.amountsCents[0] ?? null);\n const [customEuro, setCustomEuro] = useState(\"\");\n const [custom, setCustom] = useState(false);\n const [v, setV] = useState({ name: \"\", email: \"\" });\n const [hp, setHp] = useState(\"\");\n const [busy, setBusy] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n const effectiveCents = custom\n ? Math.round((Number(customEuro.replace(\",\", \".\")) || 0) * 100)\n : amountCents ?? 0;\n const valid =\n effectiveCents >= (custom ? block.customMinCents : 50) &&\n effectiveCents <= block.customMaxCents;\n\n async function submit(e: FormEvent) {\n e.preventDefault();\n if (mode === \"preview\" || busy || !valid) return;\n setBusy(true);\n setError(null);\n try {\n const { url } = await createOrderCall({\n siteId,\n items: [{ qty: 1, donationCents: effectiveCents }],\n customer: { email: v.email, name: v.name },\n successPath: settings.successPath,\n cancelPath: typeof window !== \"undefined\" ? window.location.pathname : \"/\",\n _hp: hp || undefined,\n });\n window.location.assign(url);\n } catch (err) {\n console.error(\"[xicms] donatie mislukt:\", err);\n setError(t(\"donationFailed\"));\n setBusy(false);\n }\n }\n\n return (\n <Card padding={4} className=\"xicms-shop-donation\">\n <form onSubmit={submit} style={{ display: \"grid\", gap: \"1rem\" }}>\n {block.heading && <Heading level={2} data-xicms-edit=\"heading\">{block.heading}</Heading>}\n {block.introText && <Text data-xicms-edit=\"introText\">{block.introText}</Text>}\n\n <div style={{ display: \"flex\", gap: 8, flexWrap: \"wrap\" }} role=\"group\" aria-label=\"Kies een bedrag\">\n {block.amountsCents.map((c) => {\n const active = !custom && amountCents === c;\n return (\n <button\n key={c}\n type=\"button\"\n className=\"xicms-btn\"\n aria-pressed={active}\n onClick={() => {\n setCustom(false);\n setAmountCents(c);\n }}\n style={{\n cursor: \"pointer\",\n border: active ? \"2px solid var(--xicms-primary, #333)\" : \"2px solid rgba(0,0,0,0.15)\",\n background: active ? \"var(--xicms-primary, #333)\" : \"transparent\",\n color: active ? \"var(--xicms-primary-contrast, #fff)\" : \"inherit\",\n borderRadius: 999,\n padding: \"0.5rem 1.1rem\",\n }}\n >\n {formatCents(c, settings.currency)}\n </button>\n );\n })}\n {block.allowCustom && (\n <button\n type=\"button\"\n className=\"xicms-btn\"\n aria-pressed={custom}\n onClick={() => setCustom(true)}\n style={{\n cursor: \"pointer\",\n border: custom ? \"2px solid var(--xicms-primary, #333)\" : \"2px solid rgba(0,0,0,0.15)\",\n background: \"transparent\",\n borderRadius: 999,\n padding: \"0.5rem 1.1rem\",\n }}\n >\n {t(\"donationCustomAmount\")}\n </button>\n )}\n </div>\n\n {custom && (\n <TextInput\n label={`${t(\"donationAmountLabel\", { min: formatCents(block.customMinCents, settings.currency) })} *`}\n placeholder=\"10,00\"\n value={customEuro}\n onChange={setCustomEuro}\n />\n )}\n\n <div style={{ display: \"grid\", gap: \"0.9rem\", gridTemplateColumns: \"repeat(auto-fit, minmax(min(220px, 100%), 1fr))\" }}>\n <TextInput label={`${t(\"fieldName\")} *`} value={v.name} onChange={(x) => setV((p) => ({ ...p, name: x }))} />\n <TextInput label={`${t(\"fieldEmail\")} *`} type=\"email\" value={v.email} onChange={(x) => setV((p) => ({ ...p, email: x }))} />\n </div>\n\n {/* Honeypot */}\n <input\n type=\"text\"\n tabIndex={-1}\n autoComplete=\"off\"\n value={hp}\n onChange={(e) => setHp(e.target.value)}\n style={{ position: \"absolute\", left: \"-9999px\", width: 1, height: 1 }}\n aria-hidden\n />\n\n {error && <Text role=\"alert\" style={{ color: \"var(--color-error, #c0392b)\" }}>{error}</Text>}\n\n <button\n type=\"submit\"\n className=\"xicms-btn xicms-btn--primary\"\n disabled={busy || !valid}\n style={{ cursor: busy ? \"wait\" : \"pointer\", width: \"fit-content\", opacity: valid ? 1 : 0.5 }}\n >\n {busy ? t(\"busy\") : `${block.buttonLabel} ${valid ? formatCents(effectiveCents, settings.currency) : \"\"}`}\n </button>\n </form>\n </Card>\n );\n}\n\nexport const donationBlock = defineBlock({\n type: \"donation\",\n schema: DonationBlockSchema,\n Render: DonationRender,\n label: \"Donatie\",\n icon: \"💛\",\n defaults: () => ({\n heading: \"Steun ons\",\n amountsCents: [500, 1000, 2500],\n allowCustom: true,\n customMinCents: 100,\n customMaxCents: 100_000,\n buttonLabel: \"Doneer\",\n }),\n});\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../../src/modules/shop/blocks/Donation.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState, type FormEvent } from \"react\";\nimport type { z } from \"zod\";\nimport { formatCents } from \"../../../schema/shopGeld\";\nimport type { DonationBlockSchema } from \"../../../schema/shop\";\nimport { defineBlock, type BlockRenderProps } from \"../../../blocks/registry\";\nimport { Card, Heading, Text } from \"../../../blocks/ui\";\nimport { TextInput } from \"@astryxdesign/core/TextInput\";\nimport { createOrderCall, useShopSettings } from \"../lib\";\nimport { useShopStrings } from \"../strings\";\n\ntype Blk = z.infer<typeof DonationBlockSchema>;\n\n/**\n * Donatie — bedragknoppen + vrij bedrag, naam/e-mail, betaling via dezelfde `createOrder`-flow\n * (orderregel kind:\"donation\", geen verzendadres/verzendkosten). Het gekozen bedrag is de enige\n * client-invoer die de server accepteert — binnen de min/max van dit block.\n */\nfunction DonationRender({ block, siteId, mode }: BlockRenderProps<Blk>) {\n const settings = useShopSettings();\n const t = useShopStrings();\n const [amountCents, setAmountCents] = useState<number | null>(block.amountsCents[0] ?? null);\n const [customEuro, setCustomEuro] = useState(\"\");\n const [custom, setCustom] = useState(false);\n const [v, setV] = useState({ name: \"\", email: \"\" });\n const [hp, setHp] = useState(\"\");\n const [busy, setBusy] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n const effectiveCents = custom\n ? Math.round((Number(customEuro.replace(\",\", \".\")) || 0) * 100)\n : amountCents ?? 0;\n const valid =\n effectiveCents >= (custom ? block.customMinCents : 50) &&\n effectiveCents <= block.customMaxCents;\n\n async function submit(e: FormEvent) {\n e.preventDefault();\n if (mode === \"preview\" || busy || !valid) return;\n setBusy(true);\n setError(null);\n try {\n const { url } = await createOrderCall({\n siteId,\n items: [{ qty: 1, donationCents: effectiveCents }],\n customer: { email: v.email, name: v.name },\n successPath: settings.successPath,\n cancelPath: typeof window !== \"undefined\" ? window.location.pathname : \"/\",\n _hp: hp || undefined,\n });\n window.location.assign(url);\n } catch (err) {\n console.error(\"[xicms] donatie mislukt:\", err);\n setError(t(\"donationFailed\"));\n setBusy(false);\n }\n }\n\n return (\n <Card padding={4} className=\"xicms-shop-donation\">\n <form onSubmit={submit} style={{ display: \"grid\", gap: \"1rem\" }}>\n {block.heading && <Heading level={2} data-xicms-edit=\"heading\">{block.heading}</Heading>}\n {block.introText && <Text data-xicms-edit=\"introText\">{block.introText}</Text>}\n\n <div style={{ display: \"flex\", gap: 8, flexWrap: \"wrap\" }} role=\"group\" aria-label=\"Kies een bedrag\">\n {block.amountsCents.map((c) => {\n const active = !custom && amountCents === c;\n return (\n <button\n key={c}\n type=\"button\"\n className=\"xicms-btn\"\n aria-pressed={active}\n onClick={() => {\n setCustom(false);\n setAmountCents(c);\n }}\n style={{\n cursor: \"pointer\",\n border: active ? \"2px solid var(--xicms-primary, #333)\" : \"2px solid rgba(0,0,0,0.15)\",\n background: active ? \"var(--xicms-primary, #333)\" : \"transparent\",\n color: active ? \"var(--xicms-primary-contrast, #fff)\" : \"inherit\",\n borderRadius: 999,\n padding: \"0.5rem 1.1rem\",\n }}\n >\n {formatCents(c, settings.currency)}\n </button>\n );\n })}\n {block.allowCustom && (\n <button\n type=\"button\"\n className=\"xicms-btn\"\n aria-pressed={custom}\n onClick={() => setCustom(true)}\n style={{\n cursor: \"pointer\",\n border: custom ? \"2px solid var(--xicms-primary, #333)\" : \"2px solid rgba(0,0,0,0.15)\",\n background: \"transparent\",\n borderRadius: 999,\n padding: \"0.5rem 1.1rem\",\n }}\n >\n {t(\"donationCustomAmount\")}\n </button>\n )}\n </div>\n\n {custom && (\n <TextInput\n label={`${t(\"donationAmountLabel\", { min: formatCents(block.customMinCents, settings.currency) })} *`}\n placeholder=\"10,00\"\n value={customEuro}\n onChange={setCustomEuro}\n />\n )}\n\n <div style={{ display: \"grid\", gap: \"0.9rem\", gridTemplateColumns: \"repeat(auto-fit, minmax(min(220px, 100%), 1fr))\" }}>\n <TextInput label={`${t(\"fieldName\")} *`} value={v.name} onChange={(x) => setV((p) => ({ ...p, name: x }))} />\n <TextInput label={`${t(\"fieldEmail\")} *`} type=\"email\" value={v.email} onChange={(x) => setV((p) => ({ ...p, email: x }))} />\n </div>\n\n {/* Honeypot */}\n <input\n type=\"text\"\n tabIndex={-1}\n autoComplete=\"off\"\n value={hp}\n onChange={(e) => setHp(e.target.value)}\n style={{ position: \"absolute\", left: \"-9999px\", width: 1, height: 1 }}\n aria-hidden\n />\n\n {error && <Text role=\"alert\" style={{ color: \"var(--color-error, #c0392b)\" }}>{error}</Text>}\n\n <button\n type=\"submit\"\n className=\"xicms-btn xicms-btn--primary\"\n disabled={busy || !valid}\n style={{ cursor: busy ? \"wait\" : \"pointer\", width: \"fit-content\", opacity: valid ? 1 : 0.5 }}\n >\n {busy ? t(\"busy\") : `${block.buttonLabel} ${valid ? formatCents(effectiveCents, settings.currency) : \"\"}`}\n </button>\n </form>\n </Card>\n );\n}\n\nexport const donationBlock = defineBlock({\n type: \"donation\",\n Render: DonationRender,\n label: \"Donatie\",\n icon: \"💛\",\n defaults: () => ({\n heading: \"Steun ons\",\n amountsCents: [500, 1000, 2500],\n allowCustom: true,\n customMinCents: 100,\n customMaxCents: 100_000,\n buttonLabel: \"Doneer\",\n }),\n});\n"],"mappings":"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","names":[]}
@@ -1,7 +1,6 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useEffect, useState } from "react";
4
- import { OrderStatusBlockSchema } from "../../../schema/shop.js";
5
4
  import { defineBlock } from "../../../blocks/registry.js";
6
5
  import { Card, Heading, Text } from "../../../blocks/ui/index.js";
7
6
  import { cartClear } from "../cartStore.js";
@@ -78,7 +77,6 @@ function OrderStatusRender({ block, siteId }) {
78
77
  }
79
78
  const orderStatusBlock = defineBlock({
80
79
  type: "orderStatus",
81
- schema: OrderStatusBlockSchema,
82
80
  Render: OrderStatusRender,
83
81
  label: "Bestelstatus (bedankt-pagina)",
84
82
  icon: "\u2705",
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/modules/shop/blocks/OrderStatus.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport type { z } from \"zod\";\nimport { OrderStatusBlockSchema } from \"../../../schema/shop\";\nimport { defineBlock, type BlockRenderProps } from \"../../../blocks/registry\";\nimport { Card, Heading, Text } from \"../../../blocks/ui\";\nimport { cartClear } from \"../cartStore\";\nimport { getOrderReturnStatusCall, readQueryParam, useShopSettings } from \"../lib\";\n\ntype Blk = z.infer<typeof OrderStatusBlockSchema>;\ntype View = \"loading\" | \"success\" | \"pending\" | \"cancelled\";\n\n/**\n * Bedankt-/statuspagina na terugkeer van de betaalprovider.\n * - Stripe geeft `?status=success|cancelled` mee (aparte success/cancel-URL's) → direct tonen.\n * - Mollie heeft één redirect-URL voor alle uitkomsten en geeft `?oid=` mee → we vragen de echte\n * orderstatus op via `getOrderReturnStatus` (de webhook is de bron van waarheid; kan een tel\n * nalopen, dus we pollen kort). De cart wordt geleegd zodra de betaling geslaagd/in behandeling is.\n */\nfunction OrderStatusRender({ block, siteId }: BlockRenderProps<Blk>) {\n const settings = useShopSettings();\n const [view, setView] = useState<View>(\"loading\");\n const [orderNumber, setOrderNumber] = useState<string | null>(null);\n\n useEffect(() => {\n const status = readQueryParam(\"status\");\n const oid = readQueryParam(\"oid\");\n setOrderNumber(readQueryParam(\"order\"));\n\n // Stripe-pad: status staat direct in de URL.\n if (status === \"success\") {\n cartClear(siteId);\n setView(\"success\");\n return;\n }\n if (status === \"cancelled\") {\n setView(\"cancelled\");\n return;\n }\n\n // Mollie-pad: echte status opvragen (kort pollen i.v.m. webhook-lag).\n if (!oid) {\n setView(\"success\"); // geen info → toon neutraal-positief\n return;\n }\n let cancelled = false;\n let tries = 0;\n const map = (s: string): View => (s === \"paid\" || s === \"shipped\" ? \"success\" : s === \"cancelled\" || s === \"refunded\" ? \"cancelled\" : \"pending\");\n const poll = async () => {\n tries++;\n try {\n const res = await getOrderReturnStatusCall(siteId, oid);\n if (cancelled) return;\n if (res.number) setOrderNumber(res.number);\n const v = map(res.status);\n if (v === \"pending\" && tries < 4) {\n setView(\"pending\");\n setTimeout(poll, 1800);\n return;\n }\n if (v === \"success\") cartClear(siteId);\n setView(v);\n } catch {\n if (!cancelled) setView(\"pending\");\n }\n };\n void poll();\n return () => {\n cancelled = true;\n };\n }, [siteId]);\n\n if (view === \"loading\" || view === \"pending\") {\n return (\n <Card padding={4} variant=\"muted\" className=\"xicms-shop-orderstatus\" role=\"status\">\n <Heading level={1}>{view === \"pending\" ? \"Je betaling wordt verwerkt…\" : \"Even geduld…\"}</Heading>\n <Text display=\"block\" style={{ marginTop: 8 }}>\n We bevestigen je bestelling zodra de betaling binnen is — je ontvangt daarvan een e-mail.\n </Text>\n {orderNumber && (\n <Text display=\"block\" style={{ marginTop: 8 }}>\n Je bestelnummer is <strong>{orderNumber}</strong>.\n </Text>\n )}\n </Card>\n );\n }\n\n const cancelled = view === \"cancelled\";\n return (\n <Card padding={4} variant={cancelled ? \"muted\" : \"green\"} className=\"xicms-shop-orderstatus\" role=\"status\">\n <Heading level={1} data-xicms-edit={cancelled ? \"cancelledTitle\" : \"successTitle\"}>\n {cancelled ? block.cancelledTitle : block.successTitle}\n </Heading>\n <Text display=\"block\" style={{ marginTop: 8 }} data-xicms-edit={cancelled ? \"cancelledText\" : \"successText\"}>\n {cancelled ? block.cancelledText : block.successText}\n </Text>\n {!cancelled && orderNumber && (\n <Text display=\"block\" style={{ marginTop: 8 }}>\n Je bestelnummer is <strong>{orderNumber}</strong>.\n </Text>\n )}\n <Text display=\"block\" style={{ marginTop: 12 }}>\n <a href={cancelled ? settings.cartPath : settings.shopPath}>\n {cancelled ? \"Terug naar je winkelwagen\" : \"Verder winkelen\"}\n </a>\n </Text>\n </Card>\n );\n}\n\nexport const orderStatusBlock = defineBlock({\n type: \"orderStatus\",\n schema: OrderStatusBlockSchema,\n Render: OrderStatusRender,\n label: \"Bestelstatus (bedankt-pagina)\",\n icon: \"✅\",\n defaults: () => ({}),\n});\n"],"mappings":"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","names":["cancelled"]}
1
+ {"version":3,"sources":["../../../../src/modules/shop/blocks/OrderStatus.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport type { z } from \"zod\";\nimport type { OrderStatusBlockSchema } from \"../../../schema/shop\";\nimport { defineBlock, type BlockRenderProps } from \"../../../blocks/registry\";\nimport { Card, Heading, Text } from \"../../../blocks/ui\";\nimport { cartClear } from \"../cartStore\";\nimport { getOrderReturnStatusCall, readQueryParam, useShopSettings } from \"../lib\";\n\ntype Blk = z.infer<typeof OrderStatusBlockSchema>;\ntype View = \"loading\" | \"success\" | \"pending\" | \"cancelled\";\n\n/**\n * Bedankt-/statuspagina na terugkeer van de betaalprovider.\n * - Stripe geeft `?status=success|cancelled` mee (aparte success/cancel-URL's) → direct tonen.\n * - Mollie heeft één redirect-URL voor alle uitkomsten en geeft `?oid=` mee → we vragen de echte\n * orderstatus op via `getOrderReturnStatus` (de webhook is de bron van waarheid; kan een tel\n * nalopen, dus we pollen kort). De cart wordt geleegd zodra de betaling geslaagd/in behandeling is.\n */\nfunction OrderStatusRender({ block, siteId }: BlockRenderProps<Blk>) {\n const settings = useShopSettings();\n const [view, setView] = useState<View>(\"loading\");\n const [orderNumber, setOrderNumber] = useState<string | null>(null);\n\n useEffect(() => {\n const status = readQueryParam(\"status\");\n const oid = readQueryParam(\"oid\");\n setOrderNumber(readQueryParam(\"order\"));\n\n // Stripe-pad: status staat direct in de URL.\n if (status === \"success\") {\n cartClear(siteId);\n setView(\"success\");\n return;\n }\n if (status === \"cancelled\") {\n setView(\"cancelled\");\n return;\n }\n\n // Mollie-pad: echte status opvragen (kort pollen i.v.m. webhook-lag).\n if (!oid) {\n setView(\"success\"); // geen info → toon neutraal-positief\n return;\n }\n let cancelled = false;\n let tries = 0;\n const map = (s: string): View => (s === \"paid\" || s === \"shipped\" ? \"success\" : s === \"cancelled\" || s === \"refunded\" ? \"cancelled\" : \"pending\");\n const poll = async () => {\n tries++;\n try {\n const res = await getOrderReturnStatusCall(siteId, oid);\n if (cancelled) return;\n if (res.number) setOrderNumber(res.number);\n const v = map(res.status);\n if (v === \"pending\" && tries < 4) {\n setView(\"pending\");\n setTimeout(poll, 1800);\n return;\n }\n if (v === \"success\") cartClear(siteId);\n setView(v);\n } catch {\n if (!cancelled) setView(\"pending\");\n }\n };\n void poll();\n return () => {\n cancelled = true;\n };\n }, [siteId]);\n\n if (view === \"loading\" || view === \"pending\") {\n return (\n <Card padding={4} variant=\"muted\" className=\"xicms-shop-orderstatus\" role=\"status\">\n <Heading level={1}>{view === \"pending\" ? \"Je betaling wordt verwerkt…\" : \"Even geduld…\"}</Heading>\n <Text display=\"block\" style={{ marginTop: 8 }}>\n We bevestigen je bestelling zodra de betaling binnen is — je ontvangt daarvan een e-mail.\n </Text>\n {orderNumber && (\n <Text display=\"block\" style={{ marginTop: 8 }}>\n Je bestelnummer is <strong>{orderNumber}</strong>.\n </Text>\n )}\n </Card>\n );\n }\n\n const cancelled = view === \"cancelled\";\n return (\n <Card padding={4} variant={cancelled ? \"muted\" : \"green\"} className=\"xicms-shop-orderstatus\" role=\"status\">\n <Heading level={1} data-xicms-edit={cancelled ? \"cancelledTitle\" : \"successTitle\"}>\n {cancelled ? block.cancelledTitle : block.successTitle}\n </Heading>\n <Text display=\"block\" style={{ marginTop: 8 }} data-xicms-edit={cancelled ? \"cancelledText\" : \"successText\"}>\n {cancelled ? block.cancelledText : block.successText}\n </Text>\n {!cancelled && orderNumber && (\n <Text display=\"block\" style={{ marginTop: 8 }}>\n Je bestelnummer is <strong>{orderNumber}</strong>.\n </Text>\n )}\n <Text display=\"block\" style={{ marginTop: 12 }}>\n <a href={cancelled ? settings.cartPath : settings.shopPath}>\n {cancelled ? \"Terug naar je winkelwagen\" : \"Verder winkelen\"}\n </a>\n </Text>\n </Card>\n );\n}\n\nexport const orderStatusBlock = defineBlock({\n type: \"orderStatus\",\n Render: OrderStatusRender,\n label: \"Bestelstatus (bedankt-pagina)\",\n icon: \"✅\",\n defaults: () => ({}),\n});\n"],"mappings":"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","names":["cancelled"]}
@@ -1,12 +1,7 @@
1
1
  "use client";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useEffect, useMemo, useState } from "react";
4
- import {
5
- ProductDetailBlockSchema,
6
- formatCents,
7
- productPriceCents,
8
- variantKeyFor
9
- } from "../../../schema/shop.js";
4
+ import { formatCents, productPriceCents, variantKeyFor } from "../../../schema/shopGeld.js";
10
5
  import { defineBlock } from "../../../blocks/registry.js";
11
6
  import { useBlockData } from "../../../blocks/context.js";
12
7
  import { Card, Heading, Text } from "../../../blocks/ui/index.js";
@@ -353,7 +348,6 @@ function ProductDetailRender({ block, siteId }) {
353
348
  }
354
349
  const productDetailBlock = defineBlock({
355
350
  type: "productDetail",
356
- schema: ProductDetailBlockSchema,
357
351
  Render: ProductDetailRender,
358
352
  label: "Productdetail",
359
353
  icon: "\u{1F3F7}\uFE0F",
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/modules/shop/blocks/ProductDetail.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useMemo, useState } from \"react\";\nimport type { z } from \"zod\";\nimport {\n ProductDetailBlockSchema,\n formatCents,\n productPriceCents,\n variantKeyFor,\n type PersonalizationField,\n} from \"../../../schema/shop\";\nimport { defineBlock, type BlockRenderProps } from \"../../../blocks/registry\";\nimport { useBlockData } from \"../../../blocks/context\";\nimport { Card, Heading, Text } from \"../../../blocks/ui\";\nimport { Selector } from \"@astryxdesign/core/Selector\";\nimport { cartAdd } from \"../cartStore\";\nimport { useShopStrings } from \"../strings\";\nimport {\n fetchActiveProducts,\n fetchProductBySlug,\n kleurOptie,\n readQueryParam,\n uploadPersonalizationCall,\n useShopSettings,\n variantLabelFor,\n type StoredProduct,\n} from \"../lib\";\nimport type { ShopPageData } from \"../types\";\nimport { ProductCard } from \"./ProductGrid\";\n\ntype Blk = z.infer<typeof ProductDetailBlockSchema>;\ntype PersValue = Record<string, { text?: string; imagePath?: string; fileName?: string }>;\n\n/** Personalisatie-invoer (eigen tekst of afbeelding) onder de optie-selects. */\nfunction PersonalizationInputs({\n fields,\n siteId,\n productId,\n value,\n currency,\n onChange,\n}: {\n fields: PersonalizationField[];\n siteId: string;\n productId: string;\n value: PersValue;\n currency: string;\n onChange: (next: PersValue) => void;\n}) {\n const t = useShopStrings();\n const [uploading, setUploading] = useState<string | null>(null);\n const [error, setError] = useState<string | null>(null);\n\n return (\n <div className=\"xicms-shop-personalization\" style={{ display: \"grid\", gap: \"0.75rem\" }}>\n {fields.map((f) => {\n const v = value[f.key] ?? {};\n const label = (\n <span>\n {f.label}\n {f.required ? \" *\" : \"\"}\n {f.priceCents > 0 && (\n <span style={{ opacity: 0.7, marginLeft: 6, fontSize: \"0.9em\" }}>\n {t(\"personalizationSurcharge\", { bedrag: formatCents(f.priceCents, currency) })}\n </span>\n )}\n </span>\n );\n if (f.type === \"text\") {\n return (\n <label key={f.key} style={{ display: \"grid\", gap: 4 }}>\n <span style={{ fontSize: \"0.9rem\" }}>{label}</span>\n <input\n className=\"xicms-input\"\n type=\"text\"\n maxLength={f.maxLength}\n value={v.text ?? \"\"}\n onChange={(e) => onChange({ ...value, [f.key]: { text: e.target.value } })}\n />\n <span style={{ fontSize: \"0.75rem\", opacity: 0.6 }}>\n {f.helpText ? `${f.helpText} · ` : \"\"}\n {(v.text ?? \"\").length}/{f.maxLength}\n </span>\n </label>\n );\n }\n return (\n <div key={f.key} style={{ display: \"grid\", gap: 4 }}>\n <span style={{ fontSize: \"0.9rem\" }}>{label}</span>\n <div style={{ display: \"flex\", alignItems: \"center\", gap: 10, flexWrap: \"wrap\" }}>\n <label className=\"xicms-btn xicms-btn--secondary\" style={{ cursor: \"pointer\" }}>\n {uploading === f.key\n ? t(\"personalizationUploading\")\n : v.imagePath\n ? t(\"personalizationUploaded\")\n : t(\"personalizationUpload\")}\n <input\n type=\"file\"\n accept=\"image/jpeg,image/png,image/webp\"\n style={{ display: \"none\" }}\n onChange={async (e) => {\n const file = e.target.files?.[0];\n if (!file) return;\n setError(null);\n setUploading(f.key);\n try {\n const { path } = await uploadPersonalizationCall(siteId, productId, file);\n onChange({ ...value, [f.key]: { imagePath: path, fileName: file.name } });\n } catch (err) {\n setError(err instanceof Error && err.message === \"TOO_BIG\" ? t(\"personalizationTooBig\") : t(\"personalizationFailed\"));\n } finally {\n setUploading(null);\n }\n }}\n />\n </label>\n {v.fileName && <span style={{ fontSize: \"0.85rem\", opacity: 0.75 }}>{v.fileName}</span>}\n </div>\n {f.helpText && <span style={{ fontSize: \"0.75rem\", opacity: 0.6 }}>{f.helpText}</span>}\n </div>\n );\n })}\n {error && <Text role=\"alert\" style={{ color: \"var(--color-error, #c0392b)\" }}>{error}</Text>}\n </div>\n );\n}\n\n/**\n * Productdetail — één generieke pagina met dit block toont elk product via `?p=<slug>`\n * (of een vast product via `block.slug`). Optie-selects met per-kleur-beeldwissel,\n * personalisatie (eigen naam/logo), voorraad-signalen en \"Ook leuk\"-rij.\n */\nfunction ProductDetailRender({ block, siteId }: BlockRenderProps<Blk>) {\n const settings = useShopSettings();\n const t = useShopStrings();\n const serverData = useBlockData<ShopPageData>(\"shop\");\n const [product, setProduct] = useState<StoredProduct | null | undefined>(\n serverData && \"product\" in serverData ? serverData.product : undefined,\n );\n const [related, setRelated] = useState<StoredProduct[]>([]);\n const [choices, setChoices] = useState<Record<number, string>>({});\n const [personalization, setPersonalization] = useState<PersValue>({});\n const [qty, setQty] = useState(1);\n const [manualImage, setManualImage] = useState<string | null>(null);\n const [added, setAdded] = useState(false);\n\n useEffect(() => {\n if (serverData && \"product\" in serverData) return; // SSR-data aanwezig\n const slug = block.slug || readQueryParam(\"p\") || \"\";\n if (!slug) {\n setProduct(null);\n return;\n }\n void fetchProductBySlug(siteId, slug).then(setProduct).catch((e) => {\n console.warn(\"[xicms] product laden mislukt:\", e);\n setProduct(null);\n });\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [siteId, block.slug]);\n\n // \"Ook leuk\": producten uit dezelfde categorie (server-set hergebruiken waar mogelijk).\n useEffect(() => {\n if (!block.showRelated || !product) return;\n const pick = (all: StoredProduct[]) =>\n setRelated(\n all\n .filter((x) => x.id !== product.id && x.categoryIds?.some((c) => product.categoryIds?.includes(c)))\n .slice(0, 4),\n );\n if (serverData?.products) pick(serverData.products);\n else void fetchActiveProducts(siteId).then(pick).catch(() => setRelated([]));\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [product?.id, block.showRelated, siteId]);\n\n const options = product?.options ?? [];\n const persFields = product?.personalization ?? [];\n const allChosen = options.every((_, i) => Boolean(choices[i]));\n const variantKey = product && options.length > 0 && allChosen\n ? variantKeyFor(options.map((_, i) => choices[i]))\n : undefined;\n const variant = variantKey ? product?.variants?.find((v) => v.key === variantKey) : undefined;\n\n /** Meerprijs van ingevulde personalisatie (per stuk). */\n const persSurcharge = useMemo(\n () =>\n persFields.reduce((sum, f) => {\n const v = personalization[f.key];\n return v && (v.text || v.imagePath) ? sum + f.priceCents : sum;\n }, 0),\n [persFields, personalization],\n );\n\n const priceCents = useMemo(() => {\n if (!product) return 0;\n return productPriceCents(product, variantKey) + persSurcharge;\n }, [product, variantKey, persSurcharge]);\n\n /** Hoofdbeeld: handmatige thumbnail-keuze > per-kleur-afbeelding > eerste productfoto. */\n const kleur = product ? kleurOptie(product) : null;\n const kleurIdx = kleur && product ? (product.options ?? []).findIndex((o) => o === kleur) : -1;\n const gekozenKleur = kleurIdx >= 0 ? choices[kleurIdx] : undefined;\n const kleurImage = gekozenKleur ? kleur?.images?.[gekozenKleur] : undefined;\n const mainImage = manualImage ?? kleurImage ?? product?.images?.[0];\n\n /** Alle toonbare afbeeldingen: productfoto's + unieke per-kleur-afbeeldingen. */\n const gallery = useMemo(() => {\n if (!product) return [] as { url: string; label?: string }[];\n const out: { url: string; label?: string }[] = (product.images ?? []).map((url) => ({ url }));\n for (const [value, url] of Object.entries(kleur?.images ?? {})) {\n if (!out.some((g) => g.url === url)) out.push({ url, label: value });\n }\n return out;\n }, [product, kleur]);\n\n const stockValue = options.length === 0 ? product?.stock : variant?.stock;\n const outOfStock = Boolean(product?.trackStock && stockValue !== undefined && stockValue <= 0);\n const lowStock = Boolean(product?.trackStock && stockValue !== undefined && stockValue > 0 && stockValue <= 5);\n const variantInactive = variant !== undefined && variant.active === false;\n\n if (product === undefined) return null;\n if (product === null) {\n return (\n <Card padding={4} variant=\"muted\">\n <Text>\n {t(\"productNotFound\")} <a href={settings.shopPath}>{t(\"backToShop\")}</a>\n </Text>\n </Card>\n );\n }\n\n const persComplete = persFields.every((f) => {\n if (!f.required) return true;\n const v = personalization[f.key];\n return Boolean(v && (v.text?.trim() || v.imagePath));\n });\n\n function addToCart() {\n if (!product) return;\n const filled: PersValue = {};\n for (const f of persFields) {\n const v = personalization[f.key];\n if (v && (v.text?.trim() || v.imagePath)) filled[f.key] = v;\n }\n const persLabel = persFields\n .filter((f) => filled[f.key])\n .map((f) => `${f.label}: ${filled[f.key].text ?? filled[f.key].fileName ?? \"✓\"}`)\n .join(\" · \");\n cartAdd(\n siteId,\n {\n productId: product.id,\n slug: product.slug,\n name: product.name,\n variantKey,\n variantLabel: variantKey ? variantLabelFor(options, variantKey) : undefined,\n unitPriceCents: priceCents,\n imageUrl: mainImage ?? product.images?.[0],\n personalization: Object.keys(filled).length > 0 ? filled : undefined,\n personalizationLabel: persLabel || undefined,\n },\n qty,\n );\n setAdded(true);\n }\n\n const canOrder = (options.length === 0 || allChosen) && !outOfStock && !variantInactive && persComplete;\n\n return (\n <>\n <div\n className=\"xicms-shop-detail\"\n style={{ display: \"grid\", gap: \"2rem\", gridTemplateColumns: \"repeat(auto-fit, minmax(min(320px, 100%), 1fr))\" }}\n >\n <div className=\"xicms-shop-detail__media\">\n {mainImage ? (\n <img\n src={mainImage}\n alt={product.name}\n style={{ width: \"100%\", aspectRatio: \"1/1\", objectFit: \"cover\", borderRadius: \"var(--xicms-radius, 12px)\" }}\n />\n ) : (\n <div\n className=\"xicms-shop-detail__placeholder\"\n aria-hidden=\"true\"\n style={{ width: \"100%\", aspectRatio: \"1/1\", background: \"var(--xicms-muted, #f1f1f1)\", borderRadius: \"var(--xicms-radius, 12px)\" }}\n />\n )}\n {gallery.length > 1 && (\n <div style={{ display: \"flex\", gap: 8, marginTop: 8, flexWrap: \"wrap\" }}>\n {gallery.map((g) => (\n <button\n key={g.url}\n type=\"button\"\n title={g.label}\n onClick={() => setManualImage(g.url)}\n style={{\n padding: 0,\n border: g.url === mainImage ? \"2px solid var(--xicms-primary, #333)\" : \"2px solid transparent\",\n borderRadius: 8,\n cursor: \"pointer\",\n background: \"none\",\n }}\n >\n <img src={g.url} alt={g.label ?? \"\"} style={{ width: 64, height: 64, objectFit: \"cover\", borderRadius: 6, display: \"block\" }} />\n </button>\n ))}\n </div>\n )}\n </div>\n\n {/*\n Haken voor de bewerkmodus. De tekst van een product staat NIET in dit blok — het blok wijst\n alleen een product aan (`slug`) en haalt de rest uit de productencollectie. Zonder deze\n haken kon de editor daarom niets: het tandwiel opende een paneel met twee instellingen en\n het potlood verscheen helemaal niet, want er viel in dit blok niets te bewerken. Terecht,\n en toch precies verkeerd — je stond naar een productnaam te kijken die je niet kon wijzigen.\n `data-xicms-product` zegt om welk product het gaat, `data-xicms-product-edit` welk veld\n eronder ligt; de editor schrijft die rechtstreeks naar het productdocument.\n */}\n <div className=\"xicms-shop-detail__info\" data-xicms-product={product.id}>\n <Heading level={1} data-xicms-product-edit=\"name\">{product.name}</Heading>\n <p className=\"xicms-shop-detail__price\" style={{ fontSize: \"1.35rem\", fontWeight: 700, margin: \"0.5rem 0 1rem\" }}>\n {product.compareAtCents !== undefined && product.compareAtCents > priceCents && (\n <s style={{ opacity: 0.6, marginRight: 8, fontWeight: \"normal\" }}>\n {formatCents(product.compareAtCents, settings.currency)}\n </s>\n )}\n {formatCents(priceCents, settings.currency)}\n </p>\n\n {product.description && (\n <p className=\"xicms-shop-detail__desc\" data-xicms-product-edit=\"description\" style={{ whiteSpace: \"pre-wrap\", margin: 0 }}>{product.description}</p>\n )}\n\n <div style={{ display: \"grid\", gap: \"0.75rem\", maxWidth: 420, marginTop: \"1rem\" }}>\n {options.map((opt, i) => (\n <Selector\n key={opt.name}\n label={opt.name}\n placeholder={t(\"choose\")}\n options={opt.values}\n value={choices[i] ?? \"\"}\n onChange={(v) => {\n setChoices((prev) => ({ ...prev, [i]: (v as string) ?? \"\" }));\n setManualImage(null);\n setAdded(false);\n }}\n />\n ))}\n\n {persFields.length > 0 && (\n <PersonalizationInputs\n fields={persFields}\n siteId={siteId}\n productId={product.id}\n value={personalization}\n currency={settings.currency}\n onChange={(v) => {\n setPersonalization(v);\n setAdded(false);\n }}\n />\n )}\n\n <label className=\"xicms-shop-detail__qty\" style={{ display: \"grid\", gap: 4 }}>\n <span style={{ fontSize: \"0.9rem\" }}>{t(\"qty\")}</span>\n <input\n className=\"xicms-input\"\n type=\"number\"\n min={1}\n max={999}\n value={qty}\n onChange={(e) => setQty(Math.max(1, Math.min(999, Number(e.target.value) || 1)))}\n style={{ width: 90 }}\n />\n </label>\n\n {outOfStock && <Text style={{ color: \"var(--color-error, #c0392b)\" }}>{t(\"outOfStock\")}</Text>}\n {variantInactive && <Text style={{ color: \"var(--color-error, #c0392b)\" }}>{t(\"variantUnavailable\")}</Text>}\n {lowStock && !outOfStock && (\n <Text style={{ color: \"var(--xicms-primary, #4d7fb0)\", fontWeight: 600 }}>\n {t(\"stockLeft\", { n: stockValue as number })}\n </Text>\n )}\n\n {added ? (\n <div role=\"status\" style={{ display: \"grid\", gap: 8 }}>\n <Text>{t(\"added\")}</Text>\n <div style={{ display: \"flex\", gap: 12, flexWrap: \"wrap\" }}>\n <a className=\"xicms-btn xicms-btn--primary\" href={settings.cartPath}>{t(\"toCart\")}</a>\n <a className=\"xicms-btn xicms-btn--secondary\" href={settings.shopPath}>{t(\"continueShopping\")}</a>\n </div>\n </div>\n ) : (\n <button\n type=\"button\"\n className=\"xicms-btn xicms-btn--primary\"\n disabled={!canOrder}\n onClick={addToCart}\n style={{ cursor: canOrder ? \"pointer\" : \"not-allowed\", opacity: canOrder ? 1 : 0.5, width: \"fit-content\" }}\n >\n {t(\"addToCart\")}\n </button>\n )}\n </div>\n </div>\n </div>\n\n {block.showRelated && related.length > 0 && (\n <div className=\"xicms-shop-related\" style={{ marginTop: \"3rem\" }}>\n <Heading level={2}>{t(\"related\")}</Heading>\n <div\n className=\"xicms-shop-grid__items\"\n style={{\n display: \"grid\",\n gridTemplateColumns: \"repeat(auto-fill, minmax(min(200px, calc(50% - 1rem)), 1fr))\",\n gap: \"1.25rem\",\n marginTop: \"1rem\",\n }}\n >\n {related.map((p) => (\n <ProductCard\n key={p.id}\n product={p}\n siteId={siteId}\n currency={settings.currency}\n detailPath={settings.detailPath}\n cartPath={settings.cartPath}\n t={t}\n />\n ))}\n </div>\n </div>\n )}\n </>\n );\n}\n\nexport const productDetailBlock = defineBlock({\n type: \"productDetail\",\n schema: ProductDetailBlockSchema,\n Render: ProductDetailRender,\n label: \"Productdetail\",\n icon: \"🏷️\",\n defaults: () => ({ showRelated: true }),\n});\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../../src/modules/shop/blocks/ProductDetail.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useMemo, useState } from \"react\";\nimport type { z } from \"zod\";\nimport { formatCents, productPriceCents, variantKeyFor } from \"../../../schema/shopGeld\";\nimport type { ProductDetailBlockSchema, PersonalizationField } from \"../../../schema/shop\";\nimport { defineBlock, type BlockRenderProps } from \"../../../blocks/registry\";\nimport { useBlockData } from \"../../../blocks/context\";\nimport { Card, Heading, Text } from \"../../../blocks/ui\";\nimport { Selector } from \"@astryxdesign/core/Selector\";\nimport { cartAdd } from \"../cartStore\";\nimport { useShopStrings } from \"../strings\";\nimport {\n fetchActiveProducts,\n fetchProductBySlug,\n kleurOptie,\n readQueryParam,\n uploadPersonalizationCall,\n useShopSettings,\n variantLabelFor,\n type StoredProduct,\n} from \"../lib\";\nimport type { ShopPageData } from \"../types\";\nimport { ProductCard } from \"./ProductGrid\";\n\ntype Blk = z.infer<typeof ProductDetailBlockSchema>;\ntype PersValue = Record<string, { text?: string; imagePath?: string; fileName?: string }>;\n\n/** Personalisatie-invoer (eigen tekst of afbeelding) onder de optie-selects. */\nfunction PersonalizationInputs({\n fields,\n siteId,\n productId,\n value,\n currency,\n onChange,\n}: {\n fields: PersonalizationField[];\n siteId: string;\n productId: string;\n value: PersValue;\n currency: string;\n onChange: (next: PersValue) => void;\n}) {\n const t = useShopStrings();\n const [uploading, setUploading] = useState<string | null>(null);\n const [error, setError] = useState<string | null>(null);\n\n return (\n <div className=\"xicms-shop-personalization\" style={{ display: \"grid\", gap: \"0.75rem\" }}>\n {fields.map((f) => {\n const v = value[f.key] ?? {};\n const label = (\n <span>\n {f.label}\n {f.required ? \" *\" : \"\"}\n {f.priceCents > 0 && (\n <span style={{ opacity: 0.7, marginLeft: 6, fontSize: \"0.9em\" }}>\n {t(\"personalizationSurcharge\", { bedrag: formatCents(f.priceCents, currency) })}\n </span>\n )}\n </span>\n );\n if (f.type === \"text\") {\n return (\n <label key={f.key} style={{ display: \"grid\", gap: 4 }}>\n <span style={{ fontSize: \"0.9rem\" }}>{label}</span>\n <input\n className=\"xicms-input\"\n type=\"text\"\n maxLength={f.maxLength}\n value={v.text ?? \"\"}\n onChange={(e) => onChange({ ...value, [f.key]: { text: e.target.value } })}\n />\n <span style={{ fontSize: \"0.75rem\", opacity: 0.6 }}>\n {f.helpText ? `${f.helpText} · ` : \"\"}\n {(v.text ?? \"\").length}/{f.maxLength}\n </span>\n </label>\n );\n }\n return (\n <div key={f.key} style={{ display: \"grid\", gap: 4 }}>\n <span style={{ fontSize: \"0.9rem\" }}>{label}</span>\n <div style={{ display: \"flex\", alignItems: \"center\", gap: 10, flexWrap: \"wrap\" }}>\n <label className=\"xicms-btn xicms-btn--secondary\" style={{ cursor: \"pointer\" }}>\n {uploading === f.key\n ? t(\"personalizationUploading\")\n : v.imagePath\n ? t(\"personalizationUploaded\")\n : t(\"personalizationUpload\")}\n <input\n type=\"file\"\n accept=\"image/jpeg,image/png,image/webp\"\n style={{ display: \"none\" }}\n onChange={async (e) => {\n const file = e.target.files?.[0];\n if (!file) return;\n setError(null);\n setUploading(f.key);\n try {\n const { path } = await uploadPersonalizationCall(siteId, productId, file);\n onChange({ ...value, [f.key]: { imagePath: path, fileName: file.name } });\n } catch (err) {\n setError(err instanceof Error && err.message === \"TOO_BIG\" ? t(\"personalizationTooBig\") : t(\"personalizationFailed\"));\n } finally {\n setUploading(null);\n }\n }}\n />\n </label>\n {v.fileName && <span style={{ fontSize: \"0.85rem\", opacity: 0.75 }}>{v.fileName}</span>}\n </div>\n {f.helpText && <span style={{ fontSize: \"0.75rem\", opacity: 0.6 }}>{f.helpText}</span>}\n </div>\n );\n })}\n {error && <Text role=\"alert\" style={{ color: \"var(--color-error, #c0392b)\" }}>{error}</Text>}\n </div>\n );\n}\n\n/**\n * Productdetail — één generieke pagina met dit block toont elk product via `?p=<slug>`\n * (of een vast product via `block.slug`). Optie-selects met per-kleur-beeldwissel,\n * personalisatie (eigen naam/logo), voorraad-signalen en \"Ook leuk\"-rij.\n */\nfunction ProductDetailRender({ block, siteId }: BlockRenderProps<Blk>) {\n const settings = useShopSettings();\n const t = useShopStrings();\n const serverData = useBlockData<ShopPageData>(\"shop\");\n const [product, setProduct] = useState<StoredProduct | null | undefined>(\n serverData && \"product\" in serverData ? serverData.product : undefined,\n );\n const [related, setRelated] = useState<StoredProduct[]>([]);\n const [choices, setChoices] = useState<Record<number, string>>({});\n const [personalization, setPersonalization] = useState<PersValue>({});\n const [qty, setQty] = useState(1);\n const [manualImage, setManualImage] = useState<string | null>(null);\n const [added, setAdded] = useState(false);\n\n useEffect(() => {\n if (serverData && \"product\" in serverData) return; // SSR-data aanwezig\n const slug = block.slug || readQueryParam(\"p\") || \"\";\n if (!slug) {\n setProduct(null);\n return;\n }\n void fetchProductBySlug(siteId, slug).then(setProduct).catch((e) => {\n console.warn(\"[xicms] product laden mislukt:\", e);\n setProduct(null);\n });\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [siteId, block.slug]);\n\n // \"Ook leuk\": producten uit dezelfde categorie (server-set hergebruiken waar mogelijk).\n useEffect(() => {\n if (!block.showRelated || !product) return;\n const pick = (all: StoredProduct[]) =>\n setRelated(\n all\n .filter((x) => x.id !== product.id && x.categoryIds?.some((c) => product.categoryIds?.includes(c)))\n .slice(0, 4),\n );\n if (serverData?.products) pick(serverData.products);\n else void fetchActiveProducts(siteId).then(pick).catch(() => setRelated([]));\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [product?.id, block.showRelated, siteId]);\n\n const options = product?.options ?? [];\n const persFields = product?.personalization ?? [];\n const allChosen = options.every((_, i) => Boolean(choices[i]));\n const variantKey = product && options.length > 0 && allChosen\n ? variantKeyFor(options.map((_, i) => choices[i]))\n : undefined;\n const variant = variantKey ? product?.variants?.find((v) => v.key === variantKey) : undefined;\n\n /** Meerprijs van ingevulde personalisatie (per stuk). */\n const persSurcharge = useMemo(\n () =>\n persFields.reduce((sum, f) => {\n const v = personalization[f.key];\n return v && (v.text || v.imagePath) ? sum + f.priceCents : sum;\n }, 0),\n [persFields, personalization],\n );\n\n const priceCents = useMemo(() => {\n if (!product) return 0;\n return productPriceCents(product, variantKey) + persSurcharge;\n }, [product, variantKey, persSurcharge]);\n\n /** Hoofdbeeld: handmatige thumbnail-keuze > per-kleur-afbeelding > eerste productfoto. */\n const kleur = product ? kleurOptie(product) : null;\n const kleurIdx = kleur && product ? (product.options ?? []).findIndex((o) => o === kleur) : -1;\n const gekozenKleur = kleurIdx >= 0 ? choices[kleurIdx] : undefined;\n const kleurImage = gekozenKleur ? kleur?.images?.[gekozenKleur] : undefined;\n const mainImage = manualImage ?? kleurImage ?? product?.images?.[0];\n\n /** Alle toonbare afbeeldingen: productfoto's + unieke per-kleur-afbeeldingen. */\n const gallery = useMemo(() => {\n if (!product) return [] as { url: string; label?: string }[];\n const out: { url: string; label?: string }[] = (product.images ?? []).map((url) => ({ url }));\n for (const [value, url] of Object.entries(kleur?.images ?? {})) {\n if (!out.some((g) => g.url === url)) out.push({ url, label: value });\n }\n return out;\n }, [product, kleur]);\n\n const stockValue = options.length === 0 ? product?.stock : variant?.stock;\n const outOfStock = Boolean(product?.trackStock && stockValue !== undefined && stockValue <= 0);\n const lowStock = Boolean(product?.trackStock && stockValue !== undefined && stockValue > 0 && stockValue <= 5);\n const variantInactive = variant !== undefined && variant.active === false;\n\n if (product === undefined) return null;\n if (product === null) {\n return (\n <Card padding={4} variant=\"muted\">\n <Text>\n {t(\"productNotFound\")} <a href={settings.shopPath}>{t(\"backToShop\")}</a>\n </Text>\n </Card>\n );\n }\n\n const persComplete = persFields.every((f) => {\n if (!f.required) return true;\n const v = personalization[f.key];\n return Boolean(v && (v.text?.trim() || v.imagePath));\n });\n\n function addToCart() {\n if (!product) return;\n const filled: PersValue = {};\n for (const f of persFields) {\n const v = personalization[f.key];\n if (v && (v.text?.trim() || v.imagePath)) filled[f.key] = v;\n }\n const persLabel = persFields\n .filter((f) => filled[f.key])\n .map((f) => `${f.label}: ${filled[f.key].text ?? filled[f.key].fileName ?? \"✓\"}`)\n .join(\" · \");\n cartAdd(\n siteId,\n {\n productId: product.id,\n slug: product.slug,\n name: product.name,\n variantKey,\n variantLabel: variantKey ? variantLabelFor(options, variantKey) : undefined,\n unitPriceCents: priceCents,\n imageUrl: mainImage ?? product.images?.[0],\n personalization: Object.keys(filled).length > 0 ? filled : undefined,\n personalizationLabel: persLabel || undefined,\n },\n qty,\n );\n setAdded(true);\n }\n\n const canOrder = (options.length === 0 || allChosen) && !outOfStock && !variantInactive && persComplete;\n\n return (\n <>\n <div\n className=\"xicms-shop-detail\"\n style={{ display: \"grid\", gap: \"2rem\", gridTemplateColumns: \"repeat(auto-fit, minmax(min(320px, 100%), 1fr))\" }}\n >\n <div className=\"xicms-shop-detail__media\">\n {mainImage ? (\n <img\n src={mainImage}\n alt={product.name}\n style={{ width: \"100%\", aspectRatio: \"1/1\", objectFit: \"cover\", borderRadius: \"var(--xicms-radius, 12px)\" }}\n />\n ) : (\n <div\n className=\"xicms-shop-detail__placeholder\"\n aria-hidden=\"true\"\n style={{ width: \"100%\", aspectRatio: \"1/1\", background: \"var(--xicms-muted, #f1f1f1)\", borderRadius: \"var(--xicms-radius, 12px)\" }}\n />\n )}\n {gallery.length > 1 && (\n <div style={{ display: \"flex\", gap: 8, marginTop: 8, flexWrap: \"wrap\" }}>\n {gallery.map((g) => (\n <button\n key={g.url}\n type=\"button\"\n title={g.label}\n onClick={() => setManualImage(g.url)}\n style={{\n padding: 0,\n border: g.url === mainImage ? \"2px solid var(--xicms-primary, #333)\" : \"2px solid transparent\",\n borderRadius: 8,\n cursor: \"pointer\",\n background: \"none\",\n }}\n >\n <img src={g.url} alt={g.label ?? \"\"} style={{ width: 64, height: 64, objectFit: \"cover\", borderRadius: 6, display: \"block\" }} />\n </button>\n ))}\n </div>\n )}\n </div>\n\n {/*\n Haken voor de bewerkmodus. De tekst van een product staat NIET in dit blok — het blok wijst\n alleen een product aan (`slug`) en haalt de rest uit de productencollectie. Zonder deze\n haken kon de editor daarom niets: het tandwiel opende een paneel met twee instellingen en\n het potlood verscheen helemaal niet, want er viel in dit blok niets te bewerken. Terecht,\n en toch precies verkeerd — je stond naar een productnaam te kijken die je niet kon wijzigen.\n `data-xicms-product` zegt om welk product het gaat, `data-xicms-product-edit` welk veld\n eronder ligt; de editor schrijft die rechtstreeks naar het productdocument.\n */}\n <div className=\"xicms-shop-detail__info\" data-xicms-product={product.id}>\n <Heading level={1} data-xicms-product-edit=\"name\">{product.name}</Heading>\n <p className=\"xicms-shop-detail__price\" style={{ fontSize: \"1.35rem\", fontWeight: 700, margin: \"0.5rem 0 1rem\" }}>\n {product.compareAtCents !== undefined && product.compareAtCents > priceCents && (\n <s style={{ opacity: 0.6, marginRight: 8, fontWeight: \"normal\" }}>\n {formatCents(product.compareAtCents, settings.currency)}\n </s>\n )}\n {formatCents(priceCents, settings.currency)}\n </p>\n\n {product.description && (\n <p className=\"xicms-shop-detail__desc\" data-xicms-product-edit=\"description\" style={{ whiteSpace: \"pre-wrap\", margin: 0 }}>{product.description}</p>\n )}\n\n <div style={{ display: \"grid\", gap: \"0.75rem\", maxWidth: 420, marginTop: \"1rem\" }}>\n {options.map((opt, i) => (\n <Selector\n key={opt.name}\n label={opt.name}\n placeholder={t(\"choose\")}\n options={opt.values}\n value={choices[i] ?? \"\"}\n onChange={(v) => {\n setChoices((prev) => ({ ...prev, [i]: (v as string) ?? \"\" }));\n setManualImage(null);\n setAdded(false);\n }}\n />\n ))}\n\n {persFields.length > 0 && (\n <PersonalizationInputs\n fields={persFields}\n siteId={siteId}\n productId={product.id}\n value={personalization}\n currency={settings.currency}\n onChange={(v) => {\n setPersonalization(v);\n setAdded(false);\n }}\n />\n )}\n\n <label className=\"xicms-shop-detail__qty\" style={{ display: \"grid\", gap: 4 }}>\n <span style={{ fontSize: \"0.9rem\" }}>{t(\"qty\")}</span>\n <input\n className=\"xicms-input\"\n type=\"number\"\n min={1}\n max={999}\n value={qty}\n onChange={(e) => setQty(Math.max(1, Math.min(999, Number(e.target.value) || 1)))}\n style={{ width: 90 }}\n />\n </label>\n\n {outOfStock && <Text style={{ color: \"var(--color-error, #c0392b)\" }}>{t(\"outOfStock\")}</Text>}\n {variantInactive && <Text style={{ color: \"var(--color-error, #c0392b)\" }}>{t(\"variantUnavailable\")}</Text>}\n {lowStock && !outOfStock && (\n <Text style={{ color: \"var(--xicms-primary, #4d7fb0)\", fontWeight: 600 }}>\n {t(\"stockLeft\", { n: stockValue as number })}\n </Text>\n )}\n\n {added ? (\n <div role=\"status\" style={{ display: \"grid\", gap: 8 }}>\n <Text>{t(\"added\")}</Text>\n <div style={{ display: \"flex\", gap: 12, flexWrap: \"wrap\" }}>\n <a className=\"xicms-btn xicms-btn--primary\" href={settings.cartPath}>{t(\"toCart\")}</a>\n <a className=\"xicms-btn xicms-btn--secondary\" href={settings.shopPath}>{t(\"continueShopping\")}</a>\n </div>\n </div>\n ) : (\n <button\n type=\"button\"\n className=\"xicms-btn xicms-btn--primary\"\n disabled={!canOrder}\n onClick={addToCart}\n style={{ cursor: canOrder ? \"pointer\" : \"not-allowed\", opacity: canOrder ? 1 : 0.5, width: \"fit-content\" }}\n >\n {t(\"addToCart\")}\n </button>\n )}\n </div>\n </div>\n </div>\n\n {block.showRelated && related.length > 0 && (\n <div className=\"xicms-shop-related\" style={{ marginTop: \"3rem\" }}>\n <Heading level={2}>{t(\"related\")}</Heading>\n <div\n className=\"xicms-shop-grid__items\"\n style={{\n display: \"grid\",\n gridTemplateColumns: \"repeat(auto-fill, minmax(min(200px, calc(50% - 1rem)), 1fr))\",\n gap: \"1.25rem\",\n marginTop: \"1rem\",\n }}\n >\n {related.map((p) => (\n <ProductCard\n key={p.id}\n product={p}\n siteId={siteId}\n currency={settings.currency}\n detailPath={settings.detailPath}\n cartPath={settings.cartPath}\n t={t}\n />\n ))}\n </div>\n </div>\n )}\n </>\n );\n}\n\nexport const productDetailBlock = defineBlock({\n type: \"productDetail\",\n Render: ProductDetailRender,\n label: \"Productdetail\",\n icon: \"🏷️\",\n defaults: () => ({ showRelated: true }),\n});\n"],"mappings":"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","names":[]}