Fetch BSB passage text from Bolls.life API in study lesson view; v1.1.35

Adds /api/bible-passage server route that proxies the Berean Standard Bible
from Bolls.life per-verse, with in-memory caching. Study lesson scripture
block now displays live BSB text instead of static passageText. Adds
bibleBook field to StudyProgram so admins can set the book name explicitly
when the study slug doesn't match (e.g. a study titled "ephesians-part-2").

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
nmemmert
2026-08-10 10:12:25 -04:00
parent 6acc3f1d04
commit 9e602e29ec
8 changed files with 117 additions and 5 deletions
+28 -1
View File
@@ -1506,6 +1506,8 @@ export function ColossiansStudySectionPage({ content }: Props) {
const [bannerDismissed, setBannerDismissed] = useState(() => localStorage.getItem('study-banner-dismissed-v1') === '1')
const [autoSaveMsg, setAutoSaveMsg] = useState('')
const [_checkpointReflection, _setCheckpointReflection] = useState('')
const [bsbPassage, setBsbPassage] = useState<string | null>(null)
const [bsbPassageLoading, setBsbPassageLoading] = useState(false)
const savedNoteCount = Object.values(noteMap).filter(v => v?.trim()).length
const lessonAudioEmbedUrl = useMemo(() => {
@@ -1546,6 +1548,21 @@ export function ColossiansStudySectionPage({ content }: Props) {
}
}, [])
useEffect(() => {
if (!section?.reference || !currentStudySlug) return
let cancelled = false
setBsbPassage(null)
setBsbPassageLoading(true)
const bookKey = (study?.bibleBook?.trim() || currentStudySlug).toLowerCase()
const params = new URLSearchParams({ book: bookKey, reference: section.reference })
fetch(`/api/bible-passage?${params}`)
.then(r => r.json() as Promise<{ text?: string }>)
.then(data => { if (!cancelled && data.text) setBsbPassage(data.text) })
.catch(() => {})
.finally(() => { if (!cancelled) setBsbPassageLoading(false) })
return () => { cancelled = true }
}, [section?.id, section?.reference, currentStudySlug, study?.bibleBook])
useEffect(() => {
if (!auth.checked || !auth.authenticated || !study || !isEnrolled) return
let cancelled = false
@@ -1908,7 +1925,17 @@ export function ColossiansStudySectionPage({ content }: Props) {
>✎</button>
)}
<h2>Scripture Text</h2>
<p className="study-detail-copy study-detail-copy--scripture">{section.passageText || `Add the passage text for ${section.reference} here when you move the guide online.`}</p>
{bsbPassageLoading
? <p className="study-detail-copy" style={{ opacity: 0.5 }}>Loading passage…</p>
: (bsbPassage ?? section.passageText)
? (bsbPassage ?? section.passageText).split('\n\n').map((para, i) => (
<p key={i} className="study-detail-copy study-detail-copy--scripture">
{para.split('\n').map((line, j, arr) => j < arr.length - 1 ? <>{line}<br /></> : line)}
</p>
))
: <p className="study-detail-copy" style={{ opacity: 0.5 }}>Passage text not available.</p>
}
{bsbPassage && <p style={{ fontSize: '0.78rem', color: '#5a5440', marginTop: '0.5rem' }}>Berean Standard Bible (BSB)</p>}
{isEnrolled && openAnchor === 'scripture' && (
<NoteCard
anchorLabel="Scripture Text"