// components/layout/SessionManager.tsx 'use client' import { useSession } from "next-auth/react" import { useEffect, useState, useCallback } from "react" import { useRouter } from "next/navigation" import { AlertCircle, Clock, RefreshCw, X } from "lucide-react" import { Button } from "@/components/ui/button" import { Alert, AlertDescription } from "@/components/ui/alert" import { Progress } from "@/components/ui/progress" import { useToast } from "@/hooks/use-toast" import { Card, CardContent } from "@/components/ui/card" import { cn } from "@/lib/utils" interface SessionManagerProps { lng: string; } // 다국어 메시지 const messages = { ko: { sessionExpiring: "세션 만료 경고", sessionWillExpire: "세션이 {minutes}분 후에 만료됩니다.", sessionExpired: "세션이 만료되었습니다", pleaseRelogin: "다시 로그인해주세요.", extend: "연장", extending: "연장 중...", close: "닫기", sessionExtended: "세션이 연장되었습니다", sessionExtendFailed: "세션 연장에 실패했습니다", autoLogoutIn: "{seconds}초 후 자동 로그아웃됩니다", staySignedIn: "로그인 유지", logout: "로그아웃" }, en: { sessionExpiring: "Session Expiring", sessionWillExpire: "Your session will expire in {minutes} minute(s).", sessionExpired: "Session Expired", pleaseRelogin: "Please log in again.", extend: "Extend", extending: "Extending...", close: "Close", sessionExtended: "Session has been extended", sessionExtendFailed: "Failed to extend session", autoLogoutIn: "Auto logout in {seconds} seconds", staySignedIn: "Stay Signed In", logout: "Logout" } } as const; export function SessionManager({ lng }: SessionManagerProps) { const { data: session, update } = useSession() const router = useRouter() const { toast } = useToast() const [showWarning, setShowWarning] = useState(false) const [showExpiredModal, setShowExpiredModal] = useState(false) const [isExtending, setIsExtending] = useState(false) const [autoLogoutCountdown, setAutoLogoutCountdown] = useState(0) const [timeLeft, setTimeLeft] = useState(null) const t = messages[lng as keyof typeof messages] || messages.en // 세션 연장 함수 const extendSession = useCallback(async () => { if (isExtending) return; setIsExtending(true) try { await update({ reAuthTime: Date.now() }) setShowWarning(false) setTimeLeft(null) toast({ title: t.sessionExtended, description: "세션이 성공적으로 연장되었습니다.", duration: 3000, }) } catch (error) { console.error('Failed to extend session:', error) toast({ title: t.sessionExtendFailed, description: "다시 시도해주세요.", variant: "destructive", duration: 5000, }) } finally { setIsExtending(false) } }, [isExtending, update, toast, t]) // 자동 로그아웃 처리 const handleAutoLogout = useCallback(() => { setShowExpiredModal(false) setShowWarning(false) window.location.href = `/${lng}/evcp?reason=expired` }, [lng]) // 세션 만료 체크 useEffect(() => { if (!session?.user?.sessionExpiredAt) return const checkSession = () => { const now = Date.now() const expiresAt = session.user.sessionExpiredAt! const timeUntilExpiry = expiresAt - now const warningThreshold = 5 * 60 * 1000 // 5분 const criticalThreshold = 1 * 60 * 1000 // 1분 setTimeLeft(timeUntilExpiry) // 세션 만료됨 if (timeUntilExpiry <= 0) { setShowWarning(false) setShowExpiredModal(true) setAutoLogoutCountdown(10) // 10초 후 자동 로그아웃 return } // 1분 이내 - 긴급 경고 if (timeUntilExpiry <= criticalThreshold) { setShowWarning(true) return } // 5분 이내 - 일반 경고 if (timeUntilExpiry <= warningThreshold && !showWarning) { setShowWarning(true) return } // 경고 해제 if (timeUntilExpiry > warningThreshold && showWarning) { setShowWarning(false) } } // 즉시 체크 checkSession() // 5초마다 체크 (더 정확한 카운트다운을 위해) const interval = setInterval(checkSession, 5000) return () => clearInterval(interval) }, [session, showWarning]) // 자동 로그아웃 카운트다운 useEffect(() => { if (autoLogoutCountdown <= 0) return const timer = setTimeout(() => { if (autoLogoutCountdown === 1) { handleAutoLogout() } else { setAutoLogoutCountdown(prev => prev - 1) } }, 1000) return () => clearTimeout(timer) }, [autoLogoutCountdown, handleAutoLogout]) // 사용자 활동 감지 useEffect(() => { let activityTimer: NodeJS.Timeout let lastActivity = Date.now() const resetActivityTimer = () => { const now = Date.now() const timeSinceLastActivity = now - lastActivity // 5분 이상 비활성 후 첫 활동이면 세션 연장 if (timeSinceLastActivity > 5 * 60 * 1000) { extendSession() } lastActivity = now clearTimeout(activityTimer) // 10분간 비활성이면 경고 표시 activityTimer = setTimeout(() => { if (!showWarning && session?.user?.sessionExpiredAt) { const timeUntilExpiry = session.user.sessionExpiredAt - Date.now() if (timeUntilExpiry > 0 && timeUntilExpiry <= 10 * 60 * 1000) { setShowWarning(true) } } }, 10 * 60 * 1000) } const activities = ['mousedown', 'keydown', 'scroll', 'touchstart', 'click'] activities.forEach(activity => { document.addEventListener(activity, resetActivityTimer, true) }) resetActivityTimer() // 초기 타이머 설정 return () => { clearTimeout(activityTimer) activities.forEach(activity => { document.removeEventListener(activity, resetActivityTimer, true) }) } }, [extendSession, showWarning, session]) const formatTime = (ms: number) => { const minutes = Math.floor(ms / (1000 * 60)) const seconds = Math.floor((ms % (1000 * 60)) / 1000) return { minutes, seconds } } // 세션 만료 모달 if (showExpiredModal) { return (

{t.sessionExpired}

{t.pleaseRelogin}

{autoLogoutCountdown > 0 && (

{t.autoLogoutIn.replace('{seconds}', autoLogoutCountdown.toString())}

)}
) } // 세션 경고 알림 if (showWarning && timeLeft) { const { minutes, seconds } = formatTime(timeLeft) const isCritical = timeLeft <= 60000 // 1분 이내 const progressValue = Math.max(0, Math.min(100, (timeLeft / (5 * 60 * 1000)) * 100)) return (

{t.sessionExpiring}

{minutes > 0 ? t.sessionWillExpire.replace('{minutes}', minutes.toString()) : `${seconds}초 후 세션이 만료됩니다.` }
) } return null }