@mohasinac/appkit 4.53.2 → 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/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 -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/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
|
@@ -30,7 +30,7 @@ export function NotificationPreferencesPanel({ fetchUrl = ACCOUNT_ENDPOINTS.NOTI
|
|
|
30
30
|
const { showToast } = useToast();
|
|
31
31
|
const { data: settings } = useSiteSettings();
|
|
32
32
|
const adminChannels = settings?.notificationChannels;
|
|
33
|
-
const [channels, setChannels] = useState({ email: true,
|
|
33
|
+
const [channels, setChannels] = useState({ email: true, sms: true });
|
|
34
34
|
const [types, setTypes] = useState({
|
|
35
35
|
orderUpdates: true, bids: true, promotions: true,
|
|
36
36
|
system: true, reviews: true, messages: true, offers: true,
|
|
@@ -92,8 +92,7 @@ export function NotificationPreferencesPanel({ fetchUrl = ACCOUNT_ENDPOINTS.NOTI
|
|
|
92
92
|
}
|
|
93
93
|
// Which channels are admin-enabled (only show those toggles to the user)
|
|
94
94
|
const emailAdminEnabled = adminChannels?.email?.enabled ?? false;
|
|
95
|
-
const whatsappAdminEnabled = adminChannels?.whatsapp?.enabled ?? false;
|
|
96
95
|
const smsAdminEnabled = adminChannels?.sms?.enabled ?? false;
|
|
97
|
-
const anyChannelEnabled = emailAdminEnabled ||
|
|
98
|
-
return (_jsxs(Stack, { gap: "lg", children: [_jsx(SectionCard, { title: "In-app notifications", children: _jsxs(Row, { align: "center", justify: "between", gap: "md", padding: "y-sm", children: [_jsxs(Div, { children: [_jsx(Text, { className: "text-[var(--appkit-color-text)]", size: "sm", weight: "medium", children: "In-app notifications" }), _jsx(Text, { variant: "secondary", className: "mt-0.5", size: "xs", children: "Always on \u2014 displayed in the notification bell and inbox" })] }), _jsx(Row, { className: "h-5 w-9 bg-[var(--appkit-color-primary)] px-[0.125rem]", align: "center", justify: "end", rounded: "full", "aria-label": "Always on", children: _jsx(Span, { className: "h-4 w-4", rounded: "full", surface: "default", shadow: "default" }) })] }) }), anyChannelEnabled && (_jsxs(SectionCard, { title: "Notification channels", children: [emailAdminEnabled && (_jsx(ToggleRow, { label: "Email", description: "Receive notifications by email", checked: channels.email !== false, onChange: (v) => setChannel("email", v) })),
|
|
96
|
+
const anyChannelEnabled = emailAdminEnabled || smsAdminEnabled;
|
|
97
|
+
return (_jsxs(Stack, { gap: "lg", children: [_jsx(SectionCard, { title: "In-app notifications", children: _jsxs(Row, { align: "center", justify: "between", gap: "md", padding: "y-sm", children: [_jsxs(Div, { children: [_jsx(Text, { className: "text-[var(--appkit-color-text)]", size: "sm", weight: "medium", children: "In-app notifications" }), _jsx(Text, { variant: "secondary", className: "mt-0.5", size: "xs", children: "Always on \u2014 displayed in the notification bell and inbox" })] }), _jsx(Row, { className: "h-5 w-9 bg-[var(--appkit-color-primary)] px-[0.125rem]", align: "center", justify: "end", rounded: "full", "aria-label": "Always on", children: _jsx(Span, { className: "h-4 w-4", rounded: "full", surface: "default", shadow: "default" }) })] }) }), anyChannelEnabled && (_jsxs(SectionCard, { title: "Notification channels", children: [emailAdminEnabled && (_jsx(ToggleRow, { label: "Email", description: "Receive notifications by email", checked: channels.email !== false, onChange: (v) => setChannel("email", v) })), smsAdminEnabled && (_jsx(ToggleRow, { label: "SMS", description: "Receive SMS text notifications", checked: channels.sms !== false, onChange: (v) => setChannel("sms", v) }))] })), _jsx(SectionCard, { title: "Notification types", children: TYPE_ROWS.map((row) => (_jsx(ToggleRow, { label: row.label, description: row.description, checked: types[row.key] !== false, onChange: (v) => setType(row.key, v) }, row.key))) }), _jsxs(Row, { justify: "end", gap: "sm", children: [saved && (_jsx(Text, { className: "text-[var(--appkit-color-success)] self-center", size: "sm", children: "Preferences saved \u2713" })), _jsx(Button, { onClick: handleSave, isLoading: saving, size: "sm", children: "Save preferences" })] })] }));
|
|
99
98
|
}
|
|
@@ -11,7 +11,23 @@ import { OrderCard } from "../../orders/components/OrdersList.js";
|
|
|
11
11
|
import { useOrderScope } from "../../orders/components/OrderScopeTabs.js";
|
|
12
12
|
import { useSearchParams } from "next/navigation.js";
|
|
13
13
|
const CANCELLABLE_STATUSES = new Set(["pending", "confirmed", "processing"]);
|
|
14
|
-
|
|
14
|
+
/*
|
|
15
|
+
* 🛑 Every PRE-DELIVERY status, not just "shipped".
|
|
16
|
+
*
|
|
17
|
+
* It was `["shipped"]` alone, so a buyer whose order was pending, confirmed
|
|
18
|
+
* or processing had no "where is it" link at all — which is precisely when
|
|
19
|
+
* they most want one. The timeline on the track page reads correctly for all
|
|
20
|
+
* of these: it shows the stages reached and the stages still ahead.
|
|
21
|
+
*
|
|
22
|
+
* Delivered, cancelled, refunded and returned are excluded because tracking a
|
|
23
|
+
* finished order is the order detail page's job, not a courier's.
|
|
24
|
+
*/
|
|
25
|
+
const TRACKABLE_STATUSES = new Set([
|
|
26
|
+
"pending",
|
|
27
|
+
"confirmed",
|
|
28
|
+
"processing",
|
|
29
|
+
"shipped",
|
|
30
|
+
]);
|
|
15
31
|
const SORT_OPTIONS = [
|
|
16
32
|
{ value: sortBy("createdAt", "DESC"), label: "Newest first" },
|
|
17
33
|
{ value: sortBy("createdAt", "ASC"), label: "Oldest first" },
|
|
@@ -77,12 +93,15 @@ export function UserOrdersView({ onOrderClick }) {
|
|
|
77
93
|
portal: "user",
|
|
78
94
|
title: "My Orders",
|
|
79
95
|
search: {
|
|
80
|
-
placeholder: "Search by order id…",
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
96
|
+
placeholder: "Search by item or order id…",
|
|
97
|
+
/*
|
|
98
|
+
* The route now matches product titles as well as the order id, so the
|
|
99
|
+
* placeholder can promise it. It said "order id" because that was all
|
|
100
|
+
* the route could do, with a comment calling that "the honest promise
|
|
101
|
+
* until the route can match more" — this is the promise catching up,
|
|
102
|
+
* not a widened claim.
|
|
103
|
+
*/
|
|
104
|
+
fields: ["id", "productTitle"],
|
|
86
105
|
},
|
|
87
106
|
emptyLabel: emptyMessage,
|
|
88
107
|
filterKeys: ["status", "orderType"],
|
|
@@ -75,8 +75,6 @@ export interface UserAddress {
|
|
|
75
75
|
export interface NotificationChannelPrefs {
|
|
76
76
|
/** Disable all email notifications for this user (default: true = enabled). */
|
|
77
77
|
email?: boolean;
|
|
78
|
-
/** Disable all WhatsApp notifications for this user. */
|
|
79
|
-
whatsapp?: boolean;
|
|
80
78
|
/** Disable all SMS notifications for this user. */
|
|
81
79
|
sms?: boolean;
|
|
82
80
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
interface CityState {
|
|
2
|
+
city: string;
|
|
3
|
+
state: string;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* What the last autofill wrote, so a later one can tell its own output apart
|
|
7
|
+
* from a human's typing. A field is absent when the helper declined to write
|
|
8
|
+
* it — which makes it the human's, and untouchable.
|
|
9
|
+
*/
|
|
10
|
+
export type PostalAutofillMemo = Partial<CityState>;
|
|
11
|
+
/**
|
|
12
|
+
* Merge `resolved` into `prev`, returning the new values and an updated memo.
|
|
13
|
+
*
|
|
14
|
+
* ── 🛑 THE BUG THIS EXISTS TO FIX ─────────────────────────────────────────
|
|
15
|
+
*
|
|
16
|
+
* Both address forms used `prev.city.trim() ? prev.city : city` — fill only
|
|
17
|
+
* an EMPTY field. That intent is right and is preserved here: correcting an
|
|
18
|
+
* address someone deliberately typed is a bug they cannot report, because
|
|
19
|
+
* they watch their own input vanish with no idea what did it.
|
|
20
|
+
*
|
|
21
|
+
* But "empty" cannot tell WHO filled the field. After the first lookup the
|
|
22
|
+
* city is non-empty — written by the autofill itself — so every subsequent
|
|
23
|
+
* lookup is refused. Enter 413001, get Solapur/Maharashtra; correct the PIN
|
|
24
|
+
* to 500005 (Hyderabad) and the form still shows Solapur. The PIN and the
|
|
25
|
+
* city now disagree, the form validates, and the parcel ships to the wrong
|
|
26
|
+
* city — worse than never autofilling at all.
|
|
27
|
+
*
|
|
28
|
+
* ── The rule ──────────────────────────────────────────────────────────────
|
|
29
|
+
*
|
|
30
|
+
* A field is ours to overwrite when it is EMPTY, or when it still holds
|
|
31
|
+
* exactly what we last wrote into it. The moment a human edits it, it stops
|
|
32
|
+
* matching the memo and becomes untouchable again — the original guarantee,
|
|
33
|
+
* now actually enforceable rather than approximated by "is it blank".
|
|
34
|
+
*/
|
|
35
|
+
export declare function applyPostalAutofill<T extends CityState>(prev: T, resolved: Partial<CityState>, memo: PostalAutofillMemo): {
|
|
36
|
+
next: T;
|
|
37
|
+
memo: PostalAutofillMemo;
|
|
38
|
+
};
|
|
39
|
+
export {};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* WHY: A postal lookup must fill the city/state it knows WITHOUT overwriting
|
|
3
|
+
* what the person typed — and the obvious test for that ("is the field
|
|
4
|
+
* empty?") silently breaks every lookup after the first.
|
|
5
|
+
* WHAT: `applyPostalAutofill` — merge a resolved city/state into form values,
|
|
6
|
+
* overwriting only values this helper itself wrote earlier.
|
|
7
|
+
*
|
|
8
|
+
* EXPORTS: applyPostalAutofill, type PostalAutofillMemo
|
|
9
|
+
*
|
|
10
|
+
* @tag domain:addresses
|
|
11
|
+
* @tag layer:utils
|
|
12
|
+
* @tag pattern:pure
|
|
13
|
+
* @tag sideEffects:none
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Merge `resolved` into `prev`, returning the new values and an updated memo.
|
|
17
|
+
*
|
|
18
|
+
* ── 🛑 THE BUG THIS EXISTS TO FIX ─────────────────────────────────────────
|
|
19
|
+
*
|
|
20
|
+
* Both address forms used `prev.city.trim() ? prev.city : city` — fill only
|
|
21
|
+
* an EMPTY field. That intent is right and is preserved here: correcting an
|
|
22
|
+
* address someone deliberately typed is a bug they cannot report, because
|
|
23
|
+
* they watch their own input vanish with no idea what did it.
|
|
24
|
+
*
|
|
25
|
+
* But "empty" cannot tell WHO filled the field. After the first lookup the
|
|
26
|
+
* city is non-empty — written by the autofill itself — so every subsequent
|
|
27
|
+
* lookup is refused. Enter 413001, get Solapur/Maharashtra; correct the PIN
|
|
28
|
+
* to 500005 (Hyderabad) and the form still shows Solapur. The PIN and the
|
|
29
|
+
* city now disagree, the form validates, and the parcel ships to the wrong
|
|
30
|
+
* city — worse than never autofilling at all.
|
|
31
|
+
*
|
|
32
|
+
* ── The rule ──────────────────────────────────────────────────────────────
|
|
33
|
+
*
|
|
34
|
+
* A field is ours to overwrite when it is EMPTY, or when it still holds
|
|
35
|
+
* exactly what we last wrote into it. The moment a human edits it, it stops
|
|
36
|
+
* matching the memo and becomes untouchable again — the original guarantee,
|
|
37
|
+
* now actually enforceable rather than approximated by "is it blank".
|
|
38
|
+
*/
|
|
39
|
+
export function applyPostalAutofill(prev, resolved, memo) {
|
|
40
|
+
const nextMemo = {};
|
|
41
|
+
const merge = (field) => {
|
|
42
|
+
const current = prev[field];
|
|
43
|
+
const incoming = resolved[field];
|
|
44
|
+
const ours = current.trim() === "" || current === memo[field];
|
|
45
|
+
if (!ours || !incoming)
|
|
46
|
+
return current; // human's value, or nothing to say
|
|
47
|
+
nextMemo[field] = incoming;
|
|
48
|
+
return incoming;
|
|
49
|
+
};
|
|
50
|
+
return {
|
|
51
|
+
next: { ...prev, city: merge("city"), state: merge("state") },
|
|
52
|
+
memo: nextMemo,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
@@ -11,12 +11,8 @@ export declare function getUnreadNotificationCount(userId: string): Promise<numb
|
|
|
11
11
|
export interface SendNotificationInput extends NotificationCreateInput {
|
|
12
12
|
/** User's email — used by the email channel when enabled. */
|
|
13
13
|
userEmail?: string;
|
|
14
|
-
/** User's phone with country code, digits only — used by WhatsApp channel. */
|
|
15
|
-
userPhone?: string;
|
|
16
14
|
/** Pre-rendered email HTML — falls back to a plain-text <p> when absent. */
|
|
17
15
|
emailHtml?: string;
|
|
18
|
-
/** Buyer paid the ₹10 WhatsApp order-updates addon — enables WhatsApp channel for this notification even when the user has no standing WhatsApp subscription. */
|
|
19
|
-
orderWhatsappAddonPaid?: boolean;
|
|
20
16
|
/**
|
|
21
17
|
* Which portal the recipient reads this in — decides where `actionUrl`
|
|
22
18
|
* points when the caller does not supply one.
|
|
@@ -52,17 +48,15 @@ export interface SendNotificationInput extends NotificationCreateInput {
|
|
|
52
48
|
export interface SendNotificationResult {
|
|
53
49
|
notification: NotificationDocument;
|
|
54
50
|
email: "sent" | "skipped" | "failed";
|
|
55
|
-
/** WhatsApp dispatch is now async (enqueued as a `whatsappNotify` job) — "queued" replaces "sent" as the immediate outcome; the real delivery result lands on `notification.whatsappStatus`. */
|
|
56
|
-
whatsapp: "queued" | "skipped" | "failed";
|
|
57
51
|
}
|
|
58
52
|
/**
|
|
59
53
|
* Create an in-app notification and fan out to enabled external channels.
|
|
60
54
|
*
|
|
61
|
-
* In-app is always written.
|
|
55
|
+
* In-app is always written. Email is sent only when:
|
|
62
56
|
* 1. The channel is enabled in siteSettings.notificationChannels
|
|
63
57
|
* 2. Notification priority meets the channel's minPriority threshold
|
|
64
58
|
* 3. The optional `types` filter includes this notification type (or is absent)
|
|
65
|
-
* 4. The contact field (userEmail
|
|
59
|
+
* 4. The contact field (userEmail) is supplied by the caller
|
|
66
60
|
* 5. The required credentials are set in siteSettings.credentials
|
|
67
61
|
*/
|
|
68
62
|
export declare function sendNotification(input: SendNotificationInput): Promise<SendNotificationResult>;
|
|
@@ -4,11 +4,11 @@ import { siteSettingsRepository } from "../repository/site-settings.repository.j
|
|
|
4
4
|
import { userRepository } from "../../auth/repository/user.repository.js";
|
|
5
5
|
import { createResendProvider } from "../../../providers/email-resend/provider.js";
|
|
6
6
|
import { isChannelHealthy, withChannelRetry } from "../../../_internal/server/notifications/channel-health.js";
|
|
7
|
-
import { enqueueJob } from "../../jobs/actions/enqueue-job.js";
|
|
8
7
|
import { serverLogger } from "../../../monitoring/index.js";
|
|
9
8
|
import { resolveNotificationActionUrl, TYPE_AUDIENCE, } from "../../../_internal/shared/features/notifications/action-url.js";
|
|
10
9
|
import { isEmailEligible } from "../../../_internal/shared/features/notifications/email-eligibility.js";
|
|
11
10
|
import { renderNotificationEmail } from "../../email/notification-templates.js";
|
|
11
|
+
import { DEFAULT_NOTIFICATION_CHANNELS, meetsMinPriority, } from "../schemas/firestore.js";
|
|
12
12
|
/**
|
|
13
13
|
* A notification's `actionUrl` is a site-relative path — correct for the
|
|
14
14
|
* in-app bell, useless in an email, where a bare `/user/orders/x` is not a
|
|
@@ -31,56 +31,6 @@ function absoluteActionUrl(path) {
|
|
|
31
31
|
const base = process.env.NEXT_PUBLIC_APP_URL?.replace(/\/+$/, "");
|
|
32
32
|
return base ? `${base}${path}` : path;
|
|
33
33
|
}
|
|
34
|
-
import { DEFAULT_NOTIFICATION_CHANNELS, meetsMinPriority, } from "../schemas/firestore.js";
|
|
35
|
-
/**
|
|
36
|
-
* Notification types whose WhatsApp send is gated behind the buyer's
|
|
37
|
-
* per-order ₹10 WhatsApp-updates addon (`OrderDocument.whatsappNotifyAddon`)
|
|
38
|
-
* — see `orderWhatsappAddonPaid` on `SendNotificationInput`. Every other
|
|
39
|
-
* type (bid_won, offers, scam reports, support tickets, admin alerts, …)
|
|
40
|
-
* keeps the original two-gate behavior (admin channel enabled + user hasn't
|
|
41
|
-
* opted out) unchanged.
|
|
42
|
-
*/
|
|
43
|
-
const ORDER_GATED_TYPES = new Set([
|
|
44
|
-
"order_placed",
|
|
45
|
-
"order_confirmed",
|
|
46
|
-
"order_shipped",
|
|
47
|
-
"order_delivered",
|
|
48
|
-
"order_cancelled",
|
|
49
|
-
"refund_initiated",
|
|
50
|
-
]);
|
|
51
|
-
/**
|
|
52
|
-
* Notification type → the flat `SiteSettingsCredentials` field holding its
|
|
53
|
-
* approved Meta template name.
|
|
54
|
-
*
|
|
55
|
-
* 🛑 **`Partial` here is CORRECT and is not the `typeToPrefsKey` mistake.**
|
|
56
|
-
*
|
|
57
|
-
* The distinction is what a missing key MEANS. A missing `typeToPrefsKey`
|
|
58
|
-
* entry silently ignored a user's opt-out — a real setting that read as
|
|
59
|
-
* honoured and was not. A missing entry here means "no Meta-approved template
|
|
60
|
-
* exists for this type", which is a fact about the Meta account, not a bug:
|
|
61
|
-
* six of the 28 types have one, and the rest legitimately fall back to
|
|
62
|
-
* free-form (which Meta accepts inside the 24-hour customer-service window
|
|
63
|
-
* and rejects outside it — the runner already logs that rejection).
|
|
64
|
-
*
|
|
65
|
-
* Getting six approved templates is a Meta Business review process, not a
|
|
66
|
-
* code change, so listing all 28 here with empty values would be a
|
|
67
|
-
* `Record<…>` that lies about coverage.
|
|
68
|
-
*
|
|
69
|
-
* The six that ARE approved are the order lifecycle, which is also the set
|
|
70
|
-
* gated behind the buyer's ₹10 addon above — the two lists agree on purpose.
|
|
71
|
-
*/
|
|
72
|
-
const WHATSAPP_TEMPLATE_FIELD = {
|
|
73
|
-
order_placed: "whatsappTemplateOrderPlaced",
|
|
74
|
-
order_confirmed: "whatsappTemplateOrderConfirmed",
|
|
75
|
-
order_shipped: "whatsappTemplateOrderShipped",
|
|
76
|
-
order_delivered: "whatsappTemplateOrderDelivered",
|
|
77
|
-
order_cancelled: "whatsappTemplateOrderCancelled",
|
|
78
|
-
refund_initiated: "whatsappTemplateRefundInitiated",
|
|
79
|
-
};
|
|
80
|
-
function resolveWhatsAppTemplateName(type, creds) {
|
|
81
|
-
const field = WHATSAPP_TEMPLATE_FIELD[type];
|
|
82
|
-
return (field ? creds[field] : undefined)?.trim() ?? "";
|
|
83
|
-
}
|
|
84
34
|
export async function markNotificationRead(id) {
|
|
85
35
|
await notificationRepository.markAsRead(id);
|
|
86
36
|
}
|
|
@@ -103,15 +53,15 @@ export async function getUnreadNotificationCount(userId) {
|
|
|
103
53
|
/**
|
|
104
54
|
* Create an in-app notification and fan out to enabled external channels.
|
|
105
55
|
*
|
|
106
|
-
* In-app is always written.
|
|
56
|
+
* In-app is always written. Email is sent only when:
|
|
107
57
|
* 1. The channel is enabled in siteSettings.notificationChannels
|
|
108
58
|
* 2. Notification priority meets the channel's minPriority threshold
|
|
109
59
|
* 3. The optional `types` filter includes this notification type (or is absent)
|
|
110
|
-
* 4. The contact field (userEmail
|
|
60
|
+
* 4. The contact field (userEmail) is supplied by the caller
|
|
111
61
|
* 5. The required credentials are set in siteSettings.credentials
|
|
112
62
|
*/
|
|
113
63
|
export async function sendNotification(input) {
|
|
114
|
-
const { userEmail,
|
|
64
|
+
const { userEmail, emailHtml, audience, eligibilityOverride, ...notifInput } = input;
|
|
115
65
|
/*
|
|
116
66
|
* Fill `actionUrl` when the caller did not.
|
|
117
67
|
*
|
|
@@ -153,12 +103,12 @@ export async function sendNotification(input) {
|
|
|
153
103
|
* suppresses the emailed/messaged COPY, not the notification.
|
|
154
104
|
*
|
|
155
105
|
* One map gates both channels deliberately. If a type is ever genuinely
|
|
156
|
-
*
|
|
106
|
+
* worth a channel of its own, split the map then rather than
|
|
157
107
|
* loosening this early return.
|
|
158
108
|
*/
|
|
159
109
|
if (!isEmailEligible(notifInput.type) &&
|
|
160
110
|
eligibilityOverride !== "staff_requested") {
|
|
161
|
-
return { notification, email: "skipped"
|
|
111
|
+
return { notification, email: "skipped" };
|
|
162
112
|
}
|
|
163
113
|
// Load channel config + credentials (one Firestore read).
|
|
164
114
|
let settings;
|
|
@@ -168,7 +118,7 @@ export async function sendNotification(input) {
|
|
|
168
118
|
catch (err) {
|
|
169
119
|
void normalizeError(err);
|
|
170
120
|
serverLogger.warn("sendNotification: could not load siteSettings — skipping external channels", { err });
|
|
171
|
-
return { notification, email: "skipped"
|
|
121
|
+
return { notification, email: "skipped" };
|
|
172
122
|
}
|
|
173
123
|
const channels = settings.notificationChannels ?? DEFAULT_NOTIFICATION_CHANNELS;
|
|
174
124
|
const creds = settings.credentials ?? {};
|
|
@@ -200,7 +150,6 @@ export async function sendNotification(input) {
|
|
|
200
150
|
? userRepository.revealUser(fetchedUserDoc)
|
|
201
151
|
: null;
|
|
202
152
|
const resolvedEmail = userEmail ?? fetchedUserPlain?.email ?? undefined;
|
|
203
|
-
const resolvedPhone = userPhone ?? fetchedUserPlain?.phoneNumber ?? undefined;
|
|
204
153
|
// Map NotificationType to a NotificationTypePrefs key.
|
|
205
154
|
/*
|
|
206
155
|
* 🛑 COMPLETE and compile-checked: `Record<NotificationType, …>`, not
|
|
@@ -263,7 +212,7 @@ export async function sendNotification(input) {
|
|
|
263
212
|
const typeKey = typeToPrefsKey[type];
|
|
264
213
|
// If the user has explicitly disabled this notification type, skip all external channels.
|
|
265
214
|
if (typeKey && userTypePrefs[typeKey] === false) {
|
|
266
|
-
return { notification, email: "skipped"
|
|
215
|
+
return { notification, email: "skipped" };
|
|
267
216
|
}
|
|
268
217
|
// Email channel — admin enabled AND user hasn't opted out
|
|
269
218
|
let emailStatus = "skipped";
|
|
@@ -310,54 +259,10 @@ export async function sendNotification(input) {
|
|
|
310
259
|
}
|
|
311
260
|
}
|
|
312
261
|
}
|
|
313
|
-
// WhatsApp channel — admin enabled AND user hasn't opted out AND (for
|
|
314
|
-
// order-lifecycle types) the buyer paid the per-order WhatsApp addon.
|
|
315
|
-
let whatsappStatus = "skipped";
|
|
316
|
-
if (channels.whatsapp.enabled &&
|
|
317
|
-
userChannelPrefs.whatsapp !== false &&
|
|
318
|
-
resolvedPhone &&
|
|
319
|
-
meetsMinPriority(priority, channels.whatsapp.minPriority) &&
|
|
320
|
-
(!channels.whatsapp.types?.length || channels.whatsapp.types.includes(type)) &&
|
|
321
|
-
(!ORDER_GATED_TYPES.has(type) || input.orderWhatsappAddonPaid === true)) {
|
|
322
|
-
const phoneNumberId = creds.whatsappPhoneNumberId?.trim() ?? "";
|
|
323
|
-
const accessToken = creds.whatsappCloudApiToken?.trim() ?? "";
|
|
324
|
-
if (phoneNumberId && accessToken) {
|
|
325
|
-
try {
|
|
326
|
-
const { jobId } = await enqueueJob({
|
|
327
|
-
jobType: "whatsappNotify",
|
|
328
|
-
payload: {
|
|
329
|
-
toPhone: resolvedPhone,
|
|
330
|
-
title,
|
|
331
|
-
message,
|
|
332
|
-
type,
|
|
333
|
-
templateName: resolveWhatsAppTemplateName(type, creds),
|
|
334
|
-
templateLanguage: creds.whatsappTemplateLanguage ?? "en",
|
|
335
|
-
notificationId: notification.id,
|
|
336
|
-
phoneNumberId,
|
|
337
|
-
accessToken,
|
|
338
|
-
},
|
|
339
|
-
requestedBy: input.userId,
|
|
340
|
-
});
|
|
341
|
-
whatsappStatus = "queued";
|
|
342
|
-
await notificationRepository
|
|
343
|
-
.update(notification.id, { whatsappStatus: "queued", whatsappJobId: jobId })
|
|
344
|
-
.catch((err) => {
|
|
345
|
-
void normalizeError(err);
|
|
346
|
-
serverLogger.warn("sendNotification: failed to write whatsappJobId onto notification doc", { notificationId: notification.id });
|
|
347
|
-
});
|
|
348
|
-
}
|
|
349
|
-
catch (err) {
|
|
350
|
-
void normalizeError(err);
|
|
351
|
-
serverLogger.error("sendNotification: WhatsApp job enqueue failed", { userId: input.userId, err });
|
|
352
|
-
whatsappStatus = "failed";
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
262
|
serverLogger.info("sendNotification: dispatched", {
|
|
357
263
|
userId: input.userId,
|
|
358
264
|
type,
|
|
359
265
|
email: emailStatus,
|
|
360
|
-
whatsapp: whatsappStatus,
|
|
361
266
|
});
|
|
362
|
-
return { notification, email: emailStatus
|
|
267
|
+
return { notification, email: emailStatus };
|
|
363
268
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Users, Package, Store, ShoppingBag, FileText, Settings, Shield, BarChart2, AlertTriangle, BookOpen,
|
|
2
|
+
import { Users, Package, Store, ShoppingBag, FileText, Settings, Shield, BarChart2, AlertTriangle, BookOpen, CreditCard, } from "lucide-react";
|
|
3
3
|
import { Alert, Div, Grid, Heading, Row, Section, Stack, Text, TextLink } from "../../../ui/index.js";
|
|
4
4
|
import { ROUTES } from "../../../next/routing/route-map.js";
|
|
5
5
|
const CLS_WARN_PANEL = "rounded-2xl border border-amber-200 bg-warning-surface dark:border-amber-800 p-[var(--appkit-space-6)]";
|
|
@@ -69,13 +69,6 @@ const GUIDE_CARDS = [
|
|
|
69
69
|
href: String(ROUTES.ADMIN.GUIDE_TRUST),
|
|
70
70
|
permission: "admin:moderation:read",
|
|
71
71
|
},
|
|
72
|
-
{
|
|
73
|
-
Icon: MessageSquare,
|
|
74
|
-
title: "WhatsApp Integration",
|
|
75
|
-
description: "Connect Meta's WhatsApp Business Cloud API — Business Manager setup, phone registration, access tokens, and message templates.",
|
|
76
|
-
href: String(ROUTES.ADMIN.GUIDE_WHATSAPP),
|
|
77
|
-
permission: "admin:site:read",
|
|
78
|
-
},
|
|
79
72
|
{
|
|
80
73
|
Icon: CreditCard,
|
|
81
74
|
title: "Payments (Razorpay)",
|
|
@@ -32,7 +32,7 @@ export interface AdminOrderEditorViewProps {
|
|
|
32
32
|
paymentReviewOutcome?: string;
|
|
33
33
|
/**
|
|
34
34
|
* Paid add-ons + applied coupon for this order. Operational, not decorative:
|
|
35
|
-
* `
|
|
35
|
+
* `giftWrapAddon` tells the packer to wrap the parcel, and the
|
|
36
36
|
* coupon is what an admin needs when reconciling a discounted order.
|
|
37
37
|
*/
|
|
38
38
|
addons?: OrderAddonBadgesOrder;
|
|
@@ -198,8 +198,6 @@ export function AdminOrdersView({ children, ...props }) {
|
|
|
198
198
|
// Straight off the order document — the admin list route returns
|
|
199
199
|
// raw docs, so these need no serializer entry (cf. Root Cause #38).
|
|
200
200
|
addons: {
|
|
201
|
-
whatsappNotifyAddon: Boolean(item.whatsappNotifyAddon),
|
|
202
|
-
whatsappNotifyFee: Number(item.whatsappNotifyFee ?? 0) || undefined,
|
|
203
201
|
giftWrapAddon: Boolean(item.giftWrapAddon),
|
|
204
202
|
giftWrapFee: Number(item.giftWrapFee ?? 0) || undefined,
|
|
205
203
|
giftWrapMessage: toStringValue(item.giftWrapMessage) || undefined,
|
|
@@ -6,7 +6,8 @@ import React from "react";
|
|
|
6
6
|
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
|
7
7
|
import { ProductInlineSelect } from "../../seller/components/ProductInlineSelect.js";
|
|
8
8
|
import { CategoryInlineSelect } from "../../seller/components/CategoryInlineSelect.js";
|
|
9
|
-
import { Button, Checkbox, Div, Input, Modal, Row, Select, Stack, Text, Textarea, useToast } from "../../../ui/index.js";
|
|
9
|
+
import { Button, Checkbox, Div, Input, Modal, PaginatedSelect, Row, Select, Stack, Text, Textarea, useToast } from "../../../ui/index.js";
|
|
10
|
+
import { ICON_KEY_OPTIONS } from "./sections/icon-options.js";
|
|
10
11
|
import { FormErrorSummary, applyZodIssues } from "../../../ui/forms/index.js";
|
|
11
12
|
import { homepageSectionFormSchema, } from "../schemas/homepage-section-form.js";
|
|
12
13
|
import { SectionForm, useSectionFormNav } from "../../shell/index.js";
|
|
@@ -753,7 +754,7 @@ export function AdminSectionsView({ children }) {
|
|
|
753
754
|
return (_jsxs(Div, { className: CLS_SECTION_PANEL, children: [_jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: "Welcome Section Builder" }), _jsx(Input, { label: "Headline (h1)", value: welcomeBuilder.h1, onChange: (e) => setWelcomeBuilder((prev) => ({ ...prev, h1: e.target.value })) }), _jsx(Input, { label: "Subtitle", value: welcomeBuilder.subtitle, onChange: (e) => setWelcomeBuilder((prev) => ({ ...prev, subtitle: e.target.value })) }), _jsx(Textarea, { label: "Description", value: welcomeBuilder.description, onChange: (e) => setWelcomeBuilder((prev) => ({ ...prev, description: e.target.value })), rows: 3 }), _jsx(Checkbox, { checked: welcomeBuilder.showCTA, label: "Show CTA button", onChange: (e) => setWelcomeBuilder((prev) => ({ ...prev, showCTA: e.target.checked })) }), welcomeBuilder.showCTA ? (_jsxs(_Fragment, { children: [_jsx(Input, { label: "CTA text", value: welcomeBuilder.ctaText, onChange: (e) => setWelcomeBuilder((prev) => ({ ...prev, ctaText: e.target.value })) }), _jsx(Input, { label: "CTA link", value: welcomeBuilder.ctaLink, onChange: (e) => setWelcomeBuilder((prev) => ({ ...prev, ctaLink: e.target.value })), placeholder: String(ROUTES.PUBLIC.PRODUCTS) })] })) : null] }));
|
|
754
755
|
}
|
|
755
756
|
function renderTrustIndicatorsBuilder() {
|
|
756
|
-
return (_jsxs(Div, { className: CLS_SECTION_PANEL, children: [_jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: "Trust Indicators Builder" }), _jsx(Input, { label: "Section title", value: trustIndicatorsBuilder.title, onChange: (e) => setTrustIndicatorsBuilder((prev) => ({ ...prev, title: e.target.value })) }), trustIndicatorsBuilder.indicators.map((ind, index) => (_jsxs(Stack, { className: `${__P.p3}`, gap: "sm", rounded: "md", border: "default", children: [_jsxs(Row, { align: "center", justify: "between", children: [_jsxs(Text, { className: "tracking-wide", color: "muted", size: "xs", weight: "semibold", transform: "uppercase", children: ["Indicator ", index + 1] }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => setTrustIndicatorsBuilder((prev) => ({ ...prev, indicators: prev.indicators.filter((_, i) => i !== index) })), children: "Remove" })] }), _jsx(
|
|
757
|
+
return (_jsxs(Div, { className: CLS_SECTION_PANEL, children: [_jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: "Trust Indicators Builder" }), _jsx(Input, { label: "Section title", value: trustIndicatorsBuilder.title, onChange: (e) => setTrustIndicatorsBuilder((prev) => ({ ...prev, title: e.target.value })) }), trustIndicatorsBuilder.indicators.map((ind, index) => (_jsxs(Stack, { className: `${__P.p3}`, gap: "sm", rounded: "md", border: "default", children: [_jsxs(Row, { align: "center", justify: "between", children: [_jsxs(Text, { className: "tracking-wide", color: "muted", size: "xs", weight: "semibold", transform: "uppercase", children: ["Indicator ", index + 1] }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => setTrustIndicatorsBuilder((prev) => ({ ...prev, indicators: prev.indicators.filter((_, i) => i !== index) })), children: "Remove" })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { color: "muted", size: "xs", weight: "semibold", children: "Icon" }), _jsx(PaginatedSelect, { options: ICON_KEY_OPTIONS, value: ind.icon || null, ariaLabel: `Icon for indicator ${index + 1}`, placeholder: "Choose an icon", onChange: (value) => setTrustIndicatorsBuilder((prev) => { const next = [...prev.indicators]; next[index] = { ...next[index], icon: value ?? "" }; return { ...prev, indicators: next }; }) })] }), _jsx(Input, { label: "Title", value: ind.title, onChange: (e) => setTrustIndicatorsBuilder((prev) => { const next = [...prev.indicators]; next[index] = { ...next[index], title: e.target.value }; return { ...prev, indicators: next }; }) }), _jsx(Input, { label: "Description", value: ind.description, onChange: (e) => setTrustIndicatorsBuilder((prev) => { const next = [...prev.indicators]; next[index] = { ...next[index], description: e.target.value }; return { ...prev, indicators: next }; }) })] }, ind.id))), _jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: () => setTrustIndicatorsBuilder((prev) => ({ ...prev, indicators: [...prev.indicators, { id: `ti-${Date.now()}`, icon: "verify", title: "", description: "" }] })), children: "+ Add indicator" })] }));
|
|
757
758
|
}
|
|
758
759
|
function renderCategoriesBuilder() {
|
|
759
760
|
return (_jsxs(Div, { className: CLS_SECTION_PANEL, children: [_jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: "Categories Section Builder" }), _jsx(Input, { label: "Section title", value: categoriesBuilder.title, onChange: (e) => setCategoriesBuilder((prev) => ({ ...prev, title: e.target.value })) }), _jsx(Input, { label: LBL_VIEW_MORE, value: categoriesBuilder.viewMoreLabel, onChange: (e) => setCategoriesBuilder((prev) => ({ ...prev, viewMoreLabel: e.target.value })), placeholder: PH_VIEW_MORE, helperText: HELP_VIEW_MORE }), _jsx(Input, { label: "Max categories (4\u201312)", type: "number", min: 4, max: 12, value: String(categoriesBuilder.maxCategories), onChange: (e) => setCategoriesBuilder((prev) => ({ ...prev, maxCategories: Math.min(12, Math.max(4, Number(e.target.value) || 4)) })) }), _jsx(Checkbox, { checked: categoriesBuilder.autoScroll, label: "Auto-scroll", onChange: (e) => setCategoriesBuilder((prev) => ({ ...prev, autoScroll: e.target.checked })) }), _jsx(Input, { label: LBL_SCROLL_INTERVAL, type: "number", min: 1000, step: 500, value: String(categoriesBuilder.scrollInterval), onChange: (e) => setCategoriesBuilder((prev) => ({ ...prev, scrollInterval: Math.max(1000, Number(e.target.value) || 1000) })) }), _jsx(Checkbox, { checked: categoriesBuilder.loop, label: "Loop carousel", onChange: (e) => setCategoriesBuilder((prev) => ({ ...prev, loop: e.target.checked })) })] }));
|