diff --git a/app/(main)/create-team/page.js b/app/(main)/create-team/page.js index 99df990..156d301 100644 --- a/app/(main)/create-team/page.js +++ b/app/(main)/create-team/page.js @@ -1,5 +1,5 @@ "use client"; -import { useState } from "react"; +import { useState, useEffect } from "react"; import { useSession } from "next-auth/react"; import { useRouter } from "next/navigation"; import Link from "next/link"; @@ -20,61 +20,50 @@ export default function CreateTeam() { const { data: session, status } = useSession(); const [teamName, setTeamName] = useState(""); const [error, setError] = useState(""); - const [isLoading, setIsLoading] = useState(false); + const [isLoading, setIsLoading] = useState(true); const router = useRouter(); - if (status === "loading") { - return
Loading...
; - } - - if (!session || status === "unauthenticated") { - router.replace("/login"); - return null; - } - - if (session?.user?.role === "admin") { - router.replace("/admin"); - return null; - } + useEffect(() => { + if (status === "authenticated") { + if (session.user.teamId) { + router.push(`/team/${session.user.teamId}`); + } else { + setIsLoading(false); + } + } else if (status === "unauthenticated") { + router.push("/login"); + } + }, [status, session, router]); const handleCreateTeam = async (e) => { e.preventDefault(); - if (isLoading || !teamName.trim()) return; - setIsLoading(true); + setError(""); + try { - const response = await fetch("/api/teams/create", { + const response = await fetch("/api/teams", { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - name: teamName, - userId: session.user.id, - }), + body: JSON.stringify({ name: teamName }), }); - const result = await response.json(); - if (result.success) { - // Update the user's session with the new teamId - await fetch("/api/auth/session", { - method: "PATCH", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - teamId: result.teamId, - }), - }); + const data = await response.json(); - router.push(`/team/${result.teamId}`); - } else { - setError(result.error || "Failed to create team"); + if (!response.ok) { + throw new Error(data.error || "Failed to create team"); } + + router.push(`/team/${data.teamId}`); } catch (err) { - console.error("Team creation error:", err); - setError("An unexpected error occurred"); - } finally { + setError(err.message); setIsLoading(false); } }; + if (isLoading) { + return
Loading...
; + } + return (
diff --git a/app/(main)/team-selection/page.js b/app/(main)/team-selection/page.js index e46043e..ef704e1 100644 --- a/app/(main)/team-selection/page.js +++ b/app/(main)/team-selection/page.js @@ -1,41 +1,26 @@ -"use client"; // Mark as a Client Component +"use client"; import { useState, useEffect } from "react"; import { useSession } from "next-auth/react"; import { useRouter } from "next/navigation"; import Link from "next/link"; -import { joinTeam } from "../../actions/auth"; - -// Add CSS to fix the navbar at the bottom -const styles = { - navbar: { - position: "fixed", - bottom: 0, - width: "100%", - backgroundColor: "#fff", - borderTop: "1px solid #ccc", - padding: "10px 0", - textAlign: "center", - }, -}; export default function TeamSelection() { const { data: session, status } = useSession(); const [teamCode, setTeamCode] = useState(""); const [error, setError] = useState(""); - const [isLoading, setIsLoading] = useState(false); + const [isLoading, setIsLoading] = useState(true); const router = useRouter(); - // Use useEffect for navigation instead of doing it in render useEffect(() => { - // Redirect if not authenticated - if (status === "unauthenticated") { + if (status === "authenticated") { + if (session.user.teamId) { + router.push(`/team/${session.user.teamId}`); + } else { + setIsLoading(false); + } + } else if (status === "unauthenticated") { router.push("/login"); } - - // Redirect if user is admin - if (status === "authenticated" && session?.user?.role === "admin") { - router.push("/admin"); - } }, [status, session, router]); const handleJoinTeam = async (e) => { @@ -44,33 +29,29 @@ export default function TeamSelection() { setError(""); try { - const result = await joinTeam(session.user.id, teamCode); + const response = await fetch("/api/join-team", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ teamCode }), + }); - if (result.success) { - router.push(`/team/${result.teamId}`); - } else { - setError(result.error || "Team not found"); + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.error || "Failed to join team"); } + + router.push(`/team/${data.teamId}`); } catch (err) { - setError("An unexpected error occurred"); - console.error(err); - } finally { + setError(err.message); setIsLoading(false); } }; - if (status === "loading") { + if (isLoading) { return
Loading...
; } - // Don't render anything if redirecting - if ( - status === "unauthenticated" || - (status === "authenticated" && session?.user?.role === "admin") - ) { - return null; - } - return (
@@ -81,7 +62,7 @@ export default function TeamSelection() {
-

Team Selection

+

Join a Team

-
+
🏠
diff --git a/app/(main)/team/[teamId]/page.js b/app/(main)/team/[teamId]/page.js index 2580a1f..50fa4b0 100644 --- a/app/(main)/team/[teamId]/page.js +++ b/app/(main)/team/[teamId]/page.js @@ -1,4 +1,4 @@ -"use client"; // Mark as a Client Component +"use client"; import { useState, useEffect } from "react"; import { useSession } from "next-auth/react"; import { useRouter } from "next/navigation"; @@ -10,43 +10,36 @@ export default function TeamPage({ params }) { const [teamMembers, setTeamMembers] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(""); - const [huntActive, setHuntActive] = useState(false); const router = useRouter(); const { teamId } = params; - // Fetch team data useEffect(() => { async function fetchTeamData() { try { - if (!teamId) { - throw new Error("No team ID provided"); + if (!teamId || isNaN(parseInt(teamId, 10))) { + throw new Error("Invalid team ID"); } - // Fetch team details - const teamResponse = await fetch(`/api/teams/${teamId}`); - if (!teamResponse.ok) { - const errorData = await teamResponse.json(); + const response = await fetch(`/api/teams/${teamId}`, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + credentials: "include", // Important for auth + }); + + if (!response.ok) { + const errorData = await response.json(); throw new Error(errorData.error || "Failed to fetch team data"); } - const teamData = await teamResponse.json(); - setTeam(teamData); - // Fetch team members - const membersResponse = await fetch(`/api/teams/${teamId}/members`); - if (!membersResponse.ok) { - // Don't throw here, just log the error - console.error("Failed to fetch team members"); - } else { - const membersData = await membersResponse.json(); - setTeamMembers(membersData); + const data = await response.json(); + if (!data.team) { + throw new Error("Team not found"); } - // Fetch hunt status - const huntStatusResponse = await fetch("/api/hunt-status"); - if (huntStatusResponse.ok) { - const huntStatusData = await huntStatusResponse.json(); - setHuntActive(huntStatusData.active); - } + setTeam(data.team); + setTeamMembers(data.members || []); } catch (err) { console.error("Team page error:", err); setError(err.message); @@ -57,10 +50,11 @@ export default function TeamPage({ params }) { if (status === "authenticated" && teamId) { fetchTeamData(); + const interval = setInterval(fetchTeamData, 5000); // Refresh every 5 seconds + return () => clearInterval(interval); } }, [teamId, status]); - // Handle authentication useEffect(() => { if (status === "unauthenticated") { router.push("/login"); @@ -81,59 +75,79 @@ export default function TeamPage({ params }) { return (
-
- - ← - -
Team Details
-
-
- -
-

{team.name}

- -
-

Team Code

-
{team.code}
-

Share this code with others to join your team

-
- -
-

Team Members

- {teamMembers.length === 0 ? ( -

No members have joined yet

- ) : ( -
    - {teamMembers.map((member) => ( -
  • - {member.username} -
  • - ))} -
- )} -
- -
- +
+
+
Team Name: {team.name}
+
Team Join Code: {team.code}
-
-
-
🏠
-
-
-
📊
-
-
-
🏆
-
-
-
👤
-
+
+ {teamMembers.map((member) => ( +
+ {member.username} +
+ ))}
+ +
); } diff --git a/app/(main)/team/[teamId]/route.js b/app/(main)/team/[teamId]/route.js index a21f4a2..bc57b7c 100644 --- a/app/(main)/team/[teamId]/route.js +++ b/app/(main)/team/[teamId]/route.js @@ -1,5 +1,5 @@ // app/api/teams/[teamId]/route.js -import { queryOne } from "../../../../lib/db"; +import { queryOne, query } from "../../../../lib/db"; import { getServerSession } from "next-auth/next"; import { authOptions } from "../../../api/auth/[...nextauth]/route"; @@ -32,9 +32,10 @@ export async function GET(request, context) { ); } - const team = await queryOne("SELECT * FROM teams WHERE id = ?", [ - requestedTeamId, - ]); + const team = await queryOne( + "SELECT id, name, code FROM teams WHERE id = ?", + [requestedTeamId] + ); if (!team) { return new Response(JSON.stringify({ error: "Team not found" }), { @@ -42,9 +43,13 @@ export async function GET(request, context) { }); } - return new Response(JSON.stringify(team), { status: 200 }); + const members = await query( + "SELECT id, username FROM users WHERE team_id = ?", + [requestedTeamId] + ); + + return new Response(JSON.stringify({ team, members }), { status: 200 }); } catch (error) { - console.error("Team fetch error:", error); return new Response( JSON.stringify({ error: "Failed to fetch team details" }), { status: 500 } diff --git a/app/api/join-team/route.js b/app/api/join-team/route.js new file mode 100644 index 0000000..d414251 --- /dev/null +++ b/app/api/join-team/route.js @@ -0,0 +1,46 @@ +import { queryOne } from "../../../../lib/db"; +import { getServerSession } from "next-auth/next"; +import { authOptions } from "../../auth/[...nextauth]/route"; + +export async function POST(request) { + const session = await getServerSession(authOptions); + + if (!session) { + return new Response(JSON.stringify({ error: "Unauthorized" }), { + status: 401, + }); + } + + try { + const { teamCode } = await request.json(); + if (!teamCode) { + return new Response(JSON.stringify({ error: "Team code is required" }), { + status: 400, + }); + } + + const team = await queryOne("SELECT id FROM teams WHERE code = ?", [ + teamCode, + ]); + + if (!team) { + return new Response(JSON.stringify({ error: "Invalid team code" }), { + status: 404, + }); + } + + await queryOne("UPDATE users SET team_id = ? WHERE id = ?", [ + team.id, + session.user.id, + ]); + + return new Response(JSON.stringify({ teamId: team.id }), { + status: 200, + }); + } catch (error) { + console.error("Join team error:", error); + return new Response(JSON.stringify({ error: "Failed to join team" }), { + status: 500, + }); + } +} diff --git a/app/api/register/route.js b/app/api/register/route.js new file mode 100644 index 0000000..445b1bc --- /dev/null +++ b/app/api/register/route.js @@ -0,0 +1,45 @@ +import { queryOne, run } from "../../../lib/db"; +const bcrypt = require("bcryptjs"); + +export async function POST(request) { + try { + const { username, password } = await request.json(); + + // Validate input + if (!username || !password) { + return new Response( + JSON.stringify({ error: "Username and password are required" }), + { status: 400 } + ); + } + + // Check if username exists + const existingUser = await queryOne( + "SELECT id FROM users WHERE username = ?", + [username] + ); + + if (existingUser) { + return new Response( + JSON.stringify({ error: "Username already exists" }), + { status: 400 } + ); + } + + // Hash password + const hashedPassword = await bcrypt.hash(password, 10); + + // Insert user + await run( + "INSERT INTO users (username, password, role) VALUES (?, ?, 'user')", + [username, hashedPassword] + ); + + return new Response(JSON.stringify({ success: true }), { status: 201 }); + } catch (error) { + console.error("Registration error:", error); + return new Response(JSON.stringify({ error: "Failed to register user" }), { + status: 500, + }); + } +} diff --git a/app/api/teams/[teamId]/route.js b/app/api/teams/[teamId]/route.js index e768564..d317871 100644 --- a/app/api/teams/[teamId]/route.js +++ b/app/api/teams/[teamId]/route.js @@ -1,4 +1,4 @@ -import { queryOne } from "../../../../lib/db"; +import { queryOne, query } from "../../../../lib/db"; import { getServerSession } from "next-auth/next"; import { authOptions } from "../../auth/[...nextauth]/route"; @@ -6,32 +6,40 @@ export async function GET(request, { params }) { const session = await getServerSession(authOptions); if (!session) { - return Response.json({ error: "Unauthorised" }, { status: 401 }); + return new Response(JSON.stringify({ error: "Unauthorized" }), { + status: 401, + }); } try { - const { teamId } = params; + const teamId = parseInt(params.teamId, 10); + if (isNaN(teamId)) { + return new Response(JSON.stringify({ error: "Invalid team ID" }), { + status: 400, + }); + } - // Single query to get team data with authorization check const team = await queryOne( - `SELECT t.* FROM teams t - LEFT JOIN users u ON u.id = ? - WHERE t.id = ? AND (u.team_id = t.id OR ? = 'admin')`, - [session.user.id, teamId, session.user.role] + "SELECT id, name, code FROM teams WHERE id = ?", + [teamId] ); if (!team) { - return Response.json( - { error: "Team not found or unauthorized" }, - { status: 404 } - ); + return new Response(JSON.stringify({ error: "Team not found" }), { + status: 404, + }); } - return Response.json(team); + const members = await query( + "SELECT id, username FROM users WHERE team_id = ?", + [teamId] + ); + + return new Response(JSON.stringify({ team, members }), { status: 200 }); } catch (error) { console.error("Team fetch error:", error); - return Response.json( - { error: "Failed to fetch team details" }, + return new Response( + JSON.stringify({ error: "Failed to fetch team details" }), { status: 500 } ); } diff --git a/app/api/teams/route.js b/app/api/teams/route.js new file mode 100644 index 0000000..f1de0cc --- /dev/null +++ b/app/api/teams/route.js @@ -0,0 +1,49 @@ +import { queryOne, query } from "../../../../lib/db"; +import { getServerSession } from "next-auth/next"; +import { authOptions } from "../../auth/[...nextauth]/route"; + +export async function POST(request) { + const session = await getServerSession(authOptions); + + if (!session) { + return new Response(JSON.stringify({ error: "Unauthorized" }), { + status: 401, + }); + } + + try { + const { name } = await request.json(); + if (!name) { + return new Response(JSON.stringify({ error: "Team name is required" }), { + status: 400, + }); + } + + const result = await queryOne( + "INSERT INTO teams (name, code) VALUES (?, ?)", + [name, generateTeamCode()] + ); + + if (!result.insertId) { + throw new Error("Failed to create team"); + } + + await queryOne("UPDATE users SET team_id = ? WHERE id = ?", [ + result.insertId, + session.user.id, + ]); + + return new Response(JSON.stringify({ teamId: result.insertId }), { + status: 201, + }); + } catch (error) { + console.error("Team creation error:", error); + return new Response(JSON.stringify({ error: "Failed to create team" }), { + status: 500, + }); + } +} + +function generateTeamCode() { + return Math.random().toString(36).substring(2, 8).toUpperCase(); +} diff --git a/sqlite.db b/sqlite.db index f7af11a..75eb57d 100644 Binary files a/sqlite.db and b/sqlite.db differ