Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
360 changes: 94 additions & 266 deletions apps/web/components/community/comment-section.tsx

Large diffs are not rendered by default.

34 changes: 21 additions & 13 deletions apps/web/components/community/comment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Button } from "@/components/ui/button";
import { Textarea } from "@/components/ui/textarea";
import {
ThumbsUp,
MessageSquare,
MoreVertical,
FlagTriangleRight,
Expand Down Expand Up @@ -34,6 +33,7 @@ import { isCommunityComment } from "./utils";
import { DELETED_COMMENT_PLACEHOLDER } from "@ui-config/strings";
import { useToast } from "@courselit/components-library";
import { FetchBuilder } from "@courselit/utils";
import { ReactionsBar } from "./reactions-bar";

type CommentOrReply =
| CommunityComment
Expand All @@ -42,7 +42,7 @@ type CommentOrReply =
interface CommentProps {
communityId: string;
comment: CommentOrReply;
onLike: (commentId: string, replyId?: string) => void;
onReact: (commentId: string, emoji: string, replyId?: string) => void;
onReply: (
commentId: string,
content: string,
Expand All @@ -57,7 +57,7 @@ interface CommentProps {
export function Comment({
communityId,
comment,
onLike,
onReact,
onReply,
onDelete,
membership,
Expand Down Expand Up @@ -255,15 +255,21 @@ export function Comment({
)}
</p>
<div className="flex items-center gap-4 mt-2">
<Button
variant="ghost"
size="sm"
className={`text-muted-foreground ${comment.hasLiked ? "bg-accent" : ""}`}
onClick={() => onLike(comment.commentId)}
>
<ThumbsUp className="h-4 w-4 mr-2" />
{comment.likesCount}
</Button>
<ReactionsBar
reactions={comment.reactions || []}
onReact={(emoji) => {
if (isCommunityComment(comment)) {
onReact(comment.commentId, emoji);
} else {
onReact(
comment.commentId,
emoji,
comment.replyId,
);
}
}}
compact
/>
<Button
variant="ghost"
size="sm"
Expand Down Expand Up @@ -325,7 +331,9 @@ export function Comment({
...reply,
commentId: comment.commentId,
}}
onLike={() => onLike(comment.commentId, reply.replyId)}
onReact={(commentId, emoji, replyId) =>
onReact(commentId, emoji, reply.replyId)
}
onReply={onReply}
onDelete={onDelete}
membership={membership}
Expand Down
49 changes: 49 additions & 0 deletions apps/web/components/community/emoji-picker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
"use client";

import { Button } from "@/components/ui/button";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";

const COMMON_EMOJIS = ["👍", "❤️", "😄", "🎉", "😢", "😮"];

interface EmojiPickerProps {
onEmojiSelect: (emoji: string) => void;
children?: React.ReactNode;
}

export function EmojiPicker({ onEmojiSelect, children }: EmojiPickerProps) {
return (
<Popover>
<PopoverTrigger asChild>
{children || (
<Button
variant="ghost"
size="sm"
className="text-muted-foreground"
>
+
</Button>
)}
</PopoverTrigger>
<PopoverContent align="start" side="top" className="w-auto p-2">
<div className="flex gap-1">
{COMMON_EMOJIS.map((emoji) => (
<button
key={emoji}
type="button"
className="flex h-8 w-8 cursor-pointer items-center justify-center rounded-md text-lg transition-colors hover:bg-accent"
onClick={() => {
onEmojiSelect(emoji);
}}
>
{emoji}
</button>
))}
</div>
</PopoverContent>
</Popover>
);
}
62 changes: 52 additions & 10 deletions apps/web/components/community/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,20 @@ export function CommunityForum({
}
}
likesCount
reactions {
emoji
count
hasReacted
reactors {
userId
name
avatar {
mediaId
file
thumbnail
}
}
}
commentsCount
updatedAt
hasLiked
Expand Down Expand Up @@ -256,27 +270,42 @@ export function CommunityForum({
);
};

const handleLike = async (postId: string, e?: React.MouseEvent) => {
const handleReact = async (
postId: string,
emoji: string,
e?: React.MouseEvent,
) => {
e?.stopPropagation();

setPosts((prevPosts) =>
prevPosts.map((post) =>
post.postId === postId
? {
...post,
likesCount: post.hasLiked
? post.likesCount - 1
: post.likesCount + 1,
hasLiked: !post.hasLiked,
hasLiked: true,
}
: post,
),
);

const query = `
mutation ($communityId: String!, $postId: String!) {
togglePostLike(communityId: $communityId, postId: $postId) {
mutation ($communityId: String!, $postId: String!, $emoji: String!) {
togglePostReaction(communityId: $communityId, postId: $postId, emoji: $emoji) {
postId
reactions {
emoji
count
hasReacted
reactors {
userId
name
avatar {
mediaId
file
thumbnail
}
}
}
}
}
`;
Expand All @@ -285,11 +314,24 @@ export function CommunityForum({
.setUrl(`${address.backend}/api/graph`)
.setPayload({
query,
variables: { postId, communityId: id },
variables: { postId, communityId: id, emoji },
})
.setIsGraphQLEndpoint(true)
.build();
await fetch.exec();
const response = await fetch.exec();
if (response.togglePostReaction) {
setPosts((prevPosts) =>
prevPosts.map((post) =>
post.postId === postId
? {
...post,
reactions:
response.togglePostReaction.reactions,
}
: post,
),
);
}
} catch (err) {
console.error(err.message);
toast({
Expand Down Expand Up @@ -955,7 +997,7 @@ export function CommunityForum({
)
}
onTogglePin={togglePin}
onLike={handleLike}
onReact={handleReact}
/>
))
) : (
Expand Down
20 changes: 8 additions & 12 deletions apps/web/components/community/post-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@ import {
} from "@courselit/page-blocks";
import { CommunityMedia, CommunityPost } from "@courselit/common-models";
import { capitalize, truncate } from "@courselit/utils";
import { MessageSquare, Pin, ThumbsUp } from "lucide-react";
import { MessageSquare, Pin } from "lucide-react";
import Link from "next/link";
import { useContext } from "react";
import { ThemeContext } from "@components/contexts";
import { ReactionsBar } from "./reactions-bar";

interface CommunityPostCardProps {
post: CommunityPost;
Expand All @@ -29,7 +30,7 @@ interface CommunityPostCardProps {
renderMediaPreview: (media: CommunityMedia) => React.ReactNode;
onOpen: (postId: string) => void;
onTogglePin?: (postId: string, e?: React.MouseEvent) => void;
onLike?: (postId: string, e?: React.MouseEvent) => void;
onReact?: (postId: string, emoji: string, e?: React.MouseEvent) => void;
}

export default function CommunityPostCard({
Expand All @@ -41,7 +42,7 @@ export default function CommunityPostCard({
renderMediaPreview,
onOpen,
onTogglePin,
onLike,
onReact,
}: CommunityPostCardProps) {
const { theme } = useContext(ThemeContext);

Expand Down Expand Up @@ -138,15 +139,10 @@ export default function CommunityPostCard({
</CardContent>
<CardFooter>
<div className="flex items-center gap-4">
<Button
variant="ghost"
size="sm"
className={`text-muted-foreground ${post.hasLiked ? "bg-accent" : ""}`}
onClick={(e) => onLike?.(post.postId, e)}
>
<ThumbsUp className="mr-2 h-4 w-4" />
{post.likesCount}
</Button>
<ReactionsBar
reactions={post.reactions || []}
onReact={(emoji) => onReact?.(post.postId, emoji)}
/>
<Button
variant="ghost"
size="sm"
Expand Down
Loading
Loading