feat: enhance layout and sidebar functionality with new PageHeader and SidebarContext

This commit is contained in:
JSC
2025-06-28 19:55:11 +02:00
parent 59ae7d8bf7
commit 75a76e33fe
11 changed files with 137 additions and 51 deletions

View File

@@ -1,6 +1,7 @@
import { AppLayout } from '@/components/AppLayout' import { AppLayout } from '@/components/AppLayout'
import { ProtectedRoute } from '@/components/ProtectedRoute' import { ProtectedRoute } from '@/components/ProtectedRoute'
import { AuthProvider } from '@/contexts/AuthContext' import { AuthProvider } from '@/contexts/AuthContext'
import { Button } from '@/components/ui/button'
import { ActivityPage } from '@/pages/ActivityPage' import { ActivityPage } from '@/pages/ActivityPage'
import { AdminUsersPage } from '@/pages/AdminUsersPage' import { AdminUsersPage } from '@/pages/AdminUsersPage'
import { DashboardPage } from '@/pages/DashboardPage' import { DashboardPage } from '@/pages/DashboardPage'
@@ -22,7 +23,7 @@ function App() {
path="/dashboard" path="/dashboard"
element={ element={
<ProtectedRoute> <ProtectedRoute>
<AppLayout> <AppLayout title="Dashboard" description="Welcome to your dashboard">
<DashboardPage /> <DashboardPage />
</AppLayout> </AppLayout>
</ProtectedRoute> </ProtectedRoute>
@@ -32,7 +33,7 @@ function App() {
path="/activity" path="/activity"
element={ element={
<ProtectedRoute> <ProtectedRoute>
<AppLayout> <AppLayout title="Activity" description="View recent activity and logs">
<ActivityPage /> <ActivityPage />
</AppLayout> </AppLayout>
</ProtectedRoute> </ProtectedRoute>
@@ -42,7 +43,7 @@ function App() {
path="/settings" path="/settings"
element={ element={
<ProtectedRoute> <ProtectedRoute>
<AppLayout> <AppLayout title="Settings" description="Manage your account settings and preferences">
<SettingsPage /> <SettingsPage />
</AppLayout> </AppLayout>
</ProtectedRoute> </ProtectedRoute>
@@ -52,7 +53,13 @@ function App() {
path="/admin/users" path="/admin/users"
element={ element={
<ProtectedRoute requireAdmin> <ProtectedRoute requireAdmin>
<AppLayout> <AppLayout
title="User Management"
description="Manage users and their permissions"
headerActions={
<Button>Add User</Button>
}
>
<AdminUsersPage /> <AdminUsersPage />
</AppLayout> </AppLayout>
</ProtectedRoute> </ProtectedRoute>

View File

@@ -1,19 +1,31 @@
import { type ReactNode } from 'react' import { type ReactNode } from 'react'
import { AppSidebar } from '@/components/AppSidebar' import { AppSidebar } from '@/components/AppSidebar'
import { PageHeader } from '@/components/PageHeader'
import { SidebarProvider } from '@/contexts/SidebarContext'
interface AppLayoutProps { interface AppLayoutProps {
children: ReactNode children: ReactNode
title: string
description?: string
headerActions?: ReactNode
} }
export function AppLayout({ children }: AppLayoutProps) { export function AppLayout({ children, title, description, headerActions }: AppLayoutProps) {
return ( return (
<SidebarProvider>
<div className="flex h-screen bg-background"> <div className="flex h-screen bg-background">
<AppSidebar /> <AppSidebar />
<div className="flex-1 flex flex-col overflow-hidden">
<PageHeader title={title} description={description}>
{headerActions}
</PageHeader>
<main className="flex-1 overflow-y-auto"> <main className="flex-1 overflow-y-auto">
<div className="container mx-auto p-6"> <div className="container mx-auto p-6">
{children} {children}
</div> </div>
</main> </main>
</div> </div>
</div>
</SidebarProvider>
) )
} }

View File

@@ -9,6 +9,7 @@ import {
SidebarNavItem SidebarNavItem
} from '@/components/ui/sidebar' } from '@/components/ui/sidebar'
import { useAuth } from '@/contexts/AuthContext' import { useAuth } from '@/contexts/AuthContext'
import { useSidebar } from '@/contexts/SidebarContext'
import { import {
Home, Home,
Settings, Settings,
@@ -45,6 +46,7 @@ const adminNavigationItems = [
export function AppSidebar() { export function AppSidebar() {
const { user, logout } = useAuth() const { user, logout } = useAuth()
const { isOpen } = useSidebar()
const location = useLocation() const location = useLocation()
const handleLogout = async () => { const handleLogout = async () => {
@@ -63,16 +65,18 @@ export function AppSidebar() {
] ]
return ( return (
<Sidebar className="w-64 border-r"> <Sidebar className={`border-r transition-all duration-300 ${isOpen ? 'w-64' : 'w-16'}`}>
<SidebarHeader> <SidebarHeader>
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<div className="w-8 h-8 bg-primary rounded-lg flex items-center justify-center"> <div className="w-8 h-8 bg-primary rounded-lg flex items-center justify-center">
<span className="text-primary-foreground font-bold text-sm">SB</span> <span className="text-primary-foreground font-bold text-sm">SB</span>
</div> </div>
{isOpen && (
<div> <div>
<h2 className="text-lg font-semibold">Soundboard</h2> <h2 className="text-lg font-semibold">Soundboard</h2>
<p className="text-xs text-muted-foreground">v2.0</p> <p className="text-xs text-muted-foreground">v2.0</p>
</div> </div>
)}
</div> </div>
</SidebarHeader> </SidebarHeader>
@@ -84,9 +88,12 @@ export function AppSidebar() {
return ( return (
<Link key={item.href} to={item.href}> <Link key={item.href} to={item.href}>
<SidebarNavItem active={isActive}> <SidebarNavItem
active={isActive}
title={!isOpen ? item.title : undefined}
>
<Icon className="h-4 w-4" /> <Icon className="h-4 w-4" />
{item.title} {isOpen && item.title}
</SidebarNavItem> </SidebarNavItem>
</Link> </Link>
) )
@@ -103,19 +110,21 @@ export function AppSidebar() {
className="w-8 h-8 rounded-full" className="w-8 h-8 rounded-full"
/> />
)} )}
{isOpen && (
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<p className="text-sm font-medium truncate">{user.name}</p> <p className="text-sm font-medium truncate">{user.name}</p>
<p className="text-xs text-muted-foreground truncate">{user.email}</p> <p className="text-xs text-muted-foreground truncate">{user.email}</p>
</div> </div>
)}
</div> </div>
<Button <Button
variant="ghost" variant="ghost"
onClick={handleLogout} onClick={handleLogout}
className="w-full justify-start" className={`w-full ${isOpen ? 'justify-start' : 'justify-center'}`}
> >
<LogOut className="h-4 w-4 mr-2" /> <LogOut className="h-4 w-4" />
Sign out {isOpen && <span className="ml-2">Sign out</span>}
</Button> </Button>
</SidebarFooter> </SidebarFooter>
</Sidebar> </Sidebar>

View File

@@ -0,0 +1,42 @@
import { Button } from '@/components/ui/button'
import { useSidebar } from '@/contexts/SidebarContext'
import { Menu } from 'lucide-react'
interface PageHeaderProps {
title: string
description?: string
children?: React.ReactNode
}
export function PageHeader({ title, description, children }: PageHeaderProps) {
const { toggle } = useSidebar()
return (
<div className="border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
<div className="flex h-14 items-center gap-4 px-4">
<Button
variant="ghost"
size="icon"
onClick={toggle}
className="h-9 w-9"
>
<Menu className="h-4 w-4" />
<span className="sr-only">Toggle sidebar</span>
</Button>
<div className="flex-1">
<h1 className="text-lg font-semibold">{title}</h1>
{description && (
<p className="text-sm text-muted-foreground">{description}</p>
)}
</div>
{children && (
<div className="flex items-center gap-2">
{children}
</div>
)}
</div>
</div>
)
}

View File

@@ -0,0 +1,35 @@
import { createContext, useContext, useState, type ReactNode } from 'react'
interface SidebarContextType {
isOpen: boolean
toggle: () => void
open: () => void
close: () => void
}
const SidebarContext = createContext<SidebarContextType | undefined>(undefined)
export function SidebarProvider({ children }: { children: ReactNode }) {
const [isOpen, setIsOpen] = useState(true)
const toggle = () => setIsOpen(prev => !prev)
const open = () => setIsOpen(true)
const close = () => setIsOpen(false)
const value = {
isOpen,
toggle,
open,
close,
}
return <SidebarContext.Provider value={value}>{children}</SidebarContext.Provider>
}
export function useSidebar() {
const context = useContext(SidebarContext)
if (context === undefined) {
throw new Error('useSidebar must be used within a SidebarProvider')
}
return context
}

View File

@@ -3,11 +3,6 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/com
export function ActivityPage() { export function ActivityPage() {
return ( return (
<div className="space-y-6"> <div className="space-y-6">
<div>
<h1 className="text-3xl font-bold">Activity</h1>
<p className="text-muted-foreground">View recent activity and logs</p>
</div>
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3"> <div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
<Card> <Card>
<CardHeader> <CardHeader>

View File

@@ -51,14 +51,6 @@ export function AdminUsersPage() {
return ( return (
<div className="space-y-6"> <div className="space-y-6">
<div className="flex items-center justify-between">
<div>
<h1 className="text-3xl font-bold">User Management</h1>
<p className="text-muted-foreground">Manage users and their permissions</p>
</div>
<Button>Add User</Button>
</div>
<Card> <Card>
<CardHeader> <CardHeader>
<CardTitle>Users</CardTitle> <CardTitle>Users</CardTitle>

View File

@@ -10,11 +10,6 @@ export function DashboardPage() {
return ( return (
<div className="space-y-6"> <div className="space-y-6">
<div>
<h1 className="text-3xl font-bold">Dashboard</h1>
<p className="text-muted-foreground">Welcome back, {user.name}!</p>
</div>
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3"> <div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
{/* User Profile Card */} {/* User Profile Card */}
<Card> <Card>

View File

@@ -42,7 +42,9 @@ export function LoginPage() {
const loadProviders = async () => { const loadProviders = async () => {
try { try {
const providers = await authService.getOAuthProviders() const providers = await authService.getOAuthProviders()
if (providers) {
setOauthProviders(providers) setOauthProviders(providers)
}
} catch (error) { } catch (error) {
console.error('Failed to load OAuth providers:', error) console.error('Failed to load OAuth providers:', error)
} }

View File

@@ -33,7 +33,9 @@ export function RegisterPage() {
const loadProviders = async () => { const loadProviders = async () => {
try { try {
const providers = await authService.getOAuthProviders() const providers = await authService.getOAuthProviders()
if (providers) {
setOauthProviders(providers) setOauthProviders(providers)
}
} catch (error) { } catch (error) {
console.error('Failed to load OAuth providers:', error) console.error('Failed to load OAuth providers:', error)
} }

View File

@@ -39,11 +39,6 @@ export function SettingsPage() {
return ( return (
<div className="space-y-6"> <div className="space-y-6">
<div>
<h1 className="text-3xl font-bold">Settings</h1>
<p className="text-muted-foreground">Manage your account settings and preferences</p>
</div>
<div className="grid gap-6 md:grid-cols-2"> <div className="grid gap-6 md:grid-cols-2">
<Card> <Card>
<CardHeader> <CardHeader>