diff --git a/enjoy/src/api/client.ts b/enjoy/src/api/client.ts index a086fdd8..eae0165c 100644 --- a/enjoy/src/api/client.ts +++ b/enjoy/src/api/client.ts @@ -219,6 +219,14 @@ export class Client { return this.api.delete(`/api/posts/${id}`); } + likePost(id: string): Promise { + return this.api.post(`/api/posts/${id}/like`); + } + + unlikePost(id: string): Promise { + return this.api.delete(`/api/posts/${id}/unlike`); + } + transcriptions(params?: { page?: number; items?: number; diff --git a/enjoy/src/i18n/en.json b/enjoy/src/i18n/en.json index 7501cf33..c5968fe1 100644 --- a/enjoy/src/i18n/en.json +++ b/enjoy/src/i18n/en.json @@ -640,5 +640,6 @@ "examples": "Examples", "continueLearning": "Continue learning", "enrollNow": "Enroll now", - "enrollments": "Enrollments" + "enrollments": "Enrollments", + "noLikesYet": "No likes yet" } diff --git a/enjoy/src/i18n/zh-CN.json b/enjoy/src/i18n/zh-CN.json index 8d9b68a5..dac05aab 100644 --- a/enjoy/src/i18n/zh-CN.json +++ b/enjoy/src/i18n/zh-CN.json @@ -640,5 +640,6 @@ "examples": "示例", "continueLearning": "继续练习", "enrollNow": "加入练习", - "enrollments": "参加的课程" + "enrollments": "参加的课程", + "noLikesYet": "还没有点赞" } diff --git a/enjoy/src/renderer/components/posts/post-actions.tsx b/enjoy/src/renderer/components/posts/post-actions.tsx index 61ab7184..2a1fde2e 100644 --- a/enjoy/src/renderer/components/posts/post-actions.tsx +++ b/enjoy/src/renderer/components/posts/post-actions.tsx @@ -13,6 +13,12 @@ import { AlertDialogFooter, Button, toast, + HoverCard, + HoverCardTrigger, + HoverCardContent, + Avatar, + AvatarImage, + AvatarFallback, } from "@renderer/components/ui"; import { t } from "i18next"; import Markdown from "react-markdown"; @@ -22,15 +28,19 @@ import { CopyPlusIcon, PlusCircleIcon, ChevronRightIcon, + ThumbsUpIcon, } from "lucide-react"; import { useCopyToClipboard } from "@uidotdev/usehooks"; import { Link } from "react-router-dom"; -export const PostActions = (props: { post: PostType }) => { - const { post } = props; +export const PostActions = (props: { + post: PostType; + handleUpdate: (post: PostType) => void; +}) => { + const { post, handleUpdate } = props; const [_, copyToClipboard] = useCopyToClipboard(); const [copied, setCopied] = useState(false); - const { EnjoyApp } = useContext(AppSettingsProviderContext); + const { EnjoyApp, webApi } = useContext(AppSettingsProviderContext); const [asking, setAsking] = useState(false); const [aiReplies, setAiReplies] = useState[]>([]); @@ -80,96 +90,106 @@ export const PostActions = (props: { post: PostType }) => { } }; + const toggleLike = async () => { + if (post.liked) { + webApi + .unlikePost(post.id) + .then((p) => handleUpdate(p)) + .catch((err) => toast.error(err.message)); + } else { + webApi + .likePost(post.id) + .then((p) => handleUpdate(p)) + .catch((err) => toast.error(err.message)); + } + }; + return ( <> -
- {post.target && post.targetType === "Medium" && ( - - +
+
+ + - - - - {t("addResource")} - - {(post.target as MediumType).mediumType === "Video" && - t("areYouSureToAddThisVideoToYourLibrary")} + + +
+ {post.likeByUsers?.length === 0 && ( +
+ {t("noLikesYet")} +
+ )} +
+ {post.likeByUsers?.map((user) => ( + + + + + {user.name[0].toUpperCase()} + + + + ))} +
+
+
+
+
+
+ {post.target && post.targetType === "Medium" && ( + + + + + + + {t("addResource")} + + {(post.target as MediumType).mediumType === "Video" && + t("areYouSureToAddThisVideoToYourLibrary")} - {(post.target as MediumType).mediumType === "Audio" && - t("areYouSureToAddThisAudioToYourLibrary")} - - - - {t("cancel")} - - {t("confirm")} - - - - - )} + {(post.target as MediumType).mediumType === "Audio" && + t("areYouSureToAddThisAudioToYourLibrary")} + + + + {t("cancel")} + + {t("confirm")} + + + + + )} - {typeof post.metadata?.content === "string" && ( - - )} - - {post.metadata?.type === "prompt" && ( - { - setAiReplies([...aiReplies, ...replies]); - setAsking(false); - }} - trigger={ - - } - /> - )} - - {post.metadata?.type === "gpt" && ( - <> + {typeof post.metadata?.content === "string" && ( + )} - + {post.metadata?.type === "prompt" && ( + { + setAiReplies([...aiReplies, ...replies]); + setAsking(false); + }} + trigger={ + + } + /> + )} + + {post.metadata?.type === "gpt" && ( + <> - - - )} + + + + + + )} +
{aiReplies.length > 0 && } diff --git a/enjoy/src/renderer/components/posts/post-card.tsx b/enjoy/src/renderer/components/posts/post-card.tsx index 6104488f..3c6c1880 100644 --- a/enjoy/src/renderer/components/posts/post-card.tsx +++ b/enjoy/src/renderer/components/posts/post-card.tsx @@ -17,8 +17,9 @@ import { Link } from "react-router-dom"; export const PostCard = (props: { post: PostType; handleDelete: (id: string) => void; + handleUpdate: (post: PostType) => void; }) => { - const { post, handleDelete } = props; + const { post, handleDelete, handleUpdate } = props; const { user } = useContext(AppSettingsProviderContext); return ( @@ -100,14 +101,12 @@ export const PostCard = (props: { {post.targetType == "Note" && ( <> -
- {t("sharedNote")} -
+
{t("sharedNote")}
)} - +
); }; diff --git a/enjoy/src/renderer/components/posts/posts.tsx b/enjoy/src/renderer/components/posts/posts.tsx index 406fff6b..4932fed5 100644 --- a/enjoy/src/renderer/components/posts/posts.tsx +++ b/enjoy/src/renderer/components/posts/posts.tsx @@ -137,7 +137,20 @@ export const Posts = (props: { userId?: string; by?: string }) => {
{posts.map((post) => (
- + { + const updatedPosts = posts.map((p) => { + if (p.id === post.id) { + return Object.assign(p, post); + } else { + return p; + } + }); + setPosts(updatedPosts); + }} + />
))} diff --git a/enjoy/src/types/post.d.ts b/enjoy/src/types/post.d.ts index 4a836f96..b24dd6bd 100644 --- a/enjoy/src/types/post.d.ts +++ b/enjoy/src/types/post.d.ts @@ -12,6 +12,9 @@ type PostType = { targetType?: string; targetId?: string; target?: MediumType | StoryType | RecordingType | NoteType; + liked?: boolean; + likesCount?: number; + likeByUsers?: UserType[]; createdAt: Date; updatedAt: Date; };