"use client"; import Editor from "@/components/editor"; import { getCookie, hasCookie } from "@/helpers/cookie"; import { Avatar, Button, Checkbox, Form, Input, Spacer, } from "@nextui-org/react"; import { LoaderCircle } from "lucide-react"; import { redirect } from "next/navigation"; import { ReactNode, useEffect, useState } from "react"; import { toast } from "react-toastify"; import sanitizeHtml from "sanitize-html"; import Select, { MultiValue, StylesConfig } from "react-select"; import { useTheme } from "next-themes"; import { UserType } from "@/types/UserType"; export default function CreatePostPage() { const [title, setTitle] = useState(""); const [content, setContent] = useState(""); const [errors, setErrors] = useState({}); const [waitingPost, setWaitingPost] = useState(false); const [selectedTags, setSelectedTags] = useState<MultiValue<{ value: string; label: ReactNode; isFixed: boolean; }> | null>(null); const [mounted, setMounted] = useState<boolean>(false); const [options, setOptions] = useState< { value: string; label: ReactNode; id: number; isFixed: boolean; }[] >(); const { theme } = useTheme(); const [user, setUser] = useState<UserType>(); const [sticky, setSticky] = useState(false); useEffect(() => { setMounted(true); const load = async () => { const response = await fetch( process.env.NEXT_PUBLIC_MODE === "PROD" ? `https://d2jam.com/api/v1/self?username=${getCookie("user")}` : `http://localhost:3005/api/v1/self?username=${getCookie("user")}`, { headers: { authorization: `Bearer ${getCookie("token")}` }, credentials: "include", } ); const localuser = await response.json(); setUser(localuser); const tagResponse = await fetch( process.env.NEXT_PUBLIC_MODE === "PROD" ? `https://d2jam.com/api/v1/tags` : `http://localhost:3005/api/v1/tags` ); if (tagResponse.ok) { const newoptions: { value: string; label: ReactNode; id: number; isFixed: boolean; }[] = []; for (const tag of await tagResponse.json()) { if (tag.modOnly && !localuser.mod) { continue; } newoptions.push({ value: tag.name, id: tag.id, label: ( <div className="flex gap-2 items-center"> {tag.icon && ( <Avatar className="w-6 h-6 min-w-6 min-h-6" size="sm" src={tag.icon} classNames={{ base: "bg-transparent" }} /> )} <p> {tag.name} {tag.modOnly ? " (Mod Only)" : ""} </p> </div> ), isFixed: tag.alwaysAdded, }); } setOptions(newoptions); } }; load(); }, []); const styles: StylesConfig< { value: string; label: ReactNode; isFixed: boolean; }, true > = { multiValue: (base, state) => { return { ...base, backgroundColor: state.data.isFixed ? theme == "dark" ? "#222" : "#ddd" : theme == "dark" ? "#444" : "#eee", }; }, multiValueLabel: (base, state) => { return { ...base, color: state.data.isFixed ? theme == "dark" ? "#ddd" : "#222" : theme == "dark" ? "#fff" : "#444", fontWeight: state.data.isFixed ? "normal" : "bold", paddingRight: state.data.isFixed ? "8px" : "2px", }; }, multiValueRemove: (base, state) => { return { ...base, display: state.data.isFixed ? "none" : "flex", color: theme == "dark" ? "#ddd" : "#222", }; }, control: (styles) => ({ ...styles, backgroundColor: theme == "dark" ? "#181818" : "#fff", minWidth: "300px", }), menu: (styles) => ({ ...styles, backgroundColor: theme == "dark" ? "#181818" : "#fff", color: theme == "dark" ? "#fff" : "#444", }), option: (styles, { isFocused }) => ({ ...styles, backgroundColor: isFocused ? theme == "dark" ? "#333" : "#ddd" : undefined, }), }; return ( <div className="absolute flex items-center justify-center top-0 left-0 w-screen h-screen"> <Form className="w-full max-w-2xl flex flex-col gap-4" validationErrors={errors} onSubmit={async (e) => { e.preventDefault(); if (!title && !content) { setErrors({ title: "Please enter a valid title", content: "Please enter valid content", }); toast.error("Please enter valid content"); return; } if (!title) { setErrors({ title: "Please enter a valid title" }); return; } if (!content) { setErrors({ content: "Please enter valid content" }); toast.error("Please enter valid content"); return; } if (!hasCookie("token")) { setErrors({ content: "You are not logged in" }); return; } const sanitizedHtml = sanitizeHtml(content); setWaitingPost(true); const tags = []; if (selectedTags) { for (const tag of selectedTags) { tags.push( options?.filter((option) => option.value == tag.value)[0].id ); } } const response = await fetch( process.env.NEXT_PUBLIC_MODE === "PROD" ? "https://d2jam.com/api/v1/post" : "http://localhost:3005/api/v1/post", { body: JSON.stringify({ title: title, content: sanitizedHtml, sticky, username: getCookie("user"), tags: [ ...tags, ...(options ? options.filter((tag) => tag.isFixed).map((tag) => tag.id) : []), ], }), method: "POST", headers: { "Content-Type": "application/json", authorization: `Bearer ${getCookie("token")}`, }, credentials: "include", } ); if (response.status == 401) { setErrors({ content: "Invalid user" }); setWaitingPost(false); return; } if (response.ok) { toast.success("Successfully created post"); setWaitingPost(false); redirect("/"); } else { toast.error("An error occured"); setWaitingPost(false); } }} > <Input isRequired label="Title" labelPlacement="outside" name="title" placeholder="Enter a title" type="text" value={title} onValueChange={setTitle} /> <Editor content={content} setContent={setContent} /> <Spacer /> {mounted && ( <Select styles={styles} isMulti value={selectedTags} onChange={(value) => setSelectedTags(value)} options={options} isClearable={false} isOptionDisabled={() => selectedTags != null && selectedTags.length >= 5 } /> )} {user && user.mod && ( <div> <Spacer /> <Checkbox isSelected={sticky} onValueChange={setSticky}> Sticky </Checkbox> </div> )} <Spacer /> <div className="flex gap-2"> <Button color="primary" type="submit"> {waitingPost ? ( <LoaderCircle className="animate-spin" size={16} /> ) : ( <p>Create</p> )} </Button> </div> </Form> </div> ); }