Add Democratic fundraising platform.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
125
src/components/casino/CoinFlipRoom.tsx
Normal file
125
src/components/casino/CoinFlipRoom.tsx
Normal file
@@ -0,0 +1,125 @@
|
||||
"use client";
|
||||
import { creditTicker } from "@/lib/credits-brand";
|
||||
import { useState, useEffect } from "react";
|
||||
import { io, Socket } from "socket.io-client";
|
||||
|
||||
const T = creditTicker();
|
||||
|
||||
interface Room { id: string; wageBLW: number; creatorId: string; }
|
||||
|
||||
export function CoinFlipRoom({ userId, balance }: { userId: string; balance: number }) {
|
||||
const [rooms, setRooms] = useState<Room[]>([]);
|
||||
const [wager, setWager] = useState(50);
|
||||
const [phase, setPhase] = useState<"lobby" | "waiting" | "result">("lobby");
|
||||
const [socket, setSocket] = useState<Socket | null>(null);
|
||||
const [result, setResult] = useState<{ resultLabel: string; winnerId: string; payout: number; serverSeed: string } | null>(null);
|
||||
const [myRoomId, setMyRoomId] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
fetchRooms();
|
||||
}, []);
|
||||
|
||||
async function fetchRooms() {
|
||||
const r = await fetch("/api/games/rooms?gameType=COIN_FLIP");
|
||||
if (r.ok) { const d = await r.json(); setRooms(d.rooms); }
|
||||
}
|
||||
|
||||
function connect(roomId: string) {
|
||||
const s = io("/coin-flip", { path: "/api/socket" });
|
||||
setSocket(s);
|
||||
s.emit("join_room", { roomId, userId });
|
||||
s.on("waiting", () => setPhase("waiting"));
|
||||
s.on("game_start", () => setPhase("waiting"));
|
||||
s.on("result", (data) => { setResult(data); setPhase("result"); s.disconnect(); });
|
||||
s.on("error", (msg: string) => { alert(msg); s.disconnect(); setPhase("lobby"); });
|
||||
}
|
||||
|
||||
async function createRoom() {
|
||||
const res = await fetch("/api/games/rooms", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ gameType: "COIN_FLIP", wageBLW: wager }),
|
||||
});
|
||||
if (!res.ok) { const e = await res.json(); alert(e.error); return; }
|
||||
const { room } = await res.json();
|
||||
setMyRoomId(room.id);
|
||||
connect(room.id);
|
||||
setPhase("waiting");
|
||||
}
|
||||
|
||||
if (phase === "waiting") {
|
||||
return (
|
||||
<div className="rounded-2xl border border-white/10 bg-white/5 p-8 text-center space-y-4">
|
||||
<div className="text-5xl animate-spin">🪙</div>
|
||||
<p className="text-white font-semibold">Waiting for opponent…</p>
|
||||
<p className="text-slate-400 text-sm">Room ID: <span className="font-mono text-sky-300">{myRoomId}</span></p>
|
||||
<p className="text-slate-500 text-xs">Share this with a friend to flip instantly!</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (phase === "result" && result) {
|
||||
const won = result.winnerId === userId;
|
||||
return (
|
||||
<div className={`rounded-2xl border p-8 text-center space-y-4 ${won ? "border-green-500/50 bg-green-900/20" : "border-red-500/50 bg-red-900/20"}`}>
|
||||
<div className="text-6xl">{result.resultLabel === "heads" ? "🦅" : "🔵"}</div>
|
||||
<p className="text-2xl font-bold text-white">{result.resultLabel.toUpperCase()}</p>
|
||||
<p className={`font-semibold text-lg ${won ? "text-green-300" : "text-red-300"}`}>
|
||||
{won ? `You win! +${result.payout} ${T}` : "You lose!"}
|
||||
</p>
|
||||
<p className="text-xs text-slate-500 break-all">Seed: {result.serverSeed}</p>
|
||||
<button onClick={() => { setPhase("lobby"); setResult(null); fetchRooms(); }}
|
||||
className="rounded-xl bg-gradient-to-r from-sky-500 to-indigo-500 px-6 py-2 font-semibold text-white">
|
||||
Back to Lobby
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-5">
|
||||
<div className="rounded-xl border border-white/10 bg-white/5 p-4 space-y-3">
|
||||
<p className="text-sm font-semibold text-white">Create a Room</p>
|
||||
<div className="flex gap-3">
|
||||
<div className="flex-1">
|
||||
<label className="block text-xs text-slate-400 mb-1">Wager ({T})</label>
|
||||
<input type="number" min={1} max={balance} value={wager}
|
||||
onChange={e => setWager(Math.max(1, parseInt(e.target.value) || 1))}
|
||||
className="w-full rounded-lg border border-white/10 bg-white/5 px-3 py-2 text-white focus:outline-none focus:border-sky-500" />
|
||||
</div>
|
||||
<div className="flex items-end">
|
||||
<button onClick={createRoom} disabled={wager > balance}
|
||||
className="rounded-xl bg-gradient-to-r from-sky-500 to-indigo-500 px-5 py-2 font-semibold text-white disabled:opacity-50 hover:opacity-90">
|
||||
Create
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-sm font-semibold text-white">Open Rooms</p>
|
||||
<button onClick={fetchRooms} className="text-xs text-sky-400 hover:text-sky-300">Refresh</button>
|
||||
</div>
|
||||
{rooms.length === 0 ? (
|
||||
<p className="text-center text-slate-500 py-8">No open rooms — create one!</p>
|
||||
) : (
|
||||
rooms.map(room => (
|
||||
<div key={room.id} className="flex items-center justify-between rounded-xl border border-white/10 bg-white/5 px-4 py-3">
|
||||
<div>
|
||||
<p className="text-white font-medium">{room.wageBLW} {T}</p>
|
||||
<p className="text-xs text-slate-500 font-mono">{room.id.slice(0, 8)}…</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => connect(room.id)}
|
||||
disabled={room.creatorId === userId || balance < room.wageBLW}
|
||||
className="rounded-xl bg-indigo-600 px-4 py-1.5 text-sm font-semibold text-white hover:bg-indigo-500 disabled:opacity-50">
|
||||
Join
|
||||
</button>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user