Add Clear button to notification bell dropdown

- Add notifications_cleared_at column to users table
- Clear button marks notifications as seen without deleting history
- Badge and dropdown only show notifications after last clear
- History page still shows all notifications

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
clucraft 2026-01-24 03:20:03 -05:00
parent 262a91b558
commit 28d6523959
5 changed files with 69 additions and 11 deletions

View file

@ -122,6 +122,9 @@ async function runMigrations() {
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'users' AND column_name = 'ai_verification_enabled') THEN
ALTER TABLE users ADD COLUMN ai_verification_enabled BOOLEAN DEFAULT false;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'users' AND column_name = 'notifications_cleared_at') THEN
ALTER TABLE users ADD COLUMN notifications_cleared_at TIMESTAMP;
END IF;
END $$;
`);

View file

@ -856,28 +856,41 @@ export const notificationHistoryQueries = {
return result.rows;
},
// Get recent notifications (for bell dropdown)
// Get recent notifications (for bell dropdown) - respects cleared_at
getRecent: async (userId: number, limit: number = 10): Promise<NotificationHistory[]> => {
const result = await pool.query(
`SELECT * FROM notification_history
WHERE user_id = $1
ORDER BY triggered_at DESC
`SELECT nh.* FROM notification_history nh
JOIN users u ON u.id = nh.user_id
WHERE nh.user_id = $1
AND (u.notifications_cleared_at IS NULL OR nh.triggered_at > u.notifications_cleared_at)
ORDER BY nh.triggered_at DESC
LIMIT $2`,
[userId, limit]
);
return result.rows;
},
// Count notifications in last 24 hours (for badge)
// Count notifications since last clear (for badge)
countRecent: async (userId: number, hours: number = 24): Promise<number> => {
const result = await pool.query(
`SELECT COUNT(*) FROM notification_history
WHERE user_id = $1 AND triggered_at > NOW() - INTERVAL '1 hour' * $2`,
`SELECT COUNT(*) FROM notification_history nh
JOIN users u ON u.id = nh.user_id
WHERE nh.user_id = $1
AND nh.triggered_at > NOW() - INTERVAL '1 hour' * $2
AND (u.notifications_cleared_at IS NULL OR nh.triggered_at > u.notifications_cleared_at)`,
[userId, hours]
);
return parseInt(result.rows[0].count, 10);
},
// Clear notifications (sets timestamp, doesn't delete)
clear: async (userId: number): Promise<void> => {
await pool.query(
`UPDATE users SET notifications_cleared_at = NOW() WHERE id = $1`,
[userId]
);
},
// Get total count for pagination
getTotalCount: async (userId: number): Promise<number> => {
const result = await pool.query(

View file

@ -69,4 +69,16 @@ router.get('/count', async (req: AuthRequest, res: Response) => {
}
});
// Clear notifications (marks as seen, doesn't delete history)
router.post('/clear', async (req: AuthRequest, res: Response) => {
try {
const userId = req.userId!;
await notificationHistoryQueries.clear(userId);
res.json({ message: 'Notifications cleared' });
} catch (error) {
console.error('Error clearing notifications:', error);
res.status(500).json({ error: 'Failed to clear notifications' });
}
});
export default router;