add Ko-fi bean

This commit is contained in:
Julian Tabel
2026-03-20 16:38:54 +01:00
parent c9d42b091f
commit 5bd4ca7d3e
8 changed files with 109 additions and 13 deletions

View File

@@ -1,11 +1,11 @@
---
# nuzlocke-tracker-d68l
title: 'Frontend: Journal entry editor and list view'
status: todo
status: completed
type: task
priority: normal
created_at: 2026-03-20T15:15:55Z
updated_at: 2026-03-20T15:15:59Z
updated_at: 2026-03-20T15:37:39Z
parent: nuzlocke-tracker-mz16
blocked_by:
- nuzlocke-tracker-vmto
@@ -21,15 +21,49 @@ Create the frontend UI for writing and viewing journal entries.
## Checklist
- [ ] Add `JournalEntry` TypeScript types to `frontend/src/types/`
- [ ] Create API client functions for journal CRUD
- [ ] Create `JournalList` component — chronological list of entries for a run
- [x] Add `JournalEntry` TypeScript types to `frontend/src/types/`
- [x] Create API client functions for journal CRUD
- [x] Create `JournalList` component — chronological list of entries for a run
- Show title, date, preview snippet, and linked boss (if any)
- Link each entry to its detail/edit view
- [ ] Create `JournalEditor` component — markdown textarea with title input
- [x] Create `JournalEditor` component — markdown textarea with title input
- Optional boss result selector dropdown (link entry to a boss battle)
- Preview tab to render markdown
- Save and delete actions
- [ ] Create `JournalEntryView` component — rendered markdown display
- [ ] Add journal section/tab to the run detail page
- [ ] Add route for journal entry detail/edit view
- [x] Create `JournalEntryView` component — rendered markdown display
- [x] Add journal section/tab to the run detail page
- [x] Add route for journal entry detail/edit view
## Summary of Changes
Implemented the frontend journal entry editor and list view with the following components:
**Types created:**
- `frontend/src/types/journal.ts` - TypeScript types for JournalEntry, CreateJournalEntryInput, UpdateJournalEntryInput
**API client created:**
- `frontend/src/api/journal.ts` - CRUD functions for journal entries
- `frontend/src/hooks/useJournal.ts` - React Query hooks for journal data fetching and mutations
**Components created:**
- `frontend/src/components/journal/JournalList.tsx` - Chronological list of entries with title, date, preview snippet, and linked boss display
- `frontend/src/components/journal/JournalEditor.tsx` - Markdown textarea with title input, boss result selector, write/preview tabs, save/delete actions
- `frontend/src/components/journal/JournalEntryView.tsx` - Rendered markdown display with entry metadata
- `frontend/src/components/journal/JournalSection.tsx` - Wrapper component for embedding in RunEncounters page
**Pages created:**
- `frontend/src/pages/JournalEntryPage.tsx` - Standalone page for viewing/editing a single journal entry
**Modified files:**
- `frontend/src/types/index.ts` - Added journal type exports
- `frontend/src/pages/index.ts` - Added JournalEntryPage export
- `frontend/src/App.tsx` - Added route `/runs/:runId/journal/:entryId`
- `frontend/src/pages/RunEncounters.tsx` - Added Encounters/Journal tab navigation with JournalSection integration
**Features:**
- Tab navigation in run detail page to switch between Encounters and Journal views
- Create new journal entries with markdown content and optional boss battle linking
- Edit and delete existing entries
- Write/Preview toggle in editor
- Rendered markdown display with full prose styling

View File

@@ -5,7 +5,7 @@ status: completed
type: epic
priority: normal
created_at: 2026-02-19T07:43:05Z
updated_at: 2026-03-20T15:30:38Z
updated_at: 2026-03-20T15:37:21Z
---
Let users tell the story of their nuzlocke run through session journal entries (blog posts).
@@ -35,4 +35,4 @@ The journal becomes a chronological narrative of the nuzlocke run, with game dat
## Success Criteria
- [x] Backend: journal entries CRUD API is complete (`nuzlocke-tracker-vmto`)
- [ ] Frontend: journal list, editor, and view are functional (`nuzlocke-tracker-d68l`)
- [x] Frontend: journal list, editor, and view are functional (`nuzlocke-tracker-d68l`)

View File

@@ -963,10 +963,29 @@
}
}
]
},
{
"role": "user",
"content": "I want to add a draft to integrate Ko-fi. While the idea is not to make money from this, if this where to be hosted somewhere, it will cost something, and maybe people want to help out with that.",
"timestamp": 1774021092843
},
{
"role": "assistant",
"content": "Created **Ko-fi Integration** (`nuzlocke-tracker-oar4`) as a `draft` / `deferred` feature. It has open questions to work through when you're ready to refine it — things like placement, widget vs. link, and whether supporters get any acknowledgment.\n\nAnything else?",
"timestamp": 1774021107517,
"toolCalls": [
{
"name": "Bash",
"args": {
"command": "beans create --json \"Ko-fi Integration\" -t feature -d \"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.\n\n## Open Questions\n\n- [ ] Where should the Ko-fi link/button live? (footer, about page, dedicated page, or subtle banner?)\n- [ ] Should it be a simple outbound link to a Ko-fi page, or use Ko-fi's embeddable widget/overlay?\n- [ ] Should there be any acknowledgment for supporters (e.g., a thank-you page, supporter list)?\n- [ ] Should this be gated behind user auth (only shown to logged-in users) or visible to everyone?\n- [ ] Any legal/tax considerations to document?\" -s draft -p deferred",
"description": "Create Ko-fi draft bean"
}
}
]
}
],
"createdAt": 1774018905931,
"updatedAt": 1774020533491
"updatedAt": 1774021107517
}
]
}

View File

@@ -25,3 +25,4 @@ There basically is no local storage, runs live in the database. I think that exc
/start
/status
/stop
I want to add a draft to integrate Ko-fi. While the idea is not to make money from this, if this where to be hosted somewhere, it will cost something, and maybe people want to help out with that.

View File

@@ -5,6 +5,7 @@ import {
GenlockeDetail,
GenlockeList,
Home,
JournalEntryPage,
NewGenlocke,
NewRun,
RunList,
@@ -30,6 +31,7 @@ function App() {
<Route path="runs" element={<RunList />} />
<Route path="runs/new" element={<NewRun />} />
<Route path="runs/:runId" element={<RunEncounters />} />
<Route path="runs/:runId/journal/:entryId" element={<JournalEntryPage />} />
<Route path="genlockes" element={<GenlockeList />} />
<Route path="genlockes/new" element={<NewGenlocke />} />
<Route path="genlockes/:genlockeId" element={<GenlockeDetail />} />

View File

@@ -24,6 +24,7 @@ import {
} from '../components'
import { BossDefeatModal } from '../components/BossDefeatModal'
import { ConditionBadge } from '../components/ConditionBadge'
import { JournalSection } from '../components/journal'
import type {
Route,
RouteWithChildren,
@@ -497,6 +498,7 @@ export function RunEncounters() {
const [showTeam, setShowTeam] = useState(true)
const [teamSort, setTeamSort] = useState<TeamSortKey>('route')
const [filter, setFilter] = useState<'all' | RouteStatus>('all')
const [activeTab, setActiveTab] = useState<'encounters' | 'journal'>('encounters')
const storageKey = `expandedGroups-${runId}`
const [expandedGroups, setExpandedGroups] = useState<Set<number>>(() => {
@@ -1137,7 +1139,41 @@ export function RunEncounters() {
<CustomRulesDisplay customRules={run.rules?.customRules ?? ''} />
</div>
{/* Team Section */}
{/* Tab Navigation */}
<div className="flex gap-1 mb-6 border-b border-border">
<button
type="button"
onClick={() => setActiveTab('encounters')}
className={`px-4 py-2 text-sm font-medium border-b-2 -mb-px transition-colors ${
activeTab === 'encounters'
? 'border-blue-500 text-blue-500'
: 'border-transparent text-text-secondary hover:text-text-primary'
}`}
>
Encounters
</button>
<button
type="button"
onClick={() => setActiveTab('journal')}
className={`px-4 py-2 text-sm font-medium border-b-2 -mb-px transition-colors ${
activeTab === 'journal'
? 'border-blue-500 text-blue-500'
: 'border-transparent text-text-secondary hover:text-text-primary'
}`}
>
Journal
</button>
</div>
{/* Journal Tab */}
{activeTab === 'journal' && (
<JournalSection runId={runIdNum} bossResults={bossResults} bosses={bosses} />
)}
{/* Encounters Tab */}
{activeTab === 'encounters' && (
<>
{/* Team Section */}
{(alive.length > 0 || dead.length > 0) && (
<div className="mb-6">
<div className="flex items-center justify-between mb-3">
@@ -1551,6 +1587,8 @@ export function RunEncounters() {
allowedTypes={rulesAllowedTypes.length ? rulesAllowedTypes : undefined}
/>
)}
</>
)}
{/* Shiny Encounter Modal */}
{showShinyModal && routes && (

View File

@@ -1,6 +1,7 @@
export { GenlockeDetail } from './GenlockeDetail'
export { GenlockeList } from './GenlockeList'
export { Home } from './Home'
export { JournalEntryPage } from './JournalEntryPage'
export { NewGenlocke } from './NewGenlocke'
export { NewRun } from './NewRun'
export { RunList } from './RunList'

View File

@@ -1,4 +1,5 @@
export * from './admin'
export * from './game'
export * from './journal'
export * from './rules'
export * from './stats'