Remade likes, added replies, added post and reply specific pages

This commit is contained in:
Joonas 2023-11-16 23:02:26 +02:00
parent ac57b9a09b
commit e993eabeeb
11 changed files with 1040 additions and 262 deletions

View File

@ -10,7 +10,7 @@ export function Button({
}) {
return (
<button
className="px-6 py-2 rounded-lg border w-fit hover:bg-ctp-crust"
className="px-6 py-2 rounded-lg border w-fit bg-ctp-crust/50 hover:bg-ctp-crust"
type={type}
>
<Text>{children}</Text>

View File

@ -72,100 +72,119 @@ export function Post({
userId,
post,
topTitle,
rootPostId,
}: {
post: PostWithRelations;
userId?: number;
topTitle?: ReactNode;
rootPostId?: number;
}) {
const fetcher = useFetcher();
const liked = post.likes.filter((like) => like.id === userId).length > 0;
const liked = post.likes.filter((like) => like.userId === userId).length > 0;
const reposted =
post.reposts.filter((repost) => repost.userId === userId).length > 0;
return (
<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 "
>
{topTitle ? <Text type="subtitle">{topTitle}</Text> : ""}
{post.reposters?.length > 0 ? (
<div className="flex gap-1 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>
<>
{post?.parentReply ? (
<Post post={post.parentReply} userId={userId} rootPostId={rootPostId} />
) : (
""
)}
<div className="flex items-center">
<Poster
pfp={post.author.pfp}
username={post.author.username}
name={post.author.name}
/>
</div>
<Link to={`/post/${post.id}`}>
<div>
<Text>{post.text}</Text>
<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 "
>
{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}
/>
))}
<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} />
</>
) : (
<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>
</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</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"} />
<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>
</>
);
}

View File

@ -22,16 +22,10 @@ export async function likePost(postId: number, id: number) {
return errors;
}
const like = await prisma.user.update({
where: {
id: id,
},
const like = await prisma.like.create({
data: {
likes: {
connect: {
id: post?.id,
},
},
postId: postId,
userId: id,
},
});
@ -50,27 +44,31 @@ export async function unLikePost(postId: number, id: number) {
};
const post = await getPostById(postId);
if (!post) {
errors.postId = "No post with that id";
}
const like = await prisma.like.findFirst({
where: {
userId: id,
postId: postId,
},
});
if (!like) {
errors.like = "Repost doesn't exist";
}
if (Object.values(errors).some(Boolean)) {
return errors;
}
const like = await prisma.user.update({
const likeQ = await prisma.like.delete({
where: {
id: id,
},
data: {
likes: {
disconnect: [{ id: postId }],
},
id: like.id,
},
});
if (!like) {
if (!likeQ) {
errors.like = "Something went wrong unliking the post";
return errors;
}

View File

@ -31,7 +31,7 @@ export async function getPostById(id: number) {
return post;
}
function validatePostBody(body: string) {
export function validatePostBody(body: string) {
if (!body) {
return "Post body can't be empty";
}

View File

@ -0,0 +1,57 @@
import { prisma } from "~/utils/prisma.server";
import { getPostById, validatePostBody } from "./post.server";
import type { Reply } from "@prisma/client";
async function getReplyById(id: number) {
const reply = await prisma.reply.findUnique({
where: {
id: id,
},
});
return reply;
}
export async function reply(
body: Reply["text"],
postId: Reply["postId"],
replyId: Reply["parentReplyId"] = null,
userId: Reply["userId"]
) {
const errors: {
body: string | undefined;
post: string | undefined;
reply: string | undefined;
} = {
body: validatePostBody(body),
post: undefined,
reply: undefined,
};
if (replyId && !(await getReplyById(replyId))) {
errors.reply = "Reply not found";
}
if (!(await getPostById(postId))) {
errors.post = "Post not found";
}
if (Object.values(errors).some(Boolean)) {
return errors;
}
const post = await prisma.reply.create({
data: {
text: body,
userId: userId,
postId: postId,
parentReplyId: replyId,
},
});
if (!post) {
errors.reply = "Something went wrong creating the post";
}
return errors;
}

View File

@ -116,7 +116,7 @@ export default function App() {
<Links />
</head>
<body className="min-h-screen bg-ctp-base text-ctp-text">
<div className="container flex flex-col gap-20 m-8 mx-auto xl:flex-row">
<div className="container flex flex-col gap-20 m-8 lg:px-40 mx-auto xl:flex-row">
<div className="flex mx-auto justify-center flex-grow w-80">
<nav className="flex w-full flex-col gap-5">
{!data?.id ? (

View File

@ -22,6 +22,7 @@ import { createPost } from "~/models/post.server";
import { commitSession, getSession } from "~/utils/session.server";
import type { RootLoaderTypes } from "~/root";
import { repost, unRepost } from "~/models/repost.server";
import { reply } from "~/models/reply.server";
export async function action({ request }: ActionFunctionArgs) {
const session = await getSession(request.headers.get("Cookie"));
@ -85,6 +86,21 @@ export async function action({ request }: ActionFunctionArgs) {
session.flash("globalMessage", "Post un-reposted!");
break;
case "reply":
const replyErrors = await reply(
String(formData.reply),
Number(formData.postId),
Number(formData.replyId),
userId
);
if (Object.values(replyErrors).some(Boolean)) {
return replyErrors;
}
session.flash("globalMessage", "Replied to post!");
break;
default:
return;
@ -110,7 +126,7 @@ export async function loader({ request }: LoaderFunctionArgs) {
where: { id: userId },
select: { following: { select: { id: true } } },
});
const followingArr = [...following.following.map((user) => user.id)];
const followingArr = [...following.following.map((user) => user.id), userId];
const feed = await prisma.post.findMany({
where: {
@ -157,7 +173,24 @@ export async function loader({ request }: LoaderFunctionArgs) {
},
likes: {
select: {
id: true,
userId: true,
user: {
select: {
id: true,
username: true,
pfp: true,
name: true,
},
},
},
},
_count: {
select: {
replies: {
where: {
parentReplyId: null,
},
},
},
},
},
@ -200,10 +233,7 @@ export default function Index() {
</Form>
</>
) : (
<>
<Title>Twitter clone</Title>
<SubTitle>Made with Remix</SubTitle>
</>
""
)}
</Card>
<SubTitle>Feed</SubTitle>

201
app/routes/post.$id.tsx Normal file
View File

@ -0,0 +1,201 @@
import type { ActionFunction } from "@remix-run/node";
import {
json,
type LoaderFunction,
type LoaderFunctionArgs,
} from "@remix-run/node";
import {
Form,
useActionData,
useLoaderData,
useRouteLoaderData,
} from "@remix-run/react";
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 type { RootLoaderTypes } from "~/root";
import { Button } from "~/components/Button";
import { FormLabel, TextArea } from "~/components/Form";
export async function loader({ request, params }: LoaderFunctionArgs) {
const post = await prisma.post.findUnique({
where: {
id: Number(params.id),
},
select: {
id: true,
userId: true,
text: true,
author: {
select: {
id: true,
username: true,
name: true,
pfp: true,
},
},
likes: {
select: {
id: true,
userId: true,
},
},
reposts: {
select: {
id: true,
userId: true,
},
},
replies: {
where: {
parentReplyId: null,
},
select: {
id: true,
text: true,
createdAt: true,
likes: {
select: {
id: true,
userId: true,
postId: true,
},
},
author: {
select: {
id: true,
username: true,
name: true,
pfp: true,
},
},
reposts: {
select: {
id: true,
userId: true,
postId: 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,
},
},
},
},
_count: {
select: {
childReplies: true,
},
},
},
orderBy: {
likes: {
_count: "desc",
},
},
},
createdAt: true,
_count: {
select: {
replies: {
where: {
parentReplyId: null,
},
},
},
},
},
});
if (!post) {
throw Error("Post not found");
}
return json(post);
}
export default function PostRoute() {
const rootData = useRouteLoaderData<RootLoaderTypes>("root");
const data: PostWithRelations = useLoaderData<LoaderFunction>();
const errors = useActionData<ActionFunction>();
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} />
</div>
{rootData?.id ? (
<div className="p-6 !bg-ctp-sky/5">
<SubTitle>New reply</SubTitle>
<Form action="/home" className="flex flex-col gap-3" method="POST">
<input type="hidden" name="intent" value={"reply"} />
<input type="hidden" name="postId" value={data.id} />
<FormLabel>
<Text>Reply body</Text>
<TextArea name="reply" />
<Text type="error">{errors?.body ? errors.body : ""}</Text>
</FormLabel>
<Button type="submit">Post</Button>
</Form>
</div>
) : (
<>
<Title>Twitter clone</Title>
<SubTitle>Made with Remix</SubTitle>
</>
)}
{data.replies.map((reply) => (
<div key={reply.id}>
<Post post={reply} userId={rootData?.id} rootPostId={data.id} />
</div>
))}
</Card>
</div>
);
}

309
app/routes/reply.$id.tsx Normal file
View File

@ -0,0 +1,309 @@
import type { ActionFunction } from "@remix-run/node";
import {
json,
type LoaderFunction,
type LoaderFunctionArgs,
} from "@remix-run/node";
import {
Form,
useActionData,
useLoaderData,
useRouteLoaderData,
} from "@remix-run/react";
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 type { RootLoaderTypes } from "~/root";
import { Button } from "~/components/Button";
import { FormLabel, TextArea } from "~/components/Form";
export async function loader({ request, params }: LoaderFunctionArgs) {
const reply = await prisma.reply.findUnique({
where: {
id: Number(params.id),
},
select: {
id: true,
userId: true,
text: 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,
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,
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,
},
},
_count: {
select: {
childReplies: true,
},
},
},
},
_count: {
select: {
childReplies: true,
},
},
},
},
post: {
select: {
id: true,
text: true,
createdAt: true,
likes: {
select: {
id: true,
userId: true,
postId: true,
},
},
reposts: {
select: {
id: true,
userId: true,
postId: true,
},
},
author: {
select: {
id: true,
username: true,
name: true,
pfp: true,
},
},
_count: {
select: {
replies: {
where: {
parentReplyId: null,
},
},
},
},
},
},
childReplies: {
select: {
id: true,
text: true,
createdAt: true,
likes: {
select: {
id: true,
userId: true,
postId: true,
},
},
author: {
select: {
id: true,
username: true,
name: true,
pfp: true,
},
},
reposts: {
select: {
id: true,
userId: true,
postId: 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,
},
},
},
},
_count: {
select: {
childReplies: true,
},
},
},
orderBy: {
likes: {
_count: "desc",
},
},
},
createdAt: true,
_count: {
select: {
childReplies: true,
},
},
},
});
if (!reply) {
throw Error("Reply not found");
}
return json(reply);
}
export default function ReplyRoute() {
const rootData = useRouteLoaderData<RootLoaderTypes>("root");
const data: PostWithRelations = useLoaderData<LoaderFunction>();
const errors = useActionData<ActionFunction>();
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} />
</div>
<div className="text-xl bg-ctp-pink/5">
<Post
key={data.id}
post={data}
userId={rootData?.id}
rootPostId={data.post.id}
/>
</div>
{rootData?.id ? (
<div className="p-6 !bg-ctp-sky/5">
<SubTitle>New reply</SubTitle>
<Form action="/home" className="flex flex-col gap-3" method="POST">
<input type="hidden" name="intent" value={"reply"} />
<input type="hidden" name="postId" value={data.post.id} />
<input type="hidden" name="replyId" value={data.id} />
<FormLabel>
<Text>Reply body</Text>
<TextArea name="reply" />
<Text type="error">{errors?.body ? errors.body : ""}</Text>
</FormLabel>
<Button type="submit">Post</Button>
</Form>
</div>
) : (
<>
<Title>Twitter clone</Title>
<SubTitle>Made with Remix</SubTitle>
</>
)}
{data.childReplies.map((reply) => (
<div key={reply.id}>
<Post
post={reply}
userId={rootData?.id}
rootPostId={data.post.id}
/>
</div>
))}
</Card>
</div>
);
}

View File

@ -5,8 +5,8 @@ import type {
} from "@remix-run/node";
import {
Form,
Link,
useActionData,
Link,
useLoaderData,
useRouteError,
useRouteLoaderData,
@ -15,7 +15,7 @@ import { Button } from "~/components/Button";
import { Card } from "~/components/Card";
import { Post, Poster } from "~/components/Post";
import { SubTitle, Text, Title } from "~/components/Typography";
import { followUser, getUserByName, unFollowUser } from "~/models/user.server";
import { followUser, unFollowUser } from "~/models/user.server";
import type { RootLoaderTypes } from "~/root";
import { prisma, type UserWithRelations } from "~/utils/prisma.server";
import { getSession } from "~/utils/session.server";
@ -60,7 +60,10 @@ export async function action({ request }: LoaderFunctionArgs) {
return null;
}
export async function loader({ params }: LoaderFunctionArgs) {
export async function loader({ request, params }: LoaderFunctionArgs) {
const url = new URL(request.url);
const tab = url.searchParams.get("tab") || null;
const data = await prisma.user.findFirst({
where: {
username: params.username,
@ -72,102 +75,210 @@ export async function loader({ params }: LoaderFunctionArgs) {
pfp: true,
desc: true,
createdAt: true,
posts: {
select: {
id: true,
text: true,
createdAt: true,
likes: {
select: {
id: true,
},
},
reposts: {
select: {
id: true,
userId: true,
postId: true,
user: true,
},
},
author: {
select: {
id: true,
username: true,
name: true,
pfp: true,
},
},
},
orderBy: {
createdAt: "desc",
},
},
likes: {
select: {
id: true,
text: true,
createdAt: true,
likes: {
select: {
id: true,
},
},
reposts: {
select: {
id: true,
userId: true,
postId: true,
user: true,
},
},
author: {
select: {
id: true,
username: true,
name: true,
pfp: true,
},
},
},
},
reposts: {
select: {
id: true,
userId: true,
postId: true,
createdAt: true,
user: true,
post: {
select: {
id: true,
text: true,
createdAt: true,
likes: {
select: {
id: true,
posts:
!tab || tab === "posts"
? {
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: {
where: {
parentReplyId: null,
},
},
},
},
},
reposts: {
select: {
id: true,
userId: true,
postId: true,
user: true,
orderBy: {
createdAt: "desc",
},
}
: false,
likes:
tab === "likes"
? {
select: {
id: true,
userId: true,
postId: true,
createdAt: true,
user: {
select: {
username: true,
name: true,
pfp: 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: {
where: {
parentReplyId: null,
},
},
},
},
},
},
},
author: {
select: {
id: true,
username: true,
name: true,
pfp: true,
orderBy: {
createdAt: "desc",
},
}
: false,
reposts:
tab === "reposts"
? {
select: {
id: true,
userId: true,
postId: true,
createdAt: true,
user: {
select: {
username: true,
name: true,
pfp: 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: {
where: {
parentReplyId: null,
},
},
},
},
},
},
},
},
},
},
},
orderBy: {
createdAt: "desc",
},
}
: false,
followedBy: {
select: {
id: true,
@ -192,7 +303,6 @@ export async function loader({ params }: LoaderFunctionArgs) {
export default function UserProfile() {
const rootData = useRouteLoaderData<RootLoaderTypes>("root");
const data: UserWithRelations = useLoaderData<LoaderFunction>();
console.log(data.reposts);
const actionData = useActionData<ActionFunction>();
const isFollowing =
data.followedBy.filter((follow) => follow.id === rootData?.id).length > 0;
@ -245,61 +355,81 @@ export default function UserProfile() {
</div>
</div>
</Card>
<div className="flex flex-col gap-10 md:flex-row">
<div className="flex flex-col flex-grow gap-5 w-full">
<div className="flex gap-5">
<Link to={`?tab=posts`}>
<SubTitle>Posts</SubTitle>
{data.posts.length > 0 ? (
<Card className="!p-0 !gap-0 divide-y">
{data.posts.map((post) => (
<Post userId={rootData?.id} key={post.id} post={post} />
))}
</Card>
) : (
<Text type="subtitle">No posts yet</Text>
)}
</div>
<div className="flex flex-col flex-grow gap-5 w-full">
</Link>
<Link to={`?tab=likes`}>
<SubTitle>Likes</SubTitle>
{data.likes.length > 0 ? (
<Card className="!p-0 !gap-0 divide-y">
{data.likes.map((post) => (
<Post
userId={rootData?.id}
key={post.id}
post={post}
topTitle={
<>
<strong>{data.username}</strong> liked this
</>
}
/>
))}
</Card>
) : (
<Text type="subtitle">No likes yet</Text>
)}
</div>
<div className="flex flex-col flex-grow gap-5 w-full">
</Link>
<Link to={`?tab=reposts`}>
<SubTitle>Reposts</SubTitle>
{data.reposts.length > 0 ? (
<Card className="!p-0 !gap-0 divide-y">
{data.reposts.map((repost) => (
<Post
userId={rootData?.id}
key={repost.id}
post={repost.post}
topTitle={
<>
<strong>{data.username}</strong> reposted this
</>
}
/>
))}
</Card>
) : (
<Text type="subtitle">No reposts yet</Text>
)}
</div>
</Link>
</div>
<div className="flex flex-col gap-10 lg:flex-row">
{data.posts ? (
<div className="flex flex-col flex-grow gap-5 w-full">
{data.posts.length > 0 ? (
<Card className="!p-0 !gap-0 divide-y">
{data.posts.map((post) => (
<Post userId={rootData?.id} key={post.id} post={post} />
))}
</Card>
) : (
<Text type="subtitle">No posts yet</Text>
)}
</div>
) : (
""
)}
{data.likes ? (
<div className="flex flex-col flex-grow gap-5 w-full">
{data.likes.length > 0 ? (
<Card className="!p-0 !gap-0 divide-y">
{data.likes.map((like) => (
<Post
userId={rootData?.id}
key={like.id}
post={like.post}
topTitle={
<>
<strong>{data.username}</strong> liked this
</>
}
/>
))}
</Card>
) : (
<Text type="subtitle">No likes yet</Text>
)}
</div>
) : (
""
)}
{data.reposts ? (
<div className="flex flex-col flex-grow gap-5 w-full">
{data.reposts.length > 0 ? (
<Card className="!p-0 !gap-0 divide-y">
{data.reposts.map((repost) => (
<Post
userId={rootData?.id}
key={repost.id}
post={repost.post}
topTitle={
<>
<strong>{data.username}</strong> reposted this
</>
}
/>
))}
</Card>
) : (
<Text type="subtitle">No reposts yet</Text>
)}
</div>
) : (
""
)}
</div>
</div>
);

View File

@ -17,11 +17,12 @@ model User {
name String?
desc String?
pfp String
posts Post[] @relation("UserPosts")
likes Post[] @relation("UserLikes")
posts Post[]
likes Like[]
reposts Repost[]
following User[] @relation("UserFollows")
followedBy User[] @relation("UserFollows")
replies Reply[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@ -30,21 +31,54 @@ model User {
model Post {
id Int @id @default(autoincrement())
text String
author User @relation("UserPosts", fields: [userId], references: [id])
author User @relation(fields: [userId], references: [id])
userId Int
likes User[] @relation("UserLikes")
likes Like[]
reposts Repost[]
replies Reply[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Reply {
id Int @id @default(autoincrement())
text String
author User @relation(fields: [userId], references: [id])
userId Int
post Post @relation(fields: [postId], references: [id])
postId Int
parentReply Reply? @relation("ReplyToReply", fields: [parentReplyId], references: [id])
childReplies Reply[] @relation("ReplyToReply")
parentReplyId Int?
likes Like[]
reposts Repost[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Like {
id Int @id @default(autoincrement())
user User @relation(fields: [userId], references: [id])
userId Int
post Post? @relation(fields: [postId], references: [id])
postId Int?
reply Reply? @relation(fields: [replyId], references: [id])
replyId Int?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Repost {
id Int @id @default(autoincrement())
user User @relation(fields: [userId], references: [id])
userId Int
post Post @relation(fields: [postId], references: [id])
postId Int
id Int @id @default(autoincrement())
user User @relation(fields: [userId], references: [id])
userId Int
post Post? @relation(fields: [postId], references: [id])
postId Int?
reply Reply? @relation(fields: [replyId], references: [id])
replyId Int?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt