@mohasinac/appkit 4.53.0 → 4.53.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.build-stamp.json +3 -3
- package/dist/_internal/client/scaffolds/AppShell.js +1 -1
- package/dist/_internal/client/theme/ThemeProvider.js +15 -1
- package/dist/_internal/server/features/auctions/og.d.ts +3 -1
- package/dist/_internal/server/features/auctions/og.js +3 -1
- package/dist/_internal/server/features/bundles/actions.js +1 -1
- package/dist/_internal/server/features/business-documents/download.d.ts +41 -0
- package/dist/_internal/server/features/business-documents/download.js +91 -0
- package/dist/_internal/server/features/categories/adapters.d.ts +1 -1
- package/dist/_internal/server/features/categories/adapters.js +10 -0
- package/dist/_internal/server/features/categories/price-guidance-resolve.d.ts +66 -0
- package/dist/_internal/server/features/categories/price-guidance-resolve.js +138 -0
- package/dist/_internal/server/features/checkout/actions.d.ts +14 -0
- package/dist/_internal/server/features/checkout/actions.js +106 -12
- package/dist/_internal/server/features/checkout/bundle-expansion.js +17 -0
- package/dist/_internal/server/features/checkout/data.d.ts +22 -0
- package/dist/_internal/server/features/checkout/group-rule-revalidation.d.ts +34 -0
- package/dist/_internal/server/features/checkout/group-rule-revalidation.js +124 -0
- package/dist/_internal/server/features/checkout/supplier-identity.d.ts +55 -0
- package/dist/_internal/server/features/checkout/supplier-identity.js +81 -0
- package/dist/_internal/server/features/classified/metadata.js +1 -1
- package/dist/_internal/server/features/classified/og.d.ts +3 -1
- package/dist/_internal/server/features/classified/og.js +3 -1
- package/dist/_internal/server/features/digital-code/metadata.js +1 -1
- package/dist/_internal/server/features/digital-code/og.d.ts +3 -1
- package/dist/_internal/server/features/digital-code/og.js +3 -1
- package/dist/_internal/server/features/digital-code/pool.d.ts +19 -4
- package/dist/_internal/server/features/digital-code/pool.js +19 -4
- package/dist/_internal/server/features/live/metadata.js +1 -1
- package/dist/_internal/server/features/live/og.d.ts +3 -1
- package/dist/_internal/server/features/live/og.js +3 -1
- package/dist/_internal/server/features/messages/actions.js +1 -1
- package/dist/_internal/server/features/parts/og.js +1 -1
- package/dist/_internal/server/features/pre-orders/og.d.ts +3 -1
- package/dist/_internal/server/features/pre-orders/og.js +3 -1
- package/dist/_internal/server/features/price-index/market.d.ts +57 -0
- package/dist/_internal/server/features/price-index/market.js +120 -0
- package/dist/_internal/server/features/price-index/search.d.ts +18 -0
- package/dist/_internal/server/features/price-index/search.js +47 -7
- package/dist/_internal/server/features/prize-draws/og.d.ts +3 -1
- package/dist/_internal/server/features/prize-draws/og.js +3 -1
- package/dist/_internal/server/features/products/group-members.d.ts +26 -0
- package/dist/_internal/server/features/products/group-members.js +7 -1
- package/dist/_internal/server/features/products/list-public.d.ts +12 -0
- package/dist/_internal/server/features/products/list-public.js +10 -1
- package/dist/_internal/server/features/products/listing-tab-counts.d.ts +25 -1
- package/dist/_internal/server/features/products/listing-tab-counts.js +128 -10
- package/dist/_internal/server/features/products/og.d.ts +3 -1
- package/dist/_internal/server/features/products/og.js +3 -1
- package/dist/_internal/server/features/search/scoped-sources.d.ts +26 -0
- package/dist/_internal/server/features/search/scoped-sources.js +252 -0
- package/dist/_internal/server/features/search/search-everything.d.ts +57 -0
- package/dist/_internal/server/features/search/search-everything.js +70 -20
- package/dist/_internal/server/features/seo/sitemap.js +6 -0
- package/dist/_internal/server/features/shared/listing-adapters.js +4 -2
- package/dist/_internal/server/features/sticker-trace/adapters.d.ts +11 -0
- package/dist/_internal/server/features/sticker-trace/adapters.js +1 -0
- package/dist/_internal/server/features/sticker-trace/promote-to-listings.d.ts +26 -0
- package/dist/_internal/server/features/sticker-trace/promote-to-listings.js +264 -0
- package/dist/_internal/server/features/sticker-trace/sheet-actions.d.ts +3 -1
- package/dist/_internal/server/features/sticker-trace/sheet-actions.js +32 -0
- package/dist/_internal/server/features/sticker-trace/storage.d.ts +20 -1
- package/dist/_internal/server/features/sticker-trace/storage.js +20 -1
- package/dist/_internal/server/features/stores/adapters.d.ts +1 -1
- package/dist/_internal/server/features/stores/adapters.js +16 -0
- package/dist/_internal/server/features/stores/index.d.ts +1 -0
- package/dist/_internal/server/features/stores/index.js +1 -0
- package/dist/_internal/server/features/stores/tabs.d.ts +30 -0
- package/dist/_internal/server/features/stores/tabs.js +124 -0
- package/dist/_internal/shared/checkout/rules/_defaults.js +0 -2
- package/dist/_internal/shared/checkout/rules/_limits.d.ts +14 -1
- package/dist/_internal/shared/checkout/rules/_limits.js +14 -1
- package/dist/_internal/shared/checkout/rules/_registry.js +2 -0
- package/dist/_internal/shared/checkout/rules/auction.rule.js +9 -1
- package/dist/_internal/shared/checkout/rules/bundle.rule.js +13 -0
- package/dist/_internal/shared/checkout/rules/index.d.ts +2 -0
- package/dist/_internal/shared/checkout/rules/index.js +9 -0
- package/dist/_internal/shared/checkout/rules/offer.rule.js +0 -1
- package/dist/_internal/shared/checkout/rules/payment-gate.d.ts +26 -0
- package/dist/_internal/shared/checkout/rules/payment-gate.js +133 -0
- package/dist/_internal/shared/checkout/rules/prize-draw.rule.js +29 -8
- package/dist/_internal/shared/checkout/rules/spares.rule.d.ts +16 -0
- package/dist/_internal/shared/checkout/rules/spares.rule.js +5 -0
- package/dist/_internal/shared/checkout/rules/types.d.ts +26 -2
- package/dist/_internal/shared/config/schema.d.ts +122 -0
- package/dist/_internal/shared/features/cart/schema.js +3 -2
- package/dist/_internal/shared/features/products/group-rules.d.ts +75 -0
- package/dist/_internal/shared/features/products/group-rules.js +142 -0
- package/dist/_internal/shared/listing-types/_registry.d.ts +24 -0
- package/dist/_internal/shared/listing-types/_registry.js +39 -1
- package/dist/_internal/shared/listing-types/art/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/art/config.js +2 -0
- package/dist/_internal/shared/listing-types/auction/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/auction/config.js +2 -0
- package/dist/_internal/shared/listing-types/availability.js +48 -0
- package/dist/_internal/shared/listing-types/capabilities.js +19 -0
- package/dist/_internal/shared/listing-types/classified/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/classified/config.js +2 -0
- package/dist/_internal/shared/listing-types/digital-code/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/digital-code/config.js +2 -0
- package/dist/_internal/shared/listing-types/feature-flags.js +1 -0
- package/dist/_internal/shared/listing-types/live/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/live/config.js +2 -0
- package/dist/_internal/shared/listing-types/pre-order/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/pre-order/config.js +2 -0
- package/dist/_internal/shared/listing-types/prize-draw/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/prize-draw/config.js +2 -0
- package/dist/_internal/shared/listing-types/spares/config.d.ts +74 -0
- package/dist/_internal/shared/listing-types/spares/config.js +60 -0
- package/dist/_internal/shared/listing-types/spares/ctas.d.ts +1 -0
- package/dist/_internal/shared/listing-types/spares/ctas.js +4 -0
- package/dist/_internal/shared/listing-types/spares/og.js +1 -0
- package/dist/_internal/shared/listing-types/spares/schema.d.ts +2 -0
- package/dist/_internal/shared/listing-types/spares/schema.js +3 -0
- package/dist/_internal/shared/listing-types/spares/seed-factory.d.ts +1 -0
- package/dist/_internal/shared/listing-types/spares/seed-factory.js +1 -0
- package/dist/_internal/shared/listing-types/standard/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/standard/config.js +2 -0
- package/dist/_internal/shared/listing-types/stickers/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/stickers/config.js +2 -0
- package/dist/_internal/shared/search/match.d.ts +11 -0
- package/dist/_internal/shared/search/match.js +30 -3
- package/dist/_internal/shared/search/to-groups.d.ts +52 -0
- package/dist/_internal/shared/search/to-groups.js +141 -0
- package/dist/client.d.ts +8 -2
- package/dist/client.js +4 -1
- package/dist/constants/api-endpoints.d.ts +46 -15
- package/dist/constants/api-endpoints.js +35 -5
- package/dist/constants/cache-invalidation.js +3 -1
- package/dist/constants/field-names.d.ts +6 -0
- package/dist/constants/field-names.js +6 -0
- package/dist/features/about/components/PolicyPageView.d.ts +22 -2
- package/dist/features/about/components/PolicyPageView.js +14 -2
- package/dist/features/about/components/PublicProfileView.js +2 -1
- package/dist/features/about/components/ShippingPolicyView.js +42 -2
- package/dist/features/account/components/NotificationBell.js +1 -1
- package/dist/features/account/hooks/useProfileStats.js +5 -1
- package/dist/features/addresses/repository/addresses.repository.d.ts +16 -0
- package/dist/features/addresses/repository/addresses.repository.js +21 -0
- package/dist/features/admin/actions/admin-actions.js +16 -1
- package/dist/features/admin/components/AdminCategoryEditorView.js +19 -2
- package/dist/features/admin/components/AdminNavigationView.js +1 -1
- package/dist/features/admin/components/AdminPageHeader.js +1 -1
- package/dist/features/admin/components/AdminProductEditorView.js +17 -2
- package/dist/features/admin/components/AdminSidebar.js +1 -1
- package/dist/features/admin/components/AdminSiteSettingsView.js +39 -8
- package/dist/features/admin/components/AdminSparesView.d.ts +12 -0
- package/dist/features/admin/components/AdminSparesView.js +30 -0
- package/dist/features/admin/components/AdminUsersView.js +9 -7
- package/dist/features/admin/components/index.d.ts +2 -0
- package/dist/features/admin/components/index.js +1 -0
- package/dist/features/admin/components/sections/adminSectionsTypes.js +7 -0
- package/dist/features/admin/constants/filter-tabs.d.ts +3 -0
- package/dist/features/admin/constants/filter-tabs.js +1 -0
- package/dist/features/admin/schemas/firestore.d.ts +26 -2
- package/dist/features/auctions/components/AuctionDetailPageView.js +12 -18
- package/dist/features/auth/repository/user.repository.d.ts +13 -0
- package/dist/features/auth/repository/user.repository.js +28 -0
- package/dist/features/auth/schemas/firestore.d.ts +2 -0
- package/dist/features/auth/schemas/firestore.js +2 -0
- package/dist/features/business-documents/repository/business-documents.repository.d.ts +58 -0
- package/dist/features/business-documents/repository/business-documents.repository.js +54 -0
- package/dist/features/business-documents/schemas/firestore.d.ts +105 -0
- package/dist/features/business-documents/schemas/firestore.js +75 -0
- package/dist/features/cart/actions/cart-actions.js +83 -6
- package/dist/features/cart/repository/cart.repository.js +32 -4
- package/dist/features/cart/schemas/firestore.d.ts +20 -0
- package/dist/features/cart/schemas/index.d.ts +2 -2
- package/dist/features/catalogue/components/PublicCatalogueItemDetailView.js +1 -1
- package/dist/features/categories/api/[id]/price-guidance/route.d.ts +42 -0
- package/dist/features/categories/api/[id]/price-guidance/route.js +90 -0
- package/dist/features/categories/components/BrandDetailPageView.js +32 -8
- package/dist/features/categories/components/BrandDetailTabs.d.ts +7 -1
- package/dist/features/categories/components/BrandDetailTabs.js +28 -3
- package/dist/features/categories/components/BundleCollage.js +3 -2
- package/dist/features/categories/components/BundleItemsPicker.js +3 -4
- package/dist/features/categories/components/CategoriesIndexListing.js +23 -8
- package/dist/features/categories/components/CategoryDetailPageView.js +157 -20
- package/dist/features/categories/components/CategoryDetailTabs.d.ts +7 -1
- package/dist/features/categories/components/CategoryDetailTabs.js +41 -7
- package/dist/features/categories/components/CategoryGrid.js +10 -1
- package/dist/features/categories/components/PriceGuidanceHint.d.ts +29 -0
- package/dist/features/categories/components/PriceGuidanceHint.js +61 -0
- package/dist/features/categories/components/SubcategoryCardRow.d.ts +27 -0
- package/dist/features/categories/components/SubcategoryCardRow.js +98 -0
- package/dist/features/categories/constants/category-tabs.d.ts +20 -25
- package/dist/features/categories/constants/category-tabs.js +22 -39
- package/dist/features/categories/repository/categories.repository.d.ts +12 -0
- package/dist/features/categories/repository/categories.repository.js +20 -0
- package/dist/features/categories/schemas/firestore.d.ts +22 -1
- package/dist/features/categories/schemas/firestore.js +10 -0
- package/dist/features/categories/schemas/index.d.ts +6 -0
- package/dist/features/categories/schemas/index.js +2 -0
- package/dist/features/categories/types/index.d.ts +8 -0
- package/dist/features/categories/utils/taxonomy-visual.d.ts +16 -3
- package/dist/features/categories/utils/taxonomy-visual.js +26 -10
- package/dist/features/classified/components/ClassifiedDetailPageView.js +14 -10
- package/dist/features/contact/components/ContactPageView.d.ts +33 -1
- package/dist/features/contact/components/ContactPageView.js +58 -28
- package/dist/features/digital-codes/components/DigitalCodeDetailPageView.js +14 -10
- package/dist/features/grouped/components/GroupedListingDetailView.js +9 -1
- package/dist/features/grouped/schemas/firestore.d.ts +5 -0
- package/dist/features/homepage/actions/homepage-section-actions.d.ts +3 -3
- package/dist/features/homepage/components/CollectionCardsSection.d.ts +28 -6
- package/dist/features/homepage/components/CollectionCardsSection.js +43 -8
- package/dist/features/homepage/components/HowItWorksSection.d.ts +23 -6
- package/dist/features/homepage/components/HowItWorksSection.js +4 -4
- package/dist/features/homepage/components/PromoGrid.d.ts +31 -2
- package/dist/features/homepage/components/PromoGrid.js +12 -3
- package/dist/features/homepage/components/TestimonialsCarousel.d.ts +34 -2
- package/dist/features/homepage/components/TestimonialsCarousel.js +1 -1
- package/dist/features/homepage/components/index.d.ts +0 -20
- package/dist/features/homepage/components/index.js +0 -11
- package/dist/features/homepage/index.d.ts +0 -2
- package/dist/features/homepage/index.js +0 -2
- package/dist/features/homepage/lib/section-renderer.d.ts +9 -0
- package/dist/features/homepage/lib/section-renderer.js +202 -58
- package/dist/features/homepage/repository/homepage-sections.repository.d.ts +35 -3
- package/dist/features/homepage/repository/homepage-sections.repository.js +58 -8
- package/dist/features/homepage/schemas/firestore.d.ts +106 -3
- package/dist/features/homepage/schemas/firestore.js +32 -2
- package/dist/features/homepage/schemas/index.d.ts +36 -7
- package/dist/features/homepage/schemas/index.js +39 -27
- package/dist/features/homepage/server.d.ts +0 -3
- package/dist/features/homepage/server.js +0 -3
- package/dist/features/homepage/types/index.d.ts +0 -47
- package/dist/features/homepage/types/index.js +49 -0
- package/dist/features/layout/AppLayoutShell.js +1 -1
- package/dist/features/layout/BottomNavItem.js +1 -1
- package/dist/features/layout/BottomSheet.js +1 -1
- package/dist/features/layout/TitleBarLayout.js +1 -1
- package/dist/features/live/components/LiveItemDetailPageView.js +14 -10
- package/dist/features/media/hooks/useMedia.js +17 -0
- package/dist/features/media/repository/media-assets.repository.d.ts +46 -0
- package/dist/features/media/repository/media-assets.repository.js +31 -0
- package/dist/features/media/trust-signals.d.ts +85 -0
- package/dist/features/media/trust-signals.js +126 -0
- package/dist/features/parts/components/PartDetailPageView.d.ts +14 -1
- package/dist/features/parts/components/PartDetailPageView.js +2 -2
- package/dist/features/parts/components/PartsIndexPageView.d.ts +11 -2
- package/dist/features/parts/components/PartsIndexPageView.js +2 -2
- package/dist/features/parts/components/PartsSearchBox.d.ts +9 -0
- package/dist/features/parts/components/PartsSearchBox.js +57 -0
- package/dist/features/parts/schemas/firestore.d.ts +149 -3
- package/dist/features/parts/schemas/firestore.js +184 -0
- package/dist/features/payments/repository/payout.repository.d.ts +15 -0
- package/dist/features/payments/repository/payout.repository.js +21 -0
- package/dist/features/pre-orders/components/PreOrderDetailPageView.js +12 -18
- package/dist/features/pre-orders/components/PreOrdersIndexListing.js +1 -1
- package/dist/features/price-index/components/PriceIndexToolView.d.ts +24 -1
- package/dist/features/price-index/components/PriceIndexToolView.js +299 -17
- package/dist/features/price-index/lookup-target.d.ts +48 -0
- package/dist/features/price-index/lookup-target.js +107 -0
- package/dist/features/products/components/AvailabilityScopeHint.d.ts +22 -0
- package/dist/features/products/components/AvailabilityScopeHint.js +27 -0
- package/dist/features/products/components/CompareOverlay.d.ts +20 -2
- package/dist/features/products/components/CompareOverlay.js +23 -3
- package/dist/features/products/components/CustomSectionTabContent.d.ts +31 -0
- package/dist/features/products/components/CustomSectionTabContent.js +29 -3
- package/dist/features/products/components/GroupMemberPicker.d.ts +19 -1
- package/dist/features/products/components/GroupMemberPicker.js +46 -8
- package/dist/features/products/components/GroupSettingsPanel.d.ts +11 -1
- package/dist/features/products/components/GroupSettingsPanel.js +92 -3
- package/dist/features/products/components/PrizeDrawDetailPageView.js +1 -1
- package/dist/features/products/components/PrizeDrawEntryActions.d.ts +15 -1
- package/dist/features/products/components/PrizeDrawEntryActions.js +70 -8
- package/dist/features/products/components/ProductDetailPageView.js +81 -17
- package/dist/features/products/components/ProductForm.d.ts +13 -1
- package/dist/features/products/components/ProductForm.js +16 -2
- package/dist/features/products/components/ProductsIndexListing.d.ts +16 -1
- package/dist/features/products/components/ProductsIndexListing.js +3 -3
- package/dist/features/products/components/ShowGroupSection.js +15 -2
- package/dist/features/products/components/SparesListView.d.ts +7 -0
- package/dist/features/products/components/SparesListView.js +39 -0
- package/dist/features/products/components/SublistingCarouselSection.js +2 -1
- package/dist/features/products/constants/action-defs.d.ts +1 -0
- package/dist/features/products/constants/action-defs.js +1 -0
- package/dist/features/products/repository/products.repository.d.ts +99 -0
- package/dist/features/products/repository/products.repository.js +240 -18
- package/dist/features/products/schemas/firestore.d.ts +97 -4
- package/dist/features/products/schemas/firestore.js +104 -0
- package/dist/features/products/schemas/index.d.ts +10 -19
- package/dist/features/products/schemas/index.js +23 -6
- package/dist/features/products/types/index.d.ts +45 -1
- package/dist/features/products/utils/group-member-image.d.ts +46 -0
- package/dist/features/products/utils/group-member-image.js +4 -0
- package/dist/features/products/utils/listing-badge-variant.js +4 -0
- package/dist/features/reviews/components/ReviewsList.js +1 -1
- package/dist/features/scams/components/ScamProfileView.js +1 -1
- package/dist/features/search/components/EntityTypeahead.d.ts +44 -0
- package/dist/features/search/components/EntityTypeahead.js +92 -0
- package/dist/features/search/components/Search.d.ts +12 -1
- package/dist/features/search/components/Search.js +47 -3
- package/dist/features/search/components/SearchFacetRail.d.ts +11 -0
- package/dist/features/search/components/SearchFacetRail.js +92 -0
- package/dist/features/search/components/SearchResultsClient.d.ts +33 -0
- package/dist/features/search/components/SearchResultsClient.js +103 -0
- package/dist/features/search/components/SearchResultsPageView.d.ts +15 -1
- package/dist/features/search/components/SearchResultsPageView.js +2 -2
- package/dist/features/search/components/index.d.ts +0 -6
- package/dist/features/search/components/index.js +0 -3
- package/dist/features/search/hooks/useNavSuggestions.d.ts +13 -0
- package/dist/features/search/hooks/useScopedSearch.d.ts +34 -0
- package/dist/features/search/hooks/useScopedSearch.js +114 -0
- package/dist/features/search/hooks/useSiteSearch.d.ts +26 -0
- package/dist/features/search/hooks/useSiteSearch.js +149 -0
- package/dist/features/search/index.d.ts +0 -4
- package/dist/features/search/index.js +0 -4
- package/dist/features/search/manifest.d.ts +12 -0
- package/dist/features/search/manifest.js +17 -2
- package/dist/features/search/server.d.ts +0 -3
- package/dist/features/search/server.js +0 -3
- package/dist/features/seller/components/SellerListingTypeView.d.ts +1 -1
- package/dist/features/seller/components/SellerListingTypeView.js +20 -0
- package/dist/features/seller/components/SellerPayoutSettingsView.js +35 -14
- package/dist/features/seller/components/SellerProductShell.d.ts +1 -1
- package/dist/features/seller/components/SellerProductShell.js +2 -1
- package/dist/features/seller/components/SellerProductsView.js +3 -2
- package/dist/features/seller/components/SellerSidebar.js +1 -1
- package/dist/features/seller/components/SellerSparesView.d.ts +17 -0
- package/dist/features/seller/components/SellerSparesView.js +6 -0
- package/dist/features/seller/components/index.d.ts +2 -0
- package/dist/features/seller/components/index.js +1 -0
- package/dist/features/shell/CommandPalette.js +1 -1
- package/dist/features/sticker-trace/schemas/firestore.d.ts +19 -0
- package/dist/features/stores/components/StoreAuctionsListing.js +2 -1
- package/dist/features/stores/components/StoreDetailLayoutView.d.ts +1 -1
- package/dist/features/stores/components/StoreDetailLayoutView.js +7 -82
- package/dist/features/stores/components/StoreHeader.js +1 -2
- package/dist/features/stores/components/StoreSparesPageView.d.ts +27 -0
- package/dist/features/stores/components/StoreSparesPageView.js +48 -0
- package/dist/features/stores/schemas/firestore.d.ts +19 -0
- package/dist/features/tester/seed-data/authored/buying__spares-and-groups.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/buying__spares-and-groups.js +96 -0
- package/dist/features/tester/seed-data/authored/content-discovery__authenticity-checker.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/content-discovery__authenticity-checker.js +392 -45
- package/dist/features/tester/seed-data/authored/content-discovery__category-tabs-franchise-bundles.js +54 -32
- package/dist/features/tester/seed-data/authored/content-discovery__parts-database.js +3 -2
- package/dist/features/tester/seed-data/authored/content-discovery__price-index-and-compare.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/content-discovery__price-index-and-compare.js +73 -0
- package/dist/features/tester/seed-data/authored/content-discovery__site-wide-index.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/content-discovery__site-wide-index.js +340 -0
- package/dist/features/tester/seed-data/authored/index.js +16 -0
- package/dist/features/tester/seed-data/authored/search-and-nav__header-search.d.ts +1 -0
- package/dist/features/tester/seed-data/authored/search-and-nav__header-search.js +57 -0
- package/dist/features/tester/seed-data/authored/selling__spares-and-group-editing.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/selling__spares-and-group-editing.js +77 -0
- package/dist/features/tester/seed-data/authored/tools__sticker-promote-to-listings.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/tools__sticker-promote-to-listings.js +58 -0
- package/dist/features/tester/seed-data/authored/tools__sticker-tracer.js +5 -4
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +384 -18
- package/dist/index.d.ts +17 -57
- package/dist/index.js +35 -96
- package/dist/next/routing/route-map.d.ts +42 -1
- package/dist/next/routing/route-map.js +45 -0
- package/dist/providers/db-firebase/base.repository.d.ts +22 -0
- package/dist/providers/db-firebase/base.repository.js +36 -0
- package/dist/schemas/registry.d.ts +15 -6
- package/dist/security/blind-index-tokens.d.ts +54 -0
- package/dist/security/blind-index-tokens.js +152 -0
- package/dist/security/index.d.ts +1 -1
- package/dist/security/index.js +1 -1
- package/dist/security/settings-encryption.d.ts +35 -0
- package/dist/security/settings-encryption.js +91 -0
- package/dist/seed/_helpers/brand-rows.js +38 -14
- package/dist/seed/_helpers/category-forest.js +73 -7
- package/dist/seed/_helpers/category-images-generated.d.ts +25 -6
- package/dist/seed/_helpers/category-images-generated.js +81 -6
- package/dist/seed/_helpers/category-models.d.ts +1 -1
- package/dist/seed/_helpers/category-models.js +7 -1
- package/dist/seed/_helpers/category-parts-glossary-map.js +8 -0
- package/dist/seed/_helpers/category-parts.d.ts +1 -1
- package/dist/seed/_helpers/category-parts.js +50 -2
- package/dist/seed/_helpers/character-features.d.ts +1 -1
- package/dist/seed/_helpers/character-features.js +2 -1
- package/dist/seed/_helpers/franchise-rows.js +10 -2
- package/dist/seed/_helpers/media.d.ts +47 -0
- package/dist/seed/_helpers/media.js +34 -0
- package/dist/seed/authenticity-knowledge-seed-data.d.ts +49 -5
- package/dist/seed/authenticity-knowledge-seed-data.js +10001 -549
- package/dist/seed/categories-seed-data.js +124 -20
- package/dist/seed/grouped-listings-seed-data.js +16 -0
- package/dist/seed/homepage-sections-seed-data.js +94 -5
- package/dist/seed/index.d.ts +2 -3
- package/dist/seed/index.js +2 -2
- package/dist/seed/manifest.js +3 -0
- package/dist/seed/media-assets-seed-data.d.ts +1 -1
- package/dist/seed/media-assets-seed-data.js +746 -196
- package/dist/seed/model-composition-seed-data.js +122 -6
- package/dist/seed/part-glossary-seed-data.d.ts +1 -1
- package/dist/seed/part-glossary-seed-data.js +37219 -463
- package/dist/seed/products-art-seed-data.js +7 -18
- package/dist/seed/products-auctions-seed-data.js +56 -47
- package/dist/seed/products-beyblade-x-seed-data.d.ts +2 -0
- package/dist/seed/products-beyblade-x-seed-data.js +476 -0
- package/dist/seed/products-catalogue-seed-data.d.ts +5 -5
- package/dist/seed/products-catalogue-seed-data.js +1500 -1240
- package/dist/seed/products-classifieds-seed-data.js +9 -27
- package/dist/seed/products-digital-codes-seed-data.js +9 -18
- package/dist/seed/products-live-items-seed-data.js +5 -13
- package/dist/seed/products-preorders-seed-data.js +67 -33
- package/dist/seed/products-prize-draws-seed-data.js +8 -17
- package/dist/seed/products-spares-seed-data.d.ts +2 -0
- package/dist/seed/products-spares-seed-data.js +368 -0
- package/dist/seed/products-standard-seed-data.js +39 -126
- package/dist/seed/products-stickers-seed-data.js +59 -15
- package/dist/seed/site-settings-seed-data.js +8 -0
- package/dist/seo/json-ld.d.ts +25 -0
- package/dist/seo/json-ld.js +28 -0
- package/dist/server-entry.d.ts +3 -1
- package/dist/server-entry.js +2 -2
- package/dist/server.d.ts +11 -9
- package/dist/server.js +35 -25
- package/dist/styles.css +202 -1
- package/dist/tailwind-input.css +31 -17
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/components/Accordion.js +1 -1
- package/dist/ui/components/Breadcrumb.js +1 -1
- package/dist/ui/components/DetailPageGallery.js +0 -2
- package/dist/ui/components/DetailPageHero.js +0 -1
- package/dist/ui/components/DetailPageTabs.js +1 -1
- package/dist/ui/components/Divider.js +3 -3
- package/dist/ui/components/GatedPrice.js +5 -1
- package/dist/ui/components/HorizontalScroller.js +1 -1
- package/dist/ui/components/ListingLayout.js +3 -3
- package/dist/ui/components/MediaTabs.style.css +81 -0
- package/dist/ui/components/Pagination.js +1 -1
- package/dist/ui/components/PendingValue.d.ts +45 -0
- package/dist/ui/components/PendingValue.js +7 -0
- package/dist/ui/components/Radio.style.css +119 -0
- package/dist/ui/components/RatingDisplay.js +2 -2
- package/dist/ui/components/SettingsSection.js +0 -2
- package/dist/ui/components/SiteLogo.js +3 -0
- package/dist/ui/components/SiteMark.js +3 -0
- package/dist/ui/components/SortDropdown.js +1 -1
- package/dist/ui/components/SpecTable.d.ts +58 -0
- package/dist/ui/components/SpecTable.js +8 -0
- package/dist/ui/components/StarRating.js +1 -3
- package/dist/ui/components/SummaryCard.js +1 -1
- package/dist/ui/components/index.style.css +1 -0
- package/dist/ui/icons/icon-registry.d.ts +1 -0
- package/dist/ui/icons/icon-registry.js +9 -1
- package/dist/ui/index.d.ts +4 -0
- package/dist/ui/index.js +2 -0
- package/dist/utils/search-txt-query.d.ts +12 -0
- package/dist/utils/search-txt-query.js +12 -0
- package/package.json +1 -1
- package/scripts/dedupe-peer-deps.mjs +18 -0
- package/scripts/seed-cli.mjs +250 -35
- package/scripts/verify-css-build.mjs +118 -37
- package/dist/_internal/server/features/search/actions.d.ts +0 -7
- package/dist/_internal/server/features/search/actions.js +0 -21
- package/dist/_internal/server/features/search/data.d.ts +0 -15
- package/dist/_internal/server/features/search/data.js +0 -18
- package/dist/_internal/server/features/search/index.d.ts +0 -3
- package/dist/_internal/server/features/search/index.js +0 -2
- package/dist/features/homepage/api/[id]/route.d.ts +0 -23
- package/dist/features/homepage/api/[id]/route.js +0 -103
- package/dist/features/homepage/api/route.d.ts +0 -14
- package/dist/features/homepage/api/route.js +0 -140
- package/dist/features/homepage/api/sections/[id]/route.d.ts +0 -26
- package/dist/features/homepage/api/sections/[id]/route.js +0 -95
- package/dist/features/homepage/api/sections/route.d.ts +0 -13
- package/dist/features/homepage/api/sections/route.js +0 -82
- package/dist/features/homepage/components/AdvertisementBanner.d.ts +0 -14
- package/dist/features/homepage/components/AdvertisementBanner.js +0 -35
- package/dist/features/homepage/components/BeforeAfterCard.d.ts +0 -9
- package/dist/features/homepage/components/BeforeAfterCard.js +0 -73
- package/dist/features/homepage/components/FeaturedResultsSection.d.ts +0 -17
- package/dist/features/homepage/components/FeaturedResultsSection.js +0 -17
- package/dist/features/homepage/components/HeroBanner.d.ts +0 -7
- package/dist/features/homepage/components/HeroBanner.js +0 -43
- package/dist/features/homepage/components/HeroSection.d.ts +0 -7
- package/dist/features/homepage/components/HeroSection.js +0 -6
- package/dist/features/homepage/components/HomepageSkeleton.d.ts +0 -2
- package/dist/features/homepage/components/HomepageSkeleton.js +0 -21
- package/dist/features/homepage/components/HomepageView.d.ts +0 -25
- package/dist/features/homepage/components/HomepageView.js +0 -5
- package/dist/features/homepage/components/NewsletterBanner.d.ts +0 -23
- package/dist/features/homepage/components/NewsletterBanner.js +0 -37
- package/dist/features/homepage/components/SiteFeaturesSection.d.ts +0 -14
- package/dist/features/homepage/components/SiteFeaturesSection.js +0 -11
- package/dist/features/homepage/components/TrustBadges.d.ts +0 -6
- package/dist/features/homepage/components/TrustBadges.js +0 -46
- package/dist/features/homepage/components/TrustIndicatorsSection.d.ts +0 -12
- package/dist/features/homepage/components/TrustIndicatorsSection.js +0 -11
- package/dist/features/homepage/hooks/useHomepage.d.ts +0 -11
- package/dist/features/homepage/hooks/useHomepage.js +0 -17
- package/dist/features/homepage/hooks/useHomepageSections.d.ts +0 -2
- package/dist/features/homepage/hooks/useHomepageSections.js +0 -10
- package/dist/features/homepage/repository/homepage.repository.d.ts +0 -11
- package/dist/features/homepage/repository/homepage.repository.js +0 -25
- package/dist/features/search/actions/index.d.ts +0 -1
- package/dist/features/search/actions/index.js +0 -1
- package/dist/features/search/actions/search-actions.d.ts +0 -18
- package/dist/features/search/actions/search-actions.js +0 -43
- package/dist/features/search/api/route.d.ts +0 -22
- package/dist/features/search/api/route.js +0 -128
- package/dist/features/search/columns/index.d.ts +0 -17
- package/dist/features/search/columns/index.js +0 -49
- package/dist/features/search/components/SearchFiltersRow.d.ts +0 -22
- package/dist/features/search/components/SearchFiltersRow.js +0 -21
- package/dist/features/search/components/SearchResultsSection.d.ts +0 -53
- package/dist/features/search/components/SearchResultsSection.js +0 -35
- package/dist/features/search/components/SearchView.d.ts +0 -27
- package/dist/features/search/components/SearchView.js +0 -8
- package/dist/features/search/hooks/useSearch.d.ts +0 -21
- package/dist/features/search/hooks/useSearch.js +0 -33
- package/dist/features/search/repository/search.repository.d.ts +0 -14
- package/dist/features/search/repository/search.repository.js +0 -59
- package/dist/features/search/schemas/index.d.ts +0 -70
- package/dist/features/search/schemas/index.js +0 -36
- package/dist/features/search/types/index.d.ts +0 -48
- package/dist/features/stores/components/StoreScopedSearch.d.ts +0 -17
- package/dist/features/stores/components/StoreScopedSearch.js +0 -27
- package/dist/seed/defaults/homepage-sections.d.ts +0 -2
- package/dist/seed/defaults/homepage-sections.js +0 -60
- package/dist/seed/factories/homepage-section.factory.d.ts +0 -10
- package/dist/seed/factories/homepage-section.factory.js +0 -13
- /package/dist/{features/search/types/index.js → _internal/shared/listing-types/spares/og.d.ts} +0 -0
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useCallback, useState } from "react";
|
|
3
|
+
import { useCallback, useMemo, useState } from "react";
|
|
4
4
|
import { useRouter } from "next/navigation.js";
|
|
5
|
-
import { Anchor, Button, GatedPrice, Stack, Text, LoginRequiredModal } from "../../../ui/index.js";
|
|
5
|
+
import { Anchor, Button, GatedPrice, QuantityStepper, Row, Stack, Text, LoginRequiredModal, } from "../../../ui/index.js";
|
|
6
|
+
import { useAddToCart } from "../../cart/hooks/useAddToCart.js";
|
|
7
|
+
import { useToast } from "../../../ui/components/Toast.js";
|
|
8
|
+
import { normalizeError } from "../../../errors/normalize.js";
|
|
6
9
|
import { ROUTES } from "../../../next/routing/route-map.js";
|
|
7
10
|
import { formatCurrency } from "../../../utils/number.formatter.js";
|
|
8
11
|
import { NonRefundableConsentModal } from "./NonRefundableConsentModal.js";
|
|
@@ -13,23 +16,82 @@ import { ACTIONS } from "../../../_internal/shared/actions/action-registry.js";
|
|
|
13
16
|
* Client buy panel for a prize-draw detail page (SB4-G).
|
|
14
17
|
*
|
|
15
18
|
* - Enter Draw → NonRefundableConsentModal (listingType="prize-draw")
|
|
16
|
-
* - On confirm → add
|
|
19
|
+
* - On confirm → add the chosen number of entries to the cart, then /user/cart
|
|
17
20
|
* - "View RNG source" link → prizeGithubFileUrl
|
|
21
|
+
*
|
|
22
|
+
* 🛑 THIS FLOW DID NOT WORK AT ALL UNTIL 2026-10-19, and the docstring above
|
|
23
|
+
* described the behaviour it was supposed to have. `handleConfirm` pushed to
|
|
24
|
+
* `/checkout?directItem=<slug>&type=prize-draw` — and NOTHING anywhere reads
|
|
25
|
+
* `directItem` (CheckoutRouteClient reads only `coupon` and `lane`), and
|
|
26
|
+
* nothing added to the cart. A buyer pressing "Enter draw" landed on a
|
|
27
|
+
* checkout of whatever was already in their basket, with no entry in it.
|
|
28
|
+
* Buying an entry was impossible.
|
|
29
|
+
*
|
|
30
|
+
* 🛑 MULTI-ENTRY NEEDED NO SERVER WORK. `prizeDrawRule.splitMultipleOrders`
|
|
31
|
+
* already chunks a quantity into batches of PRIZE_DRAW_MAX_REVEALS_PER_ORDER
|
|
32
|
+
* (7 entries → [3,3,1]), `enforceMaxPerUserForCart` already caps per buyer, and
|
|
33
|
+
* the rule's preflight already checks `prizeMaxEntries`. The whole server half
|
|
34
|
+
* was written and unreachable because the client never sent a quantity.
|
|
18
35
|
*/
|
|
19
36
|
export function PrizeDrawEntryActions({ productId, productSlug, title, pricePerEntry, currency, remainingEntries, revealStatus, prizeGithubFileUrl, }) {
|
|
20
37
|
const router = useRouter();
|
|
21
38
|
const { requireAuth, isAuthResolving, modalOpen, modalMessage, closeModal } = useAuthGate();
|
|
22
39
|
const [consentOpen, setConsentOpen] = useState(false);
|
|
40
|
+
const [quantity, setQuantity] = useState(1);
|
|
41
|
+
const { showToast } = useToast();
|
|
42
|
+
const { mutate: addToCart, isLoading: adding } = useAddToCart();
|
|
23
43
|
const closed = revealStatus === "closed" || remainingEntries === 0;
|
|
44
|
+
/*
|
|
45
|
+
* Capped by what is LEFT, not by maxPerUser.
|
|
46
|
+
*
|
|
47
|
+
* `remainingEntries` is a fact about the draw and is already on screen.
|
|
48
|
+
* `maxPerUser` is an async per-buyer check (`enforceMaxPerUserForCart` counts
|
|
49
|
+
* their existing orders), so mirroring it here would mean a second, slower
|
|
50
|
+
* source of truth that disagrees with the server whenever an order is in
|
|
51
|
+
* flight — and the server's refusal already names the cap.
|
|
52
|
+
*/
|
|
53
|
+
const maxQuantity = Math.max(1, Math.min(99, remainingEntries || 1));
|
|
24
54
|
const handleEnter = useCallback(() => {
|
|
25
55
|
if (closed)
|
|
26
56
|
return;
|
|
27
57
|
requireAuth(ACTION_ID.ENTER_PRIZE_DRAW, () => setConsentOpen(true));
|
|
28
58
|
}, [closed, requireAuth]);
|
|
29
|
-
const handleConfirm = useCallback(() => {
|
|
59
|
+
const handleConfirm = useCallback(async () => {
|
|
30
60
|
setConsentOpen(false);
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
61
|
+
/*
|
|
62
|
+
* ADD, then route. The previous version only routed — and to a parameter
|
|
63
|
+
* nobody reads. `useAddToCart` falls back to the guest cart on a 401, so
|
|
64
|
+
* this works signed-out too and the entry survives the login merge.
|
|
65
|
+
*/
|
|
66
|
+
try {
|
|
67
|
+
await addToCart({
|
|
68
|
+
productId,
|
|
69
|
+
quantity,
|
|
70
|
+
productTitle: title,
|
|
71
|
+
price: pricePerEntry,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
const n = normalizeError(err);
|
|
76
|
+
/*
|
|
77
|
+
* 🛑 Surfaced, and the ROUTE IS SKIPPED. The add can legitimately fail —
|
|
78
|
+
* the draw sold out between render and confirm, or this buyer is over
|
|
79
|
+
* `maxPerUser`, which is an async per-buyer check the stepper cannot
|
|
80
|
+
* mirror. Routing to the cart anyway would show an empty basket and make
|
|
81
|
+
* a correct refusal look like the same silent nothing this flow did
|
|
82
|
+
* before it was repaired.
|
|
83
|
+
*/
|
|
84
|
+
showToast(n.message, "error");
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
router.push(String(ROUTES.USER.CART));
|
|
88
|
+
}, [addToCart, productId, quantity, title, pricePerEntry, router, showToast]);
|
|
89
|
+
const totalLabel = useMemo(() => formatCurrency(pricePerEntry * quantity, currency), [pricePerEntry, quantity, currency]);
|
|
90
|
+
return (_jsxs(Stack, { gap: "md", children: [_jsx(Text, { size: "2xl", weight: "bold", color: "primary", children: _jsxs(GatedPrice, { label: "Sign in to see the entry price", children: [formatCurrency(pricePerEntry, currency), _jsx(Text, { as: "span", className: "ml-1 text-[var(--appkit-color-text-muted)]", size: "xs", weight: "normal", children: "per entry" })] }) }), !closed ? (_jsxs(Row, { align: "center", justify: "between", gap: "sm", wrap: true, children: [_jsx(Text, { size: "sm", color: "muted", children: "Entries" }), _jsx(QuantityStepper, { value: quantity, onChange: setQuantity, min: 1, max: maxQuantity })] })) : null, !closed && quantity > 1 ? (_jsx(Text, { size: "sm", color: "muted", children: _jsxs(GatedPrice, { label: "Sign in to see the total", children: [quantity, " entries \u00B7 ", totalLabel] }) })) : null, _jsx(Button, { action: closed ? undefined : ACTIONS.PRIZE_DRAW["buy-now"], variant: "primary", size: "md", className: "w-full", disabled: closed || isAuthResolving || adding, onClick: handleEnter, children: closed
|
|
91
|
+
? "Draw closed"
|
|
92
|
+
: isAuthResolving
|
|
93
|
+
? "Checking…"
|
|
94
|
+
: adding
|
|
95
|
+
? "Adding…"
|
|
96
|
+
: undefined }), prizeGithubFileUrl ? (_jsx(Anchor, { href: prizeGithubFileUrl, tone: "none", underline: "hover", size: "xs", weight: "medium", className: "text-left text-primary-600 underline-offset-4 dark:text-primary-400", children: "View RNG source code on GitHub \u2192" })) : null, _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "xs", align: "start", children: "Winners chosen by Node.js crypto.randomInt \u2014 fully auditable. Entries are locked once paid; refunds only if the prize pool is exhausted." }), _jsx(NonRefundableConsentModal, { open: consentOpen, listingType: "prize-draw", itemTitle: title, priceLabel: formatCurrency(pricePerEntry, currency), onCancel: () => setConsentOpen(false), onConfirm: () => void handleConfirm() }), _jsx(LoginRequiredModal, { isOpen: modalOpen, onClose: closeModal, message: modalMessage })] }));
|
|
35
97
|
}
|
|
@@ -24,11 +24,14 @@ const CLS_BUNDLE_ROW = "flex items-center justify-between gap-[var(--appkit-spac
|
|
|
24
24
|
const CLS_BUNDLE_LABEL = "text-[length:var(--appkit-text-xs)] text-success dark:text-success font-medium uppercase tracking-wide mb-0.5";
|
|
25
25
|
const CLS_BUNDLE_CTA = "flex-shrink-0 rounded-lg bg-success-solid hover:brightness-110 px-[var(--appkit-space-3)] py-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-xs)] font-semibold text-success-on-solid transition-colors";
|
|
26
26
|
import { ROUTES } from "../../../next/routing/route-map.js";
|
|
27
|
+
import { productImageUrls } from "../schemas/firestore.js";
|
|
28
|
+
import { PART_SLOT_LABEL } from "../../parts/schemas/firestore.js";
|
|
29
|
+
import { getModelsUsingPart } from "../../../_internal/server/features/parts/data.js";
|
|
27
30
|
import { ctaAnchorProps } from "../../layout/cta-anchor.js";
|
|
28
31
|
import { ContextualTrail } from "../../categories/components/ContextualTrail.js";
|
|
29
32
|
import { productCrumbs, withTrailHead } from "../../categories/utils/breadcrumb-chain.js";
|
|
30
33
|
import { getDefaultCurrency } from "../../../core/baseline-resolver.js";
|
|
31
|
-
import { Button, Container, Div, Heading, Main, PriceDisplay, RichText, Row, Section, Span, Stack, Text, Ul, Ol, Li,
|
|
34
|
+
import { Button, Container, Div, Heading, Main, PriceDisplay, RichText, Row, Section, Span, Stack, Text, Ul, Ol, Li, SpecTable, Icon, } from "../../../ui/index.js";
|
|
32
35
|
import { normalizeRichTextHtml } from "../../../utils/string.formatter.js";
|
|
33
36
|
import { safeDisplayName } from "../../../security/index.js";
|
|
34
37
|
import { ReviewsListingPanel } from "../../reviews/components/ReviewsListingPanel.js";
|
|
@@ -39,7 +42,7 @@ import { ProductFeatureBadges } from "./ProductFeatureBadges.js";
|
|
|
39
42
|
import { isFinalSale } from "../constants/final-sale.js";
|
|
40
43
|
import { FeatureBadgeList } from "./FeatureBadge.js";
|
|
41
44
|
import { ShareButton } from "./ShareButton.js";
|
|
42
|
-
import {
|
|
45
|
+
import { toCustomSectionTabs } from "./CustomSectionTabContent.js";
|
|
43
46
|
import { SublistingCarouselSection } from "./SublistingCarouselSection.js";
|
|
44
47
|
import { ShowGroupSection } from "./ShowGroupSection.js";
|
|
45
48
|
import { HistoryTracker } from "../../history/components/HistoryTracker.js";
|
|
@@ -89,12 +92,56 @@ export async function ProductDetailPageView({ slug, initialProduct, renderOfferA
|
|
|
89
92
|
}
|
|
90
93
|
const p = product;
|
|
91
94
|
const currency = p.currency || getDefaultCurrency();
|
|
95
|
+
/*
|
|
96
|
+
* The REVERSE link: a spare names a glossary entry, so the glossary entry's
|
|
97
|
+
* page should name it back. Fetched only when both conditions hold — most
|
|
98
|
+
* listings carry no `partGlossaryId` at all, and this must not become an
|
|
99
|
+
* extra read on every product page in the catalogue.
|
|
100
|
+
*
|
|
101
|
+
* `getModelsUsingPart` does its own `safeRead`-equivalent internally
|
|
102
|
+
* (catches and logs, returns `[]`), and this wraps it in `safeRead` again
|
|
103
|
+
* anyway — belt-and-braces costs nothing against a function that is already
|
|
104
|
+
* `cache()`-wrapped and this page is the only caller that would ever see a
|
|
105
|
+
* throw from a future change to it.
|
|
106
|
+
*/
|
|
107
|
+
const partGlossaryId = typeof p.partGlossaryId === "string" ? p.partGlossaryId : undefined;
|
|
108
|
+
const fitsModels = partGlossaryId
|
|
109
|
+
? await safeRead(() => getModelsUsingPart(partGlossaryId), {
|
|
110
|
+
route: "/products/[slug]",
|
|
111
|
+
key: "products.fitsModels",
|
|
112
|
+
fallback: [],
|
|
113
|
+
})
|
|
114
|
+
: [];
|
|
115
|
+
/*
|
|
116
|
+
* `(slot, tail)` from the id's OWN composition, `${slot}--${tail}` —
|
|
117
|
+
* mirrors `ModelCompositionPanel`'s private `tailOf`, duplicated rather than
|
|
118
|
+
* imported because that helper is a component-internal detail, not a public
|
|
119
|
+
* export, and pulling in the whole panel module for one split would be the
|
|
120
|
+
* heavier dependency.
|
|
121
|
+
*/
|
|
122
|
+
const partHref = (() => {
|
|
123
|
+
if (!partGlossaryId)
|
|
124
|
+
return undefined;
|
|
125
|
+
const i = partGlossaryId.indexOf("--");
|
|
126
|
+
if (i < 0)
|
|
127
|
+
return undefined;
|
|
128
|
+
const slot = partGlossaryId.slice(0, i);
|
|
129
|
+
const tail = partGlossaryId.slice(i + 2);
|
|
130
|
+
return String(ROUTES.PUBLIC.PART_DETAIL(slot, tail));
|
|
131
|
+
})();
|
|
132
|
+
const partSlotLabel = partGlossaryId
|
|
133
|
+
? PART_SLOT_LABEL[partGlossaryId.slice(0, partGlossaryId.indexOf("--"))]
|
|
134
|
+
: undefined;
|
|
92
135
|
// -- Derived values ---------------------------------------------------------
|
|
93
136
|
const title = String(p.title ?? p.name ?? "");
|
|
94
137
|
const price = typeof p.price === "number" ? p.price : null;
|
|
95
138
|
const originalPrice = typeof p.originalPrice === "number" ? p.originalPrice : null;
|
|
96
|
-
|
|
97
|
-
|
|
139
|
+
// `images` is `ProductImage[]` since A6 — `productImageUrls` is the one
|
|
140
|
+
// normaliser (it also accepts the legacy `string[]`). Casting it to
|
|
141
|
+
// `string[]` handed the gallery objects and rendered a placeholder.
|
|
142
|
+
const galleryUrls = productImageUrls(p.images);
|
|
143
|
+
const images = galleryUrls.length > 0
|
|
144
|
+
? galleryUrls
|
|
98
145
|
: typeof p.mainImage === "string"
|
|
99
146
|
? [p.mainImage]
|
|
100
147
|
: [];
|
|
@@ -103,8 +150,24 @@ export async function ProductDetailPageView({ slug, initialProduct, renderOfferA
|
|
|
103
150
|
const availableQuantity = typeof p.availableQuantity === "number"
|
|
104
151
|
? p.availableQuantity
|
|
105
152
|
: null;
|
|
106
|
-
|
|
107
|
-
|
|
153
|
+
/*
|
|
154
|
+
* Made to order — printed or built when ordered, so there is no stock level
|
|
155
|
+
* to report and no "only N left" to be true.
|
|
156
|
+
*
|
|
157
|
+
* 🛑 `effectiveStock` is deliberately forced to `null` rather than left
|
|
158
|
+
* alone: every scarcity line below is guarded on `effectiveStock !== null`,
|
|
159
|
+
* and a made-to-order row's stored quantity goes NEGATIVE by design (it is
|
|
160
|
+
* the production queue). Leaving it would print "only -4 left — order soon!".
|
|
161
|
+
* One assignment suppresses all four of those branches, which is why this is
|
|
162
|
+
* done here and not at each of them.
|
|
163
|
+
*/
|
|
164
|
+
const madeToOrder = p.madeToOrder === true;
|
|
165
|
+
const effectiveStock = madeToOrder ? null : availableQuantity ?? stockQuantity;
|
|
166
|
+
const inStock = madeToOrder
|
|
167
|
+
? p.status === "published"
|
|
168
|
+
: effectiveStock !== null
|
|
169
|
+
? effectiveStock > 0
|
|
170
|
+
: p.status === "published";
|
|
108
171
|
const avgRating = typeof p.avgRating === "number" ? p.avgRating : null;
|
|
109
172
|
const reviewCount = typeof p.reviewCount === "number" ? p.reviewCount : null;
|
|
110
173
|
/*
|
|
@@ -222,9 +285,6 @@ export async function ProductDetailPageView({ slug, initialProduct, renderOfferA
|
|
|
222
285
|
}))
|
|
223
286
|
: [];
|
|
224
287
|
const allSpecs = [...specs, ...customFields];
|
|
225
|
-
const customSections = Array.isArray(p.customSections)
|
|
226
|
-
? p.customSections
|
|
227
|
-
: [];
|
|
228
288
|
const allowOffers = p.allowOffers === true;
|
|
229
289
|
// Was `p.type === "simple"`. `ProductDocument` has no `type` field at all —
|
|
230
290
|
// the check passed only because the `?? "simple"` fallback made it vacuously
|
|
@@ -298,9 +358,17 @@ export async function ProductDetailPageView({ slug, initialProduct, renderOfferA
|
|
|
298
358
|
? "Refurbished"
|
|
299
359
|
: condition === "used"
|
|
300
360
|
? "Used"
|
|
301
|
-
: condition })), _jsx(Heading, { level: 1, className: "leading-snug", smSize: "2xl", color: "primary", size: "xl", weight: "bold", children: title || "Untitled Product" })] }), avgRating !== null ? (_jsxs(Row, { align: "center", gap: "sm", children: [_jsx(StarRating, { value: avgRating }), _jsxs(Span, { size: "sm", color: "muted", children: [avgRating.toFixed(1), reviewCount ? ` (${reviewCount} reviews)` : ""] }), showViews && (_jsxs(Span, { size: "sm", color: "muted", children: ["\u00B7 ", views7d.toLocaleString(), " viewed this week"] })), _jsxs(Span, { size: "xs", weight: "medium", surface: inStock ? "success-surface" : "danger-surface", className: `ml-auto ${inStock ? "text-success" : "text-error"}`, padding: "pill-sm", rounded: "full", children: [
|
|
361
|
+
: condition })), _jsx(Heading, { level: 1, className: "leading-snug", smSize: "2xl", color: "primary", size: "xl", weight: "bold", children: title || "Untitled Product" })] }), avgRating !== null ? (_jsxs(Row, { align: "center", gap: "sm", children: [_jsx(StarRating, { value: avgRating }), _jsxs(Span, { size: "sm", color: "muted", children: [avgRating.toFixed(1), reviewCount ? ` (${reviewCount} reviews)` : ""] }), showViews && (_jsxs(Span, { size: "sm", color: "muted", children: ["\u00B7 ", views7d.toLocaleString(), " viewed this week"] })), _jsxs(Span, { size: "xs", weight: "medium", surface: inStock ? "success-surface" : "danger-surface", className: `ml-auto ${inStock ? "text-success" : "text-error"}`, padding: "pill-sm", rounded: "full", children: [madeToOrder
|
|
362
|
+
? "✓ Made to order"
|
|
363
|
+
: inStock
|
|
364
|
+
? "✓ In Stock"
|
|
365
|
+
: "✗ Out of Stock", inStock && effectiveStock !== null && effectiveStock <= 10
|
|
302
366
|
? ` — only ${effectiveStock} left`
|
|
303
|
-
: ""] })] })) : (_jsxs(Span, { layout: "inline-flex", gap: "xs", className: `w-fit .5 ${inStock ? CLS_STOCK_IN : CLS_STOCK_OUT}`, rounded: "full", padding: "pill-md", size: "xs", weight: "medium", children: [
|
|
367
|
+
: ""] })] })) : (_jsxs(Span, { layout: "inline-flex", gap: "xs", className: `w-fit .5 ${inStock ? CLS_STOCK_IN : CLS_STOCK_OUT}`, rounded: "full", padding: "pill-md", size: "xs", weight: "medium", children: [madeToOrder
|
|
368
|
+
? "✓ Made to order"
|
|
369
|
+
: inStock
|
|
370
|
+
? "✓ In Stock"
|
|
371
|
+
: "✗ Out of Stock", inStock && effectiveStock !== null && effectiveStock <= 10
|
|
304
372
|
? ` — only ${effectiveStock} left`
|
|
305
373
|
: ""] })), (categorySlugs.length > 0 || subcategory || brand) && (_jsxs(Row, { gap: "sm", wrap: true, children: [categorySlugs.map((slug, i) => (_jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(slug)), className: "inline-flex items-center rounded-full border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] px-[var(--appkit-space-2-5)] py-[var(--appkit-space-1)] text-[length:var(--appkit-text-xs)] font-medium text-[var(--appkit-color-text-muted)] transition-colors hover:border-primary-300 hover:bg-primary-surface hover:text-primary-700 dark:hover:border-primary-700/60 dark:hover:text-primary-400", children: categoryNames[i] ?? slug }, slug))), subcategory && (_jsx(Span, { layout: "inline-flex", size: "xs", weight: "medium", border: "subtle", rounded: "full", padding: "pill-sm-tall", surface: "muted", color: "muted", transform: "capitalize", children: subcategory })), brand && brandSlug && (_jsx(Link, { href: String(ROUTES.PUBLIC.BRAND_DETAIL(brandSlug)), className: "inline-flex items-center rounded-full border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] px-[var(--appkit-space-2-5)] py-[var(--appkit-space-1)] text-[length:var(--appkit-text-xs)] font-medium text-[var(--appkit-color-text-muted)] transition-colors hover:border-primary-300 hover:bg-primary-surface hover:text-primary-700 dark:hover:border-primary-700/60 dark:hover:text-primary-400", children: brand })), brand && !brandSlug && (_jsx(Span, { layout: "inline-flex", size: "xs", weight: "medium", border: "subtle", rounded: "full", padding: "pill-sm-tall", surface: "muted", color: "muted", children: brand }))] })), bundleMemberships.length > 0 && (_jsx(Row, { gap: "sm", wrap: true, children: bundleMemberships.map((b) => (_jsxs(Link, { href: String(ROUTES.PUBLIC.BUNDLE_DETAIL(b.id)), className: CLS_BUNDLE_PILL, children: [_jsx(Span, { "aria-hidden": "true", children: "\uD83D\uDCE6" }), "In bundle: ", b.title] }, b.id))) })), _jsx(ProductFeatureBadges, { featured: featured, freeShipping: freeShipping, codAvailable: codEnabled === true, codHref: String(ROUTES.PUBLIC.HOW_CHECKOUT_WORKS), emiAvailable: emiEnabled === true, emiHref: String(ROUTES.PUBLIC.HOW_EMI_WORKS), condition: condition ?? undefined, returnable: !finalSale, finalSale: finalSale, labels: {
|
|
306
374
|
featured: "Featured",
|
|
@@ -320,7 +388,7 @@ export async function ProductDetailPageView({ slug, initialProduct, renderOfferA
|
|
|
320
388
|
categoryProductCount: (n, cat) => `${n} in ${cat}`,
|
|
321
389
|
} }), productFeatures && features.length > 0 && (_jsx(FeatureBadgeList, { productFeatureIds: features, features: productFeatures })), !productFeatures && features.length > 0 && (_jsxs(Div, { border: "subtle", surface: "muted", padding: "inline", rounded: "xl", children: [_jsx(Text, { className: "mb-2 tracking-wide", color: "muted", size: "xs", weight: "semibold", transform: "uppercase", children: "About this product" }), _jsx(Ul, { spacing: "comfortable", size: "sm", color: "primary", children: features.map((f, i) => (_jsxs(Li, { layout: "flex-start", gap: "2", children: [_jsx(Span, { className: "mt-0.5 flex-shrink-0 text-primary-500", children: "\u2022" }), f] }, i))) })] })), descriptionHtml && (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "text-[length:var(--appkit-text-sm)] leading-relaxed text-[var(--appkit-color-text-muted)] line-clamp-4" })), safeSeller && (_jsx(Div, { className: `${__P.p3}`, border: "subtle", rounded: "xl", surface: "muted", children: _jsxs(Row, { justify: "between", align: "center", children: [_jsxs(Div, { children: [_jsx(Text, { className: "text-[10px] tracking-wide mb-0.5", color: "faint", transform: "uppercase", children: "Sold by" }), _jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: safeSeller }), typeof p.storeRating === "number" && p.storeRating > 0 && (_jsx(Row, { gap: "xs", align: "center", className: "mt-0.5", children: _jsxs(Text, { size: "xs", color: "muted", children: [_jsx(Span, { className: "text-warning", "aria-hidden": "true", children: "\u2605" }), " ", p.storeRating.toFixed(1), typeof p.storeReviewCount === "number" &&
|
|
322
390
|
p.storeReviewCount > 0 &&
|
|
323
|
-
` · ${p.storeReviewCount} reviews`] }) }))] }), _jsxs(Row, { gap: "xs", align: "center", className: "shrink-0", children: [storeId && _jsx(MessageSellerButton, { storeId: storeId, productId: product.id }), storeHref && (_jsx(Link, { href: storeHref, className: "rounded-lg bg-primary/10 dark:bg-primary/20 px-[var(--appkit-space-3)] py-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-xs)] font-semibold text-primary-700 dark:text-primary-300 hover:bg-primary/20 dark:hover:bg-primary/30 transition-colors", children: "Visit Store \u2192" }))] })] }) }))] })), renderActions: () => (_jsxs(Stack, { className: `${__P.p5}`, border: "subtle", gap: "md", rounded: "xl", surface: "muted", children: [price !== null && (_jsxs(Div, { children: [_jsx(PriceDisplay, { amount: price, originalAmount: originalPrice ?? undefined, currency: currency, variant: "detail" }), _jsx(MarketPriceAnchor, { band: priceBand?.byListingType?.[normalizeListingType(p)] ?? null, windowDays: priceBand?.windowDays ?? null, currency: currency, price: price }), inStock && effectiveStock !== null && effectiveStock <= 3 && (_jsxs(Text, { className: "mt-1 text-warning", size: "xs", children: ["Only ", effectiveStock, " left \u2014 order soon!"] })), inStock && effectiveStock !== null && effectiveStock > 3 && effectiveStock <= 10 && (_jsxs(Text, { className: "mt-1", color: "muted", size: "xs", children: [effectiveStock, " in stock"] }))] })), _jsxs(Stack, { gap: "sm", ...ctaAnchorProps(), children: [renderPrimaryActions ? (renderPrimaryActions({
|
|
391
|
+
` · ${p.storeReviewCount} reviews`] }) }))] }), _jsxs(Row, { gap: "xs", align: "center", className: "shrink-0", children: [storeId && _jsx(MessageSellerButton, { storeId: storeId, productId: product.id }), storeHref && (_jsx(Link, { href: storeHref, className: "rounded-lg bg-primary/10 dark:bg-primary/20 px-[var(--appkit-space-3)] py-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-xs)] font-semibold text-primary-700 dark:text-primary-300 hover:bg-primary/20 dark:hover:bg-primary/30 transition-colors", children: "Visit Store \u2192" }))] })] }) }))] })), renderActions: () => (_jsxs(Stack, { className: `${__P.p5}`, border: "subtle", gap: "md", rounded: "xl", surface: "muted", children: [price !== null && (_jsxs(Div, { children: [_jsx(PriceDisplay, { amount: price, originalAmount: originalPrice ?? undefined, currency: currency, variant: "detail" }), _jsx(MarketPriceAnchor, { band: priceBand?.byListingType?.[normalizeListingType(p)] ?? null, windowDays: priceBand?.windowDays ?? null, currency: currency, price: price }), inStock && effectiveStock !== null && effectiveStock <= 3 && (_jsxs(Text, { className: "mt-1 text-warning", size: "xs", children: ["Only ", effectiveStock, " left \u2014 order soon!"] })), inStock && effectiveStock !== null && effectiveStock > 3 && effectiveStock <= 10 && (_jsxs(Text, { className: "mt-1", color: "muted", size: "xs", children: [effectiveStock, " in stock"] })), madeToOrder && (_jsx(Text, { className: "mt-1", color: "muted", size: "xs", children: "Made to order \u2014 printed when you order, so it never runs out." })), partHref && (_jsxs(Text, { className: "mt-1", color: "muted", size: "xs", children: [partSlotLabel ?? "Part", ".", " ", _jsx(Link, { href: partHref, className: "text-primary-600 hover:underline", children: "See what else fits \u2192" }), fitsModels.length > 0 && (_jsxs(_Fragment, { children: [" ", "Fits:", " ", fitsModels.slice(0, 6).map((m, i) => (_jsxs(Text, { as: "span", children: [i > 0 ? ", " : "", m.categoryId ? (_jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(m.categoryId)), className: "text-primary-600 hover:underline", children: m.name })) : (m.name)] }, m.id)))] }))] }))] })), _jsxs(Stack, { gap: "sm", ...ctaAnchorProps(), children: [renderPrimaryActions ? (renderPrimaryActions({
|
|
324
392
|
productId: product.id,
|
|
325
393
|
productSlug: product.slug ?? slug,
|
|
326
394
|
productTitle: product.title,
|
|
@@ -353,11 +421,7 @@ export async function ProductDetailPageView({ slug, initialProduct, renderOfferA
|
|
|
353
421
|
? () => (_jsx(SublistingCarouselSection, { sublistingCategoryId: sublistingCategoryId, currentListingId: String(product.id) }))
|
|
354
422
|
: undefined, renderGroupSection: groupId
|
|
355
423
|
? () => (_jsx(ShowGroupSection, { groupId: groupId, currentSlug: String(p.slug ?? product.id), isParent: isGroupParent, groupTitle: groupTitle }))
|
|
356
|
-
: undefined, renderTabs: () => (_jsx(ProductTabsShell, { partsContent: compositionContent, descriptionContent: descriptionHtml ? (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm sm:prose max-w-none dark:prose-invert", className: "text-[var(--appkit-color-text-muted)]" })) : undefined, specsContent: allSpecs.length > 0 ? (_jsx(
|
|
357
|
-
id: s.id,
|
|
358
|
-
label: s.title,
|
|
359
|
-
content: _jsx(CustomSectionTabContent, { section: s }),
|
|
360
|
-
})) })), renderBundleSection: bundleMemberships.length > 0
|
|
424
|
+
: undefined, renderTabs: () => (_jsx(ProductTabsShell, { partsContent: compositionContent, descriptionContent: descriptionHtml ? (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm sm:prose max-w-none dark:prose-invert", className: "text-[var(--appkit-color-text-muted)]" })) : undefined, specsContent: allSpecs.length > 0 ? (_jsx(SpecTable, { rows: allSpecs })) : undefined, ingredientsContent: ingredients.length > 0 ? (_jsx(Ul, { spacing: "comfortable", children: ingredients.map((item, i) => (_jsxs(Li, { layout: "flex-start", gap: "2", children: [_jsx(Span, { className: "mt-1 flex-shrink-0 h-1.5 w-1.5 bg-primary-400", rounded: "full" }), item] }, i))) })) : undefined, howToUseContent: howToUse.length > 0 ? (_jsx(Ol, { spacing: "loose", children: howToUse.map((step, i) => (_jsxs(Li, { layout: "flex-start", gap: "3", textSize: "sm", color: "muted", children: [_jsx(Span, { layout: "flex-center", size: "xs", weight: "bold", className: "flex-shrink-0 h-6 w-6 bg-primary-100 dark:bg-primary-900/30 text-primary-700 dark:text-primary-300", rounded: "full", children: i + 1 }), step] }, i))) })) : undefined, reviewsContent: _jsx(ReviewsListingPanel, { source: { kind: "product", productId: product.id }, stateMode: "local", context: "listing", initialData: initialReviews, emptyLabel: "No reviews yet \u2014 be the first to review this product." }), customTabs: toCustomSectionTabs(p.customSections) })), renderBundleSection: bundleMemberships.length > 0
|
|
361
425
|
? () => (_jsxs(Div, { className: CLS_BUNDLE_BOX, children: [_jsxs(Row, { className: "mb-3", align: "center", gap: "sm", children: [_jsx(Span, { size: "base", className: CLS_BUNDLE_ICON, "aria-hidden": "true", children: "\uD83D\uDCE6" }), _jsxs(Text, { className: CLS_BUNDLE_TITLE, children: ["Part of ", bundleMemberships.length === 1 ? "a bundle" : `${bundleMemberships.length} bundles`] })] }), _jsx(Stack, { gap: "3", children: bundleMemberships.map((b) => (_jsxs(Div, { className: CLS_BUNDLE_ROW, children: [_jsxs(Div, { className: "min-w-0", children: [_jsx(Text, { className: CLS_BUNDLE_LABEL, children: "Included in bundle" }), _jsx(Text, { className: "truncate", color: "primary", size: "sm", weight: "semibold", children: b.title })] }), _jsx(Link, { href: String(ROUTES.PUBLIC.BUNDLE_DETAIL(b.id)), className: CLS_BUNDLE_CTA, children: "View Bundle \u2192" })] }, b.id))) })] }))
|
|
362
426
|
: undefined, renderRelated: () => (_jsxs(Stack, { gap: "xl", children: [_jsx(GroupedListingsCarousel, { groups: groups }), _jsx(RelatedItemsSection, { relatedItems: relatedItems, relatedByBrand: relatedByBrand, relatedByTags: relatedByTags, relatedByStore: relatedByStore, categoryLabel: categoryNames[0] || categoryName || category || undefined, brandLabel: brand || undefined, storeLabel: storeName || undefined })] })) }), renderPrimaryActions
|
|
363
427
|
? renderPrimaryActions({
|
|
@@ -71,6 +71,18 @@ export interface ProductFormProps {
|
|
|
71
71
|
}) => React.ReactNode;
|
|
72
72
|
/** Render app-specific category selector. */
|
|
73
73
|
renderCategorySelector?: (args: CategorySelectorRenderArgs) => React.ReactNode;
|
|
74
|
+
/**
|
|
75
|
+
* Render the seller-facing price hint (`<PriceGuidanceHint categoryId>`)
|
|
76
|
+
* beside the price field. Omitted when absent — this form has no
|
|
77
|
+
* data-fetching of its own (every app-specific concern is a render-prop),
|
|
78
|
+
* and the hint is seller/admin-only, never mounted for a buyer/public
|
|
79
|
+
* context. The admin path passes nothing here on purpose: pricing an
|
|
80
|
+
* entire category's catalog from one editor is a different workflow than
|
|
81
|
+
* a seller pricing their own single listing.
|
|
82
|
+
*/
|
|
83
|
+
renderPriceGuidance?: (args: {
|
|
84
|
+
categorySlug: string | undefined;
|
|
85
|
+
}) => React.ReactNode;
|
|
74
86
|
/**
|
|
75
87
|
* Render app-specific brand selector (select existing or create new).
|
|
76
88
|
* Receives `multi=true` when brandMode is "mixed".
|
|
@@ -102,5 +114,5 @@ export interface ProductFormProps {
|
|
|
102
114
|
disabled: boolean;
|
|
103
115
|
}) => React.ReactNode;
|
|
104
116
|
}
|
|
105
|
-
export declare function ProductForm({ product, onChange, isReadonly, canEditTax, renderDescriptionEditor, renderCategorySelector, renderBrandSelector, renderStoreAddressSelector, onMediaAbort, currencyPrefix, renderGroupSettings, renderGroupJoinField, }: ProductFormProps): React.JSX.Element;
|
|
117
|
+
export declare function ProductForm({ product, onChange, isReadonly, canEditTax, renderDescriptionEditor, renderCategorySelector, renderPriceGuidance, renderBrandSelector, renderStoreAddressSelector, onMediaAbort, currencyPrefix, renderGroupSettings, renderGroupJoinField, }: ProductFormProps): React.JSX.Element;
|
|
106
118
|
export {};
|
|
@@ -28,6 +28,14 @@ const PRIZE_REVEAL_MODE_OPTIONS = [
|
|
|
28
28
|
// (classified/digital-code/live/prize-draw/art/stickers) to "product".
|
|
29
29
|
const LISTING_TYPE_TO_FEATURE_PRODUCT_TYPE = {
|
|
30
30
|
standard: "product",
|
|
31
|
+
/*
|
|
32
|
+
* 🛑 "product", not a new `ProductFeatureProductType` member. That union is
|
|
33
|
+
* the FEATURE-badge vocabulary ("free shipping", "authenticity checked") and
|
|
34
|
+
* a spare earns the same badges a standard physical item does. Minting a
|
|
35
|
+
* value here would mean every existing feature row stopped matching spares —
|
|
36
|
+
* i.e. a brand-new type with no features at all, which reads as a bug.
|
|
37
|
+
*/
|
|
38
|
+
spares: "product",
|
|
31
39
|
auction: "auction",
|
|
32
40
|
"pre-order": "preorder",
|
|
33
41
|
"prize-draw": "prize-draw",
|
|
@@ -52,7 +60,7 @@ function readFileAsDataUrl(file) {
|
|
|
52
60
|
}
|
|
53
61
|
export function ProductForm({ product, onChange, isReadonly = false,
|
|
54
62
|
/* Defaults to false — the seller path must not reach it. See the prop's docs. */
|
|
55
|
-
canEditTax = false, renderDescriptionEditor, renderCategorySelector, renderBrandSelector, renderStoreAddressSelector, onMediaAbort, currencyPrefix = "", renderGroupSettings, renderGroupJoinField, }) {
|
|
63
|
+
canEditTax = false, renderDescriptionEditor, renderCategorySelector, renderPriceGuidance, renderBrandSelector, renderStoreAddressSelector, onMediaAbort, currencyPrefix = "", renderGroupSettings, renderGroupJoinField, }) {
|
|
56
64
|
const t = useTranslations("adminProducts");
|
|
57
65
|
const { upload } = useMediaUpload();
|
|
58
66
|
const galleryIndexRef = useRef(0);
|
|
@@ -138,7 +146,13 @@ canEditTax = false, renderDescriptionEditor, renderCategorySelector, renderBrand
|
|
|
138
146
|
onValueChange: (value) => update({ brand: value }),
|
|
139
147
|
onValuesChange: (values) => update({ brands: values }),
|
|
140
148
|
disabled: isReadonly,
|
|
141
|
-
})) : product.brandMode === "mixed" ? (_jsx(TagInput, { label: "Brands", value: product.brands || [], onChange: (brands) => update({ brands }), disabled: isReadonly, placeholder: "e.g. Nike, Adidas, Puma" })) : (_jsx(FormField, { name: "brand", label: t("formBrand"), type: "text", value: product.brand || "", onChange: (value) => update({ brand: value }), disabled: isReadonly, placeholder: "e.g. Apple" })) }))] }), _jsxs(FormGroup, { columns: 2, children: [_jsx(Div, {}), " ", _jsx(FormField, { name: "status", label: t("formStatus"), type: "select", value: product.status || "draft", onChange: (value) => update({ status: value }), disabled: isReadonly, options: PRODUCT_STATUS_OPTIONS })] }), _jsxs(FormGroup, { columns: 2, children: [_jsx(FormField, { name: "price", label: t("formPrice"), type: "number", value: String(product.price ?? ""), onChange: (value) => update({ price: Number(value) }), disabled: isReadonly, placeholder: "0" }), _jsx(FormField, { name: "stockQuantity", label: t("formStock"), type: "number", value: String(product.stockQuantity ?? ""), onChange: (value) => update({ stockQuantity: Number(value) }), disabled: isReadonly, placeholder: "0" })] }),
|
|
149
|
+
})) : product.brandMode === "mixed" ? (_jsx(TagInput, { label: "Brands", value: product.brands || [], onChange: (brands) => update({ brands }), disabled: isReadonly, placeholder: "e.g. Nike, Adidas, Puma" })) : (_jsx(FormField, { name: "brand", label: t("formBrand"), type: "text", value: product.brand || "", onChange: (value) => update({ brand: value }), disabled: isReadonly, placeholder: "e.g. Apple" })) }))] }), _jsxs(FormGroup, { columns: 2, children: [_jsx(Div, {}), " ", _jsx(FormField, { name: "status", label: t("formStatus"), type: "select", value: product.status || "draft", onChange: (value) => update({ status: value }), disabled: isReadonly, options: PRODUCT_STATUS_OPTIONS })] }), _jsxs(FormGroup, { columns: 2, children: [_jsx(FormField, { name: "price", label: t("formPrice"), type: "number", value: String(product.price ?? ""), onChange: (value) => update({ price: Number(value) }), disabled: isReadonly, placeholder: "0" }), product.madeToOrder === true ? null : (_jsx(FormField, { name: "stockQuantity", label: t("formStock"), type: "number", value: String(product.stockQuantity ?? ""), onChange: (value) => update({ stockQuantity: Number(value) }), disabled: isReadonly, placeholder: "0" }))] }), renderPriceGuidance?.({
|
|
150
|
+
categorySlug: (product.categorySlugs?.[0] ?? product.category) || undefined,
|
|
151
|
+
}), _jsx(Checkbox, { label: "Made to order",
|
|
152
|
+
/* `suffix`, not a `helperText` prop — Checkbox has no such prop, and the
|
|
153
|
+
* explanation is load-bearing: a seller needs to know this turns stock
|
|
154
|
+
* tracking OFF before they tick it, not after. */
|
|
155
|
+
suffix: _jsx(Text, { size: "xs", color: "muted", children: "Printed or built when someone orders it. Stock is not tracked and the listing never shows as sold out." }), checked: !!product.madeToOrder, onChange: (e) => update({ madeToOrder: e.target.checked }), disabled: isReadonly }), !isReadonly && (_jsx(ImageUpload, { currentImage: product.mainImage, onUpload: (file) => upload(file, "products", true, {
|
|
142
156
|
type: "product-image",
|
|
143
157
|
index: 1,
|
|
144
158
|
name: product.title || "product",
|
|
@@ -15,5 +15,20 @@ export interface ProductsIndexListingProps {
|
|
|
15
15
|
label: string;
|
|
16
16
|
}[];
|
|
17
17
|
searchPlaceholder?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Scope every query to one store, for a store-detail tab.
|
|
20
|
+
*
|
|
21
|
+
* 🛑 Added so a store tab can REUSE this component. There are six
|
|
22
|
+
* hand-written `Store<Type>Listing` components — ~140 lines each of the same
|
|
23
|
+
* toolbar, availability tabs, sticky pagination and card grid — and a seventh
|
|
24
|
+
* copy for spares would push a Rule-of-Three breach from six to seven while
|
|
25
|
+
* being the cheapest possible moment not to. Consolidating the existing six
|
|
26
|
+
* is its own session; not adding to them costs one prop.
|
|
27
|
+
*
|
|
28
|
+
* It OVERRIDES the URL's `storeId` rather than merging: on a store tab the
|
|
29
|
+
* store is the page, so a `?storeId=` from a stale link must not be able to
|
|
30
|
+
* show another seller's stock under this seller's heading.
|
|
31
|
+
*/
|
|
32
|
+
storeId?: string;
|
|
18
33
|
}
|
|
19
|
-
export declare function ProductsIndexListing({ initialData, listingTypes, typeTabs, searchPlaceholder, }: ProductsIndexListingProps): React.JSX.Element;
|
|
34
|
+
export declare function ProductsIndexListing({ initialData, listingTypes, typeTabs, searchPlaceholder, storeId: scopedStoreId, }: ProductsIndexListingProps): React.JSX.Element;
|
|
@@ -67,7 +67,7 @@ function dedicatedPageFor(types) {
|
|
|
67
67
|
label: `Full ${plugin.pluralLabel} filters →`,
|
|
68
68
|
};
|
|
69
69
|
}
|
|
70
|
-
export function ProductsIndexListing({ initialData, listingTypes = GENERIC_PRODUCT_LISTING_TYPES, typeTabs = PRODUCT_TYPE_FILTER_TABS, searchPlaceholder = "Search products...", }) {
|
|
70
|
+
export function ProductsIndexListing({ initialData, listingTypes = GENERIC_PRODUCT_LISTING_TYPES, typeTabs = PRODUCT_TYPE_FILTER_TABS, searchPlaceholder = "Search products...", storeId: scopedStoreId, }) {
|
|
71
71
|
const router = useRouter();
|
|
72
72
|
const table = useUrlTable({ defaults: { pageSize: "24", sort: DEFAULT_SORT } });
|
|
73
73
|
const { showToast } = useToast();
|
|
@@ -155,7 +155,7 @@ export function ProductsIndexListing({ initialData, listingTypes = GENERIC_PRODU
|
|
|
155
155
|
maxPrice: table.get(TABLE_KEYS.MAX_PRICE) ? Number(table.get(TABLE_KEYS.MAX_PRICE)) : undefined,
|
|
156
156
|
condition: table.get(TABLE_KEYS.CONDITION) || undefined,
|
|
157
157
|
brand: table.get(TABLE_KEYS.BRAND) || undefined,
|
|
158
|
-
storeId: table.get(TABLE_KEYS.STORE_ID) || undefined,
|
|
158
|
+
storeId: scopedStoreId || table.get(TABLE_KEYS.STORE_ID) || undefined,
|
|
159
159
|
freeShipping: table.get(TABLE_KEYS.FREE_SHIPPING) === "true" ? true : undefined,
|
|
160
160
|
// These three are in FILTER_KEYS (so they count toward the filter badge)
|
|
161
161
|
// but were never put on the wire — the drawer sections were inert.
|
|
@@ -345,5 +345,5 @@ export function ProductsIndexListing({ initialData, listingTypes = GENERIC_PRODU
|
|
|
345
345
|
] }), totalPages > 1 && (_jsx(StickyToolbar, { offset: "header+pagination", tone: "translucent", border: true, padding: "toolbar", children: _jsx(Row, { justify: "center", children: _jsx(Pagination, { currentPage: page, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) }) })), _jsx(Div, { padding: "y-lg", children: isLoading ? (_jsx(Grid, { cols: "cards", gap: "md", children: Array.from({ length: 10 }).map((_, i) => (_jsxs(Div, { className: `${__O.hidden} animate-pulse`, border: "subtle", rounded: "xl", children: [_jsx(Div, { className: "aspect-square", surface: "subtle" }), _jsxs(Stack, { className: `${__P.p3}`, gap: "sm", children: [_jsx(Div, { className: "h-3 w-3/4", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-3 w-1/2", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-4 w-1/3", surface: "subtle", rounded: "default" })] })] }, i))) })) : (_jsx(ProductGrid, { products: products, getProductHref: (p) => pluginFor(p.listingType ?? "standard").detailRoute(p.slug || p.id), view: view === "grid" ? "card" : "list", onWishlistToggle: handleWishlistToggle, wishlistedIds: wishlistedIds, onAddToCart: handleAddToCart, onBuyNow: handleBuyNow, selectionMode: selection.isSelecting, selectedIds: selection.selectedIdSet, onToggleSelect: selection.toggle })) }), _jsx(CompareOverlay, { isOpen: compareIds.length > 0, productIds: compareIds, productType: "product", onClose: () => {
|
|
346
346
|
setCompareIds([]);
|
|
347
347
|
selection.clearSelection();
|
|
348
|
-
}, onRemove: (id) => setCompareIds((ids) => ids.filter((i) => i !== id)) }), _jsx(FilterDrawer, { open: filterOpen, onOpen: openFilters, onClose: () => setFilterOpen(false), onApply: applyFilters, onReset: onFilterClear, activeCount: filterActiveCount, hideTrigger: true, children: _jsx(ProductFilters, { table: pendingTable, currencyPrefix: "\u20B9", listingTypeOptions: listingTypeOptions, listingTypeFooter: dedicatedPage ? (_jsx(Div, { padding: "x-md", paddingY: "b-sm", children: _jsx(TextLink, { href: dedicatedPage.href, size: "sm", weight: "medium", children: dedicatedPage.label }) })) : null, categoryOptions: categoryOptions, brandOptions: brandOptions, featureOptions: featureOptions }) }), _jsx(LoginRequiredModal, { isOpen: modalOpen, onClose: closeModal, message: modalMessage })] }));
|
|
348
|
+
}, onRemove: (id) => setCompareIds((ids) => ids.filter((i) => i !== id)), onAdd: (id) => setCompareIds((ids) => (ids.includes(id) ? ids : [...ids, id])) }), _jsx(FilterDrawer, { open: filterOpen, onOpen: openFilters, onClose: () => setFilterOpen(false), onApply: applyFilters, onReset: onFilterClear, activeCount: filterActiveCount, hideTrigger: true, children: _jsx(ProductFilters, { table: pendingTable, currencyPrefix: "\u20B9", listingTypeOptions: listingTypeOptions, listingTypeFooter: dedicatedPage ? (_jsx(Div, { padding: "x-md", paddingY: "b-sm", children: _jsx(TextLink, { href: dedicatedPage.href, size: "sm", weight: "medium", children: dedicatedPage.label }) })) : null, categoryOptions: categoryOptions, brandOptions: brandOptions, featureOptions: featureOptions }) }), _jsx(LoginRequiredModal, { isOpen: modalOpen, onClose: closeModal, message: modalMessage })] }));
|
|
349
349
|
}
|
|
@@ -5,6 +5,8 @@ import Link from "next/link.js";
|
|
|
5
5
|
import { Div, PricesOnly, Row, Span, Text, Modal, SideDrawer, Button } from "../../../ui/index.js";
|
|
6
6
|
import { MediaImage } from "../../media/MediaImage.js";
|
|
7
7
|
import { GroupMemberPicker } from "./GroupMemberPicker.js";
|
|
8
|
+
import { resolveGroupMemberImage } from "../utils/group-member-image.js";
|
|
9
|
+
import { readGroupRules } from "../../../_internal/shared/features/products/group-rules.js";
|
|
8
10
|
import { formatCurrency } from "../../../utils/number.formatter.js";
|
|
9
11
|
import { normalizeListingType } from "../utils/listing-type.js";
|
|
10
12
|
import { pluginFor } from "../../../_internal/shared/listing-types/_registry.js";
|
|
@@ -18,7 +20,7 @@ function memberHref(m) {
|
|
|
18
20
|
return pluginFor(normalizeListingType(m)).detailRoute(slug);
|
|
19
21
|
}
|
|
20
22
|
function MemberThumb({ member, isCurrent }) {
|
|
21
|
-
const image = member
|
|
23
|
+
const image = resolveGroupMemberImage(member);
|
|
22
24
|
const href = memberHref(member);
|
|
23
25
|
const price = formatCurrency(member.price, member.currency ?? "INR");
|
|
24
26
|
return (_jsxs(Link, { href: href, "aria-label": member.title, className: "flex flex-col items-center gap-[var(--appkit-space-1-5)] flex-shrink-0 w-16 group", children: [_jsxs(Div, { rounded: "full", className: `relative w-14 h-14 overflow-hidden border-2 transition-all ${isCurrent
|
|
@@ -48,7 +50,18 @@ export function ShowGroupSection({ groupId, currentSlug, isParent, groupTitle })
|
|
|
48
50
|
// The overlay is now a PICKER, not a read-only table: pick how many of each
|
|
49
51
|
// member you want and add the whole selection as one cart line. The picker
|
|
50
52
|
// stands itself down (read-only, no CTA) when the group spans sellers.
|
|
51
|
-
const tableContent = (_jsx(GroupMemberPicker, { groupId: groupId, groupSource: "product-group", members: members,
|
|
53
|
+
const tableContent = (_jsx(GroupMemberPicker, { groupId: groupId, groupSource: "product-group", members: members,
|
|
54
|
+
/*
|
|
55
|
+
* A product group's rules live on the PARENT, and the parent is one of
|
|
56
|
+
* the members this component already fetched — found by `isGroupParent`
|
|
57
|
+
* rather than by position, because the endpoint makes no ordering
|
|
58
|
+
* promise and the parent is not reliably first.
|
|
59
|
+
*
|
|
60
|
+
* 🛑 This is why the five rule fields are in `PRODUCT_PUBLIC_FIELDS`: they
|
|
61
|
+
* reach `toPublicGroupMember` through that allow-list, and without it the
|
|
62
|
+
* picker would silently see no rules and permit what the server refuses.
|
|
63
|
+
*/
|
|
64
|
+
rules: readGroupRules(members.find((m) => m.isGroupParent)), onAdded: () => setShowAll(false), onAuthRequired: () => setShowAll(false) }));
|
|
52
65
|
return (_jsxs(_Fragment, { children: [_jsxs(Div, { className: `${__O.hidden}`, surface: "muted", rounded: "xl", border: "default", children: [_jsxs(Row, { role: "button", tabIndex: 0, onClick: () => setOpen((v) => !v), onKeyDown: (e) => { if (e.key === "Enter" || e.key === " ") {
|
|
53
66
|
e.preventDefault();
|
|
54
67
|
setOpen((v) => !v);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
type SearchParams = Record<string, string | string[]>;
|
|
3
|
+
export interface SparesListViewProps {
|
|
4
|
+
searchParams?: SearchParams;
|
|
5
|
+
}
|
|
6
|
+
export declare function SparesListView({ searchParams }: SparesListViewProps): Promise<React.JSX.Element>;
|
|
7
|
+
export {};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Container, Heading, Main, Section, Text, TextLink } from "../../../ui/index.js";
|
|
3
|
+
import { AdSlot } from "../../homepage/components/AdSlot.js";
|
|
4
|
+
import { ProductsIndexListing } from "./ProductsIndexListing.js";
|
|
5
|
+
import { PRODUCT_FIELDS } from "../../../constants/field-names.js";
|
|
6
|
+
import { sortBy } from "../../../constants/sort.js";
|
|
7
|
+
import { ROUTES } from "../../../next/routing/route-map.js";
|
|
8
|
+
import { listPublicProducts, parsePublicProductParams, defaultAvailabilityForListingTypes, } from "../../../_internal/server/features/products/list-public.js";
|
|
9
|
+
const DEFAULT_PAGE_SIZE = 24;
|
|
10
|
+
const DEFAULT_SORT = sortBy(PRODUCT_FIELDS.CREATED_AT);
|
|
11
|
+
/** The one type this page spans. Named so the two call sites cannot disagree. */
|
|
12
|
+
const SPARES_LISTING_TYPES = ["spares"];
|
|
13
|
+
export async function SparesListView({ searchParams = {} }) {
|
|
14
|
+
/*
|
|
15
|
+
* Filter semantics live in `listPublicProducts`, shared verbatim with
|
|
16
|
+
* `/api/products`, so the SSR first paint and the client's refetch cannot
|
|
17
|
+
* disagree about what the default view means. That matters more here than it
|
|
18
|
+
* looks: every public listing hook sets `staleTime: Infinity` once given SSR
|
|
19
|
+
* `initialData`, so a disagreement is FROZEN for that query key and no
|
|
20
|
+
* refetch ever repairs it (Root Cause #30).
|
|
21
|
+
*/
|
|
22
|
+
const result = await listPublicProducts(parsePublicProductParams(searchParams, {
|
|
23
|
+
listingTypes: SPARES_LISTING_TYPES,
|
|
24
|
+
pageSize: DEFAULT_PAGE_SIZE,
|
|
25
|
+
sorts: DEFAULT_SORT,
|
|
26
|
+
// Derived from the plugin's own `hideDefault`, so this page and
|
|
27
|
+
// `ProductsIndexListing` cannot mirror two different literals.
|
|
28
|
+
...defaultAvailabilityForListingTypes(SPARES_LISTING_TYPES),
|
|
29
|
+
}));
|
|
30
|
+
return (_jsxs(Main, { children: [_jsx(Section, { border: "bottom-subtle", paddingY: "b-md", padding: "t-xl", children: _jsxs(Container, { size: "xl", children: [_jsx(Heading, { level: 1, size: "3xl", weight: "bold", color: "primary", children: "Spares" }), _jsx(Text, { className: "mt-1", color: "muted", size: "sm", children: "Single components off disassembled tops \u2014 attack rings, weight disks, drivers, bits. Sellers list what they have spare; most are one of a kind." }), _jsxs(Text, { className: "mt-2", size: "sm", children: ["Not sure which part you need?", " ", _jsx(TextLink, { href: String(ROUTES.PUBLIC.PARTS), children: "Browse the parts glossary" }), " ", "\u2014 every component, what it does, and which tops use it."] })] }) }), _jsxs(Container, { size: "xl", padding: "x-md", children: [_jsx(AdSlot, { id: "listing-sidebar-top", className: "mb-4 mt-4" }), _jsx(ProductsIndexListing
|
|
31
|
+
// `undefined`, not `null` — React Query treats `null` as defined
|
|
32
|
+
// data, so a failed query would freeze an empty grid forever under
|
|
33
|
+
// `staleTime: Infinity`. Same reasoning as ProductsIndexPageView.
|
|
34
|
+
, {
|
|
35
|
+
// `undefined`, not `null` — React Query treats `null` as defined
|
|
36
|
+
// data, so a failed query would freeze an empty grid forever under
|
|
37
|
+
// `staleTime: Infinity`. Same reasoning as ProductsIndexPageView.
|
|
38
|
+
initialData: result ?? undefined, listingTypes: SPARES_LISTING_TYPES, searchPlaceholder: "Search spares by part, colour or model..." }), _jsx(AdSlot, { id: "listing-sidebar-bottom", className: "mt-8" })] })] }));
|
|
39
|
+
}
|
|
@@ -9,6 +9,7 @@ import { formatCurrency } from "../../../utils/number.formatter.js";
|
|
|
9
9
|
import { normalizeListingType } from "../utils/listing-type.js";
|
|
10
10
|
import { pluginFor } from "../../../_internal/shared/listing-types/_registry.js";
|
|
11
11
|
import { SUBLISTING_CATEGORY_ENDPOINTS } from "../../../constants/api-endpoints.js";
|
|
12
|
+
import { productImageUrls } from "../schemas/firestore.js";
|
|
12
13
|
const __O = {
|
|
13
14
|
hidden: "overflow-hidden",
|
|
14
15
|
xAuto: "overflow-x-auto",
|
|
@@ -18,7 +19,7 @@ function getHref(listing) {
|
|
|
18
19
|
return pluginFor(normalizeListingType(listing)).detailRoute(slug);
|
|
19
20
|
}
|
|
20
21
|
function ListingThumb({ listing, isCurrent, }) {
|
|
21
|
-
const image = listing.images
|
|
22
|
+
const image = productImageUrls(listing.images)[0] || listing.mainImage || "";
|
|
22
23
|
const href = getHref(listing);
|
|
23
24
|
const price = formatCurrency(listing.price, listing.currency ?? "INR");
|
|
24
25
|
return (_jsxs(Link, { href: href, "aria-label": listing.title, className: "flex flex-col items-center gap-[var(--appkit-space-1-5)] flex-shrink-0 w-16 group", children: [_jsx(Div, { rounded: "full", className: `relative w-14 h-14 overflow-hidden border-2 transition-all ${isCurrent
|
|
@@ -411,6 +411,7 @@ export declare const SELLER_BULK_ACTIONS: {
|
|
|
411
411
|
readonly live: readonly ["row-delete"];
|
|
412
412
|
readonly art: readonly ["row-delete"];
|
|
413
413
|
readonly stickers: readonly ["row-delete"];
|
|
414
|
+
readonly spares: readonly ["row-delete"];
|
|
414
415
|
readonly auctions: readonly ["row-delete"];
|
|
415
416
|
readonly preOrders: readonly ["row-delete"];
|
|
416
417
|
readonly prizeDraws: readonly ["row-delete"];
|
|
@@ -566,6 +566,7 @@ export const SELLER_BULK_ACTIONS = {
|
|
|
566
566
|
live: [ROW_ACTION_ID.DELETE],
|
|
567
567
|
art: [ROW_ACTION_ID.DELETE],
|
|
568
568
|
stickers: [ROW_ACTION_ID.DELETE],
|
|
569
|
+
spares: [ROW_ACTION_ID.DELETE],
|
|
569
570
|
auctions: [ROW_ACTION_ID.DELETE],
|
|
570
571
|
preOrders: [ROW_ACTION_ID.DELETE],
|
|
571
572
|
prizeDraws: [ROW_ACTION_ID.DELETE],
|