- 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>
7.5 KiB
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)
- User clicks Finished Books in the nav
- Lands on
/finished— grid of completed book cards (image, title, description) - Clicks a book →
/finished/:id— sees all episodes in numbered order, each with a player - 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
- Creates a Finished Book entry from the current series data (title, image, description, season number) — immediately appears on
/finished - Clears Episode Highlights — wipes
podcastFeaturedLinksso the old season's picks are gone - 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 3–5 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))
4. src/App.tsx — Nav: "Episodes" dropdown replacing flat link
"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-withinon 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-heightanimation - "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
useStatetoggle on the dropdown item — consistent with howmenuOpenalready works - Sub-items get slightly smaller text and left padding to show hierarchy
- The
max-height: 500pxon.header-nav--openwill need to increase to600pxto accommodate the expanded sub-items
New elements needed:
NavDropdowncomponent (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
FinishedBookfrom current series data, clearspodcastFeaturedLinks, 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
- Run the RSS curl check to confirm season tags (5 min)
content.ts— addFinishedBooktype andfinishedBooksfieldserver/routes/episodes.js— parse season from RSS- Admin — "End Current Series" transition button + Finished Books management tab
- Public pages — nav link, routes,
FinishedBooksPage,FinishedSeriesPage - Downloads copy tweak (last, lowest priority)