-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
384 additions
and
35 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
'use client'; | ||
|
||
import { useEffect, useState } from 'react'; | ||
import { | ||
AlertDialog, | ||
AlertDialogHeader, | ||
AlertDialogContent, | ||
AlertDialogDescription, | ||
AlertDialogTitle, | ||
AlertDialogFooter, | ||
AlertDialogAction, | ||
AlertDialogCancel | ||
} from '@/components/ui/alert-dialog'; | ||
import { useIdleTimer } from 'react-idle-timer'; | ||
import { logout } from '@/access/AuthenticationService'; | ||
import User from '@/access/User'; | ||
import Role from '@/access/Role'; | ||
|
||
const timeout = 1000 * 60 * 30; // Total timeout - 30 minutes in milliseconds | ||
const promptBeforeIdle = 1000 * 60 * 5; // Time prior to timeout until modal opens - 5 minutes in milliseconds | ||
|
||
const TimeoutModal = ({ | ||
currentUser | ||
}: { | ||
currentUser: User | ||
}) => { | ||
const [open, setOpen] = useState(false); | ||
const [remainingTime, setRemainingTime] = useState<number>(timeout); | ||
|
||
const { activate, getRemainingTime } = useIdleTimer({ | ||
onIdle: () => logout(), | ||
onPrompt: () => setOpen(true), | ||
timeout, | ||
promptBeforeIdle, | ||
throttle: 500, | ||
disabled: !currentUser.roles.includes(Role.UH) | ||
}); | ||
|
||
useEffect(() => { | ||
const interval = setInterval(() => { | ||
setRemainingTime(getRemainingTime()); | ||
}, 500); | ||
|
||
return () => { | ||
clearInterval(interval); | ||
}; | ||
}); | ||
|
||
/** | ||
* Closes the modal and resets the timer. | ||
*/ | ||
const close = () => { | ||
activate(); | ||
setOpen(false); | ||
}; | ||
|
||
/** | ||
* Convert miliseconds into mm:ss string format. | ||
* | ||
* @param ms - the number of milliseconds | ||
* | ||
* @returns the mm:ss formatted string | ||
*/ | ||
const formatTime = (ms: number) => { | ||
const date = new Date(ms); | ||
const minutes = date.getMinutes(); | ||
const seconds = date.getSeconds(); | ||
|
||
return `${minutes}:${seconds.toString().padStart(2, '0')}`; | ||
}; | ||
|
||
return ( | ||
<AlertDialog open={open}> | ||
<AlertDialogContent> | ||
<AlertDialogHeader> | ||
<AlertDialogTitle>Inactivity Warning</AlertDialogTitle> | ||
<AlertDialogDescription> | ||
Warning! This session will expire soon. | ||
Time remaining: | ||
<span className="text-text-color"> {formatTime(remainingTime)}.</span> | ||
</AlertDialogDescription> | ||
</AlertDialogHeader> | ||
<AlertDialogFooter> | ||
<AlertDialogCancel onClick={() => close()}>Stay logged in</AlertDialogCancel> | ||
<AlertDialogAction onClick={() => logout()}>Log off now</AlertDialogAction> | ||
</AlertDialogFooter> | ||
</AlertDialogContent> | ||
</AlertDialog> | ||
); | ||
} | ||
|
||
export default TimeoutModal; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
'use client' | ||
|
||
import * as React from 'react' | ||
import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog' | ||
|
||
import { cn } from '@/components/ui/utils' | ||
import { buttonVariants } from '@/components/ui/button' | ||
|
||
const AlertDialog = AlertDialogPrimitive.Root | ||
|
||
const AlertDialogTrigger = AlertDialogPrimitive.Trigger | ||
|
||
const AlertDialogPortal = AlertDialogPrimitive.Portal | ||
|
||
const AlertDialogOverlay = React.forwardRef< | ||
React.ElementRef<typeof AlertDialogPrimitive.Overlay>, | ||
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay> | ||
>(({ className, ...props }, ref) => ( | ||
<AlertDialogPrimitive.Overlay | ||
className={cn( | ||
'fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0', | ||
className | ||
)} | ||
{...props} | ||
ref={ref} | ||
/> | ||
)) | ||
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName | ||
|
||
const AlertDialogContent = React.forwardRef< | ||
React.ElementRef<typeof AlertDialogPrimitive.Content>, | ||
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content> | ||
>(({ className, ...props }, ref) => ( | ||
<AlertDialogPortal> | ||
<AlertDialogOverlay /> | ||
<AlertDialogPrimitive.Content | ||
ref={ref} | ||
className={cn( | ||
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-slate-200 bg-white py-4 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-[0.25rem] dark:border-slate-800 dark:bg-slate-950', | ||
className | ||
)} | ||
{...props} | ||
/> | ||
</AlertDialogPortal> | ||
)) | ||
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName | ||
|
||
const AlertDialogHeader = ({ | ||
className, | ||
...props | ||
}: React.HTMLAttributes<HTMLDivElement>) => ( | ||
<div | ||
className={cn( | ||
'flex flex-col space-y-2 text-center sm:text-left', | ||
className | ||
)} | ||
{...props} | ||
/> | ||
) | ||
AlertDialogHeader.displayName = 'AlertDialogHeader' | ||
|
||
const AlertDialogFooter = ({ | ||
className, | ||
...props | ||
}: React.HTMLAttributes<HTMLDivElement>) => ( | ||
<div | ||
className={cn( | ||
'flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2 px-4 pt-4 border-t', | ||
className | ||
)} | ||
{...props} | ||
/> | ||
) | ||
AlertDialogFooter.displayName = 'AlertDialogFooter' | ||
|
||
const AlertDialogTitle = React.forwardRef< | ||
React.ElementRef<typeof AlertDialogPrimitive.Title>, | ||
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title> | ||
>(({ className, ...props }, ref) => ( | ||
<AlertDialogPrimitive.Title | ||
ref={ref} | ||
className={cn('text-xl px-4 pb-4 border-b font-normal text-text-color', className)} | ||
{...props} | ||
/> | ||
)) | ||
AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName | ||
|
||
const AlertDialogDescription = React.forwardRef< | ||
React.ElementRef<typeof AlertDialogPrimitive.Description>, | ||
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description> | ||
>(({ className, ...props }, ref) => ( | ||
<AlertDialogPrimitive.Description | ||
ref={ref} | ||
className={cn('text-base px-4 py-2', className)} | ||
{...props} | ||
/> | ||
)) | ||
AlertDialogDescription.displayName = | ||
AlertDialogPrimitive.Description.displayName | ||
|
||
const AlertDialogAction = React.forwardRef< | ||
React.ElementRef<typeof AlertDialogPrimitive.Action>, | ||
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action> | ||
>(({ className, ...props }, ref) => ( | ||
<AlertDialogPrimitive.Action | ||
ref={ref} | ||
className={cn(buttonVariants(), className)} | ||
{...props} | ||
/> | ||
)) | ||
AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName | ||
|
||
const AlertDialogCancel = React.forwardRef< | ||
React.ElementRef<typeof AlertDialogPrimitive.Cancel>, | ||
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel> | ||
>(({ className, ...props }, ref) => ( | ||
<AlertDialogPrimitive.Cancel | ||
ref={ref} | ||
className={cn( | ||
buttonVariants({ variant: 'secondary' }), | ||
'mt-2 sm:mt-0', | ||
className | ||
)} | ||
{...props} | ||
/> | ||
)) | ||
AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName | ||
|
||
export { | ||
AlertDialog, | ||
AlertDialogPortal, | ||
AlertDialogOverlay, | ||
AlertDialogTrigger, | ||
AlertDialogContent, | ||
AlertDialogHeader, | ||
AlertDialogFooter, | ||
AlertDialogTitle, | ||
AlertDialogDescription, | ||
AlertDialogAction, | ||
AlertDialogCancel, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.