admin redesign: topbar state zone, podcast hub tabs, AnalyticsPanel CSS classes, useAutosave
This commit is contained in:
+41
-17
@@ -7,6 +7,7 @@ import { Link } from 'react-router-dom'
|
||||
import type { SiteContent, CustomLink, CustomBlock, ArchivedSeries, ArchivedSeriesResourceLink, ArchivedSeriesNote, ColossiansStudySection, StudyProgram, RedirectRule, PodcastFeaturedLink, SeoSettings, LegalSettings } from './content'
|
||||
import { DEFAULTS } from './content'
|
||||
import { AnalyticsPanel } from './components/AnalyticsPanel'
|
||||
import { useAutosave } from './hooks/useAutosave'
|
||||
|
||||
interface SortableLessonSectionProps {
|
||||
section: ColossiansStudySection
|
||||
@@ -295,12 +296,14 @@ type StringField = Exclude<keyof SiteContent, 'customLinks' | 'customBlocks' | '
|
||||
|
||||
type AdminView =
|
||||
| 'dashboard' | 'homepage' | 'start-here' | 'about' | 'contact'
|
||||
| 'current-series' | 'episode-highlights' | 'podcast-checklist' | 'archived-series'
|
||||
| 'podcast' | 'current-series' | 'episode-highlights' | 'podcast-checklist' | 'archived-series'
|
||||
| 'downloads' | 'custom-links' | 'content-blocks'
|
||||
| 'questions' | 'analytics' | 'assets' | 'colossians-study'
|
||||
| 'emails' | 'subscribers' | 'contacts'
|
||||
| 'seo' | 'legal' | 'security' | 'brand' | 'global'
|
||||
|
||||
type PodcastTab = 'current-series' | 'episode-highlights' | 'podcast-checklist' | 'archived-series'
|
||||
|
||||
type MainContentSection = 'hero' | 'start-here' | 'about' | 'contact' | 'series' | 'share' | 'prism' | 'global'
|
||||
|
||||
const BRAND_KIT_SWATCHES = [
|
||||
@@ -499,6 +502,7 @@ export default function AdminPage({ content, onSave, onLogout }: Props) {
|
||||
const [status, setStatus] = useState<'idle' | 'saving' | 'saved' | 'error'>('idle')
|
||||
const [errorMsg, setErrorMsg] = useState('')
|
||||
const [adminView, setAdminView] = useState<AdminView>('dashboard')
|
||||
const [podcastTab, setPodcastTab] = useState<PodcastTab>('current-series')
|
||||
const [stats, setStats] = useState<AdminStats | null>(null)
|
||||
const [statsStatus, setStatsStatus] = useState<'loading' | 'ready' | 'error'>('loading')
|
||||
const [maintenanceMsg, setMaintenanceMsg] = useState('')
|
||||
@@ -565,6 +569,9 @@ export default function AdminPage({ content, onSave, onLogout }: Props) {
|
||||
const unreadEmailCount = contactSubmissions.filter(s => !s.archived).length
|
||||
const unansweredCount = questions.filter(q => !q.answer?.trim()).length
|
||||
|
||||
// Silently saves draft every 30s when there are unsaved changes
|
||||
useAutosave(form, isDirty, handleSaveDraft)
|
||||
|
||||
// Broadcast live form state + active view into the preview iframe whenever they change
|
||||
useEffect(() => {
|
||||
if (!previewOpen) return
|
||||
@@ -2193,12 +2200,7 @@ export default function AdminPage({ content, onSave, onLogout }: Props) {
|
||||
<div className="admin-shell">
|
||||
{/* ── Top bar ── */}
|
||||
<header className="admin-topbar">
|
||||
<div className="admin-topbar-brand">
|
||||
<span className="admin-topbar-ornament">✦</span>
|
||||
<span className="admin-topbar-title">Site Admin</span>
|
||||
<span className="admin-topbar-sub">Verse by Verse with Nate</span>
|
||||
</div>
|
||||
<div className="admin-topbar-actions">
|
||||
<div className="admin-topbar-left">
|
||||
<button
|
||||
type="button"
|
||||
className={`admin-mobile-menu-btn${mobileNavOpen ? ' admin-mobile-menu-btn--open' : ''}`}
|
||||
@@ -2206,11 +2208,20 @@ export default function AdminPage({ content, onSave, onLogout }: Props) {
|
||||
aria-expanded={mobileNavOpen}
|
||||
aria-controls="admin-sidebar-nav"
|
||||
>
|
||||
{mobileNavOpen ? 'Close Menu' : 'Menu'}
|
||||
{mobileNavOpen ? 'Close' : 'Menu'}
|
||||
</button>
|
||||
<div className="admin-topbar-brand">
|
||||
<span className="admin-topbar-ornament">✦</span>
|
||||
<span className="admin-topbar-title">Site Admin</span>
|
||||
<span className="admin-topbar-sub">Verse by Verse with Nate</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="admin-topbar-state">
|
||||
{isDirty && <span className="admin-status admin-status--warn">● Unsaved</span>}
|
||||
<span className="admin-topbar-meta">Draft: {formatDate(publishState.draftUpdatedAt)}</span>
|
||||
<span className="admin-topbar-meta">Published: {formatDate(publishState.publishedAt)}</span>
|
||||
<span className="admin-topbar-meta">Live: {formatDate(publishState.publishedAt)}</span>
|
||||
</div>
|
||||
<div className="admin-topbar-actions">
|
||||
<button type="button" className="btn-admin-save" onClick={handleSaveDraft} disabled={status === 'saving' || !isDirty}>
|
||||
{status === 'saving' ? 'Saving…' : 'Save Draft'}
|
||||
</button>
|
||||
@@ -2263,10 +2274,7 @@ export default function AdminPage({ content, onSave, onLogout }: Props) {
|
||||
|
||||
<div className="admin-nav-group">
|
||||
<span className="admin-nav-label">Podcast</span>
|
||||
<button type="button" className={`admin-nav-item${adminView === 'current-series' ? ' admin-nav-item--active' : ''}`} onClick={() => navigateTo('current-series')}>Current Series</button>
|
||||
<button type="button" className={`admin-nav-item${adminView === 'episode-highlights' ? ' admin-nav-item--active' : ''}`} onClick={() => navigateTo('episode-highlights')}>Ep. Highlights</button>
|
||||
<button type="button" className={`admin-nav-item${adminView === 'podcast-checklist' ? ' admin-nav-item--active' : ''}`} onClick={() => navigateTo('podcast-checklist')}>Production Checklist</button>
|
||||
<button type="button" className={`admin-nav-item${adminView === 'archived-series' ? ' admin-nav-item--active' : ''}`} onClick={() => navigateTo('archived-series')}>Archived Series</button>
|
||||
<button type="button" className={`admin-nav-item${adminView === 'podcast' ? ' admin-nav-item--active' : ''}`} onClick={() => navigateTo('podcast')}>Podcast Hub</button>
|
||||
</div>
|
||||
|
||||
<div className="admin-nav-group">
|
||||
@@ -2756,8 +2764,24 @@ export default function AdminPage({ content, onSave, onLogout }: Props) {
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* PODCAST HUB */}
|
||||
{adminView === 'podcast' && (
|
||||
<section className="admin-panel-section">
|
||||
<div className="admin-panel-head">
|
||||
<h2>Podcast Hub</h2>
|
||||
<p>Manage all podcast-related content from one place.</p>
|
||||
</div>
|
||||
<div className="admin-tabs admin-tabs--podcast">
|
||||
<button type="button" className={`admin-tab${podcastTab === 'current-series' ? ' admin-tab--active' : ''}`} onClick={() => setPodcastTab('current-series')}>Current Series</button>
|
||||
<button type="button" className={`admin-tab${podcastTab === 'episode-highlights' ? ' admin-tab--active' : ''}`} onClick={() => setPodcastTab('episode-highlights')}>Ep. Highlights</button>
|
||||
<button type="button" className={`admin-tab${podcastTab === 'podcast-checklist' ? ' admin-tab--active' : ''}`} onClick={() => setPodcastTab('podcast-checklist')}>Production Checklist</button>
|
||||
<button type="button" className={`admin-tab${podcastTab === 'archived-series' ? ' admin-tab--active' : ''}`} onClick={() => setPodcastTab('archived-series')}>Archived Series</button>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* CURRENT SERIES */}
|
||||
{adminView === 'current-series' && (
|
||||
{(adminView === 'current-series' || (adminView === 'podcast' && podcastTab === 'current-series')) && (
|
||||
<section className="admin-panel-section">
|
||||
<div className="admin-panel-head">
|
||||
<h2>Current Series</h2>
|
||||
@@ -2782,7 +2806,7 @@ export default function AdminPage({ content, onSave, onLogout }: Props) {
|
||||
)}
|
||||
|
||||
{/* EPISODE HIGHLIGHTS */}
|
||||
{adminView === 'episode-highlights' && (
|
||||
{(adminView === 'episode-highlights' || (adminView === 'podcast' && podcastTab === 'episode-highlights')) && (
|
||||
<section className="admin-panel-section" aria-label="Episode highlights">
|
||||
<div className="admin-panel-head">
|
||||
<h2>Episode Highlights</h2>
|
||||
@@ -2843,7 +2867,7 @@ export default function AdminPage({ content, onSave, onLogout }: Props) {
|
||||
)}
|
||||
|
||||
{/* PODCAST CHECKLIST */}
|
||||
{adminView === 'podcast-checklist' && (
|
||||
{(adminView === 'podcast-checklist' || (adminView === 'podcast' && podcastTab === 'podcast-checklist')) && (
|
||||
<section className="admin-panel-section" aria-label="Podcast production checklist">
|
||||
<div className="admin-panel-head">
|
||||
<h2>Podcast Production Checklist</h2>
|
||||
@@ -3055,7 +3079,7 @@ export default function AdminPage({ content, onSave, onLogout }: Props) {
|
||||
)}
|
||||
|
||||
{/* ARCHIVED SERIES */}
|
||||
{adminView === 'archived-series' && (
|
||||
{(adminView === 'archived-series' || (adminView === 'podcast' && podcastTab === 'archived-series')) && (
|
||||
<section className="admin-panel-section">
|
||||
<div className="admin-panel-head">
|
||||
<h2>Archived Series</h2>
|
||||
|
||||
Reference in New Issue
Block a user