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:
1 parent
9268514219
commit
d06fcba60b
5 files changed
+406
-468
No files matched your search
+50
-263
@@ -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>
|
||||
|
||||
Reference in new issue
Block a user