@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
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { OrderCard, OrdersList } from "./OrdersList";
|
|
2
|
+
export { OrderAddonBadges, } from "./OrderAddonBadges";
|
|
2
3
|
export { MarketplaceOrderCard, } from "./MarketplaceOrderCard";
|
|
3
4
|
export { OrderFilters, ORDER_FILTER_KEYS, ORDER_ADMIN_SORT_OPTIONS, ORDER_SELLER_SORT_OPTIONS, ORDER_USER_SORT_OPTIONS, getOrderFilterKeys, getOrderSortOptions, } from "./OrderFilters";
|
|
4
5
|
export { OrderSiblingPayments } from "./OrderSiblingPayments";
|
|
@@ -101,6 +101,22 @@ declare class OrderRepository extends BaseRepository<OrderDocument> {
|
|
|
101
101
|
canFilter: boolean;
|
|
102
102
|
canSort: boolean;
|
|
103
103
|
};
|
|
104
|
+
whatsappNotifyAddon: {
|
|
105
|
+
canFilter: boolean;
|
|
106
|
+
canSort: boolean;
|
|
107
|
+
};
|
|
108
|
+
giftWrapAddon: {
|
|
109
|
+
canFilter: boolean;
|
|
110
|
+
canSort: boolean;
|
|
111
|
+
};
|
|
112
|
+
shipmentProtectionAddon: {
|
|
113
|
+
canFilter: boolean;
|
|
114
|
+
canSort: boolean;
|
|
115
|
+
};
|
|
116
|
+
couponCode: {
|
|
117
|
+
canFilter: boolean;
|
|
118
|
+
canSort: boolean;
|
|
119
|
+
};
|
|
104
120
|
};
|
|
105
121
|
listForSeller(productIds: string[], model: SieveModel): Promise<FirebaseSieveResult<OrderDocument>>;
|
|
106
122
|
listForUser(userId: string, model: SieveModel): Promise<FirebaseSieveResult<OrderDocument>>;
|
|
@@ -175,6 +191,26 @@ declare class OrderRepository extends BaseRepository<OrderDocument> {
|
|
|
175
191
|
canFilter: boolean;
|
|
176
192
|
canSort: boolean;
|
|
177
193
|
};
|
|
194
|
+
orderType: {
|
|
195
|
+
canFilter: boolean;
|
|
196
|
+
canSort: boolean;
|
|
197
|
+
};
|
|
198
|
+
whatsappNotifyAddon: {
|
|
199
|
+
canFilter: boolean;
|
|
200
|
+
canSort: boolean;
|
|
201
|
+
};
|
|
202
|
+
giftWrapAddon: {
|
|
203
|
+
canFilter: boolean;
|
|
204
|
+
canSort: boolean;
|
|
205
|
+
};
|
|
206
|
+
shipmentProtectionAddon: {
|
|
207
|
+
canFilter: boolean;
|
|
208
|
+
canSort: boolean;
|
|
209
|
+
};
|
|
210
|
+
couponCode: {
|
|
211
|
+
canFilter: boolean;
|
|
212
|
+
canSort: boolean;
|
|
213
|
+
};
|
|
178
214
|
};
|
|
179
215
|
listAll(model: SieveModel): Promise<FirebaseSieveResult<OrderDocument>>;
|
|
180
216
|
/**
|
|
@@ -496,6 +496,13 @@ OrderRepository.SELLER_SIEVE_FIELDS = {
|
|
|
496
496
|
orderDate: { canFilter: true, canSort: true, parseValue: parseSieveDateValue },
|
|
497
497
|
createdAt: { canFilter: true, canSort: true, parseValue: parseSieveDateValue },
|
|
498
498
|
contestable: { canFilter: true, canSort: false },
|
|
499
|
+
// Fulfilment flags. Filterable so "which of my orders need gift wrap?" is a
|
|
500
|
+
// query against the orders themselves — the whole point of recording the
|
|
501
|
+
// add-on on the order rather than keeping a side list of who asked for what.
|
|
502
|
+
whatsappNotifyAddon: { canFilter: true, canSort: false },
|
|
503
|
+
giftWrapAddon: { canFilter: true, canSort: false },
|
|
504
|
+
shipmentProtectionAddon: { canFilter: true, canSort: false },
|
|
505
|
+
couponCode: { canFilter: true, canSort: false },
|
|
499
506
|
};
|
|
500
507
|
OrderRepository.ADMIN_SIEVE_FIELDS = {
|
|
501
508
|
id: { canFilter: true, canSort: false },
|
|
@@ -516,6 +523,17 @@ OrderRepository.ADMIN_SIEVE_FIELDS = {
|
|
|
516
523
|
// S-SBUNI-RULES 2026-05-13 — refund + batch fields
|
|
517
524
|
paymentBatchId: { canFilter: true, canSort: false },
|
|
518
525
|
contestable: { canFilter: true, canSort: false },
|
|
526
|
+
// Lane segregation on /user/orders — "standard"/"auction"/"offer" etc.
|
|
527
|
+
// Never filtered as "standard" via Firestore == (see listForUser callers):
|
|
528
|
+
// orders written before this field existed have no value to match.
|
|
529
|
+
orderType: { canFilter: true, canSort: false },
|
|
530
|
+
// Same fulfilment flags as SELLER_SIEVE_FIELDS — admin needs to answer
|
|
531
|
+
// "who opted into WhatsApp updates?" when a status change goes out, without
|
|
532
|
+
// maintaining a parallel tracker.
|
|
533
|
+
whatsappNotifyAddon: { canFilter: true, canSort: false },
|
|
534
|
+
giftWrapAddon: { canFilter: true, canSort: false },
|
|
535
|
+
shipmentProtectionAddon: { canFilter: true, canSort: false },
|
|
536
|
+
couponCode: { canFilter: true, canSort: false },
|
|
519
537
|
};
|
|
520
538
|
const orderRepository = new OrderRepository();
|
|
521
539
|
export { OrderRepository, orderRepository };
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { type PaymentGateway, PaymentGatewayValues } from "../../payments/schemas";
|
|
2
2
|
export { type PaymentGateway, PaymentGatewayValues };
|
|
3
3
|
import type { ListingType } from "../../products/types/index";
|
|
4
|
+
import type { OrderType } from "../utils/order-splitter";
|
|
5
|
+
import type { AppliedOrderDiscount } from "../schemas/firestore";
|
|
4
6
|
export type PaymentStatus = "pending" | "processing" | "paid" | "failed" | "refunded" | "partial_refund";
|
|
5
7
|
export interface UserAddress {
|
|
6
8
|
id: string;
|
|
@@ -57,6 +59,13 @@ export interface Order {
|
|
|
57
59
|
address: UserAddress;
|
|
58
60
|
orderStatus: OrderStatus;
|
|
59
61
|
paymentStatus: PaymentStatus;
|
|
62
|
+
/**
|
|
63
|
+
* Which checkout lane produced this order. Absent on orders written before
|
|
64
|
+
* the field existed — treat missing as `"standard"`, never filter it out.
|
|
65
|
+
*/
|
|
66
|
+
orderType?: OrderType;
|
|
67
|
+
/** Set when this order settled an accepted Make-an-Offer. */
|
|
68
|
+
offerId?: string;
|
|
60
69
|
paymentGateway?: PaymentGateway;
|
|
61
70
|
subtotal: number;
|
|
62
71
|
shippingCost?: number;
|
|
@@ -64,7 +73,36 @@ export interface Order {
|
|
|
64
73
|
tax?: number;
|
|
65
74
|
total: number;
|
|
66
75
|
currency: string;
|
|
76
|
+
/**
|
|
77
|
+
* First applied coupon's code only. Kept for back-compat; prefer
|
|
78
|
+
* `appliedDiscounts` when rendering, since a cart may stack one coupon per
|
|
79
|
+
* store plus one platform-wide coupon and this scalar shows only one of them.
|
|
80
|
+
*/
|
|
67
81
|
couponCode?: string;
|
|
82
|
+
/** Total rupee value of every discount applied to this order. */
|
|
83
|
+
couponDiscount?: number;
|
|
84
|
+
/** Every discount applied to this order — the full stack, not just the first. */
|
|
85
|
+
appliedDiscounts?: AppliedOrderDiscount[];
|
|
86
|
+
/**
|
|
87
|
+
* Paid add-ons the buyer opted into for THIS order (= this store).
|
|
88
|
+
*
|
|
89
|
+
* These are operational flags, not just billing records: `whatsappNotifyAddon`
|
|
90
|
+
* is what the status-change notifier checks to decide whether to send a
|
|
91
|
+
* WhatsApp message, and `giftWrapAddon`/`shipmentProtectionAddon` are what
|
|
92
|
+
* the packer needs to see before dispatch. They live on the order itself and
|
|
93
|
+
* are filterable (`orders` SIEVE_FIELDS) precisely so nobody has to keep a
|
|
94
|
+
* separate list of who asked for what.
|
|
95
|
+
*/
|
|
96
|
+
whatsappNotifyAddon?: boolean;
|
|
97
|
+
whatsappNotifyFee?: number;
|
|
98
|
+
giftWrapAddon?: boolean;
|
|
99
|
+
giftWrapFee?: number;
|
|
100
|
+
/** The buyer's gift message — the packer has to physically include this. */
|
|
101
|
+
giftWrapMessage?: string;
|
|
102
|
+
shipmentProtectionAddon?: boolean;
|
|
103
|
+
shipmentProtectionFee?: number;
|
|
104
|
+
/** Buyer-facing platform commission apportioned to this order. */
|
|
105
|
+
platformFee?: number;
|
|
68
106
|
trackingNumber?: string;
|
|
69
107
|
shippingCarrier?: string;
|
|
70
108
|
/** Carrier tracking page URL — rendered as an external link that opens in a new tab. No live-tracking API integration; this is just the passthrough URL a seller/admin entered. */
|
|
@@ -2,4 +2,4 @@ export interface PreOrderBottomActionsProps {
|
|
|
2
2
|
price: number | null;
|
|
3
3
|
currency: string;
|
|
4
4
|
}
|
|
5
|
-
export declare function PreOrderBottomActions({ price, currency, }: PreOrderBottomActionsProps):
|
|
5
|
+
export declare function PreOrderBottomActions({ price, currency, }: PreOrderBottomActionsProps): import("react").JSX.Element;
|
|
@@ -1,22 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { ACTION_ID } from "../../products/constants/action-defs";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { ListingBottomActions } from "../../products/components/ListingBottomActions";
|
|
5
4
|
export function PreOrderBottomActions({ price, currency, }) {
|
|
6
|
-
|
|
7
|
-
actions: [
|
|
8
|
-
{
|
|
9
|
-
id: ACTION_ID.RESERVE_NOW,
|
|
10
|
-
label: "Reserve Now",
|
|
11
|
-
variant: "primary",
|
|
12
|
-
onClick: () => {
|
|
13
|
-
document
|
|
14
|
-
.getElementById("pre-order-buy-bar")
|
|
15
|
-
?.scrollIntoView({ behavior: "smooth" });
|
|
16
|
-
},
|
|
17
|
-
},
|
|
18
|
-
],
|
|
19
|
-
infoLabel: price !== null ? formatCurrency(price, currency) : undefined,
|
|
20
|
-
});
|
|
21
|
-
return null;
|
|
5
|
+
return (_jsx(ListingBottomActions, { listingType: "preorder", anchorId: "pre-order-buy-bar", price: price, currency: currency }));
|
|
22
6
|
}
|
|
@@ -4,9 +4,8 @@ import { AdSlot } from "../../homepage/components/AdSlot";
|
|
|
4
4
|
import { ProductsIndexListing } from "./ProductsIndexListing";
|
|
5
5
|
import { PRODUCT_FIELDS } from "../../../constants/field-names";
|
|
6
6
|
import { sortBy } from "../../../constants/sort";
|
|
7
|
-
import { ART_STICKERS_TYPE_FILTER_TABS } from "../constants/listing-tabs";
|
|
8
|
-
import { listPublicProducts, parsePublicProductParams, } from "../../../_internal/server/features/products/list-public";
|
|
9
|
-
const ART_STICKERS_LISTING_TYPES = ["art", "stickers"];
|
|
7
|
+
import { ART_STICKERS_TYPE_FILTER_TABS, ART_STICKERS_LISTING_TYPES, } from "../constants/listing-tabs";
|
|
8
|
+
import { listPublicProducts, parsePublicProductParams, defaultTogglesForListingTypes, } from "../../../_internal/server/features/products/list-public";
|
|
10
9
|
const DEFAULT_PAGE_SIZE = 24;
|
|
11
10
|
const DEFAULT_SORT = sortBy(PRODUCT_FIELDS.CREATED_AT);
|
|
12
11
|
export async function ArtStickersListView({ searchParams = {} }) {
|
|
@@ -17,7 +16,9 @@ export async function ArtStickersListView({ searchParams = {} }) {
|
|
|
17
16
|
listingTypes: ART_STICKERS_LISTING_TYPES,
|
|
18
17
|
pageSize: DEFAULT_PAGE_SIZE,
|
|
19
18
|
sorts: DEFAULT_SORT,
|
|
20
|
-
|
|
19
|
+
// Derived, so this page and ProductsIndexListing can never disagree
|
|
20
|
+
// about the default view (Root Cause #30).
|
|
21
|
+
...defaultTogglesForListingTypes(ART_STICKERS_LISTING_TYPES),
|
|
21
22
|
}));
|
|
22
23
|
return (_jsxs(Main, { children: [_jsx(Section, { border: "bottom-subtle", paddingY: "b-md", padding: "t-xl", children: _jsxs(Container, { size: "xl", children: [_jsx(Heading, { level: 1, size: "3xl", weight: "bold", color: "primary", children: "Art & Stickers" }), _jsx(Text, { className: "mt-1", color: "muted", size: "sm", children: "Original art prints and sticker packs from independent sellers" })] }) }), _jsxs(Container, { size: "xl", padding: "x-md", children: [_jsx(AdSlot, { id: "listing-sidebar-top", className: "mb-4 mt-4" }), _jsx(ProductsIndexListing, { initialData: result ?? null, listingTypes: ART_STICKERS_LISTING_TYPES, typeTabs: ART_STICKERS_TYPE_FILTER_TABS, searchPlaceholder: "Search art & stickers..." }), _jsx(AdSlot, { id: "listing-sidebar-bottom", className: "mt-8" })] })] }));
|
|
23
24
|
}
|
|
@@ -24,7 +24,6 @@ const __O = {
|
|
|
24
24
|
hidden: "overflow-hidden",
|
|
25
25
|
};
|
|
26
26
|
const DEFAULT_SORT = AUCTION_PUBLIC_SORT_OPTIONS[0].value;
|
|
27
|
-
const AUCTION_SORT_OPTIONS = AUCTION_PUBLIC_SORT_OPTIONS;
|
|
28
27
|
const FILTER_KEYS = [TABLE_KEYS.CATEGORY, TABLE_KEYS.BRAND, TABLE_KEYS.MIN_BID, TABLE_KEYS.MAX_BID, TABLE_KEYS.STORE_ID, TABLE_KEYS.DATE_FROM, TABLE_KEYS.DATE_TO];
|
|
29
28
|
export function AuctionsIndexListing({ initialData, categorySlug, brandName }) {
|
|
30
29
|
const table = useUrlTable({ defaults: { pageSize: "24", sort: DEFAULT_SORT } });
|
|
@@ -132,7 +131,7 @@ export function AuctionsIndexListing({ initialData, categorySlug, brandName }) {
|
|
|
132
131
|
},
|
|
133
132
|
},
|
|
134
133
|
] } } : {});
|
|
135
|
-
return (_jsxs(Div, { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: filterActiveCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search auctions...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, onSearchKeyDown: handleSearchKeyDown, sortValue: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT, sortOptions:
|
|
134
|
+
return (_jsxs(Div, { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: filterActiveCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search auctions...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, onSearchKeyDown: handleSearchKeyDown, sortValue: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT, sortOptions: AUCTION_PUBLIC_SORT_OPTIONS, onSortChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, view: view, onViewChange: handleViewToggle, onResetAll: resetAll, hasActiveState: hasActiveState, bulkMode: selection.isSelecting, bulkSelectedCount: selection.selectedCount, bulkTotalCount: auctions.length, onBulkSelectAll: selection.toggleAll, onBulkClear: selection.clearSelection, toggles: [
|
|
136
135
|
{ label: "Show ended", active: showEnded, onChange: (next) => table.set(TABLE_KEYS.SHOW_ENDED, next ? "true" : "") },
|
|
137
136
|
] }), _jsx(BulkActionBar, { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: [
|
|
138
137
|
{
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { MOBILE_PRIMARY_ACTIONS } from "../constants/action-defs";
|
|
2
|
+
/**
|
|
3
|
+
* The sticky-CTA registrar shared by every listing type whose bottom bar is just
|
|
4
|
+
* "primary CTA that scrolls to the buy/contact panel, plus a price label".
|
|
5
|
+
*
|
|
6
|
+
* Pre-order, prize-draw, classified, digital-code and live all fit that shape, which put
|
|
7
|
+
* it well past the Rule of Three for a per-type copy. Auction and standard product keep
|
|
8
|
+
* their own registrars — the auction ticks a live countdown into `secondaryLabel`, and
|
|
9
|
+
* the product bar carries three buttons whose labels track cart/wishlist state.
|
|
10
|
+
*
|
|
11
|
+
* Renders nothing; it exists purely to register into `BottomActionsContext`.
|
|
12
|
+
*/
|
|
13
|
+
export interface ListingBottomActionsProps {
|
|
14
|
+
/** Selects the CTA set from the ACTIONS registry (Rule #7 — never inline actions). */
|
|
15
|
+
listingType: keyof typeof MOBILE_PRIMARY_ACTIONS;
|
|
16
|
+
/** `id` of the element the CTA scrolls to. */
|
|
17
|
+
anchorId: string;
|
|
18
|
+
price: number | null;
|
|
19
|
+
currency: string;
|
|
20
|
+
/**
|
|
21
|
+
* Register nothing at all — for ended/closed/sold-out listings, matching how the
|
|
22
|
+
* auction and prize-draw registrars bail out.
|
|
23
|
+
*/
|
|
24
|
+
unavailable?: boolean;
|
|
25
|
+
/** Appended after the formatted price, e.g. "per entry". */
|
|
26
|
+
infoSuffix?: string;
|
|
27
|
+
}
|
|
28
|
+
export declare function ListingBottomActions({ listingType, anchorId, price, currency, unavailable, infoSuffix, }: ListingBottomActionsProps): null;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useBottomActions } from "../../layout/hooks/useBottomActions";
|
|
3
|
+
import { formatCurrency } from "../../../utils/number.formatter";
|
|
4
|
+
import { ACTION_META, MOBILE_PRIMARY_ACTIONS } from "../constants/action-defs";
|
|
5
|
+
export function ListingBottomActions({ listingType, anchorId, price, currency, unavailable = false, infoSuffix, }) {
|
|
6
|
+
const actionIds = MOBILE_PRIMARY_ACTIONS[listingType] ?? [];
|
|
7
|
+
const infoLabel = price !== null
|
|
8
|
+
? `${formatCurrency(price, currency)}${infoSuffix ? ` ${infoSuffix}` : ""}`
|
|
9
|
+
: undefined;
|
|
10
|
+
useBottomActions(unavailable
|
|
11
|
+
? {}
|
|
12
|
+
: {
|
|
13
|
+
actions: actionIds.map((id) => ({
|
|
14
|
+
...ACTION_META[id],
|
|
15
|
+
onClick: () => {
|
|
16
|
+
document
|
|
17
|
+
.getElementById(anchorId)
|
|
18
|
+
?.scrollIntoView({ behavior: "smooth" });
|
|
19
|
+
},
|
|
20
|
+
})),
|
|
21
|
+
infoLabel,
|
|
22
|
+
desktop: "after-scroll",
|
|
23
|
+
});
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
@@ -3,4 +3,4 @@ export interface PrizeDrawBottomActionsProps {
|
|
|
3
3
|
currency: string;
|
|
4
4
|
closed: boolean;
|
|
5
5
|
}
|
|
6
|
-
export declare function PrizeDrawBottomActions({ pricePerEntry, currency, closed, }: PrizeDrawBottomActionsProps):
|
|
6
|
+
export declare function PrizeDrawBottomActions({ pricePerEntry, currency, closed, }: PrizeDrawBottomActionsProps): import("react").JSX.Element;
|
|
@@ -1,24 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { ACTION_ID } from "../constants/action-defs";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { ListingBottomActions } from "./ListingBottomActions";
|
|
5
4
|
export function PrizeDrawBottomActions({ pricePerEntry, currency, closed, }) {
|
|
6
|
-
|
|
7
|
-
? {}
|
|
8
|
-
: {
|
|
9
|
-
actions: [
|
|
10
|
-
{
|
|
11
|
-
id: ACTION_ID.BUY_NOW,
|
|
12
|
-
label: "Buy Now",
|
|
13
|
-
variant: "primary",
|
|
14
|
-
onClick: () => {
|
|
15
|
-
document
|
|
16
|
-
.getElementById("prize-draw-buy-bar")
|
|
17
|
-
?.scrollIntoView({ behavior: "smooth" });
|
|
18
|
-
},
|
|
19
|
-
},
|
|
20
|
-
],
|
|
21
|
-
infoLabel: `${formatCurrency(pricePerEntry, currency)} per entry`,
|
|
22
|
-
});
|
|
23
|
-
return null;
|
|
5
|
+
return (_jsx(ListingBottomActions, { listingType: "prize-draw", anchorId: "prize-draw-buy-bar", price: pricePerEntry, currency: currency, unavailable: closed, infoSuffix: "per entry" }));
|
|
24
6
|
}
|
|
@@ -1,33 +1,13 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { sieveFilter, SIEVE_OP } from "@mohasinac/appkit";
|
|
3
|
-
import { productRepository } from "../../../repositories";
|
|
4
2
|
import { Container, Main, Heading, Section, Text } from "../../../ui";
|
|
5
3
|
import { AdSlot } from "../../homepage/components/AdSlot";
|
|
6
|
-
import {
|
|
4
|
+
import { PRODUCT_FIELDS } from "../../../constants/field-names";
|
|
5
|
+
import { sortBy } from "../../../constants/sort";
|
|
6
|
+
import { listPublicProducts, parsePublicProductParams, defaultTogglesForListingTypes, } from "../../../_internal/server/features/products/list-public";
|
|
7
7
|
import { PrizeDrawsIndexListing } from "./PrizeDrawsIndexListing";
|
|
8
|
-
const
|
|
8
|
+
const LISTING_TYPES = ["prize-draw"];
|
|
9
9
|
const DEFAULT_PAGE_SIZE = 24;
|
|
10
|
-
const DEFAULT_SORT =
|
|
11
|
-
function sp(params, key) {
|
|
12
|
-
const v = params[key];
|
|
13
|
-
return Array.isArray(v) ? v[0] ?? "" : v ?? "";
|
|
14
|
-
}
|
|
15
|
-
function buildPrizeDrawFilters(params) {
|
|
16
|
-
const parts = ["status==published", "listingType==prize-draw"];
|
|
17
|
-
const minPrice = sp(params, "minPrice");
|
|
18
|
-
const maxPrice = sp(params, "maxPrice");
|
|
19
|
-
if (minPrice)
|
|
20
|
-
parts.push(sieveFilter("pricePerEntry", SIEVE_OP.GTE, minPrice));
|
|
21
|
-
if (maxPrice)
|
|
22
|
-
parts.push(sieveFilter("pricePerEntry", SIEVE_OP.LTE, maxPrice));
|
|
23
|
-
const store = sp(params, "storeId");
|
|
24
|
-
if (store)
|
|
25
|
-
parts.push(sieveFilter("storeId", SIEVE_OP.EQ, store));
|
|
26
|
-
const status = sp(params, "prizeRevealStatus");
|
|
27
|
-
if (status)
|
|
28
|
-
parts.push(sieveFilter("prizeRevealStatus", SIEVE_OP.EQ, status));
|
|
29
|
-
return parts.join(",");
|
|
30
|
-
}
|
|
10
|
+
const DEFAULT_SORT = sortBy(PRODUCT_FIELDS.CREATED_AT);
|
|
31
11
|
/**
|
|
32
12
|
* Public listing page (SB4-F). Server-fetches published prize-draw products,
|
|
33
13
|
* hydrates the client `PrizeDrawsIndexListing` which renders the filter
|
|
@@ -38,13 +18,18 @@ function buildPrizeDrawFilters(params) {
|
|
|
38
18
|
* already gone (matches `PrizeDrawCollage`'s `hideWonState` prop).
|
|
39
19
|
*/
|
|
40
20
|
export async function PrizeDrawsListingView({ searchParams = {}, }) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
21
|
+
// Routed through the shared query (2026-08-21). The hand-rolled builder this
|
|
22
|
+
// replaces had its own Root Cause #30 divergence: it applied the price range
|
|
23
|
+
// to `pricePerEntry`, while the client's refetch sends minPrice/maxPrice,
|
|
24
|
+
// which the shared query applies to `price`. Both fields carry the same
|
|
25
|
+
// value today, so the split was invisible — until one of them drifted.
|
|
26
|
+
// `price` wins because it is also what PRIZE_DRAW_SORT_OPTIONS sorts by for
|
|
27
|
+
// "Entry: Low to High".
|
|
28
|
+
const result = await listPublicProducts(parsePublicProductParams(searchParams, {
|
|
29
|
+
listingTypes: LISTING_TYPES,
|
|
30
|
+
pageSize: DEFAULT_PAGE_SIZE,
|
|
31
|
+
sorts: DEFAULT_SORT,
|
|
32
|
+
...defaultTogglesForListingTypes(LISTING_TYPES),
|
|
33
|
+
}));
|
|
49
34
|
return (_jsx(Main, { children: _jsx(Section, { padding: "y-2xl", children: _jsxs(Container, { size: "xl", children: [_jsx(Heading, { level: 1, className: "mb-2", color: "primary", size: "3xl", weight: "semibold", children: "Prize Draws" }), _jsx(Text, { className: "mb-6 text-[var(--appkit-color-text-muted)]", size: "sm", children: "Fair-RNG draws for sealed Pok\u00E9mon, Hot Wheels Super Treasure Hunts, Gundam kits and more. Every winner picked by crypto.randomInt \u2014 proof on GitHub." }), _jsx(AdSlot, { id: "listing-sidebar-top", className: "mb-6" }), _jsx(PrizeDrawsIndexListing, { initialData: result ?? undefined }), _jsx(AdSlot, { id: "listing-sidebar-bottom", className: "mt-8" })] }) }) }));
|
|
50
35
|
}
|
|
@@ -122,6 +122,10 @@ export function ProductDetailActions({ productId, productTitle, productImage, pr
|
|
|
122
122
|
infoLabel: price != null && currency
|
|
123
123
|
? formatCurrency(price, currency)
|
|
124
124
|
: undefined,
|
|
125
|
+
// The bar itself owns responsive visibility now, so this one registrar serves
|
|
126
|
+
// both breakpoints. The desktop branch below must NOT also register —
|
|
127
|
+
// useBottomActions is last-mounted-wins and the two would clobber each other.
|
|
128
|
+
desktop: "after-scroll",
|
|
125
129
|
}
|
|
126
130
|
: {});
|
|
127
131
|
const authModal = (_jsx(LoginRequiredModal, { isOpen: modalOpen, onClose: closeModal, message: modalMessage }));
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import Link from "next/link";
|
|
3
|
-
import { productRepository
|
|
3
|
+
import { productRepository } from "../../../repositories";
|
|
4
4
|
const __P = {
|
|
5
5
|
p3: "p-[var(--appkit-space-3)]",
|
|
6
6
|
p5: "p-[var(--appkit-space-5)]",
|
|
@@ -25,7 +25,7 @@ import { getDefaultCurrency } from "../../../core/baseline-resolver";
|
|
|
25
25
|
import { Button, Container, Div, Heading, Main, Nav, PriceDisplay, RichText, Row, Section, Span, Stack, Text, Ul, Ol, Li, Dl, Dt, Dd, } from "../../../ui";
|
|
26
26
|
import { normalizeRichTextHtml } from "../../../utils/string.formatter";
|
|
27
27
|
import { safeDisplayName } from "../../../security";
|
|
28
|
-
import {
|
|
28
|
+
import { ReviewsListingPanel } from "../../reviews/components/ReviewsListingPanel";
|
|
29
29
|
import { ProductDetailView } from "./ProductDetailView";
|
|
30
30
|
import { ProductGalleryClient } from "./ProductGalleryClient";
|
|
31
31
|
import { ProductTabsShell } from "./ProductTabsShell";
|
|
@@ -37,7 +37,7 @@ import { SublistingCarouselSection } from "./SublistingCarouselSection";
|
|
|
37
37
|
import { ShowGroupSection } from "./ShowGroupSection";
|
|
38
38
|
import { HistoryTracker } from "../../history/components/HistoryTracker";
|
|
39
39
|
import { RelatedItemsSection } from "./RelatedItemsSection";
|
|
40
|
-
import { computeRelatedItems,
|
|
40
|
+
import { computeRelatedItems, getReviewPageForProduct } from "../../../_internal/server/features/products/data";
|
|
41
41
|
import { GroupedListingsCarousel } from "../../grouped/components/GroupedListingsCarousel";
|
|
42
42
|
import { getGroupsWithItemsForProduct } from "../../../_internal/server/features/grouped/data";
|
|
43
43
|
// ---------------------------------------------------------------------------
|
|
@@ -165,12 +165,12 @@ export async function ProductDetailPageView({ slug, initialProduct, renderOfferA
|
|
|
165
165
|
const groupTitle = typeof p.groupTitle === "string" ? p.groupTitle : undefined;
|
|
166
166
|
const storeId = typeof p.storeId === "string" ? p.storeId : null;
|
|
167
167
|
// -- Fetch reviews + the shared 4-signal related-items computation + grouped listings in parallel --
|
|
168
|
-
|
|
169
|
-
|
|
168
|
+
// Only page 1 of reviews is fetched here; <ReviewsListingPanel> pages the rest client-side.
|
|
169
|
+
const [initialReviews, related, groups] = await Promise.all([
|
|
170
|
+
getReviewPageForProduct(product.id),
|
|
170
171
|
computeRelatedItems(product),
|
|
171
172
|
getGroupsWithItemsForProduct(product.id),
|
|
172
173
|
]);
|
|
173
|
-
const reviews = reviewDocs.map(toReview);
|
|
174
174
|
const { relatedItems, relatedByBrand, relatedByTags, relatedByStore } = related;
|
|
175
175
|
return (_jsxs(Main, { children: [_jsx(HistoryTracker, { productId: product.id, productType: "product", snapshot: {
|
|
176
176
|
title: product.title,
|
|
@@ -231,7 +231,7 @@ export async function ProductDetailPageView({ slug, initialProduct, renderOfferA
|
|
|
231
231
|
? () => (_jsx(SublistingCarouselSection, { sublistingCategoryId: sublistingCategoryId, currentListingId: String(product.id) }))
|
|
232
232
|
: undefined, renderGroupSection: groupId
|
|
233
233
|
? () => (_jsx(ShowGroupSection, { groupId: groupId, currentSlug: String(p.slug ?? product.id), isParent: isGroupParent, groupTitle: groupTitle }))
|
|
234
|
-
: undefined, renderTabs: () => (_jsx(ProductTabsShell, { descriptionContent: descriptionHtml ? (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm sm:prose max-w-none dark:prose-invert", className: "text-[var(--appkit-color-text-muted)]" })) : undefined, specsContent: allSpecs.length > 0 ? (_jsx(Dl, { divide: "subtle", rounded: "xl", border: "subtle", className: "overflow-hidden", children: allSpecs.map((s, i) => (_jsxs(Row, { gap: "md", oddEven: "zebra", surface: "default", padding: "inline", children: [_jsx(Dt, { className: "w-36 flex-shrink-0", color: "primary", weight: "medium", children: s.name }), _jsxs(Dd, { className: "flex-1", color: "muted", children: [s.value, s.unit ? ` ${s.unit}` : ""] })] }, i))) })) : undefined, ingredientsContent: ingredients.length > 0 ? (_jsx(Ul, { spacing: "comfortable", children: ingredients.map((item, i) => (_jsxs(Li, { layout: "flex-start", gap: "2", children: [_jsx(Span, { className: "mt-1 flex-shrink-0 h-1.5 w-1.5 bg-primary-400", rounded: "full" }), item] }, i))) })) : undefined, howToUseContent: howToUse.length > 0 ? (_jsx(Ol, { spacing: "loose", children: howToUse.map((step, i) => (_jsxs(Li, { layout: "flex-start", gap: "3", textSize: "sm", color: "muted", children: [_jsx(Span, { layout: "flex-center", size: "xs", weight: "bold", className: "flex-shrink-0 h-6 w-6 bg-primary-100 dark:bg-primary-900/30 text-primary-700 dark:text-primary-300", rounded: "full", children: i + 1 }), step] }, i))) })) : undefined, reviewsContent: _jsx(
|
|
234
|
+
: undefined, renderTabs: () => (_jsx(ProductTabsShell, { descriptionContent: descriptionHtml ? (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm sm:prose max-w-none dark:prose-invert", className: "text-[var(--appkit-color-text-muted)]" })) : undefined, specsContent: allSpecs.length > 0 ? (_jsx(Dl, { divide: "subtle", rounded: "xl", border: "subtle", className: "overflow-hidden", children: allSpecs.map((s, i) => (_jsxs(Row, { gap: "md", oddEven: "zebra", surface: "default", padding: "inline", children: [_jsx(Dt, { className: "w-36 flex-shrink-0", color: "primary", weight: "medium", children: s.name }), _jsxs(Dd, { className: "flex-1", color: "muted", children: [s.value, s.unit ? ` ${s.unit}` : ""] })] }, i))) })) : undefined, ingredientsContent: ingredients.length > 0 ? (_jsx(Ul, { spacing: "comfortable", children: ingredients.map((item, i) => (_jsxs(Li, { layout: "flex-start", gap: "2", children: [_jsx(Span, { className: "mt-1 flex-shrink-0 h-1.5 w-1.5 bg-primary-400", rounded: "full" }), item] }, i))) })) : undefined, howToUseContent: howToUse.length > 0 ? (_jsx(Ol, { spacing: "loose", children: howToUse.map((step, i) => (_jsxs(Li, { layout: "flex-start", gap: "3", textSize: "sm", color: "muted", children: [_jsx(Span, { layout: "flex-center", size: "xs", weight: "bold", className: "flex-shrink-0 h-6 w-6 bg-primary-100 dark:bg-primary-900/30 text-primary-700 dark:text-primary-300", rounded: "full", children: i + 1 }), step] }, i))) })) : undefined, reviewsContent: _jsx(ReviewsListingPanel, { source: { kind: "product", productId: product.id }, stateMode: "local", context: "listing", initialData: initialReviews, emptyLabel: "No reviews yet \u2014 be the first to review this product." }), customTabs: customSections.map((s) => ({
|
|
235
235
|
id: s.id,
|
|
236
236
|
label: s.title,
|
|
237
237
|
content: _jsx(CustomSectionTabContent, { section: s }),
|
|
@@ -12,7 +12,7 @@ import { ACTION_ID, ACTION_META, COMPARE_MAX_ITEMS } from "../constants/action-d
|
|
|
12
12
|
import { CompareOverlay } from "./CompareOverlay";
|
|
13
13
|
import { ROUTES } from "../../../next";
|
|
14
14
|
import { pluginFor } from "../../../_internal/shared/listing-types/_registry";
|
|
15
|
-
import { ProductGrid, ProductFilters
|
|
15
|
+
import { ProductGrid, ProductFilters } from ".";
|
|
16
16
|
import { useGuestCart } from "../../cart/hooks/useGuestCart";
|
|
17
17
|
import { getGuestCartItems } from "../../cart/utils/guest-cart";
|
|
18
18
|
import { useGuestWishlist } from "../../wishlist/hooks/useGuestWishlist";
|
|
@@ -28,6 +28,9 @@ import { sortBy } from "../../../constants/sort";
|
|
|
28
28
|
import { PRODUCT_FIELDS } from "../../../constants/field-names";
|
|
29
29
|
import { useBottomActions } from "../../layout";
|
|
30
30
|
import { GENERIC_PRODUCT_LISTING_TYPES, PRODUCT_TYPE_FILTER_TABS } from "../constants/listing-tabs";
|
|
31
|
+
import { parseSelectedListingTypes } from "../utils/listing-type";
|
|
32
|
+
import { commonSortOptionsFor, hideDefaultsFor } from "../../../_internal/shared/listing-types/_registry";
|
|
33
|
+
import { TextLink } from "../../../ui";
|
|
31
34
|
const __P = {
|
|
32
35
|
p3: "p-[var(--appkit-space-3)]",
|
|
33
36
|
};
|
|
@@ -46,7 +49,40 @@ export function ProductsIndexListing({ initialData, listingTypes = GENERIC_PRODU
|
|
|
46
49
|
const [compareIds, setCompareIds] = useState([]);
|
|
47
50
|
const showSold = table.get(TABLE_KEYS.SHOW_SOLD) === "true";
|
|
48
51
|
const [view, setView] = useState(table.get(TABLE_KEYS.VIEW) || VIEW_MODE.GRID);
|
|
49
|
-
|
|
52
|
+
// ── Type filter (multi-select) ───────────────────────────────────────────
|
|
53
|
+
// The URL value is a pipe-joined set; empty means "every type this page
|
|
54
|
+
// spans". Unknown tokens are dropped rather than passed through to a
|
|
55
|
+
// Firestore `==` that could never match (Root Cause #33).
|
|
56
|
+
const typeParam = table.get(TABLE_KEYS.LISTING_TYPE);
|
|
57
|
+
const selectedTypes = parseSelectedListingTypes(typeParam).filter((t) => listingTypes.includes(t));
|
|
58
|
+
const effectiveTypes = (selectedTypes.length > 0 ? selectedTypes : listingTypes);
|
|
59
|
+
// Sorts valid across every selected type. Pick Auctions alone and you get
|
|
60
|
+
// "Ending Soon"; pick Auctions + Products and you get only what both
|
|
61
|
+
// support, because a sort on `auctionEndDate` would order the products
|
|
62
|
+
// arbitrarily (and needs an index nobody declares for that shape).
|
|
63
|
+
const sortOptions = commonSortOptionsFor(effectiveTypes, "public");
|
|
64
|
+
const sortValue = table.get(TABLE_KEYS.SORT) || DEFAULT_SORT;
|
|
65
|
+
// Guard against a sort carried over from a previous selection that the new
|
|
66
|
+
// one can't satisfy — that combination throws FAILED_PRECONDITION in
|
|
67
|
+
// Firestore and surfaces as a bare empty page (Root Cause #2).
|
|
68
|
+
const effectiveSort = sortOptions.some((o) => o.value === sortValue)
|
|
69
|
+
? sortValue
|
|
70
|
+
: DEFAULT_SORT;
|
|
71
|
+
// Which "Show X" toggles are meaningful for the current selection. A mixed
|
|
72
|
+
// set can need both (products sell out, auctions end).
|
|
73
|
+
const hideDefaults = hideDefaultsFor(effectiveTypes);
|
|
74
|
+
const showsSoldToggle = hideDefaults.includes("sold");
|
|
75
|
+
const showsEndedToggle = hideDefaults.includes("ended") || hideDefaults.includes("closed");
|
|
76
|
+
const showEnded = table.get(TABLE_KEYS.SHOW_ENDED) === "true";
|
|
77
|
+
// "Full <type> filters →" — a single selected type with a dedicated browse
|
|
78
|
+
// page gets a link to it, since that page carries facets this generic one
|
|
79
|
+
// can't (bid range, delivery-date window, reveal status).
|
|
80
|
+
const dedicatedPage = selectedTypes.length === 1 && pluginFor(selectedTypes[0]).browseRoute
|
|
81
|
+
? {
|
|
82
|
+
href: pluginFor(selectedTypes[0]).browseRoute,
|
|
83
|
+
label: `Full ${pluginFor(selectedTypes[0]).pluralLabel} filters →`,
|
|
84
|
+
}
|
|
85
|
+
: null;
|
|
50
86
|
const { pendingTable, filterActiveCount, onFilterApply, onFilterClear, onResetAll, onFilterReset } = usePendingTable(table, FILTER_KEYS);
|
|
51
87
|
const openFilters = useCallback(() => {
|
|
52
88
|
onFilterReset();
|
|
@@ -57,11 +93,19 @@ export function ProductsIndexListing({ initialData, listingTypes = GENERIC_PRODU
|
|
|
57
93
|
setFilterOpen(false);
|
|
58
94
|
}, [onFilterApply]);
|
|
59
95
|
const resetAll = useCallback(() => {
|
|
60
|
-
onResetAll({
|
|
96
|
+
onResetAll({
|
|
97
|
+
[TABLE_KEYS.QUERY]: "",
|
|
98
|
+
[TABLE_KEYS.SORT]: "",
|
|
99
|
+
[TABLE_KEYS.SHOW_SOLD]: "",
|
|
100
|
+
[TABLE_KEYS.SHOW_ENDED]: "",
|
|
101
|
+
[TABLE_KEYS.LISTING_TYPE]: "",
|
|
102
|
+
});
|
|
61
103
|
setSearchInput("");
|
|
62
104
|
}, [onResetAll]);
|
|
63
105
|
const hasActiveState = !!table.get(TABLE_KEYS.QUERY) ||
|
|
64
106
|
table.get(TABLE_KEYS.SHOW_SOLD) === "true" ||
|
|
107
|
+
table.get(TABLE_KEYS.SHOW_ENDED) === "true" ||
|
|
108
|
+
!!typeParam ||
|
|
65
109
|
table.get(TABLE_KEYS.SORT) !== DEFAULT_SORT ||
|
|
66
110
|
filterActiveCount > 0;
|
|
67
111
|
const localCart = useGuestCart();
|
|
@@ -81,13 +125,27 @@ export function ProductsIndexListing({ initialData, listingTypes = GENERIC_PRODU
|
|
|
81
125
|
brand: table.get(TABLE_KEYS.BRAND) || undefined,
|
|
82
126
|
storeId: table.get(TABLE_KEYS.STORE_ID) || undefined,
|
|
83
127
|
freeShipping: table.get(TABLE_KEYS.FREE_SHIPPING) === "true" ? true : undefined,
|
|
84
|
-
|
|
128
|
+
// These three are in FILTER_KEYS (so they count toward the filter badge)
|
|
129
|
+
// but were never put on the wire — the drawer sections were inert.
|
|
130
|
+
tags: table.get(TABLE_KEYS.TAGS) || undefined,
|
|
131
|
+
sublistingCategory: table.get(TABLE_KEYS.SUBLISTING_CATEGORY) || undefined,
|
|
132
|
+
features: table.get(TABLE_KEYS.FEATURES) || undefined,
|
|
133
|
+
sort: effectiveSort,
|
|
85
134
|
page: table.getNumber(TABLE_KEYS.PAGE, 1),
|
|
86
135
|
perPage: table.getNumber(TABLE_KEYS.PAGE_SIZE, 24),
|
|
87
|
-
|
|
88
|
-
//
|
|
89
|
-
|
|
90
|
-
|
|
136
|
+
// Pipe-joined OR-group — sievejs parses it as a same-field OR and the
|
|
137
|
+
// Firebase adapter upgrades it to a `.where(…, "in", …)` query.
|
|
138
|
+
listingType: effectiveTypes.join("|"),
|
|
139
|
+
// Hide sold-out items by default, but only when a sold-out-able type is in
|
|
140
|
+
// play — an auction has no meaningful stock, so applying it to an
|
|
141
|
+
// auctions-only view would hide every live auction.
|
|
142
|
+
inStock: showsSoldToggle && !showSold ? true : undefined,
|
|
143
|
+
// Same shape for time-boxed types. `dateFrom` only takes effect when
|
|
144
|
+
// exactly one type is selected (there is no single end-date field shared
|
|
145
|
+
// across types), which `dateFieldFor` already enforces server-side.
|
|
146
|
+
dateFrom: showsEndedToggle && !showEnded && selectedTypes.length === 1
|
|
147
|
+
? new Date().toISOString()
|
|
148
|
+
: undefined,
|
|
91
149
|
};
|
|
92
150
|
const { products, totalPages, page, isLoading } = useProducts(params, { initialData });
|
|
93
151
|
const selection = useBulkSelection({ items: products, keyExtractor: (p) => p.id });
|
|
@@ -200,14 +258,32 @@ export function ProductsIndexListing({ initialData, listingTypes = GENERIC_PRODU
|
|
|
200
258
|
},
|
|
201
259
|
},
|
|
202
260
|
] } } : {});
|
|
203
|
-
return (_jsxs(Div, { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: filterActiveCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: searchPlaceholder, onSearchChange: setSearchInput, onSearchCommit: commitSearch, onSearchKeyDown: handleSearchKeyDown, sortValue:
|
|
204
|
-
|
|
261
|
+
return (_jsxs(Div, { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: filterActiveCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: searchPlaceholder, onSearchChange: setSearchInput, onSearchCommit: commitSearch, onSearchKeyDown: handleSearchKeyDown, sortValue: effectiveSort, sortOptions: sortOptions, onSortChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, view: view, onViewChange: handleViewToggle, onResetAll: resetAll, hasActiveState: hasActiveState, bulkMode: selection.isSelecting, bulkSelectedCount: selection.selectedCount, bulkTotalCount: products.length, onBulkSelectAll: selection.toggleAll, onBulkClear: selection.clearSelection, toggles: [
|
|
262
|
+
// Each toggle appears only when it means something for the selected
|
|
263
|
+
// types — "Show ended" against a Products-only view would be noise,
|
|
264
|
+
// and "Show sold" against an Auctions-only view is meaningless.
|
|
265
|
+
...(showsSoldToggle
|
|
266
|
+
? [{ label: "Show sold", active: showSold, onChange: (next) => table.set(TABLE_KEYS.SHOW_SOLD, next ? "true" : "") }]
|
|
267
|
+
: []),
|
|
268
|
+
...(showsEndedToggle
|
|
269
|
+
? [{ label: "Show ended", active: showEnded, onChange: (next) => table.set(TABLE_KEYS.SHOW_ENDED, next ? "true" : "") }]
|
|
270
|
+
: []),
|
|
205
271
|
// Inline quick-filter: the highest-frequency drawer-only facet promoted
|
|
206
272
|
// to the sticky toolbar so it doesn't require open-drawer → check → Apply
|
|
207
273
|
// → close for every toggle. The full facet set (category/price/brand/…)
|
|
208
274
|
// remains in <FilterDrawer> below.
|
|
209
275
|
{ label: "Free shipping", active: table.get(TABLE_KEYS.FREE_SHIPPING) === "true", onChange: (next) => table.set(TABLE_KEYS.FREE_SHIPPING, next ? "true" : "") },
|
|
210
|
-
] }), _jsx(Div, { padding: "y-sm", children:
|
|
276
|
+
] }), _jsx(Div, { padding: "y-sm", children: _jsxs(Row, { justify: "between", align: "center", wrap: true, gap: "sm", children: [_jsx(FilterChipGroup, { multiple: true, label: "Type", tabs: typeTabs, value: selectedTypes.join("|"), onChange: (next) => {
|
|
277
|
+
// Clear `sort` alongside the type change: a sort valid for the
|
|
278
|
+
// old selection (say -auctionEndDate) can be invalid for the new
|
|
279
|
+
// one, and useUrlTable's single router.replace means we must set
|
|
280
|
+
// both in ONE call — a follow-up setPage/set would read stale
|
|
281
|
+
// searchParams and overwrite this update (Root Cause #13).
|
|
282
|
+
table.setMany({
|
|
283
|
+
[TABLE_KEYS.LISTING_TYPE]: next,
|
|
284
|
+
[TABLE_KEYS.SORT]: "",
|
|
285
|
+
});
|
|
286
|
+
} }), dedicatedPage && (_jsx(TextLink, { href: dedicatedPage.href, size: "sm", weight: "medium", children: dedicatedPage.label }))] }) }), _jsx(BulkActionBar, { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: [
|
|
211
287
|
{
|
|
212
288
|
id: ACTION_ID.ADD_TO_CART,
|
|
213
289
|
label: ACTION_META[ACTION_ID.ADD_TO_CART].label,
|