Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 6c0c11b

Browse files
committed
Add like button for comments
1 parent 8525d3f commit 6c0c11b

14 files changed

+271
-16
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
CreateCollection({
2+
name: "comment_likes"
3+
})
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
CreateIndex({
2+
name: "all_likes_by_comment",
3+
source: Collection("comment_likes"),
4+
terms: [{
5+
field: ["data", "comment"]
6+
}]
7+
})
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
CreateIndex({
2+
name: "unique_comment_user_like",
3+
source: Collection("comment_likes"),
4+
unique: true,
5+
terms: [{
6+
field: ["data", "comment"]
7+
}, {
8+
field: ["data", "user"]
9+
}]
10+
})
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
CreateCollection({
2+
name: 'comment_likes'
3+
})
4+
5+
/**
6+
# data: {
7+
# comment: UpdateComment
8+
# user: User
9+
# liked: boolean
10+
# timestamps {
11+
# createdAt: Time
12+
# updatedAt: Time
13+
# }
14+
# }
15+
**/
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
CreateIndex({
2+
name: 'all_likes_by_comment',
3+
source: Collection('comment_likes'),
4+
terms: [
5+
{
6+
field: ['data', 'comment'],
7+
},
8+
],
9+
})
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
CreateIndex({
2+
name: 'unique_comment_user_like',
3+
source: Collection('comment_likes'),
4+
unique: true,
5+
terms: [
6+
{
7+
field: ['data', 'comment'],
8+
},
9+
{
10+
field: ['data', 'user'],
11+
},
12+
],
13+
})

src/components/HomePageFeed.tsx

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,14 @@ import {
3333
UpdateCommentsList,
3434
FollowModal,
3535
LikeModal,
36+
useLikes,
37+
EditUpdate,
38+
Goal,
3639
} from '@/components'
37-
import { Goal } from './goals'
3840
import type { GoalResponse } from 'src/pages/[username]'
3941
import { scrollToContentWithId } from 'src/utils'
4042
import { IconBrandDiscord } from 'tabler-icons'
41-
import EditUpdate from './goals/EditUpdate'
4243
import toast, { Toaster } from 'react-hot-toast'
43-
import { useLikes } from './useLikes'
4444

4545
export function HomePageFeedUpdate({
4646
update,
@@ -266,8 +266,7 @@ export function HomePageFeedUpdate({
266266
{update.comments.data.map((comment, index) => (
267267
<UpdateComment
268268
key={comment.id}
269-
postedBy={comment.postedBy}
270-
postedOn={DateTime.fromMillis(comment.createdAt)}
269+
comment={comment}
271270
isLastComment={
272271
index === update.comments.data.length - 1
273272
}

src/components/goals/UpdateComment.tsx

Lines changed: 68 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import { Avatar } from '@/ui'
2-
import React from 'react'
3-
import { User } from 'src/pages/members'
4-
import { Markdown, A } from '@/components'
2+
import React, { useState } from 'react'
3+
import { Markdown, A, LikeModal, useLikes } from '@/components'
54
import { DateTime } from 'luxon'
5+
import { UpdateCommentType } from 'src/pages'
6+
import { useSession } from 'next-auth/client'
7+
import classNames from 'classnames'
8+
import { ThumbsUp } from 'phosphor-react'
69

710
export type GoalUpdateType = {
811
id: string
@@ -11,33 +14,53 @@ export type GoalUpdateType = {
1114
}
1215

1316
export default function UpdateComment({
14-
postedBy,
17+
comment,
1518
children,
16-
postedOn,
1719
isLastComment = false,
1820
}: {
19-
postedBy: User
2021
children: string
21-
postedOn: DateTime
22+
comment: UpdateCommentType
2223
isLastComment?: boolean
2324
}) {
25+
const postedOn = DateTime.fromMillis(comment.createdAt)
26+
const [session] = useSession()
27+
const [isLikeModalOpen, setIsLikeModalOpen] = useState(false)
28+
const { count: likesCount, hasLiked, toggleLike } = useLikes({
29+
initialCount: comment.likes.data,
30+
query: {
31+
key: ['api/fauna/has-liked-comment', comment.id],
32+
endpoint: '/api/fauna/has-liked-comment',
33+
body: {
34+
commentId: comment.id,
35+
},
36+
},
37+
mutation: {
38+
endpoint: '/api/fauna/toggle-comment-like',
39+
body: {
40+
commentId: comment.id,
41+
},
42+
},
43+
})
2444
return (
2545
<li>
2646
<div className="relative pb-6">
2747
<div className="relative flex items-start space-x-3">
2848
<div className="relative">
29-
<A href={`/${postedBy.username}`}>
30-
<Avatar src={postedBy.image} alt={postedBy.account?.firstName} />
49+
<A href={`/${comment.postedBy.username}`}>
50+
<Avatar
51+
src={comment.postedBy.image}
52+
alt={comment.postedBy.account?.firstName}
53+
/>
3154
</A>
3255
</div>
3356
<div className="min-w-0 flex-1">
3457
<div>
3558
<div className="text-sm">
3659
<a
37-
href={`/${postedBy.username}`}
60+
href={`/${comment.postedBy.username}`}
3861
className="font-medium text-gray-900"
3962
>
40-
{postedBy.account?.firstName ?? postedBy.name}
63+
{comment.postedBy.account?.firstName ?? comment.postedBy.name}
4164
</a>
4265
</div>
4366
<p className="mt-0.5 text-sm text-gray-500">
@@ -52,6 +75,40 @@ export default function UpdateComment({
5275
<Markdown>{children}</Markdown>
5376
</div>
5477
</div>
78+
79+
<div className="mt-3 flex justify-between space-x-8">
80+
<div className="flex space-x-6">
81+
<span className="inline-flex items-center text-sm">
82+
<button
83+
className="inline-flex space-x-2 text-gray-400 hover:text-gray-500"
84+
onClick={() => {
85+
if (!session) {
86+
setIsLikeModalOpen(true)
87+
return
88+
}
89+
toggleLike()
90+
}}
91+
>
92+
<ThumbsUp
93+
className={classNames(
94+
'h-5 w-5',
95+
hasLiked && 'text-brand-600'
96+
)}
97+
weight={hasLiked ? 'bold' : 'regular'}
98+
/>
99+
<span className="font-medium text-gray-900">
100+
{likesCount}
101+
</span>
102+
<span className="sr-only">likes</span>
103+
</button>
104+
<LikeModal
105+
user={comment.postedBy}
106+
isOpen={isLikeModalOpen}
107+
setIsOpen={setIsLikeModalOpen}
108+
/>
109+
</span>
110+
</div>
111+
</div>
55112
</div>
56113
</div>
57114
</div>

src/components/goals/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ export type { GoalType } from './GoalFeed'
1111
export { default as UpdateComment } from './UpdateComment'
1212
export { default as NewComment } from './NewComment'
1313
export { default as UpdateComments, UpdateCommentsList } from './UpdateComments'
14+
export { default as EditUpdate } from './EditUpdate'

src/components/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ export { default as HomePageFeed } from './HomePageFeed'
1212
export { default as Hero } from './Hero'
1313
export * from './modal'
1414
export { Pre } from './Code'
15+
export { useLikes } from './useLikes'

src/pages/api/fauna/all-updates.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,16 @@ const FaunaCreateHandler: NextApiHandler = async (
4747
createdAt: q.ToMillis(
4848
q.Select(['data', 'timestamps', 'createdAt'], commentDoc)
4949
),
50+
likes: q.Count(
51+
q.Filter(
52+
q.Paginate(
53+
q.Match(q.Index('all_likes_by_comment'), commentRef)
54+
),
55+
(commentLikeRef) => {
56+
return q.Select(['data', 'liked'], q.Get(commentLikeRef))
57+
}
58+
)
59+
),
5060
postedBy: {
5161
id: q.Select(['ref', 'id'], postedByDoc),
5262
name: q.Select(['data', 'name'], postedByDoc, null),
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { User } from 'src/pages/members'
2+
import { NextApiHandler, NextApiRequest, NextApiResponse } from 'next'
3+
import { getSession } from 'next-auth/client'
4+
5+
import faunadb from 'faunadb'
6+
const q = faunadb.query
7+
const isProduction = process.env.NODE_ENV === 'production'
8+
const client = new faunadb.Client({
9+
secret: process.env.FAUNADB_SECRET ?? 'secret',
10+
scheme: isProduction ? 'https' : 'http',
11+
domain: isProduction ? 'db.fauna.com' : 'localhost',
12+
...(isProduction ? {} : { port: 8443 }),
13+
})
14+
15+
const FaunaCreateHandler: NextApiHandler = async (
16+
req: NextApiRequest,
17+
res: NextApiResponse
18+
) => {
19+
const session = await getSession({ req })
20+
21+
if (!session) {
22+
return res.status(200).json({ liked: false })
23+
}
24+
25+
const userId = (session.user as User).id
26+
27+
try {
28+
const { commentId } = req.body
29+
const response = await client.query(
30+
q.Let(
31+
{
32+
ref: q.Match(q.Index('unique_comment_user_like'), [
33+
q.Ref(q.Collection('update_comments'), commentId),
34+
q.Ref(q.Collection('users'), userId),
35+
]),
36+
},
37+
q.If(
38+
q.Exists(q.Var('ref')),
39+
q.Select(['data', 'liked'], q.Get(q.Var('ref'))),
40+
false
41+
)
42+
)
43+
)
44+
res.status(200).json({ liked: response })
45+
} catch (error) {
46+
console.error(error)
47+
console.error({ msg: error.message })
48+
res.status(500).json({ message: error.message })
49+
}
50+
}
51+
52+
export default FaunaCreateHandler
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { User } from 'src/pages/members'
2+
import { NextApiHandler, NextApiRequest, NextApiResponse } from 'next'
3+
import { getSession } from 'next-auth/client'
4+
5+
import faunadb from 'faunadb'
6+
const q = faunadb.query
7+
const isProduction = process.env.NODE_ENV === 'production'
8+
const client = new faunadb.Client({
9+
secret: process.env.FAUNADB_SECRET ?? 'secret',
10+
scheme: isProduction ? 'https' : 'http',
11+
domain: isProduction ? 'db.fauna.com' : 'localhost',
12+
...(isProduction ? {} : { port: 8443 }),
13+
})
14+
15+
const FaunaCreateHandler: NextApiHandler = async (
16+
req: NextApiRequest,
17+
res: NextApiResponse
18+
) => {
19+
const session = await getSession({ req })
20+
21+
if (!session) {
22+
return res.status(401).json({ message: 'Unauthorized' })
23+
}
24+
25+
const userId = (session.user as User).id
26+
27+
try {
28+
const { commentId } = req.body
29+
const response = await client.query(
30+
q.Let(
31+
{
32+
ref: q.Match(q.Index('unique_comment_user_like'), [
33+
q.Ref(q.Collection('update_comments'), commentId),
34+
q.Ref(q.Collection('users'), userId),
35+
]),
36+
},
37+
q.If(
38+
q.Exists(q.Var('ref')),
39+
q.Update(
40+
q.Ref(
41+
q.Collection('comment_likes'),
42+
q.Select(['ref', 'id'], q.Get(q.Var('ref')))
43+
),
44+
{
45+
data: {
46+
liked: q.Not(q.Select(['data', 'liked'], q.Get(q.Var('ref')))),
47+
timestamps: {
48+
updatedAt: q.Now(),
49+
},
50+
},
51+
}
52+
),
53+
q.Create(q.Collection('comment_likes'), {
54+
data: {
55+
user: q.Ref(q.Collection('users'), userId),
56+
comment: q.Ref(q.Collection('update_comments'), commentId),
57+
liked: true,
58+
timestamps: {
59+
createdAt: q.Now(),
60+
updatedAt: q.Now(),
61+
},
62+
},
63+
})
64+
)
65+
)
66+
)
67+
res.status(200).json({ response })
68+
} catch (error) {
69+
console.error(error)
70+
console.error({ msg: error.message })
71+
res.status(500).json({ message: error.message })
72+
}
73+
}
74+
75+
export default FaunaCreateHandler

src/pages/index.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ export type UpdateCommentType = {
88
description: string
99
createdAt: number
1010
postedBy: User
11+
likes: {
12+
data: number
13+
}
1114
}
1215

1316
export type HomePageFeedUpdateType = {

0 commit comments

Comments
 (0)