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
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;
|