@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.
- package/dist/_internal/client/features/layout/DashboardLayoutClient.js +12 -8
- package/dist/_internal/client/features/layout/filterNavItems.d.ts +15 -0
- package/dist/_internal/client/features/layout/filterNavItems.js +19 -0
- package/dist/_internal/server/features/seo/manifest.js +9 -3
- package/dist/_internal/server/features/site-settings/actions.d.ts +14 -0
- package/dist/_internal/server/features/site-settings/actions.js +29 -0
- package/dist/_internal/shared/features/layout/types.d.ts +11 -0
- package/dist/client.d.ts +2 -0
- package/dist/client.js +2 -0
- package/dist/contracts/auth.d.ts +2 -0
- package/dist/features/admin/schemas/firestore.d.ts +21 -0
- package/dist/features/admin/schemas/firestore.js +3 -0
- package/dist/features/admin/utils/checkActionAllowed.d.ts +4 -0
- package/dist/features/admin/utils/checkActionAllowed.js +21 -0
- package/dist/features/admin/utils/getDisabledRoutes.d.ts +2 -0
- package/dist/features/admin/utils/getDisabledRoutes.js +6 -0
- package/dist/features/admin/utils/getSiteSettingsGlobal.d.ts +2 -0
- package/dist/features/admin/utils/getSiteSettingsGlobal.js +4 -0
- package/dist/features/categories/components/CategoryProductsListing.js +18 -13
- package/dist/features/layout/AppLayoutShell.js +8 -1
- package/dist/features/layout/ListingLayout.js +1 -1
- package/dist/features/layout/MainNavbar.d.ts +4 -0
- package/dist/features/layout/NavItem.js +1 -1
- package/dist/features/pre-orders/components/PreOrdersIndexListing.js +22 -14
- package/dist/features/products/components/AuctionsIndexListing.js +19 -16
- package/dist/features/products/components/ProductGrid.js +1 -1
- package/dist/features/products/components/ProductsIndexListing.js +34 -26
- package/dist/features/products/constants/action-defs.d.ts +64 -22
- package/dist/features/products/constants/action-defs.js +101 -64
- package/dist/features/stores/components/StoreAuctionsListing.js +15 -8
- package/dist/features/stores/components/StoreProductsListing.js +18 -13
- package/dist/react/contexts/SessionContext.d.ts +2 -0
- package/dist/react/hooks/useAuthGate.d.ts +8 -0
- package/dist/react/hooks/useAuthGate.js +35 -0
- package/dist/seed/site-settings-seed-data.js +3 -0
- package/dist/server.d.ts +4 -0
- package/dist/server.js +6 -0
- package/dist/tailwind-utilities.css +1 -1
- package/package.json +1 -1
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* action-defs.ts
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
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.
|
|
49
|
-
[ACTION_ID.
|
|
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.
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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";
|