From 2623005c7e8b244a625b3766ff157149b7c613e6 Mon Sep 17 00:00:00 2001 From: lebaudantoine Date: Tue, 28 Jul 2026 20:16:37 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8(frontend)=20notify=20user=20when=20th?= =?UTF-8?q?eir=20meeting=20role=20changes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Show a notification to the user whenever their role in the meeting changes, so they immediately see when they have been promoted or demoted. --- .../notifications/MainNotificationToast.tsx | 25 +++++++++++++++++ .../notifications/NotificationDuration.ts | 1 + .../notifications/NotificationType.ts | 1 + .../notifications/components/ToastRegion.tsx | 4 +++ .../components/ToastRoleChanged.tsx | 28 +++++++++++++++++++ .../src/locales/de/notifications.json | 8 ++++++ .../src/locales/en/notifications.json | 8 ++++++ .../src/locales/fr/notifications.json | 8 ++++++ .../src/locales/nl/notifications.json | 8 ++++++ 9 files changed, 91 insertions(+) create mode 100644 src/frontend/src/features/notifications/components/ToastRoleChanged.tsx diff --git a/src/frontend/src/features/notifications/MainNotificationToast.tsx b/src/frontend/src/features/notifications/MainNotificationToast.tsx index b912de64..ff80f40c 100644 --- a/src/frontend/src/features/notifications/MainNotificationToast.tsx +++ b/src/frontend/src/features/notifications/MainNotificationToast.tsx @@ -168,6 +168,31 @@ export const MainNotificationToast = () => { } }, [room, triggerNotificationSoundIfRoomIsSmall]) + useEffect(() => { + const handleAttributeChanged = ( + changedAttributes: Record, + participant: Participant + ) => { + if (!participant.isLocal || !('room_role' in changedAttributes)) return + const newRole = changedAttributes['room_role'] + toastQueue.add( + { + participant, + type: NotificationType.RoleChanged, + newRole: newRole, + }, + { + timeout: NotificationDuration.ROLE_CHANGED, + } + ) + } + room.on(RoomEvent.ParticipantAttributesChanged, handleAttributeChanged) + + return () => { + room.off(RoomEvent.ParticipantAttributesChanged, handleAttributeChanged) + } + }, [room]) + useEffect(() => { const removeParticipantNotifications = (participant: Participant) => { toastQueue.visibleToasts.forEach((toast) => { diff --git a/src/frontend/src/features/notifications/NotificationDuration.ts b/src/frontend/src/features/notifications/NotificationDuration.ts index de51a059..193187f9 100644 --- a/src/frontend/src/features/notifications/NotificationDuration.ts +++ b/src/frontend/src/features/notifications/NotificationDuration.ts @@ -14,4 +14,5 @@ export const NotificationDuration = { RECORDING_SAVING: ToastDuration.EXTRA_LONG, REACTION_RECEIVED: ToastDuration.SHORT, RECORDING_REQUESTED: ToastDuration.LONG, + ROLE_CHANGED: ToastDuration.LONG, } as const diff --git a/src/frontend/src/features/notifications/NotificationType.ts b/src/frontend/src/features/notifications/NotificationType.ts index 1475e9c2..e42fe7c3 100644 --- a/src/frontend/src/features/notifications/NotificationType.ts +++ b/src/frontend/src/features/notifications/NotificationType.ts @@ -17,4 +17,5 @@ export enum NotificationType { ScreenRecordingLimitReached = 'screenRecordingLimitReached', RecordingSaving = 'recordingSaving', PermissionsRemoved = 'permissionsRemoved', + RoleChanged = 'roleChanged', } diff --git a/src/frontend/src/features/notifications/components/ToastRegion.tsx b/src/frontend/src/features/notifications/components/ToastRegion.tsx index 89172c8b..f39c667b 100644 --- a/src/frontend/src/features/notifications/components/ToastRegion.tsx +++ b/src/frontend/src/features/notifications/components/ToastRegion.tsx @@ -14,6 +14,7 @@ import { ToastRecordingSaving } from './ToastRecordingSaving' import { ToastPermissionsRemoved } from './ToastPermissionsRemoved' import { ToastRecordingRequest } from './ToastRecordingRequest' import { ToastAutoMuteLargeRoom } from './ToastAutoMuteLargeRoom' +import { ToastRoleChanged } from '@/features/notifications/components/ToastRoleChanged' interface ToastRegionProps extends AriaToastRegionProps { state: ToastState @@ -70,6 +71,9 @@ const renderToast = ( ) + case NotificationType.RoleChanged: + return + default: return } diff --git a/src/frontend/src/features/notifications/components/ToastRoleChanged.tsx b/src/frontend/src/features/notifications/components/ToastRoleChanged.tsx new file mode 100644 index 00000000..8cb61da1 --- /dev/null +++ b/src/frontend/src/features/notifications/components/ToastRoleChanged.tsx @@ -0,0 +1,28 @@ +import { useToast } from 'react-aria' +import { useRef } from 'react' + +import { type ToastProps } from './Toast' +import { HStack } from '@/styled-system/jsx' +import { useTranslation } from 'react-i18next' +import { StyledToastContainer } from './StyledToastContainer' + +export function ToastRoleChanged({ state, ...props }: Readonly) { + const { t } = useTranslation('notifications', { keyPrefix: 'roleChanged' }) + const ref = useRef(null) + const { toastProps, contentProps } = useToast(props, state, ref) + const newRole = t(`roles.${props.toast.content.newRole}`) + + return ( + + + {t('body', { role: newRole })} + + + ) +} diff --git a/src/frontend/src/locales/de/notifications.json b/src/frontend/src/locales/de/notifications.json index 67f495b6..41122ae9 100644 --- a/src/frontend/src/locales/de/notifications.json +++ b/src/frontend/src/locales/de/notifications.json @@ -54,5 +54,13 @@ "default": "Deine Aufzeichnung wird gespeichert! Du erhältst eine E-Mail, sobald sie bereit ist." } }, + "roleChanged": { + "body": "Deine Rolle wurde geändert. Du bist jetzt {{role}}.", + "roles": { + "owner": "Host", + "administrator": "Co-host", + "member": "Mitglied" + } + }, "openMenu": "Menü öffnen" } diff --git a/src/frontend/src/locales/en/notifications.json b/src/frontend/src/locales/en/notifications.json index 77d724bd..f64b19f5 100644 --- a/src/frontend/src/locales/en/notifications.json +++ b/src/frontend/src/locales/en/notifications.json @@ -54,5 +54,13 @@ "default": "Your recording is being saved! We’ll send a notification to your email as soon as it’s ready." } }, + "roleChanged": { + "body": "Your role has changed. You are now {{role}}.", + "roles": { + "owner": "Host", + "administrator": "Co-host", + "member": "Member" + } + }, "openMenu": "Open menu" } diff --git a/src/frontend/src/locales/fr/notifications.json b/src/frontend/src/locales/fr/notifications.json index 0a84d453..3d4a1ff7 100644 --- a/src/frontend/src/locales/fr/notifications.json +++ b/src/frontend/src/locales/fr/notifications.json @@ -54,5 +54,13 @@ "default": "Nous finalisons votre enregistrement ! Vous recevrez un e-mail dès qu’il sera prêt." } }, + "roleChanged": { + "body": "Votre rôle a changé, vous êtes maitenant {{role}}.", + "roles": { + "owner": "Organisateur", + "administrator": "Co-organisateur", + "member": "Membre" + } + }, "openMenu": "Ouvrir le menu" } diff --git a/src/frontend/src/locales/nl/notifications.json b/src/frontend/src/locales/nl/notifications.json index a90cd56f..6afc63fe 100644 --- a/src/frontend/src/locales/nl/notifications.json +++ b/src/frontend/src/locales/nl/notifications.json @@ -54,5 +54,13 @@ "default": "We zijn uw opname aan het voltooien! U ontvangt een e-mail zodra deze klaar is." } }, + "roleChanged": { + "body": "Je rol is gewijzigd. Je bent nu {{role}}.", + "roles": { + "owner": "Host", + "administrator": "Co-host", + "member": "Lid" + } + }, "openMenu": "Menu openen" }