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')}> +
+
+
+ + {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" }