@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
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* Products Firestore Document Types & Constants
|
|
3
3
|
*/
|
|
4
4
|
import { type GenerateProductIdInput, type GenerateAuctionIdInput, type GeneratePreOrderIdInput } from "../../../utils/id-generators";
|
|
5
|
-
import type { ProductStatus, ListingType } from "../types";
|
|
5
|
+
import type { ProductStatus, ListingType, ProductImage } from "../types";
|
|
6
|
+
export type { ProductImage };
|
|
6
7
|
import type { BaseDocument } from "../../../_internal/shared/types/base-document";
|
|
7
8
|
import type { LotteryConfig } from "../../lottery/types";
|
|
8
9
|
export interface ProductVideoField {
|
|
@@ -12,6 +13,31 @@ export interface ProductVideoField {
|
|
|
12
13
|
trimStart?: number;
|
|
13
14
|
trimEnd?: number;
|
|
14
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* Whatever a document, a request body or a seed row happens to hold, as
|
|
18
|
+
* `ProductImage[]`.
|
|
19
|
+
*
|
|
20
|
+
* 🛑 This is called on the WRITE path (`ProductRepository.create`/`update`)
|
|
21
|
+
* **and** on the read path (`mapDoc`), and both are required. Root Cause #101
|
|
22
|
+
* is exactly this shape: `mapDoc` backfilled `categorySlugs` on read while the
|
|
23
|
+
* write schema stripped it, so the field was present on every object anyone
|
|
24
|
+
* inspected and absent from the stored document — invisible to every query,
|
|
25
|
+
* and latent until a human created a listing.
|
|
26
|
+
*
|
|
27
|
+
* Accepts `string`, `ProductImage`, and a mixed array of both, because a
|
|
28
|
+
* partially-migrated collection genuinely contains all three.
|
|
29
|
+
*/
|
|
30
|
+
export declare function toProductImages(input: unknown): ProductImage[];
|
|
31
|
+
/**
|
|
32
|
+
* Just the URLs, in order — for the many call sites that only ever needed a
|
|
33
|
+
* `string[]` (a lightbox's source list, an OG image pick, a media-abort
|
|
34
|
+
* cleanup list).
|
|
35
|
+
*
|
|
36
|
+
* This is a PROJECTION, computed on demand, not a stored mirror — so it cannot
|
|
37
|
+
* drift from `images` the way a denormalised second field would (Root Cause
|
|
38
|
+
* #42). Takes `unknown` so it also works on a legacy `string[]`.
|
|
39
|
+
*/
|
|
40
|
+
export declare function productImageUrls(input: unknown): string[];
|
|
15
41
|
export interface ProductSpecification {
|
|
16
42
|
name: string;
|
|
17
43
|
value: string;
|
|
@@ -256,7 +282,14 @@ export interface ProductDocument extends BaseDocument {
|
|
|
256
282
|
stockQuantity: number;
|
|
257
283
|
availableQuantity: number;
|
|
258
284
|
mainImage: string;
|
|
259
|
-
|
|
285
|
+
/**
|
|
286
|
+
* 🛑 `ProductImage[]`, not `string[]` (changed 2026-10-20, plan A6).
|
|
287
|
+
*
|
|
288
|
+
* Normalised by `toProductImages()` on BOTH the write path and `mapDoc`, so
|
|
289
|
+
* a legacy `string[]` document widens losslessly and a new write stores the
|
|
290
|
+
* object form. Call sites that only want URLs use `productImageUrls()`.
|
|
291
|
+
*/
|
|
292
|
+
images: ProductImage[];
|
|
260
293
|
video?: ProductVideoField;
|
|
261
294
|
status: ProductStatus;
|
|
262
295
|
storeId: string;
|
|
@@ -449,6 +482,52 @@ export interface ProductDocument extends BaseDocument {
|
|
|
449
482
|
isPromoted?: boolean;
|
|
450
483
|
isOnSale?: boolean;
|
|
451
484
|
isSold?: boolean;
|
|
485
|
+
/**
|
|
486
|
+
* This listing is PRINTED OR BUILT WHEN ORDERED, so stock does not gate it.
|
|
487
|
+
*
|
|
488
|
+
* A per-LISTING flag, deliberately not implied by `listingType`. A seller can
|
|
489
|
+
* hold a finite run of pre-cut sticker sheets and also offer a
|
|
490
|
+
* print-on-demand sheet, and the two must coexist on the same type — which a
|
|
491
|
+
* type-level rule cannot express. (`showsStockQuantity` on the plugin is the
|
|
492
|
+
* type-level field and must NOT be repurposed for this.)
|
|
493
|
+
*
|
|
494
|
+
* 🛑 It bypasses the stock CHECK and never the DECREMENT. `availableQuantity`
|
|
495
|
+
* going negative on a made-to-order row IS the production queue — how many
|
|
496
|
+
* are owed — and `stock-restore.ts` requires the restore to mirror the
|
|
497
|
+
* decrement exactly, so skipping the decrement would make a cancelled order
|
|
498
|
+
* restore stock that was never taken.
|
|
499
|
+
*
|
|
500
|
+
* 🛑 Not snapshotted onto `CartLineMember`. A snapshot goes stale the moment
|
|
501
|
+
* the flag is flipped, and every check site already holds the product
|
|
502
|
+
* document — so it is derived, never copied (Root Cause #42).
|
|
503
|
+
*
|
|
504
|
+
* 🛑 CHECKED, not assumed: nothing auto-sets `isSold` when quantity reaches
|
|
505
|
+
* zero, so a made-to-order listing does NOT destroy itself on its first
|
|
506
|
+
* sale. `buildStockUpdatePayload` writes `availableQuantity` and the rule's
|
|
507
|
+
* `stockDecrementExtras` and nothing else; the only two writers of
|
|
508
|
+
* `isSold: true` in the whole tree are auction settlement and the auction
|
|
509
|
+
* buyout, both of which are selling a unique lot and are correct to do it.
|
|
510
|
+
* If a future "mark sold out automatically" feature is added, it must skip
|
|
511
|
+
* made-to-order rows or every one of them dies on its first order.
|
|
512
|
+
*/
|
|
513
|
+
madeToOrder?: boolean;
|
|
514
|
+
/**
|
|
515
|
+
* The `stickerSheets` document this listing was promoted from.
|
|
516
|
+
*
|
|
517
|
+
* Set on BOTH the parent (the whole sheet) and every child (one cut), so the
|
|
518
|
+
* relationship is answerable from either end without a query — "which sheet
|
|
519
|
+
* is this sticker off" and "has this sheet already been promoted" are both
|
|
520
|
+
* questions the admin UI asks.
|
|
521
|
+
*/
|
|
522
|
+
sourceStickerSheetId?: string;
|
|
523
|
+
/**
|
|
524
|
+
* Which cut of that sheet, by `SheetPart.ordinal`.
|
|
525
|
+
*
|
|
526
|
+
* 🛑 The ordinal, never an array index — `parts[]` is not guaranteed dense or
|
|
527
|
+
* ordered and a re-promote rewrites it, so an index would silently point at a
|
|
528
|
+
* different sticker. Absent on the parent, which is the whole sheet.
|
|
529
|
+
*/
|
|
530
|
+
sourceStickerPartOrdinal?: number;
|
|
452
531
|
/** When true, an EMI order for this product ships as soon as it's confirmed instead of waiting for every installment to be paid. Default false. */
|
|
453
532
|
allowShipBeforeEmiComplete?: boolean;
|
|
454
533
|
/**
|
|
@@ -514,6 +593,20 @@ export interface ProductDocument extends BaseDocument {
|
|
|
514
593
|
groupParentSlug?: string;
|
|
515
594
|
groupChildSlugs?: string[];
|
|
516
595
|
groupTitle?: string;
|
|
596
|
+
groupMinMembers?: number;
|
|
597
|
+
groupMaxMembers?: number;
|
|
598
|
+
groupMaxUnitsPerMember?: number;
|
|
599
|
+
groupRequiredMemberIds?: string[];
|
|
600
|
+
/**
|
|
601
|
+
* Percentage off a self-assembled selection's subtotal, 0–100.
|
|
602
|
+
*
|
|
603
|
+
* 🛑 Applied in exactly ONE place: the repository's private
|
|
604
|
+
* `deriveLinePrice`, which runs on every `groupMembers` write and feeds
|
|
605
|
+
* `expandCartLineToOrderRows`' per-member GST proration through
|
|
606
|
+
* `allocateAcrossMembers`. A second application anywhere makes the cart total
|
|
607
|
+
* disagree with the invoice (Root Cause #75).
|
|
608
|
+
*/
|
|
609
|
+
groupDiscountPercent?: number;
|
|
517
610
|
/**
|
|
518
611
|
* W1-45 — optional icon shown as an overlay badge on every card whose
|
|
519
612
|
* product belongs to this group. Set once on the group parent; cards
|
|
@@ -683,8 +776,8 @@ export declare const ProductStatusValues: {
|
|
|
683
776
|
export declare const PRODUCT_COLLECTION: "products";
|
|
684
777
|
export declare const PRODUCT_INDEXED_FIELDS: readonly ["storeId", "status", "category", "categoryIds", "featured", "listingType", "isPromoted", "isOnSale", "isSold", "searchTxt", "createdAt", "barcodeId", "physicalLocation.zone"];
|
|
685
778
|
export declare const DEFAULT_PRODUCT_DATA: Partial<ProductDocument>;
|
|
686
|
-
export declare const PRODUCT_PUBLIC_FIELDS: readonly ["allowOffers", "minOfferPercent", "id", "title", "description", "category", "categoryIds", "categoryIdNames", "subcategory", "brand", "price", "originalPrice", "currency", "stockQuantity", "availableQuantity", "images", "status", "storeName", "featured", "tags", "specifications", "features", "shippingInfo", "returnPolicy", "finalSale", "isLot", "lotSize", "unitLabel", "partGlossaryId", "colourway", "mouldVariant", "assembledFrom", "views7d", "views30d", "unitLabel", "partGlossaryId", "colourway", "mouldVariant", "assembledFrom", "listingType", "auctionEndDate", "startingBid", "currentBid", "bidCount", "reservePrice", "buyNowPrice", "minBidIncrement", "autoExtendable", "auctionExtensionMinutes", "auctionShippingPaidBy", "bidsHaveStarted", "grading", "card", "classified", "digitalCode", "liveItem", "catalogProductId", "preOrderDeliveryDate", "preOrderDepositPercent", "preOrderDepositAmount", "preOrderMaxQuantity", "preOrderCurrentCount", "preOrderProductionStatus", "preOrderCancellable", "condition", "insurance", "insuranceCost", "shippingPaidBy", "isPromoted", "isOnSale", "isSold", "slug", "seoTitle", "seoDescription", "seoKeywords", "viewCount", "customFields", "customSections", "sublistingCategoryId", "groupId", "isGroupParent", "groupParentSlug", "groupChildSlugs", "groupTitle", "createdAt"];
|
|
687
|
-
export declare const PRODUCT_UPDATABLE_FIELDS: readonly ["title", "description", "category", "categoryIds", "subcategory", "brand", "price", "originalPrice", "stockQuantity", "images", "status", "tags", "specifications", "features", "shippingInfo", "returnPolicy", "finalSale", "isLot", "lotSize", "pickupAddressId", "condition", "insurance", "shippingPaidBy", "autoExtendable", "auctionExtensionMinutes", "auctionShippingPaidBy", "reservePrice", "buyNowPrice", "minBidIncrement", "bidsHaveStarted", "grading", "card", "classified", "digitalCode", "liveItem", "catalogProductId", "listingType", "preOrderDeliveryDate", "preOrderDepositPercent", "preOrderDepositAmount", "preOrderMaxQuantity", "preOrderProductionStatus", "preOrderCancellable", "isOnSale", "isSold", "allowShipBeforeEmiComplete", "gstRate", "hsnCode", "authenticity", "printMeta", "seoTitle", "seoDescription", "seoKeywords", "customFields", "customSections", "sublistingCategoryId", "groupId", "isGroupParent", "groupParentSlug", "groupChildSlugs", "groupTitle", "barcodeId"];
|
|
779
|
+
export declare const PRODUCT_PUBLIC_FIELDS: readonly ["allowOffers", "minOfferPercent", "id", "title", "description", "category", "categoryIds", "categoryIdNames", "subcategory", "brand", "price", "originalPrice", "currency", "stockQuantity", "availableQuantity", "images", "status", "storeName", "featured", "tags", "specifications", "features", "shippingInfo", "returnPolicy", "finalSale", "isLot", "lotSize", "unitLabel", "partGlossaryId", "colourway", "mouldVariant", "assembledFrom", "views7d", "views30d", "unitLabel", "partGlossaryId", "colourway", "mouldVariant", "assembledFrom", "listingType", "auctionEndDate", "startingBid", "currentBid", "bidCount", "reservePrice", "buyNowPrice", "minBidIncrement", "autoExtendable", "auctionExtensionMinutes", "auctionShippingPaidBy", "bidsHaveStarted", "grading", "card", "classified", "digitalCode", "liveItem", "catalogProductId", "preOrderDeliveryDate", "preOrderDepositPercent", "preOrderDepositAmount", "preOrderMaxQuantity", "preOrderCurrentCount", "preOrderProductionStatus", "preOrderCancellable", "condition", "insurance", "insuranceCost", "shippingPaidBy", "isPromoted", "isOnSale", "isSold", "madeToOrder", "sourceStickerSheetId", "sourceStickerPartOrdinal", "slug", "seoTitle", "seoDescription", "seoKeywords", "viewCount", "customFields", "customSections", "sublistingCategoryId", "groupId", "isGroupParent", "groupParentSlug", "groupChildSlugs", "groupTitle", "groupMinMembers", "groupMaxMembers", "groupMaxUnitsPerMember", "groupRequiredMemberIds", "groupDiscountPercent", "createdAt"];
|
|
780
|
+
export declare const PRODUCT_UPDATABLE_FIELDS: readonly ["title", "description", "category", "categoryIds", "subcategory", "brand", "price", "originalPrice", "stockQuantity", "images", "status", "tags", "specifications", "features", "shippingInfo", "returnPolicy", "finalSale", "isLot", "lotSize", "pickupAddressId", "condition", "insurance", "shippingPaidBy", "autoExtendable", "auctionExtensionMinutes", "auctionShippingPaidBy", "reservePrice", "buyNowPrice", "minBidIncrement", "bidsHaveStarted", "grading", "card", "classified", "digitalCode", "liveItem", "catalogProductId", "listingType", "preOrderDeliveryDate", "preOrderDepositPercent", "preOrderDepositAmount", "preOrderMaxQuantity", "preOrderProductionStatus", "preOrderCancellable", "isOnSale", "isSold", "madeToOrder", "sourceStickerSheetId", "sourceStickerPartOrdinal", "allowShipBeforeEmiComplete", "gstRate", "hsnCode", "authenticity", "printMeta", "seoTitle", "seoDescription", "seoKeywords", "customFields", "customSections", "sublistingCategoryId", "groupId", "isGroupParent", "groupParentSlug", "groupChildSlugs", "groupTitle", "groupMinMembers", "groupMaxMembers", "groupMaxUnitsPerMember", "groupRequiredMemberIds", "groupDiscountPercent", "barcodeId"];
|
|
688
781
|
export type ProductCreateInput = Omit<ProductDocument, "id" | "createdAt" | "updatedAt" | "availableQuantity" | "bidCount" | "currentBid" | "auctionOriginalEndDate">;
|
|
689
782
|
export type ProductUpdateInput = Partial<Pick<ProductDocument, (typeof PRODUCT_UPDATABLE_FIELDS)[number]>>;
|
|
690
783
|
export type ProductAdminUpdateInput = Partial<Omit<ProductDocument, "id" | "createdAt">>;
|
|
@@ -2,6 +2,74 @@
|
|
|
2
2
|
* Products Firestore Document Types & Constants
|
|
3
3
|
*/
|
|
4
4
|
import { generateProductId, generateAuctionId, generatePreOrderId, } from "../../../utils/id-generators.js";
|
|
5
|
+
/**
|
|
6
|
+
* Whatever a document, a request body or a seed row happens to hold, as
|
|
7
|
+
* `ProductImage[]`.
|
|
8
|
+
*
|
|
9
|
+
* 🛑 This is called on the WRITE path (`ProductRepository.create`/`update`)
|
|
10
|
+
* **and** on the read path (`mapDoc`), and both are required. Root Cause #101
|
|
11
|
+
* is exactly this shape: `mapDoc` backfilled `categorySlugs` on read while the
|
|
12
|
+
* write schema stripped it, so the field was present on every object anyone
|
|
13
|
+
* inspected and absent from the stored document — invisible to every query,
|
|
14
|
+
* and latent until a human created a listing.
|
|
15
|
+
*
|
|
16
|
+
* Accepts `string`, `ProductImage`, and a mixed array of both, because a
|
|
17
|
+
* partially-migrated collection genuinely contains all three.
|
|
18
|
+
*/
|
|
19
|
+
export function toProductImages(input) {
|
|
20
|
+
if (!Array.isArray(input))
|
|
21
|
+
return [];
|
|
22
|
+
const out = [];
|
|
23
|
+
for (const entry of input) {
|
|
24
|
+
if (typeof entry === "string") {
|
|
25
|
+
if (entry)
|
|
26
|
+
out.push({ url: entry });
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (entry && typeof entry === "object") {
|
|
30
|
+
/*
|
|
31
|
+
* `Partial<ProductImage>`, NOT `{ url?: unknown }` — one narrowing
|
|
32
|
+
* instead of two casts, and no `unknown` in a local binding
|
|
33
|
+
* (`audit-unknown-leakage` flagged the earlier spelling, correctly: an
|
|
34
|
+
* `unknown` here defeats the checking on every member read below it).
|
|
35
|
+
* Every member is still validated before use, so the assertion is
|
|
36
|
+
* narrowing a shape we are about to prove rather than asserting a fact.
|
|
37
|
+
*/
|
|
38
|
+
const e = entry;
|
|
39
|
+
if (typeof e.url !== "string" || !e.url)
|
|
40
|
+
continue;
|
|
41
|
+
/*
|
|
42
|
+
* 🛑 EVERY optional member is carried through, not just the two A6
|
|
43
|
+
* added. This runs on the WRITE path, so a member it forgets is a
|
|
44
|
+
* member it DESTROYS on the next save — and `thumbnailUrl`/`order`
|
|
45
|
+
* predate this function. Adding a field to `ProductImage` means adding
|
|
46
|
+
* it here; the alternative is a normaliser that quietly truncates the
|
|
47
|
+
* type it claims to normalise.
|
|
48
|
+
*/
|
|
49
|
+
out.push({
|
|
50
|
+
url: e.url,
|
|
51
|
+
...(e.alt ? { alt: e.alt } : {}),
|
|
52
|
+
...(e.thumbnailUrl ? { thumbnailUrl: e.thumbnailUrl } : {}),
|
|
53
|
+
...(typeof e.order === "number" ? { order: e.order } : {}),
|
|
54
|
+
...(typeof e.width === "number" ? { width: e.width } : {}),
|
|
55
|
+
...(typeof e.height === "number" ? { height: e.height } : {}),
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return out;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Just the URLs, in order — for the many call sites that only ever needed a
|
|
63
|
+
* `string[]` (a lightbox's source list, an OG image pick, a media-abort
|
|
64
|
+
* cleanup list).
|
|
65
|
+
*
|
|
66
|
+
* This is a PROJECTION, computed on demand, not a stored mirror — so it cannot
|
|
67
|
+
* drift from `images` the way a denormalised second field would (Root Cause
|
|
68
|
+
* #42). Takes `unknown` so it also works on a legacy `string[]`.
|
|
69
|
+
*/
|
|
70
|
+
export function productImageUrls(input) {
|
|
71
|
+
return toProductImages(input).map((i) => i.url);
|
|
72
|
+
}
|
|
5
73
|
export const MAX_CUSTOM_FIELDS = 50;
|
|
6
74
|
export const MAX_CUSTOM_SECTIONS = 3;
|
|
7
75
|
/** Runtime-accessible product status values — use instead of bare string literals. */
|
|
@@ -171,6 +239,15 @@ export const PRODUCT_PUBLIC_FIELDS = [
|
|
|
171
239
|
"isPromoted",
|
|
172
240
|
"isOnSale",
|
|
173
241
|
"isSold",
|
|
242
|
+
/* Public because the PDP has to SAY "made to order, printed when you order"
|
|
243
|
+
* instead of a stock line, and the group picker has to uncap that member's
|
|
244
|
+
* stepper. Both are client-side reads. */
|
|
245
|
+
"madeToOrder",
|
|
246
|
+
/* Public: provenance a buyer should see — "this is one cut off that sheet" —
|
|
247
|
+
* and neither value is a path or a private id. The sheet document itself is
|
|
248
|
+
* admin-only; this is just its id, same shape as `sourceCatalogueItemId`. */
|
|
249
|
+
"sourceStickerSheetId",
|
|
250
|
+
"sourceStickerPartOrdinal",
|
|
174
251
|
"slug",
|
|
175
252
|
"seoTitle",
|
|
176
253
|
"seoDescription",
|
|
@@ -184,6 +261,14 @@ export const PRODUCT_PUBLIC_FIELDS = [
|
|
|
184
261
|
"groupParentSlug",
|
|
185
262
|
"groupChildSlugs",
|
|
186
263
|
"groupTitle",
|
|
264
|
+
/* The group's own rules. Public because the PICKER enforces them too — a
|
|
265
|
+
* server-only rule reads as a broken button — and none of them is sensitive:
|
|
266
|
+
* they are merchandising limits a buyer is about to be told anyway. */
|
|
267
|
+
"groupMinMembers",
|
|
268
|
+
"groupMaxMembers",
|
|
269
|
+
"groupMaxUnitsPerMember",
|
|
270
|
+
"groupRequiredMemberIds",
|
|
271
|
+
"groupDiscountPercent",
|
|
187
272
|
"createdAt",
|
|
188
273
|
];
|
|
189
274
|
export const PRODUCT_UPDATABLE_FIELDS = [
|
|
@@ -250,6 +335,17 @@ export const PRODUCT_UPDATABLE_FIELDS = [
|
|
|
250
335
|
"preOrderCancellable",
|
|
251
336
|
"isOnSale",
|
|
252
337
|
"isSold",
|
|
338
|
+
/* 🛑 Listed here or an update STRIPS it — the write schema has no
|
|
339
|
+
* `.passthrough()`, so a field absent from this list is dropped before it
|
|
340
|
+
* reaches Firestore while the write still returns 200 (Root Cause #101). The
|
|
341
|
+
* seller checkbox would appear to save and change nothing. */
|
|
342
|
+
"madeToOrder",
|
|
343
|
+
/* Updatable so `promoteStickerSheetToListings` can set them through the same
|
|
344
|
+
* `create`/`update` path every other write uses — and so a re-promote can
|
|
345
|
+
* correct them — rather than needing a raw collection write that would also
|
|
346
|
+
* skip `deriveTaxonomy`. */
|
|
347
|
+
"sourceStickerSheetId",
|
|
348
|
+
"sourceStickerPartOrdinal",
|
|
253
349
|
"allowShipBeforeEmiComplete",
|
|
254
350
|
/*
|
|
255
351
|
* B3 — these three were MISSING, so the fields existed on the document
|
|
@@ -272,6 +368,14 @@ export const PRODUCT_UPDATABLE_FIELDS = [
|
|
|
272
368
|
"groupParentSlug",
|
|
273
369
|
"groupChildSlugs",
|
|
274
370
|
"groupTitle",
|
|
371
|
+
/* The group's own rules. Public because the PICKER enforces them too — a
|
|
372
|
+
* server-only rule reads as a broken button — and none of them is sensitive:
|
|
373
|
+
* they are merchandising limits a buyer is about to be told anyway. */
|
|
374
|
+
"groupMinMembers",
|
|
375
|
+
"groupMaxMembers",
|
|
376
|
+
"groupMaxUnitsPerMember",
|
|
377
|
+
"groupRequiredMemberIds",
|
|
378
|
+
"groupDiscountPercent",
|
|
275
379
|
"barcodeId",
|
|
276
380
|
];
|
|
277
381
|
// ── SB-UNI-N 2026-05-15 — Digital-code pool subcollection ───────────────────
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export * from "./firestore";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
+
import type { ListingType } from "../types/index";
|
|
3
4
|
export declare const productImageSchema: z.ZodObject<{
|
|
4
5
|
url: z.ZodString;
|
|
5
6
|
thumbnailUrl: z.ZodOptional<z.ZodString>;
|
|
@@ -46,19 +47,6 @@ export declare const printMetaSchema: z.ZodObject<{
|
|
|
46
47
|
finish?: string | undefined;
|
|
47
48
|
editionSize?: number | undefined;
|
|
48
49
|
}>;
|
|
49
|
-
/**
|
|
50
|
-
* Base Zod schema for a product item.
|
|
51
|
-
* Apps can extend this to add their own fields:
|
|
52
|
-
*
|
|
53
|
-
* @example
|
|
54
|
-
* import { productItemSchema } from "@mohasinac/feat-products";
|
|
55
|
-
*
|
|
56
|
-
* const myProductSchema = productItemSchema.extend({
|
|
57
|
-
* brand: z.string(),
|
|
58
|
-
* auctionEndDate: z.string().optional(),
|
|
59
|
-
* });
|
|
60
|
-
* type MyProduct = z.infer<typeof myProductSchema>;
|
|
61
|
-
*/
|
|
62
50
|
export declare const productItemSchema: z.ZodObject<{
|
|
63
51
|
id: z.ZodString;
|
|
64
52
|
title: z.ZodString;
|
|
@@ -130,7 +118,8 @@ export declare const productItemSchema: z.ZodObject<{
|
|
|
130
118
|
sellerAvatar: z.ZodOptional<z.ZodString>;
|
|
131
119
|
status: z.ZodEnum<["draft", "published", "in_review", "archived"]>;
|
|
132
120
|
condition: z.ZodOptional<z.ZodEnum<["new", "like_new", "good", "fair", "poor", "used", "refurbished", "broken", "graded"]>>;
|
|
133
|
-
|
|
121
|
+
madeToOrder: z.ZodOptional<z.ZodBoolean>;
|
|
122
|
+
listingType: z.ZodOptional<z.ZodEnum<[ListingType, ...ListingType[]]>>;
|
|
134
123
|
maxPerUser: z.ZodOptional<z.ZodNumber>;
|
|
135
124
|
partOfBundleIds: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
136
125
|
partOfBundleTitles: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
@@ -337,7 +326,7 @@ export declare const productItemSchema: z.ZodObject<{
|
|
|
337
326
|
} | undefined;
|
|
338
327
|
createdAt?: string | undefined;
|
|
339
328
|
updatedAt?: string | undefined;
|
|
340
|
-
listingType?:
|
|
329
|
+
listingType?: ListingType | undefined;
|
|
341
330
|
category?: string | undefined;
|
|
342
331
|
categorySlug?: string | undefined;
|
|
343
332
|
storeId?: string | undefined;
|
|
@@ -359,6 +348,7 @@ export declare const productItemSchema: z.ZodObject<{
|
|
|
359
348
|
description?: string | undefined;
|
|
360
349
|
subcategory?: string | undefined;
|
|
361
350
|
availableQuantity?: number | undefined;
|
|
351
|
+
madeToOrder?: boolean | undefined;
|
|
362
352
|
mainImage?: string | undefined;
|
|
363
353
|
images?: string[] | undefined;
|
|
364
354
|
featured?: boolean | undefined;
|
|
@@ -476,7 +466,7 @@ export declare const productItemSchema: z.ZodObject<{
|
|
|
476
466
|
} | undefined;
|
|
477
467
|
createdAt?: string | undefined;
|
|
478
468
|
updatedAt?: string | undefined;
|
|
479
|
-
listingType?:
|
|
469
|
+
listingType?: ListingType | undefined;
|
|
480
470
|
category?: string | undefined;
|
|
481
471
|
categorySlug?: string | undefined;
|
|
482
472
|
storeId?: string | undefined;
|
|
@@ -498,6 +488,7 @@ export declare const productItemSchema: z.ZodObject<{
|
|
|
498
488
|
description?: string | undefined;
|
|
499
489
|
subcategory?: string | undefined;
|
|
500
490
|
availableQuantity?: number | undefined;
|
|
491
|
+
madeToOrder?: boolean | undefined;
|
|
501
492
|
mainImage?: string | undefined;
|
|
502
493
|
images?: string[] | undefined;
|
|
503
494
|
featured?: boolean | undefined;
|
|
@@ -612,7 +603,7 @@ export declare const productListParamsSchema: z.ZodObject<{
|
|
|
612
603
|
minPrice: z.ZodOptional<z.ZodNumber>;
|
|
613
604
|
maxPrice: z.ZodOptional<z.ZodNumber>;
|
|
614
605
|
inStock: z.ZodOptional<z.ZodBoolean>;
|
|
615
|
-
listingType: z.ZodOptional<z.ZodEnum<[
|
|
606
|
+
listingType: z.ZodOptional<z.ZodEnum<[ListingType, ...ListingType[]]>>;
|
|
616
607
|
storeId: z.ZodOptional<z.ZodString>;
|
|
617
608
|
sort: z.ZodOptional<z.ZodString>;
|
|
618
609
|
page: z.ZodOptional<z.ZodNumber>;
|
|
@@ -623,7 +614,7 @@ export declare const productListParamsSchema: z.ZodObject<{
|
|
|
623
614
|
status?: "published" | "draft" | "in_review" | "archived" | undefined;
|
|
624
615
|
page?: number | undefined;
|
|
625
616
|
q?: string | undefined;
|
|
626
|
-
listingType?:
|
|
617
|
+
listingType?: ListingType | undefined;
|
|
627
618
|
category?: string | undefined;
|
|
628
619
|
storeId?: string | undefined;
|
|
629
620
|
featured?: boolean | undefined;
|
|
@@ -637,7 +628,7 @@ export declare const productListParamsSchema: z.ZodObject<{
|
|
|
637
628
|
status?: "published" | "draft" | "in_review" | "archived" | undefined;
|
|
638
629
|
page?: number | undefined;
|
|
639
630
|
q?: string | undefined;
|
|
640
|
-
listingType?:
|
|
631
|
+
listingType?: ListingType | undefined;
|
|
641
632
|
category?: string | undefined;
|
|
642
633
|
storeId?: string | undefined;
|
|
643
634
|
featured?: boolean | undefined;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export * from "./firestore.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { mediaFieldSchema } from "../../media/types/index.js";
|
|
4
|
+
import { ALL_LISTING_TYPES } from "../../../_internal/shared/listing-types/feature-flags.js";
|
|
4
5
|
import { PRIZE_DRAW_DURATION_DAYS_MIN, PRIZE_DRAW_DURATION_DAYS_MAX, } from "../../../_internal/shared/checkout/rules/_limits.js";
|
|
5
6
|
// --- Sub-schemas --------------------------------------------------------------
|
|
6
7
|
export const productImageSchema = z.object({
|
|
@@ -35,6 +36,25 @@ export const printMetaSchema = z.object({
|
|
|
35
36
|
* });
|
|
36
37
|
* type MyProduct = z.infer<typeof myProductSchema>;
|
|
37
38
|
*/
|
|
39
|
+
/**
|
|
40
|
+
* The `ListingType` union as a Zod enum, DERIVED.
|
|
41
|
+
*
|
|
42
|
+
* 🛑 Two hand-written copies of this lived in this file and BOTH had drifted to
|
|
43
|
+
* seven of nine — `art` and `stickers` were missing from the item schema and
|
|
44
|
+
* from the query schema. Consequence: `productItemSchema.parse()` stripped
|
|
45
|
+
* `listingType` off every art and sticker row (the field is `.optional()`, so a
|
|
46
|
+
* value outside the enum is a parse FAILURE, and a caller using `safeParse` or
|
|
47
|
+
* `.partial()` loses it silently), and `?listingType=art` on any route
|
|
48
|
+
* validating against the query schema was rejected as an invalid value.
|
|
49
|
+
*
|
|
50
|
+
* `z.enum` needs a non-empty literal tuple, hence the cast — `ALL_LISTING_TYPES`
|
|
51
|
+
* is derived from `Record<ListingType, true>`, so its CONTENTS are
|
|
52
|
+
* compile-checked against the union even though its length is not.
|
|
53
|
+
*
|
|
54
|
+
* Registered in `audit-listing-type-tab-coverage`'s STAYS_DERIVED so nobody
|
|
55
|
+
* re-expands it into a literal for readability.
|
|
56
|
+
*/
|
|
57
|
+
const LISTING_TYPE_ENUM = z.enum(ALL_LISTING_TYPES);
|
|
38
58
|
export const productItemSchema = z.object({
|
|
39
59
|
id: z.string(),
|
|
40
60
|
title: z.string(),
|
|
@@ -78,9 +98,8 @@ export const productItemSchema = z.object({
|
|
|
78
98
|
])
|
|
79
99
|
.optional(),
|
|
80
100
|
// SB1-G Phase 4 — canonical discriminator (legacy `isAuction`/`isPreOrder` booleans removed).
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
.optional(),
|
|
101
|
+
madeToOrder: z.boolean().optional(),
|
|
102
|
+
listingType: LISTING_TYPE_ENUM.optional(),
|
|
84
103
|
// SB1-C — prize-draw + bundle fields. All optional.
|
|
85
104
|
maxPerUser: z.number().int().positive().optional(),
|
|
86
105
|
partOfBundleIds: z.array(z.string()).optional(),
|
|
@@ -229,9 +248,7 @@ export const productListParamsSchema = z.object({
|
|
|
229
248
|
minPrice: z.coerce.number().optional(),
|
|
230
249
|
maxPrice: z.coerce.number().optional(),
|
|
231
250
|
inStock: z.coerce.boolean().optional(),
|
|
232
|
-
listingType:
|
|
233
|
-
.enum(["standard", "auction", "pre-order", "prize-draw", "classified", "digital-code", "live"])
|
|
234
|
-
.optional(),
|
|
251
|
+
listingType: LISTING_TYPE_ENUM.optional(),
|
|
235
252
|
storeId: z.string().optional(),
|
|
236
253
|
sort: z.string().optional(),
|
|
237
254
|
page: z.coerce.number().optional(),
|
|
@@ -11,12 +11,34 @@ export type ProductCondition = "new" | "like_new" | "good" | "fair" | "poor" | "
|
|
|
11
11
|
* accepts legacy alias tokens (`preorder` → `pre-order`, `product` → `standard`)
|
|
12
12
|
* but stored values are always one of these five.
|
|
13
13
|
*/
|
|
14
|
-
export type ListingType = "standard" | "auction" | "pre-order" | "prize-draw" | "classified" | "digital-code" | "live" | "art" | "stickers";
|
|
14
|
+
export type ListingType = "standard" | "auction" | "pre-order" | "prize-draw" | "classified" | "digital-code" | "live" | "art" | "stickers" | "spares";
|
|
15
|
+
/**
|
|
16
|
+
* One gallery image — THE single definition, used by both the Firestore
|
|
17
|
+
* document and the client shape.
|
|
18
|
+
*
|
|
19
|
+
* 🛑 This interface already existed here, barrel-exported as public API, with
|
|
20
|
+
* ZERO consumers anywhere and `Product.images` declared `string[]` beside it:
|
|
21
|
+
* a speculative type describing a shape nothing produced or read. Plan A6
|
|
22
|
+
* resurrected it rather than declaring a second `ProductImage` in
|
|
23
|
+
* `schemas/firestore.ts`, which is what the first draft did — two same-named
|
|
24
|
+
* types in one package is a defect even when both look plausible, and the
|
|
25
|
+
* barrel's choice between them is invisible at every call site (Root Cause
|
|
26
|
+
* #36). `schemas/firestore.ts` imports this one.
|
|
27
|
+
*
|
|
28
|
+
* `alt` is the point: measured across the fifteen-site competitor crawl,
|
|
29
|
+
* per-image alt coverage is ~0% (0/151, 0/306, 0/317; tcgrepublic's 37/63 is
|
|
30
|
+
* the best anyone manages). `width`/`height` let `next/image` reserve space so
|
|
31
|
+
* a gallery does not reflow as each image lands — optional because we do not
|
|
32
|
+
* always know them at write time, and a guessed aspect ratio is worse than
|
|
33
|
+
* none.
|
|
34
|
+
*/
|
|
15
35
|
export interface ProductImage {
|
|
16
36
|
url: string;
|
|
17
37
|
thumbnailUrl?: string;
|
|
18
38
|
alt?: string;
|
|
19
39
|
order?: number;
|
|
40
|
+
width?: number;
|
|
41
|
+
height?: number;
|
|
20
42
|
}
|
|
21
43
|
export interface ProductSeo {
|
|
22
44
|
title?: string;
|
|
@@ -78,6 +100,28 @@ export interface ProductItem {
|
|
|
78
100
|
sublistingIcon?: string;
|
|
79
101
|
isOnSale?: boolean;
|
|
80
102
|
isSold?: boolean;
|
|
103
|
+
/**
|
|
104
|
+
* Printed or built when ordered, so stock does not gate it. Per-LISTING, not
|
|
105
|
+
* implied by `listingType` — a seller can hold a finite run of pre-cut sheets
|
|
106
|
+
* and also offer a print-on-demand one.
|
|
107
|
+
*
|
|
108
|
+
* On the client this drives three things: the PDP says "made to order"
|
|
109
|
+
* instead of a stock line, the group picker uncaps that member's stepper, and
|
|
110
|
+
* `baseAvailable` keeps the row in the default scope at quantity 0.
|
|
111
|
+
*/
|
|
112
|
+
madeToOrder?: boolean;
|
|
113
|
+
/**
|
|
114
|
+
* Self-assembled-selection rules, present only on a group PARENT. See
|
|
115
|
+
* `_internal/shared/features/products/group-rules.ts` for the shape, the
|
|
116
|
+
* resolver and the single enforcement point — these fields exist here only
|
|
117
|
+
* so the admin editor (`GroupSettingsPanel`'s `groupRules` prop) can seed its
|
|
118
|
+
* form from the product it is already showing, with no second fetch.
|
|
119
|
+
*/
|
|
120
|
+
groupMinMembers?: number;
|
|
121
|
+
groupMaxMembers?: number;
|
|
122
|
+
groupMaxUnitsPerMember?: number;
|
|
123
|
+
groupRequiredMemberIds?: string[];
|
|
124
|
+
groupDiscountPercent?: number;
|
|
81
125
|
inStock?: boolean;
|
|
82
126
|
stockCount?: number;
|
|
83
127
|
rating?: number;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The thumbnail for one product-group member, from whichever shape the caller
|
|
3
|
+
* supplied.
|
|
4
|
+
*
|
|
5
|
+
* ── 🛑 WHY `image` IS TRIED FIRST ──────────────────────────────────────────
|
|
6
|
+
*
|
|
7
|
+
* `toPublicGroupMember` is an ALLOW-LIST projection: it emits the singular
|
|
8
|
+
* `image` (`images[0].url ?? mainImage`) and no `images[]` array at all. So a
|
|
9
|
+
* component reading only `images[0]` resolves `undefined` for every member
|
|
10
|
+
* served by the public endpoint and renders a placeholder tile.
|
|
11
|
+
*
|
|
12
|
+
* That was live on every product-group page — all seven BX-53 members showed
|
|
13
|
+
* a grey box while their images sat in Storage serving HTTP 200. Nothing
|
|
14
|
+
* errors, because an empty `src` is a legal `MediaImage`.
|
|
15
|
+
*
|
|
16
|
+
* The `images[0]` fallback stays for callers that pass a raw document rather
|
|
17
|
+
* than the projection.
|
|
18
|
+
*
|
|
19
|
+
* ── 🛑 WHY ITS OWN MODULE, AND NOT EITHER COMPONENT ────────────────────────
|
|
20
|
+
*
|
|
21
|
+
* `ShowGroupSection` renders `GroupMemberPicker`, so the import edge between
|
|
22
|
+
* them already runs one way. Putting the shared helper in either file and
|
|
23
|
+
* importing it from the other closes a CYCLE — which is exactly what
|
|
24
|
+
* `audit-import-cycles` caught when the first version of this fix exported it
|
|
25
|
+
* from `ShowGroupSection`.
|
|
26
|
+
*
|
|
27
|
+
* A helper used by two components belongs beside neither (Root Cause #18:
|
|
28
|
+
* import from the module that DEFINES the symbol). One definition, two
|
|
29
|
+
* importers, no edge between them — and one rule reachable by copy-paste is
|
|
30
|
+
* one that gets copied wrong (Root Cause #75), which is how the two spellings
|
|
31
|
+
* diverged in the first place.
|
|
32
|
+
*/
|
|
33
|
+
export interface GroupMemberImageSource {
|
|
34
|
+
/** The public projection's single thumbnail. */
|
|
35
|
+
image?: string;
|
|
36
|
+
/** A raw document's gallery. Only read when `image` is absent. */
|
|
37
|
+
images?: string[];
|
|
38
|
+
}
|
|
39
|
+
export declare function resolveGroupMemberImage(m: GroupMemberImageSource): string;
|
|
@@ -15,6 +15,10 @@ import { isListingType } from "./listing-type.js";
|
|
|
15
15
|
*/
|
|
16
16
|
export const LISTING_BADGE_VARIANT = {
|
|
17
17
|
standard: "default",
|
|
18
|
+
// Shares `primary` with art/stickers: all three are "a thing we make or
|
|
19
|
+
// salvage ourselves" rather than a mechanism (auction) or a promise
|
|
20
|
+
// (pre-order), and the badge LABEL is what distinguishes them.
|
|
21
|
+
spares: "primary",
|
|
18
22
|
auction: "warning",
|
|
19
23
|
"pre-order": "secondary",
|
|
20
24
|
"prize-draw": "primary",
|
|
@@ -29,7 +29,7 @@ export function ReviewCard({ review, context = "general", className = "" }) {
|
|
|
29
29
|
const showProductLink = context !== "listing" && !!productHref;
|
|
30
30
|
const showProfileLink = !!profileHref;
|
|
31
31
|
const hasFooter = showStoreLink || showProductLink || showProfileLink;
|
|
32
|
-
return (_jsxs(Stack, { padding: "5", shadow: "hover-md", className: `group h-full border border-neutral-200 transition ${className}`, rounded: "xl", surface: "default", children: [_jsxs(Link, { href: reviewHref, className: "flex flex-col flex-1 min-h-0", children: [_jsxs(Row, { align: "start", gap: "3", children: [review.userAvatar ? (_jsx(Div, { className: "relative h-9 w-9 flex-shrink-0", rounded: "full", overflow: "hidden", children: _jsx(MediaImage, { src: review.userAvatar, alt: displayName, size: "avatar" }) })) : (_jsx(Row, { textWeight: "medium", textSize: "sm", className: "h-9 w-9 flex-shrink-0 bg-neutral-200 text-[var(--appkit-color-text-muted)]", align: "center", justify: "center", rounded: "full", children: initials })), _jsxs(Div, { className: "flex-1 min-w-0", children: [_jsxs(Row, { wrap: true, gap: "sm", children: [_jsx(Span, { color: "inverse", weight: "medium", className: "text-[var(--appkit-color-text)] dark:", children: displayName }), review.verified && (_jsx(Span, { color: "success", surface: "success-surface", size: "xs", weight: "medium", rounded: "full", padding: "pill-xs", children: "Verified" })), date && (_jsx(Span, { size: "xs", color: "muted", children: date }))] }), _jsx(Div, { className: "mt-1", children: _jsx(StarRating, { value: review.rating, size: "sm", readOnly: true }) })] })] }), review.title && (_jsx(Heading, { color: "inverse", level: 4, className: "mt-3 text-[var(--appkit-color-text)] dark:", weight: "semibold", children: review.title })), review.comment && (_jsx(RichText, { html: normalizeRichTextHtml(review.comment), proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "mt-2 text-[length:var(--appkit-text-sm)] leading-relaxed text-[var(--appkit-color-text-muted)] text-[var(--appkit-color-text-muted)]" })), review.images && review.images.length > 0 && (_jsx(Row, { wrap: true, gap: "sm", className: "mt-3", children: review.images.map((img, i) => (_jsx(Div, { className: "relative h-16 w-16 flex-shrink-0 border border-neutral-100", rounded: "lg", overflow: "hidden", children: _jsx(MediaImage, { src: img, alt: `Review image ${i + 1}`, size: "thumbnail" }) }, i))) })), (review.helpfulCount ?? 0) > 0 && (_jsxs(Text, { className: "mt-3", color: "faint", size: "xs", children: [review.helpfulCount, " found this helpful"] }))] }), hasFooter && (_jsxs(Stack, { gap: "xs", className: "mt-3 border-t border-neutral-100", padding: "t-sm", children: [showStoreLink && (_jsxs(Link, { href: String(ROUTES.PUBLIC.STORE_DETAIL(review.storeSlug)), className: "flex items-center gap-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-xs)] font-medium text-primary hover:underline", children: [_jsx(Span, { "aria-hidden": "true", children: "\uD83C\uDFEA" }), _jsx(Span, { truncate: true, children: review.storeName })] })), showProductLink && (_jsxs(Link, { href: productHref, className: "flex items-center gap-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-xs)] font-medium text-[var(--appkit-color-text-muted)]
|
|
32
|
+
return (_jsxs(Stack, { padding: "5", shadow: "hover-md", className: `group h-full border border-neutral-200 transition ${className}`, rounded: "xl", surface: "default", children: [_jsxs(Link, { href: reviewHref, className: "flex flex-col flex-1 min-h-0", children: [_jsxs(Row, { align: "start", gap: "3", children: [review.userAvatar ? (_jsx(Div, { className: "relative h-9 w-9 flex-shrink-0", rounded: "full", overflow: "hidden", children: _jsx(MediaImage, { src: review.userAvatar, alt: displayName, size: "avatar" }) })) : (_jsx(Row, { textWeight: "medium", textSize: "sm", className: "h-9 w-9 flex-shrink-0 bg-neutral-200 text-[var(--appkit-color-text-muted)]", align: "center", justify: "center", rounded: "full", children: initials })), _jsxs(Div, { className: "flex-1 min-w-0", children: [_jsxs(Row, { wrap: true, gap: "sm", children: [_jsx(Span, { color: "inverse", weight: "medium", className: "text-[var(--appkit-color-text)] dark:", children: displayName }), review.verified && (_jsx(Span, { color: "success", surface: "success-surface", size: "xs", weight: "medium", rounded: "full", padding: "pill-xs", children: "Verified" })), date && (_jsx(Span, { size: "xs", color: "muted", children: date }))] }), _jsx(Div, { className: "mt-1", children: _jsx(StarRating, { value: review.rating, size: "sm", readOnly: true }) })] })] }), review.title && (_jsx(Heading, { color: "inverse", level: 4, className: "mt-3 text-[var(--appkit-color-text)] dark:", weight: "semibold", children: review.title })), review.comment && (_jsx(RichText, { html: normalizeRichTextHtml(review.comment), proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "mt-2 text-[length:var(--appkit-text-sm)] leading-relaxed text-[var(--appkit-color-text-muted)] text-[var(--appkit-color-text-muted)]" })), review.images && review.images.length > 0 && (_jsx(Row, { wrap: true, gap: "sm", className: "mt-3", children: review.images.map((img, i) => (_jsx(Div, { className: "relative h-16 w-16 flex-shrink-0 border border-neutral-100", rounded: "lg", overflow: "hidden", children: _jsx(MediaImage, { src: img, alt: `Review image ${i + 1}`, size: "thumbnail" }) }, i))) })), (review.helpfulCount ?? 0) > 0 && (_jsxs(Text, { className: "mt-3", color: "faint", size: "xs", children: [review.helpfulCount, " found this helpful"] }))] }), hasFooter && (_jsxs(Stack, { gap: "xs", className: "mt-3 border-t border-neutral-100", padding: "t-sm", children: [showStoreLink && (_jsxs(Link, { href: String(ROUTES.PUBLIC.STORE_DETAIL(review.storeSlug)), className: "flex items-center gap-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-xs)] font-medium text-primary hover:underline", children: [_jsx(Span, { "aria-hidden": "true", children: "\uD83C\uDFEA" }), _jsx(Span, { truncate: true, children: review.storeName })] })), showProductLink && (_jsxs(Link, { href: productHref, className: "flex items-center gap-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-xs)] font-medium text-[var(--appkit-color-text-muted)] hover:text-[var(--appkit-color-text)]", children: [_jsx(Span, { "aria-hidden": "true", children: "\uD83D\uDCE6" }), _jsx(Span, { truncate: true, children: review.productTitle ?? "View Product" }), _jsx(Span, { "aria-hidden": "true", className: "ml-auto text-primary group-hover:translate-x-0.5 transition-transform", children: "\u2192" })] })), showProfileLink && (_jsxs(Link, { href: profileHref, className: "flex items-center gap-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-xs)] font-medium text-[var(--appkit-color-text-muted)] hover:text-[var(--appkit-color-text)]", children: [_jsx(Span, { "aria-hidden": "true", children: "\uD83D\uDC64" }), _jsx(Span, { truncate: true, children: displayName })] }))] }))] }));
|
|
33
33
|
}
|
|
34
34
|
export function ReviewsList({ reviews, context = "general", isLoading, totalPages = 1, currentPage = 1, onPageChange, emptyLabel = "No reviews yet", }) {
|
|
35
35
|
if (isLoading) {
|
|
@@ -51,7 +51,7 @@ export function ScamProfileView({ scammer, isAuthenticated, incidents = [], comm
|
|
|
51
51
|
const registryHref = String(ROUTES.PUBLIC.SCAMS);
|
|
52
52
|
const contestLoginHref = `${String(ROUTES.AUTH.LOGIN)}?redirect=${encodeURIComponent(`/scams/${scammer.id}`)}`;
|
|
53
53
|
const scamTypeDef = getScamType(scammer.scamType);
|
|
54
|
-
return (_jsxs(Main, { children: [_jsx(Div, { className: "border-b
|
|
54
|
+
return (_jsxs(Main, { children: [_jsx(Div, { className: "border-b", children: _jsx(Container, { size: "xl", padding: "y-sm", children: _jsx(Breadcrumb, { items: [
|
|
55
55
|
{ label: "Home", href: "/" },
|
|
56
56
|
{ label: "Scam Registry", href: registryHref },
|
|
57
57
|
{ label: scammer.displayNames[0] ?? "Profile" },
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
export interface EntityTypeaheadPick {
|
|
3
|
+
/** The canonical entity's title, as the index holds it. */
|
|
4
|
+
readonly title: string;
|
|
5
|
+
/** Its destination, which encodes the slug. */
|
|
6
|
+
readonly href: string;
|
|
7
|
+
/** The index type it came from. */
|
|
8
|
+
readonly typeKey: string;
|
|
9
|
+
/**
|
|
10
|
+
* 🛑 What the USER typed, verbatim. See the header — a consumer that
|
|
11
|
+
* analyses the resolved title instead of this is throwing away the signal
|
|
12
|
+
* it was built to detect.
|
|
13
|
+
*/
|
|
14
|
+
readonly rawQuery: string;
|
|
15
|
+
}
|
|
16
|
+
export interface EntityTypeaheadProps {
|
|
17
|
+
/** Index type keys to search. `["model"]`, `["category"]`, `["part"]`… */
|
|
18
|
+
types: readonly string[];
|
|
19
|
+
onPick: (pick: EntityTypeaheadPick) => void;
|
|
20
|
+
/** `siteSettings.search?.indexEnabled`. Read as `!== false`. */
|
|
21
|
+
indexEnabled?: boolean;
|
|
22
|
+
label: string;
|
|
23
|
+
placeholder?: string;
|
|
24
|
+
/** Mirrors the field the consumer owns, so the two cannot disagree. */
|
|
25
|
+
value?: string;
|
|
26
|
+
onChange?: (value: string) => void;
|
|
27
|
+
limit?: number;
|
|
28
|
+
/**
|
|
29
|
+
* Default `true`: picking a suggestion fills the field with the
|
|
30
|
+
* canonical title, as every ordinary typeahead does.
|
|
31
|
+
*
|
|
32
|
+
* 🛑 Set `false` when the consumer's DOWNSTREAM LOGIC treats a mismatch
|
|
33
|
+
* between what was typed and the canonical form as a signal rather than
|
|
34
|
+
* noise — `/tools/authenticity` is the one caller that needs this. Its
|
|
35
|
+
* engine fires a trademark-evasion warning on a FUZZY match, and that
|
|
36
|
+
* warning is exactly what filling the field would silence: the engine
|
|
37
|
+
* would receive "Storm Pegasus" and match it exactly, never learning the
|
|
38
|
+
* user typed "Pegasis" at all. With this false, a pick still calls
|
|
39
|
+
* `onPick` (with `rawQuery` intact) but leaves the field exactly as
|
|
40
|
+
* typed — discovery without consequence.
|
|
41
|
+
*/
|
|
42
|
+
fillOnPick?: boolean;
|
|
43
|
+
}
|
|
44
|
+
export declare function EntityTypeahead({ types, onPick, indexEnabled, label, placeholder, value, onChange, limit, fillOnPick, }: EntityTypeaheadProps): React.JSX.Element;
|