Compare commits

..

No commits in common. "6791e364018668df860bbb277ca8668cfd34851a" and "f3007ed6ccafd17717b451439d1f46b9040c1b87" have entirely different histories.

17 changed files with 805 additions and 1489 deletions

View file

@ -2,14 +2,7 @@
import Editor from "@/components/editor"; import Editor from "@/components/editor";
import { getCookie, hasCookie } from "@/helpers/cookie"; import { getCookie, hasCookie } from "@/helpers/cookie";
import { import { Avatar, Button, Form, Input, Spacer } from "@nextui-org/react";
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";
@ -17,7 +10,6 @@ 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("");
@ -39,8 +31,6 @@ 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);
@ -56,8 +46,7 @@ export default function CreatePostPage() {
} }
); );
const localuser = await response.json(); const user = 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"
@ -74,7 +63,7 @@ export default function CreatePostPage() {
}[] = []; }[] = [];
for (const tag of await tagResponse.json()) { for (const tag of await tagResponse.json()) {
if (tag.modOnly && !localuser.mod) { if (tag.modOnly && !user.mod) {
continue; continue;
} }
newoptions.push({ newoptions.push({
@ -101,6 +90,7 @@ export default function CreatePostPage() {
} }
setOptions(newoptions); setOptions(newoptions);
setSelectedTags(newoptions.filter((tag) => tag.isFixed));
} }
}; };
load(); load();
@ -221,14 +211,8 @@ 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: {
@ -284,15 +268,6 @@ 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">

View file

@ -1,425 +0,0 @@
"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>
)}
</>
);
}

View file

@ -1,5 +1,6 @@
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() {
@ -14,4 +15,4 @@ export default async function Home() {
</div> </div>
</div> </div>
); );
} }

View file

@ -1,5 +1,6 @@
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() {
@ -14,4 +15,4 @@ export default async function Home() {
</div> </div>
</div> </div>
); );
} }

View file

@ -1,5 +1,6 @@
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() {
@ -14,4 +15,4 @@ export default async function Home() {
</div> </div>
</div> </div>
); );
} }

View file

@ -5,8 +5,7 @@ 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] = const [activeJamResponse, setActiveJamResponse] = useState<ActiveJamResponse | null>(null);
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
@ -41,18 +40,15 @@ 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: case 1: return "st";
return "st"; case 2: return "nd";
case 2: case 3: return "rd";
return "nd"; default: return "th";
case 3:
return "rd";
default:
return "th";
} }
}; };
@ -73,49 +69,34 @@ 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( {new Date(activeJamResponse.jam.startTime).toLocaleDateString('en-US', {
"en-US", month: 'long',
{ })} {new Date(activeJamResponse.jam.startTime).getDate()}
month: "long", {getOrdinalSuffix(new Date(activeJamResponse.jam.startTime).getDate())}
} {" - "}
)}{" "} {new Date(new Date(activeJamResponse.jam.startTime).getTime() +
{new Date(activeJamResponse.jam.startTime).getDate()} (activeJamResponse.jam.jammingHours * 60 * 60 * 1000)).toLocaleDateString('en-US', {
{getOrdinalSuffix( month: 'long',
new Date(activeJamResponse.jam.startTime).getDate() })} {new Date(new Date(activeJamResponse.jam.startTime).getTime() +
)} (activeJamResponse.jam.jammingHours * 60 * 60 * 1000)).getDate()}
{" - "} {getOrdinalSuffix(new Date(new Date(activeJamResponse.jam.startTime).getTime() +
{new Date( (activeJamResponse.jam.jammingHours * 60 * 60 * 1000)).getDate())}
new Date(activeJamResponse.jam.startTime).getTime() + </>
activeJamResponse.jam.jammingHours * 60 * 60 * 1000 ) : (
).toLocaleDateString("en-US", { "Dates TBA"
month: "long", )}
})}{" "} </p>
{new Date( </div>
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
@ -126,7 +107,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
@ -137,7 +118,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
@ -158,9 +139,7 @@ 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"> <p className="text-xl font-bold text-blue-500">THEME: {topTheme} RESULTS</p>
THEME: {topTheme} RESULTS
</p>
) : ( ) : (
<p>No top-scoring theme available.</p> <p>No top-scoring theme available.</p>
)} )}
@ -168,4 +147,4 @@ export default function JamHeader() {
)} )}
</div> </div>
); );
} }

View file

@ -19,7 +19,7 @@ interface NavbarUserProps {
isInJam?: boolean; isInJam?: boolean;
} }
export default async function MobileNavbarUser({ export default function MobileNavbarUser({
user, user,
jam, jam,
setIsInJam, setIsInJam,
@ -38,7 +38,7 @@ export default async function MobileNavbarUser({
/> />
</DropdownTrigger> </DropdownTrigger>
<DropdownMenu> <DropdownMenu>
{jam && (await getCurrentJam())?.jam && isInJam ? ( {jam && isInJam ? (
<DropdownItem <DropdownItem
key="create-game" key="create-game"
href="/create-game" href="/create-game"
@ -47,7 +47,7 @@ export default async function MobileNavbarUser({
Create Game Create Game
</DropdownItem> </DropdownItem>
) : null} ) : null}
{jam && (await getCurrentJam())?.jam && !isInJam ? ( {jam && !isInJam ? (
<DropdownItem <DropdownItem
key="join-event" key="join-event"
className="text-black" className="text-black"
@ -55,12 +55,12 @@ export default async function MobileNavbarUser({
try { try {
const currentJam = await getCurrentJam(); const currentJam = await getCurrentJam();
if (!currentJam || !currentJam.jam) { if (!currentJam) {
toast.error("There is no jam to join"); toast.error("There is no jam to join");
return; return;
} }
if (await joinJam(currentJam.jam.id)) { if (await joinJam(currentJam.id)) {
setIsInJam(true); setIsInJam(true);
} }
} catch (error) { } catch (error) {

View file

@ -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 } from "@/helpers/jam"; import { getCurrentJam, joinJam, ActiveJamResponse } 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";

View file

@ -25,8 +25,6 @@ import {
Shield, Shield,
ShieldAlert, ShieldAlert,
ShieldX, ShieldX,
Star,
StarOff,
Trash, Trash,
X, X,
} from "lucide-react"; } from "lucide-react";
@ -75,9 +73,7 @@ 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">
<Link href={`/p/${post.slug}`}> <p>{post.title}</p>
<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>
@ -114,9 +110,7 @@ export default function PostCard({
) : ( ) : (
<div> <div>
<div className="flex justify-between items-center"> <div className="flex justify-between items-center">
<Link href={`/p/${post.slug}`}> <p className="text-2xl">{post.title}</p>
<p className="text-2xl">{post.title}</p>
</Link>
<Button <Button
size="sm" size="sm"
variant="light" variant="light"
@ -298,81 +292,6 @@ 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"
@ -419,9 +338,7 @@ export default function PostCard({
))} ))}
{style == "compact" && ( {style == "compact" && (
<div> <div>
<Link href={`/p/${post.slug}`}> <p className="text-2xl">{post.title}</p>
<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>
@ -449,9 +366,7 @@ export default function PostCard({
)} )}
{style == "ultra" && ( {style == "ultra" && (
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<Link href={`/p/${post.slug}`}> <p>{post.title}</p>
<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>

View file

@ -1,54 +0,0 @@
"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>
);
}

View file

@ -42,11 +42,9 @@ 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");
@ -146,31 +144,6 @@ 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);
@ -194,26 +167,6 @@ 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);
} }
}; };
@ -310,24 +263,6 @@ 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">

View file

@ -1,286 +1,279 @@
"use client"; "use client";
// import React, { useState, useEffect, useCallback } from "react"; import React, { useState, useEffect } from "react";
// import { getCookie } from "@/helpers/cookie"; import { getCookie } from "@/helpers/cookie";
// import { import { getCurrentJam, hasJoinedCurrentJam, ActiveJamResponse } from "@/helpers/jam";
// 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);
// // Fetch token on the client side const [randomTheme, setRandomTheme] = useState(null);
// useEffect(() => { const [votedThemes, setVotedThemes] = useState([]);
// const fetchedToken = getCookie("token"); const [loading, setLoading] = useState(false);
// setToken(fetchedToken); const [token, setToken] = useState(null);
// }, []); const [hasJoined, setHasJoined] = useState<boolean>(false);
const [activeJamResponse, setActiveJam] = useState<ActiveJamResponse | null>(null);
const [phaseLoading, setPhaseLoading] = useState(true);
// // Fetch the current jam phase using helpers/jam // Fetch token on the client side
// useEffect(() => { useEffect(() => {
// const fetchCurrentJamPhase = async () => { const fetchedToken = getCookie("token");
// try { setToken(fetchedToken);
// 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 the current jam phase using helpers/jam
// }, []); 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
}
};
// // Fetch a random theme fetchCurrentJamPhase();
// 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&apos;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 voted themes // Fetch a random theme
// const fetchVotedThemes = useCallback(async () => { const fetchRandomTheme = 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/voteSlaughter" ? "https://d2jam.com/api/v1/themes/random"
// : "http://localhost:3005/api/v1/themes/voteSlaughter", : "http://localhost:3005/api/v1/themes/random",
// { {
// 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();
// setVotedThemes(data); setRandomTheme(data);
// } else { } else {
// console.error("Failed to fetch voted themes."); console.error("Failed to fetch random theme.");
// } }
// } catch (error) { } catch (error) {
// console.error("Error fetching voted themes:", error); console.error("Error fetching random theme:", error);
// } }
// }, [token]); };
// // Handle voting // Fetch voted themes
// const handleVote = async (voteType: string) => { const fetchVotedThemes = async () => {
// if (!randomTheme) return; if (!token) return; // Wait until token is available
// setLoading(true); 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/voteSlaughter"
// ? "https://d2jam.com/api/v1/themes/voteSlaughter" : "http://localhost:3005/api/v1/themes/voteSlaughter",
// : "http://localhost:3005/api/v1/themes/voteSlaughter", {
// { headers: { Authorization: `Bearer ${token}` },
// method: "POST", credentials: "include",
// headers: { }
// "Content-Type": "application/json", );
// Authorization: `Bearer ${token}`, if (response.ok) {
// }, const data = await response.json();
// credentials: "include", setVotedThemes(data);
// body: JSON.stringify({ } else {
// suggestionId: randomTheme.id, console.error("Failed to fetch voted themes.");
// voteType, }
// }), } catch (error) {
// } console.error("Error fetching voted themes:", error);
// ); }
};
// if (response.ok) { // Handle voting
// // Refresh data after voting const handleVote = async (voteType) => {
// fetchRandomTheme(); if (!randomTheme) return;
// fetchVotedThemes();
// } else {
// console.error("Failed to submit vote.");
// }
// } catch (error) {
// console.error("Error submitting vote:", error);
// } finally {
// setLoading(false);
// }
// };
// // Handle resetting a vote from the grid setLoading(true);
// const handleResetVote = async (themeId: number) => { try {
// try { const response = await fetch(
// setRandomTheme(votedThemes.find((theme) => theme.id === themeId)); process.env.NEXT_PUBLIC_MODE === "PROD"
// setVotedThemes((prev) => ? "https://d2jam.com/api/v1/themes/voteSlaughter"
// prev.map((theme) => : "http://localhost:3005/api/v1/themes/voteSlaughter",
// theme.id === themeId ? { ...theme, slaughterScore: 0 } : theme {
// ) method: "POST",
// ); headers: {
// } catch (error) { "Content-Type": "application/json",
// console.error("Error resetting vote:", error); Authorization: `Bearer ${token}`,
// } },
// }; credentials: "include",
body: JSON.stringify({
suggestionId: randomTheme.id,
voteType,
}),
}
);
// useEffect(() => { if (response.ok) {
// if (token && activeJamResponse?.phase === "Survival") { // Refresh data after voting
// fetchRandomTheme(); fetchRandomTheme();
// fetchVotedThemes(); fetchVotedThemes();
// } } else {
// }, [token, activeJamResponse, fetchRandomTheme, fetchVotedThemes]); console.error("Failed to submit vote.");
}
} catch (error) {
console.error("Error submitting vote:", error);
} finally {
setLoading(false);
}
};
// useEffect(() => { // Handle resetting a vote from the grid
// const init = async () => { const handleResetVote = async (themeId) => {
// const joined = await hasJoinedCurrentJam(); try {
// setHasJoined(joined); setRandomTheme(votedThemes.find((theme) => theme.id === themeId));
// setLoading(false); setVotedThemes((prev) =>
// }; prev.map((theme) =>
theme.id === themeId ? { ...theme, slaughterScore: 0 } : theme
)
);
} catch (error) {
console.error("Error resetting vote:", error);
}
};
// init();
// }, []);
// if (phaseLoading || loading) { useEffect(() => {
// return <div>Loading...</div>; if (token && activeJamResponse?.phase === "Survival") {
// } 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>
// );
// }
// // Render message if not in Theme Slaughter phase useEffect(() => {
// if (activeJamResponse?.phase !== "Survival") { const init = async () => {
// return ( const joined = await hasJoinedCurrentJam();
// <div className="p-6 bg-gray-100 dark:bg-gray-800 min-h-screen"> setHasJoined(joined);
// <h1 className="text-2xl font-bold text-gray-800 dark:text-white mb-6"> setLoading(false);
// Not in Theme Slaughter Phase };
// </h1>
// <p className="text-gray-600 dark:text-gray-400"> init();
// The current phase is{" "} }, []);
// <strong>{activeJamResponse?.phase || "Unknown"}</strong>. Please come
// back during the Theme Slaughter phase. if (phaseLoading || loading) {
// </p> return <div>Loading...</div>;
// </div> }
// );
// }
// const loggedIn = getCookie("token"); 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>
);
}
// if (!loggedIn) { // Render message if not in Theme Slaughter phase
// return <div>Sign in to be able to join the Theme Survival</div>; if (activeJamResponse?.phase !== "Survival") {
// } 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">
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>
);
}
// return ( const loggedIn = getCookie("token");
// <div className="flex h-screen">
// {/* Left Side */} if (!loggedIn) {
// <div className="w-1/2 p-6 bg-gray-100 dark:bg-gray-800 flex flex-col justify-start items-center"> return (
// {randomTheme ? ( <div>Sign in to be able to join the Theme Survival</div>
// <> );
// <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 */} return (
// <div className="w-1/2 p-6 bg-white dark:bg-gray-900 overflow-y-auto"> <div className="flex h-screen">
// <h3 className="text-xl font-bold text-gray-800 dark:text-white mb-4"> {/* Left Side */}
// Your Votes <div className="w-1/2 p-6 bg-gray-100 dark:bg-gray-800 flex flex-col justify-start items-center">
// </h3> {randomTheme ? (
// <div className="grid grid-cols-4 gap-4"> <>
// {votedThemes.map((theme) => ( <h2 className="text-2xl font-bold text-gray-800 dark:text-white mb-4">
// <div {randomTheme.suggestion}
// key={theme.id} </h2>
// onClick={() => handleResetVote(theme.id)} <div className="flex gap-4">
// className={`p-4 rounded-lg cursor-pointer ${ <button
// theme.slaughterScore > 0 onClick={() => handleVote("YES")}
// ? "bg-green-500 text-white" className="px-6 py-3 bg-green-500 text-white font-bold rounded-lg hover:bg-green-600"
// : theme.slaughterScore < 0 disabled={loading}
// ? "bg-red-500 text-white" >
// : "bg-gray-300 text-black" YES
// }`} </button>
// > <button
// {theme.suggestion} onClick={() => handleVote("NO")}
// </div> className="px-6 py-3 bg-red-500 text-white font-bold rounded-lg hover:bg-red-600"
// ))} disabled={loading}
// </div> >
// </div> NO
// </div> </button>
// ); <button
return <></>; 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>
);
}

View file

@ -1,282 +1,271 @@
"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 { import { getCurrentJam, hasJoinedCurrentJam , ActiveJamResponse } from "@/helpers/jam";
// 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] = const [activeJamResponse, setActiveJamResponse] = useState<ActiveJamResponse | null>(null);
// useState<ActiveJamResponse | null>(null); const [phaseLoading, setPhaseLoading] = useState(true); // Loading state for fetching phase
// 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) { } catch (error: any) {
// if (error instanceof Error) { console.error("Error submitting suggestion:", error.message);
// console.error("Error submitting suggestion:", error.message); setErrorMessage(error.message || "An unexpected error occurred.");
// setErrorMessage(error.message || "An unexpected error occurred."); } finally {
// } else { setLoading(false);
// 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 <div>Sign in to be able to suggest themes</div>; return (
// } <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{" "} The current phase is <strong>{activeJamResponse?.phase || "Unknown"}</strong>. Please come back during the Suggestion phase.
// <strong>{activeJamResponse?.phase || "Unknown"}</strong>. Please come </p>
// back during the Suggestion phase. </div>
// </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&apos;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&apos;t submitted any suggestions yet. You haven't submitted any suggestions yet.
// </p> </p>
// )} )}
// </div> </div>
// </div> </div>
// ); );
return <></>; }
}

View file

@ -1,245 +1,242 @@
"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 { import { getCurrentJam, hasJoinedCurrentJam , ActiveJamResponse } from "@/helpers/jam";
// getCurrentJam,
// hasJoinedCurrentJam,
// ActiveJamResponse,
// } from "@/helpers/jam";
export default function VotingPage() { export default function VotingPage() {
return <></>; const [themes, setThemes] = useState([]);
// const [themes, setThemes] = useState([]); const [loading, setLoading] = useState(false);
// const [loading, setLoading] = useState(false); const [activeJamResponse, setActiveJamResponse] = useState<ActiveJamResponse | null>(null);
// const [activeJamResponse, setActiveJamResponse] = const [hasJoined, setHasJoined] = useState<boolean>(false);
// useState<ActiveJamResponse | null>(null); const [phaseLoading, setPhaseLoading] = useState(true); // Loading state for fetching phase
// const [hasJoined, setHasJoined] = useState<boolean>(false); const token = getCookie("token");
// 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 themes only when phase is "Voting" // Fetch top N themes with voting scores
// useEffect(() => { const fetchThemes = async () => {
// // Fetch top N themes with voting scores if (!token || !activeJamResponse) return;
// async function fetchThemes() {
// if (!token || !activeJamResponse) return; try {
const response = await fetch(
process.env.NEXT_PUBLIC_MODE === "PROD"
? "https://d2jam.com/api/v1/themes/top-themes"
: "http://localhost:3005/api/v1/themes/top-themes",
{
headers: { Authorization: `Bearer ${token}` },
credentials: "include",
}
);
if (response.ok) {
const themes = await response.json();
console.log("Fetched themes:", themes); // Debug log
// Fetch user's votes for these themes
const votesResponse = await fetch(
process.env.NEXT_PUBLIC_MODE === "PROD"
? "https://d2jam.com/api/v1/themes/votes"
: "http://localhost:3005/api/v1/themes/votes",
{
headers: { Authorization: `Bearer ${token}` },
credentials: "include",
}
);
if (votesResponse.ok) {
const votes = await votesResponse.json();
console.log("Fetched votes:", votes); // Debug log
// Merge themes with user's votes
const themesWithVotes = themes.map(theme => {
const vote = votes.find(v => v.themeSuggestionId === theme.id);
console.log(`Theme ${theme.id} vote:`, vote); // Debug log
return {
...theme,
votingScore: vote ? vote.votingScore : null
};
});
console.log("Themes with votes:", themesWithVotes); // Debug log
setThemes(themesWithVotes);
}
} else {
console.error("Failed to fetch themes.");
}
} catch (error) {
console.error("Error fetching themes:", error);
}
};
// try {
// const response = await fetch( // Fetch themes only when phase is "Voting"
// process.env.NEXT_PUBLIC_MODE === "PROD" useEffect(() => {
// ? "https://d2jam.com/api/v1/themes/top-themes" if (activeJamResponse?.phase === "Voting") {
// : "http://localhost:3005/api/v1/themes/top-themes", fetchThemes();
// { }
// headers: { Authorization: `Bearer ${token}` }, }, [activeJamResponse]);
// credentials: "include",
// }
// );
// if (response.ok) {
// const themes = await response.json();
// console.log("Fetched themes:", themes); // Debug log
// // Fetch user's votes for these themes // Handle voting
// const votesResponse = await fetch( const handleVote = async (themeId, votingScore) => {
// process.env.NEXT_PUBLIC_MODE === "PROD" setLoading(true);
// ? "https://d2jam.com/api/v1/themes/votes" try {
// : "http://localhost:3005/api/v1/themes/votes", const response = await fetch(
// { process.env.NEXT_PUBLIC_MODE === "PROD"
// headers: { Authorization: `Bearer ${token}` }, ? "https://d2jam.com/api/v1/themes/vote"
// credentials: "include", : "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 }),
}
);
if (response.ok) {
// Just update the local state instead of re-fetching all themes
setThemes((prevThemes) =>
prevThemes.map((theme) =>
theme.id === themeId
? { ...theme, votingScore }
: theme
)
);
} else {
console.error("Failed to submit vote.");
}
} catch (error) {
console.error("Error submitting vote:", error);
} finally {
setLoading(false);
}
};
// if (votesResponse.ok) { useEffect(() => {
// const votes = await votesResponse.json(); const init = async () => {
// console.log("Fetched votes:", votes); // Debug log const joined = await hasJoinedCurrentJam();
setHasJoined(joined);
setLoading(false);
};
// // Merge themes with user's votes init();
// const themesWithVotes = themes.map((theme) => { }, []);
// const vote = votes.find((v) => v.themeSuggestionId === theme.id);
// console.log(`Theme ${theme.id} vote:`, vote); // Debug log
// return {
// ...theme,
// votingScore: vote ? vote.votingScore : null,
// };
// });
// console.log("Themes with votes:", themesWithVotes); // Debug log
// setThemes(themesWithVotes);
// }
// } else {
// console.error("Failed to fetch themes.");
// }
// } catch (error) {
// console.error("Error fetching themes:", error);
// }
// }
// if (activeJamResponse?.phase === "Voting") { if (phaseLoading || loading) {
// fetchThemes(); return <div>Loading...</div>;
// } }
// }, [activeJamResponse, token]);
// // Handle voting if (!hasJoined) {
// const handleVote = async (themeId, votingScore) => { return (
// setLoading(true); <div className="p-6 bg-gray-100 dark:bg-gray-800 min-h-screen">
// try { <h1 className="text-2xl font-bold text-gray-800 dark:text-white mb-6">
// const response = await fetch( Join the Jam First
// process.env.NEXT_PUBLIC_MODE === "PROD" </h1>
// ? "https://d2jam.com/api/v1/themes/vote" <p className="text-gray-600 dark:text-gray-400">
// : "http://localhost:3005/api/v1/themes/vote", You need to join the current jam before you can vote themes.
// { </p>
// method: "POST", <button
// headers: { onClick={() => joinJam(activeJamResponse?.jam?.id)}
// "Content-Type": "application/json", className="mt-4 px-6 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600"
// Authorization: `Bearer ${token}`, >
// }, Join Jam
// credentials: "include", </button>
// body: JSON.stringify({ suggestionId: themeId, votingScore }), </div>
// } );
// ); }
// if (response.ok) {
// // Just update the local state instead of re-fetching all themes
// setThemes((prevThemes) =>
// prevThemes.map((theme) =>
// theme.id === themeId ? { ...theme, votingScore } : theme
// )
// );
// } else {
// console.error("Failed to submit vote.");
// }
// } catch (error) {
// console.error("Error submitting vote:", error);
// } finally {
// setLoading(false);
// }
// };
// useEffect(() => { if (activeJamResponse?.phase !== "Voting") {
// const init = async () => { return (
// const joined = await hasJoinedCurrentJam(); <div className="p-4 bg-gray-100 dark:bg-gray-800 min-h-screen">
// setHasJoined(joined); <h1 className="text-2xl font-bold text-gray-800 dark:text-white mb-4">
// setLoading(false); 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>
);
}
// init(); const loggedIn = getCookie("token");
// }, []);
if (!loggedIn) {
return (
<div>Sign in to be able to vote</div>
);
}
// if (phaseLoading || loading) { return (
// return <div>Loading...</div>; <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>
// if (!hasJoined) { {/* Theme Suggestion */}
// return ( <div className="text-gray-800 dark:text-white">{theme.suggestion}</div>
// <div className="p-6 bg-gray-100 dark:bg-gray-800 min-h-screen"> </div>
// <h1 className="text-2xl font-bold text-gray-800 dark:text-white mb-6"> ))}
// Join the Jam First </div>
// </h1> </div>
// <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 (activeJamResponse?.phase !== "Voting") {
// 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>
// );
// }
// const loggedIn = getCookie("token");
// if (!loggedIn) {
// return <div>Sign in to be able to vote</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>
// );
}

View file

@ -1,12 +1,15 @@
"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] =
useState<ActiveJamResponse | null>(null); const [activeJamResponse, setActiveJamResponse] = 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(() => {
@ -17,13 +20,15 @@ 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
@ -34,13 +39,16 @@ 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>
); )
} }
} }

View file

@ -18,25 +18,28 @@ 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/active" ? "https://d2jam.com/api/v1/jams"
: "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) {
@ -50,7 +53,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")}`,
@ -77,7 +80,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")}`,
}, },
@ -89,4 +92,4 @@ export async function hasJoinedCurrentJam(): Promise<boolean> {
console.error("Error checking jam participation:", error); console.error("Error checking jam participation:", error);
return false; return false;
} }
} }

View file

@ -3,9 +3,7 @@ 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;