Resources page redesign: tabs + compact grids; download page redesign with site header, two-column card and a mobile-safe form; v2.26.0

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 5.5 committed 2026-10-12 16:19:28 -04:00
1 parent 9268514219
commit d06fcba60b
5 files changed
+406 -468

No files matched your search

+50 -263
View File
@@ -14,6 +14,7 @@ import { GlobalSearch } from './components/GlobalSearch'
import { EpisodeAudioPlayer } from './components/EpisodeAudioPlayer'
import { NuggetsHostPortal } from './components/NuggetsHostPortal'
import { HeaderAccount } from './components/HeaderAccount'
import { ResourcesHub } from './components/ResourcesHub'
import { BIBLE_BOOKS } from '../shared/bible-books.js'
import './App.css'
import { sendEvent, useScrollDepthTracking, useTimeOnPage, useUTMCapture, useLinkClickTracking, checkAnalyticsConsentResponse } from './analytics'
@@ -854,131 +855,6 @@ function PageBanner({ eyebrow, title, desc }: { eyebrow: string; title: string;
)
}
function DownloadLibrarySection({ content }: { content: SiteContent }) {
const resources = (content.customLinks ?? []).filter(link => link.placement === 'resources')
const [activeTag, setActiveTag] = useState<string | null>(null)
const hasPrimaryGuide = !!(content.studyGuideTitle)
const allTags = Array.from(new Set(resources.flatMap(r => r.tags ?? []))).filter(Boolean)
const filteredResources = resources.filter(r => {
return !activeTag || (r.tags ?? []).includes(activeTag)
})
if (!hasPrimaryGuide && !content.secondSeries?.guide?.title && resources.length === 0) return null
return (
<section className="section-resources section-download-library" aria-label="Download library">
<div className="section-inner">
{hasPrimaryGuide && (
<Link to="/downloads/primary-guide" className="resource-download-card resource-download-card--link resource-download-card--featured">
<div className="resource-download-header">
<img
src={content.seriesImageUrl || '/images/titus-cover.png'}
alt={content.seriesTitle}
className="resource-link-image resource-link-image--featured"
loading="lazy"
/>
<div className="resource-download-meta">
<div className="featured-badge">Featured</div>
<span className="resource-link-label">{content.studyGuideTitle}</span>
{content.studyGuideDescription && (
<p className="resource-link-description">{content.studyGuideDescription}</p>
)}
<span className="resource-link-action">Download free guide →</span>
</div>
</div>
</Link>
)}
{content.secondSeries?.guide?.title && (
<Link to="/downloads/second-guide" className="resource-download-card resource-download-card--link resource-download-card--featured">
<div className="resource-download-header">
{content.secondSeries.imageUrl && (
<img
src={content.secondSeries.imageUrl}
alt={content.secondSeries.title}
className="resource-link-image resource-link-image--featured"
loading="lazy"
/>
)}
<div className="resource-download-meta">
<div className="featured-badge">Featured</div>
<span className="resource-link-label">{content.secondSeries.guide.title}</span>
{content.secondSeries.guide.description && (
<p className="resource-link-description">{content.secondSeries.guide.description}</p>
)}
<span className="resource-link-action">Download free guide →</span>
</div>
</div>
</Link>
)}
{resources.length > 0 && (
<>
{allTags.length > 0 && (
<div className="download-filter-bar">
<div className="download-tag-filter">
<button
type="button"
className={`download-tag-chip${activeTag === null ? ' download-tag-chip--active' : ''}`}
onClick={() => setActiveTag(null)}
>
All topics
</button>
{allTags.map(tag => (
<button
key={tag}
type="button"
className={`download-tag-chip${activeTag === tag ? ' download-tag-chip--active' : ''}`}
onClick={() => setActiveTag(prev => prev === tag ? null : tag)}
>
{tag}
</button>
))}
{activeTag !== null && (
<button
type="button"
className="download-clear-link"
onClick={() => setActiveTag(null)}
>
Clear
</button>
)}
</div>
</div>
)}
{filteredResources.length > 0 ? (
<div className="resources-list">
{filteredResources.map(resource => (
<Link key={resource.id} to={`/downloads/${buildCustomDownloadPageId(resource.id)}`} className="resource-download-card resource-download-card--link">
<div className="resource-download-header">
{resource.imageUrl && (
<img src={resource.imageUrl} alt={resource.label} className="resource-link-image" loading="lazy" />
)}
<div className="resource-download-meta">
<span className="resource-link-label">{resource.label}</span>
{resource.description && <p className="resource-link-description">{resource.description}</p>}
{(resource.tags ?? []).length > 0 && (
<div className="resource-link-tags">{(resource.tags ?? []).join(', ')}</div>
)}
<span className="resource-link-action">Open download page →</span>
</div>
</div>
</Link>
))}
</div>
) : (
<p className="download-empty-state">No resources tagged "{activeTag}".</p>
)}
</>
)}
</div>
</section>
)
}
function DownloadDetailPage({ content }: { content: SiteContent }) {
const { id } = useParams<{ id: string }>()
const resource = resolveDownloadPageResource(content, id)
@@ -1002,44 +878,57 @@ function DownloadDetailPage({ content }: { content: SiteContent }) {
}
return (
<main className="thanks-page download-detail-page" aria-label={resource.label}>
<div className="thanks-card download-detail-card">
<p className="eyebrow">Resources</p>
<h1>{resource.label}</h1>
<p>{resource.summary}</p>
<div className="site">
<SiteHeader content={content} />
<SiteSearchBar content={content} />
<main className="dl-page" aria-label={resource.label}>
<div className="section-inner">
<Link to="/resources" className="dl-back">← Back to Resources</Link>
<article className="dl-card">
{resource.imageUrl && (
<div className="dl-art">
<img src={resource.imageUrl} alt={resource.label} />
</div>
)}
<div className="dl-body">
<p className="eyebrow">Free download</p>
<h1>{resource.label}</h1>
{resource.summary && <p className="dl-summary">{resource.summary}</p>}
{resource.tags.length > 0 && (
<div className="dl-tags">
{resource.tags.map(tag => <span key={tag} className="dl-tag">{tag}</span>)}
</div>
)}
{resource.imageUrl && (
<div className="download-detail-art">
<img src={resource.imageUrl} alt={resource.label} className="guide-cover-img" />
</div>
)}
<div className="dl-form">
{/* The title is already on the page, so the button stays short */}
{resource.isPrimaryGuide
? <StudyDownloadForm buttonText="Download now" />
: <ResourceDownloadForm resourceId={resource.resourceId} buttonText="Download now" />}
</div>
{resource.tags.length > 0 && (
<div className="resource-link-tags">{resource.tags.join(', ')}</div>
)}
<div className="download-detail-form-wrap">
{resource.isPrimaryGuide
? <StudyDownloadForm buttonText={resource.buttonText} />
: <ResourceDownloadForm resourceId={resource.resourceId} buttonText={resource.buttonText} />}
{(resource.amazonUrl || resource.appleBooksUrl) && (
<div className="dl-actions">
<span>Or get it from:</span>
{resource.amazonUrl && (
<a href={resource.amazonUrl} target="_blank" rel="noreferrer" className="btn-secondary">
{resource.amazonLabel || 'Get it on Amazon'}
</a>
)}
{resource.appleBooksUrl && (
<a href={resource.appleBooksUrl} target="_blank" rel="noreferrer" className="btn-secondary">
{resource.appleBooksLabel || 'Get it on Apple Books'}
</a>
)}
</div>
)}
</div>
</article>
</div>
<div className="download-detail-actions">
{resource.amazonUrl && (
<a href={resource.amazonUrl} target="_blank" rel="noreferrer" className="btn-secondary">
{resource.amazonLabel || 'Get it on Amazon'}
</a>
)}
{resource.appleBooksUrl && (
<a href={resource.appleBooksUrl} target="_blank" rel="noreferrer" className="btn-secondary">
{resource.appleBooksLabel || 'Get it on Apple Books'}
</a>
)}
<Link to="/resources" className="btn-secondary">Back to Resources</Link>
</div>
</div>
</main>
</main>
<SiteFooter content={content} />
<AnalyticsConsentBanner content={content} />
</div>
)
}
@@ -2341,107 +2230,6 @@ function NuggetsHostDetailPage({ content }: { content: SiteContent }) {
)
}
function BooksAndResourcesSection({ content }: { content: SiteContent }) {
const books = (content.customLinks ?? []).filter(link => link.placement === 'books')
const recommended = (content.customLinks ?? []).filter(link => link.placement === 'recommended')
const [activeTab, setActiveTab] = useState<'books' | 'recommended'>('books')
const hasBooks = books.length > 0
const hasRecommended = recommended.length > 0
const showTabs = hasBooks && hasRecommended
if (!hasBooks && !hasRecommended) return <TruthForLifeWidget type="resourcead" />
const tab = showTabs ? activeTab : (hasBooks ? 'books' : 'recommended')
return (
<section className="section-resources section-books-resources" aria-label="Books and recommended resources">
<div className="section-inner">
{showTabs ? (
<div className="books-resources-tabs">
<button
type="button"
className={`books-resources-tab${tab === 'books' ? ' books-resources-tab--active' : ''}`}
onClick={() => setActiveTab('books')}
>
Reading List
</button>
<button
type="button"
className={`books-resources-tab${tab === 'recommended' ? ' books-resources-tab--active' : ''}`}
onClick={() => setActiveTab('recommended')}
>
Recommended Resources
</button>
</div>
) : (
<div className="download-library-head">
<p className="eyebrow">{tab === 'books' ? 'Reading List' : 'Recommended'}</p>
<h2 className="section-heading">{tab === 'books' ? 'Books worth your time.' : 'Resources worth your time.'}</h2>
</div>
)}
{tab === 'books' && (
<>
{showTabs && (
<div className="download-library-head" style={{ marginBottom: '1.5rem' }}>
<h2 className="section-heading">Books worth your time.</h2>
</div>
)}
<div className="books-list">
{books.map(book => (
<div key={book.id} className="book-card">
{book.imageUrl && (
<img src={book.imageUrl} alt={book.label} className="book-card-cover" loading="lazy" />
)}
<div className="book-card-meta">
<span className="book-card-title">{book.label}</span>
{book.description && <p className="book-card-description">{book.description}</p>}
{(book.amazonUrl || book.url) && (
<a href={book.amazonUrl || book.url} target="_blank" rel="noopener noreferrer" className="btn-primary book-card-btn">
{book.amazonUrl ? (book.amazonLabel || 'View on Amazon') : (book.amazonLabel || 'Learn More')}
</a>
)}
{book.appleBooksUrl && (
<a href={book.appleBooksUrl} target="_blank" rel="noopener noreferrer" className="btn-secondary book-card-btn">
{book.appleBooksLabel || 'Get it on Apple Books'}
</a>
)}
</div>
</div>
))}
</div>
</>
)}
{tab === 'recommended' && (
<>
{showTabs && (
<div className="download-library-head" style={{ marginBottom: '1.5rem' }}>
<h2 className="section-heading">Recommended Resources</h2>
</div>
)}
<div className="recommended-resources-list">
{recommended.map(item => (
<a key={item.id} href={item.url} target="_blank" rel="noopener noreferrer" className="recommended-resource-card">
{item.imageUrl && (
<img src={item.imageUrl} alt={item.label} className="recommended-resource-image" loading="lazy" />
)}
<div className="recommended-resource-meta">
<span className="recommended-resource-title">{item.label}</span>
{item.description && <p className="recommended-resource-description">{item.description}</p>}
<span className="recommended-resource-action">Learn more →</span>
</div>
</a>
))}
</div>
</>
)}
</div>
</section>
)
}
function ResourcesPage({ content }: { content: SiteContent }) {
const siteTitle = content.seo?.title || DEFAULTS.seo.title
usePageMeta(
@@ -2453,9 +2241,8 @@ function ResourcesPage({ content }: { content: SiteContent }) {
<SiteHeader content={content} />
<SiteSearchBar content={content} />
<PageBanner eyebrow="Resources" title="Study Guides & Resources" desc="Free guides, worksheets, and curated resources to help you go deeper in Scripture." />
<DownloadLibrarySection content={content} />
<ResourcesHub content={content} downloadPath={id => `/downloads/${buildCustomDownloadPageId(id)}`} fallback={<TruthForLifeWidget type="resourcead" />} />
<CustomBlocksSection content={content} page="downloads" />
<BooksAndResourcesSection content={content} />
<SiteFooter content={content} />
<AnalyticsConsentBanner content={content} />
</div>