v0.3.0
This commit is contained in:
@@ -9,6 +9,8 @@ import ProfileModal from '../components/ProfileModal.jsx';
|
||||
import UserManagerModal from '../components/UserManagerModal.jsx';
|
||||
import SettingsModal from '../components/SettingsModal.jsx';
|
||||
import NewChatModal from '../components/NewChatModal.jsx';
|
||||
import GlobalBar from '../components/GlobalBar.jsx';
|
||||
import AboutModal from '../components/AboutModal.jsx';
|
||||
import './Chat.css';
|
||||
|
||||
function urlBase64ToUint8Array(base64String) {
|
||||
@@ -99,7 +101,8 @@ export default function Chat() {
|
||||
privateGroups: prev.privateGroups.map(updateGroup),
|
||||
};
|
||||
});
|
||||
// Increment unread count for the group if not currently viewing it
|
||||
// Don't badge: message is from this user, or group is currently open
|
||||
if (msg.user_id === user?.id) return;
|
||||
setUnreadGroups(prev => {
|
||||
if (msg.group_id === activeGroupId) return prev;
|
||||
const next = new Map(prev);
|
||||
@@ -110,14 +113,8 @@ export default function Chat() {
|
||||
|
||||
const handleNotification = (notif) => {
|
||||
if (notif.type === 'private_message') {
|
||||
// Private message unread is already handled by handleNewMsg above
|
||||
// (kept for push notification path when socket is not the source)
|
||||
setUnreadGroups(prev => {
|
||||
if (notif.groupId === activeGroupId) return prev;
|
||||
const next = new Map(prev);
|
||||
next.set(notif.groupId, (next.get(notif.groupId) || 0) + 1);
|
||||
return next;
|
||||
});
|
||||
// Badge is already handled by handleNewMsg via message:new socket event.
|
||||
// Nothing to do here for the socket path.
|
||||
} else {
|
||||
setNotifications(prev => [notif, ...prev]);
|
||||
toast(`${notif.fromUser?.display_name || notif.fromUser?.name || 'Someone'} mentioned you`, 'default', 4000);
|
||||
@@ -127,11 +124,51 @@ export default function Chat() {
|
||||
socket.on('message:new', handleNewMsg);
|
||||
socket.on('notification:new', handleNotification);
|
||||
|
||||
// Group list real-time updates
|
||||
const handleGroupNew = ({ group }) => {
|
||||
// Join the socket room for this new group
|
||||
socket.emit('group:join-room', { groupId: group.id });
|
||||
// Reload the full group list so name/metadata is correct
|
||||
loadGroups();
|
||||
};
|
||||
const handleGroupDeleted = ({ groupId }) => {
|
||||
// Leave the socket room so we stop receiving events for this group
|
||||
socket.emit('group:leave-room', { groupId });
|
||||
setGroups(prev => ({
|
||||
publicGroups: prev.publicGroups.filter(g => g.id !== groupId),
|
||||
privateGroups: prev.privateGroups.filter(g => g.id !== groupId),
|
||||
}));
|
||||
setActiveGroupId(prev => {
|
||||
if (prev === groupId) {
|
||||
if (isMobile) setShowSidebar(true);
|
||||
return null;
|
||||
}
|
||||
return prev;
|
||||
});
|
||||
setUnreadGroups(prev => { const next = new Map(prev); next.delete(groupId); return next; });
|
||||
};
|
||||
const handleGroupUpdated = ({ group }) => {
|
||||
setGroups(prev => {
|
||||
const update = g => g.id === group.id ? { ...g, ...group } : g;
|
||||
return {
|
||||
publicGroups: prev.publicGroups.map(update),
|
||||
privateGroups: prev.privateGroups.map(update),
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
socket.on('group:new', handleGroupNew);
|
||||
socket.on('group:deleted', handleGroupDeleted);
|
||||
socket.on('group:updated', handleGroupUpdated);
|
||||
|
||||
return () => {
|
||||
socket.off('message:new', handleNewMsg);
|
||||
socket.off('notification:new', handleNotification);
|
||||
socket.off('group:new', handleGroupNew);
|
||||
socket.off('group:deleted', handleGroupDeleted);
|
||||
socket.off('group:updated', handleGroupUpdated);
|
||||
};
|
||||
}, [socket, toast]);
|
||||
}, [socket, toast, activeGroupId, user, isMobile, loadGroups]);
|
||||
|
||||
const selectGroup = (id) => {
|
||||
setActiveGroupId(id);
|
||||
@@ -141,6 +178,13 @@ export default function Chat() {
|
||||
setUnreadGroups(prev => { const next = new Map(prev); next.delete(id); return next; });
|
||||
};
|
||||
|
||||
// Update page title with total unread badge count
|
||||
useEffect(() => {
|
||||
const totalUnread = [...unreadGroups.values()].reduce((a, b) => a + b, 0);
|
||||
const base = document.title.replace(/^\(\d+\)\s*/, '');
|
||||
document.title = totalUnread > 0 ? `(${totalUnread}) ${base}` : base;
|
||||
}, [unreadGroups]);
|
||||
|
||||
const activeGroup = [
|
||||
...(groups.publicGroups || []),
|
||||
...(groups.privateGroups || [])
|
||||
@@ -148,33 +192,42 @@ export default function Chat() {
|
||||
|
||||
return (
|
||||
<div className="chat-layout">
|
||||
{(!isMobile || showSidebar) && (
|
||||
<Sidebar
|
||||
groups={groups}
|
||||
activeGroupId={activeGroupId}
|
||||
onSelectGroup={selectGroup}
|
||||
notifications={notifications}
|
||||
unreadGroups={unreadGroups}
|
||||
onNewChat={() => setModal('newchat')}
|
||||
onProfile={() => setModal('profile')}
|
||||
onUsers={() => setModal('users')}
|
||||
onSettings={() => setModal('settings')}
|
||||
onGroupsUpdated={loadGroups}
|
||||
/>
|
||||
)}
|
||||
{/* Global top bar — spans full width on desktop, visible on mobile sidebar view */}
|
||||
<GlobalBar isMobile={isMobile} showSidebar={showSidebar} />
|
||||
|
||||
{(!isMobile || !showSidebar) && (
|
||||
<ChatWindow
|
||||
group={activeGroup}
|
||||
onBack={isMobile ? () => setShowSidebar(true) : null}
|
||||
onGroupUpdated={loadGroups}
|
||||
/>
|
||||
)}
|
||||
<div className="chat-body">
|
||||
{(!isMobile || showSidebar) && (
|
||||
<Sidebar
|
||||
groups={groups}
|
||||
activeGroupId={activeGroupId}
|
||||
onSelectGroup={selectGroup}
|
||||
notifications={notifications}
|
||||
unreadGroups={unreadGroups}
|
||||
onNewChat={() => setModal('newchat')}
|
||||
onProfile={() => setModal('profile')}
|
||||
onUsers={() => setModal('users')}
|
||||
onSettings={() => setModal('settings')}
|
||||
onGroupsUpdated={loadGroups}
|
||||
isMobile={isMobile}
|
||||
onAbout={() => setModal('about')}
|
||||
/>
|
||||
)}
|
||||
|
||||
{(!isMobile || !showSidebar) && (
|
||||
<ChatWindow
|
||||
group={activeGroup}
|
||||
onBack={isMobile ? () => { setShowSidebar(true); setActiveGroupId(null); } : null}
|
||||
onGroupUpdated={loadGroups}
|
||||
onDirectMessage={(g) => { loadGroups(); selectGroup(g.id); }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{modal === 'profile' && <ProfileModal onClose={() => setModal(null)} />}
|
||||
{modal === 'users' && <UserManagerModal onClose={() => setModal(null)} />}
|
||||
{modal === 'settings' && <SettingsModal onClose={() => setModal(null)} />}
|
||||
{modal === 'newchat' && <NewChatModal onClose={() => setModal(null)} onCreated={(g) => { loadGroups(); setModal(null); setActiveGroupId(g.id); }} />}
|
||||
{modal === 'about' && <AboutModal onClose={() => setModal(null)} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user