mirror of
https://github.com/yangshun/tech-interview-handbook.git
synced 2025-07-29 13:13:54 +08:00
[resumes][feat] Update vote animation (#396)
Co-authored-by: Terence Ho <>
This commit is contained in:
@ -1,4 +1,5 @@
|
|||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
import type { Dispatch, SetStateAction } from 'react';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import type { SubmitHandler } from 'react-hook-form';
|
import type { SubmitHandler } from 'react-hook-form';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
@ -8,7 +9,7 @@ import {
|
|||||||
} from '@heroicons/react/20/solid';
|
} from '@heroicons/react/20/solid';
|
||||||
import { FaceSmileIcon } from '@heroicons/react/24/outline';
|
import { FaceSmileIcon } from '@heroicons/react/24/outline';
|
||||||
import { Vote } from '@prisma/client';
|
import { Vote } from '@prisma/client';
|
||||||
import { Button, Spinner, TextArea } from '@tih/ui';
|
import { Button, TextArea } from '@tih/ui';
|
||||||
|
|
||||||
import { trpc } from '~/utils/trpc';
|
import { trpc } from '~/utils/trpc';
|
||||||
|
|
||||||
@ -32,6 +33,9 @@ export default function ResumeCommentListItem({
|
|||||||
const isCommentOwner = userId === comment.user.userId;
|
const isCommentOwner = userId === comment.user.userId;
|
||||||
const [isEditingComment, setIsEditingComment] = useState(false);
|
const [isEditingComment, setIsEditingComment] = useState(false);
|
||||||
|
|
||||||
|
const [upvoteAnimation, setUpvoteAnimation] = useState(false);
|
||||||
|
const [downvoteAnimation, setDownvoteAnimation] = useState(false);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
@ -104,16 +108,31 @@ export default function ResumeCommentListItem({
|
|||||||
setValue('description', value.trim(), { shouldDirty: true });
|
setValue('description', value.trim(), { shouldDirty: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
const onVote = async (value: Vote) => {
|
const onVote = async (
|
||||||
|
value: Vote,
|
||||||
|
setAnimation: Dispatch<SetStateAction<boolean>>,
|
||||||
|
) => {
|
||||||
|
setAnimation(true);
|
||||||
|
|
||||||
if (commentVotesQuery.data?.userVote?.value === value) {
|
if (commentVotesQuery.data?.userVote?.value === value) {
|
||||||
return commentVotesDeleteMutation.mutate({
|
return commentVotesDeleteMutation.mutate(
|
||||||
commentId: comment.id,
|
{
|
||||||
});
|
commentId: comment.id,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onSettled: async () => setAnimation(false),
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return commentVotesUpsertMutation.mutate({
|
return commentVotesUpsertMutation.mutate(
|
||||||
commentId: comment.id,
|
{
|
||||||
value,
|
commentId: comment.id,
|
||||||
});
|
value,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onSettled: async () => setAnimation(false),
|
||||||
|
},
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -201,20 +220,28 @@ export default function ResumeCommentListItem({
|
|||||||
commentVotesDeleteMutation.isLoading
|
commentVotesDeleteMutation.isLoading
|
||||||
}
|
}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => onVote(Vote.UPVOTE)}>
|
onClick={() => onVote(Vote.UPVOTE, setUpvoteAnimation)}>
|
||||||
<ArrowUpCircleIcon
|
<ArrowUpCircleIcon
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'h-4 w-4',
|
'h-4 w-4',
|
||||||
commentVotesQuery.data?.userVote?.value === Vote.UPVOTE
|
commentVotesQuery.data?.userVote?.value === Vote.UPVOTE ||
|
||||||
|
upvoteAnimation
|
||||||
? 'fill-indigo-500'
|
? 'fill-indigo-500'
|
||||||
: 'fill-gray-400',
|
: 'fill-gray-400',
|
||||||
userId && 'hover:fill-indigo-500',
|
userId &&
|
||||||
|
!downvoteAnimation &&
|
||||||
|
!upvoteAnimation &&
|
||||||
|
'hover:fill-indigo-500',
|
||||||
|
upvoteAnimation &&
|
||||||
|
'animate-[bounce_0.5s_infinite] cursor-default',
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div className="text-xs">
|
<div className="text-xs">
|
||||||
{commentVotesQuery.data?.numVotes ?? 0}
|
{commentVotesQuery.data?.numVotes ?? 0}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
disabled={
|
disabled={
|
||||||
!userId ||
|
!userId ||
|
||||||
@ -223,14 +250,20 @@ export default function ResumeCommentListItem({
|
|||||||
commentVotesDeleteMutation.isLoading
|
commentVotesDeleteMutation.isLoading
|
||||||
}
|
}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => onVote(Vote.DOWNVOTE)}>
|
onClick={() => onVote(Vote.DOWNVOTE, setDownvoteAnimation)}>
|
||||||
<ArrowDownCircleIcon
|
<ArrowDownCircleIcon
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'h-4 w-4',
|
'h-4 w-4',
|
||||||
commentVotesQuery.data?.userVote?.value === Vote.DOWNVOTE
|
commentVotesQuery.data?.userVote?.value === Vote.DOWNVOTE ||
|
||||||
|
downvoteAnimation
|
||||||
? 'fill-red-500'
|
? 'fill-red-500'
|
||||||
: 'fill-gray-400',
|
: 'fill-gray-400',
|
||||||
userId && 'hover:fill-red-500',
|
userId &&
|
||||||
|
!downvoteAnimation &&
|
||||||
|
!upvoteAnimation &&
|
||||||
|
'hover:fill-red-500',
|
||||||
|
downvoteAnimation &&
|
||||||
|
'animate-[bounce_0.5s_infinite] cursor-default',
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
@ -243,12 +276,6 @@ export default function ResumeCommentListItem({
|
|||||||
Edit
|
Edit
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{(commentVotesQuery.isLoading ||
|
|
||||||
commentVotesUpsertMutation.isLoading ||
|
|
||||||
commentVotesDeleteMutation.isLoading) && (
|
|
||||||
<Spinner label="loading votes..." size="xs" />
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Reference in New Issue
Block a user