From 0e72f616503e5ac3ea9a65f23d9d627af1f0c3f6 Mon Sep 17 00:00:00 2001 From: lebaudantoine Date: Sat, 9 Aug 2025 23:53:00 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=B8(frontend)=20add=20permission=20hin?= =?UTF-8?q?ts=20and=20modal=20button=20to=20join=20screen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add explicit messaging on join screen explaining why users should allow camera/microphone access, with quick button to open permission modal dialog. Targets first-time users who need guidance on permission requirements. Message persists until permissions are granted to ensure proper user onboarding and reduce support issues. --- .../src/features/rooms/components/Join.tsx | 52 +++++++++++++++++-- src/frontend/src/locales/de/rooms.json | 8 ++- src/frontend/src/locales/en/rooms.json | 8 ++- src/frontend/src/locales/fr/rooms.json | 8 ++- src/frontend/src/locales/nl/rooms.json | 8 ++- 5 files changed, 76 insertions(+), 8 deletions(-) diff --git a/src/frontend/src/features/rooms/components/Join.tsx b/src/frontend/src/features/rooms/components/Join.tsx index 782fe106..e630bb09 100644 --- a/src/frontend/src/features/rooms/components/Join.tsx +++ b/src/frontend/src/features/rooms/components/Join.tsx @@ -27,6 +27,8 @@ import { ApiLobbyStatus, ApiRequestEntry } from '../api/requestEntry' import { Spinner } from '@/primitives/Spinner' import { ApiAccessLevel } from '../api/ApiRoom' import { useLoginHint } from '@/hooks/useLoginHint' +import { useSnapshot } from 'valtio' +import { openPermissionsDialog, permissionsStore } from '@/stores/permissions' const onError = (e: Error) => console.error('ERROR', e) @@ -220,19 +222,48 @@ export const Join = ({ enterRoom() } + const permissions = useSnapshot(permissionsStore) + + const isCameraDeniedOrPrompted = + permissions.isCameraDenied || permissions.isCameraPrompted + + const isMicrophoneDeniedOrPrompted = + permissions.isMicrophoneDenied || permissions.isMicrophonePrompted + const hintMessage = useMemo(() => { + if (isCameraDeniedOrPrompted) { + return isMicrophoneDeniedOrPrompted + ? 'cameraAndMicNotGranted' + : 'cameraNotGranted' + } if (!videoEnabled) { return 'cameraDisabled' } - if (!isVideoInitiated.current) { return 'cameraStarting' } - if (videoTrack && videoEnabled) { return '' } - }, [videoTrack, videoEnabled]) + }, [ + videoTrack, + videoEnabled, + isCameraDeniedOrPrompted, + isMicrophoneDeniedOrPrompted, + ]) + + const permissionsButtonLabel = useMemo(() => { + if (!isMicrophoneDeniedOrPrompted && !isCameraDeniedOrPrompted) { + return null + } + if (isCameraDeniedOrPrompted && isMicrophoneDeniedOrPrompted) { + return 'cameraAndMicNotGranted' + } + if (isCameraDeniedOrPrompted && !isMicrophoneDeniedOrPrompted) { + return 'cameraNotGranted' + } + return null + }, [isMicrophoneDeniedOrPrompted, isCameraDeniedOrPrompted]) const renderWaitingState = () => { switch (status) { @@ -416,7 +447,10 @@ export const Join = ({ width="1280" height="720" style={{ - display: !videoEnabled ? 'none' : undefined, + display: + !videoEnabled || isCameraDeniedOrPrompted + ? 'none' + : undefined, }} className={css({ position: 'absolute', @@ -443,6 +477,7 @@ export const Join = ({ alignItems: 'center', padding: '0.24rem', boxSizing: 'border-box', + gap: '1rem', })} >

{hintMessage && t(hintMessage)}

+ {isCameraDeniedOrPrompted && ( + + )}