Files
muzak/frontend/src/app/lobby/page.tsx
T
2025-08-17 11:47:14 +02:00

362 lines
12 KiB
TypeScript

"use client";
import { useState, useEffect } from "react";
import { Markazi_Text, Nunito } from "next/font/google";
import Image from "next/image";
import Link from "next/link";
import { useAuth } from "react-oidc-context";
import { useSession } from "muzak/contexts/SessionContext";
import { getUser, api, Track } from "muzak/data/fetcher";
import getSessionId from "muzak/data/session";
const nunito = Nunito({
weight: "900",
subsets: ["latin"],
});
const MAX_PARTY_SIZE = 8;
export default function Lobby() {
const [userList, setUserList] = useState<any[]>([]);
const [noList, setNoList] = useState<any[]>([]);
const [yesList, setYesList] = useState<any[]>([]);
const [sessionSocket, setSessionSocket] = useState<WebSocket | null>(null);
const { sessionId, setSessionId } = useSession();
const [sessionState, setSessionState] = useState("lobby");
const [currentSong, setCurrentSong] = useState<Track>();
const [songLoading, setSongLoading] = useState(false);
const auth = useAuth();
const user = getUser();
// Main message handler and dispatcher
function onMessage(e) {
console.log(e);
let data = JSON.parse(e.data);
for (let key in data) {
if (key === "joined") {
addUser(data[key]);
} else if (key == "track") {
getTrack();
} else if (key == "voted") {
voteUser(data[key]);
}
}
console.log(data);
}
// Setup session
useEffect(() => {
//console.log("session: ", sessionId);
let socket: WebSocket | null;
if (sessionId) {
socket = new WebSocket(
`${process.env.NEXT_PUBLIC_WS_HOST}/voting/session/${sessionId}/`,
);
socket.onmessage = onMessage;
setSessionSocket(socket);
// console.log("session made: ", sessionId);
}
return () => {
if (socket) {
socket.close();
}
};
}, [sessionId]);
async function setSession() {
const id = await getSessionId();
setSessionId(id);
console.log("Session ID:", id);
}
useEffect(() => {
if (!auth.isLoading && !auth.error && auth.isAuthenticated) {
setSession();
}
}, [auth]);
function startSession() {
setSessionState("voting");
getTrack();
}
function addUser(user: object) {
const newUser = {
id: user["id"],
name: user["username"],
avatar: `https://i.pravatar.cc/150?img=${user["id"]}`,
hidden: false,
};
setUserList((prevUserList) => {
if (prevUserList.find((u) => u.id === user["id"])) {
console.log("User already exists, not adding");
return prevUserList;
}
return [...prevUserList, newUser];
});
}
// Visually processes the incoming vote for a user.
function voteUser(vote: object) {
const userId = vote["id"];
const userName = vote["username"];
const points = vote["points"];
if (points === 0) {
setYesList((prevYesList) => {
//if (prevYesList.find((u) => u.id === userId)) return prevYesList;
return prevYesList.filter((u) => u.id !== userId);
});
setNoList((prevNoList) => {
if (prevNoList.find((u) => u.id === userId)) {
return prevNoList;
}
return [
...prevNoList,
{
id: userId,
name: userName,
avatar: `https://i.pravatar.cc/150?img=${userId}`,
},
];
});
} else {
setNoList((prevNoList) => {
return prevNoList.filter((u) => u.id !== userId);
});
setYesList((prevYesList) => {
if (prevYesList.find((u) => u.id === userId)) {
return prevYesList;
}
return [
...prevYesList,
{
id: userId,
name: userName,
avatar: `https://i.pravatar.cc/150?img=${userId}`,
},
];
});
}
setUserList((prevUserList) => {
// Set hidden true on the user that voted:
return prevUserList.map((user) => {
if (user.id === userId) {
return { ...user, hidden: true };
}
return user;
});
});
}
async function getTrack() {
setSongLoading(true);
setUserList((prevUserList) => {
return prevUserList.map((user) => {
return { ...user, hidden: false };
});
});
setYesList([]);
setNoList([]);
const { data: session } = await api.sessions.get({ session_id: sessionId });
const trackId = session?.current_track;
if (trackId) {
const { data: track } = await api.tracks.detail({ id: trackId });
console.log(session);
console.log(track);
setCurrentSong(track);
} else {
nextTrack();
}
setSongLoading(false);
}
async function nextTrack() {
setSongLoading(true);
sessionSocket.send(
JSON.stringify({
action: "next",
token: user.access_token,
}),
);
}
if (sessionState == "lobby") {
return (
<main className="flex min-h-screen flex-col items-center justify-between p-24">
<div>
<h1
className={`${nunito.className} text-outline text-[42px] tracking-tighter text-yellow-500`}
>
LOBBY
</h1>
</div>
<div className="flex min-h-[532px] min-w-[500px] flex-col items-center justify-start gap-4 rounded-md bg-blue-950/80 p-8">
<div className="flex w-full flex-row items-center justify-between px-1">
<div className="text-3xl font-bold text-yellow-500">
Players: {userList.length}
</div>
<button
onClick={startSession}
className="rounded-lg border-2 border-black bg-yellow-500 p-4 text-2xl font-extrabold text-blue-600 hover:bg-yellow-400"
>
START!
</button>
</div>
{userList.length === 0 ? (
<div className="mt-31 min-w-52 text-3xl font-semibold text-white drop-shadow-sm drop-shadow-gray-900">
Waiting for players...
</div>
) : (
<div className="grid min-w-52 grid-flow-row grid-cols-2 items-start justify-start gap-4">
{userList.map((user) => (
<div
key={user.id}
className="flex min-w-32 items-center gap-x-4 rounded-l-[40px] rounded-r-lg border-2 bg-orange-400/90 p-2"
>
<img
src={user.avatar}
alt={user.name}
className="h-16 w-16 rounded-full bg-gray-300"
/>
<span className="text-2xl font-semibold text-white">
{user.name}
</span>
{user.host && (
<div className="rounded-full bg-blue-950 p-[5px]">
<Image
src="/crown.png"
width={24}
height={24}
alt="Crown image"
className="p-[1px]"
/>
</div>
)}
</div>
))}
</div>
)}
</div>
</main>
);
} else if (sessionState == "voting") {
return (
<main className="flex min-h-screen flex-col items-center justify-between gap-4 p-24">
<div className="flex w-full items-start justify-between text-center">
<div className="flex-1"></div>
<div className="flex flex-1 items-center justify-center">
<div className="flex h-[170px] w-[170px] items-center justify-center rounded-full border-2 bg-orange-400">
<div className="text-5xl text-white drop-shadow-sm drop-shadow-black">
<button onClick={getTrack}>30</button>
</div>
</div>
</div>
<div className="flex flex-1 flex-row items-center justify-end gap-4">
<Link
href="/pause"
className="w-30 rounded-lg border-2 border-black bg-yellow-500 p-4 text-2xl font-extrabold text-blue-600 hover:bg-yellow-400"
>
PAUSE
</Link>
<button
className="w-30 rounded-lg border-2 border-black bg-yellow-500 p-4 text-2xl font-extrabold text-blue-600 hover:bg-yellow-400"
onClick={nextTrack}
>
NEXT
</button>
</div>
</div>
<div className="flex flex-col items-center justify-center">
<div className="flex flex-row items-center justify-center gap-x-32">
<div className="grid min-w-52 grid-flow-row grid-cols-3 items-start justify-start gap-4 rounded bg-blue-950/80 p-4">
{noList.map((user) => (
<div
key={user.id}
className="flex h-[120px] w-[120px] flex-col items-center justify-center p-4"
>
<img
src={user.avatar}
alt={user.name}
width={75}
height={75}
/>
<div className="mt-1 min-w-[90px] rounded-3xl px-3 text-center text-3xl font-semibold text-white">
{user.name}
</div>
</div>
))}{" "}
</div>
{!songLoading && currentSong ? (
<div className="rounded-md bg-black p-2">
<Image
src={currentSong.album_cover}
alt={currentSong.name}
width={500}
height={500}
/>
</div>
) : (
<div className="rounded-md bg-black p-2">
<Image
src="/meowl.png"
alt="Loading.."
width={500}
height={500}
/>
</div>
)}
<div className="grid min-w-52 grid-flow-row grid-cols-3 items-start justify-start gap-4 rounded bg-blue-950/80 p-4">
{yesList.map((user) => (
<div
key={user.id}
className="flex h-[120px] w-[120px] flex-col items-center justify-center p-4"
>
<img
src={user.avatar}
alt={user.name}
width={75}
height={75}
/>
<div className="mt-1 min-w-[90px] rounded-3xl px-3 text-center text-3xl font-semibold text-white">
{user.name}
</div>
</div>
))}{" "}
</div>
</div>
{!songLoading && currentSong && (
<div className="flex flex-col items-center justify-start">
<h2
className={`${nunito.className} text-outline text-[42px] tracking-tighter text-yellow-500`}
>
{currentSong.artist}
</h2>
<h2
className={`${nunito.className} text-outline text-[42px] tracking-tighter text-yellow-500`}
>
{currentSong.name}
</h2>
</div>
)}
</div>
<div className="flex flex-row items-start justify-start gap-10 rounded bg-blue-950/80 p-4">
{userList.map((user) => (
<div
key={user.id}
className={`flex h-[100px] w-[100px] flex-col items-center justify-center p-4 ${user.hidden ? "hidden" : ""}`}
>
<img src={user.avatar} alt={user.name} width={75} height={75} />
<div className="mt-1 min-w-[90px] rounded-3xl px-3 text-center text-3xl font-semibold text-white">
{user.name}
</div>
</div>
))}
</div>
</main>
);
}
}