@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
|
@@ -1,22 +1,62 @@
|
|
|
1
|
+
"use client";
|
|
1
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* PrizeDrawCollage (SB4-B)
|
|
5
|
+
*
|
|
6
|
+
* Read-only display of a prize-draw's full prize pool. Each cell shows the
|
|
7
|
+
* primary image, item-number badge, and title. Items with `isWon === true`
|
|
8
|
+
* are dimmed under a diagonal overlay with a "Won" label so the public
|
|
9
|
+
* collage truthfully reflects what's still up for grabs.
|
|
10
|
+
*
|
|
11
|
+
* Clicking any cell opens a full-screen lightbox starting at that item and
|
|
12
|
+
* cycling circularly through all items in itemNumber order.
|
|
13
|
+
*
|
|
14
|
+
* Optional `highlightItemNumber` (passed by the reveal modal during animation)
|
|
15
|
+
* adds an emphasis ring to the cell that just won.
|
|
16
|
+
*/
|
|
17
|
+
import { useState } from "react";
|
|
2
18
|
import { Div, Text } from "../../../ui";
|
|
19
|
+
import { ImageLightbox } from "../../../ui/components/ImageLightbox";
|
|
20
|
+
function makePrizeItemClickHandler(it, idx, onItemClick, setLightboxIndex) {
|
|
21
|
+
return () => {
|
|
22
|
+
if (onItemClick) {
|
|
23
|
+
onItemClick(it);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
setLightboxIndex(idx);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/** Build the lightbox images array from sorted prize items. */
|
|
31
|
+
function toGalleryImages(items) {
|
|
32
|
+
return items.map((it) => ({
|
|
33
|
+
src: it.images?.[0] ?? "",
|
|
34
|
+
alt: it.title || `Prize #${it.itemNumber}`,
|
|
35
|
+
badge: `#${it.itemNumber}`,
|
|
36
|
+
caption: it.title || `Prize #${it.itemNumber}`,
|
|
37
|
+
sub: it.estimatedValue != null
|
|
38
|
+
? `est. ₹${(it.estimatedValue / 100).toLocaleString("en-IN")}`
|
|
39
|
+
: undefined,
|
|
40
|
+
}));
|
|
41
|
+
}
|
|
3
42
|
export function PrizeDrawCollage({ items, highlightItemNumber, onItemClick, wonLabel = "Won", hideWonState = false, }) {
|
|
43
|
+
const [lightboxIndex, setLightboxIndex] = useState(null);
|
|
4
44
|
if (!items.length) {
|
|
5
45
|
return (_jsx(Div, { className: "rounded border border-dashed border-[var(--appkit-color-border)] p-6 text-center", children: _jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: "No prizes configured yet." }) }));
|
|
6
46
|
}
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
47
|
+
const galleryImages = toGalleryImages(items);
|
|
48
|
+
return (_jsxs(_Fragment, { children: [_jsx(Div, { className: "grid grid-cols-2 gap-3 sm:grid-cols-3 md:grid-cols-4", children: items.map((it, idx) => {
|
|
49
|
+
const cover = it.images?.[0];
|
|
50
|
+
const isHighlight = highlightItemNumber === it.itemNumber;
|
|
51
|
+
return (_jsxs("button", { type: "button", onClick: makePrizeItemClickHandler(it, idx, onItemClick, setLightboxIndex), className: [
|
|
52
|
+
"group relative overflow-hidden rounded-lg border bg-[var(--appkit-color-surface)] text-left transition-transform",
|
|
53
|
+
"border-[var(--appkit-color-border)]",
|
|
54
|
+
isHighlight
|
|
55
|
+
? "ring-2 ring-offset-2 ring-[var(--appkit-color-primary)] scale-[1.02]"
|
|
56
|
+
: "hover:scale-[1.01]",
|
|
57
|
+
].join(" "), "aria-label": `View ${it.title || `prize #${it.itemNumber}`} in lightbox`, children: [_jsxs(Div, { className: "relative aspect-square w-full", children: [cover ? (
|
|
58
|
+
/* eslint-disable-next-line @next/next/no-img-element */
|
|
59
|
+
_jsx("img", { src: cover, alt: it.title || `Prize #${it.itemNumber}`, className: "absolute inset-0 h-full w-full object-cover", loading: "lazy" })) : (_jsx(Div, { className: "absolute inset-0 flex items-center justify-center bg-[var(--appkit-color-surface-muted)]", children: _jsx(Text, { className: "text-xs text-[var(--appkit-color-text-muted)]", children: "No image" }) })), _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: ["#", it.itemNumber] }), it.isWon && !hideWonState ? (_jsxs(_Fragment, { children: [_jsx(Div, { "aria-hidden": true, className: "absolute inset-0 bg-gradient-to-br from-black/60 via-black/30 to-black/60 mix-blend-multiply" }), _jsx(Div, { className: "absolute inset-0 flex items-center justify-center", children: _jsx(Text, { className: "rotate-[-12deg] rounded bg-red-600 px-3 py-1 text-xs font-bold uppercase tracking-wider text-white shadow", children: wonLabel }) })] })) : null] }), _jsxs(Div, { className: "p-2", children: [_jsx(Text, { className: "line-clamp-2 text-sm font-medium", children: it.title || `Prize #${it.itemNumber}` }), it.estimatedValue != null ? (_jsxs(Text, { className: "text-xs text-[var(--appkit-color-text-muted)]", children: ["est. \u20B9", (it.estimatedValue / 100).toLocaleString("en-IN")] })) : null] })] }, `collage-${it.itemNumber}`));
|
|
60
|
+
}) }), _jsx(ImageLightbox, { images: galleryImages, activeIndex: lightboxIndex, onClose: () => setLightboxIndex(null), onNavigate: (i) => setLightboxIndex(i), showThumbnails: true })] }));
|
|
21
61
|
}
|
|
22
62
|
export default PrizeDrawCollage;
|
|
@@ -14,6 +14,7 @@ import { PrizeDrawCollage } from "./PrizeDrawCollage";
|
|
|
14
14
|
import { PrizeDrawEntryActions } from "./PrizeDrawEntryActions";
|
|
15
15
|
import { HistoryTracker } from "../../history/components/HistoryTracker";
|
|
16
16
|
import { formatCurrency } from "../../../utils/number.formatter";
|
|
17
|
+
import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
|
|
17
18
|
function toDescriptionHtml(raw) {
|
|
18
19
|
if (!raw)
|
|
19
20
|
return "";
|
|
@@ -114,5 +115,5 @@ export async function PrizeDrawDetailPageView({ id, initialPrizeDraw, currentUse
|
|
|
114
115
|
price: pricePerEntry,
|
|
115
116
|
storeId: typeof p.storeId === "string" ? p.storeId : undefined,
|
|
116
117
|
storeName: 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: "hover:text-primary-600 transition-colors", children: "Home" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.PRIZE_DRAWS), className: "hover:text-primary-600 transition-colors", children: "Prize Draws" }), _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(PrizeDrawCollage, { items: items, hideWonState: true })), renderInfo: () => (_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-fuchsia-100 dark:bg-fuchsia-900/30 px-2.5 py-0.5 text-xs font-semibold text-fuchsia-700 dark:text-fuchsia-300", children: "Prize Draw" }), _jsx(Span, { className: `inline-block rounded-full px-2.5 py-0.5 text-xs font-semibold ${statusClass(revealStatus)}`, children: statusLabel(revealStatus) }), maxPerUser !== null && (_jsxs(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-700 dark:text-zinc-300", children: ["Limit: ", maxPerUser, " entries per customer"] })), maxPerUser !== null && userEntriesUsed !== null && (_jsxs(Span, { className: "inline-block rounded-full bg-fuchsia-100 dark:bg-fuchsia-900/30 px-2.5 py-0.5 text-xs font-semibold text-fuchsia-700 dark:text-fuchsia-300", children: ["You have used ", userEntriesUsed, "/", maxPerUser] }))] }), _jsx(Heading, { level: 1, className: "text-xl font-bold leading-snug text-zinc-900 dark:text-zinc-50 sm:text-2xl", children: title })] }), _jsx(Div, { className: "rounded-xl border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900/60 p-4", children: _jsxs(Stack, { gap: "sm", children: [_jsxs(Row, { justify: "between", align: "center", children: [_jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: "Entries" }), _jsxs(Text, { className: "text-sm font-semibold", children: [current, " / ", max, " (", remaining, " left)"] })] }), revealStart && (_jsxs(Row, { justify: "between", align: "center", children: [_jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: "Reveal window opens" }), _jsx(Text, { className: "text-sm font-medium", children: revealStart.toLocaleString() })] })), revealEnd && (_jsxs(Row, { justify: "between", align: "center", children: [_jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: "Reveal window closes" }), _jsx(Text, { className: "text-sm font-medium", children: revealEnd.toLocaleString() })] }))] }) }), 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" }))] }) }))] })), renderTabs: () => (_jsx(ProductTabsShell, { descriptionContent: descriptionHtml ? (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm sm:prose max-w-none dark:prose-invert", className: "text-zinc-700 dark:text-zinc-300" })) : undefined })), renderBuyBar: () => (_jsx(Div, { id: "prize-draw-buy-bar", className: "rounded-xl border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900/60 p-5", children: _jsx(PrizeDrawEntryActions, { productId: String(product.id), productSlug: slug, title: title, thumb: thumb, pricePerEntry: pricePerEntry, currency: currency, remainingEntries: remaining, revealStatus: revealStatus, prizeGithubFileUrl: githubUrl }) })) }), _jsxs(BuyBar, { children: [_jsx(Span, { className: "mr-auto text-sm font-bold text-zinc-900 dark:text-zinc-50", children: formatCurrency(pricePerEntry, currency) }), _jsx("a", { href: "#prize-draw-buy-bar", className: "appkit-button appkit-button--primary appkit-button--sm flex-1", children: _jsx("span", { className: "appkit-button__content", children: "
|
|
118
|
+
} }), _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: "hover:text-primary-600 transition-colors", children: "Home" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.PRIZE_DRAWS), className: "hover:text-primary-600 transition-colors", children: "Prize Draws" }), _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(PrizeDrawCollage, { items: items, hideWonState: true })), renderInfo: () => (_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-fuchsia-100 dark:bg-fuchsia-900/30 px-2.5 py-0.5 text-xs font-semibold text-fuchsia-700 dark:text-fuchsia-300", children: "Prize Draw" }), _jsx(Span, { className: `inline-block rounded-full px-2.5 py-0.5 text-xs font-semibold ${statusClass(revealStatus)}`, children: statusLabel(revealStatus) }), maxPerUser !== null && (_jsxs(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-700 dark:text-zinc-300", children: ["Limit: ", maxPerUser, " entries per customer"] })), maxPerUser !== null && userEntriesUsed !== null && (_jsxs(Span, { className: "inline-block rounded-full bg-fuchsia-100 dark:bg-fuchsia-900/30 px-2.5 py-0.5 text-xs font-semibold text-fuchsia-700 dark:text-fuchsia-300", children: ["You have used ", userEntriesUsed, "/", maxPerUser] }))] }), _jsx(Heading, { level: 1, className: "text-xl font-bold leading-snug text-zinc-900 dark:text-zinc-50 sm:text-2xl", children: title })] }), _jsx(Div, { className: "rounded-xl border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900/60 p-4", children: _jsxs(Stack, { gap: "sm", children: [_jsxs(Row, { justify: "between", align: "center", children: [_jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: "Entries" }), _jsxs(Text, { className: "text-sm font-semibold", children: [current, " / ", max, " (", remaining, " left)"] })] }), revealStart && (_jsxs(Row, { justify: "between", align: "center", children: [_jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: "Reveal window opens" }), _jsx(Text, { className: "text-sm font-medium", children: revealStart.toLocaleString() })] })), revealEnd && (_jsxs(Row, { justify: "between", align: "center", children: [_jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: "Reveal window closes" }), _jsx(Text, { className: "text-sm font-medium", children: revealEnd.toLocaleString() })] }))] }) }), 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" }))] }) }))] })), renderTabs: () => (_jsx(ProductTabsShell, { descriptionContent: descriptionHtml ? (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm sm:prose max-w-none dark:prose-invert", className: "text-zinc-700 dark:text-zinc-300" })) : undefined })), renderBuyBar: () => (_jsx(Div, { id: "prize-draw-buy-bar", className: "rounded-xl border border-zinc-100 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900/60 p-5", children: _jsx(PrizeDrawEntryActions, { productId: String(product.id), productSlug: slug, title: title, thumb: thumb, pricePerEntry: pricePerEntry, currency: currency, remainingEntries: remaining, revealStatus: revealStatus, prizeGithubFileUrl: githubUrl }) })) }), _jsxs(BuyBar, { children: [_jsx(Span, { className: "mr-auto text-sm font-bold text-zinc-900 dark:text-zinc-50", children: formatCurrency(pricePerEntry, currency) }), _jsx("a", { href: "#prize-draw-buy-bar", className: "appkit-button appkit-button--primary appkit-button--sm flex-1", children: _jsx("span", { className: "appkit-button__content", children: ACTIONS.PRIZE_DRAW["enter-draw"].label }) })] })] })] }));
|
|
118
119
|
}
|
|
@@ -10,6 +10,7 @@ import { pushCartOp } from "../../cart/utils/pending-ops";
|
|
|
10
10
|
import { NonRefundableConsentModal } from "./NonRefundableConsentModal";
|
|
11
11
|
import { useAuthGate } from "../../../react/hooks/useAuthGate";
|
|
12
12
|
import { ACTION_ID } from "../constants/action-defs";
|
|
13
|
+
import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
|
|
13
14
|
/**
|
|
14
15
|
* Client buy panel for a prize-draw detail page (SB4-G).
|
|
15
16
|
*
|
|
@@ -47,5 +48,5 @@ export function PrizeDrawEntryActions({ productId, productSlug, title, thumb, pr
|
|
|
47
48
|
showToast("Entry added to cart", "success");
|
|
48
49
|
router.push(String(ROUTES.USER.CART));
|
|
49
50
|
}, [cart, productId, title, thumb, pricePerEntry, router, showToast]);
|
|
50
|
-
return (_jsxs(Stack, { gap: "md", children: [_jsxs(Text, { className: "text-2xl font-bold text-zinc-900 dark:text-zinc-50", children: [formatCurrency(pricePerEntry, currency), _jsx(Text, { as: "span", className: "ml-1 text-xs font-normal text-[var(--appkit-color-text-muted)]", children: "per entry" })] }), _jsx(Button, { variant: "primary", size: "md", className: "w-full", disabled: closed, onClick: handleEnter, children: closed ? "Draw closed" :
|
|
51
|
+
return (_jsxs(Stack, { gap: "md", children: [_jsxs(Text, { className: "text-2xl font-bold text-zinc-900 dark:text-zinc-50", children: [formatCurrency(pricePerEntry, currency), _jsx(Text, { as: "span", className: "ml-1 text-xs font-normal text-[var(--appkit-color-text-muted)]", children: "per entry" })] }), _jsx(Button, { action: closed ? undefined : ACTIONS.PRIZE_DRAW["enter-draw"], variant: "primary", size: "md", className: "w-full", disabled: closed, onClick: handleEnter, children: closed ? "Draw closed" : undefined }), prizeGithubFileUrl ? (_jsx("a", { href: prizeGithubFileUrl, target: "_blank", rel: "noopener noreferrer", className: "text-center text-xs font-medium text-primary-600 underline-offset-4 hover:underline dark:text-primary-400", children: "View RNG source code on GitHub \u2192" })) : null, _jsx(Text, { className: "text-center text-xs text-[var(--appkit-color-text-muted)]", children: "Winners chosen by Node.js crypto.randomInt \u2014 fully auditable. Entries are locked once paid; refunds only if the prize pool is exhausted." }), _jsx(NonRefundableConsentModal, { open: consentOpen, listingType: "prize-draw", itemTitle: title, priceLabel: formatCurrency(pricePerEntry, currency), onCancel: () => setConsentOpen(false), onConfirm: handleConfirm }), _jsx(LoginRequiredModal, { isOpen: modalOpen, onClose: closeModal, message: modalMessage })] }));
|
|
51
52
|
}
|
|
@@ -71,6 +71,14 @@ export function PrizeDrawItemsEditor({ items, onChange, onUploadImage, onUploadV
|
|
|
71
71
|
nextImgs[slot] = url;
|
|
72
72
|
update(index, { images: nextImgs.filter(Boolean) });
|
|
73
73
|
};
|
|
74
|
+
const handleVideoFileChange = (e, index, itemNumber) => {
|
|
75
|
+
const file = e.target.files?.[0];
|
|
76
|
+
if (!file || !onUploadVideo)
|
|
77
|
+
return;
|
|
78
|
+
void onUploadVideo(file, itemNumber).then((url) => {
|
|
79
|
+
update(index, { video: { url } });
|
|
80
|
+
});
|
|
81
|
+
};
|
|
74
82
|
const removeImage = (index, slot) => {
|
|
75
83
|
const cur = items[index];
|
|
76
84
|
const nextImgs = (cur.images ?? []).filter((_, i) => i !== slot);
|
|
@@ -85,13 +93,7 @@ export function PrizeDrawItemsEditor({ items, onChange, onUploadImage, onUploadV
|
|
|
85
93
|
}), disabled: locked, placeholder: "2999" }) })] }), _jsxs(Stack, { gap: "xs", children: [_jsxs(Text, { className: "text-sm font-medium", children: ["Images (", (it.images ?? []).length, "/", MAX_IMAGES_PER_ITEM, ")"] }), _jsx(Row, { gap: "sm", className: "flex-wrap", children: Array.from({ length: MAX_IMAGES_PER_ITEM }).map((_, slot) => {
|
|
86
94
|
const existing = (it.images ?? [])[slot];
|
|
87
95
|
return (_jsxs(Div, { className: "w-40", children: [_jsx(ImageUpload, { currentImage: existing, label: existing ? "Replace" : `Image ${slot + 1}`, onUpload: (file) => onUploadImage(file, it.itemNumber), onChange: (url) => setImage(index, slot, url) }), existing && !locked ? (_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => removeImage(index, slot), children: "Remove" })) : null] }, slot));
|
|
88
|
-
}) })] }), onUploadVideo ? (_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { className: "text-sm font-medium", children: "Video (optional)" }), _jsx("input", { type: "file", accept: "video/mp4,video/webm", disabled: locked, onChange:
|
|
89
|
-
const file = e.target.files?.[0];
|
|
90
|
-
if (!file)
|
|
91
|
-
return;
|
|
92
|
-
const url = await onUploadVideo(file, it.itemNumber);
|
|
93
|
-
update(index, { video: { url } });
|
|
94
|
-
} }), it.video?.url ? (_jsxs(Text, { className: "text-xs text-[var(--appkit-color-text-muted)] truncate", children: ["Current: ", it.video.url] })) : null] })) : null] })] }, `prize-item-${it.itemNumber}-${index}`));
|
|
96
|
+
}) })] }), onUploadVideo ? (_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { className: "text-sm font-medium", children: "Video (optional)" }), _jsx("input", { type: "file", accept: "video/mp4,video/webm", disabled: locked, onChange: (e) => handleVideoFileChange(e, index, it.itemNumber) }), it.video?.url ? (_jsxs(Text, { className: "text-xs text-[var(--appkit-color-text-muted)] truncate", children: ["Current: ", it.video.url] })) : null] })) : null] })] }, `prize-item-${it.itemNumber}-${index}`));
|
|
95
97
|
}) }), items.length < MIN_ITEMS ? (_jsxs(Div, { className: "rounded border border-red-400/40 bg-red-50 px-3 py-2 text-sm text-red-900 dark:bg-red-900/30 dark:text-red-100", children: ["At least ", MIN_ITEMS, " prizes are required."] })) : null] }));
|
|
96
98
|
}
|
|
97
99
|
export default PrizeDrawItemsEditor;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
-
import { useState, useCallback
|
|
4
|
-
import { SlidersHorizontal, X } from "lucide-react";
|
|
3
|
+
import { useState, useCallback } from "react";
|
|
5
4
|
import { useUrlTable } from "../../../react/hooks/useUrlTable";
|
|
5
|
+
import { usePendingTable } from "../../../react/hooks/usePendingTable";
|
|
6
6
|
import { useProducts } from "../hooks/useProducts";
|
|
7
|
-
import {
|
|
7
|
+
import { FilterDrawer, ListingToolbar, Pagination, Text } from "../../../ui";
|
|
8
8
|
import { useCategoryTree, categoriesToFacetOptions } from "../../categories/hooks/useCategoryTree";
|
|
9
9
|
import { useBrands } from "../hooks/useBrands";
|
|
10
10
|
import { MarketplacePrizeDrawCard } from "./MarketplacePrizeDrawCard";
|
|
@@ -38,58 +38,28 @@ export function PrizeDrawsIndexListing({ initialData, categorySlug, brandName, s
|
|
|
38
38
|
const { categories } = useCategoryTree();
|
|
39
39
|
const categoryOptions = categoriesToFacetOptions(categories);
|
|
40
40
|
const { brandOptions } = useBrands();
|
|
41
|
-
const
|
|
42
|
-
const pendingTable = useMemo(() => ({
|
|
43
|
-
get: (key) => pendingFilters[key] ?? "",
|
|
44
|
-
getNumber: (key, fallback = 0) => {
|
|
45
|
-
const v = pendingFilters[key];
|
|
46
|
-
if (!v)
|
|
47
|
-
return fallback;
|
|
48
|
-
const n = Number(v);
|
|
49
|
-
return isNaN(n) ? fallback : n;
|
|
50
|
-
},
|
|
51
|
-
set: (key, value) => setPendingFilters((p) => ({ ...p, [key]: value })),
|
|
52
|
-
setMany: (updates) => setPendingFilters((p) => ({ ...p, ...updates })),
|
|
53
|
-
clear: (keys) => {
|
|
54
|
-
const ks = keys ?? FILTER_KEYS;
|
|
55
|
-
setPendingFilters((p) => ({
|
|
56
|
-
...p,
|
|
57
|
-
...Object.fromEntries(ks.map((k) => [k, ""])),
|
|
58
|
-
}));
|
|
59
|
-
},
|
|
60
|
-
setPage: (_) => { },
|
|
61
|
-
setPageSize: (_) => { },
|
|
62
|
-
setSort: (_) => { },
|
|
63
|
-
buildSieveParams: () => "",
|
|
64
|
-
buildSearchParams: () => "",
|
|
65
|
-
params: new URLSearchParams(),
|
|
66
|
-
}), [pendingFilters]);
|
|
41
|
+
const { pendingTable, filterActiveCount, onFilterApply, onFilterClear, onFilterReset } = usePendingTable(table, FILTER_KEYS);
|
|
67
42
|
const openFilters = useCallback(() => {
|
|
68
|
-
|
|
43
|
+
onFilterReset();
|
|
69
44
|
setFilterOpen(true);
|
|
70
|
-
}, [
|
|
45
|
+
}, [onFilterReset]);
|
|
71
46
|
const applyFilters = useCallback(() => {
|
|
72
|
-
|
|
73
|
-
for (const k of FILTER_KEYS)
|
|
74
|
-
updates[k] = pendingFilters[k] ?? "";
|
|
75
|
-
table.setMany(updates);
|
|
47
|
+
onFilterApply();
|
|
76
48
|
setFilterOpen(false);
|
|
77
|
-
}, [
|
|
78
|
-
const clearFilters = useCallback(() => {
|
|
79
|
-
setPendingFilters(Object.fromEntries(FILTER_KEYS.map((k) => [k, ""])));
|
|
80
|
-
}, []);
|
|
49
|
+
}, [onFilterApply]);
|
|
81
50
|
const resetAll = useCallback(() => {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
51
|
+
table.setMany({
|
|
52
|
+
[TABLE_KEYS.QUERY]: "",
|
|
53
|
+
[TABLE_KEYS.SORT]: "",
|
|
54
|
+
[TABLE_KEYS.SHOW_CLOSED]: "",
|
|
55
|
+
});
|
|
56
|
+
onFilterClear();
|
|
86
57
|
setSearchInput("");
|
|
87
|
-
}, [table]);
|
|
88
|
-
const activeFilterCount = FILTER_KEYS.filter((k) => !!table.get(k)).length;
|
|
58
|
+
}, [table, onFilterClear]);
|
|
89
59
|
const hasActiveState = !!table.get(TABLE_KEYS.QUERY) ||
|
|
90
60
|
showClosed ||
|
|
91
61
|
table.get(TABLE_KEYS.SORT) !== DEFAULT_SORT ||
|
|
92
|
-
|
|
62
|
+
filterActiveCount > 0;
|
|
93
63
|
const revealFilter = (table.get(TABLE_KEYS.PRIZE_REVEAL_STATUS) || undefined);
|
|
94
64
|
const params = {
|
|
95
65
|
q: table.get(TABLE_KEYS.QUERY) || undefined,
|
|
@@ -119,15 +89,12 @@ export function PrizeDrawsIndexListing({ initialData, categorySlug, brandName, s
|
|
|
119
89
|
commitSearch();
|
|
120
90
|
};
|
|
121
91
|
const gridClass = "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-4";
|
|
122
|
-
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount:
|
|
92
|
+
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: filterActiveCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search prize draws...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, onSearchKeyDown: handleSearchKeyDown, sortValue: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT, sortOptions: PRIZE_DRAW_SORT_OPTIONS, onSortChange: (v) => {
|
|
123
93
|
table.set(TABLE_KEYS.SORT, v);
|
|
124
94
|
}, view: view, onViewChange: (v) => {
|
|
125
95
|
if (v === VIEW_MODE.TABLE)
|
|
126
96
|
return;
|
|
127
97
|
setView(v);
|
|
128
98
|
table.set(TABLE_KEYS.VIEW, v);
|
|
129
|
-
}, onResetAll: resetAll, hasActiveState: hasActiveState, extra: _jsxs("label", { className: "flex items-center gap-1.5 cursor-pointer select-none shrink-0", children: [_jsx("span", { className: "hidden sm:inline text-xs text-zinc-600 dark:text-zinc-300 whitespace-nowrap", children: "Show closed" }), _jsx("button", { type: "button", role: "switch", "aria-checked": showClosed, onClick: () => table.set("showClosed", showClosed ? "" : "true"), className: `relative inline-flex h-5 w-9 flex-shrink-0 items-center rounded-full transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-1 ${showClosed ? "bg-primary" : "bg-zinc-300 dark:bg-slate-600"}`, children: _jsx("span", { className: `inline-block h-3.5 w-3.5 transform rounded-full bg-white shadow-sm transition-transform duration-200 ${showClosed ? "translate-x-[19px]" : "translate-x-[3px]"}` }) })] }) }), 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: isLoading ? (_jsx("div", { className: gridClass, children: Array.from({ length: 8 }).map((_, i) => (_jsxs("div", { className: "rounded-xl border border-zinc-100 dark:border-slate-700 overflow-hidden animate-pulse", children: [_jsx("div", { className: "aspect-square bg-zinc-200 dark:bg-slate-700" }), _jsxs("div", { className: "p-3 space-y-2", children: [_jsx("div", { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-3/4" }), _jsx("div", { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-1/2" }), _jsx("div", { className: "h-8 bg-zinc-200 dark:bg-slate-700 rounded" })] })] }, i))) })) : filteredDraws.length === 0 ? (_jsx(
|
|
130
|
-
...p,
|
|
131
|
-
prizeRevealStatus: e.target.value,
|
|
132
|
-
})), className: "w-full rounded border border-zinc-300 dark:border-slate-600 bg-white dark:bg-slate-800 px-2 py-1.5 text-sm", children: [_jsx("option", { value: "", children: "Any" }), _jsx("option", { value: "pending", children: "Reveal pending" }), _jsx("option", { value: "open", children: "Reveal open" }), _jsx("option", { value: "closed", children: "Closed" })] })] }), _jsx(ProductFilters, { table: pendingTable, currencyPrefix: "\u20B9", categoryOptions: categoryOptions, brandOptions: brandOptions })] }), _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})` : ""] }) })] })] }))] }));
|
|
99
|
+
}, onResetAll: resetAll, hasActiveState: hasActiveState, extra: _jsxs("label", { className: "flex items-center gap-1.5 cursor-pointer select-none shrink-0", children: [_jsx("span", { className: "hidden sm:inline text-xs text-zinc-600 dark:text-zinc-300 whitespace-nowrap", children: "Show closed" }), _jsx("button", { type: "button", role: "switch", "aria-checked": showClosed, onClick: () => table.set("showClosed", showClosed ? "" : "true"), className: `relative inline-flex h-5 w-9 flex-shrink-0 items-center rounded-full transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-1 ${showClosed ? "bg-primary" : "bg-zinc-300 dark:bg-slate-600"}`, children: _jsx("span", { className: `inline-block h-3.5 w-3.5 transform rounded-full bg-white shadow-sm transition-transform duration-200 ${showClosed ? "translate-x-[19px]" : "translate-x-[3px]"}` }) })] }) }), 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: isLoading ? (_jsx("div", { className: gridClass, children: Array.from({ length: 8 }).map((_, i) => (_jsxs("div", { className: "rounded-xl border border-zinc-100 dark:border-slate-700 overflow-hidden animate-pulse", children: [_jsx("div", { className: "aspect-square bg-zinc-200 dark:bg-slate-700" }), _jsxs("div", { className: "p-3 space-y-2", children: [_jsx("div", { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-3/4" }), _jsx("div", { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-1/2" }), _jsx("div", { className: "h-8 bg-zinc-200 dark:bg-slate-700 rounded" })] })] }, i))) })) : filteredDraws.length === 0 ? (_jsx(Text, { className: "py-12 text-center text-sm text-zinc-500 dark:text-zinc-400", children: "No prize draws found." })) : (_jsx("div", { className: gridClass, children: filteredDraws.map((product) => (_jsx(MarketplacePrizeDrawCard, { product: product, variant: view }, product.id))) })) }), _jsx(FilterDrawer, { open: filterOpen, onOpen: openFilters, onClose: () => setFilterOpen(false), onApply: applyFilters, onReset: onFilterClear, activeCount: filterActiveCount, hideTrigger: true, children: _jsxs("div", { className: "space-y-4", children: [_jsxs(_Fragment, { children: [_jsx("label", { htmlFor: "prizeRevealStatusFilter", className: "block text-xs font-semibold text-zinc-700 dark:text-zinc-300 mb-1.5", children: "Reveal status" }), _jsxs("select", { id: "prizeRevealStatusFilter", value: pendingTable.get(TABLE_KEYS.PRIZE_REVEAL_STATUS) ?? "", onChange: (e) => pendingTable.set(TABLE_KEYS.PRIZE_REVEAL_STATUS, e.target.value), className: "w-full rounded border border-zinc-300 dark:border-slate-600 bg-white dark:bg-slate-800 px-2 py-1.5 text-sm", children: [_jsx("option", { value: "", children: "Any" }), _jsx("option", { value: "pending", children: "Reveal pending" }), _jsx("option", { value: "open", children: "Reveal open" }), _jsx("option", { value: "closed", children: "Closed" })] })] }), _jsx(ProductFilters, { table: pendingTable, currencyPrefix: "\u20B9", categoryOptions: categoryOptions, brandOptions: brandOptions })] }) })] }));
|
|
133
100
|
}
|
|
@@ -21,6 +21,24 @@ import { PrizeDrawCollage } from "./PrizeDrawCollage";
|
|
|
21
21
|
const REVEAL_DURATION_MS = 3200; // total animation length
|
|
22
22
|
const CYCLE_INTERVAL_FAST = 80;
|
|
23
23
|
const CYCLE_INTERVAL_SLOW = 360;
|
|
24
|
+
function isUnauthenticatedResponse(status) {
|
|
25
|
+
return status === 401 || status === 403;
|
|
26
|
+
}
|
|
27
|
+
async function fetchPrizeReveal(productId, orderId) {
|
|
28
|
+
const res = await fetch(`/api/prize-draws/${productId}/reveal`, {
|
|
29
|
+
method: "POST",
|
|
30
|
+
headers: { "Content-Type": "application/json" },
|
|
31
|
+
body: JSON.stringify({ orderId }),
|
|
32
|
+
});
|
|
33
|
+
const json = (await res.json());
|
|
34
|
+
if (!res.ok) {
|
|
35
|
+
if (isUnauthenticatedResponse(res.status)) {
|
|
36
|
+
return { response: null, unauthenticated: true };
|
|
37
|
+
}
|
|
38
|
+
throw new Error("Reveal request failed");
|
|
39
|
+
}
|
|
40
|
+
return { response: json.data ?? {}, unauthenticated: false };
|
|
41
|
+
}
|
|
24
42
|
export function PrizeRevealModal({ open, onClose, items, orderId, productId, onReveal, initialPrizeWon, rngSourceUrl, }) {
|
|
25
43
|
const [phase, setPhase] = useState(initialPrizeWon ? "won" : "idle");
|
|
26
44
|
const [highlight, setHighlight] = useState(undefined);
|
|
@@ -79,21 +97,13 @@ export function PrizeRevealModal({ open, onClose, items, orderId, productId, onR
|
|
|
79
97
|
response = await onReveal({ orderId, productId });
|
|
80
98
|
}
|
|
81
99
|
else {
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const json = (await res.json());
|
|
88
|
-
if (!res.ok) {
|
|
89
|
-
if (res.status === 401 || res.status === 403) {
|
|
90
|
-
setPhase("idle");
|
|
91
|
-
setShowLoginModal(true);
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
throw new Error("Reveal request failed");
|
|
100
|
+
const result = await fetchPrizeReveal(productId, orderId);
|
|
101
|
+
if (result.unauthenticated) {
|
|
102
|
+
setPhase("idle");
|
|
103
|
+
setShowLoginModal(true);
|
|
104
|
+
return;
|
|
95
105
|
}
|
|
96
|
-
response =
|
|
106
|
+
response = result.response ?? {};
|
|
97
107
|
}
|
|
98
108
|
}
|
|
99
109
|
catch (err) {
|
|
@@ -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, reviewRepository } 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 { Button, Container, Div, Heading, Main, RichText, Row, Section, Span, Stack, Text, } from "../../../ui";
|
|
@@ -216,7 +217,7 @@ export async function ProductDetailPageView({ slug, initialProduct, renderOfferA
|
|
|
216
217
|
price: product.price,
|
|
217
218
|
storeId: product.storeId,
|
|
218
219
|
storeName: product.storeName,
|
|
219
|
-
} }), _jsxs(Container, { size: "xl", className: "px-4 py-6", children: [_jsx(ProductDetailView, { renderBreadcrumb: () => (_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:
|
|
220
|
+
} }), _jsxs(Container, { size: "xl", className: "px-4 py-6", children: [_jsx(ProductDetailView, { renderBreadcrumb: () => (_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.PRODUCTS), className: CLS_BREADCRUMB_LINK, children: "Products" }), 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 })] })), subcategory && (_jsxs(_Fragment, { children: [_jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Span, { className: "capitalize", children: subcategory })] }))] }), _jsx(ShareButton, { title: title || undefined })] })), renderGallery: () => (_jsx(ProductGalleryClient, { images: images, productName: title || undefined })), renderInfo: () => (_jsxs(Stack, { gap: "md", children: [_jsxs(Div, { children: [condition && (_jsx(Span, { className: "mb-2 inline-block rounded-full bg-zinc-100 dark:bg-zinc-800 px-2.5 py-0.5 text-xs font-medium capitalize text-zinc-600 dark:text-zinc-300", children: condition === "new"
|
|
220
221
|
? "Brand New"
|
|
221
222
|
: condition === "like_new"
|
|
222
223
|
? "Like New"
|
|
@@ -47,11 +47,13 @@ export const PRODUCT_FILTER_KEYS = {
|
|
|
47
47
|
TABLE_KEYS.FEATURES,
|
|
48
48
|
],
|
|
49
49
|
};
|
|
50
|
+
const LABEL_PRICE_HIGH_TO_LOW = "Price: High to Low";
|
|
51
|
+
const LABEL_PRICE_LOW_TO_HIGH = "Price: Low to High";
|
|
50
52
|
export const PRODUCT_ADMIN_SORT_OPTIONS = [
|
|
51
53
|
{ value: sortBy(PRODUCT_FIELDS.CREATED_AT), label: "Newest First" },
|
|
52
54
|
{ value: sortBy(PRODUCT_FIELDS.CREATED_AT, "ASC"), label: "Oldest First" },
|
|
53
|
-
{ value: sortBy(PRODUCT_FIELDS.PRICE), label:
|
|
54
|
-
{ value: sortBy(PRODUCT_FIELDS.PRICE, "ASC"), label:
|
|
55
|
+
{ value: sortBy(PRODUCT_FIELDS.PRICE), label: LABEL_PRICE_HIGH_TO_LOW },
|
|
56
|
+
{ value: sortBy(PRODUCT_FIELDS.PRICE, "ASC"), label: LABEL_PRICE_LOW_TO_HIGH },
|
|
55
57
|
{ value: sortBy(PRODUCT_FIELDS.TITLE, "ASC"), label: "Title A-Z" },
|
|
56
58
|
{ value: sortBy(PRODUCT_FIELDS.TITLE), label: "Title Z-A" },
|
|
57
59
|
{ value: sortBy(PRODUCT_FIELDS.VIEW_COUNT), label: "Most Viewed" },
|
|
@@ -59,15 +61,15 @@ export const PRODUCT_ADMIN_SORT_OPTIONS = [
|
|
|
59
61
|
export const PRODUCT_SELLER_SORT_OPTIONS = [
|
|
60
62
|
{ value: sortBy(PRODUCT_FIELDS.CREATED_AT), label: "Newest First" },
|
|
61
63
|
{ value: sortBy(PRODUCT_FIELDS.CREATED_AT, "ASC"), label: "Oldest First" },
|
|
62
|
-
{ value: sortBy(PRODUCT_FIELDS.PRICE), label:
|
|
63
|
-
{ value: sortBy(PRODUCT_FIELDS.PRICE, "ASC"), label:
|
|
64
|
+
{ value: sortBy(PRODUCT_FIELDS.PRICE), label: LABEL_PRICE_HIGH_TO_LOW },
|
|
65
|
+
{ value: sortBy(PRODUCT_FIELDS.PRICE, "ASC"), label: LABEL_PRICE_LOW_TO_HIGH },
|
|
64
66
|
{ value: sortBy(PRODUCT_FIELDS.TITLE, "ASC"), label: "Title A-Z" },
|
|
65
67
|
];
|
|
66
68
|
export const PRODUCT_PUBLIC_SORT_OPTIONS = [
|
|
67
69
|
{ value: sortBy(PRODUCT_FIELDS.CREATED_AT), label: "Newest First" },
|
|
68
70
|
{ value: sortBy(PRODUCT_FIELDS.CREATED_AT, "ASC"), label: "Oldest First" },
|
|
69
|
-
{ value: sortBy(PRODUCT_FIELDS.PRICE), label:
|
|
70
|
-
{ value: sortBy(PRODUCT_FIELDS.PRICE, "ASC"), label:
|
|
71
|
+
{ value: sortBy(PRODUCT_FIELDS.PRICE), label: LABEL_PRICE_HIGH_TO_LOW },
|
|
72
|
+
{ value: sortBy(PRODUCT_FIELDS.PRICE, "ASC"), label: LABEL_PRICE_LOW_TO_HIGH },
|
|
71
73
|
{ value: sortBy(PRODUCT_FIELDS.TITLE, "ASC"), label: "Title A–Z" },
|
|
72
74
|
{ value: sortBy(PRODUCT_FIELDS.TITLE), label: "Title Z–A" },
|
|
73
75
|
{ value: sortBy(PRODUCT_FIELDS.VIEW_COUNT), label: "Most Viewed" },
|
|
@@ -19,6 +19,13 @@ export const PRODUCT_STATUS_OPTIONS = [
|
|
|
19
19
|
{ value: "discontinued", label: "Discontinued" },
|
|
20
20
|
{ value: "sold", label: "Sold" },
|
|
21
21
|
];
|
|
22
|
+
function readFileAsDataUrl(file) {
|
|
23
|
+
return new Promise((resolve) => {
|
|
24
|
+
const reader = new FileReader();
|
|
25
|
+
reader.onload = () => resolve(String(reader.result ?? ""));
|
|
26
|
+
reader.readAsDataURL(file);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
22
29
|
export function ProductForm({ product, onChange, isReadonly = false, renderDescriptionEditor, renderCategorySelector, renderBrandSelector, renderStoreAddressSelector, onMediaAbort, currencyPrefix = "", renderGroupSettings, }) {
|
|
23
30
|
const t = useTranslations("adminProducts");
|
|
24
31
|
const { upload } = useMediaUpload();
|
|
@@ -140,16 +147,11 @@ export function ProductForm({ product, onChange, isReadonly = false, renderDescr
|
|
|
140
147
|
return d.toISOString().slice(0, 16);
|
|
141
148
|
})(), onChange: (value) => update({ prizeRevealWindowEnd: value }), disabled: fieldDisabled })] }), _jsxs(FormGroup, { columns: 2, children: [_jsx(FormField, { name: "prizeRevealDeadlineDays", label: "Reveal deadline (days)", type: "number", value: String(product.prizeRevealDeadlineDays ?? 3), onChange: (value) => update({
|
|
142
149
|
prizeRevealDeadlineDays: Number(value) || 3,
|
|
143
|
-
}), disabled: fieldDisabled, placeholder: "3", helpText: "After window opens, buyers have this many days to claim." }), _jsx(FormField, { name: "maxPerUser", label: "Max entries per customer (blank = unlimited)", type: "number", value: String(product.maxPerUser ?? ""), onChange: (value) => update({ maxPerUser: Number(value) || undefined }), disabled: fieldDisabled, placeholder: "" })] }), product.prizeGithubFileUrl ? (_jsx(FormField, { name: "prizeGithubFileUrl", label: "RNG Source Code URL (read-only)", type: "text", value: product.prizeGithubFileUrl, onChange: () => { }, disabled: true, helpText: "Auto-set on first save. Public proof-of-fairness link." })) : null, _jsx(PrizeDrawItemsEditor, { items: (product.prizeDrawItems ?? []), onChange: (items) => update({ prizeDrawItems: items }), onUploadImage:
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
const reader = new FileReader();
|
|
149
|
-
reader.onload = () => resolve(String(reader.result ?? ""));
|
|
150
|
-
reader.readAsDataURL(file);
|
|
151
|
-
});
|
|
152
|
-
} })] }));
|
|
150
|
+
}), disabled: fieldDisabled, placeholder: "3", helpText: "After window opens, buyers have this many days to claim." }), _jsx(FormField, { name: "maxPerUser", label: "Max entries per customer (blank = unlimited)", type: "number", value: String(product.maxPerUser ?? ""), onChange: (value) => update({ maxPerUser: Number(value) || undefined }), disabled: fieldDisabled, placeholder: "" })] }), product.prizeGithubFileUrl ? (_jsx(FormField, { name: "prizeGithubFileUrl", label: "RNG Source Code URL (read-only)", type: "text", value: product.prizeGithubFileUrl, onChange: () => { }, disabled: true, helpText: "Auto-set on first save. Public proof-of-fairness link." })) : null, _jsx(PrizeDrawItemsEditor, { items: (product.prizeDrawItems ?? []), onChange: (items) => update({ prizeDrawItems: items }), onUploadImage: (file) =>
|
|
151
|
+
// Caller is expected to wire a real uploader by overriding
|
|
152
|
+
// renderPrizeDrawImageUpload — fall back to data URL preview
|
|
153
|
+
// so the editor still functions in unwired admin contexts.
|
|
154
|
+
readFileAsDataUrl(file) })] }));
|
|
153
155
|
})()), isPreOrderListing(product) && (_jsxs(_Fragment, { children: [_jsxs(FormGroup, { columns: 2, children: [_jsx(FormField, { name: "preOrderDeliveryDate", label: t("formPreOrderDeliveryDate"), type: "datetime-local", value: (() => {
|
|
154
156
|
const d = resolveDate(product.preOrderDeliveryDate);
|
|
155
157
|
if (!d)
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState } from "react";
|
|
4
4
|
import { ImageLightbox } from "../../../ui/components/ImageLightbox";
|
|
5
|
+
import { Text } from "../../../ui";
|
|
5
6
|
const PlaceholderSvg = () => (_jsxs("svg", { width: "64", height: "64", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", "aria-hidden": "true", children: [_jsx("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2" }), _jsx("circle", { cx: "8.5", cy: "8.5", r: "1.5" }), _jsx("path", { d: "M21 15l-5-5L5 21" })] }));
|
|
6
7
|
export function ProductGalleryClient({ images, productName }) {
|
|
7
8
|
const [activeIndex, setActiveIndex] = useState(0);
|
|
@@ -19,5 +20,5 @@ export function ProductGalleryClient({ images, productName }) {
|
|
|
19
20
|
setLightboxIndex(activeIndex);
|
|
20
21
|
}, "aria-label": `View ${productName ?? "product"} image in full screen`, children: _jsx("img", { src: mainImage, alt: productName ? `${productName} — image ${activeIndex + 1}` : `Product image ${activeIndex + 1}`, className: "aspect-square w-full object-cover transition-transform duration-300 hover:scale-105" }) }), images.length > 1 && (_jsx("div", { className: "flex gap-2 overflow-x-auto pb-1", children: images.map((src, i) => (_jsx("button", { type: "button", onClick: () => setActiveIndex(i), className: `flex-shrink-0 h-16 w-16 overflow-hidden rounded-lg border-2 transition-all${i === activeIndex
|
|
21
22
|
? " border-primary-500"
|
|
22
|
-
: " border-transparent opacity-60 hover:opacity-100"}`, "aria-label": `View image ${i + 1}`, "aria-current": i === activeIndex ? "true" : undefined, children: _jsx("img", { src: src, alt: "", className: "h-full w-full object-cover" }) }, i))) })), images.length > 1 && (_jsxs(
|
|
23
|
+
: " border-transparent opacity-60 hover:opacity-100"}`, "aria-label": `View image ${i + 1}`, "aria-current": i === activeIndex ? "true" : undefined, children: _jsx("img", { src: src, alt: "", className: "h-full w-full object-cover" }) }, i))) })), images.length > 1 && (_jsxs(Text, { className: "text-center text-xs text-zinc-400 dark:text-zinc-500", children: [activeIndex + 1, " / ", images.length] })), _jsx(ImageLightbox, { images: lightboxImages, activeIndex: lightboxIndex, onClose: () => setLightboxIndex(null), onNavigate: (i) => setLightboxIndex(i) })] }));
|
|
23
24
|
}
|
|
@@ -146,19 +146,22 @@ export function ProductGrid({ products, renderCard, onProductClick, getProductHr
|
|
|
146
146
|
(slots?.renderEmptyState
|
|
147
147
|
? slots.renderEmptyState()
|
|
148
148
|
: null);
|
|
149
|
+
function buildProductCardContext(p) {
|
|
150
|
+
return {
|
|
151
|
+
onClick: onProductClick,
|
|
152
|
+
onWishlistToggle,
|
|
153
|
+
onAddToCart,
|
|
154
|
+
onBuyNow,
|
|
155
|
+
isWishlisted: wishlistedIds?.has(p.id) ?? false,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
149
158
|
const renderItems = () => {
|
|
150
159
|
if (view === "list") {
|
|
151
160
|
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 ${className}`, children: products.map((p) => (_jsx(ProductListRow, { product: p, onClick: onProductClick, onAddToWishlist: onWishlistToggle, isWishlisted: wishlistedIds?.has(p.id) ?? false }, p.id))) }));
|
|
152
161
|
}
|
|
153
162
|
if (view === "fluid") {
|
|
154
163
|
return (_jsx(Grid, { cols: "productCardsCompact", className: className, children: products.map((p, i) => {
|
|
155
|
-
const ctx =
|
|
156
|
-
onClick: onProductClick,
|
|
157
|
-
onWishlistToggle,
|
|
158
|
-
onAddToCart,
|
|
159
|
-
onBuyNow,
|
|
160
|
-
isWishlisted: wishlistedIds?.has(p.id) ?? false,
|
|
161
|
-
};
|
|
164
|
+
const ctx = buildProductCardContext(p);
|
|
162
165
|
const cardRenderer = renderCard ?? slots?.renderCard;
|
|
163
166
|
return cardRenderer ? (_jsx(React.Fragment, { children: cardRenderer === renderCard
|
|
164
167
|
? renderCard(p, ctx)
|
|
@@ -167,13 +170,7 @@ export function ProductGrid({ products, renderCard, onProductClick, getProductHr
|
|
|
167
170
|
}
|
|
168
171
|
const gridClass = GRID_CLASSES.card;
|
|
169
172
|
return (_jsx(Div, { className: `${gridClass} ${className}`, children: products.map((p, i) => {
|
|
170
|
-
const ctx =
|
|
171
|
-
onClick: onProductClick,
|
|
172
|
-
onWishlistToggle,
|
|
173
|
-
onAddToCart,
|
|
174
|
-
onBuyNow,
|
|
175
|
-
isWishlisted: wishlistedIds?.has(p.id) ?? false,
|
|
176
|
-
};
|
|
173
|
+
const ctx = buildProductCardContext(p);
|
|
177
174
|
const cardRenderer = renderCard ?? slots?.renderCard;
|
|
178
175
|
return cardRenderer ? (_jsx(React.Fragment, { children: cardRenderer === renderCard
|
|
179
176
|
? renderCard(p, ctx)
|