Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
219 lines
6.7 KiB
TypeScript
219 lines
6.7 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import { cn } from "@/lib/utils";
|
|
import type { PokerSnapshot } from "@/app/(frontend)/minigames/poker/actions";
|
|
import { Badge } from "@/components/ui/badge";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
|
|
import { Skeleton } from "@/components/ui/skeleton";
|
|
import { PokerGameTable } from "./PokerGameTable";
|
|
import { RenameTableDialog } from "./RenameTableDialog";
|
|
import { seatPosition } from "./seatPosition";
|
|
|
|
interface PokerTableProps {
|
|
readonly snapshot: PokerSnapshot | null;
|
|
readonly currentUserId: number | string;
|
|
readonly loading: boolean;
|
|
readonly tableError: string | null;
|
|
readonly onSnapshot: (snapshot: PokerSnapshot) => void;
|
|
readonly onJoin: () => Promise<void>;
|
|
readonly onStart: () => Promise<void>;
|
|
readonly onLeave: () => Promise<void>;
|
|
readonly onBack: () => void;
|
|
}
|
|
|
|
type SeatedSnapshot = PokerSnapshot & {
|
|
readonly you: NonNullable<PokerSnapshot["you"]>;
|
|
};
|
|
|
|
function isSeatedSnapshot(snapshot: PokerSnapshot): snapshot is SeatedSnapshot {
|
|
return snapshot.you !== null;
|
|
}
|
|
|
|
function WaitingTable({
|
|
snapshot,
|
|
currentUserId,
|
|
onSnapshot,
|
|
onJoin,
|
|
onStart,
|
|
onLeave,
|
|
onBack,
|
|
}: {
|
|
readonly snapshot: PokerSnapshot;
|
|
readonly currentUserId: number | string;
|
|
readonly onSnapshot: (snapshot: PokerSnapshot) => void;
|
|
readonly onJoin: () => Promise<void>;
|
|
readonly onStart: () => Promise<void>;
|
|
readonly onLeave: () => Promise<void>;
|
|
readonly onBack: () => void;
|
|
}) {
|
|
const [starting, setStarting] = useState(false);
|
|
const [leaving, setLeaving] = useState(false);
|
|
const viewerSeat = snapshot.seats.findIndex((id) => String(id) === String(currentUserId));
|
|
const seated = viewerSeat >= 0;
|
|
const full = snapshot.seats.length > 0 && snapshot.seats.every((id) => id !== null);
|
|
const isHost = seated && snapshot.hostSeatId === viewerSeat;
|
|
|
|
const start = async () => {
|
|
setStarting(true);
|
|
await onStart();
|
|
setStarting(false);
|
|
};
|
|
|
|
const leave = async () => {
|
|
setLeaving(true);
|
|
await onLeave();
|
|
setLeaving(false);
|
|
};
|
|
|
|
return (
|
|
<Card className="rounded-none">
|
|
<CardHeader className="flex-row items-start justify-between gap-4 border-b p-4">
|
|
<div className="flex flex-col gap-1">
|
|
<div className="flex items-center gap-1">
|
|
<CardTitle className="text-base">
|
|
{snapshot.name ?? `Table ${snapshot.tableId}`}
|
|
</CardTitle>
|
|
{isHost && (
|
|
<RenameTableDialog
|
|
tableId={snapshot.tableId}
|
|
currentName={snapshot.name}
|
|
onSnapshot={onSnapshot}
|
|
/>
|
|
)}
|
|
</div>
|
|
<p className="text-sm text-muted-foreground">
|
|
{seated
|
|
? "You have a seat reserved."
|
|
: "Join an open seat to receive a private hand view."}
|
|
</p>
|
|
</div>
|
|
<Badge variant="outline">Waiting room</Badge>
|
|
</CardHeader>
|
|
<CardContent className="p-4">
|
|
<div className="poker-felt-table relative min-h-[28rem] overflow-hidden p-4 sm:min-h-[34rem]">
|
|
{snapshot.seats.map((userId, seatId) => (
|
|
<div
|
|
key={seatId}
|
|
className={cn(
|
|
"poker-waiting-seat absolute flex flex-col gap-1 border px-3 py-2 text-xs",
|
|
seatPosition(seatId),
|
|
userId !== null && "poker-waiting-seat-occupied",
|
|
)}
|
|
>
|
|
<span className="font-semibold">Seat {seatId + 1}</span>
|
|
<span className="text-white/70">
|
|
{userId === null
|
|
? "Open seat"
|
|
: String(userId) === String(currentUserId)
|
|
? "You"
|
|
: "Occupied"}
|
|
</span>
|
|
</div>
|
|
))}
|
|
<div className="absolute left-1/2 top-1/2 flex -translate-x-1/2 -translate-y-1/2 flex-col items-center gap-2 text-center text-white">
|
|
<span className="font-mono text-xs uppercase tracking-[0.2em] text-white/60">
|
|
Polaris Hold'em
|
|
</span>
|
|
<span className="text-sm text-white/80">
|
|
{seated ? "Waiting for the host to start" : "Open seating"}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
<CardFooter className="flex flex-wrap justify-end gap-2 border-t p-4">
|
|
{seated ? (
|
|
<>
|
|
{isHost && (
|
|
<Button type="button" onClick={() => void start()} disabled={starting}>
|
|
{starting ? "Starting..." : "Start game"}
|
|
</Button>
|
|
)}
|
|
<Button type="button" variant="outline" onClick={() => void leave()} disabled={leaving}>
|
|
{leaving ? "Leaving..." : "Leave table"}
|
|
</Button>
|
|
</>
|
|
) : (
|
|
<>
|
|
<Button type="button" onClick={() => void onJoin()} disabled={full}>
|
|
{full ? "Table full" : "Join an open seat"}
|
|
</Button>
|
|
<Button type="button" variant="outline" onClick={onBack}>
|
|
Back to lobby
|
|
</Button>
|
|
</>
|
|
)}
|
|
</CardFooter>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
export function PokerTable({
|
|
snapshot,
|
|
currentUserId,
|
|
loading,
|
|
tableError,
|
|
onSnapshot,
|
|
onJoin,
|
|
onStart,
|
|
onLeave,
|
|
onBack,
|
|
}: PokerTableProps) {
|
|
if (loading && !snapshot) {
|
|
return (
|
|
<Card className="rounded-none">
|
|
<CardHeader className="gap-3 p-4">
|
|
<Skeleton className="h-5 w-40" />
|
|
<Skeleton className="h-4 w-64" />
|
|
</CardHeader>
|
|
<CardContent className="p-4 pt-0">
|
|
<Skeleton className="h-[28rem] w-full" />
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
if (!snapshot) {
|
|
return (
|
|
<Card className="rounded-none">
|
|
<CardHeader className="p-4">
|
|
<CardTitle className="text-base">Table unavailable</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="p-4 pt-0">
|
|
<p className="text-sm text-destructive">
|
|
{tableError ?? "This table could not be loaded."}
|
|
</p>
|
|
</CardContent>
|
|
<CardFooter className="border-t p-4">
|
|
<Button type="button" variant="outline" onClick={onBack}>
|
|
Back to lobby
|
|
</Button>
|
|
</CardFooter>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
if (!isSeatedSnapshot(snapshot)) {
|
|
return (
|
|
<WaitingTable
|
|
snapshot={snapshot}
|
|
currentUserId={currentUserId}
|
|
onSnapshot={onSnapshot}
|
|
onJoin={onJoin}
|
|
onStart={onStart}
|
|
onLeave={onLeave}
|
|
onBack={onBack}
|
|
/>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<PokerGameTable
|
|
snapshot={snapshot}
|
|
onSnapshot={onSnapshot}
|
|
onStart={onStart}
|
|
onLeave={onLeave}
|
|
/>
|
|
);
|
|
}
|