@mohasinac/appkit 4.9.5 → 4.11.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/_internal/client/features/layout/DashboardLayoutClient.d.ts +6 -1
- package/dist/_internal/client/features/layout/DashboardLayoutClient.js +4 -1
- package/dist/_internal/client/features/lottery/LotteryPullForm.d.ts +1 -1
- package/dist/_internal/client/features/lottery/LotteryPullForm.js +2 -2
- package/dist/_internal/server/features/admin/daily-digest-actions.d.ts +18 -0
- package/dist/_internal/server/features/admin/daily-digest-actions.js +37 -0
- package/dist/_internal/server/features/auctions/actions.js +3 -2
- package/dist/_internal/server/features/auctions/service.d.ts +4 -3
- package/dist/_internal/server/features/auctions/service.js +6 -6
- package/dist/_internal/server/features/audit-log/actions.d.ts +25 -0
- package/dist/_internal/server/features/audit-log/actions.js +38 -0
- package/dist/_internal/server/features/brands/actions.js +0 -2
- package/dist/_internal/server/features/brands/og.d.ts +4 -1
- package/dist/_internal/server/features/brands/og.js +1 -1
- package/dist/_internal/server/features/bundles/data.d.ts +9 -0
- package/dist/_internal/server/features/bundles/data.js +14 -0
- package/dist/_internal/server/features/bundles/index.d.ts +1 -1
- package/dist/_internal/server/features/bundles/index.js +1 -1
- package/dist/_internal/server/features/catalogue/og.d.ts +20 -0
- package/dist/_internal/server/features/catalogue/og.js +27 -0
- package/dist/_internal/server/features/grouped/data.d.ts +16 -0
- package/dist/_internal/server/features/grouped/data.js +50 -1
- package/dist/_internal/server/features/grouped/index.d.ts +1 -1
- package/dist/_internal/server/features/grouped/index.js +1 -1
- package/dist/_internal/server/features/orders/actions.js +9 -0
- package/dist/_internal/server/features/orders/adapters.js +25 -0
- package/dist/_internal/server/features/products/data.d.ts +23 -0
- package/dist/_internal/server/features/products/data.js +159 -0
- package/dist/_internal/server/features/products/index.d.ts +1 -1
- package/dist/_internal/server/features/products/index.js +1 -1
- package/dist/_internal/server/functions/scheduled.d.ts +3 -1
- package/dist/_internal/server/functions/scheduled.js +17 -1
- package/dist/_internal/server/jobs/core/dailyStatusDigest.d.ts +40 -0
- package/dist/_internal/server/jobs/core/dailyStatusDigest.js +164 -0
- package/dist/_internal/server/jobs/core/hardBanCascade.js +11 -0
- package/dist/_internal/server/jobs/core/testerSandboxCleanup.d.ts +1 -0
- package/dist/_internal/server/jobs/core/testerSandboxCleanup.js +4 -1
- package/dist/_internal/server/jobs/core/testerSandboxRefresh.d.ts +12 -0
- package/dist/_internal/server/jobs/core/testerSandboxRefresh.js +85 -0
- package/dist/_internal/server/jobs/core/whatsappNotify.js +16 -0
- package/dist/_internal/server/jobs/handlers/dailyStatusDigest.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/dailyStatusDigest.js +4 -0
- package/dist/_internal/server/jobs/handlers/index.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/index.js +3 -0
- package/dist/_internal/server/jobs/handlers/testerSandboxRefresh.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/testerSandboxRefresh.js +2 -0
- package/dist/_internal/server/notifications/channel-health.d.ts +27 -0
- package/dist/_internal/server/notifications/channel-health.js +101 -0
- package/dist/_internal/shared/features/auctions/config.d.ts +20 -0
- package/dist/_internal/shared/features/auctions/config.js +31 -1
- package/dist/_internal/shared/features/brands/schema.d.ts +0 -6
- package/dist/_internal/shared/features/brands/schema.js +0 -1
- package/dist/_internal/shared/features/categories/bundle-schemas.d.ts +7 -0
- package/dist/_internal/shared/features/categories/bundle-schemas.js +3 -0
- package/dist/_internal/shared/features/orders/schema.d.ts +8 -8
- package/dist/_internal/shared/listing-types/art/config.d.ts +1 -1
- package/dist/_internal/shared/listing-types/art/config.js +1 -1
- package/dist/_internal/shared/listing-types/classified/config.d.ts +1 -1
- package/dist/_internal/shared/listing-types/classified/config.js +1 -1
- package/dist/_internal/shared/listing-types/digital-code/config.d.ts +1 -1
- package/dist/_internal/shared/listing-types/digital-code/config.js +1 -1
- package/dist/_internal/shared/listing-types/live/config.d.ts +1 -1
- package/dist/_internal/shared/listing-types/live/config.js +1 -1
- package/dist/_internal/shared/listing-types/prize-draw/config.d.ts +1 -1
- package/dist/_internal/shared/listing-types/prize-draw/config.js +1 -1
- package/dist/_internal/shared/listing-types/stickers/config.d.ts +1 -1
- package/dist/_internal/shared/listing-types/stickers/config.js +1 -1
- package/dist/client.d.ts +21 -12
- package/dist/client.js +14 -7
- package/dist/constants/api-endpoints.d.ts +9 -9
- package/dist/constants/api-endpoints.js +3 -3
- package/dist/contracts/client-auth.d.ts +3 -7
- package/dist/contracts/email.d.ts +1 -0
- package/dist/features/account/components/AddressForm.js +24 -2
- package/dist/features/account/components/BecomeSellerView.d.ts +1 -2
- package/dist/features/account/components/BecomeSellerView.js +1 -1
- package/dist/features/account/components/NotificationBell.js +1 -1
- package/dist/features/account/components/UserBidsView.d.ts +1 -0
- package/dist/features/account/components/UserBidsView.js +47 -0
- package/dist/features/account/components/UserOrdersView.d.ts +4 -11
- package/dist/features/account/components/UserOrdersView.js +54 -4
- package/dist/features/account/components/UserReturnsView.d.ts +4 -9
- package/dist/features/account/components/UserReturnsView.js +37 -3
- package/dist/features/account/components/index.d.ts +3 -2
- package/dist/features/account/components/index.js +1 -0
- package/dist/features/admin/actions/admin-actions.js +22 -0
- package/dist/features/admin/actions/admin-coupon-actions.js +10 -0
- package/dist/features/admin/actions/notification-actions.js +4 -3
- package/dist/features/admin/components/AdminAdsView.d.ts +2 -3
- package/dist/features/admin/components/AdminAdsView.js +86 -89
- package/dist/features/admin/components/AdminAllEventEntriesView.js +18 -3
- package/dist/features/admin/components/AdminAuditLogView.d.ts +11 -0
- package/dist/features/admin/components/AdminAuditLogView.js +95 -0
- package/dist/features/admin/components/AdminBlogEditorView.js +37 -3
- package/dist/features/admin/components/AdminBrandEditorView.js +10 -6
- package/dist/features/admin/components/AdminBundleEditorView.js +18 -2
- package/dist/features/admin/components/AdminCarouselEditorView.js +2 -2
- package/dist/features/admin/components/AdminCarouselGroupEditorView.js +2 -2
- package/dist/features/admin/components/AdminCartsView.js +34 -4
- package/dist/features/admin/components/AdminCategoryEditorView.js +5 -4
- package/dist/features/admin/components/AdminCouponEditorView.js +2 -2
- package/dist/features/admin/components/AdminFaqEditorView.js +2 -2
- package/dist/features/admin/components/AdminHistoryView.js +11 -1
- package/dist/features/admin/components/AdminNotificationsView.js +27 -1
- package/dist/features/admin/components/AdminOrderEditorView.d.ts +10 -1
- package/dist/features/admin/components/AdminOrderEditorView.js +4 -3
- package/dist/features/admin/components/AdminOrdersView.js +92 -24
- package/dist/features/admin/components/AdminPayoutMarkPaidModal.d.ts +7 -0
- package/dist/features/admin/components/AdminPayoutMarkPaidModal.js +47 -0
- package/dist/features/admin/components/AdminPayoutsView.js +15 -31
- package/dist/features/admin/components/AdminProductEditorView.js +26 -3
- package/dist/features/admin/components/AdminSiteSettingsView.js +31 -4
- package/dist/features/admin/components/AdminStoreAddressesView.js +22 -2
- package/dist/features/admin/components/AdminStoresView.js +7 -0
- package/dist/features/admin/components/AdminSublistingCategoryEditorView.js +2 -2
- package/dist/features/admin/components/AdminTesterChecklistItemEditorView.js +2 -2
- package/dist/features/admin/components/AdminUserEditorView.d.ts +2 -2
- package/dist/features/admin/components/AdminUserEditorView.js +4 -2
- package/dist/features/admin/components/AdminUsersView.js +1 -1
- package/dist/features/admin/components/AdminWishlistsView.js +14 -1
- package/dist/features/admin/components/DataListingView.d.ts +2 -2
- package/dist/features/admin/components/ViewAuditLogEntryModal.d.ts +19 -0
- package/dist/features/admin/components/ViewAuditLogEntryModal.js +22 -0
- package/dist/features/admin/components/ViewNotificationModal.d.ts +21 -0
- package/dist/features/admin/components/ViewNotificationModal.js +15 -0
- package/dist/features/admin/components/analytics/AdminPageViewsReportView.d.ts +4 -7
- package/dist/features/admin/components/analytics/AdminPageViewsReportView.js +56 -36
- package/dist/features/admin/components/index.d.ts +8 -1
- package/dist/features/admin/components/index.js +4 -0
- package/dist/features/admin/constants/filter-tabs.d.ts +49 -0
- package/dist/features/admin/constants/filter-tabs.js +27 -0
- package/dist/features/admin/hooks/useAdminListing.d.ts +6 -0
- package/dist/features/admin/hooks/useAdminListing.js +3 -1
- package/dist/features/admin/hooks/useAdminListingData.d.ts +9 -1
- package/dist/features/admin/hooks/useAdminListingData.js +9 -2
- package/dist/features/admin/repository/site-settings.repository.d.ts +5 -0
- package/dist/features/admin/schemas/firestore.d.ts +21 -0
- package/dist/features/admin/schemas/firestore.js +6 -0
- package/dist/features/analytics/types.d.ts +1 -1
- package/dist/features/analytics/types.js +8 -0
- package/dist/features/auctions/actions/bid-actions.js +4 -1
- package/dist/features/auctions/components/AuctionBottomActions.d.ts +2 -1
- package/dist/features/auctions/components/AuctionBottomActions.js +14 -1
- package/dist/features/auctions/components/AuctionDetailPageView.js +21 -9
- package/dist/features/auctions/components/LiveMinIncrement.d.ts +18 -0
- package/dist/features/auctions/components/LiveMinIncrement.js +17 -0
- package/dist/features/auctions/components/PlaceBidFormClient.d.ts +5 -2
- package/dist/features/auctions/components/PlaceBidFormClient.js +7 -3
- package/dist/features/auctions/schemas/index.d.ts +5 -0
- package/dist/features/audit-log/repository/audit-log.repository.d.ts +45 -0
- package/dist/features/audit-log/repository/audit-log.repository.js +30 -0
- package/dist/features/audit-log/schemas/firestore.d.ts +59 -0
- package/dist/features/audit-log/schemas/firestore.js +35 -0
- package/dist/features/auth/actions/index.d.ts +0 -1
- package/dist/features/auth/actions/index.js +0 -1
- package/dist/features/auth/hooks/useAuth.d.ts +8 -35
- package/dist/features/auth/hooks/useAuth.js +21 -37
- package/dist/features/auth/permissions/constants.d.ts +1 -1
- package/dist/features/auth/permissions/constants.js +2 -0
- package/dist/features/blog/schemas/index.d.ts +32 -0
- package/dist/features/catalogue/components/AdminCatalogueApprovalsView.js +31 -3
- package/dist/features/catalogue/components/CatalogueItemEditorView.js +2 -1
- package/dist/features/catalogue/components/PublicCatalogueItemDetailView.d.ts +11 -0
- package/dist/features/catalogue/components/PublicCatalogueItemDetailView.js +33 -0
- package/dist/features/catalogue/components/PublicCatalogueView.js +2 -1
- package/dist/features/catalogue/index.d.ts +2 -0
- package/dist/features/catalogue/index.js +1 -0
- package/dist/features/categories/components/BrandDetailPageView.js +14 -12
- package/dist/features/categories/components/BundleDetailView.d.ts +3 -1
- package/dist/features/categories/components/BundleDetailView.js +3 -2
- package/dist/features/categories/components/CategoryDetailPageView.js +7 -3
- package/dist/features/categories/components/CategoryHighlightsAndFaqSection.d.ts +14 -0
- package/dist/features/categories/components/CategoryHighlightsAndFaqSection.js +22 -0
- package/dist/features/categories/components/CategoryProductsView.d.ts +1 -5
- package/dist/features/categories/components/CategoryProductsView.js +1 -1
- package/dist/features/categories/components/CategorySelectorCreate.js +16 -2
- package/dist/features/categories/schemas/firestore.d.ts +19 -2
- package/dist/features/categories/schemas/index.d.ts +24 -3
- package/dist/features/categories/schemas/index.js +3 -1
- package/dist/features/categories/types/index.d.ts +7 -0
- package/dist/features/checkout/actions/checkout-value-otp-actions.d.ts +17 -3
- package/dist/features/checkout/actions/checkout-value-otp-actions.js +101 -22
- package/dist/features/classified/components/ClassifiedContactSellerPanel.d.ts +12 -0
- package/dist/features/classified/components/ClassifiedContactSellerPanel.js +49 -0
- package/dist/features/classified/components/ClassifiedDetailPageView.d.ts +7 -0
- package/dist/features/classified/components/ClassifiedDetailPageView.js +69 -0
- package/dist/features/contact/components/ContactForm.js +17 -4
- package/dist/features/contact/components/ContactPageView.js +8 -2
- package/dist/features/contact/email.d.ts +1 -0
- package/dist/features/contact/email.js +3 -0
- package/dist/features/digital-codes/components/DigitalCodeDetailPageView.d.ts +19 -0
- package/dist/features/digital-codes/components/DigitalCodeDetailPageView.js +79 -0
- package/dist/features/events/components/AdminEventEditorView.js +54 -2
- package/dist/features/events/components/AdminEventEntriesView.d.ts +2 -16
- package/dist/features/events/components/AdminEventEntriesView.js +56 -89
- package/dist/features/events/components/EventFormDrawer.js +5 -1
- package/dist/features/events/components/EventRaffleEntryForm.d.ts +1 -3
- package/dist/features/events/components/EventRaffleEntryForm.js +1 -1
- package/dist/features/events/schemas/index.d.ts +22 -0
- package/dist/features/grouped/components/GroupedListingsCarousel.d.ts +13 -0
- package/dist/features/grouped/components/GroupedListingsCarousel.js +31 -0
- package/dist/features/grouped/repository/grouped-listings.repository.d.ts +12 -0
- package/dist/features/grouped/repository/grouped-listings.repository.js +34 -0
- package/dist/features/homepage/components/CharacterHotspotForm.js +202 -187
- package/dist/features/homepage/components/SectionCarousel.d.ts +1 -7
- package/dist/features/homepage/components/SectionCarousel.js +1 -1
- package/dist/features/layout/BottomActions.js +4 -3
- package/dist/features/layout/BottomActionsContext.d.ts +10 -0
- package/dist/features/layout/BottomActionsContext.js +5 -1
- package/dist/features/layout/TitleBarLayout.d.ts +1 -1
- package/dist/features/layout/TitleBarLayout.js +3 -3
- package/dist/features/layout/hooks/useBottomActions.d.ts +2 -0
- package/dist/features/layout/hooks/useBottomActions.js +4 -1
- package/dist/features/live/components/LiveItemDetailPageView.d.ts +9 -0
- package/dist/features/live/components/LiveItemDetailPageView.js +74 -0
- package/dist/features/media/MediaImage.js +39 -3
- package/dist/features/media/MediaVideo.js +17 -1
- package/dist/features/media/types/index.d.ts +10 -0
- package/dist/features/media/types/index.js +1 -0
- package/dist/features/media/upload/MediaUploadField.d.ts +7 -1
- package/dist/features/media/upload/MediaUploadField.js +11 -7
- package/dist/features/media/upload/video-poster.d.ts +2 -0
- package/dist/features/media/upload/video-poster.js +16 -1
- package/dist/features/media/upload/video-poster.test.js +1 -1
- package/dist/features/orders/components/OrderStatusTimeline.d.ts +12 -0
- package/dist/features/orders/components/OrderStatusTimeline.js +55 -0
- package/dist/features/orders/components/index.d.ts +2 -0
- package/dist/features/orders/components/index.js +1 -0
- package/dist/features/orders/constants/payment-window.d.ts +25 -0
- package/dist/features/orders/constants/payment-window.js +25 -0
- package/dist/features/orders/repository/orders.repository.d.ts +32 -0
- package/dist/features/orders/repository/orders.repository.js +56 -0
- package/dist/features/orders/schemas/index.d.ts +24 -24
- package/dist/features/orders/types/index.d.ts +33 -0
- package/dist/features/pre-orders/components/PreOrderDetailPageView.js +9 -1
- package/dist/features/pre-orders/schemas/index.d.ts +5 -0
- package/dist/features/products/components/BidHistory.js +9 -1
- package/dist/features/products/components/GroupSettingsPanel.js +25 -3
- package/dist/features/products/components/PrizeDrawDetailPageView.js +11 -1
- package/dist/features/products/components/PrizeDrawEntryActions.d.ts +1 -4
- package/dist/features/products/components/PrizeDrawEntryActions.js +1 -1
- package/dist/features/products/components/ProductDetailActions.d.ts +1 -2
- package/dist/features/products/components/ProductDetailActions.js +1 -1
- package/dist/features/products/components/ProductDetailPageView.js +11 -116
- package/dist/features/products/components/ProductGrid.js +15 -1
- package/dist/features/products/components/RelatedItemsSection.d.ts +16 -0
- package/dist/features/products/components/RelatedItemsSection.js +17 -0
- package/dist/features/products/constants/action-defs.d.ts +1 -0
- package/dist/features/products/constants/action-defs.js +1 -0
- package/dist/features/products/schemas/index.d.ts +5 -0
- package/dist/features/promotions/components/CouponsIndexListing.d.ts +1 -3
- package/dist/features/promotions/components/CouponsIndexListing.js +1 -1
- package/dist/features/search/components/SearchView.d.ts +1 -3
- package/dist/features/search/components/SearchView.js +1 -1
- package/dist/features/seller/components/QuickProductForm.js +2 -1
- package/dist/features/seller/components/SellerBidsView.js +66 -107
- package/dist/features/seller/components/SellerGoogleReviewsView.js +17 -1
- package/dist/features/seller/components/SellerOrdersView.d.ts +15 -3
- package/dist/features/seller/components/SellerOrdersView.js +134 -163
- package/dist/features/seller/components/SellerPayoutMethodsView.js +60 -48
- package/dist/features/seller/components/SellerPayoutSettingsView.js +69 -4
- package/dist/features/seller/components/SellerPayoutsView.d.ts +12 -0
- package/dist/features/seller/components/SellerPayoutsView.js +25 -5
- package/dist/features/seller/components/SellerProductShell.d.ts +10 -3
- package/dist/features/seller/components/SellerProductShell.js +90 -37
- package/dist/features/seller/components/SellerProductsFilterDrawer.d.ts +2 -7
- package/dist/features/seller/components/SellerProductsFilterDrawer.js +4 -4
- package/dist/features/seller/components/SellerProductsView.d.ts +2 -4
- package/dist/features/seller/components/SellerProductsView.js +131 -189
- package/dist/features/seller/components/SellerReviewsView.js +95 -133
- package/dist/features/seller/components/SellerShippingConfigsView.js +47 -67
- package/dist/features/seller/components/SellerShippingView.js +41 -4
- package/dist/features/seller/components/SellerStoreCategoriesView.js +51 -59
- package/dist/features/seller/components/SellerStorefrontView.d.ts +6 -1
- package/dist/features/seller/components/SellerStorefrontView.js +42 -5
- package/dist/features/seller/components/index.d.ts +2 -2
- package/dist/features/seller/components/index.js +2 -2
- package/dist/features/shell/FormShell.d.ts +24 -4
- package/dist/features/shell/FormShell.js +12 -6
- package/dist/features/shell/QuickFormDrawer.js +67 -12
- package/dist/features/shell/StepForm.d.ts +30 -1
- package/dist/features/shell/StepForm.js +47 -4
- package/dist/features/shell/index.d.ts +1 -1
- package/dist/features/shell/index.js +1 -1
- package/dist/features/shipments/components/AdminShipmentEditorView.js +2 -2
- package/dist/features/shipments/schemas/validation.d.ts +14 -4
- package/dist/features/tester/components/AdminTesterFeedbackListView.js +23 -2
- package/dist/features/tester/components/TesterChecklistStepRow.js +1 -1
- package/dist/features/tester/components/TesterHubView.js +3 -2
- package/dist/features/tester/seed-data/orders-tester-seed-data.js +9 -1
- package/dist/features/tester/seed-data/products-tester-seed-data.js +43 -32
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +487 -18
- package/dist/index.d.ts +17 -3
- package/dist/index.js +23 -5
- package/dist/next/routing/route-map.d.ts +13 -0
- package/dist/next/routing/route-map.js +12 -0
- package/dist/providers/email-resend/provider.js +1 -0
- package/dist/providers/firebase-client/auth.d.ts +1 -0
- package/dist/providers/firebase-client/auth.js +7 -1
- package/dist/repositories/index.d.ts +1 -0
- package/dist/repositories/index.js +1 -0
- package/dist/schemas/registry.d.ts +61 -25
- package/dist/security/pii-mask.js +1 -1
- package/dist/seed/bids-seed-data.js +48 -3
- package/dist/seed/categories-seed-data.js +69 -0
- package/dist/seed/grouped-listings-seed-data.js +167 -0
- package/dist/seed/payouts-seed-data.js +4 -4
- package/dist/seed/products-art-seed-data.js +188 -5
- package/dist/seed/products-auctions-seed-data.js +226 -6
- package/dist/seed/products-classifieds-seed-data.js +255 -8
- package/dist/seed/products-digital-codes-seed-data.js +258 -19
- package/dist/seed/products-live-items-seed-data.js +143 -10
- package/dist/seed/products-preorders-seed-data.js +213 -8
- package/dist/seed/products-prize-draws-seed-data.js +198 -0
- package/dist/seed/products-standard-seed-data.js +234 -4
- package/dist/seed/products-stickers-seed-data.js +178 -5
- package/dist/seed/site-settings-seed-data.js +23 -30
- package/dist/seed/store-extensions-seed-data.js +2 -2
- package/dist/server-entry.d.ts +6 -2
- package/dist/server-entry.js +24 -3
- package/dist/server.d.ts +10 -3
- package/dist/server.js +11 -4
- package/dist/styles.css +29 -1
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/components/Form.d.ts +11 -2
- package/dist/ui/components/Form.js +4 -3
- package/dist/ui/components/FormField.d.ts +3 -1
- package/dist/ui/components/FormField.js +22 -4
- package/dist/ui/components/ImageLightbox.js +37 -3
- package/dist/ui/components/ImageLightbox.style.css +28 -0
- package/dist/ui/components/ListingLayout.d.ts +1 -2
- package/dist/ui/components/ListingLayout.js +1 -1
- package/dist/ui/components/RecordDetailModal.d.ts +68 -0
- package/dist/ui/components/RecordDetailModal.js +8 -0
- package/dist/ui/components/SlottedListingView.js +9 -1
- package/dist/ui/forms/FormErrorSummary.d.ts +25 -0
- package/dist/ui/forms/FormErrorSummary.js +37 -0
- package/dist/ui/forms/FormShell.d.ts +37 -2
- package/dist/ui/forms/FormShell.js +37 -7
- package/dist/ui/forms/FormShell.style.css +17 -0
- package/dist/ui/forms/index.d.ts +2 -0
- package/dist/ui/forms/index.js +1 -0
- package/dist/ui/index.d.ts +4 -0
- package/dist/ui/index.js +5 -0
- package/dist/utils/media-url.d.ts +12 -1
- package/dist/utils/media-url.js +59 -1
- package/dist/validation/schemas.d.ts +4 -4
- package/package.json +1 -1
- package/dist/_internal/client/features/classified/ClassifiedDetailView.d.ts +0 -9
- package/dist/_internal/client/features/classified/ClassifiedDetailView.js +0 -55
- package/dist/_internal/client/features/digital-code/DigitalCodeDetailView.d.ts +0 -18
- package/dist/_internal/client/features/digital-code/DigitalCodeDetailView.js +0 -21
- package/dist/_internal/client/features/live/LiveItemDetailView.d.ts +0 -9
- package/dist/_internal/client/features/live/LiveItemDetailView.js +0 -25
- package/dist/_internal/server/jobs/core/prizeRevealClose.d.ts +0 -2
- package/dist/_internal/server/jobs/core/prizeRevealClose.js +0 -28
- package/dist/_internal/server/jobs/core/prizeRevealExpiry.d.ts +0 -2
- package/dist/_internal/server/jobs/core/prizeRevealExpiry.js +0 -61
- package/dist/_internal/server/jobs/core/prizeRevealOpen.d.ts +0 -2
- package/dist/_internal/server/jobs/core/prizeRevealOpen.js +0 -68
- package/dist/_internal/server/jobs/core/prizeRevealReminder.d.ts +0 -2
- package/dist/_internal/server/jobs/core/prizeRevealReminder.js +0 -49
- package/dist/_internal/server/jobs/handlers/prizeRevealClose.d.ts +0 -2
- package/dist/_internal/server/jobs/handlers/prizeRevealClose.js +0 -2
- package/dist/_internal/server/jobs/handlers/prizeRevealExpiry.d.ts +0 -2
- package/dist/_internal/server/jobs/handlers/prizeRevealExpiry.js +0 -2
- package/dist/_internal/server/jobs/handlers/prizeRevealOpen.d.ts +0 -2
- package/dist/_internal/server/jobs/handlers/prizeRevealOpen.js +0 -2
- package/dist/_internal/server/jobs/handlers/prizeRevealReminder.d.ts +0 -2
- package/dist/_internal/server/jobs/handlers/prizeRevealReminder.js +0 -2
- package/dist/features/auth/consent-otp.d.ts +0 -57
- package/dist/features/auth/consent-otp.js +0 -127
- package/dist/features/cart/components/CheckoutOtpModal.d.ts +0 -15
- package/dist/features/cart/components/CheckoutOtpModal.js +0 -12
- package/dist/features/checkout/actions/checkout-actions.d.ts +0 -31
- package/dist/features/checkout/actions/checkout-actions.js +0 -124
- package/dist/features/classified/components/ClassifiedIndexListing.d.ts +0 -5
- package/dist/features/classified/components/ClassifiedIndexListing.js +0 -123
- package/dist/features/classified/components/ClassifiedListView.d.ts +0 -6
- package/dist/features/classified/components/ClassifiedListView.js +0 -44
- package/dist/features/digital-codes/components/DigitalCodesIndexListing.d.ts +0 -5
- package/dist/features/digital-codes/components/DigitalCodesIndexListing.js +0 -114
- package/dist/features/digital-codes/components/DigitalCodesListView.d.ts +0 -6
- package/dist/features/digital-codes/components/DigitalCodesListView.js +0 -38
- package/dist/features/live/components/LiveItemsIndexListing.d.ts +0 -5
- package/dist/features/live/components/LiveItemsIndexListing.js +0 -115
- package/dist/features/live/components/LiveItemsListView.d.ts +0 -6
- package/dist/features/live/components/LiveItemsListView.js +0 -44
- package/dist/features/seller/components/SellerTemplatesView.d.ts +0 -12
- package/dist/features/seller/components/SellerTemplatesView.js +0 -276
- package/dist/react/hooks/useVisibleItems.d.ts +0 -37
- package/dist/react/hooks/useVisibleItems.js +0 -59
- package/dist/ui/components/TabStrip.d.ts +0 -34
- package/dist/ui/components/TabStrip.js +0 -56
- package/dist/ui/components/TabStrip.style.css +0 -97
|
@@ -8,6 +8,9 @@ import { Container, Div, Heading, Main, Nav, Section, Span, Text } from "../../.
|
|
|
8
8
|
import { MediaImage } from "../../media/MediaImage";
|
|
9
9
|
import { CategoryDetailTabs } from "./CategoryDetailTabs";
|
|
10
10
|
import { CategoryGrid } from "./CategoryGrid";
|
|
11
|
+
import { CategoryHighlightsAndFaqSection } from "./CategoryHighlightsAndFaqSection";
|
|
12
|
+
import { GroupedListingsCarousel } from "../../grouped/components/GroupedListingsCarousel";
|
|
13
|
+
import { getGroupsForCategory } from "../../../_internal/server/features/grouped/data";
|
|
11
14
|
const __O = {
|
|
12
15
|
hidden: "overflow-hidden",
|
|
13
16
|
xAuto: "overflow-x-auto",
|
|
@@ -25,7 +28,7 @@ export async function CategoryDetailPageView({ slug }) {
|
|
|
25
28
|
? await categoriesRepository.getDescendantIds(category.id).catch(() => [])
|
|
26
29
|
: [];
|
|
27
30
|
const categoriesIn = category?.id ? [category.id, ...descendantIds] : null;
|
|
28
|
-
const [productsResult, auctionsCountResult, preOrdersCountResult, prizeDrawsCountResult, bundlesResult, childCategories, rootSiblingCategories] = await Promise.all([
|
|
31
|
+
const [productsResult, auctionsCountResult, preOrdersCountResult, prizeDrawsCountResult, bundlesResult, childCategories, rootSiblingCategories, groupedListings] = await Promise.all([
|
|
29
32
|
categoriesIn
|
|
30
33
|
? productRepository
|
|
31
34
|
.list({
|
|
@@ -82,6 +85,7 @@ export async function CategoryDetailPageView({ slug }) {
|
|
|
82
85
|
category?.rootId
|
|
83
86
|
? categoriesRepository.getCategoriesByRootId(category.rootId).catch(() => [])
|
|
84
87
|
: Promise.resolve([]),
|
|
88
|
+
category?.slug ? getGroupsForCategory(category.slug).catch(() => []) : Promise.resolve([]),
|
|
85
89
|
]);
|
|
86
90
|
const relatedCategories = rootSiblingCategories.filter((c) => c.id !== category?.id && (!c.categoryType || c.categoryType === "category"));
|
|
87
91
|
// Stores tab — query stores whose storeCategory matches this category or any child
|
|
@@ -126,7 +130,7 @@ export async function CategoryDetailPageView({ slug }) {
|
|
|
126
130
|
const storeCount = categoryStores.length;
|
|
127
131
|
const coverImage = category?.display?.coverImage;
|
|
128
132
|
const hasCover = Boolean(coverImage);
|
|
129
|
-
return (_jsxs(Main, { children: [_jsxs(Section, { className: `relative ${__O.hidden} ${hasCover ? "min-h-[220px] md:min-h-[280px]" : "bg-[var(--appkit-color-bg)]"}`, children: [hasCover && (_jsxs(_Fragment, { children: [_jsx(Div, { className: "absolute inset-0", children: _jsx(MediaImage, { src: coverImage, alt: "", size: "banner" }) }), _jsx(Div, { surface: "overlay-md", className: "absolute inset-0" })] })), _jsxs(Div, { className: "relative z-10 max-w-7xl mx-auto", paddingY: hasCover ? "y-3xl" : "y-xl", padding: "x-md", children: [_jsxs(Nav, { layout: "flex", gap: "2xs", textSize: "sm", className: "mb-4", "aria-label": "Breadcrumb", children: [_jsx(Link, { href: String(ROUTES.HOME), className: hasCover ? "text-white/70 hover:text-white transition-colors" : "text-[var(--appkit-color-text-muted)] hover:text-primary-600 transition-colors", children: "Home" }), _jsx(Span, { className: hasCover ? "text-white/40" : "text-zinc-400", children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORIES), className: hasCover ? "text-white/70 hover:text-white transition-colors" : "text-[var(--appkit-color-text-muted)] hover:text-primary-600 transition-colors", children: "Categories" }), _jsx(Span, { className: hasCover ? "text-white/40" : "text-zinc-400", children: "/" }), _jsx(Span, { weight: "medium", className: hasCover ? "text-white" : "text-[var(--appkit-color-text)]", children: category?.name ?? slug })] }), _jsx(Heading, { color: "inverse", level: 1, className: `mb-2 ${hasCover ? "" : "text-[var(--appkit-color-text)]"}`, size: "3xl", mdSize: "4xl", weight: "bold", children: category?.name ?? slug }), category?.description && typeof category.description === "string" && !category.description.startsWith("{") && (_jsx(Text, { color: hasCover ? "inverse" : "muted", className: `max-w-2xl mb-4 ${hasCover ? "/80" : ""}`, size: "base", children: category.description })), _jsxs(Row, { wrap: true, gap: "sm", children: [productCount > 0 && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: "inverse", size: "xs", weight: "medium", className: hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "bg-primary/10 text-primary-700 dark:text-primary-400", rounded: "full", padding: "pill-sm-tall", children: [productCount.toLocaleString(), " ", productCount === 1 ? "product" : "products"] })), auctionCount > 0 && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: hasCover ? "inverse" : "warning", surface: hasCover ? undefined : "warning-surface", size: "xs", weight: "medium", className: hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "", rounded: "full", padding: "pill-sm-tall", children: [auctionCount.toLocaleString(), " ", auctionCount === 1 ? "auction" : "auctions"] })), preOrderCount > 0 && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: hasCover ? "inverse" : "info", surface: hasCover ? undefined : "info-surface", size: "xs", weight: "medium", className: hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "", rounded: "full", padding: "pill-sm-tall", children: [preOrderCount.toLocaleString(), " ", preOrderCount === 1 ? "pre-order" : "pre-orders"] })), storeCount > 0 && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: hasCover ? "inverse" : "success", surface: hasCover ? undefined : "success-surface", size: "xs", weight: "medium", className: hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "", rounded: "full", padding: "pill-sm-tall", children: [storeCount.toLocaleString(), " ", storeCount === 1 ? "store" : "stores"] }))] })] })] }), childCategories.length > 0 && (_jsx(Section, { border: "subtle", surface: "default", className: "border-b", children: _jsx(Div, { className: "max-w-7xl mx-auto", padding: "inline", children: _jsx(Div, { layout: "flex", gap: "2", className: `.5 ${__O.xAuto} [scrollbar-width:none]`, padding: "b-2xs", children: childCategories.map((child) => (_jsxs(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(child.slug)),
|
|
133
|
+
return (_jsxs(Main, { children: [_jsxs(Section, { className: `relative ${__O.hidden} ${hasCover ? "min-h-[220px] md:min-h-[280px]" : "bg-[var(--appkit-color-bg)]"}`, children: [hasCover && (_jsxs(_Fragment, { children: [_jsx(Div, { className: "absolute inset-0", children: _jsx(MediaImage, { src: coverImage, alt: "", size: "banner" }) }), _jsx(Div, { surface: "overlay-md", className: "absolute inset-0" })] })), _jsxs(Div, { className: "relative z-10 max-w-7xl mx-auto", paddingY: hasCover ? "y-3xl" : "y-xl", padding: "x-md", children: [_jsxs(Nav, { layout: "flex", gap: "2xs", textSize: "sm", className: "mb-4", "aria-label": "Breadcrumb", children: [_jsx(Link, { href: String(ROUTES.HOME), className: hasCover ? "text-white/70 hover:text-white transition-colors" : "text-[var(--appkit-color-text-muted)] hover:text-primary-600 transition-colors", children: "Home" }), _jsx(Span, { className: hasCover ? "text-white/40" : "text-zinc-400", children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORIES), className: hasCover ? "text-white/70 hover:text-white transition-colors" : "text-[var(--appkit-color-text-muted)] hover:text-primary-600 transition-colors", children: "Categories" }), _jsx(Span, { className: hasCover ? "text-white/40" : "text-zinc-400", children: "/" }), _jsx(Span, { weight: "medium", className: hasCover ? "text-white" : "text-[var(--appkit-color-text)]", children: category?.name ?? slug })] }), _jsx(Heading, { color: "inverse", level: 1, className: `mb-2 ${hasCover ? "" : "text-[var(--appkit-color-text)]"}`, size: "3xl", mdSize: "4xl", weight: "bold", children: category?.name ?? slug }), category?.description && typeof category.description === "string" && !category.description.startsWith("{") && (_jsx(Text, { color: hasCover ? "inverse" : "muted", className: `max-w-2xl mb-4 ${hasCover ? "/80" : ""}`, size: "base", children: category.description })), _jsxs(Row, { wrap: true, gap: "sm", children: [productCount > 0 && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: "inverse", size: "xs", weight: "medium", className: hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "bg-primary/10 text-primary-700 dark:text-primary-400", rounded: "full", padding: "pill-sm-tall", children: [productCount.toLocaleString(), " ", productCount === 1 ? "product" : "products"] })), auctionCount > 0 && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: hasCover ? "inverse" : "warning", surface: hasCover ? undefined : "warning-surface", size: "xs", weight: "medium", className: hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "", rounded: "full", padding: "pill-sm-tall", children: [auctionCount.toLocaleString(), " ", auctionCount === 1 ? "auction" : "auctions"] })), preOrderCount > 0 && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: hasCover ? "inverse" : "info", surface: hasCover ? undefined : "info-surface", size: "xs", weight: "medium", className: hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "", rounded: "full", padding: "pill-sm-tall", children: [preOrderCount.toLocaleString(), " ", preOrderCount === 1 ? "pre-order" : "pre-orders"] })), storeCount > 0 && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: hasCover ? "inverse" : "success", surface: hasCover ? undefined : "success-surface", size: "xs", weight: "medium", className: hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "", rounded: "full", padding: "pill-sm-tall", children: [storeCount.toLocaleString(), " ", storeCount === 1 ? "store" : "stores"] }))] })] })] }), _jsx(CategoryHighlightsAndFaqSection, { highlights: category?.highlights, faqs: category?.faqs }), childCategories.length > 0 && (_jsx(Section, { border: "subtle", surface: "default", className: "border-b", children: _jsx(Div, { className: "max-w-7xl mx-auto", padding: "inline", children: _jsx(Div, { layout: "flex", gap: "2", className: `.5 ${__O.xAuto} [scrollbar-width:none]`, padding: "b-2xs", children: childCategories.map((child) => (_jsxs(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(child.slug)),
|
|
130
134
|
// audit-responsive-wrap-ok: deliberate horizontal-scroll chip row (parent has overflow-x-auto) — each chip staying single-line is the intended design, not hidden content
|
|
131
135
|
className: "flex-shrink-0 flex items-center gap-[var(--appkit-space-1-5)] rounded-full border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface-elevated)] px-[var(--appkit-space-4)] py-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-sm)] font-medium text-[var(--appkit-color-text-muted)] hover:border-primary hover:text-primary transition-colors whitespace-nowrap", children: [child.display?.icon && (_jsx(Span, { className: "leading-none", children: child.display.icon })), child.name, (child.metrics?.productCount ?? 0) > 0 && (_jsx(Span, { size: "xs", color: "muted", children: (child.metrics?.productCount ?? 0).toLocaleString() }))] }, child.id))) }) }) })), _jsx(Section, { padding: "y-lg", children: _jsx(Container, { size: "xl", children: _jsx(CategoryDetailTabs, { categorySlug: slug, categoryId: category?.id, initialProductsData: productsResult ?? undefined, initialBundles: bundlesResult ?? [], initialStores: categoryStores, counts: {
|
|
132
136
|
products: productCount,
|
|
@@ -135,5 +139,5 @@ export async function CategoryDetailPageView({ slug }) {
|
|
|
135
139
|
prizeDraws: prizeDrawCount,
|
|
136
140
|
bundles: bundleCount,
|
|
137
141
|
stores: storeCount,
|
|
138
|
-
} }) }) }), relatedCategories.length > 0 && (_jsx(Section, { border: "subtle", surface: "default", className: "border-t", padding: "y-lg", children: _jsxs(Container, { size: "xl", children: [_jsx(Heading, { level: 2, className: "mb-4", size: "xl", weight: "semibold", children: "Related Categories" }), _jsx(CategoryGrid, { categories: relatedCategories, getHref: (c) => String(ROUTES.PUBLIC.CATEGORY_DETAIL(c.slug)) })] }) }))] }));
|
|
142
|
+
} }) }) }), groupedListings.length > 0 && (_jsx(Section, { padding: "y-lg", children: _jsx(Container, { size: "xl", children: _jsx(GroupedListingsCarousel, { groups: groupedListings }) }) })), relatedCategories.length > 0 && (_jsx(Section, { border: "subtle", surface: "default", className: "border-t", padding: "y-lg", children: _jsxs(Container, { size: "xl", children: [_jsx(Heading, { level: 2, className: "mb-4", size: "xl", weight: "semibold", children: "Related Categories" }), _jsx(CategoryGrid, { categories: relatedCategories, getHref: (c) => String(ROUTES.PUBLIC.CATEGORY_DETAIL(c.slug)) })] }) }))] }));
|
|
139
143
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface CategoryHighlightsAndFaqSectionProps {
|
|
2
|
+
highlights?: string[];
|
|
3
|
+
faqs?: {
|
|
4
|
+
question: string;
|
|
5
|
+
answer: string;
|
|
6
|
+
}[];
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Shared "why shop here" highlights + FAQ accordion, rendered on both
|
|
10
|
+
* category and brand detail pages — the only editorial content either page
|
|
11
|
+
* has beyond a one-line description (see CategoryDisplay's field list).
|
|
12
|
+
* Renders nothing when both arrays are empty/absent.
|
|
13
|
+
*/
|
|
14
|
+
export declare function CategoryHighlightsAndFaqSection({ highlights, faqs }: CategoryHighlightsAndFaqSectionProps): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Container, Heading, Li, Section, Span, Stack, Ul } from "../../../ui";
|
|
3
|
+
import { FAQAccordion } from "../../faq/components/FAQAccordion";
|
|
4
|
+
/**
|
|
5
|
+
* Shared "why shop here" highlights + FAQ accordion, rendered on both
|
|
6
|
+
* category and brand detail pages — the only editorial content either page
|
|
7
|
+
* has beyond a one-line description (see CategoryDisplay's field list).
|
|
8
|
+
* Renders nothing when both arrays are empty/absent.
|
|
9
|
+
*/
|
|
10
|
+
export function CategoryHighlightsAndFaqSection({ highlights, faqs }) {
|
|
11
|
+
const hasHighlights = Boolean(highlights && highlights.length > 0);
|
|
12
|
+
const hasFaqs = Boolean(faqs && faqs.length > 0);
|
|
13
|
+
if (!hasHighlights && !hasFaqs)
|
|
14
|
+
return null;
|
|
15
|
+
const faqItems = (faqs ?? []).map((f, i) => ({
|
|
16
|
+
id: `faq-${i}`,
|
|
17
|
+
question: f.question,
|
|
18
|
+
answer: { text: f.answer, format: "plain" },
|
|
19
|
+
category: "general",
|
|
20
|
+
}));
|
|
21
|
+
return (_jsx(Section, { padding: "y-lg", border: "subtle", className: "border-t", children: _jsx(Container, { size: "xl", children: _jsxs(Stack, { gap: "xl", children: [hasHighlights && (_jsxs(Stack, { gap: "sm", children: [_jsx(Heading, { level: 2, size: "lg", weight: "semibold", children: "Why shop here" }), _jsx(Ul, { spacing: "comfortable", size: "sm", color: "primary", children: highlights.map((h, i) => (_jsxs(Li, { layout: "flex-start", gap: "2", children: [_jsx(Span, { className: "mt-0.5 flex-shrink-0 text-primary-500", children: "\u2022" }), h] }, i))) })] })), hasFaqs && (_jsxs(Stack, { gap: "sm", children: [_jsx(Heading, { level: 2, size: "lg", weight: "semibold", children: "Frequently Asked Questions" }), _jsx(FAQAccordion, { faqs: faqItems })] }))] }) }) }));
|
|
22
|
+
}
|
|
@@ -9,10 +9,6 @@ export interface CategoryProductsViewProps {
|
|
|
9
9
|
initialCategory?: CategoryItem;
|
|
10
10
|
/** Initial SSR child categories */
|
|
11
11
|
initialChildren?: CategoryItem[];
|
|
12
|
-
labels?: {
|
|
13
|
-
emptyTitle?: string;
|
|
14
|
-
emptyDescription?: string;
|
|
15
|
-
};
|
|
16
12
|
/** Optional UrlTable-compatible state source for shared filter/sort controls */
|
|
17
13
|
table?: UrlTable;
|
|
18
14
|
/** Filter/sort preset for admin/seller/public listing behavior */
|
|
@@ -46,4 +42,4 @@ export interface CategoryProductsViewProps {
|
|
|
46
42
|
isLoading?: boolean;
|
|
47
43
|
className?: string;
|
|
48
44
|
}
|
|
49
|
-
export declare function CategoryProductsView({
|
|
45
|
+
export declare function CategoryProductsView({ table, filterVariant, sortValue, onSortChange, renderBreadcrumbs, renderChildCategories, renderFilters, renderActiveFilters, renderSearch, renderSort, renderViewToggle, renderProducts, renderPagination, category, children: childCategories, total, isLoading, className, }: CategoryProductsViewProps): React.JSX.Element;
|
|
@@ -4,7 +4,7 @@ import React from "react";
|
|
|
4
4
|
import { Div, Heading, Input, SlottedListingView, Stack, Text } from "../../../ui";
|
|
5
5
|
import { CategoryFilters } from "./CategoryFilters";
|
|
6
6
|
import { CategorySortSelect } from "./CategorySortSelect";
|
|
7
|
-
export function CategoryProductsView({
|
|
7
|
+
export function CategoryProductsView({ table, filterVariant = "public", sortValue, onSortChange, renderBreadcrumbs, renderChildCategories, renderFilters, renderActiveFilters, renderSearch, renderSort, renderViewToggle, renderProducts, renderPagination, category = null, children: childCategories = [], total = 0, isLoading = false, className = "", }) {
|
|
8
8
|
const [search, setSearch] = React.useState("");
|
|
9
9
|
const [sort, setSort] = React.useState("-createdAt");
|
|
10
10
|
const [viewMode, setViewMode] = React.useState("grid");
|
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { Button, Div, Label, Row, Select, SideDrawer } from "../../../ui";
|
|
4
|
-
import { useState, useCallback } from "react";
|
|
4
|
+
import { useState, useCallback, useEffect } from "react";
|
|
5
|
+
import { z } from "zod";
|
|
5
6
|
import { useCategories, useCreateCategory } from "../hooks/useCategorySelector";
|
|
6
7
|
import { useMessage } from "../../../react";
|
|
7
8
|
import { flattenCategories } from "../types";
|
|
8
9
|
import { DrawerFormFooter } from "../../admin/components/DrawerFormFooter";
|
|
9
10
|
import { CategoryForm } from "./CategoryForm";
|
|
11
|
+
import { useFormShellState, FormShellContext, FormErrorSummary } from "../../../ui/forms";
|
|
12
|
+
// Form-input subset — `categoryFirestoreSchema` (categories schemas) models
|
|
13
|
+
// the full stored document (id, timestamps, computed metrics) which this
|
|
14
|
+
// create-draft never has.
|
|
15
|
+
const categoryDraftSchema = z.object({
|
|
16
|
+
name: z.string().min(1, "Name is required"),
|
|
17
|
+
slug: z.string().optional(),
|
|
18
|
+
});
|
|
10
19
|
const DEFAULT_LABELS = {
|
|
11
20
|
selectPlaceholder: "Select category…",
|
|
12
21
|
addCategory: "+ Add category",
|
|
@@ -21,6 +30,11 @@ function CreateCategoryContent({ allCategories, onSuccess, onCancel, labels, sta
|
|
|
21
30
|
parentId: null,
|
|
22
31
|
order: 0,
|
|
23
32
|
});
|
|
33
|
+
const { shellCtx, validate } = useFormShellState(categoryDraftSchema);
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
validate(draft);
|
|
36
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
37
|
+
}, [draft, validate]);
|
|
24
38
|
const { mutate, isPending: isLoading } = useCreateCategory({
|
|
25
39
|
onSuccess: (res) => {
|
|
26
40
|
showSuccess(labels.successCreated);
|
|
@@ -33,7 +47,7 @@ function CreateCategoryContent({ allCategories, onSuccess, onCancel, labels, sta
|
|
|
33
47
|
return;
|
|
34
48
|
mutate(draft);
|
|
35
49
|
}, [draft, mutate]);
|
|
36
|
-
return (_jsxs(Div, { className: stackClassName, children: [_jsx(CategoryForm, { category: draft, allCategories: allCategories, onChange: setDraft, labels: labels.form, stackClassName: stackClassName }), _jsx(DrawerFormFooter, { variant: "inline", isLoading: isLoading, onSubmit: handleSave, onCancel: onCancel, isSubmitDisabled: !draft.name })] }));
|
|
50
|
+
return (_jsx(FormShellContext.Provider, { value: shellCtx, children: _jsxs(Div, { className: stackClassName, children: [_jsx(CategoryForm, { category: draft, allCategories: allCategories, onChange: setDraft, labels: labels.form, stackClassName: stackClassName }), _jsx(FormErrorSummary, {}), _jsx(DrawerFormFooter, { variant: "inline", isLoading: isLoading, onSubmit: handleSave, onCancel: onCancel, isSubmitDisabled: !draft.name })] }) }));
|
|
37
51
|
}
|
|
38
52
|
export function CategorySelectorCreate({ value, onChange, disabled = false, label, labels = {}, stackClassName = "space-y-4", }) {
|
|
39
53
|
const [drawerOpen, setDrawerOpen] = useState(false);
|
|
@@ -125,8 +125,16 @@ export interface CategoryDocument extends BaseDocument {
|
|
|
125
125
|
brandCountry?: string;
|
|
126
126
|
/** Brand founding year — categoryType==="brand". */
|
|
127
127
|
brandFounded?: number;
|
|
128
|
-
/**
|
|
129
|
-
|
|
128
|
+
/**
|
|
129
|
+
* Short bullet points — "why shop this category/brand." Category and
|
|
130
|
+
* brand detail pages only; renders nothing when absent/empty.
|
|
131
|
+
*/
|
|
132
|
+
highlights?: string[];
|
|
133
|
+
/** A handful of Q&A pairs shown on the category/brand detail page. */
|
|
134
|
+
faqs?: {
|
|
135
|
+
question: string;
|
|
136
|
+
answer: string;
|
|
137
|
+
}[];
|
|
130
138
|
/**
|
|
131
139
|
* "special" — admin-curated bundle; writes reverse `partOfBundleIds`
|
|
132
140
|
* pointers on member products and shows the "Bundled" badge on cards.
|
|
@@ -135,6 +143,15 @@ export interface CategoryDocument extends BaseDocument {
|
|
|
135
143
|
* Does NOT update `partOfBundleIds`; used as a discovery surface only.
|
|
136
144
|
*/
|
|
137
145
|
bundleKind?: "special" | "brand";
|
|
146
|
+
/**
|
|
147
|
+
* Which brand this bundle belongs to, for the brand detail page's bundle
|
|
148
|
+
* tab — works for both `bundleKind:"special"` (static) and `"brand"`
|
|
149
|
+
* (dynamic) bundles. Distinct from `bundleQueryRule.filter.brandSlug`,
|
|
150
|
+
* which is a dynamic-rule query *filter* (only meaningful when
|
|
151
|
+
* `bundleQueryRule.type==="dynamic"`) — this field is the bundle's own
|
|
152
|
+
* brand tag regardless of how its members are resolved.
|
|
153
|
+
*/
|
|
154
|
+
brandSlug?: string;
|
|
138
155
|
/** Discounted bundle price in decimal rupees. */
|
|
139
156
|
bundlePrice?: number;
|
|
140
157
|
/** Rule resolving the bundle's member products — static list or live query. */
|
|
@@ -229,8 +229,19 @@ export declare const categoryFirestoreSchema: z.ZodObject<{
|
|
|
229
229
|
brandWebsite: z.ZodOptional<z.ZodString>;
|
|
230
230
|
brandCountry: z.ZodOptional<z.ZodString>;
|
|
231
231
|
brandFounded: z.ZodOptional<z.ZodNumber>;
|
|
232
|
-
|
|
232
|
+
highlights: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
233
|
+
faqs: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
234
|
+
question: z.ZodString;
|
|
235
|
+
answer: z.ZodString;
|
|
236
|
+
}, "strip", z.ZodTypeAny, {
|
|
237
|
+
question: string;
|
|
238
|
+
answer: string;
|
|
239
|
+
}, {
|
|
240
|
+
question: string;
|
|
241
|
+
answer: string;
|
|
242
|
+
}>, "many">>;
|
|
233
243
|
bundleKind: z.ZodOptional<z.ZodEnum<["special", "brand"]>>;
|
|
244
|
+
brandSlug: z.ZodOptional<z.ZodString>;
|
|
234
245
|
bundlePrice: z.ZodOptional<z.ZodEffects<z.ZodNumber, number, number>>;
|
|
235
246
|
bundleQueryRule: z.ZodOptional<z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
|
|
236
247
|
type: z.ZodLiteral<"static">;
|
|
@@ -419,7 +430,12 @@ export declare const categoryFirestoreSchema: z.ZodObject<{
|
|
|
419
430
|
name: string;
|
|
420
431
|
tier: number;
|
|
421
432
|
}[];
|
|
433
|
+
faqs?: {
|
|
434
|
+
question: string;
|
|
435
|
+
answer: string;
|
|
436
|
+
}[] | undefined;
|
|
422
437
|
description?: string | undefined;
|
|
438
|
+
brandSlug?: string | undefined;
|
|
423
439
|
showOnHomepage?: boolean | undefined;
|
|
424
440
|
categoryType?: "brand" | "category" | "sublisting" | "bundle" | undefined;
|
|
425
441
|
bundleKind?: "brand" | "special" | undefined;
|
|
@@ -442,7 +458,7 @@ export declare const categoryFirestoreSchema: z.ZodObject<{
|
|
|
442
458
|
brandWebsite?: string | undefined;
|
|
443
459
|
brandCountry?: string | undefined;
|
|
444
460
|
brandFounded?: number | undefined;
|
|
445
|
-
|
|
461
|
+
highlights?: string[] | undefined;
|
|
446
462
|
bundlePrice?: number | undefined;
|
|
447
463
|
bundleQueryRule?: {
|
|
448
464
|
type: "static";
|
|
@@ -513,7 +529,12 @@ export declare const categoryFirestoreSchema: z.ZodObject<{
|
|
|
513
529
|
name: string;
|
|
514
530
|
tier: number;
|
|
515
531
|
}[];
|
|
532
|
+
faqs?: {
|
|
533
|
+
question: string;
|
|
534
|
+
answer: string;
|
|
535
|
+
}[] | undefined;
|
|
516
536
|
description?: string | undefined;
|
|
537
|
+
brandSlug?: string | undefined;
|
|
517
538
|
showOnHomepage?: boolean | undefined;
|
|
518
539
|
categoryType?: "brand" | "category" | "sublisting" | "bundle" | undefined;
|
|
519
540
|
bundleKind?: "brand" | "special" | undefined;
|
|
@@ -536,7 +557,7 @@ export declare const categoryFirestoreSchema: z.ZodObject<{
|
|
|
536
557
|
brandWebsite?: string | undefined;
|
|
537
558
|
brandCountry?: string | undefined;
|
|
538
559
|
brandFounded?: number | undefined;
|
|
539
|
-
|
|
560
|
+
highlights?: string[] | undefined;
|
|
540
561
|
bundlePrice?: number | undefined;
|
|
541
562
|
bundleQueryRule?: {
|
|
542
563
|
type: "static";
|
|
@@ -76,8 +76,10 @@ export const categoryFirestoreSchema = z.object({
|
|
|
76
76
|
brandWebsite: z.string().optional(),
|
|
77
77
|
brandCountry: z.string().optional(),
|
|
78
78
|
brandFounded: z.number().int().optional(),
|
|
79
|
-
|
|
79
|
+
highlights: z.array(z.string()).optional(),
|
|
80
|
+
faqs: z.array(z.object({ question: z.string(), answer: z.string() })).optional(),
|
|
80
81
|
bundleKind: z.enum(["special", "brand"]).optional(),
|
|
82
|
+
brandSlug: z.string().optional(),
|
|
81
83
|
bundlePrice: rupeesSchema.optional(),
|
|
82
84
|
bundleQueryRule: bundleQueryRuleSchema.optional(),
|
|
83
85
|
bundleStockStatus: z.enum(["in_stock", "out_of_stock"]).optional(),
|
|
@@ -37,6 +37,13 @@ export interface CategoryItem {
|
|
|
37
37
|
brandCountry?: string;
|
|
38
38
|
/** Brand founded year — only set when categoryType==="brand". */
|
|
39
39
|
brandFounded?: number;
|
|
40
|
+
/** Short bullet points — "why shop this category/brand." */
|
|
41
|
+
highlights?: string[];
|
|
42
|
+
/** A handful of Q&A pairs shown on the category/brand detail page. */
|
|
43
|
+
faqs?: {
|
|
44
|
+
question: string;
|
|
45
|
+
answer: string;
|
|
46
|
+
}[];
|
|
40
47
|
name: string;
|
|
41
48
|
slug: string;
|
|
42
49
|
description?: string;
|
|
@@ -6,9 +6,23 @@
|
|
|
6
6
|
* purpose from `checkout-actions.ts`'s third-party shipping consent OTP —
|
|
7
7
|
* same crypto primitives, separate Firestore namespace and copy.
|
|
8
8
|
*/
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
export type CheckoutValueOtpChannel = "email" | "whatsapp";
|
|
10
|
+
/**
|
|
11
|
+
* Sends a checkout-value OTP either by email (default) or WhatsApp
|
|
12
|
+
* (opt-in — the buyer must explicitly choose it in the UI). WhatsApp phone
|
|
13
|
+
* is always resolved server-side from the selected shipping address —
|
|
14
|
+
* never trust a client-supplied phone number.
|
|
15
|
+
*
|
|
16
|
+
* If the chosen channel (and, for email, its skip-worthy fallback) is
|
|
17
|
+
* currently circuit-broken or unconfigured, the OTP gate is treated as
|
|
18
|
+
* satisfied (`skipped: true`) rather than blocking checkout — the caller
|
|
19
|
+
* must log this, since it is a security-relevant bypass even though
|
|
20
|
+
* user-authorized by the channel outage.
|
|
21
|
+
*/
|
|
22
|
+
export declare function sendCheckoutValueOtp(userId: string, userEmail: string, channel?: CheckoutValueOtpChannel, addressId?: string): Promise<{
|
|
23
|
+
maskedEmail?: string;
|
|
24
|
+
maskedPhone?: string;
|
|
25
|
+
skipped?: boolean;
|
|
12
26
|
}>;
|
|
13
27
|
/** Verifies the 6-digit checkout-value OTP and marks the Firestore record verified. */
|
|
14
28
|
export declare function verifyCheckoutValueOtp(userId: string, code: string): Promise<void>;
|
|
@@ -8,18 +8,94 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { timingSafeEqual } from "crypto";
|
|
10
10
|
import { ValidationError } from "../../../errors";
|
|
11
|
+
import { normalizeError } from "../../../errors/normalize";
|
|
11
12
|
import { serverLogger } from "../../../monitoring";
|
|
12
13
|
import { sendEmail } from "../../contact/email";
|
|
13
14
|
import { getAdminDb } from "../../../providers/db-firebase";
|
|
14
15
|
import { resolveDate } from "../../../utils";
|
|
15
|
-
import {
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
import { addressesRepository } from "../../addresses/repository/addresses.repository";
|
|
17
|
+
import { siteSettingsRepository } from "../../admin/repository/site-settings.repository";
|
|
18
|
+
import { sendWhatsAppBusinessMessage } from "../../whatsapp-bot/helpers/whatsapp";
|
|
19
|
+
import { isChannelHealthy, withChannelRetry } from "../../../_internal/server/notifications/channel-health";
|
|
20
|
+
import { CHECKOUT_VALUE_OTP_EXPIRY_MS, CHECKOUT_VALUE_OTP_MAX_ATTEMPTS, hashOtp, generateOtpCode, checkoutValueOtpRef, enforceCheckoutValueOtpRateLimit, buildCheckoutValueOtpMessage, } from "../../auth/checkout-value-otp";
|
|
21
|
+
function maskEmail(userEmail) {
|
|
22
|
+
const [local, domain] = userEmail.split("@");
|
|
23
|
+
return domain
|
|
24
|
+
? `${local.length <= 2 ? "*".repeat(local.length) : local[0] + "*".repeat(local.length - 2) + local[local.length - 1]}@${domain}`
|
|
25
|
+
: "***";
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Sends a checkout-value OTP either by email (default) or WhatsApp
|
|
29
|
+
* (opt-in — the buyer must explicitly choose it in the UI). WhatsApp phone
|
|
30
|
+
* is always resolved server-side from the selected shipping address —
|
|
31
|
+
* never trust a client-supplied phone number.
|
|
32
|
+
*
|
|
33
|
+
* If the chosen channel (and, for email, its skip-worthy fallback) is
|
|
34
|
+
* currently circuit-broken or unconfigured, the OTP gate is treated as
|
|
35
|
+
* satisfied (`skipped: true`) rather than blocking checkout — the caller
|
|
36
|
+
* must log this, since it is a security-relevant bypass even though
|
|
37
|
+
* user-authorized by the channel outage.
|
|
38
|
+
*/
|
|
39
|
+
export async function sendCheckoutValueOtp(userId, userEmail, channel = "email", addressId) {
|
|
18
40
|
const db = getAdminDb();
|
|
19
41
|
await enforceCheckoutValueOtpRateLimit(db, userId);
|
|
20
42
|
const code = generateOtpCode();
|
|
21
43
|
const codeHash = hashOtp(code);
|
|
22
44
|
const expiresAt = new Date(Date.now() + CHECKOUT_VALUE_OTP_EXPIRY_MS);
|
|
45
|
+
const siteName = process.env.NEXT_PUBLIC_SITE_NAME || "LetItRip";
|
|
46
|
+
if (channel === "whatsapp") {
|
|
47
|
+
if (!addressId) {
|
|
48
|
+
throw new ValidationError("Select a delivery address before sending the code via WhatsApp.");
|
|
49
|
+
}
|
|
50
|
+
const settings = await siteSettingsRepository.getSingleton().catch(() => null);
|
|
51
|
+
const creds = settings?.credentials ?? {};
|
|
52
|
+
const whatsappOtpEnabled = settings?.notificationChannels?.whatsapp?.otpEnabled === true;
|
|
53
|
+
const phoneNumberId = creds.whatsappPhoneNumberId?.trim() ?? "";
|
|
54
|
+
const accessToken = creds.whatsappCloudApiToken?.trim() ?? "";
|
|
55
|
+
const credsReady = phoneNumberId && accessToken && !phoneNumberId.includes("PLACEHOLDER") && !accessToken.includes("PLACEHOLDER");
|
|
56
|
+
if (!whatsappOtpEnabled || !credsReady) {
|
|
57
|
+
throw new ValidationError("WhatsApp verification isn't available right now — send the code by email instead.");
|
|
58
|
+
}
|
|
59
|
+
const address = await addressesRepository.findById(addressId);
|
|
60
|
+
const phone = address?.phone?.trim();
|
|
61
|
+
if (!phone) {
|
|
62
|
+
throw new ValidationError("The selected address has no phone number on file — send the code by email instead.");
|
|
63
|
+
}
|
|
64
|
+
if (!(await isChannelHealthy("whatsapp"))) {
|
|
65
|
+
serverLogger.warn("sendCheckoutValueOtp: WhatsApp channel unhealthy — skipping OTP gate", { uid: userId, channel });
|
|
66
|
+
return { skipped: true };
|
|
67
|
+
}
|
|
68
|
+
await checkoutValueOtpRef(db, userId).set({
|
|
69
|
+
codeHash,
|
|
70
|
+
expiresAt,
|
|
71
|
+
attempts: 0,
|
|
72
|
+
verified: false,
|
|
73
|
+
createdAt: new Date(),
|
|
74
|
+
});
|
|
75
|
+
const message = buildCheckoutValueOtpMessage(code, siteName);
|
|
76
|
+
try {
|
|
77
|
+
const sent = await withChannelRetry("whatsapp", async () => {
|
|
78
|
+
const ok = await sendWhatsAppBusinessMessage({ toPhone: phone, message, phoneNumberId, accessToken });
|
|
79
|
+
if (!ok)
|
|
80
|
+
throw new Error("WhatsApp Cloud API returned a non-OK response");
|
|
81
|
+
return ok;
|
|
82
|
+
});
|
|
83
|
+
if (!sent)
|
|
84
|
+
throw new Error("WhatsApp send failed");
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
void normalizeError(err);
|
|
88
|
+
serverLogger.error("Failed to send checkout value OTP via WhatsApp", { uid: userId, err });
|
|
89
|
+
throw new ValidationError("We couldn't send the verification code via WhatsApp — please try again, or send it by email instead.");
|
|
90
|
+
}
|
|
91
|
+
const maskedPhone = phone.length > 4 ? `${"*".repeat(phone.length - 4)}${phone.slice(-4)}` : "***";
|
|
92
|
+
serverLogger.info(`Checkout value OTP sent via WhatsApp: uid=${userId}`);
|
|
93
|
+
return { maskedPhone };
|
|
94
|
+
}
|
|
95
|
+
if (!(await isChannelHealthy("email"))) {
|
|
96
|
+
serverLogger.warn("sendCheckoutValueOtp: email channel unhealthy — skipping OTP gate", { uid: userId, channel });
|
|
97
|
+
return { skipped: true };
|
|
98
|
+
}
|
|
23
99
|
await checkoutValueOtpRef(db, userId).set({
|
|
24
100
|
codeHash,
|
|
25
101
|
expiresAt,
|
|
@@ -27,24 +103,27 @@ export async function sendCheckoutValueOtp(userId, userEmail) {
|
|
|
27
103
|
verified: false,
|
|
28
104
|
createdAt: new Date(),
|
|
29
105
|
});
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
106
|
+
const maskedEmail = maskEmail(userEmail);
|
|
107
|
+
try {
|
|
108
|
+
await withChannelRetry("email", async () => {
|
|
109
|
+
const { error } = await sendEmail({
|
|
110
|
+
to: userEmail,
|
|
111
|
+
subject: `${siteName}: Verify your order`,
|
|
112
|
+
html: `
|
|
113
|
+
<div style="font-family:sans-serif;max-width:480px;margin:0 auto;padding:24px">
|
|
114
|
+
<h2 style="margin-bottom:8px">Verify Your Order</h2>
|
|
115
|
+
<p style="color:#555">Your cart total requires verification before checkout. Enter this code to continue:</p>
|
|
116
|
+
<div style="font-size:36px;font-weight:bold;letter-spacing:8px;text-align:center;margin:24px 0;padding:16px;background:#f3f4f6;border-radius:8px">${code}</div>
|
|
117
|
+
<p style="color:#888;font-size:12px">This code expires in 10 minutes. If you did not request this, please ignore this email.</p>
|
|
118
|
+
</div>
|
|
119
|
+
`,
|
|
120
|
+
});
|
|
121
|
+
if (error)
|
|
122
|
+
throw new Error(String(error));
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
catch (err) {
|
|
126
|
+
void normalizeError(err);
|
|
48
127
|
// Root-caused 2026-08-20: this used to be fire-and-forget — the
|
|
49
128
|
// Firestore OTP doc was written and the caller always got back a
|
|
50
129
|
// success response ("code sent") regardless of whether the email
|
|
@@ -52,7 +131,7 @@ export async function sendCheckoutValueOtp(userId, userEmail) {
|
|
|
52
131
|
// unverified sending domain, outage) silently stranded the buyer
|
|
53
132
|
// with a checkout they could never complete, since the OTP gate
|
|
54
133
|
// gave no indication anything had gone wrong.
|
|
55
|
-
serverLogger.error("Failed to send checkout value OTP email", { uid: userId,
|
|
134
|
+
serverLogger.error("Failed to send checkout value OTP email", { uid: userId, err });
|
|
56
135
|
throw new ValidationError("We couldn't send the verification code — please try again in a moment, or contact support if this keeps happening.");
|
|
57
136
|
}
|
|
58
137
|
serverLogger.info(`Checkout value OTP sent: uid=${userId}`);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { startClassifiedConversationAction } from "../../../_internal/server/features/classified/actions";
|
|
3
|
+
export interface ClassifiedContactSellerPanelProps {
|
|
4
|
+
productId: string;
|
|
5
|
+
onContactSeller: typeof startClassifiedConversationAction;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* The only purchase-path CTA on a classified listing — no cart, no checkout.
|
|
9
|
+
* Extracted from the old ClassifiedDetailView so the richer, full-parity
|
|
10
|
+
* ClassifiedDetailPageView can slot this into a `renderBuyBar` prop.
|
|
11
|
+
*/
|
|
12
|
+
export declare function ClassifiedContactSellerPanel({ productId, onContactSeller }: ClassifiedContactSellerPanelProps): React.JSX.Element;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { normalizeError } from "../../../errors/normalize";
|
|
4
|
+
import { useState } from "react";
|
|
5
|
+
import Link from "next/link";
|
|
6
|
+
import { Button, Div, Stack, Text } from "../../../ui";
|
|
7
|
+
import { ROUTES } from "../../../next/routing/route-map";
|
|
8
|
+
const __P = {
|
|
9
|
+
p4: "p-[var(--appkit-space-4)]",
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* The only purchase-path CTA on a classified listing — no cart, no checkout.
|
|
13
|
+
* Extracted from the old ClassifiedDetailView so the richer, full-parity
|
|
14
|
+
* ClassifiedDetailPageView can slot this into a `renderBuyBar` prop.
|
|
15
|
+
*/
|
|
16
|
+
export function ClassifiedContactSellerPanel({ productId, onContactSeller }) {
|
|
17
|
+
const [pending, setPending] = useState(false);
|
|
18
|
+
const [conversation, setConversation] = useState(null);
|
|
19
|
+
const [error, setError] = useState(null);
|
|
20
|
+
async function handleContactSeller() {
|
|
21
|
+
setPending(true);
|
|
22
|
+
setError(null);
|
|
23
|
+
try {
|
|
24
|
+
const result = await onContactSeller({ productId });
|
|
25
|
+
if (result && typeof result === "object" && "ok" in result) {
|
|
26
|
+
if (result.ok) {
|
|
27
|
+
setConversation(result.data);
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
setError(result.error);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
setConversation(result);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch (e) {
|
|
38
|
+
void normalizeError(e);
|
|
39
|
+
setError(e instanceof Error ? e.message : "Something went wrong");
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
setPending(false);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (conversation) {
|
|
46
|
+
return (_jsxs(Div, { className: `border border-border bg-muted/40 ${__P.p4}`, rounded: "lg", children: [_jsx(Text, { className: "mb-2", weight: "medium", children: "Conversation started!" }), _jsx(Link, { href: ROUTES.USER.MESSAGES, className: "text-primary underline underline-offset-2", children: "Go to your messages \u2192" })] }));
|
|
47
|
+
}
|
|
48
|
+
return (_jsxs(Stack, { gap: "sm", children: [error && (_jsx(Text, { className: "text-destructive", size: "sm", children: error })), _jsx(Button, { type: "button", variant: "primary", size: "lg", isLoading: pending, disabled: pending, onClick: handleContactSeller, className: "w-full", children: "Contact Seller" })] }));
|
|
49
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ProductDocument } from "../../products/schemas/firestore";
|
|
2
|
+
export interface ClassifiedDetailPageViewProps {
|
|
3
|
+
slug: string;
|
|
4
|
+
/** Pre-fetched product document — dedupes with generateMetadata() via React.cache(). */
|
|
5
|
+
initialProduct?: ProductDocument | null;
|
|
6
|
+
}
|
|
7
|
+
export declare function ClassifiedDetailPageView({ slug, initialProduct }: ClassifiedDetailPageViewProps): Promise<import("react").JSX.Element>;
|