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
+11 -2
View File
@@ -29,6 +29,7 @@ import {
Select,
SelectContent,
SelectItem,
SelectItemWithDescription,
SelectTrigger,
SelectValue,
Table,
@@ -346,8 +347,16 @@ export function TeamSettings({projectId, currentUserRole, currentUserId}: TeamSe
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="MEMBER">Member - Can view and use the project</SelectItem>
<SelectItem value="ADMIN">Admin - Can manage settings and members</SelectItem>
<SelectItemWithDescription
value="MEMBER"
title="Member"
description="Can view and use the project"
/>
<SelectItemWithDescription
value="ADMIN"
title="Admin"
description="Can manage settings and members"
/>
</SelectContent>
</Select>
<FormMessage />
+45 -17
View File
@@ -2,6 +2,7 @@ import {useEffect, useState} from 'react';
import {useForm} from 'react-hook-form';
import {zodResolver} from '@hookform/resolvers/zod';
import {ProjectSchemas} from '@plunk/shared';
import {TrackingMode} from '@plunk/db';
import {
Alert,
Button,
@@ -24,7 +25,11 @@ import {
FormLabel,
FormMessage,
Input,
Switch,
Select,
SelectContent,
SelectItemWithDescription,
SelectTrigger,
SelectValue,
Tabs,
TabsContent,
TabsList,
@@ -158,7 +163,7 @@ export default function Settings() {
resolver: zodResolver(ProjectSchemas.update),
defaultValues: {
name: activeProject?.name || '',
trackingEnabled: activeProject?.trackingEnabled ?? true,
tracking: activeProject?.tracking ?? TrackingMode.ENABLED,
},
});
@@ -167,7 +172,7 @@ export default function Settings() {
if (activeProject) {
form.reset({
name: activeProject.name,
trackingEnabled: activeProject.trackingEnabled ?? true,
tracking: activeProject.tracking ?? TrackingMode.ENABLED,
});
}
}, [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 && (
<FormField
control={form.control}
name="trackingEnabled"
name="tracking"
render={({field}) => (
<FormItem className="flex flex-row items-center justify-between rounded-lg border border-neutral-200 p-4">
<div className="space-y-0.5">
<FormLabel className="text-base">Email Tracking</FormLabel>
<FormDescription>
Enable open and click tracking for emails sent from this project. When disabled,
emails will be sent without tracking pixels.
</FormDescription>
</div>
<FormControl>
<Switch checked={field.value} onCheckedChange={field.onChange} />
</FormControl>
<FormItem>
<FormLabel>Email Tracking</FormLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select tracking mode" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItemWithDescription
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>
)}
/>
@@ -659,7 +683,11 @@ export default function Settings() {
{/* Team Tab */}
<TabsContent value="team">
<TeamSettings projectId={activeProject.id} currentUserRole={currentUserRole} currentUserId={user?.id || ''} />
<TeamSettings
projectId={activeProject.id}
currentUserRole={currentUserRole}
currentUserId={user?.id || ''}
/>
</TabsContent>
{/* Domains Tab */}
+11 -3
View File
@@ -10,7 +10,7 @@ import {
Label,
Select,
SelectContent,
SelectItem,
SelectItemWithDescription,
SelectTrigger,
SelectValue,
StickySaveBar,
@@ -232,8 +232,16 @@ export default function TemplateEditorPage() {
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="MARKETING">Marketing</SelectItem>
<SelectItem value="TRANSACTIONAL">Transactional</SelectItem>
<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>
<p className="text-xs text-neutral-500 mt-1">
+106 -96
View File
@@ -9,7 +9,7 @@ import {
Label,
Select,
SelectContent,
SelectItem,
SelectItemWithDescription,
SelectTrigger,
SelectValue,
} from '@plunk/ui';
@@ -74,117 +74,127 @@ export default function CreateTemplatePage() {
return (
<>
<NextSeo title="Create Template" />
<DashboardLayout>
<div className="max-w-5xl mx-auto space-y-6">
{/* Header */}
<div className="space-y-4">
<div className="flex items-center gap-3 sm:gap-4">
<Link href="/templates">
<Button variant="ghost" size="sm">
<ArrowLeft className="h-4 w-4" />
<DashboardLayout>
<div className="max-w-5xl mx-auto space-y-6">
{/* Header */}
<div className="space-y-4">
<div className="flex items-center gap-3 sm:gap-4">
<Link href="/templates">
<Button variant="ghost" size="sm">
<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>
</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>
</div>
</div>
<form onSubmit={handleSubmit} className="space-y-6">
{/* Template Settings */}
<Card>
<CardHeader>
<CardTitle>Template Settings</CardTitle>
<CardDescription>Configure your template details and email settings</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<form onSubmit={handleSubmit} className="space-y-6">
{/* Template Settings */}
<Card>
<CardHeader>
<CardTitle>Template Settings</CardTitle>
<CardDescription>Configure your template details and email settings</CardDescription>
</CardHeader>
<CardContent className="space-y-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>
<Label htmlFor="name">Template Name *</Label>
<Label htmlFor="description">Description</Label>
<Input
id="name"
id="description"
type="text"
value={name}
onChange={e => setName(e.target.value)}
required
placeholder="Welcome Email"
value={description}
onChange={e => setDescription(e.target.value)}
placeholder="Sent to new subscribers"
/>
</div>
<div>
<Label htmlFor="type">Template Type *</Label>
<Select value={type} onValueChange={value => setType(value as 'MARKETING' | 'TRANSACTIONAL')}>
<SelectTrigger id="type">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="MARKETING">Marketing</SelectItem>
<SelectItem value="TRANSACTIONAL">Transactional</SelectItem>
</SelectContent>
</Select>
<Label htmlFor="subject">Subject Line *</Label>
<Input
id="subject"
type="text"
value={subject}
onChange={e => setSubject(e.target.value)}
required
placeholder="Welcome to our platform!"
/>
</div>
</div>
<div>
<Label htmlFor="description">Description</Label>
<Input
id="description"
type="text"
value={description}
onChange={e => setDescription(e.target.value)}
placeholder="Sent to new subscribers"
<EmailSettings
from={from}
fromName={fromName}
replyTo={replyTo}
onFromChange={setFrom}
onFromNameChange={setFromName}
onReplyToChange={setReplyTo}
fromNamePlaceholder={activeProject?.name || 'Your Company'}
/>
</div>
</CardContent>
</Card>
<div>
<Label htmlFor="subject">Subject Line *</Label>
<Input
id="subject"
type="text"
value={subject}
onChange={e => setSubject(e.target.value)}
required
placeholder="Welcome to our platform!"
{/* 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>"
/>
</div>
<EmailSettings
from={from}
fromName={fromName}
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>
</CardContent>
</Card>
</form>
</div>
</DashboardLayout>
</>
);
}
+32 -7
View File
@@ -23,6 +23,7 @@ import {
Select,
SelectContent,
SelectItem,
SelectItemWithDescription,
SelectTrigger,
SelectValue,
Switch
@@ -1076,13 +1077,37 @@ function AddStepDialog({open, onOpenChange, workflowId, onSuccess}: AddStepDialo
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="SEND_EMAIL">Send Email</SelectItem>
<SelectItem value="DELAY">Delay - Wait for time</SelectItem>
<SelectItem value="WAIT_FOR_EVENT">Wait for Event</SelectItem>
<SelectItem value="CONDITION">Condition - If/else branching</SelectItem>
<SelectItem value="WEBHOOK">Webhook - Call external API</SelectItem>
<SelectItem value="UPDATE_CONTACT">Update Contact</SelectItem>
<SelectItem value="EXIT">Exit - End workflow</SelectItem>
<SelectItemWithDescription
value="SEND_EMAIL"
title="Send Email"
description="Send an email using a template"
/>
<SelectItemWithDescription
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>
</Select>
<p className="text-xs text-neutral-500 mt-1">