feat: roll out illustrative empty states across 8 pages with 2 new guide entries
- TreeLibraryPage: split empty state into no-flows (illustration + CTA) vs no-filter-results - MyAnalyticsPage/TeamAnalyticsPage: add zero-sessions empty state with illustration - SessionHistoryPage: split into no-sessions (illustration) vs no-filter-results - StepLibraryBrowser: illustrative empty state when no steps exist - ScriptTemplateList: replace plain empty state with ScriptIllustration - MySharesPage: replace icon-based empty state with ShareIllustration - IntegrationsPage: add IntegrationIllustration above setup form - Add script-templates and psa-setup guides to guides data - Add EmptyState vitest tests (7 tests) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
92
frontend/src/components/common/__tests__/EmptyState.test.tsx
Normal file
92
frontend/src/components/common/__tests__/EmptyState.test.tsx
Normal file
@@ -0,0 +1,92 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import { BrowserRouter } from 'react-router-dom'
|
||||
import { EmptyState } from '../EmptyState'
|
||||
import { FlowIllustration } from '../EmptyStateIllustrations'
|
||||
|
||||
function renderWithRouter(ui: React.ReactElement) {
|
||||
return render(<BrowserRouter>{ui}</BrowserRouter>)
|
||||
}
|
||||
|
||||
describe('EmptyState', () => {
|
||||
it('renders title and description', () => {
|
||||
renderWithRouter(
|
||||
<EmptyState
|
||||
title="No items found"
|
||||
description="Try adjusting your filters."
|
||||
/>
|
||||
)
|
||||
|
||||
expect(screen.getByText('No items found')).toBeInTheDocument()
|
||||
expect(screen.getByText('Try adjusting your filters.')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders illustration when provided', () => {
|
||||
const { container } = renderWithRouter(
|
||||
<EmptyState
|
||||
title="Empty"
|
||||
illustration={<FlowIllustration />}
|
||||
/>
|
||||
)
|
||||
|
||||
const svg = container.querySelector('svg')
|
||||
expect(svg).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders action button', () => {
|
||||
renderWithRouter(
|
||||
<EmptyState
|
||||
title="No data"
|
||||
action={<button>Create New</button>}
|
||||
/>
|
||||
)
|
||||
|
||||
expect(screen.getByRole('button', { name: 'Create New' })).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders learn more link with correct href', () => {
|
||||
renderWithRouter(
|
||||
<EmptyState
|
||||
title="Get started"
|
||||
learnMoreLink="/guides/creating-flows"
|
||||
/>
|
||||
)
|
||||
|
||||
const link = screen.getByText(/Learn more/i)
|
||||
expect(link).toBeInTheDocument()
|
||||
expect(link).toHaveAttribute('href', '/guides/creating-flows')
|
||||
})
|
||||
|
||||
it('renders custom learn more text', () => {
|
||||
renderWithRouter(
|
||||
<EmptyState
|
||||
title="Get started"
|
||||
learnMoreLink="/guides/test"
|
||||
learnMoreText="View guide"
|
||||
/>
|
||||
)
|
||||
|
||||
expect(screen.getByText(/View guide/i)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders without optional props', () => {
|
||||
renderWithRouter(<EmptyState title="Just a title" />)
|
||||
|
||||
expect(screen.getByText('Just a title')).toBeInTheDocument()
|
||||
expect(screen.queryByRole('button')).not.toBeInTheDocument()
|
||||
expect(screen.queryByRole('link')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('prefers illustration over icon when both provided', () => {
|
||||
const { container } = renderWithRouter(
|
||||
<EmptyState
|
||||
title="Test"
|
||||
icon={<span data-testid="icon">icon</span>}
|
||||
illustration={<FlowIllustration />}
|
||||
/>
|
||||
)
|
||||
|
||||
expect(container.querySelector('svg')).toBeInTheDocument()
|
||||
expect(screen.queryByTestId('icon')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
@@ -1,5 +1,7 @@
|
||||
import { FileCode, Search } from 'lucide-react'
|
||||
import { Search } from 'lucide-react'
|
||||
import { useScriptGeneratorStore } from '@/store/scriptGeneratorStore'
|
||||
import { EmptyState } from '@/components/common/EmptyState'
|
||||
import { ScriptIllustration } from '@/components/common/EmptyStateIllustrations'
|
||||
import { TemplateCard } from './TemplateCard'
|
||||
|
||||
interface Props {
|
||||
@@ -52,10 +54,13 @@ export function ScriptTemplateList({ inputValue, onClearSearch, onConfigure }: P
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center gap-3 py-12 text-center px-4">
|
||||
<FileCode size={32} className="text-muted-foreground/40" />
|
||||
<p className="text-sm text-muted-foreground">No templates found</p>
|
||||
</div>
|
||||
<EmptyState
|
||||
illustration={<ScriptIllustration />}
|
||||
title="Automate with script templates"
|
||||
description="Pre-built and custom scripts your team can reference during sessions. PowerShell, bash, and more."
|
||||
learnMoreLink="/guides/script-templates"
|
||||
className="px-4"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { useState, useEffect, useMemo } from 'react'
|
||||
import { Search, ChevronDown, ChevronUp, Loader2 } from 'lucide-react'
|
||||
import { Button } from '@/components/ui/Button'
|
||||
import { EmptyState } from '@/components/common/EmptyState'
|
||||
import { StepLibraryIllustration } from '@/components/common/EmptyStateIllustrations'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { stepsApi } from '@/api/steps'
|
||||
import { stepCategoriesApi } from '@/api/stepCategories'
|
||||
@@ -259,12 +261,24 @@ export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = f
|
||||
</Button>
|
||||
</div>
|
||||
) : steps.length === 0 ? (
|
||||
<div className="rounded-lg border border-border bg-accent/50 p-12 text-center">
|
||||
<p className="mb-2 text-lg font-medium text-foreground">No steps found</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{hasActiveFilters ? 'Try adjusting your filters' : 'Create your first step to get started!'}
|
||||
</p>
|
||||
</div>
|
||||
hasActiveFilters ? (
|
||||
<div className="rounded-lg border border-border bg-accent/50 p-12 text-center">
|
||||
<p className="mb-2 text-lg font-medium text-foreground">No steps found</p>
|
||||
<p className="text-sm text-muted-foreground">Try adjusting your filters</p>
|
||||
</div>
|
||||
) : (
|
||||
<EmptyState
|
||||
illustration={<StepLibraryIllustration />}
|
||||
title="Build a reusable step library"
|
||||
description="Save common troubleshooting steps once, reuse them across flows. Keeps your team consistent and saves build time."
|
||||
action={
|
||||
onCreateNew ? (
|
||||
<Button onClick={onCreateNew}>Browse Steps</Button>
|
||||
) : undefined
|
||||
}
|
||||
learnMoreLink="/guides/step-library"
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
<div className="space-y-6">
|
||||
{/* My Steps */}
|
||||
|
||||
Reference in New Issue
Block a user