ui: Update empty states

This commit is contained in:
Dries Augustyns
2026-04-21 10:22:05 +02:00
parent e048fc1570
commit 3d9b4e4eb3
5 changed files with 41 additions and 27 deletions
@@ -25,6 +25,7 @@ import type {PaginatedResponse} from '@plunk/types';
import {ArrowLeft, Calendar, Mail, Users} from 'lucide-react';
import {useState} from 'react';
import useSWR from 'swr';
import {EmptyState} from './EmptyState';
interface CampaignSelectionDialogProps {
open: boolean;
@@ -186,13 +187,11 @@ export function CampaignSelectionDialog({open, onOpenChange, onSelectCampaign}:
)}
{!isLoading && data?.data.length === 0 && (
<div className="text-center py-12">
<Mail className="h-12 w-12 text-neutral-400 mx-auto mb-4" />
<h3 className="text-lg font-medium text-neutral-900 mb-2">No campaigns found</h3>
<p className="text-neutral-500">
{statusFilter !== 'ALL' ? 'Try adjusting your filter' : 'Create your first campaign to get started'}
</p>
</div>
<EmptyState
icon={Mail}
title="No campaigns found"
description={statusFilter !== 'ALL' ? 'Try adjusting your filter.' : 'Create your first campaign to get started.'}
/>
)}
{data?.data.map(campaign => (
@@ -21,7 +21,8 @@ import {
TableHeader,
TableRow,
} from '@plunk/ui';
import {AlertCircle, Loader2, Trash2} from 'lucide-react';
import {AlertCircle, Database, Loader2, Trash2, Zap} from 'lucide-react';
import {EmptyState} from './EmptyState';
import {toast} from 'sonner';
import useSWR from 'swr';
import {useActiveProject} from '../lib/contexts/ActiveProjectProvider';
@@ -151,7 +152,11 @@ export function DataManagementSettings() {
</CardHeader>
<CardContent>
{customFields.length === 0 ? (
<p className="text-sm text-neutral-500">No custom fields found</p>
<EmptyState
icon={Database}
title="No custom fields"
description="Custom fields appear here once contacts have data properties set via the API."
/>
) : (
<Table>
<TableHeader>
@@ -196,7 +201,11 @@ export function DataManagementSettings() {
</CardHeader>
<CardContent>
{customEvents.length === 0 ? (
<p className="text-sm text-neutral-500">No custom events found</p>
<EmptyState
icon={Zap}
title="No custom events"
description="Custom events appear here once your contacts trigger events via the API."
/>
) : (
<Table>
<TableHeader>
+7 -4
View File
@@ -20,7 +20,8 @@ import {
Input,
} from '@plunk/ui';
import {AnimatePresence, motion} from 'framer-motion';
import {Check, CheckCircle2, ChevronDown, Copy, Loader2, RefreshCw, Trash2, XCircle} from 'lucide-react';
import {Check, CheckCircle2, ChevronDown, Copy, Globe, Loader2, RefreshCw, Trash2, XCircle} from 'lucide-react';
import {EmptyState} from './EmptyState';
import {useConfig} from '../lib/hooks/useConfig';
import {useAddDomain, useCheckDomainVerification, useDomains, useRemoveDomain} from '../lib/hooks/useDomains';
@@ -324,9 +325,11 @@ export function DomainsSettings({projectId}: DomainsSettingsProps) {
<Loader2 className="h-6 w-6 animate-spin text-neutral-400" />
</div>
) : !domains || domains.length === 0 ? (
<div className="text-center py-8 text-neutral-500">
<p>No domains added yet</p>
</div>
<EmptyState
icon={Globe}
title="No domains added"
description="Add a custom domain above to send emails from your own address."
/>
) : (
<div className="space-y-4">
{domains.map(domain => {
@@ -20,6 +20,7 @@ import type {PaginatedResponse} from '@plunk/types';
import {ArrowLeft, FileText, Search} from 'lucide-react';
import {useState} from 'react';
import useSWR from 'swr';
import {EmptyState} from './EmptyState';
interface TemplateSelectionDialogProps {
open: boolean;
@@ -224,13 +225,11 @@ export function TemplateSelectionDialog({open, onOpenChange, onSelectTemplate}:
)}
{!isLoading && data?.data.length === 0 && (
<div className="text-center py-12">
<FileText className="h-12 w-12 text-neutral-400 mx-auto mb-4" />
<h3 className="text-lg font-medium text-neutral-900 mb-2">No templates found</h3>
<p className="text-neutral-500">
{search ? 'Try adjusting your search terms' : 'Create a template first to use this feature'}
</p>
</div>
<EmptyState
icon={FileText}
title="No templates found"
description={search ? 'Try adjusting your search terms.' : 'Create a template first to use this feature.'}
/>
)}
{data?.data.map(template => (
+10 -6
View File
@@ -271,9 +271,11 @@ export default function AnalyticsPage() {
{!hasData ? (
<div className="flex h-[400px] w-full items-center justify-center">
<div className="text-center">
<Mail className="mx-auto h-8 w-8 text-neutral-300" />
<h3 className="mt-3 text-sm font-semibold text-neutral-900">No email data yet</h3>
<p className="mt-1 text-sm text-neutral-500">Send your first email to see analytics here.</p>
<div className="inline-flex items-center justify-center w-10 h-10 rounded-md border border-neutral-200 bg-neutral-50 mb-4">
<Mail className="h-5 w-5 text-neutral-400" />
</div>
<h3 className="text-sm font-semibold text-neutral-900 mb-1">No email data yet</h3>
<p className="text-sm text-neutral-500">Send your first email to see analytics here.</p>
</div>
</div>
) : (
@@ -380,9 +382,11 @@ export default function AnalyticsPage() {
{!hasData ? (
<div className="flex h-[300px] w-full items-center justify-center">
<div className="text-center">
<Eye className="mx-auto h-8 w-8 text-neutral-300" />
<h3 className="mt-3 text-sm font-semibold text-neutral-900">No engagement data</h3>
<p className="mt-1 text-sm text-neutral-500">
<div className="inline-flex items-center justify-center w-10 h-10 rounded-md border border-neutral-200 bg-neutral-50 mb-4">
<Eye className="h-5 w-5 text-neutral-400" />
</div>
<h3 className="text-sm font-semibold text-neutral-900 mb-1">No engagement data</h3>
<p className="text-sm text-neutral-500">
Engagement metrics will appear once emails are opened.
</p>
</div>