Skip to content

Commit 32cc63b

Browse files
committed
feat: add DashboardLayout component for dashboard pages
1 parent f283154 commit 32cc63b

File tree

1 file changed

+145
-0
lines changed

1 file changed

+145
-0
lines changed

src/layouts/DashboardLayout.tsx

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import { useState } from "react";
2+
import { Outlet, NavLink, useLocation } from "react-router-dom";
3+
import { useAuth } from "../context/AuthContext";
4+
import { Home, FolderOpen, Globe, Settings, ChevronLeft, ChevronRight, LogOut } from "lucide-react";
5+
6+
const NAV_ITEMS = [
7+
{ to: "/dashboard", label: "Overview", icon: Home, end: true },
8+
{ to: "/dashboard/projects", label: "All Projects", icon: FolderOpen },
9+
{ to: "/dashboard/community", label: "Community", icon: Globe },
10+
{ to: "/dashboard/settings", label: "Settings", icon: Settings },
11+
];
12+
13+
function DashboardLayout() {
14+
const [isCollapsed, setIsCollapsed] = useState(false);
15+
const { user, logOut } = useAuth();
16+
const location = useLocation();
17+
18+
// Generate breadcrumbs from pathname
19+
const getBreadcrumbs = () => {
20+
const paths = location.pathname.split("/").filter(Boolean);
21+
return paths.map((path, index) => ({
22+
label: path.charAt(0).toUpperCase() + path.slice(1),
23+
isLast: index === paths.length - 1,
24+
}));
25+
};
26+
27+
const breadcrumbs = getBreadcrumbs();
28+
29+
return (
30+
<div className="min-h-screen flex bg-gray-900">
31+
{/* Sidebar */}
32+
<aside
33+
className={`fixed left-0 top-0 h-full bg-slate-900 border-r border-slate-800 flex flex-col transition-all duration-300 z-40 ${isCollapsed ? "w-16" : "w-60"
34+
}`}
35+
>
36+
{/* Logo */}
37+
<div className={`h-14 flex items-center border-b border-slate-800 ${isCollapsed ? "justify-center px-2" : "px-4"}`}>
38+
<div className="w-8 h-8 rounded-lg bg-gradient-to-br from-blue-500 to-purple-600 flex items-center justify-center flex-shrink-0">
39+
<span className="text-white font-bold text-sm">&lt;/&gt;</span>
40+
</div>
41+
{!isCollapsed && (
42+
<span className="ml-3 text-lg font-bold text-white tracking-tight">CodeSplit</span>
43+
)}
44+
</div>
45+
46+
{/* Navigation */}
47+
<nav className="flex-1 py-4 px-2 space-y-1">
48+
{NAV_ITEMS.map((item) => (
49+
<NavLink
50+
key={item.to}
51+
to={item.to}
52+
end={item.end}
53+
className={({ isActive }) =>
54+
`flex items-center gap-3 px-3 py-2.5 rounded-lg transition-all group relative ${isActive
55+
? "bg-slate-700/50 text-white"
56+
: "text-slate-400 hover:text-white hover:bg-slate-800"
57+
} ${isCollapsed ? "justify-center" : ""}`
58+
}
59+
>
60+
{({ isActive }) => (
61+
<>
62+
{isActive && (
63+
<div className="absolute left-0 top-1/2 -translate-y-1/2 w-0.5 h-5 bg-blue-500 rounded-r" />
64+
)}
65+
<item.icon className="w-5 h-5 flex-shrink-0" />
66+
{!isCollapsed && (
67+
<span className="text-sm font-medium">{item.label}</span>
68+
)}
69+
{isCollapsed && (
70+
<div className="absolute left-full ml-2 px-2 py-1 bg-slate-800 text-white text-xs rounded opacity-0 group-hover:opacity-100 pointer-events-none whitespace-nowrap z-50 transition-opacity">
71+
{item.label}
72+
</div>
73+
)}
74+
</>
75+
)}
76+
</NavLink>
77+
))}
78+
</nav>
79+
80+
{/* Collapse Toggle */}
81+
<div className="p-2 border-t border-slate-800">
82+
<button
83+
onClick={() => setIsCollapsed(!isCollapsed)}
84+
className="w-full flex items-center justify-center gap-2 px-3 py-2 text-slate-400 hover:text-white hover:bg-slate-800 rounded-lg transition-colors"
85+
title={isCollapsed ? "Expand sidebar" : "Collapse sidebar"}
86+
>
87+
{isCollapsed ? (
88+
<ChevronRight className="w-5 h-5" />
89+
) : (
90+
<>
91+
<ChevronLeft className="w-5 h-5" />
92+
<span className="text-sm">Collapse</span>
93+
</>
94+
)}
95+
</button>
96+
</div>
97+
</aside>
98+
99+
{/* Main Area */}
100+
<div className={`flex-1 flex flex-col transition-all duration-300 ${isCollapsed ? "ml-16" : "ml-60"}`}>
101+
{/* Top Header */}
102+
<header className="sticky top-0 z-30 h-14 bg-slate-900/80 backdrop-blur-md border-b border-slate-800 flex items-center justify-between px-6">
103+
{/* Breadcrumbs */}
104+
<nav className="flex items-center gap-2 text-sm">
105+
{breadcrumbs.map((crumb, index) => (
106+
<span key={index} className="flex items-center gap-2">
107+
{index > 0 && <span className="text-slate-600">/</span>}
108+
<span className={crumb.isLast ? "text-white font-medium" : "text-slate-400"}>
109+
{crumb.label}
110+
</span>
111+
</span>
112+
))}
113+
</nav>
114+
115+
{/* User Section */}
116+
<div className="flex items-center gap-3">
117+
{user && (
118+
<div className="flex items-center gap-3">
119+
<img
120+
src={user.photoURL || "https://via.placeholder.com/32"}
121+
alt={user.displayName || "User"}
122+
className="w-8 h-8 rounded-full border border-slate-700"
123+
/>
124+
<button
125+
onClick={logOut}
126+
className="p-2 text-slate-400 hover:text-white hover:bg-slate-800 rounded-lg transition-colors"
127+
title="Logout"
128+
>
129+
<LogOut className="w-4 h-4" />
130+
</button>
131+
</div>
132+
)}
133+
</div>
134+
</header>
135+
136+
{/* Page Content */}
137+
<main className="flex-1 p-6 overflow-y-auto">
138+
<Outlet />
139+
</main>
140+
</div>
141+
</div>
142+
);
143+
}
144+
145+
export default DashboardLayout;

0 commit comments

Comments
 (0)