"use client"; import LikeButton from "@/components/posts/LikeButton"; import { getCookie, hasCookie } from "@/helpers/cookie"; import { PostType } from "@/types/PostType"; import { TagType } from "@/types/TagType"; import { UserType } from "@/types/UserType"; import Link from "next/link"; import { Avatar, Button, Card, CardBody, Chip, Dropdown, DropdownItem, DropdownMenu, DropdownSection, DropdownTrigger, Spacer, } from "@nextui-org/react"; import { formatDistance } from "date-fns"; import { Flag, LoaderCircle, MessageCircle, MoreVertical, Shield, ShieldAlert, ShieldX, Star, StarOff, Trash, X, } from "lucide-react"; import { redirect, useParams } from "next/navigation"; import { useEffect, useState } from "react"; import { toast } from "react-toastify"; import Editor from "@/components/editor"; import sanitizeHtml from "sanitize-html"; import CommentCard from "@/components/posts/CommentCard"; export default function PostPage() { const [post, setPost] = useState<PostType>(); const { slug } = useParams(); const [reduceMotion, setReduceMotion] = useState<boolean>(false); const [user, setUser] = useState<UserType>(); const [loading, setLoading] = useState<boolean>(true); const [content, setContent] = useState(""); const [waitingPost, setWaitingPost] = useState(false); useEffect(() => { const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)"); setReduceMotion(mediaQuery.matches); const handleChange = (event: MediaQueryListEvent) => { setReduceMotion(event.matches); }; mediaQuery.addEventListener("change", handleChange); return () => { mediaQuery.removeEventListener("change", handleChange); }; }, []); useEffect(() => { const loadUserAndPosts = async () => { setLoading(true); // Fetch the user const userResponse = 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", } ); if (userResponse.ok) { const userData = await userResponse.json(); setUser(userData); const postResponse = await fetch( process.env.NEXT_PUBLIC_MODE === "PROD" ? `https://d2jam.com/api/v1/post?slug=${slug}&user=${userData.slug}` : `http://localhost:3005/api/v1/post?slug=${slug}&user=${userData.slug}` ); setPost(await postResponse.json()); setLoading(false); } else { setUser(undefined); const postResponse = await fetch( process.env.NEXT_PUBLIC_MODE === "PROD" ? `https://d2jam.com/api/v1/post?slug=${slug}` : `http://localhost:3005/api/v1/post?slug=${slug}` ); setPost(await postResponse.json()); setLoading(false); } }; loadUserAndPosts(); }, [slug]); return ( <> {loading ? ( <div className="flex justify-center p-6"> <LoaderCircle className="animate-spin text-[#333] dark:text-[#999]" size={24} /> </div> ) : ( <> <Card className="bg-opacity-60 !duration-250 !ease-linear !transition-all"> <CardBody className="p-5"> <div> {post && ( <div> <Link href={`/p/${post.slug}`}> <p className="text-2xl">{post.title}</p> </Link> <div className="flex items-center gap-3 text-xs text-default-500 pt-1"> <p>By</p> <Link href={`/u/${post.author.slug}`} className="flex items-center gap-2" > <Avatar size="sm" className="w-6 h-6" src={post.author.profilePicture} classNames={{ base: "bg-transparent", }} /> <p>{post.author.name}</p> </Link> <p> {formatDistance(new Date(post.createdAt), new Date(), { addSuffix: true, })} </p> </div> <Spacer y={4} /> <div className="prose dark:prose-invert !duration-250 !ease-linear !transition-all" dangerouslySetInnerHTML={{ __html: post.content }} /> <Spacer y={4} /> {post.tags.filter((tag) => tag.name != "D2Jam").length > 0 ? ( <div className="flex gap-1"> {post.tags .filter((tag) => tag.name != "D2Jam") .map((tag: TagType) => ( <Link href="/" key={tag.id} className={`transition-all transform duration-500 ease-in-out ${ !reduceMotion ? "hover:scale-110" : "" }`} > <Chip radius="sm" size="sm" className="!duration-250 !ease-linear !transition-all" variant="faded" avatar={ tag.icon && ( <Avatar src={tag.icon} classNames={{ base: "bg-transparent" }} /> ) } > {tag.name} </Chip> </Link> ))} </div> ) : ( <></> )} {post.tags.length > 0 && <Spacer y={4} />} <div className="flex gap-3"> <LikeButton likes={post.likes.length} liked={post.hasLiked} parentId={post.id} /> <Link href="#create-comment"> <Button size="sm" variant="bordered"> <MessageCircle size={16} /> {post.comments.length} </Button> </Link> <Dropdown backdrop="opaque"> <DropdownTrigger> <Button size="sm" variant="bordered" isIconOnly> <MoreVertical size={16} /> </Button> </DropdownTrigger> <DropdownMenu className="text-[#333] dark:text-white"> <DropdownSection showDivider={user?.mod} title="Actions" > <DropdownItem key="report" startContent={<Flag />} description="Report this post to moderators to handle" onPress={() => { toast.warning( "Report functionality coming soon" ); }} > Create Report </DropdownItem> {user?.slug == post.author.slug ? ( <DropdownItem key="delete" startContent={<Trash />} description="Delete your post" onPress={async () => { 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({ postId: post.id, username: getCookie("user"), }), method: "DELETE", headers: { "Content-Type": "application/json", authorization: `Bearer ${getCookie( "token" )}`, }, credentials: "include", } ); if (response.ok) { toast.success("Deleted post"); redirect("/"); } else { toast.error("Error while deleting post"); } }} > Delete </DropdownItem> ) : ( <></> )} </DropdownSection> {user?.mod ? ( <DropdownSection title="Mod Zone"> <DropdownItem key="remove" startContent={<X />} description="Remove this post" onPress={async () => { 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({ postId: post.id, username: getCookie("user"), }), method: "DELETE", headers: { "Content-Type": "application/json", authorization: `Bearer ${getCookie( "token" )}`, }, credentials: "include", } ); if (response.ok) { toast.success("Removed post"); redirect("/"); } else { toast.error("Error while removing post"); } }} > Remove </DropdownItem> {post.sticky ? ( <DropdownItem key="unsticky" startContent={<StarOff />} description="Unsticky post" onPress={async () => { const response = await fetch( process.env.NEXT_PUBLIC_MODE === "PROD" ? "https://d2jam.com/api/v1/post/sticky" : "http://localhost:3005/api/v1/post/sticky", { body: JSON.stringify({ postId: post.id, sticky: false, username: getCookie("user"), }), method: "POST", headers: { "Content-Type": "application/json", authorization: `Bearer ${getCookie( "token" )}`, }, credentials: "include", } ); if (response.ok) { toast.success("Unsticked post"); redirect("/"); } else { toast.error("Error while removing post"); } }} > Unsticky </DropdownItem> ) : ( <DropdownItem key="sticky" startContent={<Star />} description="Sticky post" onPress={async () => { const response = await fetch( process.env.NEXT_PUBLIC_MODE === "PROD" ? "https://d2jam.com/api/v1/post/sticky" : "http://localhost:3005/api/v1/post/sticky", { body: JSON.stringify({ postId: post.id, sticky: true, username: getCookie("user"), }), method: "POST", headers: { "Content-Type": "application/json", authorization: `Bearer ${getCookie( "token" )}`, }, credentials: "include", } ); if (response.ok) { toast.success("Unsticked post"); redirect("/"); } else { toast.error("Error while removing post"); } }} > Sticky </DropdownItem> )} {user?.admin && !post.author.mod ? ( <DropdownItem key="promote-mod" startContent={<Shield />} description="Promote user to Mod" onPress={async () => { const response = await fetch( process.env.NEXT_PUBLIC_MODE === "PROD" ? "https://d2jam.com/api/v1/mod" : "http://localhost:3005/api/v1/mod", { body: JSON.stringify({ targetname: post.author.slug, mod: true, username: getCookie("user"), }), method: "POST", headers: { "Content-Type": "application/json", authorization: `Bearer ${getCookie( "token" )}`, }, credentials: "include", } ); if (response.ok) { toast.success("Promoted User to Mod"); window.location.reload(); } else { toast.error( "Error while promoting user to Mod" ); } }} > Appoint as mod </DropdownItem> ) : ( <></> )} {user?.admin && post.author.mod && !post.author.admin ? ( <DropdownItem key="demote-mod" startContent={<ShieldX />} description="Demote user from Mod" onPress={async () => { const response = await fetch( process.env.NEXT_PUBLIC_MODE === "PROD" ? "https://d2jam.com/api/v1/mod" : "http://localhost:3005/api/v1/mod", { body: JSON.stringify({ targetname: post.author.slug, username: getCookie("user"), }), method: "POST", headers: { "Content-Type": "application/json", authorization: `Bearer ${getCookie( "token" )}`, }, credentials: "include", } ); if (response.ok) { toast.success("Demoted User"); window.location.reload(); } else { toast.error("Error while demoting user"); } }} > Remove as mod </DropdownItem> ) : ( <></> )} {user?.admin && !post.author.admin ? ( <DropdownItem key="promote-admin" startContent={<ShieldAlert />} description="Promote user to Admin" onPress={async () => { const response = await fetch( process.env.NEXT_PUBLIC_MODE === "PROD" ? "https://d2jam.com/api/v1/mod" : "http://localhost:3005/api/v1/mod", { body: JSON.stringify({ targetname: post.author.slug, admin: true, username: getCookie("user"), }), method: "POST", headers: { "Content-Type": "application/json", authorization: `Bearer ${getCookie( "token" )}`, }, credentials: "include", } ); if (response.ok) { toast.success("Promoted User to Admin"); window.location.reload(); } else { toast.error( "Error while promoting user to Admin" ); } }} > Appoint as admin </DropdownItem> ) : ( <></> )} {user?.admin && post.author.admin && post.author.id !== user.id ? ( <DropdownItem key="demote-admin" startContent={<ShieldX />} description="Demote user to mod" onPress={async () => { const response = await fetch( process.env.NEXT_PUBLIC_MODE === "PROD" ? "https://d2jam.com/api/v1/mod" : "http://localhost:3005/api/v1/mod", { body: JSON.stringify({ targetname: post.author.slug, mod: true, username: getCookie("user"), }), method: "POST", headers: { "Content-Type": "application/json", authorization: `Bearer ${getCookie( "token" )}`, }, credentials: "include", } ); if (response.ok) { toast.success("Demoted User to Mod"); window.location.reload(); } else { toast.error( "Error while demoting user to mod" ); } }} > Remove as admin </DropdownItem> ) : ( <></> )} </DropdownSection> ) : ( <></> )} </DropdownMenu> </Dropdown> </div> </div> )} </div> </CardBody> </Card> <div id="create-comment" /> <Spacer y={10} /> <Editor content={content} setContent={setContent} /> <Spacer /> <Button color="primary" onPress={async () => { if (!content) { toast.error("Please enter valid content"); return; } if (!hasCookie("token")) { toast.error("You are not logged in"); return; } const sanitizedHtml = sanitizeHtml(content); setWaitingPost(true); const response = await fetch( process.env.NEXT_PUBLIC_MODE === "PROD" ? "https://d2jam.com/api/v1/comment" : "http://localhost:3005/api/v1/comment", { body: JSON.stringify({ content: sanitizedHtml, username: getCookie("user"), postId: post?.id, }), method: "POST", headers: { "Content-Type": "application/json", authorization: `Bearer ${getCookie("token")}`, }, credentials: "include", } ); if (response.status == 401) { toast.error("Invalid User"); setWaitingPost(false); return; } if (response.ok) { toast.success("Successfully created comment"); setWaitingPost(false); window.location.reload(); } else { toast.error("An error occured"); setWaitingPost(false); } }} > {waitingPost ? ( <LoaderCircle className="animate-spin" size={16} /> ) : ( <p>Create Comment</p> )} </Button> <Spacer y={10} /> <div className="flex flex-col gap-3"> {post?.comments.map((comment) => ( <div key={comment.id}> <CommentCard comment={comment} /> </div> ))} </div> </> )} </> ); }