Skip to content

Commit f5d8d43

Browse files
committed
pretty pretty
1 parent 4d944ed commit f5d8d43

File tree

2 files changed

+1191
-172
lines changed

2 files changed

+1191
-172
lines changed

components/SponsoredFeedsTable.tsx

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
import { useState } from "react";
2+
import CopyIcon from "./icons/CopyIcon";
3+
4+
interface SponsoredFeed {
5+
name: string;
6+
priceFeedId: string;
7+
updateParameters: string;
8+
}
9+
10+
interface SponsoredFeedsTableProps {
11+
feeds: SponsoredFeed[];
12+
networkName: string;
13+
}
14+
15+
export const SponsoredFeedsTable = ({
16+
feeds,
17+
networkName,
18+
}: SponsoredFeedsTableProps) => {
19+
const [copiedId, setCopiedId] = useState<string | null>(null);
20+
21+
const copyToClipboard = (text: string) => {
22+
navigator.clipboard.writeText(text).then(() => {
23+
setCopiedId(text);
24+
setTimeout(() => setCopiedId(null), 2000);
25+
});
26+
};
27+
28+
// Calculate parameter statistics
29+
const paramCounts = feeds.reduce((acc, feed) => {
30+
acc[feed.updateParameters] = (acc[feed.updateParameters] || 0) + 1;
31+
return acc;
32+
}, {} as Record<string, number>);
33+
34+
const defaultParams = Object.entries(paramCounts).sort(
35+
([, a], [, b]) => b - a
36+
)[0][0];
37+
38+
// Calculate table height based on number of items
39+
// Each row is approximately 40px (py-2 = 8px top + 8px bottom + content height)
40+
// Header is approximately 48px (py-2 = 8px top + 8px bottom + font height)
41+
// Show 7 rows by default, then scroll
42+
const maxVisibleRows = 7;
43+
const shouldScroll = feeds.length > maxVisibleRows;
44+
const tableHeight = shouldScroll ? `${48 + maxVisibleRows * 40}px` : "auto";
45+
46+
return (
47+
<div className="my-6">
48+
<p className="mb-3">
49+
The price feeds listed in the table below are currently sponsored in{" "}
50+
<strong>{networkName}</strong>.
51+
</p>
52+
53+
<div className="border border-gray-200 dark:border-gray-700 rounded-lg">
54+
{/* Summary bar */}
55+
<div className="bg-blue-50 dark:bg-blue-900/20 px-3 py-2 border-b border-gray-200 dark:border-gray-600">
56+
<div className="flex flex-wrap items-center gap-4 text-sm">
57+
<div className="flex items-center gap-1.5">
58+
<div className="w-1.5 h-1.5 bg-green-500 rounded-full flex-shrink-0"></div>
59+
<span className="font-medium">Default:</span>
60+
<span
61+
dangerouslySetInnerHTML={{
62+
__html: defaultParams.replace("<br/>", " / "),
63+
}}
64+
/>
65+
<span className="text-gray-500">
66+
({paramCounts[defaultParams]})
67+
</span>
68+
</div>
69+
{Object.entries(paramCounts)
70+
.filter(([params]) => params !== defaultParams)
71+
.map(([params, count]) => (
72+
<div key={params} className="flex items-center gap-1.5">
73+
<div className="w-1.5 h-1.5 bg-orange-500 rounded-full flex-shrink-0"></div>
74+
<span className="font-medium">Exception:</span>
75+
<span
76+
dangerouslySetInnerHTML={{
77+
__html: params.replace("<br/>", " / "),
78+
}}
79+
/>
80+
<span className="text-gray-500">({count})</span>
81+
</div>
82+
))}
83+
</div>
84+
</div>
85+
86+
{/* Table */}
87+
<div className="overflow-x-auto">
88+
<div
89+
className={`${shouldScroll ? "overflow-y-auto" : ""}`}
90+
style={{ height: tableHeight }}
91+
>
92+
<table className="w-full text-sm min-w-full">
93+
<thead className="sticky top-0 bg-gray-50 dark:bg-gray-800 z-30">
94+
<tr>
95+
<th className="text-left px-3 py-2 font-semibold text-gray-900 dark:text-gray-100 border-b border-gray-200 dark:border-gray-600 min-w-[100px]">
96+
Name
97+
</th>
98+
<th className="text-left px-3 py-2 font-semibold text-gray-900 dark:text-gray-100 border-b border-gray-200 dark:border-gray-600 min-w-[400px]">
99+
Price Feed Id
100+
</th>
101+
<th className="text-left px-3 py-2 font-semibold text-gray-900 dark:text-gray-100 border-b border-gray-200 dark:border-gray-600 min-w-[200px]">
102+
Update Parameters
103+
</th>
104+
</tr>
105+
</thead>
106+
<tbody className="bg-white dark:bg-gray-900">
107+
{feeds.map((feed, index) => {
108+
const isDefault = feed.updateParameters === defaultParams;
109+
const prevFeed = feeds[index - 1];
110+
const isFirstInGroup =
111+
!prevFeed ||
112+
prevFeed.updateParameters !== feed.updateParameters;
113+
114+
return (
115+
<tr
116+
key={feed.priceFeedId}
117+
className={`border-b border-gray-100 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-800/30 ${
118+
isFirstInGroup
119+
? "sticky top-12 bg-white dark:bg-gray-900 z-20 shadow-sm"
120+
: ""
121+
}`}
122+
>
123+
<td className="px-3 py-2 align-top">
124+
<span className="font-medium text-gray-900 dark:text-gray-100">
125+
{feed.name}
126+
</span>
127+
</td>
128+
<td className="px-3 py-2 align-top">
129+
<div className="flex items-start gap-2">
130+
<code className="text-xs font-mono text-gray-600 dark:text-gray-400 flex-1 break-all leading-relaxed">
131+
{feed.priceFeedId}
132+
</code>
133+
<button
134+
onClick={() => copyToClipboard(feed.priceFeedId)}
135+
className="p-1 hover:bg-gray-200 dark:hover:bg-gray-600 rounded flex-shrink-0 mt-0.5"
136+
title="Copy Price Feed ID"
137+
>
138+
{copiedId === feed.priceFeedId ? (
139+
<span className="text-green-500 text-xs font-bold">
140+
141+
</span>
142+
) : (
143+
<CopyIcon className="w-3 h-3 text-gray-400" />
144+
)}
145+
</button>
146+
</div>
147+
</td>
148+
<td className="px-3 py-2 align-top">
149+
{isFirstInGroup ? (
150+
<div className="flex items-start gap-1.5">
151+
<div
152+
className={`w-1.5 h-1.5 rounded-full mt-1 flex-shrink-0 ${
153+
isDefault ? "bg-green-500" : "bg-orange-500"
154+
}`}
155+
></div>
156+
<span
157+
className={`text-xs leading-relaxed font-medium ${
158+
isDefault
159+
? "text-gray-700 dark:text-gray-300"
160+
: "text-orange-600 dark:text-orange-400"
161+
}`}
162+
dangerouslySetInnerHTML={{
163+
__html: feed.updateParameters,
164+
}}
165+
/>
166+
</div>
167+
) : (
168+
<div className="flex items-start gap-1.5 text-gray-400 text-xs">
169+
<div className="w-1.5 h-1.5 bg-green-500 rounded-full mt-1 flex-shrink-0"></div>
170+
<span>Same as above</span>
171+
</div>
172+
)}
173+
</td>
174+
</tr>
175+
);
176+
})}
177+
</tbody>
178+
</table>
179+
</div>
180+
</div>
181+
182+
{/* Show count indicator when scrolling is needed */}
183+
{shouldScroll && (
184+
<div className="px-3 py-1 bg-gray-50 dark:bg-gray-800 border-t border-gray-200 dark:border-gray-600 text-xs text-gray-500 text-center">
185+
Showing {maxVisibleRows} of {feeds.length} feeds • Scroll to see
186+
more
187+
</div>
188+
)}
189+
</div>
190+
</div>
191+
);
192+
};

0 commit comments

Comments
 (0)