Assist_Design/apps/portal/src/components/auth/session-timeout-warning.tsx
2025-08-20 18:02:50 +09:00

133 lines
3.8 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client';
import { logger } from "@/lib/logger";
import { useEffect, useState } from 'react';
import { useAuthStore } from '@/lib/auth/store';
import { Button } from '@/components/ui/button';
interface SessionTimeoutWarningProps {
warningTime?: number; // Minutes before token expires to show warning
}
export function SessionTimeoutWarning({
warningTime = 10 // Show warning 10 minutes before expiry
}: SessionTimeoutWarningProps) {
const { isAuthenticated, token, logout, checkAuth } = useAuthStore();
const [showWarning, setShowWarning] = useState(false);
const [timeLeft, setTimeLeft] = useState<number>(0);
useEffect(() => {
if (!isAuthenticated || !token) {
return undefined;
}
// Parse JWT to get expiry time
try {
const parts = token.split('.');
if (parts.length !== 3) {
throw new Error('Invalid token format');
}
const payload = JSON.parse(atob(parts[1]));
if (!payload.exp) {
logger.warn('Token does not have expiration time');
return undefined;
}
const expiryTime = payload.exp * 1000; // Convert to milliseconds
const currentTime = Date.now();
const warningThreshold = warningTime * 60 * 1000; // Convert to milliseconds
const timeUntilExpiry = expiryTime - currentTime;
const timeUntilWarning = timeUntilExpiry - warningThreshold;
if (timeUntilExpiry <= 0) {
// Token already expired
logout();
return undefined;
}
if (timeUntilWarning <= 0) {
// Should show warning immediately
setShowWarning(true);
setTimeLeft(Math.ceil(timeUntilExpiry / 1000 / 60)); // Minutes left
return undefined;
} else {
// Set timeout to show warning
const warningTimeout = setTimeout(() => {
setShowWarning(true);
setTimeLeft(warningTime);
}, timeUntilWarning);
return () => clearTimeout(warningTimeout);
}
} catch (error) {
logger.error('Error parsing JWT token:', error);
logout();
return undefined;
}
}, [isAuthenticated, token, warningTime, logout]);
useEffect(() => {
if (!showWarning) return undefined;
const interval = setInterval(() => {
setTimeLeft((prev) => {
if (prev <= 1) {
// Time's up, log out
logout();
return 0;
}
return prev - 1;
});
}, 60000); // Update every minute
return () => clearInterval(interval);
}, [showWarning, logout]);
const handleExtendSession = async () => {
try {
await checkAuth(); // This will refresh the user data and validate the token
setShowWarning(false);
setTimeLeft(0);
} catch (error) {
logger.error('Failed to extend session:', error);
logout();
}
};
const handleLogoutNow = () => {
logout();
setShowWarning(false);
};
if (!showWarning) {
return null;
}
return (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
<div className="bg-white rounded-lg p-6 max-w-md w-full mx-4 shadow-xl">
<div className="flex items-center gap-2 mb-4">
<span className="text-yellow-500 text-xl"></span>
<h2 className="text-lg font-semibold">Session Expiring Soon</h2>
</div>
<p className="text-gray-600 mb-6">
Your session will expire in <strong>{timeLeft} minute{timeLeft !== 1 ? 's' : ''}</strong>.
Would you like to extend your session?
</p>
<div className="flex gap-2 justify-end">
<Button variant="outline" onClick={handleLogoutNow}>
Logout Now
</Button>
<Button onClick={handleExtendSession}>
Extend Session
</Button>
</div>
</div>
</div>
);
}