@@ -48,6 +48,7 @@ | |||||
"react-dom": "^18", | "react-dom": "^18", | ||||
"react-hook-form": "^7.49.2", | "react-hook-form": "^7.49.2", | ||||
"react-i18next": "^13.5.0", | "react-i18next": "^13.5.0", | ||||
"react-idle-timer": "^5.7.2", | |||||
"react-intl": "^6.5.5", | "react-intl": "^6.5.5", | ||||
"react-number-format": "^5.3.4", | "react-number-format": "^5.3.4", | ||||
"react-select": "^5.8.0", | "react-select": "^5.8.0", | ||||
@@ -1,4 +1,5 @@ | |||||
import AppBar from "@/components/AppBar"; | import AppBar from "@/components/AppBar"; | ||||
import AutoLogoutProvider from "@/components/AutoLogoutProvider"; | |||||
import { getServerSession } from "next-auth"; | import { getServerSession } from "next-auth"; | ||||
import { authOptions } from "@/config/authConfig"; | import { authOptions } from "@/config/authConfig"; | ||||
import { redirect } from "next/navigation"; | import { redirect } from "next/navigation"; | ||||
@@ -33,6 +34,7 @@ export default async function MainLayout({ | |||||
}} | }} | ||||
> | > | ||||
<I18nProvider namespaces={["common"]}> | <I18nProvider namespaces={["common"]}> | ||||
<AutoLogoutProvider/> | |||||
<Stack spacing={2}> | <Stack spacing={2}> | ||||
<Breadcrumb /> | <Breadcrumb /> | ||||
{children} | {children} | ||||
@@ -0,0 +1,92 @@ | |||||
'use client' | |||||
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; | |||||
setLastRequestTime: React.Dispatch<React.SetStateAction<number>>; | |||||
} | |||||
export const TimerContext = createContext<TimerContextProps | undefined>(undefined); | |||||
interface AutoLogoutProviderProps { | |||||
children?: ReactNode; | |||||
isUserLoggedIn: boolean; | |||||
} | |||||
const AutoLogoutProvider: React.FC<AutoLogoutProviderProps> = ({ children, isUserLoggedIn }) => { | |||||
const { t } = useTranslation("common") | |||||
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) { | |||||
alert(t("Your session has expired, please log in again.")) | |||||
// 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 ( | |||||
<TimerContext.Provider value={{ lastRequestTime, setLastRequestTime }}> | |||||
{children} | |||||
</TimerContext.Provider> | |||||
); | |||||
}; | |||||
export default AutoLogoutProvider; |
@@ -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 <AutoLogoutProvider isUserLoggedIn={isUserLoggedIn}/>; | |||||
}; | |||||
export default AutoLogoutProviderWrapper; |
@@ -0,0 +1 @@ | |||||
export { default } from "./AutoLogoutProviderWrapper"; |
@@ -10,7 +10,9 @@ interface SubComponents { | |||||
const ProgressByTeamSearchWrapper: React.FC & SubComponents = async () => { | const ProgressByTeamSearchWrapper: React.FC & SubComponents = async () => { | ||||
const teamprojects = await fetchTeamProjects(); | const teamprojects = await fetchTeamProjects(); | ||||
return <ProgressByTeamSearch projects={teamprojects} />; | |||||
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 <ProgressByTeamSearch projects={_teamprojects} />; | |||||
}; | }; | ||||
ProgressByTeamSearchWrapper.Loading = ProgressByTeamSearchLoading; | ProgressByTeamSearchWrapper.Loading = ProgressByTeamSearchLoading; | ||||
@@ -6,6 +6,8 @@ | |||||
"All": "All", | "All": "All", | ||||
"Your session has expired, please log in again.": "Your session has expired, please log in again.", | |||||
"Petty Cash": "Petty Cash", | "Petty Cash": "Petty Cash", | ||||
"Expense": "Expense", | "Expense": "Expense", | ||||
@@ -1,6 +1,8 @@ | |||||
{ | { | ||||
"All": "全部", | "All": "全部", | ||||
"Your session has expired, please log in again.": "登入驗證已過期, 請重新登入.", | |||||
"Petty Cash": "小額開支", | "Petty Cash": "小額開支", | ||||
"Expense": "普通開支", | "Expense": "普通開支", | ||||