@xiwebdesign/xicms-core 6.2.0 → 6.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.
- package/dist/admin/forms/FormsPanel.js +59 -27
- package/dist/admin/forms/FormsPanel.js.map +1 -1
- package/dist/admin/settings/MailTemplates.d.ts +8 -3
- package/dist/admin/settings/MailTemplates.js +2 -7
- package/dist/admin/settings/MailTemplates.js.map +1 -1
- package/dist/admin/settings/SettingsPanel.js +10 -4
- package/dist/admin/settings/SettingsPanel.js.map +1 -1
- package/dist/admin/shop/ShopPanel.d.ts +3 -3
- package/dist/admin/shop/ShopPanel.js +4 -0
- package/dist/admin/shop/ShopPanel.js.map +1 -1
- package/dist/admin.css +1 -1
- package/dist/schema/forms.d.ts +8 -1
- package/dist/schema/forms.js +7 -2
- package/dist/schema/forms.js.map +1 -1
- package/dist/schema/mail.d.ts +4 -0
- package/dist/schema/mail.js +5 -1
- package/dist/schema/mail.js.map +1 -1
- package/package.json +1 -1
|
@@ -18,6 +18,8 @@ import { Select } from "../ui/select";
|
|
|
18
18
|
import { Switch } from "../ui/switch";
|
|
19
19
|
import { useToast } from "../ui/toast";
|
|
20
20
|
import { useConfirm } from "../ui/ConfirmDialog";
|
|
21
|
+
import { MailTemplates } from "../settings/MailTemplates";
|
|
22
|
+
import { FORM_TEMPLATE_KEYS } from "../../schema/mail";
|
|
21
23
|
const FIELD_TYPES = [
|
|
22
24
|
{ value: "text", label: "Tekst" },
|
|
23
25
|
{ value: "email", label: "E-mail" },
|
|
@@ -53,31 +55,42 @@ function FormsPanel({ siteId }) {
|
|
|
53
55
|
});
|
|
54
56
|
setSelected(ref.id);
|
|
55
57
|
}
|
|
56
|
-
return /* @__PURE__ */ jsxs("div", { className: "grid gap-
|
|
57
|
-
/* @__PURE__ */ jsxs(
|
|
58
|
-
/* @__PURE__ */ jsxs(
|
|
59
|
-
/* @__PURE__ */
|
|
60
|
-
|
|
58
|
+
return /* @__PURE__ */ jsxs("div", { className: "grid gap-6", children: [
|
|
59
|
+
/* @__PURE__ */ jsxs("div", { className: "grid gap-4 md:grid-cols-[260px_1fr]", children: [
|
|
60
|
+
/* @__PURE__ */ jsxs(Card, { className: "h-fit", children: [
|
|
61
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-3 flex items-center justify-between", children: [
|
|
62
|
+
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Formulieren" }),
|
|
63
|
+
/* @__PURE__ */ jsx(Button, { size: "sm", onClick: createForm, children: "+ Nieuw" })
|
|
64
|
+
] }),
|
|
65
|
+
/* @__PURE__ */ jsxs("ul", { className: "grid gap-1", children: [
|
|
66
|
+
forms.map((f) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
|
|
67
|
+
"button",
|
|
68
|
+
{
|
|
69
|
+
onClick: () => setSelected(f.id),
|
|
70
|
+
className: `w-full truncate rounded-lg px-3 py-2 text-left text-sm ${f.id === selected ? "bg-brand-50 text-brand-700" : "hover:bg-slate-100"}`,
|
|
71
|
+
children: [
|
|
72
|
+
f.name,
|
|
73
|
+
/* @__PURE__ */ jsxs("span", { className: "ml-1 text-xs text-slate-400", children: [
|
|
74
|
+
"#",
|
|
75
|
+
f.id.slice(0, 5)
|
|
76
|
+
] })
|
|
77
|
+
]
|
|
78
|
+
}
|
|
79
|
+
) }, f.id)),
|
|
80
|
+
forms.length === 0 && /* @__PURE__ */ jsx("li", { className: "px-3 py-2 text-sm text-slate-500", children: "Nog geen formulieren." })
|
|
81
|
+
] })
|
|
61
82
|
] }),
|
|
62
|
-
/* @__PURE__ */
|
|
63
|
-
forms.map((f) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
|
|
64
|
-
"button",
|
|
65
|
-
{
|
|
66
|
-
onClick: () => setSelected(f.id),
|
|
67
|
-
className: `w-full truncate rounded-lg px-3 py-2 text-left text-sm ${f.id === selected ? "bg-brand-50 text-brand-700" : "hover:bg-slate-100"}`,
|
|
68
|
-
children: [
|
|
69
|
-
f.name,
|
|
70
|
-
/* @__PURE__ */ jsxs("span", { className: "ml-1 text-xs text-slate-400", children: [
|
|
71
|
-
"#",
|
|
72
|
-
f.id.slice(0, 5)
|
|
73
|
-
] })
|
|
74
|
-
]
|
|
75
|
-
}
|
|
76
|
-
) }, f.id)),
|
|
77
|
-
forms.length === 0 && /* @__PURE__ */ jsx("li", { className: "px-3 py-2 text-sm text-slate-500", children: "Nog geen formulieren." })
|
|
78
|
-
] })
|
|
83
|
+
current ? /* @__PURE__ */ jsx(FormDetail, { siteId, form: current, onDeleted: () => setSelected(null), onToast: toast }, current.id) : /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsx("p", { className: "text-sm text-slate-500", children: "Selecteer of maak een formulier." }) })
|
|
79
84
|
] }),
|
|
80
|
-
|
|
85
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
86
|
+
/* @__PURE__ */ jsx("h3", { className: "mb-1 text-base font-semibold", children: "E-mailteksten voor formulieren" }),
|
|
87
|
+
/* @__PURE__ */ jsxs("p", { className: "mb-3 text-sm text-slate-600", children: [
|
|
88
|
+
"Gelden voor \xE1lle formulieren: de notificatie naar jou en de bevestiging naar de invuller. De opmaak (huisstijl) en verzendprovider stel je site-breed in bij ",
|
|
89
|
+
/* @__PURE__ */ jsx("strong", { children: "Instellingen \u2192 E-mail" }),
|
|
90
|
+
"."
|
|
91
|
+
] }),
|
|
92
|
+
/* @__PURE__ */ jsx(MailTemplates, { siteId, keys: FORM_TEMPLATE_KEYS })
|
|
93
|
+
] })
|
|
81
94
|
] });
|
|
82
95
|
}
|
|
83
96
|
function FormDetail({
|
|
@@ -125,9 +138,28 @@ function FormDetail({
|
|
|
125
138
|
/* @__PURE__ */ jsx(Field, { label: "Naam", children: /* @__PURE__ */ jsx(Input, { value: def.name, onChange: (e) => setDef({ ...def, name: e.target.value }) }) }),
|
|
126
139
|
/* @__PURE__ */ jsx(Field, { label: "Verstuurknop", children: /* @__PURE__ */ jsx(Input, { value: def.submitLabel, onChange: (e) => setDef({ ...def, submitLabel: e.target.value }) }) }),
|
|
127
140
|
/* @__PURE__ */ jsx(Field, { label: "Bedankbericht", children: /* @__PURE__ */ jsx(Input, { value: def.successMessage, onChange: (e) => setDef({ ...def, successMessage: e.target.value }) }) }),
|
|
128
|
-
/* @__PURE__ */
|
|
129
|
-
|
|
130
|
-
|
|
141
|
+
/* @__PURE__ */ jsx(Field, { label: "Bedankt-pagina (optioneel)", hint: "Na verzenden doorsturen naar dit pad, bv. /bedankt", children: /* @__PURE__ */ jsx(Input, { value: def.successRedirect ?? "", onChange: (e) => setDef({ ...def, successRedirect: e.target.value || void 0 }) }) })
|
|
142
|
+
] }),
|
|
143
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-4 rounded-lg border border-slate-200 p-4", children: [
|
|
144
|
+
/* @__PURE__ */ jsx("h4", { className: "text-sm font-semibold", children: "Wat gebeurt er bij een inzending?" }),
|
|
145
|
+
/* @__PURE__ */ jsxs("p", { className: "mb-3 mt-0.5 text-xs text-slate-500", children: [
|
|
146
|
+
"Elke inzending wordt altijd bewaard onder ",
|
|
147
|
+
/* @__PURE__ */ jsx("strong", { children: "Inzendingen" }),
|
|
148
|
+
". Daarnaast kun je een kopie mailen naar jezelf en/of een bevestiging naar de invuller. De opmaak en verzendprovider stel je site-breed in bij ",
|
|
149
|
+
/* @__PURE__ */ jsx("strong", { children: "Instellingen \u2192 E-mail" }),
|
|
150
|
+
"."
|
|
151
|
+
] }),
|
|
152
|
+
/* @__PURE__ */ jsxs("div", { className: "grid gap-3 md:grid-cols-2", children: [
|
|
153
|
+
/* @__PURE__ */ jsx(Field, { label: "Kopie van inzending naar", hint: "Leeg = het notificatie-adres uit Instellingen \u2192 E-mail.", children: /* @__PURE__ */ jsx(
|
|
154
|
+
Input,
|
|
155
|
+
{
|
|
156
|
+
type: "email",
|
|
157
|
+
value: def.notifyTo ?? "",
|
|
158
|
+
onChange: (e) => setDef({ ...def, notifyTo: e.target.value || void 0 }),
|
|
159
|
+
placeholder: "jij@voorbeeld.nl"
|
|
160
|
+
}
|
|
161
|
+
) }),
|
|
162
|
+
/* @__PURE__ */ jsxs("label", { className: "mt-6 flex h-9 items-center gap-2 text-sm", children: [
|
|
131
163
|
/* @__PURE__ */ jsx(
|
|
132
164
|
"input",
|
|
133
165
|
{
|
|
@@ -136,7 +168,7 @@ function FormDetail({
|
|
|
136
168
|
onChange: (e) => setDef({ ...def, confirmSubmitter: e.target.checked })
|
|
137
169
|
}
|
|
138
170
|
),
|
|
139
|
-
"
|
|
171
|
+
"Stuur de invuller ook een bevestiging (naar het eerste e-mailveld)"
|
|
140
172
|
] })
|
|
141
173
|
] })
|
|
142
174
|
] }),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/forms/FormsPanel.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport {\n addDoc,\n collection,\n deleteDoc,\n doc,\n onSnapshot,\n orderBy,\n query,\n serverTimestamp,\n setDoc,\n} from \"firebase/firestore\";\nimport { getDb } from \"../../firebase/client\";\nimport { FormSchema, type FormDefinition, type FormField, type FieldType } from \"../../schema/forms\";\nimport { Button, Card, Field, Input, Badge } from \"../ui/primitives\";\nimport { Select } from \"../ui/select\";\nimport { Switch } from \"../ui/switch\";\nimport { useToast } from \"../ui/toast\";\nimport { useConfirm } from \"../ui/ConfirmDialog\";\n\ntype StoredForm = FormDefinition & { id: string };\n\nconst FIELD_TYPES: { value: FieldType; label: string }[] = [\n { value: \"text\", label: \"Tekst\" },\n { value: \"email\", label: \"E-mail\" },\n { value: \"tel\", label: \"Telefoon\" },\n { value: \"textarea\", label: \"Tekstvak\" },\n { value: \"select\", label: \"Keuzelijst\" },\n { value: \"radio\", label: \"Keuzerondjes\" },\n { value: \"checkbox\", label: \"Vinkje\" },\n { value: \"date\", label: \"Datum\" },\n { value: \"number\", label: \"Getal\" },\n];\n\nexport function FormsPanel({ siteId }: { siteId: string }) {\n const { toast } = useToast();\n const [forms, setForms] = useState<StoredForm[]>([]);\n const [selected, setSelected] = useState<string | null>(null);\n\n useEffect(() => {\n const db = getDb();\n if (!db) return;\n return onSnapshot(collection(db, \"sites\", siteId, \"forms\"), (snap) =>\n setForms(snap.docs.map((d) => ({ id: d.id, ...(d.data() as Omit<StoredForm, \"id\">) }))),\n );\n }, [siteId]);\n\n const current = forms.find((f) => f.id === selected) ?? null;\n\n async function createForm() {\n const db = getDb();\n if (!db) return;\n const ref = await addDoc(collection(db, \"sites\", siteId, \"forms\"), {\n name: \"Nieuw formulier\",\n fields: [{ name: \"naam\", label: \"Naam\", type: \"text\", required: true }],\n submitLabel: \"Versturen\",\n successMessage: \"Bedankt! We nemen snel contact op.\",\n } satisfies FormDefinition);\n setSelected(ref.id);\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\">\n <h3 className=\"text-sm font-semibold\">Formulieren</h3>\n <Button size=\"sm\" onClick={createForm}>+ Nieuw</Button>\n </div>\n <ul className=\"grid gap-1\">\n {forms.map((f) => (\n <li key={f.id}>\n <button\n onClick={() => setSelected(f.id)}\n className={`w-full truncate rounded-lg px-3 py-2 text-left text-sm ${\n f.id === selected ? \"bg-brand-50 text-brand-700\" : \"hover:bg-slate-100\"\n }`}\n >\n {f.name}\n <span className=\"ml-1 text-xs text-slate-400\">#{f.id.slice(0, 5)}</span>\n </button>\n </li>\n ))}\n {forms.length === 0 && <li className=\"px-3 py-2 text-sm text-slate-500\">Nog geen formulieren.</li>}\n </ul>\n </Card>\n {current ? (\n <FormDetail key={current.id} siteId={siteId} form={current} onDeleted={() => setSelected(null)} onToast={toast} />\n ) : (\n <Card>\n <p className=\"text-sm text-slate-500\">Selecteer of maak een formulier.</p>\n </Card>\n )}\n </div>\n );\n}\n\nfunction FormDetail({\n siteId,\n form,\n onDeleted,\n onToast,\n}: {\n siteId: string;\n form: StoredForm;\n onDeleted: () => void;\n onToast: (t: { title: string; description?: string; tone?: \"default\" | \"success\" | \"error\" }) => void;\n}) {\n const [def, setDef] = useState<FormDefinition>(form);\n const confirm = useConfirm();\n\n async function save() {\n const db = getDb();\n if (!db) return;\n const parsed = FormSchema.safeParse(def);\n if (!parsed.success) {\n onToast({ title: \"Ongeldig formulier\", description: parsed.error.issues[0]?.message, tone: \"error\" });\n return;\n }\n await setDoc(doc(db, \"sites\", siteId, \"forms\", form.id), parsed.data, { merge: true });\n onToast({ title: \"Opgeslagen\", tone: \"success\" });\n }\n\n async function remove() {\n const db = getDb();\n if (!db) return;\n const ok = await confirm({ title: \"Formulier verwijderen?\", description: \"Het formulier wordt verwijderd. Inzendingen blijven bewaard.\", confirmLabel: \"Verwijderen\", tone: \"danger\" });\n if (!ok) return;\n await deleteDoc(doc(db, \"sites\", siteId, \"forms\", form.id));\n onDeleted();\n }\n\n function setField(i: number, part: Partial<FormField>) {\n const fields = [...def.fields];\n fields[i] = { ...fields[i], ...part };\n setDef({ ...def, fields });\n }\n\n return (\n <div className=\"grid gap-4\">\n <Card>\n <div className=\"mb-3 flex items-center justify-between\">\n <h3 className=\"text-base font-semibold\">Formulier bewerken</h3>\n <div className=\"flex gap-2\">\n <Button variant=\"danger\" size=\"sm\" onClick={remove}>Verwijderen</Button>\n <Button size=\"sm\" onClick={save}>Opslaan</Button>\n </div>\n </div>\n <div className=\"grid gap-3 md:grid-cols-2\">\n <Field label=\"Naam\"><Input value={def.name} onChange={(e) => setDef({ ...def, name: e.target.value })} /></Field>\n <Field label=\"Verstuurknop\"><Input value={def.submitLabel} onChange={(e) => setDef({ ...def, submitLabel: e.target.value })} /></Field>\n <Field label=\"Bedankbericht\"><Input value={def.successMessage} onChange={(e) => setDef({ ...def, successMessage: e.target.value })} /></Field>\n <div className=\"grid grid-cols-2 items-end gap-2\">\n <Field label=\"Bedankt-pagina (optioneel)\" hint=\"Na verzenden doorsturen naar dit pad, bv. /bedankt\">\n <Input value={def.successRedirect ?? \"\"} onChange={(e) => setDef({ ...def, successRedirect: e.target.value || undefined })} />\n </Field>\n <label className=\"flex h-9 items-center gap-2 text-sm\">\n <input\n type=\"checkbox\"\n checked={Boolean(def.confirmSubmitter)}\n onChange={(e) => setDef({ ...def, confirmSubmitter: e.target.checked })}\n />\n Bevestigingsmail naar inzender (eerste e-mailveld)\n </label>\n </div>\n </div>\n <p className=\"mt-3 text-xs text-slate-500\">Formulier-id (voor het Formulier-blok): <code>{form.id}</code></p>\n </Card>\n\n <Card>\n <h4 className=\"mb-3 text-sm font-semibold\">Velden</h4>\n <div className=\"grid gap-3\">\n {def.fields.map((f, i) => (\n <div key={i} className=\"grid items-end gap-2 rounded-lg border border-slate-200 p-3 md:grid-cols-[1fr_1fr_140px_auto_auto]\">\n <Field label=\"Naam (sleutel)\"><Input value={f.name} onChange={(e) => setField(i, { name: e.target.value })} /></Field>\n <Field label=\"Label\"><Input value={f.label} onChange={(e) => setField(i, { label: e.target.value })} /></Field>\n <Field label=\"Type\">\n <Select value={f.type} onValueChange={(v) => setField(i, { type: v as FieldType })} options={FIELD_TYPES} />\n </Field>\n <Switch checked={f.required} onCheckedChange={(v) => setField(i, { required: v })} label=\"Verplicht\" />\n <Button variant=\"ghost\" size=\"sm\" onClick={() => setDef({ ...def, fields: def.fields.filter((_, idx) => idx !== i) })}>🗑</Button>\n {(f.type === \"select\" || f.type === \"radio\") && (\n <div className=\"md:col-span-5\">\n <Field label=\"Opties (komma-gescheiden)\">\n <Input\n value={(f.options ?? []).join(\", \")}\n onChange={(e) => setField(i, { options: e.target.value.split(\",\").map((s) => s.trim()).filter(Boolean) })}\n />\n </Field>\n </div>\n )}\n </div>\n ))}\n </div>\n <div className=\"mt-3\">\n <Button\n variant=\"secondary\"\n size=\"sm\"\n onClick={() => setDef({ ...def, fields: [...def.fields, { name: `veld${def.fields.length + 1}`, label: \"Nieuw veld\", type: \"text\", required: false }] })}\n >\n + Veld toevoegen\n </Button>\n </div>\n </Card>\n\n <Submissions siteId={siteId} formId={form.id} />\n </div>\n );\n}\n\nfunction Submissions({ siteId, formId }: { siteId: string; formId: string }) {\n const [subs, setSubs] = useState<{ id: string; values: Record<string, unknown>; createdAt?: unknown }[]>([]);\n useEffect(() => {\n const db = getDb();\n if (!db) return;\n const q = query(collection(db, \"sites\", siteId, \"forms\", formId, \"submissions\"), orderBy(\"createdAt\", \"desc\"));\n return onSnapshot(q, (snap) => setSubs(snap.docs.map((d) => ({ id: d.id, ...(d.data() as { values: Record<string, unknown> }) }))));\n }, [siteId, formId]);\n\n return (\n <Card>\n <h4 className=\"mb-3 text-sm font-semibold\">Inzendingen <Badge>{subs.length}</Badge></h4>\n {subs.length === 0 ? (\n <p className=\"text-sm text-slate-500\">Nog geen inzendingen.</p>\n ) : (\n <div className=\"grid gap-2\">\n {subs.map((s) => (\n <div key={s.id} className=\"rounded-lg border border-slate-200 p-3 text-sm\">\n {Object.entries(s.values ?? {}).map(([k, v]) => (\n <div key={k}>\n <span className=\"font-medium text-slate-600\">{k}:</span> {String(v)}\n </div>\n ))}\n </div>\n ))}\n </div>\n )}\n </Card>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/forms/FormsPanel.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport {\n addDoc,\n collection,\n deleteDoc,\n doc,\n onSnapshot,\n orderBy,\n query,\n serverTimestamp,\n setDoc,\n} from \"firebase/firestore\";\nimport { getDb } from \"../../firebase/client\";\nimport { FormSchema, type FormDefinition, type FormField, type FieldType } from \"../../schema/forms\";\nimport { Button, Card, Field, Input, Badge } from \"../ui/primitives\";\nimport { Select } from \"../ui/select\";\nimport { Switch } from \"../ui/switch\";\nimport { useToast } from \"../ui/toast\";\nimport { useConfirm } from \"../ui/ConfirmDialog\";\nimport { MailTemplates } from \"../settings/MailTemplates\";\nimport { FORM_TEMPLATE_KEYS } from \"../../schema/mail\";\n\ntype StoredForm = FormDefinition & { id: string };\n\nconst FIELD_TYPES: { value: FieldType; label: string }[] = [\n { value: \"text\", label: \"Tekst\" },\n { value: \"email\", label: \"E-mail\" },\n { value: \"tel\", label: \"Telefoon\" },\n { value: \"textarea\", label: \"Tekstvak\" },\n { value: \"select\", label: \"Keuzelijst\" },\n { value: \"radio\", label: \"Keuzerondjes\" },\n { value: \"checkbox\", label: \"Vinkje\" },\n { value: \"date\", label: \"Datum\" },\n { value: \"number\", label: \"Getal\" },\n];\n\nexport function FormsPanel({ siteId }: { siteId: string }) {\n const { toast } = useToast();\n const [forms, setForms] = useState<StoredForm[]>([]);\n const [selected, setSelected] = useState<string | null>(null);\n\n useEffect(() => {\n const db = getDb();\n if (!db) return;\n return onSnapshot(collection(db, \"sites\", siteId, \"forms\"), (snap) =>\n setForms(snap.docs.map((d) => ({ id: d.id, ...(d.data() as Omit<StoredForm, \"id\">) }))),\n );\n }, [siteId]);\n\n const current = forms.find((f) => f.id === selected) ?? null;\n\n async function createForm() {\n const db = getDb();\n if (!db) return;\n const ref = await addDoc(collection(db, \"sites\", siteId, \"forms\"), {\n name: \"Nieuw formulier\",\n fields: [{ name: \"naam\", label: \"Naam\", type: \"text\", required: true }],\n submitLabel: \"Versturen\",\n successMessage: \"Bedankt! We nemen snel contact op.\",\n } satisfies FormDefinition);\n setSelected(ref.id);\n }\n\n return (\n <div className=\"grid gap-6\">\n <div className=\"grid gap-4 md:grid-cols-[260px_1fr]\">\n <Card className=\"h-fit\">\n <div className=\"mb-3 flex items-center justify-between\">\n <h3 className=\"text-sm font-semibold\">Formulieren</h3>\n <Button size=\"sm\" onClick={createForm}>+ Nieuw</Button>\n </div>\n <ul className=\"grid gap-1\">\n {forms.map((f) => (\n <li key={f.id}>\n <button\n onClick={() => setSelected(f.id)}\n className={`w-full truncate rounded-lg px-3 py-2 text-left text-sm ${\n f.id === selected ? \"bg-brand-50 text-brand-700\" : \"hover:bg-slate-100\"\n }`}\n >\n {f.name}\n <span className=\"ml-1 text-xs text-slate-400\">#{f.id.slice(0, 5)}</span>\n </button>\n </li>\n ))}\n {forms.length === 0 && <li className=\"px-3 py-2 text-sm text-slate-500\">Nog geen formulieren.</li>}\n </ul>\n </Card>\n {current ? (\n <FormDetail key={current.id} siteId={siteId} form={current} onDeleted={() => setSelected(null)} onToast={toast} />\n ) : (\n <Card>\n <p className=\"text-sm text-slate-500\">Selecteer of maak een formulier.</p>\n </Card>\n )}\n </div>\n\n <div>\n <h3 className=\"mb-1 text-base font-semibold\">E-mailteksten voor formulieren</h3>\n <p className=\"mb-3 text-sm text-slate-600\">\n Gelden voor álle formulieren: de notificatie naar jou en de bevestiging naar de invuller. De opmaak\n (huisstijl) en verzendprovider stel je site-breed in bij <strong>Instellingen → E-mail</strong>.\n </p>\n <MailTemplates siteId={siteId} keys={FORM_TEMPLATE_KEYS} />\n </div>\n </div>\n );\n}\n\nfunction FormDetail({\n siteId,\n form,\n onDeleted,\n onToast,\n}: {\n siteId: string;\n form: StoredForm;\n onDeleted: () => void;\n onToast: (t: { title: string; description?: string; tone?: \"default\" | \"success\" | \"error\" }) => void;\n}) {\n const [def, setDef] = useState<FormDefinition>(form);\n const confirm = useConfirm();\n\n async function save() {\n const db = getDb();\n if (!db) return;\n const parsed = FormSchema.safeParse(def);\n if (!parsed.success) {\n onToast({ title: \"Ongeldig formulier\", description: parsed.error.issues[0]?.message, tone: \"error\" });\n return;\n }\n await setDoc(doc(db, \"sites\", siteId, \"forms\", form.id), parsed.data, { merge: true });\n onToast({ title: \"Opgeslagen\", tone: \"success\" });\n }\n\n async function remove() {\n const db = getDb();\n if (!db) return;\n const ok = await confirm({ title: \"Formulier verwijderen?\", description: \"Het formulier wordt verwijderd. Inzendingen blijven bewaard.\", confirmLabel: \"Verwijderen\", tone: \"danger\" });\n if (!ok) return;\n await deleteDoc(doc(db, \"sites\", siteId, \"forms\", form.id));\n onDeleted();\n }\n\n function setField(i: number, part: Partial<FormField>) {\n const fields = [...def.fields];\n fields[i] = { ...fields[i], ...part };\n setDef({ ...def, fields });\n }\n\n return (\n <div className=\"grid gap-4\">\n <Card>\n <div className=\"mb-3 flex items-center justify-between\">\n <h3 className=\"text-base font-semibold\">Formulier bewerken</h3>\n <div className=\"flex gap-2\">\n <Button variant=\"danger\" size=\"sm\" onClick={remove}>Verwijderen</Button>\n <Button size=\"sm\" onClick={save}>Opslaan</Button>\n </div>\n </div>\n <div className=\"grid gap-3 md:grid-cols-2\">\n <Field label=\"Naam\"><Input value={def.name} onChange={(e) => setDef({ ...def, name: e.target.value })} /></Field>\n <Field label=\"Verstuurknop\"><Input value={def.submitLabel} onChange={(e) => setDef({ ...def, submitLabel: e.target.value })} /></Field>\n <Field label=\"Bedankbericht\"><Input value={def.successMessage} onChange={(e) => setDef({ ...def, successMessage: e.target.value })} /></Field>\n <Field label=\"Bedankt-pagina (optioneel)\" hint=\"Na verzenden doorsturen naar dit pad, bv. /bedankt\">\n <Input value={def.successRedirect ?? \"\"} onChange={(e) => setDef({ ...def, successRedirect: e.target.value || undefined })} />\n </Field>\n </div>\n\n <div className=\"mt-4 rounded-lg border border-slate-200 p-4\">\n <h4 className=\"text-sm font-semibold\">Wat gebeurt er bij een inzending?</h4>\n <p className=\"mb-3 mt-0.5 text-xs text-slate-500\">\n Elke inzending wordt altijd bewaard onder <strong>Inzendingen</strong>. Daarnaast kun je een kopie\n mailen naar jezelf en/of een bevestiging naar de invuller. De opmaak en verzendprovider stel je\n site-breed in bij <strong>Instellingen → E-mail</strong>.\n </p>\n <div className=\"grid gap-3 md:grid-cols-2\">\n <Field label=\"Kopie van inzending naar\" hint=\"Leeg = het notificatie-adres uit Instellingen → E-mail.\">\n <Input\n type=\"email\"\n value={def.notifyTo ?? \"\"}\n onChange={(e) => setDef({ ...def, notifyTo: e.target.value || undefined })}\n placeholder=\"jij@voorbeeld.nl\"\n />\n </Field>\n <label className=\"mt-6 flex h-9 items-center gap-2 text-sm\">\n <input\n type=\"checkbox\"\n checked={Boolean(def.confirmSubmitter)}\n onChange={(e) => setDef({ ...def, confirmSubmitter: e.target.checked })}\n />\n Stuur de invuller ook een bevestiging (naar het eerste e-mailveld)\n </label>\n </div>\n </div>\n <p className=\"mt-3 text-xs text-slate-500\">Formulier-id (voor het Formulier-blok): <code>{form.id}</code></p>\n </Card>\n\n <Card>\n <h4 className=\"mb-3 text-sm font-semibold\">Velden</h4>\n <div className=\"grid gap-3\">\n {def.fields.map((f, i) => (\n <div key={i} className=\"grid items-end gap-2 rounded-lg border border-slate-200 p-3 md:grid-cols-[1fr_1fr_140px_auto_auto]\">\n <Field label=\"Naam (sleutel)\"><Input value={f.name} onChange={(e) => setField(i, { name: e.target.value })} /></Field>\n <Field label=\"Label\"><Input value={f.label} onChange={(e) => setField(i, { label: e.target.value })} /></Field>\n <Field label=\"Type\">\n <Select value={f.type} onValueChange={(v) => setField(i, { type: v as FieldType })} options={FIELD_TYPES} />\n </Field>\n <Switch checked={f.required} onCheckedChange={(v) => setField(i, { required: v })} label=\"Verplicht\" />\n <Button variant=\"ghost\" size=\"sm\" onClick={() => setDef({ ...def, fields: def.fields.filter((_, idx) => idx !== i) })}>🗑</Button>\n {(f.type === \"select\" || f.type === \"radio\") && (\n <div className=\"md:col-span-5\">\n <Field label=\"Opties (komma-gescheiden)\">\n <Input\n value={(f.options ?? []).join(\", \")}\n onChange={(e) => setField(i, { options: e.target.value.split(\",\").map((s) => s.trim()).filter(Boolean) })}\n />\n </Field>\n </div>\n )}\n </div>\n ))}\n </div>\n <div className=\"mt-3\">\n <Button\n variant=\"secondary\"\n size=\"sm\"\n onClick={() => setDef({ ...def, fields: [...def.fields, { name: `veld${def.fields.length + 1}`, label: \"Nieuw veld\", type: \"text\", required: false }] })}\n >\n + Veld toevoegen\n </Button>\n </div>\n </Card>\n\n <Submissions siteId={siteId} formId={form.id} />\n </div>\n );\n}\n\nfunction Submissions({ siteId, formId }: { siteId: string; formId: string }) {\n const [subs, setSubs] = useState<{ id: string; values: Record<string, unknown>; createdAt?: unknown }[]>([]);\n useEffect(() => {\n const db = getDb();\n if (!db) return;\n const q = query(collection(db, \"sites\", siteId, \"forms\", formId, \"submissions\"), orderBy(\"createdAt\", \"desc\"));\n return onSnapshot(q, (snap) => setSubs(snap.docs.map((d) => ({ id: d.id, ...(d.data() as { values: Record<string, unknown> }) }))));\n }, [siteId, formId]);\n\n return (\n <Card>\n <h4 className=\"mb-3 text-sm font-semibold\">Inzendingen <Badge>{subs.length}</Badge></h4>\n {subs.length === 0 ? (\n <p className=\"text-sm text-slate-500\">Nog geen inzendingen.</p>\n ) : (\n <div className=\"grid gap-2\">\n {subs.map((s) => (\n <div key={s.id} className=\"rounded-lg border border-slate-200 p-3 text-sm\">\n {Object.entries(s.values ?? {}).map(([k, v]) => (\n <div key={k}>\n <span className=\"font-medium text-slate-600\">{k}:</span> {String(v)}\n </div>\n ))}\n </div>\n ))}\n </div>\n )}\n </Card>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
import { type MailTemplateKey } from "../../schema/mail";
|
|
2
|
+
type MailTemplatesProps = {
|
|
3
|
+
siteId: string;
|
|
4
|
+
/** Welke templates tonen — bv. FORM_TEMPLATE_KEYS in Formulieren, SHOP_TEMPLATE_KEYS in de shop. */
|
|
5
|
+
keys?: readonly MailTemplateKey[];
|
|
6
|
+
};
|
|
1
7
|
/**
|
|
2
8
|
* E-mailteksten — bewerkbare templates in `settings/mailTemplates` (site-breed, siteadmin). Een
|
|
3
9
|
* leeg veld gebruikt de ingebouwde standaardtekst, dus er gaat altijd een correcte mail uit. Je
|
|
4
10
|
* bewerkt platte tekst met {{invulvelden}}; de engine verpakt die in je huisstijl (kleur, logo,
|
|
5
11
|
* footer) en stuurt een nette HTML-mail. Shop-templates verschijnen alleen op sites met webwinkel.
|
|
6
12
|
*/
|
|
7
|
-
export declare function MailTemplates({ siteId }:
|
|
8
|
-
|
|
9
|
-
}): import("react").JSX.Element;
|
|
13
|
+
export declare function MailTemplates({ siteId, keys }: MailTemplatesProps): import("react").JSX.Element;
|
|
14
|
+
export {};
|
|
@@ -17,10 +17,9 @@ function callFunction(name) {
|
|
|
17
17
|
return res.data;
|
|
18
18
|
};
|
|
19
19
|
}
|
|
20
|
-
function MailTemplates({ siteId }) {
|
|
20
|
+
function MailTemplates({ siteId, keys }) {
|
|
21
21
|
const { toast } = useToast();
|
|
22
22
|
const [templates, setTemplates] = useState(null);
|
|
23
|
-
const [shopActive, setShopActive] = useState(false);
|
|
24
23
|
const [busy, setBusy] = useState(null);
|
|
25
24
|
useEffect(() => {
|
|
26
25
|
const db = getDb();
|
|
@@ -28,10 +27,6 @@ function MailTemplates({ siteId }) {
|
|
|
28
27
|
void getDoc(doc(db, "sites", siteId, "settings", "mailTemplates")).then(
|
|
29
28
|
(snap) => setTemplates(snap.data() ?? {})
|
|
30
29
|
);
|
|
31
|
-
void getDoc(doc(db, "sites", siteId, "settings", "public")).then((snap) => {
|
|
32
|
-
const entry = (snap.data()?.modules ?? {}).shop;
|
|
33
|
-
setShopActive(entry === true || typeof entry === "object" && entry !== null);
|
|
34
|
-
});
|
|
35
30
|
}, [siteId]);
|
|
36
31
|
async function save() {
|
|
37
32
|
const db = getDb();
|
|
@@ -61,7 +56,7 @@ function MailTemplates({ siteId }) {
|
|
|
61
56
|
}
|
|
62
57
|
}
|
|
63
58
|
if (!templates) return /* @__PURE__ */ jsx("p", { className: "text-sm text-slate-500", children: "E-mailteksten laden\u2026" });
|
|
64
|
-
const defs = MAIL_TEMPLATE_DEFS.filter((d) => d.
|
|
59
|
+
const defs = keys ? MAIL_TEMPLATE_DEFS.filter((d) => keys.includes(d.key)) : MAIL_TEMPLATE_DEFS;
|
|
65
60
|
return /* @__PURE__ */ jsxs("div", { className: "grid gap-4", children: [
|
|
66
61
|
/* @__PURE__ */ jsxs("p", { className: "text-sm text-slate-600", children: [
|
|
67
62
|
"Laat een veld leeg voor de standaardtekst. De opmaak (huisstijlkleur, logo, footer) zet de engine er automatisch omheen \u2014 je bewerkt alleen de tekst. Gebruik de ",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/settings/MailTemplates.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { doc, getDoc, setDoc } from \"firebase/firestore\";\nimport { getFunctions, httpsCallable } from \"firebase/functions\";\nimport { getDb, getFirebaseApp } from \"../../firebase/client\";\nimport { functionsRegion } from \"../../firebase/region\";\nimport { MAIL_TEMPLATE_DEFS, type MailTemplateKey, type MailTemplates as MailTemplatesData } from \"../../schema/mail\";\nimport { Button, Card, Field, Input, Textarea } from \"../ui/primitives\";\nimport { useToast } from \"../ui/toast\";\n\n/** Generieke callable-helper (niet shop-gebonden) — mail is een site-brede instelling. */\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/**\n * E-mailteksten — bewerkbare templates in `settings/mailTemplates` (site-breed, siteadmin). Een\n * leeg veld gebruikt de ingebouwde standaardtekst, dus er gaat altijd een correcte mail uit. Je\n * bewerkt platte tekst met {{invulvelden}}; de engine verpakt die in je huisstijl (kleur, logo,\n * footer) en stuurt een nette HTML-mail. Shop-templates verschijnen alleen op sites met webwinkel.\n */\nexport function MailTemplates({ siteId }:
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/settings/MailTemplates.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { doc, getDoc, setDoc } from \"firebase/firestore\";\nimport { getFunctions, httpsCallable } from \"firebase/functions\";\nimport { getDb, getFirebaseApp } from \"../../firebase/client\";\nimport { functionsRegion } from \"../../firebase/region\";\nimport { MAIL_TEMPLATE_DEFS, type MailTemplateKey, type MailTemplates as MailTemplatesData } from \"../../schema/mail\";\nimport { Button, Card, Field, Input, Textarea } from \"../ui/primitives\";\nimport { useToast } from \"../ui/toast\";\n\ntype MailTemplatesProps = {\n siteId: string;\n /** Welke templates tonen — bv. FORM_TEMPLATE_KEYS in Formulieren, SHOP_TEMPLATE_KEYS in de shop. */\n keys?: readonly MailTemplateKey[];\n};\n\n/** Generieke callable-helper (niet shop-gebonden) — mail is een site-brede instelling. */\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/**\n * E-mailteksten — bewerkbare templates in `settings/mailTemplates` (site-breed, siteadmin). Een\n * leeg veld gebruikt de ingebouwde standaardtekst, dus er gaat altijd een correcte mail uit. Je\n * bewerkt platte tekst met {{invulvelden}}; de engine verpakt die in je huisstijl (kleur, logo,\n * footer) en stuurt een nette HTML-mail. Shop-templates verschijnen alleen op sites met webwinkel.\n */\nexport function MailTemplates({ siteId, keys }: MailTemplatesProps) {\n const { toast } = useToast();\n const [templates, setTemplates] = useState<MailTemplatesData | null>(null);\n const [busy, setBusy] = useState<string | null>(null);\n\n useEffect(() => {\n const db = getDb();\n if (!db) return;\n void getDoc(doc(db, \"sites\", siteId, \"settings\", \"mailTemplates\")).then((snap) =>\n setTemplates((snap.data() as MailTemplatesData | undefined) ?? {}),\n );\n }, [siteId]);\n\n async function save() {\n const db = getDb();\n if (!db || !templates) return;\n setBusy(\"save\");\n try {\n await setDoc(doc(db, \"sites\", siteId, \"settings\", \"mailTemplates\"), templates);\n toast({ title: \"E-mailteksten opgeslagen\", tone: \"success\" });\n } catch (e) {\n toast({ title: \"Opslaan mislukt\", description: e instanceof Error ? e.message : undefined, tone: \"error\" });\n } finally {\n setBusy(null);\n }\n }\n\n async function preview(key: MailTemplateKey) {\n setBusy(key);\n try {\n const res = await callFunction<{ siteId: string; template: MailTemplateKey }, { to: string }>(\"sendTemplatePreview\")({\n siteId,\n template: key,\n });\n toast({ title: \"Voorbeeld verstuurd\", description: `Naar ${res.to} (met voorbeeldgegevens en je huisstijl).`, tone: \"success\" });\n } catch (e) {\n toast({ title: \"Voorbeeld mislukt\", description: e instanceof Error ? e.message : undefined, tone: \"error\" });\n } finally {\n setBusy(null);\n }\n }\n\n if (!templates) return <p className=\"text-sm text-slate-500\">E-mailteksten laden…</p>;\n\n const defs = keys ? MAIL_TEMPLATE_DEFS.filter((d) => keys.includes(d.key as MailTemplateKey)) : MAIL_TEMPLATE_DEFS;\n\n return (\n <div className=\"grid gap-4\">\n <p className=\"text-sm text-slate-600\">\n Laat een veld leeg voor de standaardtekst. De opmaak (huisstijlkleur, logo, footer) zet de engine er\n automatisch omheen — je bewerkt alleen de tekst. Gebruik de {\"{{invulvelden}}\"} die per e-mail staan vermeld.\n </p>\n\n {defs.map((def) => {\n const key = def.key as MailTemplateKey;\n const tpl = templates[key] ?? {};\n const set = (patch: { subject?: string; bodyText?: string }) =>\n setTemplates((p) => ({ ...p, [key]: { ...p?.[key], ...patch } }));\n return (\n <Card key={key}>\n <div className=\"mb-2 flex items-start justify-between gap-3\">\n <div>\n <h4 className=\"text-sm font-semibold\">{def.label}</h4>\n <p className=\"text-xs text-slate-500\">{def.hint}</p>\n </div>\n <Button size=\"sm\" variant=\"secondary\" disabled={busy !== null} onClick={() => preview(key)}>\n {busy === key ? \"Versturen…\" : \"Stuur voorbeeld naar mij\"}\n </Button>\n </div>\n <div className=\"grid gap-3\">\n <Field label=\"Onderwerp\" hint=\"Leeg = standaard\">\n <Input value={tpl.subject ?? \"\"} onChange={(e) => set({ subject: e.target.value })} />\n </Field>\n <Field label=\"Tekst\" hint=\"Leeg = standaard\">\n <Textarea rows={6} value={tpl.bodyText ?? \"\"} onChange={(e) => set({ bodyText: e.target.value })} />\n </Field>\n <p className=\"text-xs text-slate-500\">\n Invulvelden:{\" \"}\n <code className=\"rounded bg-slate-100 px-1 py-0.5 text-[11px]\">{def.placeholders}</code>\n </p>\n </div>\n </Card>\n );\n })}\n\n <div>\n <Button onClick={save} disabled={busy !== null}>\n {busy === \"save\" ? \"Opslaan…\" : \"E-mailteksten opslaan\"}\n </Button>\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -17,7 +17,6 @@ import { useConfirm } from "../ui/ConfirmDialog";
|
|
|
17
17
|
import { ModuleSettingsForm } from "./moduleSettingsForm";
|
|
18
18
|
import { SnapshotsCard } from "./SnapshotsCard";
|
|
19
19
|
import { MailSettings } from "./MailSettings";
|
|
20
|
-
import { MailTemplates } from "./MailTemplates";
|
|
21
20
|
function SettingsPanel({ siteId }) {
|
|
22
21
|
const { toast } = useToast();
|
|
23
22
|
const confirm = useConfirm();
|
|
@@ -260,9 +259,16 @@ function SettingsPanel({ siteId }) {
|
|
|
260
259
|
}) })
|
|
261
260
|
] }),
|
|
262
261
|
/* @__PURE__ */ jsx(MailSettings, { siteId }),
|
|
263
|
-
/* @__PURE__ */ jsxs(
|
|
264
|
-
/* @__PURE__ */ jsx("h3", { className: "mb-
|
|
265
|
-
/* @__PURE__ */
|
|
262
|
+
/* @__PURE__ */ jsxs(Card, { children: [
|
|
263
|
+
/* @__PURE__ */ jsx("h3", { className: "mb-1 text-base font-semibold", children: "E-mailteksten" }),
|
|
264
|
+
/* @__PURE__ */ jsxs("p", { className: "text-sm text-slate-600", children: [
|
|
265
|
+
"De teksten van automatische e-mails bewerk je bij het onderdeel zelf: formulier-mails onder",
|
|
266
|
+
" ",
|
|
267
|
+
/* @__PURE__ */ jsx("strong", { children: "Formulieren" }),
|
|
268
|
+
", webwinkel-mails onder ",
|
|
269
|
+
/* @__PURE__ */ jsx("strong", { children: "Webwinkel \u2192 E-mails" }),
|
|
270
|
+
". Provider en opmaak (logo, huisstijlkleur) gelden site-breed en stel je hier in."
|
|
271
|
+
] })
|
|
266
272
|
] }),
|
|
267
273
|
/* @__PURE__ */ jsxs(Card, { children: [
|
|
268
274
|
/* @__PURE__ */ jsx("h3", { className: "mb-1 text-base font-semibold", children: "Formulier-afzender" }),
|
|
@@ -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\";\nimport { MailSettings } from \"./MailSettings\";\nimport { MailTemplates } from \"./MailTemplates\";\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 <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.\">\n <Input value={theme.logo ?? \"\"} onChange={(e) => setS({ ...s, theme: { ...theme, logo: e.target.value } })} placeholder=\"https://…/logo.png\" />\n </Field>\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 {/* Bewerkbare e-mailteksten (site-breed; shop-templates alleen met webwinkel). */}\n <div>\n <h3 className=\"mb-2 text-base font-semibold\">E-mailteksten</h3>\n <MailTemplates siteId={siteId} />\n </div>\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 & 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 <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.\">\n <Input value={theme.logo ?? \"\"} onChange={(e) => setS({ ...s, theme: { ...theme, logo: e.target.value } })} placeholder=\"https://…/logo.png\" />\n </Field>\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 + opmaak (formulieren én shop). Zie docs/SHARED-SETTINGS.md. */}\n <MailSettings siteId={siteId} />\n\n <Card>\n <h3 className=\"mb-1 text-base font-semibold\">E-mailteksten</h3>\n <p className=\"text-sm text-slate-600\">\n De teksten van automatische e-mails bewerk je bij het onderdeel zelf: formulier-mails onder{\" \"}\n <strong>Formulieren</strong>, webwinkel-mails onder <strong>Webwinkel → E-mails</strong>. Provider en\n opmaak (logo, huisstijlkleur) gelden site-breed en stel je hier in.\n </p>\n </Card>\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 & 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,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Webwinkel-beheer: bestellingen, producten, categorieën, kortingscodes,
|
|
3
|
-
* koppelingen (betaalprovider Stripe/Mollie). De mailprovider én de
|
|
4
|
-
* site-breed en staan onder Instellingen → E-mail
|
|
2
|
+
* Webwinkel-beheer: bestellingen, producten, categorieën, kortingscodes, de webwinkel-e-mails,
|
|
3
|
+
* winkel-instellingen en koppelingen (betaalprovider Stripe/Mollie). De mailprovider én de opmaak
|
|
4
|
+
* (huisstijl) zijn site-breed en staan onder Instellingen → E-mail (zie docs/SHARED-SETTINGS.md).
|
|
5
5
|
*/
|
|
6
6
|
export declare function ShopPanel({ siteId }: {
|
|
7
7
|
siteId: string;
|
|
@@ -7,6 +7,8 @@ import { OrdersTab } from "./OrdersTab";
|
|
|
7
7
|
import { DiscountsTab } from "./DiscountsTab";
|
|
8
8
|
import { ShopSettingsTab } from "./ShopSettingsTab";
|
|
9
9
|
import { ConnectionsTab } from "./ConnectionsTab";
|
|
10
|
+
import { MailTemplates } from "../settings/MailTemplates";
|
|
11
|
+
import { SHOP_TEMPLATE_KEYS } from "../../schema/mail";
|
|
10
12
|
function ShopPanel({ siteId }) {
|
|
11
13
|
return /* @__PURE__ */ jsxs(Tabs, { defaultValue: "orders", children: [
|
|
12
14
|
/* @__PURE__ */ jsxs(TabsList, { children: [
|
|
@@ -14,6 +16,7 @@ function ShopPanel({ siteId }) {
|
|
|
14
16
|
/* @__PURE__ */ jsx(TabsTrigger, { value: "products", children: "Producten" }),
|
|
15
17
|
/* @__PURE__ */ jsx(TabsTrigger, { value: "categories", children: "Categorie\xEBn" }),
|
|
16
18
|
/* @__PURE__ */ jsx(TabsTrigger, { value: "discounts", children: "Kortingen" }),
|
|
19
|
+
/* @__PURE__ */ jsx(TabsTrigger, { value: "emails", children: "E-mails" }),
|
|
17
20
|
/* @__PURE__ */ jsx(TabsTrigger, { value: "settings", children: "Instellingen" }),
|
|
18
21
|
/* @__PURE__ */ jsx(TabsTrigger, { value: "connections", children: "Koppelingen" })
|
|
19
22
|
] }),
|
|
@@ -21,6 +24,7 @@ function ShopPanel({ siteId }) {
|
|
|
21
24
|
/* @__PURE__ */ jsx(TabsContent, { value: "products", children: /* @__PURE__ */ jsx(ProductsTab, { siteId }) }),
|
|
22
25
|
/* @__PURE__ */ jsx(TabsContent, { value: "categories", children: /* @__PURE__ */ jsx(CategoriesTab, { siteId }) }),
|
|
23
26
|
/* @__PURE__ */ jsx(TabsContent, { value: "discounts", children: /* @__PURE__ */ jsx(DiscountsTab, { siteId }) }),
|
|
27
|
+
/* @__PURE__ */ jsx(TabsContent, { value: "emails", children: /* @__PURE__ */ jsx(MailTemplates, { siteId, keys: SHOP_TEMPLATE_KEYS }) }),
|
|
24
28
|
/* @__PURE__ */ jsx(TabsContent, { value: "settings", children: /* @__PURE__ */ jsx(ShopSettingsTab, { siteId }) }),
|
|
25
29
|
/* @__PURE__ */ jsx(TabsContent, { value: "connections", children: /* @__PURE__ */ jsx(ConnectionsTab, { siteId }) })
|
|
26
30
|
] });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/shop/ShopPanel.tsx"],"sourcesContent":["\"use client\";\n\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from \"../ui/tabs\";\nimport { ProductsTab } from \"./ProductsTab\";\nimport { CategoriesTab } from \"./CategoriesTab\";\nimport { OrdersTab } from \"./OrdersTab\";\nimport { DiscountsTab } from \"./DiscountsTab\";\nimport { ShopSettingsTab } from \"./ShopSettingsTab\";\nimport { ConnectionsTab } from \"./ConnectionsTab\";\n\n/**\n * Webwinkel-beheer: bestellingen, producten, categorieën, kortingscodes, winkel-instellingen en
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/shop/ShopPanel.tsx"],"sourcesContent":["\"use client\";\n\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from \"../ui/tabs\";\nimport { ProductsTab } from \"./ProductsTab\";\nimport { CategoriesTab } from \"./CategoriesTab\";\nimport { OrdersTab } from \"./OrdersTab\";\nimport { DiscountsTab } from \"./DiscountsTab\";\nimport { ShopSettingsTab } from \"./ShopSettingsTab\";\nimport { ConnectionsTab } from \"./ConnectionsTab\";\nimport { MailTemplates } from \"../settings/MailTemplates\";\nimport { SHOP_TEMPLATE_KEYS } from \"../../schema/mail\";\n\n/**\n * Webwinkel-beheer: bestellingen, producten, categorieën, kortingscodes, de webwinkel-e-mails,\n * winkel-instellingen en koppelingen (betaalprovider Stripe/Mollie). De mailprovider én de opmaak\n * (huisstijl) zijn site-breed en staan onder Instellingen → E-mail (zie docs/SHARED-SETTINGS.md).\n */\nexport function ShopPanel({ siteId }: { siteId: string }) {\n return (\n <Tabs defaultValue=\"orders\">\n <TabsList>\n <TabsTrigger value=\"orders\">Bestellingen</TabsTrigger>\n <TabsTrigger value=\"products\">Producten</TabsTrigger>\n <TabsTrigger value=\"categories\">Categorieën</TabsTrigger>\n <TabsTrigger value=\"discounts\">Kortingen</TabsTrigger>\n <TabsTrigger value=\"emails\">E-mails</TabsTrigger>\n <TabsTrigger value=\"settings\">Instellingen</TabsTrigger>\n <TabsTrigger value=\"connections\">Koppelingen</TabsTrigger>\n </TabsList>\n <TabsContent value=\"orders\">\n <OrdersTab siteId={siteId} />\n </TabsContent>\n <TabsContent value=\"products\">\n <ProductsTab siteId={siteId} />\n </TabsContent>\n <TabsContent value=\"categories\">\n <CategoriesTab siteId={siteId} />\n </TabsContent>\n <TabsContent value=\"discounts\">\n <DiscountsTab siteId={siteId} />\n </TabsContent>\n <TabsContent value=\"emails\">\n <MailTemplates siteId={siteId} keys={SHOP_TEMPLATE_KEYS} />\n </TabsContent>\n <TabsContent value=\"settings\">\n <ShopSettingsTab siteId={siteId} />\n </TabsContent>\n <TabsContent value=\"connections\">\n <ConnectionsTab siteId={siteId} />\n </TabsContent>\n </Tabs>\n );\n}\n"],"mappings":";AAoBM,SACE,KADF;AAlBN,SAAS,MAAM,aAAa,UAAU,mBAAmB;AACzD,SAAS,mBAAmB;AAC5B,SAAS,qBAAqB;AAC9B,SAAS,iBAAiB;AAC1B,SAAS,oBAAoB;AAC7B,SAAS,uBAAuB;AAChC,SAAS,sBAAsB;AAC/B,SAAS,qBAAqB;AAC9B,SAAS,0BAA0B;AAO5B,SAAS,UAAU,EAAE,OAAO,GAAuB;AACxD,SACE,qBAAC,QAAK,cAAa,UACjB;AAAA,yBAAC,YACC;AAAA,0BAAC,eAAY,OAAM,UAAS,0BAAY;AAAA,MACxC,oBAAC,eAAY,OAAM,YAAW,uBAAS;AAAA,MACvC,oBAAC,eAAY,OAAM,cAAa,4BAAW;AAAA,MAC3C,oBAAC,eAAY,OAAM,aAAY,uBAAS;AAAA,MACxC,oBAAC,eAAY,OAAM,UAAS,qBAAO;AAAA,MACnC,oBAAC,eAAY,OAAM,YAAW,0BAAY;AAAA,MAC1C,oBAAC,eAAY,OAAM,eAAc,yBAAW;AAAA,OAC9C;AAAA,IACA,oBAAC,eAAY,OAAM,UACjB,8BAAC,aAAU,QAAgB,GAC7B;AAAA,IACA,oBAAC,eAAY,OAAM,YACjB,8BAAC,eAAY,QAAgB,GAC/B;AAAA,IACA,oBAAC,eAAY,OAAM,cACjB,8BAAC,iBAAc,QAAgB,GACjC;AAAA,IACA,oBAAC,eAAY,OAAM,aACjB,8BAAC,gBAAa,QAAgB,GAChC;AAAA,IACA,oBAAC,eAAY,OAAM,UACjB,8BAAC,iBAAc,QAAgB,MAAM,oBAAoB,GAC3D;AAAA,IACA,oBAAC,eAAY,OAAM,YACjB,8BAAC,mBAAgB,QAAgB,GACnC;AAAA,IACA,oBAAC,eAAY,OAAM,eACjB,8BAAC,kBAAe,QAAgB,GAClC;AAAA,KACF;AAEJ;","names":[]}
|
package/dist/admin.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.xicms-admin .visible{visibility:visible}.xicms-admin .static{position:static}.xicms-admin .fixed{position:fixed}.xicms-admin .absolute{position:absolute}.xicms-admin .relative{position:relative}.xicms-admin .sticky{position:sticky}.xicms-admin .inset-0{inset:0}.xicms-admin .-right-2{right:-.5rem}.xicms-admin .-top-2{top:-.5rem}.xicms-admin .z-\[10010\]{z-index:10010}.xicms-admin .mb-1{margin-bottom:.25rem}.xicms-admin .mb-2{margin-bottom:.5rem}.xicms-admin .mb-3{margin-bottom:.75rem}.xicms-admin .ml-1{margin-left:.25rem}.xicms-admin .ml-auto{margin-left:auto}.xicms-admin .mr-1{margin-right:.25rem}.xicms-admin .mr-2{margin-right:.5rem}.xicms-admin .mt-1{margin-top:.25rem}.xicms-admin .mt-2{margin-top:.5rem}.xicms-admin .mt-3{margin-top:.75rem}.xicms-admin .mt-4{margin-top:1rem}.xicms-admin .mt-5{margin-top:1.25rem}.xicms-admin .\!block{display:block!important}.xicms-admin .block{display:block}.xicms-admin .inline{display:inline}.xicms-admin .flex{display:flex}.xicms-admin .inline-flex{display:inline-flex}.xicms-admin .table{display:table}.xicms-admin .grid{display:grid}.xicms-admin .hidden{display:none}.xicms-admin .aspect-square{aspect-ratio:1/1}.xicms-admin .h-10{height:2.5rem}.xicms-admin .h-14{height:3.5rem}.xicms-admin .h-16{height:4rem}.xicms-admin .h-28{height:7rem}.xicms-admin .h-32{height:8rem}.xicms-admin .h-5{height:1.25rem}.xicms-admin .h-6{height:1.5rem}.xicms-admin .h-8{height:2rem}.xicms-admin .h-9{height:2.25rem}.xicms-admin .h-\[70vh\]{height:70vh}.xicms-admin .h-fit{height:-moz-fit-content;height:fit-content}.xicms-admin .max-h-\[55vh\]{max-height:55vh}.xicms-admin .max-h-\[60vh\]{max-height:60vh}.xicms-admin .min-h-\[8rem\]{min-height:8rem}.xicms-admin .w-14{width:3.5rem}.xicms-admin .w-16{width:4rem}.xicms-admin .w-20{width:5rem}.xicms-admin .w-24{width:6rem}.xicms-admin .w-28{width:7rem}.xicms-admin .w-32{width:8rem}.xicms-admin .w-36{width:9rem}.xicms-admin .w-5{width:1.25rem}.xicms-admin .w-56{width:14rem}.xicms-admin .w-6{width:1.5rem}.xicms-admin .w-8{width:2rem}.xicms-admin .w-fit{width:-moz-fit-content;width:fit-content}.xicms-admin .w-full{width:100%}.xicms-admin .min-w-0{min-width:0}.xicms-admin .min-w-8{min-width:2rem}.xicms-admin .max-w-2xl{max-width:42rem}.xicms-admin .max-w-3xl{max-width:48rem}.xicms-admin .max-w-xl{max-width:36rem}.xicms-admin .max-w-xs{max-width:20rem}.xicms-admin .flex-1{flex:1 1 0%}.xicms-admin .shrink-0{flex-shrink:0}.xicms-admin .-translate-x-1\/2{--tw-translate-x:-50%}.xicms-admin .-translate-x-1\/2,.xicms-admin .transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.xicms-admin .cursor-pointer{cursor:pointer}.xicms-admin .resize{resize:both}.xicms-admin .list-decimal{list-style-type:decimal}.xicms-admin .list-disc{list-style-type:disc}.xicms-admin .grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.xicms-admin .grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.xicms-admin .grid-cols-\[120px_1fr\]{grid-template-columns:120px 1fr}.xicms-admin .flex-col{flex-direction:column}.xicms-admin .flex-wrap{flex-wrap:wrap}.xicms-admin .place-items-center{place-items:center}.xicms-admin .items-start{align-items:flex-start}.xicms-admin .items-end{align-items:flex-end}.xicms-admin .items-center{align-items:center}.xicms-admin .justify-end{justify-content:flex-end}.xicms-admin .justify-center{justify-content:center}.xicms-admin .justify-between{justify-content:space-between}.xicms-admin .gap-0\.5{gap:.125rem}.xicms-admin .gap-1{gap:.25rem}.xicms-admin .gap-1\.5{gap:.375rem}.xicms-admin .gap-2{gap:.5rem}.xicms-admin .gap-3{gap:.75rem}.xicms-admin .gap-4{gap:1rem}.xicms-admin .gap-6{gap:1.5rem}.xicms-admin .self-end{align-self:flex-end}.xicms-admin .self-center{align-self:center}.xicms-admin .overflow-auto{overflow:auto}.xicms-admin .overflow-hidden{overflow:hidden}.xicms-admin .overflow-x-auto{overflow-x:auto}.xicms-admin .overflow-y-auto{overflow-y:auto}.xicms-admin .truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.xicms-admin .whitespace-pre-wrap{white-space:pre-wrap}.xicms-admin .rounded{border-radius:.25rem}.xicms-admin .rounded-full{border-radius:9999px}.xicms-admin .rounded-lg{border-radius:.5rem}.xicms-admin .rounded-md{border-radius:.375rem}.xicms-admin .rounded-xl{border-radius:.75rem}.xicms-admin .rounded-b-lg{border-bottom-right-radius:.5rem;border-bottom-left-radius:.5rem}.xicms-admin .rounded-t{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.xicms-admin .rounded-t-lg{border-top-left-radius:.5rem;border-top-right-radius:.5rem}.xicms-admin .border{border-width:1px}.xicms-admin .border-b{border-bottom-width:1px}.xicms-admin .border-t{border-top-width:1px}.xicms-admin .border-t-0{border-top-width:0}.xicms-admin .border-amber-300{--tw-border-opacity:1;border-color:rgb(252 211 77/var(--tw-border-opacity,1))}.xicms-admin .border-brand-500{border-color:var(--xicms-primary,#4f46e5)}.xicms-admin .border-red-200{--tw-border-opacity:1;border-color:rgb(254 202 202/var(--tw-border-opacity,1))}.xicms-admin .border-slate-100{--tw-border-opacity:1;border-color:rgb(241 245 249/var(--tw-border-opacity,1))}.xicms-admin .border-slate-200{--tw-border-opacity:1;border-color:rgb(226 232 240/var(--tw-border-opacity,1))}.xicms-admin .border-slate-300{--tw-border-opacity:1;border-color:rgb(203 213 225/var(--tw-border-opacity,1))}.xicms-admin .bg-amber-50{--tw-bg-opacity:1;background-color:rgb(255 251 235/var(--tw-bg-opacity,1))}.xicms-admin .bg-black\/50{background-color:rgba(0,0,0,.5)}.xicms-admin .bg-brand-50{background-color:color-mix(in srgb,var(--xicms-primary,#4f46e5) 12%,#fff)}.xicms-admin .bg-brand-500{background-color:var(--xicms-primary,#4f46e5)}.xicms-admin .bg-red-50{--tw-bg-opacity:1;background-color:rgb(254 242 242/var(--tw-bg-opacity,1))}.xicms-admin .bg-slate-100{--tw-bg-opacity:1;background-color:rgb(241 245 249/var(--tw-bg-opacity,1))}.xicms-admin .bg-slate-200{--tw-bg-opacity:1;background-color:rgb(226 232 240/var(--tw-bg-opacity,1))}.xicms-admin .bg-slate-50{--tw-bg-opacity:1;background-color:rgb(248 250 252/var(--tw-bg-opacity,1))}.xicms-admin .bg-slate-700{--tw-bg-opacity:1;background-color:rgb(51 65 85/var(--tw-bg-opacity,1))}.xicms-admin .bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1))}.xicms-admin .object-cover{-o-object-fit:cover;object-fit:cover}.xicms-admin .p-0\.5{padding:.125rem}.xicms-admin .p-1{padding:.25rem}.xicms-admin .p-2{padding:.5rem}.xicms-admin .p-3{padding:.75rem}.xicms-admin .p-4{padding:1rem}.xicms-admin .p-5{padding:1.25rem}.xicms-admin .p-6{padding:1.5rem}.xicms-admin .px-1{padding-left:.25rem;padding-right:.25rem}.xicms-admin .px-2{padding-left:.5rem;padding-right:.5rem}.xicms-admin .px-3{padding-left:.75rem;padding-right:.75rem}.xicms-admin .px-4{padding-left:1rem;padding-right:1rem}.xicms-admin .py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.xicms-admin .py-1{padding-top:.25rem;padding-bottom:.25rem}.xicms-admin .py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.xicms-admin .py-2{padding-top:.5rem;padding-bottom:.5rem}.xicms-admin .py-3{padding-top:.75rem;padding-bottom:.75rem}.xicms-admin .py-8{padding-top:2rem;padding-bottom:2rem}.xicms-admin .pl-5{padding-left:1.25rem}.xicms-admin .pr-2{padding-right:.5rem}.xicms-admin .pt-4{padding-top:1rem}.xicms-admin .pt-5{padding-top:1.25rem}.xicms-admin .text-left{text-align:left}.xicms-admin .text-center{text-align:center}.xicms-admin .text-right{text-align:right}.xicms-admin .align-top{vertical-align:top}.xicms-admin .font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.xicms-admin .text-3xl{font-size:1.875rem;line-height:2.25rem}.xicms-admin .text-\[11px\]{font-size:11px}.xicms-admin .text-base{font-size:1rem;line-height:1.5rem}.xicms-admin .text-lg{font-size:1.125rem;line-height:1.75rem}.xicms-admin .text-sm{font-size:.875rem;line-height:1.25rem}.xicms-admin .text-xl{font-size:1.25rem;line-height:1.75rem}.xicms-admin .text-xs{font-size:.75rem;line-height:1rem}.xicms-admin .font-bold{font-weight:700}.xicms-admin .font-medium{font-weight:500}.xicms-admin .font-semibold{font-weight:600}.xicms-admin .uppercase{text-transform:uppercase}.xicms-admin .italic{font-style:italic}.xicms-admin .tracking-wide{letter-spacing:.025em}.xicms-admin .text-amber-600{--tw-text-opacity:1;color:rgb(217 119 6/var(--tw-text-opacity,1))}.xicms-admin .text-amber-700{--tw-text-opacity:1;color:rgb(180 83 9/var(--tw-text-opacity,1))}.xicms-admin .text-amber-800{--tw-text-opacity:1;color:rgb(146 64 14/var(--tw-text-opacity,1))}.xicms-admin .text-brand-700{color:color-mix(in srgb,var(--xicms-primary,#4f46e5) 82%,#000)}.xicms-admin .text-red-600{--tw-text-opacity:1;color:rgb(220 38 38/var(--tw-text-opacity,1))}.xicms-admin .text-red-700{--tw-text-opacity:1;color:rgb(185 28 28/var(--tw-text-opacity,1))}.xicms-admin .text-slate-400{--tw-text-opacity:1;color:rgb(148 163 184/var(--tw-text-opacity,1))}.xicms-admin .text-slate-500{--tw-text-opacity:1;color:rgb(100 116 139/var(--tw-text-opacity,1))}.xicms-admin .text-slate-600{--tw-text-opacity:1;color:rgb(71 85 105/var(--tw-text-opacity,1))}.xicms-admin .text-slate-700{--tw-text-opacity:1;color:rgb(51 65 85/var(--tw-text-opacity,1))}.xicms-admin .text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.xicms-admin .underline{text-decoration-line:underline}.xicms-admin .opacity-70{opacity:.7}.xicms-admin .shadow-sm{--tw-shadow:0 1px 2px 0 rgba(0,0,0,.05);--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color)}.xicms-admin .shadow-sm,.xicms-admin .shadow-xl{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.xicms-admin .shadow-xl{--tw-shadow:0 20px 25px -5px rgba(0,0,0,.1),0 8px 10px -6px rgba(0,0,0,.1);--tw-shadow-colored:0 20px 25px -5px var(--tw-shadow-color),0 8px 10px -6px var(--tw-shadow-color)}.xicms-admin .outline-none{outline:2px solid transparent;outline-offset:2px}.xicms-admin .outline{outline-style:solid}.xicms-admin .blur{--tw-blur:blur(8px)}.xicms-admin .blur,.xicms-admin .filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.xicms-admin .transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.xicms-admin .transition-\[filter\]{transition-property:filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.xicms-admin,.xicms-admin *,.xicms-admin :after,.xicms-admin :before{box-sizing:border-box}.xicms-admin{font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif;color:#0f172a;line-height:1.5;-webkit-font-smoothing:antialiased;color-scheme:light;background:#fff;--color-background-body:#fff;--color-background-surface:#fff;--color-background-card:#fff;--color-background-muted:#f1f5f9;--color-text-primary:#0f172a;--color-text-secondary:#475569;--color-border:#e2e8f0;--color-accent:#4f46e5;--color-on-accent:#fff;--color-error:#dc2626}.xicms-admin figure,.xicms-admin h1,.xicms-admin h2,.xicms-admin h3,.xicms-admin p{margin:0}.xicms-admin button{font:inherit;cursor:pointer}.xicms-admin input,.xicms-admin select,.xicms-admin textarea{font:inherit;color:inherit}.xicms-admin .ProseMirror{min-height:8rem;outline:none}.xicms-admin .ProseMirror p{margin:0 0 .6rem}.xicms-admin .ProseMirror:focus{outline:none}@media (prefers-reduced-motion:reduce){.xicms-admin *,.xicms-admin :after,.xicms-admin :before,.xicms-ve *,.xicms-ve :after,.xicms-ve :before{animation-duration:.001ms!important;animation-iteration-count:1!important;transition-duration:.001ms!important}}.xicms-admin .last\:border-0:last-child{border-width:0}.xicms-admin .hover\:border-brand-500:hover{border-color:var(--xicms-primary,#4f46e5)}.xicms-admin .hover\:bg-slate-100:hover{--tw-bg-opacity:1;background-color:rgb(241 245 249/var(--tw-bg-opacity,1))}.xicms-admin .hover\:bg-slate-50:hover{--tw-bg-opacity:1;background-color:rgb(248 250 252/var(--tw-bg-opacity,1))}.xicms-admin .hover\:text-slate-700:hover{--tw-text-opacity:1;color:rgb(51 65 85/var(--tw-text-opacity,1))}.xicms-admin .hover\:text-slate-900:hover{--tw-text-opacity:1;color:rgb(15 23 42/var(--tw-text-opacity,1))}.xicms-admin .hover\:underline:hover{text-decoration-line:underline}.xicms-admin .hover\:brightness-95:hover{--tw-brightness:brightness(.95);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.xicms-admin .focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.xicms-admin .focus\:ring-2:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.xicms-admin .focus\:ring-indigo-500:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(99 102 241/var(--tw-ring-opacity,1))}.xicms-admin .focus-visible\:outline-none:focus-visible{outline:2px solid transparent;outline-offset:2px}.xicms-admin .focus-visible\:ring-2:focus-visible{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.xicms-admin .focus-visible\:ring-brand-500:focus-visible{--tw-ring-color:var(--xicms-primary,#4f46e5)}.xicms-admin .disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.xicms-admin .disabled\:opacity-50:disabled{opacity:.5}.xicms-admin :is(.group:hover .group-hover\:opacity-80){opacity:.8}.xicms-admin .data-\[state\=active\]\:border-b-2[data-state=active]{border-bottom-width:2px}.xicms-admin .data-\[state\=active\]\:border-brand-600[data-state=active]{border-color:var(--xicms-primary,#4f46e5)}.xicms-admin .data-\[state\=active\]\:text-brand-700[data-state=active]{color:color-mix(in srgb,var(--xicms-primary,#4f46e5) 82%,#000)}@media (min-width:640px){.xicms-admin .sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.xicms-admin .sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.xicms-admin .sm\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}@media (min-width:768px){.xicms-admin .md\:col-span-5{grid-column:span 5/span 5}.xicms-admin .md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.xicms-admin .md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.xicms-admin .md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.xicms-admin .md\:grid-cols-\[1fr_110px\]{grid-template-columns:1fr 110px}.xicms-admin .md\:grid-cols-\[1fr_130px_90px_110px_auto\]{grid-template-columns:1fr 130px 90px 110px auto}.xicms-admin .md\:grid-cols-\[1fr_1fr_140px_auto_auto\]{grid-template-columns:1fr 1fr 140px auto auto}.xicms-admin .md\:grid-cols-\[260px_1fr\]{grid-template-columns:260px 1fr}.xicms-admin .md\:grid-cols-\[280px_1fr\]{grid-template-columns:280px 1fr}.xicms-admin .md\:grid-cols-\[300px_1fr\]{grid-template-columns:300px 1fr}.xicms-admin .md\:grid-cols-\[340px_1fr\]{grid-template-columns:340px 1fr}}@media (min-width:1024px){.xicms-admin .lg\:col-span-2{grid-column:span 2/span 2}.xicms-admin .lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.xicms-admin .lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}
|
|
1
|
+
.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.xicms-admin .visible{visibility:visible}.xicms-admin .static{position:static}.xicms-admin .fixed{position:fixed}.xicms-admin .absolute{position:absolute}.xicms-admin .relative{position:relative}.xicms-admin .sticky{position:sticky}.xicms-admin .inset-0{inset:0}.xicms-admin .-right-2{right:-.5rem}.xicms-admin .-top-2{top:-.5rem}.xicms-admin .z-\[10010\]{z-index:10010}.xicms-admin .mb-1{margin-bottom:.25rem}.xicms-admin .mb-2{margin-bottom:.5rem}.xicms-admin .mb-3{margin-bottom:.75rem}.xicms-admin .ml-1{margin-left:.25rem}.xicms-admin .ml-auto{margin-left:auto}.xicms-admin .mr-1{margin-right:.25rem}.xicms-admin .mr-2{margin-right:.5rem}.xicms-admin .mt-0\.5{margin-top:.125rem}.xicms-admin .mt-1{margin-top:.25rem}.xicms-admin .mt-2{margin-top:.5rem}.xicms-admin .mt-3{margin-top:.75rem}.xicms-admin .mt-4{margin-top:1rem}.xicms-admin .mt-5{margin-top:1.25rem}.xicms-admin .mt-6{margin-top:1.5rem}.xicms-admin .\!block{display:block!important}.xicms-admin .block{display:block}.xicms-admin .inline{display:inline}.xicms-admin .flex{display:flex}.xicms-admin .inline-flex{display:inline-flex}.xicms-admin .table{display:table}.xicms-admin .grid{display:grid}.xicms-admin .hidden{display:none}.xicms-admin .aspect-square{aspect-ratio:1/1}.xicms-admin .h-10{height:2.5rem}.xicms-admin .h-14{height:3.5rem}.xicms-admin .h-16{height:4rem}.xicms-admin .h-28{height:7rem}.xicms-admin .h-32{height:8rem}.xicms-admin .h-5{height:1.25rem}.xicms-admin .h-6{height:1.5rem}.xicms-admin .h-8{height:2rem}.xicms-admin .h-9{height:2.25rem}.xicms-admin .h-\[70vh\]{height:70vh}.xicms-admin .h-fit{height:-moz-fit-content;height:fit-content}.xicms-admin .max-h-\[55vh\]{max-height:55vh}.xicms-admin .max-h-\[60vh\]{max-height:60vh}.xicms-admin .min-h-\[8rem\]{min-height:8rem}.xicms-admin .w-14{width:3.5rem}.xicms-admin .w-16{width:4rem}.xicms-admin .w-20{width:5rem}.xicms-admin .w-24{width:6rem}.xicms-admin .w-28{width:7rem}.xicms-admin .w-32{width:8rem}.xicms-admin .w-36{width:9rem}.xicms-admin .w-5{width:1.25rem}.xicms-admin .w-56{width:14rem}.xicms-admin .w-6{width:1.5rem}.xicms-admin .w-8{width:2rem}.xicms-admin .w-fit{width:-moz-fit-content;width:fit-content}.xicms-admin .w-full{width:100%}.xicms-admin .min-w-0{min-width:0}.xicms-admin .min-w-8{min-width:2rem}.xicms-admin .max-w-2xl{max-width:42rem}.xicms-admin .max-w-3xl{max-width:48rem}.xicms-admin .max-w-xl{max-width:36rem}.xicms-admin .max-w-xs{max-width:20rem}.xicms-admin .flex-1{flex:1 1 0%}.xicms-admin .shrink-0{flex-shrink:0}.xicms-admin .-translate-x-1\/2{--tw-translate-x:-50%}.xicms-admin .-translate-x-1\/2,.xicms-admin .transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.xicms-admin .cursor-pointer{cursor:pointer}.xicms-admin .resize{resize:both}.xicms-admin .list-decimal{list-style-type:decimal}.xicms-admin .list-disc{list-style-type:disc}.xicms-admin .grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.xicms-admin .grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.xicms-admin .grid-cols-\[120px_1fr\]{grid-template-columns:120px 1fr}.xicms-admin .flex-col{flex-direction:column}.xicms-admin .flex-wrap{flex-wrap:wrap}.xicms-admin .place-items-center{place-items:center}.xicms-admin .items-start{align-items:flex-start}.xicms-admin .items-end{align-items:flex-end}.xicms-admin .items-center{align-items:center}.xicms-admin .justify-end{justify-content:flex-end}.xicms-admin .justify-center{justify-content:center}.xicms-admin .justify-between{justify-content:space-between}.xicms-admin .gap-0\.5{gap:.125rem}.xicms-admin .gap-1{gap:.25rem}.xicms-admin .gap-1\.5{gap:.375rem}.xicms-admin .gap-2{gap:.5rem}.xicms-admin .gap-3{gap:.75rem}.xicms-admin .gap-4{gap:1rem}.xicms-admin .gap-6{gap:1.5rem}.xicms-admin .self-end{align-self:flex-end}.xicms-admin .self-center{align-self:center}.xicms-admin .overflow-auto{overflow:auto}.xicms-admin .overflow-hidden{overflow:hidden}.xicms-admin .overflow-x-auto{overflow-x:auto}.xicms-admin .overflow-y-auto{overflow-y:auto}.xicms-admin .truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.xicms-admin .whitespace-pre-wrap{white-space:pre-wrap}.xicms-admin .rounded{border-radius:.25rem}.xicms-admin .rounded-full{border-radius:9999px}.xicms-admin .rounded-lg{border-radius:.5rem}.xicms-admin .rounded-md{border-radius:.375rem}.xicms-admin .rounded-xl{border-radius:.75rem}.xicms-admin .rounded-b-lg{border-bottom-right-radius:.5rem;border-bottom-left-radius:.5rem}.xicms-admin .rounded-t{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.xicms-admin .rounded-t-lg{border-top-left-radius:.5rem;border-top-right-radius:.5rem}.xicms-admin .border{border-width:1px}.xicms-admin .border-b{border-bottom-width:1px}.xicms-admin .border-t{border-top-width:1px}.xicms-admin .border-t-0{border-top-width:0}.xicms-admin .border-amber-300{--tw-border-opacity:1;border-color:rgb(252 211 77/var(--tw-border-opacity,1))}.xicms-admin .border-brand-500{border-color:var(--xicms-primary,#4f46e5)}.xicms-admin .border-red-200{--tw-border-opacity:1;border-color:rgb(254 202 202/var(--tw-border-opacity,1))}.xicms-admin .border-slate-100{--tw-border-opacity:1;border-color:rgb(241 245 249/var(--tw-border-opacity,1))}.xicms-admin .border-slate-200{--tw-border-opacity:1;border-color:rgb(226 232 240/var(--tw-border-opacity,1))}.xicms-admin .border-slate-300{--tw-border-opacity:1;border-color:rgb(203 213 225/var(--tw-border-opacity,1))}.xicms-admin .bg-amber-50{--tw-bg-opacity:1;background-color:rgb(255 251 235/var(--tw-bg-opacity,1))}.xicms-admin .bg-black\/50{background-color:rgba(0,0,0,.5)}.xicms-admin .bg-brand-50{background-color:color-mix(in srgb,var(--xicms-primary,#4f46e5) 12%,#fff)}.xicms-admin .bg-brand-500{background-color:var(--xicms-primary,#4f46e5)}.xicms-admin .bg-red-50{--tw-bg-opacity:1;background-color:rgb(254 242 242/var(--tw-bg-opacity,1))}.xicms-admin .bg-slate-100{--tw-bg-opacity:1;background-color:rgb(241 245 249/var(--tw-bg-opacity,1))}.xicms-admin .bg-slate-200{--tw-bg-opacity:1;background-color:rgb(226 232 240/var(--tw-bg-opacity,1))}.xicms-admin .bg-slate-50{--tw-bg-opacity:1;background-color:rgb(248 250 252/var(--tw-bg-opacity,1))}.xicms-admin .bg-slate-700{--tw-bg-opacity:1;background-color:rgb(51 65 85/var(--tw-bg-opacity,1))}.xicms-admin .bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1))}.xicms-admin .object-cover{-o-object-fit:cover;object-fit:cover}.xicms-admin .p-0\.5{padding:.125rem}.xicms-admin .p-1{padding:.25rem}.xicms-admin .p-2{padding:.5rem}.xicms-admin .p-3{padding:.75rem}.xicms-admin .p-4{padding:1rem}.xicms-admin .p-5{padding:1.25rem}.xicms-admin .p-6{padding:1.5rem}.xicms-admin .px-1{padding-left:.25rem;padding-right:.25rem}.xicms-admin .px-2{padding-left:.5rem;padding-right:.5rem}.xicms-admin .px-3{padding-left:.75rem;padding-right:.75rem}.xicms-admin .px-4{padding-left:1rem;padding-right:1rem}.xicms-admin .py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.xicms-admin .py-1{padding-top:.25rem;padding-bottom:.25rem}.xicms-admin .py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.xicms-admin .py-2{padding-top:.5rem;padding-bottom:.5rem}.xicms-admin .py-3{padding-top:.75rem;padding-bottom:.75rem}.xicms-admin .py-8{padding-top:2rem;padding-bottom:2rem}.xicms-admin .pl-5{padding-left:1.25rem}.xicms-admin .pr-2{padding-right:.5rem}.xicms-admin .pt-4{padding-top:1rem}.xicms-admin .pt-5{padding-top:1.25rem}.xicms-admin .text-left{text-align:left}.xicms-admin .text-center{text-align:center}.xicms-admin .text-right{text-align:right}.xicms-admin .align-top{vertical-align:top}.xicms-admin .font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.xicms-admin .text-3xl{font-size:1.875rem;line-height:2.25rem}.xicms-admin .text-\[11px\]{font-size:11px}.xicms-admin .text-base{font-size:1rem;line-height:1.5rem}.xicms-admin .text-lg{font-size:1.125rem;line-height:1.75rem}.xicms-admin .text-sm{font-size:.875rem;line-height:1.25rem}.xicms-admin .text-xl{font-size:1.25rem;line-height:1.75rem}.xicms-admin .text-xs{font-size:.75rem;line-height:1rem}.xicms-admin .font-bold{font-weight:700}.xicms-admin .font-medium{font-weight:500}.xicms-admin .font-semibold{font-weight:600}.xicms-admin .uppercase{text-transform:uppercase}.xicms-admin .italic{font-style:italic}.xicms-admin .tracking-wide{letter-spacing:.025em}.xicms-admin .text-amber-600{--tw-text-opacity:1;color:rgb(217 119 6/var(--tw-text-opacity,1))}.xicms-admin .text-amber-700{--tw-text-opacity:1;color:rgb(180 83 9/var(--tw-text-opacity,1))}.xicms-admin .text-amber-800{--tw-text-opacity:1;color:rgb(146 64 14/var(--tw-text-opacity,1))}.xicms-admin .text-brand-700{color:color-mix(in srgb,var(--xicms-primary,#4f46e5) 82%,#000)}.xicms-admin .text-red-600{--tw-text-opacity:1;color:rgb(220 38 38/var(--tw-text-opacity,1))}.xicms-admin .text-red-700{--tw-text-opacity:1;color:rgb(185 28 28/var(--tw-text-opacity,1))}.xicms-admin .text-slate-400{--tw-text-opacity:1;color:rgb(148 163 184/var(--tw-text-opacity,1))}.xicms-admin .text-slate-500{--tw-text-opacity:1;color:rgb(100 116 139/var(--tw-text-opacity,1))}.xicms-admin .text-slate-600{--tw-text-opacity:1;color:rgb(71 85 105/var(--tw-text-opacity,1))}.xicms-admin .text-slate-700{--tw-text-opacity:1;color:rgb(51 65 85/var(--tw-text-opacity,1))}.xicms-admin .text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.xicms-admin .underline{text-decoration-line:underline}.xicms-admin .opacity-70{opacity:.7}.xicms-admin .shadow-sm{--tw-shadow:0 1px 2px 0 rgba(0,0,0,.05);--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color)}.xicms-admin .shadow-sm,.xicms-admin .shadow-xl{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.xicms-admin .shadow-xl{--tw-shadow:0 20px 25px -5px rgba(0,0,0,.1),0 8px 10px -6px rgba(0,0,0,.1);--tw-shadow-colored:0 20px 25px -5px var(--tw-shadow-color),0 8px 10px -6px var(--tw-shadow-color)}.xicms-admin .outline-none{outline:2px solid transparent;outline-offset:2px}.xicms-admin .outline{outline-style:solid}.xicms-admin .blur{--tw-blur:blur(8px)}.xicms-admin .blur,.xicms-admin .filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.xicms-admin .transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.xicms-admin .transition-\[filter\]{transition-property:filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.xicms-admin,.xicms-admin *,.xicms-admin :after,.xicms-admin :before{box-sizing:border-box}.xicms-admin{font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif;color:#0f172a;line-height:1.5;-webkit-font-smoothing:antialiased;color-scheme:light;background:#fff;--color-background-body:#fff;--color-background-surface:#fff;--color-background-card:#fff;--color-background-muted:#f1f5f9;--color-text-primary:#0f172a;--color-text-secondary:#475569;--color-border:#e2e8f0;--color-accent:#4f46e5;--color-on-accent:#fff;--color-error:#dc2626}.xicms-admin figure,.xicms-admin h1,.xicms-admin h2,.xicms-admin h3,.xicms-admin p{margin:0}.xicms-admin button{font:inherit;cursor:pointer}.xicms-admin input,.xicms-admin select,.xicms-admin textarea{font:inherit;color:inherit}.xicms-admin .ProseMirror{min-height:8rem;outline:none}.xicms-admin .ProseMirror p{margin:0 0 .6rem}.xicms-admin .ProseMirror:focus{outline:none}@media (prefers-reduced-motion:reduce){.xicms-admin *,.xicms-admin :after,.xicms-admin :before,.xicms-ve *,.xicms-ve :after,.xicms-ve :before{animation-duration:.001ms!important;animation-iteration-count:1!important;transition-duration:.001ms!important}}.xicms-admin .last\:border-0:last-child{border-width:0}.xicms-admin .hover\:border-brand-500:hover{border-color:var(--xicms-primary,#4f46e5)}.xicms-admin .hover\:bg-slate-100:hover{--tw-bg-opacity:1;background-color:rgb(241 245 249/var(--tw-bg-opacity,1))}.xicms-admin .hover\:bg-slate-50:hover{--tw-bg-opacity:1;background-color:rgb(248 250 252/var(--tw-bg-opacity,1))}.xicms-admin .hover\:text-slate-700:hover{--tw-text-opacity:1;color:rgb(51 65 85/var(--tw-text-opacity,1))}.xicms-admin .hover\:text-slate-900:hover{--tw-text-opacity:1;color:rgb(15 23 42/var(--tw-text-opacity,1))}.xicms-admin .hover\:underline:hover{text-decoration-line:underline}.xicms-admin .hover\:brightness-95:hover{--tw-brightness:brightness(.95);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.xicms-admin .focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.xicms-admin .focus\:ring-2:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.xicms-admin .focus\:ring-indigo-500:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(99 102 241/var(--tw-ring-opacity,1))}.xicms-admin .focus-visible\:outline-none:focus-visible{outline:2px solid transparent;outline-offset:2px}.xicms-admin .focus-visible\:ring-2:focus-visible{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.xicms-admin .focus-visible\:ring-brand-500:focus-visible{--tw-ring-color:var(--xicms-primary,#4f46e5)}.xicms-admin .disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.xicms-admin .disabled\:opacity-50:disabled{opacity:.5}.xicms-admin :is(.group:hover .group-hover\:opacity-80){opacity:.8}.xicms-admin .data-\[state\=active\]\:border-b-2[data-state=active]{border-bottom-width:2px}.xicms-admin .data-\[state\=active\]\:border-brand-600[data-state=active]{border-color:var(--xicms-primary,#4f46e5)}.xicms-admin .data-\[state\=active\]\:text-brand-700[data-state=active]{color:color-mix(in srgb,var(--xicms-primary,#4f46e5) 82%,#000)}@media (min-width:640px){.xicms-admin .sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.xicms-admin .sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.xicms-admin .sm\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}@media (min-width:768px){.xicms-admin .md\:col-span-5{grid-column:span 5/span 5}.xicms-admin .md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.xicms-admin .md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.xicms-admin .md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.xicms-admin .md\:grid-cols-\[1fr_110px\]{grid-template-columns:1fr 110px}.xicms-admin .md\:grid-cols-\[1fr_130px_90px_110px_auto\]{grid-template-columns:1fr 130px 90px 110px auto}.xicms-admin .md\:grid-cols-\[1fr_1fr_140px_auto_auto\]{grid-template-columns:1fr 1fr 140px auto auto}.xicms-admin .md\:grid-cols-\[260px_1fr\]{grid-template-columns:260px 1fr}.xicms-admin .md\:grid-cols-\[280px_1fr\]{grid-template-columns:280px 1fr}.xicms-admin .md\:grid-cols-\[300px_1fr\]{grid-template-columns:300px 1fr}.xicms-admin .md\:grid-cols-\[340px_1fr\]{grid-template-columns:340px 1fr}}@media (min-width:1024px){.xicms-admin .lg\:col-span-2{grid-column:span 2/span 2}.xicms-admin .lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.xicms-admin .lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}
|
package/dist/schema/forms.d.ts
CHANGED
|
@@ -57,8 +57,13 @@ export declare const FormSchema: z.ZodObject<{
|
|
|
57
57
|
successMessage: z.ZodDefault<z.ZodString>;
|
|
58
58
|
/** Stuur na een geslaagde inzending door naar deze pagina (bv. een bedankt-pagina). */
|
|
59
59
|
successRedirect: z.ZodOptional<z.ZodString>;
|
|
60
|
-
/** Stuur de inzender een bevestigingsmail (naar het eerste e-mailveld van het formulier). */
|
|
60
|
+
/** Stuur de inzender een bevestigingsmail/kopie (naar het eerste e-mailveld van het formulier). */
|
|
61
61
|
confirmSubmitter: z.ZodOptional<z.ZodBoolean>;
|
|
62
|
+
/**
|
|
63
|
+
* Kopie van elke inzending naar dit e-mailadres. Leeg = de site-brede notificatie-instelling
|
|
64
|
+
* (Instellingen → E-mail → notificatie-adres, anders het contact-e-mailadres van de site).
|
|
65
|
+
*/
|
|
66
|
+
notifyTo: z.ZodOptional<z.ZodString>;
|
|
62
67
|
}, "strip", z.ZodTypeAny, {
|
|
63
68
|
name: string;
|
|
64
69
|
fields: {
|
|
@@ -71,6 +76,7 @@ export declare const FormSchema: z.ZodObject<{
|
|
|
71
76
|
}[];
|
|
72
77
|
submitLabel: string;
|
|
73
78
|
successMessage: string;
|
|
79
|
+
notifyTo?: string | undefined;
|
|
74
80
|
successRedirect?: string | undefined;
|
|
75
81
|
confirmSubmitter?: boolean | undefined;
|
|
76
82
|
}, {
|
|
@@ -83,6 +89,7 @@ export declare const FormSchema: z.ZodObject<{
|
|
|
83
89
|
placeholder?: string | undefined;
|
|
84
90
|
required?: boolean | undefined;
|
|
85
91
|
}[];
|
|
92
|
+
notifyTo?: string | undefined;
|
|
86
93
|
submitLabel?: string | undefined;
|
|
87
94
|
successMessage?: string | undefined;
|
|
88
95
|
successRedirect?: string | undefined;
|
package/dist/schema/forms.js
CHANGED
|
@@ -28,8 +28,13 @@ const FormSchema = z.object({
|
|
|
28
28
|
successMessage: z.string().max(500).default("Bedankt! We nemen snel contact op."),
|
|
29
29
|
/** Stuur na een geslaagde inzending door naar deze pagina (bv. een bedankt-pagina). */
|
|
30
30
|
successRedirect: z.string().max(400).optional(),
|
|
31
|
-
/** Stuur de inzender een bevestigingsmail (naar het eerste e-mailveld van het formulier). */
|
|
32
|
-
confirmSubmitter: z.boolean().optional()
|
|
31
|
+
/** Stuur de inzender een bevestigingsmail/kopie (naar het eerste e-mailveld van het formulier). */
|
|
32
|
+
confirmSubmitter: z.boolean().optional(),
|
|
33
|
+
/**
|
|
34
|
+
* Kopie van elke inzending naar dit e-mailadres. Leeg = de site-brede notificatie-instelling
|
|
35
|
+
* (Instellingen → E-mail → notificatie-adres, anders het contact-e-mailadres van de site).
|
|
36
|
+
*/
|
|
37
|
+
notifyTo: z.string().max(200).optional()
|
|
33
38
|
});
|
|
34
39
|
const SubmissionInputSchema = z.object({
|
|
35
40
|
siteId: z.string().min(1).max(80),
|
package/dist/schema/forms.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/schema/forms.ts"],"sourcesContent":["import { z } from \"zod\";\n\nexport const FieldTypeSchema = z.enum([\n \"text\",\n \"email\",\n \"tel\",\n \"textarea\",\n \"select\",\n \"checkbox\",\n // 4.16: update-veilig toegevoegd (oude definities blijven geldig).\n \"radio\",\n \"date\",\n \"number\",\n]);\nexport type FieldType = z.infer<typeof FieldTypeSchema>;\n\nexport const FormFieldSchema = z.object({\n /** Stabiele sleutel; wordt de key in de inzending. */\n name: z\n .string()\n .min(1)\n .max(60)\n .regex(/^[a-zA-Z][a-zA-Z0-9_]*$/, \"veldnaam moet met een letter beginnen en alleen letters/cijfers/_ bevatten\"),\n label: z.string().min(1).max(160),\n type: FieldTypeSchema,\n required: z.boolean().default(false),\n placeholder: z.string().max(200).optional(),\n /** Opties voor type \"select\" en \"radio\". */\n options: z.array(z.string().max(200)).max(50).optional(),\n});\nexport type FormField = z.infer<typeof FormFieldSchema>;\n\n/** Een formulierdefinitie: `sites/{siteId}/forms/{formId}`. */\nexport const FormSchema = z.object({\n name: z.string().min(1).max(160),\n fields: z.array(FormFieldSchema).min(1).max(40),\n submitLabel: z.string().max(80).default(\"Versturen\"),\n successMessage: z.string().max(500).default(\"Bedankt! We nemen snel contact op.\"),\n /** Stuur na een geslaagde inzending door naar deze pagina (bv. een bedankt-pagina). */\n successRedirect: z.string().max(400).optional(),\n /** Stuur de inzender een bevestigingsmail (naar het eerste e-mailveld van het formulier). */\n confirmSubmitter: z.boolean().optional(),\n});\nexport type FormDefinition = z.infer<typeof FormSchema>;\n\n/** Payload van een inzending (callable input). Per-veld-validatie gebeurt server-side\n * tegen de opgeslagen formulierdefinitie. */\nexport const SubmissionInputSchema = z.object({\n siteId: z.string().min(1).max(80),\n formId: z.string().min(1).max(80),\n values: z.record(z.string(), z.union([z.string().max(5000), z.boolean()])),\n /** Honeypot — moet leeg zijn (bot-detectie). */\n _hp: z.string().max(0).optional(),\n});\nexport type SubmissionInput = z.infer<typeof SubmissionInputSchema>;\n"],"mappings":"AAAA,SAAS,SAAS;AAEX,MAAM,kBAAkB,EAAE,KAAK;AAAA,EACpC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAGM,MAAM,kBAAkB,EAAE,OAAO;AAAA;AAAA,EAEtC,MAAM,EACH,OAAO,EACP,IAAI,CAAC,EACL,IAAI,EAAE,EACN,MAAM,2BAA2B,4EAA4E;AAAA,EAChH,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG;AAAA,EAChC,MAAM;AAAA,EACN,UAAU,EAAE,QAAQ,EAAE,QAAQ,KAAK;AAAA,EACnC,aAAa,EAAE,OAAO,EAAE,IAAI,GAAG,EAAE,SAAS;AAAA;AAAA,EAE1C,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,GAAG,CAAC,EAAE,IAAI,EAAE,EAAE,SAAS;AACzD,CAAC;AAIM,MAAM,aAAa,EAAE,OAAO;AAAA,EACjC,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG;AAAA,EAC/B,QAAQ,EAAE,MAAM,eAAe,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE;AAAA,EAC9C,aAAa,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,QAAQ,WAAW;AAAA,EACnD,gBAAgB,EAAE,OAAO,EAAE,IAAI,GAAG,EAAE,QAAQ,oCAAoC;AAAA;AAAA,EAEhF,iBAAiB,EAAE,OAAO,EAAE,IAAI,GAAG,EAAE,SAAS;AAAA;AAAA,EAE9C,kBAAkB,EAAE,QAAQ,EAAE,SAAS;AACzC,CAAC;AAKM,MAAM,wBAAwB,EAAE,OAAO;AAAA,EAC5C,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE;AAAA,EAChC,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE;AAAA,EAChC,QAAQ,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,IAAI,GAAI,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC;AAAA;AAAA,EAEzE,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,SAAS;AAClC,CAAC;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/schema/forms.ts"],"sourcesContent":["import { z } from \"zod\";\n\nexport const FieldTypeSchema = z.enum([\n \"text\",\n \"email\",\n \"tel\",\n \"textarea\",\n \"select\",\n \"checkbox\",\n // 4.16: update-veilig toegevoegd (oude definities blijven geldig).\n \"radio\",\n \"date\",\n \"number\",\n]);\nexport type FieldType = z.infer<typeof FieldTypeSchema>;\n\nexport const FormFieldSchema = z.object({\n /** Stabiele sleutel; wordt de key in de inzending. */\n name: z\n .string()\n .min(1)\n .max(60)\n .regex(/^[a-zA-Z][a-zA-Z0-9_]*$/, \"veldnaam moet met een letter beginnen en alleen letters/cijfers/_ bevatten\"),\n label: z.string().min(1).max(160),\n type: FieldTypeSchema,\n required: z.boolean().default(false),\n placeholder: z.string().max(200).optional(),\n /** Opties voor type \"select\" en \"radio\". */\n options: z.array(z.string().max(200)).max(50).optional(),\n});\nexport type FormField = z.infer<typeof FormFieldSchema>;\n\n/** Een formulierdefinitie: `sites/{siteId}/forms/{formId}`. */\nexport const FormSchema = z.object({\n name: z.string().min(1).max(160),\n fields: z.array(FormFieldSchema).min(1).max(40),\n submitLabel: z.string().max(80).default(\"Versturen\"),\n successMessage: z.string().max(500).default(\"Bedankt! We nemen snel contact op.\"),\n /** Stuur na een geslaagde inzending door naar deze pagina (bv. een bedankt-pagina). */\n successRedirect: z.string().max(400).optional(),\n /** Stuur de inzender een bevestigingsmail/kopie (naar het eerste e-mailveld van het formulier). */\n confirmSubmitter: z.boolean().optional(),\n /**\n * Kopie van elke inzending naar dit e-mailadres. Leeg = de site-brede notificatie-instelling\n * (Instellingen → E-mail → notificatie-adres, anders het contact-e-mailadres van de site).\n */\n notifyTo: z.string().max(200).optional(),\n});\nexport type FormDefinition = z.infer<typeof FormSchema>;\n\n/** Payload van een inzending (callable input). Per-veld-validatie gebeurt server-side\n * tegen de opgeslagen formulierdefinitie. */\nexport const SubmissionInputSchema = z.object({\n siteId: z.string().min(1).max(80),\n formId: z.string().min(1).max(80),\n values: z.record(z.string(), z.union([z.string().max(5000), z.boolean()])),\n /** Honeypot — moet leeg zijn (bot-detectie). */\n _hp: z.string().max(0).optional(),\n});\nexport type SubmissionInput = z.infer<typeof SubmissionInputSchema>;\n"],"mappings":"AAAA,SAAS,SAAS;AAEX,MAAM,kBAAkB,EAAE,KAAK;AAAA,EACpC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAGM,MAAM,kBAAkB,EAAE,OAAO;AAAA;AAAA,EAEtC,MAAM,EACH,OAAO,EACP,IAAI,CAAC,EACL,IAAI,EAAE,EACN,MAAM,2BAA2B,4EAA4E;AAAA,EAChH,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG;AAAA,EAChC,MAAM;AAAA,EACN,UAAU,EAAE,QAAQ,EAAE,QAAQ,KAAK;AAAA,EACnC,aAAa,EAAE,OAAO,EAAE,IAAI,GAAG,EAAE,SAAS;AAAA;AAAA,EAE1C,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,GAAG,CAAC,EAAE,IAAI,EAAE,EAAE,SAAS;AACzD,CAAC;AAIM,MAAM,aAAa,EAAE,OAAO;AAAA,EACjC,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG;AAAA,EAC/B,QAAQ,EAAE,MAAM,eAAe,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE;AAAA,EAC9C,aAAa,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,QAAQ,WAAW;AAAA,EACnD,gBAAgB,EAAE,OAAO,EAAE,IAAI,GAAG,EAAE,QAAQ,oCAAoC;AAAA;AAAA,EAEhF,iBAAiB,EAAE,OAAO,EAAE,IAAI,GAAG,EAAE,SAAS;AAAA;AAAA,EAE9C,kBAAkB,EAAE,QAAQ,EAAE,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA,EAKvC,UAAU,EAAE,OAAO,EAAE,IAAI,GAAG,EAAE,SAAS;AACzC,CAAC;AAKM,MAAM,wBAAwB,EAAE,OAAO;AAAA,EAC5C,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE;AAAA,EAChC,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE;AAAA,EAChC,QAAQ,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,IAAI,GAAI,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC;AAAA;AAAA,EAEzE,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,SAAS;AAClC,CAAC;","names":[]}
|
package/dist/schema/mail.d.ts
CHANGED
|
@@ -66,6 +66,10 @@ export declare const MAIL_TEMPLATE_DEFS: readonly [{
|
|
|
66
66
|
}];
|
|
67
67
|
export declare const MAIL_TEMPLATE_KEYS: readonly MailTemplateKey[];
|
|
68
68
|
export type MailTemplateKey = (typeof MAIL_TEMPLATE_DEFS)[number]["key"];
|
|
69
|
+
/** Formulier-mails — bewerkbaar in het Formulieren-gedeelte. */
|
|
70
|
+
export declare const FORM_TEMPLATE_KEYS: readonly MailTemplateKey[];
|
|
71
|
+
/** Shop-mails — bewerkbaar in het Webwinkel-gedeelte. */
|
|
72
|
+
export declare const SHOP_TEMPLATE_KEYS: readonly MailTemplateKey[];
|
|
69
73
|
export declare const MailTemplateSchema: z.ZodObject<{
|
|
70
74
|
subject: z.ZodOptional<z.ZodString>;
|
|
71
75
|
bodyText: z.ZodOptional<z.ZodString>;
|
package/dist/schema/mail.js
CHANGED
|
@@ -58,6 +58,8 @@ const MAIL_TEMPLATE_DEFS = [
|
|
|
58
58
|
}
|
|
59
59
|
];
|
|
60
60
|
const MAIL_TEMPLATE_KEYS = MAIL_TEMPLATE_DEFS.map((d) => d.key);
|
|
61
|
+
const FORM_TEMPLATE_KEYS = MAIL_TEMPLATE_DEFS.filter((d) => d.module === "core").map((d) => d.key);
|
|
62
|
+
const SHOP_TEMPLATE_KEYS = MAIL_TEMPLATE_DEFS.filter((d) => d.module === "shop").map((d) => d.key);
|
|
61
63
|
const MailTemplateSchema = z.object({
|
|
62
64
|
subject: z.string().max(200).optional(),
|
|
63
65
|
bodyText: z.string().max(1e4).optional()
|
|
@@ -66,9 +68,11 @@ const MailTemplatesSchema = z.object(
|
|
|
66
68
|
Object.fromEntries(MAIL_TEMPLATE_DEFS.map((d) => [d.key, MailTemplateSchema.optional()]))
|
|
67
69
|
);
|
|
68
70
|
export {
|
|
71
|
+
FORM_TEMPLATE_KEYS,
|
|
69
72
|
MAIL_TEMPLATE_DEFS,
|
|
70
73
|
MAIL_TEMPLATE_KEYS,
|
|
71
74
|
MailTemplateSchema,
|
|
72
|
-
MailTemplatesSchema
|
|
75
|
+
MailTemplatesSchema,
|
|
76
|
+
SHOP_TEMPLATE_KEYS
|
|
73
77
|
};
|
|
74
78
|
//# sourceMappingURL=mail.js.map
|
package/dist/schema/mail.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/schema/mail.ts"],"sourcesContent":["import { z } from \"zod\";\n\n/**\n * E-mailtemplates (site-breed, 3.7) — `sites/{siteId}/settings/mailTemplates`. Per template een\n * onderwerp + platte-tekst-body met `{{placeholders}}`. Bewust géén HTML van beheerders; de\n * huisstijl-opmaak komt in de mail-pipeline (functions `mailLayout.ts`) om de tekst heen.\n *\n * De keys zijn getagd met de module waartoe ze horen (`core`/`shop`), zodat de editor in /beheer\n * shop-templates alleen toont op sites met de webwinkel. `core`-keys (formulieren) gelden altijd.\n */\n\nexport interface MailTemplateDef {\n key: string;\n label: string;\n hint: string;\n module: \"core\" | \"shop\";\n /** Beschikbare invulvelden, als tonende hint in de editor. */\n placeholders: string;\n}\n\nexport const MAIL_TEMPLATE_DEFS = [\n {\n key: \"formNotification\",\n label: \"Formulier-notificatie (naar jou)\",\n hint: \"Naar jou zodra iemand een formulier invult.\",\n module: \"core\",\n placeholders: \"{{sitenaam}} {{formulier}} {{regels}}\",\n },\n {\n key: \"formConfirmation\",\n label: \"Formulier-bevestiging (naar de inzender)\",\n hint: \"Ontvangstbevestiging naar wie het formulier invulde.\",\n module: \"core\",\n placeholders: \"{{sitenaam}} {{formulier}} {{regels}}\",\n },\n {\n key: \"orderConfirmation\",\n label: \"Bestelbevestiging\",\n hint: \"Naar de klant zodra de betaling binnen is — met factuur-PDF als bijlage.\",\n module: \"shop\",\n placeholders: \"{{naam}} {{ordernummer}} {{regels}} {{totaal}} {{verzendadres}} {{notitie}} {{sitenaam}}\",\n },\n {\n key: \"shopkeeperNotification\",\n label: \"Nieuwe-bestelling-notificatie (naar jou)\",\n hint: \"Naar jou bij elke nieuwe bestelling.\",\n module: \"shop\",\n placeholders: \"{{sitenaam}} {{ordernummer}} {{totaal}} {{klant}} {{regels}} {{verzendadres}} {{notitie}}\",\n },\n {\n key: \"orderShipped\",\n label: \"Bestelling verzonden\",\n hint: \"Naar de klant wanneer je de status op ‘verzonden’ zet.\",\n module: \"shop\",\n placeholders: \"{{naam}} {{ordernummer}} {{regels}} {{trackingUrl}} {{verzendadres}} {{sitenaam}}\",\n },\n {\n key: \"orderCancelled\",\n label: \"Bestelling geannuleerd\",\n hint: \"Naar de klant bij annulering.\",\n module: \"shop\",\n placeholders: \"{{naam}} {{ordernummer}} {{sitenaam}}\",\n },\n {\n key: \"orderRefunded\",\n label: \"Terugbetaald\",\n hint: \"Naar de klant na een terugbetaling.\",\n module: \"shop\",\n placeholders: \"{{naam}} {{ordernummer}} {{totaal}} {{sitenaam}}\",\n },\n {\n key: \"donationThanks\",\n label: \"Donatie-bedankje\",\n hint: \"Naar de gever na een donatie.\",\n module: \"shop\",\n placeholders: \"{{naam}} {{regels}} {{sitenaam}}\",\n },\n] as const satisfies readonly MailTemplateDef[];\n\nexport const MAIL_TEMPLATE_KEYS = MAIL_TEMPLATE_DEFS.map((d) => d.key) as unknown as readonly MailTemplateKey[];\nexport type MailTemplateKey = (typeof MAIL_TEMPLATE_DEFS)[number][\"key\"];\n\nexport const MailTemplateSchema = z.object({\n subject: z.string().max(200).optional(),\n bodyText: z.string().max(10_000).optional(),\n});\nexport const MailTemplatesSchema = z.object(\n Object.fromEntries(MAIL_TEMPLATE_DEFS.map((d) => [d.key, MailTemplateSchema.optional()])) as Record<\n MailTemplateKey,\n z.ZodOptional<typeof MailTemplateSchema>\n >,\n);\nexport type MailTemplates = z.infer<typeof MailTemplatesSchema>;\n"],"mappings":"AAAA,SAAS,SAAS;AAoBX,MAAM,qBAAqB;AAAA,EAChC;AAAA,IACE,KAAK;AAAA,IACL,OAAO;AAAA,IACP,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,OAAO;AAAA,IACP,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,OAAO;AAAA,IACP,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,OAAO;AAAA,IACP,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,OAAO;AAAA,IACP,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,OAAO;AAAA,IACP,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,OAAO;AAAA,IACP,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,OAAO;AAAA,IACP,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,cAAc;AAAA,EAChB;AACF;AAEO,MAAM,qBAAqB,mBAAmB,IAAI,CAAC,MAAM,EAAE,GAAG;
|
|
1
|
+
{"version":3,"sources":["../../src/schema/mail.ts"],"sourcesContent":["import { z } from \"zod\";\n\n/**\n * E-mailtemplates (site-breed, 3.7) — `sites/{siteId}/settings/mailTemplates`. Per template een\n * onderwerp + platte-tekst-body met `{{placeholders}}`. Bewust géén HTML van beheerders; de\n * huisstijl-opmaak komt in de mail-pipeline (functions `mailLayout.ts`) om de tekst heen.\n *\n * De keys zijn getagd met de module waartoe ze horen (`core`/`shop`), zodat de editor in /beheer\n * shop-templates alleen toont op sites met de webwinkel. `core`-keys (formulieren) gelden altijd.\n */\n\nexport interface MailTemplateDef {\n key: string;\n label: string;\n hint: string;\n module: \"core\" | \"shop\";\n /** Beschikbare invulvelden, als tonende hint in de editor. */\n placeholders: string;\n}\n\nexport const MAIL_TEMPLATE_DEFS = [\n {\n key: \"formNotification\",\n label: \"Formulier-notificatie (naar jou)\",\n hint: \"Naar jou zodra iemand een formulier invult.\",\n module: \"core\",\n placeholders: \"{{sitenaam}} {{formulier}} {{regels}}\",\n },\n {\n key: \"formConfirmation\",\n label: \"Formulier-bevestiging (naar de inzender)\",\n hint: \"Ontvangstbevestiging naar wie het formulier invulde.\",\n module: \"core\",\n placeholders: \"{{sitenaam}} {{formulier}} {{regels}}\",\n },\n {\n key: \"orderConfirmation\",\n label: \"Bestelbevestiging\",\n hint: \"Naar de klant zodra de betaling binnen is — met factuur-PDF als bijlage.\",\n module: \"shop\",\n placeholders: \"{{naam}} {{ordernummer}} {{regels}} {{totaal}} {{verzendadres}} {{notitie}} {{sitenaam}}\",\n },\n {\n key: \"shopkeeperNotification\",\n label: \"Nieuwe-bestelling-notificatie (naar jou)\",\n hint: \"Naar jou bij elke nieuwe bestelling.\",\n module: \"shop\",\n placeholders: \"{{sitenaam}} {{ordernummer}} {{totaal}} {{klant}} {{regels}} {{verzendadres}} {{notitie}}\",\n },\n {\n key: \"orderShipped\",\n label: \"Bestelling verzonden\",\n hint: \"Naar de klant wanneer je de status op ‘verzonden’ zet.\",\n module: \"shop\",\n placeholders: \"{{naam}} {{ordernummer}} {{regels}} {{trackingUrl}} {{verzendadres}} {{sitenaam}}\",\n },\n {\n key: \"orderCancelled\",\n label: \"Bestelling geannuleerd\",\n hint: \"Naar de klant bij annulering.\",\n module: \"shop\",\n placeholders: \"{{naam}} {{ordernummer}} {{sitenaam}}\",\n },\n {\n key: \"orderRefunded\",\n label: \"Terugbetaald\",\n hint: \"Naar de klant na een terugbetaling.\",\n module: \"shop\",\n placeholders: \"{{naam}} {{ordernummer}} {{totaal}} {{sitenaam}}\",\n },\n {\n key: \"donationThanks\",\n label: \"Donatie-bedankje\",\n hint: \"Naar de gever na een donatie.\",\n module: \"shop\",\n placeholders: \"{{naam}} {{regels}} {{sitenaam}}\",\n },\n] as const satisfies readonly MailTemplateDef[];\n\nexport const MAIL_TEMPLATE_KEYS = MAIL_TEMPLATE_DEFS.map((d) => d.key) as unknown as readonly MailTemplateKey[];\nexport type MailTemplateKey = (typeof MAIL_TEMPLATE_DEFS)[number][\"key\"];\n\n/** Formulier-mails — bewerkbaar in het Formulieren-gedeelte. */\nexport const FORM_TEMPLATE_KEYS = MAIL_TEMPLATE_DEFS.filter((d) => d.module === \"core\").map((d) => d.key) as readonly MailTemplateKey[];\n/** Shop-mails — bewerkbaar in het Webwinkel-gedeelte. */\nexport const SHOP_TEMPLATE_KEYS = MAIL_TEMPLATE_DEFS.filter((d) => d.module === \"shop\").map((d) => d.key) as readonly MailTemplateKey[];\n\nexport const MailTemplateSchema = z.object({\n subject: z.string().max(200).optional(),\n bodyText: z.string().max(10_000).optional(),\n});\nexport const MailTemplatesSchema = z.object(\n Object.fromEntries(MAIL_TEMPLATE_DEFS.map((d) => [d.key, MailTemplateSchema.optional()])) as Record<\n MailTemplateKey,\n z.ZodOptional<typeof MailTemplateSchema>\n >,\n);\nexport type MailTemplates = z.infer<typeof MailTemplatesSchema>;\n"],"mappings":"AAAA,SAAS,SAAS;AAoBX,MAAM,qBAAqB;AAAA,EAChC;AAAA,IACE,KAAK;AAAA,IACL,OAAO;AAAA,IACP,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,OAAO;AAAA,IACP,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,OAAO;AAAA,IACP,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,OAAO;AAAA,IACP,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,OAAO;AAAA,IACP,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,OAAO;AAAA,IACP,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,OAAO;AAAA,IACP,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,OAAO;AAAA,IACP,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,cAAc;AAAA,EAChB;AACF;AAEO,MAAM,qBAAqB,mBAAmB,IAAI,CAAC,MAAM,EAAE,GAAG;AAI9D,MAAM,qBAAqB,mBAAmB,OAAO,CAAC,MAAM,EAAE,WAAW,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,GAAG;AAEjG,MAAM,qBAAqB,mBAAmB,OAAO,CAAC,MAAM,EAAE,WAAW,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,GAAG;AAEjG,MAAM,qBAAqB,EAAE,OAAO;AAAA,EACzC,SAAS,EAAE,OAAO,EAAE,IAAI,GAAG,EAAE,SAAS;AAAA,EACtC,UAAU,EAAE,OAAO,EAAE,IAAI,GAAM,EAAE,SAAS;AAC5C,CAAC;AACM,MAAM,sBAAsB,EAAE;AAAA,EACnC,OAAO,YAAY,mBAAmB,IAAI,CAAC,MAAM,CAAC,EAAE,KAAK,mBAAmB,SAAS,CAAC,CAAC,CAAC;AAI1F;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xiwebdesign/xicms-core",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.3.0",
|
|
4
4
|
"description": "XiCMS engine: gestructureerde content, pagina's, draft/publish, admin (TipTap+Radix), SEO en beveiliging voor klantsites.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"repository": {
|