mirror of
https://github.com/p-stream/p-stream.git
synced 2026-03-31 01:28:43 +00:00
add join watch party button to links dropdown
This commit is contained in:
parent
17695aefde
commit
400c31336e
1 changed files with 117 additions and 0 deletions
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Reference in a new issue