@mohasinac/appkit 4.11.0 → 4.11.2
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.js +27 -23
- 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/products/data.d.ts +2 -2
- package/dist/_internal/server/features/products/data.js +5 -23
- package/dist/_internal/server/features/products/index.d.ts +1 -0
- package/dist/_internal/server/features/products/index.js +1 -0
- package/dist/_internal/server/features/products/list-public.d.ts +111 -0
- package/dist/_internal/server/features/products/list-public.js +342 -0
- 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/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/products/to-product-item.d.ts +25 -0
- package/dist/_internal/shared/features/products/to-product-item.js +45 -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/client.d.ts +2 -1
- package/dist/client.js +4 -1
- package/dist/features/account/components/NotificationBell.js +1 -0
- package/dist/features/account/components/UserOffersPanel.js +8 -1
- package/dist/features/admin/actions/notification-actions.js +1 -1
- 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/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 -2
- package/dist/features/contact/email.js +52 -1
- package/dist/features/grouped/components/GroupedListingsCarousel.js +4 -1
- package/dist/features/orders/repository/orders.repository.d.ts +0 -15
- package/dist/features/orders/repository/orders.repository.js +0 -27
- package/dist/features/orders/utils/order-splitter.js +14 -2
- package/dist/features/pre-orders/components/PreOrdersListView.js +11 -39
- package/dist/features/products/components/ArtStickersListView.js +10 -49
- package/dist/features/products/components/ProductsIndexPageView.js +9 -62
- package/dist/features/products/repository/products.repository.js +41 -5
- 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/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/index.d.ts +2 -1
- package/dist/index.js +4 -1
- 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/package.json +1 -1
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export declare const CART_LANE: {
|
|
2
|
+
readonly AUCTION: "auction";
|
|
3
|
+
readonly OFFER: "offer";
|
|
4
|
+
readonly STANDARD: "standard";
|
|
5
|
+
};
|
|
6
|
+
export type CartLane = (typeof CART_LANE)[keyof typeof CART_LANE];
|
|
7
|
+
/**
|
|
8
|
+
* Highest obligation first. An auction win is a completed sale the buyer already
|
|
9
|
+
* committed to by bidding; an accepted offer is a price the seller has held for
|
|
10
|
+
* them on a clock; ordinary cart items carry no commitment at all.
|
|
11
|
+
*/
|
|
12
|
+
export declare const CART_LANE_PRIORITY: readonly CartLane[];
|
|
13
|
+
export declare const CART_LANE_LABELS: Record<CartLane, string>;
|
|
14
|
+
/** The two lanes whose price is fixed outside the listing. */
|
|
15
|
+
export declare function isLockedLane(lane: CartLane): boolean;
|
|
16
|
+
/** Minimal shape this module needs — works on cart documents and client rows alike. */
|
|
17
|
+
export interface LaneAssignable {
|
|
18
|
+
isAuctionWin?: boolean;
|
|
19
|
+
isOffer?: boolean;
|
|
20
|
+
offerId?: string;
|
|
21
|
+
bidId?: string;
|
|
22
|
+
}
|
|
23
|
+
export declare function laneOf(item: LaneAssignable): CartLane;
|
|
24
|
+
export declare function laneItems<T extends LaneAssignable>(items: readonly T[], lane: CartLane): T[];
|
|
25
|
+
export declare function laneCounts(items: readonly LaneAssignable[]): Record<CartLane, number>;
|
|
26
|
+
/**
|
|
27
|
+
* The one lane the buyer may check out right now, or null for an empty cart.
|
|
28
|
+
*/
|
|
29
|
+
export declare function activeLane(items: readonly LaneAssignable[]): CartLane | null;
|
|
30
|
+
export declare function isLaneCheckoutable(items: readonly LaneAssignable[], lane: CartLane): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Why `lane` can't be checked out right now — null when it can.
|
|
33
|
+
* Phrased for the buyer, naming the lane that's blocking and what to do.
|
|
34
|
+
*/
|
|
35
|
+
export declare function laneBlockReason(items: readonly LaneAssignable[], lane: CartLane): string | null;
|
|
36
|
+
/**
|
|
37
|
+
* Whether a NEW item may be added to the cart at all. While a higher-obligation
|
|
38
|
+
* lane is pending, adding more shopping would let the buyer keep deferring the
|
|
39
|
+
* thing they already committed to.
|
|
40
|
+
*/
|
|
41
|
+
export declare function canAddNewItems(items: readonly LaneAssignable[]): boolean;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* WHY: A cart can hold three kinds of line with three different obligations — a
|
|
3
|
+
* won auction (you owe it), an accepted offer (you negotiated it, at a
|
|
4
|
+
* locked price, on a deadline), and ordinary shopping. Mixing them into one
|
|
5
|
+
* total lets a buyer settle a ₹200 sticker while a ₹30,000 auction win they
|
|
6
|
+
* already committed to sits unpaid, and makes every total ambiguous.
|
|
7
|
+
* WHAT: The lane model — a derived (never stored) partition of the cart, with a
|
|
8
|
+
* strict priority order. Exactly one lane is checkout-able at a time: the
|
|
9
|
+
* highest-priority non-empty one. Lower lanes stay visible but disabled,
|
|
10
|
+
* and while a higher lane is pending nothing new may be added to the cart.
|
|
11
|
+
*
|
|
12
|
+
* Derived on purpose. A stored `lane` mirror field would drift the first time a
|
|
13
|
+
* write path forgot it (CLAUDE.md Root Cause #42), exactly as the manual-payment
|
|
14
|
+
* queue state is derived rather than stored.
|
|
15
|
+
*
|
|
16
|
+
* EXPORTS:
|
|
17
|
+
* CartLane, CART_LANE_PRIORITY, CART_LANE_LABELS, laneOf, activeLane,
|
|
18
|
+
* laneItems, laneCounts, isLaneCheckoutable, laneBlockReason, isLockedLane
|
|
19
|
+
*
|
|
20
|
+
* @tag domain:checkout,cart
|
|
21
|
+
* @tag layer:shared
|
|
22
|
+
* @tag pattern:none
|
|
23
|
+
* @tag access:isomorphic
|
|
24
|
+
* @tag consumers:CartRouteClient,CheckoutRouteClient,cart-actions,order-splitter,checkout/actions
|
|
25
|
+
* @tag sideEffects:none
|
|
26
|
+
*/
|
|
27
|
+
export const CART_LANE = {
|
|
28
|
+
AUCTION: "auction",
|
|
29
|
+
OFFER: "offer",
|
|
30
|
+
STANDARD: "standard",
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Highest obligation first. An auction win is a completed sale the buyer already
|
|
34
|
+
* committed to by bidding; an accepted offer is a price the seller has held for
|
|
35
|
+
* them on a clock; ordinary cart items carry no commitment at all.
|
|
36
|
+
*/
|
|
37
|
+
export const CART_LANE_PRIORITY = [
|
|
38
|
+
CART_LANE.AUCTION,
|
|
39
|
+
CART_LANE.OFFER,
|
|
40
|
+
CART_LANE.STANDARD,
|
|
41
|
+
];
|
|
42
|
+
export const CART_LANE_LABELS = {
|
|
43
|
+
auction: "Auction wins",
|
|
44
|
+
offer: "Accepted offers",
|
|
45
|
+
standard: "Cart",
|
|
46
|
+
};
|
|
47
|
+
/** The two lanes whose price is fixed outside the listing. */
|
|
48
|
+
export function isLockedLane(lane) {
|
|
49
|
+
return lane === CART_LANE.AUCTION || lane === CART_LANE.OFFER;
|
|
50
|
+
}
|
|
51
|
+
export function laneOf(item) {
|
|
52
|
+
if (item.isAuctionWin || item.bidId)
|
|
53
|
+
return CART_LANE.AUCTION;
|
|
54
|
+
if (item.isOffer || item.offerId)
|
|
55
|
+
return CART_LANE.OFFER;
|
|
56
|
+
return CART_LANE.STANDARD;
|
|
57
|
+
}
|
|
58
|
+
export function laneItems(items, lane) {
|
|
59
|
+
return items.filter((i) => laneOf(i) === lane);
|
|
60
|
+
}
|
|
61
|
+
export function laneCounts(items) {
|
|
62
|
+
const counts = { auction: 0, offer: 0, standard: 0 };
|
|
63
|
+
for (const item of items)
|
|
64
|
+
counts[laneOf(item)] += 1;
|
|
65
|
+
return counts;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* The one lane the buyer may check out right now, or null for an empty cart.
|
|
69
|
+
*/
|
|
70
|
+
export function activeLane(items) {
|
|
71
|
+
const counts = laneCounts(items);
|
|
72
|
+
return CART_LANE_PRIORITY.find((lane) => counts[lane] > 0) ?? null;
|
|
73
|
+
}
|
|
74
|
+
export function isLaneCheckoutable(items, lane) {
|
|
75
|
+
return activeLane(items) === lane;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Why `lane` can't be checked out right now — null when it can.
|
|
79
|
+
* Phrased for the buyer, naming the lane that's blocking and what to do.
|
|
80
|
+
*/
|
|
81
|
+
export function laneBlockReason(items, lane) {
|
|
82
|
+
const active = activeLane(items);
|
|
83
|
+
if (active === lane)
|
|
84
|
+
return null;
|
|
85
|
+
const counts = laneCounts(items);
|
|
86
|
+
if (counts[lane] === 0)
|
|
87
|
+
return "There's nothing in this tab yet.";
|
|
88
|
+
if (active === CART_LANE.AUCTION) {
|
|
89
|
+
const n = counts.auction;
|
|
90
|
+
return `Settle your ${n} won auction${n === 1 ? "" : "s"} first — auction wins are already committed purchases.`;
|
|
91
|
+
}
|
|
92
|
+
if (active === CART_LANE.OFFER) {
|
|
93
|
+
const n = counts.offer;
|
|
94
|
+
return `Complete your ${n} accepted offer${n === 1 ? "" : "s"} first — the agreed price is only held for a limited time.`;
|
|
95
|
+
}
|
|
96
|
+
return "This tab can't be checked out right now.";
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Whether a NEW item may be added to the cart at all. While a higher-obligation
|
|
100
|
+
* lane is pending, adding more shopping would let the buyer keep deferring the
|
|
101
|
+
* thing they already committed to.
|
|
102
|
+
*/
|
|
103
|
+
export function canAddNewItems(items) {
|
|
104
|
+
const active = activeLane(items);
|
|
105
|
+
return active === null || active === CART_LANE.STANDARD;
|
|
106
|
+
}
|
|
@@ -6,6 +6,25 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import type { CartItemDocument } from "../../../features/cart/schemas/firestore";
|
|
8
8
|
import type { ProductDocument } from "../../../features/products/schemas/firestore";
|
|
9
|
+
/**
|
|
10
|
+
* The single source of truth for "what do we charge for this line".
|
|
11
|
+
*
|
|
12
|
+
* Three cases, in priority order:
|
|
13
|
+
*
|
|
14
|
+
* 1. **A locked price** — an accepted offer or a won auction. `lockedPrice` is
|
|
15
|
+
* the negotiated/won amount and it OVERRIDES the current listing price, which
|
|
16
|
+
* is the whole point of the negotiation. Until 2026-08-21 none of the five
|
|
17
|
+
* copies of this function looked at `lockedPrice`, so an accepted offer was
|
|
18
|
+
* billed at the seller's listed price while the cart UI displayed the agreed
|
|
19
|
+
* one (`item.lockedPrice ?? item.price`) — the buyer saw ₹X and paid ₹Y.
|
|
20
|
+
* 2. **A bundle line** (SB-UNI-5 2026-05-13) — `item.price` is the bundle price
|
|
21
|
+
* locked at add-time.
|
|
22
|
+
* 3. **Everything else** — the live Firestore price, so a stale cart-cached
|
|
23
|
+
* price is never charged on a COD/UPI order.
|
|
24
|
+
*
|
|
25
|
+
* `product` may be null for a locked line whose listing has since been archived;
|
|
26
|
+
* the locked price is still owed, so that case must not dereference `product`.
|
|
27
|
+
*/
|
|
9
28
|
export declare function unitPriceFor(item: CartItemDocument, product: ProductDocument | null): number;
|
|
10
29
|
/**
|
|
11
30
|
* P-6 — pre-order groups charge each product's own `preOrderDepositPercent`
|
|
@@ -1,11 +1,35 @@
|
|
|
1
1
|
import { roundRupees } from "../../../utils/number.formatter";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
/**
|
|
3
|
+
* The single source of truth for "what do we charge for this line".
|
|
4
|
+
*
|
|
5
|
+
* Three cases, in priority order:
|
|
6
|
+
*
|
|
7
|
+
* 1. **A locked price** — an accepted offer or a won auction. `lockedPrice` is
|
|
8
|
+
* the negotiated/won amount and it OVERRIDES the current listing price, which
|
|
9
|
+
* is the whole point of the negotiation. Until 2026-08-21 none of the five
|
|
10
|
+
* copies of this function looked at `lockedPrice`, so an accepted offer was
|
|
11
|
+
* billed at the seller's listed price while the cart UI displayed the agreed
|
|
12
|
+
* one (`item.lockedPrice ?? item.price`) — the buyer saw ₹X and paid ₹Y.
|
|
13
|
+
* 2. **A bundle line** (SB-UNI-5 2026-05-13) — `item.price` is the bundle price
|
|
14
|
+
* locked at add-time.
|
|
15
|
+
* 3. **Everything else** — the live Firestore price, so a stale cart-cached
|
|
16
|
+
* price is never charged on a COD/UPI order.
|
|
17
|
+
*
|
|
18
|
+
* `product` may be null for a locked line whose listing has since been archived;
|
|
19
|
+
* the locked price is still owed, so that case must not dereference `product`.
|
|
20
|
+
*/
|
|
5
21
|
export function unitPriceFor(item, product) {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
22
|
+
if (typeof item.lockedPrice === "number" && item.lockedPrice > 0) {
|
|
23
|
+
return item.lockedPrice;
|
|
24
|
+
}
|
|
25
|
+
if (item.bundleCategorySlug && item.bundleProductIds?.length) {
|
|
26
|
+
return item.price;
|
|
27
|
+
}
|
|
28
|
+
// Null-safe fallback: one of the four call sites this replaced (the cart
|
|
29
|
+
// summary/preview path) already guarded with `product?.price ?? item.price`,
|
|
30
|
+
// and collapsing onto a non-null assertion would have regressed it into a
|
|
31
|
+
// crash whenever a cart line outlives its listing.
|
|
32
|
+
return product?.price ?? item.price;
|
|
9
33
|
}
|
|
10
34
|
/**
|
|
11
35
|
* P-6 — pre-order groups charge each product's own `preOrderDepositPercent`
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* toProductItem — Firestore doc → card-grid `ProductItem` mapper.
|
|
3
|
+
*
|
|
4
|
+
* Lives in `_internal/shared/` (client+server safe), NOT in
|
|
5
|
+
* `_internal/server/features/products/data.ts` where it was originally
|
|
6
|
+
* written. It is a pure field mapping with no server dependencies of its
|
|
7
|
+
* own, but `data.ts` transitively reaches `providers/db-firebase/admin.js`
|
|
8
|
+
* (firebase-admin), and `GroupedListingsCarousel.tsx` is a `"use client"`
|
|
9
|
+
* component that imports this function as a VALUE. Turbopack resolves a
|
|
10
|
+
* module's full static import graph before tree-shaking, so that single
|
|
11
|
+
* import dragged the whole firebase-admin chain into the client bundle and
|
|
12
|
+
* failed the production build outright:
|
|
13
|
+
*
|
|
14
|
+
* Module not found: .../providers/db-firebase/admin.js
|
|
15
|
+
* [Client Component Browser] → GroupedListingsCarousel
|
|
16
|
+
* → _internal/server/features/products/data.js
|
|
17
|
+
*
|
|
18
|
+
* This is the Turbopack client-bundle trap from CLAUDE.md's appkit Export
|
|
19
|
+
* Rules / Recurrent Root Cause #6, reached through an ordinary deep import
|
|
20
|
+
* rather than a barrel. Keep this file free of any server-only import so a
|
|
21
|
+
* client component can always call it directly.
|
|
22
|
+
*/
|
|
23
|
+
import type { FirestoreDocument } from "../../../../schemas/types";
|
|
24
|
+
import type { ProductItem } from "../../../../features/products/types";
|
|
25
|
+
export declare function toProductItem(doc: FirestoreDocument): ProductItem;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* toProductItem — Firestore doc → card-grid `ProductItem` mapper.
|
|
3
|
+
*
|
|
4
|
+
* Lives in `_internal/shared/` (client+server safe), NOT in
|
|
5
|
+
* `_internal/server/features/products/data.ts` where it was originally
|
|
6
|
+
* written. It is a pure field mapping with no server dependencies of its
|
|
7
|
+
* own, but `data.ts` transitively reaches `providers/db-firebase/admin.js`
|
|
8
|
+
* (firebase-admin), and `GroupedListingsCarousel.tsx` is a `"use client"`
|
|
9
|
+
* component that imports this function as a VALUE. Turbopack resolves a
|
|
10
|
+
* module's full static import graph before tree-shaking, so that single
|
|
11
|
+
* import dragged the whole firebase-admin chain into the client bundle and
|
|
12
|
+
* failed the production build outright:
|
|
13
|
+
*
|
|
14
|
+
* Module not found: .../providers/db-firebase/admin.js
|
|
15
|
+
* [Client Component Browser] → GroupedListingsCarousel
|
|
16
|
+
* → _internal/server/features/products/data.js
|
|
17
|
+
*
|
|
18
|
+
* This is the Turbopack client-bundle trap from CLAUDE.md's appkit Export
|
|
19
|
+
* Rules / Recurrent Root Cause #6, reached through an ordinary deep import
|
|
20
|
+
* rather than a barrel. Keep this file free of any server-only import so a
|
|
21
|
+
* client component can always call it directly.
|
|
22
|
+
*/
|
|
23
|
+
export function toProductItem(doc) {
|
|
24
|
+
return {
|
|
25
|
+
id: String(doc.id ?? ""),
|
|
26
|
+
title: String(doc.title ?? doc.name ?? ""),
|
|
27
|
+
price: typeof doc.price === "number" ? doc.price : 0,
|
|
28
|
+
originalPrice: typeof doc.originalPrice === "number" ? doc.originalPrice : undefined,
|
|
29
|
+
mainImage: Array.isArray(doc.images)
|
|
30
|
+
? doc.images[0]
|
|
31
|
+
: typeof doc.mainImage === "string"
|
|
32
|
+
? doc.mainImage
|
|
33
|
+
: undefined,
|
|
34
|
+
status: doc.status ?? "published",
|
|
35
|
+
slug: typeof doc.slug === "string" ? doc.slug : undefined,
|
|
36
|
+
storeName: typeof doc.storeName === "string" ? doc.storeName : undefined,
|
|
37
|
+
rating: typeof doc.rating === "number" ? doc.rating : undefined,
|
|
38
|
+
reviewCount: typeof doc.reviewCount === "number" ? doc.reviewCount : undefined,
|
|
39
|
+
// Without this, every related-item card silently linked to the standard
|
|
40
|
+
// PDP regardless of its real type (pluginFor() defaults to "standard"
|
|
41
|
+
// when listingType is missing) — same bug class as the Phase 1 routing
|
|
42
|
+
// fix, one hop further downstream in the related-items card link path.
|
|
43
|
+
listingType: doc.listingType ?? undefined,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
@@ -28,6 +28,7 @@ interface FeatureFlagSnapshot {
|
|
|
28
28
|
*/
|
|
29
29
|
export declare function isListingTypeEnabled(type: ListingType, settings: FeatureFlagSnapshot | null | undefined): boolean;
|
|
30
30
|
export declare function isCategoryTypeEnabled(type: CategoryType, settings: FeatureFlagSnapshot | null | undefined): boolean;
|
|
31
|
+
export declare const ALL_LISTING_TYPES: ListingType[];
|
|
31
32
|
/** Pull the full list of enabled listing types in canonical iteration order. */
|
|
32
33
|
export declare function enabledListingTypes(settings: FeatureFlagSnapshot | null | undefined): ListingType[];
|
|
33
34
|
export declare function enabledCategoryTypes(settings: FeatureFlagSnapshot | null | undefined): CategoryType[];
|
|
@@ -26,18 +26,32 @@ export function isCategoryTypeEnabled(type, settings) {
|
|
|
26
26
|
const flag = settings?.featureFlags?.categoryTypes?.[type];
|
|
27
27
|
return flag !== false;
|
|
28
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Every listing type, in canonical iteration order.
|
|
31
|
+
*
|
|
32
|
+
* Declared as `Record<ListingType, true>` rather than a plain array on purpose:
|
|
33
|
+
* adding a member to the `ListingType` union without adding it here is then a
|
|
34
|
+
* COMPILE error, not a silent omission. `art` / `stickers` were missing from
|
|
35
|
+
* the old hand-written array for exactly that reason, which meant
|
|
36
|
+
* `/api/products`' "strip disabled types" post-filter dropped every art and
|
|
37
|
+
* sticker row from any call that didn't name a listingType explicitly
|
|
38
|
+
* (homepage, search, related-items).
|
|
39
|
+
*/
|
|
40
|
+
const ALL_LISTING_TYPES_MAP = {
|
|
41
|
+
standard: true,
|
|
42
|
+
auction: true,
|
|
43
|
+
"pre-order": true,
|
|
44
|
+
"prize-draw": true,
|
|
45
|
+
classified: true,
|
|
46
|
+
"digital-code": true,
|
|
47
|
+
live: true,
|
|
48
|
+
art: true,
|
|
49
|
+
stickers: true,
|
|
50
|
+
};
|
|
51
|
+
export const ALL_LISTING_TYPES = Object.keys(ALL_LISTING_TYPES_MAP);
|
|
29
52
|
/** Pull the full list of enabled listing types in canonical iteration order. */
|
|
30
53
|
export function enabledListingTypes(settings) {
|
|
31
|
-
|
|
32
|
-
"standard",
|
|
33
|
-
"auction",
|
|
34
|
-
"pre-order",
|
|
35
|
-
"prize-draw",
|
|
36
|
-
"classified",
|
|
37
|
-
"digital-code",
|
|
38
|
-
"live",
|
|
39
|
-
];
|
|
40
|
-
return all.filter((t) => isListingTypeEnabled(t, settings));
|
|
54
|
+
return ALL_LISTING_TYPES.filter((t) => isListingTypeEnabled(t, settings));
|
|
41
55
|
}
|
|
42
56
|
export function enabledCategoryTypes(settings) {
|
|
43
57
|
const all = ["category", "sublisting", "brand", "bundle"];
|
package/dist/client.d.ts
CHANGED
|
@@ -321,7 +321,7 @@ export type { GuestHistoryItem, GuestHistoryType, UserHistoryItem, HistoryProduc
|
|
|
321
321
|
export type { TrackArgs as TrackHistoryArgs } from "./features/history/hooks/useHistory";
|
|
322
322
|
export { WISHLIST_MAX, HISTORY_MAX, CART_MAX_ITEMS, } from "./constants/limits";
|
|
323
323
|
export { normalizeListingType, isAuctionListing, isPreOrderListing, isStandardListing, isPrizeDrawListing, isClassifiedListing, isDigitalCodeListing, isLiveListing, isArtListing, isStickersListing, } from "./features/products/utils/listing-type";
|
|
324
|
-
export { isListingTypeEnabled, isCategoryTypeEnabled, enabledListingTypes, enabledCategoryTypes, } from "./_internal/shared/listing-types/feature-flags";
|
|
324
|
+
export { isListingTypeEnabled, isCategoryTypeEnabled, enabledListingTypes, enabledCategoryTypes, ALL_LISTING_TYPES, } from "./_internal/shared/listing-types/feature-flags";
|
|
325
325
|
export { actionTracker, setActionTrackerSink, resetActionTrackerSink, type ActionEvent, type ActionTrackerSink, } from "./_internal/shared/listing-types/action-tracker";
|
|
326
326
|
export { cartRequiresShipping, cartIsDigitalOnly, cartIsChatOnly, } from "./_internal/shared/listing-types/cart-shipping";
|
|
327
327
|
export { ACTIONS, action, act, canPerformAction, actionsForListingType, actionLabel, type ActionDef, type ActionKind, type ActionResource, type ActionTree, type ActionConfirmation, } from "./_internal/shared/actions/action-registry";
|
|
@@ -410,3 +410,4 @@ export { API_ENDPOINTS } from "./constants/index";
|
|
|
410
410
|
export { SELLER_LISTING_TABS, type SellerListingTabId, } from "./features/products/constants/listing-tabs";
|
|
411
411
|
export { ALL_TAB, EMPTY_TAB, ADMIN_PRODUCT_STATUS_TABS, ADMIN_PRODUCT_LISTING_TYPE_TABS, ADMIN_BLOG_STATUS_TABS, ADMIN_USER_STATUS_TABS, ADMIN_USER_ROLE_TABS, ADMIN_STORE_STATUS_TABS, ADMIN_PAYOUT_STATUS_TABS, ADMIN_ORDER_STATUS_TABS, ADMIN_REVIEW_STATUS_TABS, ADMIN_REVIEW_RATING_TABS, ADMIN_BID_STATUS_TABS, ADMIN_CONTACT_STATUS_TABS, ADMIN_NEWSLETTER_STATUS_TABS, ADMIN_EVENT_ENTRY_STATUS_TABS, ADMIN_EVENT_STATUS_TABS, ADMIN_CART_OWNERSHIP_TABS, ADMIN_COUPON_TYPE_TABS, SELLER_PRODUCT_STATUS_TABS, SELLER_AUCTION_STATUS_TABS, SELLER_ORDER_STATUS_TABS, SELLER_OFFER_STATUS_TABS, SELLER_BID_STATUS_TABS, } from "./features/admin/constants/filter-tabs";
|
|
412
412
|
export { PRODUCT_FIELDS, PRODUCT_STATUS_TRANSITIONS, ORDER_FIELDS, REVIEW_FIELDS, BID_FIELDS, AD_FIELDS, EVENT_FIELDS, EVENT_ENTRY_FIELDS, PAYOUT_FIELDS, STORE_FIELDS, CATEGORY_FIELDS, BLOG_FIELDS, USER_FIELDS, ADDRESS_FIELDS, BRAND_FIELDS, CART_FIELDS, WISHLIST_FIELDS, HISTORY_FIELDS, NOTIFICATION_FIELDS, SESSION_FIELDS, COUPON_USAGE_FIELDS, CONVERSATION_FIELDS, SCAMMER_FIELDS, SUPPORT_TICKET_FIELDS, CAROUSEL_FIELDS, COUPON_FIELDS, FAQ_FIELDS, HOMEPAGE_SECTION_FIELDS, SITE_SETTINGS_FIELDS, COMMON_FIELDS, OAUTH_STATE_VALUES, SCHEMA_DEFAULTS, } from "./constants/field-names";
|
|
413
|
+
export { CART_LANE, CART_LANE_PRIORITY, CART_LANE_LABELS, laneOf, activeLane, laneItems, laneCounts, isLaneCheckoutable, laneBlockReason, isLockedLane, canAddNewItems, type CartLane, type LaneAssignable, } from "./_internal/shared/checkout/lanes";
|
package/dist/client.js
CHANGED
|
@@ -319,7 +319,7 @@ export { WISHLIST_MAX, HISTORY_MAX, CART_MAX_ITEMS, } from "./constants/limits";
|
|
|
319
319
|
// SB-UNI-F 2026-05-13 — Phase 2 predicates surfaced through client barrel.
|
|
320
320
|
export { normalizeListingType, isAuctionListing, isPreOrderListing, isStandardListing, isPrizeDrawListing, isClassifiedListing, isDigitalCodeListing, isLiveListing, isArtListing, isStickersListing, } from "./features/products/utils/listing-type";
|
|
321
321
|
// SB-UNI-X4 2026-05-13 — per-type feature-flag helpers (client-safe).
|
|
322
|
-
export { isListingTypeEnabled, isCategoryTypeEnabled, enabledListingTypes, enabledCategoryTypes, } from "./_internal/shared/listing-types/feature-flags";
|
|
322
|
+
export { isListingTypeEnabled, isCategoryTypeEnabled, enabledListingTypes, enabledCategoryTypes, ALL_LISTING_TYPES, } from "./_internal/shared/listing-types/feature-flags";
|
|
323
323
|
// SB-UNI-X5 2026-05-13 — action telemetry sink (client-safe; defaults to
|
|
324
324
|
// a no-op + console.debug in dev).
|
|
325
325
|
export { actionTracker, setActionTrackerSink, resetActionTrackerSink, } from "./_internal/shared/listing-types/action-tracker";
|
|
@@ -393,3 +393,6 @@ export { API_ENDPOINTS } from "./constants/index";
|
|
|
393
393
|
export { SELLER_LISTING_TABS, } from "./features/products/constants/listing-tabs";
|
|
394
394
|
export { ALL_TAB, EMPTY_TAB, ADMIN_PRODUCT_STATUS_TABS, ADMIN_PRODUCT_LISTING_TYPE_TABS, ADMIN_BLOG_STATUS_TABS, ADMIN_USER_STATUS_TABS, ADMIN_USER_ROLE_TABS, ADMIN_STORE_STATUS_TABS, ADMIN_PAYOUT_STATUS_TABS, ADMIN_ORDER_STATUS_TABS, ADMIN_REVIEW_STATUS_TABS, ADMIN_REVIEW_RATING_TABS, ADMIN_BID_STATUS_TABS, ADMIN_CONTACT_STATUS_TABS, ADMIN_NEWSLETTER_STATUS_TABS, ADMIN_EVENT_ENTRY_STATUS_TABS, ADMIN_EVENT_STATUS_TABS, ADMIN_CART_OWNERSHIP_TABS, ADMIN_COUPON_TYPE_TABS, SELLER_PRODUCT_STATUS_TABS, SELLER_AUCTION_STATUS_TABS, SELLER_ORDER_STATUS_TABS, SELLER_OFFER_STATUS_TABS, SELLER_BID_STATUS_TABS, } from "./features/admin/constants/filter-tabs";
|
|
395
395
|
export { PRODUCT_FIELDS, PRODUCT_STATUS_TRANSITIONS, ORDER_FIELDS, REVIEW_FIELDS, BID_FIELDS, AD_FIELDS, EVENT_FIELDS, EVENT_ENTRY_FIELDS, PAYOUT_FIELDS, STORE_FIELDS, CATEGORY_FIELDS, BLOG_FIELDS, USER_FIELDS, ADDRESS_FIELDS, BRAND_FIELDS, CART_FIELDS, WISHLIST_FIELDS, HISTORY_FIELDS, NOTIFICATION_FIELDS, SESSION_FIELDS, COUPON_USAGE_FIELDS, CONVERSATION_FIELDS, SCAMMER_FIELDS, SUPPORT_TICKET_FIELDS, CAROUSEL_FIELDS, COUPON_FIELDS, FAQ_FIELDS, HOMEPAGE_SECTION_FIELDS, SITE_SETTINGS_FIELDS, COMMON_FIELDS, OAUTH_STATE_VALUES, SCHEMA_DEFAULTS, } from "./constants/field-names";
|
|
396
|
+
// Checkout lanes — the derived auction > offer > standard partition of the
|
|
397
|
+
// cart, and the priority rule that decides which one may be checked out.
|
|
398
|
+
export { CART_LANE, CART_LANE_PRIORITY, CART_LANE_LABELS, laneOf, activeLane, laneItems, laneCounts, isLaneCheckoutable, laneBlockReason, isLockedLane, canAddNewItems, } from "./_internal/shared/checkout/lanes";
|
|
@@ -79,8 +79,15 @@ export function UserOffersPanel({ fetchEndpoint = ACCOUNT_ENDPOINTS.OFFERS, onAc
|
|
|
79
79
|
}
|
|
80
80
|
throw new Error(`Error ${res.status}`);
|
|
81
81
|
}
|
|
82
|
+
// `/api/user/offers` replies through `successResponse`, i.e.
|
|
83
|
+
// `{ success, data: { items, total, … } }`. This used to read `json.items`
|
|
84
|
+
// straight off the envelope — always undefined — so "My Offers" showed
|
|
85
|
+
// "No offers yet" for every buyer, forever, with no error to notice.
|
|
86
|
+
// Both shapes are still accepted so an unwrapped caller keeps working.
|
|
82
87
|
const json = (await res.json());
|
|
83
|
-
const items = Array.isArray(json)
|
|
88
|
+
const items = Array.isArray(json)
|
|
89
|
+
? json
|
|
90
|
+
: (json.data?.items ?? json.items ?? []);
|
|
84
91
|
setOffers(items);
|
|
85
92
|
}
|
|
86
93
|
catch (err) {
|
|
@@ -106,7 +106,7 @@ export async function sendNotification(input) {
|
|
|
106
106
|
order_placed: "orderUpdates", order_confirmed: "orderUpdates",
|
|
107
107
|
order_shipped: "orderUpdates", order_delivered: "orderUpdates",
|
|
108
108
|
order_cancelled: "orderUpdates",
|
|
109
|
-
bid_placed: "bids", bid_outbid: "bids", bid_won: "bids", bid_lost: "bids",
|
|
109
|
+
bid_placed: "bids", bid_outbid: "bids", bid_won: "bids", bid_lost: "bids", auction_ended: "bids",
|
|
110
110
|
review_approved: "reviews", review_replied: "reviews",
|
|
111
111
|
promotion: "promotions",
|
|
112
112
|
system: "system", welcome: "system", account_action: "system",
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import type { AboutContentDocument } from "../../about/schemas/firestore";
|
|
6
6
|
export declare const ADMIN_CHECKOUT_BYPASS_FLAG_KEY: "adminCheckoutBypass";
|
|
7
|
-
export type NotificationType = "order_placed" | "order_confirmed" | "order_shipped" | "order_delivered" | "order_cancelled" | "bid_placed" | "bid_outbid" | "bid_won" | "bid_lost" | "review_approved" | "review_replied" | "product_available" | "promotion" | "system" | "welcome" | "account_action" | "offer_received" | "offer_responded" | "offer_expired" | "offer_counter_accepted" | "refund_initiated" | "prize_won" | "prize_reveal_expired" | "emi_installment_due_soon" | "emi_installment_overdue" | "payment_review";
|
|
7
|
+
export type NotificationType = "order_placed" | "order_confirmed" | "order_shipped" | "order_delivered" | "order_cancelled" | "bid_placed" | "bid_outbid" | "bid_won" | "bid_lost" | "auction_ended" | "review_approved" | "review_replied" | "product_available" | "promotion" | "system" | "welcome" | "account_action" | "offer_received" | "offer_responded" | "offer_expired" | "offer_counter_accepted" | "refund_initiated" | "prize_won" | "prize_reveal_expired" | "emi_installment_due_soon" | "emi_installment_overdue" | "payment_review";
|
|
8
8
|
import type { BaseDocument } from "../../../_internal/shared/types/base-document";
|
|
9
9
|
export type NotificationPriority = "low" | "normal" | "high";
|
|
10
10
|
export interface NotificationDocument extends BaseDocument {
|
|
@@ -53,6 +53,7 @@ export declare const NOTIFICATION_FIELDS: {
|
|
|
53
53
|
readonly BID_OUTBID: NotificationType;
|
|
54
54
|
readonly BID_WON: NotificationType;
|
|
55
55
|
readonly BID_LOST: NotificationType;
|
|
56
|
+
readonly AUCTION_ENDED: NotificationType;
|
|
56
57
|
readonly REVIEW_APPROVED: NotificationType;
|
|
57
58
|
readonly REVIEW_REPLIED: NotificationType;
|
|
58
59
|
readonly PRODUCT_AVAILABLE: NotificationType;
|
|
@@ -41,6 +41,7 @@ export const NOTIFICATION_FIELDS = {
|
|
|
41
41
|
BID_OUTBID: "bid_outbid",
|
|
42
42
|
BID_WON: "bid_won",
|
|
43
43
|
BID_LOST: "bid_lost",
|
|
44
|
+
AUCTION_ENDED: "auction_ended",
|
|
44
45
|
REVIEW_APPROVED: "review_approved",
|
|
45
46
|
REVIEW_REPLIED: "review_replied",
|
|
46
47
|
PRODUCT_AVAILABLE: "product_available",
|
|
@@ -13,9 +13,19 @@ export interface BuyNowAuctionInput {
|
|
|
13
13
|
productId: string;
|
|
14
14
|
}
|
|
15
15
|
export interface BuyNowAuctionResult {
|
|
16
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Buy-Now no longer creates an order directly. It writes a LOCKED CART LINE
|
|
18
|
+
* (same shape auction settlement produces for a winning bidder) and the buyer
|
|
19
|
+
* completes the real order through the auction checkout lane. The old
|
|
20
|
+
* behaviour created a document that no orders UI could render and no payment
|
|
21
|
+
* path could reach — see auctionSettlement.ts for the full writeup.
|
|
22
|
+
*/
|
|
23
|
+
cartLocked: true;
|
|
24
|
+
productId: string;
|
|
17
25
|
amount: number;
|
|
18
26
|
currency: string;
|
|
27
|
+
/** Where to send the buyer to actually pay. */
|
|
28
|
+
checkoutUrl: string;
|
|
19
29
|
}
|
|
20
30
|
export declare function buyNowAuction(userId: string, userName: string, userEmail: string, input: BuyNowAuctionInput): Promise<BuyNowAuctionResult>;
|
|
21
31
|
export declare function listBidsByProduct(productId: string, params?: {
|
|
@@ -22,6 +22,14 @@ import { ERROR_MESSAGES, AuthorizationError, ValidationError, NotFoundError, } f
|
|
|
22
22
|
import { BID_ERROR_CODES } from "../../../errors/error-codes";
|
|
23
23
|
import { increment } from "../../../contracts/field-ops";
|
|
24
24
|
import { getDefaultCurrency } from "../../../core/baseline-resolver";
|
|
25
|
+
import { cartRepository } from "../../cart/repository/cart.repository";
|
|
26
|
+
import { ROUTES } from "../../../next/routing/route-map";
|
|
27
|
+
import { CART_LANE } from "../../../_internal/shared/checkout/lanes";
|
|
28
|
+
/**
|
|
29
|
+
* Buy-Now and auction settlement give the buyer the same 48h window to pay for
|
|
30
|
+
* a locked line. Kept identical on purpose — both are "you now owe this".
|
|
31
|
+
*/
|
|
32
|
+
const AUCTION_CHECKOUT_WINDOW_MS = 48 * 60 * 60 * 1000;
|
|
25
33
|
import { resolveDate } from "../../../utils";
|
|
26
34
|
// --- Domain Functions ----------------------------------------------------------
|
|
27
35
|
export async function placeBid(userId, userEmail, input) {
|
|
@@ -194,33 +202,39 @@ export async function buyNowAuction(userId, userName, userEmail, input) {
|
|
|
194
202
|
throw new ValidationError(ERROR_MESSAGES.BID.BUY_NOW_BIDS_STARTED, { code: BID_ERROR_CODES.BUY_NOW_BIDS_STARTED });
|
|
195
203
|
}
|
|
196
204
|
const currency = getDefaultCurrency();
|
|
197
|
-
let orderId = "";
|
|
198
205
|
await unitOfWork.runBatch((batch) => {
|
|
199
|
-
const orderRef = unitOfWork.orders.createFromAuction(batch, {
|
|
200
|
-
productId,
|
|
201
|
-
productTitle: product.title,
|
|
202
|
-
userId,
|
|
203
|
-
userName,
|
|
204
|
-
userEmail,
|
|
205
|
-
storeId: product.storeId,
|
|
206
|
-
amount: buyNowPrice,
|
|
207
|
-
currency,
|
|
208
|
-
auctionProductId: productId,
|
|
209
|
-
});
|
|
210
|
-
orderId = orderRef.id;
|
|
211
206
|
unitOfWork.products.updateInBatch(batch, productId, {
|
|
212
207
|
isSold: true,
|
|
213
208
|
availableQuantity: 0,
|
|
214
209
|
auctionEndDate: new Date(),
|
|
215
210
|
});
|
|
216
211
|
});
|
|
212
|
+
// Locked cart line, not an order — see BuyNowAuctionResult above. Written
|
|
213
|
+
// through the repository directly (auctions are canAddToCart:false for
|
|
214
|
+
// user-initiated adds), the same deliberate bypass checkoutOffer uses.
|
|
215
|
+
await cartRepository.addItem(userId, {
|
|
216
|
+
productId,
|
|
217
|
+
productTitle: product.title,
|
|
218
|
+
productImage: product.mainImage ?? "",
|
|
219
|
+
price: buyNowPrice,
|
|
220
|
+
currency,
|
|
221
|
+
quantity: 1,
|
|
222
|
+
storeId: product.storeId,
|
|
223
|
+
storeName: product.storeName ?? "",
|
|
224
|
+
listingType: "auction",
|
|
225
|
+
isAuctionWin: true,
|
|
226
|
+
auctionId: productId,
|
|
227
|
+
lockedPrice: buyNowPrice,
|
|
228
|
+
checkoutDeadline: new Date(Date.now() + AUCTION_CHECKOUT_WINDOW_MS),
|
|
229
|
+
locked: true,
|
|
230
|
+
});
|
|
231
|
+
const checkoutUrl = `${String(ROUTES.USER.CHECKOUT)}?lane=${CART_LANE.AUCTION}`;
|
|
217
232
|
serverLogger.info("buyNowAuction", {
|
|
218
|
-
orderId,
|
|
219
233
|
productId,
|
|
220
234
|
userId,
|
|
221
235
|
amount: buyNowPrice,
|
|
222
236
|
});
|
|
223
|
-
return {
|
|
237
|
+
return { cartLocked: true, productId, amount: buyNowPrice, currency, checkoutUrl };
|
|
224
238
|
}
|
|
225
239
|
export async function listBidsByProduct(productId, params) {
|
|
226
240
|
const result = await bidRepository.list({
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useMemo, useState } from "react";
|
|
4
4
|
import { ChevronRight } from "lucide-react";
|
|
5
|
-
import { Badge, Button, Div, Row, Span, Stack, Text } from "../../../ui";
|
|
5
|
+
import { Badge, Button, Div, Row, Span, Stack, Text, TextLink } from "../../../ui";
|
|
6
|
+
import { ROUTES } from "../../../next/routing/route-map";
|
|
7
|
+
import { CART_LANE } from "../../../_internal/shared/checkout/lanes";
|
|
6
8
|
import { Pagination } from "../../../ui/components/Pagination";
|
|
7
9
|
const __O = {
|
|
8
10
|
hidden: "overflow-hidden",
|
|
@@ -58,7 +60,9 @@ function AuctionRow({ auction, portal, }) {
|
|
|
58
60
|
const sorted = useMemo(() => [...auction.bids].sort((a, b) => +new Date(b.bidDate) - +new Date(a.bidDate)), [auction.bids]);
|
|
59
61
|
const highest = auction.bids.reduce((max, b) => Math.max(max, b.bidAmount), 0);
|
|
60
62
|
const isWinning = auction.bids.some((b) => b.isWinning);
|
|
61
|
-
return (_jsxs(Div, { className: `border border-[var(--appkit-color-border)] ${__O.hidden} bg-[var(--appkit-color-surface)]`, rounded: "xl", shadow: "sm", children: [_jsxs(Button, { type: "button", variant: "ghost", onClick: () => setExpanded((v) => !v), gap: "lg", paddingX: "md", paddingY: "md", className: "w-full text-left", "aria-expanded": expanded, children: [_jsx(ChevronRight, { className: `shrink-0 text-[var(--appkit-color-text-muted)] transition-transform ${expanded ? "rotate-90" : ""}`, size: 16 }), _jsx(Div, { className: "flex-1 min-w-0", children: _jsx(Text, { className: "text-[var(--appkit-color-text)] line-clamp-1", size: "sm", weight: "semibold", children: auction.productTitle }) }), _jsxs(Row, { gap: "sm", className: "shrink-0", children: [isWinning && (_jsx(Badge, { variant: "active", children: "Winning" })), _jsxs(Text, { variant: "secondary", size: "xs", children: [auction.bids.length, " bid", auction.bids.length !== 1 ? "s" : ""] }), _jsx(Text, { className: "text-[var(--appkit-color-text)]", size: "sm", weight: "semibold", children: formatBidAmount(highest) })] })] }), expanded && (_jsxs(Div, { className: "border-t border-[var(--appkit-color-border)]", children: [_jsxs(Div, { textSize: "xs", textWeight: "medium", paddingX: "x-md", paddingY: "y-xs", className: `grid text-[var(--appkit-color-text-muted)] uppercase tracking-wide border-b border-[var(--appkit-color-border-subtle)] ${portal === "buyer" ? "[grid-template-columns:1fr_auto_auto]" : "[grid-template-columns:1fr_1fr_auto_auto]"}`, children: [_jsx(Span, { children: portal === "buyer" ? "Amount" : "Bidder" }), portal !== "buyer" && _jsx(Span, { children: "Amount" }), _jsx(Span, { children: "Status" }), _jsx(Span, { className: "text-right", children: "Time" })] }), sorted.map((bid) => (_jsxs(Div, { layout: "grid", align: "center", paddingX: "x-md", paddingY: "y-xs-tall", className: `border-b border-[var(--appkit-color-border-subtle)] last:border-0 hover:bg-[var(--appkit-color-border-subtle)] transition-colors ${portal === "buyer" ? "[grid-template-columns:1fr_auto_auto]" : "[grid-template-columns:1fr_1fr_auto_auto]"}`, children: [portal !== "buyer" && (_jsx(Text, { className: "text-[var(--appkit-color-text)] truncate pr-[0.75rem]", size: "sm", children: bid.userName || bid.userId })), _jsx(Text, { className: "text-[var(--appkit-color-text)]", size: "sm", weight: "medium", children: formatBidAmount(bid.bidAmount) }), _jsx(Badge, { variant: STATUS_VARIANT[bid.status] ?? "pending", className: "capitalize", children: bid.status }), _jsx(Text, { variant: "secondary", size: "xs", align: "end", children: relDate(bid.bidDate) })
|
|
63
|
+
return (_jsxs(Div, { className: `border border-[var(--appkit-color-border)] ${__O.hidden} bg-[var(--appkit-color-surface)]`, rounded: "xl", shadow: "sm", children: [_jsxs(Button, { type: "button", variant: "ghost", onClick: () => setExpanded((v) => !v), gap: "lg", paddingX: "md", paddingY: "md", className: "w-full text-left", "aria-expanded": expanded, children: [_jsx(ChevronRight, { className: `shrink-0 text-[var(--appkit-color-text-muted)] transition-transform ${expanded ? "rotate-90" : ""}`, size: 16 }), _jsx(Div, { className: "flex-1 min-w-0", children: _jsx(Text, { className: "text-[var(--appkit-color-text)] line-clamp-1", size: "sm", weight: "semibold", children: auction.productTitle }) }), _jsxs(Row, { gap: "sm", className: "shrink-0", children: [isWinning && (_jsx(Badge, { variant: "active", children: "Winning" })), _jsxs(Text, { variant: "secondary", size: "xs", children: [auction.bids.length, " bid", auction.bids.length !== 1 ? "s" : ""] }), _jsx(Text, { className: "text-[var(--appkit-color-text)]", size: "sm", weight: "semibold", children: formatBidAmount(highest) })] })] }), expanded && (_jsxs(Div, { className: "border-t border-[var(--appkit-color-border)]", children: [_jsxs(Div, { textSize: "xs", textWeight: "medium", paddingX: "x-md", paddingY: "y-xs", className: `grid text-[var(--appkit-color-text-muted)] uppercase tracking-wide border-b border-[var(--appkit-color-border-subtle)] ${portal === "buyer" ? "[grid-template-columns:1fr_auto_auto]" : "[grid-template-columns:1fr_1fr_auto_auto]"}`, children: [_jsx(Span, { children: portal === "buyer" ? "Amount" : "Bidder" }), portal !== "buyer" && _jsx(Span, { children: "Amount" }), _jsx(Span, { children: "Status" }), _jsx(Span, { className: "text-right", children: "Time" })] }), sorted.map((bid) => (_jsxs(Div, { layout: "grid", align: "center", paddingX: "x-md", paddingY: "y-xs-tall", className: `border-b border-[var(--appkit-color-border-subtle)] last:border-0 hover:bg-[var(--appkit-color-border-subtle)] transition-colors ${portal === "buyer" ? "[grid-template-columns:1fr_auto_auto]" : "[grid-template-columns:1fr_1fr_auto_auto]"}`, children: [portal !== "buyer" && (_jsx(Text, { className: "text-[var(--appkit-color-text)] truncate pr-[0.75rem]", size: "sm", children: bid.userName || bid.userId })), _jsx(Text, { className: "text-[var(--appkit-color-text)]", size: "sm", weight: "medium", children: formatBidAmount(bid.bidAmount) }), _jsx(Badge, { variant: STATUS_VARIANT[bid.status] ?? "pending", className: "capitalize", children: bid.status }), _jsxs(Stack, { gap: "xs", className: "items-end", children: [_jsx(Text, { variant: "secondary", size: "xs", align: "end", children: relDate(bid.bidDate) }), portal === "buyer" && bid.status === "won" && (_jsx(TextLink, { href: bid.orderId
|
|
64
|
+
? String(ROUTES.USER.ORDER_DETAIL(bid.orderId))
|
|
65
|
+
: `${String(ROUTES.USER.CHECKOUT)}?lane=${CART_LANE.AUCTION}`, size: "xs", weight: "semibold", children: bid.orderId ? "View order" : "Pay now →" }))] })] }, bid.id)))] }))] }));
|
|
62
66
|
}
|
|
63
67
|
export function AuctionBidsTable({ bids, portal = "buyer", emptyLabel = "No bids found.", totalPages, currentPage, onPageChange, }) {
|
|
64
68
|
const auctions = useMemo(() => groupByAuction(bids), [bids]);
|