From 4e7e8a168f9c67b68f999141b3b91d04ffcda2a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Westman?= Date: Thu, 19 Mar 2026 15:30:52 +0100 Subject: [PATCH 1/5] feat: add auto-join link sharing and companion auto-connect - Add /auto-join route that joins calls from URL params - Add share icon next to "Calls" title opening an auto-join link modal - Auto-join link modal optionally appends a companion WebSocket URL - Add useAutoJoinRestore hook to restore calls from localStorage on reload - Add companionAutoConnectUrl to ConnectToWSButton for auto-connecting WS - Add titleAction prop to PageHeader for injecting content next to title Co-Authored-By: Claude Sonnet 4.6 --- src/App.tsx | 6 ++ .../auto-join/auto-join-link-modal.tsx | 80 +++++++++++++++++++ src/components/auto-join/auto-join-page.tsx | 68 ++++++++++++++++ src/components/calls-page/calls-page.tsx | 46 +++++++++++ .../calls-page/connect-to-ws-button.tsx | 19 +++++ src/components/calls-page/header-actions.tsx | 3 + src/components/page-layout/page-header.tsx | 5 +- src/hooks/use-auto-join-restore.ts | 63 +++++++++++++++ src/utils/auto-join.ts | 6 ++ 9 files changed, 295 insertions(+), 1 deletion(-) create mode 100644 src/components/auto-join/auto-join-link-modal.tsx create mode 100644 src/components/auto-join/auto-join-page.tsx create mode 100644 src/hooks/use-auto-join-restore.ts create mode 100644 src/utils/auto-join.ts diff --git a/src/App.tsx b/src/App.tsx index bd79e026..97b6d446 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -23,6 +23,7 @@ import { ManageProductionsPage } from "./components/manage-productions-page/mana import { CreateProductionPage } from "./components/create-production/create-production-page.tsx"; import { useSetupTokenRefresh } from "./hooks/use-reauth.tsx"; import { TUserSettings } from "./components/user-settings/types"; +import { AutoJoinPage } from "./components/auto-join/auto-join-page.tsx"; const DisplayBoxPositioningContainer = styled(FlexContainer)` justify-content: center; @@ -158,6 +159,11 @@ const AppContent = ({ } errorElement={} /> + } + errorElement={} + /> } diff --git a/src/components/auto-join/auto-join-link-modal.tsx b/src/components/auto-join/auto-join-link-modal.tsx new file mode 100644 index 00000000..ccae7059 --- /dev/null +++ b/src/components/auto-join/auto-join-link-modal.tsx @@ -0,0 +1,80 @@ +import { useEffect, useRef, useState } from "react"; +import styled from "@emotion/styled"; +import { CopyButton } from "../copy-button/copy-button"; +import { FormInput } from "../form-elements/form-elements"; +import { Modal } from "../modal/modal"; +import { + InputWrapper, + LinkLabel, + ModalHeader, + ModalText, + Wrapper, +} from "../generate-urls/generate-urls-components"; + +const CheckboxRow = styled.label` + display: flex; + align-items: center; + gap: 1rem; + font-size: 1.4rem; + color: rgba(255, 255, 255, 0.8); + cursor: pointer; + margin-top: 1.5rem; + + input[type="checkbox"] { + width: 1.6rem; + height: 1.6rem; + cursor: pointer; + accent-color: #59cbe8; + } +`; + +const COMPANION_SUFFIX = "&companion=ws://127.0.0.1:12345"; + +type TAutoJoinLinkModalProps = { + url: string; + onClose: () => void; +}; + +export const AutoJoinLinkModal = ({ url, onClose }: TAutoJoinLinkModalProps) => { + const modalRef = useRef(null); + const [includeCompanion, setIncludeCompanion] = useState(false); + + const displayUrl = includeCompanion ? `${url}${COMPANION_SUFFIX}` : url; + + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (modalRef.current && !modalRef.current.contains(event.target as Node)) { + onClose(); + } + }; + document.addEventListener("mousedown", handleClickOutside); + return () => document.removeEventListener("mousedown", handleClickOutside); + }, [onClose]); + + return ( + +
+ Auto Join Link + + Share this link to let someone join the same set of calls automatically. + + + setIncludeCompanion(e.target.checked)} + /> + Include companion connection + + + + + + + + + +
+
+ ); +}; diff --git a/src/components/auto-join/auto-join-page.tsx b/src/components/auto-join/auto-join-page.tsx new file mode 100644 index 00000000..f97ea77b --- /dev/null +++ b/src/components/auto-join/auto-join-page.tsx @@ -0,0 +1,68 @@ +import { useEffect, useRef } from "react"; +import { useSearchParams, useNavigate } from "react-router"; +import { useGlobalState } from "../../global-state/context-provider"; +import { useInitiateProductionCall } from "../../hooks/use-initiate-production-call"; +import { AUTO_JOIN_STORAGE_KEY, TAutoJoinCall } from "../../utils/auto-join"; + +export const AutoJoinPage = () => { + const [searchParams] = useSearchParams(); + const navigate = useNavigate(); + const [{ userSettings, devices }, dispatch] = useGlobalState(); + const { initiateProductionCall } = useInitiateProductionCall({ dispatch }); + const hasInitiated = useRef(false); + + useEffect(() => { + if (hasInitiated.current) return; + + const callsParam = searchParams.get("calls") ?? ""; + const usernameParam = searchParams.get("username"); + const companionParam = searchParams.get("companion"); + const username = usernameParam || userSettings?.username || "Auto"; + + const calls: TAutoJoinCall[] = callsParam + .split(",") + .map((pair) => pair.split(":")) + .filter(([p, l]) => p && l) + .map(([productionId, lineId]) => ({ productionId, lineId })); + + if (calls.length === 0) { + navigate("/"); + return; + } + + hasInitiated.current = true; + + localStorage.setItem(AUTO_JOIN_STORAGE_KEY, JSON.stringify(calls)); + + if (companionParam) { + localStorage.setItem("companion_auto_connect", companionParam); + } + + const audiooutput = userSettings?.audiooutput; + const audioinput = + userSettings?.audioinput || devices?.input?.[0]?.deviceId; + + const joinAll = async () => { + for (const call of calls) { + await initiateProductionCall({ + payload: { + joinProductionOptions: { + productionId: call.productionId, + lineId: call.lineId, + username, + audioinput, + lineUsedForProgramOutput: false, + isProgramUser: false, + }, + audiooutput, + }, + }); + } + navigate("/production-calls"); + }; + + joinAll(); + }, [searchParams, userSettings, devices, navigate, initiateProductionCall]); + + return null; +}; diff --git a/src/components/calls-page/calls-page.tsx b/src/components/calls-page/calls-page.tsx index 26f874a8..3d89f4cb 100644 --- a/src/components/calls-page/calls-page.tsx +++ b/src/components/calls-page/calls-page.tsx @@ -1,8 +1,12 @@ import styled from "@emotion/styled"; import { useEffect, useRef, useState } from "react"; import { useParams } from "react-router"; +import { ShareIcon } from "../../assets/icons/icon"; import { useGlobalState } from "../../global-state/context-provider"; +import { useAutoJoinRestore } from "../../hooks/use-auto-join-restore"; import { useCallList } from "../../hooks/use-call-list"; +import { AutoJoinLinkModal } from "../auto-join/auto-join-link-modal"; +import { CopyIconWrapper } from "../copy-button/copy-components"; import { JoinProduction } from "../landing-page/join-production"; import { UserSettingsButton } from "../landing-page/user-settings-button"; import { Modal } from "../modal/modal"; @@ -46,6 +50,7 @@ const CallsContainer = styled.div` export const CallsPage = () => { const [productionId, setProductionId] = useState(null); const [addCallActive, setAddCallActive] = useState(false); + const [autoJoinModalOpen, setAutoJoinModalOpen] = useState(false); const [confirmExitModalOpen, setConfirmExitModalOpen] = useState(false); const [isMasterInputMuted, setIsMasterInputMuted] = useState(true); @@ -64,9 +69,20 @@ export const CallsPage = () => { const [showSettings, setShowSettings] = useState(false); const [isSettingGlobalMute, setIsSettingGlobalMute] = useState(false); + const [companionAutoConnectUrl, setCompanionAutoConnectUrl] = useState< + string | null + >(null); const { productionId: paramProductionId, lineId: paramLineId } = useParams(); + useEffect(() => { + const storedCompanionUrl = localStorage.getItem("companion_auto_connect"); + if (storedCompanionUrl && !companionAutoConnectUrl) { + setCompanionAutoConnectUrl(storedCompanionUrl); + localStorage.removeItem("companion_auto_connect"); + } + }, [companionAutoConnectUrl]); + const navigate = useCallsNavigation({ isEmpty: Object.values(calls).length === 0, paramProductionId, @@ -106,6 +122,7 @@ export const CallsPage = () => { }, [calls]); usePreventPullToRefresh(); + useAutoJoinRestore(); useEffect(() => { if (selectedProductionId) { @@ -151,6 +168,34 @@ export const CallsPage = () => { )} + setAutoJoinModalOpen(true)} + style={{ marginTop: "2px", marginLeft: "2px" }} + > + + + {autoJoinModalOpen && ( + { + const { productionId: pid, lineId: lid } = + c.joinProductionOptions ?? {}; + return pid && lid ? `${pid}:${lid}` : null; + }) + .filter(Boolean) + .join(",")}`} + onClose={() => setAutoJoinModalOpen(false)} + /> + )} + + ) : undefined + } hasNavigateToRoot onNavigateToRoot={() => { if (isEmpty) { @@ -193,6 +238,7 @@ export const CallsPage = () => { callActionHandlers={callActionHandlers} sendCallsStateUpdate={sendCallsStateUpdate} resetLastSentCallsState={resetLastSentCallsState} + companionAutoConnectUrl={companionAutoConnectUrl} /> diff --git a/src/components/calls-page/connect-to-ws-button.tsx b/src/components/calls-page/connect-to-ws-button.tsx index dac9e9f6..affc9bff 100644 --- a/src/components/calls-page/connect-to-ws-button.tsx +++ b/src/components/calls-page/connect-to-ws-button.tsx @@ -71,6 +71,7 @@ interface ConnectToWSButtonProps { Record void>> >; isMasterInputMuted: boolean; + companionAutoConnectUrl: string | null; handleToggleGlobalMute: () => void; sendCallsStateUpdate: () => void; resetLastSentCallsState: () => void; @@ -80,6 +81,7 @@ export const ConnectToWSButton = ({ callIndexMap, callActionHandlers, isMasterInputMuted, + companionAutoConnectUrl, handleToggleGlobalMute, sendCallsStateUpdate, resetLastSentCallsState, @@ -88,6 +90,7 @@ export const ConnectToWSButton = ({ const [isWSReconnecting, setIsWSReconnecting] = useState(false); const [isConnectionConflict, setConnectionConflict] = useState(false); const [{ calls }, dispatch] = useGlobalState(); + const [hasAutoConnected, setHasAutoConnected] = useState(false); // map call ids to indices for actions useEffect(() => { @@ -129,6 +132,22 @@ export const ConnectToWSButton = ({ wsConnect, }); + // Auto-connect to Companion if companionAutoConnectUrl is provided + useEffect(() => { + if (hasAutoConnected || isWSConnected || !companionAutoConnectUrl) { + return; + } + + setHasAutoConnected(true); + + if ( + companionAutoConnectUrl.startsWith("ws://") || + companionAutoConnectUrl.startsWith("wss://") + ) { + wsConnect(companionAutoConnectUrl); + } + }, [hasAutoConnected, isWSConnected, companionAutoConnectUrl, wsConnect]); + const handleConnect = (url: string) => { setConnectionConflict(false); wsConnect(url); diff --git a/src/components/calls-page/header-actions.tsx b/src/components/calls-page/header-actions.tsx index 3ff457cb..ecbe794a 100644 --- a/src/components/calls-page/header-actions.tsx +++ b/src/components/calls-page/header-actions.tsx @@ -52,6 +52,7 @@ type HeaderActionsProps = { callActionHandlers: React.MutableRefObject< Record void>> >; + companionAutoConnectUrl: string | null; setIsMasterInputMuted: React.Dispatch>; setAddCallActive: (addCallActive: boolean) => void; setIsSettingGlobalMute: React.Dispatch>; @@ -66,6 +67,7 @@ export const HeaderActions = ({ callIndexMap, callActionHandlers, addCallActive, + companionAutoConnectUrl, setAddCallActive, setIsSettingGlobalMute, sendCallsStateUpdate, @@ -86,6 +88,7 @@ export const HeaderActions = ({ sendCallsStateUpdate={sendCallsStateUpdate} resetLastSentCallsState={resetLastSentCallsState} handleToggleGlobalMute={handleToggleGlobalMute} + companionAutoConnectUrl={companionAutoConnectUrl} /> )} {!isEmpty && !isSingleCall && !isMobile && ( diff --git a/src/components/page-layout/page-header.tsx b/src/components/page-layout/page-header.tsx index ab23b9f0..5949b745 100644 --- a/src/components/page-layout/page-header.tsx +++ b/src/components/page-layout/page-header.tsx @@ -1,5 +1,5 @@ import styled from "@emotion/styled"; -import { FC, PropsWithChildren } from "react"; +import { FC, PropsWithChildren, ReactNode } from "react"; import { DisplayContainer } from "../generic-components"; import { DisplayContainerHeader } from "../landing-page/display-container-header"; import { LoaderDots } from "../loader/loader"; @@ -54,6 +54,7 @@ const RootButtonWrapper = styled.div` interface PageHeaderProps extends PropsWithChildren { title: string; + titleAction?: ReactNode; hasNavigateToRoot?: boolean; onNavigateToRoot?: () => void; loading?: boolean; @@ -62,6 +63,7 @@ interface PageHeaderProps extends PropsWithChildren { export const PageHeader: FC = (props) => { const { title, + titleAction, hasNavigateToRoot = false, onNavigateToRoot, loading = false, @@ -77,6 +79,7 @@ export const PageHeader: FC = (props) => { )} {title} + {titleAction} diff --git a/src/hooks/use-auto-join-restore.ts b/src/hooks/use-auto-join-restore.ts new file mode 100644 index 00000000..a24d4c39 --- /dev/null +++ b/src/hooks/use-auto-join-restore.ts @@ -0,0 +1,63 @@ +import { useEffect, useRef } from "react"; +import { useGlobalState } from "../global-state/context-provider"; +import { useInitiateProductionCall } from "./use-initiate-production-call"; +import { AUTO_JOIN_STORAGE_KEY, TAutoJoinCall } from "../utils/auto-join"; + +export const useAutoJoinRestore = () => { + const [{ calls, userSettings, devices }, dispatch] = useGlobalState(); + const { initiateProductionCall } = useInitiateProductionCall({ dispatch }); + const callsWereActive = useRef(false); + const hasRestored = useRef(false); + + // On mount: restore calls if localStorage has a saved config and state is empty + useEffect(() => { + if (hasRestored.current) return; + + const stored = localStorage.getItem(AUTO_JOIN_STORAGE_KEY); + if (!stored || Object.keys(calls).length > 0) return; + + let savedCalls: TAutoJoinCall[]; + try { + savedCalls = JSON.parse(stored); + } catch { + localStorage.removeItem(AUTO_JOIN_STORAGE_KEY); + return; + } + + if (savedCalls.length === 0) return; + + hasRestored.current = true; + + const username = userSettings?.username || "Auto"; + const audiooutput = userSettings?.audiooutput; + const audioinput = + userSettings?.audioinput || devices?.input?.[0]?.deviceId; + + savedCalls.forEach((call) => { + initiateProductionCall({ + payload: { + joinProductionOptions: { + productionId: call.productionId, + lineId: call.lineId, + username, + audioinput, + lineUsedForProgramOutput: false, + isProgramUser: false, + }, + audiooutput, + }, + }); + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + // Clear stored config when the user intentionally empties all calls + useEffect(() => { + if (Object.keys(calls).length > 0) { + callsWereActive.current = true; + } else if (callsWereActive.current) { + localStorage.removeItem(AUTO_JOIN_STORAGE_KEY); + callsWereActive.current = false; + } + }, [calls]); +}; diff --git a/src/utils/auto-join.ts b/src/utils/auto-join.ts new file mode 100644 index 00000000..e748ad26 --- /dev/null +++ b/src/utils/auto-join.ts @@ -0,0 +1,6 @@ +export const AUTO_JOIN_STORAGE_KEY = "auto_join_calls"; + +export type TAutoJoinCall = { + productionId: string; + lineId: string; +}; From 305b079d71a96d4d99d1b8b24603baa2dc7b7375 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Westman?= Date: Thu, 19 Mar 2026 15:59:27 +0100 Subject: [PATCH 2/5] fix: prettier formatting and replace for-of loop with Promise.all Co-Authored-By: Claude Sonnet 4.6 --- src/components/auto-join/auto-join-link-modal.tsx | 13 ++++++++++--- src/components/auto-join/auto-join-page.tsx | 15 ++++++--------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/components/auto-join/auto-join-link-modal.tsx b/src/components/auto-join/auto-join-link-modal.tsx index ccae7059..df867763 100644 --- a/src/components/auto-join/auto-join-link-modal.tsx +++ b/src/components/auto-join/auto-join-link-modal.tsx @@ -35,7 +35,10 @@ type TAutoJoinLinkModalProps = { onClose: () => void; }; -export const AutoJoinLinkModal = ({ url, onClose }: TAutoJoinLinkModalProps) => { +export const AutoJoinLinkModal = ({ + url, + onClose, +}: TAutoJoinLinkModalProps) => { const modalRef = useRef(null); const [includeCompanion, setIncludeCompanion] = useState(false); @@ -43,7 +46,10 @@ export const AutoJoinLinkModal = ({ url, onClose }: TAutoJoinLinkModalProps) => useEffect(() => { const handleClickOutside = (event: MouseEvent) => { - if (modalRef.current && !modalRef.current.contains(event.target as Node)) { + if ( + modalRef.current && + !modalRef.current.contains(event.target as Node) + ) { onClose(); } }; @@ -56,7 +62,8 @@ export const AutoJoinLinkModal = ({ url, onClose }: TAutoJoinLinkModalProps) =>
Auto Join Link - Share this link to let someone join the same set of calls automatically. + Share this link to let someone join the same set of calls + automatically. { const audioinput = userSettings?.audioinput || devices?.input?.[0]?.deviceId; - const joinAll = async () => { - for (const call of calls) { - await initiateProductionCall({ + Promise.all( + calls.map((call) => + initiateProductionCall({ payload: { joinProductionOptions: { productionId: call.productionId, @@ -56,12 +56,9 @@ export const AutoJoinPage = () => { }, audiooutput, }, - }); - } - navigate("/production-calls"); - }; - - joinAll(); + }) + ) + ).then(() => navigate("/production-calls")); }, [searchParams, userSettings, devices, navigate, initiateProductionCall]); return null; From e9ff541189f583532af373b92be094bf09fa7a30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Westman?= Date: Fri, 20 Mar 2026 10:08:54 +0100 Subject: [PATCH 3/5] fix: add username to modal --- src/App.tsx | 129 +++++++++--------- src/api/api.ts | 11 +- .../auto-join/auto-join-link-modal.tsx | 19 ++- 3 files changed, 87 insertions(+), 72 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 97b6d446..66658444 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -108,73 +108,70 @@ const AppContent = ({ /> )} - {continueToApp && ( - <> - {denied && ( - - - - )} - {!permission && !denied && ( - - - - )} - {apiError && ( - - - - )} - {permission && !denied && !apiError && userSettings && ( - - <> - setApiError(true)} /> - } - errorElement={} - /> - } - errorElement={} - /> - setApiError(true)} - /> - } - errorElement={} - /> - } - errorElement={} - /> - } - errorElement={} - /> - } /> - - - )} - + {continueToApp && denied && ( + + + )} + {continueToApp && !permission && !denied && ( + + + + )} + {continueToApp && apiError && ( + + + + )} + + + {permission && !denied && !apiError && userSettings && ( + <> + setApiError(true)} />} + errorElement={} + /> + } + errorElement={} + /> + setApiError(true)} /> + } + errorElement={} + /> + } + errorElement={} + /> + } + errorElement={} + /> + } + errorElement={} + /> + + )} + } /> + ); }; diff --git a/src/api/api.ts b/src/api/api.ts index 532b1f90..972b2aef 100644 --- a/src/api/api.ts +++ b/src/api/api.ts @@ -1,9 +1,12 @@ import { handleFetchRequest } from "./handle-fetch-request.ts"; -const API_VERSION = import.meta.env.VITE_BACKEND_API_VERSION ?? "api/v1/"; -const API_URL = - `${import.meta.env.VITE_BACKEND_URL.replace(/\/+$/, "")}/${API_VERSION}` || - `${window.location.origin}/${API_VERSION}`; +const API_VERSION = ( + import.meta.env.VITE_BACKEND_API_VERSION ?? "api/v1" +).replace(/\/+$/, ""); +const BACKEND_URL = import.meta.env.VITE_BACKEND_URL; +const API_URL = BACKEND_URL + ? `${BACKEND_URL.replace(/\/+$/, "")}/${API_VERSION}/` + : `${window.location.origin}/${API_VERSION}/`; const API_KEY = import.meta.env.VITE_BACKEND_API_KEY; type TCreateProductionOptions = { diff --git a/src/components/auto-join/auto-join-link-modal.tsx b/src/components/auto-join/auto-join-link-modal.tsx index df867763..5e3af867 100644 --- a/src/components/auto-join/auto-join-link-modal.tsx +++ b/src/components/auto-join/auto-join-link-modal.tsx @@ -1,7 +1,7 @@ import { useEffect, useRef, useState } from "react"; import styled from "@emotion/styled"; import { CopyButton } from "../copy-button/copy-button"; -import { FormInput } from "../form-elements/form-elements"; +import { DecorativeLabel, FormInput } from "../form-elements/form-elements"; import { Modal } from "../modal/modal"; import { InputWrapper, @@ -41,8 +41,10 @@ export const AutoJoinLinkModal = ({ }: TAutoJoinLinkModalProps) => { const modalRef = useRef(null); const [includeCompanion, setIncludeCompanion] = useState(false); + const [username, setUsername] = useState(""); - const displayUrl = includeCompanion ? `${url}${COMPANION_SUFFIX}` : url; + const usernameParam = username ? `&username=${encodeURIComponent(username)}` : ""; + const displayUrl = `${url}${usernameParam}${includeCompanion ? COMPANION_SUFFIX : ""}`; useEffect(() => { const handleClickOutside = (event: MouseEvent) => { @@ -65,6 +67,19 @@ export const AutoJoinLinkModal = ({ Share this link to let someone join the same set of calls automatically. + + + + Username (optional) + setUsername(e.target.value)} + /> + + + Date: Fri, 20 Mar 2026 10:17:03 +0100 Subject: [PATCH 4/5] feat: add optional username field to auto-join link modal Co-Authored-By: Claude Sonnet 4.6 --- src/components/auto-join/auto-join-link-modal.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/auto-join/auto-join-link-modal.tsx b/src/components/auto-join/auto-join-link-modal.tsx index 5e3af867..872f3d26 100644 --- a/src/components/auto-join/auto-join-link-modal.tsx +++ b/src/components/auto-join/auto-join-link-modal.tsx @@ -43,7 +43,9 @@ export const AutoJoinLinkModal = ({ const [includeCompanion, setIncludeCompanion] = useState(false); const [username, setUsername] = useState(""); - const usernameParam = username ? `&username=${encodeURIComponent(username)}` : ""; + const usernameParam = username + ? `&username=${encodeURIComponent(username)}` + : ""; const displayUrl = `${url}${usernameParam}${includeCompanion ? COMPANION_SUFFIX : ""}`; useEffect(() => { From 7e54cbf076d6328dc448a4b55545e9fe03732169 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Westman?= Date: Mon, 23 Mar 2026 12:36:33 +0100 Subject: [PATCH 5/5] fix: companion autoconnect --- src/hooks/use-call-list.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/hooks/use-call-list.ts b/src/hooks/use-call-list.ts index 0807ee4a..c6653418 100644 --- a/src/hooks/use-call-list.ts +++ b/src/hooks/use-call-list.ts @@ -100,7 +100,9 @@ export function useCallList({ ((data.isProgramOutputLine && !data.isProgramUser) || !data.isProgramOutputLine)) || prev.volume !== data.volume || - prev.isSomeoneSpeaking !== data.isSomeoneSpeaking; + prev.isSomeoneSpeaking !== data.isSomeoneSpeaking || + prev.lineName !== data.lineName || + prev.productionName !== data.productionName; if (!hasChanged) return;