Harden sync and security; add IQ Bible, templates, password reset, backups
Build and Push Docker Image / build-and-push (push) Successful in 7m14s
Build and Push Docker Image / build-and-push (push) Successful in 7m14s
Sync and data safety - Autosave flushes on leaving a study or the tab; storage-full errors are reported - Unsynced studies, deletes and ink are queued and retried when back online - Deletes propagate across devices (with a "Keep it" undo) and no longer resurrect - Server assigns study versions (no client-clock conflicts); "Combine both" merges a conflicted study chunk by chunk - Reading-plan progress made offline is merged, not overwritten; plans, ink and marks are cleared from the device on sign-out Security - Block SSRF through the ntfy reminder URL; failure-only rate limiting per IP and account; constant-time login; TOTP replay protection; input validation; SESSION_SECRET required in production; admin user delete fixed - Forgot password: one-time code sent to the account's ntfy topic Features - IQ Bible proxy (route allow-list, text-only caching per its terms): more commentaries, lexicons, study aids, daily verse and devotional, plan calendar export, narrations, podcast background - Study templates, reading-plan catch-up, account export, daily database snapshots Tests and tooling - ESLint config; App tests split into src/test; tests for sync queues, library hook, merge, templates, IQ Bible UI and reset form Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6bb392933a
commit
f274bc9c8a
67 files changed
+4734
-1569
No files matched your search
@@ -0,0 +1,48 @@
|
||||
// Home card: today's verse and Spurgeon's Morning & Evening reading, both from IQ Bible.
|
||||
import { useState } from 'react';
|
||||
import { useApp } from '../../context/AppContext.js';
|
||||
import { useIq, useIqAvailable } from '../../hooks/useIq.js';
|
||||
import { bookOptions } from '../../utils/bibleUtils.jsx';
|
||||
|
||||
export default function IqDailyCard() {
|
||||
const on = useIqAvailable();
|
||||
if (!on) return null;
|
||||
return <Card />;
|
||||
}
|
||||
|
||||
function Card() {
|
||||
const { jumpToReaderVerse, openBibleReader } = useApp();
|
||||
const votd = useIq('bibles/eng_kjv/verse-of-the-day');
|
||||
const dev = useIq('devotionals/today');
|
||||
const [part, setPart] = useState(() => (new Date().getHours() < 15 ? 'morning' : 'evening'));
|
||||
const verse = votd.data?.data;
|
||||
const reading = dev.data?.data?.[part];
|
||||
const name = (abbrev) => bookOptions.find((b) => b.abbrev === abbrev)?.name ?? abbrev;
|
||||
if (!verse && !reading) return null;
|
||||
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 && (
|
||||
<div>
|
||||
<p className="text-[11px] uppercase tracking-wide text-[var(--r-sub)]">Verse of the day · KJV</p>
|
||||
<p className="mt-1 font-serif text-[15px] leading-relaxed">{verse.text}</p>
|
||||
<button type="button" onClick={() => { jumpToReaderVerse(verse.book_usfm, verse.chapter_number, verse.verse_number); openBibleReader(); }}
|
||||
className="mt-1 text-xs font-medium text-[var(--r-accent)] hover:underline">{name(verse.book_usfm)} {verse.chapter_number}:{verse.verse_number} — read in context</button>
|
||||
</div>
|
||||
)}
|
||||
{reading && (
|
||||
<details className="mt-3 border-t border-[var(--r-line)] pt-3">
|
||||
<summary className="cursor-pointer text-sm font-medium">Spurgeon, {part === 'morning' ? 'Morning' : 'Evening'}: “{reading.heading_text}”</summary>
|
||||
<div className="mt-2 flex gap-1.5">
|
||||
{['morning', 'evening'].map((p) => (
|
||||
<button key={p} type="button" onClick={() => setPart(p)} aria-pressed={part === p}
|
||||
className={`rounded-full border px-2.5 py-0.5 text-[11px] ${part === p ? 'border-[var(--r-text)] bg-[var(--r-text)] text-[var(--r-bg)]' : 'border-[var(--r-line)] text-[var(--r-sub)]'}`}>{p === 'morning' ? 'Morning' : 'Evening'}</button>
|
||||
))}
|
||||
</div>
|
||||
<p className="mt-1 text-xs text-[var(--r-sub)]">{reading.heading_citation}</p>
|
||||
<div className="mt-2 max-h-72 overflow-y-auto whitespace-pre-line font-serif text-[15px] leading-relaxed">{reading.body}</div>
|
||||
<p className="mt-2 text-[10px] text-[var(--r-sub)]">Charles Spurgeon, Morning and Evening (1866), via IQ Bible.</p>
|
||||
</details>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user