@infuro/cms-core 1.0.72 → 1.0.74

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/admin.cjs +543 -120
  2. package/dist/admin.js +545 -122
  3. package/dist/api.cjs +37 -37
  4. package/dist/api.d.cts +13 -6
  5. package/dist/api.d.ts +13 -6
  6. package/dist/api.js +5 -5
  7. package/dist/auth.cjs +11 -11
  8. package/dist/auth.d.cts +15 -0
  9. package/dist/auth.d.ts +15 -0
  10. package/dist/auth.js +1 -1
  11. package/dist/{chunk-BQQMTQ4C.cjs → chunk-25T6HP5T.cjs} +2 -2
  12. package/dist/{chunk-TTOEY7AH.cjs → chunk-2JV4EDY3.cjs} +3 -3
  13. package/dist/{chunk-LJ5C4RBF.js → chunk-6TRH3VY7.js} +1 -1
  14. package/dist/{chunk-4MNVIGVJ.cjs → chunk-D3SR6CYU.cjs} +3 -3
  15. package/dist/{chunk-PQJIJ4ZX.js → chunk-DAUBLBTP.js} +1 -1
  16. package/dist/{chunk-SCE2GIKH.js → chunk-DEGN4BTH.js} +9 -0
  17. package/dist/{chunk-CKTRA3Q2.cjs → chunk-F3FKBNMQ.cjs} +9 -0
  18. package/dist/{chunk-SRGDHASW.cjs → chunk-H4UIIBFD.cjs} +2571 -2096
  19. package/dist/{chunk-YHVA5NHI.js → chunk-LUSJITTI.js} +43 -6
  20. package/dist/{chunk-43UVXBEH.js → chunk-OQOO2JMB.js} +2529 -2054
  21. package/dist/{chunk-HZZTDM5J.js → chunk-R5HLPH6P.js} +1 -1
  22. package/dist/{chunk-3LF4RYAJ.js → chunk-X3IJG25M.js} +1 -1
  23. package/dist/{chunk-2JHGIWH7.cjs → chunk-X5ILLSTD.cjs} +43 -6
  24. package/dist/{chunk-NIBEGDD4.cjs → chunk-ZBSH4GWB.cjs} +1 -1
  25. package/dist/{email-queue-MWP4R67H.js → email-queue-5TOJJT3P.js} +3 -3
  26. package/dist/{email-queue-BJDH2ID7.cjs → email-queue-X2A46VD3.cjs} +7 -7
  27. package/dist/{emit-order-notification-trigger-TBXAZAPS.js → emit-order-notification-trigger-3RAVWKLG.js} +1 -1
  28. package/dist/{emit-order-notification-trigger-I4HQSTEN.cjs → emit-order-notification-trigger-MY3KP7CD.cjs} +3 -3
  29. package/dist/{erp-order-invoice-5O42ZKC5.cjs → erp-order-invoice-23BDWC2H.cjs} +6 -6
  30. package/dist/{erp-order-invoice-JEQKFXG4.js → erp-order-invoice-SEZZUDMW.js} +2 -2
  31. package/dist/generate-local-invoice-pdf-V5FHS23Z.js +3 -0
  32. package/dist/{generate-local-invoice-pdf-2NCA2WBL.cjs → generate-local-invoice-pdf-XYBVKHNP.cjs} +2 -2
  33. package/dist/index.cjs +243 -243
  34. package/dist/index.d.cts +31 -9
  35. package/dist/index.d.ts +31 -9
  36. package/dist/index.js +10 -10
  37. package/dist/migrations/1783400000000-MakeProductConfigTaxIdNullable.ts +40 -0
  38. package/dist/{order-completion-otp-handlers-KHLZQQJX.js → order-completion-otp-handlers-2WOFSORP.js} +4 -4
  39. package/dist/{order-completion-otp-handlers-2ZKRK5SN.cjs → order-completion-otp-handlers-EWY47XUA.cjs} +6 -6
  40. package/dist/{order-notification-dispatcher-Z3MXNF4U.js → order-notification-dispatcher-COR2ZHM3.js} +4 -4
  41. package/dist/{order-notification-dispatcher-37KDVHBM.cjs → order-notification-dispatcher-KTOAMMSD.cjs} +9 -9
  42. package/package.json +1 -1
  43. package/dist/generate-local-invoice-pdf-XUH6GYEJ.js +0 -3
package/dist/admin.js CHANGED
@@ -3,14 +3,14 @@ import { useSession, signOut, signIn } from 'next-auth/react';
3
3
  import { clsx } from 'clsx';
4
4
  import { twMerge } from 'tailwind-merge';
5
5
  import * as React26 from 'react';
6
- import React26__default, { useState, useMemo, useContext, useEffect, useRef, useCallback, Suspense, createElement, createContext } from 'react';
6
+ import React26__default, { useState, useMemo, useEffect, useContext, useRef, useCallback, Suspense, createElement, createContext } from 'react';
7
7
  var React = React26__default;
8
8
 
9
9
  import { Slot } from '@radix-ui/react-slot';
10
10
  import { cva } from 'class-variance-authority';
11
11
  import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
12
12
  import * as LucideIcons from 'lucide-react';
13
- import { ChevronDown, User, Settings, LogOut, LayoutDashboard, Store, FolderTree, Layers, Building2, CalendarDays, ShoppingBag, Package, Receipt, BadgePercent, ShoppingCart, CreditCard, Users, Inbox, File, LinkIcon, ClipboardList, MessageSquare, Image as Image$1, Shield, FileText, Puzzle, X, ShieldAlert, Plus, MoreVertical, Upload, Download, Edit, Copy, RefreshCw, Trash2, Mail, Check, Save, Search, AlertCircle, MapPin, BarChart3, TrendingUp, UserCheck, Activity, Globe, Eye, MousePointer, ArrowLeftRight, RotateCcw, KeyRound, Loader2, CalendarClock, CheckCircle2, XCircle, CheckSquare, Square, XSquare, Rss, ArrowLeft, PlayCircle, UserPlus, Pencil, ShieldCheck, Smartphone, Send, FolderPlus, LayoutGrid, List, Move, Archive, Edit2, ExternalLink, Power, Star, ChevronUp, Zap, Filter, ImageIcon, GripVertical, ChevronRight, ArrowRight, ArrowUpRight, IndianRupee, Clock, Play, MessageCircle, Bot, FileUp, Tag, Box, SlidersHorizontal, Calculator, Link2 as Link2$1, Unlink, Gift, MoreHorizontal, EyeOff, Phone, QrCode, Car, Settings2, DollarSign, Hash, Circle, ChevronLeft, Bell, Share2, HardDrive, Folder, Film } from 'lucide-react';
13
+ import { Loader2, Store, ChevronDown, Check, User, Settings, LogOut, LayoutDashboard, FolderTree, Layers, Building2, CalendarDays, ShoppingBag, Package, Receipt, BadgePercent, ShoppingCart, CreditCard, Users, Inbox, File, LinkIcon, ClipboardList, MessageSquare, Image as Image$1, Shield, FileText, Puzzle, X, ShieldAlert, Plus, MoreVertical, Upload, Download, Edit, Copy, RefreshCw, Trash2, Mail, Save, Search, AlertCircle, MapPin, BarChart3, TrendingUp, UserCheck, Activity, Globe, Eye, MousePointer, ArrowLeftRight, RotateCcw, KeyRound, CalendarClock, CheckCircle2, XCircle, CheckSquare, Square, XSquare, Rss, ArrowLeft, PlayCircle, UserPlus, Pencil, ShieldCheck, Smartphone, Send, FolderPlus, LayoutGrid, List, Move, Archive, Edit2, ExternalLink, Power, Star, ChevronUp, Zap, Filter, ImageIcon, GripVertical, ChevronRight, ArrowRight, ArrowUpRight, IndianRupee, Clock, Play, MessageCircle, Bot, FileUp, Tag, Box, SlidersHorizontal, Calculator, Link2 as Link2$1, Unlink, Gift, MoreHorizontal, EyeOff, Phone, QrCode, Car, Settings2, DollarSign, Hash, Circle, ChevronLeft, Bell, Share2, HardDrive, Folder, Film } from 'lucide-react';
14
14
  import Image from 'next/image';
15
15
  import Link2 from 'next/link';
16
16
  import { usePathname, useSearchParams, useRouter } from 'next/navigation';
@@ -347,9 +347,8 @@ var init_vendor_scope = __esm({
347
347
  }
348
348
  });
349
349
  function AdminHeader() {
350
- const { data: session } = useSession();
350
+ const { data: session, update } = useSession();
351
351
  const sessionUser = session?.user;
352
- const roleLabel = sessionUser?.groupName?.trim() || (sessionUser?.isRBACAdmin ? "Administrator" : "Admin user");
353
352
  const isVendor = isVendorPortalUser(sessionUser);
354
353
  const isMobile = useIsMobile();
355
354
  const configuredLogo = process.env.NEXT_PUBLIC_ADMIN_LOGO_URL || DEFAULT_ADMIN_LOGO;
@@ -358,9 +357,125 @@ function AdminHeader() {
358
357
  const isDataLogo = useMemo(() => logoSrc.startsWith("data:"), [
359
358
  logoSrc
360
359
  ]);
360
+ const [isSwitching, setIsSwitching] = useState(false);
361
+ const [cookieVendorId, setCookieVendorId] = useState(() => {
362
+ if (typeof window === "undefined") return null;
363
+ try {
364
+ const match = document.cookie.match(/(?:^|;\s*)infuro_active_vendor_id=(\d+)/);
365
+ if (match && match[1]) return Number(match[1]);
366
+ const stored = localStorage.getItem("infuro_active_vendor_id");
367
+ if (stored) return Number(stored);
368
+ } catch {
369
+ }
370
+ return null;
371
+ });
372
+ const [fetchedStores, setFetchedStores] = useState(null);
373
+ const [fetchedRoleName, setFetchedRoleName] = useState(null);
374
+ const [fetchedActiveVendorId, setFetchedActiveVendorId] = useState(null);
375
+ useEffect(() => {
376
+ if (sessionUser?.vendorStores && sessionUser.vendorStores.length > 1 && !cookieVendorId) {
377
+ return;
378
+ }
379
+ let isMounted = true;
380
+ fetch("/api/admin/vendor/context").then((res) => res.ok ? res.json() : null).then((data) => {
381
+ if (!isMounted || !data) return;
382
+ if (Array.isArray(data.vendorStores)) {
383
+ setFetchedStores(data.vendorStores);
384
+ }
385
+ if (data.vendorRoleName) {
386
+ setFetchedRoleName(data.vendorRoleName);
387
+ }
388
+ if (typeof data.activeVendorId === "number") {
389
+ setFetchedActiveVendorId(data.activeVendorId);
390
+ }
391
+ try {
392
+ localStorage.setItem("infuro_active_vendor_context", JSON.stringify(data));
393
+ window.dispatchEvent(new CustomEvent("infuro_vendor_context_updated", {
394
+ detail: data
395
+ }));
396
+ } catch {
397
+ }
398
+ }).catch(() => {
399
+ });
400
+ return () => {
401
+ isMounted = false;
402
+ };
403
+ }, [
404
+ sessionUser?.email,
405
+ sessionUser?.vendorStores,
406
+ cookieVendorId
407
+ ]);
408
+ const stores = useMemo(() => {
409
+ if (sessionUser?.vendorStores && sessionUser.vendorStores.length > 0) {
410
+ return sessionUser.vendorStores;
411
+ }
412
+ return fetchedStores || [];
413
+ }, [
414
+ sessionUser?.vendorStores,
415
+ fetchedStores
416
+ ]);
417
+ const effectiveActiveVendorId = cookieVendorId ?? sessionUser?.activeVendorId ?? fetchedActiveVendorId;
418
+ const activeStore = useMemo(() => {
419
+ return stores.find((s) => s.id === effectiveActiveVendorId) || (stores.length > 0 ? stores[0] : null);
420
+ }, [
421
+ stores,
422
+ effectiveActiveVendorId
423
+ ]);
424
+ const effectiveRoleName = activeStore?.roleName || sessionUser?.vendorRoleName || fetchedRoleName;
425
+ const roleLabel = ((isVendor || stores.length > 0) && effectiveRoleName ? effectiveRoleName : null) || sessionUser?.groupName?.trim() || (sessionUser?.isRBACAdmin ? "Administrator" : "Admin user");
361
426
  const handleLogout = /* @__PURE__ */ __name(() => {
362
427
  void adminSignOut();
363
428
  }, "handleLogout");
429
+ const handleSwitchStore = /* @__PURE__ */ __name(async (vendorId) => {
430
+ if (vendorId === activeStore?.id || isSwitching) return;
431
+ try {
432
+ setIsSwitching(true);
433
+ document.cookie = `infuro_active_vendor_id=${vendorId}; path=/; max-age=31536000; SameSite=Lax`;
434
+ try {
435
+ localStorage.setItem("infuro_active_vendor_id", String(vendorId));
436
+ } catch {
437
+ }
438
+ setCookieVendorId(vendorId);
439
+ const res = await fetch("/api/admin/vendor/switch", {
440
+ method: "POST",
441
+ headers: {
442
+ "Content-Type": "application/json"
443
+ },
444
+ body: JSON.stringify({
445
+ vendorId
446
+ })
447
+ });
448
+ if (res.ok) {
449
+ const data = await res.json();
450
+ try {
451
+ localStorage.setItem("infuro_active_vendor_context", JSON.stringify(data));
452
+ window.dispatchEvent(new CustomEvent("infuro_vendor_context_updated", {
453
+ detail: data
454
+ }));
455
+ } catch {
456
+ }
457
+ if (typeof update === "function") {
458
+ try {
459
+ await update({
460
+ activeVendorId: vendorId,
461
+ vendorRole: data.vendorRole,
462
+ vendorRoleId: data.vendorRoleId,
463
+ vendorRoleName: data.vendorRoleName,
464
+ isVendorRoleOwner: data.isVendorRoleOwner,
465
+ vendorEntityPerms: data.vendorEntityPerms,
466
+ vendorStores: data.vendorStores
467
+ });
468
+ } catch {
469
+ }
470
+ }
471
+ window.location.reload();
472
+ }
473
+ } catch (err) {
474
+ console.error("Failed to switch store:", err);
475
+ } finally {
476
+ setIsSwitching(false);
477
+ }
478
+ }, "handleSwitchStore");
364
479
  return /* @__PURE__ */ React.createElement("header", {
365
480
  className: "bg-white border-b border-gray-200 px-4 py-2"
366
481
  }, /* @__PURE__ */ React.createElement("div", {
@@ -393,7 +508,46 @@ function AdminHeader() {
393
508
  className: "text-sm font-semibold text-gray-800"
394
509
  }, "Infuro"))), /* @__PURE__ */ React.createElement("div", {
395
510
  className: "flex items-center space-x-3"
396
- }, /* @__PURE__ */ React.createElement(DropdownMenu, null, /* @__PURE__ */ React.createElement(DropdownMenuTrigger, {
511
+ }, stores.length > 1 && /* @__PURE__ */ React.createElement(DropdownMenu, null, /* @__PURE__ */ React.createElement(DropdownMenuTrigger, {
512
+ asChild: true
513
+ }, /* @__PURE__ */ React.createElement(Button, {
514
+ variant: "outline",
515
+ size: "sm",
516
+ disabled: isSwitching,
517
+ className: "h-8 px-2.5 flex items-center gap-1.5 border-gray-200 bg-gray-50/80 hover:bg-gray-100 text-gray-800 text-xs font-medium rounded-lg shadow-none transition-colors"
518
+ }, isSwitching ? /* @__PURE__ */ React.createElement(Loader2, {
519
+ className: "h-3.5 w-3.5 animate-spin text-gray-500 shrink-0"
520
+ }) : /* @__PURE__ */ React.createElement(Store, {
521
+ className: "h-3.5 w-3.5 text-gray-500 shrink-0"
522
+ }), /* @__PURE__ */ React.createElement("span", {
523
+ className: "max-w-[130px] sm:max-w-[180px] truncate font-semibold"
524
+ }, activeStore?.name || "Select Store"), activeStore?.roleName && /* @__PURE__ */ React.createElement("span", {
525
+ className: "hidden sm:inline-block px-1.5 py-0.5 text-[10px] font-medium bg-gray-200/80 text-gray-700 rounded"
526
+ }, activeStore.roleName), /* @__PURE__ */ React.createElement(ChevronDown, {
527
+ className: "h-3 w-3 text-gray-400 shrink-0"
528
+ }))), /* @__PURE__ */ React.createElement(DropdownMenuContent, {
529
+ align: "end",
530
+ className: "w-56 p-1"
531
+ }, /* @__PURE__ */ React.createElement(DropdownMenuLabel, {
532
+ className: "text-[11px] font-semibold text-gray-500 uppercase px-2 py-1"
533
+ }, "Switch Store"), /* @__PURE__ */ React.createElement(DropdownMenuSeparator, {
534
+ className: "my-1"
535
+ }), stores.map((st) => {
536
+ const isSelected = st.id === activeStore?.id;
537
+ return /* @__PURE__ */ React.createElement(DropdownMenuItem, {
538
+ key: st.id,
539
+ onClick: /* @__PURE__ */ __name(() => handleSwitchStore(st.id), "onClick"),
540
+ className: "flex items-center justify-between px-2.5 py-2 cursor-pointer text-xs rounded-md"
541
+ }, /* @__PURE__ */ React.createElement("div", {
542
+ className: "flex flex-col gap-0.5 truncate pr-2"
543
+ }, /* @__PURE__ */ React.createElement("span", {
544
+ className: `font-medium ${isSelected ? "text-primary font-semibold" : "text-gray-800"}`
545
+ }, st.name), st.roleName && /* @__PURE__ */ React.createElement("span", {
546
+ className: "text-[10px] text-gray-400"
547
+ }, st.roleName)), isSelected && /* @__PURE__ */ React.createElement(Check, {
548
+ className: "h-3.5 w-3.5 text-primary shrink-0"
549
+ }));
550
+ }))), /* @__PURE__ */ React.createElement(DropdownMenu, null, /* @__PURE__ */ React.createElement(DropdownMenuTrigger, {
397
551
  asChild: true
398
552
  }, /* @__PURE__ */ React.createElement(Button, {
399
553
  variant: "ghost",
@@ -861,7 +1015,7 @@ var init_admin_config_context = __esm({
861
1015
  var CMS_VERSION;
862
1016
  var init_cms_version = __esm({
863
1017
  "src/lib/cms-version.ts"() {
864
- CMS_VERSION = "1.0.72" ;
1018
+ CMS_VERSION = "1.0.74" ;
865
1019
  }
866
1020
  });
867
1021
  function useCatalogCategories(enabled = true) {
@@ -969,8 +1123,54 @@ function AdminSidebar({ variant = "sidebar" }) {
969
1123
  const searchParams = useSearchParams();
970
1124
  const { data: session } = useSession();
971
1125
  const sessionUser = session?.user;
972
- const showVendorOnboard = canOnboardVendors(sessionUser);
973
- const vendorPortal = isVendorPortalUser(sessionUser);
1126
+ const [vendorContext, setVendorContext] = useState(() => {
1127
+ if (typeof window === "undefined") return null;
1128
+ try {
1129
+ const stored = localStorage.getItem("infuro_active_vendor_context");
1130
+ if (stored) return JSON.parse(stored);
1131
+ } catch {
1132
+ }
1133
+ return null;
1134
+ });
1135
+ useEffect(() => {
1136
+ const onContextUpdated = /* @__PURE__ */ __name((e) => {
1137
+ const detail = e.detail;
1138
+ if (detail) setVendorContext(detail);
1139
+ }, "onContextUpdated");
1140
+ window.addEventListener("infuro_vendor_context_updated", onContextUpdated);
1141
+ fetch("/api/admin/vendor/context").then((r) => r.ok ? r.json() : null).then((data) => {
1142
+ if (data) {
1143
+ setVendorContext(data);
1144
+ try {
1145
+ localStorage.setItem("infuro_active_vendor_context", JSON.stringify(data));
1146
+ } catch {
1147
+ }
1148
+ }
1149
+ }).catch(() => {
1150
+ });
1151
+ return () => {
1152
+ window.removeEventListener("infuro_vendor_context_updated", onContextUpdated);
1153
+ };
1154
+ }, []);
1155
+ const effectiveSessionUser = useMemo(() => {
1156
+ if (!sessionUser) return null;
1157
+ if (!vendorContext) return sessionUser;
1158
+ return {
1159
+ ...sessionUser,
1160
+ activeVendorId: vendorContext.activeVendorId ?? sessionUser.activeVendorId,
1161
+ vendorRole: vendorContext.vendorRole ?? sessionUser.vendorRole,
1162
+ vendorRoleId: vendorContext.vendorRoleId ?? sessionUser.vendorRoleId,
1163
+ vendorRoleName: vendorContext.vendorRoleName ?? sessionUser.vendorRoleName,
1164
+ isVendorRoleOwner: vendorContext.isVendorRoleOwner ?? sessionUser.isVendorRoleOwner,
1165
+ vendorEntityPerms: vendorContext.vendorEntityPerms ?? sessionUser.vendorEntityPerms,
1166
+ vendorStores: vendorContext.vendorStores ?? sessionUser.vendorStores
1167
+ };
1168
+ }, [
1169
+ sessionUser,
1170
+ vendorContext
1171
+ ]);
1172
+ const showVendorOnboard = canOnboardVendors(effectiveSessionUser);
1173
+ const vendorPortal = isVendorPortalUser(effectiveSessionUser);
974
1174
  const { customNavItems, customNavSections = [], storeEnabled, multiVendorEnabled, eventsEnabled, vendorCanCreateCategories, vendorCanCreateCollections, vendorCanCreateBrands } = useContext(AdminConfigContext);
975
1175
  const showStoreNav = storeEnabled || vendorPortal;
976
1176
  const showPlatformNav = !vendorPortal;
@@ -997,8 +1197,8 @@ function AdminSidebar({ variant = "sidebar" }) {
997
1197
  const headingCls = "text-[11px] font-semibold text-gray-400 uppercase tracking-wider px-2.5 mb-1.5";
998
1198
  const asideCls = isDrawer ? "w-full h-full min-h-0 bg-white flex flex-col overflow-hidden" : "w-52 h-full min-h-0 bg-white border-r border-gray-200 flex-shrink-0 flex flex-col overflow-hidden";
999
1199
  const canReadEntity = /* @__PURE__ */ __name((entity) => {
1000
- if (!sessionUser) return true;
1001
- return sessionHasEntityAccessFromExplanation(sessionUser, entity, "read");
1200
+ if (!effectiveSessionUser) return true;
1201
+ return sessionHasEntityAccessFromExplanation(effectiveSessionUser, entity, "read");
1002
1202
  }, "canReadEntity");
1003
1203
  return /* @__PURE__ */ React.createElement("aside", {
1004
1204
  className: asideCls
@@ -1208,12 +1408,12 @@ function AdminSidebar({ variant = "sidebar" }) {
1208
1408
  className: `${linkCls} ${isActive("/admin/vendor-profile") ? linkActive : linkInactive}`
1209
1409
  }, /* @__PURE__ */ React.createElement(User, {
1210
1410
  className: `h-4 w-4 mr-2 ${isActive("/admin/vendor-profile") ? iconActive : iconInactive}`
1211
- }), "Profile")), canManageVendorTeam(sessionUser) && /* @__PURE__ */ React.createElement("li", null, /* @__PURE__ */ React.createElement(Link2, {
1411
+ }), "Profile")), canManageVendorTeam(effectiveSessionUser) && /* @__PURE__ */ React.createElement("li", null, /* @__PURE__ */ React.createElement(Link2, {
1212
1412
  href: "/admin/vendor-team",
1213
1413
  className: `${linkCls} ${isActive("/admin/vendor-team") ? linkActive : linkInactive}`
1214
1414
  }, /* @__PURE__ */ React.createElement(Users, {
1215
1415
  className: `h-4 w-4 mr-2 ${isActive("/admin/vendor-team") ? iconActive : iconInactive}`
1216
- }), "Team")), canManageVendorRoles(sessionUser) && /* @__PURE__ */ React.createElement("li", null, /* @__PURE__ */ React.createElement(Link2, {
1416
+ }), "Team")), canManageVendorRoles(effectiveSessionUser) && /* @__PURE__ */ React.createElement("li", null, /* @__PURE__ */ React.createElement(Link2, {
1217
1417
  href: "/admin/vendor-roles",
1218
1418
  className: `${linkCls} ${isActive("/admin/vendor-roles") ? linkActive : linkInactive}`
1219
1419
  }, /* @__PURE__ */ React.createElement(Shield, {
@@ -6404,6 +6604,20 @@ var init_FieldConfiguration = __esm({
6404
6604
  }
6405
6605
  });
6406
6606
 
6607
+ // src/lib/slug-sanitizer.ts
6608
+ function sanitizeSlugInput(value) {
6609
+ return value.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-");
6610
+ }
6611
+ function normalizeSlug(value) {
6612
+ return sanitizeSlugInput(value).replace(/^-+|-+$/g, "");
6613
+ }
6614
+ var init_slug_sanitizer = __esm({
6615
+ "src/lib/slug-sanitizer.ts"() {
6616
+ __name(sanitizeSlugInput, "sanitizeSlugInput");
6617
+ __name(normalizeSlug, "normalizeSlug");
6618
+ }
6619
+ });
6620
+
6407
6621
  // src/components/Admin/FormBuilder.tsx
6408
6622
  var FormBuilder_exports = {};
6409
6623
  __export(FormBuilder_exports, {
@@ -6482,18 +6696,6 @@ function FormBuilder({ formId, duplicateFromId }) {
6482
6696
  loadId,
6483
6697
  loadFormData
6484
6698
  ]);
6485
- useEffect(() => {
6486
- if (formData.name && !formData.slug) {
6487
- const generatedSlug = formData.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
6488
- setFormData((prev) => ({
6489
- ...prev,
6490
- slug: generatedSlug
6491
- }));
6492
- }
6493
- }, [
6494
- formData.name,
6495
- formData.slug
6496
- ]);
6497
6699
  const addField = /* @__PURE__ */ __name(() => {
6498
6700
  const newField = {
6499
6701
  id: `field_${Date.now()}`,
@@ -6598,6 +6800,7 @@ function FormBuilder({ formId, duplicateFromId }) {
6598
6800
  setErrors([]);
6599
6801
  const payload = {
6600
6802
  ...formData,
6803
+ slug: normalizeSlug(formData.slug),
6601
6804
  published: isPublishing ? true : formData.published,
6602
6805
  fields: formData.fields.map((field) => {
6603
6806
  const numericId = typeof field.id === "number" ? field.id : /^\d+$/.test(String(field.id)) ? parseInt(String(field.id), 10) : void 0;
@@ -6728,8 +6931,12 @@ function FormBuilder({ formId, duplicateFromId }) {
6728
6931
  value: formData.slug,
6729
6932
  onChange: /* @__PURE__ */ __name((e) => setFormData((prev) => ({
6730
6933
  ...prev,
6731
- slug: e.target.value
6934
+ slug: sanitizeSlugInput(e.target.value)
6732
6935
  })), "onChange"),
6936
+ onBlur: /* @__PURE__ */ __name(() => setFormData((prev) => ({
6937
+ ...prev,
6938
+ slug: normalizeSlug(prev.slug)
6939
+ })), "onBlur"),
6733
6940
  placeholder: "form-slug"
6734
6941
  })), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("label", {
6735
6942
  className: "block text-sm font-medium text-gray-700 mb-1"
@@ -6918,6 +7125,7 @@ var init_FormBuilder = __esm({
6918
7125
  init_switch();
6919
7126
  init_badge();
6920
7127
  init_FieldConfiguration();
7128
+ init_slug_sanitizer();
6921
7129
  __name(FormBuilder, "FormBuilder");
6922
7130
  }
6923
7131
  });
@@ -7967,9 +8175,10 @@ function BlogEditor({ existingBlog, duplicateSource }) {
7967
8175
  setIsSaving(false);
7968
8176
  return;
7969
8177
  }
8178
+ const normalizedSlug = normalizeSlug(slug);
7970
8179
  const body = {
7971
8180
  title,
7972
- slug,
8181
+ slug: normalizedSlug,
7973
8182
  content,
7974
8183
  published: isPublishing,
7975
8184
  tags,
@@ -8239,7 +8448,8 @@ function BlogEditor({ existingBlog, duplicateSource }) {
8239
8448
  type: "text",
8240
8449
  placeholder: "blog-post-url",
8241
8450
  value: slug,
8242
- onChange: /* @__PURE__ */ __name((e) => setSlug(e.target.value), "onChange"),
8451
+ onChange: /* @__PURE__ */ __name((e) => setSlug(sanitizeSlugInput(e.target.value)), "onChange"),
8452
+ onBlur: /* @__PURE__ */ __name(() => setSlug((prev) => normalizeSlug(prev)), "onBlur"),
8243
8453
  className: "h-8 text-sm"
8244
8454
  }), /* @__PURE__ */ React.createElement("p", {
8245
8455
  className: "text-xs text-gray-500 mt-1"
@@ -8332,6 +8542,7 @@ var init_BlogEditorPage = __esm({
8332
8542
  init_JoditRichText();
8333
8543
  init_CategoryAutocomplete();
8334
8544
  init_UserAutocomplete();
8545
+ init_slug_sanitizer();
8335
8546
  __name(BlogEditor, "BlogEditor");
8336
8547
  }
8337
8548
  });
@@ -25933,7 +26144,9 @@ function OrderDiscountPanel({ orderLines, currency, contactId, appliedCoupons, o
25933
26144
  if (!res.ok) return;
25934
26145
  const data = await res.json();
25935
26146
  const rows = Array.isArray(data?.data) ? data.data : [];
25936
- if (!cancelled) setDiscounts(rows);
26147
+ const now = Date.now();
26148
+ const activeRows = rows.filter((d) => !d.validUntil || new Date(d.validUntil).getTime() > now);
26149
+ if (!cancelled) setDiscounts(activeRows);
25937
26150
  } catch {
25938
26151
  } finally {
25939
26152
  if (!cancelled) setLoading(false);
@@ -26399,6 +26612,7 @@ function OrderPlacementPage({ editOrderId }) {
26399
26612
  const listReturnUrl = safeAdminListReturnUrl(searchParams.get("from")) ?? "/admin/orders";
26400
26613
  const [customers, setCustomers] = useState([]);
26401
26614
  const [customersLoading, setCustomersLoading] = useState(true);
26615
+ const [selectedRowId, setSelectedRowId] = useState(null);
26402
26616
  const [contactId, setContactId] = useState(null);
26403
26617
  const [selectedCustomerId, setSelectedCustomerId] = useState(null);
26404
26618
  const [contactName, setContactName] = useState("");
@@ -26702,9 +26916,11 @@ function OrderPlacementPage({ editOrderId }) {
26702
26916
  const rows = (Array.isArray(data?.data) ? data.data : []).map((row) => {
26703
26917
  const id = Number(row.id);
26704
26918
  if (!Number.isFinite(id)) return null;
26705
- const rawCustomerId = Number(row.customerId);
26919
+ const rawContactId = Number(row.contactId ?? row.contact?.id);
26920
+ const rawCustomerId = Number(row.customerId ?? row.customer?.id);
26706
26921
  return {
26707
26922
  id,
26923
+ contactId: Number.isFinite(rawContactId) && rawContactId > 0 ? rawContactId : null,
26708
26924
  customerId: Number.isFinite(rawCustomerId) && rawCustomerId > 0 ? rawCustomerId : null,
26709
26925
  name: row.name ?? null,
26710
26926
  email: row.email ?? null,
@@ -26722,6 +26938,15 @@ function OrderPlacementPage({ editOrderId }) {
26722
26938
  cancelled = true;
26723
26939
  };
26724
26940
  }, []);
26941
+ useEffect(() => {
26942
+ if (contactId == null && !contactEmail) return;
26943
+ const match = customers.find((c) => contactId != null && c.contactId === contactId || contactEmail && c.email?.toLowerCase() === contactEmail.toLowerCase());
26944
+ if (match) setSelectedRowId(match.id);
26945
+ }, [
26946
+ contactId,
26947
+ contactEmail,
26948
+ customers
26949
+ ]);
26725
26950
  async function loadCustomerContacts(customerId) {
26726
26951
  setCustomerContactsLoading(true);
26727
26952
  setCustomerContacts([]);
@@ -26802,6 +27027,7 @@ function OrderPlacementPage({ editOrderId }) {
26802
27027
  __name(loadContactAddresses, "loadContactAddresses");
26803
27028
  function handleCustomerSelect(value) {
26804
27029
  if (value === MANUAL_CUSTOMER) {
27030
+ setSelectedRowId(null);
26805
27031
  setContactId(null);
26806
27032
  setSelectedCustomerId(null);
26807
27033
  setCustomerContacts([]);
@@ -26809,6 +27035,9 @@ function OrderPlacementPage({ editOrderId }) {
26809
27035
  setSubContactName("");
26810
27036
  setSubContactEmail("");
26811
27037
  setSubContactPhone("");
27038
+ setContactName("");
27039
+ setContactEmail("");
27040
+ setContactPhone("");
26812
27041
  setBillingAddress({
26813
27042
  ...emptyAddress
26814
27043
  });
@@ -26817,11 +27046,12 @@ function OrderPlacementPage({ editOrderId }) {
26817
27046
  });
26818
27047
  return;
26819
27048
  }
26820
- const id = Number(value);
26821
- if (!Number.isFinite(id)) return;
26822
- const customer = customers.find((c) => c.id === id);
27049
+ const rowId = Number(value);
27050
+ if (!Number.isFinite(rowId)) return;
27051
+ const customer = customers.find((c) => c.id === rowId);
26823
27052
  if (!customer) return;
26824
- setContactId(id);
27053
+ setSelectedRowId(rowId);
27054
+ setContactId(customer.contactId ?? null);
26825
27055
  setSelectedCustomerId(customer.customerId ?? null);
26826
27056
  setContactName(customer.name ?? "");
26827
27057
  setContactEmail(customer.email ?? "");
@@ -26832,7 +27062,9 @@ function OrderPlacementPage({ editOrderId }) {
26832
27062
  setSubContactPhone("");
26833
27063
  setPlaceForSomeoneElse(false);
26834
27064
  if (customer.customerId != null) loadCustomerContacts(customer.customerId);
26835
- if (customer.email) {
27065
+ if (customer.contactId != null) {
27066
+ loadContactAddresses(customer.contactId);
27067
+ } else if (customer.email) {
26836
27068
  loadContactAddressesByEmail(customer.email);
26837
27069
  } else {
26838
27070
  setBillingAddress({
@@ -26918,14 +27150,55 @@ function OrderPlacementPage({ editOrderId }) {
26918
27150
  status: "available"
26919
27151
  });
26920
27152
  if (q.length >= 2) params.set("search", q);
26921
- const res = await fetch(`/api/products?${params}`);
27153
+ const [res, epRes, evRes] = await Promise.all([
27154
+ fetch(`/api/products?${params}`),
27155
+ fetch("/api/event_products?limit=500").catch(() => null),
27156
+ fetch("/api/events?limit=500").catch(() => null)
27157
+ ]);
26922
27158
  if (!res.ok) {
26923
27159
  setProductHits([]);
26924
27160
  return;
26925
27161
  }
26926
27162
  const body = await res.json();
26927
27163
  const hits = Array.isArray(body.data) ? body.data : [];
26928
- hits.forEach((p) => productCache.current.set(p.id, p));
27164
+ const eventMap = /* @__PURE__ */ new Map();
27165
+ if (evRes && evRes.ok) {
27166
+ const evBody = await evRes.json().catch(() => ({}));
27167
+ const evList = Array.isArray(evBody.data) ? evBody.data : [];
27168
+ for (const ev of evList) {
27169
+ if (ev && ev.id) eventMap.set(Number(ev.id), ev);
27170
+ }
27171
+ }
27172
+ const productEventLimits = /* @__PURE__ */ new Map();
27173
+ if (epRes && epRes.ok) {
27174
+ const epBody = await epRes.json().catch(() => ({}));
27175
+ const epList = Array.isArray(epBody.data) ? epBody.data : [];
27176
+ for (const ep of epList) {
27177
+ const ev = eventMap.get(Number(ep.eventId));
27178
+ if (ev) {
27179
+ const maxGroup = typeof ev.maxGroupPurchaseQuantity === "number" && Number.isFinite(ev.maxGroupPurchaseQuantity) && ev.maxGroupPurchaseQuantity > 0 ? Math.floor(ev.maxGroupPurchaseQuantity) : null;
27180
+ const allowsGroup = ev.allowGroupOrders !== false;
27181
+ let cap;
27182
+ if (maxGroup !== null && maxGroup > 1) {
27183
+ cap = maxGroup;
27184
+ } else if (!allowsGroup) {
27185
+ cap = 1;
27186
+ } else if (maxGroup !== null) {
27187
+ cap = maxGroup;
27188
+ } else {
27189
+ cap = 999;
27190
+ }
27191
+ productEventLimits.set(Number(ep.productId), cap);
27192
+ }
27193
+ }
27194
+ }
27195
+ hits.forEach((p) => {
27196
+ const cap = productEventLimits.get(p.id);
27197
+ if (cap !== void 0) {
27198
+ p.maxPurchaseLimit = cap;
27199
+ }
27200
+ productCache.current.set(p.id, p);
27201
+ });
26929
27202
  setProductHits(hits);
26930
27203
  } catch {
26931
27204
  setProductHits([]);
@@ -27202,6 +27475,7 @@ function OrderPlacementPage({ editOrderId }) {
27202
27475
  toast.message("Select one or more products in the list first");
27203
27476
  return;
27204
27477
  }
27478
+ let reachedLimit = false;
27205
27479
  setLines((prev) => {
27206
27480
  const next = [
27207
27481
  ...prev
@@ -27209,29 +27483,47 @@ function OrderPlacementPage({ editOrderId }) {
27209
27483
  for (const id of stagedIds) {
27210
27484
  const hit = productCache.current.get(id);
27211
27485
  if (!hit) continue;
27486
+ const maxAllowed = typeof hit.maxPurchaseLimit === "number" && hit.maxPurchaseLimit > 0 ? hit.maxPurchaseLimit : 99999;
27212
27487
  const idx = next.findIndex((l) => l.productId === id);
27213
27488
  if (idx >= 0) {
27214
27489
  const row = next[idx];
27215
- next[idx] = {
27216
- ...row,
27217
- quantity: row.quantity + 1
27218
- };
27219
- } else next.push({
27220
- key: String(id),
27221
- productId: id,
27222
- label: hit.name ?? `Product #${id}`,
27223
- sku: hit.sku,
27224
- quantity: 1
27225
- });
27490
+ if (row.quantity >= maxAllowed) {
27491
+ reachedLimit = true;
27492
+ } else {
27493
+ next[idx] = {
27494
+ ...row,
27495
+ quantity: Math.min(maxAllowed, row.quantity + 1)
27496
+ };
27497
+ }
27498
+ } else {
27499
+ next.push({
27500
+ key: String(id),
27501
+ productId: id,
27502
+ label: hit.name ?? `Product #${id}`,
27503
+ sku: hit.sku,
27504
+ quantity: 1
27505
+ });
27506
+ }
27226
27507
  }
27227
27508
  return next;
27228
27509
  });
27229
27510
  setStagedIds(/* @__PURE__ */ new Set());
27230
- toast.success("Added to cart");
27511
+ if (reachedLimit) {
27512
+ toast.error("One or more items reached their maximum purchase limit");
27513
+ } else {
27514
+ toast.success("Added to cart");
27515
+ }
27231
27516
  }
27232
27517
  __name(addStagedToCart, "addStagedToCart");
27233
27518
  function updateQty(key, quantity) {
27234
- const q = Math.min(99999, Math.max(1, Math.floor(quantity) || 1));
27519
+ const line = lines.find((l) => l.key === key);
27520
+ const hit = line ? productCache.current.get(line.productId) : null;
27521
+ const maxAllowed = typeof hit?.maxPurchaseLimit === "number" && hit.maxPurchaseLimit > 0 ? hit.maxPurchaseLimit : 99999;
27522
+ const parsed = Math.max(1, Math.floor(quantity) || 1);
27523
+ const q = Math.min(maxAllowed, parsed);
27524
+ if (parsed > maxAllowed) {
27525
+ toast.error(`Maximum purchase limit for "${line?.label ?? "this product"}" is ${maxAllowed}`);
27526
+ }
27235
27527
  setLines((prev) => prev.map((l) => l.key === key ? {
27236
27528
  ...l,
27237
27529
  quantity: q
@@ -27281,6 +27573,13 @@ function OrderPlacementPage({ editOrderId }) {
27281
27573
  toast.error("Add at least one product to the cart");
27282
27574
  return;
27283
27575
  }
27576
+ for (const line of lines) {
27577
+ const hit = productCache.current.get(line.productId);
27578
+ if (hit?.maxPurchaseLimit && line.quantity > hit.maxPurchaseLimit) {
27579
+ toast.error(`Quantity for "${line.label}" cannot exceed maximum allowed limit of ${hit.maxPurchaseLimit}`);
27580
+ return;
27581
+ }
27582
+ }
27284
27583
  const unknown = preview?.lines.filter((l) => !l.found) ?? [];
27285
27584
  if (unknown.length > 0) {
27286
27585
  toast.error(`One or more items could not be validated: ${unknown.map((u) => u.productName).join(", ")}`);
@@ -27310,6 +27609,9 @@ function OrderPlacementPage({ editOrderId }) {
27310
27609
  const orderContactId = resolvedContactId;
27311
27610
  const updatedMetadata = {
27312
27611
  ...orderMetadata,
27612
+ customerName: effectiveName || void 0,
27613
+ customerEmail: effectiveEmail || void 0,
27614
+ customerPhone: effectivePhone || void 0,
27313
27615
  orderDate: orderDate || void 0,
27314
27616
  bookingDate: bookingDate || void 0,
27315
27617
  serviceDate: bookingDate || void 0,
@@ -27338,13 +27640,14 @@ function OrderPlacementPage({ editOrderId }) {
27338
27640
  })
27339
27641
  };
27340
27642
  if (orderContactId != null) payload.contactId = orderContactId;
27341
- if (!placeForSomeoneElse && contactId != null) payload.customerId = contactId;
27643
+ if (!placeForSomeoneElse && selectedCustomerId != null) payload.customerId = selectedCustomerId;
27342
27644
  if (placeForSomeoneElse && selectedCustomerId != null) {
27343
27645
  payload.accountCustomerId = selectedCustomerId;
27344
27646
  }
27345
27647
  if (coupons.length > 0) payload.discountId = coupons[0].discountId;
27346
- if (!editOrderId && orderDate) {
27347
- payload.createdAt = `${orderDate}T06:30:00.000Z`;
27648
+ const todayIso = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
27649
+ if (!editOrderId && orderDate && orderDate !== todayIso) {
27650
+ payload.createdAt = `${orderDate}T${(/* @__PURE__ */ new Date()).toISOString().slice(11)}`;
27348
27651
  }
27349
27652
  const url = editOrderId ? `/api/orders/${editOrderId}` : "/api/orders";
27350
27653
  const method = editOrderId ? "PUT" : "POST";
@@ -27431,7 +27734,7 @@ function OrderPlacementPage({ editOrderId }) {
27431
27734
  }
27432
27735
  __name(handleSubmit, "handleSubmit");
27433
27736
  const allVisibleStaged = productHits.length > 0 && productHits.every((p) => stagedIds.has(p.id));
27434
- const customerSelectValue = contactId != null ? String(contactId) : MANUAL_CUSTOMER;
27737
+ const customerSelectValue = selectedRowId != null ? String(selectedRowId) : MANUAL_CUSTOMER;
27435
27738
  const subContactSelectValue = subContactId != null ? String(subContactId) : MANUAL_CONTACT;
27436
27739
  if (editOrderId && loadingOrder) {
27437
27740
  return /* @__PURE__ */ React.createElement("div", {
@@ -27943,6 +28246,7 @@ function OrderPlacementPage({ editOrderId }) {
27943
28246
  }, /* @__PURE__ */ React.createElement(Input, {
27944
28247
  type: "number",
27945
28248
  min: 1,
28249
+ max: productCache.current.get(line.productId)?.maxPurchaseLimit ?? void 0,
27946
28250
  className: "h-8 w-14 ml-auto text-right",
27947
28251
  value: line.quantity,
27948
28252
  onChange: /* @__PURE__ */ __name((e) => updateQty(line.key, Number(e.target.value)), "onChange"),
@@ -30543,9 +30847,10 @@ function SaveButton({ pageId, pageData, existingSeoId, onSeoIdChange, onSaved, c
30543
30847
  }
30544
30848
  }
30545
30849
  }
30850
+ const normalizedSlug = normalizeSlug(pageData.slug);
30546
30851
  const payload = {
30547
30852
  title: pageData.title,
30548
- slug: pageData.slug,
30853
+ slug: normalizedSlug,
30549
30854
  content,
30550
30855
  published: pageData.published
30551
30856
  };
@@ -30785,7 +31090,8 @@ function PageBuilderPage({ pageId }) {
30785
31090
  className: "block text-xs font-medium text-gray-600 mb-1"
30786
31091
  }, "Slug *"), /* @__PURE__ */ React26__default.createElement(Input, {
30787
31092
  value: slug,
30788
- onChange: /* @__PURE__ */ __name((e) => setSlug(e.target.value), "onChange"),
31093
+ onChange: /* @__PURE__ */ __name((e) => setSlug(sanitizeSlugInput(e.target.value)), "onChange"),
31094
+ onBlur: /* @__PURE__ */ __name(() => setSlug((prev) => normalizeSlug(prev)), "onBlur"),
30789
31095
  placeholder: "page-url-slug",
30790
31096
  className: "h-8 text-sm"
30791
31097
  })))), /* @__PURE__ */ React26__default.createElement(RightSidebar, {
@@ -30822,6 +31128,7 @@ var init_PageBuilderPage = __esm({
30822
31128
  init_admin_config_context();
30823
31129
  init_registry();
30824
31130
  init_ImageOrUrlField();
31131
+ init_slug_sanitizer();
30825
31132
  __name(createSelectable, "createSelectable");
30826
31133
  __name(buildEditorResolver, "buildEditorResolver");
30827
31134
  __name(getIcon, "getIcon");
@@ -31055,7 +31362,8 @@ function BrandEditPage({ brandId }) {
31055
31362
  ]);
31056
31363
  return;
31057
31364
  }
31058
- if (!slug.trim()) {
31365
+ const normalizedSlug = normalizeSlug(slug);
31366
+ if (!normalizedSlug) {
31059
31367
  setErrors([
31060
31368
  "Slug is required"
31061
31369
  ]);
@@ -31063,10 +31371,10 @@ function BrandEditPage({ brandId }) {
31063
31371
  }
31064
31372
  setSaving(true);
31065
31373
  try {
31066
- const savedSeoId = await saveSeo(seo, slug.trim(), seoId);
31374
+ const savedSeoId = await saveSeo(seo, normalizedSlug, seoId);
31067
31375
  const payload = {
31068
31376
  name: name.trim(),
31069
- slug: slug.trim(),
31377
+ slug: normalizedSlug,
31070
31378
  description: description || null,
31071
31379
  logo: logo || null,
31072
31380
  active,
@@ -31156,7 +31464,9 @@ function BrandEditPage({ brandId }) {
31156
31464
  }, "Slug *"), /* @__PURE__ */ React.createElement("input", {
31157
31465
  type: "text",
31158
31466
  value: slug,
31159
- onChange: /* @__PURE__ */ __name((e) => setSlug(e.target.value), "onChange"),
31467
+ placeholder: "brand-slug",
31468
+ onChange: /* @__PURE__ */ __name((e) => setSlug(sanitizeSlugInput(e.target.value)), "onChange"),
31469
+ onBlur: /* @__PURE__ */ __name(() => setSlug((prev) => normalizeSlug(prev)), "onBlur"),
31160
31470
  className: "w-full rounded-md border border-gray-300 px-3 py-2 text-sm",
31161
31471
  required: true
31162
31472
  })), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("label", {
@@ -31216,6 +31526,7 @@ var init_BrandEditPage = __esm({
31216
31526
  init_DetailPageHeader();
31217
31527
  init_vendor_scope();
31218
31528
  init_ImageOrUrlField();
31529
+ init_slug_sanitizer();
31219
31530
  isCreate = /* @__PURE__ */ __name((id) => id === "create", "isCreate");
31220
31531
  __name(BrandEditPage, "BrandEditPage");
31221
31532
  }
@@ -32680,11 +32991,21 @@ function ProductEditPage({ productId }) {
32680
32991
  const pcData = await pcRes.json();
32681
32992
  const pcs = Array.isArray(pcData.data) ? pcData.data : [];
32682
32993
  if (pcs.length > 0) {
32683
- setTaxRows(pcs.map((p) => ({
32684
- taxId: p.taxId,
32685
- rate: p.rate != null && p.rate !== "" ? String(p.rate) : ""
32686
- })));
32687
- const firstRefund = pcs[0].refundPolicyId;
32994
+ const validTaxPcs = pcs.filter((p) => p.taxId != null && Number(p.taxId) > 0);
32995
+ if (validTaxPcs.length > 0) {
32996
+ setTaxRows(validTaxPcs.map((p) => ({
32997
+ taxId: p.taxId,
32998
+ rate: p.rate != null && p.rate !== "" ? String(p.rate) : ""
32999
+ })));
33000
+ } else if (!cancelled) {
33001
+ setTaxRows([
33002
+ {
33003
+ taxId: "",
33004
+ rate: ""
33005
+ }
33006
+ ]);
33007
+ }
33008
+ const firstRefund = pcs.find((p) => p.refundPolicyId != null)?.refundPolicyId;
32688
33009
  setRefundPolicyId(firstRefund != null ? Number(firstRefund) : null);
32689
33010
  } else if (!cancelled) {
32690
33011
  setTaxRows([
@@ -32693,6 +33014,7 @@ function ProductEditPage({ productId }) {
32693
33014
  rate: ""
32694
33015
  }
32695
33016
  ]);
33017
+ setRefundPolicyId(null);
32696
33018
  }
32697
33019
  }
32698
33020
  const paRes = await fetch(`/api/product_attributes?productId=${sourceProductId}&limit=100`);
@@ -33069,14 +33391,7 @@ function ProductEditPage({ productId }) {
33069
33391
  if (a == null || b == null) return true;
33070
33392
  return Math.abs(a - b) > 1e-6;
33071
33393
  }, "ratesDiffer");
33072
- const wantedConfig = /* @__PURE__ */ new Map();
33073
- for (const row of taxRows) {
33074
- if (row.taxId === "") continue;
33075
- wantedConfig.set(row.taxId, {
33076
- rate: parseTaxRate(row.rate),
33077
- refundPolicyId
33078
- });
33079
- }
33394
+ const validTaxRows = taxRows.filter((row) => row.taxId !== "");
33080
33395
  console.log("[product_config rows to save]:", taxRows, "refundPolicyId:", refundPolicyId);
33081
33396
  const pcListRes = await fetch(`/api/product_config?productId=${savedId}&limit=200`);
33082
33397
  const pcListData = pcListRes.ok ? await pcListRes.json() : {
@@ -33084,17 +33399,60 @@ function ProductEditPage({ productId }) {
33084
33399
  };
33085
33400
  console.log("[product existing pc rows]:", pcListData);
33086
33401
  const existingPc = Array.isArray(pcListData.data) ? pcListData.data : [];
33087
- for (const ep of existingPc) {
33088
- if (!wantedConfig.has(ep.taxId)) {
33089
- await fetch(`/api/product_config/${ep.id}`, {
33090
- method: "DELETE"
33091
- });
33402
+ if (validTaxRows.length > 0) {
33403
+ const wantedTaxIds = new Set(validTaxRows.map((r) => Number(r.taxId)));
33404
+ for (const ep of existingPc) {
33405
+ if (ep.taxId == null || !wantedTaxIds.has(Number(ep.taxId))) {
33406
+ await fetch(`/api/product_config/${ep.id}`, {
33407
+ method: "DELETE"
33408
+ });
33409
+ }
33092
33410
  }
33093
- }
33094
- const survivors = existingPc.filter((ep) => wantedConfig.has(ep.taxId));
33095
- for (const [taxId, cfg] of wantedConfig) {
33096
- const ep = survivors.find((e) => e.taxId === taxId);
33097
- if (!ep) {
33411
+ for (const row of validTaxRows) {
33412
+ const taxId = Number(row.taxId);
33413
+ const rate = parseTaxRate(row.rate);
33414
+ const ep = existingPc.find((e) => e.taxId != null && Number(e.taxId) === taxId);
33415
+ if (!ep) {
33416
+ await fetch("/api/product_config", {
33417
+ method: "POST",
33418
+ headers: {
33419
+ "Content-Type": "application/json"
33420
+ },
33421
+ body: JSON.stringify({
33422
+ productId: Number(savedId),
33423
+ taxId,
33424
+ rate,
33425
+ refundPolicyId
33426
+ })
33427
+ });
33428
+ } else {
33429
+ const existingRate = ep.rate == null || String(ep.rate).trim() === "" ? null : Number(ep.rate);
33430
+ const er = Number.isFinite(existingRate) ? existingRate : null;
33431
+ const policyChanged = ep.refundPolicyId !== refundPolicyId;
33432
+ if (ratesDiffer(er, rate) || policyChanged) {
33433
+ await fetch(`/api/product_config/${ep.id}`, {
33434
+ method: "PUT",
33435
+ headers: {
33436
+ "Content-Type": "application/json"
33437
+ },
33438
+ body: JSON.stringify({
33439
+ rate,
33440
+ refundPolicyId
33441
+ })
33442
+ });
33443
+ }
33444
+ }
33445
+ }
33446
+ } else if (refundPolicyId != null) {
33447
+ const nullTaxRow = existingPc.find((e) => e.taxId == null);
33448
+ for (const ep of existingPc) {
33449
+ if (ep.id !== nullTaxRow?.id) {
33450
+ await fetch(`/api/product_config/${ep.id}`, {
33451
+ method: "DELETE"
33452
+ });
33453
+ }
33454
+ }
33455
+ if (!nullTaxRow) {
33098
33456
  await fetch("/api/product_config", {
33099
33457
  method: "POST",
33100
33458
  headers: {
@@ -33102,27 +33460,27 @@ function ProductEditPage({ productId }) {
33102
33460
  },
33103
33461
  body: JSON.stringify({
33104
33462
  productId: Number(savedId),
33105
- taxId,
33106
- rate: cfg.rate,
33107
- refundPolicyId: cfg.refundPolicyId
33463
+ taxId: null,
33464
+ rate: null,
33465
+ refundPolicyId
33466
+ })
33467
+ });
33468
+ } else if (nullTaxRow.refundPolicyId !== refundPolicyId) {
33469
+ await fetch(`/api/product_config/${nullTaxRow.id}`, {
33470
+ method: "PUT",
33471
+ headers: {
33472
+ "Content-Type": "application/json"
33473
+ },
33474
+ body: JSON.stringify({
33475
+ refundPolicyId
33108
33476
  })
33109
33477
  });
33110
- } else {
33111
- const existingRate = ep.rate == null || String(ep.rate).trim() === "" ? null : Number(ep.rate);
33112
- const er = Number.isFinite(existingRate) ? existingRate : null;
33113
- const policyChanged = ep.refundPolicyId !== cfg.refundPolicyId;
33114
- if (ratesDiffer(er, cfg.rate) || policyChanged) {
33115
- await fetch(`/api/product_config/${ep.id}`, {
33116
- method: "PUT",
33117
- headers: {
33118
- "Content-Type": "application/json"
33119
- },
33120
- body: JSON.stringify({
33121
- rate: cfg.rate,
33122
- refundPolicyId: cfg.refundPolicyId
33123
- })
33124
- });
33125
- }
33478
+ }
33479
+ } else {
33480
+ for (const ep of existingPc) {
33481
+ await fetch(`/api/product_config/${ep.id}`, {
33482
+ method: "DELETE"
33483
+ });
33126
33484
  }
33127
33485
  }
33128
33486
  if (hasVariants) {
@@ -34038,7 +34396,8 @@ function CollectionEditPage({ collectionId }) {
34038
34396
  ]);
34039
34397
  return;
34040
34398
  }
34041
- if (!slug.trim()) {
34399
+ const normalizedSlug = normalizeSlug(slug);
34400
+ if (!normalizedSlug) {
34042
34401
  setErrors([
34043
34402
  "Slug is required"
34044
34403
  ]);
@@ -34046,10 +34405,10 @@ function CollectionEditPage({ collectionId }) {
34046
34405
  }
34047
34406
  setSaving(true);
34048
34407
  try {
34049
- const savedSeoId = await saveSeo(seo, slug.trim(), seoId);
34408
+ const savedSeoId = await saveSeo(seo, normalizedSlug, seoId);
34050
34409
  const payload = {
34051
34410
  name: name.trim(),
34052
- slug: slug.trim(),
34411
+ slug: normalizedSlug,
34053
34412
  hsn: hsn.trim() || null,
34054
34413
  categoryId: categoryId || null,
34055
34414
  brandId: brandId || null,
@@ -34218,7 +34577,9 @@ function CollectionEditPage({ collectionId }) {
34218
34577
  }, "Slug *"), /* @__PURE__ */ React.createElement("input", {
34219
34578
  type: "text",
34220
34579
  value: slug,
34221
- onChange: /* @__PURE__ */ __name((e) => setSlug(e.target.value), "onChange"),
34580
+ placeholder: "collection-slug",
34581
+ onChange: /* @__PURE__ */ __name((e) => setSlug(sanitizeSlugInput(e.target.value)), "onChange"),
34582
+ onBlur: /* @__PURE__ */ __name(() => setSlug((prev) => normalizeSlug(prev)), "onBlur"),
34222
34583
  className: inputCls4,
34223
34584
  required: true
34224
34585
  })), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("label", {
@@ -34454,6 +34815,7 @@ var init_CollectionEditPage = __esm({
34454
34815
  init_admin_config_context();
34455
34816
  init_category_related_product_labels();
34456
34817
  init_ImageOrUrlField();
34818
+ init_slug_sanitizer();
34457
34819
  isCreate3 = /* @__PURE__ */ __name((id) => id === "create", "isCreate");
34458
34820
  emptySlide = /* @__PURE__ */ __name(() => ({
34459
34821
  url: "",
@@ -35633,7 +35995,9 @@ var init_event_entity_types = __esm({
35633
35995
  // src/admin/pages/EventEditPage.tsx
35634
35996
  var EventEditPage_exports = {};
35635
35997
  __export(EventEditPage_exports, {
35636
- default: () => EventEditPage
35998
+ default: () => EventEditPage,
35999
+ normalizeSlug: () => normalizeSlug2,
36000
+ sanitizeSlugInput: () => sanitizeSlugInput2
35637
36001
  });
35638
36002
  function RequiredLabel({ children }) {
35639
36003
  return /* @__PURE__ */ React.createElement("label", {
@@ -35642,6 +36006,12 @@ function RequiredLabel({ children }) {
35642
36006
  className: "text-red-600"
35643
36007
  }, "*"));
35644
36008
  }
36009
+ function sanitizeSlugInput2(value) {
36010
+ return value.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-");
36011
+ }
36012
+ function normalizeSlug2(value) {
36013
+ return sanitizeSlugInput2(value).replace(/^-+|-+$/g, "");
36014
+ }
35645
36015
  function toIsoOrNull(value, timezone) {
35646
36016
  const trimmed = value.trim();
35647
36017
  if (!trimmed) return null;
@@ -35732,7 +36102,7 @@ function EventEditPage({ eventId }) {
35732
36102
  const [supportContact, setSupportContact] = useState("");
35733
36103
  const [allowGroupOrders, setAllowGroupOrders] = useState(false);
35734
36104
  const [sendIndividualTicketPDFToAttendees, setSendIndividualTicketPDFToAttendees] = useState(false);
35735
- const [maxGroupPurchaseQuantity, setMaxGroupPurchaseQuantity] = useState("");
36105
+ const [maxGroupPurchaseQuantity, setMaxGroupPurchaseQuantity] = useState("1");
35736
36106
  const [sponsors, setSponsors] = useState([]);
35737
36107
  const [agenda, setAgenda] = useState([]);
35738
36108
  const [workshops, setWorkshops] = useState([]);
@@ -35858,9 +36228,10 @@ function EventEditPage({ eventId }) {
35858
36228
  }
35859
36229
  setOfficialWebsiteUrl(data.officialWebsiteUrl ?? "");
35860
36230
  setSupportContact(data.supportContact ?? "");
35861
- setAllowGroupOrders(data.allowGroupOrders ?? false);
36231
+ const loadedAllowGroup = data.allowGroupOrders ?? false;
36232
+ setAllowGroupOrders(loadedAllowGroup);
35862
36233
  setSendIndividualTicketPDFToAttendees(data.sendIndividualTicketPDFToAttendees ?? false);
35863
- setMaxGroupPurchaseQuantity(data.maxGroupPurchaseQuantity != null ? String(data.maxGroupPurchaseQuantity) : "");
36234
+ setMaxGroupPurchaseQuantity(data.maxGroupPurchaseQuantity != null ? String(data.maxGroupPurchaseQuantity) : loadedAllowGroup ? "10" : "1");
35864
36235
  setSponsors(parseNamedListFromApi(data.sponsors, "sponsor"));
35865
36236
  setAgenda(parseNamedListFromApi(data.agenda, "agenda"));
35866
36237
  setWorkshops(parseNamedListFromApi(data.workshops, "workshop"));
@@ -35906,8 +36277,13 @@ function EventEditPage({ eventId }) {
35906
36277
  ]);
35907
36278
  const buildPayload = /* @__PURE__ */ __name(() => {
35908
36279
  const nextErrors = [];
36280
+ const normalizedSlug = normalizeSlug2(slug);
35909
36281
  if (!name.trim()) nextErrors.push("Name is required");
35910
- if (!slug.trim()) nextErrors.push("Slug is required");
36282
+ if (!normalizedSlug) {
36283
+ nextErrors.push("Slug is required");
36284
+ } else if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(normalizedSlug)) {
36285
+ nextErrors.push("Slug must contain only lowercase letters, numbers, and hyphens without leading or trailing hyphens or slashes");
36286
+ }
35911
36287
  if (!startDate.trim()) nextErrors.push("Event Start Date & Time is required");
35912
36288
  if (!endDate.trim()) nextErrors.push("Event End Date & Time is required");
35913
36289
  if (startDate.trim() && !timezone.trim()) {
@@ -35933,6 +36309,20 @@ function EventEditPage({ eventId }) {
35933
36309
  nextErrors.push("Event End Date & Time must be after Event Start Date & Time");
35934
36310
  }
35935
36311
  }
36312
+ if (registrationOpensAt.trim() && registrationClosesAt.trim()) {
36313
+ const regOpen = new Date(toIsoOrNull(registrationOpensAt, timezone) ?? "");
36314
+ const regClose = new Date(toIsoOrNull(registrationClosesAt, timezone) ?? "");
36315
+ if (!Number.isNaN(regOpen.getTime()) && !Number.isNaN(regClose.getTime()) && regClose < regOpen) {
36316
+ nextErrors.push("Registration Closes Date & Time must be after Registration Opens Date & Time");
36317
+ }
36318
+ }
36319
+ if (registrationClosesAt.trim() && endDate.trim()) {
36320
+ const regClose = new Date(toIsoOrNull(registrationClosesAt, timezone) ?? "");
36321
+ const end = new Date(toIsoOrNull(endDate, timezone) ?? "");
36322
+ if (!Number.isNaN(regClose.getTime()) && !Number.isNaN(end.getTime()) && regClose > end) {
36323
+ nextErrors.push("Registration cannot close after Event End Date & Time");
36324
+ }
36325
+ }
35936
36326
  if (additionalVenueDetails.length > ADDITIONAL_VENUE_MAX_CHARS) {
35937
36327
  nextErrors.push(`Additional Venue Details must be ${ADDITIONAL_VENUE_MAX_CHARS} characters or fewer`);
35938
36328
  }
@@ -35942,7 +36332,7 @@ function EventEditPage({ eventId }) {
35942
36332
  }
35943
36333
  const payload = {
35944
36334
  name: name.trim(),
35945
- slug: slug.trim(),
36335
+ slug: normalizedSlug,
35946
36336
  description: description.trim() || null,
35947
36337
  isActive: create && vendorPortal && approvalOn ? false : isActive,
35948
36338
  comingSoon,
@@ -35970,7 +36360,7 @@ function EventEditPage({ eventId }) {
35970
36360
  supportContact: supportContact.trim() || null,
35971
36361
  allowGroupOrders,
35972
36362
  sendIndividualTicketPDFToAttendees,
35973
- maxGroupPurchaseQuantity: maxGroupPurchaseQuantity.trim() ? Number(maxGroupPurchaseQuantity) : null,
36363
+ maxGroupPurchaseQuantity: allowGroupOrders ? Number(maxGroupPurchaseQuantity) > 0 ? Number(maxGroupPurchaseQuantity) : 10 : 1,
35974
36364
  sponsors: namedListToPayload(sponsors, {
35975
36365
  legacySponsorKeys: true
35976
36366
  }),
@@ -36238,9 +36628,16 @@ function EventEditPage({ eventId }) {
36238
36628
  })), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("label", {
36239
36629
  className: labelCls6
36240
36630
  }, "Slug"), /* @__PURE__ */ React.createElement("input", {
36631
+ id: "event-slug",
36241
36632
  type: "text",
36242
36633
  value: slug,
36243
- onChange: /* @__PURE__ */ __name((e) => setSlug(e.target.value), "onChange"),
36634
+ placeholder: "event-slug",
36635
+ onChange: /* @__PURE__ */ __name((e) => {
36636
+ setSlug(sanitizeSlugInput2(e.target.value));
36637
+ }, "onChange"),
36638
+ onBlur: /* @__PURE__ */ __name(() => {
36639
+ setSlug((prev) => normalizeSlug2(prev));
36640
+ }, "onBlur"),
36244
36641
  className: inputCls6
36245
36642
  })), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("label", {
36246
36643
  className: labelCls6
@@ -36500,7 +36897,16 @@ function EventEditPage({ eventId }) {
36500
36897
  }, "Max group purchase quantity"), /* @__PURE__ */ React.createElement("input", {
36501
36898
  type: "number",
36502
36899
  value: maxGroupPurchaseQuantity,
36503
- onChange: /* @__PURE__ */ __name((e) => setMaxGroupPurchaseQuantity(e.target.value), "onChange"),
36900
+ onChange: /* @__PURE__ */ __name((e) => {
36901
+ const val = e.target.value;
36902
+ setMaxGroupPurchaseQuantity(val);
36903
+ const num = Number(val);
36904
+ if (Number.isFinite(num) && num > 1) {
36905
+ setAllowGroupOrders(true);
36906
+ } else if (Number.isFinite(num) && num <= 1) {
36907
+ setAllowGroupOrders(false);
36908
+ }
36909
+ }, "onChange"),
36504
36910
  className: inputCls6,
36505
36911
  min: 1
36506
36912
  })), /* @__PURE__ */ React.createElement("div", {
@@ -36510,7 +36916,18 @@ function EventEditPage({ eventId }) {
36510
36916
  }, /* @__PURE__ */ React.createElement("input", {
36511
36917
  type: "checkbox",
36512
36918
  checked: allowGroupOrders,
36513
- onChange: /* @__PURE__ */ __name((e) => setAllowGroupOrders(e.target.checked), "onChange"),
36919
+ onChange: /* @__PURE__ */ __name((e) => {
36920
+ const checked = e.target.checked;
36921
+ setAllowGroupOrders(checked);
36922
+ if (!checked) {
36923
+ setMaxGroupPurchaseQuantity("1");
36924
+ } else {
36925
+ const current = Number(maxGroupPurchaseQuantity);
36926
+ if (!Number.isFinite(current) || current <= 1) {
36927
+ setMaxGroupPurchaseQuantity("10");
36928
+ }
36929
+ }
36930
+ }, "onChange"),
36514
36931
  className: "h-4 w-4 rounded border-gray-300"
36515
36932
  }), /* @__PURE__ */ React.createElement("span", {
36516
36933
  className: "text-sm text-gray-900"
@@ -36584,6 +37001,8 @@ var init_EventEditPage = __esm({
36584
37001
  labelCls6 = "block text-xs font-medium text-gray-600 mb-1";
36585
37002
  inputCls6 = "w-full rounded-md border border-gray-300 px-2 py-1.5 text-sm";
36586
37003
  __name(RequiredLabel, "RequiredLabel");
37004
+ __name(sanitizeSlugInput2, "sanitizeSlugInput");
37005
+ __name(normalizeSlug2, "normalizeSlug");
36587
37006
  ENTITY_TYPE_OPTIONS = [
36588
37007
  ...EVENT_ENTITY_TYPE_OPTIONS
36589
37008
  ];
@@ -36962,7 +37381,7 @@ function ComboEditPage({ comboId }) {
36962
37381
  const resolvedEventId = trimmedEventId && /^\d+$/.test(trimmedEventId) ? Number(trimmedEventId) : null;
36963
37382
  const payload = {
36964
37383
  name: trimmedName,
36965
- slug: slug.trim() || void 0,
37384
+ slug: slug.trim() ? normalizeSlug(slug) : void 0,
36966
37385
  desc: desc || null,
36967
37386
  eventId: resolvedEventId,
36968
37387
  price,
@@ -37057,8 +37476,9 @@ function ComboEditPage({ comboId }) {
37057
37476
  }, "Slug"), /* @__PURE__ */ React.createElement("input", {
37058
37477
  type: "text",
37059
37478
  value: slug,
37060
- onChange: /* @__PURE__ */ __name((e) => setSlug(e.target.value), "onChange"),
37061
- placeholder: "Auto generate from name",
37479
+ onChange: /* @__PURE__ */ __name((e) => setSlug(sanitizeSlugInput(e.target.value)), "onChange"),
37480
+ onBlur: /* @__PURE__ */ __name(() => setSlug((prev) => normalizeSlug(prev)), "onBlur"),
37481
+ placeholder: "combo-slug",
37062
37482
  className: "w-full rounded-md border border-gray-300 px-3 py-2 text-sm"
37063
37483
  })), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("label", {
37064
37484
  className: "block text-xs font-medium text-gray-600 mb-1"
@@ -37224,6 +37644,7 @@ var init_ComboEditPage = __esm({
37224
37644
  init_DetailPageHeader();
37225
37645
  init_inventory_validation();
37226
37646
  init_admin_config_context();
37647
+ init_slug_sanitizer();
37227
37648
  isCreate5 = /* @__PURE__ */ __name((id) => id === "create", "isCreate");
37228
37649
  __name(formatDateTimeLocal, "formatDateTimeLocal");
37229
37650
  __name(fetchEventDefaultCurrency, "fetchEventDefaultCurrency");
@@ -37455,7 +37876,7 @@ function VendorEditPage({ vendorId }) {
37455
37876
  const vendorPayload = /* @__PURE__ */ __name(() => ({
37456
37877
  name: name.trim(),
37457
37878
  legalName: legalName.trim() || null,
37458
- slug: slug.trim() || void 0,
37879
+ slug: slug.trim() ? normalizeSlug(slug) : void 0,
37459
37880
  businessType: businessType || null,
37460
37881
  description: description.trim() || null,
37461
37882
  website: website.trim() || null,
@@ -37797,7 +38218,7 @@ function VendorEditPage({ vendorId }) {
37797
38218
  signal: abortController.signal,
37798
38219
  body: JSON.stringify({
37799
38220
  ...vendorPayload(),
37800
- slug: slug.trim(),
38221
+ slug: slug.trim() ? normalizeSlug(slug) : void 0,
37801
38222
  metadata: (() => {
37802
38223
  const next = {
37803
38224
  ...metadata ?? {}
@@ -37920,8 +38341,9 @@ function VendorEditPage({ vendorId }) {
37920
38341
  }, "Slug ", create ? "(optional)" : "*"), /* @__PURE__ */ React26__default.createElement(Input, {
37921
38342
  id: "vendorSlug",
37922
38343
  value: slug,
37923
- onChange: /* @__PURE__ */ __name((e) => setSlug(e.target.value), "onChange"),
37924
- placeholder: create ? "auto from name" : void 0,
38344
+ onChange: /* @__PURE__ */ __name((e) => setSlug(sanitizeSlugInput(e.target.value)), "onChange"),
38345
+ onBlur: /* @__PURE__ */ __name(() => setSlug((prev) => normalizeSlug(prev)), "onBlur"),
38346
+ placeholder: "vendor-slug",
37925
38347
  className: `mt-1 ${fieldClass}`
37926
38348
  })), /* @__PURE__ */ React26__default.createElement("div", null, /* @__PURE__ */ React26__default.createElement(FieldLabel, {
37927
38349
  htmlFor: "businessType"
@@ -38262,6 +38684,7 @@ var init_VendorEditPage = __esm({
38262
38684
  init_checkbox();
38263
38685
  init_dialog();
38264
38686
  init_vendor_profile();
38687
+ init_slug_sanitizer();
38265
38688
  init_vendor_list_config();
38266
38689
  init_vendor_access_denied();
38267
38690
  init_vendor_access_denied();