Assist_Design/apps/portal/src/components/auth/session-timeout-warning.tsx

133 lines
3.8 KiB
TypeScript
Raw Normal View History

'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>
);
}