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