@xiwebdesign/xicms-core 5.4.0 → 5.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/dist/admin/panelRegistry.js +3 -0
  2. package/dist/admin/panelRegistry.js.map +1 -1
  3. package/dist/admin/shop/CategoriesTab.d.ts +3 -0
  4. package/dist/admin/shop/CategoriesTab.js +140 -0
  5. package/dist/admin/shop/CategoriesTab.js.map +1 -0
  6. package/dist/admin/shop/ConnectionsTab.d.ts +9 -0
  7. package/dist/admin/shop/ConnectionsTab.js +158 -0
  8. package/dist/admin/shop/ConnectionsTab.js.map +1 -0
  9. package/dist/admin/shop/OrdersTab.d.ts +3 -0
  10. package/dist/admin/shop/OrdersTab.js +236 -0
  11. package/dist/admin/shop/OrdersTab.js.map +1 -0
  12. package/dist/admin/shop/ProductsTab.d.ts +3 -0
  13. package/dist/admin/shop/ProductsTab.js +397 -0
  14. package/dist/admin/shop/ProductsTab.js.map +1 -0
  15. package/dist/admin/shop/ShopPanel.d.ts +4 -0
  16. package/dist/admin/shop/ShopPanel.js +25 -0
  17. package/dist/admin/shop/ShopPanel.js.map +1 -0
  18. package/dist/admin/shop/lib.d.ts +16 -0
  19. package/dist/admin/shop/lib.js +46 -0
  20. package/dist/admin/shop/lib.js.map +1 -0
  21. package/dist/admin.css +1 -1
  22. package/dist/modules/builtins.d.ts +1 -0
  23. package/dist/modules/builtins.js +4 -0
  24. package/dist/modules/builtins.js.map +1 -1
  25. package/dist/modules/index.d.ts +4 -0
  26. package/dist/modules/index.js +12 -0
  27. package/dist/modules/index.js.map +1 -1
  28. package/dist/modules/shop/blocks/Cart.d.ts +2 -0
  29. package/dist/modules/shop/blocks/Cart.js +91 -0
  30. package/dist/modules/shop/blocks/Cart.js.map +1 -0
  31. package/dist/modules/shop/blocks/Checkout.d.ts +2 -0
  32. package/dist/modules/shop/blocks/Checkout.js +126 -0
  33. package/dist/modules/shop/blocks/Checkout.js.map +1 -0
  34. package/dist/modules/shop/blocks/Donation.d.ts +2 -0
  35. package/dist/modules/shop/blocks/Donation.js +145 -0
  36. package/dist/modules/shop/blocks/Donation.js.map +1 -0
  37. package/dist/modules/shop/blocks/OrderStatus.d.ts +2 -0
  38. package/dist/modules/shop/blocks/OrderStatus.js +40 -0
  39. package/dist/modules/shop/blocks/OrderStatus.js.map +1 -0
  40. package/dist/modules/shop/blocks/ProductDetail.d.ts +2 -0
  41. package/dist/modules/shop/blocks/ProductDetail.js +168 -0
  42. package/dist/modules/shop/blocks/ProductDetail.js.map +1 -0
  43. package/dist/modules/shop/blocks/ProductGrid.d.ts +8 -0
  44. package/dist/modules/shop/blocks/ProductGrid.js +136 -0
  45. package/dist/modules/shop/blocks/ProductGrid.js.map +1 -0
  46. package/dist/modules/shop/blocks/ShopCategories.d.ts +2 -0
  47. package/dist/modules/shop/blocks/ShopCategories.js +69 -0
  48. package/dist/modules/shop/blocks/ShopCategories.js.map +1 -0
  49. package/dist/modules/shop/cartStore.d.ts +22 -0
  50. package/dist/modules/shop/cartStore.js +85 -0
  51. package/dist/modules/shop/cartStore.js.map +1 -0
  52. package/dist/modules/shop/lib.d.ts +25 -0
  53. package/dist/modules/shop/lib.js +61 -0
  54. package/dist/modules/shop/lib.js.map +1 -0
  55. package/dist/modules/shop/module.d.ts +1 -0
  56. package/dist/modules/shop/module.js +39 -0
  57. package/dist/modules/shop/module.js.map +1 -0
  58. package/dist/modules/shop/settings.d.ts +41 -0
  59. package/dist/modules/shop/settings.js +18 -0
  60. package/dist/modules/shop/settings.js.map +1 -0
  61. package/dist/schema/index.d.ts +1 -0
  62. package/dist/schema/index.js +1 -0
  63. package/dist/schema/index.js.map +1 -1
  64. package/dist/schema/shop.d.ts +939 -0
  65. package/dist/schema/shop.js +209 -0
  66. package/dist/schema/shop.js.map +1 -0
  67. package/package.json +1 -1
@@ -0,0 +1,397 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useMemo, useState } from "react";
4
+ import { addDoc, collection, deleteDoc, doc, onSnapshot, setDoc } from "firebase/firestore";
5
+ import { getDb } from "../../firebase/client";
6
+ import {
7
+ ProductSchema,
8
+ formatCents
9
+ } from "../../schema/shop";
10
+ import { Badge, Button, Card, Field, Input, Textarea } from "../ui/primitives";
11
+ import { Select } from "../ui/select";
12
+ import { Switch } from "../ui/switch";
13
+ import { useToast } from "../ui/toast";
14
+ import { useConfirm } from "../ui/ConfirmDialog";
15
+ import { MediaPickerButton } from "../media/MediaPicker";
16
+ import { slugify } from "../lib/slugify";
17
+ import { allVariantKeys, centsToEuroInput, eurosToCents } from "./lib";
18
+ const STATUS_OPTIONS = [
19
+ { value: "draft", label: "Concept" },
20
+ { value: "active", label: "Actief" },
21
+ { value: "archived", label: "Gearchiveerd" }
22
+ ];
23
+ function ProductsTab({ siteId }) {
24
+ const { toast } = useToast();
25
+ const confirm = useConfirm();
26
+ const [products, setProducts] = useState([]);
27
+ const [cats, setCats] = useState([]);
28
+ const [selected, setSelected] = useState(null);
29
+ const [q, setQ] = useState("");
30
+ useEffect(() => {
31
+ const db = getDb();
32
+ if (!db) return;
33
+ const un1 = onSnapshot(
34
+ collection(db, "sites", siteId, "products"),
35
+ (snap) => setProducts(
36
+ snap.docs.map((d) => ({ id: d.id, ...d.data() })).sort((a, b) => (a.order ?? 0) - (b.order ?? 0) || a.name.localeCompare(b.name, "nl"))
37
+ )
38
+ );
39
+ const un2 = onSnapshot(
40
+ collection(db, "sites", siteId, "shopCategories"),
41
+ (snap) => setCats(snap.docs.map((d) => ({ id: d.id, ...d.data() })))
42
+ );
43
+ return () => {
44
+ un1();
45
+ un2();
46
+ };
47
+ }, [siteId]);
48
+ async function createProduct() {
49
+ const db = getDb();
50
+ if (!db) return;
51
+ const ref = await addDoc(collection(db, "sites", siteId, "products"), {
52
+ name: "Nieuw product",
53
+ slug: `product-${products.length + 1}`,
54
+ description: "",
55
+ categoryIds: [],
56
+ images: [],
57
+ priceCents: 0,
58
+ vatRate: 21,
59
+ options: [],
60
+ variants: [],
61
+ trackStock: false,
62
+ status: "draft",
63
+ order: products.length,
64
+ featured: false
65
+ });
66
+ setSelected(ref.id);
67
+ }
68
+ const filtered = useMemo(() => {
69
+ const needle = q.trim().toLowerCase();
70
+ if (!needle) return products;
71
+ return products.filter((p) => p.name.toLowerCase().includes(needle) || p.slug.includes(needle));
72
+ }, [products, q]);
73
+ const current = products.find((p) => p.id === selected) ?? null;
74
+ return /* @__PURE__ */ jsxs("div", { className: "grid gap-4 md:grid-cols-[300px_1fr]", children: [
75
+ /* @__PURE__ */ jsxs(Card, { className: "h-fit", children: [
76
+ /* @__PURE__ */ jsxs("div", { className: "mb-3 flex items-center justify-between", children: [
77
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Producten" }),
78
+ /* @__PURE__ */ jsx(Button, { size: "sm", onClick: createProduct, children: "+ Nieuw" })
79
+ ] }),
80
+ /* @__PURE__ */ jsx(Input, { placeholder: "Zoeken\u2026", value: q, onChange: (e) => setQ(e.target.value), className: "mb-2" }),
81
+ /* @__PURE__ */ jsxs("ul", { className: "grid max-h-[60vh] gap-1 overflow-y-auto", children: [
82
+ filtered.map((p) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
83
+ "button",
84
+ {
85
+ onClick: () => setSelected(p.id),
86
+ className: `flex w-full items-center gap-2 rounded-lg px-3 py-2 text-left text-sm ${p.id === selected ? "bg-brand-50 text-brand-700" : "hover:bg-slate-100"}`,
87
+ children: [
88
+ p.images?.[0] && /* @__PURE__ */ jsx("img", { src: p.images[0], alt: "", className: "h-8 w-8 rounded object-cover" }),
89
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: p.name }),
90
+ /* @__PURE__ */ jsx(Badge, { tone: p.status === "active" ? "green" : p.status === "draft" ? "amber" : "slate", children: STATUS_OPTIONS.find((s) => s.value === p.status)?.label ?? p.status })
91
+ ]
92
+ }
93
+ ) }, p.id)),
94
+ filtered.length === 0 && /* @__PURE__ */ jsx("li", { className: "px-3 py-2 text-sm text-slate-500", children: "Geen producten." })
95
+ ] })
96
+ ] }),
97
+ current ? /* @__PURE__ */ jsx(
98
+ ProductDetail,
99
+ {
100
+ siteId,
101
+ product: current,
102
+ categories: cats,
103
+ onDeleted: () => setSelected(null),
104
+ onToast: toast,
105
+ confirm
106
+ },
107
+ current.id
108
+ ) : /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsx("p", { className: "text-sm text-slate-500", children: "Selecteer of maak een product." }) })
109
+ ] });
110
+ }
111
+ function ProductDetail({
112
+ siteId,
113
+ product,
114
+ categories,
115
+ onDeleted,
116
+ onToast,
117
+ confirm
118
+ }) {
119
+ const [def, setDef] = useState(product);
120
+ const [priceInput, setPriceInput] = useState(centsToEuroInput(product.priceCents));
121
+ const [compareInput, setCompareInput] = useState(centsToEuroInput(product.compareAtCents));
122
+ const variantKeys = useMemo(() => allVariantKeys(def.options ?? []), [def.options]);
123
+ function setOption(i, patch) {
124
+ setDef((p) => {
125
+ const options = [...p.options ?? []];
126
+ options[i] = { ...options[i], ...patch };
127
+ return { ...p, options };
128
+ });
129
+ }
130
+ function variantFor(key) {
131
+ return (def.variants ?? []).find((v) => v.key === key);
132
+ }
133
+ function patchVariant(key, patch) {
134
+ setDef((p) => {
135
+ const variants = [...p.variants ?? []];
136
+ const idx = variants.findIndex((v) => v.key === key);
137
+ if (idx >= 0) variants[idx] = { ...variants[idx], ...patch };
138
+ else variants.push({ key, active: true, ...patch });
139
+ return { ...p, variants };
140
+ });
141
+ }
142
+ async function save() {
143
+ const db = getDb();
144
+ if (!db) return;
145
+ const validKeys = new Set(variantKeys);
146
+ const cleanVariants = (def.variants ?? []).filter(
147
+ (v) => validKeys.has(v.key) && (v.priceCents !== void 0 || v.stock !== void 0 || v.sku !== void 0 || v.active === false)
148
+ );
149
+ const parsed = ProductSchema.safeParse({ ...def, variants: cleanVariants });
150
+ if (!parsed.success) {
151
+ onToast({ title: "Ongeldig product", description: parsed.error.issues[0]?.message, tone: "error" });
152
+ return;
153
+ }
154
+ await setDoc(doc(db, "sites", siteId, "products", product.id), parsed.data);
155
+ onToast({ title: "Product opgeslagen", tone: "success" });
156
+ }
157
+ async function remove() {
158
+ const db = getDb();
159
+ if (!db) return;
160
+ const ok = await confirm({
161
+ title: `"${product.name}" verwijderen?`,
162
+ description: "Bestaande bestellingen behouden hun eigen kopie van naam en prijs.",
163
+ tone: "danger"
164
+ });
165
+ if (!ok) return;
166
+ await deleteDoc(doc(db, "sites", siteId, "products", product.id));
167
+ onDeleted();
168
+ }
169
+ return /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsxs("div", { className: "grid gap-4", children: [
170
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-4 md:grid-cols-2", children: [
171
+ /* @__PURE__ */ jsx(Field, { label: "Naam", children: /* @__PURE__ */ jsx(
172
+ Input,
173
+ {
174
+ value: def.name,
175
+ onChange: (e) => setDef((p) => ({ ...p, name: e.target.value, slug: slugify(e.target.value) || p.slug }))
176
+ }
177
+ ) }),
178
+ /* @__PURE__ */ jsx(Field, { label: "Slug", hint: "Product-URL: /product?p=<slug>", children: /* @__PURE__ */ jsx(Input, { value: def.slug, onChange: (e) => setDef((p) => ({ ...p, slug: slugify(e.target.value) })) }) })
179
+ ] }),
180
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-4 md:grid-cols-3", children: [
181
+ /* @__PURE__ */ jsx(Field, { label: "Prijs (\u20AC, incl. btw)", children: /* @__PURE__ */ jsx(
182
+ Input,
183
+ {
184
+ value: priceInput,
185
+ onChange: (e) => {
186
+ setPriceInput(e.target.value);
187
+ const c = eurosToCents(e.target.value);
188
+ if (c !== null) setDef((p) => ({ ...p, priceCents: c }));
189
+ }
190
+ }
191
+ ) }),
192
+ /* @__PURE__ */ jsx(Field, { label: "Van-prijs (\u20AC, optioneel)", hint: "Doorgestreept naast de prijs", children: /* @__PURE__ */ jsx(
193
+ Input,
194
+ {
195
+ value: compareInput,
196
+ onChange: (e) => {
197
+ setCompareInput(e.target.value);
198
+ const c = eurosToCents(e.target.value);
199
+ setDef((p) => ({ ...p, compareAtCents: c ?? void 0 }));
200
+ }
201
+ }
202
+ ) }),
203
+ /* @__PURE__ */ jsx(Field, { label: "Btw-tarief", children: /* @__PURE__ */ jsx(
204
+ Select,
205
+ {
206
+ value: String(def.vatRate ?? 21),
207
+ onValueChange: (v) => setDef((p) => ({ ...p, vatRate: Number(v) })),
208
+ options: [
209
+ { value: "21", label: "21%" },
210
+ { value: "9", label: "9%" },
211
+ { value: "0", label: "0%" }
212
+ ]
213
+ }
214
+ ) })
215
+ ] }),
216
+ /* @__PURE__ */ jsx(Field, { label: "Omschrijving", children: /* @__PURE__ */ jsx(Textarea, { rows: 4, value: def.description ?? "", onChange: (e) => setDef((p) => ({ ...p, description: e.target.value })) }) }),
217
+ /* @__PURE__ */ jsx(Field, { label: "Afbeeldingen", hint: "Eerste afbeelding is het hoofdbeeld", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [
218
+ (def.images ?? []).map((url, i) => /* @__PURE__ */ jsxs("div", { className: "relative", children: [
219
+ /* @__PURE__ */ jsx("img", { src: url, alt: "", className: "h-16 w-16 rounded-lg object-cover" }),
220
+ /* @__PURE__ */ jsx(
221
+ "button",
222
+ {
223
+ type: "button",
224
+ "aria-label": "Verwijder afbeelding",
225
+ className: "absolute -right-2 -top-2 grid h-5 w-5 place-items-center rounded-full bg-slate-700 text-xs text-white",
226
+ onClick: () => setDef((p) => ({ ...p, images: (p.images ?? []).filter((_, j) => j !== i) })),
227
+ children: "\u2715"
228
+ }
229
+ )
230
+ ] }, url + i)),
231
+ /* @__PURE__ */ jsx(
232
+ MediaPickerButton,
233
+ {
234
+ siteId,
235
+ label: "+ Afbeelding",
236
+ onSelect: (m) => setDef((p) => ({ ...p, images: [...p.images ?? [], m.url] }))
237
+ }
238
+ )
239
+ ] }) }),
240
+ /* @__PURE__ */ jsx(Field, { label: "Categorie\xEBn", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
241
+ categories.map((c) => {
242
+ const on = (def.categoryIds ?? []).includes(c.id);
243
+ return /* @__PURE__ */ jsxs(
244
+ "button",
245
+ {
246
+ type: "button",
247
+ onClick: () => setDef((p) => ({
248
+ ...p,
249
+ categoryIds: on ? (p.categoryIds ?? []).filter((x) => x !== c.id) : [...p.categoryIds ?? [], c.id]
250
+ })),
251
+ className: `rounded-full border px-3 py-1 text-sm ${on ? "border-brand-500 bg-brand-50 text-brand-700" : "border-slate-200 hover:bg-slate-50"}`,
252
+ children: [
253
+ on ? "\u2713 " : "",
254
+ c.name
255
+ ]
256
+ },
257
+ c.id
258
+ );
259
+ }),
260
+ categories.length === 0 && /* @__PURE__ */ jsx("span", { className: "text-sm text-slate-500", children: "Maak eerst een categorie aan." })
261
+ ] }) }),
262
+ /* @__PURE__ */ jsx(
263
+ Field,
264
+ {
265
+ label: "Opties (bv. Maat, Kleur)",
266
+ hint: "Max. 3. Waarden gescheiden door komma's. Combinaties kun je hieronder per stuk aanpassen.",
267
+ children: /* @__PURE__ */ jsxs("div", { className: "grid gap-2", children: [
268
+ (def.options ?? []).map((opt, i) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
269
+ /* @__PURE__ */ jsx(
270
+ Input,
271
+ {
272
+ className: "w-36",
273
+ placeholder: "Naam (bv. Maat)",
274
+ value: opt.name,
275
+ onChange: (e) => setOption(i, { name: e.target.value })
276
+ }
277
+ ),
278
+ /* @__PURE__ */ jsx(
279
+ Input,
280
+ {
281
+ placeholder: "Waarden, bv: 56, 62, 68, 74",
282
+ value: opt.values.join(", "),
283
+ onChange: (e) => setOption(i, { values: e.target.value.split(",").map((s) => s.trim()).filter(Boolean) })
284
+ }
285
+ ),
286
+ /* @__PURE__ */ jsx(
287
+ Button,
288
+ {
289
+ size: "sm",
290
+ variant: "ghost",
291
+ onClick: () => setDef((p) => ({ ...p, options: (p.options ?? []).filter((_, j) => j !== i) })),
292
+ children: "\u2715"
293
+ }
294
+ )
295
+ ] }, i)),
296
+ (def.options ?? []).length < 3 && /* @__PURE__ */ jsx(
297
+ Button,
298
+ {
299
+ size: "sm",
300
+ variant: "secondary",
301
+ className: "w-fit",
302
+ onClick: () => setDef((p) => ({ ...p, options: [...p.options ?? [], { name: "", values: [] }] })),
303
+ children: "+ Optie"
304
+ }
305
+ )
306
+ ] })
307
+ }
308
+ ),
309
+ variantKeys.length > 0 && variantKeys.length <= 60 && /* @__PURE__ */ jsx(Field, { label: "Varianten", hint: "Leeg = standaardprijs / onbeperkte voorraad", children: /* @__PURE__ */ jsx("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-sm", children: [
310
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "text-left text-xs text-slate-500", children: [
311
+ /* @__PURE__ */ jsx("th", { className: "py-1 pr-2", children: "Combinatie" }),
312
+ /* @__PURE__ */ jsx("th", { className: "py-1 pr-2", children: "Prijs (\u20AC)" }),
313
+ def.trackStock && /* @__PURE__ */ jsx("th", { className: "py-1 pr-2", children: "Voorraad" }),
314
+ /* @__PURE__ */ jsx("th", { className: "py-1 pr-2", children: "Leverbaar" })
315
+ ] }) }),
316
+ /* @__PURE__ */ jsx("tbody", { children: variantKeys.map((key) => {
317
+ const v = variantFor(key);
318
+ return /* @__PURE__ */ jsxs("tr", { className: "border-t border-slate-100", children: [
319
+ /* @__PURE__ */ jsx("td", { className: "py-1 pr-2", children: key.split("|").join(" \xB7 ") }),
320
+ /* @__PURE__ */ jsx("td", { className: "py-1 pr-2", children: /* @__PURE__ */ jsx(
321
+ "input",
322
+ {
323
+ className: "w-24 rounded-lg border border-slate-200 px-2 py-1",
324
+ placeholder: centsToEuroInput(def.priceCents),
325
+ defaultValue: centsToEuroInput(v?.priceCents),
326
+ onChange: (e) => {
327
+ const c = eurosToCents(e.target.value);
328
+ patchVariant(key, { priceCents: c ?? void 0 });
329
+ }
330
+ }
331
+ ) }),
332
+ def.trackStock && /* @__PURE__ */ jsx("td", { className: "py-1 pr-2", children: /* @__PURE__ */ jsx(
333
+ "input",
334
+ {
335
+ className: "w-20 rounded-lg border border-slate-200 px-2 py-1",
336
+ type: "number",
337
+ min: 0,
338
+ placeholder: "\u221E",
339
+ defaultValue: v?.stock ?? "",
340
+ onChange: (e) => {
341
+ const n = e.target.value === "" ? void 0 : Math.max(0, Number(e.target.value) || 0);
342
+ patchVariant(key, { stock: n });
343
+ }
344
+ }
345
+ ) }),
346
+ /* @__PURE__ */ jsx("td", { className: "py-1 pr-2", children: /* @__PURE__ */ jsx(Switch, { checked: v?.active !== false, onCheckedChange: (on) => patchVariant(key, { active: on }) }) })
347
+ ] }, key);
348
+ }) })
349
+ ] }) }) }),
350
+ variantKeys.length > 60 && /* @__PURE__ */ jsxs("p", { className: "text-sm text-amber-600", children: [
351
+ variantKeys.length,
352
+ " combinaties \u2014 te veel om per stuk te tonen. Beperk het aantal optie-waarden."
353
+ ] }),
354
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-6", children: [
355
+ /* @__PURE__ */ jsx(Field, { label: "Status", children: /* @__PURE__ */ jsx(
356
+ Select,
357
+ {
358
+ value: def.status ?? "draft",
359
+ onValueChange: (v) => setDef((p) => ({ ...p, status: v })),
360
+ options: STATUS_OPTIONS
361
+ }
362
+ ) }),
363
+ /* @__PURE__ */ jsx(Field, { label: "Volgorde", children: /* @__PURE__ */ jsx(
364
+ Input,
365
+ {
366
+ type: "number",
367
+ className: "w-24",
368
+ value: def.order ?? 0,
369
+ onChange: (e) => setDef((p) => ({ ...p, order: Math.max(0, Number(e.target.value) || 0) }))
370
+ }
371
+ ) }),
372
+ /* @__PURE__ */ jsx(Switch, { checked: def.featured === true, onCheckedChange: (v) => setDef((p) => ({ ...p, featured: v })), label: "Uitgelicht" }),
373
+ /* @__PURE__ */ jsx(Switch, { checked: def.trackStock === true, onCheckedChange: (v) => setDef((p) => ({ ...p, trackStock: v })), label: "Voorraad bijhouden" }),
374
+ def.trackStock && (def.options ?? []).length === 0 && /* @__PURE__ */ jsx(Field, { label: "Voorraad", children: /* @__PURE__ */ jsx(
375
+ Input,
376
+ {
377
+ type: "number",
378
+ className: "w-24",
379
+ placeholder: "\u221E",
380
+ value: def.stock ?? "",
381
+ onChange: (e) => setDef((p) => ({ ...p, stock: e.target.value === "" ? void 0 : Math.max(0, Number(e.target.value) || 0) }))
382
+ }
383
+ ) })
384
+ ] }),
385
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-t border-slate-100 pt-4", children: [
386
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
387
+ /* @__PURE__ */ jsx(Button, { onClick: save, children: "Opslaan" }),
388
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-slate-500", children: formatCents(def.priceCents) })
389
+ ] }),
390
+ /* @__PURE__ */ jsx(Button, { variant: "danger", onClick: remove, children: "Verwijderen" })
391
+ ] })
392
+ ] }) });
393
+ }
394
+ export {
395
+ ProductsTab
396
+ };
397
+ //# sourceMappingURL=ProductsTab.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/admin/shop/ProductsTab.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useMemo, useState } from \"react\";\nimport { addDoc, collection, deleteDoc, doc, onSnapshot, setDoc } from \"firebase/firestore\";\nimport { getDb } from \"../../firebase/client\";\nimport {\n ProductSchema,\n formatCents,\n type Product,\n type ProductOption,\n type ProductVariant,\n type ShopCategory,\n} from \"../../schema/shop\";\nimport { Badge, Button, Card, Field, Input, Textarea } from \"../ui/primitives\";\nimport { Select } from \"../ui/select\";\nimport { Switch } from \"../ui/switch\";\nimport { useToast } from \"../ui/toast\";\nimport { useConfirm, type ConfirmOptions } from \"../ui/ConfirmDialog\";\nimport { MediaPickerButton } from \"../media/MediaPicker\";\nimport { slugify } from \"../lib/slugify\";\nimport { allVariantKeys, centsToEuroInput, eurosToCents } from \"./lib\";\n\ntype StoredProduct = Product & { id: string };\ntype StoredCategory = ShopCategory & { id: string };\n\nconst STATUS_OPTIONS = [\n { value: \"draft\", label: \"Concept\" },\n { value: \"active\", label: \"Actief\" },\n { value: \"archived\", label: \"Gearchiveerd\" },\n];\n\nexport function ProductsTab({ siteId }: { siteId: string }) {\n const { toast } = useToast();\n const confirm = useConfirm();\n const [products, setProducts] = useState<StoredProduct[]>([]);\n const [cats, setCats] = useState<StoredCategory[]>([]);\n const [selected, setSelected] = useState<string | null>(null);\n const [q, setQ] = useState(\"\");\n\n useEffect(() => {\n const db = getDb();\n if (!db) return;\n const un1 = onSnapshot(collection(db, \"sites\", siteId, \"products\"), (snap) =>\n setProducts(\n snap.docs\n .map((d) => ({ id: d.id, ...(d.data() as Product) }))\n .sort((a, b) => (a.order ?? 0) - (b.order ?? 0) || a.name.localeCompare(b.name, \"nl\")),\n ),\n );\n const un2 = onSnapshot(collection(db, \"sites\", siteId, \"shopCategories\"), (snap) =>\n setCats(snap.docs.map((d) => ({ id: d.id, ...(d.data() as ShopCategory) }))),\n );\n return () => {\n un1();\n un2();\n };\n }, [siteId]);\n\n async function createProduct() {\n const db = getDb();\n if (!db) return;\n const ref = await addDoc(collection(db, \"sites\", siteId, \"products\"), {\n name: \"Nieuw product\",\n slug: `product-${products.length + 1}`,\n description: \"\",\n categoryIds: [],\n images: [],\n priceCents: 0,\n vatRate: 21,\n options: [],\n variants: [],\n trackStock: false,\n status: \"draft\",\n order: products.length,\n featured: false,\n } satisfies Product);\n setSelected(ref.id);\n }\n\n const filtered = useMemo(() => {\n const needle = q.trim().toLowerCase();\n if (!needle) return products;\n return products.filter((p) => p.name.toLowerCase().includes(needle) || p.slug.includes(needle));\n }, [products, q]);\n\n const current = products.find((p) => p.id === selected) ?? null;\n\n return (\n <div className=\"grid gap-4 md:grid-cols-[300px_1fr]\">\n <Card className=\"h-fit\">\n <div className=\"mb-3 flex items-center justify-between\">\n <h3 className=\"text-sm font-semibold\">Producten</h3>\n <Button size=\"sm\" onClick={createProduct}>+ Nieuw</Button>\n </div>\n <Input placeholder=\"Zoeken…\" value={q} onChange={(e) => setQ(e.target.value)} className=\"mb-2\" />\n <ul className=\"grid max-h-[60vh] gap-1 overflow-y-auto\">\n {filtered.map((p) => (\n <li key={p.id}>\n <button\n onClick={() => setSelected(p.id)}\n className={`flex w-full items-center gap-2 rounded-lg px-3 py-2 text-left text-sm ${\n p.id === selected ? \"bg-brand-50 text-brand-700\" : \"hover:bg-slate-100\"\n }`}\n >\n {p.images?.[0] && <img src={p.images[0]} alt=\"\" className=\"h-8 w-8 rounded object-cover\" />}\n <span className=\"min-w-0 flex-1 truncate\">{p.name}</span>\n <Badge tone={p.status === \"active\" ? \"green\" : p.status === \"draft\" ? \"amber\" : \"slate\"}>\n {STATUS_OPTIONS.find((s) => s.value === p.status)?.label ?? p.status}\n </Badge>\n </button>\n </li>\n ))}\n {filtered.length === 0 && <li className=\"px-3 py-2 text-sm text-slate-500\">Geen producten.</li>}\n </ul>\n </Card>\n {current ? (\n <ProductDetail\n key={current.id}\n siteId={siteId}\n product={current}\n categories={cats}\n onDeleted={() => setSelected(null)}\n onToast={toast}\n confirm={confirm}\n />\n ) : (\n <Card>\n <p className=\"text-sm text-slate-500\">Selecteer of maak een product.</p>\n </Card>\n )}\n </div>\n );\n}\n\nfunction ProductDetail({\n siteId,\n product,\n categories,\n onDeleted,\n onToast,\n confirm,\n}: {\n siteId: string;\n product: StoredProduct;\n categories: StoredCategory[];\n onDeleted: () => void;\n onToast: (t: { title: string; description?: string; tone?: \"default\" | \"success\" | \"error\" }) => void;\n confirm: (opts: ConfirmOptions) => Promise<boolean>;\n}) {\n const [def, setDef] = useState<Product>(product);\n const [priceInput, setPriceInput] = useState(centsToEuroInput(product.priceCents));\n const [compareInput, setCompareInput] = useState(centsToEuroInput(product.compareAtCents));\n\n const variantKeys = useMemo(() => allVariantKeys(def.options ?? []), [def.options]);\n\n function setOption(i: number, patch: Partial<ProductOption>) {\n setDef((p) => {\n const options = [...(p.options ?? [])];\n options[i] = { ...options[i], ...patch };\n return { ...p, options };\n });\n }\n\n function variantFor(key: string): ProductVariant | undefined {\n return (def.variants ?? []).find((v) => v.key === key);\n }\n\n function patchVariant(key: string, patch: Partial<ProductVariant>) {\n setDef((p) => {\n const variants = [...(p.variants ?? [])];\n const idx = variants.findIndex((v) => v.key === key);\n if (idx >= 0) variants[idx] = { ...variants[idx], ...patch };\n else variants.push({ key, active: true, ...patch });\n return { ...p, variants };\n });\n }\n\n async function save() {\n const db = getDb();\n if (!db) return;\n // Alleen varianten bewaren die iets toevoegen (afwijkende prijs/voorraad/sku of inactief),\n // en geen wees-varianten van verwijderde optiewaarden.\n const validKeys = new Set(variantKeys);\n const cleanVariants = (def.variants ?? []).filter(\n (v) =>\n validKeys.has(v.key) &&\n (v.priceCents !== undefined || v.stock !== undefined || v.sku !== undefined || v.active === false),\n );\n const parsed = ProductSchema.safeParse({ ...def, variants: cleanVariants });\n if (!parsed.success) {\n onToast({ title: \"Ongeldig product\", description: parsed.error.issues[0]?.message, tone: \"error\" });\n return;\n }\n await setDoc(doc(db, \"sites\", siteId, \"products\", product.id), parsed.data);\n onToast({ title: \"Product opgeslagen\", tone: \"success\" });\n }\n\n async function remove() {\n const db = getDb();\n if (!db) return;\n const ok = await confirm({\n title: `\"${product.name}\" verwijderen?`,\n description: \"Bestaande bestellingen behouden hun eigen kopie van naam en prijs.\",\n tone: \"danger\",\n });\n if (!ok) return;\n await deleteDoc(doc(db, \"sites\", siteId, \"products\", product.id));\n onDeleted();\n }\n\n return (\n <Card>\n <div className=\"grid gap-4\">\n <div className=\"grid gap-4 md:grid-cols-2\">\n <Field label=\"Naam\">\n <Input\n value={def.name}\n onChange={(e) => setDef((p) => ({ ...p, name: e.target.value, slug: slugify(e.target.value) || p.slug }))}\n />\n </Field>\n <Field label=\"Slug\" hint=\"Product-URL: /product?p=<slug>\">\n <Input value={def.slug} onChange={(e) => setDef((p) => ({ ...p, slug: slugify(e.target.value) }))} />\n </Field>\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-3\">\n <Field label=\"Prijs (€, incl. btw)\">\n <Input\n value={priceInput}\n onChange={(e) => {\n setPriceInput(e.target.value);\n const c = eurosToCents(e.target.value);\n if (c !== null) setDef((p) => ({ ...p, priceCents: c }));\n }}\n />\n </Field>\n <Field label=\"Van-prijs (€, optioneel)\" hint=\"Doorgestreept naast de prijs\">\n <Input\n value={compareInput}\n onChange={(e) => {\n setCompareInput(e.target.value);\n const c = eurosToCents(e.target.value);\n setDef((p) => ({ ...p, compareAtCents: c ?? undefined }));\n }}\n />\n </Field>\n <Field label=\"Btw-tarief\">\n <Select\n value={String(def.vatRate ?? 21)}\n onValueChange={(v) => setDef((p) => ({ ...p, vatRate: Number(v) as 21 | 9 | 0 }))}\n options={[\n { value: \"21\", label: \"21%\" },\n { value: \"9\", label: \"9%\" },\n { value: \"0\", label: \"0%\" },\n ]}\n />\n </Field>\n </div>\n\n <Field label=\"Omschrijving\">\n <Textarea rows={4} value={def.description ?? \"\"} onChange={(e) => setDef((p) => ({ ...p, description: e.target.value }))} />\n </Field>\n\n <Field label=\"Afbeeldingen\" hint=\"Eerste afbeelding is het hoofdbeeld\">\n <div className=\"flex flex-wrap items-center gap-3\">\n {(def.images ?? []).map((url, i) => (\n <div key={url + i} className=\"relative\">\n <img src={url} alt=\"\" className=\"h-16 w-16 rounded-lg object-cover\" />\n <button\n type=\"button\"\n aria-label=\"Verwijder afbeelding\"\n className=\"absolute -right-2 -top-2 grid h-5 w-5 place-items-center rounded-full bg-slate-700 text-xs text-white\"\n onClick={() => setDef((p) => ({ ...p, images: (p.images ?? []).filter((_, j) => j !== i) }))}\n >\n ✕\n </button>\n </div>\n ))}\n <MediaPickerButton\n siteId={siteId}\n label=\"+ Afbeelding\"\n onSelect={(m) => setDef((p) => ({ ...p, images: [...(p.images ?? []), m.url] }))}\n />\n </div>\n </Field>\n\n <Field label=\"Categorieën\">\n <div className=\"flex flex-wrap gap-2\">\n {categories.map((c) => {\n const on = (def.categoryIds ?? []).includes(c.id);\n return (\n <button\n key={c.id}\n type=\"button\"\n onClick={() =>\n setDef((p) => ({\n ...p,\n categoryIds: on ? (p.categoryIds ?? []).filter((x) => x !== c.id) : [...(p.categoryIds ?? []), c.id],\n }))\n }\n className={`rounded-full border px-3 py-1 text-sm ${\n on ? \"border-brand-500 bg-brand-50 text-brand-700\" : \"border-slate-200 hover:bg-slate-50\"\n }`}\n >\n {on ? \"✓ \" : \"\"}\n {c.name}\n </button>\n );\n })}\n {categories.length === 0 && <span className=\"text-sm text-slate-500\">Maak eerst een categorie aan.</span>}\n </div>\n </Field>\n\n {/* Opties & varianten */}\n <Field\n label=\"Opties (bv. Maat, Kleur)\"\n hint=\"Max. 3. Waarden gescheiden door komma's. Combinaties kun je hieronder per stuk aanpassen.\"\n >\n <div className=\"grid gap-2\">\n {(def.options ?? []).map((opt, i) => (\n <div key={i} className=\"flex items-center gap-2\">\n <Input\n className=\"w-36\"\n placeholder=\"Naam (bv. Maat)\"\n value={opt.name}\n onChange={(e) => setOption(i, { name: e.target.value })}\n />\n <Input\n placeholder=\"Waarden, bv: 56, 62, 68, 74\"\n value={opt.values.join(\", \")}\n onChange={(e) =>\n setOption(i, { values: e.target.value.split(\",\").map((s) => s.trim()).filter(Boolean) })\n }\n />\n <Button\n size=\"sm\"\n variant=\"ghost\"\n onClick={() => setDef((p) => ({ ...p, options: (p.options ?? []).filter((_, j) => j !== i) }))}\n >\n ✕\n </Button>\n </div>\n ))}\n {(def.options ?? []).length < 3 && (\n <Button\n size=\"sm\"\n variant=\"secondary\"\n className=\"w-fit\"\n onClick={() => setDef((p) => ({ ...p, options: [...(p.options ?? []), { name: \"\", values: [] }] }))}\n >\n + Optie\n </Button>\n )}\n </div>\n </Field>\n\n {variantKeys.length > 0 && variantKeys.length <= 60 && (\n <Field label=\"Varianten\" hint=\"Leeg = standaardprijs / onbeperkte voorraad\">\n <div className=\"overflow-x-auto\">\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"text-left text-xs text-slate-500\">\n <th className=\"py-1 pr-2\">Combinatie</th>\n <th className=\"py-1 pr-2\">Prijs (€)</th>\n {def.trackStock && <th className=\"py-1 pr-2\">Voorraad</th>}\n <th className=\"py-1 pr-2\">Leverbaar</th>\n </tr>\n </thead>\n <tbody>\n {variantKeys.map((key) => {\n const v = variantFor(key);\n return (\n <tr key={key} className=\"border-t border-slate-100\">\n <td className=\"py-1 pr-2\">{key.split(\"|\").join(\" · \")}</td>\n <td className=\"py-1 pr-2\">\n {/* Native uncontrolled inputs: geen cursor-gedoe bij \"13,\" tijdens typen. */}\n <input\n className=\"w-24 rounded-lg border border-slate-200 px-2 py-1\"\n placeholder={centsToEuroInput(def.priceCents)}\n defaultValue={centsToEuroInput(v?.priceCents)}\n onChange={(e) => {\n const c = eurosToCents(e.target.value);\n patchVariant(key, { priceCents: c ?? undefined });\n }}\n />\n </td>\n {def.trackStock && (\n <td className=\"py-1 pr-2\">\n <input\n className=\"w-20 rounded-lg border border-slate-200 px-2 py-1\"\n type=\"number\"\n min={0}\n placeholder=\"∞\"\n defaultValue={v?.stock ?? \"\"}\n onChange={(e) => {\n const n = e.target.value === \"\" ? undefined : Math.max(0, Number(e.target.value) || 0);\n patchVariant(key, { stock: n });\n }}\n />\n </td>\n )}\n <td className=\"py-1 pr-2\">\n <Switch checked={v?.active !== false} onCheckedChange={(on) => patchVariant(key, { active: on })} />\n </td>\n </tr>\n );\n })}\n </tbody>\n </table>\n </div>\n </Field>\n )}\n {variantKeys.length > 60 && (\n <p className=\"text-sm text-amber-600\">\n {variantKeys.length} combinaties — te veel om per stuk te tonen. Beperk het aantal optie-waarden.\n </p>\n )}\n\n <div className=\"flex flex-wrap items-center gap-6\">\n <Field label=\"Status\">\n <Select\n value={def.status ?? \"draft\"}\n onValueChange={(v) => setDef((p) => ({ ...p, status: v as Product[\"status\"] }))}\n options={STATUS_OPTIONS}\n />\n </Field>\n <Field label=\"Volgorde\">\n <Input\n type=\"number\"\n className=\"w-24\"\n value={def.order ?? 0}\n onChange={(e) => setDef((p) => ({ ...p, order: Math.max(0, Number(e.target.value) || 0) }))}\n />\n </Field>\n <Switch checked={def.featured === true} onCheckedChange={(v) => setDef((p) => ({ ...p, featured: v }))} label=\"Uitgelicht\" />\n <Switch checked={def.trackStock === true} onCheckedChange={(v) => setDef((p) => ({ ...p, trackStock: v }))} label=\"Voorraad bijhouden\" />\n {def.trackStock && (def.options ?? []).length === 0 && (\n <Field label=\"Voorraad\">\n <Input\n type=\"number\"\n className=\"w-24\"\n placeholder=\"∞\"\n value={def.stock ?? \"\"}\n onChange={(e) =>\n setDef((p) => ({ ...p, stock: e.target.value === \"\" ? undefined : Math.max(0, Number(e.target.value) || 0) }))\n }\n />\n </Field>\n )}\n </div>\n\n <div className=\"flex items-center justify-between border-t border-slate-100 pt-4\">\n <div className=\"flex items-center gap-3\">\n <Button onClick={save}>Opslaan</Button>\n <span className=\"text-sm text-slate-500\">{formatCents(def.priceCents)}</span>\n </div>\n <Button variant=\"danger\" onClick={remove}>Verwijderen</Button>\n </div>\n </div>\n </Card>\n );\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,4 @@
1
+ /** Webwinkel-beheer: producten, categorieën, bestellingen en koppelingen (Stripe/SMTP). */
2
+ export declare function ShopPanel({ siteId }: {
3
+ siteId: string;
4
+ }): import("react").JSX.Element;
@@ -0,0 +1,25 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs";
4
+ import { ProductsTab } from "./ProductsTab";
5
+ import { CategoriesTab } from "./CategoriesTab";
6
+ import { OrdersTab } from "./OrdersTab";
7
+ import { ConnectionsTab } from "./ConnectionsTab";
8
+ function ShopPanel({ siteId }) {
9
+ return /* @__PURE__ */ jsxs(Tabs, { defaultValue: "orders", children: [
10
+ /* @__PURE__ */ jsxs(TabsList, { children: [
11
+ /* @__PURE__ */ jsx(TabsTrigger, { value: "orders", children: "Bestellingen" }),
12
+ /* @__PURE__ */ jsx(TabsTrigger, { value: "products", children: "Producten" }),
13
+ /* @__PURE__ */ jsx(TabsTrigger, { value: "categories", children: "Categorie\xEBn" }),
14
+ /* @__PURE__ */ jsx(TabsTrigger, { value: "connections", children: "Koppelingen" })
15
+ ] }),
16
+ /* @__PURE__ */ jsx(TabsContent, { value: "orders", children: /* @__PURE__ */ jsx(OrdersTab, { siteId }) }),
17
+ /* @__PURE__ */ jsx(TabsContent, { value: "products", children: /* @__PURE__ */ jsx(ProductsTab, { siteId }) }),
18
+ /* @__PURE__ */ jsx(TabsContent, { value: "categories", children: /* @__PURE__ */ jsx(CategoriesTab, { siteId }) }),
19
+ /* @__PURE__ */ jsx(TabsContent, { value: "connections", children: /* @__PURE__ */ jsx(ConnectionsTab, { siteId }) })
20
+ ] });
21
+ }
22
+ export {
23
+ ShopPanel
24
+ };
25
+ //# sourceMappingURL=ShopPanel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/admin/shop/ShopPanel.tsx"],"sourcesContent":["\"use client\";\n\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from \"../ui/tabs\";\nimport { ProductsTab } from \"./ProductsTab\";\nimport { CategoriesTab } from \"./CategoriesTab\";\nimport { OrdersTab } from \"./OrdersTab\";\nimport { ConnectionsTab } from \"./ConnectionsTab\";\n\n/** Webwinkel-beheer: producten, categorieën, bestellingen en koppelingen (Stripe/SMTP). */\nexport function ShopPanel({ siteId }: { siteId: string }) {\n return (\n <Tabs defaultValue=\"orders\">\n <TabsList>\n <TabsTrigger value=\"orders\">Bestellingen</TabsTrigger>\n <TabsTrigger value=\"products\">Producten</TabsTrigger>\n <TabsTrigger value=\"categories\">Categorieën</TabsTrigger>\n <TabsTrigger value=\"connections\">Koppelingen</TabsTrigger>\n </TabsList>\n <TabsContent value=\"orders\">\n <OrdersTab siteId={siteId} />\n </TabsContent>\n <TabsContent value=\"products\">\n <ProductsTab siteId={siteId} />\n </TabsContent>\n <TabsContent value=\"categories\">\n <CategoriesTab siteId={siteId} />\n </TabsContent>\n <TabsContent value=\"connections\">\n <ConnectionsTab siteId={siteId} />\n </TabsContent>\n </Tabs>\n );\n}\n"],"mappings":";AAYM,SACE,KADF;AAVN,SAAS,MAAM,aAAa,UAAU,mBAAmB;AACzD,SAAS,mBAAmB;AAC5B,SAAS,qBAAqB;AAC9B,SAAS,iBAAiB;AAC1B,SAAS,sBAAsB;AAGxB,SAAS,UAAU,EAAE,OAAO,GAAuB;AACxD,SACE,qBAAC,QAAK,cAAa,UACjB;AAAA,yBAAC,YACC;AAAA,0BAAC,eAAY,OAAM,UAAS,0BAAY;AAAA,MACxC,oBAAC,eAAY,OAAM,YAAW,uBAAS;AAAA,MACvC,oBAAC,eAAY,OAAM,cAAa,4BAAW;AAAA,MAC3C,oBAAC,eAAY,OAAM,eAAc,yBAAW;AAAA,OAC9C;AAAA,IACA,oBAAC,eAAY,OAAM,UACjB,8BAAC,aAAU,QAAgB,GAC7B;AAAA,IACA,oBAAC,eAAY,OAAM,YACjB,8BAAC,eAAY,QAAgB,GAC/B;AAAA,IACA,oBAAC,eAAY,OAAM,cACjB,8BAAC,iBAAc,QAAgB,GACjC;AAAA,IACA,oBAAC,eAAY,OAAM,eACjB,8BAAC,kBAAe,QAAgB,GAClC;AAAA,KACF;AAEJ;","names":[]}
@@ -0,0 +1,16 @@
1
+ /** Callable-helper voor de shop-beheerfuncties (saveShopSecrets, getShopStatus, …). */
2
+ export declare function shopCallable<TIn, TOut>(name: string): (data: TIn) => Promise<TOut>;
3
+ /** "13,95" / "13.95" → 1395. Ongeldig → null. */
4
+ export declare function eurosToCents(input: string): number | null;
5
+ /** 1395 → "13,95" (voor invoervelden; weergave met €-teken doet formatCents). */
6
+ export declare function centsToEuroInput(cents: number | undefined): string;
7
+ /** Cartesisch product van optie-waarden → alle variant-keys ("68|Roze"). */
8
+ export declare function allVariantKeys(options: {
9
+ name: string;
10
+ values: string[];
11
+ }[]): string[];
12
+ /** Statusweergave (NL-labels + badge-tint) voor bestellingen. */
13
+ export declare const ORDER_STATUS_LABELS: Record<string, {
14
+ label: string;
15
+ tone: "slate" | "green" | "amber";
16
+ }>;
@@ -0,0 +1,46 @@
1
+ "use client";
2
+ import { getFunctions, httpsCallable } from "firebase/functions";
3
+ import { getFirebaseApp } from "../../firebase/client";
4
+ import { functionsRegion } from "../../firebase/region";
5
+ function shopCallable(name) {
6
+ return async (data) => {
7
+ const app = getFirebaseApp();
8
+ if (!app) throw new Error("Firebase niet beschikbaar.");
9
+ const fn = httpsCallable(getFunctions(app, functionsRegion()), name);
10
+ const res = await fn(data);
11
+ return res.data;
12
+ };
13
+ }
14
+ function eurosToCents(input) {
15
+ const cleaned = input.trim().replace(/[€\s]/g, "").replace(",", ".");
16
+ if (cleaned === "") return null;
17
+ const n = Number(cleaned);
18
+ if (!Number.isFinite(n) || n < 0) return null;
19
+ return Math.round(n * 100);
20
+ }
21
+ function centsToEuroInput(cents) {
22
+ if (cents === void 0) return "";
23
+ return (cents / 100).toFixed(2).replace(".", ",");
24
+ }
25
+ function allVariantKeys(options) {
26
+ if (options.length === 0) return [];
27
+ return options.reduce(
28
+ (acc, opt) => acc.flatMap((prefix) => opt.values.map((v) => prefix ? `${prefix}|${v}` : v)),
29
+ [""]
30
+ );
31
+ }
32
+ const ORDER_STATUS_LABELS = {
33
+ pending: { label: "Wacht op betaling", tone: "amber" },
34
+ paid: { label: "Betaald", tone: "green" },
35
+ shipped: { label: "Verzonden", tone: "green" },
36
+ cancelled: { label: "Geannuleerd", tone: "slate" },
37
+ refunded: { label: "Terugbetaald", tone: "slate" }
38
+ };
39
+ export {
40
+ ORDER_STATUS_LABELS,
41
+ allVariantKeys,
42
+ centsToEuroInput,
43
+ eurosToCents,
44
+ shopCallable
45
+ };
46
+ //# sourceMappingURL=lib.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/admin/shop/lib.ts"],"sourcesContent":["\"use client\";\n\nimport { getFunctions, httpsCallable } from \"firebase/functions\";\nimport { getFirebaseApp } from \"../../firebase/client\";\nimport { functionsRegion } from \"../../firebase/region\";\n\n/** Callable-helper voor de shop-beheerfuncties (saveShopSecrets, getShopStatus, …). */\nexport function shopCallable<TIn, TOut>(name: string) {\n return async (data: TIn): Promise<TOut> => {\n const app = getFirebaseApp();\n if (!app) throw new Error(\"Firebase niet beschikbaar.\");\n const fn = httpsCallable(getFunctions(app, functionsRegion()), name);\n const res = await fn(data as Record<string, unknown>);\n return res.data as TOut;\n };\n}\n\n/** \"13,95\" / \"13.95\" → 1395. Ongeldig → null. */\nexport function eurosToCents(input: string): number | null {\n const cleaned = input.trim().replace(/[€\\s]/g, \"\").replace(\",\", \".\");\n if (cleaned === \"\") return null;\n const n = Number(cleaned);\n if (!Number.isFinite(n) || n < 0) return null;\n return Math.round(n * 100);\n}\n\n/** 1395 → \"13,95\" (voor invoervelden; weergave met €-teken doet formatCents). */\nexport function centsToEuroInput(cents: number | undefined): string {\n if (cents === undefined) return \"\";\n return (cents / 100).toFixed(2).replace(\".\", \",\");\n}\n\n/** Cartesisch product van optie-waarden → alle variant-keys (\"68|Roze\"). */\nexport function allVariantKeys(options: { name: string; values: string[] }[]): string[] {\n if (options.length === 0) return [];\n return options.reduce<string[]>(\n (acc, opt) => acc.flatMap((prefix) => opt.values.map((v) => (prefix ? `${prefix}|${v}` : v))),\n [\"\"],\n );\n}\n\n/** Statusweergave (NL-labels + badge-tint) voor bestellingen. */\nexport const ORDER_STATUS_LABELS: Record<string, { label: string; tone: \"slate\" | \"green\" | \"amber\" }> = {\n pending: { label: \"Wacht op betaling\", tone: \"amber\" },\n paid: { label: \"Betaald\", tone: \"green\" },\n shipped: { label: \"Verzonden\", tone: \"green\" },\n cancelled: { label: \"Geannuleerd\", tone: \"slate\" },\n refunded: { label: \"Terugbetaald\", tone: \"slate\" },\n};\n"],"mappings":";AAEA,SAAS,cAAc,qBAAqB;AAC5C,SAAS,sBAAsB;AAC/B,SAAS,uBAAuB;AAGzB,SAAS,aAAwB,MAAc;AACpD,SAAO,OAAO,SAA6B;AACzC,UAAM,MAAM,eAAe;AAC3B,QAAI,CAAC,IAAK,OAAM,IAAI,MAAM,4BAA4B;AACtD,UAAM,KAAK,cAAc,aAAa,KAAK,gBAAgB,CAAC,GAAG,IAAI;AACnE,UAAM,MAAM,MAAM,GAAG,IAA+B;AACpD,WAAO,IAAI;AAAA,EACb;AACF;AAGO,SAAS,aAAa,OAA8B;AACzD,QAAM,UAAU,MAAM,KAAK,EAAE,QAAQ,UAAU,EAAE,EAAE,QAAQ,KAAK,GAAG;AACnE,MAAI,YAAY,GAAI,QAAO;AAC3B,QAAM,IAAI,OAAO,OAAO;AACxB,MAAI,CAAC,OAAO,SAAS,CAAC,KAAK,IAAI,EAAG,QAAO;AACzC,SAAO,KAAK,MAAM,IAAI,GAAG;AAC3B;AAGO,SAAS,iBAAiB,OAAmC;AAClE,MAAI,UAAU,OAAW,QAAO;AAChC,UAAQ,QAAQ,KAAK,QAAQ,CAAC,EAAE,QAAQ,KAAK,GAAG;AAClD;AAGO,SAAS,eAAe,SAAyD;AACtF,MAAI,QAAQ,WAAW,EAAG,QAAO,CAAC;AAClC,SAAO,QAAQ;AAAA,IACb,CAAC,KAAK,QAAQ,IAAI,QAAQ,CAAC,WAAW,IAAI,OAAO,IAAI,CAAC,MAAO,SAAS,GAAG,MAAM,IAAI,CAAC,KAAK,CAAE,CAAC;AAAA,IAC5F,CAAC,EAAE;AAAA,EACL;AACF;AAGO,MAAM,sBAA4F;AAAA,EACvG,SAAS,EAAE,OAAO,qBAAqB,MAAM,QAAQ;AAAA,EACrD,MAAM,EAAE,OAAO,WAAW,MAAM,QAAQ;AAAA,EACxC,SAAS,EAAE,OAAO,aAAa,MAAM,QAAQ;AAAA,EAC7C,WAAW,EAAE,OAAO,eAAe,MAAM,QAAQ;AAAA,EACjD,UAAU,EAAE,OAAO,gBAAgB,MAAM,QAAQ;AACnD;","names":[]}