import { useNavigation } from '@react-navigation/native' import { useQueryErrorResetBoundary } from '@tanstack/react-query' import React, { PropsWithChildren, useCallback, useEffect, useMemo, useRef, useState } from 'react' import { onAuthRefresh } from '../../utils/auth_events' import { ResponseError } from '../../utils/response_error' import BlankState from '../primitive/blank_state_primitive' import { ErrorBoundary, ErrorBoundaryFallback, ErrorBoundaryProps } from './error_boundary' const renderPageFallback: ErrorBoundaryFallback = (error, reset) => ( ) export function PageErrorBoundary({ children, ...props }: PropsWithChildren) { return ( {children} ) } function ErrorView({ error, reset }: { error: Error; reset: () => void }) { const { reset: resetQueries } = useQueryErrorResetBoundary() const handleReset = useCallback(() => { resetQueries() reset() }, [resetQueries, reset]) useEffect(() => handleReset, [handleReset]) if (error instanceof ResponseError) { return } if (isNetworkError(error)) { return ( ) } return } function isNetworkError(error: Error) { const networkFailedMessages = [ 'Network request failed', 'Network request timed out', 'Failed to fetch', ] return new RegExp(networkFailedMessages.join('|'), 'i').test(error.message) } function ResponseErrorView({ response, onReset }: { response: Response; onReset: () => void }) { const { status } = response const heading = useMemo(() => { switch (status) { case 403: return 'Permission required' case 404: return 'Content not found' default: return 'Oops!' } }, [status]) const body = useMemo(() => { switch (status) { case 403: return 'Contact your administrator for access.' case 404: return 'If you believe something should be here, please reach out to your administrator.' default: return 'Something unexpected happened.' } }, [status]) useEffect(() => { if (status !== 401) return return onAuthRefresh(onReset) }, [status, onReset]) if (status === 429) { return } return } const RATE_LIMIT_WINDOW_S = 20 function RateLimitedView({ response, onReset }: { response: Response; onReset: () => void }) { const retryAfterS = parseInt( response.headers?.get('Retry-After') ?? String(RATE_LIMIT_WINDOW_S), 10 ) const readyAt = useRef(Date.now() + retryAfterS * 1000).current const [now, setNow] = useState(Date.now) useEffect(() => { const id = setInterval(() => { setNow(prev => (prev >= readyAt ? prev : Date.now())) }, 1000) return () => clearInterval(id) }, [readyAt]) const navigation = useNavigation() const secondsLeft = Math.max(0, Math.ceil((readyAt - now) / 1000)) return ( Too many requests Chat is being accessed too frequently. Please wait a moment. 0 ? `Retry in ${secondsLeft}s` : 'Try again'} disabled={secondsLeft > 0} onPress={onReset} size="md" accessibilityRole="button" /> Go back ) } function ErrorContent({ heading, body }: { heading: string; body: string }) { const navigation = useNavigation() return ( {heading} {body} navigation.navigate('BugReport')}> Report a bug ) }