38499-vm/frontend/src/layouts/Authenticated.tsx
2026-02-17 02:44:55 +00:00

118 lines
3.7 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { ReactNode, useEffect, useState } from 'react'
import jwt from 'jsonwebtoken';
import menuNavBar from '../menuNavBar'
import NavBar from '../components/NavBar'
import FooterBar from '../components/FooterBar'
import { useAppDispatch, useAppSelector } from '../stores/hooks'
import Search from '../components/Search';
import { useRouter } from 'next/router'
import {findMe, logoutUser} from "../stores/authSlice";
import axios from 'axios';
import { mdiAlertCircle } from '@mdi/js';
import BaseIcon from '../components/BaseIcon';
import NavBarItemPlain from '../components/NavBarItemPlain';
import moment from 'moment';
import {hasPermission} from "../helpers/userPermissions";
type Props = {
children: ReactNode
permission?: string
}
export default function LayoutAuthenticated({
children,
permission
}: Props) {
const dispatch = useAppDispatch()
const router = useRouter()
const { token, currentUser } = useAppSelector((state) => state.auth)
const bgColor = useAppSelector((state) => state.style.bgLayoutColor);
const [lockoutBanner, setLockoutBanner] = useState(null);
let localToken
if (typeof window !== 'undefined') {
// Perform localStorage action
localToken = localStorage.getItem('token')
}
const isTokenValid = () => {
const token = localStorage.getItem('token');
if (!token) return;
const date = new Date().getTime() / 1000;
const data = jwt.decode(token);
if (!data) return;
return date < data.exp;
};
useEffect(() => {
dispatch(findMe());
if (!isTokenValid()) {
dispatch(logoutUser());
router.push('/login');
}
}, [token, localToken]);
useEffect(() => {
if (!permission || !currentUser) return;
if (!hasPermission(currentUser, permission)) router.push('/error');
}, [currentUser, permission]);
useEffect(() => {
const checkLockout = async () => {
try {
const response = await axios.get('/app_settings');
const settings = response.data;
if (settings.lockoutEnabled && settings.lockoutUntil && moment(settings.lockoutUntil).isAfter(moment())) {
const allowed = settings.allowedUserIds?.includes(currentUser?.id);
setLockoutBanner({
message: settings.lockoutMessage || `System is currently locked for reconciliation until ${moment(settings.lockoutUntil).format('LLL')}.`,
until: settings.lockoutUntil
});
}
} catch (err) {
// console.error(err); // Fail silently on frontend if fetch fails
}
};
if (currentUser) {
checkLockout();
}
}, [currentUser]);
const darkMode = useAppSelector((state) => state.style.darkMode)
return (
<div className={`${darkMode ? 'dark' : ''} overflow-hidden lg:overflow-visible`}>
{lockoutBanner && (
<div className="bg-red-600 text-white p-2 text-center fixed top-0 left-0 w-full z-[100] flex items-center justify-center space-x-2 shadow-lg h-12">
<BaseIcon path={mdiAlertCircle} size="24" className="w-6 h-6" />
<span className="font-bold">SYSTEM LOCKOUT:</span>
<span>{lockoutBanner.message}</span>
</div>
)}
<div
className={`min-h-screen w-screen transition-position lg:w-auto ${bgColor} dark:bg-dark-800 dark:text-slate-100 ${lockoutBanner ? 'pt-26' : 'pt-14'}`}
>
<NavBar
menu={menuNavBar}
className={`${lockoutBanner ? 'top-12' : ''}`}
>
<NavBarItemPlain useMargin>
<Search />
</NavBarItemPlain>
</NavBar>
<div className="max-w-7xl mx-auto">
{children}
</div>
<FooterBar>Hand-crafted & Made with </FooterBar>
</div>
</div>
)
}