From 1225601d7a49695ce125ae8d9e780e2c47860d30 Mon Sep 17 00:00:00 2001 From: elpatron Date: Sat, 30 May 2026 10:18:14 +0200 Subject: [PATCH] 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 --- client/src/App.tsx | 38 +++++++++++++++++++----- client/src/components/AuthOnboarding.tsx | 5 ++-- 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/client/src/App.tsx b/client/src/App.tsx index 590b247..b587503 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react' +import { useState, useEffect, useCallback } from 'react' import './App.css' import { DialogProvider } from './components/ModalDialog.tsx' import AuthOnboarding from './components/AuthOnboarding.tsx' @@ -59,7 +59,7 @@ function App() { const [shareKey, setShareKey] = useState('') // Public demo mode (no account required) - const [isDemoMode, setIsDemoMode] = useState(false) + const [isDemoMode, setIsDemoMode] = useState(() => window.location.pathname === '/demo') const syncQueueCount = useLiveQuery( () => activeLogbookId ? db.syncQueue.where({ logbookId: activeLogbookId }).count() : db.syncQueue.count(), @@ -138,26 +138,37 @@ function App() { } }, [isAuthenticated]) - useEffect(() => { + const syncRouteFromLocation = useCallback(() => { const params = new URLSearchParams(window.location.search) const hashParams = new URLSearchParams(window.location.hash.substring(1)) + const path = window.location.pathname - if (window.location.pathname === '/demo') { + if (path === '/demo') { setIsDemoMode(true) + setIsViewerMode(false) + setIsAcceptingInvite(false) 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') || '') setShareKey(hashParams.get('key') || '') setIsViewerMode(true) + setIsAcceptingInvite(false) return } + setIsViewerMode(false) + if (params.has('token')) { setIsAcceptingInvite(true) + return } + setIsAcceptingInvite(false) + const savedUser = localStorage.getItem('active_username') const key = getActiveMasterKey() 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(() => { registerNavigation({ setActiveTab, @@ -245,7 +269,7 @@ function App() { const handleExitDemo = () => { window.history.replaceState({}, document.title, '/') - setIsDemoMode(false) + syncRouteFromLocation() } if (isDemoMode) { @@ -288,7 +312,7 @@ function App() { if (!isAuthenticated) { return (
- +
) } diff --git a/client/src/components/AuthOnboarding.tsx b/client/src/components/AuthOnboarding.tsx index f5dcb7f..d614c05 100644 --- a/client/src/components/AuthOnboarding.tsx +++ b/client/src/components/AuthOnboarding.tsx @@ -16,9 +16,10 @@ import RegistrationDisclaimer from './RegistrationDisclaimer.tsx' interface AuthOnboardingProps { onAuthenticated: () => void + onOpenDemo?: () => void } -export default function AuthOnboarding({ onAuthenticated }: AuthOnboardingProps) { +export default function AuthOnboarding({ onAuthenticated, onOpenDemo }: AuthOnboardingProps) { const { t, i18n } = useTranslation() const [username, setUsername] = useState('') const [loading, setLoading] = useState(false) @@ -526,7 +527,7 @@ export default function AuthOnboarding({ onAuthenticated }: AuthOnboardingProps)