Skip to content

Commit

Permalink
feat: like button
Browse files Browse the repository at this point in the history
  • Loading branch information
warmachine028 committed Nov 10, 2024
1 parent 4a527d9 commit a29db7b
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 5 deletions.
Binary file added client/src/app/vercel/favicon.ico
Binary file not shown.
12 changes: 7 additions & 5 deletions client/src/components/FollowButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,22 @@ const FollowButton = ({ userId, initialState }: FollowButtonProps) => {

const { mutate } = useMutation({
mutationFn: () =>
data.isFollowedByUser
? kyInstance.delete(`/api/users/${userId}/followers`)
: kyInstance.post(`/api/users/${userId}/followers`),
data.isFollowedByUser ?
kyInstance.delete(`/api/users/${userId}/followers`)
: kyInstance.post(`/api/users/${userId}/followers`),
onMutate: async () => {
await queryClient.cancelQueries({ queryKey })

const previousState = queryClient.getQueryData<FollowerInfo>(queryKey)
queryClient.setQueryData<FollowerInfo>(queryKey, () => ({
followers: (previousState?.followers || 0) + (previousState?.isFollowedByUser ? -1 : +1),
followers:
(previousState?.followers || 0) + //
(previousState?.isFollowedByUser ? -1 : +1),
isFollowedByUser: !previousState?.isFollowedByUser
}))
return { previousState }
},
onError: (error, variables, context) => {
onError: (error, _, context) => {
queryClient.setQueryData(queryKey, context?.previousState)
console.error(error)
toast({
Expand Down
74 changes: 74 additions & 0 deletions client/src/components/posts/LikeButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
'use client'

import { useToast } from '@/hooks'
import { QueryKey, useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { Button } from '../ui/button'
import { LikeInfo } from '@/types'
import { kyInstance } from '@/lib/ky'
import { Heart } from 'lucide-react'
import { cn } from '@/lib/utils'

interface LikeButtonProps {
postId: string
initialState: LikeInfo
}

const LikeButton = ({ postId, initialState }: LikeButtonProps) => {
const { toast } = useToast()
const queryClient = useQueryClient()
const queryKey: QueryKey = ['like-info', postId]

const { data } = useQuery({
queryKey,
queryFn: () => kyInstance.get(`/api/posts/${postId}/likes`).json<LikeInfo>(),
initialData: initialState,
staleTime: Infinity
})

const { mutate } = useMutation({
mutationFn: () =>
data.isLikedByUser ?
kyInstance.delete(`/api/posts/${postId}/likes`)
: kyInstance.post(`/api/posts/${postId}/likes`),
onMutate: async () => {
await queryClient.cancelQueries({ queryKey })

const previousState = queryClient.getQueryData<LikeInfo>(queryKey)
queryClient.setQueryData<LikeInfo>(queryKey, () => ({
likes:
(previousState?.likes || 0) + //
(previousState?.isLikedByUser ? -1 : +1),
isLikedByUser: !previousState?.isLikedByUser
}))
return { previousState }
},
onError: (error, _, context) => {
queryClient.setQueryData(queryKey, context?.previousState)
console.error(error)
toast({
variant: 'destructive',
description: 'Something went wrong. Please try again.'
})
}
})

return (
<Button
variant="ghost"
aria-label={data.isLikedByUser ? 'Unlike' : 'Like'}
aria-pressed={data.isLikedByUser}
aria-atomic
onClick={() => mutate()}
className="flex items-center gap-2 text-sm font-medium tabular-nums"
>
<Heart className={cn('size-4', data.isLikedByUser && 'fill-destructive text-destructive')} />
<span>
{data.likes} <span className="hidden sm:inline">likes</span>
</span>
</Button>
)
}

LikeButton.displayName = 'LikeButton'

export default LikeButton
9 changes: 9 additions & 0 deletions client/src/components/posts/Post.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { Avatar, Linkify } from '@/components'
import { UserTooltip } from '../users'
import { Media } from '@prisma/client'
import Image from 'next/image'
import LikeButton from './LikeButton'

interface PostProps {
post: PostData
Expand Down Expand Up @@ -48,6 +49,14 @@ const Post = ({ post }: PostProps) => {
<div className="whitespace-pre-line break-words">{post.content}</div>
</Linkify>
{!!post.attachments.length && <MediaPreviews attachments={post.attachments} />}
<hr />
<LikeButton
postId={post.id}
initialState={{
likes: post._count.likes,
isLikedByUser: post.likes.some((like) => like.userId === currentUser.id)
}}
/>
</article>
)
}
Expand Down

1 comment on commit a29db7b

@vercel
Copy link

@vercel vercel bot commented on a29db7b Nov 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.