"use client"; import { IconCheck, IconChevronDown, IconCopy, IconExternalLink, IconInfoCircle, IconRefresh, IconTrash, } from "@tabler/icons-react"; import { AnimatePresence, motion } from "motion/react"; import { useState } from "react"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardTitle, } from "@/components/ui/card"; import { Collapsible, CollapsibleContent, CollapsibleTrigger, } from "@/components/ui/collapsible"; import { Input } from "@/components/ui/input"; import { Switch } from "@/components/ui/switch"; import { Tooltip, TooltipContent, TooltipTrigger, } from "@/components/ui/tooltip"; import { JellyfinIcon, PlexIcon } from "./icons"; export interface WebhookConnection { id: string; provider: "plex" | "jellyfin"; token: string; mediaServerUsername: string; enabled: boolean; lastEventAt: string | null; recentEvents: { id: string; eventType: string | null; mediaType: string | null; mediaTitle: string | null; status: "success" | "ignored" | "error"; receivedAt: string; }[]; } export function timeAgo(dateStr: string): string { const diff = Date.now() - new Date(dateStr).getTime(); const mins = Math.floor(diff / 60000); if (mins < 1) return "just now"; if (mins < 60) return `${mins}m ago`; const hours = Math.floor(mins / 60); if (hours < 24) return `${hours}h ago`; const days = Math.floor(hours / 24); return `${days}d ago`; } export function WebhookCard({ provider, connection, onSave, onDelete, onRegenerateToken, onToggle, }: { provider: "plex" | "jellyfin"; connection: WebhookConnection | null; onSave: (provider: "plex" | "jellyfin", username: string) => Promise; onDelete: (provider: "plex" | "jellyfin") => Promise; onRegenerateToken: (provider: "plex" | "jellyfin") => Promise; onToggle: (provider: "plex" | "jellyfin", enabled: boolean) => Promise; }) { const [username, setUsername] = useState( connection?.mediaServerUsername ?? "", ); const [saving, setSaving] = useState(false); const [copied, setCopied] = useState(false); const [setupOpen, setSetupOpen] = useState(false); const [cardOpen, setCardOpen] = useState(false); const isPlex = provider === "plex"; const label = isPlex ? "Plex" : "Jellyfin"; const Icon = isPlex ? PlexIcon : JellyfinIcon; const webhookUrl = connection ? `${window.location.origin}/api/webhooks/${connection.token}` : null; async function handleSave() { if (!username.trim()) return; setSaving(true); try { await onSave(provider, username.trim()); } finally { setSaving(false); } } async function handleCopy() { if (!webhookUrl) return; await navigator.clipboard.writeText(webhookUrl); setCopied(true); setTimeout(() => setCopied(false), 2000); } return (
{label} {connection ? connection.lastEventAt ? `Last event ${timeAgo(connection.lastEventAt)}` : "Connected — no events yet" : "Not configured"}
{connection && (
Webhook {connection.enabled ? "enabled" : "disabled"} onToggle(provider, checked)} />
)} {isPlex && (

Plex webhooks require an active{" "} Plex Pass {" "} subscription.

)}
setUsername(e.target.value)} onKeyDown={(e) => e.key === "Enter" && handleSave()} /> {!connection ? ( ) : ( username.trim() !== connection.mediaServerUsername && ( ) )}
{webhookUrl && (
} > {copied ? ( ) : ( )} Copy URL
)}
Setup instructions
{isPlex ? (
  1. Open Plex, go to{" "} Settings > Webhooks
  2. Click{" "} Add Webhook {" "} and paste the URL above
  3. Sofa will automatically log movies and episodes when you finish watching them
  4. Make sure the username above matches your Plex account name
) : (
  1. Install the{" "} Webhook plugin {" "} from Jellyfin's plugin catalog
  2. Go to{" "} Dashboard > Plugins > Webhook
  3. Add a{" "} Generic Destination {" "} and paste the URL above
  4. Enable the{" "} Playback Stop {" "} notification type
  5. Make sure the username above matches your Jellyfin username
)}
); }