mirror of
https://github.com/Ategon/Jamjar.git
synced 2025-02-12 06:16:21 +00:00
Compare commits
No commits in common. "d8894cd0eb79caba2f829439d551b63d3107f6c1" and "6791e364018668df860bbb277ca8668cfd34851a" have entirely different histories.
d8894cd0eb
...
6791e36401
10 changed files with 745 additions and 1217 deletions
src
app
components
helpers
types
|
@ -1,446 +1,9 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import Editor from "@/components/editor";
|
|
||||||
import { getCookie, hasCookie } from "@/helpers/cookie";
|
|
||||||
import { Avatar, Button, Form, Input, Spacer } from "@nextui-org/react";
|
|
||||||
import { LoaderCircle } from "lucide-react";
|
|
||||||
import { redirect } from "next/navigation";
|
|
||||||
import { ReactNode, useEffect, useState } from "react";
|
|
||||||
import { toast } from "react-toastify";
|
|
||||||
import sanitizeHtml from "sanitize-html";
|
|
||||||
import Select, { MultiValue, StylesConfig } from "react-select";
|
|
||||||
import { useTheme } from "next-themes";
|
|
||||||
import Timers from "@/components/timers";
|
|
||||||
import Streams from "@/components/streams";
|
|
||||||
|
|
||||||
export default function CreateGamePage() {
|
export default function CreateGamePage() {
|
||||||
const [title, setTitle] = useState("");
|
|
||||||
const [content, setContent] = useState("");
|
|
||||||
const [errors, setErrors] = useState({});
|
|
||||||
const [waitingPost, setWaitingPost] = useState(false);
|
|
||||||
const [selectedTags, setSelectedTags] = useState<MultiValue<{
|
|
||||||
value: string;
|
|
||||||
label: ReactNode;
|
|
||||||
isFixed: boolean;
|
|
||||||
}> | null>(null);
|
|
||||||
const [mounted, setMounted] = useState<boolean>(false);
|
|
||||||
const [options, setOptions] = useState<
|
|
||||||
{
|
|
||||||
value: string;
|
|
||||||
label: ReactNode;
|
|
||||||
id: number;
|
|
||||||
isFixed: boolean;
|
|
||||||
}[]
|
|
||||||
>();
|
|
||||||
const { theme } = useTheme();
|
|
||||||
// Add these state variables after existing useState declarations
|
|
||||||
const [windowsLink, setWindowsLink] = useState("");
|
|
||||||
const [linuxLink, setLinuxLink] = useState("");
|
|
||||||
const [macLink, setMacLink] = useState("");
|
|
||||||
const [webGLLink, setWebGLLink] = useState("");
|
|
||||||
const [gameSlug, setGameSlug] = useState("");
|
|
||||||
const [thumbnailUrl, setThumbnailUrl] = useState("");
|
|
||||||
const [authorSearch, setAuthorSearch] = useState("");
|
|
||||||
const [selectedAuthors, setSelectedAuthors] = useState<Array<{id: number, name: string}>>([]);
|
|
||||||
const [searchResults, setSearchResults] = useState<Array<{ id: number; name: string }>>([]);
|
|
||||||
|
|
||||||
// Add this function to handle author search
|
|
||||||
const handleAuthorSearch = async (query: string) => {
|
|
||||||
if (query.length < 2) return;
|
|
||||||
|
|
||||||
const response = await fetch(
|
|
||||||
process.env.NEXT_PUBLIC_MODE === "PROD"
|
|
||||||
? `https://d2jam.com/api/v1/users/search?q=${query}`
|
|
||||||
: `http://localhost:3005/api/v1/users/search?q=${query}`,
|
|
||||||
{
|
|
||||||
headers: { authorization: `Bearer ${getCookie("token")}` },
|
|
||||||
credentials: "include",
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
const data = await response.json();
|
|
||||||
setSearchResults(data);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setMounted(true);
|
|
||||||
|
|
||||||
const load = async () => {
|
|
||||||
const response = 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",
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const user = await response.json();
|
|
||||||
|
|
||||||
const tagResponse = await fetch(
|
|
||||||
process.env.NEXT_PUBLIC_MODE === "PROD"
|
|
||||||
? `https://d2jam.com/api/v1/tags`
|
|
||||||
: `http://localhost:3005/api/v1/tags`
|
|
||||||
);
|
|
||||||
|
|
||||||
if (tagResponse.ok) {
|
|
||||||
const newoptions: {
|
|
||||||
value: string;
|
|
||||||
label: ReactNode;
|
|
||||||
id: number;
|
|
||||||
isFixed: boolean;
|
|
||||||
}[] = [];
|
|
||||||
|
|
||||||
for (const tag of await tagResponse.json()) {
|
|
||||||
if (tag.modOnly && !user.mod) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
newoptions.push({
|
|
||||||
value: tag.name,
|
|
||||||
id: tag.id,
|
|
||||||
label: (
|
|
||||||
<div className="flex gap-2 items-center">
|
|
||||||
{tag.icon && (
|
|
||||||
<Avatar
|
|
||||||
className="w-6 h-6 min-w-6 min-h-6"
|
|
||||||
size="sm"
|
|
||||||
src={tag.icon}
|
|
||||||
classNames={{ base: "bg-transparent" }}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<p>
|
|
||||||
{tag.name}
|
|
||||||
{tag.modOnly ? " (Mod Only)" : ""}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
isFixed: tag.alwaysAdded,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setOptions(newoptions);
|
|
||||||
setSelectedTags(newoptions.filter((tag) => tag.isFixed));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
load();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const styles: StylesConfig<
|
|
||||||
{
|
|
||||||
value: string;
|
|
||||||
label: ReactNode;
|
|
||||||
isFixed: boolean;
|
|
||||||
},
|
|
||||||
true
|
|
||||||
> = {
|
|
||||||
multiValue: (base, state) => {
|
|
||||||
return {
|
|
||||||
...base,
|
|
||||||
backgroundColor: state.data.isFixed
|
|
||||||
? theme == "dark"
|
|
||||||
? "#222"
|
|
||||||
: "#ddd"
|
|
||||||
: theme == "dark"
|
|
||||||
? "#444"
|
|
||||||
: "#eee",
|
|
||||||
};
|
|
||||||
},
|
|
||||||
multiValueLabel: (base, state) => {
|
|
||||||
return {
|
|
||||||
...base,
|
|
||||||
color: state.data.isFixed
|
|
||||||
? theme == "dark"
|
|
||||||
? "#ddd"
|
|
||||||
: "#222"
|
|
||||||
: theme == "dark"
|
|
||||||
? "#fff"
|
|
||||||
: "#444",
|
|
||||||
fontWeight: state.data.isFixed ? "normal" : "bold",
|
|
||||||
paddingRight: state.data.isFixed ? "8px" : "2px",
|
|
||||||
};
|
|
||||||
},
|
|
||||||
multiValueRemove: (base, state) => {
|
|
||||||
return {
|
|
||||||
...base,
|
|
||||||
display: state.data.isFixed ? "none" : "flex",
|
|
||||||
color: theme == "dark" ? "#ddd" : "#222",
|
|
||||||
};
|
|
||||||
},
|
|
||||||
control: (styles) => ({
|
|
||||||
...styles,
|
|
||||||
backgroundColor: theme == "dark" ? "#181818" : "#fff",
|
|
||||||
minWidth: "300px",
|
|
||||||
}),
|
|
||||||
menu: (styles) => ({
|
|
||||||
...styles,
|
|
||||||
backgroundColor: theme == "dark" ? "#181818" : "#fff",
|
|
||||||
color: theme == "dark" ? "#fff" : "#444",
|
|
||||||
}),
|
|
||||||
option: (styles, { isFocused }) => ({
|
|
||||||
...styles,
|
|
||||||
backgroundColor: isFocused
|
|
||||||
? theme == "dark"
|
|
||||||
? "#333"
|
|
||||||
: "#ddd"
|
|
||||||
: undefined,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="static flex items-top mt-20 justify-center top-0 left-0">
|
<div className="absolute flex items-center justify-center top-0 left-0 w-screen h-screen">
|
||||||
<Form
|
<p>Game creation coming soon</p>
|
||||||
className="w-full max-w-2xl flex flex-col gap-4"
|
|
||||||
validationErrors={errors}
|
|
||||||
onSubmit={async (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
if (!title && !content) {
|
|
||||||
setErrors({
|
|
||||||
title: "Please enter a valid title",
|
|
||||||
content: "Please enter valid content",
|
|
||||||
});
|
|
||||||
toast.error("Please enter valid content");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!title) {
|
|
||||||
setErrors({ title: "Please enter a valid title" });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!content) {
|
|
||||||
setErrors({ content: "Please enter valid content" });
|
|
||||||
toast.error("Please enter valid content");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasCookie("token")) {
|
|
||||||
setErrors({ content: "You are not logged in" });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const sanitizedHtml = sanitizeHtml(content);
|
|
||||||
setWaitingPost(true);
|
|
||||||
|
|
||||||
const tags = [];
|
|
||||||
|
|
||||||
if (selectedTags) {
|
|
||||||
for (const tag of selectedTags) {
|
|
||||||
tags.push(
|
|
||||||
options?.filter((option) => option.value == tag.value)[0].id
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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({
|
|
||||||
title: title,
|
|
||||||
content: sanitizedHtml,
|
|
||||||
username: getCookie("user"),
|
|
||||||
tags,
|
|
||||||
gameSlug,
|
|
||||||
thumbnailUrl,
|
|
||||||
windowsLink,
|
|
||||||
linuxLink,
|
|
||||||
macLink,
|
|
||||||
webGLLink,
|
|
||||||
authors: selectedAuthors.map(author => author.id)
|
|
||||||
}),
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
authorization: `Bearer ${getCookie("token")}`,
|
|
||||||
},
|
|
||||||
credentials: "include",
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response.status == 401) {
|
|
||||||
setErrors({ content: "Invalid user" });
|
|
||||||
setWaitingPost(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
toast.success("Successfully created post");
|
|
||||||
setWaitingPost(false);
|
|
||||||
redirect("/");
|
|
||||||
} else {
|
|
||||||
toast.error("An error occured");
|
|
||||||
setWaitingPost(false);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Input
|
|
||||||
isRequired
|
|
||||||
label="Game Name"
|
|
||||||
labelPlacement="outside"
|
|
||||||
name="title"
|
|
||||||
placeholder="Enter your game name"
|
|
||||||
type="text"
|
|
||||||
value={title}
|
|
||||||
onValueChange={setTitle}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Input
|
|
||||||
label="Game Slug"
|
|
||||||
labelPlacement="outside"
|
|
||||||
placeholder="your-game-name"
|
|
||||||
value={gameSlug}
|
|
||||||
onValueChange={setGameSlug}
|
|
||||||
description="This will be used in the URL: d2jam.com/games/your-game-name"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
<label className="text-sm font-medium">Add Authors</label>
|
|
||||||
<Input
|
|
||||||
placeholder="Search users..."
|
|
||||||
value={authorSearch}
|
|
||||||
onValueChange={(value) => {
|
|
||||||
setAuthorSearch(value);
|
|
||||||
handleAuthorSearch(value);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{searchResults.length > 0 && (
|
|
||||||
<div className="bg-gray-100 dark:bg-gray-800 rounded-lg p-2">
|
|
||||||
{searchResults.map((user) => (
|
|
||||||
<div
|
|
||||||
key={user.id}
|
|
||||||
className="flex justify-between items-center p-2 hover:bg-gray-200 dark:hover:bg-gray-700 rounded cursor-pointer"
|
|
||||||
onClick={() => {
|
|
||||||
setSelectedAuthors([...selectedAuthors, user]);
|
|
||||||
setSearchResults([]);
|
|
||||||
setAuthorSearch("");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span>{user.name}</span>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="flex flex-wrap gap-2 mt-2">
|
|
||||||
{selectedAuthors.map((author) => (
|
|
||||||
<div
|
|
||||||
key={author.id}
|
|
||||||
className="bg-blue-100 dark:bg-blue-800 text-blue-800 dark:text-blue-100 px-3 py-1 rounded-full flex items-center gap-2"
|
|
||||||
>
|
|
||||||
<span>{author.name}</span>
|
|
||||||
<button
|
|
||||||
onClick={() => setSelectedAuthors(selectedAuthors.filter(a => a.id !== author.id))}
|
|
||||||
className="text-sm hover:text-red-500"
|
|
||||||
>
|
|
||||||
×
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<label className="text-sm font-medium">Game Description</label>
|
|
||||||
<Editor content={content} setContent={setContent} gameEditor />
|
|
||||||
|
|
||||||
<Spacer />
|
|
||||||
|
|
||||||
{mounted && (
|
|
||||||
<Select
|
|
||||||
styles={styles}
|
|
||||||
isMulti
|
|
||||||
value={selectedTags}
|
|
||||||
onChange={(value) => setSelectedTags(value)}
|
|
||||||
options={options}
|
|
||||||
isClearable={false}
|
|
||||||
isOptionDisabled={() =>
|
|
||||||
selectedTags != null && selectedTags.length >= 5
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Spacer />
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div className="flex flex-col gap-4">
|
|
||||||
|
|
||||||
|
|
||||||
<Input
|
|
||||||
label="Thumbnail URL"
|
|
||||||
labelPlacement="outside"
|
|
||||||
placeholder="https://example.com/thumbnail.png"
|
|
||||||
value={thumbnailUrl}
|
|
||||||
onValueChange={setThumbnailUrl}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
|
||||||
<Input
|
|
||||||
label="Windows Download"
|
|
||||||
placeholder="https://example.com/game-windows.zip"
|
|
||||||
value={windowsLink}
|
|
||||||
onValueChange={setWindowsLink}
|
|
||||||
/>
|
|
||||||
<Input
|
|
||||||
label="Linux Download"
|
|
||||||
placeholder="https://example.com/game-linux.zip"
|
|
||||||
value={linuxLink}
|
|
||||||
onValueChange={setLinuxLink}
|
|
||||||
/>
|
|
||||||
<Input
|
|
||||||
label="Mac Download"
|
|
||||||
placeholder="https://example.com/game-mac.zip"
|
|
||||||
value={macLink}
|
|
||||||
onValueChange={setMacLink}
|
|
||||||
/>
|
|
||||||
<Input
|
|
||||||
label="WebGL Link"
|
|
||||||
placeholder="https://example.com/game-webgl"
|
|
||||||
value={webGLLink}
|
|
||||||
onValueChange={setWebGLLink}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div className="bg-gray-100 dark:bg-gray-800 p-4 rounded-lg">
|
|
||||||
<h3 className="text-lg font-bold mb-4">Game Metrics</h3>
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
|
||||||
<div className="bg-white dark:bg-gray-900 p-3 rounded-lg">
|
|
||||||
<p className="text-sm text-gray-600 dark:text-gray-400">Views</p>
|
|
||||||
<p className="text-2xl font-bold">0</p>
|
|
||||||
</div>
|
|
||||||
<div className="bg-white dark:bg-gray-900 p-3 rounded-lg">
|
|
||||||
<p className="text-sm text-gray-600 dark:text-gray-400">Downloads</p>
|
|
||||||
<p className="text-2xl font-bold">0</p>
|
|
||||||
</div>
|
|
||||||
<div className="bg-white dark:bg-gray-900 p-3 rounded-lg">
|
|
||||||
<p className="text-sm text-gray-600 dark:text-gray-400">Rating</p>
|
|
||||||
<p className="text-2xl font-bold">N/A</p>
|
|
||||||
</div>
|
|
||||||
<div className="bg-white dark:bg-gray-900 p-3 rounded-lg">
|
|
||||||
<p className="text-sm text-gray-600 dark:text-gray-400">Comments</p>
|
|
||||||
<p className="text-2xl font-bold">0</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<Button color="primary" type="submit">
|
|
||||||
{waitingPost ? (
|
|
||||||
<LoaderCircle className="animate-spin" size={16} />
|
|
||||||
) : (
|
|
||||||
<p>Create</p>
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Form>
|
|
||||||
<div className="flex flex-col gap-4 px-8 items-end">
|
|
||||||
<Timers />
|
|
||||||
<Streams />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,8 +17,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 Timers from "@/components/timers";
|
|
||||||
import Streams from "@/components/streams";
|
|
||||||
import { UserType } from "@/types/UserType";
|
import { UserType } from "@/types/UserType";
|
||||||
|
|
||||||
export default function CreatePostPage() {
|
export default function CreatePostPage() {
|
||||||
|
@ -170,7 +168,7 @@ export default function CreatePostPage() {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="absolute flex items-top mt-40 justify-center top-0 left-0 w-screen h-screen">
|
<div className="absolute flex items-center justify-center top-0 left-0 w-screen h-screen">
|
||||||
<Form
|
<Form
|
||||||
className="w-full max-w-2xl flex flex-col gap-4"
|
className="w-full max-w-2xl flex flex-col gap-4"
|
||||||
validationErrors={errors}
|
validationErrors={errors}
|
||||||
|
@ -307,10 +305,6 @@ export default function CreatePostPage() {
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
<div className="flex flex-col gap-4 px-8 items-end">
|
</div>
|
||||||
<Timers />
|
|
||||||
<Streams />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,12 +40,11 @@ import { useTheme } from "next-themes";
|
||||||
type EditorProps = {
|
type EditorProps = {
|
||||||
content: string;
|
content: string;
|
||||||
setContent: (content: string) => void;
|
setContent: (content: string) => void;
|
||||||
gameEditor?: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const limit = 32767;
|
const limit = 32767;
|
||||||
|
|
||||||
export default function Editor({ content, setContent,gameEditor }: EditorProps) {
|
export default function Editor({ content, setContent }: EditorProps) {
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
|
|
||||||
const editor = useEditor({
|
const editor = useEditor({
|
||||||
|
@ -98,11 +97,7 @@ export default function Editor({ content, setContent,gameEditor }: EditorProps)
|
||||||
editorProps: {
|
editorProps: {
|
||||||
attributes: {
|
attributes: {
|
||||||
class:
|
class:
|
||||||
"prose dark:prose-invert " +
|
"prose dark:prose-invert min-h-[150px] max-h-[400px] overflow-y-auto cursor-text rounded-md border p-5 focus-within:outline-none focus-within:border-blue-500 !duration-250 !ease-linear !transition-all",
|
||||||
(gameEditor
|
|
||||||
? "min-h-[600px] max-h-[600px]"
|
|
||||||
: "min-h-[150px] max-h-[400px]") +
|
|
||||||
" overflow-y-auto cursor-text rounded-md border p-5 focus-within:outline-none focus-within:border-blue-500 !duration-250 !ease-linear !transition-all",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -111,10 +111,11 @@ export default function JamHeader() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* 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
|
||||||
|
@ -125,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
|
||||||
|
@ -136,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
|
||||||
|
|
|
@ -1,296 +1,286 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { useState, useEffect, useCallback } from "react";
|
// import React, { useState, useEffect, useCallback } from "react";
|
||||||
import { getCookie } from "@/helpers/cookie";
|
// import { getCookie } from "@/helpers/cookie";
|
||||||
import {
|
// import {
|
||||||
getCurrentJam,
|
// getCurrentJam,
|
||||||
hasJoinedCurrentJam,
|
// hasJoinedCurrentJam,
|
||||||
ActiveJamResponse,
|
// ActiveJamResponse,
|
||||||
joinJam
|
// } from "@/helpers/jam";
|
||||||
} from "@/helpers/jam";
|
|
||||||
import {ThemeType} from "@/types/ThemeType";
|
|
||||||
|
|
||||||
export default function ThemeSlaughter() {
|
export default function ThemeSlaughter() {
|
||||||
const [randomTheme, setRandomTheme] = useState<ThemeType | null>(null);
|
// const [randomTheme, setRandomTheme] = useState(null);
|
||||||
const [votedThemes, setVotedThemes] = useState<ThemeType[]>([]);
|
// const [votedThemes, setVotedThemes] = useState([]);
|
||||||
const [loading, setLoading] = useState(false);
|
// const [loading, setLoading] = useState(false);
|
||||||
const [token, setToken] = useState<string | null>(null);
|
// const [token, setToken] = useState<string | null>(null);
|
||||||
const [hasJoined, setHasJoined] = useState<boolean>(false);
|
// const [hasJoined, setHasJoined] = useState<boolean>(false);
|
||||||
const [activeJamResponse, setActiveJam] = useState<ActiveJamResponse | null>(
|
// const [activeJamResponse, setActiveJam] = useState<ActiveJamResponse | null>(
|
||||||
null
|
// null
|
||||||
);
|
// );
|
||||||
const [phaseLoading, setPhaseLoading] = useState(true);
|
// const [phaseLoading, setPhaseLoading] = useState(true);
|
||||||
|
|
||||||
// Fetch token on the client side
|
// // Fetch token on the client side
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
const fetchedToken = getCookie("token");
|
// const fetchedToken = getCookie("token");
|
||||||
setToken(fetchedToken);
|
// setToken(fetchedToken);
|
||||||
}, []);
|
// }, []);
|
||||||
|
|
||||||
// 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();
|
||||||
setActiveJam(activeJam); // Set active jam details
|
// setActiveJam(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 a random theme
|
// // Fetch a random theme
|
||||||
const fetchRandomTheme = useCallback(async () => {
|
// const fetchRandomTheme = useCallback(async () => {
|
||||||
if (!token) return; // Wait until token is available
|
// if (!token) return; // Wait until token is available
|
||||||
if (!activeJamResponse) return;
|
// if (!activeJamResponse) return;
|
||||||
if (
|
// if (
|
||||||
activeJamResponse &&
|
// activeJamResponse &&
|
||||||
activeJamResponse.jam &&
|
// activeJamResponse.jam &&
|
||||||
activeJamResponse.phase != "Survival"
|
// activeJamResponse.phase != "Survival"
|
||||||
) {
|
// ) {
|
||||||
return (
|
// return (
|
||||||
<div>
|
// <div>
|
||||||
<h1>It's not Theme Survival phase.</h1>
|
// <h1>It's not Theme Survival phase.</h1>
|
||||||
</div>
|
// </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/random"
|
||||||
: "http://localhost:3005/api/v1/themes/random",
|
// : "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();
|
||||||
setRandomTheme(data);
|
// setRandomTheme(data);
|
||||||
} else {
|
// } else {
|
||||||
console.error("Failed to fetch random theme.");
|
// console.error("Failed to fetch random theme.");
|
||||||
}
|
// }
|
||||||
} catch (error) {
|
// } catch (error) {
|
||||||
console.error("Error fetching random theme:", error);
|
// console.error("Error fetching random theme:", error);
|
||||||
}
|
// }
|
||||||
}, [activeJamResponse, token]);
|
// }, [activeJamResponse, token]);
|
||||||
|
|
||||||
// Fetch voted themes
|
// // Fetch voted themes
|
||||||
const fetchVotedThemes = useCallback(async () => {
|
// const fetchVotedThemes = useCallback(async () => {
|
||||||
if (!token) return; // Wait until token is available
|
// if (!token) return; // Wait until token is available
|
||||||
|
|
||||||
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}` },
|
// 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);
|
// setVotedThemes(data);
|
||||||
} else {
|
// } else {
|
||||||
console.error("Failed to fetch voted themes.");
|
// console.error("Failed to fetch voted themes.");
|
||||||
}
|
// }
|
||||||
} catch (error) {
|
// } catch (error) {
|
||||||
console.error("Error fetching voted themes:", error);
|
// console.error("Error fetching voted themes:", error);
|
||||||
}
|
// }
|
||||||
}, [token]);
|
// }, [token]);
|
||||||
|
|
||||||
// Handle voting
|
// // Handle voting
|
||||||
const handleVote = async (voteType: string) => {
|
// const handleVote = async (voteType: string) => {
|
||||||
if (!randomTheme) return;
|
// if (!randomTheme) return;
|
||||||
|
|
||||||
setLoading(true);
|
// 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",
|
||||||
{
|
// {
|
||||||
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({
|
// body: JSON.stringify({
|
||||||
suggestionId: randomTheme.id,
|
// suggestionId: randomTheme.id,
|
||||||
voteType,
|
// voteType,
|
||||||
}),
|
// }),
|
||||||
}
|
// }
|
||||||
);
|
// );
|
||||||
|
|
||||||
if (response.ok) {
|
// if (response.ok) {
|
||||||
// Refresh data after voting
|
// // Refresh data after voting
|
||||||
fetchRandomTheme();
|
// fetchRandomTheme();
|
||||||
fetchVotedThemes();
|
// fetchVotedThemes();
|
||||||
} else {
|
// } else {
|
||||||
console.error("Failed to submit vote.");
|
// console.error("Failed to submit vote.");
|
||||||
}
|
// }
|
||||||
} catch (error) {
|
// } catch (error) {
|
||||||
console.error("Error submitting vote:", error);
|
// console.error("Error submitting vote:", error);
|
||||||
} finally {
|
// } finally {
|
||||||
setLoading(false);
|
// setLoading(false);
|
||||||
}
|
// }
|
||||||
};
|
// };
|
||||||
|
|
||||||
// Handle resetting a vote from the grid
|
// // Handle resetting a vote from the grid
|
||||||
const handleResetVote = async (themeId: number) => {
|
// const handleResetVote = async (themeId: number) => {
|
||||||
try {
|
// try {
|
||||||
const theme = votedThemes.find((theme) => theme.id === themeId);
|
// setRandomTheme(votedThemes.find((theme) => theme.id === themeId));
|
||||||
if (theme) {
|
// setVotedThemes((prev) =>
|
||||||
setRandomTheme(theme);
|
// prev.map((theme) =>
|
||||||
setVotedThemes((prev) =>
|
// theme.id === themeId ? { ...theme, slaughterScore: 0 } : theme
|
||||||
prev.map((t) =>
|
// )
|
||||||
t.id === themeId ? { ...t, slaughterScore: 0 } : t
|
// );
|
||||||
)
|
// } catch (error) {
|
||||||
);
|
// console.error("Error resetting vote:", error);
|
||||||
}
|
// }
|
||||||
} catch (error) {
|
// };
|
||||||
console.error("Error resetting vote:", error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
if (token && activeJamResponse?.phase === "Survival") {
|
// if (token && activeJamResponse?.phase === "Survival") {
|
||||||
fetchRandomTheme();
|
// fetchRandomTheme();
|
||||||
fetchVotedThemes();
|
// fetchVotedThemes();
|
||||||
}
|
// }
|
||||||
}, [token, activeJamResponse, fetchRandomTheme, fetchVotedThemes]);
|
// }, [token, activeJamResponse, fetchRandomTheme, fetchVotedThemes]);
|
||||||
|
|
||||||
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();
|
||||||
}, []);
|
// }, []);
|
||||||
|
|
||||||
if (phaseLoading || loading) {
|
// if (phaseLoading || loading) {
|
||||||
return <div>Loading...</div>;
|
// 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 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
|
// // Render message if not in Theme Slaughter phase
|
||||||
if (activeJamResponse?.phase !== "Survival") {
|
// if (activeJamResponse?.phase !== "Survival") {
|
||||||
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 Theme Slaughter Phase
|
// Not in Theme Slaughter 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
|
// <strong>{activeJamResponse?.phase || "Unknown"}</strong>. Please come
|
||||||
back during the Theme Slaughter phase.
|
// back during the Theme Slaughter phase.
|
||||||
</p>
|
// </p>
|
||||||
</div>
|
// </div>
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
|
|
||||||
const loggedIn = getCookie("token");
|
// const loggedIn = getCookie("token");
|
||||||
if (!loggedIn) {
|
|
||||||
return <div>Sign in to be able to join the Theme Survival</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasJoined) {
|
// if (!loggedIn) {
|
||||||
return (
|
// return <div>Sign in to be able to join the Theme Survival</div>;
|
||||||
<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={() => {
|
|
||||||
if (activeJamResponse?.jam?.id !== undefined) {
|
|
||||||
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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
// return (
|
||||||
<div className="flex h-screen">
|
// <div className="flex h-screen">
|
||||||
{/* Left Side */}
|
// {/* Left Side */}
|
||||||
<div className="w-1/2 p-6 bg-gray-100 dark:bg-gray-800 flex flex-col justify-start items-center">
|
// <div className="w-1/2 p-6 bg-gray-100 dark:bg-gray-800 flex flex-col justify-start items-center">
|
||||||
{randomTheme ? (
|
// {randomTheme ? (
|
||||||
<>
|
// <>
|
||||||
<h2 className="text-2xl font-bold text-gray-800 dark:text-white mb-4">
|
// <h2 className="text-2xl font-bold text-gray-800 dark:text-white mb-4">
|
||||||
{randomTheme.suggestion}
|
// {randomTheme.suggestion}
|
||||||
</h2>
|
// </h2>
|
||||||
<div className="flex gap-4">
|
// <div className="flex gap-4">
|
||||||
<button
|
// <button
|
||||||
onClick={() => handleVote("YES")}
|
// onClick={() => handleVote("YES")}
|
||||||
className="px-6 py-3 bg-green-500 text-white font-bold rounded-lg hover:bg-green-600"
|
// className="px-6 py-3 bg-green-500 text-white font-bold rounded-lg hover:bg-green-600"
|
||||||
disabled={loading}
|
// disabled={loading}
|
||||||
>
|
// >
|
||||||
YES
|
// YES
|
||||||
</button>
|
// </button>
|
||||||
<button
|
// <button
|
||||||
onClick={() => handleVote("NO")}
|
// onClick={() => handleVote("NO")}
|
||||||
className="px-6 py-3 bg-red-500 text-white font-bold rounded-lg hover:bg-red-600"
|
// className="px-6 py-3 bg-red-500 text-white font-bold rounded-lg hover:bg-red-600"
|
||||||
disabled={loading}
|
// disabled={loading}
|
||||||
>
|
// >
|
||||||
NO
|
// NO
|
||||||
</button>
|
// </button>
|
||||||
<button
|
// <button
|
||||||
onClick={() => handleVote("SKIP")}
|
// onClick={() => handleVote("SKIP")}
|
||||||
className="px-6 py-3 bg-gray-500 text-white font-bold rounded-lg hover:bg-gray-600"
|
// className="px-6 py-3 bg-gray-500 text-white font-bold rounded-lg hover:bg-gray-600"
|
||||||
disabled={loading}
|
// disabled={loading}
|
||||||
>
|
// >
|
||||||
SKIP
|
// SKIP
|
||||||
</button>
|
// </button>
|
||||||
</div>
|
// </div>
|
||||||
</>
|
// </>
|
||||||
) : (
|
// ) : (
|
||||||
<p className="text-gray-600 dark:text-gray-400">
|
// <p className="text-gray-600 dark:text-gray-400">
|
||||||
No themes available.
|
// No themes available.
|
||||||
</p>
|
// </p>
|
||||||
)}
|
// )}
|
||||||
</div>
|
// </div>
|
||||||
|
|
||||||
{/* Right Side */}
|
// {/* Right Side */}
|
||||||
<div className="w-1/2 p-6 bg-white dark:bg-gray-900 overflow-y-auto">
|
// <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">
|
// <h3 className="text-xl font-bold text-gray-800 dark:text-white mb-4">
|
||||||
Your Votes
|
// Your Votes
|
||||||
</h3>
|
// </h3>
|
||||||
<div className="grid grid-cols-4 gap-4">
|
// <div className="grid grid-cols-4 gap-4">
|
||||||
{votedThemes.map((theme) => (
|
// {votedThemes.map((theme) => (
|
||||||
<div
|
// <div
|
||||||
key={theme.id}
|
// key={theme.id}
|
||||||
onClick={() => handleResetVote(theme.id)}
|
// onClick={() => handleResetVote(theme.id)}
|
||||||
className={`p-4 rounded-lg cursor-pointer ${
|
// className={`p-4 rounded-lg cursor-pointer ${
|
||||||
theme.slaughterScore > 0
|
// theme.slaughterScore > 0
|
||||||
? "bg-green-500 text-white"
|
// ? "bg-green-500 text-white"
|
||||||
: theme.slaughterScore < 0
|
// : theme.slaughterScore < 0
|
||||||
? "bg-red-500 text-white"
|
// ? "bg-red-500 text-white"
|
||||||
: "bg-gray-300 text-black"
|
// : "bg-gray-300 text-black"
|
||||||
}`}
|
// }`}
|
||||||
>
|
// >
|
||||||
{theme.suggestion}
|
// {theme.suggestion}
|
||||||
</div>
|
// </div>
|
||||||
))}
|
// ))}
|
||||||
</div>
|
// </div>
|
||||||
</div>
|
// </div>
|
||||||
</div>
|
// </div>
|
||||||
);
|
// );
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,287 +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 {
|
// import {
|
||||||
getCurrentJam,
|
// getCurrentJam,
|
||||||
hasJoinedCurrentJam,
|
// hasJoinedCurrentJam,
|
||||||
ActiveJamResponse,
|
// ActiveJamResponse,
|
||||||
} from "@/helpers/jam";
|
// } from "@/helpers/jam";
|
||||||
import { ThemeType } from "@/types/ThemeType";
|
|
||||||
import { joinJam } 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<ThemeType[]>([]);
|
// 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) {
|
||||||
if (error instanceof Error) {
|
// 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.");
|
||||||
} else {
|
// } else {
|
||||||
console.error("Unknown error:", error);
|
// console.error("Unknown error:", error);
|
||||||
setErrorMessage("An unexpected error occurred.");
|
// setErrorMessage("An unexpected error occurred.");
|
||||||
}
|
// }
|
||||||
} finally {
|
// } finally {
|
||||||
setLoading(false);
|
// 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>;
|
||||||
}
|
// }
|
||||||
|
|
||||||
const token = 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 suggest 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 (!token) {
|
// const token = getCookie("token");
|
||||||
return <div>Sign in to be able to suggest themes</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasJoined) {
|
// if (!token) {
|
||||||
return (
|
// return <div>Sign in to be able to suggest themes</div>;
|
||||||
<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 suggest themes.
|
|
||||||
</p>
|
|
||||||
<button
|
|
||||||
onClick={() => {
|
|
||||||
if (activeJamResponse?.jam?.id !== undefined) {
|
|
||||||
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 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
|
// <strong>{activeJamResponse?.phase || "Unknown"}</strong>. Please come
|
||||||
back during the Suggestion phase.
|
// back during the Suggestion phase.
|
||||||
</p>
|
// </p>
|
||||||
</div>
|
// </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,248 +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 {
|
// import {
|
||||||
getCurrentJam,
|
// getCurrentJam,
|
||||||
hasJoinedCurrentJam,
|
// hasJoinedCurrentJam,
|
||||||
ActiveJamResponse,
|
// ActiveJamResponse,
|
||||||
} from "@/helpers/jam";
|
// } from "@/helpers/jam";
|
||||||
import { ThemeType } from "@/types/ThemeType";
|
|
||||||
import { joinJam } from "@/helpers/jam";
|
|
||||||
|
|
||||||
interface VoteType {
|
|
||||||
themeSuggestionId: number;
|
|
||||||
votingScore: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function VotingPage() {
|
export default function VotingPage() {
|
||||||
const [themes, setThemes] = useState<ThemeType[]>([]);
|
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 themes only when phase is "Voting"
|
// // Fetch themes only when phase is "Voting"
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
// Fetch top N themes with voting scores
|
// // Fetch top N themes with voting scores
|
||||||
async function fetchThemes() {
|
// async function fetchThemes() {
|
||||||
if (!token || !activeJamResponse) return;
|
// 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
|
||||||
|
|
||||||
// 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();
|
||||||
// Merge themes with user's votes
|
// console.log("Fetched votes:", votes); // Debug log
|
||||||
const themesWithVotes = themes.map((theme: ThemeType) => {
|
|
||||||
const vote = votes.find((v: VoteType) => v.themeSuggestionId === theme.id);
|
|
||||||
return {
|
|
||||||
...theme,
|
|
||||||
votingScore: vote ? vote.votingScore : null,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
setThemes(themesWithVotes);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.error("Failed to fetch themes.");
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error fetching themes:", error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (activeJamResponse?.phase === "Voting") {
|
// // Merge themes with user's votes
|
||||||
fetchThemes();
|
// const themesWithVotes = themes.map((theme) => {
|
||||||
}
|
// const vote = votes.find((v) => v.themeSuggestionId === theme.id);
|
||||||
}, [activeJamResponse, token]);
|
// console.log(`Theme ${theme.id} vote:`, vote); // Debug log
|
||||||
|
// return {
|
||||||
|
// ...theme,
|
||||||
|
// votingScore: vote ? vote.votingScore : null,
|
||||||
|
// };
|
||||||
|
// });
|
||||||
|
|
||||||
// Handle voting
|
// console.log("Themes with votes:", themesWithVotes); // Debug log
|
||||||
const handleVote = async (themeId: number, votingScore: number) => {
|
// setThemes(themesWithVotes);
|
||||||
setLoading(true);
|
// }
|
||||||
try {
|
// } else {
|
||||||
const response = await fetch(
|
// console.error("Failed to fetch themes.");
|
||||||
process.env.NEXT_PUBLIC_MODE === "PROD"
|
// }
|
||||||
? "https://d2jam.com/api/v1/themes/vote"
|
// } catch (error) {
|
||||||
: "http://localhost:3005/api/v1/themes/vote",
|
// console.error("Error fetching themes:", error);
|
||||||
{
|
// }
|
||||||
method: "POST",
|
// }
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: `Bearer ${token}`,
|
|
||||||
},
|
|
||||||
credentials: "include",
|
|
||||||
body: JSON.stringify({ suggestionId: themeId, votingScore }),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response.ok) {
|
// if (activeJamResponse?.phase === "Voting") {
|
||||||
// Just update the local state instead of re-fetching all themes
|
// fetchThemes();
|
||||||
setThemes((prevThemes) =>
|
// }
|
||||||
prevThemes.map((theme) =>
|
// }, [activeJamResponse, token]);
|
||||||
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(() => {
|
// // Handle voting
|
||||||
const init = async () => {
|
// const handleVote = async (themeId, votingScore) => {
|
||||||
const joined = await hasJoinedCurrentJam();
|
// setLoading(true);
|
||||||
setHasJoined(joined);
|
// try {
|
||||||
setLoading(false);
|
// const response = await fetch(
|
||||||
};
|
// 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 }),
|
||||||
|
// }
|
||||||
|
// );
|
||||||
|
|
||||||
init();
|
// 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 (phaseLoading || loading) {
|
// useEffect(() => {
|
||||||
return <div>Loading...</div>;
|
// const init = async () => {
|
||||||
}
|
// const joined = await hasJoinedCurrentJam();
|
||||||
|
// setHasJoined(joined);
|
||||||
|
// setLoading(false);
|
||||||
|
// };
|
||||||
|
|
||||||
if (activeJamResponse?.phase !== "Voting") {
|
// init();
|
||||||
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 (phaseLoading || loading) {
|
||||||
|
// return <div>Loading...</div>;
|
||||||
|
// }
|
||||||
|
|
||||||
if (!loggedIn) {
|
// if (!hasJoined) {
|
||||||
return <div>Sign in to be able to vote</div>;
|
// 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 (!hasJoined) {
|
// if (activeJamResponse?.phase !== "Voting") {
|
||||||
return (
|
// return (
|
||||||
<div className="p-6 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-6">
|
// <h1 className="text-2xl font-bold text-gray-800 dark:text-white mb-4">
|
||||||
Join the Jam First
|
// Not in Voting Phase
|
||||||
</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 vote themes.
|
// The current phase is{" "}
|
||||||
</p>
|
// <strong>{activeJamResponse?.phase || "Unknown"}</strong>. Please come
|
||||||
<button
|
// back during the Voting phase.
|
||||||
onClick={() => {
|
// </p>
|
||||||
if (activeJamResponse?.jam?.id !== undefined) {
|
// </div>
|
||||||
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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
// const loggedIn = getCookie("token");
|
||||||
<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 */}
|
// if (!loggedIn) {
|
||||||
<div className="text-gray-800 dark:text-white">
|
// return <div>Sign in to be able to vote</div>;
|
||||||
{theme.suggestion}
|
// }
|
||||||
</div>
|
|
||||||
</div>
|
// return (
|
||||||
))}
|
// <div className="p-3 bg-gray-100 dark:bg-gray-800 min-h-screen">
|
||||||
</div>
|
// <h1 className="text-2xl font-bold text-gray-800 dark:text-white mb-4">
|
||||||
</div>
|
// 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>
|
||||||
|
// );
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,8 +74,8 @@ export async function hasJoinedCurrentJam(): Promise<boolean> {
|
||||||
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/participation"
|
? "https://d2jam.com/api/v1/participation"
|
||||||
: "http://localhost:3005/api/v1/jams/participation",
|
: "http://localhost:3005/api/v1/participation",
|
||||||
{
|
{
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: {
|
headers: {
|
||||||
|
|
|
@ -9,5 +9,4 @@ export interface JamType {
|
||||||
startTime: Date;
|
startTime: Date;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
themePerUser: number;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
export interface ThemeType {
|
|
||||||
id: number;
|
|
||||||
suggestion: string;
|
|
||||||
slaughterScore: number;
|
|
||||||
votingScore: number;
|
|
||||||
}
|
|
Loading…
Reference in a new issue