Remove gray colours

This commit is contained in:
Dries Augustyns
2025-12-04 09:45:03 +01:00
parent 40e35735dc
commit 3b73273629
5 changed files with 245 additions and 243 deletions
+234 -232
View File
@@ -108,252 +108,254 @@ export default function CampaignsPage() {
<NextSeo title="Campaigns" /> <NextSeo title="Campaigns" />
<DashboardLayout> <DashboardLayout>
<div className="space-y-6"> <div className="space-y-6">
{/* Header */} {/* Header */}
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div> <div>
<h1 className="text-3xl font-bold text-neutral-900">Campaigns</h1> <h1 className="text-3xl font-bold text-neutral-900">Campaigns</h1>
<p className="text-neutral-500 mt-2"> <p className="text-neutral-500 mt-2">
Send one-time email broadcasts to your contacts. {data?.total ? `${data.total} total campaigns` : ''} Send one-time email broadcasts to your contacts. {data?.total ? `${data.total} total campaigns` : ''}
</p> </p>
</div>
<Link href="/campaigns/create">
<Button>
<Plus className="h-4 w-4" />
Create Campaign
</Button>
</Link>
</div>
{/* Filters */}
<Card>
<CardContent className="pt-6">
<div className="flex gap-4">
<div className="flex-1">
<Select value={statusFilter} onValueChange={setStatusFilter}>
<SelectTrigger>
<SelectValue placeholder="All Statuses" />
</SelectTrigger>
<SelectContent>
<SelectItem value="ALL">All Statuses</SelectItem>
<SelectItem value="DRAFT">Draft</SelectItem>
<SelectItem value="SCHEDULED">Scheduled</SelectItem>
<SelectItem value="SENDING">Sending</SelectItem>
<SelectItem value="SENT">Sent</SelectItem>
<SelectItem value="CANCELLED">Cancelled</SelectItem>
</SelectContent>
</Select>
</div>
</div> </div>
</CardContent> <Link href="/campaigns/create">
</Card> <Button>
<Plus className="h-4 w-4" />
Create Campaign
</Button>
</Link>
</div>
{/* Campaigns List */} {/* Filters */}
<div className="space-y-4"> <Card>
{isLoading && ( <CardContent className="pt-6">
<Card> <div className="flex gap-4">
<CardContent className="py-8 text-center text-neutral-500">Loading campaigns...</CardContent> <div className="flex-1">
</Card> <Select value={statusFilter} onValueChange={setStatusFilter}>
)} <SelectTrigger>
<SelectValue placeholder="All Statuses" />
{!isLoading && data?.campaigns.length === 0 && ( </SelectTrigger>
<Card className="border-2 border-dashed"> <SelectContent>
<CardContent className="py-16 text-center"> <SelectItem value="ALL">All Statuses</SelectItem>
<div className="max-w-md mx-auto"> <SelectItem value="DRAFT">Draft</SelectItem>
<div className="bg-primary/10 w-16 h-16 rounded-full flex items-center justify-center mx-auto mb-4"> <SelectItem value="SCHEDULED">Scheduled</SelectItem>
<Mail className="h-8 w-8 text-primary" /> <SelectItem value="SENDING">Sending</SelectItem>
</div> <SelectItem value="SENT">Sent</SelectItem>
<h3 className="text-xl font-semibold text-neutral-900 mb-2"> <SelectItem value="CANCELLED">Cancelled</SelectItem>
{statusFilter !== 'ALL' ? `No ${statusFilter.toLowerCase()} campaigns` : 'No campaigns yet'} </SelectContent>
</h3> </Select>
<p className="text-neutral-500 mb-6">
{statusFilter !== 'ALL'
? 'Try adjusting your filters or create a new campaign.'
: 'Create your first campaign to send emails to your contacts.'}
</p>
<Link href="/campaigns/create">
<Button size="lg">
<Plus className="h-4 w-4" />
Create Your First Campaign
</Button>
</Link>
</div> </div>
</CardContent> </div>
</Card> </CardContent>
)} </Card>
{data?.campaigns.map(campaign => { {/* Campaigns List */}
const openRate = campaign.sentCount > 0 ? (campaign.openedCount / campaign.sentCount) * 100 : 0; <div className="space-y-4">
const clickRate = campaign.sentCount > 0 ? (campaign.clickedCount / campaign.sentCount) * 100 : 0; {isLoading && (
const deliveryProgress = <Card>
campaign.totalRecipients > 0 ? (campaign.sentCount / campaign.totalRecipients) * 100 : 0; <CardContent className="py-8 text-center text-neutral-500">Loading campaigns...</CardContent>
</Card>
)}
return ( {!isLoading && data?.campaigns.length === 0 && (
<Card key={campaign.id} className="hover:shadow-lg transition-all hover:border-primary/20"> <Card>
<CardHeader className="pb-4"> <CardContent className="py-16 text-center">
<div className="flex items-start justify-between gap-4"> <div className="max-w-md mx-auto">
<div className="flex-1 min-w-0"> <div className="bg-primary/10 w-16 h-16 rounded-full flex items-center justify-center mx-auto mb-4">
<div className="flex items-center gap-3 mb-2"> <Mail className="h-8 w-8 text-primary" />
<Link
href={`/campaigns/${campaign.id}`}
className="hover:text-primary transition-colors flex-1 min-w-0"
>
<CardTitle className="text-xl truncate">{campaign.name}</CardTitle>
</Link>
{getStatusBadge(campaign.status)}
</div>
{campaign.description && (
<CardDescription className="line-clamp-2">{campaign.description}</CardDescription>
)}
</div> </div>
</div> <h3 className="text-xl font-semibold text-neutral-900 mb-2">
</CardHeader> {statusFilter !== 'ALL' ? `No ${statusFilter.toLowerCase()} campaigns` : 'No campaigns yet'}
<CardContent className="space-y-4"> </h3>
{/* Stats Grid */} <p className="text-neutral-500 mb-6">
<div className="grid grid-cols-2 md:grid-cols-4 gap-3"> {statusFilter !== 'ALL'
{/* Recipients */} ? 'Try adjusting your filters or create a new campaign.'
<div className="bg-blue-50 border border-blue-100 rounded-lg p-3"> : 'Create your first campaign to send emails to your contacts.'}
<div className="flex items-center gap-2 mb-1"> </p>
<Users className="h-3.5 w-3.5 text-blue-600" /> <Link href="/campaigns/create">
<span className="text-xs font-medium text-blue-900">Recipients</span> <Button size="lg">
</div> <Plus className="h-4 w-4" />
<p className="text-lg font-bold text-blue-900">{campaign.totalRecipients.toLocaleString()}</p> Create Your First Campaign
{campaign.totalRecipients > 0 && (
<p className="text-xs text-blue-700 mt-1">{deliveryProgress.toFixed(0)}% sent</p>
)}
</div>
{/* Open Rate */}
{campaign.sentCount > 0 && (
<div className="bg-purple-50 border border-purple-100 rounded-lg p-3">
<div className="flex items-center gap-2 mb-1">
<Mail className="h-3.5 w-3.5 text-purple-600" />
<span className="text-xs font-medium text-purple-900">Opens</span>
</div>
<p className="text-lg font-bold text-purple-900">{openRate.toFixed(1)}%</p>
<p className="text-xs text-purple-700 mt-1">{campaign.openedCount.toLocaleString()} opened</p>
</div>
)}
{/* Click Rate */}
{campaign.clickedCount > 0 && (
<div className="bg-orange-50 border border-orange-100 rounded-lg p-3">
<div className="flex items-center gap-2 mb-1">
<Mail className="h-3.5 w-3.5 text-orange-600" />
<span className="text-xs font-medium text-orange-900">Clicks</span>
</div>
<p className="text-lg font-bold text-orange-900">{clickRate.toFixed(1)}%</p>
<p className="text-xs text-orange-700 mt-1">{campaign.clickedCount.toLocaleString()} clicked</p>
</div>
)}
{/* Scheduled For */}
{campaign.scheduledFor && (
<div className="bg-green-50 border border-green-100 rounded-lg p-3 md:col-span-2">
<div className="flex items-center gap-2 mb-1">
<Calendar className="h-3.5 w-3.5 text-green-600" />
<span className="text-xs font-medium text-green-900">Scheduled</span>
</div>
<p className="text-sm font-semibold text-green-900">
{new Date(campaign.scheduledFor).toLocaleDateString(undefined, {
month: 'short',
day: 'numeric',
year: 'numeric',
})}
</p>
<p className="text-xs text-green-700 mt-1">
{new Date(campaign.scheduledFor).toLocaleTimeString(undefined, {
hour: 'numeric',
minute: '2-digit',
})}
</p>
</div>
)}
</div>
{/* Actions */}
<div className="flex gap-2 pt-2 border-t border-neutral-100">
<Link href={`/campaigns/${campaign.id}`} className="flex-1">
<Button variant="outline" size="sm" className="w-full">
{campaign.status === 'DRAFT' ? 'Edit Campaign' : 'View Details'}
</Button> </Button>
</Link> </Link>
<Button variant="outline" size="sm" onClick={() => handleDuplicate(campaign.id)}>
<Copy className="h-4 w-4" />
</Button>
{campaign.status === 'DRAFT' && (
<Button
variant="destructive"
size="sm"
onClick={() => {
setCampaignToDelete(campaign.id);
setShowDeleteDialog(true);
}}
>
<Trash2 className="h-4 w-4" />
</Button>
)}
{(campaign.status === 'SCHEDULED' || campaign.status === 'SENDING') && (
<Button
variant="destructive"
size="sm"
onClick={() => {
setCampaignToCancel(campaign.id);
setShowCancelDialog(true);
}}
>
Cancel
</Button>
)}
</div> </div>
</CardContent> </CardContent>
</Card> </Card>
); )}
})}
{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 (
<Card key={campaign.id} className="hover:shadow-lg transition-all hover:border-primary/20">
<CardHeader className="pb-4">
<div className="flex items-start justify-between gap-4">
<div className="flex-1 min-w-0">
<div className="flex items-center gap-3 mb-2">
<Link
href={`/campaigns/${campaign.id}`}
className="hover:text-primary transition-colors flex-1 min-w-0"
>
<CardTitle className="text-xl truncate">{campaign.name}</CardTitle>
</Link>
{getStatusBadge(campaign.status)}
</div>
{campaign.description && (
<CardDescription className="line-clamp-2">{campaign.description}</CardDescription>
)}
</div>
</div>
</CardHeader>
<CardContent className="space-y-4">
{/* Stats Grid */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
{/* Recipients */}
<div className="bg-blue-50 border border-blue-100 rounded-lg p-3">
<div className="flex items-center gap-2 mb-1">
<Users className="h-3.5 w-3.5 text-blue-600" />
<span className="text-xs font-medium text-blue-900">Recipients</span>
</div>
<p className="text-lg font-bold text-blue-900">{campaign.totalRecipients.toLocaleString()}</p>
{campaign.totalRecipients > 0 && (
<p className="text-xs text-blue-700 mt-1">{deliveryProgress.toFixed(0)}% sent</p>
)}
</div>
{/* Open Rate */}
{campaign.sentCount > 0 && (
<div className="bg-purple-50 border border-purple-100 rounded-lg p-3">
<div className="flex items-center gap-2 mb-1">
<Mail className="h-3.5 w-3.5 text-purple-600" />
<span className="text-xs font-medium text-purple-900">Opens</span>
</div>
<p className="text-lg font-bold text-purple-900">{openRate.toFixed(1)}%</p>
<p className="text-xs text-purple-700 mt-1">{campaign.openedCount.toLocaleString()} opened</p>
</div>
)}
{/* Click Rate */}
{campaign.clickedCount > 0 && (
<div className="bg-orange-50 border border-orange-100 rounded-lg p-3">
<div className="flex items-center gap-2 mb-1">
<Mail className="h-3.5 w-3.5 text-orange-600" />
<span className="text-xs font-medium text-orange-900">Clicks</span>
</div>
<p className="text-lg font-bold text-orange-900">{clickRate.toFixed(1)}%</p>
<p className="text-xs text-orange-700 mt-1">
{campaign.clickedCount.toLocaleString()} clicked
</p>
</div>
)}
{/* Scheduled For */}
{campaign.scheduledFor && (
<div className="bg-green-50 border border-green-100 rounded-lg p-3 md:col-span-2">
<div className="flex items-center gap-2 mb-1">
<Calendar className="h-3.5 w-3.5 text-green-600" />
<span className="text-xs font-medium text-green-900">Scheduled</span>
</div>
<p className="text-sm font-semibold text-green-900">
{new Date(campaign.scheduledFor).toLocaleDateString(undefined, {
month: 'short',
day: 'numeric',
year: 'numeric',
})}
</p>
<p className="text-xs text-green-700 mt-1">
{new Date(campaign.scheduledFor).toLocaleTimeString(undefined, {
hour: 'numeric',
minute: '2-digit',
})}
</p>
</div>
)}
</div>
{/* Actions */}
<div className="flex gap-2 pt-2 border-t border-neutral-100">
<Link href={`/campaigns/${campaign.id}`} className="flex-1">
<Button variant="outline" size="sm" className="w-full">
{campaign.status === 'DRAFT' ? 'Edit Campaign' : 'View Details'}
</Button>
</Link>
<Button variant="outline" size="sm" onClick={() => handleDuplicate(campaign.id)}>
<Copy className="h-4 w-4" />
</Button>
{campaign.status === 'DRAFT' && (
<Button
variant="destructive"
size="sm"
onClick={() => {
setCampaignToDelete(campaign.id);
setShowDeleteDialog(true);
}}
>
<Trash2 className="h-4 w-4" />
</Button>
)}
{(campaign.status === 'SCHEDULED' || campaign.status === 'SENDING') && (
<Button
variant="destructive"
size="sm"
onClick={() => {
setCampaignToCancel(campaign.id);
setShowCancelDialog(true);
}}
>
Cancel
</Button>
)}
</div>
</CardContent>
</Card>
);
})}
</div>
{/* Pagination */}
{data && data.totalPages > 1 && (
<div className="flex justify-center gap-2">
<Button variant="outline" onClick={() => setPage(p => Math.max(1, p - 1))} disabled={page === 1}>
Previous
</Button>
<span className="flex items-center px-4 text-sm text-neutral-600">
Page {page} of {data.totalPages}
</span>
<Button
variant="outline"
onClick={() => setPage(p => Math.min(data.totalPages, p + 1))}
disabled={page === data.totalPages}
>
Next
</Button>
</div>
)}
</div> </div>
{/* Pagination */} <ConfirmDialog
{data && data.totalPages > 1 && ( open={showCancelDialog}
<div className="flex justify-center gap-2"> onOpenChange={setShowCancelDialog}
<Button variant="outline" onClick={() => setPage(p => Math.max(1, p - 1))} disabled={page === 1}> onConfirm={handleCancel}
Previous title="Cancel Campaign"
</Button> description="Are you sure you want to cancel this campaign?"
<span className="flex items-center px-4 text-sm text-neutral-600"> confirmText="Cancel Campaign"
Page {page} of {data.totalPages} variant="destructive"
</span> />
<Button
variant="outline"
onClick={() => setPage(p => Math.min(data.totalPages, p + 1))}
disabled={page === data.totalPages}
>
Next
</Button>
</div>
)}
</div>
<ConfirmDialog <ConfirmDialog
open={showCancelDialog} open={showDeleteDialog}
onOpenChange={setShowCancelDialog} onOpenChange={setShowDeleteDialog}
onConfirm={handleCancel} onConfirm={handleDelete}
title="Cancel Campaign" title="Delete Campaign"
description="Are you sure you want to cancel this campaign?" description="Are you sure you want to delete this draft campaign? This action cannot be undone."
confirmText="Cancel Campaign" confirmText="Delete Campaign"
variant="destructive" variant="destructive"
/> />
</DashboardLayout>
<ConfirmDialog
open={showDeleteDialog}
onOpenChange={setShowDeleteDialog}
onConfirm={handleDelete}
title="Delete Campaign"
description="Are you sure you want to delete this draft campaign? This action cannot be undone."
confirmText="Delete Campaign"
variant="destructive"
/>
</DashboardLayout>
</> </>
); );
} }
+6 -6
View File
@@ -483,17 +483,17 @@ export default function Settings() {
<CardContent className="pt-6 space-y-6"> <CardContent className="pt-6 space-y-6">
{/* Reset Project */} {/* Reset Project */}
<div className="group"> <div className="group">
<div className="flex items-start justify-between gap-4 p-5 rounded-lg border border-gray-200 bg-white transition-all"> <div className="flex items-start justify-between gap-4 p-5 rounded-lg border border-neutral-200 bg-white transition-all">
<div className="flex-1"> <div className="flex-1">
<div className="flex items-center gap-2 mb-2"> <div className="flex items-center gap-2 mb-2">
<Database className="h-4 w-4 text-orange-600" /> <Database className="h-4 w-4 text-orange-600" />
<h4 className="font-semibold text-gray-900">Reset Project Data</h4> <h4 className="font-semibold text-neutral-900">Reset Project Data</h4>
</div> </div>
<p className="text-sm text-gray-600 mb-3"> <p className="text-sm text-neutral-600 mb-3">
Clear all campaigns, contacts, workflows, templates, and events. This gives you a blank Clear all campaigns, contacts, workflows, templates, and events. This gives you a blank
project to start fresh. project to start fresh.
</p> </p>
<div className="flex items-start gap-2 text-xs text-gray-500"> <div className="flex items-start gap-2 text-xs text-neutral-500">
<span className="font-medium">Preserved:</span> <span className="font-medium">Preserved:</span>
<span>API keys, domains, billing information</span> <span>API keys, domains, billing information</span>
</div> </div>
@@ -701,7 +701,7 @@ export default function Settings() {
</DialogHeader> </DialogHeader>
<div className="py-4"> <div className="py-4">
<label className="text-sm font-medium text-gray-700 block mb-2 text-center"> <label className="text-sm font-medium text-neutral-700 block mb-2 text-center">
Type{' '} Type{' '}
<span className="font-mono font-semibold text-orange-600 bg-orange-50 px-2 py-0.5 rounded">RESET</span>{' '} <span className="font-mono font-semibold text-orange-600 bg-orange-50 px-2 py-0.5 rounded">RESET</span>{' '}
to confirm to confirm
@@ -759,7 +759,7 @@ export default function Settings() {
</DialogHeader> </DialogHeader>
<div className="py-4"> <div className="py-4">
<label className="text-sm font-medium text-gray-700 block mb-2 text-center"> <label className="text-sm font-medium text-neutral-700 block mb-2 text-center">
Type <span className="font-mono font-semibold text-red-600 bg-red-50 px-2 py-0.5 rounded">DELETE</span>{' '} Type <span className="font-mono font-semibold text-red-600 bg-red-50 px-2 py-0.5 rounded">DELETE</span>{' '}
to confirm to confirm
</label> </label>
+3 -3
View File
@@ -395,7 +395,7 @@ export default function WorkflowEditorPage() {
<h1 className="text-3xl font-bold text-neutral-900">{workflow.name}</h1> <h1 className="text-3xl font-bold text-neutral-900">{workflow.name}</h1>
<span <span
className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${ className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
workflow.enabled ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-800' workflow.enabled ? 'bg-green-100 text-green-800' : 'bg-neutral-100 text-neutral-800'
}`} }`}
> >
{workflow.enabled ? ( {workflow.enabled ? (
@@ -618,8 +618,8 @@ export default function WorkflowEditorPage() {
: execution.status === 'FAILED' : execution.status === 'FAILED'
? 'bg-red-100 text-red-800' ? 'bg-red-100 text-red-800'
: execution.status === 'CANCELLED' : execution.status === 'CANCELLED'
? 'bg-gray-100 text-gray-800' ? 'bg-neutral-100 text-neutral-800'
: 'bg-gray-100 text-gray-800' : 'bg-neutral-100 text-neutral-800'
}`} }`}
> >
{execution.status} {execution.status}
+1 -1
View File
@@ -189,7 +189,7 @@ export default function WorkflowsPage() {
<CardTitle>{workflow.name}</CardTitle> <CardTitle>{workflow.name}</CardTitle>
<span <span
className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${ className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
workflow.enabled ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-800' workflow.enabled ? 'bg-green-100 text-green-800' : 'bg-neutral-100 text-neutral-800'
}`} }`}
> >
{workflow.enabled ? ( {workflow.enabled ? (
+1 -1
View File
@@ -64,7 +64,7 @@
::before, ::before,
::backdrop, ::backdrop,
::file-selector-button { ::file-selector-button {
border-color: var(--color-gray-200, currentcolor); border-color: var(--color-neutral-200, currentcolor);
} }
} }