11import './CLNTransactionsList.scss' ;
2- import { useState } from 'react' ;
2+ import { useCallback , useEffect , useRef , useState } from 'react' ;
33import { motion , AnimatePresence } from 'framer-motion' ;
44import { Row , Col , Spinner , Alert } from 'react-bootstrap' ;
5-
5+ import PerfectScrollbar from 'react-perfect-scrollbar' ;
66import { formatCurrency } from '../../../utilities/data-formatters' ;
77import { IncomingArrowSVG } from '../../../svgs/IncomingArrow' ;
88import { OutgoingArrowSVG } from '../../../svgs/OutgoingArrow' ;
99import DateBox from '../../shared/DateBox/DateBox' ;
1010import FiatBox from '../../shared/FiatBox/FiatBox' ;
1111import Transaction from '../CLNTransaction/CLNTransaction' ;
12- import { TRANSITION_DURATION , Units } from '../../../utilities/constants' ;
12+ import { TRANSITION_DURATION , Units , TODAY , SCROLL_BATCH_SIZE , SCROLL_THRESHOLD } from '../../../utilities/constants' ;
1313import { NoCLNTransactionLightSVG } from '../../../svgs/NoCLNTransactionLight' ;
1414import { NoCLNTransactionDarkSVG } from '../../../svgs/NoCLNTransactionDark' ;
1515import { useSelector } from 'react-redux' ;
1616import { selectActiveChannelsExist , selectFiatConfig , selectFiatUnit , selectIsAuthenticated , selectIsDarkMode , selectUIConfigUnit } from '../../../store/rootSelectors' ;
1717import { selectListLightningTransactions } from '../../../store/clnSelectors' ;
1818
19- const TODAY = Math . floor ( Date . now ( ) / 1000 ) ;
20-
2119const PaymentHeader = ( { payment } ) => {
2220 const fiatUnit = useSelector ( selectFiatUnit ) ;
2321 const uiConfigUnit = useSelector ( selectUIConfigUnit ) ;
@@ -213,6 +211,69 @@ export const CLNTransactionsList = () => {
213211 const listLightningTransactions = useSelector ( selectListLightningTransactions ) ;
214212 const initExpansions = ( listLightningTransactions . clnTransactions ?. reduce ( ( acc : boolean [ ] ) => [ ...acc , false ] , [ ] ) || [ ] ) ;
215213 const [ expanded , setExpanded ] = useState < boolean [ ] > ( initExpansions ) ;
214+
215+ const [ displayedTransactions , setDisplayedTransactions ] = useState < any [ ] > ( [ ] ) ;
216+ const [ currentIndex , setCurrentIndex ] = useState ( 0 ) ;
217+ const [ isLoading , setIsLoading ] = useState ( false ) ;
218+ const [ allTransactionsLoaded , setAllTransactionsLoaded ] = useState ( false ) ;
219+ const containerRef = useRef < HTMLDivElement > ( null ) ;
220+
221+ const setContainerRef = useCallback ( ( ref : HTMLElement | null ) => {
222+ if ( ref ) {
223+ ( containerRef as React . MutableRefObject < HTMLElement | null > ) . current = ref ;
224+ }
225+ } , [ ] ) ;
226+
227+ useEffect ( ( ) => {
228+ if ( listLightningTransactions ?. clnTransactions ?. length > 0 ) {
229+ const initialBatch = listLightningTransactions . clnTransactions . slice ( 0 , SCROLL_BATCH_SIZE ) ;
230+ setDisplayedTransactions ( initialBatch ) ;
231+ setCurrentIndex ( SCROLL_BATCH_SIZE ) ;
232+ if ( SCROLL_BATCH_SIZE >= listLightningTransactions . clnTransactions . length ) {
233+ setAllTransactionsLoaded ( true ) ;
234+ }
235+ }
236+ } , [ listLightningTransactions ] ) ;
237+
238+ const loadMoreTransactions = useCallback ( ( ) => {
239+ if ( isLoading || allTransactionsLoaded ) return ;
240+ setIsLoading ( true ) ;
241+ setTimeout ( ( ) => {
242+ const nextIndex = currentIndex + SCROLL_BATCH_SIZE ;
243+ const newTransactions = listLightningTransactions . clnTransactions . slice (
244+ currentIndex ,
245+ nextIndex
246+ ) ;
247+ setDisplayedTransactions ( prev => [ ...prev , ...newTransactions ] ) ;
248+ setCurrentIndex ( nextIndex ) ;
249+
250+ if ( nextIndex >= listLightningTransactions . clnTransactions . length ) {
251+ setAllTransactionsLoaded ( true ) ;
252+ }
253+
254+ setIsLoading ( false ) ;
255+ } , 300 ) ;
256+ } , [ currentIndex , isLoading , allTransactionsLoaded , listLightningTransactions ] ) ;
257+
258+ const handleScroll = useCallback ( ( container ) => {
259+ if ( ! container || isLoading || allTransactionsLoaded ) return ;
260+
261+ const { scrollTop, scrollHeight, clientHeight } = container ;
262+ const bottomOffset = scrollHeight - scrollTop - clientHeight ;
263+
264+ if ( bottomOffset < SCROLL_THRESHOLD ) {
265+ loadMoreTransactions ( ) ;
266+ }
267+ } , [ isLoading , allTransactionsLoaded , loadMoreTransactions ] ) ;
268+
269+ useEffect ( ( ) => {
270+ const container = containerRef . current ;
271+ if ( container ) {
272+ container ?. addEventListener ( 'scroll' , handleScroll ) ;
273+ return ( ) => container ?. removeEventListener ( 'scroll' , handleScroll ) ;
274+ }
275+ } , [ handleScroll ] ) ;
276+
216277 return (
217278 isAuthenticated && listLightningTransactions . isLoading ?
218279 < span className = 'h-100 d-flex justify-content-center align-items-center' >
@@ -221,29 +282,44 @@ export const CLNTransactionsList = () => {
221282 :
222283 listLightningTransactions . error ?
223284 < Alert className = 'py-0 px-1 fs-7' variant = 'danger' data-testid = 'cln-transactions-list-error' > { listLightningTransactions . error } </ Alert > :
224- listLightningTransactions ?. clnTransactions && listLightningTransactions ?. clnTransactions . length && listLightningTransactions ?. clnTransactions . length > 0 ?
225- < div className = 'cln-transactions-list' data-testid = 'cln-transactions-list' >
226- {
227- listLightningTransactions ?. clnTransactions ?. map ( ( transaction , i ) => (
228- < CLNTransactionsAccordion key = { i } i = { i } expanded = { expanded } setExpanded = { setExpanded } initExpansions = { initExpansions } transaction = { transaction } />
229- ) )
285+ listLightningTransactions ?. clnTransactions && listLightningTransactions ?. clnTransactions . length && listLightningTransactions ?. clnTransactions . length > 0 ?
286+ < PerfectScrollbar
287+ containerRef = { setContainerRef }
288+ onScrollY = { handleScroll }
289+ className = 'cln-transactions-list'
290+ data-testid = 'cln-transactions-list'
291+ options = { {
292+ suppressScrollX : true ,
293+ wheelPropagation : false
294+ } }
295+ >
296+ { displayedTransactions . map ( ( transaction , i ) => (
297+ < CLNTransactionsAccordion key = { i } i = { i } expanded = { expanded } setExpanded = { setExpanded } initExpansions = { initExpansions } transaction = { transaction } />
298+ ) ) }
299+ { isLoading && (
300+ < Col xs = { 12 } className = 'd-flex align-items-center justify-content-center mb-5' >
301+ < Spinner animation = 'grow' variant = 'primary' />
302+ </ Col >
303+ ) }
304+ { allTransactionsLoaded && listLightningTransactions ?. clnTransactions . length > 100 &&
305+ < h6 className = 'd-flex align-self-center py-4 text-muted' > No more transactions to load!</ h6 >
306+ }
307+ </ PerfectScrollbar >
308+ :
309+ < Row className = 'text-light fs-6 h-75 mt-5 align-items-center justify-content-center' >
310+ < Row className = 'd-flex align-items-center justify-content-center mt-2' >
311+ { isDarkMode ?
312+ < NoCLNTransactionDarkSVG className = 'no-clntx-dark pb-1' /> :
313+ < NoCLNTransactionLightSVG className = 'no-clntx-light pb-1' />
230314 }
231- </ div >
232- :
233- < Row className = 'text-light fs-6 h-75 mt-5 align-items-center justify-content-center' >
234- < Row className = 'd-flex align-items-center justify-content-center mt-2' >
235- { isDarkMode ?
236- < NoCLNTransactionDarkSVG className = 'no-clntx-dark pb-1' /> :
237- < NoCLNTransactionLightSVG className = 'no-clntx-light pb-1' />
315+ < Row className = 'text-center' >
316+ { activeChannelsExist ?
317+ 'No transaction found. Click send/receive to start!' :
318+ 'No transaction found. Open channel to start!'
238319 }
239- < Row className = 'text-center' >
240- { activeChannelsExist ?
241- 'No transaction found. Click send/receive to start!' :
242- 'No transaction found. Open channel to start!'
243- }
244- </ Row >
245320 </ Row >
246321 </ Row >
322+ </ Row >
247323 ) ;
248324} ;
249325
0 commit comments