@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
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* WHY: The tracer ends at extracted geometry and a promoted run's full-size
|
|
3
|
+
* part PNGs. This is what makes that worth doing — the whole sheet becomes
|
|
4
|
+
* one listing, each unique cut becomes its own, and they are grouped so a
|
|
5
|
+
* buyer takes any mix ("the sheet, no; six of B; one of D").
|
|
6
|
+
* WHAT: `promoteStickerSheetToListings` — sheet → one parent product + N child
|
|
7
|
+
* products in one product group.
|
|
8
|
+
*
|
|
9
|
+
* 🛑 DISTINCT FROM `promoteRun`, and the names are close enough to matter.
|
|
10
|
+
* · `POST .../runs/{runId}/promote` → `promoteRun`: run → SHEET. Renders
|
|
11
|
+
* full-res parts and exports, sets `activeRunId`. Stays inside the tracer.
|
|
12
|
+
* · `POST .../promote-to-listings` → this: SHEET → MARKETPLACE. Creates
|
|
13
|
+
* products. Runs once per sheet, after the above.
|
|
14
|
+
* Calling this without the first is refused, because `SheetPart.assetPath` is
|
|
15
|
+
* only written by the `mode: "full"` shard path.
|
|
16
|
+
*
|
|
17
|
+
* 🛑 ZERO BYTES ARE COPIED for the watermarked display image. A `mediaAssets`
|
|
18
|
+
* row pointing at the EXISTING private part PNG is enough: the row is what
|
|
19
|
+
* mints `/media/{shortId}`, and the proxy watermarks on read because the row is
|
|
20
|
+
* written with `watermarkedAt` absent. Clean original for the cut file,
|
|
21
|
+
* watermarked copy for the marketplace, one object. Object count is metered
|
|
22
|
+
* separately from bytes, which is the whole reason the thumb atlas exists.
|
|
23
|
+
*
|
|
24
|
+
* EXPORTS: PromoteToListingsInput, PromoteToListingsResult,
|
|
25
|
+
* promoteStickerSheetToListings
|
|
26
|
+
*
|
|
27
|
+
* @tag domain:sticker-trace,products
|
|
28
|
+
* @tag layer:server
|
|
29
|
+
* @tag pattern:none
|
|
30
|
+
* @tag access:server
|
|
31
|
+
* @tag consumers:api/admin/sticker-sheets
|
|
32
|
+
* @tag sideEffects:firestore
|
|
33
|
+
*/
|
|
34
|
+
import { ValidationError } from "../../../../errors/validation-error.js";
|
|
35
|
+
import { serverLogger } from "../../../../monitoring/server-logger.js";
|
|
36
|
+
import { normalizeError } from "../../../../errors/normalize.js";
|
|
37
|
+
import { productRepository } from "../../../../repositories/index.js";
|
|
38
|
+
import { mediaAssetsRepository } from "../../../../features/media/repository/media-assets.repository.js";
|
|
39
|
+
import { stickerSheetsRepository } from "../../../../features/sticker-trace/repository/sticker-sheets.repository.js";
|
|
40
|
+
/** A part that becomes its own listing: unique, and actually rendered. */
|
|
41
|
+
function isListableUniquePart(part) {
|
|
42
|
+
/*
|
|
43
|
+
* `duplicateOf` is proposed by perceptual hash and CONFIRMED BY A HUMAN, so
|
|
44
|
+
* trusting it is safe by design — that confirmation step exists precisely so
|
|
45
|
+
* this decision can be mechanical. Two stickers that merely look alike at
|
|
46
|
+
* thumbnail size are common on these sheets.
|
|
47
|
+
*/
|
|
48
|
+
return part.duplicateOf === undefined;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Refuse loudly, before writing anything, if the sheet is not ready.
|
|
52
|
+
*
|
|
53
|
+
* 🛑 A half-promoted sheet is the "wrong data, no error" failure this whole
|
|
54
|
+
* plan exists to avoid: some parts listed, some silently missing, and nothing
|
|
55
|
+
* anywhere saying so. Every precondition is therefore checked UP FRONT and the
|
|
56
|
+
* whole promotion refused — not checked per part inside the loop, which would
|
|
57
|
+
* leave the first N written.
|
|
58
|
+
*/
|
|
59
|
+
function assertPromotable(sheet) {
|
|
60
|
+
if (!sheet.activeRunId) {
|
|
61
|
+
throw new ValidationError(`"${sheet.title}" has no accepted run yet. Accept a run first, then promote it to listings.`);
|
|
62
|
+
}
|
|
63
|
+
const parts = sheet.parts ?? [];
|
|
64
|
+
if (parts.length === 0) {
|
|
65
|
+
throw new ValidationError(`"${sheet.title}" has no parts. Trace it and accept a run first.`);
|
|
66
|
+
}
|
|
67
|
+
const unique = parts.filter(isListableUniquePart);
|
|
68
|
+
if (unique.length === 0) {
|
|
69
|
+
throw new ValidationError(`Every part on "${sheet.title}" is marked a duplicate of another, so there is nothing to list.`);
|
|
70
|
+
}
|
|
71
|
+
/*
|
|
72
|
+
* `assetPath` absent means the accepted run was never PROMOTED — the
|
|
73
|
+
* full-resolution PNGs do not exist. Naming the parts rather than saying
|
|
74
|
+
* "not ready": the admin's next action is different depending on whether it
|
|
75
|
+
* is all of them (promote the run) or a few (the shard that rendered those
|
|
76
|
+
* failed, so retry).
|
|
77
|
+
*/
|
|
78
|
+
const unrendered = unique.filter((p) => !p.assetPath);
|
|
79
|
+
if (unrendered.length > 0) {
|
|
80
|
+
throw new ValidationError(`${unrendered.length} of ${unique.length} parts on "${sheet.title}" have no full-resolution image yet` +
|
|
81
|
+
` (${unrendered.slice(0, 5).map((p) => `#${p.ordinal}`).join(", ")}${unrendered.length > 5 ? ", …" : ""}).` +
|
|
82
|
+
` Accept & render the run before promoting it to listings.`);
|
|
83
|
+
}
|
|
84
|
+
return unique;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Register the public, WATERMARKED display copy of one part.
|
|
88
|
+
*
|
|
89
|
+
* Reuses an existing row when the part already has one, so re-promoting a sheet
|
|
90
|
+
* does not mint a second slug for the same object and orphan the first.
|
|
91
|
+
*/
|
|
92
|
+
async function ensurePartMediaSlug(sheet, part, actorUid) {
|
|
93
|
+
if (part.mediaShortId)
|
|
94
|
+
return part.mediaShortId;
|
|
95
|
+
if (!part.assetPath)
|
|
96
|
+
return undefined;
|
|
97
|
+
try {
|
|
98
|
+
const asset = await mediaAssetsRepository.createAsset(
|
|
99
|
+
/*
|
|
100
|
+
* A content-derived slug, matching every other media slug in the app.
|
|
101
|
+
* `createAsset` appends a random suffix on collision, so two sheets with
|
|
102
|
+
* the same title cannot overwrite each other's parts.
|
|
103
|
+
*/
|
|
104
|
+
`sticker-part-${sheet.id}-${String(part.ordinal).padStart(3, "0")}`, {
|
|
105
|
+
/*
|
|
106
|
+
* 🛑 The SAME path as the clean original. This is the no-byte-copy
|
|
107
|
+
* mechanism, and it is only safe because of the two fields below.
|
|
108
|
+
*/
|
|
109
|
+
storagePath: part.assetPath,
|
|
110
|
+
uploadedBy: actorUid,
|
|
111
|
+
contentType: "image/png",
|
|
112
|
+
/*
|
|
113
|
+
* Not measured. The row's `size` is used by the media usage meter, not
|
|
114
|
+
* for serving, and reading the object's metadata here would cost a
|
|
115
|
+
* Storage round trip per part — 60 on a full sheet — for a number the
|
|
116
|
+
* sticker feature already reports from `measureSheetBytes`.
|
|
117
|
+
*/
|
|
118
|
+
size: 0,
|
|
119
|
+
isPublic: true,
|
|
120
|
+
status: "finalized",
|
|
121
|
+
contextType: "sticker-part",
|
|
122
|
+
/*
|
|
123
|
+
* 🛑 `watermarkedAt` is DELIBERATELY ABSENT, and it is the whole design.
|
|
124
|
+
* The proxy skips its sharp composite only when that field is set — so
|
|
125
|
+
* leaving it unset means `/media/{shortId}` watermarks this object on
|
|
126
|
+
* every read, while `assetPath` behind a signed URL stays clean. Set it
|
|
127
|
+
* here and the marketplace would serve the unwatermarked printable
|
|
128
|
+
* original, which is the one outcome this must never produce.
|
|
129
|
+
*/
|
|
130
|
+
});
|
|
131
|
+
return asset.id;
|
|
132
|
+
}
|
|
133
|
+
catch (err) {
|
|
134
|
+
const e = normalizeError(err);
|
|
135
|
+
/*
|
|
136
|
+
* Non-fatal. A part with no public slug still lists — it falls back to no
|
|
137
|
+
* image rather than blocking the whole promotion — and the admin can
|
|
138
|
+
* re-promote to fill it in. Failing the sheet over one media row would be
|
|
139
|
+
* the worse trade.
|
|
140
|
+
*/
|
|
141
|
+
serverLogger.warn("sticker-promote: media row failed for part", {
|
|
142
|
+
sheetId: sheet.id,
|
|
143
|
+
ordinal: part.ordinal,
|
|
144
|
+
error: e.message,
|
|
145
|
+
});
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
export async function promoteStickerSheetToListings(input) {
|
|
150
|
+
const sheet = await stickerSheetsRepository.findByIdOrFail(input.sheetId);
|
|
151
|
+
const unique = assertPromotable(sheet);
|
|
152
|
+
const allParts = sheet.parts ?? [];
|
|
153
|
+
/*
|
|
154
|
+
* Media rows first, before any product write.
|
|
155
|
+
*
|
|
156
|
+
* The images are what a listing is FOR, so resolving them up front means a
|
|
157
|
+
* product is never created pointing at nothing. Sequential rather than
|
|
158
|
+
* `Promise.all`: `createAsset` does a read-then-write collision check, and 60
|
|
159
|
+
* of those in parallel is 120 concurrent operations against Rule #6's budget
|
|
160
|
+
* for an admin action that is not latency-sensitive.
|
|
161
|
+
*/
|
|
162
|
+
const slugByOrdinal = new Map();
|
|
163
|
+
for (const part of unique) {
|
|
164
|
+
const slug = await ensurePartMediaSlug(sheet, part, input.actorUid);
|
|
165
|
+
if (slug)
|
|
166
|
+
slugByOrdinal.set(part.ordinal, slug);
|
|
167
|
+
}
|
|
168
|
+
const partImage = (part) => {
|
|
169
|
+
const slug = slugByOrdinal.get(part.ordinal);
|
|
170
|
+
return slug ? `/media/${slug}` : "";
|
|
171
|
+
};
|
|
172
|
+
/*
|
|
173
|
+
* The parent: the whole sheet.
|
|
174
|
+
*
|
|
175
|
+
* 🛑 `madeToOrder: true`. A sheet that has been cut into parts has no stock
|
|
176
|
+
* of its own — it is printed when ordered — so without the flag the parent is
|
|
177
|
+
* unbuyable at quantity 0 and vanishes from the default availability scope,
|
|
178
|
+
* taking the group's entry point with it.
|
|
179
|
+
*/
|
|
180
|
+
const parent = (await productRepository.create({
|
|
181
|
+
title: sheet.title,
|
|
182
|
+
description: `The complete ${sheet.title} sheet. Individual stickers from this sheet are listed separately — ` +
|
|
183
|
+
`pick any mix from the group below.`,
|
|
184
|
+
categorySlugs: [],
|
|
185
|
+
price: input.sheetPrice,
|
|
186
|
+
currency: "INR",
|
|
187
|
+
stockQuantity: 0,
|
|
188
|
+
madeToOrder: true,
|
|
189
|
+
mainImage: unique[0] ? partImage(unique[0]) : "",
|
|
190
|
+
images: unique.slice(0, 4).map(partImage).filter(Boolean),
|
|
191
|
+
status: "draft",
|
|
192
|
+
storeId: input.storeId,
|
|
193
|
+
featured: false,
|
|
194
|
+
tags: ["stickers"],
|
|
195
|
+
condition: "new",
|
|
196
|
+
listingType: "stickers",
|
|
197
|
+
groupTitle: sheet.title,
|
|
198
|
+
sourceStickerSheetId: sheet.id,
|
|
199
|
+
}));
|
|
200
|
+
/*
|
|
201
|
+
* `startGroup` sets `groupId = slug`, so the group id IS the parent's slug —
|
|
202
|
+
* which is why the parent must exist before any child can name it.
|
|
203
|
+
*/
|
|
204
|
+
const parentSlug = parent.slug ?? parent.id;
|
|
205
|
+
await productRepository.startGroup(parent.id, parentSlug);
|
|
206
|
+
const children = [];
|
|
207
|
+
for (const part of unique) {
|
|
208
|
+
const child = (await productRepository.create({
|
|
209
|
+
title: `${sheet.title} — ${part.name}`,
|
|
210
|
+
description: `A single kiss-cut sticker from the ${sheet.title} sheet.`,
|
|
211
|
+
categorySlugs: [],
|
|
212
|
+
price: input.partPrice,
|
|
213
|
+
currency: "INR",
|
|
214
|
+
stockQuantity: 0,
|
|
215
|
+
// Same reasoning as the parent: a cut sticker is printed on order.
|
|
216
|
+
madeToOrder: true,
|
|
217
|
+
mainImage: partImage(part),
|
|
218
|
+
images: [partImage(part)].filter(Boolean),
|
|
219
|
+
status: "draft",
|
|
220
|
+
storeId: input.storeId,
|
|
221
|
+
featured: false,
|
|
222
|
+
tags: ["stickers"],
|
|
223
|
+
condition: "new",
|
|
224
|
+
listingType: "stickers",
|
|
225
|
+
sourceStickerSheetId: sheet.id,
|
|
226
|
+
sourceStickerPartOrdinal: part.ordinal,
|
|
227
|
+
}));
|
|
228
|
+
children.push(child);
|
|
229
|
+
}
|
|
230
|
+
/*
|
|
231
|
+
* 🛑 ONE batch link, via `linkChildrenToGroup`. Calling `linkChildToGroup` in
|
|
232
|
+
* a loop was the bug this phase had to fix first: it spread the CALLER's
|
|
233
|
+
* in-memory `parent.groupChildSlugs`, so N children produced exactly one
|
|
234
|
+
* entry — every iteration computing `[...stale, child]` from the same
|
|
235
|
+
* pre-loop snapshot, last write winning. Each child still got its own
|
|
236
|
+
* `groupId`, so nothing errored and `findAllByGroupId` found them all while
|
|
237
|
+
* the parent claimed one member.
|
|
238
|
+
*/
|
|
239
|
+
await productRepository.linkChildrenToGroup({ ...parent, groupId: parentSlug }, children);
|
|
240
|
+
/*
|
|
241
|
+
* Write the slugs back onto the sheet so a re-promote reuses them.
|
|
242
|
+
*
|
|
243
|
+
* 🛑 Mapped over ALL parts, keyed on `ordinal`, never on array index — and
|
|
244
|
+
* `assertPromotable` filtered to unique parts, so a plain `.map()` over
|
|
245
|
+
* `unique` here would DROP every duplicate part from the sheet. The duplicate
|
|
246
|
+
* rows carry `duplicateOf` and real geometry and must survive.
|
|
247
|
+
*/
|
|
248
|
+
const updatedParts = allParts.map((p) => {
|
|
249
|
+
const slug = slugByOrdinal.get(p.ordinal);
|
|
250
|
+
return slug ? { ...p, mediaShortId: slug } : p;
|
|
251
|
+
});
|
|
252
|
+
await stickerSheetsRepository.update(sheet.id, { parts: updatedParts });
|
|
253
|
+
serverLogger.info("sticker-promote: sheet promoted to listings", {
|
|
254
|
+
sheetId: sheet.id,
|
|
255
|
+
parentId: parent.id,
|
|
256
|
+
children: children.length,
|
|
257
|
+
skippedDuplicates: allParts.length - unique.length,
|
|
258
|
+
});
|
|
259
|
+
return {
|
|
260
|
+
parentId: parent.id,
|
|
261
|
+
childIds: children.map((c) => c.id),
|
|
262
|
+
skippedDuplicates: allParts.length - unique.length,
|
|
263
|
+
};
|
|
264
|
+
}
|
|
@@ -88,7 +88,7 @@ export declare function attachStickerSheetScan(input: AttachScanInput): Promise<
|
|
|
88
88
|
* property `PRIVATE_STICKER_SHEET_FIELDS` declares and
|
|
89
89
|
* audit-public-projection-parity enforces.
|
|
90
90
|
*/
|
|
91
|
-
export declare const STICKER_ASSET_KINDS: readonly ["scan", "preview", "overlay", "geometry", "cutSvg", "thumbAtlas"];
|
|
91
|
+
export declare const STICKER_ASSET_KINDS: readonly ["scan", "preview", "overlay", "geometry", "cutSvg", "thumbAtlas", "part"];
|
|
92
92
|
export type StickerAssetKind = (typeof STICKER_ASSET_KINDS)[number];
|
|
93
93
|
/**
|
|
94
94
|
* Mint a short-lived read URL for one asset of one sheet or run.
|
|
@@ -105,6 +105,8 @@ export declare function resolveStickerAssetUrl(input: {
|
|
|
105
105
|
sheetId: string;
|
|
106
106
|
kind: StickerAssetKind;
|
|
107
107
|
runId?: string;
|
|
108
|
+
/** Required for `kind: "part"`. Matched against `SheetPart.ordinal`. */
|
|
109
|
+
ordinal?: number;
|
|
108
110
|
}): Promise<{
|
|
109
111
|
url: string;
|
|
110
112
|
expiresAt: Date;
|
|
@@ -176,6 +176,15 @@ export const STICKER_ASSET_KINDS = [
|
|
|
176
176
|
"geometry",
|
|
177
177
|
"cutSvg",
|
|
178
178
|
"thumbAtlas",
|
|
179
|
+
/*
|
|
180
|
+
* One promoted part's full-resolution PNG, by `ordinal`.
|
|
181
|
+
*
|
|
182
|
+
* 🛑 Absent until the producing run is PROMOTED — `SheetPart.assetPath` is
|
|
183
|
+
* only written by the `mode: "full"` shard path. Resolving to `null` before
|
|
184
|
+
* then is correct and must not read as an error: it is the normal state of
|
|
185
|
+
* every traced-but-unpromoted sheet.
|
|
186
|
+
*/
|
|
187
|
+
"part",
|
|
179
188
|
];
|
|
180
189
|
/**
|
|
181
190
|
* Mint a short-lived read URL for one asset of one sheet or run.
|
|
@@ -193,6 +202,29 @@ export async function resolveStickerAssetUrl(input) {
|
|
|
193
202
|
if (input.kind === "scan") {
|
|
194
203
|
return sheet.sourceScan?.assetPath ? signedReadUrl(sheet.sourceScan.assetPath) : null;
|
|
195
204
|
}
|
|
205
|
+
if (input.kind === "part") {
|
|
206
|
+
if (typeof input.ordinal !== "number")
|
|
207
|
+
return null;
|
|
208
|
+
/*
|
|
209
|
+
* 🛑 Matched on `ordinal`, NEVER on array index — the same rule
|
|
210
|
+
* `updatePart` already states. `parts[]` is not guaranteed to be dense or
|
|
211
|
+
* ordered: a part can be removed, and a re-promote rewrites the array. An
|
|
212
|
+
* index lookup would silently serve a DIFFERENT sticker's PNG, which is
|
|
213
|
+
* indistinguishable from a correct response.
|
|
214
|
+
*/
|
|
215
|
+
const part = (sheet.parts ?? []).find((pt) => pt.ordinal === input.ordinal);
|
|
216
|
+
if (!part?.assetPath)
|
|
217
|
+
return null;
|
|
218
|
+
/*
|
|
219
|
+
* Still a signed-URL mint, like every other kind here —
|
|
220
|
+
* `audit-sticker-trace-egress` forbids a route under this feature
|
|
221
|
+
* returning bytes, and this is the unwatermarked printable original, which
|
|
222
|
+
* is the one thing that must not be served through a public path.
|
|
223
|
+
*/
|
|
224
|
+
return signedReadUrl(part.assetPath, {
|
|
225
|
+
downloadAs: `part-${String(input.ordinal).padStart(3, "0")}.png`,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
196
228
|
let path;
|
|
197
229
|
if (input.runId) {
|
|
198
230
|
const run = await stickerTraceRunsRepository.findByIdOrFail(input.runId);
|
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
import type { CutPath, Rect } from "../../../shared/features/sticker-trace/types";
|
|
2
|
-
/**
|
|
2
|
+
/**
|
|
3
|
+
* Everything this feature writes lives under here.
|
|
4
|
+
*
|
|
5
|
+
* 🛑 The one-line claim this used to carry — "nothing under here is reachable
|
|
6
|
+
* without a signed URL minted by this module" — was FALSE, in the same way the
|
|
7
|
+
* identical claim on `DIGITAL_CONTENT_PREFIX` was: `/media/[...slug]`'s
|
|
8
|
+
* raw-path fallback resolved any multi-segment slug as a literal storage path,
|
|
9
|
+
* so `/media/private/sticker-sheets/...` served these objects, watermarked, to
|
|
10
|
+
* anyone. Unlike digital-content's `randomUUID()` names, a part is written as
|
|
11
|
+
* `part-003.png` — guessable on the first try.
|
|
12
|
+
*
|
|
13
|
+
* The proxy now refuses a raw `private/` path outright. What is true is
|
|
14
|
+
* narrower than the old sentence and worth stating precisely: **no public URL
|
|
15
|
+
* exists for one of these objects unless something deliberately mints a
|
|
16
|
+
* `mediaAssets` row for it.** A row is what creates a `/media/{shortId}` slug,
|
|
17
|
+
* and a resolved row is intentionally still allowed through the proxy — that is
|
|
18
|
+
* how a promoted part gets a watermarked marketplace copy with no byte
|
|
19
|
+
* duplicated. The unwatermarked printable original has no row and is reachable
|
|
20
|
+
* only by a signed URL from this module.
|
|
21
|
+
*/
|
|
3
22
|
export declare const SCAN_ROOT = "private/sticker-sheets";
|
|
4
23
|
export declare function runPrefix(sheetId: string, runId: string): string;
|
|
5
24
|
export interface SignedUpload {
|
|
@@ -45,7 +45,26 @@
|
|
|
45
45
|
import { normalizeError } from "../../../../errors/normalize.js";
|
|
46
46
|
import { serverLogger } from "../../../../monitoring/index.js";
|
|
47
47
|
import { getAdminStorage } from "../../../../providers/db-firebase/index.js";
|
|
48
|
-
/**
|
|
48
|
+
/**
|
|
49
|
+
* Everything this feature writes lives under here.
|
|
50
|
+
*
|
|
51
|
+
* 🛑 The one-line claim this used to carry — "nothing under here is reachable
|
|
52
|
+
* without a signed URL minted by this module" — was FALSE, in the same way the
|
|
53
|
+
* identical claim on `DIGITAL_CONTENT_PREFIX` was: `/media/[...slug]`'s
|
|
54
|
+
* raw-path fallback resolved any multi-segment slug as a literal storage path,
|
|
55
|
+
* so `/media/private/sticker-sheets/...` served these objects, watermarked, to
|
|
56
|
+
* anyone. Unlike digital-content's `randomUUID()` names, a part is written as
|
|
57
|
+
* `part-003.png` — guessable on the first try.
|
|
58
|
+
*
|
|
59
|
+
* The proxy now refuses a raw `private/` path outright. What is true is
|
|
60
|
+
* narrower than the old sentence and worth stating precisely: **no public URL
|
|
61
|
+
* exists for one of these objects unless something deliberately mints a
|
|
62
|
+
* `mediaAssets` row for it.** A row is what creates a `/media/{shortId}` slug,
|
|
63
|
+
* and a resolved row is intentionally still allowed through the proxy — that is
|
|
64
|
+
* how a promoted part gets a watermarked marketplace copy with no byte
|
|
65
|
+
* duplicated. The unwatermarked printable original has no row and is reachable
|
|
66
|
+
* only by a signed URL from this module.
|
|
67
|
+
*/
|
|
49
68
|
export const SCAN_ROOT = "private/sticker-sheets";
|
|
50
69
|
/** How long a read URL lives. Long enough to fetch, short enough to be useless if leaked. */
|
|
51
70
|
const READ_TTL_MS = 5 * 60 * 1000;
|
|
@@ -27,7 +27,7 @@ import type { StoreDetail, StoreListItem } from "../../../../features/stores/typ
|
|
|
27
27
|
*/
|
|
28
28
|
export declare const PUBLIC_STORE_FIELDS: readonly ["id", "storeSlug", "ownerId", "storeName", "storeDescription", "storeCategory", "storeLogoURL", "storeBannerURL", "status", "isPublic", "stats", "bio", "location", "website", "socialLinks", "returnPolicy", "shippingPolicy", "isVacationMode", "vacationMessage", "supportsShippingProtection", "supportsSaleProtection", "createdAt"];
|
|
29
29
|
/** Everything else, with the reason it stays server-side. */
|
|
30
|
-
export declare const PRIVATE_STORE_FIELDS: readonly ["searchTxt", "updatedAt", "whatsappConfig", "adminNotes", "statusHistory", "statusHistoryTruncated", "isVerified", "suspensionReason", "capabilities", "customCommissionRate", "googleReviews", "shippingConfig", "emiEnabled", "pricesIncludeGst", "shippingPricing", "returnsDirectToSeller", "isFeatured", "vacationReturnDate", "isTestData", "testDataExpiresAt"];
|
|
30
|
+
export declare const PRIVATE_STORE_FIELDS: readonly ["searchTxt", "updatedAt", "whatsappConfig", "adminNotes", "statusHistory", "statusHistoryTruncated", "isVerified", "suspensionReason", "capabilities", "customCommissionRate", "googleReviews", "shippingConfig", "emiEnabled", "pricesIncludeGst", "shippingPricing", "returnsDirectToSeller", "isFeatured", "vacationReturnDate", "isTestData", "testDataExpiresAt", "gstin", "legalName", "registeredAddress"];
|
|
31
31
|
/**
|
|
32
32
|
* Accepted input: a `StoreDocument` (the common case) or a loosely-typed
|
|
33
33
|
* Firestore row. The legacy top-level counters below predate `stats` and are
|
|
@@ -91,6 +91,22 @@ export const PRIVATE_STORE_FIELDS = [
|
|
|
91
91
|
"vacationReturnDate", // not rendered by StoreAboutView; add to PUBLIC when a reader exists
|
|
92
92
|
"isTestData", // tester-sandbox tagging; filtered by filterTestDataForViewer
|
|
93
93
|
"testDataExpiresAt", // ditto
|
|
94
|
+
/*
|
|
95
|
+
* Tax identity (2026-10-19). Read today by exactly two server-side
|
|
96
|
+
* consumers: `resolveSupplierIdentity` (checkout, to decide whose
|
|
97
|
+
* registration an order invoices under) and the seller's own authenticated
|
|
98
|
+
* payout-settings form. Neither is a public store-profile reader.
|
|
99
|
+
*
|
|
100
|
+
* A GSTIN is not secret in the way a bank account number is — it is
|
|
101
|
+
* legally published on an invoice — but "may be disclosed" and "has a
|
|
102
|
+
* reader that needs it published on /stores/{slug}" are different
|
|
103
|
+
* questions, and only the first is true today. Move to PUBLIC_STORE_FIELDS
|
|
104
|
+
* together, with the literal in `toStoreListItem()`, once a storefront
|
|
105
|
+
* surface actually needs to show a seller's registration.
|
|
106
|
+
*/
|
|
107
|
+
"gstin",
|
|
108
|
+
"legalName",
|
|
109
|
+
"registeredAddress",
|
|
94
110
|
];
|
|
95
111
|
/** `createdAt` is `Date` on the document and `string` on the client types. */
|
|
96
112
|
function toIsoString(value) {
|
|
@@ -3,3 +3,4 @@ export { STORES_PAGE_SIZE, STORES_PRODUCTS_PAGE_SIZE, STORES_SITEMAP_LIMIT, STOR
|
|
|
3
3
|
export { renderStoreOgImage, renderStoreOg, type StoreOgData } from "./og";
|
|
4
4
|
export { resolveStoreFields, resolveStoreFieldsMany, type ProductStoreFields, } from "./denormalize";
|
|
5
5
|
export { toStoreDetail, toStoreListItem, PUBLIC_STORE_FIELDS, PRIVATE_STORE_FIELDS, type ToStoreDetailOptions, type ToStoreListItemOptions, type StoreProjectionSource, } from "./adapters";
|
|
6
|
+
export { resolveStoreTabs, getDefaultStoreTabPath, type ResolvedStoreTabs, type StoreTabLink, } from "./tabs";
|
|
@@ -3,3 +3,4 @@ export { STORES_PAGE_SIZE, STORES_PRODUCTS_PAGE_SIZE, STORES_SITEMAP_LIMIT, STOR
|
|
|
3
3
|
export { renderStoreOgImage, renderStoreOg } from "./og.js";
|
|
4
4
|
export { resolveStoreFields, resolveStoreFieldsMany, } from "./denormalize.js";
|
|
5
5
|
export { toStoreDetail, toStoreListItem, PUBLIC_STORE_FIELDS, PRIVATE_STORE_FIELDS, } from "./adapters.js";
|
|
6
|
+
export { resolveStoreTabs, getDefaultStoreTabPath, } from "./tabs.js";
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { isListingTypeEnabled } from "../../../shared/listing-types/feature-flags";
|
|
2
|
+
import { type ListingTabCounts } from "../products/listing-tab-counts";
|
|
3
|
+
type SiteSettingsSnapshot = Parameters<typeof isListingTypeEnabled>[1];
|
|
4
|
+
export interface StoreTabLink {
|
|
5
|
+
value: string;
|
|
6
|
+
label: string;
|
|
7
|
+
href: string;
|
|
8
|
+
}
|
|
9
|
+
export interface ResolvedStoreTabs {
|
|
10
|
+
/** Listing-type tabs with something in them, in `STORE_PAGE_TABS` order. */
|
|
11
|
+
dropdownTabs: StoreTabLink[];
|
|
12
|
+
/** Coupons (if any), Reviews (if any), About — always non-empty. */
|
|
13
|
+
tabs: StoreTabLink[];
|
|
14
|
+
listingCounts: ListingTabCounts;
|
|
15
|
+
couponsCount: number;
|
|
16
|
+
reviewsCount: number;
|
|
17
|
+
}
|
|
18
|
+
export declare function resolveStoreTabs(storeSlug: string, storeId: string | undefined, settings: SiteSettingsSnapshot): Promise<ResolvedStoreTabs>;
|
|
19
|
+
/**
|
|
20
|
+
* Where the bare `/stores/[slug]` route should land: the first listing-type
|
|
21
|
+
* tab holding anything, else Coupons, else Reviews, else About. `about` is
|
|
22
|
+
* always present in `tabs`, so this never falls through to nothing.
|
|
23
|
+
*/
|
|
24
|
+
export declare function getDefaultStoreTabPath(storeSlug: string, storeId: string | undefined, settings: SiteSettingsSnapshot): Promise<string>;
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* 🛑 Deliberately NO `"use server"`. That directive marks every export as a
|
|
3
|
+
* client-callable RPC endpoint, and these are not — they are read helpers
|
|
4
|
+
* invoked from a Server Component (`StoreDetailLayoutView`) and a server
|
|
5
|
+
* page (`/stores/[slug]`). Declaring it anyway also obliges every export to
|
|
6
|
+
* return `ActionResult<T>` (`audit-server-action-envelope`), which would
|
|
7
|
+
* wrap a path string in an envelope nobody unwraps. The sibling helpers
|
|
8
|
+
* this file sits beside — `adapters.ts`, `denormalize.ts` and
|
|
9
|
+
* `listing-tab-counts.ts`, which it calls — carry no directive either.
|
|
10
|
+
*
|
|
11
|
+
* WHY: `StoreDetailLayoutView`'s nav bar and the bare `/stores/[slug]`
|
|
12
|
+
* redirect both need "which tabs does this store actually have
|
|
13
|
+
* something in, and in what order". Before this file the redirect
|
|
14
|
+
* didn't ask the question at all — it always sent a visitor to
|
|
15
|
+
* `/products`, even for a store with zero products (and nothing else),
|
|
16
|
+
* landing on a tab that isn't even in the nav bar. Computing the answer
|
|
17
|
+
* twice (once per caller) is exactly the duplicated-predicate shape
|
|
18
|
+
* Root Cause #73 warns about, so it lives in one place.
|
|
19
|
+
* WHAT: `resolveStoreTabs` — every visible tab for a store, in priority
|
|
20
|
+
* order (listing types in `STORE_PAGE_TABS` order, then Coupons, then
|
|
21
|
+
* Reviews, then About last). `getDefaultStoreTabPath` picks the first
|
|
22
|
+
* one — the correct landing page for a store with no products.
|
|
23
|
+
*/
|
|
24
|
+
import { sieveAnd, sieveFilter, SIEVE_OP } from "../../../../utils/sieve-builder.js";
|
|
25
|
+
import { couponsRepository, reviewRepository } from "../../../../repositories/index.js";
|
|
26
|
+
import { ROUTES } from "../../../../next/routing/route-map.js";
|
|
27
|
+
import { STORE_PAGE_TABS } from "../../../../features/products/constants/listing-tabs.js";
|
|
28
|
+
import { isListingTypeEnabled, isCategoryTypeEnabled } from "../../../shared/listing-types/feature-flags.js";
|
|
29
|
+
import { pluginFor } from "../../../shared/listing-types/_registry.js";
|
|
30
|
+
import { listingTabCounts, } from "../products/listing-tab-counts.js";
|
|
31
|
+
function tabLabel(base, count) {
|
|
32
|
+
if (!count)
|
|
33
|
+
return base;
|
|
34
|
+
return `${base} (${count.toLocaleString()})`;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* tabSlug -> route builder. Mirrors `ROUTES.PUBLIC.STORE_*` exactly.
|
|
38
|
+
*
|
|
39
|
+
* 🛑 `spares` was missing here until this file was added, which meant
|
|
40
|
+
* `visibleStoreTabs` could include a tab this map had no builder for the
|
|
41
|
+
* moment a store's spares count went above zero — `STORE_LISTING_HREF[tab.id]`
|
|
42
|
+
* would resolve to `undefined`, and calling it as a function would throw.
|
|
43
|
+
* `/stores/[storeSlug]/spares/page.tsx` is a real route; the href map just
|
|
44
|
+
* never named it.
|
|
45
|
+
*/
|
|
46
|
+
const STORE_LISTING_HREF = {
|
|
47
|
+
products: (slug) => String(ROUTES.PUBLIC.STORE_PRODUCTS(slug)),
|
|
48
|
+
auctions: (slug) => String(ROUTES.PUBLIC.STORE_AUCTIONS(slug)),
|
|
49
|
+
"pre-orders": (slug) => String(ROUTES.PUBLIC.STORE_PRE_ORDERS(slug)),
|
|
50
|
+
"prize-draws": (slug) => String(ROUTES.PUBLIC.STORE_PRIZE_DRAWS(slug)),
|
|
51
|
+
bundles: (slug) => String(ROUTES.PUBLIC.STORE_BUNDLES(slug)),
|
|
52
|
+
classifieds: (slug) => String(ROUTES.PUBLIC.STORE_CLASSIFIEDS(slug)),
|
|
53
|
+
"digital-codes": (slug) => String(ROUTES.PUBLIC.STORE_DIGITAL_CODES(slug)),
|
|
54
|
+
live: (slug) => String(ROUTES.PUBLIC.STORE_LIVE(slug)),
|
|
55
|
+
// Combined art + stickers, mirroring the public /art page. This used to
|
|
56
|
+
// point at STORE_PRODUCTS, which filters to `standard` — so the tab showed
|
|
57
|
+
// a real non-zero count and landed on a page with none of those items.
|
|
58
|
+
art: (slug) => String(ROUTES.PUBLIC.STORE_ART(slug)),
|
|
59
|
+
spares: (slug) => String(ROUTES.PUBLIC.STORE_SPARES(slug)),
|
|
60
|
+
};
|
|
61
|
+
export async function resolveStoreTabs(storeSlug, storeId, settings) {
|
|
62
|
+
const [listingCounts, couponsCount, reviewsCount] = storeId
|
|
63
|
+
? await Promise.all([
|
|
64
|
+
listingTabCounts(STORE_PAGE_TABS, { storeId }),
|
|
65
|
+
// audit-public-test-data-ok: a COUNT of the store's own coupons, and
|
|
66
|
+
// `CouponDocument` carries no isTestData field — there is nothing to strip.
|
|
67
|
+
couponsRepository
|
|
68
|
+
.list({ filters: sieveAnd(sieveFilter("storeId", SIEVE_OP.EQ, storeId), sieveFilter("validity.isActive", SIEVE_OP.EQ, "true")), page: 1, pageSize: 1 })
|
|
69
|
+
.then((r) => r.total)
|
|
70
|
+
.catch(() => 0),
|
|
71
|
+
reviewRepository
|
|
72
|
+
.listAll({ filters: sieveAnd(sieveFilter("storeId", SIEVE_OP.EQ, storeId), sieveFilter("status", SIEVE_OP.EQ, "approved")), page: 1, pageSize: 1 })
|
|
73
|
+
.then((r) => r.total)
|
|
74
|
+
.catch(() => 0),
|
|
75
|
+
])
|
|
76
|
+
: [{}, 0, 0];
|
|
77
|
+
const visibleStoreTabs = STORE_PAGE_TABS.filter((tab) => {
|
|
78
|
+
if (tab.id === "bundles") {
|
|
79
|
+
if (!isCategoryTypeEnabled("bundle", settings))
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
else if (tab.id === pluginFor("art").tabSlug) {
|
|
83
|
+
// Combined tab — visible if either underlying listing type is enabled.
|
|
84
|
+
if (!(isListingTypeEnabled("art", settings) || isListingTypeEnabled("stickers", settings)))
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
else if (tab.listingType) {
|
|
88
|
+
if (!isListingTypeEnabled(tab.listingType, settings))
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
// A store with zero items of a given listing type shouldn't offer a tab
|
|
92
|
+
// that leads to an empty page. `undefined` means the count query FAILED,
|
|
93
|
+
// not zero, so the tab stays visible rather than hiding real stock
|
|
94
|
+
// behind a swallowed error (Root Cause #59).
|
|
95
|
+
const count = listingCounts[tab.id];
|
|
96
|
+
return count === undefined || count > 0;
|
|
97
|
+
});
|
|
98
|
+
const dropdownTabs = visibleStoreTabs.map((tab) => ({
|
|
99
|
+
value: tab.id,
|
|
100
|
+
label: tabLabel(tab.label, listingCounts[tab.id]),
|
|
101
|
+
href: STORE_LISTING_HREF[tab.id](storeSlug),
|
|
102
|
+
}));
|
|
103
|
+
const tabs = [
|
|
104
|
+
...(couponsCount > 0
|
|
105
|
+
? [{ value: "coupons", label: tabLabel("Coupons", couponsCount), href: String(ROUTES.PUBLIC.STORE_COUPONS(storeSlug)) }]
|
|
106
|
+
: []),
|
|
107
|
+
...(reviewsCount > 0
|
|
108
|
+
? [{ value: "reviews", label: tabLabel("Reviews", reviewsCount), href: String(ROUTES.PUBLIC.STORE_REVIEWS(storeSlug)) }]
|
|
109
|
+
: []),
|
|
110
|
+
{ value: "about", label: "About", href: String(ROUTES.PUBLIC.STORE_ABOUT(storeSlug)) },
|
|
111
|
+
];
|
|
112
|
+
return { dropdownTabs, tabs, listingCounts, couponsCount, reviewsCount };
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Where the bare `/stores/[slug]` route should land: the first listing-type
|
|
116
|
+
* tab holding anything, else Coupons, else Reviews, else About. `about` is
|
|
117
|
+
* always present in `tabs`, so this never falls through to nothing.
|
|
118
|
+
*/
|
|
119
|
+
export async function getDefaultStoreTabPath(storeSlug, storeId, settings) {
|
|
120
|
+
const { dropdownTabs, tabs } = await resolveStoreTabs(storeSlug, storeId, settings);
|
|
121
|
+
return (dropdownTabs[0] ?? tabs[0]).href;
|
|
122
|
+
}
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
/** Default ListingCheckoutRule hook implementations. */
|
|
2
|
-
import { STANDARD_MAX_QTY_PER_LINE } from "./_limits.js";
|
|
3
2
|
export const DEFAULT_LISTING_RULE = {
|
|
4
3
|
purchaseable: true,
|
|
5
4
|
cartEligible: true,
|
|
6
5
|
cartIneligibleHint: "This listing type cannot be added to the cart.",
|
|
7
6
|
requiresShippingAddress: true,
|
|
8
7
|
nonRefundable: false,
|
|
9
|
-
maxQuantityPerLine: STANDARD_MAX_QTY_PER_LINE,
|
|
10
8
|
canMergeWithSameProduct: true,
|
|
11
9
|
maxLinesPerOrder: Infinity,
|
|
12
10
|
refundPolicy: { full: true, partial: true, partialGranularity: "item" },
|
|
@@ -28,5 +28,18 @@ export declare const PRIZE_DRAW_DURATION_DAYS_MIN = 1;
|
|
|
28
28
|
export declare const PRIZE_DRAW_DURATION_DAYS_MAX = 15;
|
|
29
29
|
/** Maximum qty per bundle purchase (bundles are always single-shot direct buys). */
|
|
30
30
|
export declare const BUNDLE_MAX_QTY_PER_TX = 1;
|
|
31
|
-
/**
|
|
31
|
+
/**
|
|
32
|
+
* Max quantity on a single cart line.
|
|
33
|
+
*
|
|
34
|
+
* 🛑 It now IS the enforcement. It used to say "enforced at addToCart" while
|
|
35
|
+
* the actual limit was four hand-written `.max(99)` literals that never
|
|
36
|
+
* referenced it — so the constant was documentation nobody read and the rule
|
|
37
|
+
* was a magic number in four places. The cart schemas import it.
|
|
38
|
+
*
|
|
39
|
+
* Its other reader, `ListingCheckoutRule.maxQuantityPerLine`, was DELETED in
|
|
40
|
+
* the same change: that field was set by auction, offer and prize-draw and read
|
|
41
|
+
* by nothing at all, and it contradicted the feature it appeared to govern —
|
|
42
|
+
* prize-draw set it to 1 while `splitMultipleOrders` right beside it existed
|
|
43
|
+
* only to chunk a quantity of 7 into [3,3,1].
|
|
44
|
+
*/
|
|
32
45
|
export declare const STANDARD_MAX_QTY_PER_LINE = 99;
|
|
@@ -28,5 +28,18 @@ export const PRIZE_DRAW_DURATION_DAYS_MIN = 1;
|
|
|
28
28
|
export const PRIZE_DRAW_DURATION_DAYS_MAX = 15;
|
|
29
29
|
/** Maximum qty per bundle purchase (bundles are always single-shot direct buys). */
|
|
30
30
|
export const BUNDLE_MAX_QTY_PER_TX = 1;
|
|
31
|
-
/**
|
|
31
|
+
/**
|
|
32
|
+
* Max quantity on a single cart line.
|
|
33
|
+
*
|
|
34
|
+
* 🛑 It now IS the enforcement. It used to say "enforced at addToCart" while
|
|
35
|
+
* the actual limit was four hand-written `.max(99)` literals that never
|
|
36
|
+
* referenced it — so the constant was documentation nobody read and the rule
|
|
37
|
+
* was a magic number in four places. The cart schemas import it.
|
|
38
|
+
*
|
|
39
|
+
* Its other reader, `ListingCheckoutRule.maxQuantityPerLine`, was DELETED in
|
|
40
|
+
* the same change: that field was set by auction, offer and prize-draw and read
|
|
41
|
+
* by nothing at all, and it contradicted the feature it appeared to govern —
|
|
42
|
+
* prize-draw set it to 1 while `splitMultipleOrders` right beside it existed
|
|
43
|
+
* only to chunk a quantity of 7 into [3,3,1].
|
|
44
|
+
*/
|
|
32
45
|
export const STANDARD_MAX_QTY_PER_LINE = 99;
|
|
@@ -20,6 +20,7 @@ import { digitalCodeRule } from "./digital-code.rule.js";
|
|
|
20
20
|
import { liveRule } from "./live.rule.js";
|
|
21
21
|
import { artRule } from "./art.rule.js";
|
|
22
22
|
import { stickersRule } from "./stickers.rule.js";
|
|
23
|
+
import { sparesRule } from "./spares.rule.js";
|
|
23
24
|
export const CHECKOUT_RULES = {
|
|
24
25
|
standard: standardRule,
|
|
25
26
|
auction: auctionRule,
|
|
@@ -30,6 +31,7 @@ export const CHECKOUT_RULES = {
|
|
|
30
31
|
live: liveRule,
|
|
31
32
|
art: artRule,
|
|
32
33
|
stickers: stickersRule,
|
|
34
|
+
spares: sparesRule,
|
|
33
35
|
};
|
|
34
36
|
export const CATEGORY_CHECKOUT_RULES = {
|
|
35
37
|
bundle: bundleRule,
|