"use client"; import { IconBook2, IconCheck, IconChevronDown, IconCopy, IconExternalLink, IconInfoCircle, IconRefresh, IconTrash, } from "@tabler/icons-react"; import { formatDistanceToNow } from "date-fns"; 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 { Tooltip, TooltipContent, TooltipTrigger, } from "@/components/ui/tooltip"; import { useConnectionActions } from "@/lib/atoms/integrations"; import { EmbyIcon, JellyfinIcon, PlexIcon } from "./icons"; export interface WebhookConnection { id: string; provider: "plex" | "jellyfin" | "emby"; token: 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 WebhookCard({ provider, }: { provider: "plex" | "jellyfin" | "emby"; }) { const { connection, handleConnect, handleDelete, handleRegenerateToken } = useConnectionActions(provider); const [connecting, setConnecting] = useState(false); const [copied, setCopied] = useState(false); const [setupOpen, setSetupOpen] = useState(false); const [cardOpen, setCardOpen] = useState(false); const isPlex = provider === "plex"; const isEmby = provider === "emby"; const label = isPlex ? "Plex" : isEmby ? "Emby" : "Jellyfin"; const Icon = isPlex ? PlexIcon : isEmby ? EmbyIcon : JellyfinIcon; const webhookUrl = connection ? `${window.location.origin}/api/webhooks/${connection.token}` : null; async function onConnect() { setConnecting(true); try { await handleConnect(); } finally { setConnecting(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 ${formatDistanceToNow(new Date(connection.lastEventAt), { addSuffix: true })}` : "Ready — no events yet" : "Not configured"}
{isPlex && (

Requires an active{" "} Plex Pass {" "} subscription.

)} {isEmby && (

Requires{" "} Emby Server 4.7.9+ {" "} and an active{" "} Emby Premiere {" "} license.

)} {!connection ? ( ) : ( {webhookUrl && (
} > {copied ? ( ) : ( )} Copy URL
)}
)} Setup instructions
    {isPlex ? ( <>
  1. Open Plex, go to{" "} Settings > Webhooks
  2. Click{" "} Add Webhook {" "} and paste the URL above
  3. ) : isEmby ? ( <>
  4. Open Emby, go to{" "} Settings > Webhooks
  5. Add a new webhook and paste the URL above
  6. Enable the{" "} Playback {" "} event category
  7. ) : ( <>
  8. Install the{" "} Webhook plugin {" "} from Jellyfin's plugin catalog
  9. Go to{" "} Dashboard > Plugins > Webhook
  10. Add a{" "} Generic Destination {" "} and paste the URL above
  11. Enable the{" "} Playback Stop {" "} notification type
  12. )}
  13. Sofa will automatically log movies and episodes when you finish watching them

Need more help?{" "} Open docs{" "}

); }