@mohasinac/appkit 4.11.1 → 4.11.3
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 +4 -0
- package/dist/_internal/server/features/checkout/actions.js +138 -27
- package/dist/_internal/server/features/checkout/data.d.ts +10 -0
- package/dist/_internal/server/features/checkout/locked-lines.d.ts +30 -0
- package/dist/_internal/server/features/checkout/locked-lines.js +116 -0
- package/dist/_internal/server/features/orders/adapters.js +7 -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 +2 -1
- package/dist/_internal/server/features/products/index.js +2 -1
- package/dist/_internal/server/features/products/list-public.d.ts +130 -0
- package/dist/_internal/server/features/products/list-public.js +375 -0
- 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 +89 -16
- package/dist/_internal/server/jobs/core/offerExpiry.js +116 -4
- 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/server/jobs/handlers/messages.d.ts +5 -0
- package/dist/_internal/server/jobs/handlers/messages.js +5 -0
- package/dist/_internal/shared/checkout/lanes.d.ts +41 -0
- package/dist/_internal/shared/checkout/lanes.js +106 -0
- package/dist/_internal/shared/checkout/order-math.d.ts +19 -0
- package/dist/_internal/shared/checkout/order-math.js +30 -6
- 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/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/feature-flags.d.ts +1 -0
- package/dist/_internal/shared/listing-types/feature-flags.js +24 -10
- 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 +7 -2
- package/dist/client.js +8 -2
- 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/NotificationBell.js +1 -0
- package/dist/features/account/components/UserOffersPanel.js +8 -1
- package/dist/features/account/components/UserOrdersView.js +27 -5
- package/dist/features/admin/actions/notification-actions.js +1 -1
- package/dist/features/admin/components/AdminAddressesView.js +3 -1
- package/dist/features/admin/components/AdminCouponEditorView.js +1 -4
- package/dist/features/admin/components/AdminPaymentMethodsView.js +3 -1
- package/dist/features/admin/components/AdminProductsView.js +34 -18
- 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/schemas/firestore.d.ts +2 -1
- package/dist/features/admin/schemas/firestore.js +1 -0
- package/dist/features/auctions/actions/bid-actions.d.ts +11 -1
- package/dist/features/auctions/actions/bid-actions.js +29 -15
- package/dist/features/auctions/components/AuctionBidsTable.js +6 -2
- package/dist/features/auctions/components/AuctionBottomActions.js +1 -0
- package/dist/features/auctions/components/AuctionDetailPageView.js +13 -11
- package/dist/features/auctions/components/AuctionsListView.js +12 -53
- package/dist/features/auctions/components/PlaceBidFormClient.js +10 -1
- package/dist/features/auctions/repository/bid.repository.d.ts +15 -0
- package/dist/features/auctions/repository/bid.repository.js +19 -0
- package/dist/features/auctions/schemas/firestore.d.ts +11 -1
- package/dist/features/auctions/schemas/firestore.js +1 -0
- package/dist/features/cart/actions/cart-actions.d.ts +11 -0
- package/dist/features/cart/actions/cart-actions.js +24 -0
- package/dist/features/cart/repository/cart.repository.d.ts +24 -1
- package/dist/features/cart/repository/cart.repository.js +71 -6
- package/dist/features/cart/schemas/firestore.d.ts +23 -4
- 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 +56 -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 +26 -3
- package/dist/features/layout/BottomActionsContext.d.ts +14 -0
- package/dist/features/layout/BottomActionsContext.js +13 -1
- package/dist/features/layout/hooks/useBottomActions.d.ts +6 -1
- package/dist/features/layout/hooks/useBottomActions.js +4 -1
- package/dist/features/layout/index.d.ts +1 -1
- package/dist/features/live/components/LiveItemDetailPageView.js +7 -6
- package/dist/features/orders/repository/orders.repository.d.ts +4 -15
- package/dist/features/orders/repository/orders.repository.js +4 -27
- package/dist/features/orders/types/index.d.ts +16 -0
- package/dist/features/orders/utils/order-splitter.js +14 -2
- package/dist/features/pre-orders/components/PreOrderBottomActions.d.ts +1 -1
- package/dist/features/pre-orders/components/PreOrderBottomActions.js +3 -19
- package/dist/features/pre-orders/components/PreOrdersListView.js +11 -39
- package/dist/features/products/components/ArtStickersListView.js +10 -49
- 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/ProductDetailActions.js +4 -0
- package/dist/features/products/components/ProductDetailPageView.js +7 -7
- package/dist/features/products/components/ProductsIndexPageView.js +9 -62
- 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/repository/products.repository.d.ts +45 -1
- package/dist/features/products/repository/products.repository.js +67 -8
- 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/actions/offer-actions.d.ts +6 -0
- package/dist/features/seller/actions/offer-actions.js +8 -9
- package/dist/features/seller/components/SellerOffersView.d.ts +6 -1
- package/dist/features/seller/components/SellerOffersView.js +59 -8
- package/dist/features/seller/components/SellerProductsCards.js +2 -2
- package/dist/features/seller/components/SellerProductsView.js +21 -18
- package/dist/features/seller/repository/offer.repository.d.ts +18 -2
- package/dist/features/seller/repository/offer.repository.js +38 -2
- package/dist/features/seller/schemas/firestore.d.ts +7 -2
- package/dist/features/seller/schemas/firestore.js +3 -0
- package/dist/features/seller/schemas/offer-forms.d.ts +12 -0
- package/dist/features/seller/schemas/offer-forms.js +28 -0
- package/dist/features/stores/actions/store-query-actions.js +1 -1
- package/dist/features/stores/components/StoreAuctionsListing.js +5 -11
- package/dist/features/stores/components/StorePreOrdersListing.js +6 -11
- 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 +337 -1
- package/dist/index.d.ts +11 -4
- package/dist/index.js +15 -3
- 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/server-entry.d.ts +1 -0
- package/dist/server-entry.js +1 -0
- package/dist/server.d.ts +1 -0
- package/dist/server.js +3 -0
- package/dist/styles.css +44 -39
- package/dist/tailwind-utilities.css +1 -1
- 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
|
@@ -482,33 +482,6 @@ class OrderRepository extends BaseRepository {
|
|
|
482
482
|
updatedAt: serverTimestamp(),
|
|
483
483
|
});
|
|
484
484
|
}
|
|
485
|
-
/**
|
|
486
|
-
* Cloud Functions: create an auction-won order inside a caller-owned WriteBatch.
|
|
487
|
-
* Returns the new DocumentReference synchronously so the caller can chain further batch ops.
|
|
488
|
-
*/
|
|
489
|
-
createFromAuction(batch, input) {
|
|
490
|
-
const ref = this.db.collection(this.collection).doc();
|
|
491
|
-
batch.create(ref, prepareForFirestore({
|
|
492
|
-
id: ref.id,
|
|
493
|
-
productId: input.productId,
|
|
494
|
-
productTitle: input.productTitle,
|
|
495
|
-
userId: input.userId,
|
|
496
|
-
userName: input.userName,
|
|
497
|
-
userEmail: input.userEmail,
|
|
498
|
-
storeId: input.storeId ?? null,
|
|
499
|
-
quantity: 1,
|
|
500
|
-
unitPrice: input.amount,
|
|
501
|
-
totalPrice: input.amount,
|
|
502
|
-
currency: input.currency,
|
|
503
|
-
status: OrderStatusValues.CONFIRMED,
|
|
504
|
-
paymentStatus: "pending",
|
|
505
|
-
orderDate: new Date(),
|
|
506
|
-
notes: `Won via auction bid — auction product ${input.auctionProductId}`,
|
|
507
|
-
createdAt: serverTimestamp(),
|
|
508
|
-
updatedAt: serverTimestamp(),
|
|
509
|
-
}));
|
|
510
|
-
return ref;
|
|
511
|
-
}
|
|
512
485
|
}
|
|
513
486
|
OrderRepository.SELLER_SIEVE_FIELDS = {
|
|
514
487
|
id: { canFilter: true, canSort: false },
|
|
@@ -543,6 +516,10 @@ OrderRepository.ADMIN_SIEVE_FIELDS = {
|
|
|
543
516
|
// S-SBUNI-RULES 2026-05-13 — refund + batch fields
|
|
544
517
|
paymentBatchId: { canFilter: true, canSort: false },
|
|
545
518
|
contestable: { canFilter: true, canSort: false },
|
|
519
|
+
// Lane segregation on /user/orders — "standard"/"auction"/"offer" etc.
|
|
520
|
+
// Never filtered as "standard" via Firestore == (see listForUser callers):
|
|
521
|
+
// orders written before this field existed have no value to match.
|
|
522
|
+
orderType: { canFilter: true, canSort: false },
|
|
546
523
|
};
|
|
547
524
|
const orderRepository = new OrderRepository();
|
|
548
525
|
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,14 @@ 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
|
+
/** Every discount applied to this order — the full stack, not just the first. */
|
|
83
|
+
appliedDiscounts?: AppliedOrderDiscount[];
|
|
68
84
|
trackingNumber?: string;
|
|
69
85
|
shippingCarrier?: string;
|
|
70
86
|
/** 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. */
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { getListingRule, offerRule, } from "../../../_internal/shared/checkout/rules";
|
|
1
|
+
import { getListingRule, offerRule, auctionRule, } from "../../../_internal/shared/checkout/rules";
|
|
2
|
+
import { CART_LANE, laneOf } from "../../../_internal/shared/checkout/lanes";
|
|
2
3
|
/**
|
|
3
4
|
* Split a sequence of cart-items into order groups by `OrderType`.
|
|
4
5
|
*
|
|
@@ -18,7 +19,18 @@ export function splitCartIntoOrderGroups(checks) {
|
|
|
18
19
|
for (const check of checks) {
|
|
19
20
|
const { item } = check;
|
|
20
21
|
const lt = (item.listingType ?? "standard");
|
|
21
|
-
|
|
22
|
+
// Lane first, listing type second. A locked line's obligation — not the
|
|
23
|
+
// product's listing type — decides how it settles: an accepted offer is
|
|
24
|
+
// always its own order, and a won auction is always its own order even
|
|
25
|
+
// though `auctionRule` is otherwise unreachable (auctions are
|
|
26
|
+
// `cartEligible: false` for user-initiated adds; the only way one reaches
|
|
27
|
+
// the cart is auction settlement writing the winner's locked line).
|
|
28
|
+
const lane = laneOf(item);
|
|
29
|
+
const rule = lane === CART_LANE.OFFER
|
|
30
|
+
? offerRule
|
|
31
|
+
: lane === CART_LANE.AUCTION
|
|
32
|
+
? auctionRule
|
|
33
|
+
: (getListingRule(lt) ?? getListingRule("standard"));
|
|
22
34
|
// Prize-draw (and future types with maxLinesPerOrder < Infinity) may split
|
|
23
35
|
// one cart item into N virtual batch items.
|
|
24
36
|
const virtualItems = rule.splitMultipleOrders(item);
|
|
@@ -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
|
}
|
|
@@ -1,48 +1,20 @@
|
|
|
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 } from "../../../ui";
|
|
5
3
|
import { AdSlot } from "../../homepage/components/AdSlot";
|
|
6
|
-
import { parseListingSearchParams } from "../../../utils/listing-params";
|
|
7
4
|
import { PreOrdersIndexListing } from "./PreOrdersIndexListing";
|
|
8
|
-
|
|
5
|
+
import { listPublicProducts, parsePublicProductParams, } from "../../../_internal/server/features/products/list-public";
|
|
6
|
+
const PRE_ORDER_LISTING_TYPES = ["pre-order"];
|
|
9
7
|
const DEFAULT_PAGE_SIZE = 24;
|
|
10
8
|
const DEFAULT_SORT = "-createdAt";
|
|
11
|
-
function sp(params, key) {
|
|
12
|
-
const v = params[key];
|
|
13
|
-
return Array.isArray(v) ? v[0] ?? "" : v ?? "";
|
|
14
|
-
}
|
|
15
|
-
function buildPreOrderFilters(params) {
|
|
16
|
-
const parts = ["status==published", "listingType==pre-order"];
|
|
17
|
-
const minPrice = sp(params, "minPrice");
|
|
18
|
-
const maxPrice = sp(params, "maxPrice");
|
|
19
|
-
if (minPrice)
|
|
20
|
-
parts.push(sieveFilter("price", SIEVE_OP.GTE, minPrice));
|
|
21
|
-
if (maxPrice)
|
|
22
|
-
parts.push(sieveFilter("price", SIEVE_OP.LTE, maxPrice));
|
|
23
|
-
const store = sp(params, "storeId");
|
|
24
|
-
if (store)
|
|
25
|
-
parts.push(sieveFilter("storeId", SIEVE_OP.EQ, store));
|
|
26
|
-
const preOrderProductionStatus = sp(params, "preOrderProductionStatus");
|
|
27
|
-
if (preOrderProductionStatus)
|
|
28
|
-
parts.push(sieveFilter("preOrderProductionStatus", SIEVE_OP.EQ, preOrderProductionStatus));
|
|
29
|
-
// Mirror PreOrdersIndexListing's client-side default (same Root Cause pattern as
|
|
30
|
-
// auctions' dateFrom default — SSR initialData is seeded into React Query with
|
|
31
|
-
// staleTime:Infinity, so if this filter doesn't already exclude closed/out-of-stock
|
|
32
|
-
// pre-orders, the client never refetches and they leak into the default view).
|
|
33
|
-
const showClosed = sp(params, "showClosed") === "true";
|
|
34
|
-
if (!showClosed)
|
|
35
|
-
parts.push(sieveFilter("stockQuantity", SIEVE_OP.GT, 0));
|
|
36
|
-
return parts.join(",");
|
|
37
|
-
}
|
|
38
9
|
export async function PreOrdersListView({ searchParams = {} }) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
10
|
+
// Shared with /api/products. `hideSoldByDefault` covers this page's
|
|
11
|
+
// "Show closed" toggle — the stockQuantity predicate runs in memory over a
|
|
12
|
+
// bounded fetch because the default sort is createdAt, not stockQuantity.
|
|
13
|
+
const result = await listPublicProducts(parsePublicProductParams(searchParams, {
|
|
14
|
+
listingTypes: PRE_ORDER_LISTING_TYPES,
|
|
15
|
+
pageSize: DEFAULT_PAGE_SIZE,
|
|
16
|
+
sorts: DEFAULT_SORT,
|
|
17
|
+
hideSoldByDefault: true,
|
|
18
|
+
}));
|
|
47
19
|
return (_jsx(Main, { children: _jsx(Section, { padding: "y-2xl", children: _jsxs(Container, { size: "xl", children: [_jsx(Heading, { level: 1, className: "mb-8", color: "primary", size: "3xl", weight: "semibold", children: "Pre-Orders" }), _jsx(AdSlot, { id: "listing-sidebar-top", className: "mb-6" }), _jsx(PreOrdersIndexListing, { initialData: result ?? undefined }), _jsx(AdSlot, { id: "listing-sidebar-bottom", className: "mt-8" })] }) }) }));
|
|
48
20
|
}
|
|
@@ -1,62 +1,23 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { productRepository } from "../../../repositories";
|
|
3
2
|
import { Container, Heading, Main, Section, Text } from "../../../ui";
|
|
4
3
|
import { AdSlot } from "../../homepage/components/AdSlot";
|
|
5
|
-
import { parseListingSearchParams } from "../../../utils/listing-params";
|
|
6
4
|
import { ProductsIndexListing } from "./ProductsIndexListing";
|
|
7
5
|
import { PRODUCT_FIELDS } from "../../../constants/field-names";
|
|
8
|
-
import { sieveFilter, sieveMultiEq, sieveAnd, SIEVE_OP } from "../../../utils/sieve-builder";
|
|
9
6
|
import { sortBy } from "../../../constants/sort";
|
|
10
7
|
import { ART_STICKERS_TYPE_FILTER_TABS } from "../constants/listing-tabs";
|
|
8
|
+
import { listPublicProducts, parsePublicProductParams, } from "../../../_internal/server/features/products/list-public";
|
|
11
9
|
const ART_STICKERS_LISTING_TYPES = ["art", "stickers"];
|
|
12
|
-
const DEFAULT_PAGE = 1;
|
|
13
10
|
const DEFAULT_PAGE_SIZE = 24;
|
|
14
11
|
const DEFAULT_SORT = sortBy(PRODUCT_FIELDS.CREATED_AT);
|
|
15
|
-
function sp(params, key) {
|
|
16
|
-
const v = params[key];
|
|
17
|
-
return Array.isArray(v) ? v[0] ?? "" : v ?? "";
|
|
18
|
-
}
|
|
19
|
-
function buildArtStickersFilters(params) {
|
|
20
|
-
const listingTypeParam = sp(params, "listingType");
|
|
21
|
-
const listingType = listingTypeParam && ART_STICKERS_LISTING_TYPES.includes(listingTypeParam)
|
|
22
|
-
? listingTypeParam
|
|
23
|
-
: ART_STICKERS_LISTING_TYPES.join("|");
|
|
24
|
-
const parts = [
|
|
25
|
-
sieveFilter(PRODUCT_FIELDS.STATUS, SIEVE_OP.EQ, PRODUCT_FIELDS.STATUS_VALUES.PUBLISHED),
|
|
26
|
-
sieveFilter(PRODUCT_FIELDS.LISTING_TYPE, SIEVE_OP.EQ, listingType),
|
|
27
|
-
];
|
|
28
|
-
const condition = sp(params, "condition");
|
|
29
|
-
if (condition) {
|
|
30
|
-
const values = condition.split("|").filter(Boolean);
|
|
31
|
-
if (values.length === 1)
|
|
32
|
-
parts.push(sieveFilter(PRODUCT_FIELDS.CONDITION, SIEVE_OP.EQ, values[0]));
|
|
33
|
-
else if (values.length > 1)
|
|
34
|
-
parts.push(sieveMultiEq(PRODUCT_FIELDS.CONDITION, values));
|
|
35
|
-
}
|
|
36
|
-
const minPrice = sp(params, "minPrice");
|
|
37
|
-
const maxPrice = sp(params, "maxPrice");
|
|
38
|
-
if (minPrice)
|
|
39
|
-
parts.push(sieveFilter(PRODUCT_FIELDS.PRICE, SIEVE_OP.GTE, minPrice));
|
|
40
|
-
if (maxPrice)
|
|
41
|
-
parts.push(sieveFilter(PRODUCT_FIELDS.PRICE, SIEVE_OP.LTE, maxPrice));
|
|
42
|
-
const storeId = sp(params, "seller");
|
|
43
|
-
if (storeId)
|
|
44
|
-
parts.push(sieveFilter(PRODUCT_FIELDS.STORE_ID, SIEVE_OP.EQ, storeId));
|
|
45
|
-
// Mirror ProductsIndexListing's client-side "Show sold" default — see
|
|
46
|
-
// ProductsIndexPageView.tsx's buildProductFilters for the full explanation.
|
|
47
|
-
const showSold = sp(params, "showSold") === "true";
|
|
48
|
-
if (!showSold)
|
|
49
|
-
parts.push(sieveFilter(PRODUCT_FIELDS.STOCK_QUANTITY, SIEVE_OP.GT, 0));
|
|
50
|
-
return sieveAnd(...parts);
|
|
51
|
-
}
|
|
52
12
|
export async function ArtStickersListView({ searchParams = {} }) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
13
|
+
// Filter semantics live in `listPublicProducts`, shared verbatim with
|
|
14
|
+
// /api/products — so the SSR first paint and the client's refetch can never
|
|
15
|
+
// disagree about what the default view means (Root Cause #30).
|
|
16
|
+
const result = await listPublicProducts(parsePublicProductParams(searchParams, {
|
|
17
|
+
listingTypes: ART_STICKERS_LISTING_TYPES,
|
|
18
|
+
pageSize: DEFAULT_PAGE_SIZE,
|
|
19
|
+
sorts: DEFAULT_SORT,
|
|
20
|
+
hideSoldByDefault: true,
|
|
21
|
+
}));
|
|
61
22
|
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" })] })] }));
|
|
62
23
|
}
|
|
@@ -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
|
}
|
|
@@ -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 }),
|
|
@@ -1,75 +1,22 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { productRepository } from "../../../repositories";
|
|
3
2
|
import { ROUTES } from "../../../constants";
|
|
4
3
|
import { Container, Div, Heading, Main, Section, Text, TextLink } from "../../../ui";
|
|
5
4
|
import { AdSlot } from "../../homepage/components/AdSlot";
|
|
6
|
-
import { parseListingSearchParams } from "../../../utils/listing-params";
|
|
7
5
|
import { ProductsIndexListing } from "./ProductsIndexListing";
|
|
8
6
|
import { PRODUCT_FIELDS } from "../../../constants/field-names";
|
|
9
|
-
import { sieveFilter, sieveMultiEq, sieveAnd, SIEVE_OP } from "../../../utils/sieve-builder";
|
|
10
7
|
import { sortBy } from "../../../constants/sort";
|
|
11
8
|
import { GENERIC_PRODUCT_LISTING_TYPES } from "../constants/listing-tabs";
|
|
12
|
-
|
|
9
|
+
import { listPublicProducts, parsePublicProductParams, } from "../../../_internal/server/features/products/list-public";
|
|
13
10
|
const DEFAULT_PAGE_SIZE = 24;
|
|
14
11
|
const DEFAULT_SORT = sortBy(PRODUCT_FIELDS.CREATED_AT);
|
|
15
|
-
function sp(params, key) {
|
|
16
|
-
const v = params[key];
|
|
17
|
-
return Array.isArray(v) ? v[0] ?? "" : v ?? "";
|
|
18
|
-
}
|
|
19
|
-
function buildProductFilters(params) {
|
|
20
|
-
const listingTypeParam = sp(params, "listingType");
|
|
21
|
-
const listingType = listingTypeParam && GENERIC_PRODUCT_LISTING_TYPES.includes(listingTypeParam)
|
|
22
|
-
? listingTypeParam
|
|
23
|
-
: GENERIC_PRODUCT_LISTING_TYPES.join("|");
|
|
24
|
-
const parts = [
|
|
25
|
-
sieveFilter(PRODUCT_FIELDS.STATUS, SIEVE_OP.EQ, PRODUCT_FIELDS.STATUS_VALUES.PUBLISHED),
|
|
26
|
-
sieveFilter(PRODUCT_FIELDS.LISTING_TYPE, SIEVE_OP.EQ, listingType),
|
|
27
|
-
];
|
|
28
|
-
const condition = sp(params, "condition");
|
|
29
|
-
if (condition) {
|
|
30
|
-
const values = condition.split("|").filter(Boolean);
|
|
31
|
-
if (values.length === 1)
|
|
32
|
-
parts.push(sieveFilter(PRODUCT_FIELDS.CONDITION, SIEVE_OP.EQ, values[0]));
|
|
33
|
-
// BUG FIX: pipe is invalid for ==; expand to multiple AND clauses
|
|
34
|
-
else if (values.length > 1)
|
|
35
|
-
parts.push(sieveMultiEq(PRODUCT_FIELDS.CONDITION, values));
|
|
36
|
-
}
|
|
37
|
-
const minPrice = sp(params, "minPrice");
|
|
38
|
-
const maxPrice = sp(params, "maxPrice");
|
|
39
|
-
if (minPrice)
|
|
40
|
-
parts.push(sieveFilter(PRODUCT_FIELDS.PRICE, SIEVE_OP.GTE, minPrice));
|
|
41
|
-
if (maxPrice)
|
|
42
|
-
parts.push(sieveFilter(PRODUCT_FIELDS.PRICE, SIEVE_OP.LTE, maxPrice));
|
|
43
|
-
const storeId = sp(params, "seller");
|
|
44
|
-
if (storeId)
|
|
45
|
-
parts.push(sieveFilter(PRODUCT_FIELDS.STORE_ID, SIEVE_OP.EQ, storeId));
|
|
46
|
-
const freeShipping = sp(params, "freeShipping");
|
|
47
|
-
if (freeShipping === "true") {
|
|
48
|
-
parts.push(sieveFilter(PRODUCT_FIELDS.SHIPPING_PAID_BY, SIEVE_OP.EQ, PRODUCT_FIELDS.SHIPPING_PAID_BY_VALUES.SELLER));
|
|
49
|
-
}
|
|
50
|
-
// Mirror ProductsIndexListing's client-side "Show sold" default (same Root Cause
|
|
51
|
-
// pattern as auctions' dateFrom default — SSR initialData is seeded into React
|
|
52
|
-
// Query with staleTime:Infinity, so if this filter doesn't already exclude sold /
|
|
53
|
-
// out-of-stock products, the client never refetches and they leak into the default view).
|
|
54
|
-
const showSold = sp(params, "showSold") === "true";
|
|
55
|
-
if (!showSold)
|
|
56
|
-
parts.push(sieveFilter(PRODUCT_FIELDS.STOCK_QUANTITY, SIEVE_OP.GT, 0));
|
|
57
|
-
return sieveAnd(...parts);
|
|
58
|
-
}
|
|
59
12
|
export async function ProductsIndexPageView({ searchParams = {} }) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
sorts: sort,
|
|
69
|
-
page,
|
|
70
|
-
pageSize,
|
|
71
|
-
})
|
|
72
|
-
.catch(() => null);
|
|
73
|
-
const products = result ?? null;
|
|
13
|
+
// See ArtStickersListView — one shared filter implementation with
|
|
14
|
+
// /api/products, so SSR and the client refetch agree by construction.
|
|
15
|
+
const products = await listPublicProducts(parsePublicProductParams(searchParams, {
|
|
16
|
+
listingTypes: GENERIC_PRODUCT_LISTING_TYPES,
|
|
17
|
+
pageSize: DEFAULT_PAGE_SIZE,
|
|
18
|
+
sorts: DEFAULT_SORT,
|
|
19
|
+
hideSoldByDefault: true,
|
|
20
|
+
}));
|
|
74
21
|
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: "Products" }), _jsx(Text, { className: "mt-1", color: "muted", size: "sm", children: "Discover amazing products and deals" }), _jsx(Div, { className: "mt-3", children: _jsx(TextLink, { variant: "bare", href: String(ROUTES.PUBLIC.AUCTIONS), rounded: "full", paddingX: "sm", paddingY: "2xs", size: "xs", weight: "medium", layout: "inline-flex", align: "center", gap: "sm", className: "border border-primary/30 bg-primary/10 text-primary-700 dark:text-primary-400 hover:bg-primary/15 transition-colors", children: "\uD83C\uDFF7\uFE0F Looking for unique deals? Browse Auctions \u2192" }) })] }) }), _jsxs(Container, { size: "xl", padding: "x-md", children: [_jsx(AdSlot, { id: "listing-sidebar-top", className: "mb-4 mt-4" }), _jsx(ProductsIndexListing, { initialData: products }), _jsx(AdSlot, { id: "listing-sidebar-bottom", className: "mt-8" })] })] }));
|
|
75
22
|
}
|