diff --git a/src/app/theme-slaughter/page.tsx b/src/app/theme-slaughter/page.tsx new file mode 100644 index 0000000..646a0b3 --- /dev/null +++ b/src/app/theme-slaughter/page.tsx @@ -0,0 +1,18 @@ +import Timers from "@/components/timers"; +import Streams from "@/components/streams"; +import JamHeader from "@/components/jam-header"; +import ThemeSlaughter from "@/components/themes/theme-slaughter"; + +export default async function Home() { + return ( +
+
+ +
+
+ + +
+
+ ); +} \ No newline at end of file diff --git a/src/app/theme-suggestions/page.tsx b/src/app/theme-suggestions/page.tsx new file mode 100644 index 0000000..59df2fc --- /dev/null +++ b/src/app/theme-suggestions/page.tsx @@ -0,0 +1,18 @@ +import Timers from "@/components/timers"; +import Streams from "@/components/streams"; +import JamHeader from "@/components/jam-header"; +import ThemeSuggestions from "@/components/themes/theme-suggest"; + +export default async function Home() { + return ( +
+
+ +
+
+ + +
+
+ ); +} \ No newline at end of file diff --git a/src/app/theme-voting/page.tsx b/src/app/theme-voting/page.tsx new file mode 100644 index 0000000..2c9da8b --- /dev/null +++ b/src/app/theme-voting/page.tsx @@ -0,0 +1,18 @@ +import Timers from "@/components/timers"; +import Streams from "@/components/streams"; +import JamHeader from "@/components/jam-header"; +import ThemeVoting from "@/components/themes/theme-vote"; + +export default async function Home() { + return ( +
+
+ +
+
+ + +
+
+ ); +} \ No newline at end of file diff --git a/src/components/jam-header/index.tsx b/src/components/jam-header/index.tsx index 7c9007d..dddfb60 100644 --- a/src/components/jam-header/index.tsx +++ b/src/components/jam-header/index.tsx @@ -1,15 +1,150 @@ +"use client"; + import { Calendar } from "lucide-react"; +import { useEffect, useState } from "react"; +import { getCurrentJam, ActiveJamResponse } from "../../helpers/jam"; export default function JamHeader() { + const [activeJamResponse, setActiveJamResponse] = useState(null); + const [topTheme, setTopTheme] = useState(null); + + // Fetch active jam details + useEffect(() => { + const fetchData = async () => { + const jamData = await getCurrentJam(); + setActiveJamResponse(jamData); + + // If we're in Jamming phase, fetch top themes and pick the first one + if (jamData?.phase === "Jamming" && jamData.jam) { + 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" + ); + + if (response.ok) { + const themes = await response.json(); + if (themes.length > 0) { + setTopTheme(themes[0].suggestion); + } + } else { + console.error("Failed to fetch top themes.", response.status); + } + } catch (error) { + console.error("Error fetching top themes:", error); + } + } + }; + + fetchData(); + }, []); + + + // Helper function to get ordinal suffix + const getOrdinalSuffix = (day: number): string => { + if (day > 3 && day < 21) return "th"; + switch (day % 10) { + case 1: return "st"; + case 2: return "nd"; + case 3: return "rd"; + default: return "th"; + } + }; + return ( -
-
- -

Down2Jam 1

+
+ {/* Jam Header */} +
+
+ +

+ {activeJamResponse?.jam && activeJamResponse.phase ? ( + + {activeJamResponse.jam.name} - {activeJamResponse.phase} Phase + + ) : ( + (No Active Jams) + )} +

+
+
+

+ {activeJamResponse?.jam ? ( + <> + {new Date(activeJamResponse.jam.startTime).toLocaleDateString('en-US', { + month: 'long', + })} {new Date(activeJamResponse.jam.startTime).getDate()} + {getOrdinalSuffix(new Date(activeJamResponse.jam.startTime).getDate())} + {" - "} + {new Date(new Date(activeJamResponse.jam.startTime).getTime() + + (activeJamResponse.jam.jammingHours * 60 * 60 * 1000)).toLocaleDateString('en-US', { + month: 'long', + })} {new Date(new Date(activeJamResponse.jam.startTime).getTime() + + (activeJamResponse.jam.jammingHours * 60 * 60 * 1000)).getDate()} + {getOrdinalSuffix(new Date(new Date(activeJamResponse.jam.startTime).getTime() + + (activeJamResponse.jam.jammingHours * 60 * 60 * 1000)).getDate())} + + ) : ( + "Dates TBA" + )} +

-
-

April 4th - 7th

+ + {/* Phase-Specific Display */} + {activeJamResponse?.phase === "Suggestion" && ( + + )} + + {activeJamResponse?.phase === "Survival" && ( + + )} + + {activeJamResponse?.phase === "Voting" && ( + + )} + + {activeJamResponse?.phase === "Jamming" && ( +
+ {topTheme ? ( +

THEME: {topTheme}

+ ) : ( +

No top-scoring theme available.

+ )} +
+ )} + + {activeJamResponse?.phase === "Rating" && ( +
+ {topTheme ? ( +

THEME: {topTheme} RESULTS

+ ) : ( +

No top-scoring theme available.

+ )} +
+ )}
); -} +} \ No newline at end of file diff --git a/src/components/navbar/MobileNavbar.tsx b/src/components/navbar/MobileNavbar.tsx index 77ba108..149af5e 100644 --- a/src/components/navbar/MobileNavbar.tsx +++ b/src/components/navbar/MobileNavbar.tsx @@ -27,7 +27,8 @@ export default function MobileNavbar() { useEffect(() => { loadUser(); async function loadUser() { - const currentJam = await getCurrentJam(); + const currentJamResponse = await getCurrentJam(); + const currentJam = currentJamResponse?.jam; setJam(currentJam); if (!hasCookie("token")) { diff --git a/src/components/navbar/PCNavbar.tsx b/src/components/navbar/PCNavbar.tsx index 414903c..aef6e59 100644 --- a/src/components/navbar/PCNavbar.tsx +++ b/src/components/navbar/PCNavbar.tsx @@ -24,7 +24,7 @@ import NextImage from "next/image"; import { useEffect, useState } from "react"; import { usePathname } from "next/navigation"; 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 { UserType } from "@/types/UserType"; import NavbarUser from "./PCNavbarUser"; @@ -57,7 +57,8 @@ export default function PCNavbar() { useEffect(() => { loadUser(); async function loadUser() { - const currentJam = await getCurrentJam(); + const jamResponse = await getCurrentJam(); + const currentJam = jamResponse?.jam; setJam(currentJam); if (!hasCookie("token")) { @@ -141,7 +142,8 @@ export default function PCNavbar() { icon={} name="Join jam" onPress={async () => { - const currentJam = await getCurrentJam(); + const currentJamResponse = await getCurrentJam(); + const currentJam = currentJamResponse?.jam; if (!currentJam) { toast.error("There is no jam to join"); diff --git a/src/components/themes/theme-slaughter.tsx b/src/components/themes/theme-slaughter.tsx new file mode 100644 index 0000000..bc488aa --- /dev/null +++ b/src/components/themes/theme-slaughter.tsx @@ -0,0 +1,279 @@ +"use client"; + +import React, { useState, useEffect } from "react"; +import { getCookie } from "@/helpers/cookie"; +import { getCurrentJam, hasJoinedCurrentJam, ActiveJamResponse } from "@/helpers/jam"; + +export default function ThemeSlaughter() { + + const [randomTheme, setRandomTheme] = useState(null); + const [votedThemes, setVotedThemes] = useState([]); + const [loading, setLoading] = useState(false); + const [token, setToken] = useState(null); + const [hasJoined, setHasJoined] = useState(false); + const [activeJamResponse, setActiveJam] = useState(null); + const [phaseLoading, setPhaseLoading] = useState(true); + + // Fetch token on the client side + useEffect(() => { + const fetchedToken = getCookie("token"); + setToken(fetchedToken); + }, []); + + // 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 + } + }; + + fetchCurrentJamPhase(); + }, []); + + + // Fetch a random theme + const fetchRandomTheme = async () => { + if (!token) return; // Wait until token is available + if( !activeJamResponse) return; + if(activeJamResponse && activeJamResponse.jam && activeJamResponse.phase != "Survival") + { + return ( +
+

It's not Theme Survival phase.

+
+ ); + } + + 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); + } + }; + + // Fetch voted themes + const fetchVotedThemes = async () => { + if (!token) return; // Wait until token is available + + try { + const response = await fetch( + process.env.NEXT_PUBLIC_MODE === "PROD" + ? "https://d2jam.com/api/v1/themes/voteSlaughter" + : "http://localhost:3005/api/v1/themes/voteSlaughter", + { + headers: { Authorization: `Bearer ${token}` }, + credentials: "include", + } + ); + if (response.ok) { + const data = await response.json(); + setVotedThemes(data); + } else { + console.error("Failed to fetch voted themes."); + } + } catch (error) { + console.error("Error fetching voted themes:", error); + } + }; + + // Handle voting + const handleVote = async (voteType) => { + if (!randomTheme) return; + + setLoading(true); + try { + const response = await fetch( + process.env.NEXT_PUBLIC_MODE === "PROD" + ? "https://d2jam.com/api/v1/themes/voteSlaughter" + : "http://localhost:3005/api/v1/themes/voteSlaughter", + { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + credentials: "include", + body: JSON.stringify({ + suggestionId: randomTheme.id, + voteType, + }), + } + ); + + if (response.ok) { + // Refresh data after voting + fetchRandomTheme(); + 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 + const handleResetVote = async (themeId) => { + try { + setRandomTheme(votedThemes.find((theme) => theme.id === themeId)); + setVotedThemes((prev) => + prev.map((theme) => + theme.id === themeId ? { ...theme, slaughterScore: 0 } : theme + ) + ); + } catch (error) { + console.error("Error resetting vote:", error); + } + }; + + + useEffect(() => { + if (token && activeJamResponse?.phase === "Survival") { + fetchRandomTheme(); + fetchVotedThemes(); + } + }, [token, activeJamResponse]); + + + useEffect(() => { + const init = async () => { + const joined = await hasJoinedCurrentJam(); + setHasJoined(joined); + setLoading(false); + }; + + init(); + }, []); + + if (phaseLoading || loading) { + return
Loading...
; + } + + if (!hasJoined) { + return ( +
+

+ Join the Jam First +

+

+ You need to join the current jam before you can join Theme Survival. +

+ +
+ ); + } + + // Render message if not in Theme Slaughter phase + if (activeJamResponse?.phase !== "Survival") { + return ( +
+

+ Not in Theme Slaughter Phase +

+

+ The current phase is {activeJamResponse?.phase || "Unknown"}. Please come back during the Theme Slaughter phase. +

+
+ ); + } + + const loggedIn = getCookie("token"); + + if (!loggedIn) { + return ( +
Sign in to be able to join the Theme Survival
+ ); + } + + + return ( +
+ {/* Left Side */} +
+ {randomTheme ? ( + <> +

+ {randomTheme.suggestion} +

+
+ + + +
+ + ) : ( +

No themes available.

+ )} +
+ + {/* Right Side */} +
+

+ Your Votes +

+
+ {votedThemes.map((theme) => ( +
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} +
+ ))} +
+
+
+ ); +} \ No newline at end of file diff --git a/src/components/themes/theme-suggest.tsx b/src/components/themes/theme-suggest.tsx new file mode 100644 index 0000000..5477550 --- /dev/null +++ b/src/components/themes/theme-suggest.tsx @@ -0,0 +1,271 @@ +"use client"; + +import React, { useState, useEffect } from "react"; +import { getCookie } from "@/helpers/cookie"; +import { getCurrentJam, hasJoinedCurrentJam , ActiveJamResponse } from "@/helpers/jam"; + +export default function ThemeSuggestions() { + const [suggestion, setSuggestion] = useState(""); + const [loading, setLoading] = useState(false); + const [successMessage, setSuccessMessage] = useState(""); + const [errorMessage, setErrorMessage] = useState(""); + const [userSuggestions, setUserSuggestions] = useState([]); + const [themeLimit, setThemeLimit] = useState(0); + const [hasJoined, setHasJoined] = useState(false); + const [activeJamResponse, setActiveJamResponse] = useState(null); + const [phaseLoading, setPhaseLoading] = useState(true); // Loading state for fetching phase + + // Fetch the current jam phase using helpers/jam + useEffect(() => { + const fetchCurrentJamPhase = async () => { + try { + const activeJam = await getCurrentJam(); + setActiveJamResponse(activeJam); // Set active jam details + if (activeJam?.jam) { + setThemeLimit(activeJam.jam.themePerUser || Infinity); // Set theme limit + } + } catch (error) { + console.error("Error fetching current jam:", error); + } finally { + setPhaseLoading(false); // Stop loading when phase is fetched + } + }; + + fetchCurrentJamPhase(); + }, []); + + // Fetch all suggestions for the logged-in user + const fetchSuggestions = async () => { + try { + const response = await fetch( + process.env.NEXT_PUBLIC_MODE === "PROD" + ? "https://d2jam.com/api/v1/themes/suggestion" + : "http://localhost:3005/api/v1/themes/suggestion", + { + headers: { Authorization: `Bearer ${getCookie("token")}` }, + credentials: "include", + } + ); + if (response.ok) { + const data = await response.json(); + setUserSuggestions(data); + } + } catch (error) { + console.error("Error fetching suggestions:", error); + } + }; + + // Fetch suggestions only when phase is "Suggestion" + useEffect(() => { + if (activeJamResponse?.phase === "Suggestion") { + fetchSuggestions(); + } + }, [activeJamResponse]); + + // Handle form submission to add a new suggestion + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setLoading(true); + setSuccessMessage(""); + setErrorMessage(""); + + if (!suggestion.trim()) { + setErrorMessage("Suggestion cannot be empty."); + setLoading(false); + return; + } + + try { + const token = getCookie("token"); + + if (!token) { + throw new Error("User is not authenticated. Please log in."); + } + + const response = await fetch( + process.env.NEXT_PUBLIC_MODE === "PROD" + ? "https://d2jam.com/api/v1/themes/suggestion" + : "http://localhost:3005/api/v1/themes/suggestion", + { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + credentials: "include", + body: JSON.stringify({ suggestionText: suggestion }), + } + ); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error(errorData.error || "Failed to submit suggestion."); + } + + setSuccessMessage("Suggestion added successfully!"); + setSuggestion(""); // Clear input field + fetchSuggestions(); // Refresh suggestions list + } catch (error: any) { + console.error("Error submitting suggestion:", error.message); + setErrorMessage(error.message || "An unexpected error occurred."); + } finally { + setLoading(false); + } + }; + + // Handle deleting a suggestion + const handleDelete = async (id: number) => { + try { + const token = getCookie("token"); + + const response = await fetch( + process.env.NEXT_PUBLIC_MODE === "PROD" + ? `https://d2jam.com/api/v1/themes/suggestion/${id}` + : `http://localhost:3005/api/v1/themes/suggestion/${id}`, + { + method: "DELETE", + headers: { Authorization: `Bearer ${token}` }, + credentials: "include", + } + ); + + if (!response.ok) { + throw new Error("Failed to delete suggestion."); + } + + fetchSuggestions(); // Refresh suggestions list + } catch (error) { + console.error("Error deleting suggestion:", error); + } + }; + + useEffect(() => { + const init = async () => { + const joined = await hasJoinedCurrentJam(); + setHasJoined(joined); + setLoading(false); + }; + + init(); + }, []); + + // Render loading state while fetching phase + if (phaseLoading || loading) { + return
Loading...
; + } + + if (!hasJoined) { + return ( +
+

+ Join the Jam First +

+

+ You need to join the current jam before you can suggest themes. +

+ +
+ ); + } + + const token = getCookie("token"); + + if (!token) { + return ( +
Sign in to be able to suggest themes
+ ); + } + + // Render message if not in Suggestion phase + if (activeJamResponse?.phase !== "Suggestion") { + return ( +
+

+ Not in Suggestion Phase +

+

+ The current phase is {activeJamResponse?.phase || "Unknown"}. Please come back during the Suggestion phase. +

+
+ ); + } + + return ( +
+

+ Submit Your Theme Suggestion +

+ + {/* Hide form if user has reached their limit */} + {userSuggestions.length < themeLimit ? ( +
+ + {errorMessage && ( +

{errorMessage}

+ )} + {successMessage && ( +

{successMessage}

+ )} + +
+ ) : ( +

+ You've reached your theme suggestion limit for this jam! +

+ )} + + {/* List of user's suggestions */} +
+

+ Your Suggestions +

+ {userSuggestions.length > 0 ? ( +
    + {userSuggestions.map((suggestion) => ( +
  • + {suggestion.suggestion} + +
  • + ))} +
+ ) : ( +

+ You haven't submitted any suggestions yet. +

+ )} +
+
+ ); +} \ No newline at end of file diff --git a/src/components/themes/theme-vote.tsx b/src/components/themes/theme-vote.tsx new file mode 100644 index 0000000..ac5c028 --- /dev/null +++ b/src/components/themes/theme-vote.tsx @@ -0,0 +1,242 @@ +"use client"; + +import React, { useState, useEffect } from "react"; +import { getCookie } from "@/helpers/cookie"; +import { getCurrentJam, hasJoinedCurrentJam , ActiveJamResponse } from "@/helpers/jam"; + +export default function VotingPage() { + const [themes, setThemes] = useState([]); + const [loading, setLoading] = useState(false); + const [activeJamResponse, setActiveJamResponse] = useState(null); + const [hasJoined, setHasJoined] = useState(false); + const [phaseLoading, setPhaseLoading] = useState(true); // Loading state for fetching phase + const token = getCookie("token"); + + // Fetch the current jam phase using helpers/jam + useEffect(() => { + const fetchCurrentJamPhase = async () => { + try { + const activeJam = await getCurrentJam(); + setActiveJamResponse(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 top N themes with voting scores + const fetchThemes = async () => { + 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); + } + }; + + + // Fetch themes only when phase is "Voting" + useEffect(() => { + if (activeJamResponse?.phase === "Voting") { + fetchThemes(); + } + }, [activeJamResponse]); + + // Handle voting + const handleVote = async (themeId, votingScore) => { + setLoading(true); + try { + 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 }), + } + ); + + 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(() => { + const init = async () => { + const joined = await hasJoinedCurrentJam(); + setHasJoined(joined); + setLoading(false); + }; + + init(); + }, []); + + + if (phaseLoading || loading) { + return
Loading...
; + } + + if (!hasJoined) { + return ( +
+

+ Join the Jam First +

+

+ You need to join the current jam before you can vote themes. +

+ +
+ ); + } + + + if (activeJamResponse?.phase !== "Voting") { + return ( +
+

+ Not in Voting Phase +

+

+ The current phase is {activeJamResponse?.phase || "Unknown"}. Please come back during the Voting phase. +

+
+ ); + } + + const loggedIn = getCookie("token"); + + if (!loggedIn) { + return ( +
Sign in to be able to vote
+ ); + } + + return ( +
+

+ Voting Phase +

+
+ {themes.map((theme) => ( +
+ {/* Voting Buttons */} +
+ + + +
+ + {/* Theme Suggestion */} +
{theme.suggestion}
+
+ ))} +
+
+ ); +} \ No newline at end of file diff --git a/src/components/timers/index.tsx b/src/components/timers/index.tsx index 0960e75..42882d9 100644 --- a/src/components/timers/index.tsx +++ b/src/components/timers/index.tsx @@ -1,15 +1,54 @@ +"use client" import { Spacer } from "@nextui-org/react"; +import React, { useState, useEffect } from "react"; import Timer from "./Timer"; +import { getCurrentJam, ActiveJamResponse } from "@/helpers/jam"; + + export default function Timers() { - return ( -
- - -

Site under construction

-
- ); + + const [activeJamResponse, setActiveJamResponse] = useState(null); + const [phaseLoading, setPhaseLoading] = useState(true); // Loading state for fetching phase + + // Fetch the current jam phase using helpers/jam + useEffect(() => { + const fetchCurrentJamPhase = async () => { + try { + const activeJam = await getCurrentJam(); + setActiveJamResponse(activeJam); // Set active jam details + } catch (error) { + console.error("Error fetching current jam:", error); + } finally { + setPhaseLoading(false); // Stop loading when phase is fetched + } + }; + + fetchCurrentJamPhase(); + }, []); + + if(activeJamResponse && activeJamResponse.jam) + { + return ( +
+ + +

Site under construction

+
+ ); + } + else + { + return ( +
+ No upcoming jams + +

Site under construction

+
+ ) + } + } diff --git a/src/helpers/cookie.ts b/src/helpers/cookie.ts index f255031..d5059b1 100644 --- a/src/helpers/cookie.ts +++ b/src/helpers/cookie.ts @@ -9,6 +9,9 @@ export function getCookies() { } export function getCookie(cookie: string) { + if (typeof document === "undefined") { + return null; + } const pairs = document.cookie.split(";"); for (let i = 0; i < pairs.length; i++) { const pair = pairs[i].trim().split("="); diff --git a/src/helpers/jam.ts b/src/helpers/jam.ts index c37746c..c0b80ff 100644 --- a/src/helpers/jam.ts +++ b/src/helpers/jam.ts @@ -2,6 +2,11 @@ import { JamType } from "@/types/JamType"; import { getCookie } from "./cookie"; import { toast } from "react-toastify"; +export interface ActiveJamResponse { + phase: string; + jam: JamType | null; // Jam will be null if no active jam is found +} + export async function getJams(): Promise { const response = await fetch( process.env.NEXT_PUBLIC_MODE === "PROD" @@ -12,24 +17,29 @@ export async function getJams(): Promise { return response.json(); } -export async function getCurrentJam(): Promise { - const jams = await getJams(); - const now = new Date(); +export async function getCurrentJam(): Promise { + + try { + const response = await fetch( + process.env.NEXT_PUBLIC_MODE === "PROD" + ? "https://d2jam.com/api/v1/jams" + : "http://localhost:3005/api/v1/jams/active" + ); - // Get only jams that happen in the future - const futureJams = jams.filter((jam) => new Date(jam.startTime) > now); + // Parse JSON response + const data = await response.json(); - // If theres no jams happening returns null - if (futureJams.length === 0) { - return null; - } + // Return the phase and jam details + return { + phase: data.phase, + jam: data.jam, + }; - // Sort future jams by startTime (earliest first) - futureJams.sort( - (a, b) => new Date(a.startTime).getTime() - new Date(b.startTime).getTime() - ); + } catch (error) { + console.error("Error fetching active jam:", error); + return null; + } - return futureJams[0]; } export async function joinJam(jamId: number) { @@ -43,6 +53,7 @@ export async function joinJam(jamId: number) { userSlug: getCookie("user"), }), method: "POST", + credentials: 'include', headers: { "Content-Type": "application/json", authorization: `Bearer ${getCookie("token")}`, @@ -61,3 +72,24 @@ export async function joinJam(jamId: number) { return false; } } + +export async function hasJoinedCurrentJam(): Promise { + try { + const response = await fetch( + process.env.NEXT_PUBLIC_MODE === "PROD" + ? "https://d2jam.com/api/v1/participation" + : "http://localhost:3005/api/v1/participation", + { + credentials: 'include', + headers: { + Authorization: `Bearer ${getCookie("token")}`, + }, + } + ); + + return response.ok; + } catch (error) { + console.error("Error checking jam participation:", error); + return false; + } +} \ No newline at end of file diff --git a/src/types/JamType.ts b/src/types/JamType.ts index 524de5b..c20c43d 100644 --- a/src/types/JamType.ts +++ b/src/types/JamType.ts @@ -1,8 +1,11 @@ export interface JamType { id: number; name: string; - ratingHours: number; + suggestionHours:number; slaughterHours: number; + votingHours:number; + jammingHours:number; + ratingHours: number; startTime: Date; createdAt: Date; updatedAt: Date;