import { Badge, Button, Card, CardContent, Command, CommandGroup, CommandItem, CommandList, ConfirmDialog, Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, IconSpinner, Input, Label, } from '@plunk/ui'; import type {Workflow} from '@plunk/db'; import type {PaginatedResponse} from '@plunk/types'; import {EmptyState} from '@plunk/ui'; import {DashboardLayout} from '../../components/DashboardLayout'; import {network} from '../../lib/network'; import {formatRelativeTime} from '../../lib/dateUtils'; import {Calendar, Copy, Edit, Plus, Power, PowerOff, Search, Trash2, Workflow as WorkflowIcon, X, Zap} from 'lucide-react'; import {NextSeo} from 'next-seo'; import Link from 'next/link'; import {useEffect, useState} from 'react'; import {toast} from 'sonner'; import useSWR from 'swr'; import {WorkflowSchemas} from '@plunk/shared'; import dayjs from 'dayjs'; export default function WorkflowsPage() { const [page, setPage] = useState(1); const [search, setSearch] = useState(''); const [searchInput, setSearchInput] = useState(''); const [showCreateDialog, setShowCreateDialog] = useState(false); const [showDeleteDialog, setShowDeleteDialog] = useState(false); const [workflowToDelete, setWorkflowToDelete] = useState(null); const {data, mutate, isLoading} = useSWR< PaginatedResponse >(`/workflows?page=${page}&pageSize=20${search ? `&search=${search}` : ''}`, {revalidateOnFocus: false}); useEffect(() => { const timer = setTimeout(() => { setSearch(searchInput); setPage(1); }, 350); return () => clearTimeout(timer); }, [searchInput]); const handleDelete = async () => { if (!workflowToDelete) return; try { await network.fetch('DELETE', `/workflows/${workflowToDelete}`); toast.success('Workflow deleted successfully'); void mutate(); } catch (error) { toast.error(error instanceof Error ? error.message : 'Failed to delete workflow'); } finally { setWorkflowToDelete(null); } }; const handleDuplicate = async (workflowId: string) => { try { await network.fetch('POST', `/workflows/${workflowId}/duplicate`); toast.success('Workflow duplicated successfully'); void mutate(); } catch (error) { toast.error(error instanceof Error ? error.message : 'Failed to duplicate workflow'); } }; const handleToggleEnabled = async (workflowId: string, currentlyEnabled: boolean) => { try { await network.fetch('PATCH', `/workflows/${workflowId}`, { enabled: !currentlyEnabled, }); toast.success(`Workflow ${!currentlyEnabled ? 'enabled' : 'disabled'} successfully`); void mutate(); } catch (error) { toast.error(error instanceof Error ? error.message : 'Failed to toggle workflow'); } }; return ( <>
{/* Header */}

Workflows

Automate your email campaigns with powerful workflows.{' '} {data?.total ? `${data.total} total workflows` : ''}

{/* Search */}
setSearchInput(e.target.value)} className="pl-10 pr-10 h-8 text-xs" /> {searchInput && ( )}
{/* Workflows */}
{isLoading ? (
) : data?.data.length === 0 ? ( setShowCreateDialog(true)}> Create Workflow ) : undefined } /> ) : ( <>
{data?.data.map(workflow => (

{workflow.name}

{workflow.enabled ? ( <>Active ) : ( <>Disabled )}
{workflow.triggerConfig && typeof workflow.triggerConfig === 'object' && 'eventName' in workflow.triggerConfig && (
Triggers on {String(workflow.triggerConfig.eventName)}
)}
{workflow._count?.steps ?? 0} steps {workflow._count?.executions ?? 0} executions
Updated {formatRelativeTime(workflow.updatedAt)}
{dayjs(workflow.updatedAt).format('DD MMMM YYYY, hh:mm')}
))}
{/* Pagination */} {data && data.totalPages > 1 && (

Showing {(page - 1) * data.pageSize + 1} to {Math.min(page * data.pageSize, data.total)} of{' '} {data.total} workflows

Page {page} of {data.totalPages}
)} )}
{/* Create Workflow Dialog */} mutate()} />
); } interface CreateWorkflowDialogProps { open: boolean; onOpenChange: (open: boolean) => void; onSuccess: () => void; } function CreateWorkflowDialog({open, onOpenChange, onSuccess}: CreateWorkflowDialogProps) { const [name, setName] = useState(''); const [description, setDescription] = useState(''); const [eventName, setEventName] = useState(''); const [eventPopoverOpen, setEventPopoverOpen] = useState(false); const [allowReentry, setAllowReentry] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false); // Fetch available event names const {data: eventNamesData} = useSWR<{eventNames: string[]}>(open ? '/events/names' : null, { revalidateOnFocus: false, }); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setIsSubmitting(true); try { const workflow = await network.fetch('POST', '/workflows', { name, description: description || undefined, eventName: eventName.trim(), allowReentry, enabled: false, }); toast.success('Workflow created successfully'); setName(''); setDescription(''); setEventName(''); setAllowReentry(false); onOpenChange(false); onSuccess(); // Redirect to the workflow editor window.location.href = `/workflows/${workflow.id}`; } catch (error) { toast.error(error instanceof Error ? error.message : 'Failed to create workflow'); } finally { setIsSubmitting(false); } }; return ( Create New Workflow
setName(e.target.value)} required placeholder="Welcome Email Sequence" />