🔒️(frontend) hide admin actions from unprivileged users in UI

Update interface to hide admin-only actions like participant muting from
users without room admin privileges, reflecting backend permission
restrictions implemented in previous commits.
This commit is contained in:
lebaudantoine
2025-08-27 15:45:02 +02:00
committed by aleb_the_flash
parent 4793f2fa8f
commit a85a62eb1a
5 changed files with 35 additions and 13 deletions

View File

@@ -21,6 +21,7 @@ import { useFullScreen } from '../hooks/useFullScreen'
import { Participant, Track } from 'livekit-client'
import { MuteAlertDialog } from './MuteAlertDialog'
import { useMuteParticipant } from '@/features/rooms/api/muteParticipant'
import { useCanMute } from '@/features/rooms/livekit/hooks/useCanMute'
const ZoomButton = ({
trackRef,
@@ -165,6 +166,8 @@ export const ParticipantTileFocus = ({
const isScreenShare = trackRef.source == Track.Source.ScreenShare
const isLocal = trackRef.participant.isLocal
const canMute = useCanMute(participant)
return (
<div
className={css({
@@ -210,7 +213,7 @@ export const ParticipantTileFocus = ({
{participant.isLocal ? (
<EffectsButton />
) : (
<MuteButton participant={participant} />
canMute && <MuteButton participant={participant} />
)}
</>
) : (

View File

@@ -6,6 +6,7 @@ import { useTranslation } from 'react-i18next'
import { Avatar } from '@/components/Avatar'
import { useLowerHandParticipant } from '@/features/rooms/api/lowerHandParticipant'
import { getParticipantColor } from '@/features/rooms/utils/getParticipantColor'
import { useIsAdminOrOwner } from '@/features/rooms/livekit/hooks/useIsAdminOrOwner'
import { Participant } from 'livekit-client'
import { isLocal } from '@/utils/livekit'
import { RiHand } from '@remixicon/react'
@@ -22,6 +23,7 @@ export const HandRaisedListItem = ({
const name = participant.name || participant.identity
const { lowerHandParticipant } = useLowerHandParticipant()
const isAdminOrOwner = useIsAdminOrOwner()
return (
<HStack
@@ -67,16 +69,18 @@ export const HandRaisedListItem = ({
)}
</Text>
</HStack>
<Button
square
variant="greyscale"
size="sm"
onPress={() => lowerHandParticipant(participant)}
tooltip={t('participants.lowerParticipantHand', { name })}
data-attr="participants-lower-hand"
>
<RiHand />
</Button>
{isAdminOrOwner && (
<Button
square
variant="greyscale"
size="sm"
onPress={() => lowerHandParticipant(participant)}
tooltip={t('participants.lowerParticipantHand', { name })}
data-attr="participants-lower-hand"
>
<RiHand />
</Button>
)}
</HStack>
)
}

View File

@@ -2,6 +2,7 @@ import { Button } from '@/primitives'
import { useTranslation } from 'react-i18next'
import { Participant } from 'livekit-client'
import { useLowerHandParticipants } from '@/features/rooms/api/lowerHandParticipants'
import { useIsAdminOrOwner } from '@/features/rooms/livekit/hooks/useIsAdminOrOwner'
type LowerAllHandsButtonProps = {
participants: Array<Participant>
@@ -12,6 +13,10 @@ export const LowerAllHandsButton = ({
}: LowerAllHandsButtonProps) => {
const { lowerHandParticipants } = useLowerHandParticipants()
const { t } = useTranslation('rooms')
const isAdminOrOwner = useIsAdminOrOwner()
if (!isAdminOrOwner) return null
return (
<Button
aria-label={t('participants.lowerParticipantsHand')}

View File

@@ -18,6 +18,7 @@ import { Button } from '@/primitives'
import { useState } from 'react'
import { MuteAlertDialog } from '../../MuteAlertDialog'
import { useMuteParticipant } from '@/features/rooms/api/muteParticipant'
import { useCanMute } from '@/features/rooms/livekit/hooks/useCanMute'
type MicIndicatorProps = {
participant: Participant
@@ -30,6 +31,8 @@ const MicIndicator = ({ participant }: MicIndicatorProps) => {
participant: participant,
source: Source.Microphone,
})
const canMute = useCanMute(participant)
const isSpeaking = useIsSpeaking(participant)
const [isAlertOpen, setIsAlertOpen] = useState(false)
const name = participant.name || participant.identity
@@ -48,7 +51,7 @@ const MicIndicator = ({ participant }: MicIndicatorProps) => {
size="sm"
tooltip={label}
aria-label={label}
isDisabled={isMuted}
isDisabled={isMuted || !canMute}
onPress={() =>
!isMuted && isLocal(participant)
? muteParticipant(participant)
@@ -105,7 +108,7 @@ export const ParticipantListItem = ({
<Avatar name={name} bgColor={getParticipantColor(participant)} />
<VStack gap={0} alignItems="start">
<Text
sm
variant="sm"
className={css({
userSelect: 'none',
cursor: 'default',

View File

@@ -0,0 +1,7 @@
import { useIsAdminOrOwner } from './useIsAdminOrOwner'
import { Participant } from 'livekit-client'
export const useCanMute = (participant: Participant) => {
const isAdminOrOwner = useIsAdminOrOwner()
return participant.isLocal || isAdminOrOwner
}