"use client"; import { AnimatePresence, motion } from "framer-motion"; import { Bot, Copy, UserPlus, X } from "lucide-react"; import { useEffect, useState } from "react"; import { ScreenHeader, ScreenShell } from "@/components/online/ScreenHeader"; import { useGameStore } from "@/lib/game-store"; import { useOnlineStore } from "@/lib/online-store"; import { useUIStore } from "@/lib/ui-store"; import { useI18n } from "@/lib/i18n"; import { getService } from "@/lib/online/service"; import { Friend, PresenceStatus, RoomSeat } from "@/lib/online/types"; import { Avatar } from "@/components/online/Avatar"; import { cn } from "@/lib/cn"; const STATUS_COLOR: Record = { online: "bg-teal-400", offline: "bg-slate-500", "in-game": "bg-gold-400", }; // online first, then in-game, then offline const statusRank = (s: PresenceStatus) => (s === "online" ? 0 : s === "in-game" ? 1 : 2); export function RoomScreen() { const { t } = useI18n(); const room = useOnlineStore((s) => s.room); const friends = useOnlineStore((s) => s.friends); const loadFriends = useOnlineStore((s) => s.loadFriends); const setPartner = useOnlineStore((s) => s.setPartner); const inviteToSeat = useOnlineStore((s) => s.inviteToSeat); const addBot = useOnlineStore((s) => s.addBot); const clearSeat = useOnlineStore((s) => s.clearSeat); const startRoom = useOnlineStore((s) => s.startRoom); const leaveRoom = useOnlineStore((s) => s.leaveRoom); const newOnlineMatch = useGameStore((s) => s.newOnlineMatch); const enterServerMatch = useGameStore((s) => s.enterServerMatch); const goGame = useUIStore((s) => s.goGame); const go = useUIStore((s) => s.go); const [picker, setPicker] = useState(null); const [copied, setCopied] = useState(false); useEffect(() => { loadFriends(); }, [loadFriends]); // Live: when the host starts, the server sends matchFound to every human seat // (host + accepted friends) โ†’ each device auto-enters the server-run game. useEffect(() => { const svc = getService(); if (!svc.live) return; const unsub = svc.onMatchmaking((s) => { if (s.phase === "ready") { enterServerMatch(svc); goGame("home"); } }); return unsub; }, [enterServerMatch, goGame]); if (!room) return null; const hasPending = room.seats.some((s) => s.kind === "invited"); const seat = (n: number) => room.seats.find((s) => s.seat === n)!; const statusLabel = (s: PresenceStatus) => s === "online" ? t("friends.online") : s === "in-game" ? t("friends.inGame") : t("friends.offline"); const pick = async (friend: Friend) => { if (!picker) return; if (picker.seat === 2) await setPartner(friend.id); else await inviteToSeat(picker.seat, friend.id); setPicker(null); }; const copyCode = async () => { try { await navigator.clipboard.writeText(room.code); setCopied(true); setTimeout(() => setCopied(false), 1500); } catch { /* ignore */ } }; const start = async () => { if (hasPending) return; // never start while a friend's invite is still pending if (getService().live) { // Server runs the match; it pushes matchFound โ†’ the effect above enters it. await startRoom(); return; } // Offline mock: build a client-run match from the (bot-filled) seats. await startRoom(); const r = useOnlineStore.getState().room!; const players = r.seats .slice() .sort((a, b) => a.seat - b.seat) .map((s) => ({ displayName: s.player!.displayName, avatar: s.player!.avatar, level: s.player!.level, })); newOnlineMatch({ players, targetScore: r.targetScore, stake: r.stake, ranked: r.ranked }); goGame("home"); }; const leave = async () => { await leaveRoom(); go("online"); }; return ( {copied ? t("common.copied") : room.code} } /> {/* teams: stacked on phones, side-by-side in landscape so all 4 seats fit */}

{t("team.us")}

{}} onBot={() => {}} onClear={() => {}} /> setPicker({ seat: 2 })} onBot={() => addBot(2)} onClear={() => clearSeat(2)} />

{t("room.opponents")}

setPicker({ seat: 1 })} onBot={() => addBot(1)} onClear={() => clearSeat(1)} /> setPicker({ seat: 3 })} onBot={() => addBot(3)} onClear={() => clearSeat(3)} />
{hasPending && (

{t("room.waitAccept")}

)}
{/* friend picker */} {picker && ( setPicker(null)} className="fixed inset-0 z-50 flex items-end sm:items-center justify-center bg-navy-950/80 backdrop-blur-sm p-4" > e.stopPropagation()} className="panel rounded-3xl p-5 w-full max-w-sm max-h-[70vh] overflow-y-auto" >

{t("room.pickFriend")}

{t("room.inviteHint")}

{friends.length === 0 && (

{t("friends.empty")}

)} {[...friends] .sort((a, b) => statusRank(a.status) - statusRank(b.status)) .map((f) => { const inGame = f.status === "in-game"; return ( ); })}
)}
); } function SeatCard({ seat, role, onInvite, onBot, onClear, }: { seat: RoomSeat; role: "you" | "partner" | "opp"; onInvite: () => void; onBot: () => void; onClear: () => void; }) { const { t } = useI18n(); const filled = seat.kind !== "empty"; const label = role === "you" ? t("seat.you") : role === "partner" ? t("room.partner") : t("room.opponents"); return (
{label} {filled ? ( <> {seat.player?.displayName} {seat.kind === "bot" && ๐Ÿค–} {seat.kind === "invited" ? (
{t("room.waiting")}
) : ( role !== "you" && ( ) )} ) : (
)}
); }