Add Phase 1 MVP: D&D text-adventure server
FastAPI + Postgres backend with fastapi-users auth, games/characters CRUD, and a WebSocket chat endpoint where an x.ai Grok DM narrates play, rolls dice, and maintains character sheets via tool calls. React + Tailwind frontend covers registration/login, game list and creation, participation-code join flow, character viewer, and a real-time chat session view. Docker Compose skeleton (Postgres + backend) included; Caddy/frontend container wiring is deferred to the deploy milestone. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { Link, useParams, useSearchParams } from "react-router-dom";
|
||||
|
||||
import { Character, getCharacter } from "../api/characters";
|
||||
|
||||
export default function CharacterDetail() {
|
||||
const { characterId } = useParams<{ characterId: string }>();
|
||||
const [searchParams] = useSearchParams();
|
||||
const backGameId = searchParams.get("back");
|
||||
|
||||
const [character, setCharacter] = useState<Character | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
if (!characterId) return;
|
||||
getCharacter(characterId)
|
||||
.then(setCharacter)
|
||||
.finally(() => setLoading(false));
|
||||
}, [characterId]);
|
||||
|
||||
if (loading) return <p className="p-8 text-slate-400">Lädt…</p>;
|
||||
if (!character) return <p className="p-8 text-slate-400">Charakter nicht gefunden.</p>;
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-2xl px-4 py-8">
|
||||
{backGameId && (
|
||||
<Link
|
||||
to={`/games/${backGameId}`}
|
||||
className="mb-4 inline-block rounded bg-slate-800 px-3 py-2 text-sm hover:bg-slate-700"
|
||||
>
|
||||
← Zurück zum Spiel
|
||||
</Link>
|
||||
)}
|
||||
|
||||
<h1 className="text-2xl font-bold text-amber-400">{character.name}</h1>
|
||||
<p className="mt-1 text-slate-400">
|
||||
{character.race ?? "?"} · {character.char_class ?? "?"} · Stufe {character.level}
|
||||
</p>
|
||||
|
||||
{Object.keys(character.stats).length > 0 && (
|
||||
<div className="mt-4 grid grid-cols-3 gap-3 sm:grid-cols-6">
|
||||
{Object.entries(character.stats).map(([stat, value]) => (
|
||||
<div key={stat} className="rounded bg-slate-800 p-2 text-center">
|
||||
<div className="text-xs uppercase text-slate-500">{stat}</div>
|
||||
<div className="text-lg font-semibold text-slate-100">{value}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-6 whitespace-pre-wrap rounded-lg border border-slate-800 bg-slate-900 p-4 text-slate-300">
|
||||
{character.description || "Noch keine Beschreibung."}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user