@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,203 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { Button, Scale, Text } from "@/components/primitives";
|
|
5
|
+
import { cn } from "@/lib";
|
|
6
|
+
import { getProjectedRankForPrice } from "@/utils";
|
|
7
|
+
import { useAuctionContext } from "./AuctionContext";
|
|
8
|
+
|
|
9
|
+
export interface SuggestedBidContextValue {
|
|
10
|
+
value: bigint;
|
|
11
|
+
display: string;
|
|
12
|
+
position: number;
|
|
13
|
+
index: number;
|
|
14
|
+
isSelected: boolean;
|
|
15
|
+
onSelect: () => void;
|
|
16
|
+
disabled: boolean;
|
|
17
|
+
projectedRank: number | null;
|
|
18
|
+
isWinning: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const SuggestedBidContext =
|
|
22
|
+
React.createContext<SuggestedBidContextValue | null>(null);
|
|
23
|
+
|
|
24
|
+
function useSuggestedBid(): SuggestedBidContextValue {
|
|
25
|
+
const context = React.useContext(SuggestedBidContext);
|
|
26
|
+
if (!context) {
|
|
27
|
+
throw new Error(
|
|
28
|
+
"useSuggestedBid must be used within AuctionSuggestedBids.Item",
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
return context;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface AuctionSuggestedBidsProps {
|
|
35
|
+
className?: string;
|
|
36
|
+
count?: number;
|
|
37
|
+
children: (context: SuggestedBidContextValue) => React.ReactNode;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function AuctionSuggestedBidsRoot({
|
|
41
|
+
className,
|
|
42
|
+
count = 4,
|
|
43
|
+
children,
|
|
44
|
+
}: AuctionSuggestedBidsProps): React.ReactElement | null {
|
|
45
|
+
const {
|
|
46
|
+
minBidValue,
|
|
47
|
+
tickConfig,
|
|
48
|
+
tickSize,
|
|
49
|
+
isAuctionEnded,
|
|
50
|
+
bidValue,
|
|
51
|
+
setBidValue,
|
|
52
|
+
formatPrice,
|
|
53
|
+
currencySymbol,
|
|
54
|
+
bids,
|
|
55
|
+
setShowBidPreview,
|
|
56
|
+
lockedBid,
|
|
57
|
+
userBids,
|
|
58
|
+
mergedForRank,
|
|
59
|
+
maxTotalItems,
|
|
60
|
+
} = useAuctionContext();
|
|
61
|
+
|
|
62
|
+
const lockedBidId = React.useMemo(() => {
|
|
63
|
+
if (lockedBid === null) return null;
|
|
64
|
+
const lockedUserBid = userBids.find(
|
|
65
|
+
(ub) => ub.globalBidId === lockedBid.bidId,
|
|
66
|
+
);
|
|
67
|
+
return lockedUserBid?.id ?? null;
|
|
68
|
+
}, [lockedBid, userBids]);
|
|
69
|
+
|
|
70
|
+
const bidsForRankProjection = React.useMemo(() => {
|
|
71
|
+
if (lockedBidId === null) return mergedForRank;
|
|
72
|
+
return mergedForRank.filter((b) => b.id !== lockedBidId);
|
|
73
|
+
}, [mergedForRank, lockedBidId]);
|
|
74
|
+
|
|
75
|
+
const getProjectedRank = React.useCallback(
|
|
76
|
+
(priceValue: bigint) => {
|
|
77
|
+
return getProjectedRankForPrice(
|
|
78
|
+
priceValue,
|
|
79
|
+
bidsForRankProjection,
|
|
80
|
+
maxTotalItems,
|
|
81
|
+
);
|
|
82
|
+
},
|
|
83
|
+
[bidsForRankProjection, maxTotalItems],
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
if (isAuctionEnded) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const getTickSize = (value: bigint): bigint => {
|
|
91
|
+
if (!tickConfig) return tickSize;
|
|
92
|
+
return value > tickConfig.threshold
|
|
93
|
+
? tickConfig.largeTickSize
|
|
94
|
+
: tickConfig.smallTickSize;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const effectiveMinBid =
|
|
98
|
+
lockedBid !== null && lockedBid.priceValue > minBidValue
|
|
99
|
+
? lockedBid.priceValue
|
|
100
|
+
: minBidValue;
|
|
101
|
+
|
|
102
|
+
const highestBid =
|
|
103
|
+
bids.length > 0 ? BigInt(bids[0].price) : effectiveMinBid * 3n;
|
|
104
|
+
|
|
105
|
+
const leadingBid = highestBid + getTickSize(highestBid);
|
|
106
|
+
const maxBid =
|
|
107
|
+
leadingBid > effectiveMinBid ? leadingBid : effectiveMinBid * 3n;
|
|
108
|
+
|
|
109
|
+
const selectedValue = bidValue;
|
|
110
|
+
const disabled = isAuctionEnded;
|
|
111
|
+
|
|
112
|
+
return (
|
|
113
|
+
<Scale.Linear
|
|
114
|
+
domain={[effectiveMinBid, maxBid]}
|
|
115
|
+
getTickSize={getTickSize}
|
|
116
|
+
snapMode="up"
|
|
117
|
+
className={cn("grid grid-cols-2 gap-2", className)}
|
|
118
|
+
>
|
|
119
|
+
<Scale.Ticks count={count}>
|
|
120
|
+
{(
|
|
121
|
+
{ value, position }: { value: bigint; position: number },
|
|
122
|
+
index: number,
|
|
123
|
+
) => {
|
|
124
|
+
const v = value;
|
|
125
|
+
const display = `${formatPrice(v)} ${currencySymbol}`;
|
|
126
|
+
const isSelected = v === selectedValue;
|
|
127
|
+
const { rank: projectedRank, isWinning } = getProjectedRank(v);
|
|
128
|
+
|
|
129
|
+
const contextValue: SuggestedBidContextValue = {
|
|
130
|
+
value: v,
|
|
131
|
+
display,
|
|
132
|
+
position,
|
|
133
|
+
index,
|
|
134
|
+
isSelected,
|
|
135
|
+
onSelect: () => {
|
|
136
|
+
setBidValue(v);
|
|
137
|
+
setShowBidPreview(true);
|
|
138
|
+
},
|
|
139
|
+
disabled,
|
|
140
|
+
projectedRank,
|
|
141
|
+
isWinning,
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
return (
|
|
145
|
+
<SuggestedBidContext.Provider value={contextValue}>
|
|
146
|
+
{children(contextValue)}
|
|
147
|
+
</SuggestedBidContext.Provider>
|
|
148
|
+
);
|
|
149
|
+
}}
|
|
150
|
+
</Scale.Ticks>
|
|
151
|
+
</Scale.Linear>
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
interface AuctionSuggestedBidsItemProps {
|
|
156
|
+
labels?: string[];
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function AuctionSuggestedBidsItem({
|
|
160
|
+
labels = ["Minimum", "Safe Entry", "Competitive", "Leading"],
|
|
161
|
+
}: AuctionSuggestedBidsItemProps): React.ReactElement {
|
|
162
|
+
const context = useSuggestedBid();
|
|
163
|
+
const label = labels[context.index] ?? labels[labels.length - 1] ?? "Bid";
|
|
164
|
+
|
|
165
|
+
return (
|
|
166
|
+
<Button
|
|
167
|
+
type="button"
|
|
168
|
+
color="tertiary"
|
|
169
|
+
active={context.isSelected}
|
|
170
|
+
className="h-auto w-full items-start justify-between gap-2 px-3 py-2.5 text-left"
|
|
171
|
+
disabled={context.disabled}
|
|
172
|
+
onClick={context.onSelect}
|
|
173
|
+
size="card"
|
|
174
|
+
>
|
|
175
|
+
<div className="flex flex-col">
|
|
176
|
+
<Text color="secondary" size="1">
|
|
177
|
+
{label}
|
|
178
|
+
</Text>
|
|
179
|
+
<Text>{context.display}</Text>
|
|
180
|
+
</div>
|
|
181
|
+
{context.projectedRank != null && (
|
|
182
|
+
<span className="flex shrink-0 flex-col">
|
|
183
|
+
<Text color="secondary" size="1">
|
|
184
|
+
Rank
|
|
185
|
+
</Text>
|
|
186
|
+
<Text>#{context.projectedRank}</Text>
|
|
187
|
+
</span>
|
|
188
|
+
)}
|
|
189
|
+
</Button>
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
interface AuctionSuggestedBidsComponent {
|
|
194
|
+
Root: typeof AuctionSuggestedBidsRoot;
|
|
195
|
+
Item: typeof AuctionSuggestedBidsItem;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export const AuctionSuggestedBids: AuctionSuggestedBidsComponent = {
|
|
199
|
+
Root: AuctionSuggestedBidsRoot,
|
|
200
|
+
Item: AuctionSuggestedBidsItem,
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
export { useSuggestedBid };
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Button, Text } from "@/components/primitives";
|
|
4
|
+
import { cn } from "@/lib";
|
|
5
|
+
import type { AuctionUserBid } from "@/types";
|
|
6
|
+
import { formatDateTime } from "@/utils";
|
|
7
|
+
|
|
8
|
+
export interface AuctionYourBidCardProps {
|
|
9
|
+
bid: AuctionUserBid;
|
|
10
|
+
getRankForBid: (bidId: string) => number | null;
|
|
11
|
+
lockedBidId: bigint | null;
|
|
12
|
+
onLockForTopUp: (bidId: bigint, priceValue: bigint) => void;
|
|
13
|
+
onCancelTopUp: () => void;
|
|
14
|
+
isAuctionEnded: boolean;
|
|
15
|
+
onClaim?: (bidId: string) => Promise<boolean>;
|
|
16
|
+
formatPrice?: (priceValue: bigint) => string;
|
|
17
|
+
currencySymbol?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function getBidStatusLabel(
|
|
21
|
+
status: AuctionUserBid["status"],
|
|
22
|
+
isWinning: boolean,
|
|
23
|
+
): string {
|
|
24
|
+
if (status === "claimed") return "Won & Claimed";
|
|
25
|
+
if (isWinning) return "Winning";
|
|
26
|
+
if (status === "refunded") return "Refunded";
|
|
27
|
+
return "Outbid";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function AuctionYourBidCard({
|
|
31
|
+
bid,
|
|
32
|
+
getRankForBid,
|
|
33
|
+
lockedBidId,
|
|
34
|
+
onLockForTopUp,
|
|
35
|
+
onCancelTopUp,
|
|
36
|
+
isAuctionEnded,
|
|
37
|
+
onClaim,
|
|
38
|
+
formatPrice = (v) =>
|
|
39
|
+
(Number(v) / 1e18).toLocaleString("en-US", {
|
|
40
|
+
minimumFractionDigits: 3,
|
|
41
|
+
maximumFractionDigits: 3,
|
|
42
|
+
}),
|
|
43
|
+
currencySymbol = "USD",
|
|
44
|
+
}: AuctionYourBidCardProps): React.ReactElement {
|
|
45
|
+
const rank = getRankForBid(bid.id);
|
|
46
|
+
const isLocked = lockedBidId !== null && bid.globalBidId === lockedBidId;
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<div
|
|
50
|
+
className={cn(
|
|
51
|
+
"rounded-xs border bg-muted p-4",
|
|
52
|
+
isLocked ? "border-success" : "border-transparent",
|
|
53
|
+
)}
|
|
54
|
+
>
|
|
55
|
+
<div className="flex items-start justify-between gap-2">
|
|
56
|
+
<Text size="1" color="tertiary">
|
|
57
|
+
Rank #{rank ?? "-"}
|
|
58
|
+
</Text>
|
|
59
|
+
<Text
|
|
60
|
+
size="1"
|
|
61
|
+
color="tertiary"
|
|
62
|
+
className="tabular-nums"
|
|
63
|
+
suppressHydrationWarning
|
|
64
|
+
>
|
|
65
|
+
{formatDateTime(bid.createdAt)}
|
|
66
|
+
</Text>
|
|
67
|
+
</div>
|
|
68
|
+
<div className="flex justify-between">
|
|
69
|
+
<Text size="2" className="mt-2">
|
|
70
|
+
{formatPrice(bid.price)} {currencySymbol}
|
|
71
|
+
</Text>
|
|
72
|
+
<div className="mt-2 flex items-center gap-1.5">
|
|
73
|
+
{bid.isWinning ? (
|
|
74
|
+
<>
|
|
75
|
+
<div className="size-4 rounded-full bg-success" aria-hidden />
|
|
76
|
+
<Text size="2" color="success">
|
|
77
|
+
{getBidStatusLabel(bid.status, bid.isWinning)}
|
|
78
|
+
</Text>
|
|
79
|
+
</>
|
|
80
|
+
) : (
|
|
81
|
+
<>
|
|
82
|
+
<div className="size-4 rounded-full bg-destructive" aria-hidden />
|
|
83
|
+
<Text size="2" color="error">
|
|
84
|
+
{getBidStatusLabel(bid.status, bid.isWinning)}
|
|
85
|
+
</Text>
|
|
86
|
+
</>
|
|
87
|
+
)}
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
{bid.status === "claimed" ? (
|
|
91
|
+
<Text
|
|
92
|
+
size="1"
|
|
93
|
+
color="tertiary"
|
|
94
|
+
className="mt-2"
|
|
95
|
+
suppressHydrationWarning
|
|
96
|
+
>
|
|
97
|
+
Claimed {bid.claimedAt ? formatDateTime(bid.claimedAt) : ""}
|
|
98
|
+
</Text>
|
|
99
|
+
) : bid.isWinning && bid.status === "active" && onClaim ? (
|
|
100
|
+
<Button
|
|
101
|
+
color="secondary"
|
|
102
|
+
className="mt-3 w-full"
|
|
103
|
+
onClick={() => onClaim(bid.id)}
|
|
104
|
+
>
|
|
105
|
+
Claim
|
|
106
|
+
</Button>
|
|
107
|
+
) : null}
|
|
108
|
+
{bid.isWinning && !isAuctionEnded ? (
|
|
109
|
+
<div className="mt-3">
|
|
110
|
+
<Button
|
|
111
|
+
color="secondary"
|
|
112
|
+
className="w-full"
|
|
113
|
+
onClick={() =>
|
|
114
|
+
isLocked
|
|
115
|
+
? onCancelTopUp()
|
|
116
|
+
: onLockForTopUp(bid.globalBidId, bid.price)
|
|
117
|
+
}
|
|
118
|
+
>
|
|
119
|
+
{isLocked ? "Cancel top-up" : "Top up"}
|
|
120
|
+
</Button>
|
|
121
|
+
</div>
|
|
122
|
+
) : null}
|
|
123
|
+
</div>
|
|
124
|
+
);
|
|
125
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Text } from "@/components/primitives";
|
|
4
|
+
import { useAuctionContext } from "./AuctionContext";
|
|
5
|
+
import { AuctionYourBidCard } from "./AuctionYourBidCard";
|
|
6
|
+
|
|
7
|
+
export interface AuctionYourBidsProps {
|
|
8
|
+
className?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function AuctionYourBids({
|
|
12
|
+
className,
|
|
13
|
+
}: AuctionYourBidsProps): React.ReactElement | null {
|
|
14
|
+
const {
|
|
15
|
+
userBids,
|
|
16
|
+
getRankForBid,
|
|
17
|
+
lockedBid,
|
|
18
|
+
setLockedBid,
|
|
19
|
+
isAuctionEnded,
|
|
20
|
+
handleClaimEdition,
|
|
21
|
+
formatPrice,
|
|
22
|
+
currencySymbol,
|
|
23
|
+
} = useAuctionContext();
|
|
24
|
+
|
|
25
|
+
if (userBids.length === 0) return null;
|
|
26
|
+
|
|
27
|
+
const onLockForTopUp = (bidId: bigint, priceValue: bigint) => {
|
|
28
|
+
setLockedBid({ bidId, priceValue });
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const onCancelTopUp = () => setLockedBid(null);
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<div className={className}>
|
|
35
|
+
<Text
|
|
36
|
+
as="h3"
|
|
37
|
+
color="tertiary"
|
|
38
|
+
className="mb-3 shrink-0"
|
|
39
|
+
aria-label={`Your Bids (${userBids.length})`}
|
|
40
|
+
>
|
|
41
|
+
Your Bids ({userBids.length})
|
|
42
|
+
</Text>
|
|
43
|
+
<div className="-mr-1 min-h-0 flex-1 space-y-3 overflow-y-auto py-1 pr-1">
|
|
44
|
+
{userBids.map((bid) => (
|
|
45
|
+
<AuctionYourBidCard
|
|
46
|
+
key={bid.id}
|
|
47
|
+
bid={bid}
|
|
48
|
+
getRankForBid={getRankForBid}
|
|
49
|
+
lockedBidId={lockedBid?.bidId ?? null}
|
|
50
|
+
onLockForTopUp={onLockForTopUp}
|
|
51
|
+
onCancelTopUp={onCancelTopUp}
|
|
52
|
+
isAuctionEnded={isAuctionEnded}
|
|
53
|
+
onClaim={handleClaimEdition}
|
|
54
|
+
formatPrice={formatPrice}
|
|
55
|
+
currencySymbol={currencySymbol}
|
|
56
|
+
/>
|
|
57
|
+
))}
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export type { AuctionProps } from "./Auction";
|
|
2
|
+
export { Auction, useAuctionContext } from "./Auction";
|
|
3
|
+
export type { AuctionArtworkProps } from "./AuctionArtwork";
|
|
4
|
+
export { AuctionArtwork } from "./AuctionArtwork";
|
|
5
|
+
export { AuctionBidForm } from "./AuctionBidForm";
|
|
6
|
+
export type { AuctionBidInputProps } from "./AuctionBidInput";
|
|
7
|
+
export { AuctionBidInput } from "./AuctionBidInput";
|
|
8
|
+
export type { AuctionProviderProps } from "./AuctionContext";
|
|
9
|
+
export { AuctionProvider } from "./AuctionContext";
|
|
10
|
+
export type { AuctionInfoProps } from "./AuctionInfo";
|
|
11
|
+
export { AuctionInfo } from "./AuctionInfo";
|
|
12
|
+
export type {
|
|
13
|
+
AuctionBiddingPanelProps,
|
|
14
|
+
AuctionDetailsBodyProps,
|
|
15
|
+
AuctionDetailsFooterProps,
|
|
16
|
+
AuctionDetailsHeaderProps,
|
|
17
|
+
AuctionDetailsProps,
|
|
18
|
+
AuctionLayoutProps,
|
|
19
|
+
AuctionRankingsContainerProps,
|
|
20
|
+
} from "./AuctionLayout";
|
|
21
|
+
export {
|
|
22
|
+
AuctionBiddingPanel,
|
|
23
|
+
AuctionDetails,
|
|
24
|
+
AuctionDetailsBody,
|
|
25
|
+
AuctionDetailsFooter,
|
|
26
|
+
AuctionDetailsHeader,
|
|
27
|
+
AuctionLayout,
|
|
28
|
+
AuctionRankingsContainer,
|
|
29
|
+
} from "./AuctionLayout";
|
|
30
|
+
export type { AuctionRankingsProps } from "./AuctionRankings";
|
|
31
|
+
export { AuctionRankings, RankingsSkeleton } from "./AuctionRankings";
|
|
32
|
+
export type { AuctionStatusTagProps } from "./AuctionStatusTag";
|
|
33
|
+
export { AuctionStatusTag } from "./AuctionStatusTag";
|
|
34
|
+
export type {
|
|
35
|
+
AuctionSuggestedBidsProps,
|
|
36
|
+
SuggestedBidContextValue,
|
|
37
|
+
} from "./AuctionSuggestedBids";
|
|
38
|
+
export { AuctionSuggestedBids, useSuggestedBid } from "./AuctionSuggestedBids";
|
|
39
|
+
export type { AuctionYourBidCardProps } from "./AuctionYourBidCard";
|
|
40
|
+
export { AuctionYourBidCard } from "./AuctionYourBidCard";
|
|
41
|
+
export type { AuctionYourBidsProps } from "./AuctionYourBids";
|
|
42
|
+
export { AuctionYourBids } from "./AuctionYourBids";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./auction";
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { Button as ButtonPrimitive } from "@base-ui/react/button";
|
|
2
|
+
import { IconLoader2 } from "@tabler/icons-react";
|
|
3
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
4
|
+
import type * as React from "react";
|
|
5
|
+
import { cn } from "@/lib/cn";
|
|
6
|
+
|
|
7
|
+
type ButtonColor =
|
|
8
|
+
| "primary"
|
|
9
|
+
| "secondary"
|
|
10
|
+
| "tertiary"
|
|
11
|
+
| "ghost"
|
|
12
|
+
| "destructive"
|
|
13
|
+
| "link";
|
|
14
|
+
type ButtonSize =
|
|
15
|
+
| "default"
|
|
16
|
+
| "xs"
|
|
17
|
+
| "sm"
|
|
18
|
+
| "lg"
|
|
19
|
+
| "xl"
|
|
20
|
+
| "icon"
|
|
21
|
+
| "icon-xs"
|
|
22
|
+
| "icon-sm"
|
|
23
|
+
| "icon-lg"
|
|
24
|
+
| "card";
|
|
25
|
+
|
|
26
|
+
interface ButtonVariantProps {
|
|
27
|
+
color?: ButtonColor;
|
|
28
|
+
size?: ButtonSize;
|
|
29
|
+
className?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const buttonVariants = cva(
|
|
33
|
+
[
|
|
34
|
+
// Base layout & typography
|
|
35
|
+
"inline-flex cursor-pointer items-center justify-center gap-1.5",
|
|
36
|
+
"font-sans text-sm leading-none font-medium whitespace-nowrap",
|
|
37
|
+
"shrink-0 select-none",
|
|
38
|
+
// Transitions
|
|
39
|
+
"transition-all duration-150 ease-out",
|
|
40
|
+
// SVG children
|
|
41
|
+
"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
42
|
+
// Focus ring
|
|
43
|
+
"outline-none",
|
|
44
|
+
"focus-visible:ring-2 focus-visible:ring-offset-2",
|
|
45
|
+
"focus-visible:ring-ring focus-visible:ring-offset-background",
|
|
46
|
+
// Disabled - prevent interaction
|
|
47
|
+
"disabled:pointer-events-none disabled:cursor-not-allowed aria-disabled:pointer-events-none aria-disabled:cursor-not-allowed",
|
|
48
|
+
// aria-invalid
|
|
49
|
+
"aria-invalid:border-destructive aria-invalid:ring-2 aria-invalid:ring-destructive/30",
|
|
50
|
+
].join(" "),
|
|
51
|
+
{
|
|
52
|
+
variants: {
|
|
53
|
+
color: {
|
|
54
|
+
primary: [
|
|
55
|
+
"border border-solid",
|
|
56
|
+
"border-primary bg-primary text-primary-foreground",
|
|
57
|
+
"hover:border-primary-hover hover:bg-primary-hover",
|
|
58
|
+
"active:opacity-90",
|
|
59
|
+
"shadow-[0_1px_2px_0_rgb(0,0,0,0.08)]",
|
|
60
|
+
"disabled:opacity-50 aria-disabled:opacity-50",
|
|
61
|
+
].join(" "),
|
|
62
|
+
|
|
63
|
+
secondary: [
|
|
64
|
+
"border border-solid",
|
|
65
|
+
"border-border bg-transparent text-foreground",
|
|
66
|
+
"hover:border-input hover:bg-accent",
|
|
67
|
+
"active:border-input active:bg-accent-active",
|
|
68
|
+
"data-[state=active]:border-input data-[state=active]:bg-accent-active",
|
|
69
|
+
"shadow-[0_1px_2px_0_rgb(0,0,0,0.04)]",
|
|
70
|
+
"disabled:opacity-50 aria-disabled:opacity-50",
|
|
71
|
+
].join(" "),
|
|
72
|
+
|
|
73
|
+
tertiary: [
|
|
74
|
+
"border border-solid",
|
|
75
|
+
"border-border bg-muted text-muted-foreground",
|
|
76
|
+
"hover:border-input hover:bg-accent-hover hover:text-foreground",
|
|
77
|
+
"active:bg-accent-active active:text-foreground",
|
|
78
|
+
"data-[state=active]:bg-accent-active data-[state=active]:text-foreground",
|
|
79
|
+
"disabled:opacity-50 aria-disabled:opacity-50",
|
|
80
|
+
].join(" "),
|
|
81
|
+
|
|
82
|
+
ghost: [
|
|
83
|
+
"border border-transparent bg-transparent",
|
|
84
|
+
"text-muted-foreground",
|
|
85
|
+
"hover:bg-accent hover:text-foreground",
|
|
86
|
+
"active:bg-accent-active active:text-foreground",
|
|
87
|
+
"data-[state=active]:bg-accent-active data-[state=active]:text-foreground",
|
|
88
|
+
"disabled:text-disabled-foreground",
|
|
89
|
+
"aria-disabled:text-disabled-foreground",
|
|
90
|
+
].join(" "),
|
|
91
|
+
|
|
92
|
+
destructive: [
|
|
93
|
+
"border border-solid",
|
|
94
|
+
"border-destructive bg-destructive text-destructive-foreground",
|
|
95
|
+
"hover:border-destructive-hover hover:bg-destructive-hover",
|
|
96
|
+
"active:opacity-90",
|
|
97
|
+
"shadow-[0_1px_2px_0_rgb(0,0,0,0.08)]",
|
|
98
|
+
"disabled:opacity-50 aria-disabled:opacity-50",
|
|
99
|
+
].join(" "),
|
|
100
|
+
|
|
101
|
+
link: [
|
|
102
|
+
"border-transparent bg-transparent",
|
|
103
|
+
"text-foreground underline-offset-4",
|
|
104
|
+
"hover:underline",
|
|
105
|
+
"h-auto px-0 py-0",
|
|
106
|
+
"disabled:text-disabled-foreground disabled:no-underline",
|
|
107
|
+
"aria-disabled:text-disabled-foreground aria-disabled:no-underline",
|
|
108
|
+
].join(" "),
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
size: {
|
|
112
|
+
default: "h-9 rounded-md px-4 py-2 has-[>svg]:px-3",
|
|
113
|
+
xs: "h-6 gap-1 rounded px-2 text-xs has-[>svg]:px-1.5",
|
|
114
|
+
sm: "h-8 gap-1 rounded-md px-3 text-xs has-[>svg]:px-2.5",
|
|
115
|
+
lg: "h-10 rounded-lg px-5 text-[15px] has-[>svg]:px-4",
|
|
116
|
+
xl: "h-12 rounded-lg px-6 text-[15px] font-semibold has-[>svg]:px-5",
|
|
117
|
+
icon: "size-9 rounded-md",
|
|
118
|
+
"icon-xs": "size-6 rounded",
|
|
119
|
+
"icon-sm": "size-8 rounded-md",
|
|
120
|
+
"icon-lg": "size-10 rounded-lg",
|
|
121
|
+
card: "min-h-[4rem] rounded-md px-4 py-3",
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
defaultVariants: {
|
|
125
|
+
color: "primary",
|
|
126
|
+
size: "default",
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
) as (props?: ButtonVariantProps) => string;
|
|
130
|
+
|
|
131
|
+
type ButtonVariants = typeof buttonVariants;
|
|
132
|
+
|
|
133
|
+
export type ButtonProps = React.ComponentProps<typeof ButtonPrimitive> &
|
|
134
|
+
VariantProps<ButtonVariants> & {
|
|
135
|
+
loading?: boolean;
|
|
136
|
+
active?: boolean;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
export type ButtonElement = React.ComponentRef<"button">;
|
|
140
|
+
|
|
141
|
+
export function Button({
|
|
142
|
+
className,
|
|
143
|
+
color,
|
|
144
|
+
size,
|
|
145
|
+
disabled,
|
|
146
|
+
loading,
|
|
147
|
+
children,
|
|
148
|
+
active,
|
|
149
|
+
...props
|
|
150
|
+
}: ButtonProps): React.ReactElement {
|
|
151
|
+
const _disabled = disabled || loading;
|
|
152
|
+
|
|
153
|
+
const _children = loading ? (
|
|
154
|
+
<>
|
|
155
|
+
<span className="invisible contents">{children}</span>
|
|
156
|
+
<span className="absolute inset-0 flex items-center justify-center">
|
|
157
|
+
<IconLoader2 aria-hidden className="size-4 animate-spin" />
|
|
158
|
+
</span>
|
|
159
|
+
</>
|
|
160
|
+
) : (
|
|
161
|
+
children
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
return (
|
|
165
|
+
<ButtonPrimitive
|
|
166
|
+
data-state={active ? "active" : undefined}
|
|
167
|
+
disabled={_disabled}
|
|
168
|
+
focusableWhenDisabled
|
|
169
|
+
className={cn(
|
|
170
|
+
buttonVariants({ color, size }),
|
|
171
|
+
{
|
|
172
|
+
relative: loading,
|
|
173
|
+
},
|
|
174
|
+
className,
|
|
175
|
+
)}
|
|
176
|
+
{...props}
|
|
177
|
+
>
|
|
178
|
+
{_children}
|
|
179
|
+
</ButtonPrimitive>
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export { buttonVariants };
|