@xiwebdesign/xicms-core 5.8.3 → 6.1.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 (37) hide show
  1. package/dist/admin/panelRegistry.js +1 -3
  2. package/dist/admin/panelRegistry.js.map +1 -1
  3. package/dist/admin/settings/MailSettings.d.ts +10 -0
  4. package/dist/admin/settings/MailSettings.js +166 -0
  5. package/dist/admin/settings/MailSettings.js.map +1 -0
  6. package/dist/admin/settings/SettingsPanel.js +8 -2
  7. package/dist/admin/settings/SettingsPanel.js.map +1 -1
  8. package/dist/admin/shop/ConnectionsTab.d.ts +7 -3
  9. package/dist/admin/shop/ConnectionsTab.js +8 -116
  10. package/dist/admin/shop/ConnectionsTab.js.map +1 -1
  11. package/dist/admin/shop/ShopPanel.d.ts +2 -1
  12. package/dist/admin/shop/ShopPanel.js.map +1 -1
  13. package/dist/admin/shop/index.d.ts +9 -0
  14. package/dist/admin/shop/index.js +14 -0
  15. package/dist/admin/shop/index.js.map +1 -0
  16. package/dist/all.d.ts +6 -0
  17. package/dist/all.js +15 -0
  18. package/dist/all.js.map +1 -0
  19. package/dist/modules/builtins.d.ts +0 -1
  20. package/dist/modules/builtins.js +0 -4
  21. package/dist/modules/builtins.js.map +1 -1
  22. package/dist/modules/shop/index.d.ts +13 -0
  23. package/dist/modules/shop/index.js +15 -0
  24. package/dist/modules/shop/index.js.map +1 -0
  25. package/dist/schema/blocks.d.ts +1285 -1285
  26. package/dist/schema/customBlocks.d.ts +2 -2
  27. package/dist/schema/forms.d.ts +6 -6
  28. package/dist/schema/pages.d.ts +59 -59
  29. package/dist/schema/seo.d.ts +2 -2
  30. package/dist/schema/settings.d.ts +222 -222
  31. package/dist/schema/shop.d.ts +48 -48
  32. package/dist/schema/siteConfig.d.ts +108 -108
  33. package/dist/schema/symbols.d.ts +14 -14
  34. package/dist/server/index.d.ts +0 -1
  35. package/dist/server/index.js +0 -14
  36. package/dist/server/index.js.map +1 -1
  37. package/package.json +17 -1
@@ -7,7 +7,6 @@ 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";
11
10
  function NewsPanel(_) {
12
11
  return /* @__PURE__ */ jsx(NewsEditor, {});
13
12
  }
@@ -17,8 +16,7 @@ const builtinPanels = [
17
16
  { moduleId: "media", label: "Media", order: 30, minRole: "editor", Panel: MediaManager },
18
17
  { moduleId: "forms", label: "Formulieren", order: 40, minRole: "editor", Panel: FormsPanel },
19
18
  { 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
+ // Webwinkel (6.0): opt-in via `@xiwebdesign/xicms-core/admin/shop` → `shopPanel` in AdminApp.extraPanels.
22
20
  // Bouwstenen (5.0): custom blocks zonder deploy — hangt aan de altijd-actieve content-module.
23
21
  { moduleId: "content", label: "Bouwstenen", order: 60, minRole: "siteadmin", Panel: CustomBlocksPanel },
24
22
  { 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\";\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":[]}
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 // Webwinkel (6.0): opt-in via `@xiwebdesign/xicms-core/admin/shop` → `shopPanel` in AdminApp.extraPanels.\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;AAAA,EAG5F,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,10 @@
1
+ /**
2
+ * Site-brede mailprovider-configuratie (Resend / Postmark / eigen SMTP) + afzender-identiteit.
3
+ * Deze instelling wordt door MEERDERE modules gebruikt (formulier-notificaties én de shop), dus
4
+ * hoort site-breed thuis in de algemene instellingen — niet in een module-paneel. Geheimen gaan
5
+ * uitsluitend via callables naar `sites/{siteId}/private/mail` (deny-all rules); dit paneel toont
6
+ * alleen gemaskeerde status. Alleen een siteadmin kan opslaan.
7
+ */
8
+ export declare function MailSettings({ siteId }: {
9
+ siteId: string;
10
+ }): import("react").JSX.Element;
@@ -0,0 +1,166 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useCallback, useEffect, useState } from "react";
4
+ import { getFunctions, httpsCallable } from "firebase/functions";
5
+ import { getFirebaseApp } from "../../firebase/client";
6
+ import { functionsRegion } from "../../firebase/region";
7
+ import { Badge, Button, Card, Field, Input } from "../ui/primitives";
8
+ import { Switch } from "../ui/switch";
9
+ import { useToast } from "../ui/toast";
10
+ function callFunction(name) {
11
+ return async (data) => {
12
+ const app = getFirebaseApp();
13
+ if (!app) throw new Error("Firebase niet beschikbaar.");
14
+ const fn = httpsCallable(getFunctions(app, functionsRegion()), name);
15
+ const res = await fn(data);
16
+ return res.data;
17
+ };
18
+ }
19
+ function Segmented({
20
+ value,
21
+ options,
22
+ onChange
23
+ }) {
24
+ return /* @__PURE__ */ jsx("div", { className: "inline-flex rounded-lg border border-slate-200 bg-slate-50 p-0.5", children: options.map((o) => /* @__PURE__ */ jsx(
25
+ "button",
26
+ {
27
+ type: "button",
28
+ onClick: () => onChange(o.value),
29
+ className: `rounded-md px-3 py-1.5 text-sm font-medium transition ${value === o.value ? "bg-white text-brand-700 shadow-sm" : "text-slate-500 hover:text-slate-700"}`,
30
+ children: o.label
31
+ },
32
+ o.value
33
+ )) });
34
+ }
35
+ function mailProviderLabel(p) {
36
+ return p === "smtp" ? "Eigen SMTP" : p === "resend" ? "Resend" : "Postmark";
37
+ }
38
+ function MailSettings({ siteId }) {
39
+ const { toast } = useToast();
40
+ const [status, setStatus] = useState(null);
41
+ const [busy, setBusy] = useState(false);
42
+ const [mailProvider, setMailProvider] = useState("smtp");
43
+ const [resendKey, setResendKey] = useState("");
44
+ const [postmarkToken, setPostmarkToken] = useState("");
45
+ const [smtp, setSmtp] = useState({ host: "", port: "465", secure: true, user: "", pass: "" });
46
+ const [fromName, setFromName] = useState("");
47
+ const [fromEmail, setFromEmail] = useState("");
48
+ const [notifyTo, setNotifyTo] = useState("");
49
+ const refresh = useCallback(async () => {
50
+ try {
51
+ const m = await callFunction("getMailStatus")({ siteId });
52
+ setStatus(m);
53
+ setMailProvider(m.provider ?? (m.host ? "smtp" : m.hasPostmarkToken ? "postmark" : m.hasResendKey ? "resend" : "smtp"));
54
+ setSmtp((p) => ({ ...p, host: m.host ?? p.host, port: String(m.port ?? 465), secure: m.secure !== false, user: m.user ?? p.user }));
55
+ setFromName(m.fromName ?? "");
56
+ setFromEmail(m.fromEmail ?? "");
57
+ setNotifyTo(m.notifyTo ?? "");
58
+ } catch {
59
+ setStatus(null);
60
+ }
61
+ }, [siteId]);
62
+ useEffect(() => {
63
+ void refresh();
64
+ }, [refresh]);
65
+ async function saveMail() {
66
+ setBusy(true);
67
+ try {
68
+ const mail = { provider: mailProvider, fromName, fromEmail, notifyTo };
69
+ if (mailProvider === "resend") mail.resendApiKey = resendKey.trim() || void 0;
70
+ if (mailProvider === "postmark") mail.postmarkToken = postmarkToken.trim() || void 0;
71
+ if (mailProvider === "smtp") {
72
+ mail.host = smtp.host.trim();
73
+ mail.port = Number(smtp.port) || 465;
74
+ mail.secure = smtp.secure;
75
+ mail.user = smtp.user.trim();
76
+ if (smtp.pass) mail.pass = smtp.pass;
77
+ }
78
+ await callFunction("saveMailSettings")({ siteId, mail });
79
+ toast({ title: "E-mailinstellingen opgeslagen", tone: "success" });
80
+ setResendKey("");
81
+ setPostmarkToken("");
82
+ setSmtp((p) => ({ ...p, pass: "" }));
83
+ await refresh();
84
+ } catch (e) {
85
+ toast({ title: "Opslaan mislukt", description: e instanceof Error ? e.message : void 0, tone: "error" });
86
+ } finally {
87
+ setBusy(false);
88
+ }
89
+ }
90
+ async function sendTest() {
91
+ setBusy(true);
92
+ try {
93
+ const res = await callFunction("sendShopTestMail")({ siteId });
94
+ toast({ title: "Testmail verstuurd", description: `Naar ${res.to} \u2014 check de inbox (en spam).`, tone: "success" });
95
+ } catch (e) {
96
+ toast({ title: "Testmail mislukt", description: e instanceof Error ? e.message : void 0, tone: "error" });
97
+ } finally {
98
+ setBusy(false);
99
+ }
100
+ }
101
+ return /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsxs("div", { className: "grid gap-4", children: [
102
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [
103
+ /* @__PURE__ */ jsxs("div", { children: [
104
+ /* @__PURE__ */ jsx("h3", { className: "text-base font-semibold", children: "E-mail (formulieren & shop)" }),
105
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-slate-500", children: "De provider waarmee deze site e-mails verstuurt \u2014 formulier-notificaties \xE9n bestelbevestigingen. Een provider verstuurt alleen vanaf een geverifieerd domein." })
106
+ ] }),
107
+ status && /* @__PURE__ */ jsx(Badge, { tone: status.configured ? "green" : "amber", children: status.configured ? `${mailProviderLabel(mailProvider)} actief` : "Nog niet ingesteld" })
108
+ ] }),
109
+ /* @__PURE__ */ jsx(
110
+ Segmented,
111
+ {
112
+ value: mailProvider,
113
+ onChange: setMailProvider,
114
+ options: [
115
+ { value: "smtp", label: "Eigen SMTP" },
116
+ { value: "resend", label: "Resend" },
117
+ { value: "postmark", label: "Postmark" }
118
+ ]
119
+ }
120
+ ),
121
+ mailProvider === "smtp" && /* @__PURE__ */ jsxs("div", { className: "grid gap-3", children: [
122
+ /* @__PURE__ */ jsxs("p", { className: "text-sm text-slate-600", children: [
123
+ "Via je eigen mailbox: bv. het adres bij je hostingpartij, of Gmail (smtp.gmail.com, poort 465, met een",
124
+ " ",
125
+ /* @__PURE__ */ jsx("a", { className: "underline", href: "https://myaccount.google.com/apppasswords", target: "_blank", rel: "noreferrer", children: "app-wachtwoord" }),
126
+ ")."
127
+ ] }),
128
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-3 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-3 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?.hasSmtpPass ? "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
+ ] }),
138
+ mailProvider === "resend" && /* @__PURE__ */ jsxs("div", { className: "grid gap-3", children: [
139
+ /* @__PURE__ */ jsxs("p", { className: "text-sm text-slate-600", children: [
140
+ /* @__PURE__ */ jsx("a", { className: "underline", href: "https://resend.com", target: "_blank", rel: "noreferrer", children: "Resend" }),
141
+ " verstuurt vanaf een geverifieerd domein. Maak een API-sleutel (re_\u2026) en verifieer je afzenderdomein in het Resend-dashboard."
142
+ ] }),
143
+ /* @__PURE__ */ jsx(Field, { label: "Resend API-sleutel (re_\u2026)", hint: status?.hasResendKey ? "Leeg laten = ongewijzigd" : void 0, children: /* @__PURE__ */ jsx(Input, { type: "password", value: resendKey, onChange: (e) => setResendKey(e.target.value), placeholder: "re_\u2026" }) })
144
+ ] }),
145
+ mailProvider === "postmark" && /* @__PURE__ */ jsxs("div", { className: "grid gap-3", children: [
146
+ /* @__PURE__ */ jsxs("p", { className: "text-sm text-slate-600", children: [
147
+ /* @__PURE__ */ jsx("a", { className: "underline", href: "https://postmarkapp.com", target: "_blank", rel: "noreferrer", children: "Postmark" }),
148
+ " verstuurt vanaf een geverifieerde afzender/domein (Sender Signature). Kopieer het Server-token."
149
+ ] }),
150
+ /* @__PURE__ */ jsx(Field, { label: "Postmark server-token", hint: status?.hasPostmarkToken ? "Leeg laten = ongewijzigd" : void 0, children: /* @__PURE__ */ jsx(Input, { type: "password", value: postmarkToken, onChange: (e) => setPostmarkToken(e.target.value), placeholder: "xxxxxxxx-xxxx-\u2026" }) })
151
+ ] }),
152
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-3 md:grid-cols-2", children: [
153
+ /* @__PURE__ */ jsx(Field, { label: "Afzendernaam", children: /* @__PURE__ */ jsx(Input, { value: fromName, onChange: (e) => setFromName(e.target.value), placeholder: "Mijn Site" }) }),
154
+ /* @__PURE__ */ jsx(Field, { label: "Afzenderadres", children: /* @__PURE__ */ jsx(Input, { value: fromEmail, onChange: (e) => setFromEmail(e.target.value), placeholder: "info@jouwdomein.nl" }) })
155
+ ] }),
156
+ /* @__PURE__ */ jsx(Field, { label: "Notificatie-adres", hint: "Hier ontvang je zelf een mail bij elk formulier/elke bestelling; ook het doel van de testmail", children: /* @__PURE__ */ jsx(Input, { value: notifyTo, onChange: (e) => setNotifyTo(e.target.value), placeholder: "notificaties@jouwdomein.nl" }) }),
157
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
158
+ /* @__PURE__ */ jsx(Button, { onClick: saveMail, disabled: busy, children: "Opslaan" }),
159
+ /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: sendTest, disabled: busy || !status?.configured, children: "Testmail versturen" })
160
+ ] })
161
+ ] }) });
162
+ }
163
+ export {
164
+ MailSettings
165
+ };
166
+ //# sourceMappingURL=MailSettings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/admin/settings/MailSettings.tsx"],"sourcesContent":["\"use client\";\n\nimport { useCallback, useEffect, useState } from \"react\";\nimport { getFunctions, httpsCallable } from \"firebase/functions\";\nimport { getFirebaseApp } from \"../../firebase/client\";\nimport { functionsRegion } from \"../../firebase/region\";\nimport { Badge, Button, Card, Field, Input } from \"../ui/primitives\";\nimport { Switch } from \"../ui/switch\";\nimport { useToast } from \"../ui/toast\";\n\ntype MailProvider = \"resend\" | \"postmark\" | \"smtp\";\n\ninterface MailStatus {\n provider?: MailProvider;\n configured: boolean;\n host?: string;\n port?: number;\n secure?: boolean;\n user?: string;\n fromName?: string;\n fromEmail?: string;\n notifyTo?: string;\n hasResendKey?: boolean;\n hasPostmarkToken?: boolean;\n hasSmtpPass?: boolean;\n}\n\n/**\n * Generieke callable-helper (niet shop-gebonden): roept een functie op naam aan via de\n * client-Firebase. Bewust hier lokaal zodat de algemene instellingen niet van de opt-in\n * shop-module afhangen — mail is een site-brede instelling (zie docs/SHARED-SETTINGS.md).\n */\nfunction callFunction<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/** Segmented control voor providerkeuze. */\nfunction Segmented<T extends string>({\n value,\n options,\n onChange,\n}: {\n value: T;\n options: { value: T; label: string }[];\n onChange: (v: T) => void;\n}) {\n return (\n <div className=\"inline-flex rounded-lg border border-slate-200 bg-slate-50 p-0.5\">\n {options.map((o) => (\n <button\n key={o.value}\n type=\"button\"\n onClick={() => onChange(o.value)}\n className={`rounded-md px-3 py-1.5 text-sm font-medium transition ${\n value === o.value ? \"bg-white text-brand-700 shadow-sm\" : \"text-slate-500 hover:text-slate-700\"\n }`}\n >\n {o.label}\n </button>\n ))}\n </div>\n );\n}\n\nfunction mailProviderLabel(p: MailProvider): string {\n return p === \"smtp\" ? \"Eigen SMTP\" : p === \"resend\" ? \"Resend\" : \"Postmark\";\n}\n\n/**\n * Site-brede mailprovider-configuratie (Resend / Postmark / eigen SMTP) + afzender-identiteit.\n * Deze instelling wordt door MEERDERE modules gebruikt (formulier-notificaties én de shop), dus\n * hoort site-breed thuis in de algemene instellingen — niet in een module-paneel. Geheimen gaan\n * uitsluitend via callables naar `sites/{siteId}/private/mail` (deny-all rules); dit paneel toont\n * alleen gemaskeerde status. Alleen een siteadmin kan opslaan.\n */\nexport function MailSettings({ siteId }: { siteId: string }) {\n const { toast } = useToast();\n const [status, setStatus] = useState<MailStatus | null>(null);\n const [busy, setBusy] = useState(false);\n\n const [mailProvider, setMailProvider] = useState<MailProvider>(\"smtp\");\n const [resendKey, setResendKey] = useState(\"\");\n const [postmarkToken, setPostmarkToken] = useState(\"\");\n const [smtp, setSmtp] = useState({ host: \"\", port: \"465\", secure: true, user: \"\", pass: \"\" });\n const [fromName, setFromName] = useState(\"\");\n const [fromEmail, setFromEmail] = useState(\"\");\n const [notifyTo, setNotifyTo] = useState(\"\");\n\n const refresh = useCallback(async () => {\n try {\n const m = await callFunction<{ siteId: string }, MailStatus>(\"getMailStatus\")({ siteId });\n setStatus(m);\n setMailProvider(m.provider ?? (m.host ? \"smtp\" : m.hasPostmarkToken ? \"postmark\" : m.hasResendKey ? \"resend\" : \"smtp\"));\n setSmtp((p) => ({ ...p, host: m.host ?? p.host, port: String(m.port ?? 465), secure: m.secure !== false, user: m.user ?? p.user }));\n setFromName(m.fromName ?? \"\");\n setFromEmail(m.fromEmail ?? \"\");\n setNotifyTo(m.notifyTo ?? \"\");\n } catch {\n setStatus(null);\n }\n }, [siteId]);\n\n useEffect(() => {\n void refresh();\n }, [refresh]);\n\n async function saveMail() {\n setBusy(true);\n try {\n const mail: Record<string, unknown> = { provider: mailProvider, fromName, fromEmail, notifyTo };\n if (mailProvider === \"resend\") mail.resendApiKey = resendKey.trim() || undefined;\n if (mailProvider === \"postmark\") mail.postmarkToken = postmarkToken.trim() || undefined;\n if (mailProvider === \"smtp\") {\n mail.host = smtp.host.trim();\n mail.port = Number(smtp.port) || 465;\n mail.secure = smtp.secure;\n mail.user = smtp.user.trim();\n if (smtp.pass) mail.pass = smtp.pass;\n }\n await callFunction<{ siteId: string; mail: Record<string, unknown> }, { ok: boolean }>(\"saveMailSettings\")({ siteId, mail });\n toast({ title: \"E-mailinstellingen opgeslagen\", tone: \"success\" });\n setResendKey(\"\");\n setPostmarkToken(\"\");\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 // `sendShopTestMail` is een generieke testverzending (naam ongewijzigd voor compat).\n const res = await callFunction<{ 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 <Card>\n <div className=\"grid gap-4\">\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <div>\n <h3 className=\"text-base font-semibold\">E-mail (formulieren &amp; shop)</h3>\n <p className=\"text-xs text-slate-500\">\n De provider waarmee deze site e-mails verstuurt — formulier-notificaties én\n bestelbevestigingen. Een provider verstuurt alleen vanaf een geverifieerd domein.\n </p>\n </div>\n {status && (\n <Badge tone={status.configured ? \"green\" : \"amber\"}>\n {status.configured ? `${mailProviderLabel(mailProvider)} actief` : \"Nog niet ingesteld\"}\n </Badge>\n )}\n </div>\n\n <Segmented\n value={mailProvider}\n onChange={setMailProvider}\n options={[\n { value: \"smtp\", label: \"Eigen SMTP\" },\n { value: \"resend\", label: \"Resend\" },\n { value: \"postmark\", label: \"Postmark\" },\n ]}\n />\n\n {mailProvider === \"smtp\" && (\n <div className=\"grid gap-3\">\n <p className=\"text-sm text-slate-600\">\n Via je eigen mailbox: bv. het adres bij je hostingpartij, of Gmail (smtp.gmail.com, poort 465, met een{\" \"}\n <a className=\"underline\" href=\"https://myaccount.google.com/apppasswords\" target=\"_blank\" rel=\"noreferrer\">app-wachtwoord</a>).\n </p>\n <div className=\"grid gap-3 md:grid-cols-[1fr_110px]\">\n <Field label=\"SMTP-server\"><Input value={smtp.host} onChange={(e) => setSmtp((p) => ({ ...p, host: e.target.value }))} placeholder=\"smtp.gmail.com\" /></Field>\n <Field label=\"Poort\"><Input value={smtp.port} onChange={(e) => setSmtp((p) => ({ ...p, port: e.target.value }))} placeholder=\"465\" /></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-3 md:grid-cols-2\">\n <Field label=\"Gebruikersnaam\"><Input value={smtp.user} onChange={(e) => setSmtp((p) => ({ ...p, user: e.target.value }))} placeholder=\"info@jouwdomein.nl\" /></Field>\n <Field label=\"Wachtwoord\" hint={status?.hasSmtpPass ? \"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>\n )}\n\n {mailProvider === \"resend\" && (\n <div className=\"grid gap-3\">\n <p className=\"text-sm text-slate-600\">\n <a className=\"underline\" href=\"https://resend.com\" target=\"_blank\" rel=\"noreferrer\">Resend</a> verstuurt vanaf een geverifieerd domein.\n Maak een API-sleutel (re_…) en verifieer je afzenderdomein in het Resend-dashboard.\n </p>\n <Field label=\"Resend API-sleutel (re_…)\" hint={status?.hasResendKey ? \"Leeg laten = ongewijzigd\" : undefined}>\n <Input type=\"password\" value={resendKey} onChange={(e) => setResendKey(e.target.value)} placeholder=\"re_…\" />\n </Field>\n </div>\n )}\n\n {mailProvider === \"postmark\" && (\n <div className=\"grid gap-3\">\n <p className=\"text-sm text-slate-600\">\n <a className=\"underline\" href=\"https://postmarkapp.com\" target=\"_blank\" rel=\"noreferrer\">Postmark</a> verstuurt vanaf een geverifieerde afzender/domein (Sender Signature).\n Kopieer het Server-token.\n </p>\n <Field label=\"Postmark server-token\" hint={status?.hasPostmarkToken ? \"Leeg laten = ongewijzigd\" : undefined}>\n <Input type=\"password\" value={postmarkToken} onChange={(e) => setPostmarkToken(e.target.value)} placeholder=\"xxxxxxxx-xxxx-…\" />\n </Field>\n </div>\n )}\n\n {/* Gedeelde afzender-velden */}\n <div className=\"grid gap-3 md:grid-cols-2\">\n <Field label=\"Afzendernaam\"><Input value={fromName} onChange={(e) => setFromName(e.target.value)} placeholder=\"Mijn Site\" /></Field>\n <Field label=\"Afzenderadres\"><Input value={fromEmail} onChange={(e) => setFromEmail(e.target.value)} placeholder=\"info@jouwdomein.nl\" /></Field>\n </div>\n <Field label=\"Notificatie-adres\" hint=\"Hier ontvang je zelf een mail bij elk formulier/elke bestelling; ook het doel van de testmail\">\n <Input value={notifyTo} onChange={(e) => setNotifyTo(e.target.value)} placeholder=\"notificaties@jouwdomein.nl\" />\n </Field>\n\n <div className=\"flex items-center gap-2\">\n <Button onClick={saveMail} disabled={busy}>Opslaan</Button>\n <Button variant=\"secondary\" onClick={sendTest} disabled={busy || !status?.configured}>Testmail versturen</Button>\n </div>\n </div>\n </Card>\n );\n}\n"],"mappings":"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","names":[]}
@@ -16,6 +16,7 @@ import { useToast } from "../ui/toast";
16
16
  import { useConfirm } from "../ui/ConfirmDialog";
17
17
  import { ModuleSettingsForm } from "./moduleSettingsForm";
18
18
  import { SnapshotsCard } from "./SnapshotsCard";
19
+ import { MailSettings } from "./MailSettings";
19
20
  function SettingsPanel({ siteId }) {
20
21
  const { toast } = useToast();
21
22
  const confirm = useConfirm();
@@ -256,9 +257,14 @@ function SettingsPanel({ siteId }) {
256
257
  ] }, d.id);
257
258
  }) })
258
259
  ] }),
260
+ /* @__PURE__ */ jsx(MailSettings, { siteId }),
259
261
  /* @__PURE__ */ jsxs(Card, { children: [
260
- /* @__PURE__ */ jsx("h3", { className: "mb-1 text-base font-semibold", children: "Formulier-e-mail" }),
261
- /* @__PURE__ */ jsx("p", { className: "mb-3 text-xs text-slate-500", children: "Een provider verstuurt alleen vanaf een geverifieerd domein. Kies de XiCMS-afzender (werkt direct) of het eigen domein van de klant (vereist domeinverificatie)." }),
262
+ /* @__PURE__ */ jsx("h3", { className: "mb-1 text-base font-semibold", children: "Formulier-afzender" }),
263
+ /* @__PURE__ */ jsxs("p", { className: "mb-3 text-xs text-slate-500", children: [
264
+ "De afzender-identiteit die formulier-notificaties gebruiken. Kies de XiCMS-afzender (werkt direct) of het eigen domein van de klant (vereist domeinverificatie). De provider waarmee daadwerkelijk verstuurd wordt, stel je hierboven bij ",
265
+ /* @__PURE__ */ jsx("strong", { children: "E-mail" }),
266
+ " in."
267
+ ] }),
262
268
  /* @__PURE__ */ jsxs("div", { className: "grid gap-3 md:grid-cols-2", children: [
263
269
  /* @__PURE__ */ jsx(Field, { label: "Afzender-modus", children: /* @__PURE__ */ jsx(
264
270
  Select,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/settings/SettingsPanel.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useRef, useState } from \"react\";\nimport { doc, onSnapshot, writeBatch } from \"firebase/firestore\";\nimport { getFunctions, httpsCallable } from \"firebase/functions\";\nimport { getDb, getFirebaseApp } from \"../../firebase/client\";\nimport { functionsRegion } from \"../../firebase/region\";\nimport { SiteSettingsSchema, toPublicSettings, type SiteSettings } from \"../../schema/settings\";\nimport { useModules } from \"../../modules/context\";\nimport type { ModulesSiteConfig } from \"../../modules/types\";\nimport { builtinThemePresets } from \"../../theme/presets\";\nimport { Button, Card, Field, Input } from \"../ui/primitives\";\nimport { Select } from \"../ui/select\";\nimport { Switch } from \"../ui/switch\";\nimport { ArrayEditor } from \"../ui/ArrayEditor\";\nimport { useToast } from \"../ui/toast\";\nimport { useConfirm } from \"../ui/ConfirmDialog\";\nimport { ModuleSettingsForm } from \"./moduleSettingsForm\";\nimport { SnapshotsCard } from \"./SnapshotsCard\";\n\nexport function SettingsPanel({ siteId }: { siteId: string }) {\n const { toast } = useToast();\n const confirm = useConfirm();\n const { defs } = useModules();\n const [s, setS] = useState<SiteSettings>({});\n const [loaded, setLoaded] = useState(false);\n const [exporting, setExporting] = useState(false);\n const [restoring, setRestoring] = useState(false);\n const restoreInput = useRef<HTMLInputElement>(null);\n\n async function downloadBackup(silent = false): Promise<boolean> {\n const app = getFirebaseApp();\n if (!app) return false;\n setExporting(true);\n try {\n const fn = httpsCallable<{ siteId: string }, Record<string, unknown>>(\n getFunctions(app, functionsRegion()),\n \"exportSite\",\n );\n const res = await fn({ siteId });\n const blob = new Blob([JSON.stringify(res.data, null, 2)], { type: \"application/json\" });\n const url = URL.createObjectURL(blob);\n const a = document.createElement(\"a\");\n a.href = url;\n a.download = `xicms-backup-${siteId}-${new Date().toISOString().slice(0, 10)}.json`;\n a.click();\n URL.revokeObjectURL(url);\n if (!silent) toast({ title: \"Back-up gedownload\", tone: \"success\" });\n return true;\n } catch (e) {\n toast({ title: \"Back-up mislukt\", description: (e as Error).message, tone: \"error\" });\n return false;\n } finally {\n setExporting(false);\n }\n }\n\n async function restoreFromFile(file: File) {\n let backup: Record<string, unknown>;\n try {\n backup = JSON.parse(await file.text()) as Record<string, unknown>;\n } catch {\n toast({ title: \"Ongeldig bestand\", description: \"Dit is geen geldig back-upbestand.\", tone: \"error\" });\n return;\n }\n if (backup._format !== \"xicms-export\") {\n toast({ title: \"Ongeldig bestand\", description: \"Dit lijkt geen XiCMS-back-up.\", tone: \"error\" });\n return;\n }\n if (backup.siteId && backup.siteId !== siteId) {\n const ok = await confirm({\n title: \"Back-up van een andere site\",\n description: `Deze back-up is van “${String(backup.siteId)}”, niet van deze site (${siteId}). Toch terugzetten?`,\n confirmLabel: \"Toch terugzetten\",\n tone: \"danger\",\n });\n if (!ok) return;\n }\n const ok = await confirm({\n title: \"Back-up terugzetten?\",\n description: `Dit VERVANGT alle huidige pagina's, nieuws, media en instellingen door de inhoud van de back-up${backup.exportedAt ? ` van ${new Date(String(backup.exportedAt)).toLocaleString(\"nl-NL\")}` : \"\"}. We maken eerst automatisch een back-up van de huidige stand.`,\n confirmLabel: \"Terugzetten\",\n tone: \"danger\",\n });\n if (!ok) return;\n setRestoring(true);\n try {\n // Veiligheid: eerst de huidige stand wegschrijven (als download) vóór we overschrijven.\n // Lukt die back-up niet, dan NIET doorgaan met de destructieve restore (anders ben je de\n // huidige stand kwijt zonder vangnet). De gebruiker kan het daarna opnieuw proberen.\n if (!(await downloadBackup(true))) {\n toast({ title: \"Terugzetten afgebroken\", description: \"De veiligheidsback-up van de huidige stand mislukte; er is niets overschreven.\", tone: \"error\" });\n return;\n }\n const app = getFirebaseApp();\n if (!app) throw new Error(\"Firebase niet geconfigureerd.\");\n const fn = httpsCallable(getFunctions(app, functionsRegion()), \"restoreSite\");\n await fn({ siteId, backup });\n toast({ title: \"Back-up teruggezet\", description: \"Herlaad de pagina om alles bij te werken.\", tone: \"success\" });\n } catch (e) {\n toast({ title: \"Terugzetten mislukt\", description: (e as Error).message, tone: \"error\" });\n } finally {\n setRestoring(false);\n }\n }\n\n useEffect(() => {\n const db = getDb();\n if (!db) {\n setLoaded(true);\n return;\n }\n return onSnapshot(doc(db, \"sites\", siteId, \"settings\", \"site\"), (snap) => {\n if (snap.exists()) setS(snap.data() as SiteSettings);\n setLoaded(true);\n });\n }, [siteId]);\n\n async function save() {\n const db = getDb();\n if (!db) return;\n const parsed = SiteSettingsSchema.safeParse(s);\n if (!parsed.success) {\n toast({ title: \"Ongeldige instellingen\", description: parsed.error.issues[0]?.message, tone: \"error\" });\n return;\n }\n // Volledige (rol-only) settings + de publiek-veilige subset die de live site leest —\n // atomair in één batch, zodat `site` en `public` nooit uit sync kunnen raken.\n try {\n const batch = writeBatch(db);\n batch.set(doc(db, \"sites\", siteId, \"settings\", \"site\"), parsed.data, { merge: true });\n batch.set(doc(db, \"sites\", siteId, \"settings\", \"public\"), toPublicSettings(parsed.data), { merge: true });\n await batch.commit();\n toast({ title: \"Opgeslagen\", description: \"Wijzigingen zijn direct live.\", tone: \"success\" });\n } catch (e) {\n toast({ title: \"Opslaan mislukt\", description: (e as Error).message, tone: \"error\" });\n }\n }\n\n if (!loaded) return <p className=\"text-sm text-slate-500\">Laden…</p>;\n\n const forms = s.forms ?? { mode: \"xicms\" as const };\n const theme = s.theme ?? { primary: \"#4f46e5\" };\n const footer = s.footer ?? {};\n\n // Hulpfuncties voor de module-instellingen.\n const moduleState = (id: string, defaultEnabled: boolean) => {\n const raw = (s.modules as ModulesSiteConfig | undefined)?.[id];\n if (typeof raw === \"boolean\") return { enabled: raw, config: {} as Record<string, unknown> };\n return { enabled: raw?.enabled ?? defaultEnabled, config: raw?.config ?? {} };\n };\n const setModule = (id: string, next: { enabled: boolean; config: Record<string, unknown> }) =>\n setS({ ...s, modules: { ...(s.modules as ModulesSiteConfig | undefined), [id]: next } });\n\n return (\n <div className=\"grid gap-4\">\n <Card>\n <h3 className=\"mb-3 text-base font-semibold\">Algemeen</h3>\n <div className=\"grid gap-3 md:grid-cols-2\">\n <Field label=\"Sitenaam\"><Input value={s.name ?? \"\"} onChange={(e) => setS({ ...s, name: e.target.value })} /></Field>\n <Field label=\"Domein\"><Input value={s.domain ?? \"\"} onChange={(e) => setS({ ...s, domain: e.target.value })} /></Field>\n <Field label=\"Contact-e-mail\"><Input value={s.contactEmail ?? \"\"} onChange={(e) => setS({ ...s, contactEmail: e.target.value })} /></Field>\n </div>\n </Card>\n\n <Card>\n <h3 className=\"mb-3 text-base font-semibold\">Huisstijl</h3>\n <div className=\"grid gap-3\">\n <Field label=\"Thema-preset\" hint=\"Vult kleuren, lettertype en afronding in één keer in.\">\n <Select\n value=\"\"\n placeholder=\"Kies een preset…\"\n onValueChange={(id) => {\n const preset = builtinThemePresets.find((p) => p.id === id);\n if (preset) setS({ ...s, theme: { ...theme, ...preset.theme } });\n }}\n options={builtinThemePresets.map((p) => ({ value: p.id, label: p.label }))}\n />\n </Field>\n <div className=\"grid gap-3 md:grid-cols-2\">\n <Field label=\"Primaire kleur\"><Input value={theme.primary} onChange={(e) => setS({ ...s, theme: { ...theme, primary: e.target.value } })} /></Field>\n <Field label=\"Accentkleur\"><Input value={theme.accent ?? \"\"} onChange={(e) => setS({ ...s, theme: { ...theme, accent: e.target.value } })} /></Field>\n <Field label=\"Achtergrondkleur\"><Input value={theme.background ?? \"\"} onChange={(e) => setS({ ...s, theme: { ...theme, background: e.target.value } })} placeholder=\"#ffffff\" /></Field>\n <Field label=\"Tekstkleur\"><Input value={theme.foreground ?? \"\"} onChange={(e) => setS({ ...s, theme: { ...theme, foreground: e.target.value } })} placeholder=\"#0f172a\" /></Field>\n </div>\n </div>\n </Card>\n\n <Card>\n <h3 className=\"mb-3 text-base font-semibold\">SEO (standaard)</h3>\n <div className=\"grid gap-3\">\n <Field label=\"Standaard-omschrijving\">\n <Input value={s.seoDefaults?.description ?? \"\"} onChange={(e) => setS({ ...s, seoDefaults: { ...s.seoDefaults, description: e.target.value } })} />\n </Field>\n <Field label=\"OG-afbeelding (URL)\">\n <Input value={s.seoDefaults?.ogImage ?? \"\"} onChange={(e) => setS({ ...s, seoDefaults: { ...s.seoDefaults, ogImage: e.target.value } })} />\n </Field>\n </div>\n </Card>\n\n <Card>\n <h3 className=\"mb-1 text-base font-semibold\">Footer</h3>\n <p className=\"mb-3 text-xs text-slate-500\">Kolommen met links, social-links en copyright. Wijzigingen zijn direct live.</p>\n <div className=\"grid gap-4\">\n <div>\n <span className=\"text-sm font-medium\">Kolommen</span>\n <ArrayEditor\n items={footer.columns ?? []}\n onChange={(columns) => setS({ ...s, footer: { ...footer, columns } })}\n newItem={() => ({ heading: \"\", links: [] })}\n addLabel=\"+ Kolom\"\n emptyLabel=\"Nog geen kolommen.\"\n renderRow={(col, update) => (\n <div className=\"grid gap-2\">\n <Field label=\"Kop\"><Input value={col.heading ?? \"\"} onChange={(e) => update({ heading: e.target.value })} /></Field>\n <ArrayEditor\n items={col.links ?? []}\n onChange={(links) => update({ links })}\n newItem={() => ({ label: \"\", href: \"\" })}\n addLabel=\"+ Link\"\n renderRow={(link, updL) => (\n <div className=\"grid grid-cols-2 gap-2\">\n <Input value={link.label} placeholder=\"Label\" onChange={(e) => updL({ label: e.target.value })} />\n <Input value={link.href} placeholder=\"/pad of https://…\" onChange={(e) => updL({ href: e.target.value })} />\n </div>\n )}\n />\n </div>\n )}\n />\n </div>\n <div>\n <span className=\"text-sm font-medium\">Social-links</span>\n <ArrayEditor\n items={footer.social ?? []}\n onChange={(social) => setS({ ...s, footer: { ...footer, social } })}\n newItem={() => ({ platform: \"\", href: \"\" })}\n addLabel=\"+ Social-link\"\n renderRow={(link, update) => (\n <div className=\"grid grid-cols-2 gap-2\">\n <Input value={link.platform} placeholder=\"bv. Instagram\" onChange={(e) => update({ platform: e.target.value })} />\n <Input value={link.href} placeholder=\"https://…\" onChange={(e) => update({ href: e.target.value })} />\n </div>\n )}\n />\n </div>\n <div className=\"grid gap-3 md:grid-cols-2\">\n <Field label=\"Copyright (leeg = automatisch)\">\n <Input value={footer.copyright ?? \"\"} onChange={(e) => setS({ ...s, footer: { ...footer, copyright: e.target.value } })} />\n </Field>\n <label className=\"flex items-center gap-2 self-end text-sm\">\n <Switch checked={footer.showAdminLink !== false} onCheckedChange={(v) => setS({ ...s, footer: { ...footer, showAdminLink: v } })} />\n “Beheer”-link tonen\n </label>\n </div>\n </div>\n </Card>\n\n <Card>\n <h3 className=\"mb-1 text-base font-semibold\">Modules</h3>\n <p className=\"mb-3 text-xs text-slate-500\">Zet functies aan/uit en stel ze in. Uitgezette modules verbergen hun blokken en beheer-paneel.</p>\n <div className=\"grid gap-3\">\n {defs\n .filter((d) => !d.core)\n .map((d) => {\n const st = moduleState(d.id, d.defaultEnabled ?? true);\n return (\n <div key={d.id} className=\"rounded-lg border border-slate-200 p-3\">\n <label className=\"flex items-center justify-between gap-2\">\n <span>\n <strong className=\"text-sm\">{d.label}</strong>\n {d.description && <span className=\"block text-xs text-slate-500\">{d.description}</span>}\n </span>\n <Switch checked={st.enabled} onCheckedChange={(v) => setModule(d.id, { enabled: v, config: st.config })} />\n </label>\n {st.enabled && d.settingsSchema && (\n <div className=\"mt-3\">\n <ModuleSettingsForm\n schema={d.settingsSchema}\n value={st.config}\n onChange={(config) => setModule(d.id, { enabled: st.enabled, config })}\n />\n </div>\n )}\n </div>\n );\n })}\n </div>\n </Card>\n\n <Card>\n <h3 className=\"mb-1 text-base font-semibold\">Formulier-e-mail</h3>\n <p className=\"mb-3 text-xs text-slate-500\">\n Een provider verstuurt alleen vanaf een geverifieerd domein. Kies de XiCMS-afzender\n (werkt direct) of het eigen domein van de klant (vereist domeinverificatie).\n </p>\n <div className=\"grid gap-3 md:grid-cols-2\">\n <Field label=\"Afzender-modus\">\n <Select\n value={forms.mode}\n onValueChange={(v) => setS({ ...s, forms: { ...forms, mode: v as \"xicms\" | \"client\" } })}\n options={[\n { value: \"xicms\", label: \"XiCMS-domein (aanbevolen)\" },\n { value: \"client\", label: \"Eigen domein van de klant\" },\n ]}\n />\n </Field>\n <Field label=\"Van (from) e-mail\" hint=\"Moet een geverifieerd domein zijn.\">\n <Input value={forms.fromEmail ?? \"\"} onChange={(e) => setS({ ...s, forms: { ...forms, fromEmail: e.target.value } })} />\n </Field>\n <Field label=\"Van (naam)\"><Input value={forms.fromName ?? \"\"} onChange={(e) => setS({ ...s, forms: { ...forms, fromName: e.target.value } })} /></Field>\n <Field label=\"Notificaties naar\" hint=\"Meestal het mailadres van de klant.\">\n <Input value={forms.notifyTo ?? \"\"} onChange={(e) => setS({ ...s, forms: { ...forms, notifyTo: e.target.value } })} />\n </Field>\n </div>\n </Card>\n\n <Card>\n <h3 className=\"mb-1 text-base font-semibold\">Back-up &amp; herstel</h3>\n <p className=\"mb-3 text-xs text-slate-500\">\n Sla alle content van deze site (pagina's, nieuws, formulieren, instellingen en je foto-galerij)\n op in één bestand. Bewaar dat veilig; je kunt het later weer terugzetten. De foto-bestanden\n zelf blijven in de cloud staan — de back-up bevat de teksten, instellingen en verwijzingen.\n </p>\n <div className=\"flex flex-wrap gap-2\">\n <Button variant=\"secondary\" onClick={() => void downloadBackup()} disabled={exporting || restoring}>\n {exporting ? \"Bezig…\" : \"Back-up downloaden\"}\n </Button>\n <Button variant=\"secondary\" onClick={() => restoreInput.current?.click()} disabled={restoring || exporting}>\n {restoring ? \"Terugzetten…\" : \"Back-up terugzetten…\"}\n </Button>\n <input\n ref={restoreInput}\n type=\"file\"\n accept=\"application/json,.json\"\n hidden\n onChange={(e) => {\n const f = e.target.files?.[0];\n if (f) void restoreFromFile(f);\n e.target.value = \"\";\n }}\n />\n </div>\n <p className=\"mt-2 text-xs text-slate-400\">\n Terugzetten vervangt de huidige content door de back-up. We maken automatisch eerst een\n back-up van de huidige stand, zodat je nooit iets kwijtraakt.\n </p>\n </Card>\n\n <SnapshotsCard siteId={siteId} />\n\n <div className=\"flex justify-end\">\n <Button onClick={save}>Instellingen opslaan</Button>\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":["ok"]}
1
+ {"version":3,"sources":["../../../src/admin/settings/SettingsPanel.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useRef, useState } from \"react\";\nimport { doc, onSnapshot, writeBatch } from \"firebase/firestore\";\nimport { getFunctions, httpsCallable } from \"firebase/functions\";\nimport { getDb, getFirebaseApp } from \"../../firebase/client\";\nimport { functionsRegion } from \"../../firebase/region\";\nimport { SiteSettingsSchema, toPublicSettings, type SiteSettings } from \"../../schema/settings\";\nimport { useModules } from \"../../modules/context\";\nimport type { ModulesSiteConfig } from \"../../modules/types\";\nimport { builtinThemePresets } from \"../../theme/presets\";\nimport { Button, Card, Field, Input } from \"../ui/primitives\";\nimport { Select } from \"../ui/select\";\nimport { Switch } from \"../ui/switch\";\nimport { ArrayEditor } from \"../ui/ArrayEditor\";\nimport { useToast } from \"../ui/toast\";\nimport { useConfirm } from \"../ui/ConfirmDialog\";\nimport { ModuleSettingsForm } from \"./moduleSettingsForm\";\nimport { SnapshotsCard } from \"./SnapshotsCard\";\nimport { MailSettings } from \"./MailSettings\";\n\nexport function SettingsPanel({ siteId }: { siteId: string }) {\n const { toast } = useToast();\n const confirm = useConfirm();\n const { defs } = useModules();\n const [s, setS] = useState<SiteSettings>({});\n const [loaded, setLoaded] = useState(false);\n const [exporting, setExporting] = useState(false);\n const [restoring, setRestoring] = useState(false);\n const restoreInput = useRef<HTMLInputElement>(null);\n\n async function downloadBackup(silent = false): Promise<boolean> {\n const app = getFirebaseApp();\n if (!app) return false;\n setExporting(true);\n try {\n const fn = httpsCallable<{ siteId: string }, Record<string, unknown>>(\n getFunctions(app, functionsRegion()),\n \"exportSite\",\n );\n const res = await fn({ siteId });\n const blob = new Blob([JSON.stringify(res.data, null, 2)], { type: \"application/json\" });\n const url = URL.createObjectURL(blob);\n const a = document.createElement(\"a\");\n a.href = url;\n a.download = `xicms-backup-${siteId}-${new Date().toISOString().slice(0, 10)}.json`;\n a.click();\n URL.revokeObjectURL(url);\n if (!silent) toast({ title: \"Back-up gedownload\", tone: \"success\" });\n return true;\n } catch (e) {\n toast({ title: \"Back-up mislukt\", description: (e as Error).message, tone: \"error\" });\n return false;\n } finally {\n setExporting(false);\n }\n }\n\n async function restoreFromFile(file: File) {\n let backup: Record<string, unknown>;\n try {\n backup = JSON.parse(await file.text()) as Record<string, unknown>;\n } catch {\n toast({ title: \"Ongeldig bestand\", description: \"Dit is geen geldig back-upbestand.\", tone: \"error\" });\n return;\n }\n if (backup._format !== \"xicms-export\") {\n toast({ title: \"Ongeldig bestand\", description: \"Dit lijkt geen XiCMS-back-up.\", tone: \"error\" });\n return;\n }\n if (backup.siteId && backup.siteId !== siteId) {\n const ok = await confirm({\n title: \"Back-up van een andere site\",\n description: `Deze back-up is van “${String(backup.siteId)}”, niet van deze site (${siteId}). Toch terugzetten?`,\n confirmLabel: \"Toch terugzetten\",\n tone: \"danger\",\n });\n if (!ok) return;\n }\n const ok = await confirm({\n title: \"Back-up terugzetten?\",\n description: `Dit VERVANGT alle huidige pagina's, nieuws, media en instellingen door de inhoud van de back-up${backup.exportedAt ? ` van ${new Date(String(backup.exportedAt)).toLocaleString(\"nl-NL\")}` : \"\"}. We maken eerst automatisch een back-up van de huidige stand.`,\n confirmLabel: \"Terugzetten\",\n tone: \"danger\",\n });\n if (!ok) return;\n setRestoring(true);\n try {\n // Veiligheid: eerst de huidige stand wegschrijven (als download) vóór we overschrijven.\n // Lukt die back-up niet, dan NIET doorgaan met de destructieve restore (anders ben je de\n // huidige stand kwijt zonder vangnet). De gebruiker kan het daarna opnieuw proberen.\n if (!(await downloadBackup(true))) {\n toast({ title: \"Terugzetten afgebroken\", description: \"De veiligheidsback-up van de huidige stand mislukte; er is niets overschreven.\", tone: \"error\" });\n return;\n }\n const app = getFirebaseApp();\n if (!app) throw new Error(\"Firebase niet geconfigureerd.\");\n const fn = httpsCallable(getFunctions(app, functionsRegion()), \"restoreSite\");\n await fn({ siteId, backup });\n toast({ title: \"Back-up teruggezet\", description: \"Herlaad de pagina om alles bij te werken.\", tone: \"success\" });\n } catch (e) {\n toast({ title: \"Terugzetten mislukt\", description: (e as Error).message, tone: \"error\" });\n } finally {\n setRestoring(false);\n }\n }\n\n useEffect(() => {\n const db = getDb();\n if (!db) {\n setLoaded(true);\n return;\n }\n return onSnapshot(doc(db, \"sites\", siteId, \"settings\", \"site\"), (snap) => {\n if (snap.exists()) setS(snap.data() as SiteSettings);\n setLoaded(true);\n });\n }, [siteId]);\n\n async function save() {\n const db = getDb();\n if (!db) return;\n const parsed = SiteSettingsSchema.safeParse(s);\n if (!parsed.success) {\n toast({ title: \"Ongeldige instellingen\", description: parsed.error.issues[0]?.message, tone: \"error\" });\n return;\n }\n // Volledige (rol-only) settings + de publiek-veilige subset die de live site leest —\n // atomair in één batch, zodat `site` en `public` nooit uit sync kunnen raken.\n try {\n const batch = writeBatch(db);\n batch.set(doc(db, \"sites\", siteId, \"settings\", \"site\"), parsed.data, { merge: true });\n batch.set(doc(db, \"sites\", siteId, \"settings\", \"public\"), toPublicSettings(parsed.data), { merge: true });\n await batch.commit();\n toast({ title: \"Opgeslagen\", description: \"Wijzigingen zijn direct live.\", tone: \"success\" });\n } catch (e) {\n toast({ title: \"Opslaan mislukt\", description: (e as Error).message, tone: \"error\" });\n }\n }\n\n if (!loaded) return <p className=\"text-sm text-slate-500\">Laden…</p>;\n\n const forms = s.forms ?? { mode: \"xicms\" as const };\n const theme = s.theme ?? { primary: \"#4f46e5\" };\n const footer = s.footer ?? {};\n\n // Hulpfuncties voor de module-instellingen.\n const moduleState = (id: string, defaultEnabled: boolean) => {\n const raw = (s.modules as ModulesSiteConfig | undefined)?.[id];\n if (typeof raw === \"boolean\") return { enabled: raw, config: {} as Record<string, unknown> };\n return { enabled: raw?.enabled ?? defaultEnabled, config: raw?.config ?? {} };\n };\n const setModule = (id: string, next: { enabled: boolean; config: Record<string, unknown> }) =>\n setS({ ...s, modules: { ...(s.modules as ModulesSiteConfig | undefined), [id]: next } });\n\n return (\n <div className=\"grid gap-4\">\n <Card>\n <h3 className=\"mb-3 text-base font-semibold\">Algemeen</h3>\n <div className=\"grid gap-3 md:grid-cols-2\">\n <Field label=\"Sitenaam\"><Input value={s.name ?? \"\"} onChange={(e) => setS({ ...s, name: e.target.value })} /></Field>\n <Field label=\"Domein\"><Input value={s.domain ?? \"\"} onChange={(e) => setS({ ...s, domain: e.target.value })} /></Field>\n <Field label=\"Contact-e-mail\"><Input value={s.contactEmail ?? \"\"} onChange={(e) => setS({ ...s, contactEmail: e.target.value })} /></Field>\n </div>\n </Card>\n\n <Card>\n <h3 className=\"mb-3 text-base font-semibold\">Huisstijl</h3>\n <div className=\"grid gap-3\">\n <Field label=\"Thema-preset\" hint=\"Vult kleuren, lettertype en afronding in één keer in.\">\n <Select\n value=\"\"\n placeholder=\"Kies een preset…\"\n onValueChange={(id) => {\n const preset = builtinThemePresets.find((p) => p.id === id);\n if (preset) setS({ ...s, theme: { ...theme, ...preset.theme } });\n }}\n options={builtinThemePresets.map((p) => ({ value: p.id, label: p.label }))}\n />\n </Field>\n <div className=\"grid gap-3 md:grid-cols-2\">\n <Field label=\"Primaire kleur\"><Input value={theme.primary} onChange={(e) => setS({ ...s, theme: { ...theme, primary: e.target.value } })} /></Field>\n <Field label=\"Accentkleur\"><Input value={theme.accent ?? \"\"} onChange={(e) => setS({ ...s, theme: { ...theme, accent: e.target.value } })} /></Field>\n <Field label=\"Achtergrondkleur\"><Input value={theme.background ?? \"\"} onChange={(e) => setS({ ...s, theme: { ...theme, background: e.target.value } })} placeholder=\"#ffffff\" /></Field>\n <Field label=\"Tekstkleur\"><Input value={theme.foreground ?? \"\"} onChange={(e) => setS({ ...s, theme: { ...theme, foreground: e.target.value } })} placeholder=\"#0f172a\" /></Field>\n </div>\n </div>\n </Card>\n\n <Card>\n <h3 className=\"mb-3 text-base font-semibold\">SEO (standaard)</h3>\n <div className=\"grid gap-3\">\n <Field label=\"Standaard-omschrijving\">\n <Input value={s.seoDefaults?.description ?? \"\"} onChange={(e) => setS({ ...s, seoDefaults: { ...s.seoDefaults, description: e.target.value } })} />\n </Field>\n <Field label=\"OG-afbeelding (URL)\">\n <Input value={s.seoDefaults?.ogImage ?? \"\"} onChange={(e) => setS({ ...s, seoDefaults: { ...s.seoDefaults, ogImage: e.target.value } })} />\n </Field>\n </div>\n </Card>\n\n <Card>\n <h3 className=\"mb-1 text-base font-semibold\">Footer</h3>\n <p className=\"mb-3 text-xs text-slate-500\">Kolommen met links, social-links en copyright. Wijzigingen zijn direct live.</p>\n <div className=\"grid gap-4\">\n <div>\n <span className=\"text-sm font-medium\">Kolommen</span>\n <ArrayEditor\n items={footer.columns ?? []}\n onChange={(columns) => setS({ ...s, footer: { ...footer, columns } })}\n newItem={() => ({ heading: \"\", links: [] })}\n addLabel=\"+ Kolom\"\n emptyLabel=\"Nog geen kolommen.\"\n renderRow={(col, update) => (\n <div className=\"grid gap-2\">\n <Field label=\"Kop\"><Input value={col.heading ?? \"\"} onChange={(e) => update({ heading: e.target.value })} /></Field>\n <ArrayEditor\n items={col.links ?? []}\n onChange={(links) => update({ links })}\n newItem={() => ({ label: \"\", href: \"\" })}\n addLabel=\"+ Link\"\n renderRow={(link, updL) => (\n <div className=\"grid grid-cols-2 gap-2\">\n <Input value={link.label} placeholder=\"Label\" onChange={(e) => updL({ label: e.target.value })} />\n <Input value={link.href} placeholder=\"/pad of https://…\" onChange={(e) => updL({ href: e.target.value })} />\n </div>\n )}\n />\n </div>\n )}\n />\n </div>\n <div>\n <span className=\"text-sm font-medium\">Social-links</span>\n <ArrayEditor\n items={footer.social ?? []}\n onChange={(social) => setS({ ...s, footer: { ...footer, social } })}\n newItem={() => ({ platform: \"\", href: \"\" })}\n addLabel=\"+ Social-link\"\n renderRow={(link, update) => (\n <div className=\"grid grid-cols-2 gap-2\">\n <Input value={link.platform} placeholder=\"bv. Instagram\" onChange={(e) => update({ platform: e.target.value })} />\n <Input value={link.href} placeholder=\"https://…\" onChange={(e) => update({ href: e.target.value })} />\n </div>\n )}\n />\n </div>\n <div className=\"grid gap-3 md:grid-cols-2\">\n <Field label=\"Copyright (leeg = automatisch)\">\n <Input value={footer.copyright ?? \"\"} onChange={(e) => setS({ ...s, footer: { ...footer, copyright: e.target.value } })} />\n </Field>\n <label className=\"flex items-center gap-2 self-end text-sm\">\n <Switch checked={footer.showAdminLink !== false} onCheckedChange={(v) => setS({ ...s, footer: { ...footer, showAdminLink: v } })} />\n “Beheer”-link tonen\n </label>\n </div>\n </div>\n </Card>\n\n <Card>\n <h3 className=\"mb-1 text-base font-semibold\">Modules</h3>\n <p className=\"mb-3 text-xs text-slate-500\">Zet functies aan/uit en stel ze in. Uitgezette modules verbergen hun blokken en beheer-paneel.</p>\n <div className=\"grid gap-3\">\n {defs\n .filter((d) => !d.core)\n .map((d) => {\n const st = moduleState(d.id, d.defaultEnabled ?? true);\n return (\n <div key={d.id} className=\"rounded-lg border border-slate-200 p-3\">\n <label className=\"flex items-center justify-between gap-2\">\n <span>\n <strong className=\"text-sm\">{d.label}</strong>\n {d.description && <span className=\"block text-xs text-slate-500\">{d.description}</span>}\n </span>\n <Switch checked={st.enabled} onCheckedChange={(v) => setModule(d.id, { enabled: v, config: st.config })} />\n </label>\n {st.enabled && d.settingsSchema && (\n <div className=\"mt-3\">\n <ModuleSettingsForm\n schema={d.settingsSchema}\n value={st.config}\n onChange={(config) => setModule(d.id, { enabled: st.enabled, config })}\n />\n </div>\n )}\n </div>\n );\n })}\n </div>\n </Card>\n\n {/* Site-brede mailprovider (formulieren én shop). Zie docs/SHARED-SETTINGS.md. */}\n <MailSettings siteId={siteId} />\n\n <Card>\n <h3 className=\"mb-1 text-base font-semibold\">Formulier-afzender</h3>\n <p className=\"mb-3 text-xs text-slate-500\">\n De afzender-identiteit die formulier-notificaties gebruiken. Kies de XiCMS-afzender\n (werkt direct) of het eigen domein van de klant (vereist domeinverificatie). De provider\n waarmee daadwerkelijk verstuurd wordt, stel je hierboven bij <strong>E-mail</strong> in.\n </p>\n <div className=\"grid gap-3 md:grid-cols-2\">\n <Field label=\"Afzender-modus\">\n <Select\n value={forms.mode}\n onValueChange={(v) => setS({ ...s, forms: { ...forms, mode: v as \"xicms\" | \"client\" } })}\n options={[\n { value: \"xicms\", label: \"XiCMS-domein (aanbevolen)\" },\n { value: \"client\", label: \"Eigen domein van de klant\" },\n ]}\n />\n </Field>\n <Field label=\"Van (from) e-mail\" hint=\"Moet een geverifieerd domein zijn.\">\n <Input value={forms.fromEmail ?? \"\"} onChange={(e) => setS({ ...s, forms: { ...forms, fromEmail: e.target.value } })} />\n </Field>\n <Field label=\"Van (naam)\"><Input value={forms.fromName ?? \"\"} onChange={(e) => setS({ ...s, forms: { ...forms, fromName: e.target.value } })} /></Field>\n <Field label=\"Notificaties naar\" hint=\"Meestal het mailadres van de klant.\">\n <Input value={forms.notifyTo ?? \"\"} onChange={(e) => setS({ ...s, forms: { ...forms, notifyTo: e.target.value } })} />\n </Field>\n </div>\n </Card>\n\n <Card>\n <h3 className=\"mb-1 text-base font-semibold\">Back-up &amp; herstel</h3>\n <p className=\"mb-3 text-xs text-slate-500\">\n Sla alle content van deze site (pagina's, nieuws, formulieren, instellingen en je foto-galerij)\n op in één bestand. Bewaar dat veilig; je kunt het later weer terugzetten. De foto-bestanden\n zelf blijven in de cloud staan — de back-up bevat de teksten, instellingen en verwijzingen.\n </p>\n <div className=\"flex flex-wrap gap-2\">\n <Button variant=\"secondary\" onClick={() => void downloadBackup()} disabled={exporting || restoring}>\n {exporting ? \"Bezig…\" : \"Back-up downloaden\"}\n </Button>\n <Button variant=\"secondary\" onClick={() => restoreInput.current?.click()} disabled={restoring || exporting}>\n {restoring ? \"Terugzetten…\" : \"Back-up terugzetten…\"}\n </Button>\n <input\n ref={restoreInput}\n type=\"file\"\n accept=\"application/json,.json\"\n hidden\n onChange={(e) => {\n const f = e.target.files?.[0];\n if (f) void restoreFromFile(f);\n e.target.value = \"\";\n }}\n />\n </div>\n <p className=\"mt-2 text-xs text-slate-400\">\n Terugzetten vervangt de huidige content door de back-up. We maken automatisch eerst een\n back-up van de huidige stand, zodat je nooit iets kwijtraakt.\n </p>\n </Card>\n\n <SnapshotsCard siteId={siteId} />\n\n <div className=\"flex justify-end\">\n <Button onClick={save}>Instellingen opslaan</Button>\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":["ok"]}
@@ -1,7 +1,11 @@
1
1
  /**
2
- * Koppelingen — betaalprovider (Stripe of Mollie) en mailprovider (Resend, Postmark of eigen SMTP).
3
- * Geheimen gaan uitsluitend via callables naar `sites/{siteId}/private/*` (deny-all rules); dit
4
- * paneel toont alleen gemaskeerde status. Alleen een siteadmin kan opslaan.
2
+ * Koppelingen — betaalprovider (Stripe of Mollie) voor de shop. Geheimen gaan uitsluitend via
3
+ * callables naar `sites/{siteId}/private/*` (deny-all rules); dit paneel toont alleen gemaskeerde
4
+ * status. Alleen een siteadmin kan opslaan.
5
+ *
6
+ * NB: de mailprovider (Resend/Postmark/SMTP) is een SITE-BREDE instelling — die staat niet meer
7
+ * hier maar onder Instellingen → E-mail (zie docs/SHARED-SETTINGS.md), zodat ook sites zónder shop
8
+ * hun formulier-mail kunnen instellen.
5
9
  */
6
10
  export declare function ConnectionsTab({ siteId }: {
7
11
  siteId: string;