diff --git a/src/tictactoe/Player.tsx b/src/tictactoe/Player.tsx
index 64880f1..faa47f3 100644
--- a/src/tictactoe/Player.tsx
+++ b/src/tictactoe/Player.tsx
@@ -21,6 +21,7 @@ export default function Player({
localStorage.getItem("username") ?? ""
);
const [selectedMode, setSelectedMode] = useState("classic");
+ const [isQueueing, setIsQueueing] = useState(false);
// ------------------------------------------
// CONNECT
@@ -36,8 +37,21 @@ export default function Player({
// MATCHMAKING
// ------------------------------------------
async function startQueue(selectedMode: string) {
- const ticket = await joinMatchmaker(selectedMode);
- console.log("Queued:", ticket);
+ 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(() => {
@@ -148,22 +162,75 @@ export default function Player({
- startQueue(selectedMode)}
- style={{
- padding: "10px 20px",
- borderRadius: "12px",
- background: "#3498db",
- color: "white",
- border: "none",
- marginRight: "10px",
- cursor: "pointer",
- fontWeight: 600,
- }}
- >
- Join Matchmaking
-
+ {!isQueueing && (
+ startQueue(selectedMode)}
+ style={{
+ padding: "10px 20px",
+ borderRadius: "12px",
+ background: "#3498db",
+ color: "white",
+ border: "none",
+ marginRight: "10px",
+ cursor: "pointer",
+ fontWeight: 600,
+ }}
+ >
+ Join Matchmaking
+
+ )}
+
+ {/* Queueing animation */}
+ {isQueueing && (
+
+
+ Finding an opponent…
+
+
+ {/* Animated pulsing dots */}
+
+ ● ● ●
+
+
+ {/* Cancel button */}
+
+
+ )}