@mohasinac/appkit 4.13.1 → 4.14.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 +6 -0
- 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/checkout/actions.js +6 -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/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/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 +45 -9
- 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 +15 -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/blog/schema.d.ts +2 -2
- package/dist/_internal/shared/features/cart/schema.d.ts +12 -12
- 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 +32 -32
- 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 +66 -0
- package/dist/_internal/shared/history/history.js +158 -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 +7 -5
- package/dist/client.js +2 -2
- package/dist/constants/api-endpoints.d.ts +6 -0
- package/dist/constants/api-endpoints.js +2 -0
- package/dist/constants/field-names.d.ts +49 -2
- package/dist/constants/field-names.js +53 -2
- 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/UserOrdersView.js +7 -0
- 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/AdminClassifiedView.js +8 -3
- package/dist/features/admin/components/AdminDigitalCodesView.js +8 -3
- package/dist/features/admin/components/AdminFeatureFlagsView.js +1 -0
- package/dist/features/admin/components/AdminLiveView.js +8 -3
- package/dist/features/admin/components/AdminOffersView.d.ts +4 -0
- package/dist/features/admin/components/AdminOffersView.js +186 -0
- package/dist/features/admin/components/AdminOrdersView.js +10 -1
- package/dist/features/admin/components/AdminPrizeDrawsView.js +5 -0
- package/dist/features/admin/components/AdminProductsView.js +16 -29
- 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/index.d.ts +2 -0
- package/dist/features/admin/components/index.js +1 -0
- package/dist/features/admin/constants/filter-tabs.d.ts +86 -0
- package/dist/features/admin/constants/filter-tabs.js +55 -0
- package/dist/features/admin/schemas/firestore.d.ts +122 -3
- package/dist/features/admin/schemas/firestore.js +1 -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/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/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/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/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/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/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/index.d.ts +4 -4
- 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 +2 -2
- 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/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 +116 -22
- package/dist/features/orders/schemas/firestore.d.ts +117 -0
- package/dist/features/orders/schemas/firestore.js +30 -0
- package/dist/features/orders/schemas/index.d.ts +18 -18
- 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/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 +66 -66
- package/dist/features/products/schemas/product-features.validators.d.ts +6 -6
- 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/search/schemas/index.d.ts +4 -4
- package/dist/features/seller/actions/offer-actions.js +25 -2
- package/dist/features/seller/components/SellerArtView.js +5 -0
- package/dist/features/seller/components/SellerAuctionsView.js +13 -15
- package/dist/features/seller/components/SellerClassifiedView.js +5 -0
- package/dist/features/seller/components/SellerDigitalCodesView.js +5 -0
- package/dist/features/seller/components/SellerLiveView.js +5 -0
- package/dist/features/seller/components/SellerOrdersView.js +31 -9
- package/dist/features/seller/components/SellerPayoutSettingsView.js +19 -18
- 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 +73 -16
- package/dist/features/seller/repository/offer.repository.js +38 -3
- package/dist/features/seller/schemas/index.d.ts +2 -2
- package/dist/features/seller/schemas/offer-forms.d.ts +25 -0
- package/dist/features/seller/schemas/offer-forms.js +21 -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/entity-form.d.ts +109 -0
- package/dist/features/shell/entity-form.js +96 -0
- package/dist/features/shell/index.d.ts +4 -0
- package/dist/features/shell/index.js +2 -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 +105 -1
- package/dist/features/wishlist/hooks/useWishlistWithGuest.d.ts +1 -1
- package/dist/index.d.ts +13 -10
- package/dist/index.js +14 -9
- package/dist/next/routing/route-map.d.ts +7 -1
- package/dist/next/routing/route-map.js +5 -0
- 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/registry.d.ts +68 -68
- 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/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 +32 -27
- package/dist/seed/products-art-seed-data.js +50 -12
- package/dist/seed/products-auctions-seed-data.js +97 -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 +4 -3
- package/dist/server.js +8 -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/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 +1 -1
|
@@ -4,35 +4,92 @@
|
|
|
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
10
|
declare class OfferRepository extends BaseRepository<OfferDocument> {
|
|
11
11
|
constructor();
|
|
12
12
|
/** Override mapDoc to auto-decrypt PII on every offer read */
|
|
13
13
|
protected mapDoc<D = OfferDocument>(snap: import("../../../providers/db-firebase").DocumentSnapshot): D;
|
|
14
|
+
/**
|
|
15
|
+
* Every Timestamp field here carries `parseValue: parseSieveDateValue`.
|
|
16
|
+
*
|
|
17
|
+
* Without it a GTE/LTE on a Timestamp field silently matches ZERO documents:
|
|
18
|
+
* sievejs's default coercion leaves an ISO string as a string, and Firestore
|
|
19
|
+
* requires an inequality's value to match the stored type. That is Root Cause
|
|
20
|
+
* #47, and `createdAt` flipping from `canFilter: false` to `true` here is
|
|
21
|
+
* exactly the transition that introduces it.
|
|
22
|
+
*
|
|
23
|
+
* `checkoutDeadline` is added by hand: `audit-sieve-date-fields` recognises
|
|
24
|
+
* Timestamp fields by an `...At` / `...Date` / `...Time` suffix, which this
|
|
25
|
+
* name does not have, so the audit cannot flag it for you.
|
|
26
|
+
*/
|
|
14
27
|
static readonly SIEVE_FIELDS: {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
28
|
+
id: {
|
|
29
|
+
canFilter: boolean;
|
|
30
|
+
canSort: boolean;
|
|
31
|
+
};
|
|
32
|
+
status: {
|
|
33
|
+
canFilter: boolean;
|
|
34
|
+
canSort: boolean;
|
|
35
|
+
};
|
|
36
|
+
productId: {
|
|
37
|
+
canFilter: boolean;
|
|
38
|
+
canSort: boolean;
|
|
39
|
+
};
|
|
40
|
+
productTitle: {
|
|
41
|
+
canFilter: boolean;
|
|
42
|
+
canSort: boolean;
|
|
43
|
+
};
|
|
44
|
+
buyerUid: {
|
|
45
|
+
canFilter: boolean;
|
|
46
|
+
canSort: boolean;
|
|
18
47
|
};
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
48
|
+
buyerName: {
|
|
49
|
+
canFilter: boolean;
|
|
50
|
+
canSort: boolean;
|
|
22
51
|
};
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
52
|
+
storeId: {
|
|
53
|
+
canFilter: boolean;
|
|
54
|
+
canSort: boolean;
|
|
26
55
|
};
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
56
|
+
storeName: {
|
|
57
|
+
canFilter: boolean;
|
|
58
|
+
canSort: boolean;
|
|
30
59
|
};
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
60
|
+
offerAmount: {
|
|
61
|
+
canFilter: boolean;
|
|
62
|
+
canSort: boolean;
|
|
63
|
+
};
|
|
64
|
+
listedPrice: {
|
|
65
|
+
canFilter: boolean;
|
|
66
|
+
canSort: boolean;
|
|
67
|
+
};
|
|
68
|
+
createdAt: {
|
|
69
|
+
canFilter: boolean;
|
|
70
|
+
canSort: boolean;
|
|
71
|
+
parseValue: typeof parseSieveDateValue;
|
|
72
|
+
};
|
|
73
|
+
expiresAt: {
|
|
74
|
+
canFilter: boolean;
|
|
75
|
+
canSort: boolean;
|
|
76
|
+
parseValue: typeof parseSieveDateValue;
|
|
77
|
+
};
|
|
78
|
+
checkoutDeadline: {
|
|
79
|
+
canFilter: boolean;
|
|
80
|
+
canSort: boolean;
|
|
81
|
+
parseValue: typeof parseSieveDateValue;
|
|
34
82
|
};
|
|
35
83
|
};
|
|
84
|
+
/**
|
|
85
|
+
* Paginated, Firestore-native offer list (admin use).
|
|
86
|
+
*
|
|
87
|
+
* Mirrors `BidRepository.list`. The seller- and buyer-scoped reads have their
|
|
88
|
+
* own dedicated methods (`findByStore` / `findByBuyer`) because they carry
|
|
89
|
+
* mandatory ownership filters; this one is deliberately unscoped and is only
|
|
90
|
+
* reachable behind `admin:offers:read`.
|
|
91
|
+
*/
|
|
92
|
+
list(model: SieveModel): Promise<FirebaseSieveResult<OfferDocument>>;
|
|
36
93
|
create(input: OfferCreateInput): Promise<OfferDocument>;
|
|
37
94
|
findByBuyer(buyerUid: string, model?: SieveModel): Promise<FirebaseSieveResult<OfferDocument>>;
|
|
38
95
|
findByStore(storeId: string, model?: SieveModel): Promise<FirebaseSieveResult<OfferDocument>>;
|
|
@@ -4,7 +4,7 @@
|
|
|
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";
|
|
7
|
+
import { BaseRepository, prepareForFirestore, parseSieveDateValue, } from "../../../providers/db-firebase";
|
|
8
8
|
import { OFFER_COLLECTION, OFFER_FIELDS, createOfferId } from "../schemas";
|
|
9
9
|
import { encryptPiiFields, decryptPiiFields, OFFER_PII_FIELDS, } from "../../../security";
|
|
10
10
|
class OfferRepository extends BaseRepository {
|
|
@@ -18,6 +18,20 @@ class OfferRepository extends BaseRepository {
|
|
|
18
18
|
...OFFER_PII_FIELDS,
|
|
19
19
|
]);
|
|
20
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Paginated, Firestore-native offer list (admin use).
|
|
23
|
+
*
|
|
24
|
+
* Mirrors `BidRepository.list`. The seller- and buyer-scoped reads have their
|
|
25
|
+
* own dedicated methods (`findByStore` / `findByBuyer`) because they carry
|
|
26
|
+
* mandatory ownership filters; this one is deliberately unscoped and is only
|
|
27
|
+
* reachable behind `admin:offers:read`.
|
|
28
|
+
*/
|
|
29
|
+
async list(model) {
|
|
30
|
+
return this.sieveQuery(model, OfferRepository.SIEVE_FIELDS, {
|
|
31
|
+
defaultPageSize: 50,
|
|
32
|
+
maxPageSize: 200,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
21
35
|
// --- Create --------------------------------------------------------------
|
|
22
36
|
async create(input) {
|
|
23
37
|
const now = new Date();
|
|
@@ -235,12 +249,33 @@ class OfferRepository extends BaseRepository {
|
|
|
235
249
|
await batch.commit();
|
|
236
250
|
}
|
|
237
251
|
}
|
|
252
|
+
/**
|
|
253
|
+
* Every Timestamp field here carries `parseValue: parseSieveDateValue`.
|
|
254
|
+
*
|
|
255
|
+
* Without it a GTE/LTE on a Timestamp field silently matches ZERO documents:
|
|
256
|
+
* sievejs's default coercion leaves an ISO string as a string, and Firestore
|
|
257
|
+
* requires an inequality's value to match the stored type. That is Root Cause
|
|
258
|
+
* #47, and `createdAt` flipping from `canFilter: false` to `true` here is
|
|
259
|
+
* exactly the transition that introduces it.
|
|
260
|
+
*
|
|
261
|
+
* `checkoutDeadline` is added by hand: `audit-sieve-date-fields` recognises
|
|
262
|
+
* Timestamp fields by an `...At` / `...Date` / `...Time` suffix, which this
|
|
263
|
+
* name does not have, so the audit cannot flag it for you.
|
|
264
|
+
*/
|
|
238
265
|
OfferRepository.SIEVE_FIELDS = {
|
|
239
|
-
|
|
266
|
+
id: { canFilter: true, canSort: false },
|
|
267
|
+
status: { canFilter: true, canSort: true },
|
|
240
268
|
productId: { canFilter: true, canSort: false },
|
|
269
|
+
productTitle: { canFilter: true, canSort: true },
|
|
241
270
|
buyerUid: { canFilter: true, canSort: false },
|
|
271
|
+
buyerName: { canFilter: true, canSort: true },
|
|
242
272
|
storeId: { canFilter: true, canSort: false },
|
|
243
|
-
|
|
273
|
+
storeName: { canFilter: true, canSort: true },
|
|
274
|
+
offerAmount: { canFilter: true, canSort: true },
|
|
275
|
+
listedPrice: { canFilter: true, canSort: true },
|
|
276
|
+
createdAt: { canFilter: true, canSort: true, parseValue: parseSieveDateValue },
|
|
277
|
+
expiresAt: { canFilter: true, canSort: true, parseValue: parseSieveDateValue },
|
|
278
|
+
checkoutDeadline: { canFilter: true, canSort: true, parseValue: parseSieveDateValue },
|
|
244
279
|
};
|
|
245
280
|
export const offerRepository = new OfferRepository();
|
|
246
281
|
export { OfferRepository };
|
|
@@ -89,8 +89,8 @@ export declare const sellerStoreSchema: z.ZodObject<{
|
|
|
89
89
|
id: string;
|
|
90
90
|
createdAt: string;
|
|
91
91
|
updatedAt: string;
|
|
92
|
-
storeName: string;
|
|
93
92
|
ownerId: string;
|
|
93
|
+
storeName: string;
|
|
94
94
|
storeSlug: string;
|
|
95
95
|
isPublic: boolean;
|
|
96
96
|
location?: string | undefined;
|
|
@@ -121,8 +121,8 @@ export declare const sellerStoreSchema: z.ZodObject<{
|
|
|
121
121
|
id: string;
|
|
122
122
|
createdAt: string;
|
|
123
123
|
updatedAt: string;
|
|
124
|
-
storeName: string;
|
|
125
124
|
ownerId: string;
|
|
125
|
+
storeName: string;
|
|
126
126
|
storeSlug: string;
|
|
127
127
|
isPublic: boolean;
|
|
128
128
|
location?: string | undefined;
|
|
@@ -10,3 +10,28 @@ export declare const counterOfferFormSchema: z.ZodObject<{
|
|
|
10
10
|
sellerNote?: string | undefined;
|
|
11
11
|
}>;
|
|
12
12
|
export type CounterOfferFormValues = z.infer<typeof counterOfferFormSchema>;
|
|
13
|
+
/**
|
|
14
|
+
* Buyer-side Make-an-Offer form.
|
|
15
|
+
*
|
|
16
|
+
* A factory rather than a constant because both bounds depend on the listing:
|
|
17
|
+
* the floor is `minOfferAmount(listedPrice, minOfferPercent)` and the ceiling is
|
|
18
|
+
* the listed price itself. Both are re-checked server-side in `makeOffer` — this
|
|
19
|
+
* exists so the buyer is told before submitting, using the SAME
|
|
20
|
+
* `minOfferAmount()` the server uses, rather than a second rounding rule that
|
|
21
|
+
* could disagree by a rupee.
|
|
22
|
+
*/
|
|
23
|
+
export declare function makeOfferFormSchema(opts: {
|
|
24
|
+
listedPrice: number;
|
|
25
|
+
minOffer: number;
|
|
26
|
+
formatAmount: (n: number) => string;
|
|
27
|
+
}): z.ZodObject<{
|
|
28
|
+
offerAmount: z.ZodNumber;
|
|
29
|
+
buyerNote: z.ZodOptional<z.ZodString>;
|
|
30
|
+
}, "strip", z.ZodTypeAny, {
|
|
31
|
+
offerAmount: number;
|
|
32
|
+
buyerNote?: string | undefined;
|
|
33
|
+
}, {
|
|
34
|
+
offerAmount: number;
|
|
35
|
+
buyerNote?: string | undefined;
|
|
36
|
+
}>;
|
|
37
|
+
export type MakeOfferFormValues = z.infer<ReturnType<typeof makeOfferFormSchema>>;
|
|
@@ -26,3 +26,24 @@ export const counterOfferFormSchema = z.object({
|
|
|
26
26
|
.positive("Counter amount must be greater than zero."),
|
|
27
27
|
sellerNote: z.string().max(500, "Keep your note under 500 characters.").optional(),
|
|
28
28
|
});
|
|
29
|
+
/**
|
|
30
|
+
* Buyer-side Make-an-Offer form.
|
|
31
|
+
*
|
|
32
|
+
* A factory rather than a constant because both bounds depend on the listing:
|
|
33
|
+
* the floor is `minOfferAmount(listedPrice, minOfferPercent)` and the ceiling is
|
|
34
|
+
* the listed price itself. Both are re-checked server-side in `makeOffer` — this
|
|
35
|
+
* exists so the buyer is told before submitting, using the SAME
|
|
36
|
+
* `minOfferAmount()` the server uses, rather than a second rounding rule that
|
|
37
|
+
* could disagree by a rupee.
|
|
38
|
+
*/
|
|
39
|
+
export function makeOfferFormSchema(opts) {
|
|
40
|
+
const { listedPrice, minOffer, formatAmount } = opts;
|
|
41
|
+
return z.object({
|
|
42
|
+
offerAmount: z.coerce
|
|
43
|
+
.number({ invalid_type_error: "Enter an offer amount." })
|
|
44
|
+
.positive("Offer amount must be greater than zero.")
|
|
45
|
+
.min(minOffer, `Minimum offer is ${formatAmount(minOffer)}.`)
|
|
46
|
+
.max(listedPrice - 0.01, `Offer must be below the listed price of ${formatAmount(listedPrice)} — use Add to Cart to buy at that price.`),
|
|
47
|
+
buyerNote: z.string().max(300, "Keep your note under 300 characters.").optional(),
|
|
48
|
+
});
|
|
49
|
+
}
|
|
@@ -8,4 +8,3 @@ export { OfferRepository, offerRepository, } from "./repository/offer.repository
|
|
|
8
8
|
export { GET as sellerProductsGET } from "./api/products/route";
|
|
9
9
|
export { GET as sellerStoreGET } from "./api/store/route";
|
|
10
10
|
export { GET as sellerCouponsGET } from "./api/coupons/route";
|
|
11
|
-
export { GET as sellerOffersGET } from "./api/offers/route";
|
|
@@ -8,4 +8,3 @@ export { OfferRepository, offerRepository, } from "./repository/offer.repository
|
|
|
8
8
|
export { GET as sellerProductsGET } from "./api/products/route";
|
|
9
9
|
export { GET as sellerStoreGET } from "./api/store/route";
|
|
10
10
|
export { GET as sellerCouponsGET } from "./api/coupons/route";
|
|
11
|
-
export { GET as sellerOffersGET } from "./api/offers/route";
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import type { JsonValue } from "@mohasinac/appkit/client";
|
|
3
|
+
import type { ZodTypeAny } from "zod";
|
|
4
|
+
import type { FormShellStep } from "../../ui/forms/FormShell";
|
|
5
|
+
/**
|
|
6
|
+
* A collapsible form segment. Replaces `StepDef` — the differences are the
|
|
7
|
+
* point, not incidental:
|
|
8
|
+
*
|
|
9
|
+
* - there is no `goNext` gate, so a section can never block access to a
|
|
10
|
+
* later one. Everything is reachable at all times;
|
|
11
|
+
* - `required` sections sort first and open on mount;
|
|
12
|
+
* - one shared submit at the bottom, never per-section.
|
|
13
|
+
*/
|
|
14
|
+
export interface SectionDef<T extends object = Record<string, JsonValue>> {
|
|
15
|
+
/** Stable, URL-safe. Becomes the `?section=` value and the DOM anchor id. */
|
|
16
|
+
id: string;
|
|
17
|
+
label: string;
|
|
18
|
+
/** Longer heading for the open panel. Defaults to `label`. */
|
|
19
|
+
heading?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Mandatory section: sorted first, open on mount, and its header renders
|
|
22
|
+
* without a collapse control so it can't be hidden.
|
|
23
|
+
*/
|
|
24
|
+
required?: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* This section's fields belong to quick mode. Consumed by
|
|
27
|
+
* `deriveQuickFields` in `entity-form.ts`, not by SectionForm itself.
|
|
28
|
+
*/
|
|
29
|
+
quick?: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Keep the panel mounted while collapsed. Set on any section holding
|
|
32
|
+
* uncommitted state — media uploads, rich-text buffers. See
|
|
33
|
+
* `CollapsibleSection.keepMounted`.
|
|
34
|
+
*/
|
|
35
|
+
keepMounted?: boolean;
|
|
36
|
+
render: (props: {
|
|
37
|
+
values: T;
|
|
38
|
+
onChange: (partial: Partial<T>) => void;
|
|
39
|
+
errors: Record<string, string>;
|
|
40
|
+
}) => ReactNode;
|
|
41
|
+
/**
|
|
42
|
+
* Schema field names this section owns — drives error→section attribution
|
|
43
|
+
* so `<FormErrorSummary>` can offer a jump link.
|
|
44
|
+
*/
|
|
45
|
+
fields?: string[];
|
|
46
|
+
/** Per-section schema. Falls back to `schema`, then `FormSchemaContext`. */
|
|
47
|
+
schema?: ZodTypeAny;
|
|
48
|
+
/** Hide the section entirely (conditional per-type sections). */
|
|
49
|
+
when?: (values: T) => boolean;
|
|
50
|
+
}
|
|
51
|
+
export interface SectionFormProps<T extends object = Record<string, JsonValue>> {
|
|
52
|
+
sections: SectionDef<T>[];
|
|
53
|
+
values: T;
|
|
54
|
+
onChange: (partial: Partial<T>) => void;
|
|
55
|
+
onSubmit: () => void | Promise<void>;
|
|
56
|
+
submitLabel?: string;
|
|
57
|
+
isLoading?: boolean;
|
|
58
|
+
/** Whole-form schema for sections that don't declare their own. */
|
|
59
|
+
schema?: ZodTypeAny;
|
|
60
|
+
/** Controlled open set. Omit to let SectionForm own it. */
|
|
61
|
+
openIds?: string[];
|
|
62
|
+
onOpenChange?: (ids: string[]) => void;
|
|
63
|
+
/** `"single"` = accordion. Default `"multi"` — independent sections. */
|
|
64
|
+
expandMode?: "single" | "multi";
|
|
65
|
+
/** Suppress the built-in submit row (a FormShell footer owns it instead). */
|
|
66
|
+
hideActions?: boolean;
|
|
67
|
+
/**
|
|
68
|
+
* Fires whenever live validation recomputes. SectionForm deliberately does
|
|
69
|
+
* NOT mount its own `FormShellContext.Provider` — the owning view keeps the
|
|
70
|
+
* single provider and composes these in, exactly as StepForm did, so a
|
|
71
|
+
* second provider can never shadow the real one.
|
|
72
|
+
*/
|
|
73
|
+
onValidationChange?: (fieldErrors: Record<string, string>, fieldToSectionIndex: Record<string, number>) => void;
|
|
74
|
+
}
|
|
75
|
+
/** Sections in render order: required first, original order preserved within each group. */
|
|
76
|
+
export declare function orderSections<T extends object>(sections: SectionDef<T>[]): SectionDef<T>[];
|
|
77
|
+
/** `fieldName -> index into the ORDERED section list`. */
|
|
78
|
+
export declare function buildFieldToSectionIndex<T extends object>(ordered: SectionDef<T>[]): Record<string, number>;
|
|
79
|
+
export interface UseSectionFormNavResult<T extends object> {
|
|
80
|
+
/** Sections in render order — required first. */
|
|
81
|
+
ordered: SectionDef<T>[];
|
|
82
|
+
openIds: string[];
|
|
83
|
+
setOpenIds: (ids: string[]) => void;
|
|
84
|
+
/** Expand section `index`, then scroll it into view. */
|
|
85
|
+
goToSection: (index: number) => void;
|
|
86
|
+
fieldToSectionIndex: Record<string, number>;
|
|
87
|
+
/** Descriptors for `useFormShellState`'s `nav.sections`. */
|
|
88
|
+
sectionMeta: FormShellStep[];
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Navigation state for a `<SectionForm>`. Feed `sectionMeta`/`goToSection`/
|
|
92
|
+
* `fieldToSectionIndex` into `useFormShellState(schema, nav)` and `openIds`
|
|
93
|
+
* back into `<SectionForm>`.
|
|
94
|
+
*/
|
|
95
|
+
export declare function useSectionFormNav<T extends object = Record<string, JsonValue>>(sections: SectionDef<T>[], values?: T): UseSectionFormNavResult<T>;
|
|
96
|
+
/** DOM id for a section panel — kept in one place so nav and render agree. */
|
|
97
|
+
export declare function sectionAnchorId(id: string): string;
|
|
98
|
+
export declare function SectionForm<T extends object = Record<string, JsonValue>>({ sections, values, onChange, onSubmit, submitLabel, isLoading, schema, openIds: controlledOpenIds, onOpenChange, expandMode, hideActions, onValidationChange, }: SectionFormProps<T>): import("react").JSX.Element;
|
|
99
|
+
export default SectionForm;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
4
|
+
import { Button } from "../../ui/components/Button";
|
|
5
|
+
import { CollapsibleSection } from "../../ui/components/CollapsibleSection";
|
|
6
|
+
import { Badge } from "../../ui/components/Badge";
|
|
7
|
+
import { Row, Span, Stack } from "../../ui";
|
|
8
|
+
import { FormSchemaContext } from "./FormShell";
|
|
9
|
+
/** Sections in render order: required first, original order preserved within each group. */
|
|
10
|
+
export function orderSections(sections) {
|
|
11
|
+
return [...sections.filter((s) => s.required), ...sections.filter((s) => !s.required)];
|
|
12
|
+
}
|
|
13
|
+
/** `fieldName -> index into the ORDERED section list`. */
|
|
14
|
+
export function buildFieldToSectionIndex(ordered) {
|
|
15
|
+
const map = {};
|
|
16
|
+
ordered.forEach((section, i) => {
|
|
17
|
+
section.fields?.forEach((field) => { map[field] = i; });
|
|
18
|
+
});
|
|
19
|
+
return map;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Navigation state for a `<SectionForm>`. Feed `sectionMeta`/`goToSection`/
|
|
23
|
+
* `fieldToSectionIndex` into `useFormShellState(schema, nav)` and `openIds`
|
|
24
|
+
* back into `<SectionForm>`.
|
|
25
|
+
*/
|
|
26
|
+
export function useSectionFormNav(sections, values) {
|
|
27
|
+
const ordered = useMemo(() => {
|
|
28
|
+
const visible = values
|
|
29
|
+
? sections.filter((s) => (s.when ? s.when(values) : true))
|
|
30
|
+
: sections;
|
|
31
|
+
return orderSections(visible);
|
|
32
|
+
}, [sections, values]);
|
|
33
|
+
const [openIds, setOpenIds] = useState(() => orderSections(sections).filter((s) => s.required).map((s) => s.id));
|
|
34
|
+
const fieldToSectionIndex = useMemo(() => buildFieldToSectionIndex(ordered), [ordered]);
|
|
35
|
+
const sectionMeta = useMemo(() => ordered.map((s) => ({ id: s.id, label: s.label, requiredFields: s.fields })), [ordered]);
|
|
36
|
+
const goToSection = useCallback((index) => {
|
|
37
|
+
const target = ordered[index];
|
|
38
|
+
if (!target)
|
|
39
|
+
return;
|
|
40
|
+
setOpenIds((prev) => (prev.includes(target.id) ? prev : [...prev, target.id]));
|
|
41
|
+
// The panel must MOUNT before it can be scrolled to — expanding is a state
|
|
42
|
+
// update, so defer a frame. Without this the scroll lands on a zero-height
|
|
43
|
+
// collapsed header and the offending field stays off-screen.
|
|
44
|
+
requestAnimationFrame(() => {
|
|
45
|
+
const el = document.getElementById(sectionAnchorId(target.id));
|
|
46
|
+
el?.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
47
|
+
el?.querySelector("[aria-invalid='true']")?.focus();
|
|
48
|
+
});
|
|
49
|
+
}, [ordered]);
|
|
50
|
+
return { ordered, openIds, setOpenIds, goToSection, fieldToSectionIndex, sectionMeta };
|
|
51
|
+
}
|
|
52
|
+
/** DOM id for a section panel — kept in one place so nav and render agree. */
|
|
53
|
+
export function sectionAnchorId(id) {
|
|
54
|
+
return `section-${id}`;
|
|
55
|
+
}
|
|
56
|
+
export function SectionForm({ sections, values, onChange, onSubmit, submitLabel = "Save", isLoading = false, schema, openIds: controlledOpenIds, onOpenChange, expandMode = "multi", hideActions = false, onValidationChange, }) {
|
|
57
|
+
const inheritedSchema = useContext(FormSchemaContext);
|
|
58
|
+
const [fieldErrors, setFieldErrors] = useState({});
|
|
59
|
+
const [uncontrolledOpenIds, setUncontrolledOpenIds] = useState(() => orderSections(sections).filter((s) => s.required).map((s) => s.id));
|
|
60
|
+
const ordered = useMemo(() => orderSections(sections.filter((s) => (s.when ? s.when(values) : true))), [sections, values]);
|
|
61
|
+
const openIds = controlledOpenIds ?? uncontrolledOpenIds;
|
|
62
|
+
const setOpenIds = useCallback((ids) => {
|
|
63
|
+
if (!controlledOpenIds)
|
|
64
|
+
setUncontrolledOpenIds(ids);
|
|
65
|
+
onOpenChange?.(ids);
|
|
66
|
+
}, [controlledOpenIds, onOpenChange]);
|
|
67
|
+
const fieldToSectionIndex = useMemo(() => buildFieldToSectionIndex(ordered), [ordered]);
|
|
68
|
+
// Keep the latest callback without making runValidation depend on it — the
|
|
69
|
+
// caller usually passes an inline arrow, which would otherwise rebuild the
|
|
70
|
+
// validator on every render and re-fire the effect below in a loop.
|
|
71
|
+
const onValidationChangeRef = useRef(onValidationChange);
|
|
72
|
+
onValidationChangeRef.current = onValidationChange;
|
|
73
|
+
const runValidation = useCallback((nextValues) => {
|
|
74
|
+
const activeSchema = schema ?? inheritedSchema;
|
|
75
|
+
if (!activeSchema)
|
|
76
|
+
return;
|
|
77
|
+
// audit-unvalidated-safeparse-ok: bulk-replaces fieldErrors from the full
|
|
78
|
+
// parse result so a field that just became valid is cleared — which
|
|
79
|
+
// applyZodIssues' incremental setFieldError-per-issue cannot do.
|
|
80
|
+
const parsed = activeSchema.safeParse(nextValues);
|
|
81
|
+
if (parsed.success) {
|
|
82
|
+
setFieldErrors({});
|
|
83
|
+
onValidationChangeRef.current?.({}, fieldToSectionIndex);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const next = {};
|
|
87
|
+
for (const issue of parsed.error.issues) {
|
|
88
|
+
if (!issue.path || issue.path.length === 0)
|
|
89
|
+
continue;
|
|
90
|
+
next[issue.path.map(String).join(".")] = issue.message;
|
|
91
|
+
}
|
|
92
|
+
setFieldErrors(next);
|
|
93
|
+
onValidationChangeRef.current?.(next, fieldToSectionIndex);
|
|
94
|
+
}, [schema, inheritedSchema, fieldToSectionIndex]);
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
runValidation(values);
|
|
97
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
98
|
+
}, [schema, inheritedSchema, fieldToSectionIndex]);
|
|
99
|
+
const handleFieldChange = useCallback((partial) => {
|
|
100
|
+
onChange(partial);
|
|
101
|
+
runValidation({ ...values, ...partial });
|
|
102
|
+
}, [values, onChange, runValidation]);
|
|
103
|
+
const toggle = useCallback((id) => {
|
|
104
|
+
const isOpen = openIds.includes(id);
|
|
105
|
+
if (expandMode === "single") {
|
|
106
|
+
setOpenIds(isOpen ? [] : [id]);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
setOpenIds(isOpen ? openIds.filter((x) => x !== id) : [...openIds, id]);
|
|
110
|
+
}, [openIds, expandMode, setOpenIds]);
|
|
111
|
+
const errorCountFor = useCallback((section) => {
|
|
112
|
+
if (!section.fields?.length)
|
|
113
|
+
return 0;
|
|
114
|
+
return section.fields.filter((f) => Object.keys(fieldErrors).some((k) => k === f || k.startsWith(`${f}.`))).length;
|
|
115
|
+
}, [fieldErrors]);
|
|
116
|
+
return (_jsxs(Stack, { gap: "md", children: [ordered.map((section) => {
|
|
117
|
+
const isRequired = section.required === true;
|
|
118
|
+
// A required section is always open — it holds the fields without
|
|
119
|
+
// which the record cannot be saved, so hiding it is never useful.
|
|
120
|
+
const isCollapsed = isRequired ? false : !openIds.includes(section.id);
|
|
121
|
+
const errorCount = errorCountFor(section);
|
|
122
|
+
return (_jsx(CollapsibleSection, { id: sectionAnchorId(section.id), title: _jsxs(Row, { align: "center", gap: "xs", children: [_jsx(Span, { weight: "semibold", size: "sm", children: section.heading ?? section.label }), isRequired && _jsx(Span, { size: "xs", color: "muted", children: "Required" })] }), isCollapsed: isCollapsed, onToggle: () => { if (!isRequired)
|
|
123
|
+
toggle(section.id); }, keepMounted: section.keepMounted, renderHeaderExtra: errorCount > 0
|
|
124
|
+
? () => (_jsxs(Badge, { variant: "danger", children: [errorCount, " ", errorCount === 1 ? "issue" : "issues"] }))
|
|
125
|
+
: undefined, children: section.render({ values, onChange: handleFieldChange, errors: fieldErrors }) }, section.id));
|
|
126
|
+
}), !hideActions && (_jsx(Row, { justify: "end", paddingY: "y-sm", children: _jsx(Button, { variant: "primary", type: "submit", onClick: () => void onSubmit(), isLoading: isLoading, disabled: isLoading, children: submitLabel }) }))] }));
|
|
127
|
+
}
|
|
128
|
+
export default SectionForm;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import type { JsonValue } from "@mohasinac/appkit/client";
|
|
2
|
+
import type { ZodTypeAny } from "zod";
|
|
3
|
+
import type { SectionDef } from "./SectionForm";
|
|
4
|
+
import type { QuickFieldDef, QuickFieldType } from "./QuickFormDrawer";
|
|
5
|
+
/**
|
|
6
|
+
* One definition per entity, two rendered modes.
|
|
7
|
+
*
|
|
8
|
+
* The point of this type is that quick mode is DERIVED from `sections`, never
|
|
9
|
+
* declared alongside them — two hand-maintained field lists for one entity is
|
|
10
|
+
* the drift this codebase already has too much of.
|
|
11
|
+
*
|
|
12
|
+
* ## Quick mode is self-submitting
|
|
13
|
+
*
|
|
14
|
+
* Saving from the quick drawer must produce a complete, valid, publishable
|
|
15
|
+
* record. It is not step one of the full form; "open full form" is an optional
|
|
16
|
+
* secondary path. That gives a mechanical rule for what belongs in it:
|
|
17
|
+
*
|
|
18
|
+
* quickFields = schema.required − derive()'s outputs
|
|
19
|
+
*
|
|
20
|
+
* If the resulting `quickSchema` cannot satisfy the server's create schema,
|
|
21
|
+
* the definition is wrong — the missing field either joins the quick set or
|
|
22
|
+
* gets a `derive()` default. Nothing may be left "for the user to finish
|
|
23
|
+
* later": that is exactly how `/store/products/new` came to POST without a
|
|
24
|
+
* `listingType` at all.
|
|
25
|
+
*/
|
|
26
|
+
export interface EntityFormDefinition<T extends object = Record<string, JsonValue>> {
|
|
27
|
+
/** Stable key — also the `SCHEMAS.forms` registry key. */
|
|
28
|
+
entity: string;
|
|
29
|
+
/** Full-mode schema. Satisfies `audit-form-schema`. */
|
|
30
|
+
schema: ZodTypeAny;
|
|
31
|
+
/**
|
|
32
|
+
* Create-mode schema for the quick drawer. Defaults to `schema` when
|
|
33
|
+
* omitted, but should normally be `schema.pick(quickFields)`.
|
|
34
|
+
*
|
|
35
|
+
* CREATE ONLY. A quick schema is a strict subset by construction, so
|
|
36
|
+
* sending it to a PATCH would strip every field outside the quick set.
|
|
37
|
+
*/
|
|
38
|
+
quickSchema?: ZodTypeAny;
|
|
39
|
+
/** THE single source of truth for this entity's fields. */
|
|
40
|
+
sections: SectionDef<T>[];
|
|
41
|
+
/**
|
|
42
|
+
* Auto-compute fields from earlier ones — slug and SEO from a title, totals
|
|
43
|
+
* from line items, counts from an array.
|
|
44
|
+
*
|
|
45
|
+
* Two rules, both enforced rather than documented:
|
|
46
|
+
* - a `derive()` output may NEVER also be a quick field (asserted by
|
|
47
|
+
* `assertNoDeriveCollision`) — that would ask the user for something the
|
|
48
|
+
* definition already computes;
|
|
49
|
+
* - it must fill only when the target is empty or its source actually
|
|
50
|
+
* changed, never overwrite a value the user typed.
|
|
51
|
+
*/
|
|
52
|
+
derive?: (partial: Partial<T>, prev: T, ctx: {
|
|
53
|
+
mode: "create" | "edit";
|
|
54
|
+
}) => Partial<T>;
|
|
55
|
+
/** Field names `derive` can produce. Used to subtract them from the quick set. */
|
|
56
|
+
derivedFields?: readonly string[];
|
|
57
|
+
/** Input types for quick-mode fields. Anything absent defaults to `"text"`. */
|
|
58
|
+
quickFieldTypes?: Readonly<Record<string, QuickFieldType>>;
|
|
59
|
+
/** Labels for quick-mode fields. Falls back to a humanised field name. */
|
|
60
|
+
quickFieldLabels?: Readonly<Record<string, string>>;
|
|
61
|
+
/** Field names that are required in quick mode (drives the `required` flag). */
|
|
62
|
+
requiredFields?: readonly string[];
|
|
63
|
+
routes: {
|
|
64
|
+
list: string;
|
|
65
|
+
new: string;
|
|
66
|
+
edit: (id: string) => string;
|
|
67
|
+
view?: (id: string) => string;
|
|
68
|
+
};
|
|
69
|
+
labels: {
|
|
70
|
+
singular: string;
|
|
71
|
+
plural: string;
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/** `"storeName"` / `"store_name"` / `"store.name"` -> `"Store name"`. */
|
|
75
|
+
export declare function humaniseFieldName(name: string): string;
|
|
76
|
+
/**
|
|
77
|
+
* The quick-mode field set: every field owned by a `quick: true` section,
|
|
78
|
+
* minus anything `derive()` produces.
|
|
79
|
+
*
|
|
80
|
+
* Derived, never declared — so quick and full modes cannot drift apart.
|
|
81
|
+
*/
|
|
82
|
+
export declare function deriveQuickFieldNames<T extends object>(def: EntityFormDefinition<T>): string[];
|
|
83
|
+
/** Quick-mode field descriptors — feed straight into QuickFormDrawer's `fields` prop. */
|
|
84
|
+
export declare function deriveQuickFields<T extends object>(def: EntityFormDefinition<T>): QuickFieldDef[];
|
|
85
|
+
/**
|
|
86
|
+
* Throws when a `derive()` output is also offered as a quick field.
|
|
87
|
+
*
|
|
88
|
+
* That combination means the form asks the user to type something the
|
|
89
|
+
* definition computes for them, and whichever runs last silently wins. Call
|
|
90
|
+
* this once per definition — it is cheap and catches the mistake at the point
|
|
91
|
+
* the definition is written rather than at the point data is lost.
|
|
92
|
+
*/
|
|
93
|
+
export declare function assertNoDeriveCollision<T extends object>(def: EntityFormDefinition<T>): void;
|
|
94
|
+
/**
|
|
95
|
+
* Every field name the definition can reach — the union of all section fields
|
|
96
|
+
* plus everything `derive()` produces.
|
|
97
|
+
*
|
|
98
|
+
* This is what a schema must be a superset of. A document field missing from
|
|
99
|
+
* this set is unreachable in the UI; a schema field missing from it can never
|
|
100
|
+
* be populated. `audit-form-field-roundtrip` checks both directions.
|
|
101
|
+
*/
|
|
102
|
+
export declare function allDefinitionFields<T extends object>(def: EntityFormDefinition<T>): string[];
|
|
103
|
+
/**
|
|
104
|
+
* Apply `derive()` to a pending change, without clobbering user input.
|
|
105
|
+
*
|
|
106
|
+
* Returns the partial to merge: the caller's own change plus whatever the
|
|
107
|
+
* definition computed from it.
|
|
108
|
+
*/
|
|
109
|
+
export declare function applyDerive<T extends object>(def: EntityFormDefinition<T>, partial: Partial<T>, prev: T, mode: "create" | "edit"): Partial<T>;
|