add join watch party button to links dropdown

This commit is contained in:
Pas 2025-06-02 22:44:28 -06:00
parent 17695aefde
commit 400c31336e

View file

@ -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<string | null>(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 (
<form
onSubmit={handleSubmit}
className={classNames(
"m-3 p-1 rounded font-medium transition-colors duration-100 group",
"text-dropdown-text hover:text-white",
isFocused ? "bg-dropdown-contentBackground" : "",
)}
>
<div className="flex flex-col gap-1">
<div className="flex items-center gap-3">
<Icon icon={Icons.WATCH_PARTY} className="text-xl" />
<input
type="text"
value={code}
onChange={(e) => {
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}
/>
<button
type="submit"
className={classNames(
"p-1 rounded hover:bg-dropdown-contentBackground transition-colors",
isLoading && "opacity-50 cursor-not-allowed",
!code.trim() && "opacity-0 pointer-events-none",
)}
disabled={!code.trim() || isLoading}
>
{isLoading ? (
<Spinner className="w-5 h-5" />
) : (
<Icon
icon={Icons.ARROW_RIGHT}
className="text-xl transition-opacity duration-200"
/>
)}
</button>
</div>
{error && <p className="text-xs text-red-500 px-1 ml-8">{error}</p>}
</div>
</form>
);
}
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 }) {
<DropdownLink href="/discover" icon={Icons.RISING_STAR}>
{t("navigation.menu.discover")}
</DropdownLink>
<WatchPartyInputLink />
{deviceName ? (
<DropdownLink
className="!text-type-danger opacity-75 hover:opacity-100"