Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 | 42x 1x 42x 42x 42x 154x 154x 154x 422x 422x 420x 154x 154x 154x 80x 80x 80x 154x 105x 61x 56x 56x 5x 5x 2x 2x 2x 2x 2x 2x 3x 3x 105x 1x 1x 1x 1x 154x 6x 6x 6x 6x | import { useEffect, useState, useRef } from 'react'
import { useNavigate, useLocation } from 'react-router-dom'
import { useUiStore } from './uiStore'
import { useAuthStore } from '@/features/auth/authStore'
import { Button } from '@/components/ui/button'
const WARNING_BEFORE = 5 * 60 * 1000
const CHECK_INTERVAL = 1000
export function SessionTimeoutModal() {
const navigate = useNavigate()
const location = useLocation()
const sessionTimeoutAt = useUiStore((s) => s.sessionTimeoutAt)
const setSessionTimeoutAt = useUiStore((s) => s.setSessionTimeoutAt)
const clearAuth = useAuthStore((s) => s.clearAuth)
const [remaining, setRemaining] = useState<number | null>(null)
const channelRef = useRef<BroadcastChannel | null>(null)
useEffect(() => {
channelRef.current = new BroadcastChannel('session-timeout')
channelRef.current.onmessage = (event) => {
if (event.data.type === 'extend') {
setSessionTimeoutAt(event.data.timeoutAt)
}
if (event.data.type === 'logout') {
clearAuth()
navigate('/login', { replace: true })
}
}
return () => channelRef.current?.close()
}, [setSessionTimeoutAt, clearAuth, navigate])
useEffect(() => {
const interval = setInterval(() => {
if (!sessionTimeoutAt) {
setRemaining(null)
return
}
const diff = sessionTimeoutAt - Date.now()
if (diff <= 0) {
sessionStorage.setItem('returnUrl', location.pathname + location.search)
clearAuth()
navigate('/login', { replace: true })
channelRef.current?.postMessage({ type: 'logout' })
clearInterval(interval)
return
}
if (diff <= WARNING_BEFORE) {
setRemaining(diff)
} else {
setRemaining(null)
}
}, CHECK_INTERVAL)
returnE () => clearInterval(interval)
}, [sessionTimeoutAt, clearAuth, navigate, location])
function extendSession() {
const newTimeout = Date.now() + 30 * 60 * 1000
setSessionTimeoutAt(newTimeout)
setRemaining(null)
channelRef.current?.postMessage({ type: 'extend', timeoutAt: newTimeout })
}
if (remaining === null || remaining > WARNING_BEFORE) return null
const seconds = Math.ceil(remaining / 1000)
const minutes = Math.floor(seconds / 60)
const secs = seconds % 60
return (
<div
role="dialog"
className="fixed inset-0 z-50 flex items-center justify-center bg-black/50"
>
<div className="w-80 rounded-lg border border-border bg-card p-6 shadow-lg">
<h2 className="mb-2 text-lg font-semibold">Session Timeout</h2>
<p className="mb-4 text-sm text-muted-foreground">
Your session will expire in {minutes}:{secs.toString().padStart(2, '0')}
</p>
<div className="mb-4 h-2 w-full overflow-hidden rounded-full bg-muted">
<div
className="h-full rounded-full bg-primary transition-all duration-1000"
style={{
width: `${(remaining / WARNING_BEFORE) * 100}%`,
}}
/>
</div>
<Button className="w-full" onClick={extendSession}>
Stay logged in
</Button>
</div>
</div>
)
}
|