| null>(null)
+
+ const remoteParticipants = useRemoteParticipants({
+ updateOnlyOn: [
+ RoomEvent.ParticipantConnected,
+ RoomEvent.ParticipantDisconnected,
+ ],
+ })
+
+ useEffect(() => {
+ // Always clear existing timer on dependency change
+ if (idleDisconnectModalTimeoutRef.current) {
+ clearTimeout(idleDisconnectModalTimeoutRef.current)
+ idleDisconnectModalTimeoutRef.current = null
+ }
+
+ const delay = data?.idle_disconnect_warning_delay
+
+ // Disabled or invalid delay: ensure modal is closed
+ if (!delay) {
+ connectionObserverStore.isIdleDisconnectModalOpen = false
+ return
+ }
+
+ if (remoteParticipants.length === 0) {
+ idleDisconnectModalTimeoutRef.current = setTimeout(() => {
+ connectionObserverStore.isIdleDisconnectModalOpen = true
+ }, delay)
+ } else {
+ connectionObserverStore.isIdleDisconnectModalOpen = false
+ }
+
+ return () => {
+ if (idleDisconnectModalTimeoutRef.current) {
+ clearTimeout(idleDisconnectModalTimeoutRef.current)
+ idleDisconnectModalTimeoutRef.current = null
+ }
+ }
+ }, [remoteParticipants.length, data?.idle_disconnect_warning_delay])
+
useEffect(() => {
if (!isAnalyticsEnabled) return
diff --git a/src/frontend/src/features/rooms/livekit/prefabs/VideoConference.tsx b/src/frontend/src/features/rooms/livekit/prefabs/VideoConference.tsx
index 0c3efb54..edffa7e3 100644
--- a/src/frontend/src/features/rooms/livekit/prefabs/VideoConference.tsx
+++ b/src/frontend/src/features/rooms/livekit/prefabs/VideoConference.tsx
@@ -36,6 +36,7 @@ import { useSubtitles } from '@/features/subtitle/hooks/useSubtitles'
import { Subtitles } from '@/features/subtitle/component/Subtitles'
import { CarouselLayout } from '../components/layout/CarouselLayout'
import { GridLayout } from '../components/layout/GridLayout'
+import { IsIdleDisconnectModal } from '../components/IsIdleDisconnectModal'
const LayoutWrapper = styled(
'div',
@@ -191,6 +192,7 @@ export function VideoConference({ ...props }: VideoConferenceProps) {
isOpen={isShareErrorVisible}
onClose={() => setIsShareErrorVisible(false)}
/>
+
Allgemein.",
+ "stayButton": "Gespräch fortsetzen",
+ "leaveButton": "Jetzt verlassen"
+ },
"controls": {
"microphone": "Mikrofon",
"camera": "Kamera",
diff --git a/src/frontend/src/locales/en/rooms.json b/src/frontend/src/locales/en/rooms.json
index 6bce3a61..694ea115 100644
--- a/src/frontend/src/locales/en/rooms.json
+++ b/src/frontend/src/locales/en/rooms.json
@@ -149,6 +149,13 @@
"newTab": "New window"
}
},
+ "isIdleDisconnectModal": {
+ "title": "Are you still there?",
+ "body": "You are the only participant. This call will end in {{duration}}. Would you like to continue the call?",
+ "settings": "To stop seeing this message, go to Settings > General.",
+ "stayButton": "Continue the call",
+ "leaveButton": "Leave now"
+ },
"controls": {
"microphone": "Microphone",
"camera": "Camera",
diff --git a/src/frontend/src/locales/fr/rooms.json b/src/frontend/src/locales/fr/rooms.json
index ba3b9d22..d2469a50 100644
--- a/src/frontend/src/locales/fr/rooms.json
+++ b/src/frontend/src/locales/fr/rooms.json
@@ -149,6 +149,13 @@
"newTab": "Nouvelle fenêtre"
}
},
+ "isIdleDisconnectModal": {
+ "title": "Êtes-vous toujours là ?",
+ "body": "Vous êtes le seul participant. Cet appel va donc se terminer dans {{duration}}. Voulez-vous poursuivre l'appel ?",
+ "settings": "Pour ne plus voir ce message, accèder à Paramètres > Général.",
+ "stayButton": "Poursuivre l'appel",
+ "leaveButton": "Quitter maintenant"
+ },
"controls": {
"microphone": "Microphone",
"camera": "Camera",
diff --git a/src/frontend/src/locales/nl/rooms.json b/src/frontend/src/locales/nl/rooms.json
index 7821fd55..47f04777 100644
--- a/src/frontend/src/locales/nl/rooms.json
+++ b/src/frontend/src/locales/nl/rooms.json
@@ -149,6 +149,13 @@
"newTab": "Nieuw venster"
}
},
+ "isIdleDisconnectModal": {
+ "title": "Ben je er nog?",
+ "body": "Je bent de enige deelnemer. Dit gesprek wordt over {{duration}} beëindigd. Wil je het gesprek voortzetten?",
+ "settings": "Om dit bericht niet meer te zien, ga naar Instellingen > Algemeen.",
+ "stayButton": "Gesprek voortzetten",
+ "leaveButton": "Nu verlaten"
+ },
"controls": {
"microphone": "Microfoon",
"camera": "Camera",
diff --git a/src/frontend/src/stores/connectionObserver.ts b/src/frontend/src/stores/connectionObserver.ts
new file mode 100644
index 00000000..5149cd59
--- /dev/null
+++ b/src/frontend/src/stores/connectionObserver.ts
@@ -0,0 +1,9 @@
+import { proxy } from 'valtio'
+
+type State = {
+ isIdleDisconnectModalOpen: boolean
+}
+
+export const connectionObserverStore = proxy({
+ isIdleDisconnectModalOpen: false,
+})
diff --git a/src/helm/env.d/dev-keycloak/values.meet.yaml.gotmpl b/src/helm/env.d/dev-keycloak/values.meet.yaml.gotmpl
index 44f9d245..cb1bbce2 100644
--- a/src/helm/env.d/dev-keycloak/values.meet.yaml.gotmpl
+++ b/src/helm/env.d/dev-keycloak/values.meet.yaml.gotmpl
@@ -57,6 +57,7 @@ backend:
FRONTEND_TRANSCRIPT: "{'form_beta_users': 'https://grist.numerique.gouv.fr/o/docs/forms/3fFfvJoTBEQ6ZiMi8zsQwX/17'}"
FRONTEND_FEEDBACK: "{'url': 'https://grist.numerique.gouv.fr/o/docs/cbMv4G7pLY3Z/USER-RESEARCH-or-LA-SUITE/f/26'}"
FRONTEND_MANIFEST_LINK: "https://docs.numerique.gouv.fr/docs/1ef86abf-f7e0-46ce-b6c7-8be8b8af4c3d/"
+ FRONTEND_IDLE_DISCONNECT_WARNING_DELAY: 9000
AWS_S3_ENDPOINT_URL: http://minio.meet.svc.cluster.local:9000
AWS_S3_ACCESS_KEY_ID: meet
AWS_S3_SECRET_ACCESS_KEY: password