From 42611ef635ad2ceb9449269d3218d50553cbda36 Mon Sep 17 00:00:00 2001 From: DIYgod Date: Sat, 22 Oct 2022 01:57:17 +0100 Subject: [PATCH] feat: comment reactions and posting subcomment --- src/components/common/Comment.tsx | 133 +-------- src/components/common/CommentInput.tsx | 134 +++++++++ src/components/common/CommentItem.tsx | 35 ++- src/components/common/Reactions.tsx | 371 +++++++++++++++++++++++++ src/components/site/PostFooter.tsx | 342 +---------------------- src/queries/page.ts | 11 +- 6 files changed, 557 insertions(+), 469 deletions(-) create mode 100644 src/components/common/CommentInput.tsx create mode 100644 src/components/common/Reactions.tsx diff --git a/src/components/common/Comment.tsx b/src/components/common/Comment.tsx index 8c5d9865..bfcb1230 100644 --- a/src/components/common/Comment.tsx +++ b/src/components/common/Comment.tsx @@ -1,73 +1,17 @@ import clsx from "clsx" -import { Note, Profile } from "~/lib/types" -import { useAccount } from "wagmi" -import { useGetUserSites } from "~/queries/site" -import { Avatar } from "~/components/ui/Avatar" -import { Input } from "~/components/ui/Input" -import { Button } from "~/components/ui/Button" -import { useForm } from "react-hook-form" -import { useConnectModal } from "@rainbow-me/rainbowkit" -import { useState, useEffect } from "react" -import { useCommentPage, useGetComments } from "~/queries/page" -import { useRouter } from "next/router" -// @ts-ignore -import Picker from "@emoji-mart/react" -import { Popover } from "@headlessui/react" -import { FaceSmileIcon } from "@heroicons/react/24/outline" +import { Note } from "~/lib/types" +import { useGetComments } from "~/queries/page" import { CommentItem } from "~/components/common/CommentItem" +import { CommentInput } from "~/components/common/CommentInput" export const Comment: React.FC<{ page?: Note | null className?: string }> = ({ page, className }) => { - const { address } = useAccount() - const userSites = useGetUserSites(address) - const { openConnectModal } = useConnectModal() - const commentPage = useCommentPage() - const router = useRouter() - const [viewer, setViewer] = useState(null) - const [addressIn, setAddressIn] = useState("") const comments = useGetComments({ pageId: page?.id, }) - useEffect(() => { - setAddressIn(address || "") - }, [address]) - - useEffect(() => { - if (userSites.isSuccess && userSites.data?.length) { - setViewer(userSites.data[0]) - } - }, [userSites, router]) - - const form = useForm({ - defaultValues: { - content: "", - }, - }) - - const handleSubmit = form.handleSubmit(async (values) => { - if (!address) { - openConnectModal?.() - } else if (userSites.isSuccess && !userSites.data?.length) { - router.push(`/dashboard/new-site`) - } else { - commentPage.mutate({ - address, - pageId: page!.id, - content: values.content, - externalUrl: window.location.href, - }) - } - }) - - useEffect(() => { - if (commentPage.isSuccess) { - form.reset() - } - }, [commentPage.isSuccess, form]) - return (
@@ -76,73 +20,14 @@ export const Comment: React.FC<{ {comments.data?.count && comments.data.count > 1 ? "s" : ""}
-
- -
-
- -
-
- - {({ open }: { open: boolean }) => ( - <> - - - - - { - const response = await fetch( - "https://cdn.jsdelivr.net/npm/@emoji-mart/data", - ) - return response.json() - }} - onEmojiSelect={(e: any) => - form.setValue( - "content", - form.getValues("content") + e.native, - ) - } - /> - - - )} - - -
-
-
+
{comments.data?.list?.map((comment) => ( - + ))}
diff --git a/src/components/common/CommentInput.tsx b/src/components/common/CommentInput.tsx new file mode 100644 index 00000000..9b6922a3 --- /dev/null +++ b/src/components/common/CommentInput.tsx @@ -0,0 +1,134 @@ +import clsx from "clsx" +import { Note, Profile } from "~/lib/types" +import { useAccount } from "wagmi" +import { useGetUserSites } from "~/queries/site" +import { Avatar } from "~/components/ui/Avatar" +import { Input } from "~/components/ui/Input" +import { Button } from "~/components/ui/Button" +import { useForm } from "react-hook-form" +import { useConnectModal } from "@rainbow-me/rainbowkit" +import { useState, useEffect } from "react" +import { useCommentPage } from "~/queries/page" +import { useRouter } from "next/router" +// @ts-ignore +import Picker from "@emoji-mart/react" +import { Popover } from "@headlessui/react" +import { FaceSmileIcon } from "@heroicons/react/24/outline" + +export const CommentInput: React.FC<{ + pageId?: string + originalId?: string +}> = ({ pageId, originalId }) => { + const { address } = useAccount() + const userSites = useGetUserSites(address) + const { openConnectModal } = useConnectModal() + const commentPage = useCommentPage() + const router = useRouter() + const [viewer, setViewer] = useState(null) + const [addressIn, setAddressIn] = useState("") + + useEffect(() => { + setAddressIn(address || "") + }, [address]) + + useEffect(() => { + if (userSites.isSuccess && userSites.data?.length) { + setViewer(userSites.data[0]) + } + }, [userSites, router]) + + const form = useForm({ + defaultValues: { + content: "", + }, + }) + + const handleSubmit = form.handleSubmit(async (values) => { + if (!address) { + openConnectModal?.() + } else if (userSites.isSuccess && !userSites.data?.length) { + router.push(`/dashboard/new-site`) + } else if (pageId) { + commentPage.mutate({ + address, + pageId: pageId, + content: values.content, + externalUrl: window.location.href, + originalId: originalId, + }) + } + }) + + useEffect(() => { + if (commentPage.isSuccess) { + form.reset() + } + }, [commentPage.isSuccess, form]) + + return ( +
+ +
+
+ +
+
+ + {({ open }: { open: boolean }) => ( + <> + + + + + { + const response = await fetch( + "https://cdn.jsdelivr.net/npm/@emoji-mart/data", + ) + return response.json() + }} + onEmojiSelect={(e: any) => + form.setValue( + "content", + form.getValues("content") + e.native, + ) + } + /> + + + )} + + +
+
+
+ ) +} diff --git a/src/components/common/CommentItem.tsx b/src/components/common/CommentItem.tsx index 303afdf2..b1414850 100644 --- a/src/components/common/CommentItem.tsx +++ b/src/components/common/CommentItem.tsx @@ -8,6 +8,10 @@ import { CSB_SCAN } from "~/lib/env" import { getSiteLink } from "~/lib/helpers" import { PageContent } from "~/components/common/PageContent" import { NoteEntity, CharacterEntity } from "crossbell.js" +import { Button } from "~/components/ui/Button" +import { Reactions } from "~/components/common/Reactions" +import { useState } from "react" +import { CommentInput } from "~/components/common/CommentInput" dayjs.extend(duration) dayjs.extend(relativeTime) @@ -17,13 +21,16 @@ export const CommentItem: React.FC<{ character?: CharacterEntity | null } isSub?: boolean -}> = ({ comment, isSub }) => { + originalId?: string +}> = ({ comment, isSub, originalId }) => { + const [replyOpen, setReplyOpen] = useState(false) + return (
-
+
+
+ + +
+ {replyOpen && ( +
+ +
+ )}
{(comment as any)?.fromNotes?.list?.length > 0 && ( -
+
{(comment as any)?.fromNotes?.list?.map( ( subcomment: NoteEntity & { diff --git a/src/components/common/Reactions.tsx b/src/components/common/Reactions.tsx new file mode 100644 index 00000000..d7008137 --- /dev/null +++ b/src/components/common/Reactions.tsx @@ -0,0 +1,371 @@ +import { LikeIcon } from "~/components/icons/LikeIcon" +import { + useLikePage, + useUnlikePage, + useMintPage, + useGetLikes, + useCheckLike, + useGetMints, + useCheckMint, +} from "~/queries/page" +import { useGetUserSites } from "~/queries/site" +import { useAccount } from "wagmi" +import { useConnectModal } from "@rainbow-me/rainbowkit" +import { useState, useEffect } from "react" +import { Button } from "../ui/Button" +import { useRouter } from "next/router" +import { SITE_URL, CSB_SCAN } from "~/lib/env" +import { UniLink } from "~/components/ui/UniLink" +import { Modal } from "~/components/ui/Modal" +import { Avatar } from "../ui/Avatar" +import { MintIcon } from "~/components/icons/MintIcon" +import { getLikes, getMints } from "~/models/page.model" +import { CharacterList } from "~/components/common/CharacterList" +import clsx from "clsx" + +export const Reactions: React.FC<{ + className?: string + size?: "sm" | "base" + pageId?: string +}> = ({ className, size, pageId }) => { + const likePage = useLikePage() + const unlikePage = useUnlikePage() + const mintPage = useMintPage() + + const { address } = useAccount() + const { openConnectModal } = useConnectModal() + const [likeProgress, setLikeProgress] = useState(false) + const [mintProgress, setMintProgress] = useState(false) + const userSite = useGetUserSites(address) + const router = useRouter() + let [isLikeOpen, setIsLikeOpen] = useState(false) + let [isMintOpen, setIsMintOpen] = useState(false) + let [isLikeListOpen, setIsLikeListOpen] = useState(false) + let [isMintListOpen, setIsMintListOpen] = useState(false) + + const likes = useGetLikes({ + pageId: pageId, + }) + const isLike = useCheckLike({ + address, + pageId: pageId, + }) + + const mints = useGetMints({ + pageId: pageId, + }) + const isMint = useCheckMint({ + address, + pageId: pageId, + }) + + const like = async () => { + if (!address) { + setLikeProgress(true) + openConnectModal?.() + } else if (!userSite.data) { + router.push(`${SITE_URL}/dashboard/new-site`) + } else if (pageId) { + if (isLike.data?.count) { + setIsLikeOpen(true) + } else { + likePage.mutate({ + address, + pageId: pageId, + }) + } + } + } + + const mint = async () => { + if (!address) { + setMintProgress(true) + openConnectModal?.() + } else if (!userSite.data) { + router.push(`${SITE_URL}/dashboard/new-site`) + } else if (pageId) { + if (isMint.data?.count) { + setIsMintOpen(true) + } else { + mintPage.mutate({ + address, + pageId: pageId, + }) + } + } + } + + useEffect(() => { + if ( + mintProgress && + address && + isMint.isSuccess && + pageId && + userSite.isSuccess + ) { + if (!userSite.data) { + router.push(`${SITE_URL}/dashboard/new-site`) + } + if (!isMint.data.count) { + mintPage.mutate({ + address, + pageId: pageId, + }) + } + setMintProgress(false) + } + }, [ + userSite.isSuccess, + userSite.data, + router, + mintProgress, + address, + isMint.isSuccess, + isMint.data?.count, + mintPage, + pageId, + ]) + + useEffect(() => { + if ( + likeProgress && + address && + isLike.isSuccess && + pageId && + userSite.isSuccess + ) { + if (!userSite.data) { + router.push(`${SITE_URL}/dashboard/new-site`) + } + if (!isLike.data.count) { + likePage.mutate({ + address, + pageId: pageId, + }) + } + setLikeProgress(false) + } + }, [ + userSite.isSuccess, + userSite.data, + router, + likeProgress, + address, + isLike.isSuccess, + isLike.data?.count, + likePage, + pageId, + ]) + + const [likeList, setLikeList] = useState([]) + const [likeCursor, setLikeCursor] = useState() + useEffect(() => { + if (likes.isSuccess && likes.data) { + setLikeList(likes.data.list || []) + setLikeCursor(likes.data.cursor || "") + } + }, [likes.isSuccess, likes.data]) + + const loadMoreLikes = async () => { + if (likeCursor && pageId) { + const subs = await getLikes({ + pageId: pageId, + cursor: likeCursor, + }) + setLikeList((prev) => [...prev, ...(subs?.list || [])]) + setLikeCursor(subs?.cursor || "") + } + } + + const [mintList, setMintList] = useState([]) + const [mintCursor, setMintCursor] = useState() + useEffect(() => { + if (mints.isSuccess && mints.data) { + setMintList(mints.data.list || []) + setMintCursor(mints.data.cursor || "") + } + }, [mints.isSuccess, mints.data]) + + const loadMoreMints = async () => { + if (mintCursor && pageId) { + const subs = await getMints({ + pageId: pageId, + cursor: mintCursor, + }) + setMintList((prev) => [...prev, ...(subs?.list || [])]) + setMintCursor(subs?.cursor || "") + } + } + + return ( + <> +
+
+ + {size !== "sm" && ( +
    setIsLikeListOpen(true)} + > + {likes.data?.list + ?.sort((a, b) => + b.fromCharacter?.metadata?.content?.avatars ? 1 : -1, + ) + .slice(0, 3) + ?.map((like: any, index) => ( +
  • + +
  • + ))} + {(likes.data?.count || 0) > 3 && ( +
  • +
    + +{likes.data!.count - 3} +
    +
  • + )} +
+ )} +
+
+ + {size !== "sm" && ( +
    setIsMintListOpen(true)} + > + {mints.data?.list + ?.sort((a, b: any) => + b.character?.metadata?.content?.avatars ? 1 : -1, + ) + .slice(0, 3) + .map((mint: any, index) => ( +
  • + +
  • + ))} + {(mints.data?.count || 0) > 3 && ( +
  • +
    + +{mints.data!.count - 3} +
    +
  • + )} +
+ )} +
+ +
+ Your like has been permanently stored on the blockchain, view them{" "} + + here + +
+
+ +
+
+ +
+ Your minting has been permanently stored on the blockchain, view + them{" "} + + here + +
+
+ +
+
+ + +
+ + ) +} diff --git a/src/components/site/PostFooter.tsx b/src/components/site/PostFooter.tsx index f292f2ea..e94d062d 100644 --- a/src/components/site/PostFooter.tsx +++ b/src/components/site/PostFooter.tsx @@ -1,351 +1,13 @@ import { Note } from "~/lib/types" -import { LikeIcon } from "~/components/icons/LikeIcon" -import { - useLikePage, - useUnlikePage, - useMintPage, - useGetLikes, - useCheckLike, - useGetMints, - useCheckMint, -} from "~/queries/page" -import { useGetUserSites } from "~/queries/site" -import { useAccount } from "wagmi" -import { useConnectModal } from "@rainbow-me/rainbowkit" -import { useState, useEffect } from "react" -import { Button } from "../ui/Button" -import { useRouter } from "next/router" -import { SITE_URL, CSB_SCAN } from "~/lib/env" import { Comment } from "~/components/common/Comment" -import { UniLink } from "~/components/ui/UniLink" -import { Modal } from "~/components/ui/Modal" -import { Avatar } from "../ui/Avatar" -import { MintIcon } from "~/components/icons/MintIcon" -import { getLikes, getMints } from "~/models/page.model" -import { CharacterList } from "~/components/common/CharacterList" +import { Reactions } from "~/components/common/Reactions" export const PostFooter: React.FC<{ page?: Note | null }> = ({ page }) => { - const likePage = useLikePage() - const unlikePage = useUnlikePage() - const mintPage = useMintPage() - - const { address } = useAccount() - const { openConnectModal } = useConnectModal() - const [likeProgress, setLikeProgress] = useState(false) - const [mintProgress, setMintProgress] = useState(false) - const userSite = useGetUserSites(address) - const router = useRouter() - let [isLikeOpen, setIsLikeOpen] = useState(false) - let [isMintOpen, setIsMintOpen] = useState(false) - let [isLikeListOpen, setIsLikeListOpen] = useState(false) - let [isMintListOpen, setIsMintListOpen] = useState(false) - - const likes = useGetLikes({ - pageId: page?.id, - }) - const isLike = useCheckLike({ - address, - pageId: page?.id, - }) - - const mints = useGetMints({ - pageId: page?.id, - }) - const isMint = useCheckMint({ - address, - pageId: page?.id, - }) - - const like = async () => { - if (!address) { - setLikeProgress(true) - openConnectModal?.() - } else if (!userSite.data) { - router.push(`${SITE_URL}/dashboard/new-site`) - } else if (page?.id) { - if (isLike.data?.count) { - setIsLikeOpen(true) - } else { - likePage.mutate({ - address, - pageId: page?.id, - }) - } - } - } - - const mint = async () => { - if (!address) { - setMintProgress(true) - openConnectModal?.() - } else if (!userSite.data) { - router.push(`${SITE_URL}/dashboard/new-site`) - } else if (page?.id) { - if (isMint.data?.count) { - setIsMintOpen(true) - } else { - mintPage.mutate({ - address, - pageId: page?.id, - }) - } - } - } - - useEffect(() => { - if ( - mintProgress && - address && - isMint.isSuccess && - page?.id && - userSite.isSuccess - ) { - if (!userSite.data) { - router.push(`${SITE_URL}/dashboard/new-site`) - } - if (!isMint.data.count) { - mintPage.mutate({ - address, - pageId: page?.id, - }) - } - setMintProgress(false) - } - }, [ - userSite.isSuccess, - userSite.data, - router, - mintProgress, - address, - isMint.isSuccess, - isMint.data?.count, - mintPage, - page?.id, - ]) - - useEffect(() => { - if ( - likeProgress && - address && - isLike.isSuccess && - page?.id && - userSite.isSuccess - ) { - if (!userSite.data) { - router.push(`${SITE_URL}/dashboard/new-site`) - } - if (!isLike.data.count) { - likePage.mutate({ - address, - pageId: page?.id, - }) - } - setLikeProgress(false) - } - }, [ - userSite.isSuccess, - userSite.data, - router, - likeProgress, - address, - isLike.isSuccess, - isLike.data?.count, - likePage, - page?.id, - ]) - - const [likeList, setLikeList] = useState([]) - const [likeCursor, setLikeCursor] = useState() - useEffect(() => { - if (likes.isSuccess && likes.data) { - setLikeList(likes.data.list || []) - setLikeCursor(likes.data.cursor || "") - } - }, [likes.isSuccess, likes.data]) - - const loadMoreLikes = async () => { - if (likeCursor && page?.id) { - const subs = await getLikes({ - pageId: page.id, - cursor: likeCursor, - }) - setLikeList((prev) => [...prev, ...(subs?.list || [])]) - setLikeCursor(subs?.cursor || "") - } - } - - const [mintList, setMintList] = useState([]) - const [mintCursor, setMintCursor] = useState() - useEffect(() => { - if (mints.isSuccess && mints.data) { - setMintList(mints.data.list || []) - setMintCursor(mints.data.cursor || "") - } - }, [mints.isSuccess, mints.data]) - - const loadMoreMints = async () => { - if (mintCursor && page?.id) { - const subs = await getMints({ - pageId: page.id, - cursor: mintCursor, - }) - setMintList((prev) => [...prev, ...(subs?.list || [])]) - setMintCursor(subs?.cursor || "") - } - } - return ( <> -
-
- -
    setIsLikeListOpen(true)} - > - {likes.data?.list - ?.sort((a, b) => - b.fromCharacter?.metadata?.content?.avatars ? 1 : -1, - ) - .slice(0, 3) - ?.map((like: any, index) => ( -
  • - -
  • - ))} - {(likes.data?.count || 0) > 3 && ( -
  • -
    - +{likes.data!.count - 3} -
    -
  • - )} -
-
-
- -
    setIsMintListOpen(true)} - > - {mints.data?.list - ?.sort((a, b: any) => - b.character?.metadata?.content?.avatars ? 1 : -1, - ) - .slice(0, 3) - .map((mint: any, index) => ( -
  • - -
  • - ))} - {(mints.data?.count || 0) > 3 && ( -
  • -
    - +{mints.data!.count - 3} -
    -
  • - )} -
-
- -
- Your like has been permanently stored on the blockchain, view them{" "} - - here - -
-
- -
-
- -
- Your minting has been permanently stored on the blockchain, view - them{" "} - - here - -
-
- -
-
- - -
+ ) diff --git a/src/queries/page.ts b/src/queries/page.ts index b5293f8e..8847ef6a 100644 --- a/src/queries/page.ts +++ b/src/queries/page.ts @@ -179,12 +179,19 @@ export function useCommentPage() { const contract = useContract() const queryClient = useQueryClient() return useMutation( - async (input: Parameters[0]) => { + async ( + input: Parameters[0] & { + originalId?: string + }, + ) => { return pageModel.commentPage(input, contract) }, { onSuccess: (data, variables) => { - queryClient.invalidateQueries(["getComments", variables.pageId]) + queryClient.invalidateQueries([ + "getComments", + variables.originalId || variables.pageId, + ]) }, }, )