diff --git a/AceJobAgency.client/src/components/SessionTimeout.tsx b/AceJobAgency.client/src/components/SessionTimeout.tsx new file mode 100644 index 0000000..4f333f0 --- /dev/null +++ b/AceJobAgency.client/src/components/SessionTimeout.tsx @@ -0,0 +1,57 @@ +import React, { useState, useEffect, useCallback } from "react"; +import { ToastContainer, toast } from "react-toastify"; +import "react-toastify/dist/ReactToastify.css"; + +interface SessionTimeoutProps { + timeout: number; + onLogout: () => void; +} + +const SessionTimeout: React.FC = ({ + timeout, + onLogout, +}) => { + const [lastActivityTime, setLastActivityTime] = useState(Date.now()); + const [notified, setNotified] = useState(false); + + const resetTimer = useCallback(() => { + setLastActivityTime(Date.now()); + setNotified(false); + }, []); + + useEffect(() => { + const events = ["click", "mousemove", "keypress", "scroll", "touchstart"]; + const eventHandler = () => resetTimer(); + + events.forEach((event) => window.addEventListener(event, eventHandler)); + + return () => { + events.forEach((event) => + window.removeEventListener(event, eventHandler) + ); + }; + }, [resetTimer]); + + useEffect(() => { + const interval = setInterval(() => { + const timeElapsed = Date.now() - lastActivityTime; + + if (timeElapsed >= timeout) { + onLogout(); + } else if (timeElapsed >= timeout - 30000 && !notified) { + toast.warn("30 more seconds before automatic logout from idling."); + setNotified(true); + } + }, 1000); + + return () => clearInterval(interval); + }, [lastActivityTime, timeout, onLogout, notified]); + + return ( + <> + + + ); +}; + +export default SessionTimeout; diff --git a/AceJobAgency.client/src/http.ts b/AceJobAgency.client/src/http.ts index 6691281..1578e84 100644 --- a/AceJobAgency.client/src/http.ts +++ b/AceJobAgency.client/src/http.ts @@ -48,7 +48,7 @@ export function login(token: string) { export function logout() { clearAccessToken(); - window.location.reload(); + window.location.assign("/"); } export function getAccessToken() { diff --git a/AceJobAgency.client/src/main.tsx b/AceJobAgency.client/src/main.tsx index c1ca869..1453950 100644 --- a/AceJobAgency.client/src/main.tsx +++ b/AceJobAgency.client/src/main.tsx @@ -6,6 +6,8 @@ import App from "./App.tsx"; import "./index.css"; import { ToastContainer } from "react-toastify"; import "react-toastify/dist/ReactToastify.css"; +import SessionTimeout from "./components/SessionTimeout.tsx"; +import { getAccessToken, logout } from "./http.ts"; document.addEventListener("contextmenu", (event) => { event.preventDefault(); @@ -18,6 +20,9 @@ ReactDOM.createRoot(document.getElementById("root")!).render(
+ {getAccessToken() && ( + + )}
diff --git a/AceJobAgency.client/src/pages/EditProfilePage.tsx b/AceJobAgency.client/src/pages/EditProfilePage.tsx index 3ad878e..96364b0 100644 --- a/AceJobAgency.client/src/pages/EditProfilePage.tsx +++ b/AceJobAgency.client/src/pages/EditProfilePage.tsx @@ -16,6 +16,7 @@ export default function EditProfilePage() { const accessToken = getAccessToken(); if (!accessToken) { navigate(-1); + return; } http.get("/User/profile").then((response) => { if (response.status !== 200) {