Redesign resources page with PageBanner, featured download, and consistent page headers; v1.1.40
Publish Container / docker (push) Has been cancelled
Publish Container / docker (push) Has been cancelled
- Replace hero study guide form with PageBanner + featured download card in library - Add PageBanner component used consistently across all interior pages (Downloads, Episodes, About, Contact, Q&A, Finished Books) - Remove duplicate headings/eyebrows that conflicted with banner titles on About, Contact, Q&A, Finished Books pages - Compact download card sizing (padding, border-radius, image size) - Add Books & Resources tabbed section with fallback to TruthForLife widget - Fix server sanitizer to pass through 'resources' placement items without URL - Add featured-badge and featured card styling for primary guide Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "siteforge",
|
"name": "siteforge",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.1.39",
|
"version": "1.1.40",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
+1
-1
@@ -186,7 +186,7 @@ function sanitizeCustomLinks(value) {
|
|||||||
placement,
|
placement,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.filter(item => item.label && (item.url || item.amazonUrl || item.placement === 'books' || item.placement === 'recommended'))
|
.filter(item => item.label && (item.url || item.amazonUrl || item.placement === 'books' || item.placement === 'recommended' || item.placement === 'resources'))
|
||||||
}
|
}
|
||||||
|
|
||||||
function sanitizeCustomBlocks(value) {
|
function sanitizeCustomBlocks(value) {
|
||||||
|
|||||||
+102
-12
@@ -4119,11 +4119,11 @@
|
|||||||
.resource-download-card {
|
.resource-download-card {
|
||||||
background: #111;
|
background: #111;
|
||||||
border: 1px solid rgba(201, 168, 76, 0.18);
|
border: 1px solid rgba(201, 168, 76, 0.18);
|
||||||
border-radius: 18px;
|
border-radius: 14px;
|
||||||
padding: 1.5rem;
|
padding: 1rem 1.25rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 1rem;
|
gap: 0.75rem;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
transition: transform 160ms ease, background 160ms ease, border-color 160ms ease;
|
transition: transform 160ms ease, background 160ms ease, border-color 160ms ease;
|
||||||
@@ -4172,9 +4172,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.resource-link-image {
|
.resource-link-image {
|
||||||
width: 80px;
|
width: 64px;
|
||||||
height: 80px;
|
height: 64px;
|
||||||
border-radius: 14px;
|
border-radius: 10px;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
border: 1px solid rgba(201, 168, 76, 0.2);
|
border: 1px solid rgba(201, 168, 76, 0.2);
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
@@ -4480,13 +4480,10 @@
|
|||||||
|
|
||||||
/* ── Recommended Resources section ── */
|
/* ── Recommended Resources section ── */
|
||||||
.recommended-resources-list {
|
.recommended-resources-list {
|
||||||
display: flex;
|
display: grid;
|
||||||
flex-direction: column;
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||||
gap: 1.5rem;
|
gap: 1.25rem;
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
max-width: 600px;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.recommended-resource-card {
|
.recommended-resource-card {
|
||||||
@@ -4541,6 +4538,99 @@
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── Interior page banner ── */
|
||||||
|
.section-page-banner {
|
||||||
|
padding: 3.5rem 0 2rem;
|
||||||
|
border-bottom: 1px solid rgba(201, 168, 76, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-banner-inner {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-banner-title {
|
||||||
|
font-family: var(--brand-font-heading);
|
||||||
|
font-size: clamp(2rem, 4vw, 3rem);
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--brand-warm-white);
|
||||||
|
margin: 0.15rem 0 0;
|
||||||
|
line-height: 1.15;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-banner-desc {
|
||||||
|
font-family: var(--brand-font-body);
|
||||||
|
font-size: 1.05rem;
|
||||||
|
font-weight: 300;
|
||||||
|
color: var(--brand-muted);
|
||||||
|
max-width: 52ch;
|
||||||
|
margin: 0.25rem 0 0;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Featured download card ── */
|
||||||
|
.resource-download-card--featured {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
border-color: rgba(201, 168, 76, 0.35);
|
||||||
|
background: rgba(201, 168, 76, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-link-image--featured {
|
||||||
|
width: 80px !important;
|
||||||
|
height: 110px !important;
|
||||||
|
border-radius: 6px !important;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-badge {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--brand-gold);
|
||||||
|
background: rgba(201, 168, 76, 0.12);
|
||||||
|
border: 1px solid rgba(201, 168, 76, 0.3);
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 0.15rem 0.5rem;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
align-self: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Books + Recommended Resources tabs ── */
|
||||||
|
.books-resources-tabs {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.25rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
border-bottom: 1px solid rgba(201, 168, 76, 0.15);
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.books-resources-tab {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
border-bottom: 2px solid transparent;
|
||||||
|
padding: 0.6rem 1.1rem;
|
||||||
|
margin-bottom: -1px;
|
||||||
|
font-family: var(--brand-font-body);
|
||||||
|
font-size: 0.92rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: rgba(255, 255, 255, 0.5);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: color 160ms ease, border-color 160ms ease;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.books-resources-tab:hover {
|
||||||
|
color: rgba(255, 255, 255, 0.85);
|
||||||
|
}
|
||||||
|
|
||||||
|
.books-resources-tab--active {
|
||||||
|
color: var(--brand-gold);
|
||||||
|
border-bottom-color: var(--brand-gold);
|
||||||
|
}
|
||||||
|
|
||||||
/* ── Study Guide section ── */
|
/* ── Study Guide section ── */
|
||||||
.section-guide {
|
.section-guide {
|
||||||
background: #0d0d0d;
|
background: #0d0d0d;
|
||||||
|
|||||||
+133
-93
@@ -267,11 +267,27 @@ interface DownloadPageResource {
|
|||||||
resourceId: string
|
resourceId: string
|
||||||
amazonUrl?: string
|
amazonUrl?: string
|
||||||
amazonLabel?: string
|
amazonLabel?: string
|
||||||
|
isPrimaryGuide?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveDownloadPageResource(content: SiteContent, pageId: string | undefined): DownloadPageResource | null {
|
function resolveDownloadPageResource(content: SiteContent, pageId: string | undefined): DownloadPageResource | null {
|
||||||
if (!pageId) return null
|
if (!pageId) return null
|
||||||
|
|
||||||
|
if (pageId === 'primary-guide') {
|
||||||
|
return {
|
||||||
|
id: 'primary-guide',
|
||||||
|
label: content.studyGuideTitle || 'Companion Study Guide',
|
||||||
|
imageUrl: content.seriesImageUrl || '/images/titus-cover.png',
|
||||||
|
summary: content.studyGuideDescription || 'Complete the short form below and your download will start right away.',
|
||||||
|
tags: [],
|
||||||
|
buttonText: `Download ${content.studyGuideTitle || 'Guide'}`,
|
||||||
|
resourceId: 'primary-guide',
|
||||||
|
amazonUrl: content.studyGuideUrl,
|
||||||
|
amazonLabel: content.studyGuideAmazonButtonLabel,
|
||||||
|
isPrimaryGuide: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (pageId.startsWith('custom--')) {
|
if (pageId.startsWith('custom--')) {
|
||||||
const customId = pageId.slice('custom--'.length)
|
const customId = pageId.slice('custom--'.length)
|
||||||
const resource = (content.customLinks ?? []).find(link => link.id === customId && link.placement === 'resources')
|
const resource = (content.customLinks ?? []).find(link => link.id === customId && link.placement === 'resources')
|
||||||
@@ -543,7 +559,6 @@ function AboutSection({ content }: { content: SiteContent }) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="about-text">
|
<div className="about-text">
|
||||||
<p className="eyebrow">{content.aboutShowEyebrow || 'About the Show'}</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>
|
||||||
@@ -559,42 +574,6 @@ function AboutSection({ content }: { content: SiteContent }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function StudyGuideSection({ content }: { content: SiteContent }) {
|
|
||||||
return (
|
|
||||||
<section className="section-guide" aria-label="Companion study guide">
|
|
||||||
<div className="section-inner guide-inner">
|
|
||||||
<div className="guide-cover-art">
|
|
||||||
<img
|
|
||||||
src={content.seriesImageUrl || '/images/titus-cover.png'}
|
|
||||||
alt={content.seriesTitle}
|
|
||||||
className="guide-cover-img"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="guide-text">
|
|
||||||
<p className="eyebrow">Downloads</p>
|
|
||||||
<h1 className="guide-page-title">Study Guides and Downloads</h1>
|
|
||||||
<p className="guide-page-intro">Start with the primary guide below, then explore the rest of the download library further down the page.</p>
|
|
||||||
<p className="eyebrow">Free Download</p>
|
|
||||||
<h2>{content.studyGuideTitle}</h2>
|
|
||||||
<p>{content.studyGuideDescription}</p>
|
|
||||||
<StudyDownloadForm buttonText={`Download ${content.studyGuideTitle || 'Guide'}`} />
|
|
||||||
{content.studyGuideUrl && (
|
|
||||||
<div className="guide-actions">
|
|
||||||
<a
|
|
||||||
href={content.studyGuideUrl}
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
className="btn-secondary"
|
|
||||||
>
|
|
||||||
{content.studyGuideAmazonButtonLabel || 'Get it on Amazon'}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function ContactSection({ content }: { content: SiteContent }) {
|
function ContactSection({ content }: { content: SiteContent }) {
|
||||||
return (
|
return (
|
||||||
@@ -602,9 +581,6 @@ function ContactSection({ content }: { content: SiteContent }) {
|
|||||||
<div className="section-inner contact-inner">
|
<div className="section-inner contact-inner">
|
||||||
<div className="contact-copy">
|
<div className="contact-copy">
|
||||||
<div className="contact-card">
|
<div className="contact-card">
|
||||||
<p className="eyebrow">{content.contactEyebrow || 'Get in Touch'}</p>
|
|
||||||
<h2>{content.contactHeading || 'Contact Nate'}</h2>
|
|
||||||
|
|
||||||
<div className="contact-profile">
|
<div className="contact-profile">
|
||||||
<img
|
<img
|
||||||
src={content.contactPhotoUrl || '/images/nate-contact-photo.png'}
|
src={content.contactPhotoUrl || '/images/nate-contact-photo.png'}
|
||||||
@@ -716,26 +692,59 @@ function PodcastHighlightsSection({ content }: { content: SiteContent }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function PageBanner({ eyebrow, title, desc }: { eyebrow: string; title: string; desc?: string }) {
|
||||||
|
return (
|
||||||
|
<section className="section-page-banner" aria-label={title}>
|
||||||
|
<div className="section-inner page-banner-inner">
|
||||||
|
<p className="eyebrow">{eyebrow}</p>
|
||||||
|
<h1 className="page-banner-title">{title}</h1>
|
||||||
|
{desc && <p className="page-banner-desc">{desc}</p>}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function DownloadLibrarySection({ content }: { content: SiteContent }) {
|
function DownloadLibrarySection({ content }: { content: SiteContent }) {
|
||||||
const resources = (content.customLinks ?? []).filter(link => link.placement === 'resources')
|
const resources = (content.customLinks ?? []).filter(link => link.placement === 'resources')
|
||||||
const [activeTag, setActiveTag] = useState<string | null>(null)
|
const [activeTag, setActiveTag] = useState<string | null>(null)
|
||||||
|
|
||||||
if (resources.length === 0) return null
|
const hasPrimaryGuide = !!(content.studyGuideTitle)
|
||||||
|
|
||||||
const allTags = Array.from(new Set(resources.flatMap(r => r.tags ?? []))).filter(Boolean)
|
const allTags = Array.from(new Set(resources.flatMap(r => r.tags ?? []))).filter(Boolean)
|
||||||
|
|
||||||
const filteredResources = resources.filter(r => {
|
const filteredResources = resources.filter(r => {
|
||||||
return !activeTag || (r.tags ?? []).includes(activeTag)
|
return !activeTag || (r.tags ?? []).includes(activeTag)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (!hasPrimaryGuide && resources.length === 0) return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="section-resources section-download-library" aria-label="Download library">
|
<section className="section-resources section-download-library" aria-label="Download library">
|
||||||
<div className="section-inner">
|
<div className="section-inner">
|
||||||
<div className="download-library-head">
|
{hasPrimaryGuide && (
|
||||||
<p className="eyebrow">Download Library</p>
|
<Link to="/downloads/primary-guide" className="resource-download-card resource-download-card--link resource-download-card--featured">
|
||||||
<h2 className="section-heading">Guides, worksheets, and study downloads.</h2>
|
<div className="resource-download-header">
|
||||||
|
{(content.seriesImageUrl || '/images/titus-cover.png') && (
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{resources.length > 0 && (
|
||||||
|
<>
|
||||||
{allTags.length > 0 && (
|
{allTags.length > 0 && (
|
||||||
<div className="download-filter-bar">
|
<div className="download-filter-bar">
|
||||||
<div className="download-tag-filter">
|
<div className="download-tag-filter">
|
||||||
@@ -770,7 +779,6 @@ function DownloadLibrarySection({ content }: { content: SiteContent }) {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{filteredResources.length > 0 ? (
|
{filteredResources.length > 0 ? (
|
||||||
<div className="download-library-group">
|
|
||||||
<div className="resources-list">
|
<div className="resources-list">
|
||||||
{filteredResources.map(resource => (
|
{filteredResources.map(resource => (
|
||||||
<Link key={resource.id} to={`/downloads/${buildCustomDownloadPageId(resource.id)}`} className="resource-download-card resource-download-card--link">
|
<Link key={resource.id} to={`/downloads/${buildCustomDownloadPageId(resource.id)}`} className="resource-download-card resource-download-card--link">
|
||||||
@@ -790,10 +798,11 @@ function DownloadLibrarySection({ content }: { content: SiteContent }) {
|
|||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
) : (
|
) : (
|
||||||
<p className="download-empty-state">No downloads tagged "{activeTag}".</p>
|
<p className="download-empty-state">No downloads tagged "{activeTag}".</p>
|
||||||
)}
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
@@ -834,7 +843,9 @@ function DownloadDetailPage({ content }: { content: SiteContent }) {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="download-detail-form-wrap">
|
<div className="download-detail-form-wrap">
|
||||||
<ResourceDownloadForm resourceId={resource.resourceId} buttonText={resource.buttonText} />
|
{resource.isPrimaryGuide
|
||||||
|
? <StudyDownloadForm buttonText={resource.buttonText} />
|
||||||
|
: <ResourceDownloadForm resourceId={resource.resourceId} buttonText={resource.buttonText} />}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="download-detail-actions">
|
<div className="download-detail-actions">
|
||||||
@@ -1466,16 +1477,9 @@ function FinishedBooksPage({ content }: { content: SiteContent }) {
|
|||||||
<div className="site">
|
<div className="site">
|
||||||
<SiteHeader content={content} />
|
<SiteHeader content={content} />
|
||||||
<SiteSearchBar content={content} />
|
<SiteSearchBar content={content} />
|
||||||
|
<PageBanner eyebrow="Studies" title="Finished Books" desc="Every series we've completed — with a full ordered episode playlist for each book of the Bible." />
|
||||||
<section className="section-finished-books">
|
<section className="section-finished-books">
|
||||||
<div className="section-inner">
|
<div className="section-inner">
|
||||||
<div className="finished-books-header">
|
|
||||||
<h1 className="section-heading">
|
|
||||||
<span className="ornament">✦</span> Finished Books <span className="ornament">✦</span>
|
|
||||||
</h1>
|
|
||||||
<p className="finished-books-intro">
|
|
||||||
Every series we've completed — with a full ordered episode playlist for each book of the Bible.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
{books.length === 0 ? (
|
{books.length === 0 ? (
|
||||||
<div className="finished-books-empty-state">
|
<div className="finished-books-empty-state">
|
||||||
<div className="finished-books-empty-icon">📖</div>
|
<div className="finished-books-empty-icon">📖</div>
|
||||||
@@ -1715,6 +1719,7 @@ function EpisodesPage({ content }: { content: SiteContent }) {
|
|||||||
<div className="site">
|
<div className="site">
|
||||||
<SiteHeader content={content} />
|
<SiteHeader content={content} />
|
||||||
<SiteSearchBar content={content} />
|
<SiteSearchBar content={content} />
|
||||||
|
<PageBanner eyebrow="Episodes" title="Podcast Episodes" desc={content.episodesSeoIntro || 'Expository Bible teaching — one verse at a time.'} />
|
||||||
|
|
||||||
<section className="section-player" aria-label="Current series episodes">
|
<section className="section-player" aria-label="Current series episodes">
|
||||||
<div className="section-inner">
|
<div className="section-inner">
|
||||||
@@ -1738,46 +1743,53 @@ function EpisodesPage({ content }: { content: SiteContent }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function RecommendedResourcesSection({ content }: { content: SiteContent }) {
|
function BooksAndResourcesSection({ content }: { content: SiteContent }) {
|
||||||
const recommended = (content.customLinks ?? []).filter(link => link.placement === 'recommended')
|
|
||||||
if (recommended.length === 0) return <TruthForLifeWidget type="resourcead" />
|
|
||||||
return (
|
|
||||||
<section className="section-tfl" aria-label="Recommended resources">
|
|
||||||
<div className="section-inner">
|
|
||||||
<div className="tfl-label-row">
|
|
||||||
<h2 className="section-heading" style={{ marginBottom: '1rem' }}>
|
|
||||||
<span className="ornament">✦</span> Recommended Resources <span className="ornament">✦</span>
|
|
||||||
</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 BooksSectionSection({ content }: { content: SiteContent }) {
|
|
||||||
const books = (content.customLinks ?? []).filter(link => link.placement === 'books')
|
const books = (content.customLinks ?? []).filter(link => link.placement === 'books')
|
||||||
if (books.length === 0) return null
|
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 (
|
return (
|
||||||
<section className="section-resources section-books" aria-label="Recommended books">
|
<section className="section-resources section-books-resources" aria-label="Books and recommended resources">
|
||||||
<div className="section-inner">
|
<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">
|
<div className="download-library-head">
|
||||||
<p className="eyebrow">Reading List</p>
|
<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>
|
<h2 className="section-heading">Books worth your time.</h2>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
<div className="books-list">
|
<div className="books-list">
|
||||||
{books.map(book => (
|
{books.map(book => (
|
||||||
<div key={book.id} className="book-card">
|
<div key={book.id} className="book-card">
|
||||||
@@ -1796,6 +1808,32 @@ function BooksSectionSection({ content }: { content: SiteContent }) {
|
|||||||
</div>
|
</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>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
@@ -1811,11 +1849,10 @@ function ResourcesPage({ content }: { content: SiteContent }) {
|
|||||||
<div className="site">
|
<div className="site">
|
||||||
<SiteHeader content={content} />
|
<SiteHeader content={content} />
|
||||||
<SiteSearchBar content={content} />
|
<SiteSearchBar content={content} />
|
||||||
<StudyGuideSection content={content} />
|
<PageBanner eyebrow="Downloads" title="Study Guides & Resources" desc="Free guides, worksheets, and curated resources to help you go deeper in Scripture." />
|
||||||
<DownloadLibrarySection content={content} />
|
<DownloadLibrarySection content={content} />
|
||||||
<BooksSectionSection content={content} />
|
|
||||||
<CustomBlocksSection content={content} page="downloads" />
|
<CustomBlocksSection content={content} page="downloads" />
|
||||||
<RecommendedResourcesSection content={content} />
|
<BooksAndResourcesSection content={content} />
|
||||||
<SiteFooter content={content} />
|
<SiteFooter content={content} />
|
||||||
<AnalyticsConsentBanner content={content} />
|
<AnalyticsConsentBanner content={content} />
|
||||||
</div>
|
</div>
|
||||||
@@ -1832,6 +1869,7 @@ function AboutPage({ content }: { content: SiteContent }) {
|
|||||||
<div className="site">
|
<div className="site">
|
||||||
<SiteHeader content={content} />
|
<SiteHeader content={content} />
|
||||||
<SiteSearchBar content={content} />
|
<SiteSearchBar content={content} />
|
||||||
|
<PageBanner eyebrow="About" title="About the Show" />
|
||||||
<AboutSection content={content} />
|
<AboutSection content={content} />
|
||||||
<TestimonialsSection testimonials={content.testimonials ?? []} />
|
<TestimonialsSection testimonials={content.testimonials ?? []} />
|
||||||
<CustomBlocksSection content={content} page="about" />
|
<CustomBlocksSection content={content} page="about" />
|
||||||
@@ -1846,6 +1884,7 @@ function ContactPage({ content }: { content: SiteContent }) {
|
|||||||
<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'} />
|
||||||
<ContactSection content={content} />
|
<ContactSection content={content} />
|
||||||
<CustomBlocksSection content={content} page="contact" />
|
<CustomBlocksSection content={content} page="contact" />
|
||||||
<SiteFooter content={content} />
|
<SiteFooter content={content} />
|
||||||
@@ -2367,6 +2406,7 @@ function QuestionsPage({ content }: { content: SiteContent }) {
|
|||||||
<div className="site">
|
<div className="site">
|
||||||
<SiteHeader content={content} />
|
<SiteHeader content={content} />
|
||||||
<SiteSearchBar content={content} />
|
<SiteSearchBar content={content} />
|
||||||
|
<PageBanner eyebrow="Q&A" title="Questions & Answers" desc="Real questions answered from Scripture — browse by topic or submit your own." />
|
||||||
<QASection />
|
<QASection />
|
||||||
<CustomBlocksSection content={content} page="questions" />
|
<CustomBlocksSection content={content} page="questions" />
|
||||||
<SiteFooter content={content} />
|
<SiteFooter content={content} />
|
||||||
|
|||||||
@@ -783,10 +783,6 @@ export default function QASection() {
|
|||||||
return (
|
return (
|
||||||
<section id="qa" className="section-qa" aria-label="Questions and answers">
|
<section id="qa" className="section-qa" aria-label="Questions and answers">
|
||||||
<div className="section-inner">
|
<div className="section-inner">
|
||||||
<h2 className="section-heading">
|
|
||||||
<span className="ornament">✦</span> Questions & Answers <span className="ornament">✦</span>
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<div className="qa-submit-cta">
|
<div className="qa-submit-cta">
|
||||||
<p className="qa-submit-cta-text">Have a question about Scripture or the podcast?</p>
|
<p className="qa-submit-cta-text">Have a question about Scripture or the podcast?</p>
|
||||||
<Link to="/contact" className="btn-primary">Submit a Question →</Link>
|
<Link to="/contact" className="btn-primary">Submit a Question →</Link>
|
||||||
|
|||||||
Reference in New Issue
Block a user