Files
nuzlocke-tracker/frontend/src/pages/JournalEntryPage.tsx
Julian Tabel 0a519e356e
Some checks failed
CI / backend-tests (push) Failing after 1m16s
CI / frontend-tests (push) Successful in 57s
feat: add auth system, boss pokemon details, moves/abilities API, and run ownership
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>
2026-03-20 21:41:38 +01:00

98 lines
3.1 KiB
TypeScript

import { useState } from 'react'
import { useParams, useNavigate } from 'react-router-dom'
import { useRun } from '../hooks/useRuns'
import { useBossResults, useGameBosses } from '../hooks/useBosses'
import { useJournalEntry, useUpdateJournalEntry, useDeleteJournalEntry } from '../hooks/useJournal'
import { JournalEntryView } from '../components/journal/JournalEntryView'
import { JournalEditor } from '../components/journal/JournalEditor'
export function JournalEntryPage() {
const { runId, entryId } = useParams<{ runId: string; entryId: string }>()
const navigate = useNavigate()
const runIdNum = Number(runId)
const [isEditing, setIsEditing] = useState(false)
const { data: run, isLoading: runLoading } = useRun(runIdNum)
const { data: entry, isLoading: entryLoading, error } = useJournalEntry(runIdNum, entryId ?? null)
const { data: bossResults } = useBossResults(runIdNum)
const { data: bosses } = useGameBosses(run?.gameId ?? null)
const updateEntry = useUpdateJournalEntry(runIdNum, entryId ?? '')
const deleteEntry = useDeleteJournalEntry(runIdNum)
const isLoading = runLoading || entryLoading
if (isLoading) {
return (
<div className="max-w-3xl mx-auto p-8 flex justify-center">
<div className="w-8 h-8 border-4 border-blue-600 border-t-transparent rounded-full animate-spin" />
</div>
)
}
if (error || !entry || !run) {
return (
<div className="max-w-3xl mx-auto p-8">
<div className="rounded-lg bg-status-failed-bg p-4 text-status-failed">
Failed to load journal entry.
</div>
<button
type="button"
onClick={() => navigate(`/runs/${runId}`)}
className="inline-block mt-4 text-blue-600 hover:underline"
>
Back to run
</button>
</div>
)
}
const linkedBossResult = entry.bossResultId
? bossResults?.find((br) => br.id === entry.bossResultId)
: null
const linkedBoss = linkedBossResult
? bosses?.find((b) => b.id === linkedBossResult.bossBattleId)
: null
const handleSave = (data: { title: string; body: string; bossResultId: number | null }) => {
updateEntry.mutate(data, {
onSuccess: () => setIsEditing(false),
})
}
const handleDelete = () => {
deleteEntry.mutate(entry.id, {
onSuccess: () => navigate(`/runs/${runId}?tab=journal`),
})
}
if (isEditing) {
return (
<div className="max-w-3xl mx-auto p-8">
<h1 className="text-2xl font-bold text-text-primary mb-6">Edit Journal Entry</h1>
<JournalEditor
entry={entry}
bossResults={bossResults}
bosses={bosses}
onSave={handleSave}
onDelete={handleDelete}
onCancel={() => setIsEditing(false)}
isSaving={updateEntry.isPending}
isDeleting={deleteEntry.isPending}
/>
</div>
)
}
return (
<div className="max-w-3xl mx-auto p-8">
<JournalEntryView
entry={entry}
bossResult={linkedBossResult}
boss={linkedBoss}
onEdit={() => setIsEditing(true)}
onBack={() => navigate(`/runs/${runId}?tab=journal`)}
/>
</div>
)
}