"use client";
import { KeyRound, Trash2 } from "lucide-react";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
CardFooter,
} from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import {
Monitor,
Plus,
RefreshCcw,
Maximize2,
Minimize2,
Settings2,
TwitchIcon,
} from "lucide-react";
import { Badge } from "@/components/ui/badge";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { useDisplays } from "@/hooks/useDisplays";
import { DisplayMode } from "@prisma/client"; // Assuming you have this type from Prisma
import { Display } from "@/lib/types/types";
export default function DisplaysPage() {
const {
displays,
isLoading,
error,
createDisplay,
updateDisplay,
refreshDisplays,
deleteDisplay,
} = useDisplays();
const [isAddDialogOpen, setIsAddDialogOpen] = useState(false);
const [newDisplay, setNewDisplay] = useState({
name: "",
mode: "TWITCH" as DisplayMode,
twitchChannel: "",
});
const [generatedCode, setGeneratedCode] = useState<string | null>(null);
const handleGenerateCode = async (displayId: string) => {
try {
const response = await fetch("/api/displays/auth", {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ displayId }),
});
const { code } = await response.json();
setGeneratedCode(code);
// Auto-clear after 5 minutes
setTimeout(() => setGeneratedCode(null), 5 * 60 * 1000);
} catch (error) {
console.error("Failed to generate code:", error);
}
};
const handleAddDisplay = async () => {
try {
await createDisplay({
name: newDisplay.name,
mode: newDisplay.mode,
twitchChannel:
newDisplay.mode === "TWITCH"
? newDisplay.twitchChannel || "default_channel"
: undefined,
});
setIsAddDialogOpen(false);
setNewDisplay({
name: "",
mode: "TWITCH",
twitchChannel: "",
});
} catch (error) {
console.error("Failed to add display:", error);
}
};
const handleDeleteDisplay = async (id: string) => {
if (confirm("Are you sure you want to delete this display?")) {
try {
await deleteDisplay(id);
} catch (error) {
console.error("Failed to delete display:", error);
}
}
};
const handleRefresh = (id: string) => {
refreshDisplays();
};
const handleUpdateDisplay = (id: string, updates: Partial<Display>) => {
updateDisplay(id, updates);
};
const getTypeIcon = (mode: DisplayMode) => {
switch (mode) {
case "TWITCH":
return <TwitchIcon className="h-5 w-5 text-purple-500" />;
case "PLAYLIST":
return <Settings2 className="h-5 w-5 text-blue-500" />;
case "MENU":
return <Monitor className="h-5 w-5 text-pink-500" />;
default:
return <Monitor className="h-5 w-5 text-purple-500" />;
}
};
if (isLoading)
return (
<div className="flex justify-center items-center h-64">
Loading displays...
</div>
);
if (error)
return (
<div className="text-red-500 p-4">
Error loading displays: {error.message}
</div>
);
return (
<div className="min-h-screen bg-gradient-to-b w-full from-violet-50 via-white to-sky-50">
<div className="max-w-[2400px] mx-auto space-y-8 p-8">
<div className="flex items-center justify-between">
<div>
<h1 className="text-4xl font-bold tracking-tight bg-gradient-to-r from-purple-600 via-pink-500 to-blue-500 bg-clip-text text-transparent">
Display Management
</h1>
<p className="mt-2 text-gray-600">
Configure and control your display screens
</p>
</div>
<Dialog open={isAddDialogOpen} onOpenChange={setIsAddDialogOpen}>
<DialogTrigger asChild>
<Button className="bg-purple-600 hover:bg-purple-700 text-white flex items-center gap-2 rounded-xl">
<Plus className="h-5 w-5" />
Add Display
</Button>
</DialogTrigger>
<DialogContent className="rounded-2xl">
<DialogHeader>
<DialogTitle>Add New Display</DialogTitle>
</DialogHeader>
<div className="space-y-4 py-4">
<div className="space-y-2">
<Label>Display Name</Label>
<Input
className="rounded-xl border-gray-200 focus:ring-purple-500"
placeholder="Enter display name"
value={newDisplay.name}
onChange={(e) =>
setNewDisplay({ ...newDisplay, name: e.target.value })
}
/>
</div>
<div className="space-y-2">
<Label>Display Mode</Label>
<Select
value={newDisplay.mode}
onValueChange={(value: DisplayMode) =>
setNewDisplay({
...newDisplay,
mode: value,
})
}
>
<SelectTrigger className="rounded-xl border-gray-200 focus:ring-purple-500">
<SelectValue placeholder="Select mode" />
</SelectTrigger>
<SelectContent className="rounded-xl">
<SelectItem value="TWITCH">Twitch Stream</SelectItem>
<SelectItem value="PLAYLIST">Playlist</SelectItem>
<SelectItem value="MENU">Menu</SelectItem>
</SelectContent>
</Select>
</div>
{newDisplay.mode === "TWITCH" && (
<div className="space-y-2">
<Label>Twitch Channel</Label>
<Input
className="rounded-xl border-gray-200 focus:ring-purple-500"
placeholder="Enter Twitch channel name"
value={newDisplay.twitchChannel}
onChange={(e) =>
setNewDisplay({
...newDisplay,
twitchChannel: e.target.value,
})
}
/>
</div>
)}
<div className="pt-4 flex justify-end gap-2">
<Button
variant="outline"
className="rounded-xl"
onClick={() => setIsAddDialogOpen(false)}
>
Cancel
</Button>
<Button
className="bg-purple-600 hover:bg-purple-700 text-white rounded-xl"
onClick={handleAddDisplay}
>
Create Display
</Button>
</div>
</div>
</DialogContent>
</Dialog>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
{displays.map((display) => (
<Card
key={display.id}
className="rounded-2xl border-2 border-purple-100/50 hover:border-purple-200 transition-all duration-300 bg-white/70 backdrop-blur-sm overflow-hidden"
>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-4">
<div className="flex items-center gap-3">
{getTypeIcon(display.mode)}
<div>
<CardTitle className="text-xl font-semibold text-gray-900">
{display.name}
</CardTitle>
<CardDescription className="text-sm text-gray-500">
{display.mode.charAt(0) +
display.mode.slice(1).toLowerCase()}{" "}
Display
</CardDescription>
</div>
</div>
<Badge className="rounded-full px-3 py-1 bg-purple-500/10 text-purple-600">
Online
</Badge>
</CardHeader>
<CardContent className="space-y-6">
<Tabs defaultValue="content" className="w-full">
<TabsList className="grid w-full grid-cols-2 rounded-xl bg-gray-100/50">
<TabsTrigger
value="content"
className="rounded-lg data-[state=active]:bg-white data-[state=active]:shadow-sm"
>
Content
</TabsTrigger>
<TabsTrigger
value="settings"
className="rounded-lg data-[state=active]:bg-white data-[state=active]:shadow-sm"
>
Settings
</TabsTrigger>
</TabsList>
<TabsContent value="content" className="space-y-4 mt-4">
<div className="space-y-2">
<Label className="text-sm font-medium text-gray-700">
Display Type
</Label>
<Select
value={display.mode}
onValueChange={(value: DisplayMode) =>
handleUpdateDisplay(display.id, { mode: value })
}
>
<SelectTrigger className="rounded-xl border-gray-200 focus:ring-purple-500">
<SelectValue placeholder="Select type" />
</SelectTrigger>
<SelectContent className="rounded-xl">
<SelectItem value="TWITCH">Twitch Stream</SelectItem>
<SelectItem value="PLAYLIST">Playlist</SelectItem>
<SelectItem value="MENU">Menu</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label className="text-sm font-medium text-gray-700">
Content Source
</Label>
<Input
placeholder="Enter content source..."
value={
display.mode === "TWITCH"
? display.twitchChannel || ""
: ""
}
onChange={(e) => {
if (display.mode === "TWITCH") {
handleUpdateDisplay(display.id, {
twitchChannel: e.target.value,
});
}
}}
className="rounded-xl border-gray-200 focus:ring-purple-500"
/>
</div>
</TabsContent>
<TabsContent value="settings" className="space-y-4 mt-4">
<div className="space-y-4">
{/* Short Code Section */}
<div>
<Label className="text-sm font-medium text-gray-700 mb-2 block">
Display Authentication
</Label>
<div className="flex items-center gap-3">
<Button
onClick={() => handleGenerateCode(display.id)}
className="rounded-xl bg-purple-600 hover:bg-purple-700 text-white flex items-center gap-2"
size="sm"
>
<KeyRound className="h-4 w-4" />
Generate Shortcode
</Button>
{generatedCode && (
<div className="flex-1">
<div className="relative">
<div className="px-4 py-3 rounded-xl border-2 border-purple-200 bg-purple-50 text-purple-800 font-mono font-bold text-center">
{generatedCode}
</div>
</div>
</div>
)}
</div>
<p className="mt-2 text-xs text-gray-500">
Share this code with your display device to
authenticate. Codes expire automatically.
</p>
</div>
{/* Display Name Section */}
<div className="space-y-2">
<Label className="text-sm font-medium text-gray-700">
Display Name
</Label>
<Input
placeholder="Enter display name"
value={display.name}
onChange={(e) =>
handleUpdateDisplay(display.id, {
name: e.target.value,
})
}
className="rounded-xl border-gray-200 focus:ring-purple-500"
/>
</div>
</div>
</TabsContent>
</Tabs>
</CardContent>
<CardFooter className="flex justify-between">
<div className="flex gap-2">
<Button
variant="outline"
size="sm"
onClick={() => handleRefresh(display.id)}
className="rounded-xl border-gray-200 hover:border-purple-200 hover:bg-purple-50 hover:text-purple-600"
>
<RefreshCcw className="h-4 w-4" />
</Button>
<Button
variant="outline"
size="sm"
onClick={() => console.log("Fullscreen:", display.id)}
className="rounded-xl border-gray-200 hover:border-pink-200 hover:bg-pink-50 hover:text-pink-600"
>
<Maximize2 className="h-4 w-4" />
</Button>
<Button
variant="outline"
size="sm"
onClick={() => handleDeleteDisplay(display.id)}
className="rounded-xl border-gray-200 hover:border-red-200 hover:bg-red-50 hover:text-red-600"
>
<Trash2 className="h-4 w-4" />
</Button>
</div>
<Button
className="bg-purple-600 hover:bg-purple-700 text-white rounded-xl"
onClick={() =>
handleUpdateDisplay(display.id, {
name: display.name,
mode: display.mode,
twitchChannel: display.twitchChannel || undefined,
})
}
>
Update
</Button>
</CardFooter>
</Card>
))}
</div>
</div>
</div>
);
}