Engine, felt, controls, sounds, card skins, and dev popups; SSE events in the realtime hook, nav and command palette entries, stats migration, and the six blackjack test suites. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
107 lines
3 KiB
TypeScript
107 lines
3 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import { Pencil } from "lucide-react";
|
|
import { MAX_TABLE_NAME_LENGTH } from "@/lib/minigames/blackjack/constants";
|
|
import {
|
|
renameBlackjackTable,
|
|
type BlackjackSnapshot,
|
|
} from "@/app/(frontend)/minigames/blackjack/actions";
|
|
import { Button } from "@/components/ui/button";
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
DialogTrigger,
|
|
} from "@/components/ui/dialog";
|
|
import { Input } from "@/components/ui/input";
|
|
|
|
interface RenameDialogGuardProps {
|
|
readonly canRename: boolean;
|
|
readonly tableId: string;
|
|
readonly currentName: string | null;
|
|
readonly onSnapshot: (snapshot: BlackjackSnapshot) => void;
|
|
}
|
|
|
|
export function RenameDialogGuard({
|
|
canRename,
|
|
tableId,
|
|
currentName,
|
|
onSnapshot,
|
|
}: RenameDialogGuardProps) {
|
|
const [open, setOpen] = useState(false);
|
|
const [value, setValue] = useState(currentName ?? "");
|
|
const [saving, setSaving] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
if (!canRename) return null;
|
|
|
|
const save = async () => {
|
|
setSaving(true);
|
|
setError(null);
|
|
try {
|
|
const result = await renameBlackjackTable(tableId, value);
|
|
if (!result.success || !result.data) {
|
|
setError(result.error ?? "Could not rename the table.");
|
|
return;
|
|
}
|
|
onSnapshot(result.data);
|
|
setOpen(false);
|
|
} catch (caught) {
|
|
setError(caught instanceof Error ? caught.message : "Could not rename the table.");
|
|
} finally {
|
|
setSaving(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Dialog
|
|
open={open}
|
|
onOpenChange={(next) => {
|
|
if (next) setValue(currentName ?? "");
|
|
setOpen(next);
|
|
}}
|
|
>
|
|
<DialogTrigger asChild>
|
|
<Button
|
|
type="button"
|
|
variant="ghost"
|
|
size="icon"
|
|
aria-label="Rename table"
|
|
title="Rename table"
|
|
>
|
|
<Pencil className="size-4" />
|
|
</Button>
|
|
</DialogTrigger>
|
|
<DialogContent className="max-w-sm rounded-none">
|
|
<DialogHeader>
|
|
<DialogTitle>Name this table</DialogTitle>
|
|
<DialogDescription>
|
|
The name shows in the lobby and at the table. Share links stay the same.
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
<Input
|
|
value={value}
|
|
onChange={(event) => setValue(event.target.value.slice(0, MAX_TABLE_NAME_LENGTH))}
|
|
placeholder="Friday night blackjack"
|
|
maxLength={MAX_TABLE_NAME_LENGTH}
|
|
aria-label="Table name"
|
|
onKeyDown={(event) => {
|
|
if (event.key === "Enter") void save();
|
|
}}
|
|
/>
|
|
{error && <p className="text-sm text-destructive">{error}</p>}
|
|
<DialogFooter>
|
|
<Button type="button" variant="outline" onClick={() => setOpen(false)}>
|
|
Cancel
|
|
</Button>
|
|
<Button type="button" onClick={() => void save()} disabled={saving}>
|
|
{saving ? "Saving..." : "Save name"}
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
);
|
|
}
|