diff --git a/apps/web/src/pages/workflows/[id].tsx b/apps/web/src/pages/workflows/[id].tsx index 14b2f957..4288b89a 100644 --- a/apps/web/src/pages/workflows/[id].tsx +++ b/apps/web/src/pages/workflows/[id].tsx @@ -26,6 +26,10 @@ import { SelectItemWithDescription, SelectTrigger, SelectValue, + Command, + CommandGroup, + CommandItem, + CommandList, Switch, } from '@plunk/ui'; import type {Template, Workflow, WorkflowExecution, WorkflowStep, WorkflowTransition} from '@plunk/db'; @@ -793,6 +797,7 @@ function SettingsDialog({workflow, open, onOpenChange, onSave}: SettingsDialogPr const [description, setDescription] = useState(workflow.description ?? ''); const [allowReentry, setAllowReentry] = useState(workflow.allowReentry ?? false); const [eventName, setEventName] = useState(triggerConfig?.eventName ?? ''); + const [eventPopoverOpen, setEventPopoverOpen] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false); // Sync state when workflow changes or dialog opens @@ -852,29 +857,50 @@ function SettingsDialog({workflow, open, onOpenChange, onSave}: SettingsDialogPr
- {eventNamesData?.eventNames && eventNamesData.eventNames.length > 0 ? ( - - ) : ( +
setEventName(e.target.value)} + onChange={e => { + setEventName(e.target.value); + setEventPopoverOpen(true); + }} + onFocus={() => setEventPopoverOpen(true)} + onBlur={() => { + setTimeout(() => setEventPopoverOpen(false), 150); + }} placeholder="e.g., contact.created, email.opened" required + autoComplete="off" /> - )} + {eventPopoverOpen && ((eventNamesData?.eventNames?.length ?? 0) > 0 || eventName?.trim()) && ( +
+ + + + {eventNamesData?.eventNames + ?.filter(n => !eventName || n.toLowerCase().includes(eventName.toLowerCase())) + .map(n => ( + { setEventName(n); setEventPopoverOpen(false); }}> + {n} + + ))} + {eventName?.trim() && !eventNamesData?.eventNames?.some(n => n === eventName.trim()) && ( + { setEventName(eventName.trim()); setEventPopoverOpen(false); }} + > + Use “{eventName.trim()}” + + )} + + + +
+ )} +

The event that triggers this workflow to start for a contact

@@ -961,6 +987,7 @@ function AddStepDialog({open, onOpenChange, workflowId, onSuccess}: AddStepDialo // WAIT_FOR_EVENT fields const [eventName, setEventName] = useState(''); + const [eventPopoverOpen, setEventPopoverOpen] = useState(false); const [eventTimeoutAmount, setEventTimeoutAmount] = useState('1'); const [eventTimeoutUnit, setEventTimeoutUnit] = useState<'minutes' | 'hours' | 'days'>('days'); @@ -1611,34 +1638,53 @@ function AddStepDialog({open, onOpenChange, workflowId, onSuccess}: AddStepDialo - {eventNamesData?.eventNames && eventNamesData.eventNames.length > 0 ? ( - - ) : ( +
setEventName(e.target.value)} + onChange={e => { + setEventName(e.target.value); + setEventPopoverOpen(true); + }} + onFocus={() => setEventPopoverOpen(true)} + onBlur={() => { + setTimeout(() => setEventPopoverOpen(false), 150); + }} required placeholder="e.g., email.clicked, user.upgraded" className="mt-1.5" + autoComplete="off" /> - )} + {eventPopoverOpen && ((eventNamesData?.eventNames?.length ?? 0) > 0 || eventName?.trim()) && ( +
+ + + + {eventNamesData?.eventNames + ?.filter(n => !eventName || n.toLowerCase().includes(eventName.toLowerCase())) + .map(n => ( + { setEventName(n); setEventPopoverOpen(false); }}> + {n} + + ))} + {eventName?.trim() && !eventNamesData?.eventNames?.some(n => n === eventName.trim()) && ( + { setEventName(eventName.trim()); setEventPopoverOpen(false); }} + > + Use “{eventName.trim()}” + + )} + + + +
+ )} +

- {eventNamesData?.eventNames && eventNamesData.eventNames.length > 0 - ? 'The workflow will pause until this event occurs' - : 'Enter the event name to wait for'} + Enter the event name to wait for, or select from previously tracked events

@@ -1991,6 +2037,7 @@ function EditStepDialog({step, workflowId, open, onOpenChange, onSuccess}: EditS // WAIT_FOR_EVENT fields const [eventName, setEventName] = useState(String(config?.eventName || '')); + const [eventPopoverOpen, setEventPopoverOpen] = useState(false); const [eventTimeoutAmount, setEventTimeoutAmount] = useState(() => { const timeoutSeconds = Number(config?.timeout) || 86400; // Convert seconds to most appropriate unit @@ -2793,40 +2840,54 @@ function EditStepDialog({step, workflowId, open, onOpenChange, onSuccess}: EditS
- {eventNamesData?.eventNames && eventNamesData.eventNames.length > 0 ? ( - <> - -

- Select from previously tracked events in your project -

- - ) : ( - <> - setEventName(e.target.value)} - required - placeholder="e.g., email.clicked, user.upgraded" - className="mt-1.5" - /> -

- The workflow will pause until this event is triggered by the contact -

- - )} +
+ { + setEventName(e.target.value); + setEventPopoverOpen(true); + }} + onFocus={() => setEventPopoverOpen(true)} + onBlur={() => { + setTimeout(() => setEventPopoverOpen(false), 150); + }} + required + placeholder="e.g., email.clicked, user.upgraded" + className="mt-1.5" + autoComplete="off" + /> + {eventPopoverOpen && ((eventNamesData?.eventNames?.length ?? 0) > 0 || eventName?.trim()) && ( +
+ + + + {eventNamesData?.eventNames + ?.filter(n => !eventName || n.toLowerCase().includes(eventName.toLowerCase())) + .map(n => ( + { setEventName(n); setEventPopoverOpen(false); }}> + {n} + + ))} + {eventName?.trim() && !eventNamesData?.eventNames?.some(n => n === eventName.trim()) && ( + { setEventName(eventName.trim()); setEventPopoverOpen(false); }} + > + Use “{eventName.trim()}” + + )} + + + +
+ )} +
+

+ Enter the event name to wait for, or select from previously tracked events +

diff --git a/apps/web/src/pages/workflows/index.tsx b/apps/web/src/pages/workflows/index.tsx index 8e825d98..e225b32a 100644 --- a/apps/web/src/pages/workflows/index.tsx +++ b/apps/web/src/pages/workflows/index.tsx @@ -6,6 +6,10 @@ import { CardDescription, CardHeader, CardTitle, + Command, + CommandGroup, + CommandItem, + CommandList, ConfirmDialog, Dialog, DialogContent, @@ -14,11 +18,6 @@ import { DialogTitle, Input, Label, - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, } from '@plunk/ui'; import type {Workflow} from '@plunk/db'; import type {PaginatedResponse} from '@plunk/types'; @@ -321,6 +320,7 @@ function CreateWorkflowDialog({open, onOpenChange, onSuccess}: CreateWorkflowDia 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); @@ -391,34 +391,65 @@ function CreateWorkflowDialog({open, onOpenChange, onSuccess}: CreateWorkflowDia
- - {eventNamesData?.eventNames && eventNamesData.eventNames.length > 0 ? ( - - ) : ( + + {/* Combobox: 可自由輸入 event name,同時提供已追蹤 event 的下拉建議 */} +
setEventName(e.target.value)} - required + onChange={e => { + setEventName(e.target.value); + setEventPopoverOpen(true); + }} + onFocus={() => setEventPopoverOpen(true)} + onBlur={() => { + // 延遲關閉,讓 CommandItem 的 onSelect 有時間觸發 + setTimeout(() => setEventPopoverOpen(false), 150); + }} placeholder="e.g., contact.created, email.opened" + required + autoComplete="off" /> - )} + {eventPopoverOpen && ((eventNamesData?.eventNames?.length ?? 0) > 0 || eventName?.trim()) && ( +
+ + + + {eventNamesData?.eventNames + ?.filter(n => !eventName || n.toLowerCase().includes(eventName.toLowerCase())) + .map(n => ( + { + setEventName(n); + setEventPopoverOpen(false); + }} + > + {n} + + ))} + {eventName?.trim() && !eventNamesData?.eventNames?.some(n => n === eventName.trim()) && ( + { + setEventName(eventName.trim()); + setEventPopoverOpen(false); + }} + > + Use “{eventName.trim()}” + + )} + + + +
+ )} +

- {eventNamesData?.eventNames && eventNamesData.eventNames.length > 0 - ? 'Select from previously tracked events' - : 'No events tracked yet. Enter the event name that will trigger this workflow.'} + The event that triggers this workflow to start for a contact