socket and badge

This commit is contained in:
hamid zarghami
2025-07-23 14:45:57 +03:30
parent 95f3c16bb5
commit 0778ecae76
9 changed files with 86 additions and 27 deletions
+2
View File
@@ -17,6 +17,7 @@ import { refreshToken } from './pages/auth/service/AuthService';
import { Paths } from './utils/Paths';
import Login from './pages/auth/Login';
import { EmailWebSocketProvider } from './contexts/EmailWebSocketContext';
import SocketManagment from './lib/SocketManagment';
declare global {
interface Window {
@@ -133,6 +134,7 @@ const App: FC = () => {
{isLogin === 'isLogin' && userToken ? (
<EmailWebSocketProvider token={userToken}>
<Main />
<SocketManagment />
</EmailWebSocketProvider>
) : isLogin === 'isNotLogin' ? (
<Login />
+3
View File
@@ -62,6 +62,9 @@ export const useEmailEvents = (
if (handlers.onEmailRead) {
socket.on("email_read", handlers.onEmailRead);
}
if (handlers.onEmailRead) {
socket.on("email_unread", handlers.onEmailRead);
}
if (handlers.onEmailDeleted) {
socket.on("email_deleted", handlers.onEmailDeleted);
+37
View File
@@ -0,0 +1,37 @@
import { useEmailWebSocketContext } from '@/contexts/EmailWebSocketContext';
import { useEmailEvents } from '@/hooks/useEmailEvents';
import { FC, useCallback, useEffect } from 'react'
import { useQueryClient } from '@tanstack/react-query';
const SocketManagment: FC = () => {
const { socket } = useEmailWebSocketContext();
const queryClient = useQueryClient();
useEffect(() => {
if (socket) {
socket.on('connect', () => {
});
}
}, [socket]);
const handleNewEmailEvent = useCallback(() => {
queryClient.invalidateQueries({ queryKey: ['mailbox-count'] });
queryClient.invalidateQueries({ queryKey: ['inbox'] });
}, []);
const handleEmailActionEvent = useCallback(() => {
queryClient.invalidateQueries({ queryKey: ['mailbox-count'] });
queryClient.invalidateQueries({ queryKey: ['inbox'] });
}, []);
useEmailEvents(socket, {
onNewEmail: handleNewEmailEvent,
onEmailRead: handleEmailActionEvent,
onEmailDeleted: handleEmailActionEvent,
// onMailboxUpdated: handleEmailActionEvent,
});
return null
}
export default SocketManagment
+4 -23
View File
@@ -1,5 +1,5 @@
import Filters, { FilterValues } from '../../components/Filters';
import { FC, useState, useEffect, useCallback } from 'react'
import { FC, useState, useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import Table from '../../components/Table';
import { ColumnType } from '../../components/types/TableTypes';
@@ -11,10 +11,10 @@ import { InboxMessage } from './types/Types';
import { formatDate } from '@/config/func';
import MarkAsRead from '@/assets/images/mark_as_read.svg'
import { useEmailActions } from '@/hooks/useEmailActions';
import { useEmailWebSocketContext } from '@/contexts/EmailWebSocketContext';
import { useEmailEvents } from '@/hooks/useEmailEvents';
import { useQueryClient } from '@tanstack/react-query';
const List: FC = () => {
const queryClient = useQueryClient();
const navigate = useNavigate();
const location = useLocation();
const { t } = useTranslation();
@@ -28,31 +28,12 @@ const List: FC = () => {
...filters
});
// WebSocket integration
const { socket } = useEmailWebSocketContext();
const handleNewEmailEvent = useCallback(() => {
// Refresh inbox when new email arrives
refetch();
}, [refetch]);
const handleEmailActionEvent = useCallback(() => {
// Refresh inbox when email actions occur
refetch();
}, [refetch]);
useEmailEvents(socket, {
onNewEmail: handleNewEmailEvent,
onEmailRead: handleEmailActionEvent,
onEmailDeleted: handleEmailActionEvent,
// onMailboxUpdated: handleEmailActionEvent,
});
// Force refresh when component mounts or location changes
useEffect(() => {
// Refetch when coming back to inbox from other pages
if (location.pathname === '/') {
refetch();
queryClient.invalidateQueries({ queryKey: ['mailbox-count'] });
}
}, [location.pathname, refetch]);
+8 -1
View File
@@ -8,6 +8,9 @@ import { useSharedStore } from './store/sharedStore'
import { clx } from '../helpers/utils'
import { useTranslation } from 'react-i18next'
import Button from '@/components/Button'
import { useMailboxCount } from './hooks/useShareData'
import { MailboxEnum } from '@/pages/received/enum/Enum'
import { MailboxCount } from './types/SharedTypes'
const SideBar: FC = () => {
@@ -15,11 +18,12 @@ const SideBar: FC = () => {
const { openSidebar, setOpenSidebar } = useSharedStore()
const location = useLocation()
const { setOpenNewMessage } = useSharedStore()
const { data: mailboxCount } = useMailboxCount()
const isActive = (path: string) => location.pathname === path
const iconSizeSideBar = 20
if (!mailboxCount?.data) return null
return (
<>
{
@@ -61,6 +65,7 @@ const SideBar: FC = () => {
title={t('sidebar.received')}
isActive={isActive(Paths.received)}
link={Paths.received}
count={mailboxCount?.data?.mailboxes?.find((item: MailboxCount) => item.name === MailboxEnum.INBOX)?.unseen}
/>
<SideBarItem
@@ -75,6 +80,7 @@ const SideBar: FC = () => {
title={t('sidebar.draft')}
isActive={isActive(Paths.draft)}
link={Paths.draft}
count={mailboxCount?.data?.mailboxes?.find((item: MailboxCount) => item.name === MailboxEnum.DRAFTS)?.total}
/>
<SideBarItem
@@ -109,6 +115,7 @@ const SideBar: FC = () => {
title={t('sidebar.spam')}
isActive={isActive(Paths.spam)}
link={Paths.spam}
count={mailboxCount?.data?.mailboxes?.find((item: MailboxCount) => item.name === MailboxEnum.Junk)?.unseen}
/>
</div>
+9 -3
View File
@@ -11,7 +11,8 @@ type Props = {
isActive: boolean,
link: string,
isLogout?: boolean,
isWithoutLine?: boolean
isWithoutLine?: boolean,
count?: number
}
const SideBarItem: FC<Props> = (props: Props) => {
@@ -31,11 +32,16 @@ const SideBarItem: FC<Props> = (props: Props) => {
!props.isActive && 'invisible'
)}></div>
}
<div className='flex gap-3 items-center'>
<div className='flex flex-1 gap-3 items-center'>
{props.icon}
<div className={props.isActive ? 'text-black' : ''}>
<div className={props.isActive || !!props.count ? 'text-black' : ''}>
{props.title}
</div>
<div className='flex flex-1 justify-end pl-6'>
{!!props.count && <div className='h-[18px] w-fit rounded-[6px] bg-black text-white text-xs flex items-center justify-center px-2.5 pt-0.5'>
{props.count > 100 ? '+100' : props.count}
</div>}
</div>
</div>
</Link>
+10
View File
@@ -0,0 +1,10 @@
import { getMailboxCount } from "../service/Service";
import { useQuery } from "@tanstack/react-query";
export const useMailboxCount = () => {
return useQuery({
queryKey: ["mailbox-count"],
queryFn: getMailboxCount,
staleTime: 0,
});
};
+6
View File
@@ -0,0 +1,6 @@
import axios from "@/config/axios";
export const getMailboxCount = async () => {
const { data } = await axios.get("/mailbox/counts");
return data;
};
+7
View File
@@ -10,3 +10,10 @@ export type SharedStoreType = {
editingDraftId: number | null;
setEditingDraftId: (id: number | null) => void;
};
export type MailboxCount = {
id: string;
name: string;
unseen: number;
total: number;
};