@m3000/market 0.0.1 → 0.0.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/styles.css +2 -0
- package/dist/tokens.css +2 -0
- package/package.json +10 -10
- package/src/components/blocks/auction/Auction.tsx +0 -74
- package/src/components/blocks/auction/AuctionArtwork.tsx +0 -4
- package/src/components/blocks/auction/AuctionBidForm.tsx +0 -138
- package/src/components/blocks/auction/AuctionBidInput.tsx +0 -166
- package/src/components/blocks/auction/AuctionContext.tsx +0 -401
- package/src/components/blocks/auction/AuctionInfo.tsx +0 -36
- package/src/components/blocks/auction/AuctionLayout.tsx +0 -200
- package/src/components/blocks/auction/AuctionRankings.tsx +0 -435
- package/src/components/blocks/auction/AuctionStatusTag.tsx +0 -98
- package/src/components/blocks/auction/AuctionSuggestedBids.tsx +0 -203
- package/src/components/blocks/auction/AuctionYourBidCard.tsx +0 -125
- package/src/components/blocks/auction/AuctionYourBids.tsx +0 -61
- package/src/components/blocks/auction/index.ts +0 -42
- package/src/components/blocks/index.ts +0 -1
- package/src/components/index.ts +0 -2
- package/src/components/primitives/Button.tsx +0 -183
- package/src/components/primitives/Drawer.tsx +0 -125
- package/src/components/primitives/Feedback.tsx +0 -185
- package/src/components/primitives/MorphDialog.tsx +0 -160
- package/src/components/primitives/Price.tsx +0 -394
- package/src/components/primitives/PriceInput.tsx +0 -48
- package/src/components/primitives/Receipt.tsx +0 -711
- package/src/components/primitives/Scale.tsx +0 -287
- package/src/components/primitives/Separator.tsx +0 -87
- package/src/components/primitives/Skeleton.tsx +0 -33
- package/src/components/primitives/SteppedInput.tsx +0 -313
- package/src/components/primitives/Tabs.tsx +0 -161
- package/src/components/primitives/Tag.tsx +0 -48
- package/src/components/primitives/Text.tsx +0 -102
- package/src/components/primitives/countdown/Countdown.tsx +0 -43
- package/src/components/primitives/countdown/index.ts +0 -2
- package/src/components/primitives/framed-image/FramedImage.tsx +0 -51
- package/src/components/primitives/framed-image/index.ts +0 -1
- package/src/components/primitives/index.ts +0 -42
- package/src/components/primitives/ranked-list/RankedList.tsx +0 -9
- package/src/components/primitives/ranked-list/Ranking.tsx +0 -454
- package/src/components/primitives/ranked-list/index.ts +0 -8
- package/src/hooks/index.ts +0 -1
- package/src/hooks/useCountdown.ts +0 -91
- package/src/index.ts +0 -130
- package/src/lib/cn.ts +0 -81
- package/src/lib/index.ts +0 -2
- package/src/lib/motion.ts +0 -55
- package/src/public/lea-83-time-walk.png +0 -0
- package/src/public/lea-83-time-walk.webp +0 -0
- package/src/stories/Auction.stories.tsx +0 -658
- package/src/stories/AuctionLayout.stories.tsx +0 -313
- package/src/stories/AuctionStatusTag.stories.tsx +0 -166
- package/src/stories/AuctionYourBidCard.stories.tsx +0 -257
- package/src/stories/Button.stories.tsx +0 -306
- package/src/stories/Countdown.stories.tsx +0 -158
- package/src/stories/Feedback.stories.tsx +0 -80
- package/src/stories/FramedImage.stories.tsx +0 -46
- package/src/stories/MorphDialog.stories.tsx +0 -88
- package/src/stories/Price.stories.tsx +0 -292
- package/src/stories/RankedList.stories.tsx +0 -190
- package/src/stories/Receipt.stories.tsx +0 -221
- package/src/stories/Scale.stories.tsx +0 -578
- package/src/stories/Separator.stories.tsx +0 -188
- package/src/stories/Skeleton.stories.tsx +0 -138
- package/src/stories/SteppedInput.stories.tsx +0 -321
- package/src/stories/Tabs.stories.tsx +0 -215
- package/src/stories/Tag.stories.tsx +0 -138
- package/src/stories/Text.stories.tsx +0 -245
- package/src/styles/globals.css +0 -39
- package/src/styles/index.css +0 -4
- package/src/styles/theme/animation.css +0 -11
- package/src/styles/theme/color.css +0 -185
- package/src/styles/theme/index.css +0 -3
- package/src/styles/theme/typography.css +0 -3
- package/src/styles/utility.css +0 -8
- package/src/types/index.ts +0 -149
- package/src/utils/format.ts +0 -130
- package/src/utils/index.ts +0 -16
- package/src/utils/rank-utils.ts +0 -131
- package/src/utils/tick-validation.ts +0 -65
|
@@ -1,435 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { useEffect, useMemo, useRef } from "react";
|
|
4
|
-
import type { GroupItemContextValue } from "@/components";
|
|
5
|
-
import { Ranking } from "@/components";
|
|
6
|
-
import { Button, Separator, Skeleton, Text } from "@/components/primitives";
|
|
7
|
-
import { cn } from "@/lib";
|
|
8
|
-
import type { AuctionBid, AuctionUserBid } from "@/types";
|
|
9
|
-
import { formatShortRelative, getProjectedRankForPrice } from "@/utils";
|
|
10
|
-
import { useAuctionContext } from "./AuctionContext";
|
|
11
|
-
|
|
12
|
-
export interface AuctionRankingsProps {
|
|
13
|
-
className?: string;
|
|
14
|
-
renderBidRow?: (
|
|
15
|
-
bid: AuctionBid,
|
|
16
|
-
context: GroupItemContextValue<AuctionBid> & { isOutbid: boolean },
|
|
17
|
-
) => React.ReactNode;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function BidRow({
|
|
21
|
-
bid,
|
|
22
|
-
rank,
|
|
23
|
-
isOutbid,
|
|
24
|
-
isUserBid,
|
|
25
|
-
onTopUp,
|
|
26
|
-
isAuctionEnded,
|
|
27
|
-
formatPrice,
|
|
28
|
-
currencySymbol,
|
|
29
|
-
}: {
|
|
30
|
-
bid: AuctionBid;
|
|
31
|
-
rank: number;
|
|
32
|
-
isOutbid: boolean;
|
|
33
|
-
isUserBid: boolean;
|
|
34
|
-
onTopUp?: () => void;
|
|
35
|
-
isAuctionEnded: boolean;
|
|
36
|
-
formatPrice: (priceValue: bigint) => string;
|
|
37
|
-
currencySymbol: string;
|
|
38
|
-
}): React.ReactElement {
|
|
39
|
-
const timeShort = formatShortRelative(bid.createdAt);
|
|
40
|
-
|
|
41
|
-
return (
|
|
42
|
-
<div
|
|
43
|
-
className={cn(
|
|
44
|
-
"flex items-center justify-between gap-2 px-6 py-2",
|
|
45
|
-
isOutbid && "opacity-50",
|
|
46
|
-
isUserBid && "shadow-[inset_2px_0_0_0_var(--color-primary)]",
|
|
47
|
-
)}
|
|
48
|
-
>
|
|
49
|
-
<div className="flex min-w-0 items-center gap-3">
|
|
50
|
-
<Text color="tertiary" className="w-8 shrink-0" size="1">
|
|
51
|
-
#{rank}
|
|
52
|
-
</Text>
|
|
53
|
-
<span className="flex min-w-0 items-center gap-3">
|
|
54
|
-
{bid.bidder.avatarUrl && (
|
|
55
|
-
// biome-ignore lint/performance/noImgElement: shared UI stays framework-agnostic.
|
|
56
|
-
<img
|
|
57
|
-
src={bid.bidder.avatarUrl}
|
|
58
|
-
alt=""
|
|
59
|
-
className="size-6 shrink-0 rounded-full object-cover"
|
|
60
|
-
/>
|
|
61
|
-
)}
|
|
62
|
-
<Text className="truncate">
|
|
63
|
-
{isUserBid
|
|
64
|
-
? "You"
|
|
65
|
-
: bid.bidder.name || `${bid.bidder.id.slice(0, 6)}...`}
|
|
66
|
-
</Text>
|
|
67
|
-
</span>
|
|
68
|
-
</div>
|
|
69
|
-
<div className="flex shrink-0 items-center gap-4">
|
|
70
|
-
{isUserBid && !isAuctionEnded && (
|
|
71
|
-
<Button size="xs" color="secondary" onClick={onTopUp}>
|
|
72
|
-
Top up
|
|
73
|
-
</Button>
|
|
74
|
-
)}
|
|
75
|
-
<Text>
|
|
76
|
-
{formatPrice(bid.price)} {currencySymbol}
|
|
77
|
-
</Text>
|
|
78
|
-
<Text
|
|
79
|
-
size="1"
|
|
80
|
-
color="tertiary"
|
|
81
|
-
className="min-w-9 text-right tabular-nums"
|
|
82
|
-
suppressHydrationWarning
|
|
83
|
-
>
|
|
84
|
-
{timeShort}
|
|
85
|
-
</Text>
|
|
86
|
-
</div>
|
|
87
|
-
</div>
|
|
88
|
-
);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function BidPreviewRow({
|
|
92
|
-
price,
|
|
93
|
-
rank,
|
|
94
|
-
onCancel,
|
|
95
|
-
formatPrice,
|
|
96
|
-
currencySymbol,
|
|
97
|
-
previewRef,
|
|
98
|
-
}: {
|
|
99
|
-
price: bigint;
|
|
100
|
-
rank: number;
|
|
101
|
-
onCancel: () => void;
|
|
102
|
-
formatPrice: (priceValue: bigint) => string;
|
|
103
|
-
currencySymbol: string;
|
|
104
|
-
previewRef?: React.RefObject<HTMLDivElement | null>;
|
|
105
|
-
}): React.ReactElement {
|
|
106
|
-
return (
|
|
107
|
-
<div ref={previewRef} className="relative">
|
|
108
|
-
<div className="absolute inset-0 animate-[pulse_2s_ease-in-out_infinite] bg-success/10" />
|
|
109
|
-
<div className="relative flex items-center justify-between gap-2 px-6 py-2">
|
|
110
|
-
<div className="flex min-w-0 items-center gap-3">
|
|
111
|
-
<Text color="tertiary" className="w-8 shrink-0" size="1">
|
|
112
|
-
#{rank}
|
|
113
|
-
</Text>
|
|
114
|
-
<span className="flex min-w-0 items-center gap-3">
|
|
115
|
-
<Text color="secondary">New bid preview</Text>
|
|
116
|
-
</span>
|
|
117
|
-
</div>
|
|
118
|
-
<div className="flex shrink-0 items-center gap-4">
|
|
119
|
-
<Button size="xs" color="tertiary" onClick={onCancel}>
|
|
120
|
-
Cancel
|
|
121
|
-
</Button>
|
|
122
|
-
<Text weight="medium" tabularNums>
|
|
123
|
-
{formatPrice(price)} {currencySymbol}
|
|
124
|
-
</Text>
|
|
125
|
-
<span className="min-w-9"></span>
|
|
126
|
-
</div>
|
|
127
|
-
</div>
|
|
128
|
-
</div>
|
|
129
|
-
);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function TopUpPreviewRow({
|
|
133
|
-
price,
|
|
134
|
-
rank,
|
|
135
|
-
onCancel,
|
|
136
|
-
formatPrice,
|
|
137
|
-
currencySymbol,
|
|
138
|
-
}: {
|
|
139
|
-
price: bigint;
|
|
140
|
-
rank: number;
|
|
141
|
-
onCancel: () => void;
|
|
142
|
-
formatPrice: (priceValue: bigint) => string;
|
|
143
|
-
currencySymbol: string;
|
|
144
|
-
}): React.ReactElement {
|
|
145
|
-
return (
|
|
146
|
-
<div className="relative shadow-[inset_2px_0_0_0_var(--color-success)]">
|
|
147
|
-
<div className="absolute inset-0 animate-[pulse_2s_ease-in-out_infinite] bg-success/10" />
|
|
148
|
-
<div className="relative flex items-center justify-between gap-2 px-6 py-2">
|
|
149
|
-
<div className="flex min-w-0 items-center gap-3">
|
|
150
|
-
<Text color="tertiary" className="w-8 shrink-0" size="1">
|
|
151
|
-
#{rank}
|
|
152
|
-
</Text>
|
|
153
|
-
<span className="flex min-w-0 items-center gap-3">
|
|
154
|
-
<Text color="secondary">Top-up preview</Text>
|
|
155
|
-
</span>
|
|
156
|
-
</div>
|
|
157
|
-
<div className="flex shrink-0 items-center gap-4">
|
|
158
|
-
<Button size="xs" color="tertiary" onClick={onCancel}>
|
|
159
|
-
Cancel
|
|
160
|
-
</Button>
|
|
161
|
-
<Text weight="medium" tabularNums>
|
|
162
|
-
{formatPrice(price)} {currencySymbol}
|
|
163
|
-
</Text>
|
|
164
|
-
<span className="min-w-9"></span>
|
|
165
|
-
</div>
|
|
166
|
-
</div>
|
|
167
|
-
</div>
|
|
168
|
-
);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
function RankingsSkeleton(): React.ReactElement {
|
|
172
|
-
return (
|
|
173
|
-
<div>
|
|
174
|
-
<div className="flex items-center justify-between gap-2 px-6 py-3">
|
|
175
|
-
<Text render={<p />} size="3">
|
|
176
|
-
<Skeleton>username</Skeleton>
|
|
177
|
-
</Text>
|
|
178
|
-
<Text render={<p />} size="3">
|
|
179
|
-
<Skeleton>0.00420 USD 11m</Skeleton>
|
|
180
|
-
</Text>
|
|
181
|
-
</div>
|
|
182
|
-
<Separator orientation="horizontal" />
|
|
183
|
-
</div>
|
|
184
|
-
);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
export { RankingsSkeleton };
|
|
188
|
-
|
|
189
|
-
export function AuctionRankings({
|
|
190
|
-
className,
|
|
191
|
-
renderBidRow,
|
|
192
|
-
}: AuctionRankingsProps): React.ReactElement {
|
|
193
|
-
const {
|
|
194
|
-
mergedForRank,
|
|
195
|
-
maxTotalItems,
|
|
196
|
-
formatPrice,
|
|
197
|
-
currencySymbol,
|
|
198
|
-
bidValue,
|
|
199
|
-
setBidValue,
|
|
200
|
-
minBidValue,
|
|
201
|
-
userBids,
|
|
202
|
-
lockedBid,
|
|
203
|
-
setLockedBid,
|
|
204
|
-
showBidPreview,
|
|
205
|
-
isAuctionEnded,
|
|
206
|
-
cancelBidding,
|
|
207
|
-
isBiddingActive,
|
|
208
|
-
} = useAuctionContext();
|
|
209
|
-
|
|
210
|
-
const userBidMap = useMemo(() => {
|
|
211
|
-
const map = new Map<string, AuctionUserBid>();
|
|
212
|
-
for (const ub of userBids) {
|
|
213
|
-
map.set(ub.id, ub);
|
|
214
|
-
}
|
|
215
|
-
return map;
|
|
216
|
-
}, [userBids]);
|
|
217
|
-
|
|
218
|
-
const previewRef = useRef<HTMLDivElement>(null);
|
|
219
|
-
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
|
220
|
-
|
|
221
|
-
const showPreview = useMemo(() => {
|
|
222
|
-
if (!showBidPreview) return false;
|
|
223
|
-
if (lockedBid !== null) return false;
|
|
224
|
-
return bidValue >= minBidValue;
|
|
225
|
-
}, [showBidPreview, bidValue, minBidValue, lockedBid]);
|
|
226
|
-
|
|
227
|
-
const previewIndex = useMemo(() => {
|
|
228
|
-
if (!showPreview) return 0;
|
|
229
|
-
const result = getProjectedRankForPrice(
|
|
230
|
-
bidValue,
|
|
231
|
-
mergedForRank,
|
|
232
|
-
maxTotalItems,
|
|
233
|
-
);
|
|
234
|
-
return result.rank ? result.rank - 1 : 0;
|
|
235
|
-
}, [showPreview, bidValue, mergedForRank, maxTotalItems]);
|
|
236
|
-
|
|
237
|
-
const prevBiddingActiveRef = useRef(false);
|
|
238
|
-
|
|
239
|
-
useEffect(() => {
|
|
240
|
-
const justStartedBidding = isBiddingActive && !prevBiddingActiveRef.current;
|
|
241
|
-
prevBiddingActiveRef.current = isBiddingActive;
|
|
242
|
-
|
|
243
|
-
if (!showPreview) return;
|
|
244
|
-
|
|
245
|
-
const scrollToPreview = () => {
|
|
246
|
-
if (previewRef.current && scrollContainerRef.current) {
|
|
247
|
-
const container = scrollContainerRef.current;
|
|
248
|
-
const preview = previewRef.current;
|
|
249
|
-
const stickyDivider = container.querySelector<HTMLElement>(
|
|
250
|
-
"[data-ranking-group-divider]",
|
|
251
|
-
);
|
|
252
|
-
const stickyOffset = stickyDivider?.offsetHeight ?? 0;
|
|
253
|
-
|
|
254
|
-
const containerRect = container.getBoundingClientRect();
|
|
255
|
-
const previewRect = preview.getBoundingClientRect();
|
|
256
|
-
const visibleTop = containerRect.top + stickyOffset;
|
|
257
|
-
const visibleBottom = containerRect.bottom;
|
|
258
|
-
|
|
259
|
-
const isAbove = previewRect.top < visibleTop;
|
|
260
|
-
const isBelow = previewRect.bottom > visibleBottom;
|
|
261
|
-
|
|
262
|
-
if (justStartedBidding || isAbove || isBelow) {
|
|
263
|
-
let targetScrollTop = container.scrollTop;
|
|
264
|
-
|
|
265
|
-
if (justStartedBidding) {
|
|
266
|
-
const visibleHeight = container.clientHeight - stickyOffset;
|
|
267
|
-
targetScrollTop +=
|
|
268
|
-
previewRect.top -
|
|
269
|
-
visibleTop -
|
|
270
|
-
(visibleHeight - previewRect.height) / 2;
|
|
271
|
-
} else if (isAbove) {
|
|
272
|
-
targetScrollTop += previewRect.top - visibleTop;
|
|
273
|
-
} else if (isBelow) {
|
|
274
|
-
targetScrollTop += previewRect.bottom - visibleBottom;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
container.scrollTo({
|
|
278
|
-
top: Math.max(0, targetScrollTop),
|
|
279
|
-
behavior: "auto",
|
|
280
|
-
});
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
};
|
|
284
|
-
|
|
285
|
-
const frameId = requestAnimationFrame(() => {
|
|
286
|
-
requestAnimationFrame(scrollToPreview);
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
return () => cancelAnimationFrame(frameId);
|
|
290
|
-
}, [showPreview, isBiddingActive]);
|
|
291
|
-
|
|
292
|
-
const { lockedBidId, lockedBidOriginalIndex } = useMemo(() => {
|
|
293
|
-
if (lockedBid === null)
|
|
294
|
-
return { lockedBidId: null, lockedBidOriginalIndex: null };
|
|
295
|
-
const lockedUserBid = userBids.find(
|
|
296
|
-
(ub) => ub.globalBidId === lockedBid.bidId,
|
|
297
|
-
);
|
|
298
|
-
const bidId = lockedUserBid?.id ?? null;
|
|
299
|
-
const originalIndex =
|
|
300
|
-
bidId !== null ? mergedForRank.findIndex((b) => b.id === bidId) : null;
|
|
301
|
-
return { lockedBidId: bidId, lockedBidOriginalIndex: originalIndex };
|
|
302
|
-
}, [lockedBid, userBids, mergedForRank]);
|
|
303
|
-
|
|
304
|
-
const allBids: AuctionBid[] = useMemo(
|
|
305
|
-
() =>
|
|
306
|
-
mergedForRank
|
|
307
|
-
.filter((b) => b.id !== lockedBidId)
|
|
308
|
-
.map((b) => ({
|
|
309
|
-
id: b.id,
|
|
310
|
-
price: BigInt(b.price),
|
|
311
|
-
createdAt: new Date(b.created_at),
|
|
312
|
-
bidder: b.bidder ?? { id: b.id, name: "Unknown" },
|
|
313
|
-
})),
|
|
314
|
-
[mergedForRank, lockedBidId],
|
|
315
|
-
);
|
|
316
|
-
|
|
317
|
-
const topUpPreviewIndex = useMemo(() => {
|
|
318
|
-
if (lockedBid === null || lockedBidId === null) return null;
|
|
319
|
-
|
|
320
|
-
const bidsWithoutLocked = mergedForRank.filter((b) => b.id !== lockedBidId);
|
|
321
|
-
|
|
322
|
-
const result = getProjectedRankForPrice(
|
|
323
|
-
bidValue,
|
|
324
|
-
bidsWithoutLocked,
|
|
325
|
-
maxTotalItems,
|
|
326
|
-
);
|
|
327
|
-
return result.rank ? result.rank - 1 : 0;
|
|
328
|
-
}, [lockedBid, lockedBidId, bidValue, mergedForRank, maxTotalItems]);
|
|
329
|
-
|
|
330
|
-
return (
|
|
331
|
-
<div
|
|
332
|
-
ref={scrollContainerRef}
|
|
333
|
-
className={cn("min-h-0 flex-1 overflow-y-auto", className)}
|
|
334
|
-
>
|
|
335
|
-
<Ranking.Root
|
|
336
|
-
items={allBids}
|
|
337
|
-
getKey={(bid) => bid.id}
|
|
338
|
-
boundaries={[maxTotalItems]}
|
|
339
|
-
labels={["Winning Bids", "Outbid"]}
|
|
340
|
-
>
|
|
341
|
-
{showPreview && (
|
|
342
|
-
<Ranking.Slot slotKey="preview" atIndex={previewIndex}>
|
|
343
|
-
{(context) => (
|
|
344
|
-
<>
|
|
345
|
-
<BidPreviewRow
|
|
346
|
-
price={bidValue}
|
|
347
|
-
rank={context.rank}
|
|
348
|
-
onCancel={cancelBidding}
|
|
349
|
-
formatPrice={formatPrice}
|
|
350
|
-
currencySymbol={currencySymbol}
|
|
351
|
-
previewRef={previewRef}
|
|
352
|
-
/>
|
|
353
|
-
{!context.isLastInGroup && (
|
|
354
|
-
<Separator orientation="horizontal" />
|
|
355
|
-
)}
|
|
356
|
-
</>
|
|
357
|
-
)}
|
|
358
|
-
</Ranking.Slot>
|
|
359
|
-
)}
|
|
360
|
-
{lockedBid !== null && topUpPreviewIndex !== null && (
|
|
361
|
-
<Ranking.Slot slotKey="topup-preview" atIndex={topUpPreviewIndex}>
|
|
362
|
-
{(context) => (
|
|
363
|
-
<>
|
|
364
|
-
<TopUpPreviewRow
|
|
365
|
-
price={bidValue}
|
|
366
|
-
rank={context.rank}
|
|
367
|
-
onCancel={cancelBidding}
|
|
368
|
-
formatPrice={formatPrice}
|
|
369
|
-
currencySymbol={currencySymbol}
|
|
370
|
-
/>
|
|
371
|
-
{!context.isLastInGroup && (
|
|
372
|
-
<Separator orientation="horizontal" />
|
|
373
|
-
)}
|
|
374
|
-
</>
|
|
375
|
-
)}
|
|
376
|
-
</Ranking.Slot>
|
|
377
|
-
)}
|
|
378
|
-
<Ranking.Empty>
|
|
379
|
-
<Text color="tertiary">No activity</Text>
|
|
380
|
-
</Ranking.Empty>
|
|
381
|
-
<Ranking.Group>
|
|
382
|
-
<Ranking.GroupDivider className="rounded-xl" />
|
|
383
|
-
<Ranking.GroupItem>
|
|
384
|
-
<Ranking.GroupItemValue>
|
|
385
|
-
{(bid: AuctionBid, context) => {
|
|
386
|
-
const isOutbid = context.groupIndex === 1;
|
|
387
|
-
const isBelowLockedBid =
|
|
388
|
-
lockedBid !== null &&
|
|
389
|
-
lockedBidOriginalIndex !== null &&
|
|
390
|
-
lockedBidOriginalIndex >= 0 &&
|
|
391
|
-
context.globalIndex >= lockedBidOriginalIndex;
|
|
392
|
-
const extendedContext = { ...context, isOutbid };
|
|
393
|
-
|
|
394
|
-
const userBid = userBidMap.get(bid.id);
|
|
395
|
-
const isUserBid = !!userBid;
|
|
396
|
-
|
|
397
|
-
const handleTopUp = () => {
|
|
398
|
-
if (userBid) {
|
|
399
|
-
setLockedBid({
|
|
400
|
-
bidId: userBid.globalBidId,
|
|
401
|
-
priceValue: userBid.price,
|
|
402
|
-
});
|
|
403
|
-
setBidValue(userBid.price);
|
|
404
|
-
}
|
|
405
|
-
};
|
|
406
|
-
|
|
407
|
-
return (
|
|
408
|
-
<>
|
|
409
|
-
{renderBidRow ? (
|
|
410
|
-
renderBidRow(bid, extendedContext)
|
|
411
|
-
) : (
|
|
412
|
-
<BidRow
|
|
413
|
-
bid={bid}
|
|
414
|
-
rank={context.globalIndex + 1}
|
|
415
|
-
isOutbid={isOutbid || isBelowLockedBid}
|
|
416
|
-
isUserBid={isUserBid}
|
|
417
|
-
onTopUp={handleTopUp}
|
|
418
|
-
isAuctionEnded={isAuctionEnded}
|
|
419
|
-
formatPrice={formatPrice}
|
|
420
|
-
currencySymbol={currencySymbol}
|
|
421
|
-
/>
|
|
422
|
-
)}
|
|
423
|
-
{!context.isLastInGroup && (
|
|
424
|
-
<Separator orientation="horizontal" />
|
|
425
|
-
)}
|
|
426
|
-
</>
|
|
427
|
-
);
|
|
428
|
-
}}
|
|
429
|
-
</Ranking.GroupItemValue>
|
|
430
|
-
</Ranking.GroupItem>
|
|
431
|
-
</Ranking.Group>
|
|
432
|
-
</Ranking.Root>
|
|
433
|
-
</div>
|
|
434
|
-
);
|
|
435
|
-
}
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { Tag, Text } from "@/components/primitives";
|
|
4
|
-
import { cn } from "@/lib";
|
|
5
|
-
import { formatDateTime } from "@/utils";
|
|
6
|
-
import { Countdown } from "../../primitives/countdown/Countdown";
|
|
7
|
-
|
|
8
|
-
type AuctionState = "upcoming" | "live" | "closed";
|
|
9
|
-
|
|
10
|
-
function getAuctionState(
|
|
11
|
-
opensAt: Date | null,
|
|
12
|
-
endsAt: Date | null,
|
|
13
|
-
): AuctionState {
|
|
14
|
-
if (!opensAt || !endsAt) return "live";
|
|
15
|
-
const now = Date.now();
|
|
16
|
-
const open = opensAt.getTime();
|
|
17
|
-
const end = endsAt.getTime();
|
|
18
|
-
if (now < open) return "upcoming";
|
|
19
|
-
if (now > end) return "closed";
|
|
20
|
-
return "live";
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const transparentTagClassName = "bg-transparent border-transparent p-0";
|
|
24
|
-
|
|
25
|
-
export interface AuctionStatusTagProps {
|
|
26
|
-
opensAt: Date | null;
|
|
27
|
-
endsAt: Date | null;
|
|
28
|
-
background?: "filled" | "transparent";
|
|
29
|
-
showCountdown?: boolean;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export function AuctionStatusTag({
|
|
33
|
-
opensAt,
|
|
34
|
-
endsAt,
|
|
35
|
-
background = "filled",
|
|
36
|
-
showCountdown = false,
|
|
37
|
-
}: AuctionStatusTagProps): React.ReactElement | null {
|
|
38
|
-
const state = getAuctionState(opensAt, endsAt);
|
|
39
|
-
const countdownTo =
|
|
40
|
-
state === "live" ? endsAt : state === "upcoming" ? opensAt : null;
|
|
41
|
-
|
|
42
|
-
const tagClassName = cn(
|
|
43
|
-
"inline-flex items-center gap-1.5 p-2",
|
|
44
|
-
background === "transparent" && transparentTagClassName,
|
|
45
|
-
);
|
|
46
|
-
|
|
47
|
-
if (state === "live" && endsAt) {
|
|
48
|
-
return (
|
|
49
|
-
<Tag className={tagClassName} aria-label="Auction is live">
|
|
50
|
-
<span className="relative flex size-2 shrink-0" aria-hidden>
|
|
51
|
-
<span className="absolute inline-flex size-full animate-ping rounded-full bg-success opacity-75" />
|
|
52
|
-
<span className="relative inline-flex size-2 rounded-full bg-success" />
|
|
53
|
-
</span>
|
|
54
|
-
<Text size="1" suppressHydrationWarning>
|
|
55
|
-
{showCountdown && countdownTo ? (
|
|
56
|
-
<>
|
|
57
|
-
Live - <Countdown to={countdownTo} />
|
|
58
|
-
</>
|
|
59
|
-
) : (
|
|
60
|
-
<>Live - Until {formatDateTime(endsAt)}</>
|
|
61
|
-
)}
|
|
62
|
-
</Text>
|
|
63
|
-
</Tag>
|
|
64
|
-
);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
if (state === "upcoming" && opensAt) {
|
|
68
|
-
return (
|
|
69
|
-
<Tag className={tagClassName}>
|
|
70
|
-
<Text size="1" suppressHydrationWarning>
|
|
71
|
-
{showCountdown && countdownTo ? (
|
|
72
|
-
<>
|
|
73
|
-
Opens in <Countdown to={countdownTo} />
|
|
74
|
-
</>
|
|
75
|
-
) : (
|
|
76
|
-
<>Opens {formatDateTime(opensAt)}</>
|
|
77
|
-
)}
|
|
78
|
-
</Text>
|
|
79
|
-
</Tag>
|
|
80
|
-
);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (state === "closed" && endsAt) {
|
|
84
|
-
return (
|
|
85
|
-
<Tag className={tagClassName}>
|
|
86
|
-
<div
|
|
87
|
-
className="size-2.5 shrink-0 rounded-full bg-muted-foreground"
|
|
88
|
-
aria-hidden
|
|
89
|
-
/>
|
|
90
|
-
<Text suppressHydrationWarning>
|
|
91
|
-
Closed - ended {formatDateTime(endsAt)}
|
|
92
|
-
</Text>
|
|
93
|
-
</Tag>
|
|
94
|
-
);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return null;
|
|
98
|
-
}
|