@mohasinac/appkit 2.7.59 → 2.8.1
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/filters/filter-load-options.d.ts +4 -5
- package/dist/_internal/server/features/auctions/og.js +8 -32
- package/dist/_internal/server/features/blog/og.js +10 -49
- package/dist/_internal/server/features/checkout/actions.js +6 -0
- package/dist/_internal/server/features/classified/og.js +9 -51
- package/dist/_internal/server/features/digital-code/actions.d.ts +11 -0
- package/dist/_internal/server/features/digital-code/actions.js +30 -0
- package/dist/_internal/server/features/digital-code/index.d.ts +1 -0
- package/dist/_internal/server/features/digital-code/index.js +1 -0
- package/dist/_internal/server/features/digital-code/og.js +9 -51
- package/dist/_internal/server/features/events/og.js +10 -49
- package/dist/_internal/server/features/item-requests/data.d.ts +17 -0
- package/dist/_internal/server/features/item-requests/data.js +20 -0
- package/dist/_internal/server/features/item-requests/index.d.ts +2 -0
- package/dist/_internal/server/features/item-requests/index.js +2 -0
- package/dist/_internal/server/features/item-requests/og.d.ts +40 -0
- package/dist/_internal/server/features/item-requests/og.js +136 -0
- package/dist/_internal/server/features/live/actions.d.ts +5 -0
- package/dist/_internal/server/features/live/actions.js +24 -0
- package/dist/_internal/server/features/live/index.d.ts +1 -0
- package/dist/_internal/server/features/live/index.js +1 -0
- package/dist/_internal/server/features/live/og.js +9 -51
- package/dist/_internal/server/features/media/contextGuards.js +19 -8
- package/dist/_internal/server/features/pre-orders/og.js +8 -32
- package/dist/_internal/server/features/prize-draws/data.d.ts +17 -0
- package/dist/_internal/server/features/prize-draws/data.js +20 -0
- package/dist/_internal/server/features/prize-draws/index.d.ts +2 -0
- package/dist/_internal/server/features/prize-draws/index.js +2 -0
- package/dist/_internal/server/features/prize-draws/og.d.ts +30 -0
- package/dist/_internal/server/features/prize-draws/og.js +50 -0
- package/dist/_internal/server/features/products/og.js +8 -33
- package/dist/_internal/server/features/seo/og-layout.d.ts +42 -0
- package/dist/_internal/server/features/seo/og-layout.js +69 -0
- package/dist/_internal/server/features/stores/og.js +8 -41
- package/dist/_internal/server/features/sublisting-categories/og.js +11 -42
- package/dist/client.d.ts +10 -6
- package/dist/client.js +7 -3
- package/dist/constants/index.d.ts +4 -0
- package/dist/constants/index.js +2 -0
- package/dist/constants/notification-types.d.ts +20 -0
- package/dist/constants/notification-types.js +37 -0
- package/dist/constants/pagination-presets.d.ts +39 -0
- package/dist/constants/pagination-presets.js +38 -0
- package/dist/errors/messages.d.ts +0 -4
- package/dist/errors/messages.js +0 -4
- package/dist/features/about/components/PublicProfileView.js +8 -8
- package/dist/features/account/components/UserOffersPanel.js +1 -1
- package/dist/features/account/components/UserSupportView.d.ts +1 -2
- package/dist/features/account/components/UserSupportView.js +9 -1
- package/dist/features/admin/actions/admin-actions.js +16 -1
- package/dist/features/admin/components/AdminAllEventEntriesView.d.ts +2 -3
- package/dist/features/admin/components/AdminAllEventEntriesView.js +50 -83
- package/dist/features/admin/components/AdminBidsView.d.ts +2 -3
- package/dist/features/admin/components/AdminBidsView.js +64 -98
- package/dist/features/admin/components/AdminBlogView.d.ts +3 -7
- package/dist/features/admin/components/AdminBlogView.js +53 -83
- package/dist/features/admin/components/AdminBrandsView.d.ts +2 -3
- package/dist/features/admin/components/AdminBrandsView.js +84 -83
- package/dist/features/admin/components/AdminBundlesView.js +82 -125
- package/dist/features/admin/components/AdminCarouselView.d.ts +2 -2
- package/dist/features/admin/components/AdminCarouselView.js +17 -52
- package/dist/features/admin/components/AdminCartsView.d.ts +2 -3
- package/dist/features/admin/components/AdminCartsView.js +39 -88
- package/dist/features/admin/components/AdminCategoriesView.d.ts +3 -3
- package/dist/features/admin/components/AdminCategoriesView.js +98 -92
- package/dist/features/admin/components/AdminCategoryEditorView.js +2 -2
- package/dist/features/admin/components/AdminClassifiedView.d.ts +3 -0
- package/dist/features/admin/components/AdminClassifiedView.js +44 -0
- package/dist/features/admin/components/AdminContactView.d.ts +2 -2
- package/dist/features/admin/components/AdminContactView.js +104 -111
- package/dist/features/admin/components/AdminCouponsView.d.ts +3 -6
- package/dist/features/admin/components/AdminCouponsView.js +66 -86
- package/dist/features/admin/components/AdminDigitalCodesView.d.ts +3 -0
- package/dist/features/admin/components/AdminDigitalCodesView.js +51 -0
- package/dist/features/admin/components/AdminFaqsView.d.ts +4 -4
- package/dist/features/admin/components/AdminFaqsView.js +77 -33
- package/dist/features/admin/components/AdminFeaturesView.d.ts +2 -3
- package/dist/features/admin/components/AdminFeaturesView.js +57 -70
- package/dist/features/admin/components/AdminGroupedListingsView.d.ts +3 -0
- package/dist/features/admin/components/AdminGroupedListingsView.js +43 -0
- package/dist/features/admin/components/AdminHistoryView.d.ts +2 -3
- package/dist/features/admin/components/AdminHistoryView.js +35 -52
- package/dist/features/admin/components/AdminListingScaffold.d.ts +2 -2
- package/dist/features/admin/components/AdminListingScaffold.js +3 -3
- package/dist/features/admin/components/AdminLiveView.d.ts +3 -0
- package/dist/features/admin/components/AdminLiveView.js +50 -0
- package/dist/features/admin/components/AdminNewsletterView.d.ts +2 -2
- package/dist/features/admin/components/AdminNewsletterView.js +67 -97
- package/dist/features/admin/components/AdminNotificationsView.d.ts +2 -3
- package/dist/features/admin/components/AdminNotificationsView.js +67 -93
- package/dist/features/admin/components/AdminOrderEditorView.js +10 -10
- package/dist/features/admin/components/AdminOrdersView.d.ts +2 -3
- package/dist/features/admin/components/AdminOrdersView.js +93 -107
- package/dist/features/admin/components/AdminPayoutsView.d.ts +2 -3
- package/dist/features/admin/components/AdminPayoutsView.js +51 -91
- package/dist/features/admin/components/AdminPrizeDrawsView.d.ts +2 -3
- package/dist/features/admin/components/AdminPrizeDrawsView.js +39 -76
- package/dist/features/admin/components/AdminProductEditorView.js +3 -3
- package/dist/features/admin/components/AdminProductsView.d.ts +4 -4
- package/dist/features/admin/components/AdminProductsView.js +167 -163
- package/dist/features/admin/components/AdminReturnRequestsView.d.ts +1 -1
- package/dist/features/admin/components/AdminReturnRequestsView.js +64 -66
- package/dist/features/admin/components/AdminReviewsView.d.ts +3 -7
- package/dist/features/admin/components/AdminReviewsView.js +94 -110
- package/dist/features/admin/components/AdminScammersView.d.ts +2 -3
- package/dist/features/admin/components/AdminScammersView.js +53 -97
- package/dist/features/admin/components/AdminSectionsView.js +1 -1
- package/dist/features/admin/components/AdminSessionsView.d.ts +2 -3
- package/dist/features/admin/components/AdminSessionsView.js +62 -89
- package/dist/features/admin/components/AdminSiteSettingsView.js +4 -5
- package/dist/features/admin/components/AdminStoreAddressesView.d.ts +1 -1
- package/dist/features/admin/components/AdminStoreAddressesView.js +39 -53
- package/dist/features/admin/components/AdminStoreEditorView.js +21 -8
- package/dist/features/admin/components/AdminStoresView.d.ts +2 -3
- package/dist/features/admin/components/AdminStoresView.js +67 -99
- package/dist/features/admin/components/AdminSublistingCategoriesView.d.ts +1 -1
- package/dist/features/admin/components/AdminSublistingCategoriesView.js +28 -44
- package/dist/features/admin/components/AdminSupportTicketDetailView.d.ts +10 -1
- package/dist/features/admin/components/AdminSupportTicketDetailView.js +191 -5
- package/dist/features/admin/components/AdminSupportTicketsView.d.ts +2 -3
- package/dist/features/admin/components/AdminSupportTicketsView.js +65 -96
- package/dist/features/admin/components/AdminTeamView.d.ts +2 -2
- package/dist/features/admin/components/AdminTeamView.js +40 -74
- package/dist/features/admin/components/AdminUserEditorView.d.ts +11 -1
- package/dist/features/admin/components/AdminUserEditorView.js +77 -7
- package/dist/features/admin/components/AdminUsersView.d.ts +2 -3
- package/dist/features/admin/components/AdminUsersView.js +108 -100
- package/dist/features/admin/components/AdminViewCards.d.ts +1 -1
- package/dist/features/admin/components/AdminViewCards.js +15 -15
- package/dist/features/admin/components/AdminWishlistsView.d.ts +2 -3
- package/dist/features/admin/components/AdminWishlistsView.js +36 -62
- package/dist/features/admin/components/DataListingView.d.ts +122 -0
- package/dist/features/admin/components/DataListingView.js +66 -0
- package/dist/features/admin/components/index.d.ts +10 -4
- package/dist/features/admin/components/index.js +6 -2
- package/dist/features/admin/constants/filter-tabs.d.ts +23 -17
- package/dist/features/admin/constants/filter-tabs.js +21 -17
- package/dist/features/admin/hooks/useAdminListing.d.ts +1 -0
- package/dist/features/admin/hooks/useAdminListing.js +1 -1
- package/dist/features/admin/schemas/firestore.js +6 -7
- package/dist/features/auctions/server.d.ts +0 -1
- package/dist/features/auctions/server.js +0 -1
- package/dist/features/auth/actions/realtime-token-actions.js +1 -1
- package/dist/features/auth/repository/user.repository.js +1 -1
- package/dist/features/blog/components/BlogFeaturedCard.js +1 -0
- package/dist/features/cart/repository/cart.repository.d.ts +4 -1
- package/dist/features/cart/repository/cart.repository.js +12 -0
- package/dist/features/checkout/hooks/useCheckoutApi.d.ts +0 -2
- package/dist/features/checkout/index.d.ts +1 -1
- package/dist/features/events/components/AdminEventEditorView.js +10 -13
- package/dist/features/events/components/AdminEventsView.d.ts +3 -3
- package/dist/features/events/components/AdminEventsView.js +50 -86
- package/dist/features/events/components/EventCard.js +1 -0
- package/dist/features/events/components/EventOfferCard.d.ts +19 -0
- package/dist/features/events/components/EventOfferCard.js +29 -0
- package/dist/features/events/components/EventRaffleEntryForm.d.ts +25 -0
- package/dist/features/events/components/EventRaffleEntryForm.js +37 -0
- package/dist/features/events/components/EventRafflesSection.d.ts +1 -4
- package/dist/features/events/components/EventRafflesSection.js +17 -6
- package/dist/features/events/components/EventSaleBanner.d.ts +21 -0
- package/dist/features/events/components/EventSaleBanner.js +18 -0
- package/dist/features/events/components/index.d.ts +6 -0
- package/dist/features/events/components/index.js +4 -0
- package/dist/features/events/repository/event-entry.repository.d.ts +5 -0
- package/dist/features/events/repository/event-entry.repository.js +7 -0
- package/dist/features/faq/components/FAQPageContent.js +1 -0
- package/dist/features/filters/AsyncFacetSection.d.ts +1 -1
- package/dist/features/homepage/components/AdvertisementBanner.js +6 -6
- package/dist/features/homepage/components/FAQSection.d.ts +0 -5
- package/dist/features/homepage/components/HomepageSkeleton.js +2 -2
- package/dist/features/homepage/components/WelcomeSection.js +3 -3
- package/dist/features/homepage/lib/live-stats.js +1 -1
- package/dist/features/homepage/schemas/firestore.d.ts +0 -2
- package/dist/features/layout/TitleBar.js +1 -0
- package/dist/features/layout/index.d.ts +0 -2
- package/dist/features/layout/index.js +0 -1
- package/dist/features/pre-orders/server.d.ts +0 -1
- package/dist/features/pre-orders/server.js +0 -1
- package/dist/features/products/components/GroupSettingsPanel.js +2 -2
- package/dist/features/products/components/PrizeDrawItemsEditor.js +2 -2
- package/dist/features/products/components/PrizeDrawsSection.d.ts +1 -4
- package/dist/features/products/components/PrizeDrawsSection.js +17 -6
- package/dist/features/products/components/PrizeRevealModal.js +1 -1
- package/dist/features/products/components/ProductCardMetadataSection.d.ts +11 -0
- package/dist/features/products/components/ProductCardMetadataSection.js +25 -0
- package/dist/features/products/components/ProductDetailPageView.js +3 -1
- package/dist/features/products/components/ProductFeaturesSelector.js +1 -1
- package/dist/features/products/components/ProductGradingTab.d.ts +13 -0
- package/dist/features/products/components/ProductGradingTab.js +25 -0
- package/dist/features/products/components/ProductGrid.js +15 -2
- package/dist/features/products/components/ProductTabsShell.d.ts +5 -1
- package/dist/features/products/components/ProductTabsShell.js +6 -1
- package/dist/features/products/components/index.d.ts +4 -0
- package/dist/features/products/components/index.js +3 -0
- package/dist/features/products/constants/action-defs.d.ts +5 -0
- package/dist/features/products/constants/action-defs.js +52 -32
- package/dist/features/products/constants/sieve.d.ts +24 -4
- package/dist/features/products/constants/sieve.js +20 -11
- package/dist/features/products/schemas/firestore.d.ts +17 -0
- package/dist/features/products/schemas/product-features.validators.d.ts +2 -2
- package/dist/features/products/schemas/product-templates.d.ts +2 -4
- package/dist/features/products/types/index.d.ts +4 -0
- package/dist/features/promotions/components/CouponsIndexListing.js +4 -4
- package/dist/features/promotions/server.d.ts +0 -1
- package/dist/features/promotions/server.js +0 -1
- package/dist/features/reviews/components/ReviewDetailShell.js +3 -3
- package/dist/features/search/api/route.js +44 -1
- package/dist/features/search/components/Search.js +2 -0
- package/dist/features/search/hooks/useNavSuggestions.d.ts +5 -1
- package/dist/features/search/hooks/useNavSuggestions.js +31 -4
- package/dist/features/seller/components/BrandInlineSelect.js +2 -2
- package/dist/features/seller/components/CategoryInlineSelect.js +2 -2
- package/dist/features/seller/components/SellerAddressesView.js +4 -5
- package/dist/features/seller/components/SellerAuctionsView.d.ts +3 -3
- package/dist/features/seller/components/SellerAuctionsView.js +60 -90
- package/dist/features/seller/components/SellerBidsView.js +1 -1
- package/dist/features/seller/components/SellerBundlesView.js +64 -89
- package/dist/features/seller/components/SellerClassifiedView.js +67 -90
- package/dist/features/seller/components/SellerCouponEditorView.js +1 -1
- package/dist/features/seller/components/SellerCouponsView.d.ts +2 -2
- package/dist/features/seller/components/SellerCouponsView.js +45 -84
- package/dist/features/seller/components/SellerDigitalCodesView.js +63 -88
- package/dist/features/seller/components/SellerGroupedListingsView.js +33 -49
- package/dist/features/seller/components/SellerLiveView.js +63 -84
- package/dist/features/seller/components/SellerOffersPanel.js +1 -1
- package/dist/features/seller/components/SellerOffersView.d.ts +2 -2
- package/dist/features/seller/components/SellerOffersView.js +37 -69
- package/dist/features/seller/components/SellerOrdersView.d.ts +2 -2
- package/dist/features/seller/components/SellerOrdersView.js +4 -4
- package/dist/features/seller/components/SellerPayoutMethodsView.js +1 -1
- package/dist/features/seller/components/SellerPayoutsView.d.ts +2 -2
- package/dist/features/seller/components/SellerPayoutsView.js +34 -68
- package/dist/features/seller/components/SellerPreOrdersView.d.ts +2 -2
- package/dist/features/seller/components/SellerPreOrdersView.js +49 -82
- package/dist/features/seller/components/SellerPrizeDrawsView.d.ts +2 -2
- package/dist/features/seller/components/SellerPrizeDrawsView.js +49 -82
- package/dist/features/seller/components/SellerProductShell.d.ts +1 -1
- package/dist/features/seller/components/SellerProductsView.d.ts +2 -2
- package/dist/features/seller/components/SellerProductsView.js +15 -10
- package/dist/features/seller/components/SellerShippingConfigsView.js +1 -1
- package/dist/features/seller/components/SellerSidebar.d.ts +0 -4
- package/dist/features/seller/components/SellerSidebar.js +0 -2
- package/dist/features/seller/components/SellerStoreCategoriesView.js +1 -1
- package/dist/features/seller/components/SellerTemplatesView.js +2 -2
- package/dist/features/seller/components/SellersListView.d.ts +1 -2
- package/dist/features/seller/components/analytics/SellerAnalyticsStats.d.ts +5 -0
- package/dist/features/seller/components/analytics/SellerAnalyticsStats.js +2 -8
- package/dist/features/seller/components/index.d.ts +0 -3
- package/dist/features/seller/components/index.js +0 -2
- package/dist/features/seller/server.d.ts +0 -1
- package/dist/features/seller/server.js +0 -1
- package/dist/features/shell/FormShell.d.ts +1 -1
- package/dist/features/shell/FormShell.js +7 -4
- package/dist/features/shell/field-groups/ImageFieldGroup.d.ts +18 -0
- package/dist/features/shell/field-groups/ImageFieldGroup.js +15 -0
- package/dist/features/shell/field-groups/SeoFieldGroup.d.ts +22 -0
- package/dist/features/shell/field-groups/SeoFieldGroup.js +16 -0
- package/dist/features/shell/field-groups/StatusFieldGroup.d.ts +35 -0
- package/dist/features/shell/field-groups/StatusFieldGroup.js +16 -0
- package/dist/features/shell/field-groups/TitleDescriptionGroup.d.ts +40 -0
- package/dist/features/shell/field-groups/TitleDescriptionGroup.js +16 -0
- package/dist/features/shell/field-groups/index.d.ts +13 -0
- package/dist/features/shell/field-groups/index.js +9 -0
- package/dist/features/shell/index.d.ts +2 -0
- package/dist/features/shell/index.js +1 -0
- package/dist/features/stores/components/StoreCapabilitiesGuideView.d.ts +1 -2
- package/dist/features/stores/components/StoreFinanceGuideView.d.ts +1 -2
- package/dist/features/stores/components/StoreHeader.js +2 -1
- package/dist/features/stores/components/StoreListingsGuideView.d.ts +1 -2
- package/dist/features/stores/components/StoreOrdersGuideView.d.ts +1 -2
- package/dist/features/stores/components/StoreScopedSearch.d.ts +16 -0
- package/dist/features/stores/components/StoreScopedSearch.js +27 -0
- package/dist/features/stores/components/StoreSettingsGuideView.d.ts +1 -2
- package/dist/features/stores/server.d.ts +0 -1
- package/dist/features/stores/server.js +0 -1
- package/dist/features/support/schemas/firestore.d.ts +22 -1
- package/dist/features/support/schemas/firestore.js +10 -0
- package/dist/features/wishlist/server.d.ts +0 -1
- package/dist/features/wishlist/server.js +0 -1
- package/dist/index.d.ts +9 -27
- package/dist/index.js +14 -50
- package/dist/next/routing/route-map.d.ts +8 -0
- package/dist/next/routing/route-map.js +4 -0
- package/dist/react/hooks/useListingTypeFlags.d.ts +32 -0
- package/dist/react/hooks/useListingTypeFlags.js +19 -0
- package/dist/security/index.d.ts +1 -1
- package/dist/security/index.js +1 -1
- package/dist/security/pii-redact.d.ts +8 -0
- package/dist/security/pii-redact.js +10 -1
- package/dist/seed/grouped-listings-seed-data.d.ts +4 -0
- package/dist/seed/products-preorders-seed-data.d.ts +4 -0
- package/dist/seed/products-prize-draws-seed-data.js +3 -0
- package/dist/seed/site-settings-seed-data.js +5 -5
- package/dist/seed/sub-listings-seed-data.d.ts +4 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +4 -0
- package/dist/styles.css +54 -395
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/components/BaseListingCard.d.ts +10 -0
- package/dist/ui/components/BaseListingCard.js +17 -1
- package/dist/ui/components/DetailPageGallery.d.ts +32 -0
- package/dist/ui/components/DetailPageGallery.js +47 -0
- package/dist/ui/components/DetailPageHero.d.ts +30 -0
- package/dist/ui/components/DetailPageHero.js +22 -0
- package/dist/ui/components/DetailPageTabs.d.ts +34 -0
- package/dist/ui/components/DetailPageTabs.js +38 -0
- package/dist/ui/components/HorizontalScroller.style.css +15 -2
- package/dist/ui/components/ListingLayout.d.ts +11 -2
- package/dist/ui/components/ListingLayout.js +12 -6
- package/dist/ui/components/PaginatedSelect.d.ts +66 -0
- package/dist/ui/components/PaginatedSelect.js +201 -0
- package/dist/ui/components/PaginatedSelect.style.css +254 -0
- package/dist/ui/components/Semantic.d.ts +0 -4
- package/dist/ui/components/Semantic.js +0 -2
- package/dist/ui/components/SettingsSection.d.ts +47 -0
- package/dist/ui/components/SettingsSection.js +38 -0
- package/dist/ui/components/index.style.css +1 -4
- package/dist/ui/index.d.ts +12 -14
- package/dist/ui/index.js +6 -7
- package/dist/utils/id-generators.d.ts +1 -18
- package/dist/utils/id-generators.js +51 -18
- package/dist/validation/schemas.d.ts +25 -0
- package/dist/validation/schemas.js +28 -0
- package/package.json +1 -1
|
@@ -2,6 +2,20 @@ export type SortOption = {
|
|
|
2
2
|
readonly value: string;
|
|
3
3
|
readonly label: string;
|
|
4
4
|
};
|
|
5
|
+
/** Time + price sorts, the common subset across most listing-type pickers. */
|
|
6
|
+
export declare const BASE_SORT_OPTIONS: readonly [{
|
|
7
|
+
readonly value: string;
|
|
8
|
+
readonly label: "Newest First";
|
|
9
|
+
}, {
|
|
10
|
+
readonly value: string;
|
|
11
|
+
readonly label: "Oldest First";
|
|
12
|
+
}, {
|
|
13
|
+
readonly value: string;
|
|
14
|
+
readonly label: "Price: Low to High";
|
|
15
|
+
}, {
|
|
16
|
+
readonly value: string;
|
|
17
|
+
readonly label: "Price: High to Low";
|
|
18
|
+
}];
|
|
5
19
|
export declare const STANDARD_SORT_OPTIONS: readonly [{
|
|
6
20
|
readonly value: string;
|
|
7
21
|
readonly label: "Newest First";
|
|
@@ -108,10 +122,13 @@ export declare const PREORDER_SORT_OPTIONS: readonly [{
|
|
|
108
122
|
}, {
|
|
109
123
|
readonly value: string;
|
|
110
124
|
readonly label: "Latest Delivery";
|
|
111
|
-
}, {
|
|
125
|
+
}, ...({
|
|
112
126
|
readonly value: string;
|
|
113
127
|
readonly label: "Newest First";
|
|
114
|
-
}
|
|
128
|
+
} | {
|
|
129
|
+
readonly value: string;
|
|
130
|
+
readonly label: "Oldest First";
|
|
131
|
+
})[], {
|
|
115
132
|
readonly value: string;
|
|
116
133
|
readonly label: "Price: Low to High";
|
|
117
134
|
}, {
|
|
@@ -124,10 +141,13 @@ export declare const PREORDER_SORT_OPTIONS: readonly [{
|
|
|
124
141
|
readonly value: string;
|
|
125
142
|
readonly label: "Lowest Deposit First";
|
|
126
143
|
}];
|
|
127
|
-
export declare const BUNDLE_SORT_OPTIONS: readonly [{
|
|
144
|
+
export declare const BUNDLE_SORT_OPTIONS: readonly [...({
|
|
128
145
|
readonly value: string;
|
|
129
146
|
readonly label: "Newest First";
|
|
130
|
-
}
|
|
147
|
+
} | {
|
|
148
|
+
readonly value: string;
|
|
149
|
+
readonly label: "Oldest First";
|
|
150
|
+
})[], {
|
|
131
151
|
readonly value: string;
|
|
132
152
|
readonly label: "Price: Low to High";
|
|
133
153
|
}, {
|
|
@@ -5,13 +5,25 @@ const LABEL_PRICE_LOW = "Price: Low to High";
|
|
|
5
5
|
const LABEL_NEWEST = "Newest First";
|
|
6
6
|
const LABEL_OLDEST = "Oldest First";
|
|
7
7
|
// ---------------------------------------------------------------------------
|
|
8
|
-
//
|
|
8
|
+
// W2-6 — Base sort options shared by standard / pre-order / bundle / prize-draw.
|
|
9
|
+
// Auctions get their own base (auctionEndDate primary) because the "Ending Soon"
|
|
10
|
+
// dimension dominates browse intent.
|
|
9
11
|
// ---------------------------------------------------------------------------
|
|
10
|
-
|
|
12
|
+
const BASE_TIME_SORTS = [
|
|
11
13
|
{ value: sortBy(PRODUCT_FIELDS.CREATED_AT), label: LABEL_NEWEST },
|
|
12
14
|
{ value: sortBy(PRODUCT_FIELDS.CREATED_AT, "ASC"), label: LABEL_OLDEST },
|
|
15
|
+
];
|
|
16
|
+
const BASE_PRICE_SORTS = [
|
|
13
17
|
{ value: sortBy(PRODUCT_FIELDS.PRICE, "ASC"), label: LABEL_PRICE_LOW },
|
|
14
18
|
{ value: sortBy(PRODUCT_FIELDS.PRICE), label: LABEL_PRICE_HIGH },
|
|
19
|
+
];
|
|
20
|
+
/** Time + price sorts, the common subset across most listing-type pickers. */
|
|
21
|
+
export const BASE_SORT_OPTIONS = [...BASE_TIME_SORTS, ...BASE_PRICE_SORTS];
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Standard Products
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
export const STANDARD_SORT_OPTIONS = [
|
|
26
|
+
...BASE_SORT_OPTIONS,
|
|
15
27
|
{ value: sortBy(PRODUCT_FIELDS.TITLE, "ASC"), label: "Name: A–Z" },
|
|
16
28
|
{ value: sortBy(PRODUCT_FIELDS.TITLE), label: "Name: Z–A" },
|
|
17
29
|
{ value: sortBy(PRODUCT_FIELDS.FEATURED), label: "Featured First" },
|
|
@@ -29,7 +41,7 @@ export const STANDARD_PUBLIC_SORT_OPTIONS = [
|
|
|
29
41
|
STANDARD_SORT_OPTIONS[9],
|
|
30
42
|
];
|
|
31
43
|
// ---------------------------------------------------------------------------
|
|
32
|
-
// Auctions
|
|
44
|
+
// Auctions — auction-specific base puts auctionEndDate primary
|
|
33
45
|
// ---------------------------------------------------------------------------
|
|
34
46
|
export const AUCTION_SORT_OPTIONS = [
|
|
35
47
|
{ value: sortBy(PRODUCT_FIELDS.AUCTION_END_DATE, "ASC"), label: "Ending Soon" },
|
|
@@ -56,9 +68,8 @@ export const AUCTION_PUBLIC_SORT_OPTIONS = [
|
|
|
56
68
|
export const PREORDER_SORT_OPTIONS = [
|
|
57
69
|
{ value: sortBy(PRODUCT_FIELDS.PRE_ORDER_DELIVERY_DATE, "ASC"), label: "Earliest Delivery" },
|
|
58
70
|
{ value: sortBy(PRODUCT_FIELDS.PRE_ORDER_DELIVERY_DATE), label: "Latest Delivery" },
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
{ value: sortBy(PRODUCT_FIELDS.PRICE), label: LABEL_PRICE_HIGH },
|
|
71
|
+
...BASE_TIME_SORTS.slice(0, 1), // Newest First
|
|
72
|
+
...BASE_PRICE_SORTS,
|
|
62
73
|
{ value: sortBy(PRODUCT_FIELDS.PRE_ORDER_CURRENT_COUNT), label: "Fewest Slots Left" },
|
|
63
74
|
{ value: sortBy(PRODUCT_FIELDS.PRE_ORDER_DEPOSIT_AMOUNT, "ASC"), label: "Lowest Deposit First" },
|
|
64
75
|
];
|
|
@@ -66,9 +77,8 @@ export const PREORDER_SORT_OPTIONS = [
|
|
|
66
77
|
// Bundles
|
|
67
78
|
// ---------------------------------------------------------------------------
|
|
68
79
|
export const BUNDLE_SORT_OPTIONS = [
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
{ value: sortBy(PRODUCT_FIELDS.PRICE), label: LABEL_PRICE_HIGH },
|
|
80
|
+
...BASE_TIME_SORTS.slice(0, 1), // Newest First
|
|
81
|
+
...BASE_PRICE_SORTS,
|
|
72
82
|
{ value: "-savingsAmount", label: "Most Savings" },
|
|
73
83
|
{ value: "-bundleItemCount", label: "Most Items" },
|
|
74
84
|
];
|
|
@@ -76,8 +86,7 @@ export const BUNDLE_SORT_OPTIONS = [
|
|
|
76
86
|
// Prize Draws
|
|
77
87
|
// ---------------------------------------------------------------------------
|
|
78
88
|
export const PRIZE_DRAW_SORT_OPTIONS = [
|
|
79
|
-
|
|
80
|
-
{ value: sortBy(PRODUCT_FIELDS.CREATED_AT, "ASC"), label: LABEL_OLDEST },
|
|
89
|
+
...BASE_TIME_SORTS,
|
|
81
90
|
{ value: sortBy(PRODUCT_FIELDS.PRIZE_REVEAL_WINDOW_START, "ASC"), label: "Reveal: Soonest" },
|
|
82
91
|
{ value: sortBy(PRODUCT_FIELDS.PRIZE_REVEAL_WINDOW_START), label: "Reveal: Furthest" },
|
|
83
92
|
{ value: sortBy(PRODUCT_FIELDS.PRICE, "ASC"), label: "Entry: Low to High" },
|
|
@@ -132,6 +132,10 @@ export interface ProductDocument {
|
|
|
132
132
|
status: ProductStatus;
|
|
133
133
|
storeId: string;
|
|
134
134
|
storeName?: string;
|
|
135
|
+
/** W1-34 — denormalized seller rating (0–5) shown on product detail store card. */
|
|
136
|
+
storeRating?: number;
|
|
137
|
+
/** W1-34 — denormalized seller total review count shown alongside the rating. */
|
|
138
|
+
storeReviewCount?: number;
|
|
135
139
|
featured: boolean;
|
|
136
140
|
tags: string[];
|
|
137
141
|
specifications?: ProductSpecification[];
|
|
@@ -194,6 +198,19 @@ export interface ProductDocument {
|
|
|
194
198
|
groupParentSlug?: string;
|
|
195
199
|
groupChildSlugs?: string[];
|
|
196
200
|
groupTitle?: string;
|
|
201
|
+
/**
|
|
202
|
+
* W1-45 — optional icon shown as an overlay badge on every card whose
|
|
203
|
+
* product belongs to this group. Set once on the group parent; cards
|
|
204
|
+
* inherit it via the snapshot pipeline. Either an emoji or a media slug
|
|
205
|
+
* (rendered via `/media/<slug>`).
|
|
206
|
+
*/
|
|
207
|
+
groupIcon?: string;
|
|
208
|
+
/**
|
|
209
|
+
* W1-45 — denormalized icon from the sublisting category (`display.icon`).
|
|
210
|
+
* Mirrored onto the product snapshot at create/update time so cards can
|
|
211
|
+
* render it without an extra category lookup per row.
|
|
212
|
+
*/
|
|
213
|
+
sublistingIcon?: string;
|
|
197
214
|
/**
|
|
198
215
|
* Required since Phase 4 dropped the legacy `isAuction` / `isPreOrder`
|
|
199
216
|
* booleans. Every product document carries this; queries route through
|
|
@@ -89,8 +89,8 @@ export declare const productFeatureUpdateSchema: z.ZodObject<{
|
|
|
89
89
|
label?: string | undefined;
|
|
90
90
|
icon?: string | undefined;
|
|
91
91
|
isActive?: boolean | undefined;
|
|
92
|
-
iconColor?: string | undefined;
|
|
93
92
|
displayOrder?: number | undefined;
|
|
93
|
+
iconColor?: string | undefined;
|
|
94
94
|
productTypes?: ("auction" | "prize-draw" | "classified" | "digital-code" | "live" | "all" | "product" | "preorder")[] | undefined;
|
|
95
95
|
}, {
|
|
96
96
|
category?: "seller" | "custom" | "auction" | "condition" | "shipping" | "preorder" | "platform" | undefined;
|
|
@@ -98,8 +98,8 @@ export declare const productFeatureUpdateSchema: z.ZodObject<{
|
|
|
98
98
|
label?: string | undefined;
|
|
99
99
|
icon?: string | undefined;
|
|
100
100
|
isActive?: boolean | undefined;
|
|
101
|
-
iconColor?: string | undefined;
|
|
102
101
|
displayOrder?: number | undefined;
|
|
102
|
+
iconColor?: string | undefined;
|
|
103
103
|
productTypes?: ("auction" | "prize-draw" | "classified" | "digital-code" | "live" | "all" | "product" | "preorder")[] | undefined;
|
|
104
104
|
}>;
|
|
105
105
|
export type ProductFeatureAdminCreatePayload = z.infer<typeof productFeatureAdminCreateSchema>;
|
|
@@ -22,7 +22,5 @@ export interface ProductTemplateDocument {
|
|
|
22
22
|
createdAt: Date | string;
|
|
23
23
|
updatedAt: Date | string;
|
|
24
24
|
}
|
|
25
|
-
export
|
|
26
|
-
|
|
27
|
-
export interface ProductTemplateUpdateInput extends Partial<Omit<ProductTemplateDocument, "id" | "storeId" | "createdAt">> {
|
|
28
|
-
}
|
|
25
|
+
export type ProductTemplateCreateInput = Omit<ProductTemplateDocument, "id" | "createdAt" | "updatedAt">;
|
|
26
|
+
export type ProductTemplateUpdateInput = Partial<Omit<ProductTemplateDocument, "id" | "storeId" | "createdAt">>;
|
|
@@ -65,6 +65,10 @@ export interface ProductItem {
|
|
|
65
65
|
groupParentSlug?: string;
|
|
66
66
|
groupChildSlugs?: string[];
|
|
67
67
|
groupTitle?: string;
|
|
68
|
+
/** W1-45 — icon shown as a card overlay; emoji or `/media/<slug>`. */
|
|
69
|
+
groupIcon?: string;
|
|
70
|
+
/** W1-45 — denormalized icon from the sublisting category. */
|
|
71
|
+
sublistingIcon?: string;
|
|
68
72
|
isOnSale?: boolean;
|
|
69
73
|
isSold?: boolean;
|
|
70
74
|
inStock?: boolean;
|
|
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
3
3
|
import { useState, useCallback, useMemo } from "react";
|
|
4
4
|
import { Search, SlidersHorizontal, X } from "lucide-react";
|
|
5
5
|
import { useUrlTable } from "../../../react/hooks/useUrlTable";
|
|
6
|
-
import { ListingFilterDrawer, Pagination, SortDropdown, Div, Text, Heading } from "../../../ui";
|
|
6
|
+
import { ListingFilterDrawer, Pagination, SortDropdown, Div, Grid, Row, Span, Stack, Text, Heading } from "../../../ui";
|
|
7
7
|
import { usePromotions } from "../hooks/usePromotions";
|
|
8
8
|
import { CouponCard } from "./CouponCard";
|
|
9
9
|
import { TABLE_KEYS } from "../../../constants/table-keys";
|
|
@@ -89,14 +89,14 @@ export function CouponsIndexListing({ initialCoupons, storeSlug, storeId, }) {
|
|
|
89
89
|
const clearFilters = () => {
|
|
90
90
|
table.setMany({ type: "", [TABLE_KEYS.DATE_FROM]: "", [TABLE_KEYS.DATE_TO]: "" });
|
|
91
91
|
};
|
|
92
|
-
return (_jsxs(
|
|
92
|
+
return (_jsxs(Div, { className: "min-h-[40vh]", children: [_jsxs(Div, { className: "sticky top-[var(--header-height,0px)] z-20 border-b border-zinc-200 dark:border-slate-700 bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm py-2.5 px-4", children: [_jsxs(Row, { gap: "xs", className: "max-w-full", children: [_jsxs("button", { type: "button", onClick: openFilters, className: `flex shrink-0 items-center gap-2 rounded-lg border px-3.5 py-2 text-sm font-medium transition-colors ${hasActiveFilters
|
|
93
93
|
? "border-primary bg-primary-50 text-primary-700 dark:bg-primary-900/30 dark:text-primary-300"
|
|
94
|
-
: "border-zinc-300 dark:border-slate-600 text-zinc-700 dark:text-zinc-200 hover:bg-zinc-50 dark:hover:bg-slate-800"}`, children: [_jsx(SlidersHorizontal, { className: "h-4 w-4" }), _jsxs(
|
|
94
|
+
: "border-zinc-300 dark:border-slate-600 text-zinc-700 dark:text-zinc-200 hover:bg-zinc-50 dark:hover:bg-slate-800"}`, children: [_jsx(SlidersHorizontal, { className: "h-4 w-4" }), _jsxs(Span, { className: "hidden sm:inline", children: ["Filters", hasActiveFilters ? " •" : ""] })] }), _jsxs(Row, { className: "flex-1 overflow-hidden rounded-lg border border-zinc-300 dark:border-slate-600 bg-white dark:bg-slate-900", children: [_jsx("input", { type: "text", value: searchInput, onChange: (e) => setSearchInput(e.target.value), onKeyDown: handleKeyDown, placeholder: "Search by name or description\u2026", className: "min-w-0 flex-1 bg-transparent px-3 py-2 text-sm text-zinc-900 dark:text-zinc-100 placeholder-zinc-400 outline-none" }), searchInput && (_jsx("button", { type: "button", onClick: () => { setSearchInput(""); table.set(TABLE_KEYS.QUERY, ""); }, className: "p-2 text-zinc-400 hover:text-zinc-600 dark:hover:text-zinc-300", "aria-label": "Clear search", children: _jsx(X, { className: "h-3.5 w-3.5" }) })), _jsx("button", { type: "button", onClick: commitSearch, className: "flex shrink-0 items-center justify-center px-3 py-2 text-zinc-400 hover:text-primary dark:hover:text-primary-400 transition-colors", "aria-label": "Search", children: _jsx(Search, { className: "h-4 w-4" }) })] }), _jsxs(Row, { gap: "xs", className: "shrink-0 text-sm text-zinc-500 dark:text-zinc-400", children: [_jsx(Span, { className: "hidden md:inline whitespace-nowrap", children: "Sort by" }), _jsx(SortDropdown, { value: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT, onChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, options: COUPON_SORT_OPTIONS })] })] }), hasActiveFilters && (_jsxs(Row, { gap: "xs", wrap: true, className: "mt-2", children: [activeType && (_jsxs(Span, { className: "flex items-center gap-1 rounded-full bg-primary-50 dark:bg-primary-900/30 text-primary-700 dark:text-primary-300 text-xs font-medium px-2.5 py-1", children: [COUPON_TYPES.find((t) => t.value === activeType)?.label ?? activeType, _jsx("button", { type: "button", onClick: () => { table.set(TABLE_KEYS.TYPE, ""); }, "aria-label": "Remove type filter", children: _jsx(X, { className: "h-3 w-3" }) })] })), table.get(TABLE_KEYS.DATE_FROM) && (_jsxs(Span, { className: "flex items-center gap-1 rounded-full bg-primary-50 dark:bg-primary-900/30 text-primary-700 dark:text-primary-300 text-xs font-medium px-2.5 py-1", children: ["From: ", table.get(TABLE_KEYS.DATE_FROM), _jsx("button", { type: "button", onClick: () => { table.set(TABLE_KEYS.DATE_FROM, ""); }, "aria-label": "Remove from-date filter", children: _jsx(X, { className: "h-3 w-3" }) })] })), table.get(TABLE_KEYS.DATE_TO) && (_jsxs(Span, { className: "flex items-center gap-1 rounded-full bg-primary-50 dark:bg-primary-900/30 text-primary-700 dark:text-primary-300 text-xs font-medium px-2.5 py-1", children: ["To: ", table.get(TABLE_KEYS.DATE_TO), _jsx("button", { type: "button", onClick: () => { table.set(TABLE_KEYS.DATE_TO, ""); }, "aria-label": "Remove to-date filter", children: _jsx(X, { className: "h-3 w-3" }) })] })), _jsx("button", { type: "button", onClick: clearFilters, className: "text-xs text-zinc-500 dark:text-zinc-400 hover:text-zinc-700 dark:hover:text-zinc-300 underline", children: "Clear all" })] }))] }), _jsxs(Div, { className: "py-6 px-4", children: [isLoading ? (_jsx(Grid, { gap: "sm", className: "md:grid-cols-2 lg:grid-cols-3", children: Array.from({ length: 6 }).map((_, i) => (_jsxs(Stack, { gap: "sm", rounded: "xl", padding: "md", className: "border-2 border-zinc-100 dark:border-slate-700 animate-pulse", children: [_jsx(Div, { className: "h-6 bg-zinc-200 dark:bg-slate-700 rounded w-2/3" }), _jsx(Div, { className: "h-4 bg-zinc-200 dark:bg-slate-700 rounded w-full" }), _jsx(Div, { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-1/2" })] }, i))) })) : displayCoupons.length === 0 ? (_jsx(Div, { className: "py-16 text-center", children: _jsx(Text, { className: "text-zinc-400 dark:text-zinc-400", children: "No coupons match your search." }) })) : (_jsx(Grid, { gap: "sm", className: "md:grid-cols-2 lg:grid-cols-3", children: displayCoupons.map((coupon) => (_jsx(CouponCard, { coupon: coupon, labels: {
|
|
95
95
|
copy: "Copy",
|
|
96
96
|
copied: "Copied!",
|
|
97
97
|
expires: "Expires",
|
|
98
98
|
minOrder: "Min. order",
|
|
99
99
|
off: "OFF",
|
|
100
100
|
freeShipping: "Free Shipping",
|
|
101
|
-
} }, coupon.id))) })), totalPages > 1 && (_jsx("
|
|
101
|
+
} }, coupon.id))) })), totalPages > 1 && (_jsx(Row, { justify: "center", className: "mt-8", children: _jsx(Pagination, { currentPage: table.getNumber("page", 1), totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), !isLoading && total > 0 && (_jsx(Div, { className: "mt-4 text-center", children: _jsxs(Text, { className: "text-xs text-zinc-400 dark:text-zinc-400", children: [total, " coupon", total !== 1 ? "s" : "", " available"] }) }))] }), _jsxs(ListingFilterDrawer, { open: filterOpen, onClose: () => setFilterOpen(false), onApply: applyFilters, onClear: clearPending, activeCount: activeFilterCount, children: [_jsxs(_Fragment, { children: [_jsx(Heading, { level: 6, className: "text-xs font-semibold uppercase tracking-wider text-zinc-500 dark:text-zinc-400 mb-3", children: "Discount Type" }), _jsxs(Stack, { gap: "xs", children: [COUPON_TYPES.map((t) => (_jsxs("label", { className: "flex items-center gap-2 cursor-pointer text-sm text-zinc-700 dark:text-zinc-300", children: [_jsx("input", { type: "radio", name: "coupon-type", value: t.value, checked: pendingTable.get(TABLE_KEYS.TYPE) === t.value, onChange: () => { pendingTable.set(TABLE_KEYS.TYPE, t.value); }, className: "accent-primary" }), t.label] }, t.value))), pendingTable.get(TABLE_KEYS.TYPE) && (_jsx("button", { type: "button", onClick: () => { pendingTable.set(TABLE_KEYS.TYPE, ""); }, className: "text-xs text-zinc-400 hover:text-zinc-600 dark:hover:text-zinc-300 underline", children: "Clear type" }))] })] }), _jsxs(_Fragment, { children: [_jsx(Heading, { level: 6, className: "text-xs font-semibold uppercase tracking-wider text-zinc-500 dark:text-zinc-400 mb-3", children: "Valid Date Range" }), _jsxs(Stack, { gap: "sm", children: [_jsxs(_Fragment, { children: [_jsx("label", { className: "block text-xs text-zinc-500 dark:text-zinc-400 mb-1", children: "From date" }), _jsx("input", { type: "date", value: pendingTable.get(TABLE_KEYS.DATE_FROM) || "", onChange: (e) => { pendingTable.set(TABLE_KEYS.DATE_FROM, e.target.value); }, className: "w-full rounded-lg border border-zinc-300 dark:border-slate-600 bg-white dark:bg-slate-800 px-3 py-2 text-sm text-zinc-900 dark:text-zinc-100 outline-none focus:ring-2 focus:ring-primary" })] }), _jsxs(_Fragment, { children: [_jsx("label", { className: "block text-xs text-zinc-500 dark:text-zinc-400 mb-1", children: "To date" }), _jsx("input", { type: "date", value: pendingTable.get(TABLE_KEYS.DATE_TO) || "", onChange: (e) => { pendingTable.set(TABLE_KEYS.DATE_TO, e.target.value); }, className: "w-full rounded-lg border border-zinc-300 dark:border-slate-600 bg-white dark:bg-slate-800 px-3 py-2 text-sm text-zinc-900 dark:text-zinc-100 outline-none focus:ring-2 focus:ring-primary" })] })] })] })] })] }));
|
|
102
102
|
}
|
|
@@ -4,6 +4,5 @@
|
|
|
4
4
|
* Server-only entry point for promotions repositories and API route handlers.
|
|
5
5
|
*/
|
|
6
6
|
export * from "./actions";
|
|
7
|
-
export { PromotionsRepository } from "./repository/promotions.repository";
|
|
8
7
|
export { couponsRepository } from "./repository/coupons.repository";
|
|
9
8
|
export { GET as promotionsGET, GET } from "./api/route";
|
|
@@ -4,6 +4,5 @@
|
|
|
4
4
|
* Server-only entry point for promotions repositories and API route handlers.
|
|
5
5
|
*/
|
|
6
6
|
export * from "./actions";
|
|
7
|
-
export { PromotionsRepository } from "./repository/promotions.repository";
|
|
8
7
|
export { couponsRepository } from "./repository/coupons.repository";
|
|
9
8
|
export { GET as promotionsGET, GET } from "./api/route";
|
|
@@ -5,7 +5,7 @@ const CLS_RELATED_LINK = "group flex items-center gap-3 rounded-xl border border
|
|
|
5
5
|
const CLS_RELATED_LABEL = "text-xs text-zinc-400 dark:text-zinc-400 mb-0.5";
|
|
6
6
|
const CLS_RELATED_TITLE = "text-sm font-medium text-neutral-900 dark:text-white truncate group-hover:text-primary transition-colors";
|
|
7
7
|
import Link from "next/link";
|
|
8
|
-
import { Heading, RichText, Section, Span, StarRating, Text } from "../../../ui";
|
|
8
|
+
import { Div, Grid, Heading, RichText, Row, Section, Span, StarRating, Stack, Text } from "../../../ui";
|
|
9
9
|
import { maskName } from "../../../security";
|
|
10
10
|
import { getDefaultLocale } from "../../../core/baseline-resolver";
|
|
11
11
|
import { normalizeRichTextHtml } from "../../../utils/string.formatter";
|
|
@@ -77,9 +77,9 @@ export function ReviewDetailShell({ review, storeHref }) {
|
|
|
77
77
|
? String(ROUTES.PUBLIC.PROFILE(reviewerProfileId))
|
|
78
78
|
: null;
|
|
79
79
|
const currentImage = lightboxIdx !== null ? images[lightboxIdx] : null;
|
|
80
|
-
return (_jsxs(_Fragment, { children: [_jsx(
|
|
80
|
+
return (_jsxs(_Fragment, { children: [_jsx(Div, { className: "border-b border-neutral-200 dark:border-zinc-800 bg-white dark:bg-zinc-900 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, { className: "inline-flex items-center gap-1 rounded-full bg-success-surface px-3 py-1 text-xs font-semibold text-success", children: "\u2713 Verified Purchase" })), review.featured && (_jsx(Span, { className: "inline-flex items-center gap-1 rounded-full bg-yellow-100 px-3 py-1 text-xs font-semibold text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400", 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, { weight: "semibold", className: "text-sm 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, { className: "text-white text-xl", 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: "overflow-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
|
|
81
81
|
? "border-emerald-200 bg-success-surface text-success dark:border-emerald-800 cursor-default"
|
|
82
|
-
: "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: "flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-lg bg-orange-100 dark:bg-orange-900/30 text-xl", children: "\uD83D\uDCE6" }), _jsxs(
|
|
82
|
+
: "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: "flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-lg bg-orange-100 dark:bg-orange-900/30 text-xl", 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: "flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-lg bg-blue-100 dark:bg-blue-900/30 text-xl", 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: "flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-lg bg-purple-100 dark:bg-purple-900/30 text-xl", 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, { gap: "sm", className: "rounded-xl border border-neutral-200 dark:border-zinc-700 bg-white dark:bg-zinc-900 p-4", children: [_jsx(Span, { className: "flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-lg bg-purple-100 dark:bg-purple-900/30 text-xl", children: "\uD83D\uDC64" }), _jsxs(Div, { className: "min-w-0", children: [_jsx(Span, { className: "block text-xs text-zinc-400 dark:text-zinc-400 mb-0.5", children: "Reviewer" }), _jsx(Span, { className: "block text-sm font-medium 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
|
|
83
83
|
? "border-white scale-110"
|
|
84
84
|
: "border-transparent opacity-60 hover:opacity-100"}`, style: { backgroundImage: `url(${img.thumbnailUrl ?? img.url})` } }, i))) }))] }))] }));
|
|
85
85
|
}
|
|
@@ -21,6 +21,8 @@
|
|
|
21
21
|
import { NextResponse } from "next/server.js";
|
|
22
22
|
import { getProviders } from "../../../contracts";
|
|
23
23
|
import { SearchRepository } from "../repository/search.repository";
|
|
24
|
+
import { isListingTypeEnabled, enabledListingTypes, } from "../../../_internal/shared/listing-types/feature-flags";
|
|
25
|
+
import { getSiteSettingsGlobal } from "../../admin/utils/getSiteSettingsGlobal";
|
|
24
26
|
function param(url, key) {
|
|
25
27
|
return url.searchParams.get(key);
|
|
26
28
|
}
|
|
@@ -55,6 +57,32 @@ export async function GET(request) {
|
|
|
55
57
|
const maxPriceRaw = numParam(url, "maxPrice", 0);
|
|
56
58
|
const page = numParam(url, "page", 1);
|
|
57
59
|
const pageSize = Math.min(numParam(url, "pageSize", 20), 100);
|
|
60
|
+
// W1-43 — listing-type feature flag gating. Reject a search scoped to a
|
|
61
|
+
// disabled listing type with an empty result; for unscoped searches,
|
|
62
|
+
// strip disabled types post-query so search-bar suggestions stay clean.
|
|
63
|
+
let siteSettings = null;
|
|
64
|
+
try {
|
|
65
|
+
siteSettings = await getSiteSettingsGlobal();
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
siteSettings = null;
|
|
69
|
+
}
|
|
70
|
+
if (listingType && siteSettings && !isListingTypeEnabled(listingType, siteSettings)) {
|
|
71
|
+
return NextResponse.json({
|
|
72
|
+
success: true,
|
|
73
|
+
data: {
|
|
74
|
+
items: [],
|
|
75
|
+
total: 0,
|
|
76
|
+
page,
|
|
77
|
+
pageSize,
|
|
78
|
+
totalPages: 0,
|
|
79
|
+
hasMore: false,
|
|
80
|
+
backend: "firestore",
|
|
81
|
+
listingTypeDisabled: true,
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
const enabledSet = new Set(siteSettings ? enabledListingTypes(siteSettings) : []);
|
|
58
86
|
const repo = new SearchRepository(db.getRepository("products"));
|
|
59
87
|
const result = await repo.search({
|
|
60
88
|
q: param(url, "q") ?? undefined,
|
|
@@ -70,9 +98,24 @@ export async function GET(request) {
|
|
|
70
98
|
page,
|
|
71
99
|
pageSize,
|
|
72
100
|
});
|
|
101
|
+
// W1-43 — when no specific listingType filter, strip disabled types from
|
|
102
|
+
// the surface. Skipped when all types are enabled or settings failed to load.
|
|
103
|
+
let filteredItems = result.items;
|
|
104
|
+
let filteredTotal = result.total;
|
|
105
|
+
if (!listingType && enabledSet.size > 0 && enabledSet.size < 7) {
|
|
106
|
+
const before = filteredItems.length;
|
|
107
|
+
filteredItems = filteredItems.filter((it) => enabledSet.has(it.listingType ?? "standard"));
|
|
108
|
+
const removed = before - filteredItems.length;
|
|
109
|
+
filteredTotal = Math.max(0, filteredTotal - removed);
|
|
110
|
+
}
|
|
73
111
|
return NextResponse.json({
|
|
74
112
|
success: true,
|
|
75
|
-
data: {
|
|
113
|
+
data: {
|
|
114
|
+
...result,
|
|
115
|
+
items: filteredItems,
|
|
116
|
+
total: filteredTotal,
|
|
117
|
+
backend: "firestore",
|
|
118
|
+
},
|
|
76
119
|
});
|
|
77
120
|
}
|
|
78
121
|
catch (error) {
|
|
@@ -12,12 +12,14 @@ const DEFAULT_TYPE_ICONS = {
|
|
|
12
12
|
category: "🗂️",
|
|
13
13
|
blog: "✍️",
|
|
14
14
|
event: "🎉",
|
|
15
|
+
product: "📦",
|
|
15
16
|
};
|
|
16
17
|
const DEFAULT_TYPE_BADGES = {
|
|
17
18
|
page: "bg-zinc-100 text-zinc-600 dark:bg-zinc-800 dark:text-zinc-300",
|
|
18
19
|
category: "bg-blue-100 text-blue-600 dark:bg-blue-900/40 dark:text-blue-300",
|
|
19
20
|
blog: "bg-fuchsia-100 text-fuchsia-600 dark:bg-fuchsia-900/40 dark:text-fuchsia-300",
|
|
20
21
|
event: "bg-amber-100 text-amber-600 dark:bg-amber-900/40 dark:text-amber-300",
|
|
22
|
+
product: "bg-emerald-100 text-emerald-600 dark:bg-emerald-900/40 dark:text-emerald-300",
|
|
21
23
|
};
|
|
22
24
|
function SuggestionItem({ suggestion, itemIndex, activeIndex, onSelect, onHover, typeIcons, typeBadges, }) {
|
|
23
25
|
const isActive = activeIndex === itemIndex;
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
export interface NavSuggestionRecord {
|
|
2
2
|
objectID: string;
|
|
3
|
-
type: "page" | "category" | "blog" | "event";
|
|
3
|
+
type: "page" | "category" | "blog" | "event" | "product";
|
|
4
4
|
title: string;
|
|
5
5
|
subtitle?: string;
|
|
6
6
|
url: string;
|
|
7
7
|
}
|
|
8
|
+
/**
|
|
9
|
+
* W1-19 — wired to `/api/search/suggestions` 2026-05-23. Fetches up to 20
|
|
10
|
+
* matches (5 per resource type) and surfaces them as typeahead rows.
|
|
11
|
+
*/
|
|
8
12
|
export declare function useNavSuggestions(query: string, selectedType?: string, debounceMs?: number): {
|
|
9
13
|
suggestions: NavSuggestionRecord[];
|
|
10
14
|
isLoading: boolean;
|
|
@@ -1,23 +1,48 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { useEffect, useRef, useState } from "react";
|
|
3
|
+
const SUGGESTIONS_ENDPOINT = "/api/search/suggestions";
|
|
4
|
+
/**
|
|
5
|
+
* W1-19 — wired to `/api/search/suggestions` 2026-05-23. Fetches up to 20
|
|
6
|
+
* matches (5 per resource type) and surfaces them as typeahead rows.
|
|
7
|
+
*/
|
|
3
8
|
export function useNavSuggestions(query, selectedType, debounceMs = 250) {
|
|
4
9
|
const [suggestions, setSuggestions] = useState([]);
|
|
5
10
|
const [isLoading, setIsLoading] = useState(false);
|
|
6
11
|
const timerRef = useRef(null);
|
|
12
|
+
const abortRef = useRef(null);
|
|
7
13
|
useEffect(() => {
|
|
8
14
|
if (timerRef.current)
|
|
9
15
|
clearTimeout(timerRef.current);
|
|
10
|
-
if (
|
|
16
|
+
if (abortRef.current)
|
|
17
|
+
abortRef.current.abort();
|
|
18
|
+
const trimmed = query.trim();
|
|
19
|
+
if (!trimmed) {
|
|
11
20
|
setSuggestions([]);
|
|
21
|
+
setIsLoading(false);
|
|
12
22
|
return;
|
|
13
23
|
}
|
|
14
24
|
timerRef.current = setTimeout(async () => {
|
|
15
25
|
setIsLoading(true);
|
|
26
|
+
const ctrl = new AbortController();
|
|
27
|
+
abortRef.current = ctrl;
|
|
16
28
|
try {
|
|
17
|
-
|
|
29
|
+
const params = new URLSearchParams({ q: trimmed });
|
|
30
|
+
if (selectedType && selectedType !== "all") {
|
|
31
|
+
params.set("type", selectedType);
|
|
32
|
+
}
|
|
33
|
+
const res = await fetch(`${SUGGESTIONS_ENDPOINT}?${params.toString()}`, { signal: ctrl.signal, credentials: "same-origin" });
|
|
34
|
+
if (!res.ok) {
|
|
35
|
+
setSuggestions([]);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const body = (await res.json());
|
|
39
|
+
const items = body.data?.suggestions ?? body.suggestions ?? [];
|
|
40
|
+
setSuggestions(items);
|
|
18
41
|
}
|
|
19
|
-
catch {
|
|
20
|
-
|
|
42
|
+
catch (err) {
|
|
43
|
+
if (err.name !== "AbortError") {
|
|
44
|
+
setSuggestions([]);
|
|
45
|
+
}
|
|
21
46
|
}
|
|
22
47
|
finally {
|
|
23
48
|
setIsLoading(false);
|
|
@@ -26,6 +51,8 @@ export function useNavSuggestions(query, selectedType, debounceMs = 250) {
|
|
|
26
51
|
return () => {
|
|
27
52
|
if (timerRef.current)
|
|
28
53
|
clearTimeout(timerRef.current);
|
|
54
|
+
if (abortRef.current)
|
|
55
|
+
abortRef.current.abort();
|
|
29
56
|
};
|
|
30
57
|
}, [query, selectedType, debounceMs]);
|
|
31
58
|
return { suggestions, isLoading };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
import {
|
|
3
|
+
import { PaginatedSelect } from "../../../ui/components/PaginatedSelect";
|
|
4
4
|
import { BrandQuickCreateForm } from "../../admin/components/BrandQuickCreateForm";
|
|
5
5
|
import { apiClient } from "../../../http";
|
|
6
6
|
import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
@@ -40,7 +40,7 @@ async function loadPublicBrandOptions(query, page) {
|
|
|
40
40
|
}
|
|
41
41
|
export function BrandInlineSelect({ value, onChange, placeholder = "Search brands…", disabled, allowCreate = true, }) {
|
|
42
42
|
const loadOptions = allowCreate ? loadAdminBrandOptions : loadPublicBrandOptions;
|
|
43
|
-
return (_jsx(
|
|
43
|
+
return (_jsx(PaginatedSelect, { value: value || null, onChange: (v) => onChange(v ?? ""), loadOptions: loadOptions, placeholder: placeholder, disabled: disabled, createLabel: "brand", drawerTitle: "Create Brand", renderCreateForm: allowCreate
|
|
44
44
|
? ({ onCreated, onCancel }) => (_jsx(BrandQuickCreateForm, { onSaved: (id, name) => onCreated({ value: id, label: name }), onCancel: onCancel }))
|
|
45
45
|
: undefined }));
|
|
46
46
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
import {
|
|
3
|
+
import { PaginatedSelect } from "../../../ui/components/PaginatedSelect";
|
|
4
4
|
import { CategoryQuickCreateForm } from "../../admin/components/CategoryQuickCreateForm";
|
|
5
5
|
import { apiClient } from "../../../http";
|
|
6
6
|
import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
@@ -37,7 +37,7 @@ async function loadPublicCategoryOptions(query, _page) {
|
|
|
37
37
|
}
|
|
38
38
|
export function CategoryInlineSelect({ value, onChange, placeholder = "Search categories…", disabled, allowCreate = false, }) {
|
|
39
39
|
const loadOptions = allowCreate ? loadAdminCategoryOptions : loadPublicCategoryOptions;
|
|
40
|
-
return (_jsx(
|
|
40
|
+
return (_jsx(PaginatedSelect, { value: value || null, onChange: (v) => onChange(v ?? ""), loadOptions: loadOptions, placeholder: placeholder, disabled: disabled, createLabel: "category", drawerTitle: "Create Category", renderCreateForm: allowCreate
|
|
41
41
|
? ({ onCreated, onCancel }) => (_jsx(CategoryQuickCreateForm, { onSaved: (id, name) => onCreated({ value: id, label: name }), onCancel: onCancel }))
|
|
42
42
|
: undefined }));
|
|
43
43
|
}
|