Rename Downloads→Resources throughout, fix audit bugs, add /subscribe to footer; v1.1.49
Publish Container / docker (push) Successful in 14m2s

- Rename all user-facing "Downloads" text to "Resources" (nav, page eyebrows, buttons, admin labels, page title, content defaults)
- Add usePageMeta to ContactPage, EpisodeDetailPage, DownloadDetailPage, StartHerePage for correct social share previews
- Wire contactEyebrow and aboutShowEyebrow fields to the UI (were editable in admin but never rendered)
- Wrap StartHerePage in full site chrome (header, search, footer); fix Step 2 link from /#qa to /questions
- Fix admin "View All Contacts" link (was 404); now navigates to Contact admin panel
- Remove broken "Previous Studies" jump nav entry (no matching anchor)
- Remove /studys typo route
- Make dashboard "Top Resource Downloads" stat dynamic instead of hardcoded to titus-study key
- Add Newsletter link (/subscribe) to footer legal nav
- Fix server.js startup log ("Portfolio app" → "Siteforge")

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 4.6 committed 2026-09-03 08:37:23 -04:00
1 parent 0d2e68ae11
commit a072076818
7 files changed
+123 -65

No files matched your search

+32 -3
View File
@@ -5,12 +5,24 @@
"50": 4, "50": 4,
"75": 2, "75": 2,
"90": 0 "90": 0
},
"/": {
"25": 2,
"50": 2,
"75": 2,
"90": 2
},
"/about": {
"25": 6,
"50": 6,
"75": 6,
"90": 6
} }
}, },
"timeOnPage": { "timeOnPage": {
"/": { "/": {
"totalSeconds": 51, "totalSeconds": 64,
"count": 4 "count": 5
}, },
"/episodes": { "/episodes": {
"totalSeconds": 318967, "totalSeconds": 318967,
@@ -19,10 +31,27 @@
"/episodes/movn6tk3": { "/episodes/movn6tk3": {
"totalSeconds": 445, "totalSeconds": 445,
"count": 3 "count": 3
},
"/about": {
"totalSeconds": 63497,
"count": 128
} }
}, },
"outboundClicks": {}, "outboundClicks": {},
"linkClicks": {}, "linkClicks": {
"https://ko-fi.com/versebyversewithnate": {
"count": 1,
"internal": false
},
"/": {
"count": 1,
"internal": true
},
"/about": {
"count": 1,
"internal": true
}
},
"utmSources": {}, "utmSources": {},
"searchQueries": {}, "searchQueries": {},
"notFound": {}, "notFound": {},
+1 -1
View File
@@ -1,4 +1,4 @@
{ {
"users": {}, "users": {},
"updatedAt": "2026-08-26T15:41:55.120Z" "updatedAt": "2026-09-02T17:00:36.458Z"
} }
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "siteforge", "name": "siteforge",
"private": true, "private": true,
"version": "1.1.48", "version": "1.1.49",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
+1 -1
View File
@@ -196,6 +196,6 @@ Promise.all([
app.listen(PORT, () => { app.listen(PORT, () => {
logResendEmailAlignmentWarnings() logResendEmailAlignmentWarnings()
console.log(`Portfolio app listening on http://localhost:${PORT}`) console.log(`Siteforge listening on http://localhost:${PORT}`)
}) })
}) })
+16 -17
View File
@@ -728,7 +728,7 @@ const ADMIN_VIEW_OPTIONS: Array<{ group: string; options: Array<{ value: AdminVi
options: [ options: [
{ value: 'podcast', label: 'Podcast Hub' }, { value: 'podcast', label: 'Podcast Hub' },
{ value: 'colossians-study', label: 'Studies' }, { value: 'colossians-study', label: 'Studies' },
{ value: 'downloads', label: 'Downloads' }, { value: 'downloads', label: 'Resources' },
{ value: 'custom-links', label: 'Custom Links' }, { value: 'custom-links', label: 'Custom Links' },
{ value: 'content-blocks', label: 'Content Blocks' }, { value: 'content-blocks', label: 'Content Blocks' },
{ value: 'books', label: 'Books' }, { value: 'books', label: 'Books' },
@@ -798,7 +798,6 @@ const ADMIN_SECTION_LINKS: Partial<Record<AdminView, AdminSectionLink[]>> = {
downloads: [ downloads: [
{ id: 'downloads-study-guide', label: 'Study Guide' }, { id: 'downloads-study-guide', label: 'Study Guide' },
{ id: 'downloads-library', label: 'Download Library' }, { id: 'downloads-library', label: 'Download Library' },
{ id: 'downloads-previous-studies', label: 'Previous Studies' },
], ],
global: [ global: [
{ id: 'global-footer', label: 'Footer' }, { id: 'global-footer', label: 'Footer' },
@@ -903,7 +902,7 @@ const FIELDS: Array<{ key: StringField; label: string; multiline?: boolean; sect
{ key: 'seriesImageUrl', label: 'Series Cover Image URL', section: 'series' }, { key: 'seriesImageUrl', label: 'Series Cover Image URL', section: 'series' },
{ key: 'seriesListenUrl', label: 'Series Listen URL', section: 'series' }, { key: 'seriesListenUrl', label: 'Series Listen URL', section: 'series' },
{ key: 'seriesListenBtnLabel', label: 'Series — Listen Button Label', section: 'series' }, { key: 'seriesListenBtnLabel', label: 'Series — Listen Button Label', section: 'series' },
{ key: 'seriesDownloadsBtnLabel', label: 'Series — Downloads Button Label', section: 'series' }, { key: 'seriesDownloadsBtnLabel', label: 'Series — Resources Button Label', section: 'series' },
{ key: 'studyGuideTitle', label: 'Study Guide Title', section: 'series' }, { key: 'studyGuideTitle', label: 'Study Guide Title', section: 'series' },
{ key: 'studyGuideDescription', label: 'Study Guide Description', multiline: true, section: 'series' }, { key: 'studyGuideDescription', label: 'Study Guide Description', multiline: true, section: 'series' },
{ key: 'studyGuideUrl', label: 'Study Guide URL (Amazon link)', section: 'series' }, { key: 'studyGuideUrl', label: 'Study Guide URL (Amazon link)', section: 'series' },
@@ -2000,9 +1999,9 @@ async function handleAssetUpload(event: ChangeEvent<HTMLInputElement>) {
if (placement === 'footer') return 'Footer navigation links' if (placement === 'footer') return 'Footer navigation links'
if (placement === 'otherSites') return 'Other site links in footer dropdown' if (placement === 'otherSites') return 'Other site links in footer dropdown'
if (placement === 'externalSites') return 'Homepage external site links' if (placement === 'externalSites') return 'Homepage external site links'
if (placement === 'books') return 'Downloads page (Reading List)' if (placement === 'books') return 'Resources page (Reading List)'
if (placement === 'recommended') return 'Downloads page (Recommended Resources)' if (placement === 'recommended') return 'Resources page (Recommended Resources)'
return 'Downloads page (More Resources)' return 'Resources page (More Resources)'
} }
function getCustomLinkPreviewPath(placement: CustomLink['placement']) { function getCustomLinkPreviewPath(placement: CustomLink['placement']) {
@@ -2016,7 +2015,7 @@ async function handleAssetUpload(event: ChangeEvent<HTMLInputElement>) {
if (page === 'homepage') return 'Homepage' if (page === 'homepage') return 'Homepage'
if (page === 'start-here') return 'Start Here' if (page === 'start-here') return 'Start Here'
if (page === 'episodes') return 'Episodes' if (page === 'episodes') return 'Episodes'
if (page === 'downloads') return 'Downloads' if (page === 'downloads') return 'Resources'
if (page === 'about') return 'About' if (page === 'about') return 'About'
if (page === 'contact') return 'Contact' if (page === 'contact') return 'Contact'
return 'Q&A' return 'Q&A'
@@ -2832,8 +2831,8 @@ async function handleAssetUpload(event: ChangeEvent<HTMLInputElement>) {
</div> </div>
</div> </div>
<div className="admin-dashboard-card"> <div className="admin-dashboard-card">
<div className="admin-dashboard-card-value">{downloadStats['titus-study'] ?? 0}</div> <div className="admin-dashboard-card-value">{Math.max(0, ...Object.values(downloadStats))}</div>
<div className="admin-dashboard-card-label">Titus Study Downloads</div> <div className="admin-dashboard-card-label">Top Resource Downloads</div>
<div className="admin-dashboard-card-sub"> <div className="admin-dashboard-card-sub">
{Object.values(downloadStats).reduce((a, b) => a + b, 0)} total resource downloads {Object.values(downloadStats).reduce((a, b) => a + b, 0)} total resource downloads
</div> </div>
@@ -2864,7 +2863,7 @@ async function handleAssetUpload(event: ChangeEvent<HTMLInputElement>) {
{c.message && <div className="admin-dashboard-feed-preview">{c.message.slice(0, 100)}{c.message.length > 100 ? '…' : ''}</div>} {c.message && <div className="admin-dashboard-feed-preview">{c.message.slice(0, 100)}{c.message.length > 100 ? '…' : ''}</div>}
</div> </div>
))} ))}
<Link to="/contacts" className="admin-dashboard-card-action" style={{ marginTop: '0.5rem', display: 'inline-block' }}>View All Contacts →</Link> <button type="button" className="admin-dashboard-card-action" style={{ marginTop: '0.5rem', background: 'none', border: 'none', cursor: 'pointer', padding: 0 }} onClick={() => setAdminView('contact')}>View All Contacts →</button>
</div> </div>
) )
} }
@@ -3341,7 +3340,7 @@ async function handleAssetUpload(event: ChangeEvent<HTMLInputElement>) {
)} )}
</div> </div>
))} ))}
<p className="admin-stats-note">Companion study guide title, description, and Amazon link are now managed in Downloads.</p> <p className="admin-stats-note">Companion study guide title, description, and Amazon link are now managed in Resources.</p>
{renderSaveStatus()} {renderSaveStatus()}
<div className="admin-end-series-wrap"> <div className="admin-end-series-wrap">
<hr style={{ border: 'none', borderTop: '1px solid #2a2518', margin: '2rem 0 1.5rem' }} /> <hr style={{ border: 'none', borderTop: '1px solid #2a2518', margin: '2rem 0 1.5rem' }} />
@@ -3520,7 +3519,7 @@ async function handleAssetUpload(event: ChangeEvent<HTMLInputElement>) {
{adminView === 'downloads' && ( {adminView === 'downloads' && (
<section className="admin-panel-section"> <section className="admin-panel-section">
<div className="admin-panel-head"> <div className="admin-panel-head">
<h2>Downloads</h2> <h2>Resources</h2>
<p>Manage the featured study guide, current download library, and previous series downloads.</p> <p>Manage the featured study guide, current download library, and previous series downloads.</p>
</div> </div>
@@ -3533,7 +3532,7 @@ async function handleAssetUpload(event: ChangeEvent<HTMLInputElement>) {
<div className="admin-section-header" id="downloads-study-guide"> <div className="admin-section-header" id="downloads-study-guide">
<h3>Companion Study Guide</h3> <h3>Companion Study Guide</h3>
<p>This is the featured primary download at the top of the Downloads page.</p> <p>This is the featured primary download at the top of the Resources page.</p>
</div> </div>
<div className="admin-array-row"> <div className="admin-array-row">
<div className="admin-array-fields"> <div className="admin-array-fields">
@@ -3782,7 +3781,7 @@ async function handleAssetUpload(event: ChangeEvent<HTMLInputElement>) {
<div className="admin-panel-subhead"> <div className="admin-panel-subhead">
<h3>More Resources Links</h3> <h3>More Resources Links</h3>
<p>These create cards in Downloads → More guides, worksheets, and past study downloads.</p> <p>These create cards in Resources → More guides, worksheets, and past study downloads.</p>
</div> </div>
{(form.customLinks ?? []).filter(link => link.placement === 'resources').length === 0 && ( {(form.customLinks ?? []).filter(link => link.placement === 'resources').length === 0 && (
<p className="admin-stats-note">No resource links yet.</p> <p className="admin-stats-note">No resource links yet.</p>
@@ -3863,7 +3862,7 @@ async function handleAssetUpload(event: ChangeEvent<HTMLInputElement>) {
<option value="homepage">Homepage</option> <option value="homepage">Homepage</option>
<option value="start-here">Start Here</option> <option value="start-here">Start Here</option>
<option value="episodes">Episodes</option> <option value="episodes">Episodes</option>
<option value="downloads">Downloads</option> <option value="downloads">Resources</option>
<option value="about">About</option> <option value="about">About</option>
<option value="contact">Contact</option> <option value="contact">Contact</option>
<option value="questions">Q&amp;A</option> <option value="questions">Q&amp;A</option>
@@ -3901,7 +3900,7 @@ async function handleAssetUpload(event: ChangeEvent<HTMLInputElement>) {
<section className="admin-panel-section"> <section className="admin-panel-section">
<div className="admin-panel-head"> <div className="admin-panel-head">
<h2>Books</h2> <h2>Books</h2>
<p>Amazon books shown in the Reading List section on the Downloads page. Add a cover image, description, and Amazon link for each book.</p> <p>Amazon books shown in the Reading List section on the Resources page. Add a cover image, description, and Amazon link for each book.</p>
</div> </div>
{(form.customLinks ?? []).filter(l => l.placement === 'books').length === 0 && <p className="admin-stats-note">No books yet.</p>} {(form.customLinks ?? []).filter(l => l.placement === 'books').length === 0 && <p className="admin-stats-note">No books yet.</p>}
{(form.customLinks ?? []).filter(l => l.placement === 'books').map(book => ( {(form.customLinks ?? []).filter(l => l.placement === 'books').map(book => (
@@ -3955,7 +3954,7 @@ async function handleAssetUpload(event: ChangeEvent<HTMLInputElement>) {
<section className="admin-panel-section"> <section className="admin-panel-section">
<div className="admin-panel-head"> <div className="admin-panel-head">
<h2>Recommended Resources</h2> <h2>Recommended Resources</h2>
<p>Curated resources shown at the bottom of the Downloads page. When at least one is added, it replaces the default Truth For Life widget. Add a title, image, link, and description for each resource.</p> <p>Curated resources shown at the bottom of the Resources page. When at least one is added, it replaces the default Truth For Life widget. Add a title, image, link, and description for each resource.</p>
</div> </div>
{(form.customLinks ?? []).filter(l => l.placement === 'recommended').length === 0 && ( {(form.customLinks ?? []).filter(l => l.placement === 'recommended').length === 0 && (
<p className="admin-stats-note">No custom recommended resources yet — the Truth For Life widget shows by default.</p> <p className="admin-stats-note">No custom recommended resources yet — the Truth For Life widget shows by default.</p>
+70 -40
View File
@@ -428,7 +428,7 @@ function SiteHeader({ content }: { content: SiteContent }) {
<NavLink to="/questions" className={({ isActive }) => `header-nav-link${isActive ? ' header-nav-link--active' : ''}`} onClick={() => setMenuOpen(false)}>Q&amp;A</NavLink> <NavLink to="/questions" className={({ isActive }) => `header-nav-link${isActive ? ' header-nav-link--active' : ''}`} onClick={() => setMenuOpen(false)}>Q&amp;A</NavLink>
<NavLink to="/study" className={({ isActive }) => `header-nav-link${isActive ? ' header-nav-link--active' : ''}`} onClick={() => setMenuOpen(false)}>Studies</NavLink> <NavLink to="/study" className={({ isActive }) => `header-nav-link${isActive ? ' header-nav-link--active' : ''}`} onClick={() => setMenuOpen(false)}>Studies</NavLink>
<NavLink to="/study/account" className={({ isActive }) => `header-nav-link${isActive ? ' header-nav-link--active' : ''}`} onClick={() => setMenuOpen(false)}>My Account</NavLink> <NavLink to="/study/account" className={({ isActive }) => `header-nav-link${isActive ? ' header-nav-link--active' : ''}`} onClick={() => setMenuOpen(false)}>My Account</NavLink>
<NavLink to="/resources" className={({ isActive }) => `header-nav-link${isActive ? ' header-nav-link--active' : ''}`} onClick={() => setMenuOpen(false)}>Downloads</NavLink> <NavLink to="/resources" className={({ isActive }) => `header-nav-link${isActive ? ' header-nav-link--active' : ''}`} onClick={() => setMenuOpen(false)}>Resources</NavLink>
<NavLink to="/about" className={({ isActive }) => `header-nav-link${isActive ? ' header-nav-link--active' : ''}`} onClick={() => setMenuOpen(false)}>About</NavLink> <NavLink to="/about" className={({ isActive }) => `header-nav-link${isActive ? ' header-nav-link--active' : ''}`} onClick={() => setMenuOpen(false)}>About</NavLink>
<NavLink to="/contact" className={({ isActive }) => `header-nav-link${isActive ? ' header-nav-link--active' : ''}`} onClick={() => setMenuOpen(false)}>Contact</NavLink> <NavLink to="/contact" className={({ isActive }) => `header-nav-link${isActive ? ' header-nav-link--active' : ''}`} onClick={() => setMenuOpen(false)}>Contact</NavLink>
<a <a
@@ -503,6 +503,8 @@ function SiteFooter({ content }: { content: SiteContent }) {
<nav className="footer-links footer-links--legal" aria-label="Legal links"> <nav className="footer-links footer-links--legal" aria-label="Legal links">
<Link to="/finished">Finished Books</Link> <Link to="/finished">Finished Books</Link>
<span aria-hidden="true">·</span> <span aria-hidden="true">·</span>
<Link to="/subscribe">Newsletter</Link>
<span aria-hidden="true">·</span>
<Link to="/privacy">Privacy Policy</Link> <Link to="/privacy">Privacy Policy</Link>
<span aria-hidden="true">·</span> <span aria-hidden="true">·</span>
<Link to="/terms">Terms</Link> <Link to="/terms">Terms</Link>
@@ -601,6 +603,7 @@ function AboutSection({ content }: { content: SiteContent }) {
</div> </div>
</div> </div>
<div className="about-text"> <div className="about-text">
{content.aboutShowEyebrow && <p className="eyebrow">{content.aboutShowEyebrow}</p>}
<h2>{content.aboutShowHeading}</h2> <h2>{content.aboutShowHeading}</h2>
<p>{content.aboutShowP1}</p> <p>{content.aboutShowP1}</p>
<p>{content.aboutShowP2}</p> <p>{content.aboutShowP2}</p>
@@ -841,7 +844,7 @@ function DownloadLibrarySection({ content }: { content: SiteContent }) {
))} ))}
</div> </div>
) : ( ) : (
<p className="download-empty-state">No downloads tagged "{activeTag}".</p> <p className="download-empty-state">No resources tagged "{activeTag}".</p>
)} )}
</> </>
)} )}
@@ -853,15 +856,20 @@ function DownloadLibrarySection({ content }: { content: SiteContent }) {
function DownloadDetailPage({ content }: { content: SiteContent }) { function DownloadDetailPage({ content }: { content: SiteContent }) {
const { id } = useParams<{ id: string }>() const { id } = useParams<{ id: string }>()
const resource = resolveDownloadPageResource(content, id) const resource = resolveDownloadPageResource(content, id)
const siteTitle = content.seo?.title || DEFAULTS.seo.title
usePageMeta(
resource ? `${resource.label} | ${siteTitle}` : `Resource | ${siteTitle}`,
resource?.summary || 'Free study guides and resources from Verse by Verse with Nate.',
)
if (!resource) { if (!resource) {
return ( return (
<main className="thanks-page" aria-label="Download not found"> <main className="thanks-page" aria-label="Download not found">
<div className="thanks-card"> <div className="thanks-card">
<p className="eyebrow">Downloads</p> <p className="eyebrow">Resources</p>
<h1>Download not found</h1> <h1>Download not found</h1>
<p>The download you requested is not available right now.</p> <p>The download you requested is not available right now.</p>
<Link to="/resources" className="btn-primary">Back to Downloads</Link> <Link to="/resources" className="btn-primary">Back to Resources</Link>
</div> </div>
</main> </main>
) )
@@ -870,7 +878,7 @@ function DownloadDetailPage({ content }: { content: SiteContent }) {
return ( return (
<main className="thanks-page download-detail-page" aria-label={resource.label}> <main className="thanks-page download-detail-page" aria-label={resource.label}>
<div className="thanks-card download-detail-card"> <div className="thanks-card download-detail-card">
<p className="eyebrow">Downloads</p> <p className="eyebrow">Resources</p>
<h1>{resource.label}</h1> <h1>{resource.label}</h1>
<p>{resource.summary}</p> <p>{resource.summary}</p>
@@ -901,7 +909,7 @@ function DownloadDetailPage({ content }: { content: SiteContent }) {
{resource.appleBooksLabel || 'Get it on Apple Books'} {resource.appleBooksLabel || 'Get it on Apple Books'}
</a> </a>
)} )}
<Link to="/resources" className="btn-secondary">Back to Downloads</Link> <Link to="/resources" className="btn-secondary">Back to Resources</Link>
</div> </div>
</div> </div>
@@ -1289,7 +1297,7 @@ function LandingPage({ content }: { content: SiteContent }) {
<SpotifyIcon /> <SpotifyIcon />
{content.seriesListenBtnLabel || 'Listen to Series'} {content.seriesListenBtnLabel || 'Listen to Series'}
</a> </a>
<Link to="/resources" className="btn-secondary">{content.seriesDownloadsBtnLabel || 'View Downloads'}</Link> <Link to="/resources" className="btn-secondary">{content.seriesDownloadsBtnLabel || 'View Resources'}</Link>
</div> </div>
</article> </article>
</div> </div>
@@ -1422,6 +1430,12 @@ function EpisodeDetailPage({ content }: { content: SiteContent }) {
} }
}, [episode]) }, [episode])
const siteTitle = content.seo?.title || DEFAULTS.seo.title
usePageMeta(
episode ? `${episode.title} | ${siteTitle}` : `Episode | ${siteTitle}`,
episode?.summary || 'Expository Bible teaching from Verse by Verse with Nate.',
)
if (!episode) { if (!episode) {
return ( return (
<div className="site"> <div className="site">
@@ -1894,14 +1908,14 @@ function BooksAndResourcesSection({ content }: { content: SiteContent }) {
function ResourcesPage({ content }: { content: SiteContent }) { function ResourcesPage({ content }: { content: SiteContent }) {
const siteTitle = content.seo?.title || DEFAULTS.seo.title const siteTitle = content.seo?.title || DEFAULTS.seo.title
usePageMeta( usePageMeta(
`Resources & Downloads | ${siteTitle}`, `Resources | ${siteTitle}`,
'Study guides, sermon notes, and free resources from Verse by Verse with Nate.', 'Study guides, sermon notes, and free resources from Verse by Verse with Nate.',
) )
return ( return (
<div className="site"> <div className="site">
<SiteHeader content={content} /> <SiteHeader content={content} />
<SiteSearchBar content={content} /> <SiteSearchBar content={content} />
<PageBanner eyebrow="Downloads" title="Study Guides & Resources" desc="Free guides, worksheets, and curated resources to help you go deeper in Scripture." /> <PageBanner eyebrow="Resources" title="Study Guides & Resources" desc="Free guides, worksheets, and curated resources to help you go deeper in Scripture." />
<DownloadLibrarySection content={content} /> <DownloadLibrarySection content={content} />
<CustomBlocksSection content={content} page="downloads" /> <CustomBlocksSection content={content} page="downloads" />
<BooksAndResourcesSection content={content} /> <BooksAndResourcesSection content={content} />
@@ -1933,12 +1947,18 @@ function AboutPage({ content }: { content: SiteContent }) {
} }
function ContactPage({ content }: { content: SiteContent }) { function ContactPage({ content }: { content: SiteContent }) {
const siteTitle = content.seo?.title || DEFAULTS.seo.title
usePageMeta(
`Contact | ${siteTitle}`,
'Get in touch with Nate — submit a Bible question, testimony, or topic request.',
)
return ( return (
<div className="site"> <div className="site">
<SiteHeader content={content} /> <SiteHeader content={content} />
<SiteSearchBar content={content} /> <SiteSearchBar content={content} />
<PageBanner eyebrow="Contact" title={content.contactHeading || 'Get in Touch'} /> <PageBanner eyebrow={content.contactEyebrow || 'Contact'} title={content.contactHeading || 'Get in Touch'} />
<ContactSection content={content} /> <ContactSection content={content} />
<KofiSupportSection content={content} />
<CustomBlocksSection content={content} page="contact" /> <CustomBlocksSection content={content} page="contact" />
<SiteFooter content={content} /> <SiteFooter content={content} />
<AnalyticsConsentBanner content={content} /> <AnalyticsConsentBanner content={content} />
@@ -1947,38 +1967,49 @@ function ContactPage({ content }: { content: SiteContent }) {
} }
function StartHerePage({ content }: { content: SiteContent }) { function StartHerePage({ content }: { content: SiteContent }) {
const siteTitle = content.seo?.title || DEFAULTS.seo.title
usePageMeta(
`Start Here | ${siteTitle}`,
content.startHereIntro || 'A beginner\'s guide to Verse by Verse with Nate.',
)
return ( return (
<main className="thanks-page" aria-label="Start Here"> <div className="site">
<div className="thanks-card" style={{ maxWidth: '980px', width: '100%' }}> <SiteHeader content={content} />
<p className="eyebrow">Verse by Verse with Nate</p> <SiteSearchBar content={content} />
<h1>{content.startHereHeading}</h1> <main className="thanks-page" aria-label="Start Here">
<p> <div className="thanks-card" style={{ maxWidth: '980px', width: '100%' }}>
{content.startHereIntro} <p className="eyebrow">Verse by Verse with Nate</p>
</p> <h1>{content.startHereHeading}</h1>
<p>
{content.startHereIntro}
</p>
<div className="start-grid" style={{ marginTop: '1.25rem' }}> <div className="start-grid" style={{ marginTop: '1.25rem' }}>
<article className="start-card"> <article className="start-card">
<h3>{content.startHereStep1Title}</h3> <h3>{content.startHereStep1Title}</h3>
<p>{content.startHereStep1Body}</p> <p>{content.startHereStep1Body}</p>
<Link to="/episodes" className="btn-secondary">{content.startHereStep1Cta}</Link> <Link to="/episodes" className="btn-secondary">{content.startHereStep1Cta}</Link>
</article> </article>
<article className="start-card"> <article className="start-card">
<h3>{content.startHereStep2Title}</h3> <h3>{content.startHereStep2Title}</h3>
<p>{content.startHereStep2Body}</p> <p>{content.startHereStep2Body}</p>
<a href="/#qa" className="btn-secondary">{content.startHereStep2Cta}</a> <Link to="/questions" className="btn-secondary">{content.startHereStep2Cta}</Link>
</article> </article>
<article className="start-card"> <article className="start-card">
<h3>{content.startHereStep3Title}</h3> <h3>{content.startHereStep3Title}</h3>
<p>{content.startHereStep3Body}</p> <p>{content.startHereStep3Body}</p>
<Link to="/contact" className="btn-primary">{content.startHereStep3Cta}</Link> <Link to="/contact" className="btn-primary">{content.startHereStep3Cta}</Link>
</article> </article>
</div> </div>
<div style={{ marginTop: '1.4rem' }}> <div style={{ marginTop: '1.4rem' }}>
<Link to="/" className="btn-secondary">Back to Site</Link> <Link to="/" className="btn-secondary">Back to Site</Link>
</div>
</div> </div>
</div> </main>
</main> <SiteFooter content={content} />
<AnalyticsConsentBanner content={content} />
</div>
) )
} }
@@ -2664,8 +2695,7 @@ export default function App() {
<Route path="/" element={<LandingPage content={content} />} /> <Route path="/" element={<LandingPage content={content} />} />
<Route path="/start-here" element={<StartHerePage content={content} />} /> <Route path="/start-here" element={<StartHerePage content={content} />} />
<Route path="/study" element={<StudyRouteFrame content={content} child={<StudyLandingPage content={content} />} />} /> <Route path="/study" element={<StudyRouteFrame content={content} child={<StudyLandingPage content={content} />} />} />
<Route path="/studys" element={<StudyRouteFrame content={content} child={<StudyLandingPage content={content} />} />} /> <Route path="/study/signup" element={<StudyRouteFrame content={content} child={<StudySignupPage />} />} />
<Route path="/study/signup" element={<StudyRouteFrame content={content} child={<StudySignupPage />} />} />
<Route path="/study/account" element={<StudyRouteFrame content={content} child={<StudyAccountPage />} />} /> <Route path="/study/account" element={<StudyRouteFrame content={content} child={<StudyAccountPage />} />} />
<Route path="/study/:studySlug" element={<StudyRouteFrame content={content} child={<ColossiansStudyIndexPage content={content} />} />} /> <Route path="/study/:studySlug" element={<StudyRouteFrame content={content} child={<ColossiansStudyIndexPage content={content} />} />} />
<Route path="/study/:studySlug/community" element={<StudyRouteFrame content={content} child={<StudyCommunityPage content={content} />} />} /> <Route path="/study/:studySlug/community" element={<StudyRouteFrame content={content} child={<StudyCommunityPage content={content} />} />} />
+2 -2
View File
@@ -336,7 +336,7 @@ export const DEFAULTS: SiteContent = {
seriesImageUrl: '/images/titus-cover.png', seriesImageUrl: '/images/titus-cover.png',
seriesListenUrl: '/spotify', seriesListenUrl: '/spotify',
seriesListenBtnLabel: 'Listen to Series', seriesListenBtnLabel: 'Listen to Series',
seriesDownloadsBtnLabel: 'View Downloads', seriesDownloadsBtnLabel: 'View Resources',
studyGuideTitle: 'Companion Study Guide', studyGuideTitle: 'Companion Study Guide',
studyGuideDescription: studyGuideDescription:
'Go deeper in your study with the official Verse by Verse companion guide — now available on Amazon.', 'Go deeper in your study with the official Verse by Verse companion guide — now available on Amazon.',
@@ -402,7 +402,7 @@ export const DEFAULTS: SiteContent = {
whereToNextHeading: 'Choose the page you need.', whereToNextHeading: 'Choose the page you need.',
whereToNextCards: [ whereToNextCards: [
{ id: 'episodes', title: 'Episodes', description: 'Listen to latest episodes and platform links.', path: '/episodes' }, { id: 'episodes', title: 'Episodes', description: 'Listen to latest episodes and platform links.', path: '/episodes' },
{ id: 'downloads', title: 'Downloads', description: 'Main guide, extra downloads, and past study files.', path: '/resources' }, { id: 'downloads', title: 'Resources', description: 'Main guide, extra downloads, and past study files.', path: '/resources' },
{ id: 'qa', title: 'Q&A', description: 'Browse Bible questions and approved answers.', path: '/questions' }, { id: 'qa', title: 'Q&A', description: 'Browse Bible questions and approved answers.', path: '/questions' },
{ id: 'about', title: 'About', description: 'Learn about Nate and the mission of the show.', path: '/about' }, { id: 'about', title: 'About', description: 'Learn about Nate and the mission of the show.', path: '/about' },
{ id: 'contact', title: 'Contact', description: 'Submit a Bible question, testimony, or topic.', path: '/contact' }, { id: 'contact', title: 'Contact', description: 'Submit a Bible question, testimony, or topic.', path: '/contact' },