diff --git a/src/components/iq/IqDailyCard.jsx b/src/components/iq/IqDailyCard.jsx index ff1866f..8278825 100644 --- a/src/components/iq/IqDailyCard.jsx +++ b/src/components/iq/IqDailyCard.jsx @@ -1,8 +1,9 @@ // Home card: today's verse and Spurgeon's Morning & Evening reading, both from IQ Bible. -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import { useApp } from '../../context/AppContext.js'; import { useIq, useIqAvailable } from '../../hooks/useIq.js'; import { bookOptions } from '../../utils/bibleUtils.jsx'; +import { READER_TRANSLATIONS, fetchChapter } from '../../utils/bibleText.js'; export default function IqDailyCard() { const on = useIqAvailable(); @@ -11,8 +12,26 @@ export default function IqDailyCard() { } function Card() { - const { jumpToReaderVerse, openBibleReader } = useApp(); + const { jumpToReaderVerse, openBibleReader, readerTranslation } = useApp(); + // Today's verse is chosen by IQ Bible (the same for everyone); its words come from the Bible you pick. const votd = useIq('bibles/eng_kjv/verse-of-the-day'); + const [chosen, setChosen] = useState(() => { try { return localStorage.getItem('votd-translation') || ''; } catch { return ''; } }); + const translation = READER_TRANSLATIONS.some((t) => t.id === chosen) ? chosen : (readerTranslation || 'BSB'); + const pick = (id) => { setChosen(id); try { localStorage.setItem('votd-translation', id); } catch { /* storage unavailable */ } }; + const ref = votd.data?.data; + const [text, setText] = useState({ key: '', value: '', error: false }); + const key = ref ? `${translation}/${ref.book_usfm}/${ref.chapter_number}/${ref.verse_number}` : ''; + useEffect(() => { + if (!ref) return undefined; + const controller = new AbortController(); + fetchChapter(translation, ref.book_usfm, ref.chapter_number, { signal: controller.signal }) + .then(({ verses }) => { + const found = verses.find((v) => v.number === ref.verse_number)?.text; + setText({ key, value: found ?? '', error: !found }); + }) + .catch(() => { if (!controller.signal.aborted) setText({ key, value: '', error: true }); }); + return () => controller.abort(); + }, [key]); // eslint-disable-line react-hooks/exhaustive-deps const dev = useIq('devotionals/today'); const [part, setPart] = useState(() => (new Date().getHours() < 15 ? 'morning' : 'evening')); const verse = votd.data?.data; @@ -23,10 +42,21 @@ function Card() {
{verse && (
-

Verse of the day · KJV

-

{verse.text}

+
+

Verse of the day

+ +
+ {/* the translation's text; the KJV from IQ Bible is the fallback if that Bible can't be loaded right now */} +

{text.key === key && text.value ? text.value : text.key === key && text.error ? verse.text : '…'}

+ {text.key === key && text.error && translation !== 'KJV' &&

Couldn’t load {translation} just now, so this is the KJV.

} + {text.key === key && text.value && READER_TRANSLATIONS.find((t) => t.id === translation)?.notice && ( +

{READER_TRANSLATIONS.find((t) => t.id === translation).notice}

+ )}
)} {reading && ( diff --git a/src/components/iq/iq.test.jsx b/src/components/iq/iq.test.jsx index 2814eae..161af14 100644 --- a/src/components/iq/iq.test.jsx +++ b/src/components/iq/iq.test.jsx @@ -139,3 +139,23 @@ describe('IqPlanExport', () => { expect(container).toBeEmptyDOMElement(); }); }); + +describe('IqDailyCard', () => { + test('shows the verse of the day in the chosen translation, remembers it, and falls back to the KJV', async () => { + const { default: IqDailyCard } = await import('./IqDailyCard.jsx'); + routes['/api/iq/bibles/eng_kjv/verse-of-the-day'] = { data: { book_usfm: 'JHN', chapter_number: 3, verse_number: 16, text: 'KJV text of John 3:16' } }; + routes['/api/iq/devotionals/today'] = { data: {} }; + routes['https://bible.helloao.org/api/BSB/JHN/3.json'] = { chapter: { content: [{ type: 'verse', number: 16, content: ['BSB text of John 3:16'] }] } }; + routes['https://bible.helloao.org/api/eng_net/JHN/3.json'] = { chapter: { content: [{ type: 'verse', number: 16, content: ['NET text of John 3:16'] }] } }; + const { AppContext } = await import('../../context/AppContext.js'); + const ctx = { jumpToReaderVerse: vi.fn(), openBibleReader: vi.fn(), readerTranslation: 'BSB' }; + render(); + expect(await screen.findByText('BSB text of John 3:16')).toBeInTheDocument(); + await userEvent.setup().selectOptions(screen.getByLabelText('Verse of the day translation'), 'NET'); + expect(await screen.findByText('NET text of John 3:16')).toBeInTheDocument(); + expect(localStorage.getItem('votd-translation')).toBe('NET'); + await userEvent.setup().selectOptions(screen.getByLabelText('Verse of the day translation'), 'NLT'); // not reachable in this test + expect(await screen.findByText('KJV text of John 3:16')).toBeInTheDocument(); + expect(screen.getByText(/Couldn’t load NLT just now/)).toBeInTheDocument(); + }); +});