mirror of
https://github.com/yusufipk/OpenFrame.git
synced 2026-09-11 09:36:08 +00:00
194 lines
6.5 KiB
TypeScript
194 lines
6.5 KiB
TypeScript
'use client';
|
|
|
|
import { useState } from 'react';
|
|
import Link from 'next/link';
|
|
import dynamic from 'next/dynamic';
|
|
import { usePathname } from 'next/navigation';
|
|
import {
|
|
Video,
|
|
FolderOpen,
|
|
Building2,
|
|
Plus,
|
|
Settings,
|
|
LogOut,
|
|
User,
|
|
Menu,
|
|
Keyboard
|
|
} from 'lucide-react';
|
|
import { Button } from '@/components/ui/button';
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuSeparator,
|
|
DropdownMenuTrigger,
|
|
} from '@/components/ui/dropdown-menu';
|
|
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
|
import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet';
|
|
import { ThemeToggle } from '@/components/theme-toggle';
|
|
import { cn } from '@/lib/utils';
|
|
|
|
const KeyboardShortcutsModal = dynamic(
|
|
() => import('@/components/keyboard-shortcuts-modal').then(mod => mod.KeyboardShortcutsModal),
|
|
{ ssr: false }
|
|
);
|
|
|
|
interface NavItem {
|
|
href: string;
|
|
label: string;
|
|
icon: React.ComponentType<{ className?: string }>;
|
|
}
|
|
|
|
const navItems: NavItem[] = [
|
|
{ href: '/dashboard', label: 'Projects', icon: FolderOpen },
|
|
{ href: '/workspaces', label: 'Workspaces', icon: Building2 },
|
|
];
|
|
|
|
interface HeaderProps {
|
|
user?: {
|
|
name?: string | null;
|
|
email?: string | null;
|
|
image?: string | null;
|
|
} | null;
|
|
}
|
|
|
|
export function Header({ user }: HeaderProps) {
|
|
const pathname = usePathname();
|
|
const [shortcutsOpen, setShortcutsOpen] = useState(false);
|
|
|
|
// Hide header on video player pages — they use full viewport with their own back button
|
|
const isVideoPage = /\/videos\/[^/]+$/.test(pathname) || pathname.startsWith('/watch/');
|
|
if (isVideoPage) return null;
|
|
|
|
return (
|
|
<header className="sticky top-0 z-50 w-full border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
|
<div className="px-6 lg:px-8 flex h-14 items-center w-full">
|
|
{/* Mobile menu */}
|
|
<Sheet>
|
|
<SheetTrigger asChild>
|
|
<Button variant="ghost" size="icon" className="md:hidden mr-2">
|
|
<Menu className="h-5 w-5" />
|
|
<span className="sr-only">Toggle menu</span>
|
|
</Button>
|
|
</SheetTrigger>
|
|
<SheetContent side="left" className="w-64">
|
|
<nav className="flex flex-col gap-2 mt-4">
|
|
{navItems.map((item) => (
|
|
<Link
|
|
key={item.href}
|
|
href={item.href}
|
|
className={cn(
|
|
'flex items-center gap-2 px-3 py-2 text-sm font-medium rounded-md transition-colors',
|
|
pathname === item.href
|
|
? 'bg-accent text-accent-foreground'
|
|
: 'hover:bg-accent hover:text-accent-foreground'
|
|
)}
|
|
>
|
|
<item.icon className="h-4 w-4" />
|
|
{item.label}
|
|
</Link>
|
|
))}
|
|
</nav>
|
|
</SheetContent>
|
|
</Sheet>
|
|
|
|
{/* Logo */}
|
|
<Link href="/" className="flex items-center gap-2 mr-6">
|
|
<Video className="h-6 w-6 text-primary" />
|
|
<span className="font-bold text-lg hidden sm:inline-block">OpenFrame</span>
|
|
</Link>
|
|
|
|
{/* Desktop nav */}
|
|
<nav className="hidden md:flex items-center gap-1">
|
|
{navItems.map((item) => (
|
|
<Link
|
|
key={item.href}
|
|
href={item.href}
|
|
className={cn(
|
|
'flex items-center gap-2 px-3 py-2 text-sm font-medium rounded-md transition-colors',
|
|
pathname === item.href
|
|
? 'bg-accent text-accent-foreground'
|
|
: 'hover:bg-accent/50'
|
|
)}
|
|
>
|
|
<item.icon className="h-4 w-4" />
|
|
{item.label}
|
|
</Link>
|
|
))}
|
|
</nav>
|
|
|
|
{/* Right side */}
|
|
<div className="flex items-center gap-2 ml-auto">
|
|
<Button asChild size="sm" className="hidden sm:flex">
|
|
<Link href="/projects/new">
|
|
<Plus className="h-4 w-4 mr-1" />
|
|
New Project
|
|
</Link>
|
|
</Button>
|
|
|
|
<Button asChild size="icon" variant="ghost" className="sm:hidden">
|
|
<Link href="/projects/new">
|
|
<Plus className="h-5 w-5" />
|
|
</Link>
|
|
</Button>
|
|
|
|
<ThemeToggle />
|
|
|
|
{user ? (
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger asChild>
|
|
<Button variant="ghost" className="relative h-8 w-8 rounded-full">
|
|
<Avatar className="h-8 w-8">
|
|
<AvatarImage src={user.image ?? undefined} alt={user.name ?? ''} />
|
|
<AvatarFallback>
|
|
{user.name?.charAt(0).toUpperCase() ?? 'U'}
|
|
</AvatarFallback>
|
|
</Avatar>
|
|
</Button>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent align="end" className="w-56">
|
|
<div className="flex items-center justify-start gap-2 p-2">
|
|
<div className="flex flex-col space-y-1 leading-none">
|
|
{user.name && <p className="font-medium">{user.name}</p>}
|
|
{user.email && (
|
|
<p className="w-[200px] truncate text-sm text-muted-foreground">
|
|
{user.email}
|
|
</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
<DropdownMenuSeparator />
|
|
<DropdownMenuItem asChild>
|
|
<Link href="/settings">
|
|
<Settings className="h-4 w-4 mr-2" />
|
|
Settings
|
|
</Link>
|
|
</DropdownMenuItem>
|
|
<DropdownMenuItem onClick={() => setShortcutsOpen(true)}>
|
|
<Keyboard className="h-4 w-4 mr-2" />
|
|
Shortcuts
|
|
</DropdownMenuItem>
|
|
<DropdownMenuSeparator />
|
|
<DropdownMenuItem asChild>
|
|
<Link href="/signout">
|
|
<LogOut className="h-4 w-4 mr-2" />
|
|
Sign out
|
|
</Link>
|
|
</DropdownMenuItem>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
) : (
|
|
<Button asChild variant="outline" size="sm">
|
|
<Link href="/login">
|
|
<User className="h-4 w-4 mr-1" />
|
|
Sign in
|
|
</Link>
|
|
</Button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
<KeyboardShortcutsModal open={shortcutsOpen} onOpenChange={setShortcutsOpen} />
|
|
</header>
|
|
);
|
|
}
|