Files
peworkshop/app/(main)/[teamId]/page.js
T
bobbert 8593bdac19 fixed TeamId page
Fixed the issue with the scroll div not rendering correctly did some maths for it to render correctly based on the padding it needs and the view height.
2025-03-23 19:42:13 +00:00

496 lines
13 KiB
JavaScript

"use client";
import { useState, useEffect, useCallback } from "react";
import { useSession } from "next-auth/react";
import { useRouter } from "next/navigation";
import Link from "next/link";
import { use } from "react";
import NavBar from "@/components/NavBar";
import { getHuntTimer } from "@/utils/timerService";
export default function TeamPage({ params }) {
const { status, data: session } = useSession();
const [team, setTeam] = useState(null);
const [teamMembers, setTeamMembers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState("");
const [huntActive, setHuntActive] = useState(false);
const [huntInProgress, setHuntInProgress] = useState(false);
const router = useRouter();
const unwrappedParams = use(params);
const teamId = unwrappedParams.teamId;
const checkHuntStatus = useCallback(async () => {
try {
const response = await fetch("/api/hunt-status", {
method: "GET",
headers: { "Content-Type": "application/json" },
credentials: "include",
});
if (response.ok) {
const data = await response.json();
setHuntActive(data.active || false);
}
} catch (err) {
console.error("Failed to check hunt status:", err);
}
}, []);
const fetchTeamData = useCallback(async () => {
try {
if (!teamId || isNaN(parseInt(teamId, 10))) {
throw new Error("Invalid team ID");
}
const userResponse = await fetch("/api/user", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
credentials: "include",
cache: "no-store",
});
const userData = await userResponse.json();
if (!userData.teamId || userData.teamId != teamId) {
console.log("User no longer in team, redirecting to team selection");
window.location.href = "/team-selection";
return;
}
console.log("Fetching team data for team ID:", teamId);
const timestamp = new Date().getTime();
const response = await fetch(`/api/teams/${teamId}?_t=${timestamp}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
"Cache-Control": "no-cache, no-store, must-revalidate",
Pragma: "no-cache",
},
credentials: "include",
cache: "no-store",
});
if (!response.ok) {
const text = await response.text();
let errorMessage;
try {
const errorData = JSON.parse(text);
errorMessage = errorData.error || "Failed to fetch team data";
} catch (unused) {
errorMessage = `Failed to fetch team data: ${
response.status
} ${text.slice(0, 100)}`;
}
throw new Error(errorMessage);
}
const data = await response.json();
console.log("Team data received:", data);
if (!data.team) {
throw new Error("Team not found");
}
setTeam(data.team);
setTeamMembers(data.members || []);
setError("");
await checkHuntStatus();
} catch (err) {
console.error("Team page error:", err);
setError(err.message);
if (err.message.includes("401")) {
router.push("/login");
}
} finally {
setLoading(false);
}
}, [teamId, router, checkHuntStatus]);
useEffect(() => {
if (status === "authenticated" && teamId) {
console.log("Session authenticated, fetching team data");
fetchTeamData();
const refreshInterval = setInterval(fetchTeamData, 3000);
return () => clearInterval(refreshInterval);
} else if (status === "unauthenticated") {
console.log("User not authenticated, redirecting to login");
router.push("/login");
}
}, [teamId, status, fetchTeamData, router]);
useEffect(() => {
const timerData = getHuntTimer();
setHuntInProgress(timerData && timerData.startTime);
}, []);
if (status === "loading" || loading) {
return (
<div className="loading-container">
<div className="loading-spinner"></div>
<div className="loading-text">Loading team data...</div>
</div>
);
}
if (error) {
return (
<div className="error-container">
<div className="error-icon">⚠️</div>
<div className="error-message">Error: {error}</div>
<Link href="/team-selection">
<button className="back-button">Back to Team Selection</button>
</Link>
</div>
);
}
if (!team) {
return (
<div className="error-container">
<div className="error-icon">🔍</div>
<div className="error-message">Team not found</div>
<Link href="/team-selection">
<button className="back-button">Back to Team Selection</button>
</Link>
</div>
);
}
const handleStartHunt = () => {
router.push("/map");
};
return (
<div className="team-page">
<div className="team-banner">
<div className="team-info">
<h1 className="team-name">Team Name: {team.name}</h1>
<div className="team-code">{team.code}</div>
</div>
</div>
<div className="content-wrapper">
<div className="waves-container">
<img src="/images/Waves.svg" alt="Waves" className="waves-image" />
</div>
<div className="scroll-background">
<div className="members-container">
<h2 className="members-title">Team Members</h2>
<div className="members-content">
{teamMembers.length > 0 ? (
<div className="members-list">
{teamMembers.map((member) => (
<div key={member.id} className="member-card">
<div className="member-avatar">👤</div>
<div className="member-details">
<div className="member-name">
{member.username}
{member.id === session?.user?.id && " (You)"}
</div>
</div>
</div>
))}
</div>
) : (
<div className="empty-state">No team members found</div>
)}
</div>
</div>
</div>
</div>
<div className="team-actions">
<button
className="action-button leave-button"
onClick={async () => {
if (confirm("Are you sure you want to leave this team?")) {
try {
setLoading(true);
const response = await fetch(`/api/teams/${teamId}/leave`, {
method: "POST",
headers: { "Content-Type": "application/json" },
credentials: "include",
});
if (response.ok) {
localStorage.removeItem("teamData");
sessionStorage.removeItem("teamData");
localStorage.setItem("userLeftTeam", "true");
window.location.href = "/team-selection";
} else {
const errorData = await response.json();
throw new Error(errorData.error || "Failed to leave team");
}
} catch (err) {
setError(err.message);
setLoading(false);
}
}
}}
>
Leave Team
</button>
<button
className="action-button start-button"
onClick={handleStartHunt}
>
{huntInProgress ? "Continue Hunt" : "Start Hunt"}
</button>
</div>
<NavBar activeLink="team" teamData={team} />
<style jsx>{`
.team-page {
display: flex;
flex-direction: column;
min-height: 100vh;
background-color: #69b5a8;
font-family: Arial, sans-serif;
position: relative;
overflow-x: hidden;
width: 100%;
padding-bottom: 70px;
}
.content-wrapper {
position: relative;
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
padding: 20px 0 0 0;
flex-grow: 1;
}
.waves-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 120px;
overflow: hidden;
z-index: 1;
}
.waves-image {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
object-position: center top;
}
.team-banner {
background-image: url("/images/Bottom-Bar.svg");
background-repeat: no-repeat;
background-size: cover;
background-position: center;
color: white;
padding: 20px;
position: relative;
display: flex;
align-items: center;
z-index: 3;
flex-shrink: 0;
}
.team-info {
flex: 1;
display: flex;
justify-content: space-between;
align-items: center;
}
.team-name {
margin: 0;
font-size: 24px;
font-weight: bold;
color: white;
}
.team-code {
font-size: 24px;
font-weight: bold;
color: white;
margin-left: auto;
}
.scroll-background {
background-image: url("/images/scroll.svg");
background-repeat: no-repeat;
background-size: 100% 100%;
background-position: center;
display: flex;
flex-direction: column;
position: relative;
z-index: 2;
padding: 60px 20px 80px 20px;
margin: 20px auto 0;
width: 90vw;
flex: 1;
height: calc(100vh - 70px - 68px - 40px);
overflow-y: auto;
}
@media (max-width < 800px) {
.scroll-background {
width: 95%;
padding: 50px 15px 70px 15px;
}
.members-title {
margin-top: -20px;
}
.team-actions {
width: 90%;
bottom: 70px;
}
.member-card {
width: calc(100% - 8px);
max-width: calc(100% - 8px);
}
}
.members-container {
display: flex;
flex-direction: column;
width: 100%;
flex: 1;
}
.members-title {
font-size: 20px;
font-weight: bold;
margin-bottom: 15px;
color: #68543c;
text-align: center;
margin-top: -50px;
}
.members-content {
flex: 1;
display: flex;
flex-direction: column;
padding: 10px;
min-height: 200px;
}
.members-list {
display: flex;
flex-direction: column;
gap: 8px;
width: 100%;
align-items: center;
flex: 1;
}
.member-card {
background-color: white;
border-radius: 6px;
padding: 10px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
border: 1px solid #eaeaea;
width: 100%;
max-width: 320px;
margin: 0 auto 8px auto;
}
.member-avatar {
font-size: 20px;
margin-right: 12px;
}
.member-name {
font-weight: 600;
font-size: 14px;
text-align: center;
}
.empty-state {
text-align: center;
padding: 20px;
color: #888;
width: 80%;
max-width: 400px;
background-color: rgba(255, 255, 255, 0.7);
border-radius: 8px;
margin: 20px auto;
font-size: 14px;
}
.team-actions {
display: flex;
gap: 10px;
padding: 10px 20px;
position: fixed;
bottom: 80px;
left: 0;
right: 0;
z-index: 5;
width: 80%;
max-width: 450px;
margin: 0 auto;
}
.action-button {
flex: 1;
padding: 15px;
border: none;
border-radius: 8px;
font-weight: bold;
cursor: pointer;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
}
.leave-button {
background-color: #f6bd60;
color: #333;
}
.start-button {
background-color: #588157;
color: white;
}
@media (max-height: 500px) {
.content-wrapper {
padding-top: 60px;
}
.scroll-background {
padding: 40px 15px 60px 15px;
}
.members-title {
margin-top: -15px;
font-size: 18px;
}
.team-actions {
bottom: 75px;
}
}
`}</style>
</div>
);
}