Built via two more parallel agent workflows (read schema -> return files), integrated + verified. Admin: /admin/emulator (emulator_settings + emulator_texts key/value editor), /admin/badges (give-badge via RCON), /admin/rare-values (CRUD), /admin/housekeeping (CRUD), /admin/email-templates (CRUD), /admin/photos (moderation). Public: /rares (+[category]), /leaderboard (credits/diamonds/duckets), /guilds (+[id] members), /redeem (voucher -> sendCurrency). Expanded: /u/[username] now shows badges + photos + guestbook (post form); /news/[slug] now shows reactions + comments (comment form). Reactions tallied in JS (Prisma groupBy typing avoided). Self-hosted Nunito via next/font/google wired to --font-nunito (the atom theme font, no runtime external fetch). Header + admin nav extended. Verified: tsc exit 0, vitest 48/48, next build exit 0.
116 lines
3.3 KiB
TypeScript
116 lines
3.3 KiB
TypeScript
import Link from "next/link";
|
|
import { auth } from "@/lib/auth";
|
|
import { prisma } from "@/lib/prisma";
|
|
import { siteSettings } from "@/lib/services/site-settings";
|
|
|
|
export async function SiteHeader() {
|
|
const [hotelName, session] = await Promise.all([
|
|
siteSettings.get("hotel_name", "Atom"),
|
|
auth(),
|
|
]);
|
|
|
|
let credits = 0;
|
|
let duckets = 0;
|
|
let diamonds = 0;
|
|
if (session?.user?.id) {
|
|
try {
|
|
const id = Number(session.user.id);
|
|
const [user, currencies] = await Promise.all([
|
|
prisma.user.findUnique({ where: { id }, select: { credits: true } }),
|
|
prisma.usersCurrency.findMany({ where: { userId: id } }),
|
|
]);
|
|
credits = user?.credits ?? 0;
|
|
for (const c of currencies) {
|
|
if (c.type === 0) duckets = c.amount;
|
|
if (c.type === 5) diamonds = c.amount;
|
|
}
|
|
} catch {
|
|
// DB unavailable — render the header without live balances.
|
|
}
|
|
}
|
|
|
|
const isStaff = (session?.user?.rank ?? 0) >= 7;
|
|
|
|
return (
|
|
<header className="site-header">
|
|
<div className="bar">
|
|
<Link href="/" className="brand">
|
|
<span className="dot" />
|
|
{hotelName}
|
|
</Link>
|
|
<nav className="nav">
|
|
<Link className="nav-item" href="/">
|
|
Home
|
|
</Link>
|
|
<Link className="nav-item" href="/news">
|
|
News
|
|
</Link>
|
|
<Link className="nav-item" href="/shop">
|
|
Shop
|
|
</Link>
|
|
<Link className="nav-item" href="/community">
|
|
Community
|
|
</Link>
|
|
<Link className="nav-item" href="/photos">
|
|
Photos
|
|
</Link>
|
|
<Link className="nav-item" href="/leaderboard">
|
|
Leaderboard
|
|
</Link>
|
|
<Link className="nav-item" href="/guilds">
|
|
Guilds
|
|
</Link>
|
|
<Link className="nav-item" href="/rares">
|
|
Rares
|
|
</Link>
|
|
<Link className="nav-item" href="/help">
|
|
Help
|
|
</Link>
|
|
{session?.user ? (
|
|
<Link className="nav-item" href="/client">
|
|
Hotel
|
|
</Link>
|
|
) : null}
|
|
{isStaff ? (
|
|
<Link className="nav-item" href="/admin">
|
|
Admin
|
|
</Link>
|
|
) : null}
|
|
</nav>
|
|
<span className="spacer" />
|
|
{session?.user ? (
|
|
<>
|
|
<div className="currencies">
|
|
<span className="currency">
|
|
<span className="coin credits">cr</span>
|
|
{credits}
|
|
</span>
|
|
<span className="currency">
|
|
<span className="coin duckets">d</span>
|
|
{duckets}
|
|
</span>
|
|
<span className="currency">
|
|
<span className="coin diamonds">◆</span>
|
|
{diamonds}
|
|
</span>
|
|
</div>
|
|
<Link className="nav-item" href="/redeem">
|
|
Redeem
|
|
</Link>
|
|
<Link className="nav-item" href="/settings">
|
|
Settings
|
|
</Link>
|
|
<Link className="nav-item" href={`/u/${session.user.name}`}>
|
|
{session.user.name}
|
|
</Link>
|
|
</>
|
|
) : (
|
|
<Link className="btn btn-outline" href="/login">
|
|
Login
|
|
</Link>
|
|
)}
|
|
</div>
|
|
</header>
|
|
);
|
|
}
|