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>
98 lines
3.1 KiB
TypeScript
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>
|
|
)
|
|
}
|