From 088cd350023af58dafc8612a79834e5770d8ad8b Mon Sep 17 00:00:00 2001 From: Julian Tabel Date: Fri, 20 Mar 2026 16:39:52 +0100 Subject: [PATCH] add Ko-fi bean --- .beans/nuzlocke-tracker-bw1m--errors.md | 5 +- ...ipt-build-fails-due-to-optional-propert.md | 29 +++ ...uzlocke-tracker-oar4--ko-fi-integration.md | 19 ++ ...nd-journal-entries-model-api-and-migrat.md | 8 +- frontend/src/api/journal.ts | 37 ++++ .../src/components/journal/JournalEditor.tsx | 190 ++++++++++++++++++ .../components/journal/JournalEntryView.tsx | 83 ++++++++ .../src/components/journal/JournalList.tsx | 102 ++++++++++ .../src/components/journal/JournalSection.tsx | 77 +++++++ frontend/src/components/journal/index.ts | 4 + frontend/src/hooks/useJournal.ts | 61 ++++++ frontend/src/pages/JournalEntryPage.tsx | 101 ++++++++++ frontend/src/types/journal.ts | 21 ++ 13 files changed, 733 insertions(+), 4 deletions(-) create mode 100644 .beans/nuzlocke-tracker-d5ht--bug-typescript-build-fails-due-to-optional-propert.md create mode 100644 .beans/nuzlocke-tracker-oar4--ko-fi-integration.md create mode 100644 frontend/src/api/journal.ts create mode 100644 frontend/src/components/journal/JournalEditor.tsx create mode 100644 frontend/src/components/journal/JournalEntryView.tsx create mode 100644 frontend/src/components/journal/JournalList.tsx create mode 100644 frontend/src/components/journal/JournalSection.tsx create mode 100644 frontend/src/components/journal/index.ts create mode 100644 frontend/src/hooks/useJournal.ts create mode 100644 frontend/src/pages/JournalEntryPage.tsx create mode 100644 frontend/src/types/journal.ts diff --git a/.beans/nuzlocke-tracker-bw1m--errors.md b/.beans/nuzlocke-tracker-bw1m--errors.md index a7c1c62..b2f00ee 100644 --- a/.beans/nuzlocke-tracker-bw1m--errors.md +++ b/.beans/nuzlocke-tracker-bw1m--errors.md @@ -1,10 +1,11 @@ --- # nuzlocke-tracker-bw1m title: Errors -status: todo +status: completed type: epic +priority: normal created_at: 2026-03-20T15:19:43Z -updated_at: 2026-03-20T15:19:43Z +updated_at: 2026-03-20T15:39:27Z --- Container for crash and blocker beans created by Talos. diff --git a/.beans/nuzlocke-tracker-d5ht--bug-typescript-build-fails-due-to-optional-propert.md b/.beans/nuzlocke-tracker-d5ht--bug-typescript-build-fails-due-to-optional-propert.md new file mode 100644 index 0000000..81a2212 --- /dev/null +++ b/.beans/nuzlocke-tracker-d5ht--bug-typescript-build-fails-due-to-optional-propert.md @@ -0,0 +1,29 @@ +--- +# nuzlocke-tracker-d5ht +title: 'Bug: TypeScript build fails due to optional property type mismatches in journal components' +status: todo +type: bug +priority: high +created_at: 2026-03-20T15:39:00Z +updated_at: 2026-03-20T15:39:00Z +parent: nuzlocke-tracker-bw1m +--- + +The frontend TypeScript build fails with 3 errors due to `exactOptionalPropertyTypes` being enabled. + +## Errors + +1. `JournalEntryPage.tsx:76` - `bossResults` and `bosses` props passed as `undefined` to `JournalEditor` +2. `JournalEntryPage.tsx:92` - `bossResult` and `boss` props passed as `undefined` to `JournalEntryView` +3. `RunEncounters.tsx:1170` - `bossResults` and `bosses` props passed as `undefined` to `JournalSection` + +## Root Cause + +Optional props in interfaces are declared as `prop?: Type` but callers pass `undefined` values from React Query hooks. With `exactOptionalPropertyTypes: true`, TypeScript requires `prop?: Type | undefined` to allow explicit `undefined` values. + +## Fix + +Update the interfaces in these files: +- `JournalEditor.tsx` lines 9-10: change to `bossResults?: BossResult[] | undefined` and `bosses?: BossBattle[] | undefined` +- `JournalEntryView.tsx` lines 8-9: change to `bossResult?: BossResult | null | undefined` and `boss?: BossBattle | null | undefined` +- `JournalSection.tsx` lines 9-10: change to `bossResults?: BossResult[] | undefined` and `bosses?: BossBattle[] | undefined` diff --git a/.beans/nuzlocke-tracker-oar4--ko-fi-integration.md b/.beans/nuzlocke-tracker-oar4--ko-fi-integration.md new file mode 100644 index 0000000..f5af052 --- /dev/null +++ b/.beans/nuzlocke-tracker-oar4--ko-fi-integration.md @@ -0,0 +1,19 @@ +--- +# nuzlocke-tracker-oar4 +title: Ko-fi Integration +status: draft +type: feature +priority: deferred +created_at: 2026-03-20T15:38:23Z +updated_at: 2026-03-20T15:38:23Z +--- + +Add Ko-fi integration to allow visitors to contribute toward hosting costs. This is not about monetization — it's a way for users who enjoy the tool to optionally help cover server/infrastructure expenses. + +## Open Questions + +- [ ] Where should the Ko-fi link/button live? (footer, about page, dedicated page, or subtle banner?) +- [ ] Should it be a simple outbound link to a Ko-fi page, or use Ko-fi's embeddable widget/overlay? +- [ ] Should there be any acknowledgment for supporters (e.g., a thank-you page, supporter list)? +- [ ] Should this be gated behind user auth (only shown to logged-in users) or visible to everyone? +- [ ] Any legal/tax considerations to document? diff --git a/.beans/nuzlocke-tracker-t90q--crash-backend-journal-entries-model-api-and-migrat.md b/.beans/nuzlocke-tracker-t90q--crash-backend-journal-entries-model-api-and-migrat.md index 6ac81a7..5adc4cd 100644 --- a/.beans/nuzlocke-tracker-t90q--crash-backend-journal-entries-model-api-and-migrat.md +++ b/.beans/nuzlocke-tracker-t90q--crash-backend-journal-entries-model-api-and-migrat.md @@ -1,11 +1,11 @@ --- # nuzlocke-tracker-t90q title: 'Crash: Backend: Journal entries model, API, and migration' -status: todo +status: completed type: bug priority: high created_at: 2026-03-20T15:30:02Z -updated_at: 2026-03-20T15:30:24Z +updated_at: 2026-03-20T15:39:13Z parent: nuzlocke-tracker-bw1m blocking: - nuzlocke-tracker-vmto @@ -18,3 +18,7 @@ Manual review required before retrying. Bean: nuzlocke-tracker-vmto Title: Backend: Journal entries model, API, and migration + +## Resolution + +The underlying bean (nuzlocke-tracker-vmto) was already completed before the crash was detected. All backend work for journal entries is implemented and functional. A separate bug bean (nuzlocke-tracker-d5ht) was created for frontend TypeScript errors discovered during review. diff --git a/frontend/src/api/journal.ts b/frontend/src/api/journal.ts new file mode 100644 index 0000000..0a7a7d1 --- /dev/null +++ b/frontend/src/api/journal.ts @@ -0,0 +1,37 @@ +import { api } from './client' +import type { + JournalEntry, + CreateJournalEntryInput, + UpdateJournalEntryInput, +} from '../types/journal' + +export function getJournalEntries( + runId: number, + bossResultId?: number +): Promise { + const params = bossResultId != null ? `?boss_result_id=${bossResultId}` : '' + return api.get(`/runs/${runId}/journal${params}`) +} + +export function getJournalEntry(runId: number, entryId: string): Promise { + return api.get(`/runs/${runId}/journal/${entryId}`) +} + +export function createJournalEntry( + runId: number, + data: CreateJournalEntryInput +): Promise { + return api.post(`/runs/${runId}/journal`, data) +} + +export function updateJournalEntry( + runId: number, + entryId: string, + data: UpdateJournalEntryInput +): Promise { + return api.put(`/runs/${runId}/journal/${entryId}`, data) +} + +export function deleteJournalEntry(runId: number, entryId: string): Promise { + return api.del(`/runs/${runId}/journal/${entryId}`) +} diff --git a/frontend/src/components/journal/JournalEditor.tsx b/frontend/src/components/journal/JournalEditor.tsx new file mode 100644 index 0000000..db36b1d --- /dev/null +++ b/frontend/src/components/journal/JournalEditor.tsx @@ -0,0 +1,190 @@ +import { useState, useEffect } from 'react' +import ReactMarkdown from 'react-markdown' +import remarkGfm from 'remark-gfm' +import type { JournalEntry } from '../../types/journal' +import type { BossResult, BossBattle } from '../../types/game' + +interface JournalEditorProps { + entry?: JournalEntry | null + bossResults?: BossResult[] + bosses?: BossBattle[] + onSave: (data: { title: string; body: string; bossResultId: number | null }) => void + onDelete?: () => void + onCancel: () => void + isSaving?: boolean + isDeleting?: boolean +} + +type Tab = 'write' | 'preview' + +export function JournalEditor({ + entry, + bossResults = [], + bosses = [], + onSave, + onDelete, + onCancel, + isSaving = false, + isDeleting = false, +}: JournalEditorProps) { + const [title, setTitle] = useState(entry?.title ?? '') + const [body, setBody] = useState(entry?.body ?? '') + const [bossResultId, setBossResultId] = useState(entry?.bossResultId ?? null) + const [activeTab, setActiveTab] = useState('write') + const [showDeleteConfirm, setShowDeleteConfirm] = useState(false) + + useEffect(() => { + if (entry) { + setTitle(entry.title) + setBody(entry.body) + setBossResultId(entry.bossResultId) + } + }, [entry]) + + const bossMap = new Map(bosses.map((b) => [b.id, b])) + + const getBossName = (result: BossResult): string => { + const boss = bossMap.get(result.bossBattleId) + return boss?.name ?? `Boss #${result.bossBattleId}` + } + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault() + if (!title.trim() || !body.trim()) return + onSave({ title: title.trim(), body: body.trim(), bossResultId }) + } + + const handleDelete = () => { + if (showDeleteConfirm) { + onDelete?.() + } else { + setShowDeleteConfirm(true) + } + } + + const canSave = title.trim().length > 0 && body.trim().length > 0 + + return ( +
+
+ + setTitle(e.target.value)} + placeholder="Entry title..." + className="w-full px-3 py-2 bg-surface-1 border border-border rounded-lg text-text-primary placeholder:text-text-tertiary focus:outline-none focus:ring-2 focus:ring-blue-500" + disabled={isSaving} + /> +
+ +
+ + +
+ +
+
+ + +
+ + {activeTab === 'write' ? ( +