Files
codey.lol/src/layouts/Nav.astro

210 lines
8.8 KiB
Plaintext
Raw Normal View History

2025-06-18 07:46:59 -04:00
---
import { metaData, API_URL } from "../config";
2025-06-18 07:46:59 -04:00
import { Icon } from "astro-icon/components";
import { requireAuthHook } from "@/hooks/requireAuthHook";
import { padlockIconSvg, userIconSvg, externalLinkIconSvg } from "@/utils/navAssets";
import "@/assets/styles/nav.css";
2025-06-18 07:46:59 -04:00
const user = await requireAuthHook(Astro);
const hostHeader = Astro.request?.headers?.get('host') || '';
const host = hostHeader.split(':')[0];
import { getSubsiteByHost } from '../utils/subsites.js';
import { getSubsiteByPath } from '../utils/subsites.js';
const isReq = getSubsiteByHost(host)?.short === 'req' || getSubsiteByPath(Astro.url.pathname)?.short === 'req';
// Nav is the standard site navigation — whitelabel logic belongs in SubNav
const isLoggedIn = Boolean(user);
const userDisplayName = user?.user ?? null;
const navItems = [
{ label: "Home", href: "/" },
{ label: "Radio", href: "/radio" },
{ label: "Memes", href: "/memes" },
{ label: "TRip", href: "/TRip", auth: true, icon: "pirate" },
{ label: "Lighting", href: "/lighting", auth: true },
{ label: "Discord Logs", href: "/discord-logs", auth: true },
{ label: "Status", href: "https://status.boatson.boats", icon: "external" },
{ label: "Git", href: "https://kode.boatson.boats", icon: "external" },
{ label: "Login", href: "/login", guestOnly: true },
...(isLoggedIn ? [{ label: "Logout", href: "#logout", onclick: "handleLogout()" }] : []),
];
const visibleNavItems = navItems.filter((item) => {
if (item.auth && !isLoggedIn) {
return false;
}
if (item.guestOnly && isLoggedIn) {
return false;
}
return true;
});
const currentPath = Astro.url.pathname;
2025-06-18 07:46:59 -04:00
---
<script src="/scripts/nav-controls.js" defer data-api-url={API_URL}></script>
<nav class="w-full px-4 sm:px-6 py-4 bg-transparent sticky top-0 z-50 backdrop-blur-sm bg-white/80 dark:bg-[#121212]/80 border-b border-neutral-200/50 dark:border-neutral-800/50">
<div class="max-w-7xl mx-auto">
<div class="nav-bar-row flex items-center gap-4 justify-between">
<!-- Logo/Brand -->
<a
href="/"
class="text-xl sm:text-2xl font-semibold header-text whitespace-nowrap hover:opacity-80 transition-opacity">
{metaData.title}
</a>
<!-- Desktop Navigation -->
<div class="desktop-nav flex items-center">
<ul class="desktop-nav-list">
{visibleNavItems.map((item) => {
const isExternal = item.href?.startsWith("http");
const isAuthedPath = item.auth ?? false;
const normalize = (url) => (url || '/').replace(/\/+$/, '') || '/';
const normalizedCurrent = normalize(currentPath);
const normalizedHref = normalize(item.href);
const isActive = !isExternal && (
normalizedHref === '/'
? normalizedCurrent === '/'
: normalizedCurrent === normalizedHref || normalizedCurrent.startsWith(normalizedHref + '/')
);
return (
<li>
<a
href={item.href}
class={isActive
? "flex items-center gap-0 px-2.5 py-1.5 rounded-md text-xs font-medium transition-all duration-200 text-white"
: "flex items-center gap-0 px-2.5 py-1.5 rounded-md text-xs font-medium transition-all duration-200 text-neutral-700 dark:text-neutral-300 hover:bg-neutral-100 dark:hover:bg-neutral-800"
}
style={isActive ? `background: #111827` : undefined}
target={isExternal ? "_blank" : undefined}
rel={(isExternal || isAuthedPath) ? "external" : undefined}
onclick={item.onclick}
>
{item.label}
{item.icon === "external" && (
<span class="inline-flex ml-0.5" aria-hidden="true" set:html={externalLinkIconSvg}></span>
)}
{item.icon === "padlock" && (
<span class="inline-flex" aria-hidden="true" set:html={padlockIconSvg}></span>
)}
{item.icon === "pirate" && (
<span class="inline-flex ml-1" role="img" aria-label="Pirate flag">🏴‍☠️</span>
)}
</a>
</li>
);
})}
</ul>
{isLoggedIn && userDisplayName && (
<div class="nav-user-inline" title={`Logged in as ${userDisplayName}`}>
<span class="nav-user-inline__icon" aria-hidden="true" set:html={userIconSvg}></span>
<span class="nav-user-inline__name">{userDisplayName}</span>
</div>
)}
<!-- Theme Toggle Desktop -->
<button
aria-label="Toggle theme"
type="button"
class="flex items-center justify-center w-8 h-8 rounded-md hover:bg-neutral-100 dark:hover:bg-neutral-800 transition-colors"
onclick="toggleTheme()"
>
<Icon
name="fa6-solid:circle-half-stroke"
2025-11-22 21:41:41 -05:00
class="h-4 w-4 text-[#1c1c1c] dark:text-[#D4D4D4]"
/>
</button>
</div>
2025-07-17 10:38:58 -04:00
<!-- Mobile Menu Button -->
<div class="mobile-nav flex items-center gap-2">
<!-- Theme Toggle Mobile (visible) -->
<button
aria-label="Toggle theme"
type="button"
class="flex items-center justify-center w-9 h-9 rounded-lg hover:bg-neutral-100 dark:hover:bg-neutral-800 transition-colors"
onclick="toggleTheme()"
>
<Icon
name="fa6-solid:circle-half-stroke"
class="h-5 w-5 text-[#1c1c1c] dark:text-[#D4D4D4]"
/>
</button>
<button
id="mobile-menu-btn"
aria-label="Toggle menu"
type="button"
class="flex items-center justify-center w-9 h-9 rounded-lg hover:bg-neutral-100 dark:hover:bg-neutral-800 transition-colors"
>
<svg id="menu-icon" class="w-6 h-6 text-neutral-700 dark:text-neutral-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
</svg>
<svg id="close-icon" class="w-6 h-6 text-neutral-700 dark:text-neutral-300" style="display: none;" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</button>
</div>
</div>
<!-- Mobile Navigation Menu -->
<div
id="mobile-menu"
class="mobile-menu-dropdown md:hidden"
>
{isLoggedIn && userDisplayName && (
<div class="nav-user-inline nav-user-inline--mobile" title={`Logged in as ${userDisplayName}`}>
<span class="nav-user-inline__icon" aria-hidden="true" set:html={userIconSvg}></span>
<span class="nav-user-inline__name">{userDisplayName}</span>
</div>
)}
<ul class="flex flex-col gap-1 py-4">
{visibleNavItems.map((item) => {
const isExternal = item.href?.startsWith("http");
const isAuthedPath = item.auth ?? false;
const normalize = (url) => (url || '/').replace(/\/+$/, '') || '/';
const normalizedCurrent = normalize(currentPath);
const normalizedHref = normalize(item.href);
const isActive = !isExternal && (
normalizedHref === '/'
? normalizedCurrent === '/'
: normalizedCurrent === normalizedHref || normalizedCurrent.startsWith(normalizedHref + '/')
);
return (
<li>
<a
href={item.href}
class={isActive
? "flex items-center gap-0 px-4 py-3 rounded-lg text-base font-medium transition-all duration-200 text-white"
: "flex items-center gap-0 px-4 py-3 rounded-lg text-base font-medium transition-all duration-200 text-neutral-700 dark:text-neutral-300 hover:bg-neutral-100 dark:hover:bg-neutral-800"
}
style={isActive ? `background: #111827` : undefined}
target={isExternal ? "_blank" : undefined}
rel={(isExternal || isAuthedPath) ? "external" : undefined}
onclick={item.onclick}
>
<span>{item.label}</span>
{item.icon === "external" && (
<span class="inline-flex ml-0.5" aria-hidden="true" set:html={externalLinkIconSvg}></span>
)}
{item.icon === "padlock" && (
<span class="inline-flex" aria-hidden="true" set:html={padlockIconSvg}></span>
)}
{item.icon === "pirate" && (
<span class="inline-flex ml-1" role="img" aria-label="Pirate flag">🏴‍☠️</span>
)}
</a>
</li>
);
})}
</ul>
</div>
2025-06-18 07:46:59 -04:00
</div>
</nav>