diff --git a/CHANGELOG.md b/CHANGELOG.md index 59cc7172..36f336ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ and this project adheres to - 🐛(frontend) treat client-initiated connect aborts as events - 🐛(frontend) use state instead of a ref for MoreControls container - 🐛(frontend) stop init_virtual_background from firing on blur updates +- 🐛(frontend) hoist mute confirmation dialog to VideoConference level ## [1.27.0] - 2026-08-14 diff --git a/src/frontend/src/features/participantTile/components/participantTileFocus/MuteButton.tsx b/src/frontend/src/features/participantTile/components/participantTileFocus/MuteButton.tsx index 97a7ab12..2c2d1e06 100644 --- a/src/frontend/src/features/participantTile/components/participantTileFocus/MuteButton.tsx +++ b/src/frontend/src/features/participantTile/components/participantTileFocus/MuteButton.tsx @@ -1,11 +1,9 @@ import { Participant, Track } from 'livekit-client' import { useTranslation } from 'react-i18next' import { useTrackMutedIndicator } from '@livekit/components-react' -import { useMuteParticipant } from '@/features/rooms/api/muteParticipant' -import { useState } from 'react' import { Button } from '@/primitives' import { RiMicLine, RiMicOffLine } from '@remixicon/react' -import { MuteAlertDialog } from '@/features/rooms/livekit/components/MuteAlertDialog' +import { openMuteDialog } from '@/stores/muteDialog' export const MuteButton = ({ participant }: { participant: Participant }) => { const { t } = useTranslation('rooms', { keyPrefix: 'participantTileFocus' }) @@ -15,31 +13,18 @@ export const MuteButton = ({ participant }: { participant: Participant }) => { source: Track.Source.Microphone, }) - const { muteParticipant } = useMuteParticipant() - const [isAlertOpen, setIsAlertOpen] = useState(false) - const name = participant.name || participant.identity return ( - <> - - - muteParticipant(participant).then(() => setIsAlertOpen(false)) - } - onClose={() => setIsAlertOpen(false)} - name={name} - /> - + ) } diff --git a/src/frontend/src/features/participants/components/ParticipantRow.tsx b/src/frontend/src/features/participants/components/ParticipantRow.tsx index 3e7efc11..93029c3f 100644 --- a/src/frontend/src/features/participants/components/ParticipantRow.tsx +++ b/src/frontend/src/features/participants/components/ParticipantRow.tsx @@ -19,13 +19,11 @@ import { import Source = Track.Source import { RiMicFill, RiMicOffFill } from '@remixicon/react' import { Button } from '@/primitives' -import { useState } from 'react' -import { useMuteParticipant } from '@/features/rooms/api/muteParticipant' import { useCanMute } from '@/features/rooms/livekit/hooks/useCanMute' import { ParticipantMenuButton } from './menu/ParticipantMenuButton' import { PinBadge } from './PinBadge' import { UnauthenticatedBadge } from './UnauthenticatedBadge' -import { MuteAlertDialog } from '@/features/rooms/livekit/components/MuteAlertDialog' +import { openMuteDialog } from '@/stores/muteDialog' import { ParticipantName } from './ParticipantName' type MicIndicatorProps = { @@ -34,7 +32,6 @@ type MicIndicatorProps = { const MicIndicator = ({ participant }: MicIndicatorProps) => { const { t } = useTranslation('rooms') - const { muteParticipant } = useMuteParticipant() const { isMuted } = useTrackMutedIndicator({ participant: participant, source: Source.Microphone, @@ -42,7 +39,6 @@ const MicIndicator = ({ participant }: MicIndicatorProps) => { const canMute = useCanMute(participant) const isSpeaking = useIsSpeaking(participant) - const [isAlertOpen, setIsAlertOpen] = useState(false) const name = participant.name || participant.identity const label = isLocal(participant) @@ -52,46 +48,34 @@ const MicIndicator = ({ participant }: MicIndicatorProps) => { }) return ( - <> - - - muteParticipant(participant).then(() => setIsAlertOpen(false)) - } - onClose={() => setIsAlertOpen(false)} - name={name} - /> - + ) } diff --git a/src/frontend/src/features/rooms/livekit/components/MuteAlertDialogProvider.tsx b/src/frontend/src/features/rooms/livekit/components/MuteAlertDialogProvider.tsx new file mode 100644 index 00000000..b03eb688 --- /dev/null +++ b/src/frontend/src/features/rooms/livekit/components/MuteAlertDialogProvider.tsx @@ -0,0 +1,28 @@ +import { useRef } from 'react' +import { useSnapshot } from 'valtio' +import { useMuteParticipant } from '@/features/rooms/api/muteParticipant' +import { closeMuteDialog, muteDialogStore } from '@/stores/muteDialog' +import { MuteAlertDialog } from './MuteAlertDialog' + +export const MuteAlertDialogProvider = () => { + const { participant } = useSnapshot(muteDialogStore) + const { muteParticipant } = useMuteParticipant() + + const lastNameRef = useRef('') + if (participant) { + lastNameRef.current = participant.name || participant.identity + } + + return ( + { + const target = muteDialogStore.participant + if (!target) return + muteParticipant(target).then(closeMuteDialog) + }} + /> + ) +} diff --git a/src/frontend/src/features/rooms/livekit/prefabs/VideoConference.tsx b/src/frontend/src/features/rooms/livekit/prefabs/VideoConference.tsx index f470aae7..16084e99 100644 --- a/src/frontend/src/features/rooms/livekit/prefabs/VideoConference.tsx +++ b/src/frontend/src/features/rooms/livekit/prefabs/VideoConference.tsx @@ -19,6 +19,7 @@ import { RoomMetadataSynchronizer } from '../components/RoomMetadataSynchronizer import { useNoiseReduction } from '../hooks/useNoiseReduction' import { VideoResolutionSubscription } from '../components/VideoResolutionSubscription' import { SettingsDialogProvider } from '@/features/settings/components/SettingsDialogProvider' +import { MuteAlertDialogProvider } from '@/features/rooms/livekit/components/MuteAlertDialogProvider' import { IsIdleDisconnectModal } from '../components/IsIdleDisconnectModal' import { ReactionPortals } from '@/features/reactions/components/ReactionPortals' import { RoomContentArea } from '@/features/layout/components/RoomContentArea' @@ -144,6 +145,7 @@ export function VideoConference({ ...props }: VideoConferenceProps) { + diff --git a/src/frontend/src/stores/muteDialog.ts b/src/frontend/src/stores/muteDialog.ts new file mode 100644 index 00000000..d4aa4c69 --- /dev/null +++ b/src/frontend/src/stores/muteDialog.ts @@ -0,0 +1,18 @@ +import { proxy, ref } from 'valtio' +import type { Participant } from 'livekit-client' + +type State = { + participant: Participant | null +} + +export const muteDialogStore = proxy({ + participant: null, +}) + +export const openMuteDialog = (participant: Participant) => { + muteDialogStore.participant = ref(participant) +} + +export const closeMuteDialog = () => { + muteDialogStore.participant = null +}