@mohasinac/appkit 2.8.3 → 2.8.5

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 (249) hide show
  1. package/dist/_internal/client/features/layout/SidebarCollapseToggle.js +2 -0
  2. package/dist/_internal/shared/listing-types/action-tracker.js +0 -2
  3. package/dist/client.d.ts +2 -0
  4. package/dist/client.js +2 -0
  5. package/dist/core/hooks/useSiteSettings.d.ts +1 -1
  6. package/dist/core/unit-of-work.js +0 -1
  7. package/dist/features/account/components/NotificationBell.js +1 -0
  8. package/dist/features/account/components/UserOffersPanel.js +1 -0
  9. package/dist/features/account/components/UserSidebar.js +3 -1
  10. package/dist/features/account/hooks/useAccount.d.ts +2 -2
  11. package/dist/features/account/hooks/useAddressSelector.d.ts +1 -1
  12. package/dist/features/account/hooks/useAddresses.d.ts +2 -2
  13. package/dist/features/account/hooks/useNotifications.d.ts +1 -1
  14. package/dist/features/account/hooks/useProfile.d.ts +1 -1
  15. package/dist/features/account/hooks/usePublicProfile.d.ts +2 -2
  16. package/dist/features/admin/components/AdminAllEventEntriesView.js +5 -3
  17. package/dist/features/admin/components/AdminBidsView.js +6 -4
  18. package/dist/features/admin/components/AdminBlogView.js +6 -4
  19. package/dist/features/admin/components/AdminBrandsView.js +8 -6
  20. package/dist/features/admin/components/AdminBundlesView.js +10 -8
  21. package/dist/features/admin/components/AdminCarouselEditorView.js +3 -1
  22. package/dist/features/admin/components/AdminCartsView.js +6 -4
  23. package/dist/features/admin/components/AdminCategoriesView.js +7 -5
  24. package/dist/features/admin/components/AdminClassifiedView.js +4 -3
  25. package/dist/features/admin/components/AdminContactView.js +6 -4
  26. package/dist/features/admin/components/AdminCouponsView.js +6 -4
  27. package/dist/features/admin/components/AdminDigitalCodesView.js +4 -3
  28. package/dist/features/admin/components/AdminFaqsView.js +5 -3
  29. package/dist/features/admin/components/AdminFeaturesView.js +5 -4
  30. package/dist/features/admin/components/AdminGroupedListingsView.js +4 -3
  31. package/dist/features/admin/components/AdminHistoryView.js +4 -3
  32. package/dist/features/admin/components/AdminLiveView.js +4 -3
  33. package/dist/features/admin/components/AdminNewsletterView.js +5 -3
  34. package/dist/features/admin/components/AdminNotificationsView.js +6 -4
  35. package/dist/features/admin/components/AdminOrdersView.js +6 -4
  36. package/dist/features/admin/components/AdminPayoutsView.js +7 -5
  37. package/dist/features/admin/components/AdminPrizeDrawsView.js +6 -4
  38. package/dist/features/admin/components/AdminProductsView.js +7 -5
  39. package/dist/features/admin/components/AdminReturnRequestsView.js +4 -3
  40. package/dist/features/admin/components/AdminReviewsView.js +8 -6
  41. package/dist/features/admin/components/AdminScammersView.js +8 -6
  42. package/dist/features/admin/components/AdminSessionsView.js +6 -4
  43. package/dist/features/admin/components/AdminSidebar.js +3 -1
  44. package/dist/features/admin/components/AdminSiteSettingsView.js +3 -1
  45. package/dist/features/admin/components/AdminStoreAddressesView.js +2 -1
  46. package/dist/features/admin/components/AdminStoresView.js +6 -4
  47. package/dist/features/admin/components/AdminSublistingCategoriesView.js +6 -5
  48. package/dist/features/admin/components/AdminSupportTicketDetailView.js +0 -3
  49. package/dist/features/admin/components/AdminSupportTicketsView.js +8 -6
  50. package/dist/features/admin/components/AdminTeamView.js +6 -4
  51. package/dist/features/admin/components/AdminUsersView.js +6 -4
  52. package/dist/features/admin/components/AdminWishlistsView.js +4 -3
  53. package/dist/features/admin/components/DashboardStats.js +5 -1
  54. package/dist/features/admin/components/analytics/AdminAnalyticsCharts.js +3 -1
  55. package/dist/features/admin/components/analytics/AdminStatCard.js +5 -1
  56. package/dist/features/admin/constants/filter-tabs.d.ts +1 -1
  57. package/dist/features/admin/constants/filter-tabs.js +0 -11
  58. package/dist/features/admin/hooks/useAdmin.d.ts +1 -1
  59. package/dist/features/admin/hooks/useAdminSectionsListing.d.ts +1 -1
  60. package/dist/features/admin/hooks/useChat.d.ts +1 -1
  61. package/dist/features/admin/hooks/useChat.js +1 -0
  62. package/dist/features/admin/types/index.d.ts +1 -1
  63. package/dist/features/auctions/actions/bid-actions.js +4 -2
  64. package/dist/features/auctions/components/AuctionBidsTable.js +5 -1
  65. package/dist/features/auctions/components/AuctionsListView.js +7 -6
  66. package/dist/features/auctions/hooks/useAuctions.d.ts +8 -8
  67. package/dist/features/auth/hooks/useAuth.d.ts +2 -2
  68. package/dist/features/auth/hooks/useAuth.js +1 -0
  69. package/dist/features/blog/actions/blog-actions.js +3 -2
  70. package/dist/features/blog/components/BlogFeaturedCard.js +3 -1
  71. package/dist/features/blog/components/BlogListView.js +5 -1
  72. package/dist/features/blog/components/BlogPostView.js +3 -1
  73. package/dist/features/cart/components/CartDrawer.js +3 -1
  74. package/dist/features/cart/hooks/useCart.d.ts +1 -1
  75. package/dist/features/cart/hooks/useCartQuery.d.ts +1 -1
  76. package/dist/features/cart/hooks/useCheckoutReadQueries.d.ts +2 -2
  77. package/dist/features/cart/hooks/useOrder.d.ts +2 -2
  78. package/dist/features/categories/components/BrandDetailPageView.js +15 -9
  79. package/dist/features/categories/components/CategoriesIndexListing.js +1 -1
  80. package/dist/features/categories/components/CategoriesIndexPageView.js +5 -4
  81. package/dist/features/categories/components/CategoryBundlesListing.js +5 -4
  82. package/dist/features/categories/components/CategoryDetailPageView.js +15 -11
  83. package/dist/features/categories/components/CategoryFilters.d.ts +12 -12
  84. package/dist/features/categories/components/CategoryFilters.js +13 -12
  85. package/dist/features/categories/components/CategoryGrid.js +5 -1
  86. package/dist/features/categories/components/CategoryTableColumns.js +6 -2
  87. package/dist/features/categories/components/CategoryTree.d.ts +1 -1
  88. package/dist/features/categories/components/CategoryTree.js +4 -2
  89. package/dist/features/categories/components/ConcernCard.js +3 -1
  90. package/dist/features/categories/hooks/useCategories.d.ts +3 -3
  91. package/dist/features/categories/hooks/useCategorySelector.d.ts +3 -3
  92. package/dist/features/categories/hooks/useCategoryTree.d.ts +1 -1
  93. package/dist/features/categories/schemas/firestore.d.ts +2 -2
  94. package/dist/features/checkout/actions/checkout-actions.d.ts +1 -1
  95. package/dist/features/checkout/actions/checkout-actions.js +1 -1
  96. package/dist/features/checkout/hooks/useCheckoutApi.d.ts +2 -2
  97. package/dist/features/classified/components/ClassifiedListView.js +6 -5
  98. package/dist/features/collections/components/CollectionCard.js +3 -1
  99. package/dist/features/collections/hooks/useCollections.d.ts +2 -2
  100. package/dist/features/cron/jobs/auction-expiry.job.d.ts +2 -2
  101. package/dist/features/cron/jobs/auction-expiry.job.js +2 -1
  102. package/dist/features/cron/jobs/preorder-reminder.job.d.ts +1 -1
  103. package/dist/features/cron/jobs/preorder-reminder.job.js +2 -1
  104. package/dist/features/digital-codes/components/DigitalCodesListView.js +4 -3
  105. package/dist/features/events/actions/event-actions.js +6 -4
  106. package/dist/features/events/components/AdminEventEditorView.js +1 -1
  107. package/dist/features/events/components/AdminEventsView.js +7 -5
  108. package/dist/features/events/components/EventCard.js +3 -1
  109. package/dist/features/events/components/EventFilters.d.ts +4 -4
  110. package/dist/features/events/components/EventFilters.js +2 -2
  111. package/dist/features/events/components/EventRafflesSection.js +4 -2
  112. package/dist/features/events/hooks/useEvent.d.ts +3 -3
  113. package/dist/features/events/hooks/useEvents.d.ts +1 -1
  114. package/dist/features/faq/actions/faq-actions.js +4 -2
  115. package/dist/features/faq/components/FAQPageContent.js +2 -1
  116. package/dist/features/faq/hooks/useFAQs.d.ts +1 -1
  117. package/dist/features/faq/hooks/useFaqList.d.ts +15 -15
  118. package/dist/features/forms/Slider.js +3 -1
  119. package/dist/features/history/hooks/useHistory.js +1 -0
  120. package/dist/features/homepage/components/AdSlot.js +6 -2
  121. package/dist/features/homepage/components/AdvertisementBanner.js +3 -1
  122. package/dist/features/homepage/components/BeforeAfterCard.js +5 -1
  123. package/dist/features/homepage/components/CustomCardsSection.js +5 -1
  124. package/dist/features/homepage/components/HowItWorksSection.js +3 -1
  125. package/dist/features/homepage/components/MarketplaceHomepageView.js +4 -2
  126. package/dist/features/homepage/components/NewsletterBanner.js +6 -2
  127. package/dist/features/homepage/components/SecurityHighlightsSection.js +3 -1
  128. package/dist/features/homepage/components/StatsCounterSection.js +3 -1
  129. package/dist/features/homepage/components/TrustFeaturesSection.js +3 -1
  130. package/dist/features/homepage/hooks/useBlogArticles.d.ts +1 -1
  131. package/dist/features/homepage/hooks/useFeaturedAuctions.d.ts +1 -1
  132. package/dist/features/homepage/hooks/useFeaturedPreOrders.d.ts +1 -1
  133. package/dist/features/homepage/hooks/useFeaturedProducts.d.ts +1 -1
  134. package/dist/features/homepage/hooks/useFeaturedStores.d.ts +1 -1
  135. package/dist/features/homepage/hooks/useHeroCarousel.d.ts +1 -1
  136. package/dist/features/homepage/hooks/useHomepageEvents.d.ts +1 -1
  137. package/dist/features/homepage/hooks/useHomepageReviews.d.ts +1 -1
  138. package/dist/features/homepage/hooks/useHomepageSections.d.ts +1 -1
  139. package/dist/features/homepage/hooks/useTopBrands.d.ts +1 -1
  140. package/dist/features/homepage/hooks/useTopCategories.d.ts +1 -1
  141. package/dist/features/layout/AppLayoutShell.js +3 -1
  142. package/dist/features/layout/BottomActions.js +0 -27
  143. package/dist/features/layout/BottomActionsContext.d.ts +0 -36
  144. package/dist/features/layout/BottomActionsContext.js +1 -39
  145. package/dist/features/layout/BottomNavbar.js +1 -1
  146. package/dist/features/live/components/LiveItemsListView.js +6 -5
  147. package/dist/features/loyalty/hooks/useLoyaltyBalance.d.ts +1 -1
  148. package/dist/features/media/MediaLightbox.js +9 -3
  149. package/dist/features/media/modals/ImageEditor.js +6 -2
  150. package/dist/features/media/upload/MediaUploadField.js +1 -1
  151. package/dist/features/messages/hooks/useConversation.js +2 -0
  152. package/dist/features/messages/hooks/useConversations.js +1 -0
  153. package/dist/features/orders/components/OrdersList.js +3 -1
  154. package/dist/features/orders/hooks/useOrders.d.ts +2 -2
  155. package/dist/features/payments/hooks/usePayments.d.ts +1 -1
  156. package/dist/features/pre-orders/components/PreOrderDetailPageView.js +1 -1
  157. package/dist/features/pre-orders/components/PreOrdersListView.js +5 -4
  158. package/dist/features/pre-orders/hooks/usePreOrders.d.ts +2 -2
  159. package/dist/features/products/actions/product-actions.js +17 -15
  160. package/dist/features/products/columns/index.d.ts +1 -1
  161. package/dist/features/products/columns/index.js +1 -1
  162. package/dist/features/products/components/CompareOverlay.js +5 -1
  163. package/dist/features/products/components/FeatureBadge.js +3 -1
  164. package/dist/features/products/components/MarketplacePrizeDrawCard.js +1 -1
  165. package/dist/features/products/components/PrizeDrawCollage.js +0 -14
  166. package/dist/features/products/components/PrizeDrawsListingView.js +5 -4
  167. package/dist/features/products/components/PrizeDrawsSection.js +4 -2
  168. package/dist/features/products/components/ProductGrid.js +3 -1
  169. package/dist/features/products/constants/sieve.d.ts +2 -2
  170. package/dist/features/products/constants/sieve.js +2 -2
  171. package/dist/features/products/hooks/useProductDetail.d.ts +1 -1
  172. package/dist/features/products/hooks/useRelatedProducts.d.ts +1 -1
  173. package/dist/features/products/hooks/useRelatedProducts.js +5 -4
  174. package/dist/features/promotions/hooks/usePromotions.d.ts +2 -2
  175. package/dist/features/reviews/actions/review-actions.js +2 -1
  176. package/dist/features/reviews/components/ReviewDetailShell.js +8 -2
  177. package/dist/features/reviews/components/ReviewModal.js +5 -1
  178. package/dist/features/reviews/components/ReviewSummary.js +3 -1
  179. package/dist/features/reviews/components/ReviewsIndexPageView.js +7 -6
  180. package/dist/features/reviews/components/ReviewsList.js +5 -1
  181. package/dist/features/reviews/hooks/useReviews.d.ts +2 -2
  182. package/dist/features/scams/actions/scam-actions.js +3 -2
  183. package/dist/features/scams/components/ScamRegistryView.js +5 -4
  184. package/dist/features/search/actions/search-actions.js +7 -6
  185. package/dist/features/search/components/Search.js +3 -1
  186. package/dist/features/search/hooks/useSearch.d.ts +2 -2
  187. package/dist/features/seller/components/CouponInlineSelect.js +2 -1
  188. package/dist/features/seller/components/PhysicalLocationModal.js +1 -1
  189. package/dist/features/seller/components/SellerAddressesView.js +2 -1
  190. package/dist/features/seller/components/SellerAuctionsView.js +7 -5
  191. package/dist/features/seller/components/SellerBidsView.js +8 -4
  192. package/dist/features/seller/components/SellerBundlesView.js +7 -6
  193. package/dist/features/seller/components/SellerClassifiedView.js +6 -5
  194. package/dist/features/seller/components/SellerCouponEditorView.js +3 -1
  195. package/dist/features/seller/components/SellerCouponsView.js +6 -4
  196. package/dist/features/seller/components/SellerDigitalCodesView.js +5 -4
  197. package/dist/features/seller/components/SellerGroupedListingsView.js +5 -4
  198. package/dist/features/seller/components/SellerLiveView.js +6 -5
  199. package/dist/features/seller/components/SellerOffersPanel.js +1 -0
  200. package/dist/features/seller/components/SellerOffersView.js +6 -4
  201. package/dist/features/seller/components/SellerOrdersView.js +5 -3
  202. package/dist/features/seller/components/SellerPayoutMethodsView.js +4 -2
  203. package/dist/features/seller/components/SellerPayoutsView.js +6 -4
  204. package/dist/features/seller/components/SellerPreOrdersView.js +6 -4
  205. package/dist/features/seller/components/SellerPrizeDrawsView.js +6 -4
  206. package/dist/features/seller/components/SellerProductsView.js +14 -8
  207. package/dist/features/seller/components/SellerReviewsView.js +1 -0
  208. package/dist/features/seller/components/SellerShippingConfigsView.js +4 -2
  209. package/dist/features/seller/components/SellerSidebar.js +12 -4
  210. package/dist/features/seller/components/SellerStoreCategoriesView.js +5 -3
  211. package/dist/features/seller/components/SellerTemplatesView.js +5 -3
  212. package/dist/features/seller/components/SublistingInlineSelect.js +2 -1
  213. package/dist/features/seller/hooks/useSellerPayouts.d.ts +3 -3
  214. package/dist/features/seller/hooks/useSellerStore.d.ts +6 -6
  215. package/dist/features/seller/hooks/useSellerStorefront.d.ts +2 -2
  216. package/dist/features/shell/FormShell.js +10 -2
  217. package/dist/features/shell/QuickFormDrawer.js +5 -2
  218. package/dist/features/stores/actions/store-query-actions.js +3 -2
  219. package/dist/features/stores/components/StoreAuctionsPageView.js +4 -2
  220. package/dist/features/stores/components/StoreDetailLayoutView.d.ts +1 -1
  221. package/dist/features/stores/components/StoreDetailLayoutView.js +7 -6
  222. package/dist/features/stores/components/StoreHeader.js +5 -1
  223. package/dist/features/stores/components/StorePreOrdersPageView.js +4 -2
  224. package/dist/features/stores/components/StorePrizeDrawsPageView.js +4 -2
  225. package/dist/features/stores/components/StoreProductsPageView.js +2 -1
  226. package/dist/features/stores/components/StoreProductsView.js +5 -4
  227. package/dist/features/stores/components/StoresIndexListing.js +4 -3
  228. package/dist/features/stores/hooks/useStoreAddressSelector.d.ts +1 -1
  229. package/dist/features/stores/hooks/useStores.d.ts +6 -6
  230. package/dist/features/wishlist/components/WishlistPage.js +3 -1
  231. package/dist/features/wishlist/components/WishlistView.js +4 -3
  232. package/dist/features/wishlist/hooks/useUserWishlist.d.ts +15 -15
  233. package/dist/features/wishlist/hooks/useWishlist.d.ts +1 -1
  234. package/dist/features/wishlist/hooks/useWishlistWithGuest.d.ts +1 -1
  235. package/dist/providers/db-firebase/admin.js +0 -1
  236. package/dist/ui/DataTable.js +6 -2
  237. package/dist/ui/components/AvatarDisplay.js +3 -1
  238. package/dist/ui/components/BackgroundRenderer.js +4 -2
  239. package/dist/ui/components/Card.js +6 -2
  240. package/dist/ui/components/ImageGallery.js +3 -1
  241. package/dist/ui/components/ImageLightbox.js +3 -1
  242. package/dist/ui/components/PasswordStrengthIndicator.js +3 -1
  243. package/dist/ui/components/Progress.js +6 -2
  244. package/dist/ui/components/RowActionMenu.js +3 -1
  245. package/dist/ui/components/SiteLogo.js +7 -1
  246. package/dist/ui/components/Skeleton.js +3 -1
  247. package/dist/ui/components/Slider.js +3 -1
  248. package/dist/ui/components/TabStrip.js +3 -1
  249. package/package.json +1 -1
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { sieveFilter, SIEVE_OP } from "@mohasinac/appkit";
2
3
  import { productRepository } from "../../../repositories";
3
4
  import { Container, Main, Heading, Section } from "../../../ui";
4
5
  import { AdSlot } from "../../homepage/components/AdSlot";
@@ -16,15 +17,15 @@ function buildPreOrderFilters(params) {
16
17
  const minPrice = sp(params, "minPrice");
17
18
  const maxPrice = sp(params, "maxPrice");
18
19
  if (minPrice)
19
- parts.push(`price>=${minPrice}`);
20
+ parts.push(sieveFilter("price", SIEVE_OP.GTE, minPrice));
20
21
  if (maxPrice)
21
- parts.push(`price<=${maxPrice}`);
22
+ parts.push(sieveFilter("price", SIEVE_OP.LTE, maxPrice));
22
23
  const store = sp(params, "storeId");
23
24
  if (store)
24
- parts.push(`storeId==${store}`);
25
+ parts.push(sieveFilter("storeId", SIEVE_OP.EQ, store));
25
26
  const preOrderProductionStatus = sp(params, "preOrderProductionStatus");
26
27
  if (preOrderProductionStatus)
27
- parts.push(`preOrderProductionStatus==${preOrderProductionStatus}`);
28
+ parts.push(sieveFilter("preOrderProductionStatus", SIEVE_OP.EQ, preOrderProductionStatus));
28
29
  return parts.join(",");
29
30
  }
30
31
  export async function PreOrdersListView({ searchParams = {} }) {
@@ -1,7 +1,7 @@
1
1
  import type { PreorderItem } from "../types";
2
2
  export declare function usePreorders(opts?: {
3
3
  endpoint?: string;
4
- }): import("@tanstack/react-query").UseQueryResult<NoInfer<PreorderItem[]>, Error>;
4
+ }): import("@tanstack/react-query").UseQueryResult<PreorderItem[], Error>;
5
5
  export declare function usePreorder(slug: string | undefined, opts?: {
6
6
  endpoint?: string;
7
- }): import("@tanstack/react-query").UseQueryResult<NoInfer<PreorderItem | null>, Error>;
7
+ }): import("@tanstack/react-query").UseQueryResult<PreorderItem | null, Error>;
@@ -1,23 +1,25 @@
1
+ import { sieveAnd, sieveFilter, SIEVE_OP } from "@mohasinac/appkit";
2
+ import { sortBy } from "@mohasinac/appkit";
1
3
  import { productRepository } from "../repository/products.repository";
2
4
  import { ProductStatusValues } from "../schemas";
3
5
  /**
4
6
  * Reusable Sieve clauses. Single source of truth for the listing-type
5
7
  * discriminator across every action helper in this file. SB1-G (S21 2026-05-12)
6
- * routes everything through `listingType==X` — the legacy boolean flags are
8
+ * routes everything through sieveFilter("listingType", SIEVE_OP.EQ, "X") — the legacy boolean flags are
7
9
  * never emitted by code under our control any more.
8
10
  */
9
- const PUBLISHED_CLAUSE = "status==published";
10
- const AUCTIONS_PUBLISHED = `listingType==auction,${PUBLISHED_CLAUSE}`;
11
- const PREORDERS_PUBLISHED = `listingType==pre-order,${PUBLISHED_CLAUSE}`;
11
+ const PUBLISHED_CLAUSE = sieveFilter("status", SIEVE_OP.EQ, "published");
12
+ const AUCTIONS_PUBLISHED = sieveAnd(sieveFilter("listingType", SIEVE_OP.EQ, "auction"), PUBLISHED_CLAUSE);
13
+ const PREORDERS_PUBLISHED = sieveAnd(sieveFilter("listingType", SIEVE_OP.EQ, "pre-order"), PUBLISHED_CLAUSE);
12
14
  export async function listProducts(params = {}) {
13
15
  const { filters, sorts = "-createdAt", page = 1, pageSize = 20, listingType, featured, storeId, categoriesIn, } = params;
14
16
  const compoundFilters = [];
15
17
  if (listingType)
16
- compoundFilters.push(`listingType==${listingType}`);
18
+ compoundFilters.push(sieveFilter("listingType", SIEVE_OP.EQ, listingType));
17
19
  if (featured === true)
18
20
  compoundFilters.push("featured==true");
19
21
  if (storeId)
20
- compoundFilters.push(`storeId==${storeId}`);
22
+ compoundFilters.push(sieveFilter("storeId", SIEVE_OP.EQ, storeId));
21
23
  if (filters)
22
24
  compoundFilters.push(filters);
23
25
  const mergedFilters = compoundFilters.length > 0 ? compoundFilters.join(",") : undefined;
@@ -34,8 +36,8 @@ export async function getProductById(id) {
34
36
  }
35
37
  export async function getFeaturedProducts(pageSize = 8) {
36
38
  return productRepository.list({
37
- filters: `featured==true,${PUBLISHED_CLAUSE}`,
38
- sorts: "-createdAt",
39
+ filters: sieveAnd(sieveFilter("featured", SIEVE_OP.EQ, "true"), PUBLISHED_CLAUSE),
40
+ sorts: sortBy("createdAt", "DESC"),
39
41
  page: 1,
40
42
  pageSize,
41
43
  });
@@ -43,7 +45,7 @@ export async function getFeaturedProducts(pageSize = 8) {
43
45
  export async function getFeaturedAuctions(pageSize = 6) {
44
46
  return productRepository.list({
45
47
  filters: AUCTIONS_PUBLISHED,
46
- sorts: "auctionEndDate",
48
+ sorts: sortBy("auctionEndDate", "ASC"),
47
49
  page: 1,
48
50
  pageSize,
49
51
  });
@@ -51,7 +53,7 @@ export async function getFeaturedAuctions(pageSize = 6) {
51
53
  export async function getLatestProducts(pageSize = 12) {
52
54
  return productRepository.list({
53
55
  filters: PUBLISHED_CLAUSE,
54
- sorts: "-createdAt",
56
+ sorts: sortBy("createdAt", "DESC"),
55
57
  page: 1,
56
58
  pageSize,
57
59
  });
@@ -59,7 +61,7 @@ export async function getLatestProducts(pageSize = 12) {
59
61
  export async function getLatestAuctions(pageSize = 12) {
60
62
  return productRepository.list({
61
63
  filters: AUCTIONS_PUBLISHED,
62
- sorts: "-createdAt",
64
+ sorts: sortBy("createdAt", "DESC"),
63
65
  page: 1,
64
66
  pageSize,
65
67
  });
@@ -76,7 +78,7 @@ export async function listAuctions(params = {}) {
76
78
  export async function getFeaturedPreOrders(pageSize = 6) {
77
79
  return productRepository.list({
78
80
  filters: PREORDERS_PUBLISHED,
79
- sorts: "preOrderDeliveryDate",
81
+ sorts: sortBy("preOrderDeliveryDate", "ASC"),
80
82
  page: 1,
81
83
  pageSize,
82
84
  });
@@ -84,7 +86,7 @@ export async function getFeaturedPreOrders(pageSize = 6) {
84
86
  export async function getLatestPreOrders(pageSize = 12) {
85
87
  return productRepository.list({
86
88
  filters: PREORDERS_PUBLISHED,
87
- sorts: "-createdAt",
89
+ sorts: sortBy("createdAt", "DESC"),
88
90
  page: 1,
89
91
  pageSize,
90
92
  });
@@ -100,8 +102,8 @@ export async function listPreOrders(params = {}) {
100
102
  }
101
103
  export async function getRelatedProducts(categoryId, excludeId, limit = 6) {
102
104
  const result = await productRepository.list({
103
- filters: `categoryId==${categoryId},${PUBLISHED_CLAUSE}`,
104
- sorts: "-createdAt",
105
+ filters: sieveAnd(sieveFilter("categoryId", SIEVE_OP.EQ, categoryId), PUBLISHED_CLAUSE),
106
+ sorts: sortBy("createdAt", "DESC"),
105
107
  page: 1,
106
108
  pageSize: limit + 1,
107
109
  });
@@ -17,7 +17,7 @@ export type ProductSortValue = (typeof PRODUCT_SORT_VALUES)[keyof typeof PRODUCT
17
17
  * Default admin column definitions for a product table.
18
18
  * These are plain data — no React imports, no JSX.
19
19
  *
20
- * Use with `DataTable<T>` from `@mohasinac/feat-admin`.
20
+ * Use with sieveFilter("DataTable", SIEVE_OP.LT, "T>") from `@mohasinac/feat-admin`.
21
21
  *
22
22
  * @example — consumer app overrides/extends columns
23
23
  * ```ts
@@ -14,7 +14,7 @@ export const PRODUCT_SORT_VALUES = {
14
14
  * Default admin column definitions for a product table.
15
15
  * These are plain data — no React imports, no JSX.
16
16
  *
17
- * Use with `DataTable<T>` from `@mohasinac/feat-admin`.
17
+ * Use with sieveFilter("DataTable", SIEVE_OP.LT, "T>") from `@mohasinac/feat-admin`.
18
18
  *
19
19
  * @example — consumer app overrides/extends columns
20
20
  * ```ts
@@ -155,7 +155,11 @@ export function CompareOverlay({ isOpen, items: passedItems, productIds, product
155
155
  }, [onRemove]);
156
156
  if (!isOpen)
157
157
  return null;
158
- return (_jsxs(Div, { className: OVERLAY_CLASS, style: OVERLAY_STYLE, role: "dialog", "aria-modal": "true", "aria-label": labels.title, children: [_jsxs(Row, { justify: "between", align: "center", className: HEADER_CLASS, children: [_jsx(Heading, { level: 2, className: "text-base font-semibold", children: labels.title }), _jsx("button", { type: "button", onClick: onClose, "aria-label": labels.close, className: "rounded-full p-2 text-zinc-500 dark:text-zinc-400 hover:bg-zinc-100 dark:hover:bg-zinc-800", children: _jsx(X, { className: "h-5 w-5" }) })] }), _jsx(Div, { className: `flex-1 ${__O.auto} ${__P.p4}`, children: isLoading ? (_jsx(Text, { variant: "secondary", className: "text-center py-12", children: "Loading\u00E2\u20AC\u00A6" })) : trimmed.length === 0 ? (_jsx(Text, { variant: "secondary", className: "text-center py-12", children: labels.empty })) : (_jsxs(_Fragment, { children: [_jsx(Div, { className: "hidden md:grid gap-4", style: {
158
+ return (
159
+ // audit-inline-style-ok: pass-through style prop
160
+ _jsxs(Div, { className: OVERLAY_CLASS, style: OVERLAY_STYLE, role: "dialog", "aria-modal": "true", "aria-label": labels.title, children: [_jsxs(Row, { justify: "between", align: "center", className: HEADER_CLASS, children: [_jsx(Heading, { level: 2, className: "text-base font-semibold", children: labels.title }), _jsx("button", { type: "button", onClick: onClose, "aria-label": labels.close, className: "rounded-full p-2 text-zinc-500 dark:text-zinc-400 hover:bg-zinc-100 dark:hover:bg-zinc-800", children: _jsx(X, { className: "h-5 w-5" }) })] }), _jsx(Div, { className: `flex-1 ${__O.auto} ${__P.p4}`, children: isLoading ? (_jsx(Text, { variant: "secondary", className: "text-center py-12", children: "Loading\u00E2\u20AC\u00A6" })) : trimmed.length === 0 ? (_jsx(Text, { variant: "secondary", className: "text-center py-12", children: labels.empty })) : (_jsxs(_Fragment, { children: [_jsx(Div, { className: "hidden md:grid gap-4",
161
+ // audit-inline-style-ok: dynamic CSS
162
+ style: {
159
163
  gridTemplateColumns: `repeat(${trimmed.length}, minmax(0, 1fr))`,
160
164
  }, children: trimmed.map((item) => (_jsx(CompareColumn, { item: item, productType: productType, labels: labels, onRemove: onRemove ? handleRemove : undefined, onClose: onClose }, item.id))) }), _jsxs(Div, { className: "md:hidden", children: [_jsx(Div, { ref: swipeRef, className: "touch-pan-y", children: trimmed[activeIndex] && (_jsx(CompareColumn, { item: trimmed[activeIndex], productType: productType, labels: labels, onRemove: onRemove ? handleRemove : undefined, onClose: onClose })) }), trimmed.length > 1 && (_jsx(Row, { justify: "center", gap: "xs", className: "mt-4", children: trimmed.map((it, idx) => (_jsx("button", { type: "button", "aria-label": `Show item ${idx + 1}`, onClick: () => setActiveIndex(idx), className: `h-2 w-2 rounded-full transition-colors ${idx === activeIndex
161
165
  ? "bg-primary"
@@ -45,7 +45,9 @@ export function FeatureBadge({ featureId, features }) {
45
45
  const f = features.find((x) => x.id === featureId);
46
46
  if (!f)
47
47
  return null;
48
- return (_jsxs(Span, { title: f.description ?? undefined, className: BADGE_BASE_CLASS, style: colorStyleFor(f.iconColor), children: [_jsx(FeatureIcon, { icon: f.icon }), f.label] }));
48
+ return (_jsxs(Span, { title: f.description ?? undefined, className: BADGE_BASE_CLASS,
49
+ // audit-inline-style-ok: computed color style fn
50
+ style: colorStyleFor(f.iconColor), children: [_jsx(FeatureIcon, { icon: f.icon }), f.label] }));
49
51
  }
50
52
  export function FeatureBadgeList({ productFeatureIds, features, maxVisible = 0, className, }) {
51
53
  if (!productFeatureIds || productFeatureIds.length === 0)
@@ -93,7 +93,7 @@ export function MarketplacePrizeDrawCard({ product, className = "", variant = "g
93
93
  const it = thumbItems[i];
94
94
  const img = it?.images?.[0];
95
95
  return (_jsx(Div, { className: "relative overflow-hidden bg-[var(--appkit-color-surface-muted)]", children: img ? (
96
- /* eslint-disable-next-line @next/next/no-raw-media-elements, @next/next/no-img-element */
96
+ /* eslint-disable-next-line @next/next/no-img-element */
97
97
  _jsx("img", { src: img, alt: it?.title ?? `Prize ${i + 1}`, loading: "lazy", className: "absolute inset-0 h-full w-full object-cover" })) : null }, `thumb-${i}`));
98
98
  }) })) : (_jsx(Div, { className: "absolute inset-0 flex items-center justify-center bg-[var(--appkit-color-surface-muted)]", children: _jsx(Text, { className: "text-xs text-[var(--appkit-color-text-muted)]", children: "No prizes" }) })) }), _jsxs(Div, { className: "absolute right-2 top-2 flex flex-col items-end gap-1", children: [_jsx(Span, { size: "xs", weight: "medium", className: CLS_PRIZE_PILL, children: mergedLabels.prizeDrawBadge }), _jsx(Span, { size: "xs", weight: "medium", className: `inline-flex items-center rounded-full px-2 py-0.5 ${statusVariant(status)}`, children: statusLabel(status, mergedLabels) })] }), status === "closed" ? (_jsx(Div, { className: "absolute inset-0 flex items-center justify-center bg-black/40", children: _jsx(Span, { size: "xs", weight: "bold", className: "rounded bg-zinc-900/80 px-3 py-1 uppercase tracking-wider text-white", children: mergedLabels.closedBadge }) })) : null, onSelect && (_jsx(BaseListingCard.Checkbox, { selected: isSelected, onSelect: (event) => {
99
99
  event.stopPropagation();
@@ -1,19 +1,5 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
- /**
4
- * PrizeDrawCollage (SB4-B)
5
- *
6
- * Read-only display of a prize-draw's full prize pool. Each cell shows the
7
- * primary image, item-number badge, and title. Items with `isWon === true`
8
- * are dimmed under a diagonal overlay with a "Won" label so the public
9
- * collage truthfully reflects what's still up for grabs.
10
- *
11
- * Clicking any cell opens a full-screen lightbox starting at that item and
12
- * cycling circularly through all items in itemNumber order.
13
- *
14
- * Optional `highlightItemNumber` (passed by the reveal modal during animation)
15
- * adds an emphasis ring to the cell that just won.
16
- */
17
3
  import { useState } from "react";
18
4
  import { Div, Text } from "../../../ui";
19
5
  import { ImageLightbox } from "../../../ui/components/ImageLightbox";
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { sieveFilter, SIEVE_OP } from "@mohasinac/appkit";
2
3
  import { productRepository } from "../../../repositories";
3
4
  import { Container, Main, Heading, Section, Text } from "../../../ui";
4
5
  import { AdSlot } from "../../homepage/components/AdSlot";
@@ -16,15 +17,15 @@ function buildPrizeDrawFilters(params) {
16
17
  const minPrice = sp(params, "minPrice");
17
18
  const maxPrice = sp(params, "maxPrice");
18
19
  if (minPrice)
19
- parts.push(`pricePerEntry>=${minPrice}`);
20
+ parts.push(sieveFilter("pricePerEntry", SIEVE_OP.GTE, minPrice));
20
21
  if (maxPrice)
21
- parts.push(`pricePerEntry<=${maxPrice}`);
22
+ parts.push(sieveFilter("pricePerEntry", SIEVE_OP.LTE, maxPrice));
22
23
  const store = sp(params, "storeId");
23
24
  if (store)
24
- parts.push(`storeId==${store}`);
25
+ parts.push(sieveFilter("storeId", SIEVE_OP.EQ, store));
25
26
  const status = sp(params, "prizeRevealStatus");
26
27
  if (status)
27
- parts.push(`prizeRevealStatus==${status}`);
28
+ parts.push(sieveFilter("prizeRevealStatus", SIEVE_OP.EQ, status));
28
29
  return parts.join(",");
29
30
  }
30
31
  /**
@@ -1,4 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { sieveFilter, sieveAnd, SIEVE_OP } from "@mohasinac/appkit";
3
+ import { sortBy } from "@mohasinac/appkit";
2
4
  import Link from "next/link";
3
5
  import { Container, Div, Heading, Section, Stack, Text, } from "../../../ui";
4
6
  import { ROUTES } from "../../../next";
@@ -15,8 +17,8 @@ export async function PrizeDrawsSection({ config, }) {
15
17
  let draws = [];
16
18
  try {
17
19
  const result = await productRepository.list({
18
- filters: "listingType==prize-draw,status==published",
19
- sorts: "-createdAt",
20
+ filters: sieveAnd(sieveFilter("listingType", SIEVE_OP.EQ, "prize-draw"), sieveFilter("status", SIEVE_OP.EQ, "published")),
21
+ sorts: sortBy("createdAt", "DESC"),
20
22
  pageSize: limit,
21
23
  });
22
24
  draws = (result.items ?? []);
@@ -107,7 +107,9 @@ export function ProductCard({ product, href, onClick, onAddToWishlist, isWishlis
107
107
  return (_jsx(Text, { className: `mt-1 text-[11px] font-medium ${low
108
108
  ? "text-warning"
109
109
  : "text-zinc-500 dark:text-zinc-400"}`, children: low ? `Only ${stock} left` : `${stock} in stock` }));
110
- })(), featuresList && product.features && product.features.length > 0 && (_jsx(FeatureBadgeList, { productFeatureIds: product.features, features: featuresList, maxVisible: PRODUCT_FEATURE_CARD_MAX_VISIBLE, className: "mt-2" })), (onAddToCart || onBuyNow) && (_jsxs(Div, { className: "mt-2 grid gap-1.5", style: { gridTemplateColumns: onBuyNow && onAddToCart ? "1fr 1fr" : "1fr" }, children: [onBuyNow && (_jsxs(Button, { type: "button", variant: "primary", size: "sm", onClick: (e) => {
110
+ })(), featuresList && product.features && product.features.length > 0 && (_jsx(FeatureBadgeList, { productFeatureIds: product.features, features: featuresList, maxVisible: PRODUCT_FEATURE_CARD_MAX_VISIBLE, className: "mt-2" })), (onAddToCart || onBuyNow) && (
111
+ // audit-inline-style-ok: runtime grid template
112
+ _jsxs(Div, { className: "mt-2 grid gap-1.5", style: { gridTemplateColumns: onBuyNow && onAddToCart ? "1fr 1fr" : "1fr" }, children: [onBuyNow && (_jsxs(Button, { type: "button", variant: "primary", size: "sm", onClick: (e) => {
111
113
  e.stopPropagation();
112
114
  e.preventDefault();
113
115
  onBuyNow(product);
@@ -154,10 +154,10 @@ export declare const BUNDLE_SORT_OPTIONS: readonly [...({
154
154
  readonly value: string;
155
155
  readonly label: "Price: High to Low";
156
156
  }, {
157
- readonly value: "-savingsAmount";
157
+ readonly value: string;
158
158
  readonly label: "Most Savings";
159
159
  }, {
160
- readonly value: "-bundleItemCount";
160
+ readonly value: string;
161
161
  readonly label: "Most Items";
162
162
  }];
163
163
  export declare const PRIZE_DRAW_SORT_OPTIONS: readonly [{
@@ -79,8 +79,8 @@ export const PREORDER_SORT_OPTIONS = [
79
79
  export const BUNDLE_SORT_OPTIONS = [
80
80
  ...BASE_TIME_SORTS.slice(0, 1), // Newest First
81
81
  ...BASE_PRICE_SORTS,
82
- { value: "-savingsAmount", label: "Most Savings" },
83
- { value: "-bundleItemCount", label: "Most Items" },
82
+ { value: sortBy("savingsAmount", "DESC"), label: "Most Savings" },
83
+ { value: sortBy("bundleItemCount", "DESC"), label: "Most Items" },
84
84
  ];
85
85
  // ---------------------------------------------------------------------------
86
86
  // Prize Draws
@@ -10,6 +10,6 @@ export declare function useProductDetail<TProduct = ProductItem>(slug: string, o
10
10
  product: TProduct | null;
11
11
  isLoading: boolean;
12
12
  error: Error | null;
13
- refetch: (options?: import("@tanstack/query-core").RefetchOptions) => Promise<import("@tanstack/query-core").QueryObserverResult<NoInfer<ProductItem | null>, Error>>;
13
+ refetch: (options?: import("@tanstack/query-core").RefetchOptions) => Promise<import("@tanstack/query-core").QueryObserverResult<ProductItem | null, Error>>;
14
14
  };
15
15
  export {};
@@ -4,4 +4,4 @@ import type { ProductListResponse } from "../types";
4
4
  * boolean translates to the canonical `listingType` clause; new callers can
5
5
  * pass the union directly via the same parameter slot in a follow-up.
6
6
  */
7
- export declare function useRelatedProducts(category: string, excludeId: string, limit?: number, isAuction?: boolean): import("@tanstack/react-query").UseQueryResult<NoInfer<ProductListResponse>, Error>;
7
+ export declare function useRelatedProducts(category: string, excludeId: string, limit?: number, isAuction?: boolean): import("@tanstack/react-query").UseQueryResult<ProductListResponse, Error>;
@@ -1,3 +1,4 @@
1
+ import { sieveAnd, sieveFilter, SIEVE_OP, sortBy } from "@mohasinac/appkit";
1
2
  import { useQuery } from "@tanstack/react-query";
2
3
  import { apiClient } from "../../../http";
3
4
  import { PRODUCT_ENDPOINTS } from "../../../constants/api-endpoints";
@@ -8,11 +9,11 @@ import { PRODUCT_ENDPOINTS } from "../../../constants/api-endpoints";
8
9
  */
9
10
  export function useRelatedProducts(category, excludeId, limit = 8, isAuction = false) {
10
11
  const listingTypeClause = isAuction
11
- ? "listingType==auction"
12
- : "listingType==standard";
12
+ ? sieveFilter("listingType", SIEVE_OP.EQ, "auction")
13
+ : sieveFilter("listingType", SIEVE_OP.EQ, "standard");
13
14
  const params = new URLSearchParams({
14
- filters: `status==published,category==${encodeURIComponent(category)},${listingTypeClause}`,
15
- sorts: "-createdAt",
15
+ filters: sieveAnd(sieveFilter("status", SIEVE_OP.EQ, "published"), sieveFilter("category", SIEVE_OP.EQ, encodeURIComponent(category)), listingTypeClause),
16
+ sorts: sortBy("createdAt", "DESC"),
16
17
  pageSize: String(limit),
17
18
  });
18
19
  return useQuery({
@@ -9,13 +9,13 @@ export declare function usePromotions(params?: PromotionsListParams, opts?: {
9
9
  hasMore: boolean;
10
10
  isLoading: boolean;
11
11
  error: string | null;
12
- refetch: (options?: import("@tanstack/query-core").RefetchOptions) => Promise<import("@tanstack/query-core").QueryObserverResult<NoInfer<PromotionsListResponse>, Error>>;
12
+ refetch: (options?: import("@tanstack/query-core").RefetchOptions) => Promise<import("@tanstack/query-core").QueryObserverResult<PromotionsListResponse, Error>>;
13
13
  };
14
14
  export declare function useCoupon(code: string, opts?: {
15
15
  enabled?: boolean;
16
16
  endpoint?: string;
17
17
  }): {
18
- coupon: NonNullable<NoInfer<CouponItem | null>> | null;
18
+ coupon: CouponItem | null;
19
19
  isLoading: boolean;
20
20
  error: string | null;
21
21
  };
@@ -4,6 +4,7 @@
4
4
  * Pure business functions for review create/update/delete/vote and query flows.
5
5
  * Auth and rate-limit stay in consumer server-action wrappers.
6
6
  */
7
+ import { sortBy } from "@mohasinac/appkit";
7
8
  import { AuthorizationError, NotFoundError, ValidationError, } from "../../../errors";
8
9
  import { serverLogger } from "../../../monitoring";
9
10
  import { maskPublicReview } from "../../../security";
@@ -144,7 +145,7 @@ export async function voteReviewHelpful(reviewId, helpful) {
144
145
  }
145
146
  export async function listReviewsByProduct(productId, page = 1, pageSize = 10) {
146
147
  const result = await reviewRepository.listForProduct(productId, {
147
- sorts: "-createdAt",
148
+ sorts: sortBy("createdAt", "DESC"),
148
149
  page,
149
150
  pageSize,
150
151
  });
@@ -89,9 +89,15 @@ export function ReviewDetailShell({ review, storeHref }) {
89
89
  ? String(ROUTES.PUBLIC.PROFILE(reviewerProfileId))
90
90
  : null;
91
91
  const currentImage = lightboxIdx !== null ? images[lightboxIdx] : null;
92
- return (_jsxs(_Fragment, { children: [_jsx(Div, { surface: "default", className: "border-b border-neutral-200 dark:border-zinc-800 pb-8 pt-10", children: _jsxs(Div, { className: "mx-auto max-w-3xl px-4", children: [_jsxs(Row, { gap: "sm", className: "mb-4", children: [_jsx(StarRating, { value: review.rating, size: "lg", readOnly: true }), _jsxs(Span, { weight: "bold", className: "text-2xl text-neutral-900 dark:text-white", children: [review.rating, ".0"] }), review.verified && (_jsx(Span, { size: "xs", weight: "semibold", className: "inline-flex items-center gap-1 rounded-full bg-success-surface px-3 py-1 text-success", children: "\u2713 Verified Purchase" })), review.featured && (_jsx(Span, { size: "xs", weight: "semibold", className: CLS_RATING_PILL, children: "\u2605 Featured" }))] }), review.title && (_jsx(Heading, { level: 1, className: "text-2xl font-bold text-neutral-900 dark:text-white mb-4 leading-snug", children: review.title })), _jsxs(Row, { gap: "sm", children: [review.userAvatar ? (_jsx(Div, { role: "img", "aria-label": displayName, className: "h-11 w-11 flex-shrink-0 rounded-full bg-center bg-cover ring-2 ring-white dark:ring-zinc-800", style: { backgroundImage: `url(${review.userAvatar})` } })) : (_jsx(Row, { centered: true, className: "h-11 w-11 flex-shrink-0 rounded-full bg-primary/10 text-base font-bold text-primary ring-2 ring-white dark:ring-zinc-800", children: initials })), _jsxs(Div, { className: "min-w-0", children: [reviewerHref ? (_jsx(Link, { href: reviewerHref, className: "text-sm font-semibold text-neutral-900 dark:text-white hover:text-primary transition-colors", children: displayName })) : (_jsx(Span, { size: "sm", weight: "semibold", className: "text-neutral-900 dark:text-white", children: review.isAnonymous ? "Anonymous" : displayName })), date && (_jsx(Text, { size: "xs", color: "muted", className: "mt-0.5", children: date }))] })] })] }) }), _jsxs(Stack, { gap: "xl", className: "mx-auto max-w-3xl px-4 py-8", children: [review.comment && (_jsx(Section, { children: _jsx(RichText, { html: normalizeRichTextHtml(review.comment), proseClass: "prose prose-neutral dark:prose-invert max-w-none prose-p:leading-relaxed prose-headings:font-semibold prose-img:rounded-lg prose-a:text-primary", className: "text-neutral-700 dark:text-zinc-300" }) })), images.length > 0 && (_jsxs(Section, { children: [_jsxs(Heading, { level: 2, className: "text-sm font-semibold uppercase tracking-wide text-zinc-400 dark:text-zinc-400 mb-3", children: ["Photos (", images.length, ")"] }), _jsx(Grid, { gap: "xs", className: "grid-cols-3 sm:grid-cols-4", children: images.map((img, i) => (_jsxs("button", { type: "button", onClick: () => setLightboxIdx(i), "aria-label": `View photo ${i + 1}`, className: "group relative aspect-square overflow-hidden rounded-xl border border-neutral-200 dark:border-zinc-700 bg-neutral-100 dark:bg-zinc-800 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary", children: [_jsx(Div, { className: "h-full w-full bg-center bg-cover transition-transform duration-300 group-hover:scale-105", style: { backgroundImage: `url(${img.thumbnailUrl ?? img.url})` } }), _jsx(Row, { centered: true, className: "absolute inset-0 opacity-0 group-hover:opacity-100 transition-opacity bg-black/30", children: _jsx(Span, { size: "xl", className: "text-white", children: "\uD83D\uDD0D" }) })] }, i))) })] })), review.video && (_jsxs(Section, { children: [_jsx(Heading, { level: 2, className: "text-sm font-semibold uppercase tracking-wide text-zinc-400 dark:text-zinc-400 mb-3", children: "Video" }), _jsx(Div, { className: `${__O.hidden} rounded-xl border border-neutral-200 dark:border-zinc-700 bg-black aspect-video`, children: _jsx("video", { src: review.video.url, poster: review.video.thumbnailUrl, controls: true, className: "h-full w-full", preload: "metadata" }) })] })), _jsxs(Section, { className: "flex items-center gap-4 py-4 border-t border-neutral-100 dark:border-zinc-800", children: [_jsx(Div, { className: "text-sm text-neutral-500 dark:text-zinc-400", children: helpfulCount > 0 && (_jsxs(Span, { children: [_jsx(Span, { weight: "bold", className: "text-neutral-900 dark:text-white", children: helpfulCount }), " ", helpfulCount === 1 ? "person" : "people", " found this helpful"] })) }), _jsxs("button", { type: "button", onClick: handleVote, disabled: voted || voting, className: `ml-auto flex items-center gap-2 rounded-lg border px-4 py-2 text-sm font-medium transition-colors ${voted
92
+ return (_jsxs(_Fragment, { children: [_jsx(Div, { surface: "default", className: "border-b border-neutral-200 dark:border-zinc-800 pb-8 pt-10", children: _jsxs(Div, { className: "mx-auto max-w-3xl px-4", children: [_jsxs(Row, { gap: "sm", className: "mb-4", children: [_jsx(StarRating, { value: review.rating, size: "lg", readOnly: true }), _jsxs(Span, { weight: "bold", className: "text-2xl text-neutral-900 dark:text-white", children: [review.rating, ".0"] }), review.verified && (_jsx(Span, { size: "xs", weight: "semibold", className: "inline-flex items-center gap-1 rounded-full bg-success-surface px-3 py-1 text-success", children: "\u2713 Verified Purchase" })), review.featured && (_jsx(Span, { size: "xs", weight: "semibold", className: CLS_RATING_PILL, children: "\u2605 Featured" }))] }), review.title && (_jsx(Heading, { level: 1, className: "text-2xl font-bold text-neutral-900 dark:text-white mb-4 leading-snug", children: review.title })), _jsxs(Row, { gap: "sm", children: [review.userAvatar ? (_jsx(Div, { role: "img", "aria-label": displayName, className: "h-11 w-11 flex-shrink-0 rounded-full bg-center bg-cover ring-2 ring-white dark:ring-zinc-800",
93
+ // audit-inline-style-ok: dynamic image URL
94
+ style: { backgroundImage: `url(${review.userAvatar})` } })) : (_jsx(Row, { centered: true, className: "h-11 w-11 flex-shrink-0 rounded-full bg-primary/10 text-base font-bold text-primary ring-2 ring-white dark:ring-zinc-800", children: initials })), _jsxs(Div, { className: "min-w-0", children: [reviewerHref ? (_jsx(Link, { href: reviewerHref, className: "text-sm font-semibold text-neutral-900 dark:text-white hover:text-primary transition-colors", children: displayName })) : (_jsx(Span, { size: "sm", weight: "semibold", className: "text-neutral-900 dark:text-white", children: review.isAnonymous ? "Anonymous" : displayName })), date && (_jsx(Text, { size: "xs", color: "muted", className: "mt-0.5", children: date }))] })] })] }) }), _jsxs(Stack, { gap: "xl", className: "mx-auto max-w-3xl px-4 py-8", children: [review.comment && (_jsx(Section, { children: _jsx(RichText, { html: normalizeRichTextHtml(review.comment), proseClass: "prose prose-neutral dark:prose-invert max-w-none prose-p:leading-relaxed prose-headings:font-semibold prose-img:rounded-lg prose-a:text-primary", className: "text-neutral-700 dark:text-zinc-300" }) })), images.length > 0 && (_jsxs(Section, { children: [_jsxs(Heading, { level: 2, className: "text-sm font-semibold uppercase tracking-wide text-zinc-400 dark:text-zinc-400 mb-3", children: ["Photos (", images.length, ")"] }), _jsx(Grid, { gap: "xs", className: "grid-cols-3 sm:grid-cols-4", children: images.map((img, i) => (_jsxs("button", { type: "button", onClick: () => setLightboxIdx(i), "aria-label": `View photo ${i + 1}`, className: "group relative aspect-square overflow-hidden rounded-xl border border-neutral-200 dark:border-zinc-700 bg-neutral-100 dark:bg-zinc-800 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary", children: [_jsx(Div, { className: "h-full w-full bg-center bg-cover transition-transform duration-300 group-hover:scale-105",
95
+ // audit-inline-style-ok: dynamic image URL
96
+ style: { backgroundImage: `url(${img.thumbnailUrl ?? img.url})` } }), _jsx(Row, { centered: true, className: "absolute inset-0 opacity-0 group-hover:opacity-100 transition-opacity bg-black/30", children: _jsx(Span, { size: "xl", className: "text-white", children: "\uD83D\uDD0D" }) })] }, i))) })] })), review.video && (_jsxs(Section, { children: [_jsx(Heading, { level: 2, className: "text-sm font-semibold uppercase tracking-wide text-zinc-400 dark:text-zinc-400 mb-3", children: "Video" }), _jsx(Div, { className: `${__O.hidden} rounded-xl border border-neutral-200 dark:border-zinc-700 bg-black aspect-video`, children: _jsx("video", { src: review.video.url, poster: review.video.thumbnailUrl, controls: true, className: "h-full w-full", preload: "metadata" }) })] })), _jsxs(Section, { className: "flex items-center gap-4 py-4 border-t border-neutral-100 dark:border-zinc-800", children: [_jsx(Div, { className: "text-sm text-neutral-500 dark:text-zinc-400", children: helpfulCount > 0 && (_jsxs(Span, { children: [_jsx(Span, { weight: "bold", className: "text-neutral-900 dark:text-white", children: helpfulCount }), " ", helpfulCount === 1 ? "person" : "people", " found this helpful"] })) }), _jsxs("button", { type: "button", onClick: handleVote, disabled: voted || voting, className: `ml-auto flex items-center gap-2 rounded-lg border px-4 py-2 text-sm font-medium transition-colors ${voted
93
97
  ? CLS_HELPFUL_ACTIVE
94
98
  : "border-neutral-300 dark:border-zinc-600 text-neutral-700 dark:text-zinc-200 hover:border-primary hover:text-primary dark:hover:border-primary dark:hover:text-primary disabled:opacity-50"}`, children: [_jsx("span", { "aria-hidden": "true", children: voted ? "✓" : "👍" }), voted ? "Marked helpful" : voting ? "Saving…" : "Helpful?"] })] }), _jsxs(Section, { className: "grid gap-3 sm:grid-cols-3", children: [productHref && (_jsxs(Link, { href: productHref, className: CLS_RELATED_LINK, children: [_jsx("span", { className: CLS_ICON_ORANGE, children: "\uD83D\uDCE6" }), _jsxs(Div, { className: "min-w-0", children: [_jsx(Text, { className: CLS_RELATED_LABEL, children: "Product" }), _jsx(Text, { className: CLS_RELATED_TITLE, children: review.productTitle ?? "View Product" })] })] })), sellerHref && (_jsxs(Link, { href: sellerHref, className: CLS_RELATED_LINK, children: [_jsx("span", { className: CLS_ICON_BLUE, children: "\uD83C\uDFEA" }), _jsxs(Div, { className: "min-w-0", children: [_jsx(Text, { className: CLS_RELATED_LABEL, children: "Seller" }), _jsx(Text, { className: CLS_RELATED_TITLE, children: "View Seller" })] })] })), reviewerHref ? (_jsxs(Link, { href: reviewerHref, className: CLS_RELATED_LINK, children: [_jsx("span", { className: CLS_ICON_PURPLE, children: "\uD83D\uDC64" }), _jsxs(Div, { className: "min-w-0", children: [_jsx(Text, { className: CLS_RELATED_LABEL, children: "Reviewer" }), _jsx(Text, { className: CLS_RELATED_TITLE, children: displayName })] })] })) : (_jsxs(Row, { surface: "default", gap: "sm", className: `rounded-xl border border-neutral-200 dark:border-zinc-700 ${__P.p4}`, children: [_jsx(Span, { size: "xl", className: CLS_ICON_PURPLE_BARE, children: "\uD83D\uDC64" }), _jsxs(Div, { className: "min-w-0", children: [_jsx(Span, { size: "xs", className: "block text-zinc-400 dark:text-zinc-400 mb-0.5", children: "Reviewer" }), _jsx(Span, { size: "sm", weight: "medium", className: "block text-neutral-900 dark:text-white truncate", children: "Anonymous" })] })] }))] })] }), lightboxIdx !== null && currentImage && (_jsxs(Row, { centered: true, className: "fixed inset-0 z-50 bg-black/95", onClick: closeLightbox, role: "dialog", "aria-modal": "true", "aria-label": "Image lightbox", children: [_jsx("button", { type: "button", onClick: closeLightbox, "aria-label": "Close lightbox", className: "absolute top-4 right-4 z-10 flex h-10 w-10 items-center justify-center rounded-full bg-white/10 text-white hover:bg-white/20 transition-colors text-xl", children: "\u00D7" }), _jsxs(Div, { className: "absolute top-4 left-1/2 -translate-x-1/2 text-white/70 text-sm", children: [lightboxIdx + 1, " / ", images.length] }), images.length > 1 && (_jsx("button", { type: "button", onClick: (e) => { e.stopPropagation(); prevImage(); }, "aria-label": "Previous image", className: "absolute left-4 top-1/2 -translate-y-1/2 z-10 flex h-12 w-12 items-center justify-center rounded-full bg-white/10 text-white hover:bg-white/25 transition-colors text-2xl", children: "\u2039" })), _jsx(Row, { centered: true, className: "max-h-[85vh] max-w-[85vw]", onClick: (e) => e.stopPropagation(), children: _jsx("img", { src: currentImage.url, alt: `Review photo ${lightboxIdx + 1}`, className: "max-h-[85vh] max-w-[85vw] rounded-lg object-contain shadow-2xl" }) }), images.length > 1 && (_jsx("button", { type: "button", onClick: (e) => { e.stopPropagation(); nextImage(); }, "aria-label": "Next image", className: "absolute right-4 top-1/2 -translate-y-1/2 z-10 flex h-12 w-12 items-center justify-center rounded-full bg-white/10 text-white hover:bg-white/25 transition-colors text-2xl", children: "\u203A" })), images.length > 1 && (_jsx(Row, { justify: "center", gap: "xs", className: "absolute bottom-4 left-0 right-0 px-4", children: images.map((img, i) => (_jsx("button", { type: "button", onClick: (e) => { e.stopPropagation(); setLightboxIdx(i); }, "aria-label": `Go to image ${i + 1}`, className: `h-12 w-12 flex-shrink-0 rounded-md bg-center bg-cover border-2 transition-all ${i === lightboxIdx
95
99
  ? "border-white scale-110"
96
- : "border-transparent opacity-60 hover:opacity-100"}`, style: { backgroundImage: `url(${img.thumbnailUrl ?? img.url})` } }, i))) }))] }))] }));
100
+ : "border-transparent opacity-60 hover:opacity-100"}`,
101
+ // audit-inline-style-ok: dynamic image URL
102
+ style: { backgroundImage: `url(${img.thumbnailUrl ?? img.url})` } }, i))) }))] }))] }));
97
103
  }
@@ -23,7 +23,11 @@ export function ViewReviewModal({ review, isOpen, onClose, }) {
23
23
  day: "numeric",
24
24
  })
25
25
  : "";
26
- return (_jsx(Modal, { isOpen: isOpen, onClose: onClose, title: "Review Details", children: _jsxs(Div, { className: "space-y-4", children: [_jsxs(Row, { className: "gap-3", children: [review.userAvatar ? (_jsx(Div, { role: "img", "aria-label": displayName, className: "h-10 w-10 flex-shrink-0 rounded-full bg-center bg-cover", style: { backgroundImage: `url(${review.userAvatar})` } })) : (_jsx(Div, { className: "flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-full bg-neutral-200 text-sm font-medium text-neutral-600 dark:bg-zinc-700 dark:text-zinc-300", children: displayName.charAt(0).toUpperCase() })), _jsxs(Div, { children: [_jsxs(Row, { className: "gap-2", children: [_jsx(Text, { className: "font-medium text-neutral-900 dark:text-white", children: displayName }), review.verified && (_jsx(Text, { className: `text-xs ${THEME_CONSTANTS.themed.textSuccess}`, children: "\u2713 Verified purchase" }))] }), _jsxs(Row, { className: "gap-2 mt-0.5", children: [_jsx(StarRating, { value: review.rating, size: "sm", readOnly: true }), date && (_jsx(Text, { className: "text-xs text-zinc-400 dark:text-zinc-400", children: date }))] })] })] }), review.title && (_jsx(Heading, { level: 4, className: "font-semibold text-neutral-900 dark:text-white", children: review.title })), review.comment && (_jsx(RichText, { html: normalizeRichTextHtml(review.comment), proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "text-sm leading-relaxed text-neutral-600 dark:text-zinc-400" })), review.images && review.images.length > 0 && (_jsx(Row, { wrap: true, gap: "sm", children: review.images.map((img, i) => (_jsx(IconButton, { "aria-label": `View image ${i + 1}`, onClick: () => setLightboxIdx(i), variant: "ghost", className: `h-20 w-20 rounded-lg border bg-center bg-cover transition hover:opacity-80 p-0 ${lightboxIdx === i ? "ring-2 ring-primary-500" : "border-neutral-100 dark:border-zinc-700"}`, style: {
26
+ return (_jsx(Modal, { isOpen: isOpen, onClose: onClose, title: "Review Details", children: _jsxs(Div, { className: "space-y-4", children: [_jsxs(Row, { className: "gap-3", children: [review.userAvatar ? (_jsx(Div, { role: "img", "aria-label": displayName, className: "h-10 w-10 flex-shrink-0 rounded-full bg-center bg-cover",
27
+ // audit-inline-style-ok: dynamic image URL
28
+ style: { backgroundImage: `url(${review.userAvatar})` } })) : (_jsx(Div, { className: "flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-full bg-neutral-200 text-sm font-medium text-neutral-600 dark:bg-zinc-700 dark:text-zinc-300", children: displayName.charAt(0).toUpperCase() })), _jsxs(Div, { children: [_jsxs(Row, { className: "gap-2", children: [_jsx(Text, { className: "font-medium text-neutral-900 dark:text-white", children: displayName }), review.verified && (_jsx(Text, { className: `text-xs ${THEME_CONSTANTS.themed.textSuccess}`, children: "\u2713 Verified purchase" }))] }), _jsxs(Row, { className: "gap-2 mt-0.5", children: [_jsx(StarRating, { value: review.rating, size: "sm", readOnly: true }), date && (_jsx(Text, { className: "text-xs text-zinc-400 dark:text-zinc-400", children: date }))] })] })] }), review.title && (_jsx(Heading, { level: 4, className: "font-semibold text-neutral-900 dark:text-white", children: review.title })), review.comment && (_jsx(RichText, { html: normalizeRichTextHtml(review.comment), proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "text-sm leading-relaxed text-neutral-600 dark:text-zinc-400" })), review.images && review.images.length > 0 && (_jsx(Row, { wrap: true, gap: "sm", children: review.images.map((img, i) => (_jsx(IconButton, { "aria-label": `View image ${i + 1}`, onClick: () => setLightboxIdx(i), variant: "ghost", className: `h-20 w-20 rounded-lg border bg-center bg-cover transition hover:opacity-80 p-0 ${lightboxIdx === i ? "ring-2 ring-primary-500" : "border-neutral-100 dark:border-zinc-700"}`,
29
+ // audit-inline-style-ok: dynamic CSS
30
+ style: {
27
31
  backgroundImage: `url(${img.thumbnailUrl ?? img.url})`,
28
32
  }, icon: _jsx(Div, {}) }, i))) })), (review.helpfulCount ?? 0) > 0 && (_jsxs(Text, { className: "text-xs text-zinc-400 dark:text-zinc-400", children: [review.helpfulCount, " people found this helpful"] })), _jsx(Div, { className: "flex justify-end pt-2", children: _jsx(Button, { variant: "ghost", onClick: onClose, children: "Close" }) })] }) }));
29
33
  }
@@ -16,6 +16,8 @@ export function ReviewSummary({ averageRating, totalReviews, distribution, class
16
16
  return (_jsxs(Div, { className: `flex flex-col gap-4 sm:flex-row sm:items-center sm:gap-8 ${className}`, children: [_jsxs(Div, { className: "flex flex-col items-center gap-1 sm:items-start", children: [_jsx(Span, { weight: "bold", className: "text-5xl leading-none text-neutral-900 dark:text-white", children: averageRating.toFixed(1) }), _jsx(StarRating, { value: averageRating, size: "md", readOnly: true }), _jsxs(Text, { size: "sm", className: "text-neutral-500 dark:text-zinc-400", children: [totalReviews.toLocaleString(), " review", totalReviews !== 1 ? "s" : ""] })] }), _jsx(Div, { className: "flex flex-1 flex-col gap-2", children: stars.map((star) => {
17
17
  const count = distribution[star] ?? 0;
18
18
  const pct = totalReviews > 0 ? Math.round((count / totalReviews) * 100) : 0;
19
- return (_jsxs(Div, { className: "flex items-center gap-2", children: [_jsx(Span, { size: "xs", weight: "medium", className: "w-4 text-right text-neutral-600 dark:text-zinc-400 tabular-nums", children: star }), _jsx(Span, { size: "xs", className: CLS_STAR, children: "\u2605" }), _jsx(Div, { className: "flex-1 h-2 rounded-full bg-neutral-100 dark:bg-zinc-800 overflow-hidden", role: "progressbar", "aria-valuenow": pct, "aria-valuemin": 0, "aria-valuemax": 100, "aria-label": `${star} star: ${pct}%`, children: _jsx(Div, { className: CLS_BAR, style: { width: `${pct}%` } }) }), _jsxs(Span, { size: "xs", className: "w-8 text-right text-zinc-400 dark:text-zinc-400 tabular-nums", children: [pct, "%"] })] }, star));
19
+ return (_jsxs(Div, { className: "flex items-center gap-2", children: [_jsx(Span, { size: "xs", weight: "medium", className: "w-4 text-right text-neutral-600 dark:text-zinc-400 tabular-nums", children: star }), _jsx(Span, { size: "xs", className: CLS_STAR, children: "\u2605" }), _jsx(Div, { className: "flex-1 h-2 rounded-full bg-neutral-100 dark:bg-zinc-800 overflow-hidden", role: "progressbar", "aria-valuenow": pct, "aria-valuemin": 0, "aria-valuemax": 100, "aria-label": `${star} star: ${pct}%`, children: _jsx(Div, { className: CLS_BAR,
20
+ // audit-inline-style-ok: computed percentage
21
+ style: { width: `${pct}%` } }) }), _jsxs(Span, { size: "xs", className: "w-8 text-right text-zinc-400 dark:text-zinc-400 tabular-nums", children: [pct, "%"] })] }, star));
20
22
  }) })] }));
21
23
  }
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { sieveFilter, SIEVE_OP } from "@mohasinac/appkit";
2
3
  import { reviewRepository } from "../../../repositories";
3
4
  import { Container, Heading, Main, Section } from "../../../ui";
4
5
  import { AdSlot } from "../../homepage/components/AdSlot";
@@ -13,22 +14,22 @@ function buildReviewFilters(params) {
13
14
  if (rating) {
14
15
  const values = rating.split("|").filter(Boolean);
15
16
  if (values.length === 1)
16
- parts.push(`rating==${values[0]}`);
17
+ parts.push(sieveFilter("rating", SIEVE_OP.EQ, values[0]));
17
18
  else if (values.length > 1)
18
- parts.push(`rating==${values.join("|")}`);
19
+ parts.push(sieveFilter("rating", SIEVE_OP.EQ, values.join("|")));
19
20
  }
20
21
  const minVotes = sp(params, "minVotes");
21
22
  if (minVotes)
22
- parts.push(`helpfulCount>=${minVotes}`);
23
+ parts.push(sieveFilter("helpfulCount", SIEVE_OP.GTE, minVotes));
23
24
  const maxVotes = sp(params, "maxVotes");
24
25
  if (maxVotes)
25
- parts.push(`helpfulCount<=${maxVotes}`);
26
+ parts.push(sieveFilter("helpfulCount", SIEVE_OP.LTE, maxVotes));
26
27
  const dateFrom = sp(params, "dateFrom");
27
28
  const dateTo = sp(params, "dateTo");
28
29
  if (dateFrom)
29
- parts.push(`createdAt>=${dateFrom}`);
30
+ parts.push(sieveFilter("createdAt", SIEVE_OP.GTE, dateFrom));
30
31
  if (dateTo)
31
- parts.push(`createdAt<=${dateTo}`);
32
+ parts.push(sieveFilter("createdAt", SIEVE_OP.LTE, dateTo));
32
33
  const q = sp(params, "q");
33
34
  if (q)
34
35
  parts.push(`productTitle@=*${q}`);
@@ -28,7 +28,11 @@ export function ReviewCard({ review, context = "general", className = "" }) {
28
28
  const showProductLink = context !== "listing" && !!productHref;
29
29
  const showProfileLink = !!profileHref;
30
30
  const hasFooter = showStoreLink || showProductLink || showProfileLink;
31
- return (_jsxs(Div, { className: `group flex flex-col h-full rounded-xl border border-neutral-200 bg-white p-5 dark:border-zinc-700 dark:bg-zinc-900 transition-shadow hover:shadow-md ${className}`, children: [_jsxs(Link, { href: reviewHref, className: "flex flex-col flex-1 min-h-0", children: [_jsxs(Div, { className: "flex items-start gap-3", children: [review.userAvatar ? (_jsx(Div, { role: "img", "aria-label": displayName, className: "h-9 w-9 flex-shrink-0 rounded-full bg-center bg-cover", style: { backgroundImage: `url(${review.userAvatar})` } })) : (_jsx(Div, { className: "flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-full bg-neutral-200 text-sm font-medium text-neutral-600 dark:bg-zinc-700 dark:text-zinc-300", children: initials })), _jsxs(Div, { className: "flex-1 min-w-0", children: [_jsxs(Row, { wrap: true, gap: "sm", children: [_jsx(Span, { weight: "medium", className: "text-neutral-900 dark:text-white", children: displayName }), review.verified && (_jsx(Span, { size: "xs", weight: "medium", className: "rounded-full bg-success-surface px-2 py-0.5 text-success", children: "Verified" })), date && (_jsx(Span, { size: "xs", className: "text-zinc-400 dark:text-zinc-400", children: date }))] }), _jsx(Div, { className: "mt-1", children: _jsx(StarRating, { value: review.rating, size: "sm", readOnly: true }) })] })] }), review.title && (_jsx(Heading, { level: 4, className: "mt-3 font-semibold text-neutral-900 dark:text-white", children: review.title })), review.comment && (_jsx(RichText, { html: normalizeRichTextHtml(review.comment), proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "mt-2 text-sm leading-relaxed text-neutral-600 dark:text-zinc-400" })), review.images && review.images.length > 0 && (_jsx(Row, { wrap: true, gap: "sm", className: "mt-3", children: review.images.map((img, i) => (_jsx(Div, { role: "img", "aria-label": `Review image ${i + 1}`, className: "h-16 w-16 rounded-lg bg-center bg-cover border border-neutral-100 dark:border-zinc-700", style: { backgroundImage: `url(${img.thumbnailUrl ?? img.url})` } }, i))) })), (review.helpfulCount ?? 0) > 0 && (_jsxs(Text, { className: "mt-3 text-xs text-zinc-400 dark:text-zinc-400", children: [review.helpfulCount, " found this helpful"] }))] }), hasFooter && (_jsxs(Div, { className: "mt-3 pt-3 border-t border-neutral-100 dark:border-zinc-800 flex flex-col gap-1.5", children: [showStoreLink && (_jsxs(Link, { href: String(ROUTES.PUBLIC.STORE_DETAIL(review.storeSlug)), className: "flex items-center gap-1.5 text-xs font-medium text-primary hover:underline", children: [_jsx("span", { "aria-hidden": "true", children: "\uD83C\uDFEA" }), _jsx("span", { className: THEME_CONSTANTS.utilities.textClamp1, children: review.storeName })] })), showProductLink && (_jsxs(Link, { href: productHref, className: "flex items-center gap-1.5 text-xs font-medium text-neutral-500 dark:text-zinc-400 hover:text-neutral-700 dark:hover:text-zinc-200", children: [_jsx("span", { "aria-hidden": "true", children: "\uD83D\uDCE6" }), _jsx("span", { className: THEME_CONSTANTS.utilities.textClamp1, children: review.productTitle ?? "View Product" }), _jsx("span", { "aria-hidden": "true", className: "ml-auto text-primary group-hover:translate-x-0.5 transition-transform", children: "\u2192" })] })), showProfileLink && (_jsxs(Link, { href: profileHref, className: "flex items-center gap-1.5 text-xs font-medium text-neutral-500 dark:text-zinc-400 hover:text-neutral-700 dark:hover:text-zinc-200", children: [_jsx("span", { "aria-hidden": "true", children: "\uD83D\uDC64" }), _jsx("span", { className: THEME_CONSTANTS.utilities.textClamp1, children: displayName })] }))] }))] }));
31
+ return (_jsxs(Div, { className: `group flex flex-col h-full rounded-xl border border-neutral-200 bg-white p-5 dark:border-zinc-700 dark:bg-zinc-900 transition-shadow hover:shadow-md ${className}`, children: [_jsxs(Link, { href: reviewHref, className: "flex flex-col flex-1 min-h-0", children: [_jsxs(Div, { className: "flex items-start gap-3", children: [review.userAvatar ? (_jsx(Div, { role: "img", "aria-label": displayName, className: "h-9 w-9 flex-shrink-0 rounded-full bg-center bg-cover",
32
+ // audit-inline-style-ok: dynamic image URL
33
+ style: { backgroundImage: `url(${review.userAvatar})` } })) : (_jsx(Div, { className: "flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-full bg-neutral-200 text-sm font-medium text-neutral-600 dark:bg-zinc-700 dark:text-zinc-300", children: initials })), _jsxs(Div, { className: "flex-1 min-w-0", children: [_jsxs(Row, { wrap: true, gap: "sm", children: [_jsx(Span, { weight: "medium", className: "text-neutral-900 dark:text-white", children: displayName }), review.verified && (_jsx(Span, { size: "xs", weight: "medium", className: "rounded-full bg-success-surface px-2 py-0.5 text-success", children: "Verified" })), date && (_jsx(Span, { size: "xs", className: "text-zinc-400 dark:text-zinc-400", children: date }))] }), _jsx(Div, { className: "mt-1", children: _jsx(StarRating, { value: review.rating, size: "sm", readOnly: true }) })] })] }), review.title && (_jsx(Heading, { level: 4, className: "mt-3 font-semibold text-neutral-900 dark:text-white", children: review.title })), review.comment && (_jsx(RichText, { html: normalizeRichTextHtml(review.comment), proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "mt-2 text-sm leading-relaxed text-neutral-600 dark:text-zinc-400" })), review.images && review.images.length > 0 && (_jsx(Row, { wrap: true, gap: "sm", className: "mt-3", children: review.images.map((img, i) => (_jsx(Div, { role: "img", "aria-label": `Review image ${i + 1}`, className: "h-16 w-16 rounded-lg bg-center bg-cover border border-neutral-100 dark:border-zinc-700",
34
+ // audit-inline-style-ok: dynamic image URL
35
+ style: { backgroundImage: `url(${img.thumbnailUrl ?? img.url})` } }, i))) })), (review.helpfulCount ?? 0) > 0 && (_jsxs(Text, { className: "mt-3 text-xs text-zinc-400 dark:text-zinc-400", children: [review.helpfulCount, " found this helpful"] }))] }), hasFooter && (_jsxs(Div, { className: "mt-3 pt-3 border-t border-neutral-100 dark:border-zinc-800 flex flex-col gap-1.5", children: [showStoreLink && (_jsxs(Link, { href: String(ROUTES.PUBLIC.STORE_DETAIL(review.storeSlug)), className: "flex items-center gap-1.5 text-xs font-medium text-primary hover:underline", children: [_jsx("span", { "aria-hidden": "true", children: "\uD83C\uDFEA" }), _jsx("span", { className: THEME_CONSTANTS.utilities.textClamp1, children: review.storeName })] })), showProductLink && (_jsxs(Link, { href: productHref, className: "flex items-center gap-1.5 text-xs font-medium text-neutral-500 dark:text-zinc-400 hover:text-neutral-700 dark:hover:text-zinc-200", children: [_jsx("span", { "aria-hidden": "true", children: "\uD83D\uDCE6" }), _jsx("span", { className: THEME_CONSTANTS.utilities.textClamp1, children: review.productTitle ?? "View Product" }), _jsx("span", { "aria-hidden": "true", className: "ml-auto text-primary group-hover:translate-x-0.5 transition-transform", children: "\u2192" })] })), showProfileLink && (_jsxs(Link, { href: profileHref, className: "flex items-center gap-1.5 text-xs font-medium text-neutral-500 dark:text-zinc-400 hover:text-neutral-700 dark:hover:text-zinc-200", children: [_jsx("span", { "aria-hidden": "true", children: "\uD83D\uDC64" }), _jsx("span", { className: THEME_CONSTANTS.utilities.textClamp1, children: displayName })] }))] }))] }));
32
36
  }
33
37
  export function ReviewsList({ reviews, context = "general", isLoading, totalPages = 1, currentPage = 1, onPageChange, emptyLabel = "No reviews yet", }) {
34
38
  if (isLoading) {
@@ -14,7 +14,7 @@ export declare function useReviews(params?: ReviewListParams, opts?: UseReviewsO
14
14
  ratingDistribution: Record<number, number> | undefined;
15
15
  isLoading: boolean;
16
16
  error: Error | null;
17
- refetch: (options?: import("@tanstack/query-core").RefetchOptions) => Promise<import("@tanstack/query-core").QueryObserverResult<NoInfer<ReviewListResponse>, Error>>;
17
+ refetch: (options?: import("@tanstack/query-core").RefetchOptions) => Promise<import("@tanstack/query-core").QueryObserverResult<ReviewListResponse, Error>>;
18
18
  };
19
19
  interface UseProductReviewsOptions {
20
20
  page?: number;
@@ -30,6 +30,6 @@ export declare function useProductReviews(productId: string, opts?: UseProductRe
30
30
  ratingDistribution: Record<number, number> | undefined;
31
31
  isLoading: boolean;
32
32
  error: Error | null;
33
- refetch: (options?: import("@tanstack/query-core").RefetchOptions) => Promise<import("@tanstack/query-core").QueryObserverResult<NoInfer<ReviewListResponse>, Error>>;
33
+ refetch: (options?: import("@tanstack/query-core").RefetchOptions) => Promise<import("@tanstack/query-core").QueryObserverResult<ReviewListResponse, Error>>;
34
34
  };
35
35
  export {};
@@ -1,4 +1,5 @@
1
1
  "use server";
2
+ import { sieveFilter, SIEVE_OP } from "@mohasinac/appkit";
2
3
  import { scammerRepository } from "../repository/scammer.repository";
3
4
  /**
4
5
  * List verified scammer profiles for the public registry page.
@@ -12,9 +13,9 @@ export async function listVerifiedScammers(searchParams) {
12
13
  // scamType / scamPlatform filter chips — status==verified is enforced by the base query
13
14
  const filters = [];
14
15
  if (params.scamType)
15
- filters.push(`scamType==${params.scamType}`);
16
+ filters.push(sieveFilter("scamType", SIEVE_OP.EQ, String(params.scamType)));
16
17
  if (params.scamPlatform)
17
- filters.push(`scamPlatform==${params.scamPlatform}`);
18
+ filters.push(sieveFilter("scamPlatform", SIEVE_OP.EQ, String(params.scamPlatform)));
18
19
  const result = await scammerRepository.listVerified({
19
20
  filters: filters.join(",") || undefined,
20
21
  sorts: sort,
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { sortBy } from "@mohasinac/appkit";
2
3
  import Link from "next/link";
3
4
  import { ROUTES } from "../../../next/routing/route-map";
4
5
  const CLS_ICON_SM = "h-3 w-3 shrink-0";
@@ -10,10 +11,10 @@ import { listVerifiedScammers } from "../actions/scam-actions";
10
11
  import { SCAM_PLATFORM_LABELS } from "../schemas/firestore";
11
12
  import { SCAM_TYPES, SCAM_TYPE_LABELS } from "../constants/scam-types";
12
13
  const SORT_OPTIONS = [
13
- { value: "-createdAt", label: "Newest Reports" },
14
- { value: "createdAt", label: "Oldest Reports" },
15
- { value: "-incidentCount", label: "Most Victims" },
16
- { value: "-amountLost", label: "Highest Loss" },
14
+ { value: sortBy("createdAt", "DESC"), label: "Newest Reports" },
15
+ { value: sortBy("createdAt", "ASC"), label: "Oldest Reports" },
16
+ { value: sortBy("incidentCount", "DESC"), label: "Most Victims" },
17
+ { value: sortBy("amountLost", "DESC"), label: "Highest Loss" },
17
18
  ];
18
19
  function sp(params, key) {
19
20
  const v = params[key];