diff --git a/client/package.json b/client/package.json index 06462fc..2c069cd 100644 --- a/client/package.json +++ b/client/package.json @@ -54,6 +54,7 @@ "uploadthing": "7.3.0", "zod": "3.24.0-canary.20241107T043915" }, + "description": "A full-stack social media web app built on NextJS 15 for the Next Fans", "devDependencies": { "@eslint/js": "^9.14.0", "@prisma/extension-accelerate": "^1.2.1", @@ -79,17 +80,10 @@ }, "license": "MIT", "name": "next-book", - "description": "A full-stack social media web app built on NextJS 15 for the Next Fans", "overrides": { "@types/react": "npm:types-react@19.0.0-rc.1", "@types/react-dom": "npm:types-react-dom@19.0.0-rc.1" }, - "tags": [ - "nextjs", - "nextjs15", - "socialmedia", - "fullstack" - ], "private": true, "scripts": { "build": "next build", @@ -103,6 +97,12 @@ "prisma:studio": "prisma studio", "start": "next start" }, + "tags": [ + "nextjs", + "nextjs15", + "socialmedia", + "fullstack" + ], "type": "module", "version": "0.1.0" -} +} \ No newline at end of file diff --git a/client/src/components/comments/Comments.tsx b/client/src/components/comments/Comments.tsx index 265f3fe..2697e64 100644 --- a/client/src/components/comments/Comments.tsx +++ b/client/src/components/comments/Comments.tsx @@ -11,7 +11,7 @@ interface CommentProps { } const Comments = ({ post }: CommentProps) => { - const { data, fetchNextPage, hasNextPage, isFetching, isSuccess } = useInfiniteQuery({ + const { data, fetchNextPage, hasNextPage, isFetching, isSuccess, isError } = useInfiniteQuery({ queryKey: ['comments', post.id], queryFn: ({ pageParam }) => kyInstance @@ -30,7 +30,7 @@ const Comments = ({ post }: CommentProps) => { {isFetching && ( -
+
Loading comments...
@@ -45,8 +45,14 @@ const Comments = ({ post }: CommentProps) => { Load previous comments )} - {isSuccess && comments.length === 0 && ( -

No comments yet.

+ {isSuccess && + !comments.length && ( // +

No comments yet.

+ )} + {isError && ( +

+ An error occurred while loading comments. +

)} {comments.map((comment) => ( diff --git a/client/src/components/comments/Input.tsx b/client/src/components/comments/Input.tsx index afe8cac..29ac53c 100644 --- a/client/src/components/comments/Input.tsx +++ b/client/src/components/comments/Input.tsx @@ -4,8 +4,7 @@ import { type ChangeEvent, type FormEvent, useState } from 'react' import { Input } from '../ui/input' import { Button } from '../ui/button' import { Loader2, SendHorizonal } from 'lucide-react' -import Avatar from '../Avatar' -import UserTooltip from '../users/Tooltip' +import { Avatar } from '@/components' import Link from 'next/link' interface InputProps { @@ -34,7 +33,7 @@ const CommentInput = ({ post }: InputProps) => { ) diff --git a/client/src/components/comments/actions.ts b/client/src/components/comments/actions.ts index 031887b..6490f0f 100644 --- a/client/src/components/comments/actions.ts +++ b/client/src/components/comments/actions.ts @@ -5,14 +5,7 @@ import { prisma } from '@/lib' import { validateRequest } from '@/auth' import { createCommentSchema } from '@/lib/validation' -export const createComment = async ({ - post, - content -}: { - // - post: PostData - content: string -}) => { +export const createComment = async ({ post, content }: { post: PostData; content: string }) => { const { user } = await validateRequest() if (!user) { throw new Error('Unauthorized: You are not logged in') @@ -29,3 +22,24 @@ export const createComment = async ({ include: getCommentDataInclude(user.id) }) } + +export const deleteComment = async (id: string) => { + const { user } = await validateRequest() + + if (!user) { + throw new Error('Unauthorized: You are not logged in') + } + + const comment = await prisma.comment.findUnique({ where: { id } }) + + if (!comment) { + throw new Error('Comment not found') + } + if (comment.authorId !== user.id) { + throw new Error('Unauthorized: You are not the author of this comment') + } + return prisma.comment.delete({ + where: { id }, + include: getCommentDataInclude(user.id) + }) +} diff --git a/client/src/hooks/index.ts b/client/src/hooks/index.ts index a036b9b..8b092ab 100644 --- a/client/src/hooks/index.ts +++ b/client/src/hooks/index.ts @@ -6,3 +6,4 @@ export { default as useCreatePostMutation } from './useCreatePostMutation' export { default as useMediaUpload } from './useMediaUpload' export { default as useCreateCommentMutation } from './useCreateCommentMutation' export { useUploadThing } from '@/lib/uploadthing' +export { default as useDeleteCommentMutation } from './useDeleteCommentMutation' diff --git a/client/src/hooks/useDeleteCommentMutation.ts b/client/src/hooks/useDeleteCommentMutation.ts new file mode 100644 index 0000000..aa637b4 --- /dev/null +++ b/client/src/hooks/useDeleteCommentMutation.ts @@ -0,0 +1,43 @@ +import type { CommentsPage } from '@/types' +import { useQueryClient, useMutation, QueryKey, InfiniteData } from '@tanstack/react-query' +import { useToast } from '.' +import { deleteComment } from '@/components/comments/actions' + +const useDeleteCommentMutation = () => { + const { toast } = useToast() + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: deleteComment, + onSuccess: async (deletedComment) => { + const queryKey: QueryKey = ['comments', deletedComment.id] + await queryClient.cancelQueries({ queryKey }) + queryClient.setQueryData>(queryKey, (data) => { + if (!data) { + return + } + return { + pageParams: data.pageParams, + pages: data.pages.map((page) => ({ + previousCursor: page.previousCursor, + comments: page.comments.filter((c) => c.id !== deletedComment.id) + })) + } + }) + + toast({ + variant: 'success', + description: 'Comment deleted' + }) + }, + onError: (error) => { + console.error(error) + toast({ + variant: 'destructive', + description: 'Failed to delete comment. Please try again.' + }) + } + }) +} + +export default useDeleteCommentMutation