+
+ Auto Join Link
+
+ Share this link to let someone join the same set of calls
+ automatically.
+
+
+
+
+ Username (optional)
+ setUsername(e.target.value)}
+ />
+
+
+
+
+ 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..8d55fa61
--- /dev/null
+++ b/src/components/auto-join/auto-join-page.tsx
@@ -0,0 +1,65 @@
+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;
+
+ Promise.all(
+ calls.map((call) =>
+ initiateProductionCall({
+ payload: {
+ joinProductionOptions: {
+ productionId: call.productionId,
+ lineId: call.lineId,
+ username,
+ audioinput,
+ lineUsedForProgramOutput: false,
+ isProgramUser: false,
+ },
+ audiooutput,
+ },
+ })
+ )
+ ).then(() => navigate("/production-calls"));
+ }, [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