@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
|
@@ -162,10 +162,33 @@ export class CartRepository extends BaseRepository {
|
|
|
162
162
|
* subtotals all compute `price × quantity`, and a grouped line pins
|
|
163
163
|
* `quantity` to 1, so all four stay correct with no changes of their own.
|
|
164
164
|
*/
|
|
165
|
-
deriveLinePrice(members) {
|
|
165
|
+
deriveLinePrice(members, discountPercent) {
|
|
166
166
|
const raw = members.reduce((sum, m) => sum + m.unitPrice * m.quantity, 0);
|
|
167
|
+
/*
|
|
168
|
+
* 🛑 THE GROUP DISCOUNT IS APPLIED HERE AND NOWHERE ELSE.
|
|
169
|
+
*
|
|
170
|
+
* This function is the only writer of `item.price` on a multi-member line,
|
|
171
|
+
* and `item.price` is what `getSubtotal`, the cart page's per-seller
|
|
172
|
+
* subtotal, both lane subtotals, `unitPriceFor` and
|
|
173
|
+
* `expandCartLineToOrderRows`' per-member GST proration (via
|
|
174
|
+
* `allocateAcrossMembers`) all read. So discounting here reaches every one
|
|
175
|
+
* of them with no change of their own — and discounting anywhere else means
|
|
176
|
+
* the cart total and the invoice are computed from two different numbers,
|
|
177
|
+
* which is exactly what Root Cause #75 cost.
|
|
178
|
+
*
|
|
179
|
+
* In particular `allocateAcrossMembers` prorates the LOCKED line price
|
|
180
|
+
* across members for tax, so a discount applied after it would under- or
|
|
181
|
+
* over-report GST against the amount actually charged.
|
|
182
|
+
*
|
|
183
|
+
* Clamped 0–100 rather than trusted: the percent is a snapshot copied off a
|
|
184
|
+
* seller-editable rule, and a negative value would silently INFLATE the
|
|
185
|
+
* line while a value over 100 would make it negative.
|
|
186
|
+
*/
|
|
187
|
+
const pct = Math.min(100, Math.max(0, discountPercent ?? 0));
|
|
188
|
+
// audit-money-units-ok: pct/100 is a PERCENTAGE divisor (0-100 -> 0-1 fraction), not a paise conversion — raw/discounted are decimal rupees throughout.
|
|
189
|
+
const discounted = pct > 0 ? raw * (1 - pct / 100) : raw;
|
|
167
190
|
// audit-money-units-ok: decimal rupees throughout; rounding to 2dp, not paise conversion.
|
|
168
|
-
return Math.round(
|
|
191
|
+
return Math.round(discounted * 100) / 100;
|
|
169
192
|
}
|
|
170
193
|
/**
|
|
171
194
|
* Create a multi-member line. Deliberately never merges into an existing
|
|
@@ -182,7 +205,7 @@ export class CartRepository extends BaseRepository {
|
|
|
182
205
|
productId: input.productId,
|
|
183
206
|
productTitle: input.productTitle,
|
|
184
207
|
productImage: input.productImage,
|
|
185
|
-
price: this.deriveLinePrice(input.groupMembers),
|
|
208
|
+
price: this.deriveLinePrice(input.groupMembers, input.groupDiscountPercent),
|
|
186
209
|
currency: input.currency,
|
|
187
210
|
quantity: input.quantity,
|
|
188
211
|
storeId: input.storeId,
|
|
@@ -264,7 +287,12 @@ export class CartRepository extends BaseRepository {
|
|
|
264
287
|
items[itemIndex] = {
|
|
265
288
|
...target,
|
|
266
289
|
groupMembers: nextMembers,
|
|
267
|
-
|
|
290
|
+
/*
|
|
291
|
+
* The line's OWN stored percent, so changing a member quantity
|
|
292
|
+
* preserves the discount the buyer was quoted. Re-resolving the rule
|
|
293
|
+
* here would silently reprice a line mid-edit.
|
|
294
|
+
*/
|
|
295
|
+
price: this.deriveLinePrice(nextMembers, target.groupDiscountPercent),
|
|
268
296
|
updatedAt: new Date(),
|
|
269
297
|
};
|
|
270
298
|
const updatedCart = { ...cart, items, updatedAt: new Date() };
|
|
@@ -148,6 +148,24 @@ export interface CartItemDocument {
|
|
|
148
148
|
groupSlug?: string;
|
|
149
149
|
/** Display snapshot of the group/bundle name. */
|
|
150
150
|
groupTitle?: string;
|
|
151
|
+
/**
|
|
152
|
+
* The group discount, as a percentage, AS QUOTED WHEN THIS LINE WAS ADDED.
|
|
153
|
+
*
|
|
154
|
+
* 🛑 Stored on the line rather than resolved at read time, and that is not
|
|
155
|
+
* the mirror-drift mistake it resembles (Root Cause #42). It has to be here
|
|
156
|
+
* for a mechanical reason — `updateGroupMembers` recomputes `price` from
|
|
157
|
+
* `groupMembers` and holds only the CART, so with nothing on the line a
|
|
158
|
+
* buyer changing a quantity would silently lose the discount — and for a
|
|
159
|
+
* commercial one: this is the basis of the price the buyer was shown.
|
|
160
|
+
*
|
|
161
|
+
* 🛑 Because it is a snapshot it is RE-VALIDATED at checkout against the
|
|
162
|
+
* group's current rule, and a disagreement is REPORTED, never silently
|
|
163
|
+
* resolved either way. Honouring a stale percent would charge less than the
|
|
164
|
+
* seller now offers; re-deriving silently would change a total the buyer has
|
|
165
|
+
* already read. Same discipline as `revalidateAppliedCoupons`, which drops a
|
|
166
|
+
* lapsed coupon and tells the caller.
|
|
167
|
+
*/
|
|
168
|
+
groupDiscountPercent?: number;
|
|
151
169
|
/**
|
|
152
170
|
* The members of a multi-member line, AUTHORITATIVE over `bundleProductIds`.
|
|
153
171
|
* See `CartLineMember` for the quantity invariant.
|
|
@@ -300,6 +318,8 @@ export type AddToCartInput = {
|
|
|
300
318
|
groupId?: string;
|
|
301
319
|
groupSlug?: string;
|
|
302
320
|
groupTitle?: string;
|
|
321
|
+
/** See `CartItemDocument.groupDiscountPercent` — a quoted-at-add snapshot. */
|
|
322
|
+
groupDiscountPercent?: number;
|
|
303
323
|
groupMembers?: CartLineMember[];
|
|
304
324
|
};
|
|
305
325
|
/**
|
|
@@ -174,8 +174,8 @@ export declare const cartSummarySchema: z.ZodObject<{
|
|
|
174
174
|
sessionId?: string | undefined;
|
|
175
175
|
addedAt?: string | undefined;
|
|
176
176
|
}[];
|
|
177
|
-
itemCount: number;
|
|
178
177
|
subtotal: number;
|
|
178
|
+
itemCount: number;
|
|
179
179
|
}, {
|
|
180
180
|
items: {
|
|
181
181
|
id: string;
|
|
@@ -193,7 +193,7 @@ export declare const cartSummarySchema: z.ZodObject<{
|
|
|
193
193
|
sessionId?: string | undefined;
|
|
194
194
|
addedAt?: string | undefined;
|
|
195
195
|
}[];
|
|
196
|
-
itemCount: number;
|
|
197
196
|
subtotal: number;
|
|
197
|
+
itemCount: number;
|
|
198
198
|
currency?: string | undefined;
|
|
199
199
|
}>;
|
|
@@ -29,5 +29,5 @@ export function PublicCatalogueItemDetailView({ ownerSlug, itemId }) {
|
|
|
29
29
|
}
|
|
30
30
|
const { owner, item, linkedProductHref } = data;
|
|
31
31
|
const images = item.images.length > 0 ? item.images : item.mainImage ? [item.mainImage] : [];
|
|
32
|
-
return (_jsx(Main, { children: _jsxs(Container, { size: "lg", padding: "y-lg", children: [_jsxs(Row, { className: "mb-4", align: "center", gap: "xs", textSize: "xs", color: "muted", children: [_jsx(Link, { href: "/", className: "hover:text-primary-600 transition-colors", children: "Home" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: `${ROUTES.PUBLIC.PROFILE(ownerSlug)}/catalogue`, className: "hover:text-primary-600 transition-colors", children: owner.displayName ?? "Catalogue" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Span, { className: "truncate max-w-[200px]", color: "muted", children: item.title })] }), _jsxs(Grid, { cols: 2, gap: "lg", children: [images.length > 0 ? (_jsx(Div, { className: `relative aspect-square w-full ${__O.hidden}`, rounded: "xl", surface: "muted", children: _jsx(MediaImage, { src: images[0], alt: item.title, size: "hero", objectFit: "contain" }) })) : (_jsx(Div, { className: "relative aspect-square w-full", rounded: "xl", surface: "muted" })), images.length > 1 && (_jsx(Row, { gap: "sm", wrap: true, className: "col-span-2", children: images.slice(1).map((src, i) => (_jsx(Div, { className: `relative h-20 w-20 ${__O.hidden}`, rounded: "lg", surface: "muted", children: _jsx(MediaImage, { src: src, alt: `${item.title} photo ${i + 2}`, size: "thumbnail", objectFit: "cover" }) }, i))) })), _jsxs(Stack, { gap: "md", children: [_jsx(Heading, { level: 1, size: "2xl", weight: "bold", color: "primary", children: item.title }), typeof item.price === "number" && item.price > 0 && (_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "2xl", weight: "bold", color: "primary", children: _jsx(GatedPrice, { label: "Sign in to see the estimated value", children: formatCurrency(item.price) }) }), _jsx(Text, { size: "xs", color: "muted", children: "Estimated value \u2014 not for direct sale from this page." })] })), _jsxs(Row, { gap: "sm", wrap: true, children: [item.condition && (_jsx(Span, { size: "xs", weight: "medium", padding: "pill-sm", rounded: "full", surface: "subtle", color: "muted", children: item.condition })), typeof item.quantity === "number" && (_jsxs(Span, { size: "xs", weight: "medium", padding: "pill-sm", rounded: "full", surface: "subtle", color: "muted", children: ["Qty: ", item.quantity] }))] }), item.description && (_jsx(Text, { color: "muted", className: "whitespace-pre-line", children: item.description })), owner.displayName && (_jsxs(Div, { border: "subtle", rounded: "xl", surface: "muted", padding: "md", children: [_jsx(Text, { className: "text-[10px] tracking-wide mb-0.5", color: "faint", transform: "uppercase", children: "Owned by" }), _jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: owner.displayName })] })), linkedProductHref && (_jsx(Link, { href: linkedProductHref, className: "inline-flex w-fit items-center rounded-lg bg-primary px-[var(--appkit-space-4)] py-[var(--appkit-space-2)] text-[length:var(--appkit-text-sm)] font-semibold text-white transition-colors hover:opacity-90", children: "View live listing \u2192" }))] })] })] }) }));
|
|
32
|
+
return (_jsx(Main, { children: _jsxs(Container, { size: "lg", padding: "y-lg", children: [_jsxs(Row, { className: "mb-4", align: "center", gap: "xs", textSize: "xs", color: "muted", children: [_jsx(Link, { href: "/", className: "hover:text-primary-600 dark:hover:text-primary-400 transition-colors", children: "Home" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: `${ROUTES.PUBLIC.PROFILE(ownerSlug)}/catalogue`, className: "hover:text-primary-600 dark:hover:text-primary-400 transition-colors", children: owner.displayName ?? "Catalogue" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Span, { className: "truncate max-w-[200px]", color: "muted", children: item.title })] }), _jsxs(Grid, { cols: 2, gap: "lg", children: [images.length > 0 ? (_jsx(Div, { className: `relative aspect-square w-full ${__O.hidden}`, rounded: "xl", surface: "muted", children: _jsx(MediaImage, { src: images[0], alt: item.title, size: "hero", objectFit: "contain" }) })) : (_jsx(Div, { className: "relative aspect-square w-full", rounded: "xl", surface: "muted" })), images.length > 1 && (_jsx(Row, { gap: "sm", wrap: true, className: "col-span-2", children: images.slice(1).map((src, i) => (_jsx(Div, { className: `relative h-20 w-20 ${__O.hidden}`, rounded: "lg", surface: "muted", children: _jsx(MediaImage, { src: src, alt: `${item.title} photo ${i + 2}`, size: "thumbnail", objectFit: "cover" }) }, i))) })), _jsxs(Stack, { gap: "md", children: [_jsx(Heading, { level: 1, size: "2xl", weight: "bold", color: "primary", children: item.title }), typeof item.price === "number" && item.price > 0 && (_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "2xl", weight: "bold", color: "primary", children: _jsx(GatedPrice, { label: "Sign in to see the estimated value", children: formatCurrency(item.price) }) }), _jsx(Text, { size: "xs", color: "muted", children: "Estimated value \u2014 not for direct sale from this page." })] })), _jsxs(Row, { gap: "sm", wrap: true, children: [item.condition && (_jsx(Span, { size: "xs", weight: "medium", padding: "pill-sm", rounded: "full", surface: "subtle", color: "muted", children: item.condition })), typeof item.quantity === "number" && (_jsxs(Span, { size: "xs", weight: "medium", padding: "pill-sm", rounded: "full", surface: "subtle", color: "muted", children: ["Qty: ", item.quantity] }))] }), item.description && (_jsx(Text, { color: "muted", className: "whitespace-pre-line", children: item.description })), owner.displayName && (_jsxs(Div, { border: "subtle", rounded: "xl", surface: "muted", padding: "md", children: [_jsx(Text, { className: "text-[10px] tracking-wide mb-0.5", color: "faint", transform: "uppercase", children: "Owned by" }), _jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: owner.displayName })] })), linkedProductHref && (_jsx(Link, { href: linkedProductHref, className: "inline-flex w-fit items-center rounded-lg bg-primary px-[var(--appkit-space-4)] py-[var(--appkit-space-2)] text-[length:var(--appkit-text-sm)] font-semibold text-white transition-colors hover:opacity-90", children: "View live listing \u2192" }))] })] })] }) }));
|
|
33
33
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* feat-categories — GET /api/categories/[id]/price-guidance
|
|
3
|
+
*
|
|
4
|
+
* The seller-facing price hint `description-template/route.ts`'s own
|
|
5
|
+
* docstring names as "C1's job, from a surface that knows who is asking" —
|
|
6
|
+
* this is that surface. It is deliberately a SEPARATE endpoint rather than a
|
|
7
|
+
* field added back to the public, CDN-cached template route: that route's
|
|
8
|
+
* whole point is that anyone can fetch it, and `priceGuidance` is withheld
|
|
9
|
+
* from signed-out visitors for the same reason a listing's own price is
|
|
10
|
+
* (CLAUDE.md § "Guest price gating") — publishing the competitor-derived
|
|
11
|
+
* market band on an unauthenticated, publicly-cacheable response would be
|
|
12
|
+
* exactly the "a neighbouring surface publishes the range" leak that gate
|
|
13
|
+
* exists to close, even though the number is advisory rather than a real
|
|
14
|
+
* price.
|
|
15
|
+
*
|
|
16
|
+
* Consumer stub:
|
|
17
|
+
* ```ts
|
|
18
|
+
* // src/app/api/categories/[id]/price-guidance/route.ts
|
|
19
|
+
* export { categoryPriceGuidanceGET as GET } from "@mohasinac/appkit";
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* ## What this does NOT resolve
|
|
23
|
+
*
|
|
24
|
+
* `PriceGuidanceBasis.gstRate` — "the rate that will be written onto this
|
|
25
|
+
* listing, resolved the same way `deriveTaxonomy` resolves it" — is left
|
|
26
|
+
* `undefined` here on purpose. `deriveTaxonomy` is a private method on
|
|
27
|
+
* `ProductRepository`, tightly coupled to the product WRITE path (it also
|
|
28
|
+
* applies `DEFAULT_GST_RATE` as a last resort, which is correct for a
|
|
29
|
+
* document being saved and would be a guess presented as fact on a read-only
|
|
30
|
+
* hint). `describePriceGuidanceBasis` already handles an unresolved rate
|
|
31
|
+
* correctly and safely: for the common case (a seller whose prices already
|
|
32
|
+
* include GST — the schema default), the rate is never consulted at all, so
|
|
33
|
+
* this omission costs nothing. Only an EXCLUSIVE-pricing seller sees the
|
|
34
|
+
* honest "could not be resolved" note instead of a converted figure — which
|
|
35
|
+
* is the behaviour `price-guidance.ts`'s own docstring prescribes for exactly
|
|
36
|
+
* this situation, not a half-measure standing in for the real resolution.
|
|
37
|
+
*/
|
|
38
|
+
export declare const GET: (request: Request, context?: {
|
|
39
|
+
params: Promise<{
|
|
40
|
+
id: string;
|
|
41
|
+
}>;
|
|
42
|
+
} | undefined) => Promise<Response>;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* feat-categories — GET /api/categories/[id]/price-guidance
|
|
3
|
+
*
|
|
4
|
+
* The seller-facing price hint `description-template/route.ts`'s own
|
|
5
|
+
* docstring names as "C1's job, from a surface that knows who is asking" —
|
|
6
|
+
* this is that surface. It is deliberately a SEPARATE endpoint rather than a
|
|
7
|
+
* field added back to the public, CDN-cached template route: that route's
|
|
8
|
+
* whole point is that anyone can fetch it, and `priceGuidance` is withheld
|
|
9
|
+
* from signed-out visitors for the same reason a listing's own price is
|
|
10
|
+
* (CLAUDE.md § "Guest price gating") — publishing the competitor-derived
|
|
11
|
+
* market band on an unauthenticated, publicly-cacheable response would be
|
|
12
|
+
* exactly the "a neighbouring surface publishes the range" leak that gate
|
|
13
|
+
* exists to close, even though the number is advisory rather than a real
|
|
14
|
+
* price.
|
|
15
|
+
*
|
|
16
|
+
* Consumer stub:
|
|
17
|
+
* ```ts
|
|
18
|
+
* // src/app/api/categories/[id]/price-guidance/route.ts
|
|
19
|
+
* export { categoryPriceGuidanceGET as GET } from "@mohasinac/appkit";
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* ## What this does NOT resolve
|
|
23
|
+
*
|
|
24
|
+
* `PriceGuidanceBasis.gstRate` — "the rate that will be written onto this
|
|
25
|
+
* listing, resolved the same way `deriveTaxonomy` resolves it" — is left
|
|
26
|
+
* `undefined` here on purpose. `deriveTaxonomy` is a private method on
|
|
27
|
+
* `ProductRepository`, tightly coupled to the product WRITE path (it also
|
|
28
|
+
* applies `DEFAULT_GST_RATE` as a last resort, which is correct for a
|
|
29
|
+
* document being saved and would be a guess presented as fact on a read-only
|
|
30
|
+
* hint). `describePriceGuidanceBasis` already handles an unresolved rate
|
|
31
|
+
* correctly and safely: for the common case (a seller whose prices already
|
|
32
|
+
* include GST — the schema default), the rate is never consulted at all, so
|
|
33
|
+
* this omission costs nothing. Only an EXCLUSIVE-pricing seller sees the
|
|
34
|
+
* honest "could not be resolved" note instead of a converted figure — which
|
|
35
|
+
* is the behaviour `price-guidance.ts`'s own docstring prescribes for exactly
|
|
36
|
+
* this situation, not a half-measure standing in for the real resolution.
|
|
37
|
+
*/
|
|
38
|
+
import { NextResponse } from "next/server.js";
|
|
39
|
+
import { normalizeError } from "../../../../../errors/normalize.js";
|
|
40
|
+
import { serverLogger } from "../../../../../monitoring/index.js";
|
|
41
|
+
import { createRouteHandler } from "../../../../../next/api/routeHandler.js";
|
|
42
|
+
import { getProviders } from "../../../../../contracts/index.js";
|
|
43
|
+
import { categoriesRepository } from "../../../repository/categories.repository.js";
|
|
44
|
+
import { isAdminUser } from "../../../../auth/role-predicates.js";
|
|
45
|
+
import { priceGuidanceOnSellerBasis, describePriceGuidanceBasis, } from "../../../../../_internal/shared/features/categories/price-guidance.js";
|
|
46
|
+
import { resolveInheritedGuidance, } from "../../../../../_internal/server/features/categories/price-guidance-resolve.js";
|
|
47
|
+
export const GET = createRouteHandler({
|
|
48
|
+
roles: ["seller", "admin", "moderator"],
|
|
49
|
+
handler: async ({ user, params }) => {
|
|
50
|
+
try {
|
|
51
|
+
const categoryId = params?.id;
|
|
52
|
+
if (!categoryId) {
|
|
53
|
+
return NextResponse.json({ success: false, error: "Missing category id" }, { status: 400 });
|
|
54
|
+
}
|
|
55
|
+
const leafDoc = await categoriesRepository.findById(categoryId);
|
|
56
|
+
if (!leafDoc) {
|
|
57
|
+
return NextResponse.json({ success: false, error: "Category not found" }, { status: 404 });
|
|
58
|
+
}
|
|
59
|
+
const guidance = await resolveInheritedGuidance(leafDoc);
|
|
60
|
+
if (!guidance) {
|
|
61
|
+
return NextResponse.json({ success: true, data: { guidance: null, basisNote: null } });
|
|
62
|
+
}
|
|
63
|
+
/*
|
|
64
|
+
* One read, admin exempted (no store to look up). `pricesIncludeGst`
|
|
65
|
+
* absent means `true` — matching the schema default and the store
|
|
66
|
+
* route's own `?? true` (see `PriceGuidanceBasis.sellerIncludesGst`'s
|
|
67
|
+
* docstring for why this must stay consistent everywhere it's read).
|
|
68
|
+
*/
|
|
69
|
+
let sellerIncludesGst;
|
|
70
|
+
if (!isAdminUser(user)) {
|
|
71
|
+
const { db } = getProviders();
|
|
72
|
+
const storesRepo = db?.getRepository("stores");
|
|
73
|
+
const result = await storesRepo?.findAll({ filters: `ownerId==${user.uid}`, pageSize: 1 });
|
|
74
|
+
sellerIncludesGst = result?.data[0]?.pricesIncludeGst;
|
|
75
|
+
}
|
|
76
|
+
const basis = { sellerIncludesGst, gstRate: undefined };
|
|
77
|
+
const onSellerBasis = priceGuidanceOnSellerBasis(guidance, basis);
|
|
78
|
+
const basisNote = describePriceGuidanceBasis(basis);
|
|
79
|
+
return NextResponse.json({ success: true, data: { guidance: onSellerBasis, basisNote } }, {
|
|
80
|
+
// Per-seller basis, so never shared across callers.
|
|
81
|
+
headers: { "Cache-Control": "private, max-age=3600" },
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
const e = normalizeError(err);
|
|
86
|
+
serverLogger.error("price-guidance read failed", { message: e.message });
|
|
87
|
+
return NextResponse.json({ success: false, error: "Could not load price guidance" }, { status: 500 });
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
});
|
|
@@ -7,6 +7,7 @@ import { ROUTES } from "../../../next/routing/route-map.js";
|
|
|
7
7
|
import { Anchor, Container, Div, Dl, Dt, Dd, Heading, Main, Nav, Section, Span, Stack, Text } from "../../../ui/index.js";
|
|
8
8
|
import { DynamicBgDiv } from "../../../ui/components/DynamicBgDiv.js";
|
|
9
9
|
import { MediaImage } from "../../media/MediaImage.js";
|
|
10
|
+
import { RichTextRenderer } from "../../../ui/rich-text/RichTextRenderer.js";
|
|
10
11
|
import { BrandDetailTabs } from "./BrandDetailTabs.js";
|
|
11
12
|
import { CategoryGrid } from "./CategoryGrid.js";
|
|
12
13
|
import { CategoryHighlightsAndFaqSection } from "./CategoryHighlightsAndFaqSection.js";
|
|
@@ -27,8 +28,23 @@ export async function BrandDetailPageView({ slug, initialBrand }) {
|
|
|
27
28
|
// The brand IS this page's subject — a read failure must surface rather than
|
|
28
29
|
// render an anonymous, empty brand page. `undefined` still means "no such
|
|
29
30
|
// brand slug".
|
|
30
|
-
const
|
|
31
|
-
|
|
31
|
+
const brandDoc = initialBrand ?? (await categoriesRepository.getCategoryBySlug(slug)) ?? undefined;
|
|
32
|
+
const brand = (brandDoc ?? undefined);
|
|
33
|
+
/*
|
|
34
|
+
* 🛑 Read from the DOCUMENT, not from the `CategoryItem` cast — the same rule
|
|
35
|
+
* `CategoryDetailPageView` states for this exact field.
|
|
36
|
+
*
|
|
37
|
+
* `contentBody` is deliberately PRIVATE on the list projection: it is 400-900
|
|
38
|
+
* chars of HTML per row and `CategoryInlineSelect` fetches 200 rows on every
|
|
39
|
+
* seller-form open, so publishing it there would be the Fast-Origin-Transfer
|
|
40
|
+
* shape of Root Cause #104. Widening `CategoryItem` to satisfy one render site
|
|
41
|
+
* is exactly the invitation that comment warns about — and it is what the
|
|
42
|
+
* first attempt at this did, which `tsc` caught.
|
|
43
|
+
*
|
|
44
|
+
* No extra read: this page already holds the whole document (`initialBrand`,
|
|
45
|
+
* or the fetch above). Only the local cast hid it.
|
|
46
|
+
*/
|
|
47
|
+
const brandContentBody = typeof brandDoc?.contentBody === "string" ? brandDoc.contentBody : null;
|
|
32
48
|
const brandName = brand?.name;
|
|
33
49
|
const [productsResult, tabCounts, allBundles, activeBrands, groupedListings, settings] = await Promise.all([
|
|
34
50
|
brandName
|
|
@@ -44,7 +60,7 @@ export async function BrandDetailPageView({ slug, initialBrand }) {
|
|
|
44
60
|
// therefore never hide, however empty the brand was.
|
|
45
61
|
brandName
|
|
46
62
|
? listingTabCounts(CATEGORY_PAGE_TABS, { brandName })
|
|
47
|
-
: Promise.resolve({}),
|
|
63
|
+
: Promise.resolve({ available: {}, total: {} }),
|
|
48
64
|
// SB-UNI-D — bundles are categoryType:"bundle" rows on the categories
|
|
49
65
|
// collection, tagged to a brand via the real brandSlug field (added
|
|
50
66
|
// 2026-08-21 — previously a fragile seo.keywords string-match heuristic).
|
|
@@ -92,15 +108,23 @@ export async function BrandDetailPageView({ slug, initialBrand }) {
|
|
|
92
108
|
// Keyed by tabSlug. `bundles` is overridden with the brand-scoped figure —
|
|
93
109
|
// `listingTabCounts` cannot narrow bundles by brand (a bundle's brand lives on
|
|
94
110
|
// `brandSlug`, which this page already filters on locally).
|
|
111
|
+
// Badge = available scope; hiding + the header pill = total. See the same
|
|
112
|
+
// split in `CategoryDetailPageView` and `ListingTabCountResult`.
|
|
95
113
|
const counts = {
|
|
96
|
-
...tabCounts,
|
|
97
|
-
products: tabCounts.products ?? brand?.metrics?.productCount ?? 0,
|
|
98
|
-
auctions: tabCounts.auctions ?? brand?.metrics?.auctionCount ?? 0,
|
|
114
|
+
...tabCounts.available,
|
|
115
|
+
products: tabCounts.available.products ?? brand?.metrics?.productCount ?? 0,
|
|
116
|
+
auctions: tabCounts.available.auctions ?? brand?.metrics?.auctionCount ?? 0,
|
|
117
|
+
bundles: brandBundles.length,
|
|
118
|
+
};
|
|
119
|
+
const totals = {
|
|
120
|
+
...tabCounts.total,
|
|
121
|
+
products: tabCounts.total.products ?? brand?.metrics?.productCount ?? 0,
|
|
122
|
+
auctions: tabCounts.total.auctions ?? brand?.metrics?.auctionCount ?? 0,
|
|
99
123
|
bundles: brandBundles.length,
|
|
100
124
|
};
|
|
101
125
|
// Header total — `?? 0` is right here (a pill just doesn't render), unlike the
|
|
102
126
|
// tab bar where an unknown count must keep its tab visible.
|
|
103
|
-
const totalItems = Object.values(
|
|
127
|
+
const totalItems = Object.values(totals).reduce((sum, n) => sum + (n ?? 0), 0);
|
|
104
128
|
/*
|
|
105
129
|
* ── D4: structured data on the brand page ───────────────────────────────
|
|
106
130
|
*
|
|
@@ -152,5 +176,5 @@ export async function BrandDetailPageView({ slug, initialBrand }) {
|
|
|
152
176
|
const ldFaq = brand?.faqs && brand.faqs.length > 0
|
|
153
177
|
? faqJsonLd(brand.faqs.map((f) => ({ question: f.question, answer: f.answer })))
|
|
154
178
|
: null;
|
|
155
|
-
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 && (_jsx(DynamicBgDiv, { color: brandColor, className: "absolute inset-0 opacity-10" })), _jsxs(Div, { className: "relative z-10 max-w-7xl mx-auto", paddingY: hasCover ? "y-3xl" : "y-xl", padding: "x-md", children: [_jsxs(Nav, { layout: "flex", gap: "2xs", textSize: "sm", className: "mb-4", "aria-label": "Breadcrumb", children: [_jsx(Link, { href: String(ROUTES.HOME), className: hasCover ? "text-white/70 hover:text-white transition-colors" : "text-[var(--appkit-color-text-muted)] hover:text-primary-600 transition-colors", children: "Home" }), _jsx(Span, { className: hasCover ? "text-white/40" : "text-zinc-400", children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.BRANDS), className: hasCover ? "text-white/70 hover:text-white transition-colors" : "text-[var(--appkit-color-text-muted)] hover:text-primary-600 transition-colors", children: "Brands" }), _jsx(Span, { className: hasCover ? "text-white/40" : "text-zinc-400", children: "/" }), _jsx(Span, { weight: "medium", className: hasCover ? "text-white" : "text-[var(--appkit-color-text)]", children: brand?.name ?? slug })] }), _jsxs(Row, { className: "mb-3", align: "center", gap: "md", children: [brand?.display?.icon && (_jsx(Span, { className: "leading-none", size: "5xl", children: brand.display.icon })), _jsxs(_Fragment, { children: [_jsx(Heading, { color: "inverse", level: 1, className: `${hasCover ? "" : "text-[var(--appkit-color-text)]"}`, size: "3xl", mdSize: "4xl", weight: "bold", children: brand?.name ?? slug }), brand?.description && typeof brand.description === "string" && !brand.description.startsWith("{") && (_jsx(Text, { color: hasCover ? "inverse" : "muted", className: `max-w-2xl mt-1 ${hasCover ? "/80" : ""}`, size: "base", children: brand.description }))] })] }), _jsxs(Row, { gap: "sm", wrap: true, className: "mt-3", children: [(counts.products ?? 0) > 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: [counts.products.toLocaleString(), " ", counts.products === 1 ? "product" : "products"] })), (counts.auctions ?? 0) > 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: [counts.auctions.toLocaleString(), " ", counts.auctions === 1 ? "auction" : "auctions"] })), (counts["pre-orders"] ?? 0) > 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: [counts["pre-orders"].toLocaleString(), " ", counts["pre-orders"] === 1 ? "pre-order" : "pre-orders"] })), totalItems === 0 && (_jsx(Span, { size: "sm", className: hasCover ? "text-white/60" : "text-zinc-400", children: "No items listed yet" }))] })] })] }), (brand?.brandWebsite || brand?.brandCountry || brand?.brandFounded) && (_jsx(Section, { border: "subtle", surface: "default", className: "border-b", padding: "y-md", children: _jsx(Container, { size: "xl", children: _jsxs(Stack, { gap: "sm", children: [_jsx(Heading, { level: 2, size: "sm", weight: "semibold", color: "muted", transform: "uppercase", children: "About this brand" }), _jsxs(Dl, { divide: "subtle", rounded: "xl", border: "subtle", className: "overflow-hidden max-w-lg", children: [brand?.brandWebsite && (_jsxs(Row, { gap: "md", oddEven: "zebra", surface: "default", padding: "inline", children: [_jsx(Dt, { className: "w-32 flex-shrink-0", color: "primary", weight: "medium", children: "Website" }), _jsx(Dd, { className: "flex-1", children: _jsx(Anchor, { href: brand.brandWebsite, tone: "brand", underline: "hover", children: brand.brandWebsite }) })] })), brand?.brandCountry && (_jsxs(Row, { gap: "md", oddEven: "zebra", surface: "default", padding: "inline", children: [_jsx(Dt, { className: "w-32 flex-shrink-0", color: "primary", weight: "medium", children: "Country" }), _jsx(Dd, { className: "flex-1", color: "muted", children: brand.brandCountry })] })), brand?.brandFounded && (_jsxs(Row, { gap: "md", oddEven: "zebra", surface: "default", padding: "inline", children: [_jsx(Dt, { className: "w-32 flex-shrink-0", color: "primary", weight: "medium", children: "Founded" }), _jsx(Dd, { className: "flex-1", color: "muted", children: brand.brandFounded })] }))] })] }) }) })), _jsx(CategoryHighlightsAndFaqSection, { highlights: brand?.highlights, faqs: brand?.faqs }), _jsx(Section, { padding: "y-lg", children: _jsx(Container, { size: "xl", children: brandName ? (_jsx(BrandDetailTabs, { brandName: brandName, initialProductsData: productsResult ?? undefined, initialBundles: brandBundles, counts: counts, enabledListingTypes: enabledListingTypes(settings), enabledCategoryTypes: enabledCategoryTypes(settings) })) : (_jsx(Text, { paddingY: "3xl", color: "muted", size: "sm", align: "start", children: "Brand not found." })) }) }), groupedListings.length > 0 && (_jsx(Section, { padding: "y-lg", children: _jsx(Container, { size: "xl", children: _jsx(GroupedListingsCarousel, { groups: groupedListings }) }) })), relatedBrands.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 Brands" }), _jsx(CategoryGrid, { categories: relatedBrands, getHref: (b) => String(ROUTES.PUBLIC.BRAND_DETAIL(b.slug)) })] }) }))] }));
|
|
179
|
+
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 && (_jsx(DynamicBgDiv, { color: brandColor, className: "absolute inset-0 opacity-10" })), _jsxs(Div, { className: "relative z-10 max-w-7xl mx-auto", paddingY: hasCover ? "y-3xl" : "y-xl", padding: "x-md", children: [_jsxs(Nav, { layout: "flex", gap: "2xs", textSize: "sm", className: "mb-4", "aria-label": "Breadcrumb", children: [_jsx(Link, { href: String(ROUTES.HOME), className: hasCover ? "text-white/70 hover:text-white transition-colors" : "text-[var(--appkit-color-text-muted)] hover:text-primary-600 dark:hover:text-primary-400 transition-colors", children: "Home" }), _jsx(Span, { className: hasCover ? "text-white/40" : "text-zinc-400", children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.BRANDS), className: hasCover ? "text-white/70 hover:text-white transition-colors" : "text-[var(--appkit-color-text-muted)] hover:text-primary-600 dark:hover:text-primary-400 transition-colors", children: "Brands" }), _jsx(Span, { className: hasCover ? "text-white/40" : "text-zinc-400", children: "/" }), _jsx(Span, { weight: "medium", className: hasCover ? "text-white" : "text-[var(--appkit-color-text)]", children: brand?.name ?? slug })] }), _jsxs(Row, { className: "mb-3", align: "center", gap: "md", children: [brand?.display?.icon && (_jsx(Span, { className: "leading-none", size: "5xl", children: brand.display.icon })), _jsxs(_Fragment, { children: [_jsx(Heading, { color: "inverse", level: 1, className: `${hasCover ? "" : "text-[var(--appkit-color-text)]"}`, size: "3xl", mdSize: "4xl", weight: "bold", children: brand?.name ?? slug }), brand?.description && typeof brand.description === "string" && !brand.description.startsWith("{") && (_jsx(Text, { color: hasCover ? "inverse" : "muted", className: `max-w-2xl mt-1 ${hasCover ? "/80" : ""}`, size: "base", children: brand.description }))] })] }), _jsxs(Row, { gap: "sm", wrap: true, className: "mt-3", children: [(counts.products ?? 0) > 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: [counts.products.toLocaleString(), " ", counts.products === 1 ? "product" : "products"] })), (counts.auctions ?? 0) > 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: [counts.auctions.toLocaleString(), " ", counts.auctions === 1 ? "auction" : "auctions"] })), (counts["pre-orders"] ?? 0) > 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: [counts["pre-orders"].toLocaleString(), " ", counts["pre-orders"] === 1 ? "pre-order" : "pre-orders"] })), totalItems === 0 && (_jsx(Span, { size: "sm", className: hasCover ? "text-white/60" : "text-zinc-400", children: "No items listed yet" }))] })] })] }), (brand?.brandWebsite || brand?.brandCountry || brand?.brandFounded) && (_jsx(Section, { border: "subtle", surface: "default", className: "border-b", padding: "y-md", children: _jsx(Container, { size: "xl", children: _jsxs(Stack, { gap: "sm", children: [_jsx(Heading, { level: 2, size: "sm", weight: "semibold", color: "muted", transform: "uppercase", children: "About this brand" }), _jsxs(Dl, { divide: "subtle", rounded: "xl", border: "subtle", className: "overflow-hidden max-w-lg", children: [brand?.brandWebsite && (_jsxs(Row, { gap: "md", oddEven: "zebra", surface: "default", padding: "inline", children: [_jsx(Dt, { className: "w-32 flex-shrink-0", color: "primary", weight: "medium", children: "Website" }), _jsx(Dd, { className: "flex-1", children: _jsx(Anchor, { href: brand.brandWebsite, tone: "brand", underline: "hover", children: brand.brandWebsite }) })] })), brand?.brandCountry && (_jsxs(Row, { gap: "md", oddEven: "zebra", surface: "default", padding: "inline", children: [_jsx(Dt, { className: "w-32 flex-shrink-0", color: "primary", weight: "medium", children: "Country" }), _jsx(Dd, { className: "flex-1", color: "muted", children: brand.brandCountry })] })), brand?.brandFounded && (_jsxs(Row, { gap: "md", oddEven: "zebra", surface: "default", padding: "inline", children: [_jsx(Dt, { className: "w-32 flex-shrink-0", color: "primary", weight: "medium", children: "Founded" }), _jsx(Dd, { className: "flex-1", color: "muted", children: brand.brandFounded })] }))] })] }) }) })), brandContentBody && brandContentBody.trim().length > 0 && (_jsx(Section, { padding: "y-lg", children: _jsx(Container, { size: "xl", children: _jsx(RichTextRenderer, { html: brandContentBody }) }) })), _jsx(CategoryHighlightsAndFaqSection, { highlights: brand?.highlights, faqs: brand?.faqs }), _jsx(Section, { padding: "y-lg", children: _jsx(Container, { size: "xl", children: brandName ? (_jsx(BrandDetailTabs, { brandName: brandName, initialProductsData: productsResult ?? undefined, initialBundles: brandBundles, counts: counts, totals: totals, enabledListingTypes: enabledListingTypes(settings), enabledCategoryTypes: enabledCategoryTypes(settings) })) : (_jsx(Text, { paddingY: "3xl", color: "muted", size: "sm", align: "start", children: "Brand not found." })) }) }), groupedListings.length > 0 && (_jsx(Section, { padding: "y-lg", children: _jsx(Container, { size: "xl", children: _jsx(GroupedListingsCarousel, { groups: groupedListings }) }) })), relatedBrands.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 Brands" }), _jsx(CategoryGrid, { categories: relatedBrands, getHref: (b) => String(ROUTES.PUBLIC.BRAND_DETAIL(b.slug)) })] }) }))] }));
|
|
156
180
|
}
|
|
@@ -10,10 +10,16 @@ export interface BrandDetailTabsProps {
|
|
|
10
10
|
* inventory (Root Cause #59). Keyed by tab id; the previous hand-named shape
|
|
11
11
|
* covered only 5 of 9 tabs (Root Cause #61).
|
|
12
12
|
*/
|
|
13
|
+
/** Badge counts — what the default (available) scope will render. */
|
|
13
14
|
counts?: Record<string, number | undefined>;
|
|
15
|
+
/**
|
|
16
|
+
* Every published row per tab, all availability scopes. Drives hide-at-zero
|
|
17
|
+
* and the "N ended — show all" hint; see `ListingTabCountResult`.
|
|
18
|
+
*/
|
|
19
|
+
totals?: Record<string, number | undefined>;
|
|
14
20
|
/** Enabled listing types (e.g. ["standard","auction"]). When omitted, all tabs shown. */
|
|
15
21
|
enabledListingTypes?: string[];
|
|
16
22
|
/** Enabled category types (e.g. ["category","brand","bundle"]). When omitted, all tabs shown. */
|
|
17
23
|
enabledCategoryTypes?: string[];
|
|
18
24
|
}
|
|
19
|
-
export declare function BrandDetailTabs({ brandName, initialProductsData, initialBundles, counts, enabledListingTypes, enabledCategoryTypes, }: BrandDetailTabsProps): React.JSX.Element;
|
|
25
|
+
export declare function BrandDetailTabs({ brandName, initialProductsData, initialBundles, counts, totals, enabledListingTypes, enabledCategoryTypes, }: BrandDetailTabsProps): 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";
|
|
@@ -28,6 +29,18 @@ const TAB_TYPE_MAP = {
|
|
|
28
29
|
])),
|
|
29
30
|
bundles: { kind: "category", type: "bundle" },
|
|
30
31
|
};
|
|
32
|
+
/**
|
|
33
|
+
* The listing types a tab spans, for the availability-scope hint. Derived
|
|
34
|
+
* from the same maps the tabs render from, never hand-written (Root Cause
|
|
35
|
+
* #61).
|
|
36
|
+
*/
|
|
37
|
+
function typesForTab(id) {
|
|
38
|
+
const generic = GENERIC_TAB_LISTING_TYPES[id];
|
|
39
|
+
if (generic)
|
|
40
|
+
return generic;
|
|
41
|
+
const mapping = TAB_TYPE_MAP[id];
|
|
42
|
+
return mapping?.kind === "listing" ? [mapping.type] : [];
|
|
43
|
+
}
|
|
31
44
|
/** Listing types rendered by the shared products listing rather than a dedicated one. */
|
|
32
45
|
const GENERIC_TAB_LISTING_TYPES = {
|
|
33
46
|
[pluginFor("standard").tabSlug]: ["standard"],
|
|
@@ -35,9 +48,20 @@ const GENERIC_TAB_LISTING_TYPES = {
|
|
|
35
48
|
[pluginFor("digital-code").tabSlug]: ["digital-code"],
|
|
36
49
|
[pluginFor("live").tabSlug]: ["live"],
|
|
37
50
|
[pluginFor("art").tabSlug]: ART_STICKERS_LISTING_TYPES,
|
|
51
|
+
/*
|
|
52
|
+
* Spares render generically — a product grid is the whole story, unlike an
|
|
53
|
+
* auction (countdown, bid history) or a prize draw (entries remaining).
|
|
54
|
+
*
|
|
55
|
+
* 🛑 Hand-written map; a missing entry is a tab you can CLICK that renders an
|
|
56
|
+
* empty panel. `audit-tab-body-coverage` exists for exactly this, after this
|
|
57
|
+
* file silently DROPPED four types while its `CategoryDetailTabs` twin
|
|
58
|
+
* rendered them as blank panels — two spellings of one gap.
|
|
59
|
+
*/
|
|
60
|
+
[pluginFor("spares").tabSlug]: ["spares"],
|
|
38
61
|
};
|
|
39
|
-
export function BrandDetailTabs({ brandName, initialProductsData, initialBundles = [], counts, enabledListingTypes, enabledCategoryTypes, }) {
|
|
62
|
+
export function BrandDetailTabs({ brandName, initialProductsData, initialBundles = [], counts, totals, enabledListingTypes, enabledCategoryTypes, }) {
|
|
40
63
|
const countFor = (id) => counts?.[id];
|
|
64
|
+
const totalFor = (id) => totals?.[id] ?? counts?.[id];
|
|
41
65
|
const visibleTabs = CATEGORY_PAGE_TABS.filter((t) => {
|
|
42
66
|
const mapping = TAB_TYPE_MAP[t.id];
|
|
43
67
|
// A tab with no mapping has no render branch below and would show a blank
|
|
@@ -57,7 +81,8 @@ export function BrandDetailTabs({ brandName, initialProductsData, initialBundles
|
|
|
57
81
|
// Hide a tab only when its count is known and explicitly zero. `undefined`
|
|
58
82
|
// now means the count query FAILED (every tab is counted), so the tab stays
|
|
59
83
|
// visible rather than hiding real listings behind a swallowed error.
|
|
60
|
-
|
|
84
|
+
// TOTAL, not the badge — see the same note in CategoryDetailTabs.
|
|
85
|
+
const count = totalFor(t.id);
|
|
61
86
|
return count === undefined || count > 0;
|
|
62
87
|
});
|
|
63
88
|
const firstTabId = (visibleTabs[0]?.id ?? "products");
|
|
@@ -65,5 +90,5 @@ export function BrandDetailTabs({ brandName, initialProductsData, initialBundles
|
|
|
65
90
|
if (visibleTabs.length === 0) {
|
|
66
91
|
return (_jsx(EmptyState, { title: "Nothing listed here yet", description: "This brand has no active listings right now. Check back soon." }));
|
|
67
92
|
}
|
|
68
|
-
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: "", brandName: brandName, listingTypes: GENERIC_TAB_LISTING_TYPES[activeTab], initialData: activeTab === pluginFor("standard").tabSlug ? initialProductsData : undefined })), activeTab === pluginFor("auction").tabSlug && (_jsx(AuctionsIndexListing, { brandName: brandName })), activeTab === pluginFor("pre-order").tabSlug && (_jsx(PreOrdersIndexListing, { brandName: brandName })), activeTab === pluginFor("prize-draw").tabSlug && (_jsx(PrizeDrawsIndexListing, { brandName: brandName })), activeTab === "bundles" && (_jsx(CategoryBundlesListing, { initialBundles: initialBundles, brandName: brandName }))] }));
|
|
93
|
+
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: "", brandName: brandName, listingTypes: GENERIC_TAB_LISTING_TYPES[activeTab], initialData: activeTab === pluginFor("standard").tabSlug ? initialProductsData : undefined })), activeTab === pluginFor("auction").tabSlug && (_jsx(AuctionsIndexListing, { brandName: brandName })), activeTab === pluginFor("pre-order").tabSlug && (_jsx(PreOrdersIndexListing, { brandName: brandName })), activeTab === pluginFor("prize-draw").tabSlug && (_jsx(PrizeDrawsIndexListing, { brandName: brandName })), activeTab === "bundles" && (_jsx(CategoryBundlesListing, { initialBundles: initialBundles, brandName: brandName }))] }));
|
|
69
94
|
}
|
|
@@ -16,10 +16,11 @@ import { MediaImage } from "../../media/MediaImage.js";
|
|
|
16
16
|
import { ImageLightbox } from "../../../ui/components/ImageLightbox.js";
|
|
17
17
|
import { ROUTES } from "../../../next/routing/route-map.js";
|
|
18
18
|
import { formatCurrency } from "../../../utils/number.formatter.js";
|
|
19
|
+
import { productImageUrls } from "../../products/schemas/firestore.js";
|
|
19
20
|
const PLACEHOLDER_EMOJI = "📦";
|
|
20
21
|
function toGalleryImages(members, canSeePrices) {
|
|
21
22
|
return members.map((p, i) => ({
|
|
22
|
-
src: p.mainImage
|
|
23
|
+
src: p.mainImage || productImageUrls(p.images)[0] || "",
|
|
23
24
|
alt: p.title,
|
|
24
25
|
badge: `#${i + 1}`,
|
|
25
26
|
caption: p.title,
|
|
@@ -46,7 +47,7 @@ export function BundleCollage({ members, onItemClick }) {
|
|
|
46
47
|
return null;
|
|
47
48
|
const galleryImages = toGalleryImages(members, canSeePrices);
|
|
48
49
|
return (_jsxs(_Fragment, { children: [_jsx(Grid, { cols: "categoryCards", gap: "3", children: members.map((p, idx) => {
|
|
49
|
-
const cover = p.mainImage
|
|
50
|
+
const cover = p.mainImage || productImageUrls(p.images)[0];
|
|
50
51
|
const href = String(ROUTES.PUBLIC.PRODUCT_DETAIL?.(p.slug ?? p.id) ?? "#");
|
|
51
52
|
return (_jsxs(Div, { layout: "flex-col", className: "group relative overflow-hidden border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] transition-transform hover:scale-[1.01]", rounded: "lg", children: [_jsx(Button, { variant: "ghost", type: "button", onClick: makeBundleItemClickHandler(p, idx, onItemClick, setLightboxIndex), paddingX: "none", paddingY: "none", rounded: "none", className: "relative aspect-square w-full overflow-hidden focus-visible:ring-2 focus-visible:ring-[var(--appkit-color-primary)]", "aria-label": `View ${p.title} in lightbox`, children: _jsxs(Div, { className: "relative aspect-square w-full", children: [cover ? (_jsx(MediaImage, { src: cover, alt: p.title, size: "card", className: "transition-transform group-hover:scale-105" })) : (_jsx(Row, { textSize: "3xl", surface: "muted", className: "absolute inset-0", align: "center", justify: "center", children: PLACEHOLDER_EMOJI })), _jsxs(Div, { textWeight: "semibold", textSize: "xs", surface: "overlay-xl", padding: "chip-2xs", className: "absolute left-2 top-2 text-white", rounded: "default", children: ["#", idx + 1] })] }) }), _jsxs(Div, { padding: "xs", children: [_jsx(Link, { href: href, className: "line-clamp-2 text-[length:var(--appkit-text-sm)] font-medium hover:underline", children: p.title }), _jsx(Text, { className: "mt-0.5 text-[var(--appkit-color-text-muted)]", size: "xs", children: _jsx(GatedPrice, { children: formatCurrency(p.price ?? 0, p.currency ?? "INR") }) })] })] }, p.id));
|
|
52
53
|
}) }), _jsx(ImageLightbox, { images: galleryImages, activeIndex: lightboxIndex, onClose: () => setLightboxIndex(null), onNavigate: (i) => setLightboxIndex(i), showThumbnails: true })] }));
|
|
@@ -18,6 +18,7 @@ import { Badge, Button, Checkbox, Div, Input, Row, Stack, Text } from "../../../
|
|
|
18
18
|
import { BUNDLE_MAX_ITEMS, BUNDLE_MIN_ITEMS, } from "../../../_internal/shared/features/categories/bundle-config.js";
|
|
19
19
|
import { BUNDLE_COPY } from "../../../_internal/shared/features/categories/bundle-copy.js";
|
|
20
20
|
import { PRODUCT_ENDPOINTS } from "../../../constants/api-endpoints.js";
|
|
21
|
+
import { productImageUrls } from "../../products/schemas/firestore.js";
|
|
21
22
|
const __P = {
|
|
22
23
|
p3: "p-[var(--appkit-space-3)]",
|
|
23
24
|
};
|
|
@@ -137,11 +138,9 @@ export async function defaultBundleItemsFetch(query) {
|
|
|
137
138
|
return {
|
|
138
139
|
id,
|
|
139
140
|
title,
|
|
140
|
-
image: typeof rec.mainImage === "string"
|
|
141
|
+
image: typeof rec.mainImage === "string" && rec.mainImage
|
|
141
142
|
? rec.mainImage
|
|
142
|
-
:
|
|
143
|
-
? rec.images[0]
|
|
144
|
-
: undefined,
|
|
143
|
+
: productImageUrls(rec.images)[0],
|
|
145
144
|
price: typeof rec.price === "number" ? rec.price : undefined,
|
|
146
145
|
storeId: typeof rec.storeId === "string" ? rec.storeId : undefined,
|
|
147
146
|
};
|
|
@@ -4,7 +4,7 @@ import { useState, useCallback, useMemo } from "react";
|
|
|
4
4
|
import { useCategoriesFiltered } from "../hooks/useCategories.js";
|
|
5
5
|
import { CATEGORY_TABS, DEFAULT_CATEGORY_TAB, matchesCategoryTab, } from "../constants/category-tabs.js";
|
|
6
6
|
import { ROUTES } from "../../../next/routing/route-map.js";
|
|
7
|
-
import {
|
|
7
|
+
import { Div, Grid, ListingFilterDrawer, ListingToolbar, OverflowRow, Pagination, Row, Stack, Text, StickyToolbar } from "../../../ui/index.js";
|
|
8
8
|
import { CategoryCard } from "./CategoryGrid.js";
|
|
9
9
|
import { CategoryFilters } from "./CategoryFilters.js";
|
|
10
10
|
import { useUrlTable } from "../../../react/hooks/useUrlTable.js";
|
|
@@ -170,13 +170,28 @@ export function CategoriesIndexListing({ brandsOnly = false }) {
|
|
|
170
170
|
pageSize: PAGE_SIZE,
|
|
171
171
|
});
|
|
172
172
|
const activeSearch = table.get(TABLE_KEYS.QUERY) || "";
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
173
|
+
/*
|
|
174
|
+
* 🛑 `<OverflowRow>`, not a hand-rolled row of ghost Buttons.
|
|
175
|
+
*
|
|
176
|
+
* This was five `<Button variant="ghost">`s in a `<Row>` with a hand-written
|
|
177
|
+
* underline — no `role="tablist"`, no `role="tab"`, and no overflow
|
|
178
|
+
* behaviour at all, so on a narrow viewport the last tabs simply ran past
|
|
179
|
+
* the right edge with no scrollbar and no affordance. The Known-TS-Patterns
|
|
180
|
+
* table bans exactly that shape ("A hand-rolled overflow-x-auto row of
|
|
181
|
+
* pills/tabs → <OverflowRow>"), and the primitive built for it had shipped
|
|
182
|
+
* three weeks earlier without this surface being migrated.
|
|
183
|
+
*
|
|
184
|
+
* `variant="tabs"` because these swap a panel in place — they rewrite the
|
|
185
|
+
* `tab` URL param and re-partition one already-cached payload; they do not
|
|
186
|
+
* navigate. That is also why the items carry `onSelect` rather than `href`,
|
|
187
|
+
* and therefore why `linkProps`/`scroll` do not apply here.
|
|
188
|
+
*/
|
|
189
|
+
const tabItems = CATEGORY_TABS.map((tab) => ({
|
|
190
|
+
id: tab.key,
|
|
191
|
+
label: tab.label,
|
|
192
|
+
onSelect: () => switchTab(tab.key),
|
|
193
|
+
}));
|
|
194
|
+
return (_jsxs(Div, { className: "min-h-screen", children: [!brandsOnly && (_jsx(Div, { className: "mb-2", children: _jsx(OverflowRow, { items: tabItems, activeId: activeTab, variant: "tabs", ariaLabel: "Category index sections", searchPlaceholder: "Search sections\u2026" }) })), _jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: activeTab === "brands" || brandsOnly ? "Search brands..." : "Search categories...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: sort, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, view: view, onViewChange: handleViewToggle, onResetAll: resetAll, hasActiveState: hasActiveState, toggles: [
|
|
180
195
|
{ label: "Featured only", active: table.get(TABLE_KEYS.IS_FEATURED) === "true", onChange: (next) => table.set(TABLE_KEYS.IS_FEATURED, next ? "true" : "") },
|
|
181
196
|
] }), totalPages > 1 && (_jsx(StickyToolbar, { offset: "header+pagination", tone: "translucent", border: true, padding: "toolbar", children: _jsx(Row, { justify: "center", children: _jsx(Pagination, { currentPage: page, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) }) })), _jsx(Div, { padding: "y-lg", children: renderCategoryGrid({ isLoading, categories, view, activeSearch, activeTab, brandsOnly }) }), renderCategoryFilterDrawer({ filterOpen, setFilterOpen, activeFilterCount, clearFilters, applyFilters, pendingTable })] }));
|
|
182
197
|
}
|