@xiwebdesign/xicms-core 7.22.3 → 7.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin/AdminApp.js +2 -1
- package/dist/admin/AdminApp.js.map +1 -1
- package/dist/admin/CoreVersion.d.ts +6 -0
- package/dist/admin/CoreVersion.js +42 -1
- package/dist/admin/CoreVersion.js.map +1 -1
- package/dist/admin/SuperadminPanel.js +19 -3
- package/dist/admin/SuperadminPanel.js.map +1 -1
- package/dist/admin/dashboard/DashboardPanel.js +46 -9
- package/dist/admin/dashboard/DashboardPanel.js.map +1 -1
- package/dist/admin/forms/FormsPanel.d.ts +9 -0
- package/dist/admin/forms/FormsPanel.js +102 -16
- package/dist/admin/forms/FormsPanel.js.map +1 -1
- package/dist/admin/forms/gezien.d.ts +3 -0
- package/dist/admin/forms/gezien.js +22 -0
- package/dist/admin/forms/gezien.js.map +1 -0
- package/dist/admin/lib/kleur.d.ts +32 -0
- package/dist/admin/lib/kleur.js +60 -0
- package/dist/admin/lib/kleur.js.map +1 -0
- package/dist/admin/lib/pagesClient.d.ts +32 -0
- package/dist/admin/lib/pagesClient.js +60 -1
- package/dist/admin/lib/pagesClient.js.map +1 -1
- package/dist/admin/lib/seoHulp.d.ts +28 -0
- package/dist/admin/lib/seoHulp.js +33 -0
- package/dist/admin/lib/seoHulp.js.map +1 -0
- package/dist/admin/media/MediaManager.js +133 -51
- package/dist/admin/media/MediaManager.js.map +1 -1
- package/dist/admin/pages/PageEditor.d.ts +14 -0
- package/dist/admin/pages/PageEditor.js +192 -123
- package/dist/admin/pages/PageEditor.js.map +1 -1
- package/dist/admin/pages/PagesPanel.js +88 -40
- package/dist/admin/pages/PagesPanel.js.map +1 -1
- package/dist/admin/settings/SettingsPanel.js +100 -19
- package/dist/admin/settings/SettingsPanel.js.map +1 -1
- package/dist/admin/shop/ProductsTab.js +27 -2
- package/dist/admin/shop/ProductsTab.js.map +1 -1
- package/dist/admin/shop/ShopPanel.js +5 -2
- package/dist/admin/shop/ShopPanel.js.map +1 -1
- package/dist/admin/stats/StatsPanel.js +30 -2
- package/dist/admin/stats/StatsPanel.js.map +1 -1
- package/dist/admin/stats/lib.d.ts +14 -1
- package/dist/admin/stats/lib.js +46 -2
- package/dist/admin/stats/lib.js.map +1 -1
- package/dist/admin/visual/VisualEditor.js +80 -4
- package/dist/admin/visual/VisualEditor.js.map +1 -1
- package/dist/admin.css +1 -1
- package/dist/blocks/builtins/FormBlockRender.js +8 -2
- package/dist/blocks/builtins/FormBlockRender.js.map +1 -1
- package/dist/blocks/builtins/lib/submitForm.d.ts +3 -1
- package/dist/blocks/builtins/lib/submitForm.js +2 -2
- package/dist/blocks/builtins/lib/submitForm.js.map +1 -1
- package/dist/components/EditBar.d.ts +3 -1
- package/dist/components/EditBar.js +30 -4
- package/dist/components/EditBar.js.map +1 -1
- package/dist/components/News.js +8 -4
- package/dist/components/News.js.map +1 -1
- package/dist/components/PageView.d.ts +5 -0
- package/dist/components/PageView.js +24 -2
- package/dist/components/PageView.js.map +1 -1
- package/dist/components/ResponsiveImage.d.ts +1 -1
- package/dist/components/ResponsiveImage.js +3 -1
- package/dist/components/ResponsiveImage.js.map +1 -1
- package/dist/components/site/SiteNav.d.ts +29 -0
- package/dist/components/site/SiteNav.js +82 -0
- package/dist/components/site/SiteNav.js.map +1 -0
- package/dist/content/types.d.ts +8 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +8 -1
- package/dist/index.js.map +1 -1
- package/dist/media/ImageContext.d.ts +1 -6
- package/dist/media/ImageContext.js +6 -1
- package/dist/media/ImageContext.js.map +1 -1
- package/dist/modules/shop/blocks/ProductDetail.js +8 -8
- package/dist/modules/shop/blocks/ProductDetail.js.map +1 -1
- package/dist/modules/shop/blocks/ProductGrid.js +5 -2
- package/dist/modules/shop/blocks/ProductGrid.js.map +1 -1
- package/dist/modules/shop/lib.d.ts +17 -1
- package/dist/modules/shop/lib.js +6 -0
- package/dist/modules/shop/lib.js.map +1 -1
- package/dist/releaseNotes.d.ts +15 -0
- package/dist/releaseNotes.js +28 -0
- package/dist/releaseNotes.js.map +1 -0
- package/dist/schema/settings.d.ts +105 -0
- package/dist/schema/settings.js +45 -5
- package/dist/schema/settings.js.map +1 -1
- package/dist/schema/shop.d.ts +4 -0
- package/dist/schema/shop.js +25 -4
- package/dist/schema/shop.js.map +1 -1
- package/dist/schema/siteConfig.d.ts +9 -0
- package/dist/server/blockData.js +6 -2
- package/dist/server/blockData.js.map +1 -1
- package/dist/server/draft.d.ts +11 -0
- package/dist/server/draft.js +20 -1
- package/dist/server/draft.js.map +1 -1
- package/dist/server/index.d.ts +3 -1
- package/dist/server/index.js +7 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/pages.js +4 -1
- package/dist/server/pages.js.map +1 -1
- package/dist/server/previewToken.d.ts +34 -0
- package/dist/server/previewToken.js +44 -0
- package/dist/server/previewToken.js.map +1 -0
- package/dist/server/redirects.d.ts +15 -0
- package/dist/server/redirects.js +15 -0
- package/dist/server/redirects.js.map +1 -0
- package/dist/styles.css +110 -0
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {
|
|
3
|
-
import { useEffect, useState } from "react";
|
|
4
|
-
import { Button, Card, Field, Input
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useMemo, useState } from "react";
|
|
4
|
+
import { Button, Card, Field, Input } from "../ui/primitives.js";
|
|
5
|
+
import { Select } from "../ui/select.js";
|
|
5
6
|
import { Dialog, DialogTrigger, DialogContent, DialogClose } from "../ui/dialog.js";
|
|
6
7
|
import { useToast } from "../ui/toast.js";
|
|
7
8
|
import { useConfirm } from "../ui/ConfirmDialog.js";
|
|
8
|
-
import { subscribePages, createPage, deletePage } from "../lib/pagesClient.js";
|
|
9
|
-
import {
|
|
9
|
+
import { subscribePages, createPage, deletePage, restorePage, paginaToestand } from "../lib/pagesClient.js";
|
|
10
|
+
import { seoSignaal } from "../lib/seoHulp.js";
|
|
11
|
+
import { builtinPatterns, instantiatePattern } from "../../patterns/index.js";
|
|
12
|
+
import { PageEditor, ToestandBadge } from "./PageEditor.js";
|
|
10
13
|
function slugify(s) {
|
|
11
14
|
return s.toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
|
|
12
15
|
}
|
|
@@ -24,6 +27,7 @@ function isFunctioneel(p) {
|
|
|
24
27
|
return (p.blocks ?? []).some((b) => FUNCTIONELE_TYPES.has(String(b._type)));
|
|
25
28
|
}
|
|
26
29
|
function PaginaKnop({ p, selected, onClick }) {
|
|
30
|
+
const signaal = seoSignaal(p);
|
|
27
31
|
return /* @__PURE__ */ jsxs(
|
|
28
32
|
"button",
|
|
29
33
|
{
|
|
@@ -33,24 +37,35 @@ function PaginaKnop({ p, selected, onClick }) {
|
|
|
33
37
|
children: [
|
|
34
38
|
/* @__PURE__ */ jsxs("span", { className: "min-w-0", children: [
|
|
35
39
|
/* @__PURE__ */ jsx("span", { className: "block truncate", children: p.title }),
|
|
36
|
-
/* @__PURE__ */
|
|
40
|
+
/* @__PURE__ */ jsxs("span", { className: `block truncate text-xs ${selected ? "text-brand-700/70" : "text-slate-400"}`, children: [
|
|
41
|
+
p.path,
|
|
42
|
+
signaal && !p.trashed && /* @__PURE__ */ jsxs("span", { className: "text-amber-600", children: [
|
|
43
|
+
" \xB7 ",
|
|
44
|
+
signaal
|
|
45
|
+
] })
|
|
46
|
+
] })
|
|
37
47
|
] }),
|
|
38
|
-
|
|
48
|
+
/* @__PURE__ */ jsx(ToestandBadge, { p })
|
|
39
49
|
]
|
|
40
50
|
}
|
|
41
51
|
);
|
|
42
52
|
}
|
|
53
|
+
const SJABLONEN = builtinPatterns.filter((p) => p.id.startsWith("page-"));
|
|
43
54
|
function PagesPanel({ siteId }) {
|
|
44
55
|
const { toast } = useToast();
|
|
45
56
|
const confirm = useConfirm();
|
|
46
57
|
const [pages, setPages] = useState([]);
|
|
47
58
|
const [selected, setSelected] = useState(null);
|
|
48
59
|
const [newTitle, setNewTitle] = useState("");
|
|
60
|
+
const [sjabloon, setSjabloon] = useState("leeg");
|
|
49
61
|
useEffect(() => subscribePages(siteId, setPages), [siteId]);
|
|
50
|
-
const
|
|
51
|
-
const
|
|
62
|
+
const actief = useMemo(() => pages.filter((p) => !p.trashed), [pages]);
|
|
63
|
+
const prullenbak = useMemo(() => pages.filter((p) => p.trashed), [pages]);
|
|
64
|
+
const gewoon = actief.filter((p) => !isFunctioneel(p));
|
|
65
|
+
const functioneel = actief.filter(isFunctioneel);
|
|
66
|
+
const nietLive = actief.filter((p) => paginaToestand(p) === "gewijzigd").length;
|
|
52
67
|
useEffect(() => {
|
|
53
|
-
if (!selected &&
|
|
68
|
+
if (!selected && actief.length) setSelected((gewoon[0] ?? actief[0]).id);
|
|
54
69
|
}, [pages, selected]);
|
|
55
70
|
const current = pages.find((p) => p.id === selected) ?? null;
|
|
56
71
|
async function create() {
|
|
@@ -58,15 +73,50 @@ function PagesPanel({ siteId }) {
|
|
|
58
73
|
if (!title) return;
|
|
59
74
|
const slug = slugify(title) || "pagina";
|
|
60
75
|
const path = slug === "home" ? "/" : `/${slug}`;
|
|
76
|
+
const patroon = SJABLONEN.find((p) => p.id === sjabloon);
|
|
61
77
|
try {
|
|
62
|
-
const id = await createPage(siteId, {
|
|
78
|
+
const id = await createPage(siteId, {
|
|
79
|
+
title,
|
|
80
|
+
slug,
|
|
81
|
+
path,
|
|
82
|
+
...patroon ? { blocks: instantiatePattern(patroon) } : {}
|
|
83
|
+
});
|
|
63
84
|
setNewTitle("");
|
|
85
|
+
setSjabloon("leeg");
|
|
64
86
|
setSelected(id);
|
|
65
|
-
toast({
|
|
87
|
+
toast({
|
|
88
|
+
title: "Pagina aangemaakt",
|
|
89
|
+
description: patroon ? `${path} \u2014 met voorbeeldtekst; bewerk op de site en publiceer als het goed is.` : path
|
|
90
|
+
});
|
|
66
91
|
} catch (e) {
|
|
67
92
|
toast({ title: "Pagina aanmaken mislukt", description: e.message, tone: "error" });
|
|
68
93
|
}
|
|
69
94
|
}
|
|
95
|
+
async function herstel(p) {
|
|
96
|
+
try {
|
|
97
|
+
await restorePage(siteId, p.id);
|
|
98
|
+
setSelected(p.id);
|
|
99
|
+
toast({ title: "Hersteld als concept", description: "Publiceer om hem weer live te zetten.", tone: "success" });
|
|
100
|
+
} catch (e) {
|
|
101
|
+
toast({ title: "Herstellen mislukt", description: e.message, tone: "error" });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
async function definitiefWeg(p) {
|
|
105
|
+
const ok = await confirm({
|
|
106
|
+
title: `"${p.title}" definitief verwijderen?`,
|
|
107
|
+
description: "Dit kan niet ongedaan worden gemaakt \u2014 ook de versiegeschiedenis verdwijnt.",
|
|
108
|
+
confirmLabel: "Definitief verwijderen",
|
|
109
|
+
tone: "danger"
|
|
110
|
+
});
|
|
111
|
+
if (!ok) return;
|
|
112
|
+
try {
|
|
113
|
+
await deletePage(siteId, p.id);
|
|
114
|
+
if (selected === p.id) setSelected(null);
|
|
115
|
+
toast({ title: "Pagina verwijderd" });
|
|
116
|
+
} catch (e) {
|
|
117
|
+
toast({ title: "Verwijderen mislukt", description: e.message, tone: "error" });
|
|
118
|
+
}
|
|
119
|
+
}
|
|
70
120
|
return /* @__PURE__ */ jsxs("div", { className: "grid gap-4 md:grid-cols-[260px_1fr]", children: [
|
|
71
121
|
/* @__PURE__ */ jsxs(Card, { className: "h-fit", children: [
|
|
72
122
|
/* @__PURE__ */ jsxs("div", { className: "mb-3 flex items-center justify-between gap-2", children: [
|
|
@@ -75,6 +125,14 @@ function PagesPanel({ siteId }) {
|
|
|
75
125
|
/* @__PURE__ */ jsx(DialogTrigger, { asChild: true, children: /* @__PURE__ */ jsx(Button, { size: "sm", children: "+ Nieuw" }) }),
|
|
76
126
|
/* @__PURE__ */ jsx(DialogContent, { title: "Nieuwe pagina", children: /* @__PURE__ */ jsxs("div", { className: "grid gap-3", children: [
|
|
77
127
|
/* @__PURE__ */ jsx(Field, { label: "Titel", children: /* @__PURE__ */ jsx(Input, { value: newTitle, onChange: (e) => setNewTitle(e.target.value), placeholder: "bv. Over ons" }) }),
|
|
128
|
+
/* @__PURE__ */ jsx(Field, { label: "Beginnen met", hint: "Een sjabloon zet alvast passende blokken met voorbeeldtekst neer \u2014 alles is daarna aan te passen.", children: /* @__PURE__ */ jsx(
|
|
129
|
+
Select,
|
|
130
|
+
{
|
|
131
|
+
value: sjabloon,
|
|
132
|
+
onValueChange: setSjabloon,
|
|
133
|
+
options: [{ value: "leeg", label: "Lege pagina" }, ...SJABLONEN.map((p) => ({ value: p.id, label: p.label }))]
|
|
134
|
+
}
|
|
135
|
+
) }),
|
|
78
136
|
/* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2", children: [
|
|
79
137
|
/* @__PURE__ */ jsx(DialogClose, { asChild: true, children: /* @__PURE__ */ jsx(Button, { variant: "secondary", children: "Annuleren" }) }),
|
|
80
138
|
/* @__PURE__ */ jsx(DialogClose, { asChild: true, children: /* @__PURE__ */ jsx(Button, { onClick: create, children: "Aanmaken" }) })
|
|
@@ -82,41 +140,31 @@ function PagesPanel({ siteId }) {
|
|
|
82
140
|
] }) })
|
|
83
141
|
] })
|
|
84
142
|
] }),
|
|
143
|
+
nietLive > 0 && /* @__PURE__ */ jsxs("p", { className: "mb-2 rounded-lg bg-amber-50 px-3 py-2 text-xs text-amber-900", children: [
|
|
144
|
+
nietLive === 1 ? "1 pagina heeft" : `${nietLive} pagina's hebben`,
|
|
145
|
+
" wijzigingen die nog niet live staan."
|
|
146
|
+
] }),
|
|
85
147
|
/* @__PURE__ */ jsx("ul", { className: "grid gap-1", children: gewoon.map((p) => /* @__PURE__ */ jsx("li", { className: "min-w-0", children: /* @__PURE__ */ jsx(PaginaKnop, { p, selected: p.id === selected, onClick: () => setSelected(p.id) }) }, p.id)) }),
|
|
86
148
|
functioneel.length > 0 && /* @__PURE__ */ jsxs("div", { className: "mt-4 border-t border-slate-200 pt-3", children: [
|
|
87
149
|
/* @__PURE__ */ jsx("p", { className: "mb-1 px-3 text-xs font-medium tracking-wide text-slate-500 uppercase", children: "Winkel & functioneel" }),
|
|
88
150
|
/* @__PURE__ */ jsx("p", { className: "mb-2 px-3 text-xs text-slate-400", children: "Deze pagina's zijn het omhulsel van een functie. Producten en bestellingen beheer je onder Webwinkel; hier pas je alleen de tekst eromheen aan." }),
|
|
89
151
|
/* @__PURE__ */ jsx("ul", { className: "grid gap-1", children: functioneel.map((p) => /* @__PURE__ */ jsx("li", { className: "min-w-0", children: /* @__PURE__ */ jsx(PaginaKnop, { p, selected: p.id === selected, onClick: () => setSelected(p.id) }) }, p.id)) })
|
|
152
|
+
] }),
|
|
153
|
+
prullenbak.length > 0 && /* @__PURE__ */ jsxs("div", { className: "mt-4 border-t border-slate-200 pt-3", children: [
|
|
154
|
+
/* @__PURE__ */ jsx("p", { className: "mb-1 px-3 text-xs font-medium tracking-wide text-slate-500 uppercase", children: "Prullenbak" }),
|
|
155
|
+
/* @__PURE__ */ jsx("p", { className: "mb-2 px-3 text-xs text-slate-400", children: "Wordt na 30 dagen automatisch geleegd." }),
|
|
156
|
+
/* @__PURE__ */ jsx("ul", { className: "grid gap-1", children: prullenbak.map((p) => /* @__PURE__ */ jsxs("li", { className: "min-w-0", children: [
|
|
157
|
+
/* @__PURE__ */ jsx(PaginaKnop, { p, selected: p.id === selected, onClick: () => setSelected(p.id) }),
|
|
158
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-1 px-3 pb-1", children: [
|
|
159
|
+
/* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onClick: () => void herstel(p), children: "Herstellen" }),
|
|
160
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: "rounded px-2 py-1 text-xs text-red-600 hover:underline", onClick: () => void definitiefWeg(p), children: "Definitief verwijderen" })
|
|
161
|
+
] })
|
|
162
|
+
] }, p.id)) })
|
|
90
163
|
] })
|
|
91
164
|
] }),
|
|
92
|
-
/* @__PURE__ */ jsx("div", { children: current ? /* @__PURE__ */ jsxs(
|
|
93
|
-
/* @__PURE__ */ jsx(
|
|
94
|
-
/* @__PURE__ */ jsx("
|
|
95
|
-
"button",
|
|
96
|
-
{
|
|
97
|
-
type: "button",
|
|
98
|
-
className: "rounded px-2 py-1 text-sm text-red-600 hover:underline focus-visible:ring-2 focus-visible:ring-brand-500 focus-visible:outline-none",
|
|
99
|
-
onClick: async () => {
|
|
100
|
-
const ok = await confirm({
|
|
101
|
-
title: `Pagina "${current.title}" verwijderen?`,
|
|
102
|
-
description: "De pagina en de bijbehorende werkversie worden verwijderd. Dit kan niet ongedaan worden gemaakt. Van de live site verdwijnt hij zodra die opnieuw is gebouwd \u2014 meestal binnen enkele minuten.",
|
|
103
|
-
confirmLabel: "Verwijderen",
|
|
104
|
-
tone: "danger"
|
|
105
|
-
});
|
|
106
|
-
if (!ok) return;
|
|
107
|
-
await deletePage(siteId, current.id);
|
|
108
|
-
setSelected(null);
|
|
109
|
-
toast({
|
|
110
|
-
title: "Pagina verwijderd",
|
|
111
|
-
description: "De live site laat de pagina los zodra hij opnieuw is gebouwd (meestal enkele minuten)."
|
|
112
|
-
});
|
|
113
|
-
},
|
|
114
|
-
children: "Pagina verwijderen"
|
|
115
|
-
}
|
|
116
|
-
) })
|
|
117
|
-
] }) : /* @__PURE__ */ jsx(Card, { className: "grid place-items-center py-16 text-center", children: /* @__PURE__ */ jsxs("div", { className: "max-w-sm", children: [
|
|
118
|
-
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-slate-700", children: pages.length === 0 ? "Nog geen pagina's" : "Geen pagina geselecteerd" }),
|
|
119
|
-
/* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-slate-500", children: pages.length === 0 ? "Maak je eerste pagina met + Nieuw. Wat je maakt blijft een concept tot je publiceert." : "Kies links een pagina om de tekst en secties aan te passen." })
|
|
165
|
+
/* @__PURE__ */ jsx("div", { children: current ? /* @__PURE__ */ jsx(PageEditor, { siteId, page: current, allPages: pages }) : /* @__PURE__ */ jsx(Card, { className: "grid place-items-center py-16 text-center", children: /* @__PURE__ */ jsxs("div", { className: "max-w-sm", children: [
|
|
166
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-slate-700", children: actief.length === 0 ? "Nog geen pagina's" : "Geen pagina geselecteerd" }),
|
|
167
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-slate-500", children: actief.length === 0 ? "Maak je eerste pagina met + Nieuw. Wat je maakt blijft een concept tot je publiceert." : "Kies links een pagina om de instellingen aan te passen, of open hem op de site om de tekst te bewerken." })
|
|
120
168
|
] }) }) })
|
|
121
169
|
] });
|
|
122
170
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/pages/PagesPanel.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { Button, Card, Field, Input, Badge } from \"../ui/primitives\";\nimport { Dialog, DialogTrigger, DialogContent, DialogClose } from \"../ui/dialog\";\nimport { useToast } from \"../ui/toast\";\nimport { useConfirm } from \"../ui/ConfirmDialog\";\nimport { subscribePages, createPage, deletePage, type DraftPage } from \"../lib/pagesClient\";\nimport { PageEditor } from \"./PageEditor\";\n\nfunction slugify(s: string): string {\n return s\n .toLowerCase()\n .normalize(\"NFD\")\n .replace(/[̀-ͯ]/g, \"\")\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/(^-|-$)/g, \"\");\n}\n\n/**\n * Bloktypes die een pagina FUNCTIONEEL maken (7.16): een afrekenpagina is \"een pagina met een\n * checkout-blok\", en die hoort in de lijst niet tussen \"Over ons\" te staan — de inhoud (producten,\n * bestellingen) beheer je onder Webwinkel, hier staat alleen het omhulsel. Er is bewust géén\n * pagina-type-veld in het schema (dat zou een tweede waarheid zijn naast de blokken zelf); we lezen\n * het af aan wat er op de pagina staat.\n */\nconst FUNCTIONELE_TYPES = new Set([\n \"shopCategories\",\n \"productGrid\",\n \"productDetail\",\n \"cart\",\n \"checkout\",\n \"donation\",\n \"orderStatus\",\n]);\nfunction isFunctioneel(p: DraftPage): boolean {\n // De homepage hoort ALTIJD bij de gewone pagina's: op een winkel-site staat er vaak een\n // productGrid op de home, maar de home is nooit alleen \"het omhulsel van een functie\" —\n // het is de voordeur van de site, en die moet bovenaan staan waar de klant hem zoekt.\n if (p.path === \"/\") return false;\n return (p.blocks ?? []).some((b) => FUNCTIONELE_TYPES.has(String((b as { _type?: unknown })._type)));\n}\n\n/**\n * Eén rij in de paginalijst. Titel + pad onder elkaar: de kolom is 260px en een SEO-titel als\n * \"Christelijk Kraamcadeau — christelijke geschenken en kraamcadeaus\" is dan onleesbaar afgekapt\n * zónder dat je kunt zien wélke pagina het is. Het pad is kort, uniek en herkenbaar (\"/\", \"/over-\n * ons\") — precies het houvast dat de afgekapte titel mist. `title` levert de volle titel bij hover.\n */\nfunction PaginaKnop({ p, selected, onClick }: { p: DraftPage; selected: boolean; onClick: () => void }) {\n return (\n <button\n onClick={onClick}\n title={`${p.title} — ${p.path}`}\n className={`flex w-full items-center justify-between gap-2 rounded-lg px-3 py-2 text-left text-sm ${\n selected ? \"bg-brand-50 text-brand-700\" : \"hover:bg-slate-100\"\n }`}\n >\n <span className=\"min-w-0\">\n <span className=\"block truncate\">{p.title}</span>\n <span className={`block truncate text-xs ${selected ? \"text-brand-700/70\" : \"text-slate-400\"}`}>{p.path}</span>\n </span>\n {p.published ? <Badge tone=\"green\">live</Badge> : <Badge tone=\"amber\">concept</Badge>}\n </button>\n );\n}\n\nexport function PagesPanel({ siteId }: { siteId: string }) {\n const { toast } = useToast();\n const confirm = useConfirm();\n const [pages, setPages] = useState<DraftPage[]>([]);\n const [selected, setSelected] = useState<string | null>(null);\n const [newTitle, setNewTitle] = useState(\"\");\n\n useEffect(() => subscribePages(siteId, setPages), [siteId]);\n\n const gewoon = pages.filter((p) => !isFunctioneel(p));\n const functioneel = pages.filter(isFunctioneel);\n useEffect(() => {\n // Standaard de eerste GEWONE pagina: /beheer → Pagina's gaat over teksten, niet over de winkel.\n if (!selected && pages.length) setSelected((gewoon[0] ?? pages[0]).id);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [pages, selected]);\n\n const current = pages.find((p) => p.id === selected) ?? null;\n\n async function create() {\n const title = newTitle.trim();\n if (!title) return;\n const slug = slugify(title) || \"pagina\";\n const path = slug === \"home\" ? \"/\" : `/${slug}`;\n try {\n const id = await createPage(siteId, { title, slug, path });\n setNewTitle(\"\");\n setSelected(id);\n toast({ title: \"Pagina aangemaakt\", description: path });\n } catch (e) {\n // De dialoog sluit sowieso (DialogClose) — zonder deze toast dacht de klant dat de pagina\n // bestond terwijl de write (rules/offline) faalde.\n toast({ title: \"Pagina aanmaken mislukt\", description: (e as Error).message, tone: \"error\" });\n }\n }\n\n return (\n <div className=\"grid gap-4 md:grid-cols-[260px_1fr]\">\n <Card className=\"h-fit\">\n <div className=\"mb-3 flex items-center justify-between gap-2\">\n <span className=\"text-xs font-medium tracking-wide text-slate-500 uppercase\">Overzicht</span>\n <Dialog>\n <DialogTrigger asChild>\n <Button size=\"sm\">+ Nieuw</Button>\n </DialogTrigger>\n <DialogContent title=\"Nieuwe pagina\">\n <div className=\"grid gap-3\">\n <Field label=\"Titel\">\n <Input value={newTitle} onChange={(e) => setNewTitle(e.target.value)} placeholder=\"bv. Over ons\" />\n </Field>\n <div className=\"flex justify-end gap-2\">\n <DialogClose asChild>\n <Button variant=\"secondary\">Annuleren</Button>\n </DialogClose>\n <DialogClose asChild>\n <Button onClick={create}>Aanmaken</Button>\n </DialogClose>\n </div>\n </div>\n </DialogContent>\n </Dialog>\n </div>\n <ul className=\"grid gap-1\">\n {gewoon.map((p) => (\n <li key={p.id} className=\"min-w-0\">\n <PaginaKnop p={p} selected={p.id === selected} onClick={() => setSelected(p.id)} />\n </li>\n ))}\n {/* Geen lege-lijst-tekst hier: de lege staat rechts zegt hetzelfde én noemt de volgende\n stap. Twee keer \"Nog geen pagina's\" naast elkaar leest als een fout. */}\n </ul>\n {functioneel.length > 0 && (\n <div className=\"mt-4 border-t border-slate-200 pt-3\">\n <p className=\"mb-1 px-3 text-xs font-medium tracking-wide text-slate-500 uppercase\">Winkel & functioneel</p>\n <p className=\"mb-2 px-3 text-xs text-slate-400\">\n Deze pagina's zijn het omhulsel van een functie. Producten en bestellingen beheer je\n onder Webwinkel; hier pas je alleen de tekst eromheen aan.\n </p>\n <ul className=\"grid gap-1\">\n {functioneel.map((p) => (\n <li key={p.id} className=\"min-w-0\">\n <PaginaKnop p={p} selected={p.id === selected} onClick={() => setSelected(p.id)} />\n </li>\n ))}\n </ul>\n </div>\n )}\n </Card>\n\n <div>\n {current ? (\n <>\n <PageEditor siteId={siteId} page={current} allPages={pages} />\n {/* Verwijderen staat ONDER de editor en draagt geen rode vlakknop meer. Het was het\n opvallendste element op het scherm — helderrood, bovenaan, bóven \"Publiceren\" —\n waarmee de onomkeerbare actie meer aandacht kreeg dan de actie waarvoor je komt.\n Een tekstknop in de foutkleur is duidelijk genoeg; de bevestiging vangt de rest. */}\n <div className=\"mt-4 flex justify-end border-t border-slate-200 pt-4\">\n <button\n type=\"button\"\n className=\"rounded px-2 py-1 text-sm text-red-600 hover:underline focus-visible:ring-2 focus-visible:ring-brand-500 focus-visible:outline-none\"\n onClick={async () => {\n const ok = await confirm({\n title: `Pagina \"${current.title}\" verwijderen?`,\n description:\n \"De pagina en de bijbehorende werkversie worden verwijderd. Dit kan niet ongedaan worden gemaakt. \" +\n \"Van de live site verdwijnt hij zodra die opnieuw is gebouwd — meestal binnen enkele minuten.\",\n confirmLabel: \"Verwijderen\",\n tone: \"danger\",\n });\n if (!ok) return;\n await deletePage(siteId, current.id);\n setSelected(null);\n // Eerlijk over de vertraging: de pagina is wég uit het beheer, maar de live site\n // laat hem pas los na een rebuild. Beloven dat hij \"verdwenen\" is terwijl de URL\n // nog werkt, kost meer vertrouwen dan het kost om het te zeggen.\n toast({\n title: \"Pagina verwijderd\",\n description: \"De live site laat de pagina los zodra hij opnieuw is gebouwd (meestal enkele minuten).\",\n });\n }}\n >\n Pagina verwijderen\n </button>\n </div>\n </>\n ) : (\n <Card className=\"grid place-items-center py-16 text-center\">\n <div className=\"max-w-sm\">\n {/* Een lege lijst is een uitnodiging, geen mededeling: zeg wat de volgende stap is. */}\n <p className=\"text-sm font-medium text-slate-700\">\n {pages.length === 0 ? \"Nog geen pagina's\" : \"Geen pagina geselecteerd\"}\n </p>\n <p className=\"mt-1 text-sm text-slate-500\">\n {pages.length === 0\n ? \"Maak je eerste pagina met + Nieuw. Wat je maakt blijft een concept tot je publiceert.\"\n : \"Kies links een pagina om de tekst en secties aan te passen.\"}\n </p>\n </div>\n </Card>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/pages/PagesPanel.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useMemo, useState } from \"react\";\nimport { Button, Card, Field, Input } from \"../ui/primitives\";\nimport { Select } from \"../ui/select\";\nimport { Dialog, DialogTrigger, DialogContent, DialogClose } from \"../ui/dialog\";\nimport { useToast } from \"../ui/toast\";\nimport { useConfirm } from \"../ui/ConfirmDialog\";\nimport { subscribePages, createPage, deletePage, restorePage, paginaToestand, type DraftPage } from \"../lib/pagesClient\";\nimport { seoSignaal } from \"../lib/seoHulp\";\nimport { builtinPatterns, instantiatePattern } from \"../../patterns\";\nimport { PageEditor, ToestandBadge } from \"./PageEditor\";\n\nfunction slugify(s: string): string {\n return s\n .toLowerCase()\n .normalize(\"NFD\")\n .replace(/[̀-ͯ]/g, \"\")\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/(^-|-$)/g, \"\");\n}\n\n/**\n * Bloktypes die een pagina FUNCTIONEEL maken (7.16): een afrekenpagina is \"een pagina met een\n * checkout-blok\", en die hoort in de lijst niet tussen \"Over ons\" te staan — de inhoud (producten,\n * bestellingen) beheer je onder Webwinkel, hier staat alleen het omhulsel. Er is bewust géén\n * pagina-type-veld in het schema (dat zou een tweede waarheid zijn naast de blokken zelf); we lezen\n * het af aan wat er op de pagina staat.\n */\nconst FUNCTIONELE_TYPES = new Set([\n \"shopCategories\",\n \"productGrid\",\n \"productDetail\",\n \"cart\",\n \"checkout\",\n \"donation\",\n \"orderStatus\",\n]);\nfunction isFunctioneel(p: DraftPage): boolean {\n // De homepage hoort ALTIJD bij de gewone pagina's: op een winkel-site staat er vaak een\n // productGrid op de home, maar de home is nooit alleen \"het omhulsel van een functie\" —\n // het is de voordeur van de site, en die moet bovenaan staan waar de klant hem zoekt.\n if (p.path === \"/\") return false;\n return (p.blocks ?? []).some((b) => FUNCTIONELE_TYPES.has(String((b as { _type?: unknown })._type)));\n}\n\n/**\n * Eén rij in de paginalijst. Titel + pad onder elkaar: de kolom is 260px en een SEO-titel als\n * \"Christelijk Kraamcadeau — christelijke geschenken en kraamcadeaus\" is dan onleesbaar afgekapt\n * zónder dat je kunt zien wélke pagina het is. Het pad is kort, uniek en herkenbaar (\"/\", \"/over-\n * ons\") — precies het houvast dat de afgekapte titel mist. `title` levert de volle titel bij hover.\n *\n * 7.24: de badge zegt nu ook \"gewijzigd, nog niet live\" — de toestand waar klanten het vaakst\n * over belden (\"ik heb het aangepast maar het staat er niet\"), en die tot nu toe nergens zichtbaar\n * was buiten de editor. Plus een stil SEO-signaal (\"mist omschrijving\").\n */\nfunction PaginaKnop({ p, selected, onClick }: { p: DraftPage; selected: boolean; onClick: () => void }) {\n const signaal = seoSignaal(p);\n return (\n <button\n onClick={onClick}\n title={`${p.title} — ${p.path}`}\n className={`flex w-full items-center justify-between gap-2 rounded-lg px-3 py-2 text-left text-sm ${\n selected ? \"bg-brand-50 text-brand-700\" : \"hover:bg-slate-100\"\n }`}\n >\n <span className=\"min-w-0\">\n <span className=\"block truncate\">{p.title}</span>\n <span className={`block truncate text-xs ${selected ? \"text-brand-700/70\" : \"text-slate-400\"}`}>\n {p.path}\n {signaal && !p.trashed && <span className=\"text-amber-600\"> · {signaal}</span>}\n </span>\n </span>\n <ToestandBadge p={p} />\n </button>\n );\n}\n\n/** Paginasjablonen voor \"+ Nieuw\": de `page-*`-patronen, plus \"leeg\". */\nconst SJABLONEN = builtinPatterns.filter((p) => p.id.startsWith(\"page-\"));\n\nexport function PagesPanel({ siteId }: { siteId: string }) {\n const { toast } = useToast();\n const confirm = useConfirm();\n const [pages, setPages] = useState<DraftPage[]>([]);\n const [selected, setSelected] = useState<string | null>(null);\n const [newTitle, setNewTitle] = useState(\"\");\n const [sjabloon, setSjabloon] = useState(\"leeg\");\n\n useEffect(() => subscribePages(siteId, setPages), [siteId]);\n\n const actief = useMemo(() => pages.filter((p) => !p.trashed), [pages]);\n const prullenbak = useMemo(() => pages.filter((p) => p.trashed), [pages]);\n const gewoon = actief.filter((p) => !isFunctioneel(p));\n const functioneel = actief.filter(isFunctioneel);\n const nietLive = actief.filter((p) => paginaToestand(p) === \"gewijzigd\").length;\n\n useEffect(() => {\n // Standaard de eerste GEWONE pagina: /beheer → Pagina's gaat over teksten, niet over de winkel.\n if (!selected && actief.length) setSelected((gewoon[0] ?? actief[0]).id);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [pages, selected]);\n\n const current = pages.find((p) => p.id === selected) ?? null;\n\n async function create() {\n const title = newTitle.trim();\n if (!title) return;\n const slug = slugify(title) || \"pagina\";\n const path = slug === \"home\" ? \"/\" : `/${slug}`;\n const patroon = SJABLONEN.find((p) => p.id === sjabloon);\n try {\n const id = await createPage(siteId, {\n title,\n slug,\n path,\n ...(patroon ? { blocks: instantiatePattern(patroon) as DraftPage[\"blocks\"] } : {}),\n });\n setNewTitle(\"\");\n setSjabloon(\"leeg\");\n setSelected(id);\n toast({\n title: \"Pagina aangemaakt\",\n description: patroon ? `${path} — met voorbeeldtekst; bewerk op de site en publiceer als het goed is.` : path,\n });\n } catch (e) {\n // De dialoog sluit sowieso (DialogClose) — zonder deze toast dacht de klant dat de pagina\n // bestond terwijl de write (rules/offline) faalde.\n toast({ title: \"Pagina aanmaken mislukt\", description: (e as Error).message, tone: \"error\" });\n }\n }\n\n async function herstel(p: DraftPage) {\n try {\n await restorePage(siteId, p.id);\n setSelected(p.id);\n toast({ title: \"Hersteld als concept\", description: \"Publiceer om hem weer live te zetten.\", tone: \"success\" });\n } catch (e) {\n toast({ title: \"Herstellen mislukt\", description: (e as Error).message, tone: \"error\" });\n }\n }\n\n async function definitiefWeg(p: DraftPage) {\n const ok = await confirm({\n title: `\"${p.title}\" definitief verwijderen?`,\n description: \"Dit kan niet ongedaan worden gemaakt — ook de versiegeschiedenis verdwijnt.\",\n confirmLabel: \"Definitief verwijderen\",\n tone: \"danger\",\n });\n if (!ok) return;\n try {\n await deletePage(siteId, p.id);\n if (selected === p.id) setSelected(null);\n toast({ title: \"Pagina verwijderd\" });\n } catch (e) {\n toast({ title: \"Verwijderen mislukt\", description: (e as Error).message, tone: \"error\" });\n }\n }\n\n return (\n <div className=\"grid gap-4 md:grid-cols-[260px_1fr]\">\n <Card className=\"h-fit\">\n <div className=\"mb-3 flex items-center justify-between gap-2\">\n <span className=\"text-xs font-medium tracking-wide text-slate-500 uppercase\">Overzicht</span>\n <Dialog>\n <DialogTrigger asChild>\n <Button size=\"sm\">+ Nieuw</Button>\n </DialogTrigger>\n <DialogContent title=\"Nieuwe pagina\">\n <div className=\"grid gap-3\">\n <Field label=\"Titel\">\n <Input value={newTitle} onChange={(e) => setNewTitle(e.target.value)} placeholder=\"bv. Over ons\" />\n </Field>\n <Field label=\"Beginnen met\" hint=\"Een sjabloon zet alvast passende blokken met voorbeeldtekst neer — alles is daarna aan te passen.\">\n <Select\n value={sjabloon}\n onValueChange={setSjabloon}\n options={[{ value: \"leeg\", label: \"Lege pagina\" }, ...SJABLONEN.map((p) => ({ value: p.id, label: p.label }))]}\n />\n </Field>\n <div className=\"flex justify-end gap-2\">\n <DialogClose asChild>\n <Button variant=\"secondary\">Annuleren</Button>\n </DialogClose>\n <DialogClose asChild>\n <Button onClick={create}>Aanmaken</Button>\n </DialogClose>\n </div>\n </div>\n </DialogContent>\n </Dialog>\n </div>\n {nietLive > 0 && (\n <p className=\"mb-2 rounded-lg bg-amber-50 px-3 py-2 text-xs text-amber-900\">\n {nietLive === 1 ? \"1 pagina heeft\" : `${nietLive} pagina's hebben`} wijzigingen die nog niet live staan.\n </p>\n )}\n <ul className=\"grid gap-1\">\n {gewoon.map((p) => (\n <li key={p.id} className=\"min-w-0\">\n <PaginaKnop p={p} selected={p.id === selected} onClick={() => setSelected(p.id)} />\n </li>\n ))}\n {/* Geen lege-lijst-tekst hier: de lege staat rechts zegt hetzelfde én noemt de volgende\n stap. Twee keer \"Nog geen pagina's\" naast elkaar leest als een fout. */}\n </ul>\n {functioneel.length > 0 && (\n <div className=\"mt-4 border-t border-slate-200 pt-3\">\n <p className=\"mb-1 px-3 text-xs font-medium tracking-wide text-slate-500 uppercase\">Winkel & functioneel</p>\n <p className=\"mb-2 px-3 text-xs text-slate-400\">\n Deze pagina's zijn het omhulsel van een functie. Producten en bestellingen beheer je\n onder Webwinkel; hier pas je alleen de tekst eromheen aan.\n </p>\n <ul className=\"grid gap-1\">\n {functioneel.map((p) => (\n <li key={p.id} className=\"min-w-0\">\n <PaginaKnop p={p} selected={p.id === selected} onClick={() => setSelected(p.id)} />\n </li>\n ))}\n </ul>\n </div>\n )}\n {prullenbak.length > 0 && (\n <div className=\"mt-4 border-t border-slate-200 pt-3\">\n <p className=\"mb-1 px-3 text-xs font-medium tracking-wide text-slate-500 uppercase\">Prullenbak</p>\n <p className=\"mb-2 px-3 text-xs text-slate-400\">Wordt na 30 dagen automatisch geleegd.</p>\n <ul className=\"grid gap-1\">\n {prullenbak.map((p) => (\n <li key={p.id} className=\"min-w-0\">\n <PaginaKnop p={p} selected={p.id === selected} onClick={() => setSelected(p.id)} />\n <div className=\"flex gap-1 px-3 pb-1\">\n <Button variant=\"ghost\" size=\"sm\" onClick={() => void herstel(p)}>Herstellen</Button>\n <button type=\"button\" className=\"rounded px-2 py-1 text-xs text-red-600 hover:underline\" onClick={() => void definitiefWeg(p)}>\n Definitief verwijderen\n </button>\n </div>\n </li>\n ))}\n </ul>\n </div>\n )}\n </Card>\n\n <div>\n {current ? (\n <PageEditor siteId={siteId} page={current} allPages={pages} />\n ) : (\n <Card className=\"grid place-items-center py-16 text-center\">\n <div className=\"max-w-sm\">\n {/* Een lege lijst is een uitnodiging, geen mededeling: zeg wat de volgende stap is. */}\n <p className=\"text-sm font-medium text-slate-700\">\n {actief.length === 0 ? \"Nog geen pagina's\" : \"Geen pagina geselecteerd\"}\n </p>\n <p className=\"mt-1 text-sm text-slate-500\">\n {actief.length === 0\n ? \"Maak je eerste pagina met + Nieuw. Wat je maakt blijft een concept tot je publiceert.\"\n : \"Kies links een pagina om de instellingen aan te passen, of open hem op de site om de tekst te bewerken.\"}\n </p>\n </div>\n </Card>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -17,11 +17,51 @@ import { useConfirm } from "../ui/ConfirmDialog.js";
|
|
|
17
17
|
import { ModuleSettingsForm } from "./moduleSettingsForm.js";
|
|
18
18
|
import { SnapshotsCard } from "./SnapshotsCard.js";
|
|
19
19
|
import { MailSettings } from "./MailSettings.js";
|
|
20
|
+
import { useAuth } from "../../auth/AuthProvider.js";
|
|
21
|
+
import { MediaPickerButton } from "../media/MediaPicker.js";
|
|
22
|
+
import { contrast, contrastOordeel, naarHex6, tekstOp, LETTERTYPE_PAREN } from "../lib/kleur.js";
|
|
23
|
+
function KleurVeld({ label, hint, value, placeholder, onChange }) {
|
|
24
|
+
return /* @__PURE__ */ jsx(Field, { label, hint, children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
25
|
+
/* @__PURE__ */ jsx(
|
|
26
|
+
"input",
|
|
27
|
+
{
|
|
28
|
+
type: "color",
|
|
29
|
+
"aria-label": `${label} kiezen`,
|
|
30
|
+
value: naarHex6(value || placeholder, "#ffffff"),
|
|
31
|
+
onChange: (e) => onChange(e.target.value),
|
|
32
|
+
className: "h-9 w-11 shrink-0 cursor-pointer rounded-md border border-slate-200 bg-transparent p-0.5"
|
|
33
|
+
}
|
|
34
|
+
),
|
|
35
|
+
/* @__PURE__ */ jsx(Input, { value, placeholder, onChange: (e) => onChange(e.target.value) })
|
|
36
|
+
] }) });
|
|
37
|
+
}
|
|
38
|
+
function ContrastRegel({ primary, background }) {
|
|
39
|
+
const knop = contrastOordeel(contrast(primary, tekstOp(primary)));
|
|
40
|
+
const opAchtergrond = contrastOordeel(contrast(primary, background || "#ffffff"));
|
|
41
|
+
if (!knop && !opAchtergrond) return null;
|
|
42
|
+
const kleur = (t) => t === "goed" ? "text-emerald-700" : t === "matig" ? "text-amber-700" : "text-red-700";
|
|
43
|
+
return /* @__PURE__ */ jsxs("div", { className: "grid gap-1 text-xs", children: [
|
|
44
|
+
knop && /* @__PURE__ */ jsxs("p", { className: kleur(knop.toon), children: [
|
|
45
|
+
"Knoptekst op merkkleur: ",
|
|
46
|
+
knop.tekst.replace(/^Contrast /, "")
|
|
47
|
+
] }),
|
|
48
|
+
opAchtergrond && /* @__PURE__ */ jsxs("p", { className: kleur(opAchtergrond.toon), children: [
|
|
49
|
+
"Merkkleur als tekst op de achtergrond: ",
|
|
50
|
+
opAchtergrond.tekst.replace(/^Contrast /, "")
|
|
51
|
+
] }),
|
|
52
|
+
/* @__PURE__ */ jsxs("p", { className: "text-slate-500", children: [
|
|
53
|
+
"Voorbeeld: ",
|
|
54
|
+
/* @__PURE__ */ jsx("span", { className: "rounded-md px-2 py-0.5 font-medium", style: { background: primary, color: tekstOp(primary) }, children: "Knop" })
|
|
55
|
+
] })
|
|
56
|
+
] });
|
|
57
|
+
}
|
|
20
58
|
function SettingsPanel({ siteId }) {
|
|
21
59
|
const { toast } = useToast();
|
|
22
60
|
const confirm = useConfirm();
|
|
23
61
|
const { defs } = useModules();
|
|
24
62
|
const cfg = useSiteConfig();
|
|
63
|
+
const { role } = useAuth();
|
|
64
|
+
const technisch = role === "superadmin";
|
|
25
65
|
const [s, setS] = useState({});
|
|
26
66
|
const [loaded, setLoaded] = useState(false);
|
|
27
67
|
const [exporting, setExporting] = useState(false);
|
|
@@ -147,10 +187,11 @@ function SettingsPanel({ siteId }) {
|
|
|
147
187
|
{ sleutel: "huisstijl", label: "Huisstijl" },
|
|
148
188
|
{ sleutel: "seo", label: "SEO" },
|
|
149
189
|
{ sleutel: "footer", label: "Footer" },
|
|
150
|
-
{ sleutel: "
|
|
151
|
-
{ sleutel: "mail", label: "E-mail" },
|
|
190
|
+
{ sleutel: "redirects", label: "Doorverwijzingen" },
|
|
191
|
+
...technisch ? [{ sleutel: "modules", label: "Modules" }, { sleutel: "mail", label: "E-mail" }] : [],
|
|
152
192
|
{ sleutel: "backup", label: "Back-up" }
|
|
153
193
|
];
|
|
194
|
+
const redirects = s.redirects ?? [];
|
|
154
195
|
return (
|
|
155
196
|
/* Een formulier leest niet op 1300px breed; een invoerveld hoort niet breder te zijn dan de
|
|
156
197
|
tekst die erin komt. Vandaar een leesbreedte in plaats van het hele werkblad vullen. */
|
|
@@ -170,8 +211,12 @@ function SettingsPanel({ siteId }) {
|
|
|
170
211
|
/* @__PURE__ */ jsx("h3", { className: "mb-3 text-base font-semibold", children: "Algemeen" }),
|
|
171
212
|
/* @__PURE__ */ jsxs("div", { className: "grid gap-3 md:grid-cols-2", children: [
|
|
172
213
|
/* @__PURE__ */ jsx(Field, { label: "Sitenaam", children: /* @__PURE__ */ jsx(Input, { value: s.name ?? "", onChange: (e) => setS({ ...s, name: e.target.value }) }) }),
|
|
173
|
-
/* @__PURE__ */ jsx(Field, { label: "
|
|
174
|
-
/* @__PURE__ */ jsx(Field, { label: "
|
|
214
|
+
/* @__PURE__ */ jsx(Field, { label: "Contact-e-mail", hint: "Waar bezoekers en formulieren je bereiken.", children: /* @__PURE__ */ jsx(Input, { value: s.contactEmail ?? "", onChange: (e) => setS({ ...s, contactEmail: e.target.value }) }) }),
|
|
215
|
+
technisch ? /* @__PURE__ */ jsx(Field, { label: "Domein", hint: "Technisch: bepaalt inloglinks, sitemap en de revalidate-ping. Alleen superadmin.", children: /* @__PURE__ */ jsx(Input, { value: s.domain ?? "", onChange: (e) => setS({ ...s, domain: e.target.value }) }) }) : s.domain && /* @__PURE__ */ jsx(Field, { label: "Domein", children: /* @__PURE__ */ jsxs("p", { className: "text-sm text-slate-600", children: [
|
|
216
|
+
s.domain,
|
|
217
|
+
" ",
|
|
218
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-slate-400", children: "\u2014 wijzigen via XiWebdesign" })
|
|
219
|
+
] }) })
|
|
175
220
|
] })
|
|
176
221
|
] }) }),
|
|
177
222
|
sectie === "huisstijl" && /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs(Card, { children: [
|
|
@@ -190,18 +235,25 @@ function SettingsPanel({ siteId }) {
|
|
|
190
235
|
}
|
|
191
236
|
) }),
|
|
192
237
|
/* @__PURE__ */ jsxs("div", { className: "grid gap-3 md:grid-cols-2", children: [
|
|
193
|
-
/* @__PURE__ */ jsx(
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
hint: "Wordt ook gebruikt in je e-mails en op de factuur-PDF",
|
|
198
|
-
children: /* @__PURE__ */ jsx(Input, { value: theme.primary, onChange: (e) => setS({ ...s, theme: { ...theme, primary: e.target.value } }) })
|
|
199
|
-
}
|
|
200
|
-
),
|
|
201
|
-
/* @__PURE__ */ jsx(Field, { label: "Accentkleur", children: /* @__PURE__ */ jsx(Input, { value: theme.accent ?? "", onChange: (e) => setS({ ...s, theme: { ...theme, accent: e.target.value } }) }) }),
|
|
202
|
-
/* @__PURE__ */ jsx(Field, { label: "Achtergrondkleur", children: /* @__PURE__ */ jsx(Input, { value: theme.background ?? "", onChange: (e) => setS({ ...s, theme: { ...theme, background: e.target.value } }), placeholder: "#ffffff" }) }),
|
|
203
|
-
/* @__PURE__ */ jsx(Field, { label: "Tekstkleur", children: /* @__PURE__ */ jsx(Input, { value: theme.foreground ?? "", onChange: (e) => setS({ ...s, theme: { ...theme, foreground: e.target.value } }), placeholder: "#0f172a" }) })
|
|
238
|
+
/* @__PURE__ */ jsx(KleurVeld, { label: "Merkkleur", hint: "Knoppen, links, accenten \u2014 en je e-mails en factuur-PDF", value: theme.primary, onChange: (v) => setS({ ...s, theme: { ...theme, primary: v } }) }),
|
|
239
|
+
/* @__PURE__ */ jsx(KleurVeld, { label: "Accentkleur", value: theme.accent ?? "", placeholder: theme.primary, onChange: (v) => setS({ ...s, theme: { ...theme, accent: v } }) }),
|
|
240
|
+
/* @__PURE__ */ jsx(KleurVeld, { label: "Achtergrondkleur", value: theme.background ?? "", placeholder: "#ffffff", onChange: (v) => setS({ ...s, theme: { ...theme, background: v } }) }),
|
|
241
|
+
/* @__PURE__ */ jsx(KleurVeld, { label: "Tekstkleur", value: theme.foreground ?? "", placeholder: "#0f172a", onChange: (v) => setS({ ...s, theme: { ...theme, foreground: v } }) })
|
|
204
242
|
] }),
|
|
243
|
+
/* @__PURE__ */ jsx(ContrastRegel, { primary: theme.primary, background: theme.background }),
|
|
244
|
+
/* @__PURE__ */ jsx(Field, { label: "Lettertype", hint: "Een uitgekozen combinatie voor tekst en koppen. Geavanceerd: eigen fontFamily/fontUrl via de sitecode.", children: /* @__PURE__ */ jsx(
|
|
245
|
+
Select,
|
|
246
|
+
{
|
|
247
|
+
value: LETTERTYPE_PAREN.find((p) => p.fontFamily === theme.fontFamily && (p.headingFont ?? "") === (theme.headingFont ?? ""))?.id ?? "",
|
|
248
|
+
placeholder: theme.fontFamily ? `Eigen: ${theme.fontFamily.split(",")[0].replace(/'/g, "")}` : "Kies een lettertype\u2026",
|
|
249
|
+
onValueChange: (id) => {
|
|
250
|
+
const paar = LETTERTYPE_PAREN.find((p) => p.id === id);
|
|
251
|
+
if (!paar) return;
|
|
252
|
+
setS({ ...s, theme: { ...theme, fontFamily: paar.fontFamily, headingFont: paar.headingFont, fontUrl: paar.fontUrl || void 0 } });
|
|
253
|
+
},
|
|
254
|
+
options: LETTERTYPE_PAREN.map((p) => ({ value: p.id, label: p.label }))
|
|
255
|
+
}
|
|
256
|
+
) }),
|
|
205
257
|
/* @__PURE__ */ jsx(
|
|
206
258
|
Field,
|
|
207
259
|
{
|
|
@@ -247,14 +299,24 @@ function SettingsPanel({ siteId }) {
|
|
|
247
299
|
}
|
|
248
300
|
) })
|
|
249
301
|
] }),
|
|
250
|
-
/* @__PURE__ */ jsx(Field, { label: "Logo
|
|
302
|
+
/* @__PURE__ */ jsx(Field, { label: "Logo (e-mail & factuur)", hint: "PNG/JPG uit je mediabibliotheek. Verschijnt in de mail-header en op de factuur-PDF.", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
303
|
+
theme.logo && // eslint-disable-next-line @next/next/no-img-element
|
|
304
|
+
/* @__PURE__ */ jsx("img", { src: theme.logo, alt: "", className: "h-10 max-w-40 rounded-md border border-slate-200 bg-white object-contain p-1" }),
|
|
305
|
+
/* @__PURE__ */ jsx(MediaPickerButton, { siteId, label: theme.logo ? "Ander logo" : "Kies / upload", onSelect: (m) => setS({ ...s, theme: { ...theme, logo: m.url } }) }),
|
|
306
|
+
theme.logo && /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onClick: () => setS({ ...s, theme: { ...theme, logo: void 0 } }), children: "Weghalen" })
|
|
307
|
+
] }) })
|
|
251
308
|
] })
|
|
252
309
|
] }) }),
|
|
253
310
|
sectie === "seo" && /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs(Card, { children: [
|
|
254
311
|
/* @__PURE__ */ jsx("h3", { className: "mb-3 text-base font-semibold", children: "SEO (standaard)" }),
|
|
255
312
|
/* @__PURE__ */ jsxs("div", { className: "grid gap-3", children: [
|
|
256
313
|
/* @__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 } }) }) }),
|
|
257
|
-
/* @__PURE__ */ jsx(Field, { label: "
|
|
314
|
+
/* @__PURE__ */ jsx(Field, { label: "Standaardafbeelding bij het delen", hint: "Voorbeeld op social media/WhatsApp voor pagina's zonder eigen afbeelding. Liefst 1200\xD7630.", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
315
|
+
s.seoDefaults?.ogImage && // eslint-disable-next-line @next/next/no-img-element
|
|
316
|
+
/* @__PURE__ */ jsx("img", { src: s.seoDefaults.ogImage, alt: "", className: "h-14 w-24 rounded-md border border-slate-200 object-cover" }),
|
|
317
|
+
/* @__PURE__ */ jsx(MediaPickerButton, { siteId, label: s.seoDefaults?.ogImage ? "Andere kiezen" : "Kies / upload", onSelect: (m) => setS({ ...s, seoDefaults: { ...s.seoDefaults, ogImage: m.url } }) }),
|
|
318
|
+
s.seoDefaults?.ogImage && /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onClick: () => setS({ ...s, seoDefaults: { ...s.seoDefaults, ogImage: void 0 } }), children: "Weghalen" })
|
|
319
|
+
] }) })
|
|
258
320
|
] })
|
|
259
321
|
] }) }),
|
|
260
322
|
sectie === "footer" && /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs(Card, { children: [
|
|
@@ -315,7 +377,26 @@ function SettingsPanel({ siteId }) {
|
|
|
315
377
|
] })
|
|
316
378
|
] })
|
|
317
379
|
] }) }),
|
|
318
|
-
sectie === "
|
|
380
|
+
sectie === "redirects" && /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs(Card, { children: [
|
|
381
|
+
/* @__PURE__ */ jsx("h3", { className: "mb-1 text-base font-semibold", children: "Doorverwijzingen" }),
|
|
382
|
+
/* @__PURE__ */ jsx("p", { className: "mb-3 text-xs text-slate-500", children: "Een oud adres dat naar een nieuw adres wijst. Ontstaat vanzelf als je het adres van een pagina wijzigt en publiceert; hier kun je er zelf toevoegen (bijvoorbeeld van je vorige website) of opruimen. Alleen adressen binnen deze site." }),
|
|
383
|
+
/* @__PURE__ */ jsx(
|
|
384
|
+
ArrayEditor,
|
|
385
|
+
{
|
|
386
|
+
items: redirects,
|
|
387
|
+
onChange: (lijst) => setS({ ...s, redirects: lijst }),
|
|
388
|
+
newItem: () => ({ from: "/", to: "/" }),
|
|
389
|
+
addLabel: "+ Doorverwijzing",
|
|
390
|
+
emptyLabel: "Nog geen doorverwijzingen.",
|
|
391
|
+
renderRow: (r, update) => /* @__PURE__ */ jsxs("div", { className: "grid gap-2 md:grid-cols-[1fr_auto_1fr] md:items-end", children: [
|
|
392
|
+
/* @__PURE__ */ jsx(Field, { label: "Van (oud adres)", children: /* @__PURE__ */ jsx(Input, { value: r.from, placeholder: "/oude-pagina", onChange: (e) => update({ from: e.target.value }) }) }),
|
|
393
|
+
/* @__PURE__ */ jsx("span", { className: "hidden pb-2 text-slate-400 md:block", "aria-hidden": "true", children: "\u2192" }),
|
|
394
|
+
/* @__PURE__ */ jsx(Field, { label: "Naar (nieuw adres)", children: /* @__PURE__ */ jsx(Input, { value: r.to, placeholder: "/nieuwe-pagina", onChange: (e) => update({ to: e.target.value }) }) })
|
|
395
|
+
] })
|
|
396
|
+
}
|
|
397
|
+
)
|
|
398
|
+
] }) }),
|
|
399
|
+
sectie === "modules" && technisch && /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs(Card, { children: [
|
|
319
400
|
/* @__PURE__ */ jsx("h3", { className: "mb-1 text-base font-semibold", children: "Modules" }),
|
|
320
401
|
/* @__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." }),
|
|
321
402
|
/* @__PURE__ */ jsx("div", { className: "grid gap-3", children: defs.filter((d) => !d.core).map((d) => {
|
|
@@ -339,7 +420,7 @@ function SettingsPanel({ siteId }) {
|
|
|
339
420
|
] }, d.id);
|
|
340
421
|
}) })
|
|
341
422
|
] }) }),
|
|
342
|
-
sectie === "mail" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
423
|
+
sectie === "mail" && technisch && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
343
424
|
/* @__PURE__ */ jsx(MailSettings, { siteId }),
|
|
344
425
|
/* @__PURE__ */ jsxs(Card, { children: [
|
|
345
426
|
/* @__PURE__ */ jsx("h3", { className: "mb-1 text-base font-semibold", children: "E-mailteksten" }),
|