mirror of
https://github.com/Ategon/Jamjar.git
synced 2025-02-12 06:16:21 +00:00
483 lines
18 KiB
TypeScript
483 lines
18 KiB
TypeScript
"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>
|
|
);
|
|
}
|