@mohasinac/appkit 4.53.0 → 4.53.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.build-stamp.json +3 -3
- package/dist/_internal/client/scaffolds/AppShell.js +1 -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/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/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/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 +25 -0
- package/dist/_internal/server/features/stores/tabs.js +122 -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 +2 -1
- 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 +39 -15
- package/dist/constants/api-endpoints.js +28 -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 +4 -11
- 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 +113 -0
- package/dist/features/categories/components/BrandDetailPageView.js +19 -3
- package/dist/features/categories/components/BrandDetailTabs.js +10 -0
- package/dist/features/categories/components/CategoriesIndexListing.js +23 -8
- package/dist/features/categories/components/CategoryDetailPageView.js +141 -14
- package/dist/features/categories/components/CategoryDetailTabs.js +12 -0
- 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 +4 -9
- 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 +4 -9
- 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 +4 -9
- 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 +4 -11
- package/dist/features/pre-orders/components/PreOrdersIndexListing.js +1 -1
- package/dist/features/price-index/components/PriceIndexToolView.d.ts +3 -1
- package/dist/features/price-index/components/PriceIndexToolView.js +92 -13
- package/dist/features/products/components/CompareOverlay.d.ts +18 -1
- package/dist/features/products/components/CompareOverlay.js +21 -2
- 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 +91 -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 +74 -15
- 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/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 +98 -0
- package/dist/features/products/repository/products.repository.js +235 -17
- 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 +39 -0
- package/dist/features/products/utils/group-member-image.js +3 -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/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/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 +280 -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 +372 -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 +441 -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 +9 -9
- package/dist/server.js +27 -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 +135 -26
- 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
|
@@ -67,7 +67,7 @@ function dedicatedPageFor(types) {
|
|
|
67
67
|
label: `Full ${plugin.pluralLabel} filters →`,
|
|
68
68
|
};
|
|
69
69
|
}
|
|
70
|
-
export function ProductsIndexListing({ initialData, listingTypes = GENERIC_PRODUCT_LISTING_TYPES, typeTabs = PRODUCT_TYPE_FILTER_TABS, searchPlaceholder = "Search products...", }) {
|
|
70
|
+
export function ProductsIndexListing({ initialData, listingTypes = GENERIC_PRODUCT_LISTING_TYPES, typeTabs = PRODUCT_TYPE_FILTER_TABS, searchPlaceholder = "Search products...", storeId: scopedStoreId, }) {
|
|
71
71
|
const router = useRouter();
|
|
72
72
|
const table = useUrlTable({ defaults: { pageSize: "24", sort: DEFAULT_SORT } });
|
|
73
73
|
const { showToast } = useToast();
|
|
@@ -155,7 +155,7 @@ export function ProductsIndexListing({ initialData, listingTypes = GENERIC_PRODU
|
|
|
155
155
|
maxPrice: table.get(TABLE_KEYS.MAX_PRICE) ? Number(table.get(TABLE_KEYS.MAX_PRICE)) : undefined,
|
|
156
156
|
condition: table.get(TABLE_KEYS.CONDITION) || undefined,
|
|
157
157
|
brand: table.get(TABLE_KEYS.BRAND) || undefined,
|
|
158
|
-
storeId: table.get(TABLE_KEYS.STORE_ID) || undefined,
|
|
158
|
+
storeId: scopedStoreId || table.get(TABLE_KEYS.STORE_ID) || undefined,
|
|
159
159
|
freeShipping: table.get(TABLE_KEYS.FREE_SHIPPING) === "true" ? true : undefined,
|
|
160
160
|
// These three are in FILTER_KEYS (so they count toward the filter badge)
|
|
161
161
|
// but were never put on the wire — the drawer sections were inert.
|
|
@@ -345,5 +345,5 @@ export function ProductsIndexListing({ initialData, listingTypes = GENERIC_PRODU
|
|
|
345
345
|
] }), 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: isLoading ? (_jsx(Grid, { cols: "cards", gap: "md", children: Array.from({ length: 10 }).map((_, i) => (_jsxs(Div, { className: `${__O.hidden} animate-pulse`, border: "subtle", rounded: "xl", children: [_jsx(Div, { className: "aspect-square", surface: "subtle" }), _jsxs(Stack, { className: `${__P.p3}`, gap: "sm", children: [_jsx(Div, { className: "h-3 w-3/4", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-3 w-1/2", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-4 w-1/3", surface: "subtle", rounded: "default" })] })] }, i))) })) : (_jsx(ProductGrid, { products: products, getProductHref: (p) => pluginFor(p.listingType ?? "standard").detailRoute(p.slug || p.id), view: view === "grid" ? "card" : "list", onWishlistToggle: handleWishlistToggle, wishlistedIds: wishlistedIds, onAddToCart: handleAddToCart, onBuyNow: handleBuyNow, selectionMode: selection.isSelecting, selectedIds: selection.selectedIdSet, onToggleSelect: selection.toggle })) }), _jsx(CompareOverlay, { isOpen: compareIds.length > 0, productIds: compareIds, productType: "product", onClose: () => {
|
|
346
346
|
setCompareIds([]);
|
|
347
347
|
selection.clearSelection();
|
|
348
|
-
}, onRemove: (id) => setCompareIds((ids) => ids.filter((i) => i !== id)) }), _jsx(FilterDrawer, { open: filterOpen, onOpen: openFilters, onClose: () => setFilterOpen(false), onApply: applyFilters, onReset: onFilterClear, activeCount: filterActiveCount, hideTrigger: true, children: _jsx(ProductFilters, { table: pendingTable, currencyPrefix: "\u20B9", listingTypeOptions: listingTypeOptions, listingTypeFooter: dedicatedPage ? (_jsx(Div, { padding: "x-md", paddingY: "b-sm", children: _jsx(TextLink, { href: dedicatedPage.href, size: "sm", weight: "medium", children: dedicatedPage.label }) })) : null, categoryOptions: categoryOptions, brandOptions: brandOptions, featureOptions: featureOptions }) }), _jsx(LoginRequiredModal, { isOpen: modalOpen, onClose: closeModal, message: modalMessage })] }));
|
|
348
|
+
}, onRemove: (id) => setCompareIds((ids) => ids.filter((i) => i !== id)), onAdd: (id) => setCompareIds((ids) => (ids.includes(id) ? ids : [...ids, id])) }), _jsx(FilterDrawer, { open: filterOpen, onOpen: openFilters, onClose: () => setFilterOpen(false), onApply: applyFilters, onReset: onFilterClear, activeCount: filterActiveCount, hideTrigger: true, children: _jsx(ProductFilters, { table: pendingTable, currencyPrefix: "\u20B9", listingTypeOptions: listingTypeOptions, listingTypeFooter: dedicatedPage ? (_jsx(Div, { padding: "x-md", paddingY: "b-sm", children: _jsx(TextLink, { href: dedicatedPage.href, size: "sm", weight: "medium", children: dedicatedPage.label }) })) : null, categoryOptions: categoryOptions, brandOptions: brandOptions, featureOptions: featureOptions }) }), _jsx(LoginRequiredModal, { isOpen: modalOpen, onClose: closeModal, message: modalMessage })] }));
|
|
349
349
|
}
|
|
@@ -5,6 +5,8 @@ import Link from "next/link.js";
|
|
|
5
5
|
import { Div, PricesOnly, Row, Span, Text, Modal, SideDrawer, Button } from "../../../ui/index.js";
|
|
6
6
|
import { MediaImage } from "../../media/MediaImage.js";
|
|
7
7
|
import { GroupMemberPicker } from "./GroupMemberPicker.js";
|
|
8
|
+
import { resolveGroupMemberImage } from "../utils/group-member-image.js";
|
|
9
|
+
import { readGroupRules } from "../../../_internal/shared/features/products/group-rules.js";
|
|
8
10
|
import { formatCurrency } from "../../../utils/number.formatter.js";
|
|
9
11
|
import { normalizeListingType } from "../utils/listing-type.js";
|
|
10
12
|
import { pluginFor } from "../../../_internal/shared/listing-types/_registry.js";
|
|
@@ -18,7 +20,7 @@ function memberHref(m) {
|
|
|
18
20
|
return pluginFor(normalizeListingType(m)).detailRoute(slug);
|
|
19
21
|
}
|
|
20
22
|
function MemberThumb({ member, isCurrent }) {
|
|
21
|
-
const image = member
|
|
23
|
+
const image = resolveGroupMemberImage(member);
|
|
22
24
|
const href = memberHref(member);
|
|
23
25
|
const price = formatCurrency(member.price, member.currency ?? "INR");
|
|
24
26
|
return (_jsxs(Link, { href: href, "aria-label": member.title, className: "flex flex-col items-center gap-[var(--appkit-space-1-5)] flex-shrink-0 w-16 group", children: [_jsxs(Div, { rounded: "full", className: `relative w-14 h-14 overflow-hidden border-2 transition-all ${isCurrent
|
|
@@ -48,7 +50,18 @@ export function ShowGroupSection({ groupId, currentSlug, isParent, groupTitle })
|
|
|
48
50
|
// The overlay is now a PICKER, not a read-only table: pick how many of each
|
|
49
51
|
// member you want and add the whole selection as one cart line. The picker
|
|
50
52
|
// stands itself down (read-only, no CTA) when the group spans sellers.
|
|
51
|
-
const tableContent = (_jsx(GroupMemberPicker, { groupId: groupId, groupSource: "product-group", members: members,
|
|
53
|
+
const tableContent = (_jsx(GroupMemberPicker, { groupId: groupId, groupSource: "product-group", members: members,
|
|
54
|
+
/*
|
|
55
|
+
* A product group's rules live on the PARENT, and the parent is one of
|
|
56
|
+
* the members this component already fetched — found by `isGroupParent`
|
|
57
|
+
* rather than by position, because the endpoint makes no ordering
|
|
58
|
+
* promise and the parent is not reliably first.
|
|
59
|
+
*
|
|
60
|
+
* 🛑 This is why the five rule fields are in `PRODUCT_PUBLIC_FIELDS`: they
|
|
61
|
+
* reach `toPublicGroupMember` through that allow-list, and without it the
|
|
62
|
+
* picker would silently see no rules and permit what the server refuses.
|
|
63
|
+
*/
|
|
64
|
+
rules: readGroupRules(members.find((m) => m.isGroupParent)), onAdded: () => setShowAll(false), onAuthRequired: () => setShowAll(false) }));
|
|
52
65
|
return (_jsxs(_Fragment, { children: [_jsxs(Div, { className: `${__O.hidden}`, surface: "muted", rounded: "xl", border: "default", children: [_jsxs(Row, { role: "button", tabIndex: 0, onClick: () => setOpen((v) => !v), onKeyDown: (e) => { if (e.key === "Enter" || e.key === " ") {
|
|
53
66
|
e.preventDefault();
|
|
54
67
|
setOpen((v) => !v);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
type SearchParams = Record<string, string | string[]>;
|
|
3
|
+
export interface SparesListViewProps {
|
|
4
|
+
searchParams?: SearchParams;
|
|
5
|
+
}
|
|
6
|
+
export declare function SparesListView({ searchParams }: SparesListViewProps): Promise<React.JSX.Element>;
|
|
7
|
+
export {};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Container, Heading, Main, Section, Text, TextLink } from "../../../ui/index.js";
|
|
3
|
+
import { AdSlot } from "../../homepage/components/AdSlot.js";
|
|
4
|
+
import { ProductsIndexListing } from "./ProductsIndexListing.js";
|
|
5
|
+
import { PRODUCT_FIELDS } from "../../../constants/field-names.js";
|
|
6
|
+
import { sortBy } from "../../../constants/sort.js";
|
|
7
|
+
import { ROUTES } from "../../../next/routing/route-map.js";
|
|
8
|
+
import { listPublicProducts, parsePublicProductParams, defaultAvailabilityForListingTypes, } from "../../../_internal/server/features/products/list-public.js";
|
|
9
|
+
const DEFAULT_PAGE_SIZE = 24;
|
|
10
|
+
const DEFAULT_SORT = sortBy(PRODUCT_FIELDS.CREATED_AT);
|
|
11
|
+
/** The one type this page spans. Named so the two call sites cannot disagree. */
|
|
12
|
+
const SPARES_LISTING_TYPES = ["spares"];
|
|
13
|
+
export async function SparesListView({ searchParams = {} }) {
|
|
14
|
+
/*
|
|
15
|
+
* Filter semantics live in `listPublicProducts`, shared verbatim with
|
|
16
|
+
* `/api/products`, so the SSR first paint and the client's refetch cannot
|
|
17
|
+
* disagree about what the default view means. That matters more here than it
|
|
18
|
+
* looks: every public listing hook sets `staleTime: Infinity` once given SSR
|
|
19
|
+
* `initialData`, so a disagreement is FROZEN for that query key and no
|
|
20
|
+
* refetch ever repairs it (Root Cause #30).
|
|
21
|
+
*/
|
|
22
|
+
const result = await listPublicProducts(parsePublicProductParams(searchParams, {
|
|
23
|
+
listingTypes: SPARES_LISTING_TYPES,
|
|
24
|
+
pageSize: DEFAULT_PAGE_SIZE,
|
|
25
|
+
sorts: DEFAULT_SORT,
|
|
26
|
+
// Derived from the plugin's own `hideDefault`, so this page and
|
|
27
|
+
// `ProductsIndexListing` cannot mirror two different literals.
|
|
28
|
+
...defaultAvailabilityForListingTypes(SPARES_LISTING_TYPES),
|
|
29
|
+
}));
|
|
30
|
+
return (_jsxs(Main, { children: [_jsx(Section, { border: "bottom-subtle", paddingY: "b-md", padding: "t-xl", children: _jsxs(Container, { size: "xl", children: [_jsx(Heading, { level: 1, size: "3xl", weight: "bold", color: "primary", children: "Spares" }), _jsx(Text, { className: "mt-1", color: "muted", size: "sm", children: "Single components off disassembled tops \u2014 attack rings, weight disks, drivers, bits. Sellers list what they have spare; most are one of a kind." }), _jsxs(Text, { className: "mt-2", size: "sm", children: ["Not sure which part you need?", " ", _jsx(TextLink, { href: String(ROUTES.PUBLIC.PARTS), children: "Browse the parts glossary" }), " ", "\u2014 every component, what it does, and which tops use it."] })] }) }), _jsxs(Container, { size: "xl", padding: "x-md", children: [_jsx(AdSlot, { id: "listing-sidebar-top", className: "mb-4 mt-4" }), _jsx(ProductsIndexListing
|
|
31
|
+
// `undefined`, not `null` — React Query treats `null` as defined
|
|
32
|
+
// data, so a failed query would freeze an empty grid forever under
|
|
33
|
+
// `staleTime: Infinity`. Same reasoning as ProductsIndexPageView.
|
|
34
|
+
, {
|
|
35
|
+
// `undefined`, not `null` — React Query treats `null` as defined
|
|
36
|
+
// data, so a failed query would freeze an empty grid forever under
|
|
37
|
+
// `staleTime: Infinity`. Same reasoning as ProductsIndexPageView.
|
|
38
|
+
initialData: result ?? undefined, listingTypes: SPARES_LISTING_TYPES, searchPlaceholder: "Search spares by part, colour or model..." }), _jsx(AdSlot, { id: "listing-sidebar-bottom", className: "mt-8" })] })] }));
|
|
39
|
+
}
|
|
@@ -411,6 +411,7 @@ export declare const SELLER_BULK_ACTIONS: {
|
|
|
411
411
|
readonly live: readonly ["row-delete"];
|
|
412
412
|
readonly art: readonly ["row-delete"];
|
|
413
413
|
readonly stickers: readonly ["row-delete"];
|
|
414
|
+
readonly spares: readonly ["row-delete"];
|
|
414
415
|
readonly auctions: readonly ["row-delete"];
|
|
415
416
|
readonly preOrders: readonly ["row-delete"];
|
|
416
417
|
readonly prizeDraws: readonly ["row-delete"];
|
|
@@ -566,6 +566,7 @@ export const SELLER_BULK_ACTIONS = {
|
|
|
566
566
|
live: [ROW_ACTION_ID.DELETE],
|
|
567
567
|
art: [ROW_ACTION_ID.DELETE],
|
|
568
568
|
stickers: [ROW_ACTION_ID.DELETE],
|
|
569
|
+
spares: [ROW_ACTION_ID.DELETE],
|
|
569
570
|
auctions: [ROW_ACTION_ID.DELETE],
|
|
570
571
|
preOrders: [ROW_ACTION_ID.DELETE],
|
|
571
572
|
prizeDraws: [ROW_ACTION_ID.DELETE],
|
|
@@ -267,16 +267,106 @@ export declare class ProductRepository extends BaseRepository<ProductDocument> {
|
|
|
267
267
|
updateAvailableQuantity(productId: string, quantity: number): Promise<void>;
|
|
268
268
|
updateBid(productId: string, bidAmount: number, bidCount: number): Promise<void>;
|
|
269
269
|
findAvailable(): Promise<ProductDocument[]>;
|
|
270
|
+
/**
|
|
271
|
+
* Every member of a group, UNFILTERED — drafts, and every listing type.
|
|
272
|
+
*
|
|
273
|
+
* 🛑 This is the scope `dissolveGroup` needs and the ONLY one that may be
|
|
274
|
+
* unfiltered. A filtered dissolve leaves the excluded members carrying a
|
|
275
|
+
* `groupId` pointing at a group that no longer exists, which nothing ever
|
|
276
|
+
* clears — and that orphan is invisible until someone wonders why a product
|
|
277
|
+
* claims to be in a group with no other members.
|
|
278
|
+
*/
|
|
279
|
+
findAllByGroupId(groupId: string): Promise<ProductDocument[]>;
|
|
280
|
+
/**
|
|
281
|
+
* Group members a BUYER may see: eligible types, published only.
|
|
282
|
+
*
|
|
283
|
+
* 🛑 The type filter is DERIVED from `canJoinProductGroup`, not hard-coded.
|
|
284
|
+
* It used to be a literal `["standard", "pre-order"]`, which silently dropped
|
|
285
|
+
* every `stickers` / `art` member from the query — so `ShowGroupSection` saw
|
|
286
|
+
* one member and returned null, and the picker was unreachable with nothing
|
|
287
|
+
* erroring anywhere (Root Cause #58).
|
|
288
|
+
*
|
|
289
|
+
* 🛑 The `status` filter is NEW and is a leak fix, not a tightening. There
|
|
290
|
+
* was no status filter at all, so DRAFT members were reaching the public
|
|
291
|
+
* `/api/products/group/[groupId]` payload; widening the type list without
|
|
292
|
+
* this would have widened that leak at the same time.
|
|
293
|
+
*/
|
|
294
|
+
findGroupMembersForPublic(groupId: string): Promise<ProductDocument[]>;
|
|
295
|
+
/**
|
|
296
|
+
* Union the child-side query with the PARENT's own `groupChildSlugs`.
|
|
297
|
+
*
|
|
298
|
+
* Mirrors `resolveBundleMemberIds`, and exists for the same reason: Root
|
|
299
|
+
* Cause #42 documented a parent carrying `groupChildSlugs` while its children
|
|
300
|
+
* had no `groupId`, so a child-side query resolved only the parent and the
|
|
301
|
+
* group rendered as one member. The repository's six mutating methods all
|
|
302
|
+
* write both sides in one batch, so code cannot produce that state — but a
|
|
303
|
+
* seeded fixture or a direct Firestore edit still can, and the READER was
|
|
304
|
+
* never made resilient. `listBundleMembers` got this fix; this did not.
|
|
305
|
+
*/
|
|
306
|
+
private withParentSideFallback;
|
|
307
|
+
/**
|
|
308
|
+
* @deprecated Ambiguous about scope — say which one you mean.
|
|
309
|
+
* Kept so no caller silently changes behaviour; it resolves to the PUBLIC
|
|
310
|
+
* scope, which is what every existing caller wanted.
|
|
311
|
+
*/
|
|
270
312
|
findByGroupId(groupId: string): Promise<ProductDocument[]>;
|
|
271
313
|
startGroup(productId: string, slug: string): Promise<void>;
|
|
272
314
|
updateGroupTitle(productId: string, groupTitle: string): Promise<void>;
|
|
273
315
|
dissolveGroup(groupId: string): Promise<void>;
|
|
316
|
+
/**
|
|
317
|
+
* Link ONE child into a parent's group.
|
|
318
|
+
*
|
|
319
|
+
* 🛑 `arrayUnion`, not a spread of the in-memory `parent.groupChildSlugs`.
|
|
320
|
+
*
|
|
321
|
+
* The spread read a snapshot the CALLER was holding, so linking N children in
|
|
322
|
+
* a loop produced exactly ONE entry: every iteration computed
|
|
323
|
+
* `[...(stale ?? []), child]` from the same pre-loop document and the last
|
|
324
|
+
* write won. Nothing errored — each child got its own `groupId` correctly, so
|
|
325
|
+
* `findAllByGroupId` found them all while the parent's own
|
|
326
|
+
* `groupChildSlugs` claimed one member, which is what the group picker's
|
|
327
|
+
* fallback path and `GroupSettingsPanel` read.
|
|
328
|
+
*
|
|
329
|
+
* `arrayUnion` is also idempotent, so re-linking an existing child is a
|
|
330
|
+
* no-op instead of a duplicate id.
|
|
331
|
+
*
|
|
332
|
+
* For a known set, prefer `linkChildrenToGroup` below — one batch instead of
|
|
333
|
+
* N, which matters against Rule #6's round-trip budget when promoting a
|
|
334
|
+
* 60-part sticker sheet.
|
|
335
|
+
*/
|
|
274
336
|
linkChildToGroup(parent: ProductDocument, child: ProductDocument): Promise<void>;
|
|
337
|
+
/**
|
|
338
|
+
* Link SEVERAL children into a parent's group in one batch.
|
|
339
|
+
*
|
|
340
|
+
* Built for `promoteStickerSheetToListings`, which links every unique part of
|
|
341
|
+
* a sheet at once — N calls to `linkChildToGroup` would be N batch commits
|
|
342
|
+
* for work that is one logical change.
|
|
343
|
+
*/
|
|
344
|
+
linkChildrenToGroup(parent: ProductDocument, children: readonly ProductDocument[]): Promise<void>;
|
|
275
345
|
unlinkChildFromGroup(parent: ProductDocument, child: ProductDocument): Promise<void>;
|
|
276
346
|
leaveGroup(child: ProductDocument, parent: ProductDocument | null): Promise<void>;
|
|
277
347
|
addChildProduct(parent: ProductDocument, child: Omit<import("../schemas/firestore").ProductCreateInput, "groupId" | "isGroupParent" | "groupParentSlug" | "groupTitle">): Promise<ProductDocument>;
|
|
278
348
|
deleteByStore(storeId: string): Promise<number>;
|
|
279
349
|
delete(id: string): Promise<void>;
|
|
350
|
+
/**
|
|
351
|
+
* Every live listing OF one glossary part, across all stores.
|
|
352
|
+
*
|
|
353
|
+
* 🛑 NOT filtered by `listingType`, deliberately. Both `spares` and the
|
|
354
|
+
* 972 generated per-leaf catalogue rows (which are `standard`) carry
|
|
355
|
+
* `partGlossaryId`, and a buyer on `/parts/{slot}/{code}` wants any listing
|
|
356
|
+
* of that part — not only the ones that happen to be filed under the newer
|
|
357
|
+
* type. Narrowing to `spares` would hide most of the real inventory.
|
|
358
|
+
*
|
|
359
|
+
* 🛑 `orderBy` is `createdAt`, which EVERY document carries
|
|
360
|
+
* (`BaseRepository.create` writes it). Ordering by an optional field silently
|
|
361
|
+
* excludes every row lacking it — Root Cause #100, where `itemsSold` on zero
|
|
362
|
+
* of 95 products made a store`s whole review endpoint return nothing.
|
|
363
|
+
*
|
|
364
|
+
* Availability is filtered IN MEMORY rather than in the query: "available" is
|
|
365
|
+
* negation-shaped and no query expresses it, which is the asymmetry
|
|
366
|
+
* `baseAvailable` exists for. The window is bounded, so the cost is a few
|
|
367
|
+
* rows read and discarded.
|
|
368
|
+
*/
|
|
369
|
+
findByPartGlossaryId(partGlossaryId: string, limit?: number): Promise<ProductDocument[]>;
|
|
280
370
|
static readonly SIEVE_FIELDS: {
|
|
281
371
|
id: {
|
|
282
372
|
canFilter: boolean;
|
|
@@ -330,6 +420,14 @@ export declare class ProductRepository extends BaseRepository<ProductDocument> {
|
|
|
330
420
|
canFilter: boolean;
|
|
331
421
|
canSort: boolean;
|
|
332
422
|
};
|
|
423
|
+
partGlossaryId: {
|
|
424
|
+
canFilter: boolean;
|
|
425
|
+
canSort: boolean;
|
|
426
|
+
};
|
|
427
|
+
colourway: {
|
|
428
|
+
canFilter: boolean;
|
|
429
|
+
canSort: boolean;
|
|
430
|
+
};
|
|
333
431
|
isPromoted: {
|
|
334
432
|
canFilter: boolean;
|
|
335
433
|
canSort: boolean;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { normalizeError } from "../../../errors/normalize.js";
|
|
2
|
-
import { increment, serverTimestamp } from "../../../contracts/field-ops.js";
|
|
2
|
+
import { arrayUnion, increment, serverTimestamp } from "../../../contracts/field-ops.js";
|
|
3
3
|
// 🛑 `../../../errors` re-exports the REAL ValidationError (errors/validation-error),
|
|
4
4
|
// which is the one `mapToHttpError` instanceof-checks to produce a 400. The
|
|
5
5
|
// unrelated same-named class under `_internal/shared/errors` falls through every
|
|
@@ -24,7 +24,7 @@ import { planSearchTxt, beginSearchWindow, emptySearchResult, } from "../../../u
|
|
|
24
24
|
// One definition of "which fields feed searchTxt", shared with the seed
|
|
25
25
|
// wrapper. Three copies existed and two were missing the same three fields.
|
|
26
26
|
import { buildProductSearchTxt } from "../../../utils/search-txt-builders.js";
|
|
27
|
-
import { PRODUCT_COLLECTION, ProductStatusValues } from "../schemas/index.js";
|
|
27
|
+
import { PRODUCT_COLLECTION, ProductStatusValues, toProductImages } from "../schemas/index.js";
|
|
28
28
|
// Collection NAMES only — deliberately not `categoriesRepository` or
|
|
29
29
|
// `taxCodesRepository`. See deriveTaxonomy()'s header for why these are raw
|
|
30
30
|
// reads: no repository in this codebase imports another feature's, because of
|
|
@@ -45,6 +45,12 @@ import { MAX_FEATURES_PER_PRODUCT } from "../schemas/product-features.js";
|
|
|
45
45
|
// The ONE `{{placeholder}}` implementation, promoted out of the FAQ schema in
|
|
46
46
|
// B3. A second regex here would be Root Cause #75's shape.
|
|
47
47
|
import { interpolate } from "../../../_internal/shared/templating/placeholders.js";
|
|
48
|
+
/*
|
|
49
|
+
* From their DEFINING modules, not a barrel — a repository reaching the wide
|
|
50
|
+
* entry is how the Turbopack client-bundle trap gets re-opened (Root Cause #18).
|
|
51
|
+
*/
|
|
52
|
+
import { ALL_LISTING_TYPES } from "../../../_internal/shared/listing-types/feature-flags.js";
|
|
53
|
+
import { pluginFor } from "../../../_internal/shared/listing-types/_registry.js";
|
|
48
54
|
import { PRODUCT_FIELDS } from "../../../constants/field-names.js";
|
|
49
55
|
/**
|
|
50
56
|
* Canonical listing-kind tokens used in `listingType`. Note: the public Sieve
|
|
@@ -73,6 +79,21 @@ const LISTING_KIND_ALIAS_MAP = {
|
|
|
73
79
|
live: LISTING_TYPE_VALUES.LIVE,
|
|
74
80
|
art: LISTING_TYPE_VALUES.ART,
|
|
75
81
|
stickers: LISTING_TYPE_VALUES.STICKERS,
|
|
82
|
+
/*
|
|
83
|
+
* 🛑 Root Cause #58 lives here, and it is the single most expensive line to
|
|
84
|
+
* omit in this whole change. The alias returns `""` for an unrecognised
|
|
85
|
+
* token and `expandFilterAliases` then DROPS the empty clause — so a missing
|
|
86
|
+
* entry does not error, it deletes the `listingType` filter from the query.
|
|
87
|
+
* `art` and `stickers` were added to the union, the registry and the
|
|
88
|
+
* capability map and not to this map, and every art/sticker browse page
|
|
89
|
+
* silently returned the entire catalogue until someone reported it.
|
|
90
|
+
*
|
|
91
|
+
* `Record<ProductListingKind, string>` makes it compile-checked against THAT
|
|
92
|
+
* union — but `ProductListingKind` is itself hand-written above and is not
|
|
93
|
+
* checked against `ListingType`, which is why this is still on the unchecked
|
|
94
|
+
* list.
|
|
95
|
+
*/
|
|
96
|
+
spares: LISTING_TYPE_VALUES.SPARES,
|
|
76
97
|
// Legacy → canonical.
|
|
77
98
|
product: LISTING_TYPE_VALUES.STANDARD,
|
|
78
99
|
preorder: LISTING_TYPE_VALUES.PRE_ORDER,
|
|
@@ -232,6 +253,17 @@ export class ProductRepository extends BaseRepository {
|
|
|
232
253
|
if (!doc.categorySlugs || doc.categorySlugs.length === 0) {
|
|
233
254
|
doc.categorySlugs = doc.category ? [doc.category] : [];
|
|
234
255
|
}
|
|
256
|
+
/*
|
|
257
|
+
* A6 — widen a legacy `images: string[]` document to `ProductImage[]`.
|
|
258
|
+
*
|
|
259
|
+
* 🛑 This is HALF the normalisation and must stay paired with the write
|
|
260
|
+
* half in `create`/`update` below. Read-only widening is Root Cause #101
|
|
261
|
+
* exactly: the field would be the object form on every object anyone
|
|
262
|
+
* inspects and still a bare string array in Firestore, so the next write
|
|
263
|
+
* of an untouched document would persist whichever shape the caller
|
|
264
|
+
* happened to hold.
|
|
265
|
+
*/
|
|
266
|
+
doc.images = toProductImages(doc.images);
|
|
235
267
|
return doc;
|
|
236
268
|
}
|
|
237
269
|
/**
|
|
@@ -833,6 +865,13 @@ export class ProductRepository extends BaseRepository {
|
|
|
833
865
|
...data,
|
|
834
866
|
...derived,
|
|
835
867
|
...stock,
|
|
868
|
+
/*
|
|
869
|
+
* A6 — only when the write NAMES `images`. An unconditional
|
|
870
|
+
* `toProductImages(data.images)` would write `[]` over a real gallery on
|
|
871
|
+
* every stock decrement and status flip, because `data` does not carry
|
|
872
|
+
* the field at all on those paths.
|
|
873
|
+
*/
|
|
874
|
+
...(data.images !== undefined ? { images: toProductImages(data.images) } : {}),
|
|
836
875
|
searchTxt: buildProductSearchTxt(merged),
|
|
837
876
|
});
|
|
838
877
|
this.cacheSet(updated);
|
|
@@ -907,7 +946,17 @@ export class ProductRepository extends BaseRepository {
|
|
|
907
946
|
// all. A seed row already carrying the correct chain derives to the same
|
|
908
947
|
// value, so this is a no-op for every existing fixture.
|
|
909
948
|
const derived = await this.deriveTaxonomy(input, true);
|
|
910
|
-
|
|
949
|
+
/*
|
|
950
|
+
* A6 — the WRITE half of the `images` normalisation (the read half is in
|
|
951
|
+
* `mapDoc`). Every create path funnels through here, so a caller handing
|
|
952
|
+
* us `string[]` — a seed row, an older client, the shipment-link route —
|
|
953
|
+
* stores the object form rather than leaving the collection mixed.
|
|
954
|
+
*/
|
|
955
|
+
const withTaxonomy = {
|
|
956
|
+
...input,
|
|
957
|
+
...derived,
|
|
958
|
+
...(input.images !== undefined ? { images: toProductImages(input.images) } : {}),
|
|
959
|
+
};
|
|
911
960
|
const productData = {
|
|
912
961
|
...withTaxonomy,
|
|
913
962
|
barcodeId,
|
|
@@ -1053,17 +1102,89 @@ export class ProductRepository extends BaseRepository {
|
|
|
1053
1102
|
.get();
|
|
1054
1103
|
return snapshot.docs.map((doc) => this.mapDoc(doc));
|
|
1055
1104
|
}
|
|
1056
|
-
|
|
1057
|
-
|
|
1105
|
+
/**
|
|
1106
|
+
* Every member of a group, UNFILTERED — drafts, and every listing type.
|
|
1107
|
+
*
|
|
1108
|
+
* 🛑 This is the scope `dissolveGroup` needs and the ONLY one that may be
|
|
1109
|
+
* unfiltered. A filtered dissolve leaves the excluded members carrying a
|
|
1110
|
+
* `groupId` pointing at a group that no longer exists, which nothing ever
|
|
1111
|
+
* clears — and that orphan is invisible until someone wonders why a product
|
|
1112
|
+
* claims to be in a group with no other members.
|
|
1113
|
+
*/
|
|
1114
|
+
async findAllByGroupId(groupId) {
|
|
1058
1115
|
const snapshot = await this.db
|
|
1059
1116
|
.collection(this.collection)
|
|
1060
1117
|
.where("groupId", "==", groupId)
|
|
1061
|
-
.where(PRODUCT_FIELDS.LISTING_TYPE, "in", [
|
|
1062
|
-
LISTING_TYPE_VALUES.STANDARD,
|
|
1063
|
-
LISTING_TYPE_VALUES.PRE_ORDER,
|
|
1064
|
-
])
|
|
1065
1118
|
.get();
|
|
1066
|
-
|
|
1119
|
+
const byQuery = snapshot.docs.map((doc) => this.mapDoc(doc));
|
|
1120
|
+
return this.withParentSideFallback(groupId, byQuery);
|
|
1121
|
+
}
|
|
1122
|
+
/**
|
|
1123
|
+
* Group members a BUYER may see: eligible types, published only.
|
|
1124
|
+
*
|
|
1125
|
+
* 🛑 The type filter is DERIVED from `canJoinProductGroup`, not hard-coded.
|
|
1126
|
+
* It used to be a literal `["standard", "pre-order"]`, which silently dropped
|
|
1127
|
+
* every `stickers` / `art` member from the query — so `ShowGroupSection` saw
|
|
1128
|
+
* one member and returned null, and the picker was unreachable with nothing
|
|
1129
|
+
* erroring anywhere (Root Cause #58).
|
|
1130
|
+
*
|
|
1131
|
+
* 🛑 The `status` filter is NEW and is a leak fix, not a tightening. There
|
|
1132
|
+
* was no status filter at all, so DRAFT members were reaching the public
|
|
1133
|
+
* `/api/products/group/[groupId]` payload; widening the type list without
|
|
1134
|
+
* this would have widened that leak at the same time.
|
|
1135
|
+
*/
|
|
1136
|
+
async findGroupMembersForPublic(groupId) {
|
|
1137
|
+
const eligible = ALL_LISTING_TYPES.filter((t) => pluginFor(t).canJoinProductGroup);
|
|
1138
|
+
const snapshot = await this.db
|
|
1139
|
+
.collection(this.collection)
|
|
1140
|
+
.where("groupId", "==", groupId)
|
|
1141
|
+
// `in` caps at 30 values; this is 4 of 9 today and cannot approach it.
|
|
1142
|
+
.where(PRODUCT_FIELDS.LISTING_TYPE, "in", eligible)
|
|
1143
|
+
.where(PRODUCT_FIELDS.STATUS, "==", ProductStatusValues.PUBLISHED)
|
|
1144
|
+
.get();
|
|
1145
|
+
const byQuery = snapshot.docs.map((doc) => this.mapDoc(doc));
|
|
1146
|
+
const withFallback = await this.withParentSideFallback(groupId, byQuery);
|
|
1147
|
+
/*
|
|
1148
|
+
* 🛑 The fallback results are filtered TOO. Without this the fallback
|
|
1149
|
+
* becomes the hole the query just closed — a draft or an ineligible member
|
|
1150
|
+
* would reach the public payload through the parent's own list.
|
|
1151
|
+
*/
|
|
1152
|
+
const eligibleSet = new Set(eligible);
|
|
1153
|
+
return withFallback.filter((p) => eligibleSet.has(String(p.listingType ?? LISTING_TYPE_VALUES.STANDARD)) &&
|
|
1154
|
+
p.status === ProductStatusValues.PUBLISHED);
|
|
1155
|
+
}
|
|
1156
|
+
/**
|
|
1157
|
+
* Union the child-side query with the PARENT's own `groupChildSlugs`.
|
|
1158
|
+
*
|
|
1159
|
+
* Mirrors `resolveBundleMemberIds`, and exists for the same reason: Root
|
|
1160
|
+
* Cause #42 documented a parent carrying `groupChildSlugs` while its children
|
|
1161
|
+
* had no `groupId`, so a child-side query resolved only the parent and the
|
|
1162
|
+
* group rendered as one member. The repository's six mutating methods all
|
|
1163
|
+
* write both sides in one batch, so code cannot produce that state — but a
|
|
1164
|
+
* seeded fixture or a direct Firestore edit still can, and the READER was
|
|
1165
|
+
* never made resilient. `listBundleMembers` got this fix; this did not.
|
|
1166
|
+
*/
|
|
1167
|
+
async withParentSideFallback(groupId, byQuery) {
|
|
1168
|
+
const seen = new Set(byQuery.map((p) => p.id));
|
|
1169
|
+
/*
|
|
1170
|
+
* `startGroup` sets `groupId = parent.slug`, so the group id IS the
|
|
1171
|
+
* parent's slug and one lookup finds it. If that ever stops being true the
|
|
1172
|
+
* fallback simply finds nothing, which is the same behaviour as today.
|
|
1173
|
+
*/
|
|
1174
|
+
const parent = byQuery.find((p) => p.isGroupParent) ?? (await this.findById(groupId));
|
|
1175
|
+
const missing = (parent?.groupChildSlugs ?? []).filter((id) => !seen.has(id));
|
|
1176
|
+
if (missing.length === 0)
|
|
1177
|
+
return byQuery;
|
|
1178
|
+
const extra = await Promise.all(missing.map((id) => this.findById(id)));
|
|
1179
|
+
return [...byQuery, ...extra.filter((p) => p !== null)];
|
|
1180
|
+
}
|
|
1181
|
+
/**
|
|
1182
|
+
* @deprecated Ambiguous about scope — say which one you mean.
|
|
1183
|
+
* Kept so no caller silently changes behaviour; it resolves to the PUBLIC
|
|
1184
|
+
* scope, which is what every existing caller wanted.
|
|
1185
|
+
*/
|
|
1186
|
+
async findByGroupId(groupId) {
|
|
1187
|
+
return this.findGroupMembersForPublic(groupId);
|
|
1067
1188
|
}
|
|
1068
1189
|
async startGroup(productId, slug) {
|
|
1069
1190
|
await this.getCollection().doc(productId).update({
|
|
@@ -1076,7 +1197,10 @@ export class ProductRepository extends BaseRepository {
|
|
|
1076
1197
|
await this.getCollection().doc(productId).update({ groupTitle });
|
|
1077
1198
|
}
|
|
1078
1199
|
async dissolveGroup(groupId) {
|
|
1079
|
-
|
|
1200
|
+
// UNFILTERED on purpose — see findAllByGroupId. Dissolving must clear every
|
|
1201
|
+
// member's fields, including drafts and ineligible types, or they keep a
|
|
1202
|
+
// groupId pointing at nothing.
|
|
1203
|
+
const members = await this.findAllByGroupId(groupId);
|
|
1080
1204
|
const batch = this.db.batch();
|
|
1081
1205
|
const clearFields = {
|
|
1082
1206
|
groupId: null,
|
|
@@ -1090,15 +1214,62 @@ export class ProductRepository extends BaseRepository {
|
|
|
1090
1214
|
}
|
|
1091
1215
|
await batch.commit();
|
|
1092
1216
|
}
|
|
1217
|
+
/**
|
|
1218
|
+
* Link ONE child into a parent's group.
|
|
1219
|
+
*
|
|
1220
|
+
* 🛑 `arrayUnion`, not a spread of the in-memory `parent.groupChildSlugs`.
|
|
1221
|
+
*
|
|
1222
|
+
* The spread read a snapshot the CALLER was holding, so linking N children in
|
|
1223
|
+
* a loop produced exactly ONE entry: every iteration computed
|
|
1224
|
+
* `[...(stale ?? []), child]` from the same pre-loop document and the last
|
|
1225
|
+
* write won. Nothing errored — each child got its own `groupId` correctly, so
|
|
1226
|
+
* `findAllByGroupId` found them all while the parent's own
|
|
1227
|
+
* `groupChildSlugs` claimed one member, which is what the group picker's
|
|
1228
|
+
* fallback path and `GroupSettingsPanel` read.
|
|
1229
|
+
*
|
|
1230
|
+
* `arrayUnion` is also idempotent, so re-linking an existing child is a
|
|
1231
|
+
* no-op instead of a duplicate id.
|
|
1232
|
+
*
|
|
1233
|
+
* For a known set, prefer `linkChildrenToGroup` below — one batch instead of
|
|
1234
|
+
* N, which matters against Rule #6's round-trip budget when promoting a
|
|
1235
|
+
* 60-part sticker sheet.
|
|
1236
|
+
*/
|
|
1093
1237
|
async linkChildToGroup(parent, child) {
|
|
1238
|
+
await this.linkChildrenToGroup(parent, [child]);
|
|
1239
|
+
}
|
|
1240
|
+
/**
|
|
1241
|
+
* Link SEVERAL children into a parent's group in one batch.
|
|
1242
|
+
*
|
|
1243
|
+
* Built for `promoteStickerSheetToListings`, which links every unique part of
|
|
1244
|
+
* a sheet at once — N calls to `linkChildToGroup` would be N batch commits
|
|
1245
|
+
* for work that is one logical change.
|
|
1246
|
+
*/
|
|
1247
|
+
async linkChildrenToGroup(parent, children) {
|
|
1248
|
+
if (children.length === 0)
|
|
1249
|
+
return;
|
|
1094
1250
|
const batch = this.db.batch();
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1251
|
+
for (const child of children) {
|
|
1252
|
+
batch.update(this.getCollection().doc(child.id), {
|
|
1253
|
+
groupId: parent.groupId,
|
|
1254
|
+
groupParentSlug: parent.slug ?? parent.id,
|
|
1255
|
+
groupTitle: parent.groupTitle ?? null,
|
|
1256
|
+
});
|
|
1257
|
+
}
|
|
1100
1258
|
batch.update(this.getCollection().doc(parent.id), {
|
|
1101
|
-
|
|
1259
|
+
/*
|
|
1260
|
+
* Ids, matching `unlinkChildFromGroup`'s filter, which removes by id OR
|
|
1261
|
+
* slug. These are pure slugs for products (`id === slug`), so the two
|
|
1262
|
+
* agree — but the field name says "slugs" and the stored values are ids,
|
|
1263
|
+
* which is worth knowing before writing a reader that assumes either.
|
|
1264
|
+
*/
|
|
1265
|
+
/*
|
|
1266
|
+
* 🛑 `arrayUnion` from the CONTRACTS layer, not `firebase-admin`
|
|
1267
|
+
* directly. Two sibling repositories import `FieldValue` from
|
|
1268
|
+
* firebase-admin and this one deliberately does not — its own imports go
|
|
1269
|
+
* through `contracts/field-ops`, which is what keeps the admin SDK out of
|
|
1270
|
+
* this module's static import graph (Root Cause #24/#6).
|
|
1271
|
+
*/
|
|
1272
|
+
groupChildSlugs: arrayUnion(...children.map((c) => c.id)),
|
|
1102
1273
|
});
|
|
1103
1274
|
await batch.commit();
|
|
1104
1275
|
}
|
|
@@ -1185,6 +1356,39 @@ export class ProductRepository extends BaseRepository {
|
|
|
1185
1356
|
await super.delete(id);
|
|
1186
1357
|
await this.syncCategoryCounts(prior, null);
|
|
1187
1358
|
}
|
|
1359
|
+
/**
|
|
1360
|
+
* Every live listing OF one glossary part, across all stores.
|
|
1361
|
+
*
|
|
1362
|
+
* 🛑 NOT filtered by `listingType`, deliberately. Both `spares` and the
|
|
1363
|
+
* 972 generated per-leaf catalogue rows (which are `standard`) carry
|
|
1364
|
+
* `partGlossaryId`, and a buyer on `/parts/{slot}/{code}` wants any listing
|
|
1365
|
+
* of that part — not only the ones that happen to be filed under the newer
|
|
1366
|
+
* type. Narrowing to `spares` would hide most of the real inventory.
|
|
1367
|
+
*
|
|
1368
|
+
* 🛑 `orderBy` is `createdAt`, which EVERY document carries
|
|
1369
|
+
* (`BaseRepository.create` writes it). Ordering by an optional field silently
|
|
1370
|
+
* excludes every row lacking it — Root Cause #100, where `itemsSold` on zero
|
|
1371
|
+
* of 95 products made a store`s whole review endpoint return nothing.
|
|
1372
|
+
*
|
|
1373
|
+
* Availability is filtered IN MEMORY rather than in the query: "available" is
|
|
1374
|
+
* negation-shaped and no query expresses it, which is the asymmetry
|
|
1375
|
+
* `baseAvailable` exists for. The window is bounded, so the cost is a few
|
|
1376
|
+
* rows read and discarded.
|
|
1377
|
+
*/
|
|
1378
|
+
async findByPartGlossaryId(partGlossaryId, limit = 12) {
|
|
1379
|
+
if (!partGlossaryId)
|
|
1380
|
+
return [];
|
|
1381
|
+
const snap = await this.getCollection()
|
|
1382
|
+
.where(PRODUCT_FIELDS.PART_GLOSSARY_ID, "==", partGlossaryId)
|
|
1383
|
+
.where(PRODUCT_FIELDS.STATUS, "==", PRODUCT_FIELDS.STATUS_VALUES.PUBLISHED)
|
|
1384
|
+
.orderBy(PRODUCT_FIELDS.CREATED_AT, "desc")
|
|
1385
|
+
// Over-fetch, because the availability filter below removes rows. Taking
|
|
1386
|
+
// exactly `limit` here would return fewer than `limit` buyable listings
|
|
1387
|
+
// whenever any of them were sold.
|
|
1388
|
+
.limit(Math.min(60, limit * 3))
|
|
1389
|
+
.get();
|
|
1390
|
+
return snap.docs.map((d) => this.mapDoc(d)).slice(0, limit);
|
|
1391
|
+
}
|
|
1188
1392
|
/**
|
|
1189
1393
|
* Shared scope construction for `list()` and `countBy*()` — both must narrow
|
|
1190
1394
|
* by exactly the same store/status/category/search predicates or a tab's
|
|
@@ -1481,6 +1685,20 @@ ProductRepository.SIEVE_FIELDS = {
|
|
|
1481
1685
|
// admin and seller product lists.
|
|
1482
1686
|
featured: { canFilter: true, canSort: true },
|
|
1483
1687
|
listingType: { canFilter: true, canSort: false },
|
|
1688
|
+
/*
|
|
1689
|
+
* 🛑 Filterable so a `partGlossaryId` filter is not SILENTLY DROPPED.
|
|
1690
|
+
* `sieve.ts` sets `throwExceptions: false`, so a field absent from this map
|
|
1691
|
+
* does not raise — `findField` returns undefined and the clause vanishes,
|
|
1692
|
+
* which on a page scoped to one part means the ENTIRE catalogue comes back
|
|
1693
|
+
* (Root Cause #62). Both of these are real `ProductDocument` fields, so
|
|
1694
|
+
* neither is the orphan half of that pattern.
|
|
1695
|
+
*
|
|
1696
|
+
* `canSort: false` on both: they are identity, not an ordering anyone wants
|
|
1697
|
+
* — and a sort option whose field is `canSort: false` is dropped just as
|
|
1698
|
+
* silently (Root Cause #63).
|
|
1699
|
+
*/
|
|
1700
|
+
partGlossaryId: { canFilter: true, canSort: false },
|
|
1701
|
+
colourway: { canFilter: true, canSort: false },
|
|
1484
1702
|
isPromoted: { canFilter: true, canSort: true },
|
|
1485
1703
|
isOnSale: { canFilter: true, canSort: false },
|
|
1486
1704
|
price: { canFilter: true, canSort: true },
|