11"use client" ;
22
3- import React from "react" ;
3+ import React , { useRef } from "react" ;
44import Link from "next/link" ;
5+ import { useVirtualizer } from "@tanstack/react-virtual" ;
56import { BackendStreamEvent } from "@/lib/api-types" ;
67import { formatAmount } from "@/utils/amount" ;
78import TransactionTracker from "@/components/TransactionTracker" ;
89import { Download , ExternalLink , Clock } from "lucide-react" ;
910import { Button } from "../ui/Button" ;
1011
12+ const VIRTUALIZATION_THRESHOLD = 50 ;
13+
1114interface ActivityHistoryProps {
1215 events : BackendStreamEvent [ ] ;
1316 isLoading ?: boolean ;
@@ -17,6 +20,17 @@ export const ActivityHistory: React.FC<ActivityHistoryProps> = ({
1720 events,
1821 isLoading,
1922} ) => {
23+ const parentRef = useRef < HTMLDivElement > ( null ) ;
24+ const shouldVirtualize = events . length > VIRTUALIZATION_THRESHOLD ;
25+
26+ const virtualizer = useVirtualizer ( {
27+ count : events . length ,
28+ getScrollElement : ( ) => parentRef . current ,
29+ estimateSize : ( ) => 120 ,
30+ overscan : 5 ,
31+ enabled : shouldVirtualize ,
32+ } ) ;
33+
2034 const exportToCSV = ( ) => {
2135 const headers = [
2236 "Stream ID" ,
@@ -139,55 +153,128 @@ export const ActivityHistory: React.FC<ActivityHistoryProps> = ({
139153 </ Button >
140154 </ div >
141155
142- < div className = "relative space-y-4 before:absolute before:inset-0 before:ml-5 before:-translate-x-px md:before:mx-auto md:before:translate-x-0 before:h-full before:w-0.5 before:bg-gradient-to-b before:from-transparent before:via-slate-700 before:to-transparent" >
143- { events . map ( ( event , index ) => (
156+ { shouldVirtualize ? (
157+ < div
158+ ref = { parentRef }
159+ className = "relative h-[600px] overflow-auto before:absolute before:inset-0 before:ml-5 before:-translate-x-px md:before:mx-auto md:before:translate-x-0 before:h-full before:w-0.5 before:bg-gradient-to-b before:from-transparent before:via-slate-700 before:to-transparent"
160+ >
144161 < div
145- key = { ` ${ event . id } - ${ index } ` }
146- className = "relative flex items-center justify-between md:justify-normal md:odd:flex-row-reverse group "
162+ style = { { height : virtualizer . getTotalSize ( ) } }
163+ className = "relative w-full "
147164 >
148- { /* Dot */ }
149- < div className = "flex items-center justify-center w-10 h-10 rounded-full border border-slate-700 bg-slate-900 text-accent shadow shrink-0 md:order-1 md:group-odd:-translate-x-1/2 md:group-even:translate-x-1/2" >
150- < Clock className = "h-5 w-5" />
151- </ div >
152- { /* Content Card */ }
153- < div className = "w-[calc(100%-4rem)] md:w-[calc(50%-2.5rem)] p-4 bg-white/5 border border-glass-border rounded-xl hover:bg-white/10 transition-colors shadow-xl" >
154- < div className = "flex flex-col sm:flex-row justify-between items-start mb-2 gap-2" >
155- < div >
156- < p className = "text-white font-medium text-sm sm:text-base" >
157- { renderEventMessage ( event ) }
158- </ p >
159- < time className = "text-xs text-slate-400 flex items-center gap-1 mt-1" >
160- { new Date ( event . timestamp * 1000 ) . toLocaleString ( ) }
161- </ time >
165+ { virtualizer . getVirtualItems ( ) . map ( ( virtualRow ) => {
166+ const event = events [ virtualRow . index ] ;
167+ if ( ! event ) return null ;
168+ return (
169+ < div
170+ key = { `${ event . id } -${ virtualRow . index } ` }
171+ ref = { virtualizer . measureElement }
172+ data-index = { virtualRow . index }
173+ style = { {
174+ position : "absolute" ,
175+ top : 0 ,
176+ left : 0 ,
177+ width : "100%" ,
178+ transform : `translateY(${ virtualRow . start } px)` ,
179+ } }
180+ className = "relative flex items-center justify-between md:justify-normal md:odd:flex-row-reverse group mb-4"
181+ >
182+ { /* Dot */ }
183+ < div className = "flex items-center justify-center w-10 h-10 rounded-full border border-slate-700 bg-slate-900 text-accent shadow shrink-0 md:order-1 md:group-odd:-translate-x-1/2 md:group-even:translate-x-1/2" >
184+ < Clock className = "h-5 w-5" />
185+ </ div >
186+ { /* Content Card */ }
187+ < div className = "w-[calc(100%-4rem)] md:w-[calc(50%-2.5rem)] p-4 bg-white/5 border border-glass-border rounded-xl hover:bg-white/10 transition-colors shadow-xl" >
188+ < div className = "flex flex-col sm:flex-row justify-between items-start mb-2 gap-2" >
189+ < div >
190+ < p className = "text-white font-medium text-sm sm:text-base" >
191+ { renderEventMessage ( event ) }
192+ </ p >
193+ < time className = "text-xs text-slate-400 flex items-center gap-1 mt-1" >
194+ { new Date ( event . timestamp * 1000 ) . toLocaleString ( ) }
195+ </ time >
196+ </ div >
197+ < span className = "text-[10px] uppercase tracking-wider px-2 py-1 rounded bg-accent/10 text-accent font-bold border border-accent/20" >
198+ { event . eventType }
199+ </ span >
200+ </ div >
201+
202+ { event . transactionHash && (
203+ < div className = "mt-4 pt-4 border-t border-white/5 flex items-center justify-between" >
204+ < TransactionTracker
205+ status = "confirmed"
206+ action = "withdraw"
207+ txHash = { event . transactionHash }
208+ streamId = { event . streamId . toString ( ) }
209+ />
210+ < a
211+ href = { `https://stellar.expert/explorer/testnet/tx/${ event . transactionHash } ` }
212+ target = "_blank"
213+ rel = "noopener noreferrer"
214+ className = "text-slate-500 hover:text-white transition-colors"
215+ title = "View on Explorer"
216+ >
217+ < ExternalLink className = "h-4 w-4" />
218+ </ a >
219+ </ div >
220+ ) }
221+ </ div >
162222 </ div >
163- < span className = "text-[10px] uppercase tracking-wider px-2 py-1 rounded bg-accent/10 text-accent font-bold border border-accent/20" >
164- { event . eventType }
165- </ span >
223+ ) ;
224+ } ) }
225+ </ div >
226+ </ div >
227+ ) : (
228+ < div className = "relative space-y-4 before:absolute before:inset-0 before:ml-5 before:-translate-x-px md:before:mx-auto md:before:translate-x-0 before:h-full before:w-0.5 before:bg-gradient-to-b before:from-transparent before:via-slate-700 before:to-transparent" >
229+ { events . map ( ( event , index ) => (
230+ < div
231+ key = { `${ event . id } -${ index } ` }
232+ className = "relative flex items-center justify-between md:justify-normal md:odd:flex-row-reverse group"
233+ >
234+ { /* Dot */ }
235+ < div className = "flex items-center justify-center w-10 h-10 rounded-full border border-slate-700 bg-slate-900 text-accent shadow shrink-0 md:order-1 md:group-odd:-translate-x-1/2 md:group-even:translate-x-1/2" >
236+ < Clock className = "h-5 w-5" />
166237 </ div >
167-
168- { event . transactionHash && (
169- < div className = "mt-4 pt-4 border-t border-white/5 flex items-center justify-between" >
170- < TransactionTracker
171- status = "confirmed"
172- action = "withdraw"
173- txHash = { event . transactionHash }
174- streamId = { event . streamId . toString ( ) }
175- />
176- < a
177- href = { `https://stellar.expert/explorer/testnet/tx/${ event . transactionHash } ` }
178- target = "_blank"
179- rel = "noopener noreferrer"
180- className = "text-slate-500 hover:text-white transition-colors"
181- title = "View on Explorer"
182- >
183- < ExternalLink className = "h-4 w-4" />
184- </ a >
238+ { /* Content Card */ }
239+ < div className = "w-[calc(100%-4rem)] md:w-[calc(50%-2.5rem)] p-4 bg-white/5 border border-glass-border rounded-xl hover:bg-white/10 transition-colors shadow-xl" >
240+ < div className = "flex flex-col sm:flex-row justify-between items-start mb-2 gap-2" >
241+ < div >
242+ < p className = "text-white font-medium text-sm sm:text-base" >
243+ { renderEventMessage ( event ) }
244+ </ p >
245+ < time className = "text-xs text-slate-400 flex items-center gap-1 mt-1" >
246+ { new Date ( event . timestamp * 1000 ) . toLocaleString ( ) }
247+ </ time >
248+ </ div >
249+ < span className = "text-[10px] uppercase tracking-wider px-2 py-1 rounded bg-accent/10 text-accent font-bold border border-accent/20" >
250+ { event . eventType }
251+ </ span >
185252 </ div >
186- ) }
253+
254+ { event . transactionHash && (
255+ < div className = "mt-4 pt-4 border-t border-white/5 flex items-center justify-between" >
256+ < TransactionTracker
257+ status = "confirmed"
258+ action = "withdraw"
259+ txHash = { event . transactionHash }
260+ streamId = { event . streamId . toString ( ) }
261+ />
262+ < a
263+ href = { `https://stellar.expert/explorer/testnet/tx/${ event . transactionHash } ` }
264+ target = "_blank"
265+ rel = "noopener noreferrer"
266+ className = "text-slate-500 hover:text-white transition-colors"
267+ title = "View on Explorer"
268+ >
269+ < ExternalLink className = "h-4 w-4" />
270+ </ a >
271+ </ div >
272+ ) }
273+ </ div >
187274 </ div >
188- </ div >
189- ) ) }
190- </ div >
275+ ) ) }
276+ </ div >
277+ ) }
191278
192279 { events . length === 0 && ! isLoading && (
193280 < div className = "text-center py-12 text-slate-400 bg-white/5 rounded-xl border border-dashed border-slate-700" >
0 commit comments