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 {ArrowLeft, Calendar, Mail, Users} from 'lucide-react';
import {useState} from 'react'; import {useState} from 'react';
import useSWR from 'swr'; import useSWR from 'swr';
import {EmptyState} from './EmptyState';
interface CampaignSelectionDialogProps { interface CampaignSelectionDialogProps {
open: boolean; open: boolean;
@@ -186,13 +187,11 @@ export function CampaignSelectionDialog({open, onOpenChange, onSelectCampaign}:
)} )}
{!isLoading && data?.data.length === 0 && ( {!isLoading && data?.data.length === 0 && (
<div className="text-center py-12"> <EmptyState
<Mail className="h-12 w-12 text-neutral-400 mx-auto mb-4" /> icon={Mail}
<h3 className="text-lg font-medium text-neutral-900 mb-2">No campaigns found</h3> title="No campaigns found"
<p className="text-neutral-500"> description={statusFilter !== 'ALL' ? 'Try adjusting your filter.' : 'Create your first campaign to get started.'}
{statusFilter !== 'ALL' ? 'Try adjusting your filter' : 'Create your first campaign to get started'} />
</p>
</div>
)} )}
{data?.data.map(campaign => ( {data?.data.map(campaign => (
@@ -21,7 +21,8 @@ import {
TableHeader, TableHeader,
TableRow, TableRow,
} from '@plunk/ui'; } 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 {toast} from 'sonner';
import useSWR from 'swr'; import useSWR from 'swr';
import {useActiveProject} from '../lib/contexts/ActiveProjectProvider'; import {useActiveProject} from '../lib/contexts/ActiveProjectProvider';
@@ -151,7 +152,11 @@ export function DataManagementSettings() {
</CardHeader> </CardHeader>
<CardContent> <CardContent>
{customFields.length === 0 ? ( {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> <Table>
<TableHeader> <TableHeader>
@@ -196,7 +201,11 @@ export function DataManagementSettings() {
</CardHeader> </CardHeader>
<CardContent> <CardContent>
{customEvents.length === 0 ? ( {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> <Table>
<TableHeader> <TableHeader>
+7 -4
View File
@@ -20,7 +20,8 @@ import {
Input, Input,
} from '@plunk/ui'; } from '@plunk/ui';
import {AnimatePresence, motion} from 'framer-motion'; 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 {useConfig} from '../lib/hooks/useConfig';
import {useAddDomain, useCheckDomainVerification, useDomains, useRemoveDomain} from '../lib/hooks/useDomains'; 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" /> <Loader2 className="h-6 w-6 animate-spin text-neutral-400" />
</div> </div>
) : !domains || domains.length === 0 ? ( ) : !domains || domains.length === 0 ? (
<div className="text-center py-8 text-neutral-500"> <EmptyState
<p>No domains added yet</p> icon={Globe}
</div> title="No domains added"
description="Add a custom domain above to send emails from your own address."
/>
) : ( ) : (
<div className="space-y-4"> <div className="space-y-4">
{domains.map(domain => { {domains.map(domain => {
@@ -20,6 +20,7 @@ import type {PaginatedResponse} from '@plunk/types';
import {ArrowLeft, FileText, Search} from 'lucide-react'; import {ArrowLeft, FileText, Search} from 'lucide-react';
import {useState} from 'react'; import {useState} from 'react';
import useSWR from 'swr'; import useSWR from 'swr';
import {EmptyState} from './EmptyState';
interface TemplateSelectionDialogProps { interface TemplateSelectionDialogProps {
open: boolean; open: boolean;
@@ -224,13 +225,11 @@ export function TemplateSelectionDialog({open, onOpenChange, onSelectTemplate}:
)} )}
{!isLoading && data?.data.length === 0 && ( {!isLoading && data?.data.length === 0 && (
<div className="text-center py-12"> <EmptyState
<FileText className="h-12 w-12 text-neutral-400 mx-auto mb-4" /> icon={FileText}
<h3 className="text-lg font-medium text-neutral-900 mb-2">No templates found</h3> title="No templates found"
<p className="text-neutral-500"> description={search ? 'Try adjusting your search terms.' : 'Create a template first to use this feature.'}
{search ? 'Try adjusting your search terms' : 'Create a template first to use this feature'} />
</p>
</div>
)} )}
{data?.data.map(template => ( {data?.data.map(template => (
+10 -6
View File
@@ -271,9 +271,11 @@ export default function AnalyticsPage() {
{!hasData ? ( {!hasData ? (
<div className="flex h-[400px] w-full items-center justify-center"> <div className="flex h-[400px] w-full items-center justify-center">
<div className="text-center"> <div className="text-center">
<Mail className="mx-auto h-8 w-8 text-neutral-300" /> <div className="inline-flex items-center justify-center w-10 h-10 rounded-md border border-neutral-200 bg-neutral-50 mb-4">
<h3 className="mt-3 text-sm font-semibold text-neutral-900">No email data yet</h3> <Mail className="h-5 w-5 text-neutral-400" />
<p className="mt-1 text-sm text-neutral-500">Send your first email to see analytics here.</p> </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>
</div> </div>
) : ( ) : (
@@ -380,9 +382,11 @@ export default function AnalyticsPage() {
{!hasData ? ( {!hasData ? (
<div className="flex h-[300px] w-full items-center justify-center"> <div className="flex h-[300px] w-full items-center justify-center">
<div className="text-center"> <div className="text-center">
<Eye className="mx-auto h-8 w-8 text-neutral-300" /> <div className="inline-flex items-center justify-center w-10 h-10 rounded-md border border-neutral-200 bg-neutral-50 mb-4">
<h3 className="mt-3 text-sm font-semibold text-neutral-900">No engagement data</h3> <Eye className="h-5 w-5 text-neutral-400" />
<p className="mt-1 text-sm text-neutral-500"> </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. Engagement metrics will appear once emails are opened.
</p> </p>
</div> </div>