From 22cab905303b36ad3d594f680b4634be36881de9 Mon Sep 17 00:00:00 2001 From: ahk0413 Date: Fri, 10 Oct 2025 12:54:12 +0900 Subject: [PATCH] =?UTF-8?q?[feat]=20=EC=9E=90=EB=8F=99=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=95=84=EC=9B=83=20=EC=B2=98=EB=A6=AC(4=EC=8B=9C?= =?UTF-8?q?=EA=B0=84)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/layout.tsx | 2 + src/domains/login/components/IdleHandler.tsx | 9 +++++ src/domains/login/hook/useIdleLogout.ts | 40 ++++++++++++++++++++ 3 files changed, 51 insertions(+) create mode 100644 src/domains/login/components/IdleHandler.tsx create mode 100644 src/domains/login/hook/useIdleLogout.ts diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 3d86d643..c5f134dd 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -5,6 +5,7 @@ import Header from '@/shared/components/header/Header'; import FooterWrapper from '@/shared/components/footer/FooterWrapper'; import ScrollTopBtnWrapper from '@/shared/components/scroll-top/ScrollTopBtnWrapper'; import KaKaoScript from './api/kakao/KaKaoScript'; +import IdleHandler from '@/domains/login/components/IdleHandler'; export const metadata: Metadata = { title: { default: 'SSOUL', template: 'SSOUL | %s' }, @@ -21,6 +22,7 @@ export default function RootLayout({
+
{children}
diff --git a/src/domains/login/components/IdleHandler.tsx b/src/domains/login/components/IdleHandler.tsx new file mode 100644 index 00000000..4edbf6c8 --- /dev/null +++ b/src/domains/login/components/IdleHandler.tsx @@ -0,0 +1,9 @@ +'use client'; + +import { useIdleLogout } from '../hook/useIdleLogout'; + +function IdleHandler() { + useIdleLogout(); + return null; +} +export default IdleHandler; diff --git a/src/domains/login/hook/useIdleLogout.ts b/src/domains/login/hook/useIdleLogout.ts new file mode 100644 index 00000000..c2cfd493 --- /dev/null +++ b/src/domains/login/hook/useIdleLogout.ts @@ -0,0 +1,40 @@ +'use client'; + +import { useCallback, useEffect, useRef } from 'react'; +import { useLogout } from './useLogout'; +import { useToast } from '@/shared/hook/useToast'; +import { useAuthStore } from '@/domains/shared/store/auth'; + +const IDLE_TIMEOUT = 4 * 60 * 60 * 1000; + +export const useIdleLogout = () => { + const handleLogout = useLogout(); + const timerRef = useRef(null); + const warningTimerRef = useRef(null); + const { toastInfo } = useToast(); + const { isLoggedIn } = useAuthStore(); + + const resetTimer = useCallback(() => { + if (!isLoggedIn) return; + if (timerRef.current) clearTimeout(timerRef.current); + if (warningTimerRef.current) clearTimeout(warningTimerRef.current); + + timerRef.current = setTimeout(() => { + toastInfo('5초 뒤 자동 로그아웃 예정 \n 움직이면 자동 로그아웃 취소됩니다.'); + warningTimerRef.current = setTimeout(() => handleLogout(), 5000); + }, IDLE_TIMEOUT); + }, [isLoggedIn, handleLogout, toastInfo]); + + useEffect(() => { + const events = ['mousemove', 'keydown', 'mousedown', 'touchstart']; + events.forEach((e) => window.addEventListener(e, resetTimer)); + + resetTimer(); + + return () => { + if (timerRef.current) clearTimeout(timerRef.current); + if (warningTimerRef.current) clearTimeout(warningTimerRef.current); + events.forEach((e) => window.removeEventListener(e, resetTimer)); + }; + }, [resetTimer]); +};