feat: add in-app notification system with real-time delivery

Introduces a full in-app notification system with three types (simple,
boolean with server-side callbacks, navigate), three scopes (user, trip,
admin), fan-out persistence per recipient, and real-time push via
WebSocket. Includes a notification bell in the navbar, dropdown, dedicated
/notifications page, and a dev-only admin tab for testing all notification
variants.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
jubnl
2026-04-02 18:57:52 +02:00
parent 979322025d
commit c0e9a771d6
32 changed files with 1837 additions and 8 deletions
+40
View File
@@ -311,4 +311,44 @@ router.post('/rotate-jwt-secret', (req: Request, res: Response) => {
res.json({ success: true });
});
// ── Dev-only: test notification endpoints ──────────────────────────────────────
if (process.env.NODE_ENV === 'development') {
const { createNotification } = require('../services/inAppNotifications');
router.post('/dev/test-notification', (req: Request, res: Response) => {
const authReq = req as AuthRequest;
const { type, scope, target, title_key, text_key, title_params, text_params,
positive_text_key, negative_text_key, positive_callback, negative_callback,
navigate_text_key, navigate_target } = req.body;
const input: Record<string, unknown> = {
type: type || 'simple',
scope: scope || 'user',
target: target ?? authReq.user.id,
sender_id: authReq.user.id,
title_key: title_key || 'notifications.test.title',
title_params: title_params || {},
text_key: text_key || 'notifications.test.text',
text_params: text_params || {},
};
if (type === 'boolean') {
input.positive_text_key = positive_text_key || 'notifications.test.accept';
input.negative_text_key = negative_text_key || 'notifications.test.decline';
input.positive_callback = positive_callback || { action: 'test_approve', payload: {} };
input.negative_callback = negative_callback || { action: 'test_deny', payload: {} };
} else if (type === 'navigate') {
input.navigate_text_key = navigate_text_key || 'notifications.test.goThere';
input.navigate_target = navigate_target || '/dashboard';
}
try {
const ids = createNotification(input);
res.json({ success: true, notification_ids: ids });
} catch (err: any) {
res.status(400).json({ error: err.message });
}
});
}
export default router;