develop #56
@@ -1,11 +1,11 @@
|
|||||||
---
|
---
|
||||||
# nuzlocke-tracker-d68l
|
# nuzlocke-tracker-d68l
|
||||||
title: 'Frontend: Journal entry editor and list view'
|
title: 'Frontend: Journal entry editor and list view'
|
||||||
status: todo
|
status: completed
|
||||||
type: task
|
type: task
|
||||||
priority: normal
|
priority: normal
|
||||||
created_at: 2026-03-20T15:15:55Z
|
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
|
parent: nuzlocke-tracker-mz16
|
||||||
blocked_by:
|
blocked_by:
|
||||||
- nuzlocke-tracker-vmto
|
- nuzlocke-tracker-vmto
|
||||||
@@ -21,15 +21,49 @@ Create the frontend UI for writing and viewing journal entries.
|
|||||||
|
|
||||||
## Checklist
|
## Checklist
|
||||||
|
|
||||||
- [ ] Add `JournalEntry` TypeScript types to `frontend/src/types/`
|
- [x] Add `JournalEntry` TypeScript types to `frontend/src/types/`
|
||||||
- [ ] Create API client functions for journal CRUD
|
- [x] Create API client functions for journal CRUD
|
||||||
- [ ] Create `JournalList` component — chronological list of entries for a run
|
- [x] Create `JournalList` component — chronological list of entries for a run
|
||||||
- Show title, date, preview snippet, and linked boss (if any)
|
- Show title, date, preview snippet, and linked boss (if any)
|
||||||
- Link each entry to its detail/edit view
|
- 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)
|
- Optional boss result selector dropdown (link entry to a boss battle)
|
||||||
- Preview tab to render markdown
|
- Preview tab to render markdown
|
||||||
- Save and delete actions
|
- Save and delete actions
|
||||||
- [ ] Create `JournalEntryView` component — rendered markdown display
|
- [x] Create `JournalEntryView` component — rendered markdown display
|
||||||
- [ ] Add journal section/tab to the run detail page
|
- [x] Add journal section/tab to the run detail page
|
||||||
- [ ] Add route for journal entry detail/edit view
|
- [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
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ status: completed
|
|||||||
type: epic
|
type: epic
|
||||||
priority: normal
|
priority: normal
|
||||||
created_at: 2026-02-19T07:43:05Z
|
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).
|
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
|
## Success Criteria
|
||||||
|
|
||||||
- [x] Backend: journal entries CRUD API is complete (`nuzlocke-tracker-vmto`)
|
- [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`)
|
||||||
|
|||||||
@@ -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,
|
"createdAt": 1774018905931,
|
||||||
"updatedAt": 1774020533491
|
"updatedAt": 1774021107517
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -25,3 +25,4 @@ There basically is no local storage, runs live in the database. I think that exc
|
|||||||
/start
|
/start
|
||||||
/status
|
/status
|
||||||
/stop
|
/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.
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
GenlockeDetail,
|
GenlockeDetail,
|
||||||
GenlockeList,
|
GenlockeList,
|
||||||
Home,
|
Home,
|
||||||
|
JournalEntryPage,
|
||||||
NewGenlocke,
|
NewGenlocke,
|
||||||
NewRun,
|
NewRun,
|
||||||
RunList,
|
RunList,
|
||||||
@@ -30,6 +31,7 @@ function App() {
|
|||||||
<Route path="runs" element={<RunList />} />
|
<Route path="runs" element={<RunList />} />
|
||||||
<Route path="runs/new" element={<NewRun />} />
|
<Route path="runs/new" element={<NewRun />} />
|
||||||
<Route path="runs/:runId" element={<RunEncounters />} />
|
<Route path="runs/:runId" element={<RunEncounters />} />
|
||||||
|
<Route path="runs/:runId/journal/:entryId" element={<JournalEntryPage />} />
|
||||||
<Route path="genlockes" element={<GenlockeList />} />
|
<Route path="genlockes" element={<GenlockeList />} />
|
||||||
<Route path="genlockes/new" element={<NewGenlocke />} />
|
<Route path="genlockes/new" element={<NewGenlocke />} />
|
||||||
<Route path="genlockes/:genlockeId" element={<GenlockeDetail />} />
|
<Route path="genlockes/:genlockeId" element={<GenlockeDetail />} />
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import {
|
|||||||
} from '../components'
|
} from '../components'
|
||||||
import { BossDefeatModal } from '../components/BossDefeatModal'
|
import { BossDefeatModal } from '../components/BossDefeatModal'
|
||||||
import { ConditionBadge } from '../components/ConditionBadge'
|
import { ConditionBadge } from '../components/ConditionBadge'
|
||||||
|
import { JournalSection } from '../components/journal'
|
||||||
import type {
|
import type {
|
||||||
Route,
|
Route,
|
||||||
RouteWithChildren,
|
RouteWithChildren,
|
||||||
@@ -497,6 +498,7 @@ export function RunEncounters() {
|
|||||||
const [showTeam, setShowTeam] = useState(true)
|
const [showTeam, setShowTeam] = useState(true)
|
||||||
const [teamSort, setTeamSort] = useState<TeamSortKey>('route')
|
const [teamSort, setTeamSort] = useState<TeamSortKey>('route')
|
||||||
const [filter, setFilter] = useState<'all' | RouteStatus>('all')
|
const [filter, setFilter] = useState<'all' | RouteStatus>('all')
|
||||||
|
const [activeTab, setActiveTab] = useState<'encounters' | 'journal'>('encounters')
|
||||||
|
|
||||||
const storageKey = `expandedGroups-${runId}`
|
const storageKey = `expandedGroups-${runId}`
|
||||||
const [expandedGroups, setExpandedGroups] = useState<Set<number>>(() => {
|
const [expandedGroups, setExpandedGroups] = useState<Set<number>>(() => {
|
||||||
@@ -1137,7 +1139,41 @@ export function RunEncounters() {
|
|||||||
<CustomRulesDisplay customRules={run.rules?.customRules ?? ''} />
|
<CustomRulesDisplay customRules={run.rules?.customRules ?? ''} />
|
||||||
</div>
|
</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) && (
|
{(alive.length > 0 || dead.length > 0) && (
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<div className="flex items-center justify-between mb-3">
|
<div className="flex items-center justify-between mb-3">
|
||||||
@@ -1551,6 +1587,8 @@ export function RunEncounters() {
|
|||||||
allowedTypes={rulesAllowedTypes.length ? rulesAllowedTypes : undefined}
|
allowedTypes={rulesAllowedTypes.length ? rulesAllowedTypes : undefined}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Shiny Encounter Modal */}
|
{/* Shiny Encounter Modal */}
|
||||||
{showShinyModal && routes && (
|
{showShinyModal && routes && (
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
export { GenlockeDetail } from './GenlockeDetail'
|
export { GenlockeDetail } from './GenlockeDetail'
|
||||||
export { GenlockeList } from './GenlockeList'
|
export { GenlockeList } from './GenlockeList'
|
||||||
export { Home } from './Home'
|
export { Home } from './Home'
|
||||||
|
export { JournalEntryPage } from './JournalEntryPage'
|
||||||
export { NewGenlocke } from './NewGenlocke'
|
export { NewGenlocke } from './NewGenlocke'
|
||||||
export { NewRun } from './NewRun'
|
export { NewRun } from './NewRun'
|
||||||
export { RunList } from './RunList'
|
export { RunList } from './RunList'
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
export * from './admin'
|
export * from './admin'
|
||||||
export * from './game'
|
export * from './game'
|
||||||
|
export * from './journal'
|
||||||
export * from './rules'
|
export * from './rules'
|
||||||
export * from './stats'
|
export * from './stats'
|
||||||
|
|||||||
Reference in New Issue
Block a user