@infuro/cms-core 1.0.59 → 1.0.61

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 (33) hide show
  1. package/dist/admin.cjs +1020 -83
  2. package/dist/admin.js +1021 -84
  3. package/dist/api.cjs +38 -38
  4. package/dist/api.js +5 -5
  5. package/dist/auth.cjs +51 -51
  6. package/dist/auth.js +3 -3
  7. package/dist/{chunk-3EOM4V2M.cjs → chunk-4U6DATGZ.cjs} +7 -7
  8. package/dist/{chunk-BNKZNLEX.js → chunk-6DBDJ4VD.js} +2 -2
  9. package/dist/{chunk-UQWZUZ5X.cjs → chunk-73WVHINE.cjs} +30 -6
  10. package/dist/{chunk-UCKN4BBY.cjs → chunk-77I3KS7V.cjs} +4 -1
  11. package/dist/{chunk-MCCA7WLQ.cjs → chunk-CNAHVRRU.cjs} +533 -167
  12. package/dist/{chunk-77NUXO6A.cjs → chunk-J7FMZK66.cjs} +10 -2
  13. package/dist/{chunk-BC22I7C7.cjs → chunk-KM2I6AFP.cjs} +9 -9
  14. package/dist/{chunk-JIWUVQ6B.js → chunk-MVHDC3XF.js} +10 -2
  15. package/dist/{chunk-RK5ETF2I.js → chunk-NR44CK5E.js} +1 -1
  16. package/dist/{chunk-MXIWUFBP.js → chunk-O2642WVS.js} +28 -4
  17. package/dist/{chunk-HCIRL37O.js → chunk-T3HPSF7K.js} +4 -1
  18. package/dist/{chunk-BTUEUA5H.js → chunk-UHT4INVW.js} +453 -87
  19. package/dist/{chunk-HYW3MUXT.js → chunk-URN7GS23.js} +30 -16
  20. package/dist/{chunk-HXLC56ZK.cjs → chunk-YSYX4GUD.cjs} +32 -18
  21. package/dist/{emit-order-notification-trigger-HZQPPSFB.js → emit-order-notification-trigger-CZU3V2WE.js} +2 -2
  22. package/dist/{emit-order-notification-trigger-NR3VN6C6.cjs → emit-order-notification-trigger-TNEZOYEE.cjs} +4 -4
  23. package/dist/index.cjs +614 -333
  24. package/dist/index.d.cts +31 -0
  25. package/dist/index.d.ts +31 -0
  26. package/dist/index.js +329 -48
  27. package/dist/migrations/1782800000000-AddPaymentSuccessFailedRefundTriggers.ts +32 -0
  28. package/dist/migrations/1782900000000-AddEnableTurnstileToForms.ts +29 -0
  29. package/dist/{order-notification-dispatcher-2FCZFZUD.cjs → order-notification-dispatcher-KJVGLK5E.cjs} +5 -5
  30. package/dist/{order-notification-dispatcher-64WDCHHG.js → order-notification-dispatcher-ZSFKEYMP.js} +2 -2
  31. package/dist/{rbac-debug-db-6EMIVMGF.js → rbac-debug-db-5R6XFPSV.js} +1 -1
  32. package/dist/{rbac-debug-db-6PWTLBEL.cjs → rbac-debug-db-JKK2MCWF.cjs} +2 -2
  33. package/package.json +1 -1
package/dist/admin.js CHANGED
@@ -10,7 +10,7 @@ import { Slot } from '@radix-ui/react-slot';
10
10
  import { cva } from 'class-variance-authority';
11
11
  import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
12
12
  import * as LucideIcons from 'lucide-react';
13
- import { ChevronDown, User, Settings, LogOut, LayoutDashboard, Store, FolderTree, Layers, Building2, CalendarDays, ShoppingBag, Package, Receipt, BadgePercent, ShoppingCart, CreditCard, Users, Inbox, File, LinkIcon, ClipboardList, MessageSquare, Image as Image$1, Shield, FileText, Puzzle, X, Plus, MoreVertical, Upload, Download, Edit, Copy, RefreshCw, Trash2, Mail, Check, Save, Search, AlertCircle, MapPin, BarChart3, TrendingUp, UserCheck, Activity, Globe, Eye, MousePointer, ArrowLeftRight, RotateCcw, KeyRound, Loader2, CalendarClock, CheckSquare, Square, XSquare, Rss, ArrowLeft, Pencil, FolderPlus, LayoutGrid, List, Move, Archive, Edit2, ExternalLink, Power, Star, ChevronUp, Zap, Filter, CheckCircle2, ImageIcon, GripVertical, ChevronRight, ArrowRight, ArrowUpRight, IndianRupee, XCircle, Clock, Play, MessageCircle, Bot, FileUp, Tag, Box, SlidersHorizontal, Calculator, Link2 as Link2$1, Unlink, Gift, MoreHorizontal, Smartphone, ShieldCheck, QrCode, Car, Send, Settings2, DollarSign, Hash, Circle, ChevronLeft, Bell, Share2, HardDrive, Folder, Film } from 'lucide-react';
13
+ import { ChevronDown, User, Settings, LogOut, LayoutDashboard, Store, FolderTree, Layers, Building2, CalendarDays, ShoppingBag, Package, Receipt, BadgePercent, ShoppingCart, CreditCard, Users, Inbox, File, LinkIcon, ClipboardList, MessageSquare, Image as Image$1, Shield, FileText, Puzzle, X, ShieldAlert, Plus, MoreVertical, Upload, Download, Edit, Copy, RefreshCw, Trash2, Mail, Check, Save, Search, AlertCircle, MapPin, BarChart3, TrendingUp, UserCheck, Activity, Globe, Eye, MousePointer, ArrowLeftRight, RotateCcw, KeyRound, Loader2, CalendarClock, CheckSquare, Square, XSquare, Rss, ArrowLeft, Pencil, FolderPlus, LayoutGrid, List, Move, Archive, Edit2, ExternalLink, Power, Star, ChevronUp, Zap, Filter, CheckCircle2, ImageIcon, GripVertical, ChevronRight, ArrowRight, ArrowUpRight, IndianRupee, XCircle, Clock, Play, MessageCircle, Bot, FileUp, Tag, Box, SlidersHorizontal, Calculator, Link2 as Link2$1, Unlink, Gift, MoreHorizontal, ShieldCheck, EyeOff, Smartphone, QrCode, Car, Send, Settings2, DollarSign, Hash, Circle, ChevronLeft, Bell, Share2, HardDrive, Folder, Film } from 'lucide-react';
14
14
  import Image from 'next/image';
15
15
  import Link2 from 'next/link';
16
16
  import { usePathname, useSearchParams, useRouter } from 'next/navigation';
@@ -327,7 +327,15 @@ var init_vendor_scope = __esm({
327
327
  ...VENDOR_SCOPED_STORE_ENTITIES,
328
328
  "order_discounts",
329
329
  "order_addresses",
330
- "event_products"
330
+ "event_products",
331
+ "attendees",
332
+ "order-attendees",
333
+ "order_attendees",
334
+ "scan",
335
+ "verify-attendee-qr",
336
+ "verify_attendee_qr",
337
+ "forms",
338
+ "cms-forms"
331
339
  ]);
332
340
  __name(isVendorGroupName, "isVendorGroupName");
333
341
  __name(isPlatformAdministrator, "isPlatformAdministrator");
@@ -853,7 +861,7 @@ var init_admin_config_context = __esm({
853
861
  var CMS_VERSION;
854
862
  var init_cms_version = __esm({
855
863
  "src/lib/cms-version.ts"() {
856
- CMS_VERSION = "1.0.59" ;
864
+ CMS_VERSION = "1.0.61" ;
857
865
  }
858
866
  });
859
867
  function useCatalogCategories(enabled = true) {
@@ -1575,6 +1583,15 @@ var init_builtin_plugin_descriptors = __esm({
1575
1583
  enabled: true,
1576
1584
  icon: "auth_providers",
1577
1585
  settingsGroup: "auth_providers"
1586
+ },
1587
+ {
1588
+ name: "turnstile",
1589
+ label: "Cloudflare Turnstile",
1590
+ version: "1.0.0",
1591
+ description: "Protect forms, registration, and authentication from spam and bots with Cloudflare Turnstile.",
1592
+ enabled: true,
1593
+ icon: "turnstile",
1594
+ settingsGroup: "turnstile"
1578
1595
  }
1579
1596
  ];
1580
1597
  }
@@ -2175,6 +2192,11 @@ function FileUpload({ onUploadSuccess }) {
2175
2192
  if (!selectedFile) return;
2176
2193
  setUploading(true);
2177
2194
  setProgress(10);
2195
+ console.info("[upload] file-upload start", {
2196
+ name: selectedFile.name,
2197
+ type: selectedFile.type,
2198
+ size: selectedFile.size
2199
+ });
2178
2200
  const formData = new FormData();
2179
2201
  formData.append("file", selectedFile);
2180
2202
  try {
@@ -2183,20 +2205,47 @@ function FileUpload({ onUploadSuccess }) {
2183
2205
  body: formData
2184
2206
  });
2185
2207
  setProgress(60);
2186
- const data = await response.json();
2208
+ const data = await response.json().catch((parseErr) => {
2209
+ console.error("[upload] file-upload JSON parse failed", {
2210
+ status: response.status,
2211
+ parseErr
2212
+ });
2213
+ return {};
2214
+ });
2215
+ console.info("[upload] file-upload response", {
2216
+ status: response.status,
2217
+ ok: response.ok,
2218
+ url: data.url ?? data.filePath ?? null,
2219
+ error: data.error ?? null,
2220
+ details: data.details ?? null
2221
+ });
2187
2222
  if (response.ok) {
2188
- setFileUrl(data.url);
2223
+ const url = data.url ?? data.filePath;
2224
+ setFileUrl(url);
2189
2225
  setProgress(100);
2190
- onUploadSuccess(data.url);
2226
+ console.info("[upload] file-upload ok", {
2227
+ url,
2228
+ name: selectedFile.name
2229
+ });
2230
+ onUploadSuccess(url);
2191
2231
  } else {
2192
2232
  console.error("[upload] API error", {
2193
2233
  status: response.status,
2194
2234
  error: data.error,
2195
- details: data.details
2235
+ details: data.details,
2236
+ name: selectedFile.name,
2237
+ type: selectedFile.type,
2238
+ size: selectedFile.size
2196
2239
  });
2197
2240
  }
2198
2241
  } catch (error) {
2199
- console.error("Upload error:", error);
2242
+ console.error("[upload] file-upload failed", {
2243
+ name: selectedFile.name,
2244
+ type: selectedFile.type,
2245
+ size: selectedFile.size,
2246
+ error: error instanceof Error ? error.message : String(error),
2247
+ stack: error instanceof Error ? error.stack : void 0
2248
+ });
2200
2249
  }
2201
2250
  setUploading(false);
2202
2251
  }, "handleUpload");
@@ -2328,28 +2377,64 @@ function ImageOrUrlField({ label, value, onChange, placeholder = "https://\u2026
2328
2377
  return;
2329
2378
  }
2330
2379
  setIsUploading(true);
2380
+ console.info("[upload] ImageOrUrlField start", {
2381
+ name: file.name,
2382
+ type: file.type,
2383
+ size: file.size,
2384
+ isVideo: isVideo2,
2385
+ maxSizeMb
2386
+ });
2331
2387
  try {
2332
2388
  const formData = new FormData();
2333
2389
  formData.append("file", file);
2390
+ console.info("[upload] ImageOrUrlField POST /api/upload", {
2391
+ name: file.name,
2392
+ type: file.type,
2393
+ size: file.size
2394
+ });
2334
2395
  const response = await fetch("/api/upload", {
2335
2396
  method: "POST",
2336
2397
  body: formData
2337
2398
  });
2338
- const data = await response.json().catch(() => ({}));
2399
+ const data = await response.json().catch((parseErr) => {
2400
+ console.error("[upload] ImageOrUrlField JSON parse failed", {
2401
+ status: response.status,
2402
+ parseErr
2403
+ });
2404
+ return {};
2405
+ });
2406
+ console.info("[upload] ImageOrUrlField response", {
2407
+ status: response.status,
2408
+ ok: response.ok,
2409
+ filePath: data.filePath ?? null,
2410
+ error: data.error ?? null,
2411
+ details: data.details ?? null
2412
+ });
2339
2413
  if (!response.ok) {
2340
2414
  console.error("[upload] API error", {
2341
2415
  status: response.status,
2342
2416
  error: data.error,
2343
- details: data.details
2417
+ details: data.details,
2418
+ fileName: file.name,
2419
+ fileType: file.type,
2420
+ fileSize: file.size
2344
2421
  });
2345
2422
  throw new Error(data.details || data.error || "Upload failed");
2346
2423
  }
2347
2424
  console.info("[upload] API ok", {
2348
- filePath: data.filePath
2425
+ filePath: data.filePath,
2426
+ name: file.name,
2427
+ type: file.type
2349
2428
  });
2350
2429
  onChange(data.filePath ?? "");
2351
2430
  } catch (err) {
2352
- console.error("[upload] client failed", err);
2431
+ console.error("[upload] client failed", {
2432
+ name: file.name,
2433
+ type: file.type,
2434
+ size: file.size,
2435
+ error: err instanceof Error ? err.message : String(err),
2436
+ stack: err instanceof Error ? err.stack : void 0
2437
+ });
2353
2438
  setError(err instanceof Error ? err.message : "Upload failed");
2354
2439
  } finally {
2355
2440
  setIsUploading(false);
@@ -5048,6 +5133,9 @@ function AdminCRUD({ title, apiEndpoint, columns, addEditPageUrl, customViewPage
5048
5133
  });
5049
5134
  if (cancelled) return;
5050
5135
  if (!res.ok) {
5136
+ if (res.status === 403) {
5137
+ throw new Error("You don't have access to this section");
5138
+ }
5051
5139
  throw new Error(`HTTP error! status: ${res.status}`);
5052
5140
  }
5053
5141
  const result = await res.json();
@@ -5464,15 +5552,26 @@ function AdminCRUD({ title, apiEndpoint, columns, addEditPageUrl, customViewPage
5464
5552
  }, "Loading...")));
5465
5553
  }
5466
5554
  if (error) {
5555
+ const isForbidden = error.toLowerCase().includes("don't have access") || error.toLowerCase().includes("forbidden") || error.includes("403");
5467
5556
  return /* @__PURE__ */ React.createElement("div", {
5468
5557
  className: embedded ? "p-4" : "rounded-lg bg-white p-6 shadow-md"
5469
5558
  }, /* @__PURE__ */ React.createElement("div", {
5470
- className: "text-center py-8"
5471
- }, /* @__PURE__ */ React.createElement("p", {
5559
+ className: "text-center py-10"
5560
+ }, isForbidden ? /* @__PURE__ */ React.createElement("div", {
5561
+ className: "max-w-md mx-auto"
5562
+ }, /* @__PURE__ */ React.createElement("div", {
5563
+ className: "mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-red-100 mb-3"
5564
+ }, /* @__PURE__ */ React.createElement(ShieldAlert, {
5565
+ className: "h-6 w-6 text-red-600"
5566
+ })), /* @__PURE__ */ React.createElement("h3", {
5567
+ className: "text-base font-semibold text-gray-900 mb-1"
5568
+ }, "You don't have access"), /* @__PURE__ */ React.createElement("p", {
5569
+ className: "text-sm text-gray-500"
5570
+ }, "You don't have permission to view or manage this tab.")) : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("p", {
5472
5571
  className: "text-red-600 mb-4"
5473
5572
  }, "Error loading data: ", error), /* @__PURE__ */ React.createElement(Button, {
5474
5573
  onClick: /* @__PURE__ */ __name(() => window.location.reload(), "onClick")
5475
- }, "Retry")));
5574
+ }, "Retry"))));
5476
5575
  }
5477
5576
  return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", {
5478
5577
  className: shellCls
@@ -6309,6 +6408,7 @@ function FormBuilder({ formId, duplicateFromId }) {
6309
6408
  campaign: "",
6310
6409
  slug: "",
6311
6410
  published: false,
6411
+ enableTurnstile: false,
6312
6412
  fields: []
6313
6413
  });
6314
6414
  const [errors, setErrors] = useState([]);
@@ -6342,6 +6442,7 @@ function FormBuilder({ formId, duplicateFromId }) {
6342
6442
  campaign: data.campaign || "",
6343
6443
  slug: slugNext,
6344
6444
  published: isDup ? false : data.published,
6445
+ enableTurnstile: Boolean(data.enableTurnstile ?? data.enable_turnstile),
6345
6446
  fields: (data.fields ?? []).map((field, index) => ({
6346
6447
  ...field,
6347
6448
  id: isDup ? `field_dup_${Date.now()}_${index}` : field?.id != null ? String(field.id) : `field_${Date.now()}_${index}`,
@@ -6650,7 +6751,21 @@ function FormBuilder({ formId, duplicateFromId }) {
6650
6751
  })), "onCheckedChange")
6651
6752
  }), /* @__PURE__ */ React.createElement("label", {
6652
6753
  className: "text-sm font-medium text-gray-700"
6653
- }, "Published"))))), /* @__PURE__ */ React.createElement("div", {
6754
+ }, "Published")), /* @__PURE__ */ React.createElement("div", {
6755
+ className: "flex items-center space-x-2 pt-2 border-t border-gray-100"
6756
+ }, /* @__PURE__ */ React.createElement(Switch, {
6757
+ checked: formData.enableTurnstile ?? false,
6758
+ onCheckedChange: /* @__PURE__ */ __name((checked) => setFormData((prev) => ({
6759
+ ...prev,
6760
+ enableTurnstile: checked
6761
+ })), "onCheckedChange")
6762
+ }), /* @__PURE__ */ React.createElement("div", {
6763
+ className: "flex flex-col"
6764
+ }, /* @__PURE__ */ React.createElement("label", {
6765
+ className: "text-sm font-medium text-gray-700"
6766
+ }, "Enable Cloudflare Turnstile"), /* @__PURE__ */ React.createElement("span", {
6767
+ className: "text-xs text-gray-500"
6768
+ }, "Requires user verification before submission (when Turnstile plugin is active)")))))), /* @__PURE__ */ React.createElement("div", {
6654
6769
  className: "lg:col-span-2"
6655
6770
  }, /* @__PURE__ */ React.createElement("div", {
6656
6771
  className: "bg-white rounded-lg shadow"
@@ -6820,26 +6935,63 @@ function ImageUpload({ value, onChange, onRemove, label, required = false, class
6820
6935
  return;
6821
6936
  }
6822
6937
  setIsUploading(true);
6938
+ console.info("[upload] client image-upload start", {
6939
+ name: file.name,
6940
+ type: file.type,
6941
+ size: file.size,
6942
+ maxSizeMb: maxSize
6943
+ });
6823
6944
  try {
6824
6945
  const formData = new FormData();
6825
6946
  formData.append("file", file);
6947
+ console.info("[upload] client POST /api/upload", {
6948
+ name: file.name,
6949
+ type: file.type,
6950
+ size: file.size
6951
+ });
6826
6952
  const response = await fetch("/api/upload", {
6827
6953
  method: "POST",
6828
6954
  body: formData
6829
6955
  });
6830
- const data = await response.json();
6956
+ const data = await response.json().catch((parseErr) => {
6957
+ console.error("[upload] client failed to parse JSON response", {
6958
+ status: response.status,
6959
+ parseErr
6960
+ });
6961
+ return {};
6962
+ });
6963
+ console.info("[upload] client response", {
6964
+ status: response.status,
6965
+ ok: response.ok,
6966
+ filePath: data.filePath ?? null,
6967
+ error: data.error ?? null,
6968
+ details: data.details ?? null
6969
+ });
6831
6970
  if (!response.ok) {
6832
6971
  console.error("[upload] API error", {
6833
6972
  status: response.status,
6834
6973
  error: data.error,
6835
- details: data.details
6974
+ details: data.details,
6975
+ fileName: file.name,
6976
+ fileType: file.type,
6977
+ fileSize: file.size
6836
6978
  });
6837
6979
  throw new Error(data.details || data.error || "Upload failed");
6838
6980
  }
6981
+ console.info("[upload] client image-upload ok", {
6982
+ filePath: data.filePath,
6983
+ name: file.name
6984
+ });
6839
6985
  onChange(data.filePath);
6840
6986
  setUploadProgress(100);
6841
6987
  } catch (error2) {
6842
- console.error("Upload error:", error2);
6988
+ console.error("[upload] client image-upload failed", {
6989
+ name: file.name,
6990
+ type: file.type,
6991
+ size: file.size,
6992
+ error: error2 instanceof Error ? error2.message : String(error2),
6993
+ stack: error2 instanceof Error ? error2.stack : void 0
6994
+ });
6843
6995
  setError(error2 instanceof Error ? error2.message : "Failed to upload image");
6844
6996
  } finally {
6845
6997
  setIsUploading(false);
@@ -16143,6 +16295,7 @@ var init_order_notification_bindings_shared = __esm({
16143
16295
  "orderNumber",
16144
16296
  "customerName",
16145
16297
  "customerPhone",
16298
+ "customerEmail",
16146
16299
  "eventName",
16147
16300
  "vendorName",
16148
16301
  "total",
@@ -16150,7 +16303,8 @@ var init_order_notification_bindings_shared = __esm({
16150
16303
  "vendorId",
16151
16304
  "refundAmount",
16152
16305
  "invoiceNumber",
16153
- "invoiceUrl"
16306
+ "invoiceAttachment",
16307
+ "orderDetails"
16154
16308
  ];
16155
16309
  __name(useNotificationVariables, "useNotificationVariables");
16156
16310
  EMAIL_TRIGGER_DEFAULTS = {
@@ -16182,6 +16336,43 @@ If you have any questions, please contact us.
16182
16336
 
16183
16337
  Thank you!`,
16184
16338
  bodyPlaceholder: "Hi {{customerName}},\n\nYour order {{orderNumber}} has been cancelled.\n\nIf a refund is due, it will be returned to your original payment method."
16339
+ },
16340
+ payment_success: {
16341
+ subject: "Payment Successful for Order {{orderNumber}}",
16342
+ subjectPlaceholder: "e.g. Payment Successful for Order {{orderNumber}}",
16343
+ body: `Hi {{customerName}},
16344
+
16345
+ We have received your payment for order {{orderNumber}} of {{total}} {{currency}}.
16346
+
16347
+ Order Details:
16348
+ {{orderDetails}}
16349
+
16350
+ Thank you!`,
16351
+ bodyPlaceholder: "Hi {{customerName}},\n\nPayment for order {{orderNumber}} was successful.\n\nThank you!"
16352
+ },
16353
+ payment_failed: {
16354
+ subject: "Payment Failed for Order {{orderNumber}}",
16355
+ subjectPlaceholder: "e.g. Payment Failed for Order {{orderNumber}}",
16356
+ body: `Hi {{customerName}},
16357
+
16358
+ Your payment for order {{orderNumber}} of {{total}} {{currency}} could not be completed or remains unpaid.
16359
+
16360
+ If you were charged, please contact us with your order number {{orderNumber}}.
16361
+
16362
+ Thank you!`,
16363
+ bodyPlaceholder: "Hi {{customerName}},\n\nPayment for order {{orderNumber}} failed or remains unpaid.\n\nThank you!"
16364
+ },
16365
+ payment_refund_initiated: {
16366
+ subject: "Refund Initiated for Order {{orderNumber}}",
16367
+ subjectPlaceholder: "e.g. Refund Initiated for Order {{orderNumber}}",
16368
+ body: `Hi {{customerName}},
16369
+
16370
+ Your order {{orderNumber}} has been cancelled and a refund of {{refundAmount}} {{currency}} has been initiated.
16371
+
16372
+ The amount will be credited back to your original payment method.
16373
+
16374
+ Thank you!`,
16375
+ bodyPlaceholder: "Hi {{customerName}},\n\nRefund of {{refundAmount}} {{currency}} for order {{orderNumber}} has been initiated."
16185
16376
  }
16186
16377
  };
16187
16378
  VENDOR_EMAIL_TRIGGER_DEFAULTS = {
@@ -16206,12 +16397,48 @@ Thank you!`,
16206
16397
 
16207
16398
  Order {{orderNumber}} for {{total}} {{currency}} has been cancelled.
16208
16399
 
16209
- Customer: {{customerName}
16400
+ Customer: {{customerName}}
16210
16401
  Order Items:
16211
16402
  {{orderDetails}}
16212
16403
 
16213
16404
  Thank you.`,
16214
16405
  bodyPlaceholder: "Hi {{vendorName}},\n\nOrder {{orderNumber}} was cancelled."
16406
+ },
16407
+ payment_success: {
16408
+ subject: "Payment Confirmed for Order {{orderNumber}} - {{customerName}}",
16409
+ subjectPlaceholder: "e.g. Payment Confirmed for Order {{orderNumber}}",
16410
+ body: `Hi {{vendorName}},
16411
+
16412
+ Payment has been confirmed for order {{orderNumber}} ({{total}} {{currency}}).
16413
+
16414
+ Customer: {{customerName}}
16415
+ Order Items:
16416
+ {{orderDetails}}
16417
+
16418
+ Thank you!`,
16419
+ bodyPlaceholder: "Hi {{vendorName}},\n\nPayment for order {{orderNumber}} confirmed."
16420
+ },
16421
+ payment_failed: {
16422
+ subject: "Payment Unpaid / Failed for Order {{orderNumber}}",
16423
+ subjectPlaceholder: "e.g. Payment Unpaid for Order {{orderNumber}}",
16424
+ body: `Hi {{vendorName}},
16425
+
16426
+ Payment for order {{orderNumber}} ({{total}} {{currency}}) is unpaid or failed.
16427
+
16428
+ Customer: {{customerName}}
16429
+
16430
+ Thank you.`,
16431
+ bodyPlaceholder: "Hi {{vendorName}},\n\nPayment for order {{orderNumber}} failed or unpaid."
16432
+ },
16433
+ payment_refund_initiated: {
16434
+ subject: "Refund Initiated for Order {{orderNumber}}",
16435
+ subjectPlaceholder: "e.g. Refund Initiated for Order {{orderNumber}}",
16436
+ body: `Hi {{vendorName}},
16437
+
16438
+ Order {{orderNumber}} has been cancelled and a refund of {{refundAmount}} {{currency}} has been initiated for {{customerName}}.
16439
+
16440
+ Thank you.`,
16441
+ bodyPlaceholder: "Hi {{vendorName}},\n\nRefund initiated for order {{orderNumber}}."
16215
16442
  }
16216
16443
  };
16217
16444
  ADMIN_EMAIL_TRIGGER_DEFAULTS = {
@@ -16243,6 +16470,42 @@ Customer: {{customerName}}
16243
16470
  Vendor: {{vendorName}}
16244
16471
  Refund Due: {{refundAmount}} {{currency}}`,
16245
16472
  bodyPlaceholder: "Hi Admin,\n\nOrder {{orderNumber}} cancelled."
16473
+ },
16474
+ payment_success: {
16475
+ subject: "Payment Success Alert: Order {{orderNumber}} ({{total}} {{currency}})",
16476
+ subjectPlaceholder: "e.g. Payment Success Alert: Order {{orderNumber}}",
16477
+ body: `Hi Admin,
16478
+
16479
+ Payment of {{total}} {{currency}} was successfully captured for order {{orderNumber}}.
16480
+
16481
+ Customer: {{customerName}}
16482
+ Vendor: {{vendorName}}
16483
+
16484
+ Order Summary:
16485
+ {{orderDetails}}`,
16486
+ bodyPlaceholder: "Payment received for order {{orderNumber}}."
16487
+ },
16488
+ payment_failed: {
16489
+ subject: "Payment Failed / Unpaid Alert: Order {{orderNumber}}",
16490
+ subjectPlaceholder: "e.g. Payment Failed Alert: Order {{orderNumber}}",
16491
+ body: `Hi Admin,
16492
+
16493
+ Payment for order {{orderNumber}} ({{total}} {{currency}}) is unpaid or failed.
16494
+
16495
+ Customer: {{customerName}}
16496
+ Vendor: {{vendorName}}`,
16497
+ bodyPlaceholder: "Payment failed/unpaid for order {{orderNumber}}."
16498
+ },
16499
+ payment_refund_initiated: {
16500
+ subject: "Refund Initiated Alert: Order {{orderNumber}}",
16501
+ subjectPlaceholder: "e.g. Refund Initiated Alert: Order {{orderNumber}}",
16502
+ body: `Hi Admin,
16503
+
16504
+ Order {{orderNumber}} was cancelled and refund of {{refundAmount}} {{currency}} was initiated.
16505
+
16506
+ Customer: {{customerName}}
16507
+ Vendor: {{vendorName}}`,
16508
+ bodyPlaceholder: "Refund initiated for order {{orderNumber}}."
16246
16509
  }
16247
16510
  };
16248
16511
  __name(getEmailTriggerDefaults, "getEmailTriggerDefaults");
@@ -16258,6 +16521,24 @@ Refund Due: {{refundAmount}} {{currency}}`,
16258
16521
  subjectPlaceholder: "e.g. Order {{orderNumber}} Cancelled",
16259
16522
  body: `Hi {{customerName}}, your order {{orderNumber}} for {{total}} {{currency}} has been cancelled.`,
16260
16523
  bodyPlaceholder: "Hi {{customerName}}, your order {{orderNumber}} has been cancelled."
16524
+ },
16525
+ payment_success: {
16526
+ subject: "Payment Successful!",
16527
+ subjectPlaceholder: "e.g. Payment Successful!",
16528
+ body: `Hi {{customerName}}, payment for order {{orderNumber}} ({{total}} {{currency}}) was successful.`,
16529
+ bodyPlaceholder: "Payment for order {{orderNumber}} successful."
16530
+ },
16531
+ payment_failed: {
16532
+ subject: "Payment Unpaid / Failed",
16533
+ subjectPlaceholder: "e.g. Payment Unpaid / Failed",
16534
+ body: `Hi {{customerName}}, payment for order {{orderNumber}} failed or remains unpaid.`,
16535
+ bodyPlaceholder: "Payment for order {{orderNumber}} failed."
16536
+ },
16537
+ payment_refund_initiated: {
16538
+ subject: "Refund Initiated",
16539
+ subjectPlaceholder: "e.g. Refund Initiated",
16540
+ body: `Hi {{customerName}}, a refund of {{refundAmount}} {{currency}} for order {{orderNumber}} has been initiated.`,
16541
+ bodyPlaceholder: "Refund for order {{orderNumber}} initiated."
16261
16542
  }
16262
16543
  };
16263
16544
  VENDOR_PUSH_TRIGGER_DEFAULTS = {
@@ -16272,6 +16553,24 @@ Refund Due: {{refundAmount}} {{currency}}`,
16272
16553
  subjectPlaceholder: "e.g. Order {{orderNumber}} Cancelled",
16273
16554
  body: `Hi {{vendorName}}, order {{orderNumber}} from {{customerName}} has been cancelled.`,
16274
16555
  bodyPlaceholder: "Hi {{vendorName}}, order {{orderNumber}} was cancelled."
16556
+ },
16557
+ payment_success: {
16558
+ subject: "Payment Confirmed: {{orderNumber}}",
16559
+ subjectPlaceholder: "e.g. Payment Confirmed: {{orderNumber}}",
16560
+ body: `Hi {{vendorName}}, payment for order {{orderNumber}} from {{customerName}} has been confirmed.`,
16561
+ bodyPlaceholder: "Payment for order {{orderNumber}} confirmed."
16562
+ },
16563
+ payment_failed: {
16564
+ subject: "Payment Unpaid: {{orderNumber}}",
16565
+ subjectPlaceholder: "e.g. Payment Unpaid: {{orderNumber}}",
16566
+ body: `Hi {{vendorName}}, payment for order {{orderNumber}} from {{customerName}} is unpaid or failed.`,
16567
+ bodyPlaceholder: "Payment for order {{orderNumber}} unpaid."
16568
+ },
16569
+ payment_refund_initiated: {
16570
+ subject: "Refund Initiated: {{orderNumber}}",
16571
+ subjectPlaceholder: "e.g. Refund Initiated: {{orderNumber}}",
16572
+ body: `Hi {{vendorName}}, refund initiated for order {{orderNumber}} ({{customerName}}).`,
16573
+ bodyPlaceholder: "Refund initiated for order {{orderNumber}}."
16275
16574
  }
16276
16575
  };
16277
16576
  ADMIN_PUSH_TRIGGER_DEFAULTS = {
@@ -16303,6 +16602,24 @@ Customer: {{customerName}}
16303
16602
  Vendor: {{vendorName}}
16304
16603
  Refund Due: {{refundAmount}} {{currency}}`,
16305
16604
  bodyPlaceholder: "Hi Admin,\n\nOrder {{orderNumber}} cancelled."
16605
+ },
16606
+ payment_success: {
16607
+ subject: "Payment Success: {{orderNumber}}",
16608
+ subjectPlaceholder: "e.g. Payment Success: {{orderNumber}}",
16609
+ body: `Payment of {{total}} {{currency}} captured for order {{orderNumber}} ({{customerName}}).`,
16610
+ bodyPlaceholder: "Payment captured for order {{orderNumber}}."
16611
+ },
16612
+ payment_failed: {
16613
+ subject: "Payment Failed/Unpaid: {{orderNumber}}",
16614
+ subjectPlaceholder: "e.g. Payment Failed: {{orderNumber}}",
16615
+ body: `Payment for order {{orderNumber}} ({{customerName}}) is unpaid or failed.`,
16616
+ bodyPlaceholder: "Payment failed for order {{orderNumber}}."
16617
+ },
16618
+ payment_refund_initiated: {
16619
+ subject: "Refund Initiated: {{orderNumber}}",
16620
+ subjectPlaceholder: "e.g. Refund Initiated: {{orderNumber}}",
16621
+ body: `Refund of {{refundAmount}} {{currency}} initiated for order {{orderNumber}}.`,
16622
+ bodyPlaceholder: "Refund initiated for order {{orderNumber}}."
16306
16623
  }
16307
16624
  };
16308
16625
  __name(getPushTriggerDefaults, "getPushTriggerDefaults");
@@ -16956,6 +17273,7 @@ var init_emailTriggersBindingsPage = __esm({
16956
17273
  orderNumber: "Display order number",
16957
17274
  customerName: "Customer full name",
16958
17275
  customerPhone: "Customer phone",
17276
+ customerEmail: "Customer email address",
16959
17277
  eventName: "Event title / product name",
16960
17278
  vendorName: "Vendor / store name",
16961
17279
  total: "Order total amount",
@@ -16963,7 +17281,8 @@ var init_emailTriggersBindingsPage = __esm({
16963
17281
  vendorId: "Vendor / store ID",
16964
17282
  refundAmount: "Refund amount (refund triggers)",
16965
17283
  invoiceNumber: "Generated invoice number",
16966
- invoiceUrl: "Link to download invoice PDF"
17284
+ invoiceAttachment: "Include this variable to attach Tax Invoice PDF to email",
17285
+ orderDetails: "Formatted item details summary"
16967
17286
  };
16968
17287
  __name(EmailTriggerBindings, "EmailTriggerBindings");
16969
17288
  __name(TriggerEmailEditor, "TriggerEmailEditor");
@@ -17261,6 +17580,7 @@ var init_pushTriggersBindingsPage = __esm({
17261
17580
  orderNumber: "Display order number",
17262
17581
  customerName: "Customer full name",
17263
17582
  customerPhone: "Customer phone",
17583
+ customerEmail: "Customer email address",
17264
17584
  eventName: "Event title / product name",
17265
17585
  vendorName: "Vendor / store name",
17266
17586
  total: "Order total amount",
@@ -17268,7 +17588,8 @@ var init_pushTriggersBindingsPage = __esm({
17268
17588
  vendorId: "Vendor / store ID",
17269
17589
  refundAmount: "Refund amount (refund triggers)",
17270
17590
  invoiceNumber: "Generated invoice number",
17271
- invoiceUrl: "Link to download invoice PDF"
17591
+ invoiceAttachment: "Include this variable to attach Tax Invoice PDF to email",
17592
+ orderDetails: "Formatted item details summary"
17272
17593
  };
17273
17594
  __name(PushTriggerBindings, "PushTriggerBindings");
17274
17595
  }
@@ -18106,6 +18427,177 @@ var init_AuthProvidersPluginSettings = __esm({
18106
18427
  __name(AuthProvidersPluginSettings, "AuthProvidersPluginSettings");
18107
18428
  }
18108
18429
  });
18430
+ function TurnstilePluginSettings({ settingsGroup, onEnabledChange }) {
18431
+ const [loading, setLoading] = useState(true);
18432
+ const [saving, setSaving] = useState(false);
18433
+ const [enabled, setEnabled] = useState(false);
18434
+ const [siteKey, setSiteKey] = useState("");
18435
+ const [secretKey, setSecretKey] = useState("");
18436
+ const [hasSavedSecret, setHasSavedSecret] = useState(false);
18437
+ const [showSecret, setShowSecret] = useState(false);
18438
+ const onEnabledChangeRef = useRef(onEnabledChange);
18439
+ onEnabledChangeRef.current = onEnabledChange;
18440
+ useEffect(() => {
18441
+ let cancelled = false;
18442
+ (async () => {
18443
+ setLoading(true);
18444
+ try {
18445
+ const res = await fetch(`/api/settings/${settingsGroup}`, {
18446
+ cache: "no-store"
18447
+ });
18448
+ const data = res.ok ? await res.json() : {};
18449
+ if (cancelled) return;
18450
+ const isEnabled = data.enabled === "true" || data.enabled === "1";
18451
+ setEnabled(isEnabled);
18452
+ setSiteKey(String(data.siteKey ?? data.TURNSTILE_SITE_KEY ?? "").trim());
18453
+ const secret = String(data.secretKey ?? data.TURNSTILE_SECRET_KEY ?? "").trim();
18454
+ setSecretKey("");
18455
+ setHasSavedSecret(Boolean(secret));
18456
+ } catch {
18457
+ } finally {
18458
+ if (!cancelled) setLoading(false);
18459
+ }
18460
+ })();
18461
+ return () => {
18462
+ cancelled = true;
18463
+ };
18464
+ }, [
18465
+ settingsGroup
18466
+ ]);
18467
+ const handleSave = /* @__PURE__ */ __name(async () => {
18468
+ setSaving(true);
18469
+ try {
18470
+ const payload = {
18471
+ enabled: {
18472
+ value: enabled ? "true" : "false",
18473
+ type: "public"
18474
+ },
18475
+ siteKey: {
18476
+ value: siteKey.trim(),
18477
+ type: "public"
18478
+ }
18479
+ };
18480
+ if (secretKey.trim()) {
18481
+ payload.secretKey = {
18482
+ value: secretKey.trim(),
18483
+ type: "private",
18484
+ encrypted: true
18485
+ };
18486
+ }
18487
+ const res = await fetch(`/api/settings/${settingsGroup}`, {
18488
+ method: "PUT",
18489
+ headers: {
18490
+ "Content-Type": "application/json"
18491
+ },
18492
+ body: JSON.stringify(payload)
18493
+ });
18494
+ if (res.ok) {
18495
+ toast.success("Turnstile settings saved");
18496
+ if (secretKey.trim()) {
18497
+ setHasSavedSecret(true);
18498
+ setSecretKey("");
18499
+ }
18500
+ onEnabledChangeRef.current?.(enabled);
18501
+ } else {
18502
+ const err = await res.json().catch(() => ({}));
18503
+ toast.error(err?.message || "Failed to save settings");
18504
+ }
18505
+ } catch {
18506
+ toast.error("Failed to save settings");
18507
+ } finally {
18508
+ setSaving(false);
18509
+ }
18510
+ }, "handleSave");
18511
+ if (loading) {
18512
+ return /* @__PURE__ */ React.createElement("div", {
18513
+ className: "flex items-center justify-center p-8 text-muted-foreground text-sm"
18514
+ }, /* @__PURE__ */ React.createElement(Loader2, {
18515
+ className: "mr-2 h-4 w-4 animate-spin"
18516
+ }), "Loading settings\u2026");
18517
+ }
18518
+ return /* @__PURE__ */ React.createElement("div", {
18519
+ className: "space-y-6"
18520
+ }, /* @__PURE__ */ React.createElement("div", {
18521
+ className: "rounded-lg border border-gray-200 bg-gray-50/80 p-4 dark:border-gray-700 dark:bg-gray-800/40"
18522
+ }, /* @__PURE__ */ React.createElement("div", {
18523
+ className: "flex items-center justify-between"
18524
+ }, /* @__PURE__ */ React.createElement("div", {
18525
+ className: "space-y-0.5"
18526
+ }, /* @__PURE__ */ React.createElement(Label3, {
18527
+ htmlFor: `${settingsGroup}-enabled`,
18528
+ className: "text-sm font-semibold text-gray-900 dark:text-white"
18529
+ }, "Enable Cloudflare Turnstile"), /* @__PURE__ */ React.createElement("p", {
18530
+ className: "text-xs text-muted-foreground"
18531
+ }, "Master switch for bot protection. When enabled, helps protect supported forms from automated bots, while forms can apply their configured bot protection settings.")), /* @__PURE__ */ React.createElement(Switch, {
18532
+ id: `${settingsGroup}-enabled`,
18533
+ checked: enabled,
18534
+ onCheckedChange: setEnabled
18535
+ }))), /* @__PURE__ */ React.createElement("div", {
18536
+ className: "space-y-4"
18537
+ }, /* @__PURE__ */ React.createElement("div", {
18538
+ className: "space-y-1.5"
18539
+ }, /* @__PURE__ */ React.createElement(Label3, {
18540
+ htmlFor: `${settingsGroup}-siteKey`,
18541
+ className: "text-sm font-medium"
18542
+ }, "Turnstile Site Key (Public)"), /* @__PURE__ */ React.createElement(Input, {
18543
+ id: `${settingsGroup}-siteKey`,
18544
+ value: siteKey,
18545
+ onChange: /* @__PURE__ */ __name((e) => setSiteKey(e.target.value), "onChange"),
18546
+ placeholder: "0x4AAAAAA...",
18547
+ className: "h-9 font-mono text-sm"
18548
+ }), /* @__PURE__ */ React.createElement("p", {
18549
+ className: "text-xs text-muted-foreground"
18550
+ }, "Obtained from your Cloudflare dashboard under Turnstile Sites. Safe to expose to the frontend.")), /* @__PURE__ */ React.createElement("div", {
18551
+ className: "space-y-1.5"
18552
+ }, /* @__PURE__ */ React.createElement("div", {
18553
+ className: "flex items-center justify-between"
18554
+ }, /* @__PURE__ */ React.createElement(Label3, {
18555
+ htmlFor: `${settingsGroup}-secretKey`,
18556
+ className: "text-sm font-medium"
18557
+ }, "Turnstile Secret Key (Private)"), hasSavedSecret && /* @__PURE__ */ React.createElement("span", {
18558
+ className: "inline-flex items-center gap-1 text-xs font-medium text-emerald-600 dark:text-emerald-400"
18559
+ }, /* @__PURE__ */ React.createElement(ShieldCheck, {
18560
+ className: "h-3.5 w-3.5"
18561
+ }), "Secret key configured")), /* @__PURE__ */ React.createElement("div", {
18562
+ className: "relative"
18563
+ }, /* @__PURE__ */ React.createElement(Input, {
18564
+ id: `${settingsGroup}-secretKey`,
18565
+ type: showSecret ? "text" : "password",
18566
+ value: secretKey,
18567
+ onChange: /* @__PURE__ */ __name((e) => setSecretKey(e.target.value), "onChange"),
18568
+ placeholder: hasSavedSecret ? "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022 (leave blank to keep unchanged)" : "0x4AAAAAA...",
18569
+ className: "h-9 font-mono text-sm pr-10"
18570
+ }), /* @__PURE__ */ React.createElement("button", {
18571
+ type: "button",
18572
+ className: "absolute right-2.5 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-200",
18573
+ onClick: /* @__PURE__ */ __name(() => setShowSecret(!showSecret), "onClick")
18574
+ }, showSecret ? /* @__PURE__ */ React.createElement(EyeOff, {
18575
+ className: "h-4 w-4"
18576
+ }) : /* @__PURE__ */ React.createElement(Eye, {
18577
+ className: "h-4 w-4"
18578
+ }))), /* @__PURE__ */ React.createElement("p", {
18579
+ className: "text-xs text-muted-foreground"
18580
+ }, "Used only on the server to verify tokens with Cloudflare Siteverify API. Never sent to browsers."))), /* @__PURE__ */ React.createElement(Button, {
18581
+ onClick: handleSave,
18582
+ disabled: saving,
18583
+ size: "sm",
18584
+ className: "gap-2"
18585
+ }, saving ? /* @__PURE__ */ React.createElement(Loader2, {
18586
+ className: "h-4 w-4 animate-spin"
18587
+ }) : /* @__PURE__ */ React.createElement(Save, {
18588
+ className: "h-4 w-4"
18589
+ }), "Save Settings"));
18590
+ }
18591
+ var init_TurnstilePluginSettings = __esm({
18592
+ "src/admin/components/TurnstilePluginSettings.tsx"() {
18593
+ "use client";
18594
+ init_button();
18595
+ init_input();
18596
+ init_label();
18597
+ init_switch();
18598
+ __name(TurnstilePluginSettings, "TurnstilePluginSettings");
18599
+ }
18600
+ });
18109
18601
 
18110
18602
  // src/plugins/llm/chat-email-intent.ts
18111
18603
  function normalizeIntentKey(raw) {
@@ -18484,6 +18976,7 @@ function PluginSettingsPanel({ descriptor, onSaved }) {
18484
18976
  const isSocialMedia = settingsGroup === "social_media";
18485
18977
  const isMultiVendor = settingsGroup === "multi_vendor";
18486
18978
  const isAuthProviders = settingsGroup === "auth_providers";
18979
+ const isTurnstile = settingsGroup === "turnstile";
18487
18980
  const [enabled, setEnabled] = useState(true);
18488
18981
  const [botName, setBotName] = useState("");
18489
18982
  const [icon, setIcon] = useState("");
@@ -19827,6 +20320,15 @@ function PluginSettingsPanel({ descriptor, onSaved }) {
19827
20320
  }, "onEnabledChange")
19828
20321
  });
19829
20322
  }
20323
+ if (isTurnstile) {
20324
+ return /* @__PURE__ */ React.createElement(TurnstilePluginSettings, {
20325
+ settingsGroup,
20326
+ onEnabledChange: /* @__PURE__ */ __name((value) => {
20327
+ setEnabled(value);
20328
+ onSaved?.();
20329
+ }, "onEnabledChange")
20330
+ });
20331
+ }
19830
20332
  if (loading) return /* @__PURE__ */ React.createElement("div", {
19831
20333
  className: "text-sm text-gray-500 dark:text-gray-400"
19832
20334
  }, "Loading...");
@@ -21167,6 +21669,10 @@ function PluginsPage() {
21167
21669
  const ap = pluginDescriptors.find((p) => p.name === "auth_providers");
21168
21670
  if (ap) setSelectedName(ap.name);
21169
21671
  }
21672
+ if (q === "turnstile" || q === "cloudflare_turnstile" || q === "captcha") {
21673
+ const ts = pluginDescriptors.find((p) => p.name === "turnstile");
21674
+ if (ts) setSelectedName(ts.name);
21675
+ }
21170
21676
  }, [
21171
21677
  searchParams,
21172
21678
  pluginDescriptors
@@ -21279,6 +21785,7 @@ var init_PluginsPage = __esm({
21279
21785
  init_select();
21280
21786
  init_EventNotificationsPluginSettings();
21281
21787
  init_AuthProvidersPluginSettings();
21788
+ init_TurnstilePluginSettings();
21282
21789
  init_ImageOrUrlField();
21283
21790
  init_chat_email_intent();
21284
21791
  init_llm_agent_scope();
@@ -21298,7 +21805,8 @@ var init_PluginsPage = __esm({
21298
21805
  share: Share2,
21299
21806
  whatsapp: MessageCircle,
21300
21807
  event_notifications: Bell,
21301
- auth_providers: KeyRound
21808
+ auth_providers: KeyRound,
21809
+ turnstile: ShieldCheck
21302
21810
  };
21303
21811
  __name(normalizeSmsProviderChoice, "normalizeSmsProviderChoice");
21304
21812
  __name(normalizeMsg91ApiMode, "normalizeMsg91ApiMode");
@@ -22260,7 +22768,7 @@ function VendorTeamPage() {
22260
22768
  }
22261
22769
  }, []);
22262
22770
  useEffect(() => {
22263
- if (canManage) load();
22771
+ if (canManage) void load();
22264
22772
  else setLoading(false);
22265
22773
  }, [
22266
22774
  canManage,
@@ -22318,10 +22826,18 @@ function VendorTeamPage() {
22318
22826
  }, "closeInviteDialog");
22319
22827
  if (!canManage) {
22320
22828
  return /* @__PURE__ */ React25__default.createElement("div", {
22321
- className: "p-6 max-w-lg"
22829
+ className: "min-w-0 rounded-lg bg-white shadow-md"
22830
+ }, /* @__PURE__ */ React25__default.createElement("div", {
22831
+ className: "rounded-t-lg border-b border-gray-700 bg-gray-800 px-4 py-2.5"
22832
+ }, /* @__PURE__ */ React25__default.createElement("h1", {
22833
+ className: "text-base font-semibold text-white"
22834
+ }, "Vendor Team"), /* @__PURE__ */ React25__default.createElement("p", {
22835
+ className: "mt-0.5 text-xs text-gray-300"
22836
+ }, "Invite members and assign a store role")), /* @__PURE__ */ React25__default.createElement("div", {
22837
+ className: "p-6"
22322
22838
  }, /* @__PURE__ */ React25__default.createElement("p", {
22323
22839
  className: "text-sm text-red-600"
22324
- }, "Only the store owner can manage the vendor team."));
22840
+ }, "Only the store owner can manage the vendor team.")));
22325
22841
  }
22326
22842
  const handleAdd = /* @__PURE__ */ __name(async (e) => {
22327
22843
  e.preventDefault();
@@ -22423,17 +22939,21 @@ function VendorTeamPage() {
22423
22939
  }
22424
22940
  }, "handleRemove");
22425
22941
  return /* @__PURE__ */ React25__default.createElement("div", {
22426
- className: "p-6 max-w-3xl"
22942
+ className: "min-w-0 rounded-lg bg-white shadow-md"
22943
+ }, /* @__PURE__ */ React25__default.createElement("div", {
22944
+ className: "rounded-t-lg border-b border-gray-700 bg-gray-800 px-4 py-2.5"
22427
22945
  }, /* @__PURE__ */ React25__default.createElement("h1", {
22428
- className: "text-xl font-semibold text-gray-900"
22946
+ className: "text-base font-semibold text-white"
22429
22947
  }, "Vendor Team"), /* @__PURE__ */ React25__default.createElement("p", {
22430
- className: "mt-1 text-sm text-gray-600"
22431
- }, "Invite member and assign a store role."), /* @__PURE__ */ React25__default.createElement("div", {
22432
- className: "mt-6 rounded-lg border border-gray-200 overflow-hidden"
22948
+ className: "mt-0.5 text-xs text-gray-300"
22949
+ }, "Invite member and assign a store role")), /* @__PURE__ */ React25__default.createElement("div", {
22950
+ className: "p-6 space-y-6"
22951
+ }, /* @__PURE__ */ React25__default.createElement("div", {
22952
+ className: "rounded-lg border border-gray-200 overflow-hidden"
22433
22953
  }, /* @__PURE__ */ React25__default.createElement("table", {
22434
22954
  className: "min-w-full text-sm"
22435
22955
  }, /* @__PURE__ */ React25__default.createElement("thead", {
22436
- className: "bg-gray-50 text-left text-gray-600"
22956
+ className: "bg-gray-50 text-left text-gray-600 border-b border-gray-200"
22437
22957
  }, /* @__PURE__ */ React25__default.createElement("tr", null, /* @__PURE__ */ React25__default.createElement("th", {
22438
22958
  className: "px-3 py-2 font-medium"
22439
22959
  }, "Name"), /* @__PURE__ */ React25__default.createElement("th", {
@@ -22443,8 +22963,10 @@ function VendorTeamPage() {
22443
22963
  }, "Phone"), /* @__PURE__ */ React25__default.createElement("th", {
22444
22964
  className: "px-3 py-2 font-medium"
22445
22965
  }, "Store role"), /* @__PURE__ */ React25__default.createElement("th", {
22446
- className: "px-3 py-2 font-medium"
22447
- }))), /* @__PURE__ */ React25__default.createElement("tbody", null, loading ? /* @__PURE__ */ React25__default.createElement("tr", null, /* @__PURE__ */ React25__default.createElement("td", {
22966
+ className: "px-3 py-2 font-medium text-right"
22967
+ }))), /* @__PURE__ */ React25__default.createElement("tbody", {
22968
+ className: "divide-y divide-gray-100"
22969
+ }, loading ? /* @__PURE__ */ React25__default.createElement("tr", null, /* @__PURE__ */ React25__default.createElement("td", {
22448
22970
  colSpan: 5,
22449
22971
  className: "px-3 py-4 text-gray-500"
22450
22972
  }, "Loading\u2026")) : members.length === 0 ? /* @__PURE__ */ React25__default.createElement("tr", null, /* @__PURE__ */ React25__default.createElement("td", {
@@ -22454,15 +22976,17 @@ function VendorTeamPage() {
22454
22976
  key: m.vendorUserId,
22455
22977
  className: "border-t border-gray-100"
22456
22978
  }, /* @__PURE__ */ React25__default.createElement("td", {
22457
- className: "px-3 py-2"
22979
+ className: "px-3 py-2 font-medium text-gray-900"
22458
22980
  }, m.name ?? "\u2014"), /* @__PURE__ */ React25__default.createElement("td", {
22459
- className: "px-3 py-2"
22981
+ className: "px-3 py-2 text-gray-600"
22460
22982
  }, m.email), /* @__PURE__ */ React25__default.createElement("td", {
22461
- className: "px-3 py-2"
22983
+ className: "px-3 py-2 text-gray-600"
22462
22984
  }, m.phone ?? "\u2014"), /* @__PURE__ */ React25__default.createElement("td", {
22463
22985
  className: "px-3 py-2"
22464
- }, m.isOwnerRole ? /* @__PURE__ */ React25__default.createElement("span", null, m.vendorRoleName ?? "Owner") : /* @__PURE__ */ React25__default.createElement("select", {
22465
- className: "rounded border border-gray-200 px-2 py-1 text-sm",
22986
+ }, m.isOwnerRole ? /* @__PURE__ */ React25__default.createElement("span", {
22987
+ className: "text-gray-900 font-medium"
22988
+ }, m.vendorRoleName ?? "Owner") : /* @__PURE__ */ React25__default.createElement("select", {
22989
+ className: "rounded border border-gray-200 px-2 py-1 text-sm bg-white",
22466
22990
  value: m.vendorRoleId ?? "",
22467
22991
  disabled: roleUpdating === m.userId,
22468
22992
  onChange: /* @__PURE__ */ __name((e) => handleRoleChange(m.userId, Number(e.target.value)), "onChange")
@@ -22478,7 +23002,7 @@ function VendorTeamPage() {
22478
23002
  onClick: /* @__PURE__ */ __name(() => handleRemove(m.userId), "onClick")
22479
23003
  }, "Remove"))))))), /* @__PURE__ */ React25__default.createElement("form", {
22480
23004
  onSubmit: handleAdd,
22481
- className: "mt-8 space-y-4 rounded-lg border border-gray-200 p-4"
23005
+ className: "space-y-4 rounded-lg border border-gray-200 p-4"
22482
23006
  }, /* @__PURE__ */ React25__default.createElement("h2", {
22483
23007
  className: "text-sm font-medium text-gray-900"
22484
23008
  }, "Invite team member"), /* @__PURE__ */ React25__default.createElement("div", {
@@ -22512,7 +23036,7 @@ function VendorTeamPage() {
22512
23036
  htmlFor: "teamRole"
22513
23037
  }, "Store role"), /* @__PURE__ */ React25__default.createElement("select", {
22514
23038
  id: "teamRole",
22515
- className: "mt-1 w-full rounded-md border border-gray-200 px-3 py-2 text-sm",
23039
+ className: "mt-1 w-full rounded-md border border-gray-200 px-3 py-2 text-sm bg-white",
22516
23040
  value: vendorRoleId,
22517
23041
  onChange: /* @__PURE__ */ __name((e) => setVendorRoleId(e.target.value ? Number(e.target.value) : ""), "onChange")
22518
23042
  }, assignableRoles.map((r) => /* @__PURE__ */ React25__default.createElement("option", {
@@ -22551,7 +23075,7 @@ function VendorTeamPage() {
22551
23075
  }, message), /* @__PURE__ */ React25__default.createElement(Button, {
22552
23076
  type: "submit",
22553
23077
  disabled: submitting
22554
- }, submitting ? "Inviting\u2026" : "Invite member")), /* @__PURE__ */ React25__default.createElement(Dialog, {
23078
+ }, submitting ? "Inviting\u2026" : "Invite member"))), /* @__PURE__ */ React25__default.createElement(Dialog, {
22555
23079
  open: inviteDialog != null,
22556
23080
  onOpenChange: /* @__PURE__ */ __name((open) => {
22557
23081
  if (!open) closeInviteDialog();
@@ -22571,7 +23095,7 @@ function VendorTeamPage() {
22571
23095
  variant: "outline",
22572
23096
  onClick: /* @__PURE__ */ __name(() => void copyInviteLink(), "onClick")
22573
23097
  }, linkCopied ? /* @__PURE__ */ React25__default.createElement(Check, {
22574
- className: "h-4 w-4 mr-2"
23098
+ className: "h-4 w-4 mr-2 text-green-600"
22575
23099
  }) : /* @__PURE__ */ React25__default.createElement(Copy, {
22576
23100
  className: "h-4 w-4 mr-2"
22577
23101
  }), linkCopied ? "Copied" : "Copy invite link")), /* @__PURE__ */ React25__default.createElement(DialogFooter, null, /* @__PURE__ */ React25__default.createElement(Button, {
@@ -30586,15 +31110,15 @@ function ProductEditPage({ productId }) {
30586
31110
  })));
30587
31111
  }
30588
31112
  }
30589
- if (!cancelled && formsRes.ok) {
30590
- const d = await formsRes.json();
30591
- if (Array.isArray(d.data)) {
30592
- setContactForms(d.data.filter((row) => row.id != null).map((row) => ({
30593
- id: Number(row.id),
30594
- name: row.name?.trim() || `Form #${row.id}`,
30595
- slug: row.slug
30596
- })));
30597
- }
31113
+ const formsResActual = formsRes.ok ? formsRes : await fetch("/api/admin/cms-forms/product-options").catch(() => null);
31114
+ if (!cancelled && formsResActual && formsResActual.ok) {
31115
+ const d = await formsResActual.json();
31116
+ const rows = Array.isArray(d.data) ? d.data : Array.isArray(d) ? d : [];
31117
+ setContactForms(rows.filter((row) => row.id != null || row.value != null).map((row) => ({
31118
+ id: Number(row.id ?? row.value),
31119
+ name: row.name?.trim() || row.label?.trim() || `Form #${row.id ?? row.value}`,
31120
+ slug: row.slug
31121
+ })));
30598
31122
  }
30599
31123
  if (!cancelled && refundRes.ok) {
30600
31124
  const d = await refundRes.json();
@@ -32846,6 +33370,252 @@ var init_EventProductsSection = __esm({
32846
33370
  __name(EventProductsSection, "EventProductsSection");
32847
33371
  }
32848
33372
  });
33373
+ function AttachComboModal({ open, onOpenChange, excludeComboIds = [], onAttach }) {
33374
+ const [attachingId, setAttachingId] = useState(null);
33375
+ const [error, setError] = useState(null);
33376
+ const comboColumns = useMemo(() => STORE_CRUD_CONFIGS.combos.columns.filter((c) => c.field !== "eventId"), []);
33377
+ const comboFilterColumns = useMemo(() => STORE_CRUD_CONFIGS.combos.columns.filter((c) => c.field !== "eventId"), []);
33378
+ const comboFilters = useMemo(() => STORE_CRUD_CONFIGS.combos.filters?.filter((f) => f.param !== "eventId"), []);
33379
+ const extraListParams = useMemo(() => {
33380
+ const params = {};
33381
+ if (excludeComboIds.length > 0) {
33382
+ params.excludeIds = excludeComboIds.join(",");
33383
+ }
33384
+ return params;
33385
+ }, [
33386
+ excludeComboIds
33387
+ ]);
33388
+ const handleAttach = /* @__PURE__ */ __name(async (comboId) => {
33389
+ setError(null);
33390
+ setAttachingId(comboId);
33391
+ try {
33392
+ await onAttach(comboId);
33393
+ onOpenChange(false);
33394
+ } catch (err) {
33395
+ setError(err instanceof Error ? err.message : "Failed to attach combo");
33396
+ } finally {
33397
+ setAttachingId(null);
33398
+ }
33399
+ }, "handleAttach");
33400
+ return /* @__PURE__ */ React.createElement(Dialog, {
33401
+ open,
33402
+ onOpenChange
33403
+ }, /* @__PURE__ */ React.createElement(DialogContent, {
33404
+ className: "max-w-4xl max-h-[90vh] overflow-y-auto"
33405
+ }, /* @__PURE__ */ React.createElement(DialogHeader, null, /* @__PURE__ */ React.createElement(DialogTitle, null, "Attach Event Combo"), /* @__PURE__ */ React.createElement(DialogDescription, null, "Choose a combo to link to this event.")), error ? /* @__PURE__ */ React.createElement("p", {
33406
+ className: "text-sm text-red-600 bg-red-50 rounded-md px-3 py-2"
33407
+ }, error) : null, /* @__PURE__ */ React.createElement(AdminCRUD, {
33408
+ key: `attach-combos-${excludeComboIds.join(",")}-${open}`,
33409
+ title: "Combos",
33410
+ apiEndpoint: "/api/combos",
33411
+ columns: comboColumns,
33412
+ filterColumns: comboFilterColumns,
33413
+ addEditPageUrl: "",
33414
+ defaultSortField: STORE_CRUD_CONFIGS.combos.defaultSortField,
33415
+ defaultSortOrder: STORE_CRUD_CONFIGS.combos.defaultSortOrder,
33416
+ filters: comboFilters,
33417
+ extraListParams,
33418
+ embedded: true,
33419
+ disableRowClick: true,
33420
+ emptyState: {
33421
+ title: "No combos available",
33422
+ description: excludeComboIds.length ? "All existing combos are already attached to this event, or none exist yet." : "Create a combo first, then attach it here."
33423
+ },
33424
+ customRowActions: /* @__PURE__ */ __name((item) => {
33425
+ const id = Number(item.id);
33426
+ const busy = attachingId === id;
33427
+ return /* @__PURE__ */ React.createElement(Button, {
33428
+ type: "button",
33429
+ size: "sm",
33430
+ variant: "outline",
33431
+ disabled: !Number.isFinite(id) || busy,
33432
+ className: "h-7 text-xs",
33433
+ onClick: /* @__PURE__ */ __name(() => void handleAttach(id), "onClick")
33434
+ }, /* @__PURE__ */ React.createElement(Link2$1, {
33435
+ className: "h-3.5 w-3.5 mr-1"
33436
+ }), busy ? "Attaching\u2026" : "Attach");
33437
+ }, "customRowActions")
33438
+ })));
33439
+ }
33440
+ var init_AttachComboModal = __esm({
33441
+ "src/components/Admin/AttachComboModal.tsx"() {
33442
+ "use client";
33443
+ init_dialog();
33444
+ init_button();
33445
+ init_CRUD();
33446
+ init_store_crud_configs();
33447
+ __name(AttachComboModal, "AttachComboModal");
33448
+ }
33449
+ });
33450
+ function EventCombosSection({ eventId, returnUrl, readOnly = false }) {
33451
+ const [rows, setRows] = useState([]);
33452
+ const [loading, setLoading] = useState(true);
33453
+ const [attachModalOpen, setAttachModalOpen] = useState(false);
33454
+ const [listRefreshKey, setListRefreshKey] = useState(0);
33455
+ const [detachingId, setDetachingId] = useState(null);
33456
+ const comboColumns = useMemo(() => STORE_CRUD_CONFIGS.combos.columns.filter((c) => c.field !== "eventId"), []);
33457
+ const comboFilterColumns = useMemo(() => STORE_CRUD_CONFIGS.combos.columns.filter((c) => c.field !== "eventId"), []);
33458
+ const comboFilters = useMemo(() => STORE_CRUD_CONFIGS.combos.filters?.filter((f) => f.param !== "eventId"), []);
33459
+ const loadRows = useCallback(async () => {
33460
+ setLoading(true);
33461
+ try {
33462
+ const res = await fetch(`/api/combos?eventId=${encodeURIComponent(eventId)}&limit=200`);
33463
+ if (!res.ok) throw new Error("Failed to load");
33464
+ const data = await res.json();
33465
+ const list = Array.isArray(data.data) ? data.data : [];
33466
+ setRows(list);
33467
+ } catch {
33468
+ setRows([]);
33469
+ } finally {
33470
+ setLoading(false);
33471
+ }
33472
+ }, [
33473
+ eventId
33474
+ ]);
33475
+ useEffect(() => {
33476
+ void loadRows();
33477
+ }, [
33478
+ loadRows
33479
+ ]);
33480
+ const attachedComboIds = useMemo(() => rows.map((r) => Number(r.id)).filter((id) => Number.isFinite(id) && id > 0), [
33481
+ rows
33482
+ ]);
33483
+ const attachToEvent = /* @__PURE__ */ __name(async (comboId) => {
33484
+ const res = await fetch(`/api/combos/${comboId}`, {
33485
+ method: "PUT",
33486
+ headers: {
33487
+ "Content-Type": "application/json"
33488
+ },
33489
+ body: JSON.stringify({
33490
+ eventId: Number(eventId)
33491
+ })
33492
+ });
33493
+ if (!res.ok) {
33494
+ const data = await res.json().catch(() => ({}));
33495
+ throw new Error(data.error ?? "Failed to attach combo");
33496
+ }
33497
+ await loadRows();
33498
+ setListRefreshKey((k) => k + 1);
33499
+ }, "attachToEvent");
33500
+ const detachFromEvent = /* @__PURE__ */ __name(async (comboId) => {
33501
+ if (!window.confirm("Remove this combo from the event?")) return;
33502
+ setDetachingId(comboId);
33503
+ try {
33504
+ const res = await fetch(`/api/combos/${comboId}`, {
33505
+ method: "PUT",
33506
+ headers: {
33507
+ "Content-Type": "application/json"
33508
+ },
33509
+ body: JSON.stringify({
33510
+ eventId: null
33511
+ })
33512
+ });
33513
+ if (res.ok) {
33514
+ await loadRows();
33515
+ setListRefreshKey((k) => k + 1);
33516
+ }
33517
+ } finally {
33518
+ setDetachingId(null);
33519
+ }
33520
+ }, "detachFromEvent");
33521
+ const comboCreateHref = !readOnly ? `/admin/combos/create?eventId=${encodeURIComponent(eventId)}${returnUrl ? `&from=${encodeURIComponent(returnUrl)}` : ""}` : null;
33522
+ if (loading) {
33523
+ return /* @__PURE__ */ React.createElement("section", {
33524
+ className: "border border-gray-200 rounded-lg p-8 text-center text-sm text-gray-500"
33525
+ }, "Loading combos\u2026");
33526
+ }
33527
+ if (readOnly && rows.length === 0) {
33528
+ return /* @__PURE__ */ React.createElement("section", {
33529
+ className: "border border-gray-200 rounded-lg p-6"
33530
+ }, /* @__PURE__ */ React.createElement("h2", {
33531
+ className: "text-sm font-semibold text-gray-900"
33532
+ }, "Event Combos"), /* @__PURE__ */ React.createElement("p", {
33533
+ className: "mt-2 text-sm text-gray-600"
33534
+ }, "No combos are linked to this event yet."));
33535
+ }
33536
+ return /* @__PURE__ */ React.createElement("div", {
33537
+ className: "space-y-4"
33538
+ }, /* @__PURE__ */ React.createElement("section", {
33539
+ className: "border border-gray-200 rounded-lg overflow-hidden bg-white"
33540
+ }, /* @__PURE__ */ React.createElement("div", {
33541
+ className: "flex flex-wrap items-center justify-between gap-3 px-4 py-3 border-b border-gray-200"
33542
+ }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h2", {
33543
+ className: "text-sm font-semibold text-gray-900"
33544
+ }, "Event Combos"), /* @__PURE__ */ React.createElement("p", {
33545
+ className: "text-xs text-gray-500"
33546
+ }, "Event Combos linked to this event.")), !readOnly ? /* @__PURE__ */ React.createElement("div", {
33547
+ className: "flex flex-wrap items-center gap-2"
33548
+ }, /* @__PURE__ */ React.createElement(Button, {
33549
+ type: "button",
33550
+ variant: "outline",
33551
+ size: "sm",
33552
+ className: "h-8 text-xs",
33553
+ onClick: /* @__PURE__ */ __name(() => setAttachModalOpen(true), "onClick")
33554
+ }, /* @__PURE__ */ React.createElement(Link2$1, {
33555
+ className: "h-3.5 w-3.5 mr-1"
33556
+ }), "Attach event combo"), comboCreateHref ? /* @__PURE__ */ React.createElement(Link2, {
33557
+ href: comboCreateHref,
33558
+ className: "inline-flex items-center gap-1.5 rounded-md border border-gray-300 bg-white px-2.5 py-1.5 text-xs font-medium text-gray-700 hover:bg-gray-50 h-8"
33559
+ }, /* @__PURE__ */ React.createElement(Plus, {
33560
+ className: "h-3.5 w-3.5"
33561
+ }), "New event combo") : null) : null), /* @__PURE__ */ React.createElement("div", {
33562
+ className: "[&>div]:shadow-none [&>div]:rounded-none"
33563
+ }, rows.length === 0 ? /* @__PURE__ */ React.createElement("div", {
33564
+ className: "px-4 py-8 text-center text-sm text-gray-500"
33565
+ }, "No combos attached to this event yet.") : /* @__PURE__ */ React.createElement(AdminCRUD, {
33566
+ key: `event-combos-${eventId}-${attachedComboIds.join(",")}-${listRefreshKey}`,
33567
+ title: "Event Combos",
33568
+ apiEndpoint: "/api/combos",
33569
+ columns: comboColumns,
33570
+ filterColumns: comboFilterColumns,
33571
+ addEditPageUrl: "/admin/combos",
33572
+ defaultSortField: STORE_CRUD_CONFIGS.combos.defaultSortField,
33573
+ defaultSortOrder: STORE_CRUD_CONFIGS.combos.defaultSortOrder,
33574
+ filters: comboFilters,
33575
+ extraListParams: {
33576
+ eventId: String(eventId)
33577
+ },
33578
+ embedded: true,
33579
+ disableRowClick: readOnly,
33580
+ emptyState: {
33581
+ title: "No combos attached",
33582
+ description: "Attach or create a combo for this event.",
33583
+ action: !readOnly && comboCreateHref ? /* @__PURE__ */ React.createElement(Link2, {
33584
+ href: comboCreateHref,
33585
+ className: "inline-flex items-center rounded-md bg-gray-900 px-3 py-1.5 text-sm font-medium text-white hover:bg-gray-800"
33586
+ }, "Create combo") : void 0
33587
+ },
33588
+ customRowActions: readOnly ? void 0 : (item) => {
33589
+ const comboId = Number(item.id);
33590
+ const busy = detachingId === comboId;
33591
+ return /* @__PURE__ */ React.createElement(Button, {
33592
+ type: "button",
33593
+ size: "sm",
33594
+ variant: "outline",
33595
+ disabled: !Number.isFinite(comboId) || busy,
33596
+ className: "h-7 text-xs border-red-200 text-red-600 hover:text-red-700",
33597
+ onClick: /* @__PURE__ */ __name(() => void detachFromEvent(comboId), "onClick")
33598
+ }, /* @__PURE__ */ React.createElement(Unlink, {
33599
+ className: "h-3.5 w-3.5 mr-1"
33600
+ }), busy ? "Removing\u2026" : "Detach");
33601
+ }
33602
+ }))), attachModalOpen ? /* @__PURE__ */ React.createElement(AttachComboModal, {
33603
+ open: attachModalOpen,
33604
+ onOpenChange: setAttachModalOpen,
33605
+ excludeComboIds: attachedComboIds,
33606
+ onAttach: attachToEvent
33607
+ }) : null);
33608
+ }
33609
+ var init_EventCombosSection = __esm({
33610
+ "src/admin/pages/EventCombosSection.tsx"() {
33611
+ "use client";
33612
+ init_button();
33613
+ init_CRUD();
33614
+ init_AttachComboModal();
33615
+ init_store_crud_configs();
33616
+ __name(EventCombosSection, "EventCombosSection");
33617
+ }
33618
+ });
32849
33619
 
32850
33620
  // src/lib/social-media-links.ts
32851
33621
  function isValidHttpUrl(value) {
@@ -34339,10 +35109,13 @@ function EventEditPage({ eventId }) {
34339
35109
  onClick: goToNextTab,
34340
35110
  className: "rounded-md bg-gray-900 px-4 py-2 text-sm font-medium text-white hover:bg-gray-800"
34341
35111
  }, "Next") : /* @__PURE__ */ React.createElement("div", null)), showEventProducts ? /* @__PURE__ */ React.createElement("div", {
34342
- className: "border-t border-gray-200 px-4 py-4 sm:px-6"
35112
+ className: "border-t border-gray-200 px-4 py-4 sm:px-6 space-y-6"
34343
35113
  }, /* @__PURE__ */ React.createElement(EventProductsSection, {
34344
35114
  eventId: eventRecordId,
34345
35115
  returnUrl: eventEditUrl
35116
+ }), /* @__PURE__ */ React.createElement(EventCombosSection, {
35117
+ eventId: eventRecordId,
35118
+ returnUrl: eventEditUrl
34346
35119
  })) : null);
34347
35120
  }
34348
35121
  var ADDITIONAL_VENUE_MAX_CHARS, DEFAULT_EVENT_TIMEZONE, DEFAULT_EVENT_CURRENCY, EVENT_SOCIAL_KEYS, isCreate4, sectionCls4, labelCls6, inputCls6, ENTITY_TYPE_OPTIONS;
@@ -34357,6 +35130,7 @@ var init_EventEditPage = __esm({
34357
35130
  init_media_url();
34358
35131
  init_JoditRichText();
34359
35132
  init_EventProductsSection();
35133
+ init_EventCombosSection();
34360
35134
  init_admin_config_context();
34361
35135
  init_dialog();
34362
35136
  init_button();
@@ -34394,7 +35168,13 @@ function formatDateTimeLocal(value) {
34394
35168
  if (value == null) return "";
34395
35169
  const date = new Date(String(value));
34396
35170
  if (isNaN(date.getTime())) return "";
34397
- return date.toISOString().slice(0, 16);
35171
+ const pad = /* @__PURE__ */ __name((n) => String(n).padStart(2, "0"), "pad");
35172
+ const y = date.getFullYear();
35173
+ const m = pad(date.getMonth() + 1);
35174
+ const d = pad(date.getDate());
35175
+ const hh = pad(date.getHours());
35176
+ const mm = pad(date.getMinutes());
35177
+ return `${y}-${m}-${d}T${hh}:${mm}`;
34398
35178
  }
34399
35179
  async function fetchEventDefaultCurrency(eventId) {
34400
35180
  try {
@@ -34441,13 +35221,14 @@ function ComboEditPage({ comboId }) {
34441
35221
  const create = isCreate5(comboId);
34442
35222
  const duplicateFrom = searchParams.get("duplicateFrom")?.trim();
34443
35223
  const duplicateSourceId = create && duplicateFrom && /^\d+$/.test(duplicateFrom) ? duplicateFrom : null;
35224
+ const initialEventId = create ? searchParams.get("eventId")?.trim() || "" : "";
34444
35225
  const [loading, setLoading] = useState(!create || !!duplicateSourceId);
34445
35226
  const [saving, setSaving] = useState(false);
34446
35227
  const [errors, setErrors] = useState([]);
34447
35228
  const [name, setName] = useState("");
34448
35229
  const [slug, setSlug] = useState("");
34449
35230
  const [desc, setDesc] = useState("");
34450
- const [eventId, setEventId] = useState("");
35231
+ const [eventId, setEventId] = useState(initialEventId);
34451
35232
  const [priceStr, setPriceStr] = useState("");
34452
35233
  const [defaultCurrency, setDefaultCurrency] = useState("INR");
34453
35234
  const [minSelectableItems, setMinSelectableItems] = useState(1);
@@ -34684,15 +35465,9 @@ function ComboEditPage({ comboId }) {
34684
35465
  ]);
34685
35466
  return;
34686
35467
  }
34687
- if (!startsAt) {
35468
+ if (startsAt && endsAt && new Date(endsAt).getTime() < new Date(startsAt).getTime()) {
34688
35469
  setErrors([
34689
- "Starts at is required"
34690
- ]);
34691
- return;
34692
- }
34693
- if (!endsAt) {
34694
- setErrors([
34695
- "Ends at is required"
35470
+ "Ends at cannot be earlier than Starts at"
34696
35471
  ]);
34697
35472
  return;
34698
35473
  }
@@ -34760,8 +35535,8 @@ function ComboEditPage({ comboId }) {
34760
35535
  minSelectableItems,
34761
35536
  maxSelectableItems,
34762
35537
  status,
34763
- startsAt: startsAt || null,
34764
- endsAt: endsAt || null,
35538
+ startsAt: startsAt ? new Date(startsAt).toISOString() : null,
35539
+ endsAt: endsAt ? new Date(endsAt).toISOString() : null,
34765
35540
  comboItems: [
34766
35541
  ...fixedItems.map((i) => ({
34767
35542
  productId: Number(i.productId),
@@ -34985,21 +35760,23 @@ function ComboEditPage({ comboId }) {
34985
35760
  className: "grid grid-cols-2 gap-4"
34986
35761
  }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("label", {
34987
35762
  className: "block text-xs font-medium text-gray-600 mb-1"
34988
- }, "Starts at *"), /* @__PURE__ */ React.createElement("input", {
35763
+ }, "Starts at (optional)"), /* @__PURE__ */ React.createElement("input", {
34989
35764
  type: "datetime-local",
34990
35765
  value: startsAt,
34991
35766
  onChange: /* @__PURE__ */ __name((e) => setStartsAt(e.target.value), "onChange"),
34992
- className: "w-full rounded-md border border-gray-300 px-3 py-2 text-sm",
34993
- required: true
34994
- })), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("label", {
35767
+ className: "w-full rounded-md border border-gray-300 px-3 py-2 text-sm"
35768
+ }), /* @__PURE__ */ React.createElement("p", {
35769
+ className: "text-xs text-gray-400 mt-1"
35770
+ }, "Leave empty to be available immediately.")), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("label", {
34995
35771
  className: "block text-xs font-medium text-gray-600 mb-1"
34996
- }, "Ends at *"), /* @__PURE__ */ React.createElement("input", {
35772
+ }, "Ends at (optional)"), /* @__PURE__ */ React.createElement("input", {
34997
35773
  type: "datetime-local",
34998
35774
  value: endsAt,
34999
35775
  onChange: /* @__PURE__ */ __name((e) => setEndsAt(e.target.value), "onChange"),
35000
- className: "w-full rounded-md border border-gray-300 px-3 py-2 text-sm",
35001
- required: true
35002
- })))))),
35776
+ className: "w-full rounded-md border border-gray-300 px-3 py-2 text-sm"
35777
+ }), /* @__PURE__ */ React.createElement("p", {
35778
+ className: "text-xs text-gray-400 mt-1"
35779
+ }, "Leave empty if no expiration date.")))))),
35003
35780
  sidebar: /* @__PURE__ */ React.createElement(React.Fragment, null)
35004
35781
  }));
35005
35782
  }
@@ -35115,6 +35892,130 @@ function VendorEditPage({ vendorId }) {
35115
35892
  const [inviteDialog, setInviteDialog] = useState(null);
35116
35893
  const [sendingInviteEmail, setSendingInviteEmail] = useState(false);
35117
35894
  const [linkCopied, setLinkCopied] = useState(false);
35895
+ const VENDOR_CREATE_DRAFT_KEY = "cms_vendor_create_draft";
35896
+ useEffect(() => {
35897
+ if (!create || typeof window === "undefined") return;
35898
+ try {
35899
+ const saved = sessionStorage.getItem(VENDOR_CREATE_DRAFT_KEY);
35900
+ if (saved) {
35901
+ const draft = JSON.parse(saved);
35902
+ if (draft && typeof draft === "object") {
35903
+ if (draft.name) setName(draft.name);
35904
+ if (draft.legalName) setLegalName(draft.legalName);
35905
+ if (draft.slug) setSlug(draft.slug);
35906
+ if (draft.businessType) setBusinessType(draft.businessType);
35907
+ if (draft.description) setDescription(draft.description);
35908
+ if (draft.website) setWebsite(draft.website);
35909
+ if (draft.logo) setLogo(draft.logo);
35910
+ if (draft.email) setEmail(draft.email);
35911
+ if (draft.storePhoneCode) setStorePhoneCode(draft.storePhoneCode);
35912
+ if (draft.storePhoneNum) setStorePhoneNum(draft.storePhoneNum);
35913
+ if (draft.addressLine1) setAddressLine1(draft.addressLine1);
35914
+ if (draft.addressLine2) setAddressLine2(draft.addressLine2);
35915
+ if (draft.city) setCity(draft.city);
35916
+ if (draft.state) setState(draft.state);
35917
+ if (draft.country) setCountry(draft.country);
35918
+ if (draft.postalCode) setPostalCode(draft.postalCode);
35919
+ if (draft.gstin) setGstin(draft.gstin);
35920
+ if (typeof draft.active === "boolean") setActive(draft.active);
35921
+ if (draft.registrationStatus) setRegistrationStatus(draft.registrationStatus);
35922
+ if (draft.ownerName) setOwnerName(draft.ownerName);
35923
+ if (draft.ownerEmail) setOwnerEmail(draft.ownerEmail);
35924
+ if (draft.ownerPhoneCode) setOwnerPhoneCode(draft.ownerPhoneCode);
35925
+ if (draft.ownerPhoneNum) setOwnerPhoneNum(draft.ownerPhoneNum);
35926
+ if (draft.ownerDesignation) setOwnerDesignation(draft.ownerDesignation);
35927
+ if (draft.ownerAadhaarNo) setOwnerAadhaarNo(draft.ownerAadhaarNo);
35928
+ if (draft.ownerPanNo) setOwnerPanNo(draft.ownerPanNo);
35929
+ if (draft.ownerAddressLine1) setOwnerAddressLine1(draft.ownerAddressLine1);
35930
+ if (draft.ownerAddressLine2) setOwnerAddressLine2(draft.ownerAddressLine2);
35931
+ if (draft.ownerCity) setOwnerCity(draft.ownerCity);
35932
+ if (draft.ownerState) setOwnerState(draft.ownerState);
35933
+ if (draft.ownerPostalCode) setOwnerPostalCode(draft.ownerPostalCode);
35934
+ if (typeof draft.termsAccepted === "boolean") setTermsAccepted(draft.termsAccepted);
35935
+ }
35936
+ }
35937
+ } catch {
35938
+ }
35939
+ }, [
35940
+ create
35941
+ ]);
35942
+ useEffect(() => {
35943
+ if (!create || typeof window === "undefined") return;
35944
+ const hasAnyData = Boolean(name || legalName || gstin || ownerName || ownerEmail || ownerPhoneNum || ownerAadhaarNo || ownerPanNo || ownerAddressLine1);
35945
+ if (!hasAnyData) return;
35946
+ try {
35947
+ const draft = {
35948
+ name,
35949
+ legalName,
35950
+ slug,
35951
+ businessType,
35952
+ description,
35953
+ website,
35954
+ logo,
35955
+ email,
35956
+ storePhoneCode,
35957
+ storePhoneNum,
35958
+ addressLine1,
35959
+ addressLine2,
35960
+ city,
35961
+ state,
35962
+ country,
35963
+ postalCode,
35964
+ gstin,
35965
+ active,
35966
+ registrationStatus,
35967
+ ownerName,
35968
+ ownerEmail,
35969
+ ownerPhoneCode,
35970
+ ownerPhoneNum,
35971
+ ownerDesignation,
35972
+ ownerAadhaarNo,
35973
+ ownerPanNo,
35974
+ ownerAddressLine1,
35975
+ ownerAddressLine2,
35976
+ ownerCity,
35977
+ ownerState,
35978
+ ownerPostalCode,
35979
+ termsAccepted
35980
+ };
35981
+ sessionStorage.setItem(VENDOR_CREATE_DRAFT_KEY, JSON.stringify(draft));
35982
+ } catch {
35983
+ }
35984
+ }, [
35985
+ create,
35986
+ name,
35987
+ legalName,
35988
+ slug,
35989
+ businessType,
35990
+ description,
35991
+ website,
35992
+ logo,
35993
+ email,
35994
+ storePhoneCode,
35995
+ storePhoneNum,
35996
+ addressLine1,
35997
+ addressLine2,
35998
+ city,
35999
+ state,
36000
+ country,
36001
+ postalCode,
36002
+ gstin,
36003
+ active,
36004
+ registrationStatus,
36005
+ ownerName,
36006
+ ownerEmail,
36007
+ ownerPhoneCode,
36008
+ ownerPhoneNum,
36009
+ ownerDesignation,
36010
+ ownerAadhaarNo,
36011
+ ownerPanNo,
36012
+ ownerAddressLine1,
36013
+ ownerAddressLine2,
36014
+ ownerCity,
36015
+ ownerState,
36016
+ ownerPostalCode,
36017
+ termsAccepted
36018
+ ]);
35118
36019
  const vendorPayload = /* @__PURE__ */ __name(() => ({
35119
36020
  name: name.trim(),
35120
36021
  legalName: legalName.trim() || null,
@@ -35358,12 +36259,15 @@ function VendorEditPage({ vendorId }) {
35358
36259
  setSaving(false);
35359
36260
  return;
35360
36261
  }
36262
+ const abortController = new AbortController();
36263
+ const timeoutId = setTimeout(() => abortController.abort(), 15e3);
35361
36264
  try {
35362
36265
  const res = await fetch("/api/admin/vendors/onboard", {
35363
36266
  method: "POST",
35364
36267
  headers: {
35365
36268
  "Content-Type": "application/json"
35366
36269
  },
36270
+ signal: abortController.signal,
35367
36271
  body: JSON.stringify({
35368
36272
  activation: "invite",
35369
36273
  sendOwnerEmail: false,
@@ -35384,15 +36288,20 @@ function VendorEditPage({ vendorId }) {
35384
36288
  }
35385
36289
  })
35386
36290
  });
35387
- const data = await res.json();
36291
+ clearTimeout(timeoutId);
36292
+ const data = await res.json().catch(() => ({}));
35388
36293
  if (!res.ok) {
35389
36294
  setErrors([
35390
36295
  data.error || "Onboarding failed"
35391
36296
  ]);
35392
36297
  return;
35393
36298
  }
36299
+ try {
36300
+ sessionStorage.removeItem(VENDOR_CREATE_DRAFT_KEY);
36301
+ } catch {
36302
+ }
35394
36303
  const vendorIdNum = Number(data.vendor?.id);
35395
- const inviteLink = typeof data.inviteLink === "string" && data.inviteLink.trim() ? data.inviteLink.trim() : "";
36304
+ const inviteLink = typeof data.inviteLink === "string" && data.inviteLink?.trim() ? data.inviteLink.trim() : "";
35396
36305
  if (!Number.isFinite(vendorIdNum) || vendorIdNum <= 0 || !inviteLink) {
35397
36306
  toast.success(data.message || "Vendor created");
35398
36307
  router.push(listReturnUrl);
@@ -35404,11 +36313,18 @@ function VendorEditPage({ vendorId }) {
35404
36313
  vendorId: vendorIdNum,
35405
36314
  inviteLink
35406
36315
  });
35407
- } catch {
35408
- setErrors([
35409
- "Request failed"
35410
- ]);
36316
+ } catch (err) {
36317
+ if (err instanceof DOMException && err.name === "AbortError") {
36318
+ setErrors([
36319
+ "Request timed out. Your entered data is preserved. Please click submit again."
36320
+ ]);
36321
+ } else {
36322
+ setErrors([
36323
+ err instanceof Error && err.message || "Request failed"
36324
+ ]);
36325
+ }
35411
36326
  } finally {
36327
+ clearTimeout(timeoutId);
35412
36328
  setSaving(false);
35413
36329
  }
35414
36330
  }, "handleCreate");
@@ -35447,12 +36363,15 @@ function VendorEditPage({ vendorId }) {
35447
36363
  setSaving(false);
35448
36364
  return;
35449
36365
  }
36366
+ const abortController = new AbortController();
36367
+ const timeoutId = setTimeout(() => abortController.abort(), 15e3);
35450
36368
  try {
35451
36369
  const res = await fetch(`/api/vendors/${vendorId}`, {
35452
36370
  method: "PUT",
35453
36371
  headers: {
35454
36372
  "Content-Type": "application/json"
35455
36373
  },
36374
+ signal: abortController.signal,
35456
36375
  body: JSON.stringify({
35457
36376
  ...vendorPayload(),
35458
36377
  slug: slug.trim(),
@@ -35468,6 +36387,7 @@ function VendorEditPage({ vendorId }) {
35468
36387
  })
35469
36388
  });
35470
36389
  if (!res.ok) {
36390
+ clearTimeout(timeoutId);
35471
36391
  const data = await res.json().catch(() => ({}));
35472
36392
  setErrors([
35473
36393
  data.error || "Failed to save"
@@ -35481,6 +36401,7 @@ function VendorEditPage({ vendorId }) {
35481
36401
  headers: {
35482
36402
  "Content-Type": "application/json"
35483
36403
  },
36404
+ signal: abortController.signal,
35484
36405
  body: JSON.stringify({
35485
36406
  name: ownerName.trim(),
35486
36407
  email: ownerEmail.trim(),
@@ -35488,6 +36409,7 @@ function VendorEditPage({ vendorId }) {
35488
36409
  })
35489
36410
  });
35490
36411
  if (!userRes.ok) {
36412
+ clearTimeout(timeoutId);
35491
36413
  const data = await userRes.json().catch(() => ({}));
35492
36414
  setErrors([
35493
36415
  data.error || "Vendor saved, but updating owner details failed"
@@ -35495,13 +36417,21 @@ function VendorEditPage({ vendorId }) {
35495
36417
  return;
35496
36418
  }
35497
36419
  }
36420
+ clearTimeout(timeoutId);
35498
36421
  toast.success("Vendor saved");
35499
36422
  router.push(listReturnUrl);
35500
- } catch {
35501
- setErrors([
35502
- "Failed to save"
35503
- ]);
36423
+ } catch (err) {
36424
+ if (err instanceof DOMException && err.name === "AbortError") {
36425
+ setErrors([
36426
+ "Request timed out. Please try saving again."
36427
+ ]);
36428
+ } else {
36429
+ setErrors([
36430
+ err instanceof Error && err.message || "Failed to save"
36431
+ ]);
36432
+ }
35504
36433
  } finally {
36434
+ clearTimeout(timeoutId);
35505
36435
  setSaving(false);
35506
36436
  }
35507
36437
  }, "handleSave");
@@ -40907,6 +41837,8 @@ var init_AdminPageResolver = __esm({
40907
41837
  submissions: {
40908
41838
  title: "Submissions",
40909
41839
  apiEndpoint: "/api/form-submissions",
41840
+ defaultSortField: "createdAt",
41841
+ defaultSortOrder: "desc",
40910
41842
  columns: [
40911
41843
  {
40912
41844
  field: "form.name",
@@ -41452,6 +42384,10 @@ function EventDetailPage({ eventId }) {
41452
42384
  eventId: String(event.id),
41453
42385
  returnUrl: detailUrl,
41454
42386
  readOnly: true
42387
+ }), /* @__PURE__ */ React.createElement(EventCombosSection, {
42388
+ eventId: String(event.id),
42389
+ returnUrl: detailUrl,
42390
+ readOnly: true
41455
42391
  }))
41456
42392
  }));
41457
42393
  }
@@ -41467,6 +42403,7 @@ var init_EventDetailPage = __esm({
41467
42403
  init_DetailPageLayout();
41468
42404
  init_DetailPageHeader();
41469
42405
  init_EventProductsSection();
42406
+ init_EventCombosSection();
41470
42407
  sectionCls6 = "border border-gray-200 rounded-lg p-4 bg-gray-50/50";
41471
42408
  labelCls7 = "text-xs font-medium text-gray-500";
41472
42409
  valueCls = "text-sm text-gray-900 mt-0.5";