@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
@@ -7,6 +7,7 @@ import { StatsPanel } from "./stats/StatsPanel";
7
7
  import { SettingsPanel } from "./settings/SettingsPanel";
8
8
  import { NewsEditor } from "./NewsEditor";
9
9
  import { CustomBlocksPanel } from "./customBlocks/CustomBlocksPanel";
10
+ import { ShopPanel } from "./shop/ShopPanel";
10
11
  function NewsPanel(_) {
11
12
  return /* @__PURE__ */ jsx(NewsEditor, {});
12
13
  }
@@ -16,6 +17,8 @@ const builtinPanels = [
16
17
  { moduleId: "media", label: "Media", order: 30, minRole: "editor", Panel: MediaManager },
17
18
  { moduleId: "forms", label: "Formulieren", order: 40, minRole: "editor", Panel: FormsPanel },
18
19
  { moduleId: "stats", label: "Statistieken", order: 50, minRole: "editor", Panel: StatsPanel },
20
+ // Webwinkel (5.5): alleen zichtbaar als de shop-module aanstaat.
21
+ { moduleId: "shop", label: "Webwinkel", order: 45, minRole: "editor", Panel: ShopPanel },
19
22
  // Bouwstenen (5.0): custom blocks zonder deploy — hangt aan de altijd-actieve content-module.
20
23
  { moduleId: "content", label: "Bouwstenen", order: 60, minRole: "siteadmin", Panel: CustomBlocksPanel },
21
24
  { moduleId: "settings", label: "Instellingen", order: 100, minRole: "siteadmin", Panel: SettingsPanel }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/admin/panelRegistry.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * Paneel-registry — koppelt een beheer-paneel aan een module-id. `AdminApp` rendert de tabs\n * hieruit (gesorteerd op `order`, gated op rol én actieve-module-set) i.p.v. hardgecodeerd. Een\n * nieuwe module met een eigen paneel hoeft `AdminApp` dus niet meer aan te raken.\n */\nimport type { ComponentType } from \"react\";\nimport { PagesPanel } from \"./pages/PagesPanel\";\nimport { MediaManager } from \"./media/MediaManager\";\nimport { FormsPanel } from \"./forms/FormsPanel\";\nimport { StatsPanel } from \"./stats/StatsPanel\";\nimport { SettingsPanel } from \"./settings/SettingsPanel\";\nimport { NewsEditor } from \"./NewsEditor\";\nimport { CustomBlocksPanel } from \"./customBlocks/CustomBlocksPanel\";\n\nexport type PanelMinRole = \"editor\" | \"siteadmin\";\n\nexport interface PanelDefinition {\n /** Moet overeenkomen met de module-`id`; bepaalt of het paneel actief is. */\n moduleId: string;\n label: string;\n /** Sorteervolgorde (laag = links). Instellingen staat vast achteraan (100). */\n order: number;\n minRole: PanelMinRole;\n Panel: ComponentType<{ siteId: string }>;\n}\n\n/** Adapter: de NewsEditor leest zelf siteId via context; wrap voor een uniforme prop-signatuur. */\nfunction NewsPanel(_: { siteId: string }) {\n return <NewsEditor />;\n}\n\n/** De panelen van de ingebouwde modules. */\nexport const builtinPanels: PanelDefinition[] = [\n { moduleId: \"pages\", label: \"Pagina's\", order: 10, minRole: \"editor\", Panel: PagesPanel },\n { moduleId: \"news\", label: \"Nieuws\", order: 20, minRole: \"editor\", Panel: NewsPanel },\n { moduleId: \"media\", label: \"Media\", order: 30, minRole: \"editor\", Panel: MediaManager },\n { moduleId: \"forms\", label: \"Formulieren\", order: 40, minRole: \"editor\", Panel: FormsPanel },\n { moduleId: \"stats\", label: \"Statistieken\", order: 50, minRole: \"editor\", Panel: StatsPanel },\n // Bouwstenen (5.0): custom blocks zonder deploy — hangt aan de altijd-actieve content-module.\n { moduleId: \"content\", label: \"Bouwstenen\", order: 60, minRole: \"siteadmin\", Panel: CustomBlocksPanel },\n { moduleId: \"settings\", label: \"Instellingen\", order: 100, minRole: \"siteadmin\", Panel: SettingsPanel },\n];\n"],"mappings":";AA8BS;AAtBT,SAAS,kBAAkB;AAC3B,SAAS,oBAAoB;AAC7B,SAAS,kBAAkB;AAC3B,SAAS,kBAAkB;AAC3B,SAAS,qBAAqB;AAC9B,SAAS,kBAAkB;AAC3B,SAAS,yBAAyB;AAelC,SAAS,UAAU,GAAuB;AACxC,SAAO,oBAAC,cAAW;AACrB;AAGO,MAAM,gBAAmC;AAAA,EAC9C,EAAE,UAAU,SAAS,OAAO,YAAY,OAAO,IAAI,SAAS,UAAU,OAAO,WAAW;AAAA,EACxF,EAAE,UAAU,QAAQ,OAAO,UAAU,OAAO,IAAI,SAAS,UAAU,OAAO,UAAU;AAAA,EACpF,EAAE,UAAU,SAAS,OAAO,SAAS,OAAO,IAAI,SAAS,UAAU,OAAO,aAAa;AAAA,EACvF,EAAE,UAAU,SAAS,OAAO,eAAe,OAAO,IAAI,SAAS,UAAU,OAAO,WAAW;AAAA,EAC3F,EAAE,UAAU,SAAS,OAAO,gBAAgB,OAAO,IAAI,SAAS,UAAU,OAAO,WAAW;AAAA;AAAA,EAE5F,EAAE,UAAU,WAAW,OAAO,cAAc,OAAO,IAAI,SAAS,aAAa,OAAO,kBAAkB;AAAA,EACtG,EAAE,UAAU,YAAY,OAAO,gBAAgB,OAAO,KAAK,SAAS,aAAa,OAAO,cAAc;AACxG;","names":[]}
1
+ {"version":3,"sources":["../../src/admin/panelRegistry.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * Paneel-registry — koppelt een beheer-paneel aan een module-id. `AdminApp` rendert de tabs\n * hieruit (gesorteerd op `order`, gated op rol én actieve-module-set) i.p.v. hardgecodeerd. Een\n * nieuwe module met een eigen paneel hoeft `AdminApp` dus niet meer aan te raken.\n */\nimport type { ComponentType } from \"react\";\nimport { PagesPanel } from \"./pages/PagesPanel\";\nimport { MediaManager } from \"./media/MediaManager\";\nimport { FormsPanel } from \"./forms/FormsPanel\";\nimport { StatsPanel } from \"./stats/StatsPanel\";\nimport { SettingsPanel } from \"./settings/SettingsPanel\";\nimport { NewsEditor } from \"./NewsEditor\";\nimport { CustomBlocksPanel } from \"./customBlocks/CustomBlocksPanel\";\nimport { ShopPanel } from \"./shop/ShopPanel\";\n\nexport type PanelMinRole = \"editor\" | \"siteadmin\";\n\nexport interface PanelDefinition {\n /** Moet overeenkomen met de module-`id`; bepaalt of het paneel actief is. */\n moduleId: string;\n label: string;\n /** Sorteervolgorde (laag = links). Instellingen staat vast achteraan (100). */\n order: number;\n minRole: PanelMinRole;\n Panel: ComponentType<{ siteId: string }>;\n}\n\n/** Adapter: de NewsEditor leest zelf siteId via context; wrap voor een uniforme prop-signatuur. */\nfunction NewsPanel(_: { siteId: string }) {\n return <NewsEditor />;\n}\n\n/** De panelen van de ingebouwde modules. */\nexport const builtinPanels: PanelDefinition[] = [\n { moduleId: \"pages\", label: \"Pagina's\", order: 10, minRole: \"editor\", Panel: PagesPanel },\n { moduleId: \"news\", label: \"Nieuws\", order: 20, minRole: \"editor\", Panel: NewsPanel },\n { moduleId: \"media\", label: \"Media\", order: 30, minRole: \"editor\", Panel: MediaManager },\n { moduleId: \"forms\", label: \"Formulieren\", order: 40, minRole: \"editor\", Panel: FormsPanel },\n { moduleId: \"stats\", label: \"Statistieken\", order: 50, minRole: \"editor\", Panel: StatsPanel },\n // Webwinkel (5.5): alleen zichtbaar als de shop-module aanstaat.\n { moduleId: \"shop\", label: \"Webwinkel\", order: 45, minRole: \"editor\", Panel: ShopPanel },\n // Bouwstenen (5.0): custom blocks zonder deploy — hangt aan de altijd-actieve content-module.\n { moduleId: \"content\", label: \"Bouwstenen\", order: 60, minRole: \"siteadmin\", Panel: CustomBlocksPanel },\n { moduleId: \"settings\", label: \"Instellingen\", order: 100, minRole: \"siteadmin\", Panel: SettingsPanel },\n];\n"],"mappings":";AA+BS;AAvBT,SAAS,kBAAkB;AAC3B,SAAS,oBAAoB;AAC7B,SAAS,kBAAkB;AAC3B,SAAS,kBAAkB;AAC3B,SAAS,qBAAqB;AAC9B,SAAS,kBAAkB;AAC3B,SAAS,yBAAyB;AAClC,SAAS,iBAAiB;AAe1B,SAAS,UAAU,GAAuB;AACxC,SAAO,oBAAC,cAAW;AACrB;AAGO,MAAM,gBAAmC;AAAA,EAC9C,EAAE,UAAU,SAAS,OAAO,YAAY,OAAO,IAAI,SAAS,UAAU,OAAO,WAAW;AAAA,EACxF,EAAE,UAAU,QAAQ,OAAO,UAAU,OAAO,IAAI,SAAS,UAAU,OAAO,UAAU;AAAA,EACpF,EAAE,UAAU,SAAS,OAAO,SAAS,OAAO,IAAI,SAAS,UAAU,OAAO,aAAa;AAAA,EACvF,EAAE,UAAU,SAAS,OAAO,eAAe,OAAO,IAAI,SAAS,UAAU,OAAO,WAAW;AAAA,EAC3F,EAAE,UAAU,SAAS,OAAO,gBAAgB,OAAO,IAAI,SAAS,UAAU,OAAO,WAAW;AAAA;AAAA,EAE5F,EAAE,UAAU,QAAQ,OAAO,aAAa,OAAO,IAAI,SAAS,UAAU,OAAO,UAAU;AAAA;AAAA,EAEvF,EAAE,UAAU,WAAW,OAAO,cAAc,OAAO,IAAI,SAAS,aAAa,OAAO,kBAAkB;AAAA,EACtG,EAAE,UAAU,YAAY,OAAO,gBAAgB,OAAO,KAAK,SAAS,aAAa,OAAO,cAAc;AACxG;","names":[]}
@@ -0,0 +1,3 @@
1
+ export declare function CategoriesTab({ siteId }: {
2
+ siteId: string;
3
+ }): import("react").JSX.Element;
@@ -0,0 +1,140 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useState } from "react";
4
+ import { addDoc, collection, deleteDoc, doc, onSnapshot, setDoc } from "firebase/firestore";
5
+ import { getDb } from "../../firebase/client";
6
+ import { ShopCategorySchema } from "../../schema/shop";
7
+ import { Button, Card, Field, Input, Textarea } from "../ui/primitives";
8
+ import { Switch } from "../ui/switch";
9
+ import { useToast } from "../ui/toast";
10
+ import { useConfirm } from "../ui/ConfirmDialog";
11
+ import { MediaPickerButton } from "../media/MediaPicker";
12
+ import { slugify } from "../lib/slugify";
13
+ function CategoriesTab({ siteId }) {
14
+ const { toast } = useToast();
15
+ const confirm = useConfirm();
16
+ const [cats, setCats] = useState([]);
17
+ const [selected, setSelected] = useState(null);
18
+ useEffect(() => {
19
+ const db = getDb();
20
+ if (!db) return;
21
+ return onSnapshot(
22
+ collection(db, "sites", siteId, "shopCategories"),
23
+ (snap) => setCats(
24
+ snap.docs.map((d) => ({ id: d.id, ...d.data() })).sort((a, b) => (a.order ?? 0) - (b.order ?? 0))
25
+ )
26
+ );
27
+ }, [siteId]);
28
+ async function createCategory() {
29
+ const db = getDb();
30
+ if (!db) return;
31
+ const ref = await addDoc(collection(db, "sites", siteId, "shopCategories"), {
32
+ name: "Nieuwe categorie",
33
+ slug: `categorie-${cats.length + 1}`,
34
+ order: cats.length,
35
+ visible: true
36
+ });
37
+ setSelected(ref.id);
38
+ }
39
+ const current = cats.find((c) => c.id === selected) ?? null;
40
+ return /* @__PURE__ */ jsxs("div", { className: "grid gap-4 md:grid-cols-[260px_1fr]", children: [
41
+ /* @__PURE__ */ jsxs(Card, { className: "h-fit", children: [
42
+ /* @__PURE__ */ jsxs("div", { className: "mb-3 flex items-center justify-between", children: [
43
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Categorie\xEBn" }),
44
+ /* @__PURE__ */ jsx(Button, { size: "sm", onClick: createCategory, children: "+ Nieuw" })
45
+ ] }),
46
+ /* @__PURE__ */ jsxs("ul", { className: "grid gap-1", children: [
47
+ cats.map((c) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
48
+ "button",
49
+ {
50
+ onClick: () => setSelected(c.id),
51
+ className: `w-full truncate rounded-lg px-3 py-2 text-left text-sm ${c.id === selected ? "bg-brand-50 text-brand-700" : "hover:bg-slate-100"}`,
52
+ children: [
53
+ c.name,
54
+ c.visible === false && /* @__PURE__ */ jsx("span", { className: "ml-1 text-xs text-slate-400", children: "(verborgen)" })
55
+ ]
56
+ }
57
+ ) }, c.id)),
58
+ cats.length === 0 && /* @__PURE__ */ jsx("li", { className: "px-3 py-2 text-sm text-slate-500", children: "Nog geen categorie\xEBn." })
59
+ ] })
60
+ ] }),
61
+ current ? /* @__PURE__ */ jsx(
62
+ CategoryDetail,
63
+ {
64
+ siteId,
65
+ category: current,
66
+ onDeleted: () => setSelected(null),
67
+ onToast: toast,
68
+ confirm
69
+ },
70
+ current.id
71
+ ) : /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsx("p", { className: "text-sm text-slate-500", children: "Selecteer of maak een categorie." }) })
72
+ ] });
73
+ }
74
+ function CategoryDetail({
75
+ siteId,
76
+ category,
77
+ onDeleted,
78
+ onToast,
79
+ confirm
80
+ }) {
81
+ const [def, setDef] = useState(category);
82
+ async function save() {
83
+ const db = getDb();
84
+ if (!db) return;
85
+ const parsed = ShopCategorySchema.safeParse(def);
86
+ if (!parsed.success) {
87
+ onToast({ title: "Ongeldige categorie", description: parsed.error.issues[0]?.message, tone: "error" });
88
+ return;
89
+ }
90
+ await setDoc(doc(db, "sites", siteId, "shopCategories", category.id), parsed.data);
91
+ onToast({ title: "Categorie opgeslagen", tone: "success" });
92
+ }
93
+ async function remove() {
94
+ const db = getDb();
95
+ if (!db) return;
96
+ const ok = await confirm({
97
+ title: `"${category.name}" verwijderen?`,
98
+ description: "Producten in deze categorie blijven bestaan (alleen de koppeling vervalt)."
99
+ });
100
+ if (!ok) return;
101
+ await deleteDoc(doc(db, "sites", siteId, "shopCategories", category.id));
102
+ onDeleted();
103
+ }
104
+ return /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsxs("div", { className: "grid gap-4", children: [
105
+ /* @__PURE__ */ jsx(Field, { label: "Naam", children: /* @__PURE__ */ jsx(
106
+ Input,
107
+ {
108
+ value: def.name,
109
+ onChange: (e) => setDef((p) => ({ ...p, name: e.target.value, slug: slugify(e.target.value) || p.slug }))
110
+ }
111
+ ) }),
112
+ /* @__PURE__ */ jsx(Field, { label: "Slug", hint: "Wordt gebruikt in de winkel-URL (?cat=\u2026)", children: /* @__PURE__ */ jsx(Input, { value: def.slug, onChange: (e) => setDef((p) => ({ ...p, slug: slugify(e.target.value) })) }) }),
113
+ /* @__PURE__ */ jsx(Field, { label: "Omschrijving (optioneel)", children: /* @__PURE__ */ jsx(Textarea, { rows: 2, value: def.description ?? "", onChange: (e) => setDef((p) => ({ ...p, description: e.target.value })) }) }),
114
+ /* @__PURE__ */ jsx(Field, { label: "Afbeelding", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
115
+ def.imageUrl && /* @__PURE__ */ jsx("img", { src: def.imageUrl, alt: "", className: "h-14 w-14 rounded-lg object-cover" }),
116
+ /* @__PURE__ */ jsx(MediaPickerButton, { siteId, onSelect: (m) => setDef((p) => ({ ...p, imageUrl: m.url })) }),
117
+ def.imageUrl && /* @__PURE__ */ jsx(Button, { size: "sm", variant: "ghost", onClick: () => setDef((p) => ({ ...p, imageUrl: void 0 })), children: "Verwijder" })
118
+ ] }) }),
119
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-6", children: [
120
+ /* @__PURE__ */ jsx(Field, { label: "Volgorde", children: /* @__PURE__ */ jsx(
121
+ Input,
122
+ {
123
+ type: "number",
124
+ className: "w-24",
125
+ value: def.order ?? 0,
126
+ onChange: (e) => setDef((p) => ({ ...p, order: Math.max(0, Number(e.target.value) || 0) }))
127
+ }
128
+ ) }),
129
+ /* @__PURE__ */ jsx(Switch, { checked: def.visible !== false, onCheckedChange: (v) => setDef((p) => ({ ...p, visible: v })), label: "Zichtbaar in de winkel" })
130
+ ] }),
131
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
132
+ /* @__PURE__ */ jsx(Button, { onClick: save, children: "Opslaan" }),
133
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", onClick: remove, children: "Verwijderen" })
134
+ ] })
135
+ ] }) });
136
+ }
137
+ export {
138
+ CategoriesTab
139
+ };
140
+ //# sourceMappingURL=CategoriesTab.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/admin/shop/CategoriesTab.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { addDoc, collection, deleteDoc, doc, onSnapshot, setDoc } from \"firebase/firestore\";\nimport { getDb } from \"../../firebase/client\";\nimport { ShopCategorySchema, type ShopCategory } from \"../../schema/shop\";\nimport { Button, Card, Field, Input, Textarea } from \"../ui/primitives\";\nimport { Switch } from \"../ui/switch\";\nimport { useToast } from \"../ui/toast\";\nimport { useConfirm } from \"../ui/ConfirmDialog\";\nimport { MediaPickerButton } from \"../media/MediaPicker\";\nimport { slugify } from \"../lib/slugify\";\n\ntype StoredCategory = ShopCategory & { id: string };\n\nexport function CategoriesTab({ siteId }: { siteId: string }) {\n const { toast } = useToast();\n const confirm = useConfirm();\n const [cats, setCats] = useState<StoredCategory[]>([]);\n const [selected, setSelected] = useState<string | null>(null);\n\n useEffect(() => {\n const db = getDb();\n if (!db) return;\n return onSnapshot(collection(db, \"sites\", siteId, \"shopCategories\"), (snap) =>\n setCats(\n snap.docs\n .map((d) => ({ id: d.id, ...(d.data() as ShopCategory) }))\n .sort((a, b) => (a.order ?? 0) - (b.order ?? 0)),\n ),\n );\n }, [siteId]);\n\n async function createCategory() {\n const db = getDb();\n if (!db) return;\n const ref = await addDoc(collection(db, \"sites\", siteId, \"shopCategories\"), {\n name: \"Nieuwe categorie\",\n slug: `categorie-${cats.length + 1}`,\n order: cats.length,\n visible: true,\n } satisfies ShopCategory);\n setSelected(ref.id);\n }\n\n const current = cats.find((c) => c.id === selected) ?? null;\n\n return (\n <div className=\"grid gap-4 md:grid-cols-[260px_1fr]\">\n <Card className=\"h-fit\">\n <div className=\"mb-3 flex items-center justify-between\">\n <h3 className=\"text-sm font-semibold\">Categorieën</h3>\n <Button size=\"sm\" onClick={createCategory}>+ Nieuw</Button>\n </div>\n <ul className=\"grid gap-1\">\n {cats.map((c) => (\n <li key={c.id}>\n <button\n onClick={() => setSelected(c.id)}\n className={`w-full truncate rounded-lg px-3 py-2 text-left text-sm ${\n c.id === selected ? \"bg-brand-50 text-brand-700\" : \"hover:bg-slate-100\"\n }`}\n >\n {c.name}\n {c.visible === false && <span className=\"ml-1 text-xs text-slate-400\">(verborgen)</span>}\n </button>\n </li>\n ))}\n {cats.length === 0 && <li className=\"px-3 py-2 text-sm text-slate-500\">Nog geen categorieën.</li>}\n </ul>\n </Card>\n {current ? (\n <CategoryDetail\n key={current.id}\n siteId={siteId}\n category={current}\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 categorie.</p>\n </Card>\n )}\n </div>\n );\n}\n\nfunction CategoryDetail({\n siteId,\n category,\n onDeleted,\n onToast,\n confirm,\n}: {\n siteId: string;\n category: StoredCategory;\n onDeleted: () => void;\n onToast: (t: { title: string; description?: string; tone?: \"default\" | \"success\" | \"error\" }) => void;\n confirm: (opts: { title: string; description?: string }) => Promise<boolean>;\n}) {\n const [def, setDef] = useState<ShopCategory>(category);\n\n async function save() {\n const db = getDb();\n if (!db) return;\n const parsed = ShopCategorySchema.safeParse(def);\n if (!parsed.success) {\n onToast({ title: \"Ongeldige categorie\", description: parsed.error.issues[0]?.message, tone: \"error\" });\n return;\n }\n await setDoc(doc(db, \"sites\", siteId, \"shopCategories\", category.id), parsed.data);\n onToast({ title: \"Categorie opgeslagen\", tone: \"success\" });\n }\n\n async function remove() {\n const db = getDb();\n if (!db) return;\n const ok = await confirm({\n title: `\"${category.name}\" verwijderen?`,\n description: \"Producten in deze categorie blijven bestaan (alleen de koppeling vervalt).\",\n });\n if (!ok) return;\n await deleteDoc(doc(db, \"sites\", siteId, \"shopCategories\", category.id));\n onDeleted();\n }\n\n return (\n <Card>\n <div className=\"grid gap-4\">\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=\"Wordt gebruikt in de winkel-URL (?cat=…)\">\n <Input value={def.slug} onChange={(e) => setDef((p) => ({ ...p, slug: slugify(e.target.value) }))} />\n </Field>\n <Field label=\"Omschrijving (optioneel)\">\n <Textarea rows={2} value={def.description ?? \"\"} onChange={(e) => setDef((p) => ({ ...p, description: e.target.value }))} />\n </Field>\n <Field label=\"Afbeelding\">\n <div className=\"flex items-center gap-3\">\n {def.imageUrl && <img src={def.imageUrl} alt=\"\" className=\"h-14 w-14 rounded-lg object-cover\" />}\n <MediaPickerButton siteId={siteId} onSelect={(m) => setDef((p) => ({ ...p, imageUrl: m.url }))} />\n {def.imageUrl && (\n <Button size=\"sm\" variant=\"ghost\" onClick={() => setDef((p) => ({ ...p, imageUrl: undefined }))}>\n Verwijder\n </Button>\n )}\n </div>\n </Field>\n <div className=\"flex items-center gap-6\">\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.visible !== false} onCheckedChange={(v) => setDef((p) => ({ ...p, visible: v }))} label=\"Zichtbaar in de winkel\" />\n </div>\n <div className=\"flex items-center justify-between\">\n <Button onClick={save}>Opslaan</Button>\n <Button variant=\"ghost\" onClick={remove}>Verwijderen</Button>\n </div>\n </div>\n </Card>\n );\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Koppelingen — Stripe (eigen account van de klant) en e-mail (eigen SMTP, bv. Gmail of de
3
+ * mailbox bij de hostingpartij). Geheimen gaan uitsluitend via callables naar
4
+ * `sites/{siteId}/private/shop` (deny-all rules); dit paneel toont alleen gemaskeerde status.
5
+ * Alleen een siteadmin kan opslaan (de callables dwingen dat af).
6
+ */
7
+ export declare function ConnectionsTab({ siteId }: {
8
+ siteId: string;
9
+ }): import("react").JSX.Element;
@@ -0,0 +1,158 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useCallback, useEffect, useState } from "react";
4
+ import { Badge, Button, Card, Field, Input } from "../ui/primitives";
5
+ import { Switch } from "../ui/switch";
6
+ import { useToast } from "../ui/toast";
7
+ import { shopCallable } from "./lib";
8
+ function ConnectionsTab({ siteId }) {
9
+ const { toast } = useToast();
10
+ const [status, setStatus] = useState(null);
11
+ const [busy, setBusy] = useState(false);
12
+ const [stripeKey, setStripeKey] = useState("");
13
+ const [smtp, setSmtp] = useState({ host: "", port: "465", secure: true, user: "", pass: "", fromName: "", fromEmail: "", notifyTo: "" });
14
+ const refresh = useCallback(async () => {
15
+ try {
16
+ const s = await shopCallable("getShopStatus")({ siteId });
17
+ setStatus(s);
18
+ setSmtp((p) => ({
19
+ ...p,
20
+ host: s.smtp.host ?? p.host,
21
+ port: String(s.smtp.port ?? 465),
22
+ user: s.smtp.user ?? p.user,
23
+ fromName: s.smtp.fromName ?? p.fromName,
24
+ fromEmail: s.smtp.fromEmail ?? p.fromEmail,
25
+ notifyTo: s.smtp.notifyTo ?? p.notifyTo
26
+ }));
27
+ } catch {
28
+ setStatus(null);
29
+ }
30
+ }, [siteId]);
31
+ useEffect(() => {
32
+ void refresh();
33
+ }, [refresh]);
34
+ async function saveStripe() {
35
+ if (!stripeKey.trim().startsWith("rk_") && !stripeKey.trim().startsWith("sk_")) {
36
+ toast({ title: "Ongeldige sleutel", description: "Plak een geheime sleutel (sk_\u2026) of restricted key (rk_\u2026).", tone: "error" });
37
+ return;
38
+ }
39
+ setBusy(true);
40
+ try {
41
+ const res = await shopCallable("saveShopSecrets")({
42
+ siteId,
43
+ stripe: { secretKey: stripeKey.trim() }
44
+ });
45
+ toast({ title: "Stripe gekoppeld", description: res.account ? `Account: ${res.account}` : void 0, tone: "success" });
46
+ setStripeKey("");
47
+ await refresh();
48
+ } catch (e) {
49
+ toast({ title: "Stripe koppelen mislukt", description: e instanceof Error ? e.message : void 0, tone: "error" });
50
+ } finally {
51
+ setBusy(false);
52
+ }
53
+ }
54
+ async function saveSmtp() {
55
+ setBusy(true);
56
+ try {
57
+ await shopCallable("saveShopSecrets")({
58
+ siteId,
59
+ smtp: {
60
+ host: smtp.host.trim(),
61
+ port: Number(smtp.port) || 465,
62
+ secure: smtp.secure,
63
+ user: smtp.user.trim(),
64
+ // Leeg laten = bestaand wachtwoord behouden.
65
+ ...smtp.pass ? { pass: smtp.pass } : {},
66
+ fromName: smtp.fromName.trim(),
67
+ fromEmail: smtp.fromEmail.trim(),
68
+ notifyTo: smtp.notifyTo.trim()
69
+ }
70
+ });
71
+ toast({ title: "E-mailinstellingen opgeslagen", tone: "success" });
72
+ setSmtp((p) => ({ ...p, pass: "" }));
73
+ await refresh();
74
+ } catch (e) {
75
+ toast({ title: "Opslaan mislukt", description: e instanceof Error ? e.message : void 0, tone: "error" });
76
+ } finally {
77
+ setBusy(false);
78
+ }
79
+ }
80
+ async function sendTest() {
81
+ setBusy(true);
82
+ try {
83
+ const res = await shopCallable("sendShopTestMail")({ siteId });
84
+ toast({ title: "Testmail verstuurd", description: `Naar ${res.to} \u2014 check de inbox (en spam).`, tone: "success" });
85
+ } catch (e) {
86
+ toast({ title: "Testmail mislukt", description: e instanceof Error ? e.message : void 0, tone: "error" });
87
+ } finally {
88
+ setBusy(false);
89
+ }
90
+ }
91
+ return /* @__PURE__ */ jsxs("div", { className: "grid gap-4 lg:grid-cols-2", children: [
92
+ /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsxs("div", { className: "grid gap-4", children: [
93
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
94
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Stripe (betalingen)" }),
95
+ status && /* @__PURE__ */ jsx(Badge, { tone: status.stripe.configured ? "green" : "amber", children: status.stripe.configured ? `Gekoppeld${status.stripe.account ? ` \u2014 ${status.stripe.account}` : ""}` : "Nog niet gekoppeld" })
96
+ ] }),
97
+ /* @__PURE__ */ jsxs("ol", { className: "grid list-decimal gap-1 pl-5 text-sm text-slate-600", children: [
98
+ /* @__PURE__ */ jsxs("li", { children: [
99
+ "Maak een gratis account op ",
100
+ /* @__PURE__ */ jsx("a", { className: "underline", href: "https://dashboard.stripe.com/register", target: "_blank", rel: "noreferrer", children: "stripe.com" }),
101
+ " en doorloop de activatie (IBAN, KVK)."
102
+ ] }),
103
+ /* @__PURE__ */ jsxs("li", { children: [
104
+ "Zet in Stripe onder ",
105
+ /* @__PURE__ */ jsx("em", { children: "Settings \u2192 Payment methods" }),
106
+ " iDEAL aan."
107
+ ] }),
108
+ /* @__PURE__ */ jsxs("li", { children: [
109
+ "Maak onder ",
110
+ /* @__PURE__ */ jsx("em", { children: "Developers \u2192 API keys" }),
111
+ " een sleutel aan en plak hem hieronder. De webhook wordt automatisch voor je aangemaakt."
112
+ ] })
113
+ ] }),
114
+ /* @__PURE__ */ jsx(Field, { label: "Geheime sleutel (sk_live_\u2026 of rk_live_\u2026)", hint: "Wordt veilig opgeslagen; nooit zichtbaar in de browser", children: /* @__PURE__ */ jsx(Input, { type: "password", value: stripeKey, onChange: (e) => setStripeKey(e.target.value), placeholder: "sk_live_\u2026" }) }),
115
+ /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Button, { onClick: saveStripe, disabled: busy || !stripeKey.trim(), children: "Koppel Stripe" }) }),
116
+ status?.stripe.configured && !status.stripe.webhookConfigured && /* @__PURE__ */ jsx("p", { className: "rounded-lg bg-amber-50 px-3 py-2 text-sm text-amber-700", children: "Webhook nog niet geregistreerd \u2014 koppel opnieuw met een sleutel die webhook-rechten heeft." })
117
+ ] }) }),
118
+ /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsxs("div", { className: "grid gap-4", children: [
119
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
120
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "E-mail (bestelbevestigingen)" }),
121
+ status && /* @__PURE__ */ jsx(Badge, { tone: status.smtp.configured ? "green" : "amber", children: status.smtp.configured ? "Ingesteld" : "Nog niet ingesteld" })
122
+ ] }),
123
+ /* @__PURE__ */ jsxs("p", { className: "text-sm text-slate-600", children: [
124
+ "Mail wordt verstuurd via je eigen mailbox (SMTP): bv. het e-mailadres bij je hostingpartij, of Gmail (smtp.gmail.com, poort 465, met een ",
125
+ /* @__PURE__ */ jsx("a", { className: "underline", href: "https://myaccount.google.com/apppasswords", target: "_blank", rel: "noreferrer", children: "app-wachtwoord" }),
126
+ "). Zonder SMTP-instellingen valt de mail terug op de centrale XiWebdesign-afzender."
127
+ ] }),
128
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-4 md:grid-cols-[1fr_110px]", children: [
129
+ /* @__PURE__ */ jsx(Field, { label: "SMTP-server", children: /* @__PURE__ */ jsx(Input, { value: smtp.host, onChange: (e) => setSmtp((p) => ({ ...p, host: e.target.value })), placeholder: "smtp.gmail.com" }) }),
130
+ /* @__PURE__ */ jsx(Field, { label: "Poort", children: /* @__PURE__ */ jsx(Input, { value: smtp.port, onChange: (e) => setSmtp((p) => ({ ...p, port: e.target.value })), placeholder: "465" }) })
131
+ ] }),
132
+ /* @__PURE__ */ jsx(Switch, { checked: smtp.secure, onCheckedChange: (v) => setSmtp((p) => ({ ...p, secure: v })), label: "SSL (poort 465). Uit = STARTTLS (poort 587)" }),
133
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-4 md:grid-cols-2", children: [
134
+ /* @__PURE__ */ jsx(Field, { label: "Gebruikersnaam", children: /* @__PURE__ */ jsx(Input, { value: smtp.user, onChange: (e) => setSmtp((p) => ({ ...p, user: e.target.value })), placeholder: "info@jouwdomein.nl" }) }),
135
+ /* @__PURE__ */ jsx(Field, { label: "Wachtwoord", hint: status?.smtp.configured ? "Leeg laten = ongewijzigd" : void 0, children: /* @__PURE__ */ jsx(Input, { type: "password", value: smtp.pass, onChange: (e) => setSmtp((p) => ({ ...p, pass: e.target.value })) }) })
136
+ ] }),
137
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-4 md:grid-cols-2", children: [
138
+ /* @__PURE__ */ jsx(Field, { label: "Afzendernaam", children: /* @__PURE__ */ jsx(Input, { value: smtp.fromName, onChange: (e) => setSmtp((p) => ({ ...p, fromName: e.target.value })), placeholder: "Christelijk Kraamcadeau" }) }),
139
+ /* @__PURE__ */ jsx(Field, { label: "Afzenderadres", children: /* @__PURE__ */ jsx(Input, { value: smtp.fromEmail, onChange: (e) => setSmtp((p) => ({ ...p, fromEmail: e.target.value })), placeholder: "info@jouwdomein.nl" }) })
140
+ ] }),
141
+ /* @__PURE__ */ jsx(Field, { label: "Notificatie-adres", hint: "Hier ontvang je zelf een mail bij elke bestelling", children: /* @__PURE__ */ jsx(Input, { value: smtp.notifyTo, onChange: (e) => setSmtp((p) => ({ ...p, notifyTo: e.target.value })), placeholder: "bestellingen@jouwdomein.nl" }) }),
142
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
143
+ /* @__PURE__ */ jsx(Button, { onClick: saveSmtp, disabled: busy || !smtp.host.trim(), children: "Opslaan" }),
144
+ /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: sendTest, disabled: busy || !status?.smtp.configured, children: "Stuur testmail" })
145
+ ] })
146
+ ] }) }),
147
+ /* @__PURE__ */ jsx(Card, { className: "lg:col-span-2", children: /* @__PURE__ */ jsxs("p", { className: "text-sm text-slate-600", children: [
148
+ "Verzendkosten, valuta, ordernummer-prefix en de winkel-paden stel je in onder",
149
+ " ",
150
+ /* @__PURE__ */ jsx("strong", { children: "Instellingen \u2192 Modules \u2192 Webwinkel" }),
151
+ "."
152
+ ] }) })
153
+ ] });
154
+ }
155
+ export {
156
+ ConnectionsTab
157
+ };
158
+ //# sourceMappingURL=ConnectionsTab.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/admin/shop/ConnectionsTab.tsx"],"sourcesContent":["\"use client\";\n\nimport { useCallback, useEffect, useState } from \"react\";\nimport { Badge, Button, Card, Field, Input } from \"../ui/primitives\";\nimport { Switch } from \"../ui/switch\";\nimport { useToast } from \"../ui/toast\";\nimport { shopCallable } from \"./lib\";\n\ninterface ShopStatus {\n stripe: { configured: boolean; account?: string; webhookConfigured: boolean };\n smtp: { configured: boolean; host?: string; port?: number; user?: string; fromName?: string; fromEmail?: string; notifyTo?: string };\n}\n\n/**\n * Koppelingen — Stripe (eigen account van de klant) en e-mail (eigen SMTP, bv. Gmail of de\n * mailbox bij de hostingpartij). Geheimen gaan uitsluitend via callables naar\n * `sites/{siteId}/private/shop` (deny-all rules); dit paneel toont alleen gemaskeerde status.\n * Alleen een siteadmin kan opslaan (de callables dwingen dat af).\n */\nexport function ConnectionsTab({ siteId }: { siteId: string }) {\n const { toast } = useToast();\n const [status, setStatus] = useState<ShopStatus | null>(null);\n const [busy, setBusy] = useState(false);\n\n const [stripeKey, setStripeKey] = useState(\"\");\n const [smtp, setSmtp] = useState({ host: \"\", port: \"465\", secure: true, user: \"\", pass: \"\", fromName: \"\", fromEmail: \"\", notifyTo: \"\" });\n\n const refresh = useCallback(async () => {\n try {\n const s = await shopCallable<{ siteId: string }, ShopStatus>(\"getShopStatus\")({ siteId });\n setStatus(s);\n setSmtp((p) => ({\n ...p,\n host: s.smtp.host ?? p.host,\n port: String(s.smtp.port ?? 465),\n user: s.smtp.user ?? p.user,\n fromName: s.smtp.fromName ?? p.fromName,\n fromEmail: s.smtp.fromEmail ?? p.fromEmail,\n notifyTo: s.smtp.notifyTo ?? p.notifyTo,\n }));\n } catch {\n setStatus(null);\n }\n }, [siteId]);\n\n useEffect(() => {\n void refresh();\n }, [refresh]);\n\n async function saveStripe() {\n if (!stripeKey.trim().startsWith(\"rk_\") && !stripeKey.trim().startsWith(\"sk_\")) {\n toast({ title: \"Ongeldige sleutel\", description: \"Plak een geheime sleutel (sk_…) of restricted key (rk_…).\", tone: \"error\" });\n return;\n }\n setBusy(true);\n try {\n const res = await shopCallable<{ siteId: string; stripe: { secretKey: string } }, { account?: string }>(\"saveShopSecrets\")({\n siteId,\n stripe: { secretKey: stripeKey.trim() },\n });\n toast({ title: \"Stripe gekoppeld\", description: res.account ? `Account: ${res.account}` : undefined, tone: \"success\" });\n setStripeKey(\"\");\n await refresh();\n } catch (e) {\n toast({ title: \"Stripe koppelen mislukt\", description: e instanceof Error ? e.message : undefined, tone: \"error\" });\n } finally {\n setBusy(false);\n }\n }\n\n async function saveSmtp() {\n setBusy(true);\n try {\n await shopCallable<{ siteId: string; smtp: Record<string, unknown> }, { ok: boolean }>(\"saveShopSecrets\")({\n siteId,\n smtp: {\n host: smtp.host.trim(),\n port: Number(smtp.port) || 465,\n secure: smtp.secure,\n user: smtp.user.trim(),\n // Leeg laten = bestaand wachtwoord behouden.\n ...(smtp.pass ? { pass: smtp.pass } : {}),\n fromName: smtp.fromName.trim(),\n fromEmail: smtp.fromEmail.trim(),\n notifyTo: smtp.notifyTo.trim(),\n },\n });\n toast({ title: \"E-mailinstellingen opgeslagen\", tone: \"success\" });\n setSmtp((p) => ({ ...p, pass: \"\" }));\n await refresh();\n } catch (e) {\n toast({ title: \"Opslaan mislukt\", description: e instanceof Error ? e.message : undefined, tone: \"error\" });\n } finally {\n setBusy(false);\n }\n }\n\n async function sendTest() {\n setBusy(true);\n try {\n const res = await shopCallable<{ siteId: string }, { to: string }>(\"sendShopTestMail\")({ siteId });\n toast({ title: \"Testmail verstuurd\", description: `Naar ${res.to} — check de inbox (en spam).`, tone: \"success\" });\n } catch (e) {\n toast({ title: \"Testmail mislukt\", description: e instanceof Error ? e.message : undefined, tone: \"error\" });\n } finally {\n setBusy(false);\n }\n }\n\n return (\n <div className=\"grid gap-4 lg:grid-cols-2\">\n <Card>\n <div className=\"grid gap-4\">\n <div className=\"flex items-center justify-between\">\n <h3 className=\"text-sm font-semibold\">Stripe (betalingen)</h3>\n {status && (\n <Badge tone={status.stripe.configured ? \"green\" : \"amber\"}>\n {status.stripe.configured ? `Gekoppeld${status.stripe.account ? ` — ${status.stripe.account}` : \"\"}` : \"Nog niet gekoppeld\"}\n </Badge>\n )}\n </div>\n <ol className=\"grid list-decimal gap-1 pl-5 text-sm text-slate-600\">\n <li>Maak een gratis account op <a className=\"underline\" href=\"https://dashboard.stripe.com/register\" target=\"_blank\" rel=\"noreferrer\">stripe.com</a> en doorloop de activatie (IBAN, KVK).</li>\n <li>Zet in Stripe onder <em>Settings → Payment methods</em> iDEAL aan.</li>\n <li>Maak onder <em>Developers → API keys</em> een sleutel aan en plak hem hieronder. De webhook wordt automatisch voor je aangemaakt.</li>\n </ol>\n <Field label=\"Geheime sleutel (sk_live_… of rk_live_…)\" hint=\"Wordt veilig opgeslagen; nooit zichtbaar in de browser\">\n <Input type=\"password\" value={stripeKey} onChange={(e) => setStripeKey(e.target.value)} placeholder=\"sk_live_…\" />\n </Field>\n <div>\n <Button onClick={saveStripe} disabled={busy || !stripeKey.trim()}>Koppel Stripe</Button>\n </div>\n {status?.stripe.configured && !status.stripe.webhookConfigured && (\n <p className=\"rounded-lg bg-amber-50 px-3 py-2 text-sm text-amber-700\">\n Webhook nog niet geregistreerd — koppel opnieuw met een sleutel die webhook-rechten heeft.\n </p>\n )}\n </div>\n </Card>\n\n <Card>\n <div className=\"grid gap-4\">\n <div className=\"flex items-center justify-between\">\n <h3 className=\"text-sm font-semibold\">E-mail (bestelbevestigingen)</h3>\n {status && (\n <Badge tone={status.smtp.configured ? \"green\" : \"amber\"}>\n {status.smtp.configured ? \"Ingesteld\" : \"Nog niet ingesteld\"}\n </Badge>\n )}\n </div>\n <p className=\"text-sm text-slate-600\">\n Mail wordt verstuurd via je eigen mailbox (SMTP): bv. het e-mailadres bij je hostingpartij, of Gmail\n (smtp.gmail.com, poort 465, met een <a className=\"underline\" href=\"https://myaccount.google.com/apppasswords\" target=\"_blank\" rel=\"noreferrer\">app-wachtwoord</a>).\n Zonder SMTP-instellingen valt de mail terug op de centrale XiWebdesign-afzender.\n </p>\n <div className=\"grid gap-4 md:grid-cols-[1fr_110px]\">\n <Field label=\"SMTP-server\">\n <Input value={smtp.host} onChange={(e) => setSmtp((p) => ({ ...p, host: e.target.value }))} placeholder=\"smtp.gmail.com\" />\n </Field>\n <Field label=\"Poort\">\n <Input value={smtp.port} onChange={(e) => setSmtp((p) => ({ ...p, port: e.target.value }))} placeholder=\"465\" />\n </Field>\n </div>\n <Switch checked={smtp.secure} onCheckedChange={(v) => setSmtp((p) => ({ ...p, secure: v }))} label=\"SSL (poort 465). Uit = STARTTLS (poort 587)\" />\n <div className=\"grid gap-4 md:grid-cols-2\">\n <Field label=\"Gebruikersnaam\">\n <Input value={smtp.user} onChange={(e) => setSmtp((p) => ({ ...p, user: e.target.value }))} placeholder=\"info@jouwdomein.nl\" />\n </Field>\n <Field label=\"Wachtwoord\" hint={status?.smtp.configured ? \"Leeg laten = ongewijzigd\" : undefined}>\n <Input type=\"password\" value={smtp.pass} onChange={(e) => setSmtp((p) => ({ ...p, pass: e.target.value }))} />\n </Field>\n </div>\n <div className=\"grid gap-4 md:grid-cols-2\">\n <Field label=\"Afzendernaam\">\n <Input value={smtp.fromName} onChange={(e) => setSmtp((p) => ({ ...p, fromName: e.target.value }))} placeholder=\"Christelijk Kraamcadeau\" />\n </Field>\n <Field label=\"Afzenderadres\">\n <Input value={smtp.fromEmail} onChange={(e) => setSmtp((p) => ({ ...p, fromEmail: e.target.value }))} placeholder=\"info@jouwdomein.nl\" />\n </Field>\n </div>\n <Field label=\"Notificatie-adres\" hint=\"Hier ontvang je zelf een mail bij elke bestelling\">\n <Input value={smtp.notifyTo} onChange={(e) => setSmtp((p) => ({ ...p, notifyTo: e.target.value }))} placeholder=\"bestellingen@jouwdomein.nl\" />\n </Field>\n <div className=\"flex items-center gap-2\">\n <Button onClick={saveSmtp} disabled={busy || !smtp.host.trim()}>Opslaan</Button>\n <Button variant=\"secondary\" onClick={sendTest} disabled={busy || !status?.smtp.configured}>Stuur testmail</Button>\n </div>\n </div>\n </Card>\n\n <Card className=\"lg:col-span-2\">\n <p className=\"text-sm text-slate-600\">\n Verzendkosten, valuta, ordernummer-prefix en de winkel-paden stel je in onder{\" \"}\n <strong>Instellingen → Modules → Webwinkel</strong>.\n </p>\n </Card>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,3 @@
1
+ export declare function OrdersTab({ siteId }: {
2
+ siteId: string;
3
+ }): import("react").JSX.Element;