import { useState, useEffect } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { Button } from '@/components/ui/button'; import { UserPlus, UserCheck } from 'lucide-react'; import { LoadingSpinner } from '@/components/ui/loading-spinner'; import { cn } from '@/lib/utils'; import { followPlaylist, unfollowPlaylist, getPlaylist, getPlaylistFollowStatus, } from '../services/playlistService'; import type { Playlist } from '../types'; import { useToast } from '@/hooks/useToast'; import { useTranslation } from '@/hooks/useTranslation'; import { useUser } from '@/features/auth/hooks/useUser'; /** * FE-COMP-017: Follow/Unfollow button component for playlists */ interface PlaylistFollowButtonProps { playlistId: string; initialFollowing?: boolean; initialFollowerCount?: number; onFollowChange?: (isFollowing: boolean) => void; className?: string; size?: 'default' | 'sm' | 'lg' | 'icon'; variant?: 'default' | 'outline' | 'ghost'; showCount?: boolean; } export function PlaylistFollowButton({ playlistId, initialFollowing = false, initialFollowerCount = 0, onFollowChange, className, size = 'default', variant, showCount = false, }: PlaylistFollowButtonProps) { const { t } = useTranslation(); const { data: user } = useUser(); const { success: showSuccess, error: showError } = useToast(); const queryClient = useQueryClient(); const [following, setFollowing] = useState(initialFollowing); const [followerCount, setFollowerCount] = useState(initialFollowerCount); const [isUpdating, setIsUpdating] = useState(false); // Fetch playlist to get current follow status const { data: playlist } = useQuery({ queryKey: ['playlist', playlistId], queryFn: () => getPlaylist(playlistId), enabled: !!playlistId && !!user, staleTime: 30000, // 30 seconds }); // Try to fetch follow status if available const { data: followStatus } = useQuery({ queryKey: ['playlistFollowStatus', playlistId], queryFn: () => getPlaylistFollowStatus(playlistId), enabled: !!playlistId && !!user, staleTime: 30000, retry: false, }); // Update state from API response useEffect(() => { if (followStatus) { setFollowing(followStatus.is_following); setFollowerCount(followStatus.follower_count); } else if (playlist && playlist.is_following !== undefined) { setFollowing(playlist.is_following); } else if (initialFollowing !== undefined) { setFollowing(initialFollowing); } if (playlist && playlist.follower_count !== undefined) { setFollowerCount(playlist.follower_count); } else if (initialFollowerCount !== undefined) { setFollowerCount(initialFollowerCount); } }, [followStatus, playlist, initialFollowing, initialFollowerCount]); // Follow mutation const followMutation = useMutation({ mutationFn: () => followPlaylist(playlistId), onMutate: async () => { // Optimistic update setFollowing(true); setFollowerCount((prev) => prev + 1); setIsUpdating(true); }, onSuccess: () => { showSuccess(t('playlists.followBtn.followSuccess')); onFollowChange?.(true); // Invalidate queries to refresh data queryClient.invalidateQueries({ queryKey: ['playlist', playlistId] }); queryClient.invalidateQueries({ queryKey: ['playlistFollowStatus', playlistId], }); queryClient.invalidateQueries({ queryKey: ['playlists'] }); }, onError: (error: unknown) => { // Revert optimistic update setFollowing(false); setFollowerCount((prev) => Math.max(0, prev - 1)); const err = error as { response?: { data?: { error?: { message?: string }; message?: string } }; message?: string }; const errorMessage = err.response?.data?.error?.message || err.response?.data?.message || err.message || t('playlists.followBtn.followError'); showError(errorMessage); }, onSettled: () => { setIsUpdating(false); }, }); // Unfollow mutation const unfollowMutation = useMutation({ mutationFn: () => unfollowPlaylist(playlistId), onMutate: async () => { // Optimistic update setFollowing(false); setFollowerCount((prev) => Math.max(0, prev - 1)); setIsUpdating(true); }, onSuccess: () => { showSuccess(t('playlists.followBtn.unfollowSuccess')); onFollowChange?.(false); // Invalidate queries to refresh data queryClient.invalidateQueries({ queryKey: ['playlist', playlistId] }); queryClient.invalidateQueries({ queryKey: ['playlistFollowStatus', playlistId], }); queryClient.invalidateQueries({ queryKey: ['playlists'] }); }, onError: (error: unknown) => { // Revert optimistic update setFollowing(true); setFollowerCount((prev) => prev + 1); const err = error as { response?: { data?: { error?: { message?: string }; message?: string } }; message?: string }; const errorMessage = err.response?.data?.error?.message || err.response?.data?.message || err.message || t('playlists.followBtn.unfollowError'); showError(errorMessage); }, onSettled: () => { setIsUpdating(false); }, }); const handleClick = (e: React.MouseEvent) => { e.stopPropagation(); if (isUpdating || !user) return; if (following) { unfollowMutation.mutate(); } else { followMutation.mutate(); } }; // Don't show follow button if user is not logged in or is the owner if (!user || user.id === playlist?.user_id) { return null; } const isLoading = followMutation.isPending || unfollowMutation.isPending || isUpdating; const buttonVariant = variant || (following ? 'outline' : 'default'); return ( ); }