Files
nextplacement/apps/admin/app/(main)/layout.tsx
2025-08-21 12:35:37 +05:30

292 lines
11 KiB
TypeScript

'use client';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { useState, useEffect } from 'react';
import { Button } from '@workspace/ui/components/button';
import { Badge } from '@workspace/ui/components/badge';
import {
LayoutDashboard,
Users,
Briefcase,
Menu,
X,
LogOut,
Settings,
Bell,
Search,
ChevronDown
} from 'lucide-react';
import { signOutAction } from './actions';
const navLinks = [
{
href: '/',
label: 'Dashboard',
icon: LayoutDashboard,
description: 'Overview and analytics'
},
{
href: '/students',
label: 'Students',
icon: Users,
description: 'Manage student profiles'
},
{
href: '/jobs',
label: 'Jobs',
icon: Briefcase,
description: 'Job listings and applications'
},
];
export default function MainLayout({ children }: { children: React.ReactNode }) {
const pathname = usePathname();
const [isScrolled, setIsScrolled] = useState(false);
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
const [isProfileDropdownOpen, setIsProfileDropdownOpen] = useState(false);
// Handle scroll effect
useEffect(() => {
const handleScroll = () => {
setIsScrolled(window.scrollY > 10);
};
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
// Close mobile menu when route changes
useEffect(() => {
setIsMobileMenuOpen(false);
}, [pathname]);
return (
<div className="min-h-screen bg-background font-sans">
{/* Enhanced Sticky Navbar */}
<header className={`sticky top-0 z-50 w-full transition-all duration-300 ${
isScrolled
? 'bg-white/95 backdrop-blur-md shadow-lg border-b border-gray-200/50'
: 'bg-white/80 backdrop-blur-sm'
}`}>
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex items-center justify-between h-16">
{/* Logo Section */}
<div className="flex items-center gap-3 group">
<div className="relative">
<img
src="/favicon.ico"
alt="Logo"
className="w-10 h-10 rounded-xl shadow-lg group-hover:shadow-xl transition-all duration-300 group-hover:scale-110"
/>
<div className="absolute inset-0 rounded-xl bg-gradient-to-br from-red-500/20 to-pink-500/20 opacity-0 group-hover:opacity-100 transition-opacity duration-300" />
</div>
<div className="flex flex-col">
<span className="font-extrabold text-2xl tracking-tight text-gray-800 group-hover:text-gray-900 transition-colors duration-200">
Placement Portal
</span>
<span className="text-xs text-gray-500 font-medium -mt-1">Admin Portal</span>
</div>
</div>
{/* Desktop Navigation */}
<nav className="hidden md:flex items-center gap-6">
{navLinks.map((link) => {
const isActive = pathname === link.href;
const Icon = link.icon;
return (
<Link
key={link.href}
href={link.href}
className={`relative group px-6 py-3 rounded-xl font-medium transition-all duration-200 ${
isActive
? 'text-red-600 bg-red-50 border border-red-200'
: 'text-gray-700 hover:text-gray-900 hover:bg-gray-50'
}`}
prefetch={false}
>
<div className="flex items-center gap-3">
<Icon className={`w-5 h-5 transition-transform duration-200 group-hover:scale-110 ${
isActive ? 'text-red-600' : 'text-gray-500 group-hover:text-gray-700'
}`} />
<span className="text-base">{link.label}</span>
</div>
{/* Active indicator */}
{isActive && (
<div className="absolute bottom-0 left-1/2 transform -translate-x-1/2 w-1 h-1 bg-red-500 rounded-full animate-pulse" />
)}
</Link>
);
})}
</nav>
{/* Right Section - Search, Notifications, Profile */}
<div className="flex items-center gap-3">
{/* Search Button */}
<Button
variant="ghost"
size="sm"
className="hidden sm:flex items-center gap-2 text-gray-600 hover:text-gray-900 hover:bg-gray-100 transition-all duration-200"
>
<Search className="w-4 h-4" />
<span className="hidden lg:inline text-sm">Search</span>
</Button>
{/* Notifications */}
<Button
variant="ghost"
size="sm"
className="relative text-gray-600 hover:text-gray-900 hover:bg-gray-100 transition-all duration-200"
>
<Bell className="w-4 h-4" />
<Badge className="absolute -top-1 -right-1 h-5 w-5 rounded-full bg-red-500 text-xs text-white flex items-center justify-center animate-pulse">
3
</Badge>
</Button>
{/* Profile Dropdown */}
<div className="relative">
<Button
variant="ghost"
size="sm"
onClick={() => setIsProfileDropdownOpen(!isProfileDropdownOpen)}
className="flex items-center gap-2 text-gray-700 hover:text-gray-900 hover:bg-gray-100 transition-all duration-200"
>
<div className="w-8 h-8 bg-gradient-to-br from-red-500 to-pink-500 rounded-full flex items-center justify-center text-white font-semibold text-sm">
A
</div>
<span className="hidden lg:inline text-sm font-medium">Admin</span>
<ChevronDown className={`w-4 h-4 transition-transform duration-200 ${
isProfileDropdownOpen ? 'rotate-180' : ''
}`} />
</Button>
{/* Profile Dropdown Menu */}
{isProfileDropdownOpen && (
<div className="absolute right-0 mt-2 w-48 bg-white rounded-xl shadow-lg border border-gray-200 py-2 z-50 animate-in slide-in-from-top-2 duration-200">
<div className="px-4 py-3 border-b border-gray-100">
<p className="text-sm font-medium text-gray-900">Admin User</p>
<p className="text-xs text-gray-500">admin@nextplacement.com</p>
</div>
<div className="py-1">
<Button
variant="ghost"
size="sm"
className="w-full justify-start text-gray-700 hover:text-gray-900 hover:bg-gray-50"
>
<Settings className="w-4 h-4 mr-2" />
Settings
</Button>
<Button
variant="ghost"
size="sm"
onClick={signOutAction}
className="w-full justify-start text-red-600 hover:text-red-700 hover:bg-red-50"
>
<LogOut className="w-4 h-4 mr-2" />
Sign Out
</Button>
</div>
</div>
)}
</div>
{/* Mobile Menu Button */}
<Button
variant="ghost"
size="sm"
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
className="md:hidden text-gray-600 hover:text-gray-900 hover:bg-gray-100 transition-all duration-200"
>
{isMobileMenuOpen ? (
<X className="w-5 h-5" />
) : (
<Menu className="w-5 h-5" />
)}
</Button>
</div>
</div>
{/* Mobile Navigation Menu */}
{isMobileMenuOpen && (
<div className="md:hidden border-t border-gray-200 bg-white/95 backdrop-blur-md animate-in slide-in-from-top-2 duration-200">
<nav className="px-4 py-4 space-y-2">
{navLinks.map((link) => {
const isActive = pathname === link.href;
const Icon = link.icon;
return (
<Link
key={link.href}
href={link.href}
className={`flex items-center gap-3 px-4 py-3 rounded-xl font-medium transition-all duration-200 ${
isActive
? 'text-red-600 bg-red-50 border border-red-200'
: 'text-gray-700 hover:text-gray-900 hover:bg-gray-50'
}`}
prefetch={false}
>
<Icon className={`w-5 h-5 ${
isActive ? 'text-red-600' : 'text-gray-500'
}`} />
<span>{link.label}</span>
{isActive && (
<div className="ml-auto w-2 h-2 bg-red-500 rounded-full animate-pulse" />
)}
</Link>
);
})}
{/* Mobile Profile Section */}
<div className="pt-4 border-t border-gray-200 mt-4">
<div className="flex items-center gap-3 px-4 py-3">
<div className="w-10 h-10 bg-gradient-to-br from-red-500 to-pink-500 rounded-full flex items-center justify-center text-white font-semibold">
A
</div>
<div>
<p className="text-sm font-medium text-gray-900">Admin User</p>
<p className="text-xs text-gray-500">admin@nextplacement.com</p>
</div>
</div>
<div className="px-4 py-2 space-y-1">
<Button
variant="ghost"
size="sm"
className="w-full justify-start text-gray-700 hover:text-gray-900 hover:bg-gray-50"
>
<Settings className="w-4 h-4 mr-2" />
Settings
</Button>
<Button
variant="ghost"
size="sm"
className="w-full justify-start text-red-600 hover:text-red-700 hover:bg-red-50"
>
<LogOut className="w-4 h-4 mr-2" />
Sign Out
</Button>
</div>
</div>
</nav>
</div>
)}
</div>
</header>
{/* Main Content */}
<main className="bg-background min-h-screen">
{children}
</main>
{/* Click outside to close dropdowns */}
{isProfileDropdownOpen && (
<div
className="fixed inset-0 z-40"
onClick={() => setIsProfileDropdownOpen(false)}
/>
)}
</div>
);
}