import { Link, useParams, useNavigate } from "react-router-dom"; import { useTriggers, useTrigger, useDeleteTrigger, useEnableTrigger, useDisableTrigger, } from "@/hooks/useTriggers"; import { useState, useMemo } from "react"; import { ChevronDown, ChevronRight, Search, X, Plus, Copy, Check, Pencil, } from "lucide-react"; import { useAuth } from "@/contexts/AuthContext"; export default function TriggersPage() { const { ref } = useParams<{ ref?: string }>(); const { data, isLoading, error } = useTriggers({}); const triggers = data?.data || []; const [collapsedPacks, setCollapsedPacks] = useState>(new Set()); const [searchQuery, setSearchQuery] = useState(""); // Filter triggers based on search query const filteredTriggers = useMemo(() => { if (!searchQuery.trim()) return triggers; const query = searchQuery.toLowerCase(); return triggers.filter((trigger: any) => { return ( trigger.label?.toLowerCase().includes(query) || trigger.ref?.toLowerCase().includes(query) || trigger.description?.toLowerCase().includes(query) || trigger.pack_ref?.toLowerCase().includes(query) ); }); }, [triggers, searchQuery]); // Group filtered triggers by pack const triggersByPack = useMemo(() => { const grouped = new Map(); filteredTriggers.forEach((trigger: any) => { const packRef = trigger.pack_ref || "unknown"; if (!grouped.has(packRef)) { grouped.set(packRef, []); } grouped.get(packRef)!.push(trigger); }); // Sort packs alphabetically return new Map( [...grouped.entries()].sort((a, b) => a[0].localeCompare(b[0])), ); }, [filteredTriggers]); const togglePack = (packRef: string) => { setCollapsedPacks((prev) => { const next = new Set(prev); if (next.has(packRef)) { next.delete(packRef); } else { next.add(packRef); } return next; }); }; if (isLoading) { return (
); } if (error) { return (

Error: {(error as Error).message}

); } return (
{/* Left sidebar - Triggers List */}

Triggers

Create Trigger

{filteredTriggers.length} of {triggers.length} triggers

{/* Search Bar */}
setSearchQuery(e.target.value)} placeholder="Search triggers..." className="block w-full pl-10 pr-10 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500" /> {searchQuery && ( )}
{triggers.length === 0 ? (

No triggers found

) : filteredTriggers.length === 0 ? (

No triggers match your search

) : (
{Array.from(triggersByPack.entries()).map( ([packRef, packTriggers]) => { const isCollapsed = collapsedPacks.has(packRef); return (
{/* Pack Header */} {/* Triggers List */} {!isCollapsed && (
{packTriggers.map((trigger: any) => (
{trigger.label}
{trigger.enabled ? "Enabled" : "Disabled"}
{trigger.ref}
{trigger.description && (
{trigger.description}
)} ))}
)}
); }, )}
)}
{/* Right panel - Trigger Detail or Empty State */}
{ref ? ( ) : (

No trigger selected

Select a trigger from the list to view its details

)}
); } function TriggerDetail({ triggerRef }: { triggerRef: string }) { const navigate = useNavigate(); const { data: trigger, isLoading, error } = useTrigger(triggerRef); const { isAuthenticated } = useAuth(); const deleteTrigger = useDeleteTrigger(); const enableTrigger = useEnableTrigger(); const disableTrigger = useDisableTrigger(); const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); const [isTogglingEnabled, setIsTogglingEnabled] = useState(false); const [copiedWebhookUrl, setCopiedWebhookUrl] = useState(false); const handleToggleEnabled = async () => { if (!trigger?.data) return; setIsTogglingEnabled(true); try { if (trigger.data.enabled) { await disableTrigger.mutateAsync(triggerRef); } else { await enableTrigger.mutateAsync(triggerRef); } } catch (err) { console.error("Failed to toggle trigger enabled status:", err); } finally { setIsTogglingEnabled(false); } }; const handleDelete = async () => { try { await deleteTrigger.mutateAsync(triggerRef); window.location.href = "/triggers"; } catch (err) { console.error("Failed to delete trigger:", err); } }; const copyWebhookUrl = async () => { if (!trigger?.data?.webhook_key) return; const apiBaseUrl = import.meta.env.VITE_API_BASE_URL || window.location.origin; const webhookUrl = `${apiBaseUrl}/webhooks/${trigger.data.webhook_key}`; try { await navigator.clipboard.writeText(webhookUrl); setCopiedWebhookUrl(true); setTimeout(() => setCopiedWebhookUrl(false), 2000); } catch (err) { console.error("Failed to copy webhook URL:", err); } }; if (isLoading) { return (
); } if (error || !trigger) { return (

Error: {error ? (error as Error).message : "Trigger not found"}

); } const paramSchema = trigger.data?.param_schema || {}; const properties = paramSchema.properties || {}; const requiredFields = paramSchema.required || []; const paramEntries = Object.entries(properties); return (
{/* Header */}

{trigger.data?.pack_ref}. {trigger.data?.label}

{/* Toggle Switch */}
{/* Show edit and delete buttons for ad-hoc triggers (not from pack installation) */} {trigger.data?.is_adhoc && ( <> )}
{/* Delete Confirmation Modal */} {showDeleteConfirm && (

Confirm Delete

Are you sure you want to delete trigger{" "} {trigger.data?.pack_ref}.{trigger.data?.label} ?

)}
{/* Main Info Card */}

Trigger Information

Reference
{trigger.data?.ref}
Label
{trigger.data?.label}
Pack
{trigger.data?.pack_ref}
Description
{trigger.data?.description || "No description provided"}
Created
{new Date(trigger.data?.created || "").toLocaleString()}
Updated
{new Date(trigger.data?.updated || "").toLocaleString()}
{paramEntries.length > 0 && (

Parameters

{paramEntries.map(([key, param]: [string, any]) => (
{key} {requiredFields.includes(key) && ( Required )} {param?.type || "any"}
{param?.description && (

{param.description}

)} {param?.default !== undefined && (

Default:{" "} {JSON.stringify(param.default)}

)}
))}
)}
{/* Sidebar */}
{/* Webhook URL (if enabled) */} {trigger.data?.webhook_enabled && trigger.data?.webhook_key && (

Webhook URL

{import.meta.env.VITE_API_BASE_URL || window.location.origin} /webhooks/{trigger.data.webhook_key}

Use this URL to send webhook events to this trigger.

)} {/* Quick Actions */}

Quick Actions

View Pack View Rules
); }