@mohasinac/appkit 2.7.58 → 2.7.59

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 (112) hide show
  1. package/dist/_internal/server/features/scams/og.js +1 -1
  2. package/dist/_internal/shared/actions/bulk-helpers.d.ts +3 -0
  3. package/dist/_internal/shared/actions/bulk-helpers.js +19 -0
  4. package/dist/_internal/shared/config/schema.d.ts +4 -4
  5. package/dist/client.d.ts +3 -0
  6. package/dist/client.js +2 -0
  7. package/dist/configs/next.js +11 -0
  8. package/dist/features/account/components/AddressesIndexListing.js +3 -4
  9. package/dist/features/account/components/UserSidebar.js +14 -10
  10. package/dist/features/admin/components/AdminAllEventEntriesView.js +3 -9
  11. package/dist/features/admin/components/AdminBidsView.js +2 -8
  12. package/dist/features/admin/components/AdminBlogView.js +4 -9
  13. package/dist/features/admin/components/AdminBrandEditorView.js +14 -17
  14. package/dist/features/admin/components/AdminBrandsView.js +4 -9
  15. package/dist/features/admin/components/AdminBundlesView.js +39 -58
  16. package/dist/features/admin/components/AdminCarouselView.js +7 -20
  17. package/dist/features/admin/components/AdminCartsView.js +3 -4
  18. package/dist/features/admin/components/AdminCategoriesView.js +4 -9
  19. package/dist/features/admin/components/AdminCategoryEditorView.js +1 -5
  20. package/dist/features/admin/components/AdminContactView.js +6 -24
  21. package/dist/features/admin/components/AdminCouponEditorView.js +14 -17
  22. package/dist/features/admin/components/AdminCouponsView.js +8 -16
  23. package/dist/features/admin/components/AdminFaqEditorView.js +14 -17
  24. package/dist/features/admin/components/AdminFaqsView.js +18 -69
  25. package/dist/features/admin/components/AdminFeatureEditorView.js +7 -9
  26. package/dist/features/admin/components/AdminNewsletterView.js +4 -14
  27. package/dist/features/admin/components/AdminNotificationsView.js +2 -8
  28. package/dist/features/admin/components/AdminOrdersView.js +2 -8
  29. package/dist/features/admin/components/AdminPayoutsView.js +2 -8
  30. package/dist/features/admin/components/AdminPrizeDrawsView.js +4 -9
  31. package/dist/features/admin/components/AdminProductEditorView.js +1 -5
  32. package/dist/features/admin/components/AdminProductsView.js +4 -9
  33. package/dist/features/admin/components/AdminReviewsView.js +2 -8
  34. package/dist/features/admin/components/AdminScammersView.js +2 -8
  35. package/dist/features/admin/components/AdminSessionsView.js +2 -8
  36. package/dist/features/admin/components/AdminStoresView.js +2 -8
  37. package/dist/features/admin/components/AdminSublistingCategoriesView.js +2 -6
  38. package/dist/features/admin/components/AdminSublistingCategoryEditorView.js +17 -20
  39. package/dist/features/admin/components/AdminSupportTicketsView.js +2 -8
  40. package/dist/features/admin/components/AdminTeamView.js +5 -14
  41. package/dist/features/admin/components/AdminUsersView.js +2 -8
  42. package/dist/features/admin/hooks/useAdminListing.d.ts +58 -0
  43. package/dist/features/admin/hooks/useAdminListing.js +71 -0
  44. package/dist/features/auth/schemas/firestore.d.ts +1 -0
  45. package/dist/features/blog/components/BlogIndexListing.js +3 -6
  46. package/dist/features/categories/components/CategoriesIndexListing.js +3 -6
  47. package/dist/features/events/components/AdminEventsView.js +4 -4
  48. package/dist/features/events/components/EventsIndexListing.js +3 -4
  49. package/dist/features/homepage/components/AnnouncementBar.d.ts +4 -1
  50. package/dist/features/homepage/components/AnnouncementBar.js +51 -4
  51. package/dist/features/homepage/components/MarketplaceHomepageView.d.ts +3 -1
  52. package/dist/features/homepage/components/MarketplaceHomepageView.js +2 -2
  53. package/dist/features/homepage/components/index.d.ts +1 -1
  54. package/dist/features/homepage/components/index.js +1 -1
  55. package/dist/features/homepage/lib/section-renderer.d.ts +2 -2
  56. package/dist/features/homepage/lib/section-renderer.js +2 -2
  57. package/dist/features/orders/repository/orders.repository.d.ts +0 -1
  58. package/dist/features/orders/repository/orders.repository.js +0 -1
  59. package/dist/features/orders/server.d.ts +1 -1
  60. package/dist/features/orders/server.js +1 -1
  61. package/dist/features/products/components/GroupSettingsPanel.js +64 -48
  62. package/dist/features/products/repository/products.repository.d.ts +0 -2
  63. package/dist/features/products/repository/products.repository.js +0 -2
  64. package/dist/features/products/server.d.ts +1 -1
  65. package/dist/features/products/server.js +1 -1
  66. package/dist/features/promotions/components/CouponCard.js +5 -5
  67. package/dist/features/promotions/components/CouponsIndexListing.js +3 -2
  68. package/dist/features/reviews/components/ReviewsIndexListing.js +3 -4
  69. package/dist/features/reviews/repository/reviews.repository.d.ts +0 -1
  70. package/dist/features/reviews/repository/reviews.repository.js +0 -1
  71. package/dist/features/reviews/server.d.ts +1 -1
  72. package/dist/features/reviews/server.js +1 -1
  73. package/dist/features/seller/components/SellerAddressesView.js +10 -20
  74. package/dist/features/seller/components/SellerAuctionsView.js +13 -22
  75. package/dist/features/seller/components/SellerBidsView.js +5 -5
  76. package/dist/features/seller/components/SellerBundlesView.js +11 -19
  77. package/dist/features/seller/components/SellerClassifiedView.js +11 -19
  78. package/dist/features/seller/components/SellerCouponsView.js +14 -20
  79. package/dist/features/seller/components/SellerDigitalCodesView.js +11 -19
  80. package/dist/features/seller/components/SellerGroupedListingsView.js +6 -10
  81. package/dist/features/seller/components/SellerLiveView.js +11 -19
  82. package/dist/features/seller/components/SellerOffersView.js +3 -4
  83. package/dist/features/seller/components/SellerOrdersView.js +8 -22
  84. package/dist/features/seller/components/SellerPayoutMethodsView.js +11 -23
  85. package/dist/features/seller/components/SellerPayoutsView.js +3 -4
  86. package/dist/features/seller/components/SellerPreOrdersView.js +13 -22
  87. package/dist/features/seller/components/SellerPrizeDrawsView.js +13 -22
  88. package/dist/features/seller/components/SellerProductsFilterDrawer.d.ts +1 -1
  89. package/dist/features/seller/components/SellerProductsFilterDrawer.js +3 -6
  90. package/dist/features/seller/components/SellerProductsView.js +9 -22
  91. package/dist/features/seller/components/SellerShippingConfigsView.js +11 -23
  92. package/dist/features/seller/components/SellerStoreCategoriesView.js +14 -33
  93. package/dist/features/seller/components/SellerTemplatesView.js +25 -41
  94. package/dist/features/stores/components/StoreReviewsListing.js +3 -4
  95. package/dist/features/stores/components/StoresIndexListing.js +3 -4
  96. package/dist/index.d.ts +2 -6
  97. package/dist/index.js +3 -12
  98. package/dist/react/contexts/SessionContext.d.ts +2 -0
  99. package/dist/react/contexts/SessionContext.js +2 -0
  100. package/dist/react/hooks/useEntityDelete.d.ts +15 -0
  101. package/dist/react/hooks/useEntityDelete.js +39 -0
  102. package/dist/react/index.d.ts +2 -0
  103. package/dist/react/index.js +2 -0
  104. package/dist/repositories/index.d.ts +3 -3
  105. package/dist/repositories/index.js +3 -3
  106. package/dist/styles.css +1 -1
  107. package/dist/tailwind-utilities.css +1 -1
  108. package/dist/ui/components/ListingFilterDrawer.d.ts +10 -0
  109. package/dist/ui/components/ListingFilterDrawer.js +8 -0
  110. package/dist/ui/index.d.ts +2 -0
  111. package/dist/ui/index.js +1 -0
  112. package/package.json +1 -1
@@ -55,5 +55,5 @@ export function renderScamOgImage(data, siteName) {
55
55
  fontSize: 22,
56
56
  color: "#f87171",
57
57
  fontWeight: 600,
58
- }, children: scamTypeLabel }), reportCount > 0 && (_jsxs("div", { style: { fontSize: 22, color: "#64748b" }, children: [reportCount, " ", reportCount === 1 ? "report" : "reports"] }))] }), _jsx("div", { style: { fontSize: 20, color: "#64748b", marginTop: 8 }, children: "Do not transact with this individual. Verified by the LetItRip community." })] })] }));
58
+ }, children: scamTypeLabel }), reportCount > 0 && (_jsxs("div", { style: { fontSize: 22, color: "#64748b" }, children: [reportCount, " ", reportCount === 1 ? "report" : "reports"] }))] }), _jsxs("div", { style: { fontSize: 20, color: "#64748b", marginTop: 8 }, children: ["Do not transact with this individual. Verified by the ", siteName, " community."] })] })] }));
59
59
  }
@@ -0,0 +1,3 @@
1
+ import type { ActionDef } from "./action-registry";
2
+ import type { BulkActionItem } from "../../../ui/components/BulkActionBar";
3
+ export declare function buildBulkAction(actionDef: ActionDef, onClick: () => void | Promise<void>, overrides?: Partial<Pick<BulkActionItem, "id" | "variant" | "icon" | "disabled" | "loading">>): BulkActionItem;
@@ -0,0 +1,19 @@
1
+ const KIND_TO_VARIANT = {
2
+ primary: "primary",
3
+ secondary: "secondary",
4
+ danger: "danger",
5
+ ghost: "ghost",
6
+ link: "ghost",
7
+ chip: "ghost",
8
+ };
9
+ export function buildBulkAction(actionDef, onClick, overrides) {
10
+ return {
11
+ id: overrides?.id ?? `bulk-${actionDef.id}`,
12
+ label: actionDef.label,
13
+ variant: overrides?.variant ?? KIND_TO_VARIANT[actionDef.kind] ?? "secondary",
14
+ onClick,
15
+ ...(overrides?.icon !== undefined && { icon: overrides.icon }),
16
+ ...(overrides?.disabled !== undefined && { disabled: overrides.disabled }),
17
+ ...(overrides?.loading !== undefined && { loading: overrides.loading }),
18
+ };
19
+ }
@@ -13,7 +13,7 @@
13
13
  * baseUrl: "http://localhost:3000",
14
14
  * locales: ["en"],
15
15
  * routes: {
16
- * smoke: [{ path: "/", expect: ["LetItRip"] }],
16
+ * smoke: [{ path: "/", expect: ["MyBrand"] }],
17
17
  * },
18
18
  * };
19
19
  * export default config;
@@ -84,7 +84,7 @@ export interface AppkitFirebaseConfig {
84
84
  extensions?: AppkitFirebaseExtensions;
85
85
  }
86
86
  export interface AppkitBrandConfig {
87
- /** Display name of the brand, e.g. "LetItRip" */
87
+ /** Display name of the brand, e.g. "Acme Store" */
88
88
  name: string;
89
89
  /** Short / abbreviated name, e.g. "LT" */
90
90
  shortName?: string;
@@ -102,7 +102,7 @@ export interface AppkitBrandConfig {
102
102
  };
103
103
  }
104
104
  export interface AppkitSeoConfig {
105
- /** Canonical site URL, e.g. "https://letitrip.in" */
105
+ /** Canonical site URL, e.g. "https://example.com" */
106
106
  siteUrl: string;
107
107
  /** Default page <title> */
108
108
  defaultTitle?: string;
@@ -112,7 +112,7 @@ export interface AppkitSeoConfig {
112
112
  defaultImage?: string;
113
113
  /** OG site name */
114
114
  siteName?: string;
115
- /** Twitter/X handle including @, e.g. "@letitrip" */
115
+ /** Twitter/X handle including @, e.g. "@mybrand" */
116
116
  twitterHandle?: string;
117
117
  /** BCP 47 locale tag for OG/schema.org, e.g. "en-IN" */
118
118
  locale?: string;
package/dist/client.d.ts CHANGED
@@ -59,6 +59,8 @@ export { useUrlTable } from "./react/hooks/useUrlTable";
59
59
  export type { UseUrlTableOptions } from "./react/hooks/useUrlTable";
60
60
  export { useBulkSelection } from "./react/hooks/useBulkSelection";
61
61
  export type { UseBulkSelectionOptions, UseBulkSelectionReturn } from "./react/hooks/useBulkSelection";
62
+ export { useEntityDelete } from "./react/hooks/useEntityDelete";
63
+ export type { UseEntityDeleteOptions, UseEntityDeleteReturn } from "./react/hooks/useEntityDelete";
62
64
  export { ROUTES, PUBLIC_ROUTES, PROTECTED_ROUTES, AUTH_ROUTES } from "./constants/index";
63
65
  export { Container, Grid, Row, Stack } from "./ui/components/Layout";
64
66
  export { Div } from "./ui/components/Div";
@@ -225,6 +227,7 @@ export { isListingTypeEnabled, isCategoryTypeEnabled, enabledListingTypes, enabl
225
227
  export { actionTracker, setActionTrackerSink, resetActionTrackerSink, type ActionEvent, type ActionTrackerSink, } from "./_internal/shared/listing-types/action-tracker";
226
228
  export { cartRequiresShipping, cartIsDigitalOnly, cartIsChatOnly, } from "./_internal/shared/listing-types/cart-shipping";
227
229
  export { ACTIONS, action, act, canPerformAction, actionsForListingType, actionLabel, type ActionDef, type ActionKind, type ActionResource, type ActionTree, type ActionConfirmation, } from "./_internal/shared/actions/action-registry";
230
+ export { buildBulkAction } from "./_internal/shared/actions/bulk-helpers";
228
231
  export { isAdminUser, isSellerUser, isModeratorUser, isEmployeeUser, isBuyerUser, } from "./features/auth/role-predicates";
229
232
  export { DashboardLayoutClient, RoleGuard } from "./_internal/client/features/layout/index";
230
233
  export type { DashboardLayoutClientProps, RoleGuardProps, } from "./_internal/client/features/layout/index";
package/dist/client.js CHANGED
@@ -122,6 +122,7 @@ export { SessionProvider, useSession } from "./react/contexts/SessionContext";
122
122
  export { ThemeProvider } from "./react/contexts/ThemeContext";
123
123
  export { useUrlTable } from "./react/hooks/useUrlTable";
124
124
  export { useBulkSelection } from "./react/hooks/useBulkSelection";
125
+ export { useEntityDelete } from "./react/hooks/useEntityDelete";
125
126
  // Client-safe constants, UI primitives, and views
126
127
  export { ROUTES, PUBLIC_ROUTES, PROTECTED_ROUTES, AUTH_ROUTES } from "./constants/index";
127
128
  export { Container, Grid, Row, Stack } from "./ui/components/Layout";
@@ -239,6 +240,7 @@ export { cartRequiresShipping, cartIsDigitalOnly, cartIsChatOnly, } from "./_int
239
240
  // SB-UNI-W-1 2026-05-13 — CTA action registry shell (client-safe).
240
241
  // Phase 7 W-2..W-4 sweeps fill the per-resource buckets surface by surface.
241
242
  export { ACTIONS, action, act, canPerformAction, actionsForListingType, actionLabel, } from "./_internal/shared/actions/action-registry";
243
+ export { buildBulkAction } from "./_internal/shared/actions/bulk-helpers";
242
244
  // SB-UNI-E user-role predicates (pure functions, client-safe).
243
245
  export { isAdminUser, isSellerUser, isModeratorUser, isEmployeeUser, isBuyerUser, } from "./features/auth/role-predicates";
244
246
  // Layout feature — client islands (unifies admin/store/user dashboard layouts).
@@ -90,6 +90,17 @@ export function defineNextConfig(override = {}) {
90
90
  "/api/**": [
91
91
  // Firebase Admin — entire package (lib/ + esm/, all sub-SDKs)
92
92
  "./node_modules/firebase-admin/**",
93
+ // @firebase/database-compat — dynamically required by firebase-admin/database
94
+ "./node_modules/@firebase/database-compat/**",
95
+ "./node_modules/@firebase/database/**",
96
+ "./node_modules/@firebase/database-types/**",
97
+ "./node_modules/@firebase/component/**",
98
+ "./node_modules/@firebase/util/**",
99
+ "./node_modules/@firebase/logger/**",
100
+ "./node_modules/@firebase/app/**",
101
+ "./node_modules/@firebase/app-check-interop-types/**",
102
+ "./node_modules/@firebase/auth-interop-types/**",
103
+ "./node_modules/faye-websocket/**",
93
104
  // All @google-cloud packages: firestore (incl. protos/**), storage, paginator, etc.
94
105
  "./node_modules/@google-cloud/**",
95
106
  // Auth libraries used by Firebase Admin token verification
@@ -1,10 +1,9 @@
1
1
  "use client";
2
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useState, useCallback, useMemo } from "react";
4
- import { X } from "lucide-react";
5
4
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
6
5
  import { useAddresses } from "../hooks/useAddresses";
7
- import { ListingToolbar, Text } from "../../../ui";
6
+ import { ListingFilterDrawer, ListingToolbar, Text } from "../../../ui";
8
7
  import { AddressBook } from "./AddressBook";
9
8
  import { AddressFilters } from "./AddressFilters";
10
9
  import { TABLE_KEYS, VIEW_MODE } from "../../../constants/table-keys";
@@ -75,5 +74,5 @@ export function AddressesIndexListing({ onAdd, onEdit, onDelete, }) {
75
74
  },
76
75
  });
77
76
  return (_jsxs("div", { className: "space-y-4", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search by address, postcode or label...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, view: view, onViewChange: (v) => { if (v === "table")
78
- return; setView(v); }, onResetAll: resetAll, hasActiveState: hasActiveState }), _jsx("div", { className: "px-3 sm:px-4", children: isLoading ? (_jsx("div", { className: "grid sm:grid-cols-2 gap-4", children: Array.from({ length: 4 }).map((_, i) => (_jsxs("div", { className: "rounded-xl border border-zinc-200 dark:border-slate-700 animate-pulse p-4 space-y-2", children: [_jsx("div", { className: "h-4 bg-zinc-200 dark:bg-slate-700 rounded w-1/3" }), _jsx("div", { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-3/4" }), _jsx("div", { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-1/2" })] }, i))) })) : addresses.length === 0 ? (_jsx(Text, { className: "py-8 text-center text-sm text-zinc-500 dark:text-zinc-400", children: table.get(TABLE_KEYS.QUERY) ? `No addresses matching "${table.get(TABLE_KEYS.QUERY)}"` : "No saved addresses." })) : (_jsx(AddressBook, { addresses: addresses, onEdit: onEdit, onDelete: onDelete, onAdd: onAdd })) }), filterOpen && (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 z-40 bg-black/40", "aria-hidden": "true", onClick: () => setFilterOpen(false) }), _jsxs("div", { className: "fixed inset-y-0 left-0 z-50 flex w-80 flex-col bg-white dark:bg-slate-900 shadow-2xl", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: [_jsx("span", { className: "flex items-center gap-2 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 filters", className: "rounded-lg p-1.5 text-zinc-500 dark:text-zinc-400 hover:bg-zinc-100 dark:hover:bg-zinc-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(AddressFilters, { table: pendingTable }) }), _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})` : ""] }) })] })] }))] }));
77
+ return; setView(v); }, onResetAll: resetAll, hasActiveState: hasActiveState }), _jsx("div", { className: "px-3 sm:px-4", children: isLoading ? (_jsx("div", { className: "grid sm:grid-cols-2 gap-4", children: Array.from({ length: 4 }).map((_, i) => (_jsxs("div", { className: "rounded-xl border border-zinc-200 dark:border-slate-700 animate-pulse p-4 space-y-2", children: [_jsx("div", { className: "h-4 bg-zinc-200 dark:bg-slate-700 rounded w-1/3" }), _jsx("div", { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-3/4" }), _jsx("div", { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-1/2" })] }, i))) })) : addresses.length === 0 ? (_jsx(Text, { className: "py-8 text-center text-sm text-zinc-500 dark:text-zinc-400", children: table.get(TABLE_KEYS.QUERY) ? `No addresses matching "${table.get(TABLE_KEYS.QUERY)}"` : "No saved addresses." })) : (_jsx(AddressBook, { addresses: addresses, onEdit: onEdit, onDelete: onDelete, onAdd: onAdd })) }), _jsx(ListingFilterDrawer, { open: filterOpen, onClose: () => setFilterOpen(false), onApply: applyFilters, onClear: clearFilters, activeCount: activeFilterCount, children: _jsx(AddressFilters, { table: pendingTable }) })] }));
79
78
  }
@@ -4,26 +4,30 @@ import { useState, useCallback, useEffect } from "react";
4
4
  import { createPortal } from "react-dom";
5
5
  import Link from "next/link";
6
6
  import { usePathname } from "next/navigation";
7
- import { Li, Nav, Span, Ul } from "../../../ui";
7
+ import { ConfirmDeleteModal, Li, Nav, Span, Ul } from "../../../ui";
8
8
  import { BottomSheet } from "../../layout/BottomSheet";
9
9
  import { SidebarCollapseToggle } from "../../../_internal/client/features/layout/SidebarCollapseToggle";
10
10
  function isNavItemActive(item, activeHref) {
11
11
  return activeHref === item.href || activeHref.startsWith(item.href + "/");
12
12
  }
13
13
  function NavLink({ item, isActive, onClick }) {
14
+ const [showConfirm, setShowConfirm] = useState(false);
14
15
  const handleClick = (e) => {
15
- if (item.confirm && typeof window !== "undefined") {
16
- const ok = window.confirm(item.confirm.message);
17
- if (!ok) {
18
- e.preventDefault();
19
- return;
20
- }
16
+ if (item.confirm) {
17
+ e.preventDefault();
18
+ setShowConfirm(true);
19
+ return;
21
20
  }
22
21
  onClick?.();
23
22
  };
24
- return (_jsxs(Link, { href: item.href, onClick: handleClick, className: `flex items-center gap-2.5 rounded-lg px-3 py-2 text-[0.8125rem] font-medium leading-tight transition-colors ${isActive
25
- ? "bg-primary-50 dark:bg-primary-900/25 text-primary-700 dark:text-primary-300"
26
- : "text-zinc-500 dark:text-zinc-400 hover:bg-zinc-50 dark:hover:bg-slate-800 hover:text-zinc-900 dark:hover:text-zinc-100"}`, children: [item.icon && _jsx(Span, { className: "shrink-0 text-base opacity-70", children: item.icon }), _jsx(Span, { className: "flex-1 truncate", children: item.label })] }));
23
+ const linkClass = `flex items-center gap-2.5 rounded-lg px-3 py-2 text-[0.8125rem] font-medium leading-tight transition-colors ${isActive
24
+ ? "bg-primary-50 dark:bg-primary-900/25 text-primary-700 dark:text-primary-300"
25
+ : "text-zinc-500 dark:text-zinc-400 hover:bg-zinc-50 dark:hover:bg-slate-800 hover:text-zinc-900 dark:hover:text-zinc-100"}`;
26
+ return (_jsxs(_Fragment, { children: [_jsxs(Link, { href: item.href, onClick: handleClick, className: linkClass, children: [item.icon && _jsx(Span, { className: "shrink-0 text-base opacity-70", children: item.icon }), _jsx(Span, { className: "flex-1 truncate", children: item.label })] }), showConfirm && item.confirm && (_jsx(ConfirmDeleteModal, { isOpen: true, title: item.confirm.title ?? "Confirm", message: item.confirm.message, confirmText: "Continue", variant: "warning", onConfirm: () => {
27
+ setShowConfirm(false);
28
+ onClick?.();
29
+ window.location.href = item.href;
30
+ }, onClose: () => setShowConfirm(false) }))] }));
27
31
  }
28
32
  function DrawerContent({ groups, items, activeHref, onItemClick, }) {
29
33
  const [openGroups, setOpenGroups] = useState(() => {
@@ -1,11 +1,10 @@
1
1
  "use client";
2
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import React, { useState, useCallback } from "react";
4
- import { X } from "lucide-react";
5
4
  import { useMutation, useQueryClient } from "@tanstack/react-query";
6
5
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
7
6
  import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
8
- import { FilterChipGroup, ListingToolbar, Pagination, ListingViewShell, RowActionMenu, useToast } from "../../../ui";
7
+ import { FilterChipGroup, ListingToolbar, Pagination, ListingViewShell, RowActionMenu, useToast, ListingFilterDrawer } from "../../../ui";
9
8
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
10
9
  import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
11
10
  import { ADMIN_EVENT_ENTRY_STATUS_TABS } from "../constants/filter-tabs";
@@ -20,11 +19,6 @@ const SORT_OPTIONS = [
20
19
  { value: "submittedAt", label: "Oldest" },
21
20
  ];
22
21
  const STATUS_OPTIONS = ADMIN_EVENT_ENTRY_STATUS_TABS;
23
- function EntriesFilterDrawer({ filterOpen, setFilterOpen, activeFilterCount, clearFilters, pendingFilters, setPendingFilters, applyFilters, }) {
24
- if (!filterOpen)
25
- return null;
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 dark:text-zinc-400 hover:bg-zinc-100 dark:hover:bg-zinc-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
- }
28
22
  export function AdminAllEventEntriesView({ children, ...props }) {
29
23
  const hasChildren = React.Children.count(children) > 0;
30
24
  const [view, setView] = useState("table");
@@ -108,5 +102,5 @@ export function AdminAllEventEntriesView({ children, ...props }) {
108
102
  { label: ACTIONS.ADMIN["waitlist-entry"].label, onClick: () => updateMutation.mutate({ id: er.id, status: "WAITLISTED" }) },
109
103
  { label: ACTIONS.ADMIN["cancel-entry"].label, destructive: true, onClick: () => updateMutation.mutate({ id: er.id, status: "CANCELLED" }) },
110
104
  ] }));
111
- } })] }), _jsx(EntriesFilterDrawer, { filterOpen: filterOpen, setFilterOpen: setFilterOpen, activeFilterCount: activeFilterCount, clearFilters: clearFilters, pendingFilters: pendingFilters, setPendingFilters: setPendingFilters, applyFilters: applyFilters })] }));
105
+ } })] }), _jsx(ListingFilterDrawer, { open: filterOpen, onClose: () => setFilterOpen(false), onApply: applyFilters, onClear: clearFilters, activeCount: activeFilterCount, children: _jsx(FilterChipGroup, { label: "Status", tabs: STATUS_OPTIONS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) }) })] }));
112
106
  }
@@ -1,11 +1,10 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import React, { useState, useCallback } from "react";
4
- import { X } from "lucide-react";
5
4
  import { useMutation, useQueryClient } from "@tanstack/react-query";
6
5
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
7
6
  import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
8
- import { BulkActionBar, FilterChipGroup, ListingToolbar, Pagination, ListingViewShell, ConfirmDeleteModal, RowActionMenu, useToast } from "../../../ui";
7
+ import { BulkActionBar, FilterChipGroup, ListingToolbar, Pagination, ListingViewShell, ConfirmDeleteModal, RowActionMenu, useToast, ListingFilterDrawer } from "../../../ui";
9
8
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
10
9
  import { ADMIN_BID_STATUS_TABS } from "../constants/filter-tabs";
11
10
  import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
@@ -23,11 +22,6 @@ const SORT_OPTIONS = [
23
22
  { value: "-amount", label: "Highest amount" },
24
23
  ];
25
24
  const STATUS_OPTIONS = ADMIN_BID_STATUS_TABS;
26
- function BidsFilterDrawer({ filterOpen, setFilterOpen, activeFilterCount, clearFilters, pendingFilters, setPendingFilters, applyFilters, }) {
27
- if (!filterOpen)
28
- return null;
29
- 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 dark:text-zinc-400 hover:bg-zinc-100 dark:hover:bg-zinc-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})` : ""] }) })] })] }));
30
- }
31
25
  export function AdminBidsView({ children, ...props }) {
32
26
  const hasChildren = React.Children.count(children) > 0;
33
27
  const [view, setView] = useState("table");
@@ -127,6 +121,6 @@ export function AdminBidsView({ children, ...props }) {
127
121
  },
128
122
  },
129
123
  ] }));
130
- } })] }), _jsx(BidsFilterDrawer, { filterOpen: filterOpen, setFilterOpen: setFilterOpen, activeFilterCount: activeFilterCount, clearFilters: clearFilters, pendingFilters: pendingFilters, setPendingFilters: setPendingFilters, applyFilters: applyFilters })] }), _jsx(ConfirmDeleteModal, { isOpen: cancelOpen, onClose: () => { setCancelOpen(false); setSelectedRow(null); }, onConfirm: () => { if (selectedRow)
124
+ } })] }), _jsx(ListingFilterDrawer, { open: filterOpen, onClose: () => setFilterOpen(false), onApply: applyFilters, onClear: clearFilters, activeCount: activeFilterCount, children: _jsx(FilterChipGroup, { label: "Status", tabs: STATUS_OPTIONS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) }) })] }), _jsx(ConfirmDeleteModal, { isOpen: cancelOpen, onClose: () => { setCancelOpen(false); setSelectedRow(null); }, onConfirm: () => { if (selectedRow)
131
125
  cancelMutation.mutate(selectedRow.id); }, isDeleting: cancelMutation.isPending, title: "Cancel this bid?", message: "This will mark the bid as cancelled and notify the bidder. The auction will continue with remaining active bids.", confirmText: "Cancel bid", variant: "warning" })] }));
132
126
  }
@@ -1,11 +1,11 @@
1
1
  "use client";
2
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import React, { useState, useCallback } from "react";
4
- import { Plus, X } from "lucide-react";
4
+ import { Plus } from "lucide-react";
5
5
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
6
6
  import { usePanelUrlSync } from "../../../react/hooks/use-panel-url-sync";
7
7
  import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
8
- import { BulkActionBar, Button, FilterChipGroup, Heading, ListingToolbar, ListingViewShell, Pagination, SideDrawer } from "../../../ui";
8
+ import { BulkActionBar, Button, FilterChipGroup, Heading, ListingToolbar, ListingViewShell, Pagination, SideDrawer, ListingFilterDrawer } from "../../../ui";
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";
@@ -28,11 +28,6 @@ const FEATURED_TABS = [
28
28
  { id: "", label: "All" },
29
29
  { id: "true", label: "Featured only" },
30
30
  ];
31
- function BlogFilterDrawer({ filterOpen, setFilterOpen, activeFilterCount, clearFilters, pendingFilters, setPendingFilters, applyFilters, }) {
32
- if (!filterOpen)
33
- return null;
34
- 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 dark:text-zinc-400 hover:bg-zinc-100 dark:hover:bg-zinc-800 transition-colors", children: _jsx(X, { className: "h-5 w-5" }) })] })] }), _jsxs("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(FilterChipGroup, { label: "Featured", tabs: FEATURED_TABS, value: pendingFilters.isFeatured ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, isFeatured: id })), allId: "" })] }), _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})` : ""] }) })] })] }));
35
- }
36
31
  export function AdminBlogView({ children, getRowHref, ...props }) {
37
32
  const hasChildren = React.Children.count(children) > 0;
38
33
  const [view, setView] = useState("table");
@@ -113,5 +108,5 @@ export function AdminBlogView({ children, getRowHref, ...props }) {
113
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: [
114
109
  { id: "publish", label: ACTIONS.ADMIN["publish-blog"].label, variant: "primary", onClick: () => { selection.clearSelection(); } },
115
110
  { id: "draft", label: ACTIONS.ADMIN["draft-blog"].label, variant: "secondary", onClick: () => { selection.clearSelection(); } },
116
- ] }), 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-error-surface px-4 py-3 text-sm text-error dark:border-red-900/60", 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
+ ] }), 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-error-surface px-4 py-3 text-sm text-error dark:border-red-900/60", 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 }))] }), _jsxs(ListingFilterDrawer, { open: filterOpen, onClose: () => setFilterOpen(false), onApply: applyFilters, onClear: clearFilters, activeCount: activeFilterCount, children: [_jsx(FilterChipGroup, { label: "Status", tabs: STATUS_OPTIONS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) }), _jsx(FilterChipGroup, { label: "Featured", tabs: FEATURED_TABS, value: pendingFilters.isFeatured ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, isFeatured: id })), allId: "" })] }), _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 })) })] }));
117
112
  }
@@ -2,7 +2,7 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import React from "react";
4
4
  import { useMutation, useQuery } from "@tanstack/react-query";
5
- import { Button, Form, Input, StackedViewShell, Toggle, useToast, } from "../../../ui";
5
+ import { Button, ConfirmDeleteModal, Form, Input, StackedViewShell, Toggle, useToast, } from "../../../ui";
6
6
  import { FieldInput, FormShellContext, useFormShellState } from "../../../ui/forms";
7
7
  import { ImageUpload } from "../../media/upload/ImageUpload";
8
8
  import { useMediaUpload } from "../../media";
@@ -23,6 +23,7 @@ export function AdminBrandEditorView({ brandId, onSaved, onDeleted, embedded, ..
23
23
  const [website, setWebsite] = React.useState("");
24
24
  const [isActive, setIsActive] = React.useState(true);
25
25
  const [displayOrder, setDisplayOrder] = React.useState("");
26
+ const [deleteConfirmOpen, setDeleteConfirmOpen] = React.useState(false);
26
27
  const { showToast } = useToast();
27
28
  const { shellCtx, setFieldError, clearErrors } = useFormShellState();
28
29
  const brandQuery = useQuery({
@@ -90,22 +91,18 @@ export function AdminBrandEditorView({ brandId, onSaved, onDeleted, embedded, ..
90
91
  });
91
92
  const { upload } = useMediaUpload();
92
93
  const isSubmitting = saveMutation.isPending || brandQuery.isLoading;
93
- const formSection = (_jsx(FormShellContext.Provider, { value: shellCtx, children: _jsxs(Form, { onSubmit: (e) => {
94
- e.preventDefault();
95
- clearErrors();
96
- if (!name.trim()) {
97
- setFieldError("name", "Brand name is required");
98
- return;
99
- }
100
- saveMutation.mutate();
101
- }, className: "space-y-4", children: [_jsxs("div", { className: "grid sm:grid-cols-2 gap-4", children: [_jsx(FieldInput, { name: "name", label: "Brand name", value: name, onChange: (v) => handleNameChange(v), required: true, placeholder: "e.g. Hot Wheels" }), _jsx(Input, { label: "Slug", value: slug, onChange: (e) => {
102
- setSlug(e.target.value);
103
- setSlugManual(true);
104
- }, placeholder: "brand-hot-wheels", helperText: "Auto-generated from name. Must start with 'brand-'." })] }), _jsx(Input, { label: "Description", value: description, onChange: (e) => setDescription(e.target.value), placeholder: "Brief description of the brand" }), _jsxs("div", { className: "grid sm:grid-cols-2 gap-4", children: [_jsx(ImageUpload, { label: "Logo", currentImage: logoURL, onUpload: (file) => upload(file, "brands", true, { type: "brand-logo", brand: name || slug }), onChange: setLogoURL }), _jsx(ImageUpload, { label: "Banner", currentImage: bannerURL, onUpload: (file) => upload(file, "brands", true, { type: "brand-banner", brand: name || slug }), onChange: setBannerURL })] }), _jsxs("div", { className: "grid sm:grid-cols-2 gap-4", children: [_jsx(Input, { label: "Website", value: website, onChange: (e) => setWebsite(e.target.value), placeholder: "https://brand.com", type: "url" }), _jsx(Input, { label: "Display order", value: displayOrder, onChange: (e) => setDisplayOrder(e.target.value), type: "number", min: 0, placeholder: "0" })] }), _jsx(Toggle, { label: "Active", checked: isActive, onChange: setIsActive }), _jsxs("div", { className: "flex gap-3 pt-2", children: [_jsx(Button, { type: "submit", isLoading: isSubmitting, disabled: !name || isSubmitting, children: isEdit ? "Save changes" : "Create brand" }), isEdit && (_jsx(Button, { type: "button", variant: "danger", isLoading: deleteMutation.isPending, onClick: () => {
105
- if (confirm("Delete this brand? This cannot be undone.")) {
106
- deleteMutation.mutate();
107
- }
108
- }, children: "Delete brand" }))] })] }, "brand-form") }));
94
+ const formSection = (_jsxs(FormShellContext.Provider, { value: shellCtx, children: [_jsxs(Form, { onSubmit: (e) => {
95
+ e.preventDefault();
96
+ clearErrors();
97
+ if (!name.trim()) {
98
+ setFieldError("name", "Brand name is required");
99
+ return;
100
+ }
101
+ saveMutation.mutate();
102
+ }, className: "space-y-4", children: [_jsxs("div", { className: "grid sm:grid-cols-2 gap-4", children: [_jsx(FieldInput, { name: "name", label: "Brand name", value: name, onChange: (v) => handleNameChange(v), required: true, placeholder: "e.g. Hot Wheels" }), _jsx(Input, { label: "Slug", value: slug, onChange: (e) => {
103
+ setSlug(e.target.value);
104
+ setSlugManual(true);
105
+ }, placeholder: "brand-hot-wheels", helperText: "Auto-generated from name. Must start with 'brand-'." })] }), _jsx(Input, { label: "Description", value: description, onChange: (e) => setDescription(e.target.value), placeholder: "Brief description of the brand" }), _jsxs("div", { className: "grid sm:grid-cols-2 gap-4", children: [_jsx(ImageUpload, { label: "Logo", currentImage: logoURL, onUpload: (file) => upload(file, "brands", true, { type: "brand-logo", brand: name || slug }), onChange: setLogoURL }), _jsx(ImageUpload, { label: "Banner", currentImage: bannerURL, onUpload: (file) => upload(file, "brands", true, { type: "brand-banner", brand: name || slug }), onChange: setBannerURL })] }), _jsxs("div", { className: "grid sm:grid-cols-2 gap-4", children: [_jsx(Input, { label: "Website", value: website, onChange: (e) => setWebsite(e.target.value), placeholder: "https://brand.com", type: "url" }), _jsx(Input, { label: "Display order", value: displayOrder, onChange: (e) => setDisplayOrder(e.target.value), type: "number", min: 0, placeholder: "0" })] }), _jsx(Toggle, { label: "Active", checked: isActive, onChange: setIsActive }), _jsxs("div", { className: "flex gap-3 pt-2", children: [_jsx(Button, { type: "submit", isLoading: isSubmitting, disabled: !name || isSubmitting, children: isEdit ? "Save changes" : "Create brand" }), isEdit && (_jsx(Button, { type: "button", variant: "danger", onClick: () => setDeleteConfirmOpen(true), children: "Delete brand" }))] })] }, "brand-form"), deleteConfirmOpen && (_jsx(ConfirmDeleteModal, { isOpen: true, title: "Delete Brand", message: "Delete this brand? This cannot be undone.", onConfirm: () => { deleteMutation.mutate(); setDeleteConfirmOpen(false); }, onClose: () => setDeleteConfirmOpen(false), isDeleting: deleteMutation.isPending }))] }));
109
106
  if (embedded) {
110
107
  return _jsx("div", { className: "overflow-y-auto p-4", children: formSection });
111
108
  }
@@ -1,11 +1,11 @@
1
1
  "use client";
2
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import React, { useState, useCallback } from "react";
4
- import { Plus, X } from "lucide-react";
4
+ import { Plus } from "lucide-react";
5
5
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
6
6
  import { usePanelUrlSync } from "../../../react/hooks/use-panel-url-sync";
7
7
  import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
8
- import { BulkActionBar, Button, ListingToolbar, ListingViewShell, Pagination, SideDrawer, Text } from "../../../ui";
8
+ import { BulkActionBar, Button, ListingToolbar, ListingViewShell, Pagination, SideDrawer, Text, ListingFilterDrawer } from "../../../ui";
9
9
  import { AdminViewCards } from "./AdminViewCards";
10
10
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
11
11
  import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
@@ -22,11 +22,6 @@ const SORT_OPTIONS = [
22
22
  { value: "-name", label: "Name Z–A" },
23
23
  { value: "-createdAt", label: "Newest" },
24
24
  ];
25
- function BrandsFilterDrawer({ filterOpen, setFilterOpen, activeFilterCount, clearFilters, pendingFilters, setPendingFilters, applyFilters, }) {
26
- if (!filterOpen)
27
- return null;
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 dark:text-zinc-400 hover:bg-zinc-100 dark:hover:bg-zinc-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, 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})` : ""] }) })] })] }));
29
- }
30
25
  export function AdminBrandsView({ children, ...props }) {
31
26
  const hasChildren = React.Children.count(children) > 0;
32
27
  const [view, setView] = useState("table");
@@ -93,5 +88,5 @@ export function AdminBrandsView({ children, ...props }) {
93
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: [
94
89
  { id: "edit", label: ACTIONS.ADMIN["edit-brand"].label, variant: "primary", onClick: () => { const id = selection.selectedIds[0]; if (id)
95
90
  openEditPanel(id); selection.clearSelection(); } },
96
- ] }), 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-error-surface px-4 py-3 text-sm text-error dark:border-red-900/60", 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
+ ] }), 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-error-surface px-4 py-3 text-sm text-error dark:border-red-900/60", 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(ListingFilterDrawer, { open: filterOpen, onClose: () => setFilterOpen(false), onApply: applyFilters, onClear: clearFilters, activeCount: activeFilterCount, 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, 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(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 })) })] }));
97
92
  }
@@ -1,15 +1,16 @@
1
1
  "use client";
2
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useState, useCallback } from "react";
4
- import { Plus, X } from "lucide-react";
4
+ import { Plus } from "lucide-react";
5
5
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
6
6
  import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
7
- import { Badge, BulkActionBar, Button, Heading, ListingToolbar, Pagination, Stack, Text, useToast, } from "../../../ui";
7
+ import { Badge, BulkActionBar, Button, Heading, ListingToolbar, Pagination, Stack, Text, useToast, ListingFilterDrawer } from "../../../ui";
8
8
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
9
9
  import { toRecordArray, toRelativeDate, toStringValue, useAdminListingData, } from "../hooks/useAdminListingData";
10
10
  import { DataTable } from "./DataTable";
11
11
  import { BUNDLE_COPY, BUNDLE_STOCK_VARIANT, } from "../../../_internal/shared/features/categories/bundle-copy";
12
12
  import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
13
+ import { buildBulkAction } from "../../../_internal/shared/actions/bulk-helpers";
13
14
  import { useBottomActions } from "../../layout";
14
15
  const PAGE_SIZE = 25;
15
16
  const FILTER_KEYS = ["isActive", "bundleStockStatus"];
@@ -51,22 +52,6 @@ const COLUMNS = [
51
52
  },
52
53
  { key: "updatedAt", header: "Updated", className: "w-28" },
53
54
  ];
54
- function BundlesFilterDrawer({ filterOpen, setFilterOpen, activeFilterCount, clearFilters, pendingFilters, setPendingFilters, applyFilters, }) {
55
- if (!filterOpen)
56
- return null;
57
- 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 dark:text-zinc-400 hover:bg-zinc-100 dark:hover:bg-zinc-800 transition-colors", children: _jsx(X, { className: "h-5 w-5" }) })] })] }), _jsxs("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: [
58
- { label: "All", value: "" },
59
- { label: "Active", value: "true" },
60
- { label: "Inactive", value: "false" },
61
- ].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
62
- ? "bg-primary text-white border-primary"
63
- : "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))) })] }), _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: "Stock" }), _jsx("div", { className: "flex flex-wrap gap-2", children: [
64
- { label: "All", value: "" },
65
- { label: "Sold out", value: "out_of_stock" },
66
- ].map((opt) => (_jsx("button", { type: "button", onClick: () => setPendingFilters((p) => ({ ...p, bundleStockStatus: opt.value })), className: `rounded-full px-3 py-1 text-xs font-medium border transition-colors ${(pendingFilters.bundleStockStatus || "") === opt.value
67
- ? "bg-primary text-white border-primary"
68
- : "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})` : ""] }) })] })] }));
69
- }
70
55
  export function AdminBundlesView({ getEditHref, newHref }) {
71
56
  const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
72
57
  const [searchInput, setSearchInput] = useState(table.get("q") || "");
@@ -151,49 +136,45 @@ export function AdminBundlesView({ getEditHref, newHref }) {
151
136
  }
152
137
  }, [refetch, toast]);
153
138
  const bulkActions = [
154
- {
155
- id: "activate",
156
- label: ACTIONS.ADMIN["activate-bundle"].label,
157
- variant: "primary",
158
- onClick: async () => {
159
- await Promise.all(selection.selectedIds.map((id) => fetch(ADMIN_ENDPOINTS.BUNDLE_BY_ID(id), {
160
- method: "PUT",
161
- headers: { "Content-Type": "application/json" },
162
- body: JSON.stringify({ isActive: true }),
163
- })));
164
- selection.clearSelection();
165
- refetch();
166
- },
167
- },
168
- {
169
- id: "deactivate",
170
- label: ACTIONS.ADMIN["deactivate-bundle"].label,
171
- variant: "secondary",
172
- onClick: async () => {
173
- await Promise.all(selection.selectedIds.map((id) => fetch(ADMIN_ENDPOINTS.BUNDLE_BY_ID(id), {
174
- method: "PUT",
175
- headers: { "Content-Type": "application/json" },
176
- body: JSON.stringify({ isActive: false }),
177
- })));
178
- selection.clearSelection();
179
- refetch();
180
- },
181
- },
182
- {
183
- id: "delete",
184
- label: ACTIONS.ADMIN["delete-bundle"].label,
185
- variant: "danger",
186
- onClick: async () => {
187
- await Promise.all(selection.selectedIds.map((id) => fetch(ADMIN_ENDPOINTS.BUNDLE_BY_ID(id), { method: "DELETE" })));
188
- selection.clearSelection();
189
- refetch();
190
- },
191
- },
139
+ buildBulkAction(ACTIONS.ADMIN["activate-bundle"], async () => {
140
+ await Promise.all(selection.selectedIds.map((id) => fetch(ADMIN_ENDPOINTS.BUNDLE_BY_ID(id), {
141
+ method: "PUT",
142
+ headers: { "Content-Type": "application/json" },
143
+ body: JSON.stringify({ isActive: true }),
144
+ })));
145
+ selection.clearSelection();
146
+ refetch();
147
+ }),
148
+ buildBulkAction(ACTIONS.ADMIN["deactivate-bundle"], async () => {
149
+ await Promise.all(selection.selectedIds.map((id) => fetch(ADMIN_ENDPOINTS.BUNDLE_BY_ID(id), {
150
+ method: "PUT",
151
+ headers: { "Content-Type": "application/json" },
152
+ body: JSON.stringify({ isActive: false }),
153
+ })));
154
+ selection.clearSelection();
155
+ refetch();
156
+ }),
157
+ buildBulkAction(ACTIONS.ADMIN["delete-bundle"], async () => {
158
+ await Promise.all(selection.selectedIds.map((id) => fetch(ADMIN_ENDPOINTS.BUNDLE_BY_ID(id), { method: "DELETE" })));
159
+ selection.clearSelection();
160
+ refetch();
161
+ }),
192
162
  ];
193
163
  useBottomActions(selection.selectedCount > 0 ? { bulk: { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: bulkActions } } : {});
194
164
  return (_jsxs("div", { className: "min-h-screen", children: [_jsx(Heading, { level: 1, className: "sr-only", children: "Bundles" }), _jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search bundles by name or slug\u2026", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => {
195
165
  table.set("sort", v);
196
166
  }, onResetAll: resetAll, hasActiveState: hasActiveState, extra: _jsx(Button, { asChild: true, size: "sm", variant: "primary", children: _jsxs("a", { href: newHref, className: "flex items-center gap-1.5", children: [_jsx(Plus, { className: "h-4 w-4" }), BUNDLE_COPY.adminList.newButton] }) }) }), _jsx(BulkActionBar, { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: bulkActions }), 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-error-surface px-4 py-3 text-sm text-error dark:border-red-900/60", children: errorMessage })), _jsx(DataTable, { columns: COLUMNS, rows: rows, isLoading: isLoading, emptyLabel: BUNDLE_COPY.adminList.empty, getRowHref: getEditHref, selectedIds: selection.selectedIdSet, onToggleSelect: selection.toggle, onToggleSelectAll: (next) => next
197
167
  ? selection.setSelectedIds(rows.map((r) => r.id))
198
- : selection.clearSelection(), renderRowActions: (row) => (_jsx(Button, { variant: "ghost", size: "sm", isLoading: rebuildingId === row.id, disabled: rebuildingId === row.id, onClick: () => void handleRebuild(row.id), children: ACTIONS.ADMIN["rebuild-bundle"].label })) })] }), _jsx(BundlesFilterDrawer, { filterOpen: filterOpen, setFilterOpen: setFilterOpen, activeFilterCount: activeFilterCount, clearFilters: clearFilters, pendingFilters: pendingFilters, setPendingFilters: setPendingFilters, applyFilters: applyFilters })] }));
168
+ : selection.clearSelection(), renderRowActions: (row) => (_jsx(Button, { variant: "ghost", size: "sm", isLoading: rebuildingId === row.id, disabled: rebuildingId === row.id, onClick: () => void handleRebuild(row.id), children: ACTIONS.ADMIN["rebuild-bundle"].label })) })] }), _jsxs(ListingFilterDrawer, { open: filterOpen, onClose: () => setFilterOpen(false), onApply: applyFilters, onClear: clearFilters, activeCount: activeFilterCount, 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: [
169
+ { label: "All", value: "" },
170
+ { label: "Active", value: "true" },
171
+ { label: "Inactive", value: "false" },
172
+ ].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
173
+ ? "bg-primary text-white border-primary"
174
+ : "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))) })] }), _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: "Stock" }), _jsx("div", { className: "flex flex-wrap gap-2", children: [
175
+ { label: "All", value: "" },
176
+ { label: "Sold out", value: "out_of_stock" },
177
+ ].map((opt) => (_jsx("button", { type: "button", onClick: () => setPendingFilters((p) => ({ ...p, bundleStockStatus: opt.value })), className: `rounded-full px-3 py-1 text-xs font-medium border transition-colors ${(pendingFilters.bundleStockStatus || "") === opt.value
178
+ ? "bg-primary text-white border-primary"
179
+ : "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))) })] })] })] }));
199
180
  }