From 400c31336e5f81abf68169c1cf809bebb21ae1fc Mon Sep 17 00:00:00 2001 From: Pas <74743263+Pasithea0@users.noreply.github.com> Date: Mon, 2 Jun 2025 22:44:28 -0600 Subject: [PATCH] add join watch party button to links dropdown --- src/components/LinksDropdown.tsx | 117 +++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) diff --git a/src/components/LinksDropdown.tsx b/src/components/LinksDropdown.tsx index bb9a19d5..43e88d71 100644 --- a/src/components/LinksDropdown.tsx +++ b/src/components/LinksDropdown.tsx @@ -4,10 +4,13 @@ import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router-dom"; import { base64ToBuffer, decryptData } from "@/backend/accounts/crypto"; +import { getRoomStatuses } from "@/backend/player/status"; import { UserAvatar } from "@/components/Avatar"; import { Icon, Icons } from "@/components/Icon"; +import { Spinner } from "@/components/layout/Spinner"; import { Transition } from "@/components/utils/Transition"; import { useAuth } from "@/hooks/auth/useAuth"; +import { useBackendUrl } from "@/hooks/auth/useBackendUrl"; import { conf } from "@/setup/config"; import { useAuthStore } from "@/stores/auth"; @@ -86,6 +89,119 @@ function CircleDropdownLink(props: { icon: Icons; href: string }) { ); } +function WatchPartyInputLink() { + const { t } = useTranslation(); + const navigate = useNavigate(); + const [code, setCode] = useState(""); + const [isFocused, setIsFocused] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + const backendUrl = useBackendUrl(); + const account = useAuthStore((s) => s.account); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (!code.trim() || !backendUrl) return; + + setIsLoading(true); + setError(null); + + try { + const response = await getRoomStatuses( + backendUrl, + account, + code.trim().toUpperCase(), + ); + const users = Object.values(response.users); + + if (users.length === 0) { + setError(t("watchParty.emptyRoom")); + return; + } + + const hostUser = users.find((user) => user[0].isHost)?.[0]; + if (!hostUser) { + setError(t("watchParty.noHost")); + return; + } + + const { content } = hostUser; + + let targetUrl = ""; + if ( + content.type.toLowerCase() === "tv show" && + content.seasonId && + content.episodeId + ) { + targetUrl = `/media/tmdb-tv-${content.tmdbId}/${content.seasonId}/${content.episodeId}`; + } else { + targetUrl = `/media/tmdb-movie-${content.tmdbId}`; + } + + const url = new URL(targetUrl, window.location.origin); + url.searchParams.set("watchparty", code.trim().toUpperCase()); + + navigate(url.pathname + url.search); + setCode(""); + } catch (err) { + console.error("Failed to fetch room data:", err); + setError(t("watchParty.invalidRoom")); + } finally { + setIsLoading(false); + } + }; + + return ( +
+
+
+ + { + setCode(e.target.value.toUpperCase()); + setError(null); + }} + onFocus={() => setIsFocused(true)} + onBlur={() => setIsFocused(false)} + placeholder={t("watchParty.joinParty")} + className="bg-transparent border-none outline-none w-full text-base placeholder:text-dropdown-text group-hover:placeholder:text-white" + maxLength={10} + disabled={isLoading} + /> + +
+ {error &&

{error}

} +
+
+ ); +} + export function LinksDropdown(props: { children: React.ReactNode }) { const { t } = useTranslation(); const [open, setOpen] = useState(false); @@ -158,6 +274,7 @@ export function LinksDropdown(props: { children: React.ReactNode }) { {t("navigation.menu.discover")} + {deviceName ? (