@xiwebdesign/xicms-core 7.22.1 → 7.22.2

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.
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Aantal-veld voor de winkel (productpagina én winkelwagen).
3
+ *
4
+ * Waarom een eigen component en niet gewoon een `<input type="number">`? Omdat een getal-invoer
5
+ * die rechtstreeks aan een getal-state hangt op twee manieren stukgaat, en klanten liepen tegen
6
+ * allebei aan:
7
+ *
8
+ * 1. **Je kunt niets intypen.** Om "2" te typen wis je eerst de "1". Dat wissen levert een lege
9
+ * string op; `Number("") || 1` maakte daar meteen weer 1 van, React zette de 1 terug in het
10
+ * veld en de cursor sprong. Het aantal veranderen kon dus alleen nog met de pijltjes.
11
+ * 2. **In de winkelwagen verdween het product.** Daar werd een lege string 0, en `cartSetQty`
12
+ * gooit een regel met aantal 0 uit de wagen. Eén keer backspace en je bestelling was weg.
13
+ *
14
+ * De oplossing is dat het veld tijdens het typen even leeg mág zijn: de ruwe tekst staat in een
15
+ * eigen state en er wordt pas iets doorgegeven als er een geldig getal staat. Verlaat je het veld
16
+ * leeg of ongeldig, dan komt de laatste geldige waarde terug. Weghalen doe je met het kruisje,
17
+ * niet door een veld leeg te maken — dat is een bewuste handeling en geen typfout.
18
+ */
19
+ export declare function AantalInvoer({ value, onChange, min, max, ariaLabel, style, }: {
20
+ value: number;
21
+ onChange: (n: number) => void;
22
+ min?: number;
23
+ max?: number;
24
+ ariaLabel?: string;
25
+ style?: React.CSSProperties;
26
+ }): import("react").JSX.Element;
@@ -0,0 +1,50 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { useEffect, useState } from "react";
4
+ function AantalInvoer({
5
+ value,
6
+ onChange,
7
+ min = 1,
8
+ max = 999,
9
+ ariaLabel,
10
+ style
11
+ }) {
12
+ const [tekst, setTekst] = useState(String(value));
13
+ useEffect(() => {
14
+ setTekst((huidig) => Number(huidig) === value ? huidig : String(value));
15
+ }, [value]);
16
+ return /* @__PURE__ */ jsx(
17
+ "input",
18
+ {
19
+ className: "xicms-input",
20
+ type: "number",
21
+ inputMode: "numeric",
22
+ min,
23
+ max,
24
+ value: tekst,
25
+ "aria-label": ariaLabel,
26
+ onChange: (e) => {
27
+ const rauw = e.target.value;
28
+ setTekst(rauw);
29
+ if (rauw.trim() === "") return;
30
+ const n = Number(rauw);
31
+ if (!Number.isFinite(n)) return;
32
+ const begrensd = Math.max(min, Math.min(max, Math.floor(n)));
33
+ if (begrensd !== value) onChange(begrensd);
34
+ if (n > max) setTekst(String(begrensd));
35
+ },
36
+ onBlur: () => {
37
+ const n = Number(tekst);
38
+ const geldig = tekst.trim() !== "" && Number.isFinite(n) && n >= min;
39
+ const eind = geldig ? Math.max(min, Math.min(max, Math.floor(n))) : value;
40
+ setTekst(String(eind));
41
+ if (eind !== value) onChange(eind);
42
+ },
43
+ style
44
+ }
45
+ );
46
+ }
47
+ export {
48
+ AantalInvoer
49
+ };
50
+ //# sourceMappingURL=AantalInvoer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/modules/shop/blocks/AantalInvoer.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\n\n/**\n * Aantal-veld voor de winkel (productpagina én winkelwagen).\n *\n * Waarom een eigen component en niet gewoon een `<input type=\"number\">`? Omdat een getal-invoer\n * die rechtstreeks aan een getal-state hangt op twee manieren stukgaat, en klanten liepen tegen\n * allebei aan:\n *\n * 1. **Je kunt niets intypen.** Om \"2\" te typen wis je eerst de \"1\". Dat wissen levert een lege\n * string op; `Number(\"\") || 1` maakte daar meteen weer 1 van, React zette de 1 terug in het\n * veld en de cursor sprong. Het aantal veranderen kon dus alleen nog met de pijltjes.\n * 2. **In de winkelwagen verdween het product.** Daar werd een lege string 0, en `cartSetQty`\n * gooit een regel met aantal 0 uit de wagen. Eén keer backspace en je bestelling was weg.\n *\n * De oplossing is dat het veld tijdens het typen even leeg mág zijn: de ruwe tekst staat in een\n * eigen state en er wordt pas iets doorgegeven als er een geldig getal staat. Verlaat je het veld\n * leeg of ongeldig, dan komt de laatste geldige waarde terug. Weghalen doe je met het kruisje,\n * niet door een veld leeg te maken — dat is een bewuste handeling en geen typfout.\n */\nexport function AantalInvoer({\n value,\n onChange,\n min = 1,\n max = 999,\n ariaLabel,\n style,\n}: {\n value: number;\n onChange: (n: number) => void;\n min?: number;\n max?: number;\n ariaLabel?: string;\n style?: React.CSSProperties;\n}) {\n const [tekst, setTekst] = useState(String(value));\n\n // Verandert het aantal buiten dit veld om (andere regel aangepast, cart hersteld), dan volgt\n // het veld mee — maar niet terwijl de bezoeker zelf aan het typen is: dan zou de tekst\n // onder zijn handen vandaan springen.\n useEffect(() => {\n setTekst((huidig) => (Number(huidig) === value ? huidig : String(value)));\n }, [value]);\n\n return (\n <input\n className=\"xicms-input\"\n type=\"number\"\n inputMode=\"numeric\"\n min={min}\n max={max}\n value={tekst}\n aria-label={ariaLabel}\n onChange={(e) => {\n const rauw = e.target.value;\n setTekst(rauw);\n if (rauw.trim() === \"\") return; // leeg mag tijdens het typen; nog niets doorgeven\n const n = Number(rauw);\n if (!Number.isFinite(n)) return;\n const begrensd = Math.max(min, Math.min(max, Math.floor(n)));\n if (begrensd !== value) onChange(begrensd);\n // Alleen de tekst corrigeren als de invoer écht buiten de grenzen viel; anders blijft\n // \"0\" tijdens het typen van \"0\" → onmogelijk om bv. \"10\" te typen op een leeg veld.\n if (n > max) setTekst(String(begrensd));\n }}\n onBlur={() => {\n const n = Number(tekst);\n const geldig = tekst.trim() !== \"\" && Number.isFinite(n) && n >= min;\n const eind = geldig ? Math.max(min, Math.min(max, Math.floor(n))) : value;\n setTekst(String(eind));\n if (eind !== value) onChange(eind);\n }}\n style={style}\n />\n );\n}\n"],"mappings":";AA+CI;AA7CJ,SAAS,WAAW,gBAAgB;AAoB7B,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA;AAAA,EACA,MAAM;AAAA,EACN,MAAM;AAAA,EACN;AAAA,EACA;AACF,GAOG;AACD,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAS,OAAO,KAAK,CAAC;AAKhD,YAAU,MAAM;AACd,aAAS,CAAC,WAAY,OAAO,MAAM,MAAM,QAAQ,SAAS,OAAO,KAAK,CAAE;AAAA,EAC1E,GAAG,CAAC,KAAK,CAAC;AAEV,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAU;AAAA,MACV,MAAK;AAAA,MACL,WAAU;AAAA,MACV;AAAA,MACA;AAAA,MACA,OAAO;AAAA,MACP,cAAY;AAAA,MACZ,UAAU,CAAC,MAAM;AACf,cAAM,OAAO,EAAE,OAAO;AACtB,iBAAS,IAAI;AACb,YAAI,KAAK,KAAK,MAAM,GAAI;AACxB,cAAM,IAAI,OAAO,IAAI;AACrB,YAAI,CAAC,OAAO,SAAS,CAAC,EAAG;AACzB,cAAM,WAAW,KAAK,IAAI,KAAK,KAAK,IAAI,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC;AAC3D,YAAI,aAAa,MAAO,UAAS,QAAQ;AAGzC,YAAI,IAAI,IAAK,UAAS,OAAO,QAAQ,CAAC;AAAA,MACxC;AAAA,MACA,QAAQ,MAAM;AACZ,cAAM,IAAI,OAAO,KAAK;AACtB,cAAM,SAAS,MAAM,KAAK,MAAM,MAAM,OAAO,SAAS,CAAC,KAAK,KAAK;AACjE,cAAM,OAAO,SAAS,KAAK,IAAI,KAAK,KAAK,IAAI,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,IAAI;AACpE,iBAAS,OAAO,IAAI,CAAC;AACrB,YAAI,SAAS,MAAO,UAAS,IAAI;AAAA,MACnC;AAAA,MACA;AAAA;AAAA,EACF;AAEJ;","names":[]}
@@ -4,6 +4,7 @@ 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";
7
+ import { AantalInvoer } from "./AantalInvoer.js";
7
8
  import { useShopStrings } from "../strings.js";
8
9
  import { useShopSettings } from "../lib.js";
9
10
  function CartRender({ block, siteId }) {
@@ -41,15 +42,13 @@ function CartRender({ block, siteId }) {
41
42
  ] }),
42
43
  /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
43
44
  /* @__PURE__ */ jsx(
44
- "input",
45
+ AantalInvoer,
45
46
  {
46
- className: "xicms-input",
47
- type: "number",
48
- min: 0,
49
- max: 999,
50
47
  value: l.qty,
51
- "aria-label": `${t("qty")} ${l.name}`,
52
- onChange: (e) => cartSetQty(siteId, l, Math.max(0, Number(e.target.value) || 0)),
48
+ onChange: (n) => cartSetQty(siteId, l, n),
49
+ min: 1,
50
+ max: 999,
51
+ ariaLabel: `${t("qty")} ${l.name}`,
53
52
  style: { width: 72 }
54
53
  }
55
54
  ),
@@ -1 +1 @@
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 /**\n * Verzendkosten in de winkelwagen zijn een RICHTBEDRAG voor het standaardland: de klant kiest\n * zijn land (en een eventuele kortingscode) pas bij het afrekenen. Met zones staat dat er nu\n * ook bij, zodat \"€ 7,25\" niet als een belofte leest voor een Belgische bezoeker.\n */\n const shippingCents = shippingCentsFor(\n itemsCents,\n {\n shippingFlatCents: settings.shippingFlatCents,\n shippingFreeFromCents: settings.shippingFreeFromCents || undefined,\n shippingZones: settings.shippingZones,\n },\n lines.length > 0,\n );\n const meerdereZones = (settings.shippingZones ?? []).length > 1;\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 {meerdereZones ? ` ${t(\"shippingFromCountry\")}` : \"\"}\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
+ {"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 { AantalInvoer } from \"./AantalInvoer\";\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 /**\n * Verzendkosten in de winkelwagen zijn een RICHTBEDRAG voor het standaardland: de klant kiest\n * zijn land (en een eventuele kortingscode) pas bij het afrekenen. Met zones staat dat er nu\n * ook bij, zodat \"€ 7,25\" niet als een belofte leest voor een Belgische bezoeker.\n */\n const shippingCents = shippingCentsFor(\n itemsCents,\n {\n shippingFlatCents: settings.shippingFlatCents,\n shippingFreeFromCents: settings.shippingFreeFromCents || undefined,\n shippingZones: settings.shippingZones,\n },\n lines.length > 0,\n );\n const meerdereZones = (settings.shippingZones ?? []).length > 1;\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 {/* min 1: een leeg veld mag nooit de regel uit de wagen gooien — weghalen doe\n je met het kruisje hiernaast. */}\n <AantalInvoer\n value={l.qty}\n onChange={(n) => cartSetQty(siteId, l, n)}\n min={1}\n max={999}\n ariaLabel={`${t(\"qty\")} ${l.name}`}\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 {meerdereZones ? ` ${t(\"shippingFromCountry\")}` : \"\"}\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":[]}
@@ -7,6 +7,7 @@ import { useBlockData } from "../../../blocks/context.js";
7
7
  import { Card, Heading, Text } from "../../../blocks/ui/index.js";
8
8
  import { Selector } from "@astryxdesign/core/Selector";
9
9
  import { cartAdd } from "../cartStore.js";
10
+ import { AantalInvoer } from "./AantalInvoer.js";
10
11
  import { useShopStrings } from "../strings.js";
11
12
  import {
12
13
  fetchActiveProducts,
@@ -279,18 +280,7 @@ function ProductDetailRender({ block, siteId }) {
279
280
  ),
280
281
  /* @__PURE__ */ jsxs("label", { className: "xicms-shop-detail__qty", style: { display: "grid", gap: 4 }, children: [
281
282
  /* @__PURE__ */ jsx("span", { style: { fontSize: "0.9rem" }, children: t("qty") }),
282
- /* @__PURE__ */ jsx(
283
- "input",
284
- {
285
- className: "xicms-input",
286
- type: "number",
287
- min: 1,
288
- max: 999,
289
- value: qty,
290
- onChange: (e) => setQty(Math.max(1, Math.min(999, Number(e.target.value) || 1))),
291
- style: { width: 90 }
292
- }
293
- )
283
+ /* @__PURE__ */ jsx(AantalInvoer, { value: qty, onChange: setQty, min: 1, max: 999, style: { width: 90 } })
294
284
  ] }),
295
285
  outOfStock && /* @__PURE__ */ jsx(Text, { style: { color: "var(--color-error, #c0392b)" }, children: t("outOfStock") }),
296
286
  variantInactive && /* @__PURE__ */ jsx(Text, { style: { color: "var(--color-error, #c0392b)" }, children: t("variantUnavailable") }),
@@ -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 { 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 // Uitgeschakeld op het product? Dan tonen we de velden niet en rekent de server ze ook niet\n // (zie resolvePersonalization) — de configuratie blijft wel bewaard voor later.\n const persFields = product?.personalizationEnabled === false ? [] : (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":[]}
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 { AantalInvoer } from \"./AantalInvoer\";\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 // Uitgeschakeld op het product? Dan tonen we de velden niet en rekent de server ze ook niet\n // (zie resolvePersonalization) — de configuratie blijft wel bewaard voor later.\n const persFields = product?.personalizationEnabled === false ? [] : (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 <AantalInvoer value={qty} onChange={setQty} min={1} max={999} style={{ width: 90 }} />\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":[]}
package/dist/version.js CHANGED
@@ -1,4 +1,4 @@
1
- const CORE_VERSION = true ? "7.22.1" : "0.0.0-dev";
1
+ const CORE_VERSION = true ? "7.22.2" : "0.0.0-dev";
2
2
  const CORE_PACKAGE = "@xiwebdesign/xicms-core";
3
3
  function isNieuwer(beschikbaar, huidig) {
4
4
  const delen = (v) => v.split("-")[0].split(".").map((n) => Number.parseInt(n, 10) || 0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xiwebdesign/xicms-core",
3
- "version": "7.22.1",
3
+ "version": "7.22.2",
4
4
  "description": "XiCMS engine: gestructureerde content, pagina's, draft/publish, admin (TipTap+Radix), SEO en beveiliging voor klantsites.",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {