diff --git a/server.js b/server.js index a92825c..50100eb 100644 --- a/server.js +++ b/server.js @@ -68,7 +68,7 @@ app.use((_req, res, next) => { 'Content-Security-Policy', [ "default-src 'self'", - "script-src 'self' 'unsafe-inline'", + "script-src 'self' 'unsafe-inline' https://www.truthforlife.org https://ajax.googleapis.com", "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com", "font-src 'self' https://fonts.gstatic.com", "img-src 'self' data: https:", diff --git a/src/App.tsx b/src/App.tsx index aa12fd3..a4a7fce 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -6,7 +6,7 @@ import QASection from './components/QASection' import ContactForm from './components/ContactForm' import { ColossiansStudyIndexPage, ColossiansStudyNotesPage, ColossiansStudySectionPage, StudyLandingPage, StudySignupPage, StudyAccountPage, StudyCommunityPage, StudyQuizPage } from './colossiansStudy' import { SpotifyIcon } from './icons' -import type { SiteContent, ArchivedSeries, StudyProgram } from './content' +import type { SiteContent, StudyProgram } from './content' import { DEFAULTS } from './content' import { usePageMeta } from './hooks/usePageMeta' import { useGlobalSearch } from './hooks/useGlobalSearch' @@ -253,18 +253,10 @@ function buildCustomResourceDownloadId(id: string) { return `custom:${id}` } -function buildArchivedResourceDownloadId(seriesId: string, linkId: string) { - return `archived:${seriesId}:${linkId}` -} - function buildCustomDownloadPageId(id: string) { return `custom--${id}` } -function buildArchivedDownloadPageId(seriesId: string, linkId: string) { - return `archived--${seriesId}--${linkId}` -} - interface DownloadPageResource { id: string label: string @@ -298,25 +290,6 @@ function resolveDownloadPageResource(content: SiteContent, pageId: string | unde } } - if (pageId.startsWith('archived--')) { - const [, seriesId, linkId] = pageId.split('--') - const series = (content.archivedSeries ?? []).find(item => item.id === seriesId) - const link = (series?.resourceLinks ?? []).find(item => item.id === linkId) - if (!series || !link) return null - - return { - id: pageId, - label: link.label || series.title || 'Download Resource', - imageUrl: series.imageUrl, - summary: link.description || series.description || 'Fill out the form below to access this download from a previous study.', - tags: [], - buttonText: `Download ${link.label || series.title || 'Resource'}`, - resourceId: buildArchivedResourceDownloadId(series.id, link.id), - amazonUrl: link.amazonUrl, - amazonLabel: link.amazonLabel, - } - } - return null } @@ -368,23 +341,6 @@ function AnalyticsConsentBanner({ content }: { content: SiteContent }) { ) } -interface Episode { - title: string - pubDate: string - link: string - description: string - duration: string - episode: string -} - -function formatPubDate(raw: string): string { - try { - return new Date(raw).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }) - } catch { - return raw - } -} - function SiteSearchBar({ content }: { content: SiteContent }) { const { query, setQuery, results } = useGlobalSearch(content) return ( @@ -730,53 +686,26 @@ function PodcastHighlightsSection({ content }: { content: SiteContent }) { function DownloadLibrarySection({ content }: { content: SiteContent }) { const resources = (content.customLinks ?? []).filter(link => link.placement === 'resources') - const archivedWithResources = (content.archivedSeries ?? []).filter(series => (series.resourceLinks ?? []).length > 0) const [activeTag, setActiveTag] = useState(null) - const [activeType, setActiveType] = useState<'all' | 'resource' | 'series'>('all') - if (resources.length === 0 && archivedWithResources.length === 0) return null + if (resources.length === 0) return null const allTags = Array.from(new Set(resources.flatMap(r => r.tags ?? []))).filter(Boolean) - // Filter resources const filteredResources = resources.filter(r => { - const tagOk = !activeTag || (r.tags ?? []).includes(activeTag) - const typeOk = activeType === 'all' || activeType === 'resource' - return tagOk && typeOk + return !activeTag || (r.tags ?? []).includes(activeTag) }) - // Filter archived series - const filteredArchived = archivedWithResources.filter(() => { - return activeType === 'all' || activeType === 'series' - }) - - const hasTypeFilter = resources.length > 0 && archivedWithResources.length > 0 - return (

Download Library

-

Guides, worksheets, and past study downloads.

+

Guides, worksheets, and study downloads.

- {/* Type + tag filter bar */} -
- {hasTypeFilter && ( -
0 ? '0.5rem' : 0 }}> - {(['all', 'resource', 'series'] as const).map(t => ( - - ))} -
- )} - {allTags.length > 0 && (activeType === 'all' || activeType === 'resource') && ( + {allTags.length > 0 && ( +
)}
- )} -
+
+ )} - {filteredResources.length > 0 && ( + {filteredResources.length > 0 ? (
{filteredResources.map(resource => ( @@ -830,86 +759,18 @@ function DownloadLibrarySection({ content }: { content: SiteContent }) { ))}
- )} - - {activeTag !== null && filteredResources.length === 0 && activeType !== 'series' && ( + ) : (

No downloads tagged "{activeTag}".

)} - - {filteredArchived.length > 0 && ( -
-
-

Previous Studies

-

Downloads from earlier series.

-
- {filteredArchived.map(series => ( -
-
-

{series.title || 'Archived Study'}

- {series.description &&

{series.description}

} -
-
- {(series.resourceLinks ?? []).map(link => ( - -
- {series.imageUrl && ( - {series.title - )} -
- {link.label || series.title || 'Download Resource'} - {link.description &&

{link.description}

} - Open download page → -
-
- - ))} -
-
- ))} -
- )}
) } -function useEpisodesForSeries(seriesId: string | null, archivedSeries: ArchivedSeries[]) { - const [episodes, setEpisodes] = useState([]) - const [loading, setLoading] = useState(false) - - useEffect(() => { - if (!seriesId) return - const series = archivedSeries.find(s => s.id === seriesId) - if (!series?.episodeRange) return - - setLoading(true) - fetch('/api/episodes/all') - .then(r => r.ok ? r.json() : Promise.reject()) - .then((data: { episodes?: Episode[] }) => { - const { from, to } = series.episodeRange! - const filtered = (data.episodes ?? []).filter(ep => { - const num = parseInt(ep.episode, 10) - return !isNaN(num) && num >= from && num <= to - }) - setEpisodes(filtered) - }) - .catch(() => {}) - .finally(() => setLoading(false)) - }, [seriesId]) - - return { episodes, loading } -} - function DownloadDetailPage({ content }: { content: SiteContent }) { const { id } = useParams<{ id: string }>() const resource = resolveDownloadPageResource(content, id) - // Resolve related series for archived resources - const archivedSeriesId = id?.startsWith('archived--') ? id.split('--')[1] : null - const relatedSeries = archivedSeriesId ? (content.archivedSeries ?? []).find(s => s.id === archivedSeriesId) : null - const { episodes: relatedEpisodes, loading: epsLoading } = useEpisodesForSeries(archivedSeriesId, content.archivedSeries ?? []) - const spotifyUrl = content.platformSpotifyUrl || '/episodes' - if (!resource) { return (
@@ -953,39 +814,6 @@ function DownloadDetailPage({ content }: { content: SiteContent }) { Back to Downloads - {relatedSeries && (relatedEpisodes.length > 0 || epsLoading) && ( -
-

- Episodes from {relatedSeries.title} -

- {epsLoading ? ( -

Loading episodes…

- ) : ( -
- {relatedEpisodes.slice(0, 10).map((ep, idx) => ( - -
- {ep.episode && Ep. {ep.episode}} - {ep.pubDate && {formatPubDate(ep.pubDate)}} -
-

{ep.title}

-
- ))} - {relatedEpisodes.length > 10 && ( - - View all {relatedEpisodes.length} episodes → - - )} -
- )} -
- )}
) @@ -1140,28 +968,23 @@ function HomepageNewsletterSection() { type TflWidgetType = 'audiodevo' | 'abdevotional' | 'resourcead' function TruthForLifeWidget({ type = 'audiodevo' }: { type?: TflWidgetType }) { - const iframeHeight = type === 'audiodevo' ? '420' : type === 'resourcead' ? '270' : '800' const heading = type === 'resourcead' ? 'Recommended Resources' : 'Daily Devotional' + const containerId = 'tfl-syndicate-container' - const srcdoc = ` - - - - - - - -
- -` + useEffect(() => { + const container = document.getElementById(containerId) + if (!container) return + container.innerHTML = '' + + const existing = document.getElementById('syn_script') + if (existing) existing.remove() + + const script = document.createElement('script') + script.id = 'syn_script' + script.src = `https://www.truthforlife.org/static/js/responsive/lib/syndicate.js?type=${type}&id=458890` + script.type = 'text/javascript' + container.parentElement?.insertBefore(script, container) + }, [type, containerId]) return (
@@ -1176,13 +999,9 @@ function TruthForLifeWidget({ type = 'audiodevo' }: { type?: TflWidgetType }) {

-