@flowselections/actie-mail-kwekers-erp-mathot 1.0.28 → 1.0.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist-lib/components/AanbiedingsmailtjePage.d.ts.map +1 -1
  2. package/dist-lib/components/AanbiedingsmailtjePage.js +34 -19
  3. package/dist-lib/components/ActiePage.d.ts.map +1 -1
  4. package/dist-lib/components/ActiePage.js +17 -8
  5. package/dist-lib/components/CampagneDetail.d.ts.map +1 -1
  6. package/dist-lib/components/CampagneDetail.js +3 -2
  7. package/dist-lib/components/CampagneOverzicht.js +6 -6
  8. package/dist-lib/components/aanbieding/EmailPreview.js +2 -2
  9. package/dist-lib/components/overzicht/NietGekoppeldeBestellingen.d.ts.map +1 -1
  10. package/dist-lib/components/overzicht/NietGekoppeldeBestellingen.js +2 -2
  11. package/dist-lib/components/overzicht/PrestatieTegels.js +1 -1
  12. package/dist-lib/components/overzicht/RecenteCampagnes.js +1 -1
  13. package/dist-lib/components/overzicht/TopLijsten.js +1 -1
  14. package/dist-lib/components/settings/HuisstijlSettingsCard.d.ts.map +1 -1
  15. package/dist-lib/components/settings/HuisstijlSettingsCard.js +9 -18
  16. package/dist-lib/components/settings/StickerSettingsCard.d.ts.map +1 -1
  17. package/dist-lib/components/settings/StickerSettingsCard.js +3 -7
  18. package/dist-lib/components/settings/TrackingSettingsCard.d.ts.map +1 -1
  19. package/dist-lib/components/settings/TrackingSettingsCard.js +3 -4
  20. package/dist-lib/components/settings/UitsluitingenSettingsCard.d.ts.map +1 -1
  21. package/dist-lib/components/settings/UitsluitingenSettingsCard.js +3 -4
  22. package/dist-lib/index.d.ts +16 -16
  23. package/dist-lib/index.d.ts.map +1 -1
  24. package/dist-lib/index.js +20 -22
  25. package/dist-lib/integrations/supabase/auth-attacher.js +1 -1
  26. package/dist-lib/integrations/supabase/auth-middleware.d.ts +1 -1
  27. package/dist-lib/integrations/supabase/client.server.d.ts +1 -1
  28. package/dist-lib/integrations/supabase/types.d.ts +7297 -281
  29. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  30. package/dist-lib/integrations/supabase/types.js +1 -0
  31. package/dist-lib/lib/branding.functions.js +3 -3
  32. package/dist-lib/lib/campaigns.functions.d.ts.map +1 -1
  33. package/dist-lib/lib/campaigns.functions.js +10 -3
  34. package/dist-lib/lib/offer-settings.functions.d.ts +23 -0
  35. package/dist-lib/lib/offer-settings.functions.d.ts.map +1 -0
  36. package/dist-lib/lib/offer-settings.functions.js +59 -0
  37. package/dist-lib/lib/pricing.d.ts +3 -1
  38. package/dist-lib/lib/pricing.d.ts.map +1 -1
  39. package/dist-lib/lib/pricing.js +16 -2
  40. package/dist-lib/lib/public-offer.functions.d.ts +9 -0
  41. package/dist-lib/lib/public-offer.functions.d.ts.map +1 -1
  42. package/dist-lib/lib/public-offer.functions.js +62 -16
  43. package/dist-lib/lib/stats.functions.d.ts +4 -2
  44. package/dist-lib/lib/stats.functions.d.ts.map +1 -1
  45. package/dist-lib/lib/unlinked-offer-orders.functions.d.ts +1 -0
  46. package/dist-lib/lib/unlinked-offer-orders.functions.d.ts.map +1 -1
  47. package/dist-lib/lib/unlinked-offer-orders.functions.js +1 -1
  48. package/package.json +3 -3
@@ -1 +1 @@
1
- {"version":3,"file":"AanbiedingsmailtjePage.d.ts","sourceRoot":"","sources":["../../src/components/AanbiedingsmailtjePage.tsx"],"names":[],"mappings":"AAqDA,wBAAgB,sBAAsB,gCAmgCrC"}
1
+ {"version":3,"file":"AanbiedingsmailtjePage.d.ts","sourceRoot":"","sources":["../../src/components/AanbiedingsmailtjePage.tsx"],"names":[],"mappings":"AAuDA,wBAAgB,sBAAsB,gCA2hCrC"}
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useState, useEffect, useMemo } from "react";
3
- import { useAuth, supabase, Button, Card, CardContent, CardDescription, CardHeader, CardTitle, Input, Label, Textarea, Badge, Separator, Skeleton, Switch, toast, } from "@flowselections/core";
3
+ import { useAuth, supabase, Button, Card, CardContent, CardDescription, CardHeader, CardTitle, Input, Label, Textarea, Badge, Separator, Skeleton, Switch, toast, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@flowselections/core";
4
4
  import { useServerFn } from "@tanstack/react-start";
5
5
  import { useNavigate } from "@tanstack/react-router";
6
6
  import { Mail, Send, Search, X, Package, Users, Percent, Tag, BarChart3, Info, ArrowLeft, ArrowRight, CheckCircle2, Check } from "lucide-react";
@@ -21,7 +21,8 @@ export function AanbiedingsmailtjePage() {
21
21
  const [busy, setBusy] = useState(false);
22
22
  const [step, setStep] = useState(1);
23
23
  const [maxStep, setMaxStep] = useState(1);
24
- const [name, setName] = useState("Nieuwe aanbieding");
24
+ const [name, setName] = useState("");
25
+ const [confirmSend, setConfirmSend] = useState(false);
25
26
  const [subject, setSubject] = useState("🌷 Speciale aanbieding voor u!");
26
27
  const [fromName, setFromName] = useState("");
27
28
  const [fromEmail, setFromEmail] = useState(user?.email ?? "");
@@ -250,30 +251,35 @@ export function AanbiedingsmailtjePage() {
250
251
  if (existing) {
251
252
  return;
252
253
  }
254
+ // Startprijs per fust = stukprijs × planten per fust; alleen als beide bekend en > 0, anders leeg.
255
+ const unit = Number(p.sale_price ?? 0);
256
+ const ppc = Number(p.plants_per_crate ?? 0);
257
+ const startCrate = unit > 0 && ppc > 0 ? round2(unit * ppc) : 0;
253
258
  setSelProducts((prev) => [...prev, {
254
259
  product_id: p.id ?? `tmp:${p.product}`,
255
260
  product_name: p.product ?? "(naamloos)",
256
261
  description: "",
257
262
  image_url: p.image_url,
258
- normal_price: 0,
263
+ normal_price: startCrate,
259
264
  discount_price: 0,
260
265
  available_stock: p.quantity ?? 0,
261
266
  plants_per_crate: p.plants_per_crate ?? 0,
262
267
  belading: p.belading ?? "",
263
268
  barcode: p.barcode ?? "",
264
- crate_price: 0,
265
- base_crate_price: 0,
269
+ crate_price: startCrate,
270
+ base_crate_price: startCrate,
266
271
  sticker_cost: null,
267
272
  sticker_available: true,
268
- tiers: [{ min_crates: 1, price_per_crate: 0 }],
269
- base_tiers: [{ min_crates: 1, price_per_crate: 0 }],
273
+ tiers: [],
274
+ base_tiers: [],
270
275
  }]);
271
276
  };
272
277
  const removeProduct = (id) => setSelProducts((prev) => prev.filter((p) => p.product_id !== id));
273
278
  const updateProd = (id, patch) => setSelProducts((prev) => prev.map((p) => (p.product_id === id ? { ...p, ...patch } : p)));
274
279
  /** Originele (ongekorte) referenties van een product. */
275
280
  const baseOf = (p) => {
276
- const baseTiers = (p.base_tiers && p.base_tiers.length > 0 ? p.base_tiers : p.tiers) ?? [];
281
+ const baseTiers = ((p.base_tiers && p.base_tiers.length > 0 ? p.base_tiers : p.tiers) ?? [])
282
+ .filter((t) => Number.isFinite(Number(t.price_per_crate)) && Number(t.price_per_crate) > 0);
277
283
  const baseCrate = p.base_crate_price > 0
278
284
  ? p.base_crate_price
279
285
  : Math.max(p.crate_price, p.normal_price, 0);
@@ -457,8 +463,14 @@ export function AanbiedingsmailtjePage() {
457
463
  recipient_email: c.email,
458
464
  }));
459
465
  // Blokkeer ongeldige staffels (bijv. dubbele "vanaf"-aantallen) vóór opslaan.
466
+ if (!name.trim()) {
467
+ toast.error("Geef de aanbieding een naam");
468
+ setBusy(false);
469
+ return;
470
+ }
460
471
  for (const p of selProducts) {
461
- const blocking = validateTiers(p.tiers).find((i) => i.level === "error");
472
+ const filled = p.tiers.filter((t) => Number.isFinite(Number(t.price_per_crate)));
473
+ const blocking = validateTiers(filled, baseOf(p).baseCrate).find((i) => i.level === "error");
462
474
  if (blocking) {
463
475
  toast.error(`${p.product_name}: ${blocking.message}`);
464
476
  setBusy(false);
@@ -466,11 +478,12 @@ export function AanbiedingsmailtjePage() {
466
478
  }
467
479
  }
468
480
  const res = await createFn({ data: {
469
- name, subject, from_name: fromName || (user?.email ?? "FlowSelections"),
481
+ name: name.trim(), subject, from_name: fromName || (user?.email ?? "FlowSelections"),
470
482
  from_email: fromEmail, intro_message: intro,
471
483
  products: selProducts.map((p, i) => {
472
484
  const { base_tiers: _bt, base_crate_price: _bc, ...rest } = p;
473
- return { ...rest, tiers: sortTiers(p.tiers), sort_order: i };
485
+ // Staffels zonder ingevulde prijs worden niet opgeslagen.
486
+ return { ...rest, tiers: sortTiers(p.tiers.filter((t) => Number.isFinite(Number(t.price_per_crate)))), sort_order: i };
474
487
  }),
475
488
  recipients,
476
489
  } });
@@ -541,8 +554,8 @@ export function AanbiedingsmailtjePage() {
541
554
  const sorted = sortTiers(p.tiers);
542
555
  const last = sorted[sorted.length - 1];
543
556
  const nextMin = last ? last.min_crates + 5 : 1;
544
- const basePrice = p.base_crate_price > 0 ? p.base_crate_price : (last?.price_per_crate ?? 0);
545
- const newTier = { min_crates: nextMin, price_per_crate: basePrice };
557
+ // Nieuwe staffel start met een leeg prijsveld (nooit € 0).
558
+ const newTier = { min_crates: nextMin, price_per_crate: Number.NaN };
546
559
  updateProd(p.product_id, {
547
560
  tiers: [...p.tiers, newTier],
548
561
  base_tiers: [...(p.base_tiers ?? p.tiers), newTier],
@@ -554,9 +567,10 @@ export function AanbiedingsmailtjePage() {
554
567
  base_tiers: (p.base_tiers ?? p.tiers).filter((_, i) => i !== idx),
555
568
  });
556
569
  };
557
- const tierIssues = validateTiers(p.tiers);
558
- return (_jsxs("div", { className: "border rounded-md p-3 space-y-3 bg-muted/30", children: [_jsxs("div", { className: "flex items-start justify-between gap-2", children: [_jsxs("div", { className: "flex items-start gap-3 min-w-0", children: [_jsx("div", { className: "h-14 w-14 shrink-0 rounded-md border bg-muted overflow-hidden flex items-center justify-center", children: p.image_url ? (_jsx("img", { src: p.image_url, alt: p.product_name, className: "h-full w-full object-cover" })) : (_jsx(Package, { className: "h-6 w-6 text-muted-foreground" })) }), _jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "font-medium text-sm truncate", children: p.product_name }), _jsxs("div", { className: "text-xs text-muted-foreground", children: ["Originele prijs: \u20AC ", p.normal_price.toFixed(2)] })] })] }), _jsx(Button, { variant: "ghost", size: "icon", onClick: () => removeProduct(p.product_id), children: _jsx(X, { className: "h-4 w-4" }) })] }), _jsxs("div", { className: "grid grid-cols-2 gap-2", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Label, { className: "text-xs w-24 shrink-0", children: "Planten/fust:" }), _jsx("span", { className: "text-sm font-medium", children: p.plants_per_crate || "—" })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Label, { className: "text-xs w-20 shrink-0", children: "Belading:" }), _jsx("span", { className: "text-sm font-medium", children: p.belading || "—" })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Label, { className: "text-xs w-24 shrink-0", children: "Barcode:" }), _jsx("span", { className: "text-sm font-medium", children: p.barcode || "—" })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Label, { className: "text-xs w-20 shrink-0", children: "Fustprijs: \u20AC" }), _jsx(Input, { type: "number", step: "0.01", value: p.crate_price, onChange: (e) => {
559
- const newPrice = round2(Number(e.target.value));
570
+ const tierIssues = validateTiers(p.tiers, baseOf(p).baseCrate);
571
+ return (_jsxs("div", { className: "border rounded-md p-3 space-y-3 bg-muted/30", children: [_jsxs("div", { className: "flex items-start justify-between gap-2", children: [_jsxs("div", { className: "flex items-start gap-3 min-w-0", children: [_jsx("div", { className: "h-14 w-14 shrink-0 rounded-md border bg-muted overflow-hidden flex items-center justify-center", children: p.image_url ? (_jsx("img", { src: p.image_url, alt: p.product_name, className: "h-full w-full object-cover" })) : (_jsx(Package, { className: "h-6 w-6 text-muted-foreground" })) }), _jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "font-medium text-sm truncate", children: p.product_name }), _jsx("div", { className: "text-xs text-muted-foreground", children: p.normal_price > 0 ? `Originele prijs: ${fmtEuroNL(p.normal_price)}` : "Geen prijs bekend — vul de fustprijs in" })] })] }), _jsx(Button, { variant: "ghost", size: "icon", onClick: () => removeProduct(p.product_id), children: _jsx(X, { className: "h-4 w-4" }) })] }), _jsxs("div", { className: "grid grid-cols-2 gap-2", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Label, { className: "text-xs w-24 shrink-0", children: "Planten/fust:" }), _jsx("span", { className: "text-sm font-medium", children: p.plants_per_crate || "—" })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Label, { className: "text-xs w-20 shrink-0", children: "Belading:" }), _jsx("span", { className: "text-sm font-medium", children: p.belading || "—" })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Label, { className: "text-xs w-24 shrink-0", children: "Barcode:" }), _jsx("span", { className: "text-sm font-medium", children: p.barcode || "—" })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Label, { className: "text-xs w-20 shrink-0", children: "Fustprijs: \u20AC" }), _jsx(Input, { type: "number", step: "0.01", min: "0", placeholder: "Vul de fustprijs in", value: p.crate_price > 0 ? p.crate_price : "", onChange: (e) => {
572
+ const raw = Number(e.target.value);
573
+ const newPrice = e.target.value === "" || !Number.isFinite(raw) || raw < 0 ? 0 : round2(raw);
560
574
  // Fustprijs is de originele adviesprijs; staffelprijzen blijven ongemoeid.
561
575
  updateProd(p.product_id, {
562
576
  crate_price: newPrice,
@@ -566,13 +580,14 @@ export function AanbiedingsmailtjePage() {
566
580
  });
567
581
  } })] })] }), _jsx("p", { className: "text-xs text-muted-foreground -mt-1", children: "Plantgegevens komen automatisch uit het product (voorraad) en zijn hier niet aanpasbaar." }), _jsxs("div", { className: "flex flex-wrap items-center gap-2 border-t pt-2", children: [_jsx("span", { className: "text-xs text-muted-foreground", children: "Korting dit product:" }), [10, 15, 20, 25, 30].map((pct) => (_jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: () => applyProductDiscount(p.product_id, pct), children: [_jsx(Percent, { className: "h-3 w-3 mr-1" }), pct, "%"] }, pct))), _jsx(Input, { type: "number", min: 1, max: 99, placeholder: "%", className: "w-20 h-8", value: prodPct[p.product_id] ?? "", onChange: (e) => setProdPct((prev) => ({ ...prev, [p.product_id]: e.target.value })) }), _jsx(Button, { type: "button", size: "sm", variant: "secondary", onClick: () => applyProductDiscount(p.product_id, Number((prodPct[p.product_id] || "").replace(",", "."))), children: "Toepassen" }), _jsx("span", { className: "text-xs text-muted-foreground ml-1", children: "of \u20AC korting:" }), _jsx(Input, { type: "number", step: "0.01", min: "0.01", placeholder: "\u20AC", className: "w-24 h-8", value: prodEur[p.product_id] ?? "", onChange: (e) => setProdEur((prev) => ({ ...prev, [p.product_id]: e.target.value })) }), _jsx(Button, { type: "button", size: "sm", variant: "secondary", onClick: () => applyProductDiscountAmount(p.product_id, Number((prodEur[p.product_id] || "").replace(",", "."))), children: "Toepassen" })] }), _jsxs("div", { className: "flex items-center gap-2 border-t pt-2", children: [_jsx(Label, { className: "text-xs", children: "Stickeropdruk beschikbaar:" }), _jsx(Button, { type: "button", size: "sm", variant: p.sticker_available ? "default" : "outline", onClick: () => updateProd(p.product_id, { sticker_available: true }), children: "Ja" }), _jsx(Button, { type: "button", size: "sm", variant: !p.sticker_available ? "default" : "outline", onClick: () => updateProd(p.product_id, { sticker_available: false }), children: "Nee" })] }), _jsxs("div", { className: "space-y-2 border-t pt-2", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx(Label, { className: "text-xs font-medium", children: "Prijs aftuin ex. Fust vanaf .." }), _jsx(Button, { variant: "outline", size: "sm", onClick: addTier, children: "+ Staffel" })] }), p.tiers.map((t, idx) => {
568
582
  const pct = discountPercent(p.normal_price, t.price_per_crate);
569
- return (_jsxs("div", { className: "flex items-center gap-2 text-sm", children: [_jsx("span", { className: "text-xs text-muted-foreground", children: "Vanaf" }), _jsx(Input, { type: "number", className: "w-20", value: t.min_crates, onChange: (e) => updateTier(idx, { min_crates: Number(e.target.value) }) }), _jsx("span", { className: "text-xs text-muted-foreground", children: "fust(en): \u20AC" }), _jsx(Input, { type: "number", step: "0.01", className: "w-24", value: t.price_per_crate, onChange: (e) => updateTier(idx, { price_per_crate: Number(e.target.value) }) }), _jsxs("span", { className: `text-xs font-medium ${pct > 0 ? "text-destructive" : "text-muted-foreground"}`, children: ["(", pct, "%)"] }), p.tiers.length > 1 && (_jsx(Button, { variant: "ghost", size: "icon", className: "h-7 w-7", onClick: () => removeTier(idx), children: _jsx(X, { className: "h-3 w-3" }) }))] }, idx));
570
- }), _jsxs("p", { className: "text-[11px] text-muted-foreground", children: ["Percentage t.o.v. de originele fustprijs (\u20AC ", p.normal_price.toFixed(2), "), afgerond op hele procenten. Bij minder fusten dan de laagste staffel geldt de gewone prijs."] }), tierIssues.map((issue, i) => (_jsxs("p", { className: `text-[11px] ${issue.level === "error" ? "text-destructive" : "text-amber-600"}`, children: [issue.level === "error" ? "Fout: " : "Let op: ", issue.message] }, i)))] }), _jsx(Textarea, { placeholder: "Omschrijving (optioneel)", rows: 2, value: p.description, onChange: (e) => updateProd(p.product_id, { description: e.target.value }) })] }, p.product_id));
583
+ return (_jsxs("div", { className: "flex items-center gap-2 text-sm", children: [_jsx("span", { className: "text-xs text-muted-foreground", children: "Vanaf" }), _jsx(Input, { type: "number", className: "w-20", value: t.min_crates, onChange: (e) => updateTier(idx, { min_crates: Number(e.target.value) }) }), _jsx("span", { className: "text-xs text-muted-foreground", children: "fust(en): \u20AC" }), _jsx(Input, { type: "number", step: "0.01", min: "0.01", className: "w-24", placeholder: "Prijs", value: Number.isFinite(t.price_per_crate) ? t.price_per_crate : "", onChange: (e) => updateTier(idx, { price_per_crate: e.target.value === "" ? Number.NaN : Number(e.target.value.replace(",", ".")) }) }), _jsxs("span", { className: `text-xs font-medium ${pct > 0 ? "text-destructive" : "text-muted-foreground"}`, children: ["(", pct, "%)"] }), _jsx(Button, { variant: "ghost", size: "icon", className: "h-7 w-7", onClick: () => removeTier(idx), children: _jsx(X, { className: "h-3 w-3" }) })] }, idx));
584
+ }), _jsxs("p", { className: "text-[11px] text-muted-foreground", children: ["Percentage t.o.v. de originele fustprijs (", fmtEuroNL(p.normal_price), "), afgerond op hele procenten. Bij minder fusten dan de laagste staffel geldt de gewone prijs."] }), tierIssues.map((issue, i) => (_jsxs("p", { className: `text-[11px] ${issue.level === "error" ? "text-destructive" : "text-amber-600"}`, children: [issue.level === "error" ? "Fout: " : "Let op: ", issue.message] }, i)))] }), _jsx(Textarea, { placeholder: "Omschrijving (optioneel)", rows: 2, value: p.description, onChange: (e) => updateProd(p.product_id, { description: e.target.value }) })] }, p.product_id));
571
585
  }) })] }))] })] })), step === 2 && (_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Users, { className: "h-5 w-5" }), _jsx(CardTitle, { children: "Aan wie stuur je het?" })] }), _jsx(CardDescription, { children: "Kies de klanten die deze aanbieding ontvangen." }), _jsxs("div", { className: "flex items-center gap-2 text-xs text-muted-foreground pt-1", children: [_jsx(Info, { className: "h-3.5 w-3.5 shrink-0" }), _jsx("span", { children: "Er worden alleen klanten getoond met een e-mailadres. Klanten zonder e-mailadres zijn niet selecteerbaar." })] })] }), _jsxs(CardContent, { className: "space-y-3", children: [_jsxs("div", { className: "relative", children: [_jsx(Search, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { className: "pl-9", placeholder: "Zoek klant...", value: customerSearch, onChange: (e) => setCustomerSearch(e.target.value) })] }), _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("label", { className: "flex items-center gap-2 text-sm cursor-pointer", children: [_jsx("input", { type: "checkbox", checked: allVisibleSelected, onChange: toggleAllVisible }), "Selecteer alle klanten met email"] }), _jsxs(Badge, { variant: "secondary", children: [selCustomers.size, " geselecteerd"] })] }), showCustomerClasses && (_jsxs("div", { className: "space-y-2", children: [_jsx("div", { className: "text-xs font-medium text-muted-foreground", children: "Selecteer op klantgroep" }), _jsx("div", { className: "flex flex-wrap gap-2", children: classGroups.map((g) => {
572
586
  const allSel = g.customers.length > 0 && g.customers.every((c) => selCustomers.has(c.id));
573
587
  return (_jsxs(Button, { type: "button", variant: allSel ? "default" : "outline", size: "sm", onClick: () => toggleClass(g.letter), className: "gap-2", children: ["Klasse ", g.letter, _jsx(Badge, { variant: "secondary", className: "px-1.5 py-0 text-xs", children: g.customers.length })] }, g.letter));
574
588
  }) })] })), _jsx(Separator, {}), _jsx("div", { className: "max-h-[500px] overflow-y-auto space-y-2 pr-1", children: filteredCustomers.slice(0, 500).map((c) => {
575
589
  const checked = selCustomers.has(c.id);
576
590
  return (_jsxs("button", { onClick: () => toggleCustomer(c.id), className: `w-full text-left p-3 rounded-md border hover:bg-muted text-sm flex items-center gap-3 ${checked ? "bg-primary/5 border-primary/40" : ""}`, children: [_jsx("input", { type: "checkbox", checked: checked, readOnly: true }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsxs("div", { className: "font-medium truncate flex items-center gap-2", children: [_jsx("span", { className: "truncate", children: c.company_name }), c.source === "ext" && _jsx(Badge, { variant: "outline", className: "text-[10px] px-1.5 py-0", children: "Floriday" })] }), _jsx("div", { className: "text-xs text-muted-foreground truncate", children: c.email })] }), c.customer_class && _jsx(Badge, { variant: "secondary", children: c.customer_class })] }, c.id));
577
- }) })] })] })), step === 3 && (_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Mail, { className: "h-5 w-5" }), _jsx(CardTitle, { children: "Wat staat er in de mail?" })] }), _jsx(CardDescription, { children: "Pas het onderwerp en de boodschap aan \u2014 je ziet het direct in het voorbeeld." })] }), _jsxs(CardContent, { className: "grid gap-4 md:grid-cols-2", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Onderwerp" }), _jsx(Input, { value: subject, onChange: (e) => setSubject(e.target.value) })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Bericht" }), _jsx(Textarea, { value: intro, onChange: (e) => setIntro(e.target.value), rows: 3 })] }), _jsxs("div", { className: "space-y-2 md:col-span-2 hidden", children: [_jsx(Label, { children: "Afzendernaam" }), _jsx(Input, { value: fromName, onChange: (e) => setFromName(e.target.value) }), _jsx(Label, { children: "Afzender e-mail" }), _jsx(Input, { type: "email", value: fromEmail, onChange: (e) => setFromEmail(e.target.value) })] }), _jsxs("div", { className: "md:col-span-2 flex items-start gap-2 rounded-md border bg-muted/40 p-3 text-xs text-muted-foreground", children: [_jsx(Info, { className: "mt-0.5 h-3.5 w-3.5 shrink-0" }), _jsx("span", { children: "Tip: een duidelijk onderwerp en korte samenvatting zorgen voor meer openingen." })] })] })] })), step === 4 && (_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(CheckCircle2, { className: "h-5 w-5" }), _jsx(CardTitle, { children: "Controleren & versturen" })] }), _jsx(CardDescription, { children: "Controleer je aanbieding en verstuur of bewaar als concept." })] }), _jsxs(CardContent, { className: "space-y-4", children: [_jsxs("div", { className: "grid gap-3 sm:grid-cols-3", children: [_jsxs("div", { className: "rounded-md border p-3", children: [_jsx("div", { className: "text-xs text-muted-foreground", children: "Producten" }), _jsx("div", { className: "text-xl font-semibold", children: selProducts.length })] }), _jsxs("div", { className: "rounded-md border p-3", children: [_jsx("div", { className: "text-xs text-muted-foreground", children: "Ontvangers" }), _jsx("div", { className: "text-xl font-semibold", children: selCustomers.size })] }), _jsxs("div", { className: "rounded-md border p-3", children: [_jsx("div", { className: "text-xs text-muted-foreground", children: "Onderwerp" }), _jsx("div", { className: "truncate text-sm font-medium", children: subject || "—" })] })] }), _jsxs("div", { className: "space-y-2", children: [_jsx("div", { className: "text-sm font-medium", children: "Producten in deze aanbieding" }), selProducts.length === 0 ? (_jsx("div", { className: "text-sm text-muted-foreground", children: "Nog geen producten geselecteerd." })) : (_jsx("div", { className: "divide-y rounded-md border", children: selProducts.map((p) => (_jsxs("div", { className: "flex items-center justify-between gap-3 p-3 text-sm", children: [_jsx("span", { className: "truncate", children: p.product_name }), _jsxs("span", { className: "shrink-0 font-medium", children: ["\u20AC ", p.crate_price.toFixed(2)] })] }, p.product_id))) }))] }), _jsxs("div", { className: "flex flex-wrap gap-2", children: [_jsx(Button, { variant: "outline", disabled: busy, onClick: () => handleSave(false), children: "Opslaan als concept" }), _jsxs(Button, { disabled: busy || selProducts.length === 0 || selCustomers.size === 0, onClick: () => handleSave(true), children: [_jsx(Send, { className: "h-4 w-4 mr-2" }), busy ? "Bezig..." : "Actie Versturen"] })] })] })] }))] }), _jsx(EmailPreview, { subject: subject, intro: intro, fromName: fromName || (user?.email ?? ""), products: selProducts, recipientCount: selCustomers.size, offerName: name, branding: branding })] }), _jsx("div", { className: "sticky bottom-0 z-30 -mx-6 -mb-6 border-t bg-background/95 backdrop-blur px-6 py-3", children: _jsxs("div", { className: "flex items-center justify-between gap-4", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "truncate font-semibold", children: stepDef.title }), _jsxs("div", { className: "truncate text-sm text-muted-foreground", children: [selProducts.length, " product(en) \u2022 ", selCustomers.size, " klant(en)", nextHint ? ` • ${nextHint}` : ""] })] }), _jsxs("div", { className: "flex shrink-0 gap-2", children: [_jsxs(Button, { variant: "outline", onClick: () => (step === 1 ? navigate({ to: "/aanbiedingsmailtje/overzicht" }) : goTo(step - 1)), children: [_jsx(ArrowLeft, { className: "h-4 w-4 mr-2" }), step === 1 ? "Annuleren" : "Vorige"] }), step < 4 ? (_jsxs(Button, { onClick: goNext, disabled: !canNext, children: ["Verder naar ", AANBIEDING_STEPS[step].title.toLowerCase(), _jsx(ArrowRight, { className: "h-4 w-4 ml-2" })] })) : (_jsxs(Button, { disabled: busy || selProducts.length === 0 || selCustomers.size === 0, onClick: () => handleSave(true), children: [_jsx(Send, { className: "h-4 w-4 mr-2" }), busy ? "Bezig..." : "Actie Versturen"] }))] })] }) })] }));
591
+ }) })] })] })), step === 3 && (_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Mail, { className: "h-5 w-5" }), _jsx(CardTitle, { children: "Wat staat er in de mail?" })] }), _jsx(CardDescription, { children: "Pas het onderwerp en de boodschap aan \u2014 je ziet het direct in het voorbeeld." })] }), _jsxs(CardContent, { className: "grid gap-4 md:grid-cols-2", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Onderwerp" }), _jsx(Input, { value: subject, onChange: (e) => setSubject(e.target.value) })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Bericht" }), _jsx(Textarea, { value: intro, onChange: (e) => setIntro(e.target.value), rows: 3 })] }), _jsxs("div", { className: "space-y-2 md:col-span-2 hidden", children: [_jsx(Label, { children: "Afzendernaam" }), _jsx(Input, { value: fromName, onChange: (e) => setFromName(e.target.value) }), _jsx(Label, { children: "Afzender e-mail" }), _jsx(Input, { type: "email", value: fromEmail, onChange: (e) => setFromEmail(e.target.value) })] }), _jsxs("div", { className: "md:col-span-2 flex items-start gap-2 rounded-md border bg-muted/40 p-3 text-xs text-muted-foreground", children: [_jsx(Info, { className: "mt-0.5 h-3.5 w-3.5 shrink-0" }), _jsx("span", { children: "Tip: een duidelijk onderwerp en korte samenvatting zorgen voor meer openingen." })] })] })] })), step === 4 && (_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(CheckCircle2, { className: "h-5 w-5" }), _jsx(CardTitle, { children: "Controleren & versturen" })] }), _jsx(CardDescription, { children: "Controleer je aanbieding en verstuur of bewaar als concept." })] }), _jsxs(CardContent, { className: "space-y-4", children: [_jsxs("div", { className: "grid gap-3 sm:grid-cols-3", children: [_jsxs("div", { className: "rounded-md border p-3", children: [_jsx("div", { className: "text-xs text-muted-foreground", children: "Producten" }), _jsx("div", { className: "text-xl font-semibold", children: selProducts.length })] }), _jsxs("div", { className: "rounded-md border p-3", children: [_jsx("div", { className: "text-xs text-muted-foreground", children: "Ontvangers" }), _jsx("div", { className: "text-xl font-semibold", children: selCustomers.size })] }), _jsxs("div", { className: "rounded-md border p-3", children: [_jsx("div", { className: "text-xs text-muted-foreground", children: "Onderwerp" }), _jsx("div", { className: "truncate text-sm font-medium", children: subject || "—" })] })] }), _jsxs("div", { className: "space-y-2", children: [_jsx("div", { className: "text-sm font-medium", children: "Producten in deze aanbieding" }), selProducts.length === 0 ? (_jsx("div", { className: "text-sm text-muted-foreground", children: "Nog geen producten geselecteerd." })) : (_jsx("div", { className: "divide-y rounded-md border", children: selProducts.map((p) => (_jsxs("div", { className: "flex items-center justify-between gap-3 p-3 text-sm", children: [_jsx("span", { className: "truncate", children: p.product_name }), _jsxs("span", { className: "shrink-0 font-medium", children: ["\u20AC ", p.crate_price.toFixed(2)] })] }, p.product_id))) }))] }), _jsxs("div", { className: "flex flex-wrap gap-2", children: [_jsx(Button, { variant: "outline", disabled: busy, onClick: () => handleSave(false), children: "Opslaan als concept" }), _jsxs(Button, { disabled: busy || selProducts.length === 0 || selCustomers.size === 0, onClick: () => setConfirmSend(true), children: [_jsx(Send, { className: "h-4 w-4 mr-2" }), busy ? "Bezig..." : "Actie Versturen"] })] })] })] }))] }), _jsx(EmailPreview, { subject: subject, intro: intro, fromName: fromName || (user?.email ?? ""), products: selProducts, recipientCount: selCustomers.size, offerName: name, branding: branding })] }), _jsx("div", { className: "sticky bottom-0 z-30 -mx-6 -mb-6 border-t bg-background/95 backdrop-blur px-6 py-3", children: _jsxs("div", { className: "flex items-center justify-between gap-4", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "truncate font-semibold", children: stepDef.title }), _jsxs("div", { className: "truncate text-sm text-muted-foreground", children: [selProducts.length, " product(en) \u2022 ", selCustomers.size, " klant(en)", nextHint ? ` • ${nextHint}` : ""] })] }), _jsxs("div", { className: "flex shrink-0 gap-2", children: [_jsxs(Button, { variant: "outline", onClick: () => (step === 1 ? navigate({ to: "/aanbiedingsmailtje/overzicht" }) : goTo(step - 1)), children: [_jsx(ArrowLeft, { className: "h-4 w-4 mr-2" }), step === 1 ? "Annuleren" : "Vorige"] }), step < 4 ? (_jsxs(Button, { onClick: goNext, disabled: !canNext, children: ["Verder naar ", AANBIEDING_STEPS[step].title.toLowerCase(), _jsx(ArrowRight, { className: "h-4 w-4 ml-2" })] })) : (_jsxs(Button, { disabled: busy || selProducts.length === 0 || selCustomers.size === 0, onClick: () => setConfirmSend(true), children: [_jsx(Send, { className: "h-4 w-4 mr-2" }), busy ? "Bezig..." : "Actie Versturen"] }))] })] }) }), _jsx(AlertDialog, { open: confirmSend, onOpenChange: setConfirmSend, children: _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Actie versturen?" }), _jsxs(AlertDialogDescription, { children: ["Je verstuurt deze actie nu naar ", selCustomers.size, " ", selCustomers.size === 1 ? "ontvanger" : "ontvangers", ". Dit kun je niet terugdraaien."] })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: "Annuleren" }), _jsx(AlertDialogAction, { onClick: () => { setConfirmSend(false); void handleSave(true); }, children: "Ja, versturen" })] })] }) })] }));
578
592
  }
593
+ const fmtEuroNL = (n) => new Intl.NumberFormat("nl-NL", { style: "currency", currency: "EUR" }).format(Number(n) || 0);
@@ -1 +1 @@
1
- {"version":3,"file":"ActiePage.d.ts","sourceRoot":"","sources":["../../src/components/ActiePage.tsx"],"names":[],"mappings":"AAuEA,wBAAgB,SAAS,gCAysBxB"}
1
+ {"version":3,"file":"ActiePage.d.ts","sourceRoot":"","sources":["../../src/components/ActiePage.tsx"],"names":[],"mappings":"AAuEA,wBAAgB,SAAS,gCA+sBxB"}
@@ -6,7 +6,7 @@ import { Button, Input, Label, Textarea, Skeleton, toast } from "@flowselections
6
6
  import { getOfferByLinkCode, trackOfferClick, placeOfferOrder, getOfferDeliveryOptions, getOfferBranding, } from "../lib/public-offer.functions.js";
7
7
  import { ShoppingCart, Check, Package, Truck, Minus, Plus, Send, Leaf, MapPin } from "lucide-react";
8
8
  import { resolveBranding } from "../lib/branding.js";
9
- import { sortTiers, resolveTierPrice, discountPercent } from "../lib/pricing.js";
9
+ import { validTiers, resolveTierPrice, discountPercent } from "../lib/pricing.js";
10
10
  // ISO weekday: Ma=1 .. Zo=7
11
11
  function isoWeekday(d) {
12
12
  const js = d.getDay(); // 0..6, 0=Sun
@@ -175,18 +175,19 @@ export function ActiePage() {
175
175
  const plants = crates * ppc;
176
176
  // Prijsprioriteit: (1) geldende staffelprijs uit actie-e-mail, (2) discount_price uit
177
177
  // e-mail, (3) standaardprijs. Onder de laagste staffel geldt (2)/(3).
178
- const tiers = sortTiers((Array.isArray(p.tiers) ? p.tiers : []).map((t) => ({
178
+ const tiers = validTiers((Array.isArray(p.tiers) ? p.tiers : []).map((t) => ({
179
179
  min_crates: Number(t.min_crates),
180
180
  price_per_crate: Number(t.price_per_crate),
181
181
  })));
182
- const resolved = resolveTierPrice(tiers, crates, {
182
+ // Bij aantal 0 tonen we de prijs voor 1 fust (nooit stukprijs × planten).
183
+ const resolved = resolveTierPrice(tiers, Math.max(1, crates), {
183
184
  discount_price: Number(p.discount_price ?? 0),
184
185
  normal_price: Number(p.normal_price ?? 0),
185
186
  });
186
187
  const priceSource = resolved.source;
187
- // Staffelprijs is per fust; discount/normal zijn per plant.
188
- const cratePrice = priceSource === "tier" ? resolved.price : resolved.price * ppc;
189
- const unit = priceSource === "tier" ? resolved.price / ppc : resolved.price;
188
+ // Alle prijzen (staffel, actie, normaal) zijn per fust — gelijk aan de server.
189
+ const cratePrice = resolved.price;
190
+ const unit = Math.round((resolved.price / ppc) * 100) / 100;
190
191
  const priceSourceLabel = priceSource === "tier" ? "Prijs uit actie-e-mail"
191
192
  : priceSource === "discount" ? "Actieprijs uit e-mail"
192
193
  : "Standaardprijs artikelbeheer";
@@ -262,7 +263,15 @@ export function ActiePage() {
262
263
  items,
263
264
  },
264
265
  });
265
- setPlaced({ id: r.orderId, total: r.total, orderNumber: r.orderNumber ?? null, unmatched: !r.ok });
266
+ if (!r.ok && r.reason === "price_changed") {
267
+ toast.error(r.message ?? "De prijs is gewijzigd. Controleer je bestelling.");
268
+ try {
269
+ setData(await getFn({ data: { code: linkCode } }));
270
+ }
271
+ catch { /* blijft oude weergave */ }
272
+ return;
273
+ }
274
+ setPlaced({ id: r.orderId ?? "", total: r.total, orderNumber: r.orderNumber ?? null, unmatched: !r.ok });
266
275
  }
267
276
  catch (e) {
268
277
  toast.error(e?.message ?? "Fout");
@@ -276,7 +285,7 @@ export function ActiePage() {
276
285
  if (placed.unmatched) {
277
286
  return (_jsx("div", { className: "min-h-screen bg-[#fffbeb] flex items-center justify-center p-6", children: _jsxs("div", { className: "max-w-md w-full bg-white rounded-2xl shadow-lg border border-amber-200 p-8 text-center", children: [_jsx("h2", { className: "text-2xl font-bold text-slate-900", children: "Bestelling ontvangen \u2014 actie nodig" }), _jsxs("p", { className: "mt-3 text-slate-700", children: ["We hebben je bestelling (referentie ", _jsx("strong", { children: ref }), ") ontvangen, maar konden die niet automatisch aan je klantaccount koppelen."] }), _jsxs("p", { className: "mt-3 text-slate-700", children: ["Neem contact op met ", brandName, " en noem deze referentie, dan verwerken we je bestelling voor je."] })] }) }));
278
287
  }
279
- return (_jsx("div", { className: "min-h-screen bg-[#f0fdf4] flex items-center justify-center p-6", children: _jsxs("div", { className: "max-w-md w-full bg-white rounded-2xl shadow-lg border border-green-100 p-8 text-center", children: [_jsx("div", { className: "mx-auto rounded-full bg-green-100 p-3 w-fit", children: _jsx(Check, { className: "h-8 w-8 text-green-600" }) }), _jsx("h2", { className: "mt-4 text-2xl font-bold text-slate-900", children: "Bedankt voor uw bestelling!" }), _jsxs("p", { className: "mt-2 text-slate-600", children: ["Bestelling ", ref] }), _jsxs("p", { className: "mt-1 text-lg font-semibold text-green-700", children: ["Totaal: ", fmtEuro(placed.total)] }), _jsx("p", { className: "mt-4 text-sm text-slate-500", children: "We nemen zo spoedig mogelijk contact met u op." })] }) }));
288
+ return (_jsx("div", { className: "min-h-screen bg-[#f0fdf4] flex items-center justify-center p-6", children: _jsxs("div", { className: "max-w-md w-full bg-white rounded-2xl shadow-lg border border-green-100 p-8 text-center", children: [_jsx("div", { className: "mx-auto rounded-full bg-green-100 p-3 w-fit", children: _jsx(Check, { className: "h-8 w-8 text-green-600" }) }), _jsx("h2", { className: "mt-4 text-2xl font-bold text-slate-900", children: "Bedankt, je bestelling is ontvangen" }), _jsxs("p", { className: "mt-2 text-slate-600", children: ["Bestelling ", ref] }), _jsxs("p", { className: "mt-1 text-lg font-semibold text-green-700", children: ["Totaal: ", fmtEuro(placed.total)] }), _jsx("p", { className: "mt-4 text-sm text-slate-500", children: "We gaan er direct mee aan de slag." })] }) }));
280
289
  }
281
290
  return (_jsxs("div", { className: "min-h-screen bg-[#f0fdf4] flex flex-col", style: brandStyle, children: [_jsx("header", { className: "text-white", style: { backgroundColor: "var(--offer-primary)" }, children: _jsxs("div", { className: "max-w-6xl mx-auto px-4 sm:px-6 py-6 sm:py-10 text-center", children: [_jsxs("div", { className: "mx-auto mb-3 sm:mb-4 inline-flex max-w-full items-center justify-center gap-2 bg-white rounded-md px-3 py-2 shadow-sm", children: [brand.logoUrl ? (_jsx("img", { src: brand.logoUrl, alt: brandName, className: "h-6 sm:h-7 w-auto max-w-[180px] object-contain" })) : (_jsx(Leaf, { className: "h-5 w-5 shrink-0", style: { color: "var(--offer-primary)" } })), _jsx("span", { className: "font-bold text-slate-900 tracking-tight truncate", children: brandName })] }), _jsxs("h1", { className: "text-2xl sm:text-3xl md:text-4xl font-extrabold tracking-tight break-words", children: ["\uD83C\uDF31 ", data?.campaign?.subject ?? "Speciale Aanbieding"] }), data?.campaign?.intro_message ? (_jsx("p", { className: "mt-3 text-white/90 text-sm sm:text-base whitespace-pre-line max-w-2xl mx-auto", children: data.campaign.intro_message })) : (_jsx("p", { className: "mt-3 text-white/90 text-sm sm:text-base", children: "Wij hebben een mooie aanbieding voor u!" }))] }) }), _jsxs("main", { className: "flex-1 max-w-6xl w-full mx-auto px-3 sm:px-6 py-6 sm:py-8 space-y-6 pb-28 lg:pb-8", children: [_jsxs("div", { className: "w-full text-left p-3 rounded-md border border-slate-200 bg-white text-sm flex items-center gap-3", children: [_jsx("div", { className: "h-10 w-10 rounded-md bg-primary text-white grid place-items-center text-sm font-bold shrink-0", children: data?.recipient?.recipient_name
282
291
  ?.split(" ")
@@ -1 +1 @@
1
- {"version":3,"file":"CampagneDetail.d.ts","sourceRoot":"","sources":["../../src/components/CampagneDetail.tsx"],"names":[],"mappings":"AASA,wBAAgB,cAAc,gCA0H7B"}
1
+ {"version":3,"file":"CampagneDetail.d.ts","sourceRoot":"","sources":["../../src/components/CampagneDetail.tsx"],"names":[],"mappings":"AAUA,wBAAgB,cAAc,gCA0H7B"}
@@ -1,4 +1,5 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { CampaignStatusBadge, euro } from "./overzicht/campaignStatus.js";
2
3
  import { useEffect, useState } from "react";
3
4
  import { useParams } from "@tanstack/react-router";
4
5
  import { useServerFn } from "@tanstack/react-start";
@@ -60,7 +61,7 @@ export function CampagneDetail() {
60
61
  toast.success("Verwijderd");
61
62
  navigate({ to: "/aanbiedingsmailtje/overzicht" });
62
63
  };
63
- return (_jsxs("main", { className: "p-6 space-y-6", children: [_jsx(Link, { to: "/aanbiedingsmailtje/overzicht", children: _jsxs(Button, { variant: "ghost", size: "sm", className: "-ml-2", children: [_jsx(ArrowLeft, { className: "h-4 w-4 mr-1" }), "Terug"] }) }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsxs("div", { className: "flex justify-between items-start", children: [_jsxs("div", { children: [_jsx(CardTitle, { children: c.name }), _jsx(CardDescription, { children: c.subject })] }), _jsx(Badge, { variant: c.status === "sent" ? "default" : "secondary", children: c.status })] }) }), _jsxs(CardContent, { className: "space-y-3", children: [_jsxs("div", { className: "text-sm text-muted-foreground", children: ["Van: ", c.from_name, " <", c.from_email, ">"] }), c.intro_message && _jsx("p", { className: "text-sm", children: c.intro_message }), _jsxs("div", { className: "flex gap-2", children: [c.status !== "sent" && (_jsxs(Button, { onClick: handleSend, disabled: busy, children: [_jsx(Send, { className: "h-4 w-4 mr-1" }), "Versturen"] })), _jsxs(Button, { variant: "destructive", onClick: handleDel, children: [_jsx(Trash2, { className: "h-4 w-4 mr-1" }), "Verwijderen"] })] })] })] }), stats && (_jsxs("div", { className: "grid grid-cols-2 md:grid-cols-5 gap-3", children: [_jsx(StatCard, { icon: _jsx(Mail, {}), label: "Verzonden", value: stats.sent }), _jsx(StatCard, { icon: _jsx(Eye, {}), label: "Geopend", value: stats.opened }), _jsx(StatCard, { icon: _jsx(MousePointerClick, {}), label: "Klikken", value: stats.clicked }), _jsx(StatCard, { icon: _jsx(ShoppingCart, {}), label: "Bestellingen", value: stats.ordered }), _jsx(StatCard, { icon: _jsx("span", { children: "\u20AC" }), label: "Omzet", value: `€ ${Number(stats.revenue).toFixed(2)}` })] })), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsxs(CardTitle, { children: ["Producten (", data.products.length, ")"] }) }), _jsx(CardContent, { className: "space-y-2", children: data.products.map((p) => (_jsxs("div", { className: "flex justify-between border-b py-2 text-sm", children: [_jsx("span", { children: p.product_name }), _jsxs("span", { children: [_jsxs("span", { className: "line-through text-muted-foreground mr-2", children: ["\u20AC ", Number(p.normal_price).toFixed(2)] }), _jsxs("span", { className: "font-semibold", children: ["\u20AC ", Number(p.discount_price).toFixed(2)] })] })] }, p.id))) })] }), _jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsxs(CardTitle, { children: ["Ontvangers (", data.recipients.length, ")"] }), _jsx(CardDescription, { children: "Klik op een rij om de unieke actie-URL te kopi\u00EBren" })] }), _jsx(CardContent, { children: _jsx("div", { className: "max-h-[400px] overflow-y-auto divide-y text-sm", children: data.recipients.map((r) => {
64
+ return (_jsxs("main", { className: "p-6 space-y-6", children: [_jsx(Link, { to: "/aanbiedingsmailtje/overzicht", children: _jsxs(Button, { variant: "ghost", size: "sm", className: "-ml-2", children: [_jsx(ArrowLeft, { className: "h-4 w-4 mr-1" }), "Terug"] }) }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsxs("div", { className: "flex justify-between items-start", children: [_jsxs("div", { children: [_jsx(CardTitle, { children: c.name }), _jsx(CardDescription, { children: c.subject })] }), _jsx(CampaignStatusBadge, { status: c.status })] }) }), _jsxs(CardContent, { className: "space-y-3", children: [_jsxs("div", { className: "text-sm text-muted-foreground", children: ["Van: ", c.from_name, " <", c.from_email, ">"] }), c.intro_message && _jsx("p", { className: "text-sm", children: c.intro_message }), _jsxs("div", { className: "flex gap-2", children: [c.status !== "sent" && (_jsxs(Button, { onClick: handleSend, disabled: busy, children: [_jsx(Send, { className: "h-4 w-4 mr-1" }), "Versturen"] })), _jsxs(Button, { variant: "destructive", onClick: handleDel, children: [_jsx(Trash2, { className: "h-4 w-4 mr-1" }), "Verwijderen"] })] })] })] }), stats && (_jsxs("div", { className: "grid grid-cols-2 md:grid-cols-5 gap-3", children: [_jsx(StatCard, { icon: _jsx(Mail, {}), label: "Verzonden", value: stats.sent }), _jsx(StatCard, { icon: _jsx(Eye, {}), label: "Geopend", value: stats.opened }), _jsx(StatCard, { icon: _jsx(MousePointerClick, {}), label: "Klikken", value: stats.clicked }), _jsx(StatCard, { icon: _jsx(ShoppingCart, {}), label: "Bestellingen", value: stats.ordered }), _jsx(StatCard, { icon: _jsx("span", { children: "\u20AC" }), label: "Omzet", value: euro(Number(stats.revenue)) })] })), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsxs(CardTitle, { children: ["Producten (", data.products.length, ")"] }) }), _jsx(CardContent, { className: "space-y-2", children: data.products.map((p) => (_jsxs("div", { className: "flex justify-between border-b py-2 text-sm", children: [_jsx("span", { children: p.product_name }), _jsx("span", { children: Number(p.discount_price) > 0 && Number(p.discount_price) < Number(p.normal_price) ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "line-through text-muted-foreground mr-2", children: euro(Number(p.normal_price)) }), _jsx("span", { className: "font-semibold", children: euro(Number(p.discount_price)) })] })) : (_jsx("span", { className: "font-semibold", children: euro(Number(p.normal_price)) })) })] }, p.id))) })] }), _jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsxs(CardTitle, { children: ["Ontvangers (", data.recipients.length, ")"] }), _jsx(CardDescription, { children: "Klik op een rij om de unieke actie-URL te kopi\u00EBren" })] }), _jsx(CardContent, { children: _jsx("div", { className: "max-h-[400px] overflow-y-auto divide-y text-sm", children: data.recipients.map((r) => {
64
65
  const url = `${origin}/actie/${r.link_code}`;
65
66
  return (_jsxs("button", { onClick: () => { navigator.clipboard.writeText(url); toast.success("Link gekopieerd"); }, className: "w-full text-left py-2 hover:bg-muted/50 grid grid-cols-12 gap-2 items-center", children: [_jsx("div", { className: "col-span-4 font-medium truncate", children: r.recipient_name }), _jsx("div", { className: "col-span-4 text-muted-foreground truncate", children: r.recipient_email }), _jsxs("div", { className: "col-span-4 flex gap-1 justify-end", children: [r.sent_at && _jsx(Badge, { variant: "outline", children: "verzonden" }), r.opened_at && _jsx(Badge, { variant: "secondary", children: "geopend" }), r.clicked_at && _jsx(Badge, { children: "geklikt" }), r.ordered_at && _jsx(Badge, { variant: "default", children: "besteld" })] })] }, r.id));
66
67
  }) }) })] })] }));
@@ -5,12 +5,12 @@ import { Link } from "@tanstack/react-router";
5
5
  import { useServerFn } from "@tanstack/react-start";
6
6
  import { getOverallStats } from "../lib/stats.functions.js";
7
7
  import { Plus, ArrowLeft, Search, BarChart3, Mail } from "lucide-react";
8
- import { DateRangePicker, loadStoredRange, storeRange } from "./DateRangePicker";
9
- import { PrestatieTegels } from "./overzicht/PrestatieTegels";
10
- import { RecenteCampagnes } from "./overzicht/RecenteCampagnes";
11
- import { TopLijsten } from "./overzicht/TopLijsten";
12
- import { NietGekoppeldeBestellingen } from "./overzicht/NietGekoppeldeBestellingen";
13
- import { CampaignStatusBadge, campaignStatus, campaignTimeLabel, euro } from "./overzicht/campaignStatus";
8
+ import { DateRangePicker, loadStoredRange, storeRange } from "./DateRangePicker.js";
9
+ import { PrestatieTegels } from "./overzicht/PrestatieTegels.js";
10
+ import { RecenteCampagnes } from "./overzicht/RecenteCampagnes.js";
11
+ import { TopLijsten } from "./overzicht/TopLijsten.js";
12
+ import { NietGekoppeldeBestellingen } from "./overzicht/NietGekoppeldeBestellingen.js";
13
+ import { CampaignStatusBadge, campaignStatus, campaignTimeLabel, euro } from "./overzicht/campaignStatus.js";
14
14
  const STATUS_FILTERS = [
15
15
  { id: "all", label: "Alle" },
16
16
  { id: "sent", label: "Verzonden" },
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useState } from "react";
3
3
  import { Card, CardContent, CardDescription, CardHeader, CardTitle, Button, cn } from "@flowselections/core";
4
4
  import { Monitor, Smartphone, Package, Info, Mail, ExternalLink, ShoppingCart } from "lucide-react";
5
- import { sortTiers, discountPercent } from "../../lib/pricing.js";
5
+ import { validTiers, discountPercent } from "../../lib/pricing.js";
6
6
  import { resolveBranding } from "../../lib/branding.js";
7
7
  const euro = (n) => `€ ${(Number.isFinite(n) ? n : 0).toFixed(2)}`;
8
8
  export function EmailPreview({ subject, intro, fromName, products, recipientCount, offerName, branding }) {
@@ -14,7 +14,7 @@ export function EmailPreview({ subject, intro, fromName, products, recipientCoun
14
14
  return (_jsxs(Card, { className: "xl:sticky xl:top-6", children: [_jsxs(CardHeader, { className: "space-y-3", children: [_jsxs("div", { className: "flex items-start justify-between gap-3", children: [_jsxs("div", { children: [_jsx(CardTitle, { children: "Voorbeeld" }), _jsx(CardDescription, { children: view === "email"
15
15
  ? "Zo ziet je e-mail eruit voor je ontvangers."
16
16
  : "Zo ziet de actiepagina eruit na het klikken op de link." })] }), _jsxs("div", { className: "flex items-center gap-1 rounded-md border p-1", children: [_jsx(Button, { type: "button", size: "icon", variant: device === "desktop" ? "default" : "ghost", className: "h-7 w-7", "aria-label": "Desktopweergave", onClick: () => setDevice("desktop"), children: _jsx(Monitor, { className: "h-4 w-4" }) }), _jsx(Button, { type: "button", size: "icon", variant: isMobile ? "default" : "ghost", className: "h-7 w-7", "aria-label": "Mobiele weergave", onClick: () => setDevice("mobile"), children: _jsx(Smartphone, { className: "h-4 w-4" }) })] })] }), _jsxs("div", { className: "grid grid-cols-2 gap-1 rounded-md border bg-muted/40 p-1", children: [_jsxs(Button, { type: "button", size: "sm", variant: view === "email" ? "default" : "ghost", className: "h-8 gap-1.5", onClick: () => setView("email"), "aria-pressed": view === "email", children: [_jsx(Mail, { className: "h-3.5 w-3.5" }), " E-mail"] }), _jsxs(Button, { type: "button", size: "sm", variant: view === "landing" ? "default" : "ghost", className: "h-8 gap-1.5", onClick: () => setView("landing"), "aria-pressed": view === "landing", children: [_jsx(ExternalLink, { className: "h-3.5 w-3.5" }), " Actiepagina"] })] })] }), _jsxs(CardContent, { className: "space-y-3", children: [_jsx("div", { className: cn("mx-auto transition-all", isMobile ? "max-w-[340px]" : "w-full"), children: view === "email" ? (_jsxs("div", { className: "rounded-lg border bg-card overflow-hidden", children: [_jsxs("div", { className: "border-b bg-muted/40 px-4 py-3", children: [_jsx("div", { className: "text-xs text-muted-foreground", children: "Onderwerpregel" }), _jsx("div", { className: "font-semibold leading-snug break-words", children: subject?.trim() || "Speciale aanbieding voor u!" }), _jsxs("div", { className: "mt-1 text-xs text-muted-foreground truncate", children: ["Van: ", fromName?.trim() || "Aanbiedingsmailtje"] })] }), _jsxs("div", { className: "flex flex-col items-center gap-2 px-4 py-4 text-center", style: { backgroundColor: brand.primary }, children: [brand.logoUrl && (_jsx("img", { src: brand.logoUrl, alt: "", className: "h-8 w-auto max-w-[160px] object-contain" })), _jsx("div", { className: "text-sm font-bold", style: { color: "#ffffff" }, children: subject?.trim() || "Speciale aanbieding voor u!" })] }), _jsxs("div", { className: "space-y-4 px-4 py-4", children: [_jsx("p", { className: "text-sm text-muted-foreground whitespace-pre-wrap", children: intro?.trim() || "Korte samenvatting van je aanbieding..." }), _jsx("button", { className: "w-full rounded-md px-4 py-2 text-sm font-semibold", type: "button", disabled: true, style: { backgroundColor: brand.button, color: brand.buttonText }, children: "\uD83D\uDED2 Direct Bestellen" }), _jsxs("div", { className: "rounded-md border p-3 space-y-3", children: [_jsx("div", { className: "text-sm font-medium", children: "Producten in deze aanbieding" }), safeProducts.length === 0 ? (_jsx("div", { className: cn("grid gap-2", isMobile ? "grid-cols-2" : "grid-cols-3"), children: [0, 1, 2].map((i) => (_jsxs("div", { className: "rounded-md border bg-muted/40 p-3 space-y-2", children: [_jsx("div", { className: "flex h-16 items-center justify-center rounded bg-muted", children: _jsx(Package, { className: "h-5 w-5 text-muted-foreground" }) }), _jsx("div", { className: "h-2 w-3/4 rounded bg-muted" }), _jsx("div", { className: "h-2 w-1/2 rounded bg-muted" })] }, i))) })) : (_jsx("div", { className: cn("grid gap-2", isMobile ? "grid-cols-1" : "grid-cols-2"), children: safeProducts.map((p) => {
17
- const tiers = sortTiers(Array.isArray(p.tiers) ? p.tiers : []);
17
+ const tiers = validTiers(Array.isArray(p.tiers) ? p.tiers : []);
18
18
  return (_jsxs("div", { className: "rounded-md border p-2 space-y-2", children: [_jsxs("div", { className: "flex items-start gap-2", children: [_jsx("div", { className: "h-14 w-14 shrink-0 overflow-hidden rounded border bg-muted flex items-center justify-center", children: p.image_url ? (_jsx("img", { src: p.image_url, alt: p.product_name, className: "h-full w-full object-cover" })) : (_jsx(Package, { className: "h-5 w-5 text-muted-foreground" })) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: "text-sm font-medium leading-tight break-words", children: p.product_name }), _jsxs("div", { className: "mt-0.5 flex flex-wrap items-baseline gap-1.5", children: [_jsx("span", { className: "text-sm font-semibold text-primary", children: euro(p.crate_price) }), p.normal_price > p.crate_price && (_jsx("span", { className: "text-xs text-muted-foreground line-through", children: euro(p.normal_price) }))] })] })] }), p.description?.trim() && (_jsx("p", { className: "text-xs text-muted-foreground break-words", children: p.description })), tiers.length > 0 && (_jsx("ul", { className: "space-y-0.5 text-xs text-muted-foreground", children: tiers.map((t, i) => {
19
19
  const pct = discountPercent(p.normal_price, t.price_per_crate);
20
20
  return (_jsxs("li", { children: ["Vanaf ", t.min_crates, " fust(en): ", euro(t.price_per_crate), pct > 0 ? ` (-${pct}%)` : ""] }, i));
@@ -1 +1 @@
1
- {"version":3,"file":"NietGekoppeldeBestellingen.d.ts","sourceRoot":"","sources":["../../../src/components/overzicht/NietGekoppeldeBestellingen.tsx"],"names":[],"mappings":"AAiEA,wBAAgB,0BAA0B,uCAsBzC"}
1
+ {"version":3,"file":"NietGekoppeldeBestellingen.d.ts","sourceRoot":"","sources":["../../../src/components/overzicht/NietGekoppeldeBestellingen.tsx"],"names":[],"mappings":"AAkEA,wBAAgB,0BAA0B,uCAsBzC"}
@@ -5,7 +5,7 @@ import { useServerFn } from "@tanstack/react-start";
5
5
  import { toast } from "sonner";
6
6
  import { AlertTriangle } from "lucide-react";
7
7
  import { listUnlinkedOfferOrders, searchCustomersForLink, relinkOfferOrder, } from "../../lib/unlinked-offer-orders.functions.js";
8
- import { euro } from "./campaignStatus";
8
+ import { euro } from "./campaignStatus.js";
9
9
  function Row({ o, onDone }) {
10
10
  const search = useServerFn(searchCustomersForLink);
11
11
  const relink = useServerFn(relinkOfferOrder);
@@ -43,7 +43,7 @@ function Row({ o, onDone }) {
43
43
  setBusy(false);
44
44
  }
45
45
  };
46
- return (_jsxs("div", { className: "space-y-2 rounded-lg border p-3", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [_jsxs("div", { children: [_jsxs("p", { className: "font-medium", children: [o.order_number ?? o.id.slice(0, 8), " \u00B7 ", o.customer_name] }), _jsxs("p", { className: "text-xs text-muted-foreground", children: [o.customer_email, " \u00B7 ", new Date(o.created_at).toLocaleString("nl-NL"), " \u00B7 ", euro(Number(o.total_amount ?? 0))] })] }), o.order_creation_error && _jsx(Badge, { variant: "destructive", children: o.order_creation_error })] }), _jsxs("div", { className: "flex flex-wrap gap-2", children: [_jsx(Input, { className: "max-w-xs", placeholder: "Zoek klant (naam, e-mail, nummer)", value: q, onChange: (e) => setQ(e.target.value) }), _jsxs("select", { className: "rounded-md border bg-background px-2 text-sm", value: choice, onChange: (e) => setChoice(e.target.value), children: [_jsx("option", { value: "", children: "\u2014 kies klant \u2014" }), results.map((c) => (_jsxs("option", { value: c.id, children: [c.company_name ?? "(naamloos)", c.email ? ` · ${c.email}` : "", c.customer_number ? ` · ${c.customer_number}` : ""] }, c.id)))] }), _jsx(Button, { size: "sm", disabled: busy || !choice, onClick: submit, children: "Koppelen en order aanmaken" })] })] }));
46
+ return (_jsxs("div", { className: "space-y-2 rounded-lg border p-3", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [_jsxs("div", { children: [_jsxs("p", { className: "font-medium", children: [o.order_number ?? o.id.slice(0, 8), " \u00B7 ", o.customer_name] }), _jsxs("p", { className: "text-xs text-muted-foreground", children: [o.customer_email, " \u00B7 ", new Date(o.created_at).toLocaleString("nl-NL"), " \u00B7 ", euro(Number(o.total_amount ?? 0))] })] }), Number(o.order_creation_attempts ?? 0) >= 10 && _jsx(Badge, { variant: "destructive", children: "Lukt niet automatisch, kijk even na" }), o.order_creation_error && _jsx("span", { className: "text-xs text-muted-foreground", title: "Details voor beheer", children: o.order_creation_error })] }), _jsxs("div", { className: "flex flex-wrap gap-2", children: [_jsx(Input, { className: "max-w-xs", placeholder: "Zoek klant (naam, e-mail, nummer)", value: q, onChange: (e) => setQ(e.target.value) }), _jsxs("select", { className: "rounded-md border bg-background px-2 text-sm", value: choice, onChange: (e) => setChoice(e.target.value), children: [_jsx("option", { value: "", children: "\u2014 kies klant \u2014" }), results.map((c) => (_jsxs("option", { value: c.id, children: [c.company_name ?? "(naamloos)", c.email ? ` · ${c.email}` : "", c.customer_number ? ` · ${c.customer_number}` : ""] }, c.id)))] }), _jsx(Button, { size: "sm", disabled: busy || !choice, onClick: submit, children: "Koppelen en order aanmaken" })] })] }));
47
47
  }
48
48
  export function NietGekoppeldeBestellingen() {
49
49
  const list = useServerFn(listUnlinkedOfferOrders);
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Card, CardContent } from "@flowselections/core";
3
3
  import { Mail, Eye, MousePointerClick, ShoppingCart, Euro as EuroIcon, BarChart3, ArrowUp, ArrowDown } from "lucide-react";
4
- import { euro } from "./campaignStatus";
4
+ import { euro } from "./campaignStatus.js";
5
5
  const EMPTY = { campaigns: 0, sent: 0, opened: 0, clicked: 0, orders: 0, revenue: 0 };
6
6
  function pctChange(current, previous) {
7
7
  if (!Number.isFinite(current) || !Number.isFinite(previous))
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Link } from "@tanstack/react-router";
3
3
  import { Card, CardContent, CardHeader, CardTitle, Badge, Button } from "@flowselections/core";
4
4
  import { Mail, Eye, MousePointerClick, ShoppingCart, Euro as EuroIcon, ChevronRight, Plus } from "lucide-react";
5
- import { CampaignStatusBadge, campaignTimeLabel, euro } from "./campaignStatus";
5
+ import { CampaignStatusBadge, campaignTimeLabel, euro } from "./campaignStatus.js";
6
6
  function Metric({ icon, value, label }) {
7
7
  return (_jsxs("div", { className: "min-w-[76px]", children: [_jsxs("div", { className: "flex items-center gap-1.5 text-sm font-semibold", children: [_jsx("span", { className: "text-muted-foreground", children: icon }), value] }), _jsx("div", { className: "text-xs text-muted-foreground", children: label })] }));
8
8
  }
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { Card, CardContent, CardHeader, CardTitle } from "@flowselections/core";
3
3
  import { Package } from "lucide-react";
4
- import { euro } from "./campaignStatus";
4
+ import { euro } from "./campaignStatus.js";
5
5
  function Rank({ n, highlight }) {
6
6
  return (_jsx("span", { className: `flex h-6 w-6 shrink-0 items-center justify-center rounded-full text-xs font-semibold ${highlight ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground"}`, children: n }));
7
7
  }
@@ -1 +1 @@
1
- {"version":3,"file":"HuisstijlSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/components/settings/HuisstijlSettingsCard.tsx"],"names":[],"mappings":"AA+BA,wBAAgB,qBAAqB,gCAkPpC"}
1
+ {"version":3,"file":"HuisstijlSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/components/settings/HuisstijlSettingsCard.tsx"],"names":[],"mappings":"AAgCA,wBAAgB,qBAAqB,gCA0OpC"}
@@ -3,6 +3,7 @@ import { useEffect, useRef, useState } from "react";
3
3
  import { Card, CardContent, CardDescription, CardHeader, CardTitle, Button, Input, Label, supabase, toast, useAuth, } from "@flowselections/core";
4
4
  import { Palette, Upload, Trash2, Image as ImageIcon, Loader2 } from "lucide-react";
5
5
  import { resolveBranding, isValidHex, DEFAULT_PRIMARY } from "../../lib/branding.js";
6
+ import { saveOfferSettings } from "../../lib/offer-settings.functions.js";
6
7
  import { uploadBrandingLogo, deleteBrandingLogo } from "../../lib/branding.functions.js";
7
8
  const BUCKET = "offer-branding";
8
9
  const MAX_BYTES = 2 * 1024 * 1024;
@@ -46,12 +47,7 @@ export function HuisstijlSettingsCard() {
46
47
  const primaryValid = isValidHex(state.primary_color);
47
48
  const buttonValid = isValidHex(state.button_color);
48
49
  const persistLogoUrl = async (url) => {
49
- const { error } = await supabase
50
- .from("offer_settings")
51
- .update({ logo_url: url || null })
52
- .eq("singleton", true);
53
- if (error)
54
- throw error;
50
+ await saveOfferSettings({ data: { logo_url: url || null } });
55
51
  };
56
52
  const onFile = async (file) => {
57
53
  if (!ALLOWED.includes(file.type)) {
@@ -106,21 +102,16 @@ export function HuisstijlSettingsCard() {
106
102
  }
107
103
  setSaving(true);
108
104
  try {
109
- const { error } = await supabase
110
- .from("offer_settings")
111
- .update({
112
- brand_name: state.brand_name.trim() || null,
113
- logo_url: state.logo_url.trim() || null,
114
- primary_color: brand.primary,
115
- button_color: brand.button,
116
- })
117
- .eq("singleton", true);
118
- if (error)
119
- throw error;
105
+ await saveOfferSettings({ data: {
106
+ brand_name: state.brand_name.trim() || null,
107
+ logo_url: state.logo_url.trim() || null,
108
+ primary_color: brand.primary,
109
+ button_color: brand.button,
110
+ } });
120
111
  toast.success("Opgeslagen");
121
112
  }
122
113
  catch (e) {
123
- toast.error(e?.message ?? "Fout");
114
+ toast.error(e?.message ?? "Je instellingen zijn niet opgeslagen.");
124
115
  }
125
116
  finally {
126
117
  setSaving(false);
@@ -1 +1 @@
1
- {"version":3,"file":"StickerSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/components/settings/StickerSettingsCard.tsx"],"names":[],"mappings":"AAIA,wBAAgB,mBAAmB,gCA8DlC"}
1
+ {"version":3,"file":"StickerSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/components/settings/StickerSettingsCard.tsx"],"names":[],"mappings":"AAKA,wBAAgB,mBAAmB,gCA0DlC"}
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { saveOfferSettings } from "../../lib/offer-settings.functions.js";
2
3
  import { useState, useEffect } from "react";
3
4
  import { Card, CardContent, CardDescription, CardHeader, CardTitle, Button, Label, Input, supabase, toast, useAuth } from "@flowselections/core";
4
5
  import { Tag } from "lucide-react";
@@ -22,16 +23,11 @@ export function StickerSettingsCard() {
22
23
  }
23
24
  setSaving(true);
24
25
  try {
25
- const { error } = await supabase
26
- .from("offer_settings")
27
- .update({ default_sticker_cost: v })
28
- .eq("singleton", true);
29
- if (error)
30
- throw error;
26
+ await saveOfferSettings({ data: { default_sticker_cost: v } });
31
27
  toast.success("Opgeslagen");
32
28
  }
33
29
  catch (e) {
34
- toast.error(e?.message ?? "Fout");
30
+ toast.error(e?.message ?? "Je instellingen zijn niet opgeslagen.");
35
31
  }
36
32
  finally {
37
33
  setSaving(false);
@@ -1 +1 @@
1
- {"version":3,"file":"TrackingSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/components/settings/TrackingSettingsCard.tsx"],"names":[],"mappings":"AAIA,wBAAgB,oBAAoB,gCAuDnC"}
1
+ {"version":3,"file":"TrackingSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/components/settings/TrackingSettingsCard.tsx"],"names":[],"mappings":"AAKA,wBAAgB,oBAAoB,gCAsDnC"}
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { saveOfferSettings } from "../../lib/offer-settings.functions.js";
2
3
  import { useState, useEffect } from "react";
3
4
  import { Card, CardContent, CardDescription, CardHeader, CardTitle, Button, Label, Switch, supabase, toast, useAuth, Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@flowselections/core";
4
5
  import { Eye } from "lucide-react";
@@ -14,13 +15,11 @@ export function TrackingSettingsCard() {
14
15
  const save = async () => {
15
16
  setSaving(true);
16
17
  try {
17
- const { error } = await supabase.from("offer_settings").update(state).eq("singleton", true);
18
- if (error)
19
- throw error;
18
+ await saveOfferSettings({ data: state });
20
19
  toast.success("Opgeslagen");
21
20
  }
22
21
  catch (e) {
23
- toast.error(e?.message ?? "Fout");
22
+ toast.error(e?.message ?? "Je instellingen zijn niet opgeslagen.");
24
23
  }
25
24
  finally {
26
25
  setSaving(false);
@@ -1 +1 @@
1
- {"version":3,"file":"UitsluitingenSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/components/settings/UitsluitingenSettingsCard.tsx"],"names":[],"mappings":"AAMA,wBAAgB,yBAAyB,gCA+DxC"}
1
+ {"version":3,"file":"UitsluitingenSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/components/settings/UitsluitingenSettingsCard.tsx"],"names":[],"mappings":"AAOA,wBAAgB,yBAAyB,gCA8DxC"}
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { saveOfferSettings } from "../../lib/offer-settings.functions.js";
2
3
  import { useState, useEffect } from "react";
3
4
  import { Card, CardContent, CardDescription, CardHeader, CardTitle, Button, Label, supabase, toast, useAuth, Badge } from "@flowselections/core";
4
5
  import { UserX, X } from "lucide-react";
@@ -23,13 +24,11 @@ export function UitsluitingenSettingsCard() {
23
24
  const save = async () => {
24
25
  setSaving(true);
25
26
  try {
26
- const { error } = await supabase.from("offer_settings").update({ excluded_customer_ids: excluded }).eq("singleton", true);
27
- if (error)
28
- throw error;
27
+ await saveOfferSettings({ data: { excluded_customer_ids: excluded } });
29
28
  toast.success("Opgeslagen");
30
29
  }
31
30
  catch (e) {
32
- toast.error(e?.message ?? "Fout");
31
+ toast.error(e?.message ?? "Je instellingen zijn niet opgeslagen.");
33
32
  }
34
33
  finally {
35
34
  setSaving(false);