150 lines
4.0 KiB
150 lines
4.0 KiB
import clsx from 'clsx';
|
|
import { useRouter } from 'next/router';
|
|
import { useState } from 'react';
|
|
import {
|
|
ArrowDownCircleIcon,
|
|
ArrowUpCircleIcon,
|
|
} from '@heroicons/react/20/solid';
|
|
import { Vote } from '@prisma/client';
|
|
|
|
import { useGoogleAnalytics } from '~/components/global/GoogleAnalytics';
|
|
|
|
import { trpc } from '~/utils/trpc';
|
|
|
|
type ResumeCommentVoteButtonsProps = {
|
|
commentId: string;
|
|
userId: string | undefined;
|
|
};
|
|
|
|
export default function ResumeCommentVoteButtons({
|
|
commentId,
|
|
userId,
|
|
}: ResumeCommentVoteButtonsProps) {
|
|
const [upvoteAnimation, setUpvoteAnimation] = useState(false);
|
|
const [downvoteAnimation, setDownvoteAnimation] = useState(false);
|
|
const { event: gaEvent } = useGoogleAnalytics();
|
|
|
|
const trpcContext = trpc.useContext();
|
|
const router = useRouter();
|
|
|
|
// COMMENT VOTES
|
|
const commentVotesQuery = trpc.useQuery([
|
|
'resumes.comments.votes.list',
|
|
{ commentId },
|
|
]);
|
|
const commentVotesUpsertMutation = trpc.useMutation(
|
|
'resumes.comments.votes.user.upsert',
|
|
{
|
|
onSuccess: () => {
|
|
// Comment updated, invalidate query to trigger refetch
|
|
trpcContext.invalidateQueries(['resumes.comments.votes.list']);
|
|
gaEvent({
|
|
action: 'resumes.comment_vote',
|
|
category: 'engagement',
|
|
label: 'Upvote/Downvote comment',
|
|
});
|
|
},
|
|
},
|
|
);
|
|
const commentVotesDeleteMutation = trpc.useMutation(
|
|
'resumes.comments.votes.user.delete',
|
|
{
|
|
onSuccess: () => {
|
|
// Comment updated, invalidate query to trigger refetch
|
|
trpcContext.invalidateQueries(['resumes.comments.votes.list']);
|
|
gaEvent({
|
|
action: 'resumes.comment_unvote',
|
|
category: 'engagement',
|
|
label: 'Unvote comment',
|
|
});
|
|
},
|
|
},
|
|
);
|
|
|
|
const onVote = async (value: Vote, setAnimation: (_: boolean) => void) => {
|
|
if (!userId) {
|
|
router.push('/api/auth/signin');
|
|
return;
|
|
}
|
|
|
|
setAnimation(true);
|
|
|
|
if (commentVotesQuery.data?.userVote?.value === value) {
|
|
return commentVotesDeleteMutation.mutate(
|
|
{
|
|
commentId,
|
|
},
|
|
{
|
|
onSettled: async () => setAnimation(false),
|
|
},
|
|
);
|
|
}
|
|
return commentVotesUpsertMutation.mutate(
|
|
{
|
|
commentId,
|
|
value,
|
|
},
|
|
{
|
|
onSettled: async () => setAnimation(false),
|
|
},
|
|
);
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<button
|
|
disabled={
|
|
commentVotesQuery.isLoading ||
|
|
commentVotesUpsertMutation.isLoading ||
|
|
commentVotesDeleteMutation.isLoading
|
|
}
|
|
type="button"
|
|
onClick={() => onVote(Vote.UPVOTE, setUpvoteAnimation)}>
|
|
<ArrowUpCircleIcon
|
|
className={clsx(
|
|
'h-4 w-4',
|
|
commentVotesQuery.data?.userVote?.value === Vote.UPVOTE ||
|
|
upvoteAnimation
|
|
? 'fill-primary-500'
|
|
: 'fill-slate-400',
|
|
userId &&
|
|
!downvoteAnimation &&
|
|
!upvoteAnimation &&
|
|
'hover:fill-primary-500',
|
|
upvoteAnimation && 'animate-[bounce_0.5s_infinite] cursor-default',
|
|
)}
|
|
/>
|
|
</button>
|
|
|
|
<div className="flex min-w-[1rem] justify-center text-xs font-semibold text-gray-700">
|
|
{commentVotesQuery.data?.numVotes ?? 0}
|
|
</div>
|
|
|
|
<button
|
|
disabled={
|
|
commentVotesQuery.isLoading ||
|
|
commentVotesUpsertMutation.isLoading ||
|
|
commentVotesDeleteMutation.isLoading
|
|
}
|
|
type="button"
|
|
onClick={() => onVote(Vote.DOWNVOTE, setDownvoteAnimation)}>
|
|
<ArrowDownCircleIcon
|
|
className={clsx(
|
|
'h-4 w-4',
|
|
commentVotesQuery.data?.userVote?.value === Vote.DOWNVOTE ||
|
|
downvoteAnimation
|
|
? 'fill-danger-500'
|
|
: 'fill-slate-400',
|
|
userId &&
|
|
!downvoteAnimation &&
|
|
!upvoteAnimation &&
|
|
'hover:fill-danger-500',
|
|
downvoteAnimation &&
|
|
'animate-[bounce_0.5s_infinite] cursor-default',
|
|
)}
|
|
/>
|
|
</button>
|
|
</>
|
|
);
|
|
}
|