Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
199 changes: 130 additions & 69 deletions apps/web/src/pages/workflows/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -852,29 +857,50 @@ function SettingsDialog({workflow, open, onOpenChange, onSave}: SettingsDialogPr

<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>
) : (
<div className="relative">
<Input
id="eventName"
type="text"
value={eventName}
onChange={e => 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()) && (
<div className="absolute z-50 w-full mt-1 rounded-md border border-neutral-200 bg-white shadow-md">
<Command>
<CommandList>
<CommandGroup>
{eventNamesData?.eventNames
?.filter(n => !eventName || n.toLowerCase().includes(eventName.toLowerCase()))
.map(n => (
<CommandItem key={n} value={n} onSelect={() => { setEventName(n); setEventPopoverOpen(false); }}>
{n}
</CommandItem>
))}
{eventName?.trim() && !eventNamesData?.eventNames?.some(n => n === eventName.trim()) && (
<CommandItem
key="__custom__"
value={eventName.trim()}
onSelect={() => { setEventName(eventName.trim()); setEventPopoverOpen(false); }}
>
Use &ldquo;{eventName.trim()}&rdquo;
</CommandItem>
)}
</CommandGroup>
</CommandList>
</Command>
</div>
)}
</div>
<p className="text-xs text-neutral-500 mt-1">
The event that triggers this workflow to start for a contact
</p>
Expand Down Expand Up @@ -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');

Expand Down Expand Up @@ -1611,34 +1638,53 @@ function AddStepDialog({open, onOpenChange, workflowId, onSuccess}: AddStepDialo
<Label htmlFor="eventName" className="text-sm font-medium">
Event Name *
</Label>
{eventNamesData?.eventNames && eventNamesData.eventNames.length > 0 ? (
<Select value={eventName} onValueChange={setEventName} required>
<SelectTrigger id="eventName" className="mt-1.5">
<SelectValue placeholder="Select an event..." />
</SelectTrigger>
<SelectContent>
{eventNamesData.eventNames.map(name => (
<SelectItem key={name} value={name}>
{name}
</SelectItem>
))}
</SelectContent>
</Select>
) : (
<div className="relative">
<Input
id="eventName"
type="text"
value={eventName}
onChange={e => 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()) && (
<div className="absolute z-50 w-full mt-1 rounded-md border border-neutral-200 bg-white shadow-md">
<Command>
<CommandList>
<CommandGroup>
{eventNamesData?.eventNames
?.filter(n => !eventName || n.toLowerCase().includes(eventName.toLowerCase()))
.map(n => (
<CommandItem key={n} value={n} onSelect={() => { setEventName(n); setEventPopoverOpen(false); }}>
{n}
</CommandItem>
))}
{eventName?.trim() && !eventNamesData?.eventNames?.some(n => n === eventName.trim()) && (
<CommandItem
key="__custom__"
value={eventName.trim()}
onSelect={() => { setEventName(eventName.trim()); setEventPopoverOpen(false); }}
>
Use &ldquo;{eventName.trim()}&rdquo;
</CommandItem>
)}
</CommandGroup>
</CommandList>
</Command>
</div>
)}
</div>
<p className="text-xs text-neutral-500 mt-1.5">
{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
</p>
</div>

Expand Down Expand Up @@ -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<string>(() => {
const timeoutSeconds = Number(config?.timeout) || 86400;
// Convert seconds to most appropriate unit
Expand Down Expand Up @@ -2793,40 +2840,54 @@ function EditStepDialog({step, workflowId, open, onOpenChange, onSuccess}: EditS
<div className="space-y-4 pl-3">
<div>
<Label htmlFor="editEventName">Event Name *</Label>
{eventNamesData?.eventNames && eventNamesData.eventNames.length > 0 ? (
<>
<Select value={eventName} onValueChange={setEventName} required>
<SelectTrigger id="editEventName" className="mt-1.5">
<SelectValue placeholder="Select an event..." />
</SelectTrigger>
<SelectContent>
{eventNamesData.eventNames.map(name => (
<SelectItem key={name} value={name}>
{name}
</SelectItem>
))}
</SelectContent>
</Select>
<p className="text-xs text-neutral-500 mt-1.5">
Select from previously tracked events in your project
</p>
</>
) : (
<>
<Input
id="editEventName"
type="text"
value={eventName}
onChange={e => setEventName(e.target.value)}
required
placeholder="e.g., email.clicked, user.upgraded"
className="mt-1.5"
/>
<p className="text-xs text-neutral-500 mt-1.5">
The workflow will pause until this event is triggered by the contact
</p>
</>
)}
<div className="relative">
<Input
id="editEventName"
type="text"
value={eventName}
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()) && (
<div className="absolute z-50 w-full mt-1 rounded-md border border-neutral-200 bg-white shadow-md">
<Command>
<CommandList>
<CommandGroup>
{eventNamesData?.eventNames
?.filter(n => !eventName || n.toLowerCase().includes(eventName.toLowerCase()))
.map(n => (
<CommandItem key={n} value={n} onSelect={() => { setEventName(n); setEventPopoverOpen(false); }}>
{n}
</CommandItem>
))}
{eventName?.trim() && !eventNamesData?.eventNames?.some(n => n === eventName.trim()) && (
<CommandItem
key="__custom__"
value={eventName.trim()}
onSelect={() => { setEventName(eventName.trim()); setEventPopoverOpen(false); }}
>
Use &ldquo;{eventName.trim()}&rdquo;
</CommandItem>
)}
</CommandGroup>
</CommandList>
</Command>
</div>
)}
</div>
<p className="text-xs text-neutral-500 mt-1.5">
Enter the event name to wait for, or select from previously tracked events
</p>
</div>

<div>
Expand Down
85 changes: 58 additions & 27 deletions apps/web/src/pages/workflows/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import {
CardDescription,
CardHeader,
CardTitle,
Command,
CommandGroup,
CommandItem,
CommandList,
ConfirmDialog,
Dialog,
DialogContent,
Expand All @@ -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';
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -391,34 +391,65 @@ function CreateWorkflowDialog({open, onOpenChange, onSuccess}: CreateWorkflowDia
</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>
) : (
<Label htmlFor="createEventName">Trigger Event *</Label>
{/* Combobox: 可自由輸入 event name,同時提供已追蹤 event 的下拉建議 */}
<div className="relative">
<Input
id="eventName"
id="createEventName"
type="text"
value={eventName}
onChange={e => 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()) && (
<div className="absolute z-50 w-full mt-1 rounded-md border border-neutral-200 bg-white shadow-md">
<Command>
<CommandList>
<CommandGroup>
{eventNamesData?.eventNames
?.filter(n => !eventName || n.toLowerCase().includes(eventName.toLowerCase()))
.map(n => (
<CommandItem
key={n}
value={n}
onSelect={() => {
setEventName(n);
setEventPopoverOpen(false);
}}
>
{n}
</CommandItem>
))}
{eventName?.trim() && !eventNamesData?.eventNames?.some(n => n === eventName.trim()) && (
<CommandItem
key="__custom__"
value={eventName.trim()}
onSelect={() => {
setEventName(eventName.trim());
setEventPopoverOpen(false);
}}
>
Use &ldquo;{eventName.trim()}&rdquo;
</CommandItem>
)}
</CommandGroup>
</CommandList>
</Command>
</div>
)}
</div>
<p className="text-xs text-neutral-500 mt-1">
{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
</p>
</div>

Expand Down
Loading