Skip to content

Commit d4d6eb3

Browse files
authored
Merge pull request #82 from splitly25/remake-notification
fix lading page, notification button
2 parents 958d531 + af7a689 commit d4d6eb3

4 files changed

Lines changed: 497 additions & 408 deletions

File tree

web/src/components/Layout/Layout.jsx

Lines changed: 97 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
ListItemIcon,
1313
ListItemText,
1414
Tooltip,
15+
Badge,
1516
} from '@mui/material'
1617
import {
1718
Home as HomeIcon,
@@ -31,25 +32,37 @@ import {
3132
Edit as EditIcon,
3233
CameraAlt as CameraAltIcon,
3334
Chat as ChatIcon,
35+
Notifications as NotificationsIcon,
3436
} from '@mui/icons-material'
3537
import { useNavigate, useLocation } from 'react-router-dom'
3638
import { useState, useEffect } from 'react'
3739
import ChatbotButton from '~/components/Chatbot/ChatbotButton'
3840
import ChatbotWindow from '../Chatbot/ChatbotWindow'
3941
import { useDispatch, useSelector } from 'react-redux'
4042
import { logoutUserAPI, selectCurrentUser } from '~/redux/user/userSlice'
43+
import {
44+
fetchNotificationsAPI,
45+
addNotification,
46+
setNotificationRead,
47+
setAllNotificationsRead,
48+
setUnreadCount,
49+
selectUnreadCount,
50+
} from '~/redux/notification/notificationSlice'
4151
import { useColorScheme } from '@mui/material/styles'
4252
import { COLORS } from '~/theme'
4353
import { useChatbot } from '~/context/ChatbotContext'
4454
import { getInitials } from '~/utils/formatters'
4555
import LogoRounded from '~/assets/Splitly-Rounded.png'
4656
import LogoFull from '~/assets/Splitly-Full.png'
57+
import { socketIoInstance } from '~/main'
58+
import { toast } from 'react-toastify'
4759

4860
const SIDEBAR_WIDTH_EXPANDED = 256
4961
const SIDEBAR_WIDTH_COLLAPSED = 88
5062

5163
const Layout = ({ children }) => {
5264
const currentUser = useSelector(selectCurrentUser)
65+
const unreadNotificationCount = useSelector(selectUnreadCount)
5366
const navigate = useNavigate()
5467
const location = useLocation()
5568
const theme = useTheme()
@@ -71,6 +84,61 @@ const Layout = ({ children }) => {
7184
// Use Chatbot Context
7285
const { chatbotWindowOpen, setChatbotWindowOpen, numberOfNotifications, newMessage, setNewMessage } = useChatbot()
7386

87+
// Fetch initial notifications and setup socket listeners
88+
useEffect(() => {
89+
if (!currentUser?._id) return
90+
91+
// Fetch initial notifications
92+
dispatch(fetchNotificationsAPI({ limit: 10, offset: 0 }))
93+
94+
// Join notification room
95+
socketIoInstance.emit('FE_JOIN_NOTIFICATION_ROOM', currentUser._id)
96+
97+
// Listen for new notifications
98+
const handleNewNotification = (notification) => {
99+
if (notification?.recipientId === currentUser._id) {
100+
dispatch(addNotification(notification))
101+
// Show toast notification
102+
toast.info(notification.title || 'Bạn có thông báo mới', { theme: 'colored' })
103+
}
104+
}
105+
106+
// Listen for notification read (from other devices)
107+
const handleNotificationRead = ({ notificationId, userId }) => {
108+
if (userId === currentUser._id) {
109+
dispatch(setNotificationRead({ notificationId }))
110+
}
111+
}
112+
113+
// Listen for all notifications read (from other devices)
114+
const handleAllNotificationsRead = ({ userId }) => {
115+
if (userId === currentUser._id) {
116+
dispatch(setAllNotificationsRead())
117+
}
118+
}
119+
120+
// Listen for unread count update
121+
const handleUnreadCountUpdate = ({ userId, count }) => {
122+
if (userId === currentUser._id) {
123+
dispatch(setUnreadCount(count))
124+
}
125+
}
126+
127+
socketIoInstance.on('BE_NEW_NOTIFICATION', handleNewNotification)
128+
socketIoInstance.on('BE_NOTIFICATION_READ', handleNotificationRead)
129+
socketIoInstance.on('BE_ALL_NOTIFICATIONS_READ', handleAllNotificationsRead)
130+
socketIoInstance.on('BE_UNREAD_COUNT_UPDATE', handleUnreadCountUpdate)
131+
132+
// Cleanup on unmount
133+
return () => {
134+
socketIoInstance.emit('FE_LEAVE_NOTIFICATION_ROOM', currentUser._id)
135+
socketIoInstance.off('BE_NEW_NOTIFICATION', handleNewNotification)
136+
socketIoInstance.off('BE_NOTIFICATION_READ', handleNotificationRead)
137+
socketIoInstance.off('BE_ALL_NOTIFICATIONS_READ', handleAllNotificationsRead)
138+
socketIoInstance.off('BE_UNREAD_COUNT_UPDATE', handleUnreadCountUpdate)
139+
}
140+
}, [currentUser?._id, dispatch])
141+
74142
// Check for chatbot payload from navigation state or URL params
75143
useEffect(() => {
76144
// Check navigation state
@@ -215,7 +283,7 @@ const Layout = ({ children }) => {
215283
display: 'flex',
216284
alignItems: 'center',
217285
justifyContent: 'center',
218-
gap: (isMobile || isExpanded) ? '8px' : 0,
286+
gap: isMobile || isExpanded ? '8px' : 0,
219287
px: 2.5,
220288
minWidth: 0,
221289
transition: 'all 0.4s cubic-bezier(0.4, 0, 0.2, 1)',
@@ -229,12 +297,12 @@ const Layout = ({ children }) => {
229297
<Box
230298
component="span"
231299
sx={{
232-
opacity: (isMobile || isExpanded) ? 1 : 0,
233-
width: (isMobile || isExpanded) ? 'auto' : 0,
300+
opacity: isMobile || isExpanded ? 1 : 0,
301+
width: isMobile || isExpanded ? 'auto' : 0,
234302
overflow: 'hidden',
235303
whiteSpace: 'nowrap',
236304
transition: 'opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1), width 0.4s cubic-bezier(0.4, 0, 0.2, 1)',
237-
transitionDelay: (isMobile || isExpanded) ? '0.1s' : '0s',
305+
transitionDelay: isMobile || isExpanded ? '0.1s' : '0s',
238306
}}
239307
>
240308
Tạo hóa đơn mới
@@ -254,7 +322,7 @@ const Layout = ({ children }) => {
254322
display: 'flex',
255323
alignItems: 'center',
256324
justifyContent: 'flex-start',
257-
gap: (isMobile || isExpanded) ? '12px' : 0,
325+
gap: isMobile || isExpanded ? '12px' : 0,
258326
borderRadius: '16px',
259327
height: '48px',
260328
px: 2,
@@ -294,12 +362,12 @@ const Layout = ({ children }) => {
294362
<Box
295363
component="span"
296364
sx={{
297-
opacity: (isMobile || isExpanded) ? 1 : 0,
298-
width: (isMobile || isExpanded) ? 'auto' : 0,
365+
opacity: isMobile || isExpanded ? 1 : 0,
366+
width: isMobile || isExpanded ? 'auto' : 0,
299367
overflow: 'hidden',
300368
whiteSpace: 'nowrap',
301369
transition: 'opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1), width 0.4s cubic-bezier(0.4, 0, 0.2, 1)',
302-
transitionDelay: (isMobile || isExpanded) ? '0.1s' : '0s',
370+
transitionDelay: isMobile || isExpanded ? '0.1s' : '0s',
303371
}}
304372
>
305373
{item.label}
@@ -317,8 +385,8 @@ const Layout = ({ children }) => {
317385
p: 2,
318386
display: 'flex',
319387
alignItems: 'center',
320-
gap: (isMobile || isExpanded) ? 2 : 0,
321-
justifyContent: (isMobile || isExpanded) ? 'flex-start' : 'center',
388+
gap: isMobile || isExpanded ? 2 : 0,
389+
justifyContent: isMobile || isExpanded ? 'flex-start' : 'center',
322390
cursor: 'pointer',
323391
borderRadius: '16px',
324392
mx: 2,
@@ -732,11 +800,12 @@ const Layout = ({ children }) => {
732800
</IconButton> */}
733801

734802
{/* Notifications Button - Top Right */}
735-
{/* <IconButton
803+
<IconButton
804+
onClick={() => navigate('/activity')}
736805
sx={{
737806
position: 'fixed',
738807
top: 16,
739-
right: chatbotWindowOpen && !isMobile ? `calc(${chatbotWidth} + 72px)` : 72,
808+
right: chatbotWindowOpen && !isMobile ? `calc(${chatbotWidth} + 16px)` : 20,
740809
zIndex: 1200,
741810
width: 40,
742811
height: 40,
@@ -751,8 +820,22 @@ const Layout = ({ children }) => {
751820
},
752821
}}
753822
>
754-
<NotificationsIcon fontSize="small" sx={{ color: 'text.primary' }} />
755-
</IconButton> */}
823+
<Badge
824+
badgeContent={unreadNotificationCount}
825+
color="error"
826+
max={99}
827+
sx={{
828+
'& .MuiBadge-badge': {
829+
fontSize: '10px',
830+
minWidth: '18px',
831+
height: '18px',
832+
background: COLORS.gradientPrimary,
833+
},
834+
}}
835+
>
836+
<NotificationsIcon fontSize="small" sx={{ color: 'text.primary' }} />
837+
</Badge>
838+
</IconButton>
756839

757840
{/* Chatbot Button */}
758841
<ChatbotButton

0 commit comments

Comments
 (0)