From 4de9cfe86feceb918f688a12fd29648bacdb9a2f Mon Sep 17 00:00:00 2001 From: Lina Date: Mon, 22 Sep 2025 17:29:06 +0200 Subject: [PATCH 01/16] Auto logout progress bar --- components/AutoLogoutProgressBar.tsx | 172 +++++++++++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 components/AutoLogoutProgressBar.tsx diff --git a/components/AutoLogoutProgressBar.tsx b/components/AutoLogoutProgressBar.tsx new file mode 100644 index 0000000..a8ab663 --- /dev/null +++ b/components/AutoLogoutProgressBar.tsx @@ -0,0 +1,172 @@ +import { selectTempValue, setTempVariable, TempStorageEnum } from "@/src/reduxStore/states/temps"; +import { selectUser } from "@/src/reduxStore/states/user"; +import { logout } from "@/src/services/auth"; +import { formatTime } from "@/submodules/javascript-functions/date-parser"; +import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useDispatch, useSelector } from "react-redux"; + +type AutoLogoutProgressBarProps = { + className?: string; +} + +function isReload() { + if ("performance" in window) { + const navEntries = performance.getEntriesByType("navigation") as PerformanceNavigationTiming[]; + if (navEntries.length > 0) { + return navEntries[0].type === "reload"; + } + return (performance as any).navigation?.type === 1; + } + return false; +}; + +export default function AutoLogoutProgressBar(props: AutoLogoutProgressBarProps) { + const dispatch = useDispatch(); + const { t } = useTranslation('projectOverview'); + + const user = useSelector(selectUser); + + const [showProgressBar, setShowProgressBar] = useState(false); + const [remainingMinutes, setRemainingMinutes] = useState(0); + const [completeCalled, setCompleteCalled] = useState(false); + + const lastInteractionRef = useRef(Date.now()); + + useEffect(() => { + const resetTimer = () => { + lastInteractionRef.current = Date.now(); + dispatch(setTempVariable(TempStorageEnum.RESET_LOGOUT_TIMER, "X")); + } + + const onKeyDownEvent = (e: KeyboardEvent) => { + const target = e.target as HTMLElement; + // used for the chat input (we want to trigger rest on typing) + if (target.tagName === "INPUT" || target.tagName === "TEXTAREA") { + resetTimer(); + } + }; + + window.addEventListener("click", resetTimer); + window.addEventListener("keydown", onKeyDownEvent); + return () => { + window.removeEventListener("click", resetTimer); + window.removeEventListener("keydown", onKeyDownEvent); + }; + }, []); + + useEffect(() => { + const autoLogoutMinutes = user?.autoLogoutMinutes; + if (!autoLogoutMinutes) { + setShowProgressBar(false); + setRemainingMinutes(0); + return; + } + + setRemainingMinutes(Math.ceil(autoLogoutMinutes)); + + const checkInactivity = () => { + const now = Date.now(); + const inactiveMinutes = (now - lastInteractionRef.current) / 1000 / 60; + const minutesLeft = autoLogoutMinutes - inactiveMinutes; + setShowProgressBar(minutesLeft <= 5 && minutesLeft > 0); + }; + + checkInactivity(); + const interval = setInterval(checkInactivity, 1000); + return () => clearInterval(interval); + }, [user?.autoLogoutMinutes]); + + const logoutUser = useCallback(() => { + localStorage.removeItem("lastClosedAt"); + logout(); + }, []); + + const onCompleteFunc = useCallback(() => { + setCompleteCalled(true); + logoutUser(); + setTimeout(() => { + setCompleteCalled(false); + }, 1000); + }, []); + + useEffect(() => { + if (isReload()) return; + const handleBeforeUnload = () => { + const nowIso = new Date().toISOString(); + if (!localStorage.getItem("lastClosedAt") && !completeCalled) localStorage.setItem("lastClosedAt", nowIso); + }; + window.addEventListener("beforeunload", handleBeforeUnload); + const stored = localStorage.getItem("lastClosedAt"); + if (stored) { + window.removeEventListener("beforeunload", handleBeforeUnload); + logoutUser(); + } + return () => { + window.removeEventListener("beforeunload", handleBeforeUnload); + }; + }, [completeCalled]); + + return <> + {showProgressBar && } + +} + +type ReverseProgressBarProps = { + duration: number; + onComplete: () => void; + label: string; + className?: string; +}; + +function ReverseProgressBar(props: ReverseProgressBarProps) { + const dispatch = useDispatch(); + const resetLogoutTimer = useSelector(selectTempValue(TempStorageEnum.RESET_LOGOUT_TIMER)); + const [remaining, setRemaining] = useState(props.duration); + + useEffect(() => { + if (!resetLogoutTimer) return; + dispatch(setTempVariable(TempStorageEnum.RESET_LOGOUT_TIMER, null)); + setRemaining(props.duration); + }, [resetLogoutTimer]); + + useEffect(() => { + setRemaining(props.duration); + + const tick = () => { + setRemaining(prev => { + if (prev - 1 <= 0) { + if (props.onComplete) props.onComplete(); + clearInterval(interval); + return 0; + } + return prev - 1; + }); + }; + const interval = setInterval(tick, 1000); + + return () => { + clearInterval(interval); + }; + }, [props.duration, props.onComplete]); + + const progressPercent = useMemo(() => { + return Math.max(0, Math.min(100, (remaining / Math.max(1, props.duration)) * 100)); + }, [remaining, props.duration]); + + return ( +
+
+
+
+
+ {formatTime(remaining)} +
+
+ {props.label &&
{props.label}
} +
+ ); +} From d8eab6d4360eeb1b083216e92be925856145ec32 Mon Sep 17 00:00:00 2001 From: Lina Date: Tue, 23 Sep 2025 13:40:36 +0200 Subject: [PATCH 02/16] global listeners removed from submodules --- components/AutoLogoutProgressBar.tsx | 55 ++++++++++------------------ 1 file changed, 20 insertions(+), 35 deletions(-) diff --git a/components/AutoLogoutProgressBar.tsx b/components/AutoLogoutProgressBar.tsx index a8ab663..79220c1 100644 --- a/components/AutoLogoutProgressBar.tsx +++ b/components/AutoLogoutProgressBar.tsx @@ -1,12 +1,10 @@ -import { selectTempValue, setTempVariable, TempStorageEnum } from "@/src/reduxStore/states/temps"; -import { selectUser } from "@/src/reduxStore/states/user"; -import { logout } from "@/src/services/auth"; +import { FetchType, jsonFetchWrapper } from "@/submodules/javascript-functions/basic-fetch"; import { formatTime } from "@/submodules/javascript-functions/date-parser"; -import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; -import { useDispatch, useSelector } from "react-redux"; type AutoLogoutProgressBarProps = { + autoLogoutMinutes: number | null; className?: string; } @@ -21,11 +19,14 @@ function isReload() { return false; }; -export default function AutoLogoutProgressBar(props: AutoLogoutProgressBarProps) { - const dispatch = useDispatch(); - const { t } = useTranslation('projectOverview'); +const AUTH_BASE_URI = '/.ory/kratos/public/self-service/'; +function logout() { + const url = `${AUTH_BASE_URI}logout/browser`; + jsonFetchWrapper(url, FetchType.GET, (result) => { window.location.href = result.logout_url }); +} - const user = useSelector(selectUser); +export const AutoLogoutProgressBar = forwardRef((props: AutoLogoutProgressBarProps, ref) => { + const { t } = useTranslation('projectOverview'); const [showProgressBar, setShowProgressBar] = useState(false); const [remainingMinutes, setRemainingMinutes] = useState(0); @@ -33,37 +34,22 @@ export default function AutoLogoutProgressBar(props: AutoLogoutProgressBarProps) const lastInteractionRef = useRef(Date.now()); - useEffect(() => { - const resetTimer = () => { + useImperativeHandle(ref, () => ({ + resetTimer: () => { lastInteractionRef.current = Date.now(); - dispatch(setTempVariable(TempStorageEnum.RESET_LOGOUT_TIMER, "X")); + localStorage.setItem("resetLogoutTimer", "X"); } - - const onKeyDownEvent = (e: KeyboardEvent) => { - const target = e.target as HTMLElement; - // used for the chat input (we want to trigger rest on typing) - if (target.tagName === "INPUT" || target.tagName === "TEXTAREA") { - resetTimer(); - } - }; - - window.addEventListener("click", resetTimer); - window.addEventListener("keydown", onKeyDownEvent); - return () => { - window.removeEventListener("click", resetTimer); - window.removeEventListener("keydown", onKeyDownEvent); - }; - }, []); + })); useEffect(() => { - const autoLogoutMinutes = user?.autoLogoutMinutes; + const autoLogoutMinutes = props?.autoLogoutMinutes; if (!autoLogoutMinutes) { setShowProgressBar(false); setRemainingMinutes(0); return; } - setRemainingMinutes(Math.ceil(autoLogoutMinutes)); + setRemainingMinutes(autoLogoutMinutes <= 5 ? autoLogoutMinutes : 5); const checkInactivity = () => { const now = Date.now(); @@ -75,7 +61,7 @@ export default function AutoLogoutProgressBar(props: AutoLogoutProgressBarProps) checkInactivity(); const interval = setInterval(checkInactivity, 1000); return () => clearInterval(interval); - }, [user?.autoLogoutMinutes]); + }, [props?.autoLogoutMinutes]); const logoutUser = useCallback(() => { localStorage.removeItem("lastClosedAt"); @@ -110,7 +96,7 @@ export default function AutoLogoutProgressBar(props: AutoLogoutProgressBarProps) return <> {showProgressBar && } -} +}); type ReverseProgressBarProps = { duration: number; @@ -120,13 +106,12 @@ type ReverseProgressBarProps = { }; function ReverseProgressBar(props: ReverseProgressBarProps) { - const dispatch = useDispatch(); - const resetLogoutTimer = useSelector(selectTempValue(TempStorageEnum.RESET_LOGOUT_TIMER)); + const resetLogoutTimer = localStorage.getItem("resetLogoutTimer"); const [remaining, setRemaining] = useState(props.duration); useEffect(() => { if (!resetLogoutTimer) return; - dispatch(setTempVariable(TempStorageEnum.RESET_LOGOUT_TIMER, null)); + localStorage.setItem("resetLogoutTimer", null); setRemaining(props.duration); }, [resetLogoutTimer]); From 2514a0a3b67ae75eda973d20b47fba8653d13c44 Mon Sep 17 00:00:00 2001 From: Lina Date: Tue, 23 Sep 2025 13:59:52 +0200 Subject: [PATCH 03/16] added prevent logout --- components/AutoLogoutProgressBar.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/components/AutoLogoutProgressBar.tsx b/components/AutoLogoutProgressBar.tsx index 79220c1..c76d8a0 100644 --- a/components/AutoLogoutProgressBar.tsx +++ b/components/AutoLogoutProgressBar.tsx @@ -6,6 +6,7 @@ import { useTranslation } from "react-i18next"; type AutoLogoutProgressBarProps = { autoLogoutMinutes: number | null; className?: string; + preventLogout?: boolean; } function isReload() { @@ -77,7 +78,7 @@ export const AutoLogoutProgressBar = forwardRef((props: AutoLogoutProgressBarPro }, []); useEffect(() => { - if (isReload()) return; + if (isReload() || props.preventLogout) return; const handleBeforeUnload = () => { const nowIso = new Date().toISOString(); if (!localStorage.getItem("lastClosedAt") && !completeCalled) localStorage.setItem("lastClosedAt", nowIso); @@ -91,7 +92,7 @@ export const AutoLogoutProgressBar = forwardRef((props: AutoLogoutProgressBarPro return () => { window.removeEventListener("beforeunload", handleBeforeUnload); }; - }, [completeCalled]); + }, [completeCalled, props.preventLogout]); return <> {showProgressBar && } From a3f9dd1e21fb2e76c00425419afdfd44f2e68f18 Mon Sep 17 00:00:00 2001 From: Lina Date: Tue, 23 Sep 2025 15:46:01 +0200 Subject: [PATCH 04/16] Label send as param --- components/AutoLogoutProgressBar.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/components/AutoLogoutProgressBar.tsx b/components/AutoLogoutProgressBar.tsx index c76d8a0..cbd5830 100644 --- a/components/AutoLogoutProgressBar.tsx +++ b/components/AutoLogoutProgressBar.tsx @@ -1,10 +1,10 @@ import { FetchType, jsonFetchWrapper } from "@/submodules/javascript-functions/basic-fetch"; import { formatTime } from "@/submodules/javascript-functions/date-parser"; import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react"; -import { useTranslation } from "react-i18next"; type AutoLogoutProgressBarProps = { autoLogoutMinutes: number | null; + label: string; className?: string; preventLogout?: boolean; } @@ -27,8 +27,6 @@ function logout() { } export const AutoLogoutProgressBar = forwardRef((props: AutoLogoutProgressBarProps, ref) => { - const { t } = useTranslation('projectOverview'); - const [showProgressBar, setShowProgressBar] = useState(false); const [remainingMinutes, setRemainingMinutes] = useState(0); const [completeCalled, setCompleteCalled] = useState(false); @@ -95,7 +93,7 @@ export const AutoLogoutProgressBar = forwardRef((props: AutoLogoutProgressBarPro }, [completeCalled, props.preventLogout]); return <> - {showProgressBar && } + {showProgressBar && } }); From 1d5bc94e6896274a1a52591f757caec2bc686da9 Mon Sep 17 00:00:00 2001 From: Lina Date: Wed, 24 Sep 2025 14:04:40 +0200 Subject: [PATCH 05/16] Test --- components/AutoLogoutProgressBar.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/components/AutoLogoutProgressBar.tsx b/components/AutoLogoutProgressBar.tsx index c76d8a0..40988f0 100644 --- a/components/AutoLogoutProgressBar.tsx +++ b/components/AutoLogoutProgressBar.tsx @@ -32,12 +32,14 @@ export const AutoLogoutProgressBar = forwardRef((props: AutoLogoutProgressBarPro const [showProgressBar, setShowProgressBar] = useState(false); const [remainingMinutes, setRemainingMinutes] = useState(0); const [completeCalled, setCompleteCalled] = useState(false); + const [signal, setSignal] = useState(0); const lastInteractionRef = useRef(Date.now()); useImperativeHandle(ref, () => ({ resetTimer: () => { lastInteractionRef.current = Date.now(); + setSignal(prev => prev + 1); localStorage.setItem("resetLogoutTimer", "X"); } })); @@ -78,7 +80,11 @@ export const AutoLogoutProgressBar = forwardRef((props: AutoLogoutProgressBarPro }, []); useEffect(() => { - if (isReload() || props.preventLogout) return; + if (isReload()) return; + if (localStorage.getItem("comesFromEntry") === "true") { + localStorage.setItem("comesFromEntry", "false"); + return; + } const handleBeforeUnload = () => { const nowIso = new Date().toISOString(); if (!localStorage.getItem("lastClosedAt") && !completeCalled) localStorage.setItem("lastClosedAt", nowIso); @@ -95,7 +101,7 @@ export const AutoLogoutProgressBar = forwardRef((props: AutoLogoutProgressBarPro }, [completeCalled, props.preventLogout]); return <> - {showProgressBar && } + {showProgressBar && } }); @@ -104,6 +110,7 @@ type ReverseProgressBarProps = { onComplete: () => void; label: string; className?: string; + signal: number; // To force re-render }; function ReverseProgressBar(props: ReverseProgressBarProps) { From 2dae06f600b5ee577b198c17a7cab4da45625fba Mon Sep 17 00:00:00 2001 From: Lina Date: Wed, 24 Sep 2025 15:07:17 +0200 Subject: [PATCH 06/16] test --- components/AutoLogoutProgressBar.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/components/AutoLogoutProgressBar.tsx b/components/AutoLogoutProgressBar.tsx index c85f23f..6ea7284 100644 --- a/components/AutoLogoutProgressBar.tsx +++ b/components/AutoLogoutProgressBar.tsx @@ -71,6 +71,7 @@ export const AutoLogoutProgressBar = forwardRef((props: AutoLogoutProgressBarPro const onCompleteFunc = useCallback(() => { setCompleteCalled(true); + console.log("called 1") logoutUser(); setTimeout(() => { setCompleteCalled(false); @@ -79,7 +80,7 @@ export const AutoLogoutProgressBar = forwardRef((props: AutoLogoutProgressBarPro useEffect(() => { if (isReload()) return; - if (localStorage.getItem("comesFromEntry") === "true") { + if (localStorage.getItem("comesFromEntry") === "true" && props.autoLogoutMinutes) { localStorage.setItem("comesFromEntry", "false"); return; } @@ -89,14 +90,15 @@ export const AutoLogoutProgressBar = forwardRef((props: AutoLogoutProgressBarPro }; window.addEventListener("beforeunload", handleBeforeUnload); const stored = localStorage.getItem("lastClosedAt"); - if (stored) { + if (stored && props.autoLogoutMinutes) { window.removeEventListener("beforeunload", handleBeforeUnload); + console.log("called 2") logoutUser(); } return () => { window.removeEventListener("beforeunload", handleBeforeUnload); }; - }, [completeCalled, props.preventLogout]); + }, [completeCalled, props.preventLogout, props.autoLogoutMinutes]); return <> {showProgressBar && } From a885f89f8a479f95741164e29915d76560e38acc Mon Sep 17 00:00:00 2001 From: Lina Date: Wed, 24 Sep 2025 15:31:23 +0200 Subject: [PATCH 07/16] test --- components/AutoLogoutProgressBar.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/components/AutoLogoutProgressBar.tsx b/components/AutoLogoutProgressBar.tsx index 6ea7284..2fe6633 100644 --- a/components/AutoLogoutProgressBar.tsx +++ b/components/AutoLogoutProgressBar.tsx @@ -5,6 +5,7 @@ import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo type AutoLogoutProgressBarProps = { autoLogoutMinutes: number | null; label: string; + comesFromEntry?: boolean; className?: string; preventLogout?: boolean; } @@ -81,7 +82,9 @@ export const AutoLogoutProgressBar = forwardRef((props: AutoLogoutProgressBarPro useEffect(() => { if (isReload()) return; if (localStorage.getItem("comesFromEntry") === "true" && props.autoLogoutMinutes) { - localStorage.setItem("comesFromEntry", "false"); + if (!props.comesFromEntry) { + localStorage.setItem("comesFromEntry", "false"); + } return; } const handleBeforeUnload = () => { From c3c5de815761556e199857750d709819738b1d22 Mon Sep 17 00:00:00 2001 From: Lina Date: Wed, 24 Sep 2025 15:46:52 +0200 Subject: [PATCH 08/16] test --- components/AutoLogoutProgressBar.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/components/AutoLogoutProgressBar.tsx b/components/AutoLogoutProgressBar.tsx index 6ea7284..d9d72db 100644 --- a/components/AutoLogoutProgressBar.tsx +++ b/components/AutoLogoutProgressBar.tsx @@ -71,7 +71,6 @@ export const AutoLogoutProgressBar = forwardRef((props: AutoLogoutProgressBarPro const onCompleteFunc = useCallback(() => { setCompleteCalled(true); - console.log("called 1") logoutUser(); setTimeout(() => { setCompleteCalled(false); @@ -92,13 +91,12 @@ export const AutoLogoutProgressBar = forwardRef((props: AutoLogoutProgressBarPro const stored = localStorage.getItem("lastClosedAt"); if (stored && props.autoLogoutMinutes) { window.removeEventListener("beforeunload", handleBeforeUnload); - console.log("called 2") logoutUser(); } return () => { window.removeEventListener("beforeunload", handleBeforeUnload); }; - }, [completeCalled, props.preventLogout, props.autoLogoutMinutes]); + }, [completeCalled, props.preventLogout]); return <> {showProgressBar && } From 9887c8f615ec263a41f12d8c0826ff9c1a19d4e6 Mon Sep 17 00:00:00 2001 From: Lina Date: Wed, 24 Sep 2025 16:13:24 +0200 Subject: [PATCH 09/16] test --- components/AutoLogoutProgressBar.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/AutoLogoutProgressBar.tsx b/components/AutoLogoutProgressBar.tsx index f55deb9..927c248 100644 --- a/components/AutoLogoutProgressBar.tsx +++ b/components/AutoLogoutProgressBar.tsx @@ -81,6 +81,7 @@ export const AutoLogoutProgressBar = forwardRef((props: AutoLogoutProgressBarPro useEffect(() => { if (isReload()) return; if (localStorage.getItem("comesFromEntry") === "true" && props.autoLogoutMinutes) { + console.log("Setting comesFromEntry to false", props.comesFromEntry); if (!props.comesFromEntry) { localStorage.setItem("comesFromEntry", "false"); } @@ -99,7 +100,7 @@ export const AutoLogoutProgressBar = forwardRef((props: AutoLogoutProgressBarPro return () => { window.removeEventListener("beforeunload", handleBeforeUnload); }; - }, [completeCalled, props.preventLogout]); + }, [completeCalled, props.preventLogout, props.comesFromEntry]); return <> {showProgressBar && } From cf0e3b13ef69b21e689d9f9cc13ea56fe500e73b Mon Sep 17 00:00:00 2001 From: Lina Date: Wed, 24 Sep 2025 16:46:41 +0200 Subject: [PATCH 10/16] test --- components/AutoLogoutProgressBar.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/AutoLogoutProgressBar.tsx b/components/AutoLogoutProgressBar.tsx index f55deb9..0cd0d8f 100644 --- a/components/AutoLogoutProgressBar.tsx +++ b/components/AutoLogoutProgressBar.tsx @@ -7,7 +7,6 @@ type AutoLogoutProgressBarProps = { label: string; comesFromEntry?: boolean; className?: string; - preventLogout?: boolean; } function isReload() { @@ -81,6 +80,7 @@ export const AutoLogoutProgressBar = forwardRef((props: AutoLogoutProgressBarPro useEffect(() => { if (isReload()) return; if (localStorage.getItem("comesFromEntry") === "true" && props.autoLogoutMinutes) { + localStorage.removeItem("lastClosedAt"); if (!props.comesFromEntry) { localStorage.setItem("comesFromEntry", "false"); } @@ -99,7 +99,7 @@ export const AutoLogoutProgressBar = forwardRef((props: AutoLogoutProgressBarPro return () => { window.removeEventListener("beforeunload", handleBeforeUnload); }; - }, [completeCalled, props.preventLogout]); + }, [completeCalled]); return <> {showProgressBar && } From 22145ff554398243c616a55c79a9bc6da4633dd9 Mon Sep 17 00:00:00 2001 From: Lina Date: Thu, 25 Sep 2025 08:40:19 +0200 Subject: [PATCH 11/16] test --- components/AutoLogoutProgressBar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/AutoLogoutProgressBar.tsx b/components/AutoLogoutProgressBar.tsx index 9a8b1ea..b508455 100644 --- a/components/AutoLogoutProgressBar.tsx +++ b/components/AutoLogoutProgressBar.tsx @@ -122,7 +122,7 @@ function ReverseProgressBar(props: ReverseProgressBarProps) { if (!resetLogoutTimer) return; localStorage.setItem("resetLogoutTimer", null); setRemaining(props.duration); - }, [resetLogoutTimer]); + }, [resetLogoutTimer, props.signal]); useEffect(() => { setRemaining(props.duration); From e0b7bf896e9968e6795a503370655f80fe9b1ac1 Mon Sep 17 00:00:00 2001 From: Lina Date: Thu, 25 Sep 2025 09:24:50 +0200 Subject: [PATCH 12/16] test --- components/AutoLogoutProgressBar.tsx | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/components/AutoLogoutProgressBar.tsx b/components/AutoLogoutProgressBar.tsx index b508455..92286c4 100644 --- a/components/AutoLogoutProgressBar.tsx +++ b/components/AutoLogoutProgressBar.tsx @@ -122,18 +122,13 @@ function ReverseProgressBar(props: ReverseProgressBarProps) { if (!resetLogoutTimer) return; localStorage.setItem("resetLogoutTimer", null); setRemaining(props.duration); - }, [resetLogoutTimer, props.signal]); + }, [resetLogoutTimer, props.duration]); useEffect(() => { setRemaining(props.duration); const tick = () => { setRemaining(prev => { - if (prev - 1 <= 0) { - if (props.onComplete) props.onComplete(); - clearInterval(interval); - return 0; - } return prev - 1; }); }; @@ -142,7 +137,13 @@ function ReverseProgressBar(props: ReverseProgressBarProps) { return () => { clearInterval(interval); }; - }, [props.duration, props.onComplete]); + }, [props.duration]); + + useEffect(() => { + if (remaining === 1) { + props.onComplete(); + } + }, [remaining]); const progressPercent = useMemo(() => { return Math.max(0, Math.min(100, (remaining / Math.max(1, props.duration)) * 100)); From df823935467380b9948e687d359a8f09bc27b379 Mon Sep 17 00:00:00 2001 From: Lina Date: Fri, 26 Sep 2025 08:52:12 +0200 Subject: [PATCH 13/16] PR comments --- components/AutoLogoutProgressBar.tsx | 53 +++++++++++++++++++--------- 1 file changed, 36 insertions(+), 17 deletions(-) diff --git a/components/AutoLogoutProgressBar.tsx b/components/AutoLogoutProgressBar.tsx index 92286c4..b1eda02 100644 --- a/components/AutoLogoutProgressBar.tsx +++ b/components/AutoLogoutProgressBar.tsx @@ -1,6 +1,6 @@ import { FetchType, jsonFetchWrapper } from "@/submodules/javascript-functions/basic-fetch"; import { formatTime } from "@/submodules/javascript-functions/date-parser"; -import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react"; +import React, { useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react"; type AutoLogoutProgressBarProps = { autoLogoutMinutes: number | null; @@ -26,21 +26,34 @@ function logout() { jsonFetchWrapper(url, FetchType.GET, (result) => { window.location.href = result.logout_url }); } -export const AutoLogoutProgressBar = forwardRef((props: AutoLogoutProgressBarProps, ref) => { +export default function AutoLogoutProgressBar(props: AutoLogoutProgressBarProps) { const [showProgressBar, setShowProgressBar] = useState(false); const [remainingMinutes, setRemainingMinutes] = useState(0); const [completeCalled, setCompleteCalled] = useState(false); - const [signal, setSignal] = useState(0); + const progressBarRef = useRef(null); const lastInteractionRef = useRef(Date.now()); - useImperativeHandle(ref, () => ({ - resetTimer: () => { + useEffect(() => { + const resetTimer = () => { lastInteractionRef.current = Date.now(); - setSignal(prev => prev + 1); + progressBarRef.current?.resetTimer(); localStorage.setItem("resetLogoutTimer", "X"); } - })); + const onKeyDownEvent = (e: KeyboardEvent) => { + const target = e.target as HTMLElement; + // used for the chat input (we want to trigger rest on typing) + if (target.tagName === "INPUT" || target.tagName === "TEXTAREA") { + resetTimer(); + } + }; + window.addEventListener("click", resetTimer); + window.addEventListener("keydown", onKeyDownEvent) + return () => { + window.removeEventListener("click", resetTimer); + window.removeEventListener("keydown", onKeyDownEvent) + }; + }, []); useEffect(() => { const autoLogoutMinutes = props?.autoLogoutMinutes; @@ -102,16 +115,18 @@ export const AutoLogoutProgressBar = forwardRef((props: AutoLogoutProgressBarPro }, [completeCalled, props.comesFromEntry]); return <> - {showProgressBar && } + {showProgressBar && } -}); +}; type ReverseProgressBarProps = { duration: number; onComplete: () => void; label: string; className?: string; - signal: number; // To force re-render + ref?: React.Ref<{ + resetTimer: () => void; + }>; }; function ReverseProgressBar(props: ReverseProgressBarProps) { @@ -124,27 +139,31 @@ function ReverseProgressBar(props: ReverseProgressBarProps) { setRemaining(props.duration); }, [resetLogoutTimer, props.duration]); + useImperativeHandle(props.ref, () => ({ + resetTimer: () => { + setRemaining(props.duration); + } + })); + useEffect(() => { setRemaining(props.duration); const tick = () => { setRemaining(prev => { + if (prev - 1 <= 0) { + if (props.onComplete) props.onComplete(); + clearInterval(interval); + return 0; + } return prev - 1; }); }; const interval = setInterval(tick, 1000); - return () => { clearInterval(interval); }; }, [props.duration]); - useEffect(() => { - if (remaining === 1) { - props.onComplete(); - } - }, [remaining]); - const progressPercent = useMemo(() => { return Math.max(0, Math.min(100, (remaining / Math.max(1, props.duration)) * 100)); }, [remaining, props.duration]); From 4ea6988cadf4ba5c4b2283260e32410f751fda81 Mon Sep 17 00:00:00 2001 From: Lina Date: Fri, 26 Sep 2025 08:54:39 +0200 Subject: [PATCH 14/16] PR comments --- components/AutoLogoutProgressBar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/AutoLogoutProgressBar.tsx b/components/AutoLogoutProgressBar.tsx index b1eda02..f3bed99 100644 --- a/components/AutoLogoutProgressBar.tsx +++ b/components/AutoLogoutProgressBar.tsx @@ -69,7 +69,7 @@ export default function AutoLogoutProgressBar(props: AutoLogoutProgressBarProps) const now = Date.now(); const inactiveMinutes = (now - lastInteractionRef.current) / 1000 / 60; const minutesLeft = autoLogoutMinutes - inactiveMinutes; - setShowProgressBar(minutesLeft <= 5 && minutesLeft > 0); + setShowProgressBar(minutesLeft <= 5); }; checkInactivity(); From 1eb4093ccda23031c502c74a68b74d699e00bf8d Mon Sep 17 00:00:00 2001 From: Lina Date: Fri, 26 Sep 2025 09:13:38 +0200 Subject: [PATCH 15/16] PR comments --- components/AutoLogoutProgressBar.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/AutoLogoutProgressBar.tsx b/components/AutoLogoutProgressBar.tsx index f3bed99..0770dc4 100644 --- a/components/AutoLogoutProgressBar.tsx +++ b/components/AutoLogoutProgressBar.tsx @@ -1,5 +1,5 @@ import { FetchType, jsonFetchWrapper } from "@/submodules/javascript-functions/basic-fetch"; -import { formatTime } from "@/submodules/javascript-functions/date-parser"; +import { formatTimeDigitalClock } from "@/submodules/javascript-functions/date-parser"; import React, { useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react"; type AutoLogoutProgressBarProps = { @@ -177,7 +177,7 @@ function ReverseProgressBar(props: ReverseProgressBarProps) { className="absolute top-0 bottom-0 right-0 bg-gray-200" />
- {formatTime(remaining)} + {formatTimeDigitalClock(remaining)}
{props.label &&
{props.label}
} From 3241ea49c63fd1fc18881b623b474e108915704a Mon Sep 17 00:00:00 2001 From: Lina Date: Fri, 26 Sep 2025 14:27:57 +0200 Subject: [PATCH 16/16] PR comments --- components/AutoLogoutProgressBar.tsx | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/components/AutoLogoutProgressBar.tsx b/components/AutoLogoutProgressBar.tsx index 0770dc4..f616972 100644 --- a/components/AutoLogoutProgressBar.tsx +++ b/components/AutoLogoutProgressBar.tsx @@ -1,6 +1,6 @@ import { FetchType, jsonFetchWrapper } from "@/submodules/javascript-functions/basic-fetch"; import { formatTimeDigitalClock } from "@/submodules/javascript-functions/date-parser"; -import React, { useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react"; +import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react"; type AutoLogoutProgressBarProps = { autoLogoutMinutes: number | null; @@ -38,8 +38,8 @@ export default function AutoLogoutProgressBar(props: AutoLogoutProgressBarProps) const resetTimer = () => { lastInteractionRef.current = Date.now(); progressBarRef.current?.resetTimer(); - localStorage.setItem("resetLogoutTimer", "X"); } + const onKeyDownEvent = (e: KeyboardEvent) => { const target = e.target as HTMLElement; // used for the chat input (we want to trigger rest on typing) @@ -124,22 +124,16 @@ type ReverseProgressBarProps = { onComplete: () => void; label: string; className?: string; - ref?: React.Ref<{ - resetTimer: () => void; - }>; }; -function ReverseProgressBar(props: ReverseProgressBarProps) { - const resetLogoutTimer = localStorage.getItem("resetLogoutTimer"); +const ReverseProgressBar = forwardRef((props: ReverseProgressBarProps, ref) => { const [remaining, setRemaining] = useState(props.duration); useEffect(() => { - if (!resetLogoutTimer) return; - localStorage.setItem("resetLogoutTimer", null); setRemaining(props.duration); - }, [resetLogoutTimer, props.duration]); + }, [props.duration]); - useImperativeHandle(props.ref, () => ({ + useImperativeHandle(ref, () => ({ resetTimer: () => { setRemaining(props.duration); } @@ -183,4 +177,4 @@ function ReverseProgressBar(props: ReverseProgressBarProps) { {props.label &&
{props.label}
}
); -} +});