283 lines
8.6 KiB
TypeScript
283 lines
8.6 KiB
TypeScript
import React, { useEffect, useState } from "react";
|
|
import { motion, AnimatePresence } from "framer-motion";
|
|
import Leaderboard from "./Leaderboard";
|
|
import { useNakama } from "./providers/NakamaProvider";
|
|
|
|
export default function Player({
|
|
onMatchDataCallback,
|
|
}: {
|
|
onMatchDataCallback: (msg: any) => void;
|
|
}) {
|
|
const {
|
|
session,
|
|
matchId,
|
|
loginOrRegister,
|
|
logout,
|
|
onMatchData,
|
|
joinMatchmaker,
|
|
} = useNakama();
|
|
|
|
const [username, setUsername] = useState(
|
|
localStorage.getItem("username") ?? ""
|
|
);
|
|
const [selectedMode, setSelectedMode] = useState("classic");
|
|
const [isQueueing, setIsQueueing] = useState(false);
|
|
const isRegistered = localStorage.getItem("registered") === "yes";
|
|
|
|
// ------------------------------------------
|
|
// CONNECT
|
|
// ------------------------------------------
|
|
async function handleConnect() {
|
|
await loginOrRegister(username);
|
|
|
|
// Match data listener
|
|
onMatchData(onMatchDataCallback);
|
|
}
|
|
|
|
// ------------------------------------------
|
|
// MATCHMAKING
|
|
// ------------------------------------------
|
|
async function startQueue(selectedMode: string) {
|
|
setIsQueueing(true);
|
|
|
|
try {
|
|
const ticket = await joinMatchmaker(selectedMode);
|
|
console.log("Queued:", ticket);
|
|
} catch (err) {
|
|
console.error("Matchmaking failed:", err);
|
|
setIsQueueing(false);
|
|
}
|
|
}
|
|
|
|
function cancelQueue() {
|
|
setIsQueueing(false);
|
|
// Nakama matchmaker tickets auto-expire by default in your setup.
|
|
// If you later add manual ticket cancel RPC, call it here.
|
|
}
|
|
|
|
useEffect(() => {
|
|
handleConnect();
|
|
}, []);
|
|
|
|
return (
|
|
<div style={{ marginBottom: "20px" }}>
|
|
<AnimatePresence mode="wait">
|
|
{/* ---------------- LOGIN SCREEN ---------------- */}
|
|
{!session && (
|
|
<motion.div
|
|
key="login"
|
|
initial={{ opacity: 0, y: 20, scale: 0.97 }}
|
|
animate={{ opacity: 1, y: 0, scale: 1 }}
|
|
exit={{ opacity: 0, y: -20, scale: 0.97 }}
|
|
transition={{ duration: 0.4, ease: "easeOut" }}
|
|
style={{
|
|
background: "#111",
|
|
padding: "24px",
|
|
borderRadius: "16px",
|
|
width: "280px",
|
|
margin: "0 auto",
|
|
color: "white",
|
|
boxShadow:
|
|
"0 4px 16px rgba(0,0,0,0.4), inset 0 0 20px rgba(255,255,255,0.03)",
|
|
textAlign: "center",
|
|
}}
|
|
>
|
|
<h2 style={{ marginBottom: "16px" }}>Welcome!</h2>
|
|
|
|
<input
|
|
placeholder="Enter username"
|
|
value={username}
|
|
disabled={isRegistered}
|
|
onChange={(e) => setUsername(e.target.value)}
|
|
style={{
|
|
padding: "10px",
|
|
width: "100%",
|
|
borderRadius: "12px",
|
|
background: "#222",
|
|
color: "white",
|
|
border: "1px solid #333",
|
|
marginBottom: "12px",
|
|
fontSize: "14px",
|
|
}}
|
|
/>
|
|
|
|
<motion.button
|
|
whileTap={{ scale: 0.95 }}
|
|
onClick={handleConnect}
|
|
disabled={username.length === 0}
|
|
style={{
|
|
width: "100%",
|
|
padding: "10px",
|
|
borderRadius: "12px",
|
|
background: username.length ? "#2ecc71" : "#444",
|
|
border: "none",
|
|
cursor: username.length ? "pointer" : "not-allowed",
|
|
color: "white",
|
|
fontWeight: 600,
|
|
fontSize: "14px",
|
|
}}
|
|
>
|
|
Connect
|
|
</motion.button>
|
|
</motion.div>
|
|
)}
|
|
|
|
{/* ---------------- LOBBY SCREEN ---------------- */}
|
|
{session && !matchId && (
|
|
<motion.div
|
|
key="lobby"
|
|
initial={{ opacity: 0, y: 40 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
exit={{ opacity: 0, y: -40 }}
|
|
transition={{ duration: 0.45, ease: "easeOut" }}
|
|
style={{
|
|
padding: "20px",
|
|
background: "#0f0f0f",
|
|
borderRadius: "20px",
|
|
color: "white",
|
|
textAlign: "center",
|
|
}}
|
|
>
|
|
<h2 style={{ marginBottom: "10px" }}>
|
|
Hello, <span style={{ color: "#2ecc71" }}>{session.username}</span>
|
|
</h2>
|
|
|
|
<label style={{ display: "block", marginTop: "10px", opacity: 0.7 }}>
|
|
Select Game Mode
|
|
</label>
|
|
|
|
<select
|
|
value={selectedMode}
|
|
disabled={isQueueing}
|
|
onChange={(e) => setSelectedMode(e.target.value)}
|
|
style={{
|
|
padding: "8px",
|
|
margin: "10px 0 16px",
|
|
width: "60%",
|
|
borderRadius: "10px",
|
|
background: "#222",
|
|
color: "white",
|
|
border: "1px solid #333",
|
|
}}
|
|
>
|
|
<option value="classic">Classic</option>
|
|
<option value="blitz">Blitz</option>
|
|
</select>
|
|
|
|
{!isQueueing && (
|
|
<motion.button
|
|
whileTap={{ scale: 0.95 }}
|
|
onClick={() => startQueue(selectedMode)}
|
|
style={{
|
|
padding: "10px 20px",
|
|
borderRadius: "12px",
|
|
background: "#3498db",
|
|
color: "white",
|
|
border: "none",
|
|
marginRight: "10px",
|
|
cursor: "pointer",
|
|
fontWeight: 600,
|
|
}}
|
|
>
|
|
Join Matchmaking
|
|
</motion.button>
|
|
)}
|
|
|
|
{/* Queueing animation */}
|
|
{isQueueing && (
|
|
<motion.div
|
|
initial={{ opacity: 0, scale: 0.95 }}
|
|
animate={{ opacity: 1, scale: 1 }}
|
|
transition={{ duration: 0.3 }}
|
|
style={{
|
|
marginTop: "10px",
|
|
marginBottom: "10px",
|
|
padding: "12px 16px",
|
|
borderRadius: "12px",
|
|
background: "#222",
|
|
color: "white",
|
|
display: "inline-block",
|
|
fontSize: "14px",
|
|
border: "1px solid #333",
|
|
}}
|
|
>
|
|
<div style={{ marginBottom: "6px", fontWeight: 600 }}>
|
|
Finding an opponent…
|
|
</div>
|
|
|
|
{/* Animated pulsing dots */}
|
|
<motion.div
|
|
animate={{ opacity: [0.3, 1, 0.3] }}
|
|
transition={{ duration: 1.2, repeat: Infinity }}
|
|
style={{ letterSpacing: "2px", fontSize: "18px" }}
|
|
>
|
|
● ● ●
|
|
</motion.div>
|
|
|
|
{/* Cancel button */}
|
|
<button
|
|
onClick={cancelQueue}
|
|
style={{
|
|
marginTop: "10px",
|
|
padding: "6px 12px",
|
|
borderRadius: "8px",
|
|
background: "#e74c3c",
|
|
color: "white",
|
|
border: "none",
|
|
cursor: "pointer",
|
|
fontSize: "12px",
|
|
fontWeight: 600,
|
|
}}
|
|
>
|
|
Cancel
|
|
</button>
|
|
</motion.div>
|
|
)}
|
|
|
|
<div style={{ marginTop: "24px" }}>
|
|
<Leaderboard />
|
|
</div>
|
|
|
|
<motion.button
|
|
whileTap={{ scale: 0.95 }}
|
|
onClick={logout}
|
|
style={{
|
|
padding: "10px 20px",
|
|
borderRadius: "12px",
|
|
background: "#e74c3c",
|
|
color: "white",
|
|
border: "none",
|
|
cursor: "pointer",
|
|
fontWeight: 600,
|
|
}}
|
|
>
|
|
Logout
|
|
</motion.button>
|
|
|
|
</motion.div>
|
|
)}
|
|
|
|
{/* ---------------- MATCH SCREEN ---------------- */}
|
|
{session && matchId && (
|
|
<motion.div
|
|
key="match"
|
|
initial={{ opacity: 0, scale: 0.9, y: 30 }}
|
|
animate={{ opacity: 1, scale: 1, y: 0 }}
|
|
exit={{ opacity: 0, scale: 0.9, y: -20 }}
|
|
transition={{ duration: 0.35, ease: "easeOut" }}
|
|
style={{
|
|
padding: "20px",
|
|
color: "white",
|
|
textAlign: "center",
|
|
}}
|
|
>
|
|
<h2 style={{ marginBottom: "10px" }}>
|
|
Go, <span style={{ color: "#2ecc71" }}>{session.username}</span>
|
|
</h2>
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
</div>
|
|
);
|
|
}
|