Fix 15 pre-existing ESLint issues in studyPages.tsx; v2.1.1
Publish Container / build (push) Successful in 16m13s

Fixes two real bugs: useEffect and usePageMeta were called after an early
"study not found" return, violating React's rules of hooks (could corrupt
hook state if a study slug resolves inconsistently across renders). Also
removes dead code (unused NOTE_ANCHORS array, an unused state pair, a stale
eslint-disable comment), fixes three return-in-finally statements that could
mask exceptions, adds the missing study?.slug effect dependency, and cleans
up an unnecessary regex escape and an empty catch block.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 5 committed 2026-09-24 09:26:03 -04:00
1 parent 841e754e16
commit c92bb2189a
2 files changed
+37 -46

No files matched your search

+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "siteforge", "name": "siteforge",
"private": true, "private": true,
"version": "2.1.0", "version": "2.1.1",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
+36 -45
View File
@@ -211,13 +211,7 @@ function getNoteId(studySlug: string, sectionId: string) {
return `${studySlug}--${sectionId}` return `${studySlug}--${sectionId}`
} }
const NOTE_ANCHORS = [ type NoteAnchorId = 'scripture' | 'commentary' | 'questions' | 'general'
{ id: 'scripture', label: 'Scripture Text' },
{ id: 'commentary', label: 'Commentary' },
{ id: 'questions', label: 'Reflection Questions' },
] as const
type NoteAnchorId = typeof NOTE_ANCHORS[number]['id'] | 'general'
const ANCHOR_LABELS: Record<string, string> = { const ANCHOR_LABELS: Record<string, string> = {
scripture: 'Scripture Text', scripture: 'Scripture Text',
@@ -233,7 +227,9 @@ function parseNoteMap(raw: string): Record<string, string> {
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) { if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
return parsed as Record<string, string> return parsed as Record<string, string>
} }
} catch {} } catch {
// Not valid JSON — treat the raw string as legacy plain-text notes.
}
return { general: raw } return { general: raw }
} }
@@ -671,8 +667,7 @@ export function StudyLandingPage({ content }: Props) {
if (cancelled) return if (cancelled) return
setError(err instanceof Error ? err.message : 'Unable to load your study dashboard.') setError(err instanceof Error ? err.message : 'Unable to load your study dashboard.')
} finally { } finally {
if (cancelled) return if (!cancelled) setLoading(false)
setLoading(false)
} }
} }
@@ -1045,7 +1040,7 @@ export function StudyLandingPage({ content }: Props) {
) )
} }
const EMAIL_REGEX = /^[a-zA-Z0-9._%+\-]{1,64}@[a-zA-Z0-9.\-]{1,253}\.[a-zA-Z]{2,}$/ const EMAIL_REGEX = /^[a-zA-Z0-9._%+-]{1,64}@[a-zA-Z0-9.-]{1,253}\.[a-zA-Z]{2,}$/
export function StudySignupPage() { export function StudySignupPage() {
const navigate = useNavigate() const navigate = useNavigate()
@@ -1107,7 +1102,7 @@ export function StudySignupPage() {
} finally { } finally {
setBusy(false) setBusy(false)
} }
}, [mode, email, password, subscribeNewsletter]) }, [mode, email, password, subscribeNewsletter, navigate])
const submitTotp = useCallback(async () => { const submitTotp = useCallback(async () => {
if (!totpCode.trim()) { setMessage('Please enter your 6-digit code.'); return } if (!totpCode.trim()) { setMessage('Please enter your 6-digit code.'); return }
@@ -1127,7 +1122,7 @@ export function StudySignupPage() {
} finally { } finally {
setBusy(false) setBusy(false)
} }
}, [totpPendingToken, totpCode, email]) }, [totpPendingToken, totpCode, email, navigate])
const submitForgot = useCallback(async () => { const submitForgot = useCallback(async () => {
if (!email.trim() || !EMAIL_REGEX.test(email.trim())) { if (!email.trim() || !EMAIL_REGEX.test(email.trim())) {
@@ -1497,6 +1492,7 @@ export function ColossiansStudyIndexPage({ content }: Props) {
const [enrollMessage, setEnrollMessage] = useState('') const [enrollMessage, setEnrollMessage] = useState('')
const [studyProgress, setStudyProgress] = useState<StudyProgress>({ completedSectionIds: [], bookmarkedSectionIds: [] }) const [studyProgress, setStudyProgress] = useState<StudyProgress>({ completedSectionIds: [], bookmarkedSectionIds: [] })
const [progressLoading, setProgressLoading] = useState(false) const [progressLoading, setProgressLoading] = useState(false)
const enrolled = isEnrolledInStudy(auth, study?.slug)
useEffect(() => { useEffect(() => {
let cancelled = false let cancelled = false
@@ -1519,26 +1515,6 @@ export function ColossiansStudyIndexPage({ content }: Props) {
} }
}, []) }, [])
if (!study) {
return (
<main className="thanks-page" aria-label="Study not found">
<div className="thanks-card">
<p className="eyebrow">Study Hub</p>
<h1>Study not found</h1>
<p>The study you requested is not available yet.</p>
<Link to="/study" className="btn-primary">Back to Studies</Link>
</div>
</main>
)
}
const sections = study.sections
const chapterSummaries = getChapterSummaries(study)
const releasedSections = sections.filter(isSectionReleased)
const firstReleasedSection = releasedSections[0]
const populatedChapterCount = chapterSummaries.filter(chapter => chapter.lessonCount > 0).length
const enrolled = isEnrolledInStudy(auth, study.slug)
useEffect(() => { useEffect(() => {
if (!auth.checked || !auth.authenticated || !study || !enrolled) return if (!auth.checked || !auth.authenticated || !study || !enrolled) return
let cancelled = false let cancelled = false
@@ -1563,6 +1539,30 @@ export function ColossiansStudyIndexPage({ content }: Props) {
} }
}, [auth.checked, auth.authenticated, study, enrolled]) }, [auth.checked, auth.authenticated, study, enrolled])
usePageMeta(
study ? `${study.title} | Bible Study` : 'Study Not Found',
study?.description,
)
if (!study) {
return (
<main className="thanks-page" aria-label="Study not found">
<div className="thanks-card">
<p className="eyebrow">Study Hub</p>
<h1>Study not found</h1>
<p>The study you requested is not available yet.</p>
<Link to="/study" className="btn-primary">Back to Studies</Link>
</div>
</main>
)
}
const sections = study.sections
const chapterSummaries = getChapterSummaries(study)
const releasedSections = sections.filter(isSectionReleased)
const firstReleasedSection = releasedSections[0]
const populatedChapterCount = chapterSummaries.filter(chapter => chapter.lessonCount > 0).length
async function enrollInStudy() { async function enrollInStudy() {
if (!study) return if (!study) return
setEnrollMessage('') setEnrollMessage('')
@@ -1583,11 +1583,6 @@ export function ColossiansStudyIndexPage({ content }: Props) {
} }
} }
usePageMeta(
`${study.title} | Bible Study`,
study.description,
)
return ( return (
<main className="study-index-page" aria-label={`${study.title} study`}> <main className="study-index-page" aria-label={`${study.title} study`}>
<section className="section-study-course-hero"> <section className="section-study-course-hero">
@@ -1748,7 +1743,6 @@ export function ColossiansStudySectionPage({ content }: Props) {
const [showCelebration, setShowCelebration] = useState(false) const [showCelebration, setShowCelebration] = useState(false)
const [bannerDismissed, setBannerDismissed] = useState(() => localStorage.getItem('study-banner-dismissed-v1') === '1') const [bannerDismissed, setBannerDismissed] = useState(() => localStorage.getItem('study-banner-dismissed-v1') === '1')
const [autoSaveMsg, setAutoSaveMsg] = useState('') const [autoSaveMsg, setAutoSaveMsg] = useState('')
const [_checkpointReflection, _setCheckpointReflection] = useState('')
const [bsbPassage, setBsbPassage] = useState<string | null>(null) const [bsbPassage, setBsbPassage] = useState<string | null>(null)
const [bsbPassageLoading, setBsbPassageLoading] = useState(false) const [bsbPassageLoading, setBsbPassageLoading] = useState(false)
@@ -1927,7 +1921,6 @@ export function ColossiansStudySectionPage({ content }: Props) {
} }
}, 2000) }, 2000)
return () => clearTimeout(timer) return () => clearTimeout(timer)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [noteMap, noteId, auth.authenticated, noteLoading]) }, [noteMap, noteId, auth.authenticated, noteLoading])
useEffect(() => { useEffect(() => {
@@ -2596,8 +2589,7 @@ export function ColossiansStudyNotesPage({ content }: Props) {
if (cancelled) return if (cancelled) return
setMessage(err instanceof Error ? err.message : 'Unable to load notes.') setMessage(err instanceof Error ? err.message : 'Unable to load notes.')
} finally { } finally {
if (cancelled) return if (!cancelled) setLoading(false)
setLoading(false)
} }
} }
@@ -2605,7 +2597,7 @@ export function ColossiansStudyNotesPage({ content }: Props) {
return () => { return () => {
cancelled = true cancelled = true
} }
}, []) }, [study?.slug])
if (!study) { if (!study) {
return ( return (
@@ -3826,8 +3818,7 @@ export function StudyQuizPage({ content }: Props) {
if (cancelled) return if (cancelled) return
setError(err instanceof Error ? err.message : 'Unable to load answers.') setError(err instanceof Error ? err.message : 'Unable to load answers.')
} finally { } finally {
if (cancelled) return if (!cancelled) setLoading(false)
setLoading(false)
} }
} }
void load() void load()