feat: add auth system, boss pokemon details, moves/abilities API, and run ownership
Some checks failed
CI / backend-tests (push) Failing after 1m16s
CI / frontend-tests (push) Successful in 57s

Add user authentication with login/signup/protected routes, boss pokemon
detail fields and result team tracking, moves and abilities selector
components and API, run ownership and visibility controls, and various
UI improvements across encounters, run list, and journal pages.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-20 21:41:38 +01:00
parent a6cb309b8b
commit 0a519e356e
69 changed files with 3574 additions and 693 deletions

View File

@@ -1,10 +1,18 @@
import { useMemo, useState } from 'react'
import { useParams, Link } from 'react-router-dom'
import { useAuth } from '../contexts/AuthContext'
import { useRun, useUpdateRun, useNamingCategories } from '../hooks/useRuns'
import { useGameRoutes } from '../hooks/useGames'
import { useCreateEncounter, useUpdateEncounter } from '../hooks/useEncounters'
import { CustomRulesDisplay, StatCard, PokemonCard, RuleBadges, StatusChangeModal, EndRunModal } from '../components'
import type { RunStatus, EncounterDetail } from '../types'
import {
CustomRulesDisplay,
StatCard,
PokemonCard,
RuleBadges,
StatusChangeModal,
EndRunModal,
} from '../components'
import type { RunStatus, EncounterDetail, RunVisibility } from '../types'
type TeamSortKey = 'route' | 'level' | 'species' | 'dex'
@@ -49,6 +57,7 @@ export function RunDashboard() {
const runIdNum = Number(runId)
const { data: run, isLoading, error } = useRun(runIdNum)
const { data: routes } = useGameRoutes(run?.gameId ?? null)
const { user } = useAuth()
const createEncounter = useCreateEncounter(runIdNum)
const updateEncounter = useUpdateEncounter(runIdNum)
const updateRun = useUpdateRun(runIdNum)
@@ -57,6 +66,9 @@ export function RunDashboard() {
const [showEndRun, setShowEndRun] = useState(false)
const [teamSort, setTeamSort] = useState<TeamSortKey>('route')
const isOwner = user && run?.owner?.id === user.id
const canEdit = isOwner || !run?.owner
const encounters = run?.encounters ?? []
const alive = useMemo(
() =>
@@ -190,11 +202,31 @@ export function RunDashboard() {
<CustomRulesDisplay customRules={run.rules?.customRules ?? ''} />
</div>
{/* Visibility */}
{canEdit && (
<div className="mb-6">
<h2 className="text-sm font-medium text-text-tertiary mb-2">Visibility</h2>
<select
value={run.visibility}
onChange={(e) => updateRun.mutate({ visibility: e.target.value as RunVisibility })}
className="text-sm border border-border-default rounded-lg px-3 py-1.5 bg-surface-1 text-text-primary"
>
<option value="public">Public</option>
<option value="private">Private</option>
</select>
<p className="mt-1 text-xs text-text-tertiary">
{run.visibility === 'private'
? 'Only you can see this run'
: 'Anyone can view this run'}
</p>
</div>
)}
{/* Naming Scheme */}
{namingCategories && namingCategories.length > 0 && (
<div className="mb-6">
<h2 className="text-sm font-medium text-text-tertiary mb-2">Naming Scheme</h2>
{isActive ? (
{isActive && canEdit ? (
<select
value={run.namingScheme ?? ''}
onChange={(e) => updateRun.mutate({ namingScheme: e.target.value || null })}
@@ -246,7 +278,7 @@ export function RunDashboard() {
<PokemonCard
key={enc.id}
encounter={enc}
onClick={isActive ? () => setSelectedEncounter(enc) : undefined}
onClick={isActive && canEdit ? () => setSelectedEncounter(enc) : undefined}
/>
))}
</div>
@@ -263,7 +295,7 @@ export function RunDashboard() {
key={enc.id}
encounter={enc}
showFaintLevel
onClick={isActive ? () => setSelectedEncounter(enc) : undefined}
onClick={isActive && canEdit ? () => setSelectedEncounter(enc) : undefined}
/>
))}
</div>
@@ -272,7 +304,7 @@ export function RunDashboard() {
{/* Quick Actions */}
<div className="mt-8 flex gap-3">
{isActive && (
{isActive && canEdit && (
<>
<Link
to={`/runs/${runId}/encounters`}