Add Tier 2 email features: AI draft, Calendar→Email announce, delivery status, engagement badges; v1.1.24

- EmailPage: useLocation to accept pre-filled compose from Calendar; delivery status pill on sent bubbles; AI Draft button calling ANTHROPIC_API_KEY endpoint
- CalendarPage: useNavigate + announceEpisode(); ✉ Announce button in episode edit popover
- ContactsPage: emailStatus types; unreplied badge; best delivery status pill; engagement score stars
- server/routes/contact.js: POST /api/admin-contact-submissions/:id/draft-reply → Anthropic claude-haiku-4-5

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
nmemmert
2026-07-28 17:10:25 -04:00
parent e2c560a1ab
commit bf16deea92
6 changed files with 207 additions and 7 deletions
+14 -1
View File
@@ -1,5 +1,5 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Link } from 'react-router-dom'
import { Link, useNavigate } from 'react-router-dom'
// ── Types ────────────────────────────────────────────────────────────────────
@@ -248,6 +248,7 @@ export default function CalendarShell() {
// ── Calendar Client ───────────────────────────────────────────────────────────
function CalendarClient() {
const navigate = useNavigate()
const today = useMemo(() => new Date(), [])
const [checklist, setChecklist] = useState<PodcastChecklistData | null>(null)
const [loading, setLoading] = useState(true)
@@ -433,6 +434,17 @@ function CalendarClient() {
setSelectedEpisodeId(null)
}
function announceEpisode(ep: PodcastChecklistEpisode) {
const parts = [ep.series, ep.episodeNumber ? `Episode ${ep.episodeNumber}` : null, ep.title].filter(Boolean)
const subject = `New Episode: ${parts.join(' ')}`
const dateStr = ep.datePublished
? new Date(ep.datePublished + 'T12:00:00').toLocaleDateString('en-US', { weekday: 'long', month: 'long', day: 'numeric' })
: ''
const body = [parts.join(' '), dateStr ? `Published ${dateStr}` : ''].filter(Boolean).join('\n\n')
setEditEp(null)
navigate('/email', { state: { compose: true, subject, body } })
}
function openEdit(ep: PodcastChecklistEpisode) {
setEditEp(ep)
setEditForm({
@@ -920,6 +932,7 @@ function CalendarClient() {
</div>
<div className="cal-popover-actions">
<button type="button" className="em-btn em-btn--primary em-btn--sm" onClick={saveEdit}>Save</button>
<button type="button" className="em-btn em-btn--ghost em-btn--sm" onClick={() => announceEpisode(editEp)} title="Open email compose pre-filled to announce this episode"> Announce</button>
<button type="button" className="em-btn em-btn--ghost em-btn--sm" onClick={() => unschedule(editEp)}>Unschedule</button>
<button type="button" className="em-btn em-btn--ghost em-btn--sm" onClick={() => setEditEp(null)}>Cancel</button>
</div>