@mohasinac/appkit 2.7.19 → 2.7.21

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 (34) hide show
  1. package/dist/_internal/client/features/layout/DashboardLayoutClient.js +12 -8
  2. package/dist/_internal/client/features/layout/filterNavItems.d.ts +15 -0
  3. package/dist/_internal/client/features/layout/filterNavItems.js +19 -0
  4. package/dist/_internal/server/features/site-settings/actions.d.ts +14 -0
  5. package/dist/_internal/server/features/site-settings/actions.js +29 -0
  6. package/dist/_internal/shared/features/layout/types.d.ts +11 -0
  7. package/dist/client.d.ts +2 -0
  8. package/dist/client.js +2 -0
  9. package/dist/contracts/auth.d.ts +2 -0
  10. package/dist/features/admin/utils/checkActionAllowed.d.ts +4 -0
  11. package/dist/features/admin/utils/checkActionAllowed.js +21 -0
  12. package/dist/features/admin/utils/getDisabledRoutes.d.ts +2 -0
  13. package/dist/features/admin/utils/getDisabledRoutes.js +6 -0
  14. package/dist/features/admin/utils/getSiteSettingsGlobal.d.ts +2 -0
  15. package/dist/features/admin/utils/getSiteSettingsGlobal.js +4 -0
  16. package/dist/features/categories/components/CategoryProductsListing.js +18 -13
  17. package/dist/features/layout/AppLayoutShell.js +8 -1
  18. package/dist/features/layout/ListingLayout.js +1 -1
  19. package/dist/features/layout/MainNavbar.d.ts +4 -0
  20. package/dist/features/layout/NavItem.js +1 -1
  21. package/dist/features/pre-orders/components/PreOrdersIndexListing.js +22 -14
  22. package/dist/features/products/components/AuctionsIndexListing.js +19 -16
  23. package/dist/features/products/components/PrizeDrawEntryActions.js +7 -4
  24. package/dist/features/products/components/ProductGrid.js +1 -1
  25. package/dist/features/products/components/ProductsIndexListing.js +34 -26
  26. package/dist/features/stores/components/StoreAuctionsListing.js +15 -8
  27. package/dist/features/stores/components/StoreProductsListing.js +18 -13
  28. package/dist/react/contexts/SessionContext.d.ts +2 -0
  29. package/dist/react/hooks/useAuthGate.d.ts +8 -0
  30. package/dist/react/hooks/useAuthGate.js +35 -0
  31. package/dist/server.d.ts +4 -0
  32. package/dist/server.js +6 -0
  33. package/dist/tailwind-utilities.css +1 -1
  34. package/package.json +1 -1
@@ -2,12 +2,14 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useCallback, useState } from "react";
4
4
  import { useRouter } from "next/navigation";
5
- import { Button, Stack, Text, useToast } from "../../../ui";
5
+ import { Button, Stack, Text, useToast, LoginRequiredModal } from "../../../ui";
6
6
  import { ROUTES } from "../../../next";
7
7
  import { formatCurrency } from "../../../utils/number.formatter";
8
8
  import { useGuestCart } from "../../cart/hooks/useGuestCart";
9
9
  import { pushCartOp } from "../../cart/utils/pending-ops";
10
10
  import { NonRefundableConsentModal } from "./NonRefundableConsentModal";
11
+ import { useAuthGate } from "../../../react/hooks/useAuthGate";
12
+ import { ACTION_ID } from "../constants/action-defs";
11
13
  /**
12
14
  * Client buy panel for a prize-draw detail page (SB4-G).
13
15
  *
@@ -19,13 +21,14 @@ export function PrizeDrawEntryActions({ productId, productSlug, title, thumb, pr
19
21
  const router = useRouter();
20
22
  const cart = useGuestCart();
21
23
  const { showToast } = useToast();
24
+ const { requireAuth, modalOpen, modalMessage, closeModal } = useAuthGate();
22
25
  const [consentOpen, setConsentOpen] = useState(false);
23
26
  const closed = revealStatus === "closed" || remainingEntries === 0;
24
27
  const handleEnter = useCallback(() => {
25
28
  if (closed)
26
29
  return;
27
- setConsentOpen(true);
28
- }, [closed]);
30
+ requireAuth(ACTION_ID.ENTER_PRIZE_DRAW, () => setConsentOpen(true));
31
+ }, [closed, requireAuth]);
29
32
  const handleConfirm = useCallback(() => {
30
33
  cart.add(productId, 1, {
31
34
  productTitle: title,
@@ -44,5 +47,5 @@ export function PrizeDrawEntryActions({ productId, productSlug, title, thumb, pr
44
47
  showToast("Entry added to cart", "success");
45
48
  router.push(String(ROUTES.USER.CART));
46
49
  }, [cart, productId, title, thumb, pricePerEntry, router, showToast]);
47
- return (_jsxs(Stack, { gap: "md", children: [_jsxs(Text, { className: "text-2xl font-bold text-zinc-900 dark:text-zinc-50", children: [formatCurrency(pricePerEntry, currency), _jsx(Text, { as: "span", className: "ml-1 text-xs font-normal text-[var(--appkit-color-text-muted)]", children: "per entry" })] }), _jsx(Button, { variant: "primary", size: "md", className: "w-full", disabled: closed, onClick: handleEnter, children: closed ? "Draw closed" : "Enter draw" }), prizeGithubFileUrl ? (_jsx("a", { href: prizeGithubFileUrl, target: "_blank", rel: "noopener noreferrer", className: "text-center text-xs font-medium text-primary-600 underline-offset-4 hover:underline dark:text-primary-400", children: "View RNG source code on GitHub \u2192" })) : null, _jsx(Text, { className: "text-center text-xs text-[var(--appkit-color-text-muted)]", children: "Winners chosen by Node.js crypto.randomInt \u2014 fully auditable. Entries are locked once paid; refunds only if the prize pool is exhausted." }), _jsx(NonRefundableConsentModal, { open: consentOpen, listingType: "prize-draw", itemTitle: title, priceLabel: formatCurrency(pricePerEntry, currency), onCancel: () => setConsentOpen(false), onConfirm: handleConfirm })] }));
50
+ return (_jsxs(Stack, { gap: "md", children: [_jsxs(Text, { className: "text-2xl font-bold text-zinc-900 dark:text-zinc-50", children: [formatCurrency(pricePerEntry, currency), _jsx(Text, { as: "span", className: "ml-1 text-xs font-normal text-[var(--appkit-color-text-muted)]", children: "per entry" })] }), _jsx(Button, { variant: "primary", size: "md", className: "w-full", disabled: closed, onClick: handleEnter, children: closed ? "Draw closed" : "Enter draw" }), prizeGithubFileUrl ? (_jsx("a", { href: prizeGithubFileUrl, target: "_blank", rel: "noopener noreferrer", className: "text-center text-xs font-medium text-primary-600 underline-offset-4 hover:underline dark:text-primary-400", children: "View RNG source code on GitHub \u2192" })) : null, _jsx(Text, { className: "text-center text-xs text-[var(--appkit-color-text-muted)]", children: "Winners chosen by Node.js crypto.randomInt \u2014 fully auditable. Entries are locked once paid; refunds only if the prize pool is exhausted." }), _jsx(NonRefundableConsentModal, { open: consentOpen, listingType: "prize-draw", itemTitle: title, priceLabel: formatCurrency(pricePerEntry, currency), onCancel: () => setConsentOpen(false), onConfirm: handleConfirm }), _jsx(LoginRequiredModal, { isOpen: modalOpen, onClose: closeModal, message: modalMessage })] }));
48
51
  }
@@ -81,7 +81,7 @@ export function ProductCard({ product, href, onClick, onAddToWishlist, isWishlis
81
81
  e.stopPropagation();
82
82
  e.preventDefault();
83
83
  onAddToCart(product);
84
- }, className: "flex items-center justify-center gap-1 rounded-xl border-2 border-primary/40 bg-primary/5 py-2 text-xs font-semibold text-primary hover:bg-primary/10 hover:border-primary/60 active:scale-[0.97] transition-all duration-150 dark:text-primary-400 dark:border-primary-400/50 dark:bg-primary/[0.08] dark:hover:bg-primary/[0.15] dark:hover:border-primary-400/70", children: [_jsx("svg", { className: "h-3.5 w-3.5 flex-shrink-0", fill: "none", stroke: "currentColor", strokeWidth: 2, viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M3 3h2l.4 2M7 13h10l4-8H5.4m1.6 8L5 3H3m4 10v7a1 1 0 001 1h8a1 1 0 001-1v-7M9 21h6" }) }), "Cart"] }))] }))] })] })] }));
84
+ }, className: "flex items-center justify-center gap-1 rounded-xl border-2 border-primary/40 bg-primary/5 py-2 text-xs font-semibold text-zinc-800 hover:bg-primary/10 hover:border-primary/60 active:scale-[0.97] transition-all duration-150 dark:text-zinc-100 dark:border-primary-400/50 dark:bg-primary/[0.08] dark:hover:bg-primary/[0.15] dark:hover:border-primary-400/70", children: [_jsx("svg", { className: "h-3.5 w-3.5 flex-shrink-0", fill: "none", stroke: "currentColor", strokeWidth: 2, viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M3 3h2l.4 2M7 13h10l4-8H5.4m1.6 8L5 3H3m4 10v7a1 1 0 001 1h8a1 1 0 001-1v-7M9 21h6" }) }), "Cart"] }))] }))] })] })] }));
85
85
  if (href && !selectionMode) {
86
86
  return (_jsx(Link, { href: href, className: "block h-full", children: cardBody }));
87
87
  }
@@ -5,7 +5,8 @@ import { X, ShoppingCart, Heart, SlidersHorizontal, Columns } from "lucide-react
5
5
  import { useRouter } from "next/navigation";
6
6
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
7
7
  import { useProducts } from "../hooks/useProducts";
8
- import { Pagination, useToast, BulkActionBar, ListingToolbar } from "../../../ui";
8
+ import { Pagination, useToast, BulkActionBar, ListingToolbar, LoginRequiredModal } from "../../../ui";
9
+ import { useAuthGate } from "../../../react/hooks/useAuthGate";
9
10
  import { ACTION_ID, ACTION_META, COMPARE_MAX_ITEMS } from "../constants/action-defs";
10
11
  import { CompareOverlay } from "./CompareOverlay";
11
12
  import { ROUTES } from "../../../next";
@@ -24,6 +25,7 @@ export function ProductsIndexListing({ initialData }) {
24
25
  const router = useRouter();
25
26
  const table = useUrlTable({ defaults: { pageSize: "24", sort: DEFAULT_SORT } });
26
27
  const { showToast } = useToast();
28
+ const { requireAuth, modalOpen, modalMessage, closeModal } = useAuthGate();
27
29
  const [searchInput, setSearchInput] = useState(table.get(TABLE_KEYS.QUERY) || "");
28
30
  const [filterOpen, setFilterOpen] = useState(false);
29
31
  const [compareIds, setCompareIds] = useState([]);
@@ -123,17 +125,19 @@ export function ProductsIndexListing({ initialData }) {
123
125
  };
124
126
  const handleWishlistToggle = useCallback((productId) => {
125
127
  const isWishlisted = wishlistedIds.has(productId);
126
- if (isWishlisted) {
127
- localWishlist.remove(productId, "product");
128
- pushWishlistOp({ op: "remove", itemId: productId, type: "product" });
129
- showToast("Removed from wishlist", "info");
130
- }
131
- else {
132
- localWishlist.add(productId, "product");
133
- pushWishlistOp({ op: "add", itemId: productId, type: "product" });
134
- showToast("Added to wishlist", "success");
135
- }
136
- }, [wishlistedIds, localWishlist, showToast]);
128
+ requireAuth(isWishlisted ? ACTION_ID.REMOVE_FROM_WISHLIST : ACTION_ID.ADD_TO_WISHLIST, () => {
129
+ if (isWishlisted) {
130
+ localWishlist.remove(productId, "product");
131
+ pushWishlistOp({ op: "remove", itemId: productId, type: "product" });
132
+ showToast("Removed from wishlist", "info");
133
+ }
134
+ else {
135
+ localWishlist.add(productId, "product");
136
+ pushWishlistOp({ op: "add", itemId: productId, type: "product" });
137
+ showToast("Added to wishlist", "success");
138
+ }
139
+ });
140
+ }, [wishlistedIds, localWishlist, showToast, requireAuth]);
137
141
  const handleAddToCart = useCallback((product) => {
138
142
  localCart.add(product.id, 1, {
139
143
  productTitle: product.title,
@@ -144,14 +148,16 @@ export function ProductsIndexListing({ initialData }) {
144
148
  showToast("Added to cart", "success");
145
149
  }, [localCart, showToast]);
146
150
  const handleBuyNow = useCallback((product) => {
147
- localCart.add(product.id, 1, {
148
- productTitle: product.title,
149
- productImage: product.mainImage,
150
- price: product.price,
151
+ requireAuth(ACTION_ID.BUY_NOW, () => {
152
+ localCart.add(product.id, 1, {
153
+ productTitle: product.title,
154
+ productImage: product.mainImage,
155
+ price: product.price,
156
+ });
157
+ pushCartOp({ op: "add", productId: product.id, quantity: 1, productTitle: product.title, productImage: product.mainImage, price: product.price });
158
+ router.push(String(ROUTES.USER.CART));
151
159
  });
152
- pushCartOp({ op: "add", productId: product.id, quantity: 1, productTitle: product.title, productImage: product.mainImage, price: product.price });
153
- router.push(String(ROUTES.USER.CART));
154
- }, [localCart, router]);
160
+ }, [localCart, router, requireAuth]);
155
161
  return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search products...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, onSearchKeyDown: handleSearchKeyDown, sortValue: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT, sortOptions: PRODUCT_PUBLIC_SORT_OPTIONS, onSortChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, view: view, onViewChange: handleViewToggle, onResetAll: resetAll, hasActiveState: hasActiveState, bulkMode: selection.isSelecting, bulkSelectedCount: selection.selectedCount, bulkTotalCount: products.length, onBulkSelectAll: selection.toggleAll, onBulkClear: selection.clearSelection, extra: _jsxs("label", { className: "flex items-center gap-1.5 cursor-pointer select-none shrink-0", children: [_jsx("span", { className: "hidden sm:inline text-xs text-zinc-600 dark:text-zinc-300 whitespace-nowrap", children: "Show sold" }), _jsx("button", { type: "button", role: "switch", "aria-checked": showSold, onClick: () => table.set(TABLE_KEYS.SHOW_SOLD, showSold ? "" : "true"), className: `relative inline-flex h-5 w-9 flex-shrink-0 items-center rounded-full transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-1 ${showSold ? "bg-primary" : "bg-zinc-300 dark:bg-slate-600"}`, children: _jsx("span", { className: `inline-block h-3.5 w-3.5 transform rounded-full bg-white shadow-sm transition-transform duration-200 ${showSold ? "translate-x-[19px]" : "translate-x-[3px]"}` }) })] }) }), _jsx(BulkActionBar, { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: [
156
162
  {
157
163
  id: ACTION_ID.ADD_TO_CART,
@@ -174,13 +180,15 @@ export function ProductsIndexListing({ initialData }) {
174
180
  icon: _jsx(Heart, { className: "h-3.5 w-3.5" }),
175
181
  variant: "secondary",
176
182
  onClick: () => {
177
- const selected = products.filter((p) => selection.selectedIdSet.has(p.id));
178
- selected.forEach((p) => {
179
- localWishlist.add(p.id, "product");
180
- pushWishlistOp({ op: "add", itemId: p.id, type: "product" });
183
+ requireAuth(ACTION_ID.ADD_TO_WISHLIST, () => {
184
+ const selected = products.filter((p) => selection.selectedIdSet.has(p.id));
185
+ selected.forEach((p) => {
186
+ localWishlist.add(p.id, "product");
187
+ pushWishlistOp({ op: "add", itemId: p.id, type: "product" });
188
+ });
189
+ showToast(`${selected.length} items added to wishlist`, "success");
190
+ selection.clearSelection();
181
191
  });
182
- showToast(`${selected.length} items added to wishlist`, "success");
183
- selection.clearSelection();
184
192
  },
185
193
  },
186
194
  {
@@ -197,5 +205,5 @@ export function ProductsIndexListing({ initialData }) {
197
205
  ] }), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: page, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsx("div", { className: "py-6", children: isLoading ? (_jsx("div", { className: "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-4", children: Array.from({ length: 10 }).map((_, i) => (_jsxs("div", { className: "rounded-xl border border-zinc-100 dark:border-slate-700 overflow-hidden animate-pulse", children: [_jsx("div", { className: "aspect-square bg-zinc-200 dark:bg-slate-700" }), _jsxs("div", { className: "p-3 space-y-2", children: [_jsx("div", { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-3/4" }), _jsx("div", { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-1/2" }), _jsx("div", { className: "h-4 bg-zinc-200 dark:bg-slate-700 rounded w-1/3" })] })] }, i))) })) : (_jsx(ProductGrid, { products: products, getProductHref: (p) => String(ROUTES.PUBLIC.PRODUCT_DETAIL(p.slug || p.id)), view: view === "grid" ? "card" : "list", onWishlistToggle: handleWishlistToggle, wishlistedIds: wishlistedIds, onAddToCart: handleAddToCart, onBuyNow: handleBuyNow, selectionMode: selection.isSelecting, selectedIds: selection.selectedIdSet, onToggleSelect: selection.toggle })) }), _jsx(CompareOverlay, { isOpen: compareIds.length > 0, productIds: compareIds, productType: "product", onClose: () => {
198
206
  setCompareIds([]);
199
207
  selection.clearSelection();
200
- }, onRemove: (id) => setCompareIds((ids) => ids.filter((i) => i !== id)) }), filterOpen && (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 z-40 bg-black/40", "aria-hidden": "true", onClick: () => setFilterOpen(false) }), _jsxs("div", { className: "fixed inset-y-0 left-0 z-50 flex w-80 flex-col bg-white dark:bg-slate-900 shadow-2xl", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: [_jsxs("span", { className: "flex items-center gap-2 text-base font-semibold text-zinc-900 dark:text-zinc-100", children: [_jsx(SlidersHorizontal, { className: "h-4 w-4" }), "Filters"] }), _jsxs("div", { className: "flex items-center gap-2", children: [pendingFilterCount > 0 && (_jsx("button", { type: "button", onClick: clearFilters, className: "text-xs text-zinc-500 hover:text-rose-500 dark:text-zinc-400 transition-colors", children: "Clear all" })), _jsx("button", { type: "button", onClick: () => setFilterOpen(false), "aria-label": "Close filters", className: "rounded-lg p-1.5 text-zinc-500 hover:bg-zinc-100 dark:hover:bg-slate-800 transition-colors", children: _jsx(X, { className: "h-5 w-5" }) })] })] }), _jsx("div", { className: "flex-1 overflow-y-auto px-4 py-4", children: _jsx(ProductFilters, { table: pendingTable, currencyPrefix: "\u20B9", categoryOptions: categoryOptions }) }), _jsx("div", { className: "border-t border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: _jsxs("button", { type: "button", onClick: applyFilters, className: "w-full rounded-lg bg-primary py-2.5 text-sm font-semibold text-white hover:bg-primary-600 transition-colors active:scale-[0.98]", children: ["Apply Filters", pendingFilterCount > 0 ? ` (${pendingFilterCount})` : ""] }) })] })] }))] }));
208
+ }, onRemove: (id) => setCompareIds((ids) => ids.filter((i) => i !== id)) }), filterOpen && (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 z-40 bg-black/40", "aria-hidden": "true", onClick: () => setFilterOpen(false) }), _jsxs("div", { className: "fixed inset-y-0 left-0 z-50 flex w-80 flex-col bg-white dark:bg-slate-900 shadow-2xl", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: [_jsxs("span", { className: "flex items-center gap-2 text-base font-semibold text-zinc-900 dark:text-zinc-100", children: [_jsx(SlidersHorizontal, { className: "h-4 w-4" }), "Filters"] }), _jsxs("div", { className: "flex items-center gap-2", children: [pendingFilterCount > 0 && (_jsx("button", { type: "button", onClick: clearFilters, className: "text-xs text-zinc-500 hover:text-rose-500 dark:text-zinc-400 transition-colors", children: "Clear all" })), _jsx("button", { type: "button", onClick: () => setFilterOpen(false), "aria-label": "Close filters", className: "rounded-lg p-1.5 text-zinc-500 hover:bg-zinc-100 dark:hover:bg-slate-800 transition-colors", children: _jsx(X, { className: "h-5 w-5" }) })] })] }), _jsx("div", { className: "flex-1 overflow-y-auto px-4 py-4", children: _jsx(ProductFilters, { table: pendingTable, currencyPrefix: "\u20B9", categoryOptions: categoryOptions }) }), _jsx("div", { className: "border-t border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: _jsxs("button", { type: "button", onClick: applyFilters, className: "w-full rounded-lg bg-primary py-2.5 text-sm font-semibold text-white hover:bg-primary-600 transition-colors active:scale-[0.98]", children: ["Apply Filters", pendingFilterCount > 0 ? ` (${pendingFilterCount})` : ""] }) })] })] })), _jsx(LoginRequiredModal, { isOpen: modalOpen, onClose: closeModal, message: modalMessage })] }));
201
209
  }
@@ -4,7 +4,9 @@ import { useState, useCallback, useMemo } from "react";
4
4
  import { SlidersHorizontal, X } from "lucide-react";
5
5
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
6
6
  import { useProducts } from "../../products/hooks/useProducts";
7
- import { Pagination, useToast, ListingToolbar } from "../../../ui";
7
+ import { Pagination, useToast, ListingToolbar, LoginRequiredModal } from "../../../ui";
8
+ import { useAuthGate } from "../../../react/hooks/useAuthGate";
9
+ import { ACTION_ID } from "../../products/constants/action-defs";
8
10
  import { MarketplaceAuctionGrid } from "../../auctions/components/MarketplaceAuctionGrid";
9
11
  import { ProductFilters } from "../../products/components/ProductFilters";
10
12
  import { getDefaultCurrency } from "../../../core/baseline-resolver";
@@ -21,6 +23,7 @@ const FILTER_KEYS = ["minPrice", "maxPrice"];
21
23
  export function StoreAuctionsListing({ storeId, initialData }) {
22
24
  const table = useUrlTable({ defaults: { pageSize: "24", sort: "auctionEndDate" } });
23
25
  const { showToast } = useToast();
26
+ const { requireAuth, modalOpen, modalMessage, closeModal } = useAuthGate();
24
27
  const [searchInput, setSearchInput] = useState(table.get("q") || "");
25
28
  const [filterOpen, setFilterOpen] = useState(false);
26
29
  const [view, setView] = useState(table.get("view") || "grid");
@@ -113,20 +116,24 @@ export function StoreAuctionsListing({ storeId, initialData }) {
113
116
  }, [searchInput, table]);
114
117
  const wishlistActions = {
115
118
  addToWishlist: (productId) => {
116
- localWishlist.add(productId, "auction");
117
- pushWishlistOp({ op: "add", itemId: productId, type: "auction" });
118
- showToast("Added to wishlist", "success");
119
+ requireAuth(ACTION_ID.WATCH_AUCTION, () => {
120
+ localWishlist.add(productId, "auction");
121
+ pushWishlistOp({ op: "add", itemId: productId, type: "auction" });
122
+ showToast("Added to wishlist", "success");
123
+ });
119
124
  return Promise.resolve();
120
125
  },
121
126
  removeFromWishlist: (productId) => {
122
- localWishlist.remove(productId, "auction");
123
- pushWishlistOp({ op: "remove", itemId: productId, type: "auction" });
124
- showToast("Removed from wishlist", "info");
127
+ requireAuth(ACTION_ID.UNWATCH_AUCTION, () => {
128
+ localWishlist.remove(productId, "auction");
129
+ pushWishlistOp({ op: "remove", itemId: productId, type: "auction" });
130
+ showToast("Removed from wishlist", "info");
131
+ });
125
132
  return Promise.resolve();
126
133
  },
127
134
  isWishlisted: (productId) => wishlistedIds.has(productId),
128
135
  };
129
136
  const gridClass = "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-4";
130
137
  return (_jsxs("div", { className: "min-h-[200px]", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search store auctions...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || "auctionEndDate", sortOptions: AUCTION_SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, view: view, onViewChange: (v) => { if (v === "table")
131
- return; setView(v); table.set("view", v); }, onResetAll: resetAll, hasActiveState: hasActiveState }), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: page, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsx("div", { className: "py-6", children: isLoading ? (_jsx("div", { className: gridClass, children: Array.from({ length: 8 }).map((_, i) => (_jsxs("div", { className: "rounded-xl border border-zinc-100 dark:border-slate-700 overflow-hidden animate-pulse", children: [_jsx("div", { className: "aspect-square bg-zinc-200 dark:bg-slate-700" }), _jsxs("div", { className: "p-3 space-y-2", children: [_jsx("div", { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-3/4" }), _jsx("div", { className: "h-4 bg-zinc-200 dark:bg-slate-700 rounded w-1/2" }), _jsx("div", { className: "h-8 bg-zinc-200 dark:bg-slate-700 rounded" })] })] }, i))) })) : (_jsx(MarketplaceAuctionGrid, { auctions: auctions, variant: view === "list" ? "list" : "grid", gridClassName: view === "list" ? "flex flex-col gap-4" : gridClass, wishlistActions: wishlistActions, labels: { emptyTitle: "No auctions yet", emptyDescription: "This store has no active auctions." } })) }), filterOpen && (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 z-40 bg-black/40", "aria-hidden": "true", onClick: () => setFilterOpen(false) }), _jsxs("div", { className: "fixed inset-y-0 left-0 z-50 flex w-80 flex-col bg-white dark:bg-slate-900 shadow-2xl", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: [_jsxs("span", { className: "flex items-center gap-2 text-base font-semibold text-zinc-900 dark:text-zinc-100", children: [_jsx(SlidersHorizontal, { className: "h-4 w-4" }), "Filters"] }), _jsxs("div", { className: "flex items-center gap-2", children: [activeFilterCount > 0 && (_jsx("button", { type: "button", onClick: clearFilters, className: "text-xs text-zinc-500 hover:text-rose-500 dark:text-zinc-400 transition-colors", children: "Clear all" })), _jsx("button", { type: "button", onClick: () => setFilterOpen(false), "aria-label": "Close filters", className: "rounded-lg p-1.5 text-zinc-500 hover:bg-zinc-100 dark:hover:bg-slate-800 transition-colors", children: _jsx(X, { className: "h-5 w-5" }) })] })] }), _jsx("div", { className: "flex-1 overflow-y-auto px-4 py-4", children: _jsx(ProductFilters, { table: pendingTable, currencyPrefix: "\u20B9" }) }), _jsx("div", { className: "border-t border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: _jsxs("button", { type: "button", onClick: applyFilters, className: "w-full rounded-lg bg-primary py-2.5 text-sm font-semibold text-white hover:bg-primary-600 transition-colors active:scale-[0.98]", children: ["Apply Filters", activeFilterCount > 0 ? ` (${activeFilterCount})` : ""] }) })] })] }))] }));
138
+ return; setView(v); table.set("view", v); }, onResetAll: resetAll, hasActiveState: hasActiveState }), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: page, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsx("div", { className: "py-6", children: isLoading ? (_jsx("div", { className: gridClass, children: Array.from({ length: 8 }).map((_, i) => (_jsxs("div", { className: "rounded-xl border border-zinc-100 dark:border-slate-700 overflow-hidden animate-pulse", children: [_jsx("div", { className: "aspect-square bg-zinc-200 dark:bg-slate-700" }), _jsxs("div", { className: "p-3 space-y-2", children: [_jsx("div", { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-3/4" }), _jsx("div", { className: "h-4 bg-zinc-200 dark:bg-slate-700 rounded w-1/2" }), _jsx("div", { className: "h-8 bg-zinc-200 dark:bg-slate-700 rounded" })] })] }, i))) })) : (_jsx(MarketplaceAuctionGrid, { auctions: auctions, variant: view === "list" ? "list" : "grid", gridClassName: view === "list" ? "flex flex-col gap-4" : gridClass, wishlistActions: wishlistActions, labels: { emptyTitle: "No auctions yet", emptyDescription: "This store has no active auctions." } })) }), filterOpen && (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 z-40 bg-black/40", "aria-hidden": "true", onClick: () => setFilterOpen(false) }), _jsxs("div", { className: "fixed inset-y-0 left-0 z-50 flex w-80 flex-col bg-white dark:bg-slate-900 shadow-2xl", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: [_jsxs("span", { className: "flex items-center gap-2 text-base font-semibold text-zinc-900 dark:text-zinc-100", children: [_jsx(SlidersHorizontal, { className: "h-4 w-4" }), "Filters"] }), _jsxs("div", { className: "flex items-center gap-2", children: [activeFilterCount > 0 && (_jsx("button", { type: "button", onClick: clearFilters, className: "text-xs text-zinc-500 hover:text-rose-500 dark:text-zinc-400 transition-colors", children: "Clear all" })), _jsx("button", { type: "button", onClick: () => setFilterOpen(false), "aria-label": "Close filters", className: "rounded-lg p-1.5 text-zinc-500 hover:bg-zinc-100 dark:hover:bg-slate-800 transition-colors", children: _jsx(X, { className: "h-5 w-5" }) })] })] }), _jsx("div", { className: "flex-1 overflow-y-auto px-4 py-4", children: _jsx(ProductFilters, { table: pendingTable, currencyPrefix: "\u20B9" }) }), _jsx("div", { className: "border-t border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: _jsxs("button", { type: "button", onClick: applyFilters, className: "w-full rounded-lg bg-primary py-2.5 text-sm font-semibold text-white hover:bg-primary-600 transition-colors active:scale-[0.98]", children: ["Apply Filters", activeFilterCount > 0 ? ` (${activeFilterCount})` : ""] }) })] })] })), _jsx(LoginRequiredModal, { isOpen: modalOpen, onClose: closeModal, message: modalMessage })] }));
132
139
  }
@@ -4,7 +4,9 @@ import { useState, useCallback, useMemo } from "react";
4
4
  import { SlidersHorizontal, X } from "lucide-react";
5
5
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
6
6
  import { useProducts } from "../../products/hooks/useProducts";
7
- import { Pagination, useToast, ListingToolbar } from "../../../ui";
7
+ import { Pagination, useToast, ListingToolbar, LoginRequiredModal } from "../../../ui";
8
+ import { useAuthGate } from "../../../react/hooks/useAuthGate";
9
+ import { ACTION_ID } from "../../products/constants/action-defs";
8
10
  import { ROUTES } from "../../../next";
9
11
  import { ProductGrid } from "../../products/components/ProductGrid";
10
12
  import { ProductFilters, PRODUCT_PUBLIC_SORT_OPTIONS } from "../../products/components/ProductFilters";
@@ -15,6 +17,7 @@ const FILTER_KEYS = ["condition", "brand", "minPrice", "maxPrice"];
15
17
  export function StoreProductsListing({ storeId, initialData }) {
16
18
  const table = useUrlTable({ defaults: { pageSize: "24", sort: "-createdAt" } });
17
19
  const { showToast } = useToast();
20
+ const { requireAuth, modalOpen, modalMessage, closeModal } = useAuthGate();
18
21
  const [searchInput, setSearchInput] = useState(table.get("q") || "");
19
22
  const [filterOpen, setFilterOpen] = useState(false);
20
23
  const [view, setView] = useState(table.get("view") || "card");
@@ -96,17 +99,19 @@ export function StoreProductsListing({ storeId, initialData }) {
96
99
  };
97
100
  const handleWishlistToggle = useCallback((productId) => {
98
101
  const isWishlisted = wishlistedIds.has(productId);
99
- if (isWishlisted) {
100
- localWishlist.remove(productId, "product");
101
- pushWishlistOp({ op: "remove", itemId: productId, type: "product" });
102
- showToast("Removed from wishlist", "info");
103
- }
104
- else {
105
- localWishlist.add(productId, "product");
106
- pushWishlistOp({ op: "add", itemId: productId, type: "product" });
107
- showToast("Added to wishlist", "success");
108
- }
109
- }, [wishlistedIds, localWishlist, showToast]);
102
+ requireAuth(isWishlisted ? ACTION_ID.REMOVE_FROM_WISHLIST : ACTION_ID.ADD_TO_WISHLIST, () => {
103
+ if (isWishlisted) {
104
+ localWishlist.remove(productId, "product");
105
+ pushWishlistOp({ op: "remove", itemId: productId, type: "product" });
106
+ showToast("Removed from wishlist", "info");
107
+ }
108
+ else {
109
+ localWishlist.add(productId, "product");
110
+ pushWishlistOp({ op: "add", itemId: productId, type: "product" });
111
+ showToast("Added to wishlist", "success");
112
+ }
113
+ });
114
+ }, [wishlistedIds, localWishlist, showToast, requireAuth]);
110
115
  const handleAddToCart = useCallback((product) => {
111
116
  localCart.add(product.id, 1, {
112
117
  productTitle: product.title,
@@ -123,5 +128,5 @@ export function StoreProductsListing({ storeId, initialData }) {
123
128
  });
124
129
  showToast("Added to cart", "success");
125
130
  }, [localCart, showToast]);
126
- return (_jsxs("div", { className: "min-h-[200px]", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search store products...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || "-createdAt", sortOptions: PRODUCT_PUBLIC_SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, view: view === "card" ? "grid" : "list", onViewChange: (v) => handleViewToggle(v === "grid" ? "card" : "list"), onResetAll: resetAll, hasActiveState: hasActiveState }), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: page, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsx("div", { className: "py-6", children: isLoading ? (_jsx("div", { className: "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-6", children: Array.from({ length: 8 }).map((_, i) => (_jsxs("div", { className: "rounded-xl border border-zinc-100 dark:border-slate-700 overflow-hidden animate-pulse", children: [_jsx("div", { className: "aspect-square bg-zinc-200 dark:bg-slate-700" }), _jsxs("div", { className: "p-3 space-y-2", children: [_jsx("div", { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-3/4" }), _jsx("div", { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-1/2" }), _jsx("div", { className: "h-4 bg-zinc-200 dark:bg-slate-700 rounded w-1/3" })] })] }, i))) })) : (_jsx(ProductGrid, { products: products, getProductHref: (p) => String(ROUTES.PUBLIC.PRODUCT_DETAIL(p.slug || p.id)), view: view, emptyLabel: "This store has no products yet.", onWishlistToggle: handleWishlistToggle, wishlistedIds: wishlistedIds, onAddToCart: handleAddToCart })) }), filterOpen && (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 z-40 bg-black/40", "aria-hidden": "true", onClick: () => setFilterOpen(false) }), _jsxs("div", { className: "fixed inset-y-0 left-0 z-50 flex w-80 flex-col bg-white dark:bg-slate-900 shadow-2xl", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: [_jsxs("span", { className: "flex items-center gap-2 text-base font-semibold text-zinc-900 dark:text-zinc-100", children: [_jsx(SlidersHorizontal, { className: "h-4 w-4" }), "Filters"] }), _jsxs("div", { className: "flex items-center gap-2", children: [activeFilterCount > 0 && (_jsx("button", { type: "button", onClick: clearFilters, className: "text-xs text-zinc-500 hover:text-rose-500 dark:text-zinc-400 transition-colors", children: "Clear all" })), _jsx("button", { type: "button", onClick: () => setFilterOpen(false), "aria-label": "Close filters", className: "rounded-lg p-1.5 text-zinc-500 hover:bg-zinc-100 dark:hover:bg-slate-800 transition-colors", children: _jsx(X, { className: "h-5 w-5" }) })] })] }), _jsx("div", { className: "flex-1 overflow-y-auto px-4 py-4", children: _jsx(ProductFilters, { table: pendingTable, currencyPrefix: "\u20B9" }) }), _jsx("div", { className: "border-t border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: _jsxs("button", { type: "button", onClick: applyFilters, className: "w-full rounded-lg bg-primary py-2.5 text-sm font-semibold text-white hover:bg-primary-600 transition-colors active:scale-[0.98]", children: ["Apply Filters", activeFilterCount > 0 ? ` (${activeFilterCount})` : ""] }) })] })] }))] }));
131
+ return (_jsxs("div", { className: "min-h-[200px]", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search store products...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || "-createdAt", sortOptions: PRODUCT_PUBLIC_SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, view: view === "card" ? "grid" : "list", onViewChange: (v) => handleViewToggle(v === "grid" ? "card" : "list"), onResetAll: resetAll, hasActiveState: hasActiveState }), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: page, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsx("div", { className: "py-6", children: isLoading ? (_jsx("div", { className: "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-6", children: Array.from({ length: 8 }).map((_, i) => (_jsxs("div", { className: "rounded-xl border border-zinc-100 dark:border-slate-700 overflow-hidden animate-pulse", children: [_jsx("div", { className: "aspect-square bg-zinc-200 dark:bg-slate-700" }), _jsxs("div", { className: "p-3 space-y-2", children: [_jsx("div", { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-3/4" }), _jsx("div", { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-1/2" }), _jsx("div", { className: "h-4 bg-zinc-200 dark:bg-slate-700 rounded w-1/3" })] })] }, i))) })) : (_jsx(ProductGrid, { products: products, getProductHref: (p) => String(ROUTES.PUBLIC.PRODUCT_DETAIL(p.slug || p.id)), view: view, emptyLabel: "This store has no products yet.", onWishlistToggle: handleWishlistToggle, wishlistedIds: wishlistedIds, onAddToCart: handleAddToCart })) }), filterOpen && (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 z-40 bg-black/40", "aria-hidden": "true", onClick: () => setFilterOpen(false) }), _jsxs("div", { className: "fixed inset-y-0 left-0 z-50 flex w-80 flex-col bg-white dark:bg-slate-900 shadow-2xl", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: [_jsxs("span", { className: "flex items-center gap-2 text-base font-semibold text-zinc-900 dark:text-zinc-100", children: [_jsx(SlidersHorizontal, { className: "h-4 w-4" }), "Filters"] }), _jsxs("div", { className: "flex items-center gap-2", children: [activeFilterCount > 0 && (_jsx("button", { type: "button", onClick: clearFilters, className: "text-xs text-zinc-500 hover:text-rose-500 dark:text-zinc-400 transition-colors", children: "Clear all" })), _jsx("button", { type: "button", onClick: () => setFilterOpen(false), "aria-label": "Close filters", className: "rounded-lg p-1.5 text-zinc-500 hover:bg-zinc-100 dark:hover:bg-slate-800 transition-colors", children: _jsx(X, { className: "h-5 w-5" }) })] })] }), _jsx("div", { className: "flex-1 overflow-y-auto px-4 py-4", children: _jsx(ProductFilters, { table: pendingTable, currencyPrefix: "\u20B9" }) }), _jsx("div", { className: "border-t border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: _jsxs("button", { type: "button", onClick: applyFilters, className: "w-full rounded-lg bg-primary py-2.5 text-sm font-semibold text-white hover:bg-primary-600 transition-colors active:scale-[0.98]", children: ["Apply Filters", activeFilterCount > 0 ? ` (${activeFilterCount})` : ""] }) })] })] })), _jsx(LoginRequiredModal, { isOpen: modalOpen, onClose: closeModal, message: modalMessage })] }));
127
132
  }
@@ -30,6 +30,8 @@ export interface SessionUser {
30
30
  stats?: Record<string, unknown>;
31
31
  metadata?: Record<string, unknown>;
32
32
  scamAwarenessAcknowledgedAt?: Date | null;
33
+ /** RBAC permission keys granted to this user (from their role's permission set). */
34
+ permissions?: string[];
33
35
  }
34
36
  export interface SessionContextValue {
35
37
  user: SessionUser | null;
@@ -0,0 +1,8 @@
1
+ import type { ActionId } from "../../features/products/constants/action-defs";
2
+ export interface UseAuthGateReturn {
3
+ requireAuth: (actionId: ActionId, fn: () => void | Promise<void>) => void;
4
+ modalOpen: boolean;
5
+ modalMessage: string;
6
+ closeModal: () => void;
7
+ }
8
+ export declare function useAuthGate(): UseAuthGateReturn;
@@ -0,0 +1,35 @@
1
+ "use client";
2
+ import { useState, useCallback } from "react";
3
+ import { useAuth } from "../contexts/SessionContext";
4
+ import { useSiteSettings } from "../../core/hooks/useSiteSettings";
5
+ import { ACTION_META } from "../../features/products/constants/action-defs";
6
+ const DEFAULT_MSG = "You need to be signed in to continue.";
7
+ const DISABLED_MSG = "This action is currently unavailable.";
8
+ const PERMISSION_MSG = "You don't have permission to perform this action.";
9
+ export function useAuthGate() {
10
+ const { user } = useAuth();
11
+ const { data: settings } = useSiteSettings();
12
+ const [state, setState] = useState({ open: false, message: "" });
13
+ const requireAuth = useCallback((actionId, fn) => {
14
+ const meta = ACTION_META[actionId];
15
+ const enabled = settings
16
+ ?.actionConfig?.[actionId]?.enabled ??
17
+ meta?.defaultEnabled ??
18
+ true;
19
+ if (!enabled) {
20
+ setState({ open: true, message: DISABLED_MSG });
21
+ return;
22
+ }
23
+ if (meta?.requiresAuth && !user?.uid) {
24
+ setState({ open: true, message: meta.authMessage ?? DEFAULT_MSG });
25
+ return;
26
+ }
27
+ if (meta?.requiredPermission && !user?.permissions?.includes(meta.requiredPermission)) {
28
+ setState({ open: true, message: PERMISSION_MSG });
29
+ return;
30
+ }
31
+ void fn();
32
+ }, [user, settings]);
33
+ const closeModal = useCallback(() => setState((s) => ({ ...s, open: false })), []);
34
+ return { requireAuth, modalOpen: state.open, modalMessage: state.message, closeModal };
35
+ }
package/dist/server.d.ts CHANGED
@@ -487,3 +487,7 @@ export type { UserSoftBan } from "./features/auth/schemas/firestore";
487
487
  export type { BannedAction } from "./features/auth/permissions/constants";
488
488
  export { buildSitemap, buildRobots, buildManifest, buildDefaultOgImage, DEFAULT_OG_SIZE } from "./_internal/server/features/seo";
489
489
  export type { SitemapOptions, RobotsOptions, ManifestOptions, DefaultOgOptions } from "./_internal/server/features/seo";
490
+ export { checkActionAllowed } from "./features/admin/utils/checkActionAllowed";
491
+ export { getDisabledRoutes } from "./features/admin/utils/getDisabledRoutes";
492
+ export { getSiteSettingsGlobal } from "./features/admin/utils/getSiteSettingsGlobal";
493
+ export { updateActionConfigDomain, updateNavConfigDomain } from "./_internal/server/features/site-settings/actions";
package/dist/server.js CHANGED
@@ -1362,3 +1362,9 @@ export { getStoreCapabilities, storeHasCapability, } from "./_internal/server/fe
1362
1362
  export { isSoftBanned, getBanSummary } from "./features/auth/server/checkSoftBan";
1363
1363
  // -- SEO builders (sitemap / robots / manifest / og-image) --
1364
1364
  export { buildSitemap, buildRobots, buildManifest, buildDefaultOgImage, DEFAULT_OG_SIZE } from "./_internal/server/features/seo";
1365
+ // -- Action gate + nav route helpers (server-side) --
1366
+ export { checkActionAllowed } from "./features/admin/utils/checkActionAllowed";
1367
+ export { getDisabledRoutes } from "./features/admin/utils/getDisabledRoutes";
1368
+ export { getSiteSettingsGlobal } from "./features/admin/utils/getSiteSettingsGlobal";
1369
+ // -- Site-settings domain actions (updateActionConfig / updateNavConfig) --
1370
+ export { updateActionConfigDomain, updateNavConfigDomain } from "./_internal/server/features/site-settings/actions";