feat: Add type to campaign
This commit is contained in:
@@ -29,7 +29,7 @@ import {
|
||||
StickySaveBar,
|
||||
} from '@plunk/ui';
|
||||
import type {Campaign, Segment} from '@plunk/db';
|
||||
import {CampaignAudienceType, CampaignStatus} from '@plunk/db';
|
||||
import {CampaignAudienceType, CampaignStatus, TemplateType} from '@plunk/db';
|
||||
import {CampaignSchemas} from '@plunk/shared';
|
||||
import {DashboardLayout} from '../../components/DashboardLayout';
|
||||
import {EmailSettings} from '../../components/EmailSettings';
|
||||
@@ -216,6 +216,7 @@ export default function CampaignDetailsPage() {
|
||||
from: editedCampaign.from,
|
||||
fromName: editedCampaign.fromName || null,
|
||||
replyTo: editedCampaign.replyTo || null,
|
||||
type: editedCampaign.type,
|
||||
audienceType: editedCampaign.audienceType,
|
||||
segmentId: editedCampaign.segmentId || undefined,
|
||||
});
|
||||
@@ -232,6 +233,7 @@ export default function CampaignDetailsPage() {
|
||||
from: updated.data.from,
|
||||
fromName: updated.data.fromName || '',
|
||||
replyTo: updated.data.replyTo || '',
|
||||
type: updated.data.type,
|
||||
audienceType: updated.data.audienceType,
|
||||
segmentId: updated.data.segmentId || undefined,
|
||||
});
|
||||
@@ -254,6 +256,7 @@ export default function CampaignDetailsPage() {
|
||||
from: campaign.data.from,
|
||||
fromName: campaign.data.fromName || '',
|
||||
replyTo: campaign.data.replyTo || '',
|
||||
type: campaign.data.type,
|
||||
audienceType: campaign.data.audienceType,
|
||||
segmentId: campaign.data.segmentId || undefined,
|
||||
});
|
||||
@@ -274,6 +277,7 @@ export default function CampaignDetailsPage() {
|
||||
editedCampaign.from !== campaign.data.from ||
|
||||
(editedCampaign.fromName || '') !== (campaign.data.fromName || '') ||
|
||||
(editedCampaign.replyTo || '') !== (campaign.data.replyTo || '') ||
|
||||
editedCampaign.type !== campaign.data.type ||
|
||||
editedCampaign.audienceType !== campaign.data.audienceType ||
|
||||
(editedCampaign.segmentId || null) !== (campaign.data.segmentId || null);
|
||||
|
||||
@@ -461,6 +465,40 @@ export default function CampaignDetailsPage() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label>Campaign Type</Label>
|
||||
<div className="grid grid-cols-2 gap-3 mt-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setEditedCampaign({...editedCampaign, type: TemplateType.MARKETING})}
|
||||
className={`text-left p-3 rounded-lg border-2 transition-colors ${
|
||||
(editedCampaign.type ?? c.type) === TemplateType.MARKETING
|
||||
? 'border-neutral-900 bg-neutral-50'
|
||||
: 'border-neutral-200 hover:border-neutral-300'
|
||||
}`}
|
||||
>
|
||||
<p className="font-medium text-sm text-neutral-900">Marketing</p>
|
||||
<p className="text-xs text-neutral-500 mt-1">
|
||||
Subscribed contacts only, includes unsubscribe link.
|
||||
</p>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setEditedCampaign({...editedCampaign, type: TemplateType.TRANSACTIONAL})}
|
||||
className={`text-left p-3 rounded-lg border-2 transition-colors ${
|
||||
(editedCampaign.type ?? c.type) === TemplateType.TRANSACTIONAL
|
||||
? 'border-neutral-900 bg-neutral-50'
|
||||
: 'border-neutral-200 hover:border-neutral-300'
|
||||
}`}
|
||||
>
|
||||
<p className="font-medium text-sm text-neutral-900">Transactional</p>
|
||||
<p className="text-xs text-neutral-500 mt-1">
|
||||
All contacts regardless of subscription. No unsubscribe footer.
|
||||
</p>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label htmlFor="subject">Subject Line *</Label>
|
||||
<Input
|
||||
@@ -513,8 +551,8 @@ export default function CampaignDetailsPage() {
|
||||
<SelectContent>
|
||||
<SelectItemWithDescription
|
||||
value={CampaignAudienceType.ALL}
|
||||
title="All Subscribed Contacts"
|
||||
description="Send to everyone who hasn't unsubscribed"
|
||||
title={(editedCampaign.type ?? c.type) === TemplateType.TRANSACTIONAL ? 'All Contacts' : 'All Subscribed Contacts'}
|
||||
description={(editedCampaign.type ?? c.type) === TemplateType.TRANSACTIONAL ? 'Send to all contacts regardless of subscription status' : "Send to everyone who hasn't unsubscribed"}
|
||||
/>
|
||||
<SelectItemWithDescription
|
||||
value={CampaignAudienceType.SEGMENT}
|
||||
@@ -581,7 +619,10 @@ export default function CampaignDetailsPage() {
|
||||
<Info className="h-3.5 w-3.5 text-blue-600 mt-0.5 flex-shrink-0" />
|
||||
<p className="text-xs text-blue-800">
|
||||
This count will be recalculated right before sending to ensure accuracy. The final number may
|
||||
differ if contacts subscribe, unsubscribe, or segment membership changes.
|
||||
differ if contacts{' '}
|
||||
{(editedCampaign.type ?? c.type) === TemplateType.TRANSACTIONAL
|
||||
? 'are added or removed, or segment membership changes.'
|
||||
: 'subscribe, unsubscribe, or segment membership changes.'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
Textarea,
|
||||
} from '@plunk/ui';
|
||||
import type {Segment, Template} from '@plunk/db';
|
||||
import {CampaignAudienceType} from '@plunk/db';
|
||||
import {CampaignAudienceType, TemplateType} from '@plunk/db';
|
||||
import {NextSeo} from 'next-seo';
|
||||
import {DashboardLayout} from '../../components/DashboardLayout';
|
||||
import {EmailSettings} from '../../components/EmailSettings';
|
||||
@@ -41,6 +41,7 @@ export default function CreateCampaignPage() {
|
||||
const [from, setFrom] = useState('');
|
||||
const [fromName, setFromName] = useState('');
|
||||
const [replyTo, setReplyTo] = useState('');
|
||||
const [campaignType, setCampaignType] = useState<TemplateType>(TemplateType.MARKETING);
|
||||
const [audienceType, setAudienceType] = useState<CampaignAudienceType>(CampaignAudienceType.ALL);
|
||||
const [segmentId, setSegmentId] = useState('');
|
||||
const [saving, setSaving] = useState(false);
|
||||
@@ -150,6 +151,7 @@ export default function CreateCampaignPage() {
|
||||
from,
|
||||
fromName: fromName || null,
|
||||
replyTo: replyTo || null,
|
||||
type: campaignType,
|
||||
audienceType,
|
||||
segmentId: audienceType === CampaignAudienceType.SEGMENT ? segmentId : undefined,
|
||||
audienceFilter: audienceType === CampaignAudienceType.FILTERED ? [] : undefined,
|
||||
@@ -256,11 +258,54 @@ export default function CreateCampaignPage() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Email Settings */}
|
||||
{/* Campaign Type */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<StepHeader
|
||||
stepNumber={2}
|
||||
title="Campaign Type"
|
||||
description="Choose how this campaign should be treated"
|
||||
/>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setCampaignType(TemplateType.MARKETING)}
|
||||
className={`text-left p-4 rounded-lg border-2 transition-colors ${
|
||||
campaignType === TemplateType.MARKETING
|
||||
? 'border-neutral-900 bg-neutral-50'
|
||||
: 'border-neutral-200 hover:border-neutral-300'
|
||||
}`}
|
||||
>
|
||||
<p className="font-medium text-sm text-neutral-900">Marketing</p>
|
||||
<p className="text-xs text-neutral-500 mt-1">
|
||||
Sent to subscribed contacts only. Includes unsubscribe link.
|
||||
</p>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setCampaignType(TemplateType.TRANSACTIONAL)}
|
||||
className={`text-left p-4 rounded-lg border-2 transition-colors ${
|
||||
campaignType === TemplateType.TRANSACTIONAL
|
||||
? 'border-neutral-900 bg-neutral-50'
|
||||
: 'border-neutral-200 hover:border-neutral-300'
|
||||
}`}
|
||||
>
|
||||
<p className="font-medium text-sm text-neutral-900">Transactional</p>
|
||||
<p className="text-xs text-neutral-500 mt-1">
|
||||
Sent to all contacts regardless of subscription status. No unsubscribe footer.
|
||||
</p>
|
||||
</button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Email Settings */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<StepHeader
|
||||
stepNumber={3}
|
||||
title="Email Settings"
|
||||
description="Configure sender information and subject"
|
||||
/>
|
||||
@@ -294,7 +339,7 @@ export default function CreateCampaignPage() {
|
||||
{/* Email Content */}
|
||||
<Card className="overflow-visible">
|
||||
<CardHeader>
|
||||
<StepHeader stepNumber={3} title="Email Content" description="Design your email message" />
|
||||
<StepHeader stepNumber={4} title="Email Content" description="Design your email message" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-2">
|
||||
@@ -309,7 +354,7 @@ export default function CreateCampaignPage() {
|
||||
{/* Audience Selection */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<StepHeader stepNumber={4} title="Audience" description="Choose who will receive this campaign" />
|
||||
<StepHeader stepNumber={5} title="Audience" description="Choose who will receive this campaign" />
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
@@ -327,8 +372,8 @@ export default function CreateCampaignPage() {
|
||||
<SelectContent>
|
||||
<SelectItemWithDescription
|
||||
value={CampaignAudienceType.ALL}
|
||||
title="All Subscribed Contacts"
|
||||
description="Send to everyone who hasn't unsubscribed"
|
||||
title={campaignType === TemplateType.TRANSACTIONAL ? 'All Contacts' : 'All Subscribed Contacts'}
|
||||
description={campaignType === TemplateType.TRANSACTIONAL ? 'Send to all contacts regardless of subscription status' : "Send to everyone who hasn't unsubscribed"}
|
||||
/>
|
||||
<SelectItemWithDescription
|
||||
value={CampaignAudienceType.SEGMENT}
|
||||
@@ -388,9 +433,13 @@ export default function CreateCampaignPage() {
|
||||
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4 flex items-start gap-3">
|
||||
<Users className="h-5 w-5 text-blue-600 mt-0.5" />
|
||||
<div>
|
||||
<p className="text-sm font-medium text-blue-900">All subscribed contacts</p>
|
||||
<p className="text-sm font-medium text-blue-900">
|
||||
{campaignType === TemplateType.TRANSACTIONAL ? 'All contacts' : 'All subscribed contacts'}
|
||||
</p>
|
||||
<p className="text-xs text-blue-700 mt-1">
|
||||
This campaign will be sent to all contacts who haven't unsubscribed
|
||||
{campaignType === TemplateType.TRANSACTIONAL
|
||||
? 'This campaign will be sent to all contacts regardless of subscription status'
|
||||
: "This campaign will be sent to all contacts who haven't unsubscribed"}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -438,6 +487,13 @@ export default function CreateCampaignPage() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex justify-between py-2 border-b border-neutral-100">
|
||||
<span className="text-neutral-500">Type</span>
|
||||
<span className="font-medium">
|
||||
{campaignType === TemplateType.MARKETING ? 'Marketing' : 'Transactional'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between py-2 border-b border-neutral-100">
|
||||
<span className="text-neutral-500">Audience</span>
|
||||
<span className="font-medium">
|
||||
|
||||
Reference in New Issue
Block a user