feat(ui): hash router — each page has a shareable URL (#35)

- #/health, #/events, #/reaction-logs, etc.
- Browser back/forward navigation works
- Refresh preserves current page
- No new dependencies (hand-rolled hash router)

Closes #35
小橘 🍊(NEKO Team)
This commit is contained in:
小橘 2026-04-13 09:08:42 +00:00
parent d06d000ec0
commit 0c22885f4a
2 changed files with 49 additions and 17 deletions

File diff suppressed because one or more lines are too long

View File

@ -27,12 +27,44 @@ type Page =
| 'reaction-logs' | 'reaction-logs'
| 'request-logs' | 'request-logs'
const VALID_PAGES: Set<string> = new Set([
'health', 'object-defs', 'objects', 'event-defs', 'events',
'projection-defs', 'projections', 'reactions', 'api-keys',
'reaction-logs', 'request-logs',
])
function getPageFromHash(): Page {
const hash = window.location.hash.replace(/^#\/?/, '')
return VALID_PAGES.has(hash) ? (hash as Page) : 'health'
}
function setHash(page: Page) {
window.history.pushState(null, '', `#/${page}`)
}
function App() { function App() {
const [page, setPage] = useState<Page>('health') const [page, setPageState] = useState<Page>(getPageFromHash)
const [needsAuth, setNeedsAuth] = useState(false) const [needsAuth, setNeedsAuth] = useState(false)
const [tokenInput, setTokenInput] = useState('') const [tokenInput, setTokenInput] = useState('')
const [checking, setChecking] = useState(true) const [checking, setChecking] = useState(true)
// Sync hash → state on popstate (browser back/forward)
useEffect(() => {
const onHashChange = () => setPageState(getPageFromHash())
window.addEventListener('hashchange', onHashChange)
window.addEventListener('popstate', onHashChange)
return () => {
window.removeEventListener('hashchange', onHashChange)
window.removeEventListener('popstate', onHashChange)
}
}, [])
// Navigate: update hash + state
const setPage = (p: Page) => {
setHash(p)
setPageState(p)
}
useEffect(() => { useEffect(() => {
setTimeout(() => { setTimeout(() => {
if (!getToken()) { if (!getToken()) {