diff --git a/package.json b/package.json index 0d09a5f..53fc146 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "siteforge", "private": true, - "version": "2.9.1", + "version": "2.9.2", "type": "module", "scripts": { "dev": "vite", diff --git a/src/AdminPage.tsx b/src/AdminPage.tsx index 115f434..8e902ac 100644 --- a/src/AdminPage.tsx +++ b/src/AdminPage.tsx @@ -858,6 +858,18 @@ const ADMIN_SECTION_LINKS: Partial> = { { id: 'global-banner', label: 'Analytics Banner' }, { id: 'global-welcome-email', label: 'Welcome Email' }, ], + 'start-here': [ + { id: 'start-here-intro', label: 'Intro' }, + { id: 'start-here-step-1', label: 'Step 1' }, + { id: 'start-here-step-2', label: 'Step 2' }, + { id: 'start-here-step-3', label: 'Step 3' }, + ], + 'custom-links': [ + { id: 'custom-links-homepage', label: 'Homepage External' }, + { id: 'custom-links-platform', label: 'Platform + Footer' }, + { id: 'custom-links-other', label: 'Other Sites' }, + { id: 'custom-links-more', label: 'More Resources' }, + ], seo: [ { id: 'seo-metadata', label: 'Metadata' }, { id: 'seo-redirects', label: 'Redirects' }, @@ -2607,31 +2619,35 @@ const [dashboardNow, setDashboardNow] = useState(() => new Date()) ) } - function jumpToAdminSection(sectionId: string) { - document.getElementById(sectionId)?.scrollIntoView({ behavior: 'smooth', block: 'start' }) + // Tabs inside long admin pages. The chosen tab is remembered per page while the admin is open. + const [adminTab, setAdminTab] = useState>({}) + const tabOf = (view: string, first: string) => adminTab[view] ?? first + + function renderTabs(view: string, first: string, tabs: { id: string; label: string }[]) { + const current = tabOf(view, first) + return ( +
+ {tabs.map(tab => ( + + ))} +
+ ) } + // Sections of long pages are shown as tabs instead of one long scroll. function renderSectionJumpNav(view: AdminView) { const links = ADMIN_SECTION_LINKS[view] ?? [] if (links.length < 2) return null - - return ( -
- Jump to -
- {links.map(link => ( - - ))} -
-
- ) + return renderTabs(view, links[0].id, links) } function addLink() { @@ -3464,7 +3480,9 @@ const [dashboardNow, setDashboardNow] = useState(() => new Date())

Dashboard

Quick overview of your ministry site.

+ {renderTabs('dashboard', 'overview', [{ id: 'overview', label: 'Overview' }, { id: 'activity', label: 'Recent Activity' }, { id: 'users', label: 'Study Users' }])} + {tabOf('dashboard', 'overview') === 'overview' && <>

{welcomeMessage}

@@ -3533,6 +3551,8 @@ const [dashboardNow, setDashboardNow] = useState(() => new Date()) )}
+ } + {tabOf('dashboard', 'overview') === 'activity' && <>

Recent Contacts

@@ -3578,6 +3598,8 @@ const [dashboardNow, setDashboardNow] = useState(() => new Date())
+ } + {tabOf('dashboard', 'overview') === 'users' && <>

Study Users & Enrollments

@@ -3603,6 +3625,7 @@ const [dashboardNow, setDashboardNow] = useState(() => new Date()) }
+ } ) })()} @@ -3889,6 +3912,7 @@ const [dashboardNow, setDashboardNow] = useState(() => new Date()) {renderSectionJumpNav('start-here')} + {tabOf('start-here', 'start-here-intro') === 'start-here-intro' && <>

Intro

Top-level heading and opening copy for the page.

@@ -3902,6 +3926,8 @@ const [dashboardNow, setDashboardNow] = useState(() => new Date())
))} + } + {tabOf('start-here', 'start-here-intro') === 'start-here-step-1' && <>

Step 1

@@ -3914,6 +3940,8 @@ const [dashboardNow, setDashboardNow] = useState(() => new Date())
))} + } + {tabOf('start-here', 'start-here-intro') === 'start-here-step-2' && <>

Step 2

@@ -3926,6 +3954,8 @@ const [dashboardNow, setDashboardNow] = useState(() => new Date()) ))} + } + {tabOf('start-here', 'start-here-intro') === 'start-here-step-3' && <>

Step 3

@@ -3937,6 +3967,7 @@ const [dashboardNow, setDashboardNow] = useState(() => new Date()) : handleChange(key, e.target.value)} />} ))} + } {renderSaveStatus()} )} @@ -4088,6 +4119,13 @@ const [dashboardNow, setDashboardNow] = useState(() => new Date())

Current Series

Update the active series card shown on the homepage and episodes page.

+ {renderTabs('series', 'main', [ + { id: 'main', label: 'Main Series' }, + { id: 'second', label: 'Second Series' }, + { id: 'schedule', label: 'Launch: Main' }, + { id: 'schedule2', label: 'Launch: Second' }, + ])} + {tabOf('series', 'main') === 'main' && <> {FIELDS.filter(f => f.section === 'series' && !['studyGuideTitle', 'studyGuideDescription', 'studyGuideUrl'].includes(f.key)).map(({ key, label, multiline }) => (
@@ -4116,7 +4154,9 @@ const [dashboardNow, setDashboardNow] = useState(() => new Date())

Must match the season number you set on these episodes in Spotify for Creators. Used for the Finished Books playlist.

- {(() => { + } + + {tabOf('series', 'main') === 'second' && (() => { const second = form.secondSeries const patchSecond = (p: Partial) => setForm(f => ({ ...f, secondSeries: { label: 'Now Playing', title: '', description: '', imageUrl: '', listenUrl: '', listenBtnLabel: '', ...(f.secondSeries ?? {}), ...p } })) @@ -4195,7 +4235,7 @@ const [dashboardNow, setDashboardNow] = useState(() => new Date()) })()}

Companion study guide title, description, and Amazon link are now managed in Resources.

{renderSaveStatus()} -
+ {tabOf('series', 'main') === 'main' &&

{!endSeriesOpen ? (
)} - + } {/* Schedule Auto-Transition */} - {(() => { + {tabOf('series', 'main') === 'schedule' && (() => { const sched = form.scheduledSeriesTransition const patch = (p: Partial>) => setForm(f => ({ ...f, scheduledSeriesTransition: { ...(f.scheduledSeriesTransition ?? { triggersAt: '', newTitle: '', newSeason: 2 }), ...p } })) @@ -4413,7 +4453,7 @@ const [dashboardNow, setDashboardNow] = useState(() => new Date()) })()} {/* Schedule Auto-Transition — second series (Lane B) */} - {(() => { + {tabOf('series', 'main') === 'schedule2' && (() => { const sched = form.scheduledSecondSeriesTransition const patch = (p: Partial>) => setForm(f => ({ ...f, scheduledSecondSeriesTransition: { ...(f.scheduledSecondSeriesTransition ?? { triggersAt: '', newTitle: '', newSeason: 2 }), ...p } })) @@ -4703,6 +4743,7 @@ const [dashboardNow, setDashboardNow] = useState(() => new Date())

Current Downloads

{resourceLinks.length}

+ {tabOf('downloads', 'downloads-study-guide') === 'downloads-study-guide' && <>

Companion Study Guide

This is the featured primary download at the top of the Resources page.

@@ -4742,6 +4783,8 @@ const [dashboardNow, setDashboardNow] = useState(() => new Date())
+ } + {tabOf('downloads', 'downloads-study-guide') === 'downloads-library' && <> {/* Download Library — master-detail */} {(() => { const activeRes = selectedResourceId ? resourceLinks.find(l => l.id === selectedResourceId) ?? null : null @@ -4831,6 +4874,7 @@ const [dashboardNow, setDashboardNow] = useState(() => new Date()) ) })()} + } {renderSaveStatus()} )} @@ -4843,6 +4887,8 @@ const [dashboardNow, setDashboardNow] = useState(() => new Date())

Add links to show in platform buttons, footer navigation, or the More Resources download library.

+ {renderSectionJumpNav('custom-links')} + {tabOf('custom-links', 'custom-links-homepage') === 'custom-links-homepage' && <>

Homepage External Links

Links shown on the homepage, separate from the footer.

@@ -4890,6 +4936,8 @@ const [dashboardNow, setDashboardNow] = useState(() => new Date()) ))} + } + {tabOf('custom-links', 'custom-links-homepage') === 'custom-links-platform' && <>

Platform + Footer Links

Links that appear on the listen buttons row or footer nav.

@@ -4941,6 +4989,8 @@ const [dashboardNow, setDashboardNow] = useState(() => new Date()) ))} + } + {tabOf('custom-links', 'custom-links-homepage') === 'custom-links-other' && <>

Other Site Links

External links you want to surface in the footer dropdown.

@@ -4984,6 +5034,8 @@ const [dashboardNow, setDashboardNow] = useState(() => new Date()) ))} + } + {tabOf('custom-links', 'custom-links-homepage') === 'custom-links-more' && <>

More Resources Links

These create cards in Resources → More guides, worksheets, and past study downloads.

@@ -5046,6 +5098,7 @@ const [dashboardNow, setDashboardNow] = useState(() => new Date())
))} + } {renderSaveStatus()} )} @@ -5241,8 +5294,10 @@ const [dashboardNow, setDashboardNow] = useState(() => new Date())

{activeStudy.title || 'Untitled Study'}

+ {renderTabs('study', 'lessons', [{ id: 'lessons', label: 'Lessons' }, { id: 'settings', label: 'Study Settings' }])} -
+ {tabOf('study', 'lessons') === 'settings' && <> +
Study Settings

Title, slug, description, and homepage display

Expand to edit @@ -5316,6 +5371,8 @@ const [dashboardNow, setDashboardNow] = useState(() => new Date())
+ } + {tabOf('study', 'lessons') === 'lessons' && <>

Lessons ({(activeStudy.sections ?? []).length})

@@ -5407,6 +5464,7 @@ const [dashboardNow, setDashboardNow] = useState(() => new Date()) + } {renderSaveStatus()} )} @@ -5530,6 +5588,7 @@ const [dashboardNow, setDashboardNow] = useState(() => new Date()) {renderSectionJumpNav('global')} + {tabOf('global', 'global-footer') === 'global-footer' && <> {FIELDS.filter(f => f.section === 'global' && ['footerTitle','footerSubtitle','footerEmail','footerCopyright','footerPrivacyNote'].includes(f.key)).map(({ key, label, multiline }) => (
@@ -5546,6 +5605,8 @@ const [dashboardNow, setDashboardNow] = useState(() => new Date())
))} + } + {tabOf('global', 'global-footer') === 'global-header' && <> {FIELDS.filter(f => f.section === 'global' && f.key === 'headerFollowLabel').map(({ key, label }) => (
@@ -5554,6 +5615,8 @@ const [dashboardNow, setDashboardNow] = useState(() => new Date())
))} + } + {tabOf('global', 'global-footer') === 'global-platforms' && <>

Platform URLs

{FIELDS.filter(f => f.section === 'global' && f.key.startsWith('platform')).map(({ key, label }) => (
@@ -5562,6 +5625,8 @@ const [dashboardNow, setDashboardNow] = useState(() => new Date())
))} + } + {tabOf('global', 'global-footer') === 'global-banner' && <>

Analytics Banner

{FIELDS.filter(f => f.section === 'global' && f.key === 'cookieBannerText').map(({ key, label }) => (
@@ -5570,6 +5635,8 @@ const [dashboardNow, setDashboardNow] = useState(() => new Date())
))} + } + {tabOf('global', 'global-footer') === 'global-welcome-email' && <>

Welcome Email

{FIELDS.filter(f => f.section === 'global' && f.key.startsWith('welcomeEmail')).map(({ key, label, multiline }) => (
@@ -5580,6 +5647,7 @@ const [dashboardNow, setDashboardNow] = useState(() => new Date())
))} + } {renderSaveStatus()} )} @@ -6307,6 +6375,7 @@ const [dashboardNow, setDashboardNow] = useState(() => new Date()) {renderSectionJumpNav('seo')} + {tabOf('seo', 'seo-metadata') === 'seo-metadata' && <>

Metadata

Search title, descriptions, Open Graph image, canonical URL, robots policy, and sitemap paths.

@@ -6330,6 +6399,8 @@ const [dashboardNow, setDashboardNow] = useState(() => new Date())
+ } + {tabOf('seo', 'seo-metadata') === 'seo-redirects' && <>

Redirect Manager

Maintain first-party short links like /spotify without code changes.

@@ -6351,6 +6422,7 @@ const [dashboardNow, setDashboardNow] = useState(() => new Date())
))} + } {renderSaveStatus()} )} @@ -6365,6 +6437,7 @@ const [dashboardNow, setDashboardNow] = useState(() => new Date()) {renderSectionJumpNav('legal')} + {tabOf('legal', 'legal-privacy') === 'legal-privacy' && <> + } + {tabOf('legal', 'legal-privacy') === 'legal-terms' && <> + } {renderSaveStatus()} )} diff --git a/src/App.css b/src/App.css index c998a43..08d3d2c 100644 --- a/src/App.css +++ b/src/App.css @@ -13135,3 +13135,6 @@ border-bottom: 1px solid rgba(201, 168, 76, 0.22); padding-bottom: 0.5rem; } + +.admin-tabs--page { margin: 0 0 1.5rem; padding-bottom: 1rem; border-bottom: 1px solid #2a2518; } +.admin-tabs--page .admin-tab { font-size: 0.8rem; padding: 0.45rem 0.9rem; }