diff --git a/src/frontend/src/features/notifications/MainNotificationToast.tsx b/src/frontend/src/features/notifications/MainNotificationToast.tsx
index 879dda63..c61d8769 100644
--- a/src/frontend/src/features/notifications/MainNotificationToast.tsx
+++ b/src/frontend/src/features/notifications/MainNotificationToast.tsx
@@ -4,13 +4,34 @@ import { Participant, RemoteParticipant, RoomEvent } from 'livekit-client'
import { ToastProvider, toastQueue } from './components/ToastProvider'
import { NotificationType } from './NotificationType'
import { Div } from '@/primitives'
-import { isMobileBrowser } from '@livekit/components-core'
+import { ChatMessage, isMobileBrowser } from '@livekit/components-core'
import { useNotificationSound } from '@/features/notifications/hooks/useSoundNotification'
export const MainNotificationToast = () => {
const room = useRoomContext()
const { triggerNotificationSound } = useNotificationSound()
+ useEffect(() => {
+ const handleChatMessage = (
+ chatMessage: ChatMessage,
+ participant?: Participant | undefined
+ ) => {
+ if (!participant || participant.isLocal) return
+ toastQueue.add(
+ {
+ participant: participant,
+ message: chatMessage.message,
+ type: NotificationType.MessageReceived,
+ },
+ { timeout: 5000 }
+ )
+ }
+ room.on(RoomEvent.ChatMessage, handleChatMessage)
+ return () => {
+ room.off(RoomEvent.ChatMessage, handleChatMessage)
+ }
+ }, [room])
+
useEffect(() => {
const handleDataReceived = (
payload: Uint8Array,
@@ -32,7 +53,7 @@ export const MainNotificationToast = () => {
)
break
default:
- console.warn(`Unhandled notification type: ${notificationType}`)
+ return
}
}
room.on(RoomEvent.DataReceived, handleDataReceived)
diff --git a/src/frontend/src/features/notifications/NotificationType.ts b/src/frontend/src/features/notifications/NotificationType.ts
index db0bf4e9..8618fe49 100644
--- a/src/frontend/src/features/notifications/NotificationType.ts
+++ b/src/frontend/src/features/notifications/NotificationType.ts
@@ -2,5 +2,5 @@ export enum NotificationType {
ParticipantJoined = 'participantJoined',
HandRaised = 'handRaised',
ParticipantMuted = 'participantMuted',
- // todo - implement message received notification
+ MessageReceived = 'messageReceived',
}
diff --git a/src/frontend/src/features/notifications/components/ToastMessageReceived.tsx b/src/frontend/src/features/notifications/components/ToastMessageReceived.tsx
new file mode 100644
index 00000000..631838dc
--- /dev/null
+++ b/src/frontend/src/features/notifications/components/ToastMessageReceived.tsx
@@ -0,0 +1,74 @@
+import { useToast } from '@react-aria/toast'
+import { useEffect, useRef } from 'react'
+
+import { StyledToastContainer, ToastProps } from './Toast'
+import { Text } from '@/primitives'
+import { RiMessage2Line } from '@remixicon/react'
+import { useSidePanel } from '@/features/rooms/livekit/hooks/useSidePanel'
+import { Button as RACButton } from 'react-aria-components'
+import { css } from '@/styled-system/css'
+import { useTranslation } from 'react-i18next'
+
+export function ToastMessageReceived({ state, ...props }: ToastProps) {
+ const { t } = useTranslation('notifications')
+ const ref = useRef(null)
+ const { toastProps } = useToast(props, state, ref)
+
+ const toast = props.toast
+
+ const participant = toast.content.participant
+ const message = toast.content.message
+
+ const { isChatOpen, toggleChat } = useSidePanel()
+
+ useEffect(() => {
+ if (isChatOpen) {
+ state.close(toast.key)
+ }
+ }, [isChatOpen, toast, state])
+
+ if (isChatOpen) return null
+
+ return (
+
+ toggleChat()} aria-label={t('openChat')}>
+
+
+
+ {participant.name}
+
+
+ {message}
+
+
+
+
+ )
+}
diff --git a/src/frontend/src/features/notifications/components/ToastRegion.tsx b/src/frontend/src/features/notifications/components/ToastRegion.tsx
index c80d5864..7151f223 100644
--- a/src/frontend/src/features/notifications/components/ToastRegion.tsx
+++ b/src/frontend/src/features/notifications/components/ToastRegion.tsx
@@ -7,6 +7,7 @@ import { ToastJoined } from './ToastJoined'
import { ToastData } from './ToastProvider'
import { ToastRaised } from './ToastRaised'
import { ToastMuted } from './ToastMuted'
+import { ToastMessageReceived } from './ToastMessageReceived'
interface ToastRegionProps extends AriaToastRegionProps {
state: ToastState
@@ -27,6 +28,11 @@ export function ToastRegion({ state, ...props }: ToastRegionProps) {
if (toast.content?.type === NotificationType.ParticipantMuted) {
return
}
+ if (toast.content?.type === NotificationType.MessageReceived) {
+ return (
+
+ )
+ }
return
})}
diff --git a/src/frontend/src/locales/de/notifications.json b/src/frontend/src/locales/de/notifications.json
index 56caf4e6..a000aced 100644
--- a/src/frontend/src/locales/de/notifications.json
+++ b/src/frontend/src/locales/de/notifications.json
@@ -7,5 +7,6 @@
"description": "",
"cta": ""
},
- "muted": ""
+ "muted": "",
+ "openChat": ""
}
diff --git a/src/frontend/src/locales/en/notifications.json b/src/frontend/src/locales/en/notifications.json
index 23a7af21..07b0437f 100644
--- a/src/frontend/src/locales/en/notifications.json
+++ b/src/frontend/src/locales/en/notifications.json
@@ -7,5 +7,6 @@
"description": "{{name}} has raised their hand.",
"cta": "Open waiting list"
},
- "muted": "{{name}} has muted your microphone. No participant can hear you."
+ "muted": "{{name}} has muted your microphone. No participant can hear you.",
+ "openChat": "Open chat"
}
diff --git a/src/frontend/src/locales/fr/notifications.json b/src/frontend/src/locales/fr/notifications.json
index a807b7c8..40cbe068 100644
--- a/src/frontend/src/locales/fr/notifications.json
+++ b/src/frontend/src/locales/fr/notifications.json
@@ -7,5 +7,6 @@
"description": "{{name}} a levé la main.",
"cta": "Ouvrir la file d'attente"
},
- "muted": "{{name}} a coupé votre micro. Aucun participant ne peut l'entendre."
+ "muted": "{{name}} a coupé votre micro. Aucun participant ne peut l'entendre.",
+ "openChat": "Ouvrir le chat"
}