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

View File

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

View File

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

View File

@@ -51,14 +51,6 @@ export function AdminUsersPage() {
return (
<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>
<CardHeader>
<CardTitle>Users</CardTitle>

View File

@@ -10,11 +10,6 @@ export function DashboardPage() {
return (
<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">
{/* User Profile Card */}
<Card>

View File

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

View File

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

View File

@@ -39,11 +39,6 @@ export function SettingsPage() {
return (
<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">
<Card>
<CardHeader>