"use client" ;
import { useState, useEffect, useCallback } from "react" ;
import { Clock, LogOut, RefreshCw } from "lucide-react" ;
import { Button } from "@/components/ui/button" ;
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog" ;
import { Progress } from "@/components/ui/progress" ;
import { cn } from "@/lib/utils" ;
const TOTAL_SECONDS = 120 ; // 2 minutes for demo
export const title = "React Dialog Block Session Timeout Warning" ;
export default function DialogSessionTimeout () {
const [ open , setOpen ] = useState ( false );
const [ secondsLeft , setSecondsLeft ] = useState ( TOTAL_SECONDS );
const [ isExpired , setIsExpired ] = useState ( false );
const [ isExtending , setIsExtending ] = useState ( false );
const formatTime = ( seconds : number ) => {
const mins = Math. floor (seconds / 60 );
const secs = seconds % 60 ;
return `${ mins }:${ secs . toString (). padStart ( 2 , "0" ) }` ;
};
const progressValue = (secondsLeft / TOTAL_SECONDS ) * 100 ;
const handleExtendSession = useCallback ( async () => {
setIsExtending ( true );
// Simulate API call to refresh token
await new Promise (( resolve ) => setTimeout (resolve, 1000 ));
setSecondsLeft ( TOTAL_SECONDS );
setIsExtending ( false );
setOpen ( false );
}, []);
const handleLogout = () => {
console. log ( "User logged out" );
setIsExpired ( true );
};
const handleClose = () => {
setOpen ( false );
setTimeout (() => {
setSecondsLeft ( TOTAL_SECONDS );
setIsExpired ( false );
setIsExtending ( false );
}, 200 );
};
useEffect (() => {
let interval : NodeJS . Timeout ;
if (open && secondsLeft > 0 && ! isExpired) {
interval = setInterval (() => {
setSecondsLeft (( prev ) => {
if (prev <= 1 ) {
setIsExpired ( true );
return 0 ;
}
return prev - 1 ;
});
}, 1000 );
}
return () => clearInterval (interval);
}, [open, secondsLeft, isExpired]);
return (
< Dialog open = {open} onOpenChange = {setOpen}>
< div className = "flex min-h-[350px] items-center justify-center" >
< DialogTrigger asChild >
< Button variant = "outline" >
< Clock className = "mr-2 h-4 w-4" />
Show Timeout Warning
</ Button >
</ DialogTrigger >
</ div >
< DialogContent className = "sm:max-w-sm" onInteractOutside = {( e ) => e. preventDefault ()}>
{isExpired ? (
< div className = "flex flex-col items-center justify-center py-8 text-center" >
< LogOut className = "h-8 w-8 text-muted-foreground" />
< h3 className = "mt-4 text-lg font-semibold" >Session Expired</ h3 >
< p className = "mt-2 text-sm text-muted-foreground" >
Please log in again to continue.
</ p >
< Button onClick = {handleClose} className = "mt-6" >
Go to Login
</ Button >
</ div >
) : (
<>
< DialogHeader >
< DialogTitle >Session Timeout</ DialogTitle >
< DialogDescription >
Your session is about to expire due to inactivity.
</ DialogDescription >
</ DialogHeader >
< div className = "space-y-4 py-4" >
< div className = "flex flex-col items-center py-4" >
< Clock className = "h-8 w-8 text-muted-foreground mb-3" />
< p
className = { cn (
"text-4xl font-bold font-mono tabular-nums" ,
secondsLeft <= 30 && "text-destructive"
)}
>
{ formatTime (secondsLeft)}
</ p >
< p className = "text-sm text-muted-foreground mt-1" >
until automatic logout
</ p >
</ div >
< Progress
value = {progressValue}
className = { cn (
"h-1.5" ,
secondsLeft <= 30 && "[&>div]:bg-destructive"
)}
/>
</ div >
< DialogFooter >
< Button variant = "outline" onClick = {handleLogout}>
< LogOut className = "mr-2 h-4 w-4" />
Log Out
</ Button >
< Button onClick = {handleExtendSession} disabled = {isExtending}>
{isExtending ? (
<>
< RefreshCw className = "mr-2 h-4 w-4 animate-spin" />
Extending...
</>
) : (
"Stay Logged In"
)}
</ Button >
</ DialogFooter >
</>
)}
</ DialogContent >
</ Dialog >
);
}