From be403f132d302001945bcbca9d001ee89cb7d0c5 Mon Sep 17 00:00:00 2001 From: "cyril.tsui" Date: Fri, 15 Nov 2024 17:51:31 +0800 Subject: [PATCH 1/2] Add auto logout function in frontend (60 mins); need npm i react-idle-timer --- package.json | 1 + src/app/(main)/layout.tsx | 2 + .../AutoLogoutProvider/AutoLogoutProvider.tsx | 89 +++++++++++++++++++ .../AutoLogoutProviderWrapper.tsx | 14 +++ src/components/AutoLogoutProvider/index.ts | 1 + 5 files changed, 107 insertions(+) create mode 100644 src/components/AutoLogoutProvider/AutoLogoutProvider.tsx create mode 100644 src/components/AutoLogoutProvider/AutoLogoutProviderWrapper.tsx create mode 100644 src/components/AutoLogoutProvider/index.ts diff --git a/package.json b/package.json index a8419eb..5ffa637 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "react-dom": "^18", "react-hook-form": "^7.49.2", "react-i18next": "^13.5.0", + "react-idle-timer": "^5.7.2", "react-intl": "^6.5.5", "react-number-format": "^5.3.4", "react-select": "^5.8.0", diff --git a/src/app/(main)/layout.tsx b/src/app/(main)/layout.tsx index 98a0800..6e658cc 100644 --- a/src/app/(main)/layout.tsx +++ b/src/app/(main)/layout.tsx @@ -1,4 +1,5 @@ import AppBar from "@/components/AppBar"; +import AutoLogoutProvider from "@/components/AutoLogoutProvider"; import { getServerSession } from "next-auth"; import { authOptions } from "@/config/authConfig"; import { redirect } from "next/navigation"; @@ -21,6 +22,7 @@ export default async function MainLayout({ return ( <> + >; +} + +export const TimerContext = createContext(undefined); + +interface AutoLogoutProviderProps { + children?: ReactNode; + isUserLoggedIn: boolean; +} + +const AutoLogoutProvider: React.FC = ({ children, isUserLoggedIn }) => { + const [lastRequestTime, setLastRequestTime] = useState(Date.now()); + const [logoutInterval, setLogoutInterval] = useState(1); // minute + const [state, setState] = useState('Active'); + + const onIdle = () => { + setLastRequestTime(Date.now()); + setState('Idle') + } + + const onActive = () => { + setLastRequestTime(Date.now()); + setState('Active') + } + + const { + isLastActiveTab, + } = useIdleTimer({ + onIdle, + onActive, + timeout: 1_000, + throttle: 500, + crossTab: true, + syncTimers: 200, + }) + + const lastActiveTab = isLastActiveTab() === null ? 'loading' : isLastActiveTab() + + const getLogoutInterval = () => { + if (isUserLoggedIn && logoutInterval === 1) { + //TODO: get auto logout time here + setLogoutInterval(60); + } + else { + if (!isUserLoggedIn && logoutInterval > 1) { + setLogoutInterval(1); + } + } + } + + useEffect(() => { + getLogoutInterval() + const interval = setInterval(async () => { + const currentTime = Date.now(); + // if (isPasswordExpiry()) { + // navigate('/user/changePassword'); + // } + if (state !== "Active" && lastActiveTab) { + const timeElapsed = currentTime - lastRequestTime; + if (timeElapsed >= logoutInterval * 60 * 1000) { + // console.log(timeElapsed / 1000); + // console.log(logoutInterval* 60); + // console.log(logoutInterval * 60 * 1000 - timeElapsed) + signOut() + } + } + }, 1000); // Check every second + + return () => { + clearInterval(interval); + }; + }, [lastRequestTime, logoutInterval]); + + + return ( + + {children} + + ); +}; + +export default AutoLogoutProvider; \ No newline at end of file diff --git a/src/components/AutoLogoutProvider/AutoLogoutProviderWrapper.tsx b/src/components/AutoLogoutProvider/AutoLogoutProviderWrapper.tsx new file mode 100644 index 0000000..ffdc593 --- /dev/null +++ b/src/components/AutoLogoutProvider/AutoLogoutProviderWrapper.tsx @@ -0,0 +1,14 @@ +import React from "react"; +import AutoLogoutProvider from "./AutoLogoutProvider"; +import { getServerSession } from "next-auth"; +import { authOptions } from "@/config/authConfig"; + +const AutoLogoutProviderWrapper: React.FC = async () => { + + const session = await getServerSession(authOptions) + const isUserLoggedIn = session?.user !== null + + return ; +}; + +export default AutoLogoutProviderWrapper; diff --git a/src/components/AutoLogoutProvider/index.ts b/src/components/AutoLogoutProvider/index.ts new file mode 100644 index 0000000..2966d05 --- /dev/null +++ b/src/components/AutoLogoutProvider/index.ts @@ -0,0 +1 @@ +export { default } from "./AutoLogoutProviderWrapper"; \ No newline at end of file From 18ade7df8048e92ee311c242126a2810011b1374 Mon Sep 17 00:00:00 2001 From: "cyril.tsui" Date: Fri, 15 Nov 2024 18:19:10 +0800 Subject: [PATCH 2/2] update --- src/app/(main)/layout.tsx | 2 +- src/components/AutoLogoutProvider/AutoLogoutProvider.tsx | 3 +++ .../ProgressByTeamSearch/ProgressByTeamSearchWrapper.tsx | 4 +++- src/i18n/en/common.json | 2 ++ src/i18n/zh/common.json | 2 ++ 5 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/app/(main)/layout.tsx b/src/app/(main)/layout.tsx index 6e658cc..438a6b9 100644 --- a/src/app/(main)/layout.tsx +++ b/src/app/(main)/layout.tsx @@ -22,7 +22,6 @@ export default async function MainLayout({ return ( <> - + {children} diff --git a/src/components/AutoLogoutProvider/AutoLogoutProvider.tsx b/src/components/AutoLogoutProvider/AutoLogoutProvider.tsx index 73b5684..e82244f 100644 --- a/src/components/AutoLogoutProvider/AutoLogoutProvider.tsx +++ b/src/components/AutoLogoutProvider/AutoLogoutProvider.tsx @@ -2,6 +2,7 @@ import React, { createContext, useState, useEffect, ReactNode } from 'react'; import { useIdleTimer } from "react-idle-timer"; import { signOut } from "next-auth/react"; +import { useTranslation } from 'react-i18next'; interface TimerContextProps { lastRequestTime: number; @@ -16,6 +17,7 @@ interface AutoLogoutProviderProps { } const AutoLogoutProvider: React.FC = ({ children, isUserLoggedIn }) => { + const { t } = useTranslation("common") const [lastRequestTime, setLastRequestTime] = useState(Date.now()); const [logoutInterval, setLogoutInterval] = useState(1); // minute const [state, setState] = useState('Active'); @@ -65,6 +67,7 @@ const AutoLogoutProvider: React.FC = ({ children, isUse if (state !== "Active" && lastActiveTab) { const timeElapsed = currentTime - lastRequestTime; if (timeElapsed >= logoutInterval * 60 * 1000) { + alert(t("Your session has expired, please log in again.")) // console.log(timeElapsed / 1000); // console.log(logoutInterval* 60); // console.log(logoutInterval * 60 * 1000 - timeElapsed) diff --git a/src/components/ProgressByTeamSearch/ProgressByTeamSearchWrapper.tsx b/src/components/ProgressByTeamSearch/ProgressByTeamSearchWrapper.tsx index 33a1e38..e2a61c7 100644 --- a/src/components/ProgressByTeamSearch/ProgressByTeamSearchWrapper.tsx +++ b/src/components/ProgressByTeamSearch/ProgressByTeamSearchWrapper.tsx @@ -10,7 +10,9 @@ interface SubComponents { const ProgressByTeamSearchWrapper: React.FC & SubComponents = async () => { const teamprojects = await fetchTeamProjects(); - return ; + const _teamprojects = teamprojects.length > 1 ? [{id: 0, teamId: 0, teamLeadId: 0, teamCode: "All", teamName: "", projectNo: teamprojects.reduce((acc, cur) => acc + cur.projectNo, 0)}, ...teamprojects] : teamprojects + + return ; }; ProgressByTeamSearchWrapper.Loading = ProgressByTeamSearchLoading; diff --git a/src/i18n/en/common.json b/src/i18n/en/common.json index 6af5bce..c82cd5d 100644 --- a/src/i18n/en/common.json +++ b/src/i18n/en/common.json @@ -6,6 +6,8 @@ "All": "All", + "Your session has expired, please log in again.": "Your session has expired, please log in again.", + "Petty Cash": "Petty Cash", "Expense": "Expense", diff --git a/src/i18n/zh/common.json b/src/i18n/zh/common.json index 4070470..53774bf 100644 --- a/src/i18n/zh/common.json +++ b/src/i18n/zh/common.json @@ -1,6 +1,8 @@ { "All": "全部", + "Your session has expired, please log in again.": "登入驗證已過期, 請重新登入.", + "Petty Cash": "小額開支", "Expense": "普通開支",