Add following and unfollowing, profile descriptions, feed, various refactors, layout fixes and minor fixes
This commit is contained in:
parent
073d4aeeaf
commit
5a9897ef38
|
@ -6,7 +6,7 @@ export function Button({
|
|||
children,
|
||||
}: {
|
||||
type: "button" | "submit" | "reset";
|
||||
children: ReactNode;
|
||||
children?: ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<button
|
||||
|
|
|
@ -9,7 +9,7 @@ export function Card({
|
|||
}) {
|
||||
return (
|
||||
<div
|
||||
className={`flex z-10 flex-col bg-gray-50/9 gap-5 p-6 border shadow-lg w-full rounded-lg ${className}`}
|
||||
className={`flex z-10 flex-col bg-gray-50/9 gap-5 p-8 border w-full rounded-lg ${className}`}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
|
|
|
@ -18,7 +18,7 @@ export function Poster({
|
|||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<div className={`flex items-center gap-3 ${className}`}>
|
||||
<div className={`flex break-all items-center gap-3 ${className}`}>
|
||||
<img
|
||||
className="rounded-full border w-12"
|
||||
src={
|
||||
|
@ -26,7 +26,7 @@ export function Poster({
|
|||
}
|
||||
alt="pfp"
|
||||
/>
|
||||
<div className="break-all">
|
||||
<div>
|
||||
{name ? (
|
||||
<>
|
||||
<SubTitle>{name}</SubTitle>
|
||||
|
|
|
@ -18,6 +18,8 @@ export function Text({
|
|||
? "text-red-600/75 text-sm"
|
||||
: type === "subtitle"
|
||||
? "text-gray-600 text-sm"
|
||||
: type === "success"
|
||||
? "text-green-600/75"
|
||||
: "text-gray-600"
|
||||
} ${className}`}
|
||||
>
|
||||
|
@ -43,12 +45,18 @@ export function Title({
|
|||
export function SubTitle({
|
||||
children,
|
||||
className = "",
|
||||
type,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
type?: "link";
|
||||
}) {
|
||||
return (
|
||||
<h2 className={`text-xl font-semibold text-gray-700 ${className}`}>
|
||||
<h2
|
||||
className={`text-xl font-semibold ${
|
||||
type ? "text-sky-600 hover:text-sky-800" : "text-gray-700"
|
||||
} ${className}`}
|
||||
>
|
||||
{children}
|
||||
</h2>
|
||||
);
|
||||
|
|
|
@ -21,6 +21,32 @@ export async function getAllPosts() {
|
|||
return posts;
|
||||
}
|
||||
|
||||
export async function getFeed(id: number) {
|
||||
const following = await prisma.user.findFirst({
|
||||
where: { id: id },
|
||||
select: { following: { select: { id: true } } },
|
||||
});
|
||||
|
||||
const posts = await prisma.post.findMany({
|
||||
where: {
|
||||
userId: {
|
||||
in: [...following.following.map((user) => user.id)],
|
||||
},
|
||||
},
|
||||
include: {
|
||||
author: {
|
||||
include: {
|
||||
likes: true,
|
||||
posts: true,
|
||||
},
|
||||
},
|
||||
likes: true,
|
||||
},
|
||||
});
|
||||
|
||||
return posts;
|
||||
}
|
||||
|
||||
export async function getPostById(id: number) {
|
||||
const post = await prisma.post.findUnique({
|
||||
where: {
|
||||
|
|
|
@ -17,8 +17,14 @@ export async function getUserByName(
|
|||
username: true,
|
||||
password: includePw,
|
||||
name: true,
|
||||
desc: true,
|
||||
pfp: true,
|
||||
createdAt: true,
|
||||
followedBy: {
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
posts: {
|
||||
include: {
|
||||
author: true,
|
||||
|
@ -60,17 +66,83 @@ export async function userExists(username: string) {
|
|||
export interface UserErrors {
|
||||
username: string | undefined;
|
||||
name: string | undefined;
|
||||
desc: string | undefined;
|
||||
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 } = {
|
||||
followId: undefined,
|
||||
all: undefined,
|
||||
};
|
||||
|
||||
if (!(await getUserById(followId))) {
|
||||
errors.followId = "User does not exist";
|
||||
}
|
||||
|
||||
if (Object.values(errors).some(Boolean)) {
|
||||
return errors;
|
||||
}
|
||||
|
||||
const follow = await prisma.user.update({
|
||||
where: {
|
||||
id: id,
|
||||
},
|
||||
data: {
|
||||
following: {
|
||||
connect: { id: followId },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!follow) {
|
||||
errors.all = "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,
|
||||
};
|
||||
|
||||
if (!(await getUserById(followId))) {
|
||||
errors.followId = "User does not exist";
|
||||
}
|
||||
|
||||
if (Object.values(errors).some(Boolean)) {
|
||||
return errors;
|
||||
}
|
||||
|
||||
const unFollow = await prisma.user.update({
|
||||
where: {
|
||||
id: id,
|
||||
},
|
||||
data: {
|
||||
following: {
|
||||
disconnect: { id: followId },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!unFollow) {
|
||||
errors.all = "Something went wrong";
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
export async function editUser(
|
||||
data: {
|
||||
intent: string;
|
||||
username: string;
|
||||
name: string;
|
||||
desc: string;
|
||||
opassword: string;
|
||||
npassword: string;
|
||||
pfp: {
|
||||
|
@ -83,6 +155,7 @@ export async function editUser(
|
|||
let errors: UserErrors = {
|
||||
username: undefined,
|
||||
name: undefined,
|
||||
desc: undefined,
|
||||
opassword: undefined,
|
||||
npassword: undefined,
|
||||
pfp: undefined,
|
||||
|
@ -137,6 +210,29 @@ export async function editUser(
|
|||
errors.all = "Something went wrong";
|
||||
}
|
||||
|
||||
return { errors: errors };
|
||||
case "desc":
|
||||
if (!data.desc) {
|
||||
errors.desc = "Please enter a valid description";
|
||||
}
|
||||
|
||||
if (Object.values(errors).some(Boolean)) {
|
||||
return { errors: errors };
|
||||
}
|
||||
|
||||
const desc = await prisma.user.update({
|
||||
where: {
|
||||
id: id,
|
||||
},
|
||||
data: {
|
||||
desc: data.desc,
|
||||
},
|
||||
});
|
||||
|
||||
if (!desc) {
|
||||
errors.all = "Something went wrong";
|
||||
}
|
||||
|
||||
return { errors: errors };
|
||||
case "password":
|
||||
if (!data.opassword) {
|
||||
|
|
20
app/root.tsx
20
app/root.tsx
|
@ -95,8 +95,8 @@ export default function App() {
|
|||
</head>
|
||||
<body className="min-h-screen subpixel-antialiased">
|
||||
<div className="container flex flex-col gap-20 m-8 mx-auto xl:flex-row">
|
||||
<div className="flex-grow w-80">
|
||||
<nav className="flex flex-col gap-5">
|
||||
<div className="flex mx-auto justify-center flex-grow w-80">
|
||||
<nav className="flex w-full flex-col gap-5">
|
||||
{!data?.id ? (
|
||||
<>
|
||||
<NavItem text={`Login`} to={`/login`} />
|
||||
|
@ -120,12 +120,14 @@ export default function App() {
|
|||
<div className="flex-grow-[4] w-full">
|
||||
<Outlet />
|
||||
</div>
|
||||
<div className="flex-grow w-80">
|
||||
<div className="hover:bg-sky-100/50 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>
|
||||
</Link>
|
||||
<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">
|
||||
<Link className="flex flex-col gap-5 items-center" to={"/"}>
|
||||
<img src="/logo.png" alt="logo" width={50} />
|
||||
<SubTitle>Twitter clone</SubTitle>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{data?.toast ? <Toast>{data.toast}</Toast> : ""}
|
||||
|
@ -141,7 +143,7 @@ export default function App() {
|
|||
function NavItem({ to, text }: { to: string; text: string }) {
|
||||
return (
|
||||
<NavLink to={to}>
|
||||
<div className="px-4 py-2 text-center rounded-lg border hover:bg-gray-100">
|
||||
<div className="px-4 w-full py-2 text-center rounded-lg border hover:bg-gray-100">
|
||||
<SubTitle>{text}</SubTitle>
|
||||
</div>
|
||||
</NavLink>
|
||||
|
|
|
@ -18,7 +18,7 @@ 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 } from "~/models/post.server";
|
||||
import { createPost, getAllPosts, getFeed } from "~/models/post.server";
|
||||
import { commitSession, getSession } from "~/utils/session.server";
|
||||
import type { RootLoaderTypes } from "~/root";
|
||||
|
||||
|
@ -34,44 +34,41 @@ export async function action({ request }: ActionFunctionArgs) {
|
|||
.formData()
|
||||
.then((data) => Object.fromEntries(data));
|
||||
|
||||
if (!userId) {
|
||||
session.flash("globalMessage", "You need to login before doing that!");
|
||||
} else {
|
||||
switch (formData.intent) {
|
||||
case "newpost":
|
||||
const postErrors = await createPost(String(formData.post), userId);
|
||||
switch (formData.intent) {
|
||||
case "newpost":
|
||||
const postErrors = await createPost(String(formData.post), userId);
|
||||
|
||||
if (Object.values(postErrors).some(Boolean)) {
|
||||
return postErrors;
|
||||
}
|
||||
if (Object.values(postErrors).some(Boolean)) {
|
||||
return postErrors;
|
||||
}
|
||||
|
||||
session.flash("globalMessage", "Post created!");
|
||||
session.flash("globalMessage", "Post created!");
|
||||
|
||||
break;
|
||||
case "like":
|
||||
const likeErrors = await likePost(Number(formData.postId), userId);
|
||||
break;
|
||||
case "like":
|
||||
const likeErrors = await likePost(Number(formData.postId), userId);
|
||||
|
||||
if (Object.values(likeErrors).some(Boolean)) {
|
||||
return likeErrors;
|
||||
}
|
||||
if (Object.values(likeErrors).some(Boolean)) {
|
||||
return likeErrors;
|
||||
}
|
||||
|
||||
session.flash("globalMessage", "Post liked!");
|
||||
session.flash("globalMessage", "Post liked!");
|
||||
|
||||
break;
|
||||
case "unlike":
|
||||
const unLikeErrors = await unLikePost(Number(formData.postId), userId);
|
||||
break;
|
||||
case "unlike":
|
||||
const unLikeErrors = await unLikePost(Number(formData.postId), userId);
|
||||
|
||||
if (Object.values(unLikeErrors).some(Boolean)) {
|
||||
return unLikeErrors;
|
||||
}
|
||||
if (Object.values(unLikeErrors).some(Boolean)) {
|
||||
return unLikeErrors;
|
||||
}
|
||||
|
||||
session.flash("globalMessage", "Post unliked!");
|
||||
session.flash("globalMessage", "Post unliked!");
|
||||
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
return json(
|
||||
{},
|
||||
{
|
||||
|
@ -88,14 +85,16 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
|||
return redirect("/");
|
||||
}
|
||||
|
||||
return await getAllPosts();
|
||||
return await getFeed(session.get("userId"));
|
||||
}
|
||||
|
||||
export default function Index() {
|
||||
const rootData = useRouteLoaderData<RootLoaderTypes>("root");
|
||||
const data = useLoaderData<LoaderFunction>();
|
||||
const data: PostWithRelations[] = useLoaderData<LoaderFunction>();
|
||||
const errors = useActionData<ActionFunction>();
|
||||
|
||||
console.log(data);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-5">
|
||||
<Title>Home</Title>
|
||||
|
@ -120,11 +119,18 @@ export default function Index() {
|
|||
</>
|
||||
)}
|
||||
</Card>
|
||||
<Card className="!p-0 !gap-0 divide-y">
|
||||
{data.map((post: PostWithRelations) => (
|
||||
<Post userId={rootData?.id} key={post.id} post={post} />
|
||||
))}
|
||||
</Card>
|
||||
<SubTitle>Feed</SubTitle>
|
||||
{data.length > 0 ? (
|
||||
<Card className="!p-0 !gap-0 divide-y">
|
||||
{data.map((post: PostWithRelations) => (
|
||||
<Post userId={rootData?.id} key={post.id} post={post} />
|
||||
))}
|
||||
</Card>
|
||||
) : (
|
||||
<Text type="error">
|
||||
Feed is empty, follow someone so their recent posts will show up here!
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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">Login</Button>
|
||||
<Button type="submit" />
|
||||
</Form>
|
||||
</Card>
|
||||
);
|
||||
|
|
|
@ -55,6 +55,7 @@ export async function action({ request }: ActionFunctionArgs) {
|
|||
|
||||
export default function Register() {
|
||||
const errors = useActionData<ActionFunction>();
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<Title>Register</Title>
|
||||
|
|
|
@ -41,20 +41,21 @@ export async function action({ request }: ActionFunctionArgs) {
|
|||
const { username, errors } = await editUser(formData, session.get("userId"));
|
||||
|
||||
if (Object.values(errors).some(Boolean)) {
|
||||
return errors;
|
||||
return { errors: errors };
|
||||
}
|
||||
|
||||
if (username) {
|
||||
session.set("username", username);
|
||||
}
|
||||
|
||||
session.flash(
|
||||
"globalMessage",
|
||||
`Field ${formData.intent} changed successfully!`
|
||||
);
|
||||
|
||||
return json(
|
||||
{},
|
||||
{
|
||||
success: {
|
||||
[String(
|
||||
formData.intent
|
||||
)]: `Field ${formData.intent} changed successfully!`,
|
||||
},
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
"Set-Cookie": await commitSession(session),
|
||||
|
@ -73,86 +74,142 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
|||
}
|
||||
|
||||
export default function Settings() {
|
||||
const errors = useActionData<ActionFunction>();
|
||||
const data = useActionData<ActionFunction>();
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-5">
|
||||
<Title>Settings</Title>
|
||||
<div className="flex flex-col gap-5">
|
||||
<details className="space-y-4">
|
||||
<summary className="flex cursor-pointer select-none">
|
||||
<SubTitle>Change username</SubTitle>
|
||||
<summary className="text-gray-600 cursor-pointer select-none">
|
||||
Change username
|
||||
</summary>
|
||||
<Card>
|
||||
<Form method="POST" className="flex flex-col gap-3">
|
||||
<Form method="POST" className="relative flex flex-col gap-3">
|
||||
<input type="hidden" name="intent" value="username" />
|
||||
<FormLabel>
|
||||
<Text>Username</Text> <FormInput type="text" name="username" />{" "}
|
||||
<Text type="error">
|
||||
{errors?.username ? errors.username : ""}
|
||||
{data?.errors?.username ? data.errors.username : ""}
|
||||
</Text>
|
||||
</FormLabel>
|
||||
<Button type="submit">Submit</Button>
|
||||
{data?.success?.username ? (
|
||||
<Text className="!absolute right-0" type="success">
|
||||
{data.success.username}
|
||||
</Text>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</Form>
|
||||
</Card>
|
||||
</details>
|
||||
<details className="space-y-4">
|
||||
<summary className="flex cursor-pointer select-none">
|
||||
<SubTitle>Change name</SubTitle>
|
||||
<summary className="text-gray-600 cursor-pointer select-none">
|
||||
Change name
|
||||
</summary>
|
||||
<Card>
|
||||
<Form method="POST" className="flex flex-col gap-3">
|
||||
<Form method="POST" className="relative flex flex-col gap-3">
|
||||
<input type="hidden" name="intent" value="name" />
|
||||
<FormLabel>
|
||||
<Text>Name</Text> <FormInput type="text" name="name" />{" "}
|
||||
<Text type="error">{errors?.name ? errors.name : ""}</Text>
|
||||
<Text type="error">
|
||||
{data?.errors?.name ? data.errors.name : ""}
|
||||
</Text>
|
||||
</FormLabel>
|
||||
<Button type="submit">Submit</Button>
|
||||
{data?.success?.name ? (
|
||||
<Text className="!absolute right-0" type="success">
|
||||
{data.success.name}
|
||||
</Text>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</Form>
|
||||
</Card>
|
||||
</details>
|
||||
<details className="space-y-4">
|
||||
<summary className="flex cursor-pointer select-none">
|
||||
<SubTitle>Change password</SubTitle>
|
||||
<summary className="text-gray-600 cursor-pointer select-none">
|
||||
Change description
|
||||
</summary>
|
||||
<Card>
|
||||
<Form method="POST" className="flex flex-col gap-3">
|
||||
<Form method="POST" className="relative flex flex-col gap-3">
|
||||
<input type="hidden" name="intent" value="desc" />
|
||||
<FormLabel>
|
||||
<Text>Description</Text> <FormInput type="text" name="desc" />{" "}
|
||||
<Text type="error">
|
||||
{data?.errors?.desc ? data.errors.desc : ""}
|
||||
</Text>
|
||||
</FormLabel>
|
||||
<Button type="submit">Submit</Button>
|
||||
{data?.success?.desc ? (
|
||||
<Text className="!absolute right-0" type="success">
|
||||
{data.success.desc}
|
||||
</Text>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</Form>
|
||||
</Card>
|
||||
</details>
|
||||
<details className="space-y-4">
|
||||
<summary className="text-gray-600 cursor-pointer select-none">
|
||||
Change password
|
||||
</summary>
|
||||
<Card>
|
||||
<Form method="POST" className="relative flex flex-col gap-3">
|
||||
<input type="hidden" name="intent" value="password" />
|
||||
<FormLabel>
|
||||
<Text>Old password</Text>{" "}
|
||||
<FormInput type="text" name="opassword" />{" "}
|
||||
<Text type="error">
|
||||
{errors?.opassword ? errors.opassword : ""}
|
||||
{data?.errors?.opassword ? data.errors.opassword : ""}
|
||||
</Text>
|
||||
</FormLabel>
|
||||
<FormLabel>
|
||||
<Text>New password</Text>{" "}
|
||||
<FormInput type="text" name="npassword" />{" "}
|
||||
<Text type="error">
|
||||
{errors?.npassword ? errors.npassword : ""}
|
||||
{data?.errors?.npassword ? data.errors.npassword : ""}
|
||||
</Text>
|
||||
</FormLabel>
|
||||
<Button type="submit">Submit</Button>
|
||||
{data?.success?.password ? (
|
||||
<Text className="!absolute right-0" type="success">
|
||||
{data.success.password}
|
||||
</Text>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</Form>
|
||||
</Card>
|
||||
</details>
|
||||
<details className="space-y-4">
|
||||
<summary className="flex cursor-pointer select-none">
|
||||
<SubTitle>Change profile picture</SubTitle>
|
||||
<summary className="text-gray-600 cursor-pointer select-none">
|
||||
Change profile picture
|
||||
</summary>
|
||||
<Card>
|
||||
<Form
|
||||
method="POST"
|
||||
encType="multipart/form-data"
|
||||
className="flex flex-col gap-3"
|
||||
className="relative flex flex-col gap-3"
|
||||
>
|
||||
<input type="hidden" name="intent" value="pfp" />
|
||||
<FormLabel>
|
||||
<Text>New profile picture</Text>{" "}
|
||||
<FormInput type="file" name="pfp" />{" "}
|
||||
<Text type="error">{errors?.pfp ? errors.pfp : ""}</Text>
|
||||
<Text type="error">
|
||||
{data?.errors?.pfp ? data.errors.pfp : ""}
|
||||
</Text>
|
||||
</FormLabel>
|
||||
<Button type="submit">Submit</Button>
|
||||
{data?.success?.pfp ? (
|
||||
<Text className="!absolute right-0" type="success">
|
||||
{data.success.pfp}
|
||||
</Text>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</Form>
|
||||
</Card>
|
||||
</details>
|
||||
|
|
|
@ -1,12 +1,63 @@
|
|||
import type { LoaderFunction, LoaderFunctionArgs } from "@remix-run/node";
|
||||
import { Link, useLoaderData, useRouteLoaderData } from "@remix-run/react";
|
||||
import type {
|
||||
ActionFunction,
|
||||
LoaderFunction,
|
||||
LoaderFunctionArgs,
|
||||
} 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 { Post, Poster } from "~/components/Post";
|
||||
import { SubTitle, Text, Title } from "~/components/Typography";
|
||||
import { getUserByName } from "~/models/user.server";
|
||||
import { followUser, getUserByName, unFollowUser } from "~/models/user.server";
|
||||
import type { RootLoaderTypes } from "~/root";
|
||||
import type { UserWithRelations } from "~/utils/prisma.server";
|
||||
import { getSession } from "~/utils/session.server";
|
||||
|
||||
export async function action({ request }: LoaderFunctionArgs) {
|
||||
const session = await getSession(request.headers.get("Cookie"));
|
||||
if (!session.has("userId")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = await request
|
||||
.formData()
|
||||
.then((data) => Object.fromEntries(data));
|
||||
|
||||
switch (formData.intent) {
|
||||
case "follow":
|
||||
const followErrors = await followUser(
|
||||
Number(formData.id),
|
||||
session.get("userId")
|
||||
);
|
||||
|
||||
if (Object.values(followErrors).some(Boolean)) {
|
||||
return followErrors;
|
||||
}
|
||||
|
||||
break;
|
||||
case "unfollow":
|
||||
const unFollowErrors = await unFollowUser(
|
||||
Number(formData.id),
|
||||
session.get("userId")
|
||||
);
|
||||
|
||||
if (Object.values(unFollowErrors).some(Boolean)) {
|
||||
return unFollowErrors;
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function loader({ params }: LoaderFunctionArgs) {
|
||||
return await getUserByName(params.username);
|
||||
|
@ -15,6 +66,9 @@ export async function loader({ params }: LoaderFunctionArgs) {
|
|||
export default function UserProfile() {
|
||||
const rootData = useRouteLoaderData<RootLoaderTypes>("root");
|
||||
const data: UserWithRelations = useLoaderData<LoaderFunction>();
|
||||
const actionData = useActionData<ActionFunction>();
|
||||
const isFollowing =
|
||||
data.followedBy.filter((follow) => follow.id === rootData?.id).length > 0;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-5">
|
||||
|
@ -24,28 +78,31 @@ export default function UserProfile() {
|
|||
<div className="flex flex-1 gap-2 items-center">
|
||||
<Poster pfp={data.pfp} username={data.username} name={data.name} />
|
||||
</div>
|
||||
{rootData?.id && rootData?.id !== data.id ? (
|
||||
<div>
|
||||
<Form method="POST">
|
||||
<input
|
||||
type="hidden"
|
||||
name="intent"
|
||||
value={isFollowing ? "unfollow" : "follow"}
|
||||
/>
|
||||
<input type="hidden" name="id" value={data.id} />
|
||||
<Button type="submit">
|
||||
{isFollowing ? "Following!" : "Follow"}
|
||||
</Button>
|
||||
</Form>
|
||||
</div>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</div>
|
||||
{data?.desc ? (
|
||||
<div>
|
||||
<Button type="button">Follow</Button>
|
||||
<Text>{data.desc}</Text>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Text>
|
||||
description goes here, posts etc go down ward Lorem ipsum dolor sit
|
||||
amet, consectetur adipiscing elit. Curabitur rutrum augue in massa
|
||||
pretium fringilla. Sed faucibus mi felis, in lobortis mi fermentum
|
||||
at. Proin quis sagittis lacus. Donec pharetra ante dolor, sed semper
|
||||
lectus dictum vel. Phasellus a pulvinar sapien. In dapibus ex vitae
|
||||
pretium consequat. Class aptent taciti sociosqu ad litora torquent
|
||||
per conubia nostra, per inceptos himenaeos. Sed volutpat justo id
|
||||
risus placerat placerat. Proin ex elit, pulvinar ac elit sed,
|
||||
egestas ultrices dolor. In elementum eros metus. Fusce placerat enim
|
||||
eu ipsum semper feugiat. Curabitur consequat, odio et auctor
|
||||
imperdiet, nisl dui dignissim felis, porttitor vulputate elit mauris
|
||||
et lacus. Vivamus sit amet pharetra est. Vivamus pretium hendrerit
|
||||
arcu. Sed massa augue, rhoncus et ante vitae, posuere sollicitudin
|
||||
massa.
|
||||
</Text>
|
||||
</div>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
<div className="flex gap-5">
|
||||
<div className="flex gap-2 items-center">
|
||||
<Text type="subtitle">Followers:</Text>
|
||||
|
@ -64,19 +121,27 @@ export default function UserProfile() {
|
|||
<div className="flex flex-col gap-10 md:flex-row">
|
||||
<div className="flex flex-col flex-grow gap-5 w-full">
|
||||
<SubTitle>Posts</SubTitle>
|
||||
<Card className="!p-0 !gap-0 divide-y">
|
||||
{data.posts.map((post) => (
|
||||
<Post userId={rootData?.id} key={post.id} post={post} />
|
||||
))}
|
||||
</Card>
|
||||
{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">
|
||||
<SubTitle>Likes</SubTitle>
|
||||
<Card className="!p-0 !gap-0 divide-y">
|
||||
{data.likes.map((post) => (
|
||||
<Post userId={rootData?.id} key={post.id} post={post} />
|
||||
))}
|
||||
</Card>
|
||||
{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} />
|
||||
))}
|
||||
</Card>
|
||||
) : (
|
||||
<Text type="subtitle">No likes yet</Text>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -7,6 +7,11 @@ export type PostWithRelations = Prisma.PostGetPayload<{
|
|||
}>;
|
||||
export type UserWithRelations = Prisma.UserGetPayload<{
|
||||
include: {
|
||||
followedBy: {
|
||||
select: {
|
||||
id: true;
|
||||
};
|
||||
};
|
||||
posts: {
|
||||
include: {
|
||||
author: true;
|
||||
|
|
|
@ -15,6 +15,7 @@ model User {
|
|||
username String @unique
|
||||
password String
|
||||
name String?
|
||||
desc String?
|
||||
pfp String
|
||||
posts Post[] @relation("UserPosts")
|
||||
likes Post[] @relation("UserLikes")
|
||||
|
|
Loading…
Reference in New Issue