@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,401 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
createContext,
|
|
5
|
+
useCallback,
|
|
6
|
+
useContext,
|
|
7
|
+
useMemo,
|
|
8
|
+
useState,
|
|
9
|
+
} from "react";
|
|
10
|
+
import type {
|
|
11
|
+
AuctionData,
|
|
12
|
+
AuctionFormatters,
|
|
13
|
+
AuctionTickConfig,
|
|
14
|
+
AuctionUserBid,
|
|
15
|
+
OperationState,
|
|
16
|
+
RankableBid,
|
|
17
|
+
} from "@/types";
|
|
18
|
+
import { getProjectedRankForPrice, getSuggestedBidPrices } from "@/utils";
|
|
19
|
+
|
|
20
|
+
export interface AuctionContextValue {
|
|
21
|
+
auction: AuctionData;
|
|
22
|
+
isAuctionEnded: boolean;
|
|
23
|
+
|
|
24
|
+
bids: RankableBid[];
|
|
25
|
+
userBids: AuctionUserBid[];
|
|
26
|
+
maxTotalItems: number;
|
|
27
|
+
|
|
28
|
+
bidValue: bigint;
|
|
29
|
+
setBidValue: (value: bigint | ((prev: bigint) => bigint)) => void;
|
|
30
|
+
|
|
31
|
+
minBidValue: bigint;
|
|
32
|
+
reservePriceValue: bigint;
|
|
33
|
+
tickReferencePrice: bigint;
|
|
34
|
+
tickConfig: AuctionTickConfig | undefined;
|
|
35
|
+
tickSize: bigint;
|
|
36
|
+
|
|
37
|
+
placeBidOperation: OperationState;
|
|
38
|
+
topUpOperation: OperationState;
|
|
39
|
+
|
|
40
|
+
lockedBid: { bidId: bigint; priceValue: bigint } | null;
|
|
41
|
+
|
|
42
|
+
mergedForRank: RankableBid[];
|
|
43
|
+
getRankForBid: (bidId: string) => number | null;
|
|
44
|
+
getProjectedRank: (priceValue: bigint) => {
|
|
45
|
+
rank: number | null;
|
|
46
|
+
isWinning: boolean;
|
|
47
|
+
};
|
|
48
|
+
getSuggestedBids: () => bigint[];
|
|
49
|
+
|
|
50
|
+
showBidPreview: boolean;
|
|
51
|
+
setShowBidPreview: (show: boolean) => void;
|
|
52
|
+
|
|
53
|
+
isBiddingActive: boolean;
|
|
54
|
+
setIsBiddingActive: (active: boolean) => void;
|
|
55
|
+
startBidding: () => void;
|
|
56
|
+
cancelBidding: () => void;
|
|
57
|
+
|
|
58
|
+
setLockedBid: (bid: { bidId: bigint; priceValue: bigint } | null) => void;
|
|
59
|
+
handlePlaceBid: (price: string) => Promise<boolean>;
|
|
60
|
+
handleTopUp: (newPrice: string) => Promise<boolean>;
|
|
61
|
+
handleClaimEdition?: (bidId: string) => Promise<boolean>;
|
|
62
|
+
resetOperations: () => void;
|
|
63
|
+
|
|
64
|
+
formatPrice: (priceValue: bigint) => string;
|
|
65
|
+
formatTime: (date: Date) => string;
|
|
66
|
+
currencySymbol: string;
|
|
67
|
+
inputDecimals: number;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export const AuctionContext: React.Context<AuctionContextValue | null> =
|
|
71
|
+
createContext<AuctionContextValue | null>(null);
|
|
72
|
+
|
|
73
|
+
export function useAuctionContext(): AuctionContextValue {
|
|
74
|
+
const ctx = useContext(AuctionContext);
|
|
75
|
+
if (!ctx) {
|
|
76
|
+
throw new Error(
|
|
77
|
+
"useAuctionContext must be used within an Auction provider",
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
return ctx;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface AuctionProviderProps {
|
|
84
|
+
auction: AuctionData;
|
|
85
|
+
bids: RankableBid[];
|
|
86
|
+
userBids: AuctionUserBid[];
|
|
87
|
+
onPlaceBid: (price: bigint, quantity: bigint) => Promise<boolean>;
|
|
88
|
+
onTopUpBid: (
|
|
89
|
+
bidId: bigint,
|
|
90
|
+
newPrice: bigint,
|
|
91
|
+
additionalValue: bigint,
|
|
92
|
+
) => Promise<boolean>;
|
|
93
|
+
onClaimEdition?: (bidId: string) => Promise<boolean>;
|
|
94
|
+
formatters?: AuctionFormatters;
|
|
95
|
+
children: React.ReactNode;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function AuctionProvider({
|
|
99
|
+
auction,
|
|
100
|
+
bids,
|
|
101
|
+
userBids,
|
|
102
|
+
onPlaceBid,
|
|
103
|
+
onTopUpBid,
|
|
104
|
+
onClaimEdition,
|
|
105
|
+
formatters,
|
|
106
|
+
children,
|
|
107
|
+
}: AuctionProviderProps): React.ReactElement {
|
|
108
|
+
const defaultFormatPrice = (priceValue: bigint) => {
|
|
109
|
+
const val = Number(priceValue) / 1e18;
|
|
110
|
+
return val.toLocaleString("en-US", {
|
|
111
|
+
minimumFractionDigits: 3,
|
|
112
|
+
maximumFractionDigits: 3,
|
|
113
|
+
});
|
|
114
|
+
};
|
|
115
|
+
const defaultFormatTime = (date: Date) => {
|
|
116
|
+
const now = Date.now();
|
|
117
|
+
const diff = date.getTime() - now;
|
|
118
|
+
const isPast = diff < 0;
|
|
119
|
+
const absMs = Math.abs(diff);
|
|
120
|
+
const seconds = Math.floor(absMs / 1000);
|
|
121
|
+
const minutes = Math.floor(seconds / 60);
|
|
122
|
+
const hours = Math.floor(minutes / 60);
|
|
123
|
+
const days = Math.floor(hours / 24);
|
|
124
|
+
|
|
125
|
+
if (days > 0) return `${days}d ${hours % 24}h ${isPast ? "ago" : ""}`;
|
|
126
|
+
if (hours > 0) return `${hours}h ${minutes % 60}m ${isPast ? "ago" : ""}`;
|
|
127
|
+
if (minutes > 0)
|
|
128
|
+
return `${minutes}m ${seconds % 60}s ${isPast ? "ago" : ""}`;
|
|
129
|
+
return `${seconds}s ${isPast ? "ago" : ""}`;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const formatPrice = formatters?.formatPrice ?? defaultFormatPrice;
|
|
133
|
+
const formatTime = formatters?.formatTime ?? defaultFormatTime;
|
|
134
|
+
const currencySymbol = formatters?.currencySymbol ?? "USD";
|
|
135
|
+
const inputDecimals = formatters?.inputDecimals ?? 18;
|
|
136
|
+
|
|
137
|
+
const isAuctionEnded = auction.endsAt
|
|
138
|
+
? Date.now() > auction.endsAt.getTime()
|
|
139
|
+
: false;
|
|
140
|
+
|
|
141
|
+
const reservePriceValue = auction.reservePrice;
|
|
142
|
+
const tickConfig = auction.tickConfig;
|
|
143
|
+
|
|
144
|
+
const activeBids = bids.filter((b) => {
|
|
145
|
+
const bid = userBids.find((ub) => ub.id === b.id);
|
|
146
|
+
return !bid || bid.status === "active";
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
const totalActiveQty = BigInt(activeBids.length);
|
|
150
|
+
|
|
151
|
+
const mergedForRank = useMemo(() => {
|
|
152
|
+
const cutoffIds = new Set(bids.map((b) => b.id));
|
|
153
|
+
|
|
154
|
+
const myActiveBids = userBids.filter(
|
|
155
|
+
(bid) => bid.status === "active" && !cutoffIds.has(bid.id),
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
const myBidsNotInCutoff: RankableBid[] = myActiveBids.map((bid) => ({
|
|
159
|
+
id: bid.id,
|
|
160
|
+
price: bid.price.toString(),
|
|
161
|
+
created_at: bid.createdAt.toISOString(),
|
|
162
|
+
}));
|
|
163
|
+
|
|
164
|
+
const merged: RankableBid[] = [...bids, ...myBidsNotInCutoff];
|
|
165
|
+
|
|
166
|
+
return merged.sort((a, b) => {
|
|
167
|
+
const pa = BigInt(a.price);
|
|
168
|
+
const pb = BigInt(b.price);
|
|
169
|
+
if (pa !== pb) return pb > pa ? 1 : -1;
|
|
170
|
+
return (
|
|
171
|
+
new Date(a.created_at).getTime() - new Date(b.created_at).getTime()
|
|
172
|
+
);
|
|
173
|
+
});
|
|
174
|
+
}, [bids, userBids]);
|
|
175
|
+
|
|
176
|
+
const minBidValue = useMemo(() => {
|
|
177
|
+
if (totalActiveQty >= BigInt(auction.maxTotalItems)) {
|
|
178
|
+
const winningBids = mergedForRank.slice(0, auction.maxTotalItems);
|
|
179
|
+
|
|
180
|
+
if (winningBids.length > 0) {
|
|
181
|
+
const lastWinning = winningBids[winningBids.length - 1];
|
|
182
|
+
const tickSize = tickConfig
|
|
183
|
+
? BigInt(lastWinning.price) > tickConfig.threshold
|
|
184
|
+
? tickConfig.largeTickSize
|
|
185
|
+
: tickConfig.smallTickSize
|
|
186
|
+
: BigInt(lastWinning.price);
|
|
187
|
+
return BigInt(lastWinning.price) + tickSize;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return reservePriceValue;
|
|
191
|
+
}, [
|
|
192
|
+
reservePriceValue,
|
|
193
|
+
totalActiveQty,
|
|
194
|
+
auction.maxTotalItems,
|
|
195
|
+
mergedForRank,
|
|
196
|
+
tickConfig,
|
|
197
|
+
]);
|
|
198
|
+
|
|
199
|
+
const tickReferencePrice = minBidValue;
|
|
200
|
+
|
|
201
|
+
const tickSize = useMemo(() => {
|
|
202
|
+
if (!tickConfig) return minBidValue;
|
|
203
|
+
return BigInt(minBidValue) > tickConfig.threshold
|
|
204
|
+
? tickConfig.largeTickSize
|
|
205
|
+
: tickConfig.smallTickSize;
|
|
206
|
+
}, [tickConfig, minBidValue]);
|
|
207
|
+
|
|
208
|
+
const getRankForBid = useCallback(
|
|
209
|
+
(bidId: string) => {
|
|
210
|
+
const idx = mergedForRank.findIndex((b) => b.id === bidId);
|
|
211
|
+
return idx >= 0 ? idx + 1 : null;
|
|
212
|
+
},
|
|
213
|
+
[mergedForRank],
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
const getProjectedRank = useCallback(
|
|
217
|
+
(priceValue: bigint) => {
|
|
218
|
+
return getProjectedRankForPrice(
|
|
219
|
+
priceValue,
|
|
220
|
+
mergedForRank,
|
|
221
|
+
auction.maxTotalItems,
|
|
222
|
+
);
|
|
223
|
+
},
|
|
224
|
+
[mergedForRank, auction.maxTotalItems],
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
const getSuggestedBids = useCallback(() => {
|
|
228
|
+
return getSuggestedBidPrices({
|
|
229
|
+
mergedForRank,
|
|
230
|
+
maxTotalItems: auction.maxTotalItems,
|
|
231
|
+
minBidValue,
|
|
232
|
+
reservePriceValue,
|
|
233
|
+
tickConfig,
|
|
234
|
+
tickSize,
|
|
235
|
+
});
|
|
236
|
+
}, [
|
|
237
|
+
mergedForRank,
|
|
238
|
+
auction.maxTotalItems,
|
|
239
|
+
minBidValue,
|
|
240
|
+
reservePriceValue,
|
|
241
|
+
tickConfig,
|
|
242
|
+
tickSize,
|
|
243
|
+
]);
|
|
244
|
+
|
|
245
|
+
const [placeBidOperation, setPlaceBidOperation] = useState<OperationState>({
|
|
246
|
+
status: "idle",
|
|
247
|
+
});
|
|
248
|
+
const [topUpOperation, setTopUpOperation] = useState<OperationState>({
|
|
249
|
+
status: "idle",
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
const [lockedBid, setLockedBid] = useState<{
|
|
253
|
+
bidId: bigint;
|
|
254
|
+
priceValue: bigint;
|
|
255
|
+
} | null>(null);
|
|
256
|
+
|
|
257
|
+
const [showBidPreview, setShowBidPreview] = useState(false);
|
|
258
|
+
|
|
259
|
+
const [isBiddingActive, setIsBiddingActive] = useState(false);
|
|
260
|
+
|
|
261
|
+
const startBidding = useCallback(() => {
|
|
262
|
+
setIsBiddingActive(true);
|
|
263
|
+
setShowBidPreview(true);
|
|
264
|
+
}, []);
|
|
265
|
+
|
|
266
|
+
const cancelBidding = useCallback(() => {
|
|
267
|
+
setIsBiddingActive(false);
|
|
268
|
+
setShowBidPreview(false);
|
|
269
|
+
setLockedBid(null);
|
|
270
|
+
}, []);
|
|
271
|
+
|
|
272
|
+
const setLockedBidAndActivate = useCallback(
|
|
273
|
+
(bid: { bidId: bigint; priceValue: bigint } | null) => {
|
|
274
|
+
setLockedBid(bid);
|
|
275
|
+
if (bid !== null) {
|
|
276
|
+
setIsBiddingActive(true);
|
|
277
|
+
setShowBidPreview(true);
|
|
278
|
+
}
|
|
279
|
+
},
|
|
280
|
+
[],
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
const [bidValue, setBidValue] = useState<bigint>(minBidValue);
|
|
284
|
+
|
|
285
|
+
const resetOperations = useCallback(() => {
|
|
286
|
+
setPlaceBidOperation({ status: "idle" });
|
|
287
|
+
setTopUpOperation({ status: "idle" });
|
|
288
|
+
}, []);
|
|
289
|
+
|
|
290
|
+
const handlePlaceBid = useCallback(
|
|
291
|
+
async (price: string) => {
|
|
292
|
+
const priceValue = BigInt(price);
|
|
293
|
+
setPlaceBidOperation({ status: "pending" });
|
|
294
|
+
|
|
295
|
+
try {
|
|
296
|
+
const success = await onPlaceBid(priceValue, 1n);
|
|
297
|
+
if (success) {
|
|
298
|
+
setPlaceBidOperation({ status: "success" });
|
|
299
|
+
setShowBidPreview(false);
|
|
300
|
+
return true;
|
|
301
|
+
} else {
|
|
302
|
+
setPlaceBidOperation({
|
|
303
|
+
status: "error",
|
|
304
|
+
error: "Transaction failed",
|
|
305
|
+
});
|
|
306
|
+
return false;
|
|
307
|
+
}
|
|
308
|
+
} catch (err) {
|
|
309
|
+
setPlaceBidOperation({
|
|
310
|
+
status: "error",
|
|
311
|
+
error: err instanceof Error ? err.message : "Unknown error",
|
|
312
|
+
});
|
|
313
|
+
return false;
|
|
314
|
+
}
|
|
315
|
+
},
|
|
316
|
+
[onPlaceBid],
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
const handleTopUp = useCallback(
|
|
320
|
+
async (newPrice: string) => {
|
|
321
|
+
if (!lockedBid) return false;
|
|
322
|
+
|
|
323
|
+
const newPriceValue = BigInt(newPrice);
|
|
324
|
+
const additionalValue = newPriceValue - lockedBid.priceValue;
|
|
325
|
+
|
|
326
|
+
setTopUpOperation({ status: "pending" });
|
|
327
|
+
|
|
328
|
+
try {
|
|
329
|
+
const success = await onTopUpBid(
|
|
330
|
+
lockedBid.bidId,
|
|
331
|
+
newPriceValue,
|
|
332
|
+
additionalValue,
|
|
333
|
+
);
|
|
334
|
+
if (success) {
|
|
335
|
+
setTopUpOperation({ status: "success" });
|
|
336
|
+
setLockedBid(null);
|
|
337
|
+
return true;
|
|
338
|
+
} else {
|
|
339
|
+
setTopUpOperation({ status: "error", error: "Transaction failed" });
|
|
340
|
+
return false;
|
|
341
|
+
}
|
|
342
|
+
} catch (err) {
|
|
343
|
+
setTopUpOperation({
|
|
344
|
+
status: "error",
|
|
345
|
+
error: err instanceof Error ? err.message : "Unknown error",
|
|
346
|
+
});
|
|
347
|
+
return false;
|
|
348
|
+
}
|
|
349
|
+
},
|
|
350
|
+
[lockedBid, onTopUpBid],
|
|
351
|
+
);
|
|
352
|
+
|
|
353
|
+
const handleClaimEdition = useCallback(
|
|
354
|
+
async (bidId: string) => {
|
|
355
|
+
if (!onClaimEdition) return false;
|
|
356
|
+
return onClaimEdition(bidId);
|
|
357
|
+
},
|
|
358
|
+
[onClaimEdition],
|
|
359
|
+
);
|
|
360
|
+
|
|
361
|
+
const value: AuctionContextValue = {
|
|
362
|
+
auction,
|
|
363
|
+
isAuctionEnded,
|
|
364
|
+
bids,
|
|
365
|
+
userBids,
|
|
366
|
+
maxTotalItems: auction.maxTotalItems,
|
|
367
|
+
bidValue,
|
|
368
|
+
setBidValue,
|
|
369
|
+
minBidValue,
|
|
370
|
+
reservePriceValue,
|
|
371
|
+
tickReferencePrice,
|
|
372
|
+
tickConfig,
|
|
373
|
+
tickSize,
|
|
374
|
+
placeBidOperation,
|
|
375
|
+
topUpOperation,
|
|
376
|
+
lockedBid,
|
|
377
|
+
mergedForRank,
|
|
378
|
+
getRankForBid,
|
|
379
|
+
getProjectedRank,
|
|
380
|
+
getSuggestedBids,
|
|
381
|
+
showBidPreview,
|
|
382
|
+
setShowBidPreview,
|
|
383
|
+
isBiddingActive,
|
|
384
|
+
setIsBiddingActive,
|
|
385
|
+
startBidding,
|
|
386
|
+
cancelBidding,
|
|
387
|
+
setLockedBid: setLockedBidAndActivate,
|
|
388
|
+
handlePlaceBid,
|
|
389
|
+
handleTopUp,
|
|
390
|
+
handleClaimEdition,
|
|
391
|
+
resetOperations,
|
|
392
|
+
formatPrice,
|
|
393
|
+
formatTime,
|
|
394
|
+
currencySymbol,
|
|
395
|
+
inputDecimals,
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
return (
|
|
399
|
+
<AuctionContext.Provider value={value}>{children}</AuctionContext.Provider>
|
|
400
|
+
);
|
|
401
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Text } from "@/components/primitives";
|
|
4
|
+
import { useAuctionContext } from "./AuctionContext";
|
|
5
|
+
import { AuctionStatusTag } from "./AuctionStatusTag";
|
|
6
|
+
|
|
7
|
+
export interface AuctionInfoProps {
|
|
8
|
+
className?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function AuctionInfo({
|
|
12
|
+
className,
|
|
13
|
+
}: AuctionInfoProps): React.ReactElement {
|
|
14
|
+
const { auction, isAuctionEnded, maxTotalItems } = useAuctionContext();
|
|
15
|
+
|
|
16
|
+
const editionsLabel =
|
|
17
|
+
maxTotalItems === 1 ? "1 item" : `${maxTotalItems} items`;
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<div className={className}>
|
|
21
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
22
|
+
<AuctionStatusTag
|
|
23
|
+
opensAt={auction.opensAt}
|
|
24
|
+
endsAt={auction.endsAt}
|
|
25
|
+
background="transparent"
|
|
26
|
+
/>
|
|
27
|
+
</div>
|
|
28
|
+
{!isAuctionEnded ? (
|
|
29
|
+
<Text size="2" color="tertiary" className="mt-2">
|
|
30
|
+
Bid on one of {editionsLabel}. Top {maxTotalItems} bidders win and pay
|
|
31
|
+
the lowest winning bid.
|
|
32
|
+
</Text>
|
|
33
|
+
) : null}
|
|
34
|
+
</div>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { ReactNode } from "react";
|
|
4
|
+
import { Button } from "@/components/primitives";
|
|
5
|
+
import { cn } from "@/lib";
|
|
6
|
+
import { useAuctionContext } from "./AuctionContext";
|
|
7
|
+
|
|
8
|
+
export interface AuctionLayoutProps {
|
|
9
|
+
children: ReactNode;
|
|
10
|
+
className?: string;
|
|
11
|
+
height?: string | number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function AuctionLayout({
|
|
15
|
+
children,
|
|
16
|
+
className,
|
|
17
|
+
height = "calc(100vh - 4rem)",
|
|
18
|
+
}: AuctionLayoutProps): React.ReactElement {
|
|
19
|
+
const heightStyle = typeof height === "number" ? `${height}px` : height;
|
|
20
|
+
const { isBiddingActive } = useAuctionContext();
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<div
|
|
24
|
+
className={cn(
|
|
25
|
+
"relative grid overflow-hidden grid-rows-[minmax(0,1fr)_minmax(0,1fr)]",
|
|
26
|
+
isBiddingActive && "grid-rows-1",
|
|
27
|
+
"lg:grid-cols-2 lg:grid-rows-1",
|
|
28
|
+
className,
|
|
29
|
+
)}
|
|
30
|
+
style={{ height: heightStyle }}
|
|
31
|
+
>
|
|
32
|
+
{children}
|
|
33
|
+
</div>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface AuctionDetailsProps {
|
|
38
|
+
children: ReactNode;
|
|
39
|
+
className?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function AuctionDetails({
|
|
43
|
+
children,
|
|
44
|
+
className,
|
|
45
|
+
}: AuctionDetailsProps): React.ReactElement {
|
|
46
|
+
const { isBiddingActive } = useAuctionContext();
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<div
|
|
50
|
+
className={cn(
|
|
51
|
+
"relative flex h-full min-h-0 flex-col overflow-hidden",
|
|
52
|
+
"border-border",
|
|
53
|
+
"lg:border-r",
|
|
54
|
+
isBiddingActive && "overflow-hidden",
|
|
55
|
+
className,
|
|
56
|
+
)}
|
|
57
|
+
>
|
|
58
|
+
{children}
|
|
59
|
+
</div>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface AuctionDetailsHeaderProps {
|
|
64
|
+
children: ReactNode;
|
|
65
|
+
className?: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function AuctionDetailsHeader({
|
|
69
|
+
children,
|
|
70
|
+
className,
|
|
71
|
+
}: AuctionDetailsHeaderProps): React.ReactElement {
|
|
72
|
+
const { isBiddingActive } = useAuctionContext();
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<div
|
|
76
|
+
className={cn(
|
|
77
|
+
"flex min-h-0 grow flex-col p-6",
|
|
78
|
+
isBiddingActive &&
|
|
79
|
+
[
|
|
80
|
+
"max-lg:overflow-hidden",
|
|
81
|
+
"max-lg:[&>*:nth-child(n+3):not(:last-child)]:hidden",
|
|
82
|
+
"max-lg:[&>*:last-child]:pointer-events-none",
|
|
83
|
+
"max-lg:[&>*:last-child]:absolute",
|
|
84
|
+
"max-lg:[&>*:last-child]:inset-x-6",
|
|
85
|
+
"max-lg:[&>*:last-child]:bottom-6",
|
|
86
|
+
"max-lg:[&>*:last-child]:top-28",
|
|
87
|
+
"max-lg:[&>*:last-child]:min-h-0",
|
|
88
|
+
"max-lg:[&>*:last-child]:overflow-hidden",
|
|
89
|
+
].join(" "),
|
|
90
|
+
className,
|
|
91
|
+
)}
|
|
92
|
+
>
|
|
93
|
+
{children}
|
|
94
|
+
</div>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface AuctionDetailsBodyProps {
|
|
99
|
+
children: ReactNode;
|
|
100
|
+
className?: string;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function AuctionDetailsBody({
|
|
104
|
+
children,
|
|
105
|
+
className,
|
|
106
|
+
}: AuctionDetailsBodyProps): React.ReactElement {
|
|
107
|
+
return (
|
|
108
|
+
<div
|
|
109
|
+
className={cn(
|
|
110
|
+
"hidden p-6 lg:block lg:min-h-0 lg:flex-1 lg:overflow-y-auto",
|
|
111
|
+
className,
|
|
112
|
+
)}
|
|
113
|
+
>
|
|
114
|
+
{children}
|
|
115
|
+
</div>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export interface AuctionDetailsFooterProps {
|
|
120
|
+
children: ReactNode;
|
|
121
|
+
className?: string;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function AuctionDetailsFooter({
|
|
125
|
+
children,
|
|
126
|
+
className,
|
|
127
|
+
}: AuctionDetailsFooterProps): React.ReactElement {
|
|
128
|
+
return (
|
|
129
|
+
<div
|
|
130
|
+
className={cn("mt-auto shrink-0 border-t border-border p-6", className)}
|
|
131
|
+
>
|
|
132
|
+
{children}
|
|
133
|
+
</div>
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export interface AuctionRankingsContainerProps {
|
|
138
|
+
children: ReactNode;
|
|
139
|
+
className?: string;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function AuctionRankingsContainer({
|
|
143
|
+
children,
|
|
144
|
+
className,
|
|
145
|
+
}: AuctionRankingsContainerProps): React.ReactElement {
|
|
146
|
+
const { isBiddingActive } = useAuctionContext();
|
|
147
|
+
|
|
148
|
+
return (
|
|
149
|
+
<div
|
|
150
|
+
className={cn(
|
|
151
|
+
"grid h-full min-h-0 grid-rows-[minmax(0,1fr)_auto]",
|
|
152
|
+
isBiddingActive &&
|
|
153
|
+
"max-lg:absolute max-lg:inset-x-0 max-lg:bottom-0 max-lg:top-0 max-lg:z-20 max-lg:overflow-hidden max-lg:rounded-t-[1.75rem] max-lg:border-t max-lg:border-border max-lg:bg-background/95 max-lg:shadow-2xl max-lg:backdrop-blur-sm",
|
|
154
|
+
className,
|
|
155
|
+
)}
|
|
156
|
+
>
|
|
157
|
+
{children}
|
|
158
|
+
</div>
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export interface AuctionBiddingPanelProps {
|
|
163
|
+
children: ReactNode;
|
|
164
|
+
className?: string;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function AuctionBiddingPanel({
|
|
168
|
+
children,
|
|
169
|
+
className,
|
|
170
|
+
}: AuctionBiddingPanelProps): React.ReactElement {
|
|
171
|
+
const { isBiddingActive, startBidding, isAuctionEnded, setShowBidPreview } =
|
|
172
|
+
useAuctionContext();
|
|
173
|
+
|
|
174
|
+
const handleStartBidding = () => {
|
|
175
|
+
startBidding();
|
|
176
|
+
setShowBidPreview(true);
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
return (
|
|
180
|
+
<div
|
|
181
|
+
className={cn(
|
|
182
|
+
"row-start-2 rounded-t-[1.75rem] border-t border-border bg-background p-6 lg:rounded-xl",
|
|
183
|
+
className,
|
|
184
|
+
)}
|
|
185
|
+
>
|
|
186
|
+
{!isBiddingActive ? (
|
|
187
|
+
<Button
|
|
188
|
+
className="w-full"
|
|
189
|
+
size="lg"
|
|
190
|
+
disabled={isAuctionEnded}
|
|
191
|
+
onClick={handleStartBidding}
|
|
192
|
+
>
|
|
193
|
+
{isAuctionEnded ? "Auction Ended" : "Start Bidding"}
|
|
194
|
+
</Button>
|
|
195
|
+
) : (
|
|
196
|
+
children
|
|
197
|
+
)}
|
|
198
|
+
</div>
|
|
199
|
+
);
|
|
200
|
+
}
|