Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion frontend/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
<title>DebateAI</title>
<script src="https://accounts.google.com/gsi/client" async defer></script>
</head>
<body>
Expand Down
239 changes: 146 additions & 93 deletions frontend/src/Pages/TournamentBracketPage.tsx
Original file line number Diff line number Diff line change
@@ -1,104 +1,157 @@
// TournamentBracketPage.tsx
import React, { useState, useEffect } from 'react';

interface Participant {
id: number;
name: string;
avatar: string;
}
Comment on lines +4 to +8
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Align the Participant interface with the backend schema.

The frontend Participant interface doesn't match the backend's Participant struct from backend/routes/rooms.go (lines 27-31). The backend defines:

  • id as string (frontend uses number)
  • username instead of name
  • elo (rating) instead of avatar

This mismatch will cause integration issues when connecting to the real API.

Apply this diff to align with the backend:

 interface Participant {
-  id: number;
-  name: string;
-  avatar: string;
+  id: string;
+  username: string;
+  elo: number;
+  avatar?: string;  // Optional if you plan to derive it from the username or ID
 }

Then update the mock data (lines 17-26) and all references to participant.name throughout the file to use participant.username.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
interface Participant {
id: number;
name: string;
avatar: string;
}
interface Participant {
id: string;
username: string;
elo: number;
avatar?: string; // Optional if you plan to derive it from the username or ID
}
🤖 Prompt for AI Agents
In frontend/src/Pages/TournamentBracketPage.tsx around lines 4 to 8 (and update
mock data at lines 17 to 26), the Participant interface must match
backend/routes/rooms.go: change id: number to id: string, rename name to
username (string), and replace avatar with elo: number; then update the mock
participant objects at lines 17-26 to use id as strings, username properties,
and an elo number, and replace all usages of participant.name in this file with
participant.username so types and runtime behavior align with the backend.


export default function TournamentBracket() {
const participants = [
{ id: 1, name: "Rishit Tiwari", avatar: `https://i.pravatar.cc/32?u=1` },
{ id: 2, name: "Aarav Singh", avatar: `https://i.pravatar.cc/32?u=2` },
{ id: 3, name: "Ishaan Mehta", avatar: `https://i.pravatar.cc/32?u=3` },
{ id: 4, name: "Vihaan Kapoor", avatar: `https://i.pravatar.cc/32?u=4` },
{ id: 5, name: "Ayaan Khanna", avatar: `https://i.pravatar.cc/32?u=5` },
{ id: 6, name: "Vivaan Sharma", avatar: `https://i.pravatar.cc/32?u=6` },
{ id: 7, name: "Devansh Joshi", avatar: `https://i.pravatar.cc/32?u=7` },
{ id: 8, name: "Kabir Malhotra", avatar: `https://i.pravatar.cc/32?u=8` },
];
const round1Winners = [participants[0], participants[2], participants[4], participants[7]];
const semiFinalWinners = [round1Winners[0], round1Winners[3]];
const champion = semiFinalWinners[1];
const winnerHighlight = "ring-4 ring-yellow-400 shadow-lg transition-all duration-300";

const [isLoading, setIsLoading] = useState(true);
const [participants, setParticipants] = useState<Participant[]>([]);

useEffect(() => {
// Simulate API call
const timer = setTimeout(() => {
const data: Participant[] = [
{ id: 1, name: "Rishit Tiwari", avatar: `https://i.pravatar.cc/32?u=1` },
{ id: 2, name: "Aarav Singh", avatar: `https://i.pravatar.cc/32?u=2` },
{ id: 3, name: "Ishaan Mehta", avatar: `https://i.pravatar.cc/32?u=3` },
{ id: 4, name: "Vihaan Kapoor", avatar: `https://i.pravatar.cc/32?u=4` },
{ id: 5, name: "Ayaan Khanna", avatar: `https://i.pravatar.cc/32?u=5` },
{ id: 6, name: "Vivaan Sharma", avatar: `https://i.pravatar.cc/32?u=6` },
{ id: 7, name: "Devansh Joshi", avatar: `https://i.pravatar.cc/32?u=7` },
{ id: 8, name: "Kabir Malhotra", avatar: `https://i.pravatar.cc/32?u=8` },
];
setParticipants(data);
setIsLoading(false);
}, 1000);

return () => clearTimeout(timer);
}, []);

if (isLoading) {
return (
<div className="flex flex-col items-center w-full">
<h2 className="text-2xl font-bold mb-8 text-foreground">Tournament Bracket</h2>
{/* Champion */}
<div className="flex justify-center mb-12">
<div className="flex flex-col items-center relative">
<div className="text-xs font-bold text-yellow-400 mb-2">🏆 Champion</div>
<div className={`h-16 w-16 rounded-full bg-card flex items-center justify-center overflow-hidden ${winnerHighlight}`}>
<img src={champion.avatar} alt="Champion" className="w-full h-full object-cover" />
<div className="flex flex-col items-center w-full py-12">
<div className="w-full max-w-4xl space-y-12">
{/* Champion Skeleton */}
<div className="flex justify-center">
<div className="flex flex-col items-center animate-pulse">
<div className="h-4 w-24 bg-muted rounded mb-4"></div>
<div className="w-16 h-16 bg-muted rounded-full"></div>
<div className="h-4 w-20 bg-muted rounded mt-2"></div>
</div>
<div className="text-xs mt-2 font-medium text-foreground">{champion.name}</div>
</div>
</div>
{/* Finalists */}
<div className="w-full flex justify-around mb-12 relative">
{semiFinalWinners.map((finalist, index) => (
<div key={index} className="flex flex-col items-center relative">
<div className={`w-12 h-12 rounded-full bg-card flex items-center justify-center overflow-hidden border-2 ${finalist.id === champion.id ? winnerHighlight : 'border-border'}`}>
<img src={finalist.avatar} alt={finalist.name} className="w-full h-full object-cover" />
{/* Finalists Skeleton */}
<div className="w-full flex justify-around">
{[1, 2].map((i) => (
<div key={i} className="flex flex-col items-center animate-pulse">
<div className="w-12 h-12 bg-muted rounded-full mb-2"></div>
<div className="h-3 w-16 bg-muted rounded"></div>
</div>
<div className="text-xs mt-1 text-muted-foreground">{finalist.name}</div>
<div className="absolute w-px h-6 bg-border -top-6 left-1/2 transform -translate-x-1/2" />
</div>
))}
<div className="absolute h-px bg-border top-[-24px] left-[25%] w-[50%]" />
<div className="absolute w-px bg-border left-1/2 transform -translate-x-1/2 -top-12 h-6" />
))}
</div>
</div>
{/* Semifinals */}
<div className="w-full grid grid-cols-2 gap-2 mb-12 relative">
{[0, 1].map((matchIndex) => {
const player1 = round1Winners[matchIndex * 2];
const player2 = round1Winners[matchIndex * 2 + 1];
const winner = semiFinalWinners[matchIndex];
return (
<div key={matchIndex} className="relative">
<div className="flex justify-around">
{[player1, player2].map((player) => (
<div key={player.id} className="flex flex-col items-center relative">
<div className={`w-10 h-10 rounded-full bg-card flex items-center justify-center overflow-hidden border-2 ${player.id === winner.id ? winnerHighlight : 'border-border'}`}>
<img src={player.avatar} alt={player.name} className="w-full h-full object-cover" />
</div>
<div className="text-xs mt-1 text-accent-foreground">{player.name}</div>
<div className="absolute w-px h-6 bg-border -top-6 left-1/2 transform -translate-x-1/2" />
</div>
);
}

// Bracket logic
const round1Winners = [participants[0], participants[2], participants[4], participants[7]];
const semiFinalWinners = [round1Winners[0], round1Winners[3]];
const champion = semiFinalWinners[1];
Comment on lines +62 to +64
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Replace hard-coded winners with actual match results.

The bracket logic hard-codes winners by selecting specific array indices rather than determining winners based on actual match results. This means the bracket will always show the same winners regardless of the tournament data.

For the simulated data, consider one of these approaches:

  1. Add a winner property to mock data: Include match results in your simulated API response
  2. Add match result state: Allow the UI to accept or simulate match outcomes
  3. Document as placeholder logic: If this is intentional for now, add a TODO comment

Example for approach 1:

   useEffect(() => {
     const timer = setTimeout(() => {
       const data: Participant[] = [
         // ... participant data ...
       ];
+      // Mock match results (in production, this comes from the API)
+      const matchResults = {
+        round1Winners: [0, 2, 4, 7], // indices of winners
+        semiFinalWinners: [0, 7],
+        championId: 7
+      };
       setParticipants(data);
+      setMatchResults(matchResults);
       setIsLoading(false);
     }, 1000);
     return () => clearTimeout(timer);
   }, []);

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In frontend/src/Pages/TournamentBracketPage.tsx around lines 62-64 the code
currently hard-codes winners using specific participant indices which forces the
same bracket outcome; instead compute winners from the actual match data or
simulated results: update the mock API response to include a winner property on
each match (or add local matchResult state), then derive round1Winners by
mapping the round1 matches to their match.winner (or resolving matchResult for
that match) and similarly compute semiFinalWinners from semi-final matches and
champion from the final match winner; if this is intentionally temporary,
replace the hard-coded arrays with a clear TODO comment and/or fallback to using
match.winner when present.

const winnerHighlight = "ring-4 ring-yellow-400 shadow-lg transition-all duration-300";

return (
<div className="flex flex-col items-center w-full">
<h2 className="text-2xl font-bold mb-8 text-foreground">Tournament Bracket</h2>
{/* Champion */}
<div className="flex justify-center mb-12">
<div className="flex flex-col items-center relative">
<div className="text-xs font-bold text-yellow-400 mb-2">🏆 Champion</div>
<div className={`h-16 w-16 rounded-full bg-card flex items-center justify-center overflow-hidden ${winnerHighlight}`}>
<img src={champion.avatar} alt="Champion" className="w-full h-full object-cover" />
</div>
<div className="text-xs mt-2 font-medium text-foreground">{champion.name}</div>
</div>
</div>

{/* Finalists */}
<div className="w-full flex justify-around mb-12 relative">
{semiFinalWinners.map((finalist, index) => (
<div key={index} className="flex flex-col items-center relative">
<div className={`w-12 h-12 rounded-full bg-card flex items-center justify-center overflow-hidden border-2 ${finalist.id === champion.id ? winnerHighlight : 'border-border'}`}>
<img src={finalist.avatar} alt={finalist.name} className="w-full h-full object-cover" />
</div>
<div className="text-xs mt-1 text-muted-foreground">{finalist.name}</div>
<div className="absolute w-px h-6 bg-border -top-6 left-1/2 transform -translate-x-1/2" />
</div>
))}
<div className="absolute h-px bg-border top-[-24px] left-[25%] w-[50%]" />
<div className="absolute w-px bg-border left-1/2 transform -translate-x-1/2 -top-12 h-6" />
</div>

{/* Semifinals */}
<div className="w-full grid grid-cols-2 gap-2 mb-12 relative">
{[0, 1].map((matchIndex) => {
const player1 = round1Winners[matchIndex * 2];
const player2 = round1Winners[matchIndex * 2 + 1];
const winner = semiFinalWinners[matchIndex];
return (
<div key={matchIndex} className="relative">
<div className="flex justify-around">
{[player1, player2].map((player) => (
<div key={player.id} className="flex flex-col items-center relative">
<div className={`w-10 h-10 rounded-full bg-card flex items-center justify-center overflow-hidden border-2 ${player.id === winner.id ? winnerHighlight : 'border-border'}`}>
<img src={player.avatar} alt={player.name} className="w-full h-full object-cover" />
</div>
))}
</div>
<div className="absolute h-px bg-border top-[-24px] left-[25%] w-[50%]" />
<div className="absolute w-px bg-border left-1/2 transform -translate-x-1/2 -top-20 h-14" />
<div className="text-xs mt-1 text-accent-foreground">{player.name}</div>
<div className="absolute w-px h-6 bg-border -top-6 left-1/2 transform -translate-x-1/2" />
</div>
))}
</div>
);
})}
</div>
{/* First Round */}
<div className="w-full grid grid-cols-4 gap-2 relative">
{[0, 1, 2, 3].map((matchIndex) => {
const player1 = participants[matchIndex * 2];
const player2 = participants[matchIndex * 2 + 1];
const winner = round1Winners[matchIndex];
return (
<div key={matchIndex} className="relative">
<div className="flex justify-around">
{[player1, player2].map((player) => (
<div key={player.id} className="flex flex-col items-center relative">
<div className={`w-8 h-8 rounded-full bg-card flex items-center justify-center overflow-hidden border-2 ${player.id === winner.id ? winnerHighlight : 'border-border'}`}>
<img src={player.avatar} alt={player.name} className="w-full h-full object-cover" />
</div>
<div className="text-[10px] mt-1 text-foreground">{player.name}</div>
<div className="absolute w-px h-6 bg-border -top-6 left-1/2 transform -translate-x-1/2" />
<div className="absolute h-px bg-border top-[-24px] left-[25%] w-[50%]" />
<div className="absolute w-px bg-border left-1/2 transform -translate-x-1/2 -top-20 h-14" />
</div>
);
})}
</div>

{/* First Round */}
<div className="w-full grid grid-cols-4 gap-2 relative">
{[0, 1, 2, 3].map((matchIndex) => {
const player1 = participants[matchIndex * 2];
const player2 = participants[matchIndex * 2 + 1];
const winner = round1Winners[matchIndex];
return (
<div key={matchIndex} className="relative">
<div className="flex justify-around">
{[player1, player2].map((player) => (
<div key={player.id} className="flex flex-col items-center relative">
<div className={`w-8 h-8 rounded-full bg-card flex items-center justify-center overflow-hidden border-2 ${player.id === winner.id ? winnerHighlight : 'border-border'}`}>
<img src={player.avatar} alt={player.name} className="w-full h-full object-cover" />
</div>
))}
</div>
<div className="absolute h-px bg-border top-[-24px] left-[25%] w-[50%]" />
<div className="absolute w-px bg-border left-1/2 transform -translate-x-1/2 -top-20 h-14" />
<div className="text-[10px] mt-1 text-foreground">{player.name}</div>
<div className="absolute w-px h-6 bg-border -top-6 left-1/2 transform -translate-x-1/2" />
</div>
))}
</div>
);
})}
</div>
{/* Match Labels */}
<div className="w-full grid grid-cols-4 gap-2 mt-4">
<div className="text-center text-[10px] font-medium text-primary-foreground">Match 1</div>
<div className="text-center text-[10px] font-medium text-primary-foreground">Match 2</div>
<div className="text-center text-[10px] font-medium text-primary-foreground">Match 3</div>
<div className="text-center text-[10px] font-medium text-primary-foreground">Match 4</div>
</div>
<div className="absolute h-px bg-border top-[-24px] left-[25%] w-[50%]" />
<div className="absolute w-px bg-border left-1/2 transform -translate-x-1/2 -top-20 h-14" />
</div>
);
})}
</div>
);
}


{/* Match Labels */}
<div className="w-full grid grid-cols-4 gap-2 mt-4">
<div className="text-center text-[10px] font-medium text-primary-foreground">Match 1</div>
<div className="text-center text-[10px] font-medium text-primary-foreground">Match 2</div>
<div className="text-center text-[10px] font-medium text-primary-foreground">Match 3</div>
<div className="text-center text-[10px] font-medium text-primary-foreground">Match 4</div>
</div>
</div>
);
}
45 changes: 42 additions & 3 deletions frontend/src/Pages/TournamentHub.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { useState, FormEvent } from 'react';
// TournamentHub.tsx
import React, { useState, useEffect, FormEvent } from 'react';
import { Users, Calendar, Eye } from 'lucide-react';
import { useNavigate } from 'react-router-dom';

Expand Down Expand Up @@ -42,13 +43,24 @@ export default function TournamentPage() {
},
];

const [tournaments, setTournaments] = useState<Tournament[]>(initialTournaments);
const [tournaments, setTournaments] = useState<Tournament[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [name, setName] = useState('');
const [date, setDate] = useState('');
const [description, setDescription] = useState('');
const [error, setError] = useState('');
const navigate = useNavigate();

useEffect(() => {
// Simulate API call
const timer = setTimeout(() => {
setTournaments(initialTournaments);
setIsLoading(false);
}, 1000);

return () => clearTimeout(timer);
}, []);

const handleCreate = (e: FormEvent) => {
e.preventDefault();
if (!name) {
Expand Down Expand Up @@ -118,12 +130,38 @@ export default function TournamentPage() {
return avatars;
};

if (isLoading) {
// Skeleton Loader
return (
<div className="min-h-screen bg-background text-foreground py-12">
<h1 className="text-4xl sm:text-5xl font-extrabold mb-10 text-center text-primary animate-pulse">
Tournament Arena
</h1>
<div className="max-w-7xl mx-auto px-4">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
{[1, 2, 3, 4, 5, 6].map((i) => (
<div key={i} className="bg-card rounded-xl p-6 border border-border animate-pulse">
<div className="h-8 bg-muted rounded w-3/4 mb-4"></div>
<div className="h-4 bg-muted rounded w-1/2 mb-6"></div>
<div className="h-4 bg-muted rounded w-full mb-2"></div>
<div className="h-4 bg-muted rounded w-5/6 mb-6"></div>
<div className="h-10 bg-muted rounded-md w-full"></div>
</div>
))}
</div>
</div>
</div>
);
}

// Actual Tournament Hub Page
return (
<div className="min-h-screen bg-background text-foreground">
<h1 className="text-4xl sm:text-5xl font-extrabold mb-10 text-center text-primary animate-pulse">
Tournament Arena
</h1>
<div className="flex flex-col lg:flex-row gap-8 max-w-7xl mx-auto">
{/* Tournament Cards */}
<div className="flex-1">
{tournaments.length === 0 ? (
<p className="text-center text-muted-foreground text-lg">
Expand All @@ -143,7 +181,6 @@ export default function TournamentPage() {
<button
onClick={() => handleViewBracket(t)}
className="flex items-center gap-2 text-primary hover:text-primary/80 transition-all duration-200 mb-3 text-sm font-medium"
aria-label="View Tournament Bracket with Logs"
>
<Eye className="w-5 h-5" />
Spectate
Expand Down Expand Up @@ -184,6 +221,8 @@ export default function TournamentPage() {
</div>
)}
</div>

{/* Create Tournament Form */}
<div className="w-full lg:w-1/3 space-y-8">
<div className="bg-popover rounded-lg p-6 border border-border shadow-md">
<h2 className="text-2xl font-semibold mb-6 text-foreground">Create New Tournament</h2>
Expand Down
24 changes: 21 additions & 3 deletions frontend/src/index.css
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
/* Tailwind base setup */
@tailwind base;
@tailwind components;
@tailwind utilities;

/* Custom CSS variables and dark/contrast modes */
@layer base {

:root {
--background: 0 0% 100%;
--foreground: 20 14.3% 4.1%;
Expand Down Expand Up @@ -84,17 +86,33 @@
--chart-4: 300 100% 50%;
--chart-5: 180 100% 50%;
}
}

@layer base {
/* Apply border color globally */
* {
@apply border-border;
}

/* Body background and text color */
body {
@apply bg-background text-foreground;
}
}

/* Pulse animation */
@keyframes pulse {
0%, 100% {
opacity: 1;
}
50% {
opacity: 0.5;
}
}

.animate-pulse {
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}

/* Floating animation */
@keyframes floatUp {
0% {
transform: translateY(0);
Expand Down