Files
study-bible-project/src/components/DailyFive.jsx
T
nmemmertandClaude Sonnet 5.5 eccc5e509d Studying: daily five minutes, reading list, plan-day studies use your template
- Home: "Your five minutes": a rotating Observe/Interpret/Apply prompt on the
  passage whose matching note is thinnest in your own words, a 5:00 clock and a
  box that appends to that note. Pasting is blocked; sessions show in Insights.
- Study: a Reading list tab (R) for the study: books and articles to consult,
  with a note and a tick; merges across devices.
- Starting a study from a reading-plan day now uses the notes template you last
  picked when starting a study by hand (logic moved to utils/planStudy.js).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-14 13:51:49 -04:00

67 lines
4.0 KiB
React
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Home card: your five minutes for today. A short prompt on a passage from your studies, a five-minute clock, and a
// box to write in. What you write is added to that passage's notes. Pasting is turned off here on purpose.
import { useEffect, useMemo, useRef, useState } from 'react';
import { useApp } from '../context/AppContext.js';
import { loadProjectById } from '../utils/projectStorage.js';
import { FIELD_NAMES, MINUTES, dailyPatch, pickDaily } from '../utils/dailyFive.js';
import { formatClock } from '../utils/present.js';
const box = 'mb-4 rounded-xl border border-[var(--r-line)] bg-[var(--r-bar)] px-4 py-3 text-sm';
export default function DailyFive() {
const { projectIndex, saveChunkPatch } = useApp();
const [step, setStep] = useState('card'); // card -> write -> done
const [text, setText] = useState('');
const [left, setLeft] = useState(MINUTES * 60);
const [pasteTried, setPasteTried] = useState(false);
const pick = useMemo(() => pickDaily(projectIndex.map((e) => loadProjectById(e.id)).filter(Boolean)), [projectIndex]);
const started = useRef(null);
useEffect(() => {
if (step !== 'write') return undefined;
started.current = Date.now();
const t = window.setInterval(() => setLeft(Math.max(0, MINUTES * 60 - Math.round((Date.now() - started.current) / 1000))), 1000);
return () => window.clearInterval(t);
}, [step]);
if (step === 'done') return <div role="status" className={box}>Saved to your notes. Same time tomorrow.</div>;
if (!pick) return null;
const { prompt } = pick;
const save = () => {
saveChunkPatch(pick.projectId, pick.chunk.id, dailyPatch(pick.chunk, prompt.field, text));
setStep('done');
};
if (step === 'card') {
return (
<div className={`${box} flex flex-wrap items-center justify-between gap-3`}>
<span><span className="font-medium">Your five minutes:</span> {pick.reference} <span className="text-[var(--r-sub)]">· {FIELD_NAMES[prompt.field]} · {pick.projectTitle}</span></span>
<button type="button" onClick={() => { setText(''); setLeft(MINUTES * 60); setPasteTried(false); setStep('write'); }} className="rounded-full bg-[var(--r-text)] px-3 py-1 text-xs font-medium text-[var(--r-bg)]">Start</button>
</div>
);
}
return (
<section aria-label="Your five minutes" className={box}>
<div className="flex items-baseline justify-between gap-3">
<p className="font-medium">{pick.reference} <span className="font-normal text-[var(--r-sub)]">· {FIELD_NAMES[prompt.field]}</span></p>
<span role="timer" aria-label="Time left" className={`font-mono text-sm tabular-nums ${left === 0 ? 'text-[var(--r-accent)]' : 'text-[var(--r-sub)]'}`}>{left === 0 ? 'Time’s up, finish your thought' : formatClock(left)}</span>
</div>
{pick.verses.length > 0 && (
<div className="mt-2 max-h-40 overflow-y-auto rounded-lg bg-[var(--r-bg)] p-3 font-serif text-[13px] leading-relaxed">
{pick.verses.map((v) => <p key={v.number}><sup className="mr-1 font-sans text-[var(--r-accent)]">{v.number}</sup>{v.text}</p>)}
</div>
)}
<p className="mt-2 text-xs text-[var(--r-sub)]">{prompt.text}</p>
<textarea aria-label="Your five minutes" value={text} onChange={(e) => setText(e.target.value)} rows={6} autoFocus
onPaste={(e) => { e.preventDefault(); setPasteTried(true); }}
className="mt-2 w-full resize-y rounded-xl border border-[var(--r-line)] bg-[var(--r-bg)] px-3 py-2 font-serif text-[15px] leading-7 focus:outline-none" />
{pasteTried && <p role="note" className="mt-1 text-xs text-[var(--r-accent)]">Pasting is off here. Type it in your own words; that’s what the five minutes are for.</p>}
<div className="mt-2 flex gap-2">
<button type="button" disabled={!text.trim()} onClick={save} className="rounded-full bg-[var(--r-text)] px-3 py-1 text-xs font-medium text-[var(--r-bg)] disabled:opacity-40">Save to my notes</button>
<button type="button" onClick={() => setStep('card')} className="text-xs text-[var(--r-sub)] hover:underline">Not now</button>
</div>
</section>
);
}