aboutsummaryrefslogtreecommitdiffstats
path: root/frontend/src/app/(main)/layout.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/src/app/(main)/layout.tsx')
-rw-r--r--frontend/src/app/(main)/layout.tsx437
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