Discussion stays open for finished studies; only planned studies are closed; v2.11.2
Publish Container / build (push) Successful in 16m10s

A finished study is just no longer the main study, so students who take it later still need discussion.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 5.5 committed 2026-10-09 11:52:56 -04:00
1 parent f1d4909acb
commit 0ab94bfcd4
5 files changed
+20 -17

No files matched your search

+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "siteforge", "name": "siteforge",
"version": "2.11.1", "version": "2.11.2",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "siteforge", "name": "siteforge",
"version": "2.11.1", "version": "2.11.2",
"dependencies": { "dependencies": {
"@aws-sdk/client-s3": "^3.1135.0", "@aws-sdk/client-s3": "^3.1135.0",
"@dnd-kit/core": "^6.3.1", "@dnd-kit/core": "^6.3.1",
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "siteforge", "name": "siteforge",
"private": true, "private": true,
"version": "2.11.1", "version": "2.11.2",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
+5 -4
View File
@@ -225,17 +225,18 @@ export async function backfillCompletedStudies() {
return updated return updated
} }
// Discussion (community posts, replies, likes and lesson comments) is only open while a // Discussion (community posts, replies, likes and lesson comments) is open for any study that
// study is active. Finished and planned studies are closed. // has started: the current study and finished ones, so students who take a study later can
// still talk with each other. Only planned studies (not started yet) are closed.
export function isStudyDiscussionOpen(studySlug) { export function isStudyDiscussionOpen(studySlug) {
const normalized = normalizeStudySlug(studySlug) const normalized = normalizeStudySlug(studySlug)
const studies = state.cachedSiteContent?.studies const studies = state.cachedSiteContent?.studies
if (!normalized || !Array.isArray(studies)) return false if (!normalized || !Array.isArray(studies)) return false
const study = studies.find(item => normalizeStudySlug(item?.slug) === normalized) const study = studies.find(item => normalizeStudySlug(item?.slug) === normalized)
return Boolean(study) && getEffectiveStudyStatus(study) === 'active' return Boolean(study) && getEffectiveStudyStatus(study) !== 'planned'
} }
export const DISCUSSION_CLOSED_MESSAGE = 'Discussion is only open while a study is active.' export const DISCUSSION_CLOSED_MESSAGE = 'Discussion opens once this study has started.'
export function findStudyUserById(userId) { export function findStudyUserById(userId) {
if (typeof userId !== 'string' || !userId.trim()) return undefined if (typeof userId !== 'string' || !userId.trim()) return undefined
+9 -7
View File
@@ -212,9 +212,11 @@ function getEffectiveStudyStatus(study: StudyProgram): 'active' | 'planned' | 'c
return 'active' return 'active'
} }
// Discussion (community board and lesson comments) is only open while a study is active. // Discussion (community board and lesson comments) is open for every study that has started,
// including finished ones, so students who take a study later can still join in. Planned
// studies are closed until they begin.
function isDiscussionOpen(study: StudyProgram | undefined): boolean { function isDiscussionOpen(study: StudyProgram | undefined): boolean {
return Boolean(study) && getEffectiveStudyStatus(study as StudyProgram) === 'active' return Boolean(study) && getEffectiveStudyStatus(study as StudyProgram) !== 'planned'
} }
function getNextReleaseLabel(study: StudyProgram): string { function getNextReleaseLabel(study: StudyProgram): string {
@@ -1698,7 +1700,7 @@ export function ColossiansStudyIndexPage({ content }: Props) {
)} )}
{allLessonsDone && <Link to="/study/account" className="btn-secondary">🏅 My Certificate</Link>} {allLessonsDone && <Link to="/study/account" className="btn-secondary">🏅 My Certificate</Link>}
{enrolled && <Link to={`/study/${study.slug}/notes`} className="btn-secondary">My Notes</Link>} {enrolled && <Link to={`/study/${study.slug}/notes`} className="btn-secondary">My Notes</Link>}
{enrolled && studyStatus === 'active' && <Link to={`/study/${study.slug}/community`} className="btn-secondary">Community</Link>} {enrolled && studyStatus !== 'planned' && <Link to={`/study/${study.slug}/community`} className="btn-secondary">Community</Link>}
{auth.authenticated && <Link to="/study/account" className="btn-secondary">My Account</Link>} {auth.authenticated && <Link to="/study/account" className="btn-secondary">My Account</Link>}
</div> </div>
{enrollMessage && <p className="study-note-status" style={{ marginTop: '0.75rem' }}>{enrollMessage}</p>} {enrollMessage && <p className="study-note-status" style={{ marginTop: '0.75rem' }}>{enrollMessage}</p>}
@@ -4204,12 +4206,12 @@ export function StudyDiscussionHubPage({ content }: Props) {
<div className="section-inner study-course-hero-inner"> <div className="section-inner study-course-hero-inner">
<p className="eyebrow">Student Community</p> <p className="eyebrow">Student Community</p>
<h1>Study Discussion</h1> <h1>Study Discussion</h1>
<p className="study-course-hero-copy">Pick an active study to see what other students are saying, ask questions, and share what God is teaching you. Discussion is open while a study is active.</p> <p className="study-course-hero-copy">Pick a study track to see what other students are saying, ask questions, and share what God is teaching you.</p>
</div> </div>
</section> </section>
<section className="section-inner" style={{ paddingTop: '1rem', paddingBottom: '4rem' }}> <section className="section-inner" style={{ paddingTop: '1rem', paddingBottom: '4rem' }}>
{openDiscussionStudies.length === 0 ? ( {openDiscussionStudies.length === 0 ? (
<p className="study-detail-copy">Discussion opens while a study is active. There are no active studies right now — check back when the next one starts.</p> <p className="study-detail-copy">There are no studies open for discussion yet. Check back when the next one starts.</p>
) : ( ) : (
<div className="study-module-list"> <div className="study-module-list">
{openDiscussionStudies.map(study => { {openDiscussionStudies.map(study => {
@@ -4294,8 +4296,8 @@ export function StudyCommunityPage({ content }: Props) {
<div className="auth-shell auth-shell--single"> <div className="auth-shell auth-shell--single">
<div className="auth-card auth-card--center"> <div className="auth-card auth-card--center">
<p className="eyebrow">{study.title}</p> <p className="eyebrow">{study.title}</p>
<h1>Discussion Is Closed</h1> <h1>Discussion Isn't Open Yet</h1>
<p>Discussion is only open while a study is active, and this study isn't active right now.</p> <p>Discussion opens once this study has started. Check back when it begins.</p>
<div className="study-signup-actions"> <div className="study-signup-actions">
<Link to={`/study/${study.slug}`} className="btn-primary">Back to Study</Link> <Link to={`/study/${study.slug}`} className="btn-primary">Back to Study</Link>
</div> </div>
+3 -3
View File
@@ -60,7 +60,7 @@ test('re-engagement emails skip students whose only study is finished', async ()
assert.deepEqual(sent, ['c@d.co']) assert.deepEqual(sent, ['c@d.co'])
}) })
test('discussion is open only for active studies', async () => { test('discussion is open for active and finished studies, closed only for planned ones', async () => {
const { isStudyDiscussionOpen } = await import('../server/study-helpers.js') const { isStudyDiscussionOpen } = await import('../server/study-helpers.js')
const past = '2020-01-01T00:00:00Z' const past = '2020-01-01T00:00:00Z'
const future = '2099-01-01T00:00:00Z' const future = '2099-01-01T00:00:00Z'
@@ -73,8 +73,8 @@ test('discussion is open only for active studies', async () => {
], ],
} }
assert.equal(isStudyDiscussionOpen('live'), true) assert.equal(isStudyDiscussionOpen('live'), true)
assert.equal(isStudyDiscussionOpen('done'), false) assert.equal(isStudyDiscussionOpen('done'), true)
assert.equal(isStudyDiscussionOpen('auto-done'), false) assert.equal(isStudyDiscussionOpen('auto-done'), true)
assert.equal(isStudyDiscussionOpen('soon'), false) assert.equal(isStudyDiscussionOpen('soon'), false)
assert.equal(isStudyDiscussionOpen('missing'), false) assert.equal(isStudyDiscussionOpen('missing'), false)
}) })