2025-12-03 21:56:50 +00:00
|
|
|
/**
|
|
|
|
|
* Composant PlaylistHeader
|
|
|
|
|
* T0460: Create Playlist Detail Page
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import { Card, CardContent } from '@/components/ui/card';
|
|
|
|
|
import { Music, Lock, Users, Calendar, User } from 'lucide-react';
|
|
|
|
|
import { cn } from '@/lib/utils';
|
|
|
|
|
import type { Playlist } from '../types';
|
2025-12-25 11:07:29 +00:00
|
|
|
import { PlaylistFollowButton } from './PlaylistFollowButton';
|
2025-12-03 21:56:50 +00:00
|
|
|
|
|
|
|
|
interface PlaylistHeaderProps {
|
|
|
|
|
playlist: Playlist;
|
|
|
|
|
className?: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const formatDate = (dateString: string): string => {
|
|
|
|
|
const date = new Date(dateString);
|
|
|
|
|
return date.toLocaleDateString('fr-FR', {
|
|
|
|
|
year: 'numeric',
|
|
|
|
|
month: 'long',
|
|
|
|
|
day: 'numeric',
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export function PlaylistHeader({ playlist, className }: PlaylistHeaderProps) {
|
|
|
|
|
return (
|
2025-12-13 02:34:34 +00:00
|
|
|
<Card
|
|
|
|
|
className={cn('mb-6', className)}
|
|
|
|
|
role="region"
|
|
|
|
|
aria-label={`Détails de la playlist ${playlist.title}`}
|
|
|
|
|
>
|
2025-12-03 21:56:50 +00:00
|
|
|
<CardContent className="p-4 sm:p-6">
|
|
|
|
|
<div className="flex flex-col md:flex-row gap-4 sm:gap-6">
|
|
|
|
|
{/* Cover Image - Mobile optimized */}
|
refactor(ui): Design tokens - gradients, duration, textarea
- Replace cyan/magenta/purple gradients with primary/secondary
- duration-200/300 → duration-[var(--duration-normal)]
- Textarea: min-h-[100px] → min-h-24
- SearchPageHeader, DashboardPage, PlaylistHeader
- UserProfilePageHeader/Hero, PlaylistDetailPageHero
- SocialViewFeedItem, WishlistView, PostCard, ProductCard, CourseCard
- SearchPageResults, MarketplaceHome
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-10 21:56:30 +00:00
|
|
|
<div className="relative w-full md:w-64 h-48 sm:h-64 flex-shrink-0 rounded-lg overflow-hidden bg-gradient-to-br from-primary/40 to-secondary/40">
|
2025-12-03 21:56:50 +00:00
|
|
|
{playlist.cover_url ? (
|
|
|
|
|
<img
|
|
|
|
|
src={playlist.cover_url}
|
|
|
|
|
alt={`Couverture de la playlist ${playlist.title}`}
|
|
|
|
|
className="w-full h-full object-cover"
|
|
|
|
|
/>
|
|
|
|
|
) : (
|
2025-12-13 02:34:34 +00:00
|
|
|
<div
|
2025-12-03 21:56:50 +00:00
|
|
|
className="w-full h-full flex items-center justify-center"
|
|
|
|
|
role="img"
|
|
|
|
|
aria-label={`Pas de couverture pour la playlist ${playlist.title}`}
|
|
|
|
|
>
|
|
|
|
|
<Music className="w-24 h-24 text-white/50" aria-hidden="true" />
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
{/* Visibility Badge */}
|
|
|
|
|
<div className="absolute top-3 right-3">
|
|
|
|
|
{playlist.is_public ? (
|
2025-12-13 02:34:34 +00:00
|
|
|
<div
|
aesthetic-improvements: align spacing to 8px grid (Action 11.2.1.3)
- Created automated script (scripts/align-8px-grid.py) to align all spacing to 8px grid
- Replaced non-8px-aligned spacing: gap-3/p-3/m-3 (12px) → gap-4/p-4/m-4 (16px), gap-5/p-5/m-5 (20px) → gap-6/p-6/m-6 (24px), gap-10/p-10/m-10 (40px) → gap-12/p-12/m-12 (48px), gap-20/p-20/m-20 (80px) → gap-24/p-24/m-24 (96px)
- Preserved: 4px values (gap-1, p-1, m-1) as they may be intentional fine-tuning, responsive breakpoints (sm:, md:, lg:), test files, documentation
- Modified files across all components to ensure consistent 8px grid alignment
- Action 11.2.1.3: Align all elements to 8px grid - COMPLETE
2026-01-16 10:50:46 +00:00
|
|
|
className="bg-kodo-lime/100/90 text-white px-4 py-1.5 rounded-full text-sm flex items-center gap-2"
|
2025-12-03 21:56:50 +00:00
|
|
|
aria-label="Playlist publique"
|
|
|
|
|
>
|
|
|
|
|
<Users className="w-4 h-4" aria-hidden="true" />
|
|
|
|
|
Public
|
|
|
|
|
</div>
|
|
|
|
|
) : (
|
2025-12-13 02:34:34 +00:00
|
|
|
<div
|
2026-02-08 23:08:42 +00:00
|
|
|
className="bg-muted/90 text-white px-4 py-1.5 rounded-full text-sm flex items-center gap-2"
|
2025-12-03 21:56:50 +00:00
|
|
|
aria-label="Playlist privée"
|
|
|
|
|
>
|
|
|
|
|
<Lock className="w-4 h-4" aria-hidden="true" />
|
|
|
|
|
Privé
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Playlist Info - Mobile optimized */}
|
aesthetic-improvements: align spacing to 8px grid (Action 11.2.1.3)
- Created automated script (scripts/align-8px-grid.py) to align all spacing to 8px grid
- Replaced non-8px-aligned spacing: gap-3/p-3/m-3 (12px) → gap-4/p-4/m-4 (16px), gap-5/p-5/m-5 (20px) → gap-6/p-6/m-6 (24px), gap-10/p-10/m-10 (40px) → gap-12/p-12/m-12 (48px), gap-20/p-20/m-20 (80px) → gap-24/p-24/m-24 (96px)
- Preserved: 4px values (gap-1, p-1, m-1) as they may be intentional fine-tuning, responsive breakpoints (sm:, md:, lg:), test files, documentation
- Modified files across all components to ensure consistent 8px grid alignment
- Action 11.2.1.3: Align all elements to 8px grid - COMPLETE
2026-01-16 10:50:46 +00:00
|
|
|
<div className="flex-1 space-y-4 sm:space-y-4">
|
2025-12-25 11:07:29 +00:00
|
|
|
<div className="flex items-start justify-between gap-4">
|
|
|
|
|
<div className="flex-1">
|
|
|
|
|
<h1
|
|
|
|
|
className="text-2xl sm:text-3xl font-bold mb-2"
|
|
|
|
|
id="playlist-main-title"
|
2025-12-13 02:34:34 +00:00
|
|
|
>
|
2025-12-25 11:07:29 +00:00
|
|
|
{playlist.title}
|
|
|
|
|
</h1>
|
|
|
|
|
{playlist.description && (
|
|
|
|
|
<p
|
|
|
|
|
className="text-muted-foreground text-base sm:text-lg"
|
|
|
|
|
id="playlist-main-description"
|
|
|
|
|
>
|
|
|
|
|
{playlist.description}
|
|
|
|
|
</p>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
<PlaylistFollowButton
|
|
|
|
|
playlistId={playlist.id}
|
|
|
|
|
initialFollowerCount={(playlist as any).follower_count}
|
|
|
|
|
initialFollowing={(playlist as any).is_following}
|
|
|
|
|
/>
|
2025-12-03 21:56:50 +00:00
|
|
|
</div>
|
|
|
|
|
|
2025-12-13 02:34:34 +00:00
|
|
|
<div
|
aesthetic-improvements: align spacing to 8px grid (Action 11.2.1.3)
- Created automated script (scripts/align-8px-grid.py) to align all spacing to 8px grid
- Replaced non-8px-aligned spacing: gap-3/p-3/m-3 (12px) → gap-4/p-4/m-4 (16px), gap-5/p-5/m-5 (20px) → gap-6/p-6/m-6 (24px), gap-10/p-10/m-10 (40px) → gap-12/p-12/m-12 (48px), gap-20/p-20/m-20 (80px) → gap-24/p-24/m-24 (96px)
- Preserved: 4px values (gap-1, p-1, m-1) as they may be intentional fine-tuning, responsive breakpoints (sm:, md:, lg:), test files, documentation
- Modified files across all components to ensure consistent 8px grid alignment
- Action 11.2.1.3: Align all elements to 8px grid - COMPLETE
2026-01-16 10:50:46 +00:00
|
|
|
className="flex flex-wrap gap-4 sm:gap-4 text-xs sm:text-sm text-muted-foreground"
|
2025-12-13 02:34:34 +00:00
|
|
|
aria-labelledby="playlist-main-title"
|
|
|
|
|
>
|
2025-12-03 21:56:50 +00:00
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
<Music className="w-4 h-4" aria-hidden="true" />
|
2025-12-13 02:34:34 +00:00
|
|
|
<span>
|
|
|
|
|
{playlist.track_count} track
|
|
|
|
|
{playlist.track_count !== 1 ? 's' : ''}
|
|
|
|
|
</span>
|
2025-12-03 21:56:50 +00:00
|
|
|
</div>
|
|
|
|
|
{playlist.user && (
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
<User className="w-4 h-4" aria-hidden="true" />
|
2025-12-13 02:34:34 +00:00
|
|
|
<span aria-label={`Créée par ${playlist.user.username}`}>
|
|
|
|
|
par {playlist.user.username}
|
|
|
|
|
</span>
|
2025-12-03 21:56:50 +00:00
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
<Calendar className="w-4 h-4" aria-hidden="true" />
|
|
|
|
|
<span>Créée le {formatDate(playlist.created_at)}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</CardContent>
|
|
|
|
|
</Card>
|
|
|
|
|
);
|
|
|
|
|
}
|