feat: Ability to change subscription status in workflows

This commit is contained in:
Dries Augustyns
2026-05-09 08:43:05 +02:00
parent 2ea1802290
commit fadc19d139
4 changed files with 103 additions and 24 deletions
@@ -1,3 +1,4 @@
import {Label, RadioGroup, RadioGroupItem} from '@plunk/ui';
import {useState} from 'react';
import {toast} from 'sonner';
@@ -5,31 +6,50 @@ import {KeyValueEditor} from '../KeyValueEditor';
import {type EditStepDialogProps, getStepConfig, StepDialogShell, useStepUpdate} from './shared';
type SubscriptionAction = 'none' | 'subscribe' | 'unsubscribe';
const SUBSCRIPTION_OPTIONS: Array<{value: SubscriptionAction; label: string; description: string}> = [
{value: 'none', label: 'Leave as is', description: "Don't change the contact's subscription state."},
{value: 'subscribe', label: 'Subscribe', description: 'Mark the contact as subscribed.'},
{value: 'unsubscribe', label: 'Unsubscribe', description: 'Mark the contact as unsubscribed.'},
];
export function UpdateContactStepDialog({step, workflowId, open, onOpenChange, onSuccess}: EditStepDialogProps) {
const config = getStepConfig(step);
const initialUpdates =
config.updates && typeof config.updates === 'object'
? (config.updates as Record<string, string | number | boolean>)
: null;
const initialSubscriptionAction: SubscriptionAction =
config.subscriptionAction === 'subscribe' || config.subscriptionAction === 'unsubscribe'
? config.subscriptionAction
: 'none';
const [name, setName] = useState(step.name);
const [contactUpdateData, setContactUpdateData] = useState<Record<string, string | number | boolean> | null>(
initialUpdates,
);
const [subscriptionAction, setSubscriptionAction] = useState<SubscriptionAction>(initialSubscriptionAction);
const {update, isSubmitting} = useStepUpdate(workflowId, step.id);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!contactUpdateData || Object.keys(contactUpdateData).length === 0) {
toast.error('At least one field to update is required');
const hasUpdates = contactUpdateData && Object.keys(contactUpdateData).length > 0;
const hasSubscriptionAction = subscriptionAction !== 'none';
if (!hasUpdates && !hasSubscriptionAction) {
toast.error('Add at least one field to update or choose a subscription action');
return;
}
const ok = await update({
name,
config: {updates: contactUpdateData},
config: {
updates: hasUpdates ? contactUpdateData : {},
subscriptionAction,
},
});
if (ok) {
@@ -48,7 +68,32 @@ export function UpdateContactStepDialog({step, workflowId, open, onOpenChange, o
onSubmit={handleSubmit}
isSubmitting={isSubmitting}
>
<KeyValueEditor key={`edit-${step.id}`} initialData={contactUpdateData} onChange={setContactUpdateData} />
<div className="space-y-2">
<Label>Subscription state</Label>
<RadioGroup
value={subscriptionAction}
onValueChange={value => setSubscriptionAction(value as SubscriptionAction)}
className="gap-2"
>
{SUBSCRIPTION_OPTIONS.map(option => (
<label
key={option.value}
htmlFor={`subscriptionAction-${option.value}`}
className="flex items-start gap-3 rounded-md border border-neutral-200 p-3 cursor-pointer hover:bg-neutral-50"
>
<RadioGroupItem id={`subscriptionAction-${option.value}`} value={option.value} className="mt-0.5" />
<div className="space-y-0.5">
<div className="text-sm font-medium text-neutral-900">{option.label}</div>
<div className="text-xs text-neutral-500">{option.description}</div>
</div>
</label>
))}
</RadioGroup>
</div>
<div className="space-y-2">
<KeyValueEditor key={`edit-${step.id}`} initialData={contactUpdateData} onChange={setContactUpdateData} />
</div>
</StepDialogShell>
);
}
+10 -3
View File
@@ -206,11 +206,18 @@ export default function WorkflowEditorPage() {
}
break;
case 'UPDATE_CONTACT':
if (!config.updates || (typeof config.updates === 'object' && Object.keys(config.updates).length === 0)) {
errors.push(`"${step.name}" step is missing contact updates`);
case 'UPDATE_CONTACT': {
const hasUpdates =
config.updates && typeof config.updates === 'object' && Object.keys(config.updates).length > 0;
const hasSubscriptionAction =
typeof config.subscriptionAction === 'string' &&
config.subscriptionAction !== 'none' &&
config.subscriptionAction !== '';
if (!hasUpdates && !hasSubscriptionAction) {
errors.push(`"${step.name}" step is missing contact updates or a subscription action`);
}
break;
}
}
});