"use client"; import { useCallback, useEffect, useRef, useState, useContext, RefObject } from "react"; import { Box, Button, Text, VStack, HStack, Spinner, useToast } from "@chakra-ui/react"; import useRoomMeeting from "./useRoomMeeting"; import { useRouter } from "next/navigation"; import { notFound } from "next/navigation"; import useSessionStatus from "../lib/useSessionStatus"; import { useRecordingConsent } from "../recordingConsentContext"; import useApi from "../lib/useApi"; import { Meeting } from '../api'; export type RoomDetails = { params: { roomName: string; }; }; // stages: we focus on the consent, then whereby steals focus, then we focus on the consent again, then return focus to whoever stole it initially const useConsentWherebyFocusManagement = (acceptButtonRef: RefObject, wherebyRef: RefObject) => { const currentFocusRef = useRef(null); useEffect(() => { if (acceptButtonRef.current) { acceptButtonRef.current.focus(); } else { console.error("accept button ref not available yet for focus management - seems to be illegal state"); } const handleWherebyReady = () => { console.log("whereby ready - refocusing consent button"); currentFocusRef.current = document.activeElement as HTMLElement; if (acceptButtonRef.current) { acceptButtonRef.current.focus(); } }; if (wherebyRef.current) { wherebyRef.current.addEventListener("ready", handleWherebyReady); } else { console.warn("whereby ref not available yet for focus management - seems to be illegal state. not waiting, focus management off."); } return () => { wherebyRef.current?.removeEventListener("ready", handleWherebyReady); currentFocusRef.current?.focus(); }; }, []); } const useConsentDialog = (meetingId: string, wherebyRef: RefObject/*accessibility*/) => { const { state: consentState, touch, hasConsent } = useRecordingConsent(); const [consentLoading, setConsentLoading] = useState(false); const api = useApi(); const toast = useToast(); const handleConsent = useCallback(async (meetingId: string, given: boolean) => { if (!api) return; setConsentLoading(true); try { await api.v1MeetingAudioConsent({ meetingId, requestBody: { consent_given: given } }); touch(meetingId); } catch (error) { console.error('Error submitting consent:', error); } finally { setConsentLoading(false); } }, [api, touch]); useEffect(() => { if ( consentState.ready && meetingId && !hasConsent(meetingId) && !consentLoading ) { const toastId = toast({ position: "top", duration: null, render: ({ onClose }) => { const AcceptButton = () => { const buttonRef = useRef(null); useConsentWherebyFocusManagement(buttonRef, wherebyRef); return ( ); }; return ( Can we have your permission to store this meeting's audio recording on our servers? ); }, }); // Handle escape key to close the toast const handleKeyDown = (event: KeyboardEvent) => { if (event.key === 'Escape') { toast.close(toastId); } }; document.addEventListener('keydown', handleKeyDown); return () => { toast.close(toastId); document.removeEventListener('keydown', handleKeyDown); }; } }, [consentState.ready, meetingId, hasConsent, consentLoading, toast, handleConsent]); } function ConsentDialog({ meetingId, wherebyRef }: { meetingId: string; wherebyRef: React.RefObject }) { useConsentDialog(meetingId, wherebyRef); return <> } const recordingTypeRequiresConsent = (recordingType: NonNullable) => { return recordingType === 'cloud'; } // next throws even with "use client" const useWhereby = () => { const [wherebyLoaded, setWherebyLoaded] = useState(false); useEffect(() => { if (typeof window !== 'undefined') { import("@whereby.com/browser-sdk/embed").then(() => { setWherebyLoaded(true); }).catch(console.error.bind(console)); } }, []); return wherebyLoaded; } export default function Room(details: RoomDetails) { const wherebyLoaded = useWhereby(); const wherebyRef = useRef(null); const roomName = details.params.roomName; const meeting = useRoomMeeting(roomName); const router = useRouter(); const { isLoading, isAuthenticated } = useSessionStatus(); const roomUrl = meeting?.response?.host_room_url ? meeting?.response?.host_room_url : meeting?.response?.room_url; const meetingId = meeting?.response?.id; const recordingType = meeting?.response?.recording_type; const handleLeave = useCallback(() => { router.push("/browse"); }, [router]); useEffect(() => { if ( !isLoading && meeting?.error && "status" in meeting.error && meeting.error.status === 404 ) { notFound(); } }, [isLoading, meeting?.error]); useEffect(() => { if (isLoading || !isAuthenticated || !roomUrl || !wherebyLoaded) return; wherebyRef.current?.addEventListener("leave", handleLeave); return () => { wherebyRef.current?.removeEventListener("leave", handleLeave); }; }, [handleLeave, roomUrl, isLoading, isAuthenticated, wherebyLoaded]); if (isLoading) { return ( ); } return ( <> {roomUrl && meetingId && wherebyLoaded && ( <> {recordingType && recordingTypeRequiresConsent(recordingType) && } )} ); }