Selectors with description

This commit is contained in:
Dries Augustyns
2025-12-07 14:20:44 +01:00
parent 683356c17c
commit c189175658
13 changed files with 282 additions and 141 deletions
@@ -1,6 +1,6 @@
import {beforeEach, describe, expect, it, vi} from 'vitest'; import {beforeEach, describe, expect, it, vi} from 'vitest';
import type {Prisma} from '@plunk/db'; import type {Prisma} from '@plunk/db';
import {EmailSourceType, EmailStatus} from '@plunk/db'; import {EmailSourceType, EmailStatus, TrackingMode} from '@plunk/db';
import {createServiceMocks, factories, getPrismaClient} from '../../../../../test/helpers'; import {createServiceMocks, factories, getPrismaClient} from '../../../../../test/helpers';
// Mock MeterService // Mock MeterService
@@ -16,7 +16,7 @@ describe('Email Processor', () => {
const _serviceMocks = createServiceMocks(); const _serviceMocks = createServiceMocks();
beforeEach(async () => { beforeEach(async () => {
const {project} = await factories.createUserWithProject({}, {trackingEnabled: true}); const {project} = await factories.createUserWithProject({}, {tracking: TrackingMode.ENABLED});
projectId = project.id; projectId = project.id;
}); });
+4 -1
View File
@@ -89,6 +89,9 @@ export function createEmailWorker() {
? {name: email.toName, email: email.contact.email} ? {name: email.toName, email: email.contact.email}
: email.contact.email; : email.contact.email;
// Determine tracking based on project settings and email type
const shouldTrack = EmailService.shouldTrackEmail(email.project.tracking, email.sourceType);
// Send via AWS SES // Send via AWS SES
const result = await sendRawEmail({ const result = await sendRawEmail({
from: { from: {
@@ -101,7 +104,7 @@ export function createEmailWorker() {
html: compiledHtml, html: compiledHtml,
}, },
reply: email.replyTo || undefined, reply: email.replyTo || undefined,
tracking: email.project.trackingEnabled, // Use project's tracking preference tracking: shouldTrack,
attachments: email.attachments as {filename: string; content: string; contentType: string}[] | null, attachments: email.attachments as {filename: string; content: string; contentType: string}[] | null,
}); });
+22 -2
View File
@@ -1,5 +1,5 @@
import type {Contact, Email, Prisma, Project} from '@plunk/db'; import type {Contact, Email, Prisma, Project} from '@plunk/db';
import {EmailSourceType, EmailStatus} from '@plunk/db'; import {EmailSourceType, EmailStatus, TrackingMode} from '@plunk/db';
import signale from 'signale'; import signale from 'signale';
import {DASHBOARD_URI, LANDING_URI, STRIPE_ENABLED} from '../app/constants.js'; import {DASHBOARD_URI, LANDING_URI, STRIPE_ENABLED} from '../app/constants.js';
@@ -365,6 +365,9 @@ export class EmailService {
? (email.attachments as Array<{filename: string; content: string; contentType: string}>) ? (email.attachments as Array<{filename: string; content: string; contentType: string}>)
: undefined; : undefined;
// Determine tracking based on project settings and email type
const shouldTrack = this.shouldTrackEmail(email.project.tracking, email.sourceType);
// Send via AWS SES // Send via AWS SES
const result = await sendRawEmail({ const result = await sendRawEmail({
from: { from: {
@@ -379,7 +382,7 @@ export class EmailService {
reply: email.replyTo || undefined, reply: email.replyTo || undefined,
headers: customHeaders, headers: customHeaders,
attachments: attachments, attachments: attachments,
tracking: email.project.trackingEnabled, // Use project's tracking preference tracking: shouldTrack,
}); });
// Mark as sent with SES message ID // Mark as sent with SES message ID
@@ -565,6 +568,23 @@ export class EmailService {
}; };
} }
/**
* Determine if an email should be tracked based on project tracking mode and email source type
*/
public static shouldTrackEmail(trackingMode: TrackingMode, sourceType: EmailSourceType): boolean {
switch (trackingMode) {
case TrackingMode.ENABLED:
return true;
case TrackingMode.DISABLED:
return false;
case TrackingMode.MARKETING_ONLY:
// Track only campaigns and workflows (marketing), not transactional emails
return sourceType !== EmailSourceType.TRANSACTIONAL;
default:
return true;
}
}
/** /**
* Compile HTML email with optional unsubscribe footer and badge * Compile HTML email with optional unsubscribe footer and badge
* Adds unsubscribe link and Plunk badge for free tier users (only when billing is enabled) * Adds unsubscribe link and Plunk badge for free tier users (only when billing is enabled)
+11 -2
View File
@@ -29,6 +29,7 @@ import {
Select, Select,
SelectContent, SelectContent,
SelectItem, SelectItem,
SelectItemWithDescription,
SelectTrigger, SelectTrigger,
SelectValue, SelectValue,
Table, Table,
@@ -346,8 +347,16 @@ export function TeamSettings({projectId, currentUserRole, currentUserId}: TeamSe
</SelectTrigger> </SelectTrigger>
</FormControl> </FormControl>
<SelectContent> <SelectContent>
<SelectItem value="MEMBER">Member - Can view and use the project</SelectItem> <SelectItemWithDescription
<SelectItem value="ADMIN">Admin - Can manage settings and members</SelectItem> value="MEMBER"
title="Member"
description="Can view and use the project"
/>
<SelectItemWithDescription
value="ADMIN"
title="Admin"
description="Can manage settings and members"
/>
</SelectContent> </SelectContent>
</Select> </Select>
<FormMessage /> <FormMessage />
+45 -17
View File
@@ -2,6 +2,7 @@ import {useEffect, useState} from 'react';
import {useForm} from 'react-hook-form'; import {useForm} from 'react-hook-form';
import {zodResolver} from '@hookform/resolvers/zod'; import {zodResolver} from '@hookform/resolvers/zod';
import {ProjectSchemas} from '@plunk/shared'; import {ProjectSchemas} from '@plunk/shared';
import {TrackingMode} from '@plunk/db';
import { import {
Alert, Alert,
Button, Button,
@@ -24,7 +25,11 @@ import {
FormLabel, FormLabel,
FormMessage, FormMessage,
Input, Input,
Switch, Select,
SelectContent,
SelectItemWithDescription,
SelectTrigger,
SelectValue,
Tabs, Tabs,
TabsContent, TabsContent,
TabsList, TabsList,
@@ -158,7 +163,7 @@ export default function Settings() {
resolver: zodResolver(ProjectSchemas.update), resolver: zodResolver(ProjectSchemas.update),
defaultValues: { defaultValues: {
name: activeProject?.name || '', name: activeProject?.name || '',
trackingEnabled: activeProject?.trackingEnabled ?? true, tracking: activeProject?.tracking ?? TrackingMode.ENABLED,
}, },
}); });
@@ -167,7 +172,7 @@ export default function Settings() {
if (activeProject) { if (activeProject) {
form.reset({ form.reset({
name: activeProject.name, name: activeProject.name,
trackingEnabled: activeProject.trackingEnabled ?? true, tracking: activeProject.tracking ?? TrackingMode.ENABLED,
}); });
} }
}, [activeProject, form]); }, [activeProject, form]);
@@ -395,23 +400,42 @@ export default function Settings() {
)} )}
/> />
{/* Email Tracking Toggle - only show if feature is available */} {/* Email Tracking Mode - only show if feature is available */}
{trackingToggleEnabled && ( {trackingToggleEnabled && (
<FormField <FormField
control={form.control} control={form.control}
name="trackingEnabled" name="tracking"
render={({field}) => ( render={({field}) => (
<FormItem className="flex flex-row items-center justify-between rounded-lg border border-neutral-200 p-4"> <FormItem>
<div className="space-y-0.5"> <FormLabel>Email Tracking</FormLabel>
<FormLabel className="text-base">Email Tracking</FormLabel> <Select onValueChange={field.onChange} defaultValue={field.value}>
<FormDescription> <FormControl>
Enable open and click tracking for emails sent from this project. When disabled, <SelectTrigger>
emails will be sent without tracking pixels. <SelectValue placeholder="Select tracking mode" />
</FormDescription> </SelectTrigger>
</div> </FormControl>
<FormControl> <SelectContent>
<Switch checked={field.value} onCheckedChange={field.onChange} /> <SelectItemWithDescription
</FormControl> value={TrackingMode.ENABLED}
title="Enabled"
description="Track opens and clicks for all emails"
/>
<SelectItemWithDescription
value={TrackingMode.DISABLED}
title="Disabled"
description="No tracking for any emails"
/>
<SelectItemWithDescription
value={TrackingMode.MARKETING_ONLY}
title="Marketing Only"
description="Track only campaigns and workflow emails, not transactional"
/>
</SelectContent>
</Select>
<FormDescription>
Control how email opens and clicks are tracked for this project.
</FormDescription>
<FormMessage />
</FormItem> </FormItem>
)} )}
/> />
@@ -659,7 +683,11 @@ export default function Settings() {
{/* Team Tab */} {/* Team Tab */}
<TabsContent value="team"> <TabsContent value="team">
<TeamSettings projectId={activeProject.id} currentUserRole={currentUserRole} currentUserId={user?.id || ''} /> <TeamSettings
projectId={activeProject.id}
currentUserRole={currentUserRole}
currentUserId={user?.id || ''}
/>
</TabsContent> </TabsContent>
{/* Domains Tab */} {/* Domains Tab */}
+11 -3
View File
@@ -10,7 +10,7 @@ import {
Label, Label,
Select, Select,
SelectContent, SelectContent,
SelectItem, SelectItemWithDescription,
SelectTrigger, SelectTrigger,
SelectValue, SelectValue,
StickySaveBar, StickySaveBar,
@@ -232,8 +232,16 @@ export default function TemplateEditorPage() {
<SelectValue /> <SelectValue />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value="MARKETING">Marketing</SelectItem> <SelectItemWithDescription
<SelectItem value="TRANSACTIONAL">Transactional</SelectItem> value="MARKETING"
title="Marketing"
description="Includes unsubscribe link, respects opt-out"
/>
<SelectItemWithDescription
value="TRANSACTIONAL"
title="Transactional"
description="For receipts, alerts - sent regardless of opt-out"
/>
</SelectContent> </SelectContent>
</Select> </Select>
<p className="text-xs text-neutral-500 mt-1"> <p className="text-xs text-neutral-500 mt-1">
+106 -96
View File
@@ -9,7 +9,7 @@ import {
Label, Label,
Select, Select,
SelectContent, SelectContent,
SelectItem, SelectItemWithDescription,
SelectTrigger, SelectTrigger,
SelectValue, SelectValue,
} from '@plunk/ui'; } from '@plunk/ui';
@@ -74,117 +74,127 @@ export default function CreateTemplatePage() {
return ( return (
<> <>
<NextSeo title="Create Template" /> <NextSeo title="Create Template" />
<DashboardLayout> <DashboardLayout>
<div className="max-w-5xl mx-auto space-y-6"> <div className="max-w-5xl mx-auto space-y-6">
{/* Header */} {/* Header */}
<div className="space-y-4"> <div className="space-y-4">
<div className="flex items-center gap-3 sm:gap-4"> <div className="flex items-center gap-3 sm:gap-4">
<Link href="/templates"> <Link href="/templates">
<Button variant="ghost" size="sm"> <Button variant="ghost" size="sm">
<ArrowLeft className="h-4 w-4" /> <ArrowLeft className="h-4 w-4" />
</Button>
</Link>
<div className="flex-1 min-w-0">
<h1 className="text-2xl sm:text-3xl font-bold text-neutral-900">Create Template</h1>
<p className="text-neutral-500 mt-1 text-sm sm:text-base">
Create a reusable email template for campaigns and workflows
</p>
</div>
</div>
<div className="flex justify-end">
<Button onClick={handleSubmit} disabled={saving} className="w-full sm:w-auto">
<Save className="h-4 w-4" />
<span className="hidden sm:inline">{saving ? 'Creating...' : 'Create Template'}</span>
<span className="sm:hidden">{saving ? 'Creating...' : 'Create'}</span>
</Button> </Button>
</Link>
<div className="flex-1 min-w-0">
<h1 className="text-2xl sm:text-3xl font-bold text-neutral-900">Create Template</h1>
<p className="text-neutral-500 mt-1 text-sm sm:text-base">Create a reusable email template for campaigns and workflows</p>
</div> </div>
</div> </div>
<div className="flex justify-end">
<Button onClick={handleSubmit} disabled={saving} className="w-full sm:w-auto">
<Save className="h-4 w-4" />
<span className="hidden sm:inline">{saving ? 'Creating...' : 'Create Template'}</span>
<span className="sm:hidden">{saving ? 'Creating...' : 'Create'}</span>
</Button>
</div>
</div>
<form onSubmit={handleSubmit} className="space-y-6"> <form onSubmit={handleSubmit} className="space-y-6">
{/* Template Settings */} {/* Template Settings */}
<Card> <Card>
<CardHeader> <CardHeader>
<CardTitle>Template Settings</CardTitle> <CardTitle>Template Settings</CardTitle>
<CardDescription>Configure your template details and email settings</CardDescription> <CardDescription>Configure your template details and email settings</CardDescription>
</CardHeader> </CardHeader>
<CardContent className="space-y-4"> <CardContent className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<Label htmlFor="name">Template Name *</Label>
<Input
id="name"
type="text"
value={name}
onChange={e => setName(e.target.value)}
required
placeholder="Welcome Email"
/>
</div>
<div>
<Label htmlFor="type">Template Type *</Label>
<Select value={type} onValueChange={value => setType(value as 'MARKETING' | 'TRANSACTIONAL')}>
<SelectTrigger id="type">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItemWithDescription
value="MARKETING"
title="Marketing"
description="Includes unsubscribe link, respects opt-out"
/>
<SelectItemWithDescription
value="TRANSACTIONAL"
title="Transactional"
description="For receipts, alerts - sent regardless of opt-out"
/>
</SelectContent>
</Select>
</div>
</div>
<div> <div>
<Label htmlFor="name">Template Name *</Label> <Label htmlFor="description">Description</Label>
<Input <Input
id="name" id="description"
type="text" type="text"
value={name} value={description}
onChange={e => setName(e.target.value)} onChange={e => setDescription(e.target.value)}
required placeholder="Sent to new subscribers"
placeholder="Welcome Email"
/> />
</div> </div>
<div> <div>
<Label htmlFor="type">Template Type *</Label> <Label htmlFor="subject">Subject Line *</Label>
<Select value={type} onValueChange={value => setType(value as 'MARKETING' | 'TRANSACTIONAL')}> <Input
<SelectTrigger id="type"> id="subject"
<SelectValue /> type="text"
</SelectTrigger> value={subject}
<SelectContent> onChange={e => setSubject(e.target.value)}
<SelectItem value="MARKETING">Marketing</SelectItem> required
<SelectItem value="TRANSACTIONAL">Transactional</SelectItem> placeholder="Welcome to our platform!"
</SelectContent> />
</Select>
</div> </div>
</div>
<div> <EmailSettings
<Label htmlFor="description">Description</Label> from={from}
<Input fromName={fromName}
id="description" replyTo={replyTo}
type="text" onFromChange={setFrom}
value={description} onFromNameChange={setFromName}
onChange={e => setDescription(e.target.value)} onReplyToChange={setReplyTo}
placeholder="Sent to new subscribers" fromNamePlaceholder={activeProject?.name || 'Your Company'}
/> />
</div> </CardContent>
</Card>
<div> {/* Email Body */}
<Label htmlFor="subject">Subject Line *</Label> <Card>
<Input <CardHeader>
id="subject" <CardTitle>Email Body</CardTitle>
type="text" <CardDescription>Create your email using the visual editor or paste custom HTML</CardDescription>
value={subject} </CardHeader>
onChange={e => setSubject(e.target.value)} <CardContent>
required <EmailEditor
placeholder="Welcome to our platform!" value={body}
onChange={setBody}
placeholder="<h1>Welcome!</h1><p>Thanks for subscribing to our newsletter.</p>"
/> />
</div> </CardContent>
</Card>
<EmailSettings </form>
from={from} </div>
fromName={fromName} </DashboardLayout>
replyTo={replyTo}
onFromChange={setFrom}
onFromNameChange={setFromName}
onReplyToChange={setReplyTo}
fromNamePlaceholder={activeProject?.name || 'Your Company'}
/>
</CardContent>
</Card>
{/* Email Body */}
<Card>
<CardHeader>
<CardTitle>Email Body</CardTitle>
<CardDescription>Create your email using the visual editor or paste custom HTML</CardDescription>
</CardHeader>
<CardContent>
<EmailEditor
value={body}
onChange={setBody}
placeholder="<h1>Welcome!</h1><p>Thanks for subscribing to our newsletter.</p>"
/>
</CardContent>
</Card>
</form>
</div>
</DashboardLayout>
</> </>
); );
} }
+32 -7
View File
@@ -23,6 +23,7 @@ import {
Select, Select,
SelectContent, SelectContent,
SelectItem, SelectItem,
SelectItemWithDescription,
SelectTrigger, SelectTrigger,
SelectValue, SelectValue,
Switch Switch
@@ -1076,13 +1077,37 @@ function AddStepDialog({open, onOpenChange, workflowId, onSuccess}: AddStepDialo
<SelectValue /> <SelectValue />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value="SEND_EMAIL">Send Email</SelectItem> <SelectItemWithDescription
<SelectItem value="DELAY">Delay - Wait for time</SelectItem> value="SEND_EMAIL"
<SelectItem value="WAIT_FOR_EVENT">Wait for Event</SelectItem> title="Send Email"
<SelectItem value="CONDITION">Condition - If/else branching</SelectItem> description="Send an email using a template"
<SelectItem value="WEBHOOK">Webhook - Call external API</SelectItem> />
<SelectItem value="UPDATE_CONTACT">Update Contact</SelectItem> <SelectItemWithDescription
<SelectItem value="EXIT">Exit - End workflow</SelectItem> value="DELAY"
title="Delay"
description="Wait for a specified amount of time"
/>
<SelectItemWithDescription
value="WAIT_FOR_EVENT"
title="Wait for Event"
description="Pause until a specific event occurs"
/>
<SelectItemWithDescription
value="CONDITION"
title="Condition"
description="If/else branching based on contact data"
/>
<SelectItemWithDescription
value="WEBHOOK"
title="Webhook"
description="Call an external API endpoint"
/>
<SelectItemWithDescription
value="UPDATE_CONTACT"
title="Update Contact"
description="Modify contact data fields"
/>
<SelectItemWithDescription value="EXIT" title="Exit" description="End the workflow for this contact" />
</SelectContent> </SelectContent>
</Select> </Select>
<p className="text-xs text-neutral-500 mt-1"> <p className="text-xs text-neutral-500 mt-1">
@@ -7,6 +7,9 @@ CREATE TYPE "Role" AS ENUM ('OWNER', 'ADMIN', 'MEMBER');
-- CreateEnum -- CreateEnum
CREATE TYPE "TemplateType" AS ENUM ('TRANSACTIONAL', 'MARKETING'); CREATE TYPE "TemplateType" AS ENUM ('TRANSACTIONAL', 'MARKETING');
-- CreateEnum
CREATE TYPE "TrackingMode" AS ENUM ('ENABLED', 'DISABLED', 'MARKETING_ONLY');
-- CreateEnum -- CreateEnum
CREATE TYPE "CampaignStatus" AS ENUM ('DRAFT', 'SCHEDULED', 'SENDING', 'SENT', 'CANCELLED'); CREATE TYPE "CampaignStatus" AS ENUM ('DRAFT', 'SCHEDULED', 'SENDING', 'SENT', 'CANCELLED');
@@ -55,7 +58,7 @@ CREATE TABLE "projects" (
"billingLimitWorkflows" INTEGER, "billingLimitWorkflows" INTEGER,
"billingLimitCampaigns" INTEGER, "billingLimitCampaigns" INTEGER,
"billingLimitTransactional" INTEGER, "billingLimitTransactional" INTEGER,
"trackingEnabled" BOOLEAN NOT NULL DEFAULT true, "tracking" "TrackingMode" NOT NULL DEFAULT 'ENABLED',
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL, "updatedAt" TIMESTAMP(3) NOT NULL,
+7 -1
View File
@@ -54,7 +54,7 @@ model Project {
billingLimitTransactional Int? // Max transactional emails per month billingLimitTransactional Int? // Max transactional emails per month
// Email Tracking // Email Tracking
trackingEnabled Boolean @default(true) // Enable/disable open and click tracking tracking TrackingMode @default(ENABLED) // Open and click tracking mode
// Relations // Relations
members Membership[] members Membership[]
@@ -636,6 +636,12 @@ enum TemplateType {
MARKETING MARKETING
} }
enum TrackingMode {
ENABLED // Track opens and clicks for all emails
DISABLED // No tracking for any emails
MARKETING_ONLY // Track only marketing/campaign emails, not transactional
}
enum CampaignStatus { enum CampaignStatus {
DRAFT DRAFT
SCHEDULED SCHEDULED
+3 -7
View File
@@ -1,4 +1,4 @@
import {CampaignAudienceType, TemplateType, WorkflowStepType, WorkflowTriggerType} from '@plunk/db'; import {CampaignAudienceType, TemplateType, TrackingMode, WorkflowStepType, WorkflowTriggerType} from '@plunk/db';
import {z} from 'zod'; import {z} from 'zod';
const literalSchema = z.union([z.string(), z.number(), z.boolean(), z.null(), z.date()]); const literalSchema = z.union([z.string(), z.number(), z.boolean(), z.null(), z.date()]);
@@ -59,7 +59,7 @@ export const ProjectSchemas = {
}), }),
update: z.object({ update: z.object({
name: z.string().min(1).max(100).optional(), name: z.string().min(1).max(100).optional(),
trackingEnabled: z.boolean().optional(), tracking: z.nativeEnum(TrackingMode).optional(),
}), }),
} as const; } as const;
@@ -223,11 +223,7 @@ export const WorkflowStepConfigSchemas = {
), ),
waitForEvent: z.object({ waitForEvent: z.object({
eventName: z.string().min(1), eventName: z.string().min(1),
timeout: z timeout: z.number().positive().max(31536000, 'Timeout cannot exceed 365 days (31,536,000 seconds)').optional(),
.number()
.positive()
.max(31536000, 'Timeout cannot exceed 365 days (31,536,000 seconds)')
.optional(),
}), }),
condition: z.object({ condition: z.object({
field: z.string().min(1), field: z.string().min(1),
@@ -122,6 +122,37 @@ const SelectItem = React.forwardRef<
)); ));
SelectItem.displayName = SelectPrimitive.Item.displayName; SelectItem.displayName = SelectPrimitive.Item.displayName;
interface SelectItemWithDescriptionProps extends React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item> {
title: string;
description?: string;
}
const SelectItemWithDescription = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Item>,
SelectItemWithDescriptionProps
>(({className, title, description, ...props}, ref) => (
<SelectPrimitive.Item
ref={ref}
className={cn(
'relative flex w-full cursor-default select-none items-start rounded-sm py-2.5 pl-8 pr-3 text-sm outline-none focus:bg-neutral-100 focus:text-neutral-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className,
)}
{...props}
>
<span className="absolute left-2 top-3 flex h-3.5 w-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</SelectPrimitive.ItemIndicator>
</span>
<div className="flex flex-col gap-0.5">
<SelectPrimitive.ItemText className="font-medium">{title}</SelectPrimitive.ItemText>
{description && <span className="text-xs text-neutral-500 leading-tight">{description}</span>}
</div>
</SelectPrimitive.Item>
));
SelectItemWithDescription.displayName = 'SelectItemWithDescription';
const SelectSeparator = React.forwardRef< const SelectSeparator = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Separator>, React.ElementRef<typeof SelectPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator> React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
@@ -138,6 +169,7 @@ export {
SelectContent, SelectContent,
SelectLabel, SelectLabel,
SelectItem, SelectItem,
SelectItemWithDescription,
SelectSeparator, SelectSeparator,
SelectScrollUpButton, SelectScrollUpButton,
SelectScrollDownButton, SelectScrollDownButton,
+3 -2
View File
@@ -7,6 +7,7 @@ import {
PrismaClient, PrismaClient,
Role, Role,
TemplateType, TemplateType,
TrackingMode,
WorkflowExecutionStatus, WorkflowExecutionStatus,
WorkflowStepType, WorkflowStepType,
WorkflowTriggerType WorkflowTriggerType
@@ -34,7 +35,7 @@ export interface UserFactoryOptions {
export interface ProjectFactoryOptions { export interface ProjectFactoryOptions {
name?: string; name?: string;
disabled?: boolean; disabled?: boolean;
trackingEnabled?: boolean; tracking?: TrackingMode;
billingLimitWorkflows?: number | null; billingLimitWorkflows?: number | null;
billingLimitCampaigns?: number | null; billingLimitCampaigns?: number | null;
billingLimitTransactional?: number | null; billingLimitTransactional?: number | null;
@@ -127,7 +128,7 @@ export class TestFactories {
public: `pk_${uniqueId()}`, public: `pk_${uniqueId()}`,
secret: `sk_${uniqueId()}`, secret: `sk_${uniqueId()}`,
disabled: options.disabled || false, disabled: options.disabled || false,
trackingEnabled: options.trackingEnabled ?? true, tracking: options.tracking ?? TrackingMode.ENABLED,
billingLimitWorkflows: options.billingLimitWorkflows, billingLimitWorkflows: options.billingLimitWorkflows,
billingLimitCampaigns: options.billingLimitCampaigns, billingLimitCampaigns: options.billingLimitCampaigns,
billingLimitTransactional: options.billingLimitTransactional, billingLimitTransactional: options.billingLimitTransactional,