@m3000/market 0.0.1
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/LICENSE +21 -0
- package/dist/components/blocks/auction/Auction.d.ts +49 -0
- package/dist/components/blocks/auction/Auction.js +44 -0
- package/dist/components/blocks/auction/AuctionBidForm.d.ts +11 -0
- package/dist/components/blocks/auction/AuctionBidForm.js +88 -0
- package/dist/components/blocks/auction/AuctionBidInput.d.ts +9 -0
- package/dist/components/blocks/auction/AuctionBidInput.js +99 -0
- package/dist/components/blocks/auction/AuctionContext.d.ts +71 -0
- package/dist/components/blocks/auction/AuctionContext.js +228 -0
- package/dist/components/blocks/auction/AuctionInfo.d.ts +9 -0
- package/dist/components/blocks/auction/AuctionInfo.js +37 -0
- package/dist/components/blocks/auction/AuctionLayout.d.ts +63 -0
- package/dist/components/blocks/auction/AuctionLayout.js +80 -0
- package/dist/components/blocks/auction/AuctionRankings.d.ts +16 -0
- package/dist/components/blocks/auction/AuctionRankings.js +334 -0
- package/dist/components/blocks/auction/AuctionStatusTag.d.ts +15 -0
- package/dist/components/blocks/auction/AuctionStatusTag.js +60 -0
- package/dist/components/blocks/auction/AuctionSuggestedBids.d.ts +38 -0
- package/dist/components/blocks/auction/AuctionSuggestedBids.js +116 -0
- package/dist/components/blocks/auction/AuctionYourBidCard.d.ts +27 -0
- package/dist/components/blocks/auction/AuctionYourBidCard.js +94 -0
- package/dist/components/blocks/auction/AuctionYourBids.d.ts +9 -0
- package/dist/components/blocks/auction/AuctionYourBids.js +49 -0
- package/dist/components/blocks/auction/index.d.ts +12 -0
- package/dist/components/blocks/index.d.ts +12 -0
- package/dist/components/index.d.ts +28 -0
- package/dist/components/primitives/Button.d.ts +31 -0
- package/dist/components/primitives/Button.js +117 -0
- package/dist/components/primitives/Drawer.d.ts +43 -0
- package/dist/components/primitives/Drawer.js +51 -0
- package/dist/components/primitives/Feedback.d.ts +28 -0
- package/dist/components/primitives/Feedback.js +147 -0
- package/dist/components/primitives/MorphDialog.d.ts +39 -0
- package/dist/components/primitives/MorphDialog.js +87 -0
- package/dist/components/primitives/Price.d.ts +84 -0
- package/dist/components/primitives/Price.js +255 -0
- package/dist/components/primitives/PriceInput.d.ts +33 -0
- package/dist/components/primitives/PriceInput.js +25 -0
- package/dist/components/primitives/Receipt.d.ts +164 -0
- package/dist/components/primitives/Receipt.js +344 -0
- package/dist/components/primitives/Scale.d.ts +67 -0
- package/dist/components/primitives/Scale.js +132 -0
- package/dist/components/primitives/Separator.d.ts +22 -0
- package/dist/components/primitives/Separator.js +62 -0
- package/dist/components/primitives/Skeleton.d.ts +14 -0
- package/dist/components/primitives/Skeleton.js +20 -0
- package/dist/components/primitives/SteppedInput.d.ts +94 -0
- package/dist/components/primitives/SteppedInput.js +154 -0
- package/dist/components/primitives/Tabs.d.ts +37 -0
- package/dist/components/primitives/Tabs.js +99 -0
- package/dist/components/primitives/Tag.d.ts +24 -0
- package/dist/components/primitives/Tag.js +22 -0
- package/dist/components/primitives/Text.d.ts +32 -0
- package/dist/components/primitives/Text.js +65 -0
- package/dist/components/primitives/countdown/Countdown.d.ts +24 -0
- package/dist/components/primitives/countdown/Countdown.js +22 -0
- package/dist/components/primitives/framed-image/FramedImage.d.ts +13 -0
- package/dist/components/primitives/framed-image/FramedImage.js +37 -0
- package/dist/components/primitives/index.d.ts +17 -0
- package/dist/components/primitives/ranked-list/Ranking.d.ts +117 -0
- package/dist/components/primitives/ranked-list/Ranking.js +219 -0
- package/dist/components/primitives/ranked-list/index.d.ts +1 -0
- package/dist/hooks/useCountdown.d.ts +20 -0
- package/dist/hooks/useCountdown.js +75 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.js +36 -0
- package/dist/lib/cn.d.ts +6 -0
- package/dist/lib/cn.js +75 -0
- package/dist/lib/motion.d.ts +19 -0
- package/dist/lib/motion.js +43 -0
- package/dist/types/index.d.ts +120 -0
- package/dist/utils/format.d.ts +38 -0
- package/dist/utils/format.js +103 -0
- package/dist/utils/rank-utils.d.ts +34 -0
- package/dist/utils/rank-utils.js +80 -0
- package/dist/utils/tick-validation.d.ts +22 -0
- package/dist/utils/tick-validation.js +40 -0
- package/package.json +92 -0
- package/src/components/blocks/auction/Auction.tsx +74 -0
- package/src/components/blocks/auction/AuctionArtwork.tsx +4 -0
- package/src/components/blocks/auction/AuctionBidForm.tsx +138 -0
- package/src/components/blocks/auction/AuctionBidInput.tsx +166 -0
- package/src/components/blocks/auction/AuctionContext.tsx +401 -0
- package/src/components/blocks/auction/AuctionInfo.tsx +36 -0
- package/src/components/blocks/auction/AuctionLayout.tsx +200 -0
- package/src/components/blocks/auction/AuctionRankings.tsx +435 -0
- package/src/components/blocks/auction/AuctionStatusTag.tsx +98 -0
- package/src/components/blocks/auction/AuctionSuggestedBids.tsx +203 -0
- package/src/components/blocks/auction/AuctionYourBidCard.tsx +125 -0
- package/src/components/blocks/auction/AuctionYourBids.tsx +61 -0
- package/src/components/blocks/auction/index.ts +42 -0
- package/src/components/blocks/index.ts +1 -0
- package/src/components/index.ts +2 -0
- package/src/components/primitives/Button.tsx +183 -0
- package/src/components/primitives/Drawer.tsx +125 -0
- package/src/components/primitives/Feedback.tsx +185 -0
- package/src/components/primitives/MorphDialog.tsx +160 -0
- package/src/components/primitives/Price.tsx +394 -0
- package/src/components/primitives/PriceInput.tsx +48 -0
- package/src/components/primitives/Receipt.tsx +711 -0
- package/src/components/primitives/Scale.tsx +287 -0
- package/src/components/primitives/Separator.tsx +87 -0
- package/src/components/primitives/Skeleton.tsx +33 -0
- package/src/components/primitives/SteppedInput.tsx +313 -0
- package/src/components/primitives/Tabs.tsx +161 -0
- package/src/components/primitives/Tag.tsx +48 -0
- package/src/components/primitives/Text.tsx +102 -0
- package/src/components/primitives/countdown/Countdown.tsx +43 -0
- package/src/components/primitives/countdown/index.ts +2 -0
- package/src/components/primitives/framed-image/FramedImage.tsx +51 -0
- package/src/components/primitives/framed-image/index.ts +1 -0
- package/src/components/primitives/index.ts +42 -0
- package/src/components/primitives/ranked-list/RankedList.tsx +9 -0
- package/src/components/primitives/ranked-list/Ranking.tsx +454 -0
- package/src/components/primitives/ranked-list/index.ts +8 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useCountdown.ts +91 -0
- package/src/index.ts +130 -0
- package/src/lib/cn.ts +81 -0
- package/src/lib/index.ts +2 -0
- package/src/lib/motion.ts +55 -0
- 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 +658 -0
- package/src/stories/AuctionLayout.stories.tsx +313 -0
- package/src/stories/AuctionStatusTag.stories.tsx +166 -0
- package/src/stories/AuctionYourBidCard.stories.tsx +257 -0
- package/src/stories/Button.stories.tsx +306 -0
- package/src/stories/Countdown.stories.tsx +158 -0
- package/src/stories/Feedback.stories.tsx +80 -0
- package/src/stories/FramedImage.stories.tsx +46 -0
- package/src/stories/MorphDialog.stories.tsx +88 -0
- package/src/stories/Price.stories.tsx +292 -0
- package/src/stories/RankedList.stories.tsx +190 -0
- package/src/stories/Receipt.stories.tsx +221 -0
- package/src/stories/Scale.stories.tsx +578 -0
- package/src/stories/Separator.stories.tsx +188 -0
- package/src/stories/Skeleton.stories.tsx +138 -0
- package/src/stories/SteppedInput.stories.tsx +321 -0
- package/src/stories/Tabs.stories.tsx +215 -0
- package/src/stories/Tag.stories.tsx +138 -0
- package/src/stories/Text.stories.tsx +245 -0
- package/src/styles/globals.css +39 -0
- package/src/styles/index.css +4 -0
- package/src/styles/theme/animation.css +11 -0
- package/src/styles/theme/color.css +185 -0
- package/src/styles/theme/index.css +3 -0
- package/src/styles/theme/typography.css +3 -0
- package/src/styles/utility.css +8 -0
- package/src/types/index.ts +149 -0
- package/src/utils/format.ts +130 -0
- package/src/utils/index.ts +16 -0
- package/src/utils/rank-utils.ts +131 -0
- package/src/utils/tick-validation.ts +65 -0
|
@@ -0,0 +1,658 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
3
|
+
import { Ranking, Separator, Text } from "@/components";
|
|
4
|
+
import {
|
|
5
|
+
Auction,
|
|
6
|
+
AuctionArtwork,
|
|
7
|
+
AuctionBiddingPanel,
|
|
8
|
+
AuctionBidForm,
|
|
9
|
+
AuctionDetails,
|
|
10
|
+
AuctionDetailsBody,
|
|
11
|
+
AuctionDetailsHeader,
|
|
12
|
+
AuctionLayout,
|
|
13
|
+
AuctionRankings,
|
|
14
|
+
AuctionRankingsContainer,
|
|
15
|
+
AuctionStatusTag,
|
|
16
|
+
useAuctionContext,
|
|
17
|
+
} from "@/components/blocks/auction";
|
|
18
|
+
import type { AuctionData, AuctionUserBid, RankableBid } from "@/types";
|
|
19
|
+
import { formatShortRelative } from "@/utils";
|
|
20
|
+
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
// Artwork constants
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
// Multi-edition: Hokusai's "The Great Wave" – a natural fit for a limited edition art print
|
|
26
|
+
const EDITION_ARTWORK_URL =
|
|
27
|
+
"https://upload.wikimedia.org/wikipedia/commons/thumb/a/a5/Tsunami_by_hokusai_19th_century.jpg/1280px-Tsunami_by_hokusai_19th_century.jpg";
|
|
28
|
+
const EDITION_ARTWORK_ALT = "The Great Wave off Kanagawa";
|
|
29
|
+
|
|
30
|
+
// Single item: MTG Alpha "Time Walk" – a 1-of-1 collectible
|
|
31
|
+
const SINGLE_ARTWORK_URL = "/lea-83-time-walk.png";
|
|
32
|
+
const SINGLE_ARTWORK_ALT = "LEA-83 Time Walk";
|
|
33
|
+
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
// Shared helpers
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
const mockBidders = [
|
|
39
|
+
{ id: "0xalice", name: "Alice" },
|
|
40
|
+
{ id: "0xbob", name: "Bob" },
|
|
41
|
+
{ id: "0xcharlie", name: "Charlie" },
|
|
42
|
+
{ id: "0xdiana", name: "Diana" },
|
|
43
|
+
{ id: "0xeve", name: "Eve" },
|
|
44
|
+
{ id: "0xfrank", name: "Frank" },
|
|
45
|
+
{ id: "0xgrace", name: "Grace" },
|
|
46
|
+
{ id: "0xhenry", name: "Henry" },
|
|
47
|
+
{ id: "0xivy", name: "Ivy" },
|
|
48
|
+
{ id: "0xjack", name: "Jack" },
|
|
49
|
+
{ id: "0xkate", name: "Kate" },
|
|
50
|
+
{ id: "0xleo", name: "Leo" },
|
|
51
|
+
{ id: "0xmia", name: "Mia" },
|
|
52
|
+
{ id: "0xnathan", name: "Nathan" },
|
|
53
|
+
{ id: "0xolivia", name: "Olivia" },
|
|
54
|
+
{ id: "0xpeter", name: "Peter" },
|
|
55
|
+
{ id: "0xquinn", name: "Quinn" },
|
|
56
|
+
{ id: "0xrachel", name: "Rachel" },
|
|
57
|
+
{ id: "0xsam", name: "Sam" },
|
|
58
|
+
{ id: "0xtina", name: "Tina" },
|
|
59
|
+
{ id: "0xuma", name: "Uma" },
|
|
60
|
+
{ id: "0xvictor", name: "Victor" },
|
|
61
|
+
{ id: "0xwendy", name: "Wendy" },
|
|
62
|
+
{ id: "0xxavier", name: "Xavier" },
|
|
63
|
+
{ id: "0xyara", name: "Yara" },
|
|
64
|
+
{ id: "0xzack", name: "Zack" },
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
function formatPrice(price: bigint) {
|
|
68
|
+
const dollars = Number(price) / 100;
|
|
69
|
+
return dollars.toLocaleString("en-US", {
|
|
70
|
+
minimumFractionDigits: 2,
|
|
71
|
+
maximumFractionDigits: 2,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const dollarFormatters = {
|
|
76
|
+
formatPrice,
|
|
77
|
+
currencySymbol: "$",
|
|
78
|
+
inputDecimals: 2,
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
// Multi-edition mock data (20 editions of an art print)
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
|
|
85
|
+
// Dollar auction using cents as the base unit (1 dollar = 100 cents)
|
|
86
|
+
const mockAuction: AuctionData = {
|
|
87
|
+
id: "0x1234567890abcdef1234567890abcdef12345678",
|
|
88
|
+
reservePrice: 10000n, // $100 in cents
|
|
89
|
+
opensAt: new Date(Date.now() - 86400000),
|
|
90
|
+
endsAt: new Date(Date.now() + 86400000 * 3),
|
|
91
|
+
maxTotalItems: 20,
|
|
92
|
+
tickConfig: {
|
|
93
|
+
threshold: 100000n, // $1000 in cents
|
|
94
|
+
smallTickSize: 1000n, // $10 in cents
|
|
95
|
+
largeTickSize: 10000n, // $100 in cents
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const mockBids: RankableBid[] = Array.from({ length: 26 }, (_, i) => ({
|
|
100
|
+
id: String(i + 1),
|
|
101
|
+
price: (50000n - BigInt(i) * 1500n).toString(),
|
|
102
|
+
created_at: new Date(Date.now() - (3600000 + i * 1200000)).toISOString(),
|
|
103
|
+
bidder: mockBidders[i],
|
|
104
|
+
}));
|
|
105
|
+
|
|
106
|
+
// User bid matching bid #9 in the rankings (price $380 = 38000 cents)
|
|
107
|
+
const mockUserBids: AuctionUserBid[] = [
|
|
108
|
+
{
|
|
109
|
+
id: "9",
|
|
110
|
+
price: 38000n, // $380 in cents
|
|
111
|
+
createdAt: new Date(Date.now() - 18000000),
|
|
112
|
+
bidder: { id: "0xivy", name: "You" },
|
|
113
|
+
globalBidId: 9n,
|
|
114
|
+
status: "active",
|
|
115
|
+
isWinning: true,
|
|
116
|
+
},
|
|
117
|
+
];
|
|
118
|
+
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
// Single-item mock data (1-of-1 collectible card)
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
|
|
123
|
+
const mockSingleAuction: AuctionData = {
|
|
124
|
+
id: "0xabcdef1234567890abcdef1234567890abcdef12",
|
|
125
|
+
reservePrice: 50000n, // $500 in cents
|
|
126
|
+
opensAt: new Date(Date.now() - 86400000 * 2),
|
|
127
|
+
endsAt: new Date(Date.now() + 86400000),
|
|
128
|
+
maxTotalItems: 1,
|
|
129
|
+
tickConfig: {
|
|
130
|
+
threshold: 500000n, // $5000 in cents
|
|
131
|
+
smallTickSize: 5000n, // $50 in cents
|
|
132
|
+
largeTickSize: 50000n, // $500 in cents
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const mockSingleBids: RankableBid[] = [
|
|
137
|
+
{
|
|
138
|
+
id: "s1",
|
|
139
|
+
price: "280000", // $2,800
|
|
140
|
+
created_at: new Date(Date.now() - 1800000).toISOString(),
|
|
141
|
+
bidder: { id: "0xalice", name: "Alice" },
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
id: "s2",
|
|
145
|
+
price: "250000", // $2,500
|
|
146
|
+
created_at: new Date(Date.now() - 3600000).toISOString(),
|
|
147
|
+
bidder: { id: "0xbob", name: "Bob" },
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
id: "s3",
|
|
151
|
+
price: "220000", // $2,200
|
|
152
|
+
created_at: new Date(Date.now() - 7200000).toISOString(),
|
|
153
|
+
bidder: { id: "0xcharlie", name: "Charlie" },
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
id: "s4",
|
|
157
|
+
price: "180000", // $1,800
|
|
158
|
+
created_at: new Date(Date.now() - 14400000).toISOString(),
|
|
159
|
+
bidder: { id: "0xdiana", name: "Diana" },
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
id: "s5",
|
|
163
|
+
price: "150000", // $1,500
|
|
164
|
+
created_at: new Date(Date.now() - 21600000).toISOString(),
|
|
165
|
+
bidder: { id: "0xeve", name: "Eve" },
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
id: "s6",
|
|
169
|
+
price: "120000", // $1,200
|
|
170
|
+
created_at: new Date(Date.now() - 36000000).toISOString(),
|
|
171
|
+
bidder: { id: "0xfrank", name: "Frank" },
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
id: "s7",
|
|
175
|
+
price: "100000", // $1,000
|
|
176
|
+
created_at: new Date(Date.now() - 50400000).toISOString(),
|
|
177
|
+
bidder: { id: "0xgrace", name: "Grace" },
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
id: "s8",
|
|
181
|
+
price: "75000", // $750
|
|
182
|
+
created_at: new Date(Date.now() - 64800000).toISOString(),
|
|
183
|
+
bidder: { id: "0xhenry", name: "Henry" },
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
id: "s9",
|
|
187
|
+
price: "50000", // $500 (reserve price)
|
|
188
|
+
created_at: new Date(Date.now() - 86400000).toISOString(),
|
|
189
|
+
bidder: { id: "0xivy", name: "Ivy" },
|
|
190
|
+
},
|
|
191
|
+
];
|
|
192
|
+
|
|
193
|
+
// User bid at $2,200 (rank #3) for the SingleItemWithBid story
|
|
194
|
+
const mockSingleUserBids: AuctionUserBid[] = [
|
|
195
|
+
{
|
|
196
|
+
id: "s3",
|
|
197
|
+
price: 220000n, // $2,200 in cents
|
|
198
|
+
createdAt: new Date(Date.now() - 7200000),
|
|
199
|
+
bidder: { id: "0xcharlie", name: "You" },
|
|
200
|
+
globalBidId: 3n,
|
|
201
|
+
status: "active",
|
|
202
|
+
isWinning: false,
|
|
203
|
+
},
|
|
204
|
+
];
|
|
205
|
+
|
|
206
|
+
// ---------------------------------------------------------------------------
|
|
207
|
+
// Story meta
|
|
208
|
+
// ---------------------------------------------------------------------------
|
|
209
|
+
|
|
210
|
+
const meta: Meta<typeof Auction> = {
|
|
211
|
+
title: "Blocks/Auction",
|
|
212
|
+
component: Auction,
|
|
213
|
+
parameters: {
|
|
214
|
+
layout: "fullscreen",
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
export default meta;
|
|
219
|
+
|
|
220
|
+
// ---------------------------------------------------------------------------
|
|
221
|
+
// Shared sub-components
|
|
222
|
+
// ---------------------------------------------------------------------------
|
|
223
|
+
|
|
224
|
+
function AuctionStatus() {
|
|
225
|
+
const { auction } = useAuctionContext();
|
|
226
|
+
return (
|
|
227
|
+
<div className="text-xs">
|
|
228
|
+
<AuctionStatusTag
|
|
229
|
+
opensAt={auction.opensAt}
|
|
230
|
+
endsAt={auction.endsAt}
|
|
231
|
+
background="transparent"
|
|
232
|
+
/>
|
|
233
|
+
</div>
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function AutoStartBidding() {
|
|
238
|
+
const { isBiddingActive, startBidding, setShowBidPreview } =
|
|
239
|
+
useAuctionContext();
|
|
240
|
+
|
|
241
|
+
useEffect(() => {
|
|
242
|
+
if (isBiddingActive) return;
|
|
243
|
+
startBidding();
|
|
244
|
+
setShowBidPreview(true);
|
|
245
|
+
}, [isBiddingActive, setShowBidPreview, startBidding]);
|
|
246
|
+
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// ---------------------------------------------------------------------------
|
|
251
|
+
// Multi-edition stories (20 editions of an art print)
|
|
252
|
+
// ---------------------------------------------------------------------------
|
|
253
|
+
|
|
254
|
+
export const Default: StoryObj<typeof Auction> = {
|
|
255
|
+
render: () => (
|
|
256
|
+
<div className="h-screen w-full bg-muted p-8">
|
|
257
|
+
<div className="mx-auto flex h-full max-w-7xl items-center justify-center">
|
|
258
|
+
<Auction
|
|
259
|
+
auction={mockAuction}
|
|
260
|
+
bids={mockBids}
|
|
261
|
+
userBids={[]}
|
|
262
|
+
formatters={dollarFormatters}
|
|
263
|
+
onPlaceBid={async (price, qty) => {
|
|
264
|
+
console.log("Place bid:", price, "cents", qty);
|
|
265
|
+
return true;
|
|
266
|
+
}}
|
|
267
|
+
onTopUpBid={async (bidId, newPrice, value) => {
|
|
268
|
+
console.log("Top up:", bidId, newPrice, "cents", value);
|
|
269
|
+
return true;
|
|
270
|
+
}}
|
|
271
|
+
className="size-full overflow-hidden rounded-lg border border-border bg-background shadow-lg"
|
|
272
|
+
>
|
|
273
|
+
<AuctionLayout>
|
|
274
|
+
<AuctionDetails>
|
|
275
|
+
<AuctionDetailsHeader>
|
|
276
|
+
<AuctionStatus />
|
|
277
|
+
<h2 className="mt-2 text-lg font-semibold">
|
|
278
|
+
The Great Wave off Kanagawa
|
|
279
|
+
</h2>
|
|
280
|
+
<Text color="secondary">Katsushika Hokusai</Text>
|
|
281
|
+
<AuctionArtwork
|
|
282
|
+
className="mt-4"
|
|
283
|
+
src={EDITION_ARTWORK_URL}
|
|
284
|
+
alt={EDITION_ARTWORK_ALT}
|
|
285
|
+
/>
|
|
286
|
+
</AuctionDetailsHeader>
|
|
287
|
+
<AuctionDetailsBody>
|
|
288
|
+
<div className="space-y-4">
|
|
289
|
+
<div className="flex justify-between">
|
|
290
|
+
<Text size="2" color="tertiary">
|
|
291
|
+
Editions
|
|
292
|
+
</Text>
|
|
293
|
+
<Text size="2">20</Text>
|
|
294
|
+
</div>
|
|
295
|
+
<div className="flex justify-between">
|
|
296
|
+
<Text size="2" color="tertiary">
|
|
297
|
+
Reserve Price
|
|
298
|
+
</Text>
|
|
299
|
+
<Text size="2" tabularNums>
|
|
300
|
+
$100.00
|
|
301
|
+
</Text>
|
|
302
|
+
</div>
|
|
303
|
+
<Separator />
|
|
304
|
+
<Text size="2" color="secondary">
|
|
305
|
+
Bid on one of 20 limited edition prints. The top 20 bidders
|
|
306
|
+
each win an edition.
|
|
307
|
+
</Text>
|
|
308
|
+
</div>
|
|
309
|
+
</AuctionDetailsBody>
|
|
310
|
+
</AuctionDetails>
|
|
311
|
+
<AuctionRankingsContainer>
|
|
312
|
+
<AuctionRankings />
|
|
313
|
+
<AuctionBiddingPanel>
|
|
314
|
+
<AuctionBidForm.Root />
|
|
315
|
+
</AuctionBiddingPanel>
|
|
316
|
+
</AuctionRankingsContainer>
|
|
317
|
+
</AuctionLayout>
|
|
318
|
+
</Auction>
|
|
319
|
+
</div>
|
|
320
|
+
</div>
|
|
321
|
+
),
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
export const WithTopUp: StoryObj<typeof Auction> = {
|
|
325
|
+
render: () => (
|
|
326
|
+
<div className="h-screen w-full bg-muted p-8">
|
|
327
|
+
<div className="mx-auto flex h-full max-w-7xl items-center justify-center">
|
|
328
|
+
<Auction
|
|
329
|
+
auction={mockAuction}
|
|
330
|
+
bids={mockBids}
|
|
331
|
+
userBids={mockUserBids}
|
|
332
|
+
formatters={dollarFormatters}
|
|
333
|
+
onPlaceBid={async (price, qty) => {
|
|
334
|
+
console.log("Place bid:", price, "cents", qty);
|
|
335
|
+
return true;
|
|
336
|
+
}}
|
|
337
|
+
onTopUpBid={async (bidId, newPrice, value) => {
|
|
338
|
+
console.log("Top up:", bidId, newPrice, "cents", value);
|
|
339
|
+
return true;
|
|
340
|
+
}}
|
|
341
|
+
className="size-full overflow-hidden rounded-lg border border-border bg-background shadow-lg"
|
|
342
|
+
>
|
|
343
|
+
<AuctionLayout>
|
|
344
|
+
<AuctionDetails>
|
|
345
|
+
<AuctionDetailsHeader>
|
|
346
|
+
<AuctionStatus />
|
|
347
|
+
<h2 className="mt-2 text-lg font-semibold">
|
|
348
|
+
The Great Wave off Kanagawa
|
|
349
|
+
</h2>
|
|
350
|
+
<Text color="secondary">Katsushika Hokusai</Text>
|
|
351
|
+
<AuctionArtwork
|
|
352
|
+
className="mt-4"
|
|
353
|
+
src={EDITION_ARTWORK_URL}
|
|
354
|
+
alt={EDITION_ARTWORK_ALT}
|
|
355
|
+
/>
|
|
356
|
+
</AuctionDetailsHeader>
|
|
357
|
+
<AuctionDetailsBody>
|
|
358
|
+
<div className="space-y-4">
|
|
359
|
+
<div className="flex justify-between">
|
|
360
|
+
<Text size="2" color="tertiary">
|
|
361
|
+
Editions
|
|
362
|
+
</Text>
|
|
363
|
+
<Text size="2">20</Text>
|
|
364
|
+
</div>
|
|
365
|
+
<div className="flex justify-between">
|
|
366
|
+
<Text size="2" color="tertiary">
|
|
367
|
+
Reserve Price
|
|
368
|
+
</Text>
|
|
369
|
+
<Text size="2" tabularNums>
|
|
370
|
+
$100.00
|
|
371
|
+
</Text>
|
|
372
|
+
</div>
|
|
373
|
+
<Separator />
|
|
374
|
+
<Text size="2" color="secondary">
|
|
375
|
+
Bid on one of 20 limited edition prints. The top 20 bidders
|
|
376
|
+
each win an edition.
|
|
377
|
+
</Text>
|
|
378
|
+
</div>
|
|
379
|
+
</AuctionDetailsBody>
|
|
380
|
+
</AuctionDetails>
|
|
381
|
+
<AuctionRankingsContainer>
|
|
382
|
+
<AuctionRankings />
|
|
383
|
+
<AuctionBiddingPanel>
|
|
384
|
+
<AuctionBidForm.Root />
|
|
385
|
+
</AuctionBiddingPanel>
|
|
386
|
+
</AuctionRankingsContainer>
|
|
387
|
+
</AuctionLayout>
|
|
388
|
+
</Auction>
|
|
389
|
+
</div>
|
|
390
|
+
</div>
|
|
391
|
+
),
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
export const MobileBiddingOverlay: StoryObj<typeof Auction> = {
|
|
395
|
+
render: () => (
|
|
396
|
+
<div className="h-screen w-full bg-muted p-4 lg:p-8">
|
|
397
|
+
<div className="mx-auto flex h-full max-w-sm items-center justify-center">
|
|
398
|
+
<Auction
|
|
399
|
+
auction={mockAuction}
|
|
400
|
+
bids={mockBids}
|
|
401
|
+
userBids={[]}
|
|
402
|
+
formatters={dollarFormatters}
|
|
403
|
+
onPlaceBid={async (price, qty) => {
|
|
404
|
+
console.log("Place bid:", price, "cents", qty);
|
|
405
|
+
return true;
|
|
406
|
+
}}
|
|
407
|
+
onTopUpBid={async (bidId, newPrice, value) => {
|
|
408
|
+
console.log("Top up:", bidId, newPrice, "cents", value);
|
|
409
|
+
return true;
|
|
410
|
+
}}
|
|
411
|
+
className="size-full max-h-180 overflow-hidden rounded-lg border border-border bg-background shadow-lg"
|
|
412
|
+
>
|
|
413
|
+
<AutoStartBidding />
|
|
414
|
+
<AuctionLayout>
|
|
415
|
+
<AuctionDetails>
|
|
416
|
+
<AuctionDetailsHeader>
|
|
417
|
+
<AuctionStatus />
|
|
418
|
+
<h2 className="mt-2 text-lg font-semibold">
|
|
419
|
+
The Great Wave off Kanagawa
|
|
420
|
+
</h2>
|
|
421
|
+
<Text color="secondary">Katsushika Hokusai</Text>
|
|
422
|
+
<AuctionArtwork
|
|
423
|
+
className="mt-4"
|
|
424
|
+
src={EDITION_ARTWORK_URL}
|
|
425
|
+
alt={EDITION_ARTWORK_ALT}
|
|
426
|
+
/>
|
|
427
|
+
</AuctionDetailsHeader>
|
|
428
|
+
<AuctionDetailsBody>
|
|
429
|
+
<div className="space-y-4">
|
|
430
|
+
<div className="flex justify-between">
|
|
431
|
+
<Text size="2" color="tertiary">
|
|
432
|
+
Editions
|
|
433
|
+
</Text>
|
|
434
|
+
<Text size="2">20</Text>
|
|
435
|
+
</div>
|
|
436
|
+
<div className="flex justify-between">
|
|
437
|
+
<Text size="2" color="tertiary">
|
|
438
|
+
Reserve Price
|
|
439
|
+
</Text>
|
|
440
|
+
<Text size="2" tabularNums>
|
|
441
|
+
$100.00
|
|
442
|
+
</Text>
|
|
443
|
+
</div>
|
|
444
|
+
<Separator />
|
|
445
|
+
<Text size="2" color="secondary">
|
|
446
|
+
Bid on one of 20 limited edition prints. The top 20 bidders
|
|
447
|
+
each win an edition.
|
|
448
|
+
</Text>
|
|
449
|
+
</div>
|
|
450
|
+
</AuctionDetailsBody>
|
|
451
|
+
</AuctionDetails>
|
|
452
|
+
<AuctionRankingsContainer>
|
|
453
|
+
<AuctionRankings />
|
|
454
|
+
<AuctionBiddingPanel>
|
|
455
|
+
<AuctionBidForm.Root />
|
|
456
|
+
</AuctionBiddingPanel>
|
|
457
|
+
</AuctionRankingsContainer>
|
|
458
|
+
</AuctionLayout>
|
|
459
|
+
</Auction>
|
|
460
|
+
</div>
|
|
461
|
+
</div>
|
|
462
|
+
),
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
// ---------------------------------------------------------------------------
|
|
466
|
+
// Single-item stories (1-of-1 collectible)
|
|
467
|
+
// ---------------------------------------------------------------------------
|
|
468
|
+
|
|
469
|
+
interface SingleItemBid {
|
|
470
|
+
id: string;
|
|
471
|
+
name: string;
|
|
472
|
+
price: string;
|
|
473
|
+
time: string;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
const singleItemBids: SingleItemBid[] = mockSingleBids.map((b) => ({
|
|
477
|
+
id: b.id,
|
|
478
|
+
name: b.bidder?.name || `${b.bidder?.id.slice(0, 6)}...`,
|
|
479
|
+
price: formatPrice(BigInt(b.price)),
|
|
480
|
+
time: formatShortRelative(new Date(b.created_at)),
|
|
481
|
+
}));
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* Renders a preview slot in the Ranking component that shows where the user's
|
|
485
|
+
* current bid (from the bid form) would land. Reads bidValue/showBidPreview
|
|
486
|
+
* from the auction context so it updates live as the user adjusts the input.
|
|
487
|
+
*/
|
|
488
|
+
function BidPreviewSlot() {
|
|
489
|
+
const {
|
|
490
|
+
bidValue,
|
|
491
|
+
minBidValue,
|
|
492
|
+
showBidPreview,
|
|
493
|
+
isBiddingActive,
|
|
494
|
+
formatPrice: ctxFormatPrice,
|
|
495
|
+
currencySymbol,
|
|
496
|
+
getProjectedRank,
|
|
497
|
+
} = useAuctionContext();
|
|
498
|
+
|
|
499
|
+
if (!isBiddingActive || !showBidPreview || bidValue < minBidValue)
|
|
500
|
+
return null;
|
|
501
|
+
|
|
502
|
+
const { rank } = getProjectedRank(bidValue);
|
|
503
|
+
const atIndex = rank ? rank - 1 : 0;
|
|
504
|
+
|
|
505
|
+
return (
|
|
506
|
+
<Ranking.Slot slotKey="bid-preview" atIndex={atIndex}>
|
|
507
|
+
{(ctx) => (
|
|
508
|
+
<>
|
|
509
|
+
<div className="relative">
|
|
510
|
+
<div className="absolute inset-0 animate-[pulse_2s_ease-in-out_infinite] bg-success/10" />
|
|
511
|
+
<div className="relative flex items-center justify-between gap-2 px-6 py-4">
|
|
512
|
+
<div className="flex min-w-0 items-center gap-3">
|
|
513
|
+
<Text color="tertiary" className="w-8 shrink-0" size="2">
|
|
514
|
+
#{ctx.rank}
|
|
515
|
+
</Text>
|
|
516
|
+
<Text color="secondary" size="3" weight="medium">
|
|
517
|
+
Your bid
|
|
518
|
+
</Text>
|
|
519
|
+
</div>
|
|
520
|
+
<div className="flex shrink-0 items-center gap-3">
|
|
521
|
+
<Text tabularNums size="3" weight="medium">
|
|
522
|
+
{ctxFormatPrice(bidValue)} {currencySymbol}
|
|
523
|
+
</Text>
|
|
524
|
+
<span className="min-w-9" />
|
|
525
|
+
</div>
|
|
526
|
+
</div>
|
|
527
|
+
</div>
|
|
528
|
+
{!ctx.isLastInGroup && <Separator />}
|
|
529
|
+
</>
|
|
530
|
+
)}
|
|
531
|
+
</Ranking.Slot>
|
|
532
|
+
);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function SingleItemContent({ userBids }: { userBids: AuctionUserBid[] }) {
|
|
536
|
+
return (
|
|
537
|
+
<div className="h-screen w-full bg-muted p-8">
|
|
538
|
+
<div className="mx-auto flex h-full max-w-3xl items-center justify-center">
|
|
539
|
+
<Auction
|
|
540
|
+
auction={mockSingleAuction}
|
|
541
|
+
bids={mockSingleBids}
|
|
542
|
+
userBids={userBids}
|
|
543
|
+
formatters={dollarFormatters}
|
|
544
|
+
onPlaceBid={async (price, qty) => {
|
|
545
|
+
console.log("Place bid:", price, "cents", qty);
|
|
546
|
+
return true;
|
|
547
|
+
}}
|
|
548
|
+
onTopUpBid={async (bidId, newPrice, value) => {
|
|
549
|
+
console.log("Top up:", bidId, newPrice, "cents", value);
|
|
550
|
+
return true;
|
|
551
|
+
}}
|
|
552
|
+
className="flex h-full max-h-180 w-full overflow-hidden rounded-lg border border-border bg-background shadow-lg"
|
|
553
|
+
>
|
|
554
|
+
<div className="hidden min-w-0 flex-1 flex-col gap-6 p-6 lg:flex">
|
|
555
|
+
<div className="shrink-0">
|
|
556
|
+
<AuctionStatus />
|
|
557
|
+
<h2 className="mt-2 text-lg font-semibold">Time Walk</h2>
|
|
558
|
+
<Text color="secondary">
|
|
559
|
+
Magic: The Gathering · Alpha Edition
|
|
560
|
+
</Text>
|
|
561
|
+
</div>
|
|
562
|
+
<AuctionArtwork
|
|
563
|
+
className="min-h-0 flex-1"
|
|
564
|
+
src={SINGLE_ARTWORK_URL}
|
|
565
|
+
alt={SINGLE_ARTWORK_ALT}
|
|
566
|
+
/>
|
|
567
|
+
<div className="shrink-0 space-y-2 border-t border-border pt-6">
|
|
568
|
+
<div className="flex justify-between">
|
|
569
|
+
<Text size="2" color="tertiary">
|
|
570
|
+
Edition
|
|
571
|
+
</Text>
|
|
572
|
+
<Text size="2">1 of 1</Text>
|
|
573
|
+
</div>
|
|
574
|
+
<div className="flex justify-between">
|
|
575
|
+
<Text size="2" color="tertiary">
|
|
576
|
+
Reserve Price
|
|
577
|
+
</Text>
|
|
578
|
+
<Text size="2" tabularNums>
|
|
579
|
+
$500.00
|
|
580
|
+
</Text>
|
|
581
|
+
</div>
|
|
582
|
+
</div>
|
|
583
|
+
</div>
|
|
584
|
+
<div className="flex min-w-0 flex-1 flex-col lg:border-l lg:border-border">
|
|
585
|
+
<div className="min-h-0 flex-1 overflow-y-auto">
|
|
586
|
+
<Ranking.Root items={singleItemBids} getKey={(bid) => bid.id}>
|
|
587
|
+
<BidPreviewSlot />
|
|
588
|
+
<Ranking.Group>
|
|
589
|
+
<Ranking.GroupItem>
|
|
590
|
+
<Ranking.GroupItemValue>
|
|
591
|
+
{(bid: SingleItemBid, ctx) => {
|
|
592
|
+
const isHighest = ctx.globalIndex === 0;
|
|
593
|
+
return (
|
|
594
|
+
<>
|
|
595
|
+
<div
|
|
596
|
+
className={`flex items-center justify-between gap-2 px-6 ${
|
|
597
|
+
isHighest ? "py-4" : "py-2 opacity-50"
|
|
598
|
+
}`}
|
|
599
|
+
>
|
|
600
|
+
<div className="flex min-w-0 items-center gap-3">
|
|
601
|
+
<Text
|
|
602
|
+
color="tertiary"
|
|
603
|
+
className="w-8 shrink-0"
|
|
604
|
+
size={isHighest ? "2" : "1"}
|
|
605
|
+
>
|
|
606
|
+
#{ctx.globalIndex + 1}
|
|
607
|
+
</Text>
|
|
608
|
+
<Text
|
|
609
|
+
className="truncate"
|
|
610
|
+
size={isHighest ? "3" : undefined}
|
|
611
|
+
weight={isHighest ? "medium" : undefined}
|
|
612
|
+
>
|
|
613
|
+
{bid.name}
|
|
614
|
+
</Text>
|
|
615
|
+
</div>
|
|
616
|
+
<div className="flex shrink-0 items-center gap-3">
|
|
617
|
+
<Text
|
|
618
|
+
tabularNums
|
|
619
|
+
size={isHighest ? "3" : undefined}
|
|
620
|
+
weight={isHighest ? "medium" : undefined}
|
|
621
|
+
>
|
|
622
|
+
{bid.price} $
|
|
623
|
+
</Text>
|
|
624
|
+
<Text
|
|
625
|
+
size="1"
|
|
626
|
+
color="tertiary"
|
|
627
|
+
className="min-w-9 text-right"
|
|
628
|
+
>
|
|
629
|
+
{bid.time}
|
|
630
|
+
</Text>
|
|
631
|
+
</div>
|
|
632
|
+
</div>
|
|
633
|
+
{!ctx.isLastInGroup && <Separator />}
|
|
634
|
+
</>
|
|
635
|
+
);
|
|
636
|
+
}}
|
|
637
|
+
</Ranking.GroupItemValue>
|
|
638
|
+
</Ranking.GroupItem>
|
|
639
|
+
</Ranking.Group>
|
|
640
|
+
</Ranking.Root>
|
|
641
|
+
</div>
|
|
642
|
+
<AuctionBiddingPanel>
|
|
643
|
+
<AuctionBidForm.Root />
|
|
644
|
+
</AuctionBiddingPanel>
|
|
645
|
+
</div>
|
|
646
|
+
</Auction>
|
|
647
|
+
</div>
|
|
648
|
+
</div>
|
|
649
|
+
);
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
export const SingleItem: StoryObj<typeof Auction> = {
|
|
653
|
+
render: () => <SingleItemContent userBids={[]} />,
|
|
654
|
+
};
|
|
655
|
+
|
|
656
|
+
export const SingleItemWithBid: StoryObj<typeof Auction> = {
|
|
657
|
+
render: () => <SingleItemContent userBids={mockSingleUserBids} />,
|
|
658
|
+
};
|