feat: notifications in the app and, optionally, by e-mail

Derived from events and kept per person: whoever a ticket is assigned to,
whoever reports or works on it when it moves, fails QA or gets a comment,
and whoever joins a project. Never about what you did yourself, once per
event. They arrive live through a subscription to a bell in the header,
and are copied by e-mail to whoever turns it on when SMTP_URL is set.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-08 09:19:59 +02:00
parent 0b65c1c912
commit efa06a0723
60 changed files with 3406 additions and 28 deletions

View File

@@ -0,0 +1,37 @@
import { type } from '@ngrx/signals';
import { eventGroup } from '@ngrx/signals/events';
import {
Notification,
NotificationSettings,
} from '@task-stream/web/tracker/domain';
/* Notifications are "the inbox" here, so as not to clash with `Notifications` (the toasts port). */
export const inboxPageEvents = eventGroup({
source: 'Inbox',
events: {
/** Signed in: load, and follow new ones. */
opened: type<void>(),
/** Signed out: forget everything. */
closed: type<void>(),
markReadRequested: type<{ ids?: string[] }>(),
emailToggled: type<boolean>(),
},
});
export const inboxApiEvents = eventGroup({
source: 'Inbox API',
events: {
loaded: type<{ items: Notification[]; unread: number }>(),
settingsLoaded: type<NotificationSettings>(),
markedRead: type<{ ids?: string[]; at: string }>(),
failed: type<string>(),
},
});
export const inboxRealtimeEvents = eventGroup({
source: 'Inbox Realtime',
events: {
arrived: type<Notification>(),
},
});