diff --git a/src/components/common/EmptyState.tsx b/src/components/common/EmptyState.tsx index c1349413..82b4ee26 100644 --- a/src/components/common/EmptyState.tsx +++ b/src/components/common/EmptyState.tsx @@ -3,12 +3,20 @@ import { Button } from '@/components/ui/button'; import { RotateCcw } from 'lucide-react'; import { EMPTY_STATE_ILLUSTRATION_SIZES } from './emptyStateIllustration.config'; +interface EmptyStateCta { + label: string; + onClick: () => void; +} + interface EmptyStateProps { image: string; title: string; description: string; className?: string; + /** @deprecated Use `cta` — kept for the existing search-reset callers. */ onReset?: () => void; + /** Generic optional call-to-action, e.g. linking a zero-result list back to discovery. */ + cta?: EmptyStateCta; } const EmptyState: React.FC = ({ @@ -17,6 +25,7 @@ const EmptyState: React.FC = ({ description, className, onReset, + cta, }: EmptyStateProps) => { return (
= ({ Reset Search )} + + {!onReset && cta && ( + + )}
); }; diff --git a/src/components/common/__tests__/EmptyState.test.tsx b/src/components/common/__tests__/EmptyState.test.tsx new file mode 100644 index 00000000..f5744e87 --- /dev/null +++ b/src/components/common/__tests__/EmptyState.test.tsx @@ -0,0 +1,75 @@ +import { fireEvent, render, screen } from '@testing-library/react'; +import { describe, expect, it, vi } from 'vitest'; +import EmptyState from '../EmptyState'; + +describe('EmptyState', () => { + it('renders the title and description', () => { + render( + + ); + + expect(screen.getByText('No holdings yet')).toBeInTheDocument(); + expect( + screen.getByText("You don't currently hold any creator keys.") + ).toBeInTheDocument(); + }); + + it('renders no action button when neither onReset nor cta is supplied', () => { + render( + + ); + + expect(screen.queryByRole('button')).not.toBeInTheDocument(); + }); + + it('renders the legacy reset button and calls onReset when clicked', () => { + const onReset = vi.fn(); + render( + + ); + + fireEvent.click(screen.getByRole('button', { name: /reset search results/i })); + expect(onReset).toHaveBeenCalledTimes(1); + }); + + it('renders a generic cta button and calls its onClick when clicked', () => { + const onClick = vi.fn(); + render( + + ); + + fireEvent.click(screen.getByRole('button', { name: 'Discover creators' })); + expect(onClick).toHaveBeenCalledTimes(1); + }); + + it('prefers onReset over cta when both are somehow supplied', () => { + const onReset = vi.fn(); + const onClick = vi.fn(); + render( + + ); + + expect(screen.getByRole('button', { name: /reset search results/i })).toBeInTheDocument(); + expect(screen.queryByRole('button', { name: 'Discover creators' })).not.toBeInTheDocument(); + }); +}); diff --git a/src/pages/LandingPage.tsx b/src/pages/LandingPage.tsx index 9c26ce8b..220fd6ce 100644 --- a/src/pages/LandingPage.tsx +++ b/src/pages/LandingPage.tsx @@ -1078,6 +1078,23 @@ function LandingPage() { {isLoading ? ( + ) : heldKeyPositions.filter( + position => position.quantity && position.quantity > 0 + ).length === 0 ? ( + { + document + .getElementById('main-creator-list') + ?.scrollIntoView({ behavior: 'smooth', block: 'start' }); + }, + }} + /> ) : (
{heldKeyPositions