@mohasinac/appkit 4.53.0 → 4.53.2
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/.build-stamp.json +3 -3
- package/dist/_internal/client/scaffolds/AppShell.js +1 -1
- package/dist/_internal/client/theme/ThemeProvider.js +15 -1
- package/dist/_internal/server/features/auctions/og.d.ts +3 -1
- package/dist/_internal/server/features/auctions/og.js +3 -1
- package/dist/_internal/server/features/bundles/actions.js +1 -1
- package/dist/_internal/server/features/business-documents/download.d.ts +41 -0
- package/dist/_internal/server/features/business-documents/download.js +91 -0
- package/dist/_internal/server/features/categories/adapters.d.ts +1 -1
- package/dist/_internal/server/features/categories/adapters.js +10 -0
- package/dist/_internal/server/features/categories/price-guidance-resolve.d.ts +66 -0
- package/dist/_internal/server/features/categories/price-guidance-resolve.js +138 -0
- package/dist/_internal/server/features/checkout/actions.d.ts +14 -0
- package/dist/_internal/server/features/checkout/actions.js +106 -12
- package/dist/_internal/server/features/checkout/bundle-expansion.js +17 -0
- package/dist/_internal/server/features/checkout/data.d.ts +22 -0
- package/dist/_internal/server/features/checkout/group-rule-revalidation.d.ts +34 -0
- package/dist/_internal/server/features/checkout/group-rule-revalidation.js +124 -0
- package/dist/_internal/server/features/checkout/supplier-identity.d.ts +55 -0
- package/dist/_internal/server/features/checkout/supplier-identity.js +81 -0
- package/dist/_internal/server/features/classified/metadata.js +1 -1
- package/dist/_internal/server/features/classified/og.d.ts +3 -1
- package/dist/_internal/server/features/classified/og.js +3 -1
- package/dist/_internal/server/features/digital-code/metadata.js +1 -1
- package/dist/_internal/server/features/digital-code/og.d.ts +3 -1
- package/dist/_internal/server/features/digital-code/og.js +3 -1
- package/dist/_internal/server/features/digital-code/pool.d.ts +19 -4
- package/dist/_internal/server/features/digital-code/pool.js +19 -4
- package/dist/_internal/server/features/live/metadata.js +1 -1
- package/dist/_internal/server/features/live/og.d.ts +3 -1
- package/dist/_internal/server/features/live/og.js +3 -1
- package/dist/_internal/server/features/messages/actions.js +1 -1
- package/dist/_internal/server/features/parts/og.js +1 -1
- package/dist/_internal/server/features/pre-orders/og.d.ts +3 -1
- package/dist/_internal/server/features/pre-orders/og.js +3 -1
- package/dist/_internal/server/features/price-index/market.d.ts +57 -0
- package/dist/_internal/server/features/price-index/market.js +120 -0
- package/dist/_internal/server/features/price-index/search.d.ts +18 -0
- package/dist/_internal/server/features/price-index/search.js +47 -7
- package/dist/_internal/server/features/prize-draws/og.d.ts +3 -1
- package/dist/_internal/server/features/prize-draws/og.js +3 -1
- package/dist/_internal/server/features/products/group-members.d.ts +26 -0
- package/dist/_internal/server/features/products/group-members.js +7 -1
- package/dist/_internal/server/features/products/list-public.d.ts +12 -0
- package/dist/_internal/server/features/products/list-public.js +10 -1
- package/dist/_internal/server/features/products/listing-tab-counts.d.ts +25 -1
- package/dist/_internal/server/features/products/listing-tab-counts.js +128 -10
- package/dist/_internal/server/features/products/og.d.ts +3 -1
- package/dist/_internal/server/features/products/og.js +3 -1
- package/dist/_internal/server/features/search/scoped-sources.d.ts +26 -0
- package/dist/_internal/server/features/search/scoped-sources.js +252 -0
- package/dist/_internal/server/features/search/search-everything.d.ts +57 -0
- package/dist/_internal/server/features/search/search-everything.js +70 -20
- package/dist/_internal/server/features/seo/sitemap.js +6 -0
- package/dist/_internal/server/features/shared/listing-adapters.js +4 -2
- package/dist/_internal/server/features/sticker-trace/adapters.d.ts +11 -0
- package/dist/_internal/server/features/sticker-trace/adapters.js +1 -0
- package/dist/_internal/server/features/sticker-trace/promote-to-listings.d.ts +26 -0
- package/dist/_internal/server/features/sticker-trace/promote-to-listings.js +264 -0
- package/dist/_internal/server/features/sticker-trace/sheet-actions.d.ts +3 -1
- package/dist/_internal/server/features/sticker-trace/sheet-actions.js +32 -0
- package/dist/_internal/server/features/sticker-trace/storage.d.ts +20 -1
- package/dist/_internal/server/features/sticker-trace/storage.js +20 -1
- package/dist/_internal/server/features/stores/adapters.d.ts +1 -1
- package/dist/_internal/server/features/stores/adapters.js +16 -0
- package/dist/_internal/server/features/stores/index.d.ts +1 -0
- package/dist/_internal/server/features/stores/index.js +1 -0
- package/dist/_internal/server/features/stores/tabs.d.ts +30 -0
- package/dist/_internal/server/features/stores/tabs.js +124 -0
- package/dist/_internal/shared/checkout/rules/_defaults.js +0 -2
- package/dist/_internal/shared/checkout/rules/_limits.d.ts +14 -1
- package/dist/_internal/shared/checkout/rules/_limits.js +14 -1
- package/dist/_internal/shared/checkout/rules/_registry.js +2 -0
- package/dist/_internal/shared/checkout/rules/auction.rule.js +9 -1
- package/dist/_internal/shared/checkout/rules/bundle.rule.js +13 -0
- package/dist/_internal/shared/checkout/rules/index.d.ts +2 -0
- package/dist/_internal/shared/checkout/rules/index.js +9 -0
- package/dist/_internal/shared/checkout/rules/offer.rule.js +0 -1
- package/dist/_internal/shared/checkout/rules/payment-gate.d.ts +26 -0
- package/dist/_internal/shared/checkout/rules/payment-gate.js +133 -0
- package/dist/_internal/shared/checkout/rules/prize-draw.rule.js +29 -8
- package/dist/_internal/shared/checkout/rules/spares.rule.d.ts +16 -0
- package/dist/_internal/shared/checkout/rules/spares.rule.js +5 -0
- package/dist/_internal/shared/checkout/rules/types.d.ts +26 -2
- package/dist/_internal/shared/config/schema.d.ts +122 -0
- package/dist/_internal/shared/features/cart/schema.js +3 -2
- package/dist/_internal/shared/features/products/group-rules.d.ts +75 -0
- package/dist/_internal/shared/features/products/group-rules.js +142 -0
- package/dist/_internal/shared/listing-types/_registry.d.ts +24 -0
- package/dist/_internal/shared/listing-types/_registry.js +39 -1
- package/dist/_internal/shared/listing-types/art/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/art/config.js +2 -0
- package/dist/_internal/shared/listing-types/auction/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/auction/config.js +2 -0
- package/dist/_internal/shared/listing-types/availability.js +48 -0
- package/dist/_internal/shared/listing-types/capabilities.js +19 -0
- package/dist/_internal/shared/listing-types/classified/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/classified/config.js +2 -0
- package/dist/_internal/shared/listing-types/digital-code/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/digital-code/config.js +2 -0
- package/dist/_internal/shared/listing-types/feature-flags.js +1 -0
- package/dist/_internal/shared/listing-types/live/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/live/config.js +2 -0
- package/dist/_internal/shared/listing-types/pre-order/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/pre-order/config.js +2 -0
- package/dist/_internal/shared/listing-types/prize-draw/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/prize-draw/config.js +2 -0
- package/dist/_internal/shared/listing-types/spares/config.d.ts +74 -0
- package/dist/_internal/shared/listing-types/spares/config.js +60 -0
- package/dist/_internal/shared/listing-types/spares/ctas.d.ts +1 -0
- package/dist/_internal/shared/listing-types/spares/ctas.js +4 -0
- package/dist/_internal/shared/listing-types/spares/og.js +1 -0
- package/dist/_internal/shared/listing-types/spares/schema.d.ts +2 -0
- package/dist/_internal/shared/listing-types/spares/schema.js +3 -0
- package/dist/_internal/shared/listing-types/spares/seed-factory.d.ts +1 -0
- package/dist/_internal/shared/listing-types/spares/seed-factory.js +1 -0
- package/dist/_internal/shared/listing-types/standard/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/standard/config.js +2 -0
- package/dist/_internal/shared/listing-types/stickers/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/stickers/config.js +2 -0
- package/dist/_internal/shared/search/match.d.ts +11 -0
- package/dist/_internal/shared/search/match.js +30 -3
- package/dist/_internal/shared/search/to-groups.d.ts +52 -0
- package/dist/_internal/shared/search/to-groups.js +141 -0
- package/dist/client.d.ts +8 -2
- package/dist/client.js +4 -1
- package/dist/constants/api-endpoints.d.ts +46 -15
- package/dist/constants/api-endpoints.js +35 -5
- package/dist/constants/cache-invalidation.js +3 -1
- package/dist/constants/field-names.d.ts +6 -0
- package/dist/constants/field-names.js +6 -0
- package/dist/features/about/components/PolicyPageView.d.ts +22 -2
- package/dist/features/about/components/PolicyPageView.js +14 -2
- package/dist/features/about/components/PublicProfileView.js +2 -1
- package/dist/features/about/components/ShippingPolicyView.js +42 -2
- package/dist/features/account/components/NotificationBell.js +1 -1
- package/dist/features/account/hooks/useProfileStats.js +5 -1
- package/dist/features/addresses/repository/addresses.repository.d.ts +16 -0
- package/dist/features/addresses/repository/addresses.repository.js +21 -0
- package/dist/features/admin/actions/admin-actions.js +16 -1
- package/dist/features/admin/components/AdminCategoryEditorView.js +19 -2
- package/dist/features/admin/components/AdminNavigationView.js +1 -1
- package/dist/features/admin/components/AdminPageHeader.js +1 -1
- package/dist/features/admin/components/AdminProductEditorView.js +17 -2
- package/dist/features/admin/components/AdminSidebar.js +1 -1
- package/dist/features/admin/components/AdminSiteSettingsView.js +39 -8
- package/dist/features/admin/components/AdminSparesView.d.ts +12 -0
- package/dist/features/admin/components/AdminSparesView.js +30 -0
- package/dist/features/admin/components/AdminUsersView.js +9 -7
- package/dist/features/admin/components/index.d.ts +2 -0
- package/dist/features/admin/components/index.js +1 -0
- package/dist/features/admin/components/sections/adminSectionsTypes.js +7 -0
- package/dist/features/admin/constants/filter-tabs.d.ts +3 -0
- package/dist/features/admin/constants/filter-tabs.js +1 -0
- package/dist/features/admin/schemas/firestore.d.ts +26 -2
- package/dist/features/auctions/components/AuctionDetailPageView.js +12 -18
- package/dist/features/auth/repository/user.repository.d.ts +13 -0
- package/dist/features/auth/repository/user.repository.js +28 -0
- package/dist/features/auth/schemas/firestore.d.ts +2 -0
- package/dist/features/auth/schemas/firestore.js +2 -0
- package/dist/features/business-documents/repository/business-documents.repository.d.ts +58 -0
- package/dist/features/business-documents/repository/business-documents.repository.js +54 -0
- package/dist/features/business-documents/schemas/firestore.d.ts +105 -0
- package/dist/features/business-documents/schemas/firestore.js +75 -0
- package/dist/features/cart/actions/cart-actions.js +83 -6
- package/dist/features/cart/repository/cart.repository.js +32 -4
- package/dist/features/cart/schemas/firestore.d.ts +20 -0
- package/dist/features/cart/schemas/index.d.ts +2 -2
- package/dist/features/catalogue/components/PublicCatalogueItemDetailView.js +1 -1
- package/dist/features/categories/api/[id]/price-guidance/route.d.ts +42 -0
- package/dist/features/categories/api/[id]/price-guidance/route.js +90 -0
- package/dist/features/categories/components/BrandDetailPageView.js +32 -8
- package/dist/features/categories/components/BrandDetailTabs.d.ts +7 -1
- package/dist/features/categories/components/BrandDetailTabs.js +28 -3
- package/dist/features/categories/components/BundleCollage.js +3 -2
- package/dist/features/categories/components/BundleItemsPicker.js +3 -4
- package/dist/features/categories/components/CategoriesIndexListing.js +23 -8
- package/dist/features/categories/components/CategoryDetailPageView.js +157 -20
- package/dist/features/categories/components/CategoryDetailTabs.d.ts +7 -1
- package/dist/features/categories/components/CategoryDetailTabs.js +41 -7
- package/dist/features/categories/components/CategoryGrid.js +10 -1
- package/dist/features/categories/components/PriceGuidanceHint.d.ts +29 -0
- package/dist/features/categories/components/PriceGuidanceHint.js +61 -0
- package/dist/features/categories/components/SubcategoryCardRow.d.ts +27 -0
- package/dist/features/categories/components/SubcategoryCardRow.js +98 -0
- package/dist/features/categories/constants/category-tabs.d.ts +20 -25
- package/dist/features/categories/constants/category-tabs.js +22 -39
- package/dist/features/categories/repository/categories.repository.d.ts +12 -0
- package/dist/features/categories/repository/categories.repository.js +20 -0
- package/dist/features/categories/schemas/firestore.d.ts +22 -1
- package/dist/features/categories/schemas/firestore.js +10 -0
- package/dist/features/categories/schemas/index.d.ts +6 -0
- package/dist/features/categories/schemas/index.js +2 -0
- package/dist/features/categories/types/index.d.ts +8 -0
- package/dist/features/categories/utils/taxonomy-visual.d.ts +16 -3
- package/dist/features/categories/utils/taxonomy-visual.js +26 -10
- package/dist/features/classified/components/ClassifiedDetailPageView.js +14 -10
- package/dist/features/contact/components/ContactPageView.d.ts +33 -1
- package/dist/features/contact/components/ContactPageView.js +58 -28
- package/dist/features/digital-codes/components/DigitalCodeDetailPageView.js +14 -10
- package/dist/features/grouped/components/GroupedListingDetailView.js +9 -1
- package/dist/features/grouped/schemas/firestore.d.ts +5 -0
- package/dist/features/homepage/actions/homepage-section-actions.d.ts +3 -3
- package/dist/features/homepage/components/CollectionCardsSection.d.ts +28 -6
- package/dist/features/homepage/components/CollectionCardsSection.js +43 -8
- package/dist/features/homepage/components/HowItWorksSection.d.ts +23 -6
- package/dist/features/homepage/components/HowItWorksSection.js +4 -4
- package/dist/features/homepage/components/PromoGrid.d.ts +31 -2
- package/dist/features/homepage/components/PromoGrid.js +12 -3
- package/dist/features/homepage/components/TestimonialsCarousel.d.ts +34 -2
- package/dist/features/homepage/components/TestimonialsCarousel.js +1 -1
- package/dist/features/homepage/components/index.d.ts +0 -20
- package/dist/features/homepage/components/index.js +0 -11
- package/dist/features/homepage/index.d.ts +0 -2
- package/dist/features/homepage/index.js +0 -2
- package/dist/features/homepage/lib/section-renderer.d.ts +9 -0
- package/dist/features/homepage/lib/section-renderer.js +202 -58
- package/dist/features/homepage/repository/homepage-sections.repository.d.ts +35 -3
- package/dist/features/homepage/repository/homepage-sections.repository.js +58 -8
- package/dist/features/homepage/schemas/firestore.d.ts +106 -3
- package/dist/features/homepage/schemas/firestore.js +32 -2
- package/dist/features/homepage/schemas/index.d.ts +36 -7
- package/dist/features/homepage/schemas/index.js +39 -27
- package/dist/features/homepage/server.d.ts +0 -3
- package/dist/features/homepage/server.js +0 -3
- package/dist/features/homepage/types/index.d.ts +0 -47
- package/dist/features/homepage/types/index.js +49 -0
- package/dist/features/layout/AppLayoutShell.js +1 -1
- package/dist/features/layout/BottomNavItem.js +1 -1
- package/dist/features/layout/BottomSheet.js +1 -1
- package/dist/features/layout/TitleBarLayout.js +1 -1
- package/dist/features/live/components/LiveItemDetailPageView.js +14 -10
- package/dist/features/media/hooks/useMedia.js +17 -0
- package/dist/features/media/repository/media-assets.repository.d.ts +46 -0
- package/dist/features/media/repository/media-assets.repository.js +31 -0
- package/dist/features/media/trust-signals.d.ts +85 -0
- package/dist/features/media/trust-signals.js +126 -0
- package/dist/features/parts/components/PartDetailPageView.d.ts +14 -1
- package/dist/features/parts/components/PartDetailPageView.js +2 -2
- package/dist/features/parts/components/PartsIndexPageView.d.ts +11 -2
- package/dist/features/parts/components/PartsIndexPageView.js +2 -2
- package/dist/features/parts/components/PartsSearchBox.d.ts +9 -0
- package/dist/features/parts/components/PartsSearchBox.js +57 -0
- package/dist/features/parts/schemas/firestore.d.ts +149 -3
- package/dist/features/parts/schemas/firestore.js +184 -0
- package/dist/features/payments/repository/payout.repository.d.ts +15 -0
- package/dist/features/payments/repository/payout.repository.js +21 -0
- package/dist/features/pre-orders/components/PreOrderDetailPageView.js +12 -18
- package/dist/features/pre-orders/components/PreOrdersIndexListing.js +1 -1
- package/dist/features/price-index/components/PriceIndexToolView.d.ts +24 -1
- package/dist/features/price-index/components/PriceIndexToolView.js +299 -17
- package/dist/features/price-index/lookup-target.d.ts +48 -0
- package/dist/features/price-index/lookup-target.js +107 -0
- package/dist/features/products/components/AvailabilityScopeHint.d.ts +22 -0
- package/dist/features/products/components/AvailabilityScopeHint.js +27 -0
- package/dist/features/products/components/CompareOverlay.d.ts +20 -2
- package/dist/features/products/components/CompareOverlay.js +23 -3
- package/dist/features/products/components/CustomSectionTabContent.d.ts +31 -0
- package/dist/features/products/components/CustomSectionTabContent.js +29 -3
- package/dist/features/products/components/GroupMemberPicker.d.ts +19 -1
- package/dist/features/products/components/GroupMemberPicker.js +46 -8
- package/dist/features/products/components/GroupSettingsPanel.d.ts +11 -1
- package/dist/features/products/components/GroupSettingsPanel.js +92 -3
- package/dist/features/products/components/PrizeDrawDetailPageView.js +1 -1
- package/dist/features/products/components/PrizeDrawEntryActions.d.ts +15 -1
- package/dist/features/products/components/PrizeDrawEntryActions.js +70 -8
- package/dist/features/products/components/ProductDetailPageView.js +81 -17
- package/dist/features/products/components/ProductForm.d.ts +13 -1
- package/dist/features/products/components/ProductForm.js +16 -2
- package/dist/features/products/components/ProductsIndexListing.d.ts +16 -1
- package/dist/features/products/components/ProductsIndexListing.js +3 -3
- package/dist/features/products/components/ShowGroupSection.js +15 -2
- package/dist/features/products/components/SparesListView.d.ts +7 -0
- package/dist/features/products/components/SparesListView.js +39 -0
- package/dist/features/products/components/SublistingCarouselSection.js +2 -1
- package/dist/features/products/constants/action-defs.d.ts +1 -0
- package/dist/features/products/constants/action-defs.js +1 -0
- package/dist/features/products/repository/products.repository.d.ts +99 -0
- package/dist/features/products/repository/products.repository.js +240 -18
- package/dist/features/products/schemas/firestore.d.ts +97 -4
- package/dist/features/products/schemas/firestore.js +104 -0
- package/dist/features/products/schemas/index.d.ts +10 -19
- package/dist/features/products/schemas/index.js +23 -6
- package/dist/features/products/types/index.d.ts +45 -1
- package/dist/features/products/utils/group-member-image.d.ts +46 -0
- package/dist/features/products/utils/group-member-image.js +4 -0
- package/dist/features/products/utils/listing-badge-variant.js +4 -0
- package/dist/features/reviews/components/ReviewsList.js +1 -1
- package/dist/features/scams/components/ScamProfileView.js +1 -1
- package/dist/features/search/components/EntityTypeahead.d.ts +44 -0
- package/dist/features/search/components/EntityTypeahead.js +92 -0
- package/dist/features/search/components/Search.d.ts +12 -1
- package/dist/features/search/components/Search.js +47 -3
- package/dist/features/search/components/SearchFacetRail.d.ts +11 -0
- package/dist/features/search/components/SearchFacetRail.js +92 -0
- package/dist/features/search/components/SearchResultsClient.d.ts +33 -0
- package/dist/features/search/components/SearchResultsClient.js +103 -0
- package/dist/features/search/components/SearchResultsPageView.d.ts +15 -1
- package/dist/features/search/components/SearchResultsPageView.js +2 -2
- package/dist/features/search/components/index.d.ts +0 -6
- package/dist/features/search/components/index.js +0 -3
- package/dist/features/search/hooks/useNavSuggestions.d.ts +13 -0
- package/dist/features/search/hooks/useScopedSearch.d.ts +34 -0
- package/dist/features/search/hooks/useScopedSearch.js +114 -0
- package/dist/features/search/hooks/useSiteSearch.d.ts +26 -0
- package/dist/features/search/hooks/useSiteSearch.js +149 -0
- package/dist/features/search/index.d.ts +0 -4
- package/dist/features/search/index.js +0 -4
- package/dist/features/search/manifest.d.ts +12 -0
- package/dist/features/search/manifest.js +17 -2
- package/dist/features/search/server.d.ts +0 -3
- package/dist/features/search/server.js +0 -3
- package/dist/features/seller/components/SellerListingTypeView.d.ts +1 -1
- package/dist/features/seller/components/SellerListingTypeView.js +20 -0
- package/dist/features/seller/components/SellerPayoutSettingsView.js +35 -14
- package/dist/features/seller/components/SellerProductShell.d.ts +1 -1
- package/dist/features/seller/components/SellerProductShell.js +2 -1
- package/dist/features/seller/components/SellerProductsView.js +3 -2
- package/dist/features/seller/components/SellerSidebar.js +1 -1
- package/dist/features/seller/components/SellerSparesView.d.ts +17 -0
- package/dist/features/seller/components/SellerSparesView.js +6 -0
- package/dist/features/seller/components/index.d.ts +2 -0
- package/dist/features/seller/components/index.js +1 -0
- package/dist/features/shell/CommandPalette.js +1 -1
- package/dist/features/sticker-trace/schemas/firestore.d.ts +19 -0
- package/dist/features/stores/components/StoreAuctionsListing.js +2 -1
- package/dist/features/stores/components/StoreDetailLayoutView.d.ts +1 -1
- package/dist/features/stores/components/StoreDetailLayoutView.js +7 -82
- package/dist/features/stores/components/StoreHeader.js +1 -2
- package/dist/features/stores/components/StoreSparesPageView.d.ts +27 -0
- package/dist/features/stores/components/StoreSparesPageView.js +48 -0
- package/dist/features/stores/schemas/firestore.d.ts +19 -0
- package/dist/features/tester/seed-data/authored/buying__spares-and-groups.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/buying__spares-and-groups.js +96 -0
- package/dist/features/tester/seed-data/authored/content-discovery__authenticity-checker.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/content-discovery__authenticity-checker.js +392 -45
- package/dist/features/tester/seed-data/authored/content-discovery__category-tabs-franchise-bundles.js +54 -32
- package/dist/features/tester/seed-data/authored/content-discovery__parts-database.js +3 -2
- package/dist/features/tester/seed-data/authored/content-discovery__price-index-and-compare.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/content-discovery__price-index-and-compare.js +73 -0
- package/dist/features/tester/seed-data/authored/content-discovery__site-wide-index.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/content-discovery__site-wide-index.js +340 -0
- package/dist/features/tester/seed-data/authored/index.js +16 -0
- package/dist/features/tester/seed-data/authored/search-and-nav__header-search.d.ts +1 -0
- package/dist/features/tester/seed-data/authored/search-and-nav__header-search.js +57 -0
- package/dist/features/tester/seed-data/authored/selling__spares-and-group-editing.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/selling__spares-and-group-editing.js +77 -0
- package/dist/features/tester/seed-data/authored/tools__sticker-promote-to-listings.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/tools__sticker-promote-to-listings.js +58 -0
- package/dist/features/tester/seed-data/authored/tools__sticker-tracer.js +5 -4
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +384 -18
- package/dist/index.d.ts +17 -57
- package/dist/index.js +35 -96
- package/dist/next/routing/route-map.d.ts +42 -1
- package/dist/next/routing/route-map.js +45 -0
- package/dist/providers/db-firebase/base.repository.d.ts +22 -0
- package/dist/providers/db-firebase/base.repository.js +36 -0
- package/dist/schemas/registry.d.ts +15 -6
- package/dist/security/blind-index-tokens.d.ts +54 -0
- package/dist/security/blind-index-tokens.js +152 -0
- package/dist/security/index.d.ts +1 -1
- package/dist/security/index.js +1 -1
- package/dist/security/settings-encryption.d.ts +35 -0
- package/dist/security/settings-encryption.js +91 -0
- package/dist/seed/_helpers/brand-rows.js +38 -14
- package/dist/seed/_helpers/category-forest.js +73 -7
- package/dist/seed/_helpers/category-images-generated.d.ts +25 -6
- package/dist/seed/_helpers/category-images-generated.js +81 -6
- package/dist/seed/_helpers/category-models.d.ts +1 -1
- package/dist/seed/_helpers/category-models.js +7 -1
- package/dist/seed/_helpers/category-parts-glossary-map.js +8 -0
- package/dist/seed/_helpers/category-parts.d.ts +1 -1
- package/dist/seed/_helpers/category-parts.js +50 -2
- package/dist/seed/_helpers/character-features.d.ts +1 -1
- package/dist/seed/_helpers/character-features.js +2 -1
- package/dist/seed/_helpers/franchise-rows.js +10 -2
- package/dist/seed/_helpers/media.d.ts +47 -0
- package/dist/seed/_helpers/media.js +34 -0
- package/dist/seed/authenticity-knowledge-seed-data.d.ts +49 -5
- package/dist/seed/authenticity-knowledge-seed-data.js +10001 -549
- package/dist/seed/categories-seed-data.js +124 -20
- package/dist/seed/grouped-listings-seed-data.js +16 -0
- package/dist/seed/homepage-sections-seed-data.js +94 -5
- package/dist/seed/index.d.ts +2 -3
- package/dist/seed/index.js +2 -2
- package/dist/seed/manifest.js +3 -0
- package/dist/seed/media-assets-seed-data.d.ts +1 -1
- package/dist/seed/media-assets-seed-data.js +746 -196
- package/dist/seed/model-composition-seed-data.js +122 -6
- package/dist/seed/part-glossary-seed-data.d.ts +1 -1
- package/dist/seed/part-glossary-seed-data.js +37219 -463
- package/dist/seed/products-art-seed-data.js +7 -18
- package/dist/seed/products-auctions-seed-data.js +56 -47
- package/dist/seed/products-beyblade-x-seed-data.d.ts +2 -0
- package/dist/seed/products-beyblade-x-seed-data.js +476 -0
- package/dist/seed/products-catalogue-seed-data.d.ts +5 -5
- package/dist/seed/products-catalogue-seed-data.js +1500 -1240
- package/dist/seed/products-classifieds-seed-data.js +9 -27
- package/dist/seed/products-digital-codes-seed-data.js +9 -18
- package/dist/seed/products-live-items-seed-data.js +5 -13
- package/dist/seed/products-preorders-seed-data.js +67 -33
- package/dist/seed/products-prize-draws-seed-data.js +8 -17
- package/dist/seed/products-spares-seed-data.d.ts +2 -0
- package/dist/seed/products-spares-seed-data.js +368 -0
- package/dist/seed/products-standard-seed-data.js +39 -126
- package/dist/seed/products-stickers-seed-data.js +59 -15
- package/dist/seed/site-settings-seed-data.js +8 -0
- package/dist/seo/json-ld.d.ts +25 -0
- package/dist/seo/json-ld.js +28 -0
- package/dist/server-entry.d.ts +3 -1
- package/dist/server-entry.js +2 -2
- package/dist/server.d.ts +11 -9
- package/dist/server.js +35 -25
- package/dist/styles.css +202 -1
- package/dist/tailwind-input.css +31 -17
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/components/Accordion.js +1 -1
- package/dist/ui/components/Breadcrumb.js +1 -1
- package/dist/ui/components/DetailPageGallery.js +0 -2
- package/dist/ui/components/DetailPageHero.js +0 -1
- package/dist/ui/components/DetailPageTabs.js +1 -1
- package/dist/ui/components/Divider.js +3 -3
- package/dist/ui/components/GatedPrice.js +5 -1
- package/dist/ui/components/HorizontalScroller.js +1 -1
- package/dist/ui/components/ListingLayout.js +3 -3
- package/dist/ui/components/MediaTabs.style.css +81 -0
- package/dist/ui/components/Pagination.js +1 -1
- package/dist/ui/components/PendingValue.d.ts +45 -0
- package/dist/ui/components/PendingValue.js +7 -0
- package/dist/ui/components/Radio.style.css +119 -0
- package/dist/ui/components/RatingDisplay.js +2 -2
- package/dist/ui/components/SettingsSection.js +0 -2
- package/dist/ui/components/SiteLogo.js +3 -0
- package/dist/ui/components/SiteMark.js +3 -0
- package/dist/ui/components/SortDropdown.js +1 -1
- package/dist/ui/components/SpecTable.d.ts +58 -0
- package/dist/ui/components/SpecTable.js +8 -0
- package/dist/ui/components/StarRating.js +1 -3
- package/dist/ui/components/SummaryCard.js +1 -1
- package/dist/ui/components/index.style.css +1 -0
- package/dist/ui/icons/icon-registry.d.ts +1 -0
- package/dist/ui/icons/icon-registry.js +9 -1
- package/dist/ui/index.d.ts +4 -0
- package/dist/ui/index.js +2 -0
- package/dist/utils/search-txt-query.d.ts +12 -0
- package/dist/utils/search-txt-query.js +12 -0
- package/package.json +1 -1
- package/scripts/dedupe-peer-deps.mjs +18 -0
- package/scripts/seed-cli.mjs +250 -35
- package/scripts/verify-css-build.mjs +118 -37
- package/dist/_internal/server/features/search/actions.d.ts +0 -7
- package/dist/_internal/server/features/search/actions.js +0 -21
- package/dist/_internal/server/features/search/data.d.ts +0 -15
- package/dist/_internal/server/features/search/data.js +0 -18
- package/dist/_internal/server/features/search/index.d.ts +0 -3
- package/dist/_internal/server/features/search/index.js +0 -2
- package/dist/features/homepage/api/[id]/route.d.ts +0 -23
- package/dist/features/homepage/api/[id]/route.js +0 -103
- package/dist/features/homepage/api/route.d.ts +0 -14
- package/dist/features/homepage/api/route.js +0 -140
- package/dist/features/homepage/api/sections/[id]/route.d.ts +0 -26
- package/dist/features/homepage/api/sections/[id]/route.js +0 -95
- package/dist/features/homepage/api/sections/route.d.ts +0 -13
- package/dist/features/homepage/api/sections/route.js +0 -82
- package/dist/features/homepage/components/AdvertisementBanner.d.ts +0 -14
- package/dist/features/homepage/components/AdvertisementBanner.js +0 -35
- package/dist/features/homepage/components/BeforeAfterCard.d.ts +0 -9
- package/dist/features/homepage/components/BeforeAfterCard.js +0 -73
- package/dist/features/homepage/components/FeaturedResultsSection.d.ts +0 -17
- package/dist/features/homepage/components/FeaturedResultsSection.js +0 -17
- package/dist/features/homepage/components/HeroBanner.d.ts +0 -7
- package/dist/features/homepage/components/HeroBanner.js +0 -43
- package/dist/features/homepage/components/HeroSection.d.ts +0 -7
- package/dist/features/homepage/components/HeroSection.js +0 -6
- package/dist/features/homepage/components/HomepageSkeleton.d.ts +0 -2
- package/dist/features/homepage/components/HomepageSkeleton.js +0 -21
- package/dist/features/homepage/components/HomepageView.d.ts +0 -25
- package/dist/features/homepage/components/HomepageView.js +0 -5
- package/dist/features/homepage/components/NewsletterBanner.d.ts +0 -23
- package/dist/features/homepage/components/NewsletterBanner.js +0 -37
- package/dist/features/homepage/components/SiteFeaturesSection.d.ts +0 -14
- package/dist/features/homepage/components/SiteFeaturesSection.js +0 -11
- package/dist/features/homepage/components/TrustBadges.d.ts +0 -6
- package/dist/features/homepage/components/TrustBadges.js +0 -46
- package/dist/features/homepage/components/TrustIndicatorsSection.d.ts +0 -12
- package/dist/features/homepage/components/TrustIndicatorsSection.js +0 -11
- package/dist/features/homepage/hooks/useHomepage.d.ts +0 -11
- package/dist/features/homepage/hooks/useHomepage.js +0 -17
- package/dist/features/homepage/hooks/useHomepageSections.d.ts +0 -2
- package/dist/features/homepage/hooks/useHomepageSections.js +0 -10
- package/dist/features/homepage/repository/homepage.repository.d.ts +0 -11
- package/dist/features/homepage/repository/homepage.repository.js +0 -25
- package/dist/features/search/actions/index.d.ts +0 -1
- package/dist/features/search/actions/index.js +0 -1
- package/dist/features/search/actions/search-actions.d.ts +0 -18
- package/dist/features/search/actions/search-actions.js +0 -43
- package/dist/features/search/api/route.d.ts +0 -22
- package/dist/features/search/api/route.js +0 -128
- package/dist/features/search/columns/index.d.ts +0 -17
- package/dist/features/search/columns/index.js +0 -49
- package/dist/features/search/components/SearchFiltersRow.d.ts +0 -22
- package/dist/features/search/components/SearchFiltersRow.js +0 -21
- package/dist/features/search/components/SearchResultsSection.d.ts +0 -53
- package/dist/features/search/components/SearchResultsSection.js +0 -35
- package/dist/features/search/components/SearchView.d.ts +0 -27
- package/dist/features/search/components/SearchView.js +0 -8
- package/dist/features/search/hooks/useSearch.d.ts +0 -21
- package/dist/features/search/hooks/useSearch.js +0 -33
- package/dist/features/search/repository/search.repository.d.ts +0 -14
- package/dist/features/search/repository/search.repository.js +0 -59
- package/dist/features/search/schemas/index.d.ts +0 -70
- package/dist/features/search/schemas/index.js +0 -36
- package/dist/features/search/types/index.d.ts +0 -48
- package/dist/features/stores/components/StoreScopedSearch.d.ts +0 -17
- package/dist/features/stores/components/StoreScopedSearch.js +0 -27
- package/dist/seed/defaults/homepage-sections.d.ts +0 -2
- package/dist/seed/defaults/homepage-sections.js +0 -60
- package/dist/seed/factories/homepage-section.factory.d.ts +0 -10
- package/dist/seed/factories/homepage-section.factory.js +0 -13
- /package/dist/{features/search/types/index.js → _internal/shared/listing-types/spares/og.d.ts} +0 -0
|
@@ -7,8 +7,8 @@ import { categoriesRepository, productRepository, storeRepository, } from "../..
|
|
|
7
7
|
import { Container, Div, Heading, ImageMosaic, Main, MOSAIC_MIN, Section, Span, SplitTile, Stack, Text } from "../../../ui/index.js";
|
|
8
8
|
import { MediaImage } from "../../media/MediaImage.js";
|
|
9
9
|
import { RichTextRenderer } from "../../../ui/rich-text/RichTextRenderer.js";
|
|
10
|
-
import { OverflowRow } from "../../../ui/index.js";
|
|
11
10
|
import { resolveTaxonomyVisual } from "../utils/taxonomy-visual.js";
|
|
11
|
+
import { SubcategoryCardRow } from "./SubcategoryCardRow.js";
|
|
12
12
|
import { CategoryDetailTabs } from "./CategoryDetailTabs.js";
|
|
13
13
|
import { CategoryGrid } from "./CategoryGrid.js";
|
|
14
14
|
import { CategoryHighlightsAndFaqSection } from "./CategoryHighlightsAndFaqSection.js";
|
|
@@ -27,6 +27,7 @@ import { safeRead } from "../../../errors/safe-read.js";
|
|
|
27
27
|
import { breadcrumbJsonLd, collectionPageJsonLd, faqJsonLd, } from "../../../seo/json-ld.js";
|
|
28
28
|
import { toStoreListItem } from "../../../_internal/server/features/stores/adapters.js";
|
|
29
29
|
import { hidePublicTestData } from "../../../_internal/server/features/tester/visibility.js";
|
|
30
|
+
import { productImageUrls } from "../../products/schemas/firestore.js";
|
|
30
31
|
const __O = {
|
|
31
32
|
hidden: "overflow-hidden",
|
|
32
33
|
};
|
|
@@ -49,17 +50,73 @@ const __W = "max-w-7xl mx-auto";
|
|
|
49
50
|
* Admin surfaces that genuinely need both still show both —
|
|
50
51
|
* `CategoryTableColumns` renders "own (total)" explicitly.
|
|
51
52
|
*/
|
|
52
|
-
|
|
53
|
+
/*
|
|
54
|
+
* 🛑 products + auctions, SUMMED.
|
|
55
|
+
*
|
|
56
|
+
* `metrics.productCount` EXCLUDES auctions — `Tally` keeps `productIds` and
|
|
57
|
+
* `auctionIds` as separate arrays and writes `auctionCount` /
|
|
58
|
+
* `totalAuctionCount` alongside, so the comparable "how much is in here"
|
|
59
|
+
* figure is the sum of both. Reading only the product half understated every
|
|
60
|
+
* subcategory holding an auction, and understated it silently: the chip
|
|
61
|
+
* showed a plausible smaller number rather than anything that looked wrong.
|
|
62
|
+
*
|
|
63
|
+
* This page already derives its own header figures correctly that way
|
|
64
|
+
* (`productCount` and `auctionCount` resolved separately further down); this
|
|
65
|
+
* was the one place that took the product half for the whole.
|
|
66
|
+
*
|
|
67
|
+
* Prefer the rollup (`total*`) over the own-count for both halves, because a
|
|
68
|
+
* chip is a navigational promise about the whole subtree you are about to
|
|
69
|
+
* enter, not about what happens to be filed at that exact node. Shared by
|
|
70
|
+
* `toSubcategoryItems` (the chip's badge count) and the scroller's own
|
|
71
|
+
* filter below, so the two can't disagree about what "has listings" means.
|
|
72
|
+
*/
|
|
73
|
+
function listingCountFor(child) {
|
|
74
|
+
const products = child.metrics?.totalProductCount ?? child.metrics?.productCount ?? 0;
|
|
75
|
+
const auctions = child.metrics?.totalAuctionCount ?? child.metrics?.auctionCount ?? 0;
|
|
76
|
+
return products + auctions;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Direct children → the Subcategory card row's items.
|
|
80
|
+
*
|
|
81
|
+
* `descendants` is the SAME array the page already fetched, passed in so each
|
|
82
|
+
* card can carry its own direct children's names for the filter. One level
|
|
83
|
+
* only — see `SubcategoryCardRow`'s header for why matching the whole subtree
|
|
84
|
+
* would surface cards for names the visitor never sees.
|
|
85
|
+
*/
|
|
86
|
+
function toSubcategoryItems(children, descendants) {
|
|
87
|
+
/*
|
|
88
|
+
* Grandchildren indexed by parent in ONE pass. The alternative — filtering
|
|
89
|
+
* `descendants` inside the map — is O(children x descendants), and on the
|
|
90
|
+
* spinning-tops root that is 5 x ~1,200 per render for a list that never
|
|
91
|
+
* changes.
|
|
92
|
+
*
|
|
93
|
+
* 🛑 Keyed on `parentIds[last]`, the IMMEDIATE parent. `parentIds` holds the
|
|
94
|
+
* full ancestor chain, so testing `.includes(child.id)` would match every
|
|
95
|
+
* descendant at any depth and quietly turn the one-level rule into a
|
|
96
|
+
* whole-subtree match.
|
|
97
|
+
*/
|
|
98
|
+
const childNamesByParent = new Map();
|
|
99
|
+
for (const d of descendants) {
|
|
100
|
+
const parent = d.parentIds?.[d.parentIds.length - 1];
|
|
101
|
+
if (!parent)
|
|
102
|
+
continue;
|
|
103
|
+
const list = childNamesByParent.get(parent);
|
|
104
|
+
if (list)
|
|
105
|
+
list.push(d.name);
|
|
106
|
+
else
|
|
107
|
+
childNamesByParent.set(parent, [d.name]);
|
|
108
|
+
}
|
|
53
109
|
return children.map((child) => {
|
|
54
|
-
const count = child
|
|
110
|
+
const count = listingCountFor(child);
|
|
55
111
|
return {
|
|
56
112
|
id: child.id,
|
|
57
|
-
|
|
113
|
+
name: child.name,
|
|
58
114
|
// Omitted rather than rendered as 0 — `count` is "not counted" when
|
|
59
|
-
// undefined, and a
|
|
115
|
+
// undefined, and a card reading "0" invites a click that lands on an
|
|
60
116
|
// empty page.
|
|
61
117
|
count: count > 0 ? count : undefined,
|
|
62
118
|
href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(child.slug)),
|
|
119
|
+
childNames: childNamesByParent.get(child.id) ?? [],
|
|
63
120
|
...resolveTaxonomyVisual(child),
|
|
64
121
|
};
|
|
65
122
|
});
|
|
@@ -114,7 +171,7 @@ export async function CategoryDetailPageView({ slug }) {
|
|
|
114
171
|
// Four of the ten tabs used to have no count at all and could never hide.
|
|
115
172
|
categoriesIn
|
|
116
173
|
? listingTabCounts(CATEGORY_PAGE_TABS, { categoriesIn })
|
|
117
|
-
: Promise.resolve({}),
|
|
174
|
+
: Promise.resolve({ available: {}, total: {} }),
|
|
118
175
|
// SB-UNI-D — bundles fetched from the categories collection. We pull
|
|
119
176
|
// all active bundle rows; the carousel filters by category affinity.
|
|
120
177
|
categoriesIn
|
|
@@ -216,9 +273,76 @@ export async function CategoryDetailPageView({ slug }) {
|
|
|
216
273
|
})
|
|
217
274
|
: Promise.resolve([]),
|
|
218
275
|
]);
|
|
276
|
+
// 🛑 SAME TIER ONLY. `rootSiblingCategories` spans the whole root subtree —
|
|
277
|
+
// siblings AND cousins across every depth — so without a tier check this
|
|
278
|
+
// rendered deeper descendants (children, grandchildren) as "related",
|
|
279
|
+
// which reads as a second, redundant Subcategories row rather than a set
|
|
280
|
+
// of actual peers. A category with no siblings at its own tier correctly
|
|
281
|
+
// shows nothing here; the tier-grouped descendant tabs above already cover
|
|
282
|
+
// "what's below this category".
|
|
219
283
|
const relatedCategories = rootSiblingCategories
|
|
220
|
-
.filter((c) => c.id !== category?.id &&
|
|
284
|
+
.filter((c) => c.id !== category?.id &&
|
|
285
|
+
(!c.categoryType || c.categoryType === "category") &&
|
|
286
|
+
c.tier === category?.tier)
|
|
221
287
|
.slice(0, RELATED_CATEGORY_LIMIT);
|
|
288
|
+
// 🛑 ONLY children that actually have something in them. A direct child
|
|
289
|
+
// with zero listings anywhere in its own subtree is a dead end — clicking
|
|
290
|
+
// it opens an empty category page — so it's dropped from the chip row
|
|
291
|
+
// rather than rendered with no count badge. `listingCountFor` reads the
|
|
292
|
+
// ROLLUP (own + descendants), so a child that merely has no listings of
|
|
293
|
+
// its OWN but does further down is correctly kept.
|
|
294
|
+
const directChildrenWithListings = childCategories
|
|
295
|
+
.filter((c) => c.tier === (category?.tier ?? 0) + 1)
|
|
296
|
+
.filter((c) => listingCountFor(c) > 0);
|
|
297
|
+
/*
|
|
298
|
+
* The ancestor chain as cards, root first.
|
|
299
|
+
*
|
|
300
|
+
* 🛑 `ancestor.id` is used as the slug, which is correct HERE and is not a
|
|
301
|
+
* general licence: categories are one of this codebase's "pure slug"
|
|
302
|
+
* collections (`id === slug`), so the two are interchangeable for a category
|
|
303
|
+
* row. `CategoryAncestor` carries only `{id, name, tier}` — no slug field —
|
|
304
|
+
* so the alternative would be a lookup per ancestor for a value already in
|
|
305
|
+
* hand.
|
|
306
|
+
*
|
|
307
|
+
* `tier` comes from the stored ancestor rather than the array index: the
|
|
308
|
+
* index is only the depth if the chain is complete, and a chain that skipped
|
|
309
|
+
* a tier would silently relabel every level below it (the closure-skip case
|
|
310
|
+
* CLAUDE.md records under the counters invariant).
|
|
311
|
+
*/
|
|
312
|
+
/*
|
|
313
|
+
* `CategoryAncestor` is `{id, name, tier}` and carries no display block, so
|
|
314
|
+
* the artwork is looked up in rows this page ALREADY fetched —
|
|
315
|
+
* `rootSiblingCategories` spans the whole root subtree, which is where an
|
|
316
|
+
* ancestor lives. A miss simply yields no visual and the card falls back,
|
|
317
|
+
* which is why this is a plain lookup and not another read.
|
|
318
|
+
*/
|
|
319
|
+
const rowsById = new Map();
|
|
320
|
+
for (const c of [...rootSiblingCategories, ...childCategories]) {
|
|
321
|
+
if (c?.id)
|
|
322
|
+
rowsById.set(c.id, c);
|
|
323
|
+
}
|
|
324
|
+
/*
|
|
325
|
+
* 🛑 The IMMEDIATE parent only — one card, never the chain.
|
|
326
|
+
*
|
|
327
|
+
* `ancestors` is ordered root-first, so the last entry is the direct parent
|
|
328
|
+
* and everything before it is a grandparent. Rendering the whole ladder put
|
|
329
|
+
* four cards on a tier-4 page for a path the breadcrumb at the top of the
|
|
330
|
+
* page already spells out in full, and the uncles/grandparents are not
|
|
331
|
+
* places you step to from here — the one move this row needs to afford is
|
|
332
|
+
* "up one".
|
|
333
|
+
*/
|
|
334
|
+
const immediateParent = (category?.ancestors ?? []).at(-1);
|
|
335
|
+
const parentLadder = immediateParent
|
|
336
|
+
? [
|
|
337
|
+
{
|
|
338
|
+
id: immediateParent.id,
|
|
339
|
+
name: immediateParent.name,
|
|
340
|
+
href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(immediateParent.id)),
|
|
341
|
+
level: immediateParent.tier,
|
|
342
|
+
...resolveTaxonomyVisual(rowsById.get(immediateParent.id) ?? null),
|
|
343
|
+
},
|
|
344
|
+
]
|
|
345
|
+
: [];
|
|
222
346
|
// Stores tab — stores whose storeCategory is this category or ANY descendant.
|
|
223
347
|
//
|
|
224
348
|
// `storeCategory` is a single slug with no ancestor chain (unlike a product's
|
|
@@ -282,18 +406,36 @@ export async function CategoryDetailPageView({ slug }) {
|
|
|
282
406
|
* and it cast `createdAt` through `as unknown as string` where the adapter
|
|
283
407
|
* converts a Timestamp properly.
|
|
284
408
|
*/
|
|
285
|
-
.map((s) => toStoreListItem(s))
|
|
409
|
+
.map((s) => toStoreListItem(s))
|
|
410
|
+
// 🛑 `storeCategory` is a one-time "primarily sells this" tag, not proof
|
|
411
|
+
// of current stock — a store can match the category and still have
|
|
412
|
+
// nothing listed right now (the exact shape of the empty-store report
|
|
413
|
+
// this filter closes). Listing it here sends a visitor into a store page
|
|
414
|
+
// with nothing to buy, so only stores that actually have products are
|
|
415
|
+
// kept. `totalProducts` is the denormalised `stats.totalProducts`
|
|
416
|
+
// counter, incremented atomically on create/delete — cheap to read here,
|
|
417
|
+
// unlike a per-store query.
|
|
418
|
+
.filter((s) => (s.totalProducts ?? 0) > 0);
|
|
286
419
|
const storeCount = categoryStores.length;
|
|
287
420
|
// `tabCounts` is keyed by tabSlug and already covers every listing type plus
|
|
288
421
|
// bundles; `stores` is added here because this page resolves its store list
|
|
289
422
|
// eagerly to render the tab body anyway.
|
|
290
|
-
|
|
423
|
+
// Two maps, not one: the BADGE is what the default (available) scope will
|
|
424
|
+
// render, while HIDING must stay keyed on the total — a tab whose rows have
|
|
425
|
+
// all ended still has a browsable archive behind the Ended/All scopes.
|
|
426
|
+
// `stores` is added to both because this page resolves its store list
|
|
427
|
+
// eagerly to render the tab body anyway, and a store has no availability
|
|
428
|
+
// scope of its own.
|
|
429
|
+
const counts = { ...tabCounts.available, stores: storeCount };
|
|
430
|
+
const totals = { ...tabCounts.total, stores: storeCount };
|
|
291
431
|
// Header pills read the same numbers the tabs do. `?? 0` is safe here (a pill
|
|
292
432
|
// simply doesn't render) — unlike the tab bar, where an unknown count must
|
|
293
433
|
// stay visible rather than collapse to "hide me".
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
const
|
|
434
|
+
// Header pills read TOTALS, deliberately: "47 products" is a statement about
|
|
435
|
+
// what this category holds, not about what one scope is currently showing.
|
|
436
|
+
const productCount = totals.products ?? category?.metrics?.totalProductCount ?? category?.metrics?.productCount ?? 0;
|
|
437
|
+
const auctionCount = totals.auctions ?? category?.metrics?.totalAuctionCount ?? category?.metrics?.auctionCount ?? 0;
|
|
438
|
+
const preOrderCount = totals["pre-orders"] ?? 0;
|
|
297
439
|
/*
|
|
298
440
|
* G3b — OWN vs CHILDREN, so one number stops answering two questions.
|
|
299
441
|
*
|
|
@@ -333,7 +475,7 @@ export async function CategoryDetailPageView({ slug }) {
|
|
|
333
475
|
const mosaicImages = hasCover
|
|
334
476
|
? []
|
|
335
477
|
: (productsResult?.items ?? [])
|
|
336
|
-
.map((row) => row.mainImage
|
|
478
|
+
.map((row) => row.mainImage || productImageUrls(row.images)[0] || null)
|
|
337
479
|
.filter((src) => !!src)
|
|
338
480
|
.slice(0, 5);
|
|
339
481
|
const hasMosaic = mosaicImages.length >= MOSAIC_MIN;
|
|
@@ -430,7 +572,7 @@ export async function CategoryDetailPageView({ slug }) {
|
|
|
430
572
|
const ldFaq = category?.faqs && category.faqs.length > 0
|
|
431
573
|
? faqJsonLd(category.faqs.map((f) => ({ question: f.question, answer: f.answer })))
|
|
432
574
|
: null;
|
|
433
|
-
return (_jsxs(Main, { children: [_jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: { __html: JSON.stringify(ldBreadcrumb) } }), _jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: { __html: JSON.stringify(ldCollection) } }), ldFaq && (_jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: { __html: JSON.stringify(ldFaq) } })), _jsxs(Section, { className: `relative ${__O.hidden} ${hasCover ? "min-h-[220px] md:min-h-[280px]" : "bg-[var(--appkit-color-bg)]"}`, children: [hasCover && (_jsxs(_Fragment, { children: [_jsx(Div, { className: "absolute inset-0", children: _jsx(MediaImage, { src: coverImage, alt: "", size: "banner" }) }), _jsx(Div, { surface: "overlay-md", className: "absolute inset-0" })] })), !hasCover && hasMosaic && (_jsx(Div, { className: "relative z-10 max-w-7xl mx-auto", padding: "x-md", children: _jsx(Div, { padding: "t-lg", children: _jsx(ImageMosaic, { images: mosaicImages, alt: `${category?.name ?? slug} on LetItRip` }) }) })), _jsxs(Div, { className: "relative z-10 max-w-7xl mx-auto", paddingY: hasCover ? "y-3xl" : "y-xl", padding: "x-md", children: [_jsx(BreadcrumbTrail, { crumbs: trail, className: "mb-4", tone: hasCover ? "on-media" : "default" }), category?.id && _jsx(LastCategoryBeacon, { categoryId: category.id }), _jsx(Heading, { color: "inverse", level: 1, className: `mb-2 ${hasCover ? "" : "text-[var(--appkit-color-text)]"}`, size: "3xl", mdSize: "4xl", weight: "bold", children: category?.name ?? slug }), category?.description && typeof category.description === "string" && !category.description.startsWith("{") && (_jsx(Text, { color: hasCover ? "inverse" : "muted", className: `max-w-2xl mb-4 ${hasCover ? "/80" : ""}`, size: "base", children: category.description })), _jsxs(Row, { wrap: true, gap: "sm", children: [productCount > 0 && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: "inverse", size: "xs", weight: "medium", className: hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "bg-primary/10 text-primary-700 dark:text-primary-400", rounded: "full", padding: "pill-sm-tall", children: [productCount.toLocaleString(), " ", productCount === 1 ? "product" : "products"] })), showCountSplit && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: hasCover ? "inverse" : "muted", size: "xs", className: hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "", rounded: "full", padding: "pill-sm-tall", children: [ownHere.toLocaleString(), " here \u00B7 ", inSubcategories.toLocaleString(), " in subcategories"] })), auctionCount > 0 && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: hasCover ? "inverse" : "warning", surface: hasCover ? undefined : "warning-surface", size: "xs", weight: "medium", className: hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "", rounded: "full", padding: "pill-sm-tall", children: [auctionCount.toLocaleString(), " ", auctionCount === 1 ? "auction" : "auctions"] })), preOrderCount > 0 && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: hasCover ? "inverse" : "info", surface: hasCover ? undefined : "info-surface", size: "xs", weight: "medium", className: hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "", rounded: "full", padding: "pill-sm-tall", children: [preOrderCount.toLocaleString(), " ", preOrderCount === 1 ? "pre-order" : "pre-orders"] })), storeCount > 0 && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: hasCover ? "inverse" : "success", surface: hasCover ? undefined : "success-surface", size: "xs", weight: "medium", className: hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "", rounded: "full", padding: "pill-sm-tall", children: [storeCount.toLocaleString(), " ", storeCount === 1 ? "store" : "stores"] }))] })] })] }), contentBody && contentBody.trim().length > 0 && (_jsx(Section, { padding: "y-lg", children: _jsx(Container, { size: "xl", children: _jsx(RichTextRenderer, { html: contentBody }) }) })), _jsx(CategoryHighlightsAndFaqSection, { highlights: category?.highlights, faqs: category?.faqs }), compositions.length > 0 && (_jsx(Section, { border: "subtle", surface: "default", className: "border-b", children: _jsx(Div, { className: __W, padding: "section", children: _jsxs(Stack, { gap: "md", className: "min-w-0", children: [_jsx(Heading, { level: 2, size: "lg", children: "What it is made of" }), _jsx(ModelCompositionPanel, { compositions: compositions, hrefForPart: (slot, tail) => String(ROUTES.PUBLIC.PART_DETAIL(slot, tail)) })] }) }) })), (() => {
|
|
575
|
+
return (_jsxs(Main, { children: [_jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: { __html: JSON.stringify(ldBreadcrumb) } }), _jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: { __html: JSON.stringify(ldCollection) } }), ldFaq && (_jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: { __html: JSON.stringify(ldFaq) } })), _jsxs(Section, { className: `relative ${__O.hidden} ${hasCover ? "min-h-[220px] md:min-h-[280px]" : "bg-[var(--appkit-color-bg)]"}`, children: [hasCover && (_jsxs(_Fragment, { children: [_jsx(Div, { className: "absolute inset-0", children: _jsx(MediaImage, { src: coverImage, alt: "", size: "banner" }) }), _jsx(Div, { surface: "overlay-md", className: "absolute inset-0" })] })), !hasCover && hasMosaic && (_jsx(Div, { className: "relative z-10 max-w-7xl mx-auto", padding: "x-md", children: _jsx(Div, { padding: "t-lg", children: _jsx(ImageMosaic, { images: mosaicImages, alt: `${category?.name ?? slug} on LetItRip` }) }) })), _jsxs(Div, { className: "relative z-10 max-w-7xl mx-auto", paddingY: hasCover ? "y-3xl" : "y-xl", padding: "x-md", children: [_jsx(BreadcrumbTrail, { crumbs: trail, className: "mb-4", tone: hasCover ? "on-media" : "default" }), category?.id && _jsx(LastCategoryBeacon, { categoryId: category.id }), _jsx(Heading, { color: "inverse", level: 1, className: `mb-2 ${hasCover ? "" : "text-[var(--appkit-color-text)]"}`, size: "3xl", mdSize: "4xl", weight: "bold", children: category?.name ?? slug }), category?.description && typeof category.description === "string" && !category.description.startsWith("{") && (_jsx(Text, { color: hasCover ? "inverse" : "muted", className: `max-w-2xl mb-4 ${hasCover ? "/80" : ""}`, size: "base", children: category.description })), _jsxs(Row, { wrap: true, gap: "sm", children: [productCount > 0 && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: "inverse", size: "xs", weight: "medium", className: hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "bg-primary/10 text-primary-700 dark:text-primary-400", rounded: "full", padding: "pill-sm-tall", children: [productCount.toLocaleString(), " ", productCount === 1 ? "product" : "products"] })), showCountSplit && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: hasCover ? "inverse" : "muted", size: "xs", className: hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "", rounded: "full", padding: "pill-sm-tall", children: [ownHere.toLocaleString(), " here \u00B7 ", inSubcategories.toLocaleString(), " in subcategories"] })), auctionCount > 0 && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: hasCover ? "inverse" : "warning", surface: hasCover ? undefined : "warning-surface", size: "xs", weight: "medium", className: hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "", rounded: "full", padding: "pill-sm-tall", children: [auctionCount.toLocaleString(), " ", auctionCount === 1 ? "auction" : "auctions"] })), preOrderCount > 0 && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: hasCover ? "inverse" : "info", surface: hasCover ? undefined : "info-surface", size: "xs", weight: "medium", className: hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "", rounded: "full", padding: "pill-sm-tall", children: [preOrderCount.toLocaleString(), " ", preOrderCount === 1 ? "pre-order" : "pre-orders"] })), storeCount > 0 && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: hasCover ? "inverse" : "success", surface: hasCover ? undefined : "success-surface", size: "xs", weight: "medium", className: hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "", rounded: "full", padding: "pill-sm-tall", children: [storeCount.toLocaleString(), " ", storeCount === 1 ? "store" : "stores"] }))] })] })] }), category?.hasbroName && (_jsx(Section, { padding: "y-md", children: _jsx(Container, { size: "xl", children: _jsxs(Row, { gap: "md", align: "center", wrap: true, children: [category.hasbroCoverImage && (_jsx(MediaImage, { src: category.hasbroCoverImage, alt: `${category.hasbroName} logo`, size: "thumbnail", className: "h-12 w-auto flex-shrink-0" })), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "xs", color: "muted", transform: "uppercase", weight: "medium", children: "Also known as" }), _jsx(Text, { weight: "semibold", children: category.hasbroName }), _jsxs(Text, { size: "xs", color: "muted", children: ["The same wave, named differently outside Japan \u2014 Takara-Tomy releases it as ", category.name, ", Hasbro as ", category.hasbroName, ". Both names find these listings."] })] })] }) }) })), contentBody && contentBody.trim().length > 0 && (_jsx(Section, { padding: "y-lg", children: _jsx(Container, { size: "xl", children: _jsx(RichTextRenderer, { html: contentBody }) }) })), _jsx(CategoryHighlightsAndFaqSection, { highlights: category?.highlights, faqs: category?.faqs }), compositions.length > 0 && (_jsx(Section, { border: "subtle", surface: "default", className: "border-b", children: _jsx(Div, { className: __W, padding: "section", children: _jsxs(Stack, { gap: "md", className: "min-w-0", children: [_jsx(Heading, { level: 2, size: "lg", children: "What it is made of" }), _jsx(ModelCompositionPanel, { compositions: compositions, hrefForPart: (slot, tail) => String(ROUTES.PUBLIC.PART_DETAIL(slot, tail)) })] }) }) })), (() => {
|
|
434
576
|
if (category?.tier !== 0)
|
|
435
577
|
return null;
|
|
436
578
|
const branches = childCategories.filter((c) => c.tier === 1);
|
|
@@ -455,10 +597,5 @@ export async function CategoryDetailPageView({ slug }) {
|
|
|
455
597
|
imageUrl: coverOf(b),
|
|
456
598
|
countLabel: countOf(b),
|
|
457
599
|
} }) }) }));
|
|
458
|
-
})(),
|
|
459
|
-
const directChildren = childCategories.filter((c) => c.tier === (category?.tier ?? 0) + 1);
|
|
460
|
-
if (directChildren.length === 0)
|
|
461
|
-
return null;
|
|
462
|
-
return (_jsxs(Div, { padding: "b-2xs", children: [_jsx(Text, { size: "xs", color: "muted", weight: "medium", className: "mb-[var(--appkit-space-1-5)]", children: "Subcategories" }), _jsx(OverflowRow, { items: toSubcategoryItems(directChildren), ariaLabel: "Subcategories", variant: "nav", placeholder: "Browse subcategories", searchPlaceholder: "Search subcategories\u2026", noResultsText: "No matching subcategory" })] }));
|
|
463
|
-
})() }) })), _jsx(Section, { padding: "y-lg", children: _jsx(Container, { size: "xl", children: _jsx(CategoryDetailTabs, { categorySlug: slug, categoryId: category?.id, initialProductsData: productsResult ?? undefined, initialBundles: bundlesResult ?? [], initialStores: categoryStores, counts: counts, enabledListingTypes: enabledListingTypes(settings), enabledCategoryTypes: enabledCategoryTypes(settings) }) }) }), groupedListings.length > 0 && (_jsx(Section, { padding: "y-lg", children: _jsx(Container, { size: "xl", children: _jsx(GroupedListingsCarousel, { groups: groupedListings }) }) })), relatedCategories.length > 0 && (_jsx(Section, { border: "subtle", surface: "default", className: "border-t", padding: "y-lg", children: _jsxs(Container, { size: "xl", children: [_jsx(Heading, { level: 2, className: "mb-4", size: "xl", weight: "semibold", children: "Related Categories" }), _jsx(CategoryGrid, { categories: relatedCategories, getHref: (c) => String(ROUTES.PUBLIC.CATEGORY_DETAIL(c.slug)) })] }) }))] }));
|
|
600
|
+
})(), directChildrenWithListings.length > 0 && (_jsx(Section, { border: "subtle", surface: "default", className: "border-b", children: _jsx(Div, { className: __W, padding: "inline", children: _jsxs(Div, { padding: "b-2xs", children: [parentLadder.length > 0 && (_jsx(Div, { padding: "b-2xs", children: _jsx(SubcategoryCardRow, { items: parentLadder, label: "Parent category" }) })), _jsx(SubcategoryCardRow, { items: toSubcategoryItems(directChildrenWithListings, childCategories) })] }) }) })), _jsx(Section, { padding: "y-lg", children: _jsx(Container, { size: "xl", children: _jsx(CategoryDetailTabs, { categorySlug: slug, categoryId: category?.id, initialProductsData: productsResult ?? undefined, initialBundles: bundlesResult ?? [], initialStores: categoryStores, counts: counts, totals: totals, enabledListingTypes: enabledListingTypes(settings), enabledCategoryTypes: enabledCategoryTypes(settings) }) }) }), groupedListings.length > 0 && (_jsx(Section, { padding: "y-lg", children: _jsx(Container, { size: "xl", children: _jsx(GroupedListingsCarousel, { groups: groupedListings }) }) })), relatedCategories.length > 0 && (_jsx(Section, { border: "subtle", surface: "default", className: "border-t", padding: "y-lg", children: _jsxs(Container, { size: "xl", children: [_jsx(Heading, { level: 2, className: "mb-4", size: "xl", weight: "semibold", children: "Related Categories" }), _jsx(CategoryGrid, { categories: relatedCategories, getHref: (c) => String(ROUTES.PUBLIC.CATEGORY_DETAIL(c.slug)) })] }) }))] }));
|
|
464
601
|
}
|
|
@@ -17,9 +17,15 @@ export interface CategoryDetailTabsProps {
|
|
|
17
17
|
* were structurally uncountable and could never hide (Root Cause #61).
|
|
18
18
|
*/
|
|
19
19
|
counts?: Record<string, number | undefined>;
|
|
20
|
+
/**
|
|
21
|
+
* Every published row per tab, all availability scopes. Drives hide-at-zero
|
|
22
|
+
* and the "N ended — show all" hint; see `ListingTabCountResult`. Omitted,
|
|
23
|
+
* it falls back to `counts`, i.e. the pre-split behaviour.
|
|
24
|
+
*/
|
|
25
|
+
totals?: Record<string, number | undefined>;
|
|
20
26
|
/** Enabled listing types (e.g. ["standard","auction","pre-order"]). When omitted, all tabs shown. */
|
|
21
27
|
enabledListingTypes?: string[];
|
|
22
28
|
/** Enabled category types (e.g. ["category","brand","bundle"]). When omitted, all tabs shown. */
|
|
23
29
|
enabledCategoryTypes?: string[];
|
|
24
30
|
}
|
|
25
|
-
export declare function CategoryDetailTabs({ categorySlug, categoryId, initialProductsData, initialBundles, initialStores, counts, enabledListingTypes, enabledCategoryTypes, }: CategoryDetailTabsProps): React.JSX.Element;
|
|
31
|
+
export declare function CategoryDetailTabs({ categorySlug, categoryId, initialProductsData, initialBundles, initialStores, counts, totals, enabledListingTypes, enabledCategoryTypes, }: CategoryDetailTabsProps): React.JSX.Element;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState } from "react";
|
|
4
4
|
import { EmptyState, Tabs, TabsList, TabsTrigger } from "../../../ui/index.js";
|
|
5
|
+
import { AvailabilityScopeHint } from "../../products/components/AvailabilityScopeHint.js";
|
|
5
6
|
import { CategoryProductsListing } from "./CategoryProductsListing.js";
|
|
6
7
|
import { AuctionsIndexListing } from "../../products/components/AuctionsIndexListing.js";
|
|
7
8
|
import { PreOrdersIndexListing } from "../../pre-orders/components/PreOrdersIndexListing.js";
|
|
@@ -29,6 +30,21 @@ const TAB_TYPE_MAP = {
|
|
|
29
30
|
bundles: { kind: "category", type: "bundle" },
|
|
30
31
|
stores: { kind: "entity", type: "stores" },
|
|
31
32
|
};
|
|
33
|
+
/**
|
|
34
|
+
* The listing types a tab spans, for the availability-scope hint.
|
|
35
|
+
*
|
|
36
|
+
* Derived from `TAB_TYPE_MAP` rather than hand-written: that map is already
|
|
37
|
+
* built from `ALL_LISTING_TYPES`, so a tenth type is covered without a second
|
|
38
|
+
* enumeration (Root Cause #61). The combined Art & Stickers tab is the one
|
|
39
|
+
* entry that spans two, and `GENERIC_TAB_LISTING_TYPES` already says so.
|
|
40
|
+
*/
|
|
41
|
+
function typesForTab(id) {
|
|
42
|
+
const generic = GENERIC_TAB_LISTING_TYPES[id];
|
|
43
|
+
if (generic)
|
|
44
|
+
return generic;
|
|
45
|
+
const mapping = TAB_TYPE_MAP[id];
|
|
46
|
+
return mapping?.kind === "listing" ? [mapping.type] : [];
|
|
47
|
+
}
|
|
32
48
|
/** Listing types rendered by the shared products listing rather than a dedicated one. */
|
|
33
49
|
const GENERIC_TAB_LISTING_TYPES = {
|
|
34
50
|
[pluginFor("standard").tabSlug]: ["standard"],
|
|
@@ -37,9 +53,25 @@ const GENERIC_TAB_LISTING_TYPES = {
|
|
|
37
53
|
[pluginFor("live").tabSlug]: ["live"],
|
|
38
54
|
// The combined Art & Stickers tab spans both types under the `art` slug.
|
|
39
55
|
[pluginFor("art").tabSlug]: ART_STICKERS_LISTING_TYPES,
|
|
56
|
+
/*
|
|
57
|
+
* Spares render generically — there is nothing type-specific to show beyond
|
|
58
|
+
* a product grid, unlike an auction (countdown, bid history) or a prize draw
|
|
59
|
+
* (entries remaining).
|
|
60
|
+
*
|
|
61
|
+
* 🛑 This map is hand-written and a missing entry is a tab you can CLICK that
|
|
62
|
+
* renders an empty panel. It is covered — `audit-tab-body-coverage` exists
|
|
63
|
+
* for precisely this, after `CategoryDetailTabs` showed a blank panel for
|
|
64
|
+
* four types while `BrandDetailTabs` silently dropped the same four — so the
|
|
65
|
+
* eleventh type will be caught rather than discovered.
|
|
66
|
+
*/
|
|
67
|
+
[pluginFor("spares").tabSlug]: ["spares"],
|
|
40
68
|
};
|
|
41
|
-
export function CategoryDetailTabs({ categorySlug, categoryId, initialProductsData, initialBundles = [], initialStores = [], counts, enabledListingTypes, enabledCategoryTypes, }) {
|
|
69
|
+
export function CategoryDetailTabs({ categorySlug, categoryId, initialProductsData, initialBundles = [], initialStores = [], counts, totals, enabledListingTypes, enabledCategoryTypes, }) {
|
|
42
70
|
const countFor = (id) => counts?.[id];
|
|
71
|
+
// Falls back to the badge when no totals map is supplied, so a caller that
|
|
72
|
+
// has not been migrated keeps today's hide behaviour rather than hiding
|
|
73
|
+
// everything.
|
|
74
|
+
const totalFor = (id) => totals?.[id] ?? counts?.[id];
|
|
43
75
|
const visibleTabs = CATEGORY_PAGE_TABS.filter((t) => {
|
|
44
76
|
const mapping = TAB_TYPE_MAP[t.id];
|
|
45
77
|
if (mapping) {
|
|
@@ -52,11 +84,13 @@ export function CategoryDetailTabs({ categorySlug, categoryId, initialProductsDa
|
|
|
52
84
|
return false;
|
|
53
85
|
}
|
|
54
86
|
}
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
//
|
|
58
|
-
//
|
|
59
|
-
|
|
87
|
+
// 🛑 TOTAL, not the badge. The badge counts what the default (available)
|
|
88
|
+
// scope renders, and a tab whose rows have all ended badges 0 while still
|
|
89
|
+
// holding a browsable archive behind the Ended/All scopes — hiding it
|
|
90
|
+
// would strand that archive. `undefined` still means the count query
|
|
91
|
+
// FAILED, so the tab stays visible: an empty tab is a far smaller error
|
|
92
|
+
// than hiding a tab that holds real listings (Root Cause #59).
|
|
93
|
+
const count = totalFor(t.id);
|
|
60
94
|
return count === undefined || count > 0;
|
|
61
95
|
});
|
|
62
96
|
const firstTabId = (visibleTabs[0]?.id ?? "products");
|
|
@@ -66,5 +100,5 @@ export function CategoryDetailTabs({ categorySlug, categoryId, initialProductsDa
|
|
|
66
100
|
if (visibleTabs.length === 0) {
|
|
67
101
|
return (_jsx(EmptyState, { title: "Nothing listed here yet", description: "This category has no active listings right now. Check back soon." }));
|
|
68
102
|
}
|
|
69
|
-
return (_jsxs(_Fragment, { children: [_jsx(Tabs, { value: activeTab, onChange: (v) => setActiveTab(v), className: "mb-6", children: _jsx(TabsList, { children: visibleTabs.map((t) => (_jsx(TabsTrigger, { value: t.id, badge: countFor(t.id), children: t.label }, t.id))) }) }), GENERIC_TAB_LISTING_TYPES[activeTab] && (_jsx(CategoryProductsListing, { categorySlug: categorySlug, categoryId: categoryId, listingTypes: GENERIC_TAB_LISTING_TYPES[activeTab], initialData: activeTab === pluginFor("standard").tabSlug ? initialProductsData : undefined })), activeTab === pluginFor("auction").tabSlug && (_jsx(AuctionsIndexListing, { categorySlug: categorySlug })), activeTab === pluginFor("pre-order").tabSlug && (_jsx(PreOrdersIndexListing, { categorySlug: categorySlug })), activeTab === pluginFor("prize-draw").tabSlug && (_jsx(PrizeDrawsIndexListing, { categorySlug: categorySlug })), activeTab === "bundles" && (_jsx(CategoryBundlesListing, { initialBundles: initialBundles })), activeTab === "stores" && (_jsx(CategoryStoresListing, { stores: initialStores }))] }));
|
|
103
|
+
return (_jsxs(_Fragment, { children: [_jsx(Tabs, { value: activeTab, onChange: (v) => setActiveTab(v), className: "mb-6", children: _jsx(TabsList, { children: visibleTabs.map((t) => (_jsx(TabsTrigger, { value: t.id, badge: countFor(t.id), children: t.label }, t.id))) }) }), _jsx(AvailabilityScopeHint, { total: totalFor(activeTab), available: countFor(activeTab), types: typesForTab(activeTab) }), GENERIC_TAB_LISTING_TYPES[activeTab] && (_jsx(CategoryProductsListing, { categorySlug: categorySlug, categoryId: categoryId, listingTypes: GENERIC_TAB_LISTING_TYPES[activeTab], initialData: activeTab === pluginFor("standard").tabSlug ? initialProductsData : undefined })), activeTab === pluginFor("auction").tabSlug && (_jsx(AuctionsIndexListing, { categorySlug: categorySlug })), activeTab === pluginFor("pre-order").tabSlug && (_jsx(PreOrdersIndexListing, { categorySlug: categorySlug })), activeTab === pluginFor("prize-draw").tabSlug && (_jsx(PrizeDrawsIndexListing, { categorySlug: categorySlug })), activeTab === "bundles" && (_jsx(CategoryBundlesListing, { initialBundles: initialBundles })), activeTab === "stores" && (_jsx(CategoryStoresListing, { stores: initialStores }))] }));
|
|
70
104
|
}
|
|
@@ -18,7 +18,16 @@ export function CategoryCard({ category, href, onClick, className = "", }) {
|
|
|
18
18
|
// own-node-only productCount so a parent category's card shows the same
|
|
19
19
|
// "N products" a visitor actually sees once they open the page.
|
|
20
20
|
const productCount = category.metrics?.totalProductCount ?? category.metrics?.productCount ?? category.productCount ?? 0;
|
|
21
|
-
|
|
21
|
+
/*
|
|
22
|
+
* Is a real picture going to render in the image area? Drives the emoji
|
|
23
|
+
* overlay below, which must not paint over artwork. Deliberately mirrors the
|
|
24
|
+
* first two branches of the chain rather than being derived from it — the
|
|
25
|
+
* `color` branch is a flat tint, which an emoji on top of is still the
|
|
26
|
+
* intended look.
|
|
27
|
+
*/
|
|
28
|
+
const hasArtwork = (category.display?.mosaicImages?.length ?? 0) >= MOSAIC_MIN ||
|
|
29
|
+
Boolean(category.display?.coverImage);
|
|
30
|
+
const inner = (_jsxs(Stack, { className: "h-full", children: [_jsxs(Div, { surface: "muted", className: `relative aspect-[4/3] w-full ${__O.hidden} flex-shrink-0`, children: [(category.display?.mosaicImages?.length ?? 0) >= MOSAIC_MIN ? (_jsx(ImageMosaic, { images: category.display.mosaicImages, alt: category.name, className: "h-full w-full" })) : category.display?.coverImage ? (_jsx(MediaImage, { src: category.display.coverImage, alt: category.name, size: "card", className: "transition-transform duration-300 group-hover:scale-105" })) : category.display?.color ? (_jsx(DynamicBgDiv, { color: category.display.color, className: "h-full w-full opacity-80" })) : null, category.display?.icon && !hasArtwork && (_jsx(Row, { textSize: "4xl", className: "absolute inset-0", align: "center", justify: "center", children: category.display.icon })), category.isFeatured && (_jsx(Span, { size: "xs", className: CLS_FEATURED_DOT, children: "\u2605" }))] }), _jsxs(Stack, { className: `flex-1 ${__P.p3}.5`, children: [_jsx(Text, { color: "inverse", className: `leading-snug text-[var(--appkit-color-text)] dark:`, truncate: 2, size: "sm", weight: "semibold", children: category.name }), category.description && (_jsx(Text, { className: `mt-1 flex-1`, color: "muted", truncate: 2, size: "xs", children: category.description })), _jsxs(Row, { className: "mt-2", align: "center", justify: "between", gap: "sm", children: [_jsxs(Text, { size: "xs", color: "faint", children: [productCount.toLocaleString(), " ", productCount === 1 ? "item" : "items"] }), _jsxs(Span, { layout: "inline-flex", gap: "xs", size: "xs", weight: "medium", border: "default", className: "group-hover:bg-primary group-hover:border-primary group-hover:text-white transition-colors", rounded: "md", padding: "pill-sm-tall", color: "muted", children: ["Browse ", _jsx(ArrowRight, { className: "h-3 w-3" })] })] })] })] }));
|
|
22
31
|
const cardClass = `group relative flex flex-col overflow-hidden rounded-xl border border-neutral-200 bg-[var(--appkit-color-surface)] border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] shadow-sm transition hover:shadow-md h-full ${className}`;
|
|
23
32
|
if (href) {
|
|
24
33
|
return (_jsx(Link, { href: href, className: cardClass, children: inner }));
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
export interface PriceGuidanceHintProps {
|
|
3
|
+
/** Leaf category id/slug the listing is filed under. */
|
|
4
|
+
categoryId: string;
|
|
5
|
+
currency?: string;
|
|
6
|
+
className?: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The seller-facing price hint `description-template/route.ts`'s own
|
|
10
|
+
* docstring names as "C1's job, from a surface that knows who is asking" —
|
|
11
|
+
* renders the inherited competitor-derived market band beside the price
|
|
12
|
+
* field, on the SELLER'S own tax basis (the `/price-guidance` endpoint does
|
|
13
|
+
* that conversion server-side; this component only formats what it returns).
|
|
14
|
+
*
|
|
15
|
+
* Mounted via `ProductForm`'s `renderPriceGuidance` slot rather than fetched
|
|
16
|
+
* unconditionally inside the form — `ProductForm` has no data-fetching of its
|
|
17
|
+
* own (every app-specific concern is a render-prop), and this component is
|
|
18
|
+
* seller/admin-only, not something the form itself should assume a caller
|
|
19
|
+
* wants.
|
|
20
|
+
*
|
|
21
|
+
* 🛑 NEVER mount this on a public page (category/brand detail). A
|
|
22
|
+
* competitor-derived market band is exactly the "a neighbouring surface
|
|
23
|
+
* publishes the range" leak the guest price gate exists to close (CLAUDE.md
|
|
24
|
+
* § "Guest price gating"), even though the figure is advisory rather than a
|
|
25
|
+
* real listing price — which is also why `/price-guidance` itself is
|
|
26
|
+
* seller/admin/moderator-gated, not a public, edge-cached endpoint like its
|
|
27
|
+
* `description-template` sibling.
|
|
28
|
+
*/
|
|
29
|
+
export declare function PriceGuidanceHint({ categoryId, currency, className, }: PriceGuidanceHintProps): React.JSX.Element | null;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useQuery } from "@tanstack/react-query";
|
|
4
|
+
import { PricesOnly, Stack, Text } from "../../../ui/index.js";
|
|
5
|
+
import { apiClient } from "../../../http/index.js";
|
|
6
|
+
import { CATEGORY_ENDPOINTS } from "../../../constants/api-endpoints.js";
|
|
7
|
+
import { formatCurrency } from "../../../utils/number.formatter.js";
|
|
8
|
+
function unwrap(res) {
|
|
9
|
+
const body = res?.data ?? res;
|
|
10
|
+
return body && typeof body === "object" && "guidance" in body ? body : null;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* The seller-facing price hint `description-template/route.ts`'s own
|
|
14
|
+
* docstring names as "C1's job, from a surface that knows who is asking" —
|
|
15
|
+
* renders the inherited competitor-derived market band beside the price
|
|
16
|
+
* field, on the SELLER'S own tax basis (the `/price-guidance` endpoint does
|
|
17
|
+
* that conversion server-side; this component only formats what it returns).
|
|
18
|
+
*
|
|
19
|
+
* Mounted via `ProductForm`'s `renderPriceGuidance` slot rather than fetched
|
|
20
|
+
* unconditionally inside the form — `ProductForm` has no data-fetching of its
|
|
21
|
+
* own (every app-specific concern is a render-prop), and this component is
|
|
22
|
+
* seller/admin-only, not something the form itself should assume a caller
|
|
23
|
+
* wants.
|
|
24
|
+
*
|
|
25
|
+
* 🛑 NEVER mount this on a public page (category/brand detail). A
|
|
26
|
+
* competitor-derived market band is exactly the "a neighbouring surface
|
|
27
|
+
* publishes the range" leak the guest price gate exists to close (CLAUDE.md
|
|
28
|
+
* § "Guest price gating"), even though the figure is advisory rather than a
|
|
29
|
+
* real listing price — which is also why `/price-guidance` itself is
|
|
30
|
+
* seller/admin/moderator-gated, not a public, edge-cached endpoint like its
|
|
31
|
+
* `description-template` sibling.
|
|
32
|
+
*/
|
|
33
|
+
export function PriceGuidanceHint({ categoryId, currency = "INR", className = "", }) {
|
|
34
|
+
const query = useQuery({
|
|
35
|
+
queryKey: ["category-price-guidance", categoryId],
|
|
36
|
+
enabled: categoryId.length > 0,
|
|
37
|
+
staleTime: 60 * 60 * 1000,
|
|
38
|
+
queryFn: async () => unwrap(await apiClient.get(CATEGORY_ENDPOINTS.PRICE_GUIDANCE(categoryId))),
|
|
39
|
+
});
|
|
40
|
+
const guidance = query.data?.guidance;
|
|
41
|
+
const basisNote = query.data?.basisNote;
|
|
42
|
+
if (!guidance?.market)
|
|
43
|
+
return null;
|
|
44
|
+
const { median, p25, p75 } = guidance.market;
|
|
45
|
+
const fmt = (n) => formatCurrency(n, currency);
|
|
46
|
+
// p25/p75 are independently optional (see CategoryPriceGuidance's own
|
|
47
|
+
// docstring) — a range renders only when BOTH are present, never a
|
|
48
|
+
// half-range that would misreport the band's shape.
|
|
49
|
+
const range = p25 !== undefined && p75 !== undefined ? ` (${fmt(p25)}–${fmt(p75)})` : "";
|
|
50
|
+
/*
|
|
51
|
+
* `<PricesOnly>` rather than `<GatedPrice>`: this is advisory guidance
|
|
52
|
+
* BESIDE the seller's own price input, not a listing's primary price, so a
|
|
53
|
+
* viewer who may not see prices should get nothing here — a "Sign in to see
|
|
54
|
+
* price" prompt inside a seller form would be nonsense. In practice it
|
|
55
|
+
* never renders for a guest anyway, because the data source
|
|
56
|
+
* (`/api/categories/[id]/price-guidance`) is seller/admin/moderator-gated
|
|
57
|
+
* and 403s before there is a number to format; the wrapper makes that
|
|
58
|
+
* guarantee visible at the render site instead of implied by the route.
|
|
59
|
+
*/
|
|
60
|
+
return (_jsx(PricesOnly, { children: _jsxs(Stack, { gap: "xs", className: className, children: [_jsxs(Text, { size: "xs", color: "muted", children: ["Market median:", " ", _jsx(Text, { as: "span", weight: "medium", color: "primary", children: fmt(median) }), range, basisNote?.suffix ? ` ${basisNote.suffix}` : ""] }), basisNote?.note && _jsx(Text, { size: "xs", color: "faint", children: basisNote.note })] }) }));
|
|
61
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
export interface SubcategoryCardItem {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
href: string;
|
|
6
|
+
/** Rollup listing count. Omitted when zero — a card reading "0" invites a dead click. */
|
|
7
|
+
count?: number;
|
|
8
|
+
/** A real picture, when the row resolved one. */
|
|
9
|
+
imageUrl?: string;
|
|
10
|
+
/** An emoji, when that is all the row has. Never passed to an `<img>`. */
|
|
11
|
+
emoji?: string;
|
|
12
|
+
/** Direct children's names — searched, never rendered. */
|
|
13
|
+
childNames?: readonly string[];
|
|
14
|
+
/**
|
|
15
|
+
* Depth in the forest: 0 is a root, 1 its child, and so on.
|
|
16
|
+
*
|
|
17
|
+
* Rendered as a small "L0 / L1 / L2" marker so a row of ancestors reads as a
|
|
18
|
+
* LADDER rather than as five unrelated cards. Omitted on the subcategory row
|
|
19
|
+
* itself, where every card is the same depth by construction and the marker
|
|
20
|
+
* would be noise repeated N times.
|
|
21
|
+
*/
|
|
22
|
+
level?: number;
|
|
23
|
+
}
|
|
24
|
+
export declare function SubcategoryCardRow({ items, label, }: {
|
|
25
|
+
items: readonly SubcategoryCardItem[];
|
|
26
|
+
label?: string;
|
|
27
|
+
}): React.JSX.Element | null;
|