🚸(frontend) disable device controls when user lacks room permissions

Update user experience by clearly marking device toggle and control
components as disabled when users have insufficient room permissions.

Prevents confusion by providing visual feedback that device controls are
unavailable, improving clarity about what  actions users can perform
in their current role.
This commit is contained in:
lebaudantoine
2025-08-28 15:47:06 +02:00
committed by aleb_the_flash
parent 7d1f15ef91
commit 3d3242e148
7 changed files with 54 additions and 7 deletions

View File

@@ -124,6 +124,7 @@ const config: Config = {
...pandaPreset.theme.tokens.colors,
primaryDark: {
50: { value: '#161622' },
75: { value: '#222234' },
100: { value: '#2D2D46' },
200: { value: '#43436A' },
300: { value: '#5A5A8F' },

View File

@@ -7,12 +7,14 @@ import { Track } from 'livekit-client'
import { ToggleDevice } from './ToggleDevice'
import { css } from '@/styled-system/css'
import { usePersistentUserChoices } from '../../../hooks/usePersistentUserChoices'
import { useCanPublishTrack } from '../../../hooks/useCanPublishTrack'
import { useCannotUseDevice } from '../../../hooks/useCannotUseDevice'
import Source = Track.Source
import * as React from 'react'
import { SelectDevice } from './SelectDevice'
import { SettingsButton } from './SettingsButton'
import { SettingsDialogExtendedKey } from '@/features/settings/type'
import { TrackSource } from '@livekit/protocol'
import Source = Track.Source
type AudioDevicesControlProps = Omit<
UseTrackToggleProps<Source.Microphone>,
@@ -50,6 +52,8 @@ export const AudioDevicesControl = ({
const cannotUseDevice = useCannotUseDevice(kind)
const selectLabel = t(`settings.${SettingsDialogExtendedKey.AUDIO}`)
const canPublishTrack = useCanPublishTrack(TrackSource.MICROPHONE)
return (
<div
className={css({
@@ -59,6 +63,7 @@ export const AudioDevicesControl = ({
>
<ToggleDevice
{...trackProps}
isDisabled={!canPublishTrack}
kind={kind}
toggle={trackProps.toggle as () => Promise<void>}
overrideToggleButtonProps={{
@@ -77,7 +82,9 @@ export const AudioDevicesControl = ({
groupPosition="right"
square
variant={
trackProps.enabled && !cannotUseDevice ? 'primaryDark' : 'error2'
!canPublishTrack || !trackProps.enabled || cannotUseDevice
? 'error2'
: 'primaryDark'
}
>
<RiArrowUpSLine />

View File

@@ -27,6 +27,7 @@ type ToggleDeviceStyleProps = {
export type ToggleDeviceProps<T extends ToggleSource> = {
enabled: boolean
isDisabled?: boolean
toggle: (
forceState?: boolean,
captureOptions?: CaptureOptionsBySource<T>
@@ -39,6 +40,7 @@ export type ToggleDeviceProps<T extends ToggleSource> = {
export const ToggleDevice = <T extends ToggleSource>({
kind,
enabled,
isDisabled,
toggle,
context = 'room',
overrideToggleButtonProps,
@@ -105,7 +107,9 @@ export const ToggleDevice = <T extends ToggleSource>({
}, [enabled, kind, deviceShortcut, t])
const Icon =
enabled && !cannotUseDevice ? deviceIcons.toggleOn : deviceIcons.toggleOff
isDisabled || cannotUseDevice || !enabled
? deviceIcons.toggleOff
: deviceIcons.toggleOn
const roomContext = useMaybeRoomContext()
if (kind === 'audioinput' && pushToTalk && roomContext) {
@@ -117,7 +121,10 @@ export const ToggleDevice = <T extends ToggleSource>({
{cannotUseDevice && <PermissionNeededButton />}
<ToggleButton
isSelected={!enabled}
variant={enabled && !cannotUseDevice ? variant : errorVariant}
isDisabled={isDisabled}
variant={
isDisabled || cannotUseDevice || !enabled ? errorVariant : variant
}
shySelected
onPress={() => (cannotUseDevice ? openPermissionsDialog() : toggle())}
aria-label={toggleLabel}

View File

@@ -7,6 +7,7 @@ import { Track, VideoCaptureOptions } from 'livekit-client'
import { ToggleDevice } from './ToggleDevice'
import { css } from '@/styled-system/css'
import { usePersistentUserChoices } from '../../../hooks/usePersistentUserChoices'
import { useCanPublishTrack } from '../../../hooks/useCanPublishTrack'
import { useCannotUseDevice } from '../../../hooks/useCannotUseDevice'
import { useSidePanel } from '../../../hooks/useSidePanel'
import { BackgroundProcessorFactory } from '../../blur'
@@ -15,6 +16,7 @@ import * as React from 'react'
import { SelectDevice } from './SelectDevice'
import { SettingsButton } from './SettingsButton'
import { SettingsDialogExtendedKey } from '@/features/settings/type'
import { TrackSource } from '@livekit/protocol'
const EffectsButton = ({ onPress }: { onPress: () => void }) => {
const { t } = useTranslation('rooms', { keyPrefix: 'selectDevice' })
@@ -95,6 +97,7 @@ export const VideoDeviceControl = ({
}
const selectLabel = t(`settings.${SettingsDialogExtendedKey.VIDEO}`)
const canPublishTrack = useCanPublishTrack(TrackSource.CAMERA)
return (
<div
@@ -105,6 +108,7 @@ export const VideoDeviceControl = ({
>
<ToggleDevice
{...trackProps}
isDisabled={!canPublishTrack}
kind={kind}
toggle={toggleWithProcessor}
overrideToggleButtonProps={{
@@ -123,7 +127,9 @@ export const VideoDeviceControl = ({
groupPosition="right"
square
variant={
trackProps.enabled && !cannotUseDevice ? 'primaryDark' : 'error2'
!canPublishTrack || !trackProps.enabled || cannotUseDevice
? 'error2'
: 'primaryDark'
}
>
<RiArrowUpSLine />

View File

@@ -6,6 +6,8 @@ import { Track } from 'livekit-client'
import React from 'react'
import { type ButtonRecipeProps } from '@/primitives/buttonRecipe'
import { ToggleButtonProps } from '@/primitives/ToggleButton'
import { TrackSource } from '@livekit/protocol'
import { useCanPublishTrack } from '@/features/rooms/livekit/hooks/useCanPublishTrack'
type Props = Omit<
UseTrackToggleProps<Track.Source.ScreenShare>,
@@ -29,10 +31,13 @@ export const ScreenShareToggle = ({
const tooltipLabel = enabled ? 'stop' : 'start'
const Icon = enabled ? RiCloseFill : RiArrowUpLine
const canShareScreen = useCanPublishTrack(TrackSource.SCREEN_SHARE)
// fixme - remove ToggleButton custom styles when we design a proper icon
return (
<ToggleButton
isSelected={enabled}
isDisabled={!canShareScreen}
square
variant={variant}
tooltip={t(tooltipLabel)}

View File

@@ -0,0 +1,17 @@
import { TrackSource } from '@livekit/protocol'
import {
useLocalParticipant,
useParticipantPermissions,
} from '@livekit/components-react'
export function useCanPublishTrack(trackSource: TrackSource): boolean {
const { localParticipant } = useLocalParticipant()
const permissions = useParticipantPermissions({
participant: localParticipant,
})
return Boolean(
permissions?.canPublish &&
permissions?.canPublishSources?.includes(trackSource)
)
}

View File

@@ -148,6 +148,10 @@ export const buttonRecipe = cva({
backgroundColor: 'primaryDark.900',
color: 'primaryDark.100',
},
'&[data-disabled]': {
backgroundColor: 'primaryDark.75',
color: 'primaryDark.300',
},
'&[data-hovered]': {
backgroundColor: 'primaryDark.300',
color: 'white',
@@ -247,8 +251,8 @@ export const buttonRecipe = cva({
color: 'error.100 !important',
},
'&[data-disabled]': {
backgroundColor: 'error.200',
color: 'error.300',
backgroundColor: 'error.200 !important',
color: 'error.300 !important',
},
},
errorCircle: {