Keep the verse of the day when IQ Bible is down
Build and Push Docker Image / build-and-push (push) Successful in 5m42s

- Fall back to a built-in verse (one a day, same for everyone) in the reader's own translation
- No translation picker in fallback mode; card shows even with no IQ Bible key

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 5.5 committed 2026-10-13 10:30:04 -04:00
1 parent 512c6b6889
commit edc5a65d2b
4 files changed
+78 -13

No files matched your search

+17 -12
View File
@@ -1,26 +1,30 @@
// Home card: today's verse and Spurgeon's Morning & Evening reading, both from IQ Bible.
import { useEffect, useState } from 'react';
import { useApp } from '../../context/AppContext.js';
import { useIq, useIqAvailable } from '../../hooks/useIq.js';
import { useIq, useIqStatus } from '../../hooks/useIq.js';
import VerseShareImage, { BACKGROUND_KEY } from '../VerseShareImage.jsx';
import { SCENES, loadPhotoList, resolveBackground, sceneDataUrl } from '../../utils/verseBackgrounds.js';
import { bookOptions } from '../../utils/bibleUtils.jsx';
import { READER_TRANSLATIONS, fetchChapter } from '../../utils/bibleText.js';
import { fallbackVerseFor } from '../../utils/fallbackVerses.js';
export default function IqDailyCard() {
const on = useIqAvailable();
if (!on) return null;
return <Card />;
const status = useIqStatus();
if (status === null) return null;
return <Card iq={status} />;
}
function Card() {
function Card({ iq }) {
const { jumpToReaderVerse, openBibleReader, readerTranslation, readerTheme } = 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 votd = useIq(iq ? 'bibles/eng_kjv/verse-of-the-day' : null);
// When IQ Bible can't give us a verse (down, no key, allowance used up) the card shows a built-in one in the
// reader's own translation instead of disappearing.
const fallback = !votd.data?.data && votd.status !== 'loading';
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 translation = !fallback && 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 ref = votd.data?.data ?? (fallback ? fallbackVerseFor() : undefined);
const [text, setText] = useState({ key: '', value: '', error: false });
const [sharing, setSharing] = useState(false);
// The picture behind the verse: a different one each day by default, or the one you chose.
@@ -42,15 +46,16 @@ function Card() {
.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 dev = useIq(iq ? 'devotionals/today' : null);
const [part, setPart] = useState(() => (new Date().getHours() < 15 ? 'morning' : 'evening'));
const verse = votd.data?.data;
const verse = ref;
const reading = dev.data?.data?.[part];
const loaded = text.key === key;
const shown = loaded && text.value ? text.value : loaded && text.error ? ref?.text ?? '' : '…';
const shownTranslation = loaded && text.error ? 'KJV' : translation;
const name = (abbrev) => bookOptions.find((b) => b.abbrev === abbrev)?.name ?? abbrev;
if (!verse && !reading) return null;
if (fallback && !reading && loaded && text.error) return null; // nothing to show, not even the built-in verse
return (
<section className="mb-4 rounded-2xl border border-[var(--r-line)] bg-[var(--r-bar)] p-5" aria-label="Daily verse and devotional">
{verse && (
@@ -65,10 +70,10 @@ function Card() {
<optgroup label="Scenes">{SCENES.map((sc) => <option key={sc.id} value={sc.id}>{sc.name}</option>)}</optgroup>
{photos.length > 0 && <optgroup label="Your photos">{photos.map((p) => <option key={p.id} value={p.id}>{p.name}</option>)}</optgroup>}
</select>
<select aria-label="Verse of the day translation" value={translation} onChange={(e) => pick(e.target.value)}
{!fallback && <select aria-label="Verse of the day translation" value={translation} onChange={(e) => pick(e.target.value)}
className="rounded-lg border border-[var(--r-line)] bg-[var(--r-bg)] px-2 py-0.5 text-xs">
{READER_TRANSLATIONS.map((t) => <option key={t.id} value={t.id}>{t.id}</option>)}
</select>
</select>}
</span>
</div>
{/* the translation's text; the KJV from IQ Bible is the fallback if that Bible can't be loaded right now */}