Files
no-whatever/src/app/room/[id]/page.tsx
T

144 lines
4.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useEffect, useState, useCallback, useRef } from "react";
import { useParams, useRouter } from "next/navigation";
import TopNav from "@/components/TopNav";
import SwipeDeck from "@/components/SwipeDeck";
import LeaveConfirmModal from "@/components/LeaveConfirmModal";
import { useRoomPolling } from "@/hooks/useRoomPolling";
import { getUserId } from "@/lib/userId";
export default function RoomPage() {
const params = useParams<{ id: string }>();
const router = useRouter();
const roomId = params.id;
const [userId, setUserId] = useState("");
const [joined, setJoined] = useState(false);
const [joinFailed, setJoinFailed] = useState(false);
const [showLeaveConfirm, setShowLeaveConfirm] = useState(false);
const leavingRef = useRef(false);
const {
userCount, match, matchType, matchLikes, runnerUps, likeCounts, swipeCounts, restaurants, notFound, mutate,
} = useRoomPolling(roomId);
useEffect(() => {
const id = getUserId();
setUserId(id);
fetch(`/api/room/${roomId}/join`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ userId: id }),
}).then((res) => {
if (res.ok) setJoined(true);
else setJoinFailed(true);
}).catch(() => setJoinFailed(true));
}, [roomId]);
useEffect(() => {
window.history.pushState({ roomGuard: true }, "");
const handlePopState = () => {
if (leavingRef.current) return;
window.history.pushState({ roomGuard: true }, "");
setShowLeaveConfirm(true);
};
const handleBeforeUnload = (e: BeforeUnloadEvent) => {
if (leavingRef.current) return;
e.preventDefault();
};
window.addEventListener("popstate", handlePopState);
window.addEventListener("beforeunload", handleBeforeUnload);
return () => {
window.removeEventListener("popstate", handlePopState);
window.removeEventListener("beforeunload", handleBeforeUnload);
};
}, []);
const confirmLeave = useCallback(() => {
leavingRef.current = true;
setShowLeaveConfirm(false);
router.push("/");
}, [router]);
const cancelLeave = useCallback(() => {
setShowLeaveConfirm(false);
}, []);
const handleExitRequest = useCallback(() => {
setShowLeaveConfirm(true);
}, []);
const handleReset = useCallback(async () => {
await fetch(`/api/room/${roomId}/reset`, { method: "POST" });
await mutate();
}, [roomId, mutate]);
const handleNarrow = useCallback(async (restaurantIds: string[]) => {
await fetch(`/api/room/${roomId}/reset`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ restaurantIds }),
});
await mutate();
}, [roomId, mutate]);
if (notFound || joinFailed) {
return (
<div className="flex h-dvh flex-col items-center justify-center gap-4 bg-background px-6">
<p className="text-4xl">🍜</p>
<p className="text-base font-semibold text-zinc-700"></p>
<p className="text-sm text-zinc-400"> 24 </p>
<button
onClick={() => router.push("/")}
className="mt-2 h-10 rounded-xl bg-emerald-500 px-6 text-sm font-bold text-white shadow-sm transition-colors hover:bg-emerald-600"
>
</button>
</div>
);
}
const initialIndex = swipeCounts[userId] ?? 0;
const ready = joined && userId && restaurants.length > 0;
if (!ready) {
return (
<div className="flex h-dvh flex-col items-center justify-center gap-3 bg-background">
<div className="h-6 w-6 animate-spin rounded-full border-2 border-zinc-300 border-t-emerald-500" />
<p className="text-sm text-zinc-400">...</p>
</div>
);
}
return (
<div className="flex h-dvh flex-col bg-background">
<TopNav roomId={roomId} userCount={userCount} onExit={handleExitRequest} />
<SwipeDeck
restaurants={restaurants}
roomId={roomId}
userId={userId}
initialIndex={initialIndex}
matchedRestaurantId={match}
matchType={matchType}
matchLikes={matchLikes}
runnerUps={runnerUps}
likeCounts={likeCounts}
swipeCounts={swipeCounts}
userCount={userCount}
onReset={handleReset}
onNarrow={handleNarrow}
/>
<LeaveConfirmModal
open={showLeaveConfirm}
onConfirm={confirmLeave}
onCancel={cancelLeave}
/>
</div>
);
}