Conversation
… in team's schedule
- Added profile page with image upload, basic information, and team details. - Created edit profile page for updating user information. - Introduced team page displaying team members and tournament history. - Implemented team edit functionality, including leadership transfer and team image management. - Enhanced backend schema to support new fields for user profiles and teams. - Added image upload component for profile and team images with validation. - Updated API to handle profile and team image uploads and deletions.
|
Would like to merge this in for now, but will keep the branch undeleted so that work can continue in it |
|
@copilot Check for bugs |
|
@haoteng10 I've opened a new pull request, #67, to work on those changes. Once the pull request is ready, I'll request review from you. |
There was a problem hiding this comment.
Pull request overview
This PR implements a comprehensive tournament system for BlockWarriors, introducing round robin tournament functionality with database schema changes, backend mutations/queries, HTTP endpoints, and a complete frontend UI with multiple views (matrix, schedule, teams, rounds).
Changes:
- Added tournament database schema (tournaments, tournament_participants, tournament_matches tables)
- Implemented backend tournament logic with mutations for creating, joining, leaving, starting, and canceling tournaments
- Created frontend pages for tournament listing, creation, detail views, and match management
- Extended team and user profile functionality with image uploads and additional fields
Reviewed changes
Copilot reviewed 22 out of 23 changed files in this pull request and generated 15 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/backend/convex/schema.ts | Added three new tables for tournaments system with appropriate indexes |
| packages/backend/convex/tournaments.ts | New file implementing tournament CRUD operations and round robin scheduling |
| packages/backend/convex/tournamentMatches.ts | New file for tournament match management, standings calculation, and game result recording |
| packages/backend/convex/teams.ts | Extended with tournament history, image uploads, leadership transfer, and forfeit handling |
| packages/backend/convex/userProfiles.ts | Added profile image support and getUserProfileWithTeamMembers query |
| packages/backend/convex/http.ts | Added 8 new HTTP endpoints for tournament operations |
| packages/backend/convex/seed.ts | New comprehensive seeding file for development data |
| apps/blockwarriors-next/src/lib/tournament-constants.ts | Constants and types for tournament system |
| apps/blockwarriors-next/src/components/ui/image-upload.tsx | Reusable image upload component for profiles and teams |
| apps/blockwarriors-next/src/app/dashboard/tournaments/*.tsx | Four new pages for tournament list, creation, detail, and match views |
| apps/blockwarriors-next/src/app/dashboard/teams/page.tsx | Complete rewrite with client-side rendering and enhanced UI |
| apps/blockwarriors-next/src/app/dashboard/team/*.tsx | New team management pages (view, edit) |
| apps/blockwarriors-next/src/app/dashboard/profile/page.tsx | New user profile page |
Comments suppressed due to low confidence (1)
packages/backend/convex/tournaments.ts:1
- Magic number 1000 for starting ELO should be extracted to a constant in a shared configuration file for consistency with the frontend DEFAULT_TOURNAMENT_CONFIG and easier maintenance.
import { query, mutation, internalMutation } from "./_generated/server";
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| ); | ||
|
|
||
| // Filter out nulls and return | ||
| return tournaments.filter((t) => t !== null) as typeof tournaments extends (infer T | null)[] ? NonNullable<T>[] : never; |
There was a problem hiding this comment.
This complex type casting is hard to read. Consider using a simpler filter with type guard: tournaments.filter((t): t is NonNullable<typeof t> => t !== null)
| * Mock user data - represents different types of participants | ||
| * These use fake user IDs since we can't create real auth users |
There was a problem hiding this comment.
The comment says 'fake user IDs' but these are actual seed user IDs that could conflict with real users. Consider adding a prefix like 'seed_' to make them more distinguishable and document the cleanup procedure for development databases.
| // Check if we've already reached the required games | ||
| const totalGames = tournamentMatch.team1_games_won + tournamentMatch.team2_games_won; | ||
| const maxGames = tournamentMatch.games_required * 2 - 1; // Best of N | ||
| if (totalGames >= maxGames) { | ||
| return { success: false, error: "Maximum games already played" }; | ||
| } | ||
|
|
There was a problem hiding this comment.
The maxGames calculation for best-of-N is incorrect. For games_required=1 (single game), this calculates 12-1=1 (correct). But for games_required=2 (best of 3), it calculates 22-1=3 (correct). However, the logic should prevent creating more games once a team reaches games_required wins, not when total games equals maxGames. The current check on line 393 totalGames >= maxGames could allow one too many games to be created in edge cases.
| // Check if we've already reached the required games | |
| const totalGames = tournamentMatch.team1_games_won + tournamentMatch.team2_games_won; | |
| const maxGames = tournamentMatch.games_required * 2 - 1; // Best of N | |
| if (totalGames >= maxGames) { | |
| return { success: false, error: "Maximum games already played" }; | |
| } |
apps/blockwarriors-next/src/app/dashboard/tournaments/[id]/page.tsx
Outdated
Show resolved
Hide resolved
| import { Trophy } from 'lucide-react'; | ||
| 'use client'; | ||
|
|
||
| import { useState } from 'react'; |
There was a problem hiding this comment.
Unused import useState.
| import { useState } from 'react'; |
| @@ -1,6 +1,7 @@ | |||
| import { query, mutation } from "./_generated/server"; | |||
| import { query, mutation, internalMutation } from "./_generated/server"; | |||
There was a problem hiding this comment.
Unused import internalMutation.
| import { query, mutation, internalMutation } from "./_generated/server"; | |
| import { query, mutation } from "./_generated/server"; |
| import { query, mutation, internalMutation } from "./_generated/server"; | ||
| import { v } from "convex/values"; | ||
| import { Id } from "./_generated/dataModel"; | ||
| import { internal } from "./_generated/api"; |
There was a problem hiding this comment.
Unused import internal.
| import { internal } from "./_generated/api"; |
| @@ -0,0 +1,755 @@ | |||
| import { query, mutation, internalMutation } from "./_generated/server"; | |||
There was a problem hiding this comment.
Unused import internalMutation.
| import { query, mutation, internalMutation } from "./_generated/server"; | |
| import { query, mutation } from "./_generated/server"; |
| * Check if a user can act on behalf of a team (is a member of the team) | ||
| */ | ||
| async function canActForTeam( | ||
| ctx: any, | ||
| userId: string, | ||
| teamId: Id<"teams"> | ||
| ): Promise<boolean> { | ||
| const profile = await ctx.db | ||
| .query("user_profiles") | ||
| .withIndex("by_user_id", (q: any) => q.eq("user_id", userId)) | ||
| .first(); | ||
| return profile?.team_id === teamId; | ||
| } | ||
|
|
||
| /** |
There was a problem hiding this comment.
Unused function canActForTeam.
| * Check if a user can act on behalf of a team (is a member of the team) | |
| */ | |
| async function canActForTeam( | |
| ctx: any, | |
| userId: string, | |
| teamId: Id<"teams"> | |
| ): Promise<boolean> { | |
| const profile = await ctx.db | |
| .query("user_profiles") | |
| .withIndex("by_user_id", (q: any) => q.eq("user_id", userId)) | |
| .first(); | |
| return profile?.team_id === teamId; | |
| } | |
| /** |
| /** | ||
| * Get user's team ID from their profile | ||
| */ | ||
| async function getUserTeamId( | ||
| ctx: any, | ||
| userId: string | ||
| ): Promise<Id<"teams"> | null> { | ||
| const profile = await ctx.db | ||
| .query("user_profiles") | ||
| .withIndex("by_user_id", (q: any) => q.eq("user_id", userId)) | ||
| .first(); | ||
| return profile?.team_id ?? null; | ||
| } | ||
|
|
There was a problem hiding this comment.
Unused function getUserTeamId.
| /** | |
| * Get user's team ID from their profile | |
| */ | |
| async function getUserTeamId( | |
| ctx: any, | |
| userId: string | |
| ): Promise<Id<"teams"> | null> { | |
| const profile = await ctx.db | |
| .query("user_profiles") | |
| .withIndex("by_user_id", (q: any) => q.eq("user_id", userId)) | |
| .first(); | |
| return profile?.team_id ?? null; | |
| } |
- Fix missing validation in updateTournament mutation - Fix null pointer risk in recordGameResult mutation - Fix incorrect max games calculation in createTournamentGame - Verify frontend matrix score display (no bug found) Co-authored-by: haoteng10 <22121227+haoteng10@users.noreply.github.com>
- Remove unused newGamesPerMatch variable - Fix error message wording for consistency - Add clarifying comment about byes in tournaments Co-authored-by: haoteng10 <22121227+haoteng10@users.noreply.github.com>
Fix critical validation and null safety bugs in tournament implementation
… for unimplemented game types
…ult fallback impl is 1v1 pvp.
Command Description /testgame <type> [player1] [player2] Start a local test match without Convex /setarena <name\|list\|reload> Load/view arena configurations /endgame [winner\|blue\|red\|none] Force end the current game /triggerobjective <type> [player] Simulate game objective events /simulatedisconnect <player> [reason] Test disconnect handling /simulatereconnect <player> Test reconnect during grace period
{
"timestamp": 1705500000000,
"matchId": "abc123",
"gameType": "pvp",
"gameState": "IN_PROGRESS",
"players": [...],
"events": [
{"type": "disconnect", "playerId": "uuid", "reason": "QUIT", "result": "GRACE_PERIOD", ...}
],
"disconnectedPlayers": ["uuid1"],
"gameSpecific": { /* game.getGameState() output */ }
}
…e.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Ns iamin/game setup
Architecture Overview
flowchart TD subgraph Frontend["Frontend (blockwarriors-next)"] TL[Tournament List Page] TD[Tournament Detail Page] TC[Tournament Creation Form] TM[Tournament Match View] end subgraph Backend["Backend (Convex)"] TS[tournaments.ts] TMS[tournamentMatches.ts] HTTP[http.ts extensions] end subgraph Database["Schema Extensions"] TTable[tournaments table] TPTable[tournament_participants table] TMTable[tournament_matches table] end Frontend --> Backend Backend --> DatabasePhase 1: Database Schema
Add three new tables to
packages/backend/convex/schema.ts:tournamentstablename,description,format(round_robin | double_elim)status: "registration" | "in_progress" | "completed" | "cancelled"is_official: boolean (true = admin-created qualifiers/finals)created_by: user_id stringmax_teams,min_teams,games_per_match(for best-of-N)registration_deadline,start_time,end_timetournament_participantstabletournament_id,team_idseed(optional, for seeding)joined_atby_tournament_id,by_team_id,by_tournament_id_and_team_idtournament_matchestabletournament_id,round,match_numberteam1_id,team2_id(referencesteams)winner_team_id,statusscheduled_timegames: array of match_ids (references existingmatchestable)games_required(e.g., 2 for best-of-3)Phase 2: Convex Backend Functions
New file:
packages/backend/convex/tournaments.tsMutations:
createTournament- Create tournament (check admin foris_official)joinTournament- Register team (any team member can invoke)leaveTournament- Withdraw team before tournament startsstartTournament- Generate Round Robin matches (admin/creator only)cancelTournament- Cancel tournament (admin/creator only)Queries:
getTournament- Get tournament with participant countlistTournaments- List tournaments (filterable by status, official)getTournamentBracket- Get all matches for bracket viewgetMyTournaments- Tournaments where user's team is participatingNew file:
packages/backend/convex/tournamentMatches.tsMutations:
createTournamentGame- Create a game match within a tournament matchcompleteTournamentMatch- Mark match complete, update standingsgenerateRoundRobinSchedule- Internal: generate n*(n-1)/2 matchesQueries:
getTournamentMatch- Get match details with game resultsgetTournamentStandings- Calculate wins/losses/points for Round RobinExtensions to
packages/backend/convex/http.tsNew endpoints for Minecraft server:
POST /tournaments/{tid}/matches/{mid}/games/new- Create game in tournament matchPOST /tournaments/{tid}/matches/{mid}/games/update- Update game resultGET /tournaments/{tid}/matches/queued- Get queued tournament matchesPhase 3: Frontend Implementation
Tournament List Page:
/dashboard/tournaments/page.tsxTournament Detail Page:
/dashboard/tournaments/[id]/page.tsxTournament Match Page:
/dashboard/tournaments/[id]/matches/[matchId]/page.tsx/dashboard/matches/[id]/page.tsxTournament Creation Form:
/dashboard/tournaments/create/page.tsxKey Implementation Details
Team Authorization Pattern