@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.js CHANGED
@@ -457,7 +457,7 @@ var init_admin_config_context = __esm({
457
457
  var CMS_VERSION;
458
458
  var init_cms_version = __esm({
459
459
  "src/lib/cms-version.ts"() {
460
- CMS_VERSION = "1.0.40" ;
460
+ CMS_VERSION = "1.0.42" ;
461
461
  }
462
462
  });
463
463
  function useCatalogCategories(enabled = true) {
@@ -567,9 +567,12 @@ function AdminSidebar({ variant = "sidebar" }) {
567
567
  const sessionUser = session?.user;
568
568
  const showVendorOnboard = canOnboardVendors(sessionUser);
569
569
  const vendorPortal = isVendorPortalUser(sessionUser);
570
- const { customNavItems, customNavSections = [], storeEnabled, multiVendorEnabled, eventsEnabled } = useContext(AdminConfigContext);
570
+ const { customNavItems, customNavSections = [], storeEnabled, multiVendorEnabled, eventsEnabled, vendorCanCreateCategories, vendorCanCreateCollections, vendorCanCreateBrands } = useContext(AdminConfigContext);
571
571
  const showStoreNav = storeEnabled || vendorPortal;
572
572
  const showPlatformNav = !vendorPortal;
573
+ const showVendorCategories = !vendorPortal || vendorCanCreateCategories === true;
574
+ const showVendorCollections = !vendorPortal || vendorCanCreateCollections === true;
575
+ const showVendorBrands = !vendorPortal || vendorCanCreateBrands === true;
573
576
  const isDrawer = variant === "drawer";
574
577
  const { categories: catalogCategories } = useCatalogCategories(showStoreNav);
575
578
  searchParams.get("categoryId")?.trim() ?? "";
@@ -678,17 +681,17 @@ function AdminSidebar({ variant = "sidebar" }) {
678
681
  className: `${linkCls} ${isActive("/admin/vendors") ? linkActive : linkInactive}`
679
682
  }, /* @__PURE__ */ React.createElement(Store, {
680
683
  className: `h-4 w-4 mr-2 ${isActive("/admin/vendors") ? iconActive : iconInactive}`
681
- }), "Vendors")), !vendorPortal && /* @__PURE__ */ React.createElement("li", null, /* @__PURE__ */ React.createElement(Link2, {
684
+ }), "Vendors")), showVendorCategories && /* @__PURE__ */ React.createElement("li", null, /* @__PURE__ */ React.createElement(Link2, {
682
685
  href: "/admin/product_categories",
683
686
  className: `${linkCls} ${isActive("/admin/product_categories") ? linkActive : linkInactive}`
684
687
  }, /* @__PURE__ */ React.createElement(FolderTree, {
685
688
  className: `h-4 w-4 mr-2 ${isActive("/admin/product_categories") ? iconActive : iconInactive}`
686
- }), "Categories")), /* @__PURE__ */ React.createElement("li", null, /* @__PURE__ */ React.createElement(Link2, {
689
+ }), "Categories")), showVendorCollections && /* @__PURE__ */ React.createElement("li", null, /* @__PURE__ */ React.createElement(Link2, {
687
690
  href: "/admin/collections",
688
691
  className: `${linkCls} ${isActive("/admin/collections") ? linkActive : linkInactive}`
689
692
  }, /* @__PURE__ */ React.createElement(Layers, {
690
693
  className: `h-4 w-4 mr-2 ${isActive("/admin/collections") ? iconActive : iconInactive}`
691
- }), "Collections")), /* @__PURE__ */ React.createElement("li", null, /* @__PURE__ */ React.createElement(Link2, {
694
+ }), "Collections")), showVendorBrands && /* @__PURE__ */ React.createElement("li", null, /* @__PURE__ */ React.createElement(Link2, {
692
695
  href: "/admin/brands",
693
696
  className: `${linkCls} ${isActive("/admin/brands") ? linkActive : linkInactive}`
694
697
  }, /* @__PURE__ */ React.createElement(Building2, {
@@ -1371,33 +1374,52 @@ function parseBooleanSetting(value) {
1371
1374
  }
1372
1375
  return null;
1373
1376
  }
1374
- function useMultiVendorEnabled() {
1377
+ function useMultiVendorSettings() {
1375
1378
  const [multiVendorEnabled, setMultiVendorEnabled] = useState(true);
1379
+ const [vendorCanCreateCategories, setVendorCanCreateCategories] = useState(false);
1380
+ const [vendorCanCreateCollections, setVendorCanCreateCollections] = useState(false);
1381
+ const [vendorCanCreateBrands, setVendorCanCreateBrands] = useState(false);
1382
+ const [requireProductApproval, setRequireProductApproval] = useState(false);
1376
1383
  useEffect(() => {
1377
1384
  fetch("/api/settings/multi_vendor").then((r) => r.ok ? r.json() : {}).then((data) => {
1378
1385
  const parsed = parseBooleanSetting(data?.enabled ?? data?.value ?? data?.isEnabled ?? data?.active ?? data?.state);
1379
1386
  setMultiVendorEnabled(parsed ?? true);
1387
+ setVendorCanCreateCategories(parseBooleanSetting(data?.vendorCanCreateCategories) === true);
1388
+ setVendorCanCreateCollections(parseBooleanSetting(data?.vendorCanCreateCollections) === true);
1389
+ setVendorCanCreateBrands(parseBooleanSetting(data?.vendorCanCreateBrands) === true);
1390
+ setRequireProductApproval(parseBooleanSetting(data?.requireProductApproval) === true);
1380
1391
  }).catch(() => {
1381
1392
  });
1382
1393
  }, []);
1383
- return multiVendorEnabled;
1394
+ return {
1395
+ multiVendorEnabled,
1396
+ vendorCanCreateCategories,
1397
+ vendorCanCreateCollections,
1398
+ vendorCanCreateBrands,
1399
+ requireProductApproval
1400
+ };
1384
1401
  }
1385
- function useEventsEnabled() {
1402
+ function useEventsSettings() {
1386
1403
  const [eventsEnabled, setEventsEnabled] = useState(true);
1404
+ const [requireEventApproval, setRequireEventApproval] = useState(false);
1387
1405
  useEffect(() => {
1388
1406
  fetch("/api/settings/events").then((r) => r.ok ? r.json() : {}).then((data) => {
1389
1407
  const parsed = parseBooleanSetting(data?.enabled ?? data?.value ?? data?.isEnabled ?? data?.active ?? data?.state);
1390
1408
  setEventsEnabled(parsed ?? true);
1409
+ setRequireEventApproval(parseBooleanSetting(data?.requireEventApproval) === true);
1391
1410
  }).catch(() => {
1392
1411
  });
1393
1412
  }, []);
1394
- return eventsEnabled;
1413
+ return {
1414
+ eventsEnabled,
1415
+ requireEventApproval
1416
+ };
1395
1417
  }
1396
1418
  function AdminLayout({ children, customNavItems = [], customNavSections = [], customCrudConfigs = {}, categoryRelatedProductLabels = {}, renderOrderDetailFooter, theme, themeRegistry, pluginDescriptors = [] }) {
1397
1419
  const resolvedTheme = useResolvedTheme(theme, themeRegistry);
1398
1420
  const { storeEnabled, currency } = useStoreEnabled();
1399
- const multiVendorEnabled = useMultiVendorEnabled();
1400
- const eventsEnabled = useEventsEnabled();
1421
+ const { multiVendorEnabled, vendorCanCreateCategories, vendorCanCreateCollections, vendorCanCreateBrands, requireProductApproval } = useMultiVendorSettings();
1422
+ const { eventsEnabled, requireEventApproval } = useEventsSettings();
1401
1423
  const mergedPluginDescriptors = useMemo(() => {
1402
1424
  const seen = new Set(pluginDescriptors.map((p) => p.name));
1403
1425
  const extra = BUILTIN_PLUGIN_DESCRIPTORS.filter((p) => !seen.has(p.name));
@@ -1420,6 +1442,11 @@ function AdminLayout({ children, customNavItems = [], customNavSections = [], cu
1420
1442
  storeEnabled,
1421
1443
  currency,
1422
1444
  multiVendorEnabled,
1445
+ vendorCanCreateCategories,
1446
+ vendorCanCreateCollections,
1447
+ vendorCanCreateBrands,
1448
+ requireProductApproval,
1449
+ requireEventApproval,
1423
1450
  eventsEnabled
1424
1451
  }), [
1425
1452
  customNavItems,
@@ -1433,6 +1460,11 @@ function AdminLayout({ children, customNavItems = [], customNavSections = [], cu
1433
1460
  storeEnabled,
1434
1461
  currency,
1435
1462
  multiVendorEnabled,
1463
+ vendorCanCreateCategories,
1464
+ vendorCanCreateCollections,
1465
+ vendorCanCreateBrands,
1466
+ requireProductApproval,
1467
+ requireEventApproval,
1436
1468
  eventsEnabled
1437
1469
  ]);
1438
1470
  return /* @__PURE__ */ React.createElement(AdminConfigContext.Provider, {
@@ -1469,8 +1501,8 @@ var init_AdminLayout = __esm({
1469
1501
  __name(useResolvedTheme, "useResolvedTheme");
1470
1502
  __name(useStoreEnabled, "useStoreEnabled");
1471
1503
  __name(parseBooleanSetting, "parseBooleanSetting");
1472
- __name(useMultiVendorEnabled, "useMultiVendorEnabled");
1473
- __name(useEventsEnabled, "useEventsEnabled");
1504
+ __name(useMultiVendorSettings, "useMultiVendorSettings");
1505
+ __name(useEventsSettings, "useEventsSettings");
1474
1506
  __name(AdminLayout, "AdminLayout");
1475
1507
  }
1476
1508
  });
@@ -7701,6 +7733,155 @@ var init_ComponentSettings = __esm({
7701
7733
  __name(ComponentSettings, "ComponentSettings");
7702
7734
  }
7703
7735
  });
7736
+ 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 }) {
7737
+ const fileInputRef = useRef(null);
7738
+ const [isUploading, setIsUploading] = useState(false);
7739
+ const [error, setError] = useState(null);
7740
+ const [lightboxOpen, setLightboxOpen] = useState(false);
7741
+ 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";
7742
+ const closeLightbox = useCallback(() => setLightboxOpen(false), []);
7743
+ useEffect(() => {
7744
+ if (!lightboxOpen) return;
7745
+ const onKeyDown = /* @__PURE__ */ __name((e) => {
7746
+ if (e.key === "Escape") closeLightbox();
7747
+ }, "onKeyDown");
7748
+ window.addEventListener("keydown", onKeyDown);
7749
+ const prevOverflow = document.body.style.overflow;
7750
+ document.body.style.overflow = "hidden";
7751
+ return () => {
7752
+ window.removeEventListener("keydown", onKeyDown);
7753
+ document.body.style.overflow = prevOverflow;
7754
+ };
7755
+ }, [
7756
+ lightboxOpen,
7757
+ closeLightbox
7758
+ ]);
7759
+ const handleUpload = useCallback(async (file) => {
7760
+ setError(null);
7761
+ if (!ACCEPTED_TYPES.includes(file.type)) {
7762
+ setError(`Unsupported file type. Use: ${ACCEPTED_TYPES.map((t) => t.replace("image/", "")).join(", ")}`);
7763
+ return;
7764
+ }
7765
+ if (file.size > maxSizeMb * 1024 * 1024) {
7766
+ setError(`File must be under ${maxSizeMb}MB`);
7767
+ return;
7768
+ }
7769
+ setIsUploading(true);
7770
+ try {
7771
+ const formData = new FormData();
7772
+ formData.append("file", file);
7773
+ const response = await fetch("/api/upload", {
7774
+ method: "POST",
7775
+ body: formData
7776
+ });
7777
+ const data = await response.json();
7778
+ if (!response.ok) {
7779
+ throw new Error(data.error || data.details || "Upload failed");
7780
+ }
7781
+ onChange(data.filePath ?? "");
7782
+ } catch (err) {
7783
+ setError(err instanceof Error ? err.message : "Upload failed");
7784
+ } finally {
7785
+ setIsUploading(false);
7786
+ if (fileInputRef.current) fileInputRef.current.value = "";
7787
+ }
7788
+ }, [
7789
+ maxSizeMb,
7790
+ onChange
7791
+ ]);
7792
+ const onFileChange = useCallback((e) => {
7793
+ const file = e.target.files?.[0];
7794
+ if (file) void handleUpload(file);
7795
+ }, [
7796
+ handleUpload
7797
+ ]);
7798
+ const trimmed = value.trim();
7799
+ return /* @__PURE__ */ React.createElement("div", {
7800
+ className: "space-y-2"
7801
+ }, /* @__PURE__ */ React.createElement("label", {
7802
+ className: labelClassName
7803
+ }, label), trimmed ? /* @__PURE__ */ React.createElement("div", {
7804
+ className: "flex items-start gap-3"
7805
+ }, /* @__PURE__ */ React.createElement("img", {
7806
+ src: trimmed,
7807
+ alt: label,
7808
+ className: previewCls,
7809
+ role: "button",
7810
+ tabIndex: 0,
7811
+ title: "Click to enlarge",
7812
+ onClick: /* @__PURE__ */ __name(() => setLightboxOpen(true), "onClick"),
7813
+ onKeyDown: /* @__PURE__ */ __name((e) => {
7814
+ if (e.key === "Enter" || e.key === " ") {
7815
+ e.preventDefault();
7816
+ setLightboxOpen(true);
7817
+ }
7818
+ }, "onKeyDown"),
7819
+ onError: /* @__PURE__ */ __name((e) => {
7820
+ e.currentTarget.style.display = "none";
7821
+ }, "onError")
7822
+ }), /* @__PURE__ */ React.createElement("button", {
7823
+ type: "button",
7824
+ onClick: /* @__PURE__ */ __name(() => onChange(""), "onClick"),
7825
+ 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"
7826
+ }, /* @__PURE__ */ React.createElement(X, {
7827
+ className: "h-3 w-3"
7828
+ }), "Clear")) : null, /* @__PURE__ */ React.createElement("div", {
7829
+ className: "flex flex-wrap gap-2"
7830
+ }, /* @__PURE__ */ React.createElement("input", {
7831
+ type: "url",
7832
+ value,
7833
+ onChange: /* @__PURE__ */ __name((e) => {
7834
+ setError(null);
7835
+ onChange(e.target.value);
7836
+ }, "onChange"),
7837
+ placeholder,
7838
+ className: `${inputClassName} min-w-0 flex-1`
7839
+ }), /* @__PURE__ */ React.createElement("input", {
7840
+ ref: fileInputRef,
7841
+ type: "file",
7842
+ accept: ACCEPTED_TYPES.join(","),
7843
+ onChange: onFileChange,
7844
+ className: "hidden",
7845
+ disabled: isUploading
7846
+ }), /* @__PURE__ */ React.createElement("button", {
7847
+ type: "button",
7848
+ onClick: /* @__PURE__ */ __name(() => fileInputRef.current?.click(), "onClick"),
7849
+ disabled: isUploading,
7850
+ 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"
7851
+ }, /* @__PURE__ */ React.createElement(Upload, {
7852
+ className: "h-3.5 w-3.5"
7853
+ }), isUploading ? "Uploading\u2026" : "Upload")), error ? /* @__PURE__ */ React.createElement("p", {
7854
+ className: "flex items-center gap-1.5 text-xs text-red-600"
7855
+ }, /* @__PURE__ */ React.createElement(AlertCircle, {
7856
+ className: "h-3.5 w-3.5 shrink-0"
7857
+ }), error) : /* @__PURE__ */ React.createElement("p", {
7858
+ className: "text-xs text-gray-500"
7859
+ }, "Paste a URL or upload an image (PNG, JPG, GIF, WEBP up to ", maxSizeMb, "MB)"), lightboxOpen && trimmed ? /* @__PURE__ */ React.createElement("div", {
7860
+ className: "fixed inset-0 z-[100] flex items-center justify-center bg-black/70 p-4",
7861
+ role: "dialog",
7862
+ "aria-modal": "true",
7863
+ "aria-label": `${label} preview`,
7864
+ onClick: closeLightbox
7865
+ }, /* @__PURE__ */ React.createElement("img", {
7866
+ src: trimmed,
7867
+ alt: label,
7868
+ className: "max-h-[90vh] max-w-[90vw] rounded-md object-contain shadow-lg",
7869
+ onClick: /* @__PURE__ */ __name((e) => e.stopPropagation(), "onClick")
7870
+ })) : null);
7871
+ }
7872
+ var ACCEPTED_TYPES;
7873
+ var init_ImageOrUrlField = __esm({
7874
+ "src/components/Admin/ImageOrUrlField.tsx"() {
7875
+ "use client";
7876
+ ACCEPTED_TYPES = [
7877
+ "image/jpeg",
7878
+ "image/png",
7879
+ "image/gif",
7880
+ "image/webp"
7881
+ ];
7882
+ __name(ImageOrUrlField, "ImageOrUrlField");
7883
+ }
7884
+ });
7704
7885
  function generateId() {
7705
7886
  return `nav_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`;
7706
7887
  }
@@ -8003,22 +8184,16 @@ function NavbarEditor({ config, onChange }) {
8003
8184
  className: "space-y-6"
8004
8185
  }, /* @__PURE__ */ React.createElement("div", {
8005
8186
  className: "space-y-3"
8006
- }, /* @__PURE__ */ React.createElement(Label3, {
8007
- className: "text-sm font-medium"
8008
- }, "Logo"), /* @__PURE__ */ React.createElement("div", {
8009
- className: "flex items-center gap-3"
8010
- }, config.logo && /* @__PURE__ */ React.createElement("img", {
8011
- src: config.logo,
8012
- alt: "Logo",
8013
- className: "h-10 rounded border object-contain"
8014
- }), /* @__PURE__ */ React.createElement(Input, {
8187
+ }, /* @__PURE__ */ React.createElement(ImageOrUrlField, {
8188
+ label: "Logo",
8015
8189
  value: config.logo || "",
8016
- onChange: /* @__PURE__ */ __name((e) => onChange({
8190
+ onChange: /* @__PURE__ */ __name((logo) => onChange({
8017
8191
  ...config,
8018
- logo: e.target.value
8192
+ logo
8019
8193
  }), "onChange"),
8194
+ previewVariant: "logo",
8020
8195
  placeholder: "Logo image URL"
8021
- }))), /* @__PURE__ */ React.createElement("div", {
8196
+ })), /* @__PURE__ */ React.createElement("div", {
8022
8197
  className: "space-y-3"
8023
8198
  }, /* @__PURE__ */ React.createElement("div", {
8024
8199
  className: "flex items-center justify-between"
@@ -8057,6 +8232,7 @@ var init_NavbarEditor = __esm({
8057
8232
  init_button();
8058
8233
  init_switch();
8059
8234
  init_label();
8235
+ init_ImageOrUrlField();
8060
8236
  __name(generateId, "generateId");
8061
8237
  __name(NavItemEditor, "NavItemEditor");
8062
8238
  __name(updateItemInTree, "updateItemInTree");
@@ -11005,10 +11181,32 @@ function renderListPrice(value, item) {
11005
11181
  return `${formatted} ${currency}`;
11006
11182
  }
11007
11183
  }
11184
+ function renderListThumbnail(url) {
11185
+ const trimmed = url.trim();
11186
+ if (!trimmed) return "\u2014";
11187
+ return createElement("img", {
11188
+ src: trimmed,
11189
+ alt: "",
11190
+ className: "h-10 w-10 rounded border border-gray-200 bg-white object-contain"
11191
+ });
11192
+ }
11193
+ function productListImageUrl(item) {
11194
+ const meta = item.metadata;
11195
+ if (!meta || typeof meta !== "object" || Array.isArray(meta)) return "";
11196
+ const images = meta.images;
11197
+ if (!Array.isArray(images)) return "";
11198
+ const rows = images;
11199
+ const def = rows.find((r) => r?.isDefault && typeof r.url === "string" && r.url.trim());
11200
+ if (def && typeof def.url === "string") return def.url.trim();
11201
+ const first = rows.find((r) => typeof r.url === "string" && r.url.trim());
11202
+ return first && typeof first.url === "string" ? first.url.trim() : "";
11203
+ }
11008
11204
  var STORE_CRUD_CONFIGS;
11009
11205
  var init_store_crud_configs = __esm({
11010
11206
  "src/admin/store-crud-configs.ts"() {
11011
11207
  __name(renderListPrice, "renderListPrice");
11208
+ __name(renderListThumbnail, "renderListThumbnail");
11209
+ __name(productListImageUrl, "productListImageUrl");
11012
11210
  STORE_CRUD_CONFIGS = {
11013
11211
  products: {
11014
11212
  title: "Products",
@@ -11034,9 +11232,40 @@ var init_store_crud_configs = __esm({
11034
11232
  label: "Out of stock"
11035
11233
  }
11036
11234
  ]
11235
+ },
11236
+ {
11237
+ param: "approvalStatus",
11238
+ label: "Approval",
11239
+ type: "select",
11240
+ options: [
11241
+ {
11242
+ value: "",
11243
+ label: "All"
11244
+ },
11245
+ {
11246
+ value: "pending",
11247
+ label: "Pending"
11248
+ },
11249
+ {
11250
+ value: "approved",
11251
+ label: "Approved"
11252
+ },
11253
+ {
11254
+ value: "rejected",
11255
+ label: "Rejected"
11256
+ }
11257
+ ]
11037
11258
  }
11038
11259
  ],
11039
11260
  columns: [
11261
+ {
11262
+ field: "metadata",
11263
+ displayName: "Image",
11264
+ hideInCreate: true,
11265
+ hideInEdit: true,
11266
+ listFilter: false,
11267
+ render: /* @__PURE__ */ __name((_value, item) => renderListThumbnail(productListImageUrl(item)), "render")
11268
+ },
11040
11269
  {
11041
11270
  field: "name",
11042
11271
  displayName: "Name"
@@ -11098,6 +11327,33 @@ var init_store_crud_configs = __esm({
11098
11327
  }
11099
11328
  ]
11100
11329
  },
11330
+ {
11331
+ field: "approvalStatus",
11332
+ displayName: "Approval",
11333
+ type: "select",
11334
+ listFilter: false,
11335
+ options: [
11336
+ {
11337
+ value: "pending",
11338
+ label: "Pending"
11339
+ },
11340
+ {
11341
+ value: "approved",
11342
+ label: "Approved"
11343
+ },
11344
+ {
11345
+ value: "rejected",
11346
+ label: "Rejected"
11347
+ }
11348
+ ],
11349
+ render: /* @__PURE__ */ __name((value) => {
11350
+ const v = value == null || value === "" ? null : String(value);
11351
+ if (v === "pending") return "Pending";
11352
+ if (v === "approved") return "Approved";
11353
+ if (v === "rejected") return "Rejected";
11354
+ return "\u2014";
11355
+ }, "render")
11356
+ },
11101
11357
  {
11102
11358
  field: "featured",
11103
11359
  displayName: "Featured",
@@ -11221,6 +11477,14 @@ var init_store_crud_configs = __esm({
11221
11477
  title: "Collections",
11222
11478
  apiEndpoint: "/api/collections",
11223
11479
  columns: [
11480
+ {
11481
+ field: "image",
11482
+ displayName: "Image",
11483
+ hideInCreate: true,
11484
+ hideInEdit: true,
11485
+ listFilter: false,
11486
+ render: /* @__PURE__ */ __name((value) => renderListThumbnail(typeof value === "string" ? value : ""), "render")
11487
+ },
11224
11488
  {
11225
11489
  field: "name",
11226
11490
  displayName: "Name"
@@ -11617,6 +11881,13 @@ var init_store_crud_configs = __esm({
11617
11881
  field: "slug",
11618
11882
  displayName: "Slug"
11619
11883
  },
11884
+ {
11885
+ field: "isCatalog",
11886
+ displayName: "Catalog",
11887
+ type: "boolean",
11888
+ hideInCreate: true,
11889
+ hideInEdit: true
11890
+ },
11620
11891
  {
11621
11892
  field: "active",
11622
11893
  displayName: "Active",
@@ -11919,6 +12190,29 @@ var init_store_crud_configs = __esm({
11919
12190
  label: "Paid"
11920
12191
  }
11921
12192
  ]
12193
+ },
12194
+ {
12195
+ param: "approvalStatus",
12196
+ label: "Approval",
12197
+ type: "select",
12198
+ options: [
12199
+ {
12200
+ value: "",
12201
+ label: "All"
12202
+ },
12203
+ {
12204
+ value: "pending",
12205
+ label: "Pending"
12206
+ },
12207
+ {
12208
+ value: "approved",
12209
+ label: "Approved"
12210
+ },
12211
+ {
12212
+ value: "rejected",
12213
+ label: "Rejected"
12214
+ }
12215
+ ]
11922
12216
  }
11923
12217
  ],
11924
12218
  columns: [
@@ -11950,6 +12244,33 @@ var init_store_crud_configs = __esm({
11950
12244
  displayName: "Active",
11951
12245
  type: "boolean"
11952
12246
  },
12247
+ {
12248
+ field: "approvalStatus",
12249
+ displayName: "Approval",
12250
+ type: "select",
12251
+ listFilter: false,
12252
+ options: [
12253
+ {
12254
+ value: "pending",
12255
+ label: "Pending"
12256
+ },
12257
+ {
12258
+ value: "approved",
12259
+ label: "Approved"
12260
+ },
12261
+ {
12262
+ value: "rejected",
12263
+ label: "Rejected"
12264
+ }
12265
+ ],
12266
+ render: /* @__PURE__ */ __name((value) => {
12267
+ const v = value == null || value === "" ? null : String(value);
12268
+ if (v === "pending") return "Pending";
12269
+ if (v === "approved") return "Approved";
12270
+ if (v === "rejected") return "Rejected";
12271
+ return "\u2014";
12272
+ }, "render")
12273
+ },
11953
12274
  {
11954
12275
  field: "comingSoon",
11955
12276
  displayName: "Coming soon",
@@ -12236,7 +12557,12 @@ function SettingsPage() {
12236
12557
  const [themeSettingsLoading, setThemeSettingsLoading] = useState(true);
12237
12558
  const [storeEnabled, setStoreEnabled] = useState(false);
12238
12559
  const [multiVendorEnabled, setMultiVendorEnabled] = useState(true);
12560
+ const [vendorCanCreateCategories, setVendorCanCreateCategories] = useState(false);
12561
+ const [vendorCanCreateCollections, setVendorCanCreateCollections] = useState(false);
12562
+ const [vendorCanCreateBrands, setVendorCanCreateBrands] = useState(false);
12563
+ const [requireProductApproval, setRequireProductApproval] = useState(false);
12239
12564
  const [eventsEnabled, setEventsEnabled] = useState(true);
12565
+ const [requireEventApproval, setRequireEventApproval] = useState(false);
12240
12566
  const [storeSettingsLoading, setStoreSettingsLoading] = useState(true);
12241
12567
  const [currency, setCurrency] = useState(DEFAULT_CURRENCY);
12242
12568
  const [currencies, setCurrencies] = useState([]);
@@ -12296,10 +12622,15 @@ function SettingsPage() {
12296
12622
  }).finally(() => setStoreSettingsLoading(false));
12297
12623
  fetch("/api/settings/multi_vendor").then((r) => r.ok ? r.json() : {}).then((data) => {
12298
12624
  setMultiVendorEnabled(data.enabled !== "false");
12625
+ setVendorCanCreateCategories(data.vendorCanCreateCategories === "true");
12626
+ setVendorCanCreateCollections(data.vendorCanCreateCollections === "true");
12627
+ setVendorCanCreateBrands(data.vendorCanCreateBrands === "true");
12628
+ setRequireProductApproval(data.requireProductApproval === "true");
12299
12629
  }).catch(() => {
12300
12630
  });
12301
12631
  fetch("/api/settings/events").then((r) => r.ok ? r.json() : {}).then((data) => {
12302
12632
  setEventsEnabled(data.enabled !== "false");
12633
+ setRequireEventApproval(data.requireEventApproval === "true");
12303
12634
  }).catch(() => {
12304
12635
  });
12305
12636
  fetch("/api/currencies/exchange-rates").then((r) => r.ok ? r.json() : []).then((data) => {
@@ -12427,6 +12758,22 @@ function SettingsPage() {
12427
12758
  enabled: {
12428
12759
  value: multiVendorEnabled ? "true" : "false",
12429
12760
  type: "public"
12761
+ },
12762
+ vendorCanCreateCategories: {
12763
+ value: vendorCanCreateCategories ? "true" : "false",
12764
+ type: "public"
12765
+ },
12766
+ vendorCanCreateCollections: {
12767
+ value: vendorCanCreateCollections ? "true" : "false",
12768
+ type: "public"
12769
+ },
12770
+ vendorCanCreateBrands: {
12771
+ value: vendorCanCreateBrands ? "true" : "false",
12772
+ type: "public"
12773
+ },
12774
+ requireProductApproval: {
12775
+ value: requireProductApproval ? "true" : "false",
12776
+ type: "public"
12430
12777
  }
12431
12778
  })
12432
12779
  });
@@ -12439,6 +12786,10 @@ function SettingsPage() {
12439
12786
  enabled: {
12440
12787
  value: eventsEnabled ? "true" : "false",
12441
12788
  type: "public"
12789
+ },
12790
+ requireEventApproval: {
12791
+ value: requireEventApproval ? "true" : "false",
12792
+ type: "public"
12442
12793
  }
12443
12794
  })
12444
12795
  });
@@ -12710,8 +13061,46 @@ function SettingsPage() {
12710
13061
  className: "text-sm font-medium text-gray-700"
12711
13062
  }, "Multi Vendor"), /* @__PURE__ */ React.createElement("p", {
12712
13063
  className: "text-xs text-gray-500"
12713
- }, "Enable vendor portals, vendor logins, and vendor-scoped store data. Disabling this blocks vendor-only accounts from signing in."))), storeEnabled && /* @__PURE__ */ React.createElement("div", {
12714
- className: "mt-4 flex items-center gap-3 p-4 bg-gray-50 rounded-lg border border-gray-200"
13064
+ }, "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", {
13065
+ className: "mt-4 space-y-3 p-4 bg-gray-50 rounded-lg border border-gray-200"
13066
+ }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", {
13067
+ className: "text-sm font-medium text-gray-700"
13068
+ }, "Vendor catalog permissions"), /* @__PURE__ */ React.createElement("p", {
13069
+ className: "text-xs text-gray-500 mb-3"
13070
+ }, "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", {
13071
+ className: "flex items-center gap-3"
13072
+ }, /* @__PURE__ */ React.createElement(Switch, {
13073
+ checked: vendorCanCreateCategories,
13074
+ onCheckedChange: setVendorCanCreateCategories
13075
+ }), /* @__PURE__ */ React.createElement(Label3, {
13076
+ className: "text-sm font-medium text-gray-700"
13077
+ }, "Can vendors create categories")), /* @__PURE__ */ React.createElement("div", {
13078
+ className: "flex items-center gap-3"
13079
+ }, /* @__PURE__ */ React.createElement(Switch, {
13080
+ checked: vendorCanCreateCollections,
13081
+ onCheckedChange: setVendorCanCreateCollections
13082
+ }), /* @__PURE__ */ React.createElement(Label3, {
13083
+ className: "text-sm font-medium text-gray-700"
13084
+ }, "Can vendors create collections")), /* @__PURE__ */ React.createElement("div", {
13085
+ className: "flex items-center gap-3"
13086
+ }, /* @__PURE__ */ React.createElement(Switch, {
13087
+ checked: vendorCanCreateBrands,
13088
+ onCheckedChange: setVendorCanCreateBrands
13089
+ }), /* @__PURE__ */ React.createElement(Label3, {
13090
+ className: "text-sm font-medium text-gray-700"
13091
+ }, "Can vendors create brands")), /* @__PURE__ */ React.createElement("div", {
13092
+ className: "flex items-start gap-3"
13093
+ }, /* @__PURE__ */ React.createElement(Switch, {
13094
+ checked: requireProductApproval,
13095
+ onCheckedChange: setRequireProductApproval
13096
+ }), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(Label3, {
13097
+ className: "text-sm font-medium text-gray-700"
13098
+ }, "Require product approval"), /* @__PURE__ */ React.createElement("p", {
13099
+ className: "text-xs text-gray-500 mt-0.5"
13100
+ }, "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", {
13101
+ className: "mt-4 space-y-3 p-4 bg-gray-50 rounded-lg border border-gray-200"
13102
+ }, /* @__PURE__ */ React.createElement("div", {
13103
+ className: "flex items-center gap-3"
12715
13104
  }, /* @__PURE__ */ React.createElement(Switch, {
12716
13105
  checked: eventsEnabled,
12717
13106
  onCheckedChange: setEventsEnabled
@@ -12719,7 +13108,16 @@ function SettingsPage() {
12719
13108
  className: "text-sm font-medium text-gray-700"
12720
13109
  }, "Events"), /* @__PURE__ */ React.createElement("p", {
12721
13110
  className: "text-xs text-gray-500"
12722
- }, "Enable event management. Disabling this hides the Events tab and blocks all event-related APIs."))), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", {
13111
+ }, "Enable event management. Disabling this hides the Events tab and blocks all event-related APIs."))), eventsEnabled && multiVendorEnabled ? /* @__PURE__ */ React.createElement("div", {
13112
+ className: "flex items-start gap-3 pl-1 border-t border-gray-200 pt-3"
13113
+ }, /* @__PURE__ */ React.createElement(Switch, {
13114
+ checked: requireEventApproval,
13115
+ onCheckedChange: setRequireEventApproval
13116
+ }), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(Label3, {
13117
+ className: "text-sm font-medium text-gray-700"
13118
+ }, "Require event approval"), /* @__PURE__ */ React.createElement("p", {
13119
+ className: "text-xs text-gray-500 mt-0.5"
13120
+ }, "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", {
12723
13121
  className: "flex items-center justify-between mb-1"
12724
13122
  }, /* @__PURE__ */ React.createElement("label", {
12725
13123
  className: "block text-sm font-semibold text-gray-700"
@@ -16989,15 +17387,13 @@ function PluginSettingsPanel({ descriptor, onSaved }) {
16989
17387
  className: "text-xs text-gray-500 dark:text-gray-400"
16990
17388
  }, "Layout below merges with Branding settings; values here override branding when set. Use absolute URLs for logos in email."), /* @__PURE__ */ React.createElement("div", {
16991
17389
  className: "space-y-1"
16992
- }, /* @__PURE__ */ React.createElement(Label3, {
16993
- htmlFor: `${settingsGroup}-logoUrl`,
16994
- className: "text-sm"
16995
- }, "Logo URL (optional override)"), /* @__PURE__ */ React.createElement(Input, {
16996
- id: `${settingsGroup}-logoUrl`,
17390
+ }, /* @__PURE__ */ React.createElement(ImageOrUrlField, {
17391
+ label: "Logo (optional override)",
16997
17392
  value: logoUrl,
16998
- onChange: /* @__PURE__ */ __name((e) => setLogoUrl(e.target.value), "onChange"),
17393
+ onChange: setLogoUrl,
17394
+ previewVariant: "logo",
16999
17395
  placeholder: "https://\u2026",
17000
- className: "h-8 text-sm"
17396
+ inputClassName: "h-8 text-sm w-full rounded-md border border-input bg-background px-3"
17001
17397
  })), /* @__PURE__ */ React.createElement("div", {
17002
17398
  className: "space-y-1"
17003
17399
  }, /* @__PURE__ */ React.createElement(Label3, {
@@ -17050,22 +17446,23 @@ function PluginSettingsPanel({ descriptor, onSaved }) {
17050
17446
  className: "flex flex-wrap items-end gap-2 border-b border-border/60 pb-3 dark:border-gray-600"
17051
17447
  }, /* @__PURE__ */ React.createElement("div", {
17052
17448
  className: "min-w-[160px] flex-1 space-y-1"
17053
- }, /* @__PURE__ */ React.createElement(Label3, {
17054
- className: "text-xs text-muted-foreground"
17055
- }, "Icon image URL"), /* @__PURE__ */ React.createElement(Input, {
17449
+ }, /* @__PURE__ */ React.createElement(ImageOrUrlField, {
17450
+ label: "Icon image",
17056
17451
  value: row.iconUrl,
17057
- onChange: /* @__PURE__ */ __name((e) => {
17452
+ onChange: /* @__PURE__ */ __name((v) => {
17058
17453
  const next = [
17059
17454
  ...socialLinkRows
17060
17455
  ];
17061
17456
  next[i] = {
17062
17457
  ...next[i],
17063
- iconUrl: e.target.value
17458
+ iconUrl: v
17064
17459
  };
17065
17460
  setSocialLinkRows(next);
17066
17461
  }, "onChange"),
17462
+ previewVariant: "logo",
17067
17463
  placeholder: "https://\u2026",
17068
- className: "h-8 text-sm"
17464
+ inputClassName: "h-8 text-sm w-full rounded-md border border-input bg-background px-3",
17465
+ labelClassName: "text-xs text-muted-foreground"
17069
17466
  })), /* @__PURE__ */ React.createElement("div", {
17070
17467
  className: "min-w-[160px] flex-1 space-y-1"
17071
17468
  }, /* @__PURE__ */ React.createElement(Label3, {
@@ -17712,15 +18109,13 @@ function PluginSettingsPanel({ descriptor, onSaved }) {
17712
18109
  className: "h-8 text-sm"
17713
18110
  })), /* @__PURE__ */ React.createElement("div", {
17714
18111
  className: "space-y-1"
17715
- }, /* @__PURE__ */ React.createElement(Label3, {
17716
- htmlFor: `${settingsGroup}-iconImageUrl`,
17717
- className: "text-sm"
17718
- }, "Icon image URL"), /* @__PURE__ */ React.createElement(Input, {
17719
- id: `${settingsGroup}-iconImageUrl`,
18112
+ }, /* @__PURE__ */ React.createElement(ImageOrUrlField, {
18113
+ label: "Icon image",
17720
18114
  value: iconImageUrl,
17721
- onChange: /* @__PURE__ */ __name((e) => setIconImageUrl(e.target.value), "onChange"),
18115
+ onChange: setIconImageUrl,
18116
+ previewVariant: "logo",
17722
18117
  placeholder: "https://\u2026 or /images/chat-icon.png",
17723
- className: "h-8 text-sm"
18118
+ inputClassName: "h-8 text-sm w-full rounded-md border border-input bg-background px-3"
17724
18119
  }), /* @__PURE__ */ React.createElement("p", {
17725
18120
  className: "text-xs text-gray-500 dark:text-gray-400"
17726
18121
  }, "PNG or image URL. Leave empty to use emoji below.")), /* @__PURE__ */ React.createElement("div", {
@@ -18280,6 +18675,7 @@ var init_PluginsPage = __esm({
18280
18675
  init_checkbox();
18281
18676
  init_select();
18282
18677
  init_EventNotificationsPluginSettings();
18678
+ init_ImageOrUrlField();
18283
18679
  init_chat_email_intent();
18284
18680
  init_llm_agent_scope();
18285
18681
  __name(normalizeLinkedInOrganizations, "normalizeLinkedInOrganizations");
@@ -19963,7 +20359,7 @@ var init_VendorPortalProfilePage = __esm({
19963
20359
  __name(VendorPortalProfilePage, "VendorPortalProfilePage");
19964
20360
  }
19965
20361
  });
19966
- function DetailPageHeader({ title, subtitle, backHref, backLabel = "Back", closeHref, onClose, menuItems = [] }) {
20362
+ function DetailPageHeader({ title, subtitle, backHref, backLabel = "Back", closeHref, onClose, headerExtra, menuItems = [] }) {
19967
20363
  const router = useRouter();
19968
20364
  const handleClose = /* @__PURE__ */ __name(() => {
19969
20365
  if (onClose) onClose();
@@ -19987,7 +20383,7 @@ function DetailPageHeader({ title, subtitle, backHref, backLabel = "Back", close
19987
20383
  className: "text-sm text-gray-400 truncate"
19988
20384
  }, subtitle))), /* @__PURE__ */ React.createElement("div", {
19989
20385
  className: "flex items-center gap-2 shrink-0"
19990
- }, menuItems.length > 0 && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", {
20386
+ }, headerExtra, menuItems.length > 0 && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", {
19991
20387
  className: "flex items-center gap-2 md:hidden"
19992
20388
  }, menuItems.map((item, i) => {
19993
20389
  const Icon2 = item.icon;
@@ -25167,15 +25563,11 @@ function SeoTabContent(props) {
25167
25563
  onChange: /* @__PURE__ */ __name((e) => props.setSeoOgDescription(e.target.value), "onChange"),
25168
25564
  placeholder: "Open Graph description",
25169
25565
  rows: 2
25170
- })), /* @__PURE__ */ React26__default.createElement("div", {
25171
- className: "space-y-1.5"
25172
- }, /* @__PURE__ */ React26__default.createElement(Label3, {
25173
- className: "text-xs"
25174
- }, "OG Image"), /* @__PURE__ */ React26__default.createElement(Input, {
25566
+ })), /* @__PURE__ */ React26__default.createElement(ImageOrUrlField, {
25567
+ label: "OG Image",
25175
25568
  value: props.seoOgImage,
25176
- onChange: /* @__PURE__ */ __name((e) => props.setSeoOgImage(e.target.value), "onChange"),
25177
- placeholder: "https://..."
25178
- })));
25569
+ onChange: props.setSeoOgImage
25570
+ }));
25179
25571
  }
25180
25572
  function CollapsibleSection({ title, icon: Icon2, open, onToggle, children }) {
25181
25573
  return /* @__PURE__ */ React26__default.createElement("div", {
@@ -25561,6 +25953,7 @@ var init_PageBuilderPage = __esm({
25561
25953
  init_ComponentSettings();
25562
25954
  init_admin_config_context();
25563
25955
  init_registry();
25956
+ init_ImageOrUrlField();
25564
25957
  __name(createSelectable, "createSelectable");
25565
25958
  __name(buildEditorResolver, "buildEditorResolver");
25566
25959
  __name(getIcon, "getIcon");
@@ -25623,15 +26016,12 @@ function SeoSection({ values, onChange }) {
25623
26016
  onChange: /* @__PURE__ */ __name((e) => onChange("seoOgDescription", e.target.value), "onChange"),
25624
26017
  className: textareaCls,
25625
26018
  rows: 2
25626
- })), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("label", {
25627
- className: "block text-xs font-medium text-gray-600 mb-1"
25628
- }, "OG Image"), /* @__PURE__ */ React.createElement("input", {
25629
- type: "url",
26019
+ })), /* @__PURE__ */ React.createElement(ImageOrUrlField, {
26020
+ label: "OG Image",
25630
26021
  value: values.seoOgImage,
25631
- onChange: /* @__PURE__ */ __name((e) => onChange("seoOgImage", e.target.value), "onChange"),
25632
- placeholder: "https://...",
25633
- className: inputCls9
25634
- }))));
26022
+ onChange: /* @__PURE__ */ __name((v) => onChange("seoOgImage", v), "onChange"),
26023
+ inputClassName: inputCls9
26024
+ })));
25635
26025
  }
25636
26026
  async function saveSeo(seo, slug, existingSeoId) {
25637
26027
  const hasSeo = seo.seoTitle || seo.seoDescription || seo.seoKeywords || seo.seoOgTitle || seo.seoOgDescription || seo.seoOgImage;
@@ -25696,6 +26086,7 @@ async function fetchSeo(seoId) {
25696
26086
  var init_SeoSection = __esm({
25697
26087
  "src/components/Admin/SeoSection.tsx"() {
25698
26088
  "use client";
26089
+ init_ImageOrUrlField();
25699
26090
  __name(SeoSection, "SeoSection");
25700
26091
  __name(saveSeo, "saveSeo");
25701
26092
  __name(fetchSeo, "fetchSeo");
@@ -25710,6 +26101,8 @@ __export(BrandEditPage_exports, {
25710
26101
  function BrandEditPage({ brandId }) {
25711
26102
  const router = useRouter();
25712
26103
  const searchParams = useSearchParams();
26104
+ const { data: session } = useSession();
26105
+ const vendorPortal = isVendorPortalUser(session?.user);
25713
26106
  const listReturnUrl = safeAdminListReturnUrl(searchParams.get("from")) ?? "/admin/brands";
25714
26107
  const create = isCreate(brandId);
25715
26108
  const dupParam = searchParams.get("duplicateFrom");
@@ -25811,6 +26204,9 @@ function BrandEditPage({ brandId }) {
25811
26204
  active,
25812
26205
  sortOrder
25813
26206
  };
26207
+ if (!vendorPortal) {
26208
+ payload.isCatalog = true;
26209
+ }
25814
26210
  if (savedSeoId) payload.seoId = savedSeoId;
25815
26211
  const res = await fetch(create ? "/api/brands" : `/api/brands/${brandId}`, {
25816
26212
  method: create ? "POST" : "PUT",
@@ -25902,13 +26298,12 @@ function BrandEditPage({ brandId }) {
25902
26298
  onChange: /* @__PURE__ */ __name((e) => setDescription(e.target.value), "onChange"),
25903
26299
  className: "w-full rounded-md border border-gray-300 px-3 py-2 text-sm min-h-[80px]",
25904
26300
  rows: 3
25905
- })), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("label", {
25906
- className: "block text-xs font-medium text-gray-600 mb-1"
25907
- }, "Logo URL"), /* @__PURE__ */ React.createElement("input", {
25908
- type: "url",
26301
+ })), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(ImageOrUrlField, {
26302
+ label: "Logo",
25909
26303
  value: logo,
25910
- onChange: /* @__PURE__ */ __name((e) => setLogo(e.target.value), "onChange"),
25911
- className: "w-full rounded-md border border-gray-300 px-3 py-2 text-sm"
26304
+ onChange: setLogo,
26305
+ previewVariant: "logo",
26306
+ inputClassName: "w-full rounded-md border border-gray-300 px-3 py-2 text-sm"
25912
26307
  })), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("label", {
25913
26308
  className: "block text-xs font-medium text-gray-600 mb-1"
25914
26309
  }, "Sort order"), /* @__PURE__ */ React.createElement("input", {
@@ -25951,6 +26346,8 @@ var init_BrandEditPage = __esm({
25951
26346
  init_SeoSection();
25952
26347
  init_DetailPageLayout();
25953
26348
  init_DetailPageHeader();
26349
+ init_vendor_scope();
26350
+ init_ImageOrUrlField();
25954
26351
  isCreate = /* @__PURE__ */ __name((id) => id === "create", "isCreate");
25955
26352
  __name(BrandEditPage, "BrandEditPage");
25956
26353
  }
@@ -26858,16 +27255,38 @@ function ProductVariantsSection({ hasVariants, onHasVariantsChange, variantOptio
26858
27255
  }, "Reserved"), /* @__PURE__ */ React.createElement("option", {
26859
27256
  value: "sold"
26860
27257
  }, "Sold"))), /* @__PURE__ */ React.createElement("td", {
26861
- className: "px-3 py-2 align-top"
26862
- }, /* @__PURE__ */ React.createElement("textarea", {
26863
- value: row.imageUrlsText,
26864
- onChange: /* @__PURE__ */ __name((e) => setVariantField(i, "imageUrlsText", e.target.value), "onChange"),
26865
- placeholder: "https://\u2026/black-s.jpg\nhttps://\u2026/black-back.jpg",
26866
- className: `${inputCls2} min-h-[72px] min-w-[220px]`,
26867
- rows: 3
26868
- }), /* @__PURE__ */ React.createElement("p", {
26869
- className: "mt-1 text-[11px] text-gray-500"
26870
- }, "One URL per line. First image is used on PDP.")), /* @__PURE__ */ React.createElement("td", {
27258
+ className: "px-3 py-2 align-top min-w-[240px]"
27259
+ }, (() => {
27260
+ const lines = row.imageUrlsText === "" ? [
27261
+ ""
27262
+ ] : row.imageUrlsText.split("\n");
27263
+ return /* @__PURE__ */ React.createElement("div", {
27264
+ className: "space-y-2"
27265
+ }, lines.map((url, ui) => /* @__PURE__ */ React.createElement(ImageOrUrlField, {
27266
+ key: ui,
27267
+ label: ui === 0 ? "Images" : `Image ${ui + 1}`,
27268
+ value: url,
27269
+ onChange: /* @__PURE__ */ __name((v) => {
27270
+ const next = [
27271
+ ...lines
27272
+ ];
27273
+ next[ui] = v;
27274
+ setVariantField(i, "imageUrlsText", next.join("\n"));
27275
+ }, "onChange"),
27276
+ inputClassName: inputCls2
27277
+ })), /* @__PURE__ */ React.createElement("button", {
27278
+ type: "button",
27279
+ onClick: /* @__PURE__ */ __name(() => setVariantField(i, "imageUrlsText", [
27280
+ ...lines,
27281
+ ""
27282
+ ].join("\n")), "onClick"),
27283
+ 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"
27284
+ }, /* @__PURE__ */ React.createElement(Plus, {
27285
+ className: "h-3 w-3"
27286
+ }), " Add image"), /* @__PURE__ */ React.createElement("p", {
27287
+ className: "text-[11px] text-gray-500"
27288
+ }, "First image is used on PDP."));
27289
+ })()), /* @__PURE__ */ React.createElement("td", {
26871
27290
  className: "px-3 py-2 align-top text-right"
26872
27291
  }, /* @__PURE__ */ React.createElement("button", {
26873
27292
  type: "button",
@@ -26882,6 +27301,7 @@ var init_ProductVariantsSection = __esm({
26882
27301
  "src/admin/pages/ProductVariantsSection.tsx"() {
26883
27302
  "use client";
26884
27303
  init_product_variants();
27304
+ init_ImageOrUrlField();
26885
27305
  labelCls2 = "block text-xs font-medium text-gray-600 mb-1";
26886
27306
  inputCls2 = "w-full rounded-md border border-gray-300 px-2 py-1.5 text-sm";
26887
27307
  sectionCls2 = "min-w-0 overflow-hidden border border-gray-200 rounded-lg p-4 bg-gray-50/50";
@@ -27077,13 +27497,16 @@ function ProductEditPage({ productId }) {
27077
27497
  const router = useRouter();
27078
27498
  const searchParams = useSearchParams();
27079
27499
  const { data: session } = useSession();
27500
+ const { eventsEnabled, requireProductApproval } = useContext(AdminConfigContext);
27501
+ const eventsOn = eventsEnabled !== false;
27502
+ const approvalOn = requireProductApproval === true;
27080
27503
  const vendorPortal = isVendorPortalUser(session?.user);
27081
27504
  const categoryIdParam = searchParams.get("categoryId")?.trim() ?? "";
27082
27505
  const collectionIdParam = searchParams.get("collectionId")?.trim() ?? "";
27083
27506
  const eventIdParam = searchParams.get("eventId")?.trim() ?? "";
27084
27507
  const lockCategoryFromStore = isCreate2(productId) && /^\d+$/.test(categoryIdParam);
27085
27508
  const lockCollectionFromQuery = isCreate2(productId) && /^\d+$/.test(collectionIdParam);
27086
- const lockEventFromQuery = isCreate2(productId) && /^\d+$/.test(eventIdParam);
27509
+ const lockEventFromQuery = eventsOn && isCreate2(productId) && /^\d+$/.test(eventIdParam);
27087
27510
  const listReturnUrl = safeAdminListReturnUrl(searchParams.get("from")) ?? (lockCategoryFromStore ? `/admin/products?categoryId=${categoryIdParam}` : "/admin/products");
27088
27511
  const create = isCreate2(productId);
27089
27512
  const dupParam = searchParams.get("duplicateFrom");
@@ -27114,6 +27537,11 @@ function ProductEditPage({ productId }) {
27114
27537
  const [compareAtPrice, setCompareAtPrice] = useState(0);
27115
27538
  const [quantity, setQuantity] = useState(1);
27116
27539
  const [status, setStatus] = useState("draft");
27540
+ const [approvalStatus, setApprovalStatus] = useState("pending");
27541
+ const [rejectionReason, setRejectionReason] = useState("");
27542
+ const [rejectModalOpen, setRejectModalOpen] = useState(false);
27543
+ const [rejectDraft, setRejectDraft] = useState("");
27544
+ const approvalBeforeRejectRef = useRef("pending");
27117
27545
  const [featured, setFeatured] = useState(false);
27118
27546
  const [description, setDescription] = useState("");
27119
27547
  const [images, setImages] = useState([
@@ -27218,7 +27646,7 @@ function ProductEditPage({ productId }) {
27218
27646
  try {
27219
27647
  const [brandRes, catRes, attrRes, taxesRes, eventsRes, formsRes, refundRes] = await Promise.all([
27220
27648
  fetch("/api/brands?limit=500"),
27221
- fetch("/api/product_categories?limit=500&isCatalog=true"),
27649
+ fetch(vendorPortal ? "/api/product_categories?limit=500" : "/api/product_categories?limit=500&isCatalog=true"),
27222
27650
  fetch("/api/attributes?limit=500"),
27223
27651
  fetch("/api/taxes?limit=200&sortField=name&sortOrder=asc"),
27224
27652
  fetch("/api/events?limit=200&sortField=startDate&sortOrder=desc"),
@@ -27331,6 +27759,8 @@ function ProductEditPage({ productId }) {
27331
27759
  setCompareAtPrice(Number.isFinite(rawCompare) ? rawCompare : 0);
27332
27760
  setQuantity(product.quantity ?? 1);
27333
27761
  setStatus(product.status ?? "draft");
27762
+ setApprovalStatus(typeof product.approvalStatus === "string" && product.approvalStatus ? product.approvalStatus : "pending");
27763
+ setRejectionReason(typeof product.rejectionReason === "string" ? product.rejectionReason : "");
27334
27764
  setFeatured(product.featured ?? false);
27335
27765
  setDescription((m && typeof m.description === "string" ? m.description : "") ?? "");
27336
27766
  const rawImages = m?.images;
@@ -27466,6 +27896,16 @@ function ProductEditPage({ productId }) {
27466
27896
  create,
27467
27897
  eventId
27468
27898
  ]);
27899
+ useEffect(() => {
27900
+ if (create && vendorPortal && approvalOn) {
27901
+ setApprovalStatus("pending");
27902
+ setStatus((s) => s === "available" ? "draft" : s);
27903
+ }
27904
+ }, [
27905
+ create,
27906
+ vendorPortal,
27907
+ approvalOn
27908
+ ]);
27469
27909
  useEffect(() => {
27470
27910
  if (!create || !name.trim()) return;
27471
27911
  setProductSlug(slugifyProductName(name));
@@ -27500,6 +27940,33 @@ function ProductEditPage({ productId }) {
27500
27940
  }
27501
27941
  setter(num);
27502
27942
  }, "handleNumberChange");
27943
+ const openRejectModal = /* @__PURE__ */ __name((fromStatus) => {
27944
+ approvalBeforeRejectRef.current = fromStatus === "rejected" ? "pending" : fromStatus;
27945
+ setRejectDraft(rejectionReason);
27946
+ setApprovalStatus("rejected");
27947
+ setRejectModalOpen(true);
27948
+ }, "openRejectModal");
27949
+ const confirmRejectReason = /* @__PURE__ */ __name(() => {
27950
+ const reason = rejectDraft.trim();
27951
+ if (!reason) return;
27952
+ setRejectionReason(reason);
27953
+ setRejectModalOpen(false);
27954
+ }, "confirmRejectReason");
27955
+ const cancelRejectModal = /* @__PURE__ */ __name(() => {
27956
+ if (!rejectionReason.trim()) {
27957
+ setApprovalStatus(approvalBeforeRejectRef.current || "pending");
27958
+ }
27959
+ setRejectDraft(rejectionReason);
27960
+ setRejectModalOpen(false);
27961
+ }, "cancelRejectModal");
27962
+ const handleApprovalSelect = /* @__PURE__ */ __name((value) => {
27963
+ if (value === "rejected") {
27964
+ openRejectModal(approvalStatus);
27965
+ return;
27966
+ }
27967
+ setApprovalStatus(value);
27968
+ if (value !== "rejected") setRejectionReason("");
27969
+ }, "handleApprovalSelect");
27503
27970
  const handleSave = /* @__PURE__ */ __name(async () => {
27504
27971
  setErrors([]);
27505
27972
  if (!name.trim()) {
@@ -27508,6 +27975,14 @@ function ProductEditPage({ productId }) {
27508
27975
  ]);
27509
27976
  return;
27510
27977
  }
27978
+ if (approvalOn && !vendorPortal && approvalStatus === "rejected" && !rejectionReason.trim()) {
27979
+ setRejectDraft("");
27980
+ setRejectModalOpen(true);
27981
+ setErrors([
27982
+ "Rejection reason is required"
27983
+ ]);
27984
+ return;
27985
+ }
27511
27986
  if (!defaultPriceStr.trim()) {
27512
27987
  setErrors([
27513
27988
  `${pricingConfig.defaultCurrency} price is required`
@@ -27570,11 +28045,21 @@ function ProductEditPage({ productId }) {
27570
28045
  currencyPrices: null,
27571
28046
  compareAtPrice: compareAtPriceValue,
27572
28047
  quantity: resolvedQuantity,
27573
- status,
28048
+ status: create && vendorPortal && approvalOn && status === "available" ? "draft" : status,
27574
28049
  featured,
27575
28050
  contactFormId,
27576
28051
  metadata
27577
28052
  };
28053
+ if (approvalOn) {
28054
+ if (vendorPortal && create) {
28055
+ productPayload.approvalStatus = "pending";
28056
+ } else if (!vendorPortal) {
28057
+ productPayload.approvalStatus = approvalStatus;
28058
+ if (approvalStatus === "rejected") {
28059
+ productPayload.rejectionReason = rejectionReason.trim();
28060
+ }
28061
+ }
28062
+ }
27578
28063
  const res = await fetch(create ? "/api/products" : `/api/products/${productId}`, {
27579
28064
  method: create ? "POST" : "PUT",
27580
28065
  headers: {
@@ -27596,6 +28081,15 @@ function ProductEditPage({ productId }) {
27596
28081
  if (typeof savedProduct.slug === "string") {
27597
28082
  setProductSlug(savedProduct.slug);
27598
28083
  }
28084
+ if (typeof savedProduct.status === "string") {
28085
+ setStatus(savedProduct.status);
28086
+ }
28087
+ if (typeof savedProduct.approvalStatus === "string") {
28088
+ setApprovalStatus(savedProduct.approvalStatus);
28089
+ }
28090
+ if (approvalStatus === "approved") {
28091
+ setRejectionReason("");
28092
+ }
27599
28093
  const savedId = create ? savedProduct.id : productId;
27600
28094
  const savedSeoId = await saveSeo(seo, productSlugValue, seoId);
27601
28095
  const linkedSeoId = savedSeoId ?? savedProduct.seoId ?? null;
@@ -27796,7 +28290,7 @@ function ProductEditPage({ productId }) {
27796
28290
  }
27797
28291
  setVariantRows([]);
27798
28292
  }
27799
- if (create && eventId != null) {
28293
+ if (create && eventsOn && eventId != null) {
27800
28294
  const attachRes = await fetch("/api/event_products", {
27801
28295
  method: "POST",
27802
28296
  headers: {
@@ -27901,6 +28395,27 @@ function ProductEditPage({ productId }) {
27901
28395
  title: pageTitle,
27902
28396
  subtitle: pageSubtitle,
27903
28397
  closeHref: listReturnUrl,
28398
+ headerExtra: approvalOn && !vendorPortal ? /* @__PURE__ */ React.createElement("div", {
28399
+ className: "flex items-center gap-2"
28400
+ }, /* @__PURE__ */ React.createElement("select", {
28401
+ value: approvalStatus,
28402
+ onChange: /* @__PURE__ */ __name((e) => handleApprovalSelect(e.target.value), "onChange"),
28403
+ className: "h-8 rounded-md border border-gray-600 bg-gray-900 text-white text-xs px-2 max-w-[10rem]",
28404
+ "aria-label": "Approval status"
28405
+ }, /* @__PURE__ */ React.createElement("option", {
28406
+ value: "pending"
28407
+ }, "Pending"), /* @__PURE__ */ React.createElement("option", {
28408
+ value: "approved"
28409
+ }, "Approve"), /* @__PURE__ */ React.createElement("option", {
28410
+ value: "rejected"
28411
+ }, "Reject")), approvalStatus === "rejected" ? /* @__PURE__ */ React.createElement("button", {
28412
+ type: "button",
28413
+ onClick: /* @__PURE__ */ __name(() => openRejectModal("rejected"), "onClick"),
28414
+ className: "text-xs text-amber-300 hover:text-amber-200 underline underline-offset-2 max-w-[9rem] truncate",
28415
+ title: rejectionReason || "Add rejection reason"
28416
+ }, rejectionReason.trim() ? "Edit reason" : "Add reason") : null) : approvalOn && vendorPortal ? /* @__PURE__ */ React.createElement("span", {
28417
+ className: "text-xs text-gray-300 capitalize hidden sm:inline"
28418
+ }, approvalStatus.replace(/_/g, " ")) : null,
27904
28419
  menuItems: [
27905
28420
  {
27906
28421
  label: saving ? "Saving..." : "Save",
@@ -27913,7 +28428,31 @@ function ProductEditPage({ productId }) {
27913
28428
  onClick: /* @__PURE__ */ __name(() => setFeatured(!featured), "onClick")
27914
28429
  }
27915
28430
  ]
27916
- }), errors.length > 0 && /* @__PURE__ */ React.createElement("div", {
28431
+ }), /* @__PURE__ */ React.createElement(Dialog, {
28432
+ open: rejectModalOpen,
28433
+ onOpenChange: /* @__PURE__ */ __name((open) => {
28434
+ if (!open) cancelRejectModal();
28435
+ }, "onOpenChange")
28436
+ }, /* @__PURE__ */ React.createElement(DialogContent, {
28437
+ className: "max-w-md"
28438
+ }, /* @__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", {
28439
+ value: rejectDraft,
28440
+ onChange: /* @__PURE__ */ __name((e) => setRejectDraft(e.target.value), "onChange"),
28441
+ className: "w-full min-h-[100px] rounded-md border border-gray-300 px-3 py-2 text-sm",
28442
+ placeholder: "Explain what needs to change\u2026",
28443
+ autoFocus: true
28444
+ }), /* @__PURE__ */ React.createElement(DialogFooter, {
28445
+ className: "gap-2 sm:gap-0"
28446
+ }, /* @__PURE__ */ React.createElement(Button, {
28447
+ type: "button",
28448
+ variant: "outline",
28449
+ onClick: cancelRejectModal
28450
+ }, "Cancel"), /* @__PURE__ */ React.createElement(Button, {
28451
+ type: "button",
28452
+ variant: "destructive",
28453
+ disabled: !rejectDraft.trim(),
28454
+ onClick: confirmRejectReason
28455
+ }, "Confirm reject")))), errors.length > 0 && /* @__PURE__ */ React.createElement("div", {
27917
28456
  className: "bg-red-50 border-l-4 border-red-400 p-4 mx-6 mt-4"
27918
28457
  }, /* @__PURE__ */ React.createElement("div", {
27919
28458
  className: "flex"
@@ -28036,7 +28575,7 @@ function ProductEditPage({ productId }) {
28036
28575
  className: "mt-1 text-xs text-gray-500"
28037
28576
  }, "Only collections in the selected category are listed.") : /* @__PURE__ */ React.createElement("p", {
28038
28577
  className: "mt-1 text-xs text-gray-500"
28039
- }, "Select a category to load collections.")), create ? /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("label", {
28578
+ }, "Select a category to load collections.")), create && eventsOn ? /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("label", {
28040
28579
  className: labelCls3
28041
28580
  }, "Event"), /* @__PURE__ */ React.createElement("select", {
28042
28581
  value: eventId ?? "",
@@ -28132,7 +28671,8 @@ function ProductEditPage({ productId }) {
28132
28671
  }, "Status"), /* @__PURE__ */ React.createElement("select", {
28133
28672
  value: status,
28134
28673
  onChange: /* @__PURE__ */ __name((e) => setStatus(e.target.value), "onChange"),
28135
- className: inputCls3
28674
+ className: inputCls3,
28675
+ disabled: approvalOn && vendorPortal && (approvalStatus === "pending" || approvalStatus === "rejected")
28136
28676
  }, /* @__PURE__ */ React.createElement("option", {
28137
28677
  value: "draft"
28138
28678
  }, "Draft"), /* @__PURE__ */ React.createElement("option", {
@@ -28141,7 +28681,11 @@ function ProductEditPage({ productId }) {
28141
28681
  value: "reserved"
28142
28682
  }, "Reserved"), /* @__PURE__ */ React.createElement("option", {
28143
28683
  value: "sold"
28144
- }, "Sold")))))), /* @__PURE__ */ React.createElement(ProductVariantsSection, {
28684
+ }, "Sold")), approvalOn && vendorPortal && approvalStatus === "pending" ? /* @__PURE__ */ React.createElement("p", {
28685
+ className: "mt-1 text-xs text-gray-500"
28686
+ }, "Waiting for admin approval. The product goes live when approved.") : null, approvalOn && vendorPortal && approvalStatus === "rejected" ? /* @__PURE__ */ React.createElement("p", {
28687
+ className: "mt-1 text-xs text-red-600"
28688
+ }, "Rejected", rejectionReason ? `: ${rejectionReason}` : "", ".") : null)))), /* @__PURE__ */ React.createElement(ProductVariantsSection, {
28145
28689
  hasVariants,
28146
28690
  onHasVariantsChange: setHasVariants,
28147
28691
  variantOptionRows,
@@ -28268,14 +28812,11 @@ function ProductEditPage({ productId }) {
28268
28812
  className: "flex flex-wrap items-start gap-2 p-2 bg-white rounded border border-gray-200"
28269
28813
  }, /* @__PURE__ */ React.createElement("div", {
28270
28814
  className: "flex-1 min-w-[200px]"
28271
- }, /* @__PURE__ */ React.createElement("label", {
28272
- className: labelCls3
28273
- }, "Image URL"), /* @__PURE__ */ React.createElement("input", {
28274
- type: "url",
28815
+ }, /* @__PURE__ */ React.createElement(ImageOrUrlField, {
28816
+ label: "Image",
28275
28817
  value: row.url,
28276
- onChange: /* @__PURE__ */ __name((e) => setImage(i, "url", e.target.value), "onChange"),
28277
- className: inputCls3,
28278
- placeholder: "https://..."
28818
+ onChange: /* @__PURE__ */ __name((v) => setImage(i, "url", v), "onChange"),
28819
+ inputClassName: inputCls3
28279
28820
  })), /* @__PURE__ */ React.createElement("div", {
28280
28821
  className: "flex-1 min-w-[120px]"
28281
28822
  }, /* @__PURE__ */ React.createElement("label", {
@@ -28354,6 +28895,8 @@ var init_ProductEditPage = __esm({
28354
28895
  "use client";
28355
28896
  init_vendor_scope();
28356
28897
  init_admin_list_return_url();
28898
+ init_dialog();
28899
+ init_button();
28357
28900
  init_SeoSection();
28358
28901
  init_DetailPageLayout();
28359
28902
  init_DetailPageHeader();
@@ -28364,6 +28907,8 @@ var init_ProductEditPage = __esm({
28364
28907
  init_inventory_validation();
28365
28908
  init_category_item_label();
28366
28909
  init_use_category_collections();
28910
+ init_ImageOrUrlField();
28911
+ init_admin_config_context();
28367
28912
  init_ProductVariantsSection();
28368
28913
  init_product_variants();
28369
28914
  __name(parseCategoryIdFromReturnUrl, "parseCategoryIdFromReturnUrl");
@@ -28633,12 +29178,6 @@ function CollectionEditPage({ collectionId }) {
28633
29178
  ]);
28634
29179
  return;
28635
29180
  }
28636
- if (create && !categoryId) {
28637
- setErrors([
28638
- "Category is required"
28639
- ]);
28640
- return;
28641
- }
28642
29181
  setSaving(true);
28643
29182
  try {
28644
29183
  const savedSeoId = await saveSeo(seo, slug.trim(), seoId);
@@ -28859,14 +29398,11 @@ function CollectionEditPage({ collectionId }) {
28859
29398
  onChange: /* @__PURE__ */ __name((e) => setDescription(e.target.value), "onChange"),
28860
29399
  className: `${inputCls4} min-h-[80px]`,
28861
29400
  rows: 3
28862
- })), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("label", {
28863
- className: labelCls4
28864
- }, "Cover image URL"), /* @__PURE__ */ React.createElement("input", {
28865
- type: "url",
29401
+ })), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(ImageOrUrlField, {
29402
+ label: "Cover image",
28866
29403
  value: image,
28867
- onChange: /* @__PURE__ */ __name((e) => setImage(e.target.value), "onChange"),
28868
- className: inputCls4,
28869
- placeholder: "https://..."
29404
+ onChange: setImage,
29405
+ inputClassName: inputCls4
28870
29406
  })), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("label", {
28871
29407
  className: labelCls4
28872
29408
  }, "Sort order"), /* @__PURE__ */ React.createElement("input", {
@@ -28893,17 +29429,24 @@ function CollectionEditPage({ collectionId }) {
28893
29429
  className: "text-xs font-medium text-gray-700 mb-2"
28894
29430
  }, "Hero carousel"), heroSlides.map((slide, i) => /* @__PURE__ */ React.createElement("div", {
28895
29431
  key: i,
28896
- className: "flex flex-wrap gap-2 mb-3 p-2 bg-white rounded border"
28897
- }, /* @__PURE__ */ React.createElement("input", {
29432
+ className: "flex flex-wrap gap-2 mb-3 p-2 bg-white rounded border items-start"
29433
+ }, slide.type === "image" ? /* @__PURE__ */ React.createElement("div", {
29434
+ className: "flex-1 min-w-[200px]"
29435
+ }, /* @__PURE__ */ React.createElement(ImageOrUrlField, {
29436
+ label: "Slide image",
29437
+ value: slide.url,
29438
+ onChange: /* @__PURE__ */ __name((v) => updateHeroSlide(i, "url", v), "onChange"),
29439
+ inputClassName: inputCls4
29440
+ })) : /* @__PURE__ */ React.createElement("input", {
28898
29441
  type: "url",
28899
29442
  value: slide.url,
28900
29443
  onChange: /* @__PURE__ */ __name((e) => updateHeroSlide(i, "url", e.target.value), "onChange"),
28901
- placeholder: "Media URL",
29444
+ placeholder: "Video URL",
28902
29445
  className: `${inputCls4} flex-1 min-w-[200px]`
28903
29446
  }), /* @__PURE__ */ React.createElement("select", {
28904
29447
  value: slide.type,
28905
29448
  onChange: /* @__PURE__ */ __name((e) => updateHeroSlide(i, "type", e.target.value), "onChange"),
28906
- className: `${inputCls4} w-24`
29449
+ className: `${inputCls4} w-24 mt-6`
28907
29450
  }, /* @__PURE__ */ React.createElement("option", {
28908
29451
  value: "image"
28909
29452
  }, "Image"), /* @__PURE__ */ React.createElement("option", {
@@ -28913,11 +29456,11 @@ function CollectionEditPage({ collectionId }) {
28913
29456
  value: slide.caption,
28914
29457
  onChange: /* @__PURE__ */ __name((e) => updateHeroSlide(i, "caption", e.target.value), "onChange"),
28915
29458
  placeholder: "Caption",
28916
- className: `${inputCls4} flex-1 min-w-[120px]`
29459
+ className: `${inputCls4} flex-1 min-w-[120px] mt-6`
28917
29460
  }), /* @__PURE__ */ React.createElement("button", {
28918
29461
  type: "button",
28919
29462
  onClick: /* @__PURE__ */ __name(() => removeHeroSlide(i), "onClick"),
28920
- className: "p-2 text-gray-400 hover:text-red-600 rounded shrink-0"
29463
+ className: "p-2 text-gray-400 hover:text-red-600 rounded shrink-0 mt-6"
28921
29464
  }, /* @__PURE__ */ React.createElement(Trash2, {
28922
29465
  className: "h-4 w-4"
28923
29466
  })))), /* @__PURE__ */ React.createElement("button", {
@@ -29044,6 +29587,7 @@ var init_CollectionEditPage = __esm({
29044
29587
  init_use_catalog_categories();
29045
29588
  init_admin_config_context();
29046
29589
  init_category_related_product_labels();
29590
+ init_ImageOrUrlField();
29047
29591
  isCreate3 = /* @__PURE__ */ __name((id) => id === "create", "isCreate");
29048
29592
  emptySlide = /* @__PURE__ */ __name(() => ({
29049
29593
  url: "",
@@ -29061,116 +29605,6 @@ var init_CollectionEditPage = __esm({
29061
29605
  __name(CollectionEditPage, "CollectionEditPage");
29062
29606
  }
29063
29607
  });
29064
- 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 }) {
29065
- const fileInputRef = useRef(null);
29066
- const [isUploading, setIsUploading] = useState(false);
29067
- const [error, setError] = useState(null);
29068
- 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";
29069
- const handleUpload = useCallback(async (file) => {
29070
- setError(null);
29071
- if (!ACCEPTED_TYPES.includes(file.type)) {
29072
- setError(`Unsupported file type. Use: ${ACCEPTED_TYPES.map((t) => t.replace("image/", "")).join(", ")}`);
29073
- return;
29074
- }
29075
- if (file.size > maxSizeMb * 1024 * 1024) {
29076
- setError(`File must be under ${maxSizeMb}MB`);
29077
- return;
29078
- }
29079
- setIsUploading(true);
29080
- try {
29081
- const formData = new FormData();
29082
- formData.append("file", file);
29083
- const response = await fetch("/api/upload", {
29084
- method: "POST",
29085
- body: formData
29086
- });
29087
- const data = await response.json();
29088
- if (!response.ok) {
29089
- throw new Error(data.error || data.details || "Upload failed");
29090
- }
29091
- onChange(data.filePath ?? "");
29092
- } catch (err) {
29093
- setError(err instanceof Error ? err.message : "Upload failed");
29094
- } finally {
29095
- setIsUploading(false);
29096
- if (fileInputRef.current) fileInputRef.current.value = "";
29097
- }
29098
- }, [
29099
- maxSizeMb,
29100
- onChange
29101
- ]);
29102
- const onFileChange = useCallback((e) => {
29103
- const file = e.target.files?.[0];
29104
- if (file) void handleUpload(file);
29105
- }, [
29106
- handleUpload
29107
- ]);
29108
- const trimmed = value.trim();
29109
- return /* @__PURE__ */ React.createElement("div", {
29110
- className: "space-y-2"
29111
- }, /* @__PURE__ */ React.createElement("label", {
29112
- className: labelClassName
29113
- }, label), trimmed ? /* @__PURE__ */ React.createElement("div", {
29114
- className: "flex items-start gap-3"
29115
- }, /* @__PURE__ */ React.createElement("img", {
29116
- src: trimmed,
29117
- alt: label,
29118
- className: previewCls,
29119
- onError: /* @__PURE__ */ __name((e) => {
29120
- e.currentTarget.style.display = "none";
29121
- }, "onError")
29122
- }), /* @__PURE__ */ React.createElement("button", {
29123
- type: "button",
29124
- onClick: /* @__PURE__ */ __name(() => onChange(""), "onClick"),
29125
- 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"
29126
- }, /* @__PURE__ */ React.createElement(X, {
29127
- className: "h-3 w-3"
29128
- }), "Clear")) : null, /* @__PURE__ */ React.createElement("div", {
29129
- className: "flex flex-wrap gap-2"
29130
- }, /* @__PURE__ */ React.createElement("input", {
29131
- type: "url",
29132
- value,
29133
- onChange: /* @__PURE__ */ __name((e) => {
29134
- setError(null);
29135
- onChange(e.target.value);
29136
- }, "onChange"),
29137
- placeholder,
29138
- className: `${inputClassName} min-w-0 flex-1`
29139
- }), /* @__PURE__ */ React.createElement("input", {
29140
- ref: fileInputRef,
29141
- type: "file",
29142
- accept: ACCEPTED_TYPES.join(","),
29143
- onChange: onFileChange,
29144
- className: "hidden",
29145
- disabled: isUploading
29146
- }), /* @__PURE__ */ React.createElement("button", {
29147
- type: "button",
29148
- onClick: /* @__PURE__ */ __name(() => fileInputRef.current?.click(), "onClick"),
29149
- disabled: isUploading,
29150
- 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"
29151
- }, /* @__PURE__ */ React.createElement(Upload, {
29152
- className: "h-3.5 w-3.5"
29153
- }), isUploading ? "Uploading\u2026" : "Upload")), error ? /* @__PURE__ */ React.createElement("p", {
29154
- className: "flex items-center gap-1.5 text-xs text-red-600"
29155
- }, /* @__PURE__ */ React.createElement(AlertCircle, {
29156
- className: "h-3.5 w-3.5 shrink-0"
29157
- }), error) : /* @__PURE__ */ React.createElement("p", {
29158
- className: "text-xs text-gray-500"
29159
- }, "Paste a URL or upload an image (PNG, JPG, GIF, WEBP up to ", maxSizeMb, "MB)"));
29160
- }
29161
- var ACCEPTED_TYPES;
29162
- var init_ImageOrUrlField = __esm({
29163
- "src/components/Admin/ImageOrUrlField.tsx"() {
29164
- "use client";
29165
- ACCEPTED_TYPES = [
29166
- "image/jpeg",
29167
- "image/png",
29168
- "image/gif",
29169
- "image/webp"
29170
- ];
29171
- __name(ImageOrUrlField, "ImageOrUrlField");
29172
- }
29173
- });
29174
29608
  function AttachProductModal({ open, onOpenChange, categoryId, categoryName, excludeProductIds = [], onAttach }) {
29175
29609
  const [attachingId, setAttachingId] = useState(null);
29176
29610
  const [error, setError] = useState(null);
@@ -30115,6 +30549,10 @@ function combineCityCountry(city, country) {
30115
30549
  function EventEditPage({ eventId }) {
30116
30550
  const router = useRouter();
30117
30551
  const searchParams = useSearchParams();
30552
+ const { data: session } = useSession();
30553
+ const { requireEventApproval } = useContext(AdminConfigContext);
30554
+ const approvalOn = requireEventApproval === true;
30555
+ const vendorPortal = isVendorPortalUser(session?.user);
30118
30556
  const listReturnUrl = safeAdminListReturnUrl(searchParams.get("from")) ?? "/admin/events";
30119
30557
  const create = isCreate4(eventId);
30120
30558
  const dupParam = searchParams.get("duplicateFrom");
@@ -30127,6 +30565,11 @@ function EventEditPage({ eventId }) {
30127
30565
  const [slug, setSlug] = useState("");
30128
30566
  const [description, setDescription] = useState("");
30129
30567
  const [isActive, setIsActive] = useState(true);
30568
+ const [approvalStatus, setApprovalStatus] = useState("pending");
30569
+ const [rejectionReason, setRejectionReason] = useState("");
30570
+ const [rejectModalOpen, setRejectModalOpen] = useState(false);
30571
+ const [rejectDraft, setRejectDraft] = useState("");
30572
+ const approvalBeforeRejectRef = useRef("pending");
30130
30573
  const [comingSoon, setComingSoon] = useState(false);
30131
30574
  const [bannerImageUrl, setBannerImageUrl] = useState("");
30132
30575
  const [logoUrl, setLogoUrl] = useState("");
@@ -30212,6 +30655,16 @@ function EventEditPage({ eventId }) {
30212
30655
  cancelled = true;
30213
30656
  };
30214
30657
  }, []);
30658
+ useEffect(() => {
30659
+ if (create && vendorPortal && approvalOn) {
30660
+ setApprovalStatus("pending");
30661
+ setIsActive(false);
30662
+ }
30663
+ }, [
30664
+ create,
30665
+ vendorPortal,
30666
+ approvalOn
30667
+ ]);
30215
30668
  useEffect(() => {
30216
30669
  let cancelled = false;
30217
30670
  (async () => {
@@ -30233,6 +30686,8 @@ function EventEditPage({ eventId }) {
30233
30686
  setSlug(data.slug ?? "");
30234
30687
  setDescription(data.description ?? "");
30235
30688
  setIsActive(data.isActive ?? true);
30689
+ setApprovalStatus(typeof data.approvalStatus === "string" && data.approvalStatus ? data.approvalStatus : "pending");
30690
+ setRejectionReason(typeof data.rejectionReason === "string" ? data.rejectionReason : "");
30236
30691
  setComingSoon(data.comingSoon ?? false);
30237
30692
  setBannerImageUrl(data.bannerImageUrl ?? "");
30238
30693
  setLogoUrl(data.logoUrl ?? "");
@@ -30344,11 +30799,11 @@ function EventEditPage({ eventId }) {
30344
30799
  setErrors(nextErrors);
30345
30800
  return null;
30346
30801
  }
30347
- return {
30802
+ const payload = {
30348
30803
  name: name.trim(),
30349
30804
  slug: slug.trim(),
30350
30805
  description: description.trim() || null,
30351
- isActive,
30806
+ isActive: create && vendorPortal && approvalOn ? false : isActive,
30352
30807
  comingSoon,
30353
30808
  bannerImageUrl: bannerImageUrl.trim() || null,
30354
30809
  logoUrl: logoUrl.trim() || null,
@@ -30386,9 +30841,55 @@ function EventEditPage({ eventId }) {
30386
30841
  sortOrder,
30387
30842
  contactFormId
30388
30843
  };
30844
+ if (approvalOn) {
30845
+ if (vendorPortal && create) {
30846
+ payload.approvalStatus = "pending";
30847
+ } else if (!vendorPortal) {
30848
+ payload.approvalStatus = approvalStatus;
30849
+ if (approvalStatus === "rejected") {
30850
+ payload.rejectionReason = rejectionReason.trim();
30851
+ }
30852
+ }
30853
+ }
30854
+ return payload;
30389
30855
  }, "buildPayload");
30856
+ const openRejectModal = /* @__PURE__ */ __name((fromStatus) => {
30857
+ approvalBeforeRejectRef.current = fromStatus === "rejected" ? "pending" : fromStatus;
30858
+ setRejectDraft(rejectionReason);
30859
+ setApprovalStatus("rejected");
30860
+ setRejectModalOpen(true);
30861
+ }, "openRejectModal");
30862
+ const confirmRejectReason = /* @__PURE__ */ __name(() => {
30863
+ const reason = rejectDraft.trim();
30864
+ if (!reason) return;
30865
+ setRejectionReason(reason);
30866
+ setRejectModalOpen(false);
30867
+ }, "confirmRejectReason");
30868
+ const cancelRejectModal = /* @__PURE__ */ __name(() => {
30869
+ if (!rejectionReason.trim()) {
30870
+ setApprovalStatus(approvalBeforeRejectRef.current || "pending");
30871
+ }
30872
+ setRejectDraft(rejectionReason);
30873
+ setRejectModalOpen(false);
30874
+ }, "cancelRejectModal");
30875
+ const handleApprovalSelect = /* @__PURE__ */ __name((value) => {
30876
+ if (value === "rejected") {
30877
+ openRejectModal(approvalStatus);
30878
+ return;
30879
+ }
30880
+ setApprovalStatus(value);
30881
+ if (value !== "rejected") setRejectionReason("");
30882
+ }, "handleApprovalSelect");
30390
30883
  const handleSave = /* @__PURE__ */ __name(async () => {
30391
30884
  setErrors([]);
30885
+ if (approvalOn && !vendorPortal && approvalStatus === "rejected" && !rejectionReason.trim()) {
30886
+ setRejectDraft("");
30887
+ setRejectModalOpen(true);
30888
+ setErrors([
30889
+ "Rejection reason is required"
30890
+ ]);
30891
+ return;
30892
+ }
30392
30893
  const payload = buildPayload();
30393
30894
  if (!payload) return;
30394
30895
  setSaving(true);
@@ -30408,6 +30909,9 @@ function EventEditPage({ eventId }) {
30408
30909
  return;
30409
30910
  }
30410
30911
  const saved = await res.json();
30912
+ if (typeof saved.isActive === "boolean") setIsActive(saved.isActive);
30913
+ if (typeof saved.approvalStatus === "string") setApprovalStatus(saved.approvalStatus);
30914
+ if (approvalStatus === "approved") setRejectionReason("");
30411
30915
  const savedId = create ? saved.id : Number(eventId);
30412
30916
  if (savedId != null && !Number.isNaN(savedId)) {
30413
30917
  router.push(`/admin/events/${savedId}/edit?from=${encodeURIComponent(listReturnUrl)}`);
@@ -30470,19 +30974,66 @@ function EventEditPage({ eventId }) {
30470
30974
  title: create ? "Add event" : "Edit event",
30471
30975
  subtitle: create ? "Create a new event" : "Update event details and tickets",
30472
30976
  closeHref: listReturnUrl,
30977
+ headerExtra: approvalOn && !vendorPortal ? /* @__PURE__ */ React.createElement("div", {
30978
+ className: "flex items-center gap-2"
30979
+ }, /* @__PURE__ */ React.createElement("select", {
30980
+ value: approvalStatus,
30981
+ onChange: /* @__PURE__ */ __name((e) => handleApprovalSelect(e.target.value), "onChange"),
30982
+ className: "h-8 rounded-md border border-gray-600 bg-gray-900 text-white text-xs px-2 max-w-[10rem]",
30983
+ "aria-label": "Approval status"
30984
+ }, /* @__PURE__ */ React.createElement("option", {
30985
+ value: "pending"
30986
+ }, "Pending"), /* @__PURE__ */ React.createElement("option", {
30987
+ value: "approved"
30988
+ }, "Approve"), /* @__PURE__ */ React.createElement("option", {
30989
+ value: "rejected"
30990
+ }, "Reject")), approvalStatus === "rejected" ? /* @__PURE__ */ React.createElement("button", {
30991
+ type: "button",
30992
+ onClick: /* @__PURE__ */ __name(() => openRejectModal("rejected"), "onClick"),
30993
+ className: "text-xs text-amber-300 hover:text-amber-200 underline underline-offset-2 max-w-[9rem] truncate",
30994
+ title: rejectionReason || "Add rejection reason"
30995
+ }, rejectionReason.trim() ? "Edit reason" : "Add reason") : null) : approvalOn && vendorPortal ? /* @__PURE__ */ React.createElement("span", {
30996
+ className: "text-xs text-gray-300 capitalize hidden sm:inline"
30997
+ }, approvalStatus.replace(/_/g, " "), approvalStatus === "rejected" && rejectionReason ? ` \u2014 ${rejectionReason}` : "") : null,
30473
30998
  menuItems: [
30474
30999
  {
30475
31000
  label: saving ? "Saving..." : "Save",
30476
31001
  icon: Save,
30477
31002
  onClick: handleSave
30478
31003
  },
30479
- {
30480
- label: isActive ? "Deactivate" : "Activate",
30481
- icon: Power,
30482
- onClick: /* @__PURE__ */ __name(() => setIsActive(!isActive), "onClick")
30483
- }
31004
+ ...approvalOn && vendorPortal && (approvalStatus === "pending" || approvalStatus === "rejected") ? [] : [
31005
+ {
31006
+ label: isActive ? "Deactivate" : "Activate",
31007
+ icon: Power,
31008
+ onClick: /* @__PURE__ */ __name(() => setIsActive(!isActive), "onClick")
31009
+ }
31010
+ ]
30484
31011
  ]
30485
- }), !create && logoUrl.trim() ? /* @__PURE__ */ React.createElement("div", {
31012
+ }), /* @__PURE__ */ React.createElement(Dialog, {
31013
+ open: rejectModalOpen,
31014
+ onOpenChange: /* @__PURE__ */ __name((open) => {
31015
+ if (!open) cancelRejectModal();
31016
+ }, "onOpenChange")
31017
+ }, /* @__PURE__ */ React.createElement(DialogContent, {
31018
+ className: "max-w-md"
31019
+ }, /* @__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", {
31020
+ value: rejectDraft,
31021
+ onChange: /* @__PURE__ */ __name((e) => setRejectDraft(e.target.value), "onChange"),
31022
+ className: "w-full min-h-[100px] rounded-md border border-gray-300 px-3 py-2 text-sm",
31023
+ placeholder: "Explain what needs to change\u2026",
31024
+ autoFocus: true
31025
+ }), /* @__PURE__ */ React.createElement(DialogFooter, {
31026
+ className: "gap-2 sm:gap-0"
31027
+ }, /* @__PURE__ */ React.createElement(Button, {
31028
+ type: "button",
31029
+ variant: "outline",
31030
+ onClick: cancelRejectModal
31031
+ }, "Cancel"), /* @__PURE__ */ React.createElement(Button, {
31032
+ type: "button",
31033
+ variant: "destructive",
31034
+ disabled: !rejectDraft.trim(),
31035
+ onClick: confirmRejectReason
31036
+ }, "Confirm reject")))), !create && logoUrl.trim() ? /* @__PURE__ */ React.createElement("div", {
30486
31037
  className: "flex items-center gap-3 px-4 sm:px-6 py-3 border-b border-gray-100 bg-gray-50/80"
30487
31038
  }, /* @__PURE__ */ React.createElement("img", {
30488
31039
  src: logoUrl.trim(),
@@ -30841,11 +31392,15 @@ var init_EventEditPage = __esm({
30841
31392
  "src/admin/pages/EventEditPage.tsx"() {
30842
31393
  "use client";
30843
31394
  init_admin_list_return_url();
31395
+ init_vendor_scope();
30844
31396
  init_DetailPageLayout();
30845
31397
  init_DetailPageHeader();
30846
31398
  init_ImageOrUrlField();
30847
31399
  init_JoditRichText();
30848
31400
  init_EventProductsSection();
31401
+ init_admin_config_context();
31402
+ init_dialog();
31403
+ init_button();
30849
31404
  init_EventManagementFields();
30850
31405
  init_event_named_lists();
30851
31406
  init_social_media_links();
@@ -30921,6 +31476,8 @@ async function validateComboProductInventory(productIds) {
30921
31476
  function ComboEditPage({ comboId }) {
30922
31477
  const router = useRouter();
30923
31478
  const searchParams = useSearchParams();
31479
+ const { eventsEnabled } = useContext(AdminConfigContext);
31480
+ const eventsOn = eventsEnabled !== false;
30924
31481
  const listReturnUrl = safeAdminListReturnUrl(searchParams.get("from")) ?? "/admin/combos";
30925
31482
  const create = isCreate5(comboId);
30926
31483
  const duplicateFrom = searchParams.get("duplicateFrom")?.trim();
@@ -30942,8 +31499,9 @@ function ComboEditPage({ comboId }) {
30942
31499
  const [productOptions, setProductOptions] = useState([]);
30943
31500
  const [fixedItems, setFixedItems] = useState([]);
30944
31501
  const [addonItems, setAddonItems] = useState([]);
31502
+ const canPickProducts = !eventsOn || Boolean(eventId);
30945
31503
  useEffect(() => {
30946
- if (!eventId) {
31504
+ if (!eventsOn || !eventId) {
30947
31505
  setDefaultCurrency("INR");
30948
31506
  return;
30949
31507
  }
@@ -30955,9 +31513,14 @@ function ComboEditPage({ comboId }) {
30955
31513
  cancelled = true;
30956
31514
  };
30957
31515
  }, [
30958
- eventId
31516
+ eventId,
31517
+ eventsOn
30959
31518
  ]);
30960
31519
  useEffect(() => {
31520
+ if (!eventsOn) {
31521
+ setEventOptions([]);
31522
+ return;
31523
+ }
30961
31524
  let cancelled = false;
30962
31525
  (async () => {
30963
31526
  try {
@@ -30977,45 +31540,63 @@ function ComboEditPage({ comboId }) {
30977
31540
  return () => {
30978
31541
  cancelled = true;
30979
31542
  };
30980
- }, []);
31543
+ }, [
31544
+ eventsOn
31545
+ ]);
30981
31546
  useEffect(() => {
30982
- if (!eventId) {
30983
- setProductOptions([]);
30984
- return;
30985
- }
30986
31547
  let cancelled = false;
30987
31548
  (async () => {
30988
31549
  try {
30989
- const res2 = await fetch(`/api/event_products?eventId=${eventId}&limit=500`);
30990
- if (res2.ok) {
31550
+ if (eventsOn) {
31551
+ if (!eventId) {
31552
+ setProductOptions([]);
31553
+ return;
31554
+ }
31555
+ const res2 = await fetch(`/api/event_products?eventId=${eventId}&limit=500`);
31556
+ if (!res2.ok) return;
30991
31557
  const data = await res2.json();
30992
- if (!cancelled && Array.isArray(data.data)) {
30993
- const productIds = data.data.map((ep) => ep.productId).filter(Boolean);
30994
- if (productIds.length > 0) {
30995
- const prodRes = await fetch(`/api/products?ids=${productIds.join(",")}&limit=500`);
30996
- if (prodRes.ok) {
30997
- const prodData = await prodRes.json();
30998
- if (!cancelled && Array.isArray(prodData.data)) {
30999
- const fetched = prodData.data.map((p) => ({
31000
- value: String(p.id),
31001
- label: p.name ?? p.title ?? `Product #${p.id}`
31002
- }));
31003
- setProductOptions((prev) => {
31004
- const merged = [
31005
- ...fetched
31006
- ];
31007
- for (const p of prev) {
31008
- if (!merged.some((m) => m.value === p.value)) merged.push(p);
31009
- }
31010
- return merged;
31011
- });
31012
- }
31013
- }
31014
- } else {
31015
- setProductOptions([]);
31016
- }
31558
+ if (cancelled || !Array.isArray(data.data)) return;
31559
+ const productIds = data.data.map((ep) => ep.productId).filter(Boolean);
31560
+ if (productIds.length === 0) {
31561
+ setProductOptions([]);
31562
+ return;
31017
31563
  }
31564
+ const prodRes2 = await fetch(`/api/products?ids=${productIds.join(",")}&limit=500`);
31565
+ if (!prodRes2.ok) return;
31566
+ const prodData2 = await prodRes2.json();
31567
+ if (cancelled || !Array.isArray(prodData2.data)) return;
31568
+ const fetched2 = prodData2.data.map((p) => ({
31569
+ value: String(p.id),
31570
+ label: p.name ?? p.title ?? `Product #${p.id}`
31571
+ }));
31572
+ setProductOptions((prev) => {
31573
+ const merged = [
31574
+ ...fetched2
31575
+ ];
31576
+ for (const p of prev) {
31577
+ if (!merged.some((m) => m.value === p.value)) merged.push(p);
31578
+ }
31579
+ return merged;
31580
+ });
31581
+ return;
31018
31582
  }
31583
+ const prodRes = await fetch("/api/products?limit=500&sortField=name&sortOrder=asc");
31584
+ if (!prodRes.ok) return;
31585
+ const prodData = await prodRes.json();
31586
+ if (cancelled || !Array.isArray(prodData.data)) return;
31587
+ const fetched = prodData.data.map((p) => ({
31588
+ value: String(p.id),
31589
+ label: p.name ?? p.title ?? `Product #${p.id}`
31590
+ }));
31591
+ setProductOptions((prev) => {
31592
+ const merged = [
31593
+ ...fetched
31594
+ ];
31595
+ for (const p of prev) {
31596
+ if (!merged.some((m) => m.value === p.value)) merged.push(p);
31597
+ }
31598
+ return merged;
31599
+ });
31019
31600
  } catch {
31020
31601
  }
31021
31602
  })();
@@ -31023,7 +31604,8 @@ function ComboEditPage({ comboId }) {
31023
31604
  cancelled = true;
31024
31605
  };
31025
31606
  }, [
31026
- eventId
31607
+ eventId,
31608
+ eventsOn
31027
31609
  ]);
31028
31610
  useEffect(() => {
31029
31611
  let cancelled = false;
@@ -31134,7 +31716,7 @@ function ComboEditPage({ comboId }) {
31134
31716
  ]);
31135
31717
  return;
31136
31718
  }
31137
- if (!trimmedEventId || !/^\d+$/.test(trimmedEventId)) {
31719
+ if (eventsOn && (!trimmedEventId || !/^\d+$/.test(trimmedEventId))) {
31138
31720
  setErrors([
31139
31721
  "Event is required"
31140
31722
  ]);
@@ -31193,10 +31775,11 @@ function ComboEditPage({ comboId }) {
31193
31775
  }
31194
31776
  setSaving(true);
31195
31777
  try {
31778
+ const resolvedEventId = trimmedEventId && /^\d+$/.test(trimmedEventId) ? Number(trimmedEventId) : null;
31196
31779
  const payload = {
31197
31780
  name: trimmedName,
31198
31781
  desc: desc || null,
31199
- eventId: Number(trimmedEventId),
31782
+ eventId: resolvedEventId,
31200
31783
  price,
31201
31784
  currencyPrices: null,
31202
31785
  minSelectableItems,
@@ -31290,7 +31873,7 @@ function ComboEditPage({ comboId }) {
31290
31873
  value: desc,
31291
31874
  onChange: /* @__PURE__ */ __name((e) => setDesc(e.target.value), "onChange"),
31292
31875
  className: "w-full rounded-md border border-gray-300 px-3 py-2 text-sm min-h-[100px]"
31293
- })), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("label", {
31876
+ })), eventsOn ? /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("label", {
31294
31877
  className: "block text-xs font-medium text-gray-600 mb-1"
31295
31878
  }, "Event *"), /* @__PURE__ */ React.createElement("select", {
31296
31879
  value: eventId,
@@ -31305,7 +31888,7 @@ function ComboEditPage({ comboId }) {
31305
31888
  }, "Select event"), eventOptions.map((o) => /* @__PURE__ */ React.createElement("option", {
31306
31889
  key: o.value,
31307
31890
  value: o.value
31308
- }, o.label)))))), eventId && /* @__PURE__ */ React.createElement("section", null, /* @__PURE__ */ React.createElement("h2", {
31891
+ }, o.label)))) : null)), canPickProducts && /* @__PURE__ */ React.createElement("section", null, /* @__PURE__ */ React.createElement("h2", {
31309
31892
  className: "text-xs font-semibold text-gray-400 uppercase tracking-wider mb-2"
31310
31893
  }, "Combo items"), /* @__PURE__ */ React.createElement("div", {
31311
31894
  className: "min-w-0 overflow-hidden border border-gray-200 rounded-lg p-4 bg-gray-50/50 space-y-5"
@@ -31373,11 +31956,11 @@ function ComboEditPage({ comboId }) {
31373
31956
  step: "0.01",
31374
31957
  value: priceStr,
31375
31958
  onChange: /* @__PURE__ */ __name((e) => setPriceStr(e.target.value), "onChange"),
31376
- disabled: !eventId,
31959
+ disabled: !canPickProducts,
31377
31960
  className: "w-full rounded-md border border-gray-300 px-3 py-2 text-sm disabled:bg-gray-100"
31378
31961
  }), /* @__PURE__ */ React.createElement("p", {
31379
31962
  className: "text-xs text-gray-400 mt-1"
31380
- }, eventId ? "Other currencies use the event\u2019s supported currencies and exchange rates." : "Select an event to set the combo price.")), /* @__PURE__ */ React.createElement("div", {
31963
+ }, 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", {
31381
31964
  className: "grid grid-cols-2 gap-4"
31382
31965
  }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("label", {
31383
31966
  className: "block text-xs font-medium text-gray-600 mb-1"
@@ -31443,6 +32026,7 @@ var init_ComboEditPage = __esm({
31443
32026
  init_DetailPageLayout();
31444
32027
  init_DetailPageHeader();
31445
32028
  init_inventory_validation();
32029
+ init_admin_config_context();
31446
32030
  isCreate5 = /* @__PURE__ */ __name((id) => id === "create", "isCreate");
31447
32031
  __name(formatDateTimeLocal, "formatDateTimeLocal");
31448
32032
  __name(fetchEventDefaultCurrency, "fetchEventDefaultCurrency");
@@ -31533,11 +32117,10 @@ function VendorEditPage({ vendorId }) {
31533
32117
  const [ownerPhone, setOwnerPhone] = useState("");
31534
32118
  const [ownerDesignation, setOwnerDesignation] = useState("");
31535
32119
  const [termsAccepted, setTermsAccepted] = useState(false);
31536
- const [activationMode, setActivationMode] = useState("invite");
31537
- const [sendOwnerEmail, setSendOwnerEmail] = useState(true);
31538
- const [ownerPassword, setOwnerPassword] = useState("");
31539
- const [ownerPasswordConfirm, setOwnerPasswordConfirm] = useState("");
31540
32120
  const [reinviting, setReinviting] = useState(false);
32121
+ const [inviteDialog, setInviteDialog] = useState(null);
32122
+ const [sendingInviteEmail, setSendingInviteEmail] = useState(false);
32123
+ const [linkCopied, setLinkCopied] = useState(false);
31541
32124
  const vendorPayload = /* @__PURE__ */ __name(() => ({
31542
32125
  name: name.trim(),
31543
32126
  legalName: legalName.trim() || null,
@@ -31673,6 +32256,59 @@ function VendorEditPage({ vendorId }) {
31673
32256
  groupName: sessionUser?.groupName
31674
32257
  });
31675
32258
  }
32259
+ const copyInviteLink = /* @__PURE__ */ __name(async () => {
32260
+ const inviteLink = inviteDialog?.inviteLink;
32261
+ if (!inviteLink) {
32262
+ toast.error("Invite link is not available");
32263
+ return;
32264
+ }
32265
+ try {
32266
+ await navigator.clipboard.writeText(inviteLink);
32267
+ setLinkCopied(true);
32268
+ toast.success("Invite link copied");
32269
+ window.setTimeout(() => setLinkCopied(false), 2e3);
32270
+ } catch {
32271
+ toast.error("Could not copy invite link");
32272
+ }
32273
+ }, "copyInviteLink");
32274
+ const sendInviteEmail = /* @__PURE__ */ __name(async () => {
32275
+ if (!inviteDialog) return;
32276
+ setSendingInviteEmail(true);
32277
+ try {
32278
+ const res = await fetch(`/api/admin/vendors/${inviteDialog.vendorId}/resend-invite`, {
32279
+ method: "POST",
32280
+ headers: {
32281
+ "Content-Type": "application/json"
32282
+ },
32283
+ body: JSON.stringify({
32284
+ sendEmail: true,
32285
+ rotate: false
32286
+ })
32287
+ });
32288
+ const data = await res.json();
32289
+ if (!res.ok) {
32290
+ toast.error(data.error || data.message || "Failed to send invite email");
32291
+ return;
32292
+ }
32293
+ const nextLink = typeof data.inviteLink === "string" ? data.inviteLink.trim() : "";
32294
+ if (nextLink) {
32295
+ setInviteDialog((prev) => prev ? {
32296
+ ...prev,
32297
+ inviteLink: nextLink
32298
+ } : prev);
32299
+ }
32300
+ toast.success(data.emailSent ? "Invite email sent" : data.message || "Invite email queued");
32301
+ } catch {
32302
+ toast.error("Failed to send invite email");
32303
+ } finally {
32304
+ setSendingInviteEmail(false);
32305
+ }
32306
+ }, "sendInviteEmail");
32307
+ const closeInviteDialog = /* @__PURE__ */ __name(() => {
32308
+ setInviteDialog(null);
32309
+ setLinkCopied(false);
32310
+ router.push(listReturnUrl);
32311
+ }, "closeInviteDialog");
31676
32312
  const handleCreate = /* @__PURE__ */ __name(async () => {
31677
32313
  setSaving(true);
31678
32314
  setErrors([]);
@@ -31698,29 +32334,6 @@ function VendorEditPage({ vendorId }) {
31698
32334
  setSaving(false);
31699
32335
  return;
31700
32336
  }
31701
- if (activationMode === "password") {
31702
- if (!ownerPassword) {
31703
- setErrors([
31704
- "Enter a password for the owner account"
31705
- ]);
31706
- setSaving(false);
31707
- return;
31708
- }
31709
- if (ownerPassword !== ownerPasswordConfirm) {
31710
- setErrors([
31711
- "Passwords do not match"
31712
- ]);
31713
- setSaving(false);
31714
- return;
31715
- }
31716
- if (ownerPassword.length < 6) {
31717
- setErrors([
31718
- "Password must be at least 6 characters"
31719
- ]);
31720
- setSaving(false);
31721
- return;
31722
- }
31723
- }
31724
32337
  try {
31725
32338
  const res = await fetch("/api/admin/vendors/onboard", {
31726
32339
  method: "POST",
@@ -31728,18 +32341,15 @@ function VendorEditPage({ vendorId }) {
31728
32341
  "Content-Type": "application/json"
31729
32342
  },
31730
32343
  body: JSON.stringify({
31731
- activation: activationMode,
31732
- sendOwnerEmail,
32344
+ activation: "invite",
32345
+ sendOwnerEmail: false,
31733
32346
  termsAccepted: true,
31734
32347
  vendor: vendorPayload(),
31735
32348
  user: {
31736
32349
  name: ownerName.trim(),
31737
32350
  email: ownerEmail.trim(),
31738
32351
  phone: ownerPhone.trim() || void 0,
31739
- designation: ownerDesignation.trim() || void 0,
31740
- ...activationMode === "password" ? {
31741
- password: ownerPassword
31742
- } : {}
32352
+ designation: ownerDesignation.trim() || void 0
31743
32353
  }
31744
32354
  })
31745
32355
  });
@@ -31750,22 +32360,19 @@ function VendorEditPage({ vendorId }) {
31750
32360
  ]);
31751
32361
  return;
31752
32362
  }
31753
- const message = data.message || "Vendor created";
31754
- const inviteLink = typeof data.inviteLink === "string" ? data.inviteLink.trim() : "";
31755
- if (inviteLink) {
31756
- try {
31757
- await navigator.clipboard.writeText(inviteLink);
31758
- toast.success(`${message} Invite link copied to clipboard.`);
31759
- } catch {
31760
- toast.success(message);
31761
- toast.message("Invite link", {
31762
- description: inviteLink
31763
- });
31764
- }
31765
- } else {
31766
- toast.success(message);
32363
+ const vendorIdNum = Number(data.vendor?.id);
32364
+ const inviteLink = typeof data.inviteLink === "string" && data.inviteLink.trim() ? data.inviteLink.trim() : "";
32365
+ if (!Number.isFinite(vendorIdNum) || vendorIdNum <= 0 || !inviteLink) {
32366
+ toast.success(data.message || "Vendor created");
32367
+ router.push(listReturnUrl);
32368
+ return;
31767
32369
  }
31768
- router.push(listReturnUrl);
32370
+ toast.success(data.message || "Vendor created");
32371
+ setLinkCopied(false);
32372
+ setInviteDialog({
32373
+ vendorId: vendorIdNum,
32374
+ inviteLink
32375
+ });
31769
32376
  } catch {
31770
32377
  setErrors([
31771
32378
  "Request failed"
@@ -31836,7 +32443,6 @@ function VendorEditPage({ vendorId }) {
31836
32443
  setSaving(false);
31837
32444
  }
31838
32445
  }, "handleSave");
31839
- const createSubmitLabel = activationMode === "password" ? sendOwnerEmail ? "Create vendor & send welcome email" : "Create vendor & set password" : sendOwnerEmail ? "Create vendor & send invite" : "Create vendor & copy invite link";
31840
32446
  if (loading) {
31841
32447
  return /* @__PURE__ */ React26__default.createElement("div", {
31842
32448
  className: "flex items-center justify-center py-12"
@@ -31848,11 +32454,11 @@ function VendorEditPage({ vendorId }) {
31848
32454
  className: "rounded-lg bg-white shadow-md min-h-[420px]"
31849
32455
  }, /* @__PURE__ */ React26__default.createElement(DetailPageHeader, {
31850
32456
  title: create ? "Add vendor" : "Edit vendor",
31851
- subtitle: create ? "Register a vendor store and create the owner account" : "Update store and registration details",
32457
+ subtitle: create ? "Register a vendor store and create the owner invite" : "Update store and registration details",
31852
32458
  closeHref: listReturnUrl,
31853
32459
  menuItems: create ? [
31854
32460
  {
31855
- label: saving ? "Creating\u2026" : createSubmitLabel,
32461
+ label: saving ? "Creating\u2026" : "Create vendor",
31856
32462
  icon: Save,
31857
32463
  onClick: handleCreate
31858
32464
  }
@@ -32120,64 +32726,42 @@ function VendorEditPage({ vendorId }) {
32120
32726
  className: "text-xs font-semibold text-gray-400 uppercase tracking-wider mb-2"
32121
32727
  }, "Owner access"), /* @__PURE__ */ React26__default.createElement("div", {
32122
32728
  className: sectionCls6
32123
- }, /* @__PURE__ */ React26__default.createElement(RadioGroup2, {
32124
- value: activationMode,
32125
- onValueChange: /* @__PURE__ */ __name((v) => setActivationMode(v), "onValueChange"),
32126
- className: "gap-3"
32127
- }, /* @__PURE__ */ React26__default.createElement("div", {
32128
- className: "flex items-start gap-2"
32129
- }, /* @__PURE__ */ React26__default.createElement(RadioGroupItem, {
32130
- value: "invite",
32131
- id: "activation-invite",
32132
- className: "mt-0.5"
32133
- }), /* @__PURE__ */ React26__default.createElement(Label3, {
32134
- htmlFor: "activation-invite",
32135
- className: "font-normal cursor-pointer text-sm"
32136
- }, "Send invite link")), /* @__PURE__ */ React26__default.createElement("div", {
32137
- className: "flex items-start gap-2"
32138
- }, /* @__PURE__ */ React26__default.createElement(RadioGroupItem, {
32139
- value: "password",
32140
- id: "activation-password",
32141
- className: "mt-0.5"
32142
- }), /* @__PURE__ */ React26__default.createElement(Label3, {
32143
- htmlFor: "activation-password",
32144
- className: "font-normal cursor-pointer text-sm"
32145
- }, "Set password now"))), /* @__PURE__ */ React26__default.createElement("div", {
32146
- className: "flex items-center gap-2 pt-1"
32147
- }, /* @__PURE__ */ React26__default.createElement(Checkbox, {
32148
- id: "sendOwnerEmail",
32149
- checked: sendOwnerEmail,
32150
- onCheckedChange: /* @__PURE__ */ __name((checked) => setSendOwnerEmail(checked === true), "onCheckedChange")
32151
- }), /* @__PURE__ */ React26__default.createElement(Label3, {
32152
- htmlFor: "sendOwnerEmail",
32153
- className: "font-normal cursor-pointer text-sm"
32154
- }, activationMode === "invite" ? "Send invite email" : "Send welcome email")), activationMode === "password" && /* @__PURE__ */ React26__default.createElement("div", {
32155
- className: "space-y-3 pt-1"
32156
- }, /* @__PURE__ */ React26__default.createElement("div", null, /* @__PURE__ */ React26__default.createElement(FieldLabel, {
32157
- htmlFor: "ownerPassword",
32158
- required: true
32159
- }, "Password"), /* @__PURE__ */ React26__default.createElement(Input, {
32160
- id: "ownerPassword",
32161
- type: "password",
32162
- value: ownerPassword,
32163
- onChange: /* @__PURE__ */ __name((e) => setOwnerPassword(e.target.value), "onChange"),
32164
- className: `mt-1 ${fieldClass}`
32165
- })), /* @__PURE__ */ React26__default.createElement("div", null, /* @__PURE__ */ React26__default.createElement(FieldLabel, {
32166
- htmlFor: "ownerPasswordConfirm",
32167
- required: true
32168
- }, "Confirm password"), /* @__PURE__ */ React26__default.createElement(Input, {
32169
- id: "ownerPasswordConfirm",
32170
- type: "password",
32171
- value: ownerPasswordConfirm,
32172
- onChange: /* @__PURE__ */ __name((e) => setOwnerPasswordConfirm(e.target.value), "onChange"),
32173
- className: `mt-1 ${fieldClass}`
32174
- }))), /* @__PURE__ */ React26__default.createElement(Button, {
32729
+ }, /* @__PURE__ */ React26__default.createElement("p", {
32730
+ className: "text-sm text-gray-600"
32731
+ }, "Creates the vendor and an invite for the owner. After create, you can send the invite email or copy the invite link."), /* @__PURE__ */ React26__default.createElement(Button, {
32175
32732
  type: "button",
32176
32733
  disabled: saving,
32177
32734
  onClick: handleCreate,
32178
32735
  className: "w-full"
32179
- }, saving ? "Creating\u2026" : createSubmitLabel))) : void 0)
32180
- }));
32736
+ }, saving ? "Creating\u2026" : "Create vendor"))) : void 0)
32737
+ }), /* @__PURE__ */ React26__default.createElement(Dialog, {
32738
+ open: inviteDialog != null,
32739
+ onOpenChange: /* @__PURE__ */ __name((open) => {
32740
+ if (!open) closeInviteDialog();
32741
+ }, "onOpenChange")
32742
+ }, /* @__PURE__ */ React26__default.createElement(DialogContent, {
32743
+ className: "max-w-md"
32744
+ }, /* @__PURE__ */ React26__default.createElement(DialogHeader, null, /* @__PURE__ */ React26__default.createElement(DialogTitle, null, "Vendor created"), /* @__PURE__ */ React26__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__default.createElement("div", {
32745
+ className: "flex flex-col gap-2 py-2"
32746
+ }, /* @__PURE__ */ React26__default.createElement(Button, {
32747
+ type: "button",
32748
+ disabled: sendingInviteEmail,
32749
+ onClick: /* @__PURE__ */ __name(() => void sendInviteEmail(), "onClick")
32750
+ }, /* @__PURE__ */ React26__default.createElement(Mail, {
32751
+ className: "h-4 w-4 mr-2"
32752
+ }), sendingInviteEmail ? "Sending\u2026" : "Send email"), /* @__PURE__ */ React26__default.createElement(Button, {
32753
+ type: "button",
32754
+ variant: "outline",
32755
+ onClick: /* @__PURE__ */ __name(() => void copyInviteLink(), "onClick")
32756
+ }, linkCopied ? /* @__PURE__ */ React26__default.createElement(Check, {
32757
+ className: "h-4 w-4 mr-2"
32758
+ }) : /* @__PURE__ */ React26__default.createElement(Copy, {
32759
+ className: "h-4 w-4 mr-2"
32760
+ }), linkCopied ? "Copied" : "Copy invite link")), /* @__PURE__ */ React26__default.createElement(DialogFooter, null, /* @__PURE__ */ React26__default.createElement(Button, {
32761
+ type: "button",
32762
+ variant: "secondary",
32763
+ onClick: closeInviteDialog
32764
+ }, "Done")))));
32181
32765
  }
32182
32766
  var isCreate6, fieldClass, selectClass, sectionCls6;
32183
32767
  var init_VendorEditPage = __esm({
@@ -32193,7 +32777,7 @@ var init_VendorEditPage = __esm({
32193
32777
  init_label();
32194
32778
  init_textarea();
32195
32779
  init_checkbox();
32196
- init_radio_group();
32780
+ init_dialog();
32197
32781
  init_vendor_profile();
32198
32782
  init_vendor_list_config();
32199
32783
  init_vendor_access_denied();
@@ -32277,9 +32861,9 @@ var VendorCategoryWorkspacePage_exports = {};
32277
32861
  __export(VendorCategoryWorkspacePage_exports, {
32278
32862
  default: () => VendorCategoryWorkspacePage
32279
32863
  });
32280
- function buildAdminProductColumns(base, multiVendorEnabled, eventsEnabled) {
32864
+ function buildAdminProductColumns(base, showVendorColumn, eventsEnabled) {
32281
32865
  const extraColumns = [
32282
- ...multiVendorEnabled ? [
32866
+ ...showVendorColumn ? [
32283
32867
  VENDOR_EXTRA_COLUMN
32284
32868
  ] : [],
32285
32869
  ...eventsEnabled ? [
@@ -32298,9 +32882,9 @@ function buildAdminProductColumns(base, multiVendorEnabled, eventsEnabled) {
32298
32882
  ...cols.slice(nameIdx + 1)
32299
32883
  ];
32300
32884
  }
32301
- function buildAllProductColumns(base, multiVendorEnabled, eventsEnabled) {
32885
+ function buildAllProductColumns(base, showVendorColumn, eventsEnabled) {
32302
32886
  const extraColumns = [
32303
- ...multiVendorEnabled ? [
32887
+ ...showVendorColumn ? [
32304
32888
  VENDOR_EXTRA_COLUMN
32305
32889
  ] : [],
32306
32890
  ...eventsEnabled ? [
@@ -32350,7 +32934,8 @@ function VendorCategoryWorkspacePage() {
32350
32934
  const workspaceFrom = activeCategoryId ? `/admin/products?categoryId=${activeCategoryId}` : "/admin/products";
32351
32935
  const itemLabel = activeCategory ? categorySingularName(activeCategory.name) : "Product";
32352
32936
  const productColumns2 = useMemo(() => {
32353
- const base = showAllProducts ? buildAllProductColumns(STORE_CRUD_CONFIGS.products.columns, multiVendorEnabled !== false, eventsEnabled !== false) : buildAdminProductColumns(STORE_CRUD_CONFIGS.products.columns, multiVendorEnabled !== false, eventsEnabled !== false);
32937
+ const showVendorColumn = multiVendorEnabled !== false && !vendorPortal;
32938
+ const base = showAllProducts ? buildAllProductColumns(STORE_CRUD_CONFIGS.products.columns, showVendorColumn, eventsEnabled !== false) : buildAdminProductColumns(STORE_CRUD_CONFIGS.products.columns, showVendorColumn, eventsEnabled !== false);
32354
32939
  return withCollectionRelationApi(base, activeCategoryId, vendorPortal);
32355
32940
  }, [
32356
32941
  vendorPortal,
@@ -33676,7 +34261,7 @@ function CustomerPicker({ value, label, onChange }) {
33676
34261
  className: "text-xs text-gray-400 shrink-0 truncate"
33677
34262
  }, c.email ?? ""))))));
33678
34263
  }
33679
- function ConditionCard({ condition, onChange, onRemove }) {
34264
+ function ConditionCard({ condition, onChange, onRemove, eventsOn }) {
33680
34265
  const iconMap = {
33681
34266
  minAmount: /* @__PURE__ */ React.createElement(DollarSign, {
33682
34267
  className: "h-3.5 w-3.5 text-blue-500"
@@ -33732,7 +34317,7 @@ function ConditionCard({ condition, onChange, onRemove }) {
33732
34317
  value: "minQuantity"
33733
34318
  }, "Minimum quantity"), /* @__PURE__ */ React.createElement(SelectItem, {
33734
34319
  value: "productMinQuantity"
33735
- }, "Product"), /* @__PURE__ */ React.createElement(SelectItem, {
34320
+ }, "Product"), (eventsOn || condition.kind === "events") && /* @__PURE__ */ React.createElement(SelectItem, {
33736
34321
  value: "events"
33737
34322
  }, "Events"), /* @__PURE__ */ React.createElement(SelectItem, {
33738
34323
  value: "nthOrder"
@@ -33966,11 +34551,13 @@ function RewardCard({ reward, onChange, onRemove, discountType, discountValue, e
33966
34551
  })));
33967
34552
  }
33968
34553
  function DiscountConditionsBuilder({ rules, onChange, discountType = "PERCENTAGE", discountValue = "" }) {
34554
+ const { eventsEnabled } = useContext(AdminConfigContext);
34555
+ const eventsOn = eventsEnabled !== false;
33969
34556
  const parsed = ruleTreeToFriendly(rules);
33970
34557
  const [groups, setGroups] = useState(parsed.groups);
33971
34558
  const [groupOperator, setGroupOperator] = useState(parsed.groupOperator);
33972
34559
  const [rewards, setRewards] = useState(parsed.rewards);
33973
- const rewardEventId = groups.flatMap((g) => g.conditions).find((c) => c.kind === "events" && c.eventId != null)?.eventId ?? null;
34560
+ const rewardEventId = eventsOn ? groups.flatMap((g) => g.conditions).find((c) => c.kind === "events" && c.eventId != null)?.eventId ?? null : null;
33974
34561
  useEffect(() => {
33975
34562
  const missingNameIds = [];
33976
34563
  for (const g of groups) {
@@ -34207,7 +34794,8 @@ function DiscountConditionsBuilder({ rules, onChange, discountType = "PERCENTAGE
34207
34794
  })), /* @__PURE__ */ React.createElement(ConditionCard, {
34208
34795
  condition: c,
34209
34796
  onChange: /* @__PURE__ */ __name((updated) => updateCondition(group.id, c.id, updated), "onChange"),
34210
- onRemove: /* @__PURE__ */ __name(() => removeCondition(group.id, c.id), "onRemove")
34797
+ onRemove: /* @__PURE__ */ __name(() => removeCondition(group.id, c.id), "onRemove"),
34798
+ eventsOn
34211
34799
  })))), /* @__PURE__ */ React.createElement(Button, {
34212
34800
  type: "button",
34213
34801
  variant: "outline",
@@ -34270,6 +34858,7 @@ var init_DiscountsConditionsBuilder = __esm({
34270
34858
  "use client";
34271
34859
  init_button();
34272
34860
  init_select();
34861
+ init_admin_config_context();
34273
34862
  __name(uid, "uid");
34274
34863
  __name(conditionToRule, "conditionToRule");
34275
34864
  __name(rewardToRule, "rewardToRule");
@@ -36451,7 +37040,7 @@ function AdminPageResolver({ slug }) {
36451
37040
  const searchParams = useSearchParams();
36452
37041
  const { data: session } = useSession();
36453
37042
  const vendorPortal = isVendorPortalUser(session?.user);
36454
- const { customCrudConfigs, storeEnabled, multiVendorEnabled, eventsEnabled } = useContext(AdminConfigContext);
37043
+ const { customCrudConfigs, storeEnabled, multiVendorEnabled, eventsEnabled, vendorCanCreateCategories, vendorCanCreateCollections, vendorCanCreateBrands, requireProductApproval, requireEventApproval } = useContext(AdminConfigContext);
36455
37044
  const key = slug?.[0] || "dashboard";
36456
37045
  const [vendorOptions, setVendorOptions] = useState([]);
36457
37046
  useEffect(() => {
@@ -36485,6 +37074,11 @@ function AdminPageResolver({ slug }) {
36485
37074
  columns = columns.filter((column) => column.field !== "eventId" && column.field !== "eventName");
36486
37075
  filters = filters.filter((filter) => filter.param !== "eventId");
36487
37076
  }
37077
+ const showApprovalColumn = key === "products" && requireProductApproval === true || key === "events" && requireEventApproval === true;
37078
+ if (!showApprovalColumn) {
37079
+ columns = columns.filter((column) => column.field !== "approvalStatus");
37080
+ filters = filters.filter((filter) => filter.param !== "approvalStatus");
37081
+ }
36488
37082
  if (multiVendorEnabled !== false && !vendorPortal && STORE_VENDOR_RESOURCES.has(key) && key !== "event_products" && !columns.some((c) => c.field === "vendorId")) {
36489
37083
  columns = [
36490
37084
  VENDOR_COLUMN2,
@@ -36522,7 +37116,9 @@ function AdminPageResolver({ slug }) {
36522
37116
  vendorOptions,
36523
37117
  vendorPortal,
36524
37118
  multiVendorEnabled,
36525
- eventsEnabled
37119
+ eventsEnabled,
37120
+ requireProductApproval,
37121
+ requireEventApproval
36526
37122
  ]);
36527
37123
  const isContactsWithStore = key === "contacts" && storeEnabled;
36528
37124
  const extraListParams = useMemo(() => isContactsWithStore ? {
@@ -36636,7 +37232,27 @@ function AdminPageResolver({ slug }) {
36636
37232
  className: "ml-2"
36637
37233
  }, "Redirecting\u2026"));
36638
37234
  }
36639
- if (vendorPortal && key === "product_categories") {
37235
+ if (vendorPortal && key === "product_categories" && vendorCanCreateCategories !== true) {
37236
+ router.replace("/admin/products");
37237
+ return /* @__PURE__ */ React26__default.createElement("div", {
37238
+ className: "flex justify-center py-8"
37239
+ }, /* @__PURE__ */ React26__default.createElement("div", {
37240
+ className: "animate-spin rounded-full h-6 w-6 border-2 border-gray-300 border-t-gray-600"
37241
+ }), /* @__PURE__ */ React26__default.createElement("span", {
37242
+ className: "ml-2"
37243
+ }, "Redirecting\u2026"));
37244
+ }
37245
+ if (vendorPortal && key === "collections" && vendorCanCreateCollections !== true) {
37246
+ router.replace("/admin/products");
37247
+ return /* @__PURE__ */ React26__default.createElement("div", {
37248
+ className: "flex justify-center py-8"
37249
+ }, /* @__PURE__ */ React26__default.createElement("div", {
37250
+ className: "animate-spin rounded-full h-6 w-6 border-2 border-gray-300 border-t-gray-600"
37251
+ }), /* @__PURE__ */ React26__default.createElement("span", {
37252
+ className: "ml-2"
37253
+ }, "Redirecting\u2026"));
37254
+ }
37255
+ if (vendorPortal && key === "brands" && vendorCanCreateBrands !== true) {
36640
37256
  router.replace("/admin/products");
36641
37257
  return /* @__PURE__ */ React26__default.createElement("div", {
36642
37258
  className: "flex justify-center py-8"