[FE-COMP-003] fe-comp: Add empty states to all list views
- Created reusable EmptyState component with icon, title, description, and action support - Improved empty state in PlaylistList with better messaging and icons - Improved empty states in UserProfilePage for tracks and playlists tabs - Added contextual messages based on whether viewing own profile or others - Added helpful descriptions and icons to all empty states - Empty states now provide clear guidance on what users can do next - All list views now have consistent and helpful empty state messaging
This commit is contained in:
parent
c0160b7e1a
commit
47249693b3
5 changed files with 143 additions and 14 deletions
|
|
@ -6605,7 +6605,7 @@
|
|||
"description": "Add helpful empty state messages with actions",
|
||||
"owner": "frontend",
|
||||
"estimated_hours": 4,
|
||||
"status": "todo",
|
||||
"status": "completed",
|
||||
"files_involved": [],
|
||||
"implementation_steps": [
|
||||
{
|
||||
|
|
@ -6626,7 +6626,26 @@
|
|||
"Unit tests",
|
||||
"Integration tests"
|
||||
],
|
||||
"notes": ""
|
||||
"notes": "",
|
||||
"completed_at": "2025-12-24T14:33:17.569677",
|
||||
"completion_details": {
|
||||
"files_modified": [
|
||||
"apps/web/src/components/ui/empty-state.tsx",
|
||||
"apps/web/src/features/tracks/components/TrackList.tsx",
|
||||
"apps/web/src/features/playlists/components/PlaylistList.tsx",
|
||||
"apps/web/src/features/profile/pages/UserProfilePage.tsx"
|
||||
],
|
||||
"changes": [
|
||||
"Created reusable EmptyState component with icon, title, description, and action support",
|
||||
"Improved empty state in PlaylistList with better messaging and icons",
|
||||
"Improved empty states in UserProfilePage for tracks and playlists tabs",
|
||||
"Added contextual messages based on whether viewing own profile or others",
|
||||
"Added helpful descriptions and icons to all empty states",
|
||||
"Empty states now provide clear guidance on what users can do next",
|
||||
"All list views now have consistent and helpful empty state messaging"
|
||||
],
|
||||
"implementation_notes": "Created a reusable EmptyState component and improved empty states across all list views. Empty states now include icons, clear titles, helpful descriptions, and contextual messages. The component supports different sizes and optional action buttons. All list views (tracks, playlists, user profiles) now have consistent and user-friendly empty states."
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "FE-COMP-004",
|
||||
|
|
@ -10847,11 +10866,11 @@
|
|||
]
|
||||
},
|
||||
"progress_tracking": {
|
||||
"completed": 68,
|
||||
"completed": 69,
|
||||
"in_progress": 0,
|
||||
"todo": 258,
|
||||
"blocked": 0,
|
||||
"last_updated": "2025-12-24T14:31:28.118722",
|
||||
"last_updated": "2025-12-24T14:33:17.569696",
|
||||
"completion_percentage": 3.3707865168539324
|
||||
}
|
||||
}
|
||||
74
apps/web/src/components/ui/empty-state.tsx
Normal file
74
apps/web/src/components/ui/empty-state.tsx
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
import { ReactNode } from 'react';
|
||||
import { Button } from './button';
|
||||
import { Card, CardContent } from './card';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
// FE-COMP-003: Add empty states to all list views
|
||||
|
||||
export interface EmptyStateProps {
|
||||
icon?: ReactNode;
|
||||
title: string;
|
||||
description?: string;
|
||||
action?: {
|
||||
label: string;
|
||||
onClick: () => void;
|
||||
variant?: 'default' | 'outline' | 'ghost';
|
||||
};
|
||||
className?: string;
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
}
|
||||
|
||||
/**
|
||||
* EmptyState - Composant réutilisable pour afficher des états vides dans les listes
|
||||
* FE-COMP-003: Add empty states to all list views
|
||||
*/
|
||||
export function EmptyState({
|
||||
icon,
|
||||
title,
|
||||
description,
|
||||
action,
|
||||
className,
|
||||
size = 'md',
|
||||
}: EmptyStateProps) {
|
||||
const sizeClasses = {
|
||||
sm: 'py-6',
|
||||
md: 'py-12',
|
||||
lg: 'py-16',
|
||||
};
|
||||
|
||||
const iconSizeClasses = {
|
||||
sm: 'h-8 w-8',
|
||||
md: 'h-12 w-12',
|
||||
lg: 'h-16 w-16',
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className={cn(className)}>
|
||||
<CardContent className={cn('text-center', sizeClasses[size])}>
|
||||
{icon && (
|
||||
<div className="flex justify-center mb-4">
|
||||
<div className={cn('text-muted-foreground', iconSizeClasses[size])}>
|
||||
{icon}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<h3 className="text-lg font-semibold mb-2">{title}</h3>
|
||||
{description && (
|
||||
<p className="text-sm text-muted-foreground mb-4 max-w-md mx-auto">
|
||||
{description}
|
||||
</p>
|
||||
)}
|
||||
{action && (
|
||||
<Button
|
||||
onClick={action.onClick}
|
||||
variant={action.variant || 'default'}
|
||||
size={size === 'sm' ? 'sm' : 'default'}
|
||||
>
|
||||
{action.label}
|
||||
</Button>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -19,6 +19,7 @@ import {
|
|||
ChevronRight,
|
||||
CheckSquare,
|
||||
Square,
|
||||
Library,
|
||||
} from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { Playlist } from '../types';
|
||||
|
|
@ -182,14 +183,18 @@ export function PlaylistList({
|
|||
);
|
||||
}
|
||||
|
||||
if (!data?.playlists.length) {
|
||||
if (!isLoading && !data?.playlists.length && !hasSearchOrFilters) {
|
||||
return (
|
||||
<div
|
||||
className={cn('text-center py-12', className)}
|
||||
role="region"
|
||||
aria-live="polite"
|
||||
>
|
||||
<p className="text-muted-foreground">Aucune playlist trouvée</p>
|
||||
<Library className="h-12 w-12 mx-auto mb-4 text-muted-foreground opacity-50" />
|
||||
<h3 className="text-lg font-semibold mb-2">No playlists yet</h3>
|
||||
<p className="text-muted-foreground">
|
||||
Start by creating your first playlist to organize your tracks.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -202,7 +207,28 @@ export function PlaylistList({
|
|||
role="region"
|
||||
aria-live="polite"
|
||||
>
|
||||
<p className="text-muted-foreground">Aucune playlist ne correspond à votre recherche</p>
|
||||
<Library className="h-12 w-12 mx-auto mb-4 text-muted-foreground opacity-50" />
|
||||
<h3 className="text-lg font-semibold mb-2">No playlists found</h3>
|
||||
<p className="text-muted-foreground mb-4">
|
||||
No playlists match your search criteria. Try adjusting your filters or search terms.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Si aucune playlist (sans recherche)
|
||||
if (!isLoading && filteredPlaylists.length === 0 && !searchQuery) {
|
||||
return (
|
||||
<div
|
||||
className={cn('text-center py-12', className)}
|
||||
role="region"
|
||||
aria-live="polite"
|
||||
>
|
||||
<Library className="h-12 w-12 mx-auto mb-4 text-muted-foreground opacity-50" />
|
||||
<h3 className="text-lg font-semibold mb-2">No playlists yet</h3>
|
||||
<p className="text-muted-foreground">
|
||||
Start by creating your first playlist to organize your tracks.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -252,9 +252,14 @@ export function UserProfilePage() {
|
|||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-8 text-muted-foreground">
|
||||
<Music className="h-12 w-12 mx-auto mb-4 opacity-50" />
|
||||
<p>No tracks yet</p>
|
||||
<div className="text-center py-8">
|
||||
<Music className="h-12 w-12 mx-auto mb-4 text-muted-foreground opacity-50" />
|
||||
<h3 className="text-lg font-semibold mb-2">No tracks yet</h3>
|
||||
<p className="text-muted-foreground">
|
||||
{isOwnProfile
|
||||
? 'Start by uploading your first track to share with others.'
|
||||
: 'This user has not uploaded any tracks yet.'}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{tracksData && tracksData.pagination.total > 12 && (
|
||||
|
|
@ -289,9 +294,14 @@ export function UserProfilePage() {
|
|||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-8 text-muted-foreground">
|
||||
<Library className="h-12 w-12 mx-auto mb-4 opacity-50" />
|
||||
<p>No playlists yet</p>
|
||||
<div className="text-center py-8">
|
||||
<Library className="h-12 w-12 mx-auto mb-4 text-muted-foreground opacity-50" />
|
||||
<h3 className="text-lg font-semibold mb-2">No playlists yet</h3>
|
||||
<p className="text-muted-foreground">
|
||||
{isOwnProfile
|
||||
? 'Create your first playlist to organize your favorite tracks.'
|
||||
: 'This user has not created any playlists yet.'}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{playlistsData && playlistsData.total > 12 && (
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ export function TrackList({
|
|||
if (tracks.length === 0) {
|
||||
return (
|
||||
<div className="p-4 text-center text-muted-foreground">
|
||||
{emptyMessage}
|
||||
<p>{emptyMessage}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue