234 lines
5.3 KiB
Plaintext
234 lines
5.3 KiB
Plaintext
package baseview
|
|
|
|
import (
|
|
"context"
|
|
"git.haelnorr.com/h/oslstats/internal/contexts"
|
|
"git.haelnorr.com/h/oslstats/internal/db"
|
|
)
|
|
|
|
type NavItem struct {
|
|
Name string
|
|
Href string
|
|
}
|
|
|
|
type ProfileItem struct {
|
|
Name string
|
|
Href string
|
|
}
|
|
|
|
// Main navigation items (centralized)
|
|
func getNavItems() []NavItem {
|
|
return []NavItem{
|
|
{Name: "Seasons", Href: "/seasons"},
|
|
}
|
|
}
|
|
|
|
// Profile dropdown items (context-aware for admin)
|
|
func getProfileItems(ctx context.Context) []ProfileItem {
|
|
items := []ProfileItem{
|
|
{Name: "Profile", Href: "/profile"},
|
|
{Name: "Account", Href: "/account"},
|
|
}
|
|
|
|
cache := contexts.Permissions(ctx)
|
|
if cache != nil && cache.Roles["admin"] {
|
|
items = append(items, ProfileItem{
|
|
Name: "Admin Panel",
|
|
Href: "/admin",
|
|
})
|
|
}
|
|
|
|
return items
|
|
}
|
|
|
|
// Main navbar component
|
|
templ Navbar() {
|
|
{{ navItems := getNavItems() }}
|
|
{{ user := db.CurrentUser(ctx) }}
|
|
{{ profileItems := getProfileItems(ctx) }}
|
|
<div x-data="{ open: false }">
|
|
<header class="bg-crust">
|
|
<div class="mx-auto flex h-16 max-w-7xl items-center gap-8 px-4 sm:px-6 lg:px-8">
|
|
<!-- Logo -->
|
|
<a class="block" href="/">
|
|
<span class="text-3xl font-bold transition hover:text-green">
|
|
OSL Stats
|
|
</span>
|
|
</a>
|
|
<div class="flex flex-1 items-center justify-end sm:justify-between">
|
|
<!-- Desktop nav links -->
|
|
@desktopNav(navItems)
|
|
<!-- User menu / Login button -->
|
|
@userMenu(user, profileItems)
|
|
</div>
|
|
</div>
|
|
</header>
|
|
<!-- Mobile side nav -->
|
|
@mobileNav(navItems, user)
|
|
</div>
|
|
}
|
|
|
|
// Desktop navigation (private helper)
|
|
templ desktopNav(navItems []NavItem) {
|
|
<nav aria-label="Global" class="hidden sm:block">
|
|
<ul class="flex items-center gap-6 text-xl">
|
|
for _, item := range navItems {
|
|
<li>
|
|
<a
|
|
class="text-subtext1 hover:text-green transition"
|
|
href={ templ.SafeURL(item.Href) }
|
|
>
|
|
{ item.Name }
|
|
</a>
|
|
</li>
|
|
}
|
|
</ul>
|
|
</nav>
|
|
}
|
|
|
|
// User menu section (private helper)
|
|
templ userMenu(user *db.User, profileItems []ProfileItem) {
|
|
<div class="flex items-center gap-2">
|
|
<div class="sm:flex sm:gap-2">
|
|
if user != nil {
|
|
@profileDropdown(user, profileItems)
|
|
} else {
|
|
@loginButton()
|
|
}
|
|
</div>
|
|
@mobileMenuButton()
|
|
</div>
|
|
}
|
|
|
|
// Profile dropdown (private helper)
|
|
templ profileDropdown(user *db.User, items []ProfileItem) {
|
|
<div x-data="{ isActive: false }" class="relative">
|
|
<div
|
|
class="inline-flex items-center overflow-hidden rounded-lg
|
|
bg-sapphire hover:bg-sapphire/75 transition"
|
|
>
|
|
<button
|
|
x-on:click="isActive = !isActive"
|
|
class="h-full py-2 px-4 text-mantle hover:cursor-pointer"
|
|
>
|
|
<span class="sr-only">Profile</span>
|
|
{ user.Username }
|
|
</button>
|
|
</div>
|
|
<div
|
|
class="absolute end-0 z-10 mt-2 w-36 divide-y divide-surface2
|
|
rounded-lg border border-surface1 bg-surface0 shadow-lg"
|
|
role="menu"
|
|
x-cloak
|
|
x-transition
|
|
x-show="isActive"
|
|
x-on:click.away="isActive = false"
|
|
x-on:keydown.escape.window="isActive = false"
|
|
>
|
|
<!-- Profile links -->
|
|
<div class="p-2">
|
|
for _, item := range items {
|
|
<a
|
|
href={ templ.SafeURL(item.Href) }
|
|
class="block rounded-lg px-4 py-2 text-md hover:bg-crust"
|
|
role="menuitem"
|
|
>
|
|
{ item.Name }
|
|
</a>
|
|
}
|
|
</div>
|
|
<!-- Logout -->
|
|
<div class="p-2">
|
|
<form hx-post="/logout">
|
|
<button
|
|
type="submit"
|
|
class="flex w-full items-center gap-2 rounded-lg px-4 py-2
|
|
text-md text-red hover:bg-red/25 hover:cursor-pointer"
|
|
role="menuitem"
|
|
@click="isActive=false"
|
|
>
|
|
Logout
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
// Login button (private helper)
|
|
templ loginButton() {
|
|
<button
|
|
class="hidden rounded-lg px-4 py-2 sm:block hover:cursor-pointer
|
|
bg-green hover:bg-green/75 text-mantle transition"
|
|
hx-post="/login"
|
|
hx-swap="none"
|
|
>
|
|
Login
|
|
</button>
|
|
}
|
|
|
|
// Mobile menu toggle (private helper)
|
|
templ mobileMenuButton() {
|
|
<button
|
|
@click="open = !open"
|
|
class="block rounded-lg p-2.5 sm:hidden transition
|
|
bg-surface0 text-subtext0 hover:text-overlay2/75"
|
|
>
|
|
<span class="sr-only">Toggle menu</span>
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="size-5"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
d="M4 6h16M4 12h16M4 18h16"
|
|
></path>
|
|
</svg>
|
|
</button>
|
|
}
|
|
|
|
// Mobile navigation drawer (private helper)
|
|
templ mobileNav(navItems []NavItem, user *db.User) {
|
|
<div
|
|
x-show="open"
|
|
x-transition
|
|
class="absolute w-full bg-mantle sm:hidden z-10"
|
|
>
|
|
<div class="px-4 py-6">
|
|
<ul class="space-y-1">
|
|
for _, item := range navItems {
|
|
<li>
|
|
<a
|
|
href={ templ.SafeURL(item.Href) }
|
|
class="block rounded-lg px-4 py-2 text-lg
|
|
bg-surface0 text-text transition hover:bg-surface2"
|
|
>
|
|
{ item.Name }
|
|
</a>
|
|
</li>
|
|
}
|
|
</ul>
|
|
</div>
|
|
if user == nil {
|
|
<div class="px-4 pb-6">
|
|
<ul class="space-y-1">
|
|
<li class="flex justify-center items-center gap-2">
|
|
<a
|
|
class="w-26 px-4 py-2 rounded-lg bg-green text-mantle
|
|
transition hover:bg-green/75 text-center"
|
|
href="/login"
|
|
>
|
|
Login
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
}
|
|
</div>
|
|
}
|