import { redirect } from "@remix-run/node"; import { unstable_createFileUploadHandler, unstable_parseMultipartFormData, json, } from "@remix-run/node"; import { useLoaderData, Form, useActionData, Link, useTransition, } from "@remix-run/react"; import { useEffect, useState } from "react"; import Overlay from "~/components/Overlay"; import { ThreadReply } from "~/components/ThreadReply"; import prisma from "~/utils/db.server"; import { useNavigate } from "@remix-run/react"; import { useRevalidator } from "@remix-run/react"; export async function action({ request, params }) { const threadId = params.threadId; if (!parseInt(threadId)) throw new Error("Bad route parameter"); const clonedData = request.clone(); const formData = await clonedData.formData(); const post = formData.get("post"); const replying = formData.get("replying"); const fileUploadHandler = unstable_createFileUploadHandler({ directory: "./public/uploads/", maxPartSize: 500000, file: ({ filename }) => { if (formData.get("anonymize")) return `${require("crypto").randomBytes(16).toString("hex")}.${ filename.split(".").slice(-1)[0] }`; return filename; }, filter: (data) => { const fileTypes = ["jpeg", "jpg", "png", "gif"]; // if sent file is not an image, don't handle it if (!fileTypes.includes(data.contentType.split("/")[1])) return false; return true; }, }); const errors = {}; let imageName; let multiPartformdata; try { multiPartformdata = await unstable_parseMultipartFormData( request, fileUploadHandler ); multiPartformdata.get("image") !== null ? (imageName = multiPartformdata.get("image").name) : (imageName = null); } catch (err) { errors.image = "Image size too big"; } if (typeof post !== "string" || post.length > 50 || post.length < 3) { errors.post = "Post too long or short"; } if (replying !== "") { let currentThreadreplyids = []; const currentThread = await prisma.thread.findUnique({ where: { id: parseInt(threadId), }, include: { posts: true, }, }); currentThread.posts.map((post) => currentThreadreplyids.push(post.id)); if ( typeof replying !== "string" || !parseInt(replying) || !currentThreadreplyids.includes(parseInt(replying)) ) { errors.replying = "bad reply id"; } } if (Object.keys(errors).length) { return json(errors, { status: 422 }); } const createPost = await prisma.post.create({ data: { comment: post, imageName: imageName, replyingTo: replying ? parseInt(replying) : null, postId: parseInt(threadId), }, }); return createPost; } export async function loader({ params }) { const threadId = params.threadId; if (!parseInt(threadId)) throw new Error("Bad route parameter"); const thread = await prisma.thread.findUnique({ where: { id: parseInt(threadId), }, include: { posts: { include: { replies: true, }, }, }, }); if (!thread) { throw new Error("Thread not found"); } return thread; } export default function Thread() { const data = useLoaderData(); const actionData = useActionData(); const transition = useTransition(); const [replying, setReplying] = useState(); const revalidator = useRevalidator(); useEffect(() => { const timer = setInterval(() => { revalidator.revalidate(); }, 5000); return () => clearInterval(timer); }, []); return (
{ event.preventDefault(); document.getElementById(`bottom`).scrollIntoView(true); }} to={`/threads/${data.id}#bottom`} > Bottom { event.preventDefault(); document.getElementById(`top`).scrollIntoView(true); }} to={`/threads/${data.id}#top`} > Top

{revalidator.state === "loading" ? "Updating..." : ""}

{data.title} Post id: {data.id}{" "} Created at:{" "} {new Date(data.createdAt).toLocaleTimeString()}{" "} Reply count: {data.posts.length}
post image Click to show full image

{data.post}