Meta information, add reply receipts to where they were missing,class sorting and also some pfp stuff
This commit is contained in:
parent
1e632c52be
commit
1e25c30cf3
|
@ -9,7 +9,7 @@ export function Card({
|
|||
}) {
|
||||
return (
|
||||
<div
|
||||
className={`flex z-10 flex-col bg-ctp-mantle/50 gap-5 p-8 border w-full rounded-md ${className}`}
|
||||
className={`flex z-10 flex-col gap-5 p-8 w-full rounded-md border bg-ctp-mantle/50 ${className}`}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
|
|
|
@ -27,7 +27,7 @@ export function TextArea({
|
|||
}) {
|
||||
return (
|
||||
<textarea
|
||||
className="p-4 w-full bg-ctp-crust/50 text-ctp-subtext0 rounded-lg border"
|
||||
className="p-4 w-full rounded-lg border bg-ctp-crust/50 text-ctp-subtext0"
|
||||
placeholder={placeholder}
|
||||
name={name}
|
||||
></textarea>
|
||||
|
|
|
@ -22,13 +22,15 @@ export function Poster({
|
|||
return style === "compact" ? (
|
||||
<div>
|
||||
<img
|
||||
className={`peer/image rounded-full border w-6 h-6`}
|
||||
className={`w-6 h-6 rounded-full border peer/image`}
|
||||
src={
|
||||
pfp
|
||||
? `
|
||||
/uploads/${encodeURIComponent(pfp)}`
|
||||
: `/uploads/default.png`
|
||||
}
|
||||
width={24}
|
||||
height={24}
|
||||
alt="pfp"
|
||||
/>
|
||||
<Card className="!p-3 hidden peer-hover/image:flex hover:flex !absolute !w-fit">
|
||||
|
@ -36,15 +38,17 @@ export function Poster({
|
|||
</Card>
|
||||
</div>
|
||||
) : (
|
||||
<div className={`flex break-all items-center gap-3 ${className}`}>
|
||||
<div className={`flex gap-3 items-center break-all ${className}`}>
|
||||
<img
|
||||
className="rounded-full border w-12 h-12"
|
||||
className="w-12 h-12 rounded-full border"
|
||||
src={
|
||||
pfp
|
||||
? `
|
||||
/uploads/${encodeURIComponent(pfp)}`
|
||||
: `/uploads/default.png`
|
||||
}
|
||||
height={48}
|
||||
width={48}
|
||||
alt="pfp"
|
||||
/>
|
||||
<div>
|
||||
|
@ -120,8 +124,8 @@ export function Post({
|
|||
</>
|
||||
}
|
||||
/>
|
||||
<div className="h-8 pl-[39px]">
|
||||
<div className="border w-0 h-full"></div>
|
||||
<div className="h-8 pl-[36px]">
|
||||
<div className="w-0 h-full border"></div>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
|
@ -139,18 +143,31 @@ export function Post({
|
|||
} px-4`}
|
||||
>
|
||||
<div className="flex gap-4">
|
||||
<div className="flex flex-col justify-center items-center min-h-full">
|
||||
<div className="flex flex-col items-center min-h-full">
|
||||
<img
|
||||
className="w-12 h-12 rounded-full"
|
||||
className="w-12 h-11 rounded-full border"
|
||||
src={`/uploads/${post.author.pfp}`}
|
||||
alt=""
|
||||
height={48}
|
||||
width={48}
|
||||
alt="pfp"
|
||||
/>
|
||||
{child ? <div className="w-0 border h-full"></div> : ""}
|
||||
{child ? <div className="w-0 h-full border"></div> : ""}
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex relative flex-col gap-2 w-full">
|
||||
<div className="absolute right-0">
|
||||
<Text className="subtitle text-[12px] text-ctp-subtext1/50">
|
||||
{new Date(post.createdAt).toLocaleDateString("en-fi", {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
hour: "numeric",
|
||||
minute: "numeric",
|
||||
})}
|
||||
</Text>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
{post.reposters?.length > 0 ? (
|
||||
<div className="flex gap-2 items-center">
|
||||
<div className="flex gap-1 items-center">
|
||||
{post.reposters.map((user) => (
|
||||
<Poster
|
||||
style="compact"
|
||||
|
@ -189,13 +206,19 @@ export function Post({
|
|||
)}
|
||||
</div>
|
||||
</div>
|
||||
<Link to={`/${rootPostId ? "reply" : "post"}/${post.id}`}>
|
||||
{!enlarge ? (
|
||||
<Link to={`/${rootPostId ? "reply" : "post"}/${post.id}`}>
|
||||
<div>
|
||||
<Text>{post.text}</Text>
|
||||
</div>
|
||||
</Link>
|
||||
) : (
|
||||
<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"
|
||||
|
|
|
@ -5,10 +5,10 @@ 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-ctp-base w-80 p-4 fixed bottom-10 right-10 border rounded-lg shadow-lg">
|
||||
<div className="fixed right-10 bottom-10 z-10 p-4 w-80 rounded-lg border shadow-lg peer-checked/toast:hidden bg-ctp-base">
|
||||
<div className="relative">
|
||||
<label
|
||||
className="absolute right-0 cursor-pointer text-gray-300 hover:text-gray-400"
|
||||
className="absolute right-0 text-gray-300 cursor-pointer hover:text-gray-400"
|
||||
htmlFor="toast-toggle"
|
||||
>
|
||||
X
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
import type { LoaderFunctionArgs } from "@remix-run/node";
|
||||
import type { LoaderFunctionArgs, MetaFunction } from "@remix-run/node";
|
||||
import { redirect } from "@remix-run/node";
|
||||
import { Text } from "~/components/Typography";
|
||||
import { getSession } from "~/utils/session.server";
|
||||
|
||||
export const meta: MetaFunction<typeof loader> = () => {
|
||||
return [{ title: `Welcome | Twitter Clone` }];
|
||||
};
|
||||
|
||||
export async function loader({ request }: LoaderFunctionArgs) {
|
||||
const session = await getSession(request.headers.get("Cookie"));
|
||||
if (session.has("userId")) {
|
||||
|
@ -14,8 +18,8 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
|||
|
||||
export default function Index() {
|
||||
return (
|
||||
<div className="bg-ctp-sky/10 rounded-lg">
|
||||
<div className="flex flex-col gap-1 h-96 justify-center items-center">
|
||||
<div className="rounded-lg bg-ctp-sky/10">
|
||||
<div className="flex flex-col gap-1 justify-center items-center h-96">
|
||||
<img src="/logo.png" alt="twitter" width={64} />
|
||||
<h1 className="text-7xl font-black">
|
||||
<span className="text-sky-400">Twitter</span>{" "}
|
||||
|
|
|
@ -4,10 +4,12 @@ import type {
|
|||
ActionFunctionArgs,
|
||||
LoaderFunction,
|
||||
LoaderFunctionArgs,
|
||||
MetaFunction,
|
||||
} from "@remix-run/node";
|
||||
import { json, redirect } from "@remix-run/node";
|
||||
import {
|
||||
Form,
|
||||
Link,
|
||||
useActionData,
|
||||
useLoaderData,
|
||||
useRouteLoaderData,
|
||||
|
@ -24,6 +26,10 @@ 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")) {
|
||||
|
@ -126,7 +132,7 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
|||
where: { id: userId },
|
||||
select: { following: { select: { id: true } } },
|
||||
});
|
||||
const followingArr = [...following.following.map((user) => user.id), userId];
|
||||
const followingArr = [...following.following.map((user) => user.id)];
|
||||
|
||||
const feed = await prisma.post.findMany({
|
||||
where: {
|
||||
|
@ -218,18 +224,20 @@ export default function Index() {
|
|||
return (
|
||||
<div className="flex flex-col gap-5">
|
||||
<Title>Home</Title>
|
||||
<Card>
|
||||
<Card className="!gap-0">
|
||||
{rootData?.id ? (
|
||||
<>
|
||||
<SubTitle>New post</SubTitle>
|
||||
<Form className="flex flex-col gap-3" method="POST">
|
||||
<Form method="POST">
|
||||
<input type="hidden" name="intent" value={"newpost"} />
|
||||
<FormLabel>
|
||||
<Text>Post body</Text>
|
||||
<TextArea name="post" />
|
||||
<Text type="error">{errors?.body ? errors.body : ""}</Text>
|
||||
</FormLabel>
|
||||
<Button type="submit">Post</Button>
|
||||
<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>
|
||||
</>
|
||||
) : (
|
||||
|
@ -244,8 +252,12 @@ export default function Index() {
|
|||
))}
|
||||
</Card>
|
||||
) : (
|
||||
<Text type="error">
|
||||
Feed is empty, follow someone so their recent posts will show up here!
|
||||
<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>
|
||||
|
|
|
@ -3,6 +3,7 @@ import {
|
|||
type ActionFunction,
|
||||
type ActionFunctionArgs,
|
||||
type LoaderFunctionArgs,
|
||||
type MetaFunction,
|
||||
} from "@remix-run/node";
|
||||
import { Form, useActionData } from "@remix-run/react";
|
||||
import { Button } from "~/components/Button";
|
||||
|
@ -12,6 +13,10 @@ import { Text, Title } from "~/components/Typography";
|
|||
import { checkUserLogin } from "~/models/user.server";
|
||||
import { commitSession, getSession } from "~/utils/session.server";
|
||||
|
||||
export const meta: MetaFunction<typeof loader> = () => {
|
||||
return [{ title: `Login | Twitter Clone` }];
|
||||
};
|
||||
|
||||
export async function loader({ request }: LoaderFunctionArgs) {
|
||||
const session = await getSession(request.headers.get("Cookie"));
|
||||
if (session.has("userId")) {
|
||||
|
|
|
@ -3,8 +3,8 @@ import type {
|
|||
ActionFunction,
|
||||
ActionFunctionArgs,
|
||||
LoaderFunctionArgs,
|
||||
MetaFunction,
|
||||
} from "@remix-run/node";
|
||||
|
||||
import { Form, useActionData } from "@remix-run/react";
|
||||
import { commitSession, getSession } from "~/utils/session.server";
|
||||
import { createUser } from "~/models/user.server";
|
||||
|
@ -13,6 +13,10 @@ import { Button } from "~/components/Button";
|
|||
import { Card } from "~/components/Card";
|
||||
import { Text, Title } from "~/components/Typography";
|
||||
|
||||
export const meta: MetaFunction<typeof loader> = () => {
|
||||
return [{ title: `Register | Twitter Clone` }];
|
||||
};
|
||||
|
||||
export async function loader({ request }: LoaderFunctionArgs) {
|
||||
const session = await getSession(request.headers.get("Cookie"));
|
||||
if (session.has("userId")) {
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import type { ActionFunction } from "@remix-run/node";
|
||||
import {
|
||||
json,
|
||||
type LoaderFunction,
|
||||
type LoaderFunctionArgs,
|
||||
type ActionFunction,
|
||||
type MetaFunction,
|
||||
} from "@remix-run/node";
|
||||
import {
|
||||
Form,
|
||||
|
@ -19,7 +20,11 @@ import type { RootLoaderTypes } from "~/root";
|
|||
import { Button } from "~/components/Button";
|
||||
import { FormLabel, TextArea } from "~/components/Form";
|
||||
|
||||
export async function loader({ request, params }: LoaderFunctionArgs) {
|
||||
export const meta: MetaFunction<typeof loader> = ({ data, error }) => {
|
||||
return [{ title: `${error ? "Not Found" : data.text} | Twitter Clone` }];
|
||||
};
|
||||
|
||||
export async function loader({ params }: LoaderFunctionArgs) {
|
||||
const reply = await prisma.reply.findUnique({
|
||||
where: {
|
||||
id: Number(params.id),
|
||||
|
@ -341,7 +346,6 @@ 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">
|
||||
|
@ -353,8 +357,12 @@ export default function ReplyRoute() {
|
|||
child={true}
|
||||
reply={true}
|
||||
/>
|
||||
<div className="h-8 pl-[39px]">
|
||||
<div className="border w-0 h-full"></div>
|
||||
<div className="h-8 pl-[36px]">
|
||||
<div
|
||||
className={`border ${
|
||||
data?.parentReply?.parentReply?.parentReply ? "border-dashed" : ""
|
||||
} w-0 h-full`}
|
||||
></div>
|
||||
</div>
|
||||
<div>
|
||||
<Post
|
||||
|
@ -414,6 +422,17 @@ export default function ReplyRoute() {
|
|||
userId={rootData?.id}
|
||||
rootPostId={data.post.id}
|
||||
reply={true}
|
||||
topTitle={
|
||||
<>
|
||||
Replying to
|
||||
<Poster
|
||||
style="compact"
|
||||
username={data.parentReply.author.username}
|
||||
name={data.parentReply.author.name}
|
||||
pfp={data.parentReply.author.pfp}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
|
|
|
@ -2,6 +2,7 @@ import type {
|
|||
ActionFunction,
|
||||
ActionFunctionArgs,
|
||||
LoaderFunctionArgs,
|
||||
MetaFunction,
|
||||
} from "@remix-run/node";
|
||||
import {
|
||||
json,
|
||||
|
@ -26,6 +27,10 @@ import { commitSession, getSession } from "~/utils/session.server";
|
|||
|
||||
const themes = ["latte", "frappe", "macchiato", "mocha", "none"];
|
||||
|
||||
export const meta: MetaFunction<typeof loader> = () => {
|
||||
return [{ title: `Settings | Twitter Clone` }];
|
||||
};
|
||||
|
||||
export async function action({ request }: ActionFunctionArgs) {
|
||||
const cookieHeader = request.headers.get("Cookie");
|
||||
const session = await getSession(cookieHeader);
|
||||
|
@ -126,7 +131,7 @@ export default function Settings() {
|
|||
<span className="text-ctp-text/75">Change username</span>
|
||||
</summary>
|
||||
<Card>
|
||||
<Form method="POST" className="relative flex flex-col gap-3">
|
||||
<Form method="POST" className="flex relative flex-col gap-3">
|
||||
<input type="hidden" name="intent" value="username" />
|
||||
<FormLabel>
|
||||
<Text>Username</Text> <FormInput type="text" name="username" />{" "}
|
||||
|
@ -148,7 +153,7 @@ export default function Settings() {
|
|||
<span className="text-ctp-text/75">Change name</span>
|
||||
</summary>
|
||||
<Card>
|
||||
<Form method="POST" className="relative flex flex-col gap-3">
|
||||
<Form method="POST" className="flex relative flex-col gap-3">
|
||||
<input type="hidden" name="intent" value="name" />
|
||||
<FormLabel>
|
||||
<Text>Name</Text> <FormInput type="text" name="name" />{" "}
|
||||
|
@ -170,7 +175,7 @@ export default function Settings() {
|
|||
<span className="text-ctp-text/75">Change description</span>
|
||||
</summary>
|
||||
<Card>
|
||||
<Form method="POST" className="relative flex flex-col gap-3">
|
||||
<Form method="POST" className="flex relative flex-col gap-3">
|
||||
<input type="hidden" name="intent" value="desc" />
|
||||
<FormLabel>
|
||||
<Text>Description</Text> <FormInput type="text" name="desc" />{" "}
|
||||
|
@ -192,7 +197,7 @@ export default function Settings() {
|
|||
<span className="text-ctp-text/75">Change password</span>
|
||||
</summary>
|
||||
<Card>
|
||||
<Form method="POST" className="relative flex flex-col gap-3">
|
||||
<Form method="POST" className="flex relative flex-col gap-3">
|
||||
<input type="hidden" name="intent" value="password" />
|
||||
<FormLabel>
|
||||
<Text>Old password</Text>{" "}
|
||||
|
@ -225,7 +230,7 @@ export default function Settings() {
|
|||
<Form
|
||||
method="POST"
|
||||
encType="multipart/form-data"
|
||||
className="relative flex flex-col gap-3"
|
||||
className="flex relative flex-col gap-3"
|
||||
>
|
||||
<input type="hidden" name="intent" value="pfp" />
|
||||
<FormLabel>
|
||||
|
@ -249,12 +254,12 @@ export default function Settings() {
|
|||
<span className="text-ctp-text/75">Change theme</span>
|
||||
</summary>
|
||||
<Card>
|
||||
<Form className="relative flex flex-col gap-3" method="POST">
|
||||
<Form className="flex relative flex-col gap-3" method="POST">
|
||||
<input type="hidden" name="intent" value="theme" />
|
||||
<FormLabel>
|
||||
<Text>Theme</Text>
|
||||
<select
|
||||
className="w-fit rounded-lg p-2 bg-ctp-crust border"
|
||||
className="p-2 rounded-lg border w-fit bg-ctp-crust"
|
||||
name="theme"
|
||||
>
|
||||
{themes.map((theme) => (
|
||||
|
|
|
@ -2,6 +2,7 @@ import type {
|
|||
ActionFunction,
|
||||
LoaderFunction,
|
||||
LoaderFunctionArgs,
|
||||
MetaFunction,
|
||||
} from "@remix-run/node";
|
||||
import {
|
||||
Form,
|
||||
|
@ -20,6 +21,10 @@ import type { RootLoaderTypes } from "~/root";
|
|||
import { prisma, type UserWithRelations } from "~/utils/prisma.server";
|
||||
import { getSession } from "~/utils/session.server";
|
||||
|
||||
export const meta: MetaFunction<typeof loader> = ({ data, error }) => {
|
||||
return [{ title: `${error ? "Not Found" : data?.username} | Twitter Clone` }];
|
||||
};
|
||||
|
||||
export async function action({ request }: LoaderFunctionArgs) {
|
||||
const session = await getSession(request.headers.get("Cookie"));
|
||||
if (!session.has("userId")) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import type { LoaderFunction } from "@remix-run/node";
|
||||
import type { LoaderFunction, MetaFunction } from "@remix-run/node";
|
||||
import { useLoaderData } from "@remix-run/react";
|
||||
import { Card } from "~/components/Card";
|
||||
import { Poster } from "~/components/Post";
|
||||
|
@ -6,6 +6,10 @@ import { Title } from "~/components/Typography";
|
|||
import { getUsers } from "~/models/user.server";
|
||||
import type { UserWithRelations } from "~/utils/prisma.server";
|
||||
|
||||
export const meta: MetaFunction<typeof loader> = () => {
|
||||
return [{ title: `Users | Twitter Clone` }];
|
||||
};
|
||||
|
||||
export async function loader() {
|
||||
return await getUsers(10);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue