fix(app): unlimited loading screen when backend is not reachable (#364)

* Initial plan

* Add error handling for unlimited loading screen bug

Co-authored-by: antontranelis <31516529+antontranelis@users.noreply.github.com>

* Complete fix for unlimited loading screen bug with proper state management

Co-authored-by: antontranelis <31516529+antontranelis@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: antontranelis <31516529+antontranelis@users.noreply.github.com>
This commit is contained in:
Copilot 2025-09-07 12:45:37 +02:00 committed by GitHub
parent dbb405fe7e
commit a9004a47ea
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -71,6 +71,15 @@ function App() {
const [layers, setLayers] = useState<any>() const [layers, setLayers] = useState<any>()
const [layerPageRoutes, setLayerPageRoutes] = useState<any>() const [layerPageRoutes, setLayerPageRoutes] = useState<any>()
const [loading, setLoading] = useState<boolean>(true) const [loading, setLoading] = useState<boolean>(true)
const [error, setError] = useState<string | null>(null)
const retryConnection = () => {
setError(null)
setLoading(true)
if (mapApiInstance) {
getMap()
}
}
const [embedded, setEmbedded] = useState<boolean>(true) const [embedded, setEmbedded] = useState<boolean>(true)
@ -94,12 +103,27 @@ function App() {
}, [mapApiInstance]) }, [mapApiInstance])
const getMap = async () => { const getMap = async () => {
const map = await mapApiInstance?.getItems() try {
map && setMap(map) const map = await mapApiInstance?.getItems()
map && map != 'null' && setLayersApiInstance(new layersApi(map.id)) map && setMap(map)
map && map != 'null' && map.own_tag_space map && map != 'null' && setLayersApiInstance(new layersApi(map.id))
? setTagsApi(new itemsApi<Tag>('tags', undefined, map.id)) map && map != 'null' && map.own_tag_space
: setTagsApi(new itemsApi<Tag>('tags')) ? setTagsApi(new itemsApi<Tag>('tags', undefined, map.id))
: setTagsApi(new itemsApi<Tag>('tags'))
// eslint-disable-next-line no-catch-all/no-catch-all
} catch (error: any) {
// eslint-disable-next-line no-console
console.error('Failed to load map:', error)
setError(
typeof error === 'string'
? error
: error?.errors?.[0]?.message ||
error?.message ||
'Failed to connect to the server. Please check your connection and try again.',
)
setLoading(false)
// Don't rethrow since we're handling the error by setting error state
}
} }
useEffect(() => { useEffect(() => {
@ -107,25 +131,40 @@ function App() {
}, [layersApiInstance]) }, [layersApiInstance])
const getLayers = async () => { const getLayers = async () => {
const layers = await layersApiInstance?.getItems() try {
layers && setLayers(layers) const layers = await layersApiInstance?.getItems()
setLayerPageRoutes( layers && setLayers(layers)
layers setLayerPageRoutes(
?.filter((l: LayerProps) => l.listed) layers
.map((l: LayerProps) => ({ ?.filter((l: LayerProps) => l.listed)
path: '/' + l.name, // url .map((l: LayerProps) => ({
icon: ( path: '/' + l.name, // url
<SVG icon: (
src={`${config.apiUrl}assets/${l.markerIcon.image_outline ?? l.markerIcon.image}`} <SVG
className='tw:w-6 tw:h-6' src={`${config.apiUrl}assets/${l.markerIcon.image_outline ?? l.markerIcon.image}`}
preProcessor={(code: string) => className='tw:w-6 tw:h-6'
code.replace(/stroke=".*?"/g, 'stroke="currentColor"') preProcessor={(code: string) =>
} code.replace(/stroke=".*?"/g, 'stroke="currentColor"')
/> }
), />
name: l.name, // name that appear in Sidebar ),
})), name: l.name, // name that appear in Sidebar
) })),
)
// eslint-disable-next-line no-catch-all/no-catch-all
} catch (error: any) {
// eslint-disable-next-line no-console
console.error('Failed to load layers:', error)
setError(
typeof error === 'string'
? error
: error?.errors?.[0]?.message ||
error?.message ||
'Failed to load map layers. Please check your permissions and try again.',
)
setLoading(false)
// Don't rethrow since we're handling the error by setting error state
}
} }
useEffect(() => { useEffect(() => {
@ -140,8 +179,11 @@ function App() {
link.href = map?.logo && config.apiUrl + 'assets/' + map.logo // Specify the path to your favicon link.href = map?.logo && config.apiUrl + 'assets/' + map.logo // Specify the path to your favicon
} }
setLoading(false) // Only set loading to false when both map and layers are successfully loaded
}, [map]) if (map && layers) {
setLoading(false)
}
}, [map, layers])
const currentUrl = window.location.href const currentUrl = window.location.href
const bottomRoutes = getBottomRoutes(currentUrl) const bottomRoutes = getBottomRoutes(currentUrl)
@ -253,6 +295,35 @@ function App() {
</div> </div>
</div> </div>
) )
else if (error)
return (
<div className='tw:flex tw:items-center tw:justify-center tw:h-screen tw:bg-base-100'>
<div className='tw:max-w-md tw:mx-auto tw:p-6 tw:text-center'>
<div className='tw:mb-4'>
<svg
className='tw:w-16 tw:h-16 tw:mx-auto tw:text-error tw:mb-4'
fill='none'
stroke='currentColor'
viewBox='0 0 24 24'
>
<path
strokeLinecap='round'
strokeLinejoin='round'
strokeWidth={2}
d='M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.732-.833-2.5 0L4.314 16.5c-.77.833.192 2.5 1.732 2.5z'
/>
</svg>
</div>
<h2 className='tw:text-xl tw:font-semibold tw:text-base-content tw:mb-2'>
Connection Error
</h2>
<p className='tw:text-base-content/70 tw:mb-6'>{error}</p>
<button onClick={retryConnection} className='tw:btn tw:btn-primary'>
Try Again
</button>
</div>
</div>
)
else else
return ( return (
<div className='outer'> <div className='outer'>