@mohasinac/appkit 2.7.18 → 2.7.20

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 (39) 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/seo/manifest.js +9 -3
  5. package/dist/_internal/server/features/site-settings/actions.d.ts +14 -0
  6. package/dist/_internal/server/features/site-settings/actions.js +29 -0
  7. package/dist/_internal/shared/features/layout/types.d.ts +11 -0
  8. package/dist/client.d.ts +2 -0
  9. package/dist/client.js +2 -0
  10. package/dist/contracts/auth.d.ts +2 -0
  11. package/dist/features/admin/schemas/firestore.d.ts +21 -0
  12. package/dist/features/admin/schemas/firestore.js +3 -0
  13. package/dist/features/admin/utils/checkActionAllowed.d.ts +4 -0
  14. package/dist/features/admin/utils/checkActionAllowed.js +21 -0
  15. package/dist/features/admin/utils/getDisabledRoutes.d.ts +2 -0
  16. package/dist/features/admin/utils/getDisabledRoutes.js +6 -0
  17. package/dist/features/admin/utils/getSiteSettingsGlobal.d.ts +2 -0
  18. package/dist/features/admin/utils/getSiteSettingsGlobal.js +4 -0
  19. package/dist/features/categories/components/CategoryProductsListing.js +18 -13
  20. package/dist/features/layout/AppLayoutShell.js +8 -1
  21. package/dist/features/layout/ListingLayout.js +1 -1
  22. package/dist/features/layout/MainNavbar.d.ts +4 -0
  23. package/dist/features/layout/NavItem.js +1 -1
  24. package/dist/features/pre-orders/components/PreOrdersIndexListing.js +22 -14
  25. package/dist/features/products/components/AuctionsIndexListing.js +19 -16
  26. package/dist/features/products/components/ProductGrid.js +1 -1
  27. package/dist/features/products/components/ProductsIndexListing.js +34 -26
  28. package/dist/features/products/constants/action-defs.d.ts +64 -22
  29. package/dist/features/products/constants/action-defs.js +101 -64
  30. package/dist/features/stores/components/StoreAuctionsListing.js +15 -8
  31. package/dist/features/stores/components/StoreProductsListing.js +18 -13
  32. package/dist/react/contexts/SessionContext.d.ts +2 -0
  33. package/dist/react/hooks/useAuthGate.d.ts +8 -0
  34. package/dist/react/hooks/useAuthGate.js +35 -0
  35. package/dist/seed/site-settings-seed-data.js +3 -0
  36. package/dist/server.d.ts +4 -0
  37. package/dist/server.js +6 -0
  38. package/dist/tailwind-utilities.css +1 -1
  39. package/package.json +1 -1
@@ -1,17 +1,14 @@
1
1
  /**
2
2
  * action-defs.ts
3
3
  *
4
- * Single source of truth for ALL action IDs, labels, variants, and groupings
5
- * used across the platform:
4
+ * Universal CTA registry for the entire platform.
5
+ * Every action public marketplace CTAs, dashboard row actions, quick actions — is
6
+ * registered here with auth requirements, RBAC permissions, and enable/disable defaults.
6
7
  *
7
- * Product detail pages — desktop action panel + mobile BuyBar
8
- * Listing pages BulkActionsBar selections
9
- * Table rows RowActionMenu items (admin / seller / user)
10
- * Form shells submit / cancel / save-draft / publish / delete
11
- * Dashboard quick bar — top-level shortcut buttons per dashboard type
12
- *
13
- * Pure TypeScript — no React, no JSX, no callbacks.
14
- * Consuming components resolve iconName → Lucide component, and supply onClick.
8
+ * Three enforcement layers read from this registry:
9
+ * Client useAuthGate() shows LoginRequiredModal for Tier 1 (public) actions
10
+ * Server checkActionAllowed() enforces the same guards on every server action
11
+ * Admin ActionPermissionsManager toggles defaultEnabled via siteSettings.actionConfig
15
12
  */
16
13
  // ─────────────────────────────────────────────────────────────────────────────
17
14
  // § 1 Product / listing actions
@@ -30,23 +27,73 @@ export const ACTION_ID = {
30
27
  PLACE_BID: "place-bid",
31
28
  BUY_NOW_AUCTION: "buy-now-auction",
32
29
  WATCH_AUCTION: "watch-auction",
30
+ UNWATCH_AUCTION: "unwatch-auction",
33
31
  // Pre-order
34
32
  RESERVE_NOW: "reserve-now",
35
33
  CANCEL_RESERVATION: "cancel-reservation",
34
+ // ── Checkout / navigation CTAs ───────────────────────────────────────────
35
+ CHECKOUT: "checkout",
36
+ ENTER_PRIZE_DRAW: "enter-prize-draw",
37
+ ENTER_RAFFLE: "enter-raffle",
38
+ REPORT_LISTING: "report-listing",
39
+ FOLLOW_STORE: "follow-store",
40
+ // ── Reviews & social ─────────────────────────────────────────────────────
41
+ WRITE_REVIEW: "write-review",
42
+ MESSAGE_SELLER: "message-seller",
43
+ // ── Seller (public-facing CTAs on store/product pages) ───────────────────
44
+ BECOME_SELLER: "become-seller",
45
+ REQUEST_PAYOUT: "request-payout",
46
+ RESPOND_TO_REVIEW: "respond-to-review",
47
+ // ── User account actions ─────────────────────────────────────────────────
48
+ CANCEL_ORDER: "cancel-order",
49
+ REQUEST_RETURN: "request-return",
50
+ REORDER: "reorder",
51
+ TRACK_ORDER: "track-order",
52
+ EDIT_PROFILE: "edit-profile",
53
+ ADD_ADDRESS: "add-address",
54
+ EDIT_ADDRESS: "edit-address",
55
+ DELETE_ADDRESS: "delete-address",
56
+ CHANGE_PASSWORD: "change-password",
57
+ DELETE_ACCOUNT: "delete-account",
36
58
  };
37
59
  export const ACTION_META = {
38
- [ACTION_ID.BUY_NOW]: { id: ACTION_ID.BUY_NOW, label: "Buy Now", variant: "primary" },
60
+ [ACTION_ID.BUY_NOW]: { id: ACTION_ID.BUY_NOW, label: "Buy Now", variant: "primary", requiresAuth: true, authMessage: "You need to be signed in to purchase items." },
39
61
  [ACTION_ID.ADD_TO_CART]: { id: ACTION_ID.ADD_TO_CART, label: "Add to Cart", variant: "secondary", iconName: "ShoppingCart" },
40
- [ACTION_ID.ADD_TO_WISHLIST]: { id: ACTION_ID.ADD_TO_WISHLIST, label: "Add to Wishlist", variant: "ghost", iconName: "Heart" },
41
- [ACTION_ID.REMOVE_FROM_WISHLIST]: { id: ACTION_ID.REMOVE_FROM_WISHLIST, label: "Remove from Wishlist", variant: "ghost", iconName: "HeartOff" },
42
- [ACTION_ID.MAKE_OFFER]: { id: ACTION_ID.MAKE_OFFER, label: "Make an Offer", variant: "outline", iconName: "Tag", requiresAuth: true },
62
+ [ACTION_ID.ADD_TO_WISHLIST]: { id: ACTION_ID.ADD_TO_WISHLIST, label: "Add to Wishlist", variant: "ghost", iconName: "Heart", requiresAuth: true, authMessage: "You need to be signed in to save items to your wishlist." },
63
+ [ACTION_ID.REMOVE_FROM_WISHLIST]: { id: ACTION_ID.REMOVE_FROM_WISHLIST, label: "Remove from Wishlist", variant: "ghost", iconName: "HeartOff", requiresAuth: true, authMessage: "You need to be signed in to manage your wishlist." },
64
+ [ACTION_ID.MAKE_OFFER]: { id: ACTION_ID.MAKE_OFFER, label: "Make an Offer", variant: "outline", iconName: "Tag", requiresAuth: true, authMessage: "You need to be signed in to make an offer." },
43
65
  [ACTION_ID.SHARE]: { id: ACTION_ID.SHARE, label: "Share", variant: "ghost", iconName: "Share2" },
44
66
  [ACTION_ID.COMPARE]: { id: ACTION_ID.COMPARE, label: "Compare", variant: "secondary", iconName: "Columns" },
45
- [ACTION_ID.PLACE_BID]: { id: ACTION_ID.PLACE_BID, label: "Place Bid", variant: "primary", requiresAuth: true },
46
- [ACTION_ID.BUY_NOW_AUCTION]: { id: ACTION_ID.BUY_NOW_AUCTION, label: "Buy Now", variant: "secondary", requiresAuth: true },
47
- [ACTION_ID.WATCH_AUCTION]: { id: ACTION_ID.WATCH_AUCTION, label: "Watch", variant: "ghost", iconName: "Eye" },
48
- [ACTION_ID.RESERVE_NOW]: { id: ACTION_ID.RESERVE_NOW, label: "Reserve Now", variant: "primary", requiresAuth: true },
49
- [ACTION_ID.CANCEL_RESERVATION]: { id: ACTION_ID.CANCEL_RESERVATION, label: "Cancel Reservation", variant: "danger", iconName: "X", requiresAuth: true },
67
+ [ACTION_ID.PLACE_BID]: { id: ACTION_ID.PLACE_BID, label: "Place Bid", variant: "primary", requiresAuth: true, authMessage: "You need to be signed in to place a bid." },
68
+ [ACTION_ID.BUY_NOW_AUCTION]: { id: ACTION_ID.BUY_NOW_AUCTION, label: "Buy Now", variant: "secondary", requiresAuth: true, authMessage: "You need to be signed in to purchase items." },
69
+ [ACTION_ID.WATCH_AUCTION]: { id: ACTION_ID.WATCH_AUCTION, label: "Watch", variant: "ghost", iconName: "Eye", requiresAuth: true, authMessage: "You need to be signed in to watch auctions." },
70
+ [ACTION_ID.UNWATCH_AUCTION]: { id: ACTION_ID.UNWATCH_AUCTION, label: "Unwatch", variant: "ghost", iconName: "EyeOff", requiresAuth: true, authMessage: "You need to be signed in to manage your watchlist." },
71
+ [ACTION_ID.RESERVE_NOW]: { id: ACTION_ID.RESERVE_NOW, label: "Reserve Now", variant: "primary", requiresAuth: true, authMessage: "You need to be signed in to reserve a pre-order." },
72
+ [ACTION_ID.CANCEL_RESERVATION]: { id: ACTION_ID.CANCEL_RESERVATION, label: "Cancel Reservation", variant: "danger", iconName: "X", requiresAuth: true, authMessage: "You need to be signed in to cancel a reservation." },
73
+ // ── Checkout / navigation CTAs ────────────────────────────────────────────
74
+ [ACTION_ID.CHECKOUT]: { id: ACTION_ID.CHECKOUT, label: "Checkout", variant: "primary", requiresAuth: true, authMessage: "You need to be signed in to checkout." },
75
+ [ACTION_ID.ENTER_PRIZE_DRAW]: { id: ACTION_ID.ENTER_PRIZE_DRAW, label: "Enter Draw", variant: "primary", requiresAuth: true, authMessage: "You need to be signed in to enter a prize draw." },
76
+ [ACTION_ID.ENTER_RAFFLE]: { id: ACTION_ID.ENTER_RAFFLE, label: "Enter Raffle", variant: "primary", requiresAuth: true, authMessage: "You need to be signed in to enter a raffle." },
77
+ [ACTION_ID.REPORT_LISTING]: { id: ACTION_ID.REPORT_LISTING, label: "Report Listing", variant: "ghost", requiresAuth: true, authMessage: "You need to be signed in to report a listing." },
78
+ [ACTION_ID.FOLLOW_STORE]: { id: ACTION_ID.FOLLOW_STORE, label: "Follow Store", variant: "outline", requiresAuth: true, authMessage: "You need to be signed in to follow a store." },
79
+ // ── Reviews & social ──────────────────────────────────────────────────────
80
+ [ACTION_ID.WRITE_REVIEW]: { id: ACTION_ID.WRITE_REVIEW, label: "Write a Review", variant: "outline", requiresAuth: true, authMessage: "You need to be signed in to write a review." },
81
+ [ACTION_ID.MESSAGE_SELLER]: { id: ACTION_ID.MESSAGE_SELLER, label: "Message Seller", variant: "outline", requiresAuth: true, authMessage: "You need to be signed in to message a seller." },
82
+ // ── Seller CTAs ───────────────────────────────────────────────────────────
83
+ [ACTION_ID.BECOME_SELLER]: { id: ACTION_ID.BECOME_SELLER, label: "Apply as Seller", variant: "primary", requiresAuth: true, authMessage: "You need to be signed in to apply as a seller." },
84
+ [ACTION_ID.REQUEST_PAYOUT]: { id: ACTION_ID.REQUEST_PAYOUT, label: "Request Payout", variant: "outline", requiresAuth: true, authMessage: "You need to be signed in to request a payout.", requiredPermission: "seller.payouts.request" },
85
+ [ACTION_ID.RESPOND_TO_REVIEW]: { id: ACTION_ID.RESPOND_TO_REVIEW, label: "Respond to Review", variant: "outline", requiresAuth: true, authMessage: "You need to be signed in to respond to a review.", requiredPermission: "seller.reviews.respond" },
86
+ // ── User account actions ──────────────────────────────────────────────────
87
+ [ACTION_ID.CANCEL_ORDER]: { id: ACTION_ID.CANCEL_ORDER, label: "Cancel Order", variant: "danger", requiresAuth: true, authMessage: "You need to be signed in to cancel an order." },
88
+ [ACTION_ID.REQUEST_RETURN]: { id: ACTION_ID.REQUEST_RETURN, label: "Request Return", variant: "outline", requiresAuth: true, authMessage: "You need to be signed in to request a return." },
89
+ [ACTION_ID.REORDER]: { id: ACTION_ID.REORDER, label: "Reorder", variant: "secondary", requiresAuth: true, authMessage: "You need to be signed in to reorder." },
90
+ [ACTION_ID.TRACK_ORDER]: { id: ACTION_ID.TRACK_ORDER, label: "Track Order", variant: "ghost", requiresAuth: true, authMessage: "You need to be signed in to track your order." },
91
+ [ACTION_ID.EDIT_PROFILE]: { id: ACTION_ID.EDIT_PROFILE, label: "Edit Profile", variant: "outline", requiresAuth: true, authMessage: "You need to be signed in to edit your profile." },
92
+ [ACTION_ID.ADD_ADDRESS]: { id: ACTION_ID.ADD_ADDRESS, label: "Add Address", variant: "outline", requiresAuth: true, authMessage: "You need to be signed in to add an address." },
93
+ [ACTION_ID.EDIT_ADDRESS]: { id: ACTION_ID.EDIT_ADDRESS, label: "Edit Address", variant: "ghost", requiresAuth: true, authMessage: "You need to be signed in to edit an address." },
94
+ [ACTION_ID.DELETE_ADDRESS]: { id: ACTION_ID.DELETE_ADDRESS, label: "Delete Address", variant: "danger", requiresAuth: true, authMessage: "You need to be signed in to delete an address." },
95
+ [ACTION_ID.CHANGE_PASSWORD]: { id: ACTION_ID.CHANGE_PASSWORD, label: "Change Password", variant: "outline", requiresAuth: true, authMessage: "You need to be signed in to change your password." },
96
+ [ACTION_ID.DELETE_ACCOUNT]: { id: ACTION_ID.DELETE_ACCOUNT, label: "Delete Account", variant: "danger", requiresAuth: true, authMessage: "You need to be signed in to delete your account.", requiredPermission: "user.account.delete" },
50
97
  };
51
98
  // Detail page action groups — ordered top-to-bottom in the right-hand panel
52
99
  export const DETAIL_ACTIONS = {
@@ -66,7 +113,7 @@ export const MOBILE_PRIMARY_ACTIONS = {
66
113
  // Listing page bulk actions — shown in BulkActionsBar when items are selected
67
114
  export const LISTING_BULK_ACTIONS = {
68
115
  products: [ACTION_ID.ADD_TO_CART, ACTION_ID.ADD_TO_WISHLIST, ACTION_ID.COMPARE],
69
- auctions: [ACTION_ID.WATCH_AUCTION, ACTION_ID.ADD_TO_WISHLIST, ACTION_ID.COMPARE],
116
+ auctions: [ACTION_ID.WATCH_AUCTION, ACTION_ID.UNWATCH_AUCTION, ACTION_ID.COMPARE],
70
117
  preorders: [ACTION_ID.ADD_TO_CART, ACTION_ID.ADD_TO_WISHLIST, ACTION_ID.COMPARE],
71
118
  stores: [ACTION_ID.COMPARE],
72
119
  };
@@ -96,23 +143,23 @@ export const ROW_ACTION_ID = {
96
143
  ARCHIVE: "row-archive",
97
144
  };
98
145
  export const ROW_ACTION_META = {
99
- [ROW_ACTION_ID.EDIT]: { id: ROW_ACTION_ID.EDIT, label: "Edit", iconName: "Pencil" },
100
- [ROW_ACTION_ID.VIEW]: { id: ROW_ACTION_ID.VIEW, label: "View", iconName: "Eye" },
101
- [ROW_ACTION_ID.DELETE]: { id: ROW_ACTION_ID.DELETE, label: "Delete", iconName: "Trash2", destructive: true, separator: true },
102
- [ROW_ACTION_ID.APPROVE]: { id: ROW_ACTION_ID.APPROVE, label: "Approve", iconName: "Check" },
103
- [ROW_ACTION_ID.REJECT]: { id: ROW_ACTION_ID.REJECT, label: "Reject", iconName: "X", destructive: true },
104
- [ROW_ACTION_ID.SUSPEND]: { id: ROW_ACTION_ID.SUSPEND, label: "Suspend", iconName: "Ban", destructive: true, separator: true },
105
- [ROW_ACTION_ID.RESTORE]: { id: ROW_ACTION_ID.RESTORE, label: "Restore", iconName: "RotateCcw" },
106
- [ROW_ACTION_ID.MANAGE]: { id: ROW_ACTION_ID.MANAGE, label: "Manage", iconName: "Settings" },
107
- [ROW_ACTION_ID.DUPLICATE]: { id: ROW_ACTION_ID.DUPLICATE, label: "Duplicate", iconName: "Copy" },
108
- [ROW_ACTION_ID.EXPORT]: { id: ROW_ACTION_ID.EXPORT, label: "Export", iconName: "Download" },
109
- [ROW_ACTION_ID.TRACK]: { id: ROW_ACTION_ID.TRACK, label: "Track Shipment", iconName: "Truck" },
110
- [ROW_ACTION_ID.CANCEL]: { id: ROW_ACTION_ID.CANCEL, label: "Cancel", iconName: "X", destructive: true },
111
- [ROW_ACTION_ID.REFUND]: { id: ROW_ACTION_ID.REFUND, label: "Refund", iconName: "RefreshCw", destructive: true, separator: true },
112
- [ROW_ACTION_ID.RESEND]: { id: ROW_ACTION_ID.RESEND, label: "Resend", iconName: "Send" },
113
- [ROW_ACTION_ID.REPLY]: { id: ROW_ACTION_ID.REPLY, label: "Reply", iconName: "MessageSquare" },
114
- [ROW_ACTION_ID.PUBLISH]: { id: ROW_ACTION_ID.PUBLISH, label: "Publish", iconName: "Upload" },
115
- [ROW_ACTION_ID.ARCHIVE]: { id: ROW_ACTION_ID.ARCHIVE, label: "Archive", iconName: "Archive", separator: true },
146
+ [ROW_ACTION_ID.EDIT]: { id: ROW_ACTION_ID.EDIT, label: "Edit", iconName: "Pencil", requiresAuth: true },
147
+ [ROW_ACTION_ID.VIEW]: { id: ROW_ACTION_ID.VIEW, label: "View", iconName: "Eye", requiresAuth: true },
148
+ [ROW_ACTION_ID.DELETE]: { id: ROW_ACTION_ID.DELETE, label: "Delete", iconName: "Trash2", requiresAuth: true, destructive: true, separator: true },
149
+ [ROW_ACTION_ID.APPROVE]: { id: ROW_ACTION_ID.APPROVE, label: "Approve", iconName: "Check", requiresAuth: true, requiredRole: "admin", requiredPermission: "admin.content.approve" },
150
+ [ROW_ACTION_ID.REJECT]: { id: ROW_ACTION_ID.REJECT, label: "Reject", iconName: "X", requiresAuth: true, requiredRole: "admin", requiredPermission: "admin.content.approve", destructive: true },
151
+ [ROW_ACTION_ID.SUSPEND]: { id: ROW_ACTION_ID.SUSPEND, label: "Suspend", iconName: "Ban", requiresAuth: true, requiredRole: "admin", requiredPermission: "admin.users.suspend", destructive: true, separator: true },
152
+ [ROW_ACTION_ID.RESTORE]: { id: ROW_ACTION_ID.RESTORE, label: "Restore", iconName: "RotateCcw", requiresAuth: true, requiredRole: "admin", requiredPermission: "admin.users.suspend" },
153
+ [ROW_ACTION_ID.MANAGE]: { id: ROW_ACTION_ID.MANAGE, label: "Manage", iconName: "Settings", requiresAuth: true },
154
+ [ROW_ACTION_ID.DUPLICATE]: { id: ROW_ACTION_ID.DUPLICATE, label: "Duplicate", iconName: "Copy", requiresAuth: true },
155
+ [ROW_ACTION_ID.EXPORT]: { id: ROW_ACTION_ID.EXPORT, label: "Export", iconName: "Download", requiresAuth: true },
156
+ [ROW_ACTION_ID.TRACK]: { id: ROW_ACTION_ID.TRACK, label: "Track Shipment", iconName: "Truck", requiresAuth: true },
157
+ [ROW_ACTION_ID.CANCEL]: { id: ROW_ACTION_ID.CANCEL, label: "Cancel", iconName: "X", requiresAuth: true, destructive: true },
158
+ [ROW_ACTION_ID.REFUND]: { id: ROW_ACTION_ID.REFUND, label: "Refund", iconName: "RefreshCw", requiresAuth: true, requiredRole: "admin", requiredPermission: "admin.orders.refund", destructive: true, separator: true },
159
+ [ROW_ACTION_ID.RESEND]: { id: ROW_ACTION_ID.RESEND, label: "Resend", iconName: "Send", requiresAuth: true },
160
+ [ROW_ACTION_ID.REPLY]: { id: ROW_ACTION_ID.REPLY, label: "Reply", iconName: "MessageSquare", requiresAuth: true },
161
+ [ROW_ACTION_ID.PUBLISH]: { id: ROW_ACTION_ID.PUBLISH, label: "Publish", iconName: "Upload", requiresAuth: true },
162
+ [ROW_ACTION_ID.ARCHIVE]: { id: ROW_ACTION_ID.ARCHIVE, label: "Archive", iconName: "Archive", requiresAuth: true, separator: true },
116
163
  };
117
164
  // Admin dashboard row action groups per entity type
118
165
  export const ADMIN_ROW_ACTIONS = {
@@ -166,21 +213,11 @@ export const FORM_ACTION_META = {
166
213
  [FORM_ACTION_ID.DELETE]: { id: FORM_ACTION_ID.DELETE, label: "Delete", variant: "danger", type: "button", iconName: "Trash2", destructive: true },
167
214
  [FORM_ACTION_ID.DISCARD]: { id: FORM_ACTION_ID.DISCARD, label: "Discard", variant: "ghost", type: "button", destructive: true },
168
215
  };
169
- /**
170
- * Preset ordered action groups for common form footer layouts.
171
- * Consumed by FormShell and DrawerFormFooter to determine which buttons render,
172
- * in which order (left → right).
173
- */
174
216
  export const FORM_FOOTER_PRESET = {
175
- /** Standard edit drawer: Cancel | Save Changes */
176
217
  drawerEdit: [FORM_ACTION_ID.CANCEL, FORM_ACTION_ID.SUBMIT],
177
- /** Edit drawer with delete: Delete | Cancel | Save Changes */
178
218
  drawerEditDelete: [FORM_ACTION_ID.DELETE, FORM_ACTION_ID.CANCEL, FORM_ACTION_ID.SUBMIT],
179
- /** Content editor (blog / product): Discard | Save Draft | Publish */
180
219
  contentEditor: [FORM_ACTION_ID.DISCARD, FORM_ACTION_ID.SAVE_DRAFT, FORM_ACTION_ID.PUBLISH],
181
- /** Simple modal / dialog: Cancel | Submit */
182
220
  modalForm: [FORM_ACTION_ID.CANCEL, FORM_ACTION_ID.SUBMIT],
183
- /** Settings page: Reset | Save Changes */
184
221
  settingsForm: [FORM_ACTION_ID.RESET, FORM_ACTION_ID.SUBMIT],
185
222
  };
186
223
  // ─────────────────────────────────────────────────────────────────────────────
@@ -210,23 +247,23 @@ export const DASHBOARD_QUICK_ACTION_ID = {
210
247
  USER_ADD_ADDRESS: "dqa-user-add-address",
211
248
  };
212
249
  export const DASHBOARD_QUICK_ACTION_META = {
213
- [DASHBOARD_QUICK_ACTION_ID.ADMIN_ADD_PRODUCT]: { id: DASHBOARD_QUICK_ACTION_ID.ADMIN_ADD_PRODUCT, label: "Add Product", variant: "primary", iconName: "Plus" },
214
- [DASHBOARD_QUICK_ACTION_ID.ADMIN_ADD_USER]: { id: DASHBOARD_QUICK_ACTION_ID.ADMIN_ADD_USER, label: "Add User", variant: "outline", iconName: "UserPlus" },
215
- [DASHBOARD_QUICK_ACTION_ID.ADMIN_ADD_STORE]: { id: DASHBOARD_QUICK_ACTION_ID.ADMIN_ADD_STORE, label: "Add Store", variant: "outline", iconName: "Store" },
216
- [DASHBOARD_QUICK_ACTION_ID.ADMIN_ADD_COUPON]: { id: DASHBOARD_QUICK_ACTION_ID.ADMIN_ADD_COUPON, label: "Add Coupon", variant: "outline", iconName: "Tag" },
217
- [DASHBOARD_QUICK_ACTION_ID.ADMIN_ADD_EVENT]: { id: DASHBOARD_QUICK_ACTION_ID.ADMIN_ADD_EVENT, label: "Add Event", variant: "outline", iconName: "Calendar" },
218
- [DASHBOARD_QUICK_ACTION_ID.ADMIN_ADD_BLOG]: { id: DASHBOARD_QUICK_ACTION_ID.ADMIN_ADD_BLOG, label: "New Post", variant: "outline", iconName: "FileText" },
219
- [DASHBOARD_QUICK_ACTION_ID.ADMIN_ADD_FAQ]: { id: DASHBOARD_QUICK_ACTION_ID.ADMIN_ADD_FAQ, label: "Add FAQ", variant: "outline", iconName: "HelpCircle" },
220
- [DASHBOARD_QUICK_ACTION_ID.ADMIN_SETTINGS]: { id: DASHBOARD_QUICK_ACTION_ID.ADMIN_SETTINGS, label: "Settings", variant: "ghost", iconName: "Settings" },
221
- [DASHBOARD_QUICK_ACTION_ID.SELLER_ADD_PRODUCT]: { id: DASHBOARD_QUICK_ACTION_ID.SELLER_ADD_PRODUCT, label: "List a Product", variant: "primary", iconName: "Plus" },
222
- [DASHBOARD_QUICK_ACTION_ID.SELLER_ADD_COUPON]: { id: DASHBOARD_QUICK_ACTION_ID.SELLER_ADD_COUPON, label: "New Coupon", variant: "outline", iconName: "Tag" },
223
- [DASHBOARD_QUICK_ACTION_ID.SELLER_VIEW_ORDERS]: { id: DASHBOARD_QUICK_ACTION_ID.SELLER_VIEW_ORDERS, label: "Orders", variant: "outline", iconName: "ShoppingBag" },
224
- [DASHBOARD_QUICK_ACTION_ID.SELLER_PAYOUT_REQ]: { id: DASHBOARD_QUICK_ACTION_ID.SELLER_PAYOUT_REQ, label: "Request Payout", variant: "outline", iconName: "Banknote" },
225
- [DASHBOARD_QUICK_ACTION_ID.SELLER_SETTINGS]: { id: DASHBOARD_QUICK_ACTION_ID.SELLER_SETTINGS, label: "Store Settings", variant: "ghost", iconName: "Settings" },
226
- [DASHBOARD_QUICK_ACTION_ID.USER_VIEW_ORDERS]: { id: DASHBOARD_QUICK_ACTION_ID.USER_VIEW_ORDERS, label: "My Orders", variant: "outline", iconName: "Package" },
227
- [DASHBOARD_QUICK_ACTION_ID.USER_VIEW_WISHLIST]: { id: DASHBOARD_QUICK_ACTION_ID.USER_VIEW_WISHLIST, label: "Wishlist", variant: "outline", iconName: "Heart" },
228
- [DASHBOARD_QUICK_ACTION_ID.USER_EDIT_PROFILE]: { id: DASHBOARD_QUICK_ACTION_ID.USER_EDIT_PROFILE, label: "Edit Profile", variant: "outline", iconName: "User" },
229
- [DASHBOARD_QUICK_ACTION_ID.USER_ADD_ADDRESS]: { id: DASHBOARD_QUICK_ACTION_ID.USER_ADD_ADDRESS, label: "Add Address", variant: "outline", iconName: "MapPin" },
250
+ [DASHBOARD_QUICK_ACTION_ID.ADMIN_ADD_PRODUCT]: { id: DASHBOARD_QUICK_ACTION_ID.ADMIN_ADD_PRODUCT, label: "Add Product", variant: "primary", iconName: "Plus", requiresAuth: true, requiredRole: "admin", requiredPermission: "products.create" },
251
+ [DASHBOARD_QUICK_ACTION_ID.ADMIN_ADD_USER]: { id: DASHBOARD_QUICK_ACTION_ID.ADMIN_ADD_USER, label: "Add User", variant: "outline", iconName: "UserPlus", requiresAuth: true, requiredRole: "admin", requiredPermission: "admin.users.create" },
252
+ [DASHBOARD_QUICK_ACTION_ID.ADMIN_ADD_STORE]: { id: DASHBOARD_QUICK_ACTION_ID.ADMIN_ADD_STORE, label: "Add Store", variant: "outline", iconName: "Store", requiresAuth: true, requiredRole: "admin", requiredPermission: "admin.stores.create" },
253
+ [DASHBOARD_QUICK_ACTION_ID.ADMIN_ADD_COUPON]: { id: DASHBOARD_QUICK_ACTION_ID.ADMIN_ADD_COUPON, label: "Add Coupon", variant: "outline", iconName: "Tag", requiresAuth: true, requiredRole: "admin", requiredPermission: "coupons.create" },
254
+ [DASHBOARD_QUICK_ACTION_ID.ADMIN_ADD_EVENT]: { id: DASHBOARD_QUICK_ACTION_ID.ADMIN_ADD_EVENT, label: "Add Event", variant: "outline", iconName: "Calendar", requiresAuth: true, requiredRole: "admin", requiredPermission: "events.create" },
255
+ [DASHBOARD_QUICK_ACTION_ID.ADMIN_ADD_BLOG]: { id: DASHBOARD_QUICK_ACTION_ID.ADMIN_ADD_BLOG, label: "New Post", variant: "outline", iconName: "FileText", requiresAuth: true, requiredRole: "admin", requiredPermission: "blog.create" },
256
+ [DASHBOARD_QUICK_ACTION_ID.ADMIN_ADD_FAQ]: { id: DASHBOARD_QUICK_ACTION_ID.ADMIN_ADD_FAQ, label: "Add FAQ", variant: "outline", iconName: "HelpCircle", requiresAuth: true, requiredRole: "admin", requiredPermission: "faqs.create" },
257
+ [DASHBOARD_QUICK_ACTION_ID.ADMIN_SETTINGS]: { id: DASHBOARD_QUICK_ACTION_ID.ADMIN_SETTINGS, label: "Settings", variant: "ghost", iconName: "Settings", requiresAuth: true, requiredRole: "admin", requiredPermission: "admin.settings.read" },
258
+ [DASHBOARD_QUICK_ACTION_ID.SELLER_ADD_PRODUCT]: { id: DASHBOARD_QUICK_ACTION_ID.SELLER_ADD_PRODUCT, label: "List a Product", variant: "primary", iconName: "Plus", requiresAuth: true, requiredRole: "seller", requiredPermission: "products.create" },
259
+ [DASHBOARD_QUICK_ACTION_ID.SELLER_ADD_COUPON]: { id: DASHBOARD_QUICK_ACTION_ID.SELLER_ADD_COUPON, label: "New Coupon", variant: "outline", iconName: "Tag", requiresAuth: true, requiredRole: "seller", requiredPermission: "coupons.create" },
260
+ [DASHBOARD_QUICK_ACTION_ID.SELLER_VIEW_ORDERS]: { id: DASHBOARD_QUICK_ACTION_ID.SELLER_VIEW_ORDERS, label: "Orders", variant: "outline", iconName: "ShoppingBag", requiresAuth: true, requiredRole: "seller" },
261
+ [DASHBOARD_QUICK_ACTION_ID.SELLER_PAYOUT_REQ]: { id: DASHBOARD_QUICK_ACTION_ID.SELLER_PAYOUT_REQ, label: "Request Payout", variant: "outline", iconName: "Banknote", requiresAuth: true, requiredRole: "seller", requiredPermission: "seller.payouts.request" },
262
+ [DASHBOARD_QUICK_ACTION_ID.SELLER_SETTINGS]: { id: DASHBOARD_QUICK_ACTION_ID.SELLER_SETTINGS, label: "Store Settings", variant: "ghost", iconName: "Settings", requiresAuth: true, requiredRole: "seller" },
263
+ [DASHBOARD_QUICK_ACTION_ID.USER_VIEW_ORDERS]: { id: DASHBOARD_QUICK_ACTION_ID.USER_VIEW_ORDERS, label: "My Orders", variant: "outline", iconName: "Package", requiresAuth: true, requiredRole: "user" },
264
+ [DASHBOARD_QUICK_ACTION_ID.USER_VIEW_WISHLIST]: { id: DASHBOARD_QUICK_ACTION_ID.USER_VIEW_WISHLIST, label: "Wishlist", variant: "outline", iconName: "Heart", requiresAuth: true, requiredRole: "user" },
265
+ [DASHBOARD_QUICK_ACTION_ID.USER_EDIT_PROFILE]: { id: DASHBOARD_QUICK_ACTION_ID.USER_EDIT_PROFILE, label: "Edit Profile", variant: "outline", iconName: "User", requiresAuth: true, requiredRole: "user" },
266
+ [DASHBOARD_QUICK_ACTION_ID.USER_ADD_ADDRESS]: { id: DASHBOARD_QUICK_ACTION_ID.USER_ADD_ADDRESS, label: "Add Address", variant: "outline", iconName: "MapPin", requiresAuth: true, requiredRole: "user" },
230
267
  };
231
268
  // Quick action groups — ordered left-to-right in the top shortcut bar
232
269
  export const DASHBOARD_QUICK_ACTIONS = {
@@ -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
+ }
@@ -396,4 +396,7 @@ export const siteSettingsSeedData = {
396
396
  deviantartClientId: "deviantart_client_id_PLACEHOLDER",
397
397
  deviantartClientSecret: "deviantart_client_secret_PLACEHOLDER",
398
398
  },
399
+ actionConfig: {},
400
+ navConfig: {},
401
+ disabledRoutes: [],
399
402
  };
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";