@medialane/ui 0.14.0 → 0.16.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.
@@ -0,0 +1,93 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useState, useMemo } from "react";
4
+ import { Coins, LayoutGrid, List, Search } from "lucide-react";
5
+ import { cn } from "../utils/cn.js";
6
+ import { CoinCard, CoinRow, CoinCardSkeleton } from "./coin-card.js";
7
+ const FILTER_TABS = [
8
+ { label: "All", value: "all" },
9
+ { label: "Creator Coins", value: "creator" },
10
+ { label: "Memecoins", value: "memecoin" }
11
+ ];
12
+ const SORT_OPTIONS = [
13
+ { label: "Recently launched", value: "recent" },
14
+ { label: "Name", value: "name" }
15
+ ];
16
+ function CoinsExplorer({ useCoins, usePrice, coinHref, heading = true }) {
17
+ const [filter, setFilter] = useState("all");
18
+ const [sort, setSort] = useState("recent");
19
+ const [view, setView] = useState("grid");
20
+ const [query, setQuery] = useState("");
21
+ const { collections, isLoading } = useCoins({ filter, sort });
22
+ const items = useMemo(() => {
23
+ const q = query.trim().toLowerCase();
24
+ if (!q) return collections;
25
+ return collections.filter(
26
+ (c) => (c.name ?? "").toLowerCase().includes(q) || (c.symbol ?? "").toLowerCase().includes(q)
27
+ );
28
+ }, [collections, query]);
29
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
30
+ heading && /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
31
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-primary", children: [
32
+ /* @__PURE__ */ jsx(Coins, { className: "h-5 w-5" }),
33
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold uppercase tracking-wider", children: "Tokens" })
34
+ ] }),
35
+ /* @__PURE__ */ jsx("h1", { className: "text-3xl font-bold", children: "Creator coins & memecoins" }),
36
+ /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: "Discover creator-issued social tokens and claimed memecoins." })
37
+ ] }),
38
+ /* @__PURE__ */ jsxs("div", { className: "space-y-3 border-b border-border/60 pb-3", children: [
39
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [
40
+ /* @__PURE__ */ jsx("div", { className: "flex gap-1.5", children: FILTER_TABS.map(({ label, value }) => /* @__PURE__ */ jsx(
41
+ "button",
42
+ {
43
+ onClick: () => setFilter(value),
44
+ className: cn(
45
+ "rounded-lg border px-3 py-1.5 text-xs font-medium transition-colors",
46
+ filter === value ? "border-primary bg-primary/10 text-primary" : "border-border text-muted-foreground hover:border-primary/50 hover:text-foreground"
47
+ ),
48
+ children: label
49
+ },
50
+ value
51
+ )) }),
52
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
53
+ /* @__PURE__ */ jsx(
54
+ "select",
55
+ {
56
+ value: sort,
57
+ onChange: (e) => setSort(e.target.value),
58
+ className: "rounded-lg border border-border bg-background px-3 py-1.5 text-xs font-medium text-foreground",
59
+ children: SORT_OPTIONS.map((o) => /* @__PURE__ */ jsx("option", { value: o.value, children: o.label }, o.value))
60
+ }
61
+ ),
62
+ /* @__PURE__ */ jsx("div", { className: "inline-flex rounded-lg border border-border p-0.5", children: [{ v: "grid", Icon: LayoutGrid }, { v: "table", Icon: List }].map(({ v, Icon }) => /* @__PURE__ */ jsx(
63
+ "button",
64
+ {
65
+ onClick: () => setView(v),
66
+ "aria-label": v === "grid" ? "Grid view" : "Table view",
67
+ className: cn("rounded-md p-1.5 transition-colors", view === v ? "bg-primary/10 text-primary" : "text-muted-foreground hover:text-foreground"),
68
+ children: /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4" })
69
+ },
70
+ v
71
+ )) })
72
+ ] })
73
+ ] }),
74
+ /* @__PURE__ */ jsxs("div", { className: "relative", children: [
75
+ /* @__PURE__ */ jsx(Search, { className: "pointer-events-none absolute left-3 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground" }),
76
+ /* @__PURE__ */ jsx(
77
+ "input",
78
+ {
79
+ value: query,
80
+ onChange: (e) => setQuery(e.target.value),
81
+ placeholder: "Search coins by name or symbol\u2026",
82
+ className: "w-full rounded-lg border border-border bg-background py-2 pl-9 pr-3 text-sm outline-none focus:border-primary/50"
83
+ }
84
+ )
85
+ ] })
86
+ ] }),
87
+ isLoading && items.length === 0 ? /* @__PURE__ */ jsx("div", { className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4", children: Array.from({ length: 8 }).map((_, i) => /* @__PURE__ */ jsx(CoinCardSkeleton, {}, i)) }) : items.length === 0 ? /* @__PURE__ */ jsx("div", { className: "rounded-xl border border-border/60 py-16 text-center text-muted-foreground", children: query.trim() ? `No coins match "${query.trim()}".` : "No coins yet." }) : view === "table" ? /* @__PURE__ */ jsx("div", { className: "space-y-2", children: items.map((c) => /* @__PURE__ */ jsx(CoinRow, { collection: c, usePrice, href: coinHref(c) }, `${c.chain}-${c.contractAddress}`)) }) : /* @__PURE__ */ jsx("div", { className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4", children: items.map((c) => /* @__PURE__ */ jsx(CoinCard, { collection: c, usePrice, href: coinHref(c) }, `${c.chain}-${c.contractAddress}`)) })
88
+ ] });
89
+ }
90
+ export {
91
+ CoinsExplorer
92
+ };
93
+ //# sourceMappingURL=coins-explorer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/coins-explorer.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * CoinsExplorer — the shared coin-discovery surface (chain-agnostic).\n *\n * Owns view/search/filter/sort UI only. The data source (`useCoins`), the price\n * read (`usePrice`), and the link target (`coinHref`) are injected, so each app\n * (and each chain) wires its own without forking this component.\n */\n\nimport { useState, useMemo } from \"react\";\nimport { Coins, LayoutGrid, List, Search } from \"lucide-react\";\nimport { cn } from \"../utils/cn.js\";\nimport { CoinCard, CoinRow, CoinCardSkeleton, type UseCoinPrice } from \"./coin-card.js\";\nimport type { CoinCollectionLike } from \"../data/coins.js\";\n\nexport type CoinFilter = \"all\" | \"creator\" | \"memecoin\";\nexport type CoinSort = \"recent\" | \"name\";\nexport type UseCoins = (opts: { filter: CoinFilter; sort: CoinSort }) => {\n collections: CoinCollectionLike[];\n isLoading: boolean;\n};\n\nexport interface CoinsExplorerProps {\n useCoins: UseCoins;\n usePrice: UseCoinPrice;\n /** Build the link target for a coin (internal or per-chain trading app). */\n coinHref: (collection: CoinCollectionLike) => string;\n heading?: boolean;\n}\n\nconst FILTER_TABS: { label: string; value: CoinFilter }[] = [\n { label: \"All\", value: \"all\" },\n { label: \"Creator Coins\", value: \"creator\" },\n { label: \"Memecoins\", value: \"memecoin\" },\n];\n\n// Recency default — never raw swap volume (05 §11 anti-wash hygiene).\nconst SORT_OPTIONS: { label: string; value: CoinSort }[] = [\n { label: \"Recently launched\", value: \"recent\" },\n { label: \"Name\", value: \"name\" },\n];\n\nexport function CoinsExplorer({ useCoins, usePrice, coinHref, heading = true }: CoinsExplorerProps) {\n const [filter, setFilter] = useState<CoinFilter>(\"all\");\n const [sort, setSort] = useState<CoinSort>(\"recent\");\n const [view, setView] = useState<\"grid\" | \"table\">(\"grid\");\n const [query, setQuery] = useState(\"\");\n\n const { collections, isLoading } = useCoins({ filter, sort });\n const items = useMemo(() => {\n const q = query.trim().toLowerCase();\n if (!q) return collections;\n return collections.filter(\n (c) => (c.name ?? \"\").toLowerCase().includes(q) || (c.symbol ?? \"\").toLowerCase().includes(q)\n );\n }, [collections, query]);\n\n return (\n <div className=\"space-y-6\">\n {heading && (\n <div className=\"space-y-2\">\n <div className=\"flex items-center gap-2 text-primary\">\n <Coins className=\"h-5 w-5\" />\n <span className=\"text-sm font-semibold uppercase tracking-wider\">Tokens</span>\n </div>\n <h1 className=\"text-3xl font-bold\">Creator coins &amp; memecoins</h1>\n <p className=\"text-muted-foreground\">Discover creator-issued social tokens and claimed memecoins.</p>\n </div>\n )}\n\n <div className=\"space-y-3 border-b border-border/60 pb-3\">\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n <div className=\"flex gap-1.5\">\n {FILTER_TABS.map(({ label, value }) => (\n <button\n key={value}\n onClick={() => setFilter(value)}\n className={cn(\n \"rounded-lg border px-3 py-1.5 text-xs font-medium transition-colors\",\n filter === value\n ? \"border-primary bg-primary/10 text-primary\"\n : \"border-border text-muted-foreground hover:border-primary/50 hover:text-foreground\"\n )}\n >\n {label}\n </button>\n ))}\n </div>\n <div className=\"flex items-center gap-2\">\n <select\n value={sort}\n onChange={(e) => setSort(e.target.value as CoinSort)}\n className=\"rounded-lg border border-border bg-background px-3 py-1.5 text-xs font-medium text-foreground\"\n >\n {SORT_OPTIONS.map((o) => <option key={o.value} value={o.value}>{o.label}</option>)}\n </select>\n <div className=\"inline-flex rounded-lg border border-border p-0.5\">\n {([{ v: \"grid\", Icon: LayoutGrid }, { v: \"table\", Icon: List }] as const).map(({ v, Icon }) => (\n <button\n key={v}\n onClick={() => setView(v)}\n aria-label={v === \"grid\" ? \"Grid view\" : \"Table view\"}\n className={cn(\"rounded-md p-1.5 transition-colors\", view === v ? \"bg-primary/10 text-primary\" : \"text-muted-foreground hover:text-foreground\")}\n >\n <Icon className=\"h-4 w-4\" />\n </button>\n ))}\n </div>\n </div>\n </div>\n <div className=\"relative\">\n <Search className=\"pointer-events-none absolute left-3 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground\" />\n <input\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder=\"Search coins by name or symbol…\"\n className=\"w-full rounded-lg border border-border bg-background py-2 pl-9 pr-3 text-sm outline-none focus:border-primary/50\"\n />\n </div>\n </div>\n\n {isLoading && items.length === 0 ? (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4\">\n {Array.from({ length: 8 }).map((_, i) => <CoinCardSkeleton key={i} />)}\n </div>\n ) : items.length === 0 ? (\n <div className=\"rounded-xl border border-border/60 py-16 text-center text-muted-foreground\">\n {query.trim() ? `No coins match \"${query.trim()}\".` : \"No coins yet.\"}\n </div>\n ) : view === \"table\" ? (\n <div className=\"space-y-2\">\n {items.map((c) => <CoinRow key={`${c.chain}-${c.contractAddress}`} collection={c} usePrice={usePrice} href={coinHref(c)} />)}\n </div>\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4\">\n {items.map((c) => <CoinCard key={`${c.chain}-${c.contractAddress}`} collection={c} usePrice={usePrice} href={coinHref(c)} />)}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
@@ -50,7 +50,7 @@ function ListingCard({ order, inCart = false, onBuy, onCart, overflowMenu, compa
50
50
  const name = order.token?.name ?? `Token #${order.nftTokenId}`;
51
51
  const image = order.token?.image ? (0, import_ipfs.ipfsToHttp)(order.token.image) : null;
52
52
  const assetHref = `/asset/${order.nftContract}/${order.nftTokenId}`;
53
- const showActionBar = isListing && (onBuy || onCart || overflowMenu);
53
+ const showActionBar = !!(onBuy || onCart || overflowMenu);
54
54
  if (compact) {
55
55
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_motion_primitives.MotionCard, { className: "card-base", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_link.default, { href: assetHref, className: "block", children: [
56
56
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "relative aspect-square bg-muted overflow-hidden", children: image && !imgError ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_image.default, { src: image, alt: name, fill: true, unoptimized: true, sizes: "(max-width: 640px) 33vw, 20vw", className: "object-cover group-hover:scale-105 transition-transform duration-500", onError: () => setImgError(true) }) : /* @__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-xl font-mono text-muted-foreground", children: [
@@ -59,10 +59,9 @@ function ListingCard({ order, inCart = false, onBuy, onCart, overflowMenu, compa
59
59
  ] }) }) }),
60
60
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "p-2.5 space-y-0.5", children: [
61
61
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-xs font-semibold truncate", children: name }),
62
- order.price?.formatted && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { className: "text-[11px] font-bold price-value", children: [
63
- (0, import_format.formatDisplayPrice)(order.price.formatted),
64
- " ",
65
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-muted-foreground font-normal", children: order.price.currency })
62
+ order.price?.formatted && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { className: "text-[11px] font-bold price-value inline-flex items-center gap-1", children: [
63
+ order.price.currency && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_currency_icon.CurrencyIcon, { symbol: order.price.currency, size: 11 }),
64
+ (0, import_format.formatDisplayPrice)(order.price.formatted)
66
65
  ] }),
67
66
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-[10px] text-muted-foreground", children: (0, import_time.timeAgo)(order.createdAt) })
68
67
  ] })
@@ -73,24 +72,24 @@ function ListingCard({ order, inCart = false, onBuy, onCart, overflowMenu, compa
73
72
  "#",
74
73
  order.nftTokenId
75
74
  ] }) }) }),
76
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "p-4 space-y-3", children: [
77
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
78
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "font-semibold text-base truncate leading-snug", children: name }),
79
- order.token?.description ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-[11px] text-muted-foreground line-clamp-1 leading-snug mt-0.5", children: order.token.description }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { className: "text-[11px] text-muted-foreground", children: [
80
- "#",
81
- order.nftTokenId
82
- ] })
75
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "p-3.5 space-y-3", children: [
76
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "min-w-0", children: [
77
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "font-semibold text-[15px] truncate leading-snug", children: name }),
78
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-[11px] text-muted-foreground truncate leading-snug mt-0.5", children: order.token?.description ? order.token.description : `#${order.nftTokenId}` })
83
79
  ] }),
84
- order.price?.formatted && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-1.5", children: [
85
- order.price.currency && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_currency_icon.CurrencyIcon, { symbol: order.price.currency, size: 14 }),
86
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { className: "text-lg font-bold price-value leading-none", children: [
87
- (0, import_format.formatDisplayPrice)(order.price.formatted),
88
- " ",
89
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-muted-foreground font-normal text-sm", children: order.price.currency })
90
- ] })
80
+ order.price?.formatted && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-end justify-between gap-2", children: [
81
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "min-w-0", children: [
82
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-[10px] uppercase tracking-widest text-muted-foreground/55 leading-none", children: isListing ? "Price" : "Offer" }),
83
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { className: "text-lg font-bold price-value leading-none inline-flex items-center gap-1.5 mt-1.5", children: [
84
+ order.price.currency && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_currency_icon.CurrencyIcon, { symbol: order.price.currency, size: 18 }),
85
+ (0, import_format.formatDisplayPrice)(order.price.formatted),
86
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "sr-only", children: order.price.currency })
87
+ ] })
88
+ ] }),
89
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-[10px] text-muted-foreground/60 whitespace-nowrap shrink-0", children: (0, import_time.timeAgo)(order.createdAt) })
91
90
  ] }),
92
91
  showActionBar && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-1.5", children: [
93
- onBuy && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "btn-border-animated p-[1.5px] rounded-[10px] flex-1 h-9", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
92
+ isListing ? onBuy ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "btn-border-animated p-[1.5px] rounded-[10px] flex-1 h-9", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
94
93
  "button",
95
94
  {
96
95
  className: "w-full h-full rounded-[9px] bg-background flex items-center justify-center gap-1.5 text-xs font-semibold text-foreground hover:bg-muted/60 transition-all active:scale-[0.98]",
@@ -103,7 +102,27 @@ function ListingCard({ order, inCart = false, onBuy, onCart, overflowMenu, compa
103
102
  " Buy"
104
103
  ]
105
104
  }
106
- ) }),
105
+ ) }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
106
+ import_link.default,
107
+ {
108
+ href: assetHref,
109
+ className: "flex-1 h-9 inline-flex items-center justify-center gap-1.5 rounded-[9px] border border-border bg-background text-xs font-semibold text-foreground hover:bg-muted/60 transition-all active:scale-[0.98]",
110
+ children: [
111
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.ArrowUpRight, { className: "h-3.5 w-3.5 shrink-0" }),
112
+ " View"
113
+ ]
114
+ }
115
+ ) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
116
+ import_link.default,
117
+ {
118
+ href: assetHref,
119
+ className: "flex-1 h-9 inline-flex items-center justify-center gap-1.5 rounded-[9px] border border-border bg-background text-xs font-semibold text-foreground hover:bg-muted/60 transition-all active:scale-[0.98]",
120
+ children: [
121
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.ArrowUpRight, { className: "h-3.5 w-3.5 shrink-0" }),
122
+ " View asset"
123
+ ]
124
+ }
125
+ ),
107
126
  onCart && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
108
127
  "button",
109
128
  {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/listing-card.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState } from \"react\";\nimport Link from \"next/link\";\nimport Image from \"next/image\";\nimport { ShoppingCart, Check, Zap } from \"lucide-react\";\nimport { MotionCard } from \"./motion-primitives.js\";\nimport { CurrencyIcon } from \"./currency-icon.js\";\nimport { cn } from \"../utils/cn.js\";\nimport { ipfsToHttp } from \"../utils/ipfs.js\";\nimport { formatDisplayPrice } from \"../utils/format.js\";\nimport { timeAgo } from \"../utils/time.js\";\nimport type { ApiOrder } from \"@medialane/sdk\";\n\nexport interface ListingCardProps {\n order: ApiOrder;\n inCart?: boolean;\n onBuy?: (order: ApiOrder) => void;\n onCart?: (order: ApiOrder) => void;\n /** App passes a fully constructed <DropdownMenu> here */\n overflowMenu?: React.ReactNode;\n compact?: boolean;\n}\n\nexport function ListingCard({ order, inCart = false, onBuy, onCart, overflowMenu, compact = false }: ListingCardProps) {\n const [imgError, setImgError] = useState(false);\n const isListing = order.offer.itemType === \"ERC721\" || order.offer.itemType === \"ERC1155\";\n const name = order.token?.name ?? `Token #${order.nftTokenId}`;\n const image = order.token?.image ? ipfsToHttp(order.token.image) : null;\n const assetHref = `/asset/${order.nftContract}/${order.nftTokenId}`;\n\n const showActionBar = isListing && (onBuy || onCart || overflowMenu);\n\n // ─── Compact variant ──────────────────────────────────────────────────────\n if (compact) {\n return (\n <MotionCard className=\"card-base\">\n <Link href={assetHref} className=\"block\">\n <div className=\"relative aspect-square bg-muted overflow-hidden\">\n {image && !imgError ? (\n <Image src={image} alt={name} fill unoptimized sizes=\"(max-width: 640px) 33vw, 20vw\" className=\"object-cover group-hover:scale-105 transition-transform duration-500\" onError={() => setImgError(true)} />\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-xl font-mono text-muted-foreground\">#{order.nftTokenId}</span>\n </div>\n )}\n </div>\n <div className=\"p-2.5 space-y-0.5\">\n <p className=\"text-xs font-semibold truncate\">{name}</p>\n {order.price?.formatted && (\n <p className=\"text-[11px] font-bold price-value\">\n {formatDisplayPrice(order.price.formatted)} <span className=\"text-muted-foreground font-normal\">{order.price.currency}</span>\n </p>\n )}\n <p className=\"text-[10px] text-muted-foreground\">{timeAgo(order.createdAt)}</p>\n </div>\n </Link>\n </MotionCard>\n );\n }\n\n // ─── Full variant ─────────────────────────────────────────────────────────\n return (\n <MotionCard className=\"card-base\">\n <Link href={assetHref} className=\"block\">\n <div className=\"relative aspect-square bg-muted overflow-hidden\">\n {image && !imgError ? (\n <Image src={image} alt={name} fill unoptimized sizes=\"(max-width: 640px) 50vw, (max-width: 1024px) 33vw, 25vw\" className=\"object-cover group-hover:scale-105 transition-transform duration-500\" onError={() => setImgError(true)} />\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\">#{order.nftTokenId}</span>\n </div>\n )}\n </div>\n\n <div className=\"p-4 space-y-3\">\n <div>\n <p className=\"font-semibold text-base truncate leading-snug\">{name}</p>\n {order.token?.description ? (\n <p className=\"text-[11px] text-muted-foreground line-clamp-1 leading-snug mt-0.5\">{order.token.description}</p>\n ) : (\n <p className=\"text-[11px] text-muted-foreground\">#{order.nftTokenId}</p>\n )}\n </div>\n\n {order.price?.formatted && (\n <div className=\"flex items-center gap-1.5\">\n {order.price.currency && <CurrencyIcon symbol={order.price.currency} size={14} />}\n <p className=\"text-lg font-bold price-value leading-none\">\n {formatDisplayPrice(order.price.formatted)} <span className=\"text-muted-foreground font-normal text-sm\">{order.price.currency}</span>\n </p>\n </div>\n )}\n\n {showActionBar && (\n <div className=\"flex items-center gap-1.5\">\n {onBuy && (\n <div className=\"btn-border-animated p-[1.5px] rounded-[10px] flex-1 h-9\">\n <button\n className=\"w-full h-full rounded-[9px] bg-background flex items-center justify-center gap-1.5 text-xs font-semibold text-foreground hover:bg-muted/60 transition-all active:scale-[0.98]\"\n onClick={(e) => { e.preventDefault(); onBuy(order); }}\n >\n <Zap className=\"h-3.5 w-3.5 shrink-0\" /> Buy\n </button>\n </div>\n )}\n {onCart && (\n <button\n className={cn(\n \"h-9 w-9 shrink-0 rounded-[9px] border flex items-center justify-center transition-colors\",\n inCart\n ? \"border-brand-orange/50 bg-brand-orange/10 text-brand-orange\"\n : \"border-border bg-background hover:bg-muted text-foreground\"\n )}\n onClick={(e) => { e.preventDefault(); if (!inCart) onCart(order); }}\n disabled={inCart}\n aria-label={inCart ? \"Added to 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 {overflowMenu}\n </div>\n )}\n </div>\n </Link>\n </MotionCard>\n );\n}\n\nexport function ListingCardSkeleton() {\n return (\n <div className=\"card-base\">\n <div className=\"aspect-square w-full bg-muted animate-pulse\" />\n <div className=\"p-3 space-y-2.5\">\n <div className=\"space-y-1\">\n <div className=\"h-4 w-3/4 bg-muted animate-pulse rounded\" />\n <div className=\"h-3 w-1/2 bg-muted animate-pulse rounded\" />\n </div>\n <div className=\"h-6 w-1/2 bg-muted animate-pulse rounded\" />\n <div className=\"h-3 w-2/3 bg-muted animate-pulse rounded\" />\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":["Link","Image"]}
1
+ {"version":3,"sources":["../../src/components/listing-card.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState } from \"react\";\nimport Link from \"next/link\";\nimport Image from \"next/image\";\nimport { ShoppingCart, Check, Zap, ArrowUpRight } from \"lucide-react\";\nimport { MotionCard } from \"./motion-primitives.js\";\nimport { CurrencyIcon } from \"./currency-icon.js\";\nimport { cn } from \"../utils/cn.js\";\nimport { ipfsToHttp } from \"../utils/ipfs.js\";\nimport { formatDisplayPrice } from \"../utils/format.js\";\nimport { timeAgo } from \"../utils/time.js\";\nimport type { ApiOrder } from \"@medialane/sdk\";\n\nexport interface ListingCardProps {\n order: ApiOrder;\n inCart?: boolean;\n onBuy?: (order: ApiOrder) => void;\n onCart?: (order: ApiOrder) => void;\n /** App passes a fully constructed <DropdownMenu> here */\n overflowMenu?: React.ReactNode;\n compact?: boolean;\n}\n\nexport function ListingCard({ order, inCart = false, onBuy, onCart, overflowMenu, compact = false }: ListingCardProps) {\n const [imgError, setImgError] = useState(false);\n const isListing = order.offer.itemType === \"ERC721\" || order.offer.itemType === \"ERC1155\";\n const name = order.token?.name ?? `Token #${order.nftTokenId}`;\n const image = order.token?.image ? ipfsToHttp(order.token.image) : null;\n const assetHref = `/asset/${order.nftContract}/${order.nftTokenId}`;\n\n // Show the action bar for listings (Buy/View) and for offers (View asset),\n // whenever the host wired any action or an overflow menu.\n const showActionBar = !!(onBuy || onCart || overflowMenu);\n\n // ─── Compact variant ──────────────────────────────────────────────────────\n if (compact) {\n return (\n <MotionCard className=\"card-base\">\n <Link href={assetHref} className=\"block\">\n <div className=\"relative aspect-square bg-muted overflow-hidden\">\n {image && !imgError ? (\n <Image src={image} alt={name} fill unoptimized sizes=\"(max-width: 640px) 33vw, 20vw\" className=\"object-cover group-hover:scale-105 transition-transform duration-500\" onError={() => setImgError(true)} />\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-xl font-mono text-muted-foreground\">#{order.nftTokenId}</span>\n </div>\n )}\n </div>\n <div className=\"p-2.5 space-y-0.5\">\n <p className=\"text-xs font-semibold truncate\">{name}</p>\n {order.price?.formatted && (\n <p className=\"text-[11px] font-bold price-value inline-flex items-center gap-1\">\n {order.price.currency && <CurrencyIcon symbol={order.price.currency} size={11} />}\n {formatDisplayPrice(order.price.formatted)}\n </p>\n )}\n <p className=\"text-[10px] text-muted-foreground\">{timeAgo(order.createdAt)}</p>\n </div>\n </Link>\n </MotionCard>\n );\n }\n\n // ─── Full variant ─────────────────────────────────────────────────────────\n return (\n <MotionCard className=\"card-base\">\n <Link href={assetHref} className=\"block\">\n <div className=\"relative aspect-square bg-muted overflow-hidden\">\n {image && !imgError ? (\n <Image src={image} alt={name} fill unoptimized sizes=\"(max-width: 640px) 50vw, (max-width: 1024px) 33vw, 25vw\" className=\"object-cover group-hover:scale-105 transition-transform duration-500\" onError={() => setImgError(true)} />\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\">#{order.nftTokenId}</span>\n </div>\n )}\n </div>\n\n <div className=\"p-3.5 space-y-3\">\n <div className=\"min-w-0\">\n <p className=\"font-semibold text-[15px] truncate leading-snug\">{name}</p>\n <p className=\"text-[11px] text-muted-foreground truncate leading-snug mt-0.5\">\n {order.token?.description ? order.token.description : `#${order.nftTokenId}`}\n </p>\n </div>\n\n {/* Price / Offer + age */}\n {order.price?.formatted && (\n <div className=\"flex items-end justify-between gap-2\">\n <div className=\"min-w-0\">\n <p className=\"text-[10px] uppercase tracking-widest text-muted-foreground/55 leading-none\">\n {isListing ? \"Price\" : \"Offer\"}\n </p>\n <p className=\"text-lg font-bold price-value leading-none inline-flex items-center gap-1.5 mt-1.5\">\n {order.price.currency && <CurrencyIcon symbol={order.price.currency} size={18} />}\n {formatDisplayPrice(order.price.formatted)}\n <span className=\"sr-only\">{order.price.currency}</span>\n </p>\n </div>\n <p className=\"text-[10px] text-muted-foreground/60 whitespace-nowrap shrink-0\">\n {timeAgo(order.createdAt)}\n </p>\n </div>\n )}\n\n {showActionBar && (\n <div className=\"flex items-center gap-1.5\">\n {isListing ? (\n onBuy ? (\n <div className=\"btn-border-animated p-[1.5px] rounded-[10px] flex-1 h-9\">\n <button\n className=\"w-full h-full rounded-[9px] bg-background flex items-center justify-center gap-1.5 text-xs font-semibold text-foreground hover:bg-muted/60 transition-all active:scale-[0.98]\"\n onClick={(e) => { e.preventDefault(); onBuy(order); }}\n >\n <Zap className=\"h-3.5 w-3.5 shrink-0\" /> Buy\n </button>\n </div>\n ) : (\n <Link\n href={assetHref}\n className=\"flex-1 h-9 inline-flex items-center justify-center gap-1.5 rounded-[9px] border border-border bg-background text-xs font-semibold text-foreground hover:bg-muted/60 transition-all active:scale-[0.98]\"\n >\n <ArrowUpRight className=\"h-3.5 w-3.5 shrink-0\" /> View\n </Link>\n )\n ) : (\n <Link\n href={assetHref}\n className=\"flex-1 h-9 inline-flex items-center justify-center gap-1.5 rounded-[9px] border border-border bg-background text-xs font-semibold text-foreground hover:bg-muted/60 transition-all active:scale-[0.98]\"\n >\n <ArrowUpRight className=\"h-3.5 w-3.5 shrink-0\" /> View asset\n </Link>\n )}\n {onCart && (\n <button\n className={cn(\n \"h-9 w-9 shrink-0 rounded-[9px] border flex items-center justify-center transition-colors\",\n inCart\n ? \"border-brand-orange/50 bg-brand-orange/10 text-brand-orange\"\n : \"border-border bg-background hover:bg-muted text-foreground\"\n )}\n onClick={(e) => { e.preventDefault(); if (!inCart) onCart(order); }}\n disabled={inCart}\n aria-label={inCart ? \"Added to 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 {overflowMenu}\n </div>\n )}\n </div>\n </Link>\n </MotionCard>\n );\n}\n\nexport function ListingCardSkeleton() {\n return (\n <div className=\"card-base\">\n <div className=\"aspect-square w-full bg-muted animate-pulse\" />\n <div className=\"p-3 space-y-2.5\">\n <div className=\"space-y-1\">\n <div className=\"h-4 w-3/4 bg-muted animate-pulse rounded\" />\n <div className=\"h-3 w-1/2 bg-muted animate-pulse rounded\" />\n </div>\n <div className=\"h-6 w-1/2 bg-muted animate-pulse rounded\" />\n <div className=\"h-3 w-2/3 bg-muted animate-pulse rounded\" />\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":["Link","Image"]}
@@ -3,7 +3,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useState } from "react";
4
4
  import Link from "next/link";
5
5
  import Image from "next/image";
6
- import { ShoppingCart, Check, Zap } from "lucide-react";
6
+ import { ShoppingCart, Check, Zap, ArrowUpRight } from "lucide-react";
7
7
  import { MotionCard } from "./motion-primitives.js";
8
8
  import { CurrencyIcon } from "./currency-icon.js";
9
9
  import { cn } from "../utils/cn.js";
@@ -16,7 +16,7 @@ function ListingCard({ order, inCart = false, onBuy, onCart, overflowMenu, compa
16
16
  const name = order.token?.name ?? `Token #${order.nftTokenId}`;
17
17
  const image = order.token?.image ? ipfsToHttp(order.token.image) : null;
18
18
  const assetHref = `/asset/${order.nftContract}/${order.nftTokenId}`;
19
- const showActionBar = isListing && (onBuy || onCart || overflowMenu);
19
+ const showActionBar = !!(onBuy || onCart || overflowMenu);
20
20
  if (compact) {
21
21
  return /* @__PURE__ */ jsx(MotionCard, { className: "card-base", children: /* @__PURE__ */ jsxs(Link, { href: assetHref, className: "block", children: [
22
22
  /* @__PURE__ */ jsx("div", { className: "relative aspect-square bg-muted overflow-hidden", children: image && !imgError ? /* @__PURE__ */ jsx(Image, { src: image, alt: name, fill: true, unoptimized: true, sizes: "(max-width: 640px) 33vw, 20vw", className: "object-cover group-hover:scale-105 transition-transform duration-500", onError: () => setImgError(true) }) : /* @__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-xl font-mono text-muted-foreground", children: [
@@ -25,10 +25,9 @@ function ListingCard({ order, inCart = false, onBuy, onCart, overflowMenu, compa
25
25
  ] }) }) }),
26
26
  /* @__PURE__ */ jsxs("div", { className: "p-2.5 space-y-0.5", children: [
27
27
  /* @__PURE__ */ jsx("p", { className: "text-xs font-semibold truncate", children: name }),
28
- order.price?.formatted && /* @__PURE__ */ jsxs("p", { className: "text-[11px] font-bold price-value", children: [
29
- formatDisplayPrice(order.price.formatted),
30
- " ",
31
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground font-normal", children: order.price.currency })
28
+ order.price?.formatted && /* @__PURE__ */ jsxs("p", { className: "text-[11px] font-bold price-value inline-flex items-center gap-1", children: [
29
+ order.price.currency && /* @__PURE__ */ jsx(CurrencyIcon, { symbol: order.price.currency, size: 11 }),
30
+ formatDisplayPrice(order.price.formatted)
32
31
  ] }),
33
32
  /* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground", children: timeAgo(order.createdAt) })
34
33
  ] })
@@ -39,24 +38,24 @@ function ListingCard({ order, inCart = false, onBuy, onCart, overflowMenu, compa
39
38
  "#",
40
39
  order.nftTokenId
41
40
  ] }) }) }),
42
- /* @__PURE__ */ jsxs("div", { className: "p-4 space-y-3", children: [
43
- /* @__PURE__ */ jsxs("div", { children: [
44
- /* @__PURE__ */ jsx("p", { className: "font-semibold text-base truncate leading-snug", children: name }),
45
- order.token?.description ? /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground line-clamp-1 leading-snug mt-0.5", children: order.token.description }) : /* @__PURE__ */ jsxs("p", { className: "text-[11px] text-muted-foreground", children: [
46
- "#",
47
- order.nftTokenId
48
- ] })
41
+ /* @__PURE__ */ jsxs("div", { className: "p-3.5 space-y-3", children: [
42
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
43
+ /* @__PURE__ */ jsx("p", { className: "font-semibold text-[15px] truncate leading-snug", children: name }),
44
+ /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground truncate leading-snug mt-0.5", children: order.token?.description ? order.token.description : `#${order.nftTokenId}` })
49
45
  ] }),
50
- order.price?.formatted && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
51
- order.price.currency && /* @__PURE__ */ jsx(CurrencyIcon, { symbol: order.price.currency, size: 14 }),
52
- /* @__PURE__ */ jsxs("p", { className: "text-lg font-bold price-value leading-none", children: [
53
- formatDisplayPrice(order.price.formatted),
54
- " ",
55
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground font-normal text-sm", children: order.price.currency })
56
- ] })
46
+ order.price?.formatted && /* @__PURE__ */ jsxs("div", { className: "flex items-end justify-between gap-2", children: [
47
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
48
+ /* @__PURE__ */ jsx("p", { className: "text-[10px] uppercase tracking-widest text-muted-foreground/55 leading-none", children: isListing ? "Price" : "Offer" }),
49
+ /* @__PURE__ */ jsxs("p", { className: "text-lg font-bold price-value leading-none inline-flex items-center gap-1.5 mt-1.5", children: [
50
+ order.price.currency && /* @__PURE__ */ jsx(CurrencyIcon, { symbol: order.price.currency, size: 18 }),
51
+ formatDisplayPrice(order.price.formatted),
52
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: order.price.currency })
53
+ ] })
54
+ ] }),
55
+ /* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground/60 whitespace-nowrap shrink-0", children: timeAgo(order.createdAt) })
57
56
  ] }),
58
57
  showActionBar && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
59
- onBuy && /* @__PURE__ */ jsx("div", { className: "btn-border-animated p-[1.5px] rounded-[10px] flex-1 h-9", children: /* @__PURE__ */ jsxs(
58
+ isListing ? onBuy ? /* @__PURE__ */ jsx("div", { className: "btn-border-animated p-[1.5px] rounded-[10px] flex-1 h-9", children: /* @__PURE__ */ jsxs(
60
59
  "button",
61
60
  {
62
61
  className: "w-full h-full rounded-[9px] bg-background flex items-center justify-center gap-1.5 text-xs font-semibold text-foreground hover:bg-muted/60 transition-all active:scale-[0.98]",
@@ -69,7 +68,27 @@ function ListingCard({ order, inCart = false, onBuy, onCart, overflowMenu, compa
69
68
  " Buy"
70
69
  ]
71
70
  }
72
- ) }),
71
+ ) }) : /* @__PURE__ */ jsxs(
72
+ Link,
73
+ {
74
+ href: assetHref,
75
+ className: "flex-1 h-9 inline-flex items-center justify-center gap-1.5 rounded-[9px] border border-border bg-background text-xs font-semibold text-foreground hover:bg-muted/60 transition-all active:scale-[0.98]",
76
+ children: [
77
+ /* @__PURE__ */ jsx(ArrowUpRight, { className: "h-3.5 w-3.5 shrink-0" }),
78
+ " View"
79
+ ]
80
+ }
81
+ ) : /* @__PURE__ */ jsxs(
82
+ Link,
83
+ {
84
+ href: assetHref,
85
+ className: "flex-1 h-9 inline-flex items-center justify-center gap-1.5 rounded-[9px] border border-border bg-background text-xs font-semibold text-foreground hover:bg-muted/60 transition-all active:scale-[0.98]",
86
+ children: [
87
+ /* @__PURE__ */ jsx(ArrowUpRight, { className: "h-3.5 w-3.5 shrink-0" }),
88
+ " View asset"
89
+ ]
90
+ }
91
+ ),
73
92
  onCart && /* @__PURE__ */ jsx(
74
93
  "button",
75
94
  {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/listing-card.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState } from \"react\";\nimport Link from \"next/link\";\nimport Image from \"next/image\";\nimport { ShoppingCart, Check, Zap } from \"lucide-react\";\nimport { MotionCard } from \"./motion-primitives.js\";\nimport { CurrencyIcon } from \"./currency-icon.js\";\nimport { cn } from \"../utils/cn.js\";\nimport { ipfsToHttp } from \"../utils/ipfs.js\";\nimport { formatDisplayPrice } from \"../utils/format.js\";\nimport { timeAgo } from \"../utils/time.js\";\nimport type { ApiOrder } from \"@medialane/sdk\";\n\nexport interface ListingCardProps {\n order: ApiOrder;\n inCart?: boolean;\n onBuy?: (order: ApiOrder) => void;\n onCart?: (order: ApiOrder) => void;\n /** App passes a fully constructed <DropdownMenu> here */\n overflowMenu?: React.ReactNode;\n compact?: boolean;\n}\n\nexport function ListingCard({ order, inCart = false, onBuy, onCart, overflowMenu, compact = false }: ListingCardProps) {\n const [imgError, setImgError] = useState(false);\n const isListing = order.offer.itemType === \"ERC721\" || order.offer.itemType === \"ERC1155\";\n const name = order.token?.name ?? `Token #${order.nftTokenId}`;\n const image = order.token?.image ? ipfsToHttp(order.token.image) : null;\n const assetHref = `/asset/${order.nftContract}/${order.nftTokenId}`;\n\n const showActionBar = isListing && (onBuy || onCart || overflowMenu);\n\n // ─── Compact variant ──────────────────────────────────────────────────────\n if (compact) {\n return (\n <MotionCard className=\"card-base\">\n <Link href={assetHref} className=\"block\">\n <div className=\"relative aspect-square bg-muted overflow-hidden\">\n {image && !imgError ? (\n <Image src={image} alt={name} fill unoptimized sizes=\"(max-width: 640px) 33vw, 20vw\" className=\"object-cover group-hover:scale-105 transition-transform duration-500\" onError={() => setImgError(true)} />\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-xl font-mono text-muted-foreground\">#{order.nftTokenId}</span>\n </div>\n )}\n </div>\n <div className=\"p-2.5 space-y-0.5\">\n <p className=\"text-xs font-semibold truncate\">{name}</p>\n {order.price?.formatted && (\n <p className=\"text-[11px] font-bold price-value\">\n {formatDisplayPrice(order.price.formatted)} <span className=\"text-muted-foreground font-normal\">{order.price.currency}</span>\n </p>\n )}\n <p className=\"text-[10px] text-muted-foreground\">{timeAgo(order.createdAt)}</p>\n </div>\n </Link>\n </MotionCard>\n );\n }\n\n // ─── Full variant ─────────────────────────────────────────────────────────\n return (\n <MotionCard className=\"card-base\">\n <Link href={assetHref} className=\"block\">\n <div className=\"relative aspect-square bg-muted overflow-hidden\">\n {image && !imgError ? (\n <Image src={image} alt={name} fill unoptimized sizes=\"(max-width: 640px) 50vw, (max-width: 1024px) 33vw, 25vw\" className=\"object-cover group-hover:scale-105 transition-transform duration-500\" onError={() => setImgError(true)} />\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\">#{order.nftTokenId}</span>\n </div>\n )}\n </div>\n\n <div className=\"p-4 space-y-3\">\n <div>\n <p className=\"font-semibold text-base truncate leading-snug\">{name}</p>\n {order.token?.description ? (\n <p className=\"text-[11px] text-muted-foreground line-clamp-1 leading-snug mt-0.5\">{order.token.description}</p>\n ) : (\n <p className=\"text-[11px] text-muted-foreground\">#{order.nftTokenId}</p>\n )}\n </div>\n\n {order.price?.formatted && (\n <div className=\"flex items-center gap-1.5\">\n {order.price.currency && <CurrencyIcon symbol={order.price.currency} size={14} />}\n <p className=\"text-lg font-bold price-value leading-none\">\n {formatDisplayPrice(order.price.formatted)} <span className=\"text-muted-foreground font-normal text-sm\">{order.price.currency}</span>\n </p>\n </div>\n )}\n\n {showActionBar && (\n <div className=\"flex items-center gap-1.5\">\n {onBuy && (\n <div className=\"btn-border-animated p-[1.5px] rounded-[10px] flex-1 h-9\">\n <button\n className=\"w-full h-full rounded-[9px] bg-background flex items-center justify-center gap-1.5 text-xs font-semibold text-foreground hover:bg-muted/60 transition-all active:scale-[0.98]\"\n onClick={(e) => { e.preventDefault(); onBuy(order); }}\n >\n <Zap className=\"h-3.5 w-3.5 shrink-0\" /> Buy\n </button>\n </div>\n )}\n {onCart && (\n <button\n className={cn(\n \"h-9 w-9 shrink-0 rounded-[9px] border flex items-center justify-center transition-colors\",\n inCart\n ? \"border-brand-orange/50 bg-brand-orange/10 text-brand-orange\"\n : \"border-border bg-background hover:bg-muted text-foreground\"\n )}\n onClick={(e) => { e.preventDefault(); if (!inCart) onCart(order); }}\n disabled={inCart}\n aria-label={inCart ? \"Added to 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 {overflowMenu}\n </div>\n )}\n </div>\n </Link>\n </MotionCard>\n );\n}\n\nexport function ListingCardSkeleton() {\n return (\n <div className=\"card-base\">\n <div className=\"aspect-square w-full bg-muted animate-pulse\" />\n <div className=\"p-3 space-y-2.5\">\n <div className=\"space-y-1\">\n <div className=\"h-4 w-3/4 bg-muted animate-pulse rounded\" />\n <div className=\"h-3 w-1/2 bg-muted animate-pulse rounded\" />\n </div>\n <div className=\"h-6 w-1/2 bg-muted animate-pulse rounded\" />\n <div className=\"h-3 w-2/3 bg-muted animate-pulse rounded\" />\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/components/listing-card.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState } from \"react\";\nimport Link from \"next/link\";\nimport Image from \"next/image\";\nimport { ShoppingCart, Check, Zap, ArrowUpRight } from \"lucide-react\";\nimport { MotionCard } from \"./motion-primitives.js\";\nimport { CurrencyIcon } from \"./currency-icon.js\";\nimport { cn } from \"../utils/cn.js\";\nimport { ipfsToHttp } from \"../utils/ipfs.js\";\nimport { formatDisplayPrice } from \"../utils/format.js\";\nimport { timeAgo } from \"../utils/time.js\";\nimport type { ApiOrder } from \"@medialane/sdk\";\n\nexport interface ListingCardProps {\n order: ApiOrder;\n inCart?: boolean;\n onBuy?: (order: ApiOrder) => void;\n onCart?: (order: ApiOrder) => void;\n /** App passes a fully constructed <DropdownMenu> here */\n overflowMenu?: React.ReactNode;\n compact?: boolean;\n}\n\nexport function ListingCard({ order, inCart = false, onBuy, onCart, overflowMenu, compact = false }: ListingCardProps) {\n const [imgError, setImgError] = useState(false);\n const isListing = order.offer.itemType === \"ERC721\" || order.offer.itemType === \"ERC1155\";\n const name = order.token?.name ?? `Token #${order.nftTokenId}`;\n const image = order.token?.image ? ipfsToHttp(order.token.image) : null;\n const assetHref = `/asset/${order.nftContract}/${order.nftTokenId}`;\n\n // Show the action bar for listings (Buy/View) and for offers (View asset),\n // whenever the host wired any action or an overflow menu.\n const showActionBar = !!(onBuy || onCart || overflowMenu);\n\n // ─── Compact variant ──────────────────────────────────────────────────────\n if (compact) {\n return (\n <MotionCard className=\"card-base\">\n <Link href={assetHref} className=\"block\">\n <div className=\"relative aspect-square bg-muted overflow-hidden\">\n {image && !imgError ? (\n <Image src={image} alt={name} fill unoptimized sizes=\"(max-width: 640px) 33vw, 20vw\" className=\"object-cover group-hover:scale-105 transition-transform duration-500\" onError={() => setImgError(true)} />\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-xl font-mono text-muted-foreground\">#{order.nftTokenId}</span>\n </div>\n )}\n </div>\n <div className=\"p-2.5 space-y-0.5\">\n <p className=\"text-xs font-semibold truncate\">{name}</p>\n {order.price?.formatted && (\n <p className=\"text-[11px] font-bold price-value inline-flex items-center gap-1\">\n {order.price.currency && <CurrencyIcon symbol={order.price.currency} size={11} />}\n {formatDisplayPrice(order.price.formatted)}\n </p>\n )}\n <p className=\"text-[10px] text-muted-foreground\">{timeAgo(order.createdAt)}</p>\n </div>\n </Link>\n </MotionCard>\n );\n }\n\n // ─── Full variant ─────────────────────────────────────────────────────────\n return (\n <MotionCard className=\"card-base\">\n <Link href={assetHref} className=\"block\">\n <div className=\"relative aspect-square bg-muted overflow-hidden\">\n {image && !imgError ? (\n <Image src={image} alt={name} fill unoptimized sizes=\"(max-width: 640px) 50vw, (max-width: 1024px) 33vw, 25vw\" className=\"object-cover group-hover:scale-105 transition-transform duration-500\" onError={() => setImgError(true)} />\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\">#{order.nftTokenId}</span>\n </div>\n )}\n </div>\n\n <div className=\"p-3.5 space-y-3\">\n <div className=\"min-w-0\">\n <p className=\"font-semibold text-[15px] truncate leading-snug\">{name}</p>\n <p className=\"text-[11px] text-muted-foreground truncate leading-snug mt-0.5\">\n {order.token?.description ? order.token.description : `#${order.nftTokenId}`}\n </p>\n </div>\n\n {/* Price / Offer + age */}\n {order.price?.formatted && (\n <div className=\"flex items-end justify-between gap-2\">\n <div className=\"min-w-0\">\n <p className=\"text-[10px] uppercase tracking-widest text-muted-foreground/55 leading-none\">\n {isListing ? \"Price\" : \"Offer\"}\n </p>\n <p className=\"text-lg font-bold price-value leading-none inline-flex items-center gap-1.5 mt-1.5\">\n {order.price.currency && <CurrencyIcon symbol={order.price.currency} size={18} />}\n {formatDisplayPrice(order.price.formatted)}\n <span className=\"sr-only\">{order.price.currency}</span>\n </p>\n </div>\n <p className=\"text-[10px] text-muted-foreground/60 whitespace-nowrap shrink-0\">\n {timeAgo(order.createdAt)}\n </p>\n </div>\n )}\n\n {showActionBar && (\n <div className=\"flex items-center gap-1.5\">\n {isListing ? (\n onBuy ? (\n <div className=\"btn-border-animated p-[1.5px] rounded-[10px] flex-1 h-9\">\n <button\n className=\"w-full h-full rounded-[9px] bg-background flex items-center justify-center gap-1.5 text-xs font-semibold text-foreground hover:bg-muted/60 transition-all active:scale-[0.98]\"\n onClick={(e) => { e.preventDefault(); onBuy(order); }}\n >\n <Zap className=\"h-3.5 w-3.5 shrink-0\" /> Buy\n </button>\n </div>\n ) : (\n <Link\n href={assetHref}\n className=\"flex-1 h-9 inline-flex items-center justify-center gap-1.5 rounded-[9px] border border-border bg-background text-xs font-semibold text-foreground hover:bg-muted/60 transition-all active:scale-[0.98]\"\n >\n <ArrowUpRight className=\"h-3.5 w-3.5 shrink-0\" /> View\n </Link>\n )\n ) : (\n <Link\n href={assetHref}\n className=\"flex-1 h-9 inline-flex items-center justify-center gap-1.5 rounded-[9px] border border-border bg-background text-xs font-semibold text-foreground hover:bg-muted/60 transition-all active:scale-[0.98]\"\n >\n <ArrowUpRight className=\"h-3.5 w-3.5 shrink-0\" /> View asset\n </Link>\n )}\n {onCart && (\n <button\n className={cn(\n \"h-9 w-9 shrink-0 rounded-[9px] border flex items-center justify-center transition-colors\",\n inCart\n ? \"border-brand-orange/50 bg-brand-orange/10 text-brand-orange\"\n : \"border-border bg-background hover:bg-muted text-foreground\"\n )}\n onClick={(e) => { e.preventDefault(); if (!inCart) onCart(order); }}\n disabled={inCart}\n aria-label={inCart ? \"Added to 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 {overflowMenu}\n </div>\n )}\n </div>\n </Link>\n </MotionCard>\n );\n}\n\nexport function ListingCardSkeleton() {\n return (\n <div className=\"card-base\">\n <div className=\"aspect-square w-full bg-muted animate-pulse\" />\n <div className=\"p-3 space-y-2.5\">\n <div className=\"space-y-1\">\n <div className=\"h-4 w-3/4 bg-muted animate-pulse rounded\" />\n <div className=\"h-3 w-1/2 bg-muted animate-pulse rounded\" />\n </div>\n <div className=\"h-6 w-1/2 bg-muted animate-pulse rounded\" />\n <div className=\"h-3 w-2/3 bg-muted animate-pulse rounded\" />\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var coins_exports = {};
20
+ __export(coins_exports, {
21
+ coinKind: () => coinKind,
22
+ formatCoinPrice: () => formatCoinPrice,
23
+ formatFdv: () => formatFdv
24
+ });
25
+ module.exports = __toCommonJS(coins_exports);
26
+ function coinKind(service) {
27
+ return service === "external-erc20" ? "memecoin" : "creator";
28
+ }
29
+ function formatCoinPrice(n) {
30
+ if (n === 0) return "0";
31
+ if (n < 1e-6) return n.toExponential(2);
32
+ if (n < 1) return n.toPrecision(3);
33
+ return n.toLocaleString(void 0, { maximumFractionDigits: 4 });
34
+ }
35
+ function formatFdv(quotePerCoin, totalSupply, quoteSymbol) {
36
+ if (quotePerCoin == null || !totalSupply) return null;
37
+ const fdv = quotePerCoin * totalSupply;
38
+ const sym = quoteSymbol ?? "";
39
+ const abbr = fdv >= 1e9 ? `${(fdv / 1e9).toLocaleString(void 0, { maximumFractionDigits: 1 })}B` : fdv >= 1e6 ? `${(fdv / 1e6).toLocaleString(void 0, { maximumFractionDigits: 1 })}M` : fdv >= 1e3 ? `${(fdv / 1e3).toLocaleString(void 0, { maximumFractionDigits: 1 })}K` : fdv.toLocaleString(void 0, { maximumFractionDigits: 2 });
40
+ return sym ? `${abbr} ${sym}` : abbr;
41
+ }
42
+ // Annotate the CommonJS export names for ESM import in node:
43
+ 0 && (module.exports = {
44
+ coinKind,
45
+ formatCoinPrice,
46
+ formatFdv
47
+ });
48
+ //# sourceMappingURL=coins.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/data/coins.ts"],"sourcesContent":["// Coin discovery — pure, dependency-free helpers + the structural shapes the\n// coin components read. No @medialane/sdk import: ui types shared code\n// structurally (same pattern as CollectionCard's `ApiCollection & {…}`), so a\n// coin on any chain works without bumping ui's SDK. Registry-dependent logic\n// (isCoinCollection / COIN_SERVICE_IDS, price reads) lives in the apps and is\n// injected — see the CoinsExplorer/CoinCard props.\n\nexport type CoinKind = \"creator\" | \"memecoin\";\n\n/** The fields a coin tile reads. An app's `ApiCollection` structurally satisfies\n * this. `chain` is first-class — rendered as a badge; nothing assumes Starknet. */\nexport interface CoinCollectionLike {\n contractAddress: string;\n chain?: string | null;\n name?: string | null;\n symbol?: string | null;\n image?: string | null;\n service?: string | null;\n claimedBy?: string | null;\n holderCount?: number | null;\n totalSupply?: number | null;\n profile?: { image?: string | null } | null;\n}\n\n/** Minimal spot-price shape — the concrete read (Ekubo on Starknet, a DEX on\n * another chain) is injected by the app and structurally satisfies this. */\nexport interface CoinPriceLike {\n quotePerCoin: number;\n quoteSymbol: string | null;\n}\n\n/** Native creator coin vs claimed external memecoin. */\nexport function coinKind(service: string | null | undefined): CoinKind {\n return service === \"external-erc20\" ? \"memecoin\" : \"creator\";\n}\n\n/** Format a quote-per-coin spot price. */\nexport function formatCoinPrice(n: number): string {\n if (n === 0) return \"0\";\n if (n < 0.000001) return n.toExponential(2);\n if (n < 1) return n.toPrecision(3);\n return n.toLocaleString(undefined, { maximumFractionDigits: 4 });\n}\n\n/** Fully-diluted value = price × supply, abbreviated, in the quote symbol.\n * Returns null when price/supply is unknown or zero (external coins aren't\n * supply-indexed, so price × 0 must read \"—\", never \"0\"). */\nexport function formatFdv(\n quotePerCoin: number | null | undefined,\n totalSupply: number | null | undefined,\n quoteSymbol: string | null | undefined\n): string | null {\n if (quotePerCoin == null || !totalSupply) return null;\n const fdv = quotePerCoin * totalSupply;\n const sym = quoteSymbol ?? \"\";\n const abbr =\n fdv >= 1_000_000_000 ? `${(fdv / 1_000_000_000).toLocaleString(undefined, { maximumFractionDigits: 1 })}B` :\n fdv >= 1_000_000 ? `${(fdv / 1_000_000).toLocaleString(undefined, { maximumFractionDigits: 1 })}M` :\n fdv >= 1_000 ? `${(fdv / 1_000).toLocaleString(undefined, { maximumFractionDigits: 1 })}K` :\n fdv.toLocaleString(undefined, { maximumFractionDigits: 2 });\n return sym ? `${abbr} ${sym}` : abbr;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgCO,SAAS,SAAS,SAA8C;AACrE,SAAO,YAAY,mBAAmB,aAAa;AACrD;AAGO,SAAS,gBAAgB,GAAmB;AACjD,MAAI,MAAM,EAAG,QAAO;AACpB,MAAI,IAAI,KAAU,QAAO,EAAE,cAAc,CAAC;AAC1C,MAAI,IAAI,EAAG,QAAO,EAAE,YAAY,CAAC;AACjC,SAAO,EAAE,eAAe,QAAW,EAAE,uBAAuB,EAAE,CAAC;AACjE;AAKO,SAAS,UACd,cACA,aACA,aACe;AACf,MAAI,gBAAgB,QAAQ,CAAC,YAAa,QAAO;AACjD,QAAM,MAAM,eAAe;AAC3B,QAAM,MAAM,eAAe;AAC3B,QAAM,OACJ,OAAO,MAAgB,IAAI,MAAM,KAAe,eAAe,QAAW,EAAE,uBAAuB,EAAE,CAAC,CAAC,MACvG,OAAO,MAAgB,IAAI,MAAM,KAAW,eAAe,QAAW,EAAE,uBAAuB,EAAE,CAAC,CAAC,MACnG,OAAO,MAAgB,IAAI,MAAM,KAAO,eAAe,QAAW,EAAE,uBAAuB,EAAE,CAAC,CAAC,MACxE,IAAI,eAAe,QAAW,EAAE,uBAAuB,EAAE,CAAC;AACnF,SAAO,MAAM,GAAG,IAAI,IAAI,GAAG,KAAK;AAClC;","names":[]}
@@ -0,0 +1,33 @@
1
+ type CoinKind = "creator" | "memecoin";
2
+ /** The fields a coin tile reads. An app's `ApiCollection` structurally satisfies
3
+ * this. `chain` is first-class — rendered as a badge; nothing assumes Starknet. */
4
+ interface CoinCollectionLike {
5
+ contractAddress: string;
6
+ chain?: string | null;
7
+ name?: string | null;
8
+ symbol?: string | null;
9
+ image?: string | null;
10
+ service?: string | null;
11
+ claimedBy?: string | null;
12
+ holderCount?: number | null;
13
+ totalSupply?: number | null;
14
+ profile?: {
15
+ image?: string | null;
16
+ } | null;
17
+ }
18
+ /** Minimal spot-price shape — the concrete read (Ekubo on Starknet, a DEX on
19
+ * another chain) is injected by the app and structurally satisfies this. */
20
+ interface CoinPriceLike {
21
+ quotePerCoin: number;
22
+ quoteSymbol: string | null;
23
+ }
24
+ /** Native creator coin vs claimed external memecoin. */
25
+ declare function coinKind(service: string | null | undefined): CoinKind;
26
+ /** Format a quote-per-coin spot price. */
27
+ declare function formatCoinPrice(n: number): string;
28
+ /** Fully-diluted value = price × supply, abbreviated, in the quote symbol.
29
+ * Returns null when price/supply is unknown or zero (external coins aren't
30
+ * supply-indexed, so price × 0 must read "—", never "0"). */
31
+ declare function formatFdv(quotePerCoin: number | null | undefined, totalSupply: number | null | undefined, quoteSymbol: string | null | undefined): string | null;
32
+
33
+ export { type CoinCollectionLike, type CoinKind, type CoinPriceLike, coinKind, formatCoinPrice, formatFdv };
@@ -0,0 +1,33 @@
1
+ type CoinKind = "creator" | "memecoin";
2
+ /** The fields a coin tile reads. An app's `ApiCollection` structurally satisfies
3
+ * this. `chain` is first-class — rendered as a badge; nothing assumes Starknet. */
4
+ interface CoinCollectionLike {
5
+ contractAddress: string;
6
+ chain?: string | null;
7
+ name?: string | null;
8
+ symbol?: string | null;
9
+ image?: string | null;
10
+ service?: string | null;
11
+ claimedBy?: string | null;
12
+ holderCount?: number | null;
13
+ totalSupply?: number | null;
14
+ profile?: {
15
+ image?: string | null;
16
+ } | null;
17
+ }
18
+ /** Minimal spot-price shape — the concrete read (Ekubo on Starknet, a DEX on
19
+ * another chain) is injected by the app and structurally satisfies this. */
20
+ interface CoinPriceLike {
21
+ quotePerCoin: number;
22
+ quoteSymbol: string | null;
23
+ }
24
+ /** Native creator coin vs claimed external memecoin. */
25
+ declare function coinKind(service: string | null | undefined): CoinKind;
26
+ /** Format a quote-per-coin spot price. */
27
+ declare function formatCoinPrice(n: number): string;
28
+ /** Fully-diluted value = price × supply, abbreviated, in the quote symbol.
29
+ * Returns null when price/supply is unknown or zero (external coins aren't
30
+ * supply-indexed, so price × 0 must read "—", never "0"). */
31
+ declare function formatFdv(quotePerCoin: number | null | undefined, totalSupply: number | null | undefined, quoteSymbol: string | null | undefined): string | null;
32
+
33
+ export { type CoinCollectionLike, type CoinKind, type CoinPriceLike, coinKind, formatCoinPrice, formatFdv };
@@ -0,0 +1,22 @@
1
+ function coinKind(service) {
2
+ return service === "external-erc20" ? "memecoin" : "creator";
3
+ }
4
+ function formatCoinPrice(n) {
5
+ if (n === 0) return "0";
6
+ if (n < 1e-6) return n.toExponential(2);
7
+ if (n < 1) return n.toPrecision(3);
8
+ return n.toLocaleString(void 0, { maximumFractionDigits: 4 });
9
+ }
10
+ function formatFdv(quotePerCoin, totalSupply, quoteSymbol) {
11
+ if (quotePerCoin == null || !totalSupply) return null;
12
+ const fdv = quotePerCoin * totalSupply;
13
+ const sym = quoteSymbol ?? "";
14
+ const abbr = fdv >= 1e9 ? `${(fdv / 1e9).toLocaleString(void 0, { maximumFractionDigits: 1 })}B` : fdv >= 1e6 ? `${(fdv / 1e6).toLocaleString(void 0, { maximumFractionDigits: 1 })}M` : fdv >= 1e3 ? `${(fdv / 1e3).toLocaleString(void 0, { maximumFractionDigits: 1 })}K` : fdv.toLocaleString(void 0, { maximumFractionDigits: 2 });
15
+ return sym ? `${abbr} ${sym}` : abbr;
16
+ }
17
+ export {
18
+ coinKind,
19
+ formatCoinPrice,
20
+ formatFdv
21
+ };
22
+ //# sourceMappingURL=coins.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/data/coins.ts"],"sourcesContent":["// Coin discovery — pure, dependency-free helpers + the structural shapes the\n// coin components read. No @medialane/sdk import: ui types shared code\n// structurally (same pattern as CollectionCard's `ApiCollection & {…}`), so a\n// coin on any chain works without bumping ui's SDK. Registry-dependent logic\n// (isCoinCollection / COIN_SERVICE_IDS, price reads) lives in the apps and is\n// injected — see the CoinsExplorer/CoinCard props.\n\nexport type CoinKind = \"creator\" | \"memecoin\";\n\n/** The fields a coin tile reads. An app's `ApiCollection` structurally satisfies\n * this. `chain` is first-class — rendered as a badge; nothing assumes Starknet. */\nexport interface CoinCollectionLike {\n contractAddress: string;\n chain?: string | null;\n name?: string | null;\n symbol?: string | null;\n image?: string | null;\n service?: string | null;\n claimedBy?: string | null;\n holderCount?: number | null;\n totalSupply?: number | null;\n profile?: { image?: string | null } | null;\n}\n\n/** Minimal spot-price shape — the concrete read (Ekubo on Starknet, a DEX on\n * another chain) is injected by the app and structurally satisfies this. */\nexport interface CoinPriceLike {\n quotePerCoin: number;\n quoteSymbol: string | null;\n}\n\n/** Native creator coin vs claimed external memecoin. */\nexport function coinKind(service: string | null | undefined): CoinKind {\n return service === \"external-erc20\" ? \"memecoin\" : \"creator\";\n}\n\n/** Format a quote-per-coin spot price. */\nexport function formatCoinPrice(n: number): string {\n if (n === 0) return \"0\";\n if (n < 0.000001) return n.toExponential(2);\n if (n < 1) return n.toPrecision(3);\n return n.toLocaleString(undefined, { maximumFractionDigits: 4 });\n}\n\n/** Fully-diluted value = price × supply, abbreviated, in the quote symbol.\n * Returns null when price/supply is unknown or zero (external coins aren't\n * supply-indexed, so price × 0 must read \"—\", never \"0\"). */\nexport function formatFdv(\n quotePerCoin: number | null | undefined,\n totalSupply: number | null | undefined,\n quoteSymbol: string | null | undefined\n): string | null {\n if (quotePerCoin == null || !totalSupply) return null;\n const fdv = quotePerCoin * totalSupply;\n const sym = quoteSymbol ?? \"\";\n const abbr =\n fdv >= 1_000_000_000 ? `${(fdv / 1_000_000_000).toLocaleString(undefined, { maximumFractionDigits: 1 })}B` :\n fdv >= 1_000_000 ? `${(fdv / 1_000_000).toLocaleString(undefined, { maximumFractionDigits: 1 })}M` :\n fdv >= 1_000 ? `${(fdv / 1_000).toLocaleString(undefined, { maximumFractionDigits: 1 })}K` :\n fdv.toLocaleString(undefined, { maximumFractionDigits: 2 });\n return sym ? `${abbr} ${sym}` : abbr;\n}\n"],"mappings":"AAgCO,SAAS,SAAS,SAA8C;AACrE,SAAO,YAAY,mBAAmB,aAAa;AACrD;AAGO,SAAS,gBAAgB,GAAmB;AACjD,MAAI,MAAM,EAAG,QAAO;AACpB,MAAI,IAAI,KAAU,QAAO,EAAE,cAAc,CAAC;AAC1C,MAAI,IAAI,EAAG,QAAO,EAAE,YAAY,CAAC;AACjC,SAAO,EAAE,eAAe,QAAW,EAAE,uBAAuB,EAAE,CAAC;AACjE;AAKO,SAAS,UACd,cACA,aACA,aACe;AACf,MAAI,gBAAgB,QAAQ,CAAC,YAAa,QAAO;AACjD,QAAM,MAAM,eAAe;AAC3B,QAAM,MAAM,eAAe;AAC3B,QAAM,OACJ,OAAO,MAAgB,IAAI,MAAM,KAAe,eAAe,QAAW,EAAE,uBAAuB,EAAE,CAAC,CAAC,MACvG,OAAO,MAAgB,IAAI,MAAM,KAAW,eAAe,QAAW,EAAE,uBAAuB,EAAE,CAAC,CAAC,MACnG,OAAO,MAAgB,IAAI,MAAM,KAAO,eAAe,QAAW,EAAE,uBAAuB,EAAE,CAAC,CAAC,MACxE,IAAI,eAAe,QAAW,EAAE,uBAAuB,EAAE,CAAC;AACnF,SAAO,MAAM,GAAG,IAAI,IAAI,GAAG,KAAK;AAClC;","names":[]}