LiveDisplayX / src / app / admin / displays / page.tsx
page.tsx
Raw
"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>
  );
}