Add auth system with invite-only registration and admin roles

JWT-based auth (hono/jwt + bcrypt), anonymous-first flow preserved.
Registration requires invite code when REQUIRE_INVITE=true. Admin
user seeded on startup (admin/admin, forced password reset). Login
accepts email or username. Admin invitations management page in
sidebar. Regular users get invite-a-friend button when USER_INVITATIONS > 0.
Frontend gate screen blocks game access for unregistered users with
invite code entry, registration, login, and password reset flows.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-27 19:25:16 -04:00
parent df01ac8e35
commit 4881907c28
20 changed files with 1161 additions and 48 deletions
+36
View File
@@ -1,6 +1,7 @@
import { useRef, useState } from 'react';
import { useGameStore } from '@/store';
import { ConfirmModal } from '@/components/common/ConfirmModal';
import { getTokenPayload, isRegistered, isAdmin } from '@/lib/api';
export function SettingsPage() {
const settings = useGameStore((s) => s.meta.settings);
@@ -63,10 +64,45 @@ export function SettingsPage() {
window.location.reload();
};
const payload = getTokenPayload();
const registered = isRegistered();
const admin = isAdmin();
return (
<div className="space-y-6 max-w-2xl">
<h2 className="text-2xl font-bold">Settings</h2>
<div className="bg-surface-900 border border-surface-700 rounded-xl p-4 space-y-4">
<h3 className="font-semibold">Account</h3>
{registered ? (
<div className="space-y-2">
{payload?.email && (
<div className="flex items-center justify-between">
<div>
<div className="text-sm">Email</div>
<div className="text-xs text-surface-400">{payload.email}</div>
</div>
</div>
)}
{payload?.username && (
<div className="flex items-center justify-between">
<div>
<div className="text-sm">Username</div>
<div className="text-xs text-surface-400">{payload.username}</div>
</div>
</div>
)}
{admin && (
<div className="flex items-center gap-2">
<span className="text-xs px-2 py-0.5 rounded-full bg-accent/20 text-accent font-medium">Admin</span>
</div>
)}
</div>
) : (
<div className="text-sm text-surface-400">Playing as guest.</div>
)}
</div>
<div className="bg-surface-900 border border-surface-700 rounded-xl p-4 space-y-4">
<h3 className="font-semibold">Game</h3>