feat(ui): move admin panel and join-game into profile dropdown

- Replace header admin link + profile modal with a dropdown menu
- Dropdown contains: profile, settings, admin (admins only), join-game form, logout
- Remove join-game card from homepage for logged-in users (now in dropdown)
- AuthModal now only handles guest/login/register flows

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Michess
2026-04-15 13:14:15 +02:00
parent bbf26826c6
commit 2707efe115
3 changed files with 117 additions and 131 deletions

View File

@@ -1,13 +1,20 @@
"use client";
import { IconUser, IconShield } from "@tabler/icons-react";
import { IconUser, IconShield, IconUserCircle, IconSettings2, IconLogout } from "@tabler/icons-react";
import Link from "next/link";
import NotificationPanel from "./NotificationPanel";
import ThemeToggle from "./ThemeToggle";
import { useSession } from "@/context/session";
import { logout } from "@/lib/auth";
import JoinGame from "./home/JoinGame";
export default function Header() {
const { user } = useSession();
const { user, setUser } = useSession();
async function handleLogout() {
await logout();
setUser(null);
}
return (
<header className="navbar border-base-300 dark:border-neutral mx-1 w-auto justify-center border-b-2 md:mx-16 lg:mx-40">
@@ -30,12 +37,6 @@ export default function Header() {
</Link>
</div>
<div className="flex-none gap-1">
{user?.role === "admin" && (
<Link href="/admin" className="btn btn-ghost btn-sm gap-1 normal-case" title="Admin-Panel">
<IconShield size={16} />
Admin
</Link>
)}
{user?.id && typeof user.id === "number" && <NotificationPanel />}
{user?.elo != null && typeof user.id === "number" && (
<Link href={`/user/${user.name}`} className="btn btn-ghost btn-sm font-mono tabular-nums" title="Deine ELO-Wertung">
@@ -43,11 +44,57 @@ export default function Header() {
</Link>
)}
<ThemeToggle />
<label tabIndex={0} htmlFor="auth-modal" className="btn btn-ghost btn-circle avatar">
<div className="w-10 rounded-full">
<IconUser className="m-auto block h-full" />
{user?.id && typeof user.id === "number" ? (
<div className="dropdown dropdown-end">
<label tabIndex={0} className="btn btn-ghost btn-circle avatar">
<div className="w-10 rounded-full">
<IconUser className="m-auto block h-full" />
</div>
</label>
<div tabIndex={0} className="dropdown-content z-50 mt-3 w-80 rounded-box bg-base-100 p-3 shadow-lg">
<div className="px-2 pb-2 font-semibold">{user.name}</div>
<ul className="menu menu-sm p-0">
<li>
<Link href={`/user/${user.name}`}>
<IconUserCircle size={16} /> Profil
</Link>
</li>
<li>
<Link href="/settings">
<IconSettings2 size={16} /> Einstellungen
</Link>
</li>
{user.role === "admin" && (
<li>
<Link href="/admin">
<IconShield size={16} /> Admin-Panel
</Link>
</li>
)}
</ul>
<div className="divider my-2" />
<div className="px-2 pb-1 text-xs font-semibold uppercase opacity-50">Spiel beitreten</div>
<div className="px-2 pb-2">
<JoinGame />
</div>
<div className="divider my-2" />
<ul className="menu menu-sm p-0">
<li>
<button onClick={handleLogout} className="text-error">
<IconLogout size={16} /> Abmelden
</button>
</li>
</ul>
</div>
</div>
</label>
) : (
<label tabIndex={0} htmlFor="auth-modal" className="btn btn-ghost btn-circle avatar">
<div className="w-10 rounded-full">
<IconUser className="m-auto block h-full" />
</div>
</label>
)}
</div>
</header>
);

View File

@@ -1,12 +1,10 @@
"use client";
import { SessionContext } from "@/context/session";
import { login, logout, register, setGuestSession } from "@/lib/auth";
import Link from "next/link";
import { login, register, setGuestSession } from "@/lib/auth";
import type { FormEvent } from "react";
import { useContext, useEffect, useRef, useState } from "react";
import { IconSettings2, IconUserCircle } from "@tabler/icons-react";
import Guest from "./Guest";
import Login from "./Login";
import Register from "./Register";
@@ -18,15 +16,6 @@ export default function AuthModal() {
const [buttonLoading, setButtonLoading] = useState(false);
const modalToggleRef = useRef<HTMLInputElement>(null);
async function clickLogout() {
if (serverMessage) {
setServerMessage(null);
}
setActiveTab("login");
await logout();
session?.setUser(null);
}
async function submitAuth(e: FormEvent<HTMLFormElement>) {
e.preventDefault();
@@ -105,102 +94,65 @@ export default function AuthModal() {
className={"modal" + (session?.user === null ? " modal-open" : "")}
>
<label className="modal-box flex max-w-sm flex-col gap-4 pt-2">
{session?.user?.id && typeof session.user.id === "number" ? (
<div className="flex flex-col gap-2 pt-2">
<div className="flex w-full justify-between">
<div>
Logged in as <b>{session.user.name}</b>
</div>
<a className="link" onClick={clickLogout}>
Logout
</a>
</div>
<div className="flex w-full flex-col">
<Link
className="btn btn-ghost gap-1 normal-case"
href={`/user/${session.user.name}`}
onClick={() => ((modalToggleRef.current as HTMLInputElement).checked = false)}
>
<IconUserCircle /> View profile
</Link>
<Link
className="btn btn-ghost gap-1 normal-case"
href="/settings"
onClick={() => ((modalToggleRef.current as HTMLInputElement).checked = false)}
>
<IconSettings2 /> Account settings
</Link>
</div>
<div className="tabs flex-nowap w-full">
<span
onClick={() => {
setActiveTab("guest");
}}
className={
"tab tab-bordered tab-border-2 flex-grow rounded-tl-lg" +
(activeTab === "guest"
? " tab-active text-base-content"
: " text-base-content border-opacity-10 hover:border-opacity-30")
}
>
Guest
</span>
<span
onClick={() => setActiveTab("login")}
className={
"tab tab-bordered tab-border-2 flex-grow rounded-tl-lg" +
(activeTab === "login"
? " tab-active"
: " text-base-content border-opacity-10 hover:border-opacity-30")
}
>
Login
</span>
<span
onClick={() => setActiveTab("register")}
className={
"tab tab-bordered tab-border-2 flex-grow rounded-tl-lg" +
(activeTab === "register"
? " tab-active"
: " text-base-content border-opacity-10 hover:border-opacity-30")
}
>
Register
</span>
</div>
<div className="modal-action">
<label htmlFor="auth-modal" className="btn">
<form className="flex flex-col px-2" onSubmit={submitAuth}>
{activeTab === "guest" && (
<Guest currentName={session?.user?.name || "unknown user"} />
)}
{activeTab === "login" && <Login />}
{activeTab === "register" && <Register />}
{serverMessage && <div className="text-error mt-2">{serverMessage}</div>}
<div className="modal-action items-center">
{session?.user !== null && (
<label htmlFor="auth-modal" className="btn btn-ghost">
Close
</label>
</div>
)}
<button className={"btn" + (buttonLoading ? " loading" : "")} type="submit">
{activeTab === "guest" && "Confirm"}
{activeTab === "login" && "Login"}
{activeTab === "register" && "Register"}
</button>
</div>
) : (
<>
<div className="tabs flex-nowap w-full">
<span
onClick={() => {
setActiveTab("guest");
}}
className={
"tab tab-bordered tab-border-2 flex-grow rounded-tl-lg" +
(activeTab === "guest"
? " tab-active text-base-content"
: " text-base-content border-opacity-10 hover:border-opacity-30")
}
>
Guest
</span>
<span
onClick={() => setActiveTab("login")}
className={
"tab tab-bordered tab-border-2 flex-grow rounded-tl-lg" +
(activeTab === "login"
? " tab-active"
: " text-base-content border-opacity-10 hover:border-opacity-30")
}
>
Login
</span>
<span
onClick={() => setActiveTab("register")}
className={
"tab tab-bordered tab-border-2 flex-grow rounded-tl-lg" +
(activeTab === "register"
? " tab-active"
: " text-base-content border-opacity-10 hover:border-opacity-30")
}
>
Register
</span>
</div>
<form className="flex flex-col px-2" onSubmit={submitAuth}>
{activeTab === "guest" && (
<Guest currentName={session?.user?.name || "unknown user"} />
)}
{activeTab === "login" && <Login />}
{activeTab === "register" && <Register />}
{serverMessage && <div className="text-error mt-2">{serverMessage}</div>}
<div className="modal-action items-center">
{session?.user !== null && (
<label htmlFor="auth-modal" className="btn btn-ghost">
Close
</label>
)}
<button className={"btn" + (buttonLoading ? " loading" : "")} type="submit">
{activeTab === "guest" && "Confirm"}
{activeTab === "login" && "Login"}
{activeTab === "register" && "Register"}
</button>
</div>
</form>
</>
)}
</form>
</label>
</label>
</>