import { useEffect, useRef, useState } from 'react' const SHARED_TIMEOUT_MS = 10 * 60 * 1000 function isSharedDevice() { return document.cookie.split(';').some(c => c.trim() === 'hnf_shared_device=1') } interface User { email: string name: string is_admin: boolean } interface Props { children: (user: User) => React.ReactNode } export function AuthGate({ children }: Props) { const [state, setState] = useState<'checking' | 'authed' | 'login'>('checking') const [user, setUser] = useState(null) const [email, setEmail] = useState('') const [password, setPassword] = useState('') const [error, setError] = useState('') const [loading, setLoading] = useState(false) const timerRef = useRef | null>(null) useEffect(() => { fetch('/api/auth/verify?app=noticeboard', { credentials: 'include' }) .then(async r => { if (r.ok) { const data = await r.json() setUser(data) setState('authed') } else { setState('login') } }) .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) setError('') try { const res = await fetch('/api/auth/login', { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password }), }) if (!res.ok) { setError('Invalid email or password') return } const verify = await fetch('/api/auth/verify?app=noticeboard', { credentials: 'include' }) if (verify.ok) { const data = await verify.json() setUser(data) setState('authed') } else { setError("You don't have access to this app.") } } catch { setError('Connection error — please try again') } finally { setLoading(false) } } if (state === 'checking') { return (
Loading…
) } if (state === 'login') { return (

Noticeboard

{import.meta.env.VITE_HOTEL_NAME}

setEmail(e.target.value)} placeholder="Email" required autoComplete="email" style={inputStyle} /> setPassword(e.target.value)} placeholder="Password" required autoComplete="current-password" style={inputStyle} /> {error &&

{error}

}
) } return <>{children(user!)} } const inputStyle: React.CSSProperties = { background: 'var(--navy-dark)', border: '1px solid var(--surface-2)', borderRadius: '6px', color: 'var(--text)', padding: '0.625rem 0.75rem', fontSize: '1rem', width: '100%', outline: 'none', } const btnStyle: React.CSSProperties = { background: 'var(--gold)', color: 'var(--navy-dark)', border: 'none', borderRadius: '6px', padding: '0.625rem', fontSize: '1rem', fontWeight: 600, marginTop: '0.25rem', }