import classNames from "classnames";
import { useCallback, useEffect, useMemo, useState } from "react";
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";
import { usePreferencesStore } from "@/stores/preferences";
function Divider() {
return
;
}
function GoToLink(props: {
children: React.ReactNode;
href?: string;
className?: string;
onClick?: () => void;
}) {
const navigate = useNavigate();
const goTo = (href: string) => {
if (href.startsWith("http")) {
window.open(href, "_blank");
} else {
window.scrollTo(0, 0);
navigate(href);
}
};
return (
{
evt.preventDefault();
if (props.href) goTo(props.href);
else props.onClick?.();
}}
className={props.className}
>
{props.children}
);
}
function DropdownLink(props: {
children: React.ReactNode;
href?: string;
icon?: Icons;
highlight?: boolean;
className?: string;
onClick?: () => void;
}) {
return (
{props.icon ? : null}
{props.children}
);
}
function CircleDropdownLink(props: { icon: Icons; href: string }) {
return (
window.scrollTo(0, 0)}
className="tabbable w-11 h-11 rounded-full bg-dropdown-contentBackground text-dropdown-text hover:text-white transition-colors duration-100 flex justify-center items-center"
>
);
}
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 (
);
}
export function LinksDropdown(props: { children: React.ReactNode }) {
const { t } = useTranslation();
const [open, setOpen] = useState(false);
const deviceName = useAuthStore((s) => s.account?.deviceName);
const seed = useAuthStore((s) => s.account?.seed);
const bufferSeed = useMemo(
() => (seed ? base64ToBuffer(seed) : null),
[seed],
);
const { logout } = useAuth();
useEffect(() => {
function onWindowClick(evt: MouseEvent) {
if ((evt.target as HTMLElement).closest(".is-dropdown")) return;
setOpen(false);
}
window.addEventListener("click", onWindowClick);
return () => window.removeEventListener("click", onWindowClick);
}, []);
const toggleOpen = useCallback(() => {
setOpen((s) => !s);
}, []);
const enableLowPerformanceMode = usePreferencesStore(
(s) => s.enableLowPerformanceMode,
);
return (
evt.key === "Enter" && toggleOpen()}
>
{props.children}
{deviceName && bufferSeed ? (
{decryptData(deviceName, bufferSeed)}
) : (
{t("navigation.menu.register")}
)}
{t("navigation.menu.settings")}
{process.env.NODE_ENV === "development" ? (
{t("navigation.menu.development")}
) : null}
{t("navigation.menu.about")}
{!enableLowPerformanceMode && (
{t("navigation.menu.discover")}
)}
{deviceName ? (
{t("navigation.menu.logout")}
) : null}
{conf().GITHUB_LINK && (
)}
);
}