Files
BaraBingo/components/AdminDashboard.tsx
SlavaVlad 05677924b5
Some checks failed
Deploy / build-and-deploy (push) Failing after 2m53s
V1 bingo
2026-06-14 21:29:43 +03:00

165 lines
6.4 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useState, useEffect } from "react";
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "./ui/card";
import { Button } from "./ui/button";
import { Input } from "./ui/input";
import { Badge } from "./ui/badge";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, DialogTrigger } from "./ui/dialog";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select";
import { Separator } from "./ui/separator";
type Campaign = {
id: string;
name: string;
gridSize: number;
status: string;
createdAt: string;
};
export function AdminDashboard() {
const [campaigns, setCampaigns] = useState<Campaign[]>([]);
const [loading, setLoading] = useState(true);
const [newName, setNewName] = useState("");
const [newGridSize, setNewGridSize] = useState("5");
const [creating, setCreating] = useState(false);
const [dialogOpen, setDialogOpen] = useState(false);
const fetchCampaigns = async () => {
const res = await fetch("/api/campaigns");
const data = await res.json();
setCampaigns(data);
setLoading(false);
};
useEffect(() => { fetchCampaigns(); }, []);
const createCampaign = async () => {
if (!newName) return;
setCreating(true);
await fetch("/api/campaigns", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: newName, gridSize: parseInt(newGridSize) }),
});
setNewName("");
setDialogOpen(false);
await fetchCampaigns();
setCreating(false);
};
const deleteCampaign = async (id: string) => {
await fetch(`/api/campaigns/${id}`, { method: "DELETE" });
await fetchCampaigns();
};
const statusColors: Record<string, "success" | "warning" | "secondary"> = {
active: "success",
completed: "warning",
archived: "secondary",
};
return (
<div className="space-y-6 w-full max-w-2xl mx-auto">
<div className="flex items-center justify-between">
<div>
<h2 className="text-xl font-mono text-cyan-300 uppercase tracking-wider"> Command Center</h2>
<p className="text-xs text-slate-500 font-mono">Admin terminal v1.0</p>
</div>
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
<DialogTrigger asChild>
<Button className="font-mono text-xs">
+ NEW CAMPAIGN
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle className="font-mono text-cyan-300">New Campaign</DialogTitle>
<DialogDescription className="font-mono text-xs text-slate-500">
Deploy a new bingo operation
</DialogDescription>
</DialogHeader>
<div className="space-y-4 py-4">
<div className="space-y-2">
<label className="text-xs font-mono text-slate-500 uppercase">Mission Name</label>
<Input
placeholder='e.g. "Traitor Hunt #42"'
value={newName}
onChange={e => setNewName(e.target.value)}
className="font-mono"
/>
</div>
<div className="space-y-2">
<label className="text-xs font-mono text-slate-500 uppercase">Grid Size</label>
<Select value={newGridSize} onValueChange={setNewGridSize}>
<SelectTrigger className="font-mono">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="3">3×3 (Quick)</SelectItem>
<SelectItem value="4">4×4</SelectItem>
<SelectItem value="5">5×5 (Classic)</SelectItem>
<SelectItem value="6">6×6 (Chaotic)</SelectItem>
<SelectItem value="7">7×7 (Meltdown)</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => setDialogOpen(false)} className="font-mono">Cancel</Button>
<Button onClick={createCampaign} disabled={creating || !newName} className="font-mono">
{creating ? "DEPLOYING..." : "▶ DEPLOY"}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
<Separator />
{loading ? (
<div className="text-center text-slate-500 font-mono text-sm py-8">
Loading submarine manifests...
</div>
) : campaigns.length === 0 ? (
<Card className="border-dashed border-slate-700/30">
<CardContent className="py-12 text-center">
<div className="text-4xl mb-3">🗺💀</div>
<p className="text-slate-500 font-mono text-sm">No campaigns deployed</p>
<p className="text-slate-600 font-mono text-xs mt-1">Click &ldquo;New Campaign&rdquo; to start the chaos</p>
</CardContent>
</Card>
) : (
<div className="space-y-2">
{campaigns.map(c => (
<Card key={c.id} className="border-slate-700/30 hover:border-cyan-800/30 transition-colors">
<CardContent className="p-4 flex items-center justify-between">
<div>
<div className="flex items-center gap-2">
<span className="font-mono text-sm text-slate-200">{c.name}</span>
<Badge variant={statusColors[c.status] || "secondary"} className="text-[9px] uppercase">
{c.status}
</Badge>
</div>
<div className="flex gap-3 mt-1 text-[10px] text-slate-600 font-mono">
<span>{c.gridSize}×{c.gridSize}</span>
<span>{new Date(c.createdAt).toLocaleDateString()}</span>
</div>
</div>
<div className="flex gap-2">
<a href={`/admin/campaigns/${c.id}`}>
<Button variant="ghost" size="sm" className="text-xs font-mono">EDIT</Button>
</a>
<Button variant="destructive" size="sm" className="text-xs font-mono" onClick={() => deleteCampaign(c.id)}>
DEL
</Button>
</div>
</CardContent>
</Card>
))}
</div>
)}
</div>
);
}