Invite-only sign-up, present mode, series, handouts, templates, dictation, progress, sharing
Build and Push Docker Image / build-and-push (push) Successful in 6m47s

- Admin: sign-up Open / Invite only / Closed with single-use invite links
- Present mode for scripts (auto-scroll) and outlines (step through, pace vs planned minutes)
- Series: group studies on the dashboard, filter by series, calendar file for a study's sessions
- Lesson handout: printable page per session (passage, outline, questions, writing lines)
- Save a chunk's notes as your own study template (synced)
- Voice dictation for notes and scripts (browser speech recognition)
- Reading progress: map of the 66 books and a calendar of days read, synced and merged across devices
- Export highlights and bookmarks as Markdown or Word, grouped by named colour
- Verse of the day push to the ntfy topic (needs IQBIBLE_API_KEY)
- Sharing: co-teacher editing via single-use invite links; read-only links can expire and take a passcode

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 5.5 committed 2026-10-06 10:26:43 -04:00
1 parent 88bc59bac0
commit 54cb1c217d
73 files changed
+2739 -134

No files matched your search

+16
View File
@@ -85,6 +85,8 @@ export default function SettingsPage() {
ntfyUrl: authUser?.ntfyUrl ?? '',
ntfyTopic: authUser?.ntfyTopic ?? '',
reminderTime: authUser?.reminderTime ?? '07:00',
votdEnabled: authUser?.votdEnabled ?? false,
votdTime: authUser?.votdTime ?? '06:30',
});
const [reminderBusy, setReminderBusy] = useState(false);
const [reminderError, setReminderError] = useState('');
@@ -426,6 +428,20 @@ export default function SettingsPage() {
/>
</label>
</div>
{authUser?.votdAvailable && (
<div className="rounded-xl border border-[var(--r-line)] p-3">
<label className="flex items-center gap-2 text-sm font-medium text-[var(--r-text)]">
<input type="checkbox" checked={reminderForm.votdEnabled} onChange={(e) => setReminderForm((f) => ({ ...f, votdEnabled: e.target.checked }))} className="h-4 w-4 accent-[var(--r-accent)]" />
Also send the verse of the day
</label>
<label className="mt-2 block max-w-xs text-sm font-medium text-[var(--r-text)]">
Send it at
<input type="time" value={reminderForm.votdTime} onChange={(e) => setReminderForm((f) => ({ ...f, votdTime: e.target.value }))}
className="mt-1 block w-full 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)]" />
</label>
<span className="mt-1 block text-xs text-[var(--r-sub)]">A short notification with today’s verse (KJV) to the same topic, even if the reading reminder is off.</span>
</div>
)}
<label className="block max-w-xs text-sm font-medium text-[var(--r-text)]">
ntfy server <span className="text-[var(--r-sub)]">(optional — defaults to ntfy.sh)</span>
<input