From eff119e7f076455615c96cca1c99949e799db52e Mon Sep 17 00:00:00 2001 From: nmemmert Date: Fri, 9 Oct 2026 12:00:09 -0400 Subject: [PATCH] Redesign study Notes, Reflection questions and Community pages; v2.12.0 - Notes: two-pane reader with searchable lesson list and snippets, track chips, friendlier empty states - Reflection questions: numbered question cards, answered progress bar, unsaved-changes status, sticky save bar, share-to-discussion only when discussion is open - Community: compact header, lesson browser sidebar with post counts (select on mobile), guidelines card, card-style posts Co-Authored-By: Claude Sonnet 5.5 --- package-lock.json | 4 +- package.json | 2 +- src/App.css | 502 +++++++++++++++++++++++++++++++++++++++++++++ src/studyPages.tsx | 470 ++++++++++++++++++++++++++---------------- 4 files changed, 803 insertions(+), 175 deletions(-) diff --git a/package-lock.json b/package-lock.json index df5f8b9..809342e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "siteforge", - "version": "2.11.2", + "version": "2.12.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "siteforge", - "version": "2.11.2", + "version": "2.12.0", "dependencies": { "@aws-sdk/client-s3": "^3.1135.0", "@dnd-kit/core": "^6.3.1", diff --git a/package.json b/package.json index 1b407fe..387cbcd 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "siteforge", "private": true, - "version": "2.11.2", + "version": "2.12.0", "type": "module", "scripts": { "dev": "vite", diff --git a/src/App.css b/src/App.css index 7697f0c..8309044 100644 --- a/src/App.css +++ b/src/App.css @@ -4156,6 +4156,346 @@ border-left: 3px solid rgba(111, 207, 151, 0.7); } +/* ── Reflection questions ── */ +.quiz-page { + padding: clamp(1.5rem, 4vw, 2.5rem) 1.25rem 5rem; + background: + radial-gradient(ellipse at top, rgba(201, 168, 76, 0.09) 0%, transparent 55%), + var(--brand-black); +} + +.quiz-shell { max-width: 780px; margin: 0 auto; } + +.quiz-header { margin: 1rem 0 1.5rem; } +.quiz-header .eyebrow { margin: 0; } + +.quiz-header h1 { + margin: 0.15rem 0 0.2rem; + font-family: var(--brand-font-heading); + font-size: clamp(1.9rem, 4.5vw, 2.6rem); + color: var(--brand-warm-white); +} + +.quiz-reference { + margin: 0 0 0.75rem; + color: var(--brand-gold); + font-size: 0.82rem; + letter-spacing: 0.12em; + text-transform: uppercase; +} + +.quiz-intro { margin: 0 0 1.25rem; color: var(--brand-muted); line-height: 1.6; max-width: 600px; } + +.quiz-progress-label { + display: flex; + justify-content: space-between; + gap: 1rem; + margin-bottom: 0.4rem; + font-size: 0.85rem; + color: var(--brand-muted); +} + +.quiz-progress-label a { color: var(--brand-gold); text-decoration: none; } +.quiz-progress-label a:hover { text-decoration: underline; } + +.quiz-form { display: flex; flex-direction: column; gap: 1rem; } + +.quiz-card { + padding: 1.25rem 1.35rem; + border: 1px solid rgba(201, 168, 76, 0.2); + border-radius: 16px; + background: rgba(17, 17, 15, 0.84); + transition: border-color 0.2s ease; +} + +.quiz-card:focus-within { border-color: rgba(201, 168, 76, 0.55); } +.quiz-card--answered { border-left: 3px solid rgba(111, 207, 151, 0.7); } + +.quiz-card-head { display: flex; gap: 0.85rem; align-items: flex-start; margin-bottom: 0.85rem; } + +.quiz-card-number { + flex-shrink: 0; + width: 2rem; + height: 2rem; + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 50%; + border: 1px solid rgba(201, 168, 76, 0.55); + background: rgba(201, 168, 76, 0.12); + color: var(--brand-gold); + font-family: var(--brand-font-heading); + font-weight: 700; +} + +.quiz-card-question { + font-family: var(--brand-font-heading); + font-size: 1.25rem; + line-height: 1.35; + color: var(--brand-warm-white); + padding-top: 0.1rem; +} + +.quiz-card-textarea { + width: 100%; + min-height: 110px; + box-sizing: border-box; + resize: vertical; + padding: 0.8rem 1rem; + border: 1px solid rgba(201, 168, 76, 0.22); + border-radius: 12px; + background: rgba(255, 255, 255, 0.04); + color: var(--brand-warm-white); + font-family: var(--brand-font-body); + font-size: 1rem; + line-height: 1.6; +} + +.quiz-card-textarea:focus { outline: none; border-color: rgba(201, 168, 76, 0.6); } + +.quiz-card-foot { + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + margin-top: 0.6rem; + flex-wrap: wrap; +} + +.quiz-card-count { font-size: 0.78rem; color: var(--brand-muted); } + +.quiz-savebar { + position: sticky; + bottom: 0.75rem; + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + flex-wrap: wrap; + padding: 0.85rem 1.1rem; + border: 1px solid rgba(201, 168, 76, 0.35); + border-radius: 14px; + background: rgba(14, 14, 12, 0.96); + backdrop-filter: blur(6px); + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5); +} + +.quiz-savebar-status { margin: 0; font-size: 0.9rem; color: var(--brand-muted); } +.quiz-savebar-actions { display: flex; gap: 0.6rem; flex-wrap: wrap; } + +@media (max-width: 560px) { + .quiz-savebar { padding: 0.6rem 0.75rem; gap: 0.5rem; } + .quiz-savebar-status { width: 100%; font-size: 0.8rem; } + .quiz-savebar-actions { width: 100%; flex-wrap: nowrap; } + .quiz-savebar-actions .btn-primary, + .quiz-savebar-actions .btn-secondary { flex: 1; justify-content: center; padding: 0.55rem 0.5rem; font-size: 0.8rem; letter-spacing: 0.04em; } +} + +/* ── My Notes ── */ +.notes-page { + padding: clamp(1.5rem, 4vw, 2.5rem) 1.25rem 4rem; + background: + radial-gradient(ellipse at top, rgba(201, 168, 76, 0.09) 0%, transparent 55%), + var(--brand-black); +} + +.notes-shell { max-width: 1040px; margin: 0 auto; } + +.notes-header { + display: flex; + align-items: flex-end; + justify-content: space-between; + flex-wrap: wrap; + gap: 1rem; + margin: 1rem 0 1.25rem; +} + +.notes-header .eyebrow { margin: 0; } + +.notes-header h1 { + margin: 0.15rem 0 0.2rem; + font-family: var(--brand-font-heading); + font-size: clamp(1.9rem, 4.5vw, 2.6rem); + color: var(--brand-warm-white); +} + +.notes-subtitle { margin: 0; color: var(--brand-muted); } + +.notes-header-actions { display: flex; gap: 0.6rem; flex-wrap: wrap; } + +.notes-tracks { + display: flex; + gap: 0.5rem; + flex-wrap: wrap; + margin-bottom: 1.25rem; +} + +.notes-track { + padding: 0.4rem 1rem; + border: 1px solid rgba(201, 168, 76, 0.3); + border-radius: 999px; + color: var(--brand-muted); + text-decoration: none; + font-size: 0.88rem; +} + +.notes-track:hover { color: var(--brand-warm-white); border-color: rgba(201, 168, 76, 0.6); } + +.notes-track--active { + background: var(--brand-gold); + border-color: var(--brand-gold); + color: var(--brand-black); + font-weight: 600; +} + +.notes-layout { + display: grid; + grid-template-columns: minmax(250px, 340px) minmax(0, 1fr); + gap: 1.25rem; + align-items: start; +} + +.notes-list-pane { + position: sticky; + top: 5.5rem; + max-height: calc(100vh - 7rem); + overflow-y: auto; + padding: 0.9rem; + border: 1px solid rgba(201, 168, 76, 0.2); + border-radius: 16px; + background: rgba(17, 17, 15, 0.84); +} + +.notes-search { + width: 100%; + margin-bottom: 0.75rem; + padding: 0.6rem 0.9rem; + border: 1px solid rgba(201, 168, 76, 0.25); + border-radius: 999px; + background: rgba(255, 255, 255, 0.04); + color: var(--brand-warm-white); + font-family: var(--brand-font-body); + font-size: 0.9rem; +} + +.notes-search:focus { outline: none; border-color: rgba(201, 168, 76, 0.6); } + +.notes-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.35rem; } + +.notes-item { + width: 100%; + display: flex; + gap: 0.75rem; + align-items: flex-start; + padding: 0.7rem 0.7rem; + border: 1px solid transparent; + border-radius: 12px; + background: none; + color: inherit; + text-align: left; + cursor: pointer; + font-family: var(--brand-font-body); +} + +.notes-item:hover { background: rgba(201, 168, 76, 0.07); } + +.notes-item--active { + background: rgba(201, 168, 76, 0.12); + border-color: rgba(201, 168, 76, 0.45); +} + +.notes-item-number { + flex-shrink: 0; + width: 1.9rem; + height: 1.9rem; + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 50%; + border: 1px solid rgba(201, 168, 76, 0.5); + color: var(--brand-gold); + font-family: var(--brand-font-heading); + font-weight: 700; + font-size: 0.95rem; +} + +.notes-item-body { min-width: 0; display: flex; flex-direction: column; gap: 0.1rem; } +.notes-item-body strong { color: var(--brand-warm-white); font-weight: 600; font-size: 0.95rem; } +.notes-item-body small { color: var(--brand-gold); font-size: 0.72rem; letter-spacing: 0.08em; text-transform: uppercase; } + +.notes-item-snippet { + color: var(--brand-muted); + font-size: 0.82rem; + line-height: 1.4; + overflow: hidden; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; +} + +.notes-reader { + padding: clamp(1.25rem, 3vw, 2rem); + border: 1px solid rgba(201, 168, 76, 0.22); + border-radius: 18px; + background: linear-gradient(180deg, rgba(26, 26, 22, 0.95), rgba(16, 16, 14, 0.95)); +} + +.notes-reader-label { + margin: 0 0 0.3rem; + font-size: 0.75rem; + letter-spacing: 0.14em; + text-transform: uppercase; + color: var(--brand-gold); +} + +.notes-reader h2 { + margin: 0 0 1.25rem; + font-family: var(--brand-font-heading); + font-size: clamp(1.5rem, 3vw, 2rem); + color: var(--brand-warm-white); +} + +.notes-block + .notes-block { margin-top: 1.25rem; padding-top: 1.25rem; border-top: 1px solid rgba(201, 168, 76, 0.14); } + +.notes-block h3 { + margin: 0 0 0.5rem; + font-size: 0.78rem; + letter-spacing: 0.12em; + text-transform: uppercase; + font-family: var(--brand-font-body); + font-weight: 600; + color: var(--brand-muted); +} + +.notes-reader-actions { margin-top: 1.75rem; } + +.notes-empty { + max-width: 520px; + margin: 2rem auto; + padding: 2rem; + text-align: center; + border: 1px solid rgba(201, 168, 76, 0.22); + border-radius: 18px; + background: rgba(17, 17, 15, 0.84); +} + +.notes-empty-icon { font-size: 2.2rem; margin-bottom: 0.5rem; } + +.notes-empty h2 { + margin: 0 0 0.5rem; + font-family: var(--brand-font-heading); + color: var(--brand-warm-white); +} + +.notes-empty p { margin: 0 0 1.25rem; color: var(--brand-muted); line-height: 1.6; } + +.notes-empty-text { color: var(--brand-muted); font-size: 0.92rem; } + +@media (max-width: 820px) { + .notes-layout { grid-template-columns: 1fr; } + .notes-list-pane { position: static; max-height: 18rem; } +} + /* ── My Account ── */ .account-page { padding: clamp(1.5rem, 5vw, 3rem) 1.25rem 4rem; @@ -14338,3 +14678,165 @@ a.study-track-card:hover { .admin-tabs--page { margin: 0 0 1.5rem; padding-bottom: 1rem; border-bottom: 1px solid #2a2518; } .admin-tabs--page .admin-tab { font-size: 0.8rem; padding: 0.45rem 0.9rem; } + +/* ── Study community (redesign) ── */ +.community-page { + padding: clamp(1.5rem, 4vw, 2.5rem) 1.25rem 4rem; + background: + radial-gradient(ellipse at top, rgba(201, 168, 76, 0.09) 0%, transparent 55%), + var(--brand-black); +} + +.community-shell { max-width: 1040px; margin: 0 auto; } + +.community-header { + display: flex; + align-items: flex-end; + justify-content: space-between; + flex-wrap: wrap; + gap: 1rem; + margin: 1rem 0 1.5rem; +} + +.community-header .eyebrow { margin: 0; } + +.community-header h1 { + margin: 0.15rem 0 0.2rem; + font-family: var(--brand-font-heading); + font-size: clamp(1.9rem, 4.5vw, 2.6rem); + color: var(--brand-warm-white); +} + +.community-subtitle { margin: 0; color: var(--brand-muted); } +.community-header-actions { display: flex; gap: 0.6rem; flex-wrap: wrap; } + +.community-layout { + display: grid; + grid-template-columns: minmax(0, 1fr) 300px; + gap: 1.5rem; + align-items: start; +} + +.community-layout > .community-feed { min-width: 0; } + +.community-layout .community-post-card { + padding: 1.1rem 1.25rem; + margin-bottom: 0.85rem; + border: 1px solid rgba(201, 168, 76, 0.18); + border-radius: 14px; + background: rgba(17, 17, 15, 0.84); +} + +.community-layout .community-post-card:last-child { border-bottom: 1px solid rgba(201, 168, 76, 0.18); } + +.community-layout .community-compose-card { + border-color: rgba(201, 168, 76, 0.3); + background: linear-gradient(180deg, rgba(26, 26, 22, 0.95), rgba(16, 16, 14, 0.95)); +} + +.community-aside { + position: sticky; + top: 5.5rem; + display: flex; + flex-direction: column; + gap: 1rem; +} + +.community-aside-card { + padding: 1.1rem 1.15rem; + border: 1px solid rgba(201, 168, 76, 0.2); + border-radius: 16px; + background: rgba(17, 17, 15, 0.84); +} + +.community-aside-card h2 { + margin: 0 0 0.7rem; + font-family: var(--brand-font-heading); + font-size: 1.1rem; + color: #e8d9b5; +} + +.community-lesson-list { + list-style: none; + margin: 0; + padding: 0; + max-height: 22rem; + overflow-y: auto; + display: flex; + flex-direction: column; + gap: 0.2rem; +} + +.community-lesson-item { + width: 100%; + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.75rem; + padding: 0.5rem 0.7rem; + border: 1px solid transparent; + border-radius: 10px; + background: none; + color: var(--brand-muted); + text-align: left; + font-family: var(--brand-font-body); + font-size: 0.9rem; + cursor: pointer; +} + +.community-lesson-item:hover { background: rgba(201, 168, 76, 0.07); color: var(--brand-warm-white); } + +.community-lesson-item--active { + background: rgba(201, 168, 76, 0.12); + border-color: rgba(201, 168, 76, 0.4); + color: var(--brand-warm-white); +} + +.community-lesson-item small { + flex-shrink: 0; + min-width: 1.5rem; + padding: 0.05rem 0.45rem; + border-radius: 999px; + background: rgba(201, 168, 76, 0.14); + color: var(--brand-gold); + text-align: center; + font-size: 0.75rem; +} + +.community-aside-note { margin: 0.75rem 0 0; font-size: 0.85rem; color: var(--brand-muted); } + +.community-guidelines { + margin: 0; + padding-left: 1.1rem; + color: #b9aa86; + font-size: 0.88rem; + line-height: 1.6; +} + +.community-filter-select { display: none; margin-bottom: 1rem; } + +.community-filter-select label { + display: block; + margin-bottom: 0.3rem; + font-size: 0.75rem; + letter-spacing: 0.1em; + text-transform: uppercase; + color: var(--brand-muted); +} + +.community-filter-select select { + width: 100%; + padding: 0.6rem 0.8rem; + border: 1px solid rgba(201, 168, 76, 0.3); + border-radius: 10px; + background: rgba(255, 255, 255, 0.04); + color: var(--brand-warm-white); + font-family: var(--brand-font-body); +} + +@media (max-width: 860px) { + .community-layout { grid-template-columns: 1fr; } + .community-aside { position: static; order: 2; } + .community-aside .community-aside-card:first-child { display: none; } + .community-filter-select { display: block; } +} diff --git a/src/studyPages.tsx b/src/studyPages.tsx index c71daa1..a935a83 100644 --- a/src/studyPages.tsx +++ b/src/studyPages.tsx @@ -418,15 +418,23 @@ function CommunityBoard({ } } + const postCountFor = (sectionId: string) => posts.filter(p => p.sectionId === sectionId).length + const lessonsForFilter = releasedSections.filter(item => postCountFor(item.id) > 0 || item.id === activeSection) + const activeSectionTitle = releasedSections.find(item => item.id === activeSection)?.title + return ( +
- {/* Section filter tabs */} + {/* Mobile lesson filter */} {releasedSections.length > 1 && canParticipate && ( -
- - {releasedSections.map(s => ( - - ))} +
+ +
)} @@ -580,6 +588,40 @@ function CommunityBoard({
)}
+ + {canParticipate && ( + + )} +
) } @@ -2659,6 +2701,7 @@ export function ColossiansStudyNotesPage({ content }: Props) { const [loading, setLoading] = useState(true) const [message, setMessage] = useState('') const [activeNoteTab, setActiveNoteTab] = useState('') + const [noteQuery, setNoteQuery] = useState('') usePageMeta( study ? `My Notes | ${study.title}` : 'My Study Notes', @@ -2737,108 +2780,149 @@ export function ColossiansStudyNotesPage({ content }: Props) { ? getStudies(content).filter(s => (auth.enrolledStudySlugs ?? []).includes(s.slug.toLowerCase())) : [] - return ( -
-
-
- - Back to {study.title} -

Student Workspace

-

My Lesson Notes

- {!loading && auth.authenticated &&

Signed in as {auth.username}

} - {enrolledStudies.length > 1 && ( -
-

Switch Track

-
- {enrolledStudies.map(s => ( - - {s.title} - - ))} -
-
- )} - {!loading && auth.authenticated && ( -
- My Account - Back to Lessons -
- )} + const stripHtml = (html: string) => DOMPurify.sanitize(html, { ALLOWED_TAGS: [] }).replace(/\s+/g, ' ').trim() + const noteText = (raw: string) => Object.values(parseNoteMap(raw)).map(value => stripHtml(value ?? '')).filter(Boolean).join(' · ') + const sorted = [...entries].sort((a, b) => { + const aNum = a.section ? getLessonNumber(study.sections, a.section.id) : 0 + const bNum = b.section ? getLessonNumber(study.sections, b.section.id) : 0 + return aNum - bNum + }) + const queryText = noteQuery.trim().toLowerCase() + const visible = queryText + ? sorted.filter(item => `${item.section?.title ?? ''} ${item.section?.reference ?? ''} ${noteText(item.note)}`.toLowerCase().includes(queryText)) + : sorted + const active = visible.find(item => item.sectionId === activeNoteTab) ?? visible[0] - {loading &&

Loading your notes...

} - {!loading && !auth.authenticated && ( -
-

Sign In Required

-

Open any lesson and sign in from the Student Notes panel to see your saved notes here.

- Create Account or Sign In -
- )} - {!loading && auth.authenticated && !enrolled && ( -
-

Enrollment Required

-

You need to enroll in this study before accessing your notes.

- Go to Study Enrollment -
- )} - {!loading && auth.authenticated && enrolled && entries.length === 0 && ( -
-

No Notes Yet

-

You have not saved notes yet. Open a lesson and use the Student Notes area to start.

- {study.sections[0] && Open First Lesson} -
- )} - {!loading && auth.authenticated && enrolled && entries.length > 0 && (() => { - const sorted = [...entries].sort((a, b) => { - const aNum = a.section ? getLessonNumber(study.sections, a.section.id) : 0 - const bNum = b.section ? getLessonNumber(study.sections, b.section.id) : 0 - return aNum - bNum - }) - const active = sorted.find(item => item.sectionId === activeNoteTab) ?? sorted[0] - return ( -
-
- {sorted.map(({ sectionId, section }) => ( - - ))} -
-
-

{active.section ? `Lesson ${getLessonNumber(study.sections, active.section.id)}` : 'Saved Note'}

-

{active.section?.title ?? active.sectionId}

-

{active.section ? active.section.reference : 'Lesson reference unavailable'}

- {(() => { - const anchorMap = parseNoteMap(active.note) - const hasContent = Object.values(anchorMap).some(v => v?.trim()) - if (!hasContent) return

No notes saved for this lesson yet.

- return Object.entries(anchorMap).map(([key, html]) => ( - html?.trim() ? ( -
-

{ANCHOR_LABELS[key] ?? key}

-
-
- ) : null - )) - })()} - {active.section && Open Lesson} -
-
- ) - })()} - {message &&

{message}

} -
-
+ return ( +
+
+ + +
+
+

Student Workspace

+

My Notes

+

+ {study.title} + {!loading && auth.authenticated && enrolled && <> · {entries.length} {entries.length === 1 ? 'lesson' : 'lessons'} with notes} +

+
+
+ Back to Lessons + My Account +
+
+ + {enrolledStudies.length > 1 && ( +
+ {enrolledStudies.map(s2 => ( + + {s2.title} + + ))} +
+ )} + + {loading &&

Loading your notes…

} + + {!loading && !auth.authenticated && ( +
+

Sign in to see your notes

+

Open any lesson and sign in from the Student Notes panel, and your saved notes will show up here.

+ Create Account or Sign In +
+ )} + + {!loading && auth.authenticated && !enrolled && ( +
+

Enroll to use notes

+

You need to enroll in this study before you can write and read lesson notes.

+ Go to Study Enrollment +
+ )} + + {!loading && auth.authenticated && enrolled && entries.length === 0 && ( +
+ +

No notes yet

+

Open a lesson and use the Student Notes panel to jot down what stands out. Everything you save collects here, private to you.

+ {study.sections.find(isSectionReleased) && Open the First Lesson} +
+ )} + + {!loading && auth.authenticated && enrolled && entries.length > 0 && ( +
+ + + {active && ( +
+

{active.section ? `Lesson ${getLessonNumber(study.sections, active.section.id)}` : 'Saved note'}{active.section?.reference ? ` · ${active.section.reference}` : ''}

+

{active.section?.title ?? active.sectionId}

+ {(() => { + const anchorMap = parseNoteMap(active.note) + const blocks = Object.entries(anchorMap).filter(([, html]) => html?.trim()) + if (blocks.length === 0) return

No notes saved for this lesson yet.

+ return blocks.map(([key, html]) => ( +
+

{ANCHOR_LABELS[key] ?? key}

+
+
+ )) + })()} + {active.section && ( +
+ Open Lesson → +
+ )} +
+ )} +
+ )} + + {message &&

{message}

} +
) } @@ -3942,6 +4026,7 @@ export function StudyQuizPage({ content }: Props) { const [error, setError] = useState('') const [shareToDiscussion, setShareToDiscussion] = useState>({}) const [sharedIndexes, setSharedIndexes] = useState>(new Set()) + const [dirty, setDirty] = useState(false) useEffect(() => { let cancelled = false @@ -3984,6 +4069,13 @@ export function StudyQuizPage({ content }: Props) { } }, [studySlug, sectionId]) + useEffect(() => { + if (!dirty) return + function onBeforeUnload(e: BeforeUnloadEvent) { e.preventDefault() } + window.addEventListener('beforeunload', onBeforeUnload) + return () => window.removeEventListener('beforeunload', onBeforeUnload) + }, [dirty]) + async function saveQuiz() { if (!studySlug || !sectionId || !study || !section) return setSaving(true) @@ -4024,6 +4116,7 @@ export function StudyQuizPage({ content }: Props) { }) } + setDirty(false) setMessage(newlyShared.length > 0 ? `Answers saved and ${newlyShared.length} shared to the discussion.` : 'Answers saved.') } catch (err) { setError(err instanceof Error ? err.message : 'Unable to save answers.') @@ -4083,70 +4176,102 @@ export function StudyQuizPage({ content }: Props) { ) } + const answeredCount = sectionQuestions.filter((_, index) => (answers[index] ?? '').trim().length >= 2).length + const canShare = isDiscussionOpen(study) + return ( -
-
-
+
+
+ + +

Reflection Questions

{section.title}

-

Write out your answers below — they're saved privately. You can also choose to share individual answers to the lesson discussion.

-
- Back to Lesson -
-
-
+ {section.reference &&

{section.reference}

} +

Take your time. Your answers are saved privately to your account{canShare ? ', and you can choose to share any of them with the lesson discussion' : ''}.

+ {canSubmit && !loading && !error && ( +
+
+ {answeredCount} of {sectionQuestions.length} answered + ← Back to lesson +
+
+
+
+
+ )} + -
-
- {loading ? ( -

Loading reflection questions...

- ) : error ? ( -

{error}

- ) : !canSubmit ? ( -
-

No Reflection Questions

-

This lesson does not have reflection questions configured yet.

-
- ) : ( -
{ e.preventDefault(); saveQuiz() }}> - {sectionQuestions.map((question, index) => ( -
-

{`${index + 1}. ${question}`}

+ {loading ? ( +

Loading reflection questions…

+ ) : error ? ( +

{error}

+ ) : !canSubmit ? ( +
+

No reflection questions

+

This lesson doesn't have reflection questions yet.

+ Back to Lesson +
+ ) : ( + { e.preventDefault(); void saveQuiz() }}> + {sectionQuestions.map((question, index) => { + const answer = answers[index] ?? '' + const hasAnswer = answer.trim().length >= 2 + return ( +
+
+ + +