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 (
{ 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); 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 && ( )}
); }