Assist_Design/apps/portal/src/components/auth/session-timeout-warning.tsx
T. Narantuya b0c8103ee2 Refactor TypeScript configurations and enhance logging setup
- Updated nest-cli.json to enable output directory deletion and refined TypeScript compiler options.
- Modified package.json to improve development command for BFF with preserved watch output.
- Adjusted tsconfig.json to extend from a higher-level configuration and removed unnecessary options.
- Enhanced logging.module.ts to simplify logger configuration and improve log message formatting.
- Updated next.config.mjs to manage server-only libraries and optimize Webpack configuration.
- Refined error logging in various components for better clarity and consistency.
2025-08-30 18:22:31 +09:00

135 lines
3.7 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])) as { exp?: number };
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
void 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, "Error parsing JWT token");
void logout();
return undefined;
}
}, [isAuthenticated, token, warningTime, logout]);
useEffect(() => {
if (!showWarning) return undefined;
const interval = setInterval(() => {
setTimeLeft(prev => {
if (prev <= 1) {
void logout();
return 0;
}
return prev - 1;
});
}, 60000);
return () => clearInterval(interval);
}, [showWarning, logout]);
const handleExtendSession = () => {
void (async () => {
try {
await checkAuth();
setShowWarning(false);
setTimeLeft(0);
} catch (error) {
logger.error(error, "Failed to extend session");
await logout();
}
})();
};
const handleLogoutNow = () => {
void 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>
);
}