mirror of
https://github.com/Ategon/Jamjar.git
synced 2025-02-12 06:16:21 +00:00
Add comments
This commit is contained in:
parent
2d6fead452
commit
7eb95b8ce0
6 changed files with 695 additions and 429 deletions
src
app/p/[slug]
components/posts
types
|
@ -1,7 +1,7 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import LikeButton from "@/components/posts/LikeButton";
|
import LikeButton from "@/components/posts/LikeButton";
|
||||||
import { getCookie } from "@/helpers/cookie";
|
import { getCookie, hasCookie } from "@/helpers/cookie";
|
||||||
import { PostType } from "@/types/PostType";
|
import { PostType } from "@/types/PostType";
|
||||||
import { TagType } from "@/types/TagType";
|
import { TagType } from "@/types/TagType";
|
||||||
import { UserType } from "@/types/UserType";
|
import { UserType } from "@/types/UserType";
|
||||||
|
@ -36,6 +36,9 @@ import {
|
||||||
import { redirect, useParams } from "next/navigation";
|
import { redirect, useParams } from "next/navigation";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { toast } from "react-toastify";
|
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() {
|
export default function PostPage() {
|
||||||
const [post, setPost] = useState<PostType>();
|
const [post, setPost] = useState<PostType>();
|
||||||
|
@ -43,6 +46,8 @@ export default function PostPage() {
|
||||||
const [reduceMotion, setReduceMotion] = useState<boolean>(false);
|
const [reduceMotion, setReduceMotion] = useState<boolean>(false);
|
||||||
const [user, setUser] = useState<UserType>();
|
const [user, setUser] = useState<UserType>();
|
||||||
const [loading, setLoading] = useState<boolean>(true);
|
const [loading, setLoading] = useState<boolean>(true);
|
||||||
|
const [content, setContent] = useState("");
|
||||||
|
const [waitingPost, setWaitingPost] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
|
const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
|
||||||
|
@ -110,210 +115,136 @@ export default function PostPage() {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<Card className="bg-opacity-60 !duration-250 !ease-linear !transition-all">
|
<>
|
||||||
<CardBody className="p-5">
|
<Card className="bg-opacity-60 !duration-250 !ease-linear !transition-all">
|
||||||
<div>
|
<CardBody className="p-5">
|
||||||
{post && (
|
<div>
|
||||||
<div>
|
{post && (
|
||||||
<Link href={`/p/${post.slug}`}>
|
<div>
|
||||||
<p className="text-2xl">{post.title}</p>
|
<Link href={`/p/${post.slug}`}>
|
||||||
</Link>
|
<p className="text-2xl">{post.title}</p>
|
||||||
|
|
||||||
<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>
|
</Link>
|
||||||
<p>
|
|
||||||
{formatDistance(new Date(post.createdAt), new Date(), {
|
|
||||||
addSuffix: true,
|
|
||||||
})}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Spacer y={4} />
|
<div className="flex items-center gap-3 text-xs text-default-500 pt-1">
|
||||||
|
<p>By</p>
|
||||||
<div
|
<Link
|
||||||
className="prose dark:prose-invert !duration-250 !ease-linear !transition-all"
|
href={`/u/${post.author.slug}`}
|
||||||
dangerouslySetInnerHTML={{ __html: post.content }}
|
className="flex items-center gap-2"
|
||||||
/>
|
>
|
||||||
|
<Avatar
|
||||||
<Spacer y={4} />
|
size="sm"
|
||||||
|
className="w-6 h-6"
|
||||||
{post.tags.filter((tag) => tag.name != "D2Jam").length > 0 ? (
|
src={post.author.profilePicture}
|
||||||
<div className="flex gap-1">
|
classNames={{
|
||||||
{post.tags
|
base: "bg-transparent",
|
||||||
.filter((tag) => tag.name != "D2Jam")
|
}}
|
||||||
.map((tag: TagType) => (
|
/>
|
||||||
<Link
|
<p>{post.author.name}</p>
|
||||||
href="/"
|
</Link>
|
||||||
key={tag.id}
|
<p>
|
||||||
className={`transition-all transform duration-500 ease-in-out ${
|
{formatDistance(new Date(post.createdAt), new Date(), {
|
||||||
!reduceMotion ? "hover:scale-110" : ""
|
addSuffix: true,
|
||||||
}`}
|
})}
|
||||||
>
|
</p>
|
||||||
<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>
|
</div>
|
||||||
) : (
|
|
||||||
<></>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{post.tags.length > 0 && <Spacer y={4} />}
|
<Spacer y={4} />
|
||||||
|
|
||||||
<div className="flex gap-3">
|
<div
|
||||||
<LikeButton post={post} />
|
className="prose dark:prose-invert !duration-250 !ease-linear !transition-all"
|
||||||
<Button
|
dangerouslySetInnerHTML={{ __html: post.content }}
|
||||||
size="sm"
|
/>
|
||||||
variant="bordered"
|
|
||||||
onPress={() => {
|
<Spacer y={4} />
|
||||||
toast.warning("Comment functionality coming soon");
|
|
||||||
}}
|
{post.tags.filter((tag) => tag.name != "D2Jam").length >
|
||||||
>
|
0 ? (
|
||||||
<MessageCircle size={16} /> {0}
|
<div className="flex gap-1">
|
||||||
</Button>
|
{post.tags
|
||||||
<Dropdown backdrop="opaque">
|
.filter((tag) => tag.name != "D2Jam")
|
||||||
<DropdownTrigger>
|
.map((tag: TagType) => (
|
||||||
<Button size="sm" variant="bordered" isIconOnly>
|
<Link
|
||||||
<MoreVertical size={16} />
|
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>
|
</Button>
|
||||||
</DropdownTrigger>
|
</Link>
|
||||||
<DropdownMenu className="text-[#333] dark:text-white">
|
<Dropdown backdrop="opaque">
|
||||||
<DropdownSection
|
<DropdownTrigger>
|
||||||
showDivider={user?.mod}
|
<Button size="sm" variant="bordered" isIconOnly>
|
||||||
title="Actions"
|
<MoreVertical size={16} />
|
||||||
>
|
</Button>
|
||||||
<DropdownItem
|
</DropdownTrigger>
|
||||||
key="report"
|
<DropdownMenu className="text-[#333] dark:text-white">
|
||||||
startContent={<Flag />}
|
<DropdownSection
|
||||||
description="Report this post to moderators to handle"
|
showDivider={user?.mod}
|
||||||
onPress={() => {
|
title="Actions"
|
||||||
toast.warning("Report functionality coming soon");
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
Create Report
|
|
||||||
</DropdownItem>
|
|
||||||
{user?.slug == post.author.slug ? (
|
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
key="delete"
|
key="report"
|
||||||
startContent={<Trash />}
|
startContent={<Flag />}
|
||||||
description="Delete your post"
|
description="Report this post to moderators to handle"
|
||||||
onPress={async () => {
|
onPress={() => {
|
||||||
const response = await fetch(
|
toast.warning(
|
||||||
process.env.NEXT_PUBLIC_MODE === "PROD"
|
"Report functionality coming soon"
|
||||||
? "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
|
Create Report
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
) : (
|
{user?.slug == post.author.slug ? (
|
||||||
<></>
|
|
||||||
)}
|
|
||||||
</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
|
<DropdownItem
|
||||||
key="unsticky"
|
key="delete"
|
||||||
startContent={<StarOff />}
|
startContent={<Trash />}
|
||||||
description="Unsticky post"
|
description="Delete your post"
|
||||||
onPress={async () => {
|
onPress={async () => {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
process.env.NEXT_PUBLIC_MODE === "PROD"
|
process.env.NEXT_PUBLIC_MODE === "PROD"
|
||||||
? "https://d2jam.com/api/v1/post/sticky"
|
? "https://d2jam.com/api/v1/post"
|
||||||
: "http://localhost:3005/api/v1/post/sticky",
|
: "http://localhost:3005/api/v1/post",
|
||||||
{
|
{
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
postId: post.id,
|
postId: post.id,
|
||||||
sticky: false,
|
|
||||||
username: getCookie("user"),
|
username: getCookie("user"),
|
||||||
}),
|
}),
|
||||||
method: "POST",
|
method: "DELETE",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
authorization: `Bearer ${getCookie(
|
authorization: `Bearer ${getCookie(
|
||||||
|
@ -325,234 +256,383 @@ export default function PostPage() {
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
toast.success("Unsticked post");
|
toast.success("Deleted post");
|
||||||
redirect("/");
|
redirect("/");
|
||||||
} else {
|
} else {
|
||||||
toast.error("Error while removing post");
|
toast.error("Error while deleting post");
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Unsticky
|
Delete
|
||||||
</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>
|
</DropdownItem>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
)}
|
)}
|
||||||
</DropdownSection>
|
</DropdownSection>
|
||||||
) : (
|
{user?.mod ? (
|
||||||
<></>
|
<DropdownSection title="Mod Zone">
|
||||||
)}
|
<DropdownItem
|
||||||
</DropdownMenu>
|
key="remove"
|
||||||
</Dropdown>
|
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>
|
||||||
</div>
|
)}
|
||||||
)}
|
</div>
|
||||||
</div>
|
</CardBody>
|
||||||
</CardBody>
|
</Card>
|
||||||
</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>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
162
src/components/posts/CommentCard.tsx
Normal file
162
src/components/posts/CommentCard.tsx
Normal file
|
@ -0,0 +1,162 @@
|
||||||
|
import { CommentType } from "@/types/CommentType";
|
||||||
|
import { Avatar, Button, Card, CardBody, Spacer } from "@nextui-org/react";
|
||||||
|
import { formatDistance } from "date-fns";
|
||||||
|
import { LoaderCircle, Reply } from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useState } from "react";
|
||||||
|
import Editor from "../editor";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
import { getCookie, hasCookie } from "@/helpers/cookie";
|
||||||
|
import sanitizeHtml from "sanitize-html";
|
||||||
|
import LikeButton from "./LikeButton";
|
||||||
|
|
||||||
|
export default function CommentCard({ comment }: { comment: CommentType }) {
|
||||||
|
const [creatingReply, setCreatingReply] = useState<boolean>(false);
|
||||||
|
const [content, setContent] = useState("");
|
||||||
|
const [waitingPost, setWaitingPost] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className="bg-opacity-60 !duration-250 !ease-linear !transition-all">
|
||||||
|
<CardBody className="p-5">
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center gap-3 text-xs text-default-500 pt-1">
|
||||||
|
<p>By</p>
|
||||||
|
<Link
|
||||||
|
href={`/u/${comment.author.slug}`}
|
||||||
|
className="flex items-center gap-2"
|
||||||
|
>
|
||||||
|
<Avatar
|
||||||
|
size="sm"
|
||||||
|
className="w-6 h-6"
|
||||||
|
src={comment.author.profilePicture}
|
||||||
|
classNames={{
|
||||||
|
base: "bg-transparent",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<p>{comment.author.name}</p>
|
||||||
|
</Link>
|
||||||
|
<p>
|
||||||
|
{formatDistance(new Date(comment.createdAt), new Date(), {
|
||||||
|
addSuffix: true,
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Spacer y={4} />
|
||||||
|
|
||||||
|
<div
|
||||||
|
className="prose dark:prose-invert !duration-250 !ease-linear !transition-all"
|
||||||
|
dangerouslySetInnerHTML={{ __html: comment.content }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Spacer y={4} />
|
||||||
|
|
||||||
|
<div className="flex gap-3">
|
||||||
|
<LikeButton
|
||||||
|
likes={comment.likes.length}
|
||||||
|
liked={comment.hasLiked}
|
||||||
|
parentId={comment.id}
|
||||||
|
isComment
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="bordered"
|
||||||
|
onPress={() => {
|
||||||
|
setCreatingReply(!creatingReply);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Reply size={16} />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Spacer y={4} />
|
||||||
|
|
||||||
|
{creatingReply && (
|
||||||
|
<>
|
||||||
|
<Editor content={content} setContent={setContent} />
|
||||||
|
<div id="create-comment" />
|
||||||
|
<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"),
|
||||||
|
commentId: comment?.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 Reply</p>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
<Spacer y={4} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{comment.children.length > 0 &&
|
||||||
|
(comment.children[0].author ? (
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
{comment.children.map((comment) => (
|
||||||
|
<CommentCard key={comment.id} comment={comment} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
variant="bordered"
|
||||||
|
onPress={() => {
|
||||||
|
toast.warning("Feature coming soon");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Load replies
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</CardBody>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,7 +1,6 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Button } from "@nextui-org/react";
|
import { Button } from "@nextui-org/react";
|
||||||
import { PostType } from "@/types/PostType";
|
|
||||||
import { Heart } from "lucide-react";
|
import { Heart } from "lucide-react";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
import { getCookie } from "@/helpers/cookie";
|
import { getCookie } from "@/helpers/cookie";
|
||||||
|
@ -9,11 +8,22 @@ import { redirect } from "next/navigation";
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
|
|
||||||
export default function LikeButton({ post }: { post: PostType }) {
|
export default function LikeButton({
|
||||||
const [likes, setLikes] = useState<number>(post.likes.length);
|
likes,
|
||||||
const [liked, setLiked] = useState<boolean>(false);
|
liked,
|
||||||
|
parentId,
|
||||||
|
isComment = false,
|
||||||
|
}: {
|
||||||
|
likes: number;
|
||||||
|
liked: boolean;
|
||||||
|
parentId: number;
|
||||||
|
isComment?: boolean;
|
||||||
|
}) {
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
const [reduceMotion, setReduceMotion] = useState<boolean>(false);
|
const [reduceMotion, setReduceMotion] = useState<boolean>(false);
|
||||||
|
const [likeEffect, setLikeEffect] = useState<boolean>(false);
|
||||||
|
const [updatedLikes, setUpdatedLikes] = useState<number>(likes);
|
||||||
|
const [updatedLiked, setUpdatedLiked] = useState<boolean>(liked);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
|
const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
|
||||||
|
@ -34,8 +44,8 @@ export default function LikeButton({ post }: { post: PostType }) {
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="bordered"
|
variant="bordered"
|
||||||
style={{
|
style={{
|
||||||
color: post.hasLiked ? (theme == "dark" ? "#5ed4f7" : "#05b7eb") : "",
|
color: updatedLiked ? (theme == "dark" ? "#5ed4f7" : "#05b7eb") : "",
|
||||||
borderColor: post.hasLiked
|
borderColor: updatedLiked
|
||||||
? theme == "dark"
|
? theme == "dark"
|
||||||
? "#5ed4f744"
|
? "#5ed4f744"
|
||||||
: "#05b7eb44"
|
: "#05b7eb44"
|
||||||
|
@ -59,27 +69,28 @@ export default function LikeButton({ post }: { post: PostType }) {
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
username: getCookie("user"),
|
username: getCookie("user"),
|
||||||
postId: post.id,
|
postId: !isComment ? parentId : 0,
|
||||||
|
commentId: isComment ? parentId : 0,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
post.hasLiked = !post.hasLiked;
|
if (!updatedLiked) {
|
||||||
|
setLikeEffect(true);
|
||||||
if (post.hasLiked) {
|
setTimeout(() => setLikeEffect(false), 1000);
|
||||||
setLiked(true);
|
setUpdatedLikes(updatedLikes + 1);
|
||||||
setTimeout(() => setLiked(false), 1000);
|
|
||||||
setLikes(likes + 1);
|
|
||||||
} else {
|
} else {
|
||||||
setLiked(false);
|
setLikeEffect(false);
|
||||||
setLikes(likes - 1);
|
setUpdatedLikes(updatedLikes - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setUpdatedLiked(!updatedLiked);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
if (response.status == 401) {
|
if (response.status == 401) {
|
||||||
redirect("/login");
|
redirect("/login");
|
||||||
} else {
|
} else {
|
||||||
post.hasLiked = !post.hasLiked;
|
setUpdatedLiked(!updatedLiked);
|
||||||
toast.error("An error occurred");
|
toast.error("An error occurred");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -91,7 +102,7 @@ export default function LikeButton({ post }: { post: PostType }) {
|
||||||
<Heart
|
<Heart
|
||||||
size={16}
|
size={16}
|
||||||
className={
|
className={
|
||||||
liked && !reduceMotion
|
likeEffect && !reduceMotion
|
||||||
? "animate-ping absolute top-0 left-0"
|
? "animate-ping absolute top-0 left-0"
|
||||||
: "absolute top-0 left-0"
|
: "absolute top-0 left-0"
|
||||||
}
|
}
|
||||||
|
@ -102,7 +113,7 @@ export default function LikeButton({ post }: { post: PostType }) {
|
||||||
zIndex: "10",
|
zIndex: "10",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<p>{likes}</p>
|
<p>{updatedLikes}</p>
|
||||||
</div>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
|
|
|
@ -197,16 +197,16 @@ export default function PostCard({
|
||||||
{post.tags.length > 0 && <Spacer y={4} />}
|
{post.tags.length > 0 && <Spacer y={4} />}
|
||||||
|
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
<LikeButton post={post} />
|
<LikeButton
|
||||||
<Button
|
likes={post.likes.length}
|
||||||
size="sm"
|
liked={post.hasLiked}
|
||||||
variant="bordered"
|
parentId={post.id}
|
||||||
onPress={() => {
|
/>
|
||||||
toast.warning("Comment functionality coming soon");
|
<Link href={`/p/${post.slug}#create-comment`}>
|
||||||
}}
|
<Button size="sm" variant="bordered">
|
||||||
>
|
<MessageCircle size={16} /> {post.comments.length}
|
||||||
<MessageCircle size={16} /> {0}
|
</Button>
|
||||||
</Button>
|
</Link>
|
||||||
<Dropdown backdrop="opaque">
|
<Dropdown backdrop="opaque">
|
||||||
<DropdownTrigger>
|
<DropdownTrigger>
|
||||||
<Button size="sm" variant="bordered" isIconOnly>
|
<Button size="sm" variant="bordered" isIconOnly>
|
||||||
|
|
11
src/types/CommentType.ts
Normal file
11
src/types/CommentType.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import { UserType } from "./UserType";
|
||||||
|
|
||||||
|
export interface CommentType {
|
||||||
|
id: number;
|
||||||
|
content: string;
|
||||||
|
children: CommentType[];
|
||||||
|
author: UserType;
|
||||||
|
createdAt: Date;
|
||||||
|
likes: [];
|
||||||
|
hasLiked: boolean;
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { CommentType } from "./CommentType";
|
||||||
import { TagType } from "./TagType";
|
import { TagType } from "./TagType";
|
||||||
import { UserType } from "./UserType";
|
import { UserType } from "./UserType";
|
||||||
|
|
||||||
|
@ -9,6 +10,7 @@ export interface PostType {
|
||||||
content: string;
|
content: string;
|
||||||
author: UserType;
|
author: UserType;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
|
comments: CommentType[];
|
||||||
tags: TagType[];
|
tags: TagType[];
|
||||||
likes: [];
|
likes: [];
|
||||||
hasLiked: boolean;
|
hasLiked: boolean;
|
||||||
|
|
Loading…
Reference in a new issue