@mohasinac/appkit 4.13.1 → 4.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_internal/client/features/layout/DashboardLayoutClient.js +14 -5
- package/dist/_internal/client/features/lottery/LotteryAdminEditView.d.ts +3 -0
- package/dist/_internal/client/features/lottery/LotteryAdminEditView.js +6 -0
- package/dist/_internal/client/features/lottery/LotteryDetailView.d.ts +8 -0
- package/dist/_internal/client/features/lottery/LotteryDetailView.js +4 -1
- package/dist/_internal/client/features/lottery/LotteryListView.d.ts +7 -0
- package/dist/_internal/client/features/lottery/LotteryListView.js +1 -1
- package/dist/_internal/client/features/lottery/PrizeDrawLotteryDetailView.js +4 -1
- package/dist/_internal/client/features/lottery/slot-collage-items.d.ts +15 -0
- package/dist/_internal/client/features/lottery/slot-collage-items.js +13 -0
- package/dist/_internal/server/features/auctions/actions.js +8 -2
- package/dist/_internal/server/features/bundles/data.d.ts +16 -0
- package/dist/_internal/server/features/bundles/data.js +26 -0
- package/dist/_internal/server/features/bundles/index.d.ts +1 -1
- package/dist/_internal/server/features/bundles/index.js +1 -1
- package/dist/_internal/server/features/cart/service.d.ts +3 -3
- package/dist/_internal/server/features/checkout/actions.js +25 -0
- package/dist/_internal/server/features/checkout/locked-lines.d.ts +38 -0
- package/dist/_internal/server/features/checkout/locked-lines.js +171 -0
- package/dist/_internal/server/features/checkout/source-context.d.ts +5 -0
- package/dist/_internal/server/features/checkout/source-context.js +157 -0
- package/dist/_internal/server/features/homepage/index.d.ts +0 -1
- package/dist/_internal/server/features/homepage/index.js +7 -1
- package/dist/_internal/server/features/lottery/adapters.js +4 -0
- package/dist/_internal/server/features/offers/adapters.d.ts +103 -0
- package/dist/_internal/server/features/offers/adapters.js +88 -0
- package/dist/_internal/server/features/offers/index.d.ts +2 -0
- package/dist/_internal/server/features/offers/index.js +1 -0
- package/dist/_internal/server/features/orders/adapters.js +24 -0
- package/dist/_internal/server/features/products/data.js +15 -37
- package/dist/_internal/server/features/products/index.d.ts +1 -1
- package/dist/_internal/server/features/products/index.js +1 -1
- package/dist/_internal/server/features/products/list-public.d.ts +46 -20
- package/dist/_internal/server/features/products/list-public.js +266 -90
- package/dist/_internal/server/features/products/listing-tab-counts.d.ts +44 -0
- package/dist/_internal/server/features/products/listing-tab-counts.js +97 -0
- package/dist/_internal/server/features/site-settings/adapters.d.ts +51 -0
- package/dist/_internal/server/features/site-settings/adapters.js +75 -0
- package/dist/_internal/server/features/site-settings/index.d.ts +1 -0
- package/dist/_internal/server/features/site-settings/index.js +1 -0
- package/dist/_internal/server/features/stores/adapters.d.ts +56 -0
- package/dist/_internal/server/features/stores/adapters.js +91 -0
- package/dist/_internal/server/features/stores/index.d.ts +1 -0
- package/dist/_internal/server/features/stores/index.js +1 -0
- package/dist/_internal/server/jobs/core/auctionSettlement.js +56 -2
- package/dist/_internal/server/jobs/core/offerExpiry.js +55 -19
- package/dist/_internal/server/jobs/core/paymentReviewAutoApprove.d.ts +5 -0
- package/dist/_internal/server/jobs/core/paymentReviewAutoApprove.js +33 -1
- package/dist/_internal/server/jobs/handlers/messages.d.ts +7 -0
- package/dist/_internal/server/jobs/handlers/messages.js +10 -0
- package/dist/_internal/shared/actions/action-registry.js +17 -1
- package/dist/_internal/shared/checkout/lanes.d.ts +25 -0
- package/dist/_internal/shared/checkout/lanes.js +26 -0
- package/dist/_internal/shared/features/auctions/config.d.ts +27 -0
- package/dist/_internal/shared/features/auctions/config.js +27 -0
- package/dist/_internal/shared/features/auctions/schema.d.ts +14 -2
- package/dist/_internal/shared/features/auctions/schema.js +13 -1
- package/dist/_internal/shared/features/blog/schema.d.ts +2 -2
- package/dist/_internal/shared/features/cart/schema.d.ts +20 -15
- package/dist/_internal/shared/features/cart/schema.js +15 -0
- package/dist/_internal/shared/features/categories/bundle-schemas.d.ts +32 -32
- package/dist/_internal/shared/features/emi/schedule.d.ts +24 -3
- package/dist/_internal/shared/features/emi/schedule.js +20 -5
- package/dist/_internal/shared/features/events/schema.d.ts +2 -2
- package/dist/_internal/shared/features/homepage/section-gate.d.ts +14 -0
- package/dist/_internal/shared/features/homepage/section-gate.js +51 -0
- package/dist/_internal/shared/features/offers/config.d.ts +30 -0
- package/dist/_internal/shared/features/offers/config.js +59 -0
- package/dist/_internal/shared/features/orders/schema.d.ts +8 -8
- package/dist/_internal/shared/features/products/schema.d.ts +30 -30
- package/dist/_internal/shared/features/products/to-product-item.js +6 -0
- package/dist/_internal/shared/features/reviews/schema.d.ts +2 -2
- package/dist/_internal/shared/features/site-settings/fees.d.ts +60 -0
- package/dist/_internal/shared/features/site-settings/fees.js +39 -0
- package/dist/_internal/shared/features/site-settings/types.d.ts +40 -0
- package/dist/_internal/shared/features/site-settings/types.js +1 -0
- package/dist/_internal/shared/history/history.d.ts +88 -0
- package/dist/_internal/shared/history/history.js +198 -0
- package/dist/_internal/shared/history/index.d.ts +4 -0
- package/dist/_internal/shared/history/index.js +2 -0
- package/dist/_internal/shared/history/types.d.ts +61 -0
- package/dist/_internal/shared/history/types.js +6 -0
- package/dist/_internal/shared/listing-types/_registry.d.ts +54 -1
- package/dist/_internal/shared/listing-types/_registry.js +67 -0
- package/dist/_internal/shared/listing-types/art/config.d.ts +3 -0
- package/dist/_internal/shared/listing-types/art/config.js +5 -0
- package/dist/_internal/shared/listing-types/auction/config.d.ts +13 -0
- package/dist/_internal/shared/listing-types/auction/config.js +21 -0
- package/dist/_internal/shared/listing-types/availability.d.ts +79 -0
- package/dist/_internal/shared/listing-types/availability.js +104 -0
- package/dist/_internal/shared/listing-types/capabilities.d.ts +15 -0
- package/dist/_internal/shared/listing-types/capabilities.js +12 -0
- package/dist/_internal/shared/listing-types/classified/config.d.ts +3 -0
- package/dist/_internal/shared/listing-types/classified/config.js +5 -0
- package/dist/_internal/shared/listing-types/digital-code/config.d.ts +11 -0
- package/dist/_internal/shared/listing-types/digital-code/config.js +22 -2
- package/dist/_internal/shared/listing-types/live/config.d.ts +3 -0
- package/dist/_internal/shared/listing-types/live/config.js +5 -0
- package/dist/_internal/shared/listing-types/pre-order/config.d.ts +3 -0
- package/dist/_internal/shared/listing-types/pre-order/config.js +19 -0
- package/dist/_internal/shared/listing-types/prize-draw/config.d.ts +11 -0
- package/dist/_internal/shared/listing-types/prize-draw/config.js +15 -0
- package/dist/_internal/shared/listing-types/standard/config.d.ts +3 -0
- package/dist/_internal/shared/listing-types/standard/config.js +5 -0
- package/dist/_internal/shared/listing-types/stickers/config.d.ts +3 -0
- package/dist/_internal/shared/listing-types/stickers/config.js +5 -0
- package/dist/client.d.ts +20 -7
- package/dist/client.js +22 -4
- package/dist/constants/api-endpoints.d.ts +6 -0
- package/dist/constants/api-endpoints.js +2 -0
- package/dist/constants/field-names.d.ts +57 -8
- package/dist/constants/field-names.js +101 -8
- package/dist/constants/table-keys.d.ts +17 -0
- package/dist/constants/table-keys.js +17 -0
- package/dist/errors/error-codes.d.ts +13 -1
- package/dist/errors/error-codes.js +13 -1
- package/dist/errors/error-display-map.js +12 -3
- package/dist/errors/messages.d.ts +10 -2
- package/dist/errors/messages.js +10 -2
- package/dist/features/about/components/AboutView.d.ts +3 -0
- package/dist/features/about/components/AboutView.js +3 -2
- package/dist/features/about/components/PolicyPageView.d.ts +2 -1
- package/dist/features/about/components/PolicyPageView.js +61 -23
- package/dist/features/about/components/PublicProfileView.js +5 -0
- package/dist/features/about/schemas/firestore.d.ts +3 -0
- package/dist/features/account/components/UserBidsView.js +12 -4
- package/dist/features/account/components/UserOrdersView.js +7 -0
- package/dist/features/account/components/UserReturnsView.d.ts +3 -0
- package/dist/features/account/components/UserReturnsView.js +17 -1
- package/dist/features/account/components/index.d.ts +0 -2
- package/dist/features/account/components/index.js +0 -1
- package/dist/features/account/schemas/index.d.ts +2 -2
- package/dist/features/account/types/index.d.ts +21 -2
- package/dist/features/addresses/schemas/index.d.ts +4 -4
- package/dist/features/admin/components/AdminArtView.js +8 -3
- package/dist/features/admin/components/AdminAuditLogView.js +1 -0
- package/dist/features/admin/components/AdminBidsView.js +15 -2
- package/dist/features/admin/components/AdminBlogEditorView.js +65 -33
- package/dist/features/admin/components/AdminClassifiedView.js +8 -3
- package/dist/features/admin/components/AdminDigitalCodesView.js +8 -3
- package/dist/features/admin/components/AdminFeatureEditorView.js +35 -6
- package/dist/features/admin/components/AdminFeatureFlagsView.js +1 -0
- package/dist/features/admin/components/AdminLiveView.js +8 -3
- package/dist/features/admin/components/AdminNavEditorView.js +15 -5
- package/dist/features/admin/components/AdminNewsletterView.js +18 -2
- package/dist/features/admin/components/AdminOffersView.d.ts +4 -0
- package/dist/features/admin/components/AdminOffersView.js +230 -0
- package/dist/features/admin/components/AdminOrdersView.js +10 -1
- package/dist/features/admin/components/AdminPaymentMethodsView.js +24 -4
- package/dist/features/admin/components/AdminPrizeDrawsView.js +5 -0
- package/dist/features/admin/components/AdminProductsView.js +16 -29
- package/dist/features/admin/components/AdminScammerEditorView.d.ts +2 -1
- package/dist/features/admin/components/AdminScammerEditorView.js +3 -13
- package/dist/features/admin/components/AdminScammersView.js +3 -8
- package/dist/features/admin/components/AdminSectionsView.js +32 -23
- package/dist/features/admin/components/AdminSessionsView.js +17 -2
- package/dist/features/admin/components/AdminSiteSettingsView.js +31 -13
- package/dist/features/admin/components/AdminSiteView.js +6 -2
- package/dist/features/admin/components/AdminStickersView.js +8 -3
- package/dist/features/admin/components/ViewAuditLogEntryModal.js +1 -0
- 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.d.ts +4 -2
- package/dist/features/admin/components/sections/adminSectionsTypes.js +45 -27
- package/dist/features/admin/constants/filter-tabs.d.ts +89 -0
- package/dist/features/admin/constants/filter-tabs.js +56 -0
- package/dist/features/admin/schemas/firestore.d.ts +122 -3
- package/dist/features/admin/schemas/firestore.js +1 -0
- package/dist/features/admin/schemas/homepage-section-form.d.ts +22 -0
- package/dist/features/admin/schemas/homepage-section-form.js +68 -0
- package/dist/features/admin/schemas/nav-item-form.d.ts +74 -0
- package/dist/features/admin/schemas/nav-item-form.js +72 -0
- package/dist/features/auctions/actions/bid-actions.d.ts +20 -5
- package/dist/features/auctions/actions/bid-actions.js +93 -20
- package/dist/features/auctions/components/AuctionBidsTable.d.ts +10 -1
- package/dist/features/auctions/components/AuctionBidsTable.js +22 -4
- package/dist/features/auctions/components/AuctionDetailPageView.js +11 -6
- package/dist/features/auctions/components/AuctionsListView.js +5 -6
- package/dist/features/auctions/components/MarketplaceAuctionCard.d.ts +7 -0
- package/dist/features/auctions/components/MarketplaceAuctionCard.js +19 -2
- package/dist/features/auctions/components/PlaceBidFormClient.d.ts +1 -2
- package/dist/features/auctions/components/PlaceBidFormClient.js +8 -4
- package/dist/features/auctions/components/bid-detail-fields.d.ts +29 -0
- package/dist/features/auctions/components/bid-detail-fields.js +103 -0
- package/dist/features/auctions/repository/bid.repository.d.ts +13 -0
- package/dist/features/auctions/repository/bid.repository.js +23 -0
- package/dist/features/auctions/schemas/firestore.d.ts +17 -0
- package/dist/features/auctions/schemas/index.d.ts +12 -12
- package/dist/features/audit-log/schemas/firestore.d.ts +14 -0
- package/dist/features/audit-log/schemas/firestore.js +7 -0
- package/dist/features/auth/permissions/constants.d.ts +1 -1
- package/dist/features/auth/permissions/constants.js +10 -1
- package/dist/features/blog/actions/blog-actions.d.ts +7 -7
- package/dist/features/blog/schemas/blog-form.d.ts +80 -0
- package/dist/features/blog/schemas/blog-form.js +100 -0
- package/dist/features/blog/schemas/index.d.ts +6 -6
- package/dist/features/cart/repository/cart.repository.js +1 -0
- package/dist/features/cart/schemas/firestore.d.ts +15 -0
- package/dist/features/catalogue/schemas/validation.d.ts +4 -4
- package/dist/features/categories/components/BrandDetailPageView.js +23 -40
- package/dist/features/categories/components/BrandDetailTabs.d.ts +7 -7
- package/dist/features/categories/components/BrandDetailTabs.js +8 -12
- package/dist/features/categories/components/BundleCollage.js +1 -1
- package/dist/features/categories/components/CategoryDetailPageView.js +53 -57
- package/dist/features/categories/components/CategoryDetailTabs.d.ts +10 -8
- package/dist/features/categories/components/CategoryDetailTabs.js +11 -16
- package/dist/features/categories/components/CategoryProductsListing.js +11 -1
- package/dist/features/categories/components/ConcernCard.js +7 -1
- package/dist/features/categories/schemas/firestore.d.ts +13 -0
- package/dist/features/categories/schemas/index.d.ts +26 -26
- package/dist/features/classified/components/ClassifiedDetailPageView.d.ts +16 -1
- package/dist/features/classified/components/ClassifiedDetailPageView.js +23 -3
- package/dist/features/events/components/AdminEventEditorView.js +32 -54
- package/dist/features/events/components/AdminEventEntriesView.js +22 -3
- package/dist/features/events/components/AdminEventsView.js +15 -11
- package/dist/features/events/components/EventFilters.d.ts +9 -1
- package/dist/features/events/components/EventFilters.js +45 -21
- package/dist/features/events/components/EventsIndexListing.d.ts +6 -1
- package/dist/features/events/components/EventsIndexListing.js +2 -2
- package/dist/features/events/components/EventsListPageView.js +13 -4
- package/dist/features/events/repository/events.repository.d.ts +17 -0
- package/dist/features/events/repository/events.repository.js +27 -0
- package/dist/features/events/schemas/event-form.d.ts +113 -0
- package/dist/features/events/schemas/event-form.js +82 -0
- package/dist/features/events/schemas/firestore.d.ts +12 -0
- package/dist/features/events/schemas/firestore.js +12 -0
- package/dist/features/events/schemas/index.d.ts +50 -50
- package/dist/features/events/types/index.d.ts +11 -0
- package/dist/features/events/types/index.js +57 -1
- package/dist/features/faq/actions/faq-actions.d.ts +12 -12
- package/dist/features/faq/schemas/index.d.ts +8 -8
- package/dist/features/filters/FilterFacetSection.d.ts +7 -1
- package/dist/features/filters/FilterFacetSection.js +11 -3
- package/dist/features/grouped/schemas/grouped-listing-form.d.ts +101 -0
- package/dist/features/grouped/schemas/grouped-listing-form.js +89 -0
- package/dist/features/grouped/schemas/index.d.ts +4 -4
- package/dist/features/homepage/actions/homepage-section-actions.d.ts +11 -3
- package/dist/features/homepage/actions/homepage-section-actions.js +33 -1
- package/dist/features/homepage/components/AnnouncementBar.d.ts +7 -1
- package/dist/features/homepage/components/AnnouncementBar.js +3 -3
- package/dist/features/homepage/components/MarketplaceHomepageView.js +20 -6
- package/dist/features/homepage/hooks/homepage-query.d.ts +16 -0
- package/dist/features/homepage/hooks/homepage-query.js +45 -0
- package/dist/features/homepage/hooks/useFeaturedAuctions.d.ts +10 -0
- package/dist/features/homepage/hooks/useFeaturedAuctions.js +28 -8
- package/dist/features/homepage/hooks/useFeaturedPreOrders.js +17 -8
- package/dist/features/homepage/hooks/useFeaturedProducts.js +15 -8
- package/dist/features/homepage/schemas/index.d.ts +33 -2
- package/dist/features/homepage/schemas/index.js +24 -0
- package/dist/features/layout/AppLayoutShell.d.ts +11 -1
- package/dist/features/layout/AppLayoutShell.js +15 -14
- package/dist/features/layout/BackToTop.d.ts +9 -0
- package/dist/features/layout/BackToTop.js +10 -1
- package/dist/features/layout/BottomActions.js +28 -25
- package/dist/features/layout/BottomChrome.d.ts +34 -0
- package/dist/features/layout/BottomChrome.js +55 -0
- package/dist/features/layout/BottomNavLayout.d.ts +6 -0
- package/dist/features/layout/BottomNavLayout.js +7 -1
- package/dist/features/layout/BottomNavbar.js +15 -2
- package/dist/features/layout/FooterLayout.js +11 -1
- package/dist/features/layout/NavbarLayout.js +1 -1
- package/dist/features/layout/TitleBarLayout.d.ts +7 -0
- package/dist/features/layout/TitleBarLayout.js +28 -5
- package/dist/features/layout/index.d.ts +2 -0
- package/dist/features/layout/index.js +1 -0
- package/dist/features/lottery/types/index.d.ts +3 -0
- package/dist/features/media/MediaPickerModal.js +5 -1
- package/dist/features/messages/schemas/index.d.ts +2 -2
- package/dist/features/orders/components/OrderScopeTabs.d.ts +20 -0
- package/dist/features/orders/components/OrderScopeTabs.js +31 -0
- package/dist/features/orders/components/OrderStatusTimeline.d.ts +5 -1
- package/dist/features/orders/components/OrderStatusTimeline.js +39 -20
- package/dist/features/orders/constants/order-scope.d.ts +48 -0
- package/dist/features/orders/constants/order-scope.js +111 -0
- package/dist/features/orders/repository/orders.repository.d.ts +54 -7
- package/dist/features/orders/repository/orders.repository.js +121 -22
- package/dist/features/orders/schemas/firestore.d.ts +152 -0
- package/dist/features/orders/schemas/firestore.js +30 -0
- package/dist/features/orders/schemas/index.d.ts +18 -18
- package/dist/features/orders/types/index.d.ts +35 -7
- package/dist/features/pre-orders/components/PreOrdersIndexListing.js +14 -9
- package/dist/features/pre-orders/components/PreOrdersListView.js +6 -5
- package/dist/features/pre-orders/schemas/index.d.ts +1 -1
- package/dist/features/products/actions/product-actions.d.ts +14 -0
- package/dist/features/products/actions/product-actions.js +49 -11
- package/dist/features/products/api/route.d.ts +12 -0
- package/dist/features/products/api/route.js +42 -100
- package/dist/features/products/components/ArtStickersListView.js +2 -2
- package/dist/features/products/components/AuctionsIndexListing.js +13 -11
- package/dist/features/products/components/AvailabilityTabs.d.ts +11 -0
- package/dist/features/products/components/AvailabilityTabs.js +47 -0
- package/dist/features/products/components/GroupSettingsPanel.js +5 -1
- package/dist/features/products/components/MakeOfferButton.js +48 -49
- package/dist/features/products/components/PrizeDrawCollage.d.ts +20 -4
- package/dist/features/products/components/PrizeDrawCollage.js +9 -1
- package/dist/features/products/components/PrizeDrawsIndexListing.js +14 -12
- package/dist/features/products/components/PrizeDrawsListingView.js +2 -2
- package/dist/features/products/components/PrizeDrawsSection.d.ts +7 -1
- package/dist/features/products/components/PrizeDrawsSection.js +23 -18
- package/dist/features/products/components/ProductDetailPageView.d.ts +4 -1
- package/dist/features/products/components/ProductDetailPageView.js +18 -4
- package/dist/features/products/components/ProductForm.js +5 -2
- package/dist/features/products/components/ProductGrid.js +6 -1
- package/dist/features/products/components/ProductsIndexListing.js +22 -32
- package/dist/features/products/components/ProductsIndexPageView.js +6 -9
- package/dist/features/products/components/index.d.ts +2 -2
- package/dist/features/products/components/index.js +1 -1
- package/dist/features/products/config/listing-type-listing-config.d.ts +20 -0
- package/dist/features/products/config/listing-type-listing-config.js +22 -10
- package/dist/features/products/constants/action-defs.d.ts +2 -0
- package/dist/features/products/constants/action-defs.js +6 -0
- package/dist/features/products/hooks/useAvailabilityScope.d.ts +13 -0
- package/dist/features/products/hooks/useAvailabilityScope.js +19 -0
- package/dist/features/products/hooks/useProducts.d.ts +5 -0
- package/dist/features/products/hooks/useProducts.js +13 -2
- package/dist/features/products/repository/products.repository.d.ts +31 -2
- package/dist/features/products/repository/products.repository.js +53 -21
- package/dist/features/products/schemas/firestore.d.ts +17 -3
- package/dist/features/products/schemas/firestore.js +7 -2
- package/dist/features/products/schemas/index.d.ts +64 -64
- package/dist/features/products/schemas/product-features.validators.d.ts +79 -6
- package/dist/features/products/schemas/product-features.validators.js +63 -0
- package/dist/features/products/types/index.d.ts +16 -1
- package/dist/features/promotions/schemas/index.d.ts +2 -2
- package/dist/features/reviews/schemas/index.d.ts +16 -16
- package/dist/features/scams/constants/scammer-status-display.d.ts +28 -0
- package/dist/features/scams/constants/scammer-status-display.js +79 -0
- package/dist/features/search/schemas/index.d.ts +4 -4
- package/dist/features/seller/actions/offer-actions.js +57 -4
- package/dist/features/seller/components/OfferPhaseTimeline.d.ts +59 -0
- package/dist/features/seller/components/OfferPhaseTimeline.js +107 -0
- package/dist/features/seller/components/SellerArtView.js +5 -0
- package/dist/features/seller/components/SellerAuctionsView.js +13 -15
- package/dist/features/seller/components/SellerBidsView.js +11 -4
- package/dist/features/seller/components/SellerClassifiedView.js +5 -0
- package/dist/features/seller/components/SellerCouponEditorView.js +17 -17
- package/dist/features/seller/components/SellerDigitalCodesView.js +5 -0
- package/dist/features/seller/components/SellerLiveView.js +5 -0
- package/dist/features/seller/components/SellerOffersView.js +2 -1
- package/dist/features/seller/components/SellerOrdersView.js +31 -9
- package/dist/features/seller/components/SellerPayoutSettingsView.js +56 -31
- package/dist/features/seller/components/SellerPreOrdersView.js +5 -0
- package/dist/features/seller/components/SellerPrizeDrawsView.js +14 -16
- package/dist/features/seller/components/SellerProductShell.js +7 -1
- package/dist/features/seller/components/SellerProductsView.js +11 -8
- package/dist/features/seller/components/SellerStickersView.js +5 -0
- package/dist/features/seller/components/index.d.ts +0 -2
- package/dist/features/seller/components/index.js +0 -1
- package/dist/features/seller/repository/offer.repository.d.ts +142 -24
- package/dist/features/seller/repository/offer.repository.js +165 -26
- package/dist/features/seller/schemas/coupon-form.d.ts +72 -0
- package/dist/features/seller/schemas/coupon-form.js +116 -0
- package/dist/features/seller/schemas/firestore.d.ts +56 -2
- package/dist/features/seller/schemas/firestore.js +27 -0
- package/dist/features/seller/schemas/index.d.ts +2 -2
- package/dist/features/seller/schemas/offer-forms.d.ts +46 -0
- package/dist/features/seller/schemas/offer-forms.js +42 -0
- package/dist/features/seller/server.d.ts +0 -1
- package/dist/features/seller/server.js +0 -1
- package/dist/features/shell/SectionForm.d.ts +99 -0
- package/dist/features/shell/SectionForm.js +128 -0
- package/dist/features/shell/build-sections.d.ts +63 -0
- package/dist/features/shell/build-sections.js +207 -0
- package/dist/features/shell/entity-form.d.ts +109 -0
- package/dist/features/shell/entity-form.js +96 -0
- package/dist/features/shell/field-ui-meta.d.ts +115 -0
- package/dist/features/shell/field-ui-meta.js +324 -0
- package/dist/features/shell/index.d.ts +8 -0
- package/dist/features/shell/index.js +4 -0
- package/dist/features/status-history/components/StatusTimeline.d.ts +74 -0
- package/dist/features/status-history/components/StatusTimeline.js +46 -0
- package/dist/features/status-history/index.d.ts +11 -0
- package/dist/features/status-history/index.js +10 -0
- package/dist/features/store-extensions/components/ReviewDecisionModal.d.ts +27 -0
- package/dist/features/store-extensions/components/ReviewDecisionModal.js +82 -0
- package/dist/features/store-extensions/index.d.ts +12 -0
- package/dist/features/store-extensions/index.js +12 -0
- package/dist/features/store-extensions/schemas/admin-notification-form.d.ts +36 -0
- package/dist/features/store-extensions/schemas/admin-notification-form.js +114 -0
- package/dist/features/store-extensions/schemas/analytics-forms.d.ts +57 -0
- package/dist/features/store-extensions/schemas/analytics-forms.js +158 -0
- package/dist/features/store-extensions/schemas/custom-role-form.d.ts +85 -0
- package/dist/features/store-extensions/schemas/custom-role-form.js +118 -0
- package/dist/features/store-extensions/schemas/firestore.d.ts +21 -1
- package/dist/features/store-extensions/schemas/firestore.js +60 -0
- package/dist/features/store-extensions/schemas/google-config-form.d.ts +53 -0
- package/dist/features/store-extensions/schemas/google-config-form.js +105 -0
- package/dist/features/store-extensions/schemas/item-request-create-form.d.ts +28 -0
- package/dist/features/store-extensions/schemas/item-request-create-form.js +80 -0
- package/dist/features/store-extensions/schemas/listing-template-form.d.ts +78 -0
- package/dist/features/store-extensions/schemas/listing-template-form.js +87 -0
- package/dist/features/store-extensions/schemas/moderation-review-form.d.ts +49 -0
- package/dist/features/store-extensions/schemas/moderation-review-form.js +101 -0
- package/dist/features/store-extensions/schemas/payout-method-form.d.ts +153 -0
- package/dist/features/store-extensions/schemas/payout-method-form.js +155 -0
- package/dist/features/store-extensions/schemas/report-create-form.d.ts +25 -0
- package/dist/features/store-extensions/schemas/report-create-form.js +79 -0
- package/dist/features/store-extensions/schemas/report-review-form.d.ts +47 -0
- package/dist/features/store-extensions/schemas/report-review-form.js +111 -0
- package/dist/features/store-extensions/schemas/shipping-config-form.d.ts +148 -0
- package/dist/features/store-extensions/schemas/shipping-config-form.js +104 -0
- package/dist/features/store-extensions/schemas/store-category-form.d.ts +54 -0
- package/dist/features/store-extensions/schemas/store-category-form.js +73 -0
- package/dist/features/stores/api/[storeSlug]/route.js +2 -25
- package/dist/features/stores/components/StoreAuctionsListing.js +15 -1
- package/dist/features/stores/components/StoreClassifiedsListing.js +12 -2
- package/dist/features/stores/components/StoreDetailLayoutView.js +24 -71
- package/dist/features/stores/components/StoreDigitalCodesListing.js +12 -2
- package/dist/features/stores/components/StoreLiveItemsListing.js +12 -2
- package/dist/features/stores/components/StorePreOrdersListing.js +12 -1
- package/dist/features/stores/components/StoreProductsListing.js +11 -1
- package/dist/features/stores/repository/store.repository.js +6 -0
- package/dist/features/stores/schemas/index.d.ts +6 -6
- package/dist/features/support/schemas/index.d.ts +4 -4
- package/dist/features/tester/actions/checklist-item-actions.d.ts +4 -4
- package/dist/features/tester/seed-data/offers-tester-seed-data.js +119 -3
- package/dist/features/tester/seed-data/products-tester-seed-data.js +131 -1
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +431 -1
- package/dist/features/wishlist/hooks/useWishlistWithGuest.d.ts +1 -1
- package/dist/index.d.ts +25 -12
- package/dist/index.js +26 -11
- package/dist/next/routing/route-map.d.ts +23 -3
- package/dist/next/routing/route-map.js +14 -2
- package/dist/providers/db-firebase/base.repository.d.ts +30 -0
- package/dist/providers/db-firebase/base.repository.js +50 -1
- package/dist/providers/db-firebase/index.d.ts +1 -1
- package/dist/providers/db-firebase/index.js +1 -1
- package/dist/providers/db-firebase/sieve.d.ts +25 -0
- package/dist/providers/db-firebase/sieve.js +52 -18
- package/dist/react/hooks/useUrlTable.js +10 -1
- package/dist/schemas/firestore-value.d.ts +8 -0
- package/dist/schemas/firestore-value.js +44 -0
- package/dist/schemas/index.d.ts +1 -1
- package/dist/schemas/index.js +1 -1
- package/dist/schemas/registry.d.ts +702 -69
- package/dist/schemas/registry.js +64 -1
- package/dist/seed/_helpers/category-forest.d.ts +2 -0
- package/dist/seed/_helpers/category-forest.js +309 -0
- package/dist/seed/_helpers/category-tree.d.ts +27 -0
- package/dist/seed/_helpers/category-tree.js +103 -0
- package/dist/seed/bids-seed-data.js +30 -0
- package/dist/seed/catalogue-seed-data.js +12 -12
- package/dist/seed/categories-seed-data.js +177 -162
- package/dist/seed/events-seed-data.js +61 -10
- package/dist/seed/homepage-sections-seed-data.js +5 -1
- package/dist/seed/offers-seed-data.js +244 -34
- package/dist/seed/orders-seed-data.js +336 -1
- package/dist/seed/products-art-seed-data.js +50 -12
- package/dist/seed/products-auctions-seed-data.js +138 -18
- package/dist/seed/products-classifieds-seed-data.js +80 -16
- package/dist/seed/products-digital-codes-seed-data.js +66 -18
- package/dist/seed/products-live-items-seed-data.js +64 -13
- package/dist/seed/products-preorders-seed-data.d.ts +0 -1
- package/dist/seed/products-preorders-seed-data.js +52 -13
- package/dist/seed/products-prize-draws-seed-data.js +63 -14
- package/dist/seed/products-standard-seed-data.js +99 -26
- package/dist/seed/products-stickers-seed-data.js +47 -12
- package/dist/seed/reviews-seed-data.js +6 -1
- package/dist/seed/site-settings-seed-data.d.ts +1 -38
- package/dist/seed/site-settings-seed-data.js +38 -76
- package/dist/seed/stores-seed-data.js +71 -1
- package/dist/seed/users-seed-data.js +15 -5
- package/dist/server-entry.d.ts +1 -2
- package/dist/server-entry.js +4 -3
- package/dist/server.d.ts +6 -3
- package/dist/server.js +13 -4
- package/dist/styles.css +78 -12
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/components/Button.style.css +19 -4
- package/dist/ui/components/CollapsibleSection.d.ts +17 -1
- package/dist/ui/components/CollapsibleSection.js +6 -2
- package/dist/ui/components/FilterChipGroup.d.ts +25 -1
- package/dist/ui/components/FilterChipGroup.js +21 -4
- package/dist/ui/components/HandMode.style.css +47 -0
- package/dist/ui/components/Layout.d.ts +9 -2
- package/dist/ui/components/Layout.js +1 -1
- package/dist/ui/components/ListingLayout.js +8 -8
- package/dist/ui/components/ListingLayout.style.css +8 -7
- package/dist/ui/components/RecordDetailModal.d.ts +10 -1
- package/dist/ui/components/RecordDetailModal.js +2 -2
- package/dist/ui/components/StickyToolbar.d.ts +3 -1
- package/dist/ui/components/StickyToolbar.js +62 -20
- package/dist/ui/components/bottom-chrome-slot.d.ts +21 -0
- package/dist/ui/components/bottom-chrome-slot.js +29 -0
- package/dist/ui/components/index.style.css +3 -0
- package/dist/ui/forms/FormShell.d.ts +43 -13
- package/dist/ui/forms/FormShell.js +22 -11
- package/dist/ui/forms/index.d.ts +1 -1
- package/dist/ui/index.d.ts +1 -1
- package/package.json +2 -1
- package/scripts/node-esm-loader.mjs +65 -0
- package/scripts/seed-cli-loader.mjs +6 -49
- package/scripts/seed-cli.mjs +1 -1
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* WHY: "How does a scammer status look and read" had FOUR hand-written answers
|
|
3
|
+
* and they had already diverged in a way that shipped a real defect:
|
|
4
|
+
*
|
|
5
|
+
* · `AdminScammersView.STATUS_BADGE` — Record<string, string>
|
|
6
|
+
* · `AdminScammerEditorView.STATUS_COLOR` — Record<string, string>
|
|
7
|
+
* · `AdminScammerEditorView.STATUS_OPTIONS` — a literal label list
|
|
8
|
+
* · `ADMIN_SCAMMER_STATUS_TABS` — the filter chips
|
|
9
|
+
*
|
|
10
|
+
* Both colour maps carried the SAME broken `removed` entry:
|
|
11
|
+
*
|
|
12
|
+
* "bg-[…surface] text-[…muted] bg-[…surface-elevated] text-[…muted]"
|
|
13
|
+
*
|
|
14
|
+
* — two backgrounds and two inks on one element. Tailwind emits all four
|
|
15
|
+
* and the winner is decided by stylesheet order, not by position in the
|
|
16
|
+
* class string, so which background actually painted was not what either
|
|
17
|
+
* map said. One bug, two copies: the Duplication Framework's bug-fix
|
|
18
|
+
* multiplier, which is what makes this a consolidate rather than a
|
|
19
|
+
* third copy.
|
|
20
|
+
*
|
|
21
|
+
* WHAT: One display module beside the union it describes.
|
|
22
|
+
*
|
|
23
|
+
* ## Everything here is keyed `Record<ScammerStatus, …>`
|
|
24
|
+
*
|
|
25
|
+
* Not `Record<string, …>`. A status added to the union without a label or a
|
|
26
|
+
* badge is then a COMPILE error rather than a row that silently renders with
|
|
27
|
+
* the fallback styling of a status it is not — which is precisely how the
|
|
28
|
+
* loose maps hid the broken `removed` entry.
|
|
29
|
+
*
|
|
30
|
+
* EXPORTS:
|
|
31
|
+
* SCAMMER_STATUS_LABEL, SCAMMER_STATUS_BADGE, SCAMMER_STATUS_OPTIONS,
|
|
32
|
+
* toScammerStatus
|
|
33
|
+
*
|
|
34
|
+
* @tag domain:scams
|
|
35
|
+
* @tag layer:constants
|
|
36
|
+
* @tag pattern:none
|
|
37
|
+
* @tag access:isomorphic
|
|
38
|
+
* @tag consumers:AdminScammersView,AdminScammerEditorView
|
|
39
|
+
* @tag sideEffects:none
|
|
40
|
+
*/
|
|
41
|
+
import { ScammerStatusValues } from "../schemas/firestore";
|
|
42
|
+
/** Human-readable label. Replaces two `.replace(/_/g, " ")` call sites, which
|
|
43
|
+
* rendered "pending review" — mechanically correct, and not a written label. */
|
|
44
|
+
export const SCAMMER_STATUS_LABEL = {
|
|
45
|
+
pending_review: "Pending review",
|
|
46
|
+
verified: "Verified",
|
|
47
|
+
rejected: "Rejected",
|
|
48
|
+
removed: "Removed",
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Inline-chip styling. Every entry is a `bg-{status}-surface` + `text-{status}`
|
|
52
|
+
* pair per Recurrent Root Cause #67 — the two tokens invert together with the
|
|
53
|
+
* theme, which a literal `text-white` would not.
|
|
54
|
+
*
|
|
55
|
+
* `removed` is the neutral case: it has no status colour because it is not a
|
|
56
|
+
* status outcome, it is a withdrawal. ONE background, ONE ink.
|
|
57
|
+
*/
|
|
58
|
+
export const SCAMMER_STATUS_BADGE = {
|
|
59
|
+
pending_review: "bg-warning-surface text-warning",
|
|
60
|
+
verified: "bg-success-surface text-success",
|
|
61
|
+
rejected: "bg-error-surface text-error",
|
|
62
|
+
removed: "bg-[var(--appkit-color-surface-elevated)] text-[var(--appkit-color-text-muted)]",
|
|
63
|
+
};
|
|
64
|
+
/** Dropdown options, derived from the union so one cannot go missing. */
|
|
65
|
+
export const SCAMMER_STATUS_OPTIONS = Object.values(ScammerStatusValues).map((value) => ({ value, label: SCAMMER_STATUS_LABEL[value] }));
|
|
66
|
+
/**
|
|
67
|
+
* Narrow an unknown API value to a real status.
|
|
68
|
+
*
|
|
69
|
+
* The list view used `toStringValue(item.status, "pending_review")`, which
|
|
70
|
+
* produces a `string` — so an unrecognised value flowed on as if it were a
|
|
71
|
+
* status and every consumer fell back to the pending styling. This maps
|
|
72
|
+
* anything unrecognised to `pending_review` explicitly, and the return type
|
|
73
|
+
* says so.
|
|
74
|
+
*/
|
|
75
|
+
export function toScammerStatus(value) {
|
|
76
|
+
return Object.values(ScammerStatusValues).includes(value)
|
|
77
|
+
? value
|
|
78
|
+
: ScammerStatusValues.PENDING_REVIEW;
|
|
79
|
+
}
|
|
@@ -26,22 +26,22 @@ export declare const searchProductItemSchema: z.ZodObject<{
|
|
|
26
26
|
slug: string;
|
|
27
27
|
currency?: string | undefined;
|
|
28
28
|
status?: string | undefined;
|
|
29
|
+
price?: number | undefined;
|
|
30
|
+
mainImage?: string | undefined;
|
|
29
31
|
featured?: boolean | undefined;
|
|
30
32
|
listingType?: "standard" | "auction" | "pre-order" | "prize-draw" | "bundle" | undefined;
|
|
31
33
|
isPromoted?: boolean | undefined;
|
|
32
|
-
price?: number | undefined;
|
|
33
|
-
mainImage?: string | undefined;
|
|
34
34
|
}, {
|
|
35
35
|
id: string;
|
|
36
36
|
title: string;
|
|
37
37
|
slug: string;
|
|
38
38
|
currency?: string | undefined;
|
|
39
39
|
status?: string | undefined;
|
|
40
|
+
price?: number | undefined;
|
|
41
|
+
mainImage?: string | undefined;
|
|
40
42
|
featured?: boolean | undefined;
|
|
41
43
|
listingType?: "standard" | "auction" | "pre-order" | "prize-draw" | "bundle" | undefined;
|
|
42
44
|
isPromoted?: boolean | undefined;
|
|
43
|
-
price?: number | undefined;
|
|
44
|
-
mainImage?: string | undefined;
|
|
45
45
|
}>;
|
|
46
46
|
/**
|
|
47
47
|
* Form/query schema for a search request.
|
|
@@ -14,7 +14,11 @@ import { storeRepository } from "../../stores/repository/store.repository";
|
|
|
14
14
|
import { cartRepository } from "../../cart/repository/cart.repository";
|
|
15
15
|
import { maskOfferForSeller } from "../../../security";
|
|
16
16
|
import { ERROR_MESSAGES, AuthorizationError, ValidationError, NotFoundError, OFFER_ERROR_CODES, } from "../../../errors";
|
|
17
|
+
import { siteSettingsRepository } from "../../admin/repository/site-settings.repository";
|
|
18
|
+
import { canMakeOffer } from "../../../_internal/shared/listing-types/capabilities";
|
|
19
|
+
import { normalizeListingType } from "../../products/utils/listing-type";
|
|
17
20
|
import { OfferStatusValues } from "../schemas";
|
|
21
|
+
import { DEFAULT_MIN_OFFER_PERCENT, minOfferAmount, } from "../../../_internal/shared/features/offers/config";
|
|
18
22
|
/**
|
|
19
23
|
* How long an accepted offer stays claimable at its locked price. Shared by the
|
|
20
24
|
* seller-accept path, the buyer-accepts-counter path, and the expiry sweep so
|
|
@@ -26,14 +30,33 @@ const ERR_NOT_AUTHORISED = "Not authorized";
|
|
|
26
30
|
// --- Buyer: Make an Offer --------------------------------------------------
|
|
27
31
|
export async function makeOffer(userId, userEmail, input) {
|
|
28
32
|
const { productId, offerAmount, buyerNote } = input;
|
|
33
|
+
/**
|
|
34
|
+
* Three independent gates, checked in widening-to-narrowing order. Only
|
|
35
|
+
* `makeOffer` is guarded — switching the site flag off must stop NEW offers
|
|
36
|
+
* without stranding in-flight ones, so accept/decline/counter/withdraw and
|
|
37
|
+
* `checkoutOffer` deliberately stay open.
|
|
38
|
+
*/
|
|
39
|
+
const settings = await siteSettingsRepository.getSingleton().catch(() => null);
|
|
40
|
+
if (settings?.featureFlags?.offers === false) {
|
|
41
|
+
throw new ValidationError(ERROR_MESSAGES.OFFER.DISABLED, {
|
|
42
|
+
code: OFFER_ERROR_CODES.DISABLED,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
29
45
|
const product = await productRepository.findById(productId);
|
|
30
46
|
if (!product)
|
|
31
47
|
throw new NotFoundError(ERROR_MESSAGES.PRODUCT.NOT_FOUND);
|
|
48
|
+
// Some listing types cannot be negotiated at all — an auction already has
|
|
49
|
+
// bidding, a prize-draw sells fixed-price entries. This was never checked, so
|
|
50
|
+
// an offer could be (and in seed data was) attached to an auction, producing
|
|
51
|
+
// a cart line that claims `listingType: "standard"` in the offer lane.
|
|
52
|
+
if (!canMakeOffer(normalizeListingType(product))) {
|
|
53
|
+
throw new ValidationError("Offers aren't available for this kind of listing.");
|
|
54
|
+
}
|
|
32
55
|
if (!product.allowOffers)
|
|
33
56
|
throw new ValidationError("This product does not accept offers.");
|
|
34
|
-
const minAllowed =
|
|
57
|
+
const minAllowed = minOfferAmount(product.price, product.minOfferPercent);
|
|
35
58
|
if (offerAmount < minAllowed)
|
|
36
|
-
throw new ValidationError(`Minimum offer is ₹${minAllowed} (${product.minOfferPercent ??
|
|
59
|
+
throw new ValidationError(`Minimum offer is ₹${minAllowed} (${product.minOfferPercent ?? DEFAULT_MIN_OFFER_PERCENT}% of listing price).`);
|
|
37
60
|
if (offerAmount >= product.price)
|
|
38
61
|
throw new ValidationError("Your offer must be below the listed price. Use Add to Cart instead.");
|
|
39
62
|
const offerCount = await offerRepository.countByBuyerAndProduct(userId, productId, product.updatedAt);
|
|
@@ -187,7 +210,24 @@ export async function counterOfferByBuyer(userId, userEmail, input) {
|
|
|
187
210
|
const offerCount = await offerRepository.countByBuyerAndProduct(userId, offer.productId, product.updatedAt);
|
|
188
211
|
if (offerCount >= 3)
|
|
189
212
|
throw new ValidationError(ERROR_MESSAGES.OFFER.LIMIT_REACHED);
|
|
190
|
-
|
|
213
|
+
// ── Create BEFORE superseding. Order matters. ───────────────────────────
|
|
214
|
+
//
|
|
215
|
+
// This used to `withdraw()` first and `create()` second. If `create` threw —
|
|
216
|
+
// a validation slip, a Firestore blip — the buyer's live counter was ALREADY
|
|
217
|
+
// withdrawn, no replacement existed, the negotiation was dead, and one of
|
|
218
|
+
// their three offer slots had been burnt for nothing.
|
|
219
|
+
//
|
|
220
|
+
// Reversed, the worst failure is a transient extra pending offer, which
|
|
221
|
+
// `hasActiveOffer` and the expiry sweep both already handle.
|
|
222
|
+
//
|
|
223
|
+
// The chain fields are what make a multi-round negotiation readable at all:
|
|
224
|
+
// `previousOfferId` used to exist ONLY inside the serverLogger call below
|
|
225
|
+
// and never reached Firestore, so three rounds were three unrelated
|
|
226
|
+
// documents. `chainRootOfferId` is denormalised onto every round so a list
|
|
227
|
+
// row can render "Round 2" with zero extra reads, and `findChain` stays a
|
|
228
|
+
// single-field equality served by the automatic index.
|
|
229
|
+
const chainRoot = offer.chainRootOfferId ?? offer.id;
|
|
230
|
+
const nextRound = (offer.counterRound ?? 1) + 1;
|
|
191
231
|
const newOffer = await offerRepository.create({
|
|
192
232
|
productId: offer.productId,
|
|
193
233
|
productTitle: offer.productTitle,
|
|
@@ -202,7 +242,18 @@ export async function counterOfferByBuyer(userId, userEmail, input) {
|
|
|
202
242
|
listedPrice: offer.listedPrice,
|
|
203
243
|
currency: offer.currency,
|
|
204
244
|
buyerNote,
|
|
245
|
+
// NOT carrying `sellerNote` forward: it would render as "Seller note" on
|
|
246
|
+
// the new round, making the seller look like they had already answered a
|
|
247
|
+
// counter they have never seen. Round N-1 keeps its own note and is now
|
|
248
|
+
// reachable via `previousOfferId`.
|
|
249
|
+
previousOfferId: offer.id,
|
|
250
|
+
chainRootOfferId: chainRoot,
|
|
251
|
+
counterRound: nextRound,
|
|
205
252
|
});
|
|
253
|
+
// Now supersede the old round — one write, one history entry. A `withdrawn`
|
|
254
|
+
// document carrying `supersededByOfferId` renders as *Superseded* (neutral),
|
|
255
|
+
// not *Withdrawn* (negative): the buyer did not walk away, they countered.
|
|
256
|
+
await offerRepository.withdraw(offerId, newOffer.id, { actor: { uid: userId, role: "buyer" } }, offer);
|
|
206
257
|
const buyerCounterStore = offer.storeId ? await storeRepository.findById(offer.storeId) : null;
|
|
207
258
|
if (buyerCounterStore?.ownerId)
|
|
208
259
|
await sendNotification({
|
|
@@ -214,12 +265,14 @@ export async function counterOfferByBuyer(userId, userEmail, input) {
|
|
|
214
265
|
relatedId: newOffer.id,
|
|
215
266
|
relatedType: "offer",
|
|
216
267
|
});
|
|
268
|
+
// `previousOfferId` is no longer logged as its only record — it is a
|
|
269
|
+
// persisted field on `newOffer` now. A log line is not a data model.
|
|
217
270
|
serverLogger.info("counterOfferByBuyer", {
|
|
218
|
-
previousOfferId: offerId,
|
|
219
271
|
newOfferId: newOffer.id,
|
|
220
272
|
buyerUid: userId,
|
|
221
273
|
productId: offer.productId,
|
|
222
274
|
counterAmount,
|
|
275
|
+
counterRound: nextRound,
|
|
223
276
|
});
|
|
224
277
|
return newOffer;
|
|
225
278
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OfferPhaseTimeline — the offer-shaped wrapper around `<StatusTimeline>`.
|
|
3
|
+
*
|
|
4
|
+
* An offer is an order with more phases in front of it, so it shares the rail
|
|
5
|
+
* and supplies only its own vocabulary:
|
|
6
|
+
*
|
|
7
|
+
* made → countered → accepted → paid
|
|
8
|
+
* with declined / withdrawn / expired as the terminal-negative branch
|
|
9
|
+
*
|
|
10
|
+
* ## `superseded` is a RENDER KEY, never an OfferStatus
|
|
11
|
+
*
|
|
12
|
+
* A document that is `withdrawn` **and** carries `supersededByOfferId` is not
|
|
13
|
+
* a walk-away — the buyer countered, and the negotiation continued into the
|
|
14
|
+
* next round. It renders neutral ("Superseded"), not negative ("Withdrawn").
|
|
15
|
+
*
|
|
16
|
+
* Deliberately NOT an eighth `OfferStatus` value: `ADMIN_OFFER_STATUS_TABS`
|
|
17
|
+
* would then gain a chip that matches zero stored rows, which is exactly the
|
|
18
|
+
* defect `audit-filter-tab-enums` exists to catch.
|
|
19
|
+
*
|
|
20
|
+
* ## Back-compat
|
|
21
|
+
*
|
|
22
|
+
* Offers written before `statusHistory` existed have none. Branch B derives
|
|
23
|
+
* what it can from the scalars — and `expired` gets **no date at all**,
|
|
24
|
+
* because `expireMany` never wrote one. `updatedAt` is "last write of any
|
|
25
|
+
* kind" and `expiresAt` is a deadline, not an event; using either would be a
|
|
26
|
+
* fabricated timestamp.
|
|
27
|
+
*/
|
|
28
|
+
import { type TimelineEntry } from "../../status-history/components/StatusTimeline";
|
|
29
|
+
/** One round of a negotiation, as returned by the API. */
|
|
30
|
+
export interface OfferChainRound {
|
|
31
|
+
id: string;
|
|
32
|
+
counterRound: number;
|
|
33
|
+
status: string;
|
|
34
|
+
offerAmount: number;
|
|
35
|
+
counterAmount?: number;
|
|
36
|
+
superseded: boolean;
|
|
37
|
+
createdAt: string;
|
|
38
|
+
respondedAt?: string;
|
|
39
|
+
sellerNote?: string;
|
|
40
|
+
buyerNote?: string;
|
|
41
|
+
}
|
|
42
|
+
export interface OfferPhaseTimelineProps {
|
|
43
|
+
status: string;
|
|
44
|
+
timeline?: TimelineEntry[];
|
|
45
|
+
timelineTruncated?: number;
|
|
46
|
+
superseded?: boolean;
|
|
47
|
+
createdAt?: string;
|
|
48
|
+
respondedAt?: string;
|
|
49
|
+
acceptedAt?: string;
|
|
50
|
+
paidAt?: string;
|
|
51
|
+
/**
|
|
52
|
+
* Earlier rounds, oldest first. Concatenated ahead of this offer's own
|
|
53
|
+
* steps into ONE rail, each introduced by a "Round N" divider — a
|
|
54
|
+
* negotiation reads as one story, not N disconnected cards.
|
|
55
|
+
*/
|
|
56
|
+
chain?: OfferChainRound[];
|
|
57
|
+
className?: string;
|
|
58
|
+
}
|
|
59
|
+
export declare function OfferPhaseTimeline({ status, timeline, timelineTruncated, superseded, createdAt, respondedAt, acceptedAt, paidAt, chain, className, }: OfferPhaseTimelineProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* OfferPhaseTimeline — the offer-shaped wrapper around `<StatusTimeline>`.
|
|
5
|
+
*
|
|
6
|
+
* An offer is an order with more phases in front of it, so it shares the rail
|
|
7
|
+
* and supplies only its own vocabulary:
|
|
8
|
+
*
|
|
9
|
+
* made → countered → accepted → paid
|
|
10
|
+
* with declined / withdrawn / expired as the terminal-negative branch
|
|
11
|
+
*
|
|
12
|
+
* ## `superseded` is a RENDER KEY, never an OfferStatus
|
|
13
|
+
*
|
|
14
|
+
* A document that is `withdrawn` **and** carries `supersededByOfferId` is not
|
|
15
|
+
* a walk-away — the buyer countered, and the negotiation continued into the
|
|
16
|
+
* next round. It renders neutral ("Superseded"), not negative ("Withdrawn").
|
|
17
|
+
*
|
|
18
|
+
* Deliberately NOT an eighth `OfferStatus` value: `ADMIN_OFFER_STATUS_TABS`
|
|
19
|
+
* would then gain a chip that matches zero stored rows, which is exactly the
|
|
20
|
+
* defect `audit-filter-tab-enums` exists to catch.
|
|
21
|
+
*
|
|
22
|
+
* ## Back-compat
|
|
23
|
+
*
|
|
24
|
+
* Offers written before `statusHistory` existed have none. Branch B derives
|
|
25
|
+
* what it can from the scalars — and `expired` gets **no date at all**,
|
|
26
|
+
* because `expireMany` never wrote one. `updatedAt` is "last write of any
|
|
27
|
+
* kind" and `expiresAt` is a deadline, not an event; using either would be a
|
|
28
|
+
* fabricated timestamp.
|
|
29
|
+
*/
|
|
30
|
+
import { StatusTimeline, stepsFromEntries, } from "../../status-history/components/StatusTimeline";
|
|
31
|
+
const PHASE_LABELS = {
|
|
32
|
+
pending: "Offer made",
|
|
33
|
+
countered: "Countered",
|
|
34
|
+
accepted: "Accepted",
|
|
35
|
+
paid: "Paid",
|
|
36
|
+
declined: "Declined",
|
|
37
|
+
withdrawn: "Withdrawn",
|
|
38
|
+
expired: "Expired",
|
|
39
|
+
superseded: "Superseded",
|
|
40
|
+
};
|
|
41
|
+
const NEGATIVE = new Set(["declined", "withdrawn", "expired"]);
|
|
42
|
+
const labelFor = (status) => PHASE_LABELS[status] ?? status;
|
|
43
|
+
const isNegative = (status) => NEGATIVE.has(status);
|
|
44
|
+
/** Branch B — scalars only, for offers with no recorded history. */
|
|
45
|
+
function buildLegacySteps(o) {
|
|
46
|
+
const steps = [
|
|
47
|
+
{ key: "pending", label: PHASE_LABELS.pending, date: o.createdAt },
|
|
48
|
+
];
|
|
49
|
+
const s = (o.status ?? "").toLowerCase();
|
|
50
|
+
if (s === "countered") {
|
|
51
|
+
steps.push({ key: "countered", label: PHASE_LABELS.countered, date: o.respondedAt });
|
|
52
|
+
}
|
|
53
|
+
if (s === "accepted" || s === "paid") {
|
|
54
|
+
steps.push({
|
|
55
|
+
key: "accepted",
|
|
56
|
+
label: PHASE_LABELS.accepted,
|
|
57
|
+
date: o.acceptedAt ?? o.respondedAt,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
if (s === "paid") {
|
|
61
|
+
steps.push({ key: "paid", label: PHASE_LABELS.paid, date: o.paidAt });
|
|
62
|
+
}
|
|
63
|
+
if (s === "declined") {
|
|
64
|
+
steps.push({ key: "declined", label: PHASE_LABELS.declined, date: o.respondedAt, negative: true });
|
|
65
|
+
}
|
|
66
|
+
if (s === "withdrawn") {
|
|
67
|
+
steps.push(o.superseded
|
|
68
|
+
? { key: "superseded", label: PHASE_LABELS.superseded, date: o.respondedAt, muted: true }
|
|
69
|
+
: { key: "withdrawn", label: PHASE_LABELS.withdrawn, date: o.respondedAt, negative: true });
|
|
70
|
+
}
|
|
71
|
+
if (s === "expired") {
|
|
72
|
+
// No date, on purpose. `expireMany` never wrote one before W2.
|
|
73
|
+
steps.push({ key: "expired", label: PHASE_LABELS.expired, negative: true });
|
|
74
|
+
}
|
|
75
|
+
return steps;
|
|
76
|
+
}
|
|
77
|
+
/** Re-key a withdrawn+superseded step so it reads neutral, not negative. */
|
|
78
|
+
function applySupersede(steps, superseded) {
|
|
79
|
+
if (!superseded)
|
|
80
|
+
return steps;
|
|
81
|
+
return steps.map((s) => s.key === "withdrawn"
|
|
82
|
+
? { ...s, key: "superseded", label: PHASE_LABELS.superseded, negative: false, muted: true }
|
|
83
|
+
: s);
|
|
84
|
+
}
|
|
85
|
+
export function OfferPhaseTimeline({ status, timeline, timelineTruncated, superseded = false, createdAt, respondedAt, acceptedAt, paidAt, chain, className = "", }) {
|
|
86
|
+
const own = applySupersede(stepsFromEntries(timeline, labelFor, isNegative).length
|
|
87
|
+
? stepsFromEntries(timeline, labelFor, isNegative)
|
|
88
|
+
: buildLegacySteps({ status, createdAt, respondedAt, acceptedAt, paidAt, superseded }), superseded);
|
|
89
|
+
const priorRounds = (chain ?? []).filter((r) => r.id !== undefined);
|
|
90
|
+
const hasChain = priorRounds.length > 1;
|
|
91
|
+
const steps = hasChain
|
|
92
|
+
? priorRounds.flatMap((round, idx) => {
|
|
93
|
+
const roundSteps = applySupersede(buildLegacySteps({
|
|
94
|
+
status: round.status,
|
|
95
|
+
createdAt: round.createdAt,
|
|
96
|
+
respondedAt: round.respondedAt,
|
|
97
|
+
superseded: round.superseded,
|
|
98
|
+
}), round.superseded);
|
|
99
|
+
return roundSteps.map((s, i) => ({
|
|
100
|
+
...s,
|
|
101
|
+
roundLabel: i === 0 ? `Round ${round.counterRound || idx + 1}` : undefined,
|
|
102
|
+
note: i === roundSteps.length - 1 ? (round.sellerNote ?? round.buyerNote) : undefined,
|
|
103
|
+
}));
|
|
104
|
+
})
|
|
105
|
+
: own;
|
|
106
|
+
return (_jsx(StatusTimeline, { title: hasChain ? "Negotiation" : "Offer history", steps: steps, truncatedCount: timelineTruncated, emptyLabel: "No offer history yet", className: className }));
|
|
107
|
+
}
|
|
@@ -14,6 +14,8 @@ import { ROUTES } from "../../../constants/index";
|
|
|
14
14
|
import { toRecordArray, toRelativeDate, toCurrency, toStringValue, } from "../../admin/hooks/useAdminListingData";
|
|
15
15
|
import { DataListingView } from "../../admin/components/DataListingView";
|
|
16
16
|
import { SELLER_BULK_ACTIONS, ROW_ACTION_META } from "../../products/constants/action-defs";
|
|
17
|
+
import { useAvailabilityScope } from "../../products/hooks/useAvailabilityScope";
|
|
18
|
+
const LISTING_TYPES = ["art"];
|
|
17
19
|
const COLUMNS = [
|
|
18
20
|
{ key: "title", header: "Listing", render: (row) => _jsx(Text, { size: "sm", weight: "medium", children: row.title }) },
|
|
19
21
|
{ key: "price", header: "Price", render: (row) => _jsx(Text, { className: "tabular-nums", size: "sm", children: row.price }) },
|
|
@@ -57,6 +59,7 @@ export function SellerArtView({ onCreateClick, onEditClick, onDelete, onBulkDele
|
|
|
57
59
|
else
|
|
58
60
|
window.location.href = String(ROUTES.STORE.ART_NEW);
|
|
59
61
|
}, [onCreateClick]);
|
|
62
|
+
const scope = useAvailabilityScope(LISTING_TYPES);
|
|
60
63
|
const config = {
|
|
61
64
|
portal: "seller",
|
|
62
65
|
title: "Art",
|
|
@@ -87,6 +90,8 @@ export function SellerArtView({ onCreateClick, onEditClick, onDelete, onBulkDele
|
|
|
87
90
|
}),
|
|
88
91
|
getTotal: (response, mappedRows) => typeof response.meta?.total === "number" ? response.meta.total : mappedRows.length,
|
|
89
92
|
buildFilters: () => "listingType==art",
|
|
93
|
+
buildExtraParams: () => scope.extraParams,
|
|
94
|
+
renderAboveContent: scope.renderAboveContent,
|
|
90
95
|
primaryAction: { label: "New Art Print", onClick: () => handleCreate() },
|
|
91
96
|
// Mirrors the "Edit" row action below so table rows and cards both navigate on click.
|
|
92
97
|
onRowClick: (row) => handleEdit(row.id),
|
|
@@ -4,23 +4,22 @@ import { sieveFilter, SIEVE_OP } from "@mohasinac/appkit/client";
|
|
|
4
4
|
import { sortBy } from "@mohasinac/appkit/client";
|
|
5
5
|
import React, { useState, useCallback } from "react";
|
|
6
6
|
import { useEntityDelete } from "../../../react/hooks/useEntityDelete";
|
|
7
|
-
import { useUrlTable } from "../../../react/hooks/useUrlTable";
|
|
8
7
|
import { ConfirmDeleteModal, FilterChipGroup, ListingLayout, RowActionMenu } from "../../../ui";
|
|
9
8
|
import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
10
|
-
import { TABLE_KEYS } from "../../../constants/table-keys";
|
|
11
9
|
import { SELLER_AUCTION_STATUS_TABS } from "../../admin/constants/filter-tabs";
|
|
12
10
|
import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
|
|
13
11
|
import { ROUTES } from "../../../constants";
|
|
14
12
|
import { toRecordArray, toRelativeDate, toCurrency, toStringValue, } from "../../admin/hooks/useAdminListingData";
|
|
15
13
|
import { DataListingView } from "../../admin/components/DataListingView";
|
|
16
14
|
import { SELLER_BULK_ACTIONS, ROW_ACTION_META } from "../../products/constants/action-defs";
|
|
15
|
+
import { useAvailabilityScope } from "../../products/hooks/useAvailabilityScope";
|
|
16
|
+
const AUCTION_TYPES = ["auction"];
|
|
17
17
|
export function SellerAuctionsView({ children, onEditClick, onDelete, onBulkDelete, ...props }) {
|
|
18
18
|
// Independent of DataListingView's own internal useUrlTable() — both read
|
|
19
19
|
// and write the same URL, useUrlTable has no local state, so they stay in
|
|
20
20
|
// sync (same pattern SellerProductsView already uses for its "Show sold"
|
|
21
21
|
// toggle).
|
|
22
|
-
const
|
|
23
|
-
const showEnded = toggleTable.get(TABLE_KEYS.SHOW_ENDED) === "true";
|
|
22
|
+
const scope = useAvailabilityScope(AUCTION_TYPES);
|
|
24
23
|
const [deleteTargetId, setDeleteTargetId] = useState(null);
|
|
25
24
|
const { deletingId, handleDelete: performDelete } = useEntityDelete({
|
|
26
25
|
endpoint: SELLER_ENDPOINTS.PRODUCT_BY_ID,
|
|
@@ -46,7 +45,7 @@ export function SellerAuctionsView({ children, onEditClick, onDelete, onBulkDele
|
|
|
46
45
|
title: "Auctions",
|
|
47
46
|
searchPlaceholder: "Search auctions by product name",
|
|
48
47
|
emptyLabel: "No auctions found",
|
|
49
|
-
filterKeys: ["status"
|
|
48
|
+
filterKeys: ["status"],
|
|
50
49
|
defaultSort: sortBy("auctionEndDate", "ASC"),
|
|
51
50
|
queryKey: ["seller", "auctions", "listing"],
|
|
52
51
|
endpoint: SELLER_ENDPOINTS.AUCTIONS,
|
|
@@ -73,21 +72,20 @@ export function SellerAuctionsView({ children, onEditClick, onDelete, onBulkDele
|
|
|
73
72
|
buildFilters: (state) => {
|
|
74
73
|
// Previously missing entirely — the seller Auctions tab silently
|
|
75
74
|
// listed the entire seller catalog, not just auctions.
|
|
75
|
+
//
|
|
76
|
+
// The hide-ended clause that used to live here is gone: it hard-coded
|
|
77
|
+
// `auctionEndDate >= now`, which is an INEQUALITY pushed straight into
|
|
78
|
+
// Firestore alongside whichever sort the toolbar happened to be on —
|
|
79
|
+
// the FAILED_PRECONDITION shape of Root Cause #59. The scope param now
|
|
80
|
+
// carries it, and `listPublicProducts` decides whether it can be pushed
|
|
81
|
+
// down or has to be applied over a bounded fetch.
|
|
76
82
|
const parts = ["listingType==auction"];
|
|
77
83
|
if (state.status && state.status !== "All")
|
|
78
84
|
parts.push(sieveFilter("status", SIEVE_OP.EQ, state.status));
|
|
79
|
-
if (state[TABLE_KEYS.SHOW_ENDED] !== "true") {
|
|
80
|
-
parts.push(sieveFilter("auctionEndDate", SIEVE_OP.GTE, new Date().toISOString()));
|
|
81
|
-
}
|
|
82
85
|
return parts.join(",");
|
|
83
86
|
},
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
label: "Show ended",
|
|
87
|
-
active: showEnded,
|
|
88
|
-
onChange: (next) => toggleTable.set(TABLE_KEYS.SHOW_ENDED, next ? "true" : ""),
|
|
89
|
-
},
|
|
90
|
-
],
|
|
87
|
+
buildExtraParams: () => scope.extraParams,
|
|
88
|
+
renderAboveContent: scope.renderAboveContent,
|
|
91
89
|
// Rule #7: bulk-action array sourced from the SELLER_BULK_ACTIONS preset.
|
|
92
90
|
buildBulkActions: onBulkDelete
|
|
93
91
|
? (selection) => SELLER_BULK_ACTIONS.auctions.map((id) => ({
|
|
@@ -1,10 +1,11 @@
|
|
|
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 { normalizeError } from "../../../errors/normalize";
|
|
4
4
|
import { SIEVE_OP, Stack, sieveFilter } from "@mohasinac/appkit/client";
|
|
5
5
|
import { sortBy } from "@mohasinac/appkit/client";
|
|
6
6
|
import { useState, useCallback } from "react";
|
|
7
|
-
import { Badge, Button, Div, FilterChipGroup, Span, Text, useToast } from "../../../ui";
|
|
7
|
+
import { Badge, Button, Div, FilterChipGroup, RecordDetailModal, Span, Text, useToast } from "../../../ui";
|
|
8
|
+
import { buildBidDetailFields, bidStatusBadge } from "../../auctions/components/bid-detail-fields";
|
|
8
9
|
import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
9
10
|
import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
|
|
10
11
|
import { buildBulkAction } from "../../../_internal/shared/actions/bulk-helpers";
|
|
@@ -29,6 +30,7 @@ const STATUS_BADGE = {
|
|
|
29
30
|
cancelled: "danger",
|
|
30
31
|
};
|
|
31
32
|
export function SellerBidsView({ endpoint = SELLER_ENDPOINTS.BIDS }) {
|
|
33
|
+
const [detail, setDetail] = useState(null);
|
|
32
34
|
const { showToast } = useToast();
|
|
33
35
|
const [collapsedGroups, setCollapsedGroups] = useState(new Set());
|
|
34
36
|
const toggleGroup = (id) => setCollapsedGroups((s) => {
|
|
@@ -130,9 +132,14 @@ export function SellerBidsView({ endpoint = SELLER_ENDPOINTS.BIDS }) {
|
|
|
130
132
|
}
|
|
131
133
|
return (_jsx(Stack, { gap: "3", children: groupedRows.map((group) => {
|
|
132
134
|
const collapsed = collapsedGroups.has(group.id);
|
|
133
|
-
return (_jsxs(Div, { className: "border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)]", rounded: "lg", children: [_jsxs(Button, { variant: "ghost", type: "button", onClick: () => toggleGroup(group.id), justify: "between", paddingX: "md", paddingY: "y-xs-tall", textSize: "sm", weight: "semibold", textColor: "default", className: "w-full hover:bg-[var(--appkit-color-surface-raised)]", children: [_jsxs(Span, { className: "truncate", children: [group.title, " ", _jsxs(Span, { size: "xs", weight: "normal", className: "text-[var(--appkit-color-text-muted)]", children: ["\u00B7 ", group.bids.length, " bid", group.bids.length === 1 ? "" : "s"] })] }), _jsx(Span, { "aria-hidden": true, className: "text-[var(--appkit-color-text-muted)]", children: collapsed ? "▸" : "▾" })] }), !collapsed && (_jsx(Div, { className: "border-t border-[var(--appkit-color-border)]", children: _jsx(DataTable, { rows: group.bids, columns: columns, isLoading: false, emptyLabel: "", selectedIds: new Set(selection.selectedIds), onToggleSelect: selection.toggleSelect
|
|
135
|
+
return (_jsxs(Div, { className: "border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)]", rounded: "lg", children: [_jsxs(Button, { variant: "ghost", type: "button", onClick: () => toggleGroup(group.id), justify: "between", paddingX: "md", paddingY: "y-xs-tall", textSize: "sm", weight: "semibold", textColor: "default", className: "w-full hover:bg-[var(--appkit-color-surface-raised)]", children: [_jsxs(Span, { className: "truncate", children: [group.title, " ", _jsxs(Span, { size: "xs", weight: "normal", className: "text-[var(--appkit-color-text-muted)]", children: ["\u00B7 ", group.bids.length, " bid", group.bids.length === 1 ? "" : "s"] })] }), _jsx(Span, { "aria-hidden": true, className: "text-[var(--appkit-color-text-muted)]", children: collapsed ? "▸" : "▾" })] }), !collapsed && (_jsx(Div, { className: "border-t border-[var(--appkit-color-border)]", children: _jsx(DataTable, { rows: group.bids, columns: columns, isLoading: false, emptyLabel: "", selectedIds: new Set(selection.selectedIds), onToggleSelect: selection.toggleSelect,
|
|
136
|
+
// Without this the row could be selected and bulk-acted
|
|
137
|
+
// on but never opened — a seller could cancel a bid they
|
|
138
|
+
// had no way to read (Root Cause #56).
|
|
139
|
+
onRowClick: (row) => setDetail(row) }) }))] }, group.id));
|
|
134
140
|
}) }));
|
|
135
141
|
},
|
|
136
142
|
};
|
|
137
|
-
|
|
143
|
+
const detailBadge = detail ? bidStatusBadge(detail.status) : null;
|
|
144
|
+
return (_jsxs(_Fragment, { children: [_jsx(DataListingView, { config: config }), _jsx(RecordDetailModal, { isOpen: detail !== null, onClose: () => setDetail(null), title: detail?.productTitle || "Bid", badges: detailBadge ? [{ label: detailBadge.label }] : undefined, fields: detail ? buildBidDetailFields(detail, "seller") : undefined })] }));
|
|
138
145
|
}
|
|
@@ -10,6 +10,8 @@ import { ROUTES } from "../../../constants/index";
|
|
|
10
10
|
import { toRecordArray, toRelativeDate, toCurrency, toStringValue, } from "../../admin/hooks/useAdminListingData";
|
|
11
11
|
import { DataListingView } from "../../admin/components/DataListingView";
|
|
12
12
|
import { SELLER_BULK_ACTIONS, ROW_ACTION_META } from "../../products/constants/action-defs";
|
|
13
|
+
import { useAvailabilityScope } from "../../products/hooks/useAvailabilityScope";
|
|
14
|
+
const LISTING_TYPES = ["classified"];
|
|
13
15
|
const COLUMNS = [
|
|
14
16
|
{ key: "title", header: "Listing", render: (row) => _jsx(Text, { size: "sm", weight: "medium", children: row.title }) },
|
|
15
17
|
{ key: "price", header: "Price", render: (row) => _jsx(Text, { className: "tabular-nums", size: "sm", children: row.price }) },
|
|
@@ -58,6 +60,7 @@ export function SellerClassifiedView({ onCreateClick, onEditClick, onDelete, onB
|
|
|
58
60
|
else
|
|
59
61
|
window.location.href = String(ROUTES.STORE.CLASSIFIED_NEW);
|
|
60
62
|
}, [onCreateClick]);
|
|
63
|
+
const scope = useAvailabilityScope(LISTING_TYPES);
|
|
61
64
|
const config = {
|
|
62
65
|
portal: "seller",
|
|
63
66
|
title: "Classified",
|
|
@@ -92,6 +95,8 @@ export function SellerClassifiedView({ onCreateClick, onEditClick, onDelete, onB
|
|
|
92
95
|
? response.meta.total
|
|
93
96
|
: mappedRows.length,
|
|
94
97
|
buildFilters: () => "listingType==classified",
|
|
98
|
+
buildExtraParams: () => scope.extraParams,
|
|
99
|
+
renderAboveContent: scope.renderAboveContent,
|
|
95
100
|
primaryAction: { label: "New Classified", onClick: () => handleCreate() },
|
|
96
101
|
// Mirrors the "Edit" row action below so table rows and cards both navigate on click.
|
|
97
102
|
onRowClick: (row) => handleEdit(row.id),
|
|
@@ -5,6 +5,8 @@ import { Badge, Button, Checkbox, Div, Form, Heading, IconButton, Input, Label,
|
|
|
5
5
|
import { ProductInlineSelect } from "./ProductInlineSelect";
|
|
6
6
|
import { CategoryInlineSelect } from "./CategoryInlineSelect";
|
|
7
7
|
import { normalizeError } from "../../../errors/normalize";
|
|
8
|
+
import { FormErrorSummary, applyZodIssues } from "../../../ui/forms";
|
|
9
|
+
import { sellerCouponFormSchema } from "../schemas/coupon-form";
|
|
8
10
|
const __O = {
|
|
9
11
|
hidden: "overflow-hidden",
|
|
10
12
|
};
|
|
@@ -39,22 +41,20 @@ export function SellerCouponEditorView({ couponId, initial, onSave, onCancel, })
|
|
|
39
41
|
const [saving, setSaving] = useState(false);
|
|
40
42
|
const [error, setError] = useState(null);
|
|
41
43
|
const set = (key, value) => setDraft((d) => ({ ...d, [key]: value }));
|
|
42
|
-
const handleSubmit = async (e) => {
|
|
44
|
+
const handleSubmit = async (e, setFieldError) => {
|
|
43
45
|
e.preventDefault();
|
|
44
|
-
if
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
if (draft.startDate > draft.endDate) {
|
|
57
|
-
setError("Start date must be before end date");
|
|
46
|
+
// Replaces four hand-rolled `if` checks that covered presence and date
|
|
47
|
+
// order and nothing else — a 500% percentage discount, a non-numeric
|
|
48
|
+
// value, a negative minimum spend and a per-customer limit above the
|
|
49
|
+
// total all passed. Errors now land on the field, not in one banner.
|
|
50
|
+
const parsed = sellerCouponFormSchema.safeParse(draft);
|
|
51
|
+
if (!parsed.success) {
|
|
52
|
+
if (setFieldError) {
|
|
53
|
+
applyZodIssues(parsed.error.issues, setFieldError);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
setError(parsed.error.issues[0]?.message ?? "Please check the form.");
|
|
57
|
+
}
|
|
58
58
|
return;
|
|
59
59
|
}
|
|
60
60
|
setError(null);
|
|
@@ -71,11 +71,11 @@ export function SellerCouponEditorView({ couponId, initial, onSave, onCancel, })
|
|
|
71
71
|
}
|
|
72
72
|
};
|
|
73
73
|
const isEdit = Boolean(couponId);
|
|
74
|
-
return (_jsx(Form, {
|
|
74
|
+
return (_jsx(Form, { schema: sellerCouponFormSchema, onSubmit: (e) => e.preventDefault(), children: ({ setFieldError }) => (_jsxs(Stack, { gap: "none", className: `max-w-lg mx-auto border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] ${__O.hidden}`, rounded: "xl", shadow: "sm", children: [_jsx(Div, { className: "h-[3px] w-full [background:linear-gradient(to_right,var(--appkit-color-primary-700)_0%,var(--appkit-color-cobalt)_55%,var(--appkit-color-secondary-400)_100%)]", "aria-hidden": "true" }), _jsx(Div, { border: "bottom", paddingY: "y-md-lg", padding: "x-lg", children: _jsxs(Row, { justify: "between", gap: "3", children: [_jsx(Heading, { level: 2, className: "text-[var(--appkit-color-text)]", size: "lg", weight: "semibold", children: isEdit ? "Edit Coupon" : "Create Coupon" }), isEdit && (_jsx(Badge, { variant: draft.isActive ? "success" : "default", children: draft.isActive ? "Active" : "Inactive" }))] }) }), _jsxs(Stack, { gap: "5", padding: "lg", children: [_jsx(FormErrorSummary, {}), error && (_jsx(Div, { textSize: "sm", className: "border border-error/20", color: "error", surface: "danger-surface", padding: "inline", rounded: "lg", children: error })), _jsx(Input, { label: "Coupon Code", value: draft.code, onChange: (e) => set("code", e.target.value.toUpperCase().replace(/\s+/g, "")), placeholder: "e.g. WELCOME10", required: true, disabled: isEdit, helperText: isEdit ? "Code cannot be changed after creation" : "Customers enter this at checkout" }), _jsx(Select, { label: "Discount Type", value: draft.type, options: TYPE_OPTIONS, onChange: (e) => set("type", e.target.value) }), draft.type !== "free_shipping" && (_jsx(Input, { label: draft.type === "percentage" ? "Discount Percentage (%)" : "Discount Amount (₹)", type: "number", min: 1, max: draft.type === "percentage" ? 100 : undefined, value: draft.value, onChange: (e) => set("value", e.target.value), placeholder: draft.type === "percentage" ? "e.g. 10" : "e.g. 50", required: true, helperText: draft.type === "percentage"
|
|
75
75
|
? "Enter a value between 1 and 100"
|
|
76
76
|
: "Fixed rupee discount applied to the order" })), draft.type === "percentage" && (_jsx(Input, { label: "Max Discount Cap (\u20B9, optional)", type: "number", min: 0, value: draft.maxDiscount, onChange: (e) => set("maxDiscount", e.target.value), placeholder: "Leave blank for no cap", helperText: "Maximum rupee discount regardless of percentage" })), _jsx(Input, { label: "Minimum Order Amount (\u20B9, optional)", type: "number", min: 0, value: draft.minPurchase, onChange: (e) => set("minPurchase", e.target.value), placeholder: "Leave blank for no minimum" }), _jsxs(Div, { children: [_jsx(Text, { size: "sm", className: CLS_SECTION_LABEL, children: "Usage Limits" }), _jsxs(Div, { layout: "grid", gap: "3", className: "grid-cols-2", children: [_jsx(Input, { label: "Total Uses", type: "number", min: 0, value: draft.totalLimit, onChange: (e) => set("totalLimit", e.target.value), placeholder: "0 = unlimited" }), _jsx(Input, { label: "Per Customer", type: "number", min: 0, value: draft.perUserLimit, onChange: (e) => set("perUserLimit", e.target.value), placeholder: "0 = unlimited" })] })] }), _jsxs(Div, { children: [_jsx(Text, { size: "sm", className: CLS_SECTION_LABEL, children: "Validity Period" }), _jsxs(Div, { layout: "grid", gap: "3", className: "grid-cols-2", children: [_jsx(Input, { label: "Start Date", type: "date", value: draft.startDate, onChange: (e) => set("startDate", e.target.value), required: true }), _jsx(Input, { label: "End Date", type: "date", value: draft.endDate, onChange: (e) => set("endDate", e.target.value), required: true })] })] }), _jsxs(Div, { children: [_jsx(Text, { size: "sm", className: CLS_SECTION_LABEL, children: "Applicability (optional)" }), _jsxs(Stack, { gap: "sm", children: [_jsxs(Div, { children: [_jsx(Text, { size: "xs", className: "mb-1 text-[var(--appkit-color-text-secondary)]", children: "Applicable products" }), _jsx(ProductInlineSelect, { scope: "store", multiple: true, value: draft.applicableProducts, onChange: (v) => set("applicableProducts", v), placeholder: "Restrict to specific products\u2026" })] }), _jsxs(Div, { children: [_jsx(Text, { size: "xs", className: "mb-1 text-[var(--appkit-color-text-secondary)]", children: "Applicable categories" }), _jsx(CategoryInlineSelect, { value: "", onChange: (id) => {
|
|
77
77
|
if (!id || draft.applicableCategories.includes(id))
|
|
78
78
|
return;
|
|
79
79
|
set("applicableCategories", [...draft.applicableCategories, id]);
|
|
80
|
-
}, placeholder: "Add a category\u2026" }), draft.applicableCategories.length > 0 && (_jsx(Row, { wrap: true, gap: "sm", padding: "t-xs", children: draft.applicableCategories.map((cid) => (_jsxs(Span, { layout: "inline-flex", gap: "xs", border: "strong", padding: "pill-sm", rounded: "full", surface: "muted", color: "primary", size: "xs", children: [cid, _jsx(IconButton, { type: "button", "aria-label": `Remove ${cid}`, variant: "ghost", className: "text-[var(--appkit-color-text-muted)] hover:text-[var(--appkit-color-text)]", onClick: () => set("applicableCategories", draft.applicableCategories.filter((c) => c !== cid)), children: "\u00D7" })] }, cid))) }))] }), _jsx(Text, { size: "xs", className: "text-[var(--appkit-color-text-secondary)]", children: "Leave both empty to apply the coupon to every product in your store." })] })] }), _jsx(Row, { className: "border border-[var(--appkit-color-border)] dark:border-[var(--appkit-color-border-dark)]", padding: "inline", align: "center", gap: "3", rounded: "lg", children: _jsxs(Label, { layout: "flex", gap: "lg", className: "cursor-pointer w-full", children: [_jsx(Checkbox, { bare: true, checked: draft.isActive, onChange: (e) => set("isActive", e.target.checked), className: "h-4 w-4 rounded border-[var(--appkit-color-border)] text-[var(--appkit-color-primary)] focus:ring-[var(--appkit-color-primary)]" }), _jsxs(Div, { className: "flex-1 min-w-0", children: [_jsx(Text, { size: "sm", className: "text-[var(--appkit-color-text-primary)]", weight: "medium", children: "Active" }), _jsx(Text, { size: "xs", className: "text-[var(--appkit-color-text-secondary)]", children: "Customers can apply this coupon at checkout" })] })] }) })] }), _jsx(Div, { className: "border-t border-[var(--appkit-color-border)]", padding: "inlineLg", children: _jsxs(Row, { justify: "end", gap: "3", children: [onCancel && (_jsx(Button, { type: "button", variant: "outline", onClick: onCancel, disabled: saving, children: "Cancel" })), _jsx(Button, { type: "submit", isLoading: saving, disabled: saving, children: isEdit ? "Save Changes" : "Create Coupon" })] }) })] }) }));
|
|
80
|
+
}, placeholder: "Add a category\u2026" }), draft.applicableCategories.length > 0 && (_jsx(Row, { wrap: true, gap: "sm", padding: "t-xs", children: draft.applicableCategories.map((cid) => (_jsxs(Span, { layout: "inline-flex", gap: "xs", border: "strong", padding: "pill-sm", rounded: "full", surface: "muted", color: "primary", size: "xs", children: [cid, _jsx(IconButton, { type: "button", "aria-label": `Remove ${cid}`, variant: "ghost", className: "text-[var(--appkit-color-text-muted)] hover:text-[var(--appkit-color-text)]", onClick: () => set("applicableCategories", draft.applicableCategories.filter((c) => c !== cid)), children: "\u00D7" })] }, cid))) }))] }), _jsx(Text, { size: "xs", className: "text-[var(--appkit-color-text-secondary)]", children: "Leave both empty to apply the coupon to every product in your store." })] })] }), _jsx(Row, { className: "border border-[var(--appkit-color-border)] dark:border-[var(--appkit-color-border-dark)]", padding: "inline", align: "center", gap: "3", rounded: "lg", children: _jsxs(Label, { layout: "flex", gap: "lg", className: "cursor-pointer w-full", children: [_jsx(Checkbox, { bare: true, checked: draft.isActive, onChange: (e) => set("isActive", e.target.checked), className: "h-4 w-4 rounded border-[var(--appkit-color-border)] text-[var(--appkit-color-primary)] focus:ring-[var(--appkit-color-primary)]" }), _jsxs(Div, { className: "flex-1 min-w-0", children: [_jsx(Text, { size: "sm", className: "text-[var(--appkit-color-text-primary)]", weight: "medium", children: "Active" }), _jsx(Text, { size: "xs", className: "text-[var(--appkit-color-text-secondary)]", children: "Customers can apply this coupon at checkout" })] })] }) })] }), _jsx(Div, { className: "border-t border-[var(--appkit-color-border)]", padding: "inlineLg", children: _jsxs(Row, { justify: "end", gap: "3", children: [onCancel && (_jsx(Button, { type: "button", variant: "outline", onClick: onCancel, disabled: saving, children: "Cancel" })), _jsx(Button, { type: "submit", isLoading: saving, disabled: saving, onClick: (e) => void handleSubmit(e, setFieldError), children: isEdit ? "Save Changes" : "Create Coupon" })] }) })] })) }));
|
|
81
81
|
}
|