Revert "Add student birthday tracking with celebration banners and admin notification; v1.1.31"

This reverts commit c0a79b9ed0.
This commit is contained in:
nmemmert
2026-08-10 08:43:53 -04:00
parent c0a79b9ed0
commit c56e468115
7 changed files with 5 additions and 251 deletions
-94
View File
@@ -143,11 +143,6 @@ interface StudyUserRecord {
noteCount: number
subscribeNewsletter: boolean
studyRemindersEnabled: boolean
birthdayMonth: number | null
birthdayDay: number | null
birthdayAlternateMonth: number | null
birthdayAlternateDay: number | null
isBirthdayWeek: boolean
}
function StudyUsersPanel({ studies }: { studies: StudyProgram[] }) {
@@ -159,8 +154,6 @@ function StudyUsersPanel({ studies }: { studies: StudyProgram[] }) {
const [newPassword, setNewPassword] = useState<Record<string, string>>({})
const [msg, setMsg] = useState<Record<string, string>>({})
const [confirmDeleteId, setConfirmDeleteId] = useState<string | null>(null)
const [editBirthday, setEditBirthday] = useState<Record<string, { month: string; day: string; altMonth: string; altDay: string }>>({})
function flashMsg(id: string, text: string) {
setMsg(prev => ({ ...prev, [id]: text }))
@@ -218,27 +211,6 @@ function StudyUsersPanel({ studies }: { studies: StudyProgram[] }) {
} catch (e) { flashMsg(user.id, e instanceof Error ? e.message : 'Error') }
}
async function handleSaveBirthday(user: StudyUserRecord) {
const b = editBirthday[user.id] ?? { month: String(user.birthdayMonth ?? ''), day: String(user.birthdayDay ?? ''), altMonth: String(user.birthdayAlternateMonth ?? ''), altDay: String(user.birthdayAlternateDay ?? '') }
const bMonth = b.month ? parseInt(b.month, 10) : null
const bDay = b.day ? parseInt(b.day, 10) : null
const altMonth = b.altMonth ? parseInt(b.altMonth, 10) : null
const altDay = b.altDay ? parseInt(b.altDay, 10) : null
if (bMonth !== null && (bMonth < 1 || bMonth > 12)) { flashMsg(user.id, 'Month must be 112.'); return }
if (bDay !== null && (bDay < 1 || bDay > 31)) { flashMsg(user.id, 'Day must be 131.'); return }
if ((altMonth == null) !== (altDay == null)) { flashMsg(user.id, 'Set both alternate month and day, or neither.'); return }
try {
await patchUser(user.id, {
birthdayMonth: bMonth !== null ? String(bMonth) : '',
birthdayDay: bDay !== null ? String(bDay) : '',
birthdayAlternateMonth: altMonth !== null ? String(altMonth) : '',
birthdayAlternateDay: altDay !== null ? String(altDay) : '',
})
setUsers(prev => prev.map(u => u.id !== user.id ? u : { ...u, birthdayMonth: bMonth, birthdayDay: bDay, birthdayAlternateMonth: altMonth, birthdayAlternateDay: altDay }))
flashMsg(user.id, 'Birthday saved.')
} catch (e) { flashMsg(user.id, e instanceof Error ? e.message : 'Error') }
}
async function handleUnenroll(user: StudyUserRecord, slug: string) {
try {
await patchUser(user.id, { removeEnrollment: slug })
@@ -293,17 +265,6 @@ function StudyUsersPanel({ studies }: { studies: StudyProgram[] }) {
onClick={() => {
setExpandedId(isExpanded ? null : user.id)
setEditDisplayName(prev => ({ ...prev, [user.id]: user.displayName }))
if (!isExpanded) {
setEditBirthday(prev => ({
...prev,
[user.id]: {
month: user.birthdayMonth ? String(user.birthdayMonth) : '',
day: user.birthdayDay ? String(user.birthdayDay) : '',
altMonth: user.birthdayAlternateMonth ? String(user.birthdayAlternateMonth) : '',
altDay: user.birthdayAlternateDay ? String(user.birthdayAlternateDay) : '',
},
}))
}
}}
>
<div className="admin-study-user-header-left">
@@ -314,7 +275,6 @@ function StudyUsersPanel({ studies }: { studies: StudyProgram[] }) {
<span className="admin-study-user-pill">{user.enrolledStudies.length} enrolled</span>
<span className="admin-study-user-pill">{user.noteCount} notes</span>
{(user.currentStreak ?? 0) > 0 && <span className="admin-study-user-pill">{user.currentStreak} 🔥</span>}
{user.isBirthdayWeek && <span className="admin-study-user-pill" title="Birthday this week!" style={{ background: 'rgba(255,200,0,0.18)', color: '#b38600' }}>🎂 Birthday Week!</span>}
<span style={{ color: '#5a5440', fontSize: '0.8rem' }}>{isExpanded ? '▲' : '▼'}</span>
</div>
</button>
@@ -345,60 +305,6 @@ function StudyUsersPanel({ studies }: { studies: StudyProgram[] }) {
</div>
</div>
{/* Birthday */}
{(() => {
const b = editBirthday[user.id] ?? { month: String(user.birthdayMonth ?? ''), day: String(user.birthdayDay ?? ''), altMonth: String(user.birthdayAlternateMonth ?? ''), altDay: String(user.birthdayAlternateDay ?? '') }
const isSummer = (() => { const m = parseInt(b.month, 10); return Number.isInteger(m) && m >= 6 && m <= 8 })()
const MONTHS = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']
function bdayUpdate(field: string, val: string) {
setEditBirthday(prev => ({ ...prev, [user.id]: { ...(prev[user.id] ?? { month: '', day: '', altMonth: '', altDay: '' }), [field]: val } }))
}
return (
<div className="admin-study-user-section">
<h4>Birthday</h4>
<p className="admin-stats-note" style={{ marginBottom: '0.5rem' }}>Admin-only. Used for birthday celebration notifications and the student's birthday week banner.</p>
<div className="admin-study-user-row" style={{ flexWrap: 'wrap', gap: '0.4rem' }}>
<select value={b.month} onChange={e => bdayUpdate('month', e.target.value)} style={{ width: '130px' }}>
<option value="">Month</option>
{MONTHS.map((name, i) => <option key={i + 1} value={String(i + 1)}>{name}</option>)}
</select>
<select value={b.day} onChange={e => bdayUpdate('day', e.target.value)} style={{ width: '80px' }}>
<option value="">Day</option>
{Array.from({ length: 31 }, (_, i) => <option key={i + 1} value={String(i + 1)}>{i + 1}</option>)}
</select>
<button type="button" className="btn-admin-save" onClick={() => handleSaveBirthday(user)}>Save</button>
{(b.month && b.day) && (
<button type="button" className="btn-admin-reset" style={{ fontSize: '0.78rem' }} onClick={() => {
setEditBirthday(prev => ({ ...prev, [user.id]: { month: '', day: '', altMonth: '', altDay: '' } }))
patchUser(user.id, { birthdayMonth: '', birthdayDay: '', birthdayAlternateMonth: '', birthdayAlternateDay: '' })
.then(() => { setUsers(prev => prev.map(u => u.id !== user.id ? u : { ...u, birthdayMonth: null, birthdayDay: null, birthdayAlternateMonth: null, birthdayAlternateDay: null })); flashMsg(user.id, 'Birthday cleared.') })
.catch(() => flashMsg(user.id, 'Error clearing birthday.'))
}}>Clear</button>
)}
</div>
{isSummer && (
<div style={{ marginTop: '0.65rem', padding: '0.55rem 0.7rem', background: 'rgba(255,180,0,0.1)', border: '1px solid rgba(255,180,0,0.3)', borderRadius: '5px' }}>
<p style={{ margin: '0 0 0.4rem', fontSize: '0.83rem', color: '#8a6200' }}><strong> Summer birthday</strong> this falls during summer break (JuneAugust). Set an alternate school-year celebration date below so they still get recognized during the school year.</p>
<div className="admin-study-user-row" style={{ flexWrap: 'wrap', gap: '0.4rem', marginTop: '0.35rem' }}>
<select value={b.altMonth} onChange={e => bdayUpdate('altMonth', e.target.value)} style={{ width: '130px' }}>
<option value="">Celebrate in</option>
{MONTHS.map((name, i) => <option key={i + 1} value={String(i + 1)}>{name}</option>)}
</select>
<select value={b.altDay} onChange={e => bdayUpdate('altDay', e.target.value)} style={{ width: '80px' }}>
<option value="">Day</option>
{Array.from({ length: 31 }, (_, i) => <option key={i + 1} value={String(i + 1)}>{i + 1}</option>)}
</select>
</div>
{(b.altMonth && b.altDay) && <p className="admin-stats-note" style={{ marginTop: '0.3rem' }}>Birthday banner and email will fire on {MONTHS[parseInt(b.altMonth, 10) - 1]} {b.altDay} instead of their summer birthday.</p>}
</div>
)}
{user.isBirthdayWeek && (
<p style={{ marginTop: '0.5rem', fontSize: '0.85rem', color: '#b38600' }}>🎂 This student's celebration week is <strong>right now</strong>!</p>
)}
</div>
)
})()}
{/* Password Reset */}
<div className="admin-study-user-section">
<h4>Reset Password</h4>
-21
View File
@@ -23,7 +23,6 @@ type StudyAuthState = {
totpEnabled?: boolean
twoFaMethod?: 'app' | 'email' | null
totpRecoveryCodesRemaining?: number
isBirthdayWeek?: boolean
}
type StudyAuthStatusResponse = {
@@ -37,7 +36,6 @@ type StudyAuthStatusResponse = {
totpEnabled?: boolean
twoFaMethod?: 'app' | 'email' | null
totpRecoveryCodesRemaining?: number
isBirthdayWeek?: boolean
}
type StudyAccountOverview = {
@@ -1260,10 +1258,6 @@ export function ColossiansStudyIndexPage({ content }: Props) {
const [enrollMessage, setEnrollMessage] = useState('')
const [studyProgress, setStudyProgress] = useState<StudyProgress>({ completedSectionIds: [] })
const [progressLoading, setProgressLoading] = useState(false)
const [birthdayBannerDismissed, setBirthdayBannerDismissed] = useState(() => {
const yr = new Date().getFullYear()
return localStorage.getItem(`birthday-banner-dismissed-${yr}`) === '1'
})
useEffect(() => {
let cancelled = false
@@ -1274,9 +1268,7 @@ export function ColossiansStudyIndexPage({ content }: Props) {
checked: true,
authenticated: Boolean(data.authenticated),
username: data.username ?? '',
displayName: data.displayName ?? '',
enrolledStudySlugs: normalizeEnrolledStudySlugs(data.enrolledStudySlugs),
isBirthdayWeek: data.isBirthdayWeek === true,
})
})
.catch(() => {
@@ -1359,19 +1351,6 @@ export function ColossiansStudyIndexPage({ content }: Props) {
return (
<main className="study-index-page" aria-label={`${study.title} study`}>
{auth.authenticated && auth.isBirthdayWeek && !birthdayBannerDismissed && (
<div role="alert" style={{ background: 'linear-gradient(135deg, #fffbe6 0%, #fff3cc 100%)', borderBottom: '2px solid #f0c930', padding: '0.9rem 1.25rem', display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '1rem', flexWrap: 'wrap' }}>
<span style={{ fontSize: '1.05rem', color: '#7a5800' }}>
🎂 <strong>Happy Birthday{auth.displayName ? `, ${auth.displayName}` : ''}!</strong> Wishing you a wonderful birthday week — we're so glad you're part of this study community. 🎉
</span>
<button
type="button"
onClick={() => { setBirthdayBannerDismissed(true); localStorage.setItem(`birthday-banner-dismissed-${new Date().getFullYear()}`, '1') }}
aria-label="Dismiss birthday banner"
style={{ background: 'none', border: 'none', cursor: 'pointer', color: '#7a5800', fontSize: '1.1rem', lineHeight: 1, padding: '0.2rem 0.4rem', borderRadius: '3px' }}
>✕</button>
</div>
)}
<section className="section-study-course-hero">
<div className="section-inner study-course-hero-inner">
<Breadcrumbs items={[{ label: 'Home', href: '/' }, { label: 'Studies', href: '/study' }, { label: study.title }]} />