Skip to content

Commit e9cd9e0

Browse files
committed
feat: search by txn hash in UI
1 parent 6b91966 commit e9cd9e0

File tree

1 file changed

+104
-15
lines changed

1 file changed

+104
-15
lines changed

ui/src/app/bundles/page.tsx

Lines changed: 104 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ export default function BundlesPage() {
99
const [allBundles, setAllBundles] = useState<string[]>([]);
1010
const [loading, setLoading] = useState(true);
1111
const [error, setError] = useState<string | null>(null);
12+
const [searchHash, setSearchHash] = useState<string>("");
13+
const [filteredLiveBundles, setFilteredLiveBundles] = useState<Bundle[]>([]);
14+
const [filteredAllBundles, setFilteredAllBundles] = useState<string[]>([]);
15+
const [searchLoading, setSearchLoading] = useState(false);
1216

1317
useEffect(() => {
1418
const fetchLiveBundles = async () => {
@@ -56,6 +60,59 @@ export default function BundlesPage() {
5660
return () => clearInterval(interval);
5761
}, []);
5862

63+
useEffect(() => {
64+
const filterBundles = async () => {
65+
if (!searchHash.trim()) {
66+
setFilteredLiveBundles(liveBundles);
67+
setFilteredAllBundles(allBundles);
68+
return;
69+
}
70+
71+
setSearchLoading(true);
72+
73+
try {
74+
const liveBundlesWithTx = liveBundles.filter(bundle =>
75+
bundle.txnHashes?.some(hash =>
76+
hash.toLowerCase().includes(searchHash.toLowerCase())
77+
)
78+
);
79+
80+
const response = await fetch(`/api/txn/${searchHash.trim()}`);
81+
82+
if (response.ok) {
83+
const txnData = await response.json();
84+
const bundleIds = txnData.bundle_ids || [];
85+
86+
const allBundlesWithTx = allBundles.filter(bundleId =>
87+
bundleIds.includes(bundleId)
88+
);
89+
90+
setFilteredLiveBundles(liveBundlesWithTx);
91+
setFilteredAllBundles(allBundlesWithTx);
92+
} else {
93+
setFilteredLiveBundles(liveBundles.filter(bundle =>
94+
bundle.txnHashes?.some(hash =>
95+
hash.toLowerCase().includes(searchHash.toLowerCase())
96+
)
97+
));
98+
setFilteredAllBundles([]);
99+
}
100+
} catch (err) {
101+
console.error("Error filtering bundles:", err);
102+
setFilteredLiveBundles(liveBundles.filter(bundle =>
103+
bundle.txnHashes?.some(hash =>
104+
hash.toLowerCase().includes(searchHash.toLowerCase())
105+
)
106+
));
107+
setFilteredAllBundles([]);
108+
}
109+
110+
setSearchLoading(false);
111+
};
112+
113+
filterBundles();
114+
}, [searchHash, liveBundles, allBundles]);
115+
59116
if (loading) {
60117
return (
61118
<div className="flex flex-col gap-4 p-8">
@@ -68,18 +125,40 @@ export default function BundlesPage() {
68125
return (
69126
<div className="flex flex-col gap-8 p-8">
70127
<div className="flex flex-col gap-2">
71-
<h1 className="text-2xl font-bold">BundleStore (fka Mempool)</h1>
128+
<div className="flex items-center justify-between">
129+
<h1 className="text-2xl font-bold">BundleStore (fka Mempool)</h1>
130+
<div className="flex items-center gap-2">
131+
<input
132+
type="text"
133+
placeholder="Search by transaction hash..."
134+
value={searchHash}
135+
onChange={(e) => setSearchHash(e.target.value)}
136+
className="px-3 py-2 border rounded-lg bg-white/5 border-gray-300 dark:border-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent placeholder-gray-500 dark:placeholder-gray-400 text-sm min-w-[300px]"
137+
/>
138+
{searchLoading && (
139+
<div className="text-sm text-gray-500 animate-pulse">Searching...</div>
140+
)}
141+
</div>
142+
</div>
72143
{error && (
73144
<div className="text-sm text-red-600 dark:text-red-400">{error}</div>
74145
)}
75146
</div>
76147

77148
<div className="flex flex-col gap-6">
78-
<section>
79-
<h2 className="text-xl font-semibold mb-4">Live Bundles</h2>
80-
{liveBundles.length > 0 ? (
81-
<ul className="space-y-2">
82-
{liveBundles.map((bundle) => (
149+
{(filteredLiveBundles.length > 0 || !searchHash.trim()) && (
150+
<section>
151+
<h2 className="text-xl font-semibold mb-4">
152+
Live Bundles
153+
{searchHash.trim() && (
154+
<span className="text-sm font-normal text-gray-500 ml-2">
155+
({filteredLiveBundles.length} found)
156+
</span>
157+
)}
158+
</h2>
159+
{filteredLiveBundles.length > 0 ? (
160+
<ul className="space-y-2">
161+
{filteredLiveBundles.map((bundle) => (
83162
<li key={bundle.id}>
84163
<Link
85164
href={`/bundles/${bundle.id}`}
@@ -110,16 +189,25 @@ export default function BundlesPage() {
110189
</ul>
111190
) : (
112191
<p className="text-gray-600 dark:text-gray-400">
113-
No live bundles found.
192+
{searchHash.trim() ? "No live bundles found matching this transaction hash." : "No live bundles found."}
114193
</p>
115194
)}
116-
</section>
195+
</section>
196+
)}
117197

118-
<section>
119-
<h2 className="text-xl font-semibold mb-4">All Bundles</h2>
120-
{allBundles.length > 0 ? (
121-
<ul className="space-y-2">
122-
{allBundles.map((bundleId) => (
198+
{(filteredAllBundles.length > 0 || !searchHash.trim()) && (
199+
<section>
200+
<h2 className="text-xl font-semibold mb-4">
201+
All Bundles
202+
{searchHash.trim() && (
203+
<span className="text-sm font-normal text-gray-500 ml-2">
204+
({filteredAllBundles.length} found)
205+
</span>
206+
)}
207+
</h2>
208+
{filteredAllBundles.length > 0 ? (
209+
<ul className="space-y-2">
210+
{filteredAllBundles.map((bundleId) => (
123211
<li key={bundleId}>
124212
<Link
125213
href={`/bundles/${bundleId}`}
@@ -132,10 +220,11 @@ export default function BundlesPage() {
132220
</ul>
133221
) : (
134222
<p className="text-gray-600 dark:text-gray-400">
135-
No bundles found in S3.
223+
{searchHash.trim() ? "No bundles found in S3 matching this transaction hash." : "No bundles found in S3."}
136224
</p>
137225
)}
138-
</section>
226+
</section>
227+
)}
139228
</div>
140229
</div>
141230
);

0 commit comments

Comments
 (0)