@mohasinac/appkit 2.7.26 → 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/classified/ClassifiedDetailView.js +2 -2
- package/dist/_internal/client/features/digital-code/CodeRevealPanel.js +2 -2
- 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 +55 -84
- package/dist/features/layout/BottomActions.js +43 -48
- package/dist/features/layout/NavbarWithSettings.d.ts +6 -0
- package/dist/features/layout/NavbarWithSettings.js +11 -0
- 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 +2 -2
|
@@ -25,6 +25,13 @@ function cn(...classes) {
|
|
|
25
25
|
function IconBtn({ onClick, disabled, ariaLabel, ariaCurrent, className, children, }) {
|
|
26
26
|
return (_jsx(Button, { type: "button", onClick: onClick, disabled: disabled, "aria-label": ariaLabel, "aria-current": ariaCurrent, className: cn("inline-flex items-center justify-center w-12 h-12 rounded-full bg-white/15 hover:bg-white/30 text-white border-0 active:scale-95 transition-colors disabled:opacity-30 disabled:cursor-not-allowed", className), children: children }));
|
|
27
27
|
}
|
|
28
|
+
// --- Controls bar sub-components ---------------------------------------------
|
|
29
|
+
function LightboxControlsBar({ items, index, zoom, isFullscreen, L, goPrev, goNext, zoomIn, zoomOut, resetZoom, toggleFullscreen, onClose, }) {
|
|
30
|
+
return (_jsxs(Div, { className: "flex items-center justify-center gap-1.5 px-4 py-3 bg-black/85 backdrop-blur-sm shrink-0 border-t border-white/10 flex-wrap", children: [items.length > 1 && (_jsx(IconBtn, { onClick: (e) => { e.stopPropagation(); goPrev(); }, ariaLabel: L.prevImage, children: _jsx(ChevronLeft, { className: "w-7 h-7" }) })), _jsx(IconBtn, { onClick: zoomOut, disabled: zoom <= MIN_ZOOM, ariaLabel: L.zoomOut, children: _jsx(ZoomOut, { className: "w-7 h-7" }) }), _jsxs(Span, { className: "text-white/70 w-14 text-center tabular-nums text-xs", children: [Math.round(zoom * 100), "%"] }), _jsx(IconBtn, { onClick: zoomIn, disabled: zoom >= MAX_ZOOM, ariaLabel: L.zoomIn, children: _jsx(ZoomIn, { className: "w-7 h-7" }) }), zoom > 1 && (_jsx(IconBtn, { onClick: resetZoom, ariaLabel: L.resetZoom, children: _jsx(RotateCcw, { className: "w-7 h-7" }) })), _jsx(Span, { className: "w-px h-6 bg-white/15 mx-1 shrink-0", "aria-hidden": "true" }), _jsxs(Span, { className: "text-white/60 w-12 text-center tabular-nums text-xs", children: [index + 1, " / ", items.length] }), _jsx(Span, { className: "w-px h-6 bg-white/15 mx-1 shrink-0", "aria-hidden": "true" }), _jsx(IconBtn, { onClick: toggleFullscreen, ariaLabel: isFullscreen ? L.exitFullscreen : L.enterFullscreen, children: isFullscreen ? _jsx(Minimize, { className: "w-6 h-6" }) : _jsx(Maximize, { className: "w-6 h-6" }) }), _jsx(IconBtn, { onClick: onClose, ariaLabel: L.close, className: "hover:!bg-red-500/50", children: _jsx(X, { className: "w-7 h-7" }) }), items.length > 1 && (_jsx(IconBtn, { onClick: (e) => { e.stopPropagation(); goNext(); }, ariaLabel: L.nextImage, children: _jsx(ChevronRight, { className: "w-7 h-7" }) }))] }));
|
|
31
|
+
}
|
|
32
|
+
function LightboxThumbnailStrip({ items, index, onSelect, resetZoom, L, }) {
|
|
33
|
+
return (_jsx(Div, { className: "flex gap-2 px-4 py-3 bg-black/70 backdrop-blur-sm overflow-x-auto shrink-0 justify-center", style: { paddingBottom: "calc(0.75rem + env(safe-area-inset-bottom, 0px))" }, children: items.map((item, i) => (_jsx(Button, { type: "button", onClick: () => { onSelect(i); resetZoom(); }, "aria-label": L.imageThumbnail(i + 1), "aria-current": i === index ? "true" : undefined, className: cn("relative shrink-0 w-14 h-14 p-0 overflow-hidden rounded-lg border-2 transition-all active:scale-95", i === index ? "border-white opacity-100" : "border-transparent opacity-40 hover:opacity-75"), children: _jsx(MediaImage, { src: item.src, alt: item.alt, size: "thumbnail" }) }, i))) }));
|
|
34
|
+
}
|
|
28
35
|
// --- MediaLightbox ------------------------------------------------------------
|
|
29
36
|
export function MediaLightbox({ items, initialIndex = 0, isOpen, onClose, labels, }) {
|
|
30
37
|
const L = { ...DEFAULT_LABELS, ...labels };
|
|
@@ -226,19 +233,6 @@ export function MediaLightbox({ items, initialIndex = 0, isOpen, onClose, labels
|
|
|
226
233
|
transformOrigin: "center center",
|
|
227
234
|
transition: isDragging ? "none" : "transform 0.2s ease",
|
|
228
235
|
pointerEvents: "none",
|
|
229
|
-
}, children: _jsx(MediaImage, { src: current.src, alt: current.alt, size: "hero", objectFit: "contain" }) }) }),
|
|
230
|
-
e.stopPropagation();
|
|
231
|
-
goPrev();
|
|
232
|
-
}, ariaLabel: L.prevImage, children: _jsx(ChevronLeft, { className: "w-7 h-7" }) })), _jsx(IconBtn, { onClick: zoomOut, disabled: zoom <= MIN_ZOOM, ariaLabel: L.zoomOut, children: _jsx(ZoomOut, { className: "w-7 h-7" }) }), _jsxs(Span, { className: "text-white/70 w-14 text-center tabular-nums text-xs", children: [Math.round(zoom * 100), "%"] }), _jsx(IconBtn, { onClick: zoomIn, disabled: zoom >= MAX_ZOOM, ariaLabel: L.zoomIn, children: _jsx(ZoomIn, { className: "w-7 h-7" }) }), zoom > 1 && (_jsx(IconBtn, { onClick: resetZoom, ariaLabel: L.resetZoom, children: _jsx(RotateCcw, { className: "w-7 h-7" }) })), _jsx(Span, { className: "w-px h-6 bg-white/15 mx-1 shrink-0", "aria-hidden": "true" }), _jsxs(Span, { className: "text-white/60 w-12 text-center tabular-nums text-xs", children: [index + 1, " / ", items.length] }), _jsx(Span, { className: "w-px h-6 bg-white/15 mx-1 shrink-0", "aria-hidden": "true" }), _jsx(IconBtn, { onClick: toggleFullscreen, ariaLabel: isFullscreen ? L.exitFullscreen : L.enterFullscreen, children: isFullscreen ? (_jsx(Minimize, { className: "w-6 h-6" })) : (_jsx(Maximize, { className: "w-6 h-6" })) }), _jsx(IconBtn, { onClick: onClose, ariaLabel: L.close, className: "hover:!bg-red-500/50", children: _jsx(X, { className: "w-7 h-7" }) }), items.length > 1 && (_jsx(IconBtn, { onClick: (e) => {
|
|
233
|
-
e.stopPropagation();
|
|
234
|
-
goNext();
|
|
235
|
-
}, ariaLabel: L.nextImage, children: _jsx(ChevronRight, { className: "w-7 h-7" }) }))] }), items.length > 1 && (_jsx(Div, { className: "flex gap-2 px-4 py-3 bg-black/70 backdrop-blur-sm overflow-x-auto shrink-0 justify-center", style: {
|
|
236
|
-
paddingBottom: "calc(0.75rem + env(safe-area-inset-bottom, 0px))",
|
|
237
|
-
}, children: items.map((item, i) => (_jsx(Button, { type: "button", onClick: () => {
|
|
238
|
-
setIndex(i);
|
|
239
|
-
resetZoom();
|
|
240
|
-
}, "aria-label": L.imageThumbnail(i + 1), "aria-current": i === index ? "true" : undefined, className: cn("relative shrink-0 w-14 h-14 p-0 overflow-hidden rounded-lg border-2 transition-all active:scale-95", i === index
|
|
241
|
-
? "border-white opacity-100"
|
|
242
|
-
: "border-transparent opacity-40 hover:opacity-75"), children: _jsx(MediaImage, { src: item.src, alt: item.alt, size: "thumbnail" }) }, i))) }))] }));
|
|
236
|
+
}, children: _jsx(MediaImage, { src: current.src, alt: current.alt, size: "hero", objectFit: "contain" }) }) }), _jsx(LightboxControlsBar, { items: items, index: index, zoom: zoom, isFullscreen: isFullscreen, L: L, goPrev: goPrev, goNext: goNext, zoomIn: zoomIn, zoomOut: zoomOut, resetZoom: resetZoom, toggleFullscreen: toggleFullscreen, onClose: onClose }), items.length > 1 && (_jsx(LightboxThumbnailStrip, { items: items, index: index, onSelect: setIndex, resetZoom: resetZoom, L: L }))] }));
|
|
243
237
|
}
|
|
244
238
|
export default MediaLightbox;
|
|
@@ -23,14 +23,14 @@ export declare const mediaFieldSchema: z.ZodObject<{
|
|
|
23
23
|
}, "strip", z.ZodTypeAny, {
|
|
24
24
|
type: "video" | "image" | "file";
|
|
25
25
|
url: string;
|
|
26
|
-
source?: "
|
|
26
|
+
source?: "upload" | "youtube" | "external" | undefined;
|
|
27
27
|
alt?: string | undefined;
|
|
28
28
|
thumbnailUrl?: string | undefined;
|
|
29
29
|
youtubeId?: string | undefined;
|
|
30
30
|
}, {
|
|
31
31
|
type: "video" | "image" | "file";
|
|
32
32
|
url: string;
|
|
33
|
-
source?: "
|
|
33
|
+
source?: "upload" | "youtube" | "external" | undefined;
|
|
34
34
|
alt?: string | undefined;
|
|
35
35
|
thumbnailUrl?: string | undefined;
|
|
36
36
|
youtubeId?: string | undefined;
|
|
@@ -42,8 +42,8 @@ export default function CameraCapture({ mode, facingMode = "environment", onCapt
|
|
|
42
42
|
if (camera.error && onError)
|
|
43
43
|
onError(camera.error);
|
|
44
44
|
}, [camera.error, onError]);
|
|
45
|
-
const handleTakePhoto = () => {
|
|
46
|
-
const blob = camera.takePhoto();
|
|
45
|
+
const handleTakePhoto = async () => {
|
|
46
|
+
const blob = await camera.takePhoto();
|
|
47
47
|
if (blob)
|
|
48
48
|
onCapture(blob, "photo");
|
|
49
49
|
};
|
|
@@ -90,8 +90,10 @@ export function ImageUpload({ currentImage, onUpload, onChange, accept = "image/
|
|
|
90
90
|
}
|
|
91
91
|
};
|
|
92
92
|
const handleCameraCapture = (blob) => {
|
|
93
|
+
// blob.type can be empty from some camera implementations; hard-coded
|
|
94
|
+
// image/webp is always correct here since ImageUpload is photo-only.
|
|
93
95
|
const file = new File([blob], "camera-capture.webp", {
|
|
94
|
-
type: "image/webp",
|
|
96
|
+
type: blob.type || "image/webp",
|
|
95
97
|
});
|
|
96
98
|
setError("");
|
|
97
99
|
setProgress(0);
|
|
@@ -128,5 +130,5 @@ export function ImageUpload({ currentImage, onUpload, onChange, accept = "image/
|
|
|
128
130
|
}, variant: "secondary", size: "sm", children: tMediaEditor("editImage") })), _jsx(Button, { type: "button", onClick: () => fileInputRef.current?.click(), variant: "outline", size: "sm", children: tUpload("change") }), _jsx(Button, { type: "button", onClick: handleRemove, variant: "danger", size: "sm", children: tUpload("remove") })] }))] })) : (_jsxs(_Fragment, { children: [captureSource === "both" && isCameraSupported && (_jsxs(Row, { justify: "center", className: "gap-2 mb-3", children: [_jsx(Button, { type: "button", variant: captureMode === "file" ? "primary" : "outline", size: "sm", onClick: () => setCaptureMode("file"), children: t("switchToUpload") }), _jsx(Button, { type: "button", variant: captureMode === "camera" ? "primary" : "outline", size: "sm", onClick: () => setCaptureMode("camera"), children: t("switchToCamera") })] })), showCamera && isCameraSupported && (_jsx(CameraCapture, { mode: "photo", facingMode: "environment", onCapture: handleCameraCapture, onError: setError })), showCamera && !isCameraSupported && (_jsx(Button, { type: "button", onClick: () => mobileCaptureRef.current?.click(), disabled: uploading, variant: "ghost", className: "w-full aspect-[16/9] border-2 border-dashed border-zinc-200 dark:border-zinc-700 rounded-xl flex items-center justify-center flex-col text-zinc-500 dark:text-zinc-400", children: _jsx(Span, { className: "text-sm font-medium", children: t("switchToCamera") }) })), showFileInput && (_jsxs(Button, { type: "button", onClick: () => fileInputRef.current?.click(), disabled: uploading, variant: "ghost", className: "w-full aspect-[16/9] border-2 border-dashed border-zinc-200 dark:border-zinc-700 rounded-xl flex items-center justify-center flex-col text-zinc-500 dark:text-zinc-400", children: [_jsx("svg", { className: "w-12 h-12 mb-3", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" }) }), _jsx(Span, { className: "text-sm font-medium", children: uploading ? tUpload("uploading") : tUpload("clickToUpload") }), _jsxs(Span, { className: "text-xs mt-1", children: [accept
|
|
129
131
|
.split(",")
|
|
130
132
|
.map((t) => t.split("/")[1]?.toUpperCase() ?? t)
|
|
131
|
-
.join(", "), " ", "(max ", maxSizeMB, "MB)"] })] }))] })) }), _jsx("input", { ref: fileInputRef, type: "file", accept: accept, onChange: handleFileChange, className: "hidden", "aria-hidden": "true" }), showCamera && !isCameraSupported && (_jsx("input", { ref: mobileCaptureRef, type: "file", accept: "image/*", capture: "environment", onChange: handleFileChange, className: "hidden", "aria-hidden": "true" })), helperText && !error && (_jsx(Text, { variant: "secondary", size: "xs", children: helperText })), error && _jsx(Alert, { variant: "error", children: error }), uploading && (_jsxs(Row, { className: "gap-2", children: [_jsx(Spinner, { size: "sm" }), _jsx(Text, { size: "sm", variant: "secondary", children: tUpload("uploadingProgress", { progress }) })] })), cropPreviewUrl && (_jsx(ImageCropModal, { isOpen: showCropModal, imageUrl: cropPreviewUrl, onClose: handleCropClose, onSave: handleCropSave }))] }));
|
|
133
|
+
.join(", "), " ", "(max ", maxSizeMB, "MB)"] })] }))] })) }), _jsx("input", { ref: fileInputRef, type: "file", accept: accept, onChange: handleFileChange, className: "hidden", "aria-hidden": "true", "data-testid": "media-upload-input" }), showCamera && !isCameraSupported && (_jsx("input", { ref: mobileCaptureRef, type: "file", accept: "image/*", capture: "environment", onChange: handleFileChange, className: "hidden", "aria-hidden": "true", "data-testid": "media-upload-capture-input" })), helperText && !error && (_jsx(Text, { variant: "secondary", size: "xs", children: helperText })), error && _jsx(Alert, { variant: "error", children: error }), uploading && (_jsxs(Row, { className: "gap-2", children: [_jsx(Spinner, { size: "sm" }), _jsx(Text, { size: "sm", variant: "secondary", children: tUpload("uploadingProgress", { progress }) })] })), cropPreviewUrl && (_jsx(ImageCropModal, { isOpen: showCropModal, imageUrl: cropPreviewUrl, onClose: handleCropClose, onSave: handleCropSave }))] }));
|
|
132
134
|
}
|
|
@@ -64,6 +64,16 @@ function extractYouTubeId(input) {
|
|
|
64
64
|
return null;
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
|
+
// ─── Sub-components ───────────────────────────────────────────────────────────
|
|
68
|
+
function YoutubeTabPanel({ ytInput, setYtInput, ytError, setYtError, onApply, value, }) {
|
|
69
|
+
return (_jsxs(Div, { className: "space-y-2", children: [_jsxs(Row, { gap: "sm", children: [_jsx("input", { type: "text", value: ytInput, onChange: (e) => { setYtInput(e.target.value); setYtError(""); }, placeholder: "YouTube video ID or URL", className: "appkit-input flex-1" }), _jsx(Button, { type: "button", variant: "primary", size: "sm", onClick: onApply, children: "Apply" })] }), ytError && _jsx(Alert, { variant: "error", children: ytError }), value?.includes("youtube.com") && (_jsxs(Text, { size: "xs", variant: "secondary", children: ["YouTube embed: ", value] }))] }));
|
|
70
|
+
}
|
|
71
|
+
function ExternalUrlTabPanel({ extInput, setExtInput, extError, setExtError, onApply, }) {
|
|
72
|
+
return (_jsxs(Div, { className: "space-y-2", children: [_jsxs(Row, { gap: "sm", children: [_jsx("input", { type: "url", value: extInput, onChange: (e) => { setExtInput(e.target.value); setExtError(""); }, placeholder: "https://example.com/image.jpg", className: "appkit-input flex-1" }), _jsx(Button, { type: "button", variant: "primary", size: "sm", onClick: onApply, children: "Apply" })] }), extError && _jsx(Alert, { variant: "error", children: extError }), _jsx(Text, { size: "xs", variant: "secondary", children: "External URLs are stored as-is and are not watermarked." })] }));
|
|
73
|
+
}
|
|
74
|
+
function MediaPreviewPanel({ value, label, disabled, enableTrim, enableThumbnail, onEditVideo, onRemove, tUpload, tMediaEditor, }) {
|
|
75
|
+
return (_jsxs(Div, { className: "rounded-lg border border-zinc-200 dark:border-zinc-700 bg-zinc-50 dark:bg-zinc-900 p-3", children: [isVideo(value) ? (_jsx(Div, { className: "relative aspect-video overflow-hidden rounded-lg", children: _jsx(MediaVideo, { src: value, alt: label, controls: true, objectFit: "contain" }) })) : isImage(value) ? (_jsx(Div, { className: "relative aspect-video overflow-hidden rounded-lg", children: _jsx(MediaImage, { src: value, alt: label, size: "card", objectFit: "contain" }) })) : isPdf(value) ? (_jsxs(Row, { gap: "md", align: "center", children: [_jsx(Div, { "aria-hidden": true, className: "flex h-12 w-12 shrink-0 items-center justify-center rounded-lg bg-rose-100 text-rose-700 dark:bg-rose-900/40 dark:text-rose-300", children: _jsx("span", { className: "text-xs font-bold", children: "PDF" }) }), _jsx("a", { href: value, target: "_blank", rel: "noopener noreferrer", className: "text-sm font-medium text-blue-600 underline break-all dark:text-blue-400", children: filenameFromUrl(value) })] })) : (_jsx("a", { href: value, target: "_blank", rel: "noopener noreferrer", className: "text-sm underline break-all text-blue-600 dark:text-blue-400", children: filenameFromUrl(value) })), !disabled && (_jsxs(Row, { wrap: true, gap: "sm", className: "mt-2", children: [isVideo(value) && (enableTrim || enableThumbnail) && (_jsx(Button, { type: "button", onClick: () => onEditVideo(value), variant: "secondary", size: "sm", children: tMediaEditor("editVideo") })), _jsx(Button, { type: "button", onClick: onRemove, variant: "danger", size: "sm", children: tUpload("remove") })] }))] }));
|
|
76
|
+
}
|
|
67
77
|
export function MediaUploadField({ label, value, onChange, onChangeField, onUpload,
|
|
68
78
|
// SB-UNI-Z5 2026-05-13 — `kind` derives accept/maxSizeMB when not set
|
|
69
79
|
// explicitly; explicit props still win. "*" + 50 stay as the floor for
|
|
@@ -255,6 +265,15 @@ kind, accept: acceptProp, maxSizeMB: maxSizeMBProp, disabled = false, helperText
|
|
|
255
265
|
fileInputRef.current.value = "";
|
|
256
266
|
}
|
|
257
267
|
};
|
|
268
|
+
const handleEditVideo = (videoUrl) => {
|
|
269
|
+
setPendingVideoUrl(videoUrl);
|
|
270
|
+
if (enableTrim) {
|
|
271
|
+
setShowTrimModal(true);
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
setShowThumbnailModal(true);
|
|
275
|
+
}
|
|
276
|
+
};
|
|
258
277
|
const handleRemove = () => {
|
|
259
278
|
if (value && stagedUrlsRef.current.includes(value)) {
|
|
260
279
|
onAbortRef.current?.([value]);
|
|
@@ -266,7 +285,10 @@ kind, accept: acceptProp, maxSizeMB: maxSizeMBProp, disabled = false, helperText
|
|
|
266
285
|
};
|
|
267
286
|
const handleCameraCapture = async (blob, type) => {
|
|
268
287
|
const ext = type === "video" ? "webm" : "webp";
|
|
269
|
-
|
|
288
|
+
// blob.type can be empty with some MediaRecorder implementations; fall back
|
|
289
|
+
// to the canonical MIME for the capture type so the server MIME check passes.
|
|
290
|
+
const mimeType = blob.type || (type === "video" ? "video/webm" : "image/webp");
|
|
291
|
+
const file = new File([blob], `camera-capture.${ext}`, { type: mimeType });
|
|
270
292
|
setError(null);
|
|
271
293
|
setIsLoading(true);
|
|
272
294
|
try {
|
|
@@ -284,11 +306,5 @@ kind, accept: acceptProp, maxSizeMB: maxSizeMBProp, disabled = false, helperText
|
|
|
284
306
|
return (_jsxs(Div, { className: "space-y-2", children: [_jsx(Label, { className: "block text-sm font-medium text-zinc-600 dark:text-zinc-400", children: label }), hasAlternateSources && !disabled && (_jsx(Row, { gap: "none", className: "appkit-media-upload__source-tabs", children: ["upload", ...(showYoutube ? ["youtube"] : []), ...(showExternal ? ["external"] : [])].map((tab) => (_jsx("button", { type: "button", onClick: () => setSourceTab(tab), className: [
|
|
285
307
|
"appkit-media-upload__source-tab",
|
|
286
308
|
sourceTab === tab ? "appkit-media-upload__source-tab--active" : "",
|
|
287
|
-
].filter(Boolean).join(" "), children: tab === "upload" ? "Upload" : tab === "youtube" ? "YouTube" : "External URL" }, tab))) })), hasAlternateSources && sourceTab === "youtube" && !disabled && (
|
|
288
|
-
setPendingVideoUrl(value);
|
|
289
|
-
if (enableTrim)
|
|
290
|
-
setShowTrimModal(true);
|
|
291
|
-
else
|
|
292
|
-
setShowThumbnailModal(true);
|
|
293
|
-
}, variant: "secondary", size: "sm", children: tMediaEditor("editVideo") })), _jsx(Button, { type: "button", onClick: handleRemove, variant: "danger", size: "sm", children: tUpload("remove") })] }))] })), !disabled && !isLoading && (!hasAlternateSources || sourceTab === "upload") && (_jsxs(_Fragment, { children: [captureSource === "both" && isCameraSupported && (_jsxs(Row, { className: "gap-2", children: [_jsx(Button, { type: "button", variant: inputMode === "file" ? "primary" : "outline", size: "sm", onClick: () => setInputMode("file"), children: t("switchToUpload") }), _jsx(Button, { type: "button", variant: inputMode === "camera" ? "primary" : "outline", size: "sm", onClick: () => setInputMode("camera"), children: t("switchToCamera") })] })), showCamera && isCameraSupported && (_jsx(CameraCapture, { mode: captureMode, facingMode: "environment", onCapture: handleCameraCapture, onError: (msg) => setError(msg) })), showCamera && !isCameraSupported && (_jsx(Button, { type: "button", onClick: () => mobileCaptureRef.current?.click(), variant: "ghost", className: "w-full py-3 border-2 border-dashed border-zinc-200 dark:border-zinc-700 rounded-xl text-sm text-zinc-500 dark:text-zinc-400", children: t("switchToCamera") })), showFileInput && (_jsx(Button, { type: "button", onClick: () => fileInputRef.current?.click(), variant: "ghost", className: "w-full py-3 border-2 border-dashed border-zinc-200 dark:border-zinc-700 rounded-xl text-sm text-zinc-500 dark:text-zinc-400", children: value ? tUpload("replaceFile") : tUpload("chooseFile") }))] })), isLoading && (_jsxs(Row, { className: "gap-2", children: [_jsx(Spinner, { size: "sm" }), _jsx(Text, { size: "sm", variant: "secondary", children: tUpload("uploading") })] })), _jsx("input", { ref: fileInputRef, type: "file", accept: accept, onChange: handleFileChange, className: "hidden", "aria-hidden": "true" }), showCamera && !isCameraSupported && (_jsx("input", { ref: mobileCaptureRef, type: "file", accept: captureModeAccept, capture: "environment", onChange: handleFileChange, className: "hidden", "aria-hidden": "true" })), helperText && !error && (_jsx(Text, { variant: "secondary", size: "xs", children: helperText })), error && _jsx(Alert, { variant: "error", children: error }), pendingVideoUrl && (_jsx(VideoTrimModal, { isOpen: showTrimModal, videoUrl: pendingVideoUrl, onClose: handleTrimClose, onSave: handleTrimSave })), pendingVideoUrl && (_jsx(VideoThumbnailSelector, { isOpen: showThumbnailModal, videoUrl: pendingVideoUrl, onClose: handleThumbnailClose, onSelect: handleThumbnailSelect, onUpload: onUpload }))] }));
|
|
309
|
+
].filter(Boolean).join(" "), children: tab === "upload" ? "Upload" : tab === "youtube" ? "YouTube" : "External URL" }, tab))) })), hasAlternateSources && sourceTab === "youtube" && !disabled && (_jsx(YoutubeTabPanel, { ytInput: ytInput, setYtInput: setYtInput, ytError: ytError, setYtError: setYtError, onApply: handleYtApply, value: value })), hasAlternateSources && sourceTab === "external" && !disabled && (_jsx(ExternalUrlTabPanel, { extInput: extInput, setExtInput: setExtInput, extError: extError, setExtError: setExtError, onApply: handleExtApply })), (!hasAlternateSources || sourceTab === "upload") && value && !isLoading && (_jsx(MediaPreviewPanel, { value: value, label: label, disabled: disabled, enableTrim: enableTrim, enableThumbnail: enableThumbnail, onEditVideo: handleEditVideo, onRemove: handleRemove, tUpload: tUpload, tMediaEditor: tMediaEditor })), !disabled && !isLoading && (!hasAlternateSources || sourceTab === "upload") && (_jsxs(_Fragment, { children: [captureSource === "both" && isCameraSupported && (_jsxs(Row, { className: "gap-2", children: [_jsx(Button, { type: "button", variant: inputMode === "file" ? "primary" : "outline", size: "sm", onClick: () => setInputMode("file"), children: t("switchToUpload") }), _jsx(Button, { type: "button", variant: inputMode === "camera" ? "primary" : "outline", size: "sm", onClick: () => setInputMode("camera"), children: t("switchToCamera") })] })), showCamera && isCameraSupported && (_jsx(CameraCapture, { mode: captureMode, facingMode: "environment", onCapture: handleCameraCapture, onError: (msg) => setError(msg) })), showCamera && !isCameraSupported && (_jsx(Button, { type: "button", onClick: () => mobileCaptureRef.current?.click(), variant: "ghost", className: "w-full py-3 border-2 border-dashed border-zinc-200 dark:border-zinc-700 rounded-xl text-sm text-zinc-500 dark:text-zinc-400", children: t("switchToCamera") })), showFileInput && (_jsx(Button, { type: "button", onClick: () => fileInputRef.current?.click(), variant: "ghost", className: "w-full py-3 border-2 border-dashed border-zinc-200 dark:border-zinc-700 rounded-xl text-sm text-zinc-500 dark:text-zinc-400", children: value ? tUpload("replaceFile") : tUpload("chooseFile") }))] })), isLoading && (_jsxs(Row, { className: "gap-2", children: [_jsx(Spinner, { size: "sm" }), _jsx(Text, { size: "sm", variant: "secondary", children: tUpload("uploading") })] })), _jsx("input", { ref: fileInputRef, type: "file", accept: accept, onChange: handleFileChange, className: "hidden", "aria-hidden": "true", "data-testid": "media-upload-input" }), showCamera && !isCameraSupported && (_jsx("input", { ref: mobileCaptureRef, type: "file", accept: captureModeAccept, capture: "environment", onChange: handleFileChange, className: "hidden", "aria-hidden": "true", "data-testid": "media-upload-capture-input" })), helperText && !error && (_jsx(Text, { variant: "secondary", size: "xs", children: helperText })), error && _jsx(Alert, { variant: "error", children: error }), pendingVideoUrl && (_jsx(VideoTrimModal, { isOpen: showTrimModal, videoUrl: pendingVideoUrl, onClose: handleTrimClose, onSave: handleTrimSave })), pendingVideoUrl && (_jsx(VideoThumbnailSelector, { isOpen: showThumbnailModal, videoUrl: pendingVideoUrl, onClose: handleThumbnailClose, onSelect: handleThumbnailSelect, onUpload: onUpload }))] }));
|
|
294
310
|
}
|
|
@@ -100,5 +100,5 @@ export function MediaUploadList({ label, value, onChange, onUpload, accept = "im
|
|
|
100
100
|
unstageUrl(target.url);
|
|
101
101
|
}
|
|
102
102
|
};
|
|
103
|
-
return (_jsxs(Div, { className: "space-y-2", children: [_jsx(Label, { className: "block text-sm font-medium text-zinc-600 dark:text-zinc-400", children: label }), value.length > 0 && (_jsx(Div, { className: "grid grid-cols-1 sm:grid-cols-2 gap-3", children: value.map((item, index) => (_jsxs(Div, { className: "rounded-lg border border-zinc-200 dark:border-zinc-700 bg-zinc-50 dark:bg-zinc-900 p-3", children: [item.type === "video" ? (_jsx(Div, { className: "relative aspect-video overflow-hidden rounded-lg", children: _jsx(MediaVideo, { src: item.url, alt: item.alt || `Media ${index + 1}`, controls: true }) })) : item.type === "image" ? (_jsx(Div, { className: "relative aspect-video overflow-hidden rounded-lg", children: _jsx(MediaImage, { src: item.url, alt: item.alt || `Media ${index + 1}`, size: "card", objectFit: "contain" }) })) : (_jsx("a", { href: item.url, target: "_blank", rel: "noopener noreferrer", className: "text-sm underline break-all text-blue-600 dark:text-blue-400", children: fileNameFromUrl(item.url) })), !disabled && (_jsx(Div, { className: "mt-2", children: _jsx(Button, { type: "button", size: "sm", variant: "danger", onClick: () => removeAt(index), children: "Remove" }) }))] }, `${item.url}-${index}`))) })), !disabled && value.length < maxItems && (_jsx(Button, { type: "button", variant: "ghost", className: "w-full py-3 border-2 border-dashed border-zinc-200 dark:border-zinc-700 rounded-xl text-sm text-zinc-500 dark:text-zinc-400", onClick: () => inputRef.current?.click(), disabled: isLoading, children: isLoading ? "Uploading..." : "Add media" })), isLoading && (_jsxs(Div, { className: "flex items-center gap-2", children: [_jsx(Spinner, { size: "sm" }), _jsx(Text, { size: "sm", variant: "secondary", children: "Uploading" })] })), _jsx("input", { ref: inputRef, type: "file", multiple: true, accept: accept, onChange: handleFileChange, className: "hidden", "aria-hidden": "true" }), helperText && !error && (_jsx(Text, { variant: "secondary", size: "xs", children: helperText })), error && _jsx(Alert, { variant: "error", children: error })] }));
|
|
103
|
+
return (_jsxs(Div, { className: "space-y-2", children: [_jsx(Label, { className: "block text-sm font-medium text-zinc-600 dark:text-zinc-400", children: label }), value.length > 0 && (_jsx(Div, { className: "grid grid-cols-1 sm:grid-cols-2 gap-3", children: value.map((item, index) => (_jsxs(Div, { className: "rounded-lg border border-zinc-200 dark:border-zinc-700 bg-zinc-50 dark:bg-zinc-900 p-3", children: [item.type === "video" ? (_jsx(Div, { className: "relative aspect-video overflow-hidden rounded-lg", children: _jsx(MediaVideo, { src: item.url, alt: item.alt || `Media ${index + 1}`, controls: true }) })) : item.type === "image" ? (_jsx(Div, { className: "relative aspect-video overflow-hidden rounded-lg", children: _jsx(MediaImage, { src: item.url, alt: item.alt || `Media ${index + 1}`, size: "card", objectFit: "contain" }) })) : (_jsx("a", { href: item.url, target: "_blank", rel: "noopener noreferrer", className: "text-sm underline break-all text-blue-600 dark:text-blue-400", children: fileNameFromUrl(item.url) })), !disabled && (_jsx(Div, { className: "mt-2", children: _jsx(Button, { type: "button", size: "sm", variant: "danger", onClick: () => removeAt(index), children: "Remove" }) }))] }, `${item.url}-${index}`))) })), !disabled && value.length < maxItems && (_jsx(Button, { type: "button", variant: "ghost", className: "w-full py-3 border-2 border-dashed border-zinc-200 dark:border-zinc-700 rounded-xl text-sm text-zinc-500 dark:text-zinc-400", onClick: () => inputRef.current?.click(), disabled: isLoading, children: isLoading ? "Uploading..." : "Add media" })), isLoading && (_jsxs(Div, { className: "flex items-center gap-2", children: [_jsx(Spinner, { size: "sm" }), _jsx(Text, { size: "sm", variant: "secondary", children: "Uploading" })] })), _jsx("input", { ref: inputRef, type: "file", multiple: true, accept: accept, onChange: handleFileChange, className: "hidden", "aria-hidden": "true", "data-testid": "media-upload-list-input" }), helperText && !error && (_jsx(Text, { variant: "secondary", size: "xs", children: helperText })), error && _jsx(Alert, { variant: "error", children: error })] }));
|
|
104
104
|
}
|
|
@@ -8,11 +8,12 @@ import { NotFoundError, AuthorizationError, ValidationError, } from "../../../er
|
|
|
8
8
|
import { serverLogger } from "../../../monitoring";
|
|
9
9
|
import { getProviders } from "../../../contracts";
|
|
10
10
|
import { orderRepository } from "../repository/orders.repository";
|
|
11
|
+
const ERR_ORDER_NOT_FOUND = "Order not found";
|
|
11
12
|
const CANCELLABLE_STATUSES = ["pending", "confirmed"];
|
|
12
13
|
export async function cancelOrderForUser(userId, orderId, reason) {
|
|
13
14
|
const order = await orderRepository.findById(orderId);
|
|
14
15
|
if (!order)
|
|
15
|
-
throw new NotFoundError(
|
|
16
|
+
throw new NotFoundError(ERR_ORDER_NOT_FOUND);
|
|
16
17
|
if (order.userId !== userId)
|
|
17
18
|
throw new AuthorizationError("You are not authorised to cancel this order");
|
|
18
19
|
if (!CANCELLABLE_STATUSES.includes(order.status)) {
|
|
@@ -27,9 +28,9 @@ export async function listOrdersForUser(userId) {
|
|
|
27
28
|
export async function getOrderByIdForUser(userId, orderId) {
|
|
28
29
|
const order = await orderRepository.findById(orderId);
|
|
29
30
|
if (!order)
|
|
30
|
-
throw new NotFoundError(
|
|
31
|
+
throw new NotFoundError(ERR_ORDER_NOT_FOUND);
|
|
31
32
|
if (order.userId !== userId)
|
|
32
|
-
throw new NotFoundError(
|
|
33
|
+
throw new NotFoundError(ERR_ORDER_NOT_FOUND);
|
|
33
34
|
return order;
|
|
34
35
|
}
|
|
35
36
|
/**
|
|
@@ -6,6 +6,7 @@ import { BaseListingCard, Caption, Span, StatusBadge, Text, TextLink, Button, }
|
|
|
6
6
|
import { formatCurrency, formatDate } from "../../../utils";
|
|
7
7
|
import { OrderStatusValues } from "../schemas";
|
|
8
8
|
import { useLongPress } from "../../../react/hooks/useLongPress";
|
|
9
|
+
const CLS_ACTION_BUTTON = "min-w-0 flex-1 px-2 text-xs";
|
|
9
10
|
const STATUS_MAP = {
|
|
10
11
|
pending: "pending",
|
|
11
12
|
confirmed: "info",
|
|
@@ -49,5 +50,5 @@ export function MarketplaceOrderCard({ order, links, labels, className = "", var
|
|
|
49
50
|
const itemCount = order.items?.length ?? 0;
|
|
50
51
|
return (_jsxs("div", { className: `group relative overflow-hidden rounded-xl border border-zinc-200 bg-white transition-all duration-200 hover:shadow-md dark:border-slate-800 dark:bg-slate-900 ${isSelected ? "ring-2 ring-primary-500 dark:ring-primary-400" : ""} ${className}`, onMouseDown: onSelect && !isSelected ? longPress.onMouseDown : undefined, onMouseUp: onSelect && !isSelected ? longPress.onMouseUp : undefined, onMouseLeave: onSelect && !isSelected ? longPress.onMouseLeave : undefined, onTouchStart: onSelect && !isSelected ? longPress.onTouchStart : undefined, onTouchEnd: onSelect && !isSelected ? longPress.onTouchEnd : undefined, "data-section": "marketplaceordercard-div-411", children: [onSelect && (_jsx(BaseListingCard.Checkbox, { selected: isSelected, onSelect: handleSelect, label: isSelected ? "Deselect order" : "Select order", position: "top-2 left-2", className: selectable || isSelected ? "opacity-100" : "opacity-0 group-hover:opacity-100 transition-opacity" })), _jsxs("div", { className: `flex gap-4 p-4 ${isListVariant ? "flex-row items-center justify-between" : "flex-col"}`, "data-section": "marketplaceordercard-div-412", children: [_jsxs("div", { className: `flex items-start gap-3 ${selectable ? "pl-8" : ""} ${isListVariant ? "min-w-0 flex-1" : ""}`, "data-section": "marketplaceordercard-div-413", children: [_jsx("div", { className: "flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-lg bg-zinc-100 dark:bg-slate-800", "data-section": "marketplaceordercard-div-414", children: _jsx(Package, { className: "h-5 w-5 text-zinc-500 dark:text-slate-400" }) }), _jsxs("div", { className: "min-w-0 space-y-1", "data-section": "marketplaceordercard-div-415", children: [_jsx(TextLink, { href: detailHref, className: "leading-tight", children: _jsx(Text, { weight: "semibold", className: "truncate", children: order.productTitle ?? shortId }) }), _jsxs(Caption, { className: "font-mono text-xs", children: [mergedLabels.orderNumber, " ", shortId] }), _jsxs(Caption, { className: "text-xs", children: [mergedLabels.placedOn, " ", formatDate(order.orderDate)] }), itemCount > 1 && (_jsxs(Caption, { className: "text-xs", children: [mergedLabels.items, ": ", _jsx(Span, { weight: "medium", children: itemCount })] }))] })] }), _jsxs("div", { className: `flex ${isListVariant ? "flex-shrink-0 items-center gap-4" : "flex-col gap-3"}`, "data-section": "marketplaceordercard-div-416", children: [_jsxs("div", { className: `flex ${isListVariant ? "items-center gap-4" : "flex-wrap items-center justify-between gap-3"}`, "data-section": "marketplaceordercard-div-417", children: [_jsx(StatusBadge, { status: status, label: statusLabel }), _jsx(Text, { weight: "semibold", className: "tabular-nums", children: formatCurrency(order.totalPrice, order.currency) })] }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", "data-section": "marketplaceordercard-div-418", children: [(isShipped || isDelivered) &&
|
|
51
52
|
trackHref &&
|
|
52
|
-
order.trackingNumber && (_jsx(Button, { variant: "ghost", size: "sm", className:
|
|
53
|
+
order.trackingNumber && (_jsx(Button, { variant: "ghost", size: "sm", className: CLS_ACTION_BUTTON, onClick: () => navigate(trackHref), children: mergedLabels.trackOrder })), isDelivered && reviewHref && (_jsx(Button, { variant: "outline", size: "sm", className: CLS_ACTION_BUTTON, onClick: () => navigate(reviewHref), children: mergedLabels.writeReview })), _jsx(Button, { variant: "secondary", size: "sm", className: CLS_ACTION_BUTTON, onClick: () => navigate(detailHref), children: mergedLabels.viewOrder })] })] })] })] }));
|
|
53
54
|
}
|
|
@@ -34,12 +34,15 @@ export const ORDER_FILTER_KEYS = {
|
|
|
34
34
|
TABLE_KEYS.DATE_TO,
|
|
35
35
|
],
|
|
36
36
|
};
|
|
37
|
+
const LABEL_AMOUNT_HIGH_TO_LOW = "Amount: High to Low";
|
|
38
|
+
const LABEL_AMOUNT_LOW_TO_HIGH = "Amount: Low to High";
|
|
39
|
+
const LABEL_ORDER_DATE_NEWEST = "Order Date: Newest";
|
|
37
40
|
export const ORDER_ADMIN_SORT_OPTIONS = [
|
|
38
41
|
{ value: sortBy(ORDER_FIELDS.CREATED_AT), label: "Newest First" },
|
|
39
42
|
{ value: sortBy(ORDER_FIELDS.CREATED_AT, "ASC"), label: "Oldest First" },
|
|
40
|
-
{ value: sortBy(ORDER_FIELDS.TOTAL_PRICE), label:
|
|
41
|
-
{ value: sortBy(ORDER_FIELDS.TOTAL_PRICE, "ASC"), label:
|
|
42
|
-
{ value: sortBy(ORDER_FIELDS.ORDER_DATE), label:
|
|
43
|
+
{ value: sortBy(ORDER_FIELDS.TOTAL_PRICE), label: LABEL_AMOUNT_HIGH_TO_LOW },
|
|
44
|
+
{ value: sortBy(ORDER_FIELDS.TOTAL_PRICE, "ASC"), label: LABEL_AMOUNT_LOW_TO_HIGH },
|
|
45
|
+
{ value: sortBy(ORDER_FIELDS.ORDER_DATE), label: LABEL_ORDER_DATE_NEWEST },
|
|
43
46
|
{ value: sortBy(ORDER_FIELDS.ORDER_DATE, "ASC"), label: "Order Date: Oldest" },
|
|
44
47
|
{ value: sortBy(ORDER_FIELDS.USER_NAME, "ASC"), label: "Customer A–Z" },
|
|
45
48
|
{ value: sortBy(ORDER_FIELDS.PRODUCT_TITLE, "ASC"), label: "Product A–Z" },
|
|
@@ -47,17 +50,17 @@ export const ORDER_ADMIN_SORT_OPTIONS = [
|
|
|
47
50
|
export const ORDER_SELLER_SORT_OPTIONS = [
|
|
48
51
|
{ value: sortBy(ORDER_FIELDS.CREATED_AT), label: "Newest First" },
|
|
49
52
|
{ value: sortBy(ORDER_FIELDS.CREATED_AT, "ASC"), label: "Oldest First" },
|
|
50
|
-
{ value: sortBy(ORDER_FIELDS.TOTAL_PRICE), label:
|
|
51
|
-
{ value: sortBy(ORDER_FIELDS.TOTAL_PRICE, "ASC"), label:
|
|
52
|
-
{ value: sortBy(ORDER_FIELDS.ORDER_DATE), label:
|
|
53
|
+
{ value: sortBy(ORDER_FIELDS.TOTAL_PRICE), label: LABEL_AMOUNT_HIGH_TO_LOW },
|
|
54
|
+
{ value: sortBy(ORDER_FIELDS.TOTAL_PRICE, "ASC"), label: LABEL_AMOUNT_LOW_TO_HIGH },
|
|
55
|
+
{ value: sortBy(ORDER_FIELDS.ORDER_DATE), label: LABEL_ORDER_DATE_NEWEST },
|
|
53
56
|
{ value: sortBy(ORDER_FIELDS.USER_NAME, "ASC"), label: "Customer A–Z" },
|
|
54
57
|
];
|
|
55
58
|
export const ORDER_USER_SORT_OPTIONS = [
|
|
56
59
|
{ value: sortBy(ORDER_FIELDS.CREATED_AT), label: "Newest First" },
|
|
57
60
|
{ value: sortBy(ORDER_FIELDS.CREATED_AT, "ASC"), label: "Oldest First" },
|
|
58
|
-
{ value: sortBy(ORDER_FIELDS.TOTAL_PRICE), label:
|
|
59
|
-
{ value: sortBy(ORDER_FIELDS.TOTAL_PRICE, "ASC"), label:
|
|
60
|
-
{ value: sortBy(ORDER_FIELDS.ORDER_DATE), label:
|
|
61
|
+
{ value: sortBy(ORDER_FIELDS.TOTAL_PRICE), label: LABEL_AMOUNT_HIGH_TO_LOW },
|
|
62
|
+
{ value: sortBy(ORDER_FIELDS.TOTAL_PRICE, "ASC"), label: LABEL_AMOUNT_LOW_TO_HIGH },
|
|
63
|
+
{ value: sortBy(ORDER_FIELDS.ORDER_DATE), label: LABEL_ORDER_DATE_NEWEST },
|
|
61
64
|
{ value: sortBy(ORDER_FIELDS.ORDER_DATE, "ASC"), label: "Order Date: Oldest" },
|
|
62
65
|
];
|
|
63
66
|
export function getOrderFilterKeys(variant) {
|
|
@@ -12,18 +12,18 @@ export declare const orderItemSchema: z.ZodObject<{
|
|
|
12
12
|
attributes: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
13
13
|
}, "strip", z.ZodTypeAny, {
|
|
14
14
|
title: string;
|
|
15
|
+
price: number;
|
|
15
16
|
productId: string;
|
|
16
17
|
quantity: number;
|
|
17
|
-
price: number;
|
|
18
18
|
currency?: string | undefined;
|
|
19
19
|
storeId?: string | undefined;
|
|
20
20
|
image?: string | undefined;
|
|
21
21
|
attributes?: Record<string, string> | undefined;
|
|
22
22
|
}, {
|
|
23
23
|
title: string;
|
|
24
|
+
price: number;
|
|
24
25
|
productId: string;
|
|
25
26
|
quantity: number;
|
|
26
|
-
price: number;
|
|
27
27
|
currency?: string | undefined;
|
|
28
28
|
storeId?: string | undefined;
|
|
29
29
|
image?: string | undefined;
|
|
@@ -72,18 +72,18 @@ export declare const orderSchema: z.ZodObject<{
|
|
|
72
72
|
attributes: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
73
73
|
}, "strip", z.ZodTypeAny, {
|
|
74
74
|
title: string;
|
|
75
|
+
price: number;
|
|
75
76
|
productId: string;
|
|
76
77
|
quantity: number;
|
|
77
|
-
price: number;
|
|
78
78
|
currency?: string | undefined;
|
|
79
79
|
storeId?: string | undefined;
|
|
80
80
|
image?: string | undefined;
|
|
81
81
|
attributes?: Record<string, string> | undefined;
|
|
82
82
|
}, {
|
|
83
83
|
title: string;
|
|
84
|
+
price: number;
|
|
84
85
|
productId: string;
|
|
85
86
|
quantity: number;
|
|
86
|
-
price: number;
|
|
87
87
|
currency?: string | undefined;
|
|
88
88
|
storeId?: string | undefined;
|
|
89
89
|
image?: string | undefined;
|
|
@@ -127,9 +127,9 @@ export declare const orderSchema: z.ZodObject<{
|
|
|
127
127
|
address: Record<string, unknown>;
|
|
128
128
|
items: {
|
|
129
129
|
title: string;
|
|
130
|
+
price: number;
|
|
130
131
|
productId: string;
|
|
131
132
|
quantity: number;
|
|
132
|
-
price: number;
|
|
133
133
|
currency?: string | undefined;
|
|
134
134
|
storeId?: string | undefined;
|
|
135
135
|
image?: string | undefined;
|
|
@@ -162,9 +162,9 @@ export declare const orderSchema: z.ZodObject<{
|
|
|
162
162
|
address: Record<string, unknown>;
|
|
163
163
|
items: {
|
|
164
164
|
title: string;
|
|
165
|
+
price: number;
|
|
165
166
|
productId: string;
|
|
166
167
|
quantity: number;
|
|
167
|
-
price: number;
|
|
168
168
|
currency?: string | undefined;
|
|
169
169
|
storeId?: string | undefined;
|
|
170
170
|
image?: string | undefined;
|
|
@@ -10,15 +10,18 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { NextResponse } from "next/server.js";
|
|
12
12
|
import { getProviders } from "../../../../contracts";
|
|
13
|
+
const ERR_PRE_ORDER_ID_REQUIRED = "Pre-order ID is required";
|
|
14
|
+
const ERR_DB_NOT_CONFIGURED = "Database provider not configured";
|
|
15
|
+
const ERR_INTERNAL_SERVER_ERROR = "Internal server error";
|
|
13
16
|
export async function GET(request, { params }) {
|
|
14
17
|
try {
|
|
15
18
|
const { id } = await params;
|
|
16
19
|
if (!id) {
|
|
17
|
-
return NextResponse.json({ success: false, error:
|
|
20
|
+
return NextResponse.json({ success: false, error: ERR_PRE_ORDER_ID_REQUIRED }, { status: 400 });
|
|
18
21
|
}
|
|
19
22
|
const { db } = getProviders();
|
|
20
23
|
if (!db)
|
|
21
|
-
return NextResponse.json({ success: false, error:
|
|
24
|
+
return NextResponse.json({ success: false, error: ERR_DB_NOT_CONFIGURED }, { status: 503 });
|
|
22
25
|
const repo = db.getRepository("pre_orders");
|
|
23
26
|
const preOrder = await repo.findById(id);
|
|
24
27
|
if (!preOrder) {
|
|
@@ -32,7 +35,7 @@ export async function GET(request, { params }) {
|
|
|
32
35
|
catch (error) {
|
|
33
36
|
return NextResponse.json({
|
|
34
37
|
success: false,
|
|
35
|
-
error: error instanceof Error ? error.message :
|
|
38
|
+
error: error instanceof Error ? error.message : ERR_INTERNAL_SERVER_ERROR,
|
|
36
39
|
}, { status: 500 });
|
|
37
40
|
}
|
|
38
41
|
}
|
|
@@ -40,12 +43,12 @@ export async function PATCH(request, { params }) {
|
|
|
40
43
|
try {
|
|
41
44
|
const { id } = await params;
|
|
42
45
|
if (!id) {
|
|
43
|
-
return NextResponse.json({ success: false, error:
|
|
46
|
+
return NextResponse.json({ success: false, error: ERR_PRE_ORDER_ID_REQUIRED }, { status: 400 });
|
|
44
47
|
}
|
|
45
48
|
const body = await request.json();
|
|
46
49
|
const { db } = getProviders();
|
|
47
50
|
if (!db)
|
|
48
|
-
return NextResponse.json({ success: false, error:
|
|
51
|
+
return NextResponse.json({ success: false, error: ERR_DB_NOT_CONFIGURED }, { status: 503 });
|
|
49
52
|
const repo = db.getRepository("pre_orders");
|
|
50
53
|
const updated = await repo.update(id, body);
|
|
51
54
|
if (!updated) {
|
|
@@ -59,7 +62,7 @@ export async function PATCH(request, { params }) {
|
|
|
59
62
|
catch (error) {
|
|
60
63
|
return NextResponse.json({
|
|
61
64
|
success: false,
|
|
62
|
-
error: error instanceof Error ? error.message :
|
|
65
|
+
error: error instanceof Error ? error.message : ERR_INTERNAL_SERVER_ERROR,
|
|
63
66
|
}, { status: 500 });
|
|
64
67
|
}
|
|
65
68
|
}
|
|
@@ -67,11 +70,11 @@ export async function DELETE(request, { params }) {
|
|
|
67
70
|
try {
|
|
68
71
|
const { id } = await params;
|
|
69
72
|
if (!id) {
|
|
70
|
-
return NextResponse.json({ success: false, error:
|
|
73
|
+
return NextResponse.json({ success: false, error: ERR_PRE_ORDER_ID_REQUIRED }, { status: 400 });
|
|
71
74
|
}
|
|
72
75
|
const { db } = getProviders();
|
|
73
76
|
if (!db)
|
|
74
|
-
return NextResponse.json({ success: false, error:
|
|
77
|
+
return NextResponse.json({ success: false, error: ERR_DB_NOT_CONFIGURED }, { status: 503 });
|
|
75
78
|
const repo = db.getRepository("pre_orders");
|
|
76
79
|
await repo.delete(id);
|
|
77
80
|
return NextResponse.json({
|
|
@@ -82,7 +85,7 @@ export async function DELETE(request, { params }) {
|
|
|
82
85
|
catch (error) {
|
|
83
86
|
return NextResponse.json({
|
|
84
87
|
success: false,
|
|
85
|
-
error: error instanceof Error ? error.message :
|
|
88
|
+
error: error instanceof Error ? error.message : ERR_INTERNAL_SERVER_ERROR,
|
|
86
89
|
}, { status: 500 });
|
|
87
90
|
}
|
|
88
91
|
}
|
|
@@ -6,6 +6,7 @@ import { useLongPress } from "../../../react/hooks/useLongPress";
|
|
|
6
6
|
import { MediaImage } from "../../media";
|
|
7
7
|
import { useWishlistToggle } from "../../wishlist";
|
|
8
8
|
import { ROUTES } from "../../../next";
|
|
9
|
+
import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
|
|
9
10
|
import { formatCurrency } from "../../../utils";
|
|
10
11
|
import { getDefaultCurrency } from "../../../core/baseline-resolver";
|
|
11
12
|
import { BaseListingCard, Button, Div, RichText, Row, Span, Text, TextLink, } from "../../../ui";
|
|
@@ -15,10 +16,10 @@ import { PreorderBadge } from "./PreorderCard";
|
|
|
15
16
|
const DEFAULT_LABELS = {
|
|
16
17
|
preOrderBadge: "Pre-order",
|
|
17
18
|
featuredBadge: "Featured",
|
|
18
|
-
reserveNow: "
|
|
19
|
-
addToCart: "
|
|
20
|
-
addToWishlist: "
|
|
21
|
-
removeFromWishlist: "
|
|
19
|
+
reserveNow: ACTIONS.PRE_ORDER["reserve-now"].label,
|
|
20
|
+
addToCart: ACTIONS.PRODUCT["add-to-cart"].label,
|
|
21
|
+
addToWishlist: ACTIONS.PRODUCT["add-to-wishlist"].label,
|
|
22
|
+
removeFromWishlist: ACTIONS.PRODUCT["remove-from-wishlist"].label,
|
|
22
23
|
};
|
|
23
24
|
function resolveHref(product, href, hrefBuilder) {
|
|
24
25
|
if (href)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import Link from "next/link";
|
|
3
3
|
import { productRepository } from "../../../repositories";
|
|
4
|
+
const CLS_BREADCRUMB_LINK = "hover:text-primary-600 transition-colors";
|
|
4
5
|
import { ROUTES } from "../../../next";
|
|
5
6
|
import { getDefaultCurrency } from "../../../core/baseline-resolver";
|
|
6
7
|
import { formatCurrency } from "../../../utils/number.formatter";
|
|
@@ -30,6 +31,30 @@ const PRODUCTION_STATUS_LABELS = {
|
|
|
30
31
|
in_production: "In Production",
|
|
31
32
|
ready_to_ship: "Ready to Ship",
|
|
32
33
|
};
|
|
34
|
+
function PreOrderInfoSection({ title, productionStatus, maxPerUser, deliveryDate, featured, freeShipping, condition, isCancellable, category, categoryName, brand, brandSlug, productFeatures, features, descriptionHtml, safeSeller, storeHref, }) {
|
|
35
|
+
return (_jsxs(Stack, { gap: "md", children: [_jsxs(Div, { children: [_jsxs(Row, { gap: "xs", className: "mb-2 flex-wrap", children: [_jsx(Span, { className: "inline-block rounded-full bg-indigo-100 dark:bg-indigo-900/30 px-2.5 py-0.5 text-xs font-semibold text-indigo-700 dark:text-indigo-300", children: "Pre-Order" }), productionStatus && (_jsx(Span, { className: "inline-block rounded-full bg-zinc-100 dark:bg-zinc-800 px-2.5 py-0.5 text-xs font-medium text-zinc-600 dark:text-zinc-300", children: PRODUCTION_STATUS_LABELS[productionStatus] ?? productionStatus })), maxPerUser !== null && (_jsxs(Span, { className: "inline-block rounded-full bg-amber-100 dark:bg-amber-900/30 px-2.5 py-0.5 text-xs font-medium text-amber-800 dark:text-amber-200", children: ["Limit: ", maxPerUser, " per customer"] }))] }), _jsx(Heading, { level: 1, className: "text-xl font-bold leading-snug text-zinc-900 dark:text-zinc-50 sm:text-2xl", children: title })] }), deliveryDate && (_jsxs(Row, { align: "center", gap: "xs", className: "text-sm text-zinc-600 dark:text-zinc-400", children: [_jsx(Span, { children: "\uD83D\uDCC5" }), _jsx(Span, { children: "Estimated delivery:" }), _jsx(Span, { className: "font-medium", children: deliveryDate.toLocaleDateString(undefined, { year: "numeric", month: "long" }) })] })), _jsx(ProductFeatureBadges, { featured: featured, freeShipping: freeShipping, condition: condition ?? undefined, returnable: isCancellable, labels: {
|
|
36
|
+
featured: "Featured",
|
|
37
|
+
fasterDelivery: "Faster Delivery",
|
|
38
|
+
ratedSeller: "Rated Seller",
|
|
39
|
+
condition: "Condition",
|
|
40
|
+
conditionNew: "New",
|
|
41
|
+
conditionUsed: "Used",
|
|
42
|
+
conditionBroken: "For Parts",
|
|
43
|
+
conditionRefurbished: "Refurbished",
|
|
44
|
+
returnable: "Cancellable",
|
|
45
|
+
freeShipping: "Free Shipping",
|
|
46
|
+
codAvailable: "Cash on Delivery",
|
|
47
|
+
wishlistCount: (n) => `${n} wishlisted`,
|
|
48
|
+
categoryProductCount: (n, cat) => `${n} in ${cat}`,
|
|
49
|
+
} }), (categoryName || category || brand) && (_jsxs(Row, { gap: "sm", wrap: true, children: [category && (_jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(category)), className: "inline-flex items-center rounded-full border border-zinc-200 dark:border-zinc-700 bg-zinc-50 dark:bg-zinc-800/50 px-2.5 py-1 text-xs font-medium text-zinc-600 dark:text-zinc-300 transition-colors hover:border-primary-300 hover:bg-primary-50 hover:text-primary-700 dark:hover:border-primary-700/60 dark:hover:bg-primary-900/20 dark:hover:text-primary-400", children: categoryName || category })), !category && categoryName && (_jsx(Span, { className: "inline-flex items-center rounded-full border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-800/50 px-2.5 py-1 text-xs font-medium text-zinc-500 dark:text-zinc-400", children: categoryName })), brand && brandSlug && (_jsx(Link, { href: String(ROUTES.PUBLIC.BRAND_DETAIL(brandSlug)), className: "inline-flex items-center rounded-full border border-zinc-200 dark:border-zinc-700 bg-zinc-50 dark:bg-zinc-800/50 px-2.5 py-1 text-xs font-medium text-zinc-600 dark:text-zinc-300 transition-colors hover:border-primary-300 hover:bg-primary-50 hover:text-primary-700 dark:hover:border-primary-700/60 dark:hover:bg-primary-900/20 dark:hover:text-primary-400", children: brand })), brand && !brandSlug && (_jsx(Span, { className: "inline-flex items-center rounded-full border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-800/50 px-2.5 py-1 text-xs font-medium text-zinc-500 dark:text-zinc-400", children: brand }))] })), productFeatures && features.length > 0 && (_jsx(FeatureBadgeList, { productFeatureIds: features, features: productFeatures })), !productFeatures && features.length > 0 && (_jsxs(Div, { className: "rounded-xl border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900/60 px-4 py-3", children: [_jsx(Text, { className: "mb-2 text-xs font-semibold uppercase tracking-wide text-zinc-500 dark:text-zinc-400", children: "About this product" }), _jsx("ul", { className: "space-y-1.5", children: features.map((f, i) => (_jsxs("li", { className: "flex items-start gap-2 text-sm text-zinc-700 dark:text-zinc-300", children: [_jsx(Span, { className: "mt-0.5 flex-shrink-0 text-primary-500", children: "\u2022" }), f] }, i))) })] })), descriptionHtml && (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "text-sm leading-relaxed text-zinc-600 dark:text-zinc-400 line-clamp-4" })), safeSeller && (_jsx(Div, { className: "rounded-xl border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900/60 p-3", children: _jsxs(Row, { justify: "between", align: "center", children: [_jsxs(Div, { children: [_jsx(Text, { className: "text-[10px] uppercase tracking-wide text-zinc-400 dark:text-zinc-500 mb-0.5", children: "Sold by" }), _jsx(Text, { className: "text-sm font-semibold text-zinc-800 dark:text-zinc-200", children: safeSeller })] }), storeHref && (_jsx(Link, { href: storeHref, className: "shrink-0 rounded-lg bg-primary/10 dark:bg-primary/20 px-3 py-1.5 text-xs font-semibold text-primary-700 dark:text-primary-300 hover:bg-primary/20 dark:hover:bg-primary/30 transition-colors", children: "Visit Store \u2192" }))] }) }))] }));
|
|
50
|
+
}
|
|
51
|
+
function PreOrderBuyBarPanel({ reserveTarget, reservedCount, progressPct, productId, price, currency, depositAmount, depositPercent, isCancellable, tags, onReserveNow, }) {
|
|
52
|
+
return (_jsxs(Div, { id: "pre-order-buy-bar", className: "rounded-xl border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900/60 p-5 space-y-4", children: [reserveTarget > 0 && (_jsxs(Div, { className: "space-y-2", children: [_jsxs(Row, { justify: "between", align: "center", children: [_jsxs(Text, { className: "text-xs text-zinc-500", children: [reservedCount, " of ", reserveTarget, " reserved"] }), _jsxs(Span, { className: "text-xs font-semibold text-primary-600 dark:text-primary-400", children: [progressPct, "%"] })] }), _jsx(Div, { className: "h-2 w-full overflow-hidden rounded-full bg-zinc-200 dark:bg-zinc-700", children: _jsx(Div, { className: "h-full rounded-full bg-primary transition-all", style: { width: `${progressPct}%` } }) })] })), onReserveNow ? (_jsx(PreOrderActionsClient, { productId: productId, price: price, currency: currency, depositAmount: depositAmount, depositPercent: depositPercent, isCancellable: isCancellable, tags: tags, onReserveNow: onReserveNow })) : (_jsxs(_Fragment, { children: [price !== null && (_jsxs(Div, { children: [_jsx(Text, { className: "text-2xl font-bold text-zinc-900 dark:text-zinc-50", children: formatCurrency(price, currency) }), depositAmount !== null && (_jsxs(Text, { className: "mt-0.5 text-xs text-zinc-500", children: ["Reserve with ", formatCurrency(depositAmount, currency), depositPercent !== null ? ` (${depositPercent}% deposit)` : ""] }))] })), _jsxs(Stack, { gap: "sm", children: [_jsx(Button, { variant: "primary", size: "md", className: "w-full", children: "Reserve Now" }), isCancellable && (_jsx(Text, { className: "text-center text-xs text-zinc-500 dark:text-zinc-400", children: "\u2713 Free cancellation before production" }))] }), tags.length > 0 && (_jsx(Div, { className: "border-t border-zinc-200 dark:border-zinc-700 pt-4", children: _jsx(Row, { wrap: true, gap: "xs", children: tags.map((tag) => (_jsx(Span, { className: "rounded-full bg-zinc-100 dark:bg-zinc-800 px-2.5 py-1 text-xs text-zinc-600 dark:text-zinc-300", children: tag }, tag))) }) })), _jsx(Div, { className: "border-t border-zinc-200 dark:border-zinc-700 pt-4", children: _jsx(Row, { wrap: true, gap: "sm", className: "justify-center text-center", children: [
|
|
53
|
+
{ icon: "🔒", label: "Secure\nPayment" },
|
|
54
|
+
{ icon: "📅", label: "Guaranteed\nDelivery" },
|
|
55
|
+
{ icon: "↩", label: "Free\nCancellation" },
|
|
56
|
+
].map(({ icon, label }) => (_jsxs(Div, { className: "flex flex-col items-center gap-1 text-xs text-zinc-500 dark:text-zinc-400 min-w-[60px]", children: [_jsx(Span, { className: "text-base", children: icon }), _jsx(Span, { className: "whitespace-pre-line leading-tight", children: label })] }, label))) }) })] }))] }));
|
|
57
|
+
}
|
|
33
58
|
export async function PreOrderDetailPageView({ id, initialPreOrder, onReserveNow, productFeatures }) {
|
|
34
59
|
const product = initialPreOrder !== undefined
|
|
35
60
|
? (initialPreOrder ?? undefined)
|
|
@@ -114,21 +139,7 @@ export async function PreOrderDetailPageView({ id, initialPreOrder, onReserveNow
|
|
|
114
139
|
price: typeof p.price === "number" ? p.price : undefined,
|
|
115
140
|
storeId: typeof p.storeId === "string" ? p.storeId : undefined,
|
|
116
141
|
storeName: typeof p.storeName === "string" ? p.storeName : undefined,
|
|
117
|
-
} }), _jsxs(Container, { size: "xl", className: "px-4 py-6", children: [_jsxs("div", { className: "mb-4 flex items-center justify-between flex-wrap gap-2", children: [_jsxs("nav", { "aria-label": "Breadcrumb", className: "flex items-center gap-1.5 text-xs text-zinc-500 dark:text-zinc-400 flex-wrap", children: [_jsx(Link, { href: String(ROUTES.HOME), className:
|
|
118
|
-
featured: "Featured",
|
|
119
|
-
fasterDelivery: "Faster Delivery",
|
|
120
|
-
ratedSeller: "Rated Seller",
|
|
121
|
-
condition: "Condition",
|
|
122
|
-
conditionNew: "New",
|
|
123
|
-
conditionUsed: "Used",
|
|
124
|
-
conditionBroken: "For Parts",
|
|
125
|
-
conditionRefurbished: "Refurbished",
|
|
126
|
-
returnable: "Cancellable",
|
|
127
|
-
freeShipping: "Free Shipping",
|
|
128
|
-
codAvailable: "Cash on Delivery",
|
|
129
|
-
wishlistCount: (n) => `${n} wishlisted`,
|
|
130
|
-
categoryProductCount: (n, cat) => `${n} in ${cat}`,
|
|
131
|
-
} }), (categoryName || category || brand) && (_jsxs(Row, { gap: "sm", wrap: true, children: [category && (_jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(category)), className: "inline-flex items-center rounded-full border border-zinc-200 dark:border-zinc-700 bg-zinc-50 dark:bg-zinc-800/50 px-2.5 py-1 text-xs font-medium text-zinc-600 dark:text-zinc-300 transition-colors hover:border-primary-300 hover:bg-primary-50 hover:text-primary-700 dark:hover:border-primary-700/60 dark:hover:bg-primary-900/20 dark:hover:text-primary-400", children: categoryName || category })), !category && categoryName && (_jsx(Span, { className: "inline-flex items-center rounded-full border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-800/50 px-2.5 py-1 text-xs font-medium text-zinc-500 dark:text-zinc-400", children: categoryName })), brand && brandSlug && (_jsx(Link, { href: String(ROUTES.PUBLIC.BRAND_DETAIL(brandSlug)), className: "inline-flex items-center rounded-full border border-zinc-200 dark:border-zinc-700 bg-zinc-50 dark:bg-zinc-800/50 px-2.5 py-1 text-xs font-medium text-zinc-600 dark:text-zinc-300 transition-colors hover:border-primary-300 hover:bg-primary-50 hover:text-primary-700 dark:hover:border-primary-700/60 dark:hover:bg-primary-900/20 dark:hover:text-primary-400", children: brand })), brand && !brandSlug && (_jsx(Span, { className: "inline-flex items-center rounded-full border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-800/50 px-2.5 py-1 text-xs font-medium text-zinc-500 dark:text-zinc-400", children: brand }))] })), productFeatures && features.length > 0 && (_jsx(FeatureBadgeList, { productFeatureIds: features, features: productFeatures })), !productFeatures && features.length > 0 && (_jsxs(Div, { className: "rounded-xl border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900/60 px-4 py-3", children: [_jsx(Text, { className: "mb-2 text-xs font-semibold uppercase tracking-wide text-zinc-500 dark:text-zinc-400", children: "About this product" }), _jsx("ul", { className: "space-y-1.5", children: features.map((f, i) => (_jsxs("li", { className: "flex items-start gap-2 text-sm text-zinc-700 dark:text-zinc-300", children: [_jsx(Span, { className: "mt-0.5 flex-shrink-0 text-primary-500", children: "\u2022" }), f] }, i))) })] })), descriptionHtml && (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "text-sm leading-relaxed text-zinc-600 dark:text-zinc-400 line-clamp-4" })), safeSeller && (_jsx(Div, { className: "rounded-xl border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900/60 p-3", children: _jsxs(Row, { justify: "between", align: "center", children: [_jsxs(Div, { children: [_jsx(Text, { className: "text-[10px] uppercase tracking-wide text-zinc-400 dark:text-zinc-500 mb-0.5", children: "Sold by" }), _jsx(Text, { className: "text-sm font-semibold text-zinc-800 dark:text-zinc-200", children: safeSeller })] }), storeHref && (_jsx(Link, { href: storeHref, className: "shrink-0 rounded-lg bg-primary/10 dark:bg-primary/20 px-3 py-1.5 text-xs font-semibold text-primary-700 dark:text-primary-300 hover:bg-primary/20 dark:hover:bg-primary/30 transition-colors", children: "Visit Store \u2192" }))] }) }))] })), renderSublistingSection: sublistingCategoryId
|
|
142
|
+
} }), _jsxs(Container, { size: "xl", className: "px-4 py-6", children: [_jsxs("div", { className: "mb-4 flex items-center justify-between flex-wrap gap-2", children: [_jsxs("nav", { "aria-label": "Breadcrumb", className: "flex items-center gap-1.5 text-xs text-zinc-500 dark:text-zinc-400 flex-wrap", children: [_jsx(Link, { href: String(ROUTES.HOME), className: CLS_BREADCRUMB_LINK, children: "Home" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.PRE_ORDERS), className: CLS_BREADCRUMB_LINK, children: "Pre-Orders" }), category && (_jsxs(_Fragment, { children: [_jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(category)), className: CLS_BREADCRUMB_LINK, children: categoryName || category })] })), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Span, { className: "text-zinc-700 dark:text-zinc-300 truncate max-w-[200px]", children: title })] }), _jsx(ShareButton, { title: title })] }), _jsx(PreOrderDetailView, { renderGallery: () => (_jsx(ProductGalleryClient, { images: images, productName: title })), renderInfo: () => (_jsx(PreOrderInfoSection, { title: title, productionStatus: productionStatus, maxPerUser: maxPerUser, deliveryDate: deliveryDate, featured: featured, freeShipping: freeShipping, condition: condition, isCancellable: isCancellable, category: category, categoryName: categoryName, brand: brand, brandSlug: brandSlug, productFeatures: productFeatures, features: features, descriptionHtml: descriptionHtml, safeSeller: safeSeller, storeHref: storeHref })), renderSublistingSection: sublistingCategoryId
|
|
132
143
|
? () => (_jsx(SublistingCarouselSection, { sublistingCategoryId: sublistingCategoryId, currentListingId: String(product.id) }))
|
|
133
144
|
: undefined, renderGroupSection: groupId
|
|
134
145
|
? () => (_jsx(ShowGroupSection, { groupId: groupId, currentSlug: String(p.slug ?? product.id), isParent: isGroupParent, groupTitle: groupTitle }))
|
|
@@ -136,9 +147,5 @@ export async function PreOrderDetailPageView({ id, initialPreOrder, onReserveNow
|
|
|
136
147
|
id: s.id,
|
|
137
148
|
label: s.title,
|
|
138
149
|
content: _jsx(CustomSectionTabContent, { section: s }),
|
|
139
|
-
})) })), renderBuyBar: () => (
|
|
140
|
-
{ icon: "🔒", label: "Secure\nPayment" },
|
|
141
|
-
{ icon: "📅", label: "Guaranteed\nDelivery" },
|
|
142
|
-
{ icon: "↩", label: "Free\nCancellation" },
|
|
143
|
-
].map(({ icon, label }) => (_jsxs(Div, { className: "flex flex-col items-center gap-1 text-xs text-zinc-500 dark:text-zinc-400 min-w-[60px]", children: [_jsx(Span, { className: "text-base", children: icon }), _jsx(Span, { className: "whitespace-pre-line leading-tight", children: label })] }, label))) }) })] }))] })) }), _jsxs(BuyBar, { children: [price !== null && (_jsx(Span, { className: "mr-auto text-sm font-bold text-zinc-900 dark:text-zinc-50", children: formatCurrency(price, currency) })), _jsx("a", { href: "#pre-order-buy-bar", className: "appkit-button appkit-button--primary appkit-button--sm flex-1", children: _jsx("span", { className: "appkit-button__content", children: "Reserve Now" }) })] })] })] }));
|
|
150
|
+
})) })), renderBuyBar: () => (_jsx(PreOrderBuyBarPanel, { reserveTarget: reserveTarget, reservedCount: reservedCount, progressPct: progressPct, productId: String(product.id), price: price, currency: currency, depositAmount: depositAmount, depositPercent: depositPercent, isCancellable: isCancellable, tags: tags, onReserveNow: onReserveNow })) }), _jsxs(BuyBar, { children: [price !== null && (_jsx(Span, { className: "mr-auto text-sm font-bold text-zinc-900 dark:text-zinc-50", children: formatCurrency(price, currency) })), _jsx("a", { href: "#pre-order-buy-bar", className: "appkit-button appkit-button--primary appkit-button--sm flex-1", children: _jsx("span", { className: "appkit-button__content", children: "Reserve Now" }) })] })] })] }));
|
|
144
151
|
}
|