collabrix/frontend/src/contexts/UnreadMessagesContext.tsx
DGSoft cfd7068af5 feat: Add blinking envelope icons for unread messages
- Implement unread message indicators with Material-UI icons
- Add BlinkingEnvelope component with theme-compatible colors
- Create UnreadMessagesContext for managing unread states
- Integrate WebSocket message handling for real-time notifications
- Icons only appear for inactive channels/DMs, disappear when opened
- Add test functionality (double-click to mark as unread)
- Fix WebSocket URL handling for production deployment
- Unify WebSocket architecture using presence connection for all messages
2025-12-12 11:26:36 +01:00

124 lines
4.2 KiB
TypeScript

import React, { createContext, useContext, useState, ReactNode, useEffect } from 'react';
import { useAuth } from './AuthContext';
interface UnreadMessagesContextType {
unreadChannels: Set<number>;
unreadDirectMessages: Set<number>;
activeChannelId: number | null;
activeDirectMessageUserId: number | null;
markChannelAsRead: (channelId: number) => void;
markChannelAsUnread: (channelId: number) => void;
markDirectMessageAsRead: (userId: number) => void;
markDirectMessageAsUnread: (userId: number) => void;
hasUnreadChannel: (channelId: number) => boolean;
hasUnreadDirectMessage: (userId: number) => boolean;
setActiveChannel: (channelId: number | null) => void;
setActiveDirectMessage: (userId: number | null) => void;
}
const UnreadMessagesContext = createContext<UnreadMessagesContextType | undefined>(undefined);
export const useUnreadMessages = () => {
const context = useContext(UnreadMessagesContext);
if (context === undefined) {
throw new Error('useUnreadMessages must be used within a UnreadMessagesProvider');
}
return context;
};
interface UnreadMessagesProviderProps {
children: ReactNode;
}
export const UnreadMessagesProvider: React.FC<UnreadMessagesProviderProps> = ({ children }) => {
const [unreadChannels, setUnreadChannels] = useState<Set<number>>(new Set());
const [unreadDirectMessages, setUnreadDirectMessages] = useState<Set<number>>(new Set());
const [activeChannelId, setActiveChannelId] = useState<number | null>(null);
const [activeDirectMessageUserId, setActiveDirectMessageUserId] = useState<number | null>(null);
const { user } = useAuth();
// Listen for unread message events from the presence WebSocket
useEffect(() => {
const handleUnreadMessage = (event: CustomEvent) => {
const data = event.detail;
if (data.type === 'message' && data.message) {
// Mark channel as unread if message is from another user and channel is not active
if (user && data.message.sender_id !== user.id && activeChannelId !== data.message.channel_id) {
markChannelAsUnread(data.message.channel_id);
}
} else if (data.type === 'direct_message' && data.message) {
// Mark DM as unread if message is from another user and DM is not active
if (user && data.message.sender_id !== user.id && activeDirectMessageUserId !== data.message.sender_id) {
markDirectMessageAsUnread(data.message.sender_id);
}
}
};
window.addEventListener('unreadMessage', handleUnreadMessage as EventListener);
return () => {
window.removeEventListener('unreadMessage', handleUnreadMessage as EventListener);
};
}, [user, activeChannelId, activeDirectMessageUserId]);
const markChannelAsRead = (channelId: number) => {
setUnreadChannels(prev => {
const newSet = new Set(prev);
newSet.delete(channelId);
return newSet;
});
};
const markChannelAsUnread = (channelId: number) => {
setUnreadChannels(prev => new Set(prev).add(channelId));
};
const markDirectMessageAsRead = (userId: number) => {
setUnreadDirectMessages(prev => {
const newSet = new Set(prev);
newSet.delete(userId);
return newSet;
});
};
const markDirectMessageAsUnread = (userId: number) => {
setUnreadDirectMessages(prev => new Set(prev).add(userId));
};
const setActiveChannel = (channelId: number | null) => {
setActiveChannelId(channelId);
};
const setActiveDirectMessage = (userId: number | null) => {
setActiveDirectMessageUserId(userId);
};
const hasUnreadChannel = (channelId: number) => {
return unreadChannels.has(channelId);
};
const hasUnreadDirectMessage = (userId: number) => {
return unreadDirectMessages.has(userId);
};
return (
<UnreadMessagesContext.Provider
value={{
unreadChannels,
unreadDirectMessages,
activeChannelId,
activeDirectMessageUserId,
markChannelAsRead,
markChannelAsUnread,
markDirectMessageAsRead,
markDirectMessageAsUnread,
hasUnreadChannel,
hasUnreadDirectMessage,
setActiveChannel,
setActiveDirectMessage,
}}
>
{children}
</UnreadMessagesContext.Provider>
);
};