import { useEffect, useMemo, useState, useCallback } from 'react' import { Link, useLocation, useNavigate, useParams } from 'react-router-dom' import type { SiteContent, StudyProgram, StudySection } from './content' import { DEFAULT_COLOSSIANS_STUDY_SECTIONS } from './colossiansStudyData' type Props = { content: SiteContent } type StudyAuthState = { checked: boolean authenticated: boolean username: string enrolledStudySlugs?: string[] displayName?: string subscribeNewsletter?: boolean studyRemindersEnabled?: boolean avatarUrl?: string } type StudyAuthStatusResponse = { authenticated: boolean username: string enrolledStudySlugs?: string[] displayName?: string subscribeNewsletter?: boolean studyRemindersEnabled?: boolean avatarUrl?: string } type StudyAccountOverview = { profile: { username: string displayName: string subscribeNewsletter: boolean studyRemindersEnabled: boolean avatarUrl?: string } stats: { noteCount: number memberSince: string lastLoginAt: string | null } studies: Array<{ slug: string title: string status: 'active' | 'planned' enrolled: boolean totalLessons: number completedLessons: number noteCount: number }> } type StudyNotesMap = Record type StudyProgress = { completedSectionIds: string[] } type StudyCommunityReply = { id: string authorName: string authorAvatarUrl?: string message: string createdAt: string } type StudyCommunityPost = { id: string studySlug: string sectionId: string authorName: string authorAvatarUrl?: string message: string createdAt: string replies: StudyCommunityReply[] } function getLegacyColossiansStudy(content: SiteContent): StudyProgram { const legacySections = content.colossiansStudySections?.length ? content.colossiansStudySections : DEFAULT_COLOSSIANS_STUDY_SECTIONS return { id: 'study-colossians', slug: 'colossians', title: 'Colossians: Rooted in Christ', description: 'Walk through Colossians in guided lessons with commentary, Greek notes, and discussion prompts.', status: 'active', difficulty: 'intermediate', estimatedHours: 12, completionBadge: 'Colossians Completion', numberOfChapters: 4, sections: legacySections, } } function getStudies(content: SiteContent): StudyProgram[] { if (Array.isArray(content.studies) && content.studies.length > 0) return content.studies return [getLegacyColossiansStudy(content)] } function getStudyBySlug(studies: StudyProgram[], slug: string | undefined) { const safeSlug = (slug ?? 'colossians').trim().toLowerCase() return studies.find(study => study.slug === safeSlug) } function getSectionById(sections: StudySection[], sectionId: string | undefined) { if (!sectionId) return undefined return sections.find(section => section.id === sectionId) } function getLessonNumber(sections: StudySection[], sectionId: string | undefined) { const index = sections.findIndex(item => item.id === sectionId) return index >= 0 ? index + 1 : 0 } function getPrimaryQuestion(section: StudySection) { if (section.focusQuestion?.trim()) return section.focusQuestion.trim() return section.studyQuestions.length > 0 ? section.studyQuestions[0] : 'How does this passage shape the way we follow Christ this week?' } function getChapterSummaries(study: StudyProgram) { const highestSectionChapter = study.sections.reduce((max, section) => Math.max(max, section.chapter), 0) const totalChapters = Math.max(study.numberOfChapters, highestSectionChapter) return Array.from({ length: totalChapters }, (_, index) => { const chapter = index + 1 const lessonCount = study.sections.filter(section => section.chapter === chapter).length return { chapter, lessonCount } }) } function getSectionReleaseDate(section: StudySection): Date | null { const candidateValues = [ section?.releasedAt, (section as StudySection & { releaseDate?: string }).releaseDate, (section as StudySection & { availableAt?: string }).availableAt, (section as StudySection & { publishAt?: string }).publishAt, ] for (const candidate of candidateValues) { if (typeof candidate !== 'string' || !candidate.trim()) continue const parsed = new Date(candidate) if (!Number.isNaN(parsed.getTime())) return parsed } return null } function isNewLesson(section: StudySection): boolean { const releaseDate = getSectionReleaseDate(section) if (!releaseDate) return false const now = new Date() const daysSinceRelease = (now.getTime() - releaseDate.getTime()) / (1000 * 60 * 60 * 24) return daysSinceRelease >= 0 && daysSinceRelease <= 14 } function isComingSoon(section: StudySection): boolean { const releaseDate = getSectionReleaseDate(section) if (!releaseDate) return false const now = new Date() return releaseDate > now } function isSectionReleased(section: StudySection): boolean { const releaseDate = getSectionReleaseDate(section) if (!releaseDate) return false return releaseDate <= new Date() } function getReleaseDateDisplay(section: StudySection): string { const date = getSectionReleaseDate(section) if (!date) return '' return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }) } function getNoteId(studySlug: string, sectionId: string) { return `${studySlug}--${sectionId}` } function getDisplayName(auth: StudyAuthState) { return auth.displayName?.trim() || auth.username.split('@')[0] || 'student' } function formatCommunityDate(value: string) { const date = new Date(value) if (Number.isNaN(date.getTime())) return '' return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }) } function CommunityBoard({ study, auth, isEnrolled, sectionFilter, }: { study: StudyProgram auth: StudyAuthState isEnrolled: boolean sectionFilter?: string }) { const [posts, setPosts] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState('') const [message, setMessage] = useState('') const [replyDrafts, setReplyDrafts] = useState>({}) const [replyingTo, setReplyingTo] = useState(null) const [submitting, setSubmitting] = useState(false) const [newPost, setNewPost] = useState('') const [activeSection, setActiveSection] = useState(sectionFilter ?? '') const canParticipate = auth.authenticated && isEnrolled const communityDisplayName = getDisplayName(auth) const releasedSections = study.sections.filter(isSectionReleased) const visiblePosts = activeSection ? posts.filter(p => p.sectionId === activeSection) : posts useEffect(() => { let cancelled = false async function load() { if (!canParticipate) { setPosts([]) setLoading(false) return } setLoading(true) setError('') try { const data = await readJson<{ posts?: StudyCommunityPost[] }>('/api/study-community?' + new URLSearchParams({ studySlug: study.slug }).toString()) if (cancelled) return setPosts(Array.isArray(data.posts) ? data.posts : []) } catch (err) { if (cancelled) return setError(err instanceof Error ? err.message : 'Unable to load the community right now.') } finally { if (cancelled) return setLoading(false) } } void load() return () => { cancelled = true } }, [canParticipate, study.slug]) async function submitPost() { if (!newPost.trim() || !canParticipate) return setSubmitting(true) setMessage('') setError('') try { const data = await readJson<{ post?: StudyCommunityPost }>('/api/study-community/posts', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ studySlug: study.slug, sectionId: activeSection, message: newPost }), }) if (data.post) setPosts(prev => [data.post!, ...prev]) setNewPost('') setMessage('Posted to the community.') } catch (err) { setError(err instanceof Error ? err.message : 'Unable to post right now.') } finally { setSubmitting(false) } } async function submitReply(postId: string) { const reply = replyDrafts[postId]?.trim() if (!reply || !canParticipate) return setSubmitting(true) setError('') try { const data = await readJson<{ reply?: StudyCommunityReply }>(`/api/study-community/posts/${encodeURIComponent(postId)}/replies`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message: reply }), }) if (data.reply) { setPosts(prev => prev.map(post => post.id === postId ? { ...post, replies: [...(post.replies ?? []), data.reply!] } : post)) } setReplyDrafts(prev => ({ ...prev, [postId]: '' })) setReplyingTo(null) setMessage('Reply posted.') } catch (err) { setError(err instanceof Error ? err.message : 'Unable to post your reply.') } finally { setSubmitting(false) } } return (
{/* Section filter tabs */} {releasedSections.length > 1 && canParticipate && (
{releasedSections.map(s => ( ))}
)} {!auth.checked &&

Checking your account status...

} {auth.checked && !auth.authenticated && (

Sign in to join the study community.

Create Account My Account
)} {auth.checked && auth.authenticated && !isEnrolled && (

Enroll in {study.title} to participate in the community.

Go to My Account
)} {canParticipate && (
{activeSection && (

Posting to: {releasedSections.find(s => s.id === activeSection)?.title ?? 'this lesson'}

)}