@mohasinac/appkit 2.7.27 → 2.7.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_internal/client/features/filters/filter-load-options.d.ts +29 -0
- package/dist/_internal/client/features/filters/filter-load-options.js +98 -0
- package/dist/_internal/client/features/layout/DashboardLayoutClient.js +1 -1
- package/dist/_internal/server/features/checkout/actions.js +42 -35
- package/dist/_internal/server/features/classified/adapters.d.ts +21 -0
- package/dist/_internal/server/features/classified/adapters.js +24 -0
- package/dist/_internal/server/features/classified/index.d.ts +3 -0
- package/dist/_internal/server/features/classified/index.js +3 -0
- package/dist/_internal/server/features/classified/metadata.d.ts +8 -0
- package/dist/_internal/server/features/classified/metadata.js +43 -0
- package/dist/_internal/server/features/classified/og.d.ts +30 -0
- package/dist/_internal/server/features/classified/og.js +76 -0
- package/dist/_internal/server/features/digital-code/adapters.d.ts +21 -0
- package/dist/_internal/server/features/digital-code/adapters.js +29 -0
- package/dist/_internal/server/features/digital-code/index.d.ts +3 -0
- package/dist/_internal/server/features/digital-code/index.js +3 -0
- package/dist/_internal/server/features/digital-code/metadata.d.ts +8 -0
- package/dist/_internal/server/features/digital-code/metadata.js +36 -0
- package/dist/_internal/server/features/digital-code/og.d.ts +27 -0
- package/dist/_internal/server/features/digital-code/og.js +77 -0
- package/dist/_internal/server/features/live/adapters.d.ts +21 -0
- package/dist/_internal/server/features/live/adapters.js +34 -0
- package/dist/_internal/server/features/live/index.d.ts +3 -0
- package/dist/_internal/server/features/live/index.js +3 -0
- package/dist/_internal/server/features/live/metadata.d.ts +8 -0
- package/dist/_internal/server/features/live/metadata.js +39 -0
- package/dist/_internal/server/features/live/og.d.ts +27 -0
- package/dist/_internal/server/features/live/og.js +72 -0
- package/dist/_internal/server/features/promotions/actions.js +4 -3
- package/dist/_internal/server/jobs/core/onOrderCreate.js +19 -16
- package/dist/_internal/server/jobs/core/onOrderStatusChange.js +25 -22
- package/dist/_internal/server/jobs/core/onProductWrite.js +48 -58
- package/dist/_internal/shared/actions/action-registry.js +504 -9
- package/dist/_internal/shared/features/blog/schema.d.ts +4 -4
- package/dist/_internal/shared/features/brands/schema.d.ts +5 -5
- package/dist/_internal/shared/features/cart/schema.d.ts +18 -18
- package/dist/_internal/shared/features/categories/bundle-copy.d.ts +1 -1
- package/dist/_internal/shared/features/categories/bundle-schemas.d.ts +38 -38
- package/dist/_internal/shared/features/events/schema.d.ts +8 -8
- package/dist/_internal/shared/features/orders/schema.d.ts +8 -8
- package/dist/_internal/shared/features/products/schema.d.ts +42 -42
- package/dist/_internal/shared/features/promotions/schema.d.ts +4 -4
- package/dist/_internal/shared/features/reviews/schema.d.ts +1 -1
- package/dist/_internal/shared/media/limits.js +4 -3
- package/dist/client.d.ts +4 -0
- package/dist/client.js +2 -0
- package/dist/configs/next.js +4 -0
- package/dist/constants/api-endpoints.d.ts +6 -0
- package/dist/constants/api-endpoints.js +2 -0
- package/dist/constants/field-names.d.ts +1 -0
- package/dist/constants/field-names.js +1 -0
- package/dist/constants/table-keys.d.ts +6 -0
- package/dist/constants/table-keys.js +9 -0
- package/dist/core/hooks/useSyncManager.js +9 -8
- package/dist/features/about/components/FeesView.js +10 -7
- package/dist/features/about/components/HelpPageView.js +10 -1
- package/dist/features/about/components/HowAuctionsWorkView.js +10 -1
- package/dist/features/about/components/HowCheckoutWorksView.js +10 -1
- package/dist/features/about/components/HowOffersWorkView.js +10 -1
- package/dist/features/about/components/HowOrdersWorkView.js +10 -1
- package/dist/features/about/components/HowPayoutsWorkView.js +10 -1
- package/dist/features/about/components/HowPreOrdersWorkView.js +10 -1
- package/dist/features/about/components/HowReviewsWorkView.js +10 -1
- package/dist/features/about/components/PublicProfileView.js +46 -16
- package/dist/features/about/components/SecurityPrivacyView.js +7 -1
- package/dist/features/account/components/AddressesIndexListing.js +2 -2
- package/dist/features/account/components/NotificationBell.js +27 -16
- package/dist/features/account/components/NotificationPreferencesPanel.d.ts +9 -0
- package/dist/features/account/components/NotificationPreferencesPanel.js +91 -0
- package/dist/features/account/components/UserSidebar.js +4 -4
- package/dist/features/account/components/UserSupportView.js +16 -6
- package/dist/features/account/components/index.d.ts +2 -0
- package/dist/features/account/components/index.js +1 -0
- package/dist/features/account/schemas/index.d.ts +4 -4
- package/dist/features/account/types/index.d.ts +34 -0
- package/dist/features/admin/actions/admin-actions.js +4 -3
- package/dist/features/admin/actions/notification-actions.d.ts +24 -22
- package/dist/features/admin/actions/notification-actions.js +122 -22
- package/dist/features/admin/components/AdminAdEditorView.js +29 -15
- package/dist/features/admin/components/AdminAddressEditorView.d.ts +7 -0
- package/dist/features/admin/components/AdminAddressEditorView.js +117 -0
- package/dist/features/admin/components/AdminAdsView.js +22 -28
- package/dist/features/admin/components/AdminAllEventEntriesView.js +6 -1
- package/dist/features/admin/components/AdminBidsView.js +10 -3
- package/dist/features/admin/components/AdminBlogEditorView.js +2 -2
- package/dist/features/admin/components/AdminBlogView.js +8 -3
- package/dist/features/admin/components/AdminBrandsView.js +7 -2
- package/dist/features/admin/components/AdminBundleEditorView.js +5 -2
- package/dist/features/admin/components/AdminCarouselEditorView.js +6 -3
- package/dist/features/admin/components/AdminCarouselView.js +13 -7
- package/dist/features/admin/components/AdminCategoriesView.js +8 -3
- package/dist/features/admin/components/AdminCategoryEditorView.js +12 -9
- package/dist/features/admin/components/AdminContactView.js +6 -1
- package/dist/features/admin/components/AdminCouponEditorView.js +8 -2
- package/dist/features/admin/components/AdminCouponsView.js +6 -1
- package/dist/features/admin/components/AdminEmployeeEditorView.js +12 -9
- package/dist/features/admin/components/AdminFaqEditorView.js +2 -2
- package/dist/features/admin/components/AdminFaqsView.js +7 -2
- package/dist/features/admin/components/AdminFeatureFlagsView.js +58 -13
- package/dist/features/admin/components/AdminFeaturesView.js +4 -3
- package/dist/features/admin/components/AdminMediaView.js +9 -8
- package/dist/features/admin/components/AdminNavigationView.js +10 -17
- package/dist/features/admin/components/AdminNewsletterView.js +6 -1
- package/dist/features/admin/components/AdminNotificationsView.js +11 -3
- package/dist/features/admin/components/AdminOrdersView.js +6 -1
- package/dist/features/admin/components/AdminPayoutsView.js +10 -3
- package/dist/features/admin/components/AdminPrizeDrawsView.js +10 -3
- package/dist/features/admin/components/AdminProductEditorView.d.ts +3 -1
- package/dist/features/admin/components/AdminProductEditorView.js +12 -9
- package/dist/features/admin/components/AdminProductsView.js +18 -12
- package/dist/features/admin/components/AdminReturnRequestsView.js +5 -4
- package/dist/features/admin/components/AdminReviewsView.js +36 -26
- package/dist/features/admin/components/AdminScammerEditorView.js +3 -2
- package/dist/features/admin/components/AdminScammersView.js +28 -26
- package/dist/features/admin/components/AdminSectionsView.js +112 -99
- package/dist/features/admin/components/AdminSessionsView.js +10 -3
- package/dist/features/admin/components/AdminSidebar.js +4 -4
- package/dist/features/admin/components/AdminSiteSettingsView.js +45 -4
- package/dist/features/admin/components/AdminStoresView.js +6 -1
- package/dist/features/admin/components/AdminSublistingCategoriesView.js +0 -1
- package/dist/features/admin/components/AdminSupportTicketDetailView.js +1 -1
- package/dist/features/admin/components/AdminSupportTicketsView.js +32 -34
- package/dist/features/admin/components/AdminTeamView.js +6 -1
- package/dist/features/admin/components/AdminUserEditorView.js +8 -15
- package/dist/features/admin/components/AdminUsersView.js +6 -1
- package/dist/features/admin/components/AdminViewCards.js +4 -3
- package/dist/features/admin/components/DashboardStats.js +2 -2
- package/dist/features/admin/components/DataTable.js +2 -2
- package/dist/features/admin/components/index.d.ts +2 -0
- package/dist/features/admin/components/index.js +1 -0
- package/dist/features/admin/components/sections/adminSectionsTypes.js +4 -3
- package/dist/features/admin/repository/chat.repository.js +8 -8
- package/dist/features/admin/repository/site-settings.repository.d.ts +1 -0
- package/dist/features/admin/repository/site-settings.repository.js +11 -9
- package/dist/features/admin/schemas/firestore.d.ts +38 -0
- package/dist/features/admin/schemas/firestore.js +15 -0
- package/dist/features/auctions/components/AuctionBidsTable.d.ts +13 -0
- package/dist/features/auctions/components/AuctionBidsTable.js +62 -0
- package/dist/features/auctions/components/AuctionDetailPageView.js +15 -21
- package/dist/features/auctions/components/AuctionFilters.d.ts +7 -1
- package/dist/features/auctions/components/AuctionFilters.js +3 -2
- package/dist/features/auctions/components/MarketplaceAuctionCard.js +18 -9
- package/dist/features/auctions/components/MarketplaceAuctionGrid.js +3 -2
- package/dist/features/auctions/components/index.d.ts +2 -1
- package/dist/features/auctions/components/index.js +1 -1
- package/dist/features/auctions/schemas/index.d.ts +14 -14
- package/dist/features/auth/actions/realtime-token-actions.js +19 -4
- package/dist/features/auth/components/RegisterForm.js +8 -4
- package/dist/features/auth/consent-otp.d.ts +2 -0
- package/dist/features/auth/consent-otp.js +8 -0
- package/dist/features/auth/schemas/firestore.d.ts +2 -0
- package/dist/features/auth/schemas/firestore.js +18 -25
- package/dist/features/auth/schemas/index.d.ts +2 -2
- package/dist/features/before-after/api/[id]/route.js +8 -6
- package/dist/features/before-after/schemas/index.d.ts +2 -2
- package/dist/features/blog/actions/blog-actions.d.ts +3 -3
- package/dist/features/blog/components/BlogIndexListing.js +21 -2
- package/dist/features/blog/components/BlogPostForm.js +11 -5
- package/dist/features/blog/components/BlogPostView.js +10 -2
- package/dist/features/blog/schemas/index.d.ts +18 -18
- package/dist/features/cart/components/CartDrawer.js +3 -2
- package/dist/features/cart/components/CartView.js +2 -1
- package/dist/features/cart/components/CheckoutView.js +2 -1
- package/dist/features/cart/repository/cart.repository.js +4 -3
- package/dist/features/cart/utils/pending-ops.d.ts +1 -1
- package/dist/features/categories/api/[id]/route.js +4 -3
- package/dist/features/categories/api/route.js +4 -3
- package/dist/features/categories/components/BrandDetailPageView.js +2 -2
- package/dist/features/categories/components/BrandDetailTabs.d.ts +5 -1
- package/dist/features/categories/components/BrandDetailTabs.js +30 -16
- package/dist/features/categories/components/BundleCollage.d.ts +8 -0
- package/dist/features/categories/components/BundleCollage.js +50 -0
- package/dist/features/categories/components/BundleDetailView.js +2 -10
- package/dist/features/categories/components/BundleItemsPicker.js +20 -13
- package/dist/features/categories/components/CategoriesIndexListing.js +28 -9
- package/dist/features/categories/components/CategoryDetailPageView.js +2 -2
- package/dist/features/categories/components/CategoryDetailTabs.d.ts +5 -1
- package/dist/features/categories/components/CategoryDetailTabs.js +31 -16
- package/dist/features/categories/components/CategoryProductsListing.js +52 -13
- package/dist/features/categories/components/index.d.ts +2 -0
- package/dist/features/categories/components/index.js +1 -0
- package/dist/features/categories/schemas/index.d.ts +6 -6
- package/dist/features/checkout/actions/checkout-actions.js +4 -3
- package/dist/features/checkout/hooks/usePaymentCheckout.js +31 -22
- package/dist/features/classified/components/ClassifiedFilters.d.ts +12 -0
- package/dist/features/classified/components/ClassifiedFilters.js +20 -0
- package/dist/features/classified/components/ClassifiedIndexListing.d.ts +4 -0
- package/dist/features/classified/components/ClassifiedIndexListing.js +110 -0
- package/dist/features/classified/components/ClassifiedListView.d.ts +6 -0
- package/dist/features/classified/components/ClassifiedListView.js +43 -0
- package/dist/features/classified/components/index.d.ts +6 -0
- package/dist/features/classified/components/index.js +3 -0
- package/dist/features/collections/api/[slug]/route.js +16 -12
- package/dist/features/collections/schemas/index.d.ts +5 -5
- package/dist/features/consultation/api/[id]/route.js +12 -9
- package/dist/features/consultation/components/ConsultationForm.js +3 -2
- package/dist/features/consultation/schemas/index.d.ts +6 -6
- package/dist/features/contact/email.js +77 -116
- package/dist/features/copilot/components/AdminCopilotView.js +12 -2
- package/dist/features/corporate/api/[id]/route.js +12 -9
- package/dist/features/corporate/components/CorporateInquiryForm.js +2 -1
- package/dist/features/digital-codes/components/DigitalCodeFilters.d.ts +12 -0
- package/dist/features/digital-codes/components/DigitalCodeFilters.js +17 -0
- package/dist/features/digital-codes/components/DigitalCodesIndexListing.d.ts +4 -0
- package/dist/features/digital-codes/components/DigitalCodesIndexListing.js +107 -0
- package/dist/features/digital-codes/components/DigitalCodesListView.d.ts +6 -0
- package/dist/features/digital-codes/components/DigitalCodesListView.js +37 -0
- package/dist/features/digital-codes/components/index.d.ts +6 -0
- package/dist/features/digital-codes/components/index.js +3 -0
- package/dist/features/events/actions/event-actions.js +4 -3
- package/dist/features/events/components/AdminEventEditorView.js +3 -2
- package/dist/features/events/components/AdminEventsView.js +4 -2
- package/dist/features/events/components/EventPollWidget.js +5 -5
- package/dist/features/events/components/EventsIndexListing.js +2 -2
- package/dist/features/events/repository/event-entry.repository.d.ts +1 -0
- package/dist/features/events/repository/event-entry.repository.js +5 -0
- package/dist/features/events/schemas/index.d.ts +20 -20
- package/dist/features/faq/actions/faq-actions.d.ts +8 -8
- package/dist/features/faq/components/ContactCTA.js +3 -1
- package/dist/features/faq/components/FAQAccordion.js +1 -1
- package/dist/features/faq/schemas/index.d.ts +4 -4
- package/dist/features/filters/AsyncFacetSection.d.ts +18 -0
- package/dist/features/filters/AsyncFacetSection.js +65 -0
- package/dist/features/filters/index.d.ts +2 -0
- package/dist/features/filters/index.js +1 -0
- package/dist/features/forms/Checkbox.js +2 -2
- package/dist/features/forms/Input.js +2 -2
- package/dist/features/forms/Radio.js +5 -5
- package/dist/features/forms/Select.js +2 -2
- package/dist/features/forms/Slider.js +2 -2
- package/dist/features/forms/Textarea.js +2 -2
- package/dist/features/homepage/api/[id]/route.js +4 -3
- package/dist/features/homepage/api/sections/[id]/route.js +12 -9
- package/dist/features/homepage/components/AdSlot.js +2 -1
- package/dist/features/homepage/components/AdvertisementBanner.js +5 -3
- package/dist/features/homepage/components/BrandsSection.js +2 -2
- package/dist/features/homepage/components/CTABannerSection.js +2 -2
- package/dist/features/homepage/components/CharacterHotspot.d.ts +2 -2
- package/dist/features/homepage/components/CharacterHotspot.js +119 -157
- package/dist/features/homepage/components/CharacterHotspotForm.js +25 -20
- package/dist/features/homepage/components/CustomCardsSection.js +8 -6
- package/dist/features/homepage/components/FeaturedProductsSection.js +2 -2
- package/dist/features/homepage/components/GoogleReviewsSection.js +3 -3
- package/dist/features/homepage/components/HeroBanner.js +5 -4
- package/dist/features/homepage/components/HeroCarousel.js +13 -26
- package/dist/features/homepage/components/HowItWorksSection.js +4 -2
- package/dist/features/homepage/components/SocialFeedSection.js +2 -2
- package/dist/features/homepage/components/SocialPostCard.js +3 -2
- package/dist/features/homepage/lib/social-feed-fetcher.js +6 -3
- package/dist/features/layout/AppLayoutShell.d.ts +8 -2
- package/dist/features/layout/AppLayoutShell.js +54 -78
- package/dist/features/layout/BottomActions.js +43 -48
- package/dist/features/live/components/LiveItemFilters.d.ts +12 -0
- package/dist/features/live/components/LiveItemFilters.js +24 -0
- package/dist/features/live/components/LiveItemsIndexListing.d.ts +4 -0
- package/dist/features/live/components/LiveItemsIndexListing.js +108 -0
- package/dist/features/live/components/LiveItemsListView.d.ts +6 -0
- package/dist/features/live/components/LiveItemsListView.js +43 -0
- package/dist/features/live/components/index.d.ts +6 -0
- package/dist/features/live/components/index.js +3 -0
- package/dist/features/media/MediaLightbox.js +8 -14
- package/dist/features/media/types/index.d.ts +2 -2
- package/dist/features/media/upload/CameraCapture.js +2 -2
- package/dist/features/media/upload/ImageUpload.js +4 -2
- package/dist/features/media/upload/MediaUploadField.js +24 -8
- package/dist/features/media/upload/MediaUploadList.js +1 -1
- package/dist/features/orders/actions/order-actions.js +4 -3
- package/dist/features/orders/components/MarketplaceOrderCard.js +2 -1
- package/dist/features/orders/components/OrderFilters.js +12 -9
- package/dist/features/orders/schemas/index.d.ts +6 -6
- package/dist/features/pre-orders/api/[id]/route.js +12 -9
- package/dist/features/pre-orders/components/MarketplacePreorderCard.js +5 -4
- package/dist/features/pre-orders/components/PreOrderDetailPageView.js +29 -22
- package/dist/features/pre-orders/components/PreOrderFilters.d.ts +7 -1
- package/dist/features/pre-orders/components/PreOrderFilters.js +3 -2
- package/dist/features/pre-orders/components/PreOrdersIndexListing.js +17 -52
- package/dist/features/pre-orders/components/PreorderCard.d.ts +0 -9
- package/dist/features/pre-orders/components/PreorderCard.js +2 -13
- package/dist/features/pre-orders/components/index.d.ts +1 -1
- package/dist/features/pre-orders/components/index.js +1 -1
- package/dist/features/pre-orders/schemas/index.d.ts +10 -10
- package/dist/features/products/api/[id]/route.js +9 -7
- package/dist/features/products/api/route.js +1 -1
- package/dist/features/products/columns/productTableColumns.js +3 -3
- package/dist/features/products/components/AuctionsIndexListing.js +15 -51
- package/dist/features/products/components/BidHistory.js +2 -2
- package/dist/features/products/components/CompareOverlay.d.ts +2 -0
- package/dist/features/products/components/CompareOverlay.js +3 -2
- package/dist/features/products/components/MakeOfferButton.js +20 -14
- package/dist/features/products/components/MarketplacePrizeDrawCard.js +2 -1
- package/dist/features/products/components/PrizeDrawCollage.d.ts +1 -15
- package/dist/features/products/components/PrizeDrawCollage.js +54 -14
- package/dist/features/products/components/PrizeDrawDetailPageView.js +2 -1
- package/dist/features/products/components/PrizeDrawEntryActions.js +2 -1
- package/dist/features/products/components/PrizeDrawItemsEditor.js +9 -7
- package/dist/features/products/components/PrizeDrawsIndexListing.js +18 -51
- package/dist/features/products/components/PrizeRevealModal.js +24 -14
- package/dist/features/products/components/ProductDetailPageView.js +2 -1
- package/dist/features/products/components/ProductFilters.d.ts +7 -1
- package/dist/features/products/components/ProductFilters.js +11 -8
- package/dist/features/products/components/ProductForm.js +18 -16
- package/dist/features/products/components/ProductGalleryClient.js +2 -1
- package/dist/features/products/components/ProductGrid.js +12 -15
- package/dist/features/products/components/ProductsIndexListing.js +16 -52
- package/dist/features/products/components/ProductsIndexPageView.js +2 -2
- package/dist/features/products/repository/products.repository.d.ts +4 -0
- package/dist/features/products/repository/products.repository.js +22 -3
- package/dist/features/products/schemas/firestore.d.ts +7 -1
- package/dist/features/products/schemas/index.d.ts +56 -56
- package/dist/features/products/schemas/product-features.validators.d.ts +14 -14
- package/dist/features/products/types/index.d.ts +4 -0
- package/dist/features/promotions/components/CouponsIndexListing.js +1 -1
- package/dist/features/promotions/schemas/index.d.ts +6 -6
- package/dist/features/reviews/actions/review-actions.js +6 -5
- package/dist/features/reviews/api/[id]/route.js +4 -3
- package/dist/features/reviews/components/ReviewDetailPageView.js +2 -2
- package/dist/features/reviews/components/ReviewDetailShell.js +6 -3
- package/dist/features/reviews/components/ReviewsIndexListing.js +2 -2
- package/dist/features/reviews/schemas/index.d.ts +6 -6
- package/dist/features/scams/components/ScamProfileView.js +19 -10
- package/dist/features/scams/components/ScamRegistryView.js +2 -1
- package/dist/features/search/components/Search.d.ts +1 -1
- package/dist/features/search/components/Search.js +56 -49
- package/dist/features/search/schemas/index.d.ts +5 -5
- package/dist/features/seller/actions/offer-actions.js +11 -9
- package/dist/features/seller/components/SellerAddressesView.js +6 -4
- package/dist/features/seller/components/SellerBidsView.js +2 -2
- package/dist/features/seller/components/SellerCouponsView.js +1 -1
- package/dist/features/seller/components/SellerOffersPanel.js +6 -1
- package/dist/features/seller/components/SellerOrdersView.js +1 -1
- package/dist/features/seller/components/SellerPayoutSettingsView.js +3 -3
- package/dist/features/seller/components/SellerPreOrdersView.js +5 -4
- package/dist/features/seller/components/SellerPrizeDrawsView.js +5 -4
- package/dist/features/seller/components/SellerProductShell.d.ts +19 -1
- package/dist/features/seller/components/SellerProductShell.js +75 -9
- package/dist/features/seller/components/SellerProductsView.js +10 -11
- package/dist/features/seller/components/SellerShippingView.js +3 -3
- package/dist/features/seller/components/SellerSidebar.js +10 -7
- package/dist/features/seller/components/SellerStorefrontView.js +3 -3
- package/dist/features/seller/schemas/index.d.ts +4 -4
- package/dist/features/shell/FormShell.js +2 -1
- package/dist/features/shell/QuickFormDrawer.js +4 -3
- package/dist/features/shell/StepForm.js +4 -3
- package/dist/features/stores/actions/store-query-actions.js +4 -3
- package/dist/features/stores/components/StoreAboutView.js +2 -1
- package/dist/features/stores/components/StoreAuctionsListing.js +43 -64
- package/dist/features/stores/components/StoreDetailLayoutView.js +18 -4
- package/dist/features/stores/components/StorePreOrdersListing.js +33 -57
- package/dist/features/stores/components/StoreProductsListing.js +16 -49
- package/dist/features/stores/components/StoresIndexListing.js +2 -2
- package/dist/features/stores/schemas/index.d.ts +2 -2
- package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +8 -7
- package/dist/features/wishlist/hooks/useGuestWishlist.d.ts +5 -5
- package/dist/features/wishlist/hooks/useWishlistWithGuest.d.ts +6 -6
- package/dist/features/wishlist/utils/guest-wishlist.d.ts +5 -5
- package/dist/index.d.ts +27 -3
- package/dist/index.js +1743 -1725
- package/dist/monitoring/server-logger.js +5 -9
- package/dist/next/api/apiHandler.js +56 -37
- package/dist/next/components/GlobalError.js +2 -1
- package/dist/next/routing/route-map.d.ts +41 -0
- package/dist/next/routing/route-map.js +17 -0
- package/dist/react/ErrorBoundary.js +2 -1
- package/dist/react/contexts/SessionContext.js +38 -28
- package/dist/react/hooks/useCamera.d.ts +1 -1
- package/dist/react/hooks/useCamera.js +8 -7
- package/dist/seed/products-auctions-seed-data.js +40 -40
- package/dist/seed/products-preorders-seed-data.js +16 -16
- package/dist/seed/products-prize-draws-seed-data.js +4 -4
- package/dist/seed/products-standard-seed-data.js +202 -202
- package/dist/seed/site-settings-seed-data.js +6 -0
- package/dist/server-entry.d.ts +3 -0
- package/dist/server-entry.js +3 -0
- package/dist/server.d.ts +12 -0
- package/dist/server.js +15 -0
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/DataTable.js +28 -7
- package/dist/ui/components/Button.d.ts +8 -1
- package/dist/ui/components/Button.js +47 -8
- package/dist/ui/components/CollageLightbox.d.ts +18 -0
- package/dist/ui/components/CollageLightbox.js +41 -0
- package/dist/ui/components/Dropdown.js +7 -6
- package/dist/ui/components/ImageLightbox.d.ts +7 -1
- package/dist/ui/components/ImageLightbox.js +15 -5
- package/dist/ui/components/PageLoader.js +4 -3
- package/dist/ui/components/PaginatedMultiSelect.d.ts +28 -0
- package/dist/ui/components/PaginatedMultiSelect.js +141 -0
- package/dist/ui/components/PaginatedMultiSelect.style.css +272 -0
- package/dist/ui/components/Semantic.js +1 -1
- package/dist/ui/components/SideDrawer.js +17 -15
- package/dist/ui/components/index.style.css +1 -0
- package/dist/ui/index.d.ts +2 -0
- package/dist/ui/index.js +1 -0
- package/dist/utils/number.formatter.js +5 -13
- package/dist/utils/string.formatter.js +21 -18
- package/dist/validation/schemas.d.ts +2 -2
- package/package.json +1 -1
|
@@ -10,5 +10,9 @@ export interface BrandDetailTabsProps {
|
|
|
10
10
|
prizeDraws?: number;
|
|
11
11
|
bundles?: number;
|
|
12
12
|
};
|
|
13
|
+
/** Enabled listing types (e.g. ["standard","auction"]). When omitted, all tabs shown. */
|
|
14
|
+
enabledListingTypes?: string[];
|
|
15
|
+
/** Enabled category types (e.g. ["category","brand","bundle"]). When omitted, all tabs shown. */
|
|
16
|
+
enabledCategoryTypes?: string[];
|
|
13
17
|
}
|
|
14
|
-
export declare function BrandDetailTabs({ brandName, initialProductsData, initialBundles, counts, }: BrandDetailTabsProps): import("react/jsx-runtime").JSX.Element;
|
|
18
|
+
export declare function BrandDetailTabs({ brandName, initialProductsData, initialBundles, counts, enabledListingTypes, enabledCategoryTypes, }: BrandDetailTabsProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState } from "react";
|
|
4
4
|
import { CategoryProductsListing } from "./CategoryProductsListing";
|
|
5
5
|
import { AuctionsIndexListing } from "../../products/components/AuctionsIndexListing";
|
|
@@ -12,25 +12,39 @@ function tabLabel(label, count) {
|
|
|
12
12
|
return label;
|
|
13
13
|
return `${label} (${count.toLocaleString()})`;
|
|
14
14
|
}
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
const TAB_TYPE_MAP = {
|
|
16
|
+
products: { kind: "listing", type: "standard" },
|
|
17
|
+
auctions: { kind: "listing", type: "auction" },
|
|
18
|
+
"pre-orders": { kind: "listing", type: "pre-order" },
|
|
19
|
+
"prize-draws": { kind: "listing", type: "prize-draw" },
|
|
20
|
+
bundles: { kind: "category", type: "bundle" },
|
|
21
|
+
};
|
|
22
|
+
export function BrandDetailTabs({ brandName, initialProductsData, initialBundles = [], counts, enabledListingTypes, enabledCategoryTypes, }) {
|
|
23
|
+
const visibleTabs = CATEGORY_PAGE_TABS.filter((t) => {
|
|
24
|
+
const mapping = TAB_TYPE_MAP[t.id];
|
|
25
|
+
if (!mapping)
|
|
26
|
+
return true;
|
|
27
|
+
if (mapping.kind === "listing" && enabledListingTypes) {
|
|
28
|
+
return enabledListingTypes.includes(mapping.type);
|
|
29
|
+
}
|
|
30
|
+
if (mapping.kind === "category" && enabledCategoryTypes) {
|
|
31
|
+
return enabledCategoryTypes.includes(mapping.type);
|
|
32
|
+
}
|
|
33
|
+
return true;
|
|
34
|
+
});
|
|
35
|
+
const firstTabId = (visibleTabs[0]?.id ?? "products");
|
|
36
|
+
const [activeTab, setActiveTab] = useState(firstTabId);
|
|
17
37
|
const countFor = (id) => {
|
|
18
38
|
switch (id) {
|
|
19
|
-
case "products":
|
|
20
|
-
|
|
21
|
-
case "
|
|
22
|
-
|
|
23
|
-
case "
|
|
24
|
-
|
|
25
|
-
case "prize-draws":
|
|
26
|
-
return counts?.prizeDraws;
|
|
27
|
-
case "bundles":
|
|
28
|
-
return counts?.bundles;
|
|
29
|
-
default:
|
|
30
|
-
return undefined;
|
|
39
|
+
case "products": return counts?.products;
|
|
40
|
+
case "auctions": return counts?.auctions;
|
|
41
|
+
case "pre-orders": return counts?.preOrders;
|
|
42
|
+
case "prize-draws": return counts?.prizeDraws;
|
|
43
|
+
case "bundles": return counts?.bundles;
|
|
44
|
+
default: return undefined;
|
|
31
45
|
}
|
|
32
46
|
};
|
|
33
|
-
return (_jsxs(
|
|
47
|
+
return (_jsxs(_Fragment, { children: [_jsx("div", { className: "flex border-b border-zinc-200 dark:border-slate-700 mb-6 overflow-x-auto", children: visibleTabs.map((t) => (_jsx("button", { type: "button", onClick: () => setActiveTab(t.id), className: `px-5 py-2.5 text-sm font-medium whitespace-nowrap transition-colors -mb-px border-b-2 ${activeTab === t.id
|
|
34
48
|
? "border-primary text-primary"
|
|
35
49
|
: "border-transparent text-zinc-500 hover:text-zinc-900 dark:hover:text-zinc-100"}`, children: tabLabel(t.label, countFor(t.id)) }, t.id))) }), activeTab === "products" && (_jsx(CategoryProductsListing, { categorySlug: "", brandName: brandName, initialData: initialProductsData })), activeTab === "auctions" && (_jsx(AuctionsIndexListing, { brandName: brandName })), activeTab === "pre-orders" && (_jsx(PreOrdersIndexListing, { brandName: brandName })), activeTab === "prize-draws" && (_jsx(PrizeDrawsIndexListing, { brandName: brandName })), activeTab === "bundles" && (_jsx(CategoryBundlesListing, { initialBundles: initialBundles, brandName: brandName }))] }));
|
|
36
50
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ProductDocument } from "../../products/schemas/firestore";
|
|
2
|
+
export interface BundleCollageProps {
|
|
3
|
+
members: ProductDocument[];
|
|
4
|
+
/** Optional click handler — overrides the built-in lightbox (e.g. open a product drawer). */
|
|
5
|
+
onItemClick?: (product: ProductDocument) => void;
|
|
6
|
+
}
|
|
7
|
+
export declare function BundleCollage({ members, onItemClick }: BundleCollageProps): import("react/jsx-runtime").JSX.Element | null;
|
|
8
|
+
export default BundleCollage;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* BundleCollage — collage grid of bundle member products with lightbox support.
|
|
5
|
+
*
|
|
6
|
+
* Mirrors PrizeDrawCollage's visual style. Clicking any cell opens a
|
|
7
|
+
* full-screen lightbox starting at that product and cycling circularly.
|
|
8
|
+
* Each cell also keeps a "Visit product" link accessible as the keyboard
|
|
9
|
+
* action so the lightbox does not break product discovery.
|
|
10
|
+
*/
|
|
11
|
+
import { useState } from "react";
|
|
12
|
+
import Link from "next/link";
|
|
13
|
+
import { Div, Text } from "../../../ui";
|
|
14
|
+
import { ImageLightbox } from "../../../ui/components/ImageLightbox";
|
|
15
|
+
import { ROUTES } from "../../../next/routing/route-map";
|
|
16
|
+
import { formatCurrency } from "../../../utils/number.formatter";
|
|
17
|
+
const PLACEHOLDER_EMOJI = "📦";
|
|
18
|
+
function toGalleryImages(members) {
|
|
19
|
+
return members.map((p, i) => ({
|
|
20
|
+
src: p.mainImage ?? p.images?.[0] ?? "",
|
|
21
|
+
alt: p.title,
|
|
22
|
+
badge: `#${i + 1}`,
|
|
23
|
+
caption: p.title,
|
|
24
|
+
sub: formatCurrency((p.price ?? 0) / 100, p.currency ?? "INR"),
|
|
25
|
+
}));
|
|
26
|
+
}
|
|
27
|
+
function makeBundleItemClickHandler(p, idx, onItemClick, setLightboxIndex) {
|
|
28
|
+
return () => {
|
|
29
|
+
if (onItemClick) {
|
|
30
|
+
onItemClick(p);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
setLightboxIndex(idx);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
export function BundleCollage({ members, onItemClick }) {
|
|
38
|
+
const [lightboxIndex, setLightboxIndex] = useState(null);
|
|
39
|
+
if (!members.length)
|
|
40
|
+
return null;
|
|
41
|
+
const galleryImages = toGalleryImages(members);
|
|
42
|
+
return (_jsxs(_Fragment, { children: [_jsx(Div, { className: "grid grid-cols-2 gap-3 sm:grid-cols-3 md:grid-cols-4", children: members.map((p, idx) => {
|
|
43
|
+
const cover = p.mainImage ?? p.images?.[0];
|
|
44
|
+
const href = String(ROUTES.PUBLIC.PRODUCT_DETAIL?.(p.slug ?? p.id) ?? "#");
|
|
45
|
+
return (_jsxs(Div, { className: "group relative overflow-hidden rounded-lg border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] transition-transform hover:scale-[1.01]", children: [_jsxs("button", { type: "button", onClick: makeBundleItemClickHandler(p, idx, onItemClick, setLightboxIndex), className: "relative block aspect-square w-full overflow-hidden focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--appkit-color-primary)]", "aria-label": `View ${p.title} in lightbox`, children: [cover ? (
|
|
46
|
+
/* eslint-disable-next-line @next/next/no-img-element */
|
|
47
|
+
_jsx("img", { src: cover, alt: p.title, className: "absolute inset-0 h-full w-full object-cover transition-transform group-hover:scale-105", loading: "lazy" })) : (_jsx(Div, { className: "absolute inset-0 flex items-center justify-center bg-[var(--appkit-color-surface-muted)] text-3xl", children: PLACEHOLDER_EMOJI })), _jsxs(Div, { className: "absolute left-2 top-2 rounded bg-black/70 px-1.5 py-0.5 text-xs font-semibold text-white", children: ["#", idx + 1] })] }), _jsxs(Div, { className: "p-2", children: [_jsx(Link, { href: href, className: "line-clamp-2 text-sm font-medium hover:underline", children: p.title }), _jsx(Text, { className: "mt-0.5 text-xs text-[var(--appkit-color-text-muted)]", children: formatCurrency((p.price ?? 0) / 100, p.currency ?? "INR") })] })] }, p.id));
|
|
48
|
+
}) }), _jsx(ImageLightbox, { images: galleryImages, activeIndex: lightboxIndex, onClose: () => setLightboxIndex(null), onNavigate: (i) => setLightboxIndex(i), showThumbnails: true })] }));
|
|
49
|
+
}
|
|
50
|
+
export default BundleCollage;
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import Link from "next/link";
|
|
3
2
|
import { Badge, Container, Div, Heading, Main, Row, Section, Stack, Text, } from "../../../ui";
|
|
4
|
-
import { ROUTES } from "../../../next/routing/route-map";
|
|
5
3
|
import { formatCurrency } from "../../../utils/number.formatter";
|
|
6
4
|
import { BUNDLE_COPY, BUNDLE_STOCK_VARIANT, } from "../../../_internal/shared/features/categories/bundle-copy";
|
|
7
5
|
import { BundleBuyNowCta } from "./BundleBuyNowCta";
|
|
6
|
+
import { BundleCollage } from "./BundleCollage";
|
|
8
7
|
const STOCK_BADGE_TEXT = {
|
|
9
8
|
in_stock: BUNDLE_COPY.stockBadge.in_stock,
|
|
10
9
|
out_of_stock: BUNDLE_COPY.stockBadge.out_of_stock,
|
|
@@ -19,12 +18,5 @@ export function BundleDetailView({ bundle, members = [], onBuyNow, }) {
|
|
|
19
18
|
: BUNDLE_COPY.detail.priceFallback;
|
|
20
19
|
return (_jsx(Main, { children: _jsx(Section, { className: "py-10", children: _jsx(Container, { size: "xl", children: _jsxs(Stack, { gap: "lg", children: [_jsxs(Row, { gap: "lg", align: "start", className: "flex-col md:flex-row", children: [_jsx(Div, { className: "aspect-video w-full overflow-hidden rounded-2xl bg-zinc-100 md:w-1/2 dark:bg-zinc-800", children: cover ? (
|
|
21
20
|
// eslint-disable-next-line @next/next/no-img-element, lir/no-raw-media-elements
|
|
22
|
-
_jsx("img", { src: cover, alt: bundle.name, className: "h-full w-full object-cover" })) : (_jsx(Div, { className: "flex h-full w-full items-center justify-center text-6xl", children: PLACEHOLDER_EMOJI })) }), _jsxs(Stack, { gap: "md", className: "flex-1", children: [_jsx(Heading, { level: 1, className: "text-3xl font-semibold text-zinc-900 dark:text-zinc-100", children: bundle.name }), _jsxs(Row, { gap: "sm", align: "center", children: [_jsx(Text, { size: "lg", weight: "bold", children: priceLabel }), _jsxs(Text, { size: "sm", color: "muted", children: ["\u00B7 ", BUNDLE_COPY.detail.itemCount(memberCount)] }), _jsx(Badge, { variant: BUNDLE_STOCK_VARIANT[stock], children: STOCK_BADGE_TEXT[stock] })] }), bundle.description && (_jsxs(Stack, { gap: "xs", children: [_jsx(Heading, { level: 2, className: "text-sm font-semibold text-[var(--appkit-color-text-muted)]", children: BUNDLE_COPY.detailDescriptionHeading }), _jsx(Text, { className: "whitespace-pre-line", children: bundle.description })] })), onBuyNow ? (_jsx(BundleBuyNowCta, { bundleSlug: bundle.slug, outOfStock: stock === "out_of_stock", onBuyNow: onBuyNow })) : (_jsxs(Div, { className: "rounded-xl border border-dashed border-zinc-300 bg-zinc-50 p-4 text-sm dark:border-zinc-700 dark:bg-zinc-900", "aria-live": "polite", children: [_jsx(Text, { weight: "semibold", className: "mb-1 block", children: BUNDLE_COPY.detail.ctaDisabled }), _jsx(Text, { size: "sm", color: "muted", children: BUNDLE_COPY.detail.ctaHint })] }))] })] }), _jsxs(Stack, { gap: "md", children: [_jsx(Heading, { level: 2, className: "text-xl font-semibold text-zinc-900 dark:text-zinc-100", children: BUNDLE_COPY.detailItemsHeading }), members.length === 0 ? (_jsx(Text, { color: "muted", children: BUNDLE_COPY.detail.emptyMembers })) : (_jsx(
|
|
23
|
-
}
|
|
24
|
-
function BundleMemberCard({ product }) {
|
|
25
|
-
const href = String(ROUTES.PUBLIC.PRODUCT_DETAIL?.(product.slug ?? product.id) ?? "#");
|
|
26
|
-
const cover = product.mainImage ?? product.images?.[0];
|
|
27
|
-
return (_jsxs(Link, { href: href, className: "group rounded-xl border border-zinc-200 bg-white p-2 transition-colors hover:border-zinc-300 dark:border-zinc-800 dark:bg-zinc-900 dark:hover:border-zinc-700", children: [_jsx(Div, { className: "mb-2 aspect-square overflow-hidden rounded-lg bg-zinc-100 dark:bg-zinc-800", children: cover ? (
|
|
28
|
-
// eslint-disable-next-line @next/next/no-img-element, lir/no-raw-media-elements
|
|
29
|
-
_jsx("img", { src: cover, alt: product.title, className: "h-full w-full object-cover transition-transform group-hover:scale-105", loading: "lazy" })) : (_jsx(Div, { className: "flex h-full w-full items-center justify-center text-3xl", children: PLACEHOLDER_EMOJI })) }), _jsx(Text, { className: "line-clamp-2 text-sm font-medium", children: product.title }), _jsx(Text, { size: "xs", color: "muted", className: "mt-1", children: formatCurrency((product.price ?? 0) / 100, product.currency ?? "INR") })] }));
|
|
21
|
+
_jsx("img", { src: cover, alt: bundle.name, className: "h-full w-full object-cover" })) : (_jsx(Div, { className: "flex h-full w-full items-center justify-center text-6xl", children: PLACEHOLDER_EMOJI })) }), _jsxs(Stack, { gap: "md", className: "flex-1", children: [_jsx(Heading, { level: 1, className: "text-3xl font-semibold text-zinc-900 dark:text-zinc-100", children: bundle.name }), _jsxs(Row, { gap: "sm", align: "center", children: [_jsx(Text, { size: "lg", weight: "bold", children: priceLabel }), _jsxs(Text, { size: "sm", color: "muted", children: ["\u00B7 ", BUNDLE_COPY.detail.itemCount(memberCount)] }), _jsx(Badge, { variant: BUNDLE_STOCK_VARIANT[stock], children: STOCK_BADGE_TEXT[stock] })] }), bundle.description && (_jsxs(Stack, { gap: "xs", children: [_jsx(Heading, { level: 2, className: "text-sm font-semibold text-[var(--appkit-color-text-muted)]", children: BUNDLE_COPY.detailDescriptionHeading }), _jsx(Text, { className: "whitespace-pre-line", children: bundle.description })] })), onBuyNow ? (_jsx(BundleBuyNowCta, { bundleSlug: bundle.slug, outOfStock: stock === "out_of_stock", onBuyNow: onBuyNow })) : (_jsxs(Div, { className: "rounded-xl border border-dashed border-zinc-300 bg-zinc-50 p-4 text-sm dark:border-zinc-700 dark:bg-zinc-900", "aria-live": "polite", children: [_jsx(Text, { weight: "semibold", className: "mb-1 block", children: BUNDLE_COPY.detail.ctaDisabled }), _jsx(Text, { size: "sm", color: "muted", children: BUNDLE_COPY.detail.ctaHint })] }))] })] }), _jsxs(Stack, { gap: "md", children: [_jsx(Heading, { level: 2, className: "text-xl font-semibold text-zinc-900 dark:text-zinc-100", children: BUNDLE_COPY.detailItemsHeading }), members.length === 0 ? (_jsx(Text, { color: "muted", children: BUNDLE_COPY.detail.emptyMembers })) : (_jsx(BundleCollage, { members: members }))] })] }) }) }) }));
|
|
30
22
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
/**
|
|
4
4
|
* BundleItemsPicker — S-SBUNI-4 2026-05-13.
|
|
5
5
|
*
|
|
@@ -18,6 +18,24 @@ import { Badge, Button, Checkbox, Div, Input, Row, Stack, Text } from "../../../
|
|
|
18
18
|
import { BUNDLE_MAX_ITEMS, BUNDLE_MIN_ITEMS, } from "../../../_internal/shared/features/categories/bundle-config";
|
|
19
19
|
import { BUNDLE_COPY } from "../../../_internal/shared/features/categories/bundle-copy";
|
|
20
20
|
const SEARCH_DEBOUNCE_MS = 250;
|
|
21
|
+
function renderBundleSelectedChips(props) {
|
|
22
|
+
const { value, metadata, remove } = props;
|
|
23
|
+
if (value.length === 0)
|
|
24
|
+
return null;
|
|
25
|
+
return (_jsx(Div, { className: "flex flex-wrap gap-2 rounded-lg border border-zinc-200 bg-zinc-50 p-3 dark:border-zinc-700 dark:bg-zinc-900", children: value.map((id) => {
|
|
26
|
+
const meta = metadata[id];
|
|
27
|
+
const label = meta?.title ?? id;
|
|
28
|
+
return (_jsxs(Row, { gap: "xs", align: "center", className: "rounded-full border border-zinc-300 bg-white px-3 py-1 text-xs dark:border-zinc-600 dark:bg-zinc-800", children: [_jsx(Text, { size: "xs", className: "max-w-[200px] truncate", children: label }), _jsx(Button, { variant: "ghost", size: "sm", "aria-label": BUNDLE_COPY.picker.removeAria(label), onClick: () => remove(id), children: "\u00D7" })] }, id));
|
|
29
|
+
}) }));
|
|
30
|
+
}
|
|
31
|
+
function renderBundleSearchResults(props) {
|
|
32
|
+
const { results, selectedSet, value, maxItems, toggle, isSearching, searchError, debouncedQuery } = props;
|
|
33
|
+
return (_jsxs(_Fragment, { children: [searchError && _jsx(Text, { size: "sm", color: "danger", children: searchError }), isSearching && _jsx(Text, { size: "sm", color: "muted", children: BUNDLE_COPY.picker.searchingHint }), !isSearching && debouncedQuery.length >= 2 && results.length === 0 && !searchError && (_jsx(Text, { size: "sm", color: "muted", children: BUNDLE_COPY.picker.noMatches(debouncedQuery) })), results.length > 0 && (_jsx(Div, { className: "max-h-72 overflow-y-auto rounded-lg border border-zinc-200 dark:border-zinc-700", children: results.map((r) => {
|
|
34
|
+
const isSelected = selectedSet.has(r.id);
|
|
35
|
+
const atCap = !isSelected && value.length >= maxItems;
|
|
36
|
+
return (_jsxs(Row, { gap: "sm", align: "center", className: `border-b border-zinc-100 px-3 py-2 last:border-b-0 dark:border-zinc-800 ${isSelected ? "bg-blue-50 dark:bg-blue-950" : ""} ${atCap ? "opacity-50" : ""}`, children: [_jsx(Checkbox, { checked: isSelected, disabled: atCap, onChange: () => toggle(r.id), "aria-label": BUNDLE_COPY.picker.toggleAria(r.title) }), _jsxs(Stack, { gap: "xs", className: "flex-1", children: [_jsx(Text, { size: "sm", weight: "medium", children: r.title }), _jsxs(Row, { gap: "sm", children: [_jsx(Text, { size: "xs", color: "muted", children: r.id }), typeof r.priceInPaise === "number" && _jsxs(Text, { size: "xs", color: "muted", children: ["\u00B7 \u20B9", Math.round(r.priceInPaise / 100).toLocaleString("en-IN")] })] })] })] }, r.id));
|
|
37
|
+
}) }))] }));
|
|
38
|
+
}
|
|
21
39
|
export function BundleItemsPicker({ value, onChange, fetchProducts, minItems = BUNDLE_MIN_ITEMS, maxItems = BUNDLE_MAX_ITEMS, initialMetadata = {}, }) {
|
|
22
40
|
const [query, setQuery] = useState("");
|
|
23
41
|
const [debouncedQuery, setDebouncedQuery] = useState("");
|
|
@@ -83,18 +101,7 @@ export function BundleItemsPicker({ value, onChange, fetchProducts, minItems = B
|
|
|
83
101
|
const tooMany = value.length > maxItems;
|
|
84
102
|
const countLabel = BUNDLE_COPY.picker.selectedCount(value.length, minItems, maxItems);
|
|
85
103
|
const countVariant = tooFew || tooMany ? "warning" : "success";
|
|
86
|
-
return (_jsxs(Stack, { gap: "md", children: [_jsxs(Row, { gap: "sm", align: "center", justify: "between", className: "flex-wrap", children: [_jsx(Text, { size: "sm", weight: "semibold", children: BUNDLE_COPY.picker.title }), _jsx(Badge, { variant: countVariant, children: countLabel })] }), value
|
|
87
|
-
const meta = metadata[id];
|
|
88
|
-
const label = meta?.title ?? id;
|
|
89
|
-
return (_jsxs(Row, { gap: "xs", align: "center", className: "rounded-full border border-zinc-300 bg-white px-3 py-1 text-xs dark:border-zinc-600 dark:bg-zinc-800", children: [_jsx(Text, { size: "xs", className: "max-w-[200px] truncate", children: label }), _jsx(Button, { variant: "ghost", size: "sm", "aria-label": BUNDLE_COPY.picker.removeAria(label), onClick: () => remove(id), children: "\u00D7" })] }, id));
|
|
90
|
-
}) })), _jsxs(Stack, { gap: "sm", children: [_jsx(Input, { type: "search", placeholder: BUNDLE_COPY.picker.searchPlaceholder, value: query, onChange: (e) => setQuery(e.target.value), "aria-label": BUNDLE_COPY.picker.searchAriaLabel }), searchError && (_jsx(Text, { size: "sm", color: "danger", children: searchError })), isSearching && (_jsx(Text, { size: "sm", color: "muted", children: BUNDLE_COPY.picker.searchingHint })), !isSearching &&
|
|
91
|
-
debouncedQuery.length >= 2 &&
|
|
92
|
-
results.length === 0 &&
|
|
93
|
-
!searchError && (_jsx(Text, { size: "sm", color: "muted", children: BUNDLE_COPY.picker.noMatches(debouncedQuery) })), results.length > 0 && (_jsx(Div, { className: "max-h-72 overflow-y-auto rounded-lg border border-zinc-200 dark:border-zinc-700", children: results.map((r) => {
|
|
94
|
-
const isSelected = selectedSet.has(r.id);
|
|
95
|
-
const atCap = !isSelected && value.length >= maxItems;
|
|
96
|
-
return (_jsxs(Row, { gap: "sm", align: "center", className: `border-b border-zinc-100 px-3 py-2 last:border-b-0 dark:border-zinc-800 ${isSelected ? "bg-blue-50 dark:bg-blue-950" : ""} ${atCap ? "opacity-50" : ""}`, children: [_jsx(Checkbox, { checked: isSelected, disabled: atCap, onChange: () => toggle(r.id), "aria-label": BUNDLE_COPY.picker.toggleAria(r.title) }), _jsxs(Stack, { gap: "xs", className: "flex-1", children: [_jsx(Text, { size: "sm", weight: "medium", children: r.title }), _jsxs(Row, { gap: "sm", children: [_jsx(Text, { size: "xs", color: "muted", children: r.id }), typeof r.priceInPaise === "number" && (_jsxs(Text, { size: "xs", color: "muted", children: ["\u00B7 \u20B9", Math.round(r.priceInPaise / 100).toLocaleString("en-IN")] }))] })] })] }, r.id));
|
|
97
|
-
}) }))] })] }));
|
|
104
|
+
return (_jsxs(Stack, { gap: "md", children: [_jsxs(Row, { gap: "sm", align: "center", justify: "between", className: "flex-wrap", children: [_jsx(Text, { size: "sm", weight: "semibold", children: BUNDLE_COPY.picker.title }), _jsx(Badge, { variant: countVariant, children: countLabel })] }), renderBundleSelectedChips({ value, metadata, remove }), _jsxs(Stack, { gap: "sm", children: [_jsx(Input, { type: "search", placeholder: BUNDLE_COPY.picker.searchPlaceholder, value: query, onChange: (e) => setQuery(e.target.value), "aria-label": BUNDLE_COPY.picker.searchAriaLabel }), renderBundleSearchResults({ results, selectedSet, value, maxItems, toggle, isSearching, searchError, debouncedQuery })] })] }));
|
|
98
105
|
}
|
|
99
106
|
/**
|
|
100
107
|
* Default fetcher hitting the public /api/products endpoint. Consumers can
|
|
@@ -4,7 +4,7 @@ import { useState, useCallback, useMemo } from "react";
|
|
|
4
4
|
import { X } from "lucide-react";
|
|
5
5
|
import { useCategoriesFiltered } from "../hooks/useCategories";
|
|
6
6
|
import { ROUTES } from "../../../next";
|
|
7
|
-
import { Pagination,
|
|
7
|
+
import { ListingToolbar, Pagination, Text } from "../../../ui";
|
|
8
8
|
import { CategoryCard } from "./CategoryGrid";
|
|
9
9
|
import { CategoryFilters } from "./CategoryFilters";
|
|
10
10
|
import { useUrlTable } from "../../../react/hooks/useUrlTable";
|
|
@@ -12,6 +12,29 @@ import { TABLE_KEYS, VIEW_MODE } from "../../../constants/table-keys";
|
|
|
12
12
|
import { sortBy } from "../../../constants/sort";
|
|
13
13
|
import { CATEGORY_FIELDS } from "../../../constants/field-names";
|
|
14
14
|
const PAGE_SIZE = 24;
|
|
15
|
+
function renderCategoryGrid(props) {
|
|
16
|
+
const { isLoading, categories, view, activeSearch, activeTab, brandsOnly } = props;
|
|
17
|
+
const isBrandView = activeTab === "brands" || brandsOnly;
|
|
18
|
+
if (isLoading) {
|
|
19
|
+
return (_jsx("div", { className: "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-4", children: Array.from({ length: 10 }).map((_, i) => (_jsxs("div", { className: "rounded-xl border border-zinc-100 dark:border-slate-700 overflow-hidden animate-pulse", children: [_jsx("div", { className: "aspect-[4/3] bg-zinc-200 dark:bg-slate-700" }), _jsxs("div", { className: "p-3.5 space-y-2", children: [_jsx("div", { className: "h-3.5 bg-zinc-200 dark:bg-slate-700 rounded w-3/4" }), _jsx("div", { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-full" })] })] }, i))) }));
|
|
20
|
+
}
|
|
21
|
+
if (categories.length === 0) {
|
|
22
|
+
return (_jsx(Text, { className: "py-12 text-center text-sm text-zinc-500 dark:text-zinc-400", children: activeSearch ? `No ${isBrandView ? "brands" : "categories"} matching "${activeSearch}"` : isBrandView ? "No brands found" : "No categories found" }));
|
|
23
|
+
}
|
|
24
|
+
if (view === "list") {
|
|
25
|
+
return (_jsx("div", { className: "flex flex-col divide-y divide-zinc-100 dark:divide-zinc-800 rounded-xl border border-zinc-100 dark:border-zinc-800", children: categories.map((category) => _jsx(CategoryCard, { category: category, href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(category.slug)) }, category.id)) }));
|
|
26
|
+
}
|
|
27
|
+
return (_jsx("div", { className: "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-4", children: categories.map((category) => _jsx(CategoryCard, { category: category, href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(category.slug)) }, category.id)) }));
|
|
28
|
+
}
|
|
29
|
+
function renderCategoryFilterDrawer(props) {
|
|
30
|
+
const { filterOpen, setFilterOpen, activeFilterCount, clearFilters, applyFilters, pendingTable } = props;
|
|
31
|
+
if (!filterOpen)
|
|
32
|
+
return null;
|
|
33
|
+
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: "flex items-center gap-2 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 filters", 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" }) })] })] }), _jsx("div", { className: "flex-1 overflow-y-auto px-4 py-4", children: _jsx(CategoryFilters, { table: pendingTable, variant: "public" }) }), _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})` : ""] }) })] })] }));
|
|
34
|
+
}
|
|
35
|
+
function getNumericParam(table, key) {
|
|
36
|
+
return table.get(key) ? Number(table.get(key)) : undefined;
|
|
37
|
+
}
|
|
15
38
|
const DEFAULT_SORT = sortBy(CATEGORY_FIELDS.NAME, "ASC");
|
|
16
39
|
const FILTER_KEYS = [TABLE_KEYS.IS_FEATURED, "isBrand", "rootOnly", "tier", "minItemCount", "maxItemCount"];
|
|
17
40
|
const SORT_OPTIONS = [
|
|
@@ -105,9 +128,9 @@ export function CategoriesIndexListing({ initialData: _, brandsOnly = false }) {
|
|
|
105
128
|
isFeatured: table.get(TABLE_KEYS.IS_FEATURED) === "true" || undefined,
|
|
106
129
|
isBrand: isBrandParam,
|
|
107
130
|
rootOnly: table.get("rootOnly") === "true" || undefined,
|
|
108
|
-
tier: table
|
|
109
|
-
minProductCount: table
|
|
110
|
-
maxProductCount: table
|
|
131
|
+
tier: getNumericParam(table, "tier"),
|
|
132
|
+
minProductCount: getNumericParam(table, "minItemCount"),
|
|
133
|
+
maxProductCount: getNumericParam(table, "maxItemCount"),
|
|
111
134
|
sort,
|
|
112
135
|
page,
|
|
113
136
|
pageSize: PAGE_SIZE,
|
|
@@ -123,9 +146,5 @@ export function CategoriesIndexListing({ initialData: _, brandsOnly = false }) {
|
|
|
123
146
|
activeTab === tab.key
|
|
124
147
|
? "border-primary text-primary dark:text-primary-400 dark:border-primary-400"
|
|
125
148
|
: "border-transparent text-zinc-500 hover:text-zinc-800 dark:text-zinc-400 dark:hover:text-zinc-200",
|
|
126
|
-
].join(" "), children: tab.label }, tab.key))) })), _jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: activeTab === "brands" || brandsOnly ? "Search brands..." : "Search categories...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: sort, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, view: view, onViewChange: handleViewToggle, onResetAll: resetAll, hasActiveState: hasActiveState }), 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: page, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsx("div", { className: "py-6", children:
|
|
127
|
-
? `No ${activeTab === "brands" || brandsOnly ? "brands" : "categories"} matching "${activeSearch}"`
|
|
128
|
-
: activeTab === "brands" || brandsOnly
|
|
129
|
-
? "No brands found"
|
|
130
|
-
: "No categories found" })) : view === "list" ? (_jsx("div", { className: "flex flex-col divide-y divide-zinc-100 dark:divide-zinc-800 rounded-xl border border-zinc-100 dark:border-zinc-800", children: categories.map((category) => (_jsx(CategoryCard, { category: category, href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(category.slug)) }, category.id))) })) : (_jsx("div", { className: "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-4", children: categories.map((category) => (_jsx(CategoryCard, { category: category, href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(category.slug)) }, category.id))) })) }), filterOpen && (_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: "flex items-center gap-2 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 filters", 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" }) })] })] }), _jsx("div", { className: "flex-1 overflow-y-auto px-4 py-4", children: _jsx(CategoryFilters, { table: pendingTable, variant: "public" }) }), _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})` : ""] }) })] })] }))] }));
|
|
149
|
+
].join(" "), children: tab.label }, tab.key))) })), _jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: activeTab === "brands" || brandsOnly ? "Search brands..." : "Search categories...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: sort, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, view: view, onViewChange: handleViewToggle, onResetAll: resetAll, hasActiveState: hasActiveState }), 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: page, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsx("div", { className: "py-6", children: renderCategoryGrid({ isLoading, categories, view, activeSearch, activeTab, brandsOnly }) }), renderCategoryFilterDrawer({ filterOpen, setFilterOpen, activeFilterCount, clearFilters, applyFilters, pendingTable })] }));
|
|
131
150
|
}
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
|
|
|
2
2
|
import Link from "next/link";
|
|
3
3
|
import { categoriesRepository, productRepository, } from "../../../repositories";
|
|
4
4
|
import { ROUTES } from "../../../next";
|
|
5
|
-
import { Container, Main, Section } from "../../../ui";
|
|
5
|
+
import { Container, Heading, Main, Section, Text } from "../../../ui";
|
|
6
6
|
import { CategoryDetailTabs } from "./CategoryDetailTabs";
|
|
7
7
|
export async function CategoryDetailPageView({ slug }) {
|
|
8
8
|
const category = await categoriesRepository
|
|
@@ -68,7 +68,7 @@ export async function CategoryDetailPageView({ slug }) {
|
|
|
68
68
|
const totalCount = productCount + auctionCount + preOrderCount + prizeDrawCount + bundleCount;
|
|
69
69
|
const coverImage = category?.display?.coverImage;
|
|
70
70
|
const hasCover = Boolean(coverImage);
|
|
71
|
-
return (_jsxs(Main, { children: [_jsxs(
|
|
71
|
+
return (_jsxs(Main, { children: [_jsxs(Section, { className: `relative overflow-hidden ${hasCover ? "min-h-[220px] md:min-h-[280px]" : "bg-zinc-50 dark:bg-zinc-900"}`, children: [hasCover && (_jsxs(_Fragment, { children: [_jsx("div", { className: "absolute inset-0 bg-center bg-cover", style: { backgroundImage: `url(${coverImage})` } }), _jsx("div", { className: "absolute inset-0 bg-black/55" })] })), _jsxs("div", { className: `relative z-10 max-w-7xl mx-auto px-4 ${hasCover ? "py-12" : "py-8"}`, children: [_jsxs("nav", { className: "flex items-center gap-1.5 text-sm mb-4", "aria-label": "Breadcrumb", children: [_jsx(Link, { href: String(ROUTES.HOME), className: hasCover ? "text-white/70 hover:text-white transition-colors" : "text-zinc-500 hover:text-primary-600 transition-colors", children: "Home" }), _jsx("span", { className: hasCover ? "text-white/40" : "text-zinc-400", children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORIES), className: hasCover ? "text-white/70 hover:text-white transition-colors" : "text-zinc-500 hover:text-primary-600 transition-colors", children: "Categories" }), _jsx("span", { className: hasCover ? "text-white/40" : "text-zinc-400", children: "/" }), _jsx("span", { className: hasCover ? "text-white font-medium" : "text-zinc-900 dark:text-zinc-100 font-medium", children: category?.name ?? slug })] }), _jsx(Heading, { level: 1, className: `text-3xl md:text-4xl font-bold mb-2 ${hasCover ? "text-white" : "text-zinc-900 dark:text-zinc-50"}`, children: category?.name ?? slug }), category?.description && typeof category.description === "string" && !category.description.startsWith("{") && (_jsx(Text, { className: `text-base max-w-2xl mb-4 ${hasCover ? "text-white/80" : "text-zinc-600 dark:text-zinc-400"}`, children: category.description })), _jsxs("div", { className: "flex flex-wrap gap-2", children: [productCount > 0 && (_jsxs("span", { className: `inline-flex items-center gap-1 text-xs font-medium px-2.5 py-1 rounded-full ${hasCover ? "bg-white/20 text-white backdrop-blur-sm" : "bg-primary/10 text-primary-700 dark:text-primary-400"}`, children: [productCount.toLocaleString(), " ", productCount === 1 ? "product" : "products"] })), auctionCount > 0 && (_jsxs("span", { className: `inline-flex items-center gap-1 text-xs font-medium px-2.5 py-1 rounded-full ${hasCover ? "bg-white/20 text-white backdrop-blur-sm" : "bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400"}`, children: [auctionCount.toLocaleString(), " ", auctionCount === 1 ? "auction" : "auctions"] })), preOrderCount > 0 && (_jsxs("span", { className: `inline-flex items-center gap-1 text-xs font-medium px-2.5 py-1 rounded-full ${hasCover ? "bg-white/20 text-white backdrop-blur-sm" : "bg-violet-100 text-violet-700 dark:bg-violet-900/30 dark:text-violet-400"}`, children: [preOrderCount.toLocaleString(), " ", preOrderCount === 1 ? "pre-order" : "pre-orders"] }))] })] })] }), childCategories.length > 0 && (_jsx(Section, { className: "border-b border-zinc-100 dark:border-zinc-800 bg-white dark:bg-zinc-900", children: _jsx("div", { className: "max-w-7xl mx-auto px-4 py-3", children: _jsx("div", { className: "flex gap-2.5 overflow-x-auto pb-1", style: { scrollbarWidth: "none" }, children: childCategories.map((child) => (_jsxs(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(child.slug)), className: "flex-shrink-0 flex items-center gap-1.5 rounded-full border border-zinc-200 dark:border-zinc-700 bg-zinc-50 dark:bg-zinc-800 px-4 py-1.5 text-sm font-medium text-zinc-700 dark:text-zinc-200 hover:border-primary hover:text-primary transition-colors whitespace-nowrap", children: [child.display?.icon && (_jsx("span", { className: "text-base leading-none", children: child.display.icon })), child.name, (child.metrics?.productCount ?? 0) > 0 && (_jsx("span", { className: "text-xs text-zinc-400 dark:text-zinc-500", children: (child.metrics?.productCount ?? 0).toLocaleString() }))] }, child.id))) }) }) })), _jsx(Section, { className: "py-6", children: _jsx(Container, { size: "xl", children: _jsx(CategoryDetailTabs, { categorySlug: slug, categoryId: category?.id, initialProductsData: productsResult ?? undefined, initialBundles: bundlesResult ?? [], counts: {
|
|
72
72
|
products: productCount,
|
|
73
73
|
auctions: auctionCount,
|
|
74
74
|
preOrders: preOrderCount,
|
|
@@ -11,5 +11,9 @@ export interface CategoryDetailTabsProps {
|
|
|
11
11
|
prizeDraws?: number;
|
|
12
12
|
bundles?: number;
|
|
13
13
|
};
|
|
14
|
+
/** Enabled listing types (e.g. ["standard","auction","pre-order"]). When omitted, all tabs shown. */
|
|
15
|
+
enabledListingTypes?: string[];
|
|
16
|
+
/** Enabled category types (e.g. ["category","brand","bundle"]). When omitted, all tabs shown. */
|
|
17
|
+
enabledCategoryTypes?: string[];
|
|
14
18
|
}
|
|
15
|
-
export declare function CategoryDetailTabs({ categorySlug, categoryId, initialProductsData, initialBundles, counts, }: CategoryDetailTabsProps): import("react/jsx-runtime").JSX.Element;
|
|
19
|
+
export declare function CategoryDetailTabs({ categorySlug, categoryId, initialProductsData, initialBundles, counts, enabledListingTypes, enabledCategoryTypes, }: CategoryDetailTabsProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState } from "react";
|
|
4
4
|
import { CategoryProductsListing } from "./CategoryProductsListing";
|
|
5
5
|
import { AuctionsIndexListing } from "../../products/components/AuctionsIndexListing";
|
|
@@ -12,25 +12,40 @@ function tabLabel(label, count) {
|
|
|
12
12
|
return label;
|
|
13
13
|
return `${label} (${count.toLocaleString()})`;
|
|
14
14
|
}
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
/** Maps CATEGORY_PAGE_TABS id → listing type / category type key for flag filtering. */
|
|
16
|
+
const TAB_TYPE_MAP = {
|
|
17
|
+
products: { kind: "listing", type: "standard" },
|
|
18
|
+
auctions: { kind: "listing", type: "auction" },
|
|
19
|
+
"pre-orders": { kind: "listing", type: "pre-order" },
|
|
20
|
+
"prize-draws": { kind: "listing", type: "prize-draw" },
|
|
21
|
+
bundles: { kind: "category", type: "bundle" },
|
|
22
|
+
};
|
|
23
|
+
export function CategoryDetailTabs({ categorySlug, categoryId, initialProductsData, initialBundles = [], counts, enabledListingTypes, enabledCategoryTypes, }) {
|
|
24
|
+
const visibleTabs = CATEGORY_PAGE_TABS.filter((t) => {
|
|
25
|
+
const mapping = TAB_TYPE_MAP[t.id];
|
|
26
|
+
if (!mapping)
|
|
27
|
+
return true;
|
|
28
|
+
if (mapping.kind === "listing" && enabledListingTypes) {
|
|
29
|
+
return enabledListingTypes.includes(mapping.type);
|
|
30
|
+
}
|
|
31
|
+
if (mapping.kind === "category" && enabledCategoryTypes) {
|
|
32
|
+
return enabledCategoryTypes.includes(mapping.type);
|
|
33
|
+
}
|
|
34
|
+
return true;
|
|
35
|
+
});
|
|
36
|
+
const firstTabId = (visibleTabs[0]?.id ?? "products");
|
|
37
|
+
const [activeTab, setActiveTab] = useState(firstTabId);
|
|
17
38
|
const countFor = (id) => {
|
|
18
39
|
switch (id) {
|
|
19
|
-
case "products":
|
|
20
|
-
|
|
21
|
-
case "
|
|
22
|
-
|
|
23
|
-
case "
|
|
24
|
-
|
|
25
|
-
case "prize-draws":
|
|
26
|
-
return counts?.prizeDraws;
|
|
27
|
-
case "bundles":
|
|
28
|
-
return counts?.bundles;
|
|
29
|
-
default:
|
|
30
|
-
return undefined;
|
|
40
|
+
case "products": return counts?.products;
|
|
41
|
+
case "auctions": return counts?.auctions;
|
|
42
|
+
case "pre-orders": return counts?.preOrders;
|
|
43
|
+
case "prize-draws": return counts?.prizeDraws;
|
|
44
|
+
case "bundles": return counts?.bundles;
|
|
45
|
+
default: return undefined;
|
|
31
46
|
}
|
|
32
47
|
};
|
|
33
|
-
return (_jsxs(
|
|
48
|
+
return (_jsxs(_Fragment, { children: [_jsx("div", { className: "flex border-b border-zinc-200 dark:border-slate-700 mb-6 overflow-x-auto", children: visibleTabs.map((t) => (_jsx("button", { type: "button", onClick: () => setActiveTab(t.id), className: `px-5 py-2.5 text-sm font-medium whitespace-nowrap transition-colors -mb-px border-b-2 ${activeTab === t.id
|
|
34
49
|
? "border-primary text-primary"
|
|
35
50
|
: "border-transparent text-zinc-500 hover:text-zinc-900 dark:hover:text-zinc-100"}`, children: tabLabel(t.label, countFor(t.id)) }, t.id))) }), activeTab === "products" && (_jsx(CategoryProductsListing, { categorySlug: categorySlug, categoryId: categoryId, initialData: initialProductsData })), activeTab === "auctions" && (_jsx(AuctionsIndexListing, { categorySlug: categorySlug })), activeTab === "pre-orders" && (_jsx(PreOrdersIndexListing, { categorySlug: categorySlug })), activeTab === "prize-draws" && (_jsx(PrizeDrawsIndexListing, { categorySlug: categorySlug })), activeTab === "bundles" && (_jsx(CategoryBundlesListing, { initialBundles: initialBundles }))] }));
|
|
36
51
|
}
|
|
@@ -1,19 +1,27 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState, useCallback } from "react";
|
|
4
|
-
import { Search, SlidersHorizontal, LayoutGrid, List, X } from "lucide-react";
|
|
5
4
|
import { useUrlTable } from "../../../react/hooks/useUrlTable";
|
|
5
|
+
import { usePendingTable } from "../../../react/hooks/usePendingTable";
|
|
6
6
|
import { useProducts } from "../../products/hooks/useProducts";
|
|
7
|
-
import { Pagination,
|
|
7
|
+
import { Pagination, useToast, ListingToolbar, FilterDrawer, LoginRequiredModal, } from "../../../ui";
|
|
8
8
|
import { useAuthGate } from "../../../react/hooks/useAuthGate";
|
|
9
9
|
import { ACTION_ID } from "../../products/constants/action-defs";
|
|
10
10
|
import { ROUTES } from "../../../next";
|
|
11
11
|
import { ProductGrid } from "../../products/components/ProductGrid";
|
|
12
|
-
import { ProductFilters } from "../../products/components/ProductFilters";
|
|
13
|
-
import { PRODUCT_PUBLIC_SORT_OPTIONS } from "../../products/components/ProductFilters";
|
|
12
|
+
import { ProductFilters, PRODUCT_PUBLIC_SORT_OPTIONS } from "../../products/components/ProductFilters";
|
|
14
13
|
import { useGuestCart } from "../../cart/hooks/useGuestCart";
|
|
15
14
|
import { useGuestWishlist } from "../../wishlist/hooks/useGuestWishlist";
|
|
16
15
|
import { pushCartOp, pushWishlistOp } from "../../cart/utils/pending-ops";
|
|
16
|
+
const FILTER_KEYS = ["condition", "brand", "minPrice", "maxPrice"];
|
|
17
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
18
|
+
function renderCategoryProductGrid(props) {
|
|
19
|
+
const { isLoading, products, view, getProductHref, onWishlistToggle, wishlistedIds, onAddToCart } = props;
|
|
20
|
+
if (isLoading) {
|
|
21
|
+
return (_jsx("div", { className: "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-6", children: Array.from({ length: 8 }).map((_, i) => (_jsxs("div", { className: "rounded-xl border border-zinc-100 dark:border-slate-700 overflow-hidden animate-pulse", children: [_jsx("div", { className: "aspect-square bg-zinc-200 dark:bg-slate-700" }), _jsxs("div", { className: "p-3 space-y-2", children: [_jsx("div", { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-3/4" }), _jsx("div", { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-1/2" }), _jsx("div", { className: "h-4 bg-zinc-200 dark:bg-slate-700 rounded w-1/3" })] })] }, i))) }));
|
|
22
|
+
}
|
|
23
|
+
return (_jsx(ProductGrid, { products: products, getProductHref: getProductHref, view: view, emptyLabel: "No products found in this category.", onWishlistToggle: onWishlistToggle, wishlistedIds: wishlistedIds, onAddToCart: onAddToCart }));
|
|
24
|
+
}
|
|
17
25
|
export function CategoryProductsListing({ categorySlug, categoryId, brandName, initialData, }) {
|
|
18
26
|
const table = useUrlTable({ defaults: { pageSize: "24", sort: "-createdAt" } });
|
|
19
27
|
const { showToast } = useToast();
|
|
@@ -24,6 +32,7 @@ export function CategoryProductsListing({ categorySlug, categoryId, brandName, i
|
|
|
24
32
|
const localCart = useGuestCart();
|
|
25
33
|
const localWishlist = useGuestWishlist();
|
|
26
34
|
const wishlistedIds = new Set(localWishlist.items.filter((i) => i.type === "product").map((i) => i.itemId));
|
|
35
|
+
const { pendingTable, filterActiveCount, onFilterApply, onFilterClear, onFilterReset } = usePendingTable(table, FILTER_KEYS);
|
|
27
36
|
const params = {
|
|
28
37
|
q: table.get("q") || undefined,
|
|
29
38
|
category: categoryId || undefined,
|
|
@@ -36,11 +45,32 @@ export function CategoryProductsListing({ categorySlug, categoryId, brandName, i
|
|
|
36
45
|
perPage: table.getNumber("pageSize", 24),
|
|
37
46
|
listingType: "standard",
|
|
38
47
|
};
|
|
39
|
-
const { products,
|
|
48
|
+
const { products, totalPages, page, isLoading } = useProducts(params, { initialData });
|
|
40
49
|
const commitSearch = useCallback(() => {
|
|
41
50
|
table.set("q", searchInput.trim());
|
|
42
51
|
table.setPage(1);
|
|
43
52
|
}, [searchInput, table]);
|
|
53
|
+
const openFilters = useCallback(() => {
|
|
54
|
+
onFilterReset();
|
|
55
|
+
setFilterOpen(true);
|
|
56
|
+
}, [onFilterReset]);
|
|
57
|
+
const applyFilters = useCallback(() => {
|
|
58
|
+
onFilterApply();
|
|
59
|
+
setFilterOpen(false);
|
|
60
|
+
}, [onFilterApply]);
|
|
61
|
+
const handleViewToggle = (next) => {
|
|
62
|
+
setView(next);
|
|
63
|
+
table.set("view", next);
|
|
64
|
+
};
|
|
65
|
+
const resetAll = useCallback(() => {
|
|
66
|
+
table.setMany({ q: "", sort: "" });
|
|
67
|
+
onFilterClear();
|
|
68
|
+
setSearchInput("");
|
|
69
|
+
}, [table, onFilterClear]);
|
|
70
|
+
const activeFilterCount = FILTER_KEYS.filter((k) => !!table.get(k)).length;
|
|
71
|
+
const hasActiveState = !!table.get("q") ||
|
|
72
|
+
table.get("sort") !== "-createdAt" ||
|
|
73
|
+
activeFilterCount > 0;
|
|
44
74
|
const handleWishlistToggle = useCallback((productId) => {
|
|
45
75
|
const isWishlisted = wishlistedIds.has(productId);
|
|
46
76
|
requireAuth(isWishlisted ? ACTION_ID.REMOVE_FROM_WISHLIST : ACTION_ID.ADD_TO_WISHLIST, () => {
|
|
@@ -62,13 +92,22 @@ export function CategoryProductsListing({ categorySlug, categoryId, brandName, i
|
|
|
62
92
|
productImage: product.mainImage,
|
|
63
93
|
price: product.price,
|
|
64
94
|
});
|
|
65
|
-
pushCartOp({
|
|
95
|
+
pushCartOp({
|
|
96
|
+
op: "add",
|
|
97
|
+
productId: product.id,
|
|
98
|
+
quantity: 1,
|
|
99
|
+
productTitle: product.title,
|
|
100
|
+
productImage: product.mainImage,
|
|
101
|
+
price: product.price,
|
|
102
|
+
});
|
|
66
103
|
showToast("Added to cart", "success");
|
|
67
104
|
}, [localCart, showToast]);
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
105
|
+
return (_jsxs("div", { className: "min-h-[200px]", children: [_jsx(ListingToolbar, { filterCount: filterActiveCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: `Search in ${categorySlug.replace(/-/g, " ")}...`, onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || "-createdAt", sortOptions: PRODUCT_PUBLIC_SORT_OPTIONS, onSortChange: (v) => {
|
|
106
|
+
table.set("sort", v);
|
|
107
|
+
}, view: view === "card" ? "grid" : "list", onViewChange: (v) => handleViewToggle(v === "grid" ? "card" : "list"), onResetAll: resetAll, hasActiveState: hasActiveState }), 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: page, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsx("div", { className: "py-6", children: renderCategoryProductGrid({
|
|
108
|
+
isLoading, products: products, view,
|
|
109
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
110
|
+
getProductHref: (p) => String(ROUTES.PUBLIC.PRODUCT_DETAIL(p.slug || p.id)),
|
|
111
|
+
onWishlistToggle: handleWishlistToggle, wishlistedIds, onAddToCart: handleAddToCart,
|
|
112
|
+
}) }), _jsx(FilterDrawer, { open: filterOpen, onOpen: openFilters, onClose: () => setFilterOpen(false), onApply: applyFilters, onReset: onFilterClear, activeCount: filterActiveCount, hideTrigger: true, children: _jsx(ProductFilters, { table: pendingTable, currencyPrefix: "\u20B9" }) }), _jsx(LoginRequiredModal, { isOpen: modalOpen, onClose: closeModal, message: modalMessage })] }));
|
|
74
113
|
}
|
|
@@ -30,6 +30,8 @@ export { BundleBuyNowCta } from "./BundleBuyNowCta";
|
|
|
30
30
|
export type { BundleBuyNowCtaProps } from "./BundleBuyNowCta";
|
|
31
31
|
export { BundleDynamicRuleEditor } from "./BundleDynamicRuleEditor";
|
|
32
32
|
export type { BundleDynamicRuleEditorProps } from "./BundleDynamicRuleEditor";
|
|
33
|
+
export { BundleCollage } from "./BundleCollage";
|
|
34
|
+
export type { BundleCollageProps } from "./BundleCollage";
|
|
33
35
|
export { BundleItemsPicker, defaultBundleItemsFetch, } from "./BundleItemsPicker";
|
|
34
36
|
export type { BundleItemsPickerProps, BundleItemSearchResult, } from "./BundleItemsPicker";
|
|
35
37
|
export { CategoryBundlesListing } from "./CategoryBundlesListing";
|
|
@@ -17,6 +17,7 @@ export { CategoryDetailTabs } from "./CategoryDetailTabs";
|
|
|
17
17
|
export { BundleBuyNowCta } from "./BundleBuyNowCta";
|
|
18
18
|
// S-SBUNI-5 2026-05-13 — admin dynamic-rule editor (paired with BundleItemsPicker).
|
|
19
19
|
export { BundleDynamicRuleEditor } from "./BundleDynamicRuleEditor";
|
|
20
|
+
export { BundleCollage } from "./BundleCollage";
|
|
20
21
|
// S-SBUNI-4 2026-05-13 — multi-select product picker for bundle editor.
|
|
21
22
|
export { BundleItemsPicker, defaultBundleItemsFetch, } from "./BundleItemsPicker";
|
|
22
23
|
export { CategoryBundlesListing } from "./CategoryBundlesListing";
|