diff --git a/app/(main)/r/[slug]/page.tsx b/app/(main)/r/[slug]/page.tsx index faeb978..d52ce4e 100644 --- a/app/(main)/r/[slug]/page.tsx +++ b/app/(main)/r/[slug]/page.tsx @@ -4,6 +4,7 @@ import { getAuthSession } from "@/lib/auth"; import { db } from "@/lib/db"; import { INFINITE_SCROLL_PAGINATION_RESULTS } from "@/config/config"; import SubSpreadItPost from "@/components/posts/sub-spreadIt-post"; +import PostFeed from "@/components/posts/post-feed"; type SlugPageProps = { params: { @@ -41,10 +42,10 @@ const SlugPage = async ({ params }: SlugPageProps) => { r/{subSpreadIt.name} - {/* */} + /> ); }; diff --git a/components/posts/post-feed.tsx b/components/posts/post-feed.tsx new file mode 100644 index 0000000..28bd14a --- /dev/null +++ b/components/posts/post-feed.tsx @@ -0,0 +1,103 @@ +"use client"; + +import { useIntersection } from "@mantine/hooks"; +import { useSession } from "next-auth/react"; +import { useEffect, useRef } from "react"; +import { useInfiniteQuery } from "@tanstack/react-query"; +import axios from "axios"; +import { Loader2 } from "lucide-react"; + +import { ExtendedPost } from "@/types/db"; +import { INFINITE_SCROLL_PAGINATION_RESULTS } from "@/config/config"; +import Post from "./post"; + +type PostFeedProps = { + initialPosts: ExtendedPost[]; + subSpreadItName: string; +}; + +const PostFeed = ({ initialPosts, subSpreadItName }: PostFeedProps) => { + const lastPostRef = useRef(null); + const { ref, entry } = useIntersection({ + root: lastPostRef.current, + threshold: 1, + }); + const { data: session } = useSession(); + + const fetchPosts = async ({ pageParam = 1 }: { pageParam: number }) => { + const query = + `/api/posts?limit=${INFINITE_SCROLL_PAGINATION_RESULTS}&page=${pageParam}` + + (subSpreadItName ? `&subSpreadItName=${subSpreadItName}` : ""); + + const { data } = await axios.get(query); + return data as ExtendedPost[]; + }; + + const { data, fetchNextPage, isFetchingNextPage } = useInfiniteQuery< + ExtendedPost[], + Error + >({ + queryKey: ["infinite-query"], + queryFn: () => fetchPosts({ pageParam: 1 }), // Provide an initial value for pageParam + getNextPageParam: (_, pages) => { + return pages.length + 1; + }, + initialData: { + pages: [initialPosts], + pageParams: [1], + }, + initialPageParam: 1, // Provide the initialPageParam property + staleTime: Infinity, + }); + + useEffect(() => { + if (entry?.isIntersecting) { + fetchNextPage(); // Load more posts when the last post comes into view + } + }, [entry, fetchNextPage]); + + const posts = data?.pages.flatMap((page) => page) ?? initialPosts; + + const postsComponents = posts.map((post, index) => { + const votesAmt = post.votes.reduce((acc, vote) => { + if (vote.type === "UP") return acc + 1; + if (vote.type === "DOWN") return acc - 1; + return acc ?? 0; + }, 0); + + const currentVote = post.votes.find( + (vote) => vote.userId === session?.user?.id + ); + + const isLastPost = index === posts.length - 1; + + return ( +
  • + +
  • + ); + }); + + return ( +
      + {postsComponents} + {isFetchingNextPage && ( +
    • + +
    • + )} +
    • +
    + ); +}; + +export default PostFeed; diff --git a/components/posts/post.tsx b/components/posts/post.tsx new file mode 100644 index 0000000..56fb466 --- /dev/null +++ b/components/posts/post.tsx @@ -0,0 +1,88 @@ +"use client"; + +import { Vote, Post, User } from "@prisma/client"; +import { useRef } from "react"; +import Link from "next/link"; +import { MessageSquare } from "lucide-react"; + +import { formatTimeToNow } from "@/lib/utils"; +import TextEditorOutput from "../rich-text-editor/text-editor-output"; + +type PartialVote = Pick; + +type PostProps = { + post: Post & { + author: User; + votes: Vote[]; + }; + votesAmt: number; + subSpreadItName: string; + currentVote?: PartialVote; + commentAmt: number; +}; + +const Post = ({ + post, + votesAmt: _votesAmt, + currentVote: _currentVote, + subSpreadItName, + commentAmt, +}: PostProps) => { + const pRef = useRef(null); + return ( +
    +
    + {/* */} + +
    +
    + {subSpreadItName ? ( + <> + + r/{subSpreadItName} + + + + ) : null} + Posted by u/{post.author.username}{" "} + {formatTimeToNow(new Date(post.createdAt))} +
    + +

    + {post.title} +

    +
    + +
    + + {pRef.current?.clientHeight === 160 ? ( + // blur bottom if content is too long +
    + ) : null} +
    +
    +
    + +
    + + {commentAmt} comments + +
    +
    + ); +}; + +export default Post; diff --git a/components/renderers/custom-code-renderer.tsx b/components/renderers/custom-code-renderer.tsx new file mode 100644 index 0000000..0c30da1 --- /dev/null +++ b/components/renderers/custom-code-renderer.tsx @@ -0,0 +1,13 @@ +"use client"; + +const CustomCodeRenderer = ({ data }: any) => { + data; + + return ( +
    +      {data.code}
    +    
    + ); +}; + +export default CustomCodeRenderer; diff --git a/components/renderers/custom-image-renderer.tsx b/components/renderers/custom-image-renderer.tsx new file mode 100644 index 0000000..aeb06a5 --- /dev/null +++ b/components/renderers/custom-image-renderer.tsx @@ -0,0 +1,20 @@ +"use client"; + +import Image from "next/image"; + +const CustomImageRenderer = ({ data }: any) => { + const src = data.file.url; + + return ( +
    + image +
    + ); +}; + +export default CustomImageRenderer; diff --git a/components/rich-text-editor/text-editor-output.tsx b/components/rich-text-editor/text-editor-output.tsx new file mode 100644 index 0000000..88790cd --- /dev/null +++ b/components/rich-text-editor/text-editor-output.tsx @@ -0,0 +1,13 @@ +"use client"; + +//import { } from ' '; + +type TextEditorOutputProps = { + content: any; +}; + +const TextEditorOutput = ({}: TextEditorOutputProps) => { + return
    TextEditorOutput
    ; +}; + +export default TextEditorOutput; diff --git a/components/rich-text-editor/text-editor.tsx b/components/rich-text-editor/text-editor.tsx index 58ebde8..2d52f34 100644 --- a/components/rich-text-editor/text-editor.tsx +++ b/components/rich-text-editor/text-editor.tsx @@ -32,7 +32,7 @@ const TextEditor = ({ subSpreadItId }: TextEditorProps) => { content: null, }, }); - // const [headerTitle, setHeaderTitle] = useState(""); + const [editorContent, setEditorContent] = useState(); const router = useRouter(); const pathname = usePathname(); @@ -91,12 +91,9 @@ const TextEditor = ({ subSpreadItId }: TextEditorProps) => { } }, [errors]); - //TODO: Finish implementation async function handleEditorChange(editor: any) { const editorBlocks = JSON.parse(editor); setEditorContent(editorBlocks); - // console.log(editorBlocks.map((block: any) => block.content)); - console.log(editorBlocks); } async function onSubmit(data: FormData) { @@ -109,7 +106,6 @@ const TextEditor = ({ subSpreadItId }: TextEditorProps) => { } const payload: PostCreationRequest = { - //TODO: Check values title: data.title, content: editorContent, subSpreadItId, @@ -127,8 +123,6 @@ const TextEditor = ({ subSpreadItId }: TextEditorProps) => { >
    setHeaderTitle(e.target.value)} {...register("title")} placeholder="Title" className="w-full resize-none appearance-none overflow-hidden bg-transparent text-5xl font-bold focus:outline-none placeholder:text-muted-foreground"