From dbc66c2f074f86902125c6ed6a419ad2d7f2647f Mon Sep 17 00:00:00 2001 From: lebaudantoine Date: Thu, 16 Oct 2025 19:35:21 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8(frontend)=20add=20user=20setting=20to?= =?UTF-8?q?=20disable=20idle=20disconnect=20feature?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Allow users to opt-out of idle participant disconnection despite default enforcement, trusting power users who modify this setting won't forget to disconnect, though accepting risk they may block maintenance configuration updates. --- .../livekit/hooks/useConnectionObserver.ts | 13 +++++++++++-- .../settings/components/tabs/GeneralTab.tsx | 18 ++++++++++++++++++ src/frontend/src/locales/de/settings.json | 7 +++++++ src/frontend/src/locales/en/settings.json | 7 +++++++ src/frontend/src/locales/fr/settings.json | 7 +++++++ src/frontend/src/locales/nl/settings.json | 7 +++++++ src/frontend/src/stores/userPreferences.ts | 9 +++++++++ 7 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 src/frontend/src/stores/userPreferences.ts diff --git a/src/frontend/src/features/rooms/livekit/hooks/useConnectionObserver.ts b/src/frontend/src/features/rooms/livekit/hooks/useConnectionObserver.ts index 74d7f7cf..e354e68b 100644 --- a/src/frontend/src/features/rooms/livekit/hooks/useConnectionObserver.ts +++ b/src/frontend/src/features/rooms/livekit/hooks/useConnectionObserver.ts @@ -8,6 +8,8 @@ import { useIsAnalyticsEnabled } from '@/features/analytics/hooks/useIsAnalytics import posthog from 'posthog-js' import { connectionObserverStore } from '@/stores/connectionObserver' import { useConfig } from '@/api/useConfig' +import { userPreferencesStore } from '@/stores/userPreferences' +import { useSnapshot } from 'valtio' export const useConnectionObserver = () => { const room = useRoomContext() @@ -16,6 +18,8 @@ export const useConnectionObserver = () => { const { data } = useConfig() const isAnalyticsEnabled = useIsAnalyticsEnabled() + const userPreferencesSnap = useSnapshot(userPreferencesStore) + const idleDisconnectModalTimeoutRef = useRef | null>(null) @@ -34,10 +38,11 @@ export const useConnectionObserver = () => { idleDisconnectModalTimeoutRef.current = null } + const isEnabled = userPreferencesSnap.is_idle_disconnect_modal_enabled const delay = data?.idle_disconnect_warning_delay // Disabled or invalid delay: ensure modal is closed - if (!delay) { + if (!isEnabled || !delay) { connectionObserverStore.isIdleDisconnectModalOpen = false return } @@ -56,7 +61,11 @@ export const useConnectionObserver = () => { idleDisconnectModalTimeoutRef.current = null } } - }, [remoteParticipants.length, data?.idle_disconnect_warning_delay]) + }, [ + remoteParticipants.length, + data?.idle_disconnect_warning_delay, + userPreferencesSnap.is_idle_disconnect_modal_enabled, + ]) useEffect(() => { if (!isAnalyticsEnabled) return diff --git a/src/frontend/src/features/settings/components/tabs/GeneralTab.tsx b/src/frontend/src/features/settings/components/tabs/GeneralTab.tsx index 170fc02c..3b30c631 100644 --- a/src/frontend/src/features/settings/components/tabs/GeneralTab.tsx +++ b/src/frontend/src/features/settings/components/tabs/GeneralTab.tsx @@ -2,6 +2,8 @@ import { Field, H } from '@/primitives' import { useTranslation } from 'react-i18next' import { useLanguageLabels } from '@/i18n/useLanguageLabels' import { TabPanel, TabPanelProps } from '@/primitives/Tabs' +import { userPreferencesStore } from '@/stores/userPreferences' +import { useSnapshot } from 'valtio' export type GeneralTabProps = Pick @@ -9,6 +11,8 @@ export const GeneralTab = ({ id }: GeneralTabProps) => { const { t, i18n } = useTranslation('settings') const { languagesList, currentLanguage } = useLanguageLabels() + const userPreferencesSnap = useSnapshot(userPreferencesStore) + return ( {t('language.heading')} @@ -21,6 +25,20 @@ export const GeneralTab = ({ id }: GeneralTabProps) => { i18n.changeLanguage(lang as string) }} /> + {t('preferences.title')} + + (userPreferencesStore.is_idle_disconnect_modal_enabled = value) + } + wrapperProps={{ + noMargin: true, + fullWidth: true, + }} + /> ) } diff --git a/src/frontend/src/locales/de/settings.json b/src/frontend/src/locales/de/settings.json index 35c7e0cc..38370295 100644 --- a/src/frontend/src/locales/de/settings.json +++ b/src/frontend/src/locales/de/settings.json @@ -7,6 +7,13 @@ "authentication": "Authentifizierung", "nameError": "Ihr Name darf nicht leer sein" }, + "preferences": { + "title": "Einstellungen", + "idleDisconnectModal": { + "label": "Anrufe ohne Teilnehmer verlassen", + "description": "Verlässt automatisch einen Anruf nach einigen Minuten, wenn kein anderer Teilnehmer beitritt" + } + }, "audio": { "microphone": { "heading": "Mikrofon", diff --git a/src/frontend/src/locales/en/settings.json b/src/frontend/src/locales/en/settings.json index d86cd31c..e9cde714 100644 --- a/src/frontend/src/locales/en/settings.json +++ b/src/frontend/src/locales/en/settings.json @@ -7,6 +7,13 @@ "authentication": "Authentication", "nameError": "Your name cannot be empty" }, + "preferences": { + "title": "Preferences", + "idleDisconnectModal": { + "label": "Leave calls with no participants", + "description": "Automatically leaves a call after a few minutes if no other participant joins" + } + }, "audio": { "microphone": { "heading": "Microphone", diff --git a/src/frontend/src/locales/fr/settings.json b/src/frontend/src/locales/fr/settings.json index 41a7968e..5465b2e6 100644 --- a/src/frontend/src/locales/fr/settings.json +++ b/src/frontend/src/locales/fr/settings.json @@ -7,6 +7,13 @@ "authentication": "Authentification", "nameError": "Votre Nom ne peut pas être vide" }, + "preferences": { + "title": "Préférences", + "idleDisconnectModal": { + "label": "Quitter les appels sans autre participant", + "description": "Vous fait quitter un appel au bout de quelques minutes si aucun autre participant ne vous rejoint" + } + }, "audio": { "microphone": { "heading": "Micro", diff --git a/src/frontend/src/locales/nl/settings.json b/src/frontend/src/locales/nl/settings.json index 235382db..430e7b34 100644 --- a/src/frontend/src/locales/nl/settings.json +++ b/src/frontend/src/locales/nl/settings.json @@ -7,6 +7,13 @@ "authentication": "Authenticatie", "nameError": "Uw naam mag niet leeg zijn" }, + "preferences": { + "title": "Voorkeuren", + "idleDisconnectModal": { + "label": "Verlaat oproepen zonder deelnemers", + "description": "Verlaat automatisch een oproep na een paar minuten als geen andere deelnemer meedoet" + } + }, "audio": { "microphone": { "heading": "Microfoon", diff --git a/src/frontend/src/stores/userPreferences.ts b/src/frontend/src/stores/userPreferences.ts new file mode 100644 index 00000000..73fec897 --- /dev/null +++ b/src/frontend/src/stores/userPreferences.ts @@ -0,0 +1,9 @@ +import { proxy } from 'valtio' + +type State = { + is_idle_disconnect_modal_enabled: boolean +} + +export const userPreferencesStore = proxy({ + is_idle_disconnect_modal_enabled: true, +})