🚸(frontend) add notifications when admin removes participant permissions
Send notification to participants when admin revokes their camera, microphone, or screenshare permissions so they understand why their media suddenly stopped. Improves user experience by providing clear feedback about permission changes instead of leaving users confused about unexpected media interruptions during meetings.
This commit is contained in:
committed by
aleb_the_flash
parent
86a04ed718
commit
242e7cb148
@@ -104,6 +104,19 @@ export const MainNotificationToast = () => {
|
||||
{ timeout: NotificationDuration.ALERT }
|
||||
)
|
||||
break
|
||||
case NotificationType.PermissionsRemoved: {
|
||||
const removedSources = notification?.data?.removedSources
|
||||
if (!removedSources?.length) break
|
||||
toastQueue.add(
|
||||
{
|
||||
participant,
|
||||
type: notification.type,
|
||||
removedSources: removedSources,
|
||||
},
|
||||
{ timeout: NotificationDuration.ALERT }
|
||||
)
|
||||
break
|
||||
}
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
@@ -4,5 +4,6 @@ export interface NotificationPayload {
|
||||
type: NotificationType
|
||||
data?: {
|
||||
emoji?: string
|
||||
removedSources?: string[]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,4 +13,5 @@ export enum NotificationType {
|
||||
ScreenRecordingStopped = 'screenRecordingStopped',
|
||||
ScreenRecordingLimitReached = 'screenRecordingLimitReached',
|
||||
RecordingSaving = 'recordingSaving',
|
||||
PermissionsRemoved = 'permissionsRemoved',
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { useToast } from '@react-aria/toast'
|
||||
import { useMemo, useRef } from 'react'
|
||||
|
||||
import { StyledToastContainer, ToastProps } from './Toast'
|
||||
import { HStack } from '@/styled-system/jsx'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
export function ToastPermissionsRemoved({ state, ...props }: ToastProps) {
|
||||
const { t } = useTranslation('notifications', {
|
||||
keyPrefix: 'permissionsRemoved',
|
||||
})
|
||||
const ref = useRef(null)
|
||||
const { toastProps, contentProps } = useToast(props, state, ref)
|
||||
const participant = props.toast.content.participant
|
||||
|
||||
const key = useMemo(() => {
|
||||
const sources = props.toast.content.removedSources
|
||||
|
||||
if (!Array.isArray(sources) || sources.length === 0) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
if (sources.length === 1) {
|
||||
return sources[0]
|
||||
}
|
||||
|
||||
if (sources.length === 2 && sources.includes('screen_share')) {
|
||||
return 'screen_share'
|
||||
}
|
||||
|
||||
return undefined
|
||||
}, [props.toast.content.removedSources])
|
||||
|
||||
if (!participant || !key) return null
|
||||
|
||||
return (
|
||||
<StyledToastContainer {...toastProps} ref={ref}>
|
||||
<HStack
|
||||
justify="center"
|
||||
alignItems="center"
|
||||
{...contentProps}
|
||||
padding={14}
|
||||
gap={0}
|
||||
>
|
||||
{t(key)}
|
||||
</HStack>
|
||||
</StyledToastContainer>
|
||||
)
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import { ToastMessageReceived } from './ToastMessageReceived'
|
||||
import { ToastLowerHand } from './ToastLowerHand'
|
||||
import { ToastAnyRecording } from './ToastAnyRecording'
|
||||
import { ToastRecordingSaving } from './ToastRecordingSaving'
|
||||
import { ToastPermissionsRemoved } from './ToastPermissionsRemoved'
|
||||
|
||||
interface ToastRegionProps extends AriaToastRegionProps {
|
||||
state: ToastState<ToastData>
|
||||
@@ -30,6 +31,11 @@ const renderToast = (
|
||||
case NotificationType.ParticipantMuted:
|
||||
return <ToastMuted key={toast.key} toast={toast} state={state} />
|
||||
|
||||
case NotificationType.PermissionsRemoved:
|
||||
return (
|
||||
<ToastPermissionsRemoved key={toast.key} toast={toast} state={state} />
|
||||
)
|
||||
|
||||
case NotificationType.MessageReceived:
|
||||
return (
|
||||
<ToastMessageReceived key={toast.key} toast={toast} state={state} />
|
||||
|
||||
@@ -10,6 +10,10 @@ import { useRoomData } from '@/features/rooms/livekit/hooks/useRoomData'
|
||||
import { isSubsetOf } from '@/features/rooms/utils/isSubsetOf'
|
||||
import { getParticipantIsRoomAdmin } from '@/features/rooms/utils/getParticipantIsRoomAdmin'
|
||||
import Source = Track.Source
|
||||
import {
|
||||
NotificationType,
|
||||
useNotifyParticipants,
|
||||
} from '@/features/notifications'
|
||||
|
||||
export const updatePublishSources = (
|
||||
currentSources: Source[],
|
||||
@@ -33,6 +37,8 @@ export const usePublishSourcesManager = () => {
|
||||
const { data: configData } = useConfig()
|
||||
const configuration = data?.configuration
|
||||
|
||||
const { notifyParticipants } = useNotifyParticipants()
|
||||
|
||||
const defaultSources = configData?.livekit?.default_sources?.map((source) => {
|
||||
return source as Source
|
||||
})
|
||||
@@ -87,6 +93,25 @@ export const usePublishSourcesManager = () => {
|
||||
newSources
|
||||
)
|
||||
|
||||
if (!enabled) {
|
||||
/*
|
||||
* We can't rely solely on the ParticipantPermissionsChanged event here,
|
||||
* because for local participants it is emitted twice (once from Participant, once from LocalParticipant).
|
||||
* livekit/client-sdk-js/issues/1637
|
||||
* */
|
||||
await notifyParticipants({
|
||||
type: NotificationType.PermissionsRemoved,
|
||||
destinationIdentities: unprivilegedRemoteParticipants.map(
|
||||
(p) => p.identity
|
||||
),
|
||||
additionalData: {
|
||||
data: {
|
||||
removedSources: sources,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return { configuration: newConfiguration }
|
||||
} catch (error) {
|
||||
console.error(`Failed to update ${sources}:`, error)
|
||||
@@ -94,6 +119,7 @@ export const usePublishSourcesManager = () => {
|
||||
}
|
||||
},
|
||||
[
|
||||
notifyParticipants,
|
||||
configuration,
|
||||
currentSources,
|
||||
roomId,
|
||||
|
||||
@@ -16,6 +16,11 @@
|
||||
"reaction": {
|
||||
"description": "{{name}} hat mit {{emoji}} reagiert"
|
||||
},
|
||||
"permissionsRemoved": {
|
||||
"camera": "Der Organisator hat das Video für alle Teilnehmer deaktiviert.",
|
||||
"microphone": "Der Organisator hat das Mikrofon für alle Teilnehmer deaktiviert.",
|
||||
"screen_share": "Der Organisator hat die Bildschirmfreigabe für alle Teilnehmer deaktiviert."
|
||||
},
|
||||
"waitingParticipants": {
|
||||
"one": "Eine Person möchte diesem Anruf beitreten.",
|
||||
"several": "Mehrere Personen möchten diesem Anruf beitreten.",
|
||||
|
||||
@@ -16,6 +16,11 @@
|
||||
"reaction": {
|
||||
"description": "{{name}} reacted with {{emoji}}"
|
||||
},
|
||||
"permissionsRemoved": {
|
||||
"camera": "The host has disabled video for all participants.",
|
||||
"microphone": "The host has disabled the microphone for all participants.",
|
||||
"screen_share": "The host has disabled screen sharing for all participants."
|
||||
},
|
||||
"waitingParticipants": {
|
||||
"one": "One person wants to join this call.",
|
||||
"several": "Several people want to join this call.",
|
||||
|
||||
@@ -16,6 +16,11 @@
|
||||
"reaction": {
|
||||
"description": "{{name}} a reagi avec {{emoji}}"
|
||||
},
|
||||
"permissionsRemoved": {
|
||||
"camera": "L'organisateur a désactivé la vidéo de tous les participants.",
|
||||
"microphone": "L'organisateur a désactivé le micro de tous les participants.",
|
||||
"screen_share": "L'organisateur a désactivé le partage d'écran de tous les participants."
|
||||
},
|
||||
"waitingParticipants": {
|
||||
"one": "Une personne souhaite participer à cet appel.",
|
||||
"several": "Plusieurs personnes souhaitent participer à cet appel.",
|
||||
|
||||
@@ -16,6 +16,11 @@
|
||||
"reaction": {
|
||||
"description": "{{name}} reageerde met {{emoji}}"
|
||||
},
|
||||
"permissionsRemoved": {
|
||||
"camera": "De organisator heeft de video voor alle deelnemers uitgeschakeld.",
|
||||
"microphone": "De organisator heeft de microfoon voor alle deelnemers uitgeschakeld.",
|
||||
"screen_share": "De organisator heeft het schermdelen voor alle deelnemers uitgeschakeld."
|
||||
},
|
||||
"waitingParticipants": {
|
||||
"one": "Eén persoon wil deelnemen aan dit gesprek.",
|
||||
"several": "Meerdere mensen willen deelnemen aan dit gesprek.",
|
||||
|
||||
Reference in New Issue
Block a user