feat(phase7): per-channel notification settings and read receipts

Phase 7.1 — Per-channel notification settings
- New notification_settings table (user_id, channel_id, level)
- GET/PUT/DELETE handlers under /channels/{channelID}/notifications
- Push dispatch and @mention loops filtered by notification level
- Frontend: bell icon per channel cycling all/mentions/none

Phase 7.4 — Read receipts
- New read_states table (user_id, channel_id, last_read_message_id)
- PUT /channels/{channelID}/read and GET /users/me/read-states
- Auto-mark-read when messages load in ChatArea
- Unread dot for channels never opened

Also: favicon/icon refresh in index.html
This commit is contained in:
2026-06-30 12:39:14 -04:00
parent ab3e6053c8
commit cc6ed741f0
15 changed files with 389 additions and 16 deletions
+34
View File
@@ -0,0 +1,34 @@
import { create } from 'zustand';
import { api } from '../lib/api';
type NotificationLevel = 'all' | 'mentions' | 'none';
interface NotificationSettingsState {
settings: Record<string, NotificationLevel>;
initialized: boolean;
loaded: boolean;
fetchSettings: () => Promise<void>;
setLevel: (channelId: string, level: NotificationLevel) => Promise<void>;
}
export const useNotificationSettingsStore = create<NotificationSettingsState>()((set) => ({
settings: {},
initialized: false,
loaded: false,
fetchSettings: async () => {
try {
const settings = await api.get<Record<string, NotificationLevel>>('/channels/me/notifications');
set({ settings, loaded: true, initialized: true });
} catch {
set({ loaded: true, initialized: true });
}
},
setLevel: async (channelId: string, level: NotificationLevel) => {
await api.put(`/channels/${channelId}/notifications`, { level });
set((state) => ({
settings: { ...state.settings, [channelId]: level },
}));
},
}));