Skip to content

Commit

Permalink
Merge pull request #624 from Enterprise-CMCS/master
Browse files Browse the repository at this point in the history
Release to val
  • Loading branch information
mdial89f authored Jul 3, 2024
2 parents 13f0089 + d9d6676 commit e93578c
Show file tree
Hide file tree
Showing 7 changed files with 216 additions and 3 deletions.
2 changes: 2 additions & 0 deletions src/services/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
"lucide-react": "^0.291.0",
"lz-string": "^1.5.0",
"moment-timezone": "^0.5.44",
"pluralize": "^8.0.0",
"react": "^18.2.0",
"react-day-picker": "^8.8.1",
"react-dom": "^18.2.0",
Expand All @@ -83,6 +84,7 @@
"@testing-library/user-event": "^14.5.1",
"@types/lodash.debounce": "^4.0.7",
"@types/node": "^20.4.2",
"@types/pluralize": "^0.0.33",
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11",
"@typescript-eslint/eslint-plugin": "^5.59.0",
Expand Down
86 changes: 86 additions & 0 deletions src/services/ui/src/components/TimeoutModal/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { useEffect, useState } from "react";
import {
Button,
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components";
import { Auth } from "aws-amplify";
import { useIdle } from "@/hooks/useIdle";
import { useGetUser } from "@/api";
import { useCountdown } from "@/hooks/useCountdown";
import { intervalToDuration } from "date-fns";
import pluralize from "pluralize";

const TWENTY_MINS_IN_MILS = 1000 * 60 * 20;
const TEN_MINS_IN_MILS = 60 * 10;

export const TimeoutModal = () => {
const [isModalOpen, setIsModalOpen] = useState(false);
const isIdleForTwentyMins = useIdle(TWENTY_MINS_IN_MILS, {
initialState: false,
});

const [timeoutModalCountdown, { startCountdown, resetCountdown }] =
useCountdown(TEN_MINS_IN_MILS);
const { data: user } = useGetUser();

const onLogOut = () => {
Auth.signOut();
};

const onExtendSession = () => {
setIsModalOpen(false);

// artificial delay hiding the countdown reset after modal dismissal
setTimeout(() => {
resetCountdown();
}, 500);
};

useEffect(() => {
if (timeoutModalCountdown === 0) {
onLogOut();
}
}, [timeoutModalCountdown]);

useEffect(() => {
if (user?.user && isIdleForTwentyMins) {
startCountdown();
setIsModalOpen(true);
}
}, [isIdleForTwentyMins]);

const duration = intervalToDuration({
start: 0,
end: timeoutModalCountdown * 1000,
});

return (
<Dialog open={isModalOpen} onOpenChange={onExtendSession}>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>Session expiring soon</DialogTitle>
</DialogHeader>
<div className="py-4">
<span>
Your session will expire in <strong>{duration.minutes}</strong>{" "}
{pluralize("minute", duration.minutes)} and{" "}
<strong>{duration.seconds}</strong>{" "}
{pluralize("second", duration.seconds)}.
</span>
</div>
<DialogFooter>
<Button type="submit" onClick={onExtendSession}>
Yes, extend session
</Button>
<Button type="button" variant="outline" onClick={onLogOut}>
No, sign out
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
};
1 change: 1 addition & 0 deletions src/services/ui/src/components/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,4 @@ export * from "./MaintenanceBanner";
export * from "./Modal";
export * from "./Modal/ConfirmationModal";
export * from "./SearchForm";
export * from "./TimeoutModal";
55 changes: 55 additions & 0 deletions src/services/ui/src/hooks/useCountdown.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// credit: https://usehooks-ts.com/react-hook/use-countdown

import { useCallback, useEffect, useState } from "react";

type CountdownControllers = {
startCountdown: () => void;
stopCountdown: () => void;
resetCountdown: () => void;
};

export const useCountdown = (
countStart: number,
): [number, CountdownControllers] => {
const [count, setCount] = useState<number>(countStart);
const [isCountdownRunning, setIsCountdownRunning] = useState<boolean>(false);

const startCountdown = () => {
setIsCountdownRunning(true);
};

const stopCountdown = () => {
setIsCountdownRunning(false);
};

// Will set running false and reset the seconds to initial value
const resetCountdown = useCallback(() => {
stopCountdown();
setCount(countStart);
}, [stopCountdown]);

const countdownCallback = useCallback(() => {
if (count === 0) {
stopCountdown();
return;
}

setCount((oldCount) => oldCount - 1);
}, [count, stopCountdown]);

useEffect(() => {
if (isCountdownRunning === false) {
return;
}

const id = setInterval(() => {
countdownCallback();
}, 1000);

return () => {
clearInterval(id);
};
}, [isCountdownRunning, countdownCallback]);

return [count, { startCountdown, stopCountdown, resetCountdown }];
};
58 changes: 58 additions & 0 deletions src/services/ui/src/hooks/useIdle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// credit: https://github.com/mantinedev/mantine/blob/master/packages/@mantine/hooks/src/use-idle/use-idle.ts

import { useEffect, useRef, useState } from "react";

const DEFAULT_EVENTS: (keyof DocumentEventMap)[] = [
"keypress",
"mousemove",
"touchmove",
"click",
"scroll",
];
const DEFAULT_OPTIONS = {
events: DEFAULT_EVENTS,
initialState: true,
};

export function useIdle(
timeout: number,
options?: Partial<{
events: (keyof DocumentEventMap)[];
initialState: boolean;
}>,
) {
const { events, initialState } = { ...DEFAULT_OPTIONS, ...options };
const [idle, setIdle] = useState<boolean>(initialState);
const timer = useRef<number>();

// start tracking idleness on useIdle mount
useEffect(() => {
timer.current = window.setTimeout(() => {
setIdle(true);
}, timeout);
}, []);

useEffect(() => {
const handleEvents = () => {
setIdle(false);

if (timer.current) {
window.clearTimeout(timer.current);
}

timer.current = window.setTimeout(() => {
setIdle(true);
}, timeout);
};

events.forEach((event) => document.addEventListener(event, handleEvents));

return () => {
events.forEach((event) =>
document.removeEventListener(event, handleEvents),
);
};
}, [timeout]);

return idle;
}
7 changes: 4 additions & 3 deletions src/services/ui/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ import "./index.css"; // this one second
import { queryClient, router } from "./router";
import { QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import { UserContextProvider } from "@/components";
import { UserContextProvider, TimeoutModal } from "@/components";
import config from "@/config";
import { asyncWithLDProvider } from "launchdarkly-react-client-sdk";

const ldClientId = config.launchDarkly?.CLIENT_ID;
if (ldClientId === undefined) {
throw new Error(
"To configure LaunchDarkly, you must set LAUNCHDARKLY_CLIENT_ID"
"To configure LaunchDarkly, you must set LAUNCHDARKLY_CLIENT_ID",
);
}

Expand All @@ -33,12 +33,13 @@ const initializeLaunchDarkly = async () => {
<QueryClientProvider client={queryClient}>
<UserContextProvider>
<LDProvider>
<TimeoutModal />
<RouterProvider router={router} />
</LDProvider>
</UserContextProvider>
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
</React.StrictMode>
</React.StrictMode>,
);
};

Expand Down
10 changes: 10 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6387,6 +6387,11 @@
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.2.tgz#5950e50960793055845e956c427fc2b0d70c5239"
integrity sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==

"@types/pluralize@^0.0.33":
version "0.0.33"
resolved "https://registry.yarnpkg.com/@types/pluralize/-/pluralize-0.0.33.tgz#8ad9018368c584d268667dd9acd5b3b806e8c82a"
integrity sha512-JOqsl+ZoCpP4e8TDke9W79FDcSgPAR0l6pixx2JHkhnRjvShyYiAYw2LVsnA7K08Y6DeOnaU6ujmENO4os/cYg==

"@types/prop-types@*", "@types/prop-types@^15.7.11":
version "15.7.12"
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.12.tgz#12bb1e2be27293c1406acb6af1c3f3a1481d98c6"
Expand Down Expand Up @@ -13421,6 +13426,11 @@ playwright@1.42.1:
optionalDependencies:
fsevents "2.3.2"

pluralize@^8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1"
integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==

possible-typed-array-names@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz#89bb63c6fada2c3e90adc4a647beeeb39cc7bf8f"
Expand Down

0 comments on commit e93578c

Please sign in to comment.