Skip to content

Commit

Permalink
comments component
Browse files Browse the repository at this point in the history
  • Loading branch information
S4nfs committed Jul 31, 2024
1 parent 53b5a6a commit 411fdbf
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 8 deletions.
79 changes: 79 additions & 0 deletions Typescript_Course/PROJECTS/Blueeit/src/components/CommentVotes.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
'use client'
import { Button } from '@/components/ui/Button'
import { useCustomToast } from '@/hooks/use-custom-toast'
import { toast } from '@/hooks/use-toast'
import { cn } from '@/lib/utils'
import { CommentVoterequest } from '@/lib/validators/votes'
import { usePrevious } from '@mantine/hooks'
import { CommentVote, VoteType } from '@prisma/client'
import { useMutation } from '@tanstack/react-query'
import axios, { AxiosError } from 'axios'
import { ArrowBigDown, ArrowBigUp } from 'lucide-react'
import { FC, useState } from 'react'

type PartialVote = Pick<CommentVote, 'type'>
interface CommentVoteProps {
commentId: string
initialVotesAmt: number
initialVote?: PartialVote
}

const CommentVotes: FC<CommentVoteProps> = ({ commentId, initialVotesAmt, initialVote }) => {
const { loginToast } = useCustomToast()
const [votesAmt, setVotesAmt] = useState<number>(initialVotesAmt)
const [currentVote, setCurrentVote] = useState(initialVote)
const prevVote = usePrevious(currentVote)

const { mutate: vote } = useMutation({
mutationFn: async (voteType: VoteType) => {
const payload: CommentVoterequest = {
commentId,
voteType,
}

await axios.patch('/api/subreddit/post/comment/vote', payload)
},
onError: (error, voteType) => {
if (voteType === 'UP') setVotesAmt((prev) => prev - 1)
else setVotesAmt((prev) => prev + 1)

// reset current vote
setCurrentVote(prevVote)
if (error instanceof AxiosError) {
if (error.response?.status === 401) {
return loginToast()
}
}
return toast({
title: 'Something went wrong',
description: 'Vote not registered.',
variant: 'destructive',
})
},
onMutate: (type) => {
if (currentVote?.type === type) {
setCurrentVote(undefined)
if (type === 'UP') setVotesAmt((prev) => prev - 1)
else if (type === 'DOWN') setVotesAmt((prev) => prev + 1)
} else {
setCurrentVote({ type })
if (type === 'UP') setVotesAmt((prev) => prev + (currentVote ? 2 : 1))
else if (type === 'DOWN') setVotesAmt((prev) => prev - (currentVote ? 2 : 1))
}
},
})

return (
<div className='flex gap-1 '>
<Button size='sm' variant='ghost' aria-labe='upvote' onClick={() => vote('UP')}>
<ArrowBigUp className={cn('h-5 w-5 text-zinc-700', { 'text-emerald-500 fill-emerald-500': currentVote?.type === 'UP' })} />{' '}
</Button>
<p className='text-center py-2 font-medium text-sm text-zinc-900'>{votesAmt}</p>{' '}
<Button size='sm' variant='ghost' aria-labe='downvote' onClick={() => vote('DOWN')}>
<ArrowBigDown className={cn('h-5 w-5 text-zinc-700', { 'text-red-500 fill-red-500': currentVote?.type === 'DOWN' })} />{' '}
</Button>
</div>
)
}

export default CommentVotes
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ import { getAuthSession } from '@/lib/auth'
import { db } from '@/lib/db'
import { FC } from 'react'
import PostComment from './PostComment'
import CreateComment from './CreateComment'

interface CommentsSectionProps {
postId: string
}

const CommentsSection = async ({ postId }) => {
const CommentsSection = async ({ postId }: CommentsSectionProps) => {
const session = getAuthSession()
const comments = await db.comment.findMany({
where: {
Expand Down Expand Up @@ -43,11 +44,11 @@ const CommentsSection = async ({ postId }) => {
if (cur.type === 'DOWN') return acc - 1
return acc
}, 0)
const topLvelCommentVote = topLevelComment.votes.find((vote) => vote.userId === session?.user.id)
const topLevelCommentVote = topLevelComment.votes.find((vote) => vote.userId === session?.user.id)
return (
<div key={topLevelComment.id} className='flex flex-col'>
<div className='mb-2'>
<PostComment comment={topLevelComment} />
<PostComment comment={topLevelComment} postId={postId} currentVote={topLevelCommentVote} votesAmt={topLevelCommentVotesAmt} />
</div>
</div>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const CreateComment: FC<CreateCommentProps> = ({ postId, replyToId }) => {
text,
replyToId,
}
const { data } = await axios.patch(`/api/posts/${postId}/comments`, payload)
const { data } = await axios.patch(`/api/subreddit/post/comment/`, payload)
return data
},
onError: (error) => {
Expand All @@ -53,7 +53,9 @@ const CreateComment: FC<CreateCommentProps> = ({ postId, replyToId }) => {
<div className='mt-2'>
<Textarea id='comment' value={input} onChange={(e) => setInput(e.target.value)} rows={1} placeholder='What are your thoughts?' />
<div className='mt-2 flex justify-end '>
<Button isLoading={isLoading} disabled={input.length === 0} onClick={() => comment({ postId, text: input, replyToId })}></Button>
<Button isLoading={isLoading} disabled={input.length === 0} onClick={() => comment({ postId, text: input, replyToId })}>
Post
</Button>
</div>
</div>
</div>
Expand Down
69 changes: 66 additions & 3 deletions Typescript_Course/PROJECTS/Blueeit/src/components/PostComment.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,44 @@
import { FC, useRef } from 'react'
'use client'
import { FC, useRef, useState } from 'react'
import UserAvatar from './UserAvatar'
import { Comment, CommentVote, User } from '@prisma/client'
import { formatTimeToNow } from '@/lib/utils'
import CommentVotes from './CommentVotes'
import { MessageSquare } from 'lucide-react'
import { Button } from './ui/Button'
import { useRouter } from 'next/navigation'
import { useSession } from 'next-auth/react'
import { Label } from '@radix-ui/react-dropdown-menu'
import { Textarea } from './ui/textarea'
import { useMutation } from '@tanstack/react-query'
import { CommentRequest } from '@/lib/validators/comment'
import axios from 'axios'

type ExtendedComment = Comment & {
votes: CommentVote[]
author: User
}
interface PostCommentProps {
comment: ExtendedComment
votesAmt: number
currentVote: CommentVote | undefined
postId: string
}

const PostComment: FC<PostCommentProps> = ({ comment }) => {
const PostComment: FC<PostCommentProps> = ({ comment, votesAmt, currentVote, postId }) => {
const commentRef = useRef<HTMLDivElement>(null)
const router = useRouter()
const { data: session } = useSession()
const [isReplying, setIsReplying] = useState<boolean>(false)
const [input, setInput] = useState<string>('')

const { mutate: postComment, isLoading } = useMutation({
mutationFn: async ({ postId, text, replyToId }: CommentRequest) => {
const payload: CommentRequest = { postId, text, replyToId }
const { data } = await axios.patch('/api/subreddit/post/comment', payload)
return data
},
})
return (
<div ref={commentRef} className='flex flex-col'>
<div className='flex items-center'>
Expand All @@ -24,11 +50,48 @@ const PostComment: FC<PostCommentProps> = ({ comment }) => {
className='h-6 w-6'
/>
<div className='ml-2 flex items-center gap-x-2'>
<p className='text-sm font-medium text-gray-900'>u/{comment.author.userName}</p>
<p className='text-sm font-medium text-gray-900'>u/{comment.author.username}</p>
<p className='max-h-40 truncate text-xs text-zinc-500'>{formatTimeToNow(new Date(comment.createdAt))}</p>
</div>
</div>
<p className='text-sm text-zinc-900 mt-2 '>{comment.text}</p>
<div className='flex gap-2 items-center flex-wrap'>
<CommentVotes commentId={comment.id} initialVotesAmt={votesAmt} initialVote={currentVote} />
<Button variant='ghost' size='xs'>
<MessageSquare
onClick={() => {
if (!session) return router.push('/sign-in')
setIsReplying(true)
}}
className='h-4 w-4 mr-1.5'
/>
Reply
</Button>
{isReplying ? (
<div className='grid w-full gap-1.5'>
<Label htmlFor='comment'>Your Comment</Label>
<div className='mt-2'>
<Textarea id='comment' value={input} onChange={(e) => setInput(e.target.value)} rows={1} placeholder='What are your thoughts?' />
<div className='mt-2 flex justify-end '>
<Button tabIndex={-1} variant='subtle' onClick={() => setIsReplying(false)}>
Cancel
</Button>
<Button
isLoading={isLoading}
variant='ghost'
disabled={input.length === 0}
onClick={() => {
if (!input) return
postComment({ postId, text: input, replyToId: comment.replyToId ?? comment.id })
}}
>
Post
</Button>
</div>
</div>
</div>
) : null}
</div>
</div>
)
}
Expand Down

0 comments on commit 411fdbf

Please sign in to comment.