@mohasinac/appkit 4.53.1 → 4.54.0
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/theme/ThemeProvider.js +15 -1
- package/dist/_internal/server/features/auth/capabilities.js +7 -5
- 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 +22 -6
- package/dist/_internal/server/features/checkout/actions.js +96 -51
- package/dist/_internal/server/features/checkout/index.d.ts +1 -1
- package/dist/_internal/server/features/checkout/index.js +1 -1
- package/dist/_internal/server/features/checkout/supplier-identity.js +28 -6
- package/dist/_internal/server/features/orders/adapters.js +1 -3
- 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/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 +184 -10
- package/dist/_internal/server/features/search/index-build/sources.js +43 -2
- package/dist/_internal/server/features/site-settings/adapters.js +1 -1
- package/dist/_internal/server/features/stores/adapters.d.ts +1 -1
- package/dist/_internal/server/features/stores/adapters.js +0 -1
- package/dist/_internal/server/features/stores/tabs.d.ts +7 -2
- package/dist/_internal/server/features/stores/tabs.js +5 -3
- package/dist/_internal/server/jobs/core/analyticsAlertEvaluate.js +2 -5
- package/dist/_internal/server/jobs/core/jobRunners.js +0 -10
- package/dist/_internal/server/jobs/core/onOrderCreate.js +10 -104
- package/dist/_internal/server/jobs/core/onOrderStatusChange.d.ts +0 -2
- package/dist/_internal/server/jobs/core/onOrderStatusChange.js +0 -1
- package/dist/_internal/server/jobs/core/onScamReportCreate.js +0 -1
- package/dist/_internal/server/jobs/core/onScamReportRejected.js +0 -1
- package/dist/_internal/server/jobs/core/onScamReportVerified.js +0 -1
- package/dist/_internal/server/jobs/core/paymentWindowTimeout.js +0 -1
- package/dist/_internal/server/jobs/core/pendingOrderTimeout.js +0 -1
- package/dist/_internal/server/notifications/channel-health.d.ts +7 -3
- package/dist/_internal/server/notifications/channel-health.js +2 -2
- package/dist/_internal/server/notifications/send-guard.d.ts +1 -1
- package/dist/_internal/server/notifications/send-guard.js +2 -4
- package/dist/_internal/shared/actions/action-registry.d.ts +1 -1
- package/dist/_internal/shared/actions/action-registry.js +128 -28
- package/dist/_internal/shared/checkout/order-math.d.ts +1 -1
- package/dist/_internal/shared/checkout/order-math.js +1 -1
- package/dist/_internal/shared/features/categories/bundle-copy.d.ts +1 -1
- package/dist/_internal/shared/features/checkout/config.d.ts +0 -2
- package/dist/_internal/shared/features/checkout/config.js +0 -2
- package/dist/_internal/shared/features/messaging/config.d.ts +7 -5
- package/dist/_internal/shared/features/messaging/config.js +1 -4
- package/dist/_internal/shared/features/site-settings/fees.d.ts +0 -2
- package/dist/_internal/shared/features/site-settings/fees.js +0 -2
- package/dist/_internal/shared/features/site-settings/supplier-of-record.d.ts +56 -0
- package/dist/_internal/shared/features/site-settings/supplier-of-record.js +57 -0
- package/dist/_internal/shared/fees/calculator.d.ts +0 -8
- package/dist/_internal/shared/fees/calculator.js +0 -7
- package/dist/_internal/shared/listing-types/tiers.d.ts +94 -0
- package/dist/_internal/shared/listing-types/tiers.js +150 -0
- package/dist/_internal/shared/search/match.js +28 -2
- package/dist/cli/index.js +0 -2
- package/dist/client.d.ts +25 -4
- package/dist/client.js +36 -5
- package/dist/constants/api-endpoints.d.ts +28 -15
- package/dist/constants/api-endpoints.js +14 -8
- package/dist/constants/field-names.d.ts +1 -0
- package/dist/constants/field-names.js +1 -0
- package/dist/constants/notification-types.d.ts +1 -1
- package/dist/constants/notification-types.js +1 -1
- package/dist/core/integration-keys.d.ts +5 -5
- package/dist/core/integration-keys.js +21 -10
- package/dist/features/about/components/FeesView.js +0 -6
- package/dist/features/about/components/GuidesHubView.js +15 -2
- package/dist/features/account/components/NotificationPreferencesPanel.js +3 -4
- package/dist/features/account/components/UserOrdersView.js +26 -7
- package/dist/features/account/types/index.d.ts +0 -2
- package/dist/features/addresses/utils/postal-autofill.d.ts +39 -0
- package/dist/features/addresses/utils/postal-autofill.js +54 -0
- package/dist/features/admin/actions/notification-actions.d.ts +2 -8
- package/dist/features/admin/actions/notification-actions.js +9 -104
- package/dist/features/admin/components/AdminGuideHubView.js +1 -8
- package/dist/features/admin/components/AdminOrderEditorView.d.ts +1 -1
- package/dist/features/admin/components/AdminOrdersView.js +0 -2
- package/dist/features/admin/components/AdminSectionsView.js +3 -2
- package/dist/features/admin/components/AdminSiteSettingsView.js +22 -68
- package/dist/features/admin/components/AdminStoreEditorView.js +72 -34
- package/dist/features/admin/components/OwnerQuickActions.d.ts +29 -0
- package/dist/features/admin/components/OwnerQuickActions.js +142 -0
- package/dist/features/admin/components/sections/adminSectionsTypes.js +13 -4
- package/dist/features/admin/components/sections/icon-options.d.ts +21 -0
- package/dist/features/admin/components/sections/icon-options.js +23 -0
- package/dist/features/admin/components/sections/sectionTemplates.js +4 -1
- package/dist/features/admin/constants/site-settings-tabs.d.ts +7 -12
- package/dist/features/admin/constants/site-settings-tabs.js +6 -12
- package/dist/features/admin/repository/site-settings.repository.js +1 -1
- package/dist/features/admin/schemas/admin-editor-forms.d.ts +1 -1
- package/dist/features/admin/schemas/admin-editor-forms.js +4 -1
- package/dist/features/admin/schemas/admin-user-form.d.ts +0 -61
- package/dist/features/admin/schemas/admin-user-form.js +3 -44
- package/dist/features/admin/schemas/firestore.d.ts +28 -54
- package/dist/features/admin/schemas/firestore.js +40 -18
- package/dist/features/auctions/components/AuctionDetailPageView.js +12 -10
- package/dist/features/auctions/components/MarketplaceAuctionCard.d.ts +11 -1
- package/dist/features/auctions/components/MarketplaceAuctionCard.js +9 -2
- package/dist/features/auth/checkout-value-otp.d.ts +1 -1
- package/dist/features/auth/checkout-value-otp.js +1 -1
- package/dist/features/auth/permissions/constants.d.ts +29 -2
- package/dist/features/auth/permissions/constants.js +67 -1
- package/dist/features/blog/components/BlogFeaturedCard.d.ts +11 -1
- package/dist/features/blog/components/BlogFeaturedCard.js +9 -2
- package/dist/features/cart/components/CartDrawer.js +3 -2
- package/dist/features/cart/components/CartGroupLineRow.js +2 -1
- package/dist/features/cart/components/CartPriceBreakdown.d.ts +0 -2
- package/dist/features/cart/components/CartPriceBreakdown.js +1 -1
- package/dist/features/cart/components/StoreAddonsPicker.d.ts +2 -5
- package/dist/features/cart/components/StoreAddonsPicker.js +4 -5
- package/dist/features/cart/hooks/useAddToCart.js +10 -5
- package/dist/features/cart/repository/cart.repository.js +1 -2
- package/dist/features/cart/schemas/firestore.d.ts +1 -2
- package/dist/features/cart/utils/pending-ops.d.ts +11 -1
- package/dist/features/cart/utils/pending-ops.js +14 -2
- package/dist/features/categories/api/[id]/price-guidance/route.js +1 -24
- package/dist/features/categories/components/BrandDetailPageView.js +15 -6
- package/dist/features/categories/components/BrandDetailTabs.d.ts +7 -1
- package/dist/features/categories/components/BrandDetailTabs.js +18 -3
- package/dist/features/categories/components/BundleCollage.js +3 -2
- package/dist/features/categories/components/BundleDetailView.js +2 -1
- package/dist/features/categories/components/BundleItemsPicker.js +3 -4
- package/dist/features/categories/components/CategoryDetailPageView.js +19 -8
- package/dist/features/categories/components/CategoryDetailTabs.d.ts +7 -1
- package/dist/features/categories/components/CategoryDetailTabs.js +29 -7
- package/dist/features/categories/components/CategoryGrid.d.ts +11 -1
- package/dist/features/categories/components/CategoryGrid.js +9 -2
- package/dist/features/checkout/actions/checkout-value-otp-actions.d.ts +10 -5
- package/dist/features/checkout/actions/checkout-value-otp-actions.js +2 -88
- package/dist/features/classified/components/ClassifiedDetailPageView.js +12 -2
- package/dist/features/digital-codes/components/DigitalCodeDetailPageView.js +12 -2
- package/dist/features/events/components/EventCard.d.ts +11 -1
- package/dist/features/events/components/EventCard.js +9 -2
- package/dist/features/grouped/components/GroupedListingDetailView.js +3 -1
- package/dist/features/homepage/components/CTABannerSection.d.ts +3 -1
- package/dist/features/homepage/components/CTABannerSection.js +6 -3
- package/dist/features/homepage/components/HeroCarousel.js +8 -6
- package/dist/features/homepage/components/MarketplaceHomepageView.js +33 -7
- package/dist/features/homepage/components/SectionCarousel.js +13 -5
- package/dist/features/homepage/components/SecurityHighlightsSection.d.ts +8 -3
- package/dist/features/homepage/components/SecurityHighlightsSection.js +2 -2
- package/dist/features/homepage/components/StatsCounterSection.d.ts +9 -4
- package/dist/features/homepage/components/StatsCounterSection.js +2 -2
- package/dist/features/homepage/components/TestimonialsCarousel.js +1 -1
- package/dist/features/homepage/components/TrustFeaturesSection.d.ts +18 -4
- package/dist/features/homepage/components/TrustFeaturesSection.js +5 -5
- package/dist/features/homepage/components/WelcomeSection.d.ts +13 -2
- package/dist/features/homepage/components/WelcomeSection.js +3 -3
- package/dist/features/homepage/components/WhatsAppCommunitySection.d.ts +28 -0
- package/dist/features/homepage/components/WhatsAppCommunitySection.js +42 -6
- package/dist/features/homepage/components/WhatsAppGlyph.d.ts +20 -0
- package/dist/features/homepage/components/WhatsAppGlyph.js +20 -0
- package/dist/features/homepage/constants/hero-carousel-styles.d.ts +8 -6
- package/dist/features/homepage/constants/hero-carousel-styles.js +8 -6
- package/dist/features/homepage/constants/section-copy.d.ts +12 -10
- package/dist/features/homepage/constants/section-copy.js +28 -6
- package/dist/features/homepage/constants/stat-icons.d.ts +12 -0
- package/dist/features/homepage/constants/stat-icons.js +17 -0
- package/dist/features/homepage/constants/trust-icon-aliases.d.ts +8 -0
- package/dist/features/homepage/constants/trust-icon-aliases.js +75 -0
- package/dist/features/homepage/lib/section-defaults.d.ts +16 -1
- package/dist/features/homepage/lib/section-defaults.js +24 -4
- package/dist/features/homepage/lib/section-renderer.js +48 -8
- package/dist/features/homepage/schemas/firestore.d.ts +18 -1
- package/dist/features/layout/AppLayoutShell.js +2 -1
- package/dist/features/layout/BottomActions.d.ts +1 -0
- package/dist/features/layout/BottomActions.js +330 -0
- package/dist/features/layout/BottomChrome.d.ts +45 -0
- package/dist/features/layout/BottomChrome.js +5 -17
- package/dist/features/layout/SideActionDock.js +36 -13
- package/dist/features/layout/index.d.ts +2 -7
- package/dist/features/layout/index.js +7 -7
- package/dist/features/layout/useIsDesktop.d.ts +36 -0
- package/dist/features/layout/useIsDesktop.js +48 -0
- package/dist/features/live/components/LiveItemDetailPageView.js +12 -2
- package/dist/features/orders/actions/refund-actions.js +0 -1
- package/dist/features/orders/components/OrderAddonBadges.d.ts +1 -3
- package/dist/features/orders/components/OrderAddonBadges.js +3 -4
- package/dist/features/orders/components/OrderPaymentSummary.js +15 -2
- package/dist/features/orders/components/YourPurchaseStrip.d.ts +24 -0
- package/dist/features/orders/components/YourPurchaseStrip.js +77 -0
- package/dist/features/orders/components/index.d.ts +2 -1
- package/dist/features/orders/components/index.js +1 -1
- package/dist/features/orders/repository/orders.repository.d.ts +0 -8
- package/dist/features/orders/repository/orders.repository.js +0 -2
- package/dist/features/orders/schemas/firestore.d.ts +17 -9
- package/dist/features/orders/types/index.d.ts +2 -5
- package/dist/features/orders/utils/order-announcement-message.d.ts +18 -0
- package/dist/features/orders/utils/order-announcement-message.js +16 -0
- package/dist/features/orders/utils/payment-proof-message.d.ts +20 -0
- package/dist/features/orders/utils/payment-proof-message.js +17 -0
- package/dist/features/parts/components/PartDetailPageView.js +96 -1
- package/dist/features/parts/schemas/firestore.d.ts +247 -0
- package/dist/features/parts/schemas/firestore.js +6 -6
- package/dist/features/pre-orders/components/MarketplacePreorderCard.d.ts +11 -1
- package/dist/features/pre-orders/components/MarketplacePreorderCard.js +10 -3
- package/dist/features/pre-orders/components/PreOrderDetailPageView.js +12 -10
- package/dist/features/price-index/components/PriceIndexToolView.d.ts +21 -0
- package/dist/features/price-index/components/PriceIndexToolView.js +230 -27
- 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/api/[id]/route.js +8 -1
- package/dist/features/products/api/route.js +16 -4
- 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 +2 -1
- package/dist/features/products/components/CompareOverlay.js +2 -1
- package/dist/features/products/components/GroupSettingsPanel.js +2 -1
- package/dist/features/products/components/InteractiveProductCard.d.ts +7 -1
- package/dist/features/products/components/InteractiveProductCard.js +16 -4
- package/dist/features/products/components/MarketplaceBundleCard.d.ts +11 -1
- package/dist/features/products/components/MarketplaceBundleCard.js +9 -2
- package/dist/features/products/components/MarketplacePrizeDrawCard.d.ts +11 -1
- package/dist/features/products/components/MarketplacePrizeDrawCard.js +9 -2
- package/dist/features/products/components/PrizeDrawDetailPageView.js +2 -1
- package/dist/features/products/components/ProductDetailPageView.js +34 -4
- package/dist/features/products/components/ProductGrid.d.ts +14 -1
- package/dist/features/products/components/ProductGrid.js +22 -5
- package/dist/features/products/components/ProductsIndexListing.js +2 -2
- package/dist/features/products/components/SublistingCarouselSection.js +2 -1
- package/dist/features/products/repository/products.repository.d.ts +1 -0
- package/dist/features/products/repository/products.repository.js +5 -1
- package/dist/features/products/utils/group-member-image.d.ts +9 -2
- package/dist/features/products/utils/group-member-image.js +2 -1
- package/dist/features/reviews/api/[id]/route.js +83 -23
- package/dist/features/reviews/components/PurchaseReviewAffordance.d.ts +6 -0
- package/dist/features/reviews/components/PurchaseReviewAffordance.js +42 -0
- package/dist/features/reviews/components/ReviewFormDrawer.d.ts +34 -0
- package/dist/features/reviews/components/ReviewFormDrawer.js +127 -0
- package/dist/features/reviews/components/index.d.ts +4 -0
- package/dist/features/reviews/components/index.js +2 -0
- package/dist/features/scams/schemas/index.d.ts +10 -10
- package/dist/features/search/action-index/settings-entries.js +0 -9
- package/dist/features/seller/actions/seller-actions.d.ts +1 -1
- package/dist/features/seller/actions/seller-actions.js +49 -3
- package/dist/features/seller/components/SellerPayoutSettingsView.js +30 -2
- package/dist/features/seller/components/SellerProductShell.d.ts +17 -1
- package/dist/features/seller/components/SellerProductsView.js +3 -2
- package/dist/features/seller/hooks/useStoreListingEntitlements.d.ts +8 -0
- package/dist/features/seller/hooks/useStoreListingEntitlements.js +70 -0
- package/dist/features/store-extensions/repository/store-extensions.repositories.d.ts +1 -6
- package/dist/features/store-extensions/repository/store-extensions.repositories.js +1 -10
- package/dist/features/store-extensions/schemas/firestore.d.ts +1 -17
- package/dist/features/store-extensions/schemas/firestore.js +0 -10
- package/dist/features/stores/components/InteractiveStoreCard.d.ts +11 -1
- package/dist/features/stores/components/InteractiveStoreCard.js +9 -2
- package/dist/features/stores/components/StoreAuctionsListing.js +2 -1
- package/dist/features/stores/components/StoreCapabilitiesGuideView.js +0 -1
- package/dist/features/stores/components/StoreGuideHubView.js +2 -8
- package/dist/features/stores/components/StoreSettingsGuideView.js +2 -6
- package/dist/features/stores/components/index.d.ts +0 -2
- package/dist/features/stores/components/index.js +0 -1
- package/dist/features/stores/repository/store.repository.d.ts +1 -26
- package/dist/features/stores/repository/store.repository.js +5 -60
- package/dist/features/stores/schemas/firestore.d.ts +6 -25
- package/dist/features/stores/schemas/firestore.js +6 -3
- package/dist/features/stores/schemas/index.d.ts +1 -87
- package/dist/features/stores/schemas/index.js +10 -15
- package/dist/features/tester/seed-data/authored/admin__site-system.js +0 -48
- package/dist/features/tester/seed-data/authored/buying__buying-checkout.js +0 -40
- package/dist/features/tester/seed-data/authored/content-discovery__authenticity-checker.js +114 -2
- package/dist/features/tester/seed-data/authored/selling__quick-add-minimum-details.js +0 -16
- package/dist/features/tester/seed-data/authored/selling__seller-guide.js +1 -1
- package/dist/features/tester/seed-data/authored/selling__seller-marketing-extras.js +0 -79
- package/dist/features/tester/seed-data/authored/selling__seller-orders.js +1 -1
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +427 -69
- package/dist/index.d.ts +20 -32
- package/dist/index.js +34 -43
- package/dist/next/routing/route-map.d.ts +2 -8
- package/dist/next/routing/route-map.js +21 -4
- package/dist/providers/db-firebase/base.repository.d.ts +1 -1
- package/dist/providers/db-firebase/base.repository.js +1 -1
- package/dist/providers/payment-razorpay/index.d.ts +125 -0
- package/dist/providers/payment-razorpay/index.js +232 -0
- package/dist/providers/payment-razorpay/signature.d.ts +53 -0
- package/dist/providers/payment-razorpay/signature.js +71 -0
- package/dist/providers/shipping-shiprocket/index.d.ts +98 -0
- package/dist/providers/shipping-shiprocket/index.js +356 -0
- package/dist/providers.d.ts +3 -0
- package/dist/providers.js +3 -0
- package/dist/react/hooks/useCanSeePrices.d.ts +0 -27
- package/dist/react/hooks/useCanSeePrices.js +25 -0
- package/dist/repositories/index.d.ts +1 -1
- package/dist/repositories/index.js +1 -1
- package/dist/schemas/registry.d.ts +10 -115
- package/dist/schemas/registry.js +1 -2
- package/dist/security/pii-encrypt.js +1 -1
- package/dist/seed/_helpers/category-forest.js +27 -0
- package/dist/seed/_helpers/category-parts-glossary-map.js +1816 -86
- package/dist/seed/_helpers/category-parts.d.ts +1 -1
- package/dist/seed/_helpers/category-parts.js +13412 -3020
- package/dist/seed/carousel-slides-seed-data.js +47 -10
- package/dist/seed/categories-seed-data.js +48 -6
- package/dist/seed/faq-seed-data.js +2 -2
- package/dist/seed/homepage-sections-seed-data.js +286 -54
- package/dist/seed/index.d.ts +1 -1
- package/dist/seed/index.js +1 -1
- package/dist/seed/manifest.d.ts +1 -1
- package/dist/seed/manifest.js +1 -2
- package/dist/seed/model-composition-seed-data.js +6 -8
- package/dist/seed/part-glossary/chunk0.d.ts +9 -0
- package/dist/seed/part-glossary/chunk0.js +35448 -0
- package/dist/seed/part-glossary/chunk1.d.ts +9 -0
- package/dist/seed/part-glossary/chunk1.js +40494 -0
- package/dist/seed/part-glossary/chunk2.d.ts +9 -0
- package/dist/seed/part-glossary/chunk2.js +51242 -0
- package/dist/seed/part-glossary/chunk3.d.ts +9 -0
- package/dist/seed/part-glossary/chunk3.js +53337 -0
- package/dist/seed/part-glossary/chunk4.d.ts +9 -0
- package/dist/seed/part-glossary/chunk4.js +51454 -0
- package/dist/seed/part-glossary-seed-data.d.ts +1 -1
- package/dist/seed/part-glossary-seed-data.js +23 -47483
- package/dist/seed/products-beyblade-x-seed-data.js +36 -1
- package/dist/seed/site-settings-seed-data.js +34 -35
- package/dist/seed/store-extensions-seed-data.d.ts +1 -2
- package/dist/seed/store-extensions-seed-data.js +1 -23
- package/dist/seed/stores-seed-data.js +0 -1
- package/dist/server-entry.d.ts +1 -1
- package/dist/server-entry.js +1 -1
- package/dist/server.d.ts +10 -10
- package/dist/server.js +24 -22
- package/dist/styles.css +392 -43
- package/dist/tailwind-utilities.css +1 -1
- package/dist/tokens/themes/default-dark.js +1 -1
- package/dist/tokens/themes/default-light.js +2 -1
- package/dist/tokens/tokens.css +6 -2
- package/dist/ui/components/Button.d.ts +11 -0
- package/dist/ui/components/Button.js +11 -0
- package/dist/ui/components/Button.style.css +29 -0
- package/dist/ui/components/CarouselNav.d.ts +57 -0
- package/dist/ui/components/CarouselNav.js +25 -0
- package/dist/ui/components/CarouselNav.style.css +265 -0
- package/dist/ui/components/GatedPrice.js +30 -3
- package/dist/ui/components/GatedPrice.style.css +43 -40
- package/dist/ui/components/HorizontalScroller.d.ts +12 -1
- package/dist/ui/components/HorizontalScroller.js +43 -3
- package/dist/ui/components/ImageMosaic.d.ts +8 -1
- package/dist/ui/components/ImageMosaic.js +3 -2
- package/dist/ui/components/ImageMosaic.style.css +22 -0
- package/dist/ui/components/Layout.style.css +6 -0
- package/dist/ui/components/Semantic.d.ts +3 -2
- package/dist/ui/components/Semantic.js +1 -0
- package/dist/ui/components/Typography.d.ts +2 -0
- package/dist/ui/components/Typography.js +2 -0
- package/dist/ui/components/Typography.style.css +19 -0
- package/dist/ui/components/index.style.css +1 -0
- package/dist/ui/index.d.ts +3 -1
- package/dist/ui/index.js +4 -0
- package/package.json +3 -2
- package/scripts/seed-cli.mjs +133 -27
- package/dist/_internal/server/jobs/core/whatsappCatalogImport.d.ts +0 -27
- package/dist/_internal/server/jobs/core/whatsappCatalogImport.js +0 -172
- package/dist/_internal/server/jobs/core/whatsappNotify.d.ts +0 -35
- package/dist/_internal/server/jobs/core/whatsappNotify.js +0 -140
- package/dist/features/admin/components/AdminWhatsAppGuideView.d.ts +0 -2
- package/dist/features/admin/components/AdminWhatsAppGuideView.js +0 -35
- package/dist/features/orders/components/MarketplaceOrderCard.d.ts +0 -36
- package/dist/features/orders/components/MarketplaceOrderCard.js +0 -57
- package/dist/features/stores/components/StoreWhatsAppGuideView.d.ts +0 -3
- package/dist/features/stores/components/StoreWhatsAppGuideView.js +0 -12
- package/dist/features/whatsapp-bot/api/send-status/route.d.ts +0 -12
- package/dist/features/whatsapp-bot/api/send-status/route.js +0 -38
- package/dist/features/whatsapp-bot/api/webhook/route.d.ts +0 -12
- package/dist/features/whatsapp-bot/api/webhook/route.js +0 -43
- package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.d.ts +0 -6
- package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +0 -270
- package/dist/features/whatsapp-bot/components/WhatsAppChatButton.d.ts +0 -9
- package/dist/features/whatsapp-bot/components/WhatsAppChatButton.js +0 -7
- package/dist/features/whatsapp-bot/components/index.d.ts +0 -3
- package/dist/features/whatsapp-bot/components/index.js +0 -2
- package/dist/features/whatsapp-bot/helpers/payment-proof-message.d.ts +0 -14
- package/dist/features/whatsapp-bot/helpers/payment-proof-message.js +0 -17
- package/dist/features/whatsapp-bot/helpers/whatsapp.d.ts +0 -52
- package/dist/features/whatsapp-bot/helpers/whatsapp.js +0 -283
- package/dist/features/whatsapp-bot/index.d.ts +0 -3
- package/dist/features/whatsapp-bot/index.js +0 -3
- package/dist/features/whatsapp-bot/manifest.d.ts +0 -2
- package/dist/features/whatsapp-bot/manifest.js +0 -13
- package/dist/features/whatsapp-bot/messages/en.json +0 -7
- package/dist/features/whatsapp-bot/server.d.ts +0 -7
- package/dist/features/whatsapp-bot/server.js +0 -7
- package/dist/features/whatsapp-bot/types/index.d.ts +0 -126
- package/dist/features/whatsapp-bot/types/index.js +0 -1
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* WHY: The realised-price tool rendered "No completed sales recorded here yet"
|
|
3
|
+
* for every single lookup, because the only data it read was OUR completed
|
|
4
|
+
* sales and we have none for most leaves. Meanwhile the competitor crawl
|
|
5
|
+
* measured ~60 real bands (`MARKET_BANDS` in
|
|
6
|
+
* `seed/_helpers/category-content.ts`, evidence in
|
|
7
|
+
* `docs/research/competitor-crawl-2026-10/price-bands.md`) which were
|
|
8
|
+
* seeded onto the taxonomy and then read by exactly one surface — the
|
|
9
|
+
* seller form's hint. The tool had a starting point the whole time and
|
|
10
|
+
* never looked at it.
|
|
11
|
+
* WHAT: The MARKET half of the tool, resolved by the shared nearest-first
|
|
12
|
+
* ancestor walk. Signed-in only — see the gate note below.
|
|
13
|
+
*
|
|
14
|
+
* EXPORTS:
|
|
15
|
+
* PriceIndexMarketRow — one leaf's market band, API shape
|
|
16
|
+
* getPriceIndexMarketForIds — N category ids -> their market bands
|
|
17
|
+
*
|
|
18
|
+
* ── 🛑 WHY THIS IS A SEPARATE, AUTHENTICATED ENDPOINT ───────────────────────
|
|
19
|
+
*
|
|
20
|
+
* The sold half (`searchPriceIndex`, `getPriceIndexBandsForIds`) is PUBLIC and
|
|
21
|
+
* edge-cached, deliberately: a day-stale aggregate of our own completed sales
|
|
22
|
+
* is the strongest content we can publish and is not what the guest price gate
|
|
23
|
+
* protects. The market half is the opposite call, and the reasoning is already
|
|
24
|
+
* written down at `categories/api/[id]/price-guidance/route.ts`: publishing a
|
|
25
|
+
* competitor-derived band on an unauthenticated, publicly-cacheable response
|
|
26
|
+
* "would be exactly the 'a neighbouring surface publishes the range' leak that
|
|
27
|
+
* gate exists to close".
|
|
28
|
+
*
|
|
29
|
+
* So the two halves cannot share a response. Merging `market` into
|
|
30
|
+
* `getPriceIndexBandsForIds` would either poison that public cache with
|
|
31
|
+
* viewer-dependent content — one signed-in lookup populating the entry every
|
|
32
|
+
* guest then reads, the exact failure `cachedPublicRead`'s rules exist to
|
|
33
|
+
* prevent — or force the public half to go private and lose its cache. A
|
|
34
|
+
* sibling endpoint keeps both postures correct, and it is the same split the
|
|
35
|
+
* codebase already uses for `description-template` (public) against
|
|
36
|
+
* `price-guidance` (gated).
|
|
37
|
+
*
|
|
38
|
+
* ── 🛑 WHAT IS NOT DONE HERE: the seller tax-basis conversion ───────────────
|
|
39
|
+
*
|
|
40
|
+
* The seller route restates the band on the caller's own `pricesIncludeGst`
|
|
41
|
+
* basis. This one does not, for two reasons. The figures are tax-inclusive
|
|
42
|
+
* (`PRICE_GUIDANCE_TAX_BASIS`) and so is every price a buyer sees anywhere on
|
|
43
|
+
* the site, so for the tool's audience no conversion is the correct answer
|
|
44
|
+
* rather than a missing feature. And the conversion needs a store read per
|
|
45
|
+
* lookup, which on a tool that resolves up to eight leaves at a time would be
|
|
46
|
+
* a per-request cost paid to restate a number most callers want as stored. The
|
|
47
|
+
* renderer labels the basis instead.
|
|
48
|
+
*
|
|
49
|
+
* @tag domain:price-index
|
|
50
|
+
* @tag layer:server
|
|
51
|
+
* @tag pattern:projection
|
|
52
|
+
* @tag access:server-only
|
|
53
|
+
* @tag consumers:api/tools/price-index/market
|
|
54
|
+
* @tag sideEffects:none
|
|
55
|
+
*/
|
|
56
|
+
import { resolveInheritedGuidanceMany } from "../categories/price-guidance-resolve.js";
|
|
57
|
+
import { PRICE_GUIDANCE_TAX_BASIS } from "../../../shared/features/categories/price-guidance.js";
|
|
58
|
+
import { PRICE_INDEX_SEARCH_LIMIT } from "./search.js";
|
|
59
|
+
/**
|
|
60
|
+
* Market bands for the leaves the client already resolved.
|
|
61
|
+
*
|
|
62
|
+
* Bounded by `PRICE_INDEX_SEARCH_LIMIT` — the same cap the public half uses, so
|
|
63
|
+
* a crafted request cannot demand an unbounded read, and the two halves of one
|
|
64
|
+
* lookup can never disagree about how many rows a lookup covers.
|
|
65
|
+
*
|
|
66
|
+
* A leaf with no band at any level is ABSENT from the result rather than
|
|
67
|
+
* present with a null band. "We have no market evidence for this" and "this id
|
|
68
|
+
* does not exist" are different answers and the caller renders them
|
|
69
|
+
* differently; collapsing them is how a missing band becomes a confident ₹0.
|
|
70
|
+
*/
|
|
71
|
+
export async function getPriceIndexMarketForIds(rawIds) {
|
|
72
|
+
const ids = [...new Set(rawIds.filter((id) => typeof id === "string" && id.length > 0))].slice(0, PRICE_INDEX_SEARCH_LIMIT);
|
|
73
|
+
if (ids.length === 0)
|
|
74
|
+
return [];
|
|
75
|
+
const guidanceById = await resolveInheritedGuidanceMany(ids);
|
|
76
|
+
const rows = [];
|
|
77
|
+
for (const id of ids) {
|
|
78
|
+
const resolved = guidanceById.get(id);
|
|
79
|
+
const market = resolved?.guidance.market;
|
|
80
|
+
// No band anywhere up the chain. Omitted, never zeroed.
|
|
81
|
+
if (!market || !resolved)
|
|
82
|
+
continue;
|
|
83
|
+
rows.push({
|
|
84
|
+
categoryId: id,
|
|
85
|
+
median: market.median,
|
|
86
|
+
...(market.p25 === undefined ? {} : { p25: market.p25 }),
|
|
87
|
+
...(market.p75 === undefined ? {} : { p75: market.p75 }),
|
|
88
|
+
...(market.sampleSize === undefined ? {} : { sampleSize: market.sampleSize }),
|
|
89
|
+
...(toIso(market.asOf) ? { asOf: toIso(market.asOf) } : {}),
|
|
90
|
+
...(market.soldOutShare === undefined ? {} : { soldOutShare: market.soldOutShare }),
|
|
91
|
+
...(market.source ? { source: market.source } : {}),
|
|
92
|
+
inherited: resolved.inherited,
|
|
93
|
+
/*
|
|
94
|
+
* Only when inherited. Naming the leaf as its own source would be noise
|
|
95
|
+
* on a card that is already headed by that leaf's name.
|
|
96
|
+
*/
|
|
97
|
+
...(resolved.inherited
|
|
98
|
+
? {
|
|
99
|
+
viaCategoryId: resolved.fromCategoryId,
|
|
100
|
+
...(resolved.fromName ? { viaName: resolved.fromName } : {}),
|
|
101
|
+
}
|
|
102
|
+
: {}),
|
|
103
|
+
taxBasis: PRICE_GUIDANCE_TAX_BASIS,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
return rows;
|
|
107
|
+
}
|
|
108
|
+
/** Firestore `Timestamp` | `Date` | ISO string -> ISO string. */
|
|
109
|
+
function toIso(v) {
|
|
110
|
+
if (!v)
|
|
111
|
+
return undefined;
|
|
112
|
+
if (v instanceof Date)
|
|
113
|
+
return v.toISOString();
|
|
114
|
+
if (typeof v === "string")
|
|
115
|
+
return v;
|
|
116
|
+
const t = v;
|
|
117
|
+
if (typeof t.toDate === "function")
|
|
118
|
+
return t.toDate().toISOString();
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type ViewerLike } from "../tester/visibility";
|
|
2
2
|
import type { JsonValue } from "../../../../schemas/types";
|
|
3
|
+
import type { UnavailableClause } from "../../../shared/listing-types/availability";
|
|
3
4
|
import { type AvailabilityFilter } from "../../../../constants/field-names";
|
|
4
5
|
/** Vercel Hobby Fluid Compute ceiling (CLAUDE.md Rule #6) — never fetch more at once. */
|
|
5
6
|
export declare const PUBLIC_PRODUCT_MAX_PAGE_SIZE = 50;
|
|
@@ -250,6 +251,17 @@ export declare function parsePublicProductParams(params: URLSearchParams | Recor
|
|
|
250
251
|
*/
|
|
251
252
|
availability?: AvailabilityFilter;
|
|
252
253
|
}): PublicProductListInput;
|
|
254
|
+
/** Render one `UnavailableClause` as a Sieve clause against a fixed `now`. */
|
|
255
|
+
/**
|
|
256
|
+
* One unavailable clause → a Sieve fragment, with `"NOW"` bound to this
|
|
257
|
+
* query's timestamp.
|
|
258
|
+
*
|
|
259
|
+
* Exported so `listing-tab-counts` builds its clause queries with THIS
|
|
260
|
+
* function rather than a second copy. A rule reachable by copy-paste is one
|
|
261
|
+
* that gets copied wrong (Root Cause #75), and the two would only diverge the
|
|
262
|
+
* day someone adds a third `op`.
|
|
263
|
+
*/
|
|
264
|
+
export declare function clauseToSieve(clause: UnavailableClause, now: Date): string;
|
|
253
265
|
export declare function listPublicProducts(input: PublicProductListInput, opts?: PublicProductListOptions): Promise<PublicProductListResult | null>;
|
|
254
266
|
/**
|
|
255
267
|
* One store tab's worth of listings.
|
|
@@ -440,7 +440,16 @@ async function defaultExecutor(query) {
|
|
|
440
440
|
};
|
|
441
441
|
}
|
|
442
442
|
/** Render one `UnavailableClause` as a Sieve clause against a fixed `now`. */
|
|
443
|
-
|
|
443
|
+
/**
|
|
444
|
+
* One unavailable clause → a Sieve fragment, with `"NOW"` bound to this
|
|
445
|
+
* query's timestamp.
|
|
446
|
+
*
|
|
447
|
+
* Exported so `listing-tab-counts` builds its clause queries with THIS
|
|
448
|
+
* function rather than a second copy. A rule reachable by copy-paste is one
|
|
449
|
+
* that gets copied wrong (Root Cause #75), and the two would only diverge the
|
|
450
|
+
* day someone adds a third `op`.
|
|
451
|
+
*/
|
|
452
|
+
export function clauseToSieve(clause, now) {
|
|
444
453
|
const value = clause.value === "NOW" ? now.toISOString() : clause.value;
|
|
445
454
|
return clause.op === "lt"
|
|
446
455
|
? sieveFilter(clause.field, SIEVE_OP.LT, value)
|
|
@@ -31,6 +31,28 @@ export interface ListingTabScope {
|
|
|
31
31
|
* `undefined` as "keep the tab visible" (Root Cause #59).
|
|
32
32
|
*/
|
|
33
33
|
export type ListingTabCounts = Record<string, number | undefined>;
|
|
34
|
+
/**
|
|
35
|
+
* The two numbers a tab bar needs, and they are NOT the same number.
|
|
36
|
+
*
|
|
37
|
+
* 🛑 A badge showing `total` while the panel below it defaults to the
|
|
38
|
+
* AVAILABLE scope is a tab that advertises rows it will not render. That was
|
|
39
|
+
* live: a category holding one `prizeRevealStatus: "closed"` draw showed
|
|
40
|
+
* "Prize Draws 1" above "No prize draws found." — accurate counting of the
|
|
41
|
+
* wrong question, Root Cause #72's family.
|
|
42
|
+
*
|
|
43
|
+
* `available` — what the default scope will render. The BADGE.
|
|
44
|
+
* `total` — every published row in scope, across all availability
|
|
45
|
+
* scopes. The hide-at-zero decision, and the "N ended —
|
|
46
|
+
* view All" hint under an empty panel.
|
|
47
|
+
*
|
|
48
|
+
* 🛑 Hiding must stay keyed on `total`. A tab whose rows have all ended still
|
|
49
|
+
* has a browsable archive behind the Ended/All scopes; hiding it at
|
|
50
|
+
* `available === 0` would make that archive unreachable.
|
|
51
|
+
*/
|
|
52
|
+
export interface ListingTabCountResult {
|
|
53
|
+
available: ListingTabCounts;
|
|
54
|
+
total: ListingTabCounts;
|
|
55
|
+
}
|
|
34
56
|
/**
|
|
35
57
|
* Count every tab in `tabs` within `scope`, in one parallel batch.
|
|
36
58
|
*
|
|
@@ -41,4 +63,6 @@ export type ListingTabCounts = Record<string, number | undefined>;
|
|
|
41
63
|
* `stores` is not counted here: only the category page has that tab and it
|
|
42
64
|
* already resolves its store list eagerly to render the tab body.
|
|
43
65
|
*/
|
|
44
|
-
export declare function listingTabCounts(tabs: readonly ListingTab[], scope: ListingTabScope
|
|
66
|
+
export declare function listingTabCounts(tabs: readonly ListingTab[], scope: ListingTabScope, opts?: {
|
|
67
|
+
now?: Date;
|
|
68
|
+
}): Promise<ListingTabCountResult>;
|
|
@@ -11,9 +11,10 @@
|
|
|
11
11
|
* the tab bar renders, so a tenth listing type is counted automatically.
|
|
12
12
|
*
|
|
13
13
|
* EXPORTS:
|
|
14
|
-
* ListingTabScope
|
|
15
|
-
* ListingTabCounts
|
|
16
|
-
*
|
|
14
|
+
* ListingTabScope — the surface's scope (category / brand / store)
|
|
15
|
+
* ListingTabCounts — tabSlug -> count, `undefined` meaning "unknown"
|
|
16
|
+
* ListingTabCountResult — the AVAILABLE and TOTAL maps, see below
|
|
17
|
+
* listingTabCounts — resolve every tab's counts in one parallel batch
|
|
17
18
|
*
|
|
18
19
|
* @tag domain:products,categories,stores
|
|
19
20
|
* @tag layer:server
|
|
@@ -25,6 +26,11 @@
|
|
|
25
26
|
import { categoriesRepository, productRepository } from "../../../../repositories/index.js";
|
|
26
27
|
import { serverLogger } from "../../../../monitoring/index.js";
|
|
27
28
|
import { normalizeError } from "../../../../errors/normalize.js";
|
|
29
|
+
import { sieveAnd, sieveFilter, SIEVE_OP, sortBy } from "../../../../utils/sieve-builder.js";
|
|
30
|
+
import { PRODUCT_FIELDS } from "../../../../constants/field-names.js";
|
|
31
|
+
import { isListingRowAvailable, pluginFor, } from "../../../shared/listing-types/_registry.js";
|
|
32
|
+
import { normalizeListingType } from "../../../../features/products/utils/listing-type.js";
|
|
33
|
+
import { clauseToSieve, PUBLIC_PRODUCT_MAX_PAGE_SIZE } from "./list-public.js";
|
|
28
34
|
const DEFAULT_STATUS = "published";
|
|
29
35
|
/** Bundles a category owns, resolved by the same rule the bundles tab renders. */
|
|
30
36
|
async function countBundles(scope) {
|
|
@@ -65,6 +71,154 @@ function bundleMatchesScope(bundle, scope) {
|
|
|
65
71
|
}
|
|
66
72
|
return true;
|
|
67
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* How many rows in scope are NOT available, per listing type.
|
|
76
|
+
*
|
|
77
|
+
* ── 🛑 WHY THIS IS NOT AN AGGREGATE COUNT ─────────────────────────────────
|
|
78
|
+
*
|
|
79
|
+
* Two independent reasons, either of which alone is disqualifying.
|
|
80
|
+
*
|
|
81
|
+
* 1. Unavailability is an OR across DIFFERENT fields (`availableQuantity == 0`
|
|
82
|
+
* OR `isSold == true` OR `prizeRevealStatus == closed` …). Firestore cannot
|
|
83
|
+
* express that in one query, and counting each clause separately and adding
|
|
84
|
+
* them double-counts every row matching two of them.
|
|
85
|
+
*
|
|
86
|
+
* 2. A clause selects a SUPERSET, deliberately. `madeToOrder` is pointedly NOT
|
|
87
|
+
* a clause (see the note in `availability.ts`), so a print-on-demand sticker
|
|
88
|
+
* sitting at `availableQuantity: 0` matches `availableQuantity == 0` and is
|
|
89
|
+
* nevertheless AVAILABLE. Only `isListingRowAvailable` knows that, and it is
|
|
90
|
+
* a per-row predicate. Measured on the category that prompted this work,
|
|
91
|
+
* a clause-count would have badged "Art & Stickers 2" over three available
|
|
92
|
+
* rows — trading one wrong number for another.
|
|
93
|
+
*
|
|
94
|
+
* So this is Path C of `listPublicProducts` reused in miniature: one bounded
|
|
95
|
+
* query per DISTINCT clause, in parallel, merged and deduped by id, then every
|
|
96
|
+
* surviving row confirmed with the real predicate. Nine listing types collapse
|
|
97
|
+
* to five clauses, so the whole tab bar costs ~5 × 50 documents in one parallel
|
|
98
|
+
* round — the same fan-out and the same budget that scope already spends.
|
|
99
|
+
*
|
|
100
|
+
* 🛑 The queries are NOT narrowed to the tab's listing types. Firestore permits
|
|
101
|
+
* one array operator per query and the category scope already spends it on
|
|
102
|
+
* `categorySlugs array-contains-any`, so adding `listingType in (…)` risks an
|
|
103
|
+
* INVALID_ARGUMENT for no gain: unavailable rows are sparse, and tallying each
|
|
104
|
+
* row by its own `listingType` afterwards answers every tab from one pass.
|
|
105
|
+
*/
|
|
106
|
+
async function unavailableByType(scope, types, now) {
|
|
107
|
+
const byType = new Map();
|
|
108
|
+
const unknown = new Set();
|
|
109
|
+
/*
|
|
110
|
+
* One entry per DISTINCT clause, remembering which types depend on it, so a
|
|
111
|
+
* clause that cannot run takes down only those types' badges.
|
|
112
|
+
*/
|
|
113
|
+
const distinct = new Map();
|
|
114
|
+
for (const type of types) {
|
|
115
|
+
for (const clause of pluginFor(type).unavailableClauses) {
|
|
116
|
+
const key = clause.field + "|" + clause.op + "|" + String(clause.value);
|
|
117
|
+
const entry = distinct.get(key) ?? { clause, types: new Set() };
|
|
118
|
+
entry.types.add(type);
|
|
119
|
+
distinct.set(key, entry);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (distinct.size === 0)
|
|
123
|
+
return { byType, unknown };
|
|
124
|
+
const status = scope.status ?? DEFAULT_STATUS;
|
|
125
|
+
const results = await Promise.all([...distinct.values()].map(async (entry) => {
|
|
126
|
+
const { clause } = entry;
|
|
127
|
+
/*
|
|
128
|
+
* 🛑 NO SORT on an equality clause — load-bearing, not an omission.
|
|
129
|
+
*
|
|
130
|
+
* A tally does not care about order, and adding `orderBy createdAt`
|
|
131
|
+
* turns a query Firestore can merge-join from single-field indexes into
|
|
132
|
+
* one demanding a composite index PER CLAUSE PER SCOPE: fifteen new
|
|
133
|
+
* indexes on a collection already carrying 223, every one of them write
|
|
134
|
+
* amplification on every product write. Measured against the live
|
|
135
|
+
* project: all four equality clauses run index-free without the sort and
|
|
136
|
+
* return FAILED_PRECONDITION with it.
|
|
137
|
+
*
|
|
138
|
+
* An inequality is the exception Firestore forces — its first `orderBy`
|
|
139
|
+
* must be its own field (Root Cause #59) — so that one clause does need
|
|
140
|
+
* an index, which is exactly why `unknown` is tracked per type.
|
|
141
|
+
*
|
|
142
|
+
* 🛑 THE AUCTION INDEXES ARE NOT DECLARED, deliberately. Until they are,
|
|
143
|
+
* the auction clause returns FAILED_PRECONDITION, `unknown` gains
|
|
144
|
+
* `auction`, and that ONE badge falls back to its total — every other
|
|
145
|
+
* type stays exact. Adding them to
|
|
146
|
+
* `appkit/firebase/base/firestore.indexes.json` is only half the change:
|
|
147
|
+
* an undeployed index file fails `audit-firebase-rules-deployed`, so the
|
|
148
|
+
* three entries below and `npm run firebase -- deploy --only
|
|
149
|
+
* firestore:indexes` belong in the same session.
|
|
150
|
+
*
|
|
151
|
+
* products: status ASC, categorySlugs CONTAINS, auctionEndDate DESC
|
|
152
|
+
* products: brand ASC, status ASC, auctionEndDate DESC
|
|
153
|
+
* products: storeId ASC, status ASC, auctionEndDate DESC
|
|
154
|
+
*
|
|
155
|
+
* One per scope the tab bars count in, because `buildScopedQuery` leads
|
|
156
|
+
* with a different equality for each.
|
|
157
|
+
*/
|
|
158
|
+
const sorts = clause.op === "lt" && clause.sortField
|
|
159
|
+
? sortBy(clause.sortField, "DESC")
|
|
160
|
+
: undefined;
|
|
161
|
+
try {
|
|
162
|
+
const result = await productRepository.list({
|
|
163
|
+
filters: sieveAnd(sieveFilter(PRODUCT_FIELDS.STATUS, SIEVE_OP.EQ, status), clauseToSieve(clause, now)),
|
|
164
|
+
...(sorts ? { sorts } : {}),
|
|
165
|
+
page: 1,
|
|
166
|
+
pageSize: PUBLIC_PRODUCT_MAX_PAGE_SIZE,
|
|
167
|
+
}, {
|
|
168
|
+
storeId: scope.storeId,
|
|
169
|
+
categoriesIn: scope.categoriesIn,
|
|
170
|
+
brandName: scope.brandName,
|
|
171
|
+
});
|
|
172
|
+
return { entry, result };
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
const e = normalizeError(error);
|
|
176
|
+
// ERROR, not warn: a clause that cannot run is almost always a missing
|
|
177
|
+
// composite index — a permanent, silent skew of the badges that depend
|
|
178
|
+
// on it rather than a transient blip. The fields are named so the log
|
|
179
|
+
// says which index to create.
|
|
180
|
+
serverLogger.error("listingTabCounts: unavailable clause failed; those badges fall back to totals", {
|
|
181
|
+
field: clause.field,
|
|
182
|
+
op: clause.op,
|
|
183
|
+
types: [...entry.types],
|
|
184
|
+
error: e.message,
|
|
185
|
+
});
|
|
186
|
+
return { entry, result: null };
|
|
187
|
+
}
|
|
188
|
+
}));
|
|
189
|
+
const merged = new Map();
|
|
190
|
+
for (const { entry, result } of results) {
|
|
191
|
+
if (!result) {
|
|
192
|
+
for (const type of entry.types)
|
|
193
|
+
unknown.add(type);
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
const rows = result.items;
|
|
197
|
+
// A saturated window means there are more unavailable rows than were seen,
|
|
198
|
+
// so the tally is a floor for every type this clause serves.
|
|
199
|
+
if (rows.length >= PUBLIC_PRODUCT_MAX_PAGE_SIZE) {
|
|
200
|
+
for (const type of entry.types)
|
|
201
|
+
unknown.add(type);
|
|
202
|
+
}
|
|
203
|
+
for (const row of rows) {
|
|
204
|
+
const id = typeof row.id === "string" ? row.id : JSON.stringify(row.id);
|
|
205
|
+
if (!merged.has(id))
|
|
206
|
+
merged.set(id, row);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
for (const row of merged.values()) {
|
|
210
|
+
// The clause matched a superset; this is the real answer.
|
|
211
|
+
if (isListingRowAvailable(row, now))
|
|
212
|
+
continue;
|
|
213
|
+
const type = normalizeListingType(row);
|
|
214
|
+
byType.set(type, (byType.get(type) ?? 0) + 1);
|
|
215
|
+
}
|
|
216
|
+
return { byType, unknown };
|
|
217
|
+
}
|
|
218
|
+
/** The listing types one tab spans — `"art|stickers"` is a pipe OR-group. */
|
|
219
|
+
function typesOf(tab) {
|
|
220
|
+
return String(tab.listingType).split("|").filter(Boolean);
|
|
221
|
+
}
|
|
68
222
|
/**
|
|
69
223
|
* Count every tab in `tabs` within `scope`, in one parallel batch.
|
|
70
224
|
*
|
|
@@ -75,10 +229,12 @@ function bundleMatchesScope(bundle, scope) {
|
|
|
75
229
|
* `stores` is not counted here: only the category page has that tab and it
|
|
76
230
|
* already resolves its store list eagerly to render the tab body.
|
|
77
231
|
*/
|
|
78
|
-
export async function listingTabCounts(tabs, scope) {
|
|
232
|
+
export async function listingTabCounts(tabs, scope, opts) {
|
|
233
|
+
const now = opts?.now ?? new Date();
|
|
79
234
|
const listingTabs = tabs.filter((tab) => Boolean(tab.listingType));
|
|
80
235
|
const wantsBundles = tabs.some((tab) => tab.collection === "bundles");
|
|
81
|
-
const
|
|
236
|
+
const allTypes = [...new Set(listingTabs.flatMap(typesOf))];
|
|
237
|
+
const [typeCounts, bundleCount, unavailable] = await Promise.all([
|
|
82
238
|
productRepository.countByListingTypes(listingTabs.map((tab) => String(tab.listingType)), {
|
|
83
239
|
status: scope.status ?? DEFAULT_STATUS,
|
|
84
240
|
storeId: scope.storeId,
|
|
@@ -86,12 +242,30 @@ export async function listingTabCounts(tabs, scope) {
|
|
|
86
242
|
brandName: scope.brandName,
|
|
87
243
|
}),
|
|
88
244
|
wantsBundles ? countBundles(scope) : Promise.resolve(undefined),
|
|
245
|
+
unavailableByType(scope, allTypes, now),
|
|
89
246
|
]);
|
|
90
|
-
const
|
|
247
|
+
const total = {};
|
|
248
|
+
const available = {};
|
|
91
249
|
for (const tab of listingTabs) {
|
|
92
|
-
|
|
250
|
+
const tabTypes = typesOf(tab);
|
|
251
|
+
const totalForTab = typeCounts[String(tab.listingType)];
|
|
252
|
+
total[tab.id] = totalForTab;
|
|
253
|
+
if (totalForTab === undefined ||
|
|
254
|
+
tabTypes.some((t) => unavailable.unknown.has(t))) {
|
|
255
|
+
// Falling back to the total reproduces the pre-split badge rather than
|
|
256
|
+
// inventing a number — and it keeps the hint silent, because the hint
|
|
257
|
+
// only speaks when both figures are known.
|
|
258
|
+
available[tab.id] = totalForTab;
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
const gone = tabTypes.reduce((sum, type) => sum + (unavailable.byType.get(type) ?? 0), 0);
|
|
262
|
+
available[tab.id] = Math.max(0, totalForTab - gone);
|
|
263
|
+
}
|
|
264
|
+
if (wantsBundles) {
|
|
265
|
+
// A bundle has no availability scope of its own — the bundles tab renders
|
|
266
|
+
// active bundle ROWS, not products, so the two numbers are one number.
|
|
267
|
+
total.bundles = bundleCount;
|
|
268
|
+
available.bundles = bundleCount;
|
|
93
269
|
}
|
|
94
|
-
|
|
95
|
-
counts.bundles = bundleCount;
|
|
96
|
-
return counts;
|
|
270
|
+
return { available, total };
|
|
97
271
|
}
|
|
@@ -195,7 +195,26 @@ export const PUBLIC_INDEX_SOURCES = [
|
|
|
195
195
|
{
|
|
196
196
|
typeKey: "part",
|
|
197
197
|
collection: "partGlossary",
|
|
198
|
-
|
|
198
|
+
/*
|
|
199
|
+
* 🛑 `aliases` IS IN THE SELECT BECAUSE AN ABBREVIATION IS HOW PEOPLE
|
|
200
|
+
* SEARCH FOR A PART.
|
|
201
|
+
*
|
|
202
|
+
* The wiki states a part's short form outright, and it reaches 1,072 of
|
|
203
|
+
* 2,490 rows: `EWD` is Eternal Wide Defense, `BWD` is Big Wide Defense,
|
|
204
|
+
* `ES` is Eternal Spike. Those are the names printed on the part and the
|
|
205
|
+
* names the community uses — but they lived only in Firestore, because
|
|
206
|
+
* this select carried name/code/classification/slot and nothing else.
|
|
207
|
+
*
|
|
208
|
+
* So the data was there, the page was there, and searching `EWD` found
|
|
209
|
+
* NOTHING. A gap between holding a fact and being able to find it is
|
|
210
|
+
* invisible from either end.
|
|
211
|
+
*
|
|
212
|
+
* 🛑 It must be `aliases`, not `aka`. The generator UNIONS the wiki's
|
|
213
|
+
* `aka` into the pre-existing `aliases` array rather than replacing it,
|
|
214
|
+
* so `aliases` is the superset — selecting `aka` would silently drop
|
|
215
|
+
* every alias the corpus contributed.
|
|
216
|
+
*/
|
|
217
|
+
select: ["name", "code", "classification", "slot", "aliases"],
|
|
199
218
|
slugOf: (id) => (id.includes("--") ? id.replace("--", "/") : null),
|
|
200
219
|
toRecord: (id, d) => {
|
|
201
220
|
const name = str(d.name);
|
|
@@ -214,11 +233,33 @@ export const PUBLIC_INDEX_SOURCES = [
|
|
|
214
233
|
return null;
|
|
215
234
|
const code = str(d.code);
|
|
216
235
|
const classification = str(d.classification);
|
|
236
|
+
/*
|
|
237
|
+
* The abbreviations, appended to `detail` because that is the only
|
|
238
|
+
* other field the record carries and the matcher reads both.
|
|
239
|
+
*
|
|
240
|
+
* 🛑 Bounded to four and de-duplicated against the name and code. A
|
|
241
|
+
* part can carry a dozen aliases, and the artifact is a public file
|
|
242
|
+
* fetched by every visitor — the whole point of it is that it stays
|
|
243
|
+
* small enough to ship whole. Four covers the abbreviation plus the
|
|
244
|
+
* other vendor's name, which is what anyone actually types.
|
|
245
|
+
*/
|
|
246
|
+
const aliases = Array.isArray(d.aliases) ? d.aliases.map(str).filter(Boolean) : [];
|
|
247
|
+
const seen = new Set([name.toLowerCase(), (code ?? "").toLowerCase()]);
|
|
248
|
+
const extra = [];
|
|
249
|
+
for (const a of aliases) {
|
|
250
|
+
const k = String(a).toLowerCase();
|
|
251
|
+
if (seen.has(k))
|
|
252
|
+
continue;
|
|
253
|
+
seen.add(k);
|
|
254
|
+
extra.push(String(a));
|
|
255
|
+
if (extra.length === 4)
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
217
258
|
return {
|
|
218
259
|
title: name,
|
|
219
260
|
// The classification IS the disambiguation — it is why a user
|
|
220
261
|
// searching "105" can tell three results apart.
|
|
221
|
-
detail: [classification, code].filter(Boolean).join(" · "),
|
|
262
|
+
detail: [classification, code, ...extra].filter(Boolean).join(" · "),
|
|
222
263
|
};
|
|
223
264
|
},
|
|
224
265
|
},
|
|
@@ -7,7 +7,7 @@ export const PUBLIC_SITE_SETTINGS_FIELDS = [
|
|
|
7
7
|
"contact", // user/orders/[id]/payment — contact.whatsappNumber (narrowed: no address/upiVpa)
|
|
8
8
|
"payment", // CheckoutRouteClient — payment.otpCheckoutThreshold (narrowed)
|
|
9
9
|
"listings", // useListingTypeFlags — narrowed to listings.listingTypes; categoryTypes has no client reader
|
|
10
|
-
"notificationChannels", // NotificationPreferencesPanel
|
|
10
|
+
"notificationChannels", // NotificationPreferencesPanel reads the per-channel toggles
|
|
11
11
|
"announcementBar", // the homepage banner (AdminSiteSettingsView edits it)
|
|
12
12
|
"navConfig", // NavbarWithSettings, DashboardLayoutClient
|
|
13
13
|
"actionConfig", // useAuthGate
|
|
@@ -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", "
|
|
30
|
+
export declare const PRIVATE_STORE_FIELDS: readonly ["searchTxt", "updatedAt", "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
|
|
@@ -43,7 +43,6 @@ export const PRIVATE_STORE_FIELDS = [
|
|
|
43
43
|
// hundred tokens.
|
|
44
44
|
"searchTxt",
|
|
45
45
|
"updatedAt", // bookkeeping; no public reader
|
|
46
|
-
"whatsappConfig", // Meta access token + WABA/catalog ids — secret
|
|
47
46
|
"adminNotes", // schema comment: never shown to the owner or the public
|
|
48
47
|
// Admin decisions with actor UIDs and suspension reasons. The store OWNER
|
|
49
48
|
// sees this on their own admin surfaces; an anonymous visitor to /stores has
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isListingTypeEnabled } from "../../../shared/listing-types/feature-flags";
|
|
2
|
-
import { type
|
|
2
|
+
import { type ListingTabCountResult } from "../products/listing-tab-counts";
|
|
3
3
|
type SiteSettingsSnapshot = Parameters<typeof isListingTypeEnabled>[1];
|
|
4
4
|
export interface StoreTabLink {
|
|
5
5
|
value: string;
|
|
@@ -11,7 +11,12 @@ export interface ResolvedStoreTabs {
|
|
|
11
11
|
dropdownTabs: StoreTabLink[];
|
|
12
12
|
/** Coupons (if any), Reviews (if any), About — always non-empty. */
|
|
13
13
|
tabs: StoreTabLink[];
|
|
14
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Both numbers, because they answer different questions: `available` is what
|
|
16
|
+
* each tab badges, `total` is what decides whether the tab exists at all.
|
|
17
|
+
* See `ListingTabCountResult`.
|
|
18
|
+
*/
|
|
19
|
+
listingCounts: ListingTabCountResult;
|
|
15
20
|
couponsCount: number;
|
|
16
21
|
reviewsCount: number;
|
|
17
22
|
}
|
|
@@ -73,7 +73,7 @@ export async function resolveStoreTabs(storeSlug, storeId, settings) {
|
|
|
73
73
|
.then((r) => r.total)
|
|
74
74
|
.catch(() => 0),
|
|
75
75
|
])
|
|
76
|
-
: [{}, 0, 0];
|
|
76
|
+
: [{ available: {}, total: {} }, 0, 0];
|
|
77
77
|
const visibleStoreTabs = STORE_PAGE_TABS.filter((tab) => {
|
|
78
78
|
if (tab.id === "bundles") {
|
|
79
79
|
if (!isCategoryTypeEnabled("bundle", settings))
|
|
@@ -92,12 +92,14 @@ export async function resolveStoreTabs(storeSlug, storeId, settings) {
|
|
|
92
92
|
// that leads to an empty page. `undefined` means the count query FAILED,
|
|
93
93
|
// not zero, so the tab stays visible rather than hiding real stock
|
|
94
94
|
// behind a swallowed error (Root Cause #59).
|
|
95
|
-
|
|
95
|
+
// TOTAL, not the badge: a tab whose rows have all ended still has an
|
|
96
|
+
// archive behind the Ended/All scopes, and hiding it would strand that.
|
|
97
|
+
const count = listingCounts.total[tab.id];
|
|
96
98
|
return count === undefined || count > 0;
|
|
97
99
|
});
|
|
98
100
|
const dropdownTabs = visibleStoreTabs.map((tab) => ({
|
|
99
101
|
value: tab.id,
|
|
100
|
-
label: tabLabel(tab.label, listingCounts[tab.id]),
|
|
102
|
+
label: tabLabel(tab.label, listingCounts.available[tab.id]),
|
|
101
103
|
href: STORE_LISTING_HREF[tab.id](storeSlug),
|
|
102
104
|
}));
|
|
103
105
|
const tabs = [
|
|
@@ -299,7 +299,7 @@ function buildMetricCache(ctx, bucket, now, onSaturated) {
|
|
|
299
299
|
* The owner's contact details, resolved once per owner.
|
|
300
300
|
*
|
|
301
301
|
* 🛑 This is how the seller's `notifyChannels` is HONOURED rather than
|
|
302
|
-
* ignored. `sendNotification` decides email
|
|
302
|
+
* ignored. `sendNotification` decides email eligibility itself
|
|
303
303
|
* (type gate → admin allow-list → user prefs → daily budget) and takes no
|
|
304
304
|
* channel list — but it cannot email an address it was not given. So omitting
|
|
305
305
|
* `userEmail` when the seller did not tick email is a real narrowing through
|
|
@@ -389,9 +389,6 @@ export async function runAnalyticsAlertEvaluate(ctx) {
|
|
|
389
389
|
...(alert.notifyChannels?.includes("email") && contact.email
|
|
390
390
|
? { userEmail: contact.email }
|
|
391
391
|
: {}),
|
|
392
|
-
...(alert.notifyChannels?.includes("whatsapp") && contact.phone
|
|
393
|
-
? { userPhone: contact.phone }
|
|
394
|
-
: {}),
|
|
395
392
|
audience: b.scope === "admin" ? "admin" : "seller",
|
|
396
393
|
});
|
|
397
394
|
};
|
|
@@ -429,7 +426,7 @@ export async function runAnalyticsAlertEvaluate(ctx) {
|
|
|
429
426
|
*
|
|
430
427
|
* If the notification throws, the cooldown has still been recorded —
|
|
431
428
|
* so a persistently failing channel cannot turn into one attempt per
|
|
432
|
-
* run forever. The inverse order would retry a broken
|
|
429
|
+
* run forever. The inverse order would retry a broken send
|
|
433
430
|
* every hour against a 100/day email allowance.
|
|
434
431
|
*/
|
|
435
432
|
await ctx.db
|
|
@@ -11,9 +11,7 @@
|
|
|
11
11
|
import { runWeeklyPayoutEligibility } from "./weeklyPayoutEligibility.js";
|
|
12
12
|
import { runHardBanCascade } from "./hardBanCascade.js";
|
|
13
13
|
import { runResetOtpVerification } from "./resetOtpVerification.js";
|
|
14
|
-
import { runWhatsAppNotify } from "./whatsappNotify.js";
|
|
15
14
|
import { runNewsletterExport } from "./newsletterExport.js";
|
|
16
|
-
import { runWhatsAppCatalogImport } from "./whatsappCatalogImport.js";
|
|
17
15
|
import { runStoreNameBackfill } from "./storeNameBackfill.js";
|
|
18
16
|
import { runCategoryTaxonomyBackfill } from "./categoryTaxonomyBackfill.js";
|
|
19
17
|
async function runPayoutsWeeklyJob(_payload, ctx) {
|
|
@@ -42,15 +40,9 @@ async function runHardBanCascadeJob(payload, ctx) {
|
|
|
42
40
|
async function runResetOtpVerificationJob(_payload, ctx) {
|
|
43
41
|
return runResetOtpVerification(ctx);
|
|
44
42
|
}
|
|
45
|
-
async function runWhatsAppNotifyJob(payload, ctx) {
|
|
46
|
-
return runWhatsAppNotify(payload, ctx);
|
|
47
|
-
}
|
|
48
43
|
async function runNewsletterExportJob(_payload, ctx) {
|
|
49
44
|
return runNewsletterExport(ctx);
|
|
50
45
|
}
|
|
51
|
-
async function runWhatsAppCatalogImportJob(payload, ctx) {
|
|
52
|
-
return runWhatsAppCatalogImport({ storeSlug: String(payload.storeSlug ?? "") }, ctx);
|
|
53
|
-
}
|
|
54
46
|
async function runStoreNameBackfillJob(payload, ctx) {
|
|
55
47
|
return runStoreNameBackfill({ storeId: String(payload.storeId ?? "") }, ctx);
|
|
56
48
|
}
|
|
@@ -68,9 +60,7 @@ export const JOB_RUNNERS = {
|
|
|
68
60
|
payoutsWeekly: runPayoutsWeeklyJob,
|
|
69
61
|
hardBanCascade: runHardBanCascadeJob,
|
|
70
62
|
resetOtpVerification: runResetOtpVerificationJob,
|
|
71
|
-
whatsappNotify: runWhatsAppNotifyJob,
|
|
72
63
|
newsletterExport: runNewsletterExportJob,
|
|
73
|
-
whatsappCatalogImport: runWhatsAppCatalogImportJob,
|
|
74
64
|
storeNameBackfill: runStoreNameBackfillJob,
|
|
75
65
|
categoryTaxonomyBackfill: runCategoryTaxonomyBackfillJob,
|
|
76
66
|
};
|