From bcf4111cdf45f2198822cfed2eb8ac9808852e00 Mon Sep 17 00:00:00 2001 From: ff Date: Tue, 2 Jun 2026 23:56:21 -0400 Subject: [PATCH] feat: auto-logout after 1 hour of inactivity Tracks user activity events (mouse, keyboard, scroll, touch) and resets a 60-minute idle timer on each event. Shows a warning toast at 55 minutes, then calls logoutMutation at 60 minutes to clear the session. Co-Authored-By: Claude Sonnet 4.6 --- apps/Frontend/src/hooks/use-auth.tsx | 47 +++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/apps/Frontend/src/hooks/use-auth.tsx b/apps/Frontend/src/hooks/use-auth.tsx index 46f1d72d..3839cce2 100755 --- a/apps/Frontend/src/hooks/use-auth.tsx +++ b/apps/Frontend/src/hooks/use-auth.tsx @@ -1,4 +1,4 @@ -import { createContext, ReactNode, useContext } from "react"; +import { createContext, ReactNode, useContext, useEffect, useRef } from "react"; import { useQuery, useMutation, @@ -131,6 +131,51 @@ export function AuthProvider({ children }: { children: ReactNode }) { }, }); + const IDLE_TIMEOUT = 60 * 60 * 1000; + const WARN_BEFORE = 5 * 60 * 1000; + + const logoutTimer = useRef | null>(null); + const warnTimer = useRef | null>(null); + const warnedRef = useRef(false); + + useEffect(() => { + if (!user) return; + + const clearTimers = () => { + if (logoutTimer.current) clearTimeout(logoutTimer.current); + if (warnTimer.current) clearTimeout(warnTimer.current); + }; + + const resetTimers = () => { + clearTimers(); + warnedRef.current = false; + + warnTimer.current = setTimeout(() => { + if (!warnedRef.current) { + warnedRef.current = true; + toast({ + title: "Session expiring soon", + description: "You will be logged out in 5 minutes due to inactivity.", + duration: 10000, + }); + } + }, IDLE_TIMEOUT - WARN_BEFORE); + + logoutTimer.current = setTimeout(() => { + logoutMutation.mutate(); + }, IDLE_TIMEOUT); + }; + + const events = ["mousemove", "mousedown", "keydown", "scroll", "touchstart", "click"]; + events.forEach((e) => window.addEventListener(e, resetTimers, { passive: true })); + resetTimers(); + + return () => { + clearTimers(); + events.forEach((e) => window.removeEventListener(e, resetTimers)); + }; + }, [user]); + return (