@mohasinac/appkit 2.7.57 → 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 (245) hide show
  1. package/dist/_internal/client/features/layout/DashboardLayoutClient.d.ts +7 -1
  2. package/dist/_internal/client/features/layout/DashboardLayoutClient.js +8 -2
  3. package/dist/_internal/server/features/scams/og.js +1 -1
  4. package/dist/_internal/shared/actions/bulk-helpers.d.ts +3 -0
  5. package/dist/_internal/shared/actions/bulk-helpers.js +19 -0
  6. package/dist/_internal/shared/config/schema.d.ts +4 -4
  7. package/dist/client.d.ts +4 -1
  8. package/dist/client.js +3 -1
  9. package/dist/configs/next.js +11 -0
  10. package/dist/configs/tailwind.d.ts +16 -3
  11. package/dist/configs/tailwind.js +18 -5
  12. package/dist/features/account/components/AddressBook.js +1 -1
  13. package/dist/features/account/components/AddressesIndexListing.js +3 -4
  14. package/dist/features/account/components/BuyerAccountGuideView.js +7 -7
  15. package/dist/features/account/components/BuyerAuctionsGuideView.js +7 -7
  16. package/dist/features/account/components/BuyerOrdersGuideView.js +6 -6
  17. package/dist/features/account/components/BuyerShoppingGuideView.js +7 -7
  18. package/dist/features/account/components/UserOffersPanel.js +1 -1
  19. package/dist/features/account/components/UserSidebar.js +14 -10
  20. package/dist/features/account/components/UserSupportView.js +4 -4
  21. package/dist/features/admin/components/AdminAdsView.js +1 -1
  22. package/dist/features/admin/components/AdminAllEventEntriesView.js +4 -10
  23. package/dist/features/admin/components/AdminAnalyticsGuideView.js +3 -3
  24. package/dist/features/admin/components/AdminBidsView.js +3 -9
  25. package/dist/features/admin/components/AdminBlogView.js +4 -9
  26. package/dist/features/admin/components/AdminBrandEditorView.js +14 -17
  27. package/dist/features/admin/components/AdminBrandsView.js +4 -9
  28. package/dist/features/admin/components/AdminBundlesView.js +40 -59
  29. package/dist/features/admin/components/AdminCarouselView.js +8 -21
  30. package/dist/features/admin/components/AdminCartsView.js +3 -4
  31. package/dist/features/admin/components/AdminCatalogGuideView.js +5 -5
  32. package/dist/features/admin/components/AdminCategoriesView.js +4 -9
  33. package/dist/features/admin/components/AdminCategoryEditorView.js +1 -5
  34. package/dist/features/admin/components/AdminContactEditorView.js +2 -2
  35. package/dist/features/admin/components/AdminContactView.js +7 -25
  36. package/dist/features/admin/components/AdminContentGuideView.js +6 -6
  37. package/dist/features/admin/components/AdminCouponEditorView.js +14 -17
  38. package/dist/features/admin/components/AdminCouponsView.js +8 -16
  39. package/dist/features/admin/components/AdminFaqEditorView.js +14 -17
  40. package/dist/features/admin/components/AdminFaqsView.js +18 -69
  41. package/dist/features/admin/components/AdminFeatureEditorView.js +7 -9
  42. package/dist/features/admin/components/AdminFeatureFlagsView.js +1 -1
  43. package/dist/features/admin/components/AdminFeaturesView.js +1 -1
  44. package/dist/features/admin/components/AdminGuideHubView.js +1 -1
  45. package/dist/features/admin/components/AdminHistoryView.js +1 -1
  46. package/dist/features/admin/components/AdminListingScaffold.js +1 -1
  47. package/dist/features/admin/components/AdminNewsletterView.js +5 -15
  48. package/dist/features/admin/components/AdminNotificationsView.js +3 -9
  49. package/dist/features/admin/components/AdminOrdersGuideView.js +5 -5
  50. package/dist/features/admin/components/AdminOrdersView.js +3 -9
  51. package/dist/features/admin/components/AdminPayoutsView.js +3 -9
  52. package/dist/features/admin/components/AdminPrizeDrawsView.js +4 -9
  53. package/dist/features/admin/components/AdminProductEditorView.js +1 -5
  54. package/dist/features/admin/components/AdminProductsView.js +5 -10
  55. package/dist/features/admin/components/AdminReturnRequestsView.js +1 -1
  56. package/dist/features/admin/components/AdminReviewsView.js +3 -9
  57. package/dist/features/admin/components/AdminScammerEditorView.js +3 -3
  58. package/dist/features/admin/components/AdminScammersView.js +6 -12
  59. package/dist/features/admin/components/AdminSectionsView.js +1 -1
  60. package/dist/features/admin/components/AdminSessionsView.js +3 -9
  61. package/dist/features/admin/components/AdminSiteSettingsView.js +1 -1
  62. package/dist/features/admin/components/AdminStoreAddressesView.js +1 -1
  63. package/dist/features/admin/components/AdminStoresGuideView.js +6 -6
  64. package/dist/features/admin/components/AdminStoresView.js +3 -9
  65. package/dist/features/admin/components/AdminSublistingCategoriesView.js +3 -7
  66. package/dist/features/admin/components/AdminSublistingCategoryEditorView.js +17 -20
  67. package/dist/features/admin/components/AdminSupportTicketDetailView.js +4 -4
  68. package/dist/features/admin/components/AdminSupportTicketsView.js +9 -15
  69. package/dist/features/admin/components/AdminTeamGuideView.js +4 -4
  70. package/dist/features/admin/components/AdminTeamView.js +6 -15
  71. package/dist/features/admin/components/AdminTrustGuideView.js +5 -5
  72. package/dist/features/admin/components/AdminUserEditorView.js +1 -1
  73. package/dist/features/admin/components/AdminUsersGuideView.js +5 -5
  74. package/dist/features/admin/components/AdminUsersView.js +3 -9
  75. package/dist/features/admin/components/AdminViewCards.js +1 -1
  76. package/dist/features/admin/components/AdminWishlistsView.js +1 -1
  77. package/dist/features/admin/components/DataTable.js +1 -1
  78. package/dist/features/admin/hooks/useAdminListing.d.ts +58 -0
  79. package/dist/features/admin/hooks/useAdminListing.js +71 -0
  80. package/dist/features/auctions/components/AuctionBidsTable.js +1 -1
  81. package/dist/features/auctions/components/AuctionDetailPageView.js +2 -2
  82. package/dist/features/auctions/components/MarketplaceAuctionCard.js +2 -2
  83. package/dist/features/auctions/components/PlaceBidFormClient.js +1 -1
  84. package/dist/features/auth/components/VerifyEmailView.js +1 -1
  85. package/dist/features/auth/schemas/firestore.d.ts +1 -0
  86. package/dist/features/blog/components/BlogIndexListing.js +3 -6
  87. package/dist/features/blog/components/BlogListView.js +1 -1
  88. package/dist/features/blog/components/BlogPostView.js +1 -1
  89. package/dist/features/cart/components/CartDrawer.js +1 -1
  90. package/dist/features/categories/components/CategoriesIndexListing.js +3 -6
  91. package/dist/features/categories/components/CategoryDetailPageView.js +1 -1
  92. package/dist/features/checkout/components/CheckoutStepper.js +1 -1
  93. package/dist/features/collections/components/CollectionCard.js +1 -1
  94. package/dist/features/copilot/components/AdminCopilotView.js +2 -2
  95. package/dist/features/events/components/AdminEventsView.js +4 -4
  96. package/dist/features/events/components/EventPollWidget.js +2 -2
  97. package/dist/features/events/components/EventStatusBadge.js +4 -4
  98. package/dist/features/events/components/EventsIndexListing.js +3 -4
  99. package/dist/features/events/components/SpinWheelView.js +1 -1
  100. package/dist/features/faq/components/FAQAccordion.js +2 -2
  101. package/dist/features/forms/Input.js +3 -3
  102. package/dist/features/forms/Select.js +2 -2
  103. package/dist/features/forms/Textarea.js +2 -2
  104. package/dist/features/forms/utils.d.ts +1 -1
  105. package/dist/features/forms/utils.js +1 -1
  106. package/dist/features/homepage/components/AnnouncementBar.d.ts +4 -1
  107. package/dist/features/homepage/components/AnnouncementBar.js +51 -4
  108. package/dist/features/homepage/components/CharacterHotspotForm.js +2 -2
  109. package/dist/features/homepage/components/MarketplaceHomepageView.d.ts +3 -1
  110. package/dist/features/homepage/components/MarketplaceHomepageView.js +2 -2
  111. package/dist/features/homepage/components/index.d.ts +1 -1
  112. package/dist/features/homepage/components/index.js +1 -1
  113. package/dist/features/homepage/lib/section-renderer.d.ts +2 -2
  114. package/dist/features/homepage/lib/section-renderer.js +2 -2
  115. package/dist/features/layout/BottomActions.js +2 -2
  116. package/dist/features/layout/ListingLayout.js +3 -3
  117. package/dist/features/layout/TitleBarLayout.js +1 -1
  118. package/dist/features/media/index.d.ts +2 -0
  119. package/dist/features/media/index.js +1 -0
  120. package/dist/features/media/modals/ImageCropModal.js +1 -1
  121. package/dist/features/media/modals/ImageEditor.d.ts +12 -0
  122. package/dist/features/media/modals/ImageEditor.js +73 -0
  123. package/dist/features/media/upload/ImageUpload.d.ts +5 -1
  124. package/dist/features/media/upload/ImageUpload.js +33 -8
  125. package/dist/features/media/upload/MediaUploadField.d.ts +5 -1
  126. package/dist/features/media/upload/MediaUploadField.js +26 -12
  127. package/dist/features/orders/components/OrdersList.js +7 -7
  128. package/dist/features/orders/components/RefundHistoryTable.js +2 -2
  129. package/dist/features/orders/components/RefundRequestView.js +3 -3
  130. package/dist/features/orders/repository/orders.repository.d.ts +0 -1
  131. package/dist/features/orders/repository/orders.repository.js +0 -1
  132. package/dist/features/orders/server.d.ts +1 -1
  133. package/dist/features/orders/server.js +1 -1
  134. package/dist/features/pre-orders/components/PreOrderActionsClient.js +1 -1
  135. package/dist/features/pre-orders/components/PreOrderBadge.js +4 -4
  136. package/dist/features/pre-orders/components/PreOrderDetailPageView.js +1 -1
  137. package/dist/features/pre-orders/components/PreorderCard.js +2 -2
  138. package/dist/features/products/components/CustomSectionsEditor.js +1 -1
  139. package/dist/features/products/components/GroupSettingsPanel.js +64 -48
  140. package/dist/features/products/components/MakeOfferButton.js +2 -2
  141. package/dist/features/products/components/PrizeDrawDetailPageView.js +2 -2
  142. package/dist/features/products/components/PrizeDrawItemsEditor.js +3 -3
  143. package/dist/features/products/components/PrizeRevealModal.js +3 -3
  144. package/dist/features/products/components/ProductDetailPageView.js +4 -4
  145. package/dist/features/products/components/ProductFeaturesSelector.js +1 -1
  146. package/dist/features/products/components/ProductGrid.js +3 -3
  147. package/dist/features/products/repository/products.repository.d.ts +0 -2
  148. package/dist/features/products/repository/products.repository.js +0 -2
  149. package/dist/features/products/server.d.ts +1 -1
  150. package/dist/features/products/server.js +1 -1
  151. package/dist/features/promotions/components/CouponCard.js +7 -7
  152. package/dist/features/promotions/components/CouponsIndexListing.js +3 -2
  153. package/dist/features/promotions/components/PromotionsView.js +1 -1
  154. package/dist/features/reviews/components/ReviewDetailShell.js +2 -2
  155. package/dist/features/reviews/components/ReviewsIndexListing.js +3 -4
  156. package/dist/features/reviews/components/ReviewsList.js +1 -1
  157. package/dist/features/reviews/repository/reviews.repository.d.ts +0 -1
  158. package/dist/features/reviews/repository/reviews.repository.js +0 -1
  159. package/dist/features/reviews/server.d.ts +1 -1
  160. package/dist/features/reviews/server.js +1 -1
  161. package/dist/features/seller/components/SellerAddressesView.js +11 -21
  162. package/dist/features/seller/components/SellerAnalyticsAlertsView.js +2 -2
  163. package/dist/features/seller/components/SellerAuctionsView.js +14 -23
  164. package/dist/features/seller/components/SellerBidsView.js +6 -6
  165. package/dist/features/seller/components/SellerBundlesView.js +13 -21
  166. package/dist/features/seller/components/SellerClassifiedView.js +14 -22
  167. package/dist/features/seller/components/SellerCouponEditorView.js +2 -2
  168. package/dist/features/seller/components/SellerCouponsView.js +14 -20
  169. package/dist/features/seller/components/SellerDigitalCodesView.js +13 -21
  170. package/dist/features/seller/components/SellerFeaturesView.js +1 -1
  171. package/dist/features/seller/components/SellerGroupedListingsView.js +6 -10
  172. package/dist/features/seller/components/SellerLiveView.js +15 -23
  173. package/dist/features/seller/components/SellerOffersPanel.js +2 -2
  174. package/dist/features/seller/components/SellerOffersView.js +4 -5
  175. package/dist/features/seller/components/SellerOrdersView.js +9 -23
  176. package/dist/features/seller/components/SellerPayoutMethodsView.js +13 -25
  177. package/dist/features/seller/components/SellerPayoutsView.js +4 -5
  178. package/dist/features/seller/components/SellerPreOrdersView.js +13 -22
  179. package/dist/features/seller/components/SellerPrizeDrawsView.js +13 -22
  180. package/dist/features/seller/components/SellerProductsCards.js +1 -1
  181. package/dist/features/seller/components/SellerProductsFilterDrawer.d.ts +1 -1
  182. package/dist/features/seller/components/SellerProductsFilterDrawer.js +3 -6
  183. package/dist/features/seller/components/SellerProductsView.js +10 -23
  184. package/dist/features/seller/components/SellerReviewsView.js +1 -1
  185. package/dist/features/seller/components/SellerShippingConfigsView.js +14 -26
  186. package/dist/features/seller/components/SellerStoreCategoriesView.js +16 -35
  187. package/dist/features/seller/components/SellerTemplatesView.js +27 -43
  188. package/dist/features/seller/components/analytics/SellerTopProducts.js +1 -1
  189. package/dist/features/stores/components/StoreFinanceGuideView.js +2 -2
  190. package/dist/features/stores/components/StoreHeader.js +1 -1
  191. package/dist/features/stores/components/StoreListingsGuideView.js +3 -3
  192. package/dist/features/stores/components/StoreOrdersGuideView.js +4 -4
  193. package/dist/features/stores/components/StoreReviewsListing.js +3 -4
  194. package/dist/features/stores/components/StoreSettingsGuideView.js +2 -2
  195. package/dist/features/stores/components/StoresIndexListing.js +3 -4
  196. package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +4 -4
  197. package/dist/features/wishlist/components/WishlistPage.js +1 -1
  198. package/dist/index.d.ts +4 -6
  199. package/dist/index.js +6 -12
  200. package/dist/react/contexts/SessionContext.d.ts +2 -0
  201. package/dist/react/contexts/SessionContext.js +2 -0
  202. package/dist/react/hooks/useEntityDelete.d.ts +15 -0
  203. package/dist/react/hooks/useEntityDelete.js +39 -0
  204. package/dist/react/index.d.ts +2 -0
  205. package/dist/react/index.js +2 -0
  206. package/dist/repositories/index.d.ts +3 -3
  207. package/dist/repositories/index.js +3 -3
  208. package/dist/styles.css +286 -3
  209. package/dist/tailwind-utilities.css +1 -1
  210. package/dist/tokens/motion.d.ts +162 -0
  211. package/dist/tokens/motion.js +89 -0
  212. package/dist/ui/DataTable.js +3 -2
  213. package/dist/ui/components/Accordion.js +2 -1
  214. package/dist/ui/components/Card.d.ts +5 -2
  215. package/dist/ui/components/Card.js +29 -11
  216. package/dist/ui/components/Div.d.ts +2 -16
  217. package/dist/ui/components/Div.js +2 -16
  218. package/dist/ui/components/Layout.d.ts +9 -19
  219. package/dist/ui/components/Layout.js +9 -7
  220. package/dist/ui/components/ListingFilterDrawer.d.ts +10 -0
  221. package/dist/ui/components/ListingFilterDrawer.js +8 -0
  222. package/dist/ui/components/Modal.js +7 -4
  223. package/dist/ui/components/Motion.d.ts +75 -0
  224. package/dist/ui/components/Motion.js +122 -0
  225. package/dist/ui/components/Semantic.d.ts +81 -14
  226. package/dist/ui/components/Semantic.js +75 -10
  227. package/dist/ui/components/Semantic.style.css +261 -2
  228. package/dist/ui/components/SideDrawer.d.ts +1 -1
  229. package/dist/ui/components/SideDrawer.js +8 -6
  230. package/dist/ui/components/SideModal.d.ts +1 -1
  231. package/dist/ui/components/SideModal.js +11 -9
  232. package/dist/ui/components/StackedViewShell.js +6 -1
  233. package/dist/ui/components/Toast.js +5 -2
  234. package/dist/ui/components/Typography.d.ts +44 -3
  235. package/dist/ui/components/Typography.js +15 -30
  236. package/dist/ui/components/Typography.style.css +24 -0
  237. package/dist/ui/components/VacationBanner.js +2 -1
  238. package/dist/ui/components/surface-tokens.d.ts +70 -0
  239. package/dist/ui/components/surface-tokens.js +68 -0
  240. package/dist/ui/forms/FormShell.d.ts +6 -1
  241. package/dist/ui/forms/FormShell.js +32 -4
  242. package/dist/ui/index.d.ts +11 -2
  243. package/dist/ui/index.js +5 -2
  244. package/dist/validation/schemas.d.ts +28 -28
  245. package/package.json +3 -1
@@ -1,12 +1,12 @@
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 { useBulkSelection } from "../../../react/hooks/useBulkSelection";
7
7
  import { AdminViewCards } from "../../admin/components/AdminViewCards";
8
8
  import { usePanelUrlSync } from "../../../react/hooks/use-panel-url-sync";
9
- import { BulkActionBar, Button, FilterChipGroup, ListingToolbar, Pagination, ListingViewShell, SideDrawer } from "../../../ui";
9
+ import { BulkActionBar, Button, FilterChipGroup, ListingFilterDrawer, ListingToolbar, Pagination, ListingViewShell, SideDrawer } from "../../../ui";
10
10
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
11
11
  import { ADMIN_EVENT_STATUS_TABS } from "../../admin/constants/filter-tabs";
12
12
  import { toRecordArray, toRelativeDate, toStringValue, useAdminListingData, } from "../../admin/hooks/useAdminListingData";
@@ -108,5 +108,5 @@ export function AdminEventsView({ children, getRowHref, ...props }) {
108
108
  ] } } : {});
109
109
  return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search events by title or type", 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 Event"] }) }), _jsx(BulkActionBar, { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: [
110
110
  { id: "delete", label: "Delete Selected", variant: "secondary", onClick: () => { selection.clearSelection(); } },
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 events found", onRowClick: (row) => openEditPanel(row.id) })) : (_jsx(AdminViewCards, { rows: rows, view: view, isLoading: isLoading, emptyLabel: "No events found", onRowClick: undefined, selectedIdSet: selection.selectedIdSet, onToggleSelect: selection.toggle }))] }), 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: "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: "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})` : ""] }) })] })] })), _jsx(SideDrawer, { isOpen: isCreateOpen || isEditOpen, onClose: closePanel, title: isCreateOpen ? "Add Event" : "Edit Event", mode: isCreateOpen ? "create" : "edit", children: (isCreateOpen || isEditOpen) && (_jsx(AdminEventEditorView, { eventId: editId ?? undefined, onSaved: 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 events found", onRowClick: (row) => openEditPanel(row.id) })) : (_jsx(AdminViewCards, { rows: rows, view: view, isLoading: isLoading, emptyLabel: "No events found", onRowClick: undefined, 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: "Type", tabs: TYPE_OPTIONS, value: pendingFilters.type ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, type: id })) })] }), _jsx(SideDrawer, { isOpen: isCreateOpen || isEditOpen, onClose: closePanel, title: isCreateOpen ? "Add Event" : "Edit Event", mode: isCreateOpen ? "create" : "edit", children: (isCreateOpen || isEditOpen) && (_jsx(AdminEventEditorView, { eventId: editId ?? undefined, onSaved: closePanel, embedded: true })) })] }));
112
112
  }
@@ -62,7 +62,7 @@ export function EventPollWidget({ eventId, pollConfig, eventStatus, totalEntries
62
62
  return (_jsxs("div", { className: `rounded-xl border border-zinc-200 dark:border-zinc-700 px-6 py-8 text-center space-y-3 ${className}`, "data-section": "eventpollwidget-div-4", children: [_jsx(Text, { className: "font-semibold text-zinc-900 dark:text-zinc-100", children: "Login to vote" }), _jsx(Text, { className: "text-sm text-zinc-500 dark:text-zinc-400", children: "You need an account to participate in this poll." }), _jsx("a", { href: String(ROUTES.AUTH.LOGIN), className: "inline-block rounded-xl bg-primary px-6 py-2.5 text-sm font-semibold text-white hover:bg-primary-600", children: "Log In" })] }));
63
63
  }
64
64
  if (isSubmitted) {
65
- return (_jsxs("div", { className: `rounded-xl border border-green-200 dark:border-green-800 bg-green-50 dark:bg-green-900/20 px-6 py-8 text-center space-y-2 ${className}`, "data-section": "eventpollwidget-div-5", children: [_jsx(Text, { className: "font-semibold text-green-700 dark:text-green-300", children: "Vote recorded!" }), _jsx(Text, { className: "text-sm text-zinc-500 dark:text-zinc-400", children: "Thanks for participating." })] }));
65
+ return (_jsxs("div", { className: `rounded-xl border border-green-200 dark:border-green-800 bg-success-surface px-6 py-8 text-center space-y-2 ${className}`, "data-section": "eventpollwidget-div-5", children: [_jsx(Text, { className: "font-semibold text-success", children: "Vote recorded!" }), _jsx(Text, { className: "text-sm text-zinc-500 dark:text-zinc-400", children: "Thanks for participating." })] }));
66
66
  }
67
- return (_jsxs("div", { className: `space-y-4 ${className}`, "data-section": "eventpollwidget-div-6", children: [_jsx(LoginRequiredModal, { isOpen: showLoginModal, onClose: () => setShowLoginModal(false), message: "You need to be signed in to vote in this poll. Please log in or create an account to continue." }), _jsx(Text, { className: "text-sm font-medium text-zinc-700 dark:text-zinc-200", children: isMulti ? "Select all that apply:" : "Choose one:" }), _jsx("div", { className: "space-y-2", "data-section": "eventpollwidget-div-7", children: pollConfig.options.map((opt) => (_jsxs("label", { className: "flex items-center gap-3 cursor-pointer rounded-lg border border-zinc-200 dark:border-zinc-700 px-4 py-3 hover:bg-zinc-50 dark:hover:bg-zinc-800 transition-colors", children: [_jsx("input", { type: isMulti ? "checkbox" : "radio", name: `poll-${eventId}`, value: opt.id, checked: selectedVotes.includes(opt.id), onChange: () => toggleVote(opt.id), className: "accent-primary" }), _jsx("span", { className: "text-sm text-zinc-700 dark:text-zinc-300", children: opt.label })] }, opt.id))) }), pollConfig.allowComment && (_jsx("textarea", { value: comment, onChange: (e) => setComment(e.target.value), placeholder: "Add a comment (optional)", rows: 3, className: "w-full rounded-lg border border-zinc-200 dark:border-zinc-700 bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-zinc-800 dark:text-zinc-200 placeholder:text-zinc-400 focus:outline-none focus:ring-2 focus:ring-primary" })), error && _jsx(Text, { className: "text-sm text-red-500", children: error }), _jsx("button", { type: "button", onClick: handleSubmit, disabled: isLoading || selectedVotes.length === 0, className: "w-full rounded-xl bg-primary px-6 py-3 text-sm font-semibold text-white hover:bg-primary-600 disabled:opacity-60", children: isLoading ? "Submitting…" : "Submit Vote" })] }));
67
+ return (_jsxs("div", { className: `space-y-4 ${className}`, "data-section": "eventpollwidget-div-6", children: [_jsx(LoginRequiredModal, { isOpen: showLoginModal, onClose: () => setShowLoginModal(false), message: "You need to be signed in to vote in this poll. Please log in or create an account to continue." }), _jsx(Text, { className: "text-sm font-medium text-zinc-700 dark:text-zinc-200", children: isMulti ? "Select all that apply:" : "Choose one:" }), _jsx("div", { className: "space-y-2", "data-section": "eventpollwidget-div-7", children: pollConfig.options.map((opt) => (_jsxs("label", { className: "flex items-center gap-3 cursor-pointer rounded-lg border border-zinc-200 dark:border-zinc-700 px-4 py-3 hover:bg-zinc-50 dark:hover:bg-zinc-800 transition-colors", children: [_jsx("input", { type: isMulti ? "checkbox" : "radio", name: `poll-${eventId}`, value: opt.id, checked: selectedVotes.includes(opt.id), onChange: () => toggleVote(opt.id), className: "accent-primary" }), _jsx("span", { className: "text-sm text-zinc-700 dark:text-zinc-300", children: opt.label })] }, opt.id))) }), pollConfig.allowComment && (_jsx("textarea", { value: comment, onChange: (e) => setComment(e.target.value), placeholder: "Add a comment (optional)", rows: 3, className: "w-full rounded-lg border border-zinc-200 dark:border-zinc-700 bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-zinc-800 dark:text-zinc-200 placeholder:text-zinc-400 focus:outline-none focus:ring-2 focus:ring-primary" })), error && _jsx(Text, { className: "text-sm text-error", children: error }), _jsx("button", { type: "button", onClick: handleSubmit, disabled: isLoading || selectedVotes.length === 0, className: "w-full rounded-xl bg-primary px-6 py-3 text-sm font-semibold text-white hover:bg-primary-600 disabled:opacity-60", children: isLoading ? "Submitting…" : "Submit Vote" })] }));
68
68
  }
@@ -2,10 +2,10 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { Span } from "../../../ui";
3
3
  const STATUS_MAP = {
4
4
  draft: { label: "Draft", color: "bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300" },
5
- active: { label: "Active", color: "bg-green-100 text-green-700 dark:bg-green-900/40 dark:text-green-300" },
6
- paused: { label: "Paused", color: "bg-yellow-100 text-yellow-700 dark:bg-yellow-900/40 dark:text-yellow-300" },
7
- ended: { label: "Ended", color: "bg-red-100 text-red-700 dark:bg-red-900/40 dark:text-red-300" },
8
- cancelled: { label: "Cancelled", color: "bg-red-100 text-red-700 dark:bg-red-900/40 dark:text-red-300" },
5
+ active: { label: "Active", color: "bg-success-surface text-success" },
6
+ paused: { label: "Paused", color: "bg-warning-surface text-warning" },
7
+ ended: { label: "Ended", color: "bg-error-surface text-error" },
8
+ cancelled: { label: "Cancelled", color: "bg-error-surface text-error" },
9
9
  };
10
10
  export function EventStatusBadge({ status, className = "", }) {
11
11
  const { label, color } = STATUS_MAP[status] ?? STATUS_MAP.draft;
@@ -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 { useEvents } from "../hooks/useEvents";
7
- import { ListingToolbar, Pagination, Text } from "../../../ui";
6
+ import { ListingFilterDrawer, ListingToolbar, Pagination, Text } from "../../../ui";
8
7
  import { EventCard } from "./EventCard";
9
8
  import { EventFilters, EVENT_PUBLIC_SORT_OPTIONS } from "./EventFilters";
10
9
  import { EVENT_FIELDS } from "../../../constants/field-names";
@@ -116,5 +115,5 @@ export function EventsIndexListing({ initialData }) {
116
115
  const commitSearch = useCallback(() => {
117
116
  table.set(TABLE_KEYS.QUERY, searchInput.trim());
118
117
  }, [searchInput, table]);
119
- return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search events...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get(TABLE_KEYS.SORT) || sortBy(EVENT_FIELDS.STARTS_AT, "ASC"), sortOptions: EVENT_PUBLIC_SORT_OPTIONS, onSortChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, view: view, onViewChange: handleViewToggle, 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) }) })), _jsx("div", { className: "py-6", children: isLoading ? (_jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6", children: Array.from({ length: 6 }).map((_, i) => (_jsxs("div", { className: "rounded-xl border border-zinc-100 dark:border-slate-700 overflow-hidden animate-pulse", children: [_jsx("div", { className: "aspect-video bg-zinc-200 dark:bg-slate-700" }), _jsxs("div", { className: "p-4 space-y-2", children: [_jsx("div", { className: "h-4 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-full" }), _jsx("div", { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-2/3" }), _jsx("div", { className: "h-8 bg-zinc-200 dark:bg-slate-700 rounded mt-2" })] })] }, i))) })) : events.length === 0 ? (_jsx(Text, { className: "py-12 text-center text-sm text-zinc-500 dark:text-zinc-400", children: "No events found." })) : view === "list" ? (_jsx("div", { className: "flex flex-col divide-y divide-zinc-100 dark:divide-zinc-800 rounded-xl border border-zinc-100 dark:border-zinc-800", children: events.map((event) => (_jsx(EventCard, { event: event }, event.id))) })) : (_jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6", children: events.map((event) => (_jsx(EventCard, { event: event }, event.id))) })) }), 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(EventFilters, { table: pendingTable, variant: "public" }) }), _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})` : ""] }) })] })] }))] }));
118
+ return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search events...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get(TABLE_KEYS.SORT) || sortBy(EVENT_FIELDS.STARTS_AT, "ASC"), sortOptions: EVENT_PUBLIC_SORT_OPTIONS, onSortChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, view: view, onViewChange: handleViewToggle, 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) }) })), _jsx("div", { className: "py-6", children: isLoading ? (_jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6", children: Array.from({ length: 6 }).map((_, i) => (_jsxs("div", { className: "rounded-xl border border-zinc-100 dark:border-slate-700 overflow-hidden animate-pulse", children: [_jsx("div", { className: "aspect-video bg-zinc-200 dark:bg-slate-700" }), _jsxs("div", { className: "p-4 space-y-2", children: [_jsx("div", { className: "h-4 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-full" }), _jsx("div", { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-2/3" }), _jsx("div", { className: "h-8 bg-zinc-200 dark:bg-slate-700 rounded mt-2" })] })] }, i))) })) : events.length === 0 ? (_jsx(Text, { className: "py-12 text-center text-sm text-zinc-500 dark:text-zinc-400", children: "No events found." })) : view === "list" ? (_jsx("div", { className: "flex flex-col divide-y divide-zinc-100 dark:divide-zinc-800 rounded-xl border border-zinc-100 dark:border-zinc-800", children: events.map((event) => (_jsx(EventCard, { event: event }, event.id))) })) : (_jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6", children: events.map((event) => (_jsx(EventCard, { event: event }, event.id))) })) }), _jsx(ListingFilterDrawer, { open: filterOpen, onClose: () => setFilterOpen(false), onApply: applyFilters, onClear: clearFilters, activeCount: activeFilterCount, children: _jsx(EventFilters, { table: pendingTable, variant: "public" }) })] }));
120
119
  }
@@ -69,5 +69,5 @@ export function SpinWheelView({ eventId, prizes, alreadyUsed, initialPrizeId, in
69
69
  0% { transform: rotate(0deg); }
70
70
  100% { transform: rotate(2160deg); }
71
71
  }
72
- ` })] }), wonPrize ? (_jsxs(Div, { className: "rounded-xl border border-emerald-300 bg-emerald-50 dark:border-emerald-700 dark:bg-emerald-900/30 p-4 text-center", children: [_jsx(Text, { className: "text-sm uppercase tracking-wide text-emerald-700 dark:text-emerald-300", children: l.wonHeadline }), _jsx(Text, { className: "mt-1 text-lg font-bold text-emerald-900 dark:text-emerald-100", children: wonPrize.label }), resultCoupon ? (_jsxs(_Fragment, { children: [_jsxs(Text, { className: "mt-2 text-sm text-emerald-800 dark:text-emerald-200", children: [l.couponHint, " ", _jsx(Span, { className: "font-mono font-semibold", children: resultCoupon })] }), _jsx(Div, { className: "mt-3 flex justify-center", children: _jsx(ClaimCouponButton, { couponCode: resultCoupon, size: "sm", source: "spin" }) })] })) : null] })) : null, error ? (_jsx(Text, { className: "text-sm text-rose-600 dark:text-rose-400 text-center", children: error })) : null, _jsx(Div, { className: "flex justify-center", children: _jsx(Button, { variant: "primary", size: "lg", onClick: handleSpin, disabled: disabled, children: spinning ? l.spinningButton : l.spinButton }) }), alreadyUsed ? (_jsx(Text, { className: "text-center text-sm text-zinc-500 dark:text-zinc-400", children: l.alreadySpun })) : !inWindow ? (_jsx(Text, { className: "text-center text-sm text-zinc-500 dark:text-zinc-400", children: l.outsideWindow })) : null] }));
72
+ ` })] }), wonPrize ? (_jsxs(Div, { className: "rounded-xl border border-emerald-300 bg-success-surface dark:border-emerald-700 p-4 text-center", children: [_jsx(Text, { className: "text-sm uppercase tracking-wide text-success", children: l.wonHeadline }), _jsx(Text, { className: "mt-1 text-lg font-bold text-emerald-900 dark:text-emerald-100", children: wonPrize.label }), resultCoupon ? (_jsxs(_Fragment, { children: [_jsxs(Text, { className: "mt-2 text-sm text-emerald-800 dark:text-emerald-200", children: [l.couponHint, " ", _jsx(Span, { className: "font-mono font-semibold", children: resultCoupon })] }), _jsx(Div, { className: "mt-3 flex justify-center", children: _jsx(ClaimCouponButton, { couponCode: resultCoupon, size: "sm", source: "spin" }) })] })) : null] })) : null, error ? (_jsx(Text, { className: "text-sm text-rose-600 dark:text-rose-400 text-center", children: error })) : null, _jsx(Div, { className: "flex justify-center", children: _jsx(Button, { variant: "primary", size: "lg", onClick: handleSpin, disabled: disabled, children: spinning ? l.spinningButton : l.spinButton }) }), alreadyUsed ? (_jsx(Text, { className: "text-center text-sm text-zinc-500 dark:text-zinc-400", children: l.alreadySpun })) : !inWindow ? (_jsx(Text, { className: "text-center text-sm text-zinc-500 dark:text-zinc-400", children: l.outsideWindow })) : null] }));
73
73
  }
@@ -8,9 +8,9 @@ function FAQAccordionItem({ faq, isOpen, onToggle, renderExpandedFooter, }) {
8
8
  export function FAQAccordion({ faqs, className = "", renderExpandedFooter, labels, }) {
9
9
  const [openId, setOpenId] = useState(null);
10
10
  if (faqs.length === 0 && labels?.noResults) {
11
- return (_jsx(Div, { className: "rounded-xl border border-neutral-200 dark:border-slate-700 bg-white dark:bg-slate-900 p-8 text-center", children: _jsx(Text, { className: "text-neutral-600 dark:text-zinc-300", children: labels.noResults }) }));
11
+ return (_jsx(Div, { surface: "card", padding: "xl", className: "text-center", children: _jsx(Text, { className: "text-neutral-600 dark:text-zinc-300", children: labels.noResults }) }));
12
12
  }
13
- return (_jsx(Div, { className: `divide-y divide-neutral-100 dark:divide-slate-700 rounded-xl border border-neutral-200 dark:border-slate-700 bg-white dark:bg-slate-900 px-5 ${className}`, children: faqs.map((faq) => (_jsx(FAQAccordionItem, { faq: faq, isOpen: openId === faq.id, onToggle: () => setOpenId(openId === faq.id ? null : faq.id), renderExpandedFooter: renderExpandedFooter }, faq.id))) }));
13
+ return (_jsx(Div, { surface: "card", className: `divide-y divide-neutral-100 dark:divide-slate-700 px-5 ${className}`, children: faqs.map((faq) => (_jsx(FAQAccordionItem, { faq: faq, isOpen: openId === faq.id, onToggle: () => setOpenId(openId === faq.id ? null : faq.id), renderExpandedFooter: renderExpandedFooter }, faq.id))) }));
14
14
  }
15
15
  export function FAQCategoryTabs({ categories, active, onSelect, labels = {}, }) {
16
16
  return (_jsxs(Row, { wrap: true, gap: "sm", className: "scrollbar-none", children: [_jsx(Button, { onClick: () => onSelect(null), className: `rounded-full px-4 py-1.5 text-sm font-medium transition ${!active ? "bg-neutral-900 text-white" : "bg-neutral-100 dark:bg-slate-800 text-neutral-600 dark:text-zinc-300 hover:bg-neutral-200 dark:hover:bg-slate-700"}`, children: labels.all ?? "All" }), categories.map((cat) => (_jsx(Button, { onClick: () => onSelect(cat), className: `whitespace-nowrap rounded-full px-4 py-1.5 text-sm font-medium capitalize transition ${active === cat ? "bg-neutral-900 text-white" : "bg-neutral-100 dark:bg-slate-800 text-neutral-600 dark:text-zinc-300 hover:bg-neutral-200 dark:hover:bg-slate-700"}`, children: labels[cat] ?? cat.replace(/_/g, " ") }, cat)))] }));
@@ -10,14 +10,14 @@ export const Input = React.forwardRef(function Input({ label, error, helperText,
10
10
  : disabled
11
11
  ? INPUT_DISABLED
12
12
  : "";
13
- return (_jsxs(Div, { className: "w-full", children: [label && (_jsxs("label", { className: LABEL_BASE, children: [label, required && (_jsx(Span, { className: "text-red-500 ml-1", "aria-hidden": "true", children: "*" }))] })), _jsxs(Div, { className: "relative group", children: [icon && (_jsx(Div, { className: cn("absolute left-3 top-1/2 -translate-y-1/2 transition-colors duration-150", error
14
- ? "text-red-600 dark:text-red-400"
13
+ return (_jsxs(Div, { className: "w-full", children: [label && (_jsxs("label", { className: LABEL_BASE, children: [label, required && (_jsx(Span, { className: "text-error ml-1", "aria-hidden": "true", children: "*" }))] })), _jsxs(Div, { className: "relative group", children: [icon && (_jsx(Div, { className: cn("absolute left-3 top-1/2 -translate-y-1/2 transition-colors duration-150", error
14
+ ? "text-error"
15
15
  : "text-zinc-400 dark:text-zinc-400", "group-focus-within:text-primary-500 dark:group-focus-within:text-secondary-400"), children: icon })), _jsx("input", { ref: ref, className: cn("w-full", INPUT_BASE, icon ? INPUT_WITH_ICON : "", rightIcon ? "pr-10" : "", stateClasses, className), disabled: disabled, "aria-invalid": error ? "true" : undefined, "aria-describedby": error
16
16
  ? `${props.id}-error`
17
17
  : helperText
18
18
  ? `${props.id}-helper`
19
19
  : undefined, ...props }), rightIcon && (_jsx(Div, { className: cn("absolute right-3 top-1/2 -translate-y-1/2 transition-colors duration-150", error
20
- ? "text-red-600 dark:text-red-400"
20
+ ? "text-error"
21
21
  : "text-zinc-400 dark:text-zinc-400"), children: rightIcon }))] }), error && (_jsx(Text, { id: `${props.id}-error`, className: ERROR_BASE, role: "alert", children: error })), !error && helperText && (_jsx(Text, { id: `${props.id}-helper`, className: HELPER_BASE, children: helperText }))] }));
22
22
  });
23
23
  Input.displayName = "Input";
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Div, Span, Text } from "../../ui";
3
3
  import { cn, INPUT_BASE, INPUT_ERROR, LABEL_BASE, HELPER_BASE, ERROR_BASE, } from "./utils";
4
4
  export function Select({ label, error, helperText, options, placeholder, className = "", required, ...props }) {
5
- return (_jsxs(Div, { className: "w-full", children: [label && (_jsxs("label", { className: LABEL_BASE, children: [label, required && (_jsx(Span, { className: "text-red-500 ml-1", "aria-hidden": "true", children: "*" }))] })), _jsxs(Div, { className: "relative group", children: [_jsxs("select", { className: cn(INPUT_BASE, "pr-10 appearance-none cursor-pointer", error ? INPUT_ERROR : "", className), "aria-invalid": error ? "true" : undefined, ...props, children: [placeholder && (_jsx("option", { value: "", disabled: true, children: placeholder })), options.map((option) => (_jsx("option", { value: option.value, children: option.label }, option.value)))] }), _jsx(Div, { className: "absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none transition-colors duration-150", children: _jsx("svg", { className: cn("w-5 h-5 transition-transform duration-200", error
6
- ? "text-red-500 dark:text-red-400"
5
+ return (_jsxs(Div, { className: "w-full", children: [label && (_jsxs("label", { className: LABEL_BASE, children: [label, required && (_jsx(Span, { className: "text-error ml-1", "aria-hidden": "true", children: "*" }))] })), _jsxs(Div, { className: "relative group", children: [_jsxs("select", { className: cn(INPUT_BASE, "pr-10 appearance-none cursor-pointer", error ? INPUT_ERROR : "", className), "aria-invalid": error ? "true" : undefined, ...props, children: [placeholder && (_jsx("option", { value: "", disabled: true, children: placeholder })), options.map((option) => (_jsx("option", { value: option.value, children: option.label }, option.value)))] }), _jsx(Div, { className: "absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none transition-colors duration-150", children: _jsx("svg", { className: cn("w-5 h-5 transition-transform duration-200", error
6
+ ? "text-error"
7
7
  : "text-zinc-400 dark:text-zinc-400"), viewBox: "0 0 20 20", fill: "currentColor", "aria-hidden": "true", children: _jsx("path", { fillRule: "evenodd", d: "M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z", clipRule: "evenodd" }) }) })] }), error && (_jsx(Text, { className: ERROR_BASE, role: "alert", children: error })), !error && helperText && _jsx(Text, { className: HELPER_BASE, children: helperText })] }));
8
8
  }
@@ -8,8 +8,8 @@ export const Textarea = React.forwardRef(function Textarea({ label, error, helpe
8
8
  : typeof defaultValue === "string"
9
9
  ? defaultValue.length
10
10
  : 0;
11
- return (_jsxs(Div, { className: "w-full", children: [label && (_jsxs("label", { className: LABEL_BASE, children: [label, required && (_jsx(Span, { className: "text-red-500 ml-1", "aria-hidden": "true", children: "*" }))] })), _jsx("textarea", { ref: ref, className: cn(INPUT_BASE, "resize-y min-h-[80px]", error ? INPUT_ERROR : "", className), "aria-invalid": error ? "true" : undefined, maxLength: maxLength, value: value, defaultValue: defaultValue, ...props }), _jsxs(Div, { className: "flex items-center justify-between mt-1.5", children: [_jsxs(Div, { className: "flex-1", children: [error && (_jsx(Text, { className: ERROR_BASE, role: "alert", children: error })), !error && helperText && (_jsx(Text, { className: HELPER_BASE, children: helperText }))] }), showCharCount && maxLength && (_jsxs("p", { className: cn("text-xs tabular-nums ml-2 flex-shrink-0", charCount >= maxLength
12
- ? "text-red-500 dark:text-red-400"
11
+ return (_jsxs(Div, { className: "w-full", children: [label && (_jsxs("label", { className: LABEL_BASE, children: [label, required && (_jsx(Span, { className: "text-error ml-1", "aria-hidden": "true", children: "*" }))] })), _jsx("textarea", { ref: ref, className: cn(INPUT_BASE, "resize-y min-h-[80px]", error ? INPUT_ERROR : "", className), "aria-invalid": error ? "true" : undefined, maxLength: maxLength, value: value, defaultValue: defaultValue, ...props }), _jsxs(Div, { className: "flex items-center justify-between mt-1.5", children: [_jsxs(Div, { className: "flex-1", children: [error && (_jsx(Text, { className: ERROR_BASE, role: "alert", children: error })), !error && helperText && (_jsx(Text, { className: HELPER_BASE, children: helperText }))] }), showCharCount && maxLength && (_jsxs("p", { className: cn("text-xs tabular-nums ml-2 flex-shrink-0", charCount >= maxLength
12
+ ? "text-error"
13
13
  : "text-zinc-400 dark:text-zinc-400"), children: [charCount, " / ", maxLength] }))] })] }));
14
14
  });
15
15
  Textarea.displayName = "Textarea";
@@ -7,4 +7,4 @@ export declare const INPUT_DISABLED = "bg-zinc-50 dark:bg-slate-800/30 text-zinc
7
7
  export declare const INPUT_WITH_ICON = "pl-10";
8
8
  export declare const LABEL_BASE = "block text-sm font-medium text-zinc-700 dark:text-zinc-300 mb-1.5";
9
9
  export declare const HELPER_BASE = "text-xs text-zinc-500 dark:text-zinc-400 mt-1";
10
- export declare const ERROR_BASE = "text-xs text-red-600 dark:text-red-400 mt-1";
10
+ export declare const ERROR_BASE = "text-xs text-error mt-1";
@@ -10,4 +10,4 @@ export const INPUT_DISABLED = "bg-zinc-50 dark:bg-slate-800/30 text-zinc-400 dar
10
10
  export const INPUT_WITH_ICON = "pl-10";
11
11
  export const LABEL_BASE = "block text-sm font-medium text-zinc-700 dark:text-zinc-300 mb-1.5";
12
12
  export const HELPER_BASE = "text-xs text-zinc-500 dark:text-zinc-400 mt-1";
13
- export const ERROR_BASE = "text-xs text-red-600 dark:text-red-400 mt-1";
13
+ export const ERROR_BASE = "text-xs text-error mt-1";
@@ -1,5 +1,8 @@
1
+ export declare function hashBannerMessage(msg: string): string;
1
2
  export interface AnnouncementBarProps {
2
3
  message: string;
3
4
  className?: string;
5
+ /** Called when the user dismisses — consumer wires this to a server action to persist. */
6
+ onDismiss?: (hash: string) => void;
4
7
  }
5
- export declare function AnnouncementBar({ message, className }: AnnouncementBarProps): import("react/jsx-runtime").JSX.Element | null;
8
+ export declare function AnnouncementBar({ message, className, onDismiss }: AnnouncementBarProps): import("react/jsx-runtime").JSX.Element | null;
@@ -1,10 +1,57 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useState } from "react";
3
+ import { useState, useEffect } from "react";
4
4
  import { X } from "lucide-react";
5
- export function AnnouncementBar({ message, className = "" }) {
6
- const [dismissed, setDismissed] = useState(false);
5
+ import { useAuth } from "../../../react/contexts/SessionContext";
6
+ const STORAGE_KEY = "letitrip:announcement-dismissed";
7
+ const ONE_WEEK_MS = 7 * 24 * 60 * 60 * 1000;
8
+ export function hashBannerMessage(msg) {
9
+ let h = 0;
10
+ for (let i = 0; i < msg.length; i++) {
11
+ h = (Math.imul(31, h) + msg.charCodeAt(i)) | 0;
12
+ }
13
+ return h.toString(36);
14
+ }
15
+ function isLocallyDismissed(message) {
16
+ try {
17
+ const raw = localStorage.getItem(STORAGE_KEY);
18
+ if (!raw)
19
+ return false;
20
+ const { hash, at } = JSON.parse(raw);
21
+ if (hash !== hashBannerMessage(message))
22
+ return false;
23
+ return Date.now() - at < ONE_WEEK_MS;
24
+ }
25
+ catch {
26
+ return false;
27
+ }
28
+ }
29
+ function saveLocalDismissal(message) {
30
+ try {
31
+ localStorage.setItem(STORAGE_KEY, JSON.stringify({ hash: hashBannerMessage(message), at: Date.now() }));
32
+ }
33
+ catch { /* quota exceeded — ignore */ }
34
+ }
35
+ export function AnnouncementBar({ message, className = "", onDismiss }) {
36
+ const { user } = useAuth();
37
+ const bannerHash = hashBannerMessage(message);
38
+ const userAlreadyDismissed = !!user?.dismissedBannerHash && user.dismissedBannerHash === bannerHash;
39
+ const [dismissed, setDismissed] = useState(userAlreadyDismissed);
40
+ const [mounted, setMounted] = useState(userAlreadyDismissed);
41
+ useEffect(() => {
42
+ if (userAlreadyDismissed) {
43
+ setDismissed(true);
44
+ }
45
+ else if (isLocallyDismissed(message)) {
46
+ setDismissed(true);
47
+ }
48
+ setMounted(true);
49
+ }, [message, userAlreadyDismissed]);
7
50
  if (dismissed)
8
51
  return null;
9
- return (_jsx("div", { className: `relative left-1/2 right-1/2 w-screen -translate-x-1/2 bg-primary-700 ${className}`, role: "banner", "data-section": "announcementbar-div-310", children: _jsx("div", { className: "container mx-auto max-w-[1920px] px-4 sm:px-6 lg:px-8", "data-section": "announcementbar-div-311", children: _jsxs("div", { className: "relative flex items-center justify-center py-2 text-center text-sm font-medium text-white", "data-section": "announcementbar-div-312", children: [_jsx("span", { children: message }), _jsx("button", { type: "button", onClick: () => setDismissed(true), className: "absolute right-0 top-1/2 -translate-y-1/2 rounded p-0.5 hover:bg-white/20 focus:outline-none focus:ring-2 focus:ring-white", "aria-label": "Dismiss announcement", children: _jsx(X, { className: "h-4 w-4" }) })] }) }) }));
52
+ return (_jsx("div", { className: `relative left-1/2 right-1/2 w-screen -translate-x-1/2 bg-primary-700 transition-opacity duration-200 ${mounted ? "opacity-100" : "opacity-0"} ${className}`, role: "banner", "data-section": "announcementbar-div-310", children: _jsx("div", { className: "container mx-auto max-w-[1920px] px-4 sm:px-6 lg:px-8", "data-section": "announcementbar-div-311", children: _jsxs("div", { className: "relative flex items-center justify-center py-2 text-center text-sm font-medium text-white", "data-section": "announcementbar-div-312", children: [_jsx("span", { children: message }), _jsx("button", { type: "button", onClick: () => {
53
+ saveLocalDismissal(message);
54
+ onDismiss?.(bannerHash);
55
+ setDismissed(true);
56
+ }, className: "absolute right-0 top-1/2 -translate-y-1/2 rounded p-0.5 hover:bg-white/20 focus:outline-none focus:ring-2 focus:ring-white", "aria-label": "Dismiss announcement", children: _jsx(X, { className: "h-4 w-4" }) })] }) }) }));
10
57
  }
@@ -146,7 +146,7 @@ export function CharacterHotspotForm({ initial, onUploadImage, onSave, onAfterSa
146
146
  }, children: "Next: Place Pins \u2192" })] })] })), step === "place" && (_jsxs(Div, { className: CLS_STEP, style: {
147
147
  borderColor: "var(--border-ink)",
148
148
  background: "var(--surface-elevated)",
149
- }, children: [_jsx(Heading, { level: 2, className: "text-lg font-bold", children: "Place a Pin" }), _jsxs(Text, { className: "text-sm", style: { color: "var(--color-muted)" }, children: [_jsx("strong", { children: "Click anywhere on the image" }), " to drop a pin, or enter exact coordinates below."] }), _jsxs(Div, { ref: containerRef, className: CLS_IMG_WRAP, style: {
149
+ }, children: [_jsx(Heading, { level: 2, className: "text-lg font-bold", children: "Place a Pin" }), _jsxs(Text, { className: "text-sm", style: { color: "var(--color-muted)" }, children: [_jsx(Span, { weight: "bold", children: "Click anywhere on the image" }), " to drop a pin, or enter exact coordinates below."] }), _jsxs(Div, { ref: containerRef, className: CLS_IMG_WRAP, style: {
150
150
  paddingTop: "56.25%",
151
151
  cursor: "crosshair",
152
152
  background: "#111",
@@ -220,7 +220,7 @@ export function CharacterHotspotForm({ initial, onUploadImage, onSave, onAfterSa
220
220
  }, children: "Continue: Add Details \u2192" })] })] })] })), step === "details" && (_jsxs(Div, { className: CLS_STEP, style: {
221
221
  borderColor: "var(--border-ink)",
222
222
  background: "var(--surface-elevated)",
223
- }, children: [_jsx(Heading, { level: 2, className: "text-lg font-bold", children: "Pin Details" }), _jsxs(Text, { className: "text-sm", style: { color: "var(--color-muted)" }, children: ["Fill in the details for the pin at", " ", _jsxs("strong", { children: [draftPos?.xPct, "%, ", draftPos?.yPct, "%"] }), "."] }), _jsx(Div, { className: "grid grid-cols-2 gap-3", children: [
223
+ }, children: [_jsx(Heading, { level: 2, className: "text-lg font-bold", children: "Pin Details" }), _jsxs(Text, { className: "text-sm", style: { color: "var(--color-muted)" }, children: ["Fill in the details for the pin at", " ", _jsxs(Span, { weight: "bold", children: [draftPos?.xPct, "%, ", draftPos?.yPct, "%"] }), "."] }), _jsx(Div, { className: "grid grid-cols-2 gap-3", children: [
224
224
  {
225
225
  label: "Name *",
226
226
  value: draftName,
@@ -3,5 +3,7 @@ export type { MarketplaceHomepageViewAdSlots } from "../lib/section-renderer";
3
3
  export interface MarketplaceHomepageViewProps {
4
4
  adSlots?: import("../lib/section-renderer").MarketplaceHomepageViewAdSlots;
5
5
  newsletterFormSlot?: React.ReactNode;
6
+ /** Callback when user dismisses announcement banner — wire to a server action to persist. */
7
+ onBannerDismiss?: (hash: string) => void;
6
8
  }
7
- export declare function MarketplaceHomepageView({ adSlots, newsletterFormSlot, }?: MarketplaceHomepageViewProps): Promise<import("react/jsx-runtime").JSX.Element>;
9
+ export declare function MarketplaceHomepageView({ adSlots, newsletterFormSlot, onBannerDismiss, }?: MarketplaceHomepageViewProps): Promise<import("react/jsx-runtime").JSX.Element>;
@@ -23,7 +23,7 @@ function toCarouselSlides(docs) {
23
23
  updatedAt: updatedAt instanceof Date ? updatedAt.toISOString() : undefined,
24
24
  }));
25
25
  }
26
- export async function MarketplaceHomepageView({ adSlots, newsletterFormSlot, } = {}) {
26
+ export async function MarketplaceHomepageView({ adSlots, newsletterFormSlot, onBannerDismiss, } = {}) {
27
27
  const slides = await carouselRepository.getActiveSlides().catch(() => []);
28
28
  const siteSettings = await siteSettingsRepository.getSingleton().catch(() => null);
29
29
  const announcementMessage = siteSettings?.announcementBar?.message?.trim() ||
@@ -113,5 +113,5 @@ export async function MarketplaceHomepageView({ adSlots, newsletterFormSlot, } =
113
113
  : undefined,
114
114
  };
115
115
  const carouselSlides = toCarouselSlides(slides);
116
- return (_jsxs(Main, { children: [showAnnouncement ? _jsx(AnnouncementBar, { message: announcementMessage }) : null, orderedSections.map((section) => renderSection(section, adSlots, newsletterFormSlot ?? null, faqItems, carouselSlides, liveStats, sectionData))] }));
116
+ return (_jsxs(Main, { children: [showAnnouncement ? _jsx(AnnouncementBar, { message: announcementMessage, onDismiss: onBannerDismiss }) : null, orderedSections.map((section) => renderSection(section, adSlots, newsletterFormSlot ?? null, faqItems, carouselSlides, liveStats, sectionData))] }));
117
117
  }
@@ -53,7 +53,7 @@ export { FeaturedResultsSection } from "./FeaturedResultsSection";
53
53
  export type { HowItWorksInfoStep, HowItWorksInfoDetail, HowItWorksInfoViewProps, } from "./HowItWorksInfoView";
54
54
  export { HowItWorksInfoView } from "./HowItWorksInfoView";
55
55
  export type { AnnouncementBarProps } from "./AnnouncementBar";
56
- export { AnnouncementBar } from "./AnnouncementBar";
56
+ export { AnnouncementBar, hashBannerMessage } from "./AnnouncementBar";
57
57
  export type { ShopByCategorySectionProps } from "./ShopByCategorySection";
58
58
  export { ShopByCategorySection } from "./ShopByCategorySection";
59
59
  export type { FeaturedProductsSectionProps } from "./FeaturedProductsSection";
@@ -26,7 +26,7 @@ export { WhatsAppCommunitySection } from "./WhatsAppCommunitySection";
26
26
  export { HomepageSkeleton } from "./HomepageSkeleton";
27
27
  export { FeaturedResultsSection } from "./FeaturedResultsSection";
28
28
  export { HowItWorksInfoView } from "./HowItWorksInfoView";
29
- export { AnnouncementBar } from "./AnnouncementBar";
29
+ export { AnnouncementBar, hashBannerMessage } from "./AnnouncementBar";
30
30
  export { ShopByCategorySection } from "./ShopByCategorySection";
31
31
  export { FeaturedProductsSection } from "./FeaturedProductsSection";
32
32
  export { FeaturedAuctionsSection } from "./FeaturedAuctionsSection";
@@ -1,6 +1,6 @@
1
1
  import React from "react";
2
2
  import type { LiveStatsMap } from "./live-stats";
3
- import { AnnouncementBar } from "../components/AnnouncementBar";
3
+ import { AnnouncementBar, hashBannerMessage } from "../components/AnnouncementBar";
4
4
  import type { CarouselSlide } from "../types/index";
5
5
  import type { HomepageSectionDocument } from "../schemas";
6
6
  import type { ProductItem } from "../../products/types";
@@ -33,4 +33,4 @@ export type FaqItem = {
33
33
  category: string;
34
34
  };
35
35
  export declare function renderSection(section: HomepageSectionDocument, adSlots: MarketplaceHomepageViewAdSlots | undefined, newsletterFormSlot: React.ReactNode, faqItems: FaqItem[], slides: CarouselSlide[], liveStats: LiveStatsMap, sectionData?: SectionData): React.ReactNode;
36
- export { AnnouncementBar };
36
+ export { AnnouncementBar, hashBannerMessage };
@@ -3,7 +3,7 @@ import React from "react";
3
3
  import { ROUTES } from "../../../next";
4
4
  import { cleanTitle, parseWelcomeDescription } from "./section-helpers";
5
5
  import { DEFAULT_TRUST_FEATURES, DEFAULT_SECURITY_ITEMS } from "./section-defaults";
6
- import { AnnouncementBar } from "../components/AnnouncementBar";
6
+ import { AnnouncementBar, hashBannerMessage } from "../components/AnnouncementBar";
7
7
  import { HeroCarousel } from "../components/HeroCarousel";
8
8
  import { StatsCounterSection } from "../components/StatsCounterSection";
9
9
  import { TrustFeaturesSection } from "../components/TrustFeaturesSection";
@@ -202,4 +202,4 @@ export function renderSection(section, adSlots, newsletterFormSlot, faqItems, sl
202
202
  const adSlotKey = AD_SLOT_MAP[section.type];
203
203
  return (_jsxs(React.Fragment, { children: [sectionElement, adSlots && adSlotKey !== undefined && adSlotKey in adSlots && adSlots[adSlotKey]] }, section.id));
204
204
  }
205
- export { AnnouncementBar };
205
+ export { AnnouncementBar, hashBannerMessage };
@@ -55,7 +55,7 @@ function BulkPickerPanel({ bulkActions, selectedActionId, pickerOpen, onSelect,
55
55
  i > 0 ? "border-t border-zinc-100/80 dark:border-slate-800/80" : "",
56
56
  isSelected ? "bg-zinc-50 dark:bg-slate-800/60" : "",
57
57
  action.variant === "danger"
58
- ? "text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-950/30"
58
+ ? "text-error hover:bg-error-surface"
59
59
  : "text-zinc-800 dark:text-zinc-100 hover:bg-zinc-50 dark:hover:bg-slate-800/60",
60
60
  action.disabled || action.loading ? "opacity-50 cursor-not-allowed" : "",
61
61
  ]
@@ -133,7 +133,7 @@ export default function BottomActions() {
133
133
  "bg-zinc-50 hover:bg-zinc-100 active:bg-zinc-200 dark:bg-slate-800/60 dark:hover:bg-slate-700/60",
134
134
  "border-zinc-200 dark:border-slate-700",
135
135
  selectedAction?.variant === "danger"
136
- ? "text-red-600 dark:text-red-400"
136
+ ? "text-error"
137
137
  : "text-zinc-800 dark:text-zinc-100",
138
138
  ]
139
139
  .filter(Boolean)
@@ -69,7 +69,7 @@ export function ListingLayout({ headerSlot, statusTabsSlot, filterContent, filte
69
69
  sidebarOpen
70
70
  ? "bg-primary/10 border-primary/30 text-primary dark:bg-primary/15 dark:border-primary/40"
71
71
  : "border-zinc-200 dark:border-slate-700 text-zinc-600 dark:text-slate-300 hover:border-zinc-300 dark:hover:border-slate-600 hover:bg-zinc-50 dark:hover:bg-slate-800/60",
72
- ].join(" "), children: [l.filtersTitle, filterActiveCount > 0 && (_jsx(Span, { className: "inline-flex items-center justify-center w-4 h-4 text-[10px] font-bold rounded-full bg-primary text-white", children: filterActiveCount }))] })), searchSlot && _jsx(Div, { className: "flex-1 min-w-0", children: searchSlot }), _jsxs(Row, { className: "gap-1.5 flex-shrink-0", children: [sortSlot, viewToggleSlot && (_jsx(Row, { className: "gap-0.5 rounded-full border border-zinc-200 dark:border-slate-700 bg-white dark:bg-slate-900 p-0.5 shadow-sm", children: viewToggleSlot })), actionsSlot] }), toolbarPaginationSlot && (_jsx(Div, { className: "ml-auto flex-shrink-0 pl-3 border-l border-zinc-200/70 dark:border-slate-700/70", children: toolbarPaginationSlot }))] }), _jsxs(Div, { className: "flex flex-col gap-2 md:hidden", children: [_jsxs(Row, { className: "gap-2", children: [hasFilter && (_jsxs(Button, { type: "button", onClick: () => setMobileFilterOpen(true), "aria-label": l.filtersTitle, variant: "ghost", size: "sm", className: [
72
+ ].join(" "), children: [l.filtersTitle, filterActiveCount > 0 && (_jsx(Span, { className: "inline-flex items-center justify-center w-4 h-4 text-[10px] font-bold rounded-full bg-primary text-white", children: filterActiveCount }))] })), searchSlot && _jsx(Div, { className: "flex-1 min-w-0", children: searchSlot }), _jsxs(Row, { className: "gap-1.5 flex-shrink-0", children: [sortSlot, viewToggleSlot && (_jsx(Row, { surface: "card", className: "gap-0.5 rounded-full p-0.5", children: viewToggleSlot })), actionsSlot] }), toolbarPaginationSlot && (_jsx(Div, { className: "ml-auto flex-shrink-0 pl-3 border-l border-zinc-200/70 dark:border-slate-700/70", children: toolbarPaginationSlot }))] }), _jsxs(Div, { className: "flex flex-col gap-2 md:hidden", children: [_jsxs(Row, { className: "gap-2", children: [hasFilter && (_jsxs(Button, { type: "button", onClick: () => setMobileFilterOpen(true), "aria-label": l.filtersTitle, variant: "ghost", size: "sm", className: [
73
73
  "flex-shrink-0 flex items-center gap-1.5",
74
74
  "rounded-full h-9 px-3 text-sm font-medium",
75
75
  "border border-zinc-200 dark:border-slate-700",
@@ -85,7 +85,7 @@ export function ListingLayout({ headerSlot, statusTabsSlot, filterContent, filte
85
85
  sidebarOpen
86
86
  ? "w-60 xl:w-64 2xl:w-72 opacity-100"
87
87
  : "w-0 opacity-0 pointer-events-none",
88
- ].join(" "), children: _jsxs(Div, { className: "w-60 xl:w-64 2xl:w-72 rounded-2xl overflow-hidden border border-zinc-200/80 dark:border-slate-700/60 bg-white dark:bg-slate-900 shadow-sm", children: [_jsxs(Row, { justify: "between", className: "px-4 py-3 border-b border-zinc-100 dark:border-slate-800", children: [_jsx(Div, { className: "font-semibold text-sm", children: panelTitle }), filterActiveCount > 0 && (_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: onFilterClear, className: "text-xs text-primary hover:text-primary/80 hover:underline p-0 h-auto leading-none font-medium", children: l.clearAll }))] }), _jsx(Div, { className: "px-3 pt-5 pb-3 max-h-[calc(100vh-15rem)] overflow-y-auto space-y-4", children: filterContent }), _jsx(Div, { className: "px-3 pb-3 pt-2 border-t border-zinc-100 dark:border-slate-800", children: _jsx(Button, { type: "button", variant: "primary", className: "w-full rounded-xl bg-primary text-white py-2 px-3", onClick: onFilterApply, children: l.applyFilters }) })] }) })), _jsxs(Div, { className: "flex-1 min-w-0 space-y-3", children: [activeFiltersSlot, errorSlot ? (errorSlot) : (_jsxs(_Fragment, { children: [children, paginationSlot && _jsx(Div, { className: "pt-2", children: paginationSlot })] }))] })] }), toolbarPaginationSlot && (_jsx(Nav, { "aria-label": "Pagination", className: [
88
+ ].join(" "), children: _jsxs(Div, { surface: "elevated", className: "w-60 xl:w-64 2xl:w-72 overflow-hidden", children: [_jsxs(Row, { justify: "between", className: "px-4 py-3 border-b border-zinc-100 dark:border-slate-800", children: [_jsx(Div, { className: "font-semibold text-sm", children: panelTitle }), filterActiveCount > 0 && (_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: onFilterClear, className: "text-xs text-primary hover:text-primary/80 hover:underline p-0 h-auto leading-none font-medium", children: l.clearAll }))] }), _jsx(Div, { className: "px-3 pt-5 pb-3 max-h-[calc(100vh-15rem)] overflow-y-auto space-y-4", children: filterContent }), _jsx(Div, { className: "px-3 pb-3 pt-2 border-t border-zinc-100 dark:border-slate-800", children: _jsx(Button, { type: "button", variant: "primary", className: "w-full rounded-xl bg-primary text-white py-2 px-3", onClick: onFilterApply, children: l.applyFilters }) })] }) })), _jsxs(Div, { className: "flex-1 min-w-0 space-y-3", children: [activeFiltersSlot, errorSlot ? (errorSlot) : (_jsxs(_Fragment, { children: [children, paginationSlot && _jsx(Div, { className: "pt-2", children: paginationSlot })] }))] })] }), toolbarPaginationSlot && (_jsx(Nav, { "aria-label": "Pagination", className: [
89
89
  "fixed left-0 right-0 md:hidden",
90
90
  isDashboard
91
91
  ? "bottom-0"
@@ -97,7 +97,7 @@ export function ListingLayout({ headerSlot, statusTabsSlot, filterContent, filte
97
97
  "shadow-[0_-2px_8px_rgba(0,0,0,0.06)] dark:shadow-[0_-2px_8px_rgba(0,0,0,0.20)]",
98
98
  "h-10 flex items-center justify-center px-3 overflow-x-auto pb-px",
99
99
  "[scrollbar-width:none] [&::-webkit-scrollbar]:hidden",
100
- ].join(" "), children: toolbarPaginationSlot })), hasFilter && mobileFilterOpen && (_jsxs(_Fragment, { children: [_jsx(Div, { className: "fixed inset-0 z-40 bg-black/40 lg:hidden", onClick: () => setMobileFilterOpen(false), "aria-hidden": "true" }), _jsxs(Div, { ref: mobileOverlayRef, className: "fixed inset-0 z-50 flex flex-col lg:hidden bg-white dark:bg-slate-950", role: "dialog", "aria-modal": "true", "aria-label": panelTitle, children: [_jsxs(Row, { justify: "between", className: "px-4 py-3 border-b border-zinc-100 dark:border-slate-800 flex-shrink-0", children: [_jsxs(Div, { className: "font-semibold", children: [panelTitle, filterActiveCount > 0 && (_jsx(Span, { className: "ml-2 inline-flex items-center justify-center w-5 h-5 text-[11px] font-bold rounded-full bg-primary text-white", children: filterActiveCount }))] }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => setMobileFilterOpen(false), "aria-label": l.close, className: "rounded-full w-8 h-8 p-0 flex items-center justify-center hover:bg-zinc-100 dark:hover:bg-slate-800", children: "x" })] }), _jsx(Div, { className: "flex-1 overflow-y-auto px-4 pt-6 pb-4 space-y-4", children: filterContent }), _jsxs(Div, { className: "flex-shrink-0 flex gap-3 px-4 py-4 border-t border-zinc-100 dark:border-slate-800 bg-white dark:bg-slate-950", children: [_jsx(Button, { type: "button", variant: "outline", className: "flex-1 rounded-xl bg-zinc-200 dark:bg-slate-700 py-2 px-3", onClick: () => {
100
+ ].join(" "), children: toolbarPaginationSlot })), hasFilter && mobileFilterOpen && (_jsxs(_Fragment, { children: [_jsx(Div, { className: "fixed inset-0 z-40 bg-black/40 lg:hidden", onClick: () => setMobileFilterOpen(false), "aria-hidden": "true" }), _jsxs(Div, { ref: mobileOverlayRef, className: "fixed inset-0 z-50 flex flex-col lg:hidden bg-white dark:bg-slate-950", role: "dialog", "aria-modal": "true", "aria-label": panelTitle, children: [_jsxs(Row, { justify: "between", className: "px-4 py-3 border-b border-zinc-100 dark:border-slate-800 flex-shrink-0", children: [_jsxs(Div, { className: "font-semibold", children: [panelTitle, filterActiveCount > 0 && (_jsx(Span, { className: "ml-2 inline-flex items-center justify-center w-5 h-5 text-[11px] font-bold rounded-full bg-primary text-white", children: filterActiveCount }))] }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => setMobileFilterOpen(false), "aria-label": l.close, className: "rounded-full w-8 h-8 p-0 flex items-center justify-center hover:bg-zinc-100 dark:hover:bg-slate-800", children: "x" })] }), _jsx(Div, { className: "flex-1 overflow-y-auto px-4 pt-6 pb-4 space-y-4", children: filterContent }), _jsxs(Div, { surface: "muted", className: "flex-shrink-0 flex gap-3 px-4 py-4 border-t border-zinc-100 dark:border-slate-800", children: [_jsx(Button, { type: "button", variant: "outline", className: "flex-1 rounded-xl bg-zinc-200 dark:bg-slate-700 py-2 px-3", onClick: () => {
101
101
  onFilterClear?.();
102
102
  }, children: l.clearAll }), _jsx(Button, { type: "button", variant: "primary", className: "flex-1 rounded-xl bg-primary text-white py-2 px-3", onClick: handleMobileApply, children: l.applyFilters })] })] })] }))] }));
103
103
  }
@@ -31,7 +31,7 @@ export function TitleBarLayout({ onToggleSidebar, sidebarOpen, onSearchToggle, s
31
31
  const profileEl = profileHref ? (_jsx(Link, { href: profileHref, "aria-label": user ? `Profile — ${user.displayName ?? user.email}` : "Sign in", className: `relative ${iconBtn}`, children: user?.photoURL ? (
32
32
  // eslint-disable-next-line @next/next/no-img-element
33
33
  _jsx("img", { src: user.photoURL, alt: user.displayName ?? "Profile", width: 28, height: 28, loading: "lazy", className: "w-7 h-7 rounded-full object-cover" })) : (_jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M15.75 6a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0zM4.501 20.118a7.5 7.5 0 0 1 14.998 0A17.933 17.933 0 0 1 12 21.75c-2.676 0-5.216-.584-7.499-1.632z" }) })) })) : null;
34
- const authButtonsEl = !user && (loginHref || registerHref) ? (_jsxs(Row, { gap: "xs", className: "hidden lg:flex items-center", children: [loginHref && (_jsx(Link, { href: loginHref, className: "px-3 py-1.5 text-sm font-medium text-zinc-700 dark:text-zinc-300 hover:text-primary-700 dark:hover:text-secondary-400 transition-colors rounded-lg hover:bg-primary-50 dark:hover:bg-slate-800", children: "Sign in" })), registerHref && (_jsx(Link, { href: registerHref, className: "px-3 py-1.5 text-sm font-semibold rounded-lg bg-primary text-white hover:bg-primary/90 transition-colors btn-glow shadow-sm", children: "Register" }))] })) : null;
34
+ const authButtonsEl = !user && (loginHref || registerHref) ? (_jsxs(Row, { gap: "xs", className: "hidden lg:flex", children: [loginHref && (_jsx(Link, { href: loginHref, className: "px-3 py-1.5 text-sm font-medium text-zinc-700 dark:text-zinc-300 hover:text-primary-700 dark:hover:text-secondary-400 transition-colors rounded-lg hover:bg-primary-50 dark:hover:bg-slate-800", children: "Sign in" })), registerHref && (_jsx(Link, { href: registerHref, className: "px-3 py-1.5 text-sm font-semibold rounded-lg bg-primary text-white hover:bg-primary/90 transition-colors btn-glow shadow-sm", children: "Register" }))] })) : null;
35
35
  const hasTb2 = !!(notificationsEl || wishlistEl || cartEl || profileEl);
36
36
  // ── Render ───────────────────────────────────────────────────────────────────
37
37
  return (_jsxs(BlockHeader, { id: id, className: `sticky top-0 z-50 bg-white/95 dark:bg-slate-950/95 backdrop-blur-md border-b border-zinc-100 dark:border-slate-800 shadow-sm ${className}`, children: [promoStripText && (_jsx(Div, { className: "bg-gradient-to-r from-primary-700 to-secondary-600 dark:from-primary-700 dark:to-cobalt-700 text-white text-xs py-1 text-center font-medium", children: promoStripText })), _jsxs(Div, { className: "container mx-auto px-4 sm:px-6 lg:px-8 max-w-[1920px]", children: [_jsxs(Row, { justify: "between", gap: "none", className: "relative h-14", children: [_jsx(Row, { gap: "3", children: _jsx(Link, { href: logoHref, "aria-label": brandName, className: "flex items-center transition-opacity hover:opacity-80", children: _jsx(SiteLogo, { title: brandName, className: "h-7 md:h-9 lg:h-10" }) }) }), siteLogoUrl ? (_jsx(Div, { className: "absolute inset-y-0 left-1/2 -translate-x-1/2 flex items-center", children: _jsx(Link, { href: logoHref, "aria-label": brandName, className: "flex items-center transition-opacity hover:opacity-80", children: _jsx(SiteLogo, { src: siteLogoUrl, title: brandName, className: "h-7 md:h-9 lg:h-10" }) }) })) : (navSlot && _jsx(Div, { className: "hidden md:flex", children: navSlot })), _jsxs(Row, { gap: "xs", children: [devSlot, compareEl, notificationSlot, notificationsEl && _jsx(Div, { className: "hidden lg:flex", children: notificationsEl }), wishlistEl && _jsx(Div, { className: "hidden lg:flex", children: wishlistEl }), cartEl && _jsx(Div, { className: "hidden lg:flex", children: cartEl }), user
@@ -14,6 +14,8 @@ export { MediaPickerModal } from "./MediaPickerModal";
14
14
  export type { MediaPickerModalProps } from "./MediaPickerModal";
15
15
  export { ImageCropModal } from "./modals/ImageCropModal";
16
16
  export type { ImageCropData, ImageCropModalProps, } from "./modals/ImageCropModal";
17
+ export { ImageEditor } from "./modals/ImageEditor";
18
+ export type { ImageEditorProps } from "./modals/ImageEditor";
17
19
  export { VideoTrimModal } from "./modals/VideoTrimModal";
18
20
  export type { VideoTrimModalProps } from "./modals/VideoTrimModal";
19
21
  export { VideoThumbnailSelector } from "./modals/VideoThumbnailSelector";
@@ -11,6 +11,7 @@ export { useMediaUpload, useMediaAbort, useMediaCrop, useMediaTrim, useMediaClea
11
11
  // Modals
12
12
  export { MediaPickerModal } from "./MediaPickerModal";
13
13
  export { ImageCropModal } from "./modals/ImageCropModal";
14
+ export { ImageEditor } from "./modals/ImageEditor";
14
15
  export { VideoTrimModal } from "./modals/VideoTrimModal";
15
16
  export { VideoThumbnailSelector } from "./modals/VideoThumbnailSelector";
16
17
  // Upload components
@@ -95,5 +95,5 @@ export function ImageCropModal({ isOpen, imageUrl, onClose, onSave, initialCropD
95
95
  left: `${position.x}%`,
96
96
  top: `${position.y}%`,
97
97
  transform: "translate(-50%, -50%)",
98
- } }), _jsxs(Div, { className: "absolute inset-0 pointer-events-none", children: [_jsx(Div, { className: "absolute w-full h-full grid grid-cols-3 grid-rows-3", children: Array.from({ length: 9 }).map((_, i) => (_jsx(Div, { className: "border border-white/20 dark:border-slate-600/30" }, i))) }), _jsxs(Div, { className: "absolute inset-0 flex items-center justify-center", children: [_jsx(Div, { className: "w-6 h-px bg-white/50" }), _jsx(Div, { className: "absolute h-6 w-px bg-white/50" })] })] })] }), _jsxs(Div, { className: "space-y-1", children: [_jsxs(Row, { justify: "between", children: [_jsx(Text, { className: "text-xs font-medium", children: t("cropZoom") }), _jsxs(Text, { className: "text-xs text-zinc-500 dark:text-slate-400", children: [Math.round(zoom * 100), "%"] })] }), _jsxs(Row, { className: "gap-3", children: [_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => setZoom(Math.max(0.1, zoom - 0.1)), "aria-label": t("cropZoomOut"), children: _jsx("svg", { className: "w-4 h-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0zM13 10H7" }) }) }), _jsx(MediaSlider, { value: zoom, min: 0.1, max: 3, step: 0.01, onChange: (v) => setZoom(v), className: "flex-1" }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => setZoom(Math.min(3, zoom + 0.1)), "aria-label": t("cropZoomIn"), children: _jsx("svg", { className: "w-4 h-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0zM10 7v3m0 0v3m0-3h3m-3 0H7" }) }) })] }), _jsx(Div, { className: "flex gap-2", children: [0.5, 1, 1.5, 2].map((preset) => (_jsxs(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => setZoom(preset), className: "px-3 py-1 text-xs rounded-lg bg-zinc-100 dark:bg-slate-800", children: [preset * 100, "%"] }, preset))) })] }), showZoomWarning && (_jsxs(Alert, { variant: "warning", children: [_jsx("strong", { children: t("cropWarningTitle") }), " ", t("cropWarningMessage")] })), _jsxs(Row, { justify: "between", className: "text-xs text-zinc-500 dark:text-slate-400", children: [_jsxs(Span, { children: [t("cropPosition"), ": ", Math.round(position.x), "%, ", Math.round(position.y), "%"] }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: handleReset, className: "text-primary-600 dark:text-primary-400 hover:underline", children: t("cropReset") })] }), _jsxs(Div, { className: "flex gap-2 pt-2", children: [_jsx(Button, { onClick: handleSave, variant: "primary", className: "flex-1", children: t("cropSave") }), _jsx(Button, { onClick: onClose, variant: "secondary", className: "flex-1", children: t("cropCancel") })] })] }) }));
98
+ } }), _jsxs(Div, { className: "absolute inset-0 pointer-events-none", children: [_jsx(Div, { className: "absolute w-full h-full grid grid-cols-3 grid-rows-3", children: Array.from({ length: 9 }).map((_, i) => (_jsx(Div, { className: "border border-white/20 dark:border-slate-600/30" }, i))) }), _jsxs(Div, { className: "absolute inset-0 flex items-center justify-center", children: [_jsx(Div, { className: "w-6 h-px bg-white/50" }), _jsx(Div, { className: "absolute h-6 w-px bg-white/50" })] })] })] }), _jsxs(Div, { className: "space-y-1", children: [_jsxs(Row, { justify: "between", children: [_jsx(Text, { className: "text-xs font-medium", children: t("cropZoom") }), _jsxs(Text, { className: "text-xs text-zinc-500 dark:text-slate-400", children: [Math.round(zoom * 100), "%"] })] }), _jsxs(Row, { className: "gap-3", children: [_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => setZoom(Math.max(0.1, zoom - 0.1)), "aria-label": t("cropZoomOut"), children: _jsx("svg", { className: "w-4 h-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0zM13 10H7" }) }) }), _jsx(MediaSlider, { value: zoom, min: 0.1, max: 3, step: 0.01, onChange: (v) => setZoom(v), className: "flex-1" }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => setZoom(Math.min(3, zoom + 0.1)), "aria-label": t("cropZoomIn"), children: _jsx("svg", { className: "w-4 h-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0zM10 7v3m0 0v3m0-3h3m-3 0H7" }) }) })] }), _jsx(Div, { className: "flex gap-2", children: [0.5, 1, 1.5, 2].map((preset) => (_jsxs(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => setZoom(preset), className: "px-3 py-1 text-xs rounded-lg bg-zinc-100 dark:bg-slate-800", children: [preset * 100, "%"] }, preset))) })] }), showZoomWarning && (_jsxs(Alert, { variant: "warning", children: [_jsx(Span, { weight: "bold", children: t("cropWarningTitle") }), " ", t("cropWarningMessage")] })), _jsxs(Row, { justify: "between", className: "text-xs text-zinc-500 dark:text-slate-400", children: [_jsxs(Span, { children: [t("cropPosition"), ": ", Math.round(position.x), "%, ", Math.round(position.y), "%"] }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: handleReset, className: "text-primary-600 dark:text-primary-400 hover:underline", children: t("cropReset") })] }), _jsxs(Div, { className: "flex gap-2 pt-2", children: [_jsx(Button, { onClick: handleSave, variant: "primary", className: "flex-1", children: t("cropSave") }), _jsx(Button, { onClick: onClose, variant: "secondary", className: "flex-1", children: t("cropCancel") })] })] }) }));
99
99
  }
@@ -0,0 +1,12 @@
1
+ import "react-advanced-cropper/dist/style.css";
2
+ export interface ImageEditorProps {
3
+ isOpen: boolean;
4
+ imageUrl: string;
5
+ onClose: () => void;
6
+ onSave: (croppedFile: File) => void;
7
+ aspectRatio?: number;
8
+ outputFormat?: "image/jpeg" | "image/png" | "image/webp";
9
+ outputQuality?: number;
10
+ maxOutputWidth?: number;
11
+ }
12
+ export declare function ImageEditor({ isOpen, imageUrl, onClose, onSave, aspectRatio, outputFormat, outputQuality, maxOutputWidth, }: ImageEditorProps): import("react/jsx-runtime").JSX.Element;