@mohasinac/appkit 4.4.5 → 4.5.1

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 (49) hide show
  1. package/dist/_internal/server/features/checkout/actions.js +2 -0
  2. package/dist/_internal/server/features/orders/adapters.js +1 -0
  3. package/dist/features/account/hooks/useDataViewMode.js +1 -1
  4. package/dist/features/admin/components/AdminAddressesView.js +5 -0
  5. package/dist/features/admin/components/AdminArtView.js +4 -0
  6. package/dist/features/admin/components/AdminClassifiedView.js +4 -0
  7. package/dist/features/admin/components/AdminContactView.js +6 -0
  8. package/dist/features/admin/components/AdminDigitalCodesView.js +4 -0
  9. package/dist/features/admin/components/AdminGroupedListingsView.js +14 -8
  10. package/dist/features/admin/components/AdminLiveView.js +4 -0
  11. package/dist/features/admin/components/AdminOrdersView.js +6 -0
  12. package/dist/features/admin/components/AdminProductsView.js +22 -4
  13. package/dist/features/admin/components/AdminReturnRequestsView.js +5 -0
  14. package/dist/features/admin/components/AdminReviewsView.js +4 -3
  15. package/dist/features/admin/components/AdminScammersView.js +6 -0
  16. package/dist/features/admin/components/AdminStickersView.js +4 -0
  17. package/dist/features/admin/components/AdminSublistingCategoriesView.js +2 -0
  18. package/dist/features/admin/components/AdminSupportTicketsView.js +6 -0
  19. package/dist/features/admin/components/AdminTeamView.js +3 -0
  20. package/dist/features/admin/components/AdminViewCards.d.ts +3 -1
  21. package/dist/features/admin/components/AdminViewCards.js +7 -7
  22. package/dist/features/admin/components/DataListingView.d.ts +1 -1
  23. package/dist/features/admin/components/DataListingView.js +26 -2
  24. package/dist/features/admin/hooks/useAdminListing.js +1 -1
  25. package/dist/features/auth/actions/profile-actions.js +3 -3
  26. package/dist/features/orders/schemas/firestore.d.ts +2 -0
  27. package/dist/features/products/components/ProductDetailPageView.js +1 -3
  28. package/dist/features/products/constants/action-defs.d.ts +13 -2
  29. package/dist/features/products/constants/action-defs.js +33 -0
  30. package/dist/features/reviews/components/ReviewDetailShell.js +3 -3
  31. package/dist/features/reviews/components/ReviewModal.js +1 -1
  32. package/dist/features/reviews/components/ReviewsList.js +1 -1
  33. package/dist/features/reviews/types/index.d.ts +2 -6
  34. package/dist/features/seller/components/SellerArtView.js +2 -0
  35. package/dist/features/seller/components/SellerClassifiedView.js +2 -0
  36. package/dist/features/seller/components/SellerDigitalCodesView.js +2 -0
  37. package/dist/features/seller/components/SellerGroupedListingsView.js +2 -0
  38. package/dist/features/seller/components/SellerLiveView.js +2 -0
  39. package/dist/features/seller/components/SellerPayoutsView.js +3 -0
  40. package/dist/features/seller/components/SellerProductsView.js +20 -2
  41. package/dist/features/seller/components/SellerStickersView.js +2 -0
  42. package/dist/features/tester/components/TesterHubView.js +19 -3
  43. package/dist/features/tester/seed-data/orders-tester-seed-data.js +19 -1
  44. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +39 -0
  45. package/dist/index.d.ts +0 -1
  46. package/dist/seed/orders-seed-data.js +25 -1
  47. package/dist/styles.css +1 -1
  48. package/dist/tailwind-utilities.css +1 -1
  49. package/package.json +1 -1
@@ -256,6 +256,7 @@ async function createOrderForGroup(group, orderType, ctx) {
256
256
  const baseLine = {
257
257
  productId: item.productId,
258
258
  productTitle: item.productTitle,
259
+ image: item.productImage,
259
260
  quantity: item.quantity,
260
261
  unitPrice,
261
262
  totalPrice: unitPrice * item.quantity,
@@ -932,6 +933,7 @@ async function createRazorpayGroupOrder(group, orderType, ctx) {
932
933
  const baseLine = {
933
934
  productId: item.productId,
934
935
  productTitle: item.productTitle,
936
+ image: item.productImage,
935
937
  quantity: item.quantity,
936
938
  unitPrice,
937
939
  totalPrice: unitPrice * item.quantity,
@@ -3,6 +3,7 @@ export function orderDocumentToOrder(doc) {
3
3
  ? doc.items.map((item) => ({
4
4
  productId: item.productId,
5
5
  title: item.productTitle,
6
+ image: item.image,
6
7
  price: item.unitPrice,
7
8
  quantity: item.quantity,
8
9
  currency: doc.currency,
@@ -15,7 +15,7 @@ const MOBILE_BREAKPOINT_PX = 768;
15
15
  * pagination already live in the URL via useUrlTable() and must never be
16
16
  * added to this hook — see CLAUDE.md Phase 3 / user directive 2026-08-18.
17
17
  */
18
- export function useDataViewMode(fallback = "table") {
18
+ export function useDataViewMode(fallback = "list") {
19
19
  const { data: profile } = useCurrentProfile();
20
20
  const updateProfile = useUpdateCurrentProfile();
21
21
  const debounceRef = useRef(null);
@@ -11,6 +11,7 @@ import { DataListingView } from "./DataListingView";
11
11
  import { apiClient } from "../../../http";
12
12
  import { useApiMutation } from "@mohasinac/appkit/client";
13
13
  import { QuickEditMenu } from "./QuickEditMenu";
14
+ import { ROUTES } from "../../../next/routing/route-map";
14
15
  const BAN_STATUS_TABS = [
15
16
  { id: "", label: "All" },
16
17
  { id: "banned", label: "Banned" },
@@ -78,6 +79,10 @@ function buildAddressConfig(banStatus, onAction) {
78
79
  })),
79
80
  getTotal: (response, rows) => typeof response.total === "number" ? response.total : rows.length,
80
81
  buildFilters: () => undefined,
82
+ // The full address record (owner, ban reason/history) lives on the
83
+ // existing admin address edit page — reuse it instead of leaving a
84
+ // banned-address row with no detail destination.
85
+ rowHrefTemplate: String(ROUTES.ADMIN.ADDRESSES_EDIT("{id}")),
81
86
  renderRowActions: (row) => {
82
87
  const actions = [];
83
88
  if (row.status !== "banned") {
@@ -10,6 +10,7 @@ import { ListingLayout } from "../../../ui";
10
10
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
11
11
  import { toRecordArray, toRelativeDate, toCurrency, toStringValue, } from "../hooks/useAdminListingData";
12
12
  import { DataListingView } from "./DataListingView";
13
+ import { ROUTES } from "../../../next/routing/route-map";
13
14
  const ADMIN_ART_CONFIG = {
14
15
  portal: "admin",
15
16
  title: "Art",
@@ -36,6 +37,9 @@ const ADMIN_ART_CONFIG = {
36
37
  })),
37
38
  getTotal: (response, mappedRows) => typeof response.total === "number" ? response.total : mappedRows.length,
38
39
  buildFilters: () => "listingType==art",
40
+ // Art listings are products (filtered by listingType) — reuse the real
41
+ // admin product edit page rather than leaving rows non-navigable.
42
+ rowHrefTemplate: String(ROUTES.ADMIN.PRODUCTS_EDIT("{id}")),
39
43
  };
40
44
  export function AdminArtView({ children, ...props }) {
41
45
  if (React.Children.count(children) > 0) {
@@ -10,6 +10,7 @@ import { ListingLayout } from "../../../ui";
10
10
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
11
11
  import { toRecordArray, toRelativeDate, toCurrency, toStringValue, } from "../hooks/useAdminListingData";
12
12
  import { DataListingView } from "./DataListingView";
13
+ import { ROUTES } from "../../../next/routing/route-map";
13
14
  const ADMIN_CLASSIFIED_CONFIG = {
14
15
  portal: "admin",
15
16
  title: "Classified",
@@ -36,6 +37,9 @@ const ADMIN_CLASSIFIED_CONFIG = {
36
37
  })),
37
38
  getTotal: (response, mappedRows) => typeof response.total === "number" ? response.total : mappedRows.length,
38
39
  buildFilters: () => "listingType==classified",
40
+ // Classified listings are products (filtered by listingType) — reuse the
41
+ // real admin product edit page rather than leaving rows non-navigable.
42
+ rowHrefTemplate: String(ROUTES.ADMIN.PRODUCTS_EDIT("{id}")),
39
43
  };
40
44
  export function AdminClassifiedView({ children, ...props }) {
41
45
  if (React.Children.count(children) > 0) {
@@ -81,6 +81,12 @@ export function AdminContactView({ children, onBulkMarkRead, onBulkArchive, onBu
81
81
  return mappedRows.length;
82
82
  },
83
83
  buildFilters: (f) => (f.status && f.status !== "All" ? sieveFilter("status", SIEVE_OP.EQ, f.status) : undefined),
84
+ // Mirrors the row action's "View" entry so the row itself is clickable,
85
+ // not just the overflow menu.
86
+ onRowClick: (row) => {
87
+ setSelectedRow(row);
88
+ setDrawerOpen(true);
89
+ },
84
90
  buildBulkActions: (selection) => [
85
91
  ...(onBulkMarkRead
86
92
  ? [
@@ -9,6 +9,7 @@ import { ListingLayout } from "../../../ui";
9
9
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
10
10
  import { toRecordArray, toRelativeDate, toCurrency, toStringValue, } from "../hooks/useAdminListingData";
11
11
  import { DataListingView } from "./DataListingView";
12
+ import { ROUTES } from "../../../next/routing/route-map";
12
13
  const ADMIN_DIGITAL_CODES_CONFIG = {
13
14
  portal: "admin",
14
15
  title: "Digital Codes",
@@ -43,6 +44,9 @@ const ADMIN_DIGITAL_CODES_CONFIG = {
43
44
  }),
44
45
  getTotal: (response, mappedRows) => typeof response.total === "number" ? response.total : mappedRows.length,
45
46
  buildFilters: () => "listingType==digital-code",
47
+ // Digital-code listings are products (filtered by listingType) — reuse
48
+ // the real admin product edit page rather than leaving rows non-navigable.
49
+ rowHrefTemplate: String(ROUTES.ADMIN.PRODUCTS_EDIT("{id}")),
46
50
  };
47
51
  export function AdminDigitalCodesView({ children, ...props }) {
48
52
  if (React.Children.count(children) > 0) {
@@ -39,6 +39,15 @@ function ReassignProductsDrawer({ state, onClose, onSaved, }) {
39
39
  export function AdminGroupedListingsView({ children, ...props }) {
40
40
  const [reassign, setReassign] = useState(null);
41
41
  const [refreshKey, setRefreshKey] = useState(0);
42
+ const openReassign = React.useCallback((row) => {
43
+ setReassign({
44
+ groupId: row.id,
45
+ groupTitle: row.primary,
46
+ currentProductIds: Array.isArray(row._raw?.productIds)
47
+ ? (row._raw?.productIds ?? []).map(String)
48
+ : [],
49
+ });
50
+ }, []);
42
51
  const config = React.useMemo(() => ({
43
52
  portal: "admin",
44
53
  title: "Grouped Listings",
@@ -66,14 +75,11 @@ export function AdminGroupedListingsView({ children, ...props }) {
66
75
  })),
67
76
  getTotal: (response, mappedRows) => typeof response.total === "number" ? response.total : mappedRows.length,
68
77
  buildFilters: () => "",
69
- renderRowActions: (row) => (_jsx(Button, { size: "sm", variant: "outline", onClick: () => setReassign({
70
- groupId: row.id,
71
- groupTitle: row.primary,
72
- currentProductIds: Array.isArray(row._raw?.productIds)
73
- ? (row._raw?.productIds ?? []).map(String)
74
- : [],
75
- }), children: "Reassign products" })),
76
- }), [refreshKey]);
78
+ // Mirrors the row action button's "Reassign products" drawer so the
79
+ // row itself is clickable, not just the button.
80
+ onRowClick: (row) => openReassign(row),
81
+ renderRowActions: (row) => (_jsx(Button, { size: "sm", variant: "outline", onClick: () => openReassign(row), children: "Reassign products" })),
82
+ }), [refreshKey, openReassign]);
77
83
  if (React.Children.count(children) > 0) {
78
84
  return (_jsx(ListingLayout, { portal: "admin", ...props, children: children }));
79
85
  }
@@ -9,6 +9,7 @@ import { ListingLayout } from "../../../ui";
9
9
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
10
10
  import { toRecordArray, toRelativeDate, toCurrency, toStringValue, } from "../hooks/useAdminListingData";
11
11
  import { DataListingView } from "./DataListingView";
12
+ import { ROUTES } from "../../../next/routing/route-map";
12
13
  const ADMIN_LIVE_CONFIG = {
13
14
  portal: "admin",
14
15
  title: "Live Items",
@@ -42,6 +43,9 @@ const ADMIN_LIVE_CONFIG = {
42
43
  }),
43
44
  getTotal: (response, mappedRows) => typeof response.total === "number" ? response.total : mappedRows.length,
44
45
  buildFilters: () => "listingType==live",
46
+ // Live-item listings are products (filtered by listingType) — reuse the
47
+ // real admin product edit page rather than leaving rows non-navigable.
48
+ rowHrefTemplate: String(ROUTES.ADMIN.PRODUCTS_EDIT("{id}")),
45
49
  };
46
50
  export function AdminLiveView({ children, ...props }) {
47
51
  if (React.Children.count(children) > 0) {
@@ -66,6 +66,12 @@ export function AdminOrdersView({ children, ...props }) {
66
66
  })),
67
67
  getTotal: (response, mappedRows) => typeof response.meta?.total === "number" ? response.meta.total : mappedRows.length,
68
68
  buildFilters: (f) => (f.status && f.status !== "All" ? sieveFilter("status", SIEVE_OP.EQ, f.status) : undefined),
69
+ // Mirrors the row action's "View full details" entry so the row itself
70
+ // is clickable, not just the overflow menu.
71
+ onRowClick: (row) => {
72
+ setSelectedRow(row);
73
+ setDrawerOpen(true);
74
+ },
69
75
  // Rule #7: bulk-action array sourced from the ADMIN_BULK_ACTIONS preset.
70
76
  buildBulkActions: (selection) => {
71
77
  const statusByAction = {
@@ -122,13 +122,31 @@ export function AdminProductsView({ children, ...props }) {
122
122
  mapRows: (response) => toRecordArray(response.items).map((item, index) => {
123
123
  const id = toStringValue(item.id, `product-${index}`);
124
124
  rawByIdRef.current[id] = item;
125
+ // Auction listings lost their reserve/bid/end-date summary when this
126
+ // view was consolidated onto the shared Products listing — restore
127
+ // it into `secondary` instead of seller/SKU, which admins reviewing
128
+ // an auction care about far less than bid activity.
129
+ const auctionSecondary = item.listingType === "auction"
130
+ ? [
131
+ typeof item.reservePrice === "number"
132
+ ? `Reserve ₹${item.reservePrice.toLocaleString("en-IN")}`
133
+ : null,
134
+ `${typeof item.bidCount === "number" ? item.bidCount : 0} bids`,
135
+ item.auctionEndDate
136
+ ? `Ends ${new Date(item.auctionEndDate).toLocaleDateString("en-IN", { month: "short", day: "numeric" })}`
137
+ : null,
138
+ ]
139
+ .filter(Boolean)
140
+ .join(" · ")
141
+ : "";
125
142
  const base = {
126
143
  id,
127
144
  primary: toStringValue(item.title ?? item.name, "Untitled product"),
128
- secondary: [
129
- toStringValue(item.sellerName, "Unknown seller"),
130
- toStringValue(item.sku, "No SKU"),
131
- ].join(" · "),
145
+ secondary: auctionSecondary ||
146
+ [
147
+ toStringValue(item.sellerName, "Unknown seller"),
148
+ toStringValue(item.sku, "No SKU"),
149
+ ].join(" · "),
132
150
  status: toStringValue(item.status, "Unknown"),
133
151
  updatedAt: toRelativeDate(item.updatedAt ?? item.createdAt),
134
152
  featured: Boolean(item.featured),
@@ -7,6 +7,7 @@ import { useQueryClient } from "@tanstack/react-query";
7
7
  import { ConfirmDeleteModal, RowActionMenu, useToast } from "../../../ui";
8
8
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
9
9
  import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
10
+ import { ROUTES } from "../../../next/routing/route-map";
10
11
  import { toRecordArray, toRelativeDate, toCurrency, toStringValue, } from "../hooks/useAdminListingData";
11
12
  import { DataListingView } from "./DataListingView";
12
13
  import { apiClient } from "../../../http";
@@ -71,6 +72,10 @@ export function AdminReturnRequestsView(_props) {
71
72
  })),
72
73
  getTotal: (response, mappedRows) => typeof response.total === "number" ? response.total : mappedRows.length,
73
74
  buildFilters: () => undefined,
75
+ // Return requests are orders (endpoint filters status==return_requested)
76
+ // — reuse the real admin order detail page rather than leaving rows
77
+ // non-navigable.
78
+ rowHrefTemplate: String(ROUTES.ADMIN.ORDER_DETAIL("{id}")),
74
79
  renderRowActions: (row) => (_jsx(RowActionMenu, { actions: [
75
80
  {
76
81
  label: ACTIONS.ADMIN["approve-return"].label,
@@ -62,9 +62,7 @@ export function AdminReviewsView({ children, ...props }) {
62
62
  userName: toStringValue(raw.userName ?? raw.sellerName, "Unknown"),
63
63
  userAvatar: toStringValue(raw.userAvatar, "") || undefined,
64
64
  verified: Boolean(raw.verified),
65
- images: Array.isArray(raw.images)
66
- ? raw.images
67
- : [],
65
+ images: Array.isArray(raw.images) ? raw.images : [],
68
66
  helpfulCount: Number(raw.helpfulCount ?? 0),
69
67
  createdAt: toStringValue(raw.createdAt, "") || undefined,
70
68
  });
@@ -119,6 +117,9 @@ export function AdminReviewsView({ children, ...props }) {
119
117
  parts.push(sieveFilter("rating", SIEVE_OP.EQ, f.rating));
120
118
  return parts.join(",") || undefined;
121
119
  },
120
+ // Mirrors the row action's "View" entry so the row itself is clickable,
121
+ // not just the overflow menu.
122
+ onRowClick: (row) => handleViewReview(row),
122
123
  // Rule #7: bulk-action array sourced from the ADMIN_BULK_ACTIONS preset.
123
124
  // DELETE has no wired handler yet (pre-existing — approve/reject were
124
125
  // already no-op placeholders too), so it's left out until one exists.
@@ -86,6 +86,12 @@ export function AdminScammersView({ children, ...props }) {
86
86
  return mappedRows.length;
87
87
  },
88
88
  buildFilters: (f) => (f.status && f.status !== "All" ? sieveFilter("status", SIEVE_OP.EQ, f.status) : undefined),
89
+ // Mirrors the row action's "review-scammer" entry so the row itself is
90
+ // clickable, not just the overflow menu.
91
+ onRowClick: (row) => {
92
+ setSelectedRow(row);
93
+ setDrawerOpen(true);
94
+ },
89
95
  renderRowActions: (row) => (_jsx(RowActionMenu, { actions: [
90
96
  {
91
97
  label: ACTIONS.ADMIN["review-scammer"].label,
@@ -10,6 +10,7 @@ import { ListingLayout } from "../../../ui";
10
10
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
11
11
  import { toRecordArray, toRelativeDate, toCurrency, toStringValue, } from "../hooks/useAdminListingData";
12
12
  import { DataListingView } from "./DataListingView";
13
+ import { ROUTES } from "../../../next/routing/route-map";
13
14
  const ADMIN_STICKERS_CONFIG = {
14
15
  portal: "admin",
15
16
  title: "Stickers",
@@ -36,6 +37,9 @@ const ADMIN_STICKERS_CONFIG = {
36
37
  })),
37
38
  getTotal: (response, mappedRows) => typeof response.total === "number" ? response.total : mappedRows.length,
38
39
  buildFilters: () => "listingType==stickers",
40
+ // Sticker listings are products (filtered by listingType) — reuse the
41
+ // real admin product edit page rather than leaving rows non-navigable.
42
+ rowHrefTemplate: String(ROUTES.ADMIN.PRODUCTS_EDIT("{id}")),
39
43
  };
40
44
  export function AdminStickersView({ children, ...props }) {
41
45
  if (React.Children.count(children) > 0) {
@@ -4,6 +4,7 @@ import { sortBy } from "@mohasinac/appkit/client";
4
4
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
5
5
  import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
6
6
  import { buildBulkAction } from "../../../_internal/shared/actions/bulk-helpers";
7
+ import { ROUTES } from "../../../next/routing/route-map";
7
8
  import { toRecordArray, toRelativeDate, toStringValue, } from "../hooks/useAdminListingData";
8
9
  import { DataListingView } from "./DataListingView";
9
10
  export function AdminSublistingCategoriesView({ onBulkDelete, }) {
@@ -36,6 +37,7 @@ export function AdminSublistingCategoriesView({ onBulkDelete, }) {
36
37
  })),
37
38
  getTotal: (response, mappedRows) => typeof response.total === "number" ? response.total : mappedRows.length,
38
39
  buildFilters: () => undefined,
40
+ rowHrefTemplate: String(ROUTES.ADMIN.SUBLISTING_CATEGORIES_EDIT("{id}")),
39
41
  buildBulkActions: onBulkDelete
40
42
  ? (selection) => [
41
43
  buildBulkAction(ACTIONS.ADMIN["delete-sublisting-category"], async () => {
@@ -111,6 +111,12 @@ export function AdminSupportTicketsView({ children, ...props }) {
111
111
  parts.push(sieveFilter("priority", SIEVE_OP.EQ, f.priority));
112
112
  return parts.join(",") || undefined;
113
113
  },
114
+ // Mirrors the row action's "View" entry so the row itself is clickable,
115
+ // not just the overflow menu.
116
+ onRowClick: (row) => {
117
+ setSelectedRow(row);
118
+ setDrawerOpen(true);
119
+ },
114
120
  renderRowActions: (row) => (_jsx(RowActionMenu, { actions: [
115
121
  {
116
122
  label: ROW_ACTION_META[ROW_ACTION_ID.VIEW].label,
@@ -93,6 +93,9 @@ export function AdminTeamView({ children, onBulkRemove, ...props }) {
93
93
  return mappedRows.length;
94
94
  },
95
95
  buildFilters: (state) => state.group && state.group !== "All" ? sieveFilter("permissionGroup", SIEVE_OP.EQ, state.group) : undefined,
96
+ // Mirrors the row action's "edit-team-member" entry so the row itself
97
+ // is clickable, not just the overflow menu.
98
+ onRowClick: (row) => openEdit(row),
96
99
  toolbarExtra: (_jsxs(Button, { gap: "sm", type: "button", variant: "primary", onClick: openInvite, className: "whitespace-nowrap", children: [_jsx(UserPlus, { className: "h-4 w-4" }), "Invite Employee"] })),
97
100
  // Rule #7: bulk-action array sourced from the ADMIN_BULK_ACTIONS preset.
98
101
  buildBulkActions: onBulkRemove
@@ -8,6 +8,8 @@ interface AdminViewCardsProps {
8
8
  onRowClick?: (row: AdminListingScaffoldRow) => void;
9
9
  selectedIdSet?: Set<string>;
10
10
  onToggleSelect?: (id: string) => void;
11
+ /** Same row-actions menu DataTable renders in its table view — mirrored here so cards carry the same quick actions, not just navigation. */
12
+ renderRowActions?: (row: AdminListingScaffoldRow) => React.ReactNode;
11
13
  }
12
- export declare function AdminViewCards({ rows, view, isLoading, emptyLabel, onRowClick, selectedIdSet, onToggleSelect, }: AdminViewCardsProps): React.JSX.Element;
14
+ export declare function AdminViewCards({ rows, view, isLoading, emptyLabel, onRowClick, selectedIdSet, onToggleSelect, renderRowActions, }: AdminViewCardsProps): React.JSX.Element;
13
15
  export {};
@@ -22,10 +22,10 @@ function SkeletonCard({ view }) {
22
22
  }
23
23
  return (_jsx(Div, { rounded: "xl", border: "subtle", className: `${__O.hidden} animate-pulse`, children: _jsxs(Stack, { gap: "xs", padding: "md", children: [_jsx(Div, { className: "h-4 w-3/4", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-3 w-1/2", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-5 w-20 mt-1", surface: "subtle", rounded: "full" }), _jsx(Div, { className: "h-3 w-1/3", surface: "subtle", rounded: "default" })] }) }));
24
24
  }
25
- function AdminCardItem({ row, view, selected, onToggleSelect, onRowClick, }) {
25
+ function AdminCardItem({ row, view, selected, onToggleSelect, onRowClick, renderRowActions, }) {
26
26
  const flags = FLAG_BADGES.filter(({ key }) => Boolean(row[key]));
27
27
  const handleClick = (e) => {
28
- if (e.target.closest('[data-checkbox]'))
28
+ if (e.target.closest('[data-no-row-click]'))
29
29
  return;
30
30
  onRowClick?.(row);
31
31
  };
@@ -33,16 +33,16 @@ function AdminCardItem({ row, view, selected, onToggleSelect, onRowClick, }) {
33
33
  return (_jsxs(Row, { gap: "sm", className: [
34
34
  "px-[var(--appkit-space-4)] py-[var(--appkit-space-3)] cursor-pointer transition-colors hover:bg-zinc-50 hover:bg-[var(--appkit-color-surface-elevated)]/50",
35
35
  selected ? "bg-primary-50/40 dark:bg-primary-900/10" : "",
36
- ].filter(Boolean).join(" "), onClick: handleClick, role: onRowClick ? "button" : undefined, children: [onToggleSelect && (_jsx(Div, { "data-checkbox": true, className: "shrink-0", onClick: (e) => { e.stopPropagation(); onToggleSelect(row.id); }, children: _jsx(Checkbox, { bare: true, checked: selected, onChange: () => onToggleSelect(row.id), className: "h-4 w-4 rounded border-zinc-300 text-primary accent-primary cursor-pointer", "aria-label": `Select ${row.primary}` }) })), _jsxs(Stack, { gap: "none", className: "flex-1 min-w-0", children: [_jsx(Text, { size: "sm", weight: "semibold", className: "truncate", color: "primary", children: row.primary }), _jsx(Text, { size: "xs", color: "muted", className: "truncate", children: row.secondary })] }), flags.length > 0 && (_jsx(Row, { gap: "xs", className: "hidden sm:flex shrink-0", children: flags.map(({ key, label, color }) => (_jsx(Span, { padding: "pill-2xs", weight: "medium", className: `inline-flex text-[10px] ${color}`, rounded: "full", children: label }, key))) })), _jsx(StatusBadge, { status: row.status }), _jsx(Span, { size: "xs", color: "muted", className: "hidden sm:block shrink-0 w-24", align: "end", children: row.updatedAt })] }));
36
+ ].filter(Boolean).join(" "), onClick: handleClick, role: onRowClick ? "button" : undefined, children: [onToggleSelect && (_jsx(Div, { "data-no-row-click": true, className: "shrink-0", onClick: (e) => { e.stopPropagation(); onToggleSelect(row.id); }, children: _jsx(Checkbox, { bare: true, checked: selected, onChange: () => onToggleSelect(row.id), className: "h-4 w-4 rounded border-zinc-300 text-primary accent-primary cursor-pointer", "aria-label": `Select ${row.primary}` }) })), _jsxs(Stack, { gap: "none", className: "flex-1 min-w-0", children: [_jsx(Text, { size: "sm", weight: "semibold", className: "truncate", color: "primary", children: row.primary }), _jsx(Text, { size: "xs", color: "muted", className: "truncate", children: row.secondary }), row.barcodeId && (_jsx(Text, { size: "xs", color: "faint", className: "truncate font-mono", children: row.barcodeId }))] }), flags.length > 0 && (_jsx(Row, { gap: "xs", className: "hidden sm:flex shrink-0", children: flags.map(({ key, label, color }) => (_jsx(Span, { padding: "pill-2xs", weight: "medium", className: `inline-flex text-[10px] ${color}`, rounded: "full", children: label }, key))) })), _jsx(StatusBadge, { status: row.status }), _jsx(Span, { size: "xs", color: "muted", className: "hidden sm:block shrink-0 w-24", align: "end", children: row.updatedAt }), renderRowActions && (_jsx(Div, { "data-no-row-click": true, className: "shrink-0", onClick: (e) => e.stopPropagation(), children: renderRowActions(row) }))] }));
37
37
  }
38
38
  return (_jsxs(Div, { rounded: "xl", className: [
39
39
  "border overflow-hidden cursor-pointer transition-all hover:shadow-md hover:-translate-y-0.5",
40
40
  selected
41
41
  ? "border-primary ring-1 ring-primary/20 bg-primary-50/30 dark:bg-primary-900/10"
42
42
  : "border-zinc-100 border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)]",
43
- ].filter(Boolean).join(" "), onClick: handleClick, role: onRowClick ? "button" : undefined, children: [onToggleSelect && (_jsxs(Row, { gap: "xs", "data-checkbox": true, paddingY: "t-sm", padding: "x-sm", onClick: (e) => { e.stopPropagation(); onToggleSelect(row.id); }, children: [_jsx(Checkbox, { bare: true, checked: selected, onChange: () => onToggleSelect(row.id), className: "h-4 w-4 rounded border-zinc-300 text-primary accent-primary cursor-pointer", "aria-label": `Select ${row.primary}` }), flags.length > 0 && (_jsx(Row, { gap: "xs", wrap: true, children: flags.map(({ key, label, color }) => (_jsx(Span, { padding: "pill-2xs", weight: "medium", className: `inline-flex text-[10px] ${color}`, rounded: "full", children: label }, key))) }))] })), _jsxs(Stack, { gap: "xs", className: `${__P.p3}.5`, children: [_jsxs(Stack, { gap: "none", children: [_jsx(Text, { size: "sm", weight: "semibold", className: "line-clamp-2 leading-snug", color: "primary", children: row.primary }), _jsx(Text, { size: "xs", color: "muted", className: "truncate", children: row.secondary })] }), _jsxs(Row, { justify: "between", gap: "xs", children: [_jsx(StatusBadge, { status: row.status }), _jsx(Span, { color: "muted", className: "text-[11px] shrink-0", children: row.updatedAt })] })] })] }));
43
+ ].filter(Boolean).join(" "), onClick: handleClick, role: onRowClick ? "button" : undefined, children: [onToggleSelect && (_jsxs(Row, { gap: "xs", "data-no-row-click": true, paddingY: "t-sm", padding: "x-sm", onClick: (e) => { e.stopPropagation(); onToggleSelect(row.id); }, children: [_jsx(Checkbox, { bare: true, checked: selected, onChange: () => onToggleSelect(row.id), className: "h-4 w-4 rounded border-zinc-300 text-primary accent-primary cursor-pointer", "aria-label": `Select ${row.primary}` }), flags.length > 0 && (_jsx(Row, { gap: "xs", wrap: true, children: flags.map(({ key, label, color }) => (_jsx(Span, { padding: "pill-2xs", weight: "medium", className: `inline-flex text-[10px] ${color}`, rounded: "full", children: label }, key))) }))] })), _jsxs(Stack, { gap: "xs", className: `${__P.p3}.5`, children: [_jsxs(Stack, { gap: "none", children: [_jsx(Text, { size: "sm", weight: "semibold", className: "line-clamp-2 leading-snug", color: "primary", children: row.primary }), _jsx(Text, { size: "xs", color: "muted", className: "truncate", children: row.secondary }), row.barcodeId && (_jsx(Text, { size: "xs", color: "faint", className: "truncate font-mono", children: row.barcodeId }))] }), _jsxs(Row, { justify: "between", gap: "xs", children: [_jsx(StatusBadge, { status: row.status }), _jsx(Span, { color: "muted", className: "text-[11px] shrink-0", children: row.updatedAt })] }), renderRowActions && (_jsx(Row, { "data-no-row-click": true, justify: "end", gap: "xs", className: "border-t border-[var(--appkit-color-border)] -mx-[var(--appkit-space-3)] -mb-[var(--appkit-space-3)] mt-[var(--appkit-space-1)] px-[var(--appkit-space-3)] py-[var(--appkit-space-2)]", onClick: (e) => e.stopPropagation(), children: renderRowActions(row) }))] })] }));
44
44
  }
45
- export function AdminViewCards({ rows, view, isLoading, emptyLabel = "No items found", onRowClick, selectedIdSet, onToggleSelect, }) {
45
+ export function AdminViewCards({ rows, view, isLoading, emptyLabel = "No items found", onRowClick, selectedIdSet, onToggleSelect, renderRowActions, }) {
46
46
  if (isLoading) {
47
47
  const count = view === "grid" ? 12 : 8;
48
48
  if (view === "list") {
@@ -54,7 +54,7 @@ export function AdminViewCards({ rows, view, isLoading, emptyLabel = "No items f
54
54
  return (_jsx(Text, { paddingY: "3xl", size: "sm", color: "muted", align: "center", children: emptyLabel }));
55
55
  }
56
56
  if (view === "list") {
57
- return (_jsx(Div, { rounded: "xl", border: "subtle", className: `${__O.hidden} divide-y divide-zinc-100 divide-[var(--appkit-color-border)]`, children: rows.map((row) => (_jsx(AdminCardItem, { row: row, view: "list", selected: selectedIdSet?.has(row.id) ?? false, onToggleSelect: onToggleSelect, onRowClick: onRowClick }, row.id))) }));
57
+ return (_jsx(Div, { rounded: "xl", border: "subtle", className: `${__O.hidden} divide-y divide-zinc-100 divide-[var(--appkit-color-border)]`, children: rows.map((row) => (_jsx(AdminCardItem, { row: row, view: "list", selected: selectedIdSet?.has(row.id) ?? false, onToggleSelect: onToggleSelect, onRowClick: onRowClick, renderRowActions: renderRowActions }, row.id))) }));
58
58
  }
59
- return (_jsx(Grid, { gap: "md", className: "grid-cols-2 sm:grid-cols-3 lg:grid-cols-4", children: rows.map((row) => (_jsx(AdminCardItem, { row: row, view: "grid", selected: selectedIdSet?.has(row.id) ?? false, onToggleSelect: onToggleSelect, onRowClick: onRowClick }, row.id))) }));
59
+ return (_jsx(Grid, { gap: "md", className: "grid-cols-2 sm:grid-cols-3 lg:grid-cols-4", children: rows.map((row) => (_jsx(AdminCardItem, { row: row, view: "grid", selected: selectedIdSet?.has(row.id) ?? false, onToggleSelect: onToggleSelect, onRowClick: onRowClick, renderRowActions: renderRowActions }, row.id))) }));
60
60
  }
@@ -118,7 +118,7 @@ export interface ListingViewConfig<TResponse, TRow extends {
118
118
  rowHrefTemplate?: string | ((row: TRow) => string);
119
119
  /** Hide the table toggle in the toolbar; views with no table column set should use this. */
120
120
  hideTableView?: boolean;
121
- /** Initial view mode (defaults to "table" when not hidden, "grid" when hidden). */
121
+ /** Initial view mode (defaults to "list" the row-style card view). */
122
122
  initialView?: "grid" | "list" | "table";
123
123
  className?: string;
124
124
  }
@@ -7,7 +7,7 @@ import { useAdminListing } from "../hooks/useAdminListing";
7
7
  import { AdminViewCards } from "./AdminViewCards";
8
8
  import { DataTable } from "./DataTable";
9
9
  export function DataListingView({ config, }) {
10
- const effectiveInitialView = config.initialView ?? (config.hideTableView ? "grid" : "table");
10
+ const effectiveInitialView = config.initialView ?? "list";
11
11
  const listing = useAdminListing({
12
12
  ...config,
13
13
  initialView: effectiveInitialView,
@@ -42,6 +42,26 @@ export function DataListingView({ config, }) {
42
42
  ? `Add ${config.title.replace(/s$/, "")}`
43
43
  : `Edit ${config.title.replace(/s$/, "")}`;
44
44
  const isEditorOpen = panel.isCreateOpen || panel.isEditOpen;
45
+ // Shared row-navigation resolver — the table branch and the default
46
+ // AdminViewCards branch must agree on when a row is actually clickable.
47
+ // Previously AdminViewCards unconditionally wired onRowClick to
48
+ // openEditPanel even when the config had no renderEditor/onRowClick/
49
+ // rowHrefTemplate, so cards always looked clickable (cursor-pointer,
50
+ // hover state) but silently did nothing (or mutated the URL with no
51
+ // visible drawer) while the table for the same config correctly showed
52
+ // no click affordance at all — read as "table isn't clickable" by users.
53
+ const resolvedRowClick = config.onRowClick
54
+ ? (row) => config.onRowClick(row, { openEditPanel: panel.openEditPanel })
55
+ : config.renderEditor
56
+ ? (row) => panel.openEditPanel(row.id)
57
+ : config.rowHrefTemplate
58
+ ? (row) => {
59
+ const href = typeof config.rowHrefTemplate === "function"
60
+ ? config.rowHrefTemplate(row)
61
+ : config.rowHrefTemplate.replace("{id}", encodeURIComponent(row.id));
62
+ window.location.href = href;
63
+ }
64
+ : undefined;
45
65
  return (_jsxs(Div, { className: config.className ?? "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: config.searchPlaceholder, onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || config.defaultSort, sortOptions: config.sortOptions, onSortChange: (v) => table.set("sort", v), showTableView: !config.hideTableView, toggles: config.toggles, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState, extra: config.primaryAction || config.toolbarExtra ? (_jsxs(Row, { align: "center", gap: "sm", children: [config.toolbarExtra, config.primaryAction && (_jsxs(Button, { gap: "sm", size: "sm", onClick: () => config.primaryAction.onClick({
46
66
  openCreatePanel: panel.openCreatePanel,
47
67
  }), children: [config.primaryAction.icon ?? _jsx(Plus, { className: "h-4 w-4" }), config.primaryAction.label] }))] })) : undefined }), config.renderAboveContent?.(), bulkActionItems && (_jsx(BulkActionBar, { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: bulkActionItems })), totalPages > 1 && (_jsx(StickyToolbar, { offset: "header+pagination", tone: "translucent", border: true, padding: "toolbar", children: _jsx(Row, { justify: "center", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p), pageSize: pageSize, onPageSizeChange: setPageSize, paginationConfig: { showPageSizeSelector: true, pageSizeOptions: [10, 25, 50, 100] } }) }) })), _jsxs(Div, { paddingX: "x-sm-md", padding: "y-md", children: [errorMessage && (_jsx(Div, { textSize: "sm", className: "mb-4 border border-error/20", color: "error", surface: "danger-surface", padding: "inline", rounded: "xl", children: errorMessage })), view === "table" && !config.hideTableView ? (_jsx(DataTable, { columns: config.columns, rows: rows, isLoading: isLoading, emptyLabel: config.emptyLabel ?? `No ${config.title.toLowerCase()} found`, selectedIds: selection.selectedIdSet, onToggleSelect: selection.toggle, onToggleSelectAll: (next) => next
@@ -56,7 +76,11 @@ export function DataListingView({ config, }) {
56
76
  // The persisted view-mode preference is global, so a "table"
57
77
  // value can still reach here on a hideTableView-only view (see
58
78
  // the DataTable branch's guard above) — fall back to "grid".
59
- config.renderCards(rows, view === "table" ? "grid" : view, selectionContext, isLoading)) : (_jsx(AdminViewCards, { rows: rows, view: view === "table" ? "grid" : view, isLoading: isLoading, emptyLabel: config.emptyLabel ?? `No ${config.title.toLowerCase()} found`, onRowClick: (row) => panel.openEditPanel(row.id), selectedIdSet: selection.selectedIdSet, onToggleSelect: selection.toggle }))] }), config.renderFilterPanel && (_jsx(ListingFilterDrawer, { open: filterOpen, onClose: () => setFilterOpen(false), onApply: applyFilters, onClear: clearFilters, activeCount: activeFilterCount, children: config.renderFilterPanel({
79
+ config.renderCards(rows, view === "table" ? "grid" : view, selectionContext, isLoading)) : (_jsx(AdminViewCards, { rows: rows, view: view === "table" ? "grid" : view, isLoading: isLoading, emptyLabel: config.emptyLabel ?? `No ${config.title.toLowerCase()} found`, onRowClick: resolvedRowClick
80
+ ? (row) => resolvedRowClick(row)
81
+ : undefined, selectedIdSet: selection.selectedIdSet, onToggleSelect: selection.toggle, renderRowActions: config.renderRowActions
82
+ ? (row) => config.renderRowActions(row)
83
+ : undefined }))] }), config.renderFilterPanel && (_jsx(ListingFilterDrawer, { open: filterOpen, onClose: () => setFilterOpen(false), onApply: applyFilters, onClear: clearFilters, activeCount: activeFilterCount, children: config.renderFilterPanel({
60
84
  pendingFilters,
61
85
  setPendingFilters,
62
86
  applyFilters,
@@ -11,7 +11,7 @@ export function useAdminListing(config) {
11
11
  // (AdminViewCards' one-full-width-card-per-row layout) unless the caller
12
12
  // explicitly pins hideTableView (grid-only views like coupons); the user's
13
13
  // own explicit choice, once made, always wins and persists across visits.
14
- const { view, setView } = useDataViewMode(config.initialView ?? "table");
14
+ const { view, setView } = useDataViewMode(config.initialView ?? "list");
15
15
  const table = useUrlTable({
16
16
  defaults: { pageSize: String(defaultPageSize), sort: defaultSort, ...filterDefaults },
17
17
  });
@@ -54,7 +54,7 @@ export async function getSellerReviews(storeId) {
54
54
  rating: r.rating,
55
55
  title: r.title,
56
56
  comment: r.comment,
57
- images: r.images?.map((url) => ({ url })),
57
+ images: r.images,
58
58
  status: r.status,
59
59
  helpfulCount: r.helpfulCount,
60
60
  reportCount: r.reportCount,
@@ -85,7 +85,7 @@ export async function getReviewsAuthoredBy(userId) {
85
85
  rating: r.rating,
86
86
  title: r.title,
87
87
  comment: r.comment,
88
- images: r.images?.map((url) => ({ url })),
88
+ images: r.images,
89
89
  status: r.status,
90
90
  helpfulCount: r.helpfulCount,
91
91
  reportCount: r.reportCount,
@@ -110,7 +110,7 @@ export async function getReviewsReceivedBy(userId) {
110
110
  rating: r.rating,
111
111
  title: r.title,
112
112
  comment: r.comment,
113
- images: r.images?.map((url) => ({ url })),
113
+ images: r.images,
114
114
  status: r.status,
115
115
  helpfulCount: r.helpfulCount,
116
116
  reportCount: r.reportCount,
@@ -85,6 +85,8 @@ export declare const RefundStatusValues: {
85
85
  export interface OrderDocumentItem {
86
86
  productId: string;
87
87
  productTitle: string;
88
+ /** Snapshotted from the product/cart-item at order time so the order-detail UI can render a thumbnail without an extra product fetch. */
89
+ image?: string;
88
90
  quantity: number;
89
91
  unitPrice: number;
90
92
  totalPrice: number;
@@ -60,9 +60,7 @@ function toProductItem(doc) {
60
60
  };
61
61
  }
62
62
  function toReview(doc) {
63
- const images = Array.isArray(doc.images)
64
- ? doc.images.map((url) => ({ url }))
65
- : undefined;
63
+ const images = Array.isArray(doc.images) ? doc.images : undefined;
66
64
  const createdAt = doc.createdAt instanceof Date
67
65
  ? doc.createdAt.toISOString()
68
66
  : typeof doc.createdAt === "string"
@@ -292,6 +292,11 @@ export declare const DASHBOARD_QUICK_ACTION_ID: {
292
292
  readonly ADMIN_FAQS: "dqa-admin-faqs";
293
293
  readonly ADMIN_CAROUSEL: "dqa-admin-carousel";
294
294
  readonly ADMIN_SECTIONS: "dqa-admin-sections";
295
+ readonly ADMIN_BLOG: "dqa-admin-blog";
296
+ readonly ADMIN_SCAMMERS: "dqa-admin-scammers";
297
+ readonly ADMIN_BUNDLES: "dqa-admin-bundles";
298
+ readonly ADMIN_PRIZE_DRAWS: "dqa-admin-prize-draws";
299
+ readonly ADMIN_TESTER_CHECKLIST: "dqa-admin-tester-checklist";
295
300
  readonly SELLER_ADD_PRODUCT: "dqa-seller-add-product";
296
301
  readonly SELLER_ADD_COUPON: "dqa-seller-add-coupon";
297
302
  readonly SELLER_VIEW_ORDERS: "dqa-seller-view-orders";
@@ -305,6 +310,12 @@ export declare const DASHBOARD_QUICK_ACTION_ID: {
305
310
  readonly SELLER_PRODUCTS: "dqa-seller-products";
306
311
  readonly SELLER_STOREFRONT: "dqa-seller-storefront";
307
312
  readonly SELLER_ANALYTICS: "dqa-seller-analytics";
313
+ readonly SELLER_BUNDLES: "dqa-seller-bundles";
314
+ readonly SELLER_PRIZE_DRAWS: "dqa-seller-prize-draws";
315
+ readonly SELLER_OFFERS: "dqa-seller-offers";
316
+ readonly SELLER_GROUPED_LISTINGS: "dqa-seller-grouped-listings";
317
+ readonly SELLER_CATEGORIES: "dqa-seller-categories";
318
+ readonly SELLER_PRINT_CENTER: "dqa-seller-print-center";
308
319
  readonly USER_VIEW_ORDERS: "dqa-user-view-orders";
309
320
  readonly USER_VIEW_WISHLIST: "dqa-user-view-wishlist";
310
321
  readonly USER_EDIT_PROFILE: "dqa-user-edit-profile";
@@ -332,8 +343,8 @@ export interface DashboardQuickActionMeta {
332
343
  }
333
344
  export declare const DASHBOARD_QUICK_ACTION_META: Record<DashboardQuickActionId, DashboardQuickActionMeta>;
334
345
  export declare const DASHBOARD_QUICK_ACTIONS: {
335
- readonly admin: readonly ["dqa-admin-add-product", "dqa-admin-add-user", "dqa-admin-add-store", "dqa-admin-add-coupon", "dqa-admin-add-event", "dqa-admin-add-blog", "dqa-admin-users", "dqa-admin-stores", "dqa-admin-orders", "dqa-admin-payouts", "dqa-admin-analytics", "dqa-admin-categories", "dqa-admin-reviews", "dqa-admin-events", "dqa-admin-faqs", "dqa-admin-carousel", "dqa-admin-sections", "dqa-admin-support", "dqa-admin-moderation", "dqa-admin-settings"];
336
- readonly seller: readonly ["dqa-seller-add-product", "dqa-seller-products", "dqa-seller-view-orders", "dqa-seller-analytics", "dqa-seller-add-coupon", "dqa-seller-auctions", "dqa-seller-payout-request", "dqa-seller-shipping", "dqa-seller-messages", "dqa-seller-whatsapp", "dqa-seller-reviews", "dqa-seller-storefront", "dqa-seller-settings"];
346
+ readonly admin: readonly ["dqa-admin-add-product", "dqa-admin-add-user", "dqa-admin-add-store", "dqa-admin-add-coupon", "dqa-admin-add-event", "dqa-admin-add-blog", "dqa-admin-users", "dqa-admin-stores", "dqa-admin-orders", "dqa-admin-payouts", "dqa-admin-analytics", "dqa-admin-categories", "dqa-admin-reviews", "dqa-admin-events", "dqa-admin-faqs", "dqa-admin-carousel", "dqa-admin-sections", "dqa-admin-support", "dqa-admin-moderation", "dqa-admin-blog", "dqa-admin-scammers", "dqa-admin-bundles", "dqa-admin-prize-draws", "dqa-admin-tester-checklist", "dqa-admin-settings"];
347
+ readonly seller: readonly ["dqa-seller-add-product", "dqa-seller-products", "dqa-seller-view-orders", "dqa-seller-analytics", "dqa-seller-add-coupon", "dqa-seller-auctions", "dqa-seller-bundles", "dqa-seller-prize-draws", "dqa-seller-offers", "dqa-seller-grouped-listings", "dqa-seller-categories", "dqa-seller-payout-request", "dqa-seller-shipping", "dqa-seller-print-center", "dqa-seller-messages", "dqa-seller-whatsapp", "dqa-seller-reviews", "dqa-seller-storefront", "dqa-seller-settings"];
337
348
  readonly user: readonly ["dqa-user-view-orders", "dqa-user-view-wishlist", "dqa-user-edit-profile", "dqa-user-add-address", "dqa-user-recently-viewed", "dqa-user-my-catalogue", "dqa-user-my-coupons"];
338
349
  };
339
350
  export declare const ADMIN_BULK_ACTIONS: {