mirror of
https://github.com/Ategon/Jamjar.git
synced 2025-02-12 06:16:21 +00:00
Compare commits
8 commits
f3007ed6cc
...
6791e36401
Author | SHA1 | Date | |
---|---|---|---|
![]() |
6791e36401 | ||
![]() |
63d7cd49d2 | ||
![]() |
42c730e073 | ||
![]() |
94a3d31e8d | ||
![]() |
5462af1a5a | ||
![]() |
a2307ec34e | ||
![]() |
0807ce5a67 | ||
![]() |
3b4f1f0476 |
17 changed files with 1489 additions and 805 deletions
src
app
create-post
p/[slug]
theme-slaughter
theme-suggestions
theme-voting
components
jam-header
navbar
posts
themes
timers
helpers
types
|
@ -2,7 +2,14 @@
|
||||||
|
|
||||||
import Editor from "@/components/editor";
|
import Editor from "@/components/editor";
|
||||||
import { getCookie, hasCookie } from "@/helpers/cookie";
|
import { getCookie, hasCookie } from "@/helpers/cookie";
|
||||||
import { Avatar, Button, Form, Input, Spacer } from "@nextui-org/react";
|
import {
|
||||||
|
Avatar,
|
||||||
|
Button,
|
||||||
|
Checkbox,
|
||||||
|
Form,
|
||||||
|
Input,
|
||||||
|
Spacer,
|
||||||
|
} from "@nextui-org/react";
|
||||||
import { LoaderCircle } from "lucide-react";
|
import { LoaderCircle } from "lucide-react";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import { ReactNode, useEffect, useState } from "react";
|
import { ReactNode, useEffect, useState } from "react";
|
||||||
|
@ -10,6 +17,7 @@ import { toast } from "react-toastify";
|
||||||
import sanitizeHtml from "sanitize-html";
|
import sanitizeHtml from "sanitize-html";
|
||||||
import Select, { MultiValue, StylesConfig } from "react-select";
|
import Select, { MultiValue, StylesConfig } from "react-select";
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
|
import { UserType } from "@/types/UserType";
|
||||||
|
|
||||||
export default function CreatePostPage() {
|
export default function CreatePostPage() {
|
||||||
const [title, setTitle] = useState("");
|
const [title, setTitle] = useState("");
|
||||||
|
@ -31,6 +39,8 @@ export default function CreatePostPage() {
|
||||||
}[]
|
}[]
|
||||||
>();
|
>();
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
|
const [user, setUser] = useState<UserType>();
|
||||||
|
const [sticky, setSticky] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setMounted(true);
|
setMounted(true);
|
||||||
|
@ -46,7 +56,8 @@ export default function CreatePostPage() {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const user = await response.json();
|
const localuser = await response.json();
|
||||||
|
setUser(localuser);
|
||||||
|
|
||||||
const tagResponse = await fetch(
|
const tagResponse = await fetch(
|
||||||
process.env.NEXT_PUBLIC_MODE === "PROD"
|
process.env.NEXT_PUBLIC_MODE === "PROD"
|
||||||
|
@ -63,7 +74,7 @@ export default function CreatePostPage() {
|
||||||
}[] = [];
|
}[] = [];
|
||||||
|
|
||||||
for (const tag of await tagResponse.json()) {
|
for (const tag of await tagResponse.json()) {
|
||||||
if (tag.modOnly && !user.mod) {
|
if (tag.modOnly && !localuser.mod) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
newoptions.push({
|
newoptions.push({
|
||||||
|
@ -90,7 +101,6 @@ export default function CreatePostPage() {
|
||||||
}
|
}
|
||||||
|
|
||||||
setOptions(newoptions);
|
setOptions(newoptions);
|
||||||
setSelectedTags(newoptions.filter((tag) => tag.isFixed));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
load();
|
load();
|
||||||
|
@ -211,8 +221,14 @@ export default function CreatePostPage() {
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
title: title,
|
title: title,
|
||||||
content: sanitizedHtml,
|
content: sanitizedHtml,
|
||||||
|
sticky,
|
||||||
username: getCookie("user"),
|
username: getCookie("user"),
|
||||||
tags,
|
tags: [
|
||||||
|
...tags,
|
||||||
|
...(options
|
||||||
|
? options.filter((tag) => tag.isFixed).map((tag) => tag.id)
|
||||||
|
: []),
|
||||||
|
],
|
||||||
}),
|
}),
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
|
@ -268,6 +284,15 @@ export default function CreatePostPage() {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{user && user.mod && (
|
||||||
|
<div>
|
||||||
|
<Spacer />
|
||||||
|
<Checkbox isSelected={sticky} onValueChange={setSticky}>
|
||||||
|
Sticky
|
||||||
|
</Checkbox>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<Spacer />
|
<Spacer />
|
||||||
|
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
|
|
425
src/app/p/[slug]/page.tsx
Normal file
425
src/app/p/[slug]/page.tsx
Normal file
|
@ -0,0 +1,425 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import LikeButton from "@/components/posts/LikeButton";
|
||||||
|
import { getCookie } 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";
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
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 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");
|
||||||
|
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"
|
||||||
|
>
|
||||||
|
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>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</CardBody>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
import Timers from "@/components/timers";
|
import Timers from "@/components/timers";
|
||||||
import Streams from "@/components/streams";
|
import Streams from "@/components/streams";
|
||||||
import JamHeader from "@/components/jam-header";
|
|
||||||
import ThemeSlaughter from "@/components/themes/theme-slaughter";
|
import ThemeSlaughter from "@/components/themes/theme-slaughter";
|
||||||
|
|
||||||
export default async function Home() {
|
export default async function Home() {
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import Timers from "@/components/timers";
|
import Timers from "@/components/timers";
|
||||||
import Streams from "@/components/streams";
|
import Streams from "@/components/streams";
|
||||||
import JamHeader from "@/components/jam-header";
|
|
||||||
import ThemeSuggestions from "@/components/themes/theme-suggest";
|
import ThemeSuggestions from "@/components/themes/theme-suggest";
|
||||||
|
|
||||||
export default async function Home() {
|
export default async function Home() {
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import Timers from "@/components/timers";
|
import Timers from "@/components/timers";
|
||||||
import Streams from "@/components/streams";
|
import Streams from "@/components/streams";
|
||||||
import JamHeader from "@/components/jam-header";
|
|
||||||
import ThemeVoting from "@/components/themes/theme-vote";
|
import ThemeVoting from "@/components/themes/theme-vote";
|
||||||
|
|
||||||
export default async function Home() {
|
export default async function Home() {
|
||||||
|
|
|
@ -5,7 +5,8 @@ import { useEffect, useState } from "react";
|
||||||
import { getCurrentJam, ActiveJamResponse } from "../../helpers/jam";
|
import { getCurrentJam, ActiveJamResponse } from "../../helpers/jam";
|
||||||
|
|
||||||
export default function JamHeader() {
|
export default function JamHeader() {
|
||||||
const [activeJamResponse, setActiveJamResponse] = useState<ActiveJamResponse | null>(null);
|
const [activeJamResponse, setActiveJamResponse] =
|
||||||
|
useState<ActiveJamResponse | null>(null);
|
||||||
const [topTheme, setTopTheme] = useState<string | null>(null);
|
const [topTheme, setTopTheme] = useState<string | null>(null);
|
||||||
|
|
||||||
// Fetch active jam details
|
// Fetch active jam details
|
||||||
|
@ -40,15 +41,18 @@ export default function JamHeader() {
|
||||||
fetchData();
|
fetchData();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Helper function to get ordinal suffix
|
||||||
// Helper function to get ordinal suffix
|
|
||||||
const getOrdinalSuffix = (day: number): string => {
|
const getOrdinalSuffix = (day: number): string => {
|
||||||
if (day > 3 && day < 21) return "th";
|
if (day > 3 && day < 21) return "th";
|
||||||
switch (day % 10) {
|
switch (day % 10) {
|
||||||
case 1: return "st";
|
case 1:
|
||||||
case 2: return "nd";
|
return "st";
|
||||||
case 3: return "rd";
|
case 2:
|
||||||
default: return "th";
|
return "nd";
|
||||||
|
case 3:
|
||||||
|
return "rd";
|
||||||
|
default:
|
||||||
|
return "th";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -69,34 +73,49 @@ export default function JamHeader() {
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-4 px-6 font-bold">
|
<div className="p-4 px-6 font-bold">
|
||||||
<p>
|
<p>
|
||||||
{activeJamResponse?.jam ? (
|
{activeJamResponse?.jam ? (
|
||||||
<>
|
<>
|
||||||
{new Date(activeJamResponse.jam.startTime).toLocaleDateString('en-US', {
|
{new Date(activeJamResponse.jam.startTime).toLocaleDateString(
|
||||||
month: 'long',
|
"en-US",
|
||||||
})} {new Date(activeJamResponse.jam.startTime).getDate()}
|
{
|
||||||
{getOrdinalSuffix(new Date(activeJamResponse.jam.startTime).getDate())}
|
month: "long",
|
||||||
{" - "}
|
}
|
||||||
{new Date(new Date(activeJamResponse.jam.startTime).getTime() +
|
)}{" "}
|
||||||
(activeJamResponse.jam.jammingHours * 60 * 60 * 1000)).toLocaleDateString('en-US', {
|
{new Date(activeJamResponse.jam.startTime).getDate()}
|
||||||
month: 'long',
|
{getOrdinalSuffix(
|
||||||
})} {new Date(new Date(activeJamResponse.jam.startTime).getTime() +
|
new Date(activeJamResponse.jam.startTime).getDate()
|
||||||
(activeJamResponse.jam.jammingHours * 60 * 60 * 1000)).getDate()}
|
)}
|
||||||
{getOrdinalSuffix(new Date(new Date(activeJamResponse.jam.startTime).getTime() +
|
{" - "}
|
||||||
(activeJamResponse.jam.jammingHours * 60 * 60 * 1000)).getDate())}
|
{new Date(
|
||||||
</>
|
new Date(activeJamResponse.jam.startTime).getTime() +
|
||||||
) : (
|
activeJamResponse.jam.jammingHours * 60 * 60 * 1000
|
||||||
"Dates TBA"
|
).toLocaleDateString("en-US", {
|
||||||
)}
|
month: "long",
|
||||||
</p>
|
})}{" "}
|
||||||
</div>
|
{new Date(
|
||||||
|
new Date(activeJamResponse.jam.startTime).getTime() +
|
||||||
|
activeJamResponse.jam.jammingHours * 60 * 60 * 1000
|
||||||
|
).getDate()}
|
||||||
|
{getOrdinalSuffix(
|
||||||
|
new Date(
|
||||||
|
new Date(activeJamResponse.jam.startTime).getTime() +
|
||||||
|
activeJamResponse.jam.jammingHours * 60 * 60 * 1000
|
||||||
|
).getDate()
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
"Dates TBA"
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Phase-Specific Display */}
|
{/* Phase-Specific Display */}
|
||||||
{activeJamResponse?.phase === "Suggestion" && (
|
{activeJamResponse?.phase === "Suggestion" && (
|
||||||
<div className="bg-gray-100 dark:bg-gray-800 p-4 text-center rounded-b-2x">
|
<div className="bg-gray-100 dark:bg-gray-800 p-4 text-center rounded-b-2x">
|
||||||
<a
|
<a
|
||||||
href="/theme-suggestions"
|
// href="/theme-suggestions"
|
||||||
className="text-blue-300 dark:text-blue-500 hover:underline font-semibold"
|
className="text-blue-300 dark:text-blue-500 hover:underline font-semibold"
|
||||||
>
|
>
|
||||||
Go to Theme Suggestion
|
Go to Theme Suggestion
|
||||||
|
@ -107,7 +126,7 @@ export default function JamHeader() {
|
||||||
{activeJamResponse?.phase === "Survival" && (
|
{activeJamResponse?.phase === "Survival" && (
|
||||||
<div className="bg-gray-100 dark:bg-gray-800 p-4 text-center rounded-b-2x">
|
<div className="bg-gray-100 dark:bg-gray-800 p-4 text-center rounded-b-2x">
|
||||||
<a
|
<a
|
||||||
href="/theme-slaughter"
|
// href="/theme-slaughter"
|
||||||
className="text-blue-300 dark:text-blue-500 hover:underline font-semibold"
|
className="text-blue-300 dark:text-blue-500 hover:underline font-semibold"
|
||||||
>
|
>
|
||||||
Go to Theme Survival
|
Go to Theme Survival
|
||||||
|
@ -118,7 +137,7 @@ export default function JamHeader() {
|
||||||
{activeJamResponse?.phase === "Voting" && (
|
{activeJamResponse?.phase === "Voting" && (
|
||||||
<div className="bg-gray-100 dark:bg-gray-800 p-4 text-center rounded-b-2x">
|
<div className="bg-gray-100 dark:bg-gray-800 p-4 text-center rounded-b-2x">
|
||||||
<a
|
<a
|
||||||
href="/theme-voting"
|
// href="/theme-voting"
|
||||||
className="text-blue-300 dark:text-blue-500 hover:underline font-semibold"
|
className="text-blue-300 dark:text-blue-500 hover:underline font-semibold"
|
||||||
>
|
>
|
||||||
Go to Theme Voting
|
Go to Theme Voting
|
||||||
|
@ -139,7 +158,9 @@ export default function JamHeader() {
|
||||||
{activeJamResponse?.phase === "Rating" && (
|
{activeJamResponse?.phase === "Rating" && (
|
||||||
<div className="bg-gray-100 dark:bg-gray-800 p-4 text-center rounded-b-2x">
|
<div className="bg-gray-100 dark:bg-gray-800 p-4 text-center rounded-b-2x">
|
||||||
{topTheme ? (
|
{topTheme ? (
|
||||||
<p className="text-xl font-bold text-blue-500">THEME: {topTheme} RESULTS</p>
|
<p className="text-xl font-bold text-blue-500">
|
||||||
|
THEME: {topTheme} RESULTS
|
||||||
|
</p>
|
||||||
) : (
|
) : (
|
||||||
<p>No top-scoring theme available.</p>
|
<p>No top-scoring theme available.</p>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -19,7 +19,7 @@ interface NavbarUserProps {
|
||||||
isInJam?: boolean;
|
isInJam?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function MobileNavbarUser({
|
export default async function MobileNavbarUser({
|
||||||
user,
|
user,
|
||||||
jam,
|
jam,
|
||||||
setIsInJam,
|
setIsInJam,
|
||||||
|
@ -38,7 +38,7 @@ export default function MobileNavbarUser({
|
||||||
/>
|
/>
|
||||||
</DropdownTrigger>
|
</DropdownTrigger>
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
{jam && isInJam ? (
|
{jam && (await getCurrentJam())?.jam && isInJam ? (
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
key="create-game"
|
key="create-game"
|
||||||
href="/create-game"
|
href="/create-game"
|
||||||
|
@ -47,7 +47,7 @@ export default function MobileNavbarUser({
|
||||||
Create Game
|
Create Game
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
) : null}
|
) : null}
|
||||||
{jam && !isInJam ? (
|
{jam && (await getCurrentJam())?.jam && !isInJam ? (
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
key="join-event"
|
key="join-event"
|
||||||
className="text-black"
|
className="text-black"
|
||||||
|
@ -55,12 +55,12 @@ export default function MobileNavbarUser({
|
||||||
try {
|
try {
|
||||||
const currentJam = await getCurrentJam();
|
const currentJam = await getCurrentJam();
|
||||||
|
|
||||||
if (!currentJam) {
|
if (!currentJam || !currentJam.jam) {
|
||||||
toast.error("There is no jam to join");
|
toast.error("There is no jam to join");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (await joinJam(currentJam.id)) {
|
if (await joinJam(currentJam.jam.id)) {
|
||||||
setIsInJam(true);
|
setIsInJam(true);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
@ -24,7 +24,7 @@ import NextImage from "next/image";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { usePathname } from "next/navigation";
|
import { usePathname } from "next/navigation";
|
||||||
import { getCookie, hasCookie } from "@/helpers/cookie";
|
import { getCookie, hasCookie } from "@/helpers/cookie";
|
||||||
import { getCurrentJam, joinJam, ActiveJamResponse } from "@/helpers/jam";
|
import { getCurrentJam, joinJam } from "@/helpers/jam";
|
||||||
import { JamType } from "@/types/JamType";
|
import { JamType } from "@/types/JamType";
|
||||||
import { UserType } from "@/types/UserType";
|
import { UserType } from "@/types/UserType";
|
||||||
import NavbarUser from "./PCNavbarUser";
|
import NavbarUser from "./PCNavbarUser";
|
||||||
|
|
|
@ -25,6 +25,8 @@ import {
|
||||||
Shield,
|
Shield,
|
||||||
ShieldAlert,
|
ShieldAlert,
|
||||||
ShieldX,
|
ShieldX,
|
||||||
|
Star,
|
||||||
|
StarOff,
|
||||||
Trash,
|
Trash,
|
||||||
X,
|
X,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
@ -73,7 +75,9 @@ export default function PostCard({
|
||||||
(minimized ? (
|
(minimized ? (
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<p>{post.title}</p>
|
<Link href={`/p/${post.slug}`}>
|
||||||
|
<p>{post.title}</p>
|
||||||
|
</Link>
|
||||||
|
|
||||||
<div className="flex items-center gap-3 text-xs text-default-500 pt-1">
|
<div className="flex items-center gap-3 text-xs text-default-500 pt-1">
|
||||||
<p>By</p>
|
<p>By</p>
|
||||||
|
@ -110,7 +114,9 @@ export default function PostCard({
|
||||||
) : (
|
) : (
|
||||||
<div>
|
<div>
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<p className="text-2xl">{post.title}</p>
|
<Link href={`/p/${post.slug}`}>
|
||||||
|
<p className="text-2xl">{post.title}</p>
|
||||||
|
</Link>
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="light"
|
variant="light"
|
||||||
|
@ -292,6 +298,81 @@ export default function PostCard({
|
||||||
>
|
>
|
||||||
Remove
|
Remove
|
||||||
</DropdownItem>
|
</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 ? (
|
{user?.admin && !post.author.mod ? (
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
key="promote-mod"
|
key="promote-mod"
|
||||||
|
@ -338,7 +419,9 @@ export default function PostCard({
|
||||||
))}
|
))}
|
||||||
{style == "compact" && (
|
{style == "compact" && (
|
||||||
<div>
|
<div>
|
||||||
<p className="text-2xl">{post.title}</p>
|
<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">
|
<div className="flex items-center gap-3 text-xs text-default-500 pt-1">
|
||||||
<p>By</p>
|
<p>By</p>
|
||||||
|
@ -366,7 +449,9 @@ export default function PostCard({
|
||||||
)}
|
)}
|
||||||
{style == "ultra" && (
|
{style == "ultra" && (
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<p>{post.title}</p>
|
<Link href={`/p/${post.slug}`}>
|
||||||
|
<p>{post.title}</p>
|
||||||
|
</Link>
|
||||||
|
|
||||||
<div className="flex items-center gap-3 text-xs text-default-500 pt-1">
|
<div className="flex items-center gap-3 text-xs text-default-500 pt-1">
|
||||||
<p>By</p>
|
<p>By</p>
|
||||||
|
|
54
src/components/posts/StickyPostCard.tsx
Normal file
54
src/components/posts/StickyPostCard.tsx
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Avatar, Card, CardBody } from "@nextui-org/react";
|
||||||
|
import { formatDistance } from "date-fns";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { PostType } from "@/types/PostType";
|
||||||
|
import { Megaphone, NotebookText } from "lucide-react";
|
||||||
|
|
||||||
|
export default function StickyPostCard({ post }: { post: PostType }) {
|
||||||
|
return (
|
||||||
|
<Card className="bg-opacity-60 !duration-250 !ease-linear !transition-all flex">
|
||||||
|
<CardBody className="p-5">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<div className="flex gap-4 items-center text-blue-600 dark:text-blue-400 transition-all duration-250 ease-linear">
|
||||||
|
{post.tags.filter((tag) => tag.name === "Changelog").length >
|
||||||
|
0 ? (
|
||||||
|
<NotebookText />
|
||||||
|
) : (
|
||||||
|
<Megaphone />
|
||||||
|
)}
|
||||||
|
<Link href={`/p/${post.slug}`}>
|
||||||
|
<p>{post.title}</p>
|
||||||
|
</Link>
|
||||||
|
</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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardBody>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
|
@ -42,9 +42,11 @@ import {
|
||||||
import { PostTime } from "@/types/PostTimes";
|
import { PostTime } from "@/types/PostTimes";
|
||||||
import { TagType } from "@/types/TagType";
|
import { TagType } from "@/types/TagType";
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
|
import StickyPostCard from "./StickyPostCard";
|
||||||
|
|
||||||
export default function Posts() {
|
export default function Posts() {
|
||||||
const [posts, setPosts] = useState<PostType[]>();
|
const [posts, setPosts] = useState<PostType[]>();
|
||||||
|
const [stickyPosts, setStickyPosts] = useState<PostType[]>();
|
||||||
const [sort, setSort] = useState<PostSort>("newest");
|
const [sort, setSort] = useState<PostSort>("newest");
|
||||||
const [time, setTime] = useState<PostTime>("all");
|
const [time, setTime] = useState<PostTime>("all");
|
||||||
const [style, setStyle] = useState<PostStyle>("cozy");
|
const [style, setStyle] = useState<PostStyle>("cozy");
|
||||||
|
@ -144,6 +146,31 @@ export default function Posts() {
|
||||||
}`
|
}`
|
||||||
);
|
);
|
||||||
setPosts(await postsResponse.json());
|
setPosts(await postsResponse.json());
|
||||||
|
|
||||||
|
// Sticky posts
|
||||||
|
// Fetch posts with userSlug if user is available
|
||||||
|
const stickyPostsResponse = await fetch(
|
||||||
|
process.env.NEXT_PUBLIC_MODE === "PROD"
|
||||||
|
? `https://d2jam.com/api/v1/posts?sort=${sort}&user=${
|
||||||
|
userData.slug
|
||||||
|
}&time=${time}&tags=${
|
||||||
|
tagRules
|
||||||
|
? Object.entries(tagRules)
|
||||||
|
.map((key) => `${key}`)
|
||||||
|
.join("_")
|
||||||
|
: ""
|
||||||
|
}&sticky=true`
|
||||||
|
: `http://localhost:3005/api/v1/posts?sort=${sort}&user=${
|
||||||
|
userData.slug
|
||||||
|
}&time=${time}&tags=${
|
||||||
|
tagRules
|
||||||
|
? Object.entries(tagRules)
|
||||||
|
.map((key) => `${key}`)
|
||||||
|
.join("_")
|
||||||
|
: ""
|
||||||
|
}&sticky=true`
|
||||||
|
);
|
||||||
|
setStickyPosts(await stickyPostsResponse.json());
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
} else {
|
} else {
|
||||||
setUser(undefined);
|
setUser(undefined);
|
||||||
|
@ -167,6 +194,26 @@ export default function Posts() {
|
||||||
}`
|
}`
|
||||||
);
|
);
|
||||||
setPosts(await postsResponse.json());
|
setPosts(await postsResponse.json());
|
||||||
|
|
||||||
|
// Fetch posts without userSlug if user is not available
|
||||||
|
const stickyPostsResponse = await fetch(
|
||||||
|
process.env.NEXT_PUBLIC_MODE === "PROD"
|
||||||
|
? `https://d2jam.com/api/v1/posts?sort=${sort}&time=${time}&tags=${
|
||||||
|
tagRules
|
||||||
|
? Object.entries(tagRules)
|
||||||
|
.map((key, value) => `${key}-${value}`)
|
||||||
|
.join("_")
|
||||||
|
: ""
|
||||||
|
}&sticky=true`
|
||||||
|
: `http://localhost:3005/api/v1/posts?sort=${sort}&time=${time}&tags=${
|
||||||
|
tagRules
|
||||||
|
? Object.entries(tagRules)
|
||||||
|
.map((key, value) => `${key}-${value}`)
|
||||||
|
.join("_")
|
||||||
|
: ""
|
||||||
|
}&sticky=true`
|
||||||
|
);
|
||||||
|
setStickyPosts(await stickyPostsResponse.json());
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -263,6 +310,24 @@ export default function Posts() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
{loading ? (
|
||||||
|
<div className="flex justify-center p-6">
|
||||||
|
<LoaderCircle
|
||||||
|
className="animate-spin text-[#333] dark:text-[#999]"
|
||||||
|
size={24}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
stickyPosts &&
|
||||||
|
stickyPosts.length > 0 && (
|
||||||
|
<div className="flex flex-col gap-3 p-4">
|
||||||
|
{stickyPosts.map((post) => (
|
||||||
|
<StickyPostCard key={post.id} post={post} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="flex justify-between p-4 pb-0">
|
<div className="flex justify-between p-4 pb-0">
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Dropdown backdrop="opaque">
|
<Dropdown backdrop="opaque">
|
||||||
|
|
|
@ -1,279 +1,286 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { useState, useEffect } from "react";
|
// import React, { useState, useEffect, useCallback } from "react";
|
||||||
import { getCookie } from "@/helpers/cookie";
|
// import { getCookie } from "@/helpers/cookie";
|
||||||
import { getCurrentJam, hasJoinedCurrentJam, ActiveJamResponse } from "@/helpers/jam";
|
// import {
|
||||||
|
// getCurrentJam,
|
||||||
|
// hasJoinedCurrentJam,
|
||||||
|
// ActiveJamResponse,
|
||||||
|
// } from "@/helpers/jam";
|
||||||
|
|
||||||
export default function ThemeSlaughter() {
|
export default function ThemeSlaughter() {
|
||||||
|
// const [randomTheme, setRandomTheme] = useState(null);
|
||||||
|
// const [votedThemes, setVotedThemes] = useState([]);
|
||||||
|
// const [loading, setLoading] = useState(false);
|
||||||
|
// const [token, setToken] = useState<string | null>(null);
|
||||||
|
// const [hasJoined, setHasJoined] = useState<boolean>(false);
|
||||||
|
// const [activeJamResponse, setActiveJam] = useState<ActiveJamResponse | null>(
|
||||||
|
// null
|
||||||
|
// );
|
||||||
|
// const [phaseLoading, setPhaseLoading] = useState(true);
|
||||||
|
|
||||||
const [randomTheme, setRandomTheme] = useState(null);
|
// // Fetch token on the client side
|
||||||
const [votedThemes, setVotedThemes] = useState([]);
|
// useEffect(() => {
|
||||||
const [loading, setLoading] = useState(false);
|
// const fetchedToken = getCookie("token");
|
||||||
const [token, setToken] = useState(null);
|
// setToken(fetchedToken);
|
||||||
const [hasJoined, setHasJoined] = useState<boolean>(false);
|
// }, []);
|
||||||
const [activeJamResponse, setActiveJam] = useState<ActiveJamResponse | null>(null);
|
|
||||||
const [phaseLoading, setPhaseLoading] = useState(true);
|
|
||||||
|
|
||||||
// Fetch token on the client side
|
// // Fetch the current jam phase using helpers/jam
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
const fetchedToken = getCookie("token");
|
// const fetchCurrentJamPhase = async () => {
|
||||||
setToken(fetchedToken);
|
// try {
|
||||||
}, []);
|
// const activeJam = await getCurrentJam();
|
||||||
|
// setActiveJam(activeJam); // Set active jam details
|
||||||
|
// } catch (error) {
|
||||||
|
// console.error("Error fetching current jam:", error);
|
||||||
|
// } finally {
|
||||||
|
// setPhaseLoading(false); // Stop loading when phase is fetched
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
// Fetch the current jam phase using helpers/jam
|
// fetchCurrentJamPhase();
|
||||||
useEffect(() => {
|
// }, []);
|
||||||
const fetchCurrentJamPhase = async () => {
|
|
||||||
try {
|
|
||||||
const activeJam = await getCurrentJam();
|
|
||||||
setActiveJam(activeJam); // Set active jam details
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error fetching current jam:", error);
|
|
||||||
} finally {
|
|
||||||
setPhaseLoading(false); // Stop loading when phase is fetched
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
fetchCurrentJamPhase();
|
// // Fetch a random theme
|
||||||
}, []);
|
// const fetchRandomTheme = useCallback(async () => {
|
||||||
|
// if (!token) return; // Wait until token is available
|
||||||
|
// if (!activeJamResponse) return;
|
||||||
|
// if (
|
||||||
|
// activeJamResponse &&
|
||||||
|
// activeJamResponse.jam &&
|
||||||
|
// activeJamResponse.phase != "Survival"
|
||||||
|
// ) {
|
||||||
|
// return (
|
||||||
|
// <div>
|
||||||
|
// <h1>It's not Theme Survival phase.</h1>
|
||||||
|
// </div>
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
// try {
|
||||||
|
// const response = await fetch(
|
||||||
|
// process.env.NEXT_PUBLIC_MODE === "PROD"
|
||||||
|
// ? "https://d2jam.com/api/v1/themes/random"
|
||||||
|
// : "http://localhost:3005/api/v1/themes/random",
|
||||||
|
// {
|
||||||
|
// headers: { Authorization: `Bearer ${token}` },
|
||||||
|
// credentials: "include",
|
||||||
|
// }
|
||||||
|
// );
|
||||||
|
// if (response.ok) {
|
||||||
|
// const data = await response.json();
|
||||||
|
// setRandomTheme(data);
|
||||||
|
// } else {
|
||||||
|
// console.error("Failed to fetch random theme.");
|
||||||
|
// }
|
||||||
|
// } catch (error) {
|
||||||
|
// console.error("Error fetching random theme:", error);
|
||||||
|
// }
|
||||||
|
// }, [activeJamResponse, token]);
|
||||||
|
|
||||||
// Fetch a random theme
|
// // Fetch voted themes
|
||||||
const fetchRandomTheme = async () => {
|
// const fetchVotedThemes = useCallback(async () => {
|
||||||
if (!token) return; // Wait until token is available
|
// if (!token) return; // Wait until token is available
|
||||||
if( !activeJamResponse) return;
|
|
||||||
if(activeJamResponse && activeJamResponse.jam && activeJamResponse.phase != "Survival")
|
|
||||||
{
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h1>It's not Theme Survival phase.</h1>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
// try {
|
||||||
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/themes/random"
|
// ? "https://d2jam.com/api/v1/themes/voteSlaughter"
|
||||||
: "http://localhost:3005/api/v1/themes/random",
|
// : "http://localhost:3005/api/v1/themes/voteSlaughter",
|
||||||
{
|
// {
|
||||||
headers: { Authorization: `Bearer ${token}` },
|
// headers: { Authorization: `Bearer ${token}` },
|
||||||
credentials: "include",
|
// credentials: "include",
|
||||||
}
|
// }
|
||||||
);
|
// );
|
||||||
if (response.ok) {
|
// if (response.ok) {
|
||||||
const data = await response.json();
|
// const data = await response.json();
|
||||||
setRandomTheme(data);
|
// setVotedThemes(data);
|
||||||
} else {
|
// } else {
|
||||||
console.error("Failed to fetch random theme.");
|
// console.error("Failed to fetch voted themes.");
|
||||||
}
|
// }
|
||||||
} catch (error) {
|
// } catch (error) {
|
||||||
console.error("Error fetching random theme:", error);
|
// console.error("Error fetching voted themes:", error);
|
||||||
}
|
// }
|
||||||
};
|
// }, [token]);
|
||||||
|
|
||||||
// Fetch voted themes
|
// // Handle voting
|
||||||
const fetchVotedThemes = async () => {
|
// const handleVote = async (voteType: string) => {
|
||||||
if (!token) return; // Wait until token is available
|
// if (!randomTheme) return;
|
||||||
|
|
||||||
try {
|
// setLoading(true);
|
||||||
const response = await fetch(
|
// try {
|
||||||
process.env.NEXT_PUBLIC_MODE === "PROD"
|
// const response = await fetch(
|
||||||
? "https://d2jam.com/api/v1/themes/voteSlaughter"
|
// process.env.NEXT_PUBLIC_MODE === "PROD"
|
||||||
: "http://localhost:3005/api/v1/themes/voteSlaughter",
|
// ? "https://d2jam.com/api/v1/themes/voteSlaughter"
|
||||||
{
|
// : "http://localhost:3005/api/v1/themes/voteSlaughter",
|
||||||
headers: { Authorization: `Bearer ${token}` },
|
// {
|
||||||
credentials: "include",
|
// method: "POST",
|
||||||
}
|
// headers: {
|
||||||
);
|
// "Content-Type": "application/json",
|
||||||
if (response.ok) {
|
// Authorization: `Bearer ${token}`,
|
||||||
const data = await response.json();
|
// },
|
||||||
setVotedThemes(data);
|
// credentials: "include",
|
||||||
} else {
|
// body: JSON.stringify({
|
||||||
console.error("Failed to fetch voted themes.");
|
// suggestionId: randomTheme.id,
|
||||||
}
|
// voteType,
|
||||||
} catch (error) {
|
// }),
|
||||||
console.error("Error fetching voted themes:", error);
|
// }
|
||||||
}
|
// );
|
||||||
};
|
|
||||||
|
|
||||||
// Handle voting
|
// if (response.ok) {
|
||||||
const handleVote = async (voteType) => {
|
// // Refresh data after voting
|
||||||
if (!randomTheme) return;
|
// fetchRandomTheme();
|
||||||
|
// fetchVotedThemes();
|
||||||
|
// } else {
|
||||||
|
// console.error("Failed to submit vote.");
|
||||||
|
// }
|
||||||
|
// } catch (error) {
|
||||||
|
// console.error("Error submitting vote:", error);
|
||||||
|
// } finally {
|
||||||
|
// setLoading(false);
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
setLoading(true);
|
// // Handle resetting a vote from the grid
|
||||||
try {
|
// const handleResetVote = async (themeId: number) => {
|
||||||
const response = await fetch(
|
// try {
|
||||||
process.env.NEXT_PUBLIC_MODE === "PROD"
|
// setRandomTheme(votedThemes.find((theme) => theme.id === themeId));
|
||||||
? "https://d2jam.com/api/v1/themes/voteSlaughter"
|
// setVotedThemes((prev) =>
|
||||||
: "http://localhost:3005/api/v1/themes/voteSlaughter",
|
// prev.map((theme) =>
|
||||||
{
|
// theme.id === themeId ? { ...theme, slaughterScore: 0 } : theme
|
||||||
method: "POST",
|
// )
|
||||||
headers: {
|
// );
|
||||||
"Content-Type": "application/json",
|
// } catch (error) {
|
||||||
Authorization: `Bearer ${token}`,
|
// console.error("Error resetting vote:", error);
|
||||||
},
|
// }
|
||||||
credentials: "include",
|
// };
|
||||||
body: JSON.stringify({
|
|
||||||
suggestionId: randomTheme.id,
|
|
||||||
voteType,
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response.ok) {
|
// useEffect(() => {
|
||||||
// Refresh data after voting
|
// if (token && activeJamResponse?.phase === "Survival") {
|
||||||
fetchRandomTheme();
|
// fetchRandomTheme();
|
||||||
fetchVotedThemes();
|
// fetchVotedThemes();
|
||||||
} else {
|
// }
|
||||||
console.error("Failed to submit vote.");
|
// }, [token, activeJamResponse, fetchRandomTheme, fetchVotedThemes]);
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error submitting vote:", error);
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handle resetting a vote from the grid
|
// useEffect(() => {
|
||||||
const handleResetVote = async (themeId) => {
|
// const init = async () => {
|
||||||
try {
|
// const joined = await hasJoinedCurrentJam();
|
||||||
setRandomTheme(votedThemes.find((theme) => theme.id === themeId));
|
// setHasJoined(joined);
|
||||||
setVotedThemes((prev) =>
|
// setLoading(false);
|
||||||
prev.map((theme) =>
|
// };
|
||||||
theme.id === themeId ? { ...theme, slaughterScore: 0 } : theme
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error resetting vote:", error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
// init();
|
||||||
|
// }, []);
|
||||||
|
|
||||||
useEffect(() => {
|
// if (phaseLoading || loading) {
|
||||||
if (token && activeJamResponse?.phase === "Survival") {
|
// return <div>Loading...</div>;
|
||||||
fetchRandomTheme();
|
// }
|
||||||
fetchVotedThemes();
|
|
||||||
}
|
|
||||||
}, [token, activeJamResponse]);
|
|
||||||
|
|
||||||
|
// if (!hasJoined) {
|
||||||
|
// return (
|
||||||
|
// <div className="p-6 bg-gray-100 dark:bg-gray-800 min-h-screen">
|
||||||
|
// <h1 className="text-2xl font-bold text-gray-800 dark:text-white mb-6">
|
||||||
|
// Join the Jam First
|
||||||
|
// </h1>
|
||||||
|
// <p className="text-gray-600 dark:text-gray-400">
|
||||||
|
// You need to join the current jam before you can join Theme Survival.
|
||||||
|
// </p>
|
||||||
|
// <button
|
||||||
|
// onClick={() => joinJam(activeJamResponse?.jam?.id)}
|
||||||
|
// className="mt-4 px-6 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600"
|
||||||
|
// >
|
||||||
|
// Join Jam
|
||||||
|
// </button>
|
||||||
|
// </div>
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
useEffect(() => {
|
// // Render message if not in Theme Slaughter phase
|
||||||
const init = async () => {
|
// if (activeJamResponse?.phase !== "Survival") {
|
||||||
const joined = await hasJoinedCurrentJam();
|
// return (
|
||||||
setHasJoined(joined);
|
// <div className="p-6 bg-gray-100 dark:bg-gray-800 min-h-screen">
|
||||||
setLoading(false);
|
// <h1 className="text-2xl font-bold text-gray-800 dark:text-white mb-6">
|
||||||
};
|
// Not in Theme Slaughter Phase
|
||||||
|
// </h1>
|
||||||
|
// <p className="text-gray-600 dark:text-gray-400">
|
||||||
|
// The current phase is{" "}
|
||||||
|
// <strong>{activeJamResponse?.phase || "Unknown"}</strong>. Please come
|
||||||
|
// back during the Theme Slaughter phase.
|
||||||
|
// </p>
|
||||||
|
// </div>
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
init();
|
// const loggedIn = getCookie("token");
|
||||||
}, []);
|
|
||||||
|
|
||||||
if (phaseLoading || loading) {
|
// if (!loggedIn) {
|
||||||
return <div>Loading...</div>;
|
// return <div>Sign in to be able to join the Theme Survival</div>;
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (!hasJoined) {
|
// return (
|
||||||
return (
|
// <div className="flex h-screen">
|
||||||
<div className="p-6 bg-gray-100 dark:bg-gray-800 min-h-screen">
|
// {/* Left Side */}
|
||||||
<h1 className="text-2xl font-bold text-gray-800 dark:text-white mb-6">
|
// <div className="w-1/2 p-6 bg-gray-100 dark:bg-gray-800 flex flex-col justify-start items-center">
|
||||||
Join the Jam First
|
// {randomTheme ? (
|
||||||
</h1>
|
// <>
|
||||||
<p className="text-gray-600 dark:text-gray-400">
|
// <h2 className="text-2xl font-bold text-gray-800 dark:text-white mb-4">
|
||||||
You need to join the current jam before you can join Theme Survival.
|
// {randomTheme.suggestion}
|
||||||
</p>
|
// </h2>
|
||||||
<button
|
// <div className="flex gap-4">
|
||||||
onClick={() => joinJam(activeJamResponse?.jam?.id)}
|
// <button
|
||||||
className="mt-4 px-6 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600"
|
// onClick={() => handleVote("YES")}
|
||||||
>
|
// className="px-6 py-3 bg-green-500 text-white font-bold rounded-lg hover:bg-green-600"
|
||||||
Join Jam
|
// disabled={loading}
|
||||||
</button>
|
// >
|
||||||
</div>
|
// YES
|
||||||
);
|
// </button>
|
||||||
}
|
// <button
|
||||||
|
// onClick={() => handleVote("NO")}
|
||||||
|
// className="px-6 py-3 bg-red-500 text-white font-bold rounded-lg hover:bg-red-600"
|
||||||
|
// disabled={loading}
|
||||||
|
// >
|
||||||
|
// NO
|
||||||
|
// </button>
|
||||||
|
// <button
|
||||||
|
// onClick={() => handleVote("SKIP")}
|
||||||
|
// className="px-6 py-3 bg-gray-500 text-white font-bold rounded-lg hover:bg-gray-600"
|
||||||
|
// disabled={loading}
|
||||||
|
// >
|
||||||
|
// SKIP
|
||||||
|
// </button>
|
||||||
|
// </div>
|
||||||
|
// </>
|
||||||
|
// ) : (
|
||||||
|
// <p className="text-gray-600 dark:text-gray-400">
|
||||||
|
// No themes available.
|
||||||
|
// </p>
|
||||||
|
// )}
|
||||||
|
// </div>
|
||||||
|
|
||||||
// Render message if not in Theme Slaughter phase
|
// {/* Right Side */}
|
||||||
if (activeJamResponse?.phase !== "Survival") {
|
// <div className="w-1/2 p-6 bg-white dark:bg-gray-900 overflow-y-auto">
|
||||||
return (
|
// <h3 className="text-xl font-bold text-gray-800 dark:text-white mb-4">
|
||||||
<div className="p-6 bg-gray-100 dark:bg-gray-800 min-h-screen">
|
// Your Votes
|
||||||
<h1 className="text-2xl font-bold text-gray-800 dark:text-white mb-6">
|
// </h3>
|
||||||
Not in Theme Slaughter Phase
|
// <div className="grid grid-cols-4 gap-4">
|
||||||
</h1>
|
// {votedThemes.map((theme) => (
|
||||||
<p className="text-gray-600 dark:text-gray-400">
|
// <div
|
||||||
The current phase is <strong>{activeJamResponse?.phase || "Unknown"}</strong>. Please come back during the Theme Slaughter phase.
|
// key={theme.id}
|
||||||
</p>
|
// onClick={() => handleResetVote(theme.id)}
|
||||||
</div>
|
// className={`p-4 rounded-lg cursor-pointer ${
|
||||||
);
|
// theme.slaughterScore > 0
|
||||||
}
|
// ? "bg-green-500 text-white"
|
||||||
|
// : theme.slaughterScore < 0
|
||||||
const loggedIn = getCookie("token");
|
// ? "bg-red-500 text-white"
|
||||||
|
// : "bg-gray-300 text-black"
|
||||||
if (!loggedIn) {
|
// }`}
|
||||||
return (
|
// >
|
||||||
<div>Sign in to be able to join the Theme Survival</div>
|
// {theme.suggestion}
|
||||||
);
|
// </div>
|
||||||
}
|
// ))}
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
return (
|
// </div>
|
||||||
<div className="flex h-screen">
|
// );
|
||||||
{/* Left Side */}
|
return <></>;
|
||||||
<div className="w-1/2 p-6 bg-gray-100 dark:bg-gray-800 flex flex-col justify-start items-center">
|
|
||||||
{randomTheme ? (
|
|
||||||
<>
|
|
||||||
<h2 className="text-2xl font-bold text-gray-800 dark:text-white mb-4">
|
|
||||||
{randomTheme.suggestion}
|
|
||||||
</h2>
|
|
||||||
<div className="flex gap-4">
|
|
||||||
<button
|
|
||||||
onClick={() => handleVote("YES")}
|
|
||||||
className="px-6 py-3 bg-green-500 text-white font-bold rounded-lg hover:bg-green-600"
|
|
||||||
disabled={loading}
|
|
||||||
>
|
|
||||||
YES
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => handleVote("NO")}
|
|
||||||
className="px-6 py-3 bg-red-500 text-white font-bold rounded-lg hover:bg-red-600"
|
|
||||||
disabled={loading}
|
|
||||||
>
|
|
||||||
NO
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => handleVote("SKIP")}
|
|
||||||
className="px-6 py-3 bg-gray-500 text-white font-bold rounded-lg hover:bg-gray-600"
|
|
||||||
disabled={loading}
|
|
||||||
>
|
|
||||||
SKIP
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<p className="text-gray-600 dark:text-gray-400">No themes available.</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Right Side */}
|
|
||||||
<div className="w-1/2 p-6 bg-white dark:bg-gray-900 overflow-y-auto">
|
|
||||||
<h3 className="text-xl font-bold text-gray-800 dark:text-white mb-4">
|
|
||||||
Your Votes
|
|
||||||
</h3>
|
|
||||||
<div className="grid grid-cols-4 gap-4">
|
|
||||||
{votedThemes.map((theme) => (
|
|
||||||
<div
|
|
||||||
key={theme.id}
|
|
||||||
onClick={() => handleResetVote(theme.id)}
|
|
||||||
className={`p-4 rounded-lg cursor-pointer ${
|
|
||||||
theme.slaughterScore > 0
|
|
||||||
? "bg-green-500 text-white"
|
|
||||||
: theme.slaughterScore < 0
|
|
||||||
? "bg-red-500 text-white"
|
|
||||||
: "bg-gray-300 text-black"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{theme.suggestion}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
|
@ -1,271 +1,282 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { useState, useEffect } from "react";
|
// import React, { useState, useEffect } from "react";
|
||||||
import { getCookie } from "@/helpers/cookie";
|
// import { getCookie } from "@/helpers/cookie";
|
||||||
import { getCurrentJam, hasJoinedCurrentJam , ActiveJamResponse } from "@/helpers/jam";
|
// import {
|
||||||
|
// getCurrentJam,
|
||||||
|
// hasJoinedCurrentJam,
|
||||||
|
// ActiveJamResponse,
|
||||||
|
// } from "@/helpers/jam";
|
||||||
|
|
||||||
export default function ThemeSuggestions() {
|
export default function ThemeSuggestions() {
|
||||||
const [suggestion, setSuggestion] = useState("");
|
// const [suggestion, setSuggestion] = useState("");
|
||||||
const [loading, setLoading] = useState(false);
|
// const [loading, setLoading] = useState(false);
|
||||||
const [successMessage, setSuccessMessage] = useState("");
|
// const [successMessage, setSuccessMessage] = useState("");
|
||||||
const [errorMessage, setErrorMessage] = useState("");
|
// const [errorMessage, setErrorMessage] = useState("");
|
||||||
const [userSuggestions, setUserSuggestions] = useState([]);
|
// const [userSuggestions, setUserSuggestions] = useState([]);
|
||||||
const [themeLimit, setThemeLimit] = useState(0);
|
// const [themeLimit, setThemeLimit] = useState(0);
|
||||||
const [hasJoined, setHasJoined] = useState<boolean>(false);
|
// const [hasJoined, setHasJoined] = useState<boolean>(false);
|
||||||
const [activeJamResponse, setActiveJamResponse] = useState<ActiveJamResponse | null>(null);
|
// const [activeJamResponse, setActiveJamResponse] =
|
||||||
const [phaseLoading, setPhaseLoading] = useState(true); // Loading state for fetching phase
|
// useState<ActiveJamResponse | null>(null);
|
||||||
|
// const [phaseLoading, setPhaseLoading] = useState(true); // Loading state for fetching phase
|
||||||
|
|
||||||
// Fetch the current jam phase using helpers/jam
|
// // Fetch the current jam phase using helpers/jam
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
const fetchCurrentJamPhase = async () => {
|
// const fetchCurrentJamPhase = async () => {
|
||||||
try {
|
// try {
|
||||||
const activeJam = await getCurrentJam();
|
// const activeJam = await getCurrentJam();
|
||||||
setActiveJamResponse(activeJam); // Set active jam details
|
// setActiveJamResponse(activeJam); // Set active jam details
|
||||||
if (activeJam?.jam) {
|
// if (activeJam?.jam) {
|
||||||
setThemeLimit(activeJam.jam.themePerUser || Infinity); // Set theme limit
|
// setThemeLimit(activeJam.jam.themePerUser || Infinity); // Set theme limit
|
||||||
}
|
// }
|
||||||
} catch (error) {
|
// } catch (error) {
|
||||||
console.error("Error fetching current jam:", error);
|
// console.error("Error fetching current jam:", error);
|
||||||
} finally {
|
// } finally {
|
||||||
setPhaseLoading(false); // Stop loading when phase is fetched
|
// setPhaseLoading(false); // Stop loading when phase is fetched
|
||||||
}
|
// }
|
||||||
};
|
// };
|
||||||
|
|
||||||
fetchCurrentJamPhase();
|
// fetchCurrentJamPhase();
|
||||||
}, []);
|
// }, []);
|
||||||
|
|
||||||
// Fetch all suggestions for the logged-in user
|
// // Fetch all suggestions for the logged-in user
|
||||||
const fetchSuggestions = async () => {
|
// const fetchSuggestions = async () => {
|
||||||
try {
|
// try {
|
||||||
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/themes/suggestion"
|
// ? "https://d2jam.com/api/v1/themes/suggestion"
|
||||||
: "http://localhost:3005/api/v1/themes/suggestion",
|
// : "http://localhost:3005/api/v1/themes/suggestion",
|
||||||
{
|
// {
|
||||||
headers: { Authorization: `Bearer ${getCookie("token")}` },
|
// headers: { Authorization: `Bearer ${getCookie("token")}` },
|
||||||
credentials: "include",
|
// credentials: "include",
|
||||||
}
|
// }
|
||||||
);
|
// );
|
||||||
if (response.ok) {
|
// if (response.ok) {
|
||||||
const data = await response.json();
|
// const data = await response.json();
|
||||||
setUserSuggestions(data);
|
// setUserSuggestions(data);
|
||||||
}
|
// }
|
||||||
} catch (error) {
|
// } catch (error) {
|
||||||
console.error("Error fetching suggestions:", error);
|
// console.error("Error fetching suggestions:", error);
|
||||||
}
|
// }
|
||||||
};
|
// };
|
||||||
|
|
||||||
// Fetch suggestions only when phase is "Suggestion"
|
// // Fetch suggestions only when phase is "Suggestion"
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
if (activeJamResponse?.phase === "Suggestion") {
|
// if (activeJamResponse?.phase === "Suggestion") {
|
||||||
fetchSuggestions();
|
// fetchSuggestions();
|
||||||
}
|
// }
|
||||||
}, [activeJamResponse]);
|
// }, [activeJamResponse]);
|
||||||
|
|
||||||
// Handle form submission to add a new suggestion
|
// // Handle form submission to add a new suggestion
|
||||||
const handleSubmit = async (e: React.FormEvent) => {
|
// const handleSubmit = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
// e.preventDefault();
|
||||||
setLoading(true);
|
// setLoading(true);
|
||||||
setSuccessMessage("");
|
// setSuccessMessage("");
|
||||||
setErrorMessage("");
|
// setErrorMessage("");
|
||||||
|
|
||||||
if (!suggestion.trim()) {
|
// if (!suggestion.trim()) {
|
||||||
setErrorMessage("Suggestion cannot be empty.");
|
// setErrorMessage("Suggestion cannot be empty.");
|
||||||
setLoading(false);
|
// setLoading(false);
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
|
||||||
try {
|
// try {
|
||||||
const token = getCookie("token");
|
// const token = getCookie("token");
|
||||||
|
|
||||||
if (!token) {
|
// if (!token) {
|
||||||
throw new Error("User is not authenticated. Please log in.");
|
// throw new Error("User is not authenticated. Please log in.");
|
||||||
}
|
// }
|
||||||
|
|
||||||
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/themes/suggestion"
|
// ? "https://d2jam.com/api/v1/themes/suggestion"
|
||||||
: "http://localhost:3005/api/v1/themes/suggestion",
|
// : "http://localhost:3005/api/v1/themes/suggestion",
|
||||||
{
|
// {
|
||||||
method: "POST",
|
// method: "POST",
|
||||||
headers: {
|
// headers: {
|
||||||
"Content-Type": "application/json",
|
// "Content-Type": "application/json",
|
||||||
Authorization: `Bearer ${token}`,
|
// Authorization: `Bearer ${token}`,
|
||||||
},
|
// },
|
||||||
credentials: "include",
|
// credentials: "include",
|
||||||
body: JSON.stringify({ suggestionText: suggestion }),
|
// body: JSON.stringify({ suggestionText: suggestion }),
|
||||||
}
|
// }
|
||||||
);
|
// );
|
||||||
|
|
||||||
if (!response.ok) {
|
// if (!response.ok) {
|
||||||
const errorData = await response.json();
|
// const errorData = await response.json();
|
||||||
throw new Error(errorData.error || "Failed to submit suggestion.");
|
// throw new Error(errorData.error || "Failed to submit suggestion.");
|
||||||
}
|
// }
|
||||||
|
|
||||||
setSuccessMessage("Suggestion added successfully!");
|
// setSuccessMessage("Suggestion added successfully!");
|
||||||
setSuggestion(""); // Clear input field
|
// setSuggestion(""); // Clear input field
|
||||||
fetchSuggestions(); // Refresh suggestions list
|
// fetchSuggestions(); // Refresh suggestions list
|
||||||
} catch (error: any) {
|
// } catch (error) {
|
||||||
console.error("Error submitting suggestion:", error.message);
|
// if (error instanceof Error) {
|
||||||
setErrorMessage(error.message || "An unexpected error occurred.");
|
// console.error("Error submitting suggestion:", error.message);
|
||||||
} finally {
|
// setErrorMessage(error.message || "An unexpected error occurred.");
|
||||||
setLoading(false);
|
// } else {
|
||||||
}
|
// console.error("Unknown error:", error);
|
||||||
};
|
// setErrorMessage("An unexpected error occurred.");
|
||||||
|
// }
|
||||||
|
// } finally {
|
||||||
|
// setLoading(false);
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
// Handle deleting a suggestion
|
// // Handle deleting a suggestion
|
||||||
const handleDelete = async (id: number) => {
|
// const handleDelete = async (id: number) => {
|
||||||
try {
|
// try {
|
||||||
const token = getCookie("token");
|
// const token = getCookie("token");
|
||||||
|
|
||||||
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/themes/suggestion/${id}`
|
// ? `https://d2jam.com/api/v1/themes/suggestion/${id}`
|
||||||
: `http://localhost:3005/api/v1/themes/suggestion/${id}`,
|
// : `http://localhost:3005/api/v1/themes/suggestion/${id}`,
|
||||||
{
|
// {
|
||||||
method: "DELETE",
|
// method: "DELETE",
|
||||||
headers: { Authorization: `Bearer ${token}` },
|
// headers: { Authorization: `Bearer ${token}` },
|
||||||
credentials: "include",
|
// credentials: "include",
|
||||||
}
|
// }
|
||||||
);
|
// );
|
||||||
|
|
||||||
if (!response.ok) {
|
// if (!response.ok) {
|
||||||
throw new Error("Failed to delete suggestion.");
|
// throw new Error("Failed to delete suggestion.");
|
||||||
}
|
// }
|
||||||
|
|
||||||
fetchSuggestions(); // Refresh suggestions list
|
// fetchSuggestions(); // Refresh suggestions list
|
||||||
} catch (error) {
|
// } catch (error) {
|
||||||
console.error("Error deleting suggestion:", error);
|
// console.error("Error deleting suggestion:", error);
|
||||||
}
|
// }
|
||||||
};
|
// };
|
||||||
|
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
const init = async () => {
|
// const init = async () => {
|
||||||
const joined = await hasJoinedCurrentJam();
|
// const joined = await hasJoinedCurrentJam();
|
||||||
setHasJoined(joined);
|
// setHasJoined(joined);
|
||||||
setLoading(false);
|
// setLoading(false);
|
||||||
};
|
// };
|
||||||
|
|
||||||
init();
|
// init();
|
||||||
}, []);
|
// }, []);
|
||||||
|
|
||||||
// Render loading state while fetching phase
|
// // Render loading state while fetching phase
|
||||||
if (phaseLoading || loading) {
|
// if (phaseLoading || loading) {
|
||||||
return <div>Loading...</div>;
|
// return <div>Loading...</div>;
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (!hasJoined) {
|
// if (!hasJoined) {
|
||||||
return (
|
// return (
|
||||||
<div className="p-6 bg-gray-100 dark:bg-gray-800 min-h-screen">
|
// <div className="p-6 bg-gray-100 dark:bg-gray-800 min-h-screen">
|
||||||
<h1 className="text-2xl font-bold text-gray-800 dark:text-white mb-6">
|
// <h1 className="text-2xl font-bold text-gray-800 dark:text-white mb-6">
|
||||||
Join the Jam First
|
// Join the Jam First
|
||||||
</h1>
|
// </h1>
|
||||||
<p className="text-gray-600 dark:text-gray-400">
|
// <p className="text-gray-600 dark:text-gray-400">
|
||||||
You need to join the current jam before you can suggest themes.
|
// You need to join the current jam before you can suggest themes.
|
||||||
</p>
|
// </p>
|
||||||
<button
|
// <button
|
||||||
onClick={() => joinJam(activeJamResponse?.jam?.id)}
|
// onClick={() => joinJam(activeJamResponse?.jam?.id)}
|
||||||
className="mt-4 px-6 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600"
|
// className="mt-4 px-6 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600"
|
||||||
>
|
// >
|
||||||
Join Jam
|
// Join Jam
|
||||||
</button>
|
// </button>
|
||||||
</div>
|
// </div>
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
|
|
||||||
const token = getCookie("token");
|
// const token = getCookie("token");
|
||||||
|
|
||||||
if (!token) {
|
// if (!token) {
|
||||||
return (
|
// return <div>Sign in to be able to suggest themes</div>;
|
||||||
<div>Sign in to be able to suggest themes</div>
|
// }
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render message if not in Suggestion phase
|
// // Render message if not in Suggestion phase
|
||||||
if (activeJamResponse?.phase !== "Suggestion") {
|
// if (activeJamResponse?.phase !== "Suggestion") {
|
||||||
return (
|
// return (
|
||||||
<div className="p-6 bg-gray-100 dark:bg-gray-800 min-h-screen">
|
// <div className="p-6 bg-gray-100 dark:bg-gray-800 min-h-screen">
|
||||||
<h1 className="text-2xl font-bold text-gray-800 dark:text-white mb-6">
|
// <h1 className="text-2xl font-bold text-gray-800 dark:text-white mb-6">
|
||||||
Not in Suggestion Phase
|
// Not in Suggestion Phase
|
||||||
</h1>
|
// </h1>
|
||||||
<p className="text-gray-600 dark:text-gray-400">
|
// <p className="text-gray-600 dark:text-gray-400">
|
||||||
The current phase is <strong>{activeJamResponse?.phase || "Unknown"}</strong>. Please come back during the Suggestion phase.
|
// The current phase is{" "}
|
||||||
</p>
|
// <strong>{activeJamResponse?.phase || "Unknown"}</strong>. Please come
|
||||||
</div>
|
// back during the Suggestion phase.
|
||||||
);
|
// </p>
|
||||||
}
|
// </div>
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
return (
|
// return (
|
||||||
<div className="max-w-md mx-auto mt-8 p-6 bg-white dark:bg-gray-800 rounded-lg shadow-md">
|
// <div className="max-w-md mx-auto mt-8 p-6 bg-white dark:bg-gray-800 rounded-lg shadow-md">
|
||||||
<h2 className="text-xl font-bold text-gray-800 dark:text-white mb-4">
|
// <h2 className="text-xl font-bold text-gray-800 dark:text-white mb-4">
|
||||||
Submit Your Theme Suggestion
|
// Submit Your Theme Suggestion
|
||||||
</h2>
|
// </h2>
|
||||||
|
|
||||||
{/* Hide form if user has reached their limit */}
|
// {/* Hide form if user has reached their limit */}
|
||||||
{userSuggestions.length < themeLimit ? (
|
// {userSuggestions.length < themeLimit ? (
|
||||||
<form onSubmit={handleSubmit} className="flex flex-col gap-4">
|
// <form onSubmit={handleSubmit} className="flex flex-col gap-4">
|
||||||
<textarea
|
// <textarea
|
||||||
className="w-full p-3 border rounded-lg focus:outline-none text-gray-600 focus:ring focus:ring-blue-300 dark:bg-gray-700 dark:text-white"
|
// className="w-full p-3 border rounded-lg focus:outline-none text-gray-600 focus:ring focus:ring-blue-300 dark:bg-gray-700 dark:text-white"
|
||||||
placeholder="Enter your theme suggestion..."
|
// placeholder="Enter your theme suggestion..."
|
||||||
value={suggestion}
|
// value={suggestion}
|
||||||
onChange={(e) => {
|
// onChange={(e) => {
|
||||||
if (e.target.value.length <= 32) {
|
// if (e.target.value.length <= 32) {
|
||||||
setSuggestion(e.target.value);
|
// setSuggestion(e.target.value);
|
||||||
}
|
// }
|
||||||
}}
|
// }}
|
||||||
rows={1}
|
// rows={1}
|
||||||
maxLength={32}
|
// maxLength={32}
|
||||||
></textarea>
|
// ></textarea>
|
||||||
{errorMessage && (
|
// {errorMessage && (
|
||||||
<p className="text-red-500 text-sm">{errorMessage}</p>
|
// <p className="text-red-500 text-sm">{errorMessage}</p>
|
||||||
)}
|
// )}
|
||||||
{successMessage && (
|
// {successMessage && (
|
||||||
<p className="text-green-500 text-sm">{successMessage}</p>
|
// <p className="text-green-500 text-sm">{successMessage}</p>
|
||||||
)}
|
// )}
|
||||||
<button
|
// <button
|
||||||
type="submit"
|
// type="submit"
|
||||||
className={`w-full py-2 px-4 bg-blue-500 text-white font-semibold rounded-lg shadow-md hover:bg-blue-600 focus:outline-none focus:ring focus:ring-blue-300 ${
|
// className={`w-full py-2 px-4 bg-blue-500 text-white font-semibold rounded-lg shadow-md hover:bg-blue-600 focus:outline-none focus:ring focus:ring-blue-300 ${
|
||||||
loading ? "opacity-50 cursor-not-allowed" : ""
|
// loading ? "opacity-50 cursor-not-allowed" : ""
|
||||||
}`}
|
// }`}
|
||||||
disabled={loading}
|
// disabled={loading}
|
||||||
>
|
// >
|
||||||
{loading ? "Submitting..." : "Submit Suggestion"}
|
// {loading ? "Submitting..." : "Submit Suggestion"}
|
||||||
</button>
|
// </button>
|
||||||
</form>
|
// </form>
|
||||||
) : (
|
// ) : (
|
||||||
<p className="text-yellow-500 text-sm">
|
// <p className="text-yellow-500 text-sm">
|
||||||
You've reached your theme suggestion limit for this jam!
|
// You've reached your theme suggestion limit for this jam!
|
||||||
</p>
|
// </p>
|
||||||
)}
|
// )}
|
||||||
|
|
||||||
{/* List of user's suggestions */}
|
// {/* List of user's suggestions */}
|
||||||
<div className="mt-6">
|
// <div className="mt-6">
|
||||||
<h3 className="text-lg font-semibold text-gray-800 dark:text-white mb-4">
|
// <h3 className="text-lg font-semibold text-gray-800 dark:text-white mb-4">
|
||||||
Your Suggestions
|
// Your Suggestions
|
||||||
</h3>
|
// </h3>
|
||||||
{userSuggestions.length > 0 ? (
|
// {userSuggestions.length > 0 ? (
|
||||||
<ul className="space-y-4">
|
// <ul className="space-y-4">
|
||||||
{userSuggestions.map((suggestion) => (
|
// {userSuggestions.map((suggestion) => (
|
||||||
<li
|
// <li
|
||||||
key={suggestion.id}
|
// key={suggestion.id}
|
||||||
className="flex justify-between items-center text-gray-400 bg-gray-100 dark:bg-gray-700 p-3 rounded-lg shadow-sm"
|
// className="flex justify-between items-center text-gray-400 bg-gray-100 dark:bg-gray-700 p-3 rounded-lg shadow-sm"
|
||||||
>
|
// >
|
||||||
<span>{suggestion.suggestion}</span>
|
// <span>{suggestion.suggestion}</span>
|
||||||
<button
|
// <button
|
||||||
onClick={() => handleDelete(suggestion.id)}
|
// onClick={() => handleDelete(suggestion.id)}
|
||||||
className="text-red-500 hover:text-red-700 font-semibold"
|
// className="text-red-500 hover:text-red-700 font-semibold"
|
||||||
>
|
// >
|
||||||
Delete
|
// Delete
|
||||||
</button>
|
// </button>
|
||||||
</li>
|
// </li>
|
||||||
))}
|
// ))}
|
||||||
</ul>
|
// </ul>
|
||||||
) : (
|
// ) : (
|
||||||
<p className="text-gray-600 dark:text-gray-400">
|
// <p className="text-gray-600 dark:text-gray-400">
|
||||||
You haven't submitted any suggestions yet.
|
// You haven't submitted any suggestions yet.
|
||||||
</p>
|
// </p>
|
||||||
)}
|
// )}
|
||||||
</div>
|
// </div>
|
||||||
</div>
|
// </div>
|
||||||
);
|
// );
|
||||||
|
return <></>;
|
||||||
}
|
}
|
|
@ -1,242 +1,245 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { useState, useEffect } from "react";
|
// import React, { useState, useEffect } from "react";
|
||||||
import { getCookie } from "@/helpers/cookie";
|
// import { getCookie } from "@/helpers/cookie";
|
||||||
import { getCurrentJam, hasJoinedCurrentJam , ActiveJamResponse } from "@/helpers/jam";
|
// import {
|
||||||
|
// getCurrentJam,
|
||||||
|
// hasJoinedCurrentJam,
|
||||||
|
// ActiveJamResponse,
|
||||||
|
// } from "@/helpers/jam";
|
||||||
|
|
||||||
export default function VotingPage() {
|
export default function VotingPage() {
|
||||||
const [themes, setThemes] = useState([]);
|
return <></>;
|
||||||
const [loading, setLoading] = useState(false);
|
// const [themes, setThemes] = useState([]);
|
||||||
const [activeJamResponse, setActiveJamResponse] = useState<ActiveJamResponse | null>(null);
|
// const [loading, setLoading] = useState(false);
|
||||||
const [hasJoined, setHasJoined] = useState<boolean>(false);
|
// const [activeJamResponse, setActiveJamResponse] =
|
||||||
const [phaseLoading, setPhaseLoading] = useState(true); // Loading state for fetching phase
|
// useState<ActiveJamResponse | null>(null);
|
||||||
const token = getCookie("token");
|
// const [hasJoined, setHasJoined] = useState<boolean>(false);
|
||||||
|
// const [phaseLoading, setPhaseLoading] = useState(true); // Loading state for fetching phase
|
||||||
|
// const token = getCookie("token");
|
||||||
|
|
||||||
// Fetch the current jam phase using helpers/jam
|
// // Fetch the current jam phase using helpers/jam
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
const fetchCurrentJamPhase = async () => {
|
// const fetchCurrentJamPhase = async () => {
|
||||||
try {
|
// try {
|
||||||
const activeJam = await getCurrentJam();
|
// const activeJam = await getCurrentJam();
|
||||||
setActiveJamResponse(activeJam); // Set active jam details
|
// setActiveJamResponse(activeJam); // Set active jam details
|
||||||
} catch (error) {
|
// } catch (error) {
|
||||||
console.error("Error fetching current jam:", error);
|
// console.error("Error fetching current jam:", error);
|
||||||
} finally {
|
// } finally {
|
||||||
setPhaseLoading(false); // Stop loading when phase is fetched
|
// setPhaseLoading(false); // Stop loading when phase is fetched
|
||||||
}
|
// }
|
||||||
};
|
// };
|
||||||
|
|
||||||
fetchCurrentJamPhase();
|
// fetchCurrentJamPhase();
|
||||||
}, []);
|
// }, []);
|
||||||
|
|
||||||
// Fetch top N themes with voting scores
|
// // Fetch themes only when phase is "Voting"
|
||||||
const fetchThemes = async () => {
|
// useEffect(() => {
|
||||||
if (!token || !activeJamResponse) return;
|
// // Fetch top N themes with voting scores
|
||||||
|
// async function fetchThemes() {
|
||||||
|
// if (!token || !activeJamResponse) return;
|
||||||
|
|
||||||
try {
|
// try {
|
||||||
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/themes/top-themes"
|
// ? "https://d2jam.com/api/v1/themes/top-themes"
|
||||||
: "http://localhost:3005/api/v1/themes/top-themes",
|
// : "http://localhost:3005/api/v1/themes/top-themes",
|
||||||
{
|
// {
|
||||||
headers: { Authorization: `Bearer ${token}` },
|
// headers: { Authorization: `Bearer ${token}` },
|
||||||
credentials: "include",
|
// credentials: "include",
|
||||||
}
|
// }
|
||||||
);
|
// );
|
||||||
if (response.ok) {
|
// if (response.ok) {
|
||||||
const themes = await response.json();
|
// const themes = await response.json();
|
||||||
console.log("Fetched themes:", themes); // Debug log
|
// console.log("Fetched themes:", themes); // Debug log
|
||||||
|
|
||||||
// Fetch user's votes for these themes
|
// // Fetch user's votes for these themes
|
||||||
const votesResponse = await fetch(
|
// const votesResponse = await fetch(
|
||||||
process.env.NEXT_PUBLIC_MODE === "PROD"
|
// process.env.NEXT_PUBLIC_MODE === "PROD"
|
||||||
? "https://d2jam.com/api/v1/themes/votes"
|
// ? "https://d2jam.com/api/v1/themes/votes"
|
||||||
: "http://localhost:3005/api/v1/themes/votes",
|
// : "http://localhost:3005/api/v1/themes/votes",
|
||||||
{
|
// {
|
||||||
headers: { Authorization: `Bearer ${token}` },
|
// headers: { Authorization: `Bearer ${token}` },
|
||||||
credentials: "include",
|
// credentials: "include",
|
||||||
}
|
// }
|
||||||
);
|
// );
|
||||||
|
|
||||||
if (votesResponse.ok) {
|
// if (votesResponse.ok) {
|
||||||
const votes = await votesResponse.json();
|
// const votes = await votesResponse.json();
|
||||||
console.log("Fetched votes:", votes); // Debug log
|
// console.log("Fetched votes:", votes); // Debug log
|
||||||
|
|
||||||
// Merge themes with user's votes
|
// // Merge themes with user's votes
|
||||||
const themesWithVotes = themes.map(theme => {
|
// const themesWithVotes = themes.map((theme) => {
|
||||||
const vote = votes.find(v => v.themeSuggestionId === theme.id);
|
// const vote = votes.find((v) => v.themeSuggestionId === theme.id);
|
||||||
console.log(`Theme ${theme.id} vote:`, vote); // Debug log
|
// console.log(`Theme ${theme.id} vote:`, vote); // Debug log
|
||||||
return {
|
// return {
|
||||||
...theme,
|
// ...theme,
|
||||||
votingScore: vote ? vote.votingScore : null
|
// votingScore: vote ? vote.votingScore : null,
|
||||||
};
|
// };
|
||||||
});
|
// });
|
||||||
|
|
||||||
console.log("Themes with votes:", themesWithVotes); // Debug log
|
// console.log("Themes with votes:", themesWithVotes); // Debug log
|
||||||
setThemes(themesWithVotes);
|
// setThemes(themesWithVotes);
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
console.error("Failed to fetch themes.");
|
// console.error("Failed to fetch themes.");
|
||||||
}
|
// }
|
||||||
} catch (error) {
|
// } catch (error) {
|
||||||
console.error("Error fetching themes:", error);
|
// console.error("Error fetching themes:", error);
|
||||||
}
|
// }
|
||||||
};
|
// }
|
||||||
|
|
||||||
|
// if (activeJamResponse?.phase === "Voting") {
|
||||||
|
// fetchThemes();
|
||||||
|
// }
|
||||||
|
// }, [activeJamResponse, token]);
|
||||||
|
|
||||||
// Fetch themes only when phase is "Voting"
|
// // Handle voting
|
||||||
useEffect(() => {
|
// const handleVote = async (themeId, votingScore) => {
|
||||||
if (activeJamResponse?.phase === "Voting") {
|
// setLoading(true);
|
||||||
fetchThemes();
|
// try {
|
||||||
}
|
// const response = await fetch(
|
||||||
}, [activeJamResponse]);
|
// process.env.NEXT_PUBLIC_MODE === "PROD"
|
||||||
|
// ? "https://d2jam.com/api/v1/themes/vote"
|
||||||
|
// : "http://localhost:3005/api/v1/themes/vote",
|
||||||
|
// {
|
||||||
|
// method: "POST",
|
||||||
|
// headers: {
|
||||||
|
// "Content-Type": "application/json",
|
||||||
|
// Authorization: `Bearer ${token}`,
|
||||||
|
// },
|
||||||
|
// credentials: "include",
|
||||||
|
// body: JSON.stringify({ suggestionId: themeId, votingScore }),
|
||||||
|
// }
|
||||||
|
// );
|
||||||
|
|
||||||
// Handle voting
|
// if (response.ok) {
|
||||||
const handleVote = async (themeId, votingScore) => {
|
// // Just update the local state instead of re-fetching all themes
|
||||||
setLoading(true);
|
// setThemes((prevThemes) =>
|
||||||
try {
|
// prevThemes.map((theme) =>
|
||||||
const response = await fetch(
|
// theme.id === themeId ? { ...theme, votingScore } : theme
|
||||||
process.env.NEXT_PUBLIC_MODE === "PROD"
|
// )
|
||||||
? "https://d2jam.com/api/v1/themes/vote"
|
// );
|
||||||
: "http://localhost:3005/api/v1/themes/vote",
|
// } else {
|
||||||
{
|
// console.error("Failed to submit vote.");
|
||||||
method: "POST",
|
// }
|
||||||
headers: {
|
// } catch (error) {
|
||||||
"Content-Type": "application/json",
|
// console.error("Error submitting vote:", error);
|
||||||
Authorization: `Bearer ${token}`,
|
// } finally {
|
||||||
},
|
// setLoading(false);
|
||||||
credentials: "include",
|
// }
|
||||||
body: JSON.stringify({ suggestionId: themeId, votingScore }),
|
// };
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response.ok) {
|
// useEffect(() => {
|
||||||
// Just update the local state instead of re-fetching all themes
|
// const init = async () => {
|
||||||
setThemes((prevThemes) =>
|
// const joined = await hasJoinedCurrentJam();
|
||||||
prevThemes.map((theme) =>
|
// setHasJoined(joined);
|
||||||
theme.id === themeId
|
// setLoading(false);
|
||||||
? { ...theme, votingScore }
|
// };
|
||||||
: theme
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
console.error("Failed to submit vote.");
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error submitting vote:", error);
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
// init();
|
||||||
const init = async () => {
|
// }, []);
|
||||||
const joined = await hasJoinedCurrentJam();
|
|
||||||
setHasJoined(joined);
|
|
||||||
setLoading(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
init();
|
// if (phaseLoading || loading) {
|
||||||
}, []);
|
// return <div>Loading...</div>;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (!hasJoined) {
|
||||||
|
// return (
|
||||||
|
// <div className="p-6 bg-gray-100 dark:bg-gray-800 min-h-screen">
|
||||||
|
// <h1 className="text-2xl font-bold text-gray-800 dark:text-white mb-6">
|
||||||
|
// Join the Jam First
|
||||||
|
// </h1>
|
||||||
|
// <p className="text-gray-600 dark:text-gray-400">
|
||||||
|
// You need to join the current jam before you can vote themes.
|
||||||
|
// </p>
|
||||||
|
// <button
|
||||||
|
// onClick={() => joinJam(activeJamResponse?.jam?.id)}
|
||||||
|
// className="mt-4 px-6 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600"
|
||||||
|
// >
|
||||||
|
// Join Jam
|
||||||
|
// </button>
|
||||||
|
// </div>
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
if (phaseLoading || loading) {
|
// if (activeJamResponse?.phase !== "Voting") {
|
||||||
return <div>Loading...</div>;
|
// return (
|
||||||
}
|
// <div className="p-4 bg-gray-100 dark:bg-gray-800 min-h-screen">
|
||||||
|
// <h1 className="text-2xl font-bold text-gray-800 dark:text-white mb-4">
|
||||||
|
// Not in Voting Phase
|
||||||
|
// </h1>
|
||||||
|
// <p className="text-gray-600 dark:text-gray-400">
|
||||||
|
// The current phase is{" "}
|
||||||
|
// <strong>{activeJamResponse?.phase || "Unknown"}</strong>. Please come
|
||||||
|
// back during the Voting phase.
|
||||||
|
// </p>
|
||||||
|
// </div>
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
if (!hasJoined) {
|
// const loggedIn = getCookie("token");
|
||||||
return (
|
|
||||||
<div className="p-6 bg-gray-100 dark:bg-gray-800 min-h-screen">
|
|
||||||
<h1 className="text-2xl font-bold text-gray-800 dark:text-white mb-6">
|
|
||||||
Join the Jam First
|
|
||||||
</h1>
|
|
||||||
<p className="text-gray-600 dark:text-gray-400">
|
|
||||||
You need to join the current jam before you can vote themes.
|
|
||||||
</p>
|
|
||||||
<button
|
|
||||||
onClick={() => joinJam(activeJamResponse?.jam?.id)}
|
|
||||||
className="mt-4 px-6 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600"
|
|
||||||
>
|
|
||||||
Join Jam
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// if (!loggedIn) {
|
||||||
|
// return <div>Sign in to be able to vote</div>;
|
||||||
|
// }
|
||||||
|
|
||||||
if (activeJamResponse?.phase !== "Voting") {
|
// return (
|
||||||
return (
|
// <div className="p-3 bg-gray-100 dark:bg-gray-800 min-h-screen">
|
||||||
<div className="p-4 bg-gray-100 dark:bg-gray-800 min-h-screen">
|
// <h1 className="text-2xl font-bold text-gray-800 dark:text-white mb-4">
|
||||||
<h1 className="text-2xl font-bold text-gray-800 dark:text-white mb-4">
|
// Voting Phase
|
||||||
Not in Voting Phase
|
// </h1>
|
||||||
</h1>
|
// <div className="space-y-2">
|
||||||
<p className="text-gray-600 dark:text-gray-400">
|
// {themes.map((theme) => (
|
||||||
The current phase is <strong>{activeJamResponse?.phase || "Unknown"}</strong>. Please come back during the Voting phase.
|
// <div
|
||||||
</p>
|
// key={theme.id}
|
||||||
</div>
|
// className="p-3 bg-white dark:bg-gray-900 rounded-lg shadow-md flex items-center"
|
||||||
);
|
// >
|
||||||
}
|
// {/* Voting Buttons */}
|
||||||
|
// <div className="flex gap-1 mr-4">
|
||||||
|
// <button
|
||||||
|
// onClick={() => handleVote(theme.id, -1)}
|
||||||
|
// className={`px-3 py-2 rounded-lg ${
|
||||||
|
// theme.votingScore === -1
|
||||||
|
// ? "bg-red-500 text-white"
|
||||||
|
// : "bg-gray-300 text-black hover:bg-red-500 hover:text-white"
|
||||||
|
// }`}
|
||||||
|
// disabled={loading}
|
||||||
|
// >
|
||||||
|
// -1
|
||||||
|
// </button>
|
||||||
|
// <button
|
||||||
|
// onClick={() => handleVote(theme.id, 0)}
|
||||||
|
// className={`px-3 py-2 rounded-lg ${
|
||||||
|
// theme.votingScore === 0
|
||||||
|
// ? "bg-gray-500 text-white"
|
||||||
|
// : "bg-gray-300 text-black hover:bg-gray-500 hover:text-white"
|
||||||
|
// }`}
|
||||||
|
// disabled={loading}
|
||||||
|
// >
|
||||||
|
// 0
|
||||||
|
// </button>
|
||||||
|
// <button
|
||||||
|
// onClick={() => handleVote(theme.id, +1)}
|
||||||
|
// className={`px-3 py-2 rounded-lg ${
|
||||||
|
// theme.votingScore === +1
|
||||||
|
// ? "bg-green-500 text-white"
|
||||||
|
// : "bg-gray-300 text-black hover:bg-green-500 hover:text-white"
|
||||||
|
// }`}
|
||||||
|
// disabled={loading}
|
||||||
|
// >
|
||||||
|
// +1
|
||||||
|
// </button>
|
||||||
|
// </div>
|
||||||
|
|
||||||
const loggedIn = getCookie("token");
|
// {/* Theme Suggestion */}
|
||||||
|
// <div className="text-gray-800 dark:text-white">
|
||||||
if (!loggedIn) {
|
// {theme.suggestion}
|
||||||
return (
|
// </div>
|
||||||
<div>Sign in to be able to vote</div>
|
// </div>
|
||||||
);
|
// ))}
|
||||||
}
|
// </div>
|
||||||
|
// </div>
|
||||||
return (
|
// );
|
||||||
<div className="p-3 bg-gray-100 dark:bg-gray-800 min-h-screen">
|
|
||||||
<h1 className="text-2xl font-bold text-gray-800 dark:text-white mb-4">
|
|
||||||
Voting Phase
|
|
||||||
</h1>
|
|
||||||
<div className="space-y-2">
|
|
||||||
{themes.map((theme) => (
|
|
||||||
<div
|
|
||||||
key={theme.id}
|
|
||||||
className="p-3 bg-white dark:bg-gray-900 rounded-lg shadow-md flex items-center"
|
|
||||||
>
|
|
||||||
{/* Voting Buttons */}
|
|
||||||
<div className="flex gap-1 mr-4">
|
|
||||||
<button
|
|
||||||
onClick={() => handleVote(theme.id, -1)}
|
|
||||||
className={`px-3 py-2 rounded-lg ${
|
|
||||||
theme.votingScore === -1
|
|
||||||
? "bg-red-500 text-white"
|
|
||||||
: "bg-gray-300 text-black hover:bg-red-500 hover:text-white"
|
|
||||||
}`}
|
|
||||||
disabled={loading}
|
|
||||||
>
|
|
||||||
-1
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => handleVote(theme.id, 0)}
|
|
||||||
className={`px-3 py-2 rounded-lg ${
|
|
||||||
theme.votingScore === 0
|
|
||||||
? "bg-gray-500 text-white"
|
|
||||||
: "bg-gray-300 text-black hover:bg-gray-500 hover:text-white"
|
|
||||||
}`}
|
|
||||||
disabled={loading}
|
|
||||||
>
|
|
||||||
0
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => handleVote(theme.id, +1)}
|
|
||||||
className={`px-3 py-2 rounded-lg ${
|
|
||||||
theme.votingScore === +1
|
|
||||||
? "bg-green-500 text-white"
|
|
||||||
: "bg-gray-300 text-black hover:bg-green-500 hover:text-white"
|
|
||||||
}`}
|
|
||||||
disabled={loading}
|
|
||||||
>
|
|
||||||
+1
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Theme Suggestion */}
|
|
||||||
<div className="text-gray-800 dark:text-white">{theme.suggestion}</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
|
@ -1,15 +1,12 @@
|
||||||
"use client"
|
"use client";
|
||||||
import { Spacer } from "@nextui-org/react";
|
import { Spacer } from "@nextui-org/react";
|
||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import Timer from "./Timer";
|
import Timer from "./Timer";
|
||||||
import { getCurrentJam, ActiveJamResponse } from "@/helpers/jam";
|
import { getCurrentJam, ActiveJamResponse } from "@/helpers/jam";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export default function Timers() {
|
export default function Timers() {
|
||||||
|
const [activeJamResponse, setActiveJamResponse] =
|
||||||
const [activeJamResponse, setActiveJamResponse] = useState<ActiveJamResponse | null>(null);
|
useState<ActiveJamResponse | null>(null);
|
||||||
const [phaseLoading, setPhaseLoading] = useState(true); // Loading state for fetching phase
|
|
||||||
|
|
||||||
// Fetch the current jam phase using helpers/jam
|
// Fetch the current jam phase using helpers/jam
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -20,15 +17,13 @@ export default function Timers() {
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching current jam:", error);
|
console.error("Error fetching current jam:", error);
|
||||||
} finally {
|
} finally {
|
||||||
setPhaseLoading(false); // Stop loading when phase is fetched
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchCurrentJamPhase();
|
fetchCurrentJamPhase();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if(activeJamResponse && activeJamResponse.jam)
|
if (activeJamResponse && activeJamResponse.jam) {
|
||||||
{
|
|
||||||
return (
|
return (
|
||||||
<div className="text-[#333] dark:text-white transition-color duration-250">
|
<div className="text-[#333] dark:text-white transition-color duration-250">
|
||||||
<Timer
|
<Timer
|
||||||
|
@ -39,16 +34,13 @@ export default function Timers() {
|
||||||
<p>Site under construction</p>
|
<p>Site under construction</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
return (
|
return (
|
||||||
<div className="text-[#333] dark:text-white transition-color duration-250">
|
<div className="text-[#333] dark:text-white transition-color duration-250">
|
||||||
No upcoming jams
|
No upcoming jams
|
||||||
<Spacer y={8} />
|
<Spacer y={8} />
|
||||||
<p>Site under construction</p>
|
<p>Site under construction</p>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,28 +18,25 @@ export async function getJams(): Promise<JamType[]> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getCurrentJam(): Promise<ActiveJamResponse | null> {
|
export async function getCurrentJam(): Promise<ActiveJamResponse | null> {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
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/jams"
|
? "https://d2jam.com/api/v1/jams/active"
|
||||||
: "http://localhost:3005/api/v1/jams/active"
|
: "http://localhost:3005/api/v1/jams/active"
|
||||||
);
|
);
|
||||||
|
|
||||||
// Parse JSON response
|
// Parse JSON response
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
// Return the phase and jam details
|
|
||||||
return {
|
|
||||||
phase: data.phase,
|
|
||||||
jam: data.jam,
|
|
||||||
};
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error fetching active jam:", error);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Return the phase and jam details
|
||||||
|
return {
|
||||||
|
phase: data.phase,
|
||||||
|
jam: data.jam,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching active jam:", error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function joinJam(jamId: number) {
|
export async function joinJam(jamId: number) {
|
||||||
|
@ -53,7 +50,7 @@ export async function joinJam(jamId: number) {
|
||||||
userSlug: getCookie("user"),
|
userSlug: getCookie("user"),
|
||||||
}),
|
}),
|
||||||
method: "POST",
|
method: "POST",
|
||||||
credentials: 'include',
|
credentials: "include",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
authorization: `Bearer ${getCookie("token")}`,
|
authorization: `Bearer ${getCookie("token")}`,
|
||||||
|
@ -80,7 +77,7 @@ export async function hasJoinedCurrentJam(): Promise<boolean> {
|
||||||
? "https://d2jam.com/api/v1/participation"
|
? "https://d2jam.com/api/v1/participation"
|
||||||
: "http://localhost:3005/api/v1/participation",
|
: "http://localhost:3005/api/v1/participation",
|
||||||
{
|
{
|
||||||
credentials: 'include',
|
credentials: "include",
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${getCookie("token")}`,
|
Authorization: `Bearer ${getCookie("token")}`,
|
||||||
},
|
},
|
||||||
|
|
|
@ -3,7 +3,9 @@ import { UserType } from "./UserType";
|
||||||
|
|
||||||
export interface PostType {
|
export interface PostType {
|
||||||
id: number;
|
id: number;
|
||||||
|
slug: string;
|
||||||
title: string;
|
title: string;
|
||||||
|
sticky: boolean;
|
||||||
content: string;
|
content: string;
|
||||||
author: UserType;
|
author: UserType;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
|
|
Loading…
Reference in a new issue