Add shared device session timeout, sidebar scrollbar styling
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
37453ddfda
commit
dec7ac2f3e
4 changed files with 84 additions and 2 deletions
|
|
@ -1,6 +1,12 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import type { User } from '../types'
|
||||
|
||||
const SHARED_TIMEOUT_MS = 10 * 60 * 1000
|
||||
|
||||
function isSharedDevice() {
|
||||
return document.cookie.split(';').some(c => c.trim() === 'hnf_shared_device=1')
|
||||
}
|
||||
|
||||
interface Props {
|
||||
children: (user: User) => React.ReactNode
|
||||
}
|
||||
|
|
@ -23,6 +29,7 @@ export function AuthGate({ children }: Props) {
|
|||
const [password, setPassword] = useState('')
|
||||
const [error, setError] = useState('')
|
||||
const [loading, setLoading] = useState(false)
|
||||
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
fetch('/api/auth/verify?app=cashup', { credentials: 'include' })
|
||||
|
|
@ -33,6 +40,30 @@ export function AuthGate({ children }: Props) {
|
|||
.catch(() => setState('login'))
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (state !== 'authed' || !isSharedDevice()) return
|
||||
|
||||
async function forceLogout() {
|
||||
await fetch('/api/auth/logout', { method: 'POST', credentials: 'include' }).catch(() => {})
|
||||
setUser(null)
|
||||
setState('login')
|
||||
}
|
||||
|
||||
function reset() {
|
||||
if (timerRef.current) clearTimeout(timerRef.current)
|
||||
timerRef.current = setTimeout(forceLogout, SHARED_TIMEOUT_MS)
|
||||
}
|
||||
|
||||
const events = ['mousemove', 'keydown', 'click', 'touchstart'] as const
|
||||
events.forEach(e => window.addEventListener(e, reset, { passive: true }))
|
||||
reset()
|
||||
|
||||
return () => {
|
||||
if (timerRef.current) clearTimeout(timerRef.current)
|
||||
events.forEach(e => window.removeEventListener(e, reset))
|
||||
}
|
||||
}, [state])
|
||||
|
||||
async function login(e: React.FormEvent) {
|
||||
e.preventDefault()
|
||||
setLoading(true)
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ export function Layout({ user, children }: Props) {
|
|||
<p style={{ color: 'var(--text-muted)', fontSize: '0.75rem', marginTop: '0.25rem' }}>{user.name}</p>
|
||||
</div>
|
||||
|
||||
<div style={{ flex: 1, padding: '0.5rem 0', overflowY: 'auto' }}>
|
||||
<div className="nav-scroll" style={{ flex: 1, padding: '0.5rem 0', overflowY: 'auto' }}>
|
||||
{navItems.filter(item => !item.cap || can(user, item.cap)).map(({ to, label, icon: Icon }) => (
|
||||
<NavLink key={to} to={to} style={({ isActive }) => ({
|
||||
display: 'flex', alignItems: 'center', gap: '0.625rem',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue