@@ -2,21 +2,17 @@ import PlusIcon from '@/assets/plus'
22import SyncIcon from '@/assets/sync'
33import { API_URL } from '@/env'
44import { useGoBack } from '@/hooks/useGoBack'
5- import { useMutation , useQuery , useQueryClient } from '@tanstack/react-query'
5+ import { useInfiniteQuery , useMutation , useQuery , useQueryClient } from '@tanstack/react-query'
66import { formatDistance } from 'date-fns'
7- import { useState } from 'react'
7+ import { useEffect , useRef , useState } from 'react'
88import { TSubscription } from '../types'
99import Layout from './Layout'
1010import { NewSubscription , Subscription } from './NewsSource'
1111import Spinner from './Spinner'
1212
13- const mutationFn = async ( {
14- methodName,
15- params,
16- } : {
17- methodName : string
18- params ?: { [ key : string ] : string | number }
19- } ) => {
13+ const PAGE_LIMIT = 10
14+
15+ async function query < T , U > ( name : string , params : T ) : Promise < U > {
2016 if ( ! window . Telegram . WebApp . initData ) {
2117 throw new Error ( 'Telegram is not available' )
2218 }
@@ -28,7 +24,7 @@ const mutationFn = async ({
2824 } ,
2925 body : JSON . stringify ( {
3026 jsonrpc : '2.0' ,
31- method : methodName ,
27+ method : name ,
3228 params : params ?? { } ,
3329 id : 1 ,
3430 } ) ,
@@ -40,7 +36,28 @@ const mutationFn = async ({
4036 return data . result
4137}
4238
43- const queryFn = ( name : string , params ?: { [ key : string ] : string | number } ) => async ( ) => {
39+ const queryFn =
40+ ( name : string ) =>
41+ async ( {
42+ pageParam,
43+ } : {
44+ pageParam : {
45+ offset : number
46+ limit : number
47+ }
48+ } ) : Promise < { total : number ; items : TSubscription [ ] | null | undefined } > =>
49+ query <
50+ { offset : number ; limit : number } ,
51+ { total : number ; items : TSubscription [ ] | null | undefined }
52+ > ( name , pageParam )
53+
54+ const mutationFn = async ( {
55+ methodName,
56+ params,
57+ } : {
58+ methodName : string
59+ params ?: { [ key : string ] : string | number }
60+ } ) => {
4461 if ( ! window . Telegram . WebApp . initData ) {
4562 throw new Error ( 'Telegram is not available' )
4663 }
@@ -52,7 +69,7 @@ const queryFn = (name: string, params?: { [key: string]: string | number }) => a
5269 } ,
5370 body : JSON . stringify ( {
5471 jsonrpc : '2.0' ,
55- method : name ,
72+ method : methodName ,
5673 params : params ?? { } ,
5774 id : 1 ,
5875 } ) ,
@@ -67,22 +84,61 @@ const queryFn = (name: string, params?: { [key: string]: string | number }) => a
6784const NewsMonitor = ( ) => {
6885 const queryClient = useQueryClient ( )
6986 const [ showNewSubscriptionForm , setShowNewSubscriptionForm ] = useState ( false )
70-
71- const { data : subscriptions , isPending } = useQuery < { items : TSubscription [ ] ; total : number } > ( {
87+ const sentinelRef = useRef < HTMLDivElement | null > ( null )
88+ const observerRef = useRef < IntersectionObserver | null > ( null )
89+ const {
90+ data : subscriptions ,
91+ // error,
92+ fetchNextPage,
93+ hasNextPage,
94+ isFetching,
95+ isFetchingNextPage,
96+ // status,
97+ } = useInfiniteQuery ( {
7298 queryKey : [ 'subscriptions' ] ,
73- queryFn : queryFn ( 'getSubscriptions' , {
99+ queryFn : queryFn ( 'getSubscriptions' ) ,
100+ initialPageParam : {
74101 offset : 0 ,
75- limit : 100 ,
76- } ) ,
102+ limit : PAGE_LIMIT ,
103+ } ,
104+ getNextPageParam : ( lastPage , __ , lastPageParam ) => {
105+ if ( lastPage . total <= lastPageParam . offset + lastPageParam . limit ) return
106+ return {
107+ offset : lastPageParam . offset + PAGE_LIMIT ,
108+ limit : PAGE_LIMIT ,
109+ }
110+ } ,
77111 } )
78112
79113 const { data : nextScanOfSubscriptions , isPending : isPendingNextScanOfSubscriptions } = useQuery < {
80114 nextScanAt : string
81115 } > ( {
82116 queryKey : [ 'nextScanOfSubscriptions' ] ,
83- queryFn : queryFn ( 'getNextScanOfSubscriptions' ) ,
117+ queryFn : ( ) => query < null , { nextScanAt : string } > ( 'getNextScanOfSubscriptions' , null ) ,
84118 } )
85119
120+ useEffect ( ( ) => {
121+ const sentinelEl = sentinelRef . current
122+ if ( ! sentinelEl ) return
123+ observerRef . current ?. disconnect ( )
124+
125+ observerRef . current = new IntersectionObserver (
126+ ( [ entry ] ) => {
127+ if ( entry . isIntersecting && ! isFetchingNextPage && hasNextPage ) fetchNextPage ( )
128+ } ,
129+ {
130+ root : null ,
131+ rootMargin : '0px' ,
132+ threshold : 0.1 ,
133+ }
134+ )
135+
136+ observerRef . current . observe ( sentinelEl )
137+ return ( ) => {
138+ observerRef . current ?. disconnect ( )
139+ }
140+ } , [ isFetchingNextPage , fetchNextPage , hasNextPage ] )
141+
86142 const handleUpdateSubscription = useMutation ( {
87143 mutationFn,
88144 onSuccess : ( ) => {
@@ -124,15 +180,23 @@ const NewsMonitor = () => {
124180 onClick = { ( ) => setShowNewSubscriptionForm ( true ) }
125181 disabled = { showNewSubscriptionForm }
126182 >
127- { isPending ? < Spinner /> : < PlusIcon /> }
183+ { isFetching ? < Spinner /> : < PlusIcon /> }
128184 </ button >
129185 </ div >
130186 { showNewSubscriptionForm ? (
131187 < NewSubscription onClose = { ( ) => setShowNewSubscriptionForm ( false ) } />
132188 ) : null }
133- { subscriptions ?. items . map ( ( newsSource ) => (
134- < Subscription key = { newsSource . id } subscription = { newsSource } />
135- ) ) }
189+ { subscriptions ?. pages . map ( ( group ) =>
190+ group ?. items ?. map ( ( newsSource ) => (
191+ < Subscription key = { newsSource . id } subscription = { newsSource } />
192+ ) )
193+ ) }
194+ < div ref = { sentinelRef } />
195+ { hasNextPage ? (
196+ < div className = "flex h-10 w-full justify-center" >
197+ { isFetching || isFetchingNextPage ? < Spinner /> : null }
198+ </ div >
199+ ) : null }
136200 </ div >
137201 </ Layout >
138202 )
0 commit comments