You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

113 lines
4.3 KiB
TypeScript

import { type Component, Show } from 'solid-js';
import { A, useLocation } from '@solidjs/router';
import { BrainCircuit, Settings, LogOut, Menu, Shield } from 'lucide-solid';
import { useAuth } from '~/contexts/AuthContext';
import { useI18n } from '~/i18n';
interface NavbarProps {
onToggleMobile: () => void;
}
/**
* Top navigation bar visible on all authenticated pages.
*
* - **Active route detection**: The current pathname is compared against each
* link's `href` to apply a highlighted bottom-border style.
* - **Admin link visibility**: The admin navigation link is only rendered
* when `isAdmin()` is true, using a prefix match on `/admin` for the
* active state so all admin sub-routes highlight the same tab.
*/
const Navbar: Component<NavbarProps> = (props) => {
const { user, isAdmin, logout } = useAuth();
const location = useLocation();
const { t } = useI18n();
const isActive = (path: string) =>
location.pathname === path
? 'border-indigo-500 text-gray-900'
: 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700';
return (
<nav class="bg-white shadow-sm border-b border-gray-200">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between h-16">
<div class="flex">
<div class="flex-shrink-0 flex items-center">
<BrainCircuit class="h-8 w-8 text-indigo-600" />
<A href="/" class="ml-2 text-xl font-bold text-gray-900">
AI Weekly Synth
</A>
</div>
<div class="hidden sm:ml-6 sm:flex sm:space-x-8">
<A
href="/"
class={`inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium ${isActive('/')}`}
>
{t('nav.syntheses')}
</A>
<A
href="/themes"
class={`inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium ${isActive('/themes')}`}
>
{t('nav.themes')}
</A>
<Show when={isAdmin()}>
<A
href="/admin/providers"
class={`inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium ${
location.pathname.startsWith('/admin')
? 'border-indigo-500 text-gray-900'
: 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700'
}`}
>
<Shield class="h-4 w-4 mr-1" />
{t('nav.admin')}
</A>
</Show>
</div>
</div>
<div class="flex items-center">
{/* Mobile hamburger button */}
<button
type="button"
onClick={() => props.onToggleMobile()}
class="sm:hidden inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-500"
aria-label={t('nav.menuOpen')}
>
<Menu class="h-6 w-6" />
</button>
{/* Desktop user section */}
<Show when={user()}>
{(currentUser) => (
<div class="hidden sm:flex items-center space-x-4">
<span class="text-sm text-gray-700">
{currentUser().email}
</span>
<A
href="/settings"
class="text-gray-400 hover:text-gray-600 p-2 rounded-full hover:bg-gray-100 transition-colors"
title={t('nav.settings')}
>
<Settings class="h-5 w-5" />
</A>
<button
onClick={() => logout()}
class="inline-flex items-center px-3 py-1.5 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
<LogOut class="h-4 w-4 mr-2" />
{t('nav.logout')}
</button>
</div>
)}
</Show>
</div>
</div>
</div>
</nav>
);
};
export default Navbar;