Timeout modal
The timeout modal allows activations to reset to the attract screen if they are left idle for a certain amount of time. The modal will display a countdown timer and a button to restart the activation or continue.
Install packages
bash
npm i @radix-ui/react-dialog
Add tailwind styles, to the extend
key
javascript
keyframes: {
overlayShow: {
from: { opacity: "0" },
to: { opacity: "1" },
},
contentShow: {
from: { opacity: "0", transform: "translate(0, -20px) scale(0.96)" },
to: { opacity: "1", transform: "translate(0, 0) scale(1)" },
},
},
animation: {
overlayShow: "overlayShow 150ms cubic-bezier(0.16, 1, 0.3, 1)",
contentShow: "contentShow 150ms cubic-bezier(0.16, 1, 0.3, 1)",
},
Add your component
typescript
"use client";
import { usePathname, useRouter } from "next/navigation";
import * as Dialog from "@radix-ui/react-dialog";
import { useEffect, useState } from "react";
import Link from "next/link";
interface ITimeoutModalProps {}
const useIdleBodyInteraction = (
onIdle: () => void,
timeout: number = process.env.NODE_ENV !== "production" ? 60000 : 60000
): void => {
useEffect(() => {
let timer: ReturnType<typeof setTimeout>;
const resetTimer = (): void => {
clearTimeout(timer);
timer = setTimeout(onIdle, timeout);
};
const events: string[] = ["click", "mousemove", "scroll", "touchstart"];
events.forEach((event) => {
window.addEventListener(event, resetTimer);
});
// Set the initial timer
timer = setTimeout(onIdle, timeout);
return () => {
clearTimeout(timer);
events.forEach((event) => {
window.removeEventListener(event, resetTimer);
});
};
}, [onIdle, timeout]);
};
const COUNTDOWN_TIME = Number(process?.env?.NEXT_PUBLIC_REDIRECT_TIMEOUT) || 10;
const useCountDown = (isOpen: boolean, onTimeout?: () => void) => {
const [number, setNumber] = useState(COUNTDOWN_TIME);
useEffect(() => {
let interval: NodeJS.Timeout;
if (isOpen) {
interval = setInterval(() => {
setNumber(number - 1);
if (number - 1 === 0) {
if (onTimeout) {
onTimeout();
}
}
}, 1000);
}
return () => {
clearInterval(interval);
};
}, [number, isOpen, onTimeout]);
useEffect(() => {
if (isOpen === false) {
setNumber(COUNTDOWN_TIME);
}
}, [isOpen]);
return number;
};
const TimeoutModal = ({}: ITimeoutModalProps) => {
const route = useRouter();
const [open, setOpen] = useState(false);
const pathname = usePathname();
const handleIdle = (): void => {
if (pathname === "/admin") return;
if (pathname === "/") return;
setOpen(true);
};
useIdleBodyInteraction(handleIdle);
const number = useCountDown(open, () => {
setOpen(false);
route.push("/");
});
return (
<Dialog.Root open={open}>
<Dialog.Portal>
<Dialog.Overlay className="data-[state=open]:animate-overlayShow fixed inset-0" />
<Dialog.Content className="z-20 text-black data-[state=open]:animate-contentShow fixed top-0 left-0 w-screen h-screen flex flex-col items-center justify-center">
<Dialog.Description className="text-center">
{number}
</Dialog.Description>
<div className="mt-100 flex justify-end space-x-50">
<Link href="/" onClick={() => setOpen(false)}>
Restart
</Link>
<button onClick={() => setOpen(false)}>Continue</button>
</div>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
);
};
export default TimeoutModal;