@mohasinac/appkit 2.7.27 → 2.7.30
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.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 +310 -8
- 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 +2 -0
- package/dist/client.js +1 -0
- package/dist/configs/next.js +4 -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 +44 -14
- 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/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 +2 -2
- 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/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/AdminProductsView.js +18 -12
- package/dist/features/admin/components/AdminReviewsView.js +33 -24
- 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/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/AuctionDetailPageView.js +13 -19
- 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 +0 -1
- package/dist/features/auctions/components/index.js +0 -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/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.js +2 -2
- 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.js +2 -2
- 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 +8 -0
- package/dist/features/classified/components/ClassifiedFilters.js +19 -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 +8 -0
- package/dist/features/digital-codes/components/DigitalCodeFilters.js +16 -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/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/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 +8 -0
- package/dist/features/live/components/LiveItemFilters.js +23 -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 +27 -20
- 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 +2 -2
- package/dist/features/products/components/AuctionsIndexListing.js +15 -51
- package/dist/features/products/components/BidHistory.js +2 -2
- 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.js +8 -6
- package/dist/features/products/components/ProductForm.js +12 -10
- package/dist/features/products/components/ProductGalleryClient.js +2 -1
- package/dist/features/products/components/ProductGrid.js +11 -14
- package/dist/features/products/components/ProductsIndexListing.js +16 -52
- package/dist/features/products/components/ProductsIndexPageView.js +2 -2
- 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/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 +2 -2
- 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 +21 -3
- package/dist/index.js +1737 -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 +27 -0
- package/dist/next/routing/route-map.js +10 -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/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
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import React from "react";
|
|
4
4
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
5
|
-
import { Button, ConfirmDeleteModal, Form, FormActions, Select, SideDrawer, Toggle, useToast
|
|
5
|
+
import { Button, ConfirmDeleteModal, Form, FormActions, Heading, Select, SideDrawer, Text, Toggle, useToast } from "../../../ui";
|
|
6
6
|
import { apiClient } from "../../../http";
|
|
7
7
|
import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
8
8
|
const ROLE_OPTIONS = [
|
|
@@ -33,6 +33,12 @@ function formatExpiry(expiresAt) {
|
|
|
33
33
|
return `Expired ${d.toLocaleDateString()}`;
|
|
34
34
|
return `Until ${d.toLocaleDateString()}`;
|
|
35
35
|
}
|
|
36
|
+
function HardBanPanel({ userId, isHardBanned, currentHardBanReason, showHardBanForm, setShowHardBanForm, hardBanReasonInput, setHardBanReasonInput, hardBanPending, unbanPending, onHardBan, onUnban, }) {
|
|
37
|
+
return (_jsxs("div", { className: "mb-4 rounded-lg border border-zinc-200 bg-zinc-50 p-3 dark:border-zinc-700 dark:bg-zinc-900/40", children: [_jsxs("div", { className: "mb-2 flex items-center justify-between", children: [_jsx("span", { className: "text-sm font-medium text-zinc-700 dark:text-zinc-300", children: "Hard ban" }), isHardBanned ? (_jsx("span", { className: "rounded-full bg-red-100 px-2 py-0.5 text-xs font-semibold text-red-700 dark:bg-red-900/40 dark:text-red-300", children: "Banned" })) : (_jsx("span", { className: "rounded-full bg-green-100 px-2 py-0.5 text-xs font-semibold text-green-700 dark:bg-green-900/40 dark:text-green-300", children: "Active" }))] }), isHardBanned ? (_jsxs("div", { className: "space-y-2", children: [currentHardBanReason && _jsxs(Text, { className: "text-xs text-zinc-600 dark:text-zinc-400", children: ["Reason: ", currentHardBanReason] }), _jsx(Button, { type: "button", variant: "secondary", size: "sm", isLoading: unbanPending, disabled: unbanPending, onClick: onUnban, children: "Lift hard ban" })] })) : showHardBanForm ? (_jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("label", { className: "text-xs font-medium text-zinc-700 dark:text-zinc-300", children: "Ban reason (required)" }), _jsx("textarea", { value: hardBanReasonInput, onChange: (e) => setHardBanReasonInput(e.target.value), rows: 2, placeholder: "e.g. Repeated fraud, scam activity\u2026", className: "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-red-500" })] }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { type: "button", variant: "danger", size: "sm", isLoading: hardBanPending, disabled: !hardBanReasonInput.trim() || hardBanPending, onClick: () => onHardBan(hardBanReasonInput.trim()), children: "Confirm hard ban" }), _jsx(Button, { type: "button", variant: "secondary", size: "sm", onClick: () => { setShowHardBanForm(false); setHardBanReasonInput(""); }, children: "Cancel" })] })] })) : (_jsx(Button, { type: "button", variant: "danger", size: "sm", disabled: !userId, onClick: () => setShowHardBanForm(true), children: "Impose hard ban" }))] }));
|
|
38
|
+
}
|
|
39
|
+
function SoftBanPanel({ userId, softBans, showAddSoftBan, setShowAddSoftBan, softBanAction, setSoftBanAction, softBanReason, setSoftBanReason, softBanExpiry, setSoftBanExpiry, softBanPending, liftPending, onAddSoftBan, onLiftSoftBan, }) {
|
|
40
|
+
return (_jsxs("div", { className: "rounded-lg border border-zinc-200 bg-zinc-50 p-3 dark:border-zinc-700 dark:bg-zinc-900/40", children: [_jsxs("div", { className: "mb-2 flex items-center justify-between", children: [_jsxs("span", { className: "text-sm font-medium text-zinc-700 dark:text-zinc-300", children: ["Soft bans", softBans.length > 0 ? ` (${softBans.length})` : ""] }), !showAddSoftBan && (_jsx(Button, { type: "button", variant: "secondary", size: "sm", disabled: !userId, onClick: () => setShowAddSoftBan(true), children: "Add soft ban" }))] }), softBans.length > 0 && (_jsx("ul", { className: "mb-3 space-y-2", children: softBans.map((ban) => (_jsxs("li", { className: "flex items-start justify-between gap-2 rounded-md border border-zinc-200 bg-white px-3 py-2 text-xs dark:border-zinc-700 dark:bg-zinc-800", children: [_jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: "font-semibold text-zinc-800 dark:text-zinc-200", children: formatBanAction(ban.action) }), _jsx("div", { className: "text-zinc-500 dark:text-zinc-400", children: ban.reason }), _jsx("div", { className: "text-zinc-400 dark:text-zinc-500", children: formatExpiry(ban.expiresAt) })] }), _jsx(Button, { type: "button", variant: "secondary", size: "sm", isLoading: liftPending, disabled: liftPending, onClick: () => onLiftSoftBan(ban.action), children: "Lift" })] }, ban.action))) })), showAddSoftBan && (_jsxs("div", { className: "space-y-2", children: [_jsx(Select, { label: "Action to restrict", options: BANNED_ACTION_OPTIONS, value: softBanAction, onValueChange: setSoftBanAction }), _jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("label", { className: "text-xs font-medium text-zinc-700 dark:text-zinc-300", children: "Reason (required)" }), _jsx("textarea", { value: softBanReason, onChange: (e) => setSoftBanReason(e.target.value), rows: 2, placeholder: "e.g. Suspicious bid activity\u2026", className: "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" })] }), _jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("label", { className: "text-xs font-medium text-zinc-700 dark:text-zinc-300", children: "Expires at (optional \u2014 leave blank for permanent)" }), _jsx("input", { type: "datetime-local", value: softBanExpiry, onChange: (e) => setSoftBanExpiry(e.target.value), className: "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 focus:outline-none focus:ring-2 focus:ring-primary-500" })] }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { type: "button", variant: "primary", size: "sm", isLoading: softBanPending, disabled: !softBanReason.trim() || softBanPending, onClick: () => onAddSoftBan({ action: softBanAction, reason: softBanReason.trim(), ...(softBanExpiry ? { expiresAt: new Date(softBanExpiry).toISOString() } : {}) }), children: "Apply soft ban" }), _jsx(Button, { type: "button", variant: "secondary", size: "sm", onClick: () => { setShowAddSoftBan(false); setSoftBanReason(""); setSoftBanExpiry(""); }, children: "Cancel" })] })] })), softBans.length === 0 && !showAddSoftBan && (_jsx(Text, { className: "text-xs text-zinc-400 dark:text-zinc-500", children: "No active soft bans." }))] }));
|
|
41
|
+
}
|
|
36
42
|
// --- Component ---------------------------------------------------------------
|
|
37
43
|
export function AdminUserEditorView({ open, onClose, userId, displayName, currentRole, currentIsDisabled, currentEmailVerified, ownedStoreId, ownedStoreName, currentSoftBans, currentIsHardBanned, currentHardBanReason, }) {
|
|
38
44
|
const queryClient = useQueryClient();
|
|
@@ -157,18 +163,5 @@ export function AdminUserEditorView({ open, onClose, userId, displayName, curren
|
|
|
157
163
|
return (_jsxs(_Fragment, { children: [_jsx(SideDrawer, { isOpen: open, onClose: onClose, title: displayName ? `Manage: ${displayName}` : "Manage User", children: _jsxs(Form, { onSubmit: (e) => {
|
|
158
164
|
e.preventDefault();
|
|
159
165
|
saveMutation.mutate();
|
|
160
|
-
}, className: "space-y-4 p-4", children: [userId && (_jsx("div", { className: "rounded-lg border border-zinc-200 bg-zinc-50 px-3 py-2 text-xs dark:border-zinc-700 dark:bg-zinc-900/40", children: _jsxs("div", { className: "flex flex-col gap-1 text-zinc-700 dark:text-zinc-300", children: [_jsxs(
|
|
161
|
-
setShowHardBanForm(false);
|
|
162
|
-
setHardBanReasonInput("");
|
|
163
|
-
}, children: "Cancel" })] })] })) : (_jsx(Button, { type: "button", variant: "danger", size: "sm", disabled: !userId, onClick: () => setShowHardBanForm(true), children: "Impose hard ban" }))] }), _jsxs("div", { className: "rounded-lg border border-zinc-200 bg-zinc-50 p-3 dark:border-zinc-700 dark:bg-zinc-900/40", children: [_jsxs("div", { className: "mb-2 flex items-center justify-between", children: [_jsxs("span", { className: "text-sm font-medium text-zinc-700 dark:text-zinc-300", children: ["Soft bans", softBans.length > 0 ? ` (${softBans.length})` : ""] }), !showAddSoftBan && (_jsx(Button, { type: "button", variant: "secondary", size: "sm", disabled: !userId, onClick: () => setShowAddSoftBan(true), children: "Add soft ban" }))] }), softBans.length > 0 && (_jsx("ul", { className: "mb-3 space-y-2", children: softBans.map((ban) => (_jsxs("li", { className: "flex items-start justify-between gap-2 rounded-md border border-zinc-200 bg-white px-3 py-2 text-xs dark:border-zinc-700 dark:bg-zinc-800", children: [_jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: "font-semibold text-zinc-800 dark:text-zinc-200", children: formatBanAction(ban.action) }), _jsx("div", { className: "text-zinc-500 dark:text-zinc-400", children: ban.reason }), _jsx("div", { className: "text-zinc-400 dark:text-zinc-500", children: formatExpiry(ban.expiresAt) })] }), _jsx(Button, { type: "button", variant: "secondary", size: "sm", isLoading: liftSoftBanMutation.isPending, disabled: liftSoftBanMutation.isPending, onClick: () => liftSoftBanMutation.mutate(ban.action), children: "Lift" })] }, ban.action))) })), showAddSoftBan && (_jsxs("div", { className: "space-y-2", children: [_jsx(Select, { label: "Action to restrict", options: BANNED_ACTION_OPTIONS, value: softBanAction, onValueChange: setSoftBanAction }), _jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("label", { className: "text-xs font-medium text-zinc-700 dark:text-zinc-300", children: "Reason (required)" }), _jsx("textarea", { value: softBanReason, onChange: (e) => setSoftBanReason(e.target.value), rows: 2, placeholder: "e.g. Suspicious bid activity\u2026", className: "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" })] }), _jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("label", { className: "text-xs font-medium text-zinc-700 dark:text-zinc-300", children: "Expires at (optional \u2014 leave blank for permanent)" }), _jsx("input", { type: "datetime-local", value: softBanExpiry, onChange: (e) => setSoftBanExpiry(e.target.value), className: "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 focus:outline-none focus:ring-2 focus:ring-primary-500" })] }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { type: "button", variant: "primary", size: "sm", isLoading: softBanMutation.isPending, disabled: !softBanReason.trim() || softBanMutation.isPending, onClick: () => softBanMutation.mutate({
|
|
164
|
-
action: softBanAction,
|
|
165
|
-
reason: softBanReason.trim(),
|
|
166
|
-
...(softBanExpiry
|
|
167
|
-
? { expiresAt: new Date(softBanExpiry).toISOString() }
|
|
168
|
-
: {}),
|
|
169
|
-
}), children: "Apply soft ban" }), _jsx(Button, { type: "button", variant: "secondary", size: "sm", onClick: () => {
|
|
170
|
-
setShowAddSoftBan(false);
|
|
171
|
-
setSoftBanReason("");
|
|
172
|
-
setSoftBanExpiry("");
|
|
173
|
-
}, children: "Cancel" })] })] })), softBans.length === 0 && !showAddSoftBan && (_jsx("p", { className: "text-xs text-zinc-400 dark:text-zinc-500", children: "No active soft bans." }))] })] })] }) }), _jsx(ConfirmDeleteModal, { isOpen: deleteOpen, onClose: () => setDeleteOpen(false), onConfirm: () => deleteMutation.mutate(), isDeleting: deleteMutation.isPending, title: `Delete ${displayName ?? "user"}?`, message: "This action cannot be undone. The user's account and all associated data will be permanently removed.", confirmText: "Delete user", variant: "danger" })] }));
|
|
166
|
+
}, className: "space-y-4 p-4", children: [userId && (_jsx("div", { className: "rounded-lg border border-zinc-200 bg-zinc-50 px-3 py-2 text-xs dark:border-zinc-700 dark:bg-zinc-900/40", children: _jsxs("div", { className: "flex flex-col gap-1 text-zinc-700 dark:text-zinc-300", children: [_jsxs(_Fragment, { children: [_jsx("span", { className: "font-semibold", children: "Owner ID (Firebase UID):" }), " ", _jsx("code", { className: "select-all font-mono", children: userId })] }), ownedStoreId && (_jsxs(_Fragment, { children: [_jsx("span", { className: "font-semibold", children: "Owns store:" }), " ", _jsx("code", { className: "select-all font-mono", children: ownedStoreId }), ownedStoreName ? ` — ${ownedStoreName}` : ""] }))] }) })), _jsx(Select, { label: "Role", options: ROLE_OPTIONS, value: role, onValueChange: setRole }), _jsx(Toggle, { label: "Email verified", checked: emailVerified, onChange: setEmailVerified }), _jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("label", { className: "text-sm font-medium text-zinc-700 dark:text-zinc-300", children: "Admin notes (optional)" }), _jsx("textarea", { value: adminNotes, onChange: (e) => setAdminNotes(e.target.value), rows: 3, placeholder: "Internal notes about this user\u2026", className: "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" })] }), _jsxs(FormActions, { align: "right", children: [_jsx(Button, { type: "button", variant: "danger", onClick: () => setDeleteOpen(true), disabled: !userId, children: "Delete user" }), _jsx(Button, { type: "button", variant: "secondary", onClick: onClose, children: "Cancel" }), _jsx(Button, { type: "submit", isLoading: saveMutation.isPending, disabled: !userId || saveMutation.isPending, children: "Save changes" })] }), _jsxs("div", { className: "border-t border-zinc-200 pt-4 dark:border-zinc-700", children: [_jsx(Heading, { level: 3, className: "mb-3 text-sm font-semibold text-zinc-700 dark:text-zinc-300", children: "Moderation" }), _jsx(HardBanPanel, { userId: userId, isHardBanned: isHardBanned, currentHardBanReason: currentHardBanReason, showHardBanForm: showHardBanForm, setShowHardBanForm: setShowHardBanForm, hardBanReasonInput: hardBanReasonInput, setHardBanReasonInput: setHardBanReasonInput, hardBanPending: hardBanMutation.isPending, unbanPending: unbanMutation.isPending, onHardBan: (reason) => hardBanMutation.mutate(reason), onUnban: () => unbanMutation.mutate() }), _jsx(SoftBanPanel, { userId: userId, softBans: softBans, showAddSoftBan: showAddSoftBan, setShowAddSoftBan: setShowAddSoftBan, softBanAction: softBanAction, setSoftBanAction: setSoftBanAction, softBanReason: softBanReason, setSoftBanReason: setSoftBanReason, softBanExpiry: softBanExpiry, setSoftBanExpiry: setSoftBanExpiry, softBanPending: softBanMutation.isPending, liftPending: liftSoftBanMutation.isPending, onAddSoftBan: (payload) => softBanMutation.mutate(payload), onLiftSoftBan: (action) => liftSoftBanMutation.mutate(action) })] })] }) }), _jsx(ConfirmDeleteModal, { isOpen: deleteOpen, onClose: () => setDeleteOpen(false), onConfirm: () => deleteMutation.mutate(), isDeleting: deleteMutation.isPending, title: `Delete ${displayName ?? "user"}?`, message: "This action cannot be undone. The user's account and all associated data will be permanently removed.", confirmText: "Delete user", variant: "danger" })] }));
|
|
174
167
|
}
|
|
@@ -21,6 +21,11 @@ const SORT_OPTIONS = [
|
|
|
21
21
|
];
|
|
22
22
|
const STATUS_OPTIONS = ADMIN_USER_STATUS_TABS;
|
|
23
23
|
const ROLE_OPTIONS = ADMIN_USER_ROLE_TABS;
|
|
24
|
+
function UsersFilterDrawer({ filterOpen, setFilterOpen, activeFilterCount, clearFilters, pendingFilters, setPendingFilters, applyFilters, }) {
|
|
25
|
+
if (!filterOpen)
|
|
26
|
+
return null;
|
|
27
|
+
return (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 z-40 bg-black/40", "aria-hidden": "true", onClick: () => setFilterOpen(false) }), _jsxs("div", { className: "fixed inset-y-0 left-0 z-50 flex w-80 flex-col bg-white dark:bg-slate-900 shadow-2xl", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: [_jsx("span", { className: "text-base font-semibold text-zinc-900 dark:text-zinc-100", children: "Filters" }), _jsxs("div", { className: "flex items-center gap-2", children: [activeFilterCount > 0 && (_jsx("button", { type: "button", onClick: clearFilters, className: "text-xs text-zinc-500 hover:text-rose-500 dark:text-zinc-400 transition-colors", children: "Clear all" })), _jsx("button", { type: "button", onClick: () => setFilterOpen(false), "aria-label": "Close", className: "rounded-lg p-1.5 text-zinc-500 hover:bg-zinc-100 dark:hover:bg-slate-800 transition-colors", children: _jsx(X, { className: "h-5 w-5" }) })] })] }), _jsxs("div", { className: "flex-1 overflow-y-auto px-4 py-4 space-y-5", children: [_jsx(FilterChipGroup, { label: "Status", tabs: STATUS_OPTIONS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) }), _jsx(FilterChipGroup, { label: "Role", tabs: ROLE_OPTIONS, value: pendingFilters.role ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, role: id })) })] }), _jsx("div", { className: "border-t border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: _jsxs("button", { type: "button", onClick: applyFilters, className: "w-full rounded-lg bg-primary py-2.5 text-sm font-semibold text-white hover:bg-primary-600 transition-colors active:scale-[0.98]", children: ["Apply Filters", activeFilterCount > 0 ? ` (${activeFilterCount})` : ""] }) })] })] }));
|
|
28
|
+
}
|
|
24
29
|
export function AdminUsersView({ children, ...props }) {
|
|
25
30
|
const hasChildren = React.Children.count(children) > 0;
|
|
26
31
|
const [view, setView] = useState("table");
|
|
@@ -115,7 +120,7 @@ export function AdminUsersView({ children, ...props }) {
|
|
|
115
120
|
] }), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 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: errorMessage })), view === "table" ? (_jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No users found", selectedIds: selection.selectedIdSet, onToggleSelect: selection.toggle, onToggleSelectAll: (next) => next ? selection.setSelectedIds(rows.map(r => r.id)) : selection.clearSelection(), renderRowActions: (row) => (_jsx(RowActionMenu, { actions: [{
|
|
116
121
|
label: "Manage",
|
|
117
122
|
onClick: () => { setSelectedRow(row); setDrawerOpen(true); },
|
|
118
|
-
}] })) })) : (_jsx(AdminViewCards, { rows: rows, view: view, isLoading: isLoading, emptyLabel: "No users found", onRowClick: (row) => { setSelectedRow(row); setDrawerOpen(true); }, selectedIdSet: selection.selectedIdSet, onToggleSelect: selection.toggle }))] }),
|
|
123
|
+
}] })) })) : (_jsx(AdminViewCards, { rows: rows, view: view, isLoading: isLoading, emptyLabel: "No users found", onRowClick: (row) => { setSelectedRow(row); setDrawerOpen(true); }, selectedIdSet: selection.selectedIdSet, onToggleSelect: selection.toggle }))] }), _jsx(UsersFilterDrawer, { filterOpen: filterOpen, setFilterOpen: setFilterOpen, activeFilterCount: activeFilterCount, clearFilters: clearFilters, pendingFilters: pendingFilters, setPendingFilters: setPendingFilters, applyFilters: applyFilters })] }), _jsx(AdminUserEditorView, { open: drawerOpen, onClose: () => setDrawerOpen(false), userId: selectedRow?.id, displayName: selectedRow?.primary, currentRole: toStringValue(selectedRow?._raw?.role, "user"), currentEmailVerified: Boolean(selectedRow?._raw?.emailVerified), ownedStoreId: toStringValue(selectedRow?._raw?.storeId, "") || undefined, ownedStoreName: toStringValue(selectedRow?._raw?.storeName, "") || undefined, currentIsHardBanned: Boolean((selectedRow?._raw?.isDisabled ?? selectedRow?._raw?.disabled) &&
|
|
119
124
|
selectedRow?._raw?.hardBanReason), currentHardBanReason: toStringValue(selectedRow?._raw?.hardBanReason, "") || undefined, currentSoftBans: Array.isArray(selectedRow?._raw?.softBans)
|
|
120
125
|
? selectedRow._raw.softBans.map((b) => ({
|
|
121
126
|
action: toStringValue(b.action, ""),
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Text } from "../../../ui";
|
|
3
4
|
const FLAG_BADGES = [
|
|
4
5
|
{ key: "featured", label: "Featured", color: "bg-amber-100 text-amber-800 dark:bg-amber-900/30 dark:text-amber-300" },
|
|
5
6
|
{ key: "isPromoted", label: "Promoted", color: "bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-300" },
|
|
@@ -26,14 +27,14 @@ function AdminCardItem({ row, view, selected, onToggleSelect, onRowClick, }) {
|
|
|
26
27
|
return (_jsxs("div", { className: [
|
|
27
28
|
"flex items-center gap-3 px-4 py-3 cursor-pointer transition-colors hover:bg-zinc-50 dark:hover:bg-slate-800/50",
|
|
28
29
|
selected ? "bg-primary-50/40 dark:bg-primary-900/10" : "",
|
|
29
|
-
].filter(Boolean).join(" "), onClick: handleClick, role: onRowClick ? "button" : undefined, children: [onToggleSelect && (_jsx("div", { "data-checkbox": true, className: "shrink-0", onClick: (e) => { e.stopPropagation(); onToggleSelect(row.id); }, children: _jsx("input", { type: "checkbox", checked: selected, onChange: () => onToggleSelect(row.id), className: "h-4 w-4 rounded border-zinc-300 text-primary accent-primary cursor-pointer", "aria-label": `Select ${row.primary}` }) })), _jsxs("div", { className: "flex-1 min-w-0 space-y-0.5", children: [_jsx(
|
|
30
|
+
].filter(Boolean).join(" "), onClick: handleClick, role: onRowClick ? "button" : undefined, children: [onToggleSelect && (_jsx("div", { "data-checkbox": true, className: "shrink-0", onClick: (e) => { e.stopPropagation(); onToggleSelect(row.id); }, children: _jsx("input", { type: "checkbox", checked: selected, onChange: () => onToggleSelect(row.id), className: "h-4 w-4 rounded border-zinc-300 text-primary accent-primary cursor-pointer", "aria-label": `Select ${row.primary}` }) })), _jsxs("div", { className: "flex-1 min-w-0 space-y-0.5", children: [_jsx(Text, { className: "text-sm font-semibold text-zinc-900 dark:text-zinc-100 truncate", children: row.primary }), _jsx(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400 truncate", children: row.secondary })] }), flags.length > 0 && (_jsx("div", { className: "hidden sm:flex items-center gap-1 shrink-0", children: flags.map(({ key, label, color }) => (_jsx("span", { className: `inline-flex rounded-full px-1.5 py-0.5 text-[10px] font-medium ${color}`, children: label }, key))) })), _jsx(StatusBadge, { status: row.status }), _jsx("span", { className: "hidden sm:block text-xs text-zinc-400 dark:text-zinc-500 shrink-0 w-24 text-right", children: row.updatedAt })] }));
|
|
30
31
|
}
|
|
31
32
|
return (_jsxs("div", { className: [
|
|
32
33
|
"rounded-xl border overflow-hidden cursor-pointer transition-all hover:shadow-md hover:-translate-y-0.5",
|
|
33
34
|
selected
|
|
34
35
|
? "border-primary ring-1 ring-primary/20 bg-primary-50/30 dark:bg-primary-900/10"
|
|
35
36
|
: "border-zinc-100 dark:border-slate-700 bg-white dark:bg-slate-900",
|
|
36
|
-
].filter(Boolean).join(" "), onClick: handleClick, role: onRowClick ? "button" : undefined, children: [onToggleSelect && (_jsxs("div", { "data-checkbox": true, className: "flex items-center gap-2 px-3 pt-3", onClick: (e) => { e.stopPropagation(); onToggleSelect(row.id); }, children: [_jsx("input", { type: "checkbox", checked: selected, onChange: () => onToggleSelect(row.id), className: "h-4 w-4 rounded border-zinc-300 text-primary accent-primary cursor-pointer", "aria-label": `Select ${row.primary}` }), flags.length > 0 && (_jsx("div", { className: "flex items-center gap-1 flex-wrap", children: flags.map(({ key, label, color }) => (_jsx("span", { className: `inline-flex rounded-full px-1.5 py-0.5 text-[10px] font-medium ${color}`, children: label }, key))) }))] })), _jsxs("div", { className: "p-3.5 space-y-2", children: [_jsxs("div", { className: "space-y-0.5", children: [_jsx(
|
|
37
|
+
].filter(Boolean).join(" "), onClick: handleClick, role: onRowClick ? "button" : undefined, children: [onToggleSelect && (_jsxs("div", { "data-checkbox": true, className: "flex items-center gap-2 px-3 pt-3", onClick: (e) => { e.stopPropagation(); onToggleSelect(row.id); }, children: [_jsx("input", { type: "checkbox", checked: selected, onChange: () => onToggleSelect(row.id), className: "h-4 w-4 rounded border-zinc-300 text-primary accent-primary cursor-pointer", "aria-label": `Select ${row.primary}` }), flags.length > 0 && (_jsx("div", { className: "flex items-center gap-1 flex-wrap", children: flags.map(({ key, label, color }) => (_jsx("span", { className: `inline-flex rounded-full px-1.5 py-0.5 text-[10px] font-medium ${color}`, children: label }, key))) }))] })), _jsxs("div", { className: "p-3.5 space-y-2", children: [_jsxs("div", { className: "space-y-0.5", children: [_jsx(Text, { className: "text-sm font-semibold text-zinc-900 dark:text-zinc-100 line-clamp-2 leading-snug", children: row.primary }), _jsx(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400 truncate", children: row.secondary })] }), _jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx(StatusBadge, { status: row.status }), _jsx("span", { className: "text-[11px] text-zinc-400 dark:text-zinc-500 shrink-0", children: row.updatedAt })] })] })] }));
|
|
37
38
|
}
|
|
38
39
|
export function AdminViewCards({ rows, view, isLoading, emptyLabel = "No items found", onRowClick, selectedIdSet, onToggleSelect, }) {
|
|
39
40
|
if (isLoading) {
|
|
@@ -44,7 +45,7 @@ export function AdminViewCards({ rows, view, isLoading, emptyLabel = "No items f
|
|
|
44
45
|
return (_jsx("div", { className: "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-4", children: Array.from({ length: count }).map((_, i) => _jsx(SkeletonCard, { view: "grid" }, i)) }));
|
|
45
46
|
}
|
|
46
47
|
if (rows.length === 0) {
|
|
47
|
-
return (_jsx(
|
|
48
|
+
return (_jsx(Text, { className: "py-12 text-center text-sm text-zinc-500 dark:text-zinc-400", children: emptyLabel }));
|
|
48
49
|
}
|
|
49
50
|
if (view === "list") {
|
|
50
51
|
return (_jsx("div", { className: "rounded-xl border border-zinc-100 dark:border-slate-700 overflow-hidden divide-y divide-zinc-100 dark:divide-slate-700", children: rows.map((row) => (_jsx(AdminCardItem, { row: row, view: "list", selected: selectedIdSet?.has(row.id) ?? false, onToggleSelect: onToggleSelect, onRowClick: onRowClick }, row.id))) }));
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { TrendingUp, ShoppingBag, Users, Package, Clock, Star } from "lucide-react";
|
|
4
|
-
import { Grid } from "../../../ui";
|
|
4
|
+
import { Grid, Text } from "../../../ui";
|
|
5
5
|
import { formatCurrency } from "../../../utils/number.formatter";
|
|
6
6
|
import { getDefaultCurrency } from "../../../core/baseline-resolver";
|
|
7
7
|
// Brand gradient stop colors (mirrors SiteLogo wordmark gradient)
|
|
@@ -9,7 +9,7 @@ const BRAND_FROM = "var(--appkit-color-primary-700, #1343de)";
|
|
|
9
9
|
const BRAND_MID = "var(--appkit-color-cobalt, #3570fc)";
|
|
10
10
|
const BRAND_TO = "var(--appkit-color-secondary-400, #84e122)";
|
|
11
11
|
function StatCard({ label, value, sub, icon: Icon, gradient, iconColor }) {
|
|
12
|
-
return (_jsxs("div", { className: "relative rounded-xl border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] overflow-hidden shadow-sm hover:shadow-md transition-shadow", children: [_jsx("div", { className: "absolute top-0 left-0 right-0 h-[3px]", style: { background: gradient }, "aria-hidden": "true" }), _jsxs("div", { className: "px-5 pb-5 pt-6 flex items-start justify-between gap-3", children: [_jsxs("div", { className: "min-w-0 flex-1", children: [_jsx(
|
|
12
|
+
return (_jsxs("div", { className: "relative rounded-xl border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] overflow-hidden shadow-sm hover:shadow-md transition-shadow", children: [_jsx("div", { className: "absolute top-0 left-0 right-0 h-[3px]", style: { background: gradient }, "aria-hidden": "true" }), _jsxs("div", { className: "px-5 pb-5 pt-6 flex items-start justify-between gap-3", children: [_jsxs("div", { className: "min-w-0 flex-1", children: [_jsx(Text, { className: "text-[11px] font-semibold uppercase tracking-widest text-[var(--appkit-color-text-muted)]", children: label }), _jsx(Text, { className: "mt-2 text-2xl font-bold text-[var(--appkit-color-text)] tabular-nums leading-none", children: value }), sub && (_jsx(Text, { className: "mt-1.5 text-xs text-[var(--appkit-color-text-muted)]", children: sub }))] }), _jsx("div", { className: "flex-shrink-0 w-10 h-10 rounded-lg flex items-center justify-center", style: { background: gradient, opacity: 0.92 }, children: _jsx(Icon, { className: "w-5 h-5 text-white drop-shadow-sm" }) })] })] }));
|
|
13
13
|
}
|
|
14
14
|
function SkeletonCard() {
|
|
15
15
|
return (_jsxs("div", { className: "relative rounded-xl border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] overflow-hidden p-5 animate-pulse", children: [_jsx("div", { className: "absolute top-0 left-0 right-0 h-[3px] bg-[var(--appkit-color-border)]" }), _jsxs("div", { className: "flex items-start justify-between gap-3 pt-1", children: [_jsxs("div", { className: "flex-1 space-y-2", children: [_jsx("div", { className: "h-2.5 w-20 rounded bg-[var(--appkit-color-border)]" }), _jsx("div", { className: "h-7 w-24 rounded bg-[var(--appkit-color-border)]" })] }), _jsx("div", { className: "w-10 h-10 rounded-lg bg-[var(--appkit-color-border)]" })] })] }));
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { BaseListingCard, Button, Div, Span } from "../../../ui";
|
|
3
|
+
import { BaseListingCard, Button, Div, Span, Text } from "../../../ui";
|
|
4
4
|
import { useLongPress } from "../../../react/hooks/useLongPress";
|
|
5
5
|
const DEFAULT_COLUMNS = [
|
|
6
6
|
{
|
|
7
7
|
key: "primary",
|
|
8
8
|
header: "Name",
|
|
9
|
-
render: (row) => (_jsxs("div", { className: "space-y-0.5", children: [_jsx(
|
|
9
|
+
render: (row) => (_jsxs("div", { className: "space-y-0.5", children: [_jsx(Text, { className: "font-medium text-zinc-900 dark:text-zinc-100", children: String(row.primary ?? "") }), row.secondary ? _jsx(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400", children: String(row.secondary) }) : null] })),
|
|
10
10
|
},
|
|
11
11
|
{
|
|
12
12
|
key: "status",
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const LABEL_VERIFIED_SELLERS = "Verified Sellers";
|
|
1
2
|
export const SECTION_TYPE_OPTIONS = [
|
|
2
3
|
"welcome",
|
|
3
4
|
"stats",
|
|
@@ -69,7 +70,7 @@ export const DEFAULT_STATS_BUILDER = {
|
|
|
69
70
|
title: "Marketplace Stats",
|
|
70
71
|
stats: [
|
|
71
72
|
{ ...DEFAULT_STAT_ROW, key: "products", label: "Products Listed", value: "10,000+", source: "live-preset", metric: "total_listings", suffix: "+" },
|
|
72
|
-
{ ...DEFAULT_STAT_ROW, key: "sellers", label:
|
|
73
|
+
{ ...DEFAULT_STAT_ROW, key: "sellers", label: LABEL_VERIFIED_SELLERS, value: "2,000+", source: "live-preset", metric: "verified_sellers", suffix: "+" },
|
|
73
74
|
{ ...DEFAULT_STAT_ROW, key: "buyers", label: "Happy Buyers", value: "50,000+", source: "live-preset", metric: "total_buyers", suffix: "+" },
|
|
74
75
|
{ ...DEFAULT_STAT_ROW, key: "rating", label: "Average Rating", value: "4.8/5", source: "live-preset", metric: "platform_rating" },
|
|
75
76
|
],
|
|
@@ -145,7 +146,7 @@ export const DEFAULT_TRUST_INDICATORS_BUILDER = {
|
|
|
145
146
|
indicators: [
|
|
146
147
|
{ id: "ti-1", icon: "🚚", title: "Free Shipping", description: "On orders above ₹999" },
|
|
147
148
|
{ id: "ti-2", icon: "🔒", title: "Secure Payment", description: "Razorpay protected checkout" },
|
|
148
|
-
{ id: "ti-3", icon: "✅", title:
|
|
149
|
+
{ id: "ti-3", icon: "✅", title: LABEL_VERIFIED_SELLERS, description: "Every store is manually verified" },
|
|
149
150
|
{ id: "ti-4", icon: "↩️", title: "Easy Returns", description: "7-day hassle-free returns" },
|
|
150
151
|
],
|
|
151
152
|
};
|
|
@@ -177,7 +178,7 @@ export const DEFAULT_BANNER_BUILDER = {
|
|
|
177
178
|
};
|
|
178
179
|
export const DEFAULT_FEATURES_BUILDER = {
|
|
179
180
|
title: "Platform Features",
|
|
180
|
-
features: ["Free Shipping on ₹999+", "Secure Payments", "Easy Returns",
|
|
181
|
+
features: ["Free Shipping on ₹999+", "Secure Payments", "Easy Returns", LABEL_VERIFIED_SELLERS],
|
|
181
182
|
};
|
|
182
183
|
export const DEFAULT_REVIEWS_BUILDER = {
|
|
183
184
|
title: "What Collectors Say",
|
|
@@ -114,14 +114,14 @@ class ChatRepository extends BaseRepository {
|
|
|
114
114
|
const seen = new Set();
|
|
115
115
|
const rooms = [];
|
|
116
116
|
[...asBuyer.docs, ...asSeller.docs].forEach((d) => {
|
|
117
|
-
if (
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}
|
|
117
|
+
if (seen.has(d.id))
|
|
118
|
+
return;
|
|
119
|
+
seen.add(d.id);
|
|
120
|
+
const data = d.data();
|
|
121
|
+
const deletedBy = data.deletedBy ?? [];
|
|
122
|
+
// Only include rooms the user hasn't personally deleted
|
|
123
|
+
if (!deletedBy.includes(userId)) {
|
|
124
|
+
rooms.push(decryptPiiFields({ id: d.id, ...data }, [...CHAT_PII_FIELDS]));
|
|
125
125
|
}
|
|
126
126
|
});
|
|
127
127
|
rooms.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());
|
|
@@ -25,6 +25,7 @@ export declare class SiteSettingsRepository extends BaseRepository<SiteSettingsD
|
|
|
25
25
|
* @returns Promise<SiteSettingsDocument>
|
|
26
26
|
*/
|
|
27
27
|
getSingleton(): Promise<SiteSettingsDocument>;
|
|
28
|
+
private mergeEncryptedCredentials;
|
|
28
29
|
/**
|
|
29
30
|
* Update the global site settings
|
|
30
31
|
*
|
|
@@ -66,6 +66,16 @@ export class SiteSettingsRepository extends BaseRepository {
|
|
|
66
66
|
throw new DatabaseError(`Failed to retrieve site settings: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
|
+
mergeEncryptedCredentials(existing, updates) {
|
|
70
|
+
const encrypted = { ...existing };
|
|
71
|
+
for (const [key, value] of Object.entries(updates)) {
|
|
72
|
+
if (value && value.trim()) {
|
|
73
|
+
encrypted[key] = encryptSecret(value.trim());
|
|
74
|
+
}
|
|
75
|
+
// Empty / undefined → keep whatever was already stored
|
|
76
|
+
}
|
|
77
|
+
return encrypted;
|
|
78
|
+
}
|
|
69
79
|
/**
|
|
70
80
|
* Update the global site settings
|
|
71
81
|
*
|
|
@@ -81,15 +91,7 @@ export class SiteSettingsRepository extends BaseRepository {
|
|
|
81
91
|
let finalUpdates = updates;
|
|
82
92
|
if (updates.credentials) {
|
|
83
93
|
const existing = await this.getSingleton();
|
|
84
|
-
const
|
|
85
|
-
const encryptedCreds = { ...existingCreds };
|
|
86
|
-
for (const [key, value] of Object.entries(updates.credentials)) {
|
|
87
|
-
if (value && value.trim()) {
|
|
88
|
-
// Non-empty plaintext → encrypt and store
|
|
89
|
-
encryptedCreds[key] = encryptSecret(value.trim());
|
|
90
|
-
}
|
|
91
|
-
// Empty / undefined → keep whatever was already stored
|
|
92
|
-
}
|
|
94
|
+
const encryptedCreds = this.mergeEncryptedCredentials(existing.credentials ?? {}, updates.credentials);
|
|
93
95
|
finalUpdates = { ...updates, credentials: encryptedCreds };
|
|
94
96
|
}
|
|
95
97
|
const updateData = prepareForFirestore({
|
|
@@ -188,6 +188,38 @@ export interface SiteSettingsCredentialsMasked {
|
|
|
188
188
|
whatsappCloudApiToken?: string;
|
|
189
189
|
whatsappAdminNotifyNumbers?: string;
|
|
190
190
|
}
|
|
191
|
+
export interface NotificationChannelConfig {
|
|
192
|
+
inApp: {
|
|
193
|
+
/** Always true — in-app is the mandatory primary channel. */
|
|
194
|
+
enabled: true;
|
|
195
|
+
/** Signals to UI that this toggle is non-editable. */
|
|
196
|
+
readOnly: true;
|
|
197
|
+
};
|
|
198
|
+
email: {
|
|
199
|
+
enabled: boolean;
|
|
200
|
+
/** Minimum priority that triggers an email (default: "normal"). */
|
|
201
|
+
minPriority?: NotificationPriority;
|
|
202
|
+
/** Subset of NotificationTypes to email — absent means all types. */
|
|
203
|
+
types?: NotificationType[];
|
|
204
|
+
};
|
|
205
|
+
whatsapp: {
|
|
206
|
+
enabled: boolean;
|
|
207
|
+
/** Minimum priority that triggers a WhatsApp message (default: "high"). */
|
|
208
|
+
minPriority?: NotificationPriority;
|
|
209
|
+
/** Subset of NotificationTypes to WhatsApp — absent means all types. */
|
|
210
|
+
types?: NotificationType[];
|
|
211
|
+
/** When true, WhatsApp OTP is offered as an alternative to email OTP at checkout. */
|
|
212
|
+
otpEnabled?: boolean;
|
|
213
|
+
};
|
|
214
|
+
sms: {
|
|
215
|
+
enabled: boolean;
|
|
216
|
+
/** Minimum priority that triggers an SMS (default: "high"). */
|
|
217
|
+
minPriority?: NotificationPriority;
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
export declare const DEFAULT_NOTIFICATION_CHANNELS: NotificationChannelConfig;
|
|
221
|
+
/** Returns true when `priority` meets or exceeds `minPriority`. */
|
|
222
|
+
export declare function meetsMinPriority(priority: NotificationPriority, minPriority?: NotificationPriority): boolean;
|
|
191
223
|
export interface SiteSettingsDocument {
|
|
192
224
|
id: "global";
|
|
193
225
|
siteName: string;
|
|
@@ -406,6 +438,12 @@ export interface SiteSettingsDocument {
|
|
|
406
438
|
* alongside navConfig. Read by RSC public layouts to block disabled routes.
|
|
407
439
|
*/
|
|
408
440
|
disabledRoutes?: string[];
|
|
441
|
+
/**
|
|
442
|
+
* Notification channel configuration — controls which delivery channels are
|
|
443
|
+
* active. In-app is always enabled and cannot be disabled; all others are
|
|
444
|
+
* opt-in and require the corresponding credentials to be set.
|
|
445
|
+
*/
|
|
446
|
+
notificationChannels?: NotificationChannelConfig;
|
|
409
447
|
createdAt: Date;
|
|
410
448
|
updatedAt: Date;
|
|
411
449
|
}
|
|
@@ -124,6 +124,21 @@ export const chatRoomQueryHelpers = {
|
|
|
124
124
|
byOrder: (orderId) => ["orderId", "==", orderId],
|
|
125
125
|
active: () => ["adminDeleted", "==", false],
|
|
126
126
|
};
|
|
127
|
+
export const DEFAULT_NOTIFICATION_CHANNELS = {
|
|
128
|
+
inApp: { enabled: true, readOnly: true },
|
|
129
|
+
email: { enabled: false, minPriority: "normal" },
|
|
130
|
+
whatsapp: { enabled: false, minPriority: "high", otpEnabled: false },
|
|
131
|
+
sms: { enabled: false, minPriority: "high" },
|
|
132
|
+
};
|
|
133
|
+
const PRIORITY_ORDER = {
|
|
134
|
+
low: 0,
|
|
135
|
+
normal: 1,
|
|
136
|
+
high: 2,
|
|
137
|
+
};
|
|
138
|
+
/** Returns true when `priority` meets or exceeds `minPriority`. */
|
|
139
|
+
export function meetsMinPriority(priority, minPriority = "normal") {
|
|
140
|
+
return PRIORITY_ORDER[priority] >= PRIORITY_ORDER[minPriority];
|
|
141
|
+
}
|
|
127
142
|
export const SITE_SETTINGS_COLLECTION = "siteSettings";
|
|
128
143
|
export const SITE_SETTINGS_INDEXED_FIELDS = [];
|
|
129
144
|
export const DEFAULT_SITE_SETTINGS_DATA = {
|
|
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
import Link from "next/link";
|
|
3
3
|
import { productRepository } from "../../../repositories";
|
|
4
4
|
import { listBidsByProduct } from "../../auctions/actions/bid-actions";
|
|
5
|
+
const CLS_BREADCRUMB_LINK = "hover:text-primary-600 transition-colors";
|
|
5
6
|
import { ROUTES } from "../../../next";
|
|
6
7
|
import { getDefaultCurrency } from "../../../core/baseline-resolver";
|
|
7
8
|
import { formatCurrency } from "../../../utils/number.formatter";
|
|
@@ -29,6 +30,16 @@ function toDescriptionHtml(raw) {
|
|
|
29
30
|
const s = typeof raw === "string" ? raw : JSON.stringify(raw);
|
|
30
31
|
return normalizeRichTextHtml(s);
|
|
31
32
|
}
|
|
33
|
+
function renderAuctionInfoPanel(props) {
|
|
34
|
+
const { title, currentBid, currency, bidCount, isEnded, endDate, buyNowPrice, featured, freeShipping, condition, category, categoryName, brand, brandSlug, productFeatures, features, descriptionHtml, safeSeller, storeHref } = props;
|
|
35
|
+
return (_jsxs(Stack, { gap: "md", children: [_jsxs(Div, { children: [_jsxs(Row, { gap: "xs", className: "mb-2 flex-wrap", children: [_jsx(Span, { className: "inline-block rounded-full bg-amber-100 dark:bg-amber-900/30 px-2.5 py-0.5 text-xs font-semibold text-amber-700 dark:text-amber-300", children: "\uD83C\uDFF7\uFE0F Live Auction" }), isEnded ? (_jsx(Span, { className: "inline-block rounded-full bg-red-100 px-2.5 py-0.5 text-xs font-medium text-red-700 dark:bg-red-900/30 dark:text-red-400", children: "Ended" })) : (_jsx(Span, { className: "inline-block rounded-full bg-emerald-100 px-2.5 py-0.5 text-xs font-medium text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400", children: "Active" }))] }), _jsx(Heading, { level: 1, className: "text-xl font-bold leading-snug text-zinc-900 dark:text-zinc-50 sm:text-2xl", children: title })] }), _jsxs(Div, { children: [_jsx(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400 mb-0.5", children: "Current bid" }), _jsxs(Row, { align: "center", gap: "sm", wrap: true, children: [_jsx(Span, { className: "text-2xl font-bold text-primary-600 dark:text-primary-400", children: formatCurrency(currentBid, currency) }), _jsxs(Span, { className: "rounded-full bg-zinc-100 dark:bg-zinc-800 px-3 py-1 text-sm font-medium text-zinc-600 dark:text-zinc-300", children: [bidCount, " ", bidCount === 1 ? "bid" : "bids"] })] }), endDate && _jsxs(Text, { className: "mt-1.5 text-sm text-zinc-500 dark:text-zinc-400", children: [isEnded ? "Ended" : "Ends", " ", _jsx(Span, { className: "font-medium text-zinc-700 dark:text-zinc-300", children: endDate.toLocaleString() })] })] }), buyNowPrice !== null && !isEnded && (_jsxs(Row, { align: "center", gap: "sm", className: "rounded-lg border border-primary-200 dark:border-primary-800 bg-primary-50 dark:bg-primary-900/20 px-3 py-2", children: [_jsx(Span, { className: "text-xs text-zinc-600 dark:text-zinc-400", children: "Buy Now:" }), _jsx(Span, { className: "text-base font-bold text-primary-700 dark:text-primary-300", children: formatCurrency(buyNowPrice, currency) })] })), _jsx(ProductFeatureBadges, { featured: featured, freeShipping: freeShipping, condition: condition ?? undefined, labels: { featured: "Featured", fasterDelivery: "Faster Delivery", ratedSeller: "Rated Seller", condition: "Condition", conditionNew: "New", conditionUsed: "Used", conditionBroken: "For Parts", conditionRefurbished: "Refurbished", returnable: "Returnable", freeShipping: "Free Shipping", codAvailable: "Cash on Delivery", wishlistCount: (n) => `${n} wishlisted`, categoryProductCount: (n, cat) => `${n} in ${cat}` } }), (categoryName || category || brand) && (_jsxs(Row, { gap: "sm", wrap: true, children: [category && _jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(category)), className: "inline-flex items-center rounded-full border border-zinc-200 dark:border-zinc-700 bg-zinc-50 dark:bg-zinc-800/50 px-2.5 py-1 text-xs font-medium text-zinc-600 dark:text-zinc-300 transition-colors hover:border-primary-300 hover:bg-primary-50 hover:text-primary-700 dark:hover:border-primary-700/60 dark:hover:bg-primary-900/20 dark:hover:text-primary-400", children: categoryName || category }), !category && categoryName && _jsx(Span, { className: "inline-flex items-center rounded-full border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-800/50 px-2.5 py-1 text-xs font-medium text-zinc-500 dark:text-zinc-400", children: categoryName }), brand && brandSlug && _jsx(Link, { href: String(ROUTES.PUBLIC.BRAND_DETAIL(brandSlug)), className: "inline-flex items-center rounded-full border border-zinc-200 dark:border-zinc-700 bg-zinc-50 dark:bg-zinc-800/50 px-2.5 py-1 text-xs font-medium text-zinc-600 dark:text-zinc-300 transition-colors hover:border-primary-300 hover:bg-primary-50 hover:text-primary-700 dark:hover:border-primary-700/60 dark:hover:bg-primary-900/20 dark:hover:text-primary-400", children: brand }), brand && !brandSlug && _jsx(Span, { className: "inline-flex items-center rounded-full border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-800/50 px-2.5 py-1 text-xs font-medium text-zinc-500 dark:text-zinc-400", children: brand })] })), productFeatures && features.length > 0 && _jsx(FeatureBadgeList, { productFeatureIds: features, features: productFeatures }), !productFeatures && features.length > 0 && (_jsxs(Div, { className: "rounded-xl border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900/60 px-4 py-3", children: [_jsx(Text, { className: "mb-2 text-xs font-semibold uppercase tracking-wide text-zinc-500 dark:text-zinc-400", children: "About this item" }), _jsx("ul", { className: "space-y-1.5", children: features.map((f, i) => _jsxs("li", { className: "flex items-start gap-2 text-sm text-zinc-700 dark:text-zinc-300", children: [_jsx(Span, { className: "mt-0.5 flex-shrink-0 text-primary-500", children: "\u2022" }), f] }, i)) })] })), descriptionHtml && _jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "text-sm leading-relaxed text-zinc-600 dark:text-zinc-400 line-clamp-4" }), safeSeller && (_jsx(Div, { className: "rounded-xl border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900/60 p-3", children: _jsxs(Row, { justify: "between", align: "center", children: [_jsxs(Div, { children: [_jsx(Text, { className: "text-[10px] uppercase tracking-wide text-zinc-400 dark:text-zinc-500 mb-0.5", children: "Listed by" }), _jsx(Text, { className: "text-sm font-semibold text-zinc-800 dark:text-zinc-200", children: safeSeller })] }), storeHref && _jsx(Link, { href: storeHref, className: "shrink-0 rounded-lg bg-primary/10 dark:bg-primary/20 px-3 py-1.5 text-xs font-semibold text-primary-700 dark:text-primary-300 hover:bg-primary/20 dark:hover:bg-primary/30 transition-colors", children: "Visit Store \u2192" })] }) }))] }));
|
|
36
|
+
}
|
|
37
|
+
function renderAuctionStoreReviews(storeReviews) {
|
|
38
|
+
if (storeReviews.length === 0)
|
|
39
|
+
return null;
|
|
40
|
+
const avg = storeReviews.reduce((s, r) => s + r.rating, 0) / storeReviews.length;
|
|
41
|
+
return (_jsxs(Section, { className: "mt-10", children: [_jsx(Heading, { level: 2, className: "mb-2 text-xl font-semibold text-zinc-900 dark:text-zinc-50", children: "Store Reviews" }), _jsxs(Div, { className: "mb-4 flex items-center gap-3", children: [_jsx(Span, { className: "text-3xl font-bold text-zinc-900 dark:text-zinc-50", children: avg.toFixed(1) }), _jsxs(Div, { children: [_jsx(Row, { gap: "xs", children: [1, 2, 3, 4, 5].map((star) => _jsx(Span, { className: star <= Math.round(avg) ? "text-amber-400" : "text-zinc-200 dark:text-zinc-700", children: "\u2605" }, star)) }), _jsxs(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400", children: [storeReviews.length, " review", storeReviews.length !== 1 ? "s" : ""] })] })] }), _jsx(Stack, { gap: "sm", children: storeReviews.slice(0, 10).map((review) => (_jsxs(Div, { className: "rounded-xl border border-zinc-100 dark:border-zinc-800 bg-white dark:bg-zinc-900/60 p-4 space-y-1.5", children: [_jsx(Row, { justify: "between", align: "center", children: _jsxs(Row, { gap: "xs", align: "center", children: [_jsx(Span, { className: "text-sm font-medium text-zinc-800 dark:text-zinc-200", children: review.userName }), _jsx(Row, { gap: "xs", children: [1, 2, 3, 4, 5].map((star) => _jsx(Span, { className: `text-xs ${star <= review.rating ? "text-amber-400" : "text-zinc-200 dark:text-zinc-700"}`, children: "\u2605" }, star)) })] }) }), review.title && _jsx(Text, { className: "text-sm font-semibold text-zinc-800 dark:text-zinc-100", children: review.title }), _jsx(Text, { className: "text-sm text-zinc-600 dark:text-zinc-400 leading-relaxed", children: review.comment }), _jsx(Text, { className: "text-xs text-zinc-400 dark:text-zinc-500", children: review.productTitle })] }, review.id))) })] }));
|
|
42
|
+
}
|
|
32
43
|
export async function AuctionDetailPageView({ id, initialAuction, onPlaceBid, productFeatures }) {
|
|
33
44
|
const product = initialAuction !== undefined
|
|
34
45
|
? (initialAuction ?? undefined)
|
|
@@ -105,21 +116,7 @@ export async function AuctionDetailPageView({ id, initialAuction, onPlaceBid, pr
|
|
|
105
116
|
price: typeof p.price === "number" ? p.price : undefined,
|
|
106
117
|
storeId: typeof p.storeId === "string" ? p.storeId : undefined,
|
|
107
118
|
storeName: typeof p.storeName === "string" ? p.storeName : undefined,
|
|
108
|
-
} }), _jsxs(Container, { size: "xl", className: "px-4 py-6", children: [_jsxs("div", { className: "mb-4 flex items-center justify-between flex-wrap gap-2", children: [_jsxs("nav", { "aria-label": "Breadcrumb", className: "flex items-center gap-1.5 text-xs text-zinc-500 dark:text-zinc-400 flex-wrap", children: [_jsx(Link, { href: String(ROUTES.HOME), className:
|
|
109
|
-
featured: "Featured",
|
|
110
|
-
fasterDelivery: "Faster Delivery",
|
|
111
|
-
ratedSeller: "Rated Seller",
|
|
112
|
-
condition: "Condition",
|
|
113
|
-
conditionNew: "New",
|
|
114
|
-
conditionUsed: "Used",
|
|
115
|
-
conditionBroken: "For Parts",
|
|
116
|
-
conditionRefurbished: "Refurbished",
|
|
117
|
-
returnable: "Returnable",
|
|
118
|
-
freeShipping: "Free Shipping",
|
|
119
|
-
codAvailable: "Cash on Delivery",
|
|
120
|
-
wishlistCount: (n) => `${n} wishlisted`,
|
|
121
|
-
categoryProductCount: (n, cat) => `${n} in ${cat}`,
|
|
122
|
-
} }), (categoryName || category || brand) && (_jsxs(Row, { gap: "sm", wrap: true, children: [category && (_jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(category)), className: "inline-flex items-center rounded-full border border-zinc-200 dark:border-zinc-700 bg-zinc-50 dark:bg-zinc-800/50 px-2.5 py-1 text-xs font-medium text-zinc-600 dark:text-zinc-300 transition-colors hover:border-primary-300 hover:bg-primary-50 hover:text-primary-700 dark:hover:border-primary-700/60 dark:hover:bg-primary-900/20 dark:hover:text-primary-400", children: categoryName || category })), !category && categoryName && (_jsx(Span, { className: "inline-flex items-center rounded-full border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-800/50 px-2.5 py-1 text-xs font-medium text-zinc-500 dark:text-zinc-400", children: categoryName })), brand && brandSlug && (_jsx(Link, { href: String(ROUTES.PUBLIC.BRAND_DETAIL(brandSlug)), className: "inline-flex items-center rounded-full border border-zinc-200 dark:border-zinc-700 bg-zinc-50 dark:bg-zinc-800/50 px-2.5 py-1 text-xs font-medium text-zinc-600 dark:text-zinc-300 transition-colors hover:border-primary-300 hover:bg-primary-50 hover:text-primary-700 dark:hover:border-primary-700/60 dark:hover:bg-primary-900/20 dark:hover:text-primary-400", children: brand })), brand && !brandSlug && (_jsx(Span, { className: "inline-flex items-center rounded-full border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-800/50 px-2.5 py-1 text-xs font-medium text-zinc-500 dark:text-zinc-400", children: brand }))] })), productFeatures && features.length > 0 && (_jsx(FeatureBadgeList, { productFeatureIds: features, features: productFeatures })), !productFeatures && features.length > 0 && (_jsxs(Div, { className: "rounded-xl border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900/60 px-4 py-3", children: [_jsx(Text, { className: "mb-2 text-xs font-semibold uppercase tracking-wide text-zinc-500 dark:text-zinc-400", children: "About this item" }), _jsx("ul", { className: "space-y-1.5", children: features.map((f, i) => (_jsxs("li", { className: "flex items-start gap-2 text-sm text-zinc-700 dark:text-zinc-300", children: [_jsx(Span, { className: "mt-0.5 flex-shrink-0 text-primary-500", children: "\u2022" }), f] }, i))) })] })), descriptionHtml && (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "text-sm leading-relaxed text-zinc-600 dark:text-zinc-400 line-clamp-4" })), safeSeller && (_jsx(Div, { className: "rounded-xl border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900/60 p-3", children: _jsxs(Row, { justify: "between", align: "center", children: [_jsxs(Div, { children: [_jsx(Text, { className: "text-[10px] uppercase tracking-wide text-zinc-400 dark:text-zinc-500 mb-0.5", children: "Listed by" }), _jsx(Text, { className: "text-sm font-semibold text-zinc-800 dark:text-zinc-200", children: safeSeller })] }), storeHref && (_jsx(Link, { href: storeHref, className: "shrink-0 rounded-lg bg-primary/10 dark:bg-primary/20 px-3 py-1.5 text-xs font-semibold text-primary-700 dark:text-primary-300 hover:bg-primary/20 dark:hover:bg-primary/30 transition-colors", children: "Visit Store \u2192" }))] }) }))] })), renderBidForm: () => onPlaceBid ? (_jsx(Div, { id: "auction-bid-form", children: _jsx(PlaceBidFormClient, { productId: String(product.id), currentBid: currentBid, startingBid: startingBid, minBidIncrement: minBidIncrement, currency: currency, isEnded: isEnded, buyNowPrice: buyNowPrice, bidCount: bidCount, tags: tags, onPlaceBid: onPlaceBid }) })) : (
|
|
119
|
+
} }), _jsxs(Container, { size: "xl", className: "px-4 py-6", children: [_jsxs("div", { className: "mb-4 flex items-center justify-between flex-wrap gap-2", children: [_jsxs("nav", { "aria-label": "Breadcrumb", className: "flex items-center gap-1.5 text-xs text-zinc-500 dark:text-zinc-400 flex-wrap", children: [_jsx(Link, { href: String(ROUTES.HOME), className: CLS_BREADCRUMB_LINK, children: "Home" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.AUCTIONS), className: CLS_BREADCRUMB_LINK, children: "Auctions" }), category && (_jsxs(_Fragment, { children: [_jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(category)), className: CLS_BREADCRUMB_LINK, children: categoryName || category })] })), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Span, { className: "text-zinc-700 dark:text-zinc-300 truncate max-w-[200px]", children: title })] }), _jsx(ShareButton, { title: title })] }), _jsx(AuctionDetailView, { renderGallery: () => (_jsx(ProductGalleryClient, { images: images, productName: title })), renderInfo: () => renderAuctionInfoPanel({ title, currentBid, currency, bidCount, isEnded, endDate, buyNowPrice, featured, freeShipping, condition, category, categoryName, brand, brandSlug, productFeatures, features, descriptionHtml, safeSeller, storeHref }), renderBidForm: () => onPlaceBid ? (_jsx(Div, { id: "auction-bid-form", children: _jsx(PlaceBidFormClient, { productId: String(product.id), currentBid: currentBid, startingBid: startingBid, minBidIncrement: minBidIncrement, currency: currency, isEnded: isEnded, buyNowPrice: buyNowPrice, bidCount: bidCount, tags: tags, onPlaceBid: onPlaceBid }) })) : (
|
|
123
120
|
/* Read-only bid panel — shown when no bid action is wired (preview/demo) */
|
|
124
121
|
_jsxs(Div, { className: "rounded-xl border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900/60 p-5 space-y-4", children: [_jsx(Div, { className: "space-y-1", children: _jsxs(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400", children: ["Starting bid: ", formatCurrency(startingBid, currency), " \u00B7 min increment ", formatCurrency(minBidIncrement, currency)] }) }), _jsxs(Stack, { gap: "sm", children: [_jsx(Input, { type: "number", placeholder: `At least ${formatCurrency(currentBid + minBidIncrement, currency)}`, min: currentBid + minBidIncrement, "aria-label": "Your bid amount", disabled: isEnded }), _jsx(Button, { variant: "primary", size: "md", className: "w-full", disabled: isEnded, children: isEnded ? "Auction Ended" : "Place Bid" }), buyNowPrice !== null && !isEnded && (_jsxs(Button, { variant: "secondary", size: "md", className: "w-full", children: ["Buy Now \u2014 ", formatCurrency(buyNowPrice, currency)] }))] }), tags.length > 0 && (_jsx(Div, { className: "border-t border-zinc-200 dark:border-zinc-700 pt-4", children: _jsx(Row, { wrap: true, gap: "xs", children: tags.map((tag) => (_jsx(Span, { className: "rounded-full bg-zinc-100 dark:bg-zinc-800 px-2.5 py-1 text-xs text-zinc-600 dark:text-zinc-300", children: tag }, tag))) }) }))] })), renderMobileBidForm: () => !isEnded && onPlaceBid ? (_jsx(Div, { className: "lg:hidden", children: _jsx(PlaceBidFormClient, { productId: String(product.id), currentBid: currentBid, startingBid: startingBid, minBidIncrement: minBidIncrement, currency: currency, isEnded: isEnded, buyNowPrice: buyNowPrice, bidCount: bidCount, tags: tags, onPlaceBid: onPlaceBid }) })) : !isEnded ? (_jsxs(Div, { className: "rounded-xl border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900/60 p-4 lg:hidden", children: [_jsxs(Row, { align: "center", gap: "sm", className: "mb-3", children: [_jsx(Span, { className: "text-base font-bold text-primary-600 dark:text-primary-400", children: formatCurrency(currentBid, currency) }), _jsxs(Span, { className: "text-xs text-zinc-500", children: [bidCount, " bids"] })] }), _jsx(Button, { variant: "primary", size: "md", className: "w-full", children: "Place Bid" })] })) : null, renderSublistingSection: sublistingCategoryId
|
|
125
122
|
? () => (_jsx(SublistingCarouselSection, { sublistingCategoryId: sublistingCategoryId, currentListingId: String(product.id) }))
|
|
@@ -176,8 +173,5 @@ export async function AuctionDetailPageView({ id, initialAuction, onPlaceBid, pr
|
|
|
176
173
|
if (related.length === 0)
|
|
177
174
|
return null;
|
|
178
175
|
return (_jsx(RelatedProducts, { labels: { title: "Similar Auctions" }, renderGrid: () => (_jsx(MarketplaceAuctionGrid, { auctions: related, gridClassName: "grid grid-cols-2 gap-4 sm:grid-cols-3 lg:grid-cols-4" })) }));
|
|
179
|
-
} }), storeReviews
|
|
180
|
-
const avg = storeReviews.reduce((s, r) => s + r.rating, 0) / storeReviews.length;
|
|
181
|
-
return (_jsxs(Div, { className: "mb-4 flex items-center gap-3", children: [_jsx(Span, { className: "text-3xl font-bold text-zinc-900 dark:text-zinc-50", children: avg.toFixed(1) }), _jsxs(Div, { children: [_jsx(Row, { gap: "xs", children: [1, 2, 3, 4, 5].map((star) => (_jsx(Span, { className: star <= Math.round(avg) ? "text-amber-400" : "text-zinc-200 dark:text-zinc-700", children: "\u2605" }, star))) }), _jsxs(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400", children: [storeReviews.length, " review", storeReviews.length !== 1 ? "s" : ""] })] })] }));
|
|
182
|
-
})(), _jsx(Stack, { gap: "sm", children: storeReviews.slice(0, 10).map((review) => (_jsxs(Div, { className: "rounded-xl border border-zinc-100 dark:border-zinc-800 bg-white dark:bg-zinc-900/60 p-4 space-y-1.5", children: [_jsx(Row, { justify: "between", align: "center", children: _jsxs(Row, { gap: "xs", align: "center", children: [_jsx(Span, { className: "text-sm font-medium text-zinc-800 dark:text-zinc-200", children: review.userName }), _jsx(Row, { gap: "xs", children: [1, 2, 3, 4, 5].map((star) => (_jsx(Span, { className: `text-xs ${star <= review.rating ? "text-amber-400" : "text-zinc-200 dark:text-zinc-700"}`, children: "\u2605" }, star))) })] }) }), review.title && (_jsx(Text, { className: "text-sm font-semibold text-zinc-800 dark:text-zinc-100", children: review.title })), _jsx(Text, { className: "text-sm text-zinc-600 dark:text-zinc-400 leading-relaxed", children: review.comment }), _jsx(Text, { className: "text-xs text-zinc-400 dark:text-zinc-500", children: review.productTitle })] }, review.id))) })] })), !isEnded && (_jsxs(BuyBar, { children: [_jsx(Span, { className: "mr-auto text-sm font-bold text-zinc-900 dark:text-zinc-50", children: formatCurrency(currentBid, currency) }), _jsxs(Span, { className: "text-xs text-zinc-400 dark:text-zinc-500 mr-1", children: [bidCount, " bid", bidCount !== 1 ? "s" : ""] }), _jsx("a", { href: "#auction-bid-form", className: "appkit-button appkit-button--primary appkit-button--sm shrink-0", children: _jsx("span", { className: "appkit-button__content", children: "Place Bid" }) })] }))] })] }));
|
|
176
|
+
} }), renderAuctionStoreReviews(storeReviews), !isEnded && (_jsxs(BuyBar, { children: [_jsx(Span, { className: "mr-auto text-sm font-bold text-zinc-900 dark:text-zinc-50", children: formatCurrency(currentBid, currency) }), _jsxs(Span, { className: "text-xs text-zinc-400 dark:text-zinc-500 mr-1", children: [bidCount, " bid", bidCount !== 1 ? "s" : ""] }), _jsx("a", { href: "#auction-bid-form", className: "appkit-button appkit-button--primary appkit-button--sm shrink-0", children: _jsx("span", { className: "appkit-button__content", children: "Place Bid" }) })] }))] })] }));
|
|
183
177
|
}
|
|
@@ -12,6 +12,18 @@ import { formatCurrency } from "../../../utils";
|
|
|
12
12
|
import { getDefaultCurrency } from "../../../core/baseline-resolver";
|
|
13
13
|
import { BaseListingCard, Button, Caption, Div, Row, Span, Text, TextLink, } from "../../../ui";
|
|
14
14
|
import { THEME_CONSTANTS } from "../../../tokens";
|
|
15
|
+
function renderAuctionCardHero(props) {
|
|
16
|
+
const { product, auctionHref, currentSrc, hovered, images, imageIndex, hasVideo, isEnded, isEndingSoon, isSelected, selectable, mergedLabels, onSelect, setHovered, handleSelect } = props;
|
|
17
|
+
return (_jsxs(BaseListingCard.Hero, { aspect: "square", variant: "grid", onMouseEnter: () => setHovered(true), onMouseLeave: () => setHovered(false), children: [_jsx(TextLink, { href: auctionHref ?? "#", className: "absolute inset-0 block", children: _jsx(MediaImage, { src: currentSrc, alt: product.title, size: "card", fallback: "Auction image", className: `transition-transform duration-500 ${hovered && images.length > 1 ? "scale-105" : "scale-100"}` }) }), hasVideo && imageIndex === 0 ? (_jsx(Div, { className: "absolute right-2 top-2", children: _jsx(Span, { className: "flex h-8 w-8 items-center justify-center rounded-full bg-black/50 text-white", children: _jsx(Play, { className: "h-4 w-4", "aria-label": mergedLabels.videoLabel }) }) })) : null, images.length > 1 ? (_jsx(Div, { className: "pointer-events-none absolute bottom-2 left-1/2 flex -translate-x-1/2 gap-1", children: images.map((_, index) => (_jsx(Span, { className: `rounded-full transition-all duration-200 ${index === imageIndex ? "h-1.5 w-3 bg-white" : "h-1.5 w-1.5 bg-white/60"}` }, `${product.id}-image-${index}`))) })) : null, product.featured ? (_jsx(Div, { className: "pointer-events-none absolute left-2 top-2 z-10", children: _jsx(Star, { className: "h-5 w-5 fill-amber-400 text-amber-400" }) })) : null, onSelect && (_jsx(BaseListingCard.Checkbox, { selected: isSelected, onSelect: handleSelect, label: isSelected ? mergedLabels.deselectItem : mergedLabels.selectItem, className: selectable || isSelected ? "opacity-100" : "opacity-0 group-hover:opacity-100 transition-opacity" })), _jsxs(Div, { className: `absolute left-2 z-10 flex flex-col gap-1 ${product.featured ? "top-8" : "top-2"}`, children: [!isEnded ? _jsx(Span, { className: "rounded-full bg-rose-600/90 px-2 py-0.5 text-xs font-bold text-white", children: mergedLabels.liveBadge }) : null, isEndingSoon ? _jsx(Span, { className: "rounded-full bg-amber-500/90 px-2 py-0.5 text-xs font-bold text-white", children: mergedLabels.endingSoon }) : null, isEnded ? _jsx(Span, { className: "rounded-full bg-zinc-600/90 px-2 py-0.5 text-xs font-bold text-white", children: mergedLabels.ended }) : null, product.status === "sold" ? _jsx(Span, { className: "rounded-full bg-zinc-700/90 px-2 py-0.5 text-xs font-bold text-white", children: mergedLabels.sold }) : null] }), _jsx(Div, { className: "pointer-events-none absolute bottom-2 right-2 z-10", children: _jsxs(Span, { className: "inline-flex items-center gap-1 rounded-full bg-amber-600/90 px-2 py-0.5 text-xs font-semibold text-white", children: [_jsx(Gavel, { className: "h-3 w-3" }), mergedLabels.typeBadge] }) })] }));
|
|
18
|
+
}
|
|
19
|
+
function renderAuctionCardInfoList(props) {
|
|
20
|
+
const { product, auctionHref, displayBid, bidCount, isEnded, remaining, inWishlist, wishlistLoading, wishlistActions, countdownClass, mergedLabels, handleWishlist, handleNavigate } = props;
|
|
21
|
+
return (_jsxs(_Fragment, { children: [_jsxs(Div, { className: "flex items-start justify-between gap-2 min-w-0", children: [_jsx(TextLink, { href: auctionHref ?? "#", className: `${THEME_CONSTANTS.utilities.textClamp2} flex-1 text-sm font-medium leading-snug text-zinc-900 dark:text-zinc-100 transition-colors hover:text-primary`, children: product.title }), wishlistActions ? (_jsx(Button, { type: "button", variant: "ghost", onClick: handleWishlist, disabled: wishlistLoading, "aria-label": inWishlist ? mergedLabels.removeFromWishlist : mergedLabels.addToWishlist, className: "shrink-0 rounded-full p-1", children: _jsx(Heart, { className: `h-3.5 w-3.5 ${inWishlist ? "fill-rose-500 text-rose-500" : "text-zinc-400"}` }) })) : null] }), _jsxs(Div, { className: "flex items-center gap-2 flex-wrap", children: [_jsx(Text, { className: "text-sm font-bold text-primary", children: formatCurrency(displayBid, getDefaultCurrency()) }), _jsxs(Div, { className: `inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[11px] font-semibold ${countdownClass}`, children: [_jsx(Clock, { className: "h-2.5 w-2.5" }), _jsx(Span, { children: formatCountdownLabel(remaining, mergedLabels.ended) })] }), bidCount > 0 && _jsx(Caption, { className: "text-[11px]", children: mergedLabels.totalBids(bidCount) })] }), !isEnded && (_jsxs(Button, { type: "button", variant: "warning", size: "sm", className: "self-start gap-1 px-2.5 text-xs mt-0.5", onClick: handleNavigate, children: [_jsx(Gavel, { className: "h-3 w-3" }), _jsx(Span, { children: mergedLabels.placeBid })] }))] }));
|
|
22
|
+
}
|
|
23
|
+
function renderAuctionCardInfoGrid(props) {
|
|
24
|
+
const { product, auctionHref, displayBid, bidCount, hasCurrentBid, isEnded, remaining, resolvedBuyoutPrice, inWishlist, wishlistLoading, wishlistActions, countdownClass, mergedLabels, handleWishlist, handleNavigate } = props;
|
|
25
|
+
return (_jsxs(_Fragment, { children: [_jsxs(Div, { className: "flex items-start gap-2", children: [_jsx(TextLink, { href: auctionHref ?? "#", className: "min-w-0 flex-1 text-sm font-medium leading-snug text-zinc-900 dark:text-zinc-100 transition-colors hover:text-primary", children: product.title }), wishlistActions ? (_jsx(Button, { type: "button", variant: "ghost", onClick: handleWishlist, disabled: wishlistLoading, "aria-label": inWishlist ? mergedLabels.removeFromWishlist : mergedLabels.addToWishlist, className: "-mt-0.5 rounded-full p-1", children: _jsx(Heart, { className: `h-4 w-4 ${inWishlist ? "fill-rose-500 text-rose-500" : "text-zinc-400"}` }) })) : null] }), _jsxs(Div, { children: [_jsx(Caption, { children: isEnded && hasCurrentBid ? mergedLabels.winningBid : hasCurrentBid ? mergedLabels.currentBid : mergedLabels.startingBid }), _jsx(Text, { className: "text-base font-bold leading-none text-primary", children: formatCurrency(displayBid, getDefaultCurrency()) }), isEnded && product.winnerDisplayName && (_jsxs(Caption, { className: "mt-0.5 text-zinc-500 dark:text-zinc-400", children: [mergedLabels.wonBy, ": ", maskDisplayName(product.winnerDisplayName)] }))] }), _jsxs(Row, { wrap: true, justify: "between", gap: "sm", className: "gap-x-2 gap-y-1", children: [_jsxs(Div, { className: `inline-flex items-center gap-1 rounded-full px-2.5 py-1 text-xs font-semibold ${countdownClass}`, children: [_jsx(Clock, { className: "h-3 w-3" }), _jsx(Span, { children: formatCountdownLabel(remaining, mergedLabels.ended) })] }), _jsx(Caption, { children: mergedLabels.totalBids(bidCount) })] }), _jsx(Row, { wrap: true, gap: "xs", className: "mt-auto", children: isEnded ? (_jsx(Button, { type: "button", variant: "ghost", size: "sm", className: "flex-1 cursor-not-allowed gap-1 px-2 text-xs opacity-60", disabled: true, children: _jsx(Span, { children: mergedLabels.ended }) })) : (_jsxs(_Fragment, { children: [_jsxs(Button, { type: "button", variant: "warning", size: "sm", className: "flex-1 gap-1 px-2 text-xs", onClick: handleNavigate, children: [_jsx(Gavel, { className: "h-3 w-3" }), _jsx(Span, { children: mergedLabels.placeBid })] }), resolvedBuyoutPrice ? (_jsxs(Button, { type: "button", variant: "danger", size: "sm", className: "flex-1 gap-1 px-2 text-xs", onClick: handleNavigate, children: [_jsx(ShoppingBag, { className: "h-3 w-3" }), _jsx(Span, { children: mergedLabels.buyout })] })) : null] })) })] }));
|
|
26
|
+
}
|
|
15
27
|
const DEFAULT_LABELS = {
|
|
16
28
|
selectItem: "Select item",
|
|
17
29
|
deselectItem: "Deselect item",
|
|
@@ -137,13 +149,10 @@ export function MarketplaceAuctionCard({ product, buyoutPrice, className = "", v
|
|
|
137
149
|
: isEndingSoon
|
|
138
150
|
? COUNTDOWN_STATUS_CLASS.endingSoon
|
|
139
151
|
: COUNTDOWN_STATUS_CLASS.live;
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
: hasCurrentBid
|
|
147
|
-
? mergedLabels.currentBid
|
|
148
|
-
: mergedLabels.startingBid }), _jsx(Text, { className: "text-base font-bold leading-none text-primary", children: formatCurrency(displayBid, getDefaultCurrency()) }), isEnded && product.winnerDisplayName && (_jsxs(Caption, { className: "mt-0.5 text-zinc-500 dark:text-zinc-400", children: [mergedLabels.wonBy, ": ", maskDisplayName(product.winnerDisplayName)] }))] }), _jsxs(Row, { wrap: true, justify: "between", gap: "sm", className: "gap-x-2 gap-y-1", children: [_jsxs(Div, { className: `inline-flex items-center gap-1 rounded-full px-2.5 py-1 text-xs font-semibold ${countdownClass}`, children: [_jsx(Clock, { className: "h-3 w-3" }), _jsx(Span, { children: formatCountdownLabel(remaining, mergedLabels.ended) })] }), _jsx(Caption, { children: mergedLabels.totalBids(bidCount) })] }), _jsx(Row, { wrap: true, gap: "xs", className: "mt-auto", children: isEnded ? (_jsx(Button, { type: "button", variant: "ghost", size: "sm", className: "flex-1 cursor-not-allowed gap-1 px-2 text-xs opacity-60", disabled: true, children: _jsx(Span, { children: mergedLabels.ended }) })) : (_jsxs(_Fragment, { children: [_jsxs(Button, { type: "button", variant: "warning", size: "sm", className: "flex-1 gap-1 px-2 text-xs", onClick: handleNavigate, children: [_jsx(Gavel, { className: "h-3 w-3" }), _jsx(Span, { children: mergedLabels.placeBid })] }), resolvedBuyoutPrice ? (_jsxs(Button, { type: "button", variant: "danger", size: "sm", className: "flex-1 gap-1 px-2 text-xs", onClick: handleNavigate, children: [_jsx(ShoppingBag, { className: "h-3 w-3" }), _jsx(Span, { children: mergedLabels.buyout })] })) : null] })) })] })) })] }));
|
|
152
|
+
const cardInfoProps = {
|
|
153
|
+
product, auctionHref, displayBid, bidCount, hasCurrentBid, isEnded, isEndingSoon,
|
|
154
|
+
remaining, resolvedBuyoutPrice, inWishlist, wishlistLoading, wishlistActions,
|
|
155
|
+
countdownClass, mergedLabels, handleWishlist, handleNavigate,
|
|
156
|
+
};
|
|
157
|
+
return (_jsxs(BaseListingCard, { isSelected: isSelected, isDisabled: isEnded, variant: baseVariant, className: className, onMouseDown: !isSelected ? longPress.onMouseDown : undefined, onMouseUp: !isSelected ? longPress.onMouseUp : undefined, onMouseLeave: !isSelected ? longPress.onMouseLeave : undefined, onTouchStart: !isSelected ? longPress.onTouchStart : undefined, onTouchEnd: !isSelected ? longPress.onTouchEnd : undefined, children: [renderAuctionCardHero({ product, auctionHref, currentSrc, hovered, images, imageIndex, hasVideo, isEnded, isEndingSoon, isSelected, selectable, mergedLabels, onSelect, setHovered, handleSelect }), _jsx(BaseListingCard.Info, { variant: baseVariant, children: baseVariant === "list" ? renderAuctionCardInfoList(cardInfoProps) : renderAuctionCardInfoGrid(cardInfoProps) })] }));
|
|
149
158
|
}
|
|
@@ -3,15 +3,16 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { Gavel } from "lucide-react";
|
|
4
4
|
import { Div, Row, Stack, Text } from "../../../ui";
|
|
5
5
|
import { MarketplaceAuctionCard, } from "./MarketplaceAuctionCard";
|
|
6
|
+
const CLS_SKELETON_BTN = "h-8 w-20 rounded bg-zinc-300";
|
|
6
7
|
const DEFAULT_GRID_LABELS = {
|
|
7
8
|
emptyTitle: "No auctions found",
|
|
8
9
|
emptyDescription: "Check back later for live listings.",
|
|
9
10
|
};
|
|
10
11
|
function AuctionCardSkeleton({ variant = "grid", }) {
|
|
11
12
|
if (variant === "list") {
|
|
12
|
-
return (_jsxs(Div, { className: "flex min-h-[220px] flex-row overflow-hidden rounded-xl bg-zinc-200 animate-pulse", children: [_jsx(Div, { className: "aspect-square w-40 flex-shrink-0 bg-zinc-300" }), _jsxs(Div, { className: "flex-1 space-y-2 p-3", children: [_jsx(Div, { className: "h-4 w-2/3 rounded bg-zinc-300" }), _jsx(Div, { className: "h-3 w-full rounded bg-zinc-300" }), _jsx(Div, { className: "h-3 w-3/4 rounded bg-zinc-300" }), _jsx(Div, { className: "h-5 w-1/2 rounded bg-zinc-300" }), _jsxs(Row, { gap: "sm", children: [_jsx(Div, { className:
|
|
13
|
+
return (_jsxs(Div, { className: "flex min-h-[220px] flex-row overflow-hidden rounded-xl bg-zinc-200 animate-pulse", children: [_jsx(Div, { className: "aspect-square w-40 flex-shrink-0 bg-zinc-300" }), _jsxs(Div, { className: "flex-1 space-y-2 p-3", children: [_jsx(Div, { className: "h-4 w-2/3 rounded bg-zinc-300" }), _jsx(Div, { className: "h-3 w-full rounded bg-zinc-300" }), _jsx(Div, { className: "h-3 w-3/4 rounded bg-zinc-300" }), _jsx(Div, { className: "h-5 w-1/2 rounded bg-zinc-300" }), _jsxs(Row, { gap: "sm", children: [_jsx(Div, { className: CLS_SKELETON_BTN }), _jsx(Div, { className: CLS_SKELETON_BTN })] })] })] }));
|
|
13
14
|
}
|
|
14
|
-
return (_jsxs(Div, { className: "overflow-hidden rounded-xl bg-zinc-200 animate-pulse", children: [_jsx(Div, { className: "aspect-square bg-zinc-300" }), _jsxs(Div, { className: "space-y-2 p-3", children: [_jsx(Div, { className: "h-4 w-3/4 rounded bg-zinc-300" }), _jsx(Div, { className: "h-3 w-1/3 rounded bg-zinc-300" }), _jsx(Div, { className: "h-5 w-1/2 rounded bg-zinc-300" }), _jsxs(Row, { justify: "between", children: [_jsx(Div, { className: "h-3 w-1/4 rounded bg-zinc-300" }), _jsx(Div, { className: "h-3 w-1/3 rounded bg-zinc-300" })] }), _jsx(Row, { gap: "sm", children: _jsx(Div, { className:
|
|
15
|
+
return (_jsxs(Div, { className: "overflow-hidden rounded-xl bg-zinc-200 animate-pulse", children: [_jsx(Div, { className: "aspect-square bg-zinc-300" }), _jsxs(Div, { className: "space-y-2 p-3", children: [_jsx(Div, { className: "h-4 w-3/4 rounded bg-zinc-300" }), _jsx(Div, { className: "h-3 w-1/3 rounded bg-zinc-300" }), _jsx(Div, { className: "h-5 w-1/2 rounded bg-zinc-300" }), _jsxs(Row, { justify: "between", children: [_jsx(Div, { className: "h-3 w-1/4 rounded bg-zinc-300" }), _jsx(Div, { className: "h-3 w-1/3 rounded bg-zinc-300" })] }), _jsx(Row, { gap: "sm", children: _jsx(Div, { className: CLS_SKELETON_BTN }) })] })] }));
|
|
15
16
|
}
|
|
16
17
|
export function MarketplaceAuctionGrid({ auctions, loading = false, skeletonCount = 12, variant = "grid", selectable = false, selectedIds = [], onSelectionChange, gridClassName, emptyIcon, labels, cardLabels, wishlistActions, }) {
|
|
17
18
|
const mergedLabels = { ...DEFAULT_GRID_LABELS, ...labels };
|