@mohasinac/appkit 2.7.27 → 2.7.31
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/_internal/client/features/filters/filter-load-options.d.ts +29 -0
- package/dist/_internal/client/features/filters/filter-load-options.js +98 -0
- package/dist/_internal/client/features/layout/DashboardLayoutClient.js +1 -1
- package/dist/_internal/server/features/checkout/actions.js +42 -35
- package/dist/_internal/server/features/classified/adapters.d.ts +21 -0
- package/dist/_internal/server/features/classified/adapters.js +24 -0
- package/dist/_internal/server/features/classified/index.d.ts +3 -0
- package/dist/_internal/server/features/classified/index.js +3 -0
- package/dist/_internal/server/features/classified/metadata.d.ts +8 -0
- package/dist/_internal/server/features/classified/metadata.js +43 -0
- package/dist/_internal/server/features/classified/og.d.ts +30 -0
- package/dist/_internal/server/features/classified/og.js +76 -0
- package/dist/_internal/server/features/digital-code/adapters.d.ts +21 -0
- package/dist/_internal/server/features/digital-code/adapters.js +29 -0
- package/dist/_internal/server/features/digital-code/index.d.ts +3 -0
- package/dist/_internal/server/features/digital-code/index.js +3 -0
- package/dist/_internal/server/features/digital-code/metadata.d.ts +8 -0
- package/dist/_internal/server/features/digital-code/metadata.js +36 -0
- package/dist/_internal/server/features/digital-code/og.d.ts +27 -0
- package/dist/_internal/server/features/digital-code/og.js +77 -0
- package/dist/_internal/server/features/live/adapters.d.ts +21 -0
- package/dist/_internal/server/features/live/adapters.js +34 -0
- package/dist/_internal/server/features/live/index.d.ts +3 -0
- package/dist/_internal/server/features/live/index.js +3 -0
- package/dist/_internal/server/features/live/metadata.d.ts +8 -0
- package/dist/_internal/server/features/live/metadata.js +39 -0
- package/dist/_internal/server/features/live/og.d.ts +27 -0
- package/dist/_internal/server/features/live/og.js +72 -0
- package/dist/_internal/server/features/promotions/actions.js +4 -3
- package/dist/_internal/server/jobs/core/onOrderCreate.js +19 -16
- package/dist/_internal/server/jobs/core/onOrderStatusChange.js +25 -22
- package/dist/_internal/server/jobs/core/onProductWrite.js +48 -58
- package/dist/_internal/shared/actions/action-registry.js +504 -9
- package/dist/_internal/shared/features/blog/schema.d.ts +4 -4
- package/dist/_internal/shared/features/brands/schema.d.ts +5 -5
- package/dist/_internal/shared/features/cart/schema.d.ts +18 -18
- package/dist/_internal/shared/features/categories/bundle-copy.d.ts +1 -1
- package/dist/_internal/shared/features/categories/bundle-schemas.d.ts +38 -38
- package/dist/_internal/shared/features/events/schema.d.ts +8 -8
- package/dist/_internal/shared/features/orders/schema.d.ts +8 -8
- package/dist/_internal/shared/features/products/schema.d.ts +42 -42
- package/dist/_internal/shared/features/promotions/schema.d.ts +4 -4
- package/dist/_internal/shared/features/reviews/schema.d.ts +1 -1
- package/dist/_internal/shared/media/limits.js +4 -3
- package/dist/client.d.ts +4 -0
- package/dist/client.js +2 -0
- package/dist/configs/next.js +4 -0
- package/dist/constants/api-endpoints.d.ts +6 -0
- package/dist/constants/api-endpoints.js +2 -0
- package/dist/constants/field-names.d.ts +1 -0
- package/dist/constants/field-names.js +1 -0
- package/dist/constants/table-keys.d.ts +6 -0
- package/dist/constants/table-keys.js +9 -0
- package/dist/core/hooks/useSyncManager.js +9 -8
- package/dist/features/about/components/FeesView.js +10 -7
- package/dist/features/about/components/HelpPageView.js +10 -1
- package/dist/features/about/components/HowAuctionsWorkView.js +10 -1
- package/dist/features/about/components/HowCheckoutWorksView.js +10 -1
- package/dist/features/about/components/HowOffersWorkView.js +10 -1
- package/dist/features/about/components/HowOrdersWorkView.js +10 -1
- package/dist/features/about/components/HowPayoutsWorkView.js +10 -1
- package/dist/features/about/components/HowPreOrdersWorkView.js +10 -1
- package/dist/features/about/components/HowReviewsWorkView.js +10 -1
- package/dist/features/about/components/PublicProfileView.js +46 -16
- package/dist/features/about/components/SecurityPrivacyView.js +7 -1
- package/dist/features/account/components/AddressesIndexListing.js +2 -2
- package/dist/features/account/components/NotificationBell.js +27 -16
- package/dist/features/account/components/NotificationPreferencesPanel.d.ts +9 -0
- package/dist/features/account/components/NotificationPreferencesPanel.js +91 -0
- package/dist/features/account/components/UserSidebar.js +4 -4
- package/dist/features/account/components/UserSupportView.js +16 -6
- package/dist/features/account/components/index.d.ts +2 -0
- package/dist/features/account/components/index.js +1 -0
- package/dist/features/account/schemas/index.d.ts +4 -4
- package/dist/features/account/types/index.d.ts +34 -0
- package/dist/features/admin/actions/admin-actions.js +4 -3
- package/dist/features/admin/actions/notification-actions.d.ts +24 -22
- package/dist/features/admin/actions/notification-actions.js +122 -22
- package/dist/features/admin/components/AdminAdEditorView.js +29 -15
- package/dist/features/admin/components/AdminAddressEditorView.d.ts +7 -0
- package/dist/features/admin/components/AdminAddressEditorView.js +117 -0
- package/dist/features/admin/components/AdminAdsView.js +22 -28
- package/dist/features/admin/components/AdminAllEventEntriesView.js +6 -1
- package/dist/features/admin/components/AdminBidsView.js +10 -3
- package/dist/features/admin/components/AdminBlogEditorView.js +2 -2
- package/dist/features/admin/components/AdminBlogView.js +8 -3
- package/dist/features/admin/components/AdminBrandsView.js +7 -2
- package/dist/features/admin/components/AdminBundleEditorView.js +5 -2
- package/dist/features/admin/components/AdminCarouselEditorView.js +6 -3
- package/dist/features/admin/components/AdminCarouselView.js +13 -7
- package/dist/features/admin/components/AdminCategoriesView.js +8 -3
- package/dist/features/admin/components/AdminCategoryEditorView.js +12 -9
- package/dist/features/admin/components/AdminContactView.js +6 -1
- package/dist/features/admin/components/AdminCouponEditorView.js +8 -2
- package/dist/features/admin/components/AdminCouponsView.js +6 -1
- package/dist/features/admin/components/AdminEmployeeEditorView.js +12 -9
- package/dist/features/admin/components/AdminFaqEditorView.js +2 -2
- package/dist/features/admin/components/AdminFaqsView.js +7 -2
- package/dist/features/admin/components/AdminFeatureFlagsView.js +58 -13
- package/dist/features/admin/components/AdminFeaturesView.js +4 -3
- package/dist/features/admin/components/AdminMediaView.js +9 -8
- package/dist/features/admin/components/AdminNavigationView.js +10 -17
- package/dist/features/admin/components/AdminNewsletterView.js +6 -1
- package/dist/features/admin/components/AdminNotificationsView.js +11 -3
- package/dist/features/admin/components/AdminOrdersView.js +6 -1
- package/dist/features/admin/components/AdminPayoutsView.js +10 -3
- package/dist/features/admin/components/AdminPrizeDrawsView.js +10 -3
- package/dist/features/admin/components/AdminProductEditorView.d.ts +3 -1
- package/dist/features/admin/components/AdminProductEditorView.js +12 -9
- package/dist/features/admin/components/AdminProductsView.js +18 -12
- package/dist/features/admin/components/AdminReturnRequestsView.js +5 -4
- package/dist/features/admin/components/AdminReviewsView.js +36 -26
- package/dist/features/admin/components/AdminScammerEditorView.js +3 -2
- package/dist/features/admin/components/AdminScammersView.js +28 -26
- package/dist/features/admin/components/AdminSectionsView.js +112 -99
- package/dist/features/admin/components/AdminSessionsView.js +10 -3
- package/dist/features/admin/components/AdminSidebar.js +4 -4
- package/dist/features/admin/components/AdminSiteSettingsView.js +45 -4
- package/dist/features/admin/components/AdminStoresView.js +6 -1
- package/dist/features/admin/components/AdminSublistingCategoriesView.js +0 -1
- package/dist/features/admin/components/AdminSupportTicketDetailView.js +1 -1
- package/dist/features/admin/components/AdminSupportTicketsView.js +32 -34
- package/dist/features/admin/components/AdminTeamView.js +6 -1
- package/dist/features/admin/components/AdminUserEditorView.js +8 -15
- package/dist/features/admin/components/AdminUsersView.js +6 -1
- package/dist/features/admin/components/AdminViewCards.js +4 -3
- package/dist/features/admin/components/DashboardStats.js +2 -2
- package/dist/features/admin/components/DataTable.js +2 -2
- package/dist/features/admin/components/index.d.ts +2 -0
- package/dist/features/admin/components/index.js +1 -0
- package/dist/features/admin/components/sections/adminSectionsTypes.js +4 -3
- package/dist/features/admin/repository/chat.repository.js +8 -8
- package/dist/features/admin/repository/site-settings.repository.d.ts +1 -0
- package/dist/features/admin/repository/site-settings.repository.js +11 -9
- package/dist/features/admin/schemas/firestore.d.ts +38 -0
- package/dist/features/admin/schemas/firestore.js +15 -0
- package/dist/features/auctions/components/AuctionBidsTable.d.ts +13 -0
- package/dist/features/auctions/components/AuctionBidsTable.js +62 -0
- package/dist/features/auctions/components/AuctionDetailPageView.js +15 -21
- package/dist/features/auctions/components/AuctionFilters.d.ts +7 -1
- package/dist/features/auctions/components/AuctionFilters.js +3 -2
- package/dist/features/auctions/components/MarketplaceAuctionCard.js +18 -9
- package/dist/features/auctions/components/MarketplaceAuctionGrid.js +3 -2
- package/dist/features/auctions/components/index.d.ts +2 -1
- package/dist/features/auctions/components/index.js +1 -1
- package/dist/features/auctions/schemas/index.d.ts +14 -14
- package/dist/features/auth/actions/realtime-token-actions.js +19 -4
- package/dist/features/auth/components/RegisterForm.js +8 -4
- package/dist/features/auth/consent-otp.d.ts +2 -0
- package/dist/features/auth/consent-otp.js +8 -0
- package/dist/features/auth/schemas/firestore.d.ts +2 -0
- package/dist/features/auth/schemas/firestore.js +18 -25
- package/dist/features/auth/schemas/index.d.ts +2 -2
- package/dist/features/before-after/api/[id]/route.js +8 -6
- package/dist/features/before-after/schemas/index.d.ts +2 -2
- package/dist/features/blog/actions/blog-actions.d.ts +3 -3
- package/dist/features/blog/components/BlogIndexListing.js +21 -2
- package/dist/features/blog/components/BlogPostForm.js +11 -5
- package/dist/features/blog/components/BlogPostView.js +10 -2
- package/dist/features/blog/schemas/index.d.ts +18 -18
- package/dist/features/cart/components/CartDrawer.js +3 -2
- package/dist/features/cart/components/CartView.js +2 -1
- package/dist/features/cart/components/CheckoutView.js +2 -1
- package/dist/features/cart/repository/cart.repository.js +4 -3
- package/dist/features/cart/utils/pending-ops.d.ts +1 -1
- package/dist/features/categories/api/[id]/route.js +4 -3
- package/dist/features/categories/api/route.js +4 -3
- package/dist/features/categories/components/BrandDetailPageView.js +2 -2
- package/dist/features/categories/components/BrandDetailTabs.d.ts +5 -1
- package/dist/features/categories/components/BrandDetailTabs.js +30 -16
- package/dist/features/categories/components/BundleCollage.d.ts +8 -0
- package/dist/features/categories/components/BundleCollage.js +50 -0
- package/dist/features/categories/components/BundleDetailView.js +2 -10
- package/dist/features/categories/components/BundleItemsPicker.js +20 -13
- package/dist/features/categories/components/CategoriesIndexListing.js +28 -9
- package/dist/features/categories/components/CategoryDetailPageView.js +2 -2
- package/dist/features/categories/components/CategoryDetailTabs.d.ts +5 -1
- package/dist/features/categories/components/CategoryDetailTabs.js +31 -16
- package/dist/features/categories/components/CategoryProductsListing.js +52 -13
- package/dist/features/categories/components/index.d.ts +2 -0
- package/dist/features/categories/components/index.js +1 -0
- package/dist/features/categories/schemas/index.d.ts +6 -6
- package/dist/features/checkout/actions/checkout-actions.js +4 -3
- package/dist/features/checkout/hooks/usePaymentCheckout.js +31 -22
- package/dist/features/classified/components/ClassifiedFilters.d.ts +12 -0
- package/dist/features/classified/components/ClassifiedFilters.js +20 -0
- package/dist/features/classified/components/ClassifiedIndexListing.d.ts +4 -0
- package/dist/features/classified/components/ClassifiedIndexListing.js +110 -0
- package/dist/features/classified/components/ClassifiedListView.d.ts +6 -0
- package/dist/features/classified/components/ClassifiedListView.js +43 -0
- package/dist/features/classified/components/index.d.ts +6 -0
- package/dist/features/classified/components/index.js +3 -0
- package/dist/features/collections/api/[slug]/route.js +16 -12
- package/dist/features/collections/schemas/index.d.ts +5 -5
- package/dist/features/consultation/api/[id]/route.js +12 -9
- package/dist/features/consultation/components/ConsultationForm.js +3 -2
- package/dist/features/consultation/schemas/index.d.ts +6 -6
- package/dist/features/contact/email.js +77 -116
- package/dist/features/copilot/components/AdminCopilotView.js +12 -2
- package/dist/features/corporate/api/[id]/route.js +12 -9
- package/dist/features/corporate/components/CorporateInquiryForm.js +2 -1
- package/dist/features/digital-codes/components/DigitalCodeFilters.d.ts +12 -0
- package/dist/features/digital-codes/components/DigitalCodeFilters.js +17 -0
- package/dist/features/digital-codes/components/DigitalCodesIndexListing.d.ts +4 -0
- package/dist/features/digital-codes/components/DigitalCodesIndexListing.js +107 -0
- package/dist/features/digital-codes/components/DigitalCodesListView.d.ts +6 -0
- package/dist/features/digital-codes/components/DigitalCodesListView.js +37 -0
- package/dist/features/digital-codes/components/index.d.ts +6 -0
- package/dist/features/digital-codes/components/index.js +3 -0
- package/dist/features/events/actions/event-actions.js +4 -3
- package/dist/features/events/components/AdminEventEditorView.js +3 -2
- package/dist/features/events/components/AdminEventsView.js +4 -2
- package/dist/features/events/components/EventPollWidget.js +5 -5
- package/dist/features/events/components/EventsIndexListing.js +2 -2
- package/dist/features/events/repository/event-entry.repository.d.ts +1 -0
- package/dist/features/events/repository/event-entry.repository.js +5 -0
- package/dist/features/events/schemas/index.d.ts +20 -20
- package/dist/features/faq/actions/faq-actions.d.ts +8 -8
- package/dist/features/faq/components/ContactCTA.js +3 -1
- package/dist/features/faq/components/FAQAccordion.js +1 -1
- package/dist/features/faq/schemas/index.d.ts +4 -4
- package/dist/features/filters/AsyncFacetSection.d.ts +18 -0
- package/dist/features/filters/AsyncFacetSection.js +65 -0
- package/dist/features/filters/index.d.ts +2 -0
- package/dist/features/filters/index.js +1 -0
- package/dist/features/forms/Checkbox.js +2 -2
- package/dist/features/forms/Input.js +2 -2
- package/dist/features/forms/Radio.js +5 -5
- package/dist/features/forms/Select.js +2 -2
- package/dist/features/forms/Slider.js +2 -2
- package/dist/features/forms/Textarea.js +2 -2
- package/dist/features/homepage/api/[id]/route.js +4 -3
- package/dist/features/homepage/api/sections/[id]/route.js +12 -9
- package/dist/features/homepage/components/AdSlot.js +2 -1
- package/dist/features/homepage/components/AdvertisementBanner.js +5 -3
- package/dist/features/homepage/components/BrandsSection.js +2 -2
- package/dist/features/homepage/components/CTABannerSection.js +2 -2
- package/dist/features/homepage/components/CharacterHotspot.d.ts +2 -2
- package/dist/features/homepage/components/CharacterHotspot.js +119 -157
- package/dist/features/homepage/components/CharacterHotspotForm.js +25 -20
- package/dist/features/homepage/components/CustomCardsSection.js +8 -6
- package/dist/features/homepage/components/FeaturedProductsSection.js +2 -2
- package/dist/features/homepage/components/GoogleReviewsSection.js +3 -3
- package/dist/features/homepage/components/HeroBanner.js +5 -4
- package/dist/features/homepage/components/HeroCarousel.js +13 -26
- package/dist/features/homepage/components/HowItWorksSection.js +4 -2
- package/dist/features/homepage/components/SocialFeedSection.js +2 -2
- package/dist/features/homepage/components/SocialPostCard.js +3 -2
- package/dist/features/homepage/lib/social-feed-fetcher.js +6 -3
- package/dist/features/layout/AppLayoutShell.d.ts +8 -2
- package/dist/features/layout/AppLayoutShell.js +54 -78
- package/dist/features/layout/BottomActions.js +43 -48
- package/dist/features/live/components/LiveItemFilters.d.ts +12 -0
- package/dist/features/live/components/LiveItemFilters.js +24 -0
- package/dist/features/live/components/LiveItemsIndexListing.d.ts +4 -0
- package/dist/features/live/components/LiveItemsIndexListing.js +108 -0
- package/dist/features/live/components/LiveItemsListView.d.ts +6 -0
- package/dist/features/live/components/LiveItemsListView.js +43 -0
- package/dist/features/live/components/index.d.ts +6 -0
- package/dist/features/live/components/index.js +3 -0
- package/dist/features/media/MediaLightbox.js +8 -14
- package/dist/features/media/types/index.d.ts +2 -2
- package/dist/features/media/upload/CameraCapture.js +2 -2
- package/dist/features/media/upload/ImageUpload.js +4 -2
- package/dist/features/media/upload/MediaUploadField.js +24 -8
- package/dist/features/media/upload/MediaUploadList.js +1 -1
- package/dist/features/orders/actions/order-actions.js +4 -3
- package/dist/features/orders/components/MarketplaceOrderCard.js +2 -1
- package/dist/features/orders/components/OrderFilters.js +12 -9
- package/dist/features/orders/schemas/index.d.ts +6 -6
- package/dist/features/pre-orders/api/[id]/route.js +12 -9
- package/dist/features/pre-orders/components/MarketplacePreorderCard.js +5 -4
- package/dist/features/pre-orders/components/PreOrderDetailPageView.js +29 -22
- package/dist/features/pre-orders/components/PreOrderFilters.d.ts +7 -1
- package/dist/features/pre-orders/components/PreOrderFilters.js +3 -2
- package/dist/features/pre-orders/components/PreOrdersIndexListing.js +17 -52
- package/dist/features/pre-orders/components/PreorderCard.d.ts +0 -9
- package/dist/features/pre-orders/components/PreorderCard.js +2 -13
- package/dist/features/pre-orders/components/index.d.ts +1 -1
- package/dist/features/pre-orders/components/index.js +1 -1
- package/dist/features/pre-orders/schemas/index.d.ts +10 -10
- package/dist/features/products/api/[id]/route.js +9 -7
- package/dist/features/products/api/route.js +1 -1
- package/dist/features/products/columns/productTableColumns.js +3 -3
- package/dist/features/products/components/AuctionsIndexListing.js +15 -51
- package/dist/features/products/components/BidHistory.js +2 -2
- package/dist/features/products/components/CompareOverlay.d.ts +2 -0
- package/dist/features/products/components/CompareOverlay.js +3 -2
- package/dist/features/products/components/MakeOfferButton.js +20 -14
- package/dist/features/products/components/MarketplacePrizeDrawCard.js +2 -1
- package/dist/features/products/components/PrizeDrawCollage.d.ts +1 -15
- package/dist/features/products/components/PrizeDrawCollage.js +54 -14
- package/dist/features/products/components/PrizeDrawDetailPageView.js +2 -1
- package/dist/features/products/components/PrizeDrawEntryActions.js +2 -1
- package/dist/features/products/components/PrizeDrawItemsEditor.js +9 -7
- package/dist/features/products/components/PrizeDrawsIndexListing.js +18 -51
- package/dist/features/products/components/PrizeRevealModal.js +24 -14
- package/dist/features/products/components/ProductDetailPageView.js +2 -1
- package/dist/features/products/components/ProductFilters.d.ts +7 -1
- package/dist/features/products/components/ProductFilters.js +11 -8
- package/dist/features/products/components/ProductForm.js +18 -16
- package/dist/features/products/components/ProductGalleryClient.js +2 -1
- package/dist/features/products/components/ProductGrid.js +12 -15
- package/dist/features/products/components/ProductsIndexListing.js +16 -52
- package/dist/features/products/components/ProductsIndexPageView.js +2 -2
- package/dist/features/products/repository/products.repository.d.ts +4 -0
- package/dist/features/products/repository/products.repository.js +22 -3
- package/dist/features/products/schemas/firestore.d.ts +7 -1
- package/dist/features/products/schemas/index.d.ts +56 -56
- package/dist/features/products/schemas/product-features.validators.d.ts +14 -14
- package/dist/features/products/types/index.d.ts +4 -0
- package/dist/features/promotions/components/CouponsIndexListing.js +1 -1
- package/dist/features/promotions/schemas/index.d.ts +6 -6
- package/dist/features/reviews/actions/review-actions.js +6 -5
- package/dist/features/reviews/api/[id]/route.js +4 -3
- package/dist/features/reviews/components/ReviewDetailPageView.js +2 -2
- package/dist/features/reviews/components/ReviewDetailShell.js +6 -3
- package/dist/features/reviews/components/ReviewsIndexListing.js +2 -2
- package/dist/features/reviews/schemas/index.d.ts +6 -6
- package/dist/features/scams/components/ScamProfileView.js +19 -10
- package/dist/features/scams/components/ScamRegistryView.js +2 -1
- package/dist/features/search/components/Search.d.ts +1 -1
- package/dist/features/search/components/Search.js +56 -49
- package/dist/features/search/schemas/index.d.ts +5 -5
- package/dist/features/seller/actions/offer-actions.js +11 -9
- package/dist/features/seller/components/SellerAddressesView.js +6 -4
- package/dist/features/seller/components/SellerBidsView.js +2 -2
- package/dist/features/seller/components/SellerCouponsView.js +1 -1
- package/dist/features/seller/components/SellerOffersPanel.js +6 -1
- package/dist/features/seller/components/SellerOrdersView.js +1 -1
- package/dist/features/seller/components/SellerPayoutSettingsView.js +3 -3
- package/dist/features/seller/components/SellerPreOrdersView.js +5 -4
- package/dist/features/seller/components/SellerPrizeDrawsView.js +5 -4
- package/dist/features/seller/components/SellerProductShell.d.ts +19 -1
- package/dist/features/seller/components/SellerProductShell.js +75 -9
- package/dist/features/seller/components/SellerProductsView.js +10 -11
- package/dist/features/seller/components/SellerShippingView.js +3 -3
- package/dist/features/seller/components/SellerSidebar.js +10 -7
- package/dist/features/seller/components/SellerStorefrontView.js +3 -3
- package/dist/features/seller/schemas/index.d.ts +4 -4
- package/dist/features/shell/FormShell.js +2 -1
- package/dist/features/shell/QuickFormDrawer.js +4 -3
- package/dist/features/shell/StepForm.js +4 -3
- package/dist/features/stores/actions/store-query-actions.js +4 -3
- package/dist/features/stores/components/StoreAboutView.js +2 -1
- package/dist/features/stores/components/StoreAuctionsListing.js +43 -64
- package/dist/features/stores/components/StoreDetailLayoutView.js +18 -4
- package/dist/features/stores/components/StorePreOrdersListing.js +33 -57
- package/dist/features/stores/components/StoreProductsListing.js +16 -49
- package/dist/features/stores/components/StoresIndexListing.js +2 -2
- package/dist/features/stores/schemas/index.d.ts +2 -2
- package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +8 -7
- package/dist/features/wishlist/hooks/useGuestWishlist.d.ts +5 -5
- package/dist/features/wishlist/hooks/useWishlistWithGuest.d.ts +6 -6
- package/dist/features/wishlist/utils/guest-wishlist.d.ts +5 -5
- package/dist/index.d.ts +27 -3
- package/dist/index.js +1743 -1725
- package/dist/monitoring/server-logger.js +5 -9
- package/dist/next/api/apiHandler.js +56 -37
- package/dist/next/components/GlobalError.js +2 -1
- package/dist/next/routing/route-map.d.ts +41 -0
- package/dist/next/routing/route-map.js +17 -0
- package/dist/react/ErrorBoundary.js +2 -1
- package/dist/react/contexts/SessionContext.js +38 -28
- package/dist/react/hooks/useCamera.d.ts +1 -1
- package/dist/react/hooks/useCamera.js +8 -7
- package/dist/seed/products-auctions-seed-data.js +40 -40
- package/dist/seed/products-preorders-seed-data.js +16 -16
- package/dist/seed/products-prize-draws-seed-data.js +4 -4
- package/dist/seed/products-standard-seed-data.js +202 -202
- package/dist/seed/site-settings-seed-data.js +6 -0
- package/dist/server-entry.d.ts +3 -0
- package/dist/server-entry.js +3 -0
- package/dist/server.d.ts +12 -0
- package/dist/server.js +15 -0
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/DataTable.js +28 -7
- package/dist/ui/components/Button.d.ts +8 -1
- package/dist/ui/components/Button.js +47 -8
- package/dist/ui/components/CollageLightbox.d.ts +18 -0
- package/dist/ui/components/CollageLightbox.js +41 -0
- package/dist/ui/components/Dropdown.js +7 -6
- package/dist/ui/components/ImageLightbox.d.ts +7 -1
- package/dist/ui/components/ImageLightbox.js +15 -5
- package/dist/ui/components/PageLoader.js +4 -3
- package/dist/ui/components/PaginatedMultiSelect.d.ts +28 -0
- package/dist/ui/components/PaginatedMultiSelect.js +141 -0
- package/dist/ui/components/PaginatedMultiSelect.style.css +272 -0
- package/dist/ui/components/Semantic.js +1 -1
- package/dist/ui/components/SideDrawer.js +17 -15
- package/dist/ui/components/index.style.css +1 -0
- package/dist/ui/index.d.ts +2 -0
- package/dist/ui/index.js +1 -0
- package/dist/utils/number.formatter.js +5 -13
- package/dist/utils/string.formatter.js +21 -18
- package/dist/validation/schemas.d.ts +2 -2
- package/package.json +1 -1
|
@@ -6,6 +6,7 @@ import { Button, Div, FormActions, Select, SideDrawer, Text, useToast, } from ".
|
|
|
6
6
|
import { apiClient } from "../../../http";
|
|
7
7
|
import { SUPPORT_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
8
8
|
// --- Constants ---------------------------------------------------------------
|
|
9
|
+
const CLS_INPUT = "w-full rounded-lg border border-zinc-300 dark:border-zinc-700 bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-zinc-900 dark:text-zinc-100 placeholder-zinc-400 focus:outline-none focus:ring-2 focus:ring-primary-500";
|
|
9
10
|
const CATEGORY_OPTIONS = [
|
|
10
11
|
{ label: "Order Issue", value: "order_issue" },
|
|
11
12
|
{ label: "Billing / Payment", value: "billing_payment" },
|
|
@@ -81,10 +82,19 @@ export function UserSupportView(_props) {
|
|
|
81
82
|
showToast(err?.message ?? "Failed to send reply.", "error");
|
|
82
83
|
},
|
|
83
84
|
});
|
|
84
|
-
return (_jsxs(_Fragment, { children: [_jsxs(Div, { className: "mx-auto max-w-2xl px-4 py-6", children: [_jsxs(Div, { className: "mb-4 flex items-center justify-between", children: [_jsx(Text, { className: "text-xl font-semibold text-zinc-900 dark:text-zinc-100", children: "Support Tickets" }), _jsx(Button, { type: "button", variant: "primary", size: "sm", onClick: () => setNewTicketOpen(true), children: "New ticket" })] }),
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
85
|
+
return (_jsxs(_Fragment, { children: [_jsxs(Div, { className: "mx-auto max-w-2xl px-4 py-6", children: [_jsxs(Div, { className: "mb-4 flex items-center justify-between", children: [_jsx(Text, { className: "text-xl font-semibold text-zinc-900 dark:text-zinc-100", children: "Support Tickets" }), _jsx(Button, { type: "button", variant: "primary", size: "sm", onClick: () => setNewTicketOpen(true), children: "New ticket" })] }), renderTicketListArea({ isLoading, error, tickets, setSelectedTicket, setDetailOpen })] }), renderNewTicketDrawer({ newTicketOpen, setNewTicketOpen, newCategory, setNewCategory, newSubject, setNewSubject, newOrderId, setNewOrderId, newDescription, setNewDescription, createMutation }), renderTicketDetailDrawer({ detailOpen, setDetailOpen, selectedTicket, replyBody, setReplyBody, replyMutation })] }));
|
|
86
|
+
}
|
|
87
|
+
function renderTicketListArea(props) {
|
|
88
|
+
const { isLoading, error, tickets, setSelectedTicket, setDetailOpen } = props;
|
|
89
|
+
return (_jsxs(_Fragment, { children: [isLoading && (_jsx(Div, { className: "space-y-3", children: [1, 2, 3].map((i) => _jsx("div", { className: "h-16 animate-pulse rounded-lg bg-zinc-100 dark:bg-zinc-800" }, i)) })), error && (_jsx(Div, { className: "rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: "Failed to load support tickets." })), !isLoading && tickets.length === 0 && (_jsx(Div, { className: "rounded-xl border border-zinc-200 bg-zinc-50 px-6 py-10 text-center dark:border-zinc-700 dark:bg-zinc-900/40", children: _jsx(Text, { className: "text-sm text-zinc-500 dark:text-zinc-400", children: "You haven't opened any support tickets yet." }) })), _jsx("ul", { className: "space-y-3", children: tickets.map((ticket) => (_jsx("li", { children: _jsx("button", { type: "button", className: "w-full rounded-xl border border-zinc-200 bg-white p-4 text-left shadow-sm hover:border-primary-300 transition-colors dark:border-zinc-700 dark:bg-zinc-900", onClick: () => { setSelectedTicket(ticket); setDetailOpen(true); }, children: _jsxs("div", { className: "flex items-start justify-between gap-2", children: [_jsxs("div", { className: "min-w-0 flex-1", children: [_jsx(Text, { className: "font-medium text-zinc-900 dark:text-zinc-100 truncate", children: ticket.subject }), _jsxs(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400", children: [ticket.category.replace(/_/g, " "), ticket.orderId ? ` · Order: ${ticket.orderId}` : ""] })] }), _jsx("span", { className: `shrink-0 inline-flex rounded-full px-2.5 py-0.5 text-xs font-medium ${STATUS_BADGE[ticket.status] ?? STATUS_BADGE.open}`, children: ticket.status.replace(/_/g, " ") })] }) }) }, ticket.id))) })] }));
|
|
90
|
+
}
|
|
91
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
92
|
+
function renderNewTicketDrawer(props) {
|
|
93
|
+
const { newTicketOpen, setNewTicketOpen, newCategory, setNewCategory, newSubject, setNewSubject, newOrderId, setNewOrderId, newDescription, setNewDescription, createMutation } = props;
|
|
94
|
+
return (_jsx(SideDrawer, { isOpen: newTicketOpen, onClose: () => setNewTicketOpen(false), title: "Open a support ticket", children: _jsxs(Div, { className: "flex flex-col gap-4 p-4", children: [_jsx(Select, { label: "Category", options: CATEGORY_OPTIONS, value: newCategory, onValueChange: setNewCategory }), _jsxs(Div, { className: "flex flex-col gap-1", children: [_jsx("label", { className: "text-sm font-medium text-zinc-700 dark:text-zinc-300", children: "Subject" }), _jsx("input", { type: "text", value: newSubject, onChange: (e) => setNewSubject(e.target.value), placeholder: "Brief description of the issue", className: CLS_INPUT })] }), newCategory === "order_issue" && (_jsxs(Div, { className: "flex flex-col gap-1", children: [_jsx("label", { className: "text-sm font-medium text-zinc-700 dark:text-zinc-300", children: "Order ID" }), _jsx("input", { type: "text", value: newOrderId, onChange: (e) => setNewOrderId(e.target.value), placeholder: "e.g. order-3-20260508-a1b2c3", className: CLS_INPUT })] })), _jsxs(Div, { className: "flex flex-col gap-1", children: [_jsx("label", { className: "text-sm font-medium text-zinc-700 dark:text-zinc-300", children: "Description" }), _jsx("textarea", { value: newDescription, onChange: (e) => setNewDescription(e.target.value), rows: 4, placeholder: "Describe the issue in detail\u2026", className: CLS_INPUT })] }), _jsxs(FormActions, { align: "right", children: [_jsx(Button, { type: "button", variant: "secondary", onClick: () => setNewTicketOpen(false), children: "Cancel" }), _jsx(Button, { type: "button", isLoading: createMutation.isPending, disabled: !newSubject.trim() || !newDescription.trim() || createMutation.isPending, onClick: () => createMutation.mutate(), children: "Submit ticket" })] })] }) }));
|
|
95
|
+
}
|
|
96
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
97
|
+
function renderTicketDetailDrawer(props) {
|
|
98
|
+
const { detailOpen, setDetailOpen, selectedTicket, replyBody, setReplyBody, replyMutation } = props;
|
|
99
|
+
return (_jsx(SideDrawer, { isOpen: detailOpen, onClose: () => setDetailOpen(false), title: selectedTicket?.subject ?? "Ticket", children: selectedTicket && (_jsxs(Div, { className: "flex flex-col gap-4 p-4", children: [_jsxs(Div, { className: "flex flex-wrap gap-2", children: [_jsx("span", { className: `inline-flex rounded-full px-2.5 py-1 text-xs font-medium ${STATUS_BADGE[selectedTicket.status] ?? STATUS_BADGE.open}`, children: selectedTicket.status.replace(/_/g, " ") }), _jsx("span", { className: "rounded-full bg-zinc-100 px-2.5 py-1 text-xs text-zinc-600 dark:bg-zinc-800 dark:text-zinc-300", children: selectedTicket.category.replace(/_/g, " ") }), selectedTicket.orderId && _jsxs("span", { className: "rounded-full bg-zinc-100 px-2.5 py-1 text-xs text-zinc-600 dark:bg-zinc-800 dark:text-zinc-300", children: ["Order: ", selectedTicket.orderId] })] }), selectedTicket.description && (_jsxs(Div, { className: "rounded-lg border border-zinc-200 bg-zinc-50 p-3 dark:border-zinc-700 dark:bg-zinc-900/40", children: [_jsx(Text, { className: "mb-1 text-xs font-semibold text-zinc-500 uppercase tracking-wide", children: "Your description" }), _jsx(Text, { className: "whitespace-pre-wrap text-sm text-zinc-700 dark:text-zinc-200", children: selectedTicket.description })] })), (selectedTicket.messages ?? []).length > 0 && (_jsxs(Div, { className: "space-y-2", children: [_jsx(Text, { className: "text-xs font-semibold text-zinc-500 uppercase tracking-wide", children: "Messages" }), _jsx("div", { className: "space-y-2 max-h-72 overflow-y-auto", children: (selectedTicket.messages ?? []).map((msg, i) => (_jsxs("div", { className: `rounded-lg p-3 text-sm ${msg.authorRole === "user" ? "bg-zinc-50 border border-zinc-200 dark:bg-zinc-900/40 dark:border-zinc-700" : "bg-blue-50 border border-blue-200 dark:bg-blue-900/20 dark:border-blue-800"}`, children: [_jsxs("div", { className: "mb-1 flex items-center gap-2 text-xs text-zinc-400 dark:text-zinc-500", children: [_jsx("span", { className: "font-medium text-zinc-600 dark:text-zinc-300", children: ROLE_LABEL[msg.authorRole ?? "user"] ?? msg.authorRole }), msg.createdAt && _jsx("span", { children: new Date(msg.createdAt).toLocaleString() })] }), _jsx(Text, { className: "whitespace-pre-wrap text-zinc-700 dark:text-zinc-200", children: msg.body })] }, msg.id ?? i))) })] })), selectedTicket.status !== "closed" && selectedTicket.status !== "resolved" && (_jsxs(Div, { className: "flex flex-col gap-1", children: [_jsx("label", { className: "text-sm font-medium text-zinc-700 dark:text-zinc-300", children: "Reply" }), _jsx("textarea", { value: replyBody, onChange: (e) => setReplyBody(e.target.value), rows: 3, placeholder: "Add a message to your ticket\u2026", className: CLS_INPUT }), _jsx(Button, { type: "button", variant: "primary", size: "sm", isLoading: replyMutation.isPending, disabled: !replyBody.trim() || replyMutation.isPending, onClick: () => replyMutation.mutate(), children: "Send reply" })] }))] })) }));
|
|
90
100
|
}
|
|
@@ -40,3 +40,5 @@ export { UserReturnsView } from "./UserReturnsView";
|
|
|
40
40
|
export type { UserReturnsViewProps, UserReturnsViewLabels, } from "./UserReturnsView";
|
|
41
41
|
export { UserSupportView } from "./UserSupportView";
|
|
42
42
|
export type { UserSupportViewProps } from "./UserSupportView";
|
|
43
|
+
export { NotificationPreferencesPanel } from "./NotificationPreferencesPanel";
|
|
44
|
+
export type { NotificationPreferencesPanelProps } from "./NotificationPreferencesPanel";
|
|
@@ -19,3 +19,4 @@ export { BecomeSellerView } from "./BecomeSellerView";
|
|
|
19
19
|
export { UserOrderTrackView } from "./UserOrderTrackView";
|
|
20
20
|
export { UserReturnsView } from "./UserReturnsView";
|
|
21
21
|
export { UserSupportView } from "./UserSupportView";
|
|
22
|
+
export { NotificationPreferencesPanel } from "./NotificationPreferencesPanel";
|
|
@@ -132,6 +132,7 @@ export declare const userProfileSchema: z.ZodObject<{
|
|
|
132
132
|
updatedAt?: string | undefined;
|
|
133
133
|
displayName?: string | undefined;
|
|
134
134
|
email?: string | undefined;
|
|
135
|
+
phone?: string | undefined;
|
|
135
136
|
addresses?: {
|
|
136
137
|
country: string;
|
|
137
138
|
id: string;
|
|
@@ -145,8 +146,6 @@ export declare const userProfileSchema: z.ZodObject<{
|
|
|
145
146
|
line2?: string | undefined;
|
|
146
147
|
}[] | undefined;
|
|
147
148
|
photoURL?: string | undefined;
|
|
148
|
-
phone?: string | undefined;
|
|
149
|
-
bio?: string | undefined;
|
|
150
149
|
notificationPreferences?: {
|
|
151
150
|
push?: boolean | undefined;
|
|
152
151
|
promotions?: boolean | undefined;
|
|
@@ -154,12 +153,14 @@ export declare const userProfileSchema: z.ZodObject<{
|
|
|
154
153
|
orderUpdates?: boolean | undefined;
|
|
155
154
|
sms?: boolean | undefined;
|
|
156
155
|
} | undefined;
|
|
156
|
+
bio?: string | undefined;
|
|
157
157
|
}, {
|
|
158
158
|
id: string;
|
|
159
159
|
createdAt?: string | undefined;
|
|
160
160
|
updatedAt?: string | undefined;
|
|
161
161
|
displayName?: string | undefined;
|
|
162
162
|
email?: string | undefined;
|
|
163
|
+
phone?: string | undefined;
|
|
163
164
|
addresses?: {
|
|
164
165
|
country: string;
|
|
165
166
|
id: string;
|
|
@@ -173,8 +174,6 @@ export declare const userProfileSchema: z.ZodObject<{
|
|
|
173
174
|
line2?: string | undefined;
|
|
174
175
|
}[] | undefined;
|
|
175
176
|
photoURL?: string | undefined;
|
|
176
|
-
phone?: string | undefined;
|
|
177
|
-
bio?: string | undefined;
|
|
178
177
|
notificationPreferences?: {
|
|
179
178
|
push?: boolean | undefined;
|
|
180
179
|
promotions?: boolean | undefined;
|
|
@@ -182,6 +181,7 @@ export declare const userProfileSchema: z.ZodObject<{
|
|
|
182
181
|
orderUpdates?: boolean | undefined;
|
|
183
182
|
sms?: boolean | undefined;
|
|
184
183
|
} | undefined;
|
|
184
|
+
bio?: string | undefined;
|
|
185
185
|
}>;
|
|
186
186
|
export declare const updateProfileSchema: z.ZodObject<{
|
|
187
187
|
displayName: z.ZodOptional<z.ZodString>;
|
|
@@ -57,11 +57,45 @@ export interface UserAddress {
|
|
|
57
57
|
isDefault?: boolean;
|
|
58
58
|
phone?: string;
|
|
59
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* Per-channel on/off toggles at the user level.
|
|
62
|
+
* Channels are only surfaced in the UI when the admin has enabled them in
|
|
63
|
+
* siteSettings.notificationChannels — this is enforced on the server side.
|
|
64
|
+
*/
|
|
65
|
+
export interface NotificationChannelPrefs {
|
|
66
|
+
/** Disable all email notifications for this user (default: true = enabled). */
|
|
67
|
+
email?: boolean;
|
|
68
|
+
/** Disable all WhatsApp notifications for this user. */
|
|
69
|
+
whatsapp?: boolean;
|
|
70
|
+
/** Disable all SMS notifications for this user. */
|
|
71
|
+
sms?: boolean;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Per notification-type on/off controls. `true` = enabled (default when absent).
|
|
75
|
+
*/
|
|
76
|
+
export interface NotificationTypePrefs {
|
|
77
|
+
orderUpdates?: boolean;
|
|
78
|
+
bids?: boolean;
|
|
79
|
+
promotions?: boolean;
|
|
80
|
+
system?: boolean;
|
|
81
|
+
reviews?: boolean;
|
|
82
|
+
messages?: boolean;
|
|
83
|
+
offers?: boolean;
|
|
84
|
+
}
|
|
60
85
|
export interface NotificationPreferences {
|
|
86
|
+
/** Per-channel global toggles (user can silence a whole channel). */
|
|
87
|
+
channels?: NotificationChannelPrefs;
|
|
88
|
+
/** Per notification-type toggles (user can silence specific event classes). */
|
|
89
|
+
types?: NotificationTypePrefs;
|
|
90
|
+
/** @deprecated Use types.orderUpdates */
|
|
61
91
|
orderUpdates?: boolean;
|
|
92
|
+
/** @deprecated Use types.promotions */
|
|
62
93
|
promotions?: boolean;
|
|
94
|
+
/** @deprecated Use channels.sms */
|
|
63
95
|
newsletter?: boolean;
|
|
96
|
+
/** @deprecated Use channels.sms */
|
|
64
97
|
sms?: boolean;
|
|
98
|
+
/** Push notifications reserved for future use. */
|
|
65
99
|
push?: boolean;
|
|
66
100
|
}
|
|
67
101
|
export interface UserProfile {
|
|
@@ -12,6 +12,7 @@ import { userRepository } from "../../auth/repository/user.repository";
|
|
|
12
12
|
import { productRepository } from "../../products/repository/products.repository";
|
|
13
13
|
import { NotFoundError, ValidationError } from "../../../errors";
|
|
14
14
|
import { getProviders } from "../../../contracts";
|
|
15
|
+
const ERR_UID_REQUIRED = "uid is required";
|
|
15
16
|
export async function revokeSession(adminId, sessionId) {
|
|
16
17
|
if (!sessionId?.trim()) {
|
|
17
18
|
throw new ValidationError("sessionId is required");
|
|
@@ -83,7 +84,7 @@ export async function adminUpdatePayout(adminId, id, input) {
|
|
|
83
84
|
}
|
|
84
85
|
export async function adminUpdateUser(adminId, uid, input) {
|
|
85
86
|
if (!uid?.trim()) {
|
|
86
|
-
throw new ValidationError(
|
|
87
|
+
throw new ValidationError(ERR_UID_REQUIRED);
|
|
87
88
|
}
|
|
88
89
|
const existing = await userRepository.findById(uid);
|
|
89
90
|
if (!existing) {
|
|
@@ -109,7 +110,7 @@ export async function adminUpdateUser(adminId, uid, input) {
|
|
|
109
110
|
}
|
|
110
111
|
export async function adminDeleteUser(adminId, uid) {
|
|
111
112
|
if (!uid?.trim()) {
|
|
112
|
-
throw new ValidationError(
|
|
113
|
+
throw new ValidationError(ERR_UID_REQUIRED);
|
|
113
114
|
}
|
|
114
115
|
if (uid === adminId) {
|
|
115
116
|
throw new ValidationError("Cannot delete your own account");
|
|
@@ -127,7 +128,7 @@ export async function adminDeleteUser(adminId, uid) {
|
|
|
127
128
|
export async function adminUpdateStoreStatus(adminId, input) {
|
|
128
129
|
const { uid, action } = input;
|
|
129
130
|
if (!uid?.trim()) {
|
|
130
|
-
throw new ValidationError(
|
|
131
|
+
throw new ValidationError(ERR_UID_REQUIRED);
|
|
131
132
|
}
|
|
132
133
|
const user = await userRepository.findById(uid);
|
|
133
134
|
if (!user) {
|
|
@@ -1,31 +1,33 @@
|
|
|
1
|
-
|
|
2
|
-
* Notification Domain Actions (appkit)
|
|
3
|
-
*
|
|
4
|
-
* Pure business functions — auth, rate-limiting, and Next.js specifics
|
|
5
|
-
* are handled by the calling server action in the consumer.
|
|
6
|
-
*/
|
|
7
|
-
import type { NotificationDocument } from "../schemas";
|
|
8
|
-
/**
|
|
9
|
-
* Mark a single notification as read.
|
|
10
|
-
*/
|
|
1
|
+
import type { NotificationDocument, NotificationCreateInput } from "../schemas";
|
|
11
2
|
export declare function markNotificationRead(id: string): Promise<void>;
|
|
12
|
-
/**
|
|
13
|
-
* Mark all notifications as read for a given user.
|
|
14
|
-
* Returns the number of notifications updated.
|
|
15
|
-
*/
|
|
16
3
|
export declare function markAllNotificationsRead(userId: string): Promise<number>;
|
|
17
|
-
/**
|
|
18
|
-
* Delete a single notification by id.
|
|
19
|
-
*/
|
|
20
4
|
export declare function deleteNotification(id: string): Promise<void>;
|
|
21
|
-
/**
|
|
22
|
-
* List notifications for a user with unread count.
|
|
23
|
-
*/
|
|
24
5
|
export declare function listNotifications(userId: string, limit?: number): Promise<{
|
|
25
6
|
notifications: NotificationDocument[];
|
|
26
7
|
unreadCount: number;
|
|
27
8
|
}>;
|
|
9
|
+
export declare function getUnreadNotificationCount(userId: string): Promise<number>;
|
|
10
|
+
export interface SendNotificationInput extends NotificationCreateInput {
|
|
11
|
+
/** User's email — used by the email channel when enabled. */
|
|
12
|
+
userEmail?: string;
|
|
13
|
+
/** User's phone with country code, digits only — used by WhatsApp channel. */
|
|
14
|
+
userPhone?: string;
|
|
15
|
+
/** Pre-rendered email HTML — falls back to a plain-text <p> when absent. */
|
|
16
|
+
emailHtml?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface SendNotificationResult {
|
|
19
|
+
notification: NotificationDocument;
|
|
20
|
+
email: "sent" | "skipped" | "failed";
|
|
21
|
+
whatsapp: "sent" | "skipped" | "failed";
|
|
22
|
+
}
|
|
28
23
|
/**
|
|
29
|
-
*
|
|
24
|
+
* Create an in-app notification and fan out to enabled external channels.
|
|
25
|
+
*
|
|
26
|
+
* In-app is always written. Email / WhatsApp are sent only when:
|
|
27
|
+
* 1. The channel is enabled in siteSettings.notificationChannels
|
|
28
|
+
* 2. Notification priority meets the channel's minPriority threshold
|
|
29
|
+
* 3. The optional `types` filter includes this notification type (or is absent)
|
|
30
|
+
* 4. The contact field (userEmail / userPhone) is supplied by the caller
|
|
31
|
+
* 5. The required credentials are set in siteSettings.credentials
|
|
30
32
|
*/
|
|
31
|
-
export declare function
|
|
33
|
+
export declare function sendNotification(input: SendNotificationInput): Promise<SendNotificationResult>;
|
|
@@ -1,32 +1,19 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Notification Domain Actions (appkit)
|
|
3
|
-
*
|
|
4
|
-
* Pure business functions — auth, rate-limiting, and Next.js specifics
|
|
5
|
-
* are handled by the calling server action in the consumer.
|
|
6
|
-
*/
|
|
7
1
|
import { notificationRepository } from "../repository/notification.repository";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
2
|
+
import { siteSettingsRepository } from "../repository/site-settings.repository";
|
|
3
|
+
import { userRepository } from "../../auth/repository/user.repository";
|
|
4
|
+
import { createResendProvider } from "../../../providers/email-resend/provider";
|
|
5
|
+
import { sendWhatsAppBusinessMessage } from "../../whatsapp-bot/helpers/whatsapp";
|
|
6
|
+
import { serverLogger } from "../../../monitoring";
|
|
7
|
+
import { DEFAULT_NOTIFICATION_CHANNELS, meetsMinPriority, } from "../schemas/firestore";
|
|
11
8
|
export async function markNotificationRead(id) {
|
|
12
9
|
await notificationRepository.markAsRead(id);
|
|
13
10
|
}
|
|
14
|
-
/**
|
|
15
|
-
* Mark all notifications as read for a given user.
|
|
16
|
-
* Returns the number of notifications updated.
|
|
17
|
-
*/
|
|
18
11
|
export async function markAllNotificationsRead(userId) {
|
|
19
12
|
return notificationRepository.markAllAsRead(userId);
|
|
20
13
|
}
|
|
21
|
-
/**
|
|
22
|
-
* Delete a single notification by id.
|
|
23
|
-
*/
|
|
24
14
|
export async function deleteNotification(id) {
|
|
25
15
|
await notificationRepository.delete(id);
|
|
26
16
|
}
|
|
27
|
-
/**
|
|
28
|
-
* List notifications for a user with unread count.
|
|
29
|
-
*/
|
|
30
17
|
export async function listNotifications(userId, limit = 20) {
|
|
31
18
|
const [notifications, unreadCount] = await Promise.all([
|
|
32
19
|
notificationRepository.findByUser(userId, limit),
|
|
@@ -34,9 +21,122 @@ export async function listNotifications(userId, limit = 20) {
|
|
|
34
21
|
]);
|
|
35
22
|
return { notifications, unreadCount };
|
|
36
23
|
}
|
|
37
|
-
/**
|
|
38
|
-
* Get the unread notification count for a user.
|
|
39
|
-
*/
|
|
40
24
|
export async function getUnreadNotificationCount(userId) {
|
|
41
25
|
return notificationRepository.getUnreadCount(userId);
|
|
42
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Create an in-app notification and fan out to enabled external channels.
|
|
29
|
+
*
|
|
30
|
+
* In-app is always written. Email / WhatsApp are sent only when:
|
|
31
|
+
* 1. The channel is enabled in siteSettings.notificationChannels
|
|
32
|
+
* 2. Notification priority meets the channel's minPriority threshold
|
|
33
|
+
* 3. The optional `types` filter includes this notification type (or is absent)
|
|
34
|
+
* 4. The contact field (userEmail / userPhone) is supplied by the caller
|
|
35
|
+
* 5. The required credentials are set in siteSettings.credentials
|
|
36
|
+
*/
|
|
37
|
+
export async function sendNotification(input) {
|
|
38
|
+
const { userEmail, userPhone, emailHtml, ...notifInput } = input;
|
|
39
|
+
// Always write the in-app notification first.
|
|
40
|
+
const notification = await notificationRepository.create(notifInput);
|
|
41
|
+
// Load channel config + credentials (one Firestore read).
|
|
42
|
+
let settings;
|
|
43
|
+
try {
|
|
44
|
+
settings = await siteSettingsRepository.getSingleton();
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
serverLogger.warn("sendNotification: could not load siteSettings — skipping external channels", { err });
|
|
48
|
+
return { notification, email: "skipped", whatsapp: "skipped" };
|
|
49
|
+
}
|
|
50
|
+
const channels = settings.notificationChannels ?? DEFAULT_NOTIFICATION_CHANNELS;
|
|
51
|
+
const creds = settings.credentials ?? {};
|
|
52
|
+
const { type, priority, title, message } = notifInput;
|
|
53
|
+
// Load user notification preferences (best-effort — if missing, all channels allowed).
|
|
54
|
+
let userChannelPrefs = {};
|
|
55
|
+
let userTypePrefs = {};
|
|
56
|
+
try {
|
|
57
|
+
const userDoc = await userRepository.findById(input.userId);
|
|
58
|
+
userChannelPrefs = userDoc?.notificationPreferences?.channels ?? {};
|
|
59
|
+
userTypePrefs = userDoc?.notificationPreferences?.types ?? {};
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// non-fatal: fall through with empty prefs (all enabled)
|
|
63
|
+
}
|
|
64
|
+
// Map NotificationType to a NotificationTypePrefs key.
|
|
65
|
+
const typeToPrefsKey = {
|
|
66
|
+
order_placed: "orderUpdates", order_confirmed: "orderUpdates",
|
|
67
|
+
order_shipped: "orderUpdates", order_delivered: "orderUpdates",
|
|
68
|
+
order_cancelled: "orderUpdates",
|
|
69
|
+
bid_placed: "bids", bid_outbid: "bids", bid_won: "bids", bid_lost: "bids",
|
|
70
|
+
review_approved: "reviews", review_replied: "reviews",
|
|
71
|
+
promotion: "promotions",
|
|
72
|
+
system: "system", welcome: "system",
|
|
73
|
+
offer_received: "offers", offer_responded: "offers",
|
|
74
|
+
offer_expired: "offers", offer_counter_accepted: "offers",
|
|
75
|
+
refund_initiated: "orderUpdates",
|
|
76
|
+
product_available: "system",
|
|
77
|
+
};
|
|
78
|
+
const typeKey = typeToPrefsKey[type];
|
|
79
|
+
// If the user has explicitly disabled this notification type, skip all external channels.
|
|
80
|
+
if (typeKey && userTypePrefs[typeKey] === false) {
|
|
81
|
+
return { notification, email: "skipped", whatsapp: "skipped" };
|
|
82
|
+
}
|
|
83
|
+
// Email channel — admin enabled AND user hasn't opted out
|
|
84
|
+
let emailStatus = "skipped";
|
|
85
|
+
if (channels.email.enabled &&
|
|
86
|
+
userChannelPrefs.email !== false &&
|
|
87
|
+
userEmail &&
|
|
88
|
+
meetsMinPriority(priority, channels.email.minPriority) &&
|
|
89
|
+
(!channels.email.types?.length || channels.email.types.includes(type))) {
|
|
90
|
+
const apiKey = creds.resendApiKey?.trim() ?? "";
|
|
91
|
+
const fromEmail = settings.emailSettings?.fromEmail ?? "noreply@letitrip.in";
|
|
92
|
+
const fromName = settings.emailSettings?.fromName ?? "LetItRip";
|
|
93
|
+
if (apiKey && !apiKey.includes("PLACEHOLDER")) {
|
|
94
|
+
try {
|
|
95
|
+
const emailProvider = createResendProvider({ apiKey, fromEmail, fromName });
|
|
96
|
+
await emailProvider.send({
|
|
97
|
+
to: userEmail,
|
|
98
|
+
subject: title,
|
|
99
|
+
html: emailHtml ?? `<p>${message}</p>`,
|
|
100
|
+
text: message,
|
|
101
|
+
});
|
|
102
|
+
emailStatus = "sent";
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
serverLogger.error("sendNotification: email dispatch failed", { userId: input.userId, err });
|
|
106
|
+
emailStatus = "failed";
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// WhatsApp channel — admin enabled AND user hasn't opted out
|
|
111
|
+
let whatsappStatus = "skipped";
|
|
112
|
+
if (channels.whatsapp.enabled &&
|
|
113
|
+
userChannelPrefs.whatsapp !== false &&
|
|
114
|
+
userPhone &&
|
|
115
|
+
meetsMinPriority(priority, channels.whatsapp.minPriority) &&
|
|
116
|
+
(!channels.whatsapp.types?.length || channels.whatsapp.types.includes(type))) {
|
|
117
|
+
const phoneNumberId = creds.whatsappPhoneNumberId?.trim() ?? "";
|
|
118
|
+
const accessToken = creds.whatsappCloudApiToken?.trim() ?? "";
|
|
119
|
+
if (phoneNumberId && accessToken) {
|
|
120
|
+
try {
|
|
121
|
+
const ok = await sendWhatsAppBusinessMessage({
|
|
122
|
+
toPhone: userPhone,
|
|
123
|
+
message: `*${title}*\n${message}`,
|
|
124
|
+
phoneNumberId,
|
|
125
|
+
accessToken,
|
|
126
|
+
});
|
|
127
|
+
whatsappStatus = ok ? "sent" : "failed";
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
serverLogger.error("sendNotification: WhatsApp dispatch failed", { userId: input.userId, err });
|
|
131
|
+
whatsappStatus = "failed";
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
serverLogger.info("sendNotification: dispatched", {
|
|
136
|
+
userId: input.userId,
|
|
137
|
+
type,
|
|
138
|
+
email: emailStatus,
|
|
139
|
+
whatsapp: whatsappStatus,
|
|
140
|
+
});
|
|
141
|
+
return { notification, email: emailStatus, whatsapp: whatsappStatus };
|
|
142
|
+
}
|
|
@@ -5,6 +5,27 @@ import { useMutation, useQuery } from "@tanstack/react-query";
|
|
|
5
5
|
import { Alert, Button, Form, FormActions, Input, Select, StackedViewShell, Text, Toggle, useToast, } from "../../../ui";
|
|
6
6
|
import { apiClient } from "../../../http";
|
|
7
7
|
import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
8
|
+
function validateThirdPartyUrl(url, issues) {
|
|
9
|
+
if (!url.trim()) {
|
|
10
|
+
issues.push("Third-party ads require a third-party URL");
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
try {
|
|
14
|
+
const parsed = new URL(url.trim());
|
|
15
|
+
if (parsed.protocol !== "https:") {
|
|
16
|
+
issues.push("Third-party ad URL must be https");
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
issues.push("Third-party ad URL must be a valid URL");
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function renderAdScheduleSection({ startAt, setStartAt, endAt, setEndAt }) {
|
|
24
|
+
return (_jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-3", "data-section": "adminadeditorview-div-238", children: [_jsx(Input, { label: "Start at (ISO)", value: startAt, onChange: (event) => setStartAt(event.target.value), placeholder: "2026-05-01T00:00:00.000Z" }), _jsx(Input, { label: "End at (ISO)", value: endAt, onChange: (event) => setEndAt(event.target.value), placeholder: "2026-05-30T23:59:59.000Z" })] }));
|
|
25
|
+
}
|
|
26
|
+
function renderAdCreativeSection({ title, setTitle, body, setBody, imageUrl, setImageUrl, ctaLabel, setCtaLabel, ctaHref, setCtaHref, adsenseSlot, setAdsenseSlot, thirdPartyUrl, setThirdPartyUrl, provider, }) {
|
|
27
|
+
return (_jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-3", "data-section": "adminadeditorview-div-239", children: [_jsx(Input, { label: "Creative title", value: title, onChange: (e) => setTitle(e.target.value), placeholder: "Catch the mega sale" }), _jsx(Input, { label: "Creative body", value: body, onChange: (e) => setBody(e.target.value), placeholder: "Up to 40% off this weekend" }), _jsx(Input, { label: "Image URL", value: imageUrl, onChange: (e) => setImageUrl(e.target.value), placeholder: "https://..." }), _jsx(Input, { label: "CTA label", value: ctaLabel, onChange: (e) => setCtaLabel(e.target.value), placeholder: "Shop now" }), _jsx(Input, { label: "CTA URL", value: ctaHref, onChange: (e) => setCtaHref(e.target.value), placeholder: "/promotions/deals" }), provider === "adsense" ? (_jsx(Input, { label: "AdSense slot", value: adsenseSlot, onChange: (e) => setAdsenseSlot(e.target.value), placeholder: "1234567890" })) : null, provider === "thirdParty" ? (_jsx(Input, { label: "Third-party URL", value: thirdPartyUrl, onChange: (e) => setThirdPartyUrl(e.target.value), placeholder: "https://adnetwork.example/slot" })) : null] }));
|
|
28
|
+
}
|
|
8
29
|
export function AdminAdEditorView({ adId, endpointBase = ADMIN_ENDPOINTS.ADS, labels = {}, onSaved, ...rest }) {
|
|
9
30
|
const [name, setName] = React.useState("");
|
|
10
31
|
const [provider, setProvider] = React.useState("manual");
|
|
@@ -157,20 +178,7 @@ export function AdminAdEditorView({ adId, endpointBase = ADMIN_ENDPOINTS.ADS, la
|
|
|
157
178
|
}
|
|
158
179
|
}
|
|
159
180
|
if (provider === "thirdParty") {
|
|
160
|
-
|
|
161
|
-
issues.push("Third-party ads require a third-party URL");
|
|
162
|
-
}
|
|
163
|
-
else {
|
|
164
|
-
try {
|
|
165
|
-
const parsed = new URL(thirdPartyUrl.trim());
|
|
166
|
-
if (parsed.protocol !== "https:") {
|
|
167
|
-
issues.push("Third-party ad URL must be https");
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
catch {
|
|
171
|
-
issues.push("Third-party ad URL must be a valid URL");
|
|
172
|
-
}
|
|
173
|
-
}
|
|
181
|
+
validateThirdPartyUrl(thirdPartyUrl, issues);
|
|
174
182
|
if (!providerCredentialStatus?.hasThirdPartyScriptUrl) {
|
|
175
183
|
issues.push("Third-party provider script URL is missing in ad settings");
|
|
176
184
|
}
|
|
@@ -200,6 +208,12 @@ export function AdminAdEditorView({ adId, endpointBase = ADMIN_ENDPOINTS.ADS, la
|
|
|
200
208
|
? prev.filter((id) => id !== placementId)
|
|
201
209
|
: [...prev, placementId]);
|
|
202
210
|
};
|
|
211
|
+
const scheduleSection = renderAdScheduleSection({ startAt, setStartAt, endAt, setEndAt });
|
|
212
|
+
const creativeSection = renderAdCreativeSection({
|
|
213
|
+
title, setTitle, body, setBody, imageUrl, setImageUrl,
|
|
214
|
+
ctaLabel, setCtaLabel, ctaHref, setCtaHref,
|
|
215
|
+
adsenseSlot, setAdsenseSlot, thirdPartyUrl, setThirdPartyUrl, provider,
|
|
216
|
+
});
|
|
203
217
|
return (_jsx(StackedViewShell, { portal: "admin", ...rest, title: labels.title ?? (adId ? "Edit Ad" : "Create Ad"), sections: [
|
|
204
218
|
metaQuery.error ? (_jsx(Alert, { variant: "error", title: "Could not load ad metadata", children: metaQuery.error instanceof Error ? metaQuery.error.message : "Unknown error" })) : null,
|
|
205
219
|
adQuery.error ? (_jsx(Alert, { variant: "error", title: "Could not load ad", children: adQuery.error instanceof Error ? adQuery.error.message : "Unknown error" })) : null,
|
|
@@ -219,6 +233,6 @@ export function AdminAdEditorView({ adId, endpointBase = ADMIN_ENDPOINTS.ADS, la
|
|
|
219
233
|
{ label: "Active", value: "active" },
|
|
220
234
|
{ label: "Scheduled", value: "scheduled" },
|
|
221
235
|
{ label: "Paused", value: "paused" },
|
|
222
|
-
], onChange: (event) => setStatus(event.target.value) }), _jsx(Input, { label: "Priority", type: "number", value: String(priority), onChange: (event) => setPriority(Number(event.target.value || 0)) })] }), _jsx(Toggle, { checked: requiresConsent, onChange: setRequiresConsent, label: "Require user ad-consent for this ad" }), _jsxs("div", { className: "space-y-2", "data-section": "adminadeditorview-div-236", children: [_jsx(Text, { className: "text-sm font-medium", children: "Placements" }), _jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-2", "data-section": "adminadeditorview-div-237", children: placements.map((placement) => (_jsxs("label", { className: "flex items-center gap-2 rounded-md border border-neutral-200 dark:border-slate-700 px-3 py-2", children: [_jsx("input", { type: "checkbox", checked: selectedPlacements.includes(placement.id), onChange: () => togglePlacement(placement.id) }), _jsx("span", { className: "text-sm", children: placement.label })] }, placement.id))) })] }),
|
|
236
|
+
], onChange: (event) => setStatus(event.target.value) }), _jsx(Input, { label: "Priority", type: "number", value: String(priority), onChange: (event) => setPriority(Number(event.target.value || 0)) })] }), _jsx(Toggle, { checked: requiresConsent, onChange: setRequiresConsent, label: "Require user ad-consent for this ad" }), _jsxs("div", { className: "space-y-2", "data-section": "adminadeditorview-div-236", children: [_jsx(Text, { className: "text-sm font-medium", children: "Placements" }), _jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-2", "data-section": "adminadeditorview-div-237", children: placements.map((placement) => (_jsxs("label", { className: "flex items-center gap-2 rounded-md border border-neutral-200 dark:border-slate-700 px-3 py-2", children: [_jsx("input", { type: "checkbox", checked: selectedPlacements.includes(placement.id), onChange: () => togglePlacement(placement.id) }), _jsx("span", { className: "text-sm", children: placement.label })] }, placement.id))) })] }), scheduleSection, creativeSection, _jsxs("div", { className: "rounded-lg border border-neutral-200 dark:border-slate-700 p-3", "data-section": "adminadeditorview-div-240", children: [_jsx(Text, { className: "text-sm font-medium mb-1", children: "Preview" }), _jsx(Text, { className: "text-xs text-neutral-500 dark:text-zinc-400", children: title || name || "Untitled ad" }), body ? _jsx(Text, { className: "text-sm mt-1", children: body }) : null, ctaLabel ? _jsxs(Text, { className: "text-xs mt-1", children: ["CTA: ", ctaLabel, " (", ctaHref || "#", ")"] }) : null] }), publishHardeningRequired && publishIssues.length > 0 ? (_jsx(Alert, { variant: "warning", title: "Publish readiness issues", children: publishIssues.join("; ") })) : null, _jsx(FormActions, { align: "right", children: _jsx(Button, { type: "submit", disabled: saveMutation.isPending || blockedByPublishValidation || !name.trim() || selectedPlacements.length === 0, children: saveMutation.isPending ? "Saving..." : status === "active" ? "Publish ad" : adId ? "Save changes" : "Create ad" }) })] }),
|
|
223
237
|
] }));
|
|
224
238
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { StackedViewShellProps } from "../../../ui";
|
|
2
|
+
export interface AdminAddressEditorViewProps extends Omit<StackedViewShellProps, "sections"> {
|
|
3
|
+
addressId?: string;
|
|
4
|
+
onSaved?: (id: string) => void;
|
|
5
|
+
onDeleted?: () => void;
|
|
6
|
+
}
|
|
7
|
+
export declare function AdminAddressEditorView({ addressId, onSaved, onDeleted, ...rest }: AdminAddressEditorViewProps): import("react/jsx-runtime").JSX.Element;
|