Add reposts, like receipts, Catppuccin styling, refactors, fixes and limit the data thats sent to the client
This commit is contained in:
parent
b4e0b8b4d2
commit
cc2e299a63
|
@ -10,7 +10,7 @@ export function Button({
|
|||
}) {
|
||||
return (
|
||||
<button
|
||||
className="px-6 py-2 rounded-lg border w-fit hover:bg-gray-100"
|
||||
className="px-6 py-2 rounded-lg border w-fit hover:bg-ctp-crust"
|
||||
type={type}
|
||||
>
|
||||
<Text>{children}</Text>
|
||||
|
|
|
@ -9,7 +9,7 @@ export function Card({
|
|||
}) {
|
||||
return (
|
||||
<div
|
||||
className={`flex z-10 flex-col bg-gray-50/9 gap-5 p-8 border w-full rounded-lg ${className}`}
|
||||
className={`flex z-10 flex-col bg-ctp-mantle gap-5 p-8 border w-full rounded-lg ${className}`}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
|
|
|
@ -9,7 +9,7 @@ export function FormInput({ type, name }: { type: string; name: string }) {
|
|||
<input
|
||||
className={`file:bg-transparent file:border-0 ${
|
||||
type === "file" ? "p-4 cursor-pointer" : "p-2"
|
||||
} file:bg-sky-100 file:hover:bg-sky-100/75 file:text-gray-600/75 file:p-2 file:rounded-lg text-gray-600 rounded-lg border`}
|
||||
} file:bg-sky-100 file:hover:bg-sky-100/75 file:text-gray-600/75 file:p-2 file:rounded-lg bg-ctp-crust/50 text-ctp-subtext0 rounded-lg border`}
|
||||
type={type}
|
||||
name={name}
|
||||
accept={type === "file" ? "image/*" : undefined}
|
||||
|
@ -27,7 +27,7 @@ export function TextArea({
|
|||
}) {
|
||||
return (
|
||||
<textarea
|
||||
className="p-4 w-full text-gray-600 rounded-lg border"
|
||||
className="p-4 w-full bg-ctp-crust/50 text-ctp-subtext0 rounded-lg border"
|
||||
placeholder={placeholder}
|
||||
name={name}
|
||||
></textarea>
|
||||
|
|
|
@ -3,7 +3,7 @@ import { Button } from "./Button";
|
|||
import { Card } from "./Card";
|
||||
import { FormLabel, TextArea } from "./Form";
|
||||
import { SubTitle, Text } from "./Typography";
|
||||
import type { Post } from "@prisma/client";
|
||||
import type { User } from "@prisma/client";
|
||||
import type { PostWithRelations } from "~/utils/prisma.server";
|
||||
|
||||
export function Poster({
|
||||
|
@ -49,18 +49,31 @@ export function Poster({
|
|||
export function Post({
|
||||
userId,
|
||||
post,
|
||||
liker,
|
||||
following,
|
||||
}: {
|
||||
post: PostWithRelations;
|
||||
userId?: number;
|
||||
liker?: User;
|
||||
following?: { id: number }[];
|
||||
}) {
|
||||
const fetcher = useFetcher();
|
||||
const liked = post.likes.filter((like) => like.id === userId).length > 0;
|
||||
const reposted =
|
||||
post.reposts.filter((repost) => repost.id === userId).length > 0;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={post.id}
|
||||
className="flex relative flex-col gap-4 p-6 transition-all hover:bg-gray-100/50"
|
||||
className="flex relative flex-col gap-4 p-6 transition-all bg-ctp-mantle hover:bg-ctp-base first:rounded-t-lg last:rounded-b-lg "
|
||||
>
|
||||
{liker ? (
|
||||
<Text type="subtitle">
|
||||
<strong>{liker.username}</strong> liked
|
||||
</Text>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
<div className="flex items-center">
|
||||
<Poster
|
||||
pfp={post.author.pfp}
|
||||
|
@ -89,17 +102,23 @@ export function Post({
|
|||
</button>
|
||||
</fetcher.Form>
|
||||
<fetcher.Form action={`/home`} method="POST">
|
||||
<input type="hidden" name="intent" value={"repost"} />
|
||||
<input
|
||||
type="hidden"
|
||||
name="intent"
|
||||
value={reposted ? "unrepost" : "repost"}
|
||||
/>
|
||||
<input type="hidden" name="postId" value={post.id} />
|
||||
<button type="submit">
|
||||
<Text type="link">Repost</Text>
|
||||
<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 bg-white z-100">
|
||||
<Card className="absolute max-w-xs z-100">
|
||||
<SubTitle>Reply to {post.author.username}</SubTitle>
|
||||
<fetcher.Form
|
||||
action={`/home`}
|
||||
|
@ -111,7 +130,6 @@ export function Post({
|
|||
<FormLabel>
|
||||
<Text>Reply body</Text>
|
||||
<TextArea name="reply" />
|
||||
<Text type="error">-</Text>
|
||||
</FormLabel>
|
||||
<Button type="submit">Post</Button>
|
||||
</fetcher.Form>
|
||||
|
|
|
@ -5,7 +5,7 @@ export function Toast({ children }: { children: ReactNode }) {
|
|||
return (
|
||||
<>
|
||||
<input className="peer/toast" type="checkbox" id="toast-toggle" hidden />
|
||||
<div className="peer-checked/toast:hidden z-10 bg-white w-80 p-4 fixed bottom-10 right-10 border rounded-lg shadow-lg">
|
||||
<div className="peer-checked/toast:hidden z-10 bg-ctp-base w-80 p-4 fixed bottom-10 right-10 border rounded-lg shadow-lg">
|
||||
<div className="relative">
|
||||
<label
|
||||
className="absolute right-0 cursor-pointer text-gray-300 hover:text-gray-400"
|
||||
|
|
|
@ -13,14 +13,14 @@ export function Text({
|
|||
<p
|
||||
className={`${
|
||||
type === "link"
|
||||
? "text-sky-400 hover:text-sky-600"
|
||||
? "text-ctp-sky hover:text-ctp-blue"
|
||||
: type === "error"
|
||||
? "text-red-600/75 text-sm"
|
||||
? "text-ctp-red text-sm"
|
||||
: type === "subtitle"
|
||||
? "text-gray-600 text-sm"
|
||||
? "text-ctp-subtext1 text-sm"
|
||||
: type === "success"
|
||||
? "text-green-600/75"
|
||||
: "text-gray-600"
|
||||
? "text-ctp-green/25"
|
||||
: "text-ctp-text"
|
||||
} ${className}`}
|
||||
>
|
||||
{children}
|
||||
|
@ -35,11 +35,7 @@ export function Title({
|
|||
children: ReactNode;
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<h1 className={`text-2xl font-bold text-gray-800 ${className}`}>
|
||||
{children}
|
||||
</h1>
|
||||
);
|
||||
return <h1 className={`text-2xl font-bold ${className}`}>{children}</h1>;
|
||||
}
|
||||
|
||||
export function SubTitle({
|
||||
|
@ -54,7 +50,7 @@ export function SubTitle({
|
|||
return (
|
||||
<h2
|
||||
className={`text-xl font-semibold ${
|
||||
type ? "text-sky-600 hover:text-sky-800" : "text-gray-700"
|
||||
type ? "text-ctp-sky hover:text-ctp-blue" : "text-ctp-text/75"
|
||||
} ${className}`}
|
||||
>
|
||||
{children}
|
||||
|
|
|
@ -3,13 +3,13 @@ import { getPostById } from "./post.server";
|
|||
|
||||
interface PostErrors {
|
||||
postId: string | undefined;
|
||||
all: string | undefined;
|
||||
like: string | undefined;
|
||||
}
|
||||
|
||||
export async function likePost(postId: number, id: number) {
|
||||
const errors: PostErrors = {
|
||||
postId: undefined,
|
||||
all: undefined,
|
||||
like: undefined,
|
||||
};
|
||||
|
||||
const post = await getPostById(postId);
|
||||
|
@ -36,7 +36,7 @@ export async function likePost(postId: number, id: number) {
|
|||
});
|
||||
|
||||
if (!like) {
|
||||
errors.all = "Something went wrong liking the post";
|
||||
errors.like = "Something went wrong liking the post";
|
||||
return errors;
|
||||
}
|
||||
|
||||
|
@ -46,7 +46,7 @@ export async function likePost(postId: number, id: number) {
|
|||
export async function unLikePost(postId: number, id: number) {
|
||||
const errors: PostErrors = {
|
||||
postId: undefined,
|
||||
all: undefined,
|
||||
like: undefined,
|
||||
};
|
||||
|
||||
const post = await getPostById(postId);
|
||||
|
@ -71,7 +71,7 @@ export async function unLikePost(postId: number, id: number) {
|
|||
});
|
||||
|
||||
if (!like) {
|
||||
errors.all = "Something went wrong unliking the post";
|
||||
errors.like = "Something went wrong unliking the post";
|
||||
return errors;
|
||||
}
|
||||
|
||||
|
|
|
@ -29,18 +29,41 @@ export async function getFeed(id: number) {
|
|||
|
||||
const posts = await prisma.post.findMany({
|
||||
where: {
|
||||
userId: {
|
||||
in: [...following.following.map((user) => user.id)],
|
||||
},
|
||||
OR: [
|
||||
{
|
||||
userId: {
|
||||
in: [...following.following.map((user) => user.id)],
|
||||
},
|
||||
},
|
||||
{
|
||||
reposts: {
|
||||
some: {
|
||||
id: {
|
||||
in: [...following.following.map((user) => user.id), id],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
include: {
|
||||
author: {
|
||||
include: {
|
||||
likes: true,
|
||||
posts: true,
|
||||
select: {
|
||||
username: true,
|
||||
name: true,
|
||||
pfp: true,
|
||||
},
|
||||
},
|
||||
reposts: {
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
likes: {
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
likes: true,
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -66,10 +89,8 @@ function validatePostBody(body: string) {
|
|||
export async function createPost(body: Post["text"], id: number) {
|
||||
const errors: {
|
||||
body: string | undefined;
|
||||
all: string | undefined;
|
||||
} = {
|
||||
body: validatePostBody(body),
|
||||
all: undefined,
|
||||
};
|
||||
|
||||
if (Object.values(errors).some(Boolean)) {
|
||||
|
@ -84,7 +105,7 @@ export async function createPost(body: Post["text"], id: number) {
|
|||
});
|
||||
|
||||
if (!post) {
|
||||
errors.all = "Something went wrong creating the post";
|
||||
errors.body = "Something went wrong creating the post";
|
||||
return errors;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
import { prisma } from "~/utils/prisma.server";
|
||||
import { getPostById } from "./post.server";
|
||||
|
||||
export async function repost(postId: number, id: number) {
|
||||
const errors = {};
|
||||
|
||||
const post = await getPostById(postId);
|
||||
|
||||
if (!post) {
|
||||
errors.repost = "Post doesn't exist";
|
||||
}
|
||||
|
||||
if (Object.values(errors).some(Boolean)) {
|
||||
return errors;
|
||||
}
|
||||
|
||||
const repost = await prisma.user.update({
|
||||
where: {
|
||||
id: id,
|
||||
},
|
||||
data: {
|
||||
reposts: {
|
||||
connect: { id: postId },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!repost) {
|
||||
errors.repost = "Something went wrong";
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
export async function unRepost(postId: number, id: number) {
|
||||
const errors = {};
|
||||
|
||||
const post = await getPostById(postId);
|
||||
|
||||
if (!post) {
|
||||
errors.repost = "Post doesn't exist";
|
||||
}
|
||||
|
||||
if (Object.values(errors).some(Boolean)) {
|
||||
return errors;
|
||||
}
|
||||
|
||||
const repost = await prisma.user.update({
|
||||
where: {
|
||||
id: id,
|
||||
},
|
||||
data: {
|
||||
reposts: {
|
||||
disconnect: { id: postId },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!repost) {
|
||||
errors.repost = "Something went wrong";
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
|
@ -25,16 +25,73 @@ export async function getUserByName(
|
|||
id: true,
|
||||
},
|
||||
},
|
||||
following: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
posts: {
|
||||
include: {
|
||||
author: true,
|
||||
likes: true,
|
||||
author: {
|
||||
select: {
|
||||
username: true,
|
||||
name: true,
|
||||
pfp: true,
|
||||
},
|
||||
},
|
||||
reposts: {
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
likes: {
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
likes: {
|
||||
include: {
|
||||
author: true,
|
||||
likes: true,
|
||||
author: {
|
||||
select: {
|
||||
username: true,
|
||||
name: true,
|
||||
pfp: true,
|
||||
},
|
||||
},
|
||||
reposts: {
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
likes: {
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
reposts: {
|
||||
include: {
|
||||
author: {
|
||||
select: {
|
||||
username: true,
|
||||
name: true,
|
||||
pfp: true,
|
||||
},
|
||||
},
|
||||
reposts: {
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
likes: {
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
_count: {
|
||||
|
@ -84,13 +141,12 @@ export interface UserErrors {
|
|||
opassword: string | undefined;
|
||||
npassword: string | undefined;
|
||||
pfp: string | undefined;
|
||||
all: string | undefined;
|
||||
}
|
||||
|
||||
export async function followUser(followId: number, id: number) {
|
||||
const errors: { followId: string | undefined; all: string | undefined } = {
|
||||
const errors: { followId: string | undefined; follow: string | undefined } = {
|
||||
followId: undefined,
|
||||
all: undefined,
|
||||
follow: undefined,
|
||||
};
|
||||
|
||||
if (!(await getUserById(followId))) {
|
||||
|
@ -113,17 +169,18 @@ export async function followUser(followId: number, id: number) {
|
|||
});
|
||||
|
||||
if (!follow) {
|
||||
errors.all = "Something went wrong";
|
||||
errors.follow = "Something went wrong";
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
export async function unFollowUser(followId: number, id: number) {
|
||||
const errors: { followId: string | undefined; all: string | undefined } = {
|
||||
followId: undefined,
|
||||
all: undefined,
|
||||
};
|
||||
const errors: { followId: string | undefined; unfollow: string | undefined } =
|
||||
{
|
||||
followId: undefined,
|
||||
unfollow: undefined,
|
||||
};
|
||||
|
||||
if (!(await getUserById(followId))) {
|
||||
errors.followId = "User does not exist";
|
||||
|
@ -145,7 +202,7 @@ export async function unFollowUser(followId: number, id: number) {
|
|||
});
|
||||
|
||||
if (!unFollow) {
|
||||
errors.all = "Something went wrong";
|
||||
errors.unfollow = "Something went wrong";
|
||||
}
|
||||
|
||||
return errors;
|
||||
|
@ -173,7 +230,6 @@ export async function editUser(
|
|||
opassword: undefined,
|
||||
npassword: undefined,
|
||||
pfp: undefined,
|
||||
all: undefined,
|
||||
};
|
||||
|
||||
switch (data.intent) {
|
||||
|
@ -194,7 +250,7 @@ export async function editUser(
|
|||
});
|
||||
|
||||
if (!username) {
|
||||
errors.all = "Something went wrong";
|
||||
errors.username = "Something went wrong";
|
||||
}
|
||||
|
||||
return { username: username.username, errors: errors };
|
||||
|
@ -221,7 +277,7 @@ export async function editUser(
|
|||
});
|
||||
|
||||
if (!name) {
|
||||
errors.all = "Something went wrong";
|
||||
errors.name = "Something went wrong";
|
||||
}
|
||||
|
||||
return { errors: errors };
|
||||
|
@ -244,7 +300,7 @@ export async function editUser(
|
|||
});
|
||||
|
||||
if (!desc) {
|
||||
errors.all = "Something went wrong";
|
||||
errors.desc = "Something went wrong";
|
||||
}
|
||||
|
||||
return { errors: errors };
|
||||
|
@ -281,7 +337,7 @@ export async function editUser(
|
|||
});
|
||||
|
||||
if (!pw) {
|
||||
errors.all = "Something went wrong";
|
||||
errors.opassword = "Something went wrong";
|
||||
}
|
||||
|
||||
return { errors: errors };
|
||||
|
@ -320,7 +376,6 @@ export async function editUser(
|
|||
|
||||
return { errors: errors };
|
||||
default:
|
||||
errors.all = "Doesn't exist";
|
||||
return { errors: errors };
|
||||
}
|
||||
}
|
||||
|
@ -380,12 +435,10 @@ export async function createUser(
|
|||
username: string | undefined;
|
||||
password: string | undefined;
|
||||
rpassword: string | undefined;
|
||||
all: string | undefined;
|
||||
} = {
|
||||
username: await validateUsername(username),
|
||||
password: await validatePassword(password),
|
||||
rpassword: await validateRepeatPassword(password, repeatPassword),
|
||||
all: undefined,
|
||||
};
|
||||
|
||||
if (Object.values(errors).some(Boolean)) {
|
||||
|
@ -401,7 +454,7 @@ export async function createUser(
|
|||
});
|
||||
|
||||
if (!user) {
|
||||
errors.all = "Failed to create user";
|
||||
errors.username = "Failed to create user";
|
||||
return { errors: errors };
|
||||
}
|
||||
|
||||
|
|
10
app/root.tsx
10
app/root.tsx
|
@ -86,14 +86,14 @@ export default function App() {
|
|||
const data = useLoaderData<LoaderFunction>();
|
||||
|
||||
return (
|
||||
<html lang="en">
|
||||
<html className="ctp-frappe" lang="en">
|
||||
<head>
|
||||
<meta charSet="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<Meta />
|
||||
<Links />
|
||||
</head>
|
||||
<body className="min-h-screen subpixel-antialiased">
|
||||
<body className="min-h-screen bg-ctp-crust text-ctp-text">
|
||||
<div className="container flex flex-col gap-20 m-8 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">
|
||||
|
@ -122,7 +122,7 @@ export default function App() {
|
|||
</div>
|
||||
<div className="flex mx-auto justify-center flex-grow w-80">
|
||||
<div>
|
||||
<div className="hover:bg-sky-100/50 rounded-lg transition-all p-4">
|
||||
<div className="hover:bg-ctp-blue/5 rounded-lg transition-all p-4">
|
||||
<Link className="flex flex-col gap-5 items-center" to={"/"}>
|
||||
<img src="/logo.png" alt="logo" width={50} />
|
||||
<SubTitle>Twitter clone</SubTitle>
|
||||
|
@ -144,11 +144,11 @@ function NavItem({ to, text }: { to: string; text: string }) {
|
|||
return (
|
||||
<NavLink
|
||||
className={({ isActive, isPending }) =>
|
||||
isPending ? "pending" : isActive ? "bg-gray-100/40" : ""
|
||||
isPending ? "pending" : isActive ? "bg-ctp-surface0 rounded-lg" : ""
|
||||
}
|
||||
to={to}
|
||||
>
|
||||
<div className="px-4 w-full py-2 text-center rounded-lg border hover:bg-gray-100">
|
||||
<div className="px-4 w-full py-2 text-center rounded-lg border bg-ctp-surface0/40 hover:bg-ctp-surface2/25">
|
||||
<SubTitle>{text}</SubTitle>
|
||||
</div>
|
||||
</NavLink>
|
||||
|
|
|
@ -14,7 +14,7 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
|||
|
||||
export default function Index() {
|
||||
return (
|
||||
<div className="bg-sky-100 rounded-lg">
|
||||
<div className="bg-ctp-sky/10 rounded-lg">
|
||||
<div className="flex flex-col gap-1 h-96 justify-center items-center">
|
||||
<img src="/logo.png" alt="twitter" width={64} />
|
||||
<h1 className="text-7xl font-black">
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
import type { PostWithRelations } from "~/utils/prisma.server";
|
||||
import type {
|
||||
PostWithRelations,
|
||||
UserWithRelations,
|
||||
} from "~/utils/prisma.server";
|
||||
import type {
|
||||
ActionFunction,
|
||||
ActionFunctionArgs,
|
||||
|
@ -18,9 +21,11 @@ import { FormLabel, TextArea } from "~/components/Form";
|
|||
import { Post } from "~/components/Post";
|
||||
import { SubTitle, Text, Title } from "~/components/Typography";
|
||||
import { likePost, unLikePost } from "~/models/like.server";
|
||||
import { createPost, getAllPosts, getFeed } from "~/models/post.server";
|
||||
import { createPost, getFeed } from "~/models/post.server";
|
||||
import { commitSession, getSession } from "~/utils/session.server";
|
||||
import type { RootLoaderTypes } from "~/root";
|
||||
import { repost, unRepost } from "~/models/repost.server";
|
||||
import { getUserByName } from "~/models/user.server";
|
||||
|
||||
export async function action({ request }: ActionFunctionArgs) {
|
||||
const session = await getSession(request.headers.get("Cookie"));
|
||||
|
@ -62,7 +67,27 @@ export async function action({ request }: ActionFunctionArgs) {
|
|||
return unLikeErrors;
|
||||
}
|
||||
|
||||
session.flash("globalMessage", "Post unliked!");
|
||||
session.flash("globalMessage", "Post un-liked!");
|
||||
|
||||
break;
|
||||
case "repost":
|
||||
const repostErrors = await repost(Number(formData.postId), userId);
|
||||
|
||||
if (Object.values(repostErrors).some(Boolean)) {
|
||||
return repostErrors;
|
||||
}
|
||||
|
||||
session.flash("globalMessage", "Post reposted!");
|
||||
|
||||
break;
|
||||
case "unrepost":
|
||||
const unrepostErrors = await unRepost(Number(formData.postId), userId);
|
||||
|
||||
if (Object.values(unrepostErrors).some(Boolean)) {
|
||||
return unrepostErrors;
|
||||
}
|
||||
|
||||
session.flash("globalMessage", "Post un-reposted!");
|
||||
|
||||
break;
|
||||
default:
|
||||
|
@ -85,12 +110,17 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
|||
return redirect("/");
|
||||
}
|
||||
|
||||
return await getFeed(session.get("userId"));
|
||||
return {
|
||||
feed: await getFeed(session.get("userId")),
|
||||
self: await getUserByName(session.get("username")),
|
||||
};
|
||||
}
|
||||
|
||||
export default function Index() {
|
||||
const rootData = useRouteLoaderData<RootLoaderTypes>("root");
|
||||
const data: PostWithRelations[] = useLoaderData<LoaderFunction>();
|
||||
const data: { feed: PostWithRelations[]; self: UserWithRelations } =
|
||||
useLoaderData<LoaderFunction>();
|
||||
console.log(data.self.following);
|
||||
const errors = useActionData<ActionFunction>();
|
||||
|
||||
return (
|
||||
|
@ -99,7 +129,7 @@ export default function Index() {
|
|||
<Card>
|
||||
{rootData?.id ? (
|
||||
<>
|
||||
<Title>New post</Title>
|
||||
<SubTitle>New post</SubTitle>
|
||||
<Form className="flex flex-col gap-3" method="POST">
|
||||
<input type="hidden" name="intent" value={"newpost"} />
|
||||
<FormLabel>
|
||||
|
@ -118,9 +148,9 @@ export default function Index() {
|
|||
)}
|
||||
</Card>
|
||||
<SubTitle>Feed</SubTitle>
|
||||
{data.length > 0 ? (
|
||||
{data.feed.length > 0 ? (
|
||||
<Card className="!p-0 !gap-0 divide-y">
|
||||
{data.map((post: PostWithRelations) => (
|
||||
{data.feed.map((post) => (
|
||||
<Post userId={rootData?.id} key={post.id} post={post} />
|
||||
))}
|
||||
</Card>
|
||||
|
|
|
@ -66,7 +66,7 @@ export default function Login() {
|
|||
<Text>Password</Text> <FormInput type="password" name="password" />{" "}
|
||||
<Text type="error">{error ? error : ""}</Text>
|
||||
</FormLabel>
|
||||
<Button type="submit" />
|
||||
<Button type="submit">Login</Button>
|
||||
</Form>
|
||||
</Card>
|
||||
);
|
||||
|
|
|
@ -12,7 +12,7 @@ import { Form, useActionData } from "@remix-run/react";
|
|||
import { Button } from "~/components/Button";
|
||||
import { Card } from "~/components/Card";
|
||||
import { FormInput, FormLabel } from "~/components/Form";
|
||||
import { SubTitle, Title, Text } from "~/components/Typography";
|
||||
import { Title, Text } from "~/components/Typography";
|
||||
import { editUser } from "~/models/user.server";
|
||||
import { profilePictureUploadHandler } from "~/utils/file.server";
|
||||
import { commitSession, getSession } from "~/utils/session.server";
|
||||
|
@ -82,7 +82,7 @@ export default function Settings() {
|
|||
<div className="flex flex-col gap-5">
|
||||
<details className="space-y-4">
|
||||
<summary className="text-gray-600 cursor-pointer select-none">
|
||||
Change username
|
||||
<span className="text-ctp-text/75">Change username</span>
|
||||
</summary>
|
||||
<Card>
|
||||
<Form method="POST" className="relative flex flex-col gap-3">
|
||||
|
@ -95,9 +95,7 @@ export default function Settings() {
|
|||
</FormLabel>
|
||||
<Button type="submit">Submit</Button>
|
||||
{data?.success?.username ? (
|
||||
<Text className="!absolute right-0" type="success">
|
||||
{data.success.username}
|
||||
</Text>
|
||||
<Text type="success">{data.success.username}</Text>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
|
@ -106,7 +104,7 @@ export default function Settings() {
|
|||
</details>
|
||||
<details className="space-y-4">
|
||||
<summary className="text-gray-600 cursor-pointer select-none">
|
||||
Change name
|
||||
<span className="text-ctp-text/75">Change name</span>
|
||||
</summary>
|
||||
<Card>
|
||||
<Form method="POST" className="relative flex flex-col gap-3">
|
||||
|
@ -119,9 +117,7 @@ export default function Settings() {
|
|||
</FormLabel>
|
||||
<Button type="submit">Submit</Button>
|
||||
{data?.success?.name ? (
|
||||
<Text className="!absolute right-0" type="success">
|
||||
{data.success.name}
|
||||
</Text>
|
||||
<Text type="success">{data.success.name}</Text>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
|
@ -130,7 +126,7 @@ export default function Settings() {
|
|||
</details>
|
||||
<details className="space-y-4">
|
||||
<summary className="text-gray-600 cursor-pointer select-none">
|
||||
Change description
|
||||
<span className="text-ctp-text/75">Change description</span>
|
||||
</summary>
|
||||
<Card>
|
||||
<Form method="POST" className="relative flex flex-col gap-3">
|
||||
|
@ -143,9 +139,7 @@ export default function Settings() {
|
|||
</FormLabel>
|
||||
<Button type="submit">Submit</Button>
|
||||
{data?.success?.desc ? (
|
||||
<Text className="!absolute right-0" type="success">
|
||||
{data.success.desc}
|
||||
</Text>
|
||||
<Text type="success">{data.success.desc}</Text>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
|
@ -154,7 +148,7 @@ export default function Settings() {
|
|||
</details>
|
||||
<details className="space-y-4">
|
||||
<summary className="text-gray-600 cursor-pointer select-none">
|
||||
Change password
|
||||
<span className="text-ctp-text/75">Change password</span>
|
||||
</summary>
|
||||
<Card>
|
||||
<Form method="POST" className="relative flex flex-col gap-3">
|
||||
|
@ -175,9 +169,7 @@ export default function Settings() {
|
|||
</FormLabel>
|
||||
<Button type="submit">Submit</Button>
|
||||
{data?.success?.password ? (
|
||||
<Text className="!absolute right-0" type="success">
|
||||
{data.success.password}
|
||||
</Text>
|
||||
<Text type="success">{data.success.password}</Text>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
|
@ -186,7 +178,7 @@ export default function Settings() {
|
|||
</details>
|
||||
<details className="space-y-4">
|
||||
<summary className="text-gray-600 cursor-pointer select-none">
|
||||
Change profile picture
|
||||
<span className="text-ctp-text/75">Change profile picture</span>
|
||||
</summary>
|
||||
<Card>
|
||||
<Form
|
||||
|
@ -204,9 +196,7 @@ export default function Settings() {
|
|||
</FormLabel>
|
||||
<Button type="submit">Submit</Button>
|
||||
{data?.success?.pfp ? (
|
||||
<Text className="!absolute right-0" type="success">
|
||||
{data.success.pfp}
|
||||
</Text>
|
||||
<Text type="success">{data.success.pfp}</Text>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
|
|
|
@ -143,7 +143,12 @@ export default function UserProfile() {
|
|||
{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} />
|
||||
<Post
|
||||
userId={rootData?.id}
|
||||
key={post.id}
|
||||
post={post}
|
||||
liker={data}
|
||||
/>
|
||||
))}
|
||||
</Card>
|
||||
) : (
|
||||
|
|
|
@ -1,3 +1,10 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@layer base {
|
||||
*,
|
||||
::before,
|
||||
::after {
|
||||
@apply dark:border-ctp-overlay0/75;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ import { PrismaClient } from "@prisma/client";
|
|||
|
||||
let prisma: PrismaClient;
|
||||
export type PostWithRelations = Prisma.PostGetPayload<{
|
||||
include: { likes: true; author: true };
|
||||
include: { likes: true; author: true; reposts: true };
|
||||
}>;
|
||||
export type UserWithRelations = Prisma.UserGetPayload<{
|
||||
include: {
|
||||
|
@ -12,12 +12,22 @@ export type UserWithRelations = Prisma.UserGetPayload<{
|
|||
id: true;
|
||||
};
|
||||
};
|
||||
following: {
|
||||
select: {
|
||||
id: true;
|
||||
};
|
||||
};
|
||||
posts: {
|
||||
include: {
|
||||
author: true;
|
||||
likes: true;
|
||||
};
|
||||
};
|
||||
reposts: {
|
||||
include: {
|
||||
author: true;
|
||||
};
|
||||
};
|
||||
likes: {
|
||||
include: {
|
||||
author: true;
|
||||
|
|
|
@ -21,11 +21,12 @@
|
|||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@catppuccin/tailwindcss": "^0.1.6",
|
||||
"@remix-run/dev": "^2.2.0",
|
||||
"@remix-run/eslint-config": "^2.2.0",
|
||||
"@types/bcryptjs": "^2.4.5",
|
||||
"@types/react": "^18.2.20",
|
||||
"@types/react-dom": "^18.2.7",
|
||||
"@types/bcryptjs": "^2.4.5",
|
||||
"eslint": "^8.38.0",
|
||||
"prisma": "^5.5.2",
|
||||
"tailwindcss": "^3.3.5",
|
||||
|
|
|
@ -34,6 +34,9 @@ dependencies:
|
|||
version: 18.2.0(react@18.2.0)
|
||||
|
||||
devDependencies:
|
||||
'@catppuccin/tailwindcss':
|
||||
specifier: ^0.1.6
|
||||
version: 0.1.6(tailwindcss@3.3.5)
|
||||
'@remix-run/dev':
|
||||
specifier: ^2.2.0
|
||||
version: 2.2.0(@remix-run/serve@2.2.0)(typescript@5.2.2)
|
||||
|
@ -486,6 +489,14 @@ packages:
|
|||
to-fast-properties: 2.0.0
|
||||
dev: true
|
||||
|
||||
/@catppuccin/tailwindcss@0.1.6(tailwindcss@3.3.5):
|
||||
resolution: {integrity: sha512-V+Y0AwZ5SSyvOVAcDl7Ng30xy+m82OKnEJ+9+kcZZ7lRyXuZrAb2GScdq9XR3v+ggt8qiZ/G4TvaC9cJ88AAXA==}
|
||||
peerDependencies:
|
||||
tailwindcss: '>=3.0.0'
|
||||
dependencies:
|
||||
tailwindcss: 3.3.5
|
||||
dev: true
|
||||
|
||||
/@emotion/hash@0.9.1:
|
||||
resolution: {integrity: sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==}
|
||||
dev: true
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
-- CreateTable
|
||||
CREATE TABLE "_UserReposts" (
|
||||
"A" INTEGER NOT NULL,
|
||||
"B" INTEGER NOT NULL,
|
||||
CONSTRAINT "_UserReposts_A_fkey" FOREIGN KEY ("A") REFERENCES "Post" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
CONSTRAINT "_UserReposts_B_fkey" FOREIGN KEY ("B") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "_UserReposts_AB_unique" ON "_UserReposts"("A", "B");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "_UserReposts_B_index" ON "_UserReposts"("B");
|
|
@ -19,6 +19,7 @@ model User {
|
|||
pfp String
|
||||
posts Post[] @relation("UserPosts")
|
||||
likes Post[] @relation("UserLikes")
|
||||
reposts Post[] @relation("UserReposts")
|
||||
following User[] @relation("UserFollows")
|
||||
followedBy User[] @relation("UserFollows")
|
||||
|
||||
|
@ -27,11 +28,12 @@ model User {
|
|||
}
|
||||
|
||||
model Post {
|
||||
id Int @id @default(autoincrement())
|
||||
text String
|
||||
author User @relation("UserPosts", fields: [userId], references: [id])
|
||||
userId Int
|
||||
likes User[] @relation("UserLikes")
|
||||
id Int @id @default(autoincrement())
|
||||
text String
|
||||
author User @relation("UserPosts", fields: [userId], references: [id])
|
||||
userId Int
|
||||
likes User[] @relation("UserLikes")
|
||||
reposts User[] @relation("UserReposts")
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
|
|
@ -5,5 +5,9 @@ export default {
|
|||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
plugins: [
|
||||
require("@catppuccin/tailwindcss")({
|
||||
prefix: "ctp",
|
||||
}),
|
||||
],
|
||||
} satisfies Config;
|
||||
|
|
Loading…
Reference in New Issue