Fix 15 pre-existing ESLint issues in studyPages.tsx; v2.1.1
Publish Container / build (push) Successful in 16m13s
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:
1 parent
841e754e16
commit
c92bb2189a
2 files changed
+37
-46
No files matched your search
+1
-1
@@ -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
@@ -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()
|
||||||
|
|||||||
Reference in new issue
Block a user