'use client'; import Link from 'next/link'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { signOut, useSession } from '@/lib/auth-client'; interface AppHeaderProps { conversationTitle?: string | null; isSidebarOpen: boolean; onToggleSidebar: () => void; } type ThemeMode = 'dark' | 'light'; const THEME_STORAGE_KEY = 'mosaic-chat-theme'; export function AppHeader({ conversationTitle, isSidebarOpen, onToggleSidebar, }: AppHeaderProps): React.ReactElement { const { data: session } = useSession(); const [currentTime, setCurrentTime] = useState(''); const [version, setVersion] = useState(null); const [menuOpen, setMenuOpen] = useState(false); const [theme, setTheme] = useState('dark'); useEffect(() => { function updateTime(): void { setCurrentTime( new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', }), ); } updateTime(); const interval = window.setInterval(updateTime, 60_000); return () => window.clearInterval(interval); }, []); useEffect(() => { fetch('/version.json') .then(async (res) => res.json() as Promise<{ version?: string; commit?: string }>) .then((data) => { if (data.version) { setVersion(data.commit ? `${data.version}+${data.commit}` : data.version); } }) .catch(() => setVersion(null)); }, []); useEffect(() => { const storedTheme = window.localStorage.getItem(THEME_STORAGE_KEY); const nextTheme = storedTheme === 'light' ? 'light' : 'dark'; applyTheme(nextTheme); setTheme(nextTheme); }, []); const handleThemeToggle = useCallback(() => { const nextTheme = theme === 'dark' ? 'light' : 'dark'; applyTheme(nextTheme); window.localStorage.setItem(THEME_STORAGE_KEY, nextTheme); setTheme(nextTheme); }, [theme]); const handleSignOut = useCallback(async (): Promise => { await signOut(); window.location.href = '/login'; }, []); const userLabel = session?.user.name ?? session?.user.email ?? 'Mosaic User'; const initials = useMemo(() => getInitials(userLabel), [userLabel]); return (
M
Mosaic
Online
{currentTime || '--:--'}
{conversationTitle?.trim() || 'New Session'}
{version ? (
v{version}
) : null}
{menuOpen ? (
{userLabel}
{session?.user.email ? (
{session.user.email}
) : null}
setMenuOpen(false)} > Settings
) : null}
); } function ShortcutHint({ label, text }: { label: string; text: string }): React.ReactElement { return ( {label} {text} ); } function getInitials(label: string): string { const words = label.split(/\s+/).filter(Boolean).slice(0, 2); if (words.length === 0) return 'M'; return words.map((word) => word.charAt(0).toUpperCase()).join(''); } function applyTheme(theme: ThemeMode): void { const root = document.documentElement; if (theme === 'light') { root.setAttribute('data-theme', 'light'); root.classList.remove('dark'); } else { root.removeAttribute('data-theme'); root.classList.add('dark'); } }