Files
sexy/packages/frontend/src/lib/components/header/header.svelte

361 lines
16 KiB
Svelte
Raw Normal View History

2025-10-25 22:04:41 +02:00
<script lang="ts">
import { _ } from "svelte-i18n";
import { page } from "$app/state";
import { Button } from "$lib/components/ui/button";
import type { AuthStatus } from "$lib/types";
import { logout } from "$lib/services";
import { goto } from "$app/navigation";
import { getAssetUrl } from "$lib/api";
import { Avatar, AvatarFallback, AvatarImage } from "$lib/components/ui/avatar";
import { getUserInitials } from "$lib/utils";
import Separator from "../ui/separator/separator.svelte";
import BurgerMenuButton from "../burger-menu-button/burger-menu-button.svelte";
import Logo from "../logo/logo.svelte";
2025-10-25 22:04:41 +02:00
interface Props {
authStatus: AuthStatus;
}
2025-10-25 22:04:41 +02:00
let { authStatus }: Props = $props();
2025-10-25 22:04:41 +02:00
let isMobileMenuOpen = $state(false);
2025-10-25 22:04:41 +02:00
const navLinks = [
{ name: $_("header.home"), href: "/" },
{ name: $_("header.models"), href: "/models" },
{ name: $_("header.videos"), href: "/videos" },
{ name: $_("header.magazine"), href: "/magazine" },
{ name: $_("header.about"), href: "/about" },
];
2025-10-25 22:04:41 +02:00
async function handleLogout() {
closeMenu();
await logout();
goto("/login", { invalidateAll: true });
}
2025-10-25 22:04:41 +02:00
function closeMenu() {
isMobileMenuOpen = false;
}
2025-10-25 22:04:41 +02:00
function isActiveLink(link: { name: string; href: string }) {
return (
(page.url.pathname === "/" && link === navLinks[0]) ||
(page.url.pathname.startsWith(link.href) && link !== navLinks[0])
);
}
2025-10-25 22:04:41 +02:00
</script>
<header
2026-03-09 08:56:49 +01:00
class="sticky top-0 z-50 w-full backdrop-blur-xl shadow-[0_4px_24px_-8px_color-mix(in_oklab,var(--color-primary)_12%,transparent)] bg-card/50"
2025-10-25 22:04:41 +02:00
>
<div class="container mx-auto px-4">
<div class="flex items-center justify-evenly h-16">
<!-- Logo -->
<a
href="/"
class="flex w-full items-center gap-3 hover:scale-105 transition-all duration-300"
>
<Logo />
2025-10-25 22:04:41 +02:00
</a>
<!-- Desktop Navigation -->
<nav class="hidden w-full lg:flex items-center justify-center gap-8">
{#each navLinks as link (link.href)}
2025-10-25 22:04:41 +02:00
<a
href={link.href}
class={`text-sm hover:text-foreground transition-colors duration-200 font-medium relative group ${
isActiveLink(link) ? "text-foreground" : "text-foreground/85"
2025-10-25 22:04:41 +02:00
}`}
>
{link.name}
<span
class={`absolute -bottom-1 left-0 w-0 h-0.5 bg-gradient-to-r from-primary to-accent transition-all duration-300 ${isActiveLink(link) ? "w-full" : "group-hover:w-full"}`}
2025-10-25 22:04:41 +02:00
></span>
</a>
{/each}
</nav>
<!-- Desktop Auth Actions -->
2025-10-25 22:04:41 +02:00
{#if authStatus.authenticated}
<div class="w-full hidden lg:flex items-center justify-end">
2025-10-25 22:04:41 +02:00
<div class="flex items-center gap-2 rounded-full bg-muted/30 p-1">
<Button
variant="link"
size="icon"
class={`h-9 w-9 rounded-full p-0 relative text-foreground/80 group ${isActiveLink({ href: "/me" }) ? "text-foreground" : "hover:text-foreground"}`}
2025-10-25 22:04:41 +02:00
href="/me"
title={$_("header.dashboard")}
2025-10-25 22:04:41 +02:00
>
<span class="icon-[ri--dashboard-2-line] h-4 w-4"></span>
<span
class={`absolute -bottom-1 left-0 w-0 h-0.5 bg-gradient-to-r from-primary to-accent transition-all duration-300 ${isActiveLink({ href: "/me" }) ? "w-full" : "group-hover:w-full"}`}
2025-10-25 22:04:41 +02:00
></span>
<span class="sr-only">{$_("header.dashboard")}</span>
2025-10-25 22:04:41 +02:00
</Button>
<Button
variant="link"
size="icon"
class={`h-9 w-9 rounded-full p-0 relative text-foreground/80 group ${isActiveLink({ href: "/play" }) ? "text-foreground" : "hover:text-foreground"}`}
2025-10-25 22:04:41 +02:00
href="/play"
title={$_("header.play")}
2025-10-25 22:04:41 +02:00
>
<span class="icon-[ri--rocket-line] h-4 w-4"></span>
<span
class={`absolute -bottom-1 left-0 w-0 h-0.5 bg-gradient-to-r from-primary to-accent transition-all duration-300 ${isActiveLink({ href: "/play" }) ? "w-full" : "group-hover:w-full"}`}
2025-10-25 22:04:41 +02:00
></span>
<span class="sr-only">{$_("header.play")}</span>
2025-10-25 22:04:41 +02:00
</Button>
{#if authStatus.user?.is_admin}
<Button
variant="link"
size="icon"
class={`h-9 w-9 rounded-full p-0 relative text-foreground/80 group ${isActiveLink({ href: "/admin" }) ? "text-foreground" : "hover:text-foreground"}`}
href="/admin/users"
title="Admin"
>
<span class="icon-[ri--settings-3-line] h-4 w-4"></span>
<span
class={`absolute -bottom-1 left-0 w-0 h-0.5 bg-gradient-to-r from-primary to-accent transition-all duration-300 ${isActiveLink({ href: "/admin" }) ? "w-full" : "group-hover:w-full"}`}
></span>
<span class="sr-only">Admin</span>
</Button>
{/if}
<Separator orientation="vertical" class="mx-1 h-6 bg-border/50" />
2025-10-25 22:04:41 +02:00
<a href="/me" class="flex items-center gap-2 px-1 hover:opacity-80 transition-opacity">
<Avatar class="h-7 w-7 ring-2 ring-primary/20">
<AvatarImage
src={getAssetUrl(authStatus.user!.avatar, "mini")!}
alt={authStatus.user!.artist_name || authStatus.user!.email}
/>
<AvatarFallback
class="bg-gradient-to-br from-primary to-accent text-primary-foreground text-xs font-semibold"
>
{getUserInitials(authStatus.user!.artist_name || authStatus.user!.email)}
</AvatarFallback>
</Avatar>
<span class="text-sm font-medium text-foreground/90 max-w-24 truncate">
{authStatus.user!.artist_name || authStatus.user!.email.split("@")[0]}
</span>
</a>
<Button
variant="link"
size="icon"
class="h-9 w-9 rounded-full p-0 relative text-foreground/80 group hover:text-destructive"
onclick={handleLogout}
title={$_("header.logout")}
>
<span class="icon-[ri--logout-circle-r-line] h-4 w-4"></span>
</Button>
2025-10-25 22:04:41 +02:00
</div>
</div>
{:else}
<div class="hidden lg:flex w-full items-center justify-end gap-4">
<Button variant="outline" class="font-medium" href="/login">{$_("header.login")}</Button>
2025-10-25 22:04:41 +02:00
<Button
href="/signup"
class="bg-gradient-to-r from-primary to-accent hover:from-primary/90 hover:to-accent/90 font-medium"
>{$_("header.signup")}</Button
2025-10-25 22:04:41 +02:00
>
</div>
{/if}
<!-- Burger button — mobile/tablet only -->
<div class="lg:hidden ml-auto">
<BurgerMenuButton
label={$_("header.navigation")}
bind:isMobileMenuOpen
onclick={() => (isMobileMenuOpen = !isMobileMenuOpen)}
/>
</div>
2025-10-25 22:04:41 +02:00
</div>
</div>
</header>
<!-- Backdrop -->
<div
role="presentation"
class={`fixed inset-0 z-40 bg-black/60 backdrop-blur-sm transition-opacity duration-300 lg:hidden ${isMobileMenuOpen ? "opacity-100 pointer-events-auto" : "opacity-0 pointer-events-none"}`}
onclick={closeMenu}
></div>
<!-- Flyout panel -->
<div
class={`fixed inset-y-0 left-0 z-50 w-80 max-w-[85vw] bg-card/95 backdrop-blur-xl shadow-2xl shadow-primary/20 border-r border-border/30 transform transition-transform duration-300 ease-in-out lg:hidden overflow-y-auto flex flex-col ${isMobileMenuOpen ? "translate-x-0" : "-translate-x-full"}`}
inert={!isMobileMenuOpen || undefined}
>
<!-- Panel header -->
<div class="flex items-center px-5 h-16 shrink-0 border-b border-border/30">
<Logo />
</div>
<div class="flex-1 py-6 px-5 space-y-6">
<!-- User card -->
{#if authStatus.authenticated}
<div class="flex items-center gap-3 rounded-xl border border-border/40 bg-card/50 px-4 py-3">
<Avatar class="h-10 w-10 ring-2 ring-primary/20 shrink-0">
<AvatarImage
src={getAssetUrl(authStatus.user!.avatar, "mini")!}
alt={authStatus.user!.artist_name || authStatus.user!.email}
/>
<AvatarFallback
class="bg-gradient-to-br from-primary to-accent text-primary-foreground text-sm font-semibold"
>
{getUserInitials(authStatus.user!.artist_name || authStatus.user!.email)}
</AvatarFallback>
</Avatar>
<div class="flex flex-col min-w-0 flex-1">
<span class="text-sm font-semibold text-foreground truncate">
{authStatus.user!.artist_name || authStatus.user!.email.split("@")[0]}
</span>
<span class="text-xs text-muted-foreground truncate">{authStatus.user!.email}</span>
</div>
<Button
variant="ghost"
size="icon"
class="h-8 w-8 rounded-full text-muted-foreground hover:text-destructive hover:bg-destructive/10 shrink-0"
onclick={handleLogout}
title={$_("header.logout")}
>
<span class="icon-[ri--logout-circle-r-line] h-4 w-4"></span>
</Button>
</div>
{/if}
2025-10-25 22:04:41 +02:00
<!-- Navigation -->
<div class="space-y-2">
<h3 class="px-1 text-xs font-semibold text-muted-foreground uppercase tracking-wider">
{$_("header.navigation")}
</h3>
<div class="grid gap-1.5">
{#each navLinks as link (link.href)}
<a
href={link.href}
class={`flex items-center justify-between rounded-xl border px-4 py-3 transition-all duration-200 hover:border-primary/30 hover:bg-primary/5 ${
isActiveLink(link)
? "border-primary/40 bg-primary/8 text-foreground"
: "border-border/40 bg-card/50 text-foreground/85"
}`}
onclick={closeMenu}
>
<span class="font-medium text-sm">{link.name}</span>
<span class="icon-[ri--arrow-right-s-line] h-4 w-4 text-muted-foreground"></span>
</a>
{/each}
</div>
</div>
2025-10-25 22:04:41 +02:00
<!-- Account -->
<div class="space-y-2">
<h3 class="px-1 text-xs font-semibold text-muted-foreground uppercase tracking-wider">
{$_("header.account")}
</h3>
<div class="grid gap-1.5">
{#if authStatus.authenticated}
<a
class={`flex items-center gap-3 rounded-xl border px-4 py-3 transition-all duration-200 group hover:border-primary/30 hover:bg-primary/5 ${isActiveLink({ href: "/me" }) ? "border-primary/40 bg-primary/8" : "border-border/40 bg-card/50"}`}
href="/me"
onclick={closeMenu}
>
<div
class="flex h-8 w-8 items-center justify-center rounded-lg bg-muted/60 group-hover:bg-primary/10 transition-colors"
>
<span
class="icon-[ri--dashboard-2-line] h-4 w-4 text-muted-foreground group-hover:text-primary transition-colors"
></span>
</div>
<div class="flex flex-1 flex-col gap-0.5">
<span class="text-sm font-medium text-foreground">{$_("header.dashboard")}</span>
<span class="text-xs text-muted-foreground">{$_("header.dashboard_hint")}</span>
</div>
<span class="icon-[ri--arrow-right-s-line] h-4 w-4 text-muted-foreground"></span>
</a>
2025-10-25 22:04:41 +02:00
<a
class={`flex items-center gap-3 rounded-xl border px-4 py-3 transition-all duration-200 group hover:border-primary/30 hover:bg-primary/5 ${isActiveLink({ href: "/play" }) ? "border-primary/40 bg-primary/8" : "border-border/40 bg-card/50"}`}
href="/play"
onclick={closeMenu}
>
<div
class="flex h-8 w-8 items-center justify-center rounded-lg bg-muted/60 group-hover:bg-primary/10 transition-colors"
>
<span
class="icon-[ri--rocket-line] h-4 w-4 text-muted-foreground group-hover:text-primary transition-colors"
></span>
2025-10-25 22:04:41 +02:00
</div>
<div class="flex flex-1 flex-col gap-0.5">
<span class="text-sm font-medium text-foreground">{$_("header.play")}</span>
<span class="text-xs text-muted-foreground">{$_("header.play_hint")}</span>
</div>
<span class="icon-[ri--arrow-right-s-line] h-4 w-4 text-muted-foreground"></span>
</a>
{#if authStatus.user?.is_admin}
<a
class={`flex items-center gap-3 rounded-xl border px-4 py-3 transition-all duration-200 group hover:border-primary/30 hover:bg-primary/5 ${isActiveLink({ href: "/admin" }) ? "border-primary/40 bg-primary/8" : "border-border/40 bg-card/50"}`}
href="/admin/users"
onclick={closeMenu}
>
<div
class="flex h-8 w-8 items-center justify-center rounded-lg bg-muted/60 group-hover:bg-primary/10 transition-colors"
>
<span
class="icon-[ri--settings-3-line] h-4 w-4 text-muted-foreground group-hover:text-primary transition-colors"
></span>
</div>
<div class="flex flex-1 flex-col gap-0.5">
<span class="text-sm font-medium text-foreground">Admin</span>
<span class="text-xs text-muted-foreground">Manage content</span>
</div>
<span class="icon-[ri--arrow-right-s-line] h-4 w-4 text-muted-foreground"></span>
</a>
{/if}
{:else}
<a
class={`flex items-center gap-3 rounded-xl border px-4 py-3 transition-all duration-200 group hover:border-primary/30 hover:bg-primary/5 ${isActiveLink({ href: "/login" }) ? "border-primary/40 bg-primary/8" : "border-border/40 bg-card/50"}`}
href="/login"
onclick={closeMenu}
>
<div
class="flex h-8 w-8 items-center justify-center rounded-lg bg-muted/60 group-hover:bg-primary/10 transition-colors"
>
<span
class="icon-[ri--login-circle-line] h-4 w-4 text-muted-foreground group-hover:text-primary transition-colors"
></span>
</div>
<div class="flex flex-1 flex-col gap-0.5">
<span class="text-sm font-medium text-foreground">{$_("header.login")}</span>
<span class="text-xs text-muted-foreground">{$_("header.login_hint")}</span>
</div>
<span class="icon-[ri--arrow-right-s-line] h-4 w-4 text-muted-foreground"></span>
</a>
2025-10-25 22:04:41 +02:00
<a
class={`flex items-center gap-3 rounded-xl border px-4 py-3 transition-all duration-200 group hover:border-primary/30 hover:bg-primary/5 ${isActiveLink({ href: "/signup" }) ? "border-primary/40 bg-primary/8" : "border-border/40 bg-card/50"}`}
href="/signup"
onclick={closeMenu}
>
<div
class="flex h-8 w-8 items-center justify-center rounded-lg bg-muted/60 group-hover:bg-accent/10 transition-colors"
2025-10-25 22:04:41 +02:00
>
<span
class="icon-[ri--heart-add-2-line] h-4 w-4 text-muted-foreground group-hover:text-accent transition-colors"
></span>
</div>
<div class="flex flex-1 flex-col gap-0.5">
<span class="text-sm font-medium text-foreground">{$_("header.signup")}</span>
<span class="text-xs text-muted-foreground">{$_("header.signup_hint")}</span>
</div>
<span class="icon-[ri--arrow-right-s-line] h-4 w-4 text-muted-foreground"></span>
</a>
{/if}
2025-10-25 22:04:41 +02:00
</div>
</div>
2025-10-25 22:04:41 +02:00
</div>
</div>