format everything with prettier
This commit is contained in:
parent
f9ed2333aa
commit
0fc54da81a
|
@ -1,17 +1,20 @@
|
||||||
import { Link } from "@remix-run/react";
|
import { Link } from "@remix-run/react";
|
||||||
|
|
||||||
export default function Header() {
|
export default function Header() {
|
||||||
return (
|
return (
|
||||||
<div className="flex space-x-4 justify-center items-center py-6 px-12 mb-12 bg-ctp-crust rounded mx-auto shadow-lg shadow-ctp-sapphire outline">
|
<div className="flex space-x-4 justify-center items-center py-6 px-12 mb-12 bg-ctp-crust rounded mx-auto shadow-lg shadow-ctp-sapphire outline">
|
||||||
<div className="flex-1">
|
<div className="flex-1"></div>
|
||||||
|
<div className="">
|
||||||
</div>
|
<h1 className="text-4xl font-bold">Lauta</h1>
|
||||||
<div className="">
|
</div>
|
||||||
<h1 className="text-4xl font-bold">Lauta</h1>
|
<nav className="flex-1 flex justify-end">
|
||||||
</div>
|
<Link
|
||||||
<nav className="flex-1 flex justify-end">
|
className="text-ctp-blue hover:underline hover:text-ctp-teal"
|
||||||
<Link className="text-ctp-blue hover:underline hover:text-ctp-teal" to={'/'}>threads</Link>
|
to={"/"}
|
||||||
</nav>
|
>
|
||||||
</div>
|
threads
|
||||||
);
|
</Link>
|
||||||
}
|
</nav>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import Header from "./Header";
|
import Header from "./Header";
|
||||||
|
|
||||||
export default function Overlay({ children }) {
|
export default function Overlay({ children }) {
|
||||||
return (
|
return (
|
||||||
<div className={`text-ctp-text container mx-auto p-4`}>
|
<div className={`text-ctp-text container mx-auto p-4`}>
|
||||||
<Header/>
|
<Header />
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
|
@ -7,10 +7,10 @@ import {
|
||||||
ScrollRestoration,
|
ScrollRestoration,
|
||||||
} from "@remix-run/react";
|
} from "@remix-run/react";
|
||||||
|
|
||||||
import styles from "./styles/app.css"
|
import styles from "./styles/app.css";
|
||||||
|
|
||||||
export function links() {
|
export function links() {
|
||||||
return [{ rel: "stylesheet", href: styles }]
|
return [{ rel: "stylesheet", href: styles }];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const meta = () => ({
|
export const meta = () => ({
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
import { useLoaderData, useActionData, Form, Link } from "@remix-run/react";
|
import { useLoaderData, useActionData, Form, Link } from "@remix-run/react";
|
||||||
import { unstable_createFileUploadHandler, unstable_parseMultipartFormData, json, redirect } from "@remix-run/node";
|
import {
|
||||||
|
unstable_createFileUploadHandler,
|
||||||
|
unstable_parseMultipartFormData,
|
||||||
|
json,
|
||||||
|
redirect,
|
||||||
|
} from "@remix-run/node";
|
||||||
import prisma from "~/utils/db.server";
|
import prisma from "~/utils/db.server";
|
||||||
import Overlay from "~/components/Overlay";
|
import Overlay from "~/components/Overlay";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
@ -11,13 +16,13 @@ export async function action({ request }) {
|
||||||
const post = formData.get("post");
|
const post = formData.get("post");
|
||||||
|
|
||||||
const fileUploadHandler = unstable_createFileUploadHandler({
|
const fileUploadHandler = unstable_createFileUploadHandler({
|
||||||
directory: './public/uploads/',
|
directory: "./public/uploads/",
|
||||||
maxPartSize: 500000,
|
maxPartSize: 500000,
|
||||||
file: ({ filename }) => filename,
|
file: ({ filename }) => filename,
|
||||||
filter: data => {
|
filter: (data) => {
|
||||||
const fileTypes = ['jpeg', 'jpg', 'png', 'gif']
|
const fileTypes = ["jpeg", "jpg", "png", "gif"];
|
||||||
// if sent file is not an image, don't handle it
|
// if sent file is not an image, don't handle it
|
||||||
if (!fileTypes.includes(data.contentType.split('/')[1])) return false;
|
if (!fileTypes.includes(data.contentType.split("/")[1])) return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
@ -27,18 +32,21 @@ export async function action({ request }) {
|
||||||
let multiPartformdata;
|
let multiPartformdata;
|
||||||
let imageName;
|
let imageName;
|
||||||
try {
|
try {
|
||||||
multiPartformdata = await unstable_parseMultipartFormData(request, fileUploadHandler);
|
multiPartformdata = await unstable_parseMultipartFormData(
|
||||||
|
request,
|
||||||
|
fileUploadHandler
|
||||||
|
);
|
||||||
imageName = multiPartformdata.get("image").name;
|
imageName = multiPartformdata.get("image").name;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
errors.image = "Image size too big or file sent is not an image";
|
errors.image = "Image size too big or file sent is not an image";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof title !== "string" || title.length > 50 || title.length < 3) {
|
if (typeof title !== "string" || title.length > 50 || title.length < 3) {
|
||||||
errors.title = "Title too long or short";
|
errors.title = "Title too long or short";
|
||||||
};
|
}
|
||||||
if (typeof post !== "string" || post.length > 50 || post.length < 3) {
|
if (typeof post !== "string" || post.length > 50 || post.length < 3) {
|
||||||
errors.post = "Post too long or short";
|
errors.post = "Post too long or short";
|
||||||
};
|
}
|
||||||
if (Object.keys(errors).length) {
|
if (Object.keys(errors).length) {
|
||||||
return json(errors, { status: 422 });
|
return json(errors, { status: 422 });
|
||||||
}
|
}
|
||||||
|
@ -57,9 +65,9 @@ export async function action({ request }) {
|
||||||
export async function loader({ request }) {
|
export async function loader({ request }) {
|
||||||
const url = new URL(request.url);
|
const url = new URL(request.url);
|
||||||
const term = url.searchParams.get("search-term");
|
const term = url.searchParams.get("search-term");
|
||||||
|
|
||||||
if (term) {
|
if (term) {
|
||||||
if (typeof term !== 'string') throw new Error("bad search term")
|
if (typeof term !== "string") throw new Error("bad search term");
|
||||||
const data = await prisma.thread.findMany({
|
const data = await prisma.thread.findMany({
|
||||||
where: {
|
where: {
|
||||||
OR: [
|
OR: [
|
||||||
|
@ -79,9 +87,9 @@ export async function loader({ request }) {
|
||||||
posts: true,
|
posts: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
};
|
}
|
||||||
|
|
||||||
const data = await prisma.thread.findMany({
|
const data = await prisma.thread.findMany({
|
||||||
include: {
|
include: {
|
||||||
|
@ -95,55 +103,125 @@ export async function loader({ request }) {
|
||||||
export default function Index() {
|
export default function Index() {
|
||||||
const data = useLoaderData();
|
const data = useLoaderData();
|
||||||
const errors = useActionData();
|
const errors = useActionData();
|
||||||
const [search, setSearch] = useState('');
|
const [search, setSearch] = useState("");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Overlay>
|
<Overlay>
|
||||||
<div className="bg-ctp-crust outline rounded-xl shadow-lg shadow-ctp-red w-fit p-10 mx-auto space-y-4">
|
<div className="bg-ctp-crust outline rounded-xl shadow-lg shadow-ctp-red w-fit p-10 mx-auto space-y-4">
|
||||||
<h1 className="text-center text-4xl font-semibold text-transparent bg-clip-text bg-gradient-to-br from-ctp-subtext0 to-ctp-subtext1 ">Create a thread</h1>
|
<h1 className="text-center text-4xl font-semibold text-transparent bg-clip-text bg-gradient-to-br from-ctp-subtext0 to-ctp-subtext1 ">
|
||||||
<Form className="rounded space-y-4 flex flex-col flex-wrap text-center justify-center items-center " method="post" encType="multipart/form-data">
|
Create a thread
|
||||||
<div className="space-y-4">
|
</h1>
|
||||||
<label className="flex flex-col" htmlFor="title-input"><span className="font-semibold text-ctp-subtext0">Title</span>
|
<Form
|
||||||
<textarea className=" bg-ctp-surface0 m-1 p-1 rounded-md shadow shadow-ctp-overlay0" required minLength={3} type="text" name="title" placeholder="Lain" />
|
className="rounded space-y-4 flex flex-col flex-wrap text-center justify-center items-center "
|
||||||
<p className="text-ctp-red">{errors ? errors.title : ''}</p>
|
method="post"
|
||||||
</label>
|
encType="multipart/form-data"
|
||||||
<label className="flex flex-col" htmlFor="post-input"><span className="font-semibold text-ctp-subtext0">Post</span>
|
>
|
||||||
<textarea className=" bg-ctp-surface0 m-1 p-1 rounded shadow shadow-ctp-overlay0" required minLength={3} type="text" name="post" placeholder="Iwakura" />
|
<div className="space-y-4">
|
||||||
<p className="text-ctp-red">{errors ? errors.post : ''}</p>
|
<label className="flex flex-col" htmlFor="title-input">
|
||||||
</label>
|
<span className="font-semibold text-ctp-subtext0">Title</span>
|
||||||
<label className="flex flex-col" htmlFor="image-upload"><span className="font-semibold text-ctp-subtext0">Image (500kbps max)</span>
|
<textarea
|
||||||
<input className="block w-full px-3 py-1.5 text-base font-semibold text-ctp-text bg-ctp-surface0 bg-clip-padding rounded transition ease-in-out m-0" required accept="image/*" type="file" id="image-upload" name="image" />
|
className=" bg-ctp-surface0 m-1 p-1 rounded-md shadow shadow-ctp-overlay0"
|
||||||
<p className="text-ctp-red">{errors ? errors.image : ''}</p>
|
required
|
||||||
</label>
|
minLength={3}
|
||||||
</div>
|
type="text"
|
||||||
<button className=" bg-ctp-surface1 text-ctp-flamingo shadow shadow-ctp-overlay0 hover hover:bg-ctp-surface2 py-2 px-4 hover:underline rounded-full" type="submit">Create!</button>
|
name="title"
|
||||||
|
placeholder="Lain"
|
||||||
|
/>
|
||||||
|
<p className="text-ctp-red">{errors ? errors.title : ""}</p>
|
||||||
|
</label>
|
||||||
|
<label className="flex flex-col" htmlFor="post-input">
|
||||||
|
<span className="font-semibold text-ctp-subtext0">Post</span>
|
||||||
|
<textarea
|
||||||
|
className=" bg-ctp-surface0 m-1 p-1 rounded shadow shadow-ctp-overlay0"
|
||||||
|
required
|
||||||
|
minLength={3}
|
||||||
|
type="text"
|
||||||
|
name="post"
|
||||||
|
placeholder="Iwakura"
|
||||||
|
/>
|
||||||
|
<p className="text-ctp-red">{errors ? errors.post : ""}</p>
|
||||||
|
</label>
|
||||||
|
<label className="flex flex-col" htmlFor="image-upload">
|
||||||
|
<span className="font-semibold text-ctp-subtext0">
|
||||||
|
Image (500kbps max)
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
className="block w-full px-3 py-1.5 text-base font-semibold text-ctp-text bg-ctp-surface0 bg-clip-padding rounded transition ease-in-out m-0"
|
||||||
|
required
|
||||||
|
accept="image/*"
|
||||||
|
type="file"
|
||||||
|
id="image-upload"
|
||||||
|
name="image"
|
||||||
|
/>
|
||||||
|
<p className="text-ctp-red">{errors ? errors.image : ""}</p>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
className=" bg-ctp-surface1 text-ctp-flamingo shadow shadow-ctp-overlay0 hover hover:bg-ctp-surface2 py-2 px-4 hover:underline rounded-full"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
Create!
|
||||||
|
</button>
|
||||||
</Form>
|
</Form>
|
||||||
</div>
|
</div>
|
||||||
<Form onSubmit={e => e.preventDefault()} className="pt-8" method="get">
|
<Form onSubmit={(e) => e.preventDefault()} className="pt-8" method="get">
|
||||||
<label className="flex flex-col items-center" htmlFor="thread-search"><span className="text-2xl tracking-widest">{search ? `Searching with term: ${search}` : 'Search for a thread'}</span>
|
<label className="flex flex-col items-center" htmlFor="thread-search">
|
||||||
<input onChange={e => setSearch(e.target.value)} value={search} className="w-fit bg-ctp-surface0 m-1 p-1 rounded shadow shadow-ctp-overlay0" placeholder="Search term..." type="text" name="search-term" id="search" />
|
<span className="text-2xl tracking-widest">
|
||||||
|
{search ? `Searching with term: ${search}` : "Search for a thread"}
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
|
value={search}
|
||||||
|
className="w-fit bg-ctp-surface0 m-1 p-1 rounded shadow shadow-ctp-overlay0"
|
||||||
|
placeholder="Search term..."
|
||||||
|
type="text"
|
||||||
|
name="search-term"
|
||||||
|
id="search"
|
||||||
|
/>
|
||||||
</label>
|
</label>
|
||||||
</Form>
|
</Form>
|
||||||
{data.length > 0 ?
|
{data.length > 0 ? (
|
||||||
<ul className="flex flex-wrap justify-around items-center p-8">
|
<ul className="flex flex-wrap justify-around items-center p-8">
|
||||||
{data.map(thread =>
|
{data.map((thread) =>
|
||||||
thread.title.includes(search) || thread.post.includes(search) ? <li className={`bg-ctp-mantle shadow-xl outline shadow-ctp-pink rounded-xl p-4 m-4 hover:shadow-xl scale-100 hover:scale-110 duration-300`} key={thread.id}>
|
thread.title.includes(search) || thread.post.includes(search) ? (
|
||||||
<Link className="space-y-4 flex flex-col" to={`threads/${thread.id}`}>
|
<li
|
||||||
<div className="flex items-center justify-center">
|
className={`bg-ctp-mantle shadow-xl outline shadow-ctp-pink rounded-xl p-4 m-4 hover:shadow-xl scale-100 hover:scale-110 duration-300`}
|
||||||
<img className="w-48 max-h-96 rounded-xl " src={`/uploads/${thread.imageName}`} alt="thread image" />
|
key={thread.id}
|
||||||
</div>
|
>
|
||||||
<div>
|
<Link
|
||||||
<h2 className="text-2xl font-semibold text-center tracking-tighter ">{thread.title}</h2>
|
className="space-y-4 flex flex-col"
|
||||||
<p className=" text-center text-ctp-subtext0">{thread.post}</p>
|
to={`threads/${thread.id}`}
|
||||||
<p className=" text-xs text-center text-ctp-subtext1 ">Posts: {thread.posts.length}</p>
|
>
|
||||||
</div>
|
<div className="flex items-center justify-center">
|
||||||
</Link>
|
<img
|
||||||
</li> : ''
|
className="w-48 max-h-96 rounded-xl "
|
||||||
|
src={`/uploads/${thread.imageName}`}
|
||||||
|
alt="thread image"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2 className="text-2xl font-semibold text-center tracking-tighter ">
|
||||||
|
{thread.title}
|
||||||
|
</h2>
|
||||||
|
<p className=" text-center text-ctp-subtext0">
|
||||||
|
{thread.post}
|
||||||
|
</p>
|
||||||
|
<p className=" text-xs text-center text-ctp-subtext1 ">
|
||||||
|
Posts: {thread.posts.length}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
)
|
||||||
)}
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
:
|
) : (
|
||||||
<h3 className="flex p-20 justify-center items-center text-center text-2xl">no threads yet</h3>
|
<h3 className="flex p-20 justify-center items-center text-center text-2xl">
|
||||||
}
|
no threads yet
|
||||||
|
</h3>
|
||||||
|
)}
|
||||||
</Overlay>
|
</Overlay>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
import { redirect } from "@remix-run/node";
|
import { redirect } from "@remix-run/node";
|
||||||
import { unstable_createFileUploadHandler, unstable_parseMultipartFormData, json } from "@remix-run/node";
|
import {
|
||||||
|
unstable_createFileUploadHandler,
|
||||||
|
unstable_parseMultipartFormData,
|
||||||
|
json,
|
||||||
|
} from "@remix-run/node";
|
||||||
import { useLoaderData, Form, useActionData, Link } from "@remix-run/react";
|
import { useLoaderData, Form, useActionData, Link } from "@remix-run/react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import Overlay from "~/components/Overlay";
|
import Overlay from "~/components/Overlay";
|
||||||
|
@ -7,7 +11,7 @@ import prisma from "~/utils/db.server";
|
||||||
|
|
||||||
export async function action({ request, params }) {
|
export async function action({ request, params }) {
|
||||||
const threadId = params.threadId;
|
const threadId = params.threadId;
|
||||||
if (!parseInt(threadId)) throw new Error('Bad route parameter');
|
if (!parseInt(threadId)) throw new Error("Bad route parameter");
|
||||||
|
|
||||||
const clonedData = request.clone();
|
const clonedData = request.clone();
|
||||||
const formData = await clonedData.formData();
|
const formData = await clonedData.formData();
|
||||||
|
@ -15,13 +19,13 @@ export async function action({ request, params }) {
|
||||||
const replying = formData.get("replying");
|
const replying = formData.get("replying");
|
||||||
|
|
||||||
const fileUploadHandler = unstable_createFileUploadHandler({
|
const fileUploadHandler = unstable_createFileUploadHandler({
|
||||||
directory: './public/uploads/',
|
directory: "./public/uploads/",
|
||||||
maxPartSize: 500000,
|
maxPartSize: 500000,
|
||||||
file: ({ filename }) => filename,
|
file: ({ filename }) => filename,
|
||||||
filter: data => {
|
filter: (data) => {
|
||||||
const fileTypes = ['jpeg', 'jpg', 'png', 'gif']
|
const fileTypes = ["jpeg", "jpg", "png", "gif"];
|
||||||
// if sent file is not an image, don't handle it
|
// if sent file is not an image, don't handle it
|
||||||
if (!fileTypes.includes(data.contentType.split('/')[1])) return false;
|
if (!fileTypes.includes(data.contentType.split("/")[1])) return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
@ -32,14 +36,19 @@ export async function action({ request, params }) {
|
||||||
let imageName;
|
let imageName;
|
||||||
let multiPartformdata;
|
let multiPartformdata;
|
||||||
try {
|
try {
|
||||||
multiPartformdata = await unstable_parseMultipartFormData(request, fileUploadHandler);
|
multiPartformdata = await unstable_parseMultipartFormData(
|
||||||
multiPartformdata.get("image") !== null ? imageName = multiPartformdata.get("image").name : imageName = null;
|
request,
|
||||||
|
fileUploadHandler
|
||||||
|
);
|
||||||
|
multiPartformdata.get("image") !== null
|
||||||
|
? (imageName = multiPartformdata.get("image").name)
|
||||||
|
: (imageName = null);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
errors.image = "Image size too big";
|
errors.image = "Image size too big";
|
||||||
};
|
}
|
||||||
if (typeof post !== "string" || post.length > 50 || post.length < 3) {
|
if (typeof post !== "string" || post.length > 50 || post.length < 3) {
|
||||||
errors.post = "Post too long or short";
|
errors.post = "Post too long or short";
|
||||||
};
|
}
|
||||||
|
|
||||||
if (replying !== "") {
|
if (replying !== "") {
|
||||||
let currentThreadreplyids = [];
|
let currentThreadreplyids = [];
|
||||||
|
@ -48,19 +57,23 @@ export async function action({ request, params }) {
|
||||||
id: parseInt(threadId),
|
id: parseInt(threadId),
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
posts: true
|
posts: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
currentThread.posts.map(post => currentThreadreplyids.push(post.id));
|
currentThread.posts.map((post) => currentThreadreplyids.push(post.id));
|
||||||
|
|
||||||
if (typeof replying !== "string" || !parseInt(replying) || !currentThreadreplyids.includes(parseInt(replying))) {
|
if (
|
||||||
errors.replying = "bad reply id"
|
typeof replying !== "string" ||
|
||||||
};
|
!parseInt(replying) ||
|
||||||
};
|
!currentThreadreplyids.includes(parseInt(replying))
|
||||||
|
) {
|
||||||
|
errors.replying = "bad reply id";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (Object.keys(errors).length) {
|
if (Object.keys(errors).length) {
|
||||||
return json(errors, { status: 422 });
|
return json(errors, { status: 422 });
|
||||||
};
|
}
|
||||||
|
|
||||||
const createPost = await prisma.post.create({
|
const createPost = await prisma.post.create({
|
||||||
data: {
|
data: {
|
||||||
|
@ -72,11 +85,11 @@ export async function action({ request, params }) {
|
||||||
});
|
});
|
||||||
|
|
||||||
return redirect(`/threads/${threadId}`);
|
return redirect(`/threads/${threadId}`);
|
||||||
};
|
}
|
||||||
|
|
||||||
export async function loader({ params }) {
|
export async function loader({ params }) {
|
||||||
const threadId = params.threadId;
|
const threadId = params.threadId;
|
||||||
if (!parseInt(threadId)) throw new Error('Bad route parameter');
|
if (!parseInt(threadId)) throw new Error("Bad route parameter");
|
||||||
|
|
||||||
const thread = await prisma.thread.findUnique({
|
const thread = await prisma.thread.findUnique({
|
||||||
where: {
|
where: {
|
||||||
|
@ -92,7 +105,7 @@ export async function loader({ params }) {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!thread) {
|
if (!thread) {
|
||||||
throw new Error('Thread not found')
|
throw new Error("Thread not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
return thread;
|
return thread;
|
||||||
|
@ -106,72 +119,184 @@ export default function Thread() {
|
||||||
return (
|
return (
|
||||||
<Overlay>
|
<Overlay>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div id="OP" className="bg-ctp-crust w-fit flex flex-col shadow-lg border-4 rounded p-4">
|
<div
|
||||||
<span><strong>{data.title}</strong> Post id: <strong>{data.id}</strong> Created at: <strong>{new Date(data.createdAt).toLocaleString()}</strong> Reply count: <strong>{data.posts.length}</strong></span>
|
id="OP"
|
||||||
|
className="bg-ctp-crust w-fit flex flex-col shadow-lg border-4 rounded p-4"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
<strong>{data.title}</strong> Post id: <strong>{data.id}</strong>{" "}
|
||||||
|
Created at:{" "}
|
||||||
|
<strong>{new Date(data.createdAt).toLocaleString()}</strong> Reply
|
||||||
|
count: <strong>{data.posts.length}</strong>
|
||||||
|
</span>
|
||||||
<br />
|
<br />
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<img className="w-60 max-h-96" src={`/uploads/${data.imageName}`} alt="post image" />
|
<img
|
||||||
<Link className=" text-xs text-ctp-surface0" to={`/uploads/${data.imageName}`} target="_blank" referrerPolicy="no-referrer">Click to show full image</Link>
|
className="w-60 max-h-96"
|
||||||
|
src={`/uploads/${data.imageName}`}
|
||||||
|
alt="post image"
|
||||||
|
/>
|
||||||
|
<Link
|
||||||
|
className=" text-xs text-ctp-surface0"
|
||||||
|
to={`/uploads/${data.imageName}`}
|
||||||
|
target="_blank"
|
||||||
|
referrerPolicy="no-referrer"
|
||||||
|
>
|
||||||
|
Click to show full image
|
||||||
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<p className="">{data.post}</p>
|
<p className="">{data.post}</p>
|
||||||
</div>
|
</div>
|
||||||
<ul className="m-8 ">
|
<ul className="m-8 ">
|
||||||
{data.posts.map(post =>
|
{data.posts.map((post) => (
|
||||||
<li id={`${post.id}`} className="rounded w-fit shadow p-4 m-4 odd:bg-ctp-mantle border even:bg-ctp-crust" key={post.id}>
|
<li
|
||||||
|
id={`${post.id}`}
|
||||||
|
className="rounded w-fit shadow p-4 m-4 odd:bg-ctp-mantle border even:bg-ctp-crust"
|
||||||
|
key={post.id}
|
||||||
|
>
|
||||||
<div className="flex flex-wrap">
|
<div className="flex flex-wrap">
|
||||||
<span>Reply id: <strong>{post.id}</strong> Replied at: <strong>{new Date(post.createdAt).toLocaleString()} </strong></span>
|
<span>
|
||||||
<Link className="mx-2 text-ctp-rosewater hover:text-ctp-maroon hover:underline" onClick={(event) => {
|
Reply id: <strong>{post.id}</strong> Replied at:{" "}
|
||||||
event.preventDefault();
|
<strong>{new Date(post.createdAt).toLocaleString()} </strong>
|
||||||
document.getElementById(`bottom`).scrollIntoView(true);
|
</span>
|
||||||
setReplying(post.id);
|
<Link
|
||||||
}} to={`#bottom`}
|
className="mx-2 text-ctp-rosewater hover:text-ctp-maroon hover:underline"
|
||||||
>Reply
|
onClick={(event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
document.getElementById(`bottom`).scrollIntoView(true);
|
||||||
|
setReplying(post.id);
|
||||||
|
}}
|
||||||
|
to={`#bottom`}
|
||||||
|
>
|
||||||
|
Reply
|
||||||
</Link>
|
</Link>
|
||||||
<ul className="flex flex-wrap space-x-1">
|
<ul className="flex flex-wrap space-x-1">
|
||||||
{post?.replies?.map(reply =>
|
{post?.replies?.map((reply) => (
|
||||||
<li key={reply.id}>
|
<li key={reply.id}>
|
||||||
<Link onClick={event => {
|
<Link
|
||||||
event.preventDefault();
|
onClick={(event) => {
|
||||||
document.getElementById(`${reply.id}`).scrollIntoView(true);
|
event.preventDefault();
|
||||||
}} className="text-ctp-teal hover:text-ctp-sky hover:underline" to={`#${reply.id}`}>#{reply.id}</Link>
|
document
|
||||||
</li>
|
.getElementById(`${reply.id}`)
|
||||||
)}
|
.scrollIntoView(true);
|
||||||
|
}}
|
||||||
|
className="text-ctp-teal hover:text-ctp-sky hover:underline"
|
||||||
|
to={`#${reply.id}`}
|
||||||
|
>
|
||||||
|
#{reply.id}
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
{post.replyingTo ? <Link onClick={event => {
|
{post.replyingTo ? (
|
||||||
event.preventDefault();
|
<Link
|
||||||
document.getElementById(`${post.replyingTo}`).scrollIntoView(true);
|
onClick={(event) => {
|
||||||
}} className="font-semibold text-sm hover:underline" to={`#${post.replyingTo}`}>Replying to id: {post.replyingTo}</Link> : ''}
|
event.preventDefault();
|
||||||
{post.imageName !== null ?
|
document
|
||||||
<div className="flex flex-col">
|
.getElementById(`${post.replyingTo}`)
|
||||||
<img className="w-60 max-h-96" src={`/uploads/${post.imageName}`} alt="post image" />
|
.scrollIntoView(true);
|
||||||
<Link className=" text-xs text-ctp-surface0" to={`/uploads/${post.imageName}`} target="_blank" referrerPolicy="no-referrer">Click to show full image</Link>
|
}}
|
||||||
</div> : ''}
|
className="font-semibold text-sm hover:underline"
|
||||||
|
to={`#${post.replyingTo}`}
|
||||||
|
>
|
||||||
|
Replying to id: {post.replyingTo}
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
)}
|
||||||
|
{post.imageName !== null ? (
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<img
|
||||||
|
className="w-60 max-h-96"
|
||||||
|
src={`/uploads/${post.imageName}`}
|
||||||
|
alt="post image"
|
||||||
|
/>
|
||||||
|
<Link
|
||||||
|
className=" text-xs text-ctp-surface0"
|
||||||
|
to={`/uploads/${post.imageName}`}
|
||||||
|
target="_blank"
|
||||||
|
referrerPolicy="no-referrer"
|
||||||
|
>
|
||||||
|
Click to show full image
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
)}
|
||||||
<p>{post.comment}</p>
|
<p>{post.comment}</p>
|
||||||
</li>
|
</li>
|
||||||
)}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<Form id="bottom" className="space-y-4 flex flex-col items-center" method="post" encType="multipart/form-data">
|
<Form
|
||||||
|
id="bottom"
|
||||||
|
className="space-y-4 flex flex-col items-center"
|
||||||
|
method="post"
|
||||||
|
encType="multipart/form-data"
|
||||||
|
>
|
||||||
<label className="flex flex-col" htmlFor="text-input">
|
<label className="flex flex-col" htmlFor="text-input">
|
||||||
{replying ? <p className="text-center font-semibold text-ctp-black" >Replying to reply id: {replying} <button className="text-ctp-sky hover:text-ctp-teal hover:underline" onClick={() => setReplying('')}>Reset</button></p> : ''}
|
{replying ? (
|
||||||
<span className="font-semibold text-center text-ctp-subtext0" >Post</span> <textarea className=" bg-ctp-surface0 m-1 p-1 rounded shadow shadow-ctp-overlay0" required minLength={3} name="post" type="text" />
|
<p className="text-center font-semibold text-ctp-black">
|
||||||
{actionData ? <p>{actionData.post}</p> : ''}
|
Replying to reply id: {replying}{" "}
|
||||||
|
<button
|
||||||
|
className="text-ctp-sky hover:text-ctp-teal hover:underline"
|
||||||
|
onClick={() => setReplying("")}
|
||||||
|
>
|
||||||
|
Reset
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
)}
|
||||||
|
<span className="font-semibold text-center text-ctp-subtext0">
|
||||||
|
Post
|
||||||
|
</span>{" "}
|
||||||
|
<textarea
|
||||||
|
className=" bg-ctp-surface0 m-1 p-1 rounded shadow shadow-ctp-overlay0"
|
||||||
|
required
|
||||||
|
minLength={3}
|
||||||
|
name="post"
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
{actionData ? <p>{actionData.post}</p> : ""}
|
||||||
</label>
|
</label>
|
||||||
<label className="flex flex-col" htmlFor="image-upload"><span className="font-semibold text-center text-ctp-subtext0">Image (500kbps max)</span>
|
<label className="flex flex-col" htmlFor="image-upload">
|
||||||
<input className="block w-full px-3 py-1.5 text-base font-semibold text-ctp-text bg-ctp-surface0 bg-clip-padding rounded transition ease-in-out m-0" accept="image/*" type="file" id="image-upload" name="image" />
|
<span className="font-semibold text-center text-ctp-subtext0">
|
||||||
|
Image (500kbps max)
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
className="block w-full px-3 py-1.5 text-base font-semibold text-ctp-text bg-ctp-surface0 bg-clip-padding rounded transition ease-in-out m-0"
|
||||||
|
accept="image/*"
|
||||||
|
type="file"
|
||||||
|
id="image-upload"
|
||||||
|
name="image"
|
||||||
|
/>
|
||||||
<p>{actionData?.image}</p>
|
<p>{actionData?.image}</p>
|
||||||
</label>
|
</label>
|
||||||
<details class="flex items-center justify-center duration-300">
|
<details class="flex items-center justify-center duration-300">
|
||||||
<summary className="text-center text-ctp-subtext0 font-semibold cursor-pointer">Manual replying (needed when JS is disabled)</summary>
|
<summary className="text-center text-ctp-subtext0 font-semibold cursor-pointer">
|
||||||
<div class="flex flex-col items-center justify-center">
|
Manual replying (needed when JS is disabled)
|
||||||
<input className="bg-ctp-surface0 m-1 p-1 rounded shadow shadow-ctp-overlay0" placeholder="Enter a Reply ID" type="number" name="replying" value={replying} />
|
</summary>
|
||||||
</div>
|
<div class="flex flex-col items-center justify-center">
|
||||||
|
<input
|
||||||
|
className="bg-ctp-surface0 m-1 p-1 rounded shadow shadow-ctp-overlay0"
|
||||||
|
placeholder="Enter a Reply ID"
|
||||||
|
type="number"
|
||||||
|
name="replying"
|
||||||
|
value={replying}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</details>
|
</details>
|
||||||
<p className="text-ctp-red">{actionData?.replying}</p>
|
<p className="text-ctp-red">{actionData?.replying}</p>
|
||||||
|
|
||||||
<button className="bg-ctp-crust px-4 py-2 shadow rounded-full" type="submit">Submit</button>
|
<button
|
||||||
|
className="bg-ctp-crust px-4 py-2 shadow rounded-full"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</button>
|
||||||
</Form>
|
</Form>
|
||||||
</Overlay>
|
</Overlay>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -11,4 +11,4 @@ if (process.env.NODE_ENV === "production") {
|
||||||
prisma = global.prisma;
|
prisma = global.prisma;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default prisma;
|
export default prisma;
|
||||||
|
|
Loading…
Reference in New Issue