@xiwebdesign/xicms-core 5.7.1 → 5.8.1
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/shop/ConnectionsTab.d.ts +3 -4
- package/dist/admin/shop/ConnectionsTab.js +194 -83
- package/dist/admin/shop/ConnectionsTab.js.map +1 -1
- package/dist/admin.css +1 -1
- package/dist/modules/shop/blocks/OrderStatus.js +57 -7
- package/dist/modules/shop/blocks/OrderStatus.js.map +1 -1
- package/dist/modules/shop/lib.d.ts +8 -0
- package/dist/modules/shop/lib.js +8 -0
- package/dist/modules/shop/lib.js.map +1 -1
- package/dist/modules/shop/module.js +1 -1
- package/dist/modules/shop/module.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Koppelingen —
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* Alleen een siteadmin kan opslaan (de callables dwingen dat af).
|
|
2
|
+
* Koppelingen — betaalprovider (Stripe of Mollie) en mailprovider (Resend, Postmark of eigen SMTP).
|
|
3
|
+
* Geheimen gaan uitsluitend via callables naar `sites/{siteId}/private/*` (deny-all rules); dit
|
|
4
|
+
* paneel toont alleen gemaskeerde status. Alleen een siteadmin kan opslaan.
|
|
6
5
|
*/
|
|
7
6
|
export declare function ConnectionsTab({ siteId }: {
|
|
8
7
|
siteId: string;
|
|
@@ -5,25 +5,53 @@ import { Badge, Button, Card, Field, Input } from "../ui/primitives";
|
|
|
5
5
|
import { Switch } from "../ui/switch";
|
|
6
6
|
import { useToast } from "../ui/toast";
|
|
7
7
|
import { shopCallable } from "./lib";
|
|
8
|
+
function Segmented({
|
|
9
|
+
value,
|
|
10
|
+
options,
|
|
11
|
+
onChange
|
|
12
|
+
}) {
|
|
13
|
+
return /* @__PURE__ */ jsx("div", { className: "inline-flex rounded-lg border border-slate-200 bg-slate-50 p-0.5", children: options.map((o) => /* @__PURE__ */ jsx(
|
|
14
|
+
"button",
|
|
15
|
+
{
|
|
16
|
+
type: "button",
|
|
17
|
+
onClick: () => onChange(o.value),
|
|
18
|
+
className: `rounded-md px-3 py-1.5 text-sm font-medium transition ${value === o.value ? "bg-white text-brand-700 shadow-sm" : "text-slate-500 hover:text-slate-700"}`,
|
|
19
|
+
children: o.label
|
|
20
|
+
},
|
|
21
|
+
o.value
|
|
22
|
+
)) });
|
|
23
|
+
}
|
|
8
24
|
function ConnectionsTab({ siteId }) {
|
|
9
25
|
const { toast } = useToast();
|
|
10
26
|
const [status, setStatus] = useState(null);
|
|
11
27
|
const [busy, setBusy] = useState(false);
|
|
28
|
+
const [payProvider, setPayProvider] = useState("stripe");
|
|
12
29
|
const [stripeKey, setStripeKey] = useState("");
|
|
13
|
-
const [
|
|
30
|
+
const [mollieKey, setMollieKey] = useState("");
|
|
31
|
+
const [mailProvider, setMailProvider] = useState("smtp");
|
|
32
|
+
const [resendKey, setResendKey] = useState("");
|
|
33
|
+
const [postmarkToken, setPostmarkToken] = useState("");
|
|
34
|
+
const [smtp, setSmtp] = useState({ host: "", port: "465", secure: true, user: "", pass: "" });
|
|
35
|
+
const [fromName, setFromName] = useState("");
|
|
36
|
+
const [fromEmail, setFromEmail] = useState("");
|
|
37
|
+
const [notifyTo, setNotifyTo] = useState("");
|
|
14
38
|
const refresh = useCallback(async () => {
|
|
15
39
|
try {
|
|
16
|
-
const
|
|
40
|
+
const raw = await shopCallable("getShopStatus")({ siteId });
|
|
41
|
+
const s = {
|
|
42
|
+
provider: raw.provider ?? "stripe",
|
|
43
|
+
stripe: raw.stripe ?? { configured: false, webhookConfigured: false },
|
|
44
|
+
mollie: raw.mollie ?? { configured: false },
|
|
45
|
+
mail: raw.mail ?? { configured: false }
|
|
46
|
+
};
|
|
17
47
|
setStatus(s);
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
notifyTo: s.smtp.notifyTo ?? p.notifyTo
|
|
26
|
-
}));
|
|
48
|
+
setPayProvider(s.provider ?? "stripe");
|
|
49
|
+
const m = s.mail ?? {};
|
|
50
|
+
setMailProvider(m.provider ?? (m.host ? "smtp" : m.hasPostmarkToken ? "postmark" : m.hasResendKey ? "resend" : "smtp"));
|
|
51
|
+
setSmtp((p) => ({ ...p, host: m.host ?? p.host, port: String(m.port ?? 465), secure: m.secure !== false, user: m.user ?? p.user }));
|
|
52
|
+
setFromName(m.fromName ?? "");
|
|
53
|
+
setFromEmail(m.fromEmail ?? "");
|
|
54
|
+
setNotifyTo(m.notifyTo ?? "");
|
|
27
55
|
} catch {
|
|
28
56
|
setStatus(null);
|
|
29
57
|
}
|
|
@@ -31,44 +59,64 @@ function ConnectionsTab({ siteId }) {
|
|
|
31
59
|
useEffect(() => {
|
|
32
60
|
void refresh();
|
|
33
61
|
}, [refresh]);
|
|
34
|
-
async function
|
|
35
|
-
if (!stripeKey.trim().startsWith("rk_") && !stripeKey.trim().startsWith("sk_")) {
|
|
36
|
-
toast({ title: "Ongeldige sleutel", description: "Plak een geheime sleutel (sk_\u2026) of restricted key (rk_\u2026).", tone: "error" });
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
62
|
+
async function savePayment() {
|
|
39
63
|
setBusy(true);
|
|
40
64
|
try {
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
65
|
+
const payload = { siteId, provider: payProvider };
|
|
66
|
+
if (payProvider === "stripe") {
|
|
67
|
+
if (!stripeKey.trim().startsWith("sk_") && !stripeKey.trim().startsWith("rk_")) {
|
|
68
|
+
toast({ title: "Ongeldige sleutel", description: "Plak een Stripe geheime sleutel (sk_\u2026) of restricted key (rk_\u2026).", tone: "error" });
|
|
69
|
+
setBusy(false);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
payload.stripe = { secretKey: stripeKey.trim() };
|
|
73
|
+
} else {
|
|
74
|
+
if (!/^(test|live)_/.test(mollieKey.trim())) {
|
|
75
|
+
toast({ title: "Ongeldige sleutel", description: "Plak een Mollie API-sleutel (test_\u2026 of live_\u2026).", tone: "error" });
|
|
76
|
+
setBusy(false);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
payload.mollie = { apiKey: mollieKey.trim() };
|
|
80
|
+
}
|
|
81
|
+
const res = await shopCallable("saveShopSecrets")(payload);
|
|
82
|
+
toast({ title: `${payProvider === "stripe" ? "Stripe" : "Mollie"} gekoppeld`, description: res.account ? `Account: ${res.account}` : void 0, tone: "success" });
|
|
46
83
|
setStripeKey("");
|
|
84
|
+
setMollieKey("");
|
|
47
85
|
await refresh();
|
|
48
86
|
} catch (e) {
|
|
49
|
-
toast({ title: "
|
|
87
|
+
toast({ title: "Koppelen mislukt", description: e instanceof Error ? e.message : void 0, tone: "error" });
|
|
50
88
|
} finally {
|
|
51
89
|
setBusy(false);
|
|
52
90
|
}
|
|
53
91
|
}
|
|
54
|
-
async function
|
|
92
|
+
async function switchActiveProvider(p) {
|
|
93
|
+
setPayProvider(p);
|
|
94
|
+
const otherConfigured = p === "stripe" ? status?.stripe.configured : status?.mollie.configured;
|
|
95
|
+
if (otherConfigured) {
|
|
96
|
+
try {
|
|
97
|
+
await shopCallable("saveShopSecrets")({ siteId, provider: p });
|
|
98
|
+
await refresh();
|
|
99
|
+
} catch {
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
async function saveMail() {
|
|
55
104
|
setBusy(true);
|
|
56
105
|
try {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
notifyTo: smtp.notifyTo.trim()
|
|
69
|
-
}
|
|
70
|
-
});
|
|
106
|
+
const mail = { provider: mailProvider, fromName, fromEmail, notifyTo };
|
|
107
|
+
if (mailProvider === "resend") mail.resendApiKey = resendKey.trim() || void 0;
|
|
108
|
+
if (mailProvider === "postmark") mail.postmarkToken = postmarkToken.trim() || void 0;
|
|
109
|
+
if (mailProvider === "smtp") {
|
|
110
|
+
mail.host = smtp.host.trim();
|
|
111
|
+
mail.port = Number(smtp.port) || 465;
|
|
112
|
+
mail.secure = smtp.secure;
|
|
113
|
+
mail.user = smtp.user.trim();
|
|
114
|
+
if (smtp.pass) mail.pass = smtp.pass;
|
|
115
|
+
}
|
|
116
|
+
await shopCallable("saveMailSettings")({ siteId, mail });
|
|
71
117
|
toast({ title: "E-mailinstellingen opgeslagen", tone: "success" });
|
|
118
|
+
setResendKey("");
|
|
119
|
+
setPostmarkToken("");
|
|
72
120
|
setSmtp((p) => ({ ...p, pass: "" }));
|
|
73
121
|
await refresh();
|
|
74
122
|
} catch (e) {
|
|
@@ -88,70 +136,133 @@ function ConnectionsTab({ siteId }) {
|
|
|
88
136
|
setBusy(false);
|
|
89
137
|
}
|
|
90
138
|
}
|
|
91
|
-
|
|
139
|
+
const payConfigured = payProvider === "stripe" ? status?.stripe.configured : status?.mollie.configured;
|
|
140
|
+
return /* @__PURE__ */ jsxs("div", { className: "grid gap-4", children: [
|
|
92
141
|
/* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsxs("div", { className: "grid gap-4", children: [
|
|
93
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
94
|
-
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "
|
|
95
|
-
status && /* @__PURE__ */ jsx(Badge, { tone:
|
|
142
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [
|
|
143
|
+
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Betalingen" }),
|
|
144
|
+
status && /* @__PURE__ */ jsx(Badge, { tone: payConfigured ? "green" : "amber", children: payProvider === "stripe" ? status.stripe.configured ? `Stripe actief${status.stripe.account ? ` \u2014 ${status.stripe.account}` : ""}` : "Stripe nog niet gekoppeld" : status.mollie.configured ? `Mollie actief${status.mollie.profile ? ` \u2014 ${status.mollie.profile}` : ""}` : "Mollie nog niet gekoppeld" })
|
|
96
145
|
] }),
|
|
97
|
-
/* @__PURE__ */
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
146
|
+
/* @__PURE__ */ jsx(
|
|
147
|
+
Segmented,
|
|
148
|
+
{
|
|
149
|
+
value: payProvider,
|
|
150
|
+
onChange: (v) => void switchActiveProvider(v),
|
|
151
|
+
options: [
|
|
152
|
+
{ value: "stripe", label: "Stripe" },
|
|
153
|
+
{ value: "mollie", label: "Mollie" }
|
|
154
|
+
]
|
|
155
|
+
}
|
|
156
|
+
),
|
|
157
|
+
payProvider === "stripe" ? /* @__PURE__ */ jsxs("div", { className: "grid gap-3", children: [
|
|
158
|
+
/* @__PURE__ */ jsxs("ol", { className: "grid list-decimal gap-1 pl-5 text-sm text-slate-600", children: [
|
|
159
|
+
/* @__PURE__ */ jsxs("li", { children: [
|
|
160
|
+
"Maak een gratis account op ",
|
|
161
|
+
/* @__PURE__ */ jsx("a", { className: "underline", href: "https://dashboard.stripe.com/register", target: "_blank", rel: "noreferrer", children: "stripe.com" }),
|
|
162
|
+
" en activeer het (IBAN, KVK)."
|
|
163
|
+
] }),
|
|
164
|
+
/* @__PURE__ */ jsxs("li", { children: [
|
|
165
|
+
"Zet onder ",
|
|
166
|
+
/* @__PURE__ */ jsx("em", { children: "Settings \u2192 Payment methods" }),
|
|
167
|
+
" iDEAL aan."
|
|
168
|
+
] }),
|
|
169
|
+
/* @__PURE__ */ jsxs("li", { children: [
|
|
170
|
+
"Maak onder ",
|
|
171
|
+
/* @__PURE__ */ jsx("em", { children: "Developers \u2192 API keys" }),
|
|
172
|
+
" een sleutel aan en plak hem hieronder \u2014 de webhook wordt automatisch aangemaakt."
|
|
173
|
+
] })
|
|
102
174
|
] }),
|
|
103
|
-
/* @__PURE__ */
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
175
|
+
/* @__PURE__ */ jsx(Field, { label: "Stripe geheime sleutel (sk_live_\u2026 of rk_live_\u2026)", hint: "Wordt veilig opgeslagen; nooit zichtbaar in de browser", children: /* @__PURE__ */ jsx(Input, { type: "password", value: stripeKey, onChange: (e) => setStripeKey(e.target.value), placeholder: "sk_live_\u2026" }) }),
|
|
176
|
+
status?.stripe.configured && !status.stripe.webhookConfigured && /* @__PURE__ */ jsx("p", { className: "rounded-lg bg-amber-50 px-3 py-2 text-sm text-amber-700", children: "Webhook nog niet geregistreerd \u2014 koppel opnieuw met een sleutel die webhook-rechten heeft." }),
|
|
177
|
+
/* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Button, { onClick: savePayment, disabled: busy || !stripeKey.trim(), children: "Koppel Stripe" }) })
|
|
178
|
+
] }) : /* @__PURE__ */ jsxs("div", { className: "grid gap-3", children: [
|
|
179
|
+
/* @__PURE__ */ jsxs("ol", { className: "grid list-decimal gap-1 pl-5 text-sm text-slate-600", children: [
|
|
180
|
+
/* @__PURE__ */ jsxs("li", { children: [
|
|
181
|
+
"Maak een account op ",
|
|
182
|
+
/* @__PURE__ */ jsx("a", { className: "underline", href: "https://www.mollie.com/dashboard/signup", target: "_blank", rel: "noreferrer", children: "mollie.com" }),
|
|
183
|
+
" en doorloop de onboarding."
|
|
184
|
+
] }),
|
|
185
|
+
/* @__PURE__ */ jsx("li", { children: "Zet in het Mollie-dashboard de gewenste betaalmethoden (o.a. iDEAL) aan." }),
|
|
186
|
+
/* @__PURE__ */ jsxs("li", { children: [
|
|
187
|
+
"Kopieer onder ",
|
|
188
|
+
/* @__PURE__ */ jsx("em", { children: "Developers \u2192 API-keys" }),
|
|
189
|
+
" je live- of test-sleutel en plak hem hieronder. Mollie heeft geen aparte webhook-registratie nodig."
|
|
190
|
+
] })
|
|
107
191
|
] }),
|
|
108
|
-
/* @__PURE__ */
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
" een sleutel aan en plak hem hieronder. De webhook wordt automatisch voor je aangemaakt."
|
|
112
|
-
] })
|
|
113
|
-
] }),
|
|
114
|
-
/* @__PURE__ */ jsx(Field, { label: "Geheime sleutel (sk_live_\u2026 of rk_live_\u2026)", hint: "Wordt veilig opgeslagen; nooit zichtbaar in de browser", children: /* @__PURE__ */ jsx(Input, { type: "password", value: stripeKey, onChange: (e) => setStripeKey(e.target.value), placeholder: "sk_live_\u2026" }) }),
|
|
115
|
-
/* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Button, { onClick: saveStripe, disabled: busy || !stripeKey.trim(), children: "Koppel Stripe" }) }),
|
|
116
|
-
status?.stripe.configured && !status.stripe.webhookConfigured && /* @__PURE__ */ jsx("p", { className: "rounded-lg bg-amber-50 px-3 py-2 text-sm text-amber-700", children: "Webhook nog niet geregistreerd \u2014 koppel opnieuw met een sleutel die webhook-rechten heeft." })
|
|
192
|
+
/* @__PURE__ */ jsx(Field, { label: "Mollie API-sleutel (live_\u2026 of test_\u2026)", hint: "Wordt veilig opgeslagen; nooit zichtbaar in de browser", children: /* @__PURE__ */ jsx(Input, { type: "password", value: mollieKey, onChange: (e) => setMollieKey(e.target.value), placeholder: "live_\u2026" }) }),
|
|
193
|
+
/* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Button, { onClick: savePayment, disabled: busy || !mollieKey.trim(), children: "Koppel Mollie" }) })
|
|
194
|
+
] })
|
|
117
195
|
] }) }),
|
|
118
196
|
/* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsxs("div", { className: "grid gap-4", children: [
|
|
119
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
120
|
-
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "E-mail (bestelbevestigingen)" }),
|
|
121
|
-
status && /* @__PURE__ */ jsx(Badge, { tone: status.
|
|
197
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [
|
|
198
|
+
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "E-mail (bestelbevestigingen & formulieren)" }),
|
|
199
|
+
status && /* @__PURE__ */ jsx(Badge, { tone: status.mail.configured ? "green" : "amber", children: status.mail.configured ? `${mailProviderLabel(mailProvider)} actief` : "Nog niet ingesteld" })
|
|
122
200
|
] }),
|
|
123
|
-
/* @__PURE__ */
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
201
|
+
/* @__PURE__ */ jsx(
|
|
202
|
+
Segmented,
|
|
203
|
+
{
|
|
204
|
+
value: mailProvider,
|
|
205
|
+
onChange: setMailProvider,
|
|
206
|
+
options: [
|
|
207
|
+
{ value: "smtp", label: "Eigen SMTP" },
|
|
208
|
+
{ value: "resend", label: "Resend" },
|
|
209
|
+
{ value: "postmark", label: "Postmark" }
|
|
210
|
+
]
|
|
211
|
+
}
|
|
212
|
+
),
|
|
213
|
+
mailProvider === "smtp" && /* @__PURE__ */ jsxs("div", { className: "grid gap-3", children: [
|
|
214
|
+
/* @__PURE__ */ jsxs("p", { className: "text-sm text-slate-600", children: [
|
|
215
|
+
"Via je eigen mailbox: bv. het adres bij je hostingpartij, of Gmail (smtp.gmail.com, poort 465, met een",
|
|
216
|
+
" ",
|
|
217
|
+
/* @__PURE__ */ jsx("a", { className: "underline", href: "https://myaccount.google.com/apppasswords", target: "_blank", rel: "noreferrer", children: "app-wachtwoord" }),
|
|
218
|
+
")."
|
|
219
|
+
] }),
|
|
220
|
+
/* @__PURE__ */ jsxs("div", { className: "grid gap-3 md:grid-cols-[1fr_110px]", children: [
|
|
221
|
+
/* @__PURE__ */ jsx(Field, { label: "SMTP-server", children: /* @__PURE__ */ jsx(Input, { value: smtp.host, onChange: (e) => setSmtp((p) => ({ ...p, host: e.target.value })), placeholder: "smtp.gmail.com" }) }),
|
|
222
|
+
/* @__PURE__ */ jsx(Field, { label: "Poort", children: /* @__PURE__ */ jsx(Input, { value: smtp.port, onChange: (e) => setSmtp((p) => ({ ...p, port: e.target.value })), placeholder: "465" }) })
|
|
223
|
+
] }),
|
|
224
|
+
/* @__PURE__ */ jsx(Switch, { checked: smtp.secure, onCheckedChange: (v) => setSmtp((p) => ({ ...p, secure: v })), label: "SSL (poort 465). Uit = STARTTLS (poort 587)" }),
|
|
225
|
+
/* @__PURE__ */ jsxs("div", { className: "grid gap-3 md:grid-cols-2", children: [
|
|
226
|
+
/* @__PURE__ */ jsx(Field, { label: "Gebruikersnaam", children: /* @__PURE__ */ jsx(Input, { value: smtp.user, onChange: (e) => setSmtp((p) => ({ ...p, user: e.target.value })), placeholder: "info@jouwdomein.nl" }) }),
|
|
227
|
+
/* @__PURE__ */ jsx(Field, { label: "Wachtwoord", hint: status?.mail.hasSmtpPass ? "Leeg laten = ongewijzigd" : void 0, children: /* @__PURE__ */ jsx(Input, { type: "password", value: smtp.pass, onChange: (e) => setSmtp((p) => ({ ...p, pass: e.target.value })) }) })
|
|
228
|
+
] })
|
|
127
229
|
] }),
|
|
128
|
-
/* @__PURE__ */ jsxs("div", { className: "grid gap-
|
|
129
|
-
/* @__PURE__ */
|
|
130
|
-
|
|
230
|
+
mailProvider === "resend" && /* @__PURE__ */ jsxs("div", { className: "grid gap-3", children: [
|
|
231
|
+
/* @__PURE__ */ jsxs("p", { className: "text-sm text-slate-600", children: [
|
|
232
|
+
/* @__PURE__ */ jsx("a", { className: "underline", href: "https://resend.com", target: "_blank", rel: "noreferrer", children: "Resend" }),
|
|
233
|
+
" verstuurt vanaf een geverifieerd domein. Maak een API-sleutel (re_\u2026) en verifieer je afzenderdomein in het Resend-dashboard."
|
|
234
|
+
] }),
|
|
235
|
+
/* @__PURE__ */ jsx(Field, { label: "Resend API-sleutel (re_\u2026)", hint: status?.mail.hasResendKey ? "Leeg laten = ongewijzigd" : void 0, children: /* @__PURE__ */ jsx(Input, { type: "password", value: resendKey, onChange: (e) => setResendKey(e.target.value), placeholder: "re_\u2026" }) })
|
|
131
236
|
] }),
|
|
132
|
-
/* @__PURE__ */
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
237
|
+
mailProvider === "postmark" && /* @__PURE__ */ jsxs("div", { className: "grid gap-3", children: [
|
|
238
|
+
/* @__PURE__ */ jsxs("p", { className: "text-sm text-slate-600", children: [
|
|
239
|
+
/* @__PURE__ */ jsx("a", { className: "underline", href: "https://postmarkapp.com", target: "_blank", rel: "noreferrer", children: "Postmark" }),
|
|
240
|
+
" verstuurt vanaf een geverifieerde afzender/domein (Sender Signature). Kopieer het Server-token."
|
|
241
|
+
] }),
|
|
242
|
+
/* @__PURE__ */ jsx(Field, { label: "Postmark server-token", hint: status?.mail.hasPostmarkToken ? "Leeg laten = ongewijzigd" : void 0, children: /* @__PURE__ */ jsx(Input, { type: "password", value: postmarkToken, onChange: (e) => setPostmarkToken(e.target.value), placeholder: "xxxxxxxx-xxxx-\u2026" }) })
|
|
136
243
|
] }),
|
|
137
|
-
/* @__PURE__ */ jsxs("div", { className: "grid gap-
|
|
138
|
-
/* @__PURE__ */ jsx(Field, { label: "Afzendernaam", children: /* @__PURE__ */ jsx(Input, { value:
|
|
139
|
-
/* @__PURE__ */ jsx(Field, { label: "Afzenderadres", children: /* @__PURE__ */ jsx(Input, { value:
|
|
244
|
+
/* @__PURE__ */ jsxs("div", { className: "grid gap-3 md:grid-cols-2", children: [
|
|
245
|
+
/* @__PURE__ */ jsx(Field, { label: "Afzendernaam", children: /* @__PURE__ */ jsx(Input, { value: fromName, onChange: (e) => setFromName(e.target.value), placeholder: "Christelijk Kraamcadeau" }) }),
|
|
246
|
+
/* @__PURE__ */ jsx(Field, { label: "Afzenderadres", children: /* @__PURE__ */ jsx(Input, { value: fromEmail, onChange: (e) => setFromEmail(e.target.value), placeholder: "info@jouwdomein.nl" }) })
|
|
140
247
|
] }),
|
|
141
|
-
/* @__PURE__ */ jsx(Field, { label: "Notificatie-adres", hint: "Hier ontvang je zelf een mail bij elke bestelling", children: /* @__PURE__ */ jsx(Input, { value:
|
|
248
|
+
/* @__PURE__ */ jsx(Field, { label: "Notificatie-adres", hint: "Hier ontvang je zelf een mail bij elke bestelling; ook het doel van de testmail", children: /* @__PURE__ */ jsx(Input, { value: notifyTo, onChange: (e) => setNotifyTo(e.target.value), placeholder: "bestellingen@jouwdomein.nl" }) }),
|
|
142
249
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
143
|
-
/* @__PURE__ */ jsx(Button, { onClick:
|
|
144
|
-
/* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: sendTest, disabled: busy || !status?.
|
|
250
|
+
/* @__PURE__ */ jsx(Button, { onClick: saveMail, disabled: busy, children: "Opslaan" }),
|
|
251
|
+
/* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: sendTest, disabled: busy || !status?.mail.configured, children: "Stuur testmail" })
|
|
145
252
|
] })
|
|
146
253
|
] }) }),
|
|
147
|
-
/* @__PURE__ */ jsx(Card, {
|
|
148
|
-
"Verzendkosten, valuta, ordernummer-prefix en
|
|
149
|
-
" ",
|
|
150
|
-
|
|
254
|
+
/* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsxs("p", { className: "text-sm text-slate-600", children: [
|
|
255
|
+
"Verzendkosten, valuta, ordernummer-prefix en bedrijfsgegevens (factuur) stel je in onder het tabblad",
|
|
256
|
+
/* @__PURE__ */ jsx("strong", { children: " Instellingen" }),
|
|
257
|
+
". Mailteksten pas je aan onder ",
|
|
258
|
+
/* @__PURE__ */ jsx("strong", { children: "E-mails" }),
|
|
151
259
|
"."
|
|
152
260
|
] }) })
|
|
153
261
|
] });
|
|
154
262
|
}
|
|
263
|
+
function mailProviderLabel(p) {
|
|
264
|
+
return p === "smtp" ? "Eigen SMTP" : p === "resend" ? "Resend" : "Postmark";
|
|
265
|
+
}
|
|
155
266
|
export {
|
|
156
267
|
ConnectionsTab
|
|
157
268
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/shop/ConnectionsTab.tsx"],"sourcesContent":["\"use client\";\n\nimport { useCallback, useEffect, useState } from \"react\";\nimport { Badge, Button, Card, Field, Input } from \"../ui/primitives\";\nimport { Switch } from \"../ui/switch\";\nimport { useToast } from \"../ui/toast\";\nimport { shopCallable } from \"./lib\";\n\ninterface ShopStatus {\n stripe: { configured: boolean; account?: string; webhookConfigured: boolean };\n smtp: { configured: boolean; host?: string; port?: number; user?: string; fromName?: string; fromEmail?: string; notifyTo?: string };\n}\n\n/**\n * Koppelingen — Stripe (eigen account van de klant) en e-mail (eigen SMTP, bv. Gmail of de\n * mailbox bij de hostingpartij). Geheimen gaan uitsluitend via callables naar\n * `sites/{siteId}/private/shop` (deny-all rules); dit paneel toont alleen gemaskeerde status.\n * Alleen een siteadmin kan opslaan (de callables dwingen dat af).\n */\nexport function ConnectionsTab({ siteId }: { siteId: string }) {\n const { toast } = useToast();\n const [status, setStatus] = useState<ShopStatus | null>(null);\n const [busy, setBusy] = useState(false);\n\n const [stripeKey, setStripeKey] = useState(\"\");\n const [smtp, setSmtp] = useState({ host: \"\", port: \"465\", secure: true, user: \"\", pass: \"\", fromName: \"\", fromEmail: \"\", notifyTo: \"\" });\n\n const refresh = useCallback(async () => {\n try {\n const s = await shopCallable<{ siteId: string }, ShopStatus>(\"getShopStatus\")({ siteId });\n setStatus(s);\n setSmtp((p) => ({\n ...p,\n host: s.smtp.host ?? p.host,\n port: String(s.smtp.port ?? 465),\n user: s.smtp.user ?? p.user,\n fromName: s.smtp.fromName ?? p.fromName,\n fromEmail: s.smtp.fromEmail ?? p.fromEmail,\n notifyTo: s.smtp.notifyTo ?? p.notifyTo,\n }));\n } catch {\n setStatus(null);\n }\n }, [siteId]);\n\n useEffect(() => {\n void refresh();\n }, [refresh]);\n\n async function saveStripe() {\n if (!stripeKey.trim().startsWith(\"rk_\") && !stripeKey.trim().startsWith(\"sk_\")) {\n toast({ title: \"Ongeldige sleutel\", description: \"Plak een geheime sleutel (sk_…) of restricted key (rk_…).\", tone: \"error\" });\n return;\n }\n setBusy(true);\n try {\n const res = await shopCallable<{ siteId: string; stripe: { secretKey: string } }, { account?: string }>(\"saveShopSecrets\")({\n siteId,\n stripe: { secretKey: stripeKey.trim() },\n });\n toast({ title: \"Stripe gekoppeld\", description: res.account ? `Account: ${res.account}` : undefined, tone: \"success\" });\n setStripeKey(\"\");\n await refresh();\n } catch (e) {\n toast({ title: \"Stripe koppelen mislukt\", description: e instanceof Error ? e.message : undefined, tone: \"error\" });\n } finally {\n setBusy(false);\n }\n }\n\n async function saveSmtp() {\n setBusy(true);\n try {\n await shopCallable<{ siteId: string; smtp: Record<string, unknown> }, { ok: boolean }>(\"saveShopSecrets\")({\n siteId,\n smtp: {\n host: smtp.host.trim(),\n port: Number(smtp.port) || 465,\n secure: smtp.secure,\n user: smtp.user.trim(),\n // Leeg laten = bestaand wachtwoord behouden.\n ...(smtp.pass ? { pass: smtp.pass } : {}),\n fromName: smtp.fromName.trim(),\n fromEmail: smtp.fromEmail.trim(),\n notifyTo: smtp.notifyTo.trim(),\n },\n });\n toast({ title: \"E-mailinstellingen opgeslagen\", tone: \"success\" });\n setSmtp((p) => ({ ...p, pass: \"\" }));\n await refresh();\n } catch (e) {\n toast({ title: \"Opslaan mislukt\", description: e instanceof Error ? e.message : undefined, tone: \"error\" });\n } finally {\n setBusy(false);\n }\n }\n\n async function sendTest() {\n setBusy(true);\n try {\n const res = await shopCallable<{ siteId: string }, { to: string }>(\"sendShopTestMail\")({ siteId });\n toast({ title: \"Testmail verstuurd\", description: `Naar ${res.to} — check de inbox (en spam).`, tone: \"success\" });\n } catch (e) {\n toast({ title: \"Testmail mislukt\", description: e instanceof Error ? e.message : undefined, tone: \"error\" });\n } finally {\n setBusy(false);\n }\n }\n\n return (\n <div className=\"grid gap-4 lg:grid-cols-2\">\n <Card>\n <div className=\"grid gap-4\">\n <div className=\"flex items-center justify-between\">\n <h3 className=\"text-sm font-semibold\">Stripe (betalingen)</h3>\n {status && (\n <Badge tone={status.stripe.configured ? \"green\" : \"amber\"}>\n {status.stripe.configured ? `Gekoppeld${status.stripe.account ? ` — ${status.stripe.account}` : \"\"}` : \"Nog niet gekoppeld\"}\n </Badge>\n )}\n </div>\n <ol className=\"grid list-decimal gap-1 pl-5 text-sm text-slate-600\">\n <li>Maak een gratis account op <a className=\"underline\" href=\"https://dashboard.stripe.com/register\" target=\"_blank\" rel=\"noreferrer\">stripe.com</a> en doorloop de activatie (IBAN, KVK).</li>\n <li>Zet in Stripe onder <em>Settings → Payment methods</em> iDEAL aan.</li>\n <li>Maak onder <em>Developers → API keys</em> een sleutel aan en plak hem hieronder. De webhook wordt automatisch voor je aangemaakt.</li>\n </ol>\n <Field label=\"Geheime sleutel (sk_live_… of rk_live_…)\" hint=\"Wordt veilig opgeslagen; nooit zichtbaar in de browser\">\n <Input type=\"password\" value={stripeKey} onChange={(e) => setStripeKey(e.target.value)} placeholder=\"sk_live_…\" />\n </Field>\n <div>\n <Button onClick={saveStripe} disabled={busy || !stripeKey.trim()}>Koppel Stripe</Button>\n </div>\n {status?.stripe.configured && !status.stripe.webhookConfigured && (\n <p className=\"rounded-lg bg-amber-50 px-3 py-2 text-sm text-amber-700\">\n Webhook nog niet geregistreerd — koppel opnieuw met een sleutel die webhook-rechten heeft.\n </p>\n )}\n </div>\n </Card>\n\n <Card>\n <div className=\"grid gap-4\">\n <div className=\"flex items-center justify-between\">\n <h3 className=\"text-sm font-semibold\">E-mail (bestelbevestigingen)</h3>\n {status && (\n <Badge tone={status.smtp.configured ? \"green\" : \"amber\"}>\n {status.smtp.configured ? \"Ingesteld\" : \"Nog niet ingesteld\"}\n </Badge>\n )}\n </div>\n <p className=\"text-sm text-slate-600\">\n Mail wordt verstuurd via je eigen mailbox (SMTP): bv. het e-mailadres bij je hostingpartij, of Gmail\n (smtp.gmail.com, poort 465, met een <a className=\"underline\" href=\"https://myaccount.google.com/apppasswords\" target=\"_blank\" rel=\"noreferrer\">app-wachtwoord</a>).\n Deze instelling geldt site-breed: óók formulier-mails lopen erover. Zonder SMTP-instellingen\n valt de mail terug op de centrale XiWebdesign-afzender. De teksten van de mails pas je aan\n onder het tabblad E-mails.\n </p>\n <div className=\"grid gap-4 md:grid-cols-[1fr_110px]\">\n <Field label=\"SMTP-server\">\n <Input value={smtp.host} onChange={(e) => setSmtp((p) => ({ ...p, host: e.target.value }))} placeholder=\"smtp.gmail.com\" />\n </Field>\n <Field label=\"Poort\">\n <Input value={smtp.port} onChange={(e) => setSmtp((p) => ({ ...p, port: e.target.value }))} placeholder=\"465\" />\n </Field>\n </div>\n <Switch checked={smtp.secure} onCheckedChange={(v) => setSmtp((p) => ({ ...p, secure: v }))} label=\"SSL (poort 465). Uit = STARTTLS (poort 587)\" />\n <div className=\"grid gap-4 md:grid-cols-2\">\n <Field label=\"Gebruikersnaam\">\n <Input value={smtp.user} onChange={(e) => setSmtp((p) => ({ ...p, user: e.target.value }))} placeholder=\"info@jouwdomein.nl\" />\n </Field>\n <Field label=\"Wachtwoord\" hint={status?.smtp.configured ? \"Leeg laten = ongewijzigd\" : undefined}>\n <Input type=\"password\" value={smtp.pass} onChange={(e) => setSmtp((p) => ({ ...p, pass: e.target.value }))} />\n </Field>\n </div>\n <div className=\"grid gap-4 md:grid-cols-2\">\n <Field label=\"Afzendernaam\">\n <Input value={smtp.fromName} onChange={(e) => setSmtp((p) => ({ ...p, fromName: e.target.value }))} placeholder=\"Naam van je winkel\" />\n </Field>\n <Field label=\"Afzenderadres\">\n <Input value={smtp.fromEmail} onChange={(e) => setSmtp((p) => ({ ...p, fromEmail: e.target.value }))} placeholder=\"info@jouwdomein.nl\" />\n </Field>\n </div>\n <Field label=\"Notificatie-adres\" hint=\"Hier ontvang je zelf een mail bij elke bestelling\">\n <Input value={smtp.notifyTo} onChange={(e) => setSmtp((p) => ({ ...p, notifyTo: e.target.value }))} placeholder=\"bestellingen@jouwdomein.nl\" />\n </Field>\n <div className=\"flex items-center gap-2\">\n <Button onClick={saveSmtp} disabled={busy || !smtp.host.trim()}>Opslaan</Button>\n <Button variant=\"secondary\" onClick={sendTest} disabled={busy || !status?.smtp.configured}>Stuur testmail</Button>\n </div>\n </div>\n </Card>\n\n <Card className=\"lg:col-span-2\">\n <p className=\"text-sm text-slate-600\">\n Verzendkosten, valuta, ordernummer-prefix en de winkel-paden stel je in onder{\" \"}\n <strong>Instellingen → Modules → Webwinkel</strong>.\n </p>\n </Card>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/shop/ConnectionsTab.tsx"],"sourcesContent":["\"use client\";\n\nimport { useCallback, useEffect, useState } from \"react\";\nimport { Badge, Button, Card, Field, Input } from \"../ui/primitives\";\nimport { Switch } from \"../ui/switch\";\nimport { useToast } from \"../ui/toast\";\nimport { shopCallable } from \"./lib\";\n\ntype PayProvider = \"stripe\" | \"mollie\";\ntype MailProvider = \"resend\" | \"postmark\" | \"smtp\";\n\ninterface ShopStatus {\n provider: PayProvider;\n stripe: { configured: boolean; account?: string; webhookConfigured: boolean };\n mollie: { configured: boolean; profile?: string };\n mail: {\n provider?: MailProvider;\n configured: boolean;\n host?: string;\n port?: number;\n secure?: boolean;\n user?: string;\n fromName?: string;\n fromEmail?: string;\n notifyTo?: string;\n hasResendKey?: boolean;\n hasPostmarkToken?: boolean;\n hasSmtpPass?: boolean;\n };\n}\n\n/** Segmented control voor providerkeuze. */\nfunction Segmented<T extends string>({\n value,\n options,\n onChange,\n}: {\n value: T;\n options: { value: T; label: string }[];\n onChange: (v: T) => void;\n}) {\n return (\n <div className=\"inline-flex rounded-lg border border-slate-200 bg-slate-50 p-0.5\">\n {options.map((o) => (\n <button\n key={o.value}\n type=\"button\"\n onClick={() => onChange(o.value)}\n className={`rounded-md px-3 py-1.5 text-sm font-medium transition ${\n value === o.value ? \"bg-white text-brand-700 shadow-sm\" : \"text-slate-500 hover:text-slate-700\"\n }`}\n >\n {o.label}\n </button>\n ))}\n </div>\n );\n}\n\n/**\n * Koppelingen — betaalprovider (Stripe of Mollie) en mailprovider (Resend, Postmark of eigen SMTP).\n * Geheimen gaan uitsluitend via callables naar `sites/{siteId}/private/*` (deny-all rules); dit\n * paneel toont alleen gemaskeerde status. Alleen een siteadmin kan opslaan.\n */\nexport function ConnectionsTab({ siteId }: { siteId: string }) {\n const { toast } = useToast();\n const [status, setStatus] = useState<ShopStatus | null>(null);\n const [busy, setBusy] = useState(false);\n\n // Betaling\n const [payProvider, setPayProvider] = useState<PayProvider>(\"stripe\");\n const [stripeKey, setStripeKey] = useState(\"\");\n const [mollieKey, setMollieKey] = useState(\"\");\n\n // Mail\n const [mailProvider, setMailProvider] = useState<MailProvider>(\"smtp\");\n const [resendKey, setResendKey] = useState(\"\");\n const [postmarkToken, setPostmarkToken] = useState(\"\");\n const [smtp, setSmtp] = useState({ host: \"\", port: \"465\", secure: true, user: \"\", pass: \"\" });\n const [fromName, setFromName] = useState(\"\");\n const [fromEmail, setFromEmail] = useState(\"\");\n const [notifyTo, setNotifyTo] = useState(\"\");\n\n const refresh = useCallback(async () => {\n try {\n const raw = await shopCallable<{ siteId: string }, Partial<ShopStatus>>(\"getShopStatus\")({ siteId });\n // Normaliseer naar de volledige vorm zodat de render nooit op een ontbrekend deel-object\n // (bv. een oudere functions-versie zonder mollie/mail) kan crashen.\n const s: ShopStatus = {\n provider: raw.provider ?? \"stripe\",\n stripe: raw.stripe ?? { configured: false, webhookConfigured: false },\n mollie: raw.mollie ?? { configured: false },\n mail: raw.mail ?? { configured: false },\n };\n setStatus(s);\n setPayProvider(s.provider ?? \"stripe\");\n const m = s.mail ?? {};\n setMailProvider(m.provider ?? (m.host ? \"smtp\" : m.hasPostmarkToken ? \"postmark\" : m.hasResendKey ? \"resend\" : \"smtp\"));\n setSmtp((p) => ({ ...p, host: m.host ?? p.host, port: String(m.port ?? 465), secure: m.secure !== false, user: m.user ?? p.user }));\n setFromName(m.fromName ?? \"\");\n setFromEmail(m.fromEmail ?? \"\");\n setNotifyTo(m.notifyTo ?? \"\");\n } catch {\n setStatus(null);\n }\n }, [siteId]);\n\n useEffect(() => {\n void refresh();\n }, [refresh]);\n\n async function savePayment() {\n setBusy(true);\n try {\n const payload: Record<string, unknown> = { siteId, provider: payProvider };\n if (payProvider === \"stripe\") {\n if (!stripeKey.trim().startsWith(\"sk_\") && !stripeKey.trim().startsWith(\"rk_\")) {\n toast({ title: \"Ongeldige sleutel\", description: \"Plak een Stripe geheime sleutel (sk_…) of restricted key (rk_…).\", tone: \"error\" });\n setBusy(false);\n return;\n }\n payload.stripe = { secretKey: stripeKey.trim() };\n } else {\n if (!/^(test|live)_/.test(mollieKey.trim())) {\n toast({ title: \"Ongeldige sleutel\", description: \"Plak een Mollie API-sleutel (test_… of live_…).\", tone: \"error\" });\n setBusy(false);\n return;\n }\n payload.mollie = { apiKey: mollieKey.trim() };\n }\n const res = await shopCallable<Record<string, unknown>, { account?: string }>(\"saveShopSecrets\")(payload);\n toast({ title: `${payProvider === \"stripe\" ? \"Stripe\" : \"Mollie\"} gekoppeld`, description: res.account ? `Account: ${res.account}` : undefined, tone: \"success\" });\n setStripeKey(\"\");\n setMollieKey(\"\");\n await refresh();\n } catch (e) {\n toast({ title: \"Koppelen mislukt\", description: e instanceof Error ? e.message : undefined, tone: \"error\" });\n } finally {\n setBusy(false);\n }\n }\n\n async function switchActiveProvider(p: PayProvider) {\n setPayProvider(p);\n // Alleen de actieve provider wisselen als de andere al geconfigureerd is (anders vraagt het formulier eerst een sleutel).\n const otherConfigured = p === \"stripe\" ? status?.stripe.configured : status?.mollie.configured;\n if (otherConfigured) {\n try {\n await shopCallable<{ siteId: string; provider: PayProvider }, unknown>(\"saveShopSecrets\")({ siteId, provider: p });\n await refresh();\n } catch {\n /* stil — de gebruiker kan alsnog opslaan */\n }\n }\n }\n\n async function saveMail() {\n setBusy(true);\n try {\n const mail: Record<string, unknown> = { provider: mailProvider, fromName, fromEmail, notifyTo };\n if (mailProvider === \"resend\") mail.resendApiKey = resendKey.trim() || undefined;\n if (mailProvider === \"postmark\") mail.postmarkToken = postmarkToken.trim() || undefined;\n if (mailProvider === \"smtp\") {\n mail.host = smtp.host.trim();\n mail.port = Number(smtp.port) || 465;\n mail.secure = smtp.secure;\n mail.user = smtp.user.trim();\n if (smtp.pass) mail.pass = smtp.pass;\n }\n await shopCallable<{ siteId: string; mail: Record<string, unknown> }, { ok: boolean }>(\"saveMailSettings\")({ siteId, mail });\n toast({ title: \"E-mailinstellingen opgeslagen\", tone: \"success\" });\n setResendKey(\"\");\n setPostmarkToken(\"\");\n setSmtp((p) => ({ ...p, pass: \"\" }));\n await refresh();\n } catch (e) {\n toast({ title: \"Opslaan mislukt\", description: e instanceof Error ? e.message : undefined, tone: \"error\" });\n } finally {\n setBusy(false);\n }\n }\n\n async function sendTest() {\n setBusy(true);\n try {\n const res = await shopCallable<{ siteId: string }, { to: string }>(\"sendShopTestMail\")({ siteId });\n toast({ title: \"Testmail verstuurd\", description: `Naar ${res.to} — check de inbox (en spam).`, tone: \"success\" });\n } catch (e) {\n toast({ title: \"Testmail mislukt\", description: e instanceof Error ? e.message : undefined, tone: \"error\" });\n } finally {\n setBusy(false);\n }\n }\n\n const payConfigured = payProvider === \"stripe\" ? status?.stripe.configured : status?.mollie.configured;\n\n return (\n <div className=\"grid gap-4\">\n {/* ── Betalingen ─────────────────────────────────────────────── */}\n <Card>\n <div className=\"grid gap-4\">\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <h3 className=\"text-sm font-semibold\">Betalingen</h3>\n {status && (\n <Badge tone={payConfigured ? \"green\" : \"amber\"}>\n {payProvider === \"stripe\"\n ? status.stripe.configured\n ? `Stripe actief${status.stripe.account ? ` — ${status.stripe.account}` : \"\"}`\n : \"Stripe nog niet gekoppeld\"\n : status.mollie.configured\n ? `Mollie actief${status.mollie.profile ? ` — ${status.mollie.profile}` : \"\"}`\n : \"Mollie nog niet gekoppeld\"}\n </Badge>\n )}\n </div>\n\n <Segmented\n value={payProvider}\n onChange={(v) => void switchActiveProvider(v)}\n options={[\n { value: \"stripe\", label: \"Stripe\" },\n { value: \"mollie\", label: \"Mollie\" },\n ]}\n />\n\n {payProvider === \"stripe\" ? (\n <div className=\"grid gap-3\">\n <ol className=\"grid list-decimal gap-1 pl-5 text-sm text-slate-600\">\n <li>Maak een gratis account op <a className=\"underline\" href=\"https://dashboard.stripe.com/register\" target=\"_blank\" rel=\"noreferrer\">stripe.com</a> en activeer het (IBAN, KVK).</li>\n <li>Zet onder <em>Settings → Payment methods</em> iDEAL aan.</li>\n <li>Maak onder <em>Developers → API keys</em> een sleutel aan en plak hem hieronder — de webhook wordt automatisch aangemaakt.</li>\n </ol>\n <Field label=\"Stripe geheime sleutel (sk_live_… of rk_live_…)\" hint=\"Wordt veilig opgeslagen; nooit zichtbaar in de browser\">\n <Input type=\"password\" value={stripeKey} onChange={(e) => setStripeKey(e.target.value)} placeholder=\"sk_live_…\" />\n </Field>\n {status?.stripe.configured && !status.stripe.webhookConfigured && (\n <p className=\"rounded-lg bg-amber-50 px-3 py-2 text-sm text-amber-700\">\n Webhook nog niet geregistreerd — koppel opnieuw met een sleutel die webhook-rechten heeft.\n </p>\n )}\n <div><Button onClick={savePayment} disabled={busy || !stripeKey.trim()}>Koppel Stripe</Button></div>\n </div>\n ) : (\n <div className=\"grid gap-3\">\n <ol className=\"grid list-decimal gap-1 pl-5 text-sm text-slate-600\">\n <li>Maak een account op <a className=\"underline\" href=\"https://www.mollie.com/dashboard/signup\" target=\"_blank\" rel=\"noreferrer\">mollie.com</a> en doorloop de onboarding.</li>\n <li>Zet in het Mollie-dashboard de gewenste betaalmethoden (o.a. iDEAL) aan.</li>\n <li>Kopieer onder <em>Developers → API-keys</em> je live- of test-sleutel en plak hem hieronder. Mollie heeft geen aparte webhook-registratie nodig.</li>\n </ol>\n <Field label=\"Mollie API-sleutel (live_… of test_…)\" hint=\"Wordt veilig opgeslagen; nooit zichtbaar in de browser\">\n <Input type=\"password\" value={mollieKey} onChange={(e) => setMollieKey(e.target.value)} placeholder=\"live_…\" />\n </Field>\n <div><Button onClick={savePayment} disabled={busy || !mollieKey.trim()}>Koppel Mollie</Button></div>\n </div>\n )}\n </div>\n </Card>\n\n {/* ── E-mail ─────────────────────────────────────────────────── */}\n <Card>\n <div className=\"grid gap-4\">\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <h3 className=\"text-sm font-semibold\">E-mail (bestelbevestigingen & formulieren)</h3>\n {status && (\n <Badge tone={status.mail.configured ? \"green\" : \"amber\"}>\n {status.mail.configured ? `${mailProviderLabel(mailProvider)} actief` : \"Nog niet ingesteld\"}\n </Badge>\n )}\n </div>\n\n <Segmented\n value={mailProvider}\n onChange={setMailProvider}\n options={[\n { value: \"smtp\", label: \"Eigen SMTP\" },\n { value: \"resend\", label: \"Resend\" },\n { value: \"postmark\", label: \"Postmark\" },\n ]}\n />\n\n {mailProvider === \"smtp\" && (\n <div className=\"grid gap-3\">\n <p className=\"text-sm text-slate-600\">\n Via je eigen mailbox: bv. het adres bij je hostingpartij, of Gmail (smtp.gmail.com, poort 465, met een{\" \"}\n <a className=\"underline\" href=\"https://myaccount.google.com/apppasswords\" target=\"_blank\" rel=\"noreferrer\">app-wachtwoord</a>).\n </p>\n <div className=\"grid gap-3 md:grid-cols-[1fr_110px]\">\n <Field label=\"SMTP-server\"><Input value={smtp.host} onChange={(e) => setSmtp((p) => ({ ...p, host: e.target.value }))} placeholder=\"smtp.gmail.com\" /></Field>\n <Field label=\"Poort\"><Input value={smtp.port} onChange={(e) => setSmtp((p) => ({ ...p, port: e.target.value }))} placeholder=\"465\" /></Field>\n </div>\n <Switch checked={smtp.secure} onCheckedChange={(v) => setSmtp((p) => ({ ...p, secure: v }))} label=\"SSL (poort 465). Uit = STARTTLS (poort 587)\" />\n <div className=\"grid gap-3 md:grid-cols-2\">\n <Field label=\"Gebruikersnaam\"><Input value={smtp.user} onChange={(e) => setSmtp((p) => ({ ...p, user: e.target.value }))} placeholder=\"info@jouwdomein.nl\" /></Field>\n <Field label=\"Wachtwoord\" hint={status?.mail.hasSmtpPass ? \"Leeg laten = ongewijzigd\" : undefined}>\n <Input type=\"password\" value={smtp.pass} onChange={(e) => setSmtp((p) => ({ ...p, pass: e.target.value }))} />\n </Field>\n </div>\n </div>\n )}\n\n {mailProvider === \"resend\" && (\n <div className=\"grid gap-3\">\n <p className=\"text-sm text-slate-600\">\n <a className=\"underline\" href=\"https://resend.com\" target=\"_blank\" rel=\"noreferrer\">Resend</a> verstuurt vanaf een geverifieerd domein.\n Maak een API-sleutel (re_…) en verifieer je afzenderdomein in het Resend-dashboard.\n </p>\n <Field label=\"Resend API-sleutel (re_…)\" hint={status?.mail.hasResendKey ? \"Leeg laten = ongewijzigd\" : undefined}>\n <Input type=\"password\" value={resendKey} onChange={(e) => setResendKey(e.target.value)} placeholder=\"re_…\" />\n </Field>\n </div>\n )}\n\n {mailProvider === \"postmark\" && (\n <div className=\"grid gap-3\">\n <p className=\"text-sm text-slate-600\">\n <a className=\"underline\" href=\"https://postmarkapp.com\" target=\"_blank\" rel=\"noreferrer\">Postmark</a> verstuurt vanaf een geverifieerde afzender/domein (Sender Signature).\n Kopieer het Server-token.\n </p>\n <Field label=\"Postmark server-token\" hint={status?.mail.hasPostmarkToken ? \"Leeg laten = ongewijzigd\" : undefined}>\n <Input type=\"password\" value={postmarkToken} onChange={(e) => setPostmarkToken(e.target.value)} placeholder=\"xxxxxxxx-xxxx-…\" />\n </Field>\n </div>\n )}\n\n {/* Gedeelde afzender-velden */}\n <div className=\"grid gap-3 md:grid-cols-2\">\n <Field label=\"Afzendernaam\"><Input value={fromName} onChange={(e) => setFromName(e.target.value)} placeholder=\"Christelijk Kraamcadeau\" /></Field>\n <Field label=\"Afzenderadres\"><Input value={fromEmail} onChange={(e) => setFromEmail(e.target.value)} placeholder=\"info@jouwdomein.nl\" /></Field>\n </div>\n <Field label=\"Notificatie-adres\" hint=\"Hier ontvang je zelf een mail bij elke bestelling; ook het doel van de testmail\">\n <Input value={notifyTo} onChange={(e) => setNotifyTo(e.target.value)} placeholder=\"bestellingen@jouwdomein.nl\" />\n </Field>\n\n <div className=\"flex items-center gap-2\">\n <Button onClick={saveMail} disabled={busy}>Opslaan</Button>\n <Button variant=\"secondary\" onClick={sendTest} disabled={busy || !status?.mail.configured}>Stuur testmail</Button>\n </div>\n </div>\n </Card>\n\n <Card>\n <p className=\"text-sm text-slate-600\">\n Verzendkosten, valuta, ordernummer-prefix en bedrijfsgegevens (factuur) stel je in onder het tabblad\n <strong> Instellingen</strong>. Mailteksten pas je aan onder <strong>E-mails</strong>.\n </p>\n </Card>\n </div>\n );\n}\n\nfunction mailProviderLabel(p: MailProvider): string {\n return p === \"smtp\" ? \"Eigen SMTP\" : p === \"resend\" ? \"Resend\" : \"Postmark\";\n}\n"],"mappings":"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","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-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-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-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-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);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.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-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-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-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))}}
|
|
@@ -5,22 +5,72 @@ import { OrderStatusBlockSchema } from "../../../schema/shop";
|
|
|
5
5
|
import { defineBlock } from "../../../blocks/registry";
|
|
6
6
|
import { Card, Heading, Text } from "../../../blocks/ui";
|
|
7
7
|
import { cartClear } from "../cartStore";
|
|
8
|
-
import { readQueryParam, useShopSettings } from "../lib";
|
|
8
|
+
import { getOrderReturnStatusCall, readQueryParam, useShopSettings } from "../lib";
|
|
9
9
|
function OrderStatusRender({ block, siteId }) {
|
|
10
10
|
const settings = useShopSettings();
|
|
11
|
-
const [
|
|
11
|
+
const [view, setView] = useState("loading");
|
|
12
|
+
const [orderNumber, setOrderNumber] = useState(null);
|
|
12
13
|
useEffect(() => {
|
|
13
14
|
const status = readQueryParam("status");
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
const oid = readQueryParam("oid");
|
|
16
|
+
setOrderNumber(readQueryParam("order"));
|
|
17
|
+
if (status === "success") {
|
|
18
|
+
cartClear(siteId);
|
|
19
|
+
setView("success");
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (status === "cancelled") {
|
|
23
|
+
setView("cancelled");
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (!oid) {
|
|
27
|
+
setView("success");
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
let cancelled2 = false;
|
|
31
|
+
let tries = 0;
|
|
32
|
+
const map = (s) => s === "paid" || s === "shipped" ? "success" : s === "cancelled" || s === "refunded" ? "cancelled" : "pending";
|
|
33
|
+
const poll = async () => {
|
|
34
|
+
tries++;
|
|
35
|
+
try {
|
|
36
|
+
const res = await getOrderReturnStatusCall(siteId, oid);
|
|
37
|
+
if (cancelled2) return;
|
|
38
|
+
if (res.number) setOrderNumber(res.number);
|
|
39
|
+
const v = map(res.status);
|
|
40
|
+
if (v === "pending" && tries < 4) {
|
|
41
|
+
setView("pending");
|
|
42
|
+
setTimeout(poll, 1800);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (v === "success") cartClear(siteId);
|
|
46
|
+
setView(v);
|
|
47
|
+
} catch {
|
|
48
|
+
if (!cancelled2) setView("pending");
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
void poll();
|
|
52
|
+
return () => {
|
|
53
|
+
cancelled2 = true;
|
|
54
|
+
};
|
|
16
55
|
}, [siteId]);
|
|
17
|
-
|
|
56
|
+
if (view === "loading" || view === "pending") {
|
|
57
|
+
return /* @__PURE__ */ jsxs(Card, { padding: 4, variant: "muted", className: "xicms-shop-orderstatus", role: "status", children: [
|
|
58
|
+
/* @__PURE__ */ jsx(Heading, { level: 1, children: view === "pending" ? "Je betaling wordt verwerkt\u2026" : "Even geduld\u2026" }),
|
|
59
|
+
/* @__PURE__ */ jsx(Text, { display: "block", style: { marginTop: 8 }, children: "We bevestigen je bestelling zodra de betaling binnen is \u2014 je ontvangt daarvan een e-mail." }),
|
|
60
|
+
orderNumber && /* @__PURE__ */ jsxs(Text, { display: "block", style: { marginTop: 8 }, children: [
|
|
61
|
+
"Je bestelnummer is ",
|
|
62
|
+
/* @__PURE__ */ jsx("strong", { children: orderNumber }),
|
|
63
|
+
"."
|
|
64
|
+
] })
|
|
65
|
+
] });
|
|
66
|
+
}
|
|
67
|
+
const cancelled = view === "cancelled";
|
|
18
68
|
return /* @__PURE__ */ jsxs(Card, { padding: 4, variant: cancelled ? "muted" : "green", className: "xicms-shop-orderstatus", role: "status", children: [
|
|
19
69
|
/* @__PURE__ */ jsx(Heading, { level: 1, "data-xicms-edit": cancelled ? "cancelledTitle" : "successTitle", children: cancelled ? block.cancelledTitle : block.successTitle }),
|
|
20
70
|
/* @__PURE__ */ jsx(Text, { display: "block", style: { marginTop: 8 }, "data-xicms-edit": cancelled ? "cancelledText" : "successText", children: cancelled ? block.cancelledText : block.successText }),
|
|
21
|
-
!cancelled &&
|
|
71
|
+
!cancelled && orderNumber && /* @__PURE__ */ jsxs(Text, { display: "block", style: { marginTop: 8 }, children: [
|
|
22
72
|
"Je bestelnummer is ",
|
|
23
|
-
/* @__PURE__ */ jsx("strong", { children:
|
|
73
|
+
/* @__PURE__ */ jsx("strong", { children: orderNumber }),
|
|
24
74
|
"."
|
|
25
75
|
] }),
|
|
26
76
|
/* @__PURE__ */ jsx(Text, { display: "block", style: { marginTop: 12 }, children: /* @__PURE__ */ jsx("a", { href: cancelled ? settings.cartPath : settings.shopPath, children: cancelled ? "Terug naar je winkelwagen" : "Verder winkelen" }) })
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/modules/shop/blocks/OrderStatus.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport type { z } from \"zod\";\nimport { OrderStatusBlockSchema } from \"../../../schema/shop\";\nimport { defineBlock, type BlockRenderProps } from \"../../../blocks/registry\";\nimport { Card, Heading, Text } from \"../../../blocks/ui\";\nimport { cartClear } from \"../cartStore\";\nimport { readQueryParam, useShopSettings } from \"../lib\";\n\ntype Blk = z.infer<typeof OrderStatusBlockSchema>;\n\n/**\n * Bedankt-/statuspagina na terugkeer
|
|
1
|
+
{"version":3,"sources":["../../../../src/modules/shop/blocks/OrderStatus.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport type { z } from \"zod\";\nimport { OrderStatusBlockSchema } from \"../../../schema/shop\";\nimport { defineBlock, type BlockRenderProps } from \"../../../blocks/registry\";\nimport { Card, Heading, Text } from \"../../../blocks/ui\";\nimport { cartClear } from \"../cartStore\";\nimport { getOrderReturnStatusCall, readQueryParam, useShopSettings } from \"../lib\";\n\ntype Blk = z.infer<typeof OrderStatusBlockSchema>;\ntype View = \"loading\" | \"success\" | \"pending\" | \"cancelled\";\n\n/**\n * Bedankt-/statuspagina na terugkeer van de betaalprovider.\n * - Stripe geeft `?status=success|cancelled` mee (aparte success/cancel-URL's) → direct tonen.\n * - Mollie heeft één redirect-URL voor alle uitkomsten en geeft `?oid=` mee → we vragen de echte\n * orderstatus op via `getOrderReturnStatus` (de webhook is de bron van waarheid; kan een tel\n * nalopen, dus we pollen kort). De cart wordt geleegd zodra de betaling geslaagd/in behandeling is.\n */\nfunction OrderStatusRender({ block, siteId }: BlockRenderProps<Blk>) {\n const settings = useShopSettings();\n const [view, setView] = useState<View>(\"loading\");\n const [orderNumber, setOrderNumber] = useState<string | null>(null);\n\n useEffect(() => {\n const status = readQueryParam(\"status\");\n const oid = readQueryParam(\"oid\");\n setOrderNumber(readQueryParam(\"order\"));\n\n // Stripe-pad: status staat direct in de URL.\n if (status === \"success\") {\n cartClear(siteId);\n setView(\"success\");\n return;\n }\n if (status === \"cancelled\") {\n setView(\"cancelled\");\n return;\n }\n\n // Mollie-pad: echte status opvragen (kort pollen i.v.m. webhook-lag).\n if (!oid) {\n setView(\"success\"); // geen info → toon neutraal-positief\n return;\n }\n let cancelled = false;\n let tries = 0;\n const map = (s: string): View => (s === \"paid\" || s === \"shipped\" ? \"success\" : s === \"cancelled\" || s === \"refunded\" ? \"cancelled\" : \"pending\");\n const poll = async () => {\n tries++;\n try {\n const res = await getOrderReturnStatusCall(siteId, oid);\n if (cancelled) return;\n if (res.number) setOrderNumber(res.number);\n const v = map(res.status);\n if (v === \"pending\" && tries < 4) {\n setView(\"pending\");\n setTimeout(poll, 1800);\n return;\n }\n if (v === \"success\") cartClear(siteId);\n setView(v);\n } catch {\n if (!cancelled) setView(\"pending\");\n }\n };\n void poll();\n return () => {\n cancelled = true;\n };\n }, [siteId]);\n\n if (view === \"loading\" || view === \"pending\") {\n return (\n <Card padding={4} variant=\"muted\" className=\"xicms-shop-orderstatus\" role=\"status\">\n <Heading level={1}>{view === \"pending\" ? \"Je betaling wordt verwerkt…\" : \"Even geduld…\"}</Heading>\n <Text display=\"block\" style={{ marginTop: 8 }}>\n We bevestigen je bestelling zodra de betaling binnen is — je ontvangt daarvan een e-mail.\n </Text>\n {orderNumber && (\n <Text display=\"block\" style={{ marginTop: 8 }}>\n Je bestelnummer is <strong>{orderNumber}</strong>.\n </Text>\n )}\n </Card>\n );\n }\n\n const cancelled = view === \"cancelled\";\n return (\n <Card padding={4} variant={cancelled ? \"muted\" : \"green\"} className=\"xicms-shop-orderstatus\" role=\"status\">\n <Heading level={1} data-xicms-edit={cancelled ? \"cancelledTitle\" : \"successTitle\"}>\n {cancelled ? block.cancelledTitle : block.successTitle}\n </Heading>\n <Text display=\"block\" style={{ marginTop: 8 }} data-xicms-edit={cancelled ? \"cancelledText\" : \"successText\"}>\n {cancelled ? block.cancelledText : block.successText}\n </Text>\n {!cancelled && orderNumber && (\n <Text display=\"block\" style={{ marginTop: 8 }}>\n Je bestelnummer is <strong>{orderNumber}</strong>.\n </Text>\n )}\n <Text display=\"block\" style={{ marginTop: 12 }}>\n <a href={cancelled ? settings.cartPath : settings.shopPath}>\n {cancelled ? \"Terug naar je winkelwagen\" : \"Verder winkelen\"}\n </a>\n </Text>\n </Card>\n );\n}\n\nexport const orderStatusBlock = defineBlock({\n type: \"orderStatus\",\n schema: OrderStatusBlockSchema,\n Render: OrderStatusRender,\n label: \"Bestelstatus (bedankt-pagina)\",\n icon: \"✅\",\n defaults: () => ({}),\n});\n"],"mappings":"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","names":["cancelled"]}
|
|
@@ -39,6 +39,14 @@ export declare function variantLabelFor(options: {
|
|
|
39
39
|
}[], key: string): string;
|
|
40
40
|
/** Querystring-parameter client-side lezen (blocks zijn client components). */
|
|
41
41
|
export declare function readQueryParam(name: string): string | null;
|
|
42
|
+
/**
|
|
43
|
+
* Echte orderstatus opvragen (voor de bedankt-pagina). Nodig bij Mollie: die heeft één
|
|
44
|
+
* redirect-URL voor alle uitkomsten, dus de status moet server-side gecheckt worden.
|
|
45
|
+
*/
|
|
46
|
+
export declare function getOrderReturnStatusCall(siteId: string, orderId: string): Promise<{
|
|
47
|
+
status: string;
|
|
48
|
+
number?: string;
|
|
49
|
+
}>;
|
|
42
50
|
/**
|
|
43
51
|
* Zet een kleurnaam ("Roze", "Korte mouw tshirt (oker)geel") om naar een CSS-kleur voor de
|
|
44
52
|
* swatch-stipjes op productkaarten. Onbekende namen geven null (dan geen stipje).
|
package/dist/modules/shop/lib.js
CHANGED
|
@@ -74,6 +74,13 @@ function readQueryParam(name) {
|
|
|
74
74
|
if (typeof window === "undefined") return null;
|
|
75
75
|
return new URLSearchParams(window.location.search).get(name);
|
|
76
76
|
}
|
|
77
|
+
async function getOrderReturnStatusCall(siteId, orderId) {
|
|
78
|
+
const app = getFirebaseApp();
|
|
79
|
+
if (!app) return { status: "unknown" };
|
|
80
|
+
const fn = httpsCallable(getFunctions(app, functionsRegion()), "getOrderReturnStatus");
|
|
81
|
+
const res = await fn({ siteId, orderId });
|
|
82
|
+
return res.data;
|
|
83
|
+
}
|
|
77
84
|
const KLEUR_CSS = [
|
|
78
85
|
[/fuchsia|fushia/, "#dd8fb4"],
|
|
79
86
|
[/roze|pink/, "#f2c3d3"],
|
|
@@ -103,6 +110,7 @@ export {
|
|
|
103
110
|
fetchActiveProducts,
|
|
104
111
|
fetchProductBySlug,
|
|
105
112
|
fetchShopCategories,
|
|
113
|
+
getOrderReturnStatusCall,
|
|
106
114
|
kleurNaarCss,
|
|
107
115
|
kleurOptie,
|
|
108
116
|
readQueryParam,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/modules/shop/lib.ts"],"sourcesContent":["\"use client\";\n\n/**\n * Client-helpers voor de shop-blocks: producten/categorieën lezen (publieke rules) en de\n * `createOrder`-callable aanroepen. Queries bewust simpel gehouden (één `where` op status,\n * filteren/sorteren client-side) zodat er géén composite-indexes nodig zijn — assortimenten\n * van XiCMS-sites zijn klein (tientallen tot honderden producten).\n */\nimport { collection, getDocs, limit as fsLimit, query, where } from \"firebase/firestore\";\nimport { getFunctions, httpsCallable } from \"firebase/functions\";\nimport { getDb, getFirebaseApp } from \"../../firebase/client\";\nimport { functionsRegion } from \"../../firebase/region\";\nimport type { CreateOrderInput, Product, ProductOption, ShopCategory } from \"../../schema/shop\";\nimport { useModuleSettings } from \"../context\";\nimport { defaultShopSettings, type ShopSettings } from \"./settings\";\n\n/** Publieke shop-instellingen met defaults (paden, verzendkosten, valuta). */\nexport function useShopSettings(): ShopSettings {\n const raw = useModuleSettings<Partial<ShopSettings>>(\"shop\");\n return { ...defaultShopSettings, ...raw };\n}\n\nexport type StoredProduct = Product & { id: string };\nexport type StoredShopCategory = ShopCategory & { id: string };\n\nexport async function fetchActiveProducts(siteId: string): Promise<StoredProduct[]> {\n const db = getDb();\n if (!db) return [];\n const snap = await getDocs(\n query(collection(db, \"sites\", siteId, \"products\"), where(\"status\", \"==\", \"active\"), fsLimit(500)),\n );\n return snap.docs.map((d) => ({ id: d.id, ...(d.data() as Product) }));\n}\n\nexport async function fetchProductBySlug(siteId: string, slug: string): Promise<StoredProduct | null> {\n const db = getDb();\n if (!db || !slug) return null;\n // Het status-filter is verplicht: de rules staan publieke reads alleen toe voor actieve\n // producten, en Firestore weigert query's die niet aantoonbaar binnen die regel blijven.\n // Twee equality-filters → geen composite index nodig (index-merging).\n const snap = await getDocs(\n query(\n collection(db, \"sites\", siteId, \"products\"),\n where(\"slug\", \"==\", slug),\n where(\"status\", \"==\", \"active\"),\n fsLimit(1),\n ),\n );\n const doc = snap.docs[0];\n if (!doc) return null;\n return { id: doc.id, ...(doc.data() as Product) };\n}\n\nexport async function fetchShopCategories(siteId: string): Promise<StoredShopCategory[]> {\n const db = getDb();\n if (!db) return [];\n const snap = await getDocs(query(collection(db, \"sites\", siteId, \"shopCategories\"), fsLimit(100)));\n return snap.docs\n .map((d) => ({ id: d.id, ...(d.data() as ShopCategory) }))\n .filter((c) => c.visible !== false)\n .sort((a, b) => (a.order ?? 0) - (b.order ?? 0));\n}\n\n/** Start de checkout: order + Stripe-sessie server-side; geeft de redirect-URL terug. */\nexport async function createOrderCall(input: CreateOrderInput): Promise<{ url: string; orderNumber: string }> {\n const app = getFirebaseApp();\n if (!app) throw new Error(\"Webwinkel niet beschikbaar.\");\n const fn = httpsCallable(getFunctions(app, functionsRegion()), \"createOrder\");\n const res = await fn(input);\n return res.data as { url: string; orderNumber: string };\n}\n\n/** Valideert een kortingscode server-side; geeft de verwachte korting terug (preview). */\nexport interface DiscountPreview {\n valid: boolean;\n code?: string;\n discountCents?: number;\n freeShipping?: boolean;\n reason?: string;\n}\nexport async function validateDiscountCall(siteId: string, code: string, itemsCents: number): Promise<DiscountPreview> {\n const app = getFirebaseApp();\n if (!app) throw new Error(\"Webwinkel niet beschikbaar.\");\n const fn = httpsCallable(getFunctions(app, functionsRegion()), \"validateDiscountCode\");\n const res = await fn({ siteId, code, itemsCents });\n return res.data as DiscountPreview;\n}\n\n/**\n * Upload een personalisatie-afbeelding (eigen logo e.d.) via de begrensde callable —\n * geeft het Storage-pad terug dat op de orderregel meegaat. Max 5 MB, jpeg/png/webp.\n */\nexport async function uploadPersonalizationCall(\n siteId: string,\n productId: string,\n file: File,\n): Promise<{ path: string }> {\n const app = getFirebaseApp();\n if (!app) throw new Error(\"Webwinkel niet beschikbaar.\");\n if (file.size > 5 * 1024 * 1024) throw new Error(\"TOO_BIG\");\n const bytesBase64 = await new Promise<string>((resolve, reject) => {\n const reader = new FileReader();\n reader.onload = () => resolve(String(reader.result).split(\",\")[1] ?? \"\");\n reader.onerror = () => reject(new Error(\"Bestand lezen mislukt.\"));\n reader.readAsDataURL(file);\n });\n const fn = httpsCallable(getFunctions(app, functionsRegion()), \"uploadPersonalization\");\n const res = await fn({ siteId, productId, fileName: file.name, contentType: file.type, bytesBase64 });\n return res.data as { path: string };\n}\n\n/** Leesbaar variantlabel: opties [\"Maat\",\"Kleur\"] + key \"68|Roze\" → \"Maat 68 · Roze\". */\nexport function variantLabelFor(options: { name: string; values: string[] }[], key: string): string {\n const parts = key.split(\"|\");\n return options.map((o, i) => `${o.name} ${parts[i] ?? \"?\"}`).join(\" · \");\n}\n\n/** Querystring-parameter client-side lezen (blocks zijn client components). */\nexport function readQueryParam(name: string): string | null {\n if (typeof window === \"undefined\") return null;\n return new URLSearchParams(window.location.search).get(name);\n}\n\n/* ── Kleur-swatches ──────────────────────────────────────────────────────── */\n\n// Zachte, pastelachtige swatch-tinten — informatief maar rustig (geen felle vlekken op de\n// kaart). Bewust gedempt t.o.v. de echte stofkleuren; de foto toont de precieze kleur.\nconst KLEUR_CSS: [RegExp, string][] = [\n [/fuchsia|fushia/, \"#dd8fb4\"],\n [/roze|pink/, \"#f2c3d3\"],\n [/blauw|blue/, \"#bcd3ea\"],\n [/mint/, \"#bfe0d2\"],\n [/oker|geel|yellow/, \"#eccf90\"],\n [/wit|white/, \"#f6f3ec\"],\n [/creme|crème|ecru|beige|zand/, \"#f0e7d6\"],\n [/grijs|gray|grey/, \"#c7ccd3\"],\n [/groen|green/, \"#b3d1b3\"],\n [/rood|red|bordeaux/, \"#e0a0a0\"],\n [/paars|purple|lila/, \"#cdbce6\"],\n [/oranje|orange/, \"#f2bd94\"],\n [/bruin|brown/, \"#c3a488\"],\n [/zwart|black|navy|donkerblauw/, \"#6b7488\"],\n];\n\n/**\n * Zet een kleurnaam (\"Roze\", \"Korte mouw tshirt (oker)geel\") om naar een CSS-kleur voor de\n * swatch-stipjes op productkaarten. Onbekende namen geven null (dan geen stipje).\n */\nexport function kleurNaarCss(naam: string): string | null {\n const l = naam.toLowerCase();\n for (const [re, css] of KLEUR_CSS) if (re.test(l)) return css;\n return null;\n}\n\n/** De kleur-optie van een product (naam \"Kleur\"/\"Colour\"/\"Color\"), of null. */\nexport function kleurOptie(product: Pick<Product, \"options\">): ProductOption | null {\n return (product.options ?? []).find((o) => /^kleur|^colou?r/i.test(o.name)) ?? null;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/modules/shop/lib.ts"],"sourcesContent":["\"use client\";\n\n/**\n * Client-helpers voor de shop-blocks: producten/categorieën lezen (publieke rules) en de\n * `createOrder`-callable aanroepen. Queries bewust simpel gehouden (één `where` op status,\n * filteren/sorteren client-side) zodat er géén composite-indexes nodig zijn — assortimenten\n * van XiCMS-sites zijn klein (tientallen tot honderden producten).\n */\nimport { collection, getDocs, limit as fsLimit, query, where } from \"firebase/firestore\";\nimport { getFunctions, httpsCallable } from \"firebase/functions\";\nimport { getDb, getFirebaseApp } from \"../../firebase/client\";\nimport { functionsRegion } from \"../../firebase/region\";\nimport type { CreateOrderInput, Product, ProductOption, ShopCategory } from \"../../schema/shop\";\nimport { useModuleSettings } from \"../context\";\nimport { defaultShopSettings, type ShopSettings } from \"./settings\";\n\n/** Publieke shop-instellingen met defaults (paden, verzendkosten, valuta). */\nexport function useShopSettings(): ShopSettings {\n const raw = useModuleSettings<Partial<ShopSettings>>(\"shop\");\n return { ...defaultShopSettings, ...raw };\n}\n\nexport type StoredProduct = Product & { id: string };\nexport type StoredShopCategory = ShopCategory & { id: string };\n\nexport async function fetchActiveProducts(siteId: string): Promise<StoredProduct[]> {\n const db = getDb();\n if (!db) return [];\n const snap = await getDocs(\n query(collection(db, \"sites\", siteId, \"products\"), where(\"status\", \"==\", \"active\"), fsLimit(500)),\n );\n return snap.docs.map((d) => ({ id: d.id, ...(d.data() as Product) }));\n}\n\nexport async function fetchProductBySlug(siteId: string, slug: string): Promise<StoredProduct | null> {\n const db = getDb();\n if (!db || !slug) return null;\n // Het status-filter is verplicht: de rules staan publieke reads alleen toe voor actieve\n // producten, en Firestore weigert query's die niet aantoonbaar binnen die regel blijven.\n // Twee equality-filters → geen composite index nodig (index-merging).\n const snap = await getDocs(\n query(\n collection(db, \"sites\", siteId, \"products\"),\n where(\"slug\", \"==\", slug),\n where(\"status\", \"==\", \"active\"),\n fsLimit(1),\n ),\n );\n const doc = snap.docs[0];\n if (!doc) return null;\n return { id: doc.id, ...(doc.data() as Product) };\n}\n\nexport async function fetchShopCategories(siteId: string): Promise<StoredShopCategory[]> {\n const db = getDb();\n if (!db) return [];\n const snap = await getDocs(query(collection(db, \"sites\", siteId, \"shopCategories\"), fsLimit(100)));\n return snap.docs\n .map((d) => ({ id: d.id, ...(d.data() as ShopCategory) }))\n .filter((c) => c.visible !== false)\n .sort((a, b) => (a.order ?? 0) - (b.order ?? 0));\n}\n\n/** Start de checkout: order + Stripe-sessie server-side; geeft de redirect-URL terug. */\nexport async function createOrderCall(input: CreateOrderInput): Promise<{ url: string; orderNumber: string }> {\n const app = getFirebaseApp();\n if (!app) throw new Error(\"Webwinkel niet beschikbaar.\");\n const fn = httpsCallable(getFunctions(app, functionsRegion()), \"createOrder\");\n const res = await fn(input);\n return res.data as { url: string; orderNumber: string };\n}\n\n/** Valideert een kortingscode server-side; geeft de verwachte korting terug (preview). */\nexport interface DiscountPreview {\n valid: boolean;\n code?: string;\n discountCents?: number;\n freeShipping?: boolean;\n reason?: string;\n}\nexport async function validateDiscountCall(siteId: string, code: string, itemsCents: number): Promise<DiscountPreview> {\n const app = getFirebaseApp();\n if (!app) throw new Error(\"Webwinkel niet beschikbaar.\");\n const fn = httpsCallable(getFunctions(app, functionsRegion()), \"validateDiscountCode\");\n const res = await fn({ siteId, code, itemsCents });\n return res.data as DiscountPreview;\n}\n\n/**\n * Upload een personalisatie-afbeelding (eigen logo e.d.) via de begrensde callable —\n * geeft het Storage-pad terug dat op de orderregel meegaat. Max 5 MB, jpeg/png/webp.\n */\nexport async function uploadPersonalizationCall(\n siteId: string,\n productId: string,\n file: File,\n): Promise<{ path: string }> {\n const app = getFirebaseApp();\n if (!app) throw new Error(\"Webwinkel niet beschikbaar.\");\n if (file.size > 5 * 1024 * 1024) throw new Error(\"TOO_BIG\");\n const bytesBase64 = await new Promise<string>((resolve, reject) => {\n const reader = new FileReader();\n reader.onload = () => resolve(String(reader.result).split(\",\")[1] ?? \"\");\n reader.onerror = () => reject(new Error(\"Bestand lezen mislukt.\"));\n reader.readAsDataURL(file);\n });\n const fn = httpsCallable(getFunctions(app, functionsRegion()), \"uploadPersonalization\");\n const res = await fn({ siteId, productId, fileName: file.name, contentType: file.type, bytesBase64 });\n return res.data as { path: string };\n}\n\n/** Leesbaar variantlabel: opties [\"Maat\",\"Kleur\"] + key \"68|Roze\" → \"Maat 68 · Roze\". */\nexport function variantLabelFor(options: { name: string; values: string[] }[], key: string): string {\n const parts = key.split(\"|\");\n return options.map((o, i) => `${o.name} ${parts[i] ?? \"?\"}`).join(\" · \");\n}\n\n/** Querystring-parameter client-side lezen (blocks zijn client components). */\nexport function readQueryParam(name: string): string | null {\n if (typeof window === \"undefined\") return null;\n return new URLSearchParams(window.location.search).get(name);\n}\n\n/**\n * Echte orderstatus opvragen (voor de bedankt-pagina). Nodig bij Mollie: die heeft één\n * redirect-URL voor alle uitkomsten, dus de status moet server-side gecheckt worden.\n */\nexport async function getOrderReturnStatusCall(\n siteId: string,\n orderId: string,\n): Promise<{ status: string; number?: string }> {\n const app = getFirebaseApp();\n if (!app) return { status: \"unknown\" };\n const fn = httpsCallable(getFunctions(app, functionsRegion()), \"getOrderReturnStatus\");\n const res = await fn({ siteId, orderId });\n return res.data as { status: string; number?: string };\n}\n\n/* ── Kleur-swatches ──────────────────────────────────────────────────────── */\n\n// Zachte, pastelachtige swatch-tinten — informatief maar rustig (geen felle vlekken op de\n// kaart). Bewust gedempt t.o.v. de echte stofkleuren; de foto toont de precieze kleur.\nconst KLEUR_CSS: [RegExp, string][] = [\n [/fuchsia|fushia/, \"#dd8fb4\"],\n [/roze|pink/, \"#f2c3d3\"],\n [/blauw|blue/, \"#bcd3ea\"],\n [/mint/, \"#bfe0d2\"],\n [/oker|geel|yellow/, \"#eccf90\"],\n [/wit|white/, \"#f6f3ec\"],\n [/creme|crème|ecru|beige|zand/, \"#f0e7d6\"],\n [/grijs|gray|grey/, \"#c7ccd3\"],\n [/groen|green/, \"#b3d1b3\"],\n [/rood|red|bordeaux/, \"#e0a0a0\"],\n [/paars|purple|lila/, \"#cdbce6\"],\n [/oranje|orange/, \"#f2bd94\"],\n [/bruin|brown/, \"#c3a488\"],\n [/zwart|black|navy|donkerblauw/, \"#6b7488\"],\n];\n\n/**\n * Zet een kleurnaam (\"Roze\", \"Korte mouw tshirt (oker)geel\") om naar een CSS-kleur voor de\n * swatch-stipjes op productkaarten. Onbekende namen geven null (dan geen stipje).\n */\nexport function kleurNaarCss(naam: string): string | null {\n const l = naam.toLowerCase();\n for (const [re, css] of KLEUR_CSS) if (re.test(l)) return css;\n return null;\n}\n\n/** De kleur-optie van een product (naam \"Kleur\"/\"Colour\"/\"Color\"), of null. */\nexport function kleurOptie(product: Pick<Product, \"options\">): ProductOption | null {\n return (product.options ?? []).find((o) => /^kleur|^colou?r/i.test(o.name)) ?? null;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -11,7 +11,7 @@ const shopModule = defineModule({
|
|
|
11
11
|
id: "shop",
|
|
12
12
|
version: "1.0.0",
|
|
13
13
|
label: "Webwinkel",
|
|
14
|
-
description: "Webwinkel + donaties: producten met varianten, winkelwagen, afrekenen via Stripe (eigen account van de klant) en bestelbevestiging per e-mail via de eigen SMTP-mailbox.",
|
|
14
|
+
description: "Webwinkel + donaties: producten met varianten, winkelwagen, afrekenen via Stripe \xF3f Mollie (eigen account van de klant) en bestelbevestiging per e-mail via Resend, Postmark of de eigen SMTP-mailbox.",
|
|
15
15
|
defaultEnabled: false,
|
|
16
16
|
blocks: [
|
|
17
17
|
shopCategoriesBlock,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/modules/shop/module.ts"],"sourcesContent":["/**\n * Shop-module (5.5) — webwinkel + donaties op het eigen Stripe-account van de klant.\n *\n * - Producten/categorieën: `sites/{siteId}/products` + `shopCategories` (editor-write, rules).\n * - Checkout: `createOrder`-callable herrekent bedragen server-side en start een hosted\n * Stripe Checkout-sessie (iDEAL/kaart); de `shopStripeWebhook` zet de order op betaald en\n * verstuurt de bevestigingsmail (eigen SMTP van de klant, bv. Gmail — zie functions/lib/mail).\n * - Donaties: het donatie-block gebruikt dezelfde order-flow (kind:\"donation\", geen verzending).\n * - Beheer: het \"Webwinkel\"-paneel (producten/categorieën/bestellingen/koppelingen) hangt via\n * de paneel-registry aan deze module-id.\n *\n * Server-safe: geen \"use client\" hier — de blocks zelf zijn client components.\n */\nimport { defineModule } from \"../defineModule\";\nimport { ShopSettingsSchema, defaultShopSettings } from \"./settings\";\nimport { shopCategoriesBlock } from \"./blocks/ShopCategories\";\nimport { productGridBlock } from \"./blocks/ProductGrid\";\nimport { productDetailBlock } from \"./blocks/ProductDetail\";\nimport { cartBlock } from \"./blocks/Cart\";\nimport { checkoutBlock } from \"./blocks/Checkout\";\nimport { donationBlock } from \"./blocks/Donation\";\nimport { orderStatusBlock } from \"./blocks/OrderStatus\";\n\nexport const shopModule = defineModule({\n id: \"shop\",\n version: \"1.0.0\",\n label: \"Webwinkel\",\n description:\n \"Webwinkel + donaties: producten met varianten, winkelwagen, afrekenen via Stripe (eigen account van de klant) en bestelbevestiging per e-mail via de eigen SMTP-mailbox.\",\n defaultEnabled: false,\n blocks: [\n shopCategoriesBlock,\n productGridBlock,\n productDetailBlock,\n cartBlock,\n checkoutBlock,\n donationBlock,\n orderStatusBlock,\n ],\n settingsSchema: ShopSettingsSchema,\n defaultSettings: defaultShopSettings,\n requires: [\n { kind: \"function\", name: \"createOrder\" },\n { kind: \"function\", name: \"shopStripeWebhook\" },\n { kind: \"function\", name: \"saveShopSecrets\" },\n { kind: \"function\", name: \"updateOrderStatus\" },\n { kind: \"function\", name: \"validateDiscountCode\" },\n { kind: \"function\", name: \"uploadPersonalization\" },\n { kind: \"firestore\", collection: \"products\" },\n { kind: \"firestore\", collection: \"shopCategories\" },\n { kind: \"firestore\", collection: \"orders\" },\n { kind: \"firestore\", collection: \"discountCodes\" },\n { kind: \"storage\", prefix: \"sites/\" },\n ],\n});\n"],"mappings":"AAaA,SAAS,oBAAoB;AAC7B,SAAS,oBAAoB,2BAA2B;AACxD,SAAS,2BAA2B;AACpC,SAAS,wBAAwB;AACjC,SAAS,0BAA0B;AACnC,SAAS,iBAAiB;AAC1B,SAAS,qBAAqB;AAC9B,SAAS,qBAAqB;AAC9B,SAAS,wBAAwB;AAE1B,MAAM,aAAa,aAAa;AAAA,EACrC,IAAI;AAAA,EACJ,SAAS;AAAA,EACT,OAAO;AAAA,EACP,aACE;AAAA,EACF,gBAAgB;AAAA,EAChB,QAAQ;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA,gBAAgB;AAAA,EAChB,iBAAiB;AAAA,EACjB,UAAU;AAAA,IACR,EAAE,MAAM,YAAY,MAAM,cAAc;AAAA,IACxC,EAAE,MAAM,YAAY,MAAM,oBAAoB;AAAA,IAC9C,EAAE,MAAM,YAAY,MAAM,kBAAkB;AAAA,IAC5C,EAAE,MAAM,YAAY,MAAM,oBAAoB;AAAA,IAC9C,EAAE,MAAM,YAAY,MAAM,uBAAuB;AAAA,IACjD,EAAE,MAAM,YAAY,MAAM,wBAAwB;AAAA,IAClD,EAAE,MAAM,aAAa,YAAY,WAAW;AAAA,IAC5C,EAAE,MAAM,aAAa,YAAY,iBAAiB;AAAA,IAClD,EAAE,MAAM,aAAa,YAAY,SAAS;AAAA,IAC1C,EAAE,MAAM,aAAa,YAAY,gBAAgB;AAAA,IACjD,EAAE,MAAM,WAAW,QAAQ,SAAS;AAAA,EACtC;AACF,CAAC;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/modules/shop/module.ts"],"sourcesContent":["/**\n * Shop-module (5.5) — webwinkel + donaties op het eigen Stripe-account van de klant.\n *\n * - Producten/categorieën: `sites/{siteId}/products` + `shopCategories` (editor-write, rules).\n * - Checkout: `createOrder`-callable herrekent bedragen server-side en start een hosted\n * Stripe Checkout-sessie (iDEAL/kaart); de `shopStripeWebhook` zet de order op betaald en\n * verstuurt de bevestigingsmail (eigen SMTP van de klant, bv. Gmail — zie functions/lib/mail).\n * - Donaties: het donatie-block gebruikt dezelfde order-flow (kind:\"donation\", geen verzending).\n * - Beheer: het \"Webwinkel\"-paneel (producten/categorieën/bestellingen/koppelingen) hangt via\n * de paneel-registry aan deze module-id.\n *\n * Server-safe: geen \"use client\" hier — de blocks zelf zijn client components.\n */\nimport { defineModule } from \"../defineModule\";\nimport { ShopSettingsSchema, defaultShopSettings } from \"./settings\";\nimport { shopCategoriesBlock } from \"./blocks/ShopCategories\";\nimport { productGridBlock } from \"./blocks/ProductGrid\";\nimport { productDetailBlock } from \"./blocks/ProductDetail\";\nimport { cartBlock } from \"./blocks/Cart\";\nimport { checkoutBlock } from \"./blocks/Checkout\";\nimport { donationBlock } from \"./blocks/Donation\";\nimport { orderStatusBlock } from \"./blocks/OrderStatus\";\n\nexport const shopModule = defineModule({\n id: \"shop\",\n version: \"1.0.0\",\n label: \"Webwinkel\",\n description:\n \"Webwinkel + donaties: producten met varianten, winkelwagen, afrekenen via Stripe óf Mollie (eigen account van de klant) en bestelbevestiging per e-mail via Resend, Postmark of de eigen SMTP-mailbox.\",\n defaultEnabled: false,\n blocks: [\n shopCategoriesBlock,\n productGridBlock,\n productDetailBlock,\n cartBlock,\n checkoutBlock,\n donationBlock,\n orderStatusBlock,\n ],\n settingsSchema: ShopSettingsSchema,\n defaultSettings: defaultShopSettings,\n requires: [\n { kind: \"function\", name: \"createOrder\" },\n { kind: \"function\", name: \"shopStripeWebhook\" },\n { kind: \"function\", name: \"saveShopSecrets\" },\n { kind: \"function\", name: \"updateOrderStatus\" },\n { kind: \"function\", name: \"validateDiscountCode\" },\n { kind: \"function\", name: \"uploadPersonalization\" },\n { kind: \"firestore\", collection: \"products\" },\n { kind: \"firestore\", collection: \"shopCategories\" },\n { kind: \"firestore\", collection: \"orders\" },\n { kind: \"firestore\", collection: \"discountCodes\" },\n { kind: \"storage\", prefix: \"sites/\" },\n ],\n});\n"],"mappings":"AAaA,SAAS,oBAAoB;AAC7B,SAAS,oBAAoB,2BAA2B;AACxD,SAAS,2BAA2B;AACpC,SAAS,wBAAwB;AACjC,SAAS,0BAA0B;AACnC,SAAS,iBAAiB;AAC1B,SAAS,qBAAqB;AAC9B,SAAS,qBAAqB;AAC9B,SAAS,wBAAwB;AAE1B,MAAM,aAAa,aAAa;AAAA,EACrC,IAAI;AAAA,EACJ,SAAS;AAAA,EACT,OAAO;AAAA,EACP,aACE;AAAA,EACF,gBAAgB;AAAA,EAChB,QAAQ;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA,gBAAgB;AAAA,EAChB,iBAAiB;AAAA,EACjB,UAAU;AAAA,IACR,EAAE,MAAM,YAAY,MAAM,cAAc;AAAA,IACxC,EAAE,MAAM,YAAY,MAAM,oBAAoB;AAAA,IAC9C,EAAE,MAAM,YAAY,MAAM,kBAAkB;AAAA,IAC5C,EAAE,MAAM,YAAY,MAAM,oBAAoB;AAAA,IAC9C,EAAE,MAAM,YAAY,MAAM,uBAAuB;AAAA,IACjD,EAAE,MAAM,YAAY,MAAM,wBAAwB;AAAA,IAClD,EAAE,MAAM,aAAa,YAAY,WAAW;AAAA,IAC5C,EAAE,MAAM,aAAa,YAAY,iBAAiB;AAAA,IAClD,EAAE,MAAM,aAAa,YAAY,SAAS;AAAA,IAC1C,EAAE,MAAM,aAAa,YAAY,gBAAgB;AAAA,IACjD,EAAE,MAAM,WAAW,QAAQ,SAAS;AAAA,EACtC;AACF,CAAC;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xiwebdesign/xicms-core",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.8.1",
|
|
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": {
|