@@ -170,21 +170,25 @@ function Sessions({ uuid }: { uuid: string }) {
170170 const [ next , setNext ] = useState < string | null > ( null ) ;
171171 const [ historyStart , setHistoryStart ] = useState < number | null > ( null ) ;
172172 const [ state , setState ] = useState < "loading" | "ready" | "more" > ( "loading" ) ;
173- const [ error , setError ] = useState < string | null > ( null ) ;
173+ const [ failure , setFailure ] = useState < Failure | null > ( null ) ;
174174
175175 const fetchPage = useCallback (
176176 async ( before : string | null ) => {
177177 setState ( before ? "more" : "loading" ) ;
178- setError ( null ) ;
178+ setFailure ( null ) ;
179179 try {
180180 const page = await getPlayerSessions ( uuid , before ) ;
181181 const notice = coreProtectMessage ( page . coreprotect ) ;
182- if ( notice ) setError ( notice ) ;
182+ // An unavailable page has no cursor: keep what is shown so the same page can be retried.
183+ if ( notice ) {
184+ setFailure ( { message : notice , before } ) ;
185+ return ;
186+ }
183187 setRows ( ( old ) => ( before ? [ ...old , ...page . sessions ] : page . sessions ) ) ;
184188 setNext ( page . next ) ;
185189 if ( page . history_start !== undefined ) setHistoryStart ( page . history_start ?? null ) ;
186190 } catch ( err ) {
187- setError ( adminErrorMessage ( err ) ) ;
191+ setFailure ( { message : adminErrorMessage ( err ) , before } ) ;
188192 } finally {
189193 setState ( "ready" ) ;
190194 }
@@ -203,12 +207,7 @@ function Sessions({ uuid }: { uuid: string }) {
203207 < p className = "mt-1 text-xs text-[var(--tfmc-stone)]" > Session records go back to { formatEpoch ( historyStart ) } .</ p >
204208 ) : null }
205209 { state === "loading" ? < p className = { `mt-3 ${ mutedClass } ` } > Loading…</ p > : null }
206- { error ? (
207- < p className = { errorClass } role = "alert" >
208- { error }
209- </ p >
210- ) : null }
211- { state !== "loading" && ! rows . length && ! error ? < p className = { `mt-3 ${ mutedClass } ` } > No sessions recorded.</ p > : null }
210+ { state !== "loading" && ! rows . length && ! failure ? < p className = { `mt-3 ${ mutedClass } ` } > No sessions recorded.</ p > : null }
212211 { rows . length ? (
213212 < div className = "mt-3 overflow-x-auto" >
214213 < table className = "w-full min-w-[34rem] text-left text-sm" >
@@ -243,15 +242,32 @@ function Sessions({ uuid }: { uuid: string }) {
243242 </ table >
244243 </ div >
245244 ) : null }
246- { next ? (
247- < button type = "button" className = { moreClass } disabled = { state === "more" } onClick = { ( ) => void fetchPage ( next ) } >
245+ { failure ? (
246+ < Retry failure = { failure } busy = { state !== "ready" } onRetry = { ( ) => void fetchPage ( failure . before ) } />
247+ ) : next ? (
248+ < button type = "button" className = { moreClass } disabled = { state !== "ready" } onClick = { ( ) => void fetchPage ( next ) } >
248249 { state === "more" ? "Loading…" : "Load more sessions" }
249250 </ button >
250251 ) : null }
251252 </ section >
252253 ) ;
253254}
254255
256+ type Failure = { message : string ; before : string | null } ;
257+
258+ function Retry ( { failure, busy, onRetry } : { failure : Failure ; busy : boolean ; onRetry : ( ) => void } ) {
259+ return (
260+ < div >
261+ < p className = { errorClass } role = "alert" >
262+ { failure . message }
263+ </ p >
264+ < button type = "button" className = { moreClass } disabled = { busy } onClick = { onRetry } >
265+ Try again
266+ </ button >
267+ </ div >
268+ ) ;
269+ }
270+
255271function describe ( entry : ActivityEntry ) {
256272 const amount = entry . amount && entry . amount > 1 ? `${ entry . amount } × ` : "" ;
257273 return (
@@ -281,25 +297,28 @@ function Activity({ uuid }: { uuid: string }) {
281297 const [ next , setNext ] = useState < string | null > ( null ) ;
282298 const [ searchedTo , setSearchedTo ] = useState < number | null > ( null ) ;
283299 const [ state , setState ] = useState < "loading" | "ready" | "more" > ( "loading" ) ;
284- const [ error , setError ] = useState < string | null > ( null ) ;
300+ const [ failure , setFailure ] = useState < Failure | null > ( null ) ;
285301 const request = useRef ( 0 ) ;
286302
287303 const fetchPage = useCallback (
288304 async ( before : string | null ) => {
289305 const id = ++ request . current ;
290306 setState ( before ? "more" : "loading" ) ;
291- setError ( null ) ;
307+ setFailure ( null ) ;
292308 try {
293309 const page = await getPlayerActivity ( uuid , { before, kinds : kinds . length ? kinds : undefined } ) ;
294310 if ( id !== request . current ) return ;
295311 const notice = coreProtectMessage ( page . coreprotect ) ;
296- if ( notice ) setError ( notice ) ;
312+ if ( notice ) {
313+ setFailure ( { message : notice , before } ) ;
314+ return ;
315+ }
297316 setEntries ( ( old ) => ( before ? [ ...old , ...page . entries ] : page . entries ) ) ;
298317 setNext ( page . next ) ;
299318 setSearchedTo ( page . searched_to ) ;
300319 if ( page . kinds . length ) setAvailable ( page . kinds ) ;
301320 } catch ( err ) {
302- if ( id === request . current ) setError ( adminErrorMessage ( err ) ) ;
321+ if ( id === request . current ) setFailure ( { message : adminErrorMessage ( err ) , before } ) ;
303322 } finally {
304323 if ( id === request . current ) setState ( "ready" ) ;
305324 }
@@ -311,8 +330,17 @@ function Activity({ uuid }: { uuid: string }) {
311330 void fetchPage ( null ) ;
312331 } , [ fetchPage ] ) ;
313332
333+ function choose ( next : string [ ] ) {
334+ // The old filter's rows and cursor must not be paged into the new one.
335+ setNext ( null ) ;
336+ setSearchedTo ( null ) ;
337+ setEntries ( [ ] ) ;
338+ setFailure ( null ) ;
339+ setKinds ( next ) ;
340+ }
341+
314342 function toggle ( kind : string ) {
315- setKinds ( ( old ) => ( old . includes ( kind ) ? old . filter ( ( k ) => k !== kind ) : [ ...old , kind ] ) ) ;
343+ choose ( kinds . includes ( kind ) ? kinds . filter ( ( k ) => k !== kind ) : [ ...kinds , kind ] ) ;
316344 }
317345
318346 const chip = ( active : boolean ) =>
@@ -327,7 +355,7 @@ function Activity({ uuid }: { uuid: string }) {
327355 < h3 className = { headingClass } > Recent activity</ h3 >
328356 < p className = "mt-1 text-xs text-[var(--tfmc-stone)]" > From CoreProtect. Chat, command arguments and sign text are not shown.</ p >
329357 < div role = "group" aria-label = "Show" className = "mt-3 flex flex-wrap gap-1.5" >
330- < button type = "button" aria-pressed = { ! kinds . length } className = { chip ( ! kinds . length ) } onClick = { ( ) => setKinds ( [ ] ) } >
358+ < button type = "button" aria-pressed = { ! kinds . length } className = { chip ( ! kinds . length ) } onClick = { ( ) => choose ( [ ] ) } >
331359 All
332360 </ button >
333361 { available . map ( ( kind ) => (
@@ -337,12 +365,7 @@ function Activity({ uuid }: { uuid: string }) {
337365 ) ) }
338366 </ div >
339367 { state === "loading" ? < p className = { `mt-3 ${ mutedClass } ` } > Loading…</ p > : null }
340- { error ? (
341- < p className = { errorClass } role = "alert" >
342- { error }
343- </ p >
344- ) : null }
345- { state !== "loading" && ! entries . length && ! error ? (
368+ { state !== "loading" && ! entries . length && ! failure ? (
346369 < p className = { `mt-3 ${ mutedClass } ` } > { searchedTo ? "Nothing yet." : "No activity recorded." } </ p >
347370 ) : null }
348371 { entries . length ? (
@@ -361,11 +384,13 @@ function Activity({ uuid }: { uuid: string }) {
361384 ) ) }
362385 </ ul >
363386 ) : null }
364- { searchedTo && next ? (
387+ { searchedTo && next && ! failure ? (
365388 < p className = "mt-3 text-xs text-[var(--tfmc-stone)]" > Searched back to { formatEpoch ( searchedTo ) } .</ p >
366389 ) : null }
367- { next ? (
368- < button type = "button" className = { moreClass } disabled = { state === "more" } onClick = { ( ) => void fetchPage ( next ) } >
390+ { failure ? (
391+ < Retry failure = { failure } busy = { state !== "ready" } onRetry = { ( ) => void fetchPage ( failure . before ) } />
392+ ) : next ? (
393+ < button type = "button" className = { moreClass } disabled = { state !== "ready" } onClick = { ( ) => void fetchPage ( next ) } >
369394 { state === "more" ? "Loading…" : searchedTo ? "Search further back" : "Load more" }
370395 </ button >
371396 ) : null }
0 commit comments