veza/apps/web/src/features/tracks/components/LikeButton.tsx

101 lines
2.6 KiB
TypeScript
Raw Normal View History

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>
);
}