@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.
- package/dist/_internal/client/features/layout/SidebarCollapseToggle.js +2 -0
- package/dist/_internal/shared/listing-types/action-tracker.js +0 -2
- package/dist/client.d.ts +2 -0
- package/dist/client.js +2 -0
- package/dist/core/hooks/useSiteSettings.d.ts +1 -1
- package/dist/core/unit-of-work.js +0 -1
- package/dist/features/account/components/NotificationBell.js +1 -0
- package/dist/features/account/components/UserOffersPanel.js +1 -0
- package/dist/features/account/components/UserSidebar.js +3 -1
- package/dist/features/account/hooks/useAccount.d.ts +2 -2
- package/dist/features/account/hooks/useAddressSelector.d.ts +1 -1
- package/dist/features/account/hooks/useAddresses.d.ts +2 -2
- package/dist/features/account/hooks/useNotifications.d.ts +1 -1
- package/dist/features/account/hooks/useProfile.d.ts +1 -1
- package/dist/features/account/hooks/usePublicProfile.d.ts +2 -2
- package/dist/features/admin/components/AdminAllEventEntriesView.js +5 -3
- package/dist/features/admin/components/AdminBidsView.js +6 -4
- package/dist/features/admin/components/AdminBlogView.js +6 -4
- package/dist/features/admin/components/AdminBrandsView.js +8 -6
- package/dist/features/admin/components/AdminBundlesView.js +10 -8
- package/dist/features/admin/components/AdminCarouselEditorView.js +3 -1
- package/dist/features/admin/components/AdminCartsView.js +6 -4
- package/dist/features/admin/components/AdminCategoriesView.js +7 -5
- package/dist/features/admin/components/AdminClassifiedView.js +4 -3
- package/dist/features/admin/components/AdminContactView.js +6 -4
- package/dist/features/admin/components/AdminCouponsView.js +6 -4
- package/dist/features/admin/components/AdminDigitalCodesView.js +4 -3
- package/dist/features/admin/components/AdminFaqsView.js +5 -3
- package/dist/features/admin/components/AdminFeaturesView.js +5 -4
- package/dist/features/admin/components/AdminGroupedListingsView.js +4 -3
- package/dist/features/admin/components/AdminHistoryView.js +4 -3
- package/dist/features/admin/components/AdminLiveView.js +4 -3
- package/dist/features/admin/components/AdminNewsletterView.js +5 -3
- package/dist/features/admin/components/AdminNotificationsView.js +6 -4
- package/dist/features/admin/components/AdminOrdersView.js +6 -4
- package/dist/features/admin/components/AdminPayoutsView.js +7 -5
- package/dist/features/admin/components/AdminPrizeDrawsView.js +6 -4
- package/dist/features/admin/components/AdminProductsView.js +7 -5
- package/dist/features/admin/components/AdminReturnRequestsView.js +4 -3
- package/dist/features/admin/components/AdminReviewsView.js +8 -6
- package/dist/features/admin/components/AdminScammersView.js +8 -6
- package/dist/features/admin/components/AdminSessionsView.js +6 -4
- package/dist/features/admin/components/AdminSidebar.js +3 -1
- package/dist/features/admin/components/AdminSiteSettingsView.js +3 -1
- package/dist/features/admin/components/AdminStoreAddressesView.js +2 -1
- package/dist/features/admin/components/AdminStoresView.js +6 -4
- package/dist/features/admin/components/AdminSublistingCategoriesView.js +6 -5
- package/dist/features/admin/components/AdminSupportTicketDetailView.js +0 -3
- package/dist/features/admin/components/AdminSupportTicketsView.js +8 -6
- package/dist/features/admin/components/AdminTeamView.js +6 -4
- package/dist/features/admin/components/AdminUsersView.js +6 -4
- package/dist/features/admin/components/AdminWishlistsView.js +4 -3
- package/dist/features/admin/components/DashboardStats.js +5 -1
- package/dist/features/admin/components/analytics/AdminAnalyticsCharts.js +3 -1
- package/dist/features/admin/components/analytics/AdminStatCard.js +5 -1
- package/dist/features/admin/constants/filter-tabs.d.ts +1 -1
- package/dist/features/admin/constants/filter-tabs.js +0 -11
- package/dist/features/admin/hooks/useAdmin.d.ts +1 -1
- package/dist/features/admin/hooks/useAdminSectionsListing.d.ts +1 -1
- package/dist/features/admin/hooks/useChat.d.ts +1 -1
- package/dist/features/admin/hooks/useChat.js +1 -0
- package/dist/features/admin/types/index.d.ts +1 -1
- package/dist/features/auctions/actions/bid-actions.js +4 -2
- package/dist/features/auctions/components/AuctionBidsTable.js +5 -1
- package/dist/features/auctions/components/AuctionsListView.js +7 -6
- package/dist/features/auctions/hooks/useAuctions.d.ts +8 -8
- package/dist/features/auth/hooks/useAuth.d.ts +2 -2
- package/dist/features/auth/hooks/useAuth.js +1 -0
- package/dist/features/blog/actions/blog-actions.js +3 -2
- package/dist/features/blog/components/BlogFeaturedCard.js +3 -1
- package/dist/features/blog/components/BlogListView.js +5 -1
- package/dist/features/blog/components/BlogPostView.js +3 -1
- package/dist/features/cart/components/CartDrawer.js +3 -1
- package/dist/features/cart/hooks/useCart.d.ts +1 -1
- package/dist/features/cart/hooks/useCartQuery.d.ts +1 -1
- package/dist/features/cart/hooks/useCheckoutReadQueries.d.ts +2 -2
- package/dist/features/cart/hooks/useOrder.d.ts +2 -2
- package/dist/features/categories/components/BrandDetailPageView.js +15 -9
- package/dist/features/categories/components/CategoriesIndexListing.js +1 -1
- package/dist/features/categories/components/CategoriesIndexPageView.js +5 -4
- package/dist/features/categories/components/CategoryBundlesListing.js +5 -4
- package/dist/features/categories/components/CategoryDetailPageView.js +15 -11
- package/dist/features/categories/components/CategoryFilters.d.ts +12 -12
- package/dist/features/categories/components/CategoryFilters.js +13 -12
- package/dist/features/categories/components/CategoryGrid.js +5 -1
- package/dist/features/categories/components/CategoryTableColumns.js +6 -2
- package/dist/features/categories/components/CategoryTree.d.ts +1 -1
- package/dist/features/categories/components/CategoryTree.js +4 -2
- package/dist/features/categories/components/ConcernCard.js +3 -1
- package/dist/features/categories/hooks/useCategories.d.ts +3 -3
- package/dist/features/categories/hooks/useCategorySelector.d.ts +3 -3
- package/dist/features/categories/hooks/useCategoryTree.d.ts +1 -1
- package/dist/features/categories/schemas/firestore.d.ts +2 -2
- package/dist/features/checkout/actions/checkout-actions.d.ts +1 -1
- package/dist/features/checkout/actions/checkout-actions.js +1 -1
- package/dist/features/checkout/hooks/useCheckoutApi.d.ts +2 -2
- package/dist/features/classified/components/ClassifiedListView.js +6 -5
- package/dist/features/collections/components/CollectionCard.js +3 -1
- package/dist/features/collections/hooks/useCollections.d.ts +2 -2
- package/dist/features/cron/jobs/auction-expiry.job.d.ts +2 -2
- package/dist/features/cron/jobs/auction-expiry.job.js +2 -1
- package/dist/features/cron/jobs/preorder-reminder.job.d.ts +1 -1
- package/dist/features/cron/jobs/preorder-reminder.job.js +2 -1
- package/dist/features/digital-codes/components/DigitalCodesListView.js +4 -3
- package/dist/features/events/actions/event-actions.js +6 -4
- package/dist/features/events/components/AdminEventEditorView.js +1 -1
- package/dist/features/events/components/AdminEventsView.js +7 -5
- package/dist/features/events/components/EventCard.js +3 -1
- package/dist/features/events/components/EventFilters.d.ts +4 -4
- package/dist/features/events/components/EventFilters.js +2 -2
- package/dist/features/events/components/EventRafflesSection.js +4 -2
- package/dist/features/events/hooks/useEvent.d.ts +3 -3
- package/dist/features/events/hooks/useEvents.d.ts +1 -1
- package/dist/features/faq/actions/faq-actions.js +4 -2
- package/dist/features/faq/components/FAQPageContent.js +2 -1
- package/dist/features/faq/hooks/useFAQs.d.ts +1 -1
- package/dist/features/faq/hooks/useFaqList.d.ts +15 -15
- package/dist/features/forms/Slider.js +3 -1
- package/dist/features/history/hooks/useHistory.js +1 -0
- package/dist/features/homepage/components/AdSlot.js +6 -2
- package/dist/features/homepage/components/AdvertisementBanner.js +3 -1
- package/dist/features/homepage/components/BeforeAfterCard.js +5 -1
- package/dist/features/homepage/components/CustomCardsSection.js +5 -1
- package/dist/features/homepage/components/HowItWorksSection.js +3 -1
- package/dist/features/homepage/components/MarketplaceHomepageView.js +4 -2
- package/dist/features/homepage/components/NewsletterBanner.js +6 -2
- package/dist/features/homepage/components/SecurityHighlightsSection.js +3 -1
- package/dist/features/homepage/components/StatsCounterSection.js +3 -1
- package/dist/features/homepage/components/TrustFeaturesSection.js +3 -1
- package/dist/features/homepage/hooks/useBlogArticles.d.ts +1 -1
- package/dist/features/homepage/hooks/useFeaturedAuctions.d.ts +1 -1
- package/dist/features/homepage/hooks/useFeaturedPreOrders.d.ts +1 -1
- package/dist/features/homepage/hooks/useFeaturedProducts.d.ts +1 -1
- package/dist/features/homepage/hooks/useFeaturedStores.d.ts +1 -1
- package/dist/features/homepage/hooks/useHeroCarousel.d.ts +1 -1
- package/dist/features/homepage/hooks/useHomepageEvents.d.ts +1 -1
- package/dist/features/homepage/hooks/useHomepageReviews.d.ts +1 -1
- package/dist/features/homepage/hooks/useHomepageSections.d.ts +1 -1
- package/dist/features/homepage/hooks/useTopBrands.d.ts +1 -1
- package/dist/features/homepage/hooks/useTopCategories.d.ts +1 -1
- package/dist/features/layout/AppLayoutShell.js +3 -1
- package/dist/features/layout/BottomActions.js +0 -27
- package/dist/features/layout/BottomActionsContext.d.ts +0 -36
- package/dist/features/layout/BottomActionsContext.js +1 -39
- package/dist/features/layout/BottomNavbar.js +1 -1
- package/dist/features/live/components/LiveItemsListView.js +6 -5
- package/dist/features/loyalty/hooks/useLoyaltyBalance.d.ts +1 -1
- package/dist/features/media/MediaLightbox.js +9 -3
- package/dist/features/media/modals/ImageEditor.js +6 -2
- package/dist/features/media/upload/MediaUploadField.js +1 -1
- package/dist/features/messages/hooks/useConversation.js +2 -0
- package/dist/features/messages/hooks/useConversations.js +1 -0
- package/dist/features/orders/components/OrdersList.js +3 -1
- package/dist/features/orders/hooks/useOrders.d.ts +2 -2
- package/dist/features/payments/hooks/usePayments.d.ts +1 -1
- package/dist/features/pre-orders/components/PreOrderDetailPageView.js +1 -1
- package/dist/features/pre-orders/components/PreOrdersListView.js +5 -4
- package/dist/features/pre-orders/hooks/usePreOrders.d.ts +2 -2
- package/dist/features/products/actions/product-actions.js +17 -15
- package/dist/features/products/columns/index.d.ts +1 -1
- package/dist/features/products/columns/index.js +1 -1
- package/dist/features/products/components/CompareOverlay.js +5 -1
- package/dist/features/products/components/FeatureBadge.js +3 -1
- package/dist/features/products/components/MarketplacePrizeDrawCard.js +1 -1
- package/dist/features/products/components/PrizeDrawCollage.js +0 -14
- package/dist/features/products/components/PrizeDrawsListingView.js +5 -4
- package/dist/features/products/components/PrizeDrawsSection.js +4 -2
- package/dist/features/products/components/ProductGrid.js +3 -1
- package/dist/features/products/constants/sieve.d.ts +2 -2
- package/dist/features/products/constants/sieve.js +2 -2
- package/dist/features/products/hooks/useProductDetail.d.ts +1 -1
- package/dist/features/products/hooks/useRelatedProducts.d.ts +1 -1
- package/dist/features/products/hooks/useRelatedProducts.js +5 -4
- package/dist/features/promotions/hooks/usePromotions.d.ts +2 -2
- package/dist/features/reviews/actions/review-actions.js +2 -1
- package/dist/features/reviews/components/ReviewDetailShell.js +8 -2
- package/dist/features/reviews/components/ReviewModal.js +5 -1
- package/dist/features/reviews/components/ReviewSummary.js +3 -1
- package/dist/features/reviews/components/ReviewsIndexPageView.js +7 -6
- package/dist/features/reviews/components/ReviewsList.js +5 -1
- package/dist/features/reviews/hooks/useReviews.d.ts +2 -2
- package/dist/features/scams/actions/scam-actions.js +3 -2
- package/dist/features/scams/components/ScamRegistryView.js +5 -4
- package/dist/features/search/actions/search-actions.js +7 -6
- package/dist/features/search/components/Search.js +3 -1
- package/dist/features/search/hooks/useSearch.d.ts +2 -2
- package/dist/features/seller/components/CouponInlineSelect.js +2 -1
- package/dist/features/seller/components/PhysicalLocationModal.js +1 -1
- package/dist/features/seller/components/SellerAddressesView.js +2 -1
- package/dist/features/seller/components/SellerAuctionsView.js +7 -5
- package/dist/features/seller/components/SellerBidsView.js +8 -4
- package/dist/features/seller/components/SellerBundlesView.js +7 -6
- package/dist/features/seller/components/SellerClassifiedView.js +6 -5
- package/dist/features/seller/components/SellerCouponEditorView.js +3 -1
- package/dist/features/seller/components/SellerCouponsView.js +6 -4
- package/dist/features/seller/components/SellerDigitalCodesView.js +5 -4
- package/dist/features/seller/components/SellerGroupedListingsView.js +5 -4
- package/dist/features/seller/components/SellerLiveView.js +6 -5
- package/dist/features/seller/components/SellerOffersPanel.js +1 -0
- package/dist/features/seller/components/SellerOffersView.js +6 -4
- package/dist/features/seller/components/SellerOrdersView.js +5 -3
- package/dist/features/seller/components/SellerPayoutMethodsView.js +4 -2
- package/dist/features/seller/components/SellerPayoutsView.js +6 -4
- package/dist/features/seller/components/SellerPreOrdersView.js +6 -4
- package/dist/features/seller/components/SellerPrizeDrawsView.js +6 -4
- package/dist/features/seller/components/SellerProductsView.js +14 -8
- package/dist/features/seller/components/SellerReviewsView.js +1 -0
- package/dist/features/seller/components/SellerShippingConfigsView.js +4 -2
- package/dist/features/seller/components/SellerSidebar.js +12 -4
- package/dist/features/seller/components/SellerStoreCategoriesView.js +5 -3
- package/dist/features/seller/components/SellerTemplatesView.js +5 -3
- package/dist/features/seller/components/SublistingInlineSelect.js +2 -1
- package/dist/features/seller/hooks/useSellerPayouts.d.ts +3 -3
- package/dist/features/seller/hooks/useSellerStore.d.ts +6 -6
- package/dist/features/seller/hooks/useSellerStorefront.d.ts +2 -2
- package/dist/features/shell/FormShell.js +10 -2
- package/dist/features/shell/QuickFormDrawer.js +5 -2
- package/dist/features/stores/actions/store-query-actions.js +3 -2
- package/dist/features/stores/components/StoreAuctionsPageView.js +4 -2
- package/dist/features/stores/components/StoreDetailLayoutView.d.ts +1 -1
- package/dist/features/stores/components/StoreDetailLayoutView.js +7 -6
- package/dist/features/stores/components/StoreHeader.js +5 -1
- package/dist/features/stores/components/StorePreOrdersPageView.js +4 -2
- package/dist/features/stores/components/StorePrizeDrawsPageView.js +4 -2
- package/dist/features/stores/components/StoreProductsPageView.js +2 -1
- package/dist/features/stores/components/StoreProductsView.js +5 -4
- package/dist/features/stores/components/StoresIndexListing.js +4 -3
- package/dist/features/stores/hooks/useStoreAddressSelector.d.ts +1 -1
- package/dist/features/stores/hooks/useStores.d.ts +6 -6
- package/dist/features/wishlist/components/WishlistPage.js +3 -1
- package/dist/features/wishlist/components/WishlistView.js +4 -3
- package/dist/features/wishlist/hooks/useUserWishlist.d.ts +15 -15
- package/dist/features/wishlist/hooks/useWishlist.d.ts +1 -1
- package/dist/features/wishlist/hooks/useWishlistWithGuest.d.ts +1 -1
- package/dist/providers/db-firebase/admin.js +0 -1
- package/dist/ui/DataTable.js +6 -2
- package/dist/ui/components/AvatarDisplay.js +3 -1
- package/dist/ui/components/BackgroundRenderer.js +4 -2
- package/dist/ui/components/Card.js +6 -2
- package/dist/ui/components/ImageGallery.js +3 -1
- package/dist/ui/components/ImageLightbox.js +3 -1
- package/dist/ui/components/PasswordStrengthIndicator.js +3 -1
- package/dist/ui/components/Progress.js +6 -2
- package/dist/ui/components/RowActionMenu.js +3 -1
- package/dist/ui/components/SiteLogo.js +7 -1
- package/dist/ui/components/Skeleton.js +3 -1
- package/dist/ui/components/Slider.js +3 -1
- package/dist/ui/components/TabStrip.js +3 -1
- 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(
|
|
20
|
+
parts.push(sieveFilter("price", SIEVE_OP.GTE, minPrice));
|
|
20
21
|
if (maxPrice)
|
|
21
|
-
parts.push(
|
|
22
|
+
parts.push(sieveFilter("price", SIEVE_OP.LTE, maxPrice));
|
|
22
23
|
const store = sp(params, "storeId");
|
|
23
24
|
if (store)
|
|
24
|
-
parts.push(
|
|
25
|
+
parts.push(sieveFilter("storeId", SIEVE_OP.EQ, store));
|
|
25
26
|
const preOrderProductionStatus = sp(params, "preOrderProductionStatus");
|
|
26
27
|
if (preOrderProductionStatus)
|
|
27
|
-
parts.push(
|
|
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<
|
|
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<
|
|
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
|
|
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
|
|
10
|
-
const AUCTIONS_PUBLISHED =
|
|
11
|
-
const PREORDERS_PUBLISHED =
|
|
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(
|
|
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(
|
|
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:
|
|
38
|
-
sorts: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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:
|
|
104
|
-
sorts: "
|
|
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
|
|
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
|
|
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 (
|
|
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,
|
|
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-
|
|
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(
|
|
20
|
+
parts.push(sieveFilter("pricePerEntry", SIEVE_OP.GTE, minPrice));
|
|
20
21
|
if (maxPrice)
|
|
21
|
-
parts.push(
|
|
22
|
+
parts.push(sieveFilter("pricePerEntry", SIEVE_OP.LTE, maxPrice));
|
|
22
23
|
const store = sp(params, "storeId");
|
|
23
24
|
if (store)
|
|
24
|
-
parts.push(
|
|
25
|
+
parts.push(sieveFilter("storeId", SIEVE_OP.EQ, store));
|
|
25
26
|
const status = sp(params, "prizeRevealStatus");
|
|
26
27
|
if (status)
|
|
27
|
-
parts.push(
|
|
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
|
|
19
|
-
sorts: "
|
|
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) && (
|
|
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:
|
|
157
|
+
readonly value: string;
|
|
158
158
|
readonly label: "Most Savings";
|
|
159
159
|
}, {
|
|
160
|
-
readonly value:
|
|
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: "
|
|
83
|
-
{ value: "
|
|
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<
|
|
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<
|
|
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
|
|
12
|
-
: "listingType
|
|
12
|
+
? sieveFilter("listingType", SIEVE_OP.EQ, "auction")
|
|
13
|
+
: sieveFilter("listingType", SIEVE_OP.EQ, "standard");
|
|
13
14
|
const params = new URLSearchParams({
|
|
14
|
-
filters:
|
|
15
|
-
sorts: "
|
|
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<
|
|
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:
|
|
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: "
|
|
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",
|
|
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"}`,
|
|
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",
|
|
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,
|
|
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(
|
|
17
|
+
parts.push(sieveFilter("rating", SIEVE_OP.EQ, values[0]));
|
|
17
18
|
else if (values.length > 1)
|
|
18
|
-
parts.push(
|
|
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(
|
|
23
|
+
parts.push(sieveFilter("helpfulCount", SIEVE_OP.GTE, minVotes));
|
|
23
24
|
const maxVotes = sp(params, "maxVotes");
|
|
24
25
|
if (maxVotes)
|
|
25
|
-
parts.push(
|
|
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(
|
|
30
|
+
parts.push(sieveFilter("createdAt", SIEVE_OP.GTE, dateFrom));
|
|
30
31
|
if (dateTo)
|
|
31
|
-
parts.push(
|
|
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",
|
|
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<
|
|
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<
|
|
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(
|
|
16
|
+
filters.push(sieveFilter("scamType", SIEVE_OP.EQ, String(params.scamType)));
|
|
16
17
|
if (params.scamPlatform)
|
|
17
|
-
filters.push(
|
|
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: "
|
|
14
|
-
{ value: "createdAt", label: "Oldest Reports" },
|
|
15
|
-
{ value: "
|
|
16
|
-
{ value: "
|
|
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];
|