'use client'
import { useEffect, useState, useRef } from 'react'
import { useRouter } from 'next/navigation'
import { useStore } from '@/lib/store'
import { Sidebar } from './Sidebar'
import { KeyboardShortcutsProvider, useUserShortcuts } from '@/hooks/useKeyboardShortcuts'
import { Bell, Search, X, Command } from 'lucide-react'
import Link from 'next/link'
import clsx from 'clsx'
interface TerminalLayoutProps {
children: React.ReactNode
title?: string
subtitle?: string
actions?: React.ReactNode
hideHeaderSearch?: boolean // New prop to control header elements
}
export function TerminalLayout({
children,
title,
subtitle,
actions,
hideHeaderSearch = false
}: TerminalLayoutProps) {
const router = useRouter()
const { isAuthenticated, isLoading, checkAuth, domains } = useStore()
const [sidebarCollapsed, setSidebarCollapsed] = useState(false)
const [notificationsOpen, setNotificationsOpen] = useState(false)
const [searchOpen, setSearchOpen] = useState(false)
const [searchQuery, setSearchQuery] = useState('')
const [mounted, setMounted] = useState(false)
const authCheckedRef = useRef(false)
// Ensure component is mounted before rendering
useEffect(() => {
setMounted(true)
}, [])
// Load sidebar state from localStorage
useEffect(() => {
if (mounted) {
const saved = localStorage.getItem('sidebar-collapsed')
if (saved) {
setSidebarCollapsed(saved === 'true')
}
}
}, [mounted])
// Check auth only once on mount
useEffect(() => {
if (!authCheckedRef.current) {
authCheckedRef.current = true
checkAuth()
}
}, [checkAuth])
useEffect(() => {
if (!isLoading && !isAuthenticated) {
router.push('/login')
}
}, [isLoading, isAuthenticated, router])
// Available domains for notifications
const availableDomains = domains?.filter(d => d.is_available) || []
const hasNotifications = availableDomains.length > 0
// Show loading only if we're still checking auth
if (!mounted || isLoading) {
return (
)
}
if (!isAuthenticated) {
return null
}
return (
{/* Background Effects */}
{/* Sidebar */}
{/* Main Content Area */}
{/* Top Bar - No longer sticky if hideHeaderSearch is true, or generally refined */}
{/* Left: Title */}
{title && (
{title}
)}
{subtitle && (
{subtitle}
)}
{/* Right: Actions */}
{!hideHeaderSearch && (
<>
{/* Quick Search */}
setSearchOpen(true)}
className="hidden md:flex items-center gap-2 h-9 px-3 bg-foreground/5 hover:bg-foreground/8
border border-border/40 rounded-lg text-sm text-foreground-muted
hover:text-foreground transition-all duration-200 hover:border-border/60"
>
Search
⌘K
{/* Mobile Search */}
setSearchOpen(true)}
className="md:hidden flex items-center justify-center w-9 h-9 text-foreground-muted
hover:text-foreground hover:bg-foreground/5 rounded-lg transition-colors"
>
{/* Notifications */}
setNotificationsOpen(!notificationsOpen)}
className={clsx(
"relative flex items-center justify-center w-9 h-9 rounded-lg transition-all duration-200",
notificationsOpen
? "bg-foreground/10 text-foreground"
: "text-foreground-muted hover:text-foreground hover:bg-foreground/5"
)}
>
{hasNotifications && (
)}
{/* Notifications Dropdown */}
{notificationsOpen && (
Notifications
setNotificationsOpen(false)}
className="text-zinc-500 hover:text-white"
>
{availableDomains.length > 0 ? (
{availableDomains.slice(0, 5).map((domain) => (
setNotificationsOpen(false)}
className="flex items-start gap-3 p-3 hover:bg-white/5 rounded-lg transition-colors"
>
{domain.name}
Available now!
))}
) : (
No notifications
We'll notify you when domains become available
)}
)}
{/* Keyboard Shortcuts Hint */}
{}}
className="hidden sm:flex items-center gap-1.5 px-2 py-1.5 text-xs text-foreground-subtle hover:text-foreground
bg-foreground/5 rounded-lg border border-border/40 hover:border-border/60 transition-all"
title="Keyboard shortcuts (?)"
>
?
>
)}
{/* Custom Actions */}
{actions}
{/* Page Content */}
{children}
{/* Quick Search Modal - Only if not hidden, or maybe still available via hotkey?
Let's keep it available via hotkey but hidden from UI if requested */}
{searchOpen && (
setSearchOpen(false)}
>
e.stopPropagation()}
>
setSearchQuery(e.target.value)}
className="flex-1 bg-transparent text-foreground placeholder:text-foreground-subtle
outline-none text-lg"
autoFocus
/>
setSearchOpen(false)}
className="flex items-center h-6 px-2 bg-background border border-border
rounded text-xs text-foreground-subtle font-mono hover:text-foreground transition-colors"
>
ESC
Start typing to search...
)}
{/* Keyboard shortcut for search - Still active unless strictly disabled */}
setSearchOpen(true)} keys={['Meta', 'k']} />
)
}
// Keyboard shortcut component
function KeyboardShortcut({ onTrigger, keys }: { onTrigger: () => void, keys: string[] }) {
useEffect(() => {
const handler = (e: KeyboardEvent) => {
if (keys.includes('Meta') && e.metaKey && e.key === 'k') {
e.preventDefault()
onTrigger()
}
}
document.addEventListener('keydown', handler)
return () => document.removeEventListener('keydown', handler)
}, [onTrigger, keys])
return null
}
// User shortcuts wrapper
function UserShortcutsWrapper() {
useUserShortcuts()
return null
}