diff --git a/src/App.tsx b/src/App.tsx index 5b4a31b..d1f2bfd 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -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 ? (
+ ) : isLogin === 'isNotLogin' ? ( diff --git a/src/hooks/useEmailEvents.ts b/src/hooks/useEmailEvents.ts index 46967a6..305f19c 100644 --- a/src/hooks/useEmailEvents.ts +++ b/src/hooks/useEmailEvents.ts @@ -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); diff --git a/src/lib/SocketManagment.tsx b/src/lib/SocketManagment.tsx new file mode 100644 index 0000000..f295f3c --- /dev/null +++ b/src/lib/SocketManagment.tsx @@ -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 \ No newline at end of file diff --git a/src/pages/received/List.tsx b/src/pages/received/List.tsx index 45a89f8..8e572a6 100644 --- a/src/pages/received/List.tsx +++ b/src/pages/received/List.tsx @@ -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]); diff --git a/src/shared/SideBar.tsx b/src/shared/SideBar.tsx index d02129b..5046d85 100644 --- a/src/shared/SideBar.tsx +++ b/src/shared/SideBar.tsx @@ -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} /> { title={t('sidebar.draft')} isActive={isActive(Paths.draft)} link={Paths.draft} + count={mailboxCount?.data?.mailboxes?.find((item: MailboxCount) => item.name === MailboxEnum.DRAFTS)?.total} /> { title={t('sidebar.spam')} isActive={isActive(Paths.spam)} link={Paths.spam} + count={mailboxCount?.data?.mailboxes?.find((item: MailboxCount) => item.name === MailboxEnum.Junk)?.unseen} /> diff --git a/src/shared/SideBarItem.tsx b/src/shared/SideBarItem.tsx index 308bf65..51b5f19 100644 --- a/src/shared/SideBarItem.tsx +++ b/src/shared/SideBarItem.tsx @@ -11,7 +11,8 @@ type Props = { isActive: boolean, link: string, isLogout?: boolean, - isWithoutLine?: boolean + isWithoutLine?: boolean, + count?: number } const SideBarItem: FC = (props: Props) => { @@ -31,11 +32,16 @@ const SideBarItem: FC = (props: Props) => { !props.isActive && 'invisible' )}> } -
+
{props.icon} -
+
{props.title}
+
+ {!!props.count &&
+ {props.count > 100 ? '+100' : props.count} +
} +
diff --git a/src/shared/hooks/useShareData.ts b/src/shared/hooks/useShareData.ts new file mode 100644 index 0000000..fa240cd --- /dev/null +++ b/src/shared/hooks/useShareData.ts @@ -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, + }); +}; diff --git a/src/shared/service/Service.ts b/src/shared/service/Service.ts new file mode 100644 index 0000000..a77d05d --- /dev/null +++ b/src/shared/service/Service.ts @@ -0,0 +1,6 @@ +import axios from "@/config/axios"; + +export const getMailboxCount = async () => { + const { data } = await axios.get("/mailbox/counts"); + return data; +}; diff --git a/src/shared/types/SharedTypes.ts b/src/shared/types/SharedTypes.ts index 866eaf8..67dd1fd 100644 --- a/src/shared/types/SharedTypes.ts +++ b/src/shared/types/SharedTypes.ts @@ -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; +};