All green. You're good to build and deploy. Here's a summary of everything done since your last commit:

---

**🐛 Bugs fixed**
- Study hub/lesson pages turned gold — CSS merge error with `.study-index-page` fixed
- Lesson announcements not saving — `announcement` field missing from `sanitizeColossiansStudySections` whitelist
- 2FA email method blocked with "not configured" — guard checked `totpSecret` instead of `twoFaMethod`
- `STUDY_REMINDERS_FILE` constant never defined — server would crash on first reminder write
- `sanitizeStudyUsers` stripped all 2FA fields — data lost on every restart
- `scheduleStudyReminders` missing from refactored modules

** Features added**
- Commentary supports paragraph formatting (double newline = new paragraph)
- Lesson announcement banner — full-width gold stripe at top of lesson page
- "Take the Quiz" renamed to "Discussion Questions" throughout
- Students must complete checkpoint before unlocking next lesson (lesson list + Next Lesson button both gate on it)
- No checkpoint questions → simple Mark as Completed button as fallback
- Truth For Life syndication widget on homepage
- Newsletter welcome email updated to your new HTML template

**🔧 Maintenance**
- `server.js` refactored from 6,013 lines into 12 focused modules
This commit is contained in:
nmemmert
2026-06-04 09:17:34 -04:00
parent 07519b4f48
commit cb78b6522d
5 changed files with 228 additions and 73 deletions
+35
View File
@@ -1112,6 +1112,41 @@
}
/* ── Home Jump Section ── */
/* ── Truth For Life Widget ── */
.section-tfl {
background: #080808;
border-top: 1px solid rgba(201, 168, 76, 0.18);
border-bottom: 1px solid rgba(201, 168, 76, 0.18);
padding: 5rem 0;
text-align: center;
}
.tfl-attribution {
color: #8a7f5a;
font-size: 0.9rem;
margin: -0.5rem 0 2.5rem;
}
.tfl-link {
color: #c9a84c;
text-decoration: none;
}
.tfl-link:hover {
text-decoration: underline;
}
.tfl-widget-wrap {
max-width: 760px;
margin: 0 auto;
text-align: left;
}
/* Scope TFL widget styles to avoid bleed */
.tfl-widget-wrap * {
box-sizing: border-box;
}
.section-share {
background: #080808;
border-top: 1px solid rgba(201, 168, 76, 0.18);
+53
View File
@@ -1056,6 +1056,57 @@ function HomepageNewsletterSection() {
)
}
type TflWidgetType = 'audiodevo' | 'abdevotional'
function TruthForLifeWidget({ type = 'audiodevo' }: { type?: TflWidgetType }) {
const containerId = `tfl-syndicate-container-${type}`
useEffect(() => {
// Remove any previous instance of this script to allow re-init on re-mount
const existingScript = document.getElementById('tfl-syn-script-' + type)
if (existingScript) existingScript.remove()
// Clear the container so the widget can repopulate it
const container = document.getElementById(containerId)
if (container) container.innerHTML = ''
const script = document.createElement('script')
script.id = 'tfl-syn-script-' + type
script.src = `https://www.truthforlife.org/static/js/responsive/lib/syndicate.js?type=${type}&id=458890`
script.type = 'text/javascript'
// The TFL script looks for #tfl-syndicate-container by ID — temporarily
// rename our container to match, then rename back after the script fires
if (container) container.id = 'tfl-syndicate-container'
document.body.appendChild(script)
// Restore the namespaced id after a tick so React doesn't lose track
const restore = setTimeout(() => {
const el = document.getElementById('tfl-syndicate-container')
if (el) el.id = containerId
}, 2000)
return () => {
clearTimeout(restore)
script.remove()
}
}, [type])
return (
<section className="section-tfl" aria-label="Truth For Life devotional">
<div className="section-inner">
<p className="eyebrow">Daily Devotional</p>
<h2 className="section-heading">
<span className="ornament"></span> Truth For Life <span className="ornament"></span>
</h2>
<p className="tfl-attribution">
Teaching from <a href="https://www.truthforlife.org" target="_blank" rel="noreferrer" className="tfl-link">Truth For Life</a> with Alistair Begg
</p>
<div id={containerId} className="tfl-widget-wrap" />
</div>
</section>
)
}
function HomepageQACallout() {
const [questions, setQuestions] = useState<{ id: string; firstName: string; question: string; answer: string }[]>([])
@@ -1213,6 +1264,8 @@ function LandingPage({ content }: { content: SiteContent }) {
</div>
</section>
<TruthForLifeWidget type="audiodevo" />
<ExternalSitesSection content={content} />
<section className="section-home-jump" aria-label="Homepage navigation">