@xiwebdesign/xicms-core 5.5.1 → 5.5.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.
@@ -96,12 +96,12 @@ function ProductDetailRender({ block, siteId }) {
96
96
  ] }),
97
97
  /* @__PURE__ */ jsxs("div", { className: "xicms-shop-detail__info", children: [
98
98
  /* @__PURE__ */ jsx(Heading, { level: 1, children: product.name }),
99
- /* @__PURE__ */ jsxs(Text, { size: "xl", weight: "bold", display: "block", style: { margin: "0.5rem 0 1rem" }, children: [
99
+ /* @__PURE__ */ jsxs("p", { className: "xicms-shop-detail__price", style: { fontSize: "1.35rem", fontWeight: 700, margin: "0.5rem 0 1rem" }, children: [
100
100
  product.compareAtCents !== void 0 && product.compareAtCents > priceCents && /* @__PURE__ */ jsx("s", { style: { opacity: 0.6, marginRight: 8, fontWeight: "normal" }, children: formatCents(product.compareAtCents, settings.currency) }),
101
101
  formatCents(priceCents, settings.currency)
102
102
  ] }),
103
103
  product.description && // Platte tekst met behoud van regeleinden — bewust geen HTML (zie schema/shop.ts).
104
- /* @__PURE__ */ jsx(Text, { display: "block", style: { whiteSpace: "pre-wrap" }, children: product.description }),
104
+ /* @__PURE__ */ jsx("p", { className: "xicms-shop-detail__desc", style: { whiteSpace: "pre-wrap", margin: 0 }, children: product.description }),
105
105
  /* @__PURE__ */ jsxs("div", { style: { display: "grid", gap: "0.75rem", maxWidth: 420, marginTop: "1rem" }, children: [
106
106
  options.map((opt, i) => /* @__PURE__ */ jsx(
107
107
  Selector,
@@ -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 { ProductDetailBlockSchema, formatCents, productPriceCents, variantKeyFor } from \"../../../schema/shop\";\nimport { defineBlock, type BlockRenderProps } from \"../../../blocks/registry\";\nimport { Card, Heading, Text } from \"../../../blocks/ui\";\nimport { Selector } from \"@astryxdesign/core/Selector\";\nimport { cartAdd } from \"../cartStore\";\nimport { fetchProductBySlug, readQueryParam, useShopSettings, variantLabelFor, type StoredProduct } from \"../lib\";\n\ntype Blk = z.infer<typeof ProductDetailBlockSchema>;\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, variant-prijs/voorraad, aantal,\n * in-winkelwagen.\n */\nfunction ProductDetailRender({ block, siteId }: BlockRenderProps<Blk>) {\n const settings = useShopSettings();\n const [product, setProduct] = useState<StoredProduct | null | undefined>(undefined);\n const [choices, setChoices] = useState<Record<number, string>>({});\n const [qty, setQty] = useState(1);\n const [imageIdx, setImageIdx] = useState(0);\n const [added, setAdded] = useState(false);\n\n useEffect(() => {\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 }, [siteId, block.slug]);\n\n const options = product?.options ?? [];\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 const priceCents = useMemo(() => {\n if (!product) return 0;\n return productPriceCents(product, variantKey);\n }, [product, variantKey]);\n\n /** Uitverkocht? Alleen met voorraadbeheer aan én een expliciet gezette voorraad (undefined = onbeperkt). */\n const outOfStock = Boolean(\n product?.trackStock &&\n (options.length === 0\n ? product.stock !== undefined && product.stock <= 0\n : variant?.stock !== undefined && variant.stock <= 0),\n );\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>Product niet gevonden. <a href={settings.shopPath}>Terug naar de winkel</a></Text>\n </Card>\n );\n }\n\n function addToCart() {\n if (!product) return;\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: product.images?.[0],\n },\n qty,\n );\n setAdded(true);\n }\n\n const canOrder = (options.length === 0 || allChosen) && !outOfStock && !variantInactive;\n\n return (\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 {product.images?.[imageIdx] ? (\n <img\n src={product.images[imageIdx]}\n alt={product.name}\n style={{ width: \"100%\", aspectRatio: \"1/1\", objectFit: \"cover\", borderRadius: \"var(--xicms-radius, 12px)\" }}\n />\n ) : (\n <div style={{ width: \"100%\", aspectRatio: \"1/1\", display: \"grid\", placeItems: \"center\", background: \"var(--xicms-muted, #f1f1f1)\", borderRadius: \"var(--xicms-radius, 12px)\" }}>\n <span style={{ fontSize: \"3rem\", opacity: 0.4 }}>🖼️</span>\n </div>\n )}\n {(product.images?.length ?? 0) > 1 && (\n <div style={{ display: \"flex\", gap: 8, marginTop: 8, flexWrap: \"wrap\" }}>\n {product.images.map((url, i) => (\n <button\n key={url + i}\n type=\"button\"\n onClick={() => setImageIdx(i)}\n style={{\n padding: 0,\n border: i === imageIdx ? \"2px solid var(--xicms-primary, #333)\" : \"2px solid transparent\",\n borderRadius: 8,\n cursor: \"pointer\",\n background: \"none\",\n }}\n >\n <img src={url} alt=\"\" style={{ width: 64, height: 64, objectFit: \"cover\", borderRadius: 6, display: \"block\" }} />\n </button>\n ))}\n </div>\n )}\n </div>\n\n <div className=\"xicms-shop-detail__info\">\n <Heading level={1}>{product.name}</Heading>\n <Text size=\"xl\" weight=\"bold\" display=\"block\" style={{ 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 </Text>\n\n {product.description && (\n // Platte tekst met behoud van regeleinden — bewust geen HTML (zie schema/shop.ts).\n <Text display=\"block\" style={{ whiteSpace: \"pre-wrap\" }}>{product.description}</Text>\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=\"Kies…\"\n options={opt.values}\n value={choices[i] ?? \"\"}\n onChange={(v) => {\n setChoices((prev) => ({ ...prev, [i]: (v as string) ?? \"\" }));\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\" }}>Aantal</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)\" }}>Uitverkocht in deze uitvoering.</Text>}\n {variantInactive && <Text style={{ color: \"var(--color-error, #c0392b)\" }}>Deze combinatie is niet leverbaar.</Text>}\n\n {added ? (\n <div role=\"status\" style={{ display: \"grid\", gap: 8 }}>\n <Text>✓ Toegevoegd aan je winkelwagen.</Text>\n <div style={{ display: \"flex\", gap: 12, flexWrap: \"wrap\" }}>\n <a className=\"xicms-btn xicms-btn--primary\" href={settings.cartPath}>Naar winkelwagen</a>\n <a className=\"xicms-btn xicms-btn--secondary\" href={settings.shopPath}>Verder winkelen</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 In winkelwagen\n </button>\n )}\n </div>\n </div>\n </div>\n );\n}\n\nexport const productDetailBlock = defineBlock({\n type: \"productDetail\",\n schema: ProductDetailBlockSchema,\n Render: ProductDetailRender,\n label: \"Productdetail\",\n icon: \"🏷️\",\n defaults: () => ({}),\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 { ProductDetailBlockSchema, formatCents, productPriceCents, variantKeyFor } from \"../../../schema/shop\";\nimport { defineBlock, type BlockRenderProps } from \"../../../blocks/registry\";\nimport { Card, Heading, Text } from \"../../../blocks/ui\";\nimport { Selector } from \"@astryxdesign/core/Selector\";\nimport { cartAdd } from \"../cartStore\";\nimport { fetchProductBySlug, readQueryParam, useShopSettings, variantLabelFor, type StoredProduct } from \"../lib\";\n\ntype Blk = z.infer<typeof ProductDetailBlockSchema>;\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, variant-prijs/voorraad, aantal,\n * in-winkelwagen.\n */\nfunction ProductDetailRender({ block, siteId }: BlockRenderProps<Blk>) {\n const settings = useShopSettings();\n const [product, setProduct] = useState<StoredProduct | null | undefined>(undefined);\n const [choices, setChoices] = useState<Record<number, string>>({});\n const [qty, setQty] = useState(1);\n const [imageIdx, setImageIdx] = useState(0);\n const [added, setAdded] = useState(false);\n\n useEffect(() => {\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 }, [siteId, block.slug]);\n\n const options = product?.options ?? [];\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 const priceCents = useMemo(() => {\n if (!product) return 0;\n return productPriceCents(product, variantKey);\n }, [product, variantKey]);\n\n /** Uitverkocht? Alleen met voorraadbeheer aan én een expliciet gezette voorraad (undefined = onbeperkt). */\n const outOfStock = Boolean(\n product?.trackStock &&\n (options.length === 0\n ? product.stock !== undefined && product.stock <= 0\n : variant?.stock !== undefined && variant.stock <= 0),\n );\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>Product niet gevonden. <a href={settings.shopPath}>Terug naar de winkel</a></Text>\n </Card>\n );\n }\n\n function addToCart() {\n if (!product) return;\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: product.images?.[0],\n },\n qty,\n );\n setAdded(true);\n }\n\n const canOrder = (options.length === 0 || allChosen) && !outOfStock && !variantInactive;\n\n return (\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 {product.images?.[imageIdx] ? (\n <img\n src={product.images[imageIdx]}\n alt={product.name}\n style={{ width: \"100%\", aspectRatio: \"1/1\", objectFit: \"cover\", borderRadius: \"var(--xicms-radius, 12px)\" }}\n />\n ) : (\n <div style={{ width: \"100%\", aspectRatio: \"1/1\", display: \"grid\", placeItems: \"center\", background: \"var(--xicms-muted, #f1f1f1)\", borderRadius: \"var(--xicms-radius, 12px)\" }}>\n <span style={{ fontSize: \"3rem\", opacity: 0.4 }}>🖼️</span>\n </div>\n )}\n {(product.images?.length ?? 0) > 1 && (\n <div style={{ display: \"flex\", gap: 8, marginTop: 8, flexWrap: \"wrap\" }}>\n {product.images.map((url, i) => (\n <button\n key={url + i}\n type=\"button\"\n onClick={() => setImageIdx(i)}\n style={{\n padding: 0,\n border: i === imageIdx ? \"2px solid var(--xicms-primary, #333)\" : \"2px solid transparent\",\n borderRadius: 8,\n cursor: \"pointer\",\n background: \"none\",\n }}\n >\n <img src={url} alt=\"\" style={{ width: 64, height: 64, objectFit: \"cover\", borderRadius: 6, display: \"block\" }} />\n </button>\n ))}\n </div>\n )}\n </div>\n\n <div className=\"xicms-shop-detail__info\">\n <Heading level={1}>{product.name}</Heading>\n {/* Echte block-elementen (geen Astryx Text): prijs en beschrijving mogen nooit in\n elkaars inline-flow terechtkomen. */}\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 // Platte tekst met behoud van regeleinden — bewust geen HTML (zie schema/shop.ts).\n <p className=\"xicms-shop-detail__desc\" 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=\"Kies…\"\n options={opt.values}\n value={choices[i] ?? \"\"}\n onChange={(v) => {\n setChoices((prev) => ({ ...prev, [i]: (v as string) ?? \"\" }));\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\" }}>Aantal</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)\" }}>Uitverkocht in deze uitvoering.</Text>}\n {variantInactive && <Text style={{ color: \"var(--color-error, #c0392b)\" }}>Deze combinatie is niet leverbaar.</Text>}\n\n {added ? (\n <div role=\"status\" style={{ display: \"grid\", gap: 8 }}>\n <Text>✓ Toegevoegd aan je winkelwagen.</Text>\n <div style={{ display: \"flex\", gap: 12, flexWrap: \"wrap\" }}>\n <a className=\"xicms-btn xicms-btn--primary\" href={settings.cartPath}>Naar winkelwagen</a>\n <a className=\"xicms-btn xicms-btn--secondary\" href={settings.shopPath}>Verder winkelen</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 In winkelwagen\n </button>\n )}\n </div>\n </div>\n </div>\n );\n}\n\nexport const productDetailBlock = defineBlock({\n type: \"productDetail\",\n schema: ProductDetailBlockSchema,\n Render: ProductDetailRender,\n label: \"Productdetail\",\n icon: \"🏷️\",\n defaults: () => ({}),\n});\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xiwebdesign/xicms-core",
3
- "version": "5.5.1",
3
+ "version": "5.5.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": {