socket and badge
This commit is contained in:
@@ -17,6 +17,7 @@ import { refreshToken } from './pages/auth/service/AuthService';
|
|||||||
import { Paths } from './utils/Paths';
|
import { Paths } from './utils/Paths';
|
||||||
import Login from './pages/auth/Login';
|
import Login from './pages/auth/Login';
|
||||||
import { EmailWebSocketProvider } from './contexts/EmailWebSocketContext';
|
import { EmailWebSocketProvider } from './contexts/EmailWebSocketContext';
|
||||||
|
import SocketManagment from './lib/SocketManagment';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
@@ -133,6 +134,7 @@ const App: FC = () => {
|
|||||||
{isLogin === 'isLogin' && userToken ? (
|
{isLogin === 'isLogin' && userToken ? (
|
||||||
<EmailWebSocketProvider token={userToken}>
|
<EmailWebSocketProvider token={userToken}>
|
||||||
<Main />
|
<Main />
|
||||||
|
<SocketManagment />
|
||||||
</EmailWebSocketProvider>
|
</EmailWebSocketProvider>
|
||||||
) : isLogin === 'isNotLogin' ? (
|
) : isLogin === 'isNotLogin' ? (
|
||||||
<Login />
|
<Login />
|
||||||
|
|||||||
@@ -62,6 +62,9 @@ export const useEmailEvents = (
|
|||||||
if (handlers.onEmailRead) {
|
if (handlers.onEmailRead) {
|
||||||
socket.on("email_read", handlers.onEmailRead);
|
socket.on("email_read", handlers.onEmailRead);
|
||||||
}
|
}
|
||||||
|
if (handlers.onEmailRead) {
|
||||||
|
socket.on("email_unread", handlers.onEmailRead);
|
||||||
|
}
|
||||||
|
|
||||||
if (handlers.onEmailDeleted) {
|
if (handlers.onEmailDeleted) {
|
||||||
socket.on("email_deleted", handlers.onEmailDeleted);
|
socket.on("email_deleted", handlers.onEmailDeleted);
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import Filters, { FilterValues } from '../../components/Filters';
|
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 { useTranslation } from 'react-i18next'
|
||||||
import Table from '../../components/Table';
|
import Table from '../../components/Table';
|
||||||
import { ColumnType } from '../../components/types/TableTypes';
|
import { ColumnType } from '../../components/types/TableTypes';
|
||||||
@@ -11,10 +11,10 @@ import { InboxMessage } from './types/Types';
|
|||||||
import { formatDate } from '@/config/func';
|
import { formatDate } from '@/config/func';
|
||||||
import MarkAsRead from '@/assets/images/mark_as_read.svg'
|
import MarkAsRead from '@/assets/images/mark_as_read.svg'
|
||||||
import { useEmailActions } from '@/hooks/useEmailActions';
|
import { useEmailActions } from '@/hooks/useEmailActions';
|
||||||
import { useEmailWebSocketContext } from '@/contexts/EmailWebSocketContext';
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
import { useEmailEvents } from '@/hooks/useEmailEvents';
|
|
||||||
|
|
||||||
const List: FC = () => {
|
const List: FC = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -28,31 +28,12 @@ const List: FC = () => {
|
|||||||
...filters
|
...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
|
// Force refresh when component mounts or location changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Refetch when coming back to inbox from other pages
|
// Refetch when coming back to inbox from other pages
|
||||||
if (location.pathname === '/') {
|
if (location.pathname === '/') {
|
||||||
refetch();
|
refetch();
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['mailbox-count'] });
|
||||||
}
|
}
|
||||||
}, [location.pathname, refetch]);
|
}, [location.pathname, refetch]);
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ import { useSharedStore } from './store/sharedStore'
|
|||||||
import { clx } from '../helpers/utils'
|
import { clx } from '../helpers/utils'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Button from '@/components/Button'
|
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 = () => {
|
const SideBar: FC = () => {
|
||||||
|
|
||||||
@@ -15,11 +18,12 @@ const SideBar: FC = () => {
|
|||||||
const { openSidebar, setOpenSidebar } = useSharedStore()
|
const { openSidebar, setOpenSidebar } = useSharedStore()
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
const { setOpenNewMessage } = useSharedStore()
|
const { setOpenNewMessage } = useSharedStore()
|
||||||
|
const { data: mailboxCount } = useMailboxCount()
|
||||||
|
|
||||||
const isActive = (path: string) => location.pathname === path
|
const isActive = (path: string) => location.pathname === path
|
||||||
|
|
||||||
const iconSizeSideBar = 20
|
const iconSizeSideBar = 20
|
||||||
|
if (!mailboxCount?.data) return null
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{
|
{
|
||||||
@@ -61,6 +65,7 @@ const SideBar: FC = () => {
|
|||||||
title={t('sidebar.received')}
|
title={t('sidebar.received')}
|
||||||
isActive={isActive(Paths.received)}
|
isActive={isActive(Paths.received)}
|
||||||
link={Paths.received}
|
link={Paths.received}
|
||||||
|
count={mailboxCount?.data?.mailboxes?.find((item: MailboxCount) => item.name === MailboxEnum.INBOX)?.unseen}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SideBarItem
|
<SideBarItem
|
||||||
@@ -75,6 +80,7 @@ const SideBar: FC = () => {
|
|||||||
title={t('sidebar.draft')}
|
title={t('sidebar.draft')}
|
||||||
isActive={isActive(Paths.draft)}
|
isActive={isActive(Paths.draft)}
|
||||||
link={Paths.draft}
|
link={Paths.draft}
|
||||||
|
count={mailboxCount?.data?.mailboxes?.find((item: MailboxCount) => item.name === MailboxEnum.DRAFTS)?.total}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SideBarItem
|
<SideBarItem
|
||||||
@@ -109,6 +115,7 @@ const SideBar: FC = () => {
|
|||||||
title={t('sidebar.spam')}
|
title={t('sidebar.spam')}
|
||||||
isActive={isActive(Paths.spam)}
|
isActive={isActive(Paths.spam)}
|
||||||
link={Paths.spam}
|
link={Paths.spam}
|
||||||
|
count={mailboxCount?.data?.mailboxes?.find((item: MailboxCount) => item.name === MailboxEnum.Junk)?.unseen}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ type Props = {
|
|||||||
isActive: boolean,
|
isActive: boolean,
|
||||||
link: string,
|
link: string,
|
||||||
isLogout?: boolean,
|
isLogout?: boolean,
|
||||||
isWithoutLine?: boolean
|
isWithoutLine?: boolean,
|
||||||
|
count?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
const SideBarItem: FC<Props> = (props: Props) => {
|
const SideBarItem: FC<Props> = (props: Props) => {
|
||||||
@@ -31,11 +32,16 @@ const SideBarItem: FC<Props> = (props: Props) => {
|
|||||||
!props.isActive && 'invisible'
|
!props.isActive && 'invisible'
|
||||||
)}></div>
|
)}></div>
|
||||||
}
|
}
|
||||||
<div className='flex gap-3 items-center'>
|
<div className='flex flex-1 gap-3 items-center'>
|
||||||
{props.icon}
|
{props.icon}
|
||||||
<div className={props.isActive ? 'text-black' : ''}>
|
<div className={props.isActive || !!props.count ? 'text-black' : ''}>
|
||||||
{props.title}
|
{props.title}
|
||||||
</div>
|
</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>
|
</div>
|
||||||
|
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -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,
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import axios from "@/config/axios";
|
||||||
|
|
||||||
|
export const getMailboxCount = async () => {
|
||||||
|
const { data } = await axios.get("/mailbox/counts");
|
||||||
|
return data;
|
||||||
|
};
|
||||||
@@ -10,3 +10,10 @@ export type SharedStoreType = {
|
|||||||
editingDraftId: number | null;
|
editingDraftId: number | null;
|
||||||
setEditingDraftId: (id: number | null) => void;
|
setEditingDraftId: (id: number | null) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type MailboxCount = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
unseen: number;
|
||||||
|
total: number;
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user