@medialane/ui 0.1.5 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/brand-icon.cjs +0 -9
- package/dist/components/brand-icon.cjs.map +1 -1
- package/dist/components/brand-icon.js +0 -9
- package/dist/components/brand-icon.js.map +1 -1
- package/dist/components/collection-card.cjs +124 -0
- package/dist/components/collection-card.cjs.map +1 -0
- package/dist/components/collection-card.d.cts +13 -0
- package/dist/components/collection-card.d.ts +13 -0
- package/dist/components/collection-card.js +89 -0
- package/dist/components/collection-card.js.map +1 -0
- package/dist/components/motion-primitives.cjs +126 -0
- package/dist/components/motion-primitives.cjs.map +1 -0
- package/dist/components/motion-primitives.d.cts +36 -0
- package/dist/components/motion-primitives.d.ts +36 -0
- package/dist/components/motion-primitives.js +96 -0
- package/dist/components/motion-primitives.js.map +1 -0
- package/dist/components/scroll-section.cjs +72 -0
- package/dist/components/scroll-section.cjs.map +1 -0
- package/dist/components/scroll-section.d.cts +18 -0
- package/dist/components/scroll-section.d.ts +18 -0
- package/dist/components/scroll-section.js +38 -0
- package/dist/components/scroll-section.js.map +1 -0
- package/dist/components/share-button.cjs +86 -0
- package/dist/components/share-button.cjs.map +1 -0
- package/dist/components/share-button.d.cts +12 -0
- package/dist/components/share-button.d.ts +12 -0
- package/dist/components/share-button.js +62 -0
- package/dist/components/share-button.js.map +1 -0
- package/dist/components/token-card.cjs +281 -0
- package/dist/components/token-card.cjs.map +1 -0
- package/dist/components/token-card.d.cts +31 -0
- package/dist/components/token-card.d.ts +31 -0
- package/dist/components/token-card.js +256 -0
- package/dist/components/token-card.js.map +1 -0
- package/dist/index.cjs +31 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -1
- package/package.json +15 -9
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
"use client";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __export = (target, all) => {
|
|
10
|
+
for (var name in all)
|
|
11
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
+
};
|
|
13
|
+
var __copyProps = (to, from, except, desc) => {
|
|
14
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
+
for (let key of __getOwnPropNames(from))
|
|
16
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
17
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
18
|
+
}
|
|
19
|
+
return to;
|
|
20
|
+
};
|
|
21
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
22
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
23
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
24
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
25
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
26
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
27
|
+
mod
|
|
28
|
+
));
|
|
29
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
30
|
+
var token_card_exports = {};
|
|
31
|
+
__export(token_card_exports, {
|
|
32
|
+
TokenCard: () => TokenCard,
|
|
33
|
+
TokenCardSkeleton: () => TokenCardSkeleton
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(token_card_exports);
|
|
36
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
37
|
+
var import_react = require("react");
|
|
38
|
+
var import_link = __toESM(require("next/link"), 1);
|
|
39
|
+
var import_image = __toESM(require("next/image"), 1);
|
|
40
|
+
var import_lucide_react = require("lucide-react");
|
|
41
|
+
var import_cn = require("../utils/cn.js");
|
|
42
|
+
var import_ipfs = require("../utils/ipfs.js");
|
|
43
|
+
var import_format = require("../utils/format.js");
|
|
44
|
+
var import_currency_icon = require("./currency-icon.js");
|
|
45
|
+
var import_ip_type_badge = require("./ip-type-badge.js");
|
|
46
|
+
var import_motion_primitives = require("./motion-primitives.js");
|
|
47
|
+
const RARITY_STYLE = {
|
|
48
|
+
legendary: { label: "Legendary", className: "bg-yellow-400/90 text-yellow-900" },
|
|
49
|
+
epic: { label: "Epic", className: "bg-purple-500/85 text-white" },
|
|
50
|
+
rare: { label: "Rare", className: "bg-blue-500/85 text-white" },
|
|
51
|
+
uncommon: { label: "Uncommon", className: "bg-emerald-500/85 text-white" },
|
|
52
|
+
common: null
|
|
53
|
+
};
|
|
54
|
+
const BTN_BASE = "h-8 rounded-[11px] flex items-center justify-center gap-1.5 text-xs font-semibold transition-all active:scale-[0.98] shadow-none border-0";
|
|
55
|
+
const BTN_SOLID = (0, import_cn.cn)(BTN_BASE, "text-white hover:brightness-110");
|
|
56
|
+
const BTN_OUTLINE = (0, import_cn.cn)(BTN_BASE, "border border-border/60 text-foreground hover:bg-muted/60");
|
|
57
|
+
function TokenCard({
|
|
58
|
+
token,
|
|
59
|
+
isOwner = false,
|
|
60
|
+
inCart = false,
|
|
61
|
+
showBuyButton = true,
|
|
62
|
+
rarityTier,
|
|
63
|
+
className,
|
|
64
|
+
onBuy,
|
|
65
|
+
onCart,
|
|
66
|
+
onOffer,
|
|
67
|
+
onList,
|
|
68
|
+
onCancel,
|
|
69
|
+
onTransfer,
|
|
70
|
+
overflowMenu
|
|
71
|
+
}) {
|
|
72
|
+
const [imgError, setImgError] = (0, import_react.useState)(false);
|
|
73
|
+
const name = token.metadata?.name || `Token #${token.tokenId}`;
|
|
74
|
+
const image = (0, import_ipfs.ipfsToHttp)(token.metadata?.image);
|
|
75
|
+
const activeOrder = token.activeOrders?.[0];
|
|
76
|
+
const assetHref = `/asset/${token.contractAddress}/${token.tokenId}`;
|
|
77
|
+
const renderActions = () => {
|
|
78
|
+
if (!isOwner && activeOrder && showBuyButton) {
|
|
79
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
80
|
+
onBuy && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
81
|
+
"button",
|
|
82
|
+
{
|
|
83
|
+
className: (0, import_cn.cn)(BTN_SOLID, "flex-1 bg-brand-purple"),
|
|
84
|
+
onClick: (e) => {
|
|
85
|
+
e.preventDefault();
|
|
86
|
+
e.stopPropagation();
|
|
87
|
+
onBuy(token);
|
|
88
|
+
},
|
|
89
|
+
children: [
|
|
90
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Zap, { className: "h-3.5 w-3.5 shrink-0" }),
|
|
91
|
+
"Buy"
|
|
92
|
+
]
|
|
93
|
+
}
|
|
94
|
+
),
|
|
95
|
+
onCart && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
96
|
+
"button",
|
|
97
|
+
{
|
|
98
|
+
className: (0, import_cn.cn)(
|
|
99
|
+
BTN_OUTLINE,
|
|
100
|
+
"w-8 shrink-0",
|
|
101
|
+
inCart && "border-brand-orange/50 bg-brand-orange/10 text-brand-orange"
|
|
102
|
+
),
|
|
103
|
+
onClick: (e) => {
|
|
104
|
+
e.preventDefault();
|
|
105
|
+
e.stopPropagation();
|
|
106
|
+
onCart(token);
|
|
107
|
+
},
|
|
108
|
+
disabled: inCart,
|
|
109
|
+
"aria-label": inCart ? "In cart" : "Add to cart",
|
|
110
|
+
children: inCart ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Check, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.ShoppingCart, { className: "h-3.5 w-3.5" })
|
|
111
|
+
}
|
|
112
|
+
),
|
|
113
|
+
onOffer && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
114
|
+
"button",
|
|
115
|
+
{
|
|
116
|
+
className: (0, import_cn.cn)(BTN_OUTLINE, "w-8 shrink-0 text-brand-orange border-brand-orange/40 hover:bg-brand-orange/10"),
|
|
117
|
+
onClick: (e) => {
|
|
118
|
+
e.preventDefault();
|
|
119
|
+
e.stopPropagation();
|
|
120
|
+
onOffer(token);
|
|
121
|
+
},
|
|
122
|
+
"aria-label": "Make an offer",
|
|
123
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.HandCoins, { className: "h-3.5 w-3.5" })
|
|
124
|
+
}
|
|
125
|
+
)
|
|
126
|
+
] });
|
|
127
|
+
}
|
|
128
|
+
if (!isOwner) {
|
|
129
|
+
if (!onOffer) return null;
|
|
130
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
131
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_link.default, { href: assetHref, className: (0, import_cn.cn)(BTN_OUTLINE, "flex-1"), children: [
|
|
132
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.ArrowUpRight, { className: "h-3.5 w-3.5 shrink-0" }),
|
|
133
|
+
"View"
|
|
134
|
+
] }),
|
|
135
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
136
|
+
"button",
|
|
137
|
+
{
|
|
138
|
+
className: (0, import_cn.cn)(BTN_OUTLINE, "w-8 shrink-0 text-brand-orange border-brand-orange/40 hover:bg-brand-orange/10"),
|
|
139
|
+
onClick: (e) => {
|
|
140
|
+
e.preventDefault();
|
|
141
|
+
e.stopPropagation();
|
|
142
|
+
onOffer(token);
|
|
143
|
+
},
|
|
144
|
+
"aria-label": "Make an offer",
|
|
145
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.HandCoins, { className: "h-3.5 w-3.5" })
|
|
146
|
+
}
|
|
147
|
+
)
|
|
148
|
+
] });
|
|
149
|
+
}
|
|
150
|
+
if (isOwner && activeOrder) {
|
|
151
|
+
if (!onCancel) return null;
|
|
152
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
153
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
154
|
+
"button",
|
|
155
|
+
{
|
|
156
|
+
className: (0, import_cn.cn)(BTN_SOLID, "flex-1 bg-brand-rose"),
|
|
157
|
+
onClick: (e) => {
|
|
158
|
+
e.preventDefault();
|
|
159
|
+
e.stopPropagation();
|
|
160
|
+
onCancel(token);
|
|
161
|
+
},
|
|
162
|
+
children: [
|
|
163
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.X, { className: "h-3.5 w-3.5 shrink-0" }),
|
|
164
|
+
"Cancel listing"
|
|
165
|
+
]
|
|
166
|
+
}
|
|
167
|
+
),
|
|
168
|
+
onTransfer && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
169
|
+
"button",
|
|
170
|
+
{
|
|
171
|
+
className: (0, import_cn.cn)(BTN_OUTLINE, "w-8 shrink-0"),
|
|
172
|
+
onClick: (e) => {
|
|
173
|
+
e.preventDefault();
|
|
174
|
+
e.stopPropagation();
|
|
175
|
+
onTransfer(token);
|
|
176
|
+
},
|
|
177
|
+
"aria-label": "Transfer",
|
|
178
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.ArrowRightLeft, { className: "h-3.5 w-3.5" })
|
|
179
|
+
}
|
|
180
|
+
)
|
|
181
|
+
] });
|
|
182
|
+
}
|
|
183
|
+
if (isOwner && !activeOrder) {
|
|
184
|
+
if (!onList) return null;
|
|
185
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
186
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
187
|
+
"button",
|
|
188
|
+
{
|
|
189
|
+
className: (0, import_cn.cn)(BTN_SOLID, "flex-1 bg-brand-blue"),
|
|
190
|
+
onClick: (e) => {
|
|
191
|
+
e.preventDefault();
|
|
192
|
+
e.stopPropagation();
|
|
193
|
+
onList(token);
|
|
194
|
+
},
|
|
195
|
+
children: [
|
|
196
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Tag, { className: "h-3.5 w-3.5 shrink-0" }),
|
|
197
|
+
"List for sale"
|
|
198
|
+
]
|
|
199
|
+
}
|
|
200
|
+
),
|
|
201
|
+
onTransfer && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
202
|
+
"button",
|
|
203
|
+
{
|
|
204
|
+
className: (0, import_cn.cn)(BTN_OUTLINE, "w-8 shrink-0"),
|
|
205
|
+
onClick: (e) => {
|
|
206
|
+
e.preventDefault();
|
|
207
|
+
e.stopPropagation();
|
|
208
|
+
onTransfer(token);
|
|
209
|
+
},
|
|
210
|
+
"aria-label": "Transfer",
|
|
211
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.ArrowRightLeft, { className: "h-3.5 w-3.5" })
|
|
212
|
+
}
|
|
213
|
+
)
|
|
214
|
+
] });
|
|
215
|
+
}
|
|
216
|
+
return null;
|
|
217
|
+
};
|
|
218
|
+
const actionContent = renderActions();
|
|
219
|
+
const showActionBar = actionContent != null || !!overflowMenu;
|
|
220
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_motion_primitives.MotionCard, { className: (0, import_cn.cn)("card-base group relative overflow-hidden flex flex-col", className), children: [
|
|
221
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_link.default, { href: assetHref, className: "block relative shrink-0", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "relative aspect-square bg-muted overflow-hidden", children: [
|
|
222
|
+
!imgError ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
223
|
+
import_image.default,
|
|
224
|
+
{
|
|
225
|
+
src: image,
|
|
226
|
+
alt: name,
|
|
227
|
+
fill: true,
|
|
228
|
+
unoptimized: true,
|
|
229
|
+
sizes: "(max-width: 640px) 50vw, (max-width: 1024px) 33vw, 22vw",
|
|
230
|
+
className: "object-cover transition-transform duration-500 group-hover:scale-105",
|
|
231
|
+
onError: () => setImgError(true)
|
|
232
|
+
}
|
|
233
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "absolute inset-0 flex items-center justify-center bg-gradient-to-br from-brand-purple/15 to-brand-blue/15", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "text-2xl font-mono text-muted-foreground", children: [
|
|
234
|
+
"#",
|
|
235
|
+
token.tokenId
|
|
236
|
+
] }) }),
|
|
237
|
+
token.metadata?.ipType && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "absolute top-2 left-2", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ip_type_badge.IpTypeBadge, { ipType: token.metadata.ipType, size: "sm" }) }),
|
|
238
|
+
rarityTier && RARITY_STYLE[rarityTier] && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "absolute top-2 right-2 z-10", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: (0, import_cn.cn)(
|
|
239
|
+
"inline-flex items-center px-1.5 py-0.5 rounded-md backdrop-blur-sm text-[10px] font-bold leading-none",
|
|
240
|
+
RARITY_STYLE[rarityTier].className
|
|
241
|
+
), children: RARITY_STYLE[rarityTier].label }) }),
|
|
242
|
+
(token.metadataStatus === "PENDING" || token.metadataStatus === "FETCHING") && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "absolute bottom-0 inset-x-0 flex items-center justify-center gap-1.5 bg-black/50 backdrop-blur-sm py-1.5", children: [
|
|
243
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Loader2, { className: "h-3 w-3 animate-spin text-white/70" }),
|
|
244
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-[10px] text-white/70", children: "Indexing\u2026" })
|
|
245
|
+
] })
|
|
246
|
+
] }) }),
|
|
247
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "px-3 pt-2.5 pb-1 flex-1", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_link.default, { href: assetHref, className: "block space-y-0.5 mb-2", children: [
|
|
248
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-xl font-bold line-clamp-2 leading-tight", children: name }),
|
|
249
|
+
activeOrder && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { className: "flex items-center gap-1 text-[11px] font-semibold text-foreground/80", children: [
|
|
250
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_currency_icon.CurrencyIcon, { symbol: activeOrder.price.currency, size: 11 }),
|
|
251
|
+
(0, import_format.formatDisplayPrice)(activeOrder.price.formatted),
|
|
252
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "font-normal text-muted-foreground", children: activeOrder.price.currency })
|
|
253
|
+
] }),
|
|
254
|
+
token.metadata?.description ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-[10px] text-muted-foreground truncate leading-snug", children: token.metadata.description }) : token.metadata?.ipType ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-[10px] text-muted-foreground opacity-70", children: token.metadata.ipType }) : null
|
|
255
|
+
] }) }),
|
|
256
|
+
showActionBar && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-1.5 px-2 pb-2", children: [
|
|
257
|
+
actionContent,
|
|
258
|
+
overflowMenu
|
|
259
|
+
] })
|
|
260
|
+
] });
|
|
261
|
+
}
|
|
262
|
+
function TokenCardSkeleton() {
|
|
263
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "card-base overflow-hidden", children: [
|
|
264
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "aspect-square w-full animate-pulse bg-muted" }),
|
|
265
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "px-3 pt-2.5 pb-2 space-y-1.5", children: [
|
|
266
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "h-5 w-3/4 rounded-md animate-pulse bg-muted" }),
|
|
267
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "h-2.5 w-2/5 rounded-md animate-pulse bg-muted" })
|
|
268
|
+
] }),
|
|
269
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "px-2 pb-2 flex gap-1.5", children: [
|
|
270
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "h-8 flex-1 rounded-[11px] animate-pulse bg-muted" }),
|
|
271
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "h-8 w-8 rounded-[11px] animate-pulse bg-muted shrink-0" }),
|
|
272
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "h-8 w-8 rounded-[11px] animate-pulse bg-muted shrink-0" })
|
|
273
|
+
] })
|
|
274
|
+
] });
|
|
275
|
+
}
|
|
276
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
277
|
+
0 && (module.exports = {
|
|
278
|
+
TokenCard,
|
|
279
|
+
TokenCardSkeleton
|
|
280
|
+
});
|
|
281
|
+
//# sourceMappingURL=token-card.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/token-card.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState } from \"react\";\nimport Link from \"next/link\";\nimport Image from \"next/image\";\nimport {\n ShoppingCart, Tag, ArrowRightLeft, X, Loader2, HandCoins,\n Check, ArrowUpRight, Zap,\n} from \"lucide-react\";\nimport { cn } from \"../utils/cn.js\";\nimport { ipfsToHttp } from \"../utils/ipfs.js\";\nimport { formatDisplayPrice } from \"../utils/format.js\";\nimport { CurrencyIcon } from \"./currency-icon.js\";\nimport { IpTypeBadge } from \"./ip-type-badge.js\";\nimport { MotionCard } from \"./motion-primitives.js\";\nimport type { ApiToken } from \"@medialane/sdk\";\n\nexport type RarityTier = \"legendary\" | \"epic\" | \"rare\" | \"uncommon\" | \"common\";\n\nconst RARITY_STYLE: Record<RarityTier, { label: string; className: string } | null> = {\n legendary: { label: \"Legendary\", className: \"bg-yellow-400/90 text-yellow-900\" },\n epic: { label: \"Epic\", className: \"bg-purple-500/85 text-white\" },\n rare: { label: \"Rare\", className: \"bg-blue-500/85 text-white\" },\n uncommon: { label: \"Uncommon\", className: \"bg-emerald-500/85 text-white\" },\n common: null,\n};\n\nconst BTN_BASE = \"h-8 rounded-[11px] flex items-center justify-center gap-1.5 text-xs font-semibold transition-all active:scale-[0.98] shadow-none border-0\";\nconst BTN_SOLID = cn(BTN_BASE, \"text-white hover:brightness-110\");\nconst BTN_OUTLINE = cn(BTN_BASE, \"border border-border/60 text-foreground hover:bg-muted/60\");\n\nexport interface TokenCardProps {\n token: ApiToken;\n /** Whether the current user owns this token. Default: false */\n isOwner?: boolean;\n /** Whether this token's listing is already in the cart. Default: false */\n inCart?: boolean;\n /** Show the Buy button for listed tokens. Default: true */\n showBuyButton?: boolean;\n /** Optional rarity label shown as an overlay badge */\n rarityTier?: RarityTier;\n className?: string;\n /** Callbacks — omit any to hide that button */\n onBuy?: (token: ApiToken) => void;\n onCart?: (token: ApiToken) => void;\n onOffer?: (token: ApiToken) => void;\n onList?: (token: ApiToken) => void;\n onCancel?: (token: ApiToken) => void;\n onTransfer?: (token: ApiToken) => void;\n onRemix?: (token: ApiToken) => void;\n onReport?: (token: ApiToken) => void;\n /** Slot for a DropdownMenu trigger — rendered after primary buttons */\n overflowMenu?: React.ReactNode;\n}\n\nexport function TokenCard({\n token,\n isOwner = false,\n inCart = false,\n showBuyButton = true,\n rarityTier,\n className,\n onBuy,\n onCart,\n onOffer,\n onList,\n onCancel,\n onTransfer,\n overflowMenu,\n}: TokenCardProps) {\n const [imgError, setImgError] = useState(false);\n\n const name = token.metadata?.name || `Token #${token.tokenId}`;\n const image = ipfsToHttp(token.metadata?.image);\n const activeOrder = token.activeOrders?.[0];\n const assetHref = `/asset/${token.contractAddress}/${token.tokenId}`;\n\n const renderActions = () => {\n // Non-owner + listed + showBuyButton\n if (!isOwner && activeOrder && showBuyButton) {\n return (\n <>\n {onBuy && (\n <button\n className={cn(BTN_SOLID, \"flex-1 bg-brand-purple\")}\n onClick={(e) => { e.preventDefault(); e.stopPropagation(); onBuy(token); }}\n >\n <Zap className=\"h-3.5 w-3.5 shrink-0\" />\n Buy\n </button>\n )}\n {onCart && (\n <button\n className={cn(\n BTN_OUTLINE, \"w-8 shrink-0\",\n inCart && \"border-brand-orange/50 bg-brand-orange/10 text-brand-orange\"\n )}\n onClick={(e) => { e.preventDefault(); e.stopPropagation(); onCart(token); }}\n disabled={inCart}\n aria-label={inCart ? \"In cart\" : \"Add to cart\"}\n >\n {inCart ? <Check className=\"h-3.5 w-3.5\" /> : <ShoppingCart className=\"h-3.5 w-3.5\" />}\n </button>\n )}\n {onOffer && (\n <button\n className={cn(BTN_OUTLINE, \"w-8 shrink-0 text-brand-orange border-brand-orange/40 hover:bg-brand-orange/10\")}\n onClick={(e) => { e.preventDefault(); e.stopPropagation(); onOffer(token); }}\n aria-label=\"Make an offer\"\n >\n <HandCoins className=\"h-3.5 w-3.5\" />\n </button>\n )}\n </>\n );\n }\n\n // Non-owner + no listing (or showBuyButton=false)\n if (!isOwner) {\n if (!onOffer) return null;\n return (\n <>\n <Link href={assetHref} className={cn(BTN_OUTLINE, \"flex-1\")}>\n <ArrowUpRight className=\"h-3.5 w-3.5 shrink-0\" />\n View\n </Link>\n <button\n className={cn(BTN_OUTLINE, \"w-8 shrink-0 text-brand-orange border-brand-orange/40 hover:bg-brand-orange/10\")}\n onClick={(e) => { e.preventDefault(); e.stopPropagation(); onOffer(token); }}\n aria-label=\"Make an offer\"\n >\n <HandCoins className=\"h-3.5 w-3.5\" />\n </button>\n </>\n );\n }\n\n // Owner + listed\n if (isOwner && activeOrder) {\n if (!onCancel) return null;\n return (\n <>\n <button\n className={cn(BTN_SOLID, \"flex-1 bg-brand-rose\")}\n onClick={(e) => { e.preventDefault(); e.stopPropagation(); onCancel(token); }}\n >\n <X className=\"h-3.5 w-3.5 shrink-0\" />\n Cancel listing\n </button>\n {onTransfer && (\n <button\n className={cn(BTN_OUTLINE, \"w-8 shrink-0\")}\n onClick={(e) => { e.preventDefault(); e.stopPropagation(); onTransfer(token); }}\n aria-label=\"Transfer\"\n >\n <ArrowRightLeft className=\"h-3.5 w-3.5\" />\n </button>\n )}\n </>\n );\n }\n\n // Owner + unlisted\n if (isOwner && !activeOrder) {\n if (!onList) return null;\n return (\n <>\n <button\n className={cn(BTN_SOLID, \"flex-1 bg-brand-blue\")}\n onClick={(e) => { e.preventDefault(); e.stopPropagation(); onList(token); }}\n >\n <Tag className=\"h-3.5 w-3.5 shrink-0\" />\n List for sale\n </button>\n {onTransfer && (\n <button\n className={cn(BTN_OUTLINE, \"w-8 shrink-0\")}\n onClick={(e) => { e.preventDefault(); e.stopPropagation(); onTransfer(token); }}\n aria-label=\"Transfer\"\n >\n <ArrowRightLeft className=\"h-3.5 w-3.5\" />\n </button>\n )}\n </>\n );\n }\n\n return null;\n };\n\n const actionContent = renderActions();\n const showActionBar = actionContent != null || !!overflowMenu;\n\n return (\n <MotionCard className={cn(\"card-base group relative overflow-hidden flex flex-col\", className)}>\n <Link href={assetHref} className=\"block relative shrink-0\">\n <div className=\"relative aspect-square bg-muted overflow-hidden\">\n {!imgError ? (\n <Image\n src={image}\n alt={name}\n fill\n unoptimized\n sizes=\"(max-width: 640px) 50vw, (max-width: 1024px) 33vw, 22vw\"\n className=\"object-cover transition-transform duration-500 group-hover:scale-105\"\n onError={() => setImgError(true)}\n />\n ) : (\n <div className=\"absolute inset-0 flex items-center justify-center bg-gradient-to-br from-brand-purple/15 to-brand-blue/15\">\n <span className=\"text-2xl font-mono text-muted-foreground\">#{token.tokenId}</span>\n </div>\n )}\n\n {token.metadata?.ipType && (\n <div className=\"absolute top-2 left-2\">\n <IpTypeBadge ipType={token.metadata.ipType as any} size=\"sm\" />\n </div>\n )}\n\n {rarityTier && RARITY_STYLE[rarityTier] && (\n <div className=\"absolute top-2 right-2 z-10\">\n <span className={cn(\n \"inline-flex items-center px-1.5 py-0.5 rounded-md backdrop-blur-sm text-[10px] font-bold leading-none\",\n RARITY_STYLE[rarityTier]!.className\n )}>\n {RARITY_STYLE[rarityTier]!.label}\n </span>\n </div>\n )}\n\n {(token.metadataStatus === \"PENDING\" || token.metadataStatus === \"FETCHING\") && (\n <div className=\"absolute bottom-0 inset-x-0 flex items-center justify-center gap-1.5 bg-black/50 backdrop-blur-sm py-1.5\">\n <Loader2 className=\"h-3 w-3 animate-spin text-white/70\" />\n <span className=\"text-[10px] text-white/70\">Indexing…</span>\n </div>\n )}\n </div>\n </Link>\n\n <div className=\"px-3 pt-2.5 pb-1 flex-1\">\n <Link href={assetHref} className=\"block space-y-0.5 mb-2\">\n <p className=\"text-xl font-bold line-clamp-2 leading-tight\">{name}</p>\n {activeOrder && (\n <p className=\"flex items-center gap-1 text-[11px] font-semibold text-foreground/80\">\n <CurrencyIcon symbol={activeOrder.price.currency} size={11} />\n {formatDisplayPrice(activeOrder.price.formatted)}\n <span className=\"font-normal text-muted-foreground\">{activeOrder.price.currency}</span>\n </p>\n )}\n {token.metadata?.description ? (\n <p className=\"text-[10px] text-muted-foreground truncate leading-snug\">\n {token.metadata.description}\n </p>\n ) : token.metadata?.ipType ? (\n <p className=\"text-[10px] text-muted-foreground opacity-70\">{token.metadata.ipType}</p>\n ) : null}\n </Link>\n </div>\n\n {showActionBar && (\n <div className=\"flex items-center gap-1.5 px-2 pb-2\">\n {actionContent}\n {overflowMenu}\n </div>\n )}\n </MotionCard>\n );\n}\n\nexport function TokenCardSkeleton() {\n return (\n <div className=\"card-base overflow-hidden\">\n <div className=\"aspect-square w-full animate-pulse bg-muted\" />\n <div className=\"px-3 pt-2.5 pb-2 space-y-1.5\">\n <div className=\"h-5 w-3/4 rounded-md animate-pulse bg-muted\" />\n <div className=\"h-2.5 w-2/5 rounded-md animate-pulse bg-muted\" />\n </div>\n <div className=\"px-2 pb-2 flex gap-1.5\">\n <div className=\"h-8 flex-1 rounded-[11px] animate-pulse bg-muted\" />\n <div className=\"h-8 w-8 rounded-[11px] animate-pulse bg-muted shrink-0\" />\n <div className=\"h-8 w-8 rounded-[11px] animate-pulse bg-muted shrink-0\" />\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":["Link","Image"]}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { ApiToken } from '@medialane/sdk';
|
|
3
|
+
|
|
4
|
+
type RarityTier = "legendary" | "epic" | "rare" | "uncommon" | "common";
|
|
5
|
+
interface TokenCardProps {
|
|
6
|
+
token: ApiToken;
|
|
7
|
+
/** Whether the current user owns this token. Default: false */
|
|
8
|
+
isOwner?: boolean;
|
|
9
|
+
/** Whether this token's listing is already in the cart. Default: false */
|
|
10
|
+
inCart?: boolean;
|
|
11
|
+
/** Show the Buy button for listed tokens. Default: true */
|
|
12
|
+
showBuyButton?: boolean;
|
|
13
|
+
/** Optional rarity label shown as an overlay badge */
|
|
14
|
+
rarityTier?: RarityTier;
|
|
15
|
+
className?: string;
|
|
16
|
+
/** Callbacks — omit any to hide that button */
|
|
17
|
+
onBuy?: (token: ApiToken) => void;
|
|
18
|
+
onCart?: (token: ApiToken) => void;
|
|
19
|
+
onOffer?: (token: ApiToken) => void;
|
|
20
|
+
onList?: (token: ApiToken) => void;
|
|
21
|
+
onCancel?: (token: ApiToken) => void;
|
|
22
|
+
onTransfer?: (token: ApiToken) => void;
|
|
23
|
+
onRemix?: (token: ApiToken) => void;
|
|
24
|
+
onReport?: (token: ApiToken) => void;
|
|
25
|
+
/** Slot for a DropdownMenu trigger — rendered after primary buttons */
|
|
26
|
+
overflowMenu?: React.ReactNode;
|
|
27
|
+
}
|
|
28
|
+
declare function TokenCard({ token, isOwner, inCart, showBuyButton, rarityTier, className, onBuy, onCart, onOffer, onList, onCancel, onTransfer, overflowMenu, }: TokenCardProps): react_jsx_runtime.JSX.Element;
|
|
29
|
+
declare function TokenCardSkeleton(): react_jsx_runtime.JSX.Element;
|
|
30
|
+
|
|
31
|
+
export { type RarityTier, TokenCard, type TokenCardProps, TokenCardSkeleton };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { ApiToken } from '@medialane/sdk';
|
|
3
|
+
|
|
4
|
+
type RarityTier = "legendary" | "epic" | "rare" | "uncommon" | "common";
|
|
5
|
+
interface TokenCardProps {
|
|
6
|
+
token: ApiToken;
|
|
7
|
+
/** Whether the current user owns this token. Default: false */
|
|
8
|
+
isOwner?: boolean;
|
|
9
|
+
/** Whether this token's listing is already in the cart. Default: false */
|
|
10
|
+
inCart?: boolean;
|
|
11
|
+
/** Show the Buy button for listed tokens. Default: true */
|
|
12
|
+
showBuyButton?: boolean;
|
|
13
|
+
/** Optional rarity label shown as an overlay badge */
|
|
14
|
+
rarityTier?: RarityTier;
|
|
15
|
+
className?: string;
|
|
16
|
+
/** Callbacks — omit any to hide that button */
|
|
17
|
+
onBuy?: (token: ApiToken) => void;
|
|
18
|
+
onCart?: (token: ApiToken) => void;
|
|
19
|
+
onOffer?: (token: ApiToken) => void;
|
|
20
|
+
onList?: (token: ApiToken) => void;
|
|
21
|
+
onCancel?: (token: ApiToken) => void;
|
|
22
|
+
onTransfer?: (token: ApiToken) => void;
|
|
23
|
+
onRemix?: (token: ApiToken) => void;
|
|
24
|
+
onReport?: (token: ApiToken) => void;
|
|
25
|
+
/** Slot for a DropdownMenu trigger — rendered after primary buttons */
|
|
26
|
+
overflowMenu?: React.ReactNode;
|
|
27
|
+
}
|
|
28
|
+
declare function TokenCard({ token, isOwner, inCart, showBuyButton, rarityTier, className, onBuy, onCart, onOffer, onList, onCancel, onTransfer, overflowMenu, }: TokenCardProps): react_jsx_runtime.JSX.Element;
|
|
29
|
+
declare function TokenCardSkeleton(): react_jsx_runtime.JSX.Element;
|
|
30
|
+
|
|
31
|
+
export { type RarityTier, TokenCard, type TokenCardProps, TokenCardSkeleton };
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import Link from "next/link";
|
|
5
|
+
import Image from "next/image";
|
|
6
|
+
import {
|
|
7
|
+
ShoppingCart,
|
|
8
|
+
Tag,
|
|
9
|
+
ArrowRightLeft,
|
|
10
|
+
X,
|
|
11
|
+
Loader2,
|
|
12
|
+
HandCoins,
|
|
13
|
+
Check,
|
|
14
|
+
ArrowUpRight,
|
|
15
|
+
Zap
|
|
16
|
+
} from "lucide-react";
|
|
17
|
+
import { cn } from "../utils/cn.js";
|
|
18
|
+
import { ipfsToHttp } from "../utils/ipfs.js";
|
|
19
|
+
import { formatDisplayPrice } from "../utils/format.js";
|
|
20
|
+
import { CurrencyIcon } from "./currency-icon.js";
|
|
21
|
+
import { IpTypeBadge } from "./ip-type-badge.js";
|
|
22
|
+
import { MotionCard } from "./motion-primitives.js";
|
|
23
|
+
const RARITY_STYLE = {
|
|
24
|
+
legendary: { label: "Legendary", className: "bg-yellow-400/90 text-yellow-900" },
|
|
25
|
+
epic: { label: "Epic", className: "bg-purple-500/85 text-white" },
|
|
26
|
+
rare: { label: "Rare", className: "bg-blue-500/85 text-white" },
|
|
27
|
+
uncommon: { label: "Uncommon", className: "bg-emerald-500/85 text-white" },
|
|
28
|
+
common: null
|
|
29
|
+
};
|
|
30
|
+
const BTN_BASE = "h-8 rounded-[11px] flex items-center justify-center gap-1.5 text-xs font-semibold transition-all active:scale-[0.98] shadow-none border-0";
|
|
31
|
+
const BTN_SOLID = cn(BTN_BASE, "text-white hover:brightness-110");
|
|
32
|
+
const BTN_OUTLINE = cn(BTN_BASE, "border border-border/60 text-foreground hover:bg-muted/60");
|
|
33
|
+
function TokenCard({
|
|
34
|
+
token,
|
|
35
|
+
isOwner = false,
|
|
36
|
+
inCart = false,
|
|
37
|
+
showBuyButton = true,
|
|
38
|
+
rarityTier,
|
|
39
|
+
className,
|
|
40
|
+
onBuy,
|
|
41
|
+
onCart,
|
|
42
|
+
onOffer,
|
|
43
|
+
onList,
|
|
44
|
+
onCancel,
|
|
45
|
+
onTransfer,
|
|
46
|
+
overflowMenu
|
|
47
|
+
}) {
|
|
48
|
+
const [imgError, setImgError] = useState(false);
|
|
49
|
+
const name = token.metadata?.name || `Token #${token.tokenId}`;
|
|
50
|
+
const image = ipfsToHttp(token.metadata?.image);
|
|
51
|
+
const activeOrder = token.activeOrders?.[0];
|
|
52
|
+
const assetHref = `/asset/${token.contractAddress}/${token.tokenId}`;
|
|
53
|
+
const renderActions = () => {
|
|
54
|
+
if (!isOwner && activeOrder && showBuyButton) {
|
|
55
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
56
|
+
onBuy && /* @__PURE__ */ jsxs(
|
|
57
|
+
"button",
|
|
58
|
+
{
|
|
59
|
+
className: cn(BTN_SOLID, "flex-1 bg-brand-purple"),
|
|
60
|
+
onClick: (e) => {
|
|
61
|
+
e.preventDefault();
|
|
62
|
+
e.stopPropagation();
|
|
63
|
+
onBuy(token);
|
|
64
|
+
},
|
|
65
|
+
children: [
|
|
66
|
+
/* @__PURE__ */ jsx(Zap, { className: "h-3.5 w-3.5 shrink-0" }),
|
|
67
|
+
"Buy"
|
|
68
|
+
]
|
|
69
|
+
}
|
|
70
|
+
),
|
|
71
|
+
onCart && /* @__PURE__ */ jsx(
|
|
72
|
+
"button",
|
|
73
|
+
{
|
|
74
|
+
className: cn(
|
|
75
|
+
BTN_OUTLINE,
|
|
76
|
+
"w-8 shrink-0",
|
|
77
|
+
inCart && "border-brand-orange/50 bg-brand-orange/10 text-brand-orange"
|
|
78
|
+
),
|
|
79
|
+
onClick: (e) => {
|
|
80
|
+
e.preventDefault();
|
|
81
|
+
e.stopPropagation();
|
|
82
|
+
onCart(token);
|
|
83
|
+
},
|
|
84
|
+
disabled: inCart,
|
|
85
|
+
"aria-label": inCart ? "In cart" : "Add to cart",
|
|
86
|
+
children: inCart ? /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(ShoppingCart, { className: "h-3.5 w-3.5" })
|
|
87
|
+
}
|
|
88
|
+
),
|
|
89
|
+
onOffer && /* @__PURE__ */ jsx(
|
|
90
|
+
"button",
|
|
91
|
+
{
|
|
92
|
+
className: cn(BTN_OUTLINE, "w-8 shrink-0 text-brand-orange border-brand-orange/40 hover:bg-brand-orange/10"),
|
|
93
|
+
onClick: (e) => {
|
|
94
|
+
e.preventDefault();
|
|
95
|
+
e.stopPropagation();
|
|
96
|
+
onOffer(token);
|
|
97
|
+
},
|
|
98
|
+
"aria-label": "Make an offer",
|
|
99
|
+
children: /* @__PURE__ */ jsx(HandCoins, { className: "h-3.5 w-3.5" })
|
|
100
|
+
}
|
|
101
|
+
)
|
|
102
|
+
] });
|
|
103
|
+
}
|
|
104
|
+
if (!isOwner) {
|
|
105
|
+
if (!onOffer) return null;
|
|
106
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
107
|
+
/* @__PURE__ */ jsxs(Link, { href: assetHref, className: cn(BTN_OUTLINE, "flex-1"), children: [
|
|
108
|
+
/* @__PURE__ */ jsx(ArrowUpRight, { className: "h-3.5 w-3.5 shrink-0" }),
|
|
109
|
+
"View"
|
|
110
|
+
] }),
|
|
111
|
+
/* @__PURE__ */ jsx(
|
|
112
|
+
"button",
|
|
113
|
+
{
|
|
114
|
+
className: cn(BTN_OUTLINE, "w-8 shrink-0 text-brand-orange border-brand-orange/40 hover:bg-brand-orange/10"),
|
|
115
|
+
onClick: (e) => {
|
|
116
|
+
e.preventDefault();
|
|
117
|
+
e.stopPropagation();
|
|
118
|
+
onOffer(token);
|
|
119
|
+
},
|
|
120
|
+
"aria-label": "Make an offer",
|
|
121
|
+
children: /* @__PURE__ */ jsx(HandCoins, { className: "h-3.5 w-3.5" })
|
|
122
|
+
}
|
|
123
|
+
)
|
|
124
|
+
] });
|
|
125
|
+
}
|
|
126
|
+
if (isOwner && activeOrder) {
|
|
127
|
+
if (!onCancel) return null;
|
|
128
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
129
|
+
/* @__PURE__ */ jsxs(
|
|
130
|
+
"button",
|
|
131
|
+
{
|
|
132
|
+
className: cn(BTN_SOLID, "flex-1 bg-brand-rose"),
|
|
133
|
+
onClick: (e) => {
|
|
134
|
+
e.preventDefault();
|
|
135
|
+
e.stopPropagation();
|
|
136
|
+
onCancel(token);
|
|
137
|
+
},
|
|
138
|
+
children: [
|
|
139
|
+
/* @__PURE__ */ jsx(X, { className: "h-3.5 w-3.5 shrink-0" }),
|
|
140
|
+
"Cancel listing"
|
|
141
|
+
]
|
|
142
|
+
}
|
|
143
|
+
),
|
|
144
|
+
onTransfer && /* @__PURE__ */ jsx(
|
|
145
|
+
"button",
|
|
146
|
+
{
|
|
147
|
+
className: cn(BTN_OUTLINE, "w-8 shrink-0"),
|
|
148
|
+
onClick: (e) => {
|
|
149
|
+
e.preventDefault();
|
|
150
|
+
e.stopPropagation();
|
|
151
|
+
onTransfer(token);
|
|
152
|
+
},
|
|
153
|
+
"aria-label": "Transfer",
|
|
154
|
+
children: /* @__PURE__ */ jsx(ArrowRightLeft, { className: "h-3.5 w-3.5" })
|
|
155
|
+
}
|
|
156
|
+
)
|
|
157
|
+
] });
|
|
158
|
+
}
|
|
159
|
+
if (isOwner && !activeOrder) {
|
|
160
|
+
if (!onList) return null;
|
|
161
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
162
|
+
/* @__PURE__ */ jsxs(
|
|
163
|
+
"button",
|
|
164
|
+
{
|
|
165
|
+
className: cn(BTN_SOLID, "flex-1 bg-brand-blue"),
|
|
166
|
+
onClick: (e) => {
|
|
167
|
+
e.preventDefault();
|
|
168
|
+
e.stopPropagation();
|
|
169
|
+
onList(token);
|
|
170
|
+
},
|
|
171
|
+
children: [
|
|
172
|
+
/* @__PURE__ */ jsx(Tag, { className: "h-3.5 w-3.5 shrink-0" }),
|
|
173
|
+
"List for sale"
|
|
174
|
+
]
|
|
175
|
+
}
|
|
176
|
+
),
|
|
177
|
+
onTransfer && /* @__PURE__ */ jsx(
|
|
178
|
+
"button",
|
|
179
|
+
{
|
|
180
|
+
className: cn(BTN_OUTLINE, "w-8 shrink-0"),
|
|
181
|
+
onClick: (e) => {
|
|
182
|
+
e.preventDefault();
|
|
183
|
+
e.stopPropagation();
|
|
184
|
+
onTransfer(token);
|
|
185
|
+
},
|
|
186
|
+
"aria-label": "Transfer",
|
|
187
|
+
children: /* @__PURE__ */ jsx(ArrowRightLeft, { className: "h-3.5 w-3.5" })
|
|
188
|
+
}
|
|
189
|
+
)
|
|
190
|
+
] });
|
|
191
|
+
}
|
|
192
|
+
return null;
|
|
193
|
+
};
|
|
194
|
+
const actionContent = renderActions();
|
|
195
|
+
const showActionBar = actionContent != null || !!overflowMenu;
|
|
196
|
+
return /* @__PURE__ */ jsxs(MotionCard, { className: cn("card-base group relative overflow-hidden flex flex-col", className), children: [
|
|
197
|
+
/* @__PURE__ */ jsx(Link, { href: assetHref, className: "block relative shrink-0", children: /* @__PURE__ */ jsxs("div", { className: "relative aspect-square bg-muted overflow-hidden", children: [
|
|
198
|
+
!imgError ? /* @__PURE__ */ jsx(
|
|
199
|
+
Image,
|
|
200
|
+
{
|
|
201
|
+
src: image,
|
|
202
|
+
alt: name,
|
|
203
|
+
fill: true,
|
|
204
|
+
unoptimized: true,
|
|
205
|
+
sizes: "(max-width: 640px) 50vw, (max-width: 1024px) 33vw, 22vw",
|
|
206
|
+
className: "object-cover transition-transform duration-500 group-hover:scale-105",
|
|
207
|
+
onError: () => setImgError(true)
|
|
208
|
+
}
|
|
209
|
+
) : /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center bg-gradient-to-br from-brand-purple/15 to-brand-blue/15", children: /* @__PURE__ */ jsxs("span", { className: "text-2xl font-mono text-muted-foreground", children: [
|
|
210
|
+
"#",
|
|
211
|
+
token.tokenId
|
|
212
|
+
] }) }),
|
|
213
|
+
token.metadata?.ipType && /* @__PURE__ */ jsx("div", { className: "absolute top-2 left-2", children: /* @__PURE__ */ jsx(IpTypeBadge, { ipType: token.metadata.ipType, size: "sm" }) }),
|
|
214
|
+
rarityTier && RARITY_STYLE[rarityTier] && /* @__PURE__ */ jsx("div", { className: "absolute top-2 right-2 z-10", children: /* @__PURE__ */ jsx("span", { className: cn(
|
|
215
|
+
"inline-flex items-center px-1.5 py-0.5 rounded-md backdrop-blur-sm text-[10px] font-bold leading-none",
|
|
216
|
+
RARITY_STYLE[rarityTier].className
|
|
217
|
+
), children: RARITY_STYLE[rarityTier].label }) }),
|
|
218
|
+
(token.metadataStatus === "PENDING" || token.metadataStatus === "FETCHING") && /* @__PURE__ */ jsxs("div", { className: "absolute bottom-0 inset-x-0 flex items-center justify-center gap-1.5 bg-black/50 backdrop-blur-sm py-1.5", children: [
|
|
219
|
+
/* @__PURE__ */ jsx(Loader2, { className: "h-3 w-3 animate-spin text-white/70" }),
|
|
220
|
+
/* @__PURE__ */ jsx("span", { className: "text-[10px] text-white/70", children: "Indexing\u2026" })
|
|
221
|
+
] })
|
|
222
|
+
] }) }),
|
|
223
|
+
/* @__PURE__ */ jsx("div", { className: "px-3 pt-2.5 pb-1 flex-1", children: /* @__PURE__ */ jsxs(Link, { href: assetHref, className: "block space-y-0.5 mb-2", children: [
|
|
224
|
+
/* @__PURE__ */ jsx("p", { className: "text-xl font-bold line-clamp-2 leading-tight", children: name }),
|
|
225
|
+
activeOrder && /* @__PURE__ */ jsxs("p", { className: "flex items-center gap-1 text-[11px] font-semibold text-foreground/80", children: [
|
|
226
|
+
/* @__PURE__ */ jsx(CurrencyIcon, { symbol: activeOrder.price.currency, size: 11 }),
|
|
227
|
+
formatDisplayPrice(activeOrder.price.formatted),
|
|
228
|
+
/* @__PURE__ */ jsx("span", { className: "font-normal text-muted-foreground", children: activeOrder.price.currency })
|
|
229
|
+
] }),
|
|
230
|
+
token.metadata?.description ? /* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground truncate leading-snug", children: token.metadata.description }) : token.metadata?.ipType ? /* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground opacity-70", children: token.metadata.ipType }) : null
|
|
231
|
+
] }) }),
|
|
232
|
+
showActionBar && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 px-2 pb-2", children: [
|
|
233
|
+
actionContent,
|
|
234
|
+
overflowMenu
|
|
235
|
+
] })
|
|
236
|
+
] });
|
|
237
|
+
}
|
|
238
|
+
function TokenCardSkeleton() {
|
|
239
|
+
return /* @__PURE__ */ jsxs("div", { className: "card-base overflow-hidden", children: [
|
|
240
|
+
/* @__PURE__ */ jsx("div", { className: "aspect-square w-full animate-pulse bg-muted" }),
|
|
241
|
+
/* @__PURE__ */ jsxs("div", { className: "px-3 pt-2.5 pb-2 space-y-1.5", children: [
|
|
242
|
+
/* @__PURE__ */ jsx("div", { className: "h-5 w-3/4 rounded-md animate-pulse bg-muted" }),
|
|
243
|
+
/* @__PURE__ */ jsx("div", { className: "h-2.5 w-2/5 rounded-md animate-pulse bg-muted" })
|
|
244
|
+
] }),
|
|
245
|
+
/* @__PURE__ */ jsxs("div", { className: "px-2 pb-2 flex gap-1.5", children: [
|
|
246
|
+
/* @__PURE__ */ jsx("div", { className: "h-8 flex-1 rounded-[11px] animate-pulse bg-muted" }),
|
|
247
|
+
/* @__PURE__ */ jsx("div", { className: "h-8 w-8 rounded-[11px] animate-pulse bg-muted shrink-0" }),
|
|
248
|
+
/* @__PURE__ */ jsx("div", { className: "h-8 w-8 rounded-[11px] animate-pulse bg-muted shrink-0" })
|
|
249
|
+
] })
|
|
250
|
+
] });
|
|
251
|
+
}
|
|
252
|
+
export {
|
|
253
|
+
TokenCard,
|
|
254
|
+
TokenCardSkeleton
|
|
255
|
+
};
|
|
256
|
+
//# sourceMappingURL=token-card.js.map
|