@infuro/cms-core 1.0.60 → 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.
package/dist/admin.cjs CHANGED
@@ -895,7 +895,7 @@ var init_admin_config_context = __esm({
895
895
  var CMS_VERSION;
896
896
  var init_cms_version = __esm({
897
897
  "src/lib/cms-version.ts"() {
898
- CMS_VERSION = "1.0.60" ;
898
+ CMS_VERSION = "1.0.61" ;
899
899
  }
900
900
  });
901
901
  function useCatalogCategories(enabled = true) {
@@ -2226,6 +2226,11 @@ function FileUpload({ onUploadSuccess }) {
2226
2226
  if (!selectedFile) return;
2227
2227
  setUploading(true);
2228
2228
  setProgress(10);
2229
+ console.info("[upload] file-upload start", {
2230
+ name: selectedFile.name,
2231
+ type: selectedFile.type,
2232
+ size: selectedFile.size
2233
+ });
2229
2234
  const formData = new FormData();
2230
2235
  formData.append("file", selectedFile);
2231
2236
  try {
@@ -2234,20 +2239,47 @@ function FileUpload({ onUploadSuccess }) {
2234
2239
  body: formData
2235
2240
  });
2236
2241
  setProgress(60);
2237
- 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
+ });
2238
2256
  if (response.ok) {
2239
- setFileUrl(data.url);
2257
+ const url = data.url ?? data.filePath;
2258
+ setFileUrl(url);
2240
2259
  setProgress(100);
2241
- onUploadSuccess(data.url);
2260
+ console.info("[upload] file-upload ok", {
2261
+ url,
2262
+ name: selectedFile.name
2263
+ });
2264
+ onUploadSuccess(url);
2242
2265
  } else {
2243
2266
  console.error("[upload] API error", {
2244
2267
  status: response.status,
2245
2268
  error: data.error,
2246
- details: data.details
2269
+ details: data.details,
2270
+ name: selectedFile.name,
2271
+ type: selectedFile.type,
2272
+ size: selectedFile.size
2247
2273
  });
2248
2274
  }
2249
2275
  } catch (error) {
2250
- 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
+ });
2251
2283
  }
2252
2284
  setUploading(false);
2253
2285
  }, "handleUpload");
@@ -2379,28 +2411,64 @@ function ImageOrUrlField({ label, value, onChange, placeholder = "https://\u2026
2379
2411
  return;
2380
2412
  }
2381
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
+ });
2382
2421
  try {
2383
2422
  const formData = new FormData();
2384
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
+ });
2385
2429
  const response = await fetch("/api/upload", {
2386
2430
  method: "POST",
2387
2431
  body: formData
2388
2432
  });
2389
- 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
+ });
2390
2447
  if (!response.ok) {
2391
2448
  console.error("[upload] API error", {
2392
2449
  status: response.status,
2393
2450
  error: data.error,
2394
- details: data.details
2451
+ details: data.details,
2452
+ fileName: file.name,
2453
+ fileType: file.type,
2454
+ fileSize: file.size
2395
2455
  });
2396
2456
  throw new Error(data.details || data.error || "Upload failed");
2397
2457
  }
2398
2458
  console.info("[upload] API ok", {
2399
- filePath: data.filePath
2459
+ filePath: data.filePath,
2460
+ name: file.name,
2461
+ type: file.type
2400
2462
  });
2401
2463
  onChange(data.filePath ?? "");
2402
2464
  } catch (err) {
2403
- 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
+ });
2404
2472
  setError(err instanceof Error ? err.message : "Upload failed");
2405
2473
  } finally {
2406
2474
  setIsUploading(false);
@@ -5099,6 +5167,9 @@ function AdminCRUD({ title, apiEndpoint, columns, addEditPageUrl, customViewPage
5099
5167
  });
5100
5168
  if (cancelled) return;
5101
5169
  if (!res.ok) {
5170
+ if (res.status === 403) {
5171
+ throw new Error("You don't have access to this section");
5172
+ }
5102
5173
  throw new Error(`HTTP error! status: ${res.status}`);
5103
5174
  }
5104
5175
  const result = await res.json();
@@ -5515,15 +5586,26 @@ function AdminCRUD({ title, apiEndpoint, columns, addEditPageUrl, customViewPage
5515
5586
  }, "Loading...")));
5516
5587
  }
5517
5588
  if (error) {
5589
+ const isForbidden = error.toLowerCase().includes("don't have access") || error.toLowerCase().includes("forbidden") || error.includes("403");
5518
5590
  return /* @__PURE__ */ React.createElement("div", {
5519
5591
  className: embedded ? "p-4" : "rounded-lg bg-white p-6 shadow-md"
5520
5592
  }, /* @__PURE__ */ React.createElement("div", {
5521
- className: "text-center py-8"
5522
- }, /* @__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", {
5523
5605
  className: "text-red-600 mb-4"
5524
5606
  }, "Error loading data: ", error), /* @__PURE__ */ React.createElement(Button, {
5525
5607
  onClick: /* @__PURE__ */ __name(() => window.location.reload(), "onClick")
5526
- }, "Retry")));
5608
+ }, "Retry"))));
5527
5609
  }
5528
5610
  return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", {
5529
5611
  className: shellCls
@@ -6887,26 +6969,63 @@ function ImageUpload({ value, onChange, onRemove, label, required = false, class
6887
6969
  return;
6888
6970
  }
6889
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
+ });
6890
6978
  try {
6891
6979
  const formData = new FormData();
6892
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
+ });
6893
6986
  const response = await fetch("/api/upload", {
6894
6987
  method: "POST",
6895
6988
  body: formData
6896
6989
  });
6897
- 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
+ });
6898
7004
  if (!response.ok) {
6899
7005
  console.error("[upload] API error", {
6900
7006
  status: response.status,
6901
7007
  error: data.error,
6902
- details: data.details
7008
+ details: data.details,
7009
+ fileName: file.name,
7010
+ fileType: file.type,
7011
+ fileSize: file.size
6903
7012
  });
6904
7013
  throw new Error(data.details || data.error || "Upload failed");
6905
7014
  }
7015
+ console.info("[upload] client image-upload ok", {
7016
+ filePath: data.filePath,
7017
+ name: file.name
7018
+ });
6906
7019
  onChange(data.filePath);
6907
7020
  setUploadProgress(100);
6908
7021
  } catch (error2) {
6909
- 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
+ });
6910
7029
  setError(error2 instanceof Error ? error2.message : "Failed to upload image");
6911
7030
  } finally {
6912
7031
  setIsUploading(false);
@@ -33285,6 +33404,252 @@ var init_EventProductsSection = __esm({
33285
33404
  __name(EventProductsSection, "EventProductsSection");
33286
33405
  }
33287
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
+ });
33288
33653
 
33289
33654
  // src/lib/social-media-links.ts
33290
33655
  function isValidHttpUrl(value) {
@@ -34778,10 +35143,13 @@ function EventEditPage({ eventId }) {
34778
35143
  onClick: goToNextTab,
34779
35144
  className: "rounded-md bg-gray-900 px-4 py-2 text-sm font-medium text-white hover:bg-gray-800"
34780
35145
  }, "Next") : /* @__PURE__ */ React.createElement("div", null)), showEventProducts ? /* @__PURE__ */ React.createElement("div", {
34781
- 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"
34782
35147
  }, /* @__PURE__ */ React.createElement(EventProductsSection, {
34783
35148
  eventId: eventRecordId,
34784
35149
  returnUrl: eventEditUrl
35150
+ }), /* @__PURE__ */ React.createElement(EventCombosSection, {
35151
+ eventId: eventRecordId,
35152
+ returnUrl: eventEditUrl
34785
35153
  })) : null);
34786
35154
  }
34787
35155
  var ADDITIONAL_VENUE_MAX_CHARS, DEFAULT_EVENT_TIMEZONE, DEFAULT_EVENT_CURRENCY, EVENT_SOCIAL_KEYS, isCreate4, sectionCls4, labelCls6, inputCls6, ENTITY_TYPE_OPTIONS;
@@ -34796,6 +35164,7 @@ var init_EventEditPage = __esm({
34796
35164
  init_media_url();
34797
35165
  init_JoditRichText();
34798
35166
  init_EventProductsSection();
35167
+ init_EventCombosSection();
34799
35168
  init_admin_config_context();
34800
35169
  init_dialog();
34801
35170
  init_button();
@@ -34833,7 +35202,13 @@ function formatDateTimeLocal(value) {
34833
35202
  if (value == null) return "";
34834
35203
  const date = new Date(String(value));
34835
35204
  if (isNaN(date.getTime())) return "";
34836
- 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}`;
34837
35212
  }
34838
35213
  async function fetchEventDefaultCurrency(eventId) {
34839
35214
  try {
@@ -34880,13 +35255,14 @@ function ComboEditPage({ comboId }) {
34880
35255
  const create = isCreate5(comboId);
34881
35256
  const duplicateFrom = searchParams.get("duplicateFrom")?.trim();
34882
35257
  const duplicateSourceId = create && duplicateFrom && /^\d+$/.test(duplicateFrom) ? duplicateFrom : null;
35258
+ const initialEventId = create ? searchParams.get("eventId")?.trim() || "" : "";
34883
35259
  const [loading, setLoading] = React25.useState(!create || !!duplicateSourceId);
34884
35260
  const [saving, setSaving] = React25.useState(false);
34885
35261
  const [errors, setErrors] = React25.useState([]);
34886
35262
  const [name, setName] = React25.useState("");
34887
35263
  const [slug, setSlug] = React25.useState("");
34888
35264
  const [desc, setDesc] = React25.useState("");
34889
- const [eventId, setEventId] = React25.useState("");
35265
+ const [eventId, setEventId] = React25.useState(initialEventId);
34890
35266
  const [priceStr, setPriceStr] = React25.useState("");
34891
35267
  const [defaultCurrency, setDefaultCurrency] = React25.useState("INR");
34892
35268
  const [minSelectableItems, setMinSelectableItems] = React25.useState(1);
@@ -35123,15 +35499,9 @@ function ComboEditPage({ comboId }) {
35123
35499
  ]);
35124
35500
  return;
35125
35501
  }
35126
- if (!startsAt) {
35502
+ if (startsAt && endsAt && new Date(endsAt).getTime() < new Date(startsAt).getTime()) {
35127
35503
  setErrors([
35128
- "Starts at is required"
35129
- ]);
35130
- return;
35131
- }
35132
- if (!endsAt) {
35133
- setErrors([
35134
- "Ends at is required"
35504
+ "Ends at cannot be earlier than Starts at"
35135
35505
  ]);
35136
35506
  return;
35137
35507
  }
@@ -35199,8 +35569,8 @@ function ComboEditPage({ comboId }) {
35199
35569
  minSelectableItems,
35200
35570
  maxSelectableItems,
35201
35571
  status,
35202
- startsAt: startsAt || null,
35203
- endsAt: endsAt || null,
35572
+ startsAt: startsAt ? new Date(startsAt).toISOString() : null,
35573
+ endsAt: endsAt ? new Date(endsAt).toISOString() : null,
35204
35574
  comboItems: [
35205
35575
  ...fixedItems.map((i) => ({
35206
35576
  productId: Number(i.productId),
@@ -35424,21 +35794,23 @@ function ComboEditPage({ comboId }) {
35424
35794
  className: "grid grid-cols-2 gap-4"
35425
35795
  }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("label", {
35426
35796
  className: "block text-xs font-medium text-gray-600 mb-1"
35427
- }, "Starts at *"), /* @__PURE__ */ React.createElement("input", {
35797
+ }, "Starts at (optional)"), /* @__PURE__ */ React.createElement("input", {
35428
35798
  type: "datetime-local",
35429
35799
  value: startsAt,
35430
35800
  onChange: /* @__PURE__ */ __name((e) => setStartsAt(e.target.value), "onChange"),
35431
- className: "w-full rounded-md border border-gray-300 px-3 py-2 text-sm",
35432
- required: true
35433
- })), /* @__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", {
35434
35805
  className: "block text-xs font-medium text-gray-600 mb-1"
35435
- }, "Ends at *"), /* @__PURE__ */ React.createElement("input", {
35806
+ }, "Ends at (optional)"), /* @__PURE__ */ React.createElement("input", {
35436
35807
  type: "datetime-local",
35437
35808
  value: endsAt,
35438
35809
  onChange: /* @__PURE__ */ __name((e) => setEndsAt(e.target.value), "onChange"),
35439
- className: "w-full rounded-md border border-gray-300 px-3 py-2 text-sm",
35440
- required: true
35441
- })))))),
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.")))))),
35442
35814
  sidebar: /* @__PURE__ */ React.createElement(React.Fragment, null)
35443
35815
  }));
35444
35816
  }
@@ -35554,6 +35926,130 @@ function VendorEditPage({ vendorId }) {
35554
35926
  const [inviteDialog, setInviteDialog] = React25.useState(null);
35555
35927
  const [sendingInviteEmail, setSendingInviteEmail] = React25.useState(false);
35556
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
+ ]);
35557
36053
  const vendorPayload = /* @__PURE__ */ __name(() => ({
35558
36054
  name: name.trim(),
35559
36055
  legalName: legalName.trim() || null,
@@ -35797,12 +36293,15 @@ function VendorEditPage({ vendorId }) {
35797
36293
  setSaving(false);
35798
36294
  return;
35799
36295
  }
36296
+ const abortController = new AbortController();
36297
+ const timeoutId = setTimeout(() => abortController.abort(), 15e3);
35800
36298
  try {
35801
36299
  const res = await fetch("/api/admin/vendors/onboard", {
35802
36300
  method: "POST",
35803
36301
  headers: {
35804
36302
  "Content-Type": "application/json"
35805
36303
  },
36304
+ signal: abortController.signal,
35806
36305
  body: JSON.stringify({
35807
36306
  activation: "invite",
35808
36307
  sendOwnerEmail: false,
@@ -35823,15 +36322,20 @@ function VendorEditPage({ vendorId }) {
35823
36322
  }
35824
36323
  })
35825
36324
  });
35826
- const data = await res.json();
36325
+ clearTimeout(timeoutId);
36326
+ const data = await res.json().catch(() => ({}));
35827
36327
  if (!res.ok) {
35828
36328
  setErrors([
35829
36329
  data.error || "Onboarding failed"
35830
36330
  ]);
35831
36331
  return;
35832
36332
  }
36333
+ try {
36334
+ sessionStorage.removeItem(VENDOR_CREATE_DRAFT_KEY);
36335
+ } catch {
36336
+ }
35833
36337
  const vendorIdNum = Number(data.vendor?.id);
35834
- 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() : "";
35835
36339
  if (!Number.isFinite(vendorIdNum) || vendorIdNum <= 0 || !inviteLink) {
35836
36340
  sonner.toast.success(data.message || "Vendor created");
35837
36341
  router.push(listReturnUrl);
@@ -35843,11 +36347,18 @@ function VendorEditPage({ vendorId }) {
35843
36347
  vendorId: vendorIdNum,
35844
36348
  inviteLink
35845
36349
  });
35846
- } catch {
35847
- setErrors([
35848
- "Request failed"
35849
- ]);
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
+ }
35850
36360
  } finally {
36361
+ clearTimeout(timeoutId);
35851
36362
  setSaving(false);
35852
36363
  }
35853
36364
  }, "handleCreate");
@@ -35886,12 +36397,15 @@ function VendorEditPage({ vendorId }) {
35886
36397
  setSaving(false);
35887
36398
  return;
35888
36399
  }
36400
+ const abortController = new AbortController();
36401
+ const timeoutId = setTimeout(() => abortController.abort(), 15e3);
35889
36402
  try {
35890
36403
  const res = await fetch(`/api/vendors/${vendorId}`, {
35891
36404
  method: "PUT",
35892
36405
  headers: {
35893
36406
  "Content-Type": "application/json"
35894
36407
  },
36408
+ signal: abortController.signal,
35895
36409
  body: JSON.stringify({
35896
36410
  ...vendorPayload(),
35897
36411
  slug: slug.trim(),
@@ -35907,6 +36421,7 @@ function VendorEditPage({ vendorId }) {
35907
36421
  })
35908
36422
  });
35909
36423
  if (!res.ok) {
36424
+ clearTimeout(timeoutId);
35910
36425
  const data = await res.json().catch(() => ({}));
35911
36426
  setErrors([
35912
36427
  data.error || "Failed to save"
@@ -35920,6 +36435,7 @@ function VendorEditPage({ vendorId }) {
35920
36435
  headers: {
35921
36436
  "Content-Type": "application/json"
35922
36437
  },
36438
+ signal: abortController.signal,
35923
36439
  body: JSON.stringify({
35924
36440
  name: ownerName.trim(),
35925
36441
  email: ownerEmail.trim(),
@@ -35927,6 +36443,7 @@ function VendorEditPage({ vendorId }) {
35927
36443
  })
35928
36444
  });
35929
36445
  if (!userRes.ok) {
36446
+ clearTimeout(timeoutId);
35930
36447
  const data = await userRes.json().catch(() => ({}));
35931
36448
  setErrors([
35932
36449
  data.error || "Vendor saved, but updating owner details failed"
@@ -35934,13 +36451,21 @@ function VendorEditPage({ vendorId }) {
35934
36451
  return;
35935
36452
  }
35936
36453
  }
36454
+ clearTimeout(timeoutId);
35937
36455
  sonner.toast.success("Vendor saved");
35938
36456
  router.push(listReturnUrl);
35939
- } catch {
35940
- setErrors([
35941
- "Failed to save"
35942
- ]);
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
+ }
35943
36467
  } finally {
36468
+ clearTimeout(timeoutId);
35944
36469
  setSaving(false);
35945
36470
  }
35946
36471
  }, "handleSave");
@@ -41346,6 +41871,8 @@ var init_AdminPageResolver = __esm({
41346
41871
  submissions: {
41347
41872
  title: "Submissions",
41348
41873
  apiEndpoint: "/api/form-submissions",
41874
+ defaultSortField: "createdAt",
41875
+ defaultSortOrder: "desc",
41349
41876
  columns: [
41350
41877
  {
41351
41878
  field: "form.name",
@@ -41891,6 +42418,10 @@ function EventDetailPage({ eventId }) {
41891
42418
  eventId: String(event.id),
41892
42419
  returnUrl: detailUrl,
41893
42420
  readOnly: true
42421
+ }), /* @__PURE__ */ React.createElement(EventCombosSection, {
42422
+ eventId: String(event.id),
42423
+ returnUrl: detailUrl,
42424
+ readOnly: true
41894
42425
  }))
41895
42426
  }));
41896
42427
  }
@@ -41906,6 +42437,7 @@ var init_EventDetailPage = __esm({
41906
42437
  init_DetailPageLayout();
41907
42438
  init_DetailPageHeader();
41908
42439
  init_EventProductsSection();
42440
+ init_EventCombosSection();
41909
42441
  sectionCls6 = "border border-gray-200 rounded-lg p-4 bg-gray-50/50";
41910
42442
  labelCls7 = "text-xs font-medium text-gray-500";
41911
42443
  valueCls = "text-sm text-gray-900 mt-0.5";