101 lines
2.6 KiB
TypeScript
101 lines
2.6 KiB
TypeScript
|
|
import { useState, useEffect } from 'react';
|
||
|
|
import { Heart } from 'lucide-react';
|
||
|
|
import { likeTrack, unlikeTrack, getTrackLikes, TrackUploadError } from '../services/trackService';
|
||
|
|
import { Button } from '@/components/ui/button';
|
||
|
|
import { useToast } from '@/hooks/useToast';
|
||
|
|
import { cn } from '@/lib/utils';
|
||
|
|
|
||
|
|
interface LikeButtonProps {
|
||
|
|
trackId: number;
|
||
|
|
className?: string;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* LikeButton Component
|
||
|
|
* T0285: Composant pour liker/unliker un track avec animation
|
||
|
|
*/
|
||
|
|
export function LikeButton({ trackId, className }: LikeButtonProps) {
|
||
|
|
const [isLiked, setIsLiked] = useState(false);
|
||
|
|
const [count, setCount] = useState(0);
|
||
|
|
const [loading, setLoading] = useState(false);
|
||
|
|
const [animating, setAnimating] = useState(false);
|
||
|
|
const { toast } = useToast();
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
loadLikes();
|
||
|
|
}, [trackId]);
|
||
|
|
|
||
|
|
const loadLikes = async () => {
|
||
|
|
try {
|
||
|
|
const data = await getTrackLikes(trackId);
|
||
|
|
setIsLiked(data.isLiked);
|
||
|
|
setCount(data.count);
|
||
|
|
} catch (error) {
|
||
|
|
console.error('Failed to load likes:', error);
|
||
|
|
// Ne pas afficher d'erreur toast pour le chargement initial
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
const handleToggle = async () => {
|
||
|
|
if (loading) return;
|
||
|
|
|
||
|
|
setLoading(true);
|
||
|
|
setAnimating(true);
|
||
|
|
|
||
|
|
// Animation duration
|
||
|
|
setTimeout(() => setAnimating(false), 300);
|
||
|
|
|
||
|
|
try {
|
||
|
|
if (isLiked) {
|
||
|
|
await unlikeTrack(trackId);
|
||
|
|
setIsLiked(false);
|
||
|
|
setCount((c) => Math.max(0, c - 1));
|
||
|
|
} else {
|
||
|
|
await likeTrack(trackId);
|
||
|
|
setIsLiked(true);
|
||
|
|
setCount((c) => c + 1);
|
||
|
|
}
|
||
|
|
} catch (error) {
|
||
|
|
console.error('Failed to toggle like:', error);
|
||
|
|
|
||
|
|
// Revert state on error
|
||
|
|
setIsLiked((prev) => !prev);
|
||
|
|
setCount((c) => (isLiked ? c + 1 : Math.max(0, c - 1)));
|
||
|
|
|
||
|
|
// Show error toast
|
||
|
|
if (error instanceof TrackUploadError) {
|
||
|
|
toast.error(error.message);
|
||
|
|
} else {
|
||
|
|
toast.error('Impossible de modifier le like. Veuillez réessayer.');
|
||
|
|
}
|
||
|
|
} finally {
|
||
|
|
setLoading(false);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
return (
|
||
|
|
<Button
|
||
|
|
variant="ghost"
|
||
|
|
size="sm"
|
||
|
|
onClick={handleToggle}
|
||
|
|
disabled={loading}
|
||
|
|
className={cn(
|
||
|
|
'transition-all duration-200',
|
||
|
|
animating && 'scale-110',
|
||
|
|
isLiked && 'text-red-500 hover:text-red-600',
|
||
|
|
className
|
||
|
|
)}
|
||
|
|
aria-label={isLiked ? 'Retirer le like' : 'Ajouter un like'}
|
||
|
|
>
|
||
|
|
<Heart
|
||
|
|
className={cn(
|
||
|
|
'w-4 h-4 mr-2 transition-all duration-200',
|
||
|
|
isLiked && 'fill-red-500 text-red-500',
|
||
|
|
animating && 'scale-125'
|
||
|
|
)}
|
||
|
|
/>
|
||
|
|
{count > 0 && <span className="text-sm">{count}</span>}
|
||
|
|
</Button>
|
||
|
|
);
|
||
|
|
}
|