@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.cjs CHANGED
@@ -361,7 +361,15 @@ var init_vendor_scope = __esm({
361
361
  ...VENDOR_SCOPED_STORE_ENTITIES,
362
362
  "order_discounts",
363
363
  "order_addresses",
364
- "event_products"
364
+ "event_products",
365
+ "attendees",
366
+ "order-attendees",
367
+ "order_attendees",
368
+ "scan",
369
+ "verify-attendee-qr",
370
+ "verify_attendee_qr",
371
+ "forms",
372
+ "cms-forms"
365
373
  ]);
366
374
  __name(isVendorGroupName, "isVendorGroupName");
367
375
  __name(isPlatformAdministrator, "isPlatformAdministrator");
@@ -887,7 +895,7 @@ var init_admin_config_context = __esm({
887
895
  var CMS_VERSION;
888
896
  var init_cms_version = __esm({
889
897
  "src/lib/cms-version.ts"() {
890
- CMS_VERSION = "1.0.59" ;
898
+ CMS_VERSION = "1.0.61" ;
891
899
  }
892
900
  });
893
901
  function useCatalogCategories(enabled = true) {
@@ -1609,6 +1617,15 @@ var init_builtin_plugin_descriptors = __esm({
1609
1617
  enabled: true,
1610
1618
  icon: "auth_providers",
1611
1619
  settingsGroup: "auth_providers"
1620
+ },
1621
+ {
1622
+ name: "turnstile",
1623
+ label: "Cloudflare Turnstile",
1624
+ version: "1.0.0",
1625
+ description: "Protect forms, registration, and authentication from spam and bots with Cloudflare Turnstile.",
1626
+ enabled: true,
1627
+ icon: "turnstile",
1628
+ settingsGroup: "turnstile"
1612
1629
  }
1613
1630
  ];
1614
1631
  }
@@ -2209,6 +2226,11 @@ function FileUpload({ onUploadSuccess }) {
2209
2226
  if (!selectedFile) return;
2210
2227
  setUploading(true);
2211
2228
  setProgress(10);
2229
+ console.info("[upload] file-upload start", {
2230
+ name: selectedFile.name,
2231
+ type: selectedFile.type,
2232
+ size: selectedFile.size
2233
+ });
2212
2234
  const formData = new FormData();
2213
2235
  formData.append("file", selectedFile);
2214
2236
  try {
@@ -2217,20 +2239,47 @@ function FileUpload({ onUploadSuccess }) {
2217
2239
  body: formData
2218
2240
  });
2219
2241
  setProgress(60);
2220
- const data = await response.json();
2242
+ const data = await response.json().catch((parseErr) => {
2243
+ console.error("[upload] file-upload JSON parse failed", {
2244
+ status: response.status,
2245
+ parseErr
2246
+ });
2247
+ return {};
2248
+ });
2249
+ console.info("[upload] file-upload response", {
2250
+ status: response.status,
2251
+ ok: response.ok,
2252
+ url: data.url ?? data.filePath ?? null,
2253
+ error: data.error ?? null,
2254
+ details: data.details ?? null
2255
+ });
2221
2256
  if (response.ok) {
2222
- setFileUrl(data.url);
2257
+ const url = data.url ?? data.filePath;
2258
+ setFileUrl(url);
2223
2259
  setProgress(100);
2224
- onUploadSuccess(data.url);
2260
+ console.info("[upload] file-upload ok", {
2261
+ url,
2262
+ name: selectedFile.name
2263
+ });
2264
+ onUploadSuccess(url);
2225
2265
  } else {
2226
2266
  console.error("[upload] API error", {
2227
2267
  status: response.status,
2228
2268
  error: data.error,
2229
- details: data.details
2269
+ details: data.details,
2270
+ name: selectedFile.name,
2271
+ type: selectedFile.type,
2272
+ size: selectedFile.size
2230
2273
  });
2231
2274
  }
2232
2275
  } catch (error) {
2233
- console.error("Upload error:", error);
2276
+ console.error("[upload] file-upload failed", {
2277
+ name: selectedFile.name,
2278
+ type: selectedFile.type,
2279
+ size: selectedFile.size,
2280
+ error: error instanceof Error ? error.message : String(error),
2281
+ stack: error instanceof Error ? error.stack : void 0
2282
+ });
2234
2283
  }
2235
2284
  setUploading(false);
2236
2285
  }, "handleUpload");
@@ -2362,28 +2411,64 @@ function ImageOrUrlField({ label, value, onChange, placeholder = "https://\u2026
2362
2411
  return;
2363
2412
  }
2364
2413
  setIsUploading(true);
2414
+ console.info("[upload] ImageOrUrlField start", {
2415
+ name: file.name,
2416
+ type: file.type,
2417
+ size: file.size,
2418
+ isVideo: isVideo2,
2419
+ maxSizeMb
2420
+ });
2365
2421
  try {
2366
2422
  const formData = new FormData();
2367
2423
  formData.append("file", file);
2424
+ console.info("[upload] ImageOrUrlField POST /api/upload", {
2425
+ name: file.name,
2426
+ type: file.type,
2427
+ size: file.size
2428
+ });
2368
2429
  const response = await fetch("/api/upload", {
2369
2430
  method: "POST",
2370
2431
  body: formData
2371
2432
  });
2372
- const data = await response.json().catch(() => ({}));
2433
+ const data = await response.json().catch((parseErr) => {
2434
+ console.error("[upload] ImageOrUrlField JSON parse failed", {
2435
+ status: response.status,
2436
+ parseErr
2437
+ });
2438
+ return {};
2439
+ });
2440
+ console.info("[upload] ImageOrUrlField response", {
2441
+ status: response.status,
2442
+ ok: response.ok,
2443
+ filePath: data.filePath ?? null,
2444
+ error: data.error ?? null,
2445
+ details: data.details ?? null
2446
+ });
2373
2447
  if (!response.ok) {
2374
2448
  console.error("[upload] API error", {
2375
2449
  status: response.status,
2376
2450
  error: data.error,
2377
- details: data.details
2451
+ details: data.details,
2452
+ fileName: file.name,
2453
+ fileType: file.type,
2454
+ fileSize: file.size
2378
2455
  });
2379
2456
  throw new Error(data.details || data.error || "Upload failed");
2380
2457
  }
2381
2458
  console.info("[upload] API ok", {
2382
- filePath: data.filePath
2459
+ filePath: data.filePath,
2460
+ name: file.name,
2461
+ type: file.type
2383
2462
  });
2384
2463
  onChange(data.filePath ?? "");
2385
2464
  } catch (err) {
2386
- console.error("[upload] client failed", err);
2465
+ console.error("[upload] client failed", {
2466
+ name: file.name,
2467
+ type: file.type,
2468
+ size: file.size,
2469
+ error: err instanceof Error ? err.message : String(err),
2470
+ stack: err instanceof Error ? err.stack : void 0
2471
+ });
2387
2472
  setError(err instanceof Error ? err.message : "Upload failed");
2388
2473
  } finally {
2389
2474
  setIsUploading(false);
@@ -5082,6 +5167,9 @@ function AdminCRUD({ title, apiEndpoint, columns, addEditPageUrl, customViewPage
5082
5167
  });
5083
5168
  if (cancelled) return;
5084
5169
  if (!res.ok) {
5170
+ if (res.status === 403) {
5171
+ throw new Error("You don't have access to this section");
5172
+ }
5085
5173
  throw new Error(`HTTP error! status: ${res.status}`);
5086
5174
  }
5087
5175
  const result = await res.json();
@@ -5498,15 +5586,26 @@ function AdminCRUD({ title, apiEndpoint, columns, addEditPageUrl, customViewPage
5498
5586
  }, "Loading...")));
5499
5587
  }
5500
5588
  if (error) {
5589
+ const isForbidden = error.toLowerCase().includes("don't have access") || error.toLowerCase().includes("forbidden") || error.includes("403");
5501
5590
  return /* @__PURE__ */ React.createElement("div", {
5502
5591
  className: embedded ? "p-4" : "rounded-lg bg-white p-6 shadow-md"
5503
5592
  }, /* @__PURE__ */ React.createElement("div", {
5504
- className: "text-center py-8"
5505
- }, /* @__PURE__ */ React.createElement("p", {
5593
+ className: "text-center py-10"
5594
+ }, isForbidden ? /* @__PURE__ */ React.createElement("div", {
5595
+ className: "max-w-md mx-auto"
5596
+ }, /* @__PURE__ */ React.createElement("div", {
5597
+ className: "mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-red-100 mb-3"
5598
+ }, /* @__PURE__ */ React.createElement(LucideIcons.ShieldAlert, {
5599
+ className: "h-6 w-6 text-red-600"
5600
+ })), /* @__PURE__ */ React.createElement("h3", {
5601
+ className: "text-base font-semibold text-gray-900 mb-1"
5602
+ }, "You don't have access"), /* @__PURE__ */ React.createElement("p", {
5603
+ className: "text-sm text-gray-500"
5604
+ }, "You don't have permission to view or manage this tab.")) : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("p", {
5506
5605
  className: "text-red-600 mb-4"
5507
5606
  }, "Error loading data: ", error), /* @__PURE__ */ React.createElement(Button, {
5508
5607
  onClick: /* @__PURE__ */ __name(() => window.location.reload(), "onClick")
5509
- }, "Retry")));
5608
+ }, "Retry"))));
5510
5609
  }
5511
5610
  return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", {
5512
5611
  className: shellCls
@@ -6343,6 +6442,7 @@ function FormBuilder({ formId, duplicateFromId }) {
6343
6442
  campaign: "",
6344
6443
  slug: "",
6345
6444
  published: false,
6445
+ enableTurnstile: false,
6346
6446
  fields: []
6347
6447
  });
6348
6448
  const [errors, setErrors] = React25.useState([]);
@@ -6376,6 +6476,7 @@ function FormBuilder({ formId, duplicateFromId }) {
6376
6476
  campaign: data.campaign || "",
6377
6477
  slug: slugNext,
6378
6478
  published: isDup ? false : data.published,
6479
+ enableTurnstile: Boolean(data.enableTurnstile ?? data.enable_turnstile),
6379
6480
  fields: (data.fields ?? []).map((field, index) => ({
6380
6481
  ...field,
6381
6482
  id: isDup ? `field_dup_${Date.now()}_${index}` : field?.id != null ? String(field.id) : `field_${Date.now()}_${index}`,
@@ -6684,7 +6785,21 @@ function FormBuilder({ formId, duplicateFromId }) {
6684
6785
  })), "onCheckedChange")
6685
6786
  }), /* @__PURE__ */ React.createElement("label", {
6686
6787
  className: "text-sm font-medium text-gray-700"
6687
- }, "Published"))))), /* @__PURE__ */ React.createElement("div", {
6788
+ }, "Published")), /* @__PURE__ */ React.createElement("div", {
6789
+ className: "flex items-center space-x-2 pt-2 border-t border-gray-100"
6790
+ }, /* @__PURE__ */ React.createElement(Switch, {
6791
+ checked: formData.enableTurnstile ?? false,
6792
+ onCheckedChange: /* @__PURE__ */ __name((checked) => setFormData((prev) => ({
6793
+ ...prev,
6794
+ enableTurnstile: checked
6795
+ })), "onCheckedChange")
6796
+ }), /* @__PURE__ */ React.createElement("div", {
6797
+ className: "flex flex-col"
6798
+ }, /* @__PURE__ */ React.createElement("label", {
6799
+ className: "text-sm font-medium text-gray-700"
6800
+ }, "Enable Cloudflare Turnstile"), /* @__PURE__ */ React.createElement("span", {
6801
+ className: "text-xs text-gray-500"
6802
+ }, "Requires user verification before submission (when Turnstile plugin is active)")))))), /* @__PURE__ */ React.createElement("div", {
6688
6803
  className: "lg:col-span-2"
6689
6804
  }, /* @__PURE__ */ React.createElement("div", {
6690
6805
  className: "bg-white rounded-lg shadow"
@@ -6854,26 +6969,63 @@ function ImageUpload({ value, onChange, onRemove, label, required = false, class
6854
6969
  return;
6855
6970
  }
6856
6971
  setIsUploading(true);
6972
+ console.info("[upload] client image-upload start", {
6973
+ name: file.name,
6974
+ type: file.type,
6975
+ size: file.size,
6976
+ maxSizeMb: maxSize
6977
+ });
6857
6978
  try {
6858
6979
  const formData = new FormData();
6859
6980
  formData.append("file", file);
6981
+ console.info("[upload] client POST /api/upload", {
6982
+ name: file.name,
6983
+ type: file.type,
6984
+ size: file.size
6985
+ });
6860
6986
  const response = await fetch("/api/upload", {
6861
6987
  method: "POST",
6862
6988
  body: formData
6863
6989
  });
6864
- const data = await response.json();
6990
+ const data = await response.json().catch((parseErr) => {
6991
+ console.error("[upload] client failed to parse JSON response", {
6992
+ status: response.status,
6993
+ parseErr
6994
+ });
6995
+ return {};
6996
+ });
6997
+ console.info("[upload] client response", {
6998
+ status: response.status,
6999
+ ok: response.ok,
7000
+ filePath: data.filePath ?? null,
7001
+ error: data.error ?? null,
7002
+ details: data.details ?? null
7003
+ });
6865
7004
  if (!response.ok) {
6866
7005
  console.error("[upload] API error", {
6867
7006
  status: response.status,
6868
7007
  error: data.error,
6869
- details: data.details
7008
+ details: data.details,
7009
+ fileName: file.name,
7010
+ fileType: file.type,
7011
+ fileSize: file.size
6870
7012
  });
6871
7013
  throw new Error(data.details || data.error || "Upload failed");
6872
7014
  }
7015
+ console.info("[upload] client image-upload ok", {
7016
+ filePath: data.filePath,
7017
+ name: file.name
7018
+ });
6873
7019
  onChange(data.filePath);
6874
7020
  setUploadProgress(100);
6875
7021
  } catch (error2) {
6876
- console.error("Upload error:", error2);
7022
+ console.error("[upload] client image-upload failed", {
7023
+ name: file.name,
7024
+ type: file.type,
7025
+ size: file.size,
7026
+ error: error2 instanceof Error ? error2.message : String(error2),
7027
+ stack: error2 instanceof Error ? error2.stack : void 0
7028
+ });
6877
7029
  setError(error2 instanceof Error ? error2.message : "Failed to upload image");
6878
7030
  } finally {
6879
7031
  setIsUploading(false);
@@ -16177,6 +16329,7 @@ var init_order_notification_bindings_shared = __esm({
16177
16329
  "orderNumber",
16178
16330
  "customerName",
16179
16331
  "customerPhone",
16332
+ "customerEmail",
16180
16333
  "eventName",
16181
16334
  "vendorName",
16182
16335
  "total",
@@ -16184,7 +16337,8 @@ var init_order_notification_bindings_shared = __esm({
16184
16337
  "vendorId",
16185
16338
  "refundAmount",
16186
16339
  "invoiceNumber",
16187
- "invoiceUrl"
16340
+ "invoiceAttachment",
16341
+ "orderDetails"
16188
16342
  ];
16189
16343
  __name(useNotificationVariables, "useNotificationVariables");
16190
16344
  EMAIL_TRIGGER_DEFAULTS = {
@@ -16216,6 +16370,43 @@ If you have any questions, please contact us.
16216
16370
 
16217
16371
  Thank you!`,
16218
16372
  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."
16373
+ },
16374
+ payment_success: {
16375
+ subject: "Payment Successful for Order {{orderNumber}}",
16376
+ subjectPlaceholder: "e.g. Payment Successful for Order {{orderNumber}}",
16377
+ body: `Hi {{customerName}},
16378
+
16379
+ We have received your payment for order {{orderNumber}} of {{total}} {{currency}}.
16380
+
16381
+ Order Details:
16382
+ {{orderDetails}}
16383
+
16384
+ Thank you!`,
16385
+ bodyPlaceholder: "Hi {{customerName}},\n\nPayment for order {{orderNumber}} was successful.\n\nThank you!"
16386
+ },
16387
+ payment_failed: {
16388
+ subject: "Payment Failed for Order {{orderNumber}}",
16389
+ subjectPlaceholder: "e.g. Payment Failed for Order {{orderNumber}}",
16390
+ body: `Hi {{customerName}},
16391
+
16392
+ Your payment for order {{orderNumber}} of {{total}} {{currency}} could not be completed or remains unpaid.
16393
+
16394
+ If you were charged, please contact us with your order number {{orderNumber}}.
16395
+
16396
+ Thank you!`,
16397
+ bodyPlaceholder: "Hi {{customerName}},\n\nPayment for order {{orderNumber}} failed or remains unpaid.\n\nThank you!"
16398
+ },
16399
+ payment_refund_initiated: {
16400
+ subject: "Refund Initiated for Order {{orderNumber}}",
16401
+ subjectPlaceholder: "e.g. Refund Initiated for Order {{orderNumber}}",
16402
+ body: `Hi {{customerName}},
16403
+
16404
+ Your order {{orderNumber}} has been cancelled and a refund of {{refundAmount}} {{currency}} has been initiated.
16405
+
16406
+ The amount will be credited back to your original payment method.
16407
+
16408
+ Thank you!`,
16409
+ bodyPlaceholder: "Hi {{customerName}},\n\nRefund of {{refundAmount}} {{currency}} for order {{orderNumber}} has been initiated."
16219
16410
  }
16220
16411
  };
16221
16412
  VENDOR_EMAIL_TRIGGER_DEFAULTS = {
@@ -16240,12 +16431,48 @@ Thank you!`,
16240
16431
 
16241
16432
  Order {{orderNumber}} for {{total}} {{currency}} has been cancelled.
16242
16433
 
16243
- Customer: {{customerName}
16434
+ Customer: {{customerName}}
16244
16435
  Order Items:
16245
16436
  {{orderDetails}}
16246
16437
 
16247
16438
  Thank you.`,
16248
16439
  bodyPlaceholder: "Hi {{vendorName}},\n\nOrder {{orderNumber}} was cancelled."
16440
+ },
16441
+ payment_success: {
16442
+ subject: "Payment Confirmed for Order {{orderNumber}} - {{customerName}}",
16443
+ subjectPlaceholder: "e.g. Payment Confirmed for Order {{orderNumber}}",
16444
+ body: `Hi {{vendorName}},
16445
+
16446
+ Payment has been confirmed for order {{orderNumber}} ({{total}} {{currency}}).
16447
+
16448
+ Customer: {{customerName}}
16449
+ Order Items:
16450
+ {{orderDetails}}
16451
+
16452
+ Thank you!`,
16453
+ bodyPlaceholder: "Hi {{vendorName}},\n\nPayment for order {{orderNumber}} confirmed."
16454
+ },
16455
+ payment_failed: {
16456
+ subject: "Payment Unpaid / Failed for Order {{orderNumber}}",
16457
+ subjectPlaceholder: "e.g. Payment Unpaid for Order {{orderNumber}}",
16458
+ body: `Hi {{vendorName}},
16459
+
16460
+ Payment for order {{orderNumber}} ({{total}} {{currency}}) is unpaid or failed.
16461
+
16462
+ Customer: {{customerName}}
16463
+
16464
+ Thank you.`,
16465
+ bodyPlaceholder: "Hi {{vendorName}},\n\nPayment for order {{orderNumber}} failed or unpaid."
16466
+ },
16467
+ payment_refund_initiated: {
16468
+ subject: "Refund Initiated for Order {{orderNumber}}",
16469
+ subjectPlaceholder: "e.g. Refund Initiated for Order {{orderNumber}}",
16470
+ body: `Hi {{vendorName}},
16471
+
16472
+ Order {{orderNumber}} has been cancelled and a refund of {{refundAmount}} {{currency}} has been initiated for {{customerName}}.
16473
+
16474
+ Thank you.`,
16475
+ bodyPlaceholder: "Hi {{vendorName}},\n\nRefund initiated for order {{orderNumber}}."
16249
16476
  }
16250
16477
  };
16251
16478
  ADMIN_EMAIL_TRIGGER_DEFAULTS = {
@@ -16277,6 +16504,42 @@ Customer: {{customerName}}
16277
16504
  Vendor: {{vendorName}}
16278
16505
  Refund Due: {{refundAmount}} {{currency}}`,
16279
16506
  bodyPlaceholder: "Hi Admin,\n\nOrder {{orderNumber}} cancelled."
16507
+ },
16508
+ payment_success: {
16509
+ subject: "Payment Success Alert: Order {{orderNumber}} ({{total}} {{currency}})",
16510
+ subjectPlaceholder: "e.g. Payment Success Alert: Order {{orderNumber}}",
16511
+ body: `Hi Admin,
16512
+
16513
+ Payment of {{total}} {{currency}} was successfully captured for order {{orderNumber}}.
16514
+
16515
+ Customer: {{customerName}}
16516
+ Vendor: {{vendorName}}
16517
+
16518
+ Order Summary:
16519
+ {{orderDetails}}`,
16520
+ bodyPlaceholder: "Payment received for order {{orderNumber}}."
16521
+ },
16522
+ payment_failed: {
16523
+ subject: "Payment Failed / Unpaid Alert: Order {{orderNumber}}",
16524
+ subjectPlaceholder: "e.g. Payment Failed Alert: Order {{orderNumber}}",
16525
+ body: `Hi Admin,
16526
+
16527
+ Payment for order {{orderNumber}} ({{total}} {{currency}}) is unpaid or failed.
16528
+
16529
+ Customer: {{customerName}}
16530
+ Vendor: {{vendorName}}`,
16531
+ bodyPlaceholder: "Payment failed/unpaid for order {{orderNumber}}."
16532
+ },
16533
+ payment_refund_initiated: {
16534
+ subject: "Refund Initiated Alert: Order {{orderNumber}}",
16535
+ subjectPlaceholder: "e.g. Refund Initiated Alert: Order {{orderNumber}}",
16536
+ body: `Hi Admin,
16537
+
16538
+ Order {{orderNumber}} was cancelled and refund of {{refundAmount}} {{currency}} was initiated.
16539
+
16540
+ Customer: {{customerName}}
16541
+ Vendor: {{vendorName}}`,
16542
+ bodyPlaceholder: "Refund initiated for order {{orderNumber}}."
16280
16543
  }
16281
16544
  };
16282
16545
  __name(getEmailTriggerDefaults, "getEmailTriggerDefaults");
@@ -16292,6 +16555,24 @@ Refund Due: {{refundAmount}} {{currency}}`,
16292
16555
  subjectPlaceholder: "e.g. Order {{orderNumber}} Cancelled",
16293
16556
  body: `Hi {{customerName}}, your order {{orderNumber}} for {{total}} {{currency}} has been cancelled.`,
16294
16557
  bodyPlaceholder: "Hi {{customerName}}, your order {{orderNumber}} has been cancelled."
16558
+ },
16559
+ payment_success: {
16560
+ subject: "Payment Successful!",
16561
+ subjectPlaceholder: "e.g. Payment Successful!",
16562
+ body: `Hi {{customerName}}, payment for order {{orderNumber}} ({{total}} {{currency}}) was successful.`,
16563
+ bodyPlaceholder: "Payment for order {{orderNumber}} successful."
16564
+ },
16565
+ payment_failed: {
16566
+ subject: "Payment Unpaid / Failed",
16567
+ subjectPlaceholder: "e.g. Payment Unpaid / Failed",
16568
+ body: `Hi {{customerName}}, payment for order {{orderNumber}} failed or remains unpaid.`,
16569
+ bodyPlaceholder: "Payment for order {{orderNumber}} failed."
16570
+ },
16571
+ payment_refund_initiated: {
16572
+ subject: "Refund Initiated",
16573
+ subjectPlaceholder: "e.g. Refund Initiated",
16574
+ body: `Hi {{customerName}}, a refund of {{refundAmount}} {{currency}} for order {{orderNumber}} has been initiated.`,
16575
+ bodyPlaceholder: "Refund for order {{orderNumber}} initiated."
16295
16576
  }
16296
16577
  };
16297
16578
  VENDOR_PUSH_TRIGGER_DEFAULTS = {
@@ -16306,6 +16587,24 @@ Refund Due: {{refundAmount}} {{currency}}`,
16306
16587
  subjectPlaceholder: "e.g. Order {{orderNumber}} Cancelled",
16307
16588
  body: `Hi {{vendorName}}, order {{orderNumber}} from {{customerName}} has been cancelled.`,
16308
16589
  bodyPlaceholder: "Hi {{vendorName}}, order {{orderNumber}} was cancelled."
16590
+ },
16591
+ payment_success: {
16592
+ subject: "Payment Confirmed: {{orderNumber}}",
16593
+ subjectPlaceholder: "e.g. Payment Confirmed: {{orderNumber}}",
16594
+ body: `Hi {{vendorName}}, payment for order {{orderNumber}} from {{customerName}} has been confirmed.`,
16595
+ bodyPlaceholder: "Payment for order {{orderNumber}} confirmed."
16596
+ },
16597
+ payment_failed: {
16598
+ subject: "Payment Unpaid: {{orderNumber}}",
16599
+ subjectPlaceholder: "e.g. Payment Unpaid: {{orderNumber}}",
16600
+ body: `Hi {{vendorName}}, payment for order {{orderNumber}} from {{customerName}} is unpaid or failed.`,
16601
+ bodyPlaceholder: "Payment for order {{orderNumber}} unpaid."
16602
+ },
16603
+ payment_refund_initiated: {
16604
+ subject: "Refund Initiated: {{orderNumber}}",
16605
+ subjectPlaceholder: "e.g. Refund Initiated: {{orderNumber}}",
16606
+ body: `Hi {{vendorName}}, refund initiated for order {{orderNumber}} ({{customerName}}).`,
16607
+ bodyPlaceholder: "Refund initiated for order {{orderNumber}}."
16309
16608
  }
16310
16609
  };
16311
16610
  ADMIN_PUSH_TRIGGER_DEFAULTS = {
@@ -16337,6 +16636,24 @@ Customer: {{customerName}}
16337
16636
  Vendor: {{vendorName}}
16338
16637
  Refund Due: {{refundAmount}} {{currency}}`,
16339
16638
  bodyPlaceholder: "Hi Admin,\n\nOrder {{orderNumber}} cancelled."
16639
+ },
16640
+ payment_success: {
16641
+ subject: "Payment Success: {{orderNumber}}",
16642
+ subjectPlaceholder: "e.g. Payment Success: {{orderNumber}}",
16643
+ body: `Payment of {{total}} {{currency}} captured for order {{orderNumber}} ({{customerName}}).`,
16644
+ bodyPlaceholder: "Payment captured for order {{orderNumber}}."
16645
+ },
16646
+ payment_failed: {
16647
+ subject: "Payment Failed/Unpaid: {{orderNumber}}",
16648
+ subjectPlaceholder: "e.g. Payment Failed: {{orderNumber}}",
16649
+ body: `Payment for order {{orderNumber}} ({{customerName}}) is unpaid or failed.`,
16650
+ bodyPlaceholder: "Payment failed for order {{orderNumber}}."
16651
+ },
16652
+ payment_refund_initiated: {
16653
+ subject: "Refund Initiated: {{orderNumber}}",
16654
+ subjectPlaceholder: "e.g. Refund Initiated: {{orderNumber}}",
16655
+ body: `Refund of {{refundAmount}} {{currency}} initiated for order {{orderNumber}}.`,
16656
+ bodyPlaceholder: "Refund initiated for order {{orderNumber}}."
16340
16657
  }
16341
16658
  };
16342
16659
  __name(getPushTriggerDefaults, "getPushTriggerDefaults");
@@ -16990,6 +17307,7 @@ var init_emailTriggersBindingsPage = __esm({
16990
17307
  orderNumber: "Display order number",
16991
17308
  customerName: "Customer full name",
16992
17309
  customerPhone: "Customer phone",
17310
+ customerEmail: "Customer email address",
16993
17311
  eventName: "Event title / product name",
16994
17312
  vendorName: "Vendor / store name",
16995
17313
  total: "Order total amount",
@@ -16997,7 +17315,8 @@ var init_emailTriggersBindingsPage = __esm({
16997
17315
  vendorId: "Vendor / store ID",
16998
17316
  refundAmount: "Refund amount (refund triggers)",
16999
17317
  invoiceNumber: "Generated invoice number",
17000
- invoiceUrl: "Link to download invoice PDF"
17318
+ invoiceAttachment: "Include this variable to attach Tax Invoice PDF to email",
17319
+ orderDetails: "Formatted item details summary"
17001
17320
  };
17002
17321
  __name(EmailTriggerBindings, "EmailTriggerBindings");
17003
17322
  __name(TriggerEmailEditor, "TriggerEmailEditor");
@@ -17295,6 +17614,7 @@ var init_pushTriggersBindingsPage = __esm({
17295
17614
  orderNumber: "Display order number",
17296
17615
  customerName: "Customer full name",
17297
17616
  customerPhone: "Customer phone",
17617
+ customerEmail: "Customer email address",
17298
17618
  eventName: "Event title / product name",
17299
17619
  vendorName: "Vendor / store name",
17300
17620
  total: "Order total amount",
@@ -17302,7 +17622,8 @@ var init_pushTriggersBindingsPage = __esm({
17302
17622
  vendorId: "Vendor / store ID",
17303
17623
  refundAmount: "Refund amount (refund triggers)",
17304
17624
  invoiceNumber: "Generated invoice number",
17305
- invoiceUrl: "Link to download invoice PDF"
17625
+ invoiceAttachment: "Include this variable to attach Tax Invoice PDF to email",
17626
+ orderDetails: "Formatted item details summary"
17306
17627
  };
17307
17628
  __name(PushTriggerBindings, "PushTriggerBindings");
17308
17629
  }
@@ -18140,6 +18461,177 @@ var init_AuthProvidersPluginSettings = __esm({
18140
18461
  __name(AuthProvidersPluginSettings, "AuthProvidersPluginSettings");
18141
18462
  }
18142
18463
  });
18464
+ function TurnstilePluginSettings({ settingsGroup, onEnabledChange }) {
18465
+ const [loading, setLoading] = React25.useState(true);
18466
+ const [saving, setSaving] = React25.useState(false);
18467
+ const [enabled, setEnabled] = React25.useState(false);
18468
+ const [siteKey, setSiteKey] = React25.useState("");
18469
+ const [secretKey, setSecretKey] = React25.useState("");
18470
+ const [hasSavedSecret, setHasSavedSecret] = React25.useState(false);
18471
+ const [showSecret, setShowSecret] = React25.useState(false);
18472
+ const onEnabledChangeRef = React25.useRef(onEnabledChange);
18473
+ onEnabledChangeRef.current = onEnabledChange;
18474
+ React25.useEffect(() => {
18475
+ let cancelled = false;
18476
+ (async () => {
18477
+ setLoading(true);
18478
+ try {
18479
+ const res = await fetch(`/api/settings/${settingsGroup}`, {
18480
+ cache: "no-store"
18481
+ });
18482
+ const data = res.ok ? await res.json() : {};
18483
+ if (cancelled) return;
18484
+ const isEnabled = data.enabled === "true" || data.enabled === "1";
18485
+ setEnabled(isEnabled);
18486
+ setSiteKey(String(data.siteKey ?? data.TURNSTILE_SITE_KEY ?? "").trim());
18487
+ const secret = String(data.secretKey ?? data.TURNSTILE_SECRET_KEY ?? "").trim();
18488
+ setSecretKey("");
18489
+ setHasSavedSecret(Boolean(secret));
18490
+ } catch {
18491
+ } finally {
18492
+ if (!cancelled) setLoading(false);
18493
+ }
18494
+ })();
18495
+ return () => {
18496
+ cancelled = true;
18497
+ };
18498
+ }, [
18499
+ settingsGroup
18500
+ ]);
18501
+ const handleSave = /* @__PURE__ */ __name(async () => {
18502
+ setSaving(true);
18503
+ try {
18504
+ const payload = {
18505
+ enabled: {
18506
+ value: enabled ? "true" : "false",
18507
+ type: "public"
18508
+ },
18509
+ siteKey: {
18510
+ value: siteKey.trim(),
18511
+ type: "public"
18512
+ }
18513
+ };
18514
+ if (secretKey.trim()) {
18515
+ payload.secretKey = {
18516
+ value: secretKey.trim(),
18517
+ type: "private",
18518
+ encrypted: true
18519
+ };
18520
+ }
18521
+ const res = await fetch(`/api/settings/${settingsGroup}`, {
18522
+ method: "PUT",
18523
+ headers: {
18524
+ "Content-Type": "application/json"
18525
+ },
18526
+ body: JSON.stringify(payload)
18527
+ });
18528
+ if (res.ok) {
18529
+ sonner.toast.success("Turnstile settings saved");
18530
+ if (secretKey.trim()) {
18531
+ setHasSavedSecret(true);
18532
+ setSecretKey("");
18533
+ }
18534
+ onEnabledChangeRef.current?.(enabled);
18535
+ } else {
18536
+ const err = await res.json().catch(() => ({}));
18537
+ sonner.toast.error(err?.message || "Failed to save settings");
18538
+ }
18539
+ } catch {
18540
+ sonner.toast.error("Failed to save settings");
18541
+ } finally {
18542
+ setSaving(false);
18543
+ }
18544
+ }, "handleSave");
18545
+ if (loading) {
18546
+ return /* @__PURE__ */ React.createElement("div", {
18547
+ className: "flex items-center justify-center p-8 text-muted-foreground text-sm"
18548
+ }, /* @__PURE__ */ React.createElement(LucideIcons.Loader2, {
18549
+ className: "mr-2 h-4 w-4 animate-spin"
18550
+ }), "Loading settings\u2026");
18551
+ }
18552
+ return /* @__PURE__ */ React.createElement("div", {
18553
+ className: "space-y-6"
18554
+ }, /* @__PURE__ */ React.createElement("div", {
18555
+ className: "rounded-lg border border-gray-200 bg-gray-50/80 p-4 dark:border-gray-700 dark:bg-gray-800/40"
18556
+ }, /* @__PURE__ */ React.createElement("div", {
18557
+ className: "flex items-center justify-between"
18558
+ }, /* @__PURE__ */ React.createElement("div", {
18559
+ className: "space-y-0.5"
18560
+ }, /* @__PURE__ */ React.createElement(Label3, {
18561
+ htmlFor: `${settingsGroup}-enabled`,
18562
+ className: "text-sm font-semibold text-gray-900 dark:text-white"
18563
+ }, "Enable Cloudflare Turnstile"), /* @__PURE__ */ React.createElement("p", {
18564
+ className: "text-xs text-muted-foreground"
18565
+ }, "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, {
18566
+ id: `${settingsGroup}-enabled`,
18567
+ checked: enabled,
18568
+ onCheckedChange: setEnabled
18569
+ }))), /* @__PURE__ */ React.createElement("div", {
18570
+ className: "space-y-4"
18571
+ }, /* @__PURE__ */ React.createElement("div", {
18572
+ className: "space-y-1.5"
18573
+ }, /* @__PURE__ */ React.createElement(Label3, {
18574
+ htmlFor: `${settingsGroup}-siteKey`,
18575
+ className: "text-sm font-medium"
18576
+ }, "Turnstile Site Key (Public)"), /* @__PURE__ */ React.createElement(Input, {
18577
+ id: `${settingsGroup}-siteKey`,
18578
+ value: siteKey,
18579
+ onChange: /* @__PURE__ */ __name((e) => setSiteKey(e.target.value), "onChange"),
18580
+ placeholder: "0x4AAAAAA...",
18581
+ className: "h-9 font-mono text-sm"
18582
+ }), /* @__PURE__ */ React.createElement("p", {
18583
+ className: "text-xs text-muted-foreground"
18584
+ }, "Obtained from your Cloudflare dashboard under Turnstile Sites. Safe to expose to the frontend.")), /* @__PURE__ */ React.createElement("div", {
18585
+ className: "space-y-1.5"
18586
+ }, /* @__PURE__ */ React.createElement("div", {
18587
+ className: "flex items-center justify-between"
18588
+ }, /* @__PURE__ */ React.createElement(Label3, {
18589
+ htmlFor: `${settingsGroup}-secretKey`,
18590
+ className: "text-sm font-medium"
18591
+ }, "Turnstile Secret Key (Private)"), hasSavedSecret && /* @__PURE__ */ React.createElement("span", {
18592
+ className: "inline-flex items-center gap-1 text-xs font-medium text-emerald-600 dark:text-emerald-400"
18593
+ }, /* @__PURE__ */ React.createElement(LucideIcons.ShieldCheck, {
18594
+ className: "h-3.5 w-3.5"
18595
+ }), "Secret key configured")), /* @__PURE__ */ React.createElement("div", {
18596
+ className: "relative"
18597
+ }, /* @__PURE__ */ React.createElement(Input, {
18598
+ id: `${settingsGroup}-secretKey`,
18599
+ type: showSecret ? "text" : "password",
18600
+ value: secretKey,
18601
+ onChange: /* @__PURE__ */ __name((e) => setSecretKey(e.target.value), "onChange"),
18602
+ 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...",
18603
+ className: "h-9 font-mono text-sm pr-10"
18604
+ }), /* @__PURE__ */ React.createElement("button", {
18605
+ type: "button",
18606
+ className: "absolute right-2.5 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-200",
18607
+ onClick: /* @__PURE__ */ __name(() => setShowSecret(!showSecret), "onClick")
18608
+ }, showSecret ? /* @__PURE__ */ React.createElement(LucideIcons.EyeOff, {
18609
+ className: "h-4 w-4"
18610
+ }) : /* @__PURE__ */ React.createElement(LucideIcons.Eye, {
18611
+ className: "h-4 w-4"
18612
+ }))), /* @__PURE__ */ React.createElement("p", {
18613
+ className: "text-xs text-muted-foreground"
18614
+ }, "Used only on the server to verify tokens with Cloudflare Siteverify API. Never sent to browsers."))), /* @__PURE__ */ React.createElement(Button, {
18615
+ onClick: handleSave,
18616
+ disabled: saving,
18617
+ size: "sm",
18618
+ className: "gap-2"
18619
+ }, saving ? /* @__PURE__ */ React.createElement(LucideIcons.Loader2, {
18620
+ className: "h-4 w-4 animate-spin"
18621
+ }) : /* @__PURE__ */ React.createElement(LucideIcons.Save, {
18622
+ className: "h-4 w-4"
18623
+ }), "Save Settings"));
18624
+ }
18625
+ var init_TurnstilePluginSettings = __esm({
18626
+ "src/admin/components/TurnstilePluginSettings.tsx"() {
18627
+ "use client";
18628
+ init_button();
18629
+ init_input();
18630
+ init_label();
18631
+ init_switch();
18632
+ __name(TurnstilePluginSettings, "TurnstilePluginSettings");
18633
+ }
18634
+ });
18143
18635
 
18144
18636
  // src/plugins/llm/chat-email-intent.ts
18145
18637
  function normalizeIntentKey(raw) {
@@ -18518,6 +19010,7 @@ function PluginSettingsPanel({ descriptor, onSaved }) {
18518
19010
  const isSocialMedia = settingsGroup === "social_media";
18519
19011
  const isMultiVendor = settingsGroup === "multi_vendor";
18520
19012
  const isAuthProviders = settingsGroup === "auth_providers";
19013
+ const isTurnstile = settingsGroup === "turnstile";
18521
19014
  const [enabled, setEnabled] = React25.useState(true);
18522
19015
  const [botName, setBotName] = React25.useState("");
18523
19016
  const [icon, setIcon] = React25.useState("");
@@ -19861,6 +20354,15 @@ function PluginSettingsPanel({ descriptor, onSaved }) {
19861
20354
  }, "onEnabledChange")
19862
20355
  });
19863
20356
  }
20357
+ if (isTurnstile) {
20358
+ return /* @__PURE__ */ React.createElement(TurnstilePluginSettings, {
20359
+ settingsGroup,
20360
+ onEnabledChange: /* @__PURE__ */ __name((value) => {
20361
+ setEnabled(value);
20362
+ onSaved?.();
20363
+ }, "onEnabledChange")
20364
+ });
20365
+ }
19864
20366
  if (loading) return /* @__PURE__ */ React.createElement("div", {
19865
20367
  className: "text-sm text-gray-500 dark:text-gray-400"
19866
20368
  }, "Loading...");
@@ -21201,6 +21703,10 @@ function PluginsPage() {
21201
21703
  const ap = pluginDescriptors.find((p) => p.name === "auth_providers");
21202
21704
  if (ap) setSelectedName(ap.name);
21203
21705
  }
21706
+ if (q === "turnstile" || q === "cloudflare_turnstile" || q === "captcha") {
21707
+ const ts = pluginDescriptors.find((p) => p.name === "turnstile");
21708
+ if (ts) setSelectedName(ts.name);
21709
+ }
21204
21710
  }, [
21205
21711
  searchParams,
21206
21712
  pluginDescriptors
@@ -21313,6 +21819,7 @@ var init_PluginsPage = __esm({
21313
21819
  init_select();
21314
21820
  init_EventNotificationsPluginSettings();
21315
21821
  init_AuthProvidersPluginSettings();
21822
+ init_TurnstilePluginSettings();
21316
21823
  init_ImageOrUrlField();
21317
21824
  init_chat_email_intent();
21318
21825
  init_llm_agent_scope();
@@ -21332,7 +21839,8 @@ var init_PluginsPage = __esm({
21332
21839
  share: LucideIcons.Share2,
21333
21840
  whatsapp: LucideIcons.MessageCircle,
21334
21841
  event_notifications: LucideIcons.Bell,
21335
- auth_providers: LucideIcons.KeyRound
21842
+ auth_providers: LucideIcons.KeyRound,
21843
+ turnstile: LucideIcons.ShieldCheck
21336
21844
  };
21337
21845
  __name(normalizeSmsProviderChoice, "normalizeSmsProviderChoice");
21338
21846
  __name(normalizeMsg91ApiMode, "normalizeMsg91ApiMode");
@@ -22294,7 +22802,7 @@ function VendorTeamPage() {
22294
22802
  }
22295
22803
  }, []);
22296
22804
  React25.useEffect(() => {
22297
- if (canManage) load();
22805
+ if (canManage) void load();
22298
22806
  else setLoading(false);
22299
22807
  }, [
22300
22808
  canManage,
@@ -22352,10 +22860,18 @@ function VendorTeamPage() {
22352
22860
  }, "closeInviteDialog");
22353
22861
  if (!canManage) {
22354
22862
  return /* @__PURE__ */ React25__namespace.default.createElement("div", {
22355
- className: "p-6 max-w-lg"
22863
+ className: "min-w-0 rounded-lg bg-white shadow-md"
22864
+ }, /* @__PURE__ */ React25__namespace.default.createElement("div", {
22865
+ className: "rounded-t-lg border-b border-gray-700 bg-gray-800 px-4 py-2.5"
22866
+ }, /* @__PURE__ */ React25__namespace.default.createElement("h1", {
22867
+ className: "text-base font-semibold text-white"
22868
+ }, "Vendor Team"), /* @__PURE__ */ React25__namespace.default.createElement("p", {
22869
+ className: "mt-0.5 text-xs text-gray-300"
22870
+ }, "Invite members and assign a store role")), /* @__PURE__ */ React25__namespace.default.createElement("div", {
22871
+ className: "p-6"
22356
22872
  }, /* @__PURE__ */ React25__namespace.default.createElement("p", {
22357
22873
  className: "text-sm text-red-600"
22358
- }, "Only the store owner can manage the vendor team."));
22874
+ }, "Only the store owner can manage the vendor team.")));
22359
22875
  }
22360
22876
  const handleAdd = /* @__PURE__ */ __name(async (e) => {
22361
22877
  e.preventDefault();
@@ -22457,17 +22973,21 @@ function VendorTeamPage() {
22457
22973
  }
22458
22974
  }, "handleRemove");
22459
22975
  return /* @__PURE__ */ React25__namespace.default.createElement("div", {
22460
- className: "p-6 max-w-3xl"
22976
+ className: "min-w-0 rounded-lg bg-white shadow-md"
22977
+ }, /* @__PURE__ */ React25__namespace.default.createElement("div", {
22978
+ className: "rounded-t-lg border-b border-gray-700 bg-gray-800 px-4 py-2.5"
22461
22979
  }, /* @__PURE__ */ React25__namespace.default.createElement("h1", {
22462
- className: "text-xl font-semibold text-gray-900"
22980
+ className: "text-base font-semibold text-white"
22463
22981
  }, "Vendor Team"), /* @__PURE__ */ React25__namespace.default.createElement("p", {
22464
- className: "mt-1 text-sm text-gray-600"
22465
- }, "Invite member and assign a store role."), /* @__PURE__ */ React25__namespace.default.createElement("div", {
22466
- className: "mt-6 rounded-lg border border-gray-200 overflow-hidden"
22982
+ className: "mt-0.5 text-xs text-gray-300"
22983
+ }, "Invite member and assign a store role")), /* @__PURE__ */ React25__namespace.default.createElement("div", {
22984
+ className: "p-6 space-y-6"
22985
+ }, /* @__PURE__ */ React25__namespace.default.createElement("div", {
22986
+ className: "rounded-lg border border-gray-200 overflow-hidden"
22467
22987
  }, /* @__PURE__ */ React25__namespace.default.createElement("table", {
22468
22988
  className: "min-w-full text-sm"
22469
22989
  }, /* @__PURE__ */ React25__namespace.default.createElement("thead", {
22470
- className: "bg-gray-50 text-left text-gray-600"
22990
+ className: "bg-gray-50 text-left text-gray-600 border-b border-gray-200"
22471
22991
  }, /* @__PURE__ */ React25__namespace.default.createElement("tr", null, /* @__PURE__ */ React25__namespace.default.createElement("th", {
22472
22992
  className: "px-3 py-2 font-medium"
22473
22993
  }, "Name"), /* @__PURE__ */ React25__namespace.default.createElement("th", {
@@ -22477,8 +22997,10 @@ function VendorTeamPage() {
22477
22997
  }, "Phone"), /* @__PURE__ */ React25__namespace.default.createElement("th", {
22478
22998
  className: "px-3 py-2 font-medium"
22479
22999
  }, "Store role"), /* @__PURE__ */ React25__namespace.default.createElement("th", {
22480
- className: "px-3 py-2 font-medium"
22481
- }))), /* @__PURE__ */ React25__namespace.default.createElement("tbody", null, loading ? /* @__PURE__ */ React25__namespace.default.createElement("tr", null, /* @__PURE__ */ React25__namespace.default.createElement("td", {
23000
+ className: "px-3 py-2 font-medium text-right"
23001
+ }))), /* @__PURE__ */ React25__namespace.default.createElement("tbody", {
23002
+ className: "divide-y divide-gray-100"
23003
+ }, loading ? /* @__PURE__ */ React25__namespace.default.createElement("tr", null, /* @__PURE__ */ React25__namespace.default.createElement("td", {
22482
23004
  colSpan: 5,
22483
23005
  className: "px-3 py-4 text-gray-500"
22484
23006
  }, "Loading\u2026")) : members.length === 0 ? /* @__PURE__ */ React25__namespace.default.createElement("tr", null, /* @__PURE__ */ React25__namespace.default.createElement("td", {
@@ -22488,15 +23010,17 @@ function VendorTeamPage() {
22488
23010
  key: m.vendorUserId,
22489
23011
  className: "border-t border-gray-100"
22490
23012
  }, /* @__PURE__ */ React25__namespace.default.createElement("td", {
22491
- className: "px-3 py-2"
23013
+ className: "px-3 py-2 font-medium text-gray-900"
22492
23014
  }, m.name ?? "\u2014"), /* @__PURE__ */ React25__namespace.default.createElement("td", {
22493
- className: "px-3 py-2"
23015
+ className: "px-3 py-2 text-gray-600"
22494
23016
  }, m.email), /* @__PURE__ */ React25__namespace.default.createElement("td", {
22495
- className: "px-3 py-2"
23017
+ className: "px-3 py-2 text-gray-600"
22496
23018
  }, m.phone ?? "\u2014"), /* @__PURE__ */ React25__namespace.default.createElement("td", {
22497
23019
  className: "px-3 py-2"
22498
- }, m.isOwnerRole ? /* @__PURE__ */ React25__namespace.default.createElement("span", null, m.vendorRoleName ?? "Owner") : /* @__PURE__ */ React25__namespace.default.createElement("select", {
22499
- className: "rounded border border-gray-200 px-2 py-1 text-sm",
23020
+ }, m.isOwnerRole ? /* @__PURE__ */ React25__namespace.default.createElement("span", {
23021
+ className: "text-gray-900 font-medium"
23022
+ }, m.vendorRoleName ?? "Owner") : /* @__PURE__ */ React25__namespace.default.createElement("select", {
23023
+ className: "rounded border border-gray-200 px-2 py-1 text-sm bg-white",
22500
23024
  value: m.vendorRoleId ?? "",
22501
23025
  disabled: roleUpdating === m.userId,
22502
23026
  onChange: /* @__PURE__ */ __name((e) => handleRoleChange(m.userId, Number(e.target.value)), "onChange")
@@ -22512,7 +23036,7 @@ function VendorTeamPage() {
22512
23036
  onClick: /* @__PURE__ */ __name(() => handleRemove(m.userId), "onClick")
22513
23037
  }, "Remove"))))))), /* @__PURE__ */ React25__namespace.default.createElement("form", {
22514
23038
  onSubmit: handleAdd,
22515
- className: "mt-8 space-y-4 rounded-lg border border-gray-200 p-4"
23039
+ className: "space-y-4 rounded-lg border border-gray-200 p-4"
22516
23040
  }, /* @__PURE__ */ React25__namespace.default.createElement("h2", {
22517
23041
  className: "text-sm font-medium text-gray-900"
22518
23042
  }, "Invite team member"), /* @__PURE__ */ React25__namespace.default.createElement("div", {
@@ -22546,7 +23070,7 @@ function VendorTeamPage() {
22546
23070
  htmlFor: "teamRole"
22547
23071
  }, "Store role"), /* @__PURE__ */ React25__namespace.default.createElement("select", {
22548
23072
  id: "teamRole",
22549
- className: "mt-1 w-full rounded-md border border-gray-200 px-3 py-2 text-sm",
23073
+ className: "mt-1 w-full rounded-md border border-gray-200 px-3 py-2 text-sm bg-white",
22550
23074
  value: vendorRoleId,
22551
23075
  onChange: /* @__PURE__ */ __name((e) => setVendorRoleId(e.target.value ? Number(e.target.value) : ""), "onChange")
22552
23076
  }, assignableRoles.map((r) => /* @__PURE__ */ React25__namespace.default.createElement("option", {
@@ -22585,7 +23109,7 @@ function VendorTeamPage() {
22585
23109
  }, message), /* @__PURE__ */ React25__namespace.default.createElement(Button, {
22586
23110
  type: "submit",
22587
23111
  disabled: submitting
22588
- }, submitting ? "Inviting\u2026" : "Invite member")), /* @__PURE__ */ React25__namespace.default.createElement(Dialog, {
23112
+ }, submitting ? "Inviting\u2026" : "Invite member"))), /* @__PURE__ */ React25__namespace.default.createElement(Dialog, {
22589
23113
  open: inviteDialog != null,
22590
23114
  onOpenChange: /* @__PURE__ */ __name((open) => {
22591
23115
  if (!open) closeInviteDialog();
@@ -22605,7 +23129,7 @@ function VendorTeamPage() {
22605
23129
  variant: "outline",
22606
23130
  onClick: /* @__PURE__ */ __name(() => void copyInviteLink(), "onClick")
22607
23131
  }, linkCopied ? /* @__PURE__ */ React25__namespace.default.createElement(LucideIcons.Check, {
22608
- className: "h-4 w-4 mr-2"
23132
+ className: "h-4 w-4 mr-2 text-green-600"
22609
23133
  }) : /* @__PURE__ */ React25__namespace.default.createElement(LucideIcons.Copy, {
22610
23134
  className: "h-4 w-4 mr-2"
22611
23135
  }), linkCopied ? "Copied" : "Copy invite link")), /* @__PURE__ */ React25__namespace.default.createElement(DialogFooter, null, /* @__PURE__ */ React25__namespace.default.createElement(Button, {
@@ -30620,15 +31144,15 @@ function ProductEditPage({ productId }) {
30620
31144
  })));
30621
31145
  }
30622
31146
  }
30623
- if (!cancelled && formsRes.ok) {
30624
- const d = await formsRes.json();
30625
- if (Array.isArray(d.data)) {
30626
- setContactForms(d.data.filter((row) => row.id != null).map((row) => ({
30627
- id: Number(row.id),
30628
- name: row.name?.trim() || `Form #${row.id}`,
30629
- slug: row.slug
30630
- })));
30631
- }
31147
+ const formsResActual = formsRes.ok ? formsRes : await fetch("/api/admin/cms-forms/product-options").catch(() => null);
31148
+ if (!cancelled && formsResActual && formsResActual.ok) {
31149
+ const d = await formsResActual.json();
31150
+ const rows = Array.isArray(d.data) ? d.data : Array.isArray(d) ? d : [];
31151
+ setContactForms(rows.filter((row) => row.id != null || row.value != null).map((row) => ({
31152
+ id: Number(row.id ?? row.value),
31153
+ name: row.name?.trim() || row.label?.trim() || `Form #${row.id ?? row.value}`,
31154
+ slug: row.slug
31155
+ })));
30632
31156
  }
30633
31157
  if (!cancelled && refundRes.ok) {
30634
31158
  const d = await refundRes.json();
@@ -32880,6 +33404,252 @@ var init_EventProductsSection = __esm({
32880
33404
  __name(EventProductsSection, "EventProductsSection");
32881
33405
  }
32882
33406
  });
33407
+ function AttachComboModal({ open, onOpenChange, excludeComboIds = [], onAttach }) {
33408
+ const [attachingId, setAttachingId] = React25.useState(null);
33409
+ const [error, setError] = React25.useState(null);
33410
+ const comboColumns = React25.useMemo(() => exports.STORE_CRUD_CONFIGS.combos.columns.filter((c) => c.field !== "eventId"), []);
33411
+ const comboFilterColumns = React25.useMemo(() => exports.STORE_CRUD_CONFIGS.combos.columns.filter((c) => c.field !== "eventId"), []);
33412
+ const comboFilters = React25.useMemo(() => exports.STORE_CRUD_CONFIGS.combos.filters?.filter((f) => f.param !== "eventId"), []);
33413
+ const extraListParams = React25.useMemo(() => {
33414
+ const params = {};
33415
+ if (excludeComboIds.length > 0) {
33416
+ params.excludeIds = excludeComboIds.join(",");
33417
+ }
33418
+ return params;
33419
+ }, [
33420
+ excludeComboIds
33421
+ ]);
33422
+ const handleAttach = /* @__PURE__ */ __name(async (comboId) => {
33423
+ setError(null);
33424
+ setAttachingId(comboId);
33425
+ try {
33426
+ await onAttach(comboId);
33427
+ onOpenChange(false);
33428
+ } catch (err) {
33429
+ setError(err instanceof Error ? err.message : "Failed to attach combo");
33430
+ } finally {
33431
+ setAttachingId(null);
33432
+ }
33433
+ }, "handleAttach");
33434
+ return /* @__PURE__ */ React.createElement(Dialog, {
33435
+ open,
33436
+ onOpenChange
33437
+ }, /* @__PURE__ */ React.createElement(DialogContent, {
33438
+ className: "max-w-4xl max-h-[90vh] overflow-y-auto"
33439
+ }, /* @__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", {
33440
+ className: "text-sm text-red-600 bg-red-50 rounded-md px-3 py-2"
33441
+ }, error) : null, /* @__PURE__ */ React.createElement(AdminCRUD, {
33442
+ key: `attach-combos-${excludeComboIds.join(",")}-${open}`,
33443
+ title: "Combos",
33444
+ apiEndpoint: "/api/combos",
33445
+ columns: comboColumns,
33446
+ filterColumns: comboFilterColumns,
33447
+ addEditPageUrl: "",
33448
+ defaultSortField: exports.STORE_CRUD_CONFIGS.combos.defaultSortField,
33449
+ defaultSortOrder: exports.STORE_CRUD_CONFIGS.combos.defaultSortOrder,
33450
+ filters: comboFilters,
33451
+ extraListParams,
33452
+ embedded: true,
33453
+ disableRowClick: true,
33454
+ emptyState: {
33455
+ title: "No combos available",
33456
+ description: excludeComboIds.length ? "All existing combos are already attached to this event, or none exist yet." : "Create a combo first, then attach it here."
33457
+ },
33458
+ customRowActions: /* @__PURE__ */ __name((item) => {
33459
+ const id = Number(item.id);
33460
+ const busy = attachingId === id;
33461
+ return /* @__PURE__ */ React.createElement(Button, {
33462
+ type: "button",
33463
+ size: "sm",
33464
+ variant: "outline",
33465
+ disabled: !Number.isFinite(id) || busy,
33466
+ className: "h-7 text-xs",
33467
+ onClick: /* @__PURE__ */ __name(() => void handleAttach(id), "onClick")
33468
+ }, /* @__PURE__ */ React.createElement(LucideIcons.Link2, {
33469
+ className: "h-3.5 w-3.5 mr-1"
33470
+ }), busy ? "Attaching\u2026" : "Attach");
33471
+ }, "customRowActions")
33472
+ })));
33473
+ }
33474
+ var init_AttachComboModal = __esm({
33475
+ "src/components/Admin/AttachComboModal.tsx"() {
33476
+ "use client";
33477
+ init_dialog();
33478
+ init_button();
33479
+ init_CRUD();
33480
+ init_store_crud_configs();
33481
+ __name(AttachComboModal, "AttachComboModal");
33482
+ }
33483
+ });
33484
+ function EventCombosSection({ eventId, returnUrl, readOnly = false }) {
33485
+ const [rows, setRows] = React25.useState([]);
33486
+ const [loading, setLoading] = React25.useState(true);
33487
+ const [attachModalOpen, setAttachModalOpen] = React25.useState(false);
33488
+ const [listRefreshKey, setListRefreshKey] = React25.useState(0);
33489
+ const [detachingId, setDetachingId] = React25.useState(null);
33490
+ const comboColumns = React25.useMemo(() => exports.STORE_CRUD_CONFIGS.combos.columns.filter((c) => c.field !== "eventId"), []);
33491
+ const comboFilterColumns = React25.useMemo(() => exports.STORE_CRUD_CONFIGS.combos.columns.filter((c) => c.field !== "eventId"), []);
33492
+ const comboFilters = React25.useMemo(() => exports.STORE_CRUD_CONFIGS.combos.filters?.filter((f) => f.param !== "eventId"), []);
33493
+ const loadRows = React25.useCallback(async () => {
33494
+ setLoading(true);
33495
+ try {
33496
+ const res = await fetch(`/api/combos?eventId=${encodeURIComponent(eventId)}&limit=200`);
33497
+ if (!res.ok) throw new Error("Failed to load");
33498
+ const data = await res.json();
33499
+ const list = Array.isArray(data.data) ? data.data : [];
33500
+ setRows(list);
33501
+ } catch {
33502
+ setRows([]);
33503
+ } finally {
33504
+ setLoading(false);
33505
+ }
33506
+ }, [
33507
+ eventId
33508
+ ]);
33509
+ React25.useEffect(() => {
33510
+ void loadRows();
33511
+ }, [
33512
+ loadRows
33513
+ ]);
33514
+ const attachedComboIds = React25.useMemo(() => rows.map((r) => Number(r.id)).filter((id) => Number.isFinite(id) && id > 0), [
33515
+ rows
33516
+ ]);
33517
+ const attachToEvent = /* @__PURE__ */ __name(async (comboId) => {
33518
+ const res = await fetch(`/api/combos/${comboId}`, {
33519
+ method: "PUT",
33520
+ headers: {
33521
+ "Content-Type": "application/json"
33522
+ },
33523
+ body: JSON.stringify({
33524
+ eventId: Number(eventId)
33525
+ })
33526
+ });
33527
+ if (!res.ok) {
33528
+ const data = await res.json().catch(() => ({}));
33529
+ throw new Error(data.error ?? "Failed to attach combo");
33530
+ }
33531
+ await loadRows();
33532
+ setListRefreshKey((k) => k + 1);
33533
+ }, "attachToEvent");
33534
+ const detachFromEvent = /* @__PURE__ */ __name(async (comboId) => {
33535
+ if (!window.confirm("Remove this combo from the event?")) return;
33536
+ setDetachingId(comboId);
33537
+ try {
33538
+ const res = await fetch(`/api/combos/${comboId}`, {
33539
+ method: "PUT",
33540
+ headers: {
33541
+ "Content-Type": "application/json"
33542
+ },
33543
+ body: JSON.stringify({
33544
+ eventId: null
33545
+ })
33546
+ });
33547
+ if (res.ok) {
33548
+ await loadRows();
33549
+ setListRefreshKey((k) => k + 1);
33550
+ }
33551
+ } finally {
33552
+ setDetachingId(null);
33553
+ }
33554
+ }, "detachFromEvent");
33555
+ const comboCreateHref = !readOnly ? `/admin/combos/create?eventId=${encodeURIComponent(eventId)}${returnUrl ? `&from=${encodeURIComponent(returnUrl)}` : ""}` : null;
33556
+ if (loading) {
33557
+ return /* @__PURE__ */ React.createElement("section", {
33558
+ className: "border border-gray-200 rounded-lg p-8 text-center text-sm text-gray-500"
33559
+ }, "Loading combos\u2026");
33560
+ }
33561
+ if (readOnly && rows.length === 0) {
33562
+ return /* @__PURE__ */ React.createElement("section", {
33563
+ className: "border border-gray-200 rounded-lg p-6"
33564
+ }, /* @__PURE__ */ React.createElement("h2", {
33565
+ className: "text-sm font-semibold text-gray-900"
33566
+ }, "Event Combos"), /* @__PURE__ */ React.createElement("p", {
33567
+ className: "mt-2 text-sm text-gray-600"
33568
+ }, "No combos are linked to this event yet."));
33569
+ }
33570
+ return /* @__PURE__ */ React.createElement("div", {
33571
+ className: "space-y-4"
33572
+ }, /* @__PURE__ */ React.createElement("section", {
33573
+ className: "border border-gray-200 rounded-lg overflow-hidden bg-white"
33574
+ }, /* @__PURE__ */ React.createElement("div", {
33575
+ className: "flex flex-wrap items-center justify-between gap-3 px-4 py-3 border-b border-gray-200"
33576
+ }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h2", {
33577
+ className: "text-sm font-semibold text-gray-900"
33578
+ }, "Event Combos"), /* @__PURE__ */ React.createElement("p", {
33579
+ className: "text-xs text-gray-500"
33580
+ }, "Event Combos linked to this event.")), !readOnly ? /* @__PURE__ */ React.createElement("div", {
33581
+ className: "flex flex-wrap items-center gap-2"
33582
+ }, /* @__PURE__ */ React.createElement(Button, {
33583
+ type: "button",
33584
+ variant: "outline",
33585
+ size: "sm",
33586
+ className: "h-8 text-xs",
33587
+ onClick: /* @__PURE__ */ __name(() => setAttachModalOpen(true), "onClick")
33588
+ }, /* @__PURE__ */ React.createElement(LucideIcons.Link2, {
33589
+ className: "h-3.5 w-3.5 mr-1"
33590
+ }), "Attach event combo"), comboCreateHref ? /* @__PURE__ */ React.createElement(Link2__default.default, {
33591
+ href: comboCreateHref,
33592
+ 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"
33593
+ }, /* @__PURE__ */ React.createElement(LucideIcons.Plus, {
33594
+ className: "h-3.5 w-3.5"
33595
+ }), "New event combo") : null) : null), /* @__PURE__ */ React.createElement("div", {
33596
+ className: "[&>div]:shadow-none [&>div]:rounded-none"
33597
+ }, rows.length === 0 ? /* @__PURE__ */ React.createElement("div", {
33598
+ className: "px-4 py-8 text-center text-sm text-gray-500"
33599
+ }, "No combos attached to this event yet.") : /* @__PURE__ */ React.createElement(AdminCRUD, {
33600
+ key: `event-combos-${eventId}-${attachedComboIds.join(",")}-${listRefreshKey}`,
33601
+ title: "Event Combos",
33602
+ apiEndpoint: "/api/combos",
33603
+ columns: comboColumns,
33604
+ filterColumns: comboFilterColumns,
33605
+ addEditPageUrl: "/admin/combos",
33606
+ defaultSortField: exports.STORE_CRUD_CONFIGS.combos.defaultSortField,
33607
+ defaultSortOrder: exports.STORE_CRUD_CONFIGS.combos.defaultSortOrder,
33608
+ filters: comboFilters,
33609
+ extraListParams: {
33610
+ eventId: String(eventId)
33611
+ },
33612
+ embedded: true,
33613
+ disableRowClick: readOnly,
33614
+ emptyState: {
33615
+ title: "No combos attached",
33616
+ description: "Attach or create a combo for this event.",
33617
+ action: !readOnly && comboCreateHref ? /* @__PURE__ */ React.createElement(Link2__default.default, {
33618
+ href: comboCreateHref,
33619
+ 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"
33620
+ }, "Create combo") : void 0
33621
+ },
33622
+ customRowActions: readOnly ? void 0 : (item) => {
33623
+ const comboId = Number(item.id);
33624
+ const busy = detachingId === comboId;
33625
+ return /* @__PURE__ */ React.createElement(Button, {
33626
+ type: "button",
33627
+ size: "sm",
33628
+ variant: "outline",
33629
+ disabled: !Number.isFinite(comboId) || busy,
33630
+ className: "h-7 text-xs border-red-200 text-red-600 hover:text-red-700",
33631
+ onClick: /* @__PURE__ */ __name(() => void detachFromEvent(comboId), "onClick")
33632
+ }, /* @__PURE__ */ React.createElement(LucideIcons.Unlink, {
33633
+ className: "h-3.5 w-3.5 mr-1"
33634
+ }), busy ? "Removing\u2026" : "Detach");
33635
+ }
33636
+ }))), attachModalOpen ? /* @__PURE__ */ React.createElement(AttachComboModal, {
33637
+ open: attachModalOpen,
33638
+ onOpenChange: setAttachModalOpen,
33639
+ excludeComboIds: attachedComboIds,
33640
+ onAttach: attachToEvent
33641
+ }) : null);
33642
+ }
33643
+ var init_EventCombosSection = __esm({
33644
+ "src/admin/pages/EventCombosSection.tsx"() {
33645
+ "use client";
33646
+ init_button();
33647
+ init_CRUD();
33648
+ init_AttachComboModal();
33649
+ init_store_crud_configs();
33650
+ __name(EventCombosSection, "EventCombosSection");
33651
+ }
33652
+ });
32883
33653
 
32884
33654
  // src/lib/social-media-links.ts
32885
33655
  function isValidHttpUrl(value) {
@@ -34373,10 +35143,13 @@ function EventEditPage({ eventId }) {
34373
35143
  onClick: goToNextTab,
34374
35144
  className: "rounded-md bg-gray-900 px-4 py-2 text-sm font-medium text-white hover:bg-gray-800"
34375
35145
  }, "Next") : /* @__PURE__ */ React.createElement("div", null)), showEventProducts ? /* @__PURE__ */ React.createElement("div", {
34376
- className: "border-t border-gray-200 px-4 py-4 sm:px-6"
35146
+ className: "border-t border-gray-200 px-4 py-4 sm:px-6 space-y-6"
34377
35147
  }, /* @__PURE__ */ React.createElement(EventProductsSection, {
34378
35148
  eventId: eventRecordId,
34379
35149
  returnUrl: eventEditUrl
35150
+ }), /* @__PURE__ */ React.createElement(EventCombosSection, {
35151
+ eventId: eventRecordId,
35152
+ returnUrl: eventEditUrl
34380
35153
  })) : null);
34381
35154
  }
34382
35155
  var ADDITIONAL_VENUE_MAX_CHARS, DEFAULT_EVENT_TIMEZONE, DEFAULT_EVENT_CURRENCY, EVENT_SOCIAL_KEYS, isCreate4, sectionCls4, labelCls6, inputCls6, ENTITY_TYPE_OPTIONS;
@@ -34391,6 +35164,7 @@ var init_EventEditPage = __esm({
34391
35164
  init_media_url();
34392
35165
  init_JoditRichText();
34393
35166
  init_EventProductsSection();
35167
+ init_EventCombosSection();
34394
35168
  init_admin_config_context();
34395
35169
  init_dialog();
34396
35170
  init_button();
@@ -34428,7 +35202,13 @@ function formatDateTimeLocal(value) {
34428
35202
  if (value == null) return "";
34429
35203
  const date = new Date(String(value));
34430
35204
  if (isNaN(date.getTime())) return "";
34431
- return date.toISOString().slice(0, 16);
35205
+ const pad = /* @__PURE__ */ __name((n) => String(n).padStart(2, "0"), "pad");
35206
+ const y = date.getFullYear();
35207
+ const m = pad(date.getMonth() + 1);
35208
+ const d = pad(date.getDate());
35209
+ const hh = pad(date.getHours());
35210
+ const mm = pad(date.getMinutes());
35211
+ return `${y}-${m}-${d}T${hh}:${mm}`;
34432
35212
  }
34433
35213
  async function fetchEventDefaultCurrency(eventId) {
34434
35214
  try {
@@ -34475,13 +35255,14 @@ function ComboEditPage({ comboId }) {
34475
35255
  const create = isCreate5(comboId);
34476
35256
  const duplicateFrom = searchParams.get("duplicateFrom")?.trim();
34477
35257
  const duplicateSourceId = create && duplicateFrom && /^\d+$/.test(duplicateFrom) ? duplicateFrom : null;
35258
+ const initialEventId = create ? searchParams.get("eventId")?.trim() || "" : "";
34478
35259
  const [loading, setLoading] = React25.useState(!create || !!duplicateSourceId);
34479
35260
  const [saving, setSaving] = React25.useState(false);
34480
35261
  const [errors, setErrors] = React25.useState([]);
34481
35262
  const [name, setName] = React25.useState("");
34482
35263
  const [slug, setSlug] = React25.useState("");
34483
35264
  const [desc, setDesc] = React25.useState("");
34484
- const [eventId, setEventId] = React25.useState("");
35265
+ const [eventId, setEventId] = React25.useState(initialEventId);
34485
35266
  const [priceStr, setPriceStr] = React25.useState("");
34486
35267
  const [defaultCurrency, setDefaultCurrency] = React25.useState("INR");
34487
35268
  const [minSelectableItems, setMinSelectableItems] = React25.useState(1);
@@ -34718,15 +35499,9 @@ function ComboEditPage({ comboId }) {
34718
35499
  ]);
34719
35500
  return;
34720
35501
  }
34721
- if (!startsAt) {
35502
+ if (startsAt && endsAt && new Date(endsAt).getTime() < new Date(startsAt).getTime()) {
34722
35503
  setErrors([
34723
- "Starts at is required"
34724
- ]);
34725
- return;
34726
- }
34727
- if (!endsAt) {
34728
- setErrors([
34729
- "Ends at is required"
35504
+ "Ends at cannot be earlier than Starts at"
34730
35505
  ]);
34731
35506
  return;
34732
35507
  }
@@ -34794,8 +35569,8 @@ function ComboEditPage({ comboId }) {
34794
35569
  minSelectableItems,
34795
35570
  maxSelectableItems,
34796
35571
  status,
34797
- startsAt: startsAt || null,
34798
- endsAt: endsAt || null,
35572
+ startsAt: startsAt ? new Date(startsAt).toISOString() : null,
35573
+ endsAt: endsAt ? new Date(endsAt).toISOString() : null,
34799
35574
  comboItems: [
34800
35575
  ...fixedItems.map((i) => ({
34801
35576
  productId: Number(i.productId),
@@ -35019,21 +35794,23 @@ function ComboEditPage({ comboId }) {
35019
35794
  className: "grid grid-cols-2 gap-4"
35020
35795
  }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("label", {
35021
35796
  className: "block text-xs font-medium text-gray-600 mb-1"
35022
- }, "Starts at *"), /* @__PURE__ */ React.createElement("input", {
35797
+ }, "Starts at (optional)"), /* @__PURE__ */ React.createElement("input", {
35023
35798
  type: "datetime-local",
35024
35799
  value: startsAt,
35025
35800
  onChange: /* @__PURE__ */ __name((e) => setStartsAt(e.target.value), "onChange"),
35026
- className: "w-full rounded-md border border-gray-300 px-3 py-2 text-sm",
35027
- required: true
35028
- })), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("label", {
35801
+ className: "w-full rounded-md border border-gray-300 px-3 py-2 text-sm"
35802
+ }), /* @__PURE__ */ React.createElement("p", {
35803
+ className: "text-xs text-gray-400 mt-1"
35804
+ }, "Leave empty to be available immediately.")), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("label", {
35029
35805
  className: "block text-xs font-medium text-gray-600 mb-1"
35030
- }, "Ends at *"), /* @__PURE__ */ React.createElement("input", {
35806
+ }, "Ends at (optional)"), /* @__PURE__ */ React.createElement("input", {
35031
35807
  type: "datetime-local",
35032
35808
  value: endsAt,
35033
35809
  onChange: /* @__PURE__ */ __name((e) => setEndsAt(e.target.value), "onChange"),
35034
- className: "w-full rounded-md border border-gray-300 px-3 py-2 text-sm",
35035
- required: true
35036
- })))))),
35810
+ className: "w-full rounded-md border border-gray-300 px-3 py-2 text-sm"
35811
+ }), /* @__PURE__ */ React.createElement("p", {
35812
+ className: "text-xs text-gray-400 mt-1"
35813
+ }, "Leave empty if no expiration date.")))))),
35037
35814
  sidebar: /* @__PURE__ */ React.createElement(React.Fragment, null)
35038
35815
  }));
35039
35816
  }
@@ -35149,6 +35926,130 @@ function VendorEditPage({ vendorId }) {
35149
35926
  const [inviteDialog, setInviteDialog] = React25.useState(null);
35150
35927
  const [sendingInviteEmail, setSendingInviteEmail] = React25.useState(false);
35151
35928
  const [linkCopied, setLinkCopied] = React25.useState(false);
35929
+ const VENDOR_CREATE_DRAFT_KEY = "cms_vendor_create_draft";
35930
+ React25.useEffect(() => {
35931
+ if (!create || typeof window === "undefined") return;
35932
+ try {
35933
+ const saved = sessionStorage.getItem(VENDOR_CREATE_DRAFT_KEY);
35934
+ if (saved) {
35935
+ const draft = JSON.parse(saved);
35936
+ if (draft && typeof draft === "object") {
35937
+ if (draft.name) setName(draft.name);
35938
+ if (draft.legalName) setLegalName(draft.legalName);
35939
+ if (draft.slug) setSlug(draft.slug);
35940
+ if (draft.businessType) setBusinessType(draft.businessType);
35941
+ if (draft.description) setDescription(draft.description);
35942
+ if (draft.website) setWebsite(draft.website);
35943
+ if (draft.logo) setLogo(draft.logo);
35944
+ if (draft.email) setEmail(draft.email);
35945
+ if (draft.storePhoneCode) setStorePhoneCode(draft.storePhoneCode);
35946
+ if (draft.storePhoneNum) setStorePhoneNum(draft.storePhoneNum);
35947
+ if (draft.addressLine1) setAddressLine1(draft.addressLine1);
35948
+ if (draft.addressLine2) setAddressLine2(draft.addressLine2);
35949
+ if (draft.city) setCity(draft.city);
35950
+ if (draft.state) setState(draft.state);
35951
+ if (draft.country) setCountry(draft.country);
35952
+ if (draft.postalCode) setPostalCode(draft.postalCode);
35953
+ if (draft.gstin) setGstin(draft.gstin);
35954
+ if (typeof draft.active === "boolean") setActive(draft.active);
35955
+ if (draft.registrationStatus) setRegistrationStatus(draft.registrationStatus);
35956
+ if (draft.ownerName) setOwnerName(draft.ownerName);
35957
+ if (draft.ownerEmail) setOwnerEmail(draft.ownerEmail);
35958
+ if (draft.ownerPhoneCode) setOwnerPhoneCode(draft.ownerPhoneCode);
35959
+ if (draft.ownerPhoneNum) setOwnerPhoneNum(draft.ownerPhoneNum);
35960
+ if (draft.ownerDesignation) setOwnerDesignation(draft.ownerDesignation);
35961
+ if (draft.ownerAadhaarNo) setOwnerAadhaarNo(draft.ownerAadhaarNo);
35962
+ if (draft.ownerPanNo) setOwnerPanNo(draft.ownerPanNo);
35963
+ if (draft.ownerAddressLine1) setOwnerAddressLine1(draft.ownerAddressLine1);
35964
+ if (draft.ownerAddressLine2) setOwnerAddressLine2(draft.ownerAddressLine2);
35965
+ if (draft.ownerCity) setOwnerCity(draft.ownerCity);
35966
+ if (draft.ownerState) setOwnerState(draft.ownerState);
35967
+ if (draft.ownerPostalCode) setOwnerPostalCode(draft.ownerPostalCode);
35968
+ if (typeof draft.termsAccepted === "boolean") setTermsAccepted(draft.termsAccepted);
35969
+ }
35970
+ }
35971
+ } catch {
35972
+ }
35973
+ }, [
35974
+ create
35975
+ ]);
35976
+ React25.useEffect(() => {
35977
+ if (!create || typeof window === "undefined") return;
35978
+ const hasAnyData = Boolean(name || legalName || gstin || ownerName || ownerEmail || ownerPhoneNum || ownerAadhaarNo || ownerPanNo || ownerAddressLine1);
35979
+ if (!hasAnyData) return;
35980
+ try {
35981
+ const draft = {
35982
+ name,
35983
+ legalName,
35984
+ slug,
35985
+ businessType,
35986
+ description,
35987
+ website,
35988
+ logo,
35989
+ email,
35990
+ storePhoneCode,
35991
+ storePhoneNum,
35992
+ addressLine1,
35993
+ addressLine2,
35994
+ city,
35995
+ state,
35996
+ country,
35997
+ postalCode,
35998
+ gstin,
35999
+ active,
36000
+ registrationStatus,
36001
+ ownerName,
36002
+ ownerEmail,
36003
+ ownerPhoneCode,
36004
+ ownerPhoneNum,
36005
+ ownerDesignation,
36006
+ ownerAadhaarNo,
36007
+ ownerPanNo,
36008
+ ownerAddressLine1,
36009
+ ownerAddressLine2,
36010
+ ownerCity,
36011
+ ownerState,
36012
+ ownerPostalCode,
36013
+ termsAccepted
36014
+ };
36015
+ sessionStorage.setItem(VENDOR_CREATE_DRAFT_KEY, JSON.stringify(draft));
36016
+ } catch {
36017
+ }
36018
+ }, [
36019
+ create,
36020
+ name,
36021
+ legalName,
36022
+ slug,
36023
+ businessType,
36024
+ description,
36025
+ website,
36026
+ logo,
36027
+ email,
36028
+ storePhoneCode,
36029
+ storePhoneNum,
36030
+ addressLine1,
36031
+ addressLine2,
36032
+ city,
36033
+ state,
36034
+ country,
36035
+ postalCode,
36036
+ gstin,
36037
+ active,
36038
+ registrationStatus,
36039
+ ownerName,
36040
+ ownerEmail,
36041
+ ownerPhoneCode,
36042
+ ownerPhoneNum,
36043
+ ownerDesignation,
36044
+ ownerAadhaarNo,
36045
+ ownerPanNo,
36046
+ ownerAddressLine1,
36047
+ ownerAddressLine2,
36048
+ ownerCity,
36049
+ ownerState,
36050
+ ownerPostalCode,
36051
+ termsAccepted
36052
+ ]);
35152
36053
  const vendorPayload = /* @__PURE__ */ __name(() => ({
35153
36054
  name: name.trim(),
35154
36055
  legalName: legalName.trim() || null,
@@ -35392,12 +36293,15 @@ function VendorEditPage({ vendorId }) {
35392
36293
  setSaving(false);
35393
36294
  return;
35394
36295
  }
36296
+ const abortController = new AbortController();
36297
+ const timeoutId = setTimeout(() => abortController.abort(), 15e3);
35395
36298
  try {
35396
36299
  const res = await fetch("/api/admin/vendors/onboard", {
35397
36300
  method: "POST",
35398
36301
  headers: {
35399
36302
  "Content-Type": "application/json"
35400
36303
  },
36304
+ signal: abortController.signal,
35401
36305
  body: JSON.stringify({
35402
36306
  activation: "invite",
35403
36307
  sendOwnerEmail: false,
@@ -35418,15 +36322,20 @@ function VendorEditPage({ vendorId }) {
35418
36322
  }
35419
36323
  })
35420
36324
  });
35421
- const data = await res.json();
36325
+ clearTimeout(timeoutId);
36326
+ const data = await res.json().catch(() => ({}));
35422
36327
  if (!res.ok) {
35423
36328
  setErrors([
35424
36329
  data.error || "Onboarding failed"
35425
36330
  ]);
35426
36331
  return;
35427
36332
  }
36333
+ try {
36334
+ sessionStorage.removeItem(VENDOR_CREATE_DRAFT_KEY);
36335
+ } catch {
36336
+ }
35428
36337
  const vendorIdNum = Number(data.vendor?.id);
35429
- const inviteLink = typeof data.inviteLink === "string" && data.inviteLink.trim() ? data.inviteLink.trim() : "";
36338
+ const inviteLink = typeof data.inviteLink === "string" && data.inviteLink?.trim() ? data.inviteLink.trim() : "";
35430
36339
  if (!Number.isFinite(vendorIdNum) || vendorIdNum <= 0 || !inviteLink) {
35431
36340
  sonner.toast.success(data.message || "Vendor created");
35432
36341
  router.push(listReturnUrl);
@@ -35438,11 +36347,18 @@ function VendorEditPage({ vendorId }) {
35438
36347
  vendorId: vendorIdNum,
35439
36348
  inviteLink
35440
36349
  });
35441
- } catch {
35442
- setErrors([
35443
- "Request failed"
35444
- ]);
36350
+ } catch (err) {
36351
+ if (err instanceof DOMException && err.name === "AbortError") {
36352
+ setErrors([
36353
+ "Request timed out. Your entered data is preserved. Please click submit again."
36354
+ ]);
36355
+ } else {
36356
+ setErrors([
36357
+ err instanceof Error && err.message || "Request failed"
36358
+ ]);
36359
+ }
35445
36360
  } finally {
36361
+ clearTimeout(timeoutId);
35446
36362
  setSaving(false);
35447
36363
  }
35448
36364
  }, "handleCreate");
@@ -35481,12 +36397,15 @@ function VendorEditPage({ vendorId }) {
35481
36397
  setSaving(false);
35482
36398
  return;
35483
36399
  }
36400
+ const abortController = new AbortController();
36401
+ const timeoutId = setTimeout(() => abortController.abort(), 15e3);
35484
36402
  try {
35485
36403
  const res = await fetch(`/api/vendors/${vendorId}`, {
35486
36404
  method: "PUT",
35487
36405
  headers: {
35488
36406
  "Content-Type": "application/json"
35489
36407
  },
36408
+ signal: abortController.signal,
35490
36409
  body: JSON.stringify({
35491
36410
  ...vendorPayload(),
35492
36411
  slug: slug.trim(),
@@ -35502,6 +36421,7 @@ function VendorEditPage({ vendorId }) {
35502
36421
  })
35503
36422
  });
35504
36423
  if (!res.ok) {
36424
+ clearTimeout(timeoutId);
35505
36425
  const data = await res.json().catch(() => ({}));
35506
36426
  setErrors([
35507
36427
  data.error || "Failed to save"
@@ -35515,6 +36435,7 @@ function VendorEditPage({ vendorId }) {
35515
36435
  headers: {
35516
36436
  "Content-Type": "application/json"
35517
36437
  },
36438
+ signal: abortController.signal,
35518
36439
  body: JSON.stringify({
35519
36440
  name: ownerName.trim(),
35520
36441
  email: ownerEmail.trim(),
@@ -35522,6 +36443,7 @@ function VendorEditPage({ vendorId }) {
35522
36443
  })
35523
36444
  });
35524
36445
  if (!userRes.ok) {
36446
+ clearTimeout(timeoutId);
35525
36447
  const data = await userRes.json().catch(() => ({}));
35526
36448
  setErrors([
35527
36449
  data.error || "Vendor saved, but updating owner details failed"
@@ -35529,13 +36451,21 @@ function VendorEditPage({ vendorId }) {
35529
36451
  return;
35530
36452
  }
35531
36453
  }
36454
+ clearTimeout(timeoutId);
35532
36455
  sonner.toast.success("Vendor saved");
35533
36456
  router.push(listReturnUrl);
35534
- } catch {
35535
- setErrors([
35536
- "Failed to save"
35537
- ]);
36457
+ } catch (err) {
36458
+ if (err instanceof DOMException && err.name === "AbortError") {
36459
+ setErrors([
36460
+ "Request timed out. Please try saving again."
36461
+ ]);
36462
+ } else {
36463
+ setErrors([
36464
+ err instanceof Error && err.message || "Failed to save"
36465
+ ]);
36466
+ }
35538
36467
  } finally {
36468
+ clearTimeout(timeoutId);
35539
36469
  setSaving(false);
35540
36470
  }
35541
36471
  }, "handleSave");
@@ -40941,6 +41871,8 @@ var init_AdminPageResolver = __esm({
40941
41871
  submissions: {
40942
41872
  title: "Submissions",
40943
41873
  apiEndpoint: "/api/form-submissions",
41874
+ defaultSortField: "createdAt",
41875
+ defaultSortOrder: "desc",
40944
41876
  columns: [
40945
41877
  {
40946
41878
  field: "form.name",
@@ -41486,6 +42418,10 @@ function EventDetailPage({ eventId }) {
41486
42418
  eventId: String(event.id),
41487
42419
  returnUrl: detailUrl,
41488
42420
  readOnly: true
42421
+ }), /* @__PURE__ */ React.createElement(EventCombosSection, {
42422
+ eventId: String(event.id),
42423
+ returnUrl: detailUrl,
42424
+ readOnly: true
41489
42425
  }))
41490
42426
  }));
41491
42427
  }
@@ -41501,6 +42437,7 @@ var init_EventDetailPage = __esm({
41501
42437
  init_DetailPageLayout();
41502
42438
  init_DetailPageHeader();
41503
42439
  init_EventProductsSection();
42440
+ init_EventCombosSection();
41504
42441
  sectionCls6 = "border border-gray-200 rounded-lg p-4 bg-gray-50/50";
41505
42442
  labelCls7 = "text-xs font-medium text-gray-500";
41506
42443
  valueCls = "text-sm text-gray-900 mt-0.5";