// The prayer journal: what you are praying about (and for whom), the verses tied to each request, the days you
// prayed, and the answers when they come.
import { useEffect, useMemo, useRef, useState } from 'react';
import { useApp } from '../context/AppContext.js';
import PageShell from '../components/study/PageShell.jsx';
import { useToast } from '../components/Toasts.jsx';
import { STATUSES, filterPrayers, daysToAnswer, labelFor, live, parsePrayerImport, parseVerses, pinned as pinnedList, prayersToJson, prayersToMarkdown } from '../utils/prayers.js';
import { localDateString } from '../utils/readingPlans.js';
const field = 'rounded-xl border border-[var(--r-line)] bg-[var(--r-bg)] px-3 py-2 text-sm text-[var(--r-text)] focus:outline-none focus:ring-2 focus:ring-[var(--r-line)]';
const small = 'rounded-lg border border-[var(--r-line)] px-2.5 py-1 text-xs hover:bg-black/5';
const tabBtn = (on) => `rounded-full border px-3 py-1 text-xs font-medium ${on ? 'border-[var(--r-text)] bg-[var(--r-text)] text-[var(--r-bg)]' : 'border-[var(--r-line)] text-[var(--r-sub)] hover:bg-black/5'}`;
function VerseChips({ verses, onOpen }) {
if (!verses?.length) return null;
return (
{verses.map((v, i) => (
onOpen(v.book, v.chapter, v.verse ?? 1)}
className="rounded-full border border-[var(--r-line)] px-2 py-0.5 text-[11px] text-[var(--r-accent)] hover:bg-black/5" title="Read it in the Bible">{labelFor(v)}
))}
);
}
function PrayerCard({ prayer, today, actions, onOpenVerse, pinPos }) {
const [mode, setMode] = useState(null); // null | 'edit' | 'answer' | 'partial'
const [text, setText] = useState('');
const [note, setNote] = useState('');
const prayedToday = prayer.prayedOn.includes(today);
const isPinned = !!pinPos;
const days = daysToAnswer(prayer);
return (
{mode === 'edit' ? (
) : (
{(prayer.forWhom || isPinned || prayer.status) && (
{isPinned && Pinned }
{prayer.status && {STATUSES[prayer.status]} }
{prayer.forWhom && For {prayer.forWhom} }
)}
{prayer.text}
)}
{prayer.status === 'partial' && prayer.answerNote && mode !== 'partial' && (
Partly answered
{prayer.answerNote}
)}
{prayer.answeredAt && (
Answered {new Date(prayer.answeredAt).toLocaleDateString()}{days != null ? ` · after ${days} day${days === 1 ? '' : 's'}` : ''}
{prayer.answerNote &&
{prayer.answerNote}
}
)}
{mode === 'answer' && (
)}
{mode === 'partial' && (
)}
{!prayer.answeredAt && (
actions.toggle(prayer.id)} aria-pressed={prayedToday} className={`${small} ${prayedToday ? 'border-[var(--r-accent)] text-[var(--r-accent)]' : ''}`}>
{prayedToday ? '✓ Prayed today' : 'I prayed'}
)}
{prayer.prayedOn.length > 0 && Prayed on {prayer.prayedOn.length} day{prayer.prayedOn.length === 1 ? '' : 's'} }
{!prayer.answeredAt && isPinned && (
<>
actions.move(prayer.id, -1)} disabled={pinPos.index === 0} aria-label="Move up" title="Move up" className={`${small} disabled:opacity-40`}>↑
actions.move(prayer.id, 1)} disabled={pinPos.index === pinPos.count - 1} aria-label="Move down" title="Move down" className={`${small} disabled:opacity-40`}>↓
>
)}
{!prayer.answeredAt && actions.pin(prayer.id, !isPinned)} aria-pressed={isPinned} className={`${small} ${isPinned ? 'border-[var(--r-accent)] text-[var(--r-accent)]' : ''}`}>{isPinned ? 'Unpin' : 'Pin'} }
{!prayer.answeredAt && (
<>
actions.status(prayer.id, prayer.status === 'waiting' ? '' : 'waiting')} aria-pressed={prayer.status === 'waiting'} className={`${small} ${prayer.status === 'waiting' ? 'border-[var(--r-accent)] text-[var(--r-accent)]' : ''}`}>Waiting
{ setNote(prayer.status === 'partial' ? prayer.answerNote : ''); setMode('partial'); }} aria-pressed={prayer.status === 'partial'} className={`${small} ${prayer.status === 'partial' ? 'border-[var(--r-accent)] text-[var(--r-accent)]' : ''}`}>Partly answered
>
)}
{prayer.answeredAt
? actions.reopen(prayer.id)} className={small}>Pray again
: { setNote(''); setMode('answer'); }} className={small}>Answered }
{ setText(prayer.text); setMode('edit'); }} className={small}>Edit
actions.remove(prayer.id)} className={`${small} text-[var(--r-danger,#b42318)]`}>Delete
);
}
export default function PrayerPage() {
const { prayers, addPrayer, editPrayer, togglePrayed, answerPrayer, reopenPrayer, deletePrayer, pinPrayer, movePrayer, setPrayerStatus, importPrayers, prayerDraft, clearPrayerDraft, jumpToReaderVerse, openBibleReader } = useApp();
const { toast } = useToast();
const [tab, setTab] = useState('open');
const [query, setQuery] = useState('');
const [form, setForm] = useState({ text: '', forWhom: '', verses: '' });
const today = localDateString();
const fileRef = useRef(null);
// "Pray about this verse" from the Reader arrives with the verse filled in
useEffect(() => {
if (prayerDraft) { setForm((f) => ({ ...f, verses: prayerDraft.label })); clearPrayerDraft(); }
}, [prayerDraft, clearPrayerDraft]);
const parsed = useMemo(() => parseVerses(form.verses), [form.verses]);
const shown = filterPrayers(prayers, { tab, query });
const counts = { open: filterPrayers(prayers, { tab: 'open' }).length, answered: filterPrayers(prayers, { tab: 'answered' }).length };
const add = (e) => {
e.preventDefault();
if (!form.text.trim()) return;
addPrayer({ text: form.text, forWhom: form.forWhom, verses: parsed.verses });
setForm({ text: '', forWhom: '', verses: '' });
setTab('open');
};
const actions = {
edit: editPrayer, toggle: togglePrayed, reopen: reopenPrayer, pin: pinPrayer, move: movePrayer, status: setPrayerStatus,
answer: (id, note) => { answerPrayer(id, note); toast('Marked answered. Thank God for it.'); },
remove: (id) => { const undo = deletePrayer(id); toast('Prayer deleted', { action: { label: 'Undo', onClick: undo } }); },
};
const openVerse = (book, chapter, verse) => { jumpToReaderVerse(book, chapter, verse); openBibleReader(); };
const pinOrder = useMemo(() => pinnedList(prayers).map((p) => p.id), [prayers]);
const download = (json = false) => {
const url = URL.createObjectURL(new Blob([json ? prayersToJson(prayers) : prayersToMarkdown(prayers)], { type: json ? 'application/json' : 'text/markdown;charset=utf-8' }));
const a = document.createElement('a');
a.href = url; a.download = json ? 'prayer-journal.json' : 'prayer-journal.md';
document.body.appendChild(a); a.click(); a.remove();
setTimeout(() => URL.revokeObjectURL(url), 1000);
};
const onImportFile = async (e) => {
const file = e.target.files?.[0];
e.target.value = '';
if (!file) return;
let result;
try { result = parsePrayerImport(await file.text(), prayers); } catch { result = { prayers: [], skipped: 0, error: 'Couldn’t read that file.' }; }
if (result.error) { toast(result.error); return; }
if (result.prayers.length) importPrayers(result.prayers);
toast(`Imported ${result.prayers.length} prayer${result.prayers.length === 1 ? '' : 's'}${result.skipped ? ` (${result.skipped} already here or skipped)` : ''}`);
};
return (
{shown.length === 0 ? (
{live(prayers).length === 0 ? 'Nothing here yet. Write down something you’re praying about, and come back to mark how God answers.'
: query ? 'No prayers match that search.' : tab === 'answered' ? 'No answered prayers yet.' : 'Nothing open right now.'}
) : (
)}
);
}