diff --git a/apps/web/src/components/mobile-menu.tsx b/apps/web/src/components/mobile-menu.tsx new file mode 100644 index 0000000..8d5aec4 --- /dev/null +++ b/apps/web/src/components/mobile-menu.tsx @@ -0,0 +1,56 @@ +"use client"; + +import { IconMenu2, IconX } from "@tabler/icons-react"; +import { useEffect, useRef, useState } from "react"; + +import { Button } from "@/components/ui/button"; + +/** + * Mobile-only header menu (`sm:hidden`). Holds the nav links and auth actions + * that don't fit the bar on a phone, behind a hamburger. Closes on outside + * click, Escape, or any click inside (so navigating a link dismisses it). + */ +export function MobileMenu({ children, label }: { children: React.ReactNode; label: string }) { + const [open, setOpen] = useState(false); + const ref = useRef(null); + + useEffect(() => { + if (!open) return; + function onPointer(e: MouseEvent) { + if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false); + } + function onKey(e: KeyboardEvent) { + if (e.key === "Escape") setOpen(false); + } + document.addEventListener("mousedown", onPointer); + document.addEventListener("keydown", onKey); + return () => { + document.removeEventListener("mousedown", onPointer); + document.removeEventListener("keydown", onKey); + }; + }, [open]); + + return ( +
+ + {open && ( +
setOpen(false)} + className="absolute end-0 z-50 mt-1.5 w-56 rounded-md border border-border bg-popover p-2 text-popover-foreground shadow-md" + > + {children} +
+ )} +
+ ); +} diff --git a/apps/web/src/components/site-header.tsx b/apps/web/src/components/site-header.tsx index 5012c56..dec36d0 100644 --- a/apps/web/src/components/site-header.tsx +++ b/apps/web/src/components/site-header.tsx @@ -2,9 +2,11 @@ import { IconBrandDiscord } from "@tabler/icons-react"; import { LanguageSwitcher } from "@/components/language-switcher"; import { LogoutButton } from "@/components/logout-button"; +import { MobileMenu } from "@/components/mobile-menu"; import { ThemeToggle } from "@/components/theme-toggle"; import { Wordmark } from "@/components/landing/wordmark"; import { Button } from "@/components/ui/button"; +import { logoutAction } from "@/lib/auth-actions"; import { isPrimaryHostname, requestHostname } from "@/lib/custom-domain"; import { resolveHostOAuth } from "@/lib/guild-oauth"; import { appBaseUrl } from "@/lib/url"; @@ -15,6 +17,10 @@ export interface HeaderUser { avatar: string | null; } +/** Shared style for a row inside the mobile menu. */ +const MENU_ROW = + "flex items-center gap-2 rounded-sm px-2.5 py-2 text-sm text-muted-foreground transition-colors hover:bg-accent hover:text-foreground"; + export async function SiteHeader({ user }: { user: HeaderUser | null }) { const t = await getDict(); const locale = await getLocale(); @@ -62,8 +68,10 @@ export async function SiteHeader({ user }: { user: HeaderUser | null }) { + + {/* Desktop auth actions */} {user ? ( -
+
{user.avatar && (
) : ( - )} + + {/* Mobile menu: nav links + auth actions that don't fit the bar */} + + + {t.pricing.nav} + + + {t.stats.nav} + + + MSK Scripts Hub + +
+ {user ? ( + <> + + {t.header.dashboard} + +
+ +
+ + ) : ( + + + {t.header.login} + + )} +
diff --git a/apps/web/src/i18n/dictionaries.ts b/apps/web/src/i18n/dictionaries.ts index 56931c9..4e69d05 100644 --- a/apps/web/src/i18n/dictionaries.ts +++ b/apps/web/src/i18n/dictionaries.ts @@ -1,7 +1,7 @@ export type Locale = "en" | "de" | "hu" | "fr" | "es" | "pt" | "pl"; const en = { - header: { login: "Log in", dashboard: "Dashboard", logout: "Log out", toggleTheme: "Toggle theme", language: "Language" }, + header: { login: "Log in", dashboard: "Dashboard", logout: "Log out", toggleTheme: "Toggle theme", language: "Language", menu: "Menu" }, stats: { nav: "Stats", eyebrow: "Platform stats", @@ -549,7 +549,7 @@ const en = { export type Dictionary = typeof en; const de: Dictionary = { - header: { login: "Anmelden", dashboard: "Dashboard", logout: "Abmelden", toggleTheme: "Theme wechseln", language: "Sprache" }, + header: { login: "Anmelden", dashboard: "Dashboard", logout: "Abmelden", toggleTheme: "Theme wechseln", language: "Sprache", menu: "Menü" }, stats: { nav: "Statistik", eyebrow: "Plattform-Statistik", @@ -1093,7 +1093,7 @@ const de: Dictionary = { }; const hu: Dictionary = { - header: { login: "Bejelentkezés", dashboard: "Vezérlőpult", logout: "Kijelentkezés", toggleTheme: "Téma váltása", language: "Nyelv" }, + header: { login: "Bejelentkezés", dashboard: "Vezérlőpult", logout: "Kijelentkezés", toggleTheme: "Téma váltása", language: "Nyelv", menu: "Menü" }, stats: { nav: "Statisztika", eyebrow: "Platformstatisztika", @@ -1637,7 +1637,7 @@ const hu: Dictionary = { }; const fr: Dictionary = { - header: { login: "Connexion", dashboard: "Tableau de bord", logout: "Déconnexion", toggleTheme: "Changer de thème", language: "Langue" }, + header: { login: "Connexion", dashboard: "Tableau de bord", logout: "Déconnexion", toggleTheme: "Changer de thème", language: "Langue", menu: "Menu" }, stats: { nav: "Statistiques", eyebrow: "Statistiques de la plateforme", @@ -2182,7 +2182,7 @@ const fr: Dictionary = { const es: Dictionary = { - header: { login: "Iniciar sesión", dashboard: "Dashboard", logout: "Cerrar sesión", toggleTheme: "Cambiar tema", language: "Idioma" }, + header: { login: "Iniciar sesión", dashboard: "Dashboard", logout: "Cerrar sesión", toggleTheme: "Cambiar tema", language: "Idioma", menu: "Menú" }, stats: { nav: "Estadísticas", eyebrow: "Estadísticas de la plataforma", @@ -2727,7 +2727,7 @@ const es: Dictionary = { const pt: Dictionary = { - header: { login: "Entrar", dashboard: "Painel", logout: "Sair", toggleTheme: "Alternar tema", language: "Idioma" }, + header: { login: "Entrar", dashboard: "Painel", logout: "Sair", toggleTheme: "Alternar tema", language: "Idioma", menu: "Menu" }, stats: { nav: "Estatísticas", eyebrow: "Estatísticas da plataforma", @@ -3272,7 +3272,7 @@ const pt: Dictionary = { const pl: Dictionary = { - header: { login: "Zaloguj się", dashboard: "Panel", logout: "Wyloguj się", toggleTheme: "Zmień motyw", language: "Język" }, + header: { login: "Zaloguj się", dashboard: "Panel", logout: "Wyloguj się", toggleTheme: "Zmień motyw", language: "Język", menu: "Menu" }, stats: { nav: "Statystyki", eyebrow: "Statystyki platformy",