Some checks failed
Deploy to Production / Build and Deploy (push) Failing after 1m7s
Replaced ~100+ hardcoded French and English text strings across 30+ components with proper i18n t() calls. Added 57 new translation keys to all 15 locale files (ar, de, en, es, fa, fr, hi, it, ja, ko, nl, pl, pt, ru, zh). Key changes: - contextual-ai-chat.tsx: 30 French strings → t() (actions, toasts, labels, placeholders) - ai-chat.tsx: 15 French/English strings → t() (header, tabs, welcome, insights, history) - note-inline-editor.tsx: 20 French fallbacks removed (toolbar, save status, checklist) - lab-skeleton.tsx: French loading text → t() - admin-header.tsx, header.tsx, editor-connections-section.tsx: French fallbacks removed - New AI chat component, agent cards, sidebar, settings panel i18n cleanup Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
137 lines
6.0 KiB
TypeScript
137 lines
6.0 KiB
TypeScript
'use client'
|
|
|
|
import { signOut, useSession } from 'next-auth/react'
|
|
import { Shield, Search, Settings, LogOut, User, StickyNote, FlaskConical, Bot } from 'lucide-react'
|
|
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuSeparator,
|
|
DropdownMenuTrigger,
|
|
} from '@/components/ui/dropdown-menu'
|
|
import { NotificationPanel } from './notification-panel'
|
|
import { useLanguage } from '@/lib/i18n'
|
|
|
|
/**
|
|
* Admin header — visuellement identique au Header principal.
|
|
* Utilise exclusivement des <a> (rechargement complet) au lieu de <Link>
|
|
* pour éviter React Error #310 (bug React #33580 / Next.js #63388).
|
|
*/
|
|
export function AdminHeader() {
|
|
const { data: session } = useSession()
|
|
const { t } = useLanguage()
|
|
|
|
const user = session?.user
|
|
const initial = user?.name
|
|
? user.name.charAt(0).toUpperCase()
|
|
: user?.email?.[0]?.toUpperCase() ?? '?'
|
|
|
|
return (
|
|
<header className="flex-none flex items-center justify-between whitespace-nowrap border-b border-solid border-slate-200 dark:border-slate-800 bg-white dark:bg-[#1e2128] px-6 py-3 z-20">
|
|
{/* ── Logo + Search ── */}
|
|
<div className="flex items-center gap-8">
|
|
<a href="/" className="flex items-center gap-3 text-slate-900 dark:text-white group no-underline">
|
|
<div className="size-8 bg-primary rounded-lg flex items-center justify-center text-primary-foreground shadow-sm group-hover:shadow-md transition-all">
|
|
<StickyNote className="w-5 h-5" />
|
|
</div>
|
|
<h2 className="text-xl font-bold leading-tight tracking-tight">MEMENTO</h2>
|
|
</a>
|
|
|
|
{/* Badge Admin */}
|
|
<span className="hidden sm:flex items-center gap-1.5 px-2.5 py-0.5 rounded-full bg-primary/10 text-primary text-xs font-semibold">
|
|
<Shield className="h-3 w-3" />
|
|
Admin
|
|
</span>
|
|
|
|
{/* Search (décoratif en mode admin) — même taille que l'entête principale */}
|
|
<label className="hidden md:flex flex-col min-w-40 w-96 !h-10">
|
|
<div className="flex w-full flex-1 items-stretch rounded-full h-full bg-slate-100 dark:bg-slate-800 border border-transparent">
|
|
<div className="text-slate-400 dark:text-slate-400 flex items-center justify-center pl-4">
|
|
<Search className="w-4 h-4" />
|
|
</div>
|
|
<input
|
|
className="form-input flex w-full min-w-0 flex-1 resize-none overflow-hidden bg-transparent border-none text-slate-900 dark:text-white placeholder:text-slate-400 dark:placeholder:text-slate-500 px-3 text-sm focus:ring-0 focus:outline-none"
|
|
placeholder={t('search.placeholder') }
|
|
type="text"
|
|
disabled
|
|
aria-label={t('search.disabledAdmin')}
|
|
/>
|
|
</div>
|
|
</label>
|
|
</div>
|
|
|
|
{/* ── Droite : nav + notifs + settings + avatar ── */}
|
|
<div className="flex flex-1 justify-end gap-2 items-center">
|
|
{/* Nav pills — toutes en <a> pour éviter la RSC race condition */}
|
|
<div className="hidden md:flex items-center gap-1 bg-slate-100 dark:bg-slate-800/60 rounded-full px-1.5 py-1">
|
|
<a
|
|
href="/agents"
|
|
className="flex items-center justify-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-medium text-muted-foreground hover:text-foreground transition-colors"
|
|
>
|
|
<Bot className="h-3.5 w-3.5" />
|
|
<span>{t('nav.agents') || 'Agents'}</span>
|
|
</a>
|
|
<a
|
|
href="/lab"
|
|
className="flex items-center justify-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-medium text-muted-foreground hover:text-foreground transition-colors"
|
|
>
|
|
<FlaskConical className="h-3.5 w-3.5" />
|
|
<span>{t('nav.lab') || 'The Lab'}</span>
|
|
</a>
|
|
</div>
|
|
|
|
|
|
{/* Notifications */}
|
|
<NotificationPanel />
|
|
|
|
{/* Settings */}
|
|
<a
|
|
href="/settings"
|
|
className="flex items-center justify-center size-10 rounded-full hover:bg-slate-100 dark:hover:bg-slate-800 text-slate-600 dark:text-slate-300 transition-colors"
|
|
aria-label={t('settings.title')}
|
|
>
|
|
<Settings className="w-5 h-5" />
|
|
</a>
|
|
|
|
{/* Avatar + menu */}
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger asChild>
|
|
<div
|
|
className="flex items-center justify-center bg-center bg-no-repeat bg-cover rounded-full size-10 ring-2 ring-white dark:ring-slate-700 cursor-pointer shadow-sm hover:shadow-md transition-shadow bg-primary/10 dark:bg-primary/20 text-primary dark:text-primary-foreground"
|
|
style={user?.image ? { backgroundImage: `url(${(user as any).image})` } : undefined}
|
|
>
|
|
{!user?.image && (
|
|
<span className="text-sm font-semibold">{initial}</span>
|
|
)}
|
|
</div>
|
|
</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 className="cursor-pointer">
|
|
<a href="/settings/profile">
|
|
<User className="mr-2 h-4 w-4" />
|
|
<span>{t('settings.profile') }</span>
|
|
</a>
|
|
</DropdownMenuItem>
|
|
<DropdownMenuSeparator />
|
|
<DropdownMenuItem
|
|
onClick={() => signOut({ callbackUrl: '/' })}
|
|
className="cursor-pointer text-red-600 focus:text-red-600"
|
|
>
|
|
<LogOut className="mr-2 h-4 w-4" />
|
|
<span>{t('auth.signOut') }</span>
|
|
</DropdownMenuItem>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
</div>
|
|
</header>
|
|
)
|
|
}
|