@xiwebdesign/xicms-core 7.1.0 → 7.3.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 (110) hide show
  1. package/dist/XiCMSProvider.d.ts +2 -8
  2. package/dist/XiCMSProvider.js +1 -8
  3. package/dist/XiCMSProvider.js.map +1 -1
  4. package/dist/admin/AdminApp.js +7 -12
  5. package/dist/admin/AdminApp.js.map +1 -1
  6. package/dist/admin/NewsEditor.d.ts +8 -1
  7. package/dist/admin/NewsEditor.js +19 -18
  8. package/dist/admin/NewsEditor.js.map +1 -1
  9. package/dist/admin/SuperadminPanel.js +6 -7
  10. package/dist/admin/SuperadminPanel.js.map +1 -1
  11. package/dist/admin/customBlocks/CustomBlocksPanel.js +3 -4
  12. package/dist/admin/customBlocks/CustomBlocksPanel.js.map +1 -1
  13. package/dist/admin/lib/pagesClient.js +3 -4
  14. package/dist/admin/lib/pagesClient.js.map +1 -1
  15. package/dist/admin/lib/symbolsClient.js +3 -4
  16. package/dist/admin/lib/symbolsClient.js.map +1 -1
  17. package/dist/admin/pages/PagesPanel.js +5 -2
  18. package/dist/admin/pages/PagesPanel.js.map +1 -1
  19. package/dist/admin/settings/MailSettings.js +2 -3
  20. package/dist/admin/settings/MailSettings.js.map +1 -1
  21. package/dist/admin/settings/MailTemplates.js +2 -3
  22. package/dist/admin/settings/MailTemplates.js.map +1 -1
  23. package/dist/admin/settings/SettingsPanel.js +250 -225
  24. package/dist/admin/settings/SettingsPanel.js.map +1 -1
  25. package/dist/admin/settings/SnapshotsCard.js +3 -4
  26. package/dist/admin/settings/SnapshotsCard.js.map +1 -1
  27. package/dist/admin/shop/lib.js +2 -3
  28. package/dist/admin/shop/lib.js.map +1 -1
  29. package/dist/admin/visual/VisualEditor.js +239 -103
  30. package/dist/admin/visual/VisualEditor.js.map +1 -1
  31. package/dist/admin.css +1 -1
  32. package/dist/blocks/builtins/Agenda.js +6 -6
  33. package/dist/blocks/builtins/Agenda.js.map +1 -1
  34. package/dist/blocks/builtins/Banner.js +1 -1
  35. package/dist/blocks/builtins/Banner.js.map +1 -1
  36. package/dist/blocks/builtins/Breadcrumbs.js +5 -2
  37. package/dist/blocks/builtins/Breadcrumbs.js.map +1 -1
  38. package/dist/blocks/builtins/Cards.js +2 -2
  39. package/dist/blocks/builtins/Cards.js.map +1 -1
  40. package/dist/blocks/builtins/Contact.js +3 -3
  41. package/dist/blocks/builtins/Contact.js.map +1 -1
  42. package/dist/blocks/builtins/Cta.js +3 -3
  43. package/dist/blocks/builtins/Cta.js.map +1 -1
  44. package/dist/blocks/builtins/Downloads.js +5 -5
  45. package/dist/blocks/builtins/Downloads.js.map +1 -1
  46. package/dist/blocks/builtins/Elements.js +8 -5
  47. package/dist/blocks/builtins/Elements.js.map +1 -1
  48. package/dist/blocks/builtins/Hero.js +1 -1
  49. package/dist/blocks/builtins/Hero.js.map +1 -1
  50. package/dist/blocks/builtins/Jobs.js +4 -1
  51. package/dist/blocks/builtins/Jobs.js.map +1 -1
  52. package/dist/blocks/builtins/MapBlock.js +11 -11
  53. package/dist/blocks/builtins/MapBlock.js.map +1 -1
  54. package/dist/blocks/builtins/Newsletter.js +3 -2
  55. package/dist/blocks/builtins/Newsletter.js.map +1 -1
  56. package/dist/blocks/builtins/OpeningHours.js +2 -2
  57. package/dist/blocks/builtins/OpeningHours.js.map +1 -1
  58. package/dist/blocks/builtins/PdfBlock.js +2 -2
  59. package/dist/blocks/builtins/PdfBlock.js.map +1 -1
  60. package/dist/blocks/builtins/Portfolio.js +4 -4
  61. package/dist/blocks/builtins/Portfolio.js.map +1 -1
  62. package/dist/blocks/builtins/Pricing.js +3 -3
  63. package/dist/blocks/builtins/Pricing.js.map +1 -1
  64. package/dist/blocks/builtins/Sections.js +7 -7
  65. package/dist/blocks/builtins/Sections.js.map +1 -1
  66. package/dist/blocks/builtins/Team.js +3 -3
  67. package/dist/blocks/builtins/Team.js.map +1 -1
  68. package/dist/blocks/builtins/Widgets.js +28 -28
  69. package/dist/blocks/builtins/Widgets.js.map +1 -1
  70. package/dist/blocks/builtins/lib/submitForm.js +3 -4
  71. package/dist/blocks/builtins/lib/submitForm.js.map +1 -1
  72. package/dist/blocks/ui/index.d.ts +8 -1
  73. package/dist/blocks/ui/index.js +3 -2
  74. package/dist/blocks/ui/index.js.map +1 -1
  75. package/dist/components/EditBar.d.ts +9 -1
  76. package/dist/components/EditBar.js +6 -5
  77. package/dist/components/EditBar.js.map +1 -1
  78. package/dist/components/PageView.js +3 -5
  79. package/dist/components/PageView.js.map +1 -1
  80. package/dist/firebase/admin.js +4 -0
  81. package/dist/firebase/admin.js.map +1 -1
  82. package/dist/firebase/client.js +23 -5
  83. package/dist/firebase/client.js.map +1 -1
  84. package/dist/firebase/emulators.d.ts +9 -0
  85. package/dist/firebase/emulators.js +24 -0
  86. package/dist/firebase/emulators.js.map +1 -0
  87. package/dist/firebase/functionsClient.d.ts +18 -0
  88. package/dist/firebase/functionsClient.js +17 -0
  89. package/dist/firebase/functionsClient.js.map +1 -0
  90. package/dist/lock/DemoLock.js +19 -2
  91. package/dist/lock/DemoLock.js.map +1 -1
  92. package/dist/modules/shop/cartStore.js +2 -1
  93. package/dist/modules/shop/cartStore.js.map +1 -1
  94. package/dist/modules/shop/lib.js +9 -10
  95. package/dist/modules/shop/lib.js.map +1 -1
  96. package/dist/server/index.d.ts +1 -1
  97. package/dist/server/index.js +2 -0
  98. package/dist/server/index.js.map +1 -1
  99. package/dist/server/pages.d.ts +11 -0
  100. package/dist/server/pages.js +47 -0
  101. package/dist/server/pages.js.map +1 -1
  102. package/dist/server/version.d.ts +7 -0
  103. package/dist/server/version.js +2 -1
  104. package/dist/server/version.js.map +1 -1
  105. package/dist/styles.css +130 -29
  106. package/dist/theme/color.d.ts +17 -0
  107. package/dist/theme/color.js +12 -0
  108. package/dist/theme/color.js.map +1 -0
  109. package/dist/version.js +1 -1
  110. package/package.json +1 -1
@@ -1,10 +1,9 @@
1
1
  "use client";
2
- import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useEffect, useRef, useState } from "react";
4
4
  import { doc, onSnapshot } from "firebase/firestore";
5
- import { getFunctions, httpsCallable } from "firebase/functions";
5
+ import { callable } from "../../firebase/functionsClient.js";
6
6
  import { getDb, getFirebaseApp } from "../../firebase/client.js";
7
- import { functionsRegion } from "../../firebase/region.js";
8
7
  import { SiteSettingsSchema } from "../../schema/settings.js";
9
8
  import { useModules } from "../../modules/context.js";
10
9
  import { builtinThemePresets } from "../../theme/presets.js";
@@ -31,10 +30,7 @@ function SettingsPanel({ siteId }) {
31
30
  if (!app) return false;
32
31
  setExporting(true);
33
32
  try {
34
- const fn = httpsCallable(
35
- getFunctions(app, functionsRegion()),
36
- "exportSite"
37
- );
33
+ const fn = callable(app, "exportSite");
38
34
  const res = await fn({ siteId });
39
35
  const blob = new Blob([JSON.stringify(res.data, null, 2)], { type: "application/json" });
40
36
  const url = URL.createObjectURL(blob);
@@ -88,7 +84,7 @@ function SettingsPanel({ siteId }) {
88
84
  }
89
85
  const app = getFirebaseApp();
90
86
  if (!app) throw new Error("Firebase niet geconfigureerd.");
91
- const fn = httpsCallable(getFunctions(app, functionsRegion()), "restoreSite");
87
+ const fn = callable(app, "restoreSite");
92
88
  await fn({ siteId, backup });
93
89
  toast({ title: "Back-up teruggezet", description: "Herlaad de pagina om alles bij te werken.", tone: "success" });
94
90
  } catch (e) {
@@ -97,6 +93,7 @@ function SettingsPanel({ siteId }) {
97
93
  setRestoring(false);
98
94
  }
99
95
  }
96
+ const [sectie, setSectie] = useState("algemeen");
100
97
  useEffect(() => {
101
98
  const db = getDb();
102
99
  if (!db) {
@@ -119,7 +116,7 @@ function SettingsPanel({ siteId }) {
119
116
  try {
120
117
  const app = getFirebaseApp();
121
118
  if (!app) throw new Error("Firebase is niet geconfigureerd.");
122
- const fn = httpsCallable(getFunctions(app, functionsRegion()), "saveSiteSettings");
119
+ const fn = callable(app, "saveSiteSettings");
123
120
  const res = await fn({ siteId, settings: parsed.data });
124
121
  toast({ title: "Opgeslagen", description: res.data?.message ?? "Wijzigingen zijn live.", tone: "success" });
125
122
  } catch (e) {
@@ -137,235 +134,263 @@ function SettingsPanel({ siteId }) {
137
134
  return { enabled: raw?.enabled ?? defaultEnabled, config: raw?.config ?? {} };
138
135
  };
139
136
  const setModule = (id, next) => setS({ ...s, modules: { ...s.modules, [id]: next } });
140
- return /* @__PURE__ */ jsxs("div", { className: "grid gap-4", children: [
141
- /* @__PURE__ */ jsxs(Card, { children: [
142
- /* @__PURE__ */ jsx("h3", { className: "mb-3 text-base font-semibold", children: "Algemeen" }),
143
- /* @__PURE__ */ jsxs("div", { className: "grid gap-3 md:grid-cols-2", children: [
144
- /* @__PURE__ */ jsx(Field, { label: "Sitenaam", children: /* @__PURE__ */ jsx(Input, { value: s.name ?? "", onChange: (e) => setS({ ...s, name: e.target.value }) }) }),
145
- /* @__PURE__ */ jsx(Field, { label: "Domein", children: /* @__PURE__ */ jsx(Input, { value: s.domain ?? "", onChange: (e) => setS({ ...s, domain: e.target.value }) }) }),
146
- /* @__PURE__ */ jsx(Field, { label: "Contact-e-mail", children: /* @__PURE__ */ jsx(Input, { value: s.contactEmail ?? "", onChange: (e) => setS({ ...s, contactEmail: e.target.value }) }) })
147
- ] })
148
- ] }),
149
- /* @__PURE__ */ jsxs(Card, { children: [
150
- /* @__PURE__ */ jsx("h3", { className: "mb-3 text-base font-semibold", children: "Huisstijl" }),
151
- /* @__PURE__ */ jsxs("div", { className: "grid gap-3", children: [
152
- /* @__PURE__ */ jsx(Field, { label: "Thema-preset", hint: "Vult kleuren, lettertype en afronding in \xE9\xE9n keer in.", children: /* @__PURE__ */ jsx(
153
- Select,
154
- {
155
- value: "",
156
- placeholder: "Kies een preset\u2026",
157
- onValueChange: (id) => {
158
- const preset = builtinThemePresets.find((p) => p.id === id);
159
- if (preset) setS({ ...s, theme: { ...theme, ...preset.theme } });
160
- },
161
- options: builtinThemePresets.map((p) => ({ value: p.id, label: p.label }))
162
- }
163
- ) }),
137
+ const SECTIES = [
138
+ { sleutel: "algemeen", label: "Algemeen" },
139
+ { sleutel: "huisstijl", label: "Huisstijl" },
140
+ { sleutel: "seo", label: "SEO" },
141
+ { sleutel: "footer", label: "Footer" },
142
+ { sleutel: "modules", label: "Modules" },
143
+ { sleutel: "mail", label: "E-mail" },
144
+ { sleutel: "backup", label: "Back-up" }
145
+ ];
146
+ return (
147
+ /* Een formulier leest niet op 1300px breed; een invoerveld hoort niet breder te zijn dan de
148
+ tekst die erin komt. Vandaar een leesbreedte in plaats van het hele werkblad vullen. */
149
+ /* @__PURE__ */ jsxs("div", { className: "grid max-w-4xl gap-4", children: [
150
+ /* @__PURE__ */ jsx("nav", { "aria-label": "Onderdelen van de instellingen", className: "flex flex-wrap gap-1", children: SECTIES.map((x) => /* @__PURE__ */ jsx(
151
+ "button",
152
+ {
153
+ type: "button",
154
+ "aria-current": sectie === x.sleutel ? "page" : void 0,
155
+ onClick: () => setSectie(x.sleutel),
156
+ className: "rounded-lg px-3 py-1.5 text-sm font-medium text-slate-600 hover:bg-slate-200 hover:text-slate-900 focus-visible:ring-2 focus-visible:ring-brand-500 focus-visible:outline-none aria-[current=page]:bg-slate-200 aria-[current=page]:text-slate-900",
157
+ children: x.label
158
+ },
159
+ x.sleutel
160
+ )) }),
161
+ sectie === "algemeen" && /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs(Card, { children: [
162
+ /* @__PURE__ */ jsx("h3", { className: "mb-3 text-base font-semibold", children: "Algemeen" }),
164
163
  /* @__PURE__ */ jsxs("div", { className: "grid gap-3 md:grid-cols-2", children: [
165
- /* @__PURE__ */ jsx(Field, { label: "Primaire kleur", children: /* @__PURE__ */ jsx(Input, { value: theme.primary, onChange: (e) => setS({ ...s, theme: { ...theme, primary: e.target.value } }) }) }),
166
- /* @__PURE__ */ jsx(Field, { label: "Accentkleur", children: /* @__PURE__ */ jsx(Input, { value: theme.accent ?? "", onChange: (e) => setS({ ...s, theme: { ...theme, accent: e.target.value } }) }) }),
167
- /* @__PURE__ */ jsx(Field, { label: "Achtergrondkleur", children: /* @__PURE__ */ jsx(Input, { value: theme.background ?? "", onChange: (e) => setS({ ...s, theme: { ...theme, background: e.target.value } }), placeholder: "#ffffff" }) }),
168
- /* @__PURE__ */ jsx(Field, { label: "Tekstkleur", children: /* @__PURE__ */ jsx(Input, { value: theme.foreground ?? "", onChange: (e) => setS({ ...s, theme: { ...theme, foreground: e.target.value } }), placeholder: "#0f172a" }) })
169
- ] }),
170
- /* @__PURE__ */ jsx(
171
- Field,
172
- {
173
- label: "Donkere modus",
174
- hint: donkerModus === "auto" ? "Volgt de voorkeur van de bezoeker: wie zijn telefoon of computer op donker heeft staan, ziet de site donker." : donkerModus === "dark" ? "De site is voor iedereen donker, ongeacht de voorkeur van de bezoeker." : "De site is voor iedereen licht.",
175
- children: /* @__PURE__ */ jsx(
176
- Select,
177
- {
178
- value: donkerModus,
179
- onValueChange: (v) => setS({ ...s, theme: { ...theme, dark: { ...theme.dark ?? {}, mode: v } } }),
180
- options: [
181
- { value: "light", label: "Altijd licht" },
182
- { value: "dark", label: "Altijd donker" },
183
- { value: "auto", label: "Automatisch \u2014 volg de bezoeker" }
184
- ]
185
- }
186
- )
187
- }
188
- ),
189
- donkerModus !== "light" && /* @__PURE__ */ jsxs("div", { className: "grid gap-3 sm:grid-cols-3", children: [
190
- /* @__PURE__ */ jsx(Field, { label: "Donkere achtergrond", children: /* @__PURE__ */ jsx(
191
- Input,
192
- {
193
- value: theme.dark?.background ?? "",
194
- onChange: (e) => setS({ ...s, theme: { ...theme, dark: { ...theme.dark ?? {}, background: e.target.value } } }),
195
- placeholder: "#0f172a"
196
- }
197
- ) }),
198
- /* @__PURE__ */ jsx(Field, { label: "Donkere tekstkleur", children: /* @__PURE__ */ jsx(
199
- Input,
164
+ /* @__PURE__ */ jsx(Field, { label: "Sitenaam", children: /* @__PURE__ */ jsx(Input, { value: s.name ?? "", onChange: (e) => setS({ ...s, name: e.target.value }) }) }),
165
+ /* @__PURE__ */ jsx(Field, { label: "Domein", children: /* @__PURE__ */ jsx(Input, { value: s.domain ?? "", onChange: (e) => setS({ ...s, domain: e.target.value }) }) }),
166
+ /* @__PURE__ */ jsx(Field, { label: "Contact-e-mail", children: /* @__PURE__ */ jsx(Input, { value: s.contactEmail ?? "", onChange: (e) => setS({ ...s, contactEmail: e.target.value }) }) })
167
+ ] })
168
+ ] }) }),
169
+ sectie === "huisstijl" && /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs(Card, { children: [
170
+ /* @__PURE__ */ jsx("h3", { className: "mb-3 text-base font-semibold", children: "Huisstijl" }),
171
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-3", children: [
172
+ /* @__PURE__ */ jsx(Field, { label: "Thema-preset", hint: "Vult kleuren, lettertype en afronding in \xE9\xE9n keer in.", children: /* @__PURE__ */ jsx(
173
+ Select,
200
174
  {
201
- value: theme.dark?.foreground ?? "",
202
- onChange: (e) => setS({ ...s, theme: { ...theme, dark: { ...theme.dark ?? {}, foreground: e.target.value } } }),
203
- placeholder: "#e2e8f0"
175
+ value: "",
176
+ placeholder: "Kies een preset\u2026",
177
+ onValueChange: (id) => {
178
+ const preset = builtinThemePresets.find((p) => p.id === id);
179
+ if (preset) setS({ ...s, theme: { ...theme, ...preset.theme } });
180
+ },
181
+ options: builtinThemePresets.map((p) => ({ value: p.id, label: p.label }))
204
182
  }
205
183
  ) }),
206
- /* @__PURE__ */ jsx(Field, { label: "Donkere merkkleur", hint: "Laat leeg om dezelfde te gebruiken.", children: /* @__PURE__ */ jsx(
207
- Input,
208
- {
209
- value: theme.dark?.primary ?? "",
210
- onChange: (e) => setS({ ...s, theme: { ...theme, dark: { ...theme.dark ?? {}, primary: e.target.value } } }),
211
- placeholder: theme.primary
212
- }
213
- ) })
214
- ] }),
215
- /* @__PURE__ */ jsx(Field, { label: "Logo-URL (e-mail & factuur)", hint: "Absolute https-URL naar een PNG/JPG. Verschijnt in de mail-header en op de factuur-PDF.", children: /* @__PURE__ */ jsx(Input, { value: theme.logo ?? "", onChange: (e) => setS({ ...s, theme: { ...theme, logo: e.target.value } }), placeholder: "https://\u2026/logo.png" }) })
216
- ] })
217
- ] }),
218
- /* @__PURE__ */ jsxs(Card, { children: [
219
- /* @__PURE__ */ jsx("h3", { className: "mb-3 text-base font-semibold", children: "SEO (standaard)" }),
220
- /* @__PURE__ */ jsxs("div", { className: "grid gap-3", children: [
221
- /* @__PURE__ */ jsx(Field, { label: "Standaard-omschrijving", children: /* @__PURE__ */ jsx(Input, { value: s.seoDefaults?.description ?? "", onChange: (e) => setS({ ...s, seoDefaults: { ...s.seoDefaults, description: e.target.value } }) }) }),
222
- /* @__PURE__ */ jsx(Field, { label: "OG-afbeelding (URL)", children: /* @__PURE__ */ jsx(Input, { value: s.seoDefaults?.ogImage ?? "", onChange: (e) => setS({ ...s, seoDefaults: { ...s.seoDefaults, ogImage: e.target.value } }) }) })
223
- ] })
224
- ] }),
225
- /* @__PURE__ */ jsxs(Card, { children: [
226
- /* @__PURE__ */ jsx("h3", { className: "mb-1 text-base font-semibold", children: "Footer" }),
227
- /* @__PURE__ */ jsx("p", { className: "mb-3 text-xs text-slate-500", children: "Kolommen met links, social-links en copyright. Wijzigingen zijn direct live." }),
228
- /* @__PURE__ */ jsxs("div", { className: "grid gap-4", children: [
229
- /* @__PURE__ */ jsxs("div", { children: [
230
- /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: "Kolommen" }),
231
- /* @__PURE__ */ jsx(
232
- ArrayEditor,
233
- {
234
- items: footer.columns ?? [],
235
- onChange: (columns) => setS({ ...s, footer: { ...footer, columns } }),
236
- newItem: () => ({ heading: "", links: [] }),
237
- addLabel: "+ Kolom",
238
- emptyLabel: "Nog geen kolommen.",
239
- renderRow: (col, update) => /* @__PURE__ */ jsxs("div", { className: "grid gap-2", children: [
240
- /* @__PURE__ */ jsx(Field, { label: "Kop", children: /* @__PURE__ */ jsx(Input, { value: col.heading ?? "", onChange: (e) => update({ heading: e.target.value }) }) }),
241
- /* @__PURE__ */ jsx(
242
- ArrayEditor,
243
- {
244
- items: col.links ?? [],
245
- onChange: (links) => update({ links }),
246
- newItem: () => ({ label: "", href: "" }),
247
- addLabel: "+ Link",
248
- renderRow: (link, updL) => /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-2", children: [
249
- /* @__PURE__ */ jsx(Input, { value: link.label, placeholder: "Label", onChange: (e) => updL({ label: e.target.value }) }),
250
- /* @__PURE__ */ jsx(Input, { value: link.href, placeholder: "/pad of https://\u2026", onChange: (e) => updL({ href: e.target.value }) })
251
- ] })
252
- }
253
- )
254
- ] })
255
- }
256
- )
257
- ] }),
258
- /* @__PURE__ */ jsxs("div", { children: [
259
- /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: "Social-links" }),
184
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-3 md:grid-cols-2", children: [
185
+ /* @__PURE__ */ jsx(Field, { label: "Primaire kleur", children: /* @__PURE__ */ jsx(Input, { value: theme.primary, onChange: (e) => setS({ ...s, theme: { ...theme, primary: e.target.value } }) }) }),
186
+ /* @__PURE__ */ jsx(Field, { label: "Accentkleur", children: /* @__PURE__ */ jsx(Input, { value: theme.accent ?? "", onChange: (e) => setS({ ...s, theme: { ...theme, accent: e.target.value } }) }) }),
187
+ /* @__PURE__ */ jsx(Field, { label: "Achtergrondkleur", children: /* @__PURE__ */ jsx(Input, { value: theme.background ?? "", onChange: (e) => setS({ ...s, theme: { ...theme, background: e.target.value } }), placeholder: "#ffffff" }) }),
188
+ /* @__PURE__ */ jsx(Field, { label: "Tekstkleur", children: /* @__PURE__ */ jsx(Input, { value: theme.foreground ?? "", onChange: (e) => setS({ ...s, theme: { ...theme, foreground: e.target.value } }), placeholder: "#0f172a" }) })
189
+ ] }),
260
190
  /* @__PURE__ */ jsx(
261
- ArrayEditor,
191
+ Field,
262
192
  {
263
- items: footer.social ?? [],
264
- onChange: (social) => setS({ ...s, footer: { ...footer, social } }),
265
- newItem: () => ({ platform: "", href: "" }),
266
- addLabel: "+ Social-link",
267
- renderRow: (link, update) => /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-2", children: [
268
- /* @__PURE__ */ jsx(Input, { value: link.platform, placeholder: "bv. Instagram", onChange: (e) => update({ platform: e.target.value }) }),
269
- /* @__PURE__ */ jsx(Input, { value: link.href, placeholder: "https://\u2026", onChange: (e) => update({ href: e.target.value }) })
270
- ] })
193
+ label: "Donkere modus",
194
+ hint: donkerModus === "auto" ? "Volgt de voorkeur van de bezoeker: wie zijn telefoon of computer op donker heeft staan, ziet de site donker." : donkerModus === "dark" ? "De site is voor iedereen donker, ongeacht de voorkeur van de bezoeker." : "De site is voor iedereen licht.",
195
+ children: /* @__PURE__ */ jsx(
196
+ Select,
197
+ {
198
+ value: donkerModus,
199
+ onValueChange: (v) => setS({ ...s, theme: { ...theme, dark: { ...theme.dark ?? {}, mode: v } } }),
200
+ options: [
201
+ { value: "light", label: "Altijd licht" },
202
+ { value: "dark", label: "Altijd donker" },
203
+ { value: "auto", label: "Automatisch \u2014 volg de bezoeker" }
204
+ ]
205
+ }
206
+ )
271
207
  }
272
- )
273
- ] }),
274
- /* @__PURE__ */ jsxs("div", { className: "grid gap-3 md:grid-cols-2", children: [
275
- /* @__PURE__ */ jsx(Field, { label: "Copyright (leeg = automatisch)", children: /* @__PURE__ */ jsx(Input, { value: footer.copyright ?? "", onChange: (e) => setS({ ...s, footer: { ...footer, copyright: e.target.value } }) }) }),
276
- /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 self-end text-sm", children: [
277
- /* @__PURE__ */ jsx(Switch, { checked: footer.showAdminLink !== false, onCheckedChange: (v) => setS({ ...s, footer: { ...footer, showAdminLink: v } }) }),
278
- "\u201CBeheer\u201D-link tonen"
208
+ ),
209
+ donkerModus !== "light" && /* @__PURE__ */ jsxs("div", { className: "grid gap-3 sm:grid-cols-3", children: [
210
+ /* @__PURE__ */ jsx(Field, { label: "Donkere achtergrond", children: /* @__PURE__ */ jsx(
211
+ Input,
212
+ {
213
+ value: theme.dark?.background ?? "",
214
+ onChange: (e) => setS({ ...s, theme: { ...theme, dark: { ...theme.dark ?? {}, background: e.target.value } } }),
215
+ placeholder: "#0f172a"
216
+ }
217
+ ) }),
218
+ /* @__PURE__ */ jsx(Field, { label: "Donkere tekstkleur", children: /* @__PURE__ */ jsx(
219
+ Input,
220
+ {
221
+ value: theme.dark?.foreground ?? "",
222
+ onChange: (e) => setS({ ...s, theme: { ...theme, dark: { ...theme.dark ?? {}, foreground: e.target.value } } }),
223
+ placeholder: "#e2e8f0"
224
+ }
225
+ ) }),
226
+ /* @__PURE__ */ jsx(Field, { label: "Donkere merkkleur", hint: "Laat leeg om dezelfde te gebruiken.", children: /* @__PURE__ */ jsx(
227
+ Input,
228
+ {
229
+ value: theme.dark?.primary ?? "",
230
+ onChange: (e) => setS({ ...s, theme: { ...theme, dark: { ...theme.dark ?? {}, primary: e.target.value } } }),
231
+ placeholder: theme.primary
232
+ }
233
+ ) })
234
+ ] }),
235
+ /* @__PURE__ */ jsx(Field, { label: "Logo-URL (e-mail & factuur)", hint: "Absolute https-URL naar een PNG/JPG. Verschijnt in de mail-header en op de factuur-PDF.", children: /* @__PURE__ */ jsx(Input, { value: theme.logo ?? "", onChange: (e) => setS({ ...s, theme: { ...theme, logo: e.target.value } }), placeholder: "https://\u2026/logo.png" }) })
236
+ ] })
237
+ ] }) }),
238
+ sectie === "seo" && /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs(Card, { children: [
239
+ /* @__PURE__ */ jsx("h3", { className: "mb-3 text-base font-semibold", children: "SEO (standaard)" }),
240
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-3", children: [
241
+ /* @__PURE__ */ jsx(Field, { label: "Standaard-omschrijving", children: /* @__PURE__ */ jsx(Input, { value: s.seoDefaults?.description ?? "", onChange: (e) => setS({ ...s, seoDefaults: { ...s.seoDefaults, description: e.target.value } }) }) }),
242
+ /* @__PURE__ */ jsx(Field, { label: "OG-afbeelding (URL)", children: /* @__PURE__ */ jsx(Input, { value: s.seoDefaults?.ogImage ?? "", onChange: (e) => setS({ ...s, seoDefaults: { ...s.seoDefaults, ogImage: e.target.value } }) }) })
243
+ ] })
244
+ ] }) }),
245
+ sectie === "footer" && /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs(Card, { children: [
246
+ /* @__PURE__ */ jsx("h3", { className: "mb-1 text-base font-semibold", children: "Footer" }),
247
+ /* @__PURE__ */ jsx("p", { className: "mb-3 text-xs text-slate-500", children: "Kolommen met links, social-links en copyright. Wijzigingen zijn direct live." }),
248
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-4", children: [
249
+ /* @__PURE__ */ jsxs("div", { children: [
250
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: "Kolommen" }),
251
+ /* @__PURE__ */ jsx(
252
+ ArrayEditor,
253
+ {
254
+ items: footer.columns ?? [],
255
+ onChange: (columns) => setS({ ...s, footer: { ...footer, columns } }),
256
+ newItem: () => ({ heading: "", links: [] }),
257
+ addLabel: "+ Kolom",
258
+ emptyLabel: "Nog geen kolommen.",
259
+ renderRow: (col, update) => /* @__PURE__ */ jsxs("div", { className: "grid gap-2", children: [
260
+ /* @__PURE__ */ jsx(Field, { label: "Kop", children: /* @__PURE__ */ jsx(Input, { value: col.heading ?? "", onChange: (e) => update({ heading: e.target.value }) }) }),
261
+ /* @__PURE__ */ jsx(
262
+ ArrayEditor,
263
+ {
264
+ items: col.links ?? [],
265
+ onChange: (links) => update({ links }),
266
+ newItem: () => ({ label: "", href: "" }),
267
+ addLabel: "+ Link",
268
+ renderRow: (link, updL) => /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-2", children: [
269
+ /* @__PURE__ */ jsx(Input, { value: link.label, placeholder: "Label", onChange: (e) => updL({ label: e.target.value }) }),
270
+ /* @__PURE__ */ jsx(Input, { value: link.href, placeholder: "/pad of https://\u2026", onChange: (e) => updL({ href: e.target.value }) })
271
+ ] })
272
+ }
273
+ )
274
+ ] })
275
+ }
276
+ )
277
+ ] }),
278
+ /* @__PURE__ */ jsxs("div", { children: [
279
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: "Social-links" }),
280
+ /* @__PURE__ */ jsx(
281
+ ArrayEditor,
282
+ {
283
+ items: footer.social ?? [],
284
+ onChange: (social) => setS({ ...s, footer: { ...footer, social } }),
285
+ newItem: () => ({ platform: "", href: "" }),
286
+ addLabel: "+ Social-link",
287
+ renderRow: (link, update) => /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-2", children: [
288
+ /* @__PURE__ */ jsx(Input, { value: link.platform, placeholder: "bv. Instagram", onChange: (e) => update({ platform: e.target.value }) }),
289
+ /* @__PURE__ */ jsx(Input, { value: link.href, placeholder: "https://\u2026", onChange: (e) => update({ href: e.target.value }) })
290
+ ] })
291
+ }
292
+ )
293
+ ] }),
294
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-3 md:grid-cols-2", children: [
295
+ /* @__PURE__ */ jsx(Field, { label: "Copyright (leeg = automatisch)", children: /* @__PURE__ */ jsx(Input, { value: footer.copyright ?? "", onChange: (e) => setS({ ...s, footer: { ...footer, copyright: e.target.value } }) }) }),
296
+ /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 self-end text-sm", children: [
297
+ /* @__PURE__ */ jsx(Switch, { checked: footer.showAdminLink !== false, onCheckedChange: (v) => setS({ ...s, footer: { ...footer, showAdminLink: v } }) }),
298
+ "\u201CBeheer\u201D-link tonen"
299
+ ] })
279
300
  ] })
280
301
  ] })
281
- ] })
282
- ] }),
283
- /* @__PURE__ */ jsxs(Card, { children: [
284
- /* @__PURE__ */ jsx("h3", { className: "mb-1 text-base font-semibold", children: "Modules" }),
285
- /* @__PURE__ */ jsx("p", { className: "mb-3 text-xs text-slate-500", children: "Zet functies aan/uit en stel ze in. Uitgezette modules verbergen hun blokken en beheer-paneel." }),
286
- /* @__PURE__ */ jsx("div", { className: "grid gap-3", children: defs.filter((d) => !d.core).map((d) => {
287
- const st = moduleState(d.id, d.defaultEnabled ?? true);
288
- return /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-slate-200 p-3", children: [
289
- /* @__PURE__ */ jsxs("label", { className: "flex items-center justify-between gap-2", children: [
290
- /* @__PURE__ */ jsxs("span", { children: [
291
- /* @__PURE__ */ jsx("strong", { className: "text-sm", children: d.label }),
292
- d.description && /* @__PURE__ */ jsx("span", { className: "block text-xs text-slate-500", children: d.description })
302
+ ] }) }),
303
+ sectie === "modules" && /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs(Card, { children: [
304
+ /* @__PURE__ */ jsx("h3", { className: "mb-1 text-base font-semibold", children: "Modules" }),
305
+ /* @__PURE__ */ jsx("p", { className: "mb-3 text-xs text-slate-500", children: "Zet functies aan/uit en stel ze in. Uitgezette modules verbergen hun blokken en beheer-paneel." }),
306
+ /* @__PURE__ */ jsx("div", { className: "grid gap-3", children: defs.filter((d) => !d.core).map((d) => {
307
+ const st = moduleState(d.id, d.defaultEnabled ?? true);
308
+ return /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-slate-200 p-3", children: [
309
+ /* @__PURE__ */ jsxs("label", { className: "flex items-center justify-between gap-2", children: [
310
+ /* @__PURE__ */ jsxs("span", { children: [
311
+ /* @__PURE__ */ jsx("strong", { className: "text-sm", children: d.label }),
312
+ d.description && /* @__PURE__ */ jsx("span", { className: "block text-xs text-slate-500", children: d.description })
313
+ ] }),
314
+ /* @__PURE__ */ jsx(Switch, { checked: st.enabled, onCheckedChange: (v) => setModule(d.id, { enabled: v, config: st.config }) })
293
315
  ] }),
294
- /* @__PURE__ */ jsx(Switch, { checked: st.enabled, onCheckedChange: (v) => setModule(d.id, { enabled: v, config: st.config }) })
316
+ st.enabled && d.settingsSchema && /* @__PURE__ */ jsx("div", { className: "mt-3", children: /* @__PURE__ */ jsx(
317
+ ModuleSettingsForm,
318
+ {
319
+ schema: d.settingsSchema,
320
+ value: st.config,
321
+ onChange: (config) => setModule(d.id, { enabled: st.enabled, config })
322
+ }
323
+ ) })
324
+ ] }, d.id);
325
+ }) })
326
+ ] }) }),
327
+ sectie === "mail" && /* @__PURE__ */ jsxs(Fragment, { children: [
328
+ /* @__PURE__ */ jsx(MailSettings, { siteId }),
329
+ /* @__PURE__ */ jsxs(Card, { children: [
330
+ /* @__PURE__ */ jsx("h3", { className: "mb-1 text-base font-semibold", children: "E-mailteksten" }),
331
+ /* @__PURE__ */ jsxs("p", { className: "text-sm text-slate-600", children: [
332
+ "De teksten van automatische e-mails bewerk je bij het onderdeel zelf: formulier-mails onder",
333
+ " ",
334
+ /* @__PURE__ */ jsx("strong", { children: "Formulieren" }),
335
+ ", webwinkel-mails onder ",
336
+ /* @__PURE__ */ jsx("strong", { children: "Webwinkel \u2192 E-mails" }),
337
+ ". Provider en opmaak (logo, huisstijlkleur) gelden site-breed en stel je hier in."
338
+ ] })
339
+ ] }),
340
+ /* @__PURE__ */ jsxs(Card, { children: [
341
+ /* @__PURE__ */ jsx("h3", { className: "mb-1 text-base font-semibold", children: "Formulier-afzender" }),
342
+ /* @__PURE__ */ jsxs("p", { className: "mb-3 text-xs text-slate-500", children: [
343
+ "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 ",
344
+ /* @__PURE__ */ jsx("strong", { children: "E-mail" }),
345
+ " in."
295
346
  ] }),
296
- st.enabled && d.settingsSchema && /* @__PURE__ */ jsx("div", { className: "mt-3", children: /* @__PURE__ */ jsx(
297
- ModuleSettingsForm,
298
- {
299
- schema: d.settingsSchema,
300
- value: st.config,
301
- onChange: (config) => setModule(d.id, { enabled: st.enabled, config })
302
- }
303
- ) })
304
- ] }, d.id);
305
- }) })
306
- ] }),
307
- /* @__PURE__ */ jsx(MailSettings, { siteId }),
308
- /* @__PURE__ */ jsxs(Card, { children: [
309
- /* @__PURE__ */ jsx("h3", { className: "mb-1 text-base font-semibold", children: "E-mailteksten" }),
310
- /* @__PURE__ */ jsxs("p", { className: "text-sm text-slate-600", children: [
311
- "De teksten van automatische e-mails bewerk je bij het onderdeel zelf: formulier-mails onder",
312
- " ",
313
- /* @__PURE__ */ jsx("strong", { children: "Formulieren" }),
314
- ", webwinkel-mails onder ",
315
- /* @__PURE__ */ jsx("strong", { children: "Webwinkel \u2192 E-mails" }),
316
- ". Provider en opmaak (logo, huisstijlkleur) gelden site-breed en stel je hier in."
317
- ] })
318
- ] }),
319
- /* @__PURE__ */ jsxs(Card, { children: [
320
- /* @__PURE__ */ jsx("h3", { className: "mb-1 text-base font-semibold", children: "Formulier-afzender" }),
321
- /* @__PURE__ */ jsxs("p", { className: "mb-3 text-xs text-slate-500", children: [
322
- "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 ",
323
- /* @__PURE__ */ jsx("strong", { children: "E-mail" }),
324
- " in."
347
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-3 md:grid-cols-2", children: [
348
+ /* @__PURE__ */ jsx(Field, { label: "Afzender-modus", children: /* @__PURE__ */ jsx(
349
+ Select,
350
+ {
351
+ value: forms.mode,
352
+ onValueChange: (v) => setS({ ...s, forms: { ...forms, mode: v } }),
353
+ options: [
354
+ { value: "xicms", label: "XiCMS-domein (aanbevolen)" },
355
+ { value: "client", label: "Eigen domein van de klant" }
356
+ ]
357
+ }
358
+ ) }),
359
+ /* @__PURE__ */ jsx(Field, { label: "Van (from) e-mail", hint: "Moet een geverifieerd domein zijn.", children: /* @__PURE__ */ jsx(Input, { value: forms.fromEmail ?? "", onChange: (e) => setS({ ...s, forms: { ...forms, fromEmail: e.target.value } }) }) }),
360
+ /* @__PURE__ */ jsx(Field, { label: "Van (naam)", children: /* @__PURE__ */ jsx(Input, { value: forms.fromName ?? "", onChange: (e) => setS({ ...s, forms: { ...forms, fromName: e.target.value } }) }) }),
361
+ /* @__PURE__ */ jsx(Field, { label: "Notificaties naar", hint: "Meestal het mailadres van de klant.", children: /* @__PURE__ */ jsx(Input, { value: forms.notifyTo ?? "", onChange: (e) => setS({ ...s, forms: { ...forms, notifyTo: e.target.value } }) }) })
362
+ ] })
363
+ ] })
325
364
  ] }),
326
- /* @__PURE__ */ jsxs("div", { className: "grid gap-3 md:grid-cols-2", children: [
327
- /* @__PURE__ */ jsx(Field, { label: "Afzender-modus", children: /* @__PURE__ */ jsx(
328
- Select,
329
- {
330
- value: forms.mode,
331
- onValueChange: (v) => setS({ ...s, forms: { ...forms, mode: v } }),
332
- options: [
333
- { value: "xicms", label: "XiCMS-domein (aanbevolen)" },
334
- { value: "client", label: "Eigen domein van de klant" }
335
- ]
336
- }
337
- ) }),
338
- /* @__PURE__ */ jsx(Field, { label: "Van (from) e-mail", hint: "Moet een geverifieerd domein zijn.", children: /* @__PURE__ */ jsx(Input, { value: forms.fromEmail ?? "", onChange: (e) => setS({ ...s, forms: { ...forms, fromEmail: e.target.value } }) }) }),
339
- /* @__PURE__ */ jsx(Field, { label: "Van (naam)", children: /* @__PURE__ */ jsx(Input, { value: forms.fromName ?? "", onChange: (e) => setS({ ...s, forms: { ...forms, fromName: e.target.value } }) }) }),
340
- /* @__PURE__ */ jsx(Field, { label: "Notificaties naar", hint: "Meestal het mailadres van de klant.", children: /* @__PURE__ */ jsx(Input, { value: forms.notifyTo ?? "", onChange: (e) => setS({ ...s, forms: { ...forms, notifyTo: e.target.value } }) }) })
341
- ] })
342
- ] }),
343
- /* @__PURE__ */ jsxs(Card, { children: [
344
- /* @__PURE__ */ jsx("h3", { className: "mb-1 text-base font-semibold", children: "Back-up & herstel" }),
345
- /* @__PURE__ */ jsx("p", { className: "mb-3 text-xs text-slate-500", children: "Sla alle content van deze site (pagina's, nieuws, formulieren, instellingen en je foto-galerij) op in \xE9\xE9n bestand. Bewaar dat veilig; je kunt het later weer terugzetten. De foto-bestanden zelf blijven in de cloud staan \u2014 de back-up bevat de teksten, instellingen en verwijzingen." }),
346
- /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
347
- /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: () => void downloadBackup(), disabled: exporting || restoring, children: exporting ? "Bezig\u2026" : "Back-up downloaden" }),
348
- /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: () => restoreInput.current?.click(), disabled: restoring || exporting, children: restoring ? "Terugzetten\u2026" : "Back-up terugzetten\u2026" }),
349
- /* @__PURE__ */ jsx(
350
- "input",
351
- {
352
- ref: restoreInput,
353
- type: "file",
354
- accept: "application/json,.json",
355
- hidden: true,
356
- onChange: (e) => {
357
- const f = e.target.files?.[0];
358
- if (f) void restoreFromFile(f);
359
- e.target.value = "";
360
- }
361
- }
362
- )
365
+ sectie === "backup" && /* @__PURE__ */ jsxs(Fragment, { children: [
366
+ /* @__PURE__ */ jsxs(Card, { children: [
367
+ /* @__PURE__ */ jsx("h3", { className: "mb-1 text-base font-semibold", children: "Back-up & herstel" }),
368
+ /* @__PURE__ */ jsx("p", { className: "mb-3 text-xs text-slate-500", children: "Sla alle content van deze site (pagina's, nieuws, formulieren, instellingen en je foto-galerij) op in \xE9\xE9n bestand. Bewaar dat veilig; je kunt het later weer terugzetten. De foto-bestanden zelf blijven in de cloud staan \u2014 de back-up bevat de teksten, instellingen en verwijzingen." }),
369
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
370
+ /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: () => void downloadBackup(), disabled: exporting || restoring, children: exporting ? "Bezig\u2026" : "Back-up downloaden" }),
371
+ /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: () => restoreInput.current?.click(), disabled: restoring || exporting, children: restoring ? "Terugzetten\u2026" : "Back-up terugzetten\u2026" }),
372
+ /* @__PURE__ */ jsx(
373
+ "input",
374
+ {
375
+ ref: restoreInput,
376
+ type: "file",
377
+ accept: "application/json,.json",
378
+ hidden: true,
379
+ onChange: (e) => {
380
+ const f = e.target.files?.[0];
381
+ if (f) void restoreFromFile(f);
382
+ e.target.value = "";
383
+ }
384
+ }
385
+ )
386
+ ] }),
387
+ /* @__PURE__ */ jsx("p", { className: "mt-2 text-xs text-slate-400", children: "Terugzetten vervangt de huidige content door de back-up. We maken automatisch eerst een back-up van de huidige stand, zodat je nooit iets kwijtraakt." })
388
+ ] }),
389
+ /* @__PURE__ */ jsx(SnapshotsCard, { siteId })
363
390
  ] }),
364
- /* @__PURE__ */ jsx("p", { className: "mt-2 text-xs text-slate-400", children: "Terugzetten vervangt de huidige content door de back-up. We maken automatisch eerst een back-up van de huidige stand, zodat je nooit iets kwijtraakt." })
365
- ] }),
366
- /* @__PURE__ */ jsx(SnapshotsCard, { siteId }),
367
- /* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsx(Button, { onClick: save, children: "Instellingen opslaan" }) })
368
- ] });
391
+ /* @__PURE__ */ jsx("div", { className: "sticky bottom-0 -mx-4 flex justify-end border-t px-4 py-3", style: { borderColor: "var(--color-border)", background: "var(--admin-canvas)" }, children: /* @__PURE__ */ jsx(Button, { onClick: save, children: "Instellingen opslaan" }) })
392
+ ] })
393
+ );
369
394
  }
370
395
  export {
371
396
  SettingsPanel