diff --git a/src/frontend/src/features/notifications/MainNotificationToast.tsx b/src/frontend/src/features/notifications/MainNotificationToast.tsx
index ac1c00d7..cd664160 100644
--- a/src/frontend/src/features/notifications/MainNotificationToast.tsx
+++ b/src/frontend/src/features/notifications/MainNotificationToast.tsx
@@ -1,6 +1,6 @@
import { useEffect } from 'react'
import { useRoomContext } from '@livekit/components-react'
-import { Participant, RoomEvent } from 'livekit-client'
+import { Participant, RemoteParticipant, RoomEvent } from 'livekit-client'
import { ToastProvider, toastQueue } from './components/ToastProvider'
import { NotificationType } from './NotificationType'
import { Div } from '@/primitives'
@@ -27,6 +27,45 @@ export const MainNotificationToast = () => {
}
}, [room])
+ // fixme - close all related toasters when hands are lowered remotely
+ useEffect(() => {
+ const decoder = new TextDecoder()
+
+ const handleNotificationReceived = (
+ payload: Uint8Array,
+ participant?: RemoteParticipant
+ ) => {
+ if (!participant) {
+ return
+ }
+ const notification = decoder.decode(payload)
+ const existingToast = toastQueue.visibleToasts.find(
+ (toast) =>
+ toast.content.participant === participant &&
+ toast.content.type === NotificationType.Raised
+ )
+ if (existingToast && notification === NotificationType.Lowered) {
+ toastQueue.close(existingToast.key)
+ return
+ }
+ if (!existingToast && notification === NotificationType.Raised) {
+ toastQueue.add(
+ {
+ participant,
+ type: NotificationType.Raised,
+ },
+ { timeout: 5000 }
+ )
+ }
+ }
+
+ room.on(RoomEvent.DataReceived, handleNotificationReceived)
+
+ return () => {
+ room.off(RoomEvent.DataReceived, handleNotificationReceived)
+ }
+ }, [room])
+
return (
diff --git a/src/frontend/src/features/notifications/NotificationType.ts b/src/frontend/src/features/notifications/NotificationType.ts
index 2f965016..6b1570f9 100644
--- a/src/frontend/src/features/notifications/NotificationType.ts
+++ b/src/frontend/src/features/notifications/NotificationType.ts
@@ -1,4 +1,6 @@
export enum NotificationType {
Joined = 'joined',
Default = 'default',
+ Raised = 'raised',
+ Lowered = 'lowered',
}
diff --git a/src/frontend/src/features/notifications/components/ToastRaised.tsx b/src/frontend/src/features/notifications/components/ToastRaised.tsx
new file mode 100644
index 00000000..1479d959
--- /dev/null
+++ b/src/frontend/src/features/notifications/components/ToastRaised.tsx
@@ -0,0 +1,66 @@
+import { useToast } from '@react-aria/toast'
+import { useRef } from 'react'
+
+import { StyledToastContainer, ToastProps } from './Toast'
+import { HStack } from '@/styled-system/jsx'
+import { Button, Div } from '@/primitives'
+import { useTranslation } from 'react-i18next'
+import { RiCloseLine, RiHand } from '@remixicon/react'
+import { useWidgetInteraction } from '@/features/rooms/livekit/hooks/useWidgetInteraction'
+
+export function ToastRaised({ state, ...props }: ToastProps) {
+ const { t } = useTranslation('notifications')
+ const ref = useRef(null)
+ const { toastProps, contentProps, titleProps, closeButtonProps } = useToast(
+ props,
+ state,
+ ref
+ )
+ const participant = props.toast.content.participant
+ const { isParticipantsOpen, toggleParticipants } = useWidgetInteraction()
+
+ return (
+
+
+
+
+ {t('raised.description', {
+ name: participant.name || t('defaultName'),
+ })}
+
+ {!isParticipantsOpen && (
+
+ )}
+
+
+
+ )
+}
diff --git a/src/frontend/src/features/notifications/components/ToastRegion.tsx b/src/frontend/src/features/notifications/components/ToastRegion.tsx
index b27ffc1c..0c0b4ab2 100644
--- a/src/frontend/src/features/notifications/components/ToastRegion.tsx
+++ b/src/frontend/src/features/notifications/components/ToastRegion.tsx
@@ -5,6 +5,7 @@ import { useRef } from 'react'
import { NotificationType } from '../NotificationType'
import { ToastJoined } from './ToastJoined'
import { ToastData } from './ToastProvider'
+import { ToastRaised } from '@/features/notifications/components/ToastRaised.tsx'
interface ToastRegionProps extends AriaToastRegionProps {
state: ToastState
@@ -19,6 +20,9 @@ export function ToastRegion({ state, ...props }: ToastRegionProps) {
if (toast.content?.type === NotificationType.Joined) {
return
}
+ if (toast.content?.type === NotificationType.Raised) {
+ return
+ }
return
})}
diff --git a/src/frontend/src/features/rooms/livekit/components/controls/HandToggle.tsx b/src/frontend/src/features/rooms/livekit/components/controls/HandToggle.tsx
index db917c4c..ce55e079 100644
--- a/src/frontend/src/features/rooms/livekit/components/controls/HandToggle.tsx
+++ b/src/frontend/src/features/rooms/livekit/components/controls/HandToggle.tsx
@@ -2,21 +2,33 @@ import { useTranslation } from 'react-i18next'
import { RiHand } from '@remixicon/react'
import { ToggleButton } from '@/primitives'
import { css } from '@/styled-system/css'
-import { useLocalParticipant } from '@livekit/components-react'
+import { useRoomContext } from '@livekit/components-react'
import { useRaisedHand } from '@/features/rooms/livekit/hooks/useRaisedHand'
+import { NotificationType } from '@/features/notifications/NotificationType'
export const HandToggle = () => {
const { t } = useTranslation('rooms')
- const localParticipant = useLocalParticipant().localParticipant
+ const room = useRoomContext()
const { isHandRaised, toggleRaisedHand } = useRaisedHand({
- participant: localParticipant,
+ participant: room.localParticipant,
})
const label = isHandRaised
? t('controls.hand.lower')
: t('controls.hand.raise')
+ const notifyOtherParticipants = (isHandRaised: boolean) => {
+ room.localParticipant.publishData(
+ new TextEncoder().encode(
+ !isHandRaised ? NotificationType.Raised : NotificationType.Lowered
+ ),
+ {
+ reliable: true,
+ }
+ )
+ }
+
return (
{
aria-label={label}
tooltip={label}
isSelected={isHandRaised}
- onPress={() => toggleRaisedHand()}
+ onPress={() => {
+ notifyOtherParticipants(isHandRaised)
+ toggleRaisedHand()
+ }}
>
diff --git a/src/frontend/src/locales/de/notifications.json b/src/frontend/src/locales/de/notifications.json
index a173f1ed..788b95c8 100644
--- a/src/frontend/src/locales/de/notifications.json
+++ b/src/frontend/src/locales/de/notifications.json
@@ -2,5 +2,9 @@
"defaultName": "",
"joined": {
"description": ""
+ },
+ "raised": {
+ "description": "",
+ "cta": ""
}
}
diff --git a/src/frontend/src/locales/en/notifications.json b/src/frontend/src/locales/en/notifications.json
index 82351f11..d63a070a 100644
--- a/src/frontend/src/locales/en/notifications.json
+++ b/src/frontend/src/locales/en/notifications.json
@@ -2,5 +2,9 @@
"defaultName": "A contributor",
"joined": {
"description": "{{name}} join the room"
+ },
+ "raised": {
+ "description": "{{name}} raised its hand.",
+ "cta": "Open waiting list"
}
}
diff --git a/src/frontend/src/locales/fr/notifications.json b/src/frontend/src/locales/fr/notifications.json
index 1449a497..9405a191 100644
--- a/src/frontend/src/locales/fr/notifications.json
+++ b/src/frontend/src/locales/fr/notifications.json
@@ -2,5 +2,9 @@
"defaultName": "Un contributeur",
"joined": {
"description": "{{name}} participe à l'appel"
+ },
+ "raised": {
+ "description": "{{name}} a levé la main.",
+ "cta": "Ouvrir la file d'attente"
}
}
diff --git a/src/frontend/src/primitives/buttonRecipe.ts b/src/frontend/src/primitives/buttonRecipe.ts
index 628d8206..bfc645d6 100644
--- a/src/frontend/src/primitives/buttonRecipe.ts
+++ b/src/frontend/src/primitives/buttonRecipe.ts
@@ -77,6 +77,7 @@ export const buttonRecipe = cva({
color: 'primary',
'&[data-hovered]': {
background: 'gray.100 !important',
+ color: 'primary !important',
},
},
danger: {