import { Badge, Button, Card, CardContent, CardDescription, CardHeader, CardTitle, ConfirmDialog, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@plunk/ui'; import type {Campaign} from '@plunk/db'; import {CampaignStatus} from '@plunk/db'; import {DashboardLayout} from '../../components/DashboardLayout'; import {network} from '../../lib/network'; import {Calendar, Copy, Mail, Plus, Trash2, Users} from 'lucide-react'; import {NextSeo} from 'next-seo'; import Link from 'next/link'; import {useState} from 'react'; import {toast} from 'sonner'; import useSWR from 'swr'; interface PaginatedCampaigns { campaigns: Campaign[]; total: number; page: number; pageSize: number; totalPages: number; } export default function CampaignsPage() { const [page, setPage] = useState(1); const [statusFilter, setStatusFilter] = useState('ALL'); const [showCancelDialog, setShowCancelDialog] = useState(false); const [campaignToCancel, setCampaignToCancel] = useState(null); const [showDeleteDialog, setShowDeleteDialog] = useState(false); const [campaignToDelete, setCampaignToDelete] = useState(null); const {data, mutate, isLoading} = useSWR( `/campaigns?page=${page}&pageSize=20${statusFilter !== 'ALL' ? `&status=${statusFilter}` : ''}`, {revalidateOnFocus: false}, ); const getStatusBadge = (status: CampaignStatus) => { const variants: Record< CampaignStatus, {variant: 'default' | 'secondary' | 'destructive' | 'outline'; label: string; className?: string} > = { DRAFT: {variant: 'secondary', label: 'Draft', className: 'bg-neutral-100 text-neutral-700'}, SCHEDULED: {variant: 'default', label: 'Scheduled', className: 'bg-blue-100 text-blue-700'}, SENDING: {variant: 'default', label: 'Sending', className: 'bg-purple-100 text-purple-700'}, SENT: {variant: 'default', label: 'Sent', className: 'bg-green-100 text-green-700'}, CANCELLED: {variant: 'destructive', label: 'Cancelled', className: 'bg-red-100 text-red-700'}, }; const config = variants[status]; return ( {config.label} ); }; const handleCancel = async () => { if (!campaignToCancel) return; try { await network.fetch('POST', `/campaigns/${campaignToCancel}/cancel`); toast.success('Campaign cancelled successfully'); void mutate(); } catch (error) { toast.error(error instanceof Error ? error.message : 'Failed to cancel campaign'); } finally { setCampaignToCancel(null); } }; const handleDuplicate = async (campaignId: string) => { try { await network.fetch('POST', `/campaigns/${campaignId}/duplicate`); toast.success('Campaign duplicated successfully'); void mutate(); } catch (error) { toast.error(error instanceof Error ? error.message : 'Failed to duplicate campaign'); } }; const handleDelete = async () => { if (!campaignToDelete) return; try { await network.fetch('DELETE', `/campaigns/${campaignToDelete}`); toast.success('Campaign deleted successfully'); void mutate(); } catch (error) { toast.error(error instanceof Error ? error.message : 'Failed to delete campaign'); } finally { setCampaignToDelete(null); } }; return ( <>
{/* Header */}

Campaigns

Send one-time email broadcasts to your contacts. {data?.total ? `${data.total} total campaigns` : ''}

{/* Filters */}
{/* Campaigns List */}
{isLoading && ( Loading campaigns... )} {!isLoading && data?.campaigns.length === 0 && (

{statusFilter !== 'ALL' ? `No ${statusFilter.toLowerCase()} campaigns` : 'No campaigns yet'}

{statusFilter !== 'ALL' ? 'Try adjusting your filters or create a new campaign.' : 'Create your first campaign to send emails to your contacts.'}

)} {data?.campaigns.map(campaign => { const openRate = campaign.sentCount > 0 ? (campaign.openedCount / campaign.sentCount) * 100 : 0; const clickRate = campaign.sentCount > 0 ? (campaign.clickedCount / campaign.sentCount) * 100 : 0; const deliveryProgress = campaign.totalRecipients > 0 ? (campaign.sentCount / campaign.totalRecipients) * 100 : 0; return (
{campaign.name} {getStatusBadge(campaign.status)}
{campaign.description && ( {campaign.description} )}
{/* Stats Grid */}
{/* Recipients */}
Recipients

{campaign.totalRecipients.toLocaleString()}

{campaign.totalRecipients > 0 && (

{deliveryProgress.toFixed(0)}% sent

)}
{/* Open Rate */} {campaign.sentCount > 0 && (
Opens

{openRate.toFixed(1)}%

{campaign.openedCount.toLocaleString()} opened

)} {/* Click Rate */} {campaign.clickedCount > 0 && (
Clicks

{clickRate.toFixed(1)}%

{campaign.clickedCount.toLocaleString()} clicked

)} {/* Scheduled For */} {campaign.scheduledFor && (
Scheduled

{new Date(campaign.scheduledFor).toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric', })}

{new Date(campaign.scheduledFor).toLocaleTimeString(undefined, { hour: 'numeric', minute: '2-digit', })}

)}
{/* Actions */}
{campaign.status === 'DRAFT' && ( )} {(campaign.status === 'SCHEDULED' || campaign.status === 'SENDING') && ( )}
); })}
{/* Pagination */} {data && data.totalPages > 1 && (
Page {page} of {data.totalPages}
)}
); }