@mohasinac/appkit 4.11.2 → 4.12.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/server/features/checkout/actions.d.ts +40 -3
- package/dist/_internal/server/features/checkout/actions.js +232 -47
- package/dist/_internal/server/features/checkout/data.d.ts +10 -0
- package/dist/_internal/server/features/orders/adapters.js +20 -0
- package/dist/_internal/server/features/products/data.d.ts +12 -3
- package/dist/_internal/server/features/products/data.js +55 -4
- 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 +47 -0
- package/dist/_internal/server/features/products/list-public.js +131 -6
- package/dist/_internal/server/features/reviews/data.d.ts +1 -1
- package/dist/_internal/server/features/reviews/data.js +3 -2
- package/dist/_internal/server/features/tester/visibility.js +3 -3
- package/dist/_internal/server/jobs/core/auctionSettlement.js +5 -2
- package/dist/_internal/server/jobs/core/testerSandboxCleanup.d.ts +1 -1
- package/dist/_internal/server/jobs/core/testerSandboxCleanup.js +4 -1
- package/dist/_internal/server/jobs/core/testerSandboxRefresh.js +2 -0
- package/dist/_internal/server/jobs/handlers/_helpers.js +4 -1
- package/dist/_internal/shared/checkout/lanes.d.ts +20 -0
- package/dist/_internal/shared/checkout/lanes.js +28 -0
- package/dist/_internal/shared/features/checkout/config.d.ts +4 -0
- package/dist/_internal/shared/features/checkout/config.js +8 -0
- package/dist/_internal/shared/features/promotions/schema.d.ts +0 -16
- package/dist/_internal/shared/features/promotions/schema.js +0 -2
- package/dist/_internal/shared/features/reviews/config.d.ts +8 -1
- package/dist/_internal/shared/features/reviews/config.js +8 -1
- package/dist/_internal/shared/fees/calculator.d.ts +34 -2
- package/dist/_internal/shared/fees/calculator.js +45 -1
- package/dist/_internal/shared/listing-types/_registry.d.ts +88 -1
- package/dist/_internal/shared/listing-types/_registry.js +63 -1
- package/dist/_internal/shared/listing-types/art/config.d.ts +59 -0
- package/dist/_internal/shared/listing-types/art/config.js +12 -0
- package/dist/_internal/shared/listing-types/auction/config.d.ts +53 -0
- package/dist/_internal/shared/listing-types/auction/config.js +17 -0
- package/dist/_internal/shared/listing-types/classified/config.d.ts +59 -0
- package/dist/_internal/shared/listing-types/classified/config.js +14 -0
- package/dist/_internal/shared/listing-types/digital-code/config.d.ts +59 -0
- package/dist/_internal/shared/listing-types/digital-code/config.js +12 -0
- package/dist/_internal/shared/listing-types/live/config.d.ts +59 -0
- package/dist/_internal/shared/listing-types/live/config.js +14 -0
- package/dist/_internal/shared/listing-types/pre-order/config.d.ts +110 -0
- package/dist/_internal/shared/listing-types/pre-order/config.js +14 -0
- package/dist/_internal/shared/listing-types/prize-draw/config.d.ts +44 -0
- package/dist/_internal/shared/listing-types/prize-draw/config.js +10 -0
- package/dist/_internal/shared/listing-types/standard/config.d.ts +59 -0
- package/dist/_internal/shared/listing-types/standard/config.js +9 -0
- package/dist/_internal/shared/listing-types/stickers/config.d.ts +59 -0
- package/dist/_internal/shared/listing-types/stickers/config.js +12 -0
- package/dist/client.d.ts +12 -2
- package/dist/client.js +8 -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 +12 -0
- package/dist/constants/field-names.js +12 -0
- package/dist/constants/table-keys.d.ts +2 -0
- package/dist/constants/table-keys.js +2 -0
- package/dist/features/account/components/UserOrdersView.js +27 -5
- package/dist/features/admin/components/AdminAddressesView.js +21 -11
- package/dist/features/admin/components/AdminArtView.d.ts +9 -0
- package/dist/features/admin/components/AdminArtView.js +11 -35
- package/dist/features/admin/components/AdminClassifiedView.d.ts +9 -0
- package/dist/features/admin/components/AdminClassifiedView.js +11 -35
- package/dist/features/admin/components/AdminCouponEditorView.js +1 -4
- package/dist/features/admin/components/AdminDigitalCodesView.d.ts +9 -0
- package/dist/features/admin/components/AdminDigitalCodesView.js +11 -42
- package/dist/features/admin/components/AdminLiveView.d.ts +9 -0
- package/dist/features/admin/components/AdminLiveView.js +11 -41
- package/dist/features/admin/components/AdminOrderEditorView.d.ts +8 -1
- package/dist/features/admin/components/AdminOrderEditorView.js +4 -3
- package/dist/features/admin/components/AdminOrdersView.js +14 -1
- package/dist/features/admin/components/AdminPaymentMethodsView.js +18 -11
- package/dist/features/admin/components/AdminProductsView.js +34 -18
- package/dist/features/admin/components/AdminSiteConfigGuideView.js +1 -1
- package/dist/features/admin/components/AdminSiteSettingsView.js +4 -2
- package/dist/features/admin/components/AdminStickersView.d.ts +9 -0
- package/dist/features/admin/components/AdminStickersView.js +11 -35
- package/dist/features/admin/constants/filter-tabs.d.ts +26 -9
- package/dist/features/admin/constants/filter-tabs.js +22 -9
- package/dist/features/admin/hooks/useAdminListing.js +7 -2
- package/dist/features/admin/schemas/firestore.d.ts +5 -0
- package/dist/features/admin/schemas/firestore.js +1 -0
- package/dist/features/auctions/actions/bid-actions.js +2 -2
- package/dist/features/auctions/components/AuctionBidsTable.js +1 -1
- package/dist/features/auctions/components/AuctionBottomActions.js +1 -0
- package/dist/features/auctions/components/AuctionDetailPageView.js +13 -11
- package/dist/features/cart/components/CartDrawer.d.ts +9 -1
- package/dist/features/cart/components/CartDrawer.js +4 -3
- package/dist/features/cart/components/CartPriceBreakdown.d.ts +61 -0
- package/dist/features/cart/components/CartPriceBreakdown.js +25 -0
- package/dist/features/cart/components/StoreAddonsPicker.d.ts +47 -0
- package/dist/features/cart/components/StoreAddonsPicker.js +21 -0
- package/dist/features/cart/components/index.d.ts +4 -0
- package/dist/features/cart/components/index.js +2 -0
- package/dist/features/cart/repository/cart.repository.d.ts +20 -2
- package/dist/features/cart/repository/cart.repository.js +49 -0
- package/dist/features/cart/schemas/firestore.d.ts +26 -2
- package/dist/features/categories/components/BrandDetailTabs.js +31 -11
- package/dist/features/categories/components/CategoryDetailTabs.js +27 -7
- package/dist/features/categories/components/CategoryProductsListing.d.ts +9 -1
- package/dist/features/categories/components/CategoryProductsListing.js +5 -2
- package/dist/features/classified/components/ClassifiedDetailPageView.js +2 -1
- package/dist/features/contact/email.js +5 -1
- package/dist/features/digital-codes/components/DigitalCodeDetailPageView.js +17 -16
- package/dist/features/homepage/components/FeaturedResultsSection.js +1 -1
- package/dist/features/homepage/components/SectionCarousel.js +1 -1
- package/dist/features/homepage/components/ShopByCategorySection.js +1 -1
- package/dist/features/layout/AppLayoutShell.js +9 -2
- package/dist/features/layout/BottomActions.js +62 -6
- package/dist/features/layout/BottomActionsContext.d.ts +26 -0
- package/dist/features/layout/BottomActionsContext.js +18 -1
- package/dist/features/layout/hooks/useBottomActions.d.ts +12 -1
- package/dist/features/layout/hooks/useBottomActions.js +14 -1
- package/dist/features/layout/index.d.ts +1 -1
- package/dist/features/live/components/LiveItemDetailPageView.js +7 -6
- package/dist/features/orders/components/OrderAddonBadges.d.ts +36 -0
- package/dist/features/orders/components/OrderAddonBadges.js +28 -0
- package/dist/features/orders/components/index.d.ts +1 -0
- package/dist/features/orders/components/index.js +1 -0
- package/dist/features/orders/repository/orders.repository.d.ts +36 -0
- package/dist/features/orders/repository/orders.repository.js +18 -0
- package/dist/features/orders/types/index.d.ts +38 -0
- package/dist/features/pre-orders/components/PreOrderBottomActions.d.ts +1 -1
- package/dist/features/pre-orders/components/PreOrderBottomActions.js +3 -19
- package/dist/features/products/components/ArtStickersListView.js +5 -4
- package/dist/features/products/components/AuctionsIndexListing.js +1 -2
- package/dist/features/products/components/ListingBottomActions.d.ts +28 -0
- package/dist/features/products/components/ListingBottomActions.js +25 -0
- package/dist/features/products/components/PrizeDrawBottomActions.d.ts +1 -1
- package/dist/features/products/components/PrizeDrawBottomActions.js +3 -21
- package/dist/features/products/components/PrizeDrawsListingView.js +18 -33
- package/dist/features/products/components/ProductDetailActions.js +4 -0
- package/dist/features/products/components/ProductDetailPageView.js +7 -7
- package/dist/features/products/components/ProductsIndexListing.js +87 -11
- package/dist/features/products/components/ProductsIndexPageView.js +14 -2
- package/dist/features/products/config/listing-type-listing-config.d.ts +31 -0
- package/dist/features/products/config/listing-type-listing-config.js +74 -0
- package/dist/features/products/constants/listing-tabs.d.ts +43 -198
- package/dist/features/products/constants/listing-tabs.js +71 -58
- package/dist/features/products/constants/sieve.d.ts +104 -1
- package/dist/features/products/constants/sieve.js +27 -7
- package/dist/features/products/hooks/useProducts.js +14 -0
- package/dist/features/products/repository/products.repository.d.ts +45 -1
- package/dist/features/products/repository/products.repository.js +26 -3
- package/dist/features/products/types/index.d.ts +15 -0
- package/dist/features/products/utils/listing-badge-variant.d.ts +20 -1
- package/dist/features/products/utils/listing-badge-variant.js +26 -1
- package/dist/features/products/utils/listing-type.d.ts +24 -0
- package/dist/features/products/utils/listing-type.js +48 -0
- package/dist/features/promotions/actions/coupon-actions.d.ts +7 -0
- package/dist/features/promotions/actions/coupon-actions.js +12 -1
- package/dist/features/promotions/actions/coupon-stacking.d.ts +24 -0
- package/dist/features/promotions/actions/coupon-stacking.js +21 -0
- package/dist/features/promotions/actions/index.d.ts +1 -0
- package/dist/features/promotions/actions/index.js +1 -0
- package/dist/features/promotions/actions/seller-coupon-actions.d.ts +0 -1
- package/dist/features/promotions/actions/seller-coupon-actions.js +0 -1
- package/dist/features/promotions/components/CouponHelpDetails.d.ts +20 -0
- package/dist/features/promotions/components/CouponHelpDetails.js +17 -0
- package/dist/features/promotions/components/CouponsIndexListing.js +2 -1
- package/dist/features/promotions/components/index.d.ts +2 -0
- package/dist/features/promotions/components/index.js +1 -0
- package/dist/features/promotions/constants/coupon-help.d.ts +27 -0
- package/dist/features/promotions/constants/coupon-help.js +42 -0
- package/dist/features/promotions/repository/coupons.repository.d.ts +10 -1
- package/dist/features/promotions/repository/coupons.repository.js +22 -8
- package/dist/features/promotions/schemas/firestore.d.ts +0 -1
- package/dist/features/promotions/schemas/firestore.js +1 -1
- package/dist/features/promotions/schemas/index.d.ts +0 -20
- package/dist/features/promotions/schemas/index.js +0 -1
- package/dist/features/reviews/actions/review-actions.js +5 -1
- package/dist/features/reviews/components/ReviewDetailPageView.js +2 -1
- package/dist/features/reviews/components/ReviewFilters.d.ts +7 -1
- package/dist/features/reviews/components/ReviewFilters.js +4 -3
- package/dist/features/reviews/components/ReviewsIndexListing.d.ts +6 -0
- package/dist/features/reviews/components/ReviewsIndexListing.js +9 -109
- package/dist/features/reviews/components/ReviewsListingPanel.d.ts +41 -0
- package/dist/features/reviews/components/ReviewsListingPanel.js +226 -0
- package/dist/features/reviews/components/index.d.ts +2 -0
- package/dist/features/reviews/components/index.js +1 -0
- package/dist/features/reviews/hooks/useReviews.d.ts +8 -0
- package/dist/features/reviews/hooks/useReviews.js +14 -6
- package/dist/features/reviews/repository/reviews.repository.d.ts +29 -1
- package/dist/features/reviews/repository/reviews.repository.js +77 -41
- package/dist/features/reviews/types/index.d.ts +8 -1
- package/dist/features/seller/components/SellerOrdersView.js +2 -1
- package/dist/features/seller/components/SellerProductsCards.js +2 -2
- package/dist/features/seller/components/SellerProductsView.js +21 -18
- package/dist/features/stores/actions/store-query-actions.js +1 -1
- package/dist/features/stores/components/StoreArtStickersPageView.d.ts +21 -0
- package/dist/features/stores/components/StoreArtStickersPageView.js +30 -0
- package/dist/features/stores/components/StoreAuctionsListing.js +5 -11
- package/dist/features/stores/components/StoreAuctionsPageView.d.ts +4 -1
- package/dist/features/stores/components/StoreAuctionsPageView.js +17 -17
- package/dist/features/stores/components/StoreClassifiedsListing.js +7 -0
- package/dist/features/stores/components/StoreClassifiedsPageView.d.ts +4 -1
- package/dist/features/stores/components/StoreClassifiedsPageView.js +16 -12
- package/dist/features/stores/components/StoreDetailLayoutView.js +4 -3
- package/dist/features/stores/components/StoreDigitalCodesListing.js +5 -0
- package/dist/features/stores/components/StoreDigitalCodesPageView.d.ts +4 -1
- package/dist/features/stores/components/StoreDigitalCodesPageView.js +16 -12
- package/dist/features/stores/components/StoreLiveItemsListing.js +7 -0
- package/dist/features/stores/components/StoreLiveItemsPageView.d.ts +4 -1
- package/dist/features/stores/components/StoreLiveItemsPageView.js +16 -12
- package/dist/features/stores/components/StorePreOrdersListing.js +6 -11
- package/dist/features/stores/components/StorePreOrdersPageView.d.ts +4 -1
- package/dist/features/stores/components/StorePreOrdersPageView.js +17 -17
- package/dist/features/stores/components/StorePrizeDrawsPageView.d.ts +4 -8
- package/dist/features/stores/components/StorePrizeDrawsPageView.js +17 -22
- package/dist/features/stores/components/StoreProductsListing.d.ts +7 -1
- package/dist/features/stores/components/StoreProductsListing.js +3 -2
- package/dist/features/stores/components/StoreProductsPageView.d.ts +1 -1
- package/dist/features/stores/components/StoreProductsPageView.js +16 -21
- package/dist/features/stores/components/StoreReviewsListing.d.ts +4 -0
- package/dist/features/stores/components/StoreReviewsListing.js +7 -108
- package/dist/features/stores/schemas/index.d.ts +2 -2
- package/dist/features/tester/seed-data/coupons-tester-seed-data.js +3 -3
- package/dist/features/tester/seed-data/index.d.ts +1 -0
- package/dist/features/tester/seed-data/index.js +1 -0
- package/dist/features/tester/seed-data/offers-tester-seed-data.d.ts +2 -0
- package/dist/features/tester/seed-data/offers-tester-seed-data.js +61 -0
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +939 -1
- package/dist/index.d.ts +9 -3
- package/dist/index.js +11 -2
- package/dist/next/routing/route-map.d.ts +2 -0
- package/dist/next/routing/route-map.js +5 -0
- package/dist/react/hooks/useListingTypeFlags.d.ts +12 -19
- package/dist/react/hooks/useListingTypeFlags.js +4 -9
- package/dist/schemas/registry.d.ts +0 -12
- package/dist/seed/claimed-coupons-seed-data.js +3 -3
- package/dist/seed/coupons-seed-data.js +29 -10
- package/dist/seed/faq-seed-data.js +2 -2
- package/dist/seed/manifest.js +2 -2
- package/dist/seed/reviews-seed-data.js +52 -0
- package/dist/server-entry.d.ts +1 -0
- package/dist/server-entry.js +1 -0
- package/dist/styles.css +55 -39
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/components/Checkbox.d.ts +10 -1
- package/dist/ui/components/Checkbox.js +14 -2
- package/dist/ui/components/Checkbox.style.css +11 -0
- package/dist/ui/components/FilterChipGroup.d.ts +21 -3
- package/dist/ui/components/FilterChipGroup.js +33 -4
- package/dist/ui/components/HorizontalScroller.d.ts +1 -2
- package/dist/ui/components/HorizontalScroller.js +9 -5
- package/dist/ui/components/HorizontalScroller.style.css +43 -38
- package/package.json +1 -1
|
@@ -13,6 +13,8 @@ import { PRODUCT_COLLECTION } from "../../../../features/products/schemas/firest
|
|
|
13
13
|
import { PRODUCTS_SITEMAP_LIMIT } from "../../../shared/features/products/config";
|
|
14
14
|
import { PRODUCT_FIELDS } from "../../../../constants/field-names";
|
|
15
15
|
import { SIEVE_OP, sieveAnd, sieveFilter, sortBy } from "../../../../utils/sieve-builder";
|
|
16
|
+
import { REVIEW_FIELDS } from "../../../../constants/field-names";
|
|
17
|
+
import { REVIEWS_DETAIL_PAGE_SIZE } from "../../../shared/features/reviews/config";
|
|
16
18
|
// ---------------------------------------------------------------------------
|
|
17
19
|
// Request-scoped cache — each function is deduplicated per RSC render tree.
|
|
18
20
|
// Both generateMetadata() and the page component get the same promise.
|
|
@@ -26,8 +28,8 @@ export const getProductForDetail = cache(async (slugOrId) => {
|
|
|
26
28
|
return product;
|
|
27
29
|
});
|
|
28
30
|
/** Fetch the first page of approved reviews for a product, deduped per request. */
|
|
29
|
-
export const getReviewsForProduct = cache(async (productId) => {
|
|
30
|
-
return reviewRepository.findApprovedByProduct(productId).catch(() => []);
|
|
31
|
+
export const getReviewsForProduct = cache(async (productId, limit = REVIEWS_DETAIL_PAGE_SIZE) => {
|
|
32
|
+
return reviewRepository.findApprovedByProduct(productId, limit).catch(() => []);
|
|
31
33
|
});
|
|
32
34
|
/** Firestore review doc → client Review shape. Shared by every listing-type detail page's reviews tab. */
|
|
33
35
|
export function toReview(doc) {
|
|
@@ -57,10 +59,59 @@ export function toReview(doc) {
|
|
|
57
59
|
};
|
|
58
60
|
}
|
|
59
61
|
/** Fetch + map approved reviews for a product straight to the client Review shape, deduped per request. */
|
|
60
|
-
export const getReviewItemsForProduct = cache(async (productId) => {
|
|
61
|
-
const docs = await reviewRepository
|
|
62
|
+
export const getReviewItemsForProduct = cache(async (productId, limit = REVIEWS_DETAIL_PAGE_SIZE) => {
|
|
63
|
+
const docs = await reviewRepository
|
|
64
|
+
.findApprovedByProduct(productId, limit)
|
|
65
|
+
.catch(() => []);
|
|
62
66
|
return docs.map(toReview);
|
|
63
67
|
});
|
|
68
|
+
/**
|
|
69
|
+
* Page 1 of a product's approved reviews, shaped exactly like `GET /api/reviews?productId=`
|
|
70
|
+
* so it can seed `<ReviewsListingPanel initialData>` without a client round-trip.
|
|
71
|
+
*
|
|
72
|
+
* The defaults here MUST match the panel's bare (unfiltered, page-1) query defaults —
|
|
73
|
+
* `staleTime: Infinity` freezes whatever `initialData` is handed over, so a mismatch would
|
|
74
|
+
* pin the wrong rows into the cache (Recurrent Root Cause #30).
|
|
75
|
+
*/
|
|
76
|
+
export const getReviewPageForProduct = cache(async (productId, pageSize = REVIEWS_DETAIL_PAGE_SIZE) => {
|
|
77
|
+
const empty = {
|
|
78
|
+
items: [],
|
|
79
|
+
total: 0,
|
|
80
|
+
page: 1,
|
|
81
|
+
pageSize,
|
|
82
|
+
totalPages: 1,
|
|
83
|
+
hasMore: false,
|
|
84
|
+
averageRating: 0,
|
|
85
|
+
ratingDistribution: {},
|
|
86
|
+
totalApproved: 0,
|
|
87
|
+
};
|
|
88
|
+
try {
|
|
89
|
+
const [summary, result] = await Promise.all([
|
|
90
|
+
reviewRepository.getApprovedRatingSummary(productId),
|
|
91
|
+
reviewRepository.listForProduct(productId, {
|
|
92
|
+
filters: sieveFilter(REVIEW_FIELDS.STATUS, SIEVE_OP.EQ, "approved"),
|
|
93
|
+
sorts: sortBy(REVIEW_FIELDS.CREATED_AT),
|
|
94
|
+
page: 1,
|
|
95
|
+
pageSize,
|
|
96
|
+
}),
|
|
97
|
+
]);
|
|
98
|
+
return {
|
|
99
|
+
items: result.items.map(toReview),
|
|
100
|
+
total: result.total,
|
|
101
|
+
page: result.page,
|
|
102
|
+
pageSize: result.pageSize,
|
|
103
|
+
totalPages: result.totalPages,
|
|
104
|
+
hasMore: result.hasMore,
|
|
105
|
+
averageRating: summary.averageRating,
|
|
106
|
+
ratingDistribution: summary.ratingDistribution,
|
|
107
|
+
totalApproved: summary.total,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
void normalizeError(err);
|
|
112
|
+
return empty;
|
|
113
|
+
}
|
|
114
|
+
});
|
|
64
115
|
/** List all published products for sitemap generation. */
|
|
65
116
|
export async function listSitemapProducts() {
|
|
66
117
|
try {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { getProductForDetail, getReviewsForProduct, listSitemapProducts, type SitemapProduct, computeRelatedItems, toProductItem, toReview, getReviewItemsForProduct, type RelatedItemsResult, } from "./data";
|
|
1
|
+
export { getProductForDetail, getReviewsForProduct, listSitemapProducts, type SitemapProduct, computeRelatedItems, toProductItem, toReview, getReviewItemsForProduct, getReviewPageForProduct, type RelatedItemsResult, } from "./data";
|
|
2
2
|
export { listPublicProducts, parsePublicProductParams, PUBLIC_PRODUCT_MAX_PAGE_SIZE, type PublicProductListInput, type PublicProductListResult, type PublicProductListOptions, type PublicProductExecutor, type PublicProductQuery, type ExecutorResult, } from "./list-public";
|
|
3
3
|
export { assertProductOwnership, assertStatusTransition, assertInStock, effectivePrice, isAvailableForPurchase, } from "./service";
|
|
4
4
|
export { createProductAction, createAuctionAction, createPreOrderAction, updateProductAction, deleteProductAction, setProductStatusAction, setProductFeaturedAction, } from "./actions";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { getProductForDetail, getReviewsForProduct, listSitemapProducts, computeRelatedItems, toProductItem, toReview, getReviewItemsForProduct, } from "./data";
|
|
1
|
+
export { getProductForDetail, getReviewsForProduct, listSitemapProducts, computeRelatedItems, toProductItem, toReview, getReviewItemsForProduct, getReviewPageForProduct, } from "./data";
|
|
2
2
|
export { listPublicProducts, parsePublicProductParams, PUBLIC_PRODUCT_MAX_PAGE_SIZE, } from "./list-public";
|
|
3
3
|
export { assertProductOwnership, assertStatusTransition, assertInStock, effectivePrice, isAvailableForPurchase, } from "./service";
|
|
4
4
|
export { createProductAction, createAuctionAction, createPreOrderAction, updateProductAction, deleteProductAction, setProductStatusAction, setProductFeaturedAction, } from "./actions";
|
|
@@ -1,6 +1,25 @@
|
|
|
1
1
|
import type { JsonValue } from "../../../../schemas/types";
|
|
2
2
|
/** Vercel Hobby Fluid Compute ceiling (CLAUDE.md Rule #6) — never fetch more at once. */
|
|
3
3
|
export declare const PUBLIC_PRODUCT_MAX_PAGE_SIZE = 50;
|
|
4
|
+
/**
|
|
5
|
+
* Which "Show X" defaults apply to a set of listing types.
|
|
6
|
+
*
|
|
7
|
+
* THE POINT OF THIS FUNCTION is that SSR and the client refetch call the SAME
|
|
8
|
+
* implementation. `staleTime: Infinity` freezes SSR `initialData` forever, so
|
|
9
|
+
* if the SSR filter-builder computes a different default than the client's
|
|
10
|
+
* bare-URL state, the first paint and every later refetch disagree and one of
|
|
11
|
+
* them is wrong — permanently (Root Cause #30). Two mirrored literals is
|
|
12
|
+
* exactly how that bug is written; one shared function is how it isn't.
|
|
13
|
+
*
|
|
14
|
+
* `/products` now spans all nine listing types, which makes this load-bearing
|
|
15
|
+
* rather than theoretical: a mixed set includes both sold-out-able types
|
|
16
|
+
* (products, art) and time-boxed ones (auctions, pre-orders, prize draws), so
|
|
17
|
+
* BOTH toggles can apply at once.
|
|
18
|
+
*/
|
|
19
|
+
export declare function defaultTogglesForListingTypes(types: readonly string[] | undefined): {
|
|
20
|
+
hideSoldByDefault: boolean;
|
|
21
|
+
hideEndedByDefault: boolean;
|
|
22
|
+
};
|
|
4
23
|
/**
|
|
5
24
|
* A product document as it comes back from either executor. Deliberately
|
|
6
25
|
* `JsonValue`-shaped rather than `ProductDocument`: the upstream listingProcessor
|
|
@@ -30,6 +49,16 @@ export interface PublicProductListInput {
|
|
|
30
49
|
freeShipping?: boolean;
|
|
31
50
|
isPartOfBundle?: boolean;
|
|
32
51
|
features?: readonly string[];
|
|
52
|
+
/** Pipe-joined multi-select over the `tags` array field. */
|
|
53
|
+
tags?: readonly string[];
|
|
54
|
+
/** Pipe-joined multi-select over `sublistingCategoryId`. */
|
|
55
|
+
sublistingCategoryIds?: readonly string[];
|
|
56
|
+
/**
|
|
57
|
+
* Per-listing-type facets (classified meetup city, digital-code delivery
|
|
58
|
+
* method, live-item species, …), keyed by TABLE_KEY. Resolved to their
|
|
59
|
+
* nested Firestore paths via `TYPE_FACET_FIELD`.
|
|
60
|
+
*/
|
|
61
|
+
typeFacets?: Record<string, string>;
|
|
33
62
|
preOrderProductionStatus?: string;
|
|
34
63
|
prizeRevealStatus?: string;
|
|
35
64
|
/** Hide sold-out rows. Applied in memory — never pushed into Firestore. */
|
|
@@ -108,4 +137,22 @@ export declare function parsePublicProductParams(params: URLSearchParams | Recor
|
|
|
108
137
|
hideEndedByDefault?: boolean;
|
|
109
138
|
}): PublicProductListInput;
|
|
110
139
|
export declare function listPublicProducts(input: PublicProductListInput, opts?: PublicProductListOptions): Promise<PublicProductListResult | null>;
|
|
140
|
+
/**
|
|
141
|
+
* One store tab's worth of listings.
|
|
142
|
+
*
|
|
143
|
+
* Every `Store*PageView` used to hand-roll this: `productRepository.list()`
|
|
144
|
+
* with an inline `sieveAnd(...)` and a `.catch(() => null)`. That is the exact
|
|
145
|
+
* shape that made `/art` render empty — an unsafe inequality (or any missing
|
|
146
|
+
* index) becomes FAILED_PRECONDITION, the catch turns it into `null`, and the
|
|
147
|
+
* page shows "no results" with nothing logged. Eight views carried it, and
|
|
148
|
+
* `audit-listing-filter-parity` had none of them registered.
|
|
149
|
+
*
|
|
150
|
+
* Routing them through `listPublicProducts` also gives every store tab
|
|
151
|
+
* something it never had: the URL's own sort/filter/page params reach the SSR
|
|
152
|
+
* fetch, so the first paint matches what the toolbar says is selected.
|
|
153
|
+
*/
|
|
154
|
+
export declare function listStoreProducts(storeId: string, listingTypes: readonly string[], searchParams?: URLSearchParams | Record<string, string | string[] | undefined>, defaults?: {
|
|
155
|
+
pageSize?: number;
|
|
156
|
+
sorts?: string;
|
|
157
|
+
}): Promise<PublicProductListResult | null>;
|
|
111
158
|
export {};
|
|
@@ -32,8 +32,36 @@ import { PRODUCT_FIELDS } from "../../../../constants/field-names";
|
|
|
32
32
|
import { TABLE_KEYS } from "../../../../constants/table-keys";
|
|
33
33
|
import { sortBy } from "../../../../constants/sort";
|
|
34
34
|
import { SIEVE_OP, sieveAnd, sieveFilter, expandSieveParam, } from "../../../../utils/sieve-builder";
|
|
35
|
+
import { ALL_LISTING_TYPES } from "../../../shared/listing-types/feature-flags";
|
|
36
|
+
import { hideDefaultsFor } from "../../../shared/listing-types/_registry";
|
|
35
37
|
/** Vercel Hobby Fluid Compute ceiling (CLAUDE.md Rule #6) — never fetch more at once. */
|
|
36
38
|
export const PUBLIC_PRODUCT_MAX_PAGE_SIZE = 50;
|
|
39
|
+
/**
|
|
40
|
+
* Which "Show X" defaults apply to a set of listing types.
|
|
41
|
+
*
|
|
42
|
+
* THE POINT OF THIS FUNCTION is that SSR and the client refetch call the SAME
|
|
43
|
+
* implementation. `staleTime: Infinity` freezes SSR `initialData` forever, so
|
|
44
|
+
* if the SSR filter-builder computes a different default than the client's
|
|
45
|
+
* bare-URL state, the first paint and every later refetch disagree and one of
|
|
46
|
+
* them is wrong — permanently (Root Cause #30). Two mirrored literals is
|
|
47
|
+
* exactly how that bug is written; one shared function is how it isn't.
|
|
48
|
+
*
|
|
49
|
+
* `/products` now spans all nine listing types, which makes this load-bearing
|
|
50
|
+
* rather than theoretical: a mixed set includes both sold-out-able types
|
|
51
|
+
* (products, art) and time-boxed ones (auctions, pre-orders, prize draws), so
|
|
52
|
+
* BOTH toggles can apply at once.
|
|
53
|
+
*/
|
|
54
|
+
export function defaultTogglesForListingTypes(types) {
|
|
55
|
+
// No explicit set = every type is in play, so both defaults apply.
|
|
56
|
+
const effective = types && types.length > 0 ? types : ALL_LISTING_TYPES;
|
|
57
|
+
const defaults = hideDefaultsFor(effective.filter((t) => ALL_LISTING_TYPES.includes(t)));
|
|
58
|
+
return {
|
|
59
|
+
hideSoldByDefault: defaults.includes("sold"),
|
|
60
|
+
// "ended" and "closed" are the same query mechanic (a date range against
|
|
61
|
+
// the type's own end field); they differ only in the toggle's wording.
|
|
62
|
+
hideEndedByDefault: defaults.includes("ended") || defaults.includes("closed"),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
37
65
|
const DEFAULT_PAGE = 1;
|
|
38
66
|
const DEFAULT_PAGE_SIZE = 24;
|
|
39
67
|
const DEFAULT_SORTS = sortBy(PRODUCT_FIELDS.CREATED_AT);
|
|
@@ -52,6 +80,40 @@ function num(raw) {
|
|
|
52
80
|
const n = Number(raw);
|
|
53
81
|
return Number.isFinite(n) ? n : undefined;
|
|
54
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* Per-type facet URL key → the Firestore field path it filters.
|
|
85
|
+
*
|
|
86
|
+
* Every target is already allowlisted in the products repository's
|
|
87
|
+
* SIEVE_FIELDS — the nested `classified.*` / `digitalCode.*` / `liveItem.*`
|
|
88
|
+
* entries have been there since those types shipped. What was missing was any
|
|
89
|
+
* code path that actually emitted a clause for them: the store listings
|
|
90
|
+
* declared these keys in their FILTER_KEYS (so they counted toward the
|
|
91
|
+
* filter badge) and rendered the controls, but never put them on the wire.
|
|
92
|
+
*/
|
|
93
|
+
const TYPE_FACET_FIELD = {
|
|
94
|
+
[TABLE_KEYS.CITY]: "classified.meetupArea.city",
|
|
95
|
+
[TABLE_KEYS.NEGOTIABLE]: "classified.negotiable",
|
|
96
|
+
[TABLE_KEYS.ACCEPTS_SHIPPING]: "classified.acceptsShipping",
|
|
97
|
+
[TABLE_KEYS.DELIVERY_METHOD]: "digitalCode.codeDeliveryMethod",
|
|
98
|
+
[TABLE_KEYS.SPECIES]: "liveItem.species",
|
|
99
|
+
[TABLE_KEYS.LIVE_SEX]: "liveItem.sex",
|
|
100
|
+
[TABLE_KEYS.JURISDICTION]: "liveItem.jurisdictionAllowed",
|
|
101
|
+
};
|
|
102
|
+
/** Read every known per-type facet present in the params. */
|
|
103
|
+
function readTypeFacets(get) {
|
|
104
|
+
const out = {};
|
|
105
|
+
for (const key of Object.keys(TYPE_FACET_FIELD)) {
|
|
106
|
+
const value = get(key);
|
|
107
|
+
if (value)
|
|
108
|
+
out[key] = value;
|
|
109
|
+
}
|
|
110
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
111
|
+
}
|
|
112
|
+
/** Split a pipe-joined multi-select value; `undefined` when empty. */
|
|
113
|
+
function pipeList(raw) {
|
|
114
|
+
const parts = raw.split("|").map((s) => s.trim()).filter(Boolean);
|
|
115
|
+
return parts.length > 0 ? parts : undefined;
|
|
116
|
+
}
|
|
55
117
|
/**
|
|
56
118
|
* Read the shared public-listing query params. SSR views hand in Next's
|
|
57
119
|
* `searchParams` record; the API route hands in `url.searchParams`. Both produce
|
|
@@ -59,14 +121,19 @@ function num(raw) {
|
|
|
59
121
|
*/
|
|
60
122
|
export function parsePublicProductParams(params, defaults) {
|
|
61
123
|
const get = (k) => first(params, k);
|
|
62
|
-
//
|
|
124
|
+
// The type filter is a multi-select checkbox group, so its URL value is a
|
|
125
|
+
// pipe-joined set (`auction|pre-order`). Selected types NARROW the page's
|
|
126
|
+
// span; anything outside the span is discarded rather than allowed to widen
|
|
127
|
+
// it (an /art URL must not be able to ask for auctions). Nothing selected —
|
|
128
|
+
// or nothing valid selected — means the page's full span.
|
|
63
129
|
const typeParam = get(TABLE_KEYS.LISTING_TYPE);
|
|
64
130
|
const span = defaults?.listingTypes;
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
131
|
+
const selected = typeParam
|
|
132
|
+
.split("|")
|
|
133
|
+
.map((t) => t.trim())
|
|
134
|
+
.filter((t) => t && t !== "All");
|
|
135
|
+
const narrowed = span ? selected.filter((t) => span.includes(t)) : selected;
|
|
136
|
+
const listingTypes = narrowed.length > 0 ? narrowed : span;
|
|
70
137
|
const featuresRaw = get(TABLE_KEYS.FEATURES);
|
|
71
138
|
return {
|
|
72
139
|
listingTypes,
|
|
@@ -85,6 +152,12 @@ export function parsePublicProductParams(params, defaults) {
|
|
|
85
152
|
freeShipping: get(TABLE_KEYS.FREE_SHIPPING) === "true" || undefined,
|
|
86
153
|
isPartOfBundle: get(TABLE_KEYS.IS_PART_OF_BUNDLE) === "true" || undefined,
|
|
87
154
|
features: featuresRaw ? featuresRaw.split("|").filter(Boolean) : undefined,
|
|
155
|
+
// Tags and Sublisting Type were rendered by <ProductFilters> for all three
|
|
156
|
+
// variants and counted toward the active-filter badge, but were never
|
|
157
|
+
// parsed here — so ticking one inflated the badge and changed nothing.
|
|
158
|
+
tags: pipeList(get(TABLE_KEYS.TAGS)),
|
|
159
|
+
sublistingCategoryIds: pipeList(get(TABLE_KEYS.SUBLISTING_CATEGORY)),
|
|
160
|
+
typeFacets: readTypeFacets(get),
|
|
88
161
|
preOrderProductionStatus: get(TABLE_KEYS.PREORDER_STATUS) || get("preOrderStatus") || undefined,
|
|
89
162
|
prizeRevealStatus: get(TABLE_KEYS.PRIZE_REVEAL_STATUS) || undefined,
|
|
90
163
|
// Three spellings of one intent — /products and /art call it "Show sold",
|
|
@@ -178,6 +251,31 @@ function buildFirestoreSafeFilters(input) {
|
|
|
178
251
|
if (input.features?.length === 1) {
|
|
179
252
|
parts.push(sieveFilter(PRODUCT_FIELDS.FEATURES, SIEVE_OP.CONTAINS, input.features[0]));
|
|
180
253
|
}
|
|
254
|
+
// `tags` is an array field, so the same one-value limit applies.
|
|
255
|
+
if (input.tags?.length === 1) {
|
|
256
|
+
parts.push(sieveFilter(PRODUCT_FIELDS.TAGS, SIEVE_OP.CONTAINS, input.tags[0]));
|
|
257
|
+
}
|
|
258
|
+
// A scalar field, so several values ARE expressible as an OR-group (which
|
|
259
|
+
// the Firebase adapter upgrades to `in`).
|
|
260
|
+
if (input.sublistingCategoryIds?.length) {
|
|
261
|
+
parts.push(sieveFilter(PRODUCT_FIELDS.SUBLISTING_CATEGORY_ID, SIEVE_OP.EQ, input.sublistingCategoryIds.join("|")));
|
|
262
|
+
}
|
|
263
|
+
// Per-type facets. Unknown keys can't reach here — `readTypeFacets` only
|
|
264
|
+
// reads the ones in TYPE_FACET_FIELD, so an arbitrary query param can never
|
|
265
|
+
// be turned into a Firestore field path.
|
|
266
|
+
for (const [key, raw] of Object.entries(input.typeFacets ?? {})) {
|
|
267
|
+
const field = TYPE_FACET_FIELD[key];
|
|
268
|
+
if (!field || !raw)
|
|
269
|
+
continue;
|
|
270
|
+
// `jurisdictionAllowed` is an array field — array-contains, not equality.
|
|
271
|
+
const op = field.endsWith("jurisdictionAllowed") ? SIEVE_OP.CONTAINS : SIEVE_OP.EQ;
|
|
272
|
+
const values = raw.split("|").filter(Boolean);
|
|
273
|
+
if (values.length === 0)
|
|
274
|
+
continue;
|
|
275
|
+
parts.push(op === SIEVE_OP.CONTAINS
|
|
276
|
+
? sieveFilter(field, op, values[0])
|
|
277
|
+
: sieveFilter(field, op, values.join("|")));
|
|
278
|
+
}
|
|
181
279
|
if (input.rawFilters)
|
|
182
280
|
parts.push(input.rawFilters);
|
|
183
281
|
return sieveAnd(...parts.filter(Boolean));
|
|
@@ -320,6 +418,33 @@ export async function listPublicProducts(input, opts) {
|
|
|
320
418
|
sorts,
|
|
321
419
|
};
|
|
322
420
|
}
|
|
421
|
+
/**
|
|
422
|
+
* One store tab's worth of listings.
|
|
423
|
+
*
|
|
424
|
+
* Every `Store*PageView` used to hand-roll this: `productRepository.list()`
|
|
425
|
+
* with an inline `sieveAnd(...)` and a `.catch(() => null)`. That is the exact
|
|
426
|
+
* shape that made `/art` render empty — an unsafe inequality (or any missing
|
|
427
|
+
* index) becomes FAILED_PRECONDITION, the catch turns it into `null`, and the
|
|
428
|
+
* page shows "no results" with nothing logged. Eight views carried it, and
|
|
429
|
+
* `audit-listing-filter-parity` had none of them registered.
|
|
430
|
+
*
|
|
431
|
+
* Routing them through `listPublicProducts` also gives every store tab
|
|
432
|
+
* something it never had: the URL's own sort/filter/page params reach the SSR
|
|
433
|
+
* fetch, so the first paint matches what the toolbar says is selected.
|
|
434
|
+
*/
|
|
435
|
+
export async function listStoreProducts(storeId, listingTypes, searchParams = {}, defaults) {
|
|
436
|
+
return listPublicProducts({
|
|
437
|
+
...parsePublicProductParams(searchParams, {
|
|
438
|
+
listingTypes,
|
|
439
|
+
pageSize: defaults?.pageSize ?? DEFAULT_PAGE_SIZE,
|
|
440
|
+
sorts: defaults?.sorts,
|
|
441
|
+
...defaultTogglesForListingTypes(listingTypes),
|
|
442
|
+
}),
|
|
443
|
+
// The route owns the store identity — a `?storeId=` in the URL must not be
|
|
444
|
+
// able to make one store's tab render another store's inventory.
|
|
445
|
+
storeId,
|
|
446
|
+
});
|
|
447
|
+
}
|
|
323
448
|
function sortRows(rows, field, desc) {
|
|
324
449
|
return [...rows].sort((a, b) => {
|
|
325
450
|
const av = a[field];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const getReviewsForProduct: (productId: string) => Promise<never[] | import("../../../..").ReviewDocument[]>;
|
|
1
|
+
export declare const getReviewsForProduct: (productId: string, limit?: number) => Promise<never[] | import("../../../..").ReviewDocument[]>;
|
|
2
2
|
export declare const getReviewsForStore: (storeId: string, page?: number, pageSize?: number) => Promise<import("../../../..").FirebaseSieveResult<import("../../../..").ReviewDocument> | {
|
|
3
3
|
items: never[];
|
|
4
4
|
total: number;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { cache } from "react";
|
|
2
2
|
import { reviewRepository, orderRepository } from "../../../../repositories";
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
import { REVIEWS_DETAIL_PAGE_SIZE } from "../../../shared/features/reviews/config";
|
|
4
|
+
export const getReviewsForProduct = cache(async (productId, limit = REVIEWS_DETAIL_PAGE_SIZE) => {
|
|
5
|
+
return reviewRepository.findApprovedByProduct(productId, limit).catch(() => []);
|
|
5
6
|
});
|
|
6
7
|
export const getReviewsForStore = cache(async (storeId, page = 1, pageSize = 20) => {
|
|
7
8
|
return reviewRepository.listForStore(storeId, { page, pageSize }).catch(() => ({
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
* created as fully public/active documents so testers exercise the real browse/search/
|
|
4
4
|
* checkout code paths, but must stay invisible to non-testers. Filtering happens at the
|
|
5
5
|
* application layer (post-fetch, in-memory) rather than via a Firestore `where` clause,
|
|
6
|
-
* because `isTestData` is a new optional field — a Firestore inequality filter
|
|
7
|
-
* (
|
|
8
|
-
*
|
|
6
|
+
* because `isTestData` is a new optional field — a Firestore inequality filter on it
|
|
7
|
+
* (a `!=` clause) would silently exclude every pre-existing document that doesn't have
|
|
8
|
+
* the field set at all, which is worse than the problem it solves.
|
|
9
9
|
*/
|
|
10
10
|
import { isAdminUser } from "../../../../features/auth/role-predicates";
|
|
11
11
|
/** True when the viewer is allowed to see isTestData:true documents. */
|
|
@@ -2,12 +2,15 @@ import { bidRepository, cartRepository, productRepository, storeRepository, } fr
|
|
|
2
2
|
import { sendNotification } from "../../../../features/admin/actions/notification-actions";
|
|
3
3
|
import { AUCTION_MESSAGES } from "../handlers/messages";
|
|
4
4
|
import { ROUTES } from "../../../../next/routing/route-map";
|
|
5
|
-
import { CART_LANE } from "../../../shared/checkout/lanes";
|
|
5
|
+
import { CART_LANE, LANE_CHECKOUT_WINDOW_MS } from "../../../shared/checkout/lanes";
|
|
6
6
|
/**
|
|
7
7
|
* How long the winner has to pay before the locked cart line lapses. Mirrors
|
|
8
8
|
* the accepted-offer window so both locked lanes behave the same.
|
|
9
9
|
*/
|
|
10
|
-
|
|
10
|
+
// Was a local `48 * 60 * 60 * 1000` here AND an identical one in bid-actions.ts
|
|
11
|
+
// (the Buy-Now path), with nothing linking them — changing one would have given
|
|
12
|
+
// the same kind of win two different deadlines depending on how it was won.
|
|
13
|
+
const AUCTION_CHECKOUT_WINDOW_MS = LANE_CHECKOUT_WINDOW_MS.auction;
|
|
11
14
|
/** Deep link the winner straight into the auction checkout lane. */
|
|
12
15
|
const AUCTION_CHECKOUT_URL = `${String(ROUTES.USER.CHECKOUT)}?lane=${CART_LANE.AUCTION}`;
|
|
13
16
|
async function settleAuction(ctx, product) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { JobContext } from "../runtime/types";
|
|
2
|
-
export declare const SANDBOX_COLLECTIONS: readonly ["categories", "stores", "products", "blogPosts", "events"];
|
|
2
|
+
export declare const SANDBOX_COLLECTIONS: readonly ["categories", "stores", "products", "blogPosts", "events", "offers"];
|
|
3
3
|
export interface TesterSandboxCleanupOptions {
|
|
4
4
|
/** true = wipe every isTestData:true doc regardless of testDataExpiresAt (manual force-purge). */
|
|
5
5
|
force?: boolean;
|
|
@@ -3,7 +3,10 @@ import { BID_FIELDS } from "../../../../constants/field-names";
|
|
|
3
3
|
// Exported so testerSandboxRefresh.ts (the every-4h revert+prune job) reuses
|
|
4
4
|
// the exact same collection scope instead of redefining it — one source of
|
|
5
5
|
// truth for "what counts as sandbox scope."
|
|
6
|
-
|
|
6
|
+
// Keep in sync with testerSandboxRefresh.ts's SEED_BY_COLLECTION — the daily
|
|
7
|
+
// TTL cleanup and the 4-hourly revert+prune must agree on what "sandbox" means
|
|
8
|
+
// (CLAUDE.md § Tester QA standards #3).
|
|
9
|
+
export const SANDBOX_COLLECTIONS = ["categories", "stores", "products", "blogPosts", "events", "offers"];
|
|
7
10
|
const BID_CHUNK_SIZE = 30; // Firestore `in` query cap
|
|
8
11
|
export async function runTesterSandboxCleanup(ctx, opts = {}) {
|
|
9
12
|
const cutoff = opts.force ? null : new Date();
|
|
@@ -8,6 +8,7 @@ import { storesTesterSeedData } from "../../../../features/tester/seed-data/stor
|
|
|
8
8
|
import { productsTesterSeedData } from "../../../../features/tester/seed-data/products-tester-seed-data";
|
|
9
9
|
import { blogTesterSeedData } from "../../../../features/tester/seed-data/blog-tester-seed-data";
|
|
10
10
|
import { eventsTesterSeedData } from "../../../../features/tester/seed-data/events-tester-seed-data";
|
|
11
|
+
import { offersTesterSeedData } from "../../../../features/tester/seed-data/offers-tester-seed-data";
|
|
11
12
|
import { bidsTesterSeedData } from "../../../../features/tester/seed-data/bids-tester-seed-data";
|
|
12
13
|
const SEED_BY_COLLECTION = {
|
|
13
14
|
categories: categoriesTesterSeedData,
|
|
@@ -15,6 +16,7 @@ const SEED_BY_COLLECTION = {
|
|
|
15
16
|
products: productsTesterSeedData,
|
|
16
17
|
blogPosts: blogTesterSeedData,
|
|
17
18
|
events: eventsTesterSeedData,
|
|
19
|
+
offers: offersTesterSeedData,
|
|
18
20
|
};
|
|
19
21
|
const BID_CHUNK_SIZE = 30; // Firestore `in` query cap
|
|
20
22
|
/**
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Shared helpers for job handlers.
|
|
3
3
|
*/
|
|
4
4
|
import { getAdminRealtimeDb } from "../../../../providers/db-firebase";
|
|
5
|
+
import { PRODUCT_FIELDS } from "../../../../constants/field-names";
|
|
5
6
|
import { BATCH_LIMIT } from "./messages";
|
|
6
7
|
/** Batch-delete document refs, respecting Firestore's 500-write batch limit. */
|
|
7
8
|
export async function batchDelete(ctx, refs) {
|
|
@@ -23,7 +24,9 @@ export { getAdminRealtimeDb };
|
|
|
23
24
|
* only the ones past testDataExpiresAt (the scheduled 7-day sweep).
|
|
24
25
|
*/
|
|
25
26
|
export async function getTestDataRefs(db, collection, cutoff) {
|
|
26
|
-
let q = db
|
|
27
|
+
let q = db
|
|
28
|
+
.collection(collection)
|
|
29
|
+
.where(PRODUCT_FIELDS.IS_TEST_DATA, "==", true);
|
|
27
30
|
if (cutoff)
|
|
28
31
|
q = q.where("testDataExpiresAt", "<=", cutoff);
|
|
29
32
|
const snap = await q.limit(200).get();
|
|
@@ -11,6 +11,26 @@ export type CartLane = (typeof CART_LANE)[keyof typeof CART_LANE];
|
|
|
11
11
|
*/
|
|
12
12
|
export declare const CART_LANE_PRIORITY: readonly CartLane[];
|
|
13
13
|
export declare const CART_LANE_LABELS: Record<CartLane, string>;
|
|
14
|
+
/**
|
|
15
|
+
* How long a locked line survives before it lapses, per lane.
|
|
16
|
+
*
|
|
17
|
+
* Lives here, with the lane model, because the deadline is a property OF the
|
|
18
|
+
* lane — and because `AUCTION_CHECKOUT_WINDOW_MS` was previously declared twice
|
|
19
|
+
* (`auctionSettlement.ts` and `bid-actions.ts`, i.e. the settlement path and the
|
|
20
|
+
* Buy-Now path) with no link between them: changing one would silently give the
|
|
21
|
+
* same kind of win two different deadlines depending on how it was won.
|
|
22
|
+
*
|
|
23
|
+
* The two windows are equal today but mean different things, so they stay
|
|
24
|
+
* separate names rather than one shared constant:
|
|
25
|
+
* - **auction** — payment is MANDATORY. You bid, you won, you owe it. Past the
|
|
26
|
+
* deadline the bid is forfeited and the buyer is warned that repeated
|
|
27
|
+
* non-payment restricts their account.
|
|
28
|
+
* - **offer** — payment is OPTIONAL. Declining to buy at your own offer price
|
|
29
|
+
* is the buyer's right; the offer simply lapses with no penalty.
|
|
30
|
+
*/
|
|
31
|
+
export declare const LANE_CHECKOUT_WINDOW_MS: Record<"auction" | "offer", number>;
|
|
32
|
+
/** 2 days, in hours — for buyer-facing copy ("pay within 48 hours"). */
|
|
33
|
+
export declare const LANE_CHECKOUT_WINDOW_HOURS: Record<"auction" | "offer", number>;
|
|
14
34
|
/** The two lanes whose price is fixed outside the listing. */
|
|
15
35
|
export declare function isLockedLane(lane: CartLane): boolean;
|
|
16
36
|
/** Minimal shape this module needs — works on cart documents and client rows alike. */
|
|
@@ -44,6 +44,34 @@ export const CART_LANE_LABELS = {
|
|
|
44
44
|
offer: "Accepted offers",
|
|
45
45
|
standard: "Cart",
|
|
46
46
|
};
|
|
47
|
+
/**
|
|
48
|
+
* How long a locked line survives before it lapses, per lane.
|
|
49
|
+
*
|
|
50
|
+
* Lives here, with the lane model, because the deadline is a property OF the
|
|
51
|
+
* lane — and because `AUCTION_CHECKOUT_WINDOW_MS` was previously declared twice
|
|
52
|
+
* (`auctionSettlement.ts` and `bid-actions.ts`, i.e. the settlement path and the
|
|
53
|
+
* Buy-Now path) with no link between them: changing one would silently give the
|
|
54
|
+
* same kind of win two different deadlines depending on how it was won.
|
|
55
|
+
*
|
|
56
|
+
* The two windows are equal today but mean different things, so they stay
|
|
57
|
+
* separate names rather than one shared constant:
|
|
58
|
+
* - **auction** — payment is MANDATORY. You bid, you won, you owe it. Past the
|
|
59
|
+
* deadline the bid is forfeited and the buyer is warned that repeated
|
|
60
|
+
* non-payment restricts their account.
|
|
61
|
+
* - **offer** — payment is OPTIONAL. Declining to buy at your own offer price
|
|
62
|
+
* is the buyer's right; the offer simply lapses with no penalty.
|
|
63
|
+
*/
|
|
64
|
+
export const LANE_CHECKOUT_WINDOW_MS = {
|
|
65
|
+
/** 2 days. Mandatory — enforced by the expiry sweep, which forfeits the bid. */
|
|
66
|
+
auction: 48 * 60 * 60 * 1000,
|
|
67
|
+
/** 2 days. Optional — lapses quietly, no penalty. */
|
|
68
|
+
offer: 48 * 60 * 60 * 1000,
|
|
69
|
+
};
|
|
70
|
+
/** 2 days, in hours — for buyer-facing copy ("pay within 48 hours"). */
|
|
71
|
+
export const LANE_CHECKOUT_WINDOW_HOURS = {
|
|
72
|
+
auction: LANE_CHECKOUT_WINDOW_MS.auction / (60 * 60 * 1000),
|
|
73
|
+
offer: LANE_CHECKOUT_WINDOW_MS.offer / (60 * 60 * 1000),
|
|
74
|
+
};
|
|
47
75
|
/** The two lanes whose price is fixed outside the listing. */
|
|
48
76
|
export function isLockedLane(lane) {
|
|
49
77
|
return lane === CART_LANE.AUCTION || lane === CART_LANE.OFFER;
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
export declare const CHECKOUT_DEFAULT_COMMISSIONS: {
|
|
2
|
+
readonly platformFeePercent: 5;
|
|
3
|
+
readonly gstPercent: 18;
|
|
4
|
+
readonly minimumTransactionFee: 0;
|
|
5
|
+
readonly platformFeeMax: 10;
|
|
2
6
|
readonly codDepositPercent: 10;
|
|
3
7
|
readonly codHandlingFeeMin: 200;
|
|
4
8
|
readonly codHandlingFeePercent: 10;
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
export const CHECKOUT_DEFAULT_COMMISSIONS = {
|
|
2
|
+
// Platform-commission rates. Previously absent here because no checkout path
|
|
3
|
+
// computed a buyer-facing platform fee — the moment one did, a siteSettings
|
|
4
|
+
// document that failed to load would have produced NaN fees rather than a
|
|
5
|
+
// sane fallback. Mirrors the SiteSettingsDocument defaults.
|
|
6
|
+
platformFeePercent: 5,
|
|
7
|
+
gstPercent: 18,
|
|
8
|
+
minimumTransactionFee: 0,
|
|
9
|
+
platformFeeMax: 10,
|
|
2
10
|
codDepositPercent: 10,
|
|
3
11
|
codHandlingFeeMin: 200,
|
|
4
12
|
codHandlingFeePercent: 10,
|
|
@@ -23,7 +23,6 @@ export declare const createCouponSchema: z.ZodObject<{
|
|
|
23
23
|
name: z.ZodString;
|
|
24
24
|
type: z.ZodEnum<["percentage", "fixed", "free_shipping", "buy_x_get_y"]>;
|
|
25
25
|
scope: z.ZodEnum<["admin", "seller"]>;
|
|
26
|
-
sellerId: z.ZodOptional<z.ZodString>;
|
|
27
26
|
discount: z.ZodObject<{
|
|
28
27
|
value: z.ZodNumber;
|
|
29
28
|
maxDiscount: z.ZodOptional<z.ZodNumber>;
|
|
@@ -62,17 +61,14 @@ export declare const createCouponSchema: z.ZodObject<{
|
|
|
62
61
|
}>;
|
|
63
62
|
restrictions: z.ZodOptional<z.ZodObject<{
|
|
64
63
|
firstTimeUserOnly: z.ZodDefault<z.ZodBoolean>;
|
|
65
|
-
combineWithSellerCoupons: z.ZodDefault<z.ZodBoolean>;
|
|
66
64
|
applicableProducts: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
67
65
|
applicableCategories: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
68
66
|
}, "strip", z.ZodTypeAny, {
|
|
69
67
|
firstTimeUserOnly: boolean;
|
|
70
|
-
combineWithSellerCoupons: boolean;
|
|
71
68
|
applicableProducts?: string[] | undefined;
|
|
72
69
|
applicableCategories?: string[] | undefined;
|
|
73
70
|
}, {
|
|
74
71
|
firstTimeUserOnly?: boolean | undefined;
|
|
75
|
-
combineWithSellerCoupons?: boolean | undefined;
|
|
76
72
|
applicableProducts?: string[] | undefined;
|
|
77
73
|
applicableCategories?: string[] | undefined;
|
|
78
74
|
}>>;
|
|
@@ -95,10 +91,8 @@ export declare const createCouponSchema: z.ZodObject<{
|
|
|
95
91
|
endDate: string;
|
|
96
92
|
startDate: string;
|
|
97
93
|
};
|
|
98
|
-
sellerId?: string | undefined;
|
|
99
94
|
restrictions?: {
|
|
100
95
|
firstTimeUserOnly: boolean;
|
|
101
|
-
combineWithSellerCoupons: boolean;
|
|
102
96
|
applicableProducts?: string[] | undefined;
|
|
103
97
|
applicableCategories?: string[] | undefined;
|
|
104
98
|
} | undefined;
|
|
@@ -121,10 +115,8 @@ export declare const createCouponSchema: z.ZodObject<{
|
|
|
121
115
|
startDate: string;
|
|
122
116
|
isActive?: boolean | undefined;
|
|
123
117
|
};
|
|
124
|
-
sellerId?: string | undefined;
|
|
125
118
|
restrictions?: {
|
|
126
119
|
firstTimeUserOnly?: boolean | undefined;
|
|
127
|
-
combineWithSellerCoupons?: boolean | undefined;
|
|
128
120
|
applicableProducts?: string[] | undefined;
|
|
129
121
|
applicableCategories?: string[] | undefined;
|
|
130
122
|
} | undefined;
|
|
@@ -134,7 +126,6 @@ export declare const updateCouponSchema: z.ZodObject<Omit<{
|
|
|
134
126
|
name: z.ZodOptional<z.ZodString>;
|
|
135
127
|
type: z.ZodOptional<z.ZodEnum<["percentage", "fixed", "free_shipping", "buy_x_get_y"]>>;
|
|
136
128
|
scope: z.ZodOptional<z.ZodEnum<["admin", "seller"]>>;
|
|
137
|
-
sellerId: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
138
129
|
discount: z.ZodOptional<z.ZodObject<{
|
|
139
130
|
value: z.ZodNumber;
|
|
140
131
|
maxDiscount: z.ZodOptional<z.ZodNumber>;
|
|
@@ -173,17 +164,14 @@ export declare const updateCouponSchema: z.ZodObject<Omit<{
|
|
|
173
164
|
}>>;
|
|
174
165
|
restrictions: z.ZodOptional<z.ZodOptional<z.ZodObject<{
|
|
175
166
|
firstTimeUserOnly: z.ZodDefault<z.ZodBoolean>;
|
|
176
|
-
combineWithSellerCoupons: z.ZodDefault<z.ZodBoolean>;
|
|
177
167
|
applicableProducts: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
178
168
|
applicableCategories: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
179
169
|
}, "strip", z.ZodTypeAny, {
|
|
180
170
|
firstTimeUserOnly: boolean;
|
|
181
|
-
combineWithSellerCoupons: boolean;
|
|
182
171
|
applicableProducts?: string[] | undefined;
|
|
183
172
|
applicableCategories?: string[] | undefined;
|
|
184
173
|
}, {
|
|
185
174
|
firstTimeUserOnly?: boolean | undefined;
|
|
186
|
-
combineWithSellerCoupons?: boolean | undefined;
|
|
187
175
|
applicableProducts?: string[] | undefined;
|
|
188
176
|
applicableCategories?: string[] | undefined;
|
|
189
177
|
}>>>;
|
|
@@ -191,7 +179,6 @@ export declare const updateCouponSchema: z.ZodObject<Omit<{
|
|
|
191
179
|
type?: "fixed" | "percentage" | "free_shipping" | "buy_x_get_y" | undefined;
|
|
192
180
|
scope?: "seller" | "admin" | undefined;
|
|
193
181
|
name?: string | undefined;
|
|
194
|
-
sellerId?: string | undefined;
|
|
195
182
|
discount?: {
|
|
196
183
|
value: number;
|
|
197
184
|
minPurchase: number;
|
|
@@ -208,7 +195,6 @@ export declare const updateCouponSchema: z.ZodObject<Omit<{
|
|
|
208
195
|
} | undefined;
|
|
209
196
|
restrictions?: {
|
|
210
197
|
firstTimeUserOnly: boolean;
|
|
211
|
-
combineWithSellerCoupons: boolean;
|
|
212
198
|
applicableProducts?: string[] | undefined;
|
|
213
199
|
applicableCategories?: string[] | undefined;
|
|
214
200
|
} | undefined;
|
|
@@ -216,7 +202,6 @@ export declare const updateCouponSchema: z.ZodObject<Omit<{
|
|
|
216
202
|
type?: "fixed" | "percentage" | "free_shipping" | "buy_x_get_y" | undefined;
|
|
217
203
|
scope?: "seller" | "admin" | undefined;
|
|
218
204
|
name?: string | undefined;
|
|
219
|
-
sellerId?: string | undefined;
|
|
220
205
|
discount?: {
|
|
221
206
|
value: number;
|
|
222
207
|
maxDiscount?: number | undefined;
|
|
@@ -233,7 +218,6 @@ export declare const updateCouponSchema: z.ZodObject<Omit<{
|
|
|
233
218
|
} | undefined;
|
|
234
219
|
restrictions?: {
|
|
235
220
|
firstTimeUserOnly?: boolean | undefined;
|
|
236
|
-
combineWithSellerCoupons?: boolean | undefined;
|
|
237
221
|
applicableProducts?: string[] | undefined;
|
|
238
222
|
applicableCategories?: string[] | undefined;
|
|
239
223
|
} | undefined;
|