mirror of
https://github.com/utopia-os/utopia-ui.git
synced 2025-12-12 23:36:00 +00:00
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:
parent
dbb405fe7e
commit
a9004a47ea
125
app/src/App.tsx
125
app/src/App.tsx
@ -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'>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user