refactor(lib): move info modal to dedicated /info route

- Add InfoRedirect component for one-time redirect to /info on page load
- Simplify Modal component to pure presentation (remove redirect logic)
- Change NavBar info button from showModal() to Link to /info route
- Remove unused window.my_modal_3 global type declaration
- Fix modal centering by removing conflicting position classes

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Anton Tranelis 2026-01-12 19:34:06 +01:00
parent ec2bce1ccb
commit 4eb782a6f0
6 changed files with 34 additions and 25 deletions

View File

@ -22,6 +22,7 @@ import {
Content,
AuthProvider,
Modal,
InfoRedirect,
InvitePage,
LoginPage,
SignupPage,
@ -41,7 +42,7 @@ import {
UserSettings,
} from 'utopia-ui'
import { Route, Routes, useNavigate } from 'react-router-dom'
import { Route, Routes } from 'react-router-dom'
import './App.css'
import { Suspense, useEffect, useState } from 'react'
@ -196,14 +197,6 @@ function App() {
const currentUrl = window.location.href
const bottomRoutes = getBottomRoutes(currentUrl)
const navigate = useNavigate()
// Redirect to /info if map.info_open is true (on first load)
useEffect(() => {
if (map?.info_open && window.location.pathname === '/') {
void navigate('/info')
}
}, [map?.info_open, navigate])
if (map && layers)
return (
@ -218,6 +211,7 @@ function App() {
>
<Permissions api={permissionsApiInstance} adminRole={config.adminRole} />
{tagsApi && <Tags api={tagsApi}></Tags>}
<InfoRedirect enabled={map.info_open} />
<SideBar routes={[...routes, ...layerPageRoutes]} bottomRoutes={bottomRoutes} />
<Content>
<Quests />

View File

@ -0,0 +1,27 @@
import { useEffect, useRef } from 'react'
import { useNavigate } from 'react-router-dom'
interface InfoRedirectProps {
/** If true, redirects to /info route on initial page load (once) */
enabled?: boolean
}
/**
* Redirects to /info route on initial page load when enabled.
* Only redirects once per session to avoid redirect loops.
* Place this component inside the Router context but outside of Routes.
* @category AppShell
*/
export function InfoRedirect({ enabled }: InfoRedirectProps) {
const navigate = useNavigate()
const hasRedirected = useRef(false)
useEffect(() => {
if (enabled && window.location.pathname === '/' && !hasRedirected.current) {
hasRedirected.current = true
void navigate('/info')
}
}, [enabled, navigate])
return null
}

View File

@ -50,14 +50,9 @@ export default function NavBar({ appName }: { appName: string }) {
{appName}
</h1>
</Link>
<button
className='tw:btn tw:px-2 tw:btn-ghost'
onClick={() => {
window.my_modal_3.showModal()
}}
>
<Link className='tw:btn tw:px-2 tw:btn-ghost' to='/info'>
<QuestionMarkIcon className='tw:h-5 tw:w-5' />
</button>
</Link>
</div>
</div>

View File

@ -1,4 +1,5 @@
export * from './AppShell'
export { SideBar } from './SideBar'
export { Content } from './Content'
export { InfoRedirect } from './InfoRedirect'
export { default as SVG } from 'react-inlinesvg'

View File

@ -8,7 +8,7 @@ export function Modal({ children }: { children: React.ReactNode }) {
<MapOverlayPage
backdrop
card
className='tw:absolute tw:h-fit tw:max-h-[calc(100%-2.5em)] tw:top-4 tw:bottom-4 tw:left-1/2 tw:transform tw:-translate-x-1/2 tw:overflow-scroll tw:md:w-[calc(50%-32px)] tw:w-[calc(100%-32px)] tw:min-w-80 tw:max-w-[612px] tw:transition-opacity tw:duration-500 tw:opacity-100 tw:pointer-events-auto'
className='tw:h-fit tw:max-h-[calc(100%-2.5em)] tw:overflow-auto tw:w-[calc(100%-32px)] tw:min-w-80 tw:max-w-[612px] tw:transition-opacity tw:duration-500 tw:opacity-100 tw:pointer-events-auto'
>
{children}
</MapOverlayPage>

View File

@ -10,11 +10,3 @@ export * from './Components/Input'
export * from './Components/Item'
export * from './Components/Onboarding'
export * from './Components/Profile'
declare global {
interface Window {
my_modal_3: {
showModal(): void
}
}
}