A lot of code trying to make the replies work (I can't recursively show all parent replies because Prisma hasn't implemented recursive relation includes after 2 years of the initial request, which sucks)

This commit is contained in:
Joonas 2023-11-18 17:53:07 +02:00
parent 364d3a1917
commit 1e632c52be
3 changed files with 332 additions and 120 deletions

View File

@ -3,9 +3,8 @@ import { Button } from "./Button";
import { Card } from "./Card";
import { FormLabel, TextArea } from "./Form";
import { SubTitle, Text } from "./Typography";
import type { User } from "@prisma/client";
import type { PostWithRelations } from "~/utils/prisma.server";
import { ReactNode } from "react";
import type { ReactNode } from "react";
export function Poster({
username,
@ -72,11 +71,17 @@ export function Post({
userId,
post,
topTitle,
child,
reply,
enlarge,
rootPostId,
}: {
post: PostWithRelations;
userId?: number;
topTitle?: ReactNode;
child?: boolean;
reply?: boolean;
enlarge?: boolean;
rootPostId?: number;
}) {
const fetcher = useFetcher();
@ -86,103 +91,175 @@ export function Post({
return (
<>
{post?.parentReply ? (
<Post post={post.parentReply} userId={userId} rootPostId={rootPostId} />
{post?.parentReply?.text ? (
<>
<Post
post={post.parentReply}
userId={userId}
child={true}
reply={true}
rootPostId={rootPostId}
topTitle={
<>
Replying to{" "}
{post.parentReply.parentReply ? (
<Poster
style="compact"
username={post.parentReply.parentReply.author.username}
name={post.parentReply.parentReply.author.name}
pfp={post.parentReply.parentReply.author.pfp}
/>
) : (
<Poster
style="compact"
username={post.parentReply.post.author.username}
name={post.parentReply.post.author.name}
pfp={post.parentReply.post.author.pfp}
/>
)}
</>
}
/>
<div className="h-8 pl-[39px]">
<div className="border w-0 h-full"></div>
</div>
</>
) : (
""
)}
<div
key={post.id}
className="flex relative flex-col gap-4 p-6 transition-all bg-ctp-mantle/20 hover:bg-ctp-base first:rounded-t-lg last:rounded-b-lg "
className={`flex relative flex-col ${
enlarge ? "text-xl" : ""
} gap-4 transition-all bg-ctp-mantle/20 hover:bg-ctp-base first:rounded-t-lg last:rounded-b-lg`}
>
{topTitle ? <Text type="subtitle">{topTitle}</Text> : ""}
{post.reposters?.length > 0 ? (
<div className="flex gap-2 items-center">
{post.reposters.map((user) => (
<Poster
style="compact"
key={user.id}
name={user.name}
username={user.username}
pfp={user.pfp}
<div
className={` ${!child && reply ? `border-y py-4` : ""} ${
!reply ? "!p-4" : ""
} px-4`}
>
<div className="flex gap-4">
<div className="flex flex-col justify-center items-center min-h-full">
<img
className="w-12 h-12 rounded-full"
src={`/uploads/${post.author.pfp}`}
alt=""
/>
))}
<Text type="subtitle">reposted this</Text>
</div>
) : (
""
)}
<div className="flex items-center">
<Poster
pfp={post.author.pfp}
username={post.author.username}
name={post.author.name}
/>
</div>
<Link to={`/${rootPostId ? "reply" : "post"}/${post.id}`}>
<div>
<Text>{post.text}</Text>
</div>
</Link>
<div className="flex gap-2 text-sm">
<Text>{new Date(post.createdAt).toLocaleDateString()}</Text>
<fetcher.Form action={`/home`} method="POST">
<input
type="hidden"
name="intent"
value={liked ? "unlike" : "like"}
/>
<input type="hidden" name="postId" value={post.id} />
<button type="submit">
<Text type="link">
{liked ? "Unlike" : "Like"} ({post.likes.length})
</Text>
</button>
</fetcher.Form>
<fetcher.Form action={`/home`} method="POST">
<input
type="hidden"
name="intent"
value={reposted ? "unrepost" : "repost"}
/>
<input type="hidden" name="postId" value={post.id} />
<button type="submit">
<Text type="link">
{reposted ? "Unrepost" : "Repost"} ({post.reposts.length})
</Text>
</button>
</fetcher.Form>
<details className="space-y-2">
<summary className="flex cursor-pointer">
<Text type="link">
Reply ({post._count.replies}
{post._count.childReplies})
</Text>
</summary>
<Card className="absolute max-w-xs z-100">
<SubTitle>Reply to {post.author.username}</SubTitle>
<fetcher.Form
action={`/home`}
className="flex flex-col gap-3"
method="POST"
>
<input type="hidden" name="intent" value={"reply"} />
{rootPostId ? (
<>
<input type="hidden" name="postId" value={rootPostId} />
<input type="hidden" name="replyId" value={post.id} />
</>
{child ? <div className="w-0 border h-full"></div> : ""}
</div>
<div className="flex flex-col gap-2">
<div className="flex flex-col gap-2">
{post.reposters?.length > 0 ? (
<div className="flex gap-2 items-center">
{post.reposters.map((user) => (
<Poster
style="compact"
key={user.id}
name={user.name}
username={user.username}
pfp={user.pfp}
/>
))}
<Text type="subtitle">reposted this</Text>
</div>
) : (
<input type="hidden" name="postId" value={post.id} />
""
)}
<FormLabel>
<Text>Reply body</Text>
<TextArea name="reply" />
</FormLabel>
<Button type="submit">Post</Button>
</fetcher.Form>
</Card>
</details>
{topTitle ? (
<Text className="flex gap-1" type="subtitle">
{topTitle}
</Text>
) : (
""
)}
<div className="flex gap-2">
{post.author.name ? (
<>
<SubTitle>{post.author.name}</SubTitle>
<Link to={`/users/${post.author.username}`}>
<Text type="link">@{post.author.username}</Text>
</Link>
</>
) : (
<SubTitle>
<Link to={`/users/${post.author.username}`}>
<Text type="link">@{post.author.username}</Text>
</Link>
</SubTitle>
)}
</div>
</div>
<Link to={`/${rootPostId ? "reply" : "post"}/${post.id}`}>
<div>
<Text>{post.text}</Text>
</div>
</Link>
<div className="flex gap-2 text-sm">
<Text>{new Date(post.createdAt).toLocaleDateString()}</Text>
<fetcher.Form action={`/home`} method="POST">
<input
type="hidden"
name="intent"
value={liked ? "unlike" : "like"}
/>
<input type="hidden" name="postId" value={post.id} />
<button type="submit">
<Text type="link">
{liked ? "Unlike" : "Like"} ({post.likes.length})
</Text>
</button>
</fetcher.Form>
<fetcher.Form action={`/home`} method="POST">
<input
type="hidden"
name="intent"
value={reposted ? "unrepost" : "repost"}
/>
<input type="hidden" name="postId" value={post.id} />
<button type="submit">
<Text type="link">
{reposted ? "Unrepost" : "Repost"} ({post.reposts.length})
</Text>
</button>
</fetcher.Form>
<details className="space-y-2">
<summary className="flex cursor-pointer">
<Text type="link">
Reply ({post._count.replies}
{post._count.childReplies})
</Text>
</summary>
<Card className="absolute max-w-xs z-100">
<SubTitle>Reply to {post.author.username}</SubTitle>
<fetcher.Form
action={`/home`}
className="flex flex-col gap-3"
method="POST"
>
<input type="hidden" name="intent" value={"reply"} />
{rootPostId ? (
<>
<input
type="hidden"
name="postId"
value={rootPostId}
/>
<input type="hidden" name="replyId" value={post.id} />
</>
) : (
<input type="hidden" name="postId" value={post.id} />
)}
<FormLabel>
<Text>Reply body</Text>
<TextArea name="reply" />
</FormLabel>
<Button type="submit">Post</Button>
</fetcher.Form>
</Card>
</details>
</div>
</div>
</div>
</div>
</div>
</>

View File

@ -14,7 +14,7 @@ import { Card } from "~/components/Card";
import { SubTitle, Text, Title } from "~/components/Typography";
import type { PostWithRelations } from "~/utils/prisma.server";
import { prisma } from "~/utils/prisma.server";
import { Post } from "~/components/Post";
import { Post, Poster } from "~/components/Post";
import type { RootLoaderTypes } from "~/root";
import { Button } from "~/components/Button";
import { FormLabel, TextArea } from "~/components/Form";
@ -166,9 +166,14 @@ export default function PostRoute() {
return (
<div className="flex flex-col gap-5">
<Title>Post</Title>
<Card className="!p-0 !gap-0 divide-y">
<div className="bg-ctp-pink/5">
<Post key={data.id} post={data} userId={rootData?.id} />
<Card className="!p-0 !gap-0">
<div className="border-b">
<Post
key={data.id}
post={data}
enlarge={true}
userId={rootData?.id}
/>
</div>
{rootData?.id ? (
<div className="p-6 !bg-ctp-sky/5">
@ -192,7 +197,23 @@ export default function PostRoute() {
)}
{data.replies.map((reply) => (
<div key={reply.id}>
<Post post={reply} userId={rootData?.id} rootPostId={data.id} />
<Post
post={reply}
userId={rootData?.id}
rootPostId={data.id}
reply={true}
topTitle={
<Text className="flex gap-1" type="subtitle">
Replying to{" "}
<Poster
style="compact"
username={data.author.username}
name={data.author.name}
pfp={data.author.pfp}
/>
</Text>
}
/>
</div>
))}
</Card>

View File

@ -14,7 +14,7 @@ import { Card } from "~/components/Card";
import { SubTitle, Text, Title } from "~/components/Typography";
import type { PostWithRelations } from "~/utils/prisma.server";
import { prisma } from "~/utils/prisma.server";
import { Post } from "~/components/Post";
import { Post, Poster } from "~/components/Post";
import type { RootLoaderTypes } from "~/root";
import { Button } from "~/components/Button";
import { FormLabel, TextArea } from "~/components/Form";
@ -53,26 +53,6 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
id: true,
text: true,
createdAt: true,
author: {
select: {
id: true,
username: true,
name: true,
pfp: true,
},
},
likes: {
select: {
id: true,
userId: true,
},
},
reposts: {
select: {
id: true,
userId: true,
},
},
parentReply: {
select: {
id: true,
@ -86,6 +66,42 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
pfp: true,
},
},
post: {
include: {
author: {
select: {
id: true,
username: true,
name: true,
pfp: true,
},
},
},
},
parentReply: {
select: {
author: {
select: {
id: true,
username: true,
name: true,
pfp: true,
},
},
post: {
include: {
author: {
select: {
id: true,
username: true,
name: true,
pfp: true,
},
},
},
},
},
},
likes: {
select: {
id: true,
@ -105,6 +121,74 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
},
},
},
author: {
select: {
id: true,
username: true,
name: true,
pfp: true,
},
},
likes: {
select: {
id: true,
userId: true,
},
},
post: {
select: {
id: true,
text: true,
createdAt: true,
likes: {
select: {
id: true,
userId: true,
postId: true,
user: {
select: {
username: true,
name: true,
pfp: true,
},
},
},
},
reposts: {
select: {
id: true,
userId: true,
postId: true,
user: {
select: {
username: true,
name: true,
pfp: true,
},
},
},
},
author: {
select: {
id: true,
username: true,
name: true,
pfp: true,
},
},
_count: {
select: {
replies: true,
},
},
},
},
reposts: {
select: {
id: true,
userId: true,
},
},
_count: {
select: {
childReplies: true,
@ -257,20 +341,49 @@ export default function ReplyRoute() {
const rootData = useRouteLoaderData<RootLoaderTypes>("root");
const data: PostWithRelations = useLoaderData<LoaderFunction>();
const errors = useActionData<ActionFunction>();
console.log(data);
return (
<div className="flex flex-col gap-5">
<Title>Post</Title>
<Card className="!p-0 !gap-0 divide-y">
<div className="text-sm">
<Post key={data.id} post={data.post} userId={rootData?.id} />
<Card className="!p-0 !pt-6 !gap-0">
<Post
post={data.post}
userId={rootData?.id}
child={true}
reply={true}
/>
<div className="h-8 pl-[39px]">
<div className="border w-0 h-full"></div>
</div>
<div className="text-xl bg-ctp-pink/5">
<div>
<Post
key={data.id}
post={data}
userId={rootData?.id}
rootPostId={data.post.id}
enlarge={true}
reply={true}
topTitle={
<>
Replying to
{data.parentReply ? (
<Poster
style="compact"
username={data.parentReply.author.username}
name={data.parentReply.author.name}
pfp={data.parentReply.author.pfp}
/>
) : (
<Poster
style="compact"
username={data.post.author.username}
name={data.post.author.name}
pfp={data.post.author.pfp}
/>
)}
</>
}
/>
</div>
{rootData?.id ? (
@ -300,6 +413,7 @@ export default function ReplyRoute() {
post={reply}
userId={rootData?.id}
rootPostId={data.post.id}
reply={true}
/>
</div>
))}