fix: Demo navigation via history API and route sync.

Replace unreliable pathname assignment with pushState and central route syncing so the demo opens from the login screen and responds to browser back/forward.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-30 10:18:14 +02:00
co-authored by Cursor
parent 180e5727df
commit 1225601d7a
2 changed files with 34 additions and 9 deletions
+31 -7
View File
@@ -1,4 +1,4 @@
import { useState, useEffect } from 'react' import { useState, useEffect, useCallback } from 'react'
import './App.css' import './App.css'
import { DialogProvider } from './components/ModalDialog.tsx' import { DialogProvider } from './components/ModalDialog.tsx'
import AuthOnboarding from './components/AuthOnboarding.tsx' import AuthOnboarding from './components/AuthOnboarding.tsx'
@@ -59,7 +59,7 @@ function App() {
const [shareKey, setShareKey] = useState('') const [shareKey, setShareKey] = useState('')
// Public demo mode (no account required) // Public demo mode (no account required)
const [isDemoMode, setIsDemoMode] = useState(false) const [isDemoMode, setIsDemoMode] = useState(() => window.location.pathname === '/demo')
const syncQueueCount = useLiveQuery( const syncQueueCount = useLiveQuery(
() => activeLogbookId ? db.syncQueue.where({ logbookId: activeLogbookId }).count() : db.syncQueue.count(), () => activeLogbookId ? db.syncQueue.where({ logbookId: activeLogbookId }).count() : db.syncQueue.count(),
@@ -138,26 +138,37 @@ function App() {
} }
}, [isAuthenticated]) }, [isAuthenticated])
useEffect(() => { const syncRouteFromLocation = useCallback(() => {
const params = new URLSearchParams(window.location.search) const params = new URLSearchParams(window.location.search)
const hashParams = new URLSearchParams(window.location.hash.substring(1)) const hashParams = new URLSearchParams(window.location.hash.substring(1))
const path = window.location.pathname
if (window.location.pathname === '/demo') { if (path === '/demo') {
setIsDemoMode(true) setIsDemoMode(true)
setIsViewerMode(false)
setIsAcceptingInvite(false)
return return
} }
if (window.location.pathname === '/share' && params.has('token') && hashParams.has('key')) { setIsDemoMode(false)
if (path === '/share' && params.has('token') && hashParams.has('key')) {
setShareToken(params.get('token') || '') setShareToken(params.get('token') || '')
setShareKey(hashParams.get('key') || '') setShareKey(hashParams.get('key') || '')
setIsViewerMode(true) setIsViewerMode(true)
setIsAcceptingInvite(false)
return return
} }
setIsViewerMode(false)
if (params.has('token')) { if (params.has('token')) {
setIsAcceptingInvite(true) setIsAcceptingInvite(true)
return
} }
setIsAcceptingInvite(false)
const savedUser = localStorage.getItem('active_username') const savedUser = localStorage.getItem('active_username')
const key = getActiveMasterKey() const key = getActiveMasterKey()
if (savedUser && key) { if (savedUser && key) {
@@ -171,6 +182,19 @@ function App() {
} }
}, []) }, [])
useEffect(() => {
syncRouteFromLocation()
window.addEventListener('popstate', syncRouteFromLocation)
return () => window.removeEventListener('popstate', syncRouteFromLocation)
}, [syncRouteFromLocation])
const openDemo = useCallback(() => {
window.history.pushState({}, document.title, '/demo')
setIsDemoMode(true)
setIsViewerMode(false)
setIsAcceptingInvite(false)
}, [])
useEffect(() => { useEffect(() => {
registerNavigation({ registerNavigation({
setActiveTab, setActiveTab,
@@ -245,7 +269,7 @@ function App() {
const handleExitDemo = () => { const handleExitDemo = () => {
window.history.replaceState({}, document.title, '/') window.history.replaceState({}, document.title, '/')
setIsDemoMode(false) syncRouteFromLocation()
} }
if (isDemoMode) { if (isDemoMode) {
@@ -288,7 +312,7 @@ function App() {
if (!isAuthenticated) { if (!isAuthenticated) {
return ( return (
<div className="auth-screen"> <div className="auth-screen">
<AuthOnboarding onAuthenticated={handleAuthenticated} /> <AuthOnboarding onAuthenticated={handleAuthenticated} onOpenDemo={openDemo} />
</div> </div>
) )
} }
+3 -2
View File
@@ -16,9 +16,10 @@ import RegistrationDisclaimer from './RegistrationDisclaimer.tsx'
interface AuthOnboardingProps { interface AuthOnboardingProps {
onAuthenticated: () => void onAuthenticated: () => void
onOpenDemo?: () => void
} }
export default function AuthOnboarding({ onAuthenticated }: AuthOnboardingProps) { export default function AuthOnboarding({ onAuthenticated, onOpenDemo }: AuthOnboardingProps) {
const { t, i18n } = useTranslation() const { t, i18n } = useTranslation()
const [username, setUsername] = useState('') const [username, setUsername] = useState('')
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
@@ -526,7 +527,7 @@ export default function AuthOnboarding({ onAuthenticated }: AuthOnboardingProps)
<button <button
type="button" type="button"
className="btn secondary" className="btn secondary"
onClick={() => { window.location.pathname = '/demo' }} onClick={() => onOpenDemo?.()}
disabled={loading} disabled={loading}
style={{ width: '100%' }} style={{ width: '100%' }}
> >