From a9004a47eadff2c0002587c3e68c8d56c174512c Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sun, 7 Sep 2025 12:45:37 +0200 Subject: [PATCH] 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> --- app/src/App.tsx | 125 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 98 insertions(+), 27 deletions(-) diff --git a/app/src/App.tsx b/app/src/App.tsx index c46d151e..fbb525c3 100644 --- a/app/src/App.tsx +++ b/app/src/App.tsx @@ -71,6 +71,15 @@ function App() { const [layers, setLayers] = useState() const [layerPageRoutes, setLayerPageRoutes] = useState() const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) + + const retryConnection = () => { + setError(null) + setLoading(true) + if (mapApiInstance) { + getMap() + } + } const [embedded, setEmbedded] = useState(true) @@ -94,12 +103,27 @@ function App() { }, [mapApiInstance]) const getMap = async () => { - const map = await mapApiInstance?.getItems() - map && setMap(map) - map && map != 'null' && setLayersApiInstance(new layersApi(map.id)) - map && map != 'null' && map.own_tag_space - ? setTagsApi(new itemsApi('tags', undefined, map.id)) - : setTagsApi(new itemsApi('tags')) + try { + const map = await mapApiInstance?.getItems() + map && setMap(map) + map && map != 'null' && setLayersApiInstance(new layersApi(map.id)) + map && map != 'null' && map.own_tag_space + ? setTagsApi(new itemsApi('tags', undefined, map.id)) + : setTagsApi(new itemsApi('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(() => { @@ -107,25 +131,40 @@ function App() { }, [layersApiInstance]) const getLayers = async () => { - const layers = await layersApiInstance?.getItems() - layers && setLayers(layers) - setLayerPageRoutes( - layers - ?.filter((l: LayerProps) => l.listed) - .map((l: LayerProps) => ({ - path: '/' + l.name, // url - icon: ( - - code.replace(/stroke=".*?"/g, 'stroke="currentColor"') - } - /> - ), - name: l.name, // name that appear in Sidebar - })), - ) + try { + const layers = await layersApiInstance?.getItems() + layers && setLayers(layers) + setLayerPageRoutes( + layers + ?.filter((l: LayerProps) => l.listed) + .map((l: LayerProps) => ({ + path: '/' + l.name, // url + icon: ( + + code.replace(/stroke=".*?"/g, 'stroke="currentColor"') + } + /> + ), + 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(() => { @@ -140,8 +179,11 @@ function App() { link.href = map?.logo && config.apiUrl + 'assets/' + map.logo // Specify the path to your favicon } - setLoading(false) - }, [map]) + // Only set loading to false when both map and layers are successfully loaded + if (map && layers) { + setLoading(false) + } + }, [map, layers]) const currentUrl = window.location.href const bottomRoutes = getBottomRoutes(currentUrl) @@ -253,6 +295,35 @@ function App() { ) + else if (error) + return ( +
+
+
+ + + +
+

+ Connection Error +

+

{error}

+ +
+
+ ) else return (