@mohasinac/appkit 2.7.54 → 2.7.55

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
@@ -13,7 +13,7 @@ export declare const eventInputSchema: z.ZodObject<{
13
13
  isOnline: z.ZodDefault<z.ZodBoolean>;
14
14
  entryFee: z.ZodDefault<z.ZodNumber>;
15
15
  }, "strip", z.ZodTypeAny, {
16
- type: "TOURNAMENT" | "CONVENTION" | "MEETUP" | "SALE";
16
+ type: "SALE" | "TOURNAMENT" | "CONVENTION" | "MEETUP";
17
17
  title: string;
18
18
  slug: string;
19
19
  tags: string[];
@@ -26,7 +26,7 @@ export declare const eventInputSchema: z.ZodObject<{
26
26
  maxEntries?: number | undefined;
27
27
  imageUrl?: string | undefined;
28
28
  }, {
29
- type: "TOURNAMENT" | "CONVENTION" | "MEETUP" | "SALE";
29
+ type: "SALE" | "TOURNAMENT" | "CONVENTION" | "MEETUP";
30
30
  title: string;
31
31
  slug: string;
32
32
  startsAt: string;
@@ -53,7 +53,7 @@ export declare const eventUpdateSchema: z.ZodObject<Omit<{
53
53
  isOnline: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
54
54
  entryFee: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
55
55
  }, "slug">, "strip", z.ZodTypeAny, {
56
- type?: "TOURNAMENT" | "CONVENTION" | "MEETUP" | "SALE" | undefined;
56
+ type?: "SALE" | "TOURNAMENT" | "CONVENTION" | "MEETUP" | undefined;
57
57
  title?: string | undefined;
58
58
  description?: string | undefined;
59
59
  tags?: string[] | undefined;
@@ -65,7 +65,7 @@ export declare const eventUpdateSchema: z.ZodObject<Omit<{
65
65
  entryFee?: number | undefined;
66
66
  isOnline?: boolean | undefined;
67
67
  }, {
68
- type?: "TOURNAMENT" | "CONVENTION" | "MEETUP" | "SALE" | undefined;
68
+ type?: "SALE" | "TOURNAMENT" | "CONVENTION" | "MEETUP" | undefined;
69
69
  title?: string | undefined;
70
70
  description?: string | undefined;
71
71
  tags?: string[] | undefined;
@@ -323,6 +323,7 @@ export declare const DEMO_ENDPOINTS: {
323
323
  export declare const WHATSAPP_SELLER_ENDPOINTS: {
324
324
  readonly SETTINGS: "/api/store/whatsapp-settings";
325
325
  readonly CATALOG_SYNC: "/api/store/whatsapp-settings/catalog-sync";
326
+ readonly CATALOG_IMPORT: "/api/store/whatsapp-settings/catalog-import";
326
327
  };
327
328
  export declare const API_ENDPOINTS: {
328
329
  readonly LOGS: {
@@ -632,6 +633,7 @@ export declare const API_ENDPOINTS: {
632
633
  readonly WHATSAPP_SELLER: {
633
634
  readonly SETTINGS: "/api/store/whatsapp-settings";
634
635
  readonly CATALOG_SYNC: "/api/store/whatsapp-settings/catalog-sync";
636
+ readonly CATALOG_IMPORT: "/api/store/whatsapp-settings/catalog-import";
635
637
  };
636
638
  readonly SUPPORT: {
637
639
  readonly TICKETS: "/api/support/tickets";
@@ -953,6 +955,7 @@ export declare const API_ROUTES: {
953
955
  readonly WHATSAPP_SELLER: {
954
956
  readonly SETTINGS: "/api/store/whatsapp-settings";
955
957
  readonly CATALOG_SYNC: "/api/store/whatsapp-settings/catalog-sync";
958
+ readonly CATALOG_IMPORT: "/api/store/whatsapp-settings/catalog-import";
956
959
  };
957
960
  readonly SUPPORT: {
958
961
  readonly TICKETS: "/api/support/tickets";
@@ -442,6 +442,7 @@ export const DEMO_ENDPOINTS = {
442
442
  export const WHATSAPP_SELLER_ENDPOINTS = {
443
443
  SETTINGS: "/api/store/whatsapp-settings",
444
444
  CATALOG_SYNC: "/api/store/whatsapp-settings/catalog-sync",
445
+ CATALOG_IMPORT: "/api/store/whatsapp-settings/catalog-import",
445
446
  };
446
447
  // ---------------------------------------------------------------------------
447
448
  // Aggregate namespace export
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useCallback, useEffect, useState, useTransition } from "react";
4
4
  import { Alert, Badge, Button, Div, Heading, LoginRequiredModal, Spinner, Text } from "../../../ui";
5
5
  import { isAuthError } from "../../../utils/auth-error";
6
+ import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
6
7
  function formatRupees(amount) {
7
8
  if (amount === undefined || amount === null)
8
9
  return "—";
@@ -89,5 +90,5 @@ export function UserOffersPanel({ fetchEndpoint = "/api/user/offers", onAcceptCo
89
90
  function handleUpdate(id, patch) {
90
91
  setOffers((prev) => prev.map((o) => (o.id === id ? { ...o, ...patch } : o)));
91
92
  }
92
- return (_jsxs(Div, { className: `space-y-4 ${className}`, children: [_jsx(LoginRequiredModal, { isOpen: showLoginModal, onClose: () => setShowLoginModal(false), message: "You need to be signed in to manage your offers. Please log in or create an account to continue." }), _jsxs(Div, { className: "flex items-center justify-between", children: [_jsx(Heading, { level: 2, className: "text-lg font-semibold text-zinc-900 dark:text-zinc-100", children: "My Offers" }), _jsx(Button, { size: "sm", variant: "ghost", onClick: loadOffers, disabled: loading, className: "border border-zinc-300 dark:border-zinc-600 text-xs", children: loading ? "Refreshing…" : "Refresh" })] }), fetchError && _jsx(Alert, { variant: "error", children: _jsx(Text, { className: "text-sm", children: fetchError }) }), loading && (_jsx(Div, { className: "flex justify-center py-12", children: _jsx(Spinner, { size: "lg" }) })), !loading && offers.length === 0 && (_jsx(Div, { className: "text-center py-12", children: _jsx(Text, { className: "text-zinc-400 dark:text-zinc-500 text-sm", children: "No offers yet" }) })), !loading && offers.length > 0 && (_jsx(Div, { className: "space-y-3", children: offers.map((offer) => (_jsx(BuyerOfferCard, { offer: offer, onAcceptCounter: onAcceptCounter, onWithdraw: onWithdraw, onCheckout: onCheckout, onUpdate: handleUpdate, onNeedsLogin: () => setShowLoginModal(true) }, offer.id))) }))] }));
93
+ return (_jsxs(Div, { className: `space-y-4 ${className}`, children: [_jsx(LoginRequiredModal, { isOpen: showLoginModal, onClose: () => setShowLoginModal(false), message: "You need to be signed in to manage your offers. Please log in or create an account to continue." }), _jsxs(Div, { className: "flex items-center justify-between", children: [_jsx(Heading, { level: 2, className: "text-lg font-semibold text-zinc-900 dark:text-zinc-100", children: "My Offers" }), _jsx(Button, { size: "sm", variant: "ghost", onClick: loadOffers, disabled: loading, className: "border border-zinc-300 dark:border-zinc-600 text-xs", children: loading ? "Refreshing…" : ACTIONS.USER["refresh-offers"].label })] }), fetchError && _jsx(Alert, { variant: "error", children: _jsx(Text, { className: "text-sm", children: fetchError }) }), loading && (_jsx(Div, { className: "flex justify-center py-12", children: _jsx(Spinner, { size: "lg" }) })), !loading && offers.length === 0 && (_jsx(Div, { className: "text-center py-12", children: _jsx(Text, { className: "text-zinc-400 dark:text-zinc-500 text-sm", children: "No offers yet" }) })), !loading && offers.length > 0 && (_jsx(Div, { className: "space-y-3", children: offers.map((offer) => (_jsx(BuyerOfferCard, { offer: offer, onAcceptCounter: onAcceptCounter, onWithdraw: onWithdraw, onCheckout: onCheckout, onUpdate: handleUpdate, onNeedsLogin: () => setShowLoginModal(true) }, offer.id))) }))] }));
93
94
  }
@@ -7,6 +7,7 @@ import { useUrlTable } from "../../../react/hooks/useUrlTable";
7
7
  import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
8
8
  import { FilterChipGroup, ListingToolbar, Pagination, ListingViewShell, 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_EVENT_ENTRY_STATUS_TABS } from "../constants/filter-tabs";
11
12
  import { toRecordArray, toRelativeDate, toStringValue, useAdminListingData, } from "../hooks/useAdminListingData";
12
13
  import { DataTable } from "./DataTable";
@@ -103,9 +104,9 @@ export function AdminAllEventEntriesView({ children, ...props }) {
103
104
  return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search by user name or event 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 }), 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 entries found", renderRowActions: (row) => {
104
105
  const er = row;
105
106
  return (_jsx(RowActionMenu, { actions: [
106
- { label: "Confirm", onClick: () => updateMutation.mutate({ id: er.id, status: "CONFIRMED" }) },
107
- { label: "Waitlist", onClick: () => updateMutation.mutate({ id: er.id, status: "WAITLISTED" }) },
108
- { label: "Cancel", destructive: true, onClick: () => updateMutation.mutate({ id: er.id, status: "CANCELLED" }) },
107
+ { label: ACTIONS.ADMIN["confirm-entry"].label, onClick: () => updateMutation.mutate({ id: er.id, status: "CONFIRMED" }) },
108
+ { label: ACTIONS.ADMIN["waitlist-entry"].label, onClick: () => updateMutation.mutate({ id: er.id, status: "WAITLISTED" }) },
109
+ { label: ACTIONS.ADMIN["cancel-entry"].label, destructive: true, onClick: () => updateMutation.mutate({ id: er.id, status: "CANCELLED" }) },
109
110
  ] }));
110
111
  } })] }), _jsx(EntriesFilterDrawer, { filterOpen: filterOpen, setFilterOpen: setFilterOpen, activeFilterCount: activeFilterCount, clearFilters: clearFilters, pendingFilters: pendingFilters, setPendingFilters: setPendingFilters, applyFilters: applyFilters })] }));
111
112
  }
@@ -8,6 +8,8 @@ import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
8
8
  import { BulkActionBar, FilterChipGroup, ListingToolbar, Pagination, ListingViewShell, ConfirmDeleteModal, RowActionMenu, useToast } from "../../../ui";
9
9
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
10
10
  import { ADMIN_BID_STATUS_TABS } from "../constants/filter-tabs";
11
+ import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
12
+ import { ROW_ACTION_META, ROW_ACTION_ID } from "../../products/constants/action-defs";
11
13
  import { toRecordArray, toRelativeDate, toRupees, toStringValue, useAdminListingData, } from "../hooks/useAdminListingData";
12
14
  import { DataTable } from "./DataTable";
13
15
  import { apiClient } from "../../../http";
@@ -106,14 +108,14 @@ export function AdminBidsView({ children, ...props }) {
106
108
  return _jsx(ListingViewShell, { portal: "admin", ...props, children: children });
107
109
  }
108
110
  return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search bids, products, or bidder 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: [
109
- { id: "cancel", label: "Cancel Selected", variant: "secondary", onClick: () => { selection.clearSelection(); } },
111
+ { id: ROW_ACTION_ID.CANCEL, label: ACTIONS.ADMIN["cancel-bid"].label, variant: "secondary", onClick: () => { selection.clearSelection(); } },
110
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 })), _jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No bids found", renderRowActions: (row) => {
111
113
  const bidRow = row;
112
114
  const isCancelled = bidRow.status === "cancelled" || bidRow.status === "voided";
113
115
  return (_jsx(RowActionMenu, { actions: [
114
116
  {
115
- label: "Cancel bid",
116
- destructive: true,
117
+ label: ROW_ACTION_META[ROW_ACTION_ID.CANCEL].label,
118
+ destructive: ROW_ACTION_META[ROW_ACTION_ID.CANCEL].destructive,
117
119
  disabled: isCancelled,
118
120
  onClick: () => {
119
121
  setSelectedRow(bidRow);
@@ -9,6 +9,7 @@ import { BulkActionBar, Button, FilterChipGroup, Heading, ListingToolbar, Listin
9
9
  import { AdminViewCards } from "./AdminViewCards";
10
10
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
11
11
  import { ADMIN_BLOG_STATUS_TABS } from "../constants/filter-tabs";
12
+ import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
12
13
  import { toRecordArray, toRelativeDate, toStringValue, useAdminListingData, } from "../hooks/useAdminListingData";
13
14
  import { DataTable } from "./DataTable";
14
15
  import { AdminBlogEditorView } from "./AdminBlogEditorView";
@@ -105,7 +106,7 @@ export function AdminBlogView({ children, getRowHref, ...props }) {
105
106
  return _jsx(ListingViewShell, { portal: "admin", ...props, children: children });
106
107
  }
107
108
  return (_jsxs("div", { className: "min-h-screen", children: [_jsx(Heading, { level: 1, className: "sr-only", children: "Blog Posts" }), _jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search articles, authors, or tags", 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" }), "New Post"] }) }), _jsx(BulkActionBar, { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: [
108
- { id: "publish", label: "Publish Selected", variant: "primary", onClick: () => { selection.clearSelection(); } },
109
- { id: "draft", label: "Move to Draft", variant: "secondary", onClick: () => { selection.clearSelection(); } },
109
+ { id: "publish", label: ACTIONS.ADMIN["publish-blog"].label, variant: "primary", onClick: () => { selection.clearSelection(); } },
110
+ { id: "draft", label: ACTIONS.ADMIN["draft-blog"].label, variant: "secondary", onClick: () => { selection.clearSelection(); } },
110
111
  ] }), 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 blog posts found", selectedIds: selection.selectedIdSet, onToggleSelect: selection.toggle, onToggleSelectAll: (next) => next ? selection.setSelectedIds(rows.map(r => r.id)) : selection.clearSelection(), onRowClick: (row) => openEditPanel(row.id) })) : (_jsx(AdminViewCards, { rows: rows, view: view, isLoading: isLoading, emptyLabel: "No blog posts found", onRowClick: (row) => openEditPanel(row.id), selectedIdSet: selection.selectedIdSet, onToggleSelect: selection.toggle }))] }), _jsx(BlogFilterDrawer, { filterOpen: filterOpen, setFilterOpen: setFilterOpen, activeFilterCount: activeFilterCount, clearFilters: clearFilters, pendingFilters: pendingFilters, setPendingFilters: setPendingFilters, applyFilters: applyFilters }), _jsx(SideDrawer, { isOpen: isCreateOpen || isEditOpen, onClose: closePanel, title: isCreateOpen ? "New Post" : "Edit Post", mode: isCreateOpen ? "create" : "edit", children: (isCreateOpen || isEditOpen) && (_jsx(AdminBlogEditorView, { postId: editId ?? undefined, onSaved: closePanel, onDeleted: closePanel, embedded: true })) })] }));
111
112
  }
@@ -8,6 +8,7 @@ import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
8
8
  import { BulkActionBar, Button, ListingToolbar, ListingViewShell, Pagination, SideDrawer, Text } from "../../../ui";
9
9
  import { AdminViewCards } from "./AdminViewCards";
10
10
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
11
+ import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
11
12
  import { toRecordArray, toRelativeDate, toStringValue, useAdminListingData, } from "../hooks/useAdminListingData";
12
13
  import { DataTable } from "./DataTable";
13
14
  import { AdminBrandEditorView } from "./AdminBrandEditorView";
@@ -85,7 +86,7 @@ export function AdminBrandsView({ children, ...props }) {
85
86
  return _jsx(ListingViewShell, { portal: "admin", ...props, children: children });
86
87
  }
87
88
  return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search brands by name or slug", 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 Brand"] }) }), _jsx(BulkActionBar, { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: [
88
- { id: "edit", label: "Edit Brand", variant: "primary", onClick: () => { const id = selection.selectedIds[0]; if (id)
89
+ { id: "edit", label: ACTIONS.ADMIN["edit-brand"].label, variant: "primary", onClick: () => { const id = selection.selectedIds[0]; if (id)
89
90
  openEditPanel(id); selection.clearSelection(); } },
90
91
  ] }), 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 brands found", selectedIds: selection.selectedIdSet, onToggleSelect: selection.toggle, onToggleSelectAll: (next) => next ? selection.setSelectedIds(rows.map(r => r.id)) : selection.clearSelection(), onRowClick: (row) => openEditPanel(row.id) })) : (_jsx(AdminViewCards, { rows: rows, view: view, isLoading: isLoading, emptyLabel: "No brands found", onRowClick: (row) => openEditPanel(row.id), selectedIdSet: selection.selectedIdSet, onToggleSelect: selection.toggle }))] }), _jsx(BrandsFilterDrawer, { filterOpen: filterOpen, setFilterOpen: setFilterOpen, activeFilterCount: activeFilterCount, clearFilters: clearFilters, pendingFilters: pendingFilters, setPendingFilters: setPendingFilters, applyFilters: applyFilters }), _jsx(SideDrawer, { isOpen: isCreateOpen || isEditOpen, onClose: closePanel, title: isCreateOpen ? "Add Brand" : "Edit Brand", mode: isCreateOpen ? "create" : "edit", children: (isCreateOpen || isEditOpen) && (_jsx(AdminBrandEditorView, { brandId: editId ?? undefined, onSaved: closePanel, onDeleted: closePanel, embedded: true })) })] }));
91
92
  }
@@ -13,7 +13,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
13
13
  * static).
14
14
  */
15
15
  import { useCallback, useEffect, useMemo, useState } from "react";
16
- import { Button, Checkbox, Container, Heading, Input, Row, Section, Select, Stack, Text, Textarea, } from "../../../ui";
16
+ import { Button, Checkbox, ConfirmDeleteModal, Container, Heading, Input, Row, Section, Select, Stack, Text, Textarea, } from "../../../ui";
17
17
  import { FieldInput, FormShellContext, useFormShellState } from "../../../ui/forms";
18
18
  import { BundleItemsPicker, defaultBundleItemsFetch, } from "../../categories/components/BundleItemsPicker";
19
19
  import { BundleDynamicRuleEditor } from "../../categories/components/BundleDynamicRuleEditor";
@@ -77,6 +77,7 @@ export function AdminBundleEditorView({ bundleId, onSaved, onDeleted, }) {
77
77
  const [loading, setLoading] = useState(isEdit);
78
78
  const [saving, setSaving] = useState(false);
79
79
  const [deleting, setDeleting] = useState(false);
80
+ const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false);
80
81
  const [apiError, setApiError] = useState(null);
81
82
  const [metadata, setMetadata] = useState({});
82
83
  const { shellCtx, setFieldError, clearErrors } = useFormShellState();
@@ -188,15 +189,13 @@ export function AdminBundleEditorView({ bundleId, onSaved, onDeleted, }) {
188
189
  const handleDelete = useCallback(async () => {
189
190
  if (!bundleId)
190
191
  return;
191
- if (!window.confirm(BUNDLE_COPY.adminEditor.deleteConfirm)) {
192
- return;
193
- }
194
192
  setDeleting(true);
195
193
  setApiError(null);
196
194
  try {
197
195
  const res = await fetch(`/api/admin/bundles/${encodeURIComponent(bundleId)}`, { method: "DELETE" });
198
196
  if (!res.ok)
199
197
  throw new Error(`Delete failed: ${res.status}`);
198
+ setDeleteConfirmOpen(false);
200
199
  onDeleted?.();
201
200
  }
202
201
  catch (err) {
@@ -214,5 +213,5 @@ export function AdminBundleEditorView({ bundleId, onSaved, onDeleted, }) {
214
213
  }
215
214
  return (_jsx(FormShellContext.Provider, { value: shellCtx, children: _jsx(Section, { className: "py-10", children: _jsx(Container, { size: "lg", children: _jsxs(Stack, { gap: "lg", children: [_jsxs(Row, { gap: "sm", align: "center", justify: "between", className: "flex-wrap", children: [_jsx(Heading, { level: 1, className: "text-2xl font-semibold text-zinc-900 dark:text-zinc-100", children: isEdit
216
215
  ? BUNDLE_COPY.adminEditorTitleEdit
217
- : BUNDLE_COPY.adminEditorTitleNew }), isEdit && (_jsx(Button, { variant: "danger", onClick: handleDelete, disabled: deleting, children: BUNDLE_COPY.adminEditor.deleteButton(deleting) }))] }), apiError && (_jsx(Text, { color: "danger", role: "alert", children: apiError })), _jsxs(Stack, { gap: "md", children: [_jsx(FieldInput, { name: "name", label: BUNDLE_COPY.adminEditor.fields.nameLabel, value: form.name, onChange: (v) => setForm((f) => ({ ...f, name: v })), placeholder: BUNDLE_COPY.adminEditor.fields.namePlaceholder, disabled: saving, required: true }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "semibold", children: BUNDLE_COPY.adminEditor.fields.descriptionLabel }), _jsx(Textarea, { value: form.description, onChange: (e) => setForm((f) => ({ ...f, description: e.target.value })), placeholder: BUNDLE_COPY.adminEditor.fields.descriptionPlaceholder, rows: 4, disabled: saving })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(FieldInput, { name: "price", label: BUNDLE_COPY.adminEditor.fields.priceLabel, type: "number", inputMode: "decimal", min: 1, step: 1, value: form.priceRupees, onChange: (v) => setForm((f) => ({ ...f, priceRupees: v })), placeholder: BUNDLE_COPY.adminEditor.fields.pricePlaceholder, disabled: saving, required: true }), _jsx(Text, { size: "xs", color: "muted", children: BUNDLE_COPY.adminEditor.fields.pricePaiseHint(parsePriceRupees(form.priceRupees)) })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "semibold", children: BUNDLE_COPY.adminEditor.fields.coverImageLabel }), _jsx(Input, { type: "url", value: form.coverImage, onChange: (e) => setForm((f) => ({ ...f, coverImage: e.target.value })), placeholder: "https://\u2026", disabled: saving })] }), _jsx(Checkbox, { checked: form.isActive, onChange: (e) => setForm((f) => ({ ...f, isActive: e.target.checked })), disabled: saving, label: BUNDLE_COPY.adminEditor.fields.activeLabel }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "semibold", children: BUNDLE_COPY.adminEditor.ruleTypeLabel }), _jsx(Select, { options: RULE_TYPE_OPTIONS, value: form.ruleType, onValueChange: (next) => setForm((f) => ({ ...f, ruleType: next })), disabled: saving, "aria-label": BUNDLE_COPY.adminEditor.ruleTypeLabel })] }), form.ruleType === "static" ? (_jsx(BundleItemsPicker, { value: form.productIds, onChange: (next) => setForm((f) => ({ ...f, productIds: next })), fetchProducts: fetchProducts, initialMetadata: metadata })) : (_jsx(BundleDynamicRuleEditor, { value: form.dynamicRule, onChange: (next) => setForm((f) => ({ ...f, dynamicRule: next })), disabled: saving }))] }), _jsx(Row, { gap: "sm", align: "center", justify: "end", children: _jsx(Button, { variant: "primary", onClick: handleSave, disabled: saving, "aria-busy": saving, children: BUNDLE_COPY.adminEditor.saveButton(saving, isEdit) }) })] }) }) }) }));
216
+ : BUNDLE_COPY.adminEditorTitleNew }), isEdit && (_jsx(Button, { variant: "danger", onClick: () => setDeleteConfirmOpen(true), disabled: deleting, children: BUNDLE_COPY.adminEditor.deleteButton(deleting) }))] }), _jsx(ConfirmDeleteModal, { isOpen: deleteConfirmOpen, onClose: () => setDeleteConfirmOpen(false), onConfirm: handleDelete, title: "Delete this bundle?", message: BUNDLE_COPY.adminEditor.deleteConfirm, isDeleting: deleting }), apiError && (_jsx(Text, { color: "danger", role: "alert", children: apiError })), _jsxs(Stack, { gap: "md", children: [_jsx(FieldInput, { name: "name", label: BUNDLE_COPY.adminEditor.fields.nameLabel, value: form.name, onChange: (v) => setForm((f) => ({ ...f, name: v })), placeholder: BUNDLE_COPY.adminEditor.fields.namePlaceholder, disabled: saving, required: true }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "semibold", children: BUNDLE_COPY.adminEditor.fields.descriptionLabel }), _jsx(Textarea, { value: form.description, onChange: (e) => setForm((f) => ({ ...f, description: e.target.value })), placeholder: BUNDLE_COPY.adminEditor.fields.descriptionPlaceholder, rows: 4, disabled: saving })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(FieldInput, { name: "price", label: BUNDLE_COPY.adminEditor.fields.priceLabel, type: "number", inputMode: "decimal", min: 1, step: 1, value: form.priceRupees, onChange: (v) => setForm((f) => ({ ...f, priceRupees: v })), placeholder: BUNDLE_COPY.adminEditor.fields.pricePlaceholder, disabled: saving, required: true }), _jsx(Text, { size: "xs", color: "muted", children: BUNDLE_COPY.adminEditor.fields.pricePaiseHint(parsePriceRupees(form.priceRupees)) })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "semibold", children: BUNDLE_COPY.adminEditor.fields.coverImageLabel }), _jsx(Input, { type: "url", value: form.coverImage, onChange: (e) => setForm((f) => ({ ...f, coverImage: e.target.value })), placeholder: "https://\u2026", disabled: saving })] }), _jsx(Checkbox, { checked: form.isActive, onChange: (e) => setForm((f) => ({ ...f, isActive: e.target.checked })), disabled: saving, label: BUNDLE_COPY.adminEditor.fields.activeLabel }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "semibold", children: BUNDLE_COPY.adminEditor.ruleTypeLabel }), _jsx(Select, { options: RULE_TYPE_OPTIONS, value: form.ruleType, onValueChange: (next) => setForm((f) => ({ ...f, ruleType: next })), disabled: saving, "aria-label": BUNDLE_COPY.adminEditor.ruleTypeLabel })] }), form.ruleType === "static" ? (_jsx(BundleItemsPicker, { value: form.productIds, onChange: (next) => setForm((f) => ({ ...f, productIds: next })), fetchProducts: fetchProducts, initialMetadata: metadata })) : (_jsx(BundleDynamicRuleEditor, { value: form.dynamicRule, onChange: (next) => setForm((f) => ({ ...f, dynamicRule: next })), disabled: saving }))] }), _jsx(Row, { gap: "sm", align: "center", justify: "end", children: _jsx(Button, { variant: "primary", onClick: handleSave, disabled: saving, "aria-busy": saving, children: BUNDLE_COPY.adminEditor.saveButton(saving, isEdit) }) })] }) }) }) }));
218
217
  }
@@ -152,7 +152,7 @@ export function AdminBundlesView({ getEditHref, newHref }) {
152
152
  const bulkActions = [
153
153
  {
154
154
  id: "activate",
155
- label: "Activate",
155
+ label: ACTIONS.ADMIN["activate-bundle"].label,
156
156
  variant: "primary",
157
157
  onClick: async () => {
158
158
  await Promise.all(selection.selectedIds.map((id) => fetch(ADMIN_ENDPOINTS.BUNDLE_BY_ID(id), {
@@ -166,7 +166,7 @@ export function AdminBundlesView({ getEditHref, newHref }) {
166
166
  },
167
167
  {
168
168
  id: "deactivate",
169
- label: "Deactivate",
169
+ label: ACTIONS.ADMIN["deactivate-bundle"].label,
170
170
  variant: "secondary",
171
171
  onClick: async () => {
172
172
  await Promise.all(selection.selectedIds.map((id) => fetch(ADMIN_ENDPOINTS.BUNDLE_BY_ID(id), {
@@ -180,7 +180,7 @@ export function AdminBundlesView({ getEditHref, newHref }) {
180
180
  },
181
181
  {
182
182
  id: "delete",
183
- label: "Delete",
183
+ label: ACTIONS.ADMIN["delete-bundle"].label,
184
184
  variant: "danger",
185
185
  onClick: async () => {
186
186
  await Promise.all(selection.selectedIds.map((id) => fetch(ADMIN_ENDPOINTS.BUNDLE_BY_ID(id), { method: "DELETE" })));
@@ -1,4 +1,5 @@
1
1
  import type { ListingViewShellProps } from "../../../ui";
2
2
  export interface AdminCarouselViewProps extends ListingViewShellProps {
3
+ onBulkDelete?: (ids: string[]) => Promise<void>;
3
4
  }
4
- export declare function AdminCarouselView({ children, ...props }: AdminCarouselViewProps): import("react/jsx-runtime").JSX.Element;
5
+ export declare function AdminCarouselView({ children, onBulkDelete, ...props }: AdminCarouselViewProps): 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 { ListingToolbar, ListingViewShell, Pagination, Text } from "../../../ui";
8
+ import { BulkActionBar, ListingToolbar, ListingViewShell, Pagination, Text } from "../../../ui";
9
9
  import { ADMIN_ENDPOINTS, HOMEPAGE_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 { RowActionMenu } from "../../../ui/components/RowActionMenu";
@@ -19,7 +20,7 @@ function CarouselFilterDrawer({ filterOpen, setFilterOpen, activeFilterCount, cl
19
20
  return null;
20
21
  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: "Active", value: "true" }, { label: "Inactive", value: "false" }].map((opt) => (_jsx("button", { type: "button", onClick: () => setPendingFilters((p) => ({ ...p, active: opt.value })), className: `rounded-full px-3 py-1 text-xs font-medium border transition-colors ${(pendingFilters.active || "") === 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})` : ""] }) })] })] }));
21
22
  }
22
- export function AdminCarouselView({ children, ...props }) {
23
+ export function AdminCarouselView({ children, onBulkDelete, ...props }) {
23
24
  const hasChildren = React.Children.count(children) > 0;
24
25
  const [view, setView] = useState("table");
25
26
  const [draggingId, setDraggingId] = useState(null);
@@ -134,6 +135,14 @@ export function AdminCarouselView({ children, ...props }) {
134
135
  const currentPage = table.getNumber("page", 1);
135
136
  const totalPages = Math.ceil(total / PAGE_SIZE);
136
137
  const selection = useBulkSelection({ items: fetchedRows, keyExtractor: (r) => r.id });
138
+ const bulkActions = [
139
+ ...(onBulkDelete ? [{
140
+ id: "bulk-delete",
141
+ label: ACTIONS.ADMIN["delete-carousel"].label,
142
+ variant: "danger",
143
+ onClick: async () => { await onBulkDelete(selection.selectedIds); selection.clearSelection(); },
144
+ }] : []),
145
+ ];
137
146
  if (hasChildren) {
138
147
  return _jsx(ListingViewShell, { portal: "admin", ...props, children: children });
139
148
  }
@@ -171,14 +180,14 @@ export function AdminCarouselView({ children, ...props }) {
171
180
  key: "actions",
172
181
  header: "",
173
182
  render: (row) => (_jsx(RowActionMenu, { actions: [
174
- { label: "Edit", onClick: () => { window.location.href = `/admin/carousel/${row.id}/edit`; } },
183
+ { label: ACTIONS.ADMIN["edit-carousel"].label, onClick: () => { window.location.href = `/admin/carousel/${row.id}/edit`; } },
175
184
  {
176
- label: "Delete",
185
+ label: ACTIONS.ADMIN["delete-carousel"].label,
177
186
  destructive: true,
178
187
  onClick: () => handleDeleteSlide(row.id),
179
188
  },
180
189
  ] })),
181
190
  },
182
191
  ];
183
- return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search slide titles", onSearchChange: setSearchInput, onSearchCommit: commitSearch, 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: displayRows, columns: columns, isLoading: isLoading, emptyLabel: "No carousel slides \u2014 use 'New slide' to add one", getRowHref: (row) => `/admin/carousel/${row.id}/edit` })] }), _jsx(CarouselFilterDrawer, { filterOpen: filterOpen, setFilterOpen: setFilterOpen, activeFilterCount: activeFilterCount, clearFilters: clearFilters, pendingFilters: pendingFilters, setPendingFilters: setPendingFilters, applyFilters: applyFilters })] }));
192
+ return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search slide titles", onSearchChange: setSearchInput, onSearchCommit: commitSearch, 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) }) })), 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 })), _jsx(DataTable, { rows: displayRows, columns: columns, isLoading: isLoading, emptyLabel: "No carousel slides \u2014 use 'New slide' to add one", getRowHref: (row) => `/admin/carousel/${row.id}/edit` })] }), _jsx(CarouselFilterDrawer, { filterOpen: filterOpen, setFilterOpen: setFilterOpen, activeFilterCount: activeFilterCount, clearFilters: clearFilters, pendingFilters: pendingFilters, setPendingFilters: setPendingFilters, applyFilters: applyFilters })] }));
184
193
  }
@@ -8,6 +8,7 @@ import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
8
8
  import { BulkActionBar, Button, Heading, ListingToolbar, ListingViewShell, Pagination, SideDrawer, Text } from "../../../ui";
9
9
  import { AdminViewCards } from "./AdminViewCards";
10
10
  import { CATEGORY_ENDPOINTS } from "../../../constants/api-endpoints";
11
+ import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
11
12
  import { toRecordArray, toRelativeDate, toStringValue, useAdminListingData, } from "../hooks/useAdminListingData";
12
13
  import { DataTable } from "./DataTable";
13
14
  import { AdminCategoryEditorView } from "./AdminCategoryEditorView";
@@ -93,7 +94,7 @@ export function AdminCategoriesView({ children, getRowHref, ...props }) {
93
94
  return _jsx(ListingViewShell, { portal: "admin", ...props, children: children });
94
95
  }
95
96
  return (_jsxs("div", { className: "min-h-screen", children: [_jsx(Heading, { level: 1, className: "sr-only", children: "Categories" }), _jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search categories, slugs, or parent category", 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 Category"] }) }), _jsx(BulkActionBar, { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: [
96
- { id: "edit", label: "Edit Category", variant: "primary", onClick: () => { const id = selection.selectedIds[0]; if (id)
97
+ { id: "edit", label: ACTIONS.ADMIN["edit-category"].label, variant: "primary", onClick: () => { const id = selection.selectedIds[0]; if (id)
97
98
  openEditPanel(id); selection.clearSelection(); } },
98
99
  ] }), 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 categories found", selectedIds: selection.selectedIdSet, onToggleSelect: selection.toggle, onToggleSelectAll: (next) => next ? selection.setSelectedIds(rows.map(r => r.id)) : selection.clearSelection(), onRowClick: (row) => openEditPanel(row.id) })) : (_jsx(AdminViewCards, { rows: rows, view: view, isLoading: isLoading, emptyLabel: "No categories found", onRowClick: (row) => openEditPanel(row.id), selectedIdSet: selection.selectedIdSet, onToggleSelect: selection.toggle }))] }), _jsx(CategoriesFilterDrawer, { filterOpen: filterOpen, setFilterOpen: setFilterOpen, activeFilterCount: activeFilterCount, clearFilters: clearFilters, pendingFilters: pendingFilters, setPendingFilters: setPendingFilters, applyFilters: applyFilters }), _jsx(SideDrawer, { isOpen: isCreateOpen || isEditOpen, onClose: closePanel, title: isCreateOpen ? "Add Category" : "Edit Category", mode: isCreateOpen ? "create" : "edit", children: (isCreateOpen || isEditOpen) && (_jsx(AdminCategoryEditorView, { categoryId: editId ?? undefined, onSaved: closePanel, onDeleted: closePanel, embedded: true })) })] }));
99
100
  }
@@ -1,4 +1,7 @@
1
1
  import type { ListingViewShellProps } from "../../../ui";
2
2
  export interface AdminContactViewProps extends ListingViewShellProps {
3
+ onBulkMarkRead?: (ids: string[]) => Promise<void>;
4
+ onBulkArchive?: (ids: string[]) => Promise<void>;
5
+ onBulkDelete?: (ids: string[]) => Promise<void>;
3
6
  }
4
- export declare function AdminContactView({ children, ...props }: AdminContactViewProps): import("react/jsx-runtime").JSX.Element;
7
+ export declare function AdminContactView({ children, onBulkMarkRead, onBulkArchive, onBulkDelete, ...props }: AdminContactViewProps): import("react/jsx-runtime").JSX.Element;
@@ -5,8 +5,10 @@ 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 { ConfirmDeleteModal, FilterChipGroup, ListingToolbar, ListingViewShell, Pagination, RowActionMenu, useToast, } from "../../../ui";
8
+ import { BulkActionBar, 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";
11
+ import { ROW_ACTION_META, ROW_ACTION_ID } from "../../../features/products/constants/action-defs";
10
12
  import { ADMIN_CONTACT_STATUS_TABS } from "../constants/filter-tabs";
11
13
  import { toRecordArray, toRelativeDate, toStringValue, useAdminListingData, } from "../hooks/useAdminListingData";
12
14
  import { DataTable } from "./DataTable";
@@ -25,7 +27,7 @@ function ContactFilterDrawer({ filterOpen, setFilterOpen, activeFilterCount, cle
25
27
  return null;
26
28
  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})` : ""] }) })] })] }));
27
29
  }
28
- export function AdminContactView({ children, ...props }) {
30
+ export function AdminContactView({ children, onBulkMarkRead, onBulkArchive, onBulkDelete, ...props }) {
29
31
  const hasChildren = React.Children.count(children) > 0;
30
32
  const [view, setView] = useState("table");
31
33
  const queryClient = useQueryClient();
@@ -121,26 +123,44 @@ export function AdminContactView({ children, ...props }) {
121
123
  const currentPage = table.getNumber("page", 1);
122
124
  const totalPages = Math.ceil(total / PAGE_SIZE);
123
125
  const selection = useBulkSelection({ items: rows, keyExtractor: (r) => r.id });
126
+ const bulkActions = [
127
+ ...(onBulkMarkRead ? [{
128
+ id: "bulk-mark-read",
129
+ label: ACTIONS.ADMIN["mark-contact-read"].label,
130
+ onClick: async () => { await onBulkMarkRead(selection.selectedIds); selection.clearSelection(); },
131
+ }] : []),
132
+ ...(onBulkArchive ? [{
133
+ id: "bulk-archive",
134
+ label: ACTIONS.ADMIN["archive-contact"].label,
135
+ onClick: async () => { await onBulkArchive(selection.selectedIds); selection.clearSelection(); },
136
+ }] : []),
137
+ ...(onBulkDelete ? [{
138
+ id: "bulk-delete",
139
+ label: ACTIONS.ADMIN["delete-contact"].label,
140
+ variant: "danger",
141
+ onClick: async () => { await onBulkDelete(selection.selectedIds); selection.clearSelection(); },
142
+ }] : []),
143
+ ];
124
144
  if (hasChildren) {
125
145
  return _jsx(ListingViewShell, { portal: "admin", ...props, children: children });
126
146
  }
127
- return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search by subject, name, or email", 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 }), 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 contact submissions found", renderRowActions: (row) => {
147
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search by subject, name, or email", 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 }), 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 })), _jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No contact submissions found", renderRowActions: (row) => {
128
148
  const cr = row;
129
149
  return (_jsx(RowActionMenu, { actions: [
130
- { label: "View", onClick: () => { setSelectedRow(cr); setDrawerOpen(true); } },
150
+ { label: ROW_ACTION_META[ROW_ACTION_ID.VIEW].label, onClick: () => { setSelectedRow(cr); setDrawerOpen(true); } },
131
151
  {
132
- label: "Mark read",
152
+ label: ACTIONS.ADMIN["mark-contact-read"].label,
133
153
  disabled: cr.status === "read" || cr.status === "resolved",
134
154
  onClick: () => actionMutation.mutate({ id: cr.id, action: "read" }),
135
155
  },
136
156
  {
137
- label: "Archive",
157
+ label: ACTIONS.ADMIN["archive-contact"].label,
138
158
  disabled: cr.status === "resolved",
139
159
  onClick: () => actionMutation.mutate({ id: cr.id, action: "resolved" }),
140
160
  },
141
161
  { separator: true, label: "", onClick: () => { } },
142
162
  {
143
- label: "Delete",
163
+ label: ACTIONS.ADMIN["delete-contact"].label,
144
164
  destructive: true,
145
165
  onClick: () => { setSelectedRow(cr); setDeleteOpen(true); },
146
166
  },
@@ -3,5 +3,7 @@ import type { AdminListingScaffoldRow } from "./AdminListingScaffold";
3
3
  export interface AdminCouponsViewProps extends ListingViewShellProps {
4
4
  actionHref?: string;
5
5
  getRowHref?: (row: AdminListingScaffoldRow) => string;
6
+ onBulkArchive?: (ids: string[]) => Promise<void>;
7
+ onBulkDelete?: (ids: string[]) => Promise<void>;
6
8
  }
7
- export declare function AdminCouponsView({ children, getRowHref, ...props }: AdminCouponsViewProps): import("react/jsx-runtime").JSX.Element;
9
+ export declare function AdminCouponsView({ children, getRowHref, onBulkArchive, onBulkDelete, ...props }: AdminCouponsViewProps): import("react/jsx-runtime").JSX.Element;
@@ -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, Div, FilterChipGroup, ListingToolbar, Pagination, ListingViewShell, SideDrawer, Text, useToast } from "../../../ui";
8
+ import { BulkActionBar, Button, Div, FilterChipGroup, ListingToolbar, Pagination, ListingViewShell, SideDrawer, Text, 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_COUPON_TYPE_TABS } from "../constants/filter-tabs";
11
12
  import { apiClient } from "../../../http";
12
13
  import { toRecordArray, toStringValue, useAdminListingData, } from "../hooks/useAdminListingData";
@@ -26,7 +27,7 @@ function CouponsFilterDrawer({ filterOpen, setFilterOpen, activeFilterCount, cle
26
27
  return null;
27
28
  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: "Type", tabs: TYPE_OPTIONS, value: pendingFilters.type ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, type: 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})` : ""] }) })] })] }));
28
29
  }
29
- export function AdminCouponsView({ children, getRowHref, ...props }) {
30
+ export function AdminCouponsView({ children, getRowHref, onBulkArchive, onBulkDelete, ...props }) {
30
31
  const hasChildren = React.Children.count(children) > 0;
31
32
  const [view, setView] = useState("table");
32
33
  const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
@@ -102,8 +103,21 @@ export function AdminCouponsView({ children, getRowHref, ...props }) {
102
103
  const currentPage = table.getNumber("page", 1);
103
104
  const totalPages = Math.ceil(total / PAGE_SIZE);
104
105
  const selection = useBulkSelection({ items: rows, keyExtractor: (r) => r.id });
106
+ const bulkActions = [
107
+ ...(onBulkArchive ? [{
108
+ id: "bulk-archive",
109
+ label: "Archive",
110
+ onClick: async () => { await onBulkArchive(selection.selectedIds); selection.clearSelection(); },
111
+ }] : []),
112
+ ...(onBulkDelete ? [{
113
+ id: "bulk-delete",
114
+ label: ACTIONS.ADMIN["delete-coupon"].label,
115
+ variant: "danger",
116
+ onClick: async () => { await onBulkDelete(selection.selectedIds); selection.clearSelection(); },
117
+ }] : []),
118
+ ];
105
119
  if (hasChildren) {
106
120
  return _jsx(ListingViewShell, { portal: "admin", ...props, children: children });
107
121
  }
108
- return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search codes, campaigns, or seller scopes", 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 Coupon"] }) }), 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 })), isLoading ? (_jsx(Div, { className: "fluid-grid-card gap-3", children: Array.from({ length: 6 }).map((_, i) => (_jsxs(Div, { className: "rounded-xl border-2 border-zinc-100 dark:border-slate-700 p-4 animate-pulse space-y-3", children: [_jsx(Div, { className: "h-6 bg-zinc-200 dark:bg-slate-700 rounded w-2/3" }), _jsx(Div, { className: "h-4 bg-zinc-200 dark:bg-slate-700 rounded w-full" }), _jsx(Div, { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-1/2" })] }, i))) })) : rows.length === 0 ? (_jsx(Div, { className: "py-16 text-center", children: _jsx(Text, { className: "text-zinc-400 dark:text-zinc-500", children: "No coupons found" }) })) : (_jsx(Div, { className: "fluid-grid-card gap-3", children: rows.map((row) => (_jsx(CouponCard, { coupon: row.raw, onEdit: openEditPanel, onToggleActive: handleToggle, onDelete: handleDelete }, row.id))) }))] }), _jsx(CouponsFilterDrawer, { filterOpen: filterOpen, setFilterOpen: setFilterOpen, activeFilterCount: activeFilterCount, clearFilters: clearFilters, pendingFilters: pendingFilters, setPendingFilters: setPendingFilters, applyFilters: applyFilters }), _jsx(SideDrawer, { isOpen: isCreateOpen || isEditOpen, onClose: closePanel, title: isCreateOpen ? "Add Coupon" : "Edit Coupon", mode: isCreateOpen ? "create" : "edit", children: (isCreateOpen || isEditOpen) && (_jsx(AdminCouponEditorView, { couponId: editId ?? undefined, onSaved: closePanel, onDeleted: closePanel, embedded: true })) })] }));
122
+ return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search codes, campaigns, or seller scopes", 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 Coupon"] }) }), 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 })), isLoading ? (_jsx(Div, { className: "fluid-grid-card gap-3", children: Array.from({ length: 6 }).map((_, i) => (_jsxs(Div, { className: "rounded-xl border-2 border-zinc-100 dark:border-slate-700 p-4 animate-pulse space-y-3", children: [_jsx(Div, { className: "h-6 bg-zinc-200 dark:bg-slate-700 rounded w-2/3" }), _jsx(Div, { className: "h-4 bg-zinc-200 dark:bg-slate-700 rounded w-full" }), _jsx(Div, { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-1/2" })] }, i))) })) : rows.length === 0 ? (_jsx(Div, { className: "py-16 text-center", children: _jsx(Text, { className: "text-zinc-400 dark:text-zinc-500", children: "No coupons found" }) })) : (_jsx(Div, { className: "fluid-grid-card gap-3", children: rows.map((row) => (_jsx(CouponCard, { coupon: row.raw, onEdit: openEditPanel, onToggleActive: handleToggle, onDelete: handleDelete }, row.id))) }))] }), _jsx(CouponsFilterDrawer, { filterOpen: filterOpen, setFilterOpen: setFilterOpen, activeFilterCount: activeFilterCount, clearFilters: clearFilters, pendingFilters: pendingFilters, setPendingFilters: setPendingFilters, applyFilters: applyFilters }), _jsx(SideDrawer, { isOpen: isCreateOpen || isEditOpen, onClose: closePanel, title: isCreateOpen ? "Add Coupon" : "Edit Coupon", mode: isCreateOpen ? "create" : "edit", children: (isCreateOpen || isEditOpen) && (_jsx(AdminCouponEditorView, { couponId: editId ?? undefined, onSaved: closePanel, onDeleted: closePanel, embedded: true })) })] }));
109
123
  }
@@ -3,5 +3,7 @@ import type { AdminListingScaffoldRow } from "./AdminListingScaffold";
3
3
  export interface AdminFaqsViewProps extends ListingViewShellProps {
4
4
  actionHref?: string;
5
5
  getRowHref?: (row: AdminListingScaffoldRow) => string;
6
+ onBulkArchive?: (ids: string[]) => Promise<void>;
7
+ onBulkDelete?: (ids: string[]) => Promise<void>;
6
8
  }
7
- export declare function AdminFaqsView({ children, getRowHref, ...props }: AdminFaqsViewProps): import("react/jsx-runtime").JSX.Element;
9
+ export declare function AdminFaqsView({ children, getRowHref, onBulkArchive, onBulkDelete, ...props }: AdminFaqsViewProps): import("react/jsx-runtime").JSX.Element;