diff options
Diffstat (limited to 'frontend/src/app/(main)/layout.tsx')
-rw-r--r-- | frontend/src/app/(main)/layout.tsx | 437 |
1 files changed, 203 insertions, 234 deletions
diff --git a/frontend/src/app/(main)/layout.tsx b/frontend/src/app/(main)/layout.tsx index ee1026c..11e557b 100644 --- a/frontend/src/app/(main)/layout.tsx +++ b/frontend/src/app/(main)/layout.tsx @@ -1,12 +1,12 @@ 'use client'; -import { useEffect, useState } from 'react'; -import { useRouter } from 'next/navigation'; +import { useState, useEffect } from 'react'; import Link from 'next/link'; import { Button } from '@/components/ui/button'; -import { authApi, userApi } from '@/lib/api'; import { ThemeToggle } from '@/components/shared/ThemeToggle'; import { PageTransition } from '@/components/shared/PageTransition'; +import ProtectedRoute from '@/components/shared/ProtectedRoute'; +import { useAuth } from '@/components/shared/AuthContext'; import { ChevronLeftIcon, ChevronRightIcon, @@ -25,38 +25,18 @@ export default function MainLayout({ }: Readonly<{ children: React.ReactNode; }>) { - const router = useRouter(); const pathname = usePathname(); - const [userName, setUserName] = useState<string>(''); - const [isLoading, setIsLoading] = useState(true); + const { user, logout } = useAuth(); const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(false); const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); useEffect(() => { - // Check if user is authenticated - const token = localStorage.getItem('token'); - if (!token) { - router.push('/login'); - return; - } - - // Fetch user profile - userApi.getProfile() - .then(data => { - setUserName(data.user.Name); - setIsLoading(false); - }) - .catch(() => { - // If error fetching profile, redirect to login - router.push('/login'); - }); - // Load sidebar state from localStorage const savedSidebarState = localStorage.getItem('sidebarCollapsed'); if (savedSidebarState !== null) { setIsSidebarCollapsed(savedSidebarState === 'true'); } - }, [router]); + }, []); // Close mobile menu when changing routes useEffect(() => { @@ -64,7 +44,7 @@ export default function MainLayout({ }, [pathname]); const handleLogout = () => { - authApi.logout(); + logout(); }; const toggleSidebar = () => { @@ -77,227 +57,216 @@ export default function MainLayout({ setIsMobileMenuOpen(!isMobileMenuOpen); }; - if (isLoading) { - return ( - <div className="flex h-screen items-center justify-center bg-background"> - <div className="flex flex-col items-center space-y-4"> - <div className="relative h-12 w-12"> - <div className="absolute top-0 h-full w-full rounded-full border-4 border-t-primary border-r-transparent border-b-transparent border-l-transparent animate-spin"></div> - </div> - <p className="text-sm font-medium text-muted-foreground animate-pulse">Loading...</p> - </div> - </div> - ); - } - return ( - <div className="min-h-screen bg-background flex flex-col"> - {/* Top Navigation */} - <header className="border-b shadow-sm backdrop-blur-sm bg-background/90 sticky top-0 z-50 w-full"> - <div className="w-full flex h-16 items-center px-4 md:px-6"> - <div className="flex items-center gap-4"> - {/* Mobile menu button - only visible on small screens */} - <button - className="md:hidden rounded-full p-2 hover:bg-muted transition-colors" - onClick={toggleMobileMenu} - aria-label="Toggle mobile menu" - > - <MenuIcon size={20} /> - </button> - <div className="font-semibold text-xl"> - <span className="bg-clip-text text-transparent bg-gradient-to-r from-primary to-primary/70"> - Finance Management - </span> + <ProtectedRoute> + <div className="min-h-screen bg-background flex flex-col"> + {/* Top Navigation */} + <header className="border-b shadow-sm backdrop-blur-sm bg-background/90 sticky top-0 z-50 w-full"> + <div className="w-full flex h-16 items-center px-4 md:px-6"> + <div className="flex items-center gap-4"> + {/* Mobile menu button - only visible on small screens */} + <button + className="md:hidden rounded-full p-2 hover:bg-muted transition-colors" + onClick={toggleMobileMenu} + aria-label="Toggle mobile menu" + > + <MenuIcon size={20} /> + </button> + <div className="font-semibold text-xl"> + <span className="bg-clip-text text-transparent bg-gradient-to-r from-primary to-primary/70"> + Finance Management + </span> + </div> </div> - </div> - - <div className="flex items-center ms-auto gap-4"> - <div className="hidden md:flex items-center gap-1 bg-muted/50 px-3 py-1.5 rounded-full"> - <span className="relative flex h-2 w-2"> - <span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-primary/50 opacity-75"></span> - <span className="relative inline-flex rounded-full h-2 w-2 bg-primary"></span> - </span> - <span className='text-sm font-medium ml-1.5 transition-all duration-300'> - {userName} - </span> + + <div className="flex items-center ms-auto gap-4"> + <div className="hidden md:flex items-center gap-1 bg-muted/50 px-3 py-1.5 rounded-full"> + <span className="relative flex h-2 w-2"> + <span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-primary/50 opacity-75"></span> + <span className="relative inline-flex rounded-full h-2 w-2 bg-primary"></span> + </span> + <span className='text-sm font-medium ml-1.5 transition-all duration-300'> + {user?.Name} + </span> + </div> + <ThemeToggle /> + <Button + variant="ghost" + size="icon" + onClick={handleLogout} + className="hover:bg-destructive/10 transition-colors duration-300" + aria-label="Logout" + > + <LogOutIcon size={18} className="text-destructive/90 transition-transform hover:scale-110 duration-300" /> + </Button> </div> - <ThemeToggle /> - <Button - variant="ghost" - size="icon" - onClick={handleLogout} - className="hover:bg-destructive/10 transition-colors duration-300" - aria-label="Logout" - > - <LogOutIcon size={18} className="text-destructive/90 transition-transform hover:scale-110 duration-300" /> - </Button> - </div> - </div> - </header> - - {/* Mobile Menu - outside the normal flow and only visible when toggled */} - <div - className={` - md:hidden fixed inset-0 z-40 bg-background/95 backdrop-blur-sm transition-all duration-300 - ${isMobileMenuOpen ? 'opacity-100 pointer-events-auto' : 'opacity-0 pointer-events-none'} - `} - > - <div className="pt-20 px-4"> - <nav className="space-y-4"> - <Link - href="/dashboard" - className={` - flex items-center p-3 rounded-lg transition-colors - ${pathname === '/dashboard' ? 'bg-primary/10 text-primary' : 'hover:bg-muted'} - `} - > - <LayoutDashboardIcon size={20} className="mr-3" /> - Dashboard - </Link> - <Link - href="/loans" - className={` - flex items-center p-3 rounded-lg transition-colors - ${pathname === '/loans' ? 'bg-primary/10 text-primary' : 'hover:bg-muted'} - `} - > - <CoinsIcon size={20} className="mr-3" /> - Loans - </Link> - <Link - href="/goals" - className={` - flex items-center p-3 rounded-lg transition-colors - ${pathname === '/goals' ? 'bg-primary/10 text-primary' : 'hover:bg-muted'} - `} - > - <TargetIcon size={20} className="mr-3" /> - Goals - </Link> - <Link - href="/settings" - className={` - flex items-center p-3 rounded-lg transition-colors - ${pathname === '/settings' ? 'bg-primary/10 text-primary' : 'hover:bg-muted'} - `} - > - <SettingsIcon size={20} className="mr-3" /> - Settings - </Link> - </nav> - <div className="mt-8 border-t pt-4"> - <Button - variant="outline" - className="w-full justify-start text-destructive hover:text-destructive hover:bg-destructive/10" - onClick={handleLogout} - > - <LogOutIcon size={18} className="mr-2" /> - Logout - </Button> </div> - </div> - </div> + </header> - {/* Main Content */} - <div className="flex flex-1 overflow-hidden"> - {/* Sidebar - hidden on mobile */} - <aside + {/* Mobile Menu - outside the normal flow and only visible when toggled */} + <div className={` - hidden md:flex flex-col h-[calc(100vh-4rem)] border-r bg-background flex-shrink-0 relative - transition-all duration-300 ease-in-out - ${isSidebarCollapsed ? 'w-16' : 'w-64'} + md:hidden fixed inset-0 z-40 bg-background/95 backdrop-blur-sm transition-all duration-300 + ${isMobileMenuOpen ? 'opacity-100 pointer-events-auto' : 'opacity-0 pointer-events-none'} `} > - {/* Sidebar Toggle Button */} - <button - onClick={toggleSidebar} - className="absolute -right-3 top-10 bg-primary text-primary-foreground rounded-full p-1 shadow-md hover:bg-primary/90 transition-colors duration-300 hover:scale-110 group z-10" - aria-label={isSidebarCollapsed ? "Expand sidebar" : "Collapse sidebar"} - > - <div className="transition-transform duration-300 group-hover:rotate-[360deg]"> - {isSidebarCollapsed ? <ChevronRightIcon size={16} /> : <ChevronLeftIcon size={16} />} - </div> - </button> - - <nav className="space-y-2 px-2 mt-4 flex-1"> - <Link - href="/dashboard" - className={` - flex items-center p-2 rounded-md transition-all duration-200 overflow-hidden - ${pathname === '/dashboard' - ? 'bg-primary/10 text-primary font-medium' - : 'hover:bg-muted text-foreground/80 hover:text-foreground' - } - ${isSidebarCollapsed ? 'justify-center' : 'justify-start'} - `} - title="Dashboard" - > - <LayoutDashboardIcon size={18} className={`transition-transform duration-300 ${pathname === '/dashboard' ? 'scale-110' : ''}`} /> - <span className={`ml-2 transition-all duration-300 overflow-hidden whitespace-nowrap ${isSidebarCollapsed ? 'w-0 opacity-0' : 'w-auto opacity-100'}`}> + <div className="pt-20 px-4"> + <nav className="space-y-4"> + <Link + href="/dashboard" + className={` + flex items-center p-3 rounded-lg transition-colors + ${pathname === '/dashboard' ? 'bg-primary/10 text-primary' : 'hover:bg-muted'} + `} + > + <LayoutDashboardIcon size={20} className="mr-3" /> Dashboard - </span> - </Link> - <Link - href="/loans" - className={` - flex items-center p-2 rounded-md transition-all duration-200 overflow-hidden - ${pathname === '/loans' - ? 'bg-primary/10 text-primary font-medium' - : 'hover:bg-muted text-foreground/80 hover:text-foreground' - } - ${isSidebarCollapsed ? 'justify-center' : 'justify-start'} - `} - title="Loans" - > - <CoinsIcon size={18} className={`transition-transform duration-300 ${pathname === '/loans' ? 'scale-110' : ''}`} /> - <span className={`ml-2 transition-all duration-300 overflow-hidden whitespace-nowrap ${isSidebarCollapsed ? 'w-0 opacity-0' : 'w-auto opacity-100'}`}> + </Link> + <Link + href="/loans" + className={` + flex items-center p-3 rounded-lg transition-colors + ${pathname === '/loans' ? 'bg-primary/10 text-primary' : 'hover:bg-muted'} + `} + > + <CoinsIcon size={20} className="mr-3" /> Loans - </span> - </Link> - <Link - href="/goals" - className={` - flex items-center p-2 rounded-md transition-all duration-200 overflow-hidden - ${pathname === '/goals' - ? 'bg-primary/10 text-primary font-medium' - : 'hover:bg-muted text-foreground/80 hover:text-foreground' - } - ${isSidebarCollapsed ? 'justify-center' : 'justify-start'} - `} - title="Goals" - > - <TargetIcon size={18} className={`transition-transform duration-300 ${pathname === '/goals' ? 'scale-110' : ''}`} /> - <span className={`ml-2 transition-all duration-300 overflow-hidden whitespace-nowrap ${isSidebarCollapsed ? 'w-0 opacity-0' : 'w-auto opacity-100'}`}> + </Link> + <Link + href="/goals" + className={` + flex items-center p-3 rounded-lg transition-colors + ${pathname === '/goals' ? 'bg-primary/10 text-primary' : 'hover:bg-muted'} + `} + > + <TargetIcon size={20} className="mr-3" /> Goals - </span> - </Link> - <Link - href="/settings" - className={` - flex items-center p-2 rounded-md transition-all duration-200 overflow-hidden - ${pathname === '/settings' - ? 'bg-primary/10 text-primary font-medium' - : 'hover:bg-muted text-foreground/80 hover:text-foreground' - } - ${isSidebarCollapsed ? 'justify-center' : 'justify-start'} - `} - title="Settings" - > - <SettingsIcon size={18} className={`transition-transform duration-300 ${pathname === '/settings' ? 'scale-110' : ''}`} /> - <span className={`ml-2 transition-all duration-300 overflow-hidden whitespace-nowrap ${isSidebarCollapsed ? 'w-0 opacity-0' : 'w-auto opacity-100'}`}> + </Link> + <Link + href="/settings" + className={` + flex items-center p-3 rounded-lg transition-colors + ${pathname === '/settings' ? 'bg-primary/10 text-primary' : 'hover:bg-muted'} + `} + > + <SettingsIcon size={20} className="mr-3" /> Settings - </span> - </Link> - </nav> - </aside> + </Link> + </nav> + <div className="mt-8 border-t pt-4"> + <Button + variant="outline" + className="w-full justify-start text-destructive hover:text-destructive hover:bg-destructive/10" + onClick={handleLogout} + > + <LogOutIcon size={18} className="mr-2" /> + Logout + </Button> + </div> + </div> + </div> - {/* Page Content */} - <main className="flex-1 p-4 md:p-8 overflow-auto h-[calc(100vh-4rem)] transition-all duration-300"> - <PageTransition> - {children} - </PageTransition> - </main> + {/* Main Content */} + <div className="flex flex-1 overflow-hidden"> + {/* Sidebar - hidden on mobile */} + <aside + className={` + hidden md:flex flex-col h-[calc(100vh-4rem)] border-r bg-background flex-shrink-0 relative + transition-all duration-300 ease-in-out + ${isSidebarCollapsed ? 'w-16' : 'w-64'} + `} + > + {/* Sidebar Toggle Button */} + <button + onClick={toggleSidebar} + className="absolute -right-3 top-10 bg-primary text-primary-foreground rounded-full p-1 shadow-md hover:bg-primary/90 transition-colors duration-300 hover:scale-110 group z-10" + aria-label={isSidebarCollapsed ? "Expand sidebar" : "Collapse sidebar"} + > + <div className="transition-transform duration-300 group-hover:rotate-[360deg]"> + {isSidebarCollapsed ? <ChevronRightIcon size={16} /> : <ChevronLeftIcon size={16} />} + </div> + </button> + + <nav className="space-y-2 px-2 mt-4 flex-1"> + <Link + href="/dashboard" + className={` + flex items-center p-2 rounded-md transition-all duration-200 overflow-hidden + ${pathname === '/dashboard' + ? 'bg-primary/10 text-primary font-medium' + : 'hover:bg-muted text-foreground/80 hover:text-foreground' + } + ${isSidebarCollapsed ? 'justify-center' : 'justify-start'} + `} + title="Dashboard" + > + <LayoutDashboardIcon size={18} className={`transition-transform duration-300 ${pathname === '/dashboard' ? 'scale-110' : ''}`} /> + <span className={`ml-2 transition-all duration-300 overflow-hidden whitespace-nowrap ${isSidebarCollapsed ? 'w-0 opacity-0' : 'w-auto opacity-100'}`}> + Dashboard + </span> + </Link> + <Link + href="/loans" + className={` + flex items-center p-2 rounded-md transition-all duration-200 overflow-hidden + ${pathname === '/loans' + ? 'bg-primary/10 text-primary font-medium' + : 'hover:bg-muted text-foreground/80 hover:text-foreground' + } + ${isSidebarCollapsed ? 'justify-center' : 'justify-start'} + `} + title="Loans" + > + <CoinsIcon size={18} className={`transition-transform duration-300 ${pathname === '/loans' ? 'scale-110' : ''}`} /> + <span className={`ml-2 transition-all duration-300 overflow-hidden whitespace-nowrap ${isSidebarCollapsed ? 'w-0 opacity-0' : 'w-auto opacity-100'}`}> + Loans + </span> + </Link> + <Link + href="/goals" + className={` + flex items-center p-2 rounded-md transition-all duration-200 overflow-hidden + ${pathname === '/goals' + ? 'bg-primary/10 text-primary font-medium' + : 'hover:bg-muted text-foreground/80 hover:text-foreground' + } + ${isSidebarCollapsed ? 'justify-center' : 'justify-start'} + `} + title="Goals" + > + <TargetIcon size={18} className={`transition-transform duration-300 ${pathname === '/goals' ? 'scale-110' : ''}`} /> + <span className={`ml-2 transition-all duration-300 overflow-hidden whitespace-nowrap ${isSidebarCollapsed ? 'w-0 opacity-0' : 'w-auto opacity-100'}`}> + Goals + </span> + </Link> + <Link + href="/settings" + className={` + flex items-center p-2 rounded-md transition-all duration-200 overflow-hidden + ${pathname === '/settings' + ? 'bg-primary/10 text-primary font-medium' + : 'hover:bg-muted text-foreground/80 hover:text-foreground' + } + ${isSidebarCollapsed ? 'justify-center' : 'justify-start'} + `} + title="Settings" + > + <SettingsIcon size={18} className={`transition-transform duration-300 ${pathname === '/settings' ? 'scale-110' : ''}`} /> + <span className={`ml-2 transition-all duration-300 overflow-hidden whitespace-nowrap ${isSidebarCollapsed ? 'w-0 opacity-0' : 'w-auto opacity-100'}`}> + Settings + </span> + </Link> + </nav> + </aside> + + {/* Page Content */} + <main className="flex-1 p-4 md:p-8 overflow-auto h-[calc(100vh-4rem)] transition-all duration-300"> + <PageTransition> + {children} + </PageTransition> + </main> + </div> </div> - </div> + </ProtectedRoute> ); }
\ No newline at end of file |