@infuro/cms-core 1.0.40 → 1.0.42

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
@@ -492,7 +492,7 @@ var init_admin_config_context = __esm({
492
492
  var CMS_VERSION;
493
493
  var init_cms_version = __esm({
494
494
  "src/lib/cms-version.ts"() {
495
- CMS_VERSION = "1.0.40" ;
495
+ CMS_VERSION = "1.0.42" ;
496
496
  }
497
497
  });
498
498
  function useCatalogCategories(enabled = true) {
@@ -602,9 +602,12 @@ function AdminSidebar({ variant = "sidebar" }) {
602
602
  const sessionUser = session?.user;
603
603
  const showVendorOnboard = canOnboardVendors(sessionUser);
604
604
  const vendorPortal = isVendorPortalUser(sessionUser);
605
- const { customNavItems, customNavSections = [], storeEnabled, multiVendorEnabled, eventsEnabled } = React26.useContext(exports.AdminConfigContext);
605
+ const { customNavItems, customNavSections = [], storeEnabled, multiVendorEnabled, eventsEnabled, vendorCanCreateCategories, vendorCanCreateCollections, vendorCanCreateBrands } = React26.useContext(exports.AdminConfigContext);
606
606
  const showStoreNav = storeEnabled || vendorPortal;
607
607
  const showPlatformNav = !vendorPortal;
608
+ const showVendorCategories = !vendorPortal || vendorCanCreateCategories === true;
609
+ const showVendorCollections = !vendorPortal || vendorCanCreateCollections === true;
610
+ const showVendorBrands = !vendorPortal || vendorCanCreateBrands === true;
608
611
  const isDrawer = variant === "drawer";
609
612
  const { categories: catalogCategories } = useCatalogCategories(showStoreNav);
610
613
  searchParams.get("categoryId")?.trim() ?? "";
@@ -713,17 +716,17 @@ function AdminSidebar({ variant = "sidebar" }) {
713
716
  className: `${linkCls} ${isActive("/admin/vendors") ? linkActive : linkInactive}`
714
717
  }, /* @__PURE__ */ React.createElement(LucideIcons.Store, {
715
718
  className: `h-4 w-4 mr-2 ${isActive("/admin/vendors") ? iconActive : iconInactive}`
716
- }), "Vendors")), !vendorPortal && /* @__PURE__ */ React.createElement("li", null, /* @__PURE__ */ React.createElement(Link2__default.default, {
719
+ }), "Vendors")), showVendorCategories && /* @__PURE__ */ React.createElement("li", null, /* @__PURE__ */ React.createElement(Link2__default.default, {
717
720
  href: "/admin/product_categories",
718
721
  className: `${linkCls} ${isActive("/admin/product_categories") ? linkActive : linkInactive}`
719
722
  }, /* @__PURE__ */ React.createElement(LucideIcons.FolderTree, {
720
723
  className: `h-4 w-4 mr-2 ${isActive("/admin/product_categories") ? iconActive : iconInactive}`
721
- }), "Categories")), /* @__PURE__ */ React.createElement("li", null, /* @__PURE__ */ React.createElement(Link2__default.default, {
724
+ }), "Categories")), showVendorCollections && /* @__PURE__ */ React.createElement("li", null, /* @__PURE__ */ React.createElement(Link2__default.default, {
722
725
  href: "/admin/collections",
723
726
  className: `${linkCls} ${isActive("/admin/collections") ? linkActive : linkInactive}`
724
727
  }, /* @__PURE__ */ React.createElement(LucideIcons.Layers, {
725
728
  className: `h-4 w-4 mr-2 ${isActive("/admin/collections") ? iconActive : iconInactive}`
726
- }), "Collections")), /* @__PURE__ */ React.createElement("li", null, /* @__PURE__ */ React.createElement(Link2__default.default, {
729
+ }), "Collections")), showVendorBrands && /* @__PURE__ */ React.createElement("li", null, /* @__PURE__ */ React.createElement(Link2__default.default, {
727
730
  href: "/admin/brands",
728
731
  className: `${linkCls} ${isActive("/admin/brands") ? linkActive : linkInactive}`
729
732
  }, /* @__PURE__ */ React.createElement(LucideIcons.Building2, {
@@ -1406,33 +1409,52 @@ function parseBooleanSetting(value) {
1406
1409
  }
1407
1410
  return null;
1408
1411
  }
1409
- function useMultiVendorEnabled() {
1412
+ function useMultiVendorSettings() {
1410
1413
  const [multiVendorEnabled, setMultiVendorEnabled] = React26.useState(true);
1414
+ const [vendorCanCreateCategories, setVendorCanCreateCategories] = React26.useState(false);
1415
+ const [vendorCanCreateCollections, setVendorCanCreateCollections] = React26.useState(false);
1416
+ const [vendorCanCreateBrands, setVendorCanCreateBrands] = React26.useState(false);
1417
+ const [requireProductApproval, setRequireProductApproval] = React26.useState(false);
1411
1418
  React26.useEffect(() => {
1412
1419
  fetch("/api/settings/multi_vendor").then((r) => r.ok ? r.json() : {}).then((data) => {
1413
1420
  const parsed = parseBooleanSetting(data?.enabled ?? data?.value ?? data?.isEnabled ?? data?.active ?? data?.state);
1414
1421
  setMultiVendorEnabled(parsed ?? true);
1422
+ setVendorCanCreateCategories(parseBooleanSetting(data?.vendorCanCreateCategories) === true);
1423
+ setVendorCanCreateCollections(parseBooleanSetting(data?.vendorCanCreateCollections) === true);
1424
+ setVendorCanCreateBrands(parseBooleanSetting(data?.vendorCanCreateBrands) === true);
1425
+ setRequireProductApproval(parseBooleanSetting(data?.requireProductApproval) === true);
1415
1426
  }).catch(() => {
1416
1427
  });
1417
1428
  }, []);
1418
- return multiVendorEnabled;
1429
+ return {
1430
+ multiVendorEnabled,
1431
+ vendorCanCreateCategories,
1432
+ vendorCanCreateCollections,
1433
+ vendorCanCreateBrands,
1434
+ requireProductApproval
1435
+ };
1419
1436
  }
1420
- function useEventsEnabled() {
1437
+ function useEventsSettings() {
1421
1438
  const [eventsEnabled, setEventsEnabled] = React26.useState(true);
1439
+ const [requireEventApproval, setRequireEventApproval] = React26.useState(false);
1422
1440
  React26.useEffect(() => {
1423
1441
  fetch("/api/settings/events").then((r) => r.ok ? r.json() : {}).then((data) => {
1424
1442
  const parsed = parseBooleanSetting(data?.enabled ?? data?.value ?? data?.isEnabled ?? data?.active ?? data?.state);
1425
1443
  setEventsEnabled(parsed ?? true);
1444
+ setRequireEventApproval(parseBooleanSetting(data?.requireEventApproval) === true);
1426
1445
  }).catch(() => {
1427
1446
  });
1428
1447
  }, []);
1429
- return eventsEnabled;
1448
+ return {
1449
+ eventsEnabled,
1450
+ requireEventApproval
1451
+ };
1430
1452
  }
1431
1453
  function AdminLayout({ children, customNavItems = [], customNavSections = [], customCrudConfigs = {}, categoryRelatedProductLabels = {}, renderOrderDetailFooter, theme, themeRegistry, pluginDescriptors = [] }) {
1432
1454
  const resolvedTheme = useResolvedTheme(theme, themeRegistry);
1433
1455
  const { storeEnabled, currency } = useStoreEnabled();
1434
- const multiVendorEnabled = useMultiVendorEnabled();
1435
- const eventsEnabled = useEventsEnabled();
1456
+ const { multiVendorEnabled, vendorCanCreateCategories, vendorCanCreateCollections, vendorCanCreateBrands, requireProductApproval } = useMultiVendorSettings();
1457
+ const { eventsEnabled, requireEventApproval } = useEventsSettings();
1436
1458
  const mergedPluginDescriptors = React26.useMemo(() => {
1437
1459
  const seen = new Set(pluginDescriptors.map((p) => p.name));
1438
1460
  const extra = BUILTIN_PLUGIN_DESCRIPTORS.filter((p) => !seen.has(p.name));
@@ -1455,6 +1477,11 @@ function AdminLayout({ children, customNavItems = [], customNavSections = [], cu
1455
1477
  storeEnabled,
1456
1478
  currency,
1457
1479
  multiVendorEnabled,
1480
+ vendorCanCreateCategories,
1481
+ vendorCanCreateCollections,
1482
+ vendorCanCreateBrands,
1483
+ requireProductApproval,
1484
+ requireEventApproval,
1458
1485
  eventsEnabled
1459
1486
  }), [
1460
1487
  customNavItems,
@@ -1468,6 +1495,11 @@ function AdminLayout({ children, customNavItems = [], customNavSections = [], cu
1468
1495
  storeEnabled,
1469
1496
  currency,
1470
1497
  multiVendorEnabled,
1498
+ vendorCanCreateCategories,
1499
+ vendorCanCreateCollections,
1500
+ vendorCanCreateBrands,
1501
+ requireProductApproval,
1502
+ requireEventApproval,
1471
1503
  eventsEnabled
1472
1504
  ]);
1473
1505
  return /* @__PURE__ */ React.createElement(exports.AdminConfigContext.Provider, {
@@ -1504,8 +1536,8 @@ var init_AdminLayout = __esm({
1504
1536
  __name(useResolvedTheme, "useResolvedTheme");
1505
1537
  __name(useStoreEnabled, "useStoreEnabled");
1506
1538
  __name(parseBooleanSetting, "parseBooleanSetting");
1507
- __name(useMultiVendorEnabled, "useMultiVendorEnabled");
1508
- __name(useEventsEnabled, "useEventsEnabled");
1539
+ __name(useMultiVendorSettings, "useMultiVendorSettings");
1540
+ __name(useEventsSettings, "useEventsSettings");
1509
1541
  __name(AdminLayout, "AdminLayout");
1510
1542
  }
1511
1543
  });
@@ -7736,6 +7768,155 @@ var init_ComponentSettings = __esm({
7736
7768
  __name(ComponentSettings, "ComponentSettings");
7737
7769
  }
7738
7770
  });
7771
+ function ImageOrUrlField({ label, value, onChange, placeholder = "https://\u2026", inputClassName = "w-full rounded-md border border-gray-300 px-2 py-1.5 text-sm", labelClassName = "block text-xs font-medium text-gray-600 mb-1", previewVariant = "banner", maxSizeMb = 10 }) {
7772
+ const fileInputRef = React26.useRef(null);
7773
+ const [isUploading, setIsUploading] = React26.useState(false);
7774
+ const [error, setError] = React26.useState(null);
7775
+ const [lightboxOpen, setLightboxOpen] = React26.useState(false);
7776
+ const previewCls = previewVariant === "logo" ? "h-20 w-20 rounded-md border border-gray-200 bg-white object-contain cursor-pointer" : "max-h-32 w-full rounded-md border border-gray-200 bg-gray-50 object-cover cursor-pointer";
7777
+ const closeLightbox = React26.useCallback(() => setLightboxOpen(false), []);
7778
+ React26.useEffect(() => {
7779
+ if (!lightboxOpen) return;
7780
+ const onKeyDown = /* @__PURE__ */ __name((e) => {
7781
+ if (e.key === "Escape") closeLightbox();
7782
+ }, "onKeyDown");
7783
+ window.addEventListener("keydown", onKeyDown);
7784
+ const prevOverflow = document.body.style.overflow;
7785
+ document.body.style.overflow = "hidden";
7786
+ return () => {
7787
+ window.removeEventListener("keydown", onKeyDown);
7788
+ document.body.style.overflow = prevOverflow;
7789
+ };
7790
+ }, [
7791
+ lightboxOpen,
7792
+ closeLightbox
7793
+ ]);
7794
+ const handleUpload = React26.useCallback(async (file) => {
7795
+ setError(null);
7796
+ if (!ACCEPTED_TYPES.includes(file.type)) {
7797
+ setError(`Unsupported file type. Use: ${ACCEPTED_TYPES.map((t) => t.replace("image/", "")).join(", ")}`);
7798
+ return;
7799
+ }
7800
+ if (file.size > maxSizeMb * 1024 * 1024) {
7801
+ setError(`File must be under ${maxSizeMb}MB`);
7802
+ return;
7803
+ }
7804
+ setIsUploading(true);
7805
+ try {
7806
+ const formData = new FormData();
7807
+ formData.append("file", file);
7808
+ const response = await fetch("/api/upload", {
7809
+ method: "POST",
7810
+ body: formData
7811
+ });
7812
+ const data = await response.json();
7813
+ if (!response.ok) {
7814
+ throw new Error(data.error || data.details || "Upload failed");
7815
+ }
7816
+ onChange(data.filePath ?? "");
7817
+ } catch (err) {
7818
+ setError(err instanceof Error ? err.message : "Upload failed");
7819
+ } finally {
7820
+ setIsUploading(false);
7821
+ if (fileInputRef.current) fileInputRef.current.value = "";
7822
+ }
7823
+ }, [
7824
+ maxSizeMb,
7825
+ onChange
7826
+ ]);
7827
+ const onFileChange = React26.useCallback((e) => {
7828
+ const file = e.target.files?.[0];
7829
+ if (file) void handleUpload(file);
7830
+ }, [
7831
+ handleUpload
7832
+ ]);
7833
+ const trimmed = value.trim();
7834
+ return /* @__PURE__ */ React.createElement("div", {
7835
+ className: "space-y-2"
7836
+ }, /* @__PURE__ */ React.createElement("label", {
7837
+ className: labelClassName
7838
+ }, label), trimmed ? /* @__PURE__ */ React.createElement("div", {
7839
+ className: "flex items-start gap-3"
7840
+ }, /* @__PURE__ */ React.createElement("img", {
7841
+ src: trimmed,
7842
+ alt: label,
7843
+ className: previewCls,
7844
+ role: "button",
7845
+ tabIndex: 0,
7846
+ title: "Click to enlarge",
7847
+ onClick: /* @__PURE__ */ __name(() => setLightboxOpen(true), "onClick"),
7848
+ onKeyDown: /* @__PURE__ */ __name((e) => {
7849
+ if (e.key === "Enter" || e.key === " ") {
7850
+ e.preventDefault();
7851
+ setLightboxOpen(true);
7852
+ }
7853
+ }, "onKeyDown"),
7854
+ onError: /* @__PURE__ */ __name((e) => {
7855
+ e.currentTarget.style.display = "none";
7856
+ }, "onError")
7857
+ }), /* @__PURE__ */ React.createElement("button", {
7858
+ type: "button",
7859
+ onClick: /* @__PURE__ */ __name(() => onChange(""), "onClick"),
7860
+ className: "inline-flex items-center gap-1 rounded border border-gray-300 bg-white px-2 py-1 text-xs text-gray-600 hover:bg-gray-50"
7861
+ }, /* @__PURE__ */ React.createElement(LucideIcons.X, {
7862
+ className: "h-3 w-3"
7863
+ }), "Clear")) : null, /* @__PURE__ */ React.createElement("div", {
7864
+ className: "flex flex-wrap gap-2"
7865
+ }, /* @__PURE__ */ React.createElement("input", {
7866
+ type: "url",
7867
+ value,
7868
+ onChange: /* @__PURE__ */ __name((e) => {
7869
+ setError(null);
7870
+ onChange(e.target.value);
7871
+ }, "onChange"),
7872
+ placeholder,
7873
+ className: `${inputClassName} min-w-0 flex-1`
7874
+ }), /* @__PURE__ */ React.createElement("input", {
7875
+ ref: fileInputRef,
7876
+ type: "file",
7877
+ accept: ACCEPTED_TYPES.join(","),
7878
+ onChange: onFileChange,
7879
+ className: "hidden",
7880
+ disabled: isUploading
7881
+ }), /* @__PURE__ */ React.createElement("button", {
7882
+ type: "button",
7883
+ onClick: /* @__PURE__ */ __name(() => fileInputRef.current?.click(), "onClick"),
7884
+ disabled: isUploading,
7885
+ className: "inline-flex shrink-0 items-center gap-1.5 rounded-md border border-gray-300 bg-white px-3 py-1.5 text-sm font-medium text-gray-700 hover:bg-gray-50 disabled:opacity-50"
7886
+ }, /* @__PURE__ */ React.createElement(LucideIcons.Upload, {
7887
+ className: "h-3.5 w-3.5"
7888
+ }), isUploading ? "Uploading\u2026" : "Upload")), error ? /* @__PURE__ */ React.createElement("p", {
7889
+ className: "flex items-center gap-1.5 text-xs text-red-600"
7890
+ }, /* @__PURE__ */ React.createElement(LucideIcons.AlertCircle, {
7891
+ className: "h-3.5 w-3.5 shrink-0"
7892
+ }), error) : /* @__PURE__ */ React.createElement("p", {
7893
+ className: "text-xs text-gray-500"
7894
+ }, "Paste a URL or upload an image (PNG, JPG, GIF, WEBP up to ", maxSizeMb, "MB)"), lightboxOpen && trimmed ? /* @__PURE__ */ React.createElement("div", {
7895
+ className: "fixed inset-0 z-[100] flex items-center justify-center bg-black/70 p-4",
7896
+ role: "dialog",
7897
+ "aria-modal": "true",
7898
+ "aria-label": `${label} preview`,
7899
+ onClick: closeLightbox
7900
+ }, /* @__PURE__ */ React.createElement("img", {
7901
+ src: trimmed,
7902
+ alt: label,
7903
+ className: "max-h-[90vh] max-w-[90vw] rounded-md object-contain shadow-lg",
7904
+ onClick: /* @__PURE__ */ __name((e) => e.stopPropagation(), "onClick")
7905
+ })) : null);
7906
+ }
7907
+ var ACCEPTED_TYPES;
7908
+ var init_ImageOrUrlField = __esm({
7909
+ "src/components/Admin/ImageOrUrlField.tsx"() {
7910
+ "use client";
7911
+ ACCEPTED_TYPES = [
7912
+ "image/jpeg",
7913
+ "image/png",
7914
+ "image/gif",
7915
+ "image/webp"
7916
+ ];
7917
+ __name(ImageOrUrlField, "ImageOrUrlField");
7918
+ }
7919
+ });
7739
7920
  function generateId() {
7740
7921
  return `nav_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`;
7741
7922
  }
@@ -8038,22 +8219,16 @@ function NavbarEditor({ config, onChange }) {
8038
8219
  className: "space-y-6"
8039
8220
  }, /* @__PURE__ */ React.createElement("div", {
8040
8221
  className: "space-y-3"
8041
- }, /* @__PURE__ */ React.createElement(Label3, {
8042
- className: "text-sm font-medium"
8043
- }, "Logo"), /* @__PURE__ */ React.createElement("div", {
8044
- className: "flex items-center gap-3"
8045
- }, config.logo && /* @__PURE__ */ React.createElement("img", {
8046
- src: config.logo,
8047
- alt: "Logo",
8048
- className: "h-10 rounded border object-contain"
8049
- }), /* @__PURE__ */ React.createElement(Input, {
8222
+ }, /* @__PURE__ */ React.createElement(ImageOrUrlField, {
8223
+ label: "Logo",
8050
8224
  value: config.logo || "",
8051
- onChange: /* @__PURE__ */ __name((e) => onChange({
8225
+ onChange: /* @__PURE__ */ __name((logo) => onChange({
8052
8226
  ...config,
8053
- logo: e.target.value
8227
+ logo
8054
8228
  }), "onChange"),
8229
+ previewVariant: "logo",
8055
8230
  placeholder: "Logo image URL"
8056
- }))), /* @__PURE__ */ React.createElement("div", {
8231
+ })), /* @__PURE__ */ React.createElement("div", {
8057
8232
  className: "space-y-3"
8058
8233
  }, /* @__PURE__ */ React.createElement("div", {
8059
8234
  className: "flex items-center justify-between"
@@ -8092,6 +8267,7 @@ var init_NavbarEditor = __esm({
8092
8267
  init_button();
8093
8268
  init_switch();
8094
8269
  init_label();
8270
+ init_ImageOrUrlField();
8095
8271
  __name(generateId, "generateId");
8096
8272
  __name(NavItemEditor, "NavItemEditor");
8097
8273
  __name(updateItemInTree, "updateItemInTree");
@@ -11040,10 +11216,32 @@ function renderListPrice(value, item) {
11040
11216
  return `${formatted} ${currency}`;
11041
11217
  }
11042
11218
  }
11219
+ function renderListThumbnail(url) {
11220
+ const trimmed = url.trim();
11221
+ if (!trimmed) return "\u2014";
11222
+ return React26.createElement("img", {
11223
+ src: trimmed,
11224
+ alt: "",
11225
+ className: "h-10 w-10 rounded border border-gray-200 bg-white object-contain"
11226
+ });
11227
+ }
11228
+ function productListImageUrl(item) {
11229
+ const meta = item.metadata;
11230
+ if (!meta || typeof meta !== "object" || Array.isArray(meta)) return "";
11231
+ const images = meta.images;
11232
+ if (!Array.isArray(images)) return "";
11233
+ const rows = images;
11234
+ const def = rows.find((r) => r?.isDefault && typeof r.url === "string" && r.url.trim());
11235
+ if (def && typeof def.url === "string") return def.url.trim();
11236
+ const first = rows.find((r) => typeof r.url === "string" && r.url.trim());
11237
+ return first && typeof first.url === "string" ? first.url.trim() : "";
11238
+ }
11043
11239
  exports.STORE_CRUD_CONFIGS = void 0;
11044
11240
  var init_store_crud_configs = __esm({
11045
11241
  "src/admin/store-crud-configs.ts"() {
11046
11242
  __name(renderListPrice, "renderListPrice");
11243
+ __name(renderListThumbnail, "renderListThumbnail");
11244
+ __name(productListImageUrl, "productListImageUrl");
11047
11245
  exports.STORE_CRUD_CONFIGS = {
11048
11246
  products: {
11049
11247
  title: "Products",
@@ -11069,9 +11267,40 @@ var init_store_crud_configs = __esm({
11069
11267
  label: "Out of stock"
11070
11268
  }
11071
11269
  ]
11270
+ },
11271
+ {
11272
+ param: "approvalStatus",
11273
+ label: "Approval",
11274
+ type: "select",
11275
+ options: [
11276
+ {
11277
+ value: "",
11278
+ label: "All"
11279
+ },
11280
+ {
11281
+ value: "pending",
11282
+ label: "Pending"
11283
+ },
11284
+ {
11285
+ value: "approved",
11286
+ label: "Approved"
11287
+ },
11288
+ {
11289
+ value: "rejected",
11290
+ label: "Rejected"
11291
+ }
11292
+ ]
11072
11293
  }
11073
11294
  ],
11074
11295
  columns: [
11296
+ {
11297
+ field: "metadata",
11298
+ displayName: "Image",
11299
+ hideInCreate: true,
11300
+ hideInEdit: true,
11301
+ listFilter: false,
11302
+ render: /* @__PURE__ */ __name((_value, item) => renderListThumbnail(productListImageUrl(item)), "render")
11303
+ },
11075
11304
  {
11076
11305
  field: "name",
11077
11306
  displayName: "Name"
@@ -11133,6 +11362,33 @@ var init_store_crud_configs = __esm({
11133
11362
  }
11134
11363
  ]
11135
11364
  },
11365
+ {
11366
+ field: "approvalStatus",
11367
+ displayName: "Approval",
11368
+ type: "select",
11369
+ listFilter: false,
11370
+ options: [
11371
+ {
11372
+ value: "pending",
11373
+ label: "Pending"
11374
+ },
11375
+ {
11376
+ value: "approved",
11377
+ label: "Approved"
11378
+ },
11379
+ {
11380
+ value: "rejected",
11381
+ label: "Rejected"
11382
+ }
11383
+ ],
11384
+ render: /* @__PURE__ */ __name((value) => {
11385
+ const v = value == null || value === "" ? null : String(value);
11386
+ if (v === "pending") return "Pending";
11387
+ if (v === "approved") return "Approved";
11388
+ if (v === "rejected") return "Rejected";
11389
+ return "\u2014";
11390
+ }, "render")
11391
+ },
11136
11392
  {
11137
11393
  field: "featured",
11138
11394
  displayName: "Featured",
@@ -11256,6 +11512,14 @@ var init_store_crud_configs = __esm({
11256
11512
  title: "Collections",
11257
11513
  apiEndpoint: "/api/collections",
11258
11514
  columns: [
11515
+ {
11516
+ field: "image",
11517
+ displayName: "Image",
11518
+ hideInCreate: true,
11519
+ hideInEdit: true,
11520
+ listFilter: false,
11521
+ render: /* @__PURE__ */ __name((value) => renderListThumbnail(typeof value === "string" ? value : ""), "render")
11522
+ },
11259
11523
  {
11260
11524
  field: "name",
11261
11525
  displayName: "Name"
@@ -11652,6 +11916,13 @@ var init_store_crud_configs = __esm({
11652
11916
  field: "slug",
11653
11917
  displayName: "Slug"
11654
11918
  },
11919
+ {
11920
+ field: "isCatalog",
11921
+ displayName: "Catalog",
11922
+ type: "boolean",
11923
+ hideInCreate: true,
11924
+ hideInEdit: true
11925
+ },
11655
11926
  {
11656
11927
  field: "active",
11657
11928
  displayName: "Active",
@@ -11954,6 +12225,29 @@ var init_store_crud_configs = __esm({
11954
12225
  label: "Paid"
11955
12226
  }
11956
12227
  ]
12228
+ },
12229
+ {
12230
+ param: "approvalStatus",
12231
+ label: "Approval",
12232
+ type: "select",
12233
+ options: [
12234
+ {
12235
+ value: "",
12236
+ label: "All"
12237
+ },
12238
+ {
12239
+ value: "pending",
12240
+ label: "Pending"
12241
+ },
12242
+ {
12243
+ value: "approved",
12244
+ label: "Approved"
12245
+ },
12246
+ {
12247
+ value: "rejected",
12248
+ label: "Rejected"
12249
+ }
12250
+ ]
11957
12251
  }
11958
12252
  ],
11959
12253
  columns: [
@@ -11985,6 +12279,33 @@ var init_store_crud_configs = __esm({
11985
12279
  displayName: "Active",
11986
12280
  type: "boolean"
11987
12281
  },
12282
+ {
12283
+ field: "approvalStatus",
12284
+ displayName: "Approval",
12285
+ type: "select",
12286
+ listFilter: false,
12287
+ options: [
12288
+ {
12289
+ value: "pending",
12290
+ label: "Pending"
12291
+ },
12292
+ {
12293
+ value: "approved",
12294
+ label: "Approved"
12295
+ },
12296
+ {
12297
+ value: "rejected",
12298
+ label: "Rejected"
12299
+ }
12300
+ ],
12301
+ render: /* @__PURE__ */ __name((value) => {
12302
+ const v = value == null || value === "" ? null : String(value);
12303
+ if (v === "pending") return "Pending";
12304
+ if (v === "approved") return "Approved";
12305
+ if (v === "rejected") return "Rejected";
12306
+ return "\u2014";
12307
+ }, "render")
12308
+ },
11988
12309
  {
11989
12310
  field: "comingSoon",
11990
12311
  displayName: "Coming soon",
@@ -12271,7 +12592,12 @@ function SettingsPage() {
12271
12592
  const [themeSettingsLoading, setThemeSettingsLoading] = React26.useState(true);
12272
12593
  const [storeEnabled, setStoreEnabled] = React26.useState(false);
12273
12594
  const [multiVendorEnabled, setMultiVendorEnabled] = React26.useState(true);
12595
+ const [vendorCanCreateCategories, setVendorCanCreateCategories] = React26.useState(false);
12596
+ const [vendorCanCreateCollections, setVendorCanCreateCollections] = React26.useState(false);
12597
+ const [vendorCanCreateBrands, setVendorCanCreateBrands] = React26.useState(false);
12598
+ const [requireProductApproval, setRequireProductApproval] = React26.useState(false);
12274
12599
  const [eventsEnabled, setEventsEnabled] = React26.useState(true);
12600
+ const [requireEventApproval, setRequireEventApproval] = React26.useState(false);
12275
12601
  const [storeSettingsLoading, setStoreSettingsLoading] = React26.useState(true);
12276
12602
  const [currency, setCurrency] = React26.useState(DEFAULT_CURRENCY);
12277
12603
  const [currencies, setCurrencies] = React26.useState([]);
@@ -12331,10 +12657,15 @@ function SettingsPage() {
12331
12657
  }).finally(() => setStoreSettingsLoading(false));
12332
12658
  fetch("/api/settings/multi_vendor").then((r) => r.ok ? r.json() : {}).then((data) => {
12333
12659
  setMultiVendorEnabled(data.enabled !== "false");
12660
+ setVendorCanCreateCategories(data.vendorCanCreateCategories === "true");
12661
+ setVendorCanCreateCollections(data.vendorCanCreateCollections === "true");
12662
+ setVendorCanCreateBrands(data.vendorCanCreateBrands === "true");
12663
+ setRequireProductApproval(data.requireProductApproval === "true");
12334
12664
  }).catch(() => {
12335
12665
  });
12336
12666
  fetch("/api/settings/events").then((r) => r.ok ? r.json() : {}).then((data) => {
12337
12667
  setEventsEnabled(data.enabled !== "false");
12668
+ setRequireEventApproval(data.requireEventApproval === "true");
12338
12669
  }).catch(() => {
12339
12670
  });
12340
12671
  fetch("/api/currencies/exchange-rates").then((r) => r.ok ? r.json() : []).then((data) => {
@@ -12462,6 +12793,22 @@ function SettingsPage() {
12462
12793
  enabled: {
12463
12794
  value: multiVendorEnabled ? "true" : "false",
12464
12795
  type: "public"
12796
+ },
12797
+ vendorCanCreateCategories: {
12798
+ value: vendorCanCreateCategories ? "true" : "false",
12799
+ type: "public"
12800
+ },
12801
+ vendorCanCreateCollections: {
12802
+ value: vendorCanCreateCollections ? "true" : "false",
12803
+ type: "public"
12804
+ },
12805
+ vendorCanCreateBrands: {
12806
+ value: vendorCanCreateBrands ? "true" : "false",
12807
+ type: "public"
12808
+ },
12809
+ requireProductApproval: {
12810
+ value: requireProductApproval ? "true" : "false",
12811
+ type: "public"
12465
12812
  }
12466
12813
  })
12467
12814
  });
@@ -12474,6 +12821,10 @@ function SettingsPage() {
12474
12821
  enabled: {
12475
12822
  value: eventsEnabled ? "true" : "false",
12476
12823
  type: "public"
12824
+ },
12825
+ requireEventApproval: {
12826
+ value: requireEventApproval ? "true" : "false",
12827
+ type: "public"
12477
12828
  }
12478
12829
  })
12479
12830
  });
@@ -12745,8 +13096,46 @@ function SettingsPage() {
12745
13096
  className: "text-sm font-medium text-gray-700"
12746
13097
  }, "Multi Vendor"), /* @__PURE__ */ React.createElement("p", {
12747
13098
  className: "text-xs text-gray-500"
12748
- }, "Enable vendor portals, vendor logins, and vendor-scoped store data. Disabling this blocks vendor-only accounts from signing in."))), storeEnabled && /* @__PURE__ */ React.createElement("div", {
12749
- className: "mt-4 flex items-center gap-3 p-4 bg-gray-50 rounded-lg border border-gray-200"
13099
+ }, "Enable vendor portals, vendor logins, and vendor-scoped store data. Disabling this blocks vendor-only accounts from signing in."))), storeEnabled && multiVendorEnabled && /* @__PURE__ */ React.createElement("div", {
13100
+ className: "mt-4 space-y-3 p-4 bg-gray-50 rounded-lg border border-gray-200"
13101
+ }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", {
13102
+ className: "text-sm font-medium text-gray-700"
13103
+ }, "Vendor catalog permissions"), /* @__PURE__ */ React.createElement("p", {
13104
+ className: "text-xs text-gray-500 mb-3"
13105
+ }, "When off (default), vendors only pick admin-created categories, collections, and brands on products. When on, vendors also get that Store tab and can create their own.")), /* @__PURE__ */ React.createElement("div", {
13106
+ className: "flex items-center gap-3"
13107
+ }, /* @__PURE__ */ React.createElement(Switch, {
13108
+ checked: vendorCanCreateCategories,
13109
+ onCheckedChange: setVendorCanCreateCategories
13110
+ }), /* @__PURE__ */ React.createElement(Label3, {
13111
+ className: "text-sm font-medium text-gray-700"
13112
+ }, "Can vendors create categories")), /* @__PURE__ */ React.createElement("div", {
13113
+ className: "flex items-center gap-3"
13114
+ }, /* @__PURE__ */ React.createElement(Switch, {
13115
+ checked: vendorCanCreateCollections,
13116
+ onCheckedChange: setVendorCanCreateCollections
13117
+ }), /* @__PURE__ */ React.createElement(Label3, {
13118
+ className: "text-sm font-medium text-gray-700"
13119
+ }, "Can vendors create collections")), /* @__PURE__ */ React.createElement("div", {
13120
+ className: "flex items-center gap-3"
13121
+ }, /* @__PURE__ */ React.createElement(Switch, {
13122
+ checked: vendorCanCreateBrands,
13123
+ onCheckedChange: setVendorCanCreateBrands
13124
+ }), /* @__PURE__ */ React.createElement(Label3, {
13125
+ className: "text-sm font-medium text-gray-700"
13126
+ }, "Can vendors create brands")), /* @__PURE__ */ React.createElement("div", {
13127
+ className: "flex items-start gap-3"
13128
+ }, /* @__PURE__ */ React.createElement(Switch, {
13129
+ checked: requireProductApproval,
13130
+ onCheckedChange: setRequireProductApproval
13131
+ }), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(Label3, {
13132
+ className: "text-sm font-medium text-gray-700"
13133
+ }, "Require product approval"), /* @__PURE__ */ React.createElement("p", {
13134
+ className: "text-xs text-gray-500 mt-0.5"
13135
+ }, "When on, new vendor products start as waiting for approval. Admins approve or reject; only after approval can the vendor set the product to available (live).")))), storeEnabled && /* @__PURE__ */ React.createElement("div", {
13136
+ className: "mt-4 space-y-3 p-4 bg-gray-50 rounded-lg border border-gray-200"
13137
+ }, /* @__PURE__ */ React.createElement("div", {
13138
+ className: "flex items-center gap-3"
12750
13139
  }, /* @__PURE__ */ React.createElement(Switch, {
12751
13140
  checked: eventsEnabled,
12752
13141
  onCheckedChange: setEventsEnabled
@@ -12754,7 +13143,16 @@ function SettingsPage() {
12754
13143
  className: "text-sm font-medium text-gray-700"
12755
13144
  }, "Events"), /* @__PURE__ */ React.createElement("p", {
12756
13145
  className: "text-xs text-gray-500"
12757
- }, "Enable event management. Disabling this hides the Events tab and blocks all event-related APIs."))), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", {
13146
+ }, "Enable event management. Disabling this hides the Events tab and blocks all event-related APIs."))), eventsEnabled && multiVendorEnabled ? /* @__PURE__ */ React.createElement("div", {
13147
+ className: "flex items-start gap-3 pl-1 border-t border-gray-200 pt-3"
13148
+ }, /* @__PURE__ */ React.createElement(Switch, {
13149
+ checked: requireEventApproval,
13150
+ onCheckedChange: setRequireEventApproval
13151
+ }), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(Label3, {
13152
+ className: "text-sm font-medium text-gray-700"
13153
+ }, "Require event approval"), /* @__PURE__ */ React.createElement("p", {
13154
+ className: "text-xs text-gray-500 mt-0.5"
13155
+ }, "When on, new vendor events start as pending. Admins approve or reject; approval activates the event (live)."))) : null), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", {
12758
13156
  className: "flex items-center justify-between mb-1"
12759
13157
  }, /* @__PURE__ */ React.createElement("label", {
12760
13158
  className: "block text-sm font-semibold text-gray-700"
@@ -17024,15 +17422,13 @@ function PluginSettingsPanel({ descriptor, onSaved }) {
17024
17422
  className: "text-xs text-gray-500 dark:text-gray-400"
17025
17423
  }, "Layout below merges with Branding settings; values here override branding when set. Use absolute URLs for logos in email."), /* @__PURE__ */ React.createElement("div", {
17026
17424
  className: "space-y-1"
17027
- }, /* @__PURE__ */ React.createElement(Label3, {
17028
- htmlFor: `${settingsGroup}-logoUrl`,
17029
- className: "text-sm"
17030
- }, "Logo URL (optional override)"), /* @__PURE__ */ React.createElement(Input, {
17031
- id: `${settingsGroup}-logoUrl`,
17425
+ }, /* @__PURE__ */ React.createElement(ImageOrUrlField, {
17426
+ label: "Logo (optional override)",
17032
17427
  value: logoUrl,
17033
- onChange: /* @__PURE__ */ __name((e) => setLogoUrl(e.target.value), "onChange"),
17428
+ onChange: setLogoUrl,
17429
+ previewVariant: "logo",
17034
17430
  placeholder: "https://\u2026",
17035
- className: "h-8 text-sm"
17431
+ inputClassName: "h-8 text-sm w-full rounded-md border border-input bg-background px-3"
17036
17432
  })), /* @__PURE__ */ React.createElement("div", {
17037
17433
  className: "space-y-1"
17038
17434
  }, /* @__PURE__ */ React.createElement(Label3, {
@@ -17085,22 +17481,23 @@ function PluginSettingsPanel({ descriptor, onSaved }) {
17085
17481
  className: "flex flex-wrap items-end gap-2 border-b border-border/60 pb-3 dark:border-gray-600"
17086
17482
  }, /* @__PURE__ */ React.createElement("div", {
17087
17483
  className: "min-w-[160px] flex-1 space-y-1"
17088
- }, /* @__PURE__ */ React.createElement(Label3, {
17089
- className: "text-xs text-muted-foreground"
17090
- }, "Icon image URL"), /* @__PURE__ */ React.createElement(Input, {
17484
+ }, /* @__PURE__ */ React.createElement(ImageOrUrlField, {
17485
+ label: "Icon image",
17091
17486
  value: row.iconUrl,
17092
- onChange: /* @__PURE__ */ __name((e) => {
17487
+ onChange: /* @__PURE__ */ __name((v) => {
17093
17488
  const next = [
17094
17489
  ...socialLinkRows
17095
17490
  ];
17096
17491
  next[i] = {
17097
17492
  ...next[i],
17098
- iconUrl: e.target.value
17493
+ iconUrl: v
17099
17494
  };
17100
17495
  setSocialLinkRows(next);
17101
17496
  }, "onChange"),
17497
+ previewVariant: "logo",
17102
17498
  placeholder: "https://\u2026",
17103
- className: "h-8 text-sm"
17499
+ inputClassName: "h-8 text-sm w-full rounded-md border border-input bg-background px-3",
17500
+ labelClassName: "text-xs text-muted-foreground"
17104
17501
  })), /* @__PURE__ */ React.createElement("div", {
17105
17502
  className: "min-w-[160px] flex-1 space-y-1"
17106
17503
  }, /* @__PURE__ */ React.createElement(Label3, {
@@ -17747,15 +18144,13 @@ function PluginSettingsPanel({ descriptor, onSaved }) {
17747
18144
  className: "h-8 text-sm"
17748
18145
  })), /* @__PURE__ */ React.createElement("div", {
17749
18146
  className: "space-y-1"
17750
- }, /* @__PURE__ */ React.createElement(Label3, {
17751
- htmlFor: `${settingsGroup}-iconImageUrl`,
17752
- className: "text-sm"
17753
- }, "Icon image URL"), /* @__PURE__ */ React.createElement(Input, {
17754
- id: `${settingsGroup}-iconImageUrl`,
18147
+ }, /* @__PURE__ */ React.createElement(ImageOrUrlField, {
18148
+ label: "Icon image",
17755
18149
  value: iconImageUrl,
17756
- onChange: /* @__PURE__ */ __name((e) => setIconImageUrl(e.target.value), "onChange"),
18150
+ onChange: setIconImageUrl,
18151
+ previewVariant: "logo",
17757
18152
  placeholder: "https://\u2026 or /images/chat-icon.png",
17758
- className: "h-8 text-sm"
18153
+ inputClassName: "h-8 text-sm w-full rounded-md border border-input bg-background px-3"
17759
18154
  }), /* @__PURE__ */ React.createElement("p", {
17760
18155
  className: "text-xs text-gray-500 dark:text-gray-400"
17761
18156
  }, "PNG or image URL. Leave empty to use emoji below.")), /* @__PURE__ */ React.createElement("div", {
@@ -18315,6 +18710,7 @@ var init_PluginsPage = __esm({
18315
18710
  init_checkbox();
18316
18711
  init_select();
18317
18712
  init_EventNotificationsPluginSettings();
18713
+ init_ImageOrUrlField();
18318
18714
  init_chat_email_intent();
18319
18715
  init_llm_agent_scope();
18320
18716
  __name(normalizeLinkedInOrganizations, "normalizeLinkedInOrganizations");
@@ -19998,7 +20394,7 @@ var init_VendorPortalProfilePage = __esm({
19998
20394
  __name(VendorPortalProfilePage, "VendorPortalProfilePage");
19999
20395
  }
20000
20396
  });
20001
- function DetailPageHeader({ title, subtitle, backHref, backLabel = "Back", closeHref, onClose, menuItems = [] }) {
20397
+ function DetailPageHeader({ title, subtitle, backHref, backLabel = "Back", closeHref, onClose, headerExtra, menuItems = [] }) {
20002
20398
  const router = navigation.useRouter();
20003
20399
  const handleClose = /* @__PURE__ */ __name(() => {
20004
20400
  if (onClose) onClose();
@@ -20022,7 +20418,7 @@ function DetailPageHeader({ title, subtitle, backHref, backLabel = "Back", close
20022
20418
  className: "text-sm text-gray-400 truncate"
20023
20419
  }, subtitle))), /* @__PURE__ */ React.createElement("div", {
20024
20420
  className: "flex items-center gap-2 shrink-0"
20025
- }, menuItems.length > 0 && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", {
20421
+ }, headerExtra, menuItems.length > 0 && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", {
20026
20422
  className: "flex items-center gap-2 md:hidden"
20027
20423
  }, menuItems.map((item, i) => {
20028
20424
  const Icon2 = item.icon;
@@ -25202,15 +25598,11 @@ function SeoTabContent(props) {
25202
25598
  onChange: /* @__PURE__ */ __name((e) => props.setSeoOgDescription(e.target.value), "onChange"),
25203
25599
  placeholder: "Open Graph description",
25204
25600
  rows: 2
25205
- })), /* @__PURE__ */ React26__namespace.default.createElement("div", {
25206
- className: "space-y-1.5"
25207
- }, /* @__PURE__ */ React26__namespace.default.createElement(Label3, {
25208
- className: "text-xs"
25209
- }, "OG Image"), /* @__PURE__ */ React26__namespace.default.createElement(Input, {
25601
+ })), /* @__PURE__ */ React26__namespace.default.createElement(ImageOrUrlField, {
25602
+ label: "OG Image",
25210
25603
  value: props.seoOgImage,
25211
- onChange: /* @__PURE__ */ __name((e) => props.setSeoOgImage(e.target.value), "onChange"),
25212
- placeholder: "https://..."
25213
- })));
25604
+ onChange: props.setSeoOgImage
25605
+ }));
25214
25606
  }
25215
25607
  function CollapsibleSection({ title, icon: Icon2, open, onToggle, children }) {
25216
25608
  return /* @__PURE__ */ React26__namespace.default.createElement("div", {
@@ -25596,6 +25988,7 @@ var init_PageBuilderPage = __esm({
25596
25988
  init_ComponentSettings();
25597
25989
  init_admin_config_context();
25598
25990
  init_registry();
25991
+ init_ImageOrUrlField();
25599
25992
  __name(createSelectable, "createSelectable");
25600
25993
  __name(buildEditorResolver, "buildEditorResolver");
25601
25994
  __name(getIcon, "getIcon");
@@ -25658,15 +26051,12 @@ function SeoSection({ values, onChange }) {
25658
26051
  onChange: /* @__PURE__ */ __name((e) => onChange("seoOgDescription", e.target.value), "onChange"),
25659
26052
  className: textareaCls,
25660
26053
  rows: 2
25661
- })), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("label", {
25662
- className: "block text-xs font-medium text-gray-600 mb-1"
25663
- }, "OG Image"), /* @__PURE__ */ React.createElement("input", {
25664
- type: "url",
26054
+ })), /* @__PURE__ */ React.createElement(ImageOrUrlField, {
26055
+ label: "OG Image",
25665
26056
  value: values.seoOgImage,
25666
- onChange: /* @__PURE__ */ __name((e) => onChange("seoOgImage", e.target.value), "onChange"),
25667
- placeholder: "https://...",
25668
- className: inputCls9
25669
- }))));
26057
+ onChange: /* @__PURE__ */ __name((v) => onChange("seoOgImage", v), "onChange"),
26058
+ inputClassName: inputCls9
26059
+ })));
25670
26060
  }
25671
26061
  async function saveSeo(seo, slug, existingSeoId) {
25672
26062
  const hasSeo = seo.seoTitle || seo.seoDescription || seo.seoKeywords || seo.seoOgTitle || seo.seoOgDescription || seo.seoOgImage;
@@ -25731,6 +26121,7 @@ async function fetchSeo(seoId) {
25731
26121
  var init_SeoSection = __esm({
25732
26122
  "src/components/Admin/SeoSection.tsx"() {
25733
26123
  "use client";
26124
+ init_ImageOrUrlField();
25734
26125
  __name(SeoSection, "SeoSection");
25735
26126
  __name(saveSeo, "saveSeo");
25736
26127
  __name(fetchSeo, "fetchSeo");
@@ -25745,6 +26136,8 @@ __export(BrandEditPage_exports, {
25745
26136
  function BrandEditPage({ brandId }) {
25746
26137
  const router = navigation.useRouter();
25747
26138
  const searchParams = navigation.useSearchParams();
26139
+ const { data: session } = react.useSession();
26140
+ const vendorPortal = isVendorPortalUser(session?.user);
25748
26141
  const listReturnUrl = safeAdminListReturnUrl(searchParams.get("from")) ?? "/admin/brands";
25749
26142
  const create = isCreate(brandId);
25750
26143
  const dupParam = searchParams.get("duplicateFrom");
@@ -25846,6 +26239,9 @@ function BrandEditPage({ brandId }) {
25846
26239
  active,
25847
26240
  sortOrder
25848
26241
  };
26242
+ if (!vendorPortal) {
26243
+ payload.isCatalog = true;
26244
+ }
25849
26245
  if (savedSeoId) payload.seoId = savedSeoId;
25850
26246
  const res = await fetch(create ? "/api/brands" : `/api/brands/${brandId}`, {
25851
26247
  method: create ? "POST" : "PUT",
@@ -25937,13 +26333,12 @@ function BrandEditPage({ brandId }) {
25937
26333
  onChange: /* @__PURE__ */ __name((e) => setDescription(e.target.value), "onChange"),
25938
26334
  className: "w-full rounded-md border border-gray-300 px-3 py-2 text-sm min-h-[80px]",
25939
26335
  rows: 3
25940
- })), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("label", {
25941
- className: "block text-xs font-medium text-gray-600 mb-1"
25942
- }, "Logo URL"), /* @__PURE__ */ React.createElement("input", {
25943
- type: "url",
26336
+ })), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(ImageOrUrlField, {
26337
+ label: "Logo",
25944
26338
  value: logo,
25945
- onChange: /* @__PURE__ */ __name((e) => setLogo(e.target.value), "onChange"),
25946
- className: "w-full rounded-md border border-gray-300 px-3 py-2 text-sm"
26339
+ onChange: setLogo,
26340
+ previewVariant: "logo",
26341
+ inputClassName: "w-full rounded-md border border-gray-300 px-3 py-2 text-sm"
25947
26342
  })), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("label", {
25948
26343
  className: "block text-xs font-medium text-gray-600 mb-1"
25949
26344
  }, "Sort order"), /* @__PURE__ */ React.createElement("input", {
@@ -25986,6 +26381,8 @@ var init_BrandEditPage = __esm({
25986
26381
  init_SeoSection();
25987
26382
  init_DetailPageLayout();
25988
26383
  init_DetailPageHeader();
26384
+ init_vendor_scope();
26385
+ init_ImageOrUrlField();
25989
26386
  isCreate = /* @__PURE__ */ __name((id) => id === "create", "isCreate");
25990
26387
  __name(BrandEditPage, "BrandEditPage");
25991
26388
  }
@@ -26893,16 +27290,38 @@ function ProductVariantsSection({ hasVariants, onHasVariantsChange, variantOptio
26893
27290
  }, "Reserved"), /* @__PURE__ */ React.createElement("option", {
26894
27291
  value: "sold"
26895
27292
  }, "Sold"))), /* @__PURE__ */ React.createElement("td", {
26896
- className: "px-3 py-2 align-top"
26897
- }, /* @__PURE__ */ React.createElement("textarea", {
26898
- value: row.imageUrlsText,
26899
- onChange: /* @__PURE__ */ __name((e) => setVariantField(i, "imageUrlsText", e.target.value), "onChange"),
26900
- placeholder: "https://\u2026/black-s.jpg\nhttps://\u2026/black-back.jpg",
26901
- className: `${inputCls2} min-h-[72px] min-w-[220px]`,
26902
- rows: 3
26903
- }), /* @__PURE__ */ React.createElement("p", {
26904
- className: "mt-1 text-[11px] text-gray-500"
26905
- }, "One URL per line. First image is used on PDP.")), /* @__PURE__ */ React.createElement("td", {
27293
+ className: "px-3 py-2 align-top min-w-[240px]"
27294
+ }, (() => {
27295
+ const lines = row.imageUrlsText === "" ? [
27296
+ ""
27297
+ ] : row.imageUrlsText.split("\n");
27298
+ return /* @__PURE__ */ React.createElement("div", {
27299
+ className: "space-y-2"
27300
+ }, lines.map((url, ui) => /* @__PURE__ */ React.createElement(ImageOrUrlField, {
27301
+ key: ui,
27302
+ label: ui === 0 ? "Images" : `Image ${ui + 1}`,
27303
+ value: url,
27304
+ onChange: /* @__PURE__ */ __name((v) => {
27305
+ const next = [
27306
+ ...lines
27307
+ ];
27308
+ next[ui] = v;
27309
+ setVariantField(i, "imageUrlsText", next.join("\n"));
27310
+ }, "onChange"),
27311
+ inputClassName: inputCls2
27312
+ })), /* @__PURE__ */ React.createElement("button", {
27313
+ type: "button",
27314
+ onClick: /* @__PURE__ */ __name(() => setVariantField(i, "imageUrlsText", [
27315
+ ...lines,
27316
+ ""
27317
+ ].join("\n")), "onClick"),
27318
+ className: "inline-flex items-center gap-1 rounded border border-gray-300 bg-white px-2 py-1 text-[11px] text-gray-700 hover:bg-gray-50"
27319
+ }, /* @__PURE__ */ React.createElement(LucideIcons.Plus, {
27320
+ className: "h-3 w-3"
27321
+ }), " Add image"), /* @__PURE__ */ React.createElement("p", {
27322
+ className: "text-[11px] text-gray-500"
27323
+ }, "First image is used on PDP."));
27324
+ })()), /* @__PURE__ */ React.createElement("td", {
26906
27325
  className: "px-3 py-2 align-top text-right"
26907
27326
  }, /* @__PURE__ */ React.createElement("button", {
26908
27327
  type: "button",
@@ -26917,6 +27336,7 @@ var init_ProductVariantsSection = __esm({
26917
27336
  "src/admin/pages/ProductVariantsSection.tsx"() {
26918
27337
  "use client";
26919
27338
  init_product_variants();
27339
+ init_ImageOrUrlField();
26920
27340
  labelCls2 = "block text-xs font-medium text-gray-600 mb-1";
26921
27341
  inputCls2 = "w-full rounded-md border border-gray-300 px-2 py-1.5 text-sm";
26922
27342
  sectionCls2 = "min-w-0 overflow-hidden border border-gray-200 rounded-lg p-4 bg-gray-50/50";
@@ -27112,13 +27532,16 @@ function ProductEditPage({ productId }) {
27112
27532
  const router = navigation.useRouter();
27113
27533
  const searchParams = navigation.useSearchParams();
27114
27534
  const { data: session } = react.useSession();
27535
+ const { eventsEnabled, requireProductApproval } = React26.useContext(exports.AdminConfigContext);
27536
+ const eventsOn = eventsEnabled !== false;
27537
+ const approvalOn = requireProductApproval === true;
27115
27538
  const vendorPortal = isVendorPortalUser(session?.user);
27116
27539
  const categoryIdParam = searchParams.get("categoryId")?.trim() ?? "";
27117
27540
  const collectionIdParam = searchParams.get("collectionId")?.trim() ?? "";
27118
27541
  const eventIdParam = searchParams.get("eventId")?.trim() ?? "";
27119
27542
  const lockCategoryFromStore = isCreate2(productId) && /^\d+$/.test(categoryIdParam);
27120
27543
  const lockCollectionFromQuery = isCreate2(productId) && /^\d+$/.test(collectionIdParam);
27121
- const lockEventFromQuery = isCreate2(productId) && /^\d+$/.test(eventIdParam);
27544
+ const lockEventFromQuery = eventsOn && isCreate2(productId) && /^\d+$/.test(eventIdParam);
27122
27545
  const listReturnUrl = safeAdminListReturnUrl(searchParams.get("from")) ?? (lockCategoryFromStore ? `/admin/products?categoryId=${categoryIdParam}` : "/admin/products");
27123
27546
  const create = isCreate2(productId);
27124
27547
  const dupParam = searchParams.get("duplicateFrom");
@@ -27149,6 +27572,11 @@ function ProductEditPage({ productId }) {
27149
27572
  const [compareAtPrice, setCompareAtPrice] = React26.useState(0);
27150
27573
  const [quantity, setQuantity] = React26.useState(1);
27151
27574
  const [status, setStatus] = React26.useState("draft");
27575
+ const [approvalStatus, setApprovalStatus] = React26.useState("pending");
27576
+ const [rejectionReason, setRejectionReason] = React26.useState("");
27577
+ const [rejectModalOpen, setRejectModalOpen] = React26.useState(false);
27578
+ const [rejectDraft, setRejectDraft] = React26.useState("");
27579
+ const approvalBeforeRejectRef = React26.useRef("pending");
27152
27580
  const [featured, setFeatured] = React26.useState(false);
27153
27581
  const [description, setDescription] = React26.useState("");
27154
27582
  const [images, setImages] = React26.useState([
@@ -27253,7 +27681,7 @@ function ProductEditPage({ productId }) {
27253
27681
  try {
27254
27682
  const [brandRes, catRes, attrRes, taxesRes, eventsRes, formsRes, refundRes] = await Promise.all([
27255
27683
  fetch("/api/brands?limit=500"),
27256
- fetch("/api/product_categories?limit=500&isCatalog=true"),
27684
+ fetch(vendorPortal ? "/api/product_categories?limit=500" : "/api/product_categories?limit=500&isCatalog=true"),
27257
27685
  fetch("/api/attributes?limit=500"),
27258
27686
  fetch("/api/taxes?limit=200&sortField=name&sortOrder=asc"),
27259
27687
  fetch("/api/events?limit=200&sortField=startDate&sortOrder=desc"),
@@ -27366,6 +27794,8 @@ function ProductEditPage({ productId }) {
27366
27794
  setCompareAtPrice(Number.isFinite(rawCompare) ? rawCompare : 0);
27367
27795
  setQuantity(product.quantity ?? 1);
27368
27796
  setStatus(product.status ?? "draft");
27797
+ setApprovalStatus(typeof product.approvalStatus === "string" && product.approvalStatus ? product.approvalStatus : "pending");
27798
+ setRejectionReason(typeof product.rejectionReason === "string" ? product.rejectionReason : "");
27369
27799
  setFeatured(product.featured ?? false);
27370
27800
  setDescription((m && typeof m.description === "string" ? m.description : "") ?? "");
27371
27801
  const rawImages = m?.images;
@@ -27501,6 +27931,16 @@ function ProductEditPage({ productId }) {
27501
27931
  create,
27502
27932
  eventId
27503
27933
  ]);
27934
+ React26.useEffect(() => {
27935
+ if (create && vendorPortal && approvalOn) {
27936
+ setApprovalStatus("pending");
27937
+ setStatus((s) => s === "available" ? "draft" : s);
27938
+ }
27939
+ }, [
27940
+ create,
27941
+ vendorPortal,
27942
+ approvalOn
27943
+ ]);
27504
27944
  React26.useEffect(() => {
27505
27945
  if (!create || !name.trim()) return;
27506
27946
  setProductSlug(slugifyProductName(name));
@@ -27535,6 +27975,33 @@ function ProductEditPage({ productId }) {
27535
27975
  }
27536
27976
  setter(num);
27537
27977
  }, "handleNumberChange");
27978
+ const openRejectModal = /* @__PURE__ */ __name((fromStatus) => {
27979
+ approvalBeforeRejectRef.current = fromStatus === "rejected" ? "pending" : fromStatus;
27980
+ setRejectDraft(rejectionReason);
27981
+ setApprovalStatus("rejected");
27982
+ setRejectModalOpen(true);
27983
+ }, "openRejectModal");
27984
+ const confirmRejectReason = /* @__PURE__ */ __name(() => {
27985
+ const reason = rejectDraft.trim();
27986
+ if (!reason) return;
27987
+ setRejectionReason(reason);
27988
+ setRejectModalOpen(false);
27989
+ }, "confirmRejectReason");
27990
+ const cancelRejectModal = /* @__PURE__ */ __name(() => {
27991
+ if (!rejectionReason.trim()) {
27992
+ setApprovalStatus(approvalBeforeRejectRef.current || "pending");
27993
+ }
27994
+ setRejectDraft(rejectionReason);
27995
+ setRejectModalOpen(false);
27996
+ }, "cancelRejectModal");
27997
+ const handleApprovalSelect = /* @__PURE__ */ __name((value) => {
27998
+ if (value === "rejected") {
27999
+ openRejectModal(approvalStatus);
28000
+ return;
28001
+ }
28002
+ setApprovalStatus(value);
28003
+ if (value !== "rejected") setRejectionReason("");
28004
+ }, "handleApprovalSelect");
27538
28005
  const handleSave = /* @__PURE__ */ __name(async () => {
27539
28006
  setErrors([]);
27540
28007
  if (!name.trim()) {
@@ -27543,6 +28010,14 @@ function ProductEditPage({ productId }) {
27543
28010
  ]);
27544
28011
  return;
27545
28012
  }
28013
+ if (approvalOn && !vendorPortal && approvalStatus === "rejected" && !rejectionReason.trim()) {
28014
+ setRejectDraft("");
28015
+ setRejectModalOpen(true);
28016
+ setErrors([
28017
+ "Rejection reason is required"
28018
+ ]);
28019
+ return;
28020
+ }
27546
28021
  if (!defaultPriceStr.trim()) {
27547
28022
  setErrors([
27548
28023
  `${pricingConfig.defaultCurrency} price is required`
@@ -27605,11 +28080,21 @@ function ProductEditPage({ productId }) {
27605
28080
  currencyPrices: null,
27606
28081
  compareAtPrice: compareAtPriceValue,
27607
28082
  quantity: resolvedQuantity,
27608
- status,
28083
+ status: create && vendorPortal && approvalOn && status === "available" ? "draft" : status,
27609
28084
  featured,
27610
28085
  contactFormId,
27611
28086
  metadata
27612
28087
  };
28088
+ if (approvalOn) {
28089
+ if (vendorPortal && create) {
28090
+ productPayload.approvalStatus = "pending";
28091
+ } else if (!vendorPortal) {
28092
+ productPayload.approvalStatus = approvalStatus;
28093
+ if (approvalStatus === "rejected") {
28094
+ productPayload.rejectionReason = rejectionReason.trim();
28095
+ }
28096
+ }
28097
+ }
27613
28098
  const res = await fetch(create ? "/api/products" : `/api/products/${productId}`, {
27614
28099
  method: create ? "POST" : "PUT",
27615
28100
  headers: {
@@ -27631,6 +28116,15 @@ function ProductEditPage({ productId }) {
27631
28116
  if (typeof savedProduct.slug === "string") {
27632
28117
  setProductSlug(savedProduct.slug);
27633
28118
  }
28119
+ if (typeof savedProduct.status === "string") {
28120
+ setStatus(savedProduct.status);
28121
+ }
28122
+ if (typeof savedProduct.approvalStatus === "string") {
28123
+ setApprovalStatus(savedProduct.approvalStatus);
28124
+ }
28125
+ if (approvalStatus === "approved") {
28126
+ setRejectionReason("");
28127
+ }
27634
28128
  const savedId = create ? savedProduct.id : productId;
27635
28129
  const savedSeoId = await saveSeo(seo, productSlugValue, seoId);
27636
28130
  const linkedSeoId = savedSeoId ?? savedProduct.seoId ?? null;
@@ -27831,7 +28325,7 @@ function ProductEditPage({ productId }) {
27831
28325
  }
27832
28326
  setVariantRows([]);
27833
28327
  }
27834
- if (create && eventId != null) {
28328
+ if (create && eventsOn && eventId != null) {
27835
28329
  const attachRes = await fetch("/api/event_products", {
27836
28330
  method: "POST",
27837
28331
  headers: {
@@ -27936,6 +28430,27 @@ function ProductEditPage({ productId }) {
27936
28430
  title: pageTitle,
27937
28431
  subtitle: pageSubtitle,
27938
28432
  closeHref: listReturnUrl,
28433
+ headerExtra: approvalOn && !vendorPortal ? /* @__PURE__ */ React.createElement("div", {
28434
+ className: "flex items-center gap-2"
28435
+ }, /* @__PURE__ */ React.createElement("select", {
28436
+ value: approvalStatus,
28437
+ onChange: /* @__PURE__ */ __name((e) => handleApprovalSelect(e.target.value), "onChange"),
28438
+ className: "h-8 rounded-md border border-gray-600 bg-gray-900 text-white text-xs px-2 max-w-[10rem]",
28439
+ "aria-label": "Approval status"
28440
+ }, /* @__PURE__ */ React.createElement("option", {
28441
+ value: "pending"
28442
+ }, "Pending"), /* @__PURE__ */ React.createElement("option", {
28443
+ value: "approved"
28444
+ }, "Approve"), /* @__PURE__ */ React.createElement("option", {
28445
+ value: "rejected"
28446
+ }, "Reject")), approvalStatus === "rejected" ? /* @__PURE__ */ React.createElement("button", {
28447
+ type: "button",
28448
+ onClick: /* @__PURE__ */ __name(() => openRejectModal("rejected"), "onClick"),
28449
+ className: "text-xs text-amber-300 hover:text-amber-200 underline underline-offset-2 max-w-[9rem] truncate",
28450
+ title: rejectionReason || "Add rejection reason"
28451
+ }, rejectionReason.trim() ? "Edit reason" : "Add reason") : null) : approvalOn && vendorPortal ? /* @__PURE__ */ React.createElement("span", {
28452
+ className: "text-xs text-gray-300 capitalize hidden sm:inline"
28453
+ }, approvalStatus.replace(/_/g, " ")) : null,
27939
28454
  menuItems: [
27940
28455
  {
27941
28456
  label: saving ? "Saving..." : "Save",
@@ -27948,7 +28463,31 @@ function ProductEditPage({ productId }) {
27948
28463
  onClick: /* @__PURE__ */ __name(() => setFeatured(!featured), "onClick")
27949
28464
  }
27950
28465
  ]
27951
- }), errors.length > 0 && /* @__PURE__ */ React.createElement("div", {
28466
+ }), /* @__PURE__ */ React.createElement(Dialog, {
28467
+ open: rejectModalOpen,
28468
+ onOpenChange: /* @__PURE__ */ __name((open) => {
28469
+ if (!open) cancelRejectModal();
28470
+ }, "onOpenChange")
28471
+ }, /* @__PURE__ */ React.createElement(DialogContent, {
28472
+ className: "max-w-md"
28473
+ }, /* @__PURE__ */ React.createElement(DialogHeader, null, /* @__PURE__ */ React.createElement(DialogTitle, null, "Rejection reason"), /* @__PURE__ */ React.createElement(DialogDescription, null, "Explain what the vendor needs to fix. This is required before you can save a rejected product.")), /* @__PURE__ */ React.createElement("textarea", {
28474
+ value: rejectDraft,
28475
+ onChange: /* @__PURE__ */ __name((e) => setRejectDraft(e.target.value), "onChange"),
28476
+ className: "w-full min-h-[100px] rounded-md border border-gray-300 px-3 py-2 text-sm",
28477
+ placeholder: "Explain what needs to change\u2026",
28478
+ autoFocus: true
28479
+ }), /* @__PURE__ */ React.createElement(DialogFooter, {
28480
+ className: "gap-2 sm:gap-0"
28481
+ }, /* @__PURE__ */ React.createElement(Button, {
28482
+ type: "button",
28483
+ variant: "outline",
28484
+ onClick: cancelRejectModal
28485
+ }, "Cancel"), /* @__PURE__ */ React.createElement(Button, {
28486
+ type: "button",
28487
+ variant: "destructive",
28488
+ disabled: !rejectDraft.trim(),
28489
+ onClick: confirmRejectReason
28490
+ }, "Confirm reject")))), errors.length > 0 && /* @__PURE__ */ React.createElement("div", {
27952
28491
  className: "bg-red-50 border-l-4 border-red-400 p-4 mx-6 mt-4"
27953
28492
  }, /* @__PURE__ */ React.createElement("div", {
27954
28493
  className: "flex"
@@ -28071,7 +28610,7 @@ function ProductEditPage({ productId }) {
28071
28610
  className: "mt-1 text-xs text-gray-500"
28072
28611
  }, "Only collections in the selected category are listed.") : /* @__PURE__ */ React.createElement("p", {
28073
28612
  className: "mt-1 text-xs text-gray-500"
28074
- }, "Select a category to load collections.")), create ? /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("label", {
28613
+ }, "Select a category to load collections.")), create && eventsOn ? /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("label", {
28075
28614
  className: labelCls3
28076
28615
  }, "Event"), /* @__PURE__ */ React.createElement("select", {
28077
28616
  value: eventId ?? "",
@@ -28167,7 +28706,8 @@ function ProductEditPage({ productId }) {
28167
28706
  }, "Status"), /* @__PURE__ */ React.createElement("select", {
28168
28707
  value: status,
28169
28708
  onChange: /* @__PURE__ */ __name((e) => setStatus(e.target.value), "onChange"),
28170
- className: inputCls3
28709
+ className: inputCls3,
28710
+ disabled: approvalOn && vendorPortal && (approvalStatus === "pending" || approvalStatus === "rejected")
28171
28711
  }, /* @__PURE__ */ React.createElement("option", {
28172
28712
  value: "draft"
28173
28713
  }, "Draft"), /* @__PURE__ */ React.createElement("option", {
@@ -28176,7 +28716,11 @@ function ProductEditPage({ productId }) {
28176
28716
  value: "reserved"
28177
28717
  }, "Reserved"), /* @__PURE__ */ React.createElement("option", {
28178
28718
  value: "sold"
28179
- }, "Sold")))))), /* @__PURE__ */ React.createElement(ProductVariantsSection, {
28719
+ }, "Sold")), approvalOn && vendorPortal && approvalStatus === "pending" ? /* @__PURE__ */ React.createElement("p", {
28720
+ className: "mt-1 text-xs text-gray-500"
28721
+ }, "Waiting for admin approval. The product goes live when approved.") : null, approvalOn && vendorPortal && approvalStatus === "rejected" ? /* @__PURE__ */ React.createElement("p", {
28722
+ className: "mt-1 text-xs text-red-600"
28723
+ }, "Rejected", rejectionReason ? `: ${rejectionReason}` : "", ".") : null)))), /* @__PURE__ */ React.createElement(ProductVariantsSection, {
28180
28724
  hasVariants,
28181
28725
  onHasVariantsChange: setHasVariants,
28182
28726
  variantOptionRows,
@@ -28303,14 +28847,11 @@ function ProductEditPage({ productId }) {
28303
28847
  className: "flex flex-wrap items-start gap-2 p-2 bg-white rounded border border-gray-200"
28304
28848
  }, /* @__PURE__ */ React.createElement("div", {
28305
28849
  className: "flex-1 min-w-[200px]"
28306
- }, /* @__PURE__ */ React.createElement("label", {
28307
- className: labelCls3
28308
- }, "Image URL"), /* @__PURE__ */ React.createElement("input", {
28309
- type: "url",
28850
+ }, /* @__PURE__ */ React.createElement(ImageOrUrlField, {
28851
+ label: "Image",
28310
28852
  value: row.url,
28311
- onChange: /* @__PURE__ */ __name((e) => setImage(i, "url", e.target.value), "onChange"),
28312
- className: inputCls3,
28313
- placeholder: "https://..."
28853
+ onChange: /* @__PURE__ */ __name((v) => setImage(i, "url", v), "onChange"),
28854
+ inputClassName: inputCls3
28314
28855
  })), /* @__PURE__ */ React.createElement("div", {
28315
28856
  className: "flex-1 min-w-[120px]"
28316
28857
  }, /* @__PURE__ */ React.createElement("label", {
@@ -28389,6 +28930,8 @@ var init_ProductEditPage = __esm({
28389
28930
  "use client";
28390
28931
  init_vendor_scope();
28391
28932
  init_admin_list_return_url();
28933
+ init_dialog();
28934
+ init_button();
28392
28935
  init_SeoSection();
28393
28936
  init_DetailPageLayout();
28394
28937
  init_DetailPageHeader();
@@ -28399,6 +28942,8 @@ var init_ProductEditPage = __esm({
28399
28942
  init_inventory_validation();
28400
28943
  init_category_item_label();
28401
28944
  init_use_category_collections();
28945
+ init_ImageOrUrlField();
28946
+ init_admin_config_context();
28402
28947
  init_ProductVariantsSection();
28403
28948
  init_product_variants();
28404
28949
  __name(parseCategoryIdFromReturnUrl, "parseCategoryIdFromReturnUrl");
@@ -28668,12 +29213,6 @@ function CollectionEditPage({ collectionId }) {
28668
29213
  ]);
28669
29214
  return;
28670
29215
  }
28671
- if (create && !categoryId) {
28672
- setErrors([
28673
- "Category is required"
28674
- ]);
28675
- return;
28676
- }
28677
29216
  setSaving(true);
28678
29217
  try {
28679
29218
  const savedSeoId = await saveSeo(seo, slug.trim(), seoId);
@@ -28894,14 +29433,11 @@ function CollectionEditPage({ collectionId }) {
28894
29433
  onChange: /* @__PURE__ */ __name((e) => setDescription(e.target.value), "onChange"),
28895
29434
  className: `${inputCls4} min-h-[80px]`,
28896
29435
  rows: 3
28897
- })), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("label", {
28898
- className: labelCls4
28899
- }, "Cover image URL"), /* @__PURE__ */ React.createElement("input", {
28900
- type: "url",
29436
+ })), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(ImageOrUrlField, {
29437
+ label: "Cover image",
28901
29438
  value: image,
28902
- onChange: /* @__PURE__ */ __name((e) => setImage(e.target.value), "onChange"),
28903
- className: inputCls4,
28904
- placeholder: "https://..."
29439
+ onChange: setImage,
29440
+ inputClassName: inputCls4
28905
29441
  })), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("label", {
28906
29442
  className: labelCls4
28907
29443
  }, "Sort order"), /* @__PURE__ */ React.createElement("input", {
@@ -28928,17 +29464,24 @@ function CollectionEditPage({ collectionId }) {
28928
29464
  className: "text-xs font-medium text-gray-700 mb-2"
28929
29465
  }, "Hero carousel"), heroSlides.map((slide, i) => /* @__PURE__ */ React.createElement("div", {
28930
29466
  key: i,
28931
- className: "flex flex-wrap gap-2 mb-3 p-2 bg-white rounded border"
28932
- }, /* @__PURE__ */ React.createElement("input", {
29467
+ className: "flex flex-wrap gap-2 mb-3 p-2 bg-white rounded border items-start"
29468
+ }, slide.type === "image" ? /* @__PURE__ */ React.createElement("div", {
29469
+ className: "flex-1 min-w-[200px]"
29470
+ }, /* @__PURE__ */ React.createElement(ImageOrUrlField, {
29471
+ label: "Slide image",
29472
+ value: slide.url,
29473
+ onChange: /* @__PURE__ */ __name((v) => updateHeroSlide(i, "url", v), "onChange"),
29474
+ inputClassName: inputCls4
29475
+ })) : /* @__PURE__ */ React.createElement("input", {
28933
29476
  type: "url",
28934
29477
  value: slide.url,
28935
29478
  onChange: /* @__PURE__ */ __name((e) => updateHeroSlide(i, "url", e.target.value), "onChange"),
28936
- placeholder: "Media URL",
29479
+ placeholder: "Video URL",
28937
29480
  className: `${inputCls4} flex-1 min-w-[200px]`
28938
29481
  }), /* @__PURE__ */ React.createElement("select", {
28939
29482
  value: slide.type,
28940
29483
  onChange: /* @__PURE__ */ __name((e) => updateHeroSlide(i, "type", e.target.value), "onChange"),
28941
- className: `${inputCls4} w-24`
29484
+ className: `${inputCls4} w-24 mt-6`
28942
29485
  }, /* @__PURE__ */ React.createElement("option", {
28943
29486
  value: "image"
28944
29487
  }, "Image"), /* @__PURE__ */ React.createElement("option", {
@@ -28948,11 +29491,11 @@ function CollectionEditPage({ collectionId }) {
28948
29491
  value: slide.caption,
28949
29492
  onChange: /* @__PURE__ */ __name((e) => updateHeroSlide(i, "caption", e.target.value), "onChange"),
28950
29493
  placeholder: "Caption",
28951
- className: `${inputCls4} flex-1 min-w-[120px]`
29494
+ className: `${inputCls4} flex-1 min-w-[120px] mt-6`
28952
29495
  }), /* @__PURE__ */ React.createElement("button", {
28953
29496
  type: "button",
28954
29497
  onClick: /* @__PURE__ */ __name(() => removeHeroSlide(i), "onClick"),
28955
- className: "p-2 text-gray-400 hover:text-red-600 rounded shrink-0"
29498
+ className: "p-2 text-gray-400 hover:text-red-600 rounded shrink-0 mt-6"
28956
29499
  }, /* @__PURE__ */ React.createElement(LucideIcons.Trash2, {
28957
29500
  className: "h-4 w-4"
28958
29501
  })))), /* @__PURE__ */ React.createElement("button", {
@@ -29079,6 +29622,7 @@ var init_CollectionEditPage = __esm({
29079
29622
  init_use_catalog_categories();
29080
29623
  init_admin_config_context();
29081
29624
  init_category_related_product_labels();
29625
+ init_ImageOrUrlField();
29082
29626
  isCreate3 = /* @__PURE__ */ __name((id) => id === "create", "isCreate");
29083
29627
  emptySlide = /* @__PURE__ */ __name(() => ({
29084
29628
  url: "",
@@ -29096,116 +29640,6 @@ var init_CollectionEditPage = __esm({
29096
29640
  __name(CollectionEditPage, "CollectionEditPage");
29097
29641
  }
29098
29642
  });
29099
- function ImageOrUrlField({ label, value, onChange, placeholder = "https://\u2026", inputClassName = "w-full rounded-md border border-gray-300 px-2 py-1.5 text-sm", labelClassName = "block text-xs font-medium text-gray-600 mb-1", previewVariant = "banner", maxSizeMb = 10 }) {
29100
- const fileInputRef = React26.useRef(null);
29101
- const [isUploading, setIsUploading] = React26.useState(false);
29102
- const [error, setError] = React26.useState(null);
29103
- const previewCls = previewVariant === "logo" ? "h-20 w-20 rounded-md border border-gray-200 bg-white object-contain" : "max-h-32 w-full rounded-md border border-gray-200 bg-gray-50 object-cover";
29104
- const handleUpload = React26.useCallback(async (file) => {
29105
- setError(null);
29106
- if (!ACCEPTED_TYPES.includes(file.type)) {
29107
- setError(`Unsupported file type. Use: ${ACCEPTED_TYPES.map((t) => t.replace("image/", "")).join(", ")}`);
29108
- return;
29109
- }
29110
- if (file.size > maxSizeMb * 1024 * 1024) {
29111
- setError(`File must be under ${maxSizeMb}MB`);
29112
- return;
29113
- }
29114
- setIsUploading(true);
29115
- try {
29116
- const formData = new FormData();
29117
- formData.append("file", file);
29118
- const response = await fetch("/api/upload", {
29119
- method: "POST",
29120
- body: formData
29121
- });
29122
- const data = await response.json();
29123
- if (!response.ok) {
29124
- throw new Error(data.error || data.details || "Upload failed");
29125
- }
29126
- onChange(data.filePath ?? "");
29127
- } catch (err) {
29128
- setError(err instanceof Error ? err.message : "Upload failed");
29129
- } finally {
29130
- setIsUploading(false);
29131
- if (fileInputRef.current) fileInputRef.current.value = "";
29132
- }
29133
- }, [
29134
- maxSizeMb,
29135
- onChange
29136
- ]);
29137
- const onFileChange = React26.useCallback((e) => {
29138
- const file = e.target.files?.[0];
29139
- if (file) void handleUpload(file);
29140
- }, [
29141
- handleUpload
29142
- ]);
29143
- const trimmed = value.trim();
29144
- return /* @__PURE__ */ React.createElement("div", {
29145
- className: "space-y-2"
29146
- }, /* @__PURE__ */ React.createElement("label", {
29147
- className: labelClassName
29148
- }, label), trimmed ? /* @__PURE__ */ React.createElement("div", {
29149
- className: "flex items-start gap-3"
29150
- }, /* @__PURE__ */ React.createElement("img", {
29151
- src: trimmed,
29152
- alt: label,
29153
- className: previewCls,
29154
- onError: /* @__PURE__ */ __name((e) => {
29155
- e.currentTarget.style.display = "none";
29156
- }, "onError")
29157
- }), /* @__PURE__ */ React.createElement("button", {
29158
- type: "button",
29159
- onClick: /* @__PURE__ */ __name(() => onChange(""), "onClick"),
29160
- className: "inline-flex items-center gap-1 rounded border border-gray-300 bg-white px-2 py-1 text-xs text-gray-600 hover:bg-gray-50"
29161
- }, /* @__PURE__ */ React.createElement(LucideIcons.X, {
29162
- className: "h-3 w-3"
29163
- }), "Clear")) : null, /* @__PURE__ */ React.createElement("div", {
29164
- className: "flex flex-wrap gap-2"
29165
- }, /* @__PURE__ */ React.createElement("input", {
29166
- type: "url",
29167
- value,
29168
- onChange: /* @__PURE__ */ __name((e) => {
29169
- setError(null);
29170
- onChange(e.target.value);
29171
- }, "onChange"),
29172
- placeholder,
29173
- className: `${inputClassName} min-w-0 flex-1`
29174
- }), /* @__PURE__ */ React.createElement("input", {
29175
- ref: fileInputRef,
29176
- type: "file",
29177
- accept: ACCEPTED_TYPES.join(","),
29178
- onChange: onFileChange,
29179
- className: "hidden",
29180
- disabled: isUploading
29181
- }), /* @__PURE__ */ React.createElement("button", {
29182
- type: "button",
29183
- onClick: /* @__PURE__ */ __name(() => fileInputRef.current?.click(), "onClick"),
29184
- disabled: isUploading,
29185
- className: "inline-flex shrink-0 items-center gap-1.5 rounded-md border border-gray-300 bg-white px-3 py-1.5 text-sm font-medium text-gray-700 hover:bg-gray-50 disabled:opacity-50"
29186
- }, /* @__PURE__ */ React.createElement(LucideIcons.Upload, {
29187
- className: "h-3.5 w-3.5"
29188
- }), isUploading ? "Uploading\u2026" : "Upload")), error ? /* @__PURE__ */ React.createElement("p", {
29189
- className: "flex items-center gap-1.5 text-xs text-red-600"
29190
- }, /* @__PURE__ */ React.createElement(LucideIcons.AlertCircle, {
29191
- className: "h-3.5 w-3.5 shrink-0"
29192
- }), error) : /* @__PURE__ */ React.createElement("p", {
29193
- className: "text-xs text-gray-500"
29194
- }, "Paste a URL or upload an image (PNG, JPG, GIF, WEBP up to ", maxSizeMb, "MB)"));
29195
- }
29196
- var ACCEPTED_TYPES;
29197
- var init_ImageOrUrlField = __esm({
29198
- "src/components/Admin/ImageOrUrlField.tsx"() {
29199
- "use client";
29200
- ACCEPTED_TYPES = [
29201
- "image/jpeg",
29202
- "image/png",
29203
- "image/gif",
29204
- "image/webp"
29205
- ];
29206
- __name(ImageOrUrlField, "ImageOrUrlField");
29207
- }
29208
- });
29209
29643
  function AttachProductModal({ open, onOpenChange, categoryId, categoryName, excludeProductIds = [], onAttach }) {
29210
29644
  const [attachingId, setAttachingId] = React26.useState(null);
29211
29645
  const [error, setError] = React26.useState(null);
@@ -30150,6 +30584,10 @@ function combineCityCountry(city, country) {
30150
30584
  function EventEditPage({ eventId }) {
30151
30585
  const router = navigation.useRouter();
30152
30586
  const searchParams = navigation.useSearchParams();
30587
+ const { data: session } = react.useSession();
30588
+ const { requireEventApproval } = React26.useContext(exports.AdminConfigContext);
30589
+ const approvalOn = requireEventApproval === true;
30590
+ const vendorPortal = isVendorPortalUser(session?.user);
30153
30591
  const listReturnUrl = safeAdminListReturnUrl(searchParams.get("from")) ?? "/admin/events";
30154
30592
  const create = isCreate4(eventId);
30155
30593
  const dupParam = searchParams.get("duplicateFrom");
@@ -30162,6 +30600,11 @@ function EventEditPage({ eventId }) {
30162
30600
  const [slug, setSlug] = React26.useState("");
30163
30601
  const [description, setDescription] = React26.useState("");
30164
30602
  const [isActive, setIsActive] = React26.useState(true);
30603
+ const [approvalStatus, setApprovalStatus] = React26.useState("pending");
30604
+ const [rejectionReason, setRejectionReason] = React26.useState("");
30605
+ const [rejectModalOpen, setRejectModalOpen] = React26.useState(false);
30606
+ const [rejectDraft, setRejectDraft] = React26.useState("");
30607
+ const approvalBeforeRejectRef = React26.useRef("pending");
30165
30608
  const [comingSoon, setComingSoon] = React26.useState(false);
30166
30609
  const [bannerImageUrl, setBannerImageUrl] = React26.useState("");
30167
30610
  const [logoUrl, setLogoUrl] = React26.useState("");
@@ -30247,6 +30690,16 @@ function EventEditPage({ eventId }) {
30247
30690
  cancelled = true;
30248
30691
  };
30249
30692
  }, []);
30693
+ React26.useEffect(() => {
30694
+ if (create && vendorPortal && approvalOn) {
30695
+ setApprovalStatus("pending");
30696
+ setIsActive(false);
30697
+ }
30698
+ }, [
30699
+ create,
30700
+ vendorPortal,
30701
+ approvalOn
30702
+ ]);
30250
30703
  React26.useEffect(() => {
30251
30704
  let cancelled = false;
30252
30705
  (async () => {
@@ -30268,6 +30721,8 @@ function EventEditPage({ eventId }) {
30268
30721
  setSlug(data.slug ?? "");
30269
30722
  setDescription(data.description ?? "");
30270
30723
  setIsActive(data.isActive ?? true);
30724
+ setApprovalStatus(typeof data.approvalStatus === "string" && data.approvalStatus ? data.approvalStatus : "pending");
30725
+ setRejectionReason(typeof data.rejectionReason === "string" ? data.rejectionReason : "");
30271
30726
  setComingSoon(data.comingSoon ?? false);
30272
30727
  setBannerImageUrl(data.bannerImageUrl ?? "");
30273
30728
  setLogoUrl(data.logoUrl ?? "");
@@ -30379,11 +30834,11 @@ function EventEditPage({ eventId }) {
30379
30834
  setErrors(nextErrors);
30380
30835
  return null;
30381
30836
  }
30382
- return {
30837
+ const payload = {
30383
30838
  name: name.trim(),
30384
30839
  slug: slug.trim(),
30385
30840
  description: description.trim() || null,
30386
- isActive,
30841
+ isActive: create && vendorPortal && approvalOn ? false : isActive,
30387
30842
  comingSoon,
30388
30843
  bannerImageUrl: bannerImageUrl.trim() || null,
30389
30844
  logoUrl: logoUrl.trim() || null,
@@ -30421,9 +30876,55 @@ function EventEditPage({ eventId }) {
30421
30876
  sortOrder,
30422
30877
  contactFormId
30423
30878
  };
30879
+ if (approvalOn) {
30880
+ if (vendorPortal && create) {
30881
+ payload.approvalStatus = "pending";
30882
+ } else if (!vendorPortal) {
30883
+ payload.approvalStatus = approvalStatus;
30884
+ if (approvalStatus === "rejected") {
30885
+ payload.rejectionReason = rejectionReason.trim();
30886
+ }
30887
+ }
30888
+ }
30889
+ return payload;
30424
30890
  }, "buildPayload");
30891
+ const openRejectModal = /* @__PURE__ */ __name((fromStatus) => {
30892
+ approvalBeforeRejectRef.current = fromStatus === "rejected" ? "pending" : fromStatus;
30893
+ setRejectDraft(rejectionReason);
30894
+ setApprovalStatus("rejected");
30895
+ setRejectModalOpen(true);
30896
+ }, "openRejectModal");
30897
+ const confirmRejectReason = /* @__PURE__ */ __name(() => {
30898
+ const reason = rejectDraft.trim();
30899
+ if (!reason) return;
30900
+ setRejectionReason(reason);
30901
+ setRejectModalOpen(false);
30902
+ }, "confirmRejectReason");
30903
+ const cancelRejectModal = /* @__PURE__ */ __name(() => {
30904
+ if (!rejectionReason.trim()) {
30905
+ setApprovalStatus(approvalBeforeRejectRef.current || "pending");
30906
+ }
30907
+ setRejectDraft(rejectionReason);
30908
+ setRejectModalOpen(false);
30909
+ }, "cancelRejectModal");
30910
+ const handleApprovalSelect = /* @__PURE__ */ __name((value) => {
30911
+ if (value === "rejected") {
30912
+ openRejectModal(approvalStatus);
30913
+ return;
30914
+ }
30915
+ setApprovalStatus(value);
30916
+ if (value !== "rejected") setRejectionReason("");
30917
+ }, "handleApprovalSelect");
30425
30918
  const handleSave = /* @__PURE__ */ __name(async () => {
30426
30919
  setErrors([]);
30920
+ if (approvalOn && !vendorPortal && approvalStatus === "rejected" && !rejectionReason.trim()) {
30921
+ setRejectDraft("");
30922
+ setRejectModalOpen(true);
30923
+ setErrors([
30924
+ "Rejection reason is required"
30925
+ ]);
30926
+ return;
30927
+ }
30427
30928
  const payload = buildPayload();
30428
30929
  if (!payload) return;
30429
30930
  setSaving(true);
@@ -30443,6 +30944,9 @@ function EventEditPage({ eventId }) {
30443
30944
  return;
30444
30945
  }
30445
30946
  const saved = await res.json();
30947
+ if (typeof saved.isActive === "boolean") setIsActive(saved.isActive);
30948
+ if (typeof saved.approvalStatus === "string") setApprovalStatus(saved.approvalStatus);
30949
+ if (approvalStatus === "approved") setRejectionReason("");
30446
30950
  const savedId = create ? saved.id : Number(eventId);
30447
30951
  if (savedId != null && !Number.isNaN(savedId)) {
30448
30952
  router.push(`/admin/events/${savedId}/edit?from=${encodeURIComponent(listReturnUrl)}`);
@@ -30505,19 +31009,66 @@ function EventEditPage({ eventId }) {
30505
31009
  title: create ? "Add event" : "Edit event",
30506
31010
  subtitle: create ? "Create a new event" : "Update event details and tickets",
30507
31011
  closeHref: listReturnUrl,
31012
+ headerExtra: approvalOn && !vendorPortal ? /* @__PURE__ */ React.createElement("div", {
31013
+ className: "flex items-center gap-2"
31014
+ }, /* @__PURE__ */ React.createElement("select", {
31015
+ value: approvalStatus,
31016
+ onChange: /* @__PURE__ */ __name((e) => handleApprovalSelect(e.target.value), "onChange"),
31017
+ className: "h-8 rounded-md border border-gray-600 bg-gray-900 text-white text-xs px-2 max-w-[10rem]",
31018
+ "aria-label": "Approval status"
31019
+ }, /* @__PURE__ */ React.createElement("option", {
31020
+ value: "pending"
31021
+ }, "Pending"), /* @__PURE__ */ React.createElement("option", {
31022
+ value: "approved"
31023
+ }, "Approve"), /* @__PURE__ */ React.createElement("option", {
31024
+ value: "rejected"
31025
+ }, "Reject")), approvalStatus === "rejected" ? /* @__PURE__ */ React.createElement("button", {
31026
+ type: "button",
31027
+ onClick: /* @__PURE__ */ __name(() => openRejectModal("rejected"), "onClick"),
31028
+ className: "text-xs text-amber-300 hover:text-amber-200 underline underline-offset-2 max-w-[9rem] truncate",
31029
+ title: rejectionReason || "Add rejection reason"
31030
+ }, rejectionReason.trim() ? "Edit reason" : "Add reason") : null) : approvalOn && vendorPortal ? /* @__PURE__ */ React.createElement("span", {
31031
+ className: "text-xs text-gray-300 capitalize hidden sm:inline"
31032
+ }, approvalStatus.replace(/_/g, " "), approvalStatus === "rejected" && rejectionReason ? ` \u2014 ${rejectionReason}` : "") : null,
30508
31033
  menuItems: [
30509
31034
  {
30510
31035
  label: saving ? "Saving..." : "Save",
30511
31036
  icon: LucideIcons.Save,
30512
31037
  onClick: handleSave
30513
31038
  },
30514
- {
30515
- label: isActive ? "Deactivate" : "Activate",
30516
- icon: LucideIcons.Power,
30517
- onClick: /* @__PURE__ */ __name(() => setIsActive(!isActive), "onClick")
30518
- }
31039
+ ...approvalOn && vendorPortal && (approvalStatus === "pending" || approvalStatus === "rejected") ? [] : [
31040
+ {
31041
+ label: isActive ? "Deactivate" : "Activate",
31042
+ icon: LucideIcons.Power,
31043
+ onClick: /* @__PURE__ */ __name(() => setIsActive(!isActive), "onClick")
31044
+ }
31045
+ ]
30519
31046
  ]
30520
- }), !create && logoUrl.trim() ? /* @__PURE__ */ React.createElement("div", {
31047
+ }), /* @__PURE__ */ React.createElement(Dialog, {
31048
+ open: rejectModalOpen,
31049
+ onOpenChange: /* @__PURE__ */ __name((open) => {
31050
+ if (!open) cancelRejectModal();
31051
+ }, "onOpenChange")
31052
+ }, /* @__PURE__ */ React.createElement(DialogContent, {
31053
+ className: "max-w-md"
31054
+ }, /* @__PURE__ */ React.createElement(DialogHeader, null, /* @__PURE__ */ React.createElement(DialogTitle, null, "Rejection reason"), /* @__PURE__ */ React.createElement(DialogDescription, null, "Explain what the vendor needs to fix. This is required before you can save a rejected event.")), /* @__PURE__ */ React.createElement("textarea", {
31055
+ value: rejectDraft,
31056
+ onChange: /* @__PURE__ */ __name((e) => setRejectDraft(e.target.value), "onChange"),
31057
+ className: "w-full min-h-[100px] rounded-md border border-gray-300 px-3 py-2 text-sm",
31058
+ placeholder: "Explain what needs to change\u2026",
31059
+ autoFocus: true
31060
+ }), /* @__PURE__ */ React.createElement(DialogFooter, {
31061
+ className: "gap-2 sm:gap-0"
31062
+ }, /* @__PURE__ */ React.createElement(Button, {
31063
+ type: "button",
31064
+ variant: "outline",
31065
+ onClick: cancelRejectModal
31066
+ }, "Cancel"), /* @__PURE__ */ React.createElement(Button, {
31067
+ type: "button",
31068
+ variant: "destructive",
31069
+ disabled: !rejectDraft.trim(),
31070
+ onClick: confirmRejectReason
31071
+ }, "Confirm reject")))), !create && logoUrl.trim() ? /* @__PURE__ */ React.createElement("div", {
30521
31072
  className: "flex items-center gap-3 px-4 sm:px-6 py-3 border-b border-gray-100 bg-gray-50/80"
30522
31073
  }, /* @__PURE__ */ React.createElement("img", {
30523
31074
  src: logoUrl.trim(),
@@ -30876,11 +31427,15 @@ var init_EventEditPage = __esm({
30876
31427
  "src/admin/pages/EventEditPage.tsx"() {
30877
31428
  "use client";
30878
31429
  init_admin_list_return_url();
31430
+ init_vendor_scope();
30879
31431
  init_DetailPageLayout();
30880
31432
  init_DetailPageHeader();
30881
31433
  init_ImageOrUrlField();
30882
31434
  init_JoditRichText();
30883
31435
  init_EventProductsSection();
31436
+ init_admin_config_context();
31437
+ init_dialog();
31438
+ init_button();
30884
31439
  init_EventManagementFields();
30885
31440
  init_event_named_lists();
30886
31441
  init_social_media_links();
@@ -30956,6 +31511,8 @@ async function validateComboProductInventory(productIds) {
30956
31511
  function ComboEditPage({ comboId }) {
30957
31512
  const router = navigation.useRouter();
30958
31513
  const searchParams = navigation.useSearchParams();
31514
+ const { eventsEnabled } = React26.useContext(exports.AdminConfigContext);
31515
+ const eventsOn = eventsEnabled !== false;
30959
31516
  const listReturnUrl = safeAdminListReturnUrl(searchParams.get("from")) ?? "/admin/combos";
30960
31517
  const create = isCreate5(comboId);
30961
31518
  const duplicateFrom = searchParams.get("duplicateFrom")?.trim();
@@ -30977,8 +31534,9 @@ function ComboEditPage({ comboId }) {
30977
31534
  const [productOptions, setProductOptions] = React26.useState([]);
30978
31535
  const [fixedItems, setFixedItems] = React26.useState([]);
30979
31536
  const [addonItems, setAddonItems] = React26.useState([]);
31537
+ const canPickProducts = !eventsOn || Boolean(eventId);
30980
31538
  React26.useEffect(() => {
30981
- if (!eventId) {
31539
+ if (!eventsOn || !eventId) {
30982
31540
  setDefaultCurrency("INR");
30983
31541
  return;
30984
31542
  }
@@ -30990,9 +31548,14 @@ function ComboEditPage({ comboId }) {
30990
31548
  cancelled = true;
30991
31549
  };
30992
31550
  }, [
30993
- eventId
31551
+ eventId,
31552
+ eventsOn
30994
31553
  ]);
30995
31554
  React26.useEffect(() => {
31555
+ if (!eventsOn) {
31556
+ setEventOptions([]);
31557
+ return;
31558
+ }
30996
31559
  let cancelled = false;
30997
31560
  (async () => {
30998
31561
  try {
@@ -31012,45 +31575,63 @@ function ComboEditPage({ comboId }) {
31012
31575
  return () => {
31013
31576
  cancelled = true;
31014
31577
  };
31015
- }, []);
31578
+ }, [
31579
+ eventsOn
31580
+ ]);
31016
31581
  React26.useEffect(() => {
31017
- if (!eventId) {
31018
- setProductOptions([]);
31019
- return;
31020
- }
31021
31582
  let cancelled = false;
31022
31583
  (async () => {
31023
31584
  try {
31024
- const res2 = await fetch(`/api/event_products?eventId=${eventId}&limit=500`);
31025
- if (res2.ok) {
31585
+ if (eventsOn) {
31586
+ if (!eventId) {
31587
+ setProductOptions([]);
31588
+ return;
31589
+ }
31590
+ const res2 = await fetch(`/api/event_products?eventId=${eventId}&limit=500`);
31591
+ if (!res2.ok) return;
31026
31592
  const data = await res2.json();
31027
- if (!cancelled && Array.isArray(data.data)) {
31028
- const productIds = data.data.map((ep) => ep.productId).filter(Boolean);
31029
- if (productIds.length > 0) {
31030
- const prodRes = await fetch(`/api/products?ids=${productIds.join(",")}&limit=500`);
31031
- if (prodRes.ok) {
31032
- const prodData = await prodRes.json();
31033
- if (!cancelled && Array.isArray(prodData.data)) {
31034
- const fetched = prodData.data.map((p) => ({
31035
- value: String(p.id),
31036
- label: p.name ?? p.title ?? `Product #${p.id}`
31037
- }));
31038
- setProductOptions((prev) => {
31039
- const merged = [
31040
- ...fetched
31041
- ];
31042
- for (const p of prev) {
31043
- if (!merged.some((m) => m.value === p.value)) merged.push(p);
31044
- }
31045
- return merged;
31046
- });
31047
- }
31048
- }
31049
- } else {
31050
- setProductOptions([]);
31051
- }
31593
+ if (cancelled || !Array.isArray(data.data)) return;
31594
+ const productIds = data.data.map((ep) => ep.productId).filter(Boolean);
31595
+ if (productIds.length === 0) {
31596
+ setProductOptions([]);
31597
+ return;
31052
31598
  }
31599
+ const prodRes2 = await fetch(`/api/products?ids=${productIds.join(",")}&limit=500`);
31600
+ if (!prodRes2.ok) return;
31601
+ const prodData2 = await prodRes2.json();
31602
+ if (cancelled || !Array.isArray(prodData2.data)) return;
31603
+ const fetched2 = prodData2.data.map((p) => ({
31604
+ value: String(p.id),
31605
+ label: p.name ?? p.title ?? `Product #${p.id}`
31606
+ }));
31607
+ setProductOptions((prev) => {
31608
+ const merged = [
31609
+ ...fetched2
31610
+ ];
31611
+ for (const p of prev) {
31612
+ if (!merged.some((m) => m.value === p.value)) merged.push(p);
31613
+ }
31614
+ return merged;
31615
+ });
31616
+ return;
31053
31617
  }
31618
+ const prodRes = await fetch("/api/products?limit=500&sortField=name&sortOrder=asc");
31619
+ if (!prodRes.ok) return;
31620
+ const prodData = await prodRes.json();
31621
+ if (cancelled || !Array.isArray(prodData.data)) return;
31622
+ const fetched = prodData.data.map((p) => ({
31623
+ value: String(p.id),
31624
+ label: p.name ?? p.title ?? `Product #${p.id}`
31625
+ }));
31626
+ setProductOptions((prev) => {
31627
+ const merged = [
31628
+ ...fetched
31629
+ ];
31630
+ for (const p of prev) {
31631
+ if (!merged.some((m) => m.value === p.value)) merged.push(p);
31632
+ }
31633
+ return merged;
31634
+ });
31054
31635
  } catch {
31055
31636
  }
31056
31637
  })();
@@ -31058,7 +31639,8 @@ function ComboEditPage({ comboId }) {
31058
31639
  cancelled = true;
31059
31640
  };
31060
31641
  }, [
31061
- eventId
31642
+ eventId,
31643
+ eventsOn
31062
31644
  ]);
31063
31645
  React26.useEffect(() => {
31064
31646
  let cancelled = false;
@@ -31169,7 +31751,7 @@ function ComboEditPage({ comboId }) {
31169
31751
  ]);
31170
31752
  return;
31171
31753
  }
31172
- if (!trimmedEventId || !/^\d+$/.test(trimmedEventId)) {
31754
+ if (eventsOn && (!trimmedEventId || !/^\d+$/.test(trimmedEventId))) {
31173
31755
  setErrors([
31174
31756
  "Event is required"
31175
31757
  ]);
@@ -31228,10 +31810,11 @@ function ComboEditPage({ comboId }) {
31228
31810
  }
31229
31811
  setSaving(true);
31230
31812
  try {
31813
+ const resolvedEventId = trimmedEventId && /^\d+$/.test(trimmedEventId) ? Number(trimmedEventId) : null;
31231
31814
  const payload = {
31232
31815
  name: trimmedName,
31233
31816
  desc: desc || null,
31234
- eventId: Number(trimmedEventId),
31817
+ eventId: resolvedEventId,
31235
31818
  price,
31236
31819
  currencyPrices: null,
31237
31820
  minSelectableItems,
@@ -31325,7 +31908,7 @@ function ComboEditPage({ comboId }) {
31325
31908
  value: desc,
31326
31909
  onChange: /* @__PURE__ */ __name((e) => setDesc(e.target.value), "onChange"),
31327
31910
  className: "w-full rounded-md border border-gray-300 px-3 py-2 text-sm min-h-[100px]"
31328
- })), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("label", {
31911
+ })), eventsOn ? /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("label", {
31329
31912
  className: "block text-xs font-medium text-gray-600 mb-1"
31330
31913
  }, "Event *"), /* @__PURE__ */ React.createElement("select", {
31331
31914
  value: eventId,
@@ -31340,7 +31923,7 @@ function ComboEditPage({ comboId }) {
31340
31923
  }, "Select event"), eventOptions.map((o) => /* @__PURE__ */ React.createElement("option", {
31341
31924
  key: o.value,
31342
31925
  value: o.value
31343
- }, o.label)))))), eventId && /* @__PURE__ */ React.createElement("section", null, /* @__PURE__ */ React.createElement("h2", {
31926
+ }, o.label)))) : null)), canPickProducts && /* @__PURE__ */ React.createElement("section", null, /* @__PURE__ */ React.createElement("h2", {
31344
31927
  className: "text-xs font-semibold text-gray-400 uppercase tracking-wider mb-2"
31345
31928
  }, "Combo items"), /* @__PURE__ */ React.createElement("div", {
31346
31929
  className: "min-w-0 overflow-hidden border border-gray-200 rounded-lg p-4 bg-gray-50/50 space-y-5"
@@ -31408,11 +31991,11 @@ function ComboEditPage({ comboId }) {
31408
31991
  step: "0.01",
31409
31992
  value: priceStr,
31410
31993
  onChange: /* @__PURE__ */ __name((e) => setPriceStr(e.target.value), "onChange"),
31411
- disabled: !eventId,
31994
+ disabled: !canPickProducts,
31412
31995
  className: "w-full rounded-md border border-gray-300 px-3 py-2 text-sm disabled:bg-gray-100"
31413
31996
  }), /* @__PURE__ */ React.createElement("p", {
31414
31997
  className: "text-xs text-gray-400 mt-1"
31415
- }, eventId ? "Other currencies use the event\u2019s supported currencies and exchange rates." : "Select an event to set the combo price.")), /* @__PURE__ */ React.createElement("div", {
31998
+ }, eventsOn ? eventId ? "Other currencies use the event\u2019s supported currencies and exchange rates." : "Select an event to set the combo price." : "Price uses the store default currency (INR unless configured otherwise).")), /* @__PURE__ */ React.createElement("div", {
31416
31999
  className: "grid grid-cols-2 gap-4"
31417
32000
  }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("label", {
31418
32001
  className: "block text-xs font-medium text-gray-600 mb-1"
@@ -31478,6 +32061,7 @@ var init_ComboEditPage = __esm({
31478
32061
  init_DetailPageLayout();
31479
32062
  init_DetailPageHeader();
31480
32063
  init_inventory_validation();
32064
+ init_admin_config_context();
31481
32065
  isCreate5 = /* @__PURE__ */ __name((id) => id === "create", "isCreate");
31482
32066
  __name(formatDateTimeLocal, "formatDateTimeLocal");
31483
32067
  __name(fetchEventDefaultCurrency, "fetchEventDefaultCurrency");
@@ -31568,11 +32152,10 @@ function VendorEditPage({ vendorId }) {
31568
32152
  const [ownerPhone, setOwnerPhone] = React26.useState("");
31569
32153
  const [ownerDesignation, setOwnerDesignation] = React26.useState("");
31570
32154
  const [termsAccepted, setTermsAccepted] = React26.useState(false);
31571
- const [activationMode, setActivationMode] = React26.useState("invite");
31572
- const [sendOwnerEmail, setSendOwnerEmail] = React26.useState(true);
31573
- const [ownerPassword, setOwnerPassword] = React26.useState("");
31574
- const [ownerPasswordConfirm, setOwnerPasswordConfirm] = React26.useState("");
31575
32155
  const [reinviting, setReinviting] = React26.useState(false);
32156
+ const [inviteDialog, setInviteDialog] = React26.useState(null);
32157
+ const [sendingInviteEmail, setSendingInviteEmail] = React26.useState(false);
32158
+ const [linkCopied, setLinkCopied] = React26.useState(false);
31576
32159
  const vendorPayload = /* @__PURE__ */ __name(() => ({
31577
32160
  name: name.trim(),
31578
32161
  legalName: legalName.trim() || null,
@@ -31708,6 +32291,59 @@ function VendorEditPage({ vendorId }) {
31708
32291
  groupName: sessionUser?.groupName
31709
32292
  });
31710
32293
  }
32294
+ const copyInviteLink = /* @__PURE__ */ __name(async () => {
32295
+ const inviteLink = inviteDialog?.inviteLink;
32296
+ if (!inviteLink) {
32297
+ sonner.toast.error("Invite link is not available");
32298
+ return;
32299
+ }
32300
+ try {
32301
+ await navigator.clipboard.writeText(inviteLink);
32302
+ setLinkCopied(true);
32303
+ sonner.toast.success("Invite link copied");
32304
+ window.setTimeout(() => setLinkCopied(false), 2e3);
32305
+ } catch {
32306
+ sonner.toast.error("Could not copy invite link");
32307
+ }
32308
+ }, "copyInviteLink");
32309
+ const sendInviteEmail = /* @__PURE__ */ __name(async () => {
32310
+ if (!inviteDialog) return;
32311
+ setSendingInviteEmail(true);
32312
+ try {
32313
+ const res = await fetch(`/api/admin/vendors/${inviteDialog.vendorId}/resend-invite`, {
32314
+ method: "POST",
32315
+ headers: {
32316
+ "Content-Type": "application/json"
32317
+ },
32318
+ body: JSON.stringify({
32319
+ sendEmail: true,
32320
+ rotate: false
32321
+ })
32322
+ });
32323
+ const data = await res.json();
32324
+ if (!res.ok) {
32325
+ sonner.toast.error(data.error || data.message || "Failed to send invite email");
32326
+ return;
32327
+ }
32328
+ const nextLink = typeof data.inviteLink === "string" ? data.inviteLink.trim() : "";
32329
+ if (nextLink) {
32330
+ setInviteDialog((prev) => prev ? {
32331
+ ...prev,
32332
+ inviteLink: nextLink
32333
+ } : prev);
32334
+ }
32335
+ sonner.toast.success(data.emailSent ? "Invite email sent" : data.message || "Invite email queued");
32336
+ } catch {
32337
+ sonner.toast.error("Failed to send invite email");
32338
+ } finally {
32339
+ setSendingInviteEmail(false);
32340
+ }
32341
+ }, "sendInviteEmail");
32342
+ const closeInviteDialog = /* @__PURE__ */ __name(() => {
32343
+ setInviteDialog(null);
32344
+ setLinkCopied(false);
32345
+ router.push(listReturnUrl);
32346
+ }, "closeInviteDialog");
31711
32347
  const handleCreate = /* @__PURE__ */ __name(async () => {
31712
32348
  setSaving(true);
31713
32349
  setErrors([]);
@@ -31733,29 +32369,6 @@ function VendorEditPage({ vendorId }) {
31733
32369
  setSaving(false);
31734
32370
  return;
31735
32371
  }
31736
- if (activationMode === "password") {
31737
- if (!ownerPassword) {
31738
- setErrors([
31739
- "Enter a password for the owner account"
31740
- ]);
31741
- setSaving(false);
31742
- return;
31743
- }
31744
- if (ownerPassword !== ownerPasswordConfirm) {
31745
- setErrors([
31746
- "Passwords do not match"
31747
- ]);
31748
- setSaving(false);
31749
- return;
31750
- }
31751
- if (ownerPassword.length < 6) {
31752
- setErrors([
31753
- "Password must be at least 6 characters"
31754
- ]);
31755
- setSaving(false);
31756
- return;
31757
- }
31758
- }
31759
32372
  try {
31760
32373
  const res = await fetch("/api/admin/vendors/onboard", {
31761
32374
  method: "POST",
@@ -31763,18 +32376,15 @@ function VendorEditPage({ vendorId }) {
31763
32376
  "Content-Type": "application/json"
31764
32377
  },
31765
32378
  body: JSON.stringify({
31766
- activation: activationMode,
31767
- sendOwnerEmail,
32379
+ activation: "invite",
32380
+ sendOwnerEmail: false,
31768
32381
  termsAccepted: true,
31769
32382
  vendor: vendorPayload(),
31770
32383
  user: {
31771
32384
  name: ownerName.trim(),
31772
32385
  email: ownerEmail.trim(),
31773
32386
  phone: ownerPhone.trim() || void 0,
31774
- designation: ownerDesignation.trim() || void 0,
31775
- ...activationMode === "password" ? {
31776
- password: ownerPassword
31777
- } : {}
32387
+ designation: ownerDesignation.trim() || void 0
31778
32388
  }
31779
32389
  })
31780
32390
  });
@@ -31785,22 +32395,19 @@ function VendorEditPage({ vendorId }) {
31785
32395
  ]);
31786
32396
  return;
31787
32397
  }
31788
- const message = data.message || "Vendor created";
31789
- const inviteLink = typeof data.inviteLink === "string" ? data.inviteLink.trim() : "";
31790
- if (inviteLink) {
31791
- try {
31792
- await navigator.clipboard.writeText(inviteLink);
31793
- sonner.toast.success(`${message} Invite link copied to clipboard.`);
31794
- } catch {
31795
- sonner.toast.success(message);
31796
- sonner.toast.message("Invite link", {
31797
- description: inviteLink
31798
- });
31799
- }
31800
- } else {
31801
- sonner.toast.success(message);
32398
+ const vendorIdNum = Number(data.vendor?.id);
32399
+ const inviteLink = typeof data.inviteLink === "string" && data.inviteLink.trim() ? data.inviteLink.trim() : "";
32400
+ if (!Number.isFinite(vendorIdNum) || vendorIdNum <= 0 || !inviteLink) {
32401
+ sonner.toast.success(data.message || "Vendor created");
32402
+ router.push(listReturnUrl);
32403
+ return;
31802
32404
  }
31803
- router.push(listReturnUrl);
32405
+ sonner.toast.success(data.message || "Vendor created");
32406
+ setLinkCopied(false);
32407
+ setInviteDialog({
32408
+ vendorId: vendorIdNum,
32409
+ inviteLink
32410
+ });
31804
32411
  } catch {
31805
32412
  setErrors([
31806
32413
  "Request failed"
@@ -31871,7 +32478,6 @@ function VendorEditPage({ vendorId }) {
31871
32478
  setSaving(false);
31872
32479
  }
31873
32480
  }, "handleSave");
31874
- const createSubmitLabel = activationMode === "password" ? sendOwnerEmail ? "Create vendor & send welcome email" : "Create vendor & set password" : sendOwnerEmail ? "Create vendor & send invite" : "Create vendor & copy invite link";
31875
32481
  if (loading) {
31876
32482
  return /* @__PURE__ */ React26__namespace.default.createElement("div", {
31877
32483
  className: "flex items-center justify-center py-12"
@@ -31883,11 +32489,11 @@ function VendorEditPage({ vendorId }) {
31883
32489
  className: "rounded-lg bg-white shadow-md min-h-[420px]"
31884
32490
  }, /* @__PURE__ */ React26__namespace.default.createElement(DetailPageHeader, {
31885
32491
  title: create ? "Add vendor" : "Edit vendor",
31886
- subtitle: create ? "Register a vendor store and create the owner account" : "Update store and registration details",
32492
+ subtitle: create ? "Register a vendor store and create the owner invite" : "Update store and registration details",
31887
32493
  closeHref: listReturnUrl,
31888
32494
  menuItems: create ? [
31889
32495
  {
31890
- label: saving ? "Creating\u2026" : createSubmitLabel,
32496
+ label: saving ? "Creating\u2026" : "Create vendor",
31891
32497
  icon: LucideIcons.Save,
31892
32498
  onClick: handleCreate
31893
32499
  }
@@ -32155,64 +32761,42 @@ function VendorEditPage({ vendorId }) {
32155
32761
  className: "text-xs font-semibold text-gray-400 uppercase tracking-wider mb-2"
32156
32762
  }, "Owner access"), /* @__PURE__ */ React26__namespace.default.createElement("div", {
32157
32763
  className: sectionCls6
32158
- }, /* @__PURE__ */ React26__namespace.default.createElement(RadioGroup2, {
32159
- value: activationMode,
32160
- onValueChange: /* @__PURE__ */ __name((v) => setActivationMode(v), "onValueChange"),
32161
- className: "gap-3"
32162
- }, /* @__PURE__ */ React26__namespace.default.createElement("div", {
32163
- className: "flex items-start gap-2"
32164
- }, /* @__PURE__ */ React26__namespace.default.createElement(RadioGroupItem, {
32165
- value: "invite",
32166
- id: "activation-invite",
32167
- className: "mt-0.5"
32168
- }), /* @__PURE__ */ React26__namespace.default.createElement(Label3, {
32169
- htmlFor: "activation-invite",
32170
- className: "font-normal cursor-pointer text-sm"
32171
- }, "Send invite link")), /* @__PURE__ */ React26__namespace.default.createElement("div", {
32172
- className: "flex items-start gap-2"
32173
- }, /* @__PURE__ */ React26__namespace.default.createElement(RadioGroupItem, {
32174
- value: "password",
32175
- id: "activation-password",
32176
- className: "mt-0.5"
32177
- }), /* @__PURE__ */ React26__namespace.default.createElement(Label3, {
32178
- htmlFor: "activation-password",
32179
- className: "font-normal cursor-pointer text-sm"
32180
- }, "Set password now"))), /* @__PURE__ */ React26__namespace.default.createElement("div", {
32181
- className: "flex items-center gap-2 pt-1"
32182
- }, /* @__PURE__ */ React26__namespace.default.createElement(Checkbox, {
32183
- id: "sendOwnerEmail",
32184
- checked: sendOwnerEmail,
32185
- onCheckedChange: /* @__PURE__ */ __name((checked) => setSendOwnerEmail(checked === true), "onCheckedChange")
32186
- }), /* @__PURE__ */ React26__namespace.default.createElement(Label3, {
32187
- htmlFor: "sendOwnerEmail",
32188
- className: "font-normal cursor-pointer text-sm"
32189
- }, activationMode === "invite" ? "Send invite email" : "Send welcome email")), activationMode === "password" && /* @__PURE__ */ React26__namespace.default.createElement("div", {
32190
- className: "space-y-3 pt-1"
32191
- }, /* @__PURE__ */ React26__namespace.default.createElement("div", null, /* @__PURE__ */ React26__namespace.default.createElement(FieldLabel, {
32192
- htmlFor: "ownerPassword",
32193
- required: true
32194
- }, "Password"), /* @__PURE__ */ React26__namespace.default.createElement(Input, {
32195
- id: "ownerPassword",
32196
- type: "password",
32197
- value: ownerPassword,
32198
- onChange: /* @__PURE__ */ __name((e) => setOwnerPassword(e.target.value), "onChange"),
32199
- className: `mt-1 ${fieldClass}`
32200
- })), /* @__PURE__ */ React26__namespace.default.createElement("div", null, /* @__PURE__ */ React26__namespace.default.createElement(FieldLabel, {
32201
- htmlFor: "ownerPasswordConfirm",
32202
- required: true
32203
- }, "Confirm password"), /* @__PURE__ */ React26__namespace.default.createElement(Input, {
32204
- id: "ownerPasswordConfirm",
32205
- type: "password",
32206
- value: ownerPasswordConfirm,
32207
- onChange: /* @__PURE__ */ __name((e) => setOwnerPasswordConfirm(e.target.value), "onChange"),
32208
- className: `mt-1 ${fieldClass}`
32209
- }))), /* @__PURE__ */ React26__namespace.default.createElement(Button, {
32764
+ }, /* @__PURE__ */ React26__namespace.default.createElement("p", {
32765
+ className: "text-sm text-gray-600"
32766
+ }, "Creates the vendor and an invite for the owner. After create, you can send the invite email or copy the invite link."), /* @__PURE__ */ React26__namespace.default.createElement(Button, {
32210
32767
  type: "button",
32211
32768
  disabled: saving,
32212
32769
  onClick: handleCreate,
32213
32770
  className: "w-full"
32214
- }, saving ? "Creating\u2026" : createSubmitLabel))) : void 0)
32215
- }));
32771
+ }, saving ? "Creating\u2026" : "Create vendor"))) : void 0)
32772
+ }), /* @__PURE__ */ React26__namespace.default.createElement(Dialog, {
32773
+ open: inviteDialog != null,
32774
+ onOpenChange: /* @__PURE__ */ __name((open) => {
32775
+ if (!open) closeInviteDialog();
32776
+ }, "onOpenChange")
32777
+ }, /* @__PURE__ */ React26__namespace.default.createElement(DialogContent, {
32778
+ className: "max-w-md"
32779
+ }, /* @__PURE__ */ React26__namespace.default.createElement(DialogHeader, null, /* @__PURE__ */ React26__namespace.default.createElement(DialogTitle, null, "Vendor created"), /* @__PURE__ */ React26__namespace.default.createElement(DialogDescription, null, "Send an invite email to the owner, or copy the invite link to share another way. The link is not shown here.")), /* @__PURE__ */ React26__namespace.default.createElement("div", {
32780
+ className: "flex flex-col gap-2 py-2"
32781
+ }, /* @__PURE__ */ React26__namespace.default.createElement(Button, {
32782
+ type: "button",
32783
+ disabled: sendingInviteEmail,
32784
+ onClick: /* @__PURE__ */ __name(() => void sendInviteEmail(), "onClick")
32785
+ }, /* @__PURE__ */ React26__namespace.default.createElement(LucideIcons.Mail, {
32786
+ className: "h-4 w-4 mr-2"
32787
+ }), sendingInviteEmail ? "Sending\u2026" : "Send email"), /* @__PURE__ */ React26__namespace.default.createElement(Button, {
32788
+ type: "button",
32789
+ variant: "outline",
32790
+ onClick: /* @__PURE__ */ __name(() => void copyInviteLink(), "onClick")
32791
+ }, linkCopied ? /* @__PURE__ */ React26__namespace.default.createElement(LucideIcons.Check, {
32792
+ className: "h-4 w-4 mr-2"
32793
+ }) : /* @__PURE__ */ React26__namespace.default.createElement(LucideIcons.Copy, {
32794
+ className: "h-4 w-4 mr-2"
32795
+ }), linkCopied ? "Copied" : "Copy invite link")), /* @__PURE__ */ React26__namespace.default.createElement(DialogFooter, null, /* @__PURE__ */ React26__namespace.default.createElement(Button, {
32796
+ type: "button",
32797
+ variant: "secondary",
32798
+ onClick: closeInviteDialog
32799
+ }, "Done")))));
32216
32800
  }
32217
32801
  var isCreate6, fieldClass, selectClass, sectionCls6;
32218
32802
  var init_VendorEditPage = __esm({
@@ -32228,7 +32812,7 @@ var init_VendorEditPage = __esm({
32228
32812
  init_label();
32229
32813
  init_textarea();
32230
32814
  init_checkbox();
32231
- init_radio_group();
32815
+ init_dialog();
32232
32816
  init_vendor_profile();
32233
32817
  init_vendor_list_config();
32234
32818
  init_vendor_access_denied();
@@ -32312,9 +32896,9 @@ var VendorCategoryWorkspacePage_exports = {};
32312
32896
  __export(VendorCategoryWorkspacePage_exports, {
32313
32897
  default: () => VendorCategoryWorkspacePage
32314
32898
  });
32315
- function buildAdminProductColumns(base, multiVendorEnabled, eventsEnabled) {
32899
+ function buildAdminProductColumns(base, showVendorColumn, eventsEnabled) {
32316
32900
  const extraColumns = [
32317
- ...multiVendorEnabled ? [
32901
+ ...showVendorColumn ? [
32318
32902
  VENDOR_EXTRA_COLUMN
32319
32903
  ] : [],
32320
32904
  ...eventsEnabled ? [
@@ -32333,9 +32917,9 @@ function buildAdminProductColumns(base, multiVendorEnabled, eventsEnabled) {
32333
32917
  ...cols.slice(nameIdx + 1)
32334
32918
  ];
32335
32919
  }
32336
- function buildAllProductColumns(base, multiVendorEnabled, eventsEnabled) {
32920
+ function buildAllProductColumns(base, showVendorColumn, eventsEnabled) {
32337
32921
  const extraColumns = [
32338
- ...multiVendorEnabled ? [
32922
+ ...showVendorColumn ? [
32339
32923
  VENDOR_EXTRA_COLUMN
32340
32924
  ] : [],
32341
32925
  ...eventsEnabled ? [
@@ -32385,7 +32969,8 @@ function VendorCategoryWorkspacePage() {
32385
32969
  const workspaceFrom = activeCategoryId ? `/admin/products?categoryId=${activeCategoryId}` : "/admin/products";
32386
32970
  const itemLabel = activeCategory ? categorySingularName(activeCategory.name) : "Product";
32387
32971
  const productColumns2 = React26.useMemo(() => {
32388
- const base = showAllProducts ? buildAllProductColumns(exports.STORE_CRUD_CONFIGS.products.columns, multiVendorEnabled !== false, eventsEnabled !== false) : buildAdminProductColumns(exports.STORE_CRUD_CONFIGS.products.columns, multiVendorEnabled !== false, eventsEnabled !== false);
32972
+ const showVendorColumn = multiVendorEnabled !== false && !vendorPortal;
32973
+ const base = showAllProducts ? buildAllProductColumns(exports.STORE_CRUD_CONFIGS.products.columns, showVendorColumn, eventsEnabled !== false) : buildAdminProductColumns(exports.STORE_CRUD_CONFIGS.products.columns, showVendorColumn, eventsEnabled !== false);
32389
32974
  return withCollectionRelationApi(base, activeCategoryId, vendorPortal);
32390
32975
  }, [
32391
32976
  vendorPortal,
@@ -33711,7 +34296,7 @@ function CustomerPicker({ value, label, onChange }) {
33711
34296
  className: "text-xs text-gray-400 shrink-0 truncate"
33712
34297
  }, c.email ?? ""))))));
33713
34298
  }
33714
- function ConditionCard({ condition, onChange, onRemove }) {
34299
+ function ConditionCard({ condition, onChange, onRemove, eventsOn }) {
33715
34300
  const iconMap = {
33716
34301
  minAmount: /* @__PURE__ */ React.createElement(LucideIcons.DollarSign, {
33717
34302
  className: "h-3.5 w-3.5 text-blue-500"
@@ -33767,7 +34352,7 @@ function ConditionCard({ condition, onChange, onRemove }) {
33767
34352
  value: "minQuantity"
33768
34353
  }, "Minimum quantity"), /* @__PURE__ */ React.createElement(SelectItem, {
33769
34354
  value: "productMinQuantity"
33770
- }, "Product"), /* @__PURE__ */ React.createElement(SelectItem, {
34355
+ }, "Product"), (eventsOn || condition.kind === "events") && /* @__PURE__ */ React.createElement(SelectItem, {
33771
34356
  value: "events"
33772
34357
  }, "Events"), /* @__PURE__ */ React.createElement(SelectItem, {
33773
34358
  value: "nthOrder"
@@ -34001,11 +34586,13 @@ function RewardCard({ reward, onChange, onRemove, discountType, discountValue, e
34001
34586
  })));
34002
34587
  }
34003
34588
  function DiscountConditionsBuilder({ rules, onChange, discountType = "PERCENTAGE", discountValue = "" }) {
34589
+ const { eventsEnabled } = React26.useContext(exports.AdminConfigContext);
34590
+ const eventsOn = eventsEnabled !== false;
34004
34591
  const parsed = ruleTreeToFriendly(rules);
34005
34592
  const [groups, setGroups] = React26.useState(parsed.groups);
34006
34593
  const [groupOperator, setGroupOperator] = React26.useState(parsed.groupOperator);
34007
34594
  const [rewards, setRewards] = React26.useState(parsed.rewards);
34008
- const rewardEventId = groups.flatMap((g) => g.conditions).find((c) => c.kind === "events" && c.eventId != null)?.eventId ?? null;
34595
+ const rewardEventId = eventsOn ? groups.flatMap((g) => g.conditions).find((c) => c.kind === "events" && c.eventId != null)?.eventId ?? null : null;
34009
34596
  React26.useEffect(() => {
34010
34597
  const missingNameIds = [];
34011
34598
  for (const g of groups) {
@@ -34242,7 +34829,8 @@ function DiscountConditionsBuilder({ rules, onChange, discountType = "PERCENTAGE
34242
34829
  })), /* @__PURE__ */ React.createElement(ConditionCard, {
34243
34830
  condition: c,
34244
34831
  onChange: /* @__PURE__ */ __name((updated) => updateCondition(group.id, c.id, updated), "onChange"),
34245
- onRemove: /* @__PURE__ */ __name(() => removeCondition(group.id, c.id), "onRemove")
34832
+ onRemove: /* @__PURE__ */ __name(() => removeCondition(group.id, c.id), "onRemove"),
34833
+ eventsOn
34246
34834
  })))), /* @__PURE__ */ React.createElement(Button, {
34247
34835
  type: "button",
34248
34836
  variant: "outline",
@@ -34305,6 +34893,7 @@ var init_DiscountsConditionsBuilder = __esm({
34305
34893
  "use client";
34306
34894
  init_button();
34307
34895
  init_select();
34896
+ init_admin_config_context();
34308
34897
  __name(uid, "uid");
34309
34898
  __name(conditionToRule, "conditionToRule");
34310
34899
  __name(rewardToRule, "rewardToRule");
@@ -36486,7 +37075,7 @@ function AdminPageResolver({ slug }) {
36486
37075
  const searchParams = navigation.useSearchParams();
36487
37076
  const { data: session } = react.useSession();
36488
37077
  const vendorPortal = isVendorPortalUser(session?.user);
36489
- const { customCrudConfigs, storeEnabled, multiVendorEnabled, eventsEnabled } = React26.useContext(exports.AdminConfigContext);
37078
+ const { customCrudConfigs, storeEnabled, multiVendorEnabled, eventsEnabled, vendorCanCreateCategories, vendorCanCreateCollections, vendorCanCreateBrands, requireProductApproval, requireEventApproval } = React26.useContext(exports.AdminConfigContext);
36490
37079
  const key = slug?.[0] || "dashboard";
36491
37080
  const [vendorOptions, setVendorOptions] = React26.useState([]);
36492
37081
  React26.useEffect(() => {
@@ -36520,6 +37109,11 @@ function AdminPageResolver({ slug }) {
36520
37109
  columns = columns.filter((column) => column.field !== "eventId" && column.field !== "eventName");
36521
37110
  filters = filters.filter((filter) => filter.param !== "eventId");
36522
37111
  }
37112
+ const showApprovalColumn = key === "products" && requireProductApproval === true || key === "events" && requireEventApproval === true;
37113
+ if (!showApprovalColumn) {
37114
+ columns = columns.filter((column) => column.field !== "approvalStatus");
37115
+ filters = filters.filter((filter) => filter.param !== "approvalStatus");
37116
+ }
36523
37117
  if (multiVendorEnabled !== false && !vendorPortal && STORE_VENDOR_RESOURCES.has(key) && key !== "event_products" && !columns.some((c) => c.field === "vendorId")) {
36524
37118
  columns = [
36525
37119
  VENDOR_COLUMN2,
@@ -36557,7 +37151,9 @@ function AdminPageResolver({ slug }) {
36557
37151
  vendorOptions,
36558
37152
  vendorPortal,
36559
37153
  multiVendorEnabled,
36560
- eventsEnabled
37154
+ eventsEnabled,
37155
+ requireProductApproval,
37156
+ requireEventApproval
36561
37157
  ]);
36562
37158
  const isContactsWithStore = key === "contacts" && storeEnabled;
36563
37159
  const extraListParams = React26.useMemo(() => isContactsWithStore ? {
@@ -36671,7 +37267,27 @@ function AdminPageResolver({ slug }) {
36671
37267
  className: "ml-2"
36672
37268
  }, "Redirecting\u2026"));
36673
37269
  }
36674
- if (vendorPortal && key === "product_categories") {
37270
+ if (vendorPortal && key === "product_categories" && vendorCanCreateCategories !== true) {
37271
+ router.replace("/admin/products");
37272
+ return /* @__PURE__ */ React26__namespace.default.createElement("div", {
37273
+ className: "flex justify-center py-8"
37274
+ }, /* @__PURE__ */ React26__namespace.default.createElement("div", {
37275
+ className: "animate-spin rounded-full h-6 w-6 border-2 border-gray-300 border-t-gray-600"
37276
+ }), /* @__PURE__ */ React26__namespace.default.createElement("span", {
37277
+ className: "ml-2"
37278
+ }, "Redirecting\u2026"));
37279
+ }
37280
+ if (vendorPortal && key === "collections" && vendorCanCreateCollections !== true) {
37281
+ router.replace("/admin/products");
37282
+ return /* @__PURE__ */ React26__namespace.default.createElement("div", {
37283
+ className: "flex justify-center py-8"
37284
+ }, /* @__PURE__ */ React26__namespace.default.createElement("div", {
37285
+ className: "animate-spin rounded-full h-6 w-6 border-2 border-gray-300 border-t-gray-600"
37286
+ }), /* @__PURE__ */ React26__namespace.default.createElement("span", {
37287
+ className: "ml-2"
37288
+ }, "Redirecting\u2026"));
37289
+ }
37290
+ if (vendorPortal && key === "brands" && vendorCanCreateBrands !== true) {
36675
37291
  router.replace("/admin/products");
36676
37292
  return /* @__PURE__ */ React26__namespace.default.createElement("div", {
36677
37293
  className: "flex justify-center py-8"