feat: Ability to change workflow trigger

This commit is contained in:
Dries Augustyns
2026-02-18 20:26:41 +01:00
parent f947ee6f22
commit eec37ecb31
4 changed files with 167 additions and 24 deletions
+41 -5
View File
@@ -226,6 +226,22 @@ function CustomNode({
onMouseLeave={() => setShowActions(false)}
>
{/* Action buttons - shown on hover */}
{showActions && data.type === 'TRIGGER' && (
<div className="absolute -top-3 -right-3 flex gap-1.5 z-10">
<Button
onClick={e => {
e.stopPropagation();
data.onEdit?.();
}}
variant="outline"
size="icon"
className="h-7 w-7 shadow-md"
title="Edit trigger settings"
>
<Settings className="h-3.5 w-3.5" />
</Button>
</div>
)}
{showActions && data.type !== 'TRIGGER' && (
<div className="absolute -top-3 -right-3 flex gap-1.5 z-10">
<Button
@@ -322,6 +338,14 @@ function CustomNode({
</div>
</div>
)}
{data.type === 'TRIGGER' && data.config?.eventName && (
<div className="mt-3 pt-3 border-t border-neutral-100">
<div className="flex items-center gap-2 text-xs text-neutral-600">
<Lightbulb className="h-3 w-3" />
<span className="truncate">{data.config.eventName}</span>
</div>
</div>
)}
{data.type === 'WEBHOOK' && data.config?.url && (
<div className="mt-3 pt-3 border-t border-neutral-100">
<div className="flex items-center gap-2 text-xs text-neutral-600">
@@ -375,11 +399,23 @@ export function WorkflowBuilder({workflowId, steps, onUpdate}: WorkflowBuilderPr
const [stepToDelete, setStepToDelete] = useState<string | null>(null);
// Define handlers before they are used in useMemo
const handleEditStep = useCallback((stepId: string) => {
// This will be handled by the parent component
const event = new CustomEvent('workflow-edit-step', {detail: {stepId}});
window.dispatchEvent(event);
}, []);
const handleEditStep = useCallback(
(stepId: string) => {
// Check if this is a TRIGGER step
const step = steps.find(s => s.id === stepId);
if (step?.type === 'TRIGGER') {
// For TRIGGER steps, open workflow settings instead
const event = new CustomEvent('workflow-open-settings');
window.dispatchEvent(event);
} else {
// For other steps, open step editor
const event = new CustomEvent('workflow-edit-step', {detail: {stepId}});
window.dispatchEvent(event);
}
},
[steps],
);
const handleDeleteStepClick = useCallback((stepId: string) => {
setStepToDelete(stepId);
+72 -3
View File
@@ -318,7 +318,12 @@ export default function WorkflowEditorPage() {
}
};
const handleUpdateSettings = async (data: {name: string; description?: string}) => {
const handleUpdateSettings = async (data: {
name: string;
description?: string;
allowReentry?: boolean;
triggerConfig?: {eventName: string};
}) => {
try {
await network.fetch<Workflow, typeof WorkflowSchemas.update>('PATCH', `/workflows/${id}`, data);
toast.success('Workflow updated successfully');
@@ -352,9 +357,15 @@ export default function WorkflowEditorPage() {
}
};
const handleOpenSettingsEvent = () => {
setShowSettingsDialog(true);
};
window.addEventListener('workflow-edit-step', handleEditStepEvent);
window.addEventListener('workflow-open-settings', handleOpenSettingsEvent);
return () => {
window.removeEventListener('workflow-edit-step', handleEditStepEvent);
window.removeEventListener('workflow-open-settings', handleOpenSettingsEvent);
};
}, [workflow]);
@@ -741,21 +752,49 @@ interface SettingsDialogProps {
workflow: Workflow;
open: boolean;
onOpenChange: (open: boolean) => void;
onSave: (data: {name: string; description?: string; allowReentry?: boolean}) => Promise<void>;
onSave: (data: {
name: string;
description?: string;
allowReentry?: boolean;
triggerConfig?: {eventName: string};
}) => Promise<void>;
}
function SettingsDialog({workflow, open, onOpenChange, onSave}: SettingsDialogProps) {
const triggerConfig = workflow.triggerConfig as {eventName?: string} | null;
const [name, setName] = useState(workflow.name);
const [description, setDescription] = useState(workflow.description ?? '');
const [allowReentry, setAllowReentry] = useState(workflow.allowReentry ?? false);
const [eventName, setEventName] = useState(triggerConfig?.eventName ?? '');
const [isSubmitting, setIsSubmitting] = useState(false);
// Sync state when workflow changes or dialog opens
useEffect(() => {
if (open) {
setName(workflow.name);
setDescription(workflow.description ?? '');
setAllowReentry(workflow.allowReentry ?? false);
const config = workflow.triggerConfig as {eventName?: string} | null;
setEventName(config?.eventName ?? '');
}
}, [open, workflow]);
// 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 {
await onSave({name, description: description || undefined, allowReentry});
await onSave({
name,
description: description || undefined,
allowReentry,
triggerConfig: eventName.trim() ? {eventName: eventName.trim()} : undefined,
});
} finally {
setIsSubmitting(false);
}
@@ -784,6 +823,36 @@ function SettingsDialog({workflow, open, onOpenChange, onSave}: SettingsDialogPr
/>
</div>
<div>
<Label htmlFor="eventName">Trigger Event *</Label>
{eventNamesData?.eventNames && eventNamesData.eventNames.length > 0 ? (
<Select value={eventName} onValueChange={setEventName} required>
<SelectTrigger id="eventName">
<SelectValue placeholder="Select an event" />
</SelectTrigger>
<SelectContent>
{eventNamesData.eventNames.map(name => (
<SelectItem key={name} value={name}>
{name}
</SelectItem>
))}
</SelectContent>
</Select>
) : (
<Input
id="eventName"
type="text"
value={eventName}
onChange={e => setEventName(e.target.value)}
placeholder="e.g., contact.created, email.opened"
required
/>
)}
<p className="text-xs text-neutral-500 mt-1">
The event that triggers this workflow to start for a contact
</p>
</div>
<div className="flex items-start gap-3 p-4 bg-neutral-50 rounded-lg border border-neutral-200">
<Switch id="allowReentry" checked={allowReentry} onCheckedChange={setAllowReentry} />
<div className="flex-1">