Add birthday celebrations for students
Parents can set a birthday (MM-DD) per student in the Kids panel. During the student's birthday week (Mon–Sun), a festive overlay appears on login with balloons, birthday cake, confetti, and a personalized message — shown once per day via sessionStorage. Summer birthdays (June–Aug) show a note in admin suggesting the parent set an alternate school-week date to celebrate early. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
7f6268d6e5
commit
1eedfc7715
7 files changed
+146
-8
No files matched your search
@@ -324,8 +324,26 @@ const GRADABLE_TYPES = ['assignment', 'quiz', 'matching', 'crossword', 'spelling
|
||||
|
||||
// ---------- kid picker ----------
|
||||
|
||||
// Returns true if today falls within the Mon–Sun week that contains the birthday (MM-DD)
|
||||
function isBirthdayWeek(birthday) {
|
||||
if (!birthday) return false;
|
||||
const [mm, dd] = birthday.split('-').map(Number);
|
||||
if (!mm || !dd) return false;
|
||||
const now = new Date();
|
||||
const year = now.getFullYear();
|
||||
const bday = new Date(year, mm - 1, dd);
|
||||
// Monday of the week containing the birthday
|
||||
const dow = bday.getDay(); // 0=Sun
|
||||
const monday = new Date(bday);
|
||||
monday.setDate(bday.getDate() - (dow === 0 ? 6 : dow - 1));
|
||||
const sunday = new Date(monday);
|
||||
sunday.setDate(monday.getDate() + 6);
|
||||
const todayMidnight = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
||||
return todayMidnight >= monday && todayMidnight <= sunday;
|
||||
}
|
||||
|
||||
app.get('/api/students', (req, res) => {
|
||||
res.json(db.prepare(`SELECT id, name, emoji, theme, bg_pattern, streak_count, streak_date, young_learner FROM students ORDER BY name`).all());
|
||||
res.json(db.prepare(`SELECT id, name, emoji, theme, bg_pattern, streak_count, streak_date, young_learner, birthday FROM students ORDER BY name`).all());
|
||||
});
|
||||
|
||||
const VALID_THEMES = ['blue','green','purple','orange','pink','red','teal','yellow','indigo'];
|
||||
@@ -376,7 +394,7 @@ app.post('/api/students/:id/complete-day', (req, res) => {
|
||||
// Everything the standalone spelling home screen needs
|
||||
app.get('/api/state/:studentId', (req, res) => {
|
||||
const id = req.params.studentId;
|
||||
const student = db.prepare(`SELECT id, name, emoji, theme, bg_pattern, young_learner FROM students WHERE id = ?`).get(id);
|
||||
const student = db.prepare(`SELECT id, name, emoji, theme, bg_pattern, young_learner, birthday FROM students WHERE id = ?`).get(id);
|
||||
if (!student) return res.status(404).json({ error: 'No such student' });
|
||||
|
||||
const assignment = db.prepare(`
|
||||
@@ -402,7 +420,7 @@ app.get('/api/state/:studentId', (req, res) => {
|
||||
AND date(p.due) <= date('now')
|
||||
`).get(id, assignment ? assignment.id : -1).n;
|
||||
|
||||
res.json({ student, assignment, listProgress, dueReviews });
|
||||
res.json({ student: { ...student, isBirthdayWeek: isBirthdayWeek(student.birthday) }, assignment, listProgress, dueReviews });
|
||||
});
|
||||
|
||||
// Build a practice session: the target list's unmastered words + due reviews.
|
||||
@@ -696,19 +714,32 @@ app.get('/api/public-settings', (req, res) => {
|
||||
res.json({ show_home_emoji: getSetting('show_home_emoji') !== 'false' });
|
||||
});
|
||||
|
||||
// Validate MM-DD format (no year stored — birthday repeats annually)
|
||||
function parseBirthday(val) {
|
||||
if (!val) return null;
|
||||
const m = String(val).match(/^(\d{1,2})-(\d{2})$/);
|
||||
if (!m) return null;
|
||||
const mm = Number(m[1]), dd = Number(m[2]);
|
||||
if (mm < 1 || mm > 12 || dd < 1 || dd > 31) return null;
|
||||
return `${String(mm).padStart(2, '0')}-${String(dd).padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
app.post('/api/students', requirePin, (req, res) => {
|
||||
const name = String(req.body.name || '').trim();
|
||||
if (!name) return res.status(400).json({ error: 'Name required' });
|
||||
const id = db.prepare(`INSERT INTO students (name, emoji) VALUES (?, ?)`)
|
||||
.run(name, req.body.emoji || '🙂').lastInsertRowid;
|
||||
const birthday = parseBirthday(req.body.birthday);
|
||||
const id = db.prepare(`INSERT INTO students (name, emoji, birthday) VALUES (?, ?, ?)`)
|
||||
.run(name, req.body.emoji || '🙂', birthday).lastInsertRowid;
|
||||
res.json({ id });
|
||||
});
|
||||
|
||||
app.put('/api/students/:id', requirePin, (req, res) => {
|
||||
const { emoji, theme } = req.body;
|
||||
const { emoji, theme, birthday } = req.body;
|
||||
if (emoji) db.prepare(`UPDATE students SET emoji = ? WHERE id = ?`).run(String(emoji), req.params.id);
|
||||
if (theme && VALID_THEMES.includes(theme))
|
||||
db.prepare(`UPDATE students SET theme = ? WHERE id = ?`).run(theme, req.params.id);
|
||||
if (birthday !== undefined)
|
||||
db.prepare(`UPDATE students SET birthday = ? WHERE id = ?`).run(parseBirthday(birthday), req.params.id);
|
||||
res.json({ ok: true });
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user