import { useState, useEffect } from 'react'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { Button } from '@/components/ui/button'; import { Heart, Loader2 } from 'lucide-react'; import { cn } from '@/lib/utils'; import { likeTrack, unlikeTrack, getTrackLikes, } from '../services/interactionService'; import { useToast } from '@/hooks/useToast'; import { useUser } from '@/features/auth/hooks/useUser'; import { useIsRateLimited } from '@/hooks/useIsRateLimited'; /** * FE-COMP-016: Like/Unlike button component for tracks with count display */ interface LikeButtonProps { trackId: string; initialLikeCount?: number; initialIsLiked?: boolean; onLikeChange?: (isLiked: boolean, count: number) => void; className?: string; size?: 'default' | 'sm' | 'lg' | 'icon'; variant?: 'default' | 'outline' | 'ghost'; showCount?: boolean; compact?: boolean; } export function LikeButton({ trackId, initialLikeCount, initialIsLiked = false, onLikeChange, className, size = 'default', variant = 'ghost', showCount = true, compact = false, }: LikeButtonProps) { const { data: user } = useUser(); const { success: showSuccess, error: showError } = useToast(); const queryClient = useQueryClient(); const isRateLimited = useIsRateLimited(); const [isLiked, setIsLiked] = useState(initialIsLiked); const [likeCount, setLikeCount] = useState(initialLikeCount ?? 0); const [isUpdating, setIsUpdating] = useState(false); const [animating, setAnimating] = useState(false); // Fetch like status from API const { data: likesData } = useQuery({ queryKey: ['trackLikes', trackId], queryFn: () => getTrackLikes(trackId), enabled: !!trackId && !!user, staleTime: 30000, // 30 seconds retry: false, }); // Update state from API response useEffect(() => { if (likesData) { setIsLiked(likesData.isLiked); setLikeCount(likesData.count); } else if (initialIsLiked !== undefined) { setIsLiked(initialIsLiked); } if (initialLikeCount !== undefined) { setLikeCount(initialLikeCount); } }, [likesData, initialIsLiked, initialLikeCount]); // Like mutation const likeMutation = useMutation({ mutationFn: () => likeTrack(trackId), onMutate: async () => { // Optimistic update setIsLiked(true); setLikeCount((prev) => prev + 1); setIsUpdating(true); }, onSuccess: () => { showSuccess('Ajouté aux favoris'); onLikeChange?.(true, likeCount + 1); // Invalidate queries to refresh data queryClient.invalidateQueries({ queryKey: ['trackLikes', trackId] }); queryClient.invalidateQueries({ queryKey: ['tracks'] }); }, onError: (error: any) => { // Revert optimistic update setIsLiked(false); setLikeCount((prev) => Math.max(0, prev - 1)); const errorMessage = error.response?.data?.error?.message || error.response?.data?.message || error.message || "Erreur lors de l'ajout aux favoris"; showError(errorMessage); }, onSettled: () => { setIsUpdating(false); }, }); // Unlike mutation const unlikeMutation = useMutation({ mutationFn: () => unlikeTrack(trackId), onMutate: async () => { // Optimistic update setIsLiked(false); setLikeCount((prev) => Math.max(0, prev - 1)); setIsUpdating(true); }, onSuccess: () => { showSuccess('Retiré des favoris'); onLikeChange?.(false, Math.max(0, likeCount - 1)); // Invalidate queries to refresh data queryClient.invalidateQueries({ queryKey: ['trackLikes', trackId] }); queryClient.invalidateQueries({ queryKey: ['tracks'] }); }, onError: (error: any) => { // Revert optimistic update setIsLiked(true); setLikeCount((prev) => prev + 1); const errorMessage = error.response?.data?.error?.message || error.response?.data?.message || error.message || 'Erreur lors du retrait des favoris'; showError(errorMessage); }, onSettled: () => { setIsUpdating(false); }, }); const handleClick = (e: React.MouseEvent) => { e.stopPropagation(); if (isUpdating || !user || isRateLimited) return; // Trigger bounce animation on like (not unlike) if (!isLiked) { setAnimating(true); setTimeout(() => setAnimating(false), 400); } if (isLiked) { unlikeMutation.mutate(); } else { likeMutation.mutate(); } }; // Don't show button if user is not logged in if (!user) { return null; } const isLoading = likeMutation.isPending || unlikeMutation.isPending || isUpdating; return ( ); }