@mohasinac/appkit 4.53.2 → 4.54.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.build-stamp.json +3 -3
- package/dist/_internal/server/features/auth/capabilities.js +7 -5
- 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/products/listing-tab-counts.js +81 -25
- 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/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/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 +21 -15
- package/dist/constants/api-endpoints.js +7 -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 +4 -3
- 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/components/BrandDetailPageView.js +2 -1
- package/dist/features/categories/components/BundleDetailView.js +2 -1
- package/dist/features/categories/components/CategoryDetailPageView.js +2 -1
- 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 +2 -1
- package/dist/features/digital-codes/components/DigitalCodeDetailPageView.js +2 -1
- 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 +2 -1
- 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 +4 -3
- 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.js +1 -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 +27 -2
- 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/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/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/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/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 +415 -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 -88
- package/dist/seed/_helpers/category-parts.d.ts +1 -1
- package/dist/seed/_helpers/category-parts.js +13399 -3019
- 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-catalogue-seed-data.d.ts +5 -5
- package/dist/seed/products-catalogue-seed-data.js +63936 -13803
- 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 +8 -10
- package/dist/server.js +16 -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/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
|
@@ -28,7 +28,7 @@ import { serverLogger } from "../../../../monitoring/index.js";
|
|
|
28
28
|
import { normalizeError } from "../../../../errors/normalize.js";
|
|
29
29
|
import { sieveAnd, sieveFilter, SIEVE_OP, sortBy } from "../../../../utils/sieve-builder.js";
|
|
30
30
|
import { PRODUCT_FIELDS } from "../../../../constants/field-names.js";
|
|
31
|
-
import { isListingRowAvailable,
|
|
31
|
+
import { isListingRowAvailable, pluginFor, } from "../../../shared/listing-types/_registry.js";
|
|
32
32
|
import { normalizeListingType } from "../../../../features/products/utils/listing-type.js";
|
|
33
33
|
import { clauseToSieve, PUBLIC_PRODUCT_MAX_PAGE_SIZE } from "./list-public.js";
|
|
34
34
|
const DEFAULT_STATUS = "published";
|
|
@@ -105,21 +105,63 @@ function bundleMatchesScope(bundle, scope) {
|
|
|
105
105
|
*/
|
|
106
106
|
async function unavailableByType(scope, types, now) {
|
|
107
107
|
const byType = new Map();
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
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 };
|
|
111
124
|
const status = scope.status ?? DEFAULT_STATUS;
|
|
112
|
-
const results = await Promise.all(
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
+
*/
|
|
116
158
|
const sorts = clause.op === "lt" && clause.sortField
|
|
117
159
|
? sortBy(clause.sortField, "DESC")
|
|
118
|
-
:
|
|
160
|
+
: undefined;
|
|
119
161
|
try {
|
|
120
|
-
|
|
162
|
+
const result = await productRepository.list({
|
|
121
163
|
filters: sieveAnd(sieveFilter(PRODUCT_FIELDS.STATUS, SIEVE_OP.EQ, status), clauseToSieve(clause, now)),
|
|
122
|
-
sorts,
|
|
164
|
+
...(sorts ? { sorts } : {}),
|
|
123
165
|
page: 1,
|
|
124
166
|
pageSize: PUBLIC_PRODUCT_MAX_PAGE_SIZE,
|
|
125
167
|
}, {
|
|
@@ -127,26 +169,37 @@ async function unavailableByType(scope, types, now) {
|
|
|
127
169
|
categoriesIn: scope.categoriesIn,
|
|
128
170
|
brandName: scope.brandName,
|
|
129
171
|
});
|
|
172
|
+
return { entry, result };
|
|
130
173
|
}
|
|
131
174
|
catch (error) {
|
|
132
175
|
const e = normalizeError(error);
|
|
133
176
|
// ERROR, not warn: a clause that cannot run is almost always a missing
|
|
134
|
-
// composite index — a permanent, silent skew of
|
|
135
|
-
//
|
|
136
|
-
|
|
137
|
-
|
|
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 };
|
|
138
187
|
}
|
|
139
188
|
}));
|
|
140
189
|
const merged = new Map();
|
|
141
|
-
|
|
142
|
-
for (const result of results) {
|
|
190
|
+
for (const { entry, result } of results) {
|
|
143
191
|
if (!result) {
|
|
144
|
-
|
|
192
|
+
for (const type of entry.types)
|
|
193
|
+
unknown.add(type);
|
|
145
194
|
continue;
|
|
146
195
|
}
|
|
147
196
|
const rows = result.items;
|
|
148
|
-
|
|
149
|
-
|
|
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
|
+
}
|
|
150
203
|
for (const row of rows) {
|
|
151
204
|
const id = typeof row.id === "string" ? row.id : JSON.stringify(row.id);
|
|
152
205
|
if (!merged.has(id))
|
|
@@ -160,7 +213,7 @@ async function unavailableByType(scope, types, now) {
|
|
|
160
213
|
const type = normalizeListingType(row);
|
|
161
214
|
byType.set(type, (byType.get(type) ?? 0) + 1);
|
|
162
215
|
}
|
|
163
|
-
return { byType,
|
|
216
|
+
return { byType, unknown };
|
|
164
217
|
}
|
|
165
218
|
/** The listing types one tab spans — `"art|stickers"` is a pipe OR-group. */
|
|
166
219
|
function typesOf(tab) {
|
|
@@ -194,15 +247,18 @@ export async function listingTabCounts(tabs, scope, opts) {
|
|
|
194
247
|
const total = {};
|
|
195
248
|
const available = {};
|
|
196
249
|
for (const tab of listingTabs) {
|
|
250
|
+
const tabTypes = typesOf(tab);
|
|
197
251
|
const totalForTab = typeCounts[String(tab.listingType)];
|
|
198
252
|
total[tab.id] = totalForTab;
|
|
199
|
-
if (totalForTab === undefined ||
|
|
200
|
-
|
|
201
|
-
// total reproduces the pre-
|
|
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.
|
|
202
258
|
available[tab.id] = totalForTab;
|
|
203
259
|
continue;
|
|
204
260
|
}
|
|
205
|
-
const gone =
|
|
261
|
+
const gone = tabTypes.reduce((sum, type) => sum + (unavailable.byType.get(type) ?? 0), 0);
|
|
206
262
|
available[tab.id] = Math.max(0, totalForTab - gone);
|
|
207
263
|
}
|
|
208
264
|
if (wantsBundles) {
|
|
@@ -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
|
|
@@ -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
|
};
|
|
@@ -1,77 +1,7 @@
|
|
|
1
1
|
import { normalizeError } from "../../../../errors/normalize.js";
|
|
2
2
|
import { ORDER_PII_FIELDS, revealPii } from "../../../../security/index.js";
|
|
3
|
-
import { adminNotificationsRepository
|
|
4
|
-
import {
|
|
5
|
-
import { sendWhatsAppBusinessMessage, buildPurchaseAnnouncementMessage, } from "../../../../features/whatsapp-bot/server.js";
|
|
6
|
-
import { resolveKeys } from "../../../../core/integration-keys.js";
|
|
7
|
-
import { guardSend, logSuppressedSend } from "../../notifications/send-guard.js";
|
|
8
|
-
async function sendAnnouncement(ctx, toPhone, message, phoneNumberId, accessToken, label, orderId) {
|
|
9
|
-
try {
|
|
10
|
-
const sent = await sendWhatsAppBusinessMessage({
|
|
11
|
-
toPhone,
|
|
12
|
-
message,
|
|
13
|
-
phoneNumberId,
|
|
14
|
-
accessToken,
|
|
15
|
-
});
|
|
16
|
-
if (sent) {
|
|
17
|
-
ctx.logger.info(`Announcement sent to ${label}`, {
|
|
18
|
-
orderId,
|
|
19
|
-
toPhone: `...${toPhone.slice(-4)}`,
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
else {
|
|
23
|
-
ctx.logger.error(`Announcement delivery failed for ${label} (non-fatal)`, null, { orderId });
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
catch (err) {
|
|
27
|
-
void normalizeError(err);
|
|
28
|
-
ctx.logger.error(`Announcement send threw for ${label} (non-fatal)`, err, { orderId });
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
async function notifyStoreOwner(ctx, storeId, message, phoneNumberId, accessToken, orderId) {
|
|
32
|
-
try {
|
|
33
|
-
const store = await storeRepository.findBySlug(storeId);
|
|
34
|
-
if (!store?.ownerId)
|
|
35
|
-
return;
|
|
36
|
-
const owner = await userRepository.findById(store.ownerId);
|
|
37
|
-
const encryptedPhone = owner?.phoneNumber;
|
|
38
|
-
if (!encryptedPhone)
|
|
39
|
-
return;
|
|
40
|
-
const ownerPhone = decryptPii(encryptedPhone);
|
|
41
|
-
if (ownerPhone) {
|
|
42
|
-
await sendAnnouncement(ctx, ownerPhone, message, phoneNumberId, accessToken, "store-owner", orderId);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
catch (err) {
|
|
46
|
-
void normalizeError(err);
|
|
47
|
-
ctx.logger.error("Store owner lookup failed (non-fatal)", err, { orderId, storeId });
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* Seeded credentials are `*_PLACEHOLDER` strings rather than empty, so an
|
|
52
|
-
* emptiness test alone would happily send with a fake token. Mirrors the guard
|
|
53
|
-
* in checkout-value-otp-actions.ts.
|
|
54
|
-
*/
|
|
55
|
-
function usable(value) {
|
|
56
|
-
const v = value?.trim() ?? "";
|
|
57
|
-
return v && !v.includes("PLACEHOLDER") ? v : "";
|
|
58
|
-
}
|
|
59
|
-
async function resolveWhatsAppCredentials(ctx, orderId) {
|
|
60
|
-
let db = {};
|
|
61
|
-
try {
|
|
62
|
-
db = await resolveKeys();
|
|
63
|
-
}
|
|
64
|
-
catch (err) {
|
|
65
|
-
void normalizeError(err);
|
|
66
|
-
// Firestore unreachable from the Functions runtime — env-only is still a
|
|
67
|
-
// valid configuration, so this is a warning, not a failure.
|
|
68
|
-
ctx.logger.warn("Credential lookup failed — falling back to env", { orderId });
|
|
69
|
-
}
|
|
70
|
-
return {
|
|
71
|
-
phoneNumberId: usable(db.whatsappPhoneNumberId) || usable(ctx.env("WHATSAPP_PHONE_NUMBER_ID")),
|
|
72
|
-
accessToken: usable(db.whatsappCloudApiToken) || usable(ctx.env("WHATSAPP_CLOUD_API_TOKEN")),
|
|
73
|
-
};
|
|
74
|
-
}
|
|
3
|
+
import { adminNotificationsRepository } from "../../../../repositories/index.js";
|
|
4
|
+
import { buildPurchaseAnnouncementMessage } from "../../../../features/orders/utils/order-announcement-message.js";
|
|
75
5
|
export async function handleOrderCreate(input, ctx) {
|
|
76
6
|
const { orderId, order } = input;
|
|
77
7
|
const items = order.items ?? [];
|
|
@@ -140,37 +70,13 @@ export async function handleOrderCreate(input, ctx) {
|
|
|
140
70
|
ctx.logger.error("Failed to write admin order notification (non-fatal)", err, { orderId });
|
|
141
71
|
}
|
|
142
72
|
/*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
73
|
+
* The seller is told about their own sale by the `order_placed`
|
|
74
|
+
* notification `createOrderForGroup` already sends (in-app + email). This
|
|
75
|
+
* handler used to ALSO push a WhatsApp Cloud API message here — a duplicate
|
|
76
|
+
* channel for the same event, and the only thing in this file that needed a
|
|
77
|
+
* Meta token, a registered number and a billing relationship. Removed with
|
|
78
|
+
* the rest of the WhatsApp integration on 2026-10-20; no signal was lost,
|
|
79
|
+
* because the notification path is independent and unchanged.
|
|
148
80
|
*/
|
|
149
|
-
|
|
150
|
-
if (!storeId) {
|
|
151
|
-
ctx.logger.info("Order has no storeId — nothing to announce to a seller", { orderId });
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
const { phoneNumberId, accessToken } = await resolveWhatsAppCredentials(ctx, orderId);
|
|
155
|
-
if (!phoneNumberId || !accessToken) {
|
|
156
|
-
ctx.logger.info("WhatsApp Cloud API not configured — skipping seller announcement", { orderId });
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
159
|
-
// Audience is "user": a seller receiving news of their own sale is a
|
|
160
|
-
// marketplace participant, not an operator of the site. Staff exemption is
|
|
161
|
-
// for the digest and the payout summary, and widening it to "anyone we think
|
|
162
|
-
// ought to see this" would make the kill switch mean nothing.
|
|
163
|
-
const guard = {
|
|
164
|
-
channel: "whatsapp",
|
|
165
|
-
feature: "order_placed_seller",
|
|
166
|
-
audience: "user",
|
|
167
|
-
};
|
|
168
|
-
const decision = await guardSend(guard);
|
|
169
|
-
if (!decision.allow) {
|
|
170
|
-
logSuppressedSend(guard, decision);
|
|
171
|
-
ctx.logger.info("Seller order announcement suppressed by messaging guard", { orderId, storeId });
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
await notifyStoreOwner(ctx, storeId, message, phoneNumberId, accessToken, orderId);
|
|
175
|
-
ctx.logger.info(`Order announcement complete`, { orderId, storeId });
|
|
81
|
+
ctx.logger.info("Order announcement complete", { orderId, storeId: order.storeId ?? null });
|
|
176
82
|
}
|
|
@@ -6,8 +6,6 @@ export type OrderAfter = {
|
|
|
6
6
|
userEmail: string;
|
|
7
7
|
productTitle: string;
|
|
8
8
|
trackingNumber?: string;
|
|
9
|
-
/** Buyer opted into the ₹10 WhatsApp order-updates addon at checkout. */
|
|
10
|
-
whatsappNotifyAddon?: boolean;
|
|
11
9
|
};
|
|
12
10
|
export type OrderBefore = {
|
|
13
11
|
status: OrderStatus;
|
|
@@ -54,7 +54,6 @@ export async function handleOrderStatusChange(input, ctx) {
|
|
|
54
54
|
relatedId: orderId,
|
|
55
55
|
relatedType: "order",
|
|
56
56
|
userEmail,
|
|
57
|
-
orderWhatsappAddonPaid: after.whatsappNotifyAddon === true,
|
|
58
57
|
});
|
|
59
58
|
// The RTDB mirror that used to live here has been removed. `sendNotification`
|
|
60
59
|
// above is the real path (in-app + email + WhatsApp); the extra
|
|
@@ -56,7 +56,6 @@ export async function runPaymentWindowTimeout(ctx) {
|
|
|
56
56
|
message: ORDER_MESSAGES.CANCELLED_PAYMENT_WINDOW_MESSAGE(entry.data.productTitle),
|
|
57
57
|
relatedId: entry.id,
|
|
58
58
|
relatedType: "order",
|
|
59
|
-
orderWhatsappAddonPaid: entry.data.whatsappNotifyAddon === true,
|
|
60
59
|
});
|
|
61
60
|
}
|
|
62
61
|
catch (err) {
|
|
@@ -67,7 +67,6 @@ export async function runPendingOrderTimeout(ctx) {
|
|
|
67
67
|
message: ORDER_MESSAGES.CANCELLED_TIMEOUT_MESSAGE(entry.data.productTitle, timeoutHours),
|
|
68
68
|
relatedId: entry.id,
|
|
69
69
|
relatedType: "order",
|
|
70
|
-
orderWhatsappAddonPaid: entry.data.whatsappNotifyAddon === true,
|
|
71
70
|
});
|
|
72
71
|
}
|
|
73
72
|
catch (err) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Circuit breaker for outbound notification channels
|
|
2
|
+
* Circuit breaker for outbound notification channels.
|
|
3
3
|
*
|
|
4
4
|
* Persisted as a singleton Firestore doc `system/channelHealth` so the
|
|
5
5
|
* "is this channel currently known-bad" state survives across requests and
|
|
@@ -9,7 +9,11 @@
|
|
|
9
9
|
* provider). After the cooldown, the next attempt is a half-open probe:
|
|
10
10
|
* its own outcome decides whether the circuit re-closes or re-opens.
|
|
11
11
|
*/
|
|
12
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Email is the only outbound channel with a transport. "whatsapp" was the
|
|
14
|
+
* other arm until 2026-10-20, when the Cloud API integration was removed.
|
|
15
|
+
*/
|
|
16
|
+
export type NotificationChannel = "email";
|
|
13
17
|
/**
|
|
14
18
|
* Returns false only when the circuit is open and still within its cooldown
|
|
15
19
|
* window. Any other state (closed, or open-but-cooldown-elapsed) is treated
|
|
@@ -20,7 +24,7 @@ export declare function recordChannelOutcome(channel: NotificationChannel, succe
|
|
|
20
24
|
/**
|
|
21
25
|
* Runs `fn` up to 3 times (500ms then 1.5s between attempts) and records the
|
|
22
26
|
* final outcome against the channel's health state. `fn` must throw on
|
|
23
|
-
* failure — for a helper that returns a boolean instead (e.g.
|
|
27
|
+
* failure — for a helper that returns a boolean instead (e.g. a sender
|
|
24
28
|
* Cloud API helpers), convert `false` into a thrown error at the call site
|
|
25
29
|
* before passing it in.
|
|
26
30
|
*/
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Circuit breaker for outbound notification channels
|
|
2
|
+
* Circuit breaker for outbound notification channels.
|
|
3
3
|
*
|
|
4
4
|
* Persisted as a singleton Firestore doc `system/channelHealth` so the
|
|
5
5
|
* "is this channel currently known-bad" state survives across requests and
|
|
@@ -72,7 +72,7 @@ function delay(ms) {
|
|
|
72
72
|
/**
|
|
73
73
|
* Runs `fn` up to 3 times (500ms then 1.5s between attempts) and records the
|
|
74
74
|
* final outcome against the channel's health state. `fn` must throw on
|
|
75
|
-
* failure — for a helper that returns a boolean instead (e.g.
|
|
75
|
+
* failure — for a helper that returns a boolean instead (e.g. a sender
|
|
76
76
|
* Cloud API helpers), convert `false` into a thrown error at the call site
|
|
77
77
|
* before passing it in.
|
|
78
78
|
*/
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* message.
|
|
16
16
|
*
|
|
17
17
|
* The guarantee that nothing bypasses this is STRUCTURAL instead: the guard
|
|
18
|
-
* argument is REQUIRED on `sendEmail
|
|
18
|
+
* argument is REQUIRED on `sendEmail`, so an
|
|
19
19
|
* unguarded send does not typecheck, and `scripts/audit-unguarded-send.mjs`
|
|
20
20
|
* fails the build on a raw `createResendProvider().send()`. Make the wrong
|
|
21
21
|
* thing unrepresentable rather than adding a second layer of defence that has
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* message.
|
|
16
16
|
*
|
|
17
17
|
* The guarantee that nothing bypasses this is STRUCTURAL instead: the guard
|
|
18
|
-
* argument is REQUIRED on `sendEmail
|
|
18
|
+
* argument is REQUIRED on `sendEmail`, so an
|
|
19
19
|
* unguarded send does not typecheck, and `scripts/audit-unguarded-send.mjs`
|
|
20
20
|
* fails the build on a raw `createResendProvider().send()`. Make the wrong
|
|
21
21
|
* thing unrepresentable rather than adding a second layer of defence that has
|
|
@@ -69,9 +69,7 @@ export async function guardSend(ctx) {
|
|
|
69
69
|
if (ctx.audience === "user" && !ctx.transactional) {
|
|
70
70
|
const channelEnabled = ctx.channel === "email"
|
|
71
71
|
? (messaging?.emailEnabled ?? MESSAGE_BUDGET_DEFAULTS.emailEnabled)
|
|
72
|
-
:
|
|
73
|
-
? (messaging?.whatsappEnabled ?? MESSAGE_BUDGET_DEFAULTS.whatsappEnabled)
|
|
74
|
-
: false; // sms has no sender; if one appears it starts disabled.
|
|
72
|
+
: false; // sms has no sender; if one appears it starts disabled.
|
|
75
73
|
if (!channelEnabled) {
|
|
76
74
|
return {
|
|
77
75
|
allow: false,
|
|
@@ -29,7 +29,7 @@ import type { IconKey } from "../../../ui/icons/icon-registry";
|
|
|
29
29
|
/** What KIND of action — drives default UI affordance (link vs button vs danger). */
|
|
30
30
|
export type ActionKind = "primary" | "secondary" | "danger" | "ghost" | "link" | "chip";
|
|
31
31
|
/** Resource bucket — keyed top-level in the ACTIONS tree. */
|
|
32
|
-
export type ActionResource = "PRODUCT" | "AUCTION" | "PRE_ORDER" | "PRIZE_DRAW" | "DIGITAL_CODE" | "BUNDLE" | "GROUP" | "CATEGORY" | "BRAND" | "SUBLISTING" | "STORE" | "BLOG" | "EVENT" | "SHIPMENT" | "CATALOGUE" | "USER" | "SELLER" | "ADMIN" | "CART" | "CHECKOUT" | "NAV" | "MEDIA" | "SUPPORT" | "LOTTERY" | "TESTER";
|
|
32
|
+
export type ActionResource = "PRODUCT" | "AUCTION" | "PRE_ORDER" | "PRIZE_DRAW" | "DIGITAL_CODE" | "BUNDLE" | "GROUP" | "CATEGORY" | "BRAND" | "SUBLISTING" | "STORE" | "BLOG" | "EVENT" | "REVIEW" | "SHIPMENT" | "CATALOGUE" | "USER" | "SELLER" | "ADMIN" | "CART" | "CHECKOUT" | "NAV" | "MEDIA" | "SUPPORT" | "LOTTERY" | "TESTER";
|
|
33
33
|
/** Confirmation modal config — when set, <Button action={...}> opens
|
|
34
34
|
* a confirm dialog before firing the actual handler. */
|
|
35
35
|
export interface ActionConfirmation {
|