@mohasinac/appkit 2.7.54 → 2.7.56

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 (76) hide show
  1. package/dist/_internal/server/jobs/runtime/adapters/firebase.js +5 -14
  2. package/dist/_internal/shared/actions/action-registry.js +827 -4
  3. package/dist/_internal/shared/features/events/schema.d.ts +4 -4
  4. package/dist/constants/api-endpoints.d.ts +3 -0
  5. package/dist/constants/api-endpoints.js +1 -0
  6. package/dist/features/account/components/UserOffersPanel.js +2 -1
  7. package/dist/features/admin/components/AdminAllEventEntriesView.js +4 -3
  8. package/dist/features/admin/components/AdminBidsView.js +5 -3
  9. package/dist/features/admin/components/AdminBlogView.js +3 -2
  10. package/dist/features/admin/components/AdminBrandsView.js +2 -1
  11. package/dist/features/admin/components/AdminBundleEditorView.js +4 -5
  12. package/dist/features/admin/components/AdminBundlesView.js +3 -3
  13. package/dist/features/admin/components/AdminCarouselView.d.ts +2 -1
  14. package/dist/features/admin/components/AdminCarouselView.js +14 -5
  15. package/dist/features/admin/components/AdminCategoriesView.js +2 -1
  16. package/dist/features/admin/components/AdminContactView.d.ts +4 -1
  17. package/dist/features/admin/components/AdminContactView.js +27 -7
  18. package/dist/features/admin/components/AdminCouponsView.d.ts +3 -1
  19. package/dist/features/admin/components/AdminCouponsView.js +17 -3
  20. package/dist/features/admin/components/AdminFaqsView.d.ts +3 -1
  21. package/dist/features/admin/components/AdminFaqsView.js +18 -4
  22. package/dist/features/admin/components/AdminFeaturesView.js +2 -1
  23. package/dist/features/admin/components/AdminMediaView.js +3 -2
  24. package/dist/features/admin/components/AdminNavigationView.js +3 -2
  25. package/dist/features/admin/components/AdminNewsletterView.d.ts +2 -1
  26. package/dist/features/admin/components/AdminNewsletterView.js +13 -4
  27. package/dist/features/admin/components/AdminNotificationsView.js +6 -4
  28. package/dist/features/admin/components/AdminOrdersView.js +4 -3
  29. package/dist/features/admin/components/AdminPayoutsView.js +2 -2
  30. package/dist/features/admin/components/AdminPrizeDrawsView.js +2 -1
  31. package/dist/features/admin/components/AdminProductsView.js +3 -3
  32. package/dist/features/admin/components/AdminReviewsView.js +4 -3
  33. package/dist/features/admin/components/AdminScammersView.js +2 -1
  34. package/dist/features/admin/components/AdminSessionsView.js +5 -3
  35. package/dist/features/admin/components/AdminStoresView.js +2 -2
  36. package/dist/features/admin/components/AdminSublistingCategoriesView.d.ts +5 -1
  37. package/dist/features/admin/components/AdminSublistingCategoriesView.js +12 -3
  38. package/dist/features/admin/components/AdminSupportTicketsView.js +2 -1
  39. package/dist/features/admin/components/AdminTeamView.d.ts +2 -1
  40. package/dist/features/admin/components/AdminTeamView.js +13 -4
  41. package/dist/features/admin/components/AdminUsersView.js +2 -2
  42. package/dist/features/products/constants/action-defs.d.ts +86 -7
  43. package/dist/features/products/constants/action-defs.js +99 -7
  44. package/dist/features/seller/components/QuickProductForm.d.ts +13 -0
  45. package/dist/features/seller/components/QuickProductForm.js +50 -0
  46. package/dist/features/seller/components/SellerAuctionsView.d.ts +4 -1
  47. package/dist/features/seller/components/SellerAuctionsView.js +37 -3
  48. package/dist/features/seller/components/SellerBidsView.js +2 -1
  49. package/dist/features/seller/components/SellerBundlesView.d.ts +3 -1
  50. package/dist/features/seller/components/SellerBundlesView.js +38 -5
  51. package/dist/features/seller/components/SellerClassifiedView.d.ts +3 -1
  52. package/dist/features/seller/components/SellerClassifiedView.js +37 -4
  53. package/dist/features/seller/components/SellerDigitalCodesView.d.ts +3 -1
  54. package/dist/features/seller/components/SellerDigitalCodesView.js +37 -4
  55. package/dist/features/seller/components/SellerGoogleReviewsView.js +2 -1
  56. package/dist/features/seller/components/SellerGroupedListingsView.js +3 -2
  57. package/dist/features/seller/components/SellerLiveView.d.ts +3 -1
  58. package/dist/features/seller/components/SellerLiveView.js +37 -4
  59. package/dist/features/seller/components/SellerOffersPanel.js +2 -1
  60. package/dist/features/seller/components/SellerOffersView.d.ts +4 -1
  61. package/dist/features/seller/components/SellerOffersView.js +8 -3
  62. package/dist/features/seller/components/SellerPayoutsView.d.ts +3 -1
  63. package/dist/features/seller/components/SellerPayoutsView.js +7 -3
  64. package/dist/features/seller/components/SellerPreOrdersView.d.ts +2 -1
  65. package/dist/features/seller/components/SellerPreOrdersView.js +30 -7
  66. package/dist/features/seller/components/SellerPrizeDrawsView.d.ts +2 -1
  67. package/dist/features/seller/components/SellerPrizeDrawsView.js +30 -7
  68. package/dist/features/seller/components/SellerProductShell.js +12 -0
  69. package/dist/features/seller/components/SellerReviewsView.js +2 -1
  70. package/dist/features/seller/components/SellerShippingConfigsView.js +3 -2
  71. package/dist/features/seller/components/SellerStoreCategoriesView.js +6 -5
  72. package/dist/features/seller/components/SellerTemplatesView.js +4 -3
  73. package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +30 -16
  74. package/dist/styles.css +1 -1
  75. package/dist/tailwind-utilities.css +1 -1
  76. package/package.json +1 -1
@@ -5,8 +5,9 @@ import { Plus, X } from "lucide-react";
5
5
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
6
6
  import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
7
7
  import { usePanelUrlSync } from "../../../react/hooks/use-panel-url-sync";
8
- import { Button, Heading, ListingToolbar, ListingViewShell, Pagination, SideDrawer, Text } from "../../../ui";
8
+ import { BulkActionBar, Button, Heading, ListingToolbar, ListingViewShell, Pagination, SideDrawer, Text } from "../../../ui";
9
9
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
10
+ import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
10
11
  import { toRecordArray, toRelativeDate, toStringValue, useAdminListingData, } from "../hooks/useAdminListingData";
11
12
  import { DataTable } from "./DataTable";
12
13
  import { AdminViewCards } from "./AdminViewCards";
@@ -24,7 +25,7 @@ function FaqsFilterDrawer({ filterOpen, setFilterOpen, activeFilterCount, clearF
24
25
  return null;
25
26
  return (_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: [_jsx("span", { className: "text-base font-semibold text-zinc-900 dark:text-zinc-100", children: "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", 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 space-y-5", children: _jsxs("div", { className: "space-y-2", children: [_jsx(Text, { className: "text-xs font-semibold uppercase tracking-widest text-zinc-500 dark:text-zinc-400", children: "Status" }), _jsx("div", { className: "flex flex-wrap gap-2", children: [{ label: "All", value: "" }, { label: "Published", value: "true" }, { label: "Draft", value: "false" }].map((opt) => (_jsx("button", { type: "button", onClick: () => setPendingFilters((p) => ({ ...p, isActive: opt.value })), className: `rounded-full px-3 py-1 text-xs font-medium border transition-colors ${(pendingFilters.isActive || "") === opt.value ? "bg-primary text-white border-primary" : "border-zinc-300 dark:border-slate-600 text-zinc-700 dark:text-zinc-300 hover:bg-zinc-50 dark:hover:bg-slate-800"}`, children: opt.label }, opt.label))) })] }) }), _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})` : ""] }) })] })] }));
26
27
  }
27
- export function AdminFaqsView({ children, getRowHref, ...props }) {
28
+ export function AdminFaqsView({ children, getRowHref, onBulkArchive, onBulkDelete, ...props }) {
28
29
  const hasChildren = React.Children.count(children) > 0;
29
30
  const [view, setView] = useState("table");
30
31
  const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
@@ -60,7 +61,7 @@ export function AdminFaqsView({ children, getRowHref, ...props }) {
60
61
  const hasActiveState = !!table.get("q") || table.get("sort") !== DEFAULT_SORT || activeFilterCount > 0;
61
62
  const isActiveRaw = table.get("isActive");
62
63
  const filters = isActiveRaw ? `isActive==${isActiveRaw}` : undefined;
63
- const { rows, total, isLoading, errorMessage } = useAdminListingData({
64
+ const { rows, total, isLoading, errorMessage, refetch } = useAdminListingData({
64
65
  queryKey: ["admin", "faqs", "listing"],
65
66
  endpoint: ADMIN_ENDPOINTS.FAQS,
66
67
  page: table.getNumber("page", 1),
@@ -80,8 +81,21 @@ export function AdminFaqsView({ children, getRowHref, ...props }) {
80
81
  const currentPage = table.getNumber("page", 1);
81
82
  const totalPages = Math.ceil(total / PAGE_SIZE);
82
83
  const selection = useBulkSelection({ items: rows, keyExtractor: (r) => r.id });
84
+ const bulkActions = [
85
+ ...(onBulkArchive ? [{
86
+ id: "bulk-archive",
87
+ label: ACTIONS.ADMIN["archive-faq"].label,
88
+ onClick: async () => { await onBulkArchive(selection.selectedIds); selection.clearSelection(); refetch?.(); },
89
+ }] : []),
90
+ ...(onBulkDelete ? [{
91
+ id: "bulk-delete",
92
+ label: ACTIONS.ADMIN["delete-faq"].label,
93
+ variant: "danger",
94
+ onClick: async () => { await onBulkDelete(selection.selectedIds); selection.clearSelection(); refetch?.(); },
95
+ }] : []),
96
+ ];
83
97
  if (hasChildren) {
84
98
  return _jsx(ListingViewShell, { portal: "admin", ...props, children: children });
85
99
  }
86
- return (_jsxs("div", { className: "min-h-screen", children: [_jsx(Heading, { level: 1, className: "sr-only", children: "FAQs" }), _jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search questions, categories, or tokens", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState, extra: _jsxs(Button, { size: "sm", onClick: openCreatePanel, className: "flex items-center gap-1.5", children: [_jsx(Plus, { className: "h-4 w-4" }), "Add FAQ"] }) }), 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: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })), view === "table" ? (_jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No FAQs found", onRowClick: (row) => openEditPanel(row.id) })) : (_jsx(AdminViewCards, { rows: rows, view: view, isLoading: isLoading, emptyLabel: "No FAQs found", onRowClick: undefined, selectedIdSet: selection.selectedIdSet, onToggleSelect: selection.toggle }))] }), _jsx(FaqsFilterDrawer, { filterOpen: filterOpen, setFilterOpen: setFilterOpen, activeFilterCount: activeFilterCount, clearFilters: clearFilters, pendingFilters: pendingFilters, setPendingFilters: setPendingFilters, applyFilters: applyFilters }), _jsx(SideDrawer, { isOpen: isCreateOpen || isEditOpen, onClose: closePanel, title: isCreateOpen ? "Add FAQ" : "Edit FAQ", mode: isCreateOpen ? "create" : "edit", children: (isCreateOpen || isEditOpen) && (_jsx(AdminFaqEditorView, { faqId: editId ?? undefined, onSaved: closePanel, onDeleted: closePanel, embedded: true })) })] }));
100
+ return (_jsxs("div", { className: "min-h-screen", children: [_jsx(Heading, { level: 1, className: "sr-only", children: "FAQs" }), _jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search questions, categories, or tokens", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState, extra: _jsxs(Button, { size: "sm", onClick: openCreatePanel, className: "flex items-center gap-1.5", children: [_jsx(Plus, { className: "h-4 w-4" }), "Add FAQ"] }) }), 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: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), selection.selectedCount > 0 && bulkActions.length > 0 && (_jsx(BulkActionBar, { selectedCount: selection.selectedCount, actions: bulkActions, onClearSelection: selection.clearSelection })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })), view === "table" ? (_jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No FAQs found", onRowClick: (row) => openEditPanel(row.id) })) : (_jsx(AdminViewCards, { rows: rows, view: view, isLoading: isLoading, emptyLabel: "No FAQs found", onRowClick: undefined, selectedIdSet: selection.selectedIdSet, onToggleSelect: selection.toggle }))] }), _jsx(FaqsFilterDrawer, { filterOpen: filterOpen, setFilterOpen: setFilterOpen, activeFilterCount: activeFilterCount, clearFilters: clearFilters, pendingFilters: pendingFilters, setPendingFilters: setPendingFilters, applyFilters: applyFilters }), _jsx(SideDrawer, { isOpen: isCreateOpen || isEditOpen, onClose: closePanel, title: isCreateOpen ? "Add FAQ" : "Edit FAQ", mode: isCreateOpen ? "create" : "edit", children: (isCreateOpen || isEditOpen) && (_jsx(AdminFaqEditorView, { faqId: editId ?? undefined, onSaved: closePanel, onDeleted: closePanel, embedded: true })) })] }));
87
101
  }
@@ -7,6 +7,7 @@ import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
7
7
  import { usePanelUrlSync } from "../../../react/hooks/use-panel-url-sync";
8
8
  import { BulkActionBar, Button, Div, ListingToolbar, ListingViewShell, Pagination, Row, SideDrawer, Text, } from "../../../ui";
9
9
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
10
+ import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
10
11
  import { toRecordArray, toRelativeDate, toStringValue, useAdminListingData, } from "../hooks/useAdminListingData";
11
12
  import { DataTable } from "./DataTable";
12
13
  import { AdminFeatureEditorView } from "./AdminFeatureEditorView";
@@ -92,6 +93,6 @@ export function AdminFeaturesView({ children, ...props }) {
92
93
  }, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState, extra: _jsxs(Button, { size: "sm", onClick: openCreatePanel, className: "flex items-center gap-1.5", children: [_jsx(Plus, { className: "h-4 w-4" }), "Add Feature"] }) }), _jsx(Row, { className: STICKY_TABS_CLASS, gap: "xs", children: PRODUCT_FEATURE_SCOPE_TABS.map((tab) => (_jsx("button", { type: "button", onClick: () => {
93
94
  table.set("scope", tab.value);
94
95
  }, className: tabClass(tab.value), children: tab.label }, tab.value))) }), _jsx(BulkActionBar, { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: [
95
- { id: "delete", label: "Delete Selected", variant: "secondary", onClick: () => { selection.clearSelection(); } },
96
+ { id: "delete", label: ACTIONS.ADMIN["delete-feature"].label, variant: "secondary", onClick: () => { selection.clearSelection(); } },
96
97
  ] }), totalPages > 1 && (_jsx(Div, { className: PAGINATION_BAR_CLASS, children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs(Div, { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx(Text, { as: "div", className: ERROR_BANNER_CLASS, children: errorMessage })), _jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No features found", onRowClick: (row) => openEditPanel(row.id) })] }), _jsx(SideDrawer, { isOpen: isCreateOpen || isEditOpen, onClose: closePanel, title: isCreateOpen ? "Add Feature" : "Edit Feature", mode: isCreateOpen ? "create" : "edit", children: (isCreateOpen || isEditOpen) && (_jsx(AdminFeatureEditorView, { featureId: editId ?? undefined, onSaved: closePanel, onDeleted: closePanel, embedded: true })) })] }));
97
98
  }
@@ -2,6 +2,7 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import React from "react";
4
4
  import { Alert, Button, Div, Input, Select, StackedViewShell, Text, } from "../../../ui";
5
+ import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
5
6
  import { MediaUploadField, MediaUploadList, useMediaCleanup, useMediaUpload, } from "../../media";
6
7
  const PREFIX_OPTIONS = [
7
8
  { value: "", label: "All" },
@@ -61,11 +62,11 @@ function MediaBrowser({ onCopy }) {
61
62
  const isImage = (f.contentType ?? "").startsWith("image/");
62
63
  return (_jsxs(Div, { className: "group rounded-lg border border-zinc-200 dark:border-slate-700 overflow-hidden bg-zinc-50 dark:bg-slate-800", children: [_jsx(Div, { className: "aspect-square flex items-center justify-center bg-zinc-100 dark:bg-slate-700 text-xs text-zinc-500 dark:text-zinc-400", children: isImage ? (
63
64
  // eslint-disable-next-line @next/next/no-img-element
64
- _jsx("img", { src: f.downloadURL, alt: f.name, className: "h-full w-full object-cover", loading: "lazy" })) : (_jsx(Text, { className: "px-2 text-center break-all", children: f.contentType ?? "file" })) }), _jsxs(Div, { className: "p-1.5", children: [_jsx(Text, { className: "truncate text-[10px] text-zinc-600 dark:text-zinc-400 font-mono", children: f.name.split("/").pop() }), _jsx(Button, { type: "button", variant: "ghost", className: "mt-1 w-full text-xs", onClick: () => onCopy(f.downloadURL), children: "Copy URL" })] })] }, f.name));
65
+ _jsx("img", { src: f.downloadURL, alt: f.name, className: "h-full w-full object-cover", loading: "lazy" })) : (_jsx(Text, { className: "px-2 text-center break-all", children: f.contentType ?? "file" })) }), _jsxs(Div, { className: "p-1.5", children: [_jsx(Text, { className: "truncate text-[10px] text-zinc-600 dark:text-zinc-400 font-mono", children: f.name.split("/").pop() }), _jsx(Button, { type: "button", variant: "ghost", className: "mt-1 w-full text-xs", onClick: () => onCopy(f.downloadURL), children: ACTIONS.MEDIA["copy-url"].label })] })] }, f.name));
65
66
  }), filtered.length === 0 && !isLoading && (_jsx(Text, { className: "col-span-full py-6 text-center text-sm text-zinc-500 dark:text-zinc-400", children: "No files found." }))] }), _jsxs(Div, { className: "flex items-center justify-between", children: [_jsxs(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400", children: [filtered.length, " file(s)", nextPageToken ? " · more available" : ""] }), nextPageToken && (_jsx(Button, { type: "button", variant: "outline", onClick: () => void load(nextPageToken), disabled: isLoading, children: isLoading ? "Loading…" : "Load more" }))] })] }));
66
67
  }
67
68
  function MediaUploaderPanel({ onUpload, isUploadPending, isCleanupPending, heroAssetUrl, setHeroAssetUrl, galleryAssets, setGalleryAssets, stagedUrls, setHeroStagedUrls, setGalleryStagedUrls, handleAbort, copiedUrl, onCopy, onClearPreviews, onDiscardStaged, }) {
68
- return (_jsxs(Div, { className: "space-y-5 rounded-xl border border-zinc-200 bg-white p-4 dark:border-slate-700 dark:bg-slate-900", children: [_jsx(Text, { className: "text-sm font-semibold text-zinc-900 dark:text-zinc-100", children: "Upload & Copy URL" }), _jsx(MediaUploadField, { label: "Primary media asset", value: heroAssetUrl, onChange: setHeroAssetUrl, onUpload: onUpload, helperText: "Uploads via signed URL (sign \u2192 PUT \u2192 finalize) and previews the returned URL.", onAbort: handleAbort, onStagedUrlsChange: setHeroStagedUrls }), heroAssetUrl && (_jsxs(Div, { className: "flex items-center gap-2 rounded-lg bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 px-3 py-2", children: [_jsx(Text, { className: "flex-1 truncate text-xs text-zinc-600 dark:text-zinc-400 font-mono", children: heroAssetUrl }), _jsx(Button, { type: "button", variant: "outline", onClick: () => onCopy(heroAssetUrl), children: copiedUrl === heroAssetUrl ? "Copied!" : "Copy URL" })] })), _jsx(MediaUploadList, { label: "Gallery assets", value: galleryAssets, onChange: setGalleryAssets, onUpload: onUpload, helperText: "Attach multiple images or videos for batch checks.", onAbort: handleAbort, onStagedUrlsChange: setGalleryStagedUrls, maxItems: 12 }), galleryAssets.length > 0 && (_jsxs(Div, { className: "flex flex-col gap-1", children: [_jsx(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400 font-medium", children: "Gallery URLs" }), galleryAssets.map((asset, i) => (_jsxs(Div, { className: "flex items-center gap-2 rounded bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 px-2 py-1", children: [_jsx(Text, { className: "flex-1 truncate text-xs text-zinc-600 dark:text-zinc-400 font-mono", children: asset.url }), _jsx(Button, { type: "button", variant: "ghost", onClick: () => onCopy(asset.url), children: copiedUrl === asset.url ? "Copied!" : "Copy" })] }, i)))] })), _jsxs(Div, { className: "flex flex-wrap items-center gap-2", children: [_jsx(Button, { type: "button", variant: "outline", onClick: onClearPreviews, disabled: isUploadPending || isCleanupPending, children: "Clear previews" }), _jsx(Button, { type: "button", variant: "outline", onClick: onDiscardStaged, disabled: isUploadPending || isCleanupPending || stagedUrls.length === 0, children: isCleanupPending ? "Discarding…" : "Discard staged uploads" }), _jsxs(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400", children: [stagedUrls.length, " staged upload(s)"] })] })] }));
69
+ return (_jsxs(Div, { className: "space-y-5 rounded-xl border border-zinc-200 bg-white p-4 dark:border-slate-700 dark:bg-slate-900", children: [_jsx(Text, { className: "text-sm font-semibold text-zinc-900 dark:text-zinc-100", children: "Upload & Copy URL" }), _jsx(MediaUploadField, { label: "Primary media asset", value: heroAssetUrl, onChange: setHeroAssetUrl, onUpload: onUpload, helperText: "Uploads via signed URL (sign \u2192 PUT \u2192 finalize) and previews the returned URL.", onAbort: handleAbort, onStagedUrlsChange: setHeroStagedUrls }), heroAssetUrl && (_jsxs(Div, { className: "flex items-center gap-2 rounded-lg bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 px-3 py-2", children: [_jsx(Text, { className: "flex-1 truncate text-xs text-zinc-600 dark:text-zinc-400 font-mono", children: heroAssetUrl }), _jsx(Button, { type: "button", variant: "outline", onClick: () => onCopy(heroAssetUrl), children: copiedUrl === heroAssetUrl ? "Copied!" : ACTIONS.MEDIA["copy-url"].label })] })), _jsx(MediaUploadList, { label: "Gallery assets", value: galleryAssets, onChange: setGalleryAssets, onUpload: onUpload, helperText: "Attach multiple images or videos for batch checks.", onAbort: handleAbort, onStagedUrlsChange: setGalleryStagedUrls, maxItems: 12 }), galleryAssets.length > 0 && (_jsxs(Div, { className: "flex flex-col gap-1", children: [_jsx(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400 font-medium", children: "Gallery URLs" }), galleryAssets.map((asset, i) => (_jsxs(Div, { className: "flex items-center gap-2 rounded bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 px-2 py-1", children: [_jsx(Text, { className: "flex-1 truncate text-xs text-zinc-600 dark:text-zinc-400 font-mono", children: asset.url }), _jsx(Button, { type: "button", variant: "ghost", onClick: () => onCopy(asset.url), children: copiedUrl === asset.url ? "Copied!" : "Copy" })] }, i)))] })), _jsxs(Div, { className: "flex flex-wrap items-center gap-2", children: [_jsx(Button, { type: "button", variant: "outline", onClick: onClearPreviews, disabled: isUploadPending || isCleanupPending, children: ACTIONS.MEDIA["clear-previews"].label }), _jsx(Button, { type: "button", variant: "outline", onClick: onDiscardStaged, disabled: isUploadPending || isCleanupPending || stagedUrls.length === 0, children: isCleanupPending ? "Discarding…" : ACTIONS.MEDIA["discard-staged"].label }), _jsxs(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400", children: [stagedUrls.length, " staged upload(s)"] })] })] }));
69
70
  }
70
71
  export function AdminMediaView({ labels = {}, children, ...rest }) {
71
72
  const hasChildren = React.Children.count(children) > 0;
@@ -6,10 +6,11 @@ import { Alert, Button, ConfirmDeleteModal, RowActionMenu, StackedViewShell, Tex
6
6
  import { apiClient } from "../../../http";
7
7
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
8
8
  import { AdminNavEditorView } from "./AdminNavEditorView";
9
+ import { ROW_ACTION_META, ROW_ACTION_ID } from "../../../features/products/constants/action-defs";
9
10
  function NavItemRow({ item, idx, total, reorderPending, onMoveUp, onMoveDown, onVisibilityChange, onEdit, onDelete, }) {
10
11
  return (_jsxs("div", { className: "flex items-center gap-3 px-4 py-3 bg-white dark:bg-zinc-900", children: [_jsxs("div", { className: "flex flex-col gap-0.5 shrink-0", children: [_jsx("button", { type: "button", onClick: () => onMoveUp(idx), disabled: idx === 0 || reorderPending, className: "text-zinc-400 hover:text-zinc-700 dark:hover:text-zinc-200 disabled:opacity-30 leading-none text-xs", "aria-label": "Move up", children: "\u25B2" }), _jsx("button", { type: "button", onClick: () => onMoveDown(idx), disabled: idx >= total - 1 || reorderPending, className: "text-zinc-400 hover:text-zinc-700 dark:hover:text-zinc-200 disabled:opacity-30 leading-none text-xs", "aria-label": "Move down", children: "\u25BC" })] }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsxs(Text, { className: "text-sm font-medium text-zinc-800 dark:text-zinc-100 truncate", children: [item.parentId ? _jsx("span", { className: "text-zinc-400 mr-1", children: "\u21B3" }) : null, item.label] }), _jsx(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400 truncate", children: item.href })] }), _jsx(Toggle, { checked: item.isVisible ?? true, onChange: (val) => onVisibilityChange(item.id, val), label: "" }), _jsx(RowActionMenu, { actions: [
11
- { label: "Edit", onClick: () => onEdit(item) },
12
- { label: "Delete", destructive: true, onClick: () => onDelete(item) },
12
+ { label: ROW_ACTION_META[ROW_ACTION_ID.EDIT].label, onClick: () => onEdit(item) },
13
+ { label: ROW_ACTION_META[ROW_ACTION_ID.DELETE].label, destructive: true, onClick: () => onDelete(item) },
13
14
  ] })] }));
14
15
  }
15
16
  // --- Component ---------------------------------------------------------------
@@ -1,4 +1,5 @@
1
1
  import type { ListingViewShellProps } from "../../../ui";
2
2
  export interface AdminNewsletterViewProps extends ListingViewShellProps {
3
+ onBulkUnsubscribe?: (ids: string[]) => Promise<void>;
3
4
  }
4
- export declare function AdminNewsletterView({ children, ...props }: AdminNewsletterViewProps): import("react/jsx-runtime").JSX.Element;
5
+ export declare function AdminNewsletterView({ children, onBulkUnsubscribe, ...props }: AdminNewsletterViewProps): import("react/jsx-runtime").JSX.Element;
@@ -5,8 +5,9 @@ import { X } from "lucide-react";
5
5
  import { useMutation, useQueryClient } from "@tanstack/react-query";
6
6
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
7
7
  import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
8
- import { Button, ConfirmDeleteModal, FilterChipGroup, ListingToolbar, ListingViewShell, Pagination, RowActionMenu, useToast, } from "../../../ui";
8
+ import { BulkActionBar, Button, ConfirmDeleteModal, FilterChipGroup, ListingToolbar, ListingViewShell, Pagination, RowActionMenu, useToast, } from "../../../ui";
9
9
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
10
+ import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
10
11
  import { ADMIN_NEWSLETTER_STATUS_TABS } from "../constants/filter-tabs";
11
12
  import { toRecordArray, toRelativeDate, toStringValue, useAdminListingData, } from "../hooks/useAdminListingData";
12
13
  import { DataTable } from "./DataTable";
@@ -24,7 +25,7 @@ function NewsletterFilterDrawer({ filterOpen, setFilterOpen, activeFilterCount,
24
25
  return null;
25
26
  return (_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: [_jsx("span", { className: "text-base font-semibold text-zinc-900 dark:text-zinc-100", children: "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", 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 space-y-5", children: _jsx(FilterChipGroup, { label: "Status", tabs: STATUS_OPTIONS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) }) }), _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})` : ""] }) })] })] }));
26
27
  }
27
- export function AdminNewsletterView({ children, ...props }) {
28
+ export function AdminNewsletterView({ children, onBulkUnsubscribe, ...props }) {
28
29
  const hasChildren = React.Children.count(children) > 0;
29
30
  const [view, setView] = useState("table");
30
31
  const queryClient = useQueryClient();
@@ -123,14 +124,22 @@ export function AdminNewsletterView({ children, ...props }) {
123
124
  const currentPage = table.getNumber("page", 1);
124
125
  const totalPages = Math.ceil(total / PAGE_SIZE);
125
126
  const selection = useBulkSelection({ items: rows, keyExtractor: (r) => r.id });
127
+ const bulkActions = [
128
+ ...(onBulkUnsubscribe ? [{
129
+ id: "bulk-unsubscribe",
130
+ label: ACTIONS.ADMIN["unsubscribe-newsletter"].label,
131
+ variant: "danger",
132
+ onClick: async () => { await onBulkUnsubscribe(selection.selectedIds); selection.clearSelection(); },
133
+ }] : []),
134
+ ];
126
135
  if (hasChildren) {
127
136
  return _jsx(ListingViewShell, { portal: "admin", ...props, children: children });
128
137
  }
129
- return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search by email or source", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState, extra: _jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: handleExportCsv, children: "Export CSV" }) }), 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: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })), _jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No subscribers found", renderRowActions: (row) => {
138
+ return (_jsxs(_Fragment, { children: [selection.selectedCount > 0 && bulkActions.length > 0 && (_jsx(BulkActionBar, { selectedCount: selection.selectedCount, actions: bulkActions, onClearSelection: selection.clearSelection })), _jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search by email or source", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState, extra: _jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: handleExportCsv, children: ACTIONS.ADMIN["export-csv"].label }) }), 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: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })), _jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No subscribers found", renderRowActions: (row) => {
130
139
  const nr = row;
131
140
  return (_jsx(RowActionMenu, { actions: [
132
141
  {
133
- label: "Unsubscribe",
142
+ label: ACTIONS.ADMIN["unsubscribe-newsletter"].label,
134
143
  destructive: true,
135
144
  disabled: nr.status === "unsubscribed",
136
145
  onClick: () => { setSelectedRow(nr); setUnsubscribeOpen(true); },
@@ -7,6 +7,8 @@ import { useUrlTable } from "../../../react/hooks/useUrlTable";
7
7
  import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
8
8
  import { BulkActionBar, ConfirmDeleteModal, ListingToolbar, ListingViewShell, Pagination, RowActionMenu, Text, useToast } from "../../../ui";
9
9
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
10
+ import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
11
+ import { ROW_ACTION_META, ROW_ACTION_ID } from "../../products/constants/action-defs";
10
12
  import { toRecordArray, toRelativeDate, toStringValue, useAdminListingData, } from "../hooks/useAdminListingData";
11
13
  import { DataTable } from "./DataTable";
12
14
  import { apiClient } from "../../../http";
@@ -117,13 +119,13 @@ export function AdminNotificationsView({ children, ...props }) {
117
119
  return _jsx(ListingViewShell, { portal: "admin", ...props, children: children });
118
120
  }
119
121
  return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search by title or user ID", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState }), _jsx(BulkActionBar, { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: [
120
- { id: "mark-read", label: "Mark Read", variant: "primary", onClick: () => { selection.clearSelection(); } },
121
- { id: "delete", label: "Delete Selected", variant: "secondary", onClick: () => { selection.clearSelection(); } },
122
+ { id: ROW_ACTION_ID.MARK_READ, label: ACTIONS.ADMIN["mark-read"].label, variant: "primary", onClick: () => { selection.clearSelection(); } },
123
+ { id: ROW_ACTION_ID.DELETE, label: ACTIONS.ADMIN["delete-notification"].label, variant: "secondary", onClick: () => { selection.clearSelection(); } },
122
124
  ] }), 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: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })), _jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No notifications found", renderRowActions: (row) => {
123
125
  const nr = row;
124
126
  return (_jsx(RowActionMenu, { actions: [
125
- { label: "Resend", onClick: () => resendMutation.mutate(nr.id) },
126
- { label: "Delete", destructive: true, onClick: () => setDeleteTarget(nr) },
127
+ { label: ACTIONS.ADMIN["resend-notification"].label, onClick: () => resendMutation.mutate(nr.id) },
128
+ { label: ROW_ACTION_META[ROW_ACTION_ID.DELETE].label, destructive: ROW_ACTION_META[ROW_ACTION_ID.DELETE].destructive, onClick: () => setDeleteTarget(nr) },
127
129
  ] }));
128
130
  } })] }), _jsx(NotificationsFilterDrawer, { filterOpen: filterOpen, setFilterOpen: setFilterOpen, activeFilterCount: activeFilterCount, clearFilters: clearFilters, pendingFilters: pendingFilters, setPendingFilters: setPendingFilters, applyFilters: applyFilters })] }), _jsx(ConfirmDeleteModal, { isOpen: Boolean(deleteTarget), onClose: () => setDeleteTarget(null), onConfirm: () => { if (deleteTarget)
129
131
  deleteMutation.mutate(deleteTarget.id); }, isDeleting: deleteMutation.isPending, title: "Delete notification?", message: "This notification will be permanently removed.", confirmText: "Delete", variant: "danger" })] }));
@@ -9,6 +9,7 @@ import { apiClient } from "../../../http";
9
9
  import { QuickEditMenu } from "./QuickEditMenu";
10
10
  import { AdminViewCards } from "./AdminViewCards";
11
11
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
12
+ import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
12
13
  import { ADMIN_ORDER_STATUS_TABS } from "../constants/filter-tabs";
13
14
  import { toRecordArray, toRelativeDate, toRupees, toStringValue, useAdminListingData, } from "../hooks/useAdminListingData";
14
15
  import { DataTable } from "./DataTable";
@@ -102,11 +103,11 @@ export function AdminOrdersView({ children, ...props }) {
102
103
  return _jsx(ListingViewShell, { portal: "admin", ...props, children: children });
103
104
  }
104
105
  return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search orders, buyers, or tracking IDs", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState }), _jsx(BulkActionBar, { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: [
105
- { id: "mark-shipped", label: "Mark as Shipped", variant: "secondary", onClick: () => { for (const id of selection.selectedIds)
106
+ { id: "mark-shipped", label: ACTIONS.ADMIN["mark-shipped"].label, variant: "secondary", onClick: () => { for (const id of selection.selectedIds)
106
107
  void handleQuickStatus(id, "SHIPPED"); selection.clearSelection(); } },
107
- { id: "mark-delivered", label: "Mark as Delivered", variant: "primary", onClick: () => { for (const id of selection.selectedIds)
108
+ { id: "mark-delivered", label: ACTIONS.ADMIN["mark-delivered"].label, variant: "primary", onClick: () => { for (const id of selection.selectedIds)
108
109
  void handleQuickStatus(id, "DELIVERED"); selection.clearSelection(); } },
109
- { id: "mark-cancelled", label: "Cancel Orders", variant: "danger", onClick: () => { for (const id of selection.selectedIds)
110
+ { id: "mark-cancelled", label: ACTIONS.ADMIN["cancel-order"].label, variant: "danger", onClick: () => { for (const id of selection.selectedIds)
110
111
  void handleQuickStatus(id, "CANCELLED"); selection.clearSelection(); } },
111
112
  ] }), 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: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })), view === "table" ? (_jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No orders found", selectedIds: selection.selectedIdSet, onToggleSelect: selection.toggle, onToggleSelectAll: (next) => next ? selection.setSelectedIds(rows.map(r => r.id)) : selection.clearSelection(), renderRowActions: (row) => (_jsx(QuickEditMenu, { actions: [
112
113
  {
@@ -130,8 +130,8 @@ export function AdminPayoutsView({ children, ...props }) {
130
130
  if (hasChildren) {
131
131
  return _jsx(ListingViewShell, { portal: "admin", ...props, children: children });
132
132
  }
133
- return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search stores, payout IDs, or order groups", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState, extra: _jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: handleExportCsv, children: "Export CSV" }) }), _jsx(BulkActionBar, { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: [
134
- { id: "mark-paid", label: "Mark Paid", variant: "primary", onClick: () => { selection.clearSelection(); } },
133
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search stores, payout IDs, or order groups", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState, extra: _jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: handleExportCsv, children: ACTIONS.ADMIN["export-csv"].label }) }), _jsx(BulkActionBar, { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: [
134
+ { id: "mark-paid", label: ACTIONS.ADMIN["mark-paid"].label, variant: "primary", onClick: () => { selection.clearSelection(); } },
135
135
  ] }), 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: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })), _jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No payouts found", renderRowActions: (row) => {
136
136
  const pr = row;
137
137
  return (_jsx(RowActionMenu, { actions: [
@@ -6,6 +6,7 @@ import { useUrlTable } from "../../../react/hooks/useUrlTable";
6
6
  import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
7
7
  import { Alert, Badge, BulkActionBar, Button, FilterChipGroup, ListingToolbar, ListingViewShell, Pagination, Text } from "../../../ui";
8
8
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
9
+ import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
9
10
  import { ADMIN_PRODUCT_STATUS_TABS } from "../constants/filter-tabs";
10
11
  import { ROUTES } from "../../../constants";
11
12
  import { toRecordArray, toRelativeDate, toRupees, toStringValue, useAdminListingData, } from "../hooks/useAdminListingData";
@@ -139,6 +140,6 @@ export function AdminPrizeDrawsView({ children, ...props }) {
139
140
  return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search prize draws by name or store\u2026", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => {
140
141
  table.set("sort", v);
141
142
  }, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState }), _jsx(BulkActionBar, { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: [
142
- { id: "delete", label: "Delete Selected", variant: "secondary", onClick: () => { selection.clearSelection(); } },
143
+ { id: "delete", label: ACTIONS.ADMIN["delete-prize-draw"].label, variant: "secondary", onClick: () => { selection.clearSelection(); } },
143
144
  ] }), totalPages > 1 && (_jsx("div", { className: "sticky z-10 flex justify-center bg-[var(--appkit-color-surface)]/95 backdrop-blur-sm border-b border-[var(--appkit-color-border)] px-3 py-1.5", style: { top: "calc(var(--header-height, 0px) + 44px)" }, children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx(Alert, { variant: "error", className: "mb-4", children: errorMessage })), _jsx(DataTable, { columns: PRIZE_DRAW_COLUMNS, rows: rows, isLoading: isLoading, emptyLabel: "No prize draws found", getRowHref: (row) => String(ROUTES.ADMIN.PRIZE_DRAWS_EDIT(row.id)), renderRowActions: (row) => (_jsx(Button, { variant: "ghost", size: "sm", asChild: true, children: _jsx("a", { href: String(ROUTES.ADMIN.PRIZE_DRAWS_EDIT(row.id)), "aria-label": "Edit", children: _jsx(Pencil, { className: "w-4 h-4" }) }) })) })] }), _jsx(PrizeDrawsFilterDrawer, { filterOpen: filterOpen, setFilterOpen: setFilterOpen, activeFilterCount: activeFilterCount, clearFilters: clearFilters, pendingFilters: pendingFilters, setPendingFilters: setPendingFilters, applyFilters: applyFilters })] }));
144
145
  }
@@ -182,11 +182,11 @@ export function AdminProductsView({ children, actionHref, getRowHref, ...props }
182
182
  return (_jsxs("div", { className: "min-h-screen", children: [_jsx(Heading, { level: 1, className: "sr-only", children: "Products" }), _jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search products, SKUs, or seller names", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState, toggles: [
183
183
  { label: "Show sold", active: showSold, onChange: (next) => table.set("showSold", next ? "true" : "") },
184
184
  ], extra: _jsxs(Button, { size: "sm", onClick: openCreatePanel, className: "flex items-center gap-1.5", children: [_jsx(Plus, { className: "h-4 w-4" }), "Add Product"] }) }), _jsx(BulkActionBar, { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: [
185
- { id: "feature", label: "Toggle Featured", variant: "secondary", onClick: () => { for (const id of selection.selectedIds)
185
+ { id: "feature", label: ACTIONS.ADMIN["toggle-featured"].label, variant: "secondary", onClick: () => { for (const id of selection.selectedIds)
186
186
  void handleToggle(id, "featured", !rows.find(r => r.id === id)?.featured); selection.clearSelection(); } },
187
- { id: "promote", label: "Toggle Promoted", variant: "secondary", onClick: () => { for (const id of selection.selectedIds)
187
+ { id: "promote", label: ACTIONS.ADMIN["toggle-promoted"].label, variant: "secondary", onClick: () => { for (const id of selection.selectedIds)
188
188
  void handleToggle(id, "isPromoted", !rows.find(r => r.id === id)?.isPromoted); selection.clearSelection(); } },
189
- { id: "sale", label: "Toggle On Sale", variant: "secondary", onClick: () => { for (const id of selection.selectedIds)
189
+ { id: "sale", label: ACTIONS.ADMIN["toggle-on-sale"].label, variant: "secondary", onClick: () => { for (const id of selection.selectedIds)
190
190
  void handleToggle(id, "isOnSale", !rows.find(r => r.id === id)?.isOnSale); selection.clearSelection(); } },
191
191
  ] }), 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: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })), view === "table" ? (_jsx(DataTable, { rows: rows, columns: [...buildBaseColumns(), flagColumn], isLoading: isLoading, emptyLabel: "No products found", onRowClick: (row) => openEditPanel(row.id), selectedIds: selection.selectedIdSet, onToggleSelect: selection.toggle, onToggleSelectAll: (next) => next ? selection.setSelectedIds(rows.map(r => r.id)) : selection.clearSelection(), renderRowActions: (row) => (_jsx(QuickEditMenu, { actions: [
192
192
  {
@@ -8,6 +8,7 @@ import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
8
8
  import { BulkActionBar, FilterChipGroup, ListingToolbar, Pagination, ListingViewShell, Modal, RowActionMenu, Button, useToast } from "../../../ui";
9
9
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
10
10
  import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
11
+ import { ROW_ACTION_META, ROW_ACTION_ID } from "../../../features/products/constants/action-defs";
11
12
  import { ADMIN_REVIEW_STATUS_TABS, ADMIN_REVIEW_RATING_TABS } from "../constants/filter-tabs";
12
13
  import { toRecordArray, toRelativeDate, toStringValue, useAdminListingData, } from "../hooks/useAdminListingData";
13
14
  import { DataTable } from "./DataTable";
@@ -159,15 +160,15 @@ export function AdminReviewsView({ renderDetailView, children, ...props }) {
159
160
  { label: ACTIONS.ADMIN["approve-review"].label, onClick: () => patchMutation.mutate({ id: rr.id, payload: { status: "approved" } }) },
160
161
  { label: ACTIONS.ADMIN["reject-review"].label, destructive: true, onClick: () => patchMutation.mutate({ id: rr.id, payload: { status: "rejected" } }) },
161
162
  {
162
- label: rr.isFeatured ? "Unfeature" : "Feature",
163
+ label: rr.isFeatured ? ACTIONS.ADMIN["unfeature-review"].label : ACTIONS.ADMIN["feature-review"].label,
163
164
  onClick: () => patchMutation.mutate({ id: rr.id, payload: { featured: !rr.isFeatured } }),
164
165
  },
165
166
  {
166
- label: "Reply",
167
+ label: ROW_ACTION_META[ROW_ACTION_ID.REPLY].label,
167
168
  onClick: () => { setReplyTarget(rr); setReplyText(""); setReplyOpen(true); },
168
169
  },
169
170
  {
170
- label: "View",
171
+ label: ROW_ACTION_META[ROW_ACTION_ID.VIEW].label,
171
172
  onClick: () => handleViewReview(rr),
172
173
  },
173
174
  ] }));
@@ -6,6 +6,7 @@ import { useUrlTable } from "../../../react/hooks/useUrlTable";
6
6
  import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
7
7
  import { FilterChipGroup, ListingToolbar, ListingViewShell, Pagination, RowActionMenu, Text } from "../../../ui";
8
8
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
9
+ import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
9
10
  import { ADMIN_SCAMMER_STATUS_TABS } from "../constants/filter-tabs";
10
11
  import { toRecordArray, toRelativeDate, toStringValue, useAdminListingData, } from "../hooks/useAdminListingData";
11
12
  import { DataTable } from "./DataTable";
@@ -135,7 +136,7 @@ export function AdminScammersView({ children, ...props }) {
135
136
  table.set("sort", v);
136
137
  }, showTableView: true, view: view, onViewChange: (v) => setView(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: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })), _jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No scammer profiles found", renderRowActions: (row) => (_jsx(RowActionMenu, { actions: [
137
138
  {
138
- label: "Review",
139
+ label: ACTIONS.ADMIN["review-scammer"].label,
139
140
  onClick: () => {
140
141
  setSelectedRow(row);
141
142
  setDrawerOpen(true);
@@ -7,6 +7,8 @@ import { useUrlTable } from "../../../react/hooks/useUrlTable";
7
7
  import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
8
8
  import { BulkActionBar, ConfirmDeleteModal, ListingToolbar, ListingViewShell, Pagination, RowActionMenu, Text, useToast } from "../../../ui";
9
9
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
10
+ import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
11
+ import { ROW_ACTION_META, ROW_ACTION_ID } from "../../products/constants/action-defs";
10
12
  import { toRecordArray, toRelativeDate, toStringValue, useAdminListingData, } from "../hooks/useAdminListingData";
11
13
  import { DataTable } from "./DataTable";
12
14
  import { apiClient } from "../../../http";
@@ -117,11 +119,11 @@ export function AdminSessionsView({ children, ...props }) {
117
119
  return _jsx(ListingViewShell, { portal: "admin", ...props, children: children });
118
120
  }
119
121
  return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search by user or device", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState }), _jsx(BulkActionBar, { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: [
120
- { id: "revoke", label: "Revoke Sessions", variant: "secondary", onClick: () => { selection.clearSelection(); } },
122
+ { id: ROW_ACTION_ID.REVOKE, label: ACTIONS.ADMIN["revoke-session"].label, variant: "secondary", onClick: () => { selection.clearSelection(); } },
121
123
  ] }), 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: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })), _jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No sessions found", renderRowActions: (row) => (_jsx(RowActionMenu, { actions: [
122
124
  {
123
- label: "Revoke",
124
- destructive: true,
125
+ label: ROW_ACTION_META[ROW_ACTION_ID.REVOKE].label,
126
+ destructive: ROW_ACTION_META[ROW_ACTION_ID.REVOKE].destructive,
125
127
  onClick: () => setRevokeTarget(row),
126
128
  },
127
129
  ] })) })] }), _jsx(SessionsFilterDrawer, { filterOpen: filterOpen, setFilterOpen: setFilterOpen, activeFilterCount: activeFilterCount, clearFilters: clearFilters, pendingFilters: pendingFilters, setPendingFilters: setPendingFilters, applyFilters: applyFilters })] }), _jsx(ConfirmDeleteModal, { isOpen: Boolean(revokeTarget), onClose: () => setRevokeTarget(null), onConfirm: () => { if (revokeTarget)
@@ -112,7 +112,7 @@ export function AdminStoresView({ children, ...props }) {
112
112
  return _jsx(ListingViewShell, { portal: "admin", ...props, children: children });
113
113
  }
114
114
  return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search stores, slugs, or owner names", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState }), _jsx(BulkActionBar, { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: [
115
- { id: "manage", label: "Manage Store", variant: "primary", onClick: () => { const id = selection.selectedIds[0]; if (id)
115
+ { id: "manage", label: ACTIONS.ADMIN["manage-store"].label, variant: "primary", onClick: () => { const id = selection.selectedIds[0]; if (id)
116
116
  openEditPanel(id); selection.clearSelection(); } },
117
117
  ] }), 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: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })), view === "table" ? (_jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No stores found", selectedIds: selection.selectedIdSet, onToggleSelect: selection.toggle, onToggleSelectAll: (next) => next ? selection.setSelectedIds(rows.map(r => r.id)) : selection.clearSelection(), renderRowActions: (row) => {
118
118
  const sr = row;
@@ -120,7 +120,7 @@ export function AdminStoresView({ children, ...props }) {
120
120
  const isVerified = Boolean(sr._raw?.isVerified);
121
121
  return (_jsx(RowActionMenu, { actions: [
122
122
  {
123
- label: "Manage",
123
+ label: ACTIONS.ADMIN["manage-store"].label,
124
124
  onClick: () => openEditPanel(sr.id),
125
125
  },
126
126
  {
@@ -1 +1,5 @@
1
- export declare function AdminSublistingCategoriesView(): import("react/jsx-runtime").JSX.Element;
1
+ interface AdminSublistingCategoriesViewProps {
2
+ onBulkDelete?: (ids: string[]) => Promise<void>;
3
+ }
4
+ export declare function AdminSublistingCategoriesView({ onBulkDelete }: AdminSublistingCategoriesViewProps): import("react/jsx-runtime").JSX.Element;
5
+ export {};
@@ -3,8 +3,9 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useState, useCallback } from "react";
4
4
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
5
5
  import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
6
- import { ListingToolbar, Pagination } from "../../../ui";
6
+ import { BulkActionBar, ListingToolbar, Pagination } from "../../../ui";
7
7
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
8
+ import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
8
9
  import { toRecordArray, toRelativeDate, toStringValue, useAdminListingData, } from "../hooks/useAdminListingData";
9
10
  import { DataTable } from "./DataTable";
10
11
  const PAGE_SIZE = 25;
@@ -15,7 +16,7 @@ const SORT_OPTIONS = [
15
16
  { value: "-productCount", label: "Most listings" },
16
17
  { value: "-createdAt", label: "Newest" },
17
18
  ];
18
- export function AdminSublistingCategoriesView() {
19
+ export function AdminSublistingCategoriesView({ onBulkDelete }) {
19
20
  const [view, setView] = useState("table");
20
21
  const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
21
22
  const [searchInput, setSearchInput] = useState(table.get("q") || "");
@@ -51,7 +52,15 @@ export function AdminSublistingCategoriesView() {
51
52
  const currentPage = table.getNumber("page", 1);
52
53
  const totalPages = Math.ceil(total / PAGE_SIZE);
53
54
  const selection = useBulkSelection({ items: rows ?? [], keyExtractor: (r) => r.id });
55
+ const bulkActions = [
56
+ ...(onBulkDelete ? [{
57
+ id: "bulk-delete",
58
+ label: ACTIONS.ADMIN["delete-sublisting-category"].label,
59
+ variant: "danger",
60
+ onClick: async () => { await onBulkDelete(selection.selectedIds); selection.clearSelection(); },
61
+ }] : []),
62
+ ];
54
63
  return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { searchValue: searchInput, searchPlaceholder: "Search sub-listing categories\u2026", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => {
55
64
  table.set("sort", v);
56
- }, showTableView: true, view: view, onViewChange: (v) => setView(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: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })), _jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No sub-listing categories found" })] })] }));
65
+ }, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState }), selection.selectedCount > 0 && bulkActions.length > 0 && (_jsx(BulkActionBar, { selectedCount: selection.selectedCount, actions: bulkActions, onClearSelection: selection.clearSelection })), 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: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })), _jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No sub-listing categories found" })] })] }));
57
66
  }
@@ -6,6 +6,7 @@ import { useUrlTable } from "../../../react/hooks/useUrlTable";
6
6
  import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
7
7
  import { FilterChipGroup, ListingToolbar, ListingViewShell, Pagination, RowActionMenu, Text } from "../../../ui";
8
8
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
9
+ import { ROW_ACTION_META, ROW_ACTION_ID } from "../../../features/products/constants/action-defs";
9
10
  import { ADMIN_SUPPORT_TICKET_STATUS_TABS, ADMIN_SUPPORT_TICKET_PRIORITY_TABS, } from "../constants/filter-tabs";
10
11
  import { toRecordArray, toRelativeDate, toStringValue, useAdminListingData, } from "../hooks/useAdminListingData";
11
12
  import { DataTable } from "./DataTable";
@@ -144,7 +145,7 @@ export function AdminSupportTicketsView({ children, ...props }) {
144
145
  return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search by subject", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => {
145
146
  table.set("sort", v);
146
147
  }, showTableView: true, view: view, onViewChange: (v) => setView(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: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })), _jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No support tickets found", renderRowActions: (row) => (_jsx(RowActionMenu, { actions: [
147
- { label: "View", onClick: () => { setSelectedRow(row); setDrawerOpen(true); } },
148
+ { label: ROW_ACTION_META[ROW_ACTION_ID.VIEW].label, onClick: () => { setSelectedRow(row); setDrawerOpen(true); } },
148
149
  ] })), columns: buildTicketColumns(setSelectedRow, setDrawerOpen) })] }), _jsx(SupportTicketsFilterDrawer, { filterOpen: filterOpen, setFilterOpen: setFilterOpen, activeFilterCount: activeFilterCount, clearFilters: clearFilters, pendingFilters: pendingFilters, setPendingFilters: setPendingFilters, applyFilters: applyFilters })] }), _jsx(AdminSupportTicketDetailView, { open: drawerOpen, onClose: () => setDrawerOpen(false), ticketId: selectedRow?.id, subject: selectedRow?.primary, userDisplayName: toStringValue(selectedRow?._raw?.userDisplayName, ""), category: toStringValue(selectedRow?._raw?.category, "general"), currentStatus: selectedRow?.status, currentPriority: toStringValue(selectedRow?._raw?.priority, "normal"), description: toStringValue(selectedRow?._raw?.description, ""), messages: Array.isArray(selectedRow?._raw?.messages)
149
150
  ? selectedRow._raw.messages
150
151
  : [], internalNotes: toStringValue(selectedRow?._raw?.internalNotes, "") || undefined, orderId: toStringValue(selectedRow?._raw?.orderId, "") || undefined })] }));
@@ -1,4 +1,5 @@
1
1
  import type { ListingViewShellProps } from "../../../ui";
2
2
  export interface AdminTeamViewProps extends ListingViewShellProps {
3
+ onBulkRemove?: (ids: string[]) => Promise<void>;
3
4
  }
4
- export declare function AdminTeamView({ children, ...props }: AdminTeamViewProps): import("react/jsx-runtime").JSX.Element;
5
+ export declare function AdminTeamView({ children, onBulkRemove, ...props }: AdminTeamViewProps): import("react/jsx-runtime").JSX.Element;
@@ -4,8 +4,9 @@ import React, { useState, useCallback } from "react";
4
4
  import { X, UserPlus } from "lucide-react";
5
5
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
6
6
  import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
7
- import { Button, FilterChipGroup, ListingToolbar, Pagination, ListingViewShell, RowActionMenu, } from "../../../ui";
7
+ import { BulkActionBar, Button, FilterChipGroup, ListingToolbar, Pagination, ListingViewShell, RowActionMenu, } from "../../../ui";
8
8
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
9
+ import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
9
10
  import { ALL_TAB } from "../constants/filter-tabs";
10
11
  import { toRecordArray, toRelativeDate, toStringValue, useAdminListingData, } from "../hooks/useAdminListingData";
11
12
  import { DataTable } from "./DataTable";
@@ -48,7 +49,7 @@ function TeamFilterDrawer({ filterOpen, setFilterOpen, activeFilterCount, clearF
48
49
  return null;
49
50
  return (_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: [_jsx("span", { className: "text-base font-semibold text-zinc-900 dark:text-zinc-100", children: "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", 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(FilterChipGroup, { label: "Permission Group", tabs: GROUP_TABS, value: pendingGroup, onChange: (id) => setPendingGroup(id) }) }), _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})` : ""] }) })] })] }));
50
51
  }
51
- export function AdminTeamView({ children, ...props }) {
52
+ export function AdminTeamView({ children, onBulkRemove, ...props }) {
52
53
  const hasChildren = React.Children.count(children) > 0;
53
54
  const [view, setView] = useState("table");
54
55
  const table = useUrlTable({
@@ -130,14 +131,22 @@ export function AdminTeamView({ children, ...props }) {
130
131
  const currentPage = table.getNumber("page", 1);
131
132
  const totalPages = Math.ceil(total / PAGE_SIZE);
132
133
  const selection = useBulkSelection({ items: rows, keyExtractor: (r) => r.id });
134
+ const bulkActions = [
135
+ ...(onBulkRemove ? [{
136
+ id: "bulk-remove",
137
+ label: ACTIONS.ADMIN["remove-team-member"].label,
138
+ variant: "danger",
139
+ onClick: async () => { await onBulkRemove(selection.selectedIds); selection.clearSelection(); },
140
+ }] : []),
141
+ ];
133
142
  if (hasChildren) {
134
143
  return (_jsx(ListingViewShell, { portal: "admin", ...props, children: children }));
135
144
  }
136
145
  return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search by name or email", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => {
137
146
  table.set("sort", v);
138
- }, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState, extra: _jsxs(Button, { type: "button", variant: "primary", onClick: openInvite, className: "flex items-center gap-1.5 whitespace-nowrap", children: [_jsx(UserPlus, { className: "h-4 w-4" }), "Invite Employee"] }) }), 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: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })), _jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No employees found", renderRowActions: (row) => (_jsx(RowActionMenu, { actions: [
147
+ }, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState, extra: _jsxs(Button, { type: "button", variant: "primary", onClick: openInvite, className: "flex items-center gap-1.5 whitespace-nowrap", children: [_jsx(UserPlus, { className: "h-4 w-4" }), "Invite Employee"] }) }), selection.selectedCount > 0 && bulkActions.length > 0 && (_jsx(BulkActionBar, { selectedCount: selection.selectedCount, actions: bulkActions, onClearSelection: selection.clearSelection })), 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: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })), _jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No employees found", renderRowActions: (row) => (_jsx(RowActionMenu, { actions: [
139
148
  {
140
- label: "Edit Permissions",
149
+ label: ACTIONS.ADMIN["edit-team-member"].label,
141
150
  onClick: () => openEdit(row),
142
151
  },
143
152
  ] })) })] }), _jsx(TeamFilterDrawer, { filterOpen: filterOpen, setFilterOpen: setFilterOpen, activeFilterCount: activeFilterCount, clearFilters: clearFilters, pendingGroup: pendingGroup, setPendingGroup: setPendingGroup, applyFilters: applyFilters })] }), _jsx(AdminEmployeeEditorView, { open: drawerOpen, onClose: () => setDrawerOpen(false), mode: inviteMode ? "invite" : "edit", userId: selectedRow?.id, displayName: selectedRow?.primary, currentPermissionGroup: toStringValue(selectedRow?._raw?.permissionGroup, "custom"), currentPermissions: Array.isArray(selectedRow?._raw?.permissions)