Add study account engagement features; v1.2.4
- Pause reminders: remindersPausedUntil field + scheduler skip + account UI with 7/14/30-day presets - Email digest mode: reminderFrequency field (immediate/weekly) + weekly digest email + scheduler + account UI toggle - Admin user list improvements: streak, reengagement, frequency, pause, per-study lesson progress - Admin manual re-engagement: POST endpoint + tier selector button in admin panel - Study bookmarks: bookmarkedSectionIds in progress file + POST/DELETE endpoints + toggle button in lesson UI - Study completion certificate email: auto-sent on first certificate issuance - Content fields for certificate and weekly digest email templates in admin Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
5bc8245ed2
commit
038ef2b039
12 files changed
+540
-38
No files matched your search
@@ -171,6 +171,7 @@ export function register(app) {
|
||||
|
||||
const progress = await loadUserProgress(user.id)
|
||||
const completedSectionIds = progress.byStudy[studySlug]?.completedSectionIds ?? []
|
||||
const bookmarkedSectionIds = progress.byStudy[studySlug]?.bookmarkedSectionIds ?? []
|
||||
|
||||
// Record first visit timestamp on the user record (additive, never overwrite)
|
||||
if (!user.firstVisitAt) {
|
||||
@@ -178,7 +179,7 @@ export function register(app) {
|
||||
queueStudyUsersWrite()
|
||||
}
|
||||
|
||||
res.json({ studySlug, completedSectionIds })
|
||||
res.json({ studySlug, completedSectionIds, bookmarkedSectionIds })
|
||||
})
|
||||
|
||||
app.post('/api/study-progress/:studySlug/:sectionId', requireStudyAuth, async (req, res) => {
|
||||
@@ -301,6 +302,45 @@ export function register(app) {
|
||||
res.json({ ok: true, studySlug, sectionId, answers })
|
||||
})
|
||||
|
||||
// ── Bookmarks ─────────────────────────────────────────────────────────────
|
||||
|
||||
app.post('/api/study-progress/:studySlug/:sectionId/bookmark', requireStudyAuth, async (req, res) => {
|
||||
const user = req.studyUser
|
||||
const studySlug = normalizeStudySlug(req.params.studySlug)
|
||||
const sectionId = normalizeLessonSectionId(req.params.sectionId)
|
||||
if (!studySlug || !sectionId) { res.status(400).json({ message: 'Invalid study slug or section id.' }); return }
|
||||
if (!isStudyUserEnrolled(user, studySlug)) { res.status(403).json({ message: 'Please enroll in this study to bookmark lessons.' }); return }
|
||||
|
||||
const progress = await loadUserProgress(user.id)
|
||||
const studyProgress = progress.byStudy[studySlug] ?? { completedSectionIds: [], bookmarkedSectionIds: [] }
|
||||
const bookmarkedSectionIds = Array.isArray(studyProgress.bookmarkedSectionIds) ? studyProgress.bookmarkedSectionIds : []
|
||||
if (!bookmarkedSectionIds.includes(sectionId)) {
|
||||
studyProgress.bookmarkedSectionIds = [...bookmarkedSectionIds, sectionId]
|
||||
}
|
||||
progress.byStudy[studySlug] = studyProgress
|
||||
progress.updatedAt = new Date().toISOString()
|
||||
state.studyProgressCache.set(user.id, progress)
|
||||
queueUserProgressWrite(user.id)
|
||||
res.json({ ok: true, studySlug, bookmarkedSectionIds: studyProgress.bookmarkedSectionIds })
|
||||
})
|
||||
|
||||
app.delete('/api/study-progress/:studySlug/:sectionId/bookmark', requireStudyAuth, async (req, res) => {
|
||||
const user = req.studyUser
|
||||
const studySlug = normalizeStudySlug(req.params.studySlug)
|
||||
const sectionId = normalizeLessonSectionId(req.params.sectionId)
|
||||
if (!studySlug || !sectionId) { res.status(400).json({ message: 'Invalid study slug or section id.' }); return }
|
||||
if (!isStudyUserEnrolled(user, studySlug)) { res.status(403).json({ message: 'Not enrolled.' }); return }
|
||||
|
||||
const progress = await loadUserProgress(user.id)
|
||||
const studyProgress = progress.byStudy[studySlug] ?? { completedSectionIds: [], bookmarkedSectionIds: [] }
|
||||
studyProgress.bookmarkedSectionIds = (studyProgress.bookmarkedSectionIds ?? []).filter(id => id !== sectionId)
|
||||
progress.byStudy[studySlug] = studyProgress
|
||||
progress.updatedAt = new Date().toISOString()
|
||||
state.studyProgressCache.set(user.id, progress)
|
||||
queueUserProgressWrite(user.id)
|
||||
res.json({ ok: true, studySlug, bookmarkedSectionIds: studyProgress.bookmarkedSectionIds })
|
||||
})
|
||||
|
||||
// ── Community ─────────────────────────────────────────────────────────────
|
||||
|
||||
app.get('/api/study-community', requireStudyAuth, async (req, res) => {
|
||||
|
||||
Reference in new issue
Block a user