diff --git a/apps/admin/app/(main)/layout.tsx b/apps/admin/app/(main)/layout.tsx index ea23a63..d318fce 100644 --- a/apps/admin/app/(main)/layout.tsx +++ b/apps/admin/app/(main)/layout.tsx @@ -1,38 +1,289 @@ +'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'; const navLinks = [ - { href: '/', label: 'Dashboard' }, - { href: '/students', label: 'Students' }, - { href: '/jobs', label: 'Jobs' }, + { + 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 (
- {/* Sticky top navbar */} -
-
-
- Logo - NextPlacement -
- + + Search + + + {/* Notifications */} + + + {/* Profile Dropdown */} +
+ + + {/* Profile Dropdown Menu */} + {isProfileDropdownOpen && ( +
+
+

Admin User

+

admin@nextplacement.com

+
+
+ + +
+
+ )} +
+ + {/* Mobile Menu Button */} + +
+
+ + {/* Mobile Navigation Menu */} + {isMobileMenuOpen && ( +
+ +
+ )} -
+ + {/* Main Content */} +
{children}
+ + {/* Click outside to close dropdowns */} + {isProfileDropdownOpen && ( +
setIsProfileDropdownOpen(false)} + /> + )}
); } diff --git a/apps/admin/package.json b/apps/admin/package.json index 0e6d5e3..2c68218 100644 --- a/apps/admin/package.json +++ b/apps/admin/package.json @@ -12,11 +12,14 @@ "typecheck": "tsc --noEmit" }, "dependencies": { + "@headlessui/react": "^2.2.4", + "@heroicons/react": "^2.2.0", "@hookform/resolvers": "^5.1.1", "@tailwindcss/postcss": "^4.0.8", "@tanstack/react-table": "^8.21.3", "@workspace/db": "workspace:*", "@workspace/ui": "workspace:*", + "clsx": "^2.1.1", "date-fns": "^4.1.0", "framer-motion": "^12.22.0", "lucide-react": "^0.475.0", diff --git a/packages/ui/src/styles/globals.css b/packages/ui/src/styles/globals.css index fbd6fe5..263f80b 100644 --- a/packages/ui/src/styles/globals.css +++ b/packages/ui/src/styles/globals.css @@ -128,3 +128,51 @@ .animate-bounce-slow { animation: bounce-slow 2.5s infinite; } .animate-fade-in { animation: fade-in 1.2s 0.5s both; } .animate-ripple { animation: ripple 0.6s linear; } + +/* Navbar specific animations */ +@keyframes slide-in-from-top-2 { + from { + opacity: 0; + transform: translateY(-8px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes scale-in { + from { + opacity: 0; + transform: scale(0.95); + } + to { + opacity: 1; + transform: scale(1); + } +} + +@keyframes glow-pulse { + 0%, 100% { + box-shadow: 0 0 5px rgba(239, 68, 68, 0.3); + } + 50% { + box-shadow: 0 0 20px rgba(239, 68, 68, 0.6); + } +} + +.animate-in { + animation-fill-mode: both; +} + +.slide-in-from-top-2 { + animation: slide-in-from-top-2 0.2s ease-out; +} + +.scale-in { + animation: scale-in 0.2s ease-out; +} + +.glow-pulse { + animation: glow-pulse 2s ease-in-out infinite; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8fb37a3..f70d1bb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,6 +26,12 @@ importers: apps/admin: dependencies: + '@headlessui/react': + specifier: ^2.2.4 + version: 2.2.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@heroicons/react': + specifier: ^2.2.0 + version: 2.2.0(react@19.1.0) '@hookform/resolvers': specifier: ^5.1.1 version: 5.1.1(react-hook-form@7.59.0(react@19.1.0)) @@ -41,6 +47,9 @@ importers: '@workspace/ui': specifier: workspace:* version: link:../../packages/ui + clsx: + specifier: ^2.1.1 + version: 2.1.1 date-fns: specifier: ^4.1.0 version: 4.1.0 @@ -712,9 +721,27 @@ packages: react: '>=16.8.0' react-dom: '>=16.8.0' + '@floating-ui/react@0.26.28': + resolution: {integrity: sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + '@floating-ui/utils@0.2.10': resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} + '@headlessui/react@2.2.4': + resolution: {integrity: sha512-lz+OGcAH1dK93rgSMzXmm1qKOJkBUqZf1L4M8TWLNplftQD3IkoEDdUFNfAn4ylsN6WOTVtWaLmvmaHOUk1dTA==} + engines: {node: '>=10'} + peerDependencies: + react: ^18 || ^19 || ^19.0.0-rc + react-dom: ^18 || ^19 || ^19.0.0-rc + + '@heroicons/react@2.2.0': + resolution: {integrity: sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ==} + peerDependencies: + react: '>= 16 || ^19.0.0-rc' + '@hookform/resolvers@5.1.1': resolution: {integrity: sha512-J/NVING3LMAEvexJkyTLjruSm7aOFx7QX21pzkiJfMoNG0wl5aFEjLTl7ay7IQb9EWY6AkrBy7tHL2Alijpdcg==} peerDependencies: @@ -1318,6 +1345,43 @@ packages: '@radix-ui/rect@1.1.1': resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} + '@react-aria/focus@3.20.5': + resolution: {integrity: sha512-JpFtXmWQ0Oca7FcvkqgjSyo6xEP7v3oQOLUId6o0xTvm4AD5W0mU2r3lYrbhsJ+XxdUUX4AVR5473sZZ85kU4A==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + + '@react-aria/interactions@3.25.3': + resolution: {integrity: sha512-J1bhlrNtjPS/fe5uJQ+0c7/jiXniwa4RQlP+Emjfc/iuqpW2RhbF9ou5vROcLzWIyaW8tVMZ468J68rAs/aZ5A==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + + '@react-aria/ssr@3.9.9': + resolution: {integrity: sha512-2P5thfjfPy/np18e5wD4WPt8ydNXhij1jwA8oehxZTFqlgVMGXzcWKxTb4RtJrLFsqPO7RUQTiY8QJk0M4Vy2g==} + engines: {node: '>= 12'} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + + '@react-aria/utils@3.29.1': + resolution: {integrity: sha512-yXMFVJ73rbQ/yYE/49n5Uidjw7kh192WNN9PNQGV0Xoc7EJUlSOxqhnpHmYTyO0EotJ8fdM1fMH8durHjUSI8g==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + + '@react-stately/flags@3.1.2': + resolution: {integrity: sha512-2HjFcZx1MyQXoPqcBGALwWWmgFVUk2TuKVIQxCbRq7fPyWXIl6VHcakCLurdtYC2Iks7zizvz0Idv48MQ38DWg==} + + '@react-stately/utils@3.10.7': + resolution: {integrity: sha512-cWvjGAocvy4abO9zbr6PW6taHgF24Mwy/LbQ4TC4Aq3tKdKDntxyD+sh7AkSRfJRT2ccMVaHVv2+FfHThd3PKQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + + '@react-types/shared@3.30.0': + resolution: {integrity: sha512-COIazDAx1ncDg046cTJ8SFYsX8aS3lB/08LDnbkH/SkdYrFPWDlXMrO/sUam8j1WWM+PJ+4d1mj7tODIKNiFog==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + '@standard-schema/utils@0.3.0': resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} @@ -1422,10 +1486,19 @@ packages: react: '>=16.8' react-dom: '>=16.8' + '@tanstack/react-virtual@3.13.12': + resolution: {integrity: sha512-Gd13QdxPSukP8ZrkbgS2RwoZseTTbQPLnQEn7HY/rqtM+8Zt95f7xKC7N0EsKs7aoz0WzZ+fditZux+F8EzYxA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + '@tanstack/table-core@8.21.3': resolution: {integrity: sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==} engines: {node: '>=12'} + '@tanstack/virtual-core@3.13.12': + resolution: {integrity: sha512-1YBOJfRHV4sXUmWsFSf5rQor4Ss82G8dQWLRbnk3GA4jeP8hQt1hxXh0tmflpC0dz3VgEv/1+qwPyLeWkQuPFA==} + '@tootallnate/quickjs-emscripten@0.23.0': resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==} @@ -3327,6 +3400,9 @@ packages: swap-case@1.1.2: resolution: {integrity: sha512-BAmWG6/bx8syfc6qXPprof3Mn5vQgf5dwdUNJhsNqU9WdPt5P+ES/wQ5bxfijy8zwZgZZHslC3iAsxsuQMCzJQ==} + tabbable@6.2.0: + resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==} + tailwind-merge@3.3.1: resolution: {integrity: sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==} @@ -3513,6 +3589,11 @@ packages: '@types/react': optional: true + use-sync-external-store@1.5.0: + resolution: {integrity: sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -3828,8 +3909,30 @@ snapshots: react: 19.1.0 react-dom: 19.1.0(react@19.1.0) + '@floating-ui/react@0.26.28(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@floating-ui/react-dom': 2.1.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@floating-ui/utils': 0.2.10 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + tabbable: 6.2.0 + '@floating-ui/utils@0.2.10': {} + '@headlessui/react@2.2.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@floating-ui/react': 0.26.28(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@react-aria/focus': 3.20.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@react-aria/interactions': 3.25.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@tanstack/react-virtual': 3.13.12(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + use-sync-external-store: 1.5.0(react@19.1.0) + + '@heroicons/react@2.2.0(react@19.1.0)': + dependencies: + react: 19.1.0 + '@hookform/resolvers@5.1.1(react-hook-form@7.59.0(react@19.1.0))': dependencies: '@standard-schema/utils': 0.3.0 @@ -4357,6 +4460,55 @@ snapshots: '@radix-ui/rect@1.1.1': {} + '@react-aria/focus@3.20.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@react-aria/interactions': 3.25.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@react-aria/utils': 3.29.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@react-types/shared': 3.30.0(react@19.1.0) + '@swc/helpers': 0.5.15 + clsx: 2.1.1 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + '@react-aria/interactions@3.25.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@react-aria/ssr': 3.9.9(react@19.1.0) + '@react-aria/utils': 3.29.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@react-stately/flags': 3.1.2 + '@react-types/shared': 3.30.0(react@19.1.0) + '@swc/helpers': 0.5.15 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + '@react-aria/ssr@3.9.9(react@19.1.0)': + dependencies: + '@swc/helpers': 0.5.15 + react: 19.1.0 + + '@react-aria/utils@3.29.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@react-aria/ssr': 3.9.9(react@19.1.0) + '@react-stately/flags': 3.1.2 + '@react-stately/utils': 3.10.7(react@19.1.0) + '@react-types/shared': 3.30.0(react@19.1.0) + '@swc/helpers': 0.5.15 + clsx: 2.1.1 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + '@react-stately/flags@3.1.2': + dependencies: + '@swc/helpers': 0.5.15 + + '@react-stately/utils@3.10.7(react@19.1.0)': + dependencies: + '@swc/helpers': 0.5.15 + react: 19.1.0 + + '@react-types/shared@3.30.0(react@19.1.0)': + dependencies: + react: 19.1.0 + '@standard-schema/utils@0.3.0': {} '@swc/counter@0.1.3': {} @@ -4443,8 +4595,16 @@ snapshots: react: 19.1.0 react-dom: 19.1.0(react@19.1.0) + '@tanstack/react-virtual@3.13.12(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@tanstack/virtual-core': 3.13.12 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + '@tanstack/table-core@8.21.3': {} + '@tanstack/virtual-core@3.13.12': {} + '@tootallnate/quickjs-emscripten@0.23.0': {} '@tsconfig/node10@1.0.11': {} @@ -6620,6 +6780,8 @@ snapshots: lower-case: 1.1.4 upper-case: 1.1.3 + tabbable@6.2.0: {} + tailwind-merge@3.3.1: {} tailwindcss@4.1.11: {} @@ -6809,6 +6971,10 @@ snapshots: optionalDependencies: '@types/react': 19.1.8 + use-sync-external-store@1.5.0(react@19.1.0): + dependencies: + react: 19.1.0 + util-deprecate@1.0.2: {} v8-compile-cache-lib@3.0.1: {}