282 lines
6.7 KiB
TypeScript
282 lines
6.7 KiB
TypeScript
import { prisma, type PostWithRelations } from "~/utils/prisma.server";
|
|
import type {
|
|
ActionFunction,
|
|
ActionFunctionArgs,
|
|
LoaderFunction,
|
|
LoaderFunctionArgs,
|
|
MetaFunction,
|
|
} from "@remix-run/node";
|
|
import { json, redirect } from "@remix-run/node";
|
|
import {
|
|
Form,
|
|
Link,
|
|
useActionData,
|
|
useLoaderData,
|
|
useRouteLoaderData,
|
|
} from "@remix-run/react";
|
|
import { Button } from "~/components/Button";
|
|
import { Card } from "~/components/Card";
|
|
import { FormLabel, TextArea } from "~/components/Form";
|
|
import { Post } from "~/components/Post";
|
|
import { SubTitle, Text, Title } from "~/components/Typography";
|
|
import { like, unLike } from "~/models/like.server";
|
|
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 const meta: MetaFunction<typeof loader> = () => {
|
|
return [{ title: `Home | Twitter Clone` }];
|
|
};
|
|
|
|
export async function action({ request }: ActionFunctionArgs) {
|
|
const session = await getSession(request.headers.get("Cookie"));
|
|
if (!session.has("userId")) {
|
|
return redirect("/");
|
|
}
|
|
|
|
const userId = session.get("userId");
|
|
|
|
const formData = await request
|
|
.formData()
|
|
.then((data) => Object.fromEntries(data));
|
|
|
|
switch (formData.intent) {
|
|
case "newpost":
|
|
const postErrors = await createPost(String(formData.post), userId);
|
|
|
|
if (Object.values(postErrors).some(Boolean)) {
|
|
return postErrors;
|
|
}
|
|
|
|
session.flash("globalMessage", "Post created!");
|
|
|
|
break;
|
|
case "like":
|
|
const likeErrors = await like(
|
|
Number(formData.postId),
|
|
userId,
|
|
Number(formData.replyId)
|
|
);
|
|
|
|
if (Object.values(likeErrors).some(Boolean)) {
|
|
return likeErrors;
|
|
}
|
|
|
|
session.flash("globalMessage", "Post liked!");
|
|
|
|
break;
|
|
case "unlike":
|
|
const unLikeErrors = await unLike(
|
|
Number(formData.postId),
|
|
userId,
|
|
Number(formData.replyId)
|
|
);
|
|
|
|
if (Object.values(unLikeErrors).some(Boolean)) {
|
|
return unLikeErrors;
|
|
}
|
|
|
|
session.flash("globalMessage", "Post un-liked!");
|
|
|
|
break;
|
|
case "repost":
|
|
const repostErrors = await repost(
|
|
Number(formData.postId),
|
|
userId,
|
|
Number(formData.replyId)
|
|
);
|
|
|
|
if (Object.values(repostErrors).some(Boolean)) {
|
|
return repostErrors;
|
|
}
|
|
|
|
session.flash("globalMessage", "Post reposted!");
|
|
|
|
break;
|
|
case "unrepost":
|
|
const unrepostErrors = await unRepost(
|
|
Number(formData.postId),
|
|
userId,
|
|
Number(formData.replyId)
|
|
);
|
|
|
|
if (Object.values(unrepostErrors).some(Boolean)) {
|
|
return unrepostErrors;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
return json(
|
|
{},
|
|
{
|
|
headers: {
|
|
"Set-Cookie": await commitSession(session),
|
|
},
|
|
}
|
|
);
|
|
}
|
|
|
|
export async function loader({ request }: LoaderFunctionArgs) {
|
|
const session = await getSession(request.headers.get("Cookie"));
|
|
if (!session.has("userId")) {
|
|
return redirect("/");
|
|
}
|
|
const userId = session.get("userId");
|
|
const following = await prisma.user.findFirst({
|
|
where: { id: userId },
|
|
select: { following: { select: { id: true } } },
|
|
});
|
|
const followingArr = [...following.following.map((user) => user.id)];
|
|
|
|
const feed = await prisma.post.findMany({
|
|
where: {
|
|
OR: [
|
|
{
|
|
userId: {
|
|
in: followingArr,
|
|
},
|
|
},
|
|
{
|
|
reposts: {
|
|
some: {
|
|
userId: {
|
|
in: followingArr,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
],
|
|
},
|
|
select: {
|
|
id: true,
|
|
text: true,
|
|
createdAt: true,
|
|
author: {
|
|
select: {
|
|
username: true,
|
|
name: true,
|
|
pfp: true,
|
|
},
|
|
},
|
|
reposts: {
|
|
select: {
|
|
userId: true,
|
|
user: {
|
|
select: {
|
|
id: true,
|
|
username: true,
|
|
pfp: true,
|
|
name: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
likes: {
|
|
select: {
|
|
userId: true,
|
|
user: {
|
|
select: {
|
|
id: true,
|
|
username: true,
|
|
pfp: true,
|
|
name: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
_count: {
|
|
select: {
|
|
replies: {
|
|
where: {
|
|
parentReplyId: null,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
orderBy: {
|
|
createdAt: "desc",
|
|
},
|
|
});
|
|
|
|
feed.forEach((post) => {
|
|
const filter = post.reposts.filter((user) =>
|
|
followingArr.includes(user.userId)
|
|
);
|
|
const reposters = filter.map((repost) => repost.user);
|
|
post.reposters = reposters;
|
|
});
|
|
|
|
return feed;
|
|
}
|
|
|
|
export default function Index() {
|
|
const rootData = useRouteLoaderData<RootLoaderTypes>("root");
|
|
const data: PostWithRelations[] = useLoaderData<LoaderFunction>();
|
|
const errors = useActionData<ActionFunction>();
|
|
|
|
return (
|
|
<div className="flex flex-col gap-5">
|
|
<Title>Home</Title>
|
|
<Card className="!gap-0">
|
|
{rootData?.id ? (
|
|
<>
|
|
<SubTitle>New post</SubTitle>
|
|
<Form method="POST">
|
|
<input type="hidden" name="intent" value={"newpost"} />
|
|
<div className="flex flex-col gap-3">
|
|
<FormLabel>
|
|
<Text>Post body</Text>
|
|
<TextArea name="post" />
|
|
<Text type="error">{errors?.body ? errors.body : ""}</Text>
|
|
</FormLabel>
|
|
<Button type="submit">Post</Button>
|
|
</div>
|
|
</Form>
|
|
</>
|
|
) : (
|
|
""
|
|
)}
|
|
</Card>
|
|
<SubTitle>Feed</SubTitle>
|
|
{data.length > 0 ? (
|
|
<Card className="!p-0 !gap-0 divide-y">
|
|
{data.map((post) => (
|
|
<Post userId={rootData?.id} key={post.id} post={post} />
|
|
))}
|
|
</Card>
|
|
) : (
|
|
<Text className="flex gap-1" type="error">
|
|
Feed is empty,{" "}
|
|
<Link to={`/users`}>
|
|
<Text type="link">follow someone</Text>
|
|
</Link>{" "}
|
|
so their recent posts will show up here!
|
|
</Text>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|