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

@@ -82,21 +82,8 @@ export default function Home() {
{/* Laufende Partien Live-Kacheln */} {/* Laufende Partien Live-Kacheln */}
<LiveGames /> <LiveGames />
{/* Öffentliche Spiele + Beitreten */} {/* Öffentliche Spiele */}
<div className={`grid gap-6 grid-cols-1 ${isLoggedIn ? "lg:grid-cols-3" : ""}`}> <PublicGames />
<div className={isLoggedIn ? "lg:col-span-2" : ""}>
<PublicGames />
</div>
{isLoggedIn && (
<div className="card bg-base-200 shadow h-fit">
<div className="card-body">
<h2 className="card-title text-lg">🔗 Einladung annehmen</h2>
<p className="text-sm opacity-70 mb-2">Link oder Code eingeben</p>
<JoinGame />
</div>
</div>
)}
</div>
</div> </div>
); );

View File

@@ -1,13 +1,20 @@
"use client"; "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 Link from "next/link";
import NotificationPanel from "./NotificationPanel"; import NotificationPanel from "./NotificationPanel";
import ThemeToggle from "./ThemeToggle"; import ThemeToggle from "./ThemeToggle";
import { useSession } from "@/context/session"; import { useSession } from "@/context/session";
import { logout } from "@/lib/auth";
import JoinGame from "./home/JoinGame";
export default function Header() { export default function Header() {
const { user } = useSession(); const { user, setUser } = useSession();
async function handleLogout() {
await logout();
setUser(null);
}
return ( 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"> <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> </Link>
</div> </div>
<div className="flex-none gap-1"> <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?.id && typeof user.id === "number" && <NotificationPanel />}
{user?.elo != null && typeof user.id === "number" && ( {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"> <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> </Link>
)} )}
<ThemeToggle /> <ThemeToggle />
<label tabIndex={0} htmlFor="auth-modal" className="btn btn-ghost btn-circle avatar">
<div className="w-10 rounded-full"> {user?.id && typeof user.id === "number" ? (
<IconUser className="m-auto block h-full" /> <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> </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> </div>
</header> </header>
); );

View File

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