feat(auth): Implemented Session Persistence

This commit is contained in:
dhinesh-m24
2026-01-29 12:39:45 +05:30
parent e214e32c17
commit 7133e59e57
10 changed files with 417 additions and 32 deletions

View File

@@ -10,6 +10,7 @@ import {
} from 'lucide-react';
import Sidebar from './Sidebar';
import { getDashboardPath } from '../../services/firestoreService';
import { useSessionPersistence } from '../../hooks/useSessionPersistence';
/**
* Main Application Layout for Authenticated Users.
@@ -17,6 +18,9 @@ import { getDashboardPath } from '../../services/firestoreService';
* Handles role-based navigation rendering and validates user access to dashboard routes.
*/
const AppLayout: React.FC = () => {
// Initialize session persistence and token refresh
useSessionPersistence();
// Typed selectors
const { isAuthenticated, user } = useSelector((state: RootState) => state.auth);
const dispatch = useDispatch<AppDispatch>();
@@ -47,7 +51,10 @@ const AppLayout: React.FC = () => {
}
const handleLogout = () => {
dispatch(logoutUser());
dispatch(logoutUser()).then(() => {
// Navigate to login page after logout is complete
navigate('/login', { replace: true });
});
};
return (

View File

@@ -0,0 +1,48 @@
import React from 'react';
import PageHeader from '../../common/components/PageHeader';
import { motion } from 'framer-motion';
interface DashboardLayoutProps {
title: string;
subtitle?: string;
actions?: React.ReactNode;
children: React.ReactNode;
maxWidth?: string;
backAction?: React.ReactNode;
}
const DashboardLayout: React.FC<DashboardLayoutProps> = ({
title,
subtitle,
actions,
children,
maxWidth = 'max-w-7xl',
backAction
}) => {
return (
<div className="p-4 md:p-8">
<div className={`${maxWidth} mx-auto`}>
{backAction && (
<motion.div
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.4, ease: [0.16, 1, 0.3, 1] }}
className="mb-4"
>
{backAction}
</motion.div>
)}
<PageHeader
title={title}
subtitle={subtitle}
actions={actions}
/>
{children}
</div>
</div>
);
};
export default DashboardLayout;

View File

@@ -0,0 +1,23 @@
import React from 'react';
interface PublicLayoutProps {
children: React.ReactNode;
}
/**
* Layout for public, unauthenticated pages (e.g. Login, Forgot Password).
* Provides a centered container with a gradient background.
*/
const PublicLayout: React.FC<PublicLayoutProps> = ({ children }) => {
return (
<div className="min-h-screen w-full bg-slate-50 flex items-center justify-center">
{/* Background decoration */}
<div className="absolute inset-0 bg-gradient-to-br from-blue-50 to-indigo-50 -z-10" />
<div className="w-full">
{children}
</div>
</div>
);
};
export default PublicLayout;

View File

@@ -1,5 +1,5 @@
import React, { useMemo } from 'react';
import { Link, useLocation } from 'react-router-dom';
import { Link, useLocation, useNavigate } from 'react-router-dom';
import { LogOut, Menu, X } from 'lucide-react';
import { Button } from '../../common/components/ui/button';
import { NAV_CONFIG } from "../../common/config/navigation";
@@ -22,6 +22,19 @@ const Sidebar: React.FC<SidebarProps> = ({
onLogout
}) => {
const location = useLocation();
const navigate = useNavigate();
/**
* Handle logout with navigation
* Ensures user is redirected to login page after logout
*/
const handleLogoutClick = async () => {
onLogout();
// Small delay to allow logout to complete before navigation
setTimeout(() => {
navigate('/login', { replace: true });
}, 100);
};
// Filter navigation based on user role
const filteredNav = useMemo(() => {
@@ -107,7 +120,7 @@ const Sidebar: React.FC<SidebarProps> = ({
<Button
variant="ghost"
size="icon"
onClick={onLogout}
onClick={handleLogoutClick}
title="Logout"
className="text-secondary-text hover:text-destructive hover:bg-destructive/10"
>