@infuro/cms-core 1.0.41 → 1.0.43

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.41" ;
460
+ CMS_VERSION = "1.0.43" ;
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
  });
@@ -6641,9 +6673,10 @@ var init_CategoryAutocomplete = __esm({
6641
6673
  __name(CategoryAutocomplete, "CategoryAutocomplete");
6642
6674
  }
6643
6675
  });
6644
- function UserAutocomplete({ selectedUserId, onUserChange, placeholder = "Select author...", className = "" }) {
6676
+ function UserAutocomplete({ selectedUserId, onUserChange, placeholder = "Select author...", className = "", groupName = ADMIN_GROUP_NAME }) {
6645
6677
  const [inputValue, setInputValue] = useState("");
6646
6678
  const [suggestions, setSuggestions] = useState([]);
6679
+ const [selectedUser, setSelectedUser] = useState(null);
6647
6680
  const [isLoading, setIsLoading] = useState(false);
6648
6681
  const [showSuggestions, setShowSuggestions] = useState(false);
6649
6682
  const inputRef = useRef(null);
@@ -6652,14 +6685,18 @@ function UserAutocomplete({ selectedUserId, onUserChange, placeholder = "Select
6652
6685
  setIsLoading(true);
6653
6686
  try {
6654
6687
  const params = new URLSearchParams();
6655
- if (query.trim()) {
6656
- params.append("search", query);
6657
- }
6688
+ if (query.trim()) params.append("search", query);
6658
6689
  params.append("limit", "50");
6690
+ const group = groupName.trim() || ADMIN_GROUP_NAME;
6691
+ params.append("groupName", group);
6659
6692
  const response = await fetch(`/api/users?${params}`);
6660
6693
  if (response.ok) {
6661
6694
  const data = await response.json();
6662
- setSuggestions(data.data || []);
6695
+ const rows = Array.isArray(data.data) ? data.data : [];
6696
+ setSuggestions(rows.filter((u) => {
6697
+ const g = u.group?.name;
6698
+ return g == null || g === group;
6699
+ }));
6663
6700
  }
6664
6701
  } catch (error) {
6665
6702
  console.error("Error fetching users:", error);
@@ -6673,28 +6710,57 @@ function UserAutocomplete({ selectedUserId, onUserChange, placeholder = "Select
6673
6710
  }, 300);
6674
6711
  return () => clearTimeout(timeoutId);
6675
6712
  }, [
6676
- inputValue
6713
+ inputValue,
6714
+ groupName
6715
+ ]);
6716
+ useEffect(() => {
6717
+ if (selectedUserId == null) {
6718
+ setSelectedUser(null);
6719
+ return;
6720
+ }
6721
+ if (selectedUser?.id === selectedUserId) return;
6722
+ let cancelled = false;
6723
+ (async () => {
6724
+ try {
6725
+ const res = await fetch(`/api/users/${selectedUserId}`);
6726
+ if (!res.ok || cancelled) return;
6727
+ const data = await res.json();
6728
+ if (!cancelled && data?.id != null) {
6729
+ setSelectedUser({
6730
+ id: Number(data.id),
6731
+ name: String(data.name ?? ""),
6732
+ email: String(data.email ?? "")
6733
+ });
6734
+ }
6735
+ } catch {
6736
+ }
6737
+ })();
6738
+ return () => {
6739
+ cancelled = true;
6740
+ };
6741
+ }, [
6742
+ selectedUserId,
6743
+ selectedUser?.id
6677
6744
  ]);
6678
6745
  const handleInputChange = /* @__PURE__ */ __name((e) => {
6679
- const value = e.target.value;
6680
- setInputValue(value);
6746
+ setInputValue(e.target.value);
6681
6747
  setShowSuggestions(true);
6682
6748
  }, "handleInputChange");
6683
6749
  const handleSuggestionSelect = /* @__PURE__ */ __name((user) => {
6684
6750
  onUserChange(user.id);
6751
+ setSelectedUser(user);
6685
6752
  setInputValue("");
6686
6753
  setShowSuggestions(false);
6687
6754
  setSuggestions([]);
6688
6755
  }, "handleSuggestionSelect");
6689
6756
  const handleRemoveUser = /* @__PURE__ */ __name(() => {
6690
6757
  onUserChange(null);
6758
+ setSelectedUser(null);
6691
6759
  }, "handleRemoveUser");
6692
6760
  const handleKeyPress = /* @__PURE__ */ __name((e) => {
6693
6761
  if (e.key === "Enter") {
6694
6762
  e.preventDefault();
6695
- if (suggestions.length > 0) {
6696
- handleSuggestionSelect(suggestions[0]);
6697
- }
6763
+ if (suggestions.length > 0) handleSuggestionSelect(suggestions[0]);
6698
6764
  } else if (e.key === "Escape") {
6699
6765
  setShowSuggestions(false);
6700
6766
  inputRef.current?.blur();
@@ -6709,14 +6775,13 @@ function UserAutocomplete({ selectedUserId, onUserChange, placeholder = "Select
6709
6775
  document.addEventListener("mousedown", handleClickOutside);
6710
6776
  return () => document.removeEventListener("mousedown", handleClickOutside);
6711
6777
  }, []);
6712
- const selectedUser = suggestions.find((user) => user.id === selectedUserId);
6713
6778
  return /* @__PURE__ */ React.createElement("div", {
6714
6779
  className: `relative ${className}`
6715
6780
  }, selectedUser && /* @__PURE__ */ React.createElement("div", {
6716
6781
  className: "mb-2"
6717
6782
  }, /* @__PURE__ */ React.createElement(Badge, {
6718
6783
  className: "flex items-center gap-1"
6719
- }, selectedUser.name, /* @__PURE__ */ React.createElement(X, {
6784
+ }, selectedUser.name || selectedUser.email || `User #${selectedUser.id}`, /* @__PURE__ */ React.createElement(X, {
6720
6785
  size: 12,
6721
6786
  className: "cursor-pointer hover:text-red-500",
6722
6787
  onClick: handleRemoveUser
@@ -6760,6 +6825,7 @@ var init_UserAutocomplete = __esm({
6760
6825
  "use client";
6761
6826
  init_input();
6762
6827
  init_badge();
6828
+ init_permission_entities();
6763
6829
  __name(UserAutocomplete, "UserAutocomplete");
6764
6830
  }
6765
6831
  });
@@ -7190,7 +7256,8 @@ function BlogEditor({ existingBlog, duplicateSource }) {
7190
7256
  selectedUserId: authorId,
7191
7257
  onUserChange: setAuthorId,
7192
7258
  placeholder: "Select author...",
7193
- className: "w-full"
7259
+ className: "w-full",
7260
+ groupName: "Administrator"
7194
7261
  })), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(ImageUpload, {
7195
7262
  value: coverImage,
7196
7263
  onChange: setCoverImage,
@@ -7701,6 +7768,155 @@ var init_ComponentSettings = __esm({
7701
7768
  __name(ComponentSettings, "ComponentSettings");
7702
7769
  }
7703
7770
  });
7771
+ function ImageOrUrlField({ label, value, onChange, placeholder = "https://\u2026", inputClassName = "w-full rounded-md border border-gray-300 px-2 py-1.5 text-sm", labelClassName = "block text-xs font-medium text-gray-600 mb-1", previewVariant = "banner", maxSizeMb = 10 }) {
7772
+ const fileInputRef = useRef(null);
7773
+ const [isUploading, setIsUploading] = useState(false);
7774
+ const [error, setError] = useState(null);
7775
+ const [lightboxOpen, setLightboxOpen] = useState(false);
7776
+ const previewCls = previewVariant === "logo" ? "h-20 w-20 rounded-md border border-gray-200 bg-white object-contain cursor-pointer" : "max-h-32 w-full rounded-md border border-gray-200 bg-gray-50 object-cover cursor-pointer";
7777
+ const closeLightbox = useCallback(() => setLightboxOpen(false), []);
7778
+ useEffect(() => {
7779
+ if (!lightboxOpen) return;
7780
+ const onKeyDown = /* @__PURE__ */ __name((e) => {
7781
+ if (e.key === "Escape") closeLightbox();
7782
+ }, "onKeyDown");
7783
+ window.addEventListener("keydown", onKeyDown);
7784
+ const prevOverflow = document.body.style.overflow;
7785
+ document.body.style.overflow = "hidden";
7786
+ return () => {
7787
+ window.removeEventListener("keydown", onKeyDown);
7788
+ document.body.style.overflow = prevOverflow;
7789
+ };
7790
+ }, [
7791
+ lightboxOpen,
7792
+ closeLightbox
7793
+ ]);
7794
+ const handleUpload = useCallback(async (file) => {
7795
+ setError(null);
7796
+ if (!ACCEPTED_TYPES.includes(file.type)) {
7797
+ setError(`Unsupported file type. Use: ${ACCEPTED_TYPES.map((t) => t.replace("image/", "")).join(", ")}`);
7798
+ return;
7799
+ }
7800
+ if (file.size > maxSizeMb * 1024 * 1024) {
7801
+ setError(`File must be under ${maxSizeMb}MB`);
7802
+ return;
7803
+ }
7804
+ setIsUploading(true);
7805
+ try {
7806
+ const formData = new FormData();
7807
+ formData.append("file", file);
7808
+ const response = await fetch("/api/upload", {
7809
+ method: "POST",
7810
+ body: formData
7811
+ });
7812
+ const data = await response.json();
7813
+ if (!response.ok) {
7814
+ throw new Error(data.error || data.details || "Upload failed");
7815
+ }
7816
+ onChange(data.filePath ?? "");
7817
+ } catch (err) {
7818
+ setError(err instanceof Error ? err.message : "Upload failed");
7819
+ } finally {
7820
+ setIsUploading(false);
7821
+ if (fileInputRef.current) fileInputRef.current.value = "";
7822
+ }
7823
+ }, [
7824
+ maxSizeMb,
7825
+ onChange
7826
+ ]);
7827
+ const onFileChange = useCallback((e) => {
7828
+ const file = e.target.files?.[0];
7829
+ if (file) void handleUpload(file);
7830
+ }, [
7831
+ handleUpload
7832
+ ]);
7833
+ const trimmed = value.trim();
7834
+ return /* @__PURE__ */ React.createElement("div", {
7835
+ className: "space-y-2"
7836
+ }, /* @__PURE__ */ React.createElement("label", {
7837
+ className: labelClassName
7838
+ }, label), trimmed ? /* @__PURE__ */ React.createElement("div", {
7839
+ className: "flex items-start gap-3"
7840
+ }, /* @__PURE__ */ React.createElement("img", {
7841
+ src: trimmed,
7842
+ alt: label,
7843
+ className: previewCls,
7844
+ role: "button",
7845
+ tabIndex: 0,
7846
+ title: "Click to enlarge",
7847
+ onClick: /* @__PURE__ */ __name(() => setLightboxOpen(true), "onClick"),
7848
+ onKeyDown: /* @__PURE__ */ __name((e) => {
7849
+ if (e.key === "Enter" || e.key === " ") {
7850
+ e.preventDefault();
7851
+ setLightboxOpen(true);
7852
+ }
7853
+ }, "onKeyDown"),
7854
+ onError: /* @__PURE__ */ __name((e) => {
7855
+ e.currentTarget.style.display = "none";
7856
+ }, "onError")
7857
+ }), /* @__PURE__ */ React.createElement("button", {
7858
+ type: "button",
7859
+ onClick: /* @__PURE__ */ __name(() => onChange(""), "onClick"),
7860
+ className: "inline-flex items-center gap-1 rounded border border-gray-300 bg-white px-2 py-1 text-xs text-gray-600 hover:bg-gray-50"
7861
+ }, /* @__PURE__ */ React.createElement(X, {
7862
+ className: "h-3 w-3"
7863
+ }), "Clear")) : null, /* @__PURE__ */ React.createElement("div", {
7864
+ className: "flex flex-wrap gap-2"
7865
+ }, /* @__PURE__ */ React.createElement("input", {
7866
+ type: "url",
7867
+ value,
7868
+ onChange: /* @__PURE__ */ __name((e) => {
7869
+ setError(null);
7870
+ onChange(e.target.value);
7871
+ }, "onChange"),
7872
+ placeholder,
7873
+ className: `${inputClassName} min-w-0 flex-1`
7874
+ }), /* @__PURE__ */ React.createElement("input", {
7875
+ ref: fileInputRef,
7876
+ type: "file",
7877
+ accept: ACCEPTED_TYPES.join(","),
7878
+ onChange: onFileChange,
7879
+ className: "hidden",
7880
+ disabled: isUploading
7881
+ }), /* @__PURE__ */ React.createElement("button", {
7882
+ type: "button",
7883
+ onClick: /* @__PURE__ */ __name(() => fileInputRef.current?.click(), "onClick"),
7884
+ disabled: isUploading,
7885
+ className: "inline-flex shrink-0 items-center gap-1.5 rounded-md border border-gray-300 bg-white px-3 py-1.5 text-sm font-medium text-gray-700 hover:bg-gray-50 disabled:opacity-50"
7886
+ }, /* @__PURE__ */ React.createElement(Upload, {
7887
+ className: "h-3.5 w-3.5"
7888
+ }), isUploading ? "Uploading\u2026" : "Upload")), error ? /* @__PURE__ */ React.createElement("p", {
7889
+ className: "flex items-center gap-1.5 text-xs text-red-600"
7890
+ }, /* @__PURE__ */ React.createElement(AlertCircle, {
7891
+ className: "h-3.5 w-3.5 shrink-0"
7892
+ }), error) : /* @__PURE__ */ React.createElement("p", {
7893
+ className: "text-xs text-gray-500"
7894
+ }, "Paste a URL or upload an image (PNG, JPG, GIF, WEBP up to ", maxSizeMb, "MB)"), lightboxOpen && trimmed ? /* @__PURE__ */ React.createElement("div", {
7895
+ className: "fixed inset-0 z-[100] flex items-center justify-center bg-black/70 p-4",
7896
+ role: "dialog",
7897
+ "aria-modal": "true",
7898
+ "aria-label": `${label} preview`,
7899
+ onClick: closeLightbox
7900
+ }, /* @__PURE__ */ React.createElement("img", {
7901
+ src: trimmed,
7902
+ alt: label,
7903
+ className: "max-h-[90vh] max-w-[90vw] rounded-md object-contain shadow-lg",
7904
+ onClick: /* @__PURE__ */ __name((e) => e.stopPropagation(), "onClick")
7905
+ })) : null);
7906
+ }
7907
+ var ACCEPTED_TYPES;
7908
+ var init_ImageOrUrlField = __esm({
7909
+ "src/components/Admin/ImageOrUrlField.tsx"() {
7910
+ "use client";
7911
+ ACCEPTED_TYPES = [
7912
+ "image/jpeg",
7913
+ "image/png",
7914
+ "image/gif",
7915
+ "image/webp"
7916
+ ];
7917
+ __name(ImageOrUrlField, "ImageOrUrlField");
7918
+ }
7919
+ });
7704
7920
  function generateId() {
7705
7921
  return `nav_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`;
7706
7922
  }
@@ -8003,22 +8219,16 @@ function NavbarEditor({ config, onChange }) {
8003
8219
  className: "space-y-6"
8004
8220
  }, /* @__PURE__ */ React.createElement("div", {
8005
8221
  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, {
8222
+ }, /* @__PURE__ */ React.createElement(ImageOrUrlField, {
8223
+ label: "Logo",
8015
8224
  value: config.logo || "",
8016
- onChange: /* @__PURE__ */ __name((e) => onChange({
8225
+ onChange: /* @__PURE__ */ __name((logo) => onChange({
8017
8226
  ...config,
8018
- logo: e.target.value
8227
+ logo
8019
8228
  }), "onChange"),
8229
+ previewVariant: "logo",
8020
8230
  placeholder: "Logo image URL"
8021
- }))), /* @__PURE__ */ React.createElement("div", {
8231
+ })), /* @__PURE__ */ React.createElement("div", {
8022
8232
  className: "space-y-3"
8023
8233
  }, /* @__PURE__ */ React.createElement("div", {
8024
8234
  className: "flex items-center justify-between"
@@ -8057,6 +8267,7 @@ var init_NavbarEditor = __esm({
8057
8267
  init_button();
8058
8268
  init_switch();
8059
8269
  init_label();
8270
+ init_ImageOrUrlField();
8060
8271
  __name(generateId, "generateId");
8061
8272
  __name(NavItemEditor, "NavItemEditor");
8062
8273
  __name(updateItemInTree, "updateItemInTree");
@@ -11005,10 +11216,32 @@ function renderListPrice(value, item) {
11005
11216
  return `${formatted} ${currency}`;
11006
11217
  }
11007
11218
  }
11219
+ function renderListThumbnail(url) {
11220
+ const trimmed = url.trim();
11221
+ if (!trimmed) return "\u2014";
11222
+ return createElement("img", {
11223
+ src: trimmed,
11224
+ alt: "",
11225
+ className: "h-10 w-10 rounded border border-gray-200 bg-white object-contain"
11226
+ });
11227
+ }
11228
+ function productListImageUrl(item) {
11229
+ const meta = item.metadata;
11230
+ if (!meta || typeof meta !== "object" || Array.isArray(meta)) return "";
11231
+ const images = meta.images;
11232
+ if (!Array.isArray(images)) return "";
11233
+ const rows = images;
11234
+ const def = rows.find((r) => r?.isDefault && typeof r.url === "string" && r.url.trim());
11235
+ if (def && typeof def.url === "string") return def.url.trim();
11236
+ const first = rows.find((r) => typeof r.url === "string" && r.url.trim());
11237
+ return first && typeof first.url === "string" ? first.url.trim() : "";
11238
+ }
11008
11239
  var STORE_CRUD_CONFIGS;
11009
11240
  var init_store_crud_configs = __esm({
11010
11241
  "src/admin/store-crud-configs.ts"() {
11011
11242
  __name(renderListPrice, "renderListPrice");
11243
+ __name(renderListThumbnail, "renderListThumbnail");
11244
+ __name(productListImageUrl, "productListImageUrl");
11012
11245
  STORE_CRUD_CONFIGS = {
11013
11246
  products: {
11014
11247
  title: "Products",
@@ -11034,9 +11267,40 @@ var init_store_crud_configs = __esm({
11034
11267
  label: "Out of stock"
11035
11268
  }
11036
11269
  ]
11270
+ },
11271
+ {
11272
+ param: "approvalStatus",
11273
+ label: "Approval",
11274
+ type: "select",
11275
+ options: [
11276
+ {
11277
+ value: "",
11278
+ label: "All"
11279
+ },
11280
+ {
11281
+ value: "pending",
11282
+ label: "Pending"
11283
+ },
11284
+ {
11285
+ value: "approved",
11286
+ label: "Approved"
11287
+ },
11288
+ {
11289
+ value: "rejected",
11290
+ label: "Rejected"
11291
+ }
11292
+ ]
11037
11293
  }
11038
11294
  ],
11039
11295
  columns: [
11296
+ {
11297
+ field: "metadata",
11298
+ displayName: "Image",
11299
+ hideInCreate: true,
11300
+ hideInEdit: true,
11301
+ listFilter: false,
11302
+ render: /* @__PURE__ */ __name((_value, item) => renderListThumbnail(productListImageUrl(item)), "render")
11303
+ },
11040
11304
  {
11041
11305
  field: "name",
11042
11306
  displayName: "Name"
@@ -11098,6 +11362,33 @@ var init_store_crud_configs = __esm({
11098
11362
  }
11099
11363
  ]
11100
11364
  },
11365
+ {
11366
+ field: "approvalStatus",
11367
+ displayName: "Approval",
11368
+ type: "select",
11369
+ listFilter: false,
11370
+ options: [
11371
+ {
11372
+ value: "pending",
11373
+ label: "Pending"
11374
+ },
11375
+ {
11376
+ value: "approved",
11377
+ label: "Approved"
11378
+ },
11379
+ {
11380
+ value: "rejected",
11381
+ label: "Rejected"
11382
+ }
11383
+ ],
11384
+ render: /* @__PURE__ */ __name((value) => {
11385
+ const v = value == null || value === "" ? null : String(value);
11386
+ if (v === "pending") return "Pending";
11387
+ if (v === "approved") return "Approved";
11388
+ if (v === "rejected") return "Rejected";
11389
+ return "\u2014";
11390
+ }, "render")
11391
+ },
11101
11392
  {
11102
11393
  field: "featured",
11103
11394
  displayName: "Featured",
@@ -11221,6 +11512,14 @@ var init_store_crud_configs = __esm({
11221
11512
  title: "Collections",
11222
11513
  apiEndpoint: "/api/collections",
11223
11514
  columns: [
11515
+ {
11516
+ field: "image",
11517
+ displayName: "Image",
11518
+ hideInCreate: true,
11519
+ hideInEdit: true,
11520
+ listFilter: false,
11521
+ render: /* @__PURE__ */ __name((value) => renderListThumbnail(typeof value === "string" ? value : ""), "render")
11522
+ },
11224
11523
  {
11225
11524
  field: "name",
11226
11525
  displayName: "Name"
@@ -11617,6 +11916,13 @@ var init_store_crud_configs = __esm({
11617
11916
  field: "slug",
11618
11917
  displayName: "Slug"
11619
11918
  },
11919
+ {
11920
+ field: "isCatalog",
11921
+ displayName: "Catalog",
11922
+ type: "boolean",
11923
+ hideInCreate: true,
11924
+ hideInEdit: true
11925
+ },
11620
11926
  {
11621
11927
  field: "active",
11622
11928
  displayName: "Active",
@@ -11919,6 +12225,29 @@ var init_store_crud_configs = __esm({
11919
12225
  label: "Paid"
11920
12226
  }
11921
12227
  ]
12228
+ },
12229
+ {
12230
+ param: "approvalStatus",
12231
+ label: "Approval",
12232
+ type: "select",
12233
+ options: [
12234
+ {
12235
+ value: "",
12236
+ label: "All"
12237
+ },
12238
+ {
12239
+ value: "pending",
12240
+ label: "Pending"
12241
+ },
12242
+ {
12243
+ value: "approved",
12244
+ label: "Approved"
12245
+ },
12246
+ {
12247
+ value: "rejected",
12248
+ label: "Rejected"
12249
+ }
12250
+ ]
11922
12251
  }
11923
12252
  ],
11924
12253
  columns: [
@@ -11950,6 +12279,33 @@ var init_store_crud_configs = __esm({
11950
12279
  displayName: "Active",
11951
12280
  type: "boolean"
11952
12281
  },
12282
+ {
12283
+ field: "approvalStatus",
12284
+ displayName: "Approval",
12285
+ type: "select",
12286
+ listFilter: false,
12287
+ options: [
12288
+ {
12289
+ value: "pending",
12290
+ label: "Pending"
12291
+ },
12292
+ {
12293
+ value: "approved",
12294
+ label: "Approved"
12295
+ },
12296
+ {
12297
+ value: "rejected",
12298
+ label: "Rejected"
12299
+ }
12300
+ ],
12301
+ render: /* @__PURE__ */ __name((value) => {
12302
+ const v = value == null || value === "" ? null : String(value);
12303
+ if (v === "pending") return "Pending";
12304
+ if (v === "approved") return "Approved";
12305
+ if (v === "rejected") return "Rejected";
12306
+ return "\u2014";
12307
+ }, "render")
12308
+ },
11953
12309
  {
11954
12310
  field: "comingSoon",
11955
12311
  displayName: "Coming soon",
@@ -12236,7 +12592,12 @@ function SettingsPage() {
12236
12592
  const [themeSettingsLoading, setThemeSettingsLoading] = useState(true);
12237
12593
  const [storeEnabled, setStoreEnabled] = useState(false);
12238
12594
  const [multiVendorEnabled, setMultiVendorEnabled] = useState(true);
12595
+ const [vendorCanCreateCategories, setVendorCanCreateCategories] = useState(false);
12596
+ const [vendorCanCreateCollections, setVendorCanCreateCollections] = useState(false);
12597
+ const [vendorCanCreateBrands, setVendorCanCreateBrands] = useState(false);
12598
+ const [requireProductApproval, setRequireProductApproval] = useState(false);
12239
12599
  const [eventsEnabled, setEventsEnabled] = useState(true);
12600
+ const [requireEventApproval, setRequireEventApproval] = useState(false);
12240
12601
  const [storeSettingsLoading, setStoreSettingsLoading] = useState(true);
12241
12602
  const [currency, setCurrency] = useState(DEFAULT_CURRENCY);
12242
12603
  const [currencies, setCurrencies] = useState([]);
@@ -12296,10 +12657,15 @@ function SettingsPage() {
12296
12657
  }).finally(() => setStoreSettingsLoading(false));
12297
12658
  fetch("/api/settings/multi_vendor").then((r) => r.ok ? r.json() : {}).then((data) => {
12298
12659
  setMultiVendorEnabled(data.enabled !== "false");
12660
+ setVendorCanCreateCategories(data.vendorCanCreateCategories === "true");
12661
+ setVendorCanCreateCollections(data.vendorCanCreateCollections === "true");
12662
+ setVendorCanCreateBrands(data.vendorCanCreateBrands === "true");
12663
+ setRequireProductApproval(data.requireProductApproval === "true");
12299
12664
  }).catch(() => {
12300
12665
  });
12301
12666
  fetch("/api/settings/events").then((r) => r.ok ? r.json() : {}).then((data) => {
12302
12667
  setEventsEnabled(data.enabled !== "false");
12668
+ setRequireEventApproval(data.requireEventApproval === "true");
12303
12669
  }).catch(() => {
12304
12670
  });
12305
12671
  fetch("/api/currencies/exchange-rates").then((r) => r.ok ? r.json() : []).then((data) => {
@@ -12427,6 +12793,22 @@ function SettingsPage() {
12427
12793
  enabled: {
12428
12794
  value: multiVendorEnabled ? "true" : "false",
12429
12795
  type: "public"
12796
+ },
12797
+ vendorCanCreateCategories: {
12798
+ value: vendorCanCreateCategories ? "true" : "false",
12799
+ type: "public"
12800
+ },
12801
+ vendorCanCreateCollections: {
12802
+ value: vendorCanCreateCollections ? "true" : "false",
12803
+ type: "public"
12804
+ },
12805
+ vendorCanCreateBrands: {
12806
+ value: vendorCanCreateBrands ? "true" : "false",
12807
+ type: "public"
12808
+ },
12809
+ requireProductApproval: {
12810
+ value: requireProductApproval ? "true" : "false",
12811
+ type: "public"
12430
12812
  }
12431
12813
  })
12432
12814
  });
@@ -12439,6 +12821,10 @@ function SettingsPage() {
12439
12821
  enabled: {
12440
12822
  value: eventsEnabled ? "true" : "false",
12441
12823
  type: "public"
12824
+ },
12825
+ requireEventApproval: {
12826
+ value: requireEventApproval ? "true" : "false",
12827
+ type: "public"
12442
12828
  }
12443
12829
  })
12444
12830
  });
@@ -12710,8 +13096,46 @@ function SettingsPage() {
12710
13096
  className: "text-sm font-medium text-gray-700"
12711
13097
  }, "Multi Vendor"), /* @__PURE__ */ React.createElement("p", {
12712
13098
  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"
13099
+ }, "Enable vendor portals, vendor logins, and vendor-scoped store data. Disabling this blocks vendor-only accounts from signing in."))), storeEnabled && multiVendorEnabled && /* @__PURE__ */ React.createElement("div", {
13100
+ className: "mt-4 space-y-3 p-4 bg-gray-50 rounded-lg border border-gray-200"
13101
+ }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", {
13102
+ className: "text-sm font-medium text-gray-700"
13103
+ }, "Vendor catalog permissions"), /* @__PURE__ */ React.createElement("p", {
13104
+ className: "text-xs text-gray-500 mb-3"
13105
+ }, "When off (default), vendors only pick admin-created categories, collections, and brands on products. When on, vendors also get that Store tab and can create their own.")), /* @__PURE__ */ React.createElement("div", {
13106
+ className: "flex items-center gap-3"
13107
+ }, /* @__PURE__ */ React.createElement(Switch, {
13108
+ checked: vendorCanCreateCategories,
13109
+ onCheckedChange: setVendorCanCreateCategories
13110
+ }), /* @__PURE__ */ React.createElement(Label3, {
13111
+ className: "text-sm font-medium text-gray-700"
13112
+ }, "Can vendors create categories")), /* @__PURE__ */ React.createElement("div", {
13113
+ className: "flex items-center gap-3"
13114
+ }, /* @__PURE__ */ React.createElement(Switch, {
13115
+ checked: vendorCanCreateCollections,
13116
+ onCheckedChange: setVendorCanCreateCollections
13117
+ }), /* @__PURE__ */ React.createElement(Label3, {
13118
+ className: "text-sm font-medium text-gray-700"
13119
+ }, "Can vendors create collections")), /* @__PURE__ */ React.createElement("div", {
13120
+ className: "flex items-center gap-3"
13121
+ }, /* @__PURE__ */ React.createElement(Switch, {
13122
+ checked: vendorCanCreateBrands,
13123
+ onCheckedChange: setVendorCanCreateBrands
13124
+ }), /* @__PURE__ */ React.createElement(Label3, {
13125
+ className: "text-sm font-medium text-gray-700"
13126
+ }, "Can vendors create brands")), /* @__PURE__ */ React.createElement("div", {
13127
+ className: "flex items-start gap-3"
13128
+ }, /* @__PURE__ */ React.createElement(Switch, {
13129
+ checked: requireProductApproval,
13130
+ onCheckedChange: setRequireProductApproval
13131
+ }), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(Label3, {
13132
+ className: "text-sm font-medium text-gray-700"
13133
+ }, "Require product approval"), /* @__PURE__ */ React.createElement("p", {
13134
+ className: "text-xs text-gray-500 mt-0.5"
13135
+ }, "When on, new vendor products start as waiting for approval. Admins approve or reject; only after approval can the vendor set the product to available (live).")))), storeEnabled && /* @__PURE__ */ React.createElement("div", {
13136
+ className: "mt-4 space-y-3 p-4 bg-gray-50 rounded-lg border border-gray-200"
13137
+ }, /* @__PURE__ */ React.createElement("div", {
13138
+ className: "flex items-center gap-3"
12715
13139
  }, /* @__PURE__ */ React.createElement(Switch, {
12716
13140
  checked: eventsEnabled,
12717
13141
  onCheckedChange: setEventsEnabled
@@ -12719,7 +13143,16 @@ function SettingsPage() {
12719
13143
  className: "text-sm font-medium text-gray-700"
12720
13144
  }, "Events"), /* @__PURE__ */ React.createElement("p", {
12721
13145
  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", {
13146
+ }, "Enable event management. Disabling this hides the Events tab and blocks all event-related APIs."))), eventsEnabled && multiVendorEnabled ? /* @__PURE__ */ React.createElement("div", {
13147
+ className: "flex items-start gap-3 pl-1 border-t border-gray-200 pt-3"
13148
+ }, /* @__PURE__ */ React.createElement(Switch, {
13149
+ checked: requireEventApproval,
13150
+ onCheckedChange: setRequireEventApproval
13151
+ }), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(Label3, {
13152
+ className: "text-sm font-medium text-gray-700"
13153
+ }, "Require event approval"), /* @__PURE__ */ React.createElement("p", {
13154
+ className: "text-xs text-gray-500 mt-0.5"
13155
+ }, "When on, new vendor events start as pending. Admins approve or reject; approval activates the event (live)."))) : null), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", {
12723
13156
  className: "flex items-center justify-between mb-1"
12724
13157
  }, /* @__PURE__ */ React.createElement("label", {
12725
13158
  className: "block text-sm font-semibold text-gray-700"
@@ -13558,7 +13991,11 @@ function ScheduleAuthorSelect({ value, onChange, disabled }) {
13558
13991
  (async () => {
13559
13992
  setLoading(true);
13560
13993
  try {
13561
- const res = await fetch("/api/users?limit=500");
13994
+ const params = new URLSearchParams({
13995
+ limit: "100",
13996
+ groupName: ADMIN_GROUP_NAME
13997
+ });
13998
+ const res = await fetch(`/api/users?${params}`);
13562
13999
  if (!res.ok || cancelled) return;
13563
14000
  const data = await res.json();
13564
14001
  const rows = Array.isArray(data.data) ? data.data : [];
@@ -13591,7 +14028,7 @@ function ScheduleAuthorSelect({ value, onChange, disabled }) {
13591
14028
  }, /* @__PURE__ */ React.createElement(SelectTrigger, {
13592
14029
  className: "w-full"
13593
14030
  }, /* @__PURE__ */ React.createElement(SelectValue, {
13594
- placeholder: users.length === 0 ? "No users found" : "Select author"
14031
+ placeholder: users.length === 0 ? "No administrators found" : "Select author"
13595
14032
  })), /* @__PURE__ */ React.createElement(SelectContent, {
13596
14033
  className: "max-h-72"
13597
14034
  }, users.map((u) => /* @__PURE__ */ React.createElement(SelectItem, {
@@ -13603,6 +14040,7 @@ var init_ScheduleAuthorSelect = __esm({
13603
14040
  "src/admin/ScheduleAuthorSelect.tsx"() {
13604
14041
  "use client";
13605
14042
  init_select();
14043
+ init_permission_entities();
13606
14044
  __name(userLabel, "userLabel");
13607
14045
  __name(ScheduleAuthorSelect, "ScheduleAuthorSelect");
13608
14046
  }
@@ -16989,15 +17427,13 @@ function PluginSettingsPanel({ descriptor, onSaved }) {
16989
17427
  className: "text-xs text-gray-500 dark:text-gray-400"
16990
17428
  }, "Layout below merges with Branding settings; values here override branding when set. Use absolute URLs for logos in email."), /* @__PURE__ */ React.createElement("div", {
16991
17429
  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`,
17430
+ }, /* @__PURE__ */ React.createElement(ImageOrUrlField, {
17431
+ label: "Logo (optional override)",
16997
17432
  value: logoUrl,
16998
- onChange: /* @__PURE__ */ __name((e) => setLogoUrl(e.target.value), "onChange"),
17433
+ onChange: setLogoUrl,
17434
+ previewVariant: "logo",
16999
17435
  placeholder: "https://\u2026",
17000
- className: "h-8 text-sm"
17436
+ inputClassName: "h-8 text-sm w-full rounded-md border border-input bg-background px-3"
17001
17437
  })), /* @__PURE__ */ React.createElement("div", {
17002
17438
  className: "space-y-1"
17003
17439
  }, /* @__PURE__ */ React.createElement(Label3, {
@@ -17050,22 +17486,23 @@ function PluginSettingsPanel({ descriptor, onSaved }) {
17050
17486
  className: "flex flex-wrap items-end gap-2 border-b border-border/60 pb-3 dark:border-gray-600"
17051
17487
  }, /* @__PURE__ */ React.createElement("div", {
17052
17488
  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, {
17489
+ }, /* @__PURE__ */ React.createElement(ImageOrUrlField, {
17490
+ label: "Icon image",
17056
17491
  value: row.iconUrl,
17057
- onChange: /* @__PURE__ */ __name((e) => {
17492
+ onChange: /* @__PURE__ */ __name((v) => {
17058
17493
  const next = [
17059
17494
  ...socialLinkRows
17060
17495
  ];
17061
17496
  next[i] = {
17062
17497
  ...next[i],
17063
- iconUrl: e.target.value
17498
+ iconUrl: v
17064
17499
  };
17065
17500
  setSocialLinkRows(next);
17066
17501
  }, "onChange"),
17502
+ previewVariant: "logo",
17067
17503
  placeholder: "https://\u2026",
17068
- className: "h-8 text-sm"
17504
+ inputClassName: "h-8 text-sm w-full rounded-md border border-input bg-background px-3",
17505
+ labelClassName: "text-xs text-muted-foreground"
17069
17506
  })), /* @__PURE__ */ React.createElement("div", {
17070
17507
  className: "min-w-[160px] flex-1 space-y-1"
17071
17508
  }, /* @__PURE__ */ React.createElement(Label3, {
@@ -17712,15 +18149,13 @@ function PluginSettingsPanel({ descriptor, onSaved }) {
17712
18149
  className: "h-8 text-sm"
17713
18150
  })), /* @__PURE__ */ React.createElement("div", {
17714
18151
  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`,
18152
+ }, /* @__PURE__ */ React.createElement(ImageOrUrlField, {
18153
+ label: "Icon image",
17720
18154
  value: iconImageUrl,
17721
- onChange: /* @__PURE__ */ __name((e) => setIconImageUrl(e.target.value), "onChange"),
18155
+ onChange: setIconImageUrl,
18156
+ previewVariant: "logo",
17722
18157
  placeholder: "https://\u2026 or /images/chat-icon.png",
17723
- className: "h-8 text-sm"
18158
+ inputClassName: "h-8 text-sm w-full rounded-md border border-input bg-background px-3"
17724
18159
  }), /* @__PURE__ */ React.createElement("p", {
17725
18160
  className: "text-xs text-gray-500 dark:text-gray-400"
17726
18161
  }, "PNG or image URL. Leave empty to use emoji below.")), /* @__PURE__ */ React.createElement("div", {
@@ -18280,6 +18715,7 @@ var init_PluginsPage = __esm({
18280
18715
  init_checkbox();
18281
18716
  init_select();
18282
18717
  init_EventNotificationsPluginSettings();
18718
+ init_ImageOrUrlField();
18283
18719
  init_chat_email_intent();
18284
18720
  init_llm_agent_scope();
18285
18721
  __name(normalizeLinkedInOrganizations, "normalizeLinkedInOrganizations");
@@ -19963,7 +20399,7 @@ var init_VendorPortalProfilePage = __esm({
19963
20399
  __name(VendorPortalProfilePage, "VendorPortalProfilePage");
19964
20400
  }
19965
20401
  });
19966
- function DetailPageHeader({ title, subtitle, backHref, backLabel = "Back", closeHref, onClose, menuItems = [] }) {
20402
+ function DetailPageHeader({ title, subtitle, backHref, backLabel = "Back", closeHref, onClose, headerExtra, menuItems = [] }) {
19967
20403
  const router = useRouter();
19968
20404
  const handleClose = /* @__PURE__ */ __name(() => {
19969
20405
  if (onClose) onClose();
@@ -19987,7 +20423,7 @@ function DetailPageHeader({ title, subtitle, backHref, backLabel = "Back", close
19987
20423
  className: "text-sm text-gray-400 truncate"
19988
20424
  }, subtitle))), /* @__PURE__ */ React.createElement("div", {
19989
20425
  className: "flex items-center gap-2 shrink-0"
19990
- }, menuItems.length > 0 && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", {
20426
+ }, headerExtra, menuItems.length > 0 && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", {
19991
20427
  className: "flex items-center gap-2 md:hidden"
19992
20428
  }, menuItems.map((item, i) => {
19993
20429
  const Icon2 = item.icon;
@@ -25167,15 +25603,11 @@ function SeoTabContent(props) {
25167
25603
  onChange: /* @__PURE__ */ __name((e) => props.setSeoOgDescription(e.target.value), "onChange"),
25168
25604
  placeholder: "Open Graph description",
25169
25605
  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, {
25606
+ })), /* @__PURE__ */ React26__default.createElement(ImageOrUrlField, {
25607
+ label: "OG Image",
25175
25608
  value: props.seoOgImage,
25176
- onChange: /* @__PURE__ */ __name((e) => props.setSeoOgImage(e.target.value), "onChange"),
25177
- placeholder: "https://..."
25178
- })));
25609
+ onChange: props.setSeoOgImage
25610
+ }));
25179
25611
  }
25180
25612
  function CollapsibleSection({ title, icon: Icon2, open, onToggle, children }) {
25181
25613
  return /* @__PURE__ */ React26__default.createElement("div", {
@@ -25561,6 +25993,7 @@ var init_PageBuilderPage = __esm({
25561
25993
  init_ComponentSettings();
25562
25994
  init_admin_config_context();
25563
25995
  init_registry();
25996
+ init_ImageOrUrlField();
25564
25997
  __name(createSelectable, "createSelectable");
25565
25998
  __name(buildEditorResolver, "buildEditorResolver");
25566
25999
  __name(getIcon, "getIcon");
@@ -25623,15 +26056,12 @@ function SeoSection({ values, onChange }) {
25623
26056
  onChange: /* @__PURE__ */ __name((e) => onChange("seoOgDescription", e.target.value), "onChange"),
25624
26057
  className: textareaCls,
25625
26058
  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",
26059
+ })), /* @__PURE__ */ React.createElement(ImageOrUrlField, {
26060
+ label: "OG Image",
25630
26061
  value: values.seoOgImage,
25631
- onChange: /* @__PURE__ */ __name((e) => onChange("seoOgImage", e.target.value), "onChange"),
25632
- placeholder: "https://...",
25633
- className: inputCls9
25634
- }))));
26062
+ onChange: /* @__PURE__ */ __name((v) => onChange("seoOgImage", v), "onChange"),
26063
+ inputClassName: inputCls9
26064
+ })));
25635
26065
  }
25636
26066
  async function saveSeo(seo, slug, existingSeoId) {
25637
26067
  const hasSeo = seo.seoTitle || seo.seoDescription || seo.seoKeywords || seo.seoOgTitle || seo.seoOgDescription || seo.seoOgImage;
@@ -25696,6 +26126,7 @@ async function fetchSeo(seoId) {
25696
26126
  var init_SeoSection = __esm({
25697
26127
  "src/components/Admin/SeoSection.tsx"() {
25698
26128
  "use client";
26129
+ init_ImageOrUrlField();
25699
26130
  __name(SeoSection, "SeoSection");
25700
26131
  __name(saveSeo, "saveSeo");
25701
26132
  __name(fetchSeo, "fetchSeo");
@@ -25710,6 +26141,8 @@ __export(BrandEditPage_exports, {
25710
26141
  function BrandEditPage({ brandId }) {
25711
26142
  const router = useRouter();
25712
26143
  const searchParams = useSearchParams();
26144
+ const { data: session } = useSession();
26145
+ const vendorPortal = isVendorPortalUser(session?.user);
25713
26146
  const listReturnUrl = safeAdminListReturnUrl(searchParams.get("from")) ?? "/admin/brands";
25714
26147
  const create = isCreate(brandId);
25715
26148
  const dupParam = searchParams.get("duplicateFrom");
@@ -25811,6 +26244,9 @@ function BrandEditPage({ brandId }) {
25811
26244
  active,
25812
26245
  sortOrder
25813
26246
  };
26247
+ if (!vendorPortal) {
26248
+ payload.isCatalog = true;
26249
+ }
25814
26250
  if (savedSeoId) payload.seoId = savedSeoId;
25815
26251
  const res = await fetch(create ? "/api/brands" : `/api/brands/${brandId}`, {
25816
26252
  method: create ? "POST" : "PUT",
@@ -25902,13 +26338,12 @@ function BrandEditPage({ brandId }) {
25902
26338
  onChange: /* @__PURE__ */ __name((e) => setDescription(e.target.value), "onChange"),
25903
26339
  className: "w-full rounded-md border border-gray-300 px-3 py-2 text-sm min-h-[80px]",
25904
26340
  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",
26341
+ })), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(ImageOrUrlField, {
26342
+ label: "Logo",
25909
26343
  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"
26344
+ onChange: setLogo,
26345
+ previewVariant: "logo",
26346
+ inputClassName: "w-full rounded-md border border-gray-300 px-3 py-2 text-sm"
25912
26347
  })), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("label", {
25913
26348
  className: "block text-xs font-medium text-gray-600 mb-1"
25914
26349
  }, "Sort order"), /* @__PURE__ */ React.createElement("input", {
@@ -25951,6 +26386,8 @@ var init_BrandEditPage = __esm({
25951
26386
  init_SeoSection();
25952
26387
  init_DetailPageLayout();
25953
26388
  init_DetailPageHeader();
26389
+ init_vendor_scope();
26390
+ init_ImageOrUrlField();
25954
26391
  isCreate = /* @__PURE__ */ __name((id) => id === "create", "isCreate");
25955
26392
  __name(BrandEditPage, "BrandEditPage");
25956
26393
  }
@@ -26858,16 +27295,38 @@ function ProductVariantsSection({ hasVariants, onHasVariantsChange, variantOptio
26858
27295
  }, "Reserved"), /* @__PURE__ */ React.createElement("option", {
26859
27296
  value: "sold"
26860
27297
  }, "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", {
27298
+ className: "px-3 py-2 align-top min-w-[240px]"
27299
+ }, (() => {
27300
+ const lines = row.imageUrlsText === "" ? [
27301
+ ""
27302
+ ] : row.imageUrlsText.split("\n");
27303
+ return /* @__PURE__ */ React.createElement("div", {
27304
+ className: "space-y-2"
27305
+ }, lines.map((url, ui) => /* @__PURE__ */ React.createElement(ImageOrUrlField, {
27306
+ key: ui,
27307
+ label: ui === 0 ? "Images" : `Image ${ui + 1}`,
27308
+ value: url,
27309
+ onChange: /* @__PURE__ */ __name((v) => {
27310
+ const next = [
27311
+ ...lines
27312
+ ];
27313
+ next[ui] = v;
27314
+ setVariantField(i, "imageUrlsText", next.join("\n"));
27315
+ }, "onChange"),
27316
+ inputClassName: inputCls2
27317
+ })), /* @__PURE__ */ React.createElement("button", {
27318
+ type: "button",
27319
+ onClick: /* @__PURE__ */ __name(() => setVariantField(i, "imageUrlsText", [
27320
+ ...lines,
27321
+ ""
27322
+ ].join("\n")), "onClick"),
27323
+ 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"
27324
+ }, /* @__PURE__ */ React.createElement(Plus, {
27325
+ className: "h-3 w-3"
27326
+ }), " Add image"), /* @__PURE__ */ React.createElement("p", {
27327
+ className: "text-[11px] text-gray-500"
27328
+ }, "First image is used on PDP."));
27329
+ })()), /* @__PURE__ */ React.createElement("td", {
26871
27330
  className: "px-3 py-2 align-top text-right"
26872
27331
  }, /* @__PURE__ */ React.createElement("button", {
26873
27332
  type: "button",
@@ -26882,6 +27341,7 @@ var init_ProductVariantsSection = __esm({
26882
27341
  "src/admin/pages/ProductVariantsSection.tsx"() {
26883
27342
  "use client";
26884
27343
  init_product_variants();
27344
+ init_ImageOrUrlField();
26885
27345
  labelCls2 = "block text-xs font-medium text-gray-600 mb-1";
26886
27346
  inputCls2 = "w-full rounded-md border border-gray-300 px-2 py-1.5 text-sm";
26887
27347
  sectionCls2 = "min-w-0 overflow-hidden border border-gray-200 rounded-lg p-4 bg-gray-50/50";
@@ -27077,13 +27537,16 @@ function ProductEditPage({ productId }) {
27077
27537
  const router = useRouter();
27078
27538
  const searchParams = useSearchParams();
27079
27539
  const { data: session } = useSession();
27540
+ const { eventsEnabled, requireProductApproval } = useContext(AdminConfigContext);
27541
+ const eventsOn = eventsEnabled !== false;
27542
+ const approvalOn = requireProductApproval === true;
27080
27543
  const vendorPortal = isVendorPortalUser(session?.user);
27081
27544
  const categoryIdParam = searchParams.get("categoryId")?.trim() ?? "";
27082
27545
  const collectionIdParam = searchParams.get("collectionId")?.trim() ?? "";
27083
27546
  const eventIdParam = searchParams.get("eventId")?.trim() ?? "";
27084
27547
  const lockCategoryFromStore = isCreate2(productId) && /^\d+$/.test(categoryIdParam);
27085
27548
  const lockCollectionFromQuery = isCreate2(productId) && /^\d+$/.test(collectionIdParam);
27086
- const lockEventFromQuery = isCreate2(productId) && /^\d+$/.test(eventIdParam);
27549
+ const lockEventFromQuery = eventsOn && isCreate2(productId) && /^\d+$/.test(eventIdParam);
27087
27550
  const listReturnUrl = safeAdminListReturnUrl(searchParams.get("from")) ?? (lockCategoryFromStore ? `/admin/products?categoryId=${categoryIdParam}` : "/admin/products");
27088
27551
  const create = isCreate2(productId);
27089
27552
  const dupParam = searchParams.get("duplicateFrom");
@@ -27114,6 +27577,11 @@ function ProductEditPage({ productId }) {
27114
27577
  const [compareAtPrice, setCompareAtPrice] = useState(0);
27115
27578
  const [quantity, setQuantity] = useState(1);
27116
27579
  const [status, setStatus] = useState("draft");
27580
+ const [approvalStatus, setApprovalStatus] = useState("pending");
27581
+ const [rejectionReason, setRejectionReason] = useState("");
27582
+ const [rejectModalOpen, setRejectModalOpen] = useState(false);
27583
+ const [rejectDraft, setRejectDraft] = useState("");
27584
+ const approvalBeforeRejectRef = useRef("pending");
27117
27585
  const [featured, setFeatured] = useState(false);
27118
27586
  const [description, setDescription] = useState("");
27119
27587
  const [images, setImages] = useState([
@@ -27218,7 +27686,7 @@ function ProductEditPage({ productId }) {
27218
27686
  try {
27219
27687
  const [brandRes, catRes, attrRes, taxesRes, eventsRes, formsRes, refundRes] = await Promise.all([
27220
27688
  fetch("/api/brands?limit=500"),
27221
- fetch("/api/product_categories?limit=500&isCatalog=true"),
27689
+ fetch(vendorPortal ? "/api/product_categories?limit=500" : "/api/product_categories?limit=500&isCatalog=true"),
27222
27690
  fetch("/api/attributes?limit=500"),
27223
27691
  fetch("/api/taxes?limit=200&sortField=name&sortOrder=asc"),
27224
27692
  fetch("/api/events?limit=200&sortField=startDate&sortOrder=desc"),
@@ -27331,6 +27799,8 @@ function ProductEditPage({ productId }) {
27331
27799
  setCompareAtPrice(Number.isFinite(rawCompare) ? rawCompare : 0);
27332
27800
  setQuantity(product.quantity ?? 1);
27333
27801
  setStatus(product.status ?? "draft");
27802
+ setApprovalStatus(typeof product.approvalStatus === "string" && product.approvalStatus ? product.approvalStatus : "pending");
27803
+ setRejectionReason(typeof product.rejectionReason === "string" ? product.rejectionReason : "");
27334
27804
  setFeatured(product.featured ?? false);
27335
27805
  setDescription((m && typeof m.description === "string" ? m.description : "") ?? "");
27336
27806
  const rawImages = m?.images;
@@ -27466,6 +27936,16 @@ function ProductEditPage({ productId }) {
27466
27936
  create,
27467
27937
  eventId
27468
27938
  ]);
27939
+ useEffect(() => {
27940
+ if (create && vendorPortal && approvalOn) {
27941
+ setApprovalStatus("pending");
27942
+ setStatus((s) => s === "available" ? "draft" : s);
27943
+ }
27944
+ }, [
27945
+ create,
27946
+ vendorPortal,
27947
+ approvalOn
27948
+ ]);
27469
27949
  useEffect(() => {
27470
27950
  if (!create || !name.trim()) return;
27471
27951
  setProductSlug(slugifyProductName(name));
@@ -27500,6 +27980,33 @@ function ProductEditPage({ productId }) {
27500
27980
  }
27501
27981
  setter(num);
27502
27982
  }, "handleNumberChange");
27983
+ const openRejectModal = /* @__PURE__ */ __name((fromStatus) => {
27984
+ approvalBeforeRejectRef.current = fromStatus === "rejected" ? "pending" : fromStatus;
27985
+ setRejectDraft(rejectionReason);
27986
+ setApprovalStatus("rejected");
27987
+ setRejectModalOpen(true);
27988
+ }, "openRejectModal");
27989
+ const confirmRejectReason = /* @__PURE__ */ __name(() => {
27990
+ const reason = rejectDraft.trim();
27991
+ if (!reason) return;
27992
+ setRejectionReason(reason);
27993
+ setRejectModalOpen(false);
27994
+ }, "confirmRejectReason");
27995
+ const cancelRejectModal = /* @__PURE__ */ __name(() => {
27996
+ if (!rejectionReason.trim()) {
27997
+ setApprovalStatus(approvalBeforeRejectRef.current || "pending");
27998
+ }
27999
+ setRejectDraft(rejectionReason);
28000
+ setRejectModalOpen(false);
28001
+ }, "cancelRejectModal");
28002
+ const handleApprovalSelect = /* @__PURE__ */ __name((value) => {
28003
+ if (value === "rejected") {
28004
+ openRejectModal(approvalStatus);
28005
+ return;
28006
+ }
28007
+ setApprovalStatus(value);
28008
+ if (value !== "rejected") setRejectionReason("");
28009
+ }, "handleApprovalSelect");
27503
28010
  const handleSave = /* @__PURE__ */ __name(async () => {
27504
28011
  setErrors([]);
27505
28012
  if (!name.trim()) {
@@ -27508,6 +28015,14 @@ function ProductEditPage({ productId }) {
27508
28015
  ]);
27509
28016
  return;
27510
28017
  }
28018
+ if (approvalOn && !vendorPortal && approvalStatus === "rejected" && !rejectionReason.trim()) {
28019
+ setRejectDraft("");
28020
+ setRejectModalOpen(true);
28021
+ setErrors([
28022
+ "Rejection reason is required"
28023
+ ]);
28024
+ return;
28025
+ }
27511
28026
  if (!defaultPriceStr.trim()) {
27512
28027
  setErrors([
27513
28028
  `${pricingConfig.defaultCurrency} price is required`
@@ -27570,11 +28085,21 @@ function ProductEditPage({ productId }) {
27570
28085
  currencyPrices: null,
27571
28086
  compareAtPrice: compareAtPriceValue,
27572
28087
  quantity: resolvedQuantity,
27573
- status,
28088
+ status: create && vendorPortal && approvalOn && status === "available" ? "draft" : status,
27574
28089
  featured,
27575
28090
  contactFormId,
27576
28091
  metadata
27577
28092
  };
28093
+ if (approvalOn) {
28094
+ if (vendorPortal && create) {
28095
+ productPayload.approvalStatus = "pending";
28096
+ } else if (!vendorPortal) {
28097
+ productPayload.approvalStatus = approvalStatus;
28098
+ if (approvalStatus === "rejected") {
28099
+ productPayload.rejectionReason = rejectionReason.trim();
28100
+ }
28101
+ }
28102
+ }
27578
28103
  const res = await fetch(create ? "/api/products" : `/api/products/${productId}`, {
27579
28104
  method: create ? "POST" : "PUT",
27580
28105
  headers: {
@@ -27596,6 +28121,15 @@ function ProductEditPage({ productId }) {
27596
28121
  if (typeof savedProduct.slug === "string") {
27597
28122
  setProductSlug(savedProduct.slug);
27598
28123
  }
28124
+ if (typeof savedProduct.status === "string") {
28125
+ setStatus(savedProduct.status);
28126
+ }
28127
+ if (typeof savedProduct.approvalStatus === "string") {
28128
+ setApprovalStatus(savedProduct.approvalStatus);
28129
+ }
28130
+ if (approvalStatus === "approved") {
28131
+ setRejectionReason("");
28132
+ }
27599
28133
  const savedId = create ? savedProduct.id : productId;
27600
28134
  const savedSeoId = await saveSeo(seo, productSlugValue, seoId);
27601
28135
  const linkedSeoId = savedSeoId ?? savedProduct.seoId ?? null;
@@ -27796,7 +28330,7 @@ function ProductEditPage({ productId }) {
27796
28330
  }
27797
28331
  setVariantRows([]);
27798
28332
  }
27799
- if (create && eventId != null) {
28333
+ if (create && eventsOn && eventId != null) {
27800
28334
  const attachRes = await fetch("/api/event_products", {
27801
28335
  method: "POST",
27802
28336
  headers: {
@@ -27901,6 +28435,27 @@ function ProductEditPage({ productId }) {
27901
28435
  title: pageTitle,
27902
28436
  subtitle: pageSubtitle,
27903
28437
  closeHref: listReturnUrl,
28438
+ headerExtra: approvalOn && !vendorPortal ? /* @__PURE__ */ React.createElement("div", {
28439
+ className: "flex items-center gap-2"
28440
+ }, /* @__PURE__ */ React.createElement("select", {
28441
+ value: approvalStatus,
28442
+ onChange: /* @__PURE__ */ __name((e) => handleApprovalSelect(e.target.value), "onChange"),
28443
+ className: "h-8 rounded-md border border-gray-600 bg-gray-900 text-white text-xs px-2 max-w-[10rem]",
28444
+ "aria-label": "Approval status"
28445
+ }, /* @__PURE__ */ React.createElement("option", {
28446
+ value: "pending"
28447
+ }, "Pending"), /* @__PURE__ */ React.createElement("option", {
28448
+ value: "approved"
28449
+ }, "Approve"), /* @__PURE__ */ React.createElement("option", {
28450
+ value: "rejected"
28451
+ }, "Reject")), approvalStatus === "rejected" ? /* @__PURE__ */ React.createElement("button", {
28452
+ type: "button",
28453
+ onClick: /* @__PURE__ */ __name(() => openRejectModal("rejected"), "onClick"),
28454
+ className: "text-xs text-amber-300 hover:text-amber-200 underline underline-offset-2 max-w-[9rem] truncate",
28455
+ title: rejectionReason || "Add rejection reason"
28456
+ }, rejectionReason.trim() ? "Edit reason" : "Add reason") : null) : approvalOn && vendorPortal ? /* @__PURE__ */ React.createElement("span", {
28457
+ className: "text-xs text-gray-300 capitalize hidden sm:inline"
28458
+ }, approvalStatus.replace(/_/g, " ")) : null,
27904
28459
  menuItems: [
27905
28460
  {
27906
28461
  label: saving ? "Saving..." : "Save",
@@ -27913,7 +28468,31 @@ function ProductEditPage({ productId }) {
27913
28468
  onClick: /* @__PURE__ */ __name(() => setFeatured(!featured), "onClick")
27914
28469
  }
27915
28470
  ]
27916
- }), errors.length > 0 && /* @__PURE__ */ React.createElement("div", {
28471
+ }), /* @__PURE__ */ React.createElement(Dialog, {
28472
+ open: rejectModalOpen,
28473
+ onOpenChange: /* @__PURE__ */ __name((open) => {
28474
+ if (!open) cancelRejectModal();
28475
+ }, "onOpenChange")
28476
+ }, /* @__PURE__ */ React.createElement(DialogContent, {
28477
+ className: "max-w-md"
28478
+ }, /* @__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", {
28479
+ value: rejectDraft,
28480
+ onChange: /* @__PURE__ */ __name((e) => setRejectDraft(e.target.value), "onChange"),
28481
+ className: "w-full min-h-[100px] rounded-md border border-gray-300 px-3 py-2 text-sm",
28482
+ placeholder: "Explain what needs to change\u2026",
28483
+ autoFocus: true
28484
+ }), /* @__PURE__ */ React.createElement(DialogFooter, {
28485
+ className: "gap-2 sm:gap-0"
28486
+ }, /* @__PURE__ */ React.createElement(Button, {
28487
+ type: "button",
28488
+ variant: "outline",
28489
+ onClick: cancelRejectModal
28490
+ }, "Cancel"), /* @__PURE__ */ React.createElement(Button, {
28491
+ type: "button",
28492
+ variant: "destructive",
28493
+ disabled: !rejectDraft.trim(),
28494
+ onClick: confirmRejectReason
28495
+ }, "Confirm reject")))), errors.length > 0 && /* @__PURE__ */ React.createElement("div", {
27917
28496
  className: "bg-red-50 border-l-4 border-red-400 p-4 mx-6 mt-4"
27918
28497
  }, /* @__PURE__ */ React.createElement("div", {
27919
28498
  className: "flex"
@@ -28036,7 +28615,7 @@ function ProductEditPage({ productId }) {
28036
28615
  className: "mt-1 text-xs text-gray-500"
28037
28616
  }, "Only collections in the selected category are listed.") : /* @__PURE__ */ React.createElement("p", {
28038
28617
  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", {
28618
+ }, "Select a category to load collections.")), create && eventsOn ? /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("label", {
28040
28619
  className: labelCls3
28041
28620
  }, "Event"), /* @__PURE__ */ React.createElement("select", {
28042
28621
  value: eventId ?? "",
@@ -28132,7 +28711,8 @@ function ProductEditPage({ productId }) {
28132
28711
  }, "Status"), /* @__PURE__ */ React.createElement("select", {
28133
28712
  value: status,
28134
28713
  onChange: /* @__PURE__ */ __name((e) => setStatus(e.target.value), "onChange"),
28135
- className: inputCls3
28714
+ className: inputCls3,
28715
+ disabled: approvalOn && vendorPortal && (approvalStatus === "pending" || approvalStatus === "rejected")
28136
28716
  }, /* @__PURE__ */ React.createElement("option", {
28137
28717
  value: "draft"
28138
28718
  }, "Draft"), /* @__PURE__ */ React.createElement("option", {
@@ -28141,7 +28721,11 @@ function ProductEditPage({ productId }) {
28141
28721
  value: "reserved"
28142
28722
  }, "Reserved"), /* @__PURE__ */ React.createElement("option", {
28143
28723
  value: "sold"
28144
- }, "Sold")))))), /* @__PURE__ */ React.createElement(ProductVariantsSection, {
28724
+ }, "Sold")), approvalOn && vendorPortal && approvalStatus === "pending" ? /* @__PURE__ */ React.createElement("p", {
28725
+ className: "mt-1 text-xs text-gray-500"
28726
+ }, "Waiting for admin approval. The product goes live when approved.") : null, approvalOn && vendorPortal && approvalStatus === "rejected" ? /* @__PURE__ */ React.createElement("p", {
28727
+ className: "mt-1 text-xs text-red-600"
28728
+ }, "Rejected", rejectionReason ? `: ${rejectionReason}` : "", ".") : null)))), /* @__PURE__ */ React.createElement(ProductVariantsSection, {
28145
28729
  hasVariants,
28146
28730
  onHasVariantsChange: setHasVariants,
28147
28731
  variantOptionRows,
@@ -28268,14 +28852,11 @@ function ProductEditPage({ productId }) {
28268
28852
  className: "flex flex-wrap items-start gap-2 p-2 bg-white rounded border border-gray-200"
28269
28853
  }, /* @__PURE__ */ React.createElement("div", {
28270
28854
  className: "flex-1 min-w-[200px]"
28271
- }, /* @__PURE__ */ React.createElement("label", {
28272
- className: labelCls3
28273
- }, "Image URL"), /* @__PURE__ */ React.createElement("input", {
28274
- type: "url",
28855
+ }, /* @__PURE__ */ React.createElement(ImageOrUrlField, {
28856
+ label: "Image",
28275
28857
  value: row.url,
28276
- onChange: /* @__PURE__ */ __name((e) => setImage(i, "url", e.target.value), "onChange"),
28277
- className: inputCls3,
28278
- placeholder: "https://..."
28858
+ onChange: /* @__PURE__ */ __name((v) => setImage(i, "url", v), "onChange"),
28859
+ inputClassName: inputCls3
28279
28860
  })), /* @__PURE__ */ React.createElement("div", {
28280
28861
  className: "flex-1 min-w-[120px]"
28281
28862
  }, /* @__PURE__ */ React.createElement("label", {
@@ -28354,6 +28935,8 @@ var init_ProductEditPage = __esm({
28354
28935
  "use client";
28355
28936
  init_vendor_scope();
28356
28937
  init_admin_list_return_url();
28938
+ init_dialog();
28939
+ init_button();
28357
28940
  init_SeoSection();
28358
28941
  init_DetailPageLayout();
28359
28942
  init_DetailPageHeader();
@@ -28364,6 +28947,8 @@ var init_ProductEditPage = __esm({
28364
28947
  init_inventory_validation();
28365
28948
  init_category_item_label();
28366
28949
  init_use_category_collections();
28950
+ init_ImageOrUrlField();
28951
+ init_admin_config_context();
28367
28952
  init_ProductVariantsSection();
28368
28953
  init_product_variants();
28369
28954
  __name(parseCategoryIdFromReturnUrl, "parseCategoryIdFromReturnUrl");
@@ -28633,12 +29218,6 @@ function CollectionEditPage({ collectionId }) {
28633
29218
  ]);
28634
29219
  return;
28635
29220
  }
28636
- if (create && !categoryId) {
28637
- setErrors([
28638
- "Category is required"
28639
- ]);
28640
- return;
28641
- }
28642
29221
  setSaving(true);
28643
29222
  try {
28644
29223
  const savedSeoId = await saveSeo(seo, slug.trim(), seoId);
@@ -28859,14 +29438,11 @@ function CollectionEditPage({ collectionId }) {
28859
29438
  onChange: /* @__PURE__ */ __name((e) => setDescription(e.target.value), "onChange"),
28860
29439
  className: `${inputCls4} min-h-[80px]`,
28861
29440
  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",
29441
+ })), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(ImageOrUrlField, {
29442
+ label: "Cover image",
28866
29443
  value: image,
28867
- onChange: /* @__PURE__ */ __name((e) => setImage(e.target.value), "onChange"),
28868
- className: inputCls4,
28869
- placeholder: "https://..."
29444
+ onChange: setImage,
29445
+ inputClassName: inputCls4
28870
29446
  })), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("label", {
28871
29447
  className: labelCls4
28872
29448
  }, "Sort order"), /* @__PURE__ */ React.createElement("input", {
@@ -28893,17 +29469,24 @@ function CollectionEditPage({ collectionId }) {
28893
29469
  className: "text-xs font-medium text-gray-700 mb-2"
28894
29470
  }, "Hero carousel"), heroSlides.map((slide, i) => /* @__PURE__ */ React.createElement("div", {
28895
29471
  key: i,
28896
- className: "flex flex-wrap gap-2 mb-3 p-2 bg-white rounded border"
28897
- }, /* @__PURE__ */ React.createElement("input", {
29472
+ className: "flex flex-wrap gap-2 mb-3 p-2 bg-white rounded border items-start"
29473
+ }, slide.type === "image" ? /* @__PURE__ */ React.createElement("div", {
29474
+ className: "flex-1 min-w-[200px]"
29475
+ }, /* @__PURE__ */ React.createElement(ImageOrUrlField, {
29476
+ label: "Slide image",
29477
+ value: slide.url,
29478
+ onChange: /* @__PURE__ */ __name((v) => updateHeroSlide(i, "url", v), "onChange"),
29479
+ inputClassName: inputCls4
29480
+ })) : /* @__PURE__ */ React.createElement("input", {
28898
29481
  type: "url",
28899
29482
  value: slide.url,
28900
29483
  onChange: /* @__PURE__ */ __name((e) => updateHeroSlide(i, "url", e.target.value), "onChange"),
28901
- placeholder: "Media URL",
29484
+ placeholder: "Video URL",
28902
29485
  className: `${inputCls4} flex-1 min-w-[200px]`
28903
29486
  }), /* @__PURE__ */ React.createElement("select", {
28904
29487
  value: slide.type,
28905
29488
  onChange: /* @__PURE__ */ __name((e) => updateHeroSlide(i, "type", e.target.value), "onChange"),
28906
- className: `${inputCls4} w-24`
29489
+ className: `${inputCls4} w-24 mt-6`
28907
29490
  }, /* @__PURE__ */ React.createElement("option", {
28908
29491
  value: "image"
28909
29492
  }, "Image"), /* @__PURE__ */ React.createElement("option", {
@@ -28913,11 +29496,11 @@ function CollectionEditPage({ collectionId }) {
28913
29496
  value: slide.caption,
28914
29497
  onChange: /* @__PURE__ */ __name((e) => updateHeroSlide(i, "caption", e.target.value), "onChange"),
28915
29498
  placeholder: "Caption",
28916
- className: `${inputCls4} flex-1 min-w-[120px]`
29499
+ className: `${inputCls4} flex-1 min-w-[120px] mt-6`
28917
29500
  }), /* @__PURE__ */ React.createElement("button", {
28918
29501
  type: "button",
28919
29502
  onClick: /* @__PURE__ */ __name(() => removeHeroSlide(i), "onClick"),
28920
- className: "p-2 text-gray-400 hover:text-red-600 rounded shrink-0"
29503
+ className: "p-2 text-gray-400 hover:text-red-600 rounded shrink-0 mt-6"
28921
29504
  }, /* @__PURE__ */ React.createElement(Trash2, {
28922
29505
  className: "h-4 w-4"
28923
29506
  })))), /* @__PURE__ */ React.createElement("button", {
@@ -29044,6 +29627,7 @@ var init_CollectionEditPage = __esm({
29044
29627
  init_use_catalog_categories();
29045
29628
  init_admin_config_context();
29046
29629
  init_category_related_product_labels();
29630
+ init_ImageOrUrlField();
29047
29631
  isCreate3 = /* @__PURE__ */ __name((id) => id === "create", "isCreate");
29048
29632
  emptySlide = /* @__PURE__ */ __name(() => ({
29049
29633
  url: "",
@@ -29061,116 +29645,6 @@ var init_CollectionEditPage = __esm({
29061
29645
  __name(CollectionEditPage, "CollectionEditPage");
29062
29646
  }
29063
29647
  });
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
29648
  function AttachProductModal({ open, onOpenChange, categoryId, categoryName, excludeProductIds = [], onAttach }) {
29175
29649
  const [attachingId, setAttachingId] = useState(null);
29176
29650
  const [error, setError] = useState(null);
@@ -30115,6 +30589,10 @@ function combineCityCountry(city, country) {
30115
30589
  function EventEditPage({ eventId }) {
30116
30590
  const router = useRouter();
30117
30591
  const searchParams = useSearchParams();
30592
+ const { data: session } = useSession();
30593
+ const { requireEventApproval } = useContext(AdminConfigContext);
30594
+ const approvalOn = requireEventApproval === true;
30595
+ const vendorPortal = isVendorPortalUser(session?.user);
30118
30596
  const listReturnUrl = safeAdminListReturnUrl(searchParams.get("from")) ?? "/admin/events";
30119
30597
  const create = isCreate4(eventId);
30120
30598
  const dupParam = searchParams.get("duplicateFrom");
@@ -30127,6 +30605,11 @@ function EventEditPage({ eventId }) {
30127
30605
  const [slug, setSlug] = useState("");
30128
30606
  const [description, setDescription] = useState("");
30129
30607
  const [isActive, setIsActive] = useState(true);
30608
+ const [approvalStatus, setApprovalStatus] = useState("pending");
30609
+ const [rejectionReason, setRejectionReason] = useState("");
30610
+ const [rejectModalOpen, setRejectModalOpen] = useState(false);
30611
+ const [rejectDraft, setRejectDraft] = useState("");
30612
+ const approvalBeforeRejectRef = useRef("pending");
30130
30613
  const [comingSoon, setComingSoon] = useState(false);
30131
30614
  const [bannerImageUrl, setBannerImageUrl] = useState("");
30132
30615
  const [logoUrl, setLogoUrl] = useState("");
@@ -30212,6 +30695,16 @@ function EventEditPage({ eventId }) {
30212
30695
  cancelled = true;
30213
30696
  };
30214
30697
  }, []);
30698
+ useEffect(() => {
30699
+ if (create && vendorPortal && approvalOn) {
30700
+ setApprovalStatus("pending");
30701
+ setIsActive(false);
30702
+ }
30703
+ }, [
30704
+ create,
30705
+ vendorPortal,
30706
+ approvalOn
30707
+ ]);
30215
30708
  useEffect(() => {
30216
30709
  let cancelled = false;
30217
30710
  (async () => {
@@ -30233,6 +30726,8 @@ function EventEditPage({ eventId }) {
30233
30726
  setSlug(data.slug ?? "");
30234
30727
  setDescription(data.description ?? "");
30235
30728
  setIsActive(data.isActive ?? true);
30729
+ setApprovalStatus(typeof data.approvalStatus === "string" && data.approvalStatus ? data.approvalStatus : "pending");
30730
+ setRejectionReason(typeof data.rejectionReason === "string" ? data.rejectionReason : "");
30236
30731
  setComingSoon(data.comingSoon ?? false);
30237
30732
  setBannerImageUrl(data.bannerImageUrl ?? "");
30238
30733
  setLogoUrl(data.logoUrl ?? "");
@@ -30344,11 +30839,11 @@ function EventEditPage({ eventId }) {
30344
30839
  setErrors(nextErrors);
30345
30840
  return null;
30346
30841
  }
30347
- return {
30842
+ const payload = {
30348
30843
  name: name.trim(),
30349
30844
  slug: slug.trim(),
30350
30845
  description: description.trim() || null,
30351
- isActive,
30846
+ isActive: create && vendorPortal && approvalOn ? false : isActive,
30352
30847
  comingSoon,
30353
30848
  bannerImageUrl: bannerImageUrl.trim() || null,
30354
30849
  logoUrl: logoUrl.trim() || null,
@@ -30386,9 +30881,55 @@ function EventEditPage({ eventId }) {
30386
30881
  sortOrder,
30387
30882
  contactFormId
30388
30883
  };
30884
+ if (approvalOn) {
30885
+ if (vendorPortal && create) {
30886
+ payload.approvalStatus = "pending";
30887
+ } else if (!vendorPortal) {
30888
+ payload.approvalStatus = approvalStatus;
30889
+ if (approvalStatus === "rejected") {
30890
+ payload.rejectionReason = rejectionReason.trim();
30891
+ }
30892
+ }
30893
+ }
30894
+ return payload;
30389
30895
  }, "buildPayload");
30896
+ const openRejectModal = /* @__PURE__ */ __name((fromStatus) => {
30897
+ approvalBeforeRejectRef.current = fromStatus === "rejected" ? "pending" : fromStatus;
30898
+ setRejectDraft(rejectionReason);
30899
+ setApprovalStatus("rejected");
30900
+ setRejectModalOpen(true);
30901
+ }, "openRejectModal");
30902
+ const confirmRejectReason = /* @__PURE__ */ __name(() => {
30903
+ const reason = rejectDraft.trim();
30904
+ if (!reason) return;
30905
+ setRejectionReason(reason);
30906
+ setRejectModalOpen(false);
30907
+ }, "confirmRejectReason");
30908
+ const cancelRejectModal = /* @__PURE__ */ __name(() => {
30909
+ if (!rejectionReason.trim()) {
30910
+ setApprovalStatus(approvalBeforeRejectRef.current || "pending");
30911
+ }
30912
+ setRejectDraft(rejectionReason);
30913
+ setRejectModalOpen(false);
30914
+ }, "cancelRejectModal");
30915
+ const handleApprovalSelect = /* @__PURE__ */ __name((value) => {
30916
+ if (value === "rejected") {
30917
+ openRejectModal(approvalStatus);
30918
+ return;
30919
+ }
30920
+ setApprovalStatus(value);
30921
+ if (value !== "rejected") setRejectionReason("");
30922
+ }, "handleApprovalSelect");
30390
30923
  const handleSave = /* @__PURE__ */ __name(async () => {
30391
30924
  setErrors([]);
30925
+ if (approvalOn && !vendorPortal && approvalStatus === "rejected" && !rejectionReason.trim()) {
30926
+ setRejectDraft("");
30927
+ setRejectModalOpen(true);
30928
+ setErrors([
30929
+ "Rejection reason is required"
30930
+ ]);
30931
+ return;
30932
+ }
30392
30933
  const payload = buildPayload();
30393
30934
  if (!payload) return;
30394
30935
  setSaving(true);
@@ -30408,6 +30949,9 @@ function EventEditPage({ eventId }) {
30408
30949
  return;
30409
30950
  }
30410
30951
  const saved = await res.json();
30952
+ if (typeof saved.isActive === "boolean") setIsActive(saved.isActive);
30953
+ if (typeof saved.approvalStatus === "string") setApprovalStatus(saved.approvalStatus);
30954
+ if (approvalStatus === "approved") setRejectionReason("");
30411
30955
  const savedId = create ? saved.id : Number(eventId);
30412
30956
  if (savedId != null && !Number.isNaN(savedId)) {
30413
30957
  router.push(`/admin/events/${savedId}/edit?from=${encodeURIComponent(listReturnUrl)}`);
@@ -30470,19 +31014,66 @@ function EventEditPage({ eventId }) {
30470
31014
  title: create ? "Add event" : "Edit event",
30471
31015
  subtitle: create ? "Create a new event" : "Update event details and tickets",
30472
31016
  closeHref: listReturnUrl,
31017
+ headerExtra: approvalOn && !vendorPortal ? /* @__PURE__ */ React.createElement("div", {
31018
+ className: "flex items-center gap-2"
31019
+ }, /* @__PURE__ */ React.createElement("select", {
31020
+ value: approvalStatus,
31021
+ onChange: /* @__PURE__ */ __name((e) => handleApprovalSelect(e.target.value), "onChange"),
31022
+ className: "h-8 rounded-md border border-gray-600 bg-gray-900 text-white text-xs px-2 max-w-[10rem]",
31023
+ "aria-label": "Approval status"
31024
+ }, /* @__PURE__ */ React.createElement("option", {
31025
+ value: "pending"
31026
+ }, "Pending"), /* @__PURE__ */ React.createElement("option", {
31027
+ value: "approved"
31028
+ }, "Approve"), /* @__PURE__ */ React.createElement("option", {
31029
+ value: "rejected"
31030
+ }, "Reject")), approvalStatus === "rejected" ? /* @__PURE__ */ React.createElement("button", {
31031
+ type: "button",
31032
+ onClick: /* @__PURE__ */ __name(() => openRejectModal("rejected"), "onClick"),
31033
+ className: "text-xs text-amber-300 hover:text-amber-200 underline underline-offset-2 max-w-[9rem] truncate",
31034
+ title: rejectionReason || "Add rejection reason"
31035
+ }, rejectionReason.trim() ? "Edit reason" : "Add reason") : null) : approvalOn && vendorPortal ? /* @__PURE__ */ React.createElement("span", {
31036
+ className: "text-xs text-gray-300 capitalize hidden sm:inline"
31037
+ }, approvalStatus.replace(/_/g, " "), approvalStatus === "rejected" && rejectionReason ? ` \u2014 ${rejectionReason}` : "") : null,
30473
31038
  menuItems: [
30474
31039
  {
30475
31040
  label: saving ? "Saving..." : "Save",
30476
31041
  icon: Save,
30477
31042
  onClick: handleSave
30478
31043
  },
30479
- {
30480
- label: isActive ? "Deactivate" : "Activate",
30481
- icon: Power,
30482
- onClick: /* @__PURE__ */ __name(() => setIsActive(!isActive), "onClick")
30483
- }
31044
+ ...approvalOn && vendorPortal && (approvalStatus === "pending" || approvalStatus === "rejected") ? [] : [
31045
+ {
31046
+ label: isActive ? "Deactivate" : "Activate",
31047
+ icon: Power,
31048
+ onClick: /* @__PURE__ */ __name(() => setIsActive(!isActive), "onClick")
31049
+ }
31050
+ ]
30484
31051
  ]
30485
- }), !create && logoUrl.trim() ? /* @__PURE__ */ React.createElement("div", {
31052
+ }), /* @__PURE__ */ React.createElement(Dialog, {
31053
+ open: rejectModalOpen,
31054
+ onOpenChange: /* @__PURE__ */ __name((open) => {
31055
+ if (!open) cancelRejectModal();
31056
+ }, "onOpenChange")
31057
+ }, /* @__PURE__ */ React.createElement(DialogContent, {
31058
+ className: "max-w-md"
31059
+ }, /* @__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", {
31060
+ value: rejectDraft,
31061
+ onChange: /* @__PURE__ */ __name((e) => setRejectDraft(e.target.value), "onChange"),
31062
+ className: "w-full min-h-[100px] rounded-md border border-gray-300 px-3 py-2 text-sm",
31063
+ placeholder: "Explain what needs to change\u2026",
31064
+ autoFocus: true
31065
+ }), /* @__PURE__ */ React.createElement(DialogFooter, {
31066
+ className: "gap-2 sm:gap-0"
31067
+ }, /* @__PURE__ */ React.createElement(Button, {
31068
+ type: "button",
31069
+ variant: "outline",
31070
+ onClick: cancelRejectModal
31071
+ }, "Cancel"), /* @__PURE__ */ React.createElement(Button, {
31072
+ type: "button",
31073
+ variant: "destructive",
31074
+ disabled: !rejectDraft.trim(),
31075
+ onClick: confirmRejectReason
31076
+ }, "Confirm reject")))), !create && logoUrl.trim() ? /* @__PURE__ */ React.createElement("div", {
30486
31077
  className: "flex items-center gap-3 px-4 sm:px-6 py-3 border-b border-gray-100 bg-gray-50/80"
30487
31078
  }, /* @__PURE__ */ React.createElement("img", {
30488
31079
  src: logoUrl.trim(),
@@ -30841,11 +31432,15 @@ var init_EventEditPage = __esm({
30841
31432
  "src/admin/pages/EventEditPage.tsx"() {
30842
31433
  "use client";
30843
31434
  init_admin_list_return_url();
31435
+ init_vendor_scope();
30844
31436
  init_DetailPageLayout();
30845
31437
  init_DetailPageHeader();
30846
31438
  init_ImageOrUrlField();
30847
31439
  init_JoditRichText();
30848
31440
  init_EventProductsSection();
31441
+ init_admin_config_context();
31442
+ init_dialog();
31443
+ init_button();
30849
31444
  init_EventManagementFields();
30850
31445
  init_event_named_lists();
30851
31446
  init_social_media_links();
@@ -30921,6 +31516,8 @@ async function validateComboProductInventory(productIds) {
30921
31516
  function ComboEditPage({ comboId }) {
30922
31517
  const router = useRouter();
30923
31518
  const searchParams = useSearchParams();
31519
+ const { eventsEnabled } = useContext(AdminConfigContext);
31520
+ const eventsOn = eventsEnabled !== false;
30924
31521
  const listReturnUrl = safeAdminListReturnUrl(searchParams.get("from")) ?? "/admin/combos";
30925
31522
  const create = isCreate5(comboId);
30926
31523
  const duplicateFrom = searchParams.get("duplicateFrom")?.trim();
@@ -30942,8 +31539,9 @@ function ComboEditPage({ comboId }) {
30942
31539
  const [productOptions, setProductOptions] = useState([]);
30943
31540
  const [fixedItems, setFixedItems] = useState([]);
30944
31541
  const [addonItems, setAddonItems] = useState([]);
31542
+ const canPickProducts = !eventsOn || Boolean(eventId);
30945
31543
  useEffect(() => {
30946
- if (!eventId) {
31544
+ if (!eventsOn || !eventId) {
30947
31545
  setDefaultCurrency("INR");
30948
31546
  return;
30949
31547
  }
@@ -30955,9 +31553,14 @@ function ComboEditPage({ comboId }) {
30955
31553
  cancelled = true;
30956
31554
  };
30957
31555
  }, [
30958
- eventId
31556
+ eventId,
31557
+ eventsOn
30959
31558
  ]);
30960
31559
  useEffect(() => {
31560
+ if (!eventsOn) {
31561
+ setEventOptions([]);
31562
+ return;
31563
+ }
30961
31564
  let cancelled = false;
30962
31565
  (async () => {
30963
31566
  try {
@@ -30977,45 +31580,63 @@ function ComboEditPage({ comboId }) {
30977
31580
  return () => {
30978
31581
  cancelled = true;
30979
31582
  };
30980
- }, []);
31583
+ }, [
31584
+ eventsOn
31585
+ ]);
30981
31586
  useEffect(() => {
30982
- if (!eventId) {
30983
- setProductOptions([]);
30984
- return;
30985
- }
30986
31587
  let cancelled = false;
30987
31588
  (async () => {
30988
31589
  try {
30989
- const res2 = await fetch(`/api/event_products?eventId=${eventId}&limit=500`);
30990
- if (res2.ok) {
31590
+ if (eventsOn) {
31591
+ if (!eventId) {
31592
+ setProductOptions([]);
31593
+ return;
31594
+ }
31595
+ const res2 = await fetch(`/api/event_products?eventId=${eventId}&limit=500`);
31596
+ if (!res2.ok) return;
30991
31597
  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
- }
31598
+ if (cancelled || !Array.isArray(data.data)) return;
31599
+ const productIds = data.data.map((ep) => ep.productId).filter(Boolean);
31600
+ if (productIds.length === 0) {
31601
+ setProductOptions([]);
31602
+ return;
31017
31603
  }
31604
+ const prodRes2 = await fetch(`/api/products?ids=${productIds.join(",")}&limit=500`);
31605
+ if (!prodRes2.ok) return;
31606
+ const prodData2 = await prodRes2.json();
31607
+ if (cancelled || !Array.isArray(prodData2.data)) return;
31608
+ const fetched2 = prodData2.data.map((p) => ({
31609
+ value: String(p.id),
31610
+ label: p.name ?? p.title ?? `Product #${p.id}`
31611
+ }));
31612
+ setProductOptions((prev) => {
31613
+ const merged = [
31614
+ ...fetched2
31615
+ ];
31616
+ for (const p of prev) {
31617
+ if (!merged.some((m) => m.value === p.value)) merged.push(p);
31618
+ }
31619
+ return merged;
31620
+ });
31621
+ return;
31018
31622
  }
31623
+ const prodRes = await fetch("/api/products?limit=500&sortField=name&sortOrder=asc");
31624
+ if (!prodRes.ok) return;
31625
+ const prodData = await prodRes.json();
31626
+ if (cancelled || !Array.isArray(prodData.data)) return;
31627
+ const fetched = prodData.data.map((p) => ({
31628
+ value: String(p.id),
31629
+ label: p.name ?? p.title ?? `Product #${p.id}`
31630
+ }));
31631
+ setProductOptions((prev) => {
31632
+ const merged = [
31633
+ ...fetched
31634
+ ];
31635
+ for (const p of prev) {
31636
+ if (!merged.some((m) => m.value === p.value)) merged.push(p);
31637
+ }
31638
+ return merged;
31639
+ });
31019
31640
  } catch {
31020
31641
  }
31021
31642
  })();
@@ -31023,7 +31644,8 @@ function ComboEditPage({ comboId }) {
31023
31644
  cancelled = true;
31024
31645
  };
31025
31646
  }, [
31026
- eventId
31647
+ eventId,
31648
+ eventsOn
31027
31649
  ]);
31028
31650
  useEffect(() => {
31029
31651
  let cancelled = false;
@@ -31134,7 +31756,7 @@ function ComboEditPage({ comboId }) {
31134
31756
  ]);
31135
31757
  return;
31136
31758
  }
31137
- if (!trimmedEventId || !/^\d+$/.test(trimmedEventId)) {
31759
+ if (eventsOn && (!trimmedEventId || !/^\d+$/.test(trimmedEventId))) {
31138
31760
  setErrors([
31139
31761
  "Event is required"
31140
31762
  ]);
@@ -31193,10 +31815,11 @@ function ComboEditPage({ comboId }) {
31193
31815
  }
31194
31816
  setSaving(true);
31195
31817
  try {
31818
+ const resolvedEventId = trimmedEventId && /^\d+$/.test(trimmedEventId) ? Number(trimmedEventId) : null;
31196
31819
  const payload = {
31197
31820
  name: trimmedName,
31198
31821
  desc: desc || null,
31199
- eventId: Number(trimmedEventId),
31822
+ eventId: resolvedEventId,
31200
31823
  price,
31201
31824
  currencyPrices: null,
31202
31825
  minSelectableItems,
@@ -31290,7 +31913,7 @@ function ComboEditPage({ comboId }) {
31290
31913
  value: desc,
31291
31914
  onChange: /* @__PURE__ */ __name((e) => setDesc(e.target.value), "onChange"),
31292
31915
  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", {
31916
+ })), eventsOn ? /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("label", {
31294
31917
  className: "block text-xs font-medium text-gray-600 mb-1"
31295
31918
  }, "Event *"), /* @__PURE__ */ React.createElement("select", {
31296
31919
  value: eventId,
@@ -31305,7 +31928,7 @@ function ComboEditPage({ comboId }) {
31305
31928
  }, "Select event"), eventOptions.map((o) => /* @__PURE__ */ React.createElement("option", {
31306
31929
  key: o.value,
31307
31930
  value: o.value
31308
- }, o.label)))))), eventId && /* @__PURE__ */ React.createElement("section", null, /* @__PURE__ */ React.createElement("h2", {
31931
+ }, o.label)))) : null)), canPickProducts && /* @__PURE__ */ React.createElement("section", null, /* @__PURE__ */ React.createElement("h2", {
31309
31932
  className: "text-xs font-semibold text-gray-400 uppercase tracking-wider mb-2"
31310
31933
  }, "Combo items"), /* @__PURE__ */ React.createElement("div", {
31311
31934
  className: "min-w-0 overflow-hidden border border-gray-200 rounded-lg p-4 bg-gray-50/50 space-y-5"
@@ -31373,11 +31996,11 @@ function ComboEditPage({ comboId }) {
31373
31996
  step: "0.01",
31374
31997
  value: priceStr,
31375
31998
  onChange: /* @__PURE__ */ __name((e) => setPriceStr(e.target.value), "onChange"),
31376
- disabled: !eventId,
31999
+ disabled: !canPickProducts,
31377
32000
  className: "w-full rounded-md border border-gray-300 px-3 py-2 text-sm disabled:bg-gray-100"
31378
32001
  }), /* @__PURE__ */ React.createElement("p", {
31379
32002
  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", {
32003
+ }, 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
32004
  className: "grid grid-cols-2 gap-4"
31382
32005
  }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("label", {
31383
32006
  className: "block text-xs font-medium text-gray-600 mb-1"
@@ -31443,6 +32066,7 @@ var init_ComboEditPage = __esm({
31443
32066
  init_DetailPageLayout();
31444
32067
  init_DetailPageHeader();
31445
32068
  init_inventory_validation();
32069
+ init_admin_config_context();
31446
32070
  isCreate5 = /* @__PURE__ */ __name((id) => id === "create", "isCreate");
31447
32071
  __name(formatDateTimeLocal, "formatDateTimeLocal");
31448
32072
  __name(fetchEventDefaultCurrency, "fetchEventDefaultCurrency");
@@ -32277,9 +32901,9 @@ var VendorCategoryWorkspacePage_exports = {};
32277
32901
  __export(VendorCategoryWorkspacePage_exports, {
32278
32902
  default: () => VendorCategoryWorkspacePage
32279
32903
  });
32280
- function buildAdminProductColumns(base, multiVendorEnabled, eventsEnabled) {
32904
+ function buildAdminProductColumns(base, showVendorColumn, eventsEnabled) {
32281
32905
  const extraColumns = [
32282
- ...multiVendorEnabled ? [
32906
+ ...showVendorColumn ? [
32283
32907
  VENDOR_EXTRA_COLUMN
32284
32908
  ] : [],
32285
32909
  ...eventsEnabled ? [
@@ -32298,9 +32922,9 @@ function buildAdminProductColumns(base, multiVendorEnabled, eventsEnabled) {
32298
32922
  ...cols.slice(nameIdx + 1)
32299
32923
  ];
32300
32924
  }
32301
- function buildAllProductColumns(base, multiVendorEnabled, eventsEnabled) {
32925
+ function buildAllProductColumns(base, showVendorColumn, eventsEnabled) {
32302
32926
  const extraColumns = [
32303
- ...multiVendorEnabled ? [
32927
+ ...showVendorColumn ? [
32304
32928
  VENDOR_EXTRA_COLUMN
32305
32929
  ] : [],
32306
32930
  ...eventsEnabled ? [
@@ -32350,7 +32974,8 @@ function VendorCategoryWorkspacePage() {
32350
32974
  const workspaceFrom = activeCategoryId ? `/admin/products?categoryId=${activeCategoryId}` : "/admin/products";
32351
32975
  const itemLabel = activeCategory ? categorySingularName(activeCategory.name) : "Product";
32352
32976
  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);
32977
+ const showVendorColumn = multiVendorEnabled !== false && !vendorPortal;
32978
+ const base = showAllProducts ? buildAllProductColumns(STORE_CRUD_CONFIGS.products.columns, showVendorColumn, eventsEnabled !== false) : buildAdminProductColumns(STORE_CRUD_CONFIGS.products.columns, showVendorColumn, eventsEnabled !== false);
32354
32979
  return withCollectionRelationApi(base, activeCategoryId, vendorPortal);
32355
32980
  }, [
32356
32981
  vendorPortal,
@@ -33676,7 +34301,7 @@ function CustomerPicker({ value, label, onChange }) {
33676
34301
  className: "text-xs text-gray-400 shrink-0 truncate"
33677
34302
  }, c.email ?? ""))))));
33678
34303
  }
33679
- function ConditionCard({ condition, onChange, onRemove }) {
34304
+ function ConditionCard({ condition, onChange, onRemove, eventsOn }) {
33680
34305
  const iconMap = {
33681
34306
  minAmount: /* @__PURE__ */ React.createElement(DollarSign, {
33682
34307
  className: "h-3.5 w-3.5 text-blue-500"
@@ -33732,7 +34357,7 @@ function ConditionCard({ condition, onChange, onRemove }) {
33732
34357
  value: "minQuantity"
33733
34358
  }, "Minimum quantity"), /* @__PURE__ */ React.createElement(SelectItem, {
33734
34359
  value: "productMinQuantity"
33735
- }, "Product"), /* @__PURE__ */ React.createElement(SelectItem, {
34360
+ }, "Product"), (eventsOn || condition.kind === "events") && /* @__PURE__ */ React.createElement(SelectItem, {
33736
34361
  value: "events"
33737
34362
  }, "Events"), /* @__PURE__ */ React.createElement(SelectItem, {
33738
34363
  value: "nthOrder"
@@ -33966,11 +34591,13 @@ function RewardCard({ reward, onChange, onRemove, discountType, discountValue, e
33966
34591
  })));
33967
34592
  }
33968
34593
  function DiscountConditionsBuilder({ rules, onChange, discountType = "PERCENTAGE", discountValue = "" }) {
34594
+ const { eventsEnabled } = useContext(AdminConfigContext);
34595
+ const eventsOn = eventsEnabled !== false;
33969
34596
  const parsed = ruleTreeToFriendly(rules);
33970
34597
  const [groups, setGroups] = useState(parsed.groups);
33971
34598
  const [groupOperator, setGroupOperator] = useState(parsed.groupOperator);
33972
34599
  const [rewards, setRewards] = useState(parsed.rewards);
33973
- const rewardEventId = groups.flatMap((g) => g.conditions).find((c) => c.kind === "events" && c.eventId != null)?.eventId ?? null;
34600
+ const rewardEventId = eventsOn ? groups.flatMap((g) => g.conditions).find((c) => c.kind === "events" && c.eventId != null)?.eventId ?? null : null;
33974
34601
  useEffect(() => {
33975
34602
  const missingNameIds = [];
33976
34603
  for (const g of groups) {
@@ -34207,7 +34834,8 @@ function DiscountConditionsBuilder({ rules, onChange, discountType = "PERCENTAGE
34207
34834
  })), /* @__PURE__ */ React.createElement(ConditionCard, {
34208
34835
  condition: c,
34209
34836
  onChange: /* @__PURE__ */ __name((updated) => updateCondition(group.id, c.id, updated), "onChange"),
34210
- onRemove: /* @__PURE__ */ __name(() => removeCondition(group.id, c.id), "onRemove")
34837
+ onRemove: /* @__PURE__ */ __name(() => removeCondition(group.id, c.id), "onRemove"),
34838
+ eventsOn
34211
34839
  })))), /* @__PURE__ */ React.createElement(Button, {
34212
34840
  type: "button",
34213
34841
  variant: "outline",
@@ -34270,6 +34898,7 @@ var init_DiscountsConditionsBuilder = __esm({
34270
34898
  "use client";
34271
34899
  init_button();
34272
34900
  init_select();
34901
+ init_admin_config_context();
34273
34902
  __name(uid, "uid");
34274
34903
  __name(conditionToRule, "conditionToRule");
34275
34904
  __name(rewardToRule, "rewardToRule");
@@ -36451,7 +37080,7 @@ function AdminPageResolver({ slug }) {
36451
37080
  const searchParams = useSearchParams();
36452
37081
  const { data: session } = useSession();
36453
37082
  const vendorPortal = isVendorPortalUser(session?.user);
36454
- const { customCrudConfigs, storeEnabled, multiVendorEnabled, eventsEnabled } = useContext(AdminConfigContext);
37083
+ const { customCrudConfigs, storeEnabled, multiVendorEnabled, eventsEnabled, vendorCanCreateCategories, vendorCanCreateCollections, vendorCanCreateBrands, requireProductApproval, requireEventApproval } = useContext(AdminConfigContext);
36455
37084
  const key = slug?.[0] || "dashboard";
36456
37085
  const [vendorOptions, setVendorOptions] = useState([]);
36457
37086
  useEffect(() => {
@@ -36485,6 +37114,11 @@ function AdminPageResolver({ slug }) {
36485
37114
  columns = columns.filter((column) => column.field !== "eventId" && column.field !== "eventName");
36486
37115
  filters = filters.filter((filter) => filter.param !== "eventId");
36487
37116
  }
37117
+ const showApprovalColumn = key === "products" && requireProductApproval === true || key === "events" && requireEventApproval === true;
37118
+ if (!showApprovalColumn) {
37119
+ columns = columns.filter((column) => column.field !== "approvalStatus");
37120
+ filters = filters.filter((filter) => filter.param !== "approvalStatus");
37121
+ }
36488
37122
  if (multiVendorEnabled !== false && !vendorPortal && STORE_VENDOR_RESOURCES.has(key) && key !== "event_products" && !columns.some((c) => c.field === "vendorId")) {
36489
37123
  columns = [
36490
37124
  VENDOR_COLUMN2,
@@ -36522,7 +37156,9 @@ function AdminPageResolver({ slug }) {
36522
37156
  vendorOptions,
36523
37157
  vendorPortal,
36524
37158
  multiVendorEnabled,
36525
- eventsEnabled
37159
+ eventsEnabled,
37160
+ requireProductApproval,
37161
+ requireEventApproval
36526
37162
  ]);
36527
37163
  const isContactsWithStore = key === "contacts" && storeEnabled;
36528
37164
  const extraListParams = useMemo(() => isContactsWithStore ? {
@@ -36636,7 +37272,27 @@ function AdminPageResolver({ slug }) {
36636
37272
  className: "ml-2"
36637
37273
  }, "Redirecting\u2026"));
36638
37274
  }
36639
- if (vendorPortal && key === "product_categories") {
37275
+ if (vendorPortal && key === "product_categories" && vendorCanCreateCategories !== true) {
37276
+ router.replace("/admin/products");
37277
+ return /* @__PURE__ */ React26__default.createElement("div", {
37278
+ className: "flex justify-center py-8"
37279
+ }, /* @__PURE__ */ React26__default.createElement("div", {
37280
+ className: "animate-spin rounded-full h-6 w-6 border-2 border-gray-300 border-t-gray-600"
37281
+ }), /* @__PURE__ */ React26__default.createElement("span", {
37282
+ className: "ml-2"
37283
+ }, "Redirecting\u2026"));
37284
+ }
37285
+ if (vendorPortal && key === "collections" && vendorCanCreateCollections !== true) {
37286
+ router.replace("/admin/products");
37287
+ return /* @__PURE__ */ React26__default.createElement("div", {
37288
+ className: "flex justify-center py-8"
37289
+ }, /* @__PURE__ */ React26__default.createElement("div", {
37290
+ className: "animate-spin rounded-full h-6 w-6 border-2 border-gray-300 border-t-gray-600"
37291
+ }), /* @__PURE__ */ React26__default.createElement("span", {
37292
+ className: "ml-2"
37293
+ }, "Redirecting\u2026"));
37294
+ }
37295
+ if (vendorPortal && key === "brands" && vendorCanCreateBrands !== true) {
36640
37296
  router.replace("/admin/products");
36641
37297
  return /* @__PURE__ */ React26__default.createElement("div", {
36642
37298
  className: "flex justify-center py-8"