feat: Ability to change subscription status in workflows
This commit is contained in:
@@ -979,7 +979,7 @@ export class WorkflowExecutionService {
|
||||
_stepExecution: WorkflowStepExecution,
|
||||
config: StepConfig,
|
||||
): Promise<StepResult> {
|
||||
const {updates} = WorkflowStepConfigSchemas.updateContact.parse(config);
|
||||
const {updates, subscriptionAction} = WorkflowStepConfigSchemas.updateContact.parse(config);
|
||||
|
||||
const contact = execution.contact;
|
||||
const currentData =
|
||||
@@ -987,24 +987,43 @@ export class WorkflowExecutionService {
|
||||
? (contact.data as Record<string, unknown>)
|
||||
: {};
|
||||
|
||||
// Merge updates with current data
|
||||
const newData = {
|
||||
...currentData,
|
||||
...updates,
|
||||
};
|
||||
const hasDataUpdates = updates && Object.keys(updates).length > 0;
|
||||
const newData = hasDataUpdates ? {...currentData, ...updates} : currentData;
|
||||
|
||||
// Update contact in database
|
||||
await prisma.contact.update({
|
||||
where: {id: contact.id},
|
||||
data: {
|
||||
data: newData ? toPrismaJson(newData) : undefined,
|
||||
},
|
||||
});
|
||||
const desiredSubscribed =
|
||||
subscriptionAction === 'subscribe' ? true : subscriptionAction === 'unsubscribe' ? false : undefined;
|
||||
const subscriptionChanging = desiredSubscribed !== undefined && desiredSubscribed !== contact.subscribed;
|
||||
|
||||
const updateData: Prisma.ContactUpdateInput = {};
|
||||
if (hasDataUpdates) {
|
||||
updateData.data = toPrismaJson(newData);
|
||||
}
|
||||
if (subscriptionChanging) {
|
||||
updateData.subscribed = desiredSubscribed;
|
||||
}
|
||||
|
||||
if (Object.keys(updateData).length > 0) {
|
||||
await prisma.contact.update({
|
||||
where: {id: contact.id},
|
||||
data: updateData,
|
||||
});
|
||||
}
|
||||
|
||||
if (subscriptionChanging) {
|
||||
const {EventService} = await import('./EventService.js');
|
||||
await EventService.trackEvent(
|
||||
execution.workflow.projectId,
|
||||
desiredSubscribed ? 'contact.subscribed' : 'contact.unsubscribed',
|
||||
contact.id,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
updated: true,
|
||||
updated: hasDataUpdates || subscriptionChanging,
|
||||
updates,
|
||||
newData,
|
||||
subscriptionAction,
|
||||
subscribed: desiredSubscribed ?? contact.subscribed,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user