Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: ✨ Type: Feature, Enhancement | Title: Enhancements in Post Creation and Display Post Feed #14

Merged
merged 1 commit into from
Dec 24, 2023
Merged
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
5 changes: 3 additions & 2 deletions app/(main)/r/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down Expand Up @@ -41,10 +42,10 @@ const SlugPage = async ({ params }: SlugPageProps) => {
r/{subSpreadIt.name}
</h1>
<SubSpreadItPost session={session} />
{/* <PostFeed
<PostFeed
initialPosts={subSpreadIt.posts}
subSpreadItName={subSpreadIt.name}
/> */}
/>
</>
);
};
Expand Down
103 changes: 103 additions & 0 deletions components/posts/post-feed.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLElement>(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 (
<li
key={post.id}
ref={isLastPost ? ref : null}
>
<Post
post={post}
commentAmt={post.comments.length}
subSpreadItName={post.subSpreadIt?.name}
votesAmt={votesAmt}
currentVote={currentVote}
/>
</li>
);
});

return (
<ul className="flex flex-col col-span-2 space-y-6">
{postsComponents}
{isFetchingNextPage && (
<li className="flex justify-center">
<Loader2 className="w-6 h-6 text-muted-foreground animate-spin" />
</li>
)}
<li ref={ref}></li>
</ul>
);
};

export default PostFeed;
88 changes: 88 additions & 0 deletions components/posts/post.tsx
Original file line number Diff line number Diff line change
@@ -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<Vote, "type">;

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<HTMLParagraphElement>(null);
return (
<div className="rounded-md bg-background border">
<div className="px-6 py-4 flex justify-between">
{/* <PostVoteClient
postId={post.id}
initialVotesAmt={_votesAmt}
initialVote={_currentVote?.type}
/> */}

<div className="w-0 flex-1">
<div className="max-h-40 mt-1 text-xs text-muted-foreground">
{subSpreadItName ? (
<>
<a
className="underline text-emerald-500 text-sm underline-offset-2"
href={`/r/${subSpreadItName}`}
>
r/{subSpreadItName}
</a>
<span className="px-1">•</span>
</>
) : null}
<span>Posted by u/{post.author.username}</span>{" "}
{formatTimeToNow(new Date(post.createdAt))}
</div>
<a href={`/r/${subSpreadItName}/post/${post.id}`}>
<h1 className="text-lg font-semibold py-2 leading-6 text-muted-foreground">
{post.title}
</h1>
</a>

<div
className="relative text-sm max-h-40 w-full overflow-clip"
ref={pRef}
>
<TextEditorOutput content={post.content} />
{pRef.current?.clientHeight === 160 ? (
// blur bottom if content is too long
<div className="absolute bottom-0 left-0 h-24 w-full bg-gradient-to-t from-background to-transparent"></div>
) : null}
</div>
</div>
</div>

<div className="bg-background z-20 text-sm px-4 py-4 sm:px-6">
<Link
href={`/r/${subSpreadItName}/post/${post.id}`}
className="w-fit flex items-center gap-2 text-muted-foreground"
>
<MessageSquare className="h-4 w-4" /> {commentAmt} comments
</Link>
</div>
</div>
);
};

export default Post;
13 changes: 13 additions & 0 deletions components/renderers/custom-code-renderer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"use client";

const CustomCodeRenderer = ({ data }: any) => {
data;

return (
<pre className="bg-gray-800 rounded-md p-4">
<code className="text-gray-100 text-sm">{data.code}</code>
</pre>
);
};

export default CustomCodeRenderer;
20 changes: 20 additions & 0 deletions components/renderers/custom-image-renderer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"use client";

import Image from "next/image";

const CustomImageRenderer = ({ data }: any) => {
const src = data.file.url;

return (
<div className="relative w-full min-h-[15rem]">
<Image
alt="image"
className="object-contain"
fill
src={src}
/>
</div>
);
};

export default CustomImageRenderer;
13 changes: 13 additions & 0 deletions components/rich-text-editor/text-editor-output.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"use client";

//import { } from ' ';

type TextEditorOutputProps = {
content: any;
};

const TextEditorOutput = ({}: TextEditorOutputProps) => {
return <div> TextEditorOutput </div>;
};

export default TextEditorOutput;
8 changes: 1 addition & 7 deletions components/rich-text-editor/text-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const TextEditor = ({ subSpreadItId }: TextEditorProps) => {
content: null,
},
});
// const [headerTitle, setHeaderTitle] = useState("");

const [editorContent, setEditorContent] = useState();
const router = useRouter();
const pathname = usePathname();
Expand Down Expand Up @@ -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) {
Expand All @@ -109,7 +106,6 @@ const TextEditor = ({ subSpreadItId }: TextEditorProps) => {
}

const payload: PostCreationRequest = {
//TODO: Check values
title: data.title,
content: editorContent,
subSpreadItId,
Expand All @@ -127,8 +123,6 @@ const TextEditor = ({ subSpreadItId }: TextEditorProps) => {
>
<div className="prose prose-stone dark:prose-invert">
<TextareaAutosize
// value={headerTitle}
// onChange={(e) => 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"
Expand Down
Loading