@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
|
@@ -4,35 +4,103 @@
|
|
|
4
4
|
* Data access layer for the `offers` Firestore collection.
|
|
5
5
|
* Handles buyer make-an-offer, seller counter/accept/decline lifecycle.
|
|
6
6
|
*/
|
|
7
|
-
import { BaseRepository } from "../../../providers/db-firebase";
|
|
7
|
+
import { BaseRepository, parseSieveDateValue } from "../../../providers/db-firebase";
|
|
8
8
|
import type { SieveModel, FirebaseSieveResult } from "../../../providers/db-firebase";
|
|
9
9
|
import type { OfferDocument, OfferCreateInput, OfferUpdateInput } from "../schemas";
|
|
10
|
+
import { type HistoryActor } from "../../../_internal/shared/history/index";
|
|
11
|
+
/**
|
|
12
|
+
* Who/why for a write that lands in an offer's `statusHistory`.
|
|
13
|
+
* Optional throughout so existing callers keep compiling and record `system`.
|
|
14
|
+
*/
|
|
15
|
+
export interface OfferWriteContext {
|
|
16
|
+
actor?: HistoryActor;
|
|
17
|
+
trigger?: string;
|
|
18
|
+
reason?: string;
|
|
19
|
+
note?: string;
|
|
20
|
+
}
|
|
10
21
|
declare class OfferRepository extends BaseRepository<OfferDocument> {
|
|
11
22
|
constructor();
|
|
12
23
|
/** Override mapDoc to auto-decrypt PII on every offer read */
|
|
13
24
|
protected mapDoc<D = OfferDocument>(snap: import("../../../providers/db-firebase").DocumentSnapshot): D;
|
|
25
|
+
/**
|
|
26
|
+
* Every Timestamp field here carries `parseValue: parseSieveDateValue`.
|
|
27
|
+
*
|
|
28
|
+
* Without it a GTE/LTE on a Timestamp field silently matches ZERO documents:
|
|
29
|
+
* sievejs's default coercion leaves an ISO string as a string, and Firestore
|
|
30
|
+
* requires an inequality's value to match the stored type. That is Root Cause
|
|
31
|
+
* #47, and `createdAt` flipping from `canFilter: false` to `true` here is
|
|
32
|
+
* exactly the transition that introduces it.
|
|
33
|
+
*
|
|
34
|
+
* `checkoutDeadline` is added by hand: `audit-sieve-date-fields` recognises
|
|
35
|
+
* Timestamp fields by an `...At` / `...Date` / `...Time` suffix, which this
|
|
36
|
+
* name does not have, so the audit cannot flag it for you.
|
|
37
|
+
*/
|
|
14
38
|
static readonly SIEVE_FIELDS: {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
39
|
+
id: {
|
|
40
|
+
canFilter: boolean;
|
|
41
|
+
canSort: boolean;
|
|
42
|
+
};
|
|
43
|
+
status: {
|
|
44
|
+
canFilter: boolean;
|
|
45
|
+
canSort: boolean;
|
|
18
46
|
};
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
47
|
+
productId: {
|
|
48
|
+
canFilter: boolean;
|
|
49
|
+
canSort: boolean;
|
|
22
50
|
};
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
51
|
+
productTitle: {
|
|
52
|
+
canFilter: boolean;
|
|
53
|
+
canSort: boolean;
|
|
26
54
|
};
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
55
|
+
buyerUid: {
|
|
56
|
+
canFilter: boolean;
|
|
57
|
+
canSort: boolean;
|
|
30
58
|
};
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
59
|
+
buyerName: {
|
|
60
|
+
canFilter: boolean;
|
|
61
|
+
canSort: boolean;
|
|
62
|
+
};
|
|
63
|
+
storeId: {
|
|
64
|
+
canFilter: boolean;
|
|
65
|
+
canSort: boolean;
|
|
66
|
+
};
|
|
67
|
+
storeName: {
|
|
68
|
+
canFilter: boolean;
|
|
69
|
+
canSort: boolean;
|
|
70
|
+
};
|
|
71
|
+
offerAmount: {
|
|
72
|
+
canFilter: boolean;
|
|
73
|
+
canSort: boolean;
|
|
74
|
+
};
|
|
75
|
+
listedPrice: {
|
|
76
|
+
canFilter: boolean;
|
|
77
|
+
canSort: boolean;
|
|
78
|
+
};
|
|
79
|
+
createdAt: {
|
|
80
|
+
canFilter: boolean;
|
|
81
|
+
canSort: boolean;
|
|
82
|
+
parseValue: typeof parseSieveDateValue;
|
|
83
|
+
};
|
|
84
|
+
expiresAt: {
|
|
85
|
+
canFilter: boolean;
|
|
86
|
+
canSort: boolean;
|
|
87
|
+
parseValue: typeof parseSieveDateValue;
|
|
88
|
+
};
|
|
89
|
+
checkoutDeadline: {
|
|
90
|
+
canFilter: boolean;
|
|
91
|
+
canSort: boolean;
|
|
92
|
+
parseValue: typeof parseSieveDateValue;
|
|
34
93
|
};
|
|
35
94
|
};
|
|
95
|
+
/**
|
|
96
|
+
* Paginated, Firestore-native offer list (admin use).
|
|
97
|
+
*
|
|
98
|
+
* Mirrors `BidRepository.list`. The seller- and buyer-scoped reads have their
|
|
99
|
+
* own dedicated methods (`findByStore` / `findByBuyer`) because they carry
|
|
100
|
+
* mandatory ownership filters; this one is deliberately unscoped and is only
|
|
101
|
+
* reachable behind `admin:offers:read`.
|
|
102
|
+
*/
|
|
103
|
+
list(model: SieveModel): Promise<FirebaseSieveResult<OfferDocument>>;
|
|
36
104
|
create(input: OfferCreateInput): Promise<OfferDocument>;
|
|
37
105
|
findByBuyer(buyerUid: string, model?: SieveModel): Promise<FirebaseSieveResult<OfferDocument>>;
|
|
38
106
|
findByStore(storeId: string, model?: SieveModel): Promise<FirebaseSieveResult<OfferDocument>>;
|
|
@@ -50,12 +118,32 @@ declare class OfferRepository extends BaseRepository<OfferDocument> {
|
|
|
50
118
|
* `product.updatedAt` advances past the previously-created offer dates.
|
|
51
119
|
*/
|
|
52
120
|
countByBuyerAndProduct(buyerUid: string, productId: string, since: Date): Promise<number>;
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
121
|
+
/**
|
|
122
|
+
* The single write choke point for offers, so `statusHistory` is appended
|
|
123
|
+
* in ONE place rather than at every call site — the same shape
|
|
124
|
+
* `OrderRepository.updateWithHistory` uses.
|
|
125
|
+
*
|
|
126
|
+
* `prior` is the already-fetched document when the caller has one. Every
|
|
127
|
+
* real caller does (`respondToOffer`, `acceptCounterOffer`, `withdrawOffer`,
|
|
128
|
+
* `counterOfferByBuyer`, `finalizeLockedLines`, the admin PATCH, both expiry
|
|
129
|
+
* sweeps), so threading it through means history costs **zero extra reads**.
|
|
130
|
+
*/
|
|
131
|
+
updateStatus(offerId: string, patch: OfferUpdateInput, ctx?: OfferWriteContext, prior?: OfferDocument | null): Promise<OfferDocument>;
|
|
132
|
+
accept(offerId: string, lockedPrice: number, sellerNote?: string, checkoutDeadline?: Date, ctx?: OfferWriteContext, prior?: OfferDocument | null): Promise<OfferDocument>;
|
|
133
|
+
decline(offerId: string, sellerNote?: string, ctx?: OfferWriteContext, prior?: OfferDocument | null): Promise<OfferDocument>;
|
|
134
|
+
counter(offerId: string, counterAmount: number, sellerNote?: string, ctx?: OfferWriteContext, prior?: OfferDocument | null): Promise<OfferDocument>;
|
|
135
|
+
acceptCounter(offerId: string, checkoutDeadline?: Date, ctx?: OfferWriteContext, prior?: OfferDocument | null): Promise<OfferDocument>;
|
|
136
|
+
/**
|
|
137
|
+
* Two distinct events share this method:
|
|
138
|
+
* - the buyer walks away → status alone
|
|
139
|
+
* - the buyer counters → status + `supersededByOfferId`
|
|
140
|
+
*
|
|
141
|
+
* The second is what makes the chain walkable forward, and it is ONE write
|
|
142
|
+
* with ONE history entry. A `withdrawn` document carrying
|
|
143
|
+
* `supersededByOfferId` renders as *Superseded* (neutral) rather than
|
|
144
|
+
* *Withdrawn* (negative) — the negotiation continued, it did not end.
|
|
145
|
+
*/
|
|
146
|
+
withdraw(offerId: string, supersededByOfferId?: string, ctx?: OfferWriteContext, prior?: OfferDocument | null): Promise<OfferDocument>;
|
|
59
147
|
/**
|
|
60
148
|
* Terminal transition, called once the buyer's order for this offer exists.
|
|
61
149
|
*
|
|
@@ -64,7 +152,21 @@ declare class OfferRepository extends BaseRepository<OfferDocument> {
|
|
|
64
152
|
* client-side patch in UserOffersPanel, which meant a reload showed the offer
|
|
65
153
|
* back at "accepted" and it could be added to the cart and ordered again.
|
|
66
154
|
*/
|
|
67
|
-
markPaid(offerId: string, orderId: string): Promise<OfferDocument>;
|
|
155
|
+
markPaid(offerId: string, orderId: string, prior?: OfferDocument | null): Promise<OfferDocument>;
|
|
156
|
+
/**
|
|
157
|
+
* Every round of one negotiation, oldest first.
|
|
158
|
+
*
|
|
159
|
+
* A single-field equality — served by Firestore's AUTOMATIC index, so this
|
|
160
|
+
* adds no composite index and none of the existing `offers` indexes is
|
|
161
|
+
* affected. The sort is done in memory because the result is at most 3 rows
|
|
162
|
+
* (the per-buyer offer limit), which is cheaper than the index a compound
|
|
163
|
+
* orderBy would demand.
|
|
164
|
+
*
|
|
165
|
+
* Offers written before `chainRootOfferId` existed return ZERO rows, not
|
|
166
|
+
* one — callers must fall back to `[offer]` and never read empty as
|
|
167
|
+
* "this offer is missing".
|
|
168
|
+
*/
|
|
169
|
+
findChain(chainRootOfferId: string): Promise<OfferDocument[]>;
|
|
68
170
|
/**
|
|
69
171
|
* Cloud Functions compatibility: pending/countered offers already expired.
|
|
70
172
|
*/
|
|
@@ -76,7 +178,23 @@ declare class OfferRepository extends BaseRepository<OfferDocument> {
|
|
|
76
178
|
* price remained claimable long after the seller expected it to lapse.
|
|
77
179
|
*/
|
|
78
180
|
findExpiredAccepted(now: Date): Promise<OfferDocument[]>;
|
|
79
|
-
|
|
181
|
+
/**
|
|
182
|
+
* Bulk-expire, taking the DOCUMENTS rather than their ids.
|
|
183
|
+
*
|
|
184
|
+
* The signature changed deliberately. `arrayUnion` would let a batch append
|
|
185
|
+
* without a read, but it **cannot enforce the FIFO cap** — so history would
|
|
186
|
+
* grow unbounded on exactly the path that touches the most documents at
|
|
187
|
+
* once. Folding entries in memory and writing whole arrays keeps the cap,
|
|
188
|
+
* and costs **zero extra reads**: `findExpiredActive`, `findExpiredAccepted`
|
|
189
|
+
* and the admin cancel route all already hold full `OfferDocument`s.
|
|
190
|
+
*
|
|
191
|
+
* Also carries the admin path: a reasoned cancel is an expiry with an actor
|
|
192
|
+
* and a reason, recorded in the offer's own history as well as in
|
|
193
|
+
* `adminAuditLog`.
|
|
194
|
+
*/
|
|
195
|
+
expireMany(offers: OfferDocument[], ctx?: OfferWriteContext & {
|
|
196
|
+
cancelledByAdminUid?: string;
|
|
197
|
+
}): Promise<void>;
|
|
80
198
|
}
|
|
81
199
|
export declare const offerRepository: OfferRepository;
|
|
82
200
|
export { OfferRepository };
|
|
@@ -4,9 +4,12 @@
|
|
|
4
4
|
* Data access layer for the `offers` Firestore collection.
|
|
5
5
|
* Handles buyer make-an-offer, seller counter/accept/decline lifecycle.
|
|
6
6
|
*/
|
|
7
|
-
import { BaseRepository, prepareForFirestore, } from "../../../providers/db-firebase";
|
|
8
|
-
import { OFFER_COLLECTION, OFFER_FIELDS, createOfferId } from "../schemas";
|
|
7
|
+
import { BaseRepository, prepareForFirestore, parseSieveDateValue, } from "../../../providers/db-firebase";
|
|
8
|
+
import { OFFER_COLLECTION, OFFER_FIELDS, OFFER_TRACKED_FIELDS, createOfferId } from "../schemas";
|
|
9
|
+
import { withHistory } from "../../../_internal/shared/history/index";
|
|
9
10
|
import { encryptPiiFields, decryptPiiFields, OFFER_PII_FIELDS, } from "../../../security";
|
|
11
|
+
/** Max rounds in one negotiation chain — matches the per-buyer offer limit. */
|
|
12
|
+
const OFFER_CHAIN_MAX = 3;
|
|
10
13
|
class OfferRepository extends BaseRepository {
|
|
11
14
|
constructor() {
|
|
12
15
|
super(OFFER_COLLECTION);
|
|
@@ -18,6 +21,20 @@ class OfferRepository extends BaseRepository {
|
|
|
18
21
|
...OFFER_PII_FIELDS,
|
|
19
22
|
]);
|
|
20
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Paginated, Firestore-native offer list (admin use).
|
|
26
|
+
*
|
|
27
|
+
* Mirrors `BidRepository.list`. The seller- and buyer-scoped reads have their
|
|
28
|
+
* own dedicated methods (`findByStore` / `findByBuyer`) because they carry
|
|
29
|
+
* mandatory ownership filters; this one is deliberately unscoped and is only
|
|
30
|
+
* reachable behind `admin:offers:read`.
|
|
31
|
+
*/
|
|
32
|
+
async list(model) {
|
|
33
|
+
return this.sieveQuery(model, OfferRepository.SIEVE_FIELDS, {
|
|
34
|
+
defaultPageSize: 50,
|
|
35
|
+
maxPageSize: 200,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
21
38
|
// --- Create --------------------------------------------------------------
|
|
22
39
|
async create(input) {
|
|
23
40
|
const now = new Date();
|
|
@@ -33,6 +50,13 @@ class OfferRepository extends BaseRepository {
|
|
|
33
50
|
expiresAt,
|
|
34
51
|
createdAt: now,
|
|
35
52
|
updatedAt: now,
|
|
53
|
+
// The root of a chain stores its OWN id. Without this, round 1 is the
|
|
54
|
+
// only document with no `chainRootOfferId`, so `findChain(root.id)`
|
|
55
|
+
// would return rounds 2..N and silently omit the round the buyer
|
|
56
|
+
// actually started with. Set here rather than at the call site because
|
|
57
|
+
// the id is generated in this method.
|
|
58
|
+
chainRootOfferId: input.chainRootOfferId ?? id,
|
|
59
|
+
counterRound: input.counterRound ?? 1,
|
|
36
60
|
};
|
|
37
61
|
// Encrypt PII fields before persisting
|
|
38
62
|
const encrypted = encryptPiiFields({ ...data }, [...OFFER_PII_FIELDS]);
|
|
@@ -128,13 +152,35 @@ class OfferRepository extends BaseRepository {
|
|
|
128
152
|
return snap.size;
|
|
129
153
|
}
|
|
130
154
|
// --- Mutations -----------------------------------------------------------
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
155
|
+
/**
|
|
156
|
+
* The single write choke point for offers, so `statusHistory` is appended
|
|
157
|
+
* in ONE place rather than at every call site — the same shape
|
|
158
|
+
* `OrderRepository.updateWithHistory` uses.
|
|
159
|
+
*
|
|
160
|
+
* `prior` is the already-fetched document when the caller has one. Every
|
|
161
|
+
* real caller does (`respondToOffer`, `acceptCounterOffer`, `withdrawOffer`,
|
|
162
|
+
* `counterOfferByBuyer`, `finalizeLockedLines`, the admin PATCH, both expiry
|
|
163
|
+
* sweeps), so threading it through means history costs **zero extra reads**.
|
|
164
|
+
*/
|
|
165
|
+
async updateStatus(offerId, patch, ctx, prior) {
|
|
166
|
+
const full = { ...patch, updatedAt: new Date() };
|
|
167
|
+
const current = prior !== undefined ? prior : await this.findById(offerId);
|
|
168
|
+
const withEntry = withHistory(current, full, {
|
|
169
|
+
tracked: OFFER_TRACKED_FIELDS,
|
|
170
|
+
actor: ctx?.actor ?? { role: "system" },
|
|
171
|
+
trigger: ctx?.trigger ?? "offerRepository.updateStatus",
|
|
172
|
+
reason: ctx?.reason,
|
|
173
|
+
note: ctx?.note,
|
|
174
|
+
// buyerName/buyerEmail live on this document. `encryptPiiFields` is a
|
|
175
|
+
// flat top-level loop and never descends into arrays, so without this
|
|
176
|
+
// they would persist in PLAINTEXT inside statusHistory.
|
|
177
|
+
piiFields: OFFER_PII_FIELDS,
|
|
178
|
+
historyField: OFFER_FIELDS.STATUS_HISTORY,
|
|
179
|
+
truncatedField: OFFER_FIELDS.STATUS_HISTORY_TRUNCATED,
|
|
135
180
|
});
|
|
181
|
+
return this.update(offerId, (withEntry ?? full));
|
|
136
182
|
}
|
|
137
|
-
async accept(offerId, lockedPrice, sellerNote, checkoutDeadline) {
|
|
183
|
+
async accept(offerId, lockedPrice, sellerNote, checkoutDeadline, ctx, prior) {
|
|
138
184
|
// One write, not two. `checkoutDeadline` used to be missing from
|
|
139
185
|
// OfferUpdateInput, which forced callers to follow every accept() with a
|
|
140
186
|
// separate generic update() — leaving a window where an offer was
|
|
@@ -146,25 +192,28 @@ class OfferRepository extends BaseRepository {
|
|
|
146
192
|
acceptedAt: new Date(),
|
|
147
193
|
respondedAt: new Date(),
|
|
148
194
|
...(checkoutDeadline ? { checkoutDeadline } : {}),
|
|
149
|
-
});
|
|
195
|
+
}, { actor: { role: "seller" }, trigger: "respondToOffer:accept", note: sellerNote, ...ctx }, prior);
|
|
150
196
|
}
|
|
151
|
-
async decline(offerId, sellerNote) {
|
|
197
|
+
async decline(offerId, sellerNote, ctx, prior) {
|
|
152
198
|
return this.updateStatus(offerId, {
|
|
153
199
|
status: "declined",
|
|
154
200
|
sellerNote,
|
|
155
201
|
respondedAt: new Date(),
|
|
156
|
-
});
|
|
202
|
+
}, { actor: { role: "seller" }, trigger: "respondToOffer:decline", note: sellerNote, ...ctx }, prior);
|
|
157
203
|
}
|
|
158
|
-
async counter(offerId, counterAmount, sellerNote) {
|
|
204
|
+
async counter(offerId, counterAmount, sellerNote, ctx, prior) {
|
|
159
205
|
return this.updateStatus(offerId, {
|
|
160
206
|
status: "countered",
|
|
161
207
|
counterAmount,
|
|
162
208
|
sellerNote,
|
|
163
209
|
respondedAt: new Date(),
|
|
164
|
-
});
|
|
210
|
+
}, { actor: { role: "seller" }, trigger: "respondToOffer:counter", note: sellerNote, ...ctx }, prior);
|
|
165
211
|
}
|
|
166
|
-
async acceptCounter(offerId, checkoutDeadline) {
|
|
167
|
-
|
|
212
|
+
async acceptCounter(offerId, checkoutDeadline, ctx, prior) {
|
|
213
|
+
// Reuse the caller's document when supplied — this method used to always
|
|
214
|
+
// re-read, so passing `prior` makes the feature land one read CHEAPER
|
|
215
|
+
// than before rather than one more expensive.
|
|
216
|
+
const offer = prior !== undefined && prior !== null ? prior : await this.findById(offerId);
|
|
168
217
|
if (!offer || !offer.counterAmount)
|
|
169
218
|
throw new Error("Offer or counter not found");
|
|
170
219
|
return this.updateStatus(offerId, {
|
|
@@ -173,13 +222,29 @@ class OfferRepository extends BaseRepository {
|
|
|
173
222
|
acceptedAt: new Date(),
|
|
174
223
|
respondedAt: new Date(),
|
|
175
224
|
...(checkoutDeadline ? { checkoutDeadline } : {}),
|
|
176
|
-
});
|
|
225
|
+
}, { actor: { role: "buyer" }, trigger: "acceptCounterOffer", ...ctx }, offer);
|
|
177
226
|
}
|
|
178
|
-
|
|
227
|
+
/**
|
|
228
|
+
* Two distinct events share this method:
|
|
229
|
+
* - the buyer walks away → status alone
|
|
230
|
+
* - the buyer counters → status + `supersededByOfferId`
|
|
231
|
+
*
|
|
232
|
+
* The second is what makes the chain walkable forward, and it is ONE write
|
|
233
|
+
* with ONE history entry. A `withdrawn` document carrying
|
|
234
|
+
* `supersededByOfferId` renders as *Superseded* (neutral) rather than
|
|
235
|
+
* *Withdrawn* (negative) — the negotiation continued, it did not end.
|
|
236
|
+
*/
|
|
237
|
+
async withdraw(offerId, supersededByOfferId, ctx, prior) {
|
|
179
238
|
return this.updateStatus(offerId, {
|
|
180
239
|
status: "withdrawn",
|
|
181
240
|
respondedAt: new Date(),
|
|
182
|
-
|
|
241
|
+
...(supersededByOfferId ? { supersededByOfferId } : {}),
|
|
242
|
+
}, {
|
|
243
|
+
actor: { role: "buyer" },
|
|
244
|
+
trigger: supersededByOfferId ? "counterOfferByBuyer:supersede" : "withdrawOffer",
|
|
245
|
+
...(supersededByOfferId ? { reason: "Superseded by the buyer's counter" } : {}),
|
|
246
|
+
...ctx,
|
|
247
|
+
}, prior);
|
|
183
248
|
}
|
|
184
249
|
/**
|
|
185
250
|
* Terminal transition, called once the buyer's order for this offer exists.
|
|
@@ -189,12 +254,35 @@ class OfferRepository extends BaseRepository {
|
|
|
189
254
|
* client-side patch in UserOffersPanel, which meant a reload showed the offer
|
|
190
255
|
* back at "accepted" and it could be added to the cart and ordered again.
|
|
191
256
|
*/
|
|
192
|
-
async markPaid(offerId, orderId) {
|
|
257
|
+
async markPaid(offerId, orderId, prior) {
|
|
193
258
|
return this.updateStatus(offerId, {
|
|
194
259
|
status: "paid",
|
|
195
260
|
paidOrderId: orderId,
|
|
196
261
|
paidAt: new Date(),
|
|
197
|
-
});
|
|
262
|
+
}, { actor: { role: "system" }, trigger: "finalizeLockedLines" }, prior);
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Every round of one negotiation, oldest first.
|
|
266
|
+
*
|
|
267
|
+
* A single-field equality — served by Firestore's AUTOMATIC index, so this
|
|
268
|
+
* adds no composite index and none of the existing `offers` indexes is
|
|
269
|
+
* affected. The sort is done in memory because the result is at most 3 rows
|
|
270
|
+
* (the per-buyer offer limit), which is cheaper than the index a compound
|
|
271
|
+
* orderBy would demand.
|
|
272
|
+
*
|
|
273
|
+
* Offers written before `chainRootOfferId` existed return ZERO rows, not
|
|
274
|
+
* one — callers must fall back to `[offer]` and never read empty as
|
|
275
|
+
* "this offer is missing".
|
|
276
|
+
*/
|
|
277
|
+
async findChain(chainRootOfferId) {
|
|
278
|
+
const snapshot = await this.db
|
|
279
|
+
.collection(this.collection)
|
|
280
|
+
.where(OFFER_FIELDS.CHAIN_ROOT_OFFER_ID, "==", chainRootOfferId)
|
|
281
|
+
.limit(OFFER_CHAIN_MAX)
|
|
282
|
+
.get();
|
|
283
|
+
return snapshot.docs
|
|
284
|
+
.map((doc) => this.mapDoc(doc))
|
|
285
|
+
.sort((a, b) => (a.counterRound ?? 1) - (b.counterRound ?? 1));
|
|
198
286
|
}
|
|
199
287
|
/**
|
|
200
288
|
* Cloud Functions compatibility: pending/countered offers already expired.
|
|
@@ -223,24 +311,75 @@ class OfferRepository extends BaseRepository {
|
|
|
223
311
|
.get();
|
|
224
312
|
return snapshot.docs.map((doc) => this.mapDoc(doc));
|
|
225
313
|
}
|
|
226
|
-
|
|
314
|
+
/**
|
|
315
|
+
* Bulk-expire, taking the DOCUMENTS rather than their ids.
|
|
316
|
+
*
|
|
317
|
+
* The signature changed deliberately. `arrayUnion` would let a batch append
|
|
318
|
+
* without a read, but it **cannot enforce the FIFO cap** — so history would
|
|
319
|
+
* grow unbounded on exactly the path that touches the most documents at
|
|
320
|
+
* once. Folding entries in memory and writing whole arrays keeps the cap,
|
|
321
|
+
* and costs **zero extra reads**: `findExpiredActive`, `findExpiredAccepted`
|
|
322
|
+
* and the admin cancel route all already hold full `OfferDocument`s.
|
|
323
|
+
*
|
|
324
|
+
* Also carries the admin path: a reasoned cancel is an expiry with an actor
|
|
325
|
+
* and a reason, recorded in the offer's own history as well as in
|
|
326
|
+
* `adminAuditLog`.
|
|
327
|
+
*/
|
|
328
|
+
async expireMany(offers, ctx) {
|
|
329
|
+
if (offers.length === 0)
|
|
330
|
+
return;
|
|
227
331
|
const batch = this.db.batch();
|
|
228
332
|
const now = new Date();
|
|
229
|
-
for (const
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
333
|
+
for (const offer of offers) {
|
|
334
|
+
const patch = {
|
|
335
|
+
status: "expired",
|
|
336
|
+
updatedAt: now,
|
|
337
|
+
...(ctx?.cancelledByAdminUid
|
|
338
|
+
? { cancelledByAdminUid: ctx.cancelledByAdminUid, cancelReason: ctx.reason }
|
|
339
|
+
: {}),
|
|
340
|
+
};
|
|
341
|
+
const withEntry = withHistory(offer, patch, {
|
|
342
|
+
tracked: OFFER_TRACKED_FIELDS,
|
|
343
|
+
actor: ctx?.actor ?? { role: "system" },
|
|
344
|
+
trigger: ctx?.trigger ?? "runOfferExpiry",
|
|
345
|
+
reason: ctx?.reason,
|
|
346
|
+
piiFields: OFFER_PII_FIELDS,
|
|
347
|
+
historyField: OFFER_FIELDS.STATUS_HISTORY,
|
|
348
|
+
truncatedField: OFFER_FIELDS.STATUS_HISTORY_TRUNCATED,
|
|
349
|
+
now,
|
|
233
350
|
});
|
|
351
|
+
batch.update(this.db.collection(this.collection).doc(offer.id), (withEntry ?? patch));
|
|
234
352
|
}
|
|
235
353
|
await batch.commit();
|
|
236
354
|
}
|
|
237
355
|
}
|
|
356
|
+
/**
|
|
357
|
+
* Every Timestamp field here carries `parseValue: parseSieveDateValue`.
|
|
358
|
+
*
|
|
359
|
+
* Without it a GTE/LTE on a Timestamp field silently matches ZERO documents:
|
|
360
|
+
* sievejs's default coercion leaves an ISO string as a string, and Firestore
|
|
361
|
+
* requires an inequality's value to match the stored type. That is Root Cause
|
|
362
|
+
* #47, and `createdAt` flipping from `canFilter: false` to `true` here is
|
|
363
|
+
* exactly the transition that introduces it.
|
|
364
|
+
*
|
|
365
|
+
* `checkoutDeadline` is added by hand: `audit-sieve-date-fields` recognises
|
|
366
|
+
* Timestamp fields by an `...At` / `...Date` / `...Time` suffix, which this
|
|
367
|
+
* name does not have, so the audit cannot flag it for you.
|
|
368
|
+
*/
|
|
238
369
|
OfferRepository.SIEVE_FIELDS = {
|
|
239
|
-
|
|
370
|
+
id: { canFilter: true, canSort: false },
|
|
371
|
+
status: { canFilter: true, canSort: true },
|
|
240
372
|
productId: { canFilter: true, canSort: false },
|
|
373
|
+
productTitle: { canFilter: true, canSort: true },
|
|
241
374
|
buyerUid: { canFilter: true, canSort: false },
|
|
375
|
+
buyerName: { canFilter: true, canSort: true },
|
|
242
376
|
storeId: { canFilter: true, canSort: false },
|
|
243
|
-
|
|
377
|
+
storeName: { canFilter: true, canSort: true },
|
|
378
|
+
offerAmount: { canFilter: true, canSort: true },
|
|
379
|
+
listedPrice: { canFilter: true, canSort: true },
|
|
380
|
+
createdAt: { canFilter: true, canSort: true, parseValue: parseSieveDateValue },
|
|
381
|
+
expiresAt: { canFilter: true, canSort: true, parseValue: parseSieveDateValue },
|
|
382
|
+
checkoutDeadline: { canFilter: true, canSort: true, parseValue: parseSieveDateValue },
|
|
244
383
|
};
|
|
245
384
|
export const offerRepository = new OfferRepository();
|
|
246
385
|
export { OfferRepository };
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* 🛑 `annotate()` must be the OUTERMOST call on each field — it keys a WeakMap
|
|
4
|
+
* by schema instance and every zod wrapper returns a new one.
|
|
5
|
+
*/
|
|
6
|
+
export declare const sellerCouponFormSchema: z.ZodEffects<z.ZodObject<{
|
|
7
|
+
code: z.ZodString;
|
|
8
|
+
type: z.ZodEnum<["percentage", "fixed", "free_shipping"]>;
|
|
9
|
+
value: z.ZodEffects<z.ZodString, string, string>;
|
|
10
|
+
minPurchase: z.ZodEffects<z.ZodString, string, string>;
|
|
11
|
+
maxDiscount: z.ZodEffects<z.ZodString, string, string>;
|
|
12
|
+
totalLimit: z.ZodEffects<z.ZodString, string, string>;
|
|
13
|
+
perUserLimit: z.ZodEffects<z.ZodString, string, string>;
|
|
14
|
+
startDate: z.ZodString;
|
|
15
|
+
endDate: z.ZodString;
|
|
16
|
+
isActive: z.ZodBoolean;
|
|
17
|
+
applicableProducts: z.ZodArray<z.ZodString, "many">;
|
|
18
|
+
applicableCategories: z.ZodArray<z.ZodString, "many">;
|
|
19
|
+
}, "strip", z.ZodTypeAny, {
|
|
20
|
+
value: string;
|
|
21
|
+
code: string;
|
|
22
|
+
type: "fixed" | "percentage" | "free_shipping";
|
|
23
|
+
isActive: boolean;
|
|
24
|
+
endDate: string;
|
|
25
|
+
maxDiscount: string;
|
|
26
|
+
minPurchase: string;
|
|
27
|
+
applicableProducts: string[];
|
|
28
|
+
applicableCategories: string[];
|
|
29
|
+
totalLimit: string;
|
|
30
|
+
perUserLimit: string;
|
|
31
|
+
startDate: string;
|
|
32
|
+
}, {
|
|
33
|
+
value: string;
|
|
34
|
+
code: string;
|
|
35
|
+
type: "fixed" | "percentage" | "free_shipping";
|
|
36
|
+
isActive: boolean;
|
|
37
|
+
endDate: string;
|
|
38
|
+
maxDiscount: string;
|
|
39
|
+
minPurchase: string;
|
|
40
|
+
applicableProducts: string[];
|
|
41
|
+
applicableCategories: string[];
|
|
42
|
+
totalLimit: string;
|
|
43
|
+
perUserLimit: string;
|
|
44
|
+
startDate: string;
|
|
45
|
+
}>, {
|
|
46
|
+
value: string;
|
|
47
|
+
code: string;
|
|
48
|
+
type: "fixed" | "percentage" | "free_shipping";
|
|
49
|
+
isActive: boolean;
|
|
50
|
+
endDate: string;
|
|
51
|
+
maxDiscount: string;
|
|
52
|
+
minPurchase: string;
|
|
53
|
+
applicableProducts: string[];
|
|
54
|
+
applicableCategories: string[];
|
|
55
|
+
totalLimit: string;
|
|
56
|
+
perUserLimit: string;
|
|
57
|
+
startDate: string;
|
|
58
|
+
}, {
|
|
59
|
+
value: string;
|
|
60
|
+
code: string;
|
|
61
|
+
type: "fixed" | "percentage" | "free_shipping";
|
|
62
|
+
isActive: boolean;
|
|
63
|
+
endDate: string;
|
|
64
|
+
maxDiscount: string;
|
|
65
|
+
minPurchase: string;
|
|
66
|
+
applicableProducts: string[];
|
|
67
|
+
applicableCategories: string[];
|
|
68
|
+
totalLimit: string;
|
|
69
|
+
perUserLimit: string;
|
|
70
|
+
startDate: string;
|
|
71
|
+
}>;
|
|
72
|
+
export type SellerCouponFormValues = z.infer<typeof sellerCouponFormSchema>;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* WHY: `SellerCouponEditorView` rendered `<Form onSubmit noValidate>` with no
|
|
3
|
+
* schema at all, while `AdminCouponEditorView` — the same entity, one
|
|
4
|
+
* portal over — has a Zod schema wired through `<Form schema>`. The
|
|
5
|
+
* seller side relied on four hand-rolled `if` checks that covered
|
|
6
|
+
* presence and date order and nothing else, so a coupon could be saved
|
|
7
|
+
* with a **500% discount**, a non-numeric value, a negative minimum spend
|
|
8
|
+
* or a per-user limit above the total limit. Every failure surfaced in a
|
|
9
|
+
* single banner rather than on the field that caused it.
|
|
10
|
+
* WHAT: The seller coupon form's schema.
|
|
11
|
+
*
|
|
12
|
+
* ## Everything is a string, on purpose
|
|
13
|
+
*
|
|
14
|
+
* `CouponEditorDraft` holds every numeric field as a string because they come
|
|
15
|
+
* from `<input>`s. Coercing inside the schema keeps that boundary in one place
|
|
16
|
+
* — the alternative is a `Number()` at each call site, which is how a `NaN`
|
|
17
|
+
* reaches Firestore the first time one is forgotten.
|
|
18
|
+
*
|
|
19
|
+
* ## Why the rules are conditional
|
|
20
|
+
*
|
|
21
|
+
* `free_shipping` has no discount value, `percentage` is bounded 1–100, and
|
|
22
|
+
* `fixed` is any positive amount. A flat per-field rule cannot say that, so
|
|
23
|
+
* the cross-field rules live in one `superRefine` keyed by type.
|
|
24
|
+
*
|
|
25
|
+
* EXPORTS:
|
|
26
|
+
* sellerCouponFormSchema, type SellerCouponFormValues
|
|
27
|
+
*
|
|
28
|
+
* @tag domain:seller,promotions
|
|
29
|
+
* @tag layer:schema
|
|
30
|
+
* @tag pattern:none
|
|
31
|
+
* @tag access:isomorphic
|
|
32
|
+
* @tag consumers:SellerCouponEditorView
|
|
33
|
+
* @tag sideEffects:none
|
|
34
|
+
*/
|
|
35
|
+
import { z } from "zod";
|
|
36
|
+
import { annotate } from "../../shell/field-ui-meta";
|
|
37
|
+
/** A blank optional numeric string — the form's "not set". */
|
|
38
|
+
const numericString = (label) => z
|
|
39
|
+
.string()
|
|
40
|
+
.refine((v) => v.trim() === "" || (!Number.isNaN(Number(v)) && Number(v) >= 0), {
|
|
41
|
+
message: `${label} must be a number of zero or more.`,
|
|
42
|
+
});
|
|
43
|
+
/**
|
|
44
|
+
* 🛑 `annotate()` must be the OUTERMOST call on each field — it keys a WeakMap
|
|
45
|
+
* by schema instance and every zod wrapper returns a new one.
|
|
46
|
+
*/
|
|
47
|
+
export const sellerCouponFormSchema = z
|
|
48
|
+
.object({
|
|
49
|
+
code: annotate(z
|
|
50
|
+
.string()
|
|
51
|
+
.min(2, "A coupon code needs at least 2 characters.")
|
|
52
|
+
.max(40, "Keep the code under 40 characters.")
|
|
53
|
+
.regex(/^[A-Z0-9_-]+$/i, "Use letters, numbers, hyphens and underscores only."), {
|
|
54
|
+
section: "basics", sectionLabel: "Coupon", sectionRequired: true,
|
|
55
|
+
quick: true, order: 1, row: "pair",
|
|
56
|
+
}),
|
|
57
|
+
type: annotate(z.enum(["percentage", "fixed", "free_shipping"]), {
|
|
58
|
+
section: "basics", quick: true, order: 2, row: "pair",
|
|
59
|
+
}),
|
|
60
|
+
value: annotate(numericString("The discount value"), {
|
|
61
|
+
section: "basics", quick: true, order: 3, row: "pair",
|
|
62
|
+
}),
|
|
63
|
+
minPurchase: annotate(numericString("Minimum purchase"), {
|
|
64
|
+
section: "limits", sectionLabel: "Limits", order: 1, row: "pair",
|
|
65
|
+
}),
|
|
66
|
+
maxDiscount: annotate(numericString("Maximum discount"), {
|
|
67
|
+
section: "limits", order: 2, row: "pair",
|
|
68
|
+
}),
|
|
69
|
+
totalLimit: annotate(numericString("Total uses"), {
|
|
70
|
+
section: "limits", order: 3, row: "pair",
|
|
71
|
+
}),
|
|
72
|
+
perUserLimit: annotate(numericString("Uses per customer"), {
|
|
73
|
+
section: "limits", order: 4, row: "pair",
|
|
74
|
+
}),
|
|
75
|
+
startDate: annotate(z.string().min(1, "A start date is required."), {
|
|
76
|
+
section: "validity", sectionLabel: "Validity", quick: true, order: 1, row: "pair", kind: "date",
|
|
77
|
+
}),
|
|
78
|
+
endDate: annotate(z.string().min(1, "An end date is required."), {
|
|
79
|
+
section: "validity", quick: true, order: 2, row: "pair", kind: "date",
|
|
80
|
+
}),
|
|
81
|
+
isActive: annotate(z.boolean(), {
|
|
82
|
+
section: "validity", order: 3, row: "quarter",
|
|
83
|
+
}),
|
|
84
|
+
applicableProducts: annotate(z.array(z.string()), {
|
|
85
|
+
section: "scope", sectionLabel: "Applies to", order: 1, row: "full", kind: "list",
|
|
86
|
+
}),
|
|
87
|
+
applicableCategories: annotate(z.array(z.string()), {
|
|
88
|
+
section: "scope", order: 2, row: "full", kind: "list",
|
|
89
|
+
}),
|
|
90
|
+
})
|
|
91
|
+
.superRefine((v, ctx) => {
|
|
92
|
+
const issue = (path, message) => ctx.addIssue({ code: z.ZodIssueCode.custom, path: [path], message });
|
|
93
|
+
// A discount value is required for everything except free shipping, and
|
|
94
|
+
// a percentage above 100 is not a discount — it is the store paying the
|
|
95
|
+
// customer. Nothing bounded this before.
|
|
96
|
+
if (v.type !== "free_shipping") {
|
|
97
|
+
const amount = Number(v.value);
|
|
98
|
+
if (v.value.trim() === "")
|
|
99
|
+
issue("value", "A discount value is required.");
|
|
100
|
+
else if (amount <= 0)
|
|
101
|
+
issue("value", "The discount must be greater than zero.");
|
|
102
|
+
else if (v.type === "percentage" && amount > 100) {
|
|
103
|
+
issue("value", "A percentage discount cannot exceed 100%.");
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (v.startDate && v.endDate && v.startDate > v.endDate) {
|
|
107
|
+
issue("endDate", "The end date must be after the start date.");
|
|
108
|
+
}
|
|
109
|
+
// A per-customer limit above the total makes the total unreachable — the
|
|
110
|
+
// coupon reads as more generous than it can ever be.
|
|
111
|
+
const total = Number(v.totalLimit);
|
|
112
|
+
const perUser = Number(v.perUserLimit);
|
|
113
|
+
if (v.totalLimit.trim() && v.perUserLimit.trim() && perUser > total) {
|
|
114
|
+
issue("perUserLimit", "Uses per customer cannot exceed the total uses.");
|
|
115
|
+
}
|
|
116
|
+
});
|