@xiwebdesign/xicms-core 5.3.0 → 5.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/XiCMSProvider.d.ts +11 -2
- package/dist/XiCMSProvider.js +10 -5
- package/dist/XiCMSProvider.js.map +1 -1
- package/dist/admin/blockEditors/GenericEditor.js +18 -8
- package/dist/admin/blockEditors/GenericEditor.js.map +1 -1
- package/dist/admin/customBlocks/CustomBlocksPanel.js +12 -2
- package/dist/admin/customBlocks/CustomBlocksPanel.js.map +1 -1
- package/dist/admin/lib/pagesClient.js +4 -1
- package/dist/admin/lib/pagesClient.js.map +1 -1
- package/dist/admin/lib/upload.js +14 -7
- package/dist/admin/lib/upload.js.map +1 -1
- package/dist/admin/media/MediaManager.js +60 -28
- package/dist/admin/media/MediaManager.js.map +1 -1
- package/dist/admin/media/MediaPicker.js +25 -15
- package/dist/admin/media/MediaPicker.js.map +1 -1
- package/dist/admin/pages/PageEditor.d.ts +2 -1
- package/dist/admin/pages/PageEditor.js +16 -2
- package/dist/admin/pages/PageEditor.js.map +1 -1
- package/dist/admin/pages/PagesPanel.js +9 -5
- package/dist/admin/pages/PagesPanel.js.map +1 -1
- package/dist/admin/panelRegistry.js +3 -0
- package/dist/admin/panelRegistry.js.map +1 -1
- package/dist/admin/shop/CategoriesTab.d.ts +3 -0
- package/dist/admin/shop/CategoriesTab.js +140 -0
- package/dist/admin/shop/CategoriesTab.js.map +1 -0
- package/dist/admin/shop/ConnectionsTab.d.ts +9 -0
- package/dist/admin/shop/ConnectionsTab.js +158 -0
- package/dist/admin/shop/ConnectionsTab.js.map +1 -0
- package/dist/admin/shop/OrdersTab.d.ts +3 -0
- package/dist/admin/shop/OrdersTab.js +236 -0
- package/dist/admin/shop/OrdersTab.js.map +1 -0
- package/dist/admin/shop/ProductsTab.d.ts +3 -0
- package/dist/admin/shop/ProductsTab.js +397 -0
- package/dist/admin/shop/ProductsTab.js.map +1 -0
- package/dist/admin/shop/ShopPanel.d.ts +4 -0
- package/dist/admin/shop/ShopPanel.js +25 -0
- package/dist/admin/shop/ShopPanel.js.map +1 -0
- package/dist/admin/shop/lib.d.ts +16 -0
- package/dist/admin/shop/lib.js +46 -0
- package/dist/admin/shop/lib.js.map +1 -0
- package/dist/admin/visual/VisualEditor.js +11 -2
- package/dist/admin/visual/VisualEditor.js.map +1 -1
- package/dist/admin.css +1 -1
- package/dist/agentFiles/files.d.ts +15 -3
- package/dist/agentFiles/files.js +57 -0
- package/dist/agentFiles/files.js.map +1 -1
- package/dist/auth/AuthProvider.js +3 -3
- package/dist/auth/AuthProvider.js.map +1 -1
- package/dist/blocks/builtins/Columns.js +1 -1
- package/dist/blocks/builtins/Columns.js.map +1 -1
- package/dist/blocks/builtins/Gallery.js +2 -2
- package/dist/blocks/builtins/Gallery.js.map +1 -1
- package/dist/blocks/builtins/Hero.js +1 -1
- package/dist/blocks/builtins/Hero.js.map +1 -1
- package/dist/blocks/builtins/SymbolRef.js.map +1 -1
- package/dist/components/PageView.js +4 -3
- package/dist/components/PageView.js.map +1 -1
- package/dist/demo/index.d.ts +7 -0
- package/dist/demo/index.js +23 -0
- package/dist/demo/index.js.map +1 -0
- package/dist/index.d.ts +2 -4
- package/dist/index.js +0 -20
- package/dist/index.js.map +1 -1
- package/dist/modules/builtins.d.ts +1 -0
- package/dist/modules/builtins.js +4 -0
- package/dist/modules/builtins.js.map +1 -1
- package/dist/modules/index.d.ts +4 -0
- package/dist/modules/index.js +12 -0
- package/dist/modules/index.js.map +1 -1
- package/dist/modules/registry.d.ts +3 -4
- package/dist/modules/registry.js +2 -3
- package/dist/modules/registry.js.map +1 -1
- package/dist/modules/shop/blocks/Cart.d.ts +2 -0
- package/dist/modules/shop/blocks/Cart.js +91 -0
- package/dist/modules/shop/blocks/Cart.js.map +1 -0
- package/dist/modules/shop/blocks/Checkout.d.ts +2 -0
- package/dist/modules/shop/blocks/Checkout.js +126 -0
- package/dist/modules/shop/blocks/Checkout.js.map +1 -0
- package/dist/modules/shop/blocks/Donation.d.ts +2 -0
- package/dist/modules/shop/blocks/Donation.js +145 -0
- package/dist/modules/shop/blocks/Donation.js.map +1 -0
- package/dist/modules/shop/blocks/OrderStatus.d.ts +2 -0
- package/dist/modules/shop/blocks/OrderStatus.js +40 -0
- package/dist/modules/shop/blocks/OrderStatus.js.map +1 -0
- package/dist/modules/shop/blocks/ProductDetail.d.ts +2 -0
- package/dist/modules/shop/blocks/ProductDetail.js +168 -0
- package/dist/modules/shop/blocks/ProductDetail.js.map +1 -0
- package/dist/modules/shop/blocks/ProductGrid.d.ts +8 -0
- package/dist/modules/shop/blocks/ProductGrid.js +136 -0
- package/dist/modules/shop/blocks/ProductGrid.js.map +1 -0
- package/dist/modules/shop/blocks/ShopCategories.d.ts +2 -0
- package/dist/modules/shop/blocks/ShopCategories.js +69 -0
- package/dist/modules/shop/blocks/ShopCategories.js.map +1 -0
- package/dist/modules/shop/cartStore.d.ts +22 -0
- package/dist/modules/shop/cartStore.js +85 -0
- package/dist/modules/shop/cartStore.js.map +1 -0
- package/dist/modules/shop/lib.d.ts +25 -0
- package/dist/modules/shop/lib.js +61 -0
- package/dist/modules/shop/lib.js.map +1 -0
- package/dist/modules/shop/module.d.ts +1 -0
- package/dist/modules/shop/module.js +39 -0
- package/dist/modules/shop/module.js.map +1 -0
- package/dist/modules/shop/settings.d.ts +41 -0
- package/dist/modules/shop/settings.js +18 -0
- package/dist/modules/shop/settings.js.map +1 -0
- package/dist/schema/blocks.d.ts +27 -12
- package/dist/schema/blocks.js +3 -0
- package/dist/schema/blocks.js.map +1 -1
- package/dist/schema/index.d.ts +1 -0
- package/dist/schema/index.js +1 -0
- package/dist/schema/index.js.map +1 -1
- package/dist/schema/settings.d.ts +1 -34
- package/dist/schema/settings.js +2 -8
- package/dist/schema/settings.js.map +1 -1
- package/dist/schema/shop.d.ts +939 -0
- package/dist/schema/shop.js +209 -0
- package/dist/schema/shop.js.map +1 -0
- package/dist/schema/siteConfig.d.ts +3 -71
- package/dist/schema/siteConfig.js +3 -8
- package/dist/schema/siteConfig.js.map +1 -1
- package/dist/schema/symbols.d.ts +2 -2
- package/dist/schema/symbols.js.map +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +2 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/pages.d.ts +23 -3
- package/dist/server/pages.js +37 -3
- package/dist/server/pages.js.map +1 -1
- package/dist/server/seo.js +5 -3
- package/dist/server/seo.js.map +1 -1
- package/dist/settings/useSiteSettings.d.ts +15 -3
- package/dist/settings/useSiteSettings.js +26 -5
- package/dist/settings/useSiteSettings.js.map +1 -1
- package/dist/styles.css +14 -0
- package/package.json +6 -2
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { DonationBlockSchema, formatCents } from "../../../schema/shop";
|
|
5
|
+
import { defineBlock } from "../../../blocks/registry";
|
|
6
|
+
import { Card, Heading, Text } from "../../../blocks/ui";
|
|
7
|
+
import { TextInput } from "@astryxdesign/core/TextInput";
|
|
8
|
+
import { createOrderCall, useShopSettings } from "../lib";
|
|
9
|
+
function DonationRender({ block, siteId, mode }) {
|
|
10
|
+
const settings = useShopSettings();
|
|
11
|
+
const [amountCents, setAmountCents] = useState(block.amountsCents[0] ?? null);
|
|
12
|
+
const [customEuro, setCustomEuro] = useState("");
|
|
13
|
+
const [custom, setCustom] = useState(false);
|
|
14
|
+
const [v, setV] = useState({ name: "", email: "" });
|
|
15
|
+
const [hp, setHp] = useState("");
|
|
16
|
+
const [busy, setBusy] = useState(false);
|
|
17
|
+
const [error, setError] = useState(null);
|
|
18
|
+
const effectiveCents = custom ? Math.round((Number(customEuro.replace(",", ".")) || 0) * 100) : amountCents ?? 0;
|
|
19
|
+
const valid = effectiveCents >= (custom ? block.customMinCents : 50) && effectiveCents <= block.customMaxCents;
|
|
20
|
+
async function submit(e) {
|
|
21
|
+
e.preventDefault();
|
|
22
|
+
if (mode === "preview" || busy || !valid) return;
|
|
23
|
+
setBusy(true);
|
|
24
|
+
setError(null);
|
|
25
|
+
try {
|
|
26
|
+
const { url } = await createOrderCall({
|
|
27
|
+
siteId,
|
|
28
|
+
items: [{ qty: 1, donationCents: effectiveCents }],
|
|
29
|
+
customer: { email: v.email, name: v.name },
|
|
30
|
+
successPath: settings.successPath,
|
|
31
|
+
cancelPath: typeof window !== "undefined" ? window.location.pathname : "/",
|
|
32
|
+
_hp: hp || void 0
|
|
33
|
+
});
|
|
34
|
+
window.location.assign(url);
|
|
35
|
+
} catch (err) {
|
|
36
|
+
console.error("[xicms] donatie mislukt:", err);
|
|
37
|
+
setError("Doneren is niet gelukt. Probeer het later opnieuw.");
|
|
38
|
+
setBusy(false);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return /* @__PURE__ */ jsx(Card, { padding: 4, className: "xicms-shop-donation", children: /* @__PURE__ */ jsxs("form", { onSubmit: submit, style: { display: "grid", gap: "1rem" }, children: [
|
|
42
|
+
block.heading && /* @__PURE__ */ jsx(Heading, { level: 2, "data-xicms-edit": "heading", children: block.heading }),
|
|
43
|
+
block.introText && /* @__PURE__ */ jsx(Text, { "data-xicms-edit": "introText", children: block.introText }),
|
|
44
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8, flexWrap: "wrap" }, role: "group", "aria-label": "Kies een bedrag", children: [
|
|
45
|
+
block.amountsCents.map((c) => {
|
|
46
|
+
const active = !custom && amountCents === c;
|
|
47
|
+
return /* @__PURE__ */ jsx(
|
|
48
|
+
"button",
|
|
49
|
+
{
|
|
50
|
+
type: "button",
|
|
51
|
+
className: "xicms-btn",
|
|
52
|
+
"aria-pressed": active,
|
|
53
|
+
onClick: () => {
|
|
54
|
+
setCustom(false);
|
|
55
|
+
setAmountCents(c);
|
|
56
|
+
},
|
|
57
|
+
style: {
|
|
58
|
+
cursor: "pointer",
|
|
59
|
+
border: active ? "2px solid var(--xicms-primary, #333)" : "2px solid rgba(0,0,0,0.15)",
|
|
60
|
+
background: active ? "var(--xicms-primary, #333)" : "transparent",
|
|
61
|
+
color: active ? "var(--xicms-primary-contrast, #fff)" : "inherit",
|
|
62
|
+
borderRadius: 999,
|
|
63
|
+
padding: "0.5rem 1.1rem"
|
|
64
|
+
},
|
|
65
|
+
children: formatCents(c, settings.currency)
|
|
66
|
+
},
|
|
67
|
+
c
|
|
68
|
+
);
|
|
69
|
+
}),
|
|
70
|
+
block.allowCustom && /* @__PURE__ */ jsx(
|
|
71
|
+
"button",
|
|
72
|
+
{
|
|
73
|
+
type: "button",
|
|
74
|
+
className: "xicms-btn",
|
|
75
|
+
"aria-pressed": custom,
|
|
76
|
+
onClick: () => setCustom(true),
|
|
77
|
+
style: {
|
|
78
|
+
cursor: "pointer",
|
|
79
|
+
border: custom ? "2px solid var(--xicms-primary, #333)" : "2px solid rgba(0,0,0,0.15)",
|
|
80
|
+
background: "transparent",
|
|
81
|
+
borderRadius: 999,
|
|
82
|
+
padding: "0.5rem 1.1rem"
|
|
83
|
+
},
|
|
84
|
+
children: "Ander bedrag"
|
|
85
|
+
}
|
|
86
|
+
)
|
|
87
|
+
] }),
|
|
88
|
+
custom && /* @__PURE__ */ jsx(
|
|
89
|
+
TextInput,
|
|
90
|
+
{
|
|
91
|
+
label: `Bedrag in euro's (min. ${formatCents(block.customMinCents, settings.currency)})`,
|
|
92
|
+
isRequired: true,
|
|
93
|
+
placeholder: "10,00",
|
|
94
|
+
value: customEuro,
|
|
95
|
+
onChange: setCustomEuro
|
|
96
|
+
}
|
|
97
|
+
),
|
|
98
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "grid", gap: "0.9rem", gridTemplateColumns: "repeat(auto-fit, minmax(min(220px, 100%), 1fr))" }, children: [
|
|
99
|
+
/* @__PURE__ */ jsx(TextInput, { label: "Naam", isRequired: true, value: v.name, onChange: (x) => setV((p) => ({ ...p, name: x })) }),
|
|
100
|
+
/* @__PURE__ */ jsx(TextInput, { label: "E-mail", type: "email", isRequired: true, value: v.email, onChange: (x) => setV((p) => ({ ...p, email: x })) })
|
|
101
|
+
] }),
|
|
102
|
+
/* @__PURE__ */ jsx(
|
|
103
|
+
"input",
|
|
104
|
+
{
|
|
105
|
+
type: "text",
|
|
106
|
+
tabIndex: -1,
|
|
107
|
+
autoComplete: "off",
|
|
108
|
+
value: hp,
|
|
109
|
+
onChange: (e) => setHp(e.target.value),
|
|
110
|
+
style: { position: "absolute", left: "-9999px", width: 1, height: 1 },
|
|
111
|
+
"aria-hidden": true
|
|
112
|
+
}
|
|
113
|
+
),
|
|
114
|
+
error && /* @__PURE__ */ jsx(Text, { role: "alert", style: { color: "var(--color-error, #c0392b)" }, children: error }),
|
|
115
|
+
/* @__PURE__ */ jsx(
|
|
116
|
+
"button",
|
|
117
|
+
{
|
|
118
|
+
type: "submit",
|
|
119
|
+
className: "xicms-btn xicms-btn--primary",
|
|
120
|
+
disabled: busy || !valid,
|
|
121
|
+
style: { cursor: busy ? "wait" : "pointer", width: "fit-content", opacity: valid ? 1 : 0.5 },
|
|
122
|
+
children: busy ? "Bezig\u2026" : `${block.buttonLabel} ${valid ? formatCents(effectiveCents, settings.currency) : ""}`
|
|
123
|
+
}
|
|
124
|
+
)
|
|
125
|
+
] }) });
|
|
126
|
+
}
|
|
127
|
+
const donationBlock = defineBlock({
|
|
128
|
+
type: "donation",
|
|
129
|
+
schema: DonationBlockSchema,
|
|
130
|
+
Render: DonationRender,
|
|
131
|
+
label: "Donatie",
|
|
132
|
+
icon: "\u{1F49B}",
|
|
133
|
+
defaults: () => ({
|
|
134
|
+
heading: "Steun ons",
|
|
135
|
+
amountsCents: [500, 1e3, 2500],
|
|
136
|
+
allowCustom: true,
|
|
137
|
+
customMinCents: 100,
|
|
138
|
+
customMaxCents: 1e5,
|
|
139
|
+
buttonLabel: "Doneer"
|
|
140
|
+
})
|
|
141
|
+
});
|
|
142
|
+
export {
|
|
143
|
+
donationBlock
|
|
144
|
+
};
|
|
145
|
+
//# sourceMappingURL=Donation.js.map
|
|
@@ -0,0 +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\";\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 [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(\"Doneren is niet gelukt. Probeer het later opnieuw.\");\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 Ander bedrag\n </button>\n )}\n </div>\n\n {custom && (\n <TextInput\n label={`Bedrag in euro's (min. ${formatCents(block.customMinCents, settings.currency)})`}\n isRequired\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=\"Naam\" isRequired value={v.name} onChange={(x) => setV((p) => ({ ...p, name: x }))} />\n <TextInput label=\"E-mail\" type=\"email\" isRequired 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 ? \"Bezig…\" : `${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":[]}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
import { OrderStatusBlockSchema } from "../../../schema/shop";
|
|
5
|
+
import { defineBlock } from "../../../blocks/registry";
|
|
6
|
+
import { Card, Heading, Text } from "../../../blocks/ui";
|
|
7
|
+
import { cartClear } from "../cartStore";
|
|
8
|
+
import { readQueryParam, useShopSettings } from "../lib";
|
|
9
|
+
function OrderStatusRender({ block, siteId }) {
|
|
10
|
+
const settings = useShopSettings();
|
|
11
|
+
const [state, setState] = useState({ status: null, order: null });
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
const status = readQueryParam("status");
|
|
14
|
+
setState({ status, order: readQueryParam("order") });
|
|
15
|
+
if (status === "success") cartClear(siteId);
|
|
16
|
+
}, [siteId]);
|
|
17
|
+
const cancelled = state.status === "cancelled";
|
|
18
|
+
return /* @__PURE__ */ jsxs(Card, { padding: 4, variant: cancelled ? "muted" : "green", className: "xicms-shop-orderstatus", role: "status", children: [
|
|
19
|
+
/* @__PURE__ */ jsx(Heading, { level: 1, "data-xicms-edit": cancelled ? "cancelledTitle" : "successTitle", children: cancelled ? block.cancelledTitle : block.successTitle }),
|
|
20
|
+
/* @__PURE__ */ jsx(Text, { display: "block", style: { marginTop: 8 }, "data-xicms-edit": cancelled ? "cancelledText" : "successText", children: cancelled ? block.cancelledText : block.successText }),
|
|
21
|
+
!cancelled && state.order && /* @__PURE__ */ jsxs(Text, { display: "block", style: { marginTop: 8 }, children: [
|
|
22
|
+
"Je bestelnummer is ",
|
|
23
|
+
/* @__PURE__ */ jsx("strong", { children: state.order }),
|
|
24
|
+
"."
|
|
25
|
+
] }),
|
|
26
|
+
/* @__PURE__ */ jsx(Text, { display: "block", style: { marginTop: 12 }, children: /* @__PURE__ */ jsx("a", { href: cancelled ? settings.cartPath : settings.shopPath, children: cancelled ? "Terug naar je winkelwagen" : "Verder winkelen" }) })
|
|
27
|
+
] });
|
|
28
|
+
}
|
|
29
|
+
const orderStatusBlock = defineBlock({
|
|
30
|
+
type: "orderStatus",
|
|
31
|
+
schema: OrderStatusBlockSchema,
|
|
32
|
+
Render: OrderStatusRender,
|
|
33
|
+
label: "Bestelstatus (bedankt-pagina)",
|
|
34
|
+
icon: "\u2705",
|
|
35
|
+
defaults: () => ({})
|
|
36
|
+
});
|
|
37
|
+
export {
|
|
38
|
+
orderStatusBlock
|
|
39
|
+
};
|
|
40
|
+
//# sourceMappingURL=OrderStatus.js.map
|
|
@@ -0,0 +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 { readQueryParam, useShopSettings } from \"../lib\";\n\ntype Blk = z.infer<typeof OrderStatusBlockSchema>;\n\n/**\n * Bedankt-/statuspagina na terugkeer uit Stripe. Bij `?status=success` wordt de cart geleegd\n * (pas hier — een geannuleerde betaling behoudt de winkelwagen). De echte orderstatus wordt\n * server-side gezet door de Stripe-webhook; dit block toont alleen de redirect-uitkomst.\n */\nfunction OrderStatusRender({ block, siteId }: BlockRenderProps<Blk>) {\n const settings = useShopSettings();\n const [state, setState] = useState<{ status: string | null; order: string | null }>({ status: null, order: null });\n\n useEffect(() => {\n const status = readQueryParam(\"status\");\n setState({ status, order: readQueryParam(\"order\") });\n if (status === \"success\") cartClear(siteId);\n }, [siteId]);\n\n const cancelled = state.status === \"cancelled\";\n\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 && state.order && (\n <Text display=\"block\" style={{ marginTop: 8 }}>\n Je bestelnummer is <strong>{state.order}</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":";AA+BM,cAOE,YAPF;AA7BN,SAAS,WAAW,gBAAgB;AAEpC,SAAS,8BAA8B;AACvC,SAAS,mBAA0C;AACnD,SAAS,MAAM,SAAS,YAAY;AACpC,SAAS,iBAAiB;AAC1B,SAAS,gBAAgB,uBAAuB;AAShD,SAAS,kBAAkB,EAAE,OAAO,OAAO,GAA0B;AACnE,QAAM,WAAW,gBAAgB;AACjC,QAAM,CAAC,OAAO,QAAQ,IAAI,SAA0D,EAAE,QAAQ,MAAM,OAAO,KAAK,CAAC;AAEjH,YAAU,MAAM;AACd,UAAM,SAAS,eAAe,QAAQ;AACtC,aAAS,EAAE,QAAQ,OAAO,eAAe,OAAO,EAAE,CAAC;AACnD,QAAI,WAAW,UAAW,WAAU,MAAM;AAAA,EAC5C,GAAG,CAAC,MAAM,CAAC;AAEX,QAAM,YAAY,MAAM,WAAW;AAEnC,SACE,qBAAC,QAAK,SAAS,GAAG,SAAS,YAAY,UAAU,SAAS,WAAU,0BAAyB,MAAK,UAChG;AAAA,wBAAC,WAAQ,OAAO,GAAG,mBAAiB,YAAY,mBAAmB,gBAChE,sBAAY,MAAM,iBAAiB,MAAM,cAC5C;AAAA,IACA,oBAAC,QAAK,SAAQ,SAAQ,OAAO,EAAE,WAAW,EAAE,GAAG,mBAAiB,YAAY,kBAAkB,eAC3F,sBAAY,MAAM,gBAAgB,MAAM,aAC3C;AAAA,IACC,CAAC,aAAa,MAAM,SACnB,qBAAC,QAAK,SAAQ,SAAQ,OAAO,EAAE,WAAW,EAAE,GAAG;AAAA;AAAA,MAC1B,oBAAC,YAAQ,gBAAM,OAAM;AAAA,MAAS;AAAA,OACnD;AAAA,IAEF,oBAAC,QAAK,SAAQ,SAAQ,OAAO,EAAE,WAAW,GAAG,GAC3C,8BAAC,OAAE,MAAM,YAAY,SAAS,WAAW,SAAS,UAC/C,sBAAY,8BAA8B,mBAC7C,GACF;AAAA,KACF;AAEJ;AAEO,MAAM,mBAAmB,YAAY;AAAA,EAC1C,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,MAAM;AAAA,EACN,UAAU,OAAO,CAAC;AACpB,CAAC;","names":[]}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useMemo, useState } from "react";
|
|
4
|
+
import { ProductDetailBlockSchema, formatCents, productPriceCents, variantKeyFor } from "../../../schema/shop";
|
|
5
|
+
import { defineBlock } from "../../../blocks/registry";
|
|
6
|
+
import { Card, Heading, Text } from "../../../blocks/ui";
|
|
7
|
+
import { Selector } from "@astryxdesign/core/Selector";
|
|
8
|
+
import { cartAdd } from "../cartStore";
|
|
9
|
+
import { fetchProductBySlug, readQueryParam, useShopSettings, variantLabelFor } from "../lib";
|
|
10
|
+
function ProductDetailRender({ block, siteId }) {
|
|
11
|
+
const settings = useShopSettings();
|
|
12
|
+
const [product, setProduct] = useState(void 0);
|
|
13
|
+
const [choices, setChoices] = useState({});
|
|
14
|
+
const [qty, setQty] = useState(1);
|
|
15
|
+
const [imageIdx, setImageIdx] = useState(0);
|
|
16
|
+
const [added, setAdded] = useState(false);
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
const slug = block.slug || readQueryParam("p") || "";
|
|
19
|
+
if (!slug) {
|
|
20
|
+
setProduct(null);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
void fetchProductBySlug(siteId, slug).then(setProduct).catch(() => setProduct(null));
|
|
24
|
+
}, [siteId, block.slug]);
|
|
25
|
+
const options = product?.options ?? [];
|
|
26
|
+
const allChosen = options.every((_, i) => Boolean(choices[i]));
|
|
27
|
+
const variantKey = product && options.length > 0 && allChosen ? variantKeyFor(options.map((_, i) => choices[i])) : void 0;
|
|
28
|
+
const variant = variantKey ? product?.variants?.find((v) => v.key === variantKey) : void 0;
|
|
29
|
+
const priceCents = useMemo(() => {
|
|
30
|
+
if (!product) return 0;
|
|
31
|
+
return productPriceCents(product, variantKey);
|
|
32
|
+
}, [product, variantKey]);
|
|
33
|
+
const outOfStock = Boolean(
|
|
34
|
+
product?.trackStock && (options.length === 0 ? product.stock !== void 0 && product.stock <= 0 : variant?.stock !== void 0 && variant.stock <= 0)
|
|
35
|
+
);
|
|
36
|
+
const variantInactive = variant !== void 0 && variant.active === false;
|
|
37
|
+
if (product === void 0) return null;
|
|
38
|
+
if (product === null) {
|
|
39
|
+
return /* @__PURE__ */ jsx(Card, { padding: 4, variant: "muted", children: /* @__PURE__ */ jsxs(Text, { children: [
|
|
40
|
+
"Product niet gevonden. ",
|
|
41
|
+
/* @__PURE__ */ jsx("a", { href: settings.shopPath, children: "Terug naar de winkel" })
|
|
42
|
+
] }) });
|
|
43
|
+
}
|
|
44
|
+
function addToCart() {
|
|
45
|
+
if (!product) return;
|
|
46
|
+
cartAdd(
|
|
47
|
+
siteId,
|
|
48
|
+
{
|
|
49
|
+
productId: product.id,
|
|
50
|
+
slug: product.slug,
|
|
51
|
+
name: product.name,
|
|
52
|
+
variantKey,
|
|
53
|
+
variantLabel: variantKey ? variantLabelFor(options, variantKey) : void 0,
|
|
54
|
+
unitPriceCents: priceCents,
|
|
55
|
+
imageUrl: product.images?.[0]
|
|
56
|
+
},
|
|
57
|
+
qty
|
|
58
|
+
);
|
|
59
|
+
setAdded(true);
|
|
60
|
+
}
|
|
61
|
+
const canOrder = (options.length === 0 || allChosen) && !outOfStock && !variantInactive;
|
|
62
|
+
return /* @__PURE__ */ jsxs(
|
|
63
|
+
"div",
|
|
64
|
+
{
|
|
65
|
+
className: "xicms-shop-detail",
|
|
66
|
+
style: { display: "grid", gap: "2rem", gridTemplateColumns: "repeat(auto-fit, minmax(min(320px, 100%), 1fr))" },
|
|
67
|
+
children: [
|
|
68
|
+
/* @__PURE__ */ jsxs("div", { className: "xicms-shop-detail__media", children: [
|
|
69
|
+
product.images?.[imageIdx] ? /* @__PURE__ */ jsx(
|
|
70
|
+
"img",
|
|
71
|
+
{
|
|
72
|
+
src: product.images[imageIdx],
|
|
73
|
+
alt: product.name,
|
|
74
|
+
style: { width: "100%", aspectRatio: "1/1", objectFit: "cover", borderRadius: "var(--xicms-radius, 12px)" }
|
|
75
|
+
}
|
|
76
|
+
) : /* @__PURE__ */ jsx("div", { style: { width: "100%", aspectRatio: "1/1", display: "grid", placeItems: "center", background: "var(--xicms-muted, #f1f1f1)", borderRadius: "var(--xicms-radius, 12px)" }, children: /* @__PURE__ */ jsx("span", { style: { fontSize: "3rem", opacity: 0.4 }, children: "\u{1F5BC}\uFE0F" }) }),
|
|
77
|
+
(product.images?.length ?? 0) > 1 && /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: 8, marginTop: 8, flexWrap: "wrap" }, children: product.images.map((url, i) => /* @__PURE__ */ jsx(
|
|
78
|
+
"button",
|
|
79
|
+
{
|
|
80
|
+
type: "button",
|
|
81
|
+
onClick: () => setImageIdx(i),
|
|
82
|
+
style: {
|
|
83
|
+
padding: 0,
|
|
84
|
+
border: i === imageIdx ? "2px solid var(--xicms-primary, #333)" : "2px solid transparent",
|
|
85
|
+
borderRadius: 8,
|
|
86
|
+
cursor: "pointer",
|
|
87
|
+
background: "none"
|
|
88
|
+
},
|
|
89
|
+
children: /* @__PURE__ */ jsx("img", { src: url, alt: "", style: { width: 64, height: 64, objectFit: "cover", borderRadius: 6, display: "block" } })
|
|
90
|
+
},
|
|
91
|
+
url + i
|
|
92
|
+
)) })
|
|
93
|
+
] }),
|
|
94
|
+
/* @__PURE__ */ jsxs("div", { className: "xicms-shop-detail__info", children: [
|
|
95
|
+
/* @__PURE__ */ jsx(Heading, { level: 1, children: product.name }),
|
|
96
|
+
/* @__PURE__ */ jsxs(Text, { size: "xl", weight: "bold", display: "block", style: { margin: "0.5rem 0 1rem" }, children: [
|
|
97
|
+
product.compareAtCents !== void 0 && product.compareAtCents > priceCents && /* @__PURE__ */ jsx("s", { style: { opacity: 0.6, marginRight: 8, fontWeight: "normal" }, children: formatCents(product.compareAtCents, settings.currency) }),
|
|
98
|
+
formatCents(priceCents, settings.currency)
|
|
99
|
+
] }),
|
|
100
|
+
product.description && // Platte tekst met behoud van regeleinden — bewust geen HTML (zie schema/shop.ts).
|
|
101
|
+
/* @__PURE__ */ jsx(Text, { display: "block", style: { whiteSpace: "pre-wrap" }, children: product.description }),
|
|
102
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "grid", gap: "0.75rem", maxWidth: 420, marginTop: "1rem" }, children: [
|
|
103
|
+
options.map((opt, i) => /* @__PURE__ */ jsx(
|
|
104
|
+
Selector,
|
|
105
|
+
{
|
|
106
|
+
label: opt.name,
|
|
107
|
+
placeholder: "Kies\u2026",
|
|
108
|
+
options: opt.values,
|
|
109
|
+
value: choices[i] ?? "",
|
|
110
|
+
onChange: (v) => {
|
|
111
|
+
setChoices((prev) => ({ ...prev, [i]: v ?? "" }));
|
|
112
|
+
setAdded(false);
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
opt.name
|
|
116
|
+
)),
|
|
117
|
+
/* @__PURE__ */ jsxs("label", { className: "xicms-shop-detail__qty", style: { display: "grid", gap: 4 }, children: [
|
|
118
|
+
/* @__PURE__ */ jsx("span", { style: { fontSize: "0.9rem" }, children: "Aantal" }),
|
|
119
|
+
/* @__PURE__ */ jsx(
|
|
120
|
+
"input",
|
|
121
|
+
{
|
|
122
|
+
className: "xicms-input",
|
|
123
|
+
type: "number",
|
|
124
|
+
min: 1,
|
|
125
|
+
max: 999,
|
|
126
|
+
value: qty,
|
|
127
|
+
onChange: (e) => setQty(Math.max(1, Math.min(999, Number(e.target.value) || 1))),
|
|
128
|
+
style: { width: 90 }
|
|
129
|
+
}
|
|
130
|
+
)
|
|
131
|
+
] }),
|
|
132
|
+
outOfStock && /* @__PURE__ */ jsx(Text, { style: { color: "var(--color-error, #c0392b)" }, children: "Uitverkocht in deze uitvoering." }),
|
|
133
|
+
variantInactive && /* @__PURE__ */ jsx(Text, { style: { color: "var(--color-error, #c0392b)" }, children: "Deze combinatie is niet leverbaar." }),
|
|
134
|
+
added ? /* @__PURE__ */ jsxs("div", { role: "status", style: { display: "grid", gap: 8 }, children: [
|
|
135
|
+
/* @__PURE__ */ jsx(Text, { children: "\u2713 Toegevoegd aan je winkelwagen." }),
|
|
136
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 12, flexWrap: "wrap" }, children: [
|
|
137
|
+
/* @__PURE__ */ jsx("a", { className: "xicms-btn xicms-btn--primary", href: settings.cartPath, children: "Naar winkelwagen" }),
|
|
138
|
+
/* @__PURE__ */ jsx("a", { className: "xicms-btn xicms-btn--secondary", href: settings.shopPath, children: "Verder winkelen" })
|
|
139
|
+
] })
|
|
140
|
+
] }) : /* @__PURE__ */ jsx(
|
|
141
|
+
"button",
|
|
142
|
+
{
|
|
143
|
+
type: "button",
|
|
144
|
+
className: "xicms-btn xicms-btn--primary",
|
|
145
|
+
disabled: !canOrder,
|
|
146
|
+
onClick: addToCart,
|
|
147
|
+
style: { cursor: canOrder ? "pointer" : "not-allowed", opacity: canOrder ? 1 : 0.5, width: "fit-content" },
|
|
148
|
+
children: "In winkelwagen"
|
|
149
|
+
}
|
|
150
|
+
)
|
|
151
|
+
] })
|
|
152
|
+
] })
|
|
153
|
+
]
|
|
154
|
+
}
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
const productDetailBlock = defineBlock({
|
|
158
|
+
type: "productDetail",
|
|
159
|
+
schema: ProductDetailBlockSchema,
|
|
160
|
+
Render: ProductDetailRender,
|
|
161
|
+
label: "Productdetail",
|
|
162
|
+
icon: "\u{1F3F7}\uFE0F",
|
|
163
|
+
defaults: () => ({})
|
|
164
|
+
});
|
|
165
|
+
export {
|
|
166
|
+
productDetailBlock
|
|
167
|
+
};
|
|
168
|
+
//# sourceMappingURL=ProductDetail.js.map
|
|
@@ -0,0 +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(() => setProduct(null));\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":[]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { z } from "zod";
|
|
2
|
+
import { type StoredProduct } from "../lib";
|
|
3
|
+
/** Prijsregel met optionele doorgestreepte "van"-prijs en "vanaf" bij variant-prijzen. */
|
|
4
|
+
export declare function PriceLine({ product, currency }: {
|
|
5
|
+
product: StoredProduct;
|
|
6
|
+
currency: string;
|
|
7
|
+
}): import("react").JSX.Element;
|
|
8
|
+
export declare const productGridBlock: import("../../..").BlockDefinition<z.ZodTypeAny>;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useMemo, useState } from "react";
|
|
4
|
+
import { ProductGridBlockSchema, formatCents } from "../../../schema/shop";
|
|
5
|
+
import { defineBlock } from "../../../blocks/registry";
|
|
6
|
+
import { Card, Text } from "../../../blocks/ui";
|
|
7
|
+
import { Heading } from "../../../blocks/ui";
|
|
8
|
+
import { cartAdd } from "../cartStore";
|
|
9
|
+
import {
|
|
10
|
+
fetchActiveProducts,
|
|
11
|
+
fetchShopCategories,
|
|
12
|
+
readQueryParam,
|
|
13
|
+
useShopSettings
|
|
14
|
+
} from "../lib";
|
|
15
|
+
function PriceLine({ product, currency }) {
|
|
16
|
+
const variantPrices = (product.variants ?? []).filter((v) => v.priceCents !== void 0).map((v) => v.priceCents);
|
|
17
|
+
const min = Math.min(product.priceCents, ...variantPrices);
|
|
18
|
+
const hasRange = variantPrices.some((p) => p !== product.priceCents);
|
|
19
|
+
return /* @__PURE__ */ jsxs("span", { className: "xicms-shop-price", children: [
|
|
20
|
+
product.compareAtCents !== void 0 && product.compareAtCents > min && /* @__PURE__ */ jsx("s", { style: { opacity: 0.6, marginRight: 6 }, children: formatCents(product.compareAtCents, currency) }),
|
|
21
|
+
hasRange ? `vanaf ${formatCents(min, currency)}` : formatCents(min, currency)
|
|
22
|
+
] });
|
|
23
|
+
}
|
|
24
|
+
function ProductCard({
|
|
25
|
+
product,
|
|
26
|
+
siteId,
|
|
27
|
+
currency,
|
|
28
|
+
detailPath,
|
|
29
|
+
cartPath
|
|
30
|
+
}) {
|
|
31
|
+
const [added, setAdded] = useState(false);
|
|
32
|
+
const href = `${detailPath}?p=${encodeURIComponent(product.slug)}`;
|
|
33
|
+
const simple = (product.options ?? []).length === 0;
|
|
34
|
+
return /* @__PURE__ */ jsxs(Card, { padding: 0, className: "xicms-shop-card", children: [
|
|
35
|
+
/* @__PURE__ */ jsxs("a", { href, style: { textDecoration: "none", color: "inherit", display: "block" }, children: [
|
|
36
|
+
product.images?.[0] ? /* @__PURE__ */ jsx(
|
|
37
|
+
"img",
|
|
38
|
+
{
|
|
39
|
+
src: product.images[0],
|
|
40
|
+
alt: product.name,
|
|
41
|
+
loading: "lazy",
|
|
42
|
+
style: { width: "100%", aspectRatio: "1/1", objectFit: "cover", display: "block", borderRadius: "inherit" }
|
|
43
|
+
}
|
|
44
|
+
) : /* @__PURE__ */ jsx("div", { style: { width: "100%", aspectRatio: "1/1", display: "grid", placeItems: "center", background: "var(--xicms-muted, #f1f1f1)", borderRadius: "inherit" }, children: /* @__PURE__ */ jsx("span", { style: { fontSize: "2rem", opacity: 0.4 }, children: "\u{1F5BC}\uFE0F" }) }),
|
|
45
|
+
/* @__PURE__ */ jsxs("div", { style: { padding: "0.8rem 1rem 0.25rem" }, children: [
|
|
46
|
+
/* @__PURE__ */ jsx(Text, { weight: "bold", display: "block", children: product.name }),
|
|
47
|
+
/* @__PURE__ */ jsx(Text, { size: "sm", display: "block", style: { marginTop: 2 }, children: /* @__PURE__ */ jsx(PriceLine, { product, currency }) })
|
|
48
|
+
] })
|
|
49
|
+
] }),
|
|
50
|
+
/* @__PURE__ */ jsx("div", { style: { padding: "0.5rem 1rem 1rem" }, children: simple ? added ? /* @__PURE__ */ jsx("a", { className: "xicms-shop-card__added", href: cartPath, style: { fontSize: "0.9rem" }, children: "\u2713 Toegevoegd \u2014 naar winkelwagen" }) : /* @__PURE__ */ jsx(
|
|
51
|
+
"button",
|
|
52
|
+
{
|
|
53
|
+
type: "button",
|
|
54
|
+
className: "xicms-btn xicms-btn--primary",
|
|
55
|
+
style: { cursor: "pointer" },
|
|
56
|
+
onClick: () => {
|
|
57
|
+
cartAdd(siteId, {
|
|
58
|
+
productId: product.id,
|
|
59
|
+
slug: product.slug,
|
|
60
|
+
name: product.name,
|
|
61
|
+
unitPriceCents: product.priceCents,
|
|
62
|
+
imageUrl: product.images?.[0]
|
|
63
|
+
});
|
|
64
|
+
setAdded(true);
|
|
65
|
+
},
|
|
66
|
+
children: "In winkelwagen"
|
|
67
|
+
}
|
|
68
|
+
) : /* @__PURE__ */ jsx("a", { className: "xicms-btn xicms-btn--secondary", href, children: "Bekijk opties" }) })
|
|
69
|
+
] });
|
|
70
|
+
}
|
|
71
|
+
function ProductGridRender({ block, siteId }) {
|
|
72
|
+
const settings = useShopSettings();
|
|
73
|
+
const [products, setProducts] = useState(null);
|
|
74
|
+
const [cats, setCats] = useState([]);
|
|
75
|
+
const [queryCat, setQueryCat] = useState(null);
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
void fetchActiveProducts(siteId).then(setProducts).catch(() => setProducts([]));
|
|
78
|
+
void fetchShopCategories(siteId).then(setCats).catch(() => setCats([]));
|
|
79
|
+
if (block.useQueryCategory && !block.categorySlug) setQueryCat(readQueryParam("cat"));
|
|
80
|
+
}, [siteId, block.useQueryCategory, block.categorySlug]);
|
|
81
|
+
const activeSlug = block.categorySlug || queryCat || null;
|
|
82
|
+
const activeCat = activeSlug ? cats.find((c) => c.slug === activeSlug) ?? null : null;
|
|
83
|
+
const visible = useMemo(() => {
|
|
84
|
+
if (!products) return [];
|
|
85
|
+
let list = products;
|
|
86
|
+
if (activeCat) list = list.filter((p) => (p.categoryIds ?? []).includes(activeCat.id));
|
|
87
|
+
if (block.featuredOnly) list = list.filter((p) => p.featured);
|
|
88
|
+
const sorted = [...list];
|
|
89
|
+
if (block.sort === "name") sorted.sort((a, b) => a.name.localeCompare(b.name, "nl"));
|
|
90
|
+
else if (block.sort === "priceAsc") sorted.sort((a, b) => a.priceCents - b.priceCents);
|
|
91
|
+
else if (block.sort === "priceDesc") sorted.sort((a, b) => b.priceCents - a.priceCents);
|
|
92
|
+
else sorted.sort((a, b) => (a.order ?? 0) - (b.order ?? 0) || a.name.localeCompare(b.name, "nl"));
|
|
93
|
+
return sorted.slice(0, block.limit);
|
|
94
|
+
}, [products, activeCat, block.featuredOnly, block.sort, block.limit]);
|
|
95
|
+
if (products === null) return null;
|
|
96
|
+
return /* @__PURE__ */ jsxs("div", { className: "xicms-shop-grid", children: [
|
|
97
|
+
(block.heading || activeCat) && /* @__PURE__ */ jsx(Heading, { level: 2, "data-xicms-edit": "heading", children: activeCat?.name ?? block.heading }),
|
|
98
|
+
activeCat && /* @__PURE__ */ jsx(Text, { size: "sm", display: "block", style: { marginTop: 4 }, children: /* @__PURE__ */ jsx("a", { href: settings.shopPath, children: "\u2190 Alle categorie\xEBn" }) }),
|
|
99
|
+
visible.length === 0 ? /* @__PURE__ */ jsx(Card, { padding: 4, variant: "muted", style: { marginTop: "1rem" }, children: /* @__PURE__ */ jsx(Text, { children: "Geen producten gevonden." }) }) : /* @__PURE__ */ jsx(
|
|
100
|
+
"div",
|
|
101
|
+
{
|
|
102
|
+
className: "xicms-shop-grid__items",
|
|
103
|
+
style: {
|
|
104
|
+
display: "grid",
|
|
105
|
+
gridTemplateColumns: `repeat(auto-fill, minmax(min(240px, calc(100%/${block.columns} - 1rem)), 1fr))`,
|
|
106
|
+
gap: "1.25rem",
|
|
107
|
+
marginTop: "1.25rem"
|
|
108
|
+
},
|
|
109
|
+
children: visible.map((p) => /* @__PURE__ */ jsx(
|
|
110
|
+
ProductCard,
|
|
111
|
+
{
|
|
112
|
+
product: p,
|
|
113
|
+
siteId,
|
|
114
|
+
currency: settings.currency,
|
|
115
|
+
detailPath: settings.detailPath,
|
|
116
|
+
cartPath: settings.cartPath
|
|
117
|
+
},
|
|
118
|
+
p.id
|
|
119
|
+
))
|
|
120
|
+
}
|
|
121
|
+
)
|
|
122
|
+
] });
|
|
123
|
+
}
|
|
124
|
+
const productGridBlock = defineBlock({
|
|
125
|
+
type: "productGrid",
|
|
126
|
+
schema: ProductGridBlockSchema,
|
|
127
|
+
Render: ProductGridRender,
|
|
128
|
+
label: "Producten-overzicht",
|
|
129
|
+
icon: "\u{1F6CD}\uFE0F",
|
|
130
|
+
defaults: () => ({ useQueryCategory: true, columns: 3, limit: 60, sort: "order" })
|
|
131
|
+
});
|
|
132
|
+
export {
|
|
133
|
+
PriceLine,
|
|
134
|
+
productGridBlock
|
|
135
|
+
};
|
|
136
|
+
//# sourceMappingURL=ProductGrid.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/modules/shop/blocks/ProductGrid.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useMemo, useState } from \"react\";\nimport type { z } from \"zod\";\nimport { ProductGridBlockSchema, formatCents } from \"../../../schema/shop\";\nimport { defineBlock, type BlockRenderProps } from \"../../../blocks/registry\";\nimport { Card, Text } from \"../../../blocks/ui\";\nimport { Heading } from \"../../../blocks/ui\";\nimport { cartAdd } from \"../cartStore\";\nimport {\n fetchActiveProducts,\n fetchShopCategories,\n readQueryParam,\n useShopSettings,\n type StoredProduct,\n type StoredShopCategory,\n} from \"../lib\";\n\ntype Blk = z.infer<typeof ProductGridBlockSchema>;\n\n/** Prijsregel met optionele doorgestreepte \"van\"-prijs en \"vanaf\" bij variant-prijzen. */\nexport function PriceLine({ product, currency }: { product: StoredProduct; currency: string }) {\n const variantPrices = (product.variants ?? [])\n .filter((v) => v.priceCents !== undefined)\n .map((v) => v.priceCents as number);\n const min = Math.min(product.priceCents, ...variantPrices);\n const hasRange = variantPrices.some((p) => p !== product.priceCents);\n return (\n <span className=\"xicms-shop-price\">\n {product.compareAtCents !== undefined && product.compareAtCents > min && (\n <s style={{ opacity: 0.6, marginRight: 6 }}>{formatCents(product.compareAtCents, currency)}</s>\n )}\n {hasRange ? `vanaf ${formatCents(min, currency)}` : formatCents(min, currency)}\n </span>\n );\n}\n\nfunction ProductCard({\n product,\n siteId,\n currency,\n detailPath,\n cartPath,\n}: {\n product: StoredProduct;\n siteId: string;\n currency: string;\n detailPath: string;\n cartPath: string;\n}) {\n const [added, setAdded] = useState(false);\n const href = `${detailPath}?p=${encodeURIComponent(product.slug)}`;\n const simple = (product.options ?? []).length === 0;\n\n return (\n <Card padding={0} className=\"xicms-shop-card\">\n <a href={href} style={{ textDecoration: \"none\", color: \"inherit\", display: \"block\" }}>\n {product.images?.[0] ? (\n <img\n src={product.images[0]}\n alt={product.name}\n loading=\"lazy\"\n style={{ width: \"100%\", aspectRatio: \"1/1\", objectFit: \"cover\", display: \"block\", borderRadius: \"inherit\" }}\n />\n ) : (\n <div style={{ width: \"100%\", aspectRatio: \"1/1\", display: \"grid\", placeItems: \"center\", background: \"var(--xicms-muted, #f1f1f1)\", borderRadius: \"inherit\" }}>\n <span style={{ fontSize: \"2rem\", opacity: 0.4 }}>🖼️</span>\n </div>\n )}\n <div style={{ padding: \"0.8rem 1rem 0.25rem\" }}>\n <Text weight=\"bold\" display=\"block\">{product.name}</Text>\n <Text size=\"sm\" display=\"block\" style={{ marginTop: 2 }}>\n <PriceLine product={product} currency={currency} />\n </Text>\n </div>\n </a>\n <div style={{ padding: \"0.5rem 1rem 1rem\" }}>\n {simple ? (\n added ? (\n <a className=\"xicms-shop-card__added\" href={cartPath} style={{ fontSize: \"0.9rem\" }}>\n ✓ Toegevoegd — naar winkelwagen\n </a>\n ) : (\n <button\n type=\"button\"\n className=\"xicms-btn xicms-btn--primary\"\n style={{ cursor: \"pointer\" }}\n onClick={() => {\n cartAdd(siteId, {\n productId: product.id,\n slug: product.slug,\n name: product.name,\n unitPriceCents: product.priceCents,\n imageUrl: product.images?.[0],\n });\n setAdded(true);\n }}\n >\n In winkelwagen\n </button>\n )\n ) : (\n <a className=\"xicms-btn xicms-btn--secondary\" href={href}>\n Bekijk opties\n </a>\n )}\n </div>\n </Card>\n );\n}\n\nfunction ProductGridRender({ block, siteId }: BlockRenderProps<Blk>) {\n const settings = useShopSettings();\n const [products, setProducts] = useState<StoredProduct[] | null>(null);\n const [cats, setCats] = useState<StoredShopCategory[]>([]);\n const [queryCat, setQueryCat] = useState<string | null>(null);\n\n useEffect(() => {\n void fetchActiveProducts(siteId).then(setProducts).catch(() => setProducts([]));\n void fetchShopCategories(siteId).then(setCats).catch(() => setCats([]));\n if (block.useQueryCategory && !block.categorySlug) setQueryCat(readQueryParam(\"cat\"));\n }, [siteId, block.useQueryCategory, block.categorySlug]);\n\n const activeSlug = block.categorySlug || queryCat || null;\n const activeCat = activeSlug ? cats.find((c) => c.slug === activeSlug) ?? null : null;\n\n const visible = useMemo(() => {\n if (!products) return [];\n let list = products;\n if (activeCat) list = list.filter((p) => (p.categoryIds ?? []).includes(activeCat.id));\n if (block.featuredOnly) list = list.filter((p) => p.featured);\n const sorted = [...list];\n if (block.sort === \"name\") sorted.sort((a, b) => a.name.localeCompare(b.name, \"nl\"));\n else if (block.sort === \"priceAsc\") sorted.sort((a, b) => a.priceCents - b.priceCents);\n else if (block.sort === \"priceDesc\") sorted.sort((a, b) => b.priceCents - a.priceCents);\n else sorted.sort((a, b) => (a.order ?? 0) - (b.order ?? 0) || a.name.localeCompare(b.name, \"nl\"));\n return sorted.slice(0, block.limit);\n }, [products, activeCat, block.featuredOnly, block.sort, block.limit]);\n\n if (products === null) return null;\n\n return (\n <div className=\"xicms-shop-grid\">\n {(block.heading || activeCat) && (\n <Heading level={2} data-xicms-edit=\"heading\">\n {activeCat?.name ?? block.heading}\n </Heading>\n )}\n {activeCat && (\n <Text size=\"sm\" display=\"block\" style={{ marginTop: 4 }}>\n <a href={settings.shopPath}>← Alle categorieën</a>\n </Text>\n )}\n {visible.length === 0 ? (\n <Card padding={4} variant=\"muted\" style={{ marginTop: \"1rem\" }}>\n <Text>Geen producten gevonden.</Text>\n </Card>\n ) : (\n <div\n className=\"xicms-shop-grid__items\"\n style={{\n display: \"grid\",\n gridTemplateColumns: `repeat(auto-fill, minmax(min(240px, calc(100%/${block.columns} - 1rem)), 1fr))`,\n gap: \"1.25rem\",\n marginTop: \"1.25rem\",\n }}\n >\n {visible.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 />\n ))}\n </div>\n )}\n </div>\n );\n}\n\nexport const productGridBlock = defineBlock({\n type: \"productGrid\",\n schema: ProductGridBlockSchema,\n Render: ProductGridRender,\n label: \"Producten-overzicht\",\n icon: \"🛍️\",\n defaults: () => ({ useQueryCategory: true, columns: 3, limit: 60, sort: \"order\" }),\n});\n"],"mappings":"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","names":[]}
|