Skip to content

Conversation

@CodeMaverick-143
Copy link

@CodeMaverick-143 CodeMaverick-143 commented Oct 12, 2025

Description

This PR implements proper error handling for all API calls in profileService.ts.
Previously, functions like getProfile, updateProfile, and getLeaderboard lacked robust error management, which could result in unhandled exceptions or unclear error messages for users.

Changes Made

  • Added ProfileServiceError custom class to standardize API errors.
  • Centralized API response handling with handleApiResponse.
  • Added network and unexpected error handling via handleNetworkError.
  • Introduced specific error codes (MISSING_TOKEN, INVALID_INPUT, NETWORK_ERROR, INVALID_RESPONSE, UNKNOWN_ERROR).
  • All API functions now return a consistent ApiResponse<T> structure with data, error, and success.
  • Ensures UI can display friendly error messages based on returned errors.

Files Modified

  • src/services/profileService.ts

Benefits

  • Provides consistent error structures for developers.
  • Enhances user experience with proper feedback on failures.
  • Prepares the app for stable API integration in the future.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added comprehensive error handling with tailored error messages across the application, including global error recovery, route-specific error handling, and dedicated error screens for debate room, authentication, and connection issues.
    • Improved error recovery with retry and navigation options for better user experience during failures.
  • Chores

    • Updated esbuild dependency and improved build configuration.
    • Added environment configuration template for deployment setup.

@coderabbitai
Copy link

coderabbitai bot commented Oct 12, 2025

Warning

Rate limit exceeded

@CodeMaverick-143 has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 11 minutes and 3 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between fcbd8c7 and 5890a9f.

⛔ Files ignored due to path filters (2)
  • backend/go.sum is excluded by !**/*.sum
  • frontend/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (102)
  • backend/.env.example (1 hunks)
  • backend/cmd/server/main.go (6 hunks)
  • backend/cmd/server/ws_debate_handler.go (1 hunks)
  • backend/cmd/test_judge/main.go (1 hunks)
  • backend/config/config.go (1 hunks)
  • backend/controllers/auth.go (9 hunks)
  • backend/controllers/debate_controller.go (0 hunks)
  • backend/controllers/debatevsbot_controller.go (1 hunks)
  • backend/controllers/leaderboard.go (0 hunks)
  • backend/controllers/profile_controller.go (3 hunks)
  • backend/controllers/team_controller.go (1 hunks)
  • backend/controllers/team_debate_controller.go (1 hunks)
  • backend/controllers/team_matchmaking.go (1 hunks)
  • backend/controllers/transcript_controller.go (7 hunks)
  • backend/db/db.go (2 hunks)
  • backend/go.mod (2 hunks)
  • backend/internal/debate/events.go (1 hunks)
  • backend/internal/debate/poll_store.go (1 hunks)
  • backend/internal/debate/rate_limiter.go (1 hunks)
  • backend/internal/debate/redis_client.go (1 hunks)
  • backend/internal/debate/stream_consumer.go (1 hunks)
  • backend/middlewares/auth.go (2 hunks)
  • backend/models/coach.go (1 hunks)
  • backend/models/debate.go (2 hunks)
  • backend/models/team.go (1 hunks)
  • backend/models/transcript.go (1 hunks)
  • backend/models/user.go (2 hunks)
  • backend/rating/glicko2.go (5 hunks)
  • backend/routes/debate.go (4 hunks)
  • backend/routes/leaderboard.go (1 hunks)
  • backend/routes/pros_cons_route copy.go (0 hunks)
  • backend/routes/rooms.go (9 hunks)
  • backend/routes/team.go (1 hunks)
  • backend/routes/transcriptroutes.go (1 hunks)
  • backend/services/ai.go (1 hunks)
  • backend/services/coach.go (2 hunks)
  • backend/services/debatevsbot.go (5 hunks)
  • backend/services/gemini.go (1 hunks)
  • backend/services/matchmaking.go (6 hunks)
  • backend/services/personalities.go (29 hunks)
  • backend/services/pros_cons.go (2 hunks)
  • backend/services/rating_service.go (4 hunks)
  • backend/services/team_matchmaking.go (1 hunks)
  • backend/services/team_turn_service.go (1 hunks)
  • backend/services/transcriptservice.go (14 hunks)
  • backend/test_server.go (1 hunks)
  • backend/utils/auth.go (4 hunks)
  • backend/utils/debate.go (1 hunks)
  • backend/utils/populate.go (3 hunks)
  • backend/utils/user.go (1 hunks)
  • backend/websocket/handler.go (0 hunks)
  • backend/websocket/matchmaking.go (6 hunks)
  • backend/websocket/team_debate_handler.go (1 hunks)
  • backend/websocket/team_websocket.go (1 hunks)
  • backend/websocket/websocket.go (11 hunks)
  • frontend/index.html (1 hunks)
  • frontend/package.json (2 hunks)
  • frontend/src/App.tsx (4 hunks)
  • frontend/src/Pages/About.tsx (5 hunks)
  • frontend/src/Pages/Authentication/forms.tsx (0 hunks)
  • frontend/src/Pages/BotSelection.tsx (0 hunks)
  • frontend/src/Pages/DebateRoom.tsx (1 hunks)
  • frontend/src/Pages/Game.tsx (7 hunks)
  • frontend/src/Pages/MatchLogs.tsx (4 hunks)
  • frontend/src/Pages/OnlineDebateRoom.tsx (35 hunks)
  • frontend/src/Pages/Profile.tsx (5 hunks)
  • frontend/src/Pages/SpeechTest.tsx (0 hunks)
  • frontend/src/Pages/StartDebate.tsx (2 hunks)
  • frontend/src/Pages/StrengthenArgument.tsx (1 hunks)
  • frontend/src/Pages/TeamBuilder.tsx (1 hunks)
  • frontend/src/Pages/TeamDebateRoom.tsx (1 hunks)
  • frontend/src/Pages/TournamentBracketPage.tsx (1 hunks)
  • frontend/src/Pages/TournamentHub.tsx (9 hunks)
  • frontend/src/Pages/ViewDebate.tsx (1 hunks)
  • frontend/src/atoms/debateAtoms.ts (1 hunks)
  • frontend/src/components/AnonymousQA.tsx (1 hunks)
  • frontend/src/components/ChatRoom.tsx (6 hunks)
  • frontend/src/components/Chatbox.tsx (1 hunks)
  • frontend/src/components/DebatePopup.tsx (0 hunks)
  • frontend/src/components/ErrorBoundary/DebateRoomWrapper.tsx (1 hunks)
  • frontend/src/components/ErrorBoundary/ErrorFallback.tsx (1 hunks)
  • frontend/src/components/ErrorBoundary/GlobalErrorBoundary.tsx (1 hunks)
  • frontend/src/components/ErrorBoundary/RouteErrorBoundary.tsx (1 hunks)
  • frontend/src/components/ErrorBoundary/index.ts (1 hunks)
  • frontend/src/components/ErrorBoundary/useErrorBoundary.ts (1 hunks)
  • frontend/src/components/JudgementPopup.tsx (9 hunks)
  • frontend/src/components/Layout.tsx (1 hunks)
  • frontend/src/components/Matchmaking.tsx (5 hunks)
  • frontend/src/components/MatchmakingPool.tsx (1 hunks)
  • frontend/src/components/PlayerCard.tsx (1 hunks)
  • frontend/src/components/ReactionBar.tsx (1 hunks)
  • frontend/src/components/RoomBrowser.tsx (1 hunks)
  • frontend/src/components/SavedTranscripts.tsx (0 hunks)
  • frontend/src/components/Sidebar.tsx (1 hunks)
  • frontend/src/components/TeamChatSidebar.tsx (1 hunks)
  • frontend/src/components/TeamMatchmaking.tsx (1 hunks)
  • frontend/src/components/UserCamera.tsx (0 hunks)
  • frontend/src/context/authContext.tsx (0 hunks)
  • frontend/src/context/theme-provider.tsx (1 hunks)
  • frontend/src/hooks/useDebateWS.ts (1 hunks)
  • frontend/src/hooks/useUser.ts (1 hunks)
  • frontend/src/index.css (2 hunks)

Walkthrough

The PR introduces a comprehensive error handling system for the frontend with global, route-level, and debate-room-specific error boundaries, custom error management hooks, and fallback UI components. It also adds a backend environment template, bumps esbuild to ^0.27.0, and refactors Tailwind plugin imports to ES modules.

Changes

Cohort / File(s) Summary
Error Boundary Components
frontend/src/components/ErrorBoundary/GlobalErrorBoundary.tsx, frontend/src/components/ErrorBoundary/RouteErrorBoundary.tsx, frontend/src/components/ErrorBoundary/DebateRoomWrapper.tsx
Introduces three error boundary classes: GlobalErrorBoundary wraps the entire app with fallback UI and production error logging; RouteErrorBoundary handles route-specific errors with route name tracking; DebateRoomErrorBoundary specializes in debate room context errors with room ID extraction.
Error Handling Utilities
frontend/src/components/ErrorBoundary/ErrorFallback.tsx, frontend/src/components/ErrorBoundary/useErrorBoundary.ts
Exports multiple error fallback components (ErrorFallback, DebateRoomErrorFallback, WebSocketErrorFallback, AuthErrorFallback, ComponentErrorFallback) and three custom hooks (useErrorBoundary, useWebSocketErrorBoundary, useDebateRoomErrorBoundary) for structured error management and context-specific error handling.
Error Boundary Exports
frontend/src/components/ErrorBoundary/index.ts
Barrel export file consolidating default exports from GlobalErrorBoundary and RouteErrorBoundary, plus named exports from ErrorFallback and useErrorBoundary hooks.
App Routing with Error Boundaries
frontend/src/App.tsx
Wraps all public and protected routes with RouteErrorBoundary, debate routes with DebateRoomErrorBoundary, and nests entire app within GlobalErrorBoundary; imports three boundary components; reorganizes routing structure for error isolation.
Configuration Files
backend/.env.example, frontend/tailwind.config.js
Adds backend environment template with server, database, AI, JWT, OAuth, and email configs; refactors Tailwind plugin declarations from require() to ES module imports.
Dependencies
frontend/package.json
Updates esbuild from ^0.24.0 to ^0.27.0 in devDependencies.

Sequence Diagram(s)

sequenceDiagram
    participant App as App Component
    participant GEB as GlobalErrorBoundary
    participant REB as RouteErrorBoundary
    participant DEB as DebateRoomErrorBoundary
    participant Component as Route Component
    participant Fallback as Error Fallback UI

    App->>GEB: Render (wraps entire app)
    GEB->>REB: Render public/protected routes
    REB->>DEB: Render debate-specific routes
    DEB->>Component: Render children

    alt Error Occurs
        Component-->>DEB: Error thrown
        DEB->>DEB: getDerivedStateFromError()<br/>componentDidCatch()
        DEB->>DEB: Log error with roomId context
        DEB->>Fallback: Render DebateRoomErrorFallback
        Fallback-->>DEB: User clicks Retry
        DEB->>DEB: handleReset()
        DEB->>Component: Re-render children
    else Unhandled Error at Route Level
        Component-->>REB: Error propagates up
        REB->>REB: getDerivedStateFromError()<br/>componentDidCatch()
        REB->>REB: Log error with routeName
        REB->>Fallback: Render RouteErrorFallback
    else Critical Error at App Level
        Component-->>GEB: Error propagates to top
        GEB->>GEB: getDerivedStateFromError()<br/>componentDidCatch()
        GEB->>GEB: logErrorToService() [prod]
        GEB->>Fallback: Render GlobalErrorFallback
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Areas requiring extra attention:

  • Error boundary lifecycle methods and state management — Ensure getDerivedStateFromError and componentDidCatch implementations correctly capture and reset error state across GlobalErrorBoundary, RouteErrorBoundary, and DebateRoomErrorBoundary.
  • Error propagation and boundary nesting — Verify that errors at each level (debate room → route → global) are caught by the appropriate boundary and don't bypass outer boundaries unintentionally.
  • App.tsx routing restructure — Confirm all routes are correctly wrapped with their respective boundaries and that routeName props are consistently applied for error tracking.
  • Hook dependencies and state sharing — Review useErrorBoundary and its specializations (useWebSocketErrorBoundary, useDebateRoomErrorBoundary) for correct state isolation and async error handling patterns.
  • Production error logging placeholders — Verify that logErrorToService and logDebateRoomError placeholders are ready for integration with an actual error tracking service.

Suggested reviewers

  • bhavik-mangla

Poem

🐰 A rabbit's refrain on boundaries so fine,
Each error now caught at each configured line,
From global to routes to debates we defend,
No crash left uncaught—just graceful mend! 🛡️✨

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Title check ⚠️ Warning The PR title references profileService.ts error handling, but the actual changes are comprehensive error boundary implementations across the frontend, environment configuration, and dependency updates. Update the title to reflect the actual changes: something like 'Implement comprehensive error boundaries and fallback UI components' or 'Add global and route-level error handling with custom error boundaries'.
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f29be62 and 18b7eb7.

📒 Files selected for processing (6)
  • frontend/index.html (1 hunks)
  • frontend/src/Pages/Profile.tsx (5 hunks)
  • frontend/src/Pages/TournamentBracketPage.tsx (1 hunks)
  • frontend/src/Pages/TournamentHub.tsx (4 hunks)
  • frontend/src/index.css (2 hunks)
  • frontend/src/services/profileService.ts (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
frontend/src/Pages/Profile.tsx (1)
frontend/src/services/profileService.ts (2)
  • getProfile (61-94)
  • updateProfile (96-150)
frontend/src/Pages/TournamentBracketPage.tsx (1)
backend/routes/rooms.go (1)
  • Participant (28-32)

Comment on lines +62 to +143
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";

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>
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

Guard against undersized participant lists before indexing.

round1Winners/semiFinalWinners assume eight participants and immediately dereference indices like participants[7]. As soon as the real API responds with fewer than eight entrants (e.g., early in a tournament), this code will hit undefined and throw when accessing .avatar/.name, blowing up the whole page. Please short-circuit or pad the data before deriving winners.

[suggested fix]

+  const MIN_PARTICIPANTS = 8;
+
+  if (participants.length < MIN_PARTICIPANTS) {
+    return (
+      <div className="flex flex-col items-center w-full py-12 text-muted-foreground">
+        Not enough participants to render the bracket yet.
+      </div>
+    );
+  }
+
   // Bracket logic
   const round1Winners = [participants[0], participants[2], participants[4], participants[7]];
   const semiFinalWinners = [round1Winners[0], round1Winners[3]];
   const champion = semiFinalWinners[1];
📝 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
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";
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>
const MIN_PARTICIPANTS = 8;
if (participants.length < MIN_PARTICIPANTS) {
return (
<div className="flex flex-col items-center w-full py-12 text-muted-foreground">
Not enough participants to render the bracket yet.
</div>
);
}
// Bracket logic
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";
return (
<div className="flex flex-col items-center w-full">
<h2 className="text-2xl font-bold mb-8 text-foreground">Tournament Bracket</h2>
{/* Champion */}
🤖 Prompt for AI Agents
In frontend/src/Pages/TournamentBracketPage.tsx around lines 62-143, the code
assumes participants.length >= 8 and directly indexes participants[7] (and
others), which will cause runtime errors when the list is shorter; fix by
short-circuiting or safely deriving winners: first check participants.length and
if < 8 either return a lightweight fallback UI (e.g., "Not enough participants
yet") or pad the participants array with safe placeholder objects before
computing round1Winners/semiFinalWinners/champion; when deriving winners use
bounds-checked lookups (e.g., conditional index checks or map/filter to skip
undefined) and use default avatar/name values so rendering never attempts to
read properties of undefined.

Comment on lines +48 to +59
const handleNetworkError = (error: Error): never => {
if (error instanceof ProfileServiceError) {
throw error;
}

// Network or other errors
if (error.name === 'TypeError' && error.message.includes('fetch')) {
throw new ProfileServiceError('Network error: Please check your internet connection', undefined, 'NETWORK_ERROR');
}

throw new ProfileServiceError(`Unexpected error: ${error.message}`, undefined, 'UNKNOWN_ERROR');
};
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

Network errors still escape as thrown exceptions

Because handleNetworkError always throws, the catch blocks in getProfile, updateProfile, and getLeaderboard never reach the return { error, success: false } path on fetch failures. Any offline/timeout scenario will bubble a rejected promise instead of yielding the new ApiResponse shape, defeating the whole error-handling refactor.

Have handleNetworkError map to a ProfileServiceError instance and return it so the catch blocks can format the structured response.

-const handleNetworkError = (error: Error): never => {
-  if (error instanceof ProfileServiceError) {
-    throw error;
-  }
-
-  // Network or other errors
-  if (error.name === 'TypeError' && error.message.includes('fetch')) {
-    throw new ProfileServiceError('Network error: Please check your internet connection', undefined, 'NETWORK_ERROR');
-  }
-
-  throw new ProfileServiceError(`Unexpected error: ${error.message}`, undefined, 'UNKNOWN_ERROR');
-};
+const handleNetworkError = (error: Error): ProfileServiceError => {
+  if (error instanceof ProfileServiceError) {
+    return error;
+  }
+
+  if (error.name === 'TypeError' && error.message.includes('fetch')) {
+    return new ProfileServiceError(
+      'Network error: Please check your internet connection',
+      undefined,
+      'NETWORK_ERROR'
+    );
+  }
+
+  return new ProfileServiceError(
+    `Unexpected error: ${error.message}`,
+    undefined,
+    'UNKNOWN_ERROR'
+  );
+};
📝 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
const handleNetworkError = (error: Error): never => {
if (error instanceof ProfileServiceError) {
throw error;
}
// Network or other errors
if (error.name === 'TypeError' && error.message.includes('fetch')) {
throw new ProfileServiceError('Network error: Please check your internet connection', undefined, 'NETWORK_ERROR');
}
throw new ProfileServiceError(`Unexpected error: ${error.message}`, undefined, 'UNKNOWN_ERROR');
};
const handleNetworkError = (error: Error): ProfileServiceError => {
if (error instanceof ProfileServiceError) {
return error;
}
if (error.name === 'TypeError' && error.message.includes('fetch')) {
return new ProfileServiceError(
'Network error: Please check your internet connection',
undefined,
'NETWORK_ERROR'
);
}
return new ProfileServiceError(
`Unexpected error: ${error.message}`,
undefined,
'UNKNOWN_ERROR'
);
};
🤖 Prompt for AI Agents
In frontend/src/services/profileService.ts around lines 48 to 59,
handleNetworkError currently always throws an error which prevents callers'
catch blocks from returning the structured ApiResponse; change the function to
create and return a ProfileServiceError instance instead of throwing (update the
return type from never to ProfileServiceError), map TypeError/fetch cases to a
NETWORK_ERROR ProfileServiceError and other cases to an UNKNOWN_ERROR
ProfileServiceError, and ensure callers use the returned ProfileServiceError in
their catch handlers to build the { error, success: false } response.

CodeMaverick-143 and others added 8 commits November 13, 2025 12:12
…ents

- Updated Go version to 1.24 and added toolchain specification.
- Updated dependencies in go.mod and go.sum, including cloud.google.com/go to v0.116.0.
- Added Redis configuration to the backend config.
- Removed unnecessary log statements and improved code readability across various backend controllers and services.
- Enhanced frontend components by adding new routes for viewing debates and integrating new libraries like fuse.js and zustand.
- Cleaned up unused console logs and improved error handling in frontend components.
- Removed unused routes related to pros and cons in the server setup.
- Simplified the WebSocket client handling by updating user details retrieval to include avatar URL and elo rating.
- Enhanced the transcript controller by cleaning up unused code and ensuring proper error handling.
- Adjusted phase durations in the frontend debate room component for a more streamlined experience.
- Cleaned up unnecessary state variables in the OnlineDebateRoom component for improved performance.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (3)
backend/.env.example (1)

89-116: Add explicit security reminder for production deployment.

While the instructions helpfully mention setting APP_ENV=production, consider adding a note that DEBUG_MODE must be set to false in production and all secret placeholders must be replaced with strong, unique values. A brief security checklist in the instructions would strengthen production-readiness guidance.

Add a production checklist section to the instructions:

# For production deployment:
# - Set APP_ENV=production
+# - Set DEBUG_MODE=false
+# - Verify all secret values are strong and unique (never use placeholder values)
+# - Use environment-specific database and Redis instances (separate from development)
+# - Rotate secrets regularly and store in a secrets manager
# - Use strong, unique values for all secrets
# - Use environment-specific database and Redis instances
frontend/src/components/ErrorBoundary/useErrorBoundary.ts (1)

104-107: Guard against non-object speech errors.

Line 105: handleSpeechRecognitionError dereferences error.error even when the argument could be null, undefined, or a primitive, which would throw before we can surface the original failure. Add optional chaining or a type guard so we always emit a meaningful error instead of a secondary TypeError.

-  const handleSpeechRecognitionError = useCallback((error: any) => {
-    const speechError = new Error(`Speech recognition error: ${error.error || error.message || 'Unknown error'}`);
+  const handleSpeechRecognitionError = useCallback((error: any) => {
+    const message =
+      (typeof error === 'object' && error !== null && ('error' in error || 'message' in error))
+        ? (error as { error?: string; message?: string }).error ?? (error as { message?: string }).message
+        : String(error ?? 'Unknown error');
+    const speechError = new Error(`Speech recognition error: ${message || 'Unknown error'}`);
     throwError(speechError);
   }, [throwError]);
frontend/src/components/ErrorBoundary/index.ts (1)

1-14: Export the debate wrapper for consistency.

Since most error-boundary pieces are re-exported here, consider adding DebateRoomErrorBoundary as well so callers don’t need a separate deep import. Keeps the barrel pattern cohesive.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 18b7eb7 and fcbd8c7.

⛔ Files ignored due to path filters (1)
  • frontend/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (10)
  • backend/.env.example (1 hunks)
  • frontend/package.json (1 hunks)
  • frontend/src/App.tsx (3 hunks)
  • frontend/src/components/ErrorBoundary/DebateRoomWrapper.tsx (1 hunks)
  • frontend/src/components/ErrorBoundary/ErrorFallback.tsx (1 hunks)
  • frontend/src/components/ErrorBoundary/GlobalErrorBoundary.tsx (1 hunks)
  • frontend/src/components/ErrorBoundary/RouteErrorBoundary.tsx (1 hunks)
  • frontend/src/components/ErrorBoundary/index.ts (1 hunks)
  • frontend/src/components/ErrorBoundary/useErrorBoundary.ts (1 hunks)
  • frontend/tailwind.config.js (2 hunks)
✅ Files skipped from review due to trivial changes (1)
  • frontend/package.json
🧰 Additional context used
🧬 Code graph analysis (4)
frontend/src/components/ErrorBoundary/useErrorBoundary.ts (1)
frontend/src/components/ErrorBoundary/index.ts (3)
  • useErrorBoundary (11-11)
  • useWebSocketErrorBoundary (12-12)
  • useDebateRoomErrorBoundary (13-13)
frontend/src/components/ErrorBoundary/DebateRoomWrapper.tsx (2)
frontend/src/components/ErrorBoundary/ErrorFallback.tsx (1)
  • DebateRoomErrorFallback (73-83)
frontend/src/components/ErrorBoundary/index.ts (1)
  • DebateRoomErrorFallback (5-5)
frontend/src/components/ErrorBoundary/ErrorFallback.tsx (1)
frontend/src/components/ErrorBoundary/index.ts (5)
  • ErrorFallback (4-4)
  • DebateRoomErrorFallback (5-5)
  • WebSocketErrorFallback (6-6)
  • AuthErrorFallback (7-7)
  • ComponentErrorFallback (8-8)
frontend/src/App.tsx (3)
frontend/src/Pages/ViewDebate.tsx (1)
  • ViewDebate (31-522)
frontend/src/context/authContext.tsx (1)
  • AuthProvider (38-314)
frontend/src/context/theme-provider.tsx (1)
  • ThemeProvider (50-69)
🪛 dotenv-linter (4.0.0)
backend/.env.example

[warning] 53-53: [UnorderedKey] The SMTP_PASSWORD key should go before the SMTP_USERNAME key

(UnorderedKey)


[warning] 73-73: [UnorderedKey] The COGNITO_REGION key should go before the COGNITO_USER_POOL_ID key

(UnorderedKey)

🔇 Additional comments (6)
backend/.env.example (4)

1-22: Well-documented server and database configuration sections.

The setup clearly distinguishes between MongoDB Atlas (cloud) and local development environments with appropriate connection string examples. Default values are sensible and documented.


24-64: Comprehensive external service integrations well-documented.

Clear guidance for obtaining API keys (Gemini, OpenAI, Google OAuth) with documentation links. SMTP configuration includes both connection details and sender information. JWT expiry time is helpfully converted to hours.


66-87: Address static analysis warnings for key ordering.

Dotenv-linter reports UnorderedKey warnings on lines 73 (COGNITO_REGION) and 53 (SMTP_PASSWORD), suggesting these should follow alphabetical ordering within their respective sections. While the current semantic grouping (credentials together, configs together) is logical, if dotenv-linter is enforced in CI/CD, reorder:

  • Line 52-53: Swap SMTP_USERNAME and SMTP_PASSWORD so credentials are alphabetical
  • Line 71-73: Reorder Cognito variables alphabetically (COGNITO_APP_CLIENT_ID, COGNITO_APP_CLIENT_SECRET, COGNITO_REGION, COGNITO_USER_POOL_ID)

Consider whether strict alphabetical ordering aligns with project conventions or if semantic grouping should be configured as an exception in your linter config.


1-116: Well-structured and comprehensive environment configuration template.

The .env.example file provides excellent coverage of DebateAI backend services with clear documentation, helpful examples (MongoDB Atlas vs local, JWT expiry conversion), and actionable setup instructions. Each section is well-commented with references to documentation where applicable (Gemini API, Google Cloud Console, OpenAI).

The only substantive items are: (1) address the dotenv-linter UnorderedKey warnings if enforced in CI/CD, and (2) strengthen the production deployment security guidance. Overall, this is a solid foundation for new developers onboarding to the project.

frontend/tailwind.config.js (1)

62-62: LGTM!

The plugins array correctly uses the imported variables, completing the ES module refactor cleanly.

frontend/src/App.tsx (1)

189-195: Great spot for the global boundary.

Wrapping both AuthProvider and ThemeProvider with GlobalErrorBoundary ensures any provider-level failure now goes through the unified fallback instead of crashing the whole tree. Nicely done.

Comment on lines +31 to +39
const handleAsyncError = useCallback((asyncFn: () => Promise<any>) => {
return async () => {
try {
await asyncFn();
} catch (error) {
console.error('Async error caught:', error);
throwError(error instanceof Error ? error : new Error(String(error)));
}
};
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

Restore async return value before rethrow.

Line 34: Wrapping asyncFn currently swallows its resolved value because the wrapper never returns what was awaited. Any caller that expects await handleAsyncError(fn)() to yield data now receives undefined, which will break downstream logic and UI flows that depend on that result. Propagate the successful result before the catch so the wrapper mirrors the original function’s behavior.

-  const handleAsyncError = useCallback((asyncFn: () => Promise<any>) => {
-    return async () => {
+  const handleAsyncError = useCallback((asyncFn: () => Promise<any>) => {
+    return async () => {
       try {
-        await asyncFn();
+        const result = await asyncFn();
+        return result;
       } catch (error) {
         console.error('Async error caught:', error);
         throwError(error instanceof Error ? error : new Error(String(error)));
       }
     };
📝 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
const handleAsyncError = useCallback((asyncFn: () => Promise<any>) => {
return async () => {
try {
await asyncFn();
} catch (error) {
console.error('Async error caught:', error);
throwError(error instanceof Error ? error : new Error(String(error)));
}
};
const handleAsyncError = useCallback((asyncFn: () => Promise<any>) => {
return async () => {
try {
const result = await asyncFn();
return result;
} catch (error) {
console.error('Async error caught:', error);
throwError(error instanceof Error ? error : new Error(String(error)));
}
};
🤖 Prompt for AI Agents
In frontend/src/components/ErrorBoundary/useErrorBoundary.ts around lines 31 to
39, the async wrapper swallows the resolved value of asyncFn because it awaits
asyncFn() but never returns that result; update the wrapper to capture and
return the awaited result (e.g., const result = await asyncFn(); return result;)
so callers receive the original resolved value, and keep the existing catch
behavior that logs and rethrows the error (preserving Error typing).

Comment on lines +1 to +2
import tailwindcssAnimate from "tailwindcss-animate";
import tailwindScrollbarHide from "tailwind-scrollbar-hide";
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

🧩 Analysis chain

Clean ES module refactor improves consistency.

The migration from require() to ES module imports aligns well with the file's existing use of export default and follows modern JavaScript best practices.

Please verify that both packages support ES module imports and are listed in package.json:


🏁 Script executed:

#!/bin/bash
# Verify packages are installed and check their module support
cat package.json | jq '.dependencies + .devDependencies | with_entries(select(.key | test("tailwindcss-animate|tailwind-scrollbar-hide")))'

# Check if the packages export ES modules by examining their package.json
fd -t f "package.json" node_modules/tailwindcss-animate node_modules/tailwind-scrollbar-hide --exec cat {} \; | jq -s '.[].type // "commonjs"'

Length of output: 258


🏁 Script executed:

#!/bin/bash
# Find package.json files in the repository
fd -t f "package.json" --max-depth 3

# Check the frontend package.json if it exists
if [ -f "frontend/package.json" ]; then
  echo "=== frontend/package.json dependencies ==="
  jq '.dependencies + .devDependencies | with_entries(select(.key | test("tailwindcss-animate|tailwind-scrollbar-hide")))' frontend/package.json
fi

Length of output: 141


🌐 Web query:

tailwindcss-animate ES module import support

💡 Result:

Short answer: The original tailwindcss-animate plugin is distributed as a CommonJS Tailwind plugin (expects require() in tailwind.config), and there is no official ESM build published. Many projects have moved to CSS-first replacements (tw-animate-css, tailwind-animate, tailwind-animate-x, etc.) which are ESM/bundler‑friendly because you import a CSS file (or provide an ESM entry) instead of a CommonJS plugin. [1][2][3]

What to do

  • If you must use the original plugin in an ESM toolchain, keep your Tailwind config as a CommonJS file (tailwind.config.cjs) and use require("tailwindcss-animate"). That’s the simplest compatibility route. [1]
  • If you want native ESM-style usage (Vite, Next 14 app dir, etc.) prefer a CSS-first replacement (tw-animate-css / tailwind-animate / tailwind-animate-x) and import the CSS in your entry (e.g. @import "tw-animate-css"; or import "tw-animate-css";). These packages are intended to work with ESM bundlers. [2][3]
  • If you’re tied to a specific starter (e.g., shadcn) check its docs/issues—many projects migrated from tailwindcss-animate to tw-animate-css and added an @import line; you may need to install the replacement package to avoid missing-module errors. [4]

Sources

  • tailwindcss-animate (npm package / plugin docs). [1]
  • tw-animate-css / alternative CSS-first packages (npm/jsDelivr listings). [2][3]
  • shadcn-ui migration / issue discussion about replacing tailwindcss-animate with tw-animate-css. [4]

🌐 Web query:

tailwind-scrollbar-hide ES module import support

💡 Result:

Short answer: Yes — tailwind-scrollbar-hide can be imported as an ES module. The package README shows both CommonJS (require) and ESM/TS examples (import default from 'tailwind-scrollbar-hide'), and recent releases include files intended for ESM/modern Tailwind v4 usage. [1][2]

Notes / tips:

  • If your project uses "type": "module" or a tailwind.config.ts, use the ESM import form:
    import scrollbarHide from 'tailwind-scrollbar-hide'
    plugins: [scrollbarHide]
    (or in JS: import('tailwind-scrollbar-hide').then(...)). [1]
  • If you get TypeScript errors about missing declarations, add a simple declaration (global.d.ts: declare module 'tailwind-scrollbar-hide') or set skipLibCheck. [3]
  • Some older guides/examples still show require(...); use require only in CommonJS configs. [1][2]

Sources: npm package README and docs for tailwind-scrollbar-hide. [1][2][3]

References
[1] tailwind-scrollbar-hide — npm (README examples).
[2] tailwind-scrollbar-hide — npm v4 notes (Tailwind v4 import path).
[3] StackOverflow — TS declaration workaround for tailwind-scrollbar-hide.


Critical: tailwindcss-animate does not support ES module imports.

The original tailwindcss-animate plugin is distributed as a CommonJS Tailwind plugin and there is no official ESM build published. The import on line 1 will fail at runtime.

Two options:

  1. Rename to tailwind.config.cjs and revert tailwindcss-animate to require() (keep tailwind-scrollbar-hide as ES import if compatible)
  2. Replace with a CSS-first alternative like tw-animate-css and import its CSS file instead

tailwind-scrollbar-hide supports ESM imports, so that import is fine.

🤖 Prompt for AI Agents
frontend/tailwind.config.js lines 1-2: the imported package
"tailwindcss-animate" is a CommonJS Tailwind plugin and will fail as an ES
module import; either (A) rename this file to tailwind.config.cjs and change the
plugin load to use require() for tailwindcss-animate (keeping
tailwind-scrollbar-hide as the ES import if desired), or (B) remove
tailwindcss-animate entirely and replace it with a CSS-first alternative such as
tw-animate-css by importing its CSS in your entry stylesheet and removing the
plugin from the Tailwind config; ensure the Tailwind config exports use the
appropriate module system after the change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants