@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
|
@@ -31,14 +31,14 @@ export declare const blogPostSchema: z.ZodObject<{
|
|
|
31
31
|
}, "strip", z.ZodTypeAny, {
|
|
32
32
|
type: "video" | "image" | "file";
|
|
33
33
|
url: string;
|
|
34
|
-
source?: "
|
|
34
|
+
source?: "upload" | "youtube" | "external" | undefined;
|
|
35
35
|
alt?: string | undefined;
|
|
36
36
|
thumbnailUrl?: string | undefined;
|
|
37
37
|
youtubeId?: string | undefined;
|
|
38
38
|
}, {
|
|
39
39
|
type: "video" | "image" | "file";
|
|
40
40
|
url: string;
|
|
41
|
-
source?: "
|
|
41
|
+
source?: "upload" | "youtube" | "external" | undefined;
|
|
42
42
|
alt?: string | undefined;
|
|
43
43
|
thumbnailUrl?: string | undefined;
|
|
44
44
|
youtubeId?: string | undefined;
|
|
@@ -48,7 +48,7 @@ export declare const blogPostSchema: z.ZodObject<{
|
|
|
48
48
|
}, string>]>>>, {
|
|
49
49
|
type: "video" | "image" | "file";
|
|
50
50
|
url: string;
|
|
51
|
-
source?: "
|
|
51
|
+
source?: "upload" | "youtube" | "external" | undefined;
|
|
52
52
|
alt?: string | undefined;
|
|
53
53
|
thumbnailUrl?: string | undefined;
|
|
54
54
|
youtubeId?: string | undefined;
|
|
@@ -58,7 +58,7 @@ export declare const blogPostSchema: z.ZodObject<{
|
|
|
58
58
|
} | null, string | {
|
|
59
59
|
type: "video" | "image" | "file";
|
|
60
60
|
url: string;
|
|
61
|
-
source?: "
|
|
61
|
+
source?: "upload" | "youtube" | "external" | undefined;
|
|
62
62
|
alt?: string | undefined;
|
|
63
63
|
thumbnailUrl?: string | undefined;
|
|
64
64
|
youtubeId?: string | undefined;
|
|
@@ -73,14 +73,14 @@ export declare const blogPostSchema: z.ZodObject<{
|
|
|
73
73
|
}, "strip", z.ZodTypeAny, {
|
|
74
74
|
type: "video" | "image" | "file";
|
|
75
75
|
url: string;
|
|
76
|
-
source?: "
|
|
76
|
+
source?: "upload" | "youtube" | "external" | undefined;
|
|
77
77
|
alt?: string | undefined;
|
|
78
78
|
thumbnailUrl?: string | undefined;
|
|
79
79
|
youtubeId?: string | undefined;
|
|
80
80
|
}, {
|
|
81
81
|
type: "video" | "image" | "file";
|
|
82
82
|
url: string;
|
|
83
|
-
source?: "
|
|
83
|
+
source?: "upload" | "youtube" | "external" | undefined;
|
|
84
84
|
alt?: string | undefined;
|
|
85
85
|
thumbnailUrl?: string | undefined;
|
|
86
86
|
youtubeId?: string | undefined;
|
|
@@ -95,14 +95,14 @@ export declare const blogPostSchema: z.ZodObject<{
|
|
|
95
95
|
}, "strip", z.ZodTypeAny, {
|
|
96
96
|
type: "video" | "image" | "file";
|
|
97
97
|
url: string;
|
|
98
|
-
source?: "
|
|
98
|
+
source?: "upload" | "youtube" | "external" | undefined;
|
|
99
99
|
alt?: string | undefined;
|
|
100
100
|
thumbnailUrl?: string | undefined;
|
|
101
101
|
youtubeId?: string | undefined;
|
|
102
102
|
}, {
|
|
103
103
|
type: "video" | "image" | "file";
|
|
104
104
|
url: string;
|
|
105
|
-
source?: "
|
|
105
|
+
source?: "upload" | "youtube" | "external" | undefined;
|
|
106
106
|
alt?: string | undefined;
|
|
107
107
|
thumbnailUrl?: string | undefined;
|
|
108
108
|
youtubeId?: string | undefined;
|
|
@@ -130,7 +130,7 @@ export declare const blogPostSchema: z.ZodObject<{
|
|
|
130
130
|
coverImage: {
|
|
131
131
|
type: "video" | "image" | "file";
|
|
132
132
|
url: string;
|
|
133
|
-
source?: "
|
|
133
|
+
source?: "upload" | "youtube" | "external" | undefined;
|
|
134
134
|
alt?: string | undefined;
|
|
135
135
|
thumbnailUrl?: string | undefined;
|
|
136
136
|
youtubeId?: string | undefined;
|
|
@@ -141,7 +141,7 @@ export declare const blogPostSchema: z.ZodObject<{
|
|
|
141
141
|
contentImages: {
|
|
142
142
|
type: "video" | "image" | "file";
|
|
143
143
|
url: string;
|
|
144
|
-
source?: "
|
|
144
|
+
source?: "upload" | "youtube" | "external" | undefined;
|
|
145
145
|
alt?: string | undefined;
|
|
146
146
|
thumbnailUrl?: string | undefined;
|
|
147
147
|
youtubeId?: string | undefined;
|
|
@@ -149,7 +149,7 @@ export declare const blogPostSchema: z.ZodObject<{
|
|
|
149
149
|
additionalImages: {
|
|
150
150
|
type: "video" | "image" | "file";
|
|
151
151
|
url: string;
|
|
152
|
-
source?: "
|
|
152
|
+
source?: "upload" | "youtube" | "external" | undefined;
|
|
153
153
|
alt?: string | undefined;
|
|
154
154
|
thumbnailUrl?: string | undefined;
|
|
155
155
|
youtubeId?: string | undefined;
|
|
@@ -183,7 +183,7 @@ export declare const blogPostSchema: z.ZodObject<{
|
|
|
183
183
|
coverImage?: string | {
|
|
184
184
|
type: "video" | "image" | "file";
|
|
185
185
|
url: string;
|
|
186
|
-
source?: "
|
|
186
|
+
source?: "upload" | "youtube" | "external" | undefined;
|
|
187
187
|
alt?: string | undefined;
|
|
188
188
|
thumbnailUrl?: string | undefined;
|
|
189
189
|
youtubeId?: string | undefined;
|
|
@@ -196,7 +196,7 @@ export declare const blogPostSchema: z.ZodObject<{
|
|
|
196
196
|
contentImages?: {
|
|
197
197
|
type: "video" | "image" | "file";
|
|
198
198
|
url: string;
|
|
199
|
-
source?: "
|
|
199
|
+
source?: "upload" | "youtube" | "external" | undefined;
|
|
200
200
|
alt?: string | undefined;
|
|
201
201
|
thumbnailUrl?: string | undefined;
|
|
202
202
|
youtubeId?: string | undefined;
|
|
@@ -204,7 +204,7 @@ export declare const blogPostSchema: z.ZodObject<{
|
|
|
204
204
|
additionalImages?: {
|
|
205
205
|
type: "video" | "image" | "file";
|
|
206
206
|
url: string;
|
|
207
|
-
source?: "
|
|
207
|
+
source?: "upload" | "youtube" | "external" | undefined;
|
|
208
208
|
alt?: string | undefined;
|
|
209
209
|
thumbnailUrl?: string | undefined;
|
|
210
210
|
youtubeId?: string | undefined;
|
|
@@ -226,16 +226,16 @@ export declare const blogListParamsSchema: z.ZodObject<{
|
|
|
226
226
|
sort?: string | undefined;
|
|
227
227
|
perPage?: number | undefined;
|
|
228
228
|
q?: string | undefined;
|
|
229
|
-
page?: number | undefined;
|
|
230
229
|
category?: "news" | "tips" | "guides" | "updates" | "community" | undefined;
|
|
231
|
-
tags?: string | undefined;
|
|
232
230
|
featured?: boolean | undefined;
|
|
231
|
+
tags?: string | undefined;
|
|
232
|
+
page?: number | undefined;
|
|
233
233
|
}, {
|
|
234
234
|
sort?: string | undefined;
|
|
235
235
|
perPage?: number | undefined;
|
|
236
236
|
q?: string | undefined;
|
|
237
|
-
page?: number | undefined;
|
|
238
237
|
category?: "news" | "tips" | "guides" | "updates" | "community" | undefined;
|
|
239
|
-
tags?: string | undefined;
|
|
240
238
|
featured?: boolean | undefined;
|
|
239
|
+
tags?: string | undefined;
|
|
240
|
+
page?: number | undefined;
|
|
241
241
|
}>;
|
|
@@ -2,14 +2,15 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
import { Aside, Button, Div, Heading, Row, Span, Text } from "../../../ui";
|
|
3
3
|
import { formatCurrency } from "../../../utils/number.formatter";
|
|
4
4
|
import { THEME_CONSTANTS } from "../../../tokens";
|
|
5
|
+
import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
|
|
5
6
|
export function CartItemRow({ item, onQtyChange, onRemove, href, isOutOfStock = false }) {
|
|
6
7
|
return (_jsxs(Div, { className: `flex gap-4 rounded-xl border bg-white dark:bg-slate-900 p-4 transition-opacity ${isOutOfStock ? "border-neutral-200 dark:border-slate-700 opacity-60" : "border-neutral-200 dark:border-slate-700"}`, children: [_jsx(Div, { className: "h-20 w-20 flex-shrink-0 overflow-hidden rounded-lg bg-neutral-100 dark:bg-slate-800", children: item.meta.image && (_jsx(Div, { role: "img", "aria-label": item.meta.title, className: "h-full w-full bg-center bg-cover", style: { backgroundImage: `url(${item.meta.image})` } })) }), _jsxs(Div, { className: "flex flex-1 flex-col justify-between", children: [_jsxs(Div, { className: "flex items-start gap-1.5", children: [href ? (_jsx("a", { href: href, target: "_blank", rel: "noopener noreferrer", className: `font-medium text-neutral-900 dark:text-zinc-100 hover:underline underline-offset-2 ${THEME_CONSTANTS.utilities.textClamp2}`, children: item.meta.title })) : (_jsx(Text, { className: `font-medium text-neutral-900 dark:text-zinc-100 ${THEME_CONSTANTS.utilities.textClamp2}`, children: item.meta.title })), isOutOfStock && (_jsx(Span, { className: "flex-shrink-0 rounded bg-red-100 dark:bg-red-900/30 px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-[var(--appkit-color-error)]", children: "Out of Stock" }))] }), item.meta.attributes &&
|
|
7
8
|
Object.keys(item.meta.attributes).length > 0 && (_jsx(Text, { className: "text-xs text-neutral-500 dark:text-zinc-400", children: Object.entries(item.meta.attributes)
|
|
8
9
|
.map(([k, v]) => `${k}: ${v}`)
|
|
9
|
-
.join(", ") })), _jsxs(Row, { justify: "between", children: [_jsx(Text, { className: "font-semibold text-neutral-900 dark:text-zinc-100", children: formatCurrency(item.meta.price * item.quantity, item.meta.currency) }), onQtyChange && !isOutOfStock && (_jsxs(Row, { className: "gap-2", children: [_jsx(Button, { onClick: () => onQtyChange(item.id, item.quantity - 1), disabled: item.quantity <= 1, variant: "outline", size: "sm", className: "flex h-7 w-7 items-center justify-center rounded-full border border-neutral-200 dark:border-slate-600 text-sm disabled:opacity-40", children: "\u2212" }), _jsx(Span, { className: "min-w-[1.5rem] text-center text-sm", children: item.quantity }), _jsx(Button, { onClick: () => onQtyChange(item.id, item.quantity + 1), variant: "outline", size: "sm", className: "flex h-7 w-7 items-center justify-center rounded-full border border-neutral-200 dark:border-slate-600 text-sm", children: "+" })] })), isOutOfStock && (_jsxs(Span, { className: "text-xs text-neutral-400 dark:text-zinc-500", children: ["Qty: ", item.quantity] }))] })] }), onRemove && (_jsx(Button, { onClick: () => onRemove(item.id), variant: "ghost", size: "sm", "aria-label": "
|
|
10
|
+
.join(", ") })), _jsxs(Row, { justify: "between", children: [_jsx(Text, { className: "font-semibold text-neutral-900 dark:text-zinc-100", children: formatCurrency(item.meta.price * item.quantity, item.meta.currency) }), onQtyChange && !isOutOfStock && (_jsxs(Row, { className: "gap-2", children: [_jsx(Button, { onClick: () => onQtyChange(item.id, item.quantity - 1), disabled: item.quantity <= 1, variant: "outline", size: "sm", className: "flex h-7 w-7 items-center justify-center rounded-full border border-neutral-200 dark:border-slate-600 text-sm disabled:opacity-40", children: "\u2212" }), _jsx(Span, { className: "min-w-[1.5rem] text-center text-sm", children: item.quantity }), _jsx(Button, { onClick: () => onQtyChange(item.id, item.quantity + 1), variant: "outline", size: "sm", className: "flex h-7 w-7 items-center justify-center rounded-full border border-neutral-200 dark:border-slate-600 text-sm", children: "+" })] })), isOutOfStock && (_jsxs(Span, { className: "text-xs text-neutral-400 dark:text-zinc-500", children: ["Qty: ", item.quantity] }))] })] }), onRemove && (_jsx(Button, { onClick: () => onRemove(item.id), variant: "ghost", size: "sm", "aria-label": ACTIONS.CART["remove-item"].ariaLabel, className: "self-start text-neutral-400 dark:text-zinc-500 transition hover:text-red-500", children: "\u2715" }))] }));
|
|
10
11
|
}
|
|
11
12
|
export function CartDrawer({ isOpen, onClose, items, subtotal = 0, currency, isLoading, onQtyChange, onRemove, onCheckout, labels = {}, }) {
|
|
12
13
|
if (!isOpen)
|
|
13
14
|
return null;
|
|
14
|
-
return (_jsxs(_Fragment, { children: [_jsx(Div, { role: "presentation", className: "fixed inset-0 z-40 bg-black/40", onClick: onClose }), _jsxs(Aside, { className: "fixed inset-y-0 right-0 z-50 flex w-full max-w-sm flex-col bg-white dark:bg-slate-900 shadow-xl", children: [_jsxs(Row, { justify: "between", className: "border-b border-neutral-200 dark:border-slate-700 p-4", children: [_jsx(Heading, { level: 2, className: "text-lg font-semibold", children: labels.title ?? "Cart" }), _jsx(Button, { onClick: onClose, variant: "ghost", size: "sm", "aria-label": "Close cart", className: "text-neutral-500 dark:text-zinc-400 hover:text-neutral-900 dark:hover:text-zinc-100", children: "\u2715" })] }), _jsx(Div, { className: "flex-1 overflow-y-auto p-4 space-y-3", children: isLoading ? (_jsx(Div, { className: "flex justify-center py-12", children: _jsx(Div, { className: "h-8 w-8 animate-spin rounded-full border-2 border-neutral-300 dark:border-slate-600 border-t-neutral-800 dark:border-t-zinc-200" }) })) : items.length === 0 ? (_jsx(Text, { className: "py-12 text-center text-sm text-neutral-500 dark:text-zinc-400", children: labels.empty ?? "Your cart is empty" })) : (items.map((item) => (_jsx(CartItemRow, { item: item, onQtyChange: onQtyChange, onRemove: onRemove }, item.id)))) }), items.length > 0 && (_jsxs(Div, { className: "border-t border-neutral-200 dark:border-slate-700 p-4 space-y-4", children: [_jsxs(Row, { justify: "between", className: "text-sm", children: [_jsx(Span, { className: "text-neutral-600 dark:text-zinc-300", children: labels.subtotal ?? "Subtotal" }), _jsx(Span, { className: "font-semibold", children: formatCurrency(subtotal, currency) })] }), onCheckout && (_jsx(Button, { onClick: onCheckout, variant: "primary", className: "w-full rounded-xl bg-neutral-900 py-3 text-sm font-semibold text-white transition hover:bg-neutral-800", children: labels.checkout ?? "
|
|
15
|
+
return (_jsxs(_Fragment, { children: [_jsx(Div, { role: "presentation", className: "fixed inset-0 z-40 bg-black/40", onClick: onClose }), _jsxs(Aside, { className: "fixed inset-y-0 right-0 z-50 flex w-full max-w-sm flex-col bg-white dark:bg-slate-900 shadow-xl", children: [_jsxs(Row, { justify: "between", className: "border-b border-neutral-200 dark:border-slate-700 p-4", children: [_jsx(Heading, { level: 2, className: "text-lg font-semibold", children: labels.title ?? "Cart" }), _jsx(Button, { onClick: onClose, variant: "ghost", size: "sm", "aria-label": "Close cart", className: "text-neutral-500 dark:text-zinc-400 hover:text-neutral-900 dark:hover:text-zinc-100", children: "\u2715" })] }), _jsx(Div, { className: "flex-1 overflow-y-auto p-4 space-y-3", children: isLoading ? (_jsx(Div, { className: "flex justify-center py-12", children: _jsx(Div, { className: "h-8 w-8 animate-spin rounded-full border-2 border-neutral-300 dark:border-slate-600 border-t-neutral-800 dark:border-t-zinc-200" }) })) : items.length === 0 ? (_jsx(Text, { className: "py-12 text-center text-sm text-neutral-500 dark:text-zinc-400", children: labels.empty ?? "Your cart is empty" })) : (items.map((item) => (_jsx(CartItemRow, { item: item, onQtyChange: onQtyChange, onRemove: onRemove }, item.id)))) }), items.length > 0 && (_jsxs(Div, { className: "border-t border-neutral-200 dark:border-slate-700 p-4 space-y-4", children: [_jsxs(Row, { justify: "between", className: "text-sm", children: [_jsx(Span, { className: "text-neutral-600 dark:text-zinc-300", children: labels.subtotal ?? "Subtotal" }), _jsx(Span, { className: "font-semibold", children: formatCurrency(subtotal, currency) })] }), onCheckout && (_jsx(Button, { onClick: onCheckout, variant: "primary", className: "w-full rounded-xl bg-neutral-900 py-3 text-sm font-semibold text-white transition hover:bg-neutral-800", children: labels.checkout ?? ACTIONS.CART["checkout"].label }))] }))] })] }));
|
|
15
16
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from "crypto";
|
|
2
2
|
import { DatabaseError, NotFoundError } from "../../../errors";
|
|
3
|
+
const ERR_CART_ITEM_NOT_FOUND = "Cart item not found";
|
|
3
4
|
import { BaseRepository, prepareForFirestore, } from "../../../providers/db-firebase";
|
|
4
5
|
import { decryptPii } from "../../../security";
|
|
5
6
|
import { CART_COLLECTION, } from "../schemas";
|
|
@@ -120,7 +121,7 @@ export class CartRepository extends BaseRepository {
|
|
|
120
121
|
throw new NotFoundError("Cart not found");
|
|
121
122
|
const itemIndex = cart.items.findIndex((item) => item.itemId === itemId);
|
|
122
123
|
if (itemIndex < 0)
|
|
123
|
-
throw new NotFoundError(
|
|
124
|
+
throw new NotFoundError(ERR_CART_ITEM_NOT_FOUND);
|
|
124
125
|
const items = [...cart.items];
|
|
125
126
|
items[itemIndex] = {
|
|
126
127
|
...items[itemIndex],
|
|
@@ -152,7 +153,7 @@ export class CartRepository extends BaseRepository {
|
|
|
152
153
|
throw new NotFoundError("Cart not found");
|
|
153
154
|
const itemExists = cart.items.some((item) => item.itemId === itemId);
|
|
154
155
|
if (!itemExists)
|
|
155
|
-
throw new NotFoundError(
|
|
156
|
+
throw new NotFoundError(ERR_CART_ITEM_NOT_FOUND);
|
|
156
157
|
const items = cart.items.filter((item) => item.itemId !== itemId);
|
|
157
158
|
const updatedCart = {
|
|
158
159
|
...cart,
|
|
@@ -223,7 +224,7 @@ export class CartRepository extends BaseRepository {
|
|
|
223
224
|
throw new NotFoundError("Cart not found");
|
|
224
225
|
const itemIndex = cart.items.findIndex((item) => item.itemId === itemId);
|
|
225
226
|
if (itemIndex < 0)
|
|
226
|
-
throw new NotFoundError(
|
|
227
|
+
throw new NotFoundError(ERR_CART_ITEM_NOT_FOUND);
|
|
227
228
|
const items = [...cart.items];
|
|
228
229
|
items[itemIndex] = {
|
|
229
230
|
...items[itemIndex],
|
|
@@ -20,7 +20,7 @@ export interface CartOp {
|
|
|
20
20
|
export interface WishlistOp {
|
|
21
21
|
op: WishlistOpKind;
|
|
22
22
|
itemId: string;
|
|
23
|
-
type: "product" | "auction" | "preorder" | "category" | "store";
|
|
23
|
+
type: "product" | "auction" | "preorder" | "category" | "store" | "classified" | "digital-code" | "live";
|
|
24
24
|
title?: string;
|
|
25
25
|
image?: string;
|
|
26
26
|
ts: number;
|
|
@@ -12,6 +12,7 @@ import { NextResponse } from "next/server.js";
|
|
|
12
12
|
import { z } from "zod";
|
|
13
13
|
import { getProviders } from "../../../../contracts";
|
|
14
14
|
import { createRouteHandler } from "../../../../next";
|
|
15
|
+
const ERR_CATEGORY_NOT_FOUND = "Category not found";
|
|
15
16
|
// ---- GET /api/categories/[id] ------------------------------------------------
|
|
16
17
|
export async function GET(_request, context) {
|
|
17
18
|
try {
|
|
@@ -23,7 +24,7 @@ export async function GET(_request, context) {
|
|
|
23
24
|
const repo = db.getRepository("categories");
|
|
24
25
|
const category = await repo.findById(id);
|
|
25
26
|
if (!category) {
|
|
26
|
-
return NextResponse.json({ success: false, error:
|
|
27
|
+
return NextResponse.json({ success: false, error: ERR_CATEGORY_NOT_FOUND }, { status: 404 });
|
|
27
28
|
}
|
|
28
29
|
return NextResponse.json({ success: true, data: category });
|
|
29
30
|
}
|
|
@@ -65,7 +66,7 @@ export const categoryItemPATCH = createRouteHandler({
|
|
|
65
66
|
const repo = db.getRepository("categories");
|
|
66
67
|
const category = await repo.findById(id);
|
|
67
68
|
if (!category)
|
|
68
|
-
return NextResponse.json({ success: false, error:
|
|
69
|
+
return NextResponse.json({ success: false, error: ERR_CATEGORY_NOT_FOUND }, { status: 404 });
|
|
69
70
|
const updated = await repo.update(id, {
|
|
70
71
|
...body,
|
|
71
72
|
updatedAt: new Date().toISOString(),
|
|
@@ -87,7 +88,7 @@ export const categoryItemDELETE = createRouteHandler({
|
|
|
87
88
|
const repo = db.getRepository("categories");
|
|
88
89
|
const category = await repo.findById(id);
|
|
89
90
|
if (!category)
|
|
90
|
-
return NextResponse.json({ success: false, error:
|
|
91
|
+
return NextResponse.json({ success: false, error: ERR_CATEGORY_NOT_FOUND }, { status: 404 });
|
|
91
92
|
// Guard: refuse to delete if it has children
|
|
92
93
|
if (category.childrenIds?.length) {
|
|
93
94
|
return NextResponse.json({ success: false, error: "Cannot delete a category that has children" }, { status: 409 });
|
|
@@ -16,6 +16,7 @@ import { NextResponse } from "next/server.js";
|
|
|
16
16
|
import { z } from "zod";
|
|
17
17
|
import { getProviders } from "../../../contracts";
|
|
18
18
|
import { createRouteHandler } from "../../../next";
|
|
19
|
+
const CACHE_CONTROL_PUBLIC = "public, max-age=3600, s-maxage=3600, stale-while-revalidate=86400";
|
|
19
20
|
function buildTreeFromFlat(items, rootId) {
|
|
20
21
|
const byId = new Map(items.map((i) => [i.id, i]));
|
|
21
22
|
// Roots: tier === 0 or no parentIds
|
|
@@ -130,7 +131,7 @@ export async function GET(request) {
|
|
|
130
131
|
});
|
|
131
132
|
const items = result.data.sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
132
133
|
const res = NextResponse.json({ success: true, data: items });
|
|
133
|
-
res.headers.set("Cache-Control",
|
|
134
|
+
res.headers.set("Cache-Control", CACHE_CONTROL_PUBLIC);
|
|
134
135
|
return res;
|
|
135
136
|
}
|
|
136
137
|
// -- Tree mode (default or explicit ?tree=true) ----------------------------
|
|
@@ -143,7 +144,7 @@ export async function GET(request) {
|
|
|
143
144
|
if (tree === "true" || !flat) {
|
|
144
145
|
const treeNodes = buildTreeFromFlat(allResult.data, rootId);
|
|
145
146
|
const res = NextResponse.json({ success: true, data: treeNodes });
|
|
146
|
-
res.headers.set("Cache-Control",
|
|
147
|
+
res.headers.set("Cache-Control", CACHE_CONTROL_PUBLIC);
|
|
147
148
|
return res;
|
|
148
149
|
}
|
|
149
150
|
// Explicit ?flat=true on full set (non-filtered path, shouldn't reach here
|
|
@@ -152,7 +153,7 @@ export async function GET(request) {
|
|
|
152
153
|
success: true,
|
|
153
154
|
data: allResult.data.sort((a, b) => (a.order ?? 0) - (b.order ?? 0)),
|
|
154
155
|
});
|
|
155
|
-
res.headers.set("Cache-Control",
|
|
156
|
+
res.headers.set("Cache-Control", CACHE_CONTROL_PUBLIC);
|
|
156
157
|
return res;
|
|
157
158
|
}
|
|
158
159
|
catch (error) {
|
|
@@ -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 { BrandDetailTabs } from "./BrandDetailTabs";
|
|
7
7
|
export async function BrandDetailPageView({ slug, initialBrand }) {
|
|
8
8
|
const brand = (initialBrand ?? (await categoriesRepository
|
|
@@ -81,5 +81,5 @@ export async function BrandDetailPageView({ slug, initialBrand }) {
|
|
|
81
81
|
counts.preOrders +
|
|
82
82
|
counts.prizeDraws +
|
|
83
83
|
counts.bundles;
|
|
84
|
-
return (_jsxs(Main, { children: [_jsxs(
|
|
84
|
+
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" })] })), !hasCover && (_jsx("div", { className: "absolute inset-0 opacity-10", style: { backgroundColor: brandColor } })), _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.BRANDS ?? "/brands"), className: hasCover ? "text-white/70 hover:text-white transition-colors" : "text-zinc-500 hover:text-primary-600 transition-colors", children: "Brands" }), _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: brand?.name ?? slug })] }), _jsxs("div", { className: "flex items-center gap-4 mb-3", children: [brand?.display?.icon && (_jsx("span", { className: `text-5xl leading-none ${!hasCover ? "" : ""}`, children: brand.display.icon })), _jsxs(_Fragment, { children: [_jsx(Heading, { level: 1, className: `text-3xl md:text-4xl font-bold ${hasCover ? "text-white" : "text-zinc-900 dark:text-zinc-50"}`, children: brand?.name ?? slug }), brand?.description && typeof brand.description === "string" && !brand.description.startsWith("{") && (_jsx(Text, { className: `text-base max-w-2xl mt-1 ${hasCover ? "text-white/80" : "text-zinc-600 dark:text-zinc-400"}`, children: brand.description }))] })] }), _jsxs("div", { className: "flex flex-wrap gap-2 mt-3", children: [counts.products > 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: [counts.products.toLocaleString(), " ", counts.products === 1 ? "product" : "products"] })), counts.auctions > 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: [counts.auctions.toLocaleString(), " ", counts.auctions === 1 ? "auction" : "auctions"] })), counts.preOrders > 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: [counts.preOrders.toLocaleString(), " ", counts.preOrders === 1 ? "pre-order" : "pre-orders"] })), totalItems === 0 && (_jsx("span", { className: `text-sm ${hasCover ? "text-white/60" : "text-zinc-400"}`, children: "No items listed yet" }))] })] })] }), _jsx(Section, { className: "py-6", children: _jsx(Container, { size: "xl", children: brandName ? (_jsx(BrandDetailTabs, { brandName: brandName, initialProductsData: productsResult ?? undefined, initialBundles: brandBundles, counts: counts })) : (_jsx(Text, { className: "py-12 text-center text-sm text-zinc-500 dark:text-zinc-400", children: "Brand not found." })) }) })] }));
|
|
85
85
|
}
|
|
@@ -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";
|
|
@@ -30,7 +30,7 @@ export function BrandDetailTabs({ brandName, initialProductsData, initialBundles
|
|
|
30
30
|
return undefined;
|
|
31
31
|
}
|
|
32
32
|
};
|
|
33
|
-
return (_jsxs(
|
|
33
|
+
return (_jsxs(_Fragment, { children: [_jsx("div", { className: "flex border-b border-zinc-200 dark:border-slate-700 mb-6 overflow-x-auto", children: CATEGORY_PAGE_TABS.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
34
|
? "border-primary text-primary"
|
|
35
35
|
: "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
36
|
}
|
|
@@ -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
|
}
|