mirror of
https://github.com/dat515-2025/Group-8.git
synced 2026-03-22 06:57:47 +01:00
refactor(structure): move to 7project dir
This commit is contained in:
158
7project/frontend/src/components/Layout.tsx
Normal file
158
7project/frontend/src/components/Layout.tsx
Normal file
@@ -0,0 +1,158 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Link, useLocation, useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
Home,
|
||||
CreditCard,
|
||||
PieChart,
|
||||
User,
|
||||
Settings,
|
||||
LogOut,
|
||||
Menu,
|
||||
X,
|
||||
DollarSign
|
||||
} from 'lucide-react';
|
||||
import { useAuth } from '../contexts/AuthContext';
|
||||
|
||||
interface LayoutProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const Layout: React.FC<LayoutProps> = ({ children }) => {
|
||||
const [sidebarOpen, setSidebarOpen] = useState(false);
|
||||
const { user, logout } = useAuth();
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const navigation = [
|
||||
{ name: 'Dashboard', href: '/', icon: Home },
|
||||
{ name: 'Transactions', href: '/transactions', icon: CreditCard },
|
||||
{ name: 'Analytics', href: '/analytics', icon: PieChart },
|
||||
{ name: 'Profile', href: '/profile', icon: User },
|
||||
{ name: 'Settings', href: '/settings', icon: Settings },
|
||||
];
|
||||
|
||||
const handleLogout = async () => {
|
||||
await logout();
|
||||
navigate('/login');
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
{/* Mobile sidebar */}
|
||||
<div className={`lg:hidden ${sidebarOpen ? 'block' : 'hidden'}`}>
|
||||
<div className="fixed inset-0 z-50 flex">
|
||||
<div className="relative flex w-full max-w-xs flex-1 flex-col bg-white shadow-xl">
|
||||
<div className="absolute top-0 right-0 -mr-12 pt-2">
|
||||
<button
|
||||
type="button"
|
||||
className="ml-1 flex h-10 w-10 items-center justify-center rounded-full focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white"
|
||||
onClick={() => setSidebarOpen(false)}
|
||||
>
|
||||
<X className="h-6 w-6 text-white" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex flex-1 flex-col overflow-y-auto pb-4 pt-5">
|
||||
<div className="flex items-center flex-shrink-0 px-4">
|
||||
<DollarSign className="h-8 w-8 text-blue-600" />
|
||||
<span className="ml-2 text-xl font-bold text-gray-900">FinanceTracker</span>
|
||||
</div>
|
||||
<nav className="mt-8 flex-1 space-y-1 px-2">
|
||||
{navigation.map((item) => {
|
||||
const isActive = location.pathname === item.href;
|
||||
return (
|
||||
<Link
|
||||
key={item.name}
|
||||
to={item.href}
|
||||
className={`group flex items-center px-2 py-2 text-base font-medium rounded-md transition-colors ${
|
||||
isActive
|
||||
? 'bg-blue-100 text-blue-900'
|
||||
: 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'
|
||||
}`}
|
||||
onClick={() => setSidebarOpen(false)}
|
||||
>
|
||||
<item.icon className={`mr-4 h-6 w-6 ${isActive ? 'text-blue-500' : 'text-gray-400'}`} />
|
||||
{item.name}
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-14 flex-shrink-0"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Desktop sidebar */}
|
||||
<div className="hidden lg:fixed lg:inset-y-0 lg:flex lg:w-64 lg:flex-col">
|
||||
<div className="flex flex-1 flex-col min-h-0 bg-white shadow-lg">
|
||||
<div className="flex items-center h-16 flex-shrink-0 px-4 bg-white border-b border-gray-200">
|
||||
<DollarSign className="h-8 w-8 text-blue-600" />
|
||||
<span className="ml-2 text-xl font-bold text-gray-900">FinanceTracker</span>
|
||||
</div>
|
||||
<div className="flex-1 flex flex-col overflow-y-auto">
|
||||
<nav className="flex-1 px-2 py-4 space-y-1">
|
||||
{navigation.map((item) => {
|
||||
const isActive = location.pathname === item.href;
|
||||
return (
|
||||
<Link
|
||||
key={item.name}
|
||||
to={item.href}
|
||||
className={`group flex items-center px-2 py-2 text-sm font-medium rounded-md transition-colors ${
|
||||
isActive
|
||||
? 'bg-blue-100 text-blue-900'
|
||||
: 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'
|
||||
}`}
|
||||
>
|
||||
<item.icon className={`mr-3 h-5 w-5 ${isActive ? 'text-blue-500' : 'text-gray-400'}`} />
|
||||
{item.name}
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main content */}
|
||||
<div className="lg:pl-64 flex flex-col flex-1">
|
||||
{/* Header */}
|
||||
<div className="sticky top-0 z-40 flex h-16 flex-shrink-0 items-center gap-x-4 border-b border-gray-200 bg-white px-4 shadow-sm sm:gap-x-6 sm:px-6 lg:px-8">
|
||||
<button
|
||||
type="button"
|
||||
className="p-2.5 text-gray-700 lg:hidden"
|
||||
onClick={() => setSidebarOpen(true)}
|
||||
>
|
||||
<Menu className="h-6 w-6" />
|
||||
</button>
|
||||
|
||||
<div className="flex flex-1 gap-x-4 self-stretch lg:gap-x-6">
|
||||
<div className="flex flex-1 items-center">
|
||||
<h1 className="text-lg font-semibold text-gray-900">
|
||||
{navigation.find(item => item.href === location.pathname)?.name || 'Finance Tracker'}
|
||||
</h1>
|
||||
</div>
|
||||
<div className="flex items-center gap-x-4 lg:gap-x-6">
|
||||
<div className="flex items-center gap-x-2">
|
||||
<div className="text-sm text-gray-500">
|
||||
Welcome, {user?.first_name || user?.email?.split('@')[0]}
|
||||
</div>
|
||||
<button
|
||||
onClick={handleLogout}
|
||||
className="flex items-center gap-x-2 rounded-md bg-gray-100 px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-200 transition-colors"
|
||||
>
|
||||
<LogOut className="h-4 w-4" />
|
||||
Logout
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Page content */}
|
||||
<main className="flex-1">
|
||||
{children}
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user