Skip to content

Commit

Permalink
feat: add comment reaction functionality to CommentActions component
Browse files Browse the repository at this point in the history
  • Loading branch information
trinhdinhtai committed Jun 10, 2024
1 parent cdefe2f commit 1f61652
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 4 deletions.
81 changes: 81 additions & 0 deletions components/posts/comment/comment-actions.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,103 @@
"use client"

import { useCommentContext } from "@/contexts/comment"
import { useCommentsContext } from "@/contexts/comments"
import { useRatesContext } from "@/contexts/rates"
import { GetCommentsResponse } from "@/server/api/routers/comment"
import { api, RouterInputs } from "@/trpc/react"
import { UseTRPCMutationOptions } from "@trpc/react-query/shared"
import { ThumbsDownIcon, ThumbsUpIcon } from "lucide-react"

import { useCurrentUser } from "@/hooks/use-current-user"
import { Button } from "@/components/ui/button"

export default function CommentActions() {
const { comment, setIsReplying } = useCommentContext()
const { increment, decrement, getCount } = useRatesContext()
const { slug } = useCommentsContext()
const { isAuthenticated } = useCurrentUser()

const utils = api.useUtils()

const mutationOptions: UseTRPCMutationOptions<
RouterInputs["reaction"]["set"] | RouterInputs["reaction"]["delete"],
unknown,
void,
{
previousData: GetCommentsResponse | undefined
}
> = {
onMutate: (newData) => {
increment()
void utils.comment.getAll.cancel()

const input = {
slug,
...(comment.parentId ? { parentId: comment.parentId } : {}),
}

const previousData = utils.comment.getAll.getData(input)

utils.comment.getAll.setData(input, (oldData) => {
if (!oldData) return oldData

return oldData.map((c) => {
if (c.id === newData.id) {
const hasLike = "like" in newData

let likesCount: number = c.likesCount
let dislikesCount: number = c.dislikesCount

if (c.liked === true) likesCount--
if (c.liked === false) dislikesCount--

if (hasLike && newData.like) likesCount++
if (hasLike && !newData.like) dislikesCount++

return {
...c,
likesCount,
dislikesCount,
liked: hasLike ? newData.like : null,
}
}

return c
})
})

return { previousData }
},
onSettled: () => {
decrement()

if (getCount() === 0) {
void utils.comment.getAll.invalidate()
}
},
}

const reactionSetMutation = api.reaction.set.useMutation(mutationOptions)
const reactionDeleteMutation =
api.reaction.delete.useMutation(mutationOptions)

const handleCommentReaction = (like: boolean) => {
if (like === comment.liked) {
reactionDeleteMutation.mutate({ id: comment.id })
return
}

reactionSetMutation.mutate({ id: comment.id, like })
}

return (
<div className="flex gap-1">
<Button
variant="secondary"
size="sm"
disabled={!isAuthenticated}
className="gap-1"
onClick={() => handleCommentReaction(true)}
>
<ThumbsUpIcon className="size-4" />
{comment.likesCount}
Expand Down
5 changes: 1 addition & 4 deletions components/posts/comment/comment-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@
import { useMemo, useState } from "react"
import { CommentContext, CommentProvider } from "@/contexts/comment"
import { useCommentsContext } from "@/contexts/comments"
import {
CommentResponse,
GetCommentsResponse,
} from "@/server/api/routers/comment"
import { CommentResponse } from "@/server/api/routers/comment"

import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { Skeleton } from "@/components/ui/skeleton"
Expand Down
2 changes: 2 additions & 0 deletions server/api/root.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { commentRouter } from "@/server/api/routers/comment"
import { reactionRouter } from "@/server/api/routers/reaction"
import { createCallerFactory, createTRPCRouter } from "@/server/api/trpc"

export const appRouter = createTRPCRouter({
comment: commentRouter,
reaction: reactionRouter,
})

export type AppRouter = typeof appRouter
Expand Down
12 changes: 12 additions & 0 deletions server/api/routers/comment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,23 @@ export const commentRouter = createTRPCRouter({
},
})

const userReaction = await ctx.db.postCommentReaction.findFirst({
where: {
commentId: comment.id,
userId: session?.user.id,
},
})

const liked = userReaction && userReaction.like === true
const disliked = userReaction && userReaction.like === false

return {
...comment,
likesCount,
dislikesCount,
repliesCount,
liked,
disliked,
}
})
)
Expand Down
60 changes: 60 additions & 0 deletions server/api/routers/reaction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc"
import { z } from "zod"

export const reactionRouter = createTRPCRouter({
set: protectedProcedure
.input(
z.object({
id: z.string(),
like: z.boolean(),
})
)
.mutation(async ({ ctx, input }) => {
await new Promise((resolve) => setTimeout(resolve, 1000))
const user = ctx.session.user

const reaction = await ctx.db.postCommentReaction.findFirst({
where: {
commentId: input.id,
userId: user.id,
},
})

if (!reaction) {
await ctx.db.postCommentReaction.create({
data: {
commentId: input.id,
userId: user.id,
like: input.like,
},
})
return
}

await ctx.db.postCommentReaction.update({
where: {
id: reaction.id,
},
data: {
like: input.like,
},
})
}),
delete: protectedProcedure
.input(
z.object({
id: z.string(),
})
)
.mutation(async ({ ctx, input }) => {
await new Promise((resolve) => setTimeout(resolve, 1000))
const user = ctx.session.user

await ctx.db.postCommentReaction.deleteMany({
where: {
commentId: input.id,
userId: user.id,
},
})
}),
})

0 comments on commit 1f61652

Please sign in to comment.