feat: enhance layout and sidebar functionality with new PageHeader and SidebarContext
This commit is contained in:
15
src/App.tsx
15
src/App.tsx
@@ -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>
|
||||
|
||||
@@ -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 (
|
||||
<div className="flex h-screen bg-background">
|
||||
<AppSidebar />
|
||||
<main className="flex-1 overflow-y-auto">
|
||||
<div className="container mx-auto p-6">
|
||||
{children}
|
||||
<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>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</SidebarProvider>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold">Soundboard</h2>
|
||||
<p className="text-xs text-muted-foreground">v2.0</p>
|
||||
</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"
|
||||
/>
|
||||
)}
|
||||
<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>
|
||||
{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>
|
||||
|
||||
42
src/components/PageHeader.tsx
Normal file
42
src/components/PageHeader.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
35
src/contexts/SidebarContext.tsx
Normal file
35
src/contexts/SidebarContext.tsx
Normal 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
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -42,7 +42,9 @@ export function LoginPage() {
|
||||
const loadProviders = async () => {
|
||||
try {
|
||||
const providers = await authService.getOAuthProviders()
|
||||
setOauthProviders(providers)
|
||||
if (providers) {
|
||||
setOauthProviders(providers)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load OAuth providers:', error)
|
||||
}
|
||||
|
||||
@@ -33,7 +33,9 @@ export function RegisterPage() {
|
||||
const loadProviders = async () => {
|
||||
try {
|
||||
const providers = await authService.getOAuthProviders()
|
||||
setOauthProviders(providers)
|
||||
if (providers) {
|
||||
setOauthProviders(providers)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load OAuth providers:', error)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user