From c92bb2189a7626738bc44822dd7d4046d025b0b0 Mon Sep 17 00:00:00 2001 From: nmemmert Date: Thu, 24 Sep 2026 09:26:03 -0400 Subject: [PATCH] Fix 15 pre-existing ESLint issues in studyPages.tsx; v2.1.1 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 --- package.json | 2 +- src/studyPages.tsx | 81 +++++++++++++++++++++------------------------- 2 files changed, 37 insertions(+), 46 deletions(-) diff --git a/package.json b/package.json index 74cb672..639f108 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "siteforge", "private": true, - "version": "2.1.0", + "version": "2.1.1", "type": "module", "scripts": { "dev": "vite", diff --git a/src/studyPages.tsx b/src/studyPages.tsx index a086015..e8acf8f 100644 --- a/src/studyPages.tsx +++ b/src/studyPages.tsx @@ -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 = { scripture: 'Scripture Text', @@ -233,7 +227,9 @@ function parseNoteMap(raw: string): Record { if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) { return parsed as Record } - } 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({ 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 ( -
-
-

Study Hub

-

Study not found

-

The study you requested is not available yet.

- Back to Studies -
-
- ) - } - - 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 ( +
+
+

Study Hub

+

Study not found

+

The study you requested is not available yet.

+ Back to Studies +
+
+ ) + } + + 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 (
@@ -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(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()