"use client"; import { Avatar, Button, Card, CardBody, Chip, Dropdown, DropdownItem, DropdownMenu, DropdownSection, DropdownTrigger, Spacer, } from "@nextui-org/react"; import { formatDistance } from "date-fns"; import Link from "next/link"; import { PostType } from "@/types/PostType"; import { Flag, MessageCircle, Minus, MoreVertical, Plus, Shield, ShieldAlert, ShieldX, Star, StarOff, Trash, X, } from "lucide-react"; import LikeButton from "./LikeButton"; import { PostStyle } from "@/types/PostStyle"; import { UserType } from "@/types/UserType"; import { useEffect, useState } from "react"; import { getCookie } from "@/helpers/cookie"; import { toast } from "react-toastify"; import { TagType } from "@/types/TagType"; export default function PostCard({ post, style, user, }: { post: PostType; style: PostStyle; user?: UserType; }) { const [minimized, setMinimized] = useState<boolean>(false); const [hidden, setHidden] = useState<boolean>(false); const [reduceMotion, setReduceMotion] = useState<boolean>(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); }; }, []); return ( <Card className="bg-opacity-60 !duration-250 !ease-linear !transition-all" style={{ display: hidden ? "none" : "flex" }} > <CardBody className="p-5"> {(style == "cozy" || style == "adaptive") && (minimized ? ( <div className="flex items-center justify-between"> <div className="flex items-center gap-4"> <Link href={`/p/${post.slug}`}> <p>{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> </div> <Button size="sm" variant="light" isIconOnly onPress={() => setMinimized(false)} > <Plus size={16} /> </Button> </div> ) : ( <div> <div className="flex justify-between items-center"> <Link href={`/p/${post.slug}`}> <p className="text-2xl">{post.title}</p> </Link> <Button size="sm" variant="light" isIconOnly onPress={() => setMinimized(true)} > <Minus size={16} /> </Button> </div> <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 post={post} /> <Button size="sm" variant="bordered" onPress={() => { toast.warning("Comment functionality coming soon"); }} > <MessageCircle size={16} /> {0} </Button> <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"); setHidden(true); } 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"); setHidden(true); } 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"); window.location.reload(); } 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("Stickied post"); window.location.reload(); } 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" > Appoint as mod </DropdownItem> ) : ( <></> )} {user?.admin && post.author.mod && post.author.id !== user.id ? ( <DropdownItem key="demote-mod" startContent={<ShieldX />} description="Demote user from Mod" > Remove as mod </DropdownItem> ) : ( <></> )} {user?.admin && !post.author.admin ? ( <DropdownItem key="promote-admin" startContent={<ShieldAlert />} description="Promote user to Admin" > Appoint as admin </DropdownItem> ) : ( <></> )} </DropdownSection> ) : ( <></> )} </DropdownMenu> </Dropdown> </div> </div> ))} {style == "compact" && ( <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> </div> )} {style == "ultra" && ( <div className="flex items-center gap-4"> <Link href={`/p/${post.slug}`}> <p>{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> </div> )} </CardBody> </Card> ); }