Files
Siteforge/plans/finished-books-playlist.md
T
nmemmert 4b411f763f Add inbound email capture via Cloudflare Email Worker
- Cloudflare Email Worker forwards hello@ and nate@ to Gmail and POSTs
  parsed MIME email to /api/inbound-email as admin inbox entries
- New server route authenticates via shared secret and deduplicates by
  Message-ID before storing inbound emails as contact submissions
- Admin inbox shows "email" badge for inbound messages; reply composer
  opens blank with auto-subject "Re: [original]" and signature preview
- Documents setup steps in cloudflare/email-worker.js and .env.example

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 11:23:27 -04:00

7.5 KiB
Raw Blame History

Finished Books — Episode Playlist Feature

Goal

Add a proper Finished Books section where completed book studies live with a full ordered episode playlist. The Downloads page gets a minor label/copy update (Option A). A single "End Current Series" button in admin handles the full book transition in one step.


User Flow (Public Site)

  1. User clicks Finished Books in the nav
  2. Lands on /finished — grid of completed book cards (image, title, description)
  3. Clicks a book → /finished/:id — sees all episodes in numbered order, each with a player
  4. Breadcrumb: Finished Books → Titus

Episode Highlights on the Episodes page only ever shows picks from the current book. When a book ends, highlights are cleared and replaced with picks from the new series automatically (see transition workflow below).


Book Transition Workflow — "End Current Series" Button

A single button in admin (lives on the Current Series tab) that does all three transition steps at once:

What it does

  1. Creates a Finished Book entry from the current series data (title, image, description, season number) — immediately appears on /finished
  2. Clears Episode Highlights — wipes podcastFeaturedLinks so the old season's picks are gone
  3. Opens a guided form for the new series — prompts for new series title, image URL, and season number before saving

The form flow

  • User clicks "End Current Series & Start New Book"
  • A confirmation modal appears with a summary: "This will archive [Titus] to Finished Books and clear Episode Highlights."
  • Below the confirmation, a small form: New Series Title, New Series Image, New Season Number
  • User fills it in and hits Confirm
  • All three changes save together as one content update

What the user still does manually after

  • Go to Episode Highlights and add 35 hand-picked episodes from the new series (the section is now empty and ready)

Changes Required

1. server/routes/episodes.js — Parse itunes:season from RSS

One line added to parseRssItems alongside the existing itunes:episode extraction:

const season = (/<itunes:season>([\s\S]*?)<\/itunes:season>/.exec(block)?.[1] ?? '').trim()

Include season in the returned episode object.

2. src/content.ts — New FinishedBook type + field on SiteContent

Clean, purpose-built interface:

export interface FinishedBook {
  id: string
  title: string
  description: string
  imageUrl: string
  season: number
}

Add finishedBooks: FinishedBook[] to SiteContent with default [].

3. src/App.tsx — New useEpisodesForBook hook

Fetches /api/episodes/all and filters by season number, sorted ascending:

const filtered = (data.episodes ?? [])
  .filter(ep => ep.season === String(book.season))
  .sort((a, b) => parseInt(a.episode) - parseInt(b.episode))

"Episodes" becomes a dropdown parent with two children: Current Series (/episodes) and Finished Books (/finished). The flat <NavLink to="/episodes"> is replaced with a dropdown component. Everything else in the nav stays flat and unchanged.

Desktop behavior:

  • "Episodes ▾" shows on hover (or focus), revealing a small popover menu below with the two links
  • Parent label stays gold/active when either child route is active
  • Implemented with CSS :hover + :focus-within on a wrapper — no JS state needed for desktop

Mobile behavior (under ~540px):

  • The mobile nav already collapses to a full-width vertical list with max-height animation
  • "Episodes" becomes a top-level row with an expand arrow (chevron)
  • Tapping it toggles a sub-list showing "Current Series" and "Finished Books" indented beneath it
  • Implemented with a small React useState toggle on the dropdown item — consistent with how menuOpen already works
  • Sub-items get slightly smaller text and left padding to show hierarchy
  • The max-height: 500px on .header-nav--open will need to increase to 600px to accommodate the expanded sub-items

New elements needed:

  • NavDropdown component (or inline JSX) wrapping the two episode links
  • CSS classes: .nav-dropdown, .nav-dropdown-menu, .nav-dropdown-item, .nav-dropdown--open (mobile only)
  • Mobile sub-item styles: indented, smaller text, no bottom border on last child before parent closes

Footer: Add a flat "Finished Books" link alongside the existing footer nav links — no dropdown needed there.

5. src/App.tsx — Two new routes

<Route path="/finished" element={<FinishedBooksPage content={content} />} />
<Route path="/finished/:id" element={<FinishedSeriesPage content={content} />} />

6. src/App.tsx — Two new page components

FinishedBooksPage — grid of finishedBooks cards (image, title, description), each linking to /finished/:id. Friendly empty state if no books yet.

FinishedSeriesPage — uses useEpisodesForBook, renders episodes sorted ascending by episode number as a numbered playlist. Each row: episode number, title, duration, EpisodeAudioPlayer.

7. src/AdminPage.tsx — "End Current Series" button on Current Series tab

  • Button labeled "End Current Series & Start New Book"
  • Opens an inline confirmation + new series form (not a browser confirm() — a proper UI panel)
  • On confirm: creates FinishedBook from current series data, clears podcastFeaturedLinks, updates series fields with new book info
  • All saved as one atomic content update

8. src/AdminPage.tsx — Finished Books management section

Separate tab for viewing and managing the finished books list (in case you need to edit or remove one):

  • Shows each finished book as a collapsible card
  • Edit title, description, image, season number
  • Remove button
  • "Add Finished Book" button for manually adding one without going through the transition wizard

9. CSS — Playlist and finished books grid styles

Reuse existing episode-list and episode-card patterns where possible.

10. src/App.tsx — Downloads page copy tweak (Option A, low priority)

Minor label changes only — no structural change:

  • Eyebrow becomes "Current Study"
  • Heading becomes {content.seriesTitle} — Study Guide
  • Everything else (download form, Amazon button, library) stays identical

Prerequisite — Verify RSS Season Tags

Before starting, confirm Titus episodes have <itunes:season> tags and what number they use:

curl -s https://anchor.fm/s/11068d290/podcast/rss | grep -o '<itunes:season>[^<]*</itunes:season>' | head -5

If seasons are missing from the RSS, the fallback is an episodeRange: { from, to } field on FinishedBook instead of season.


Files Touched Summary

File What changes
server/routes/episodes.js Parse itunes:season into episode objects
src/content.ts New FinishedBook interface + finishedBooks field on SiteContent
src/App.tsx New hook, nav links, 2 routes, 2 page components, Downloads copy tweak
src/AdminPage.tsx "End Current Series" transition button + Finished Books management tab
CSS Playlist page + finished books grid styles

Order of Work

  1. Run the RSS curl check to confirm season tags (5 min)
  2. content.ts — add FinishedBook type and finishedBooks field
  3. server/routes/episodes.js — parse season from RSS
  4. Admin — "End Current Series" transition button + Finished Books management tab
  5. Public pages — nav link, routes, FinishedBooksPage, FinishedSeriesPage
  6. Downloads copy tweak (last, lowest priority)