From 40ffe3b722c54e67b2272ec6b4e2c9b3e8d487dd Mon Sep 17 00:00:00 2001
From: Duy Luong <33130695+lmduy2612@users.noreply.github.com>
Date: Mon, 12 Oct 2026 22:18:44 +0700
Subject: [PATCH] fix(ux): add helpful empty state messages to list views
(#164)
---
src/components/sections/activity-feed.tsx | 13 +++++-
src/components/sections/top-tippers-table.tsx | 8 ++--
src/components/ui/empty-state.test.tsx | 46 +++++++++++++++++++
src/components/ui/empty-state.tsx | 38 +++++++++++++++
src/components/ui/index.ts | 4 ++
5 files changed, 104 insertions(+), 5 deletions(-)
create mode 100644 src/components/ui/empty-state.test.tsx
create mode 100644 src/components/ui/empty-state.tsx
diff --git a/src/components/sections/activity-feed.tsx b/src/components/sections/activity-feed.tsx
index caca089..b0ef3df 100644
--- a/src/components/sections/activity-feed.tsx
+++ b/src/components/sections/activity-feed.tsx
@@ -10,6 +10,7 @@ import { ActivityFeedItem, ActivityFeedFilters, ActivityType } from '@/types';
import { formatDateTime, formatCurrency } from '@/utils/formatters';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
+import { EmptyState } from '@/components/ui/empty-state';
import {
Bell,
Gift,
@@ -126,8 +127,16 @@ export function ActivityFeed({
{/* Activity List */}
- {filteredItems.length === 0 && !loading ? (
-
{filteredItems.map((item) => (
diff --git a/src/components/sections/top-tippers-table.tsx b/src/components/sections/top-tippers-table.tsx
index 6359a70..8a993cf 100644
--- a/src/components/sections/top-tippers-table.tsx
+++ b/src/components/sections/top-tippers-table.tsx
@@ -5,6 +5,7 @@
import type { TopTipper } from '@/types';
import { formatCurrency, formatDate } from '@/utils/formatters';
+import { EmptyState } from '@/components/ui/empty-state';
export interface TopTippersTableProps {
data: TopTipper[];
@@ -13,9 +14,10 @@ export interface TopTippersTableProps {
export function TopTippersTable({ data }: TopTippersTableProps): JSX.Element {
if (data.length === 0) {
return (
-
- No tippers for this period
-
+
);
}
diff --git a/src/components/ui/empty-state.test.tsx b/src/components/ui/empty-state.test.tsx
new file mode 100644
index 0000000..968aef1
--- /dev/null
+++ b/src/components/ui/empty-state.test.tsx
@@ -0,0 +1,46 @@
+import { describe, it, expect } from 'vitest';
+import { render, screen } from '@testing-library/react';
+import { EmptyState } from './empty-state';
+import { Inbox, Plus } from 'lucide-react';
+import { Button } from './button';
+
+describe('EmptyState Component (#164)', () => {
+ it('renders title and description properly', () => {
+ render(
+
+ );
+
+ expect(screen.getByRole('status')).toBeInTheDocument();
+ expect(screen.getByText('No transactions yet')).toBeInTheDocument();
+ expect(
+ screen.getByText('Send a tip or link your wallet to see your transaction history.')
+ ).toBeInTheDocument();
+ });
+
+ it('renders custom icon when provided', () => {
+ render(
+
+ );
+
+ expect(screen.getByTestId('empty-state')).toBeInTheDocument();
+ expect(screen.getByText('Empty inbox')).toBeInTheDocument();
+ });
+
+ it('renders optional action button', () => {
+ render(
+
Create New}
+ />
+ );
+
+ expect(screen.getByRole('button', { name: /create new/i })).toBeInTheDocument();
+ });
+});
diff --git a/src/components/ui/empty-state.tsx b/src/components/ui/empty-state.tsx
new file mode 100644
index 0000000..23c4adf
--- /dev/null
+++ b/src/components/ui/empty-state.tsx
@@ -0,0 +1,38 @@
+import * as React from 'react';
+import { LucideIcon } from 'lucide-react';
+import { cn } from '@/lib/utils';
+import { Card } from '@/components/ui/card';
+
+export interface EmptyStateProps extends React.HTMLAttributes {
+ icon?: LucideIcon | React.ComponentType<{ className?: string }>;
+ title: string;
+ description?: string;
+ action?: React.ReactNode;
+}
+
+export function EmptyState({
+ icon: Icon,
+ title,
+ description,
+ action,
+ className,
+ ...props
+}: EmptyStateProps): JSX.Element {
+ return (
+
+ {Icon && (
+
+
+
+ )}
+ {title}
+ {description && {description}
}
+ {action && {action}
}
+
+ );
+}
diff --git a/src/components/ui/index.ts b/src/components/ui/index.ts
index 2e3b8c2..7f4a10f 100644
--- a/src/components/ui/index.ts
+++ b/src/components/ui/index.ts
@@ -47,3 +47,7 @@ export {
DropdownMenuSubTrigger,
DropdownMenuRadioGroup,
} from './dropdown-menu';
+
+export { EmptyState } from './empty-state';
+export type { EmptyStateProps } from './empty-state';
+