@mohasinac/appkit 2.7.27 → 2.7.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_internal/client/features/filters/filter-load-options.d.ts +29 -0
- package/dist/_internal/client/features/filters/filter-load-options.js +98 -0
- package/dist/_internal/client/features/layout/DashboardLayoutClient.js +1 -1
- package/dist/_internal/server/features/checkout/actions.js +42 -35
- package/dist/_internal/server/features/classified/adapters.d.ts +21 -0
- package/dist/_internal/server/features/classified/adapters.js +24 -0
- package/dist/_internal/server/features/classified/index.d.ts +3 -0
- package/dist/_internal/server/features/classified/index.js +3 -0
- package/dist/_internal/server/features/classified/metadata.d.ts +8 -0
- package/dist/_internal/server/features/classified/metadata.js +43 -0
- package/dist/_internal/server/features/classified/og.d.ts +30 -0
- package/dist/_internal/server/features/classified/og.js +76 -0
- package/dist/_internal/server/features/digital-code/adapters.d.ts +21 -0
- package/dist/_internal/server/features/digital-code/adapters.js +29 -0
- package/dist/_internal/server/features/digital-code/index.d.ts +3 -0
- package/dist/_internal/server/features/digital-code/index.js +3 -0
- package/dist/_internal/server/features/digital-code/metadata.d.ts +8 -0
- package/dist/_internal/server/features/digital-code/metadata.js +36 -0
- package/dist/_internal/server/features/digital-code/og.d.ts +27 -0
- package/dist/_internal/server/features/digital-code/og.js +77 -0
- package/dist/_internal/server/features/live/adapters.d.ts +21 -0
- package/dist/_internal/server/features/live/adapters.js +34 -0
- package/dist/_internal/server/features/live/index.d.ts +3 -0
- package/dist/_internal/server/features/live/index.js +3 -0
- package/dist/_internal/server/features/live/metadata.d.ts +8 -0
- package/dist/_internal/server/features/live/metadata.js +39 -0
- package/dist/_internal/server/features/live/og.d.ts +27 -0
- package/dist/_internal/server/features/live/og.js +72 -0
- package/dist/_internal/server/features/promotions/actions.js +4 -3
- package/dist/_internal/server/jobs/core/onOrderCreate.js +19 -16
- package/dist/_internal/server/jobs/core/onOrderStatusChange.js +25 -22
- package/dist/_internal/server/jobs/core/onProductWrite.js +48 -58
- package/dist/_internal/shared/actions/action-registry.js +504 -9
- package/dist/_internal/shared/features/blog/schema.d.ts +4 -4
- package/dist/_internal/shared/features/brands/schema.d.ts +5 -5
- package/dist/_internal/shared/features/cart/schema.d.ts +18 -18
- package/dist/_internal/shared/features/categories/bundle-copy.d.ts +1 -1
- package/dist/_internal/shared/features/categories/bundle-schemas.d.ts +38 -38
- package/dist/_internal/shared/features/events/schema.d.ts +8 -8
- package/dist/_internal/shared/features/orders/schema.d.ts +8 -8
- package/dist/_internal/shared/features/products/schema.d.ts +42 -42
- package/dist/_internal/shared/features/promotions/schema.d.ts +4 -4
- package/dist/_internal/shared/features/reviews/schema.d.ts +1 -1
- package/dist/_internal/shared/media/limits.js +4 -3
- package/dist/client.d.ts +4 -0
- package/dist/client.js +2 -0
- package/dist/configs/next.js +4 -0
- package/dist/constants/api-endpoints.d.ts +6 -0
- package/dist/constants/api-endpoints.js +2 -0
- package/dist/constants/field-names.d.ts +1 -0
- package/dist/constants/field-names.js +1 -0
- package/dist/constants/table-keys.d.ts +6 -0
- package/dist/constants/table-keys.js +9 -0
- package/dist/core/hooks/useSyncManager.js +9 -8
- package/dist/features/about/components/FeesView.js +10 -7
- package/dist/features/about/components/HelpPageView.js +10 -1
- package/dist/features/about/components/HowAuctionsWorkView.js +10 -1
- package/dist/features/about/components/HowCheckoutWorksView.js +10 -1
- package/dist/features/about/components/HowOffersWorkView.js +10 -1
- package/dist/features/about/components/HowOrdersWorkView.js +10 -1
- package/dist/features/about/components/HowPayoutsWorkView.js +10 -1
- package/dist/features/about/components/HowPreOrdersWorkView.js +10 -1
- package/dist/features/about/components/HowReviewsWorkView.js +10 -1
- package/dist/features/about/components/PublicProfileView.js +46 -16
- package/dist/features/about/components/SecurityPrivacyView.js +7 -1
- package/dist/features/account/components/AddressesIndexListing.js +2 -2
- package/dist/features/account/components/NotificationBell.js +27 -16
- package/dist/features/account/components/NotificationPreferencesPanel.d.ts +9 -0
- package/dist/features/account/components/NotificationPreferencesPanel.js +91 -0
- package/dist/features/account/components/UserSidebar.js +4 -4
- package/dist/features/account/components/UserSupportView.js +16 -6
- package/dist/features/account/components/index.d.ts +2 -0
- package/dist/features/account/components/index.js +1 -0
- package/dist/features/account/schemas/index.d.ts +4 -4
- package/dist/features/account/types/index.d.ts +34 -0
- package/dist/features/admin/actions/admin-actions.js +4 -3
- package/dist/features/admin/actions/notification-actions.d.ts +24 -22
- package/dist/features/admin/actions/notification-actions.js +122 -22
- package/dist/features/admin/components/AdminAdEditorView.js +29 -15
- package/dist/features/admin/components/AdminAddressEditorView.d.ts +7 -0
- package/dist/features/admin/components/AdminAddressEditorView.js +117 -0
- package/dist/features/admin/components/AdminAdsView.js +22 -28
- package/dist/features/admin/components/AdminAllEventEntriesView.js +6 -1
- package/dist/features/admin/components/AdminBidsView.js +10 -3
- package/dist/features/admin/components/AdminBlogEditorView.js +2 -2
- package/dist/features/admin/components/AdminBlogView.js +8 -3
- package/dist/features/admin/components/AdminBrandsView.js +7 -2
- package/dist/features/admin/components/AdminBundleEditorView.js +5 -2
- package/dist/features/admin/components/AdminCarouselEditorView.js +6 -3
- package/dist/features/admin/components/AdminCarouselView.js +13 -7
- package/dist/features/admin/components/AdminCategoriesView.js +8 -3
- package/dist/features/admin/components/AdminCategoryEditorView.js +12 -9
- package/dist/features/admin/components/AdminContactView.js +6 -1
- package/dist/features/admin/components/AdminCouponEditorView.js +8 -2
- package/dist/features/admin/components/AdminCouponsView.js +6 -1
- package/dist/features/admin/components/AdminEmployeeEditorView.js +12 -9
- package/dist/features/admin/components/AdminFaqEditorView.js +2 -2
- package/dist/features/admin/components/AdminFaqsView.js +7 -2
- package/dist/features/admin/components/AdminFeatureFlagsView.js +58 -13
- package/dist/features/admin/components/AdminFeaturesView.js +4 -3
- package/dist/features/admin/components/AdminMediaView.js +9 -8
- package/dist/features/admin/components/AdminNavigationView.js +10 -17
- package/dist/features/admin/components/AdminNewsletterView.js +6 -1
- package/dist/features/admin/components/AdminNotificationsView.js +11 -3
- package/dist/features/admin/components/AdminOrdersView.js +6 -1
- package/dist/features/admin/components/AdminPayoutsView.js +10 -3
- package/dist/features/admin/components/AdminPrizeDrawsView.js +10 -3
- package/dist/features/admin/components/AdminProductEditorView.d.ts +3 -1
- package/dist/features/admin/components/AdminProductEditorView.js +12 -9
- package/dist/features/admin/components/AdminProductsView.js +18 -12
- package/dist/features/admin/components/AdminReturnRequestsView.js +5 -4
- package/dist/features/admin/components/AdminReviewsView.js +36 -26
- package/dist/features/admin/components/AdminScammerEditorView.js +3 -2
- package/dist/features/admin/components/AdminScammersView.js +28 -26
- package/dist/features/admin/components/AdminSectionsView.js +112 -99
- package/dist/features/admin/components/AdminSessionsView.js +10 -3
- package/dist/features/admin/components/AdminSidebar.js +4 -4
- package/dist/features/admin/components/AdminSiteSettingsView.js +45 -4
- package/dist/features/admin/components/AdminStoresView.js +6 -1
- package/dist/features/admin/components/AdminSublistingCategoriesView.js +0 -1
- package/dist/features/admin/components/AdminSupportTicketDetailView.js +1 -1
- package/dist/features/admin/components/AdminSupportTicketsView.js +32 -34
- package/dist/features/admin/components/AdminTeamView.js +6 -1
- package/dist/features/admin/components/AdminUserEditorView.js +8 -15
- package/dist/features/admin/components/AdminUsersView.js +6 -1
- package/dist/features/admin/components/AdminViewCards.js +4 -3
- package/dist/features/admin/components/DashboardStats.js +2 -2
- package/dist/features/admin/components/DataTable.js +2 -2
- package/dist/features/admin/components/index.d.ts +2 -0
- package/dist/features/admin/components/index.js +1 -0
- package/dist/features/admin/components/sections/adminSectionsTypes.js +4 -3
- package/dist/features/admin/repository/chat.repository.js +8 -8
- package/dist/features/admin/repository/site-settings.repository.d.ts +1 -0
- package/dist/features/admin/repository/site-settings.repository.js +11 -9
- package/dist/features/admin/schemas/firestore.d.ts +38 -0
- package/dist/features/admin/schemas/firestore.js +15 -0
- package/dist/features/auctions/components/AuctionBidsTable.d.ts +13 -0
- package/dist/features/auctions/components/AuctionBidsTable.js +62 -0
- package/dist/features/auctions/components/AuctionDetailPageView.js +15 -21
- package/dist/features/auctions/components/AuctionFilters.d.ts +7 -1
- package/dist/features/auctions/components/AuctionFilters.js +3 -2
- package/dist/features/auctions/components/MarketplaceAuctionCard.js +18 -9
- package/dist/features/auctions/components/MarketplaceAuctionGrid.js +3 -2
- package/dist/features/auctions/components/index.d.ts +2 -1
- package/dist/features/auctions/components/index.js +1 -1
- package/dist/features/auctions/schemas/index.d.ts +14 -14
- package/dist/features/auth/actions/realtime-token-actions.js +19 -4
- package/dist/features/auth/components/RegisterForm.js +8 -4
- package/dist/features/auth/consent-otp.d.ts +2 -0
- package/dist/features/auth/consent-otp.js +8 -0
- package/dist/features/auth/schemas/firestore.d.ts +2 -0
- package/dist/features/auth/schemas/firestore.js +18 -25
- package/dist/features/auth/schemas/index.d.ts +2 -2
- package/dist/features/before-after/api/[id]/route.js +8 -6
- package/dist/features/before-after/schemas/index.d.ts +2 -2
- package/dist/features/blog/actions/blog-actions.d.ts +3 -3
- package/dist/features/blog/components/BlogIndexListing.js +21 -2
- package/dist/features/blog/components/BlogPostForm.js +11 -5
- package/dist/features/blog/components/BlogPostView.js +10 -2
- package/dist/features/blog/schemas/index.d.ts +18 -18
- package/dist/features/cart/components/CartDrawer.js +3 -2
- package/dist/features/cart/components/CartView.js +2 -1
- package/dist/features/cart/components/CheckoutView.js +2 -1
- package/dist/features/cart/repository/cart.repository.js +4 -3
- package/dist/features/cart/utils/pending-ops.d.ts +1 -1
- package/dist/features/categories/api/[id]/route.js +4 -3
- package/dist/features/categories/api/route.js +4 -3
- package/dist/features/categories/components/BrandDetailPageView.js +2 -2
- package/dist/features/categories/components/BrandDetailTabs.d.ts +5 -1
- package/dist/features/categories/components/BrandDetailTabs.js +30 -16
- package/dist/features/categories/components/BundleCollage.d.ts +8 -0
- package/dist/features/categories/components/BundleCollage.js +50 -0
- package/dist/features/categories/components/BundleDetailView.js +2 -10
- package/dist/features/categories/components/BundleItemsPicker.js +20 -13
- package/dist/features/categories/components/CategoriesIndexListing.js +28 -9
- package/dist/features/categories/components/CategoryDetailPageView.js +2 -2
- package/dist/features/categories/components/CategoryDetailTabs.d.ts +5 -1
- package/dist/features/categories/components/CategoryDetailTabs.js +31 -16
- package/dist/features/categories/components/CategoryProductsListing.js +52 -13
- package/dist/features/categories/components/index.d.ts +2 -0
- package/dist/features/categories/components/index.js +1 -0
- package/dist/features/categories/schemas/index.d.ts +6 -6
- package/dist/features/checkout/actions/checkout-actions.js +4 -3
- package/dist/features/checkout/hooks/usePaymentCheckout.js +31 -22
- package/dist/features/classified/components/ClassifiedFilters.d.ts +12 -0
- package/dist/features/classified/components/ClassifiedFilters.js +20 -0
- package/dist/features/classified/components/ClassifiedIndexListing.d.ts +4 -0
- package/dist/features/classified/components/ClassifiedIndexListing.js +110 -0
- package/dist/features/classified/components/ClassifiedListView.d.ts +6 -0
- package/dist/features/classified/components/ClassifiedListView.js +43 -0
- package/dist/features/classified/components/index.d.ts +6 -0
- package/dist/features/classified/components/index.js +3 -0
- package/dist/features/collections/api/[slug]/route.js +16 -12
- package/dist/features/collections/schemas/index.d.ts +5 -5
- package/dist/features/consultation/api/[id]/route.js +12 -9
- package/dist/features/consultation/components/ConsultationForm.js +3 -2
- package/dist/features/consultation/schemas/index.d.ts +6 -6
- package/dist/features/contact/email.js +77 -116
- package/dist/features/copilot/components/AdminCopilotView.js +12 -2
- package/dist/features/corporate/api/[id]/route.js +12 -9
- package/dist/features/corporate/components/CorporateInquiryForm.js +2 -1
- package/dist/features/digital-codes/components/DigitalCodeFilters.d.ts +12 -0
- package/dist/features/digital-codes/components/DigitalCodeFilters.js +17 -0
- package/dist/features/digital-codes/components/DigitalCodesIndexListing.d.ts +4 -0
- package/dist/features/digital-codes/components/DigitalCodesIndexListing.js +107 -0
- package/dist/features/digital-codes/components/DigitalCodesListView.d.ts +6 -0
- package/dist/features/digital-codes/components/DigitalCodesListView.js +37 -0
- package/dist/features/digital-codes/components/index.d.ts +6 -0
- package/dist/features/digital-codes/components/index.js +3 -0
- package/dist/features/events/actions/event-actions.js +4 -3
- package/dist/features/events/components/AdminEventEditorView.js +3 -2
- package/dist/features/events/components/AdminEventsView.js +4 -2
- package/dist/features/events/components/EventPollWidget.js +5 -5
- package/dist/features/events/components/EventsIndexListing.js +2 -2
- package/dist/features/events/repository/event-entry.repository.d.ts +1 -0
- package/dist/features/events/repository/event-entry.repository.js +5 -0
- package/dist/features/events/schemas/index.d.ts +20 -20
- package/dist/features/faq/actions/faq-actions.d.ts +8 -8
- package/dist/features/faq/components/ContactCTA.js +3 -1
- package/dist/features/faq/components/FAQAccordion.js +1 -1
- package/dist/features/faq/schemas/index.d.ts +4 -4
- package/dist/features/filters/AsyncFacetSection.d.ts +18 -0
- package/dist/features/filters/AsyncFacetSection.js +65 -0
- package/dist/features/filters/index.d.ts +2 -0
- package/dist/features/filters/index.js +1 -0
- package/dist/features/forms/Checkbox.js +2 -2
- package/dist/features/forms/Input.js +2 -2
- package/dist/features/forms/Radio.js +5 -5
- package/dist/features/forms/Select.js +2 -2
- package/dist/features/forms/Slider.js +2 -2
- package/dist/features/forms/Textarea.js +2 -2
- package/dist/features/homepage/api/[id]/route.js +4 -3
- package/dist/features/homepage/api/sections/[id]/route.js +12 -9
- package/dist/features/homepage/components/AdSlot.js +2 -1
- package/dist/features/homepage/components/AdvertisementBanner.js +5 -3
- package/dist/features/homepage/components/BrandsSection.js +2 -2
- package/dist/features/homepage/components/CTABannerSection.js +2 -2
- package/dist/features/homepage/components/CharacterHotspot.d.ts +2 -2
- package/dist/features/homepage/components/CharacterHotspot.js +119 -157
- package/dist/features/homepage/components/CharacterHotspotForm.js +25 -20
- package/dist/features/homepage/components/CustomCardsSection.js +8 -6
- package/dist/features/homepage/components/FeaturedProductsSection.js +2 -2
- package/dist/features/homepage/components/GoogleReviewsSection.js +3 -3
- package/dist/features/homepage/components/HeroBanner.js +5 -4
- package/dist/features/homepage/components/HeroCarousel.js +13 -26
- package/dist/features/homepage/components/HowItWorksSection.js +4 -2
- package/dist/features/homepage/components/SocialFeedSection.js +2 -2
- package/dist/features/homepage/components/SocialPostCard.js +3 -2
- package/dist/features/homepage/lib/social-feed-fetcher.js +6 -3
- package/dist/features/layout/AppLayoutShell.d.ts +8 -2
- package/dist/features/layout/AppLayoutShell.js +54 -78
- package/dist/features/layout/BottomActions.js +43 -48
- package/dist/features/live/components/LiveItemFilters.d.ts +12 -0
- package/dist/features/live/components/LiveItemFilters.js +24 -0
- package/dist/features/live/components/LiveItemsIndexListing.d.ts +4 -0
- package/dist/features/live/components/LiveItemsIndexListing.js +108 -0
- package/dist/features/live/components/LiveItemsListView.d.ts +6 -0
- package/dist/features/live/components/LiveItemsListView.js +43 -0
- package/dist/features/live/components/index.d.ts +6 -0
- package/dist/features/live/components/index.js +3 -0
- package/dist/features/media/MediaLightbox.js +8 -14
- package/dist/features/media/types/index.d.ts +2 -2
- package/dist/features/media/upload/CameraCapture.js +2 -2
- package/dist/features/media/upload/ImageUpload.js +4 -2
- package/dist/features/media/upload/MediaUploadField.js +24 -8
- package/dist/features/media/upload/MediaUploadList.js +1 -1
- package/dist/features/orders/actions/order-actions.js +4 -3
- package/dist/features/orders/components/MarketplaceOrderCard.js +2 -1
- package/dist/features/orders/components/OrderFilters.js +12 -9
- package/dist/features/orders/schemas/index.d.ts +6 -6
- package/dist/features/pre-orders/api/[id]/route.js +12 -9
- package/dist/features/pre-orders/components/MarketplacePreorderCard.js +5 -4
- package/dist/features/pre-orders/components/PreOrderDetailPageView.js +29 -22
- package/dist/features/pre-orders/components/PreOrderFilters.d.ts +7 -1
- package/dist/features/pre-orders/components/PreOrderFilters.js +3 -2
- package/dist/features/pre-orders/components/PreOrdersIndexListing.js +17 -52
- package/dist/features/pre-orders/components/PreorderCard.d.ts +0 -9
- package/dist/features/pre-orders/components/PreorderCard.js +2 -13
- package/dist/features/pre-orders/components/index.d.ts +1 -1
- package/dist/features/pre-orders/components/index.js +1 -1
- package/dist/features/pre-orders/schemas/index.d.ts +10 -10
- package/dist/features/products/api/[id]/route.js +9 -7
- package/dist/features/products/api/route.js +1 -1
- package/dist/features/products/columns/productTableColumns.js +3 -3
- package/dist/features/products/components/AuctionsIndexListing.js +15 -51
- package/dist/features/products/components/BidHistory.js +2 -2
- package/dist/features/products/components/CompareOverlay.d.ts +2 -0
- package/dist/features/products/components/CompareOverlay.js +3 -2
- package/dist/features/products/components/MakeOfferButton.js +20 -14
- package/dist/features/products/components/MarketplacePrizeDrawCard.js +2 -1
- package/dist/features/products/components/PrizeDrawCollage.d.ts +1 -15
- package/dist/features/products/components/PrizeDrawCollage.js +54 -14
- package/dist/features/products/components/PrizeDrawDetailPageView.js +2 -1
- package/dist/features/products/components/PrizeDrawEntryActions.js +2 -1
- package/dist/features/products/components/PrizeDrawItemsEditor.js +9 -7
- package/dist/features/products/components/PrizeDrawsIndexListing.js +18 -51
- package/dist/features/products/components/PrizeRevealModal.js +24 -14
- package/dist/features/products/components/ProductDetailPageView.js +2 -1
- package/dist/features/products/components/ProductFilters.d.ts +7 -1
- package/dist/features/products/components/ProductFilters.js +11 -8
- package/dist/features/products/components/ProductForm.js +18 -16
- package/dist/features/products/components/ProductGalleryClient.js +2 -1
- package/dist/features/products/components/ProductGrid.js +12 -15
- package/dist/features/products/components/ProductsIndexListing.js +16 -52
- package/dist/features/products/components/ProductsIndexPageView.js +2 -2
- package/dist/features/products/repository/products.repository.d.ts +4 -0
- package/dist/features/products/repository/products.repository.js +22 -3
- package/dist/features/products/schemas/firestore.d.ts +7 -1
- package/dist/features/products/schemas/index.d.ts +56 -56
- package/dist/features/products/schemas/product-features.validators.d.ts +14 -14
- package/dist/features/products/types/index.d.ts +4 -0
- package/dist/features/promotions/components/CouponsIndexListing.js +1 -1
- package/dist/features/promotions/schemas/index.d.ts +6 -6
- package/dist/features/reviews/actions/review-actions.js +6 -5
- package/dist/features/reviews/api/[id]/route.js +4 -3
- package/dist/features/reviews/components/ReviewDetailPageView.js +2 -2
- package/dist/features/reviews/components/ReviewDetailShell.js +6 -3
- package/dist/features/reviews/components/ReviewsIndexListing.js +2 -2
- package/dist/features/reviews/schemas/index.d.ts +6 -6
- package/dist/features/scams/components/ScamProfileView.js +19 -10
- package/dist/features/scams/components/ScamRegistryView.js +2 -1
- package/dist/features/search/components/Search.d.ts +1 -1
- package/dist/features/search/components/Search.js +56 -49
- package/dist/features/search/schemas/index.d.ts +5 -5
- package/dist/features/seller/actions/offer-actions.js +11 -9
- package/dist/features/seller/components/SellerAddressesView.js +6 -4
- package/dist/features/seller/components/SellerBidsView.js +2 -2
- package/dist/features/seller/components/SellerCouponsView.js +1 -1
- package/dist/features/seller/components/SellerOffersPanel.js +6 -1
- package/dist/features/seller/components/SellerOrdersView.js +1 -1
- package/dist/features/seller/components/SellerPayoutSettingsView.js +3 -3
- package/dist/features/seller/components/SellerPreOrdersView.js +5 -4
- package/dist/features/seller/components/SellerPrizeDrawsView.js +5 -4
- package/dist/features/seller/components/SellerProductShell.d.ts +19 -1
- package/dist/features/seller/components/SellerProductShell.js +75 -9
- package/dist/features/seller/components/SellerProductsView.js +10 -11
- package/dist/features/seller/components/SellerShippingView.js +3 -3
- package/dist/features/seller/components/SellerSidebar.js +10 -7
- package/dist/features/seller/components/SellerStorefrontView.js +3 -3
- package/dist/features/seller/schemas/index.d.ts +4 -4
- package/dist/features/shell/FormShell.js +2 -1
- package/dist/features/shell/QuickFormDrawer.js +4 -3
- package/dist/features/shell/StepForm.js +4 -3
- package/dist/features/stores/actions/store-query-actions.js +4 -3
- package/dist/features/stores/components/StoreAboutView.js +2 -1
- package/dist/features/stores/components/StoreAuctionsListing.js +43 -64
- package/dist/features/stores/components/StoreDetailLayoutView.js +18 -4
- package/dist/features/stores/components/StorePreOrdersListing.js +33 -57
- package/dist/features/stores/components/StoreProductsListing.js +16 -49
- package/dist/features/stores/components/StoresIndexListing.js +2 -2
- package/dist/features/stores/schemas/index.d.ts +2 -2
- package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +8 -7
- package/dist/features/wishlist/hooks/useGuestWishlist.d.ts +5 -5
- package/dist/features/wishlist/hooks/useWishlistWithGuest.d.ts +6 -6
- package/dist/features/wishlist/utils/guest-wishlist.d.ts +5 -5
- package/dist/index.d.ts +27 -3
- package/dist/index.js +1743 -1725
- package/dist/monitoring/server-logger.js +5 -9
- package/dist/next/api/apiHandler.js +56 -37
- package/dist/next/components/GlobalError.js +2 -1
- package/dist/next/routing/route-map.d.ts +41 -0
- package/dist/next/routing/route-map.js +17 -0
- package/dist/react/ErrorBoundary.js +2 -1
- package/dist/react/contexts/SessionContext.js +38 -28
- package/dist/react/hooks/useCamera.d.ts +1 -1
- package/dist/react/hooks/useCamera.js +8 -7
- package/dist/seed/products-auctions-seed-data.js +40 -40
- package/dist/seed/products-preorders-seed-data.js +16 -16
- package/dist/seed/products-prize-draws-seed-data.js +4 -4
- package/dist/seed/products-standard-seed-data.js +202 -202
- package/dist/seed/site-settings-seed-data.js +6 -0
- package/dist/server-entry.d.ts +3 -0
- package/dist/server-entry.js +3 -0
- package/dist/server.d.ts +12 -0
- package/dist/server.js +15 -0
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/DataTable.js +28 -7
- package/dist/ui/components/Button.d.ts +8 -1
- package/dist/ui/components/Button.js +47 -8
- package/dist/ui/components/CollageLightbox.d.ts +18 -0
- package/dist/ui/components/CollageLightbox.js +41 -0
- package/dist/ui/components/Dropdown.js +7 -6
- package/dist/ui/components/ImageLightbox.d.ts +7 -1
- package/dist/ui/components/ImageLightbox.js +15 -5
- package/dist/ui/components/PageLoader.js +4 -3
- package/dist/ui/components/PaginatedMultiSelect.d.ts +28 -0
- package/dist/ui/components/PaginatedMultiSelect.js +141 -0
- package/dist/ui/components/PaginatedMultiSelect.style.css +272 -0
- package/dist/ui/components/Semantic.js +1 -1
- package/dist/ui/components/SideDrawer.js +17 -15
- package/dist/ui/components/index.style.css +1 -0
- package/dist/ui/index.d.ts +2 -0
- package/dist/ui/index.js +1 -0
- package/dist/utils/number.formatter.js +5 -13
- package/dist/utils/string.formatter.js +21 -18
- package/dist/validation/schemas.d.ts +2 -2
- package/package.json +1 -1
|
@@ -5,7 +5,7 @@ import { Plus, X } from "lucide-react";
|
|
|
5
5
|
import { useUrlTable } from "../../../react/hooks/useUrlTable";
|
|
6
6
|
import { usePanelUrlSync } from "../../../react/hooks/use-panel-url-sync";
|
|
7
7
|
import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
|
|
8
|
-
import { BulkActionBar, Button,
|
|
8
|
+
import { BulkActionBar, Button, Heading, ListingToolbar, ListingViewShell, Pagination, SideDrawer, Text } from "../../../ui";
|
|
9
9
|
import { AdminViewCards } from "./AdminViewCards";
|
|
10
10
|
import { CATEGORY_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
11
11
|
import { toRecordArray, toRelativeDate, toStringValue, useAdminListingData, } from "../hooks/useAdminListingData";
|
|
@@ -19,6 +19,11 @@ const SORT_OPTIONS = [
|
|
|
19
19
|
{ value: "-name", label: "Name Z–A" },
|
|
20
20
|
{ value: "-createdAt", label: "Newest" },
|
|
21
21
|
];
|
|
22
|
+
function CategoriesFilterDrawer({ filterOpen, setFilterOpen, activeFilterCount, clearFilters, pendingFilters, setPendingFilters, applyFilters, }) {
|
|
23
|
+
if (!filterOpen)
|
|
24
|
+
return null;
|
|
25
|
+
return (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 z-40 bg-black/40", "aria-hidden": "true", onClick: () => setFilterOpen(false) }), _jsxs("div", { className: "fixed inset-y-0 left-0 z-50 flex w-80 flex-col bg-white dark:bg-slate-900 shadow-2xl", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: [_jsx("span", { className: "text-base font-semibold text-zinc-900 dark:text-zinc-100", children: "Filters" }), _jsxs("div", { className: "flex items-center gap-2", children: [activeFilterCount > 0 && (_jsx("button", { type: "button", onClick: clearFilters, className: "text-xs text-zinc-500 hover:text-rose-500 dark:text-zinc-400 transition-colors", children: "Clear all" })), _jsx("button", { type: "button", onClick: () => setFilterOpen(false), "aria-label": "Close", className: "rounded-lg p-1.5 text-zinc-500 hover:bg-zinc-100 dark:hover:bg-slate-800 transition-colors", children: _jsx(X, { className: "h-5 w-5" }) })] })] }), _jsxs("div", { className: "flex-1 overflow-y-auto px-4 py-4 space-y-5", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Text, { className: "text-xs font-semibold uppercase tracking-widest text-zinc-500 dark:text-zinc-400", children: "Active" }), _jsx("div", { className: "flex flex-wrap gap-2", children: [{ label: "All", value: "" }, { label: "Active", value: "true" }, { label: "Inactive", value: "false" }].map((opt) => (_jsx("button", { type: "button", onClick: () => setPendingFilters((p) => ({ ...p, isActive: opt.value })), className: `rounded-full px-3 py-1 text-xs font-medium border transition-colors ${(pendingFilters.isActive || "") === opt.value ? "bg-primary text-white border-primary" : "border-zinc-300 dark:border-slate-600 text-zinc-700 dark:text-zinc-300 hover:bg-zinc-50 dark:hover:bg-slate-800"}`, children: opt.label }, opt.label))) })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Text, { className: "text-xs font-semibold uppercase tracking-widest text-zinc-500 dark:text-zinc-400", children: "Featured" }), _jsx("div", { className: "flex flex-wrap gap-2", children: [{ label: "All", value: "" }, { label: "Featured only", value: "true" }].map((opt) => (_jsx("button", { type: "button", onClick: () => setPendingFilters((p) => ({ ...p, isFeatured: opt.value })), className: `rounded-full px-3 py-1 text-xs font-medium border transition-colors ${(pendingFilters.isFeatured || "") === opt.value ? "bg-primary text-white border-primary" : "border-zinc-300 dark:border-slate-600 text-zinc-700 dark:text-zinc-300 hover:bg-zinc-50 dark:hover:bg-slate-800"}`, children: opt.label }, opt.label))) })] })] }), _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})` : ""] }) })] })] }));
|
|
26
|
+
}
|
|
22
27
|
export function AdminCategoriesView({ children, getRowHref, ...props }) {
|
|
23
28
|
const hasChildren = React.Children.count(children) > 0;
|
|
24
29
|
const [view, setView] = useState("table");
|
|
@@ -87,8 +92,8 @@ export function AdminCategoriesView({ children, getRowHref, ...props }) {
|
|
|
87
92
|
if (hasChildren) {
|
|
88
93
|
return _jsx(ListingViewShell, { portal: "admin", ...props, children: children });
|
|
89
94
|
}
|
|
90
|
-
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search categories, slugs, or parent category", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState, extra: _jsxs(Button, { size: "sm", onClick: openCreatePanel, className: "flex items-center gap-1.5", children: [_jsx(Plus, { className: "h-4 w-4" }), "Add Category"] }) }), _jsx(BulkActionBar, { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: [
|
|
95
|
+
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(Heading, { level: 1, className: "sr-only", children: "Categories" }), _jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search categories, slugs, or parent category", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState, extra: _jsxs(Button, { size: "sm", onClick: openCreatePanel, className: "flex items-center gap-1.5", children: [_jsx(Plus, { className: "h-4 w-4" }), "Add Category"] }) }), _jsx(BulkActionBar, { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: [
|
|
91
96
|
{ id: "edit", label: "Edit Category", variant: "primary", onClick: () => { const id = selection.selectedIds[0]; if (id)
|
|
92
97
|
openEditPanel(id); selection.clearSelection(); } },
|
|
93
|
-
] }), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })), view === "table" ? (_jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No categories found", selectedIds: selection.selectedIdSet, onToggleSelect: selection.toggle, onToggleSelectAll: (next) => next ? selection.setSelectedIds(rows.map(r => r.id)) : selection.clearSelection(), onRowClick: (row) => openEditPanel(row.id) })) : (_jsx(AdminViewCards, { rows: rows, view: view, isLoading: isLoading, emptyLabel: "No categories found", onRowClick: (row) => openEditPanel(row.id), selectedIdSet: selection.selectedIdSet, onToggleSelect: selection.toggle }))] }),
|
|
98
|
+
] }), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })), view === "table" ? (_jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No categories found", selectedIds: selection.selectedIdSet, onToggleSelect: selection.toggle, onToggleSelectAll: (next) => next ? selection.setSelectedIds(rows.map(r => r.id)) : selection.clearSelection(), onRowClick: (row) => openEditPanel(row.id) })) : (_jsx(AdminViewCards, { rows: rows, view: view, isLoading: isLoading, emptyLabel: "No categories found", onRowClick: (row) => openEditPanel(row.id), selectedIdSet: selection.selectedIdSet, onToggleSelect: selection.toggle }))] }), _jsx(CategoriesFilterDrawer, { filterOpen: filterOpen, setFilterOpen: setFilterOpen, activeFilterCount: activeFilterCount, clearFilters: clearFilters, pendingFilters: pendingFilters, setPendingFilters: setPendingFilters, applyFilters: applyFilters }), _jsx(SideDrawer, { isOpen: isCreateOpen || isEditOpen, onClose: closePanel, title: isCreateOpen ? "Add Category" : "Edit Category", mode: isCreateOpen ? "create" : "edit", children: (isCreateOpen || isEditOpen) && (_jsx(AdminCategoryEditorView, { categoryId: editId ?? undefined, onSaved: closePanel, onDeleted: closePanel, embedded: true })) })] }));
|
|
94
99
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import React from "react";
|
|
4
4
|
import { useMutation, useQuery } from "@tanstack/react-query";
|
|
5
|
-
import { Button,
|
|
5
|
+
import { Button, Card, CardBody, ConfirmDeleteModal, Form, InlineCreateSelect, Input, StackedViewShell, Text, Toggle, useToast } from "../../../ui";
|
|
6
6
|
import { apiClient } from "../../../http";
|
|
7
7
|
import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
8
8
|
import { CategoryQuickCreateForm } from "./CategoryQuickCreateForm";
|
|
@@ -28,6 +28,7 @@ function toCategorySlug(str) {
|
|
|
28
28
|
}
|
|
29
29
|
export function AdminCategoryEditorView({ categoryId, onSaved, onDeleted, embedded, ...rest }) {
|
|
30
30
|
const isEdit = Boolean(categoryId);
|
|
31
|
+
const [deleteOpen, setDeleteOpen] = React.useState(false);
|
|
31
32
|
const [name, setName] = React.useState("");
|
|
32
33
|
const [slug, setSlug] = React.useState("");
|
|
33
34
|
const [slugManual, setSlugManual] = React.useState(false);
|
|
@@ -99,19 +100,21 @@ export function AdminCategoryEditorView({ categoryId, onSaved, onDeleted, embedd
|
|
|
99
100
|
onError: (err) => showToast(err?.message ?? "Failed to delete category.", "error"),
|
|
100
101
|
});
|
|
101
102
|
const isSubmitting = saveMutation.isPending || categoryQuery.isLoading;
|
|
102
|
-
const
|
|
103
|
+
const actionSidebar = (_jsxs(Card, { variant: "outlined", padding: "md", className: "space-y-3", children: [_jsx(Text, { className: "text-xs font-semibold uppercase tracking-widest text-zinc-500 dark:text-zinc-400", children: "Status" }), _jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: isEdit ? (isActive ? "Active" : "Inactive") : "New" }), _jsx(Button, { type: "submit", form: "category-editor-form", className: "w-full", isLoading: isSubmitting, disabled: !name || isSubmitting, children: isEdit ? "Save changes" : "Create category" }), isEdit && (_jsx(Button, { type: "button", variant: "danger", className: "w-full", isLoading: deleteMutation.isPending, onClick: () => setDeleteOpen(true), children: "Delete category" }))] }));
|
|
104
|
+
const formContent = (_jsxs(Form, { id: "category-editor-form", onSubmit: (e) => {
|
|
103
105
|
e.preventDefault();
|
|
104
106
|
saveMutation.mutate();
|
|
105
|
-
}, className: "space-y-4", children: [_jsxs("div", { className: "grid sm:grid-cols-2 gap-4", children: [_jsx(Input, { label: "Category name", value: name, onChange: (e) => handleNameChange(e.target.value), required: true, placeholder: "e.g. Toys & Games" }), _jsx(Input, { label: "Slug", value: slug, onChange: (e) => {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
107
|
+
}, className: "space-y-6", children: [_jsxs(Card, { variant: "outlined", padding: "lg", children: [_jsx(Text, { className: "text-xs font-semibold uppercase tracking-widest text-zinc-500 dark:text-zinc-400 mb-4", children: "Identity" }), _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "grid sm:grid-cols-2 gap-4", children: [_jsx(Input, { label: "Category name", value: name, onChange: (e) => handleNameChange(e.target.value), required: true, placeholder: "e.g. Toys & Games" }), _jsx(Input, { label: "Slug", value: slug, onChange: (e) => {
|
|
108
|
+
setSlug(e.target.value);
|
|
109
|
+
setSlugManual(true);
|
|
110
|
+
}, placeholder: "toys-and-games", helperText: "Auto-generated from name. Used in URLs." })] }), _jsx(Input, { label: "Description", value: description, onChange: (e) => setDescription(e.target.value), placeholder: "Brief description of the category" }), _jsxs("div", { className: "flex flex-col gap-1", children: [_jsx(Text, { className: "text-sm font-medium text-zinc-700 dark:text-zinc-300", children: "Parent category" }), _jsx(InlineCreateSelect, { value: parentId || null, onChange: (v) => setParentId(v ?? ""), loadOptions: loadCategoryOptions, placeholder: "Search categories\u2026 (leave empty for root)", searchPlaceholder: "Type category name\u2026", noResultsText: "No categories found", ariaLabel: "Parent category", createLabel: "Category", renderCreateForm: ({ onCreated, onCancel }) => (_jsx(CategoryQuickCreateForm, { onSaved: (id, n) => { setParentId(id); onCreated({ value: id, label: n }); }, onCancel: onCancel })) }), _jsx(Text, { className: "text-xs text-neutral-500 dark:text-neutral-400", children: "Leave empty to create a root category." })] })] })] }), _jsxs(Card, { variant: "outlined", padding: "lg", children: [_jsx(Text, { className: "text-xs font-semibold uppercase tracking-widest text-zinc-500 dark:text-zinc-400 mb-4", children: "Display" }), _jsxs("div", { className: "space-y-4", children: [_jsx(Input, { label: "Display order", value: order, onChange: (e) => setOrder(e.target.value), type: "number", min: 0, placeholder: "0" }), _jsx(Toggle, { label: "Active", checked: isActive, onChange: setIsActive }), _jsx(Toggle, { label: "Show in menu", checked: showInMenu, onChange: setShowInMenu })] })] }), _jsxs("div", { className: "flex gap-3 lg:hidden", children: [_jsx(Button, { type: "submit", isLoading: isSubmitting, disabled: !name || isSubmitting, children: isEdit ? "Save changes" : "Create category" }), isEdit && (_jsx(Button, { type: "button", variant: "danger", isLoading: deleteMutation.isPending, onClick: () => {
|
|
109
111
|
if (confirm("Delete this category? Products in this category will become uncategorized.")) {
|
|
110
112
|
deleteMutation.mutate();
|
|
111
113
|
}
|
|
112
114
|
}, children: "Delete category" }))] })] }, "cat-form"));
|
|
113
115
|
if (embedded) {
|
|
114
|
-
return _jsx("div", { className: "overflow-y-auto p-4", children:
|
|
116
|
+
return _jsx("div", { className: "overflow-y-auto p-4", children: formContent });
|
|
115
117
|
}
|
|
116
|
-
|
|
118
|
+
const twoPanel = (_jsxs("div", { className: "grid gap-6 lg:grid-cols-[1fr_280px] lg:items-start", children: [_jsx(CardBody, { className: "min-w-0 space-y-6 p-0", children: formContent }), _jsx("div", { className: "hidden lg:block lg:sticky lg:top-[var(--header-height,0px)]", children: actionSidebar })] }));
|
|
119
|
+
return (_jsxs(_Fragment, { children: [_jsx(StackedViewShell, { portal: "admin", ...rest, title: isEdit ? "Edit Category" : "Create Category", sections: [twoPanel] }), _jsx(ConfirmDeleteModal, { isOpen: deleteOpen, onClose: () => setDeleteOpen(false), onConfirm: () => deleteMutation.mutate(), isDeleting: deleteMutation.isPending, title: "Delete this category?", message: "Products in this category will become uncategorized. This action cannot be undone.", confirmText: "Delete category", variant: "danger" })] }));
|
|
117
120
|
}
|
|
@@ -20,6 +20,11 @@ const SORT_OPTIONS = [
|
|
|
20
20
|
{ value: "createdAt", label: "Oldest" },
|
|
21
21
|
];
|
|
22
22
|
const STATUS_OPTIONS = ADMIN_CONTACT_STATUS_TABS;
|
|
23
|
+
function ContactFilterDrawer({ filterOpen, setFilterOpen, activeFilterCount, clearFilters, pendingFilters, setPendingFilters, applyFilters, }) {
|
|
24
|
+
if (!filterOpen)
|
|
25
|
+
return null;
|
|
26
|
+
return (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 z-40 bg-black/40", "aria-hidden": "true", onClick: () => setFilterOpen(false) }), _jsxs("div", { className: "fixed inset-y-0 left-0 z-50 flex w-80 flex-col bg-white dark:bg-slate-900 shadow-2xl", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: [_jsx("span", { className: "text-base font-semibold text-zinc-900 dark:text-zinc-100", children: "Filters" }), _jsxs("div", { className: "flex items-center gap-2", children: [activeFilterCount > 0 && (_jsx("button", { type: "button", onClick: clearFilters, className: "text-xs text-zinc-500 hover:text-rose-500 dark:text-zinc-400 transition-colors", children: "Clear all" })), _jsx("button", { type: "button", onClick: () => setFilterOpen(false), "aria-label": "Close", className: "rounded-lg p-1.5 text-zinc-500 hover:bg-zinc-100 dark:hover:bg-slate-800 transition-colors", children: _jsx(X, { className: "h-5 w-5" }) })] })] }), _jsx("div", { className: "flex-1 overflow-y-auto px-4 py-4 space-y-5", children: _jsx(FilterChipGroup, { label: "Status", tabs: STATUS_OPTIONS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) }) }), _jsx("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})` : ""] }) })] })] }));
|
|
27
|
+
}
|
|
23
28
|
export function AdminContactView({ children, ...props }) {
|
|
24
29
|
const hasChildren = React.Children.count(children) > 0;
|
|
25
30
|
const [view, setView] = useState("table");
|
|
@@ -140,6 +145,6 @@ export function AdminContactView({ children, ...props }) {
|
|
|
140
145
|
onClick: () => { setSelectedRow(cr); setDeleteOpen(true); },
|
|
141
146
|
},
|
|
142
147
|
] }));
|
|
143
|
-
} })] }),
|
|
148
|
+
} })] }), _jsx(ContactFilterDrawer, { filterOpen: filterOpen, setFilterOpen: setFilterOpen, activeFilterCount: activeFilterCount, clearFilters: clearFilters, pendingFilters: pendingFilters, setPendingFilters: setPendingFilters, applyFilters: applyFilters })] }), _jsx(AdminContactEditorView, { open: drawerOpen, onClose: () => { setDrawerOpen(false); setSelectedRow(null); }, submissionId: selectedRow?.id, subject: toStringValue(selectedRow?._raw?.subject, "No subject"), name: toStringValue(selectedRow?._raw?.name, ""), email: toStringValue(selectedRow?._raw?.email, ""), message: toStringValue(selectedRow?._raw?.message ?? selectedRow?._raw?.body, ""), currentStatus: selectedRow?.status }), _jsx(ConfirmDeleteModal, { isOpen: deleteOpen, onClose: () => { setDeleteOpen(false); setSelectedRow(null); }, onConfirm: () => { if (selectedRow)
|
|
144
149
|
deleteMutation.mutate(selectedRow.id); }, isDeleting: deleteMutation.isPending, title: "Delete submission?", message: "This contact submission will be permanently removed.", confirmText: "Delete", variant: "danger" })] }));
|
|
145
150
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import React from "react";
|
|
4
4
|
import { useMutation, useQuery } from "@tanstack/react-query";
|
|
5
5
|
import { Button, Form, Input, Select, StackedViewShell, Toggle, useToast, } from "../../../ui";
|
|
@@ -25,6 +25,12 @@ function toDateInputValue(val) {
|
|
|
25
25
|
return "";
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
|
+
function CouponDiscountFields({ type, discountValue, setDiscountValue, maxDiscount, setMaxDiscount, minPurchase, setMinPurchase, buyQty, setBuyQty, getQty, setGetQty, discountLabel, }) {
|
|
29
|
+
return (_jsxs(_Fragment, { children: [type !== "free_shipping" && type !== "buy_x_get_y" && (_jsx(Input, { label: discountLabel, value: discountValue, onChange: (e) => setDiscountValue(e.target.value), type: "number", min: 0, required: true, placeholder: type === "percentage" ? "e.g. 20" : "e.g. 5000" })), type === "percentage" && (_jsx(Input, { label: "Max discount cap (paise, optional)", value: maxDiscount, onChange: (e) => setMaxDiscount(e.target.value), type: "number", min: 0, placeholder: "e.g. 20000", helperText: "Leave blank for no cap." })), (type === "percentage" || type === "fixed") && (_jsx(Input, { label: "Min order value (paise, optional)", value: minPurchase, onChange: (e) => setMinPurchase(e.target.value), type: "number", min: 0, placeholder: "e.g. 50000", helperText: "Leave blank for no minimum." })), type === "buy_x_get_y" && (_jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsx(Input, { label: "Buy quantity", value: buyQty, onChange: (e) => setBuyQty(e.target.value), type: "number", min: 1, required: true }), _jsx(Input, { label: "Get quantity", value: getQty, onChange: (e) => setGetQty(e.target.value), type: "number", min: 1, required: true })] }))] }));
|
|
30
|
+
}
|
|
31
|
+
function CouponValidityFields({ startDate, setStartDate, endDate, setEndDate, isActive, setIsActive, totalLimit, setTotalLimit, perUserLimit, setPerUserLimit, isEdit, currentUsage, }) {
|
|
32
|
+
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsx(Input, { label: "Total usage limit (optional)", value: totalLimit, onChange: (e) => setTotalLimit(e.target.value), type: "number", min: 0, placeholder: "Unlimited" }), _jsx(Input, { label: "Per-user limit (optional)", value: perUserLimit, onChange: (e) => setPerUserLimit(e.target.value), type: "number", min: 0, placeholder: "Unlimited" })] }), isEdit && (_jsx(Input, { label: "Current usage", value: String(currentUsage), disabled: true, helperText: "Read-only \u2014 updated by orders." })), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsx(Input, { label: "Start date", value: startDate, onChange: (e) => setStartDate(e.target.value), type: "date", required: true }), _jsx(Input, { label: "End date (optional)", value: endDate, onChange: (e) => setEndDate(e.target.value), type: "date", helperText: "Leave blank for no expiry." })] }), _jsx(Toggle, { label: "Active", checked: isActive, onChange: setIsActive })] }));
|
|
33
|
+
}
|
|
28
34
|
// --- Component ---------------------------------------------------------------
|
|
29
35
|
export function AdminCouponEditorView({ couponId, onSaved, onDeleted, embedded, ...rest }) {
|
|
30
36
|
const isEdit = Boolean(couponId);
|
|
@@ -169,7 +175,7 @@ export function AdminCouponEditorView({ couponId, onSaved, onDeleted, embedded,
|
|
|
169
175
|
}, className: "space-y-5", children: [_jsx(Select, { label: "Coupon type", options: TYPE_OPTIONS, value: type, onValueChange: (v) => setType(v), required: true }), _jsx(Input, { label: "Campaign name", value: name, onChange: (e) => handleNameChange(e.target.value), required: true, placeholder: "e.g. Summer Sale 20%" }), !isEdit && (_jsx(Input, { label: "Coupon code", value: code, onChange: (e) => {
|
|
170
176
|
setCode(toCouponCode(e.target.value));
|
|
171
177
|
setCodeManual(true);
|
|
172
|
-
}, required: true, placeholder: "e.g. SUMMER20", helperText: "Auto-generated from name. Uppercase alphanumeric + hyphens only." })), isEdit && (_jsx(Input, { label: "Coupon code", value: code, disabled: true, helperText: "Code cannot be changed after creation." })), _jsx(Input, { label: "Description (optional)", value: description, onChange: (e) => setDescription(e.target.value), placeholder: "Internal notes about this coupon" }),
|
|
178
|
+
}, required: true, placeholder: "e.g. SUMMER20", helperText: "Auto-generated from name. Uppercase alphanumeric + hyphens only." })), isEdit && (_jsx(Input, { label: "Coupon code", value: code, disabled: true, helperText: "Code cannot be changed after creation." })), _jsx(Input, { label: "Description (optional)", value: description, onChange: (e) => setDescription(e.target.value), placeholder: "Internal notes about this coupon" }), _jsx(CouponDiscountFields, { type: type, discountValue: discountValue, setDiscountValue: setDiscountValue, maxDiscount: maxDiscount, setMaxDiscount: setMaxDiscount, minPurchase: minPurchase, setMinPurchase: setMinPurchase, buyQty: buyQty, setBuyQty: setBuyQty, getQty: getQty, setGetQty: setGetQty, discountLabel: discountLabel }), _jsx(CouponValidityFields, { startDate: startDate, setStartDate: setStartDate, endDate: endDate, setEndDate: setEndDate, isActive: isActive, setIsActive: setIsActive, totalLimit: totalLimit, setTotalLimit: setTotalLimit, perUserLimit: perUserLimit, setPerUserLimit: setPerUserLimit, isEdit: isEdit, currentUsage: currentUsage }), _jsx(Toggle, { label: "First-time users only", checked: firstTimeOnly, onChange: setFirstTimeOnly }), _jsx(Toggle, { label: "Allow stacking with seller coupons", checked: combinable, onChange: setCombinable }), _jsx(Toggle, { label: "Applies to auctions", checked: appliesToAuctions, onChange: setAppliesToAuctions }), _jsxs("div", { className: "flex gap-3 pt-2", children: [_jsx(Button, { type: "submit", isLoading: isSubmitting, disabled: !canSave || isSubmitting, children: isEdit ? "Save changes" : "Create coupon" }), isEdit && (_jsx(Button, { type: "button", variant: "danger", isLoading: deleteMutation.isPending, onClick: () => {
|
|
173
179
|
if (confirm("Delete this coupon? This cannot be undone.")) {
|
|
174
180
|
deleteMutation.mutate();
|
|
175
181
|
}
|
|
@@ -21,6 +21,11 @@ const SORT_OPTIONS = [
|
|
|
21
21
|
{ value: "code", label: "Code A–Z" },
|
|
22
22
|
];
|
|
23
23
|
const TYPE_OPTIONS = ADMIN_COUPON_TYPE_TABS;
|
|
24
|
+
function CouponsFilterDrawer({ filterOpen, setFilterOpen, activeFilterCount, clearFilters, pendingFilters, setPendingFilters, applyFilters, }) {
|
|
25
|
+
if (!filterOpen)
|
|
26
|
+
return null;
|
|
27
|
+
return (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 z-40 bg-black/40", "aria-hidden": "true", onClick: () => setFilterOpen(false) }), _jsxs("div", { className: "fixed inset-y-0 left-0 z-50 flex w-80 flex-col bg-white dark:bg-slate-900 shadow-2xl", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: [_jsx("span", { className: "text-base font-semibold text-zinc-900 dark:text-zinc-100", children: "Filters" }), _jsxs("div", { className: "flex items-center gap-2", children: [activeFilterCount > 0 && (_jsx("button", { type: "button", onClick: clearFilters, className: "text-xs text-zinc-500 hover:text-rose-500 dark:text-zinc-400 transition-colors", children: "Clear all" })), _jsx("button", { type: "button", onClick: () => setFilterOpen(false), "aria-label": "Close", className: "rounded-lg p-1.5 text-zinc-500 hover:bg-zinc-100 dark:hover:bg-slate-800 transition-colors", children: _jsx(X, { className: "h-5 w-5" }) })] })] }), _jsx("div", { className: "flex-1 overflow-y-auto px-4 py-4 space-y-5", children: _jsx(FilterChipGroup, { label: "Type", tabs: TYPE_OPTIONS, value: pendingFilters.type ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, type: id })) }) }), _jsx("div", { className: "border-t border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: _jsxs("button", { type: "button", onClick: applyFilters, className: "w-full rounded-lg bg-primary py-2.5 text-sm font-semibold text-white hover:bg-primary-600 transition-colors active:scale-[0.98]", children: ["Apply Filters", activeFilterCount > 0 ? ` (${activeFilterCount})` : ""] }) })] })] }));
|
|
28
|
+
}
|
|
24
29
|
export function AdminCouponsView({ children, getRowHref, ...props }) {
|
|
25
30
|
const hasChildren = React.Children.count(children) > 0;
|
|
26
31
|
const [view, setView] = useState("table");
|
|
@@ -100,5 +105,5 @@ export function AdminCouponsView({ children, getRowHref, ...props }) {
|
|
|
100
105
|
if (hasChildren) {
|
|
101
106
|
return _jsx(ListingViewShell, { portal: "admin", ...props, children: children });
|
|
102
107
|
}
|
|
103
|
-
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search codes, campaigns, or seller scopes", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState, extra: _jsxs(Button, { size: "sm", onClick: openCreatePanel, className: "flex items-center gap-1.5", children: [_jsx(Plus, { className: "h-4 w-4" }), "Add Coupon"] }) }), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })), isLoading ? (_jsx(Div, { className: "fluid-grid-card gap-3", children: Array.from({ length: 6 }).map((_, i) => (_jsxs(Div, { className: "rounded-xl border-2 border-zinc-100 dark:border-slate-700 p-4 animate-pulse space-y-3", children: [_jsx(Div, { className: "h-6 bg-zinc-200 dark:bg-slate-700 rounded w-2/3" }), _jsx(Div, { className: "h-4 bg-zinc-200 dark:bg-slate-700 rounded w-full" }), _jsx(Div, { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-1/2" })] }, i))) })) : rows.length === 0 ? (_jsx(Div, { className: "py-16 text-center", children: _jsx(Text, { className: "text-zinc-400 dark:text-zinc-500", children: "No coupons found" }) })) : (_jsx(Div, { className: "fluid-grid-card gap-3", children: rows.map((row) => (_jsx(CouponCard, { coupon: row.raw, onEdit: openEditPanel, onToggleActive: handleToggle, onDelete: handleDelete }, row.id))) }))] }),
|
|
108
|
+
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search codes, campaigns, or seller scopes", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState, extra: _jsxs(Button, { size: "sm", onClick: openCreatePanel, className: "flex items-center gap-1.5", children: [_jsx(Plus, { className: "h-4 w-4" }), "Add Coupon"] }) }), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })), isLoading ? (_jsx(Div, { className: "fluid-grid-card gap-3", children: Array.from({ length: 6 }).map((_, i) => (_jsxs(Div, { className: "rounded-xl border-2 border-zinc-100 dark:border-slate-700 p-4 animate-pulse space-y-3", children: [_jsx(Div, { className: "h-6 bg-zinc-200 dark:bg-slate-700 rounded w-2/3" }), _jsx(Div, { className: "h-4 bg-zinc-200 dark:bg-slate-700 rounded w-full" }), _jsx(Div, { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-1/2" })] }, i))) })) : rows.length === 0 ? (_jsx(Div, { className: "py-16 text-center", children: _jsx(Text, { className: "text-zinc-400 dark:text-zinc-500", children: "No coupons found" }) })) : (_jsx(Div, { className: "fluid-grid-card gap-3", children: rows.map((row) => (_jsx(CouponCard, { coupon: row.raw, onEdit: openEditPanel, onToggleActive: handleToggle, onDelete: handleDelete }, row.id))) }))] }), _jsx(CouponsFilterDrawer, { filterOpen: filterOpen, setFilterOpen: setFilterOpen, activeFilterCount: activeFilterCount, clearFilters: clearFilters, pendingFilters: pendingFilters, setPendingFilters: setPendingFilters, applyFilters: applyFilters }), _jsx(SideDrawer, { isOpen: isCreateOpen || isEditOpen, onClose: closePanel, title: isCreateOpen ? "Add Coupon" : "Edit Coupon", mode: isCreateOpen ? "create" : "edit", children: (isCreateOpen || isEditOpen) && (_jsx(AdminCouponEditorView, { couponId: editId ?? undefined, onSaved: closePanel, onDeleted: closePanel, embedded: true })) })] }));
|
|
104
109
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {
|
|
2
|
+
import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import React from "react";
|
|
4
4
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
5
|
-
import { Button, ConfirmDeleteModal, Form, FormActions, Select, SideDrawer,
|
|
5
|
+
import { Button, ConfirmDeleteModal, Form, FormActions, Select, SideDrawer, Text, useToast } from "../../../ui";
|
|
6
6
|
import { apiClient } from "../../../http";
|
|
7
7
|
import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
8
8
|
import { PERMISSION_GROUPS, } from "../../auth/permissions/constants";
|
|
@@ -69,6 +69,15 @@ function formatPermLabel(perm) {
|
|
|
69
69
|
.replace(/-/g, " ")
|
|
70
70
|
.replace(/\b\w/g, (c) => c.toUpperCase());
|
|
71
71
|
}
|
|
72
|
+
function PermissionDomainsPanel({ permissions, togglePerm }) {
|
|
73
|
+
return (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs("span", { className: "text-sm font-medium text-zinc-700 dark:text-zinc-300", children: ["Permissions", _jsxs("span", { className: "ml-2 text-xs font-normal text-zinc-500 dark:text-zinc-400", children: ["(", permissions.size, " selected)"] })] }), _jsx("div", { className: "rounded-xl border border-zinc-200 dark:border-slate-700 divide-y divide-zinc-100 dark:divide-slate-700 max-h-[42vh] overflow-y-auto", children: PERMISSION_DOMAINS.map((domain) => {
|
|
74
|
+
const domainPerms = getPermissionsForDomain(domain.prefix);
|
|
75
|
+
if (domainPerms.length === 0)
|
|
76
|
+
return null;
|
|
77
|
+
const checked = domainPerms.filter((p) => permissions.has(p)).length;
|
|
78
|
+
return (_jsxs("details", { className: "group", children: [_jsxs("summary", { className: "flex cursor-pointer items-center justify-between px-3 py-2 text-xs font-semibold uppercase tracking-wide text-zinc-600 dark:text-zinc-400 select-none hover:bg-zinc-50 dark:hover:bg-slate-800 transition-colors", children: [_jsx("span", { children: domain.label }), _jsxs("span", { className: "text-xs font-normal normal-case text-zinc-400 dark:text-zinc-500", children: [checked, "/", domainPerms.length] })] }), _jsx("div", { className: "grid grid-cols-2 gap-x-2 gap-y-1.5 px-3 py-2.5 bg-zinc-50/60 dark:bg-slate-800/40", children: domainPerms.map((perm) => (_jsxs("label", { className: "flex items-center gap-2 cursor-pointer text-xs text-zinc-700 dark:text-zinc-300", children: [_jsx("input", { type: "checkbox", checked: permissions.has(perm), onChange: () => togglePerm(perm), className: "h-3.5 w-3.5 rounded border-zinc-300 dark:border-slate-600 accent-primary" }), formatPermLabel(perm)] }, perm))) })] }, domain.prefix));
|
|
79
|
+
}) })] }));
|
|
80
|
+
}
|
|
72
81
|
// --- Component ---------------------------------------------------------------
|
|
73
82
|
export function AdminEmployeeEditorView({ open, onClose, mode, userId, displayName, currentPermissionGroup, currentPermissions = [], }) {
|
|
74
83
|
const queryClient = useQueryClient();
|
|
@@ -158,11 +167,5 @@ export function AdminEmployeeEditorView({ open, onClose, mode, userId, displayNa
|
|
|
158
167
|
else
|
|
159
168
|
updateMutation.mutate();
|
|
160
169
|
};
|
|
161
|
-
return (_jsxs(_Fragment, { children: [_jsx(SideDrawer, { isOpen: open, onClose: onClose, title: mode === "invite" ? "Invite Employee" : `Edit — ${displayName ?? "Employee"}`, children: _jsxs(Form, { onSubmit: handleSubmit, className: "flex flex-col gap-5 p-4", children: [mode === "invite" && (_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx("label", { className: "text-sm font-medium text-zinc-700 dark:text-zinc-300", children: "Email address" }), _jsx("input", { type: "email", required: true, value: email, onChange: (e) => setEmail(e.target.value), placeholder: "employee@example.com", className: "rounded-lg border border-zinc-300 dark:border-slate-600 bg-white dark:bg-slate-800 px-3 py-2 text-sm text-zinc-900 dark:text-zinc-100 placeholder-zinc-400 focus:outline-none focus:ring-2 focus:ring-primary/60 transition" })] })), _jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx("label", { className: "text-sm font-medium text-zinc-700 dark:text-zinc-300", children: "Permission group" }), _jsx(Select, { value: group, onChange: (e) => applyGroupPreset(e.target.value), options: GROUP_OPTIONS }), _jsx(
|
|
162
|
-
const domainPerms = getPermissionsForDomain(domain.prefix);
|
|
163
|
-
if (domainPerms.length === 0)
|
|
164
|
-
return null;
|
|
165
|
-
const checked = domainPerms.filter((p) => permissions.has(p)).length;
|
|
166
|
-
return (_jsxs("details", { className: "group", children: [_jsxs("summary", { className: "flex cursor-pointer items-center justify-between px-3 py-2 text-xs font-semibold uppercase tracking-wide text-zinc-600 dark:text-zinc-400 select-none hover:bg-zinc-50 dark:hover:bg-slate-800 transition-colors", children: [_jsx("span", { children: domain.label }), _jsxs("span", { className: "text-xs font-normal normal-case text-zinc-400 dark:text-zinc-500", children: [checked, "/", domainPerms.length] })] }), _jsx("div", { className: "grid grid-cols-2 gap-x-2 gap-y-1.5 px-3 py-2.5 bg-zinc-50/60 dark:bg-slate-800/40", children: domainPerms.map((perm) => (_jsxs("label", { className: "flex items-center gap-2 cursor-pointer text-xs text-zinc-700 dark:text-zinc-300", children: [_jsx("input", { type: "checkbox", checked: permissions.has(perm), onChange: () => togglePerm(perm), className: "h-3.5 w-3.5 rounded border-zinc-300 dark:border-slate-600 accent-primary" }), formatPermLabel(perm)] }, perm))) })] }, domain.prefix));
|
|
167
|
-
}) })] }), _jsxs(FormActions, { children: [_jsx(Button, { type: "submit", variant: "primary", disabled: isBusy, isLoading: isBusy, children: mode === "invite" ? "Send Invite" : "Save" }), mode === "edit" && userId && (_jsx(Button, { type: "button", variant: "danger", disabled: isBusy, onClick: () => setRevokeOpen(true), children: "Revoke Access" }))] })] }) }), mode === "edit" && (_jsx(ConfirmDeleteModal, { isOpen: revokeOpen, onClose: () => setRevokeOpen(false), onConfirm: () => revokeMutation.mutate(), isDeleting: revokeMutation.isPending, title: "Revoke employee access?", message: `${displayName ?? "This employee"} will lose admin panel access immediately. Their user account remains active — only their role is reset to user.`, confirmText: "Revoke Access", variant: "danger" }))] }));
|
|
170
|
+
return (_jsxs(_Fragment, { children: [_jsx(SideDrawer, { isOpen: open, onClose: onClose, title: mode === "invite" ? "Invite Employee" : `Edit — ${displayName ?? "Employee"}`, children: _jsxs(Form, { onSubmit: handleSubmit, className: "flex flex-col gap-5 p-4", children: [mode === "invite" && (_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx("label", { className: "text-sm font-medium text-zinc-700 dark:text-zinc-300", children: "Email address" }), _jsx("input", { type: "email", required: true, value: email, onChange: (e) => setEmail(e.target.value), placeholder: "employee@example.com", className: "rounded-lg border border-zinc-300 dark:border-slate-600 bg-white dark:bg-slate-800 px-3 py-2 text-sm text-zinc-900 dark:text-zinc-100 placeholder-zinc-400 focus:outline-none focus:ring-2 focus:ring-primary/60 transition" })] })), _jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx("label", { className: "text-sm font-medium text-zinc-700 dark:text-zinc-300", children: "Permission group" }), _jsx(Select, { value: group, onChange: (e) => applyGroupPreset(e.target.value), options: GROUP_OPTIONS }), _jsx(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400", children: "Selecting a group auto-fills the permissions below. You can still customise individual permissions." })] }), _jsx(PermissionDomainsPanel, { permissions: permissions, togglePerm: togglePerm }), _jsxs(FormActions, { children: [_jsx(Button, { type: "submit", variant: "primary", disabled: isBusy, isLoading: isBusy, children: mode === "invite" ? "Send Invite" : "Save" }), mode === "edit" && userId && (_jsx(Button, { type: "button", variant: "danger", disabled: isBusy, onClick: () => setRevokeOpen(true), children: "Revoke Access" }))] })] }) }), mode === "edit" && (_jsx(ConfirmDeleteModal, { isOpen: revokeOpen, onClose: () => setRevokeOpen(false), onConfirm: () => revokeMutation.mutate(), isDeleting: revokeMutation.isPending, title: "Revoke employee access?", message: `${displayName ?? "This employee"} will lose admin panel access immediately. Their user account remains active — only their role is reset to user.`, confirmText: "Revoke Access", variant: "danger" }))] }));
|
|
168
171
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import React from "react";
|
|
4
4
|
import { useMutation, useQuery } from "@tanstack/react-query";
|
|
5
|
-
import { Button, Form, Input, RichTextEditor, Select, StackedViewShell, TagInput, Toggle, useToast
|
|
5
|
+
import { Button, Form, Input, RichTextEditor, Select, StackedViewShell, TagInput, Text, Toggle, useToast } from "../../../ui";
|
|
6
6
|
import { apiClient } from "../../../http";
|
|
7
7
|
import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
8
8
|
// --- Constants ---------------------------------------------------------------
|
|
@@ -118,7 +118,7 @@ export function AdminFaqEditorView({ faqId, onSaved, onDeleted, embedded, ...res
|
|
|
118
118
|
}, className: "space-y-5", children: [_jsx(Input, { label: "Question", value: question, onChange: (e) => handleQuestionChange(e.target.value), required: true, placeholder: "e.g. How does bidding work on LetItRip?" }), _jsx(Input, { label: "Slug", value: slug, onChange: (e) => {
|
|
119
119
|
setSlug(e.target.value);
|
|
120
120
|
setSlugManual(true);
|
|
121
|
-
}, placeholder: "faq-how-does-bidding-work", helperText: "Auto-generated from question. Must start with 'faq-'." }), _jsxs("div", { className: "space-y-1", children: [_jsx(
|
|
121
|
+
}, placeholder: "faq-how-does-bidding-work", helperText: "Auto-generated from question. Must start with 'faq-'." }), _jsxs("div", { className: "space-y-1", children: [_jsx(Text, { className: "text-sm font-medium text-zinc-700 dark:text-zinc-300", children: "Answer" }), _jsx(RichTextEditor, { value: answer, onChange: setAnswer, placeholder: "Write a clear, helpful answer...", minHeightClassName: "min-h-[200px]" })] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsx(Select, { label: "Category", options: CATEGORY_OPTIONS, value: category, onValueChange: setCategory }), _jsx(Input, { label: "Display order", value: String(order), onChange: (e) => setOrder(parseInt(e.target.value, 10) || 0), type: "number", min: 0, helperText: "Lower = shown first within category." })] }), _jsx(Input, { label: "Priority", value: String(priority), onChange: (e) => setPriority(parseInt(e.target.value, 10) || 0), type: "number", min: 0, helperText: "Higher priority FAQs appear first in search results." }), _jsx(TagInput, { label: "Tags", value: tags, onChange: setTags, placeholder: "e.g. shipping, pokemon, returns" }), _jsxs("div", { className: "space-y-3 rounded-lg border border-zinc-200 dark:border-zinc-700 p-4", children: [_jsx(Text, { className: "text-sm font-medium text-zinc-700 dark:text-zinc-300", children: "Visibility" }), _jsx(Toggle, { label: "Active (visible to users)", checked: isActive, onChange: setIsActive }), _jsx(Toggle, { label: "Pinned (always shown at top)", checked: isPinned, onChange: setIsPinned }), _jsx(Toggle, { label: "Show on homepage FAQ section", checked: showOnHomepage, onChange: setShowOnHomepage }), _jsx(Toggle, { label: "Show in footer FAQ links", checked: showInFooter, onChange: setShowInFooter })] }), _jsxs("div", { className: "flex gap-3 pt-2", children: [_jsx(Button, { type: "submit", isLoading: isSubmitting, disabled: !canSave || isSubmitting, children: isEdit ? "Save changes" : "Create FAQ" }), isEdit && (_jsx(Button, { type: "button", variant: "danger", isLoading: deleteMutation.isPending, onClick: () => {
|
|
122
122
|
if (confirm("Delete this FAQ? This cannot be undone.")) {
|
|
123
123
|
deleteMutation.mutate();
|
|
124
124
|
}
|
|
@@ -5,7 +5,7 @@ import { Plus, X } from "lucide-react";
|
|
|
5
5
|
import { useUrlTable } from "../../../react/hooks/useUrlTable";
|
|
6
6
|
import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
|
|
7
7
|
import { usePanelUrlSync } from "../../../react/hooks/use-panel-url-sync";
|
|
8
|
-
import { Button,
|
|
8
|
+
import { Button, Heading, ListingToolbar, ListingViewShell, Pagination, SideDrawer, Text } from "../../../ui";
|
|
9
9
|
import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
10
10
|
import { toRecordArray, toRelativeDate, toStringValue, useAdminListingData, } from "../hooks/useAdminListingData";
|
|
11
11
|
import { DataTable } from "./DataTable";
|
|
@@ -19,6 +19,11 @@ const SORT_OPTIONS = [
|
|
|
19
19
|
{ value: "-createdAt", label: "Newest" },
|
|
20
20
|
{ value: "question", label: "Question A–Z" },
|
|
21
21
|
];
|
|
22
|
+
function FaqsFilterDrawer({ filterOpen, setFilterOpen, activeFilterCount, clearFilters, pendingFilters, setPendingFilters, applyFilters, }) {
|
|
23
|
+
if (!filterOpen)
|
|
24
|
+
return null;
|
|
25
|
+
return (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 z-40 bg-black/40", "aria-hidden": "true", onClick: () => setFilterOpen(false) }), _jsxs("div", { className: "fixed inset-y-0 left-0 z-50 flex w-80 flex-col bg-white dark:bg-slate-900 shadow-2xl", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: [_jsx("span", { className: "text-base font-semibold text-zinc-900 dark:text-zinc-100", children: "Filters" }), _jsxs("div", { className: "flex items-center gap-2", children: [activeFilterCount > 0 && (_jsx("button", { type: "button", onClick: clearFilters, className: "text-xs text-zinc-500 hover:text-rose-500 dark:text-zinc-400 transition-colors", children: "Clear all" })), _jsx("button", { type: "button", onClick: () => setFilterOpen(false), "aria-label": "Close", className: "rounded-lg p-1.5 text-zinc-500 hover:bg-zinc-100 dark:hover:bg-slate-800 transition-colors", children: _jsx(X, { className: "h-5 w-5" }) })] })] }), _jsx("div", { className: "flex-1 overflow-y-auto px-4 py-4 space-y-5", children: _jsxs("div", { className: "space-y-2", children: [_jsx(Text, { className: "text-xs font-semibold uppercase tracking-widest text-zinc-500 dark:text-zinc-400", children: "Status" }), _jsx("div", { className: "flex flex-wrap gap-2", children: [{ label: "All", value: "" }, { label: "Published", value: "true" }, { label: "Draft", value: "false" }].map((opt) => (_jsx("button", { type: "button", onClick: () => setPendingFilters((p) => ({ ...p, isActive: opt.value })), className: `rounded-full px-3 py-1 text-xs font-medium border transition-colors ${(pendingFilters.isActive || "") === opt.value ? "bg-primary text-white border-primary" : "border-zinc-300 dark:border-slate-600 text-zinc-700 dark:text-zinc-300 hover:bg-zinc-50 dark:hover:bg-slate-800"}`, children: opt.label }, opt.label))) })] }) }), _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})` : ""] }) })] })] }));
|
|
26
|
+
}
|
|
22
27
|
export function AdminFaqsView({ children, getRowHref, ...props }) {
|
|
23
28
|
const hasChildren = React.Children.count(children) > 0;
|
|
24
29
|
const [view, setView] = useState("table");
|
|
@@ -78,5 +83,5 @@ export function AdminFaqsView({ children, getRowHref, ...props }) {
|
|
|
78
83
|
if (hasChildren) {
|
|
79
84
|
return _jsx(ListingViewShell, { portal: "admin", ...props, children: children });
|
|
80
85
|
}
|
|
81
|
-
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search questions, categories, or tokens", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState, extra: _jsxs(Button, { size: "sm", onClick: openCreatePanel, className: "flex items-center gap-1.5", children: [_jsx(Plus, { className: "h-4 w-4" }), "Add FAQ"] }) }), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })), view === "table" ? (_jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No FAQs found", onRowClick: (row) => openEditPanel(row.id) })) : (_jsx(AdminViewCards, { rows: rows, view: view, isLoading: isLoading, emptyLabel: "No FAQs found", onRowClick: undefined, selectedIdSet: selection.selectedIdSet, onToggleSelect: selection.toggle }))] }),
|
|
86
|
+
return (_jsxs("div", { className: "min-h-screen", children: [_jsx(Heading, { level: 1, className: "sr-only", children: "FAQs" }), _jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search questions, categories, or tokens", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState, extra: _jsxs(Button, { size: "sm", onClick: openCreatePanel, className: "flex items-center gap-1.5", children: [_jsx(Plus, { className: "h-4 w-4" }), "Add FAQ"] }) }), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })), view === "table" ? (_jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No FAQs found", onRowClick: (row) => openEditPanel(row.id) })) : (_jsx(AdminViewCards, { rows: rows, view: view, isLoading: isLoading, emptyLabel: "No FAQs found", onRowClick: undefined, selectedIdSet: selection.selectedIdSet, onToggleSelect: selection.toggle }))] }), _jsx(FaqsFilterDrawer, { filterOpen: filterOpen, setFilterOpen: setFilterOpen, activeFilterCount: activeFilterCount, clearFilters: clearFilters, pendingFilters: pendingFilters, setPendingFilters: setPendingFilters, applyFilters: applyFilters }), _jsx(SideDrawer, { isOpen: isCreateOpen || isEditOpen, onClose: closePanel, title: isCreateOpen ? "Add FAQ" : "Edit FAQ", mode: isCreateOpen ? "create" : "edit", children: (isCreateOpen || isEditOpen) && (_jsx(AdminFaqEditorView, { faqId: editId ?? undefined, onSaved: closePanel, onDeleted: closePanel, embedded: true })) })] }));
|
|
82
87
|
}
|
|
@@ -5,6 +5,44 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
|
5
5
|
import { Alert, Button, Div, Form, FormActions, Input, StackedViewShell, Text, Toggle, useToast, } from "../../../ui";
|
|
6
6
|
import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
7
7
|
import { apiClient } from "../../../http";
|
|
8
|
+
// Platform feature keys (flat booleans with rollout % support)
|
|
9
|
+
const PLATFORM_FLAGS = [
|
|
10
|
+
{ key: "blog", label: "Blog Posts", desc: "Publish articles for admins / sellers" },
|
|
11
|
+
{ key: "events", label: "Events", desc: "Raffles, spin wheels, polls" },
|
|
12
|
+
{ key: "reviews", label: "Reviews", desc: "Product review system" },
|
|
13
|
+
{ key: "wishlists", label: "Wishlists", desc: "Per-user saved lists" },
|
|
14
|
+
{ key: "coupons", label: "Coupons", desc: "Discount code system" },
|
|
15
|
+
{ key: "notifications", label: "Notifications", desc: "In-app notification feed" },
|
|
16
|
+
{ key: "chats", label: "Live Chat", desc: "Buyer-seller messaging" },
|
|
17
|
+
{ key: "sellerRegistration", label: "Seller Reg.", desc: "New vendor registration" },
|
|
18
|
+
{ key: "smsVerification", label: "SMS Verify", desc: "SMS OTP verification" },
|
|
19
|
+
{ key: "translations", label: "Translations", desc: "Multi-language support" },
|
|
20
|
+
{ key: "seedPanel", label: "Seed Panel", desc: "Dev seed data panel" },
|
|
21
|
+
];
|
|
22
|
+
// Listing type keys (nested under featureFlags.listingTypes — no rollout %)
|
|
23
|
+
const LISTING_TYPE_FLAGS = [
|
|
24
|
+
{ key: "standard", label: "Standard", desc: "Fixed-price products (Phase 1)" },
|
|
25
|
+
{ key: "auction", label: "Auction", desc: "eBay-style bidding (Phase 1)" },
|
|
26
|
+
{ key: "pre-order", label: "Pre-order", desc: "Future delivery + deposit (Phase 1)" },
|
|
27
|
+
{ key: "prize-draw", label: "Prize Draw", desc: "Raffle reveal window (Phase 1)" },
|
|
28
|
+
{ key: "classified", label: "Classified", desc: "Chat-only / no cart (Phase 2)" },
|
|
29
|
+
{ key: "digital-code", label: "Digital Code", desc: "Steam-key instant delivery (Phase 2)" },
|
|
30
|
+
{ key: "live", label: "Live Items", desc: "Animals / plants + verification (Phase 2)" },
|
|
31
|
+
];
|
|
32
|
+
// Category type keys (nested under featureFlags.categoryTypes — no rollout %)
|
|
33
|
+
const CATEGORY_TYPE_FLAGS = [
|
|
34
|
+
{ key: "category", label: "Categories", desc: "Standard hierarchy tier 1–3" },
|
|
35
|
+
{ key: "sublisting", label: "Sublistings", desc: "Grading / card-number sub-tiers" },
|
|
36
|
+
{ key: "brand", label: "Brands", desc: "Brand storefront pages" },
|
|
37
|
+
{ key: "bundle", label: "Bundles", desc: "Curated product collections" },
|
|
38
|
+
];
|
|
39
|
+
function AccordionSection({ title, defaultOpen = true, children }) {
|
|
40
|
+
const [open, setOpen] = React.useState(defaultOpen);
|
|
41
|
+
return (_jsxs(Div, { className: "rounded-lg border border-zinc-200 dark:border-zinc-700 overflow-hidden", children: [_jsxs("button", { type: "button", onClick: () => setOpen((v) => !v), className: "w-full flex items-center justify-between px-4 py-3 bg-zinc-50 dark:bg-zinc-800/60 text-left", "aria-expanded": open, children: [_jsx(Text, { className: "text-xs font-semibold uppercase tracking-widest text-zinc-500 dark:text-zinc-400", children: title }), _jsx("svg", { className: `h-4 w-4 text-zinc-400 transition-transform ${open ? "rotate-180" : ""}`, fill: "none", stroke: "currentColor", strokeWidth: 2, viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M19 9l-7 7-7-7" }) })] }), open && (_jsx(Div, { className: "divide-y divide-zinc-100 dark:divide-zinc-800", children: children }))] }));
|
|
42
|
+
}
|
|
43
|
+
function FlagRow({ label, desc, checked, onChange, rollout, onRolloutChange, showRollout }) {
|
|
44
|
+
return (_jsxs(Div, { className: "flex items-center justify-between gap-4 px-4 py-3 bg-white dark:bg-zinc-900", children: [_jsxs(Div, { className: "flex-1 min-w-0", children: [_jsx(Toggle, { checked: checked, onChange: onChange, label: label }), _jsx(Text, { className: "mt-0.5 ml-10 text-xs text-zinc-400 dark:text-zinc-500 truncate", children: desc })] }), showRollout && (_jsxs(Div, { className: "flex flex-col gap-1 w-28 shrink-0", children: [_jsx(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400", children: "Rollout %" }), _jsx(Input, { type: "number", min: 0, max: 100, value: rollout ?? 100, onChange: (e) => onRolloutChange?.(Math.min(100, Math.max(0, Number(e.target.value)))), disabled: !checked, className: "w-full" })] }))] }));
|
|
45
|
+
}
|
|
8
46
|
export function AdminFeatureFlagsView({ labels = {}, renderFlags, ...rest }) {
|
|
9
47
|
const queryClient = useQueryClient();
|
|
10
48
|
const { showToast } = useToast();
|
|
@@ -23,6 +61,24 @@ export function AdminFeatureFlagsView({ labels = {}, renderFlags, ...rest }) {
|
|
|
23
61
|
setFlags(data.flags ?? {});
|
|
24
62
|
setRollouts(data.rollouts ?? {});
|
|
25
63
|
}, [data]);
|
|
64
|
+
const getPlatformFlag = (key) => Boolean(flags[key]);
|
|
65
|
+
const setPlatformFlag = (key, val) => setFlags((prev) => ({ ...prev, [key]: val }));
|
|
66
|
+
const getListingTypeFlag = (key) => {
|
|
67
|
+
const lt = flags.listingTypes;
|
|
68
|
+
return lt?.[key] ?? false;
|
|
69
|
+
};
|
|
70
|
+
const setListingTypeFlag = (key, val) => setFlags((prev) => ({
|
|
71
|
+
...prev,
|
|
72
|
+
listingTypes: { ...(prev.listingTypes ?? {}), [key]: val },
|
|
73
|
+
}));
|
|
74
|
+
const getCategoryTypeFlag = (key) => {
|
|
75
|
+
const ct = flags.categoryTypes;
|
|
76
|
+
return ct?.[key] ?? false;
|
|
77
|
+
};
|
|
78
|
+
const setCategoryTypeFlag = (key, val) => setFlags((prev) => ({
|
|
79
|
+
...prev,
|
|
80
|
+
categoryTypes: { ...(prev.categoryTypes ?? {}), [key]: val },
|
|
81
|
+
}));
|
|
26
82
|
const saveFlags = useMutation({
|
|
27
83
|
mutationFn: async () => {
|
|
28
84
|
await apiClient.put(ADMIN_ENDPOINTS.FEATURE_FLAGS, { flags, rollouts });
|
|
@@ -35,24 +91,13 @@ export function AdminFeatureFlagsView({ labels = {}, renderFlags, ...rest }) {
|
|
|
35
91
|
showToast("Failed to save feature flags.", "error");
|
|
36
92
|
},
|
|
37
93
|
});
|
|
38
|
-
const featureKeys = React.useMemo(() => Object.keys(flags), [flags]);
|
|
39
|
-
const formatLabel = (value) => value
|
|
40
|
-
.replace(/([A-Z])/g, " $1")
|
|
41
|
-
.replace(/_/g, " ")
|
|
42
|
-
.replace(/^./, (char) => char.toUpperCase())
|
|
43
|
-
.trim();
|
|
44
94
|
const defaultFlags = () => (_jsxs(Form, { onSubmit: (event) => {
|
|
45
95
|
event.preventDefault();
|
|
46
96
|
saveFlags.mutate();
|
|
47
|
-
}, className: "space-y-4", children: [
|
|
48
|
-
setFlags((prev) => ({ ...prev, [key]: value }));
|
|
49
|
-
}, label: formatLabel(key) }), _jsx(Text, { className: "mt-0.5 ml-10 text-xs text-zinc-400 dark:text-zinc-500 font-mono", children: key })] }), _jsxs(Div, { className: "flex flex-col gap-1 w-32 shrink-0", children: [_jsx(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400", children: "Rollout %" }), _jsx(Input, { type: "number", min: 0, max: 100, value: rollouts[key] ?? 100, onChange: (e) => setRollouts((prev) => ({
|
|
50
|
-
...prev,
|
|
51
|
-
[key]: Math.min(100, Math.max(0, Number(e.target.value))),
|
|
52
|
-
})), disabled: !flags[key], className: "w-full" })] })] }, key))), _jsxs(FormActions, { align: "right", children: [_jsx(Button, { type: "button", variant: "secondary", onClick: () => {
|
|
97
|
+
}, className: "space-y-4", children: [_jsx(AccordionSection, { title: "Platform Features", children: PLATFORM_FLAGS.map(({ key, label, desc }) => (_jsx(FlagRow, { label: label, desc: desc, checked: getPlatformFlag(key), onChange: (val) => setPlatformFlag(key, val), rollout: rollouts[key], onRolloutChange: (val) => setRollouts((prev) => ({ ...prev, [key]: val })), showRollout: true }, key))) }), _jsx(AccordionSection, { title: "Listing Types", children: LISTING_TYPE_FLAGS.map(({ key, label, desc }) => (_jsx(FlagRow, { label: label, desc: desc, checked: getListingTypeFlag(key), onChange: (val) => setListingTypeFlag(key, val), showRollout: false }, key))) }), _jsx(AccordionSection, { title: "Category Types", children: CATEGORY_TYPE_FLAGS.map(({ key, label, desc }) => (_jsx(FlagRow, { label: label, desc: desc, checked: getCategoryTypeFlag(key), onChange: (val) => setCategoryTypeFlag(key, val), showRollout: false }, key))) }), _jsxs(FormActions, { align: "right", children: [_jsx(Button, { type: "button", variant: "secondary", onClick: () => {
|
|
53
98
|
setFlags(data?.flags ?? {});
|
|
54
99
|
setRollouts(data?.rollouts ?? {});
|
|
55
|
-
}, disabled: saveFlags.isPending, children: "Reset" }), _jsx(Button, { type: "submit", isLoading: saveFlags.isPending, disabled: saveFlags.isPending
|
|
100
|
+
}, disabled: saveFlags.isPending, children: "Reset" }), _jsx(Button, { type: "submit", isLoading: saveFlags.isPending, disabled: saveFlags.isPending, children: "Save All Flags" })] })] }));
|
|
56
101
|
const loadingAlert = isLoading ? (_jsx(Alert, { variant: "info", title: "Loading feature flags", children: "Fetching feature flags\u2026" })) : null;
|
|
57
102
|
const errorAlert = error ? (_jsx(Alert, { variant: "error", title: "Could not load feature flags", children: error instanceof Error ? error.message : "Unknown error" })) : null;
|
|
58
103
|
return (_jsx(StackedViewShell, { portal: "admin", ...rest, title: labels.title ?? "Feature Flags", sections: [loadingAlert, errorAlert, renderFlags?.() ?? defaultFlags()] }));
|
|
@@ -5,7 +5,7 @@ import { Plus } from "lucide-react";
|
|
|
5
5
|
import { useUrlTable } from "../../../react/hooks/useUrlTable";
|
|
6
6
|
import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
|
|
7
7
|
import { usePanelUrlSync } from "../../../react/hooks/use-panel-url-sync";
|
|
8
|
-
import { Button, Div, ListingToolbar, ListingViewShell, Pagination, Row, SideDrawer, Text, } from "../../../ui";
|
|
8
|
+
import { BulkActionBar, Button, Div, ListingToolbar, ListingViewShell, Pagination, Row, SideDrawer, Text, } from "../../../ui";
|
|
9
9
|
import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
10
10
|
import { toRecordArray, toRelativeDate, toStringValue, useAdminListingData, } from "../hooks/useAdminListingData";
|
|
11
11
|
import { DataTable } from "./DataTable";
|
|
@@ -61,7 +61,6 @@ export function AdminFeaturesView({ children, ...props }) {
|
|
|
61
61
|
const [searchInput, setSearchInput] = React.useState(table.get("q") || "");
|
|
62
62
|
const commitSearch = React.useCallback(() => {
|
|
63
63
|
table.set("q", searchInput.trim());
|
|
64
|
-
table.setPage(1);
|
|
65
64
|
}, [searchInput, table]);
|
|
66
65
|
const resetAll = React.useCallback(() => {
|
|
67
66
|
table.setMany({ q: "", sort: "", scope: DEFAULT_SCOPE });
|
|
@@ -92,5 +91,7 @@ export function AdminFeaturesView({ children, ...props }) {
|
|
|
92
91
|
table.set("sort", v);
|
|
93
92
|
}, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState, extra: _jsxs(Button, { size: "sm", onClick: openCreatePanel, className: "flex items-center gap-1.5", children: [_jsx(Plus, { className: "h-4 w-4" }), "Add Feature"] }) }), _jsx(Row, { className: STICKY_TABS_CLASS, gap: "xs", children: PRODUCT_FEATURE_SCOPE_TABS.map((tab) => (_jsx("button", { type: "button", onClick: () => {
|
|
94
93
|
table.set("scope", tab.value);
|
|
95
|
-
}, className: tabClass(tab.value), children: tab.label }, tab.value))) }),
|
|
94
|
+
}, className: tabClass(tab.value), children: tab.label }, tab.value))) }), _jsx(BulkActionBar, { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: [
|
|
95
|
+
{ id: "delete", label: "Delete Selected", variant: "secondary", onClick: () => { selection.clearSelection(); } },
|
|
96
|
+
] }), totalPages > 1 && (_jsx(Div, { className: PAGINATION_BAR_CLASS, children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs(Div, { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx(Text, { as: "div", className: ERROR_BANNER_CLASS, children: errorMessage })), _jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No features found", onRowClick: (row) => openEditPanel(row.id) })] }), _jsx(SideDrawer, { isOpen: isCreateOpen || isEditOpen, onClose: closePanel, title: isCreateOpen ? "Add Feature" : "Edit Feature", mode: isCreateOpen ? "create" : "edit", children: (isCreateOpen || isEditOpen) && (_jsx(AdminFeatureEditorView, { featureId: editId ?? undefined, onSaved: closePanel, onDeleted: closePanel, embedded: true })) })] }));
|
|
96
97
|
}
|
|
@@ -64,13 +64,18 @@ function MediaBrowser({ onCopy }) {
|
|
|
64
64
|
_jsx("img", { src: f.downloadURL, alt: f.name, className: "h-full w-full object-cover", loading: "lazy" })) : (_jsx(Text, { className: "px-2 text-center break-all", children: f.contentType ?? "file" })) }), _jsxs(Div, { className: "p-1.5", children: [_jsx(Text, { className: "truncate text-[10px] text-zinc-600 dark:text-zinc-400 font-mono", children: f.name.split("/").pop() }), _jsx(Button, { type: "button", variant: "ghost", className: "mt-1 w-full text-xs", onClick: () => onCopy(f.downloadURL), children: "Copy URL" })] })] }, f.name));
|
|
65
65
|
}), filtered.length === 0 && !isLoading && (_jsx(Text, { className: "col-span-full py-6 text-center text-sm text-zinc-500 dark:text-zinc-400", children: "No files found." }))] }), _jsxs(Div, { className: "flex items-center justify-between", children: [_jsxs(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400", children: [filtered.length, " file(s)", nextPageToken ? " · more available" : ""] }), nextPageToken && (_jsx(Button, { type: "button", variant: "outline", onClick: () => void load(nextPageToken), disabled: isLoading, children: isLoading ? "Loading…" : "Load more" }))] })] }));
|
|
66
66
|
}
|
|
67
|
+
function MediaUploaderPanel({ onUpload, isUploadPending, isCleanupPending, heroAssetUrl, setHeroAssetUrl, galleryAssets, setGalleryAssets, stagedUrls, setHeroStagedUrls, setGalleryStagedUrls, handleAbort, copiedUrl, onCopy, onClearPreviews, onDiscardStaged, }) {
|
|
68
|
+
return (_jsxs(Div, { className: "space-y-5 rounded-xl border border-zinc-200 bg-white p-4 dark:border-slate-700 dark:bg-slate-900", children: [_jsx(Text, { className: "text-sm font-semibold text-zinc-900 dark:text-zinc-100", children: "Upload & Copy URL" }), _jsx(MediaUploadField, { label: "Primary media asset", value: heroAssetUrl, onChange: setHeroAssetUrl, onUpload: onUpload, helperText: "Uploads via signed URL (sign \u2192 PUT \u2192 finalize) and previews the returned URL.", onAbort: handleAbort, onStagedUrlsChange: setHeroStagedUrls }), heroAssetUrl && (_jsxs(Div, { className: "flex items-center gap-2 rounded-lg bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 px-3 py-2", children: [_jsx(Text, { className: "flex-1 truncate text-xs text-zinc-600 dark:text-zinc-400 font-mono", children: heroAssetUrl }), _jsx(Button, { type: "button", variant: "outline", onClick: () => onCopy(heroAssetUrl), children: copiedUrl === heroAssetUrl ? "Copied!" : "Copy URL" })] })), _jsx(MediaUploadList, { label: "Gallery assets", value: galleryAssets, onChange: setGalleryAssets, onUpload: onUpload, helperText: "Attach multiple images or videos for batch checks.", onAbort: handleAbort, onStagedUrlsChange: setGalleryStagedUrls, maxItems: 12 }), galleryAssets.length > 0 && (_jsxs(Div, { className: "flex flex-col gap-1", children: [_jsx(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400 font-medium", children: "Gallery URLs" }), galleryAssets.map((asset, i) => (_jsxs(Div, { className: "flex items-center gap-2 rounded bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 px-2 py-1", children: [_jsx(Text, { className: "flex-1 truncate text-xs text-zinc-600 dark:text-zinc-400 font-mono", children: asset.url }), _jsx(Button, { type: "button", variant: "ghost", onClick: () => onCopy(asset.url), children: copiedUrl === asset.url ? "Copied!" : "Copy" })] }, i)))] })), _jsxs(Div, { className: "flex flex-wrap items-center gap-2", children: [_jsx(Button, { type: "button", variant: "outline", onClick: onClearPreviews, disabled: isUploadPending || isCleanupPending, children: "Clear previews" }), _jsx(Button, { type: "button", variant: "outline", onClick: onDiscardStaged, disabled: isUploadPending || isCleanupPending || stagedUrls.length === 0, children: isCleanupPending ? "Discarding…" : "Discard staged uploads" }), _jsxs(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400", children: [stagedUrls.length, " staged upload(s)"] })] })] }));
|
|
69
|
+
}
|
|
67
70
|
export function AdminMediaView({ labels = {}, children, ...rest }) {
|
|
68
71
|
const hasChildren = React.Children.count(children) > 0;
|
|
69
72
|
const { upload, isPending: isUploadPending } = useMediaUpload();
|
|
70
73
|
const { cleanup, isPending: isCleanupPending } = useMediaCleanup();
|
|
71
74
|
const [heroAssetUrl, setHeroAssetUrl] = React.useState("");
|
|
72
75
|
const [galleryAssets, setGalleryAssets] = React.useState([]);
|
|
73
|
-
const [
|
|
76
|
+
const [heroStagedUrls, setHeroStagedUrls] = React.useState([]);
|
|
77
|
+
const [galleryStagedUrls, setGalleryStagedUrls] = React.useState([]);
|
|
78
|
+
const stagedUrls = React.useMemo(() => Array.from(new Set([...heroStagedUrls, ...galleryStagedUrls])), [heroStagedUrls, galleryStagedUrls]);
|
|
74
79
|
const [operationMessage, setOperationMessage] = React.useState(null);
|
|
75
80
|
const [copiedUrl, setCopiedUrl] = React.useState(null);
|
|
76
81
|
const onUpload = React.useCallback(async (file) => {
|
|
@@ -89,7 +94,8 @@ export function AdminMediaView({ labels = {}, children, ...rest }) {
|
|
|
89
94
|
await cleanup(stagedUrls);
|
|
90
95
|
setHeroAssetUrl("");
|
|
91
96
|
setGalleryAssets([]);
|
|
92
|
-
|
|
97
|
+
setHeroStagedUrls([]);
|
|
98
|
+
setGalleryStagedUrls([]);
|
|
93
99
|
setOperationMessage("Discarded staged media uploads.");
|
|
94
100
|
}
|
|
95
101
|
catch {
|
|
@@ -113,11 +119,6 @@ export function AdminMediaView({ labels = {}, children, ...rest }) {
|
|
|
113
119
|
_jsx(Alert, { variant: "info", title: "Media Library", children: "Upload new files in the sandbox below, or browse the existing Storage bucket via the grid." }, "media-info"),
|
|
114
120
|
_jsx(MediaBrowser, { onCopy: copyToClipboard }, "media-browser"),
|
|
115
121
|
operationMessage ? (_jsx(Alert, { variant: operationMessage.startsWith("Failed") || operationMessage.startsWith("Some") || operationMessage.startsWith("Could") ? "error" : "success", title: "Media", children: operationMessage }, "media-op-message")) : null,
|
|
116
|
-
|
|
117
|
-
setHeroAssetUrl("");
|
|
118
|
-
setGalleryAssets([]);
|
|
119
|
-
}, disabled: isUploadPending || isCleanupPending, children: "Clear previews" }), _jsx(Button, { type: "button", variant: "outline", onClick: () => {
|
|
120
|
-
void clearStagedUploads();
|
|
121
|
-
}, disabled: isUploadPending || isCleanupPending || stagedUrls.length === 0, children: isCleanupPending ? "Discarding…" : "Discard staged uploads" }), _jsxs(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400", children: [stagedUrls.length, " staged upload(s)"] })] })] }, "media-uploader"),
|
|
122
|
+
_jsx(MediaUploaderPanel, { onUpload: onUpload, isUploadPending: isUploadPending, isCleanupPending: isCleanupPending, heroAssetUrl: heroAssetUrl, setHeroAssetUrl: setHeroAssetUrl, galleryAssets: galleryAssets, setGalleryAssets: setGalleryAssets, stagedUrls: stagedUrls, setHeroStagedUrls: setHeroStagedUrls, setGalleryStagedUrls: setGalleryStagedUrls, handleAbort: handleAbort, copiedUrl: copiedUrl, onCopy: copyToClipboard, onClearPreviews: () => { setHeroAssetUrl(""); setGalleryAssets([]); }, onDiscardStaged: () => { void clearStagedUploads(); } }, "media-uploader"),
|
|
122
123
|
] }));
|
|
123
124
|
}
|