-
Notifications
You must be signed in to change notification settings - Fork 125
Added concede (surrender) functionality for vs bot and vs player #162
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added concede (surrender) functionality for vs bot and vs player #162
Conversation
📝 WalkthroughWalkthroughThis change introduces a "concede" feature allowing users to admit defeat in ongoing debates. Implementation spans backend endpoints (HTTP for bot debates, WebSocket for online debates), state management, database updates to record losses, gamification triggers, and frontend UI with confirmation dialogs and post-concession navigation. Changes
Sequence DiagramssequenceDiagram
participant User as User (Browser)
participant UI as DebateRoom UI
participant Service as VS Bot Service
participant Backend as Backend Controller
participant DB as MongoDB
participant Gamification as Gamification Service
User->>UI: Clicks Concede Button
UI->>UI: Show Confirmation Dialog
User->>UI: Confirms Concession
UI->>Service: concedeDebate(debateId, messages)
Service->>Backend: POST /vsbot/concede
Backend->>DB: Update DebateVsBot outcome to "User conceded"
Backend->>DB: Retrieve user by email
Backend->>DB: Save transcript with outcome = "loss"
Backend->>Gamification: updateGamificationAfterBotDebate(userId, loss)
Note over Backend: Wrapped in recover() to catch panics
Gamification-->>Backend: Rating/badge updates (or error logged)
Backend-->>Service: 200 OK
Service-->>UI: Response received
UI->>UI: Mark debate as ended
UI->>UI: Show popup "Debate Conceded"
UI->>UI: Navigate to /game after 2 seconds
UI-->>User: Redirect complete
sequenceDiagram
participant User as User (Browser)
participant UI as OnlineDebateRoom UI
participant WS as WebSocket
participant Backend as Backend WebSocket Handler
participant Ratings as Rating Service
participant Opponent as Opponent (Browser)
User->>UI: Clicks Concede Button
UI->>UI: Show Confirmation Dialog
User->>UI: Confirms Concession
UI->>WS: Send concede message
WS->>Backend: "concede" message received
Backend->>Ratings: UpdateRatings(concederId, opponentId, loss, win)
Ratings-->>Backend: Ratings updated
Backend->>WS: Broadcast concede event to all
WS->>UI: Receive concede message
UI->>UI: Transition debate to Finished
UI->>UI: Show popup "You Conceded"
WS->>Opponent: Broadcast concede event
Opponent->>Opponent: Transition to Finished
Opponent->>Opponent: Show popup "You Won"
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
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. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
backend/.gitignorebackend/controllers/debatevsbot_controller.gobackend/routes/debatevsbot.gobackend/websocket/websocket.gofrontend/src/Pages/DebateRoom.tsxfrontend/src/Pages/OnlineDebateRoom.tsxfrontend/src/services/vsbot.ts
🧰 Additional context used
🧬 Code graph analysis (4)
backend/routes/debatevsbot.go (1)
backend/controllers/debatevsbot_controller.go (1)
ConcedeDebate(523-609)
frontend/src/Pages/DebateRoom.tsx (2)
frontend/src/services/vsbot.ts (1)
concedeDebate(103-118)frontend/src/components/ui/button.tsx (1)
Button(57-57)
backend/controllers/debatevsbot_controller.go (5)
backend/utils/auth.go (1)
ValidateTokenAndFetchEmail(133-146)backend/models/debatevsbot.go (1)
DebateVsBot(20-31)backend/db/db.go (2)
DebateVsBotCollection(19-19)GetCollection(23-25)backend/models/user.go (1)
User(10-35)backend/services/transcriptservice.go (1)
SaveDebateTranscript(662-726)
backend/websocket/websocket.go (5)
backend/routes/rooms.go (1)
Room(21-26)backend/structs/websocket.go (2)
Room(30-37)Message(10-13)backend/services/ai.go (1)
Message(21-24)backend/models/debatevsbot.go (1)
Message(6-10)backend/services/rating_service.go (1)
UpdateRatings(28-131)
🔇 Additional comments (10)
backend/.gitignore (1)
2-2: LGTM! Good security practice.Adding
config.prod.ymlto.gitignoreprevents developers from accidentally committing production configuration files containing sensitive data.backend/routes/debatevsbot.go (1)
16-16: LGTM! Route follows existing patterns.The new
/vsbot/concedeendpoint is properly registered and follows the same pattern as other routes in this file.frontend/src/services/vsbot.ts (1)
103-118: LGTM! API function follows existing patterns.The
concedeDebatefunction is properly implemented and follows the same pattern as other API calls in this file (auth header, credentials, error handling).frontend/src/Pages/DebateRoom.tsx (1)
828-835: LGTM! Concede button placement is appropriate.The Concede button is properly placed in the user panel and correctly hidden when the debate has ended.
frontend/src/Pages/OnlineDebateRoom.tsx (2)
1263-1270: LGTM! WebSocket concede handler is correct.The handler properly updates the UI when receiving a concede message from the opponent and provides appropriate feedback.
2120-2129: LGTM! Concede button visibility logic is correct.The button is appropriately shown only during active debate phases (excluding Setup and Finished).
backend/websocket/websocket.go (2)
475-476: LGTM! Concede case properly added to message switch.
14-16: LGTM! Import addition is appropriate.The
arguehub/servicesimport is needed for theUpdateRatingscall in the concede handler.backend/controllers/debatevsbot_controller.go (2)
518-521: LGTM! ConcedeRequest type is properly defined.The struct correctly requires
debateIdand accepts optionalhistory.
388-389: Minor formatting improvements noted.The log statement formatting changes improve readability.
Also applies to: 421-421, 425-425, 463-464
| func ConcedeDebate(c *gin.Context) { | ||
| token := c.GetHeader("Authorization") | ||
| if token == "" { | ||
| c.JSON(401, gin.H{"error": "Authorization token required"}) | ||
| return | ||
| } | ||
|
|
||
| token = strings.TrimPrefix(token, "Bearer ") | ||
| valid, email, err := utils.ValidateTokenAndFetchEmail("./config/config.prod.yml", token, c) | ||
| if err != nil || !valid { | ||
| c.JSON(401, gin.H{"error": "Invalid or expired token"}) | ||
| return | ||
| } | ||
|
|
||
| var req ConcedeRequest | ||
| if err := c.ShouldBindJSON(&req); err != nil { | ||
| c.JSON(400, gin.H{"error": "Invalid request payload: " + err.Error()}) | ||
| return | ||
| } | ||
|
|
||
| objID, err := primitive.ObjectIDFromHex(req.DebateId) | ||
| if err != nil { | ||
| c.JSON(400, gin.H{"error": "Invalid debate ID"}) | ||
| return | ||
| } | ||
|
|
||
| // Fetch the debate to get details | ||
| var debate models.DebateVsBot | ||
| err = db.DebateVsBotCollection.FindOne(context.Background(), bson.M{"_id": objID}).Decode(&debate) | ||
| if err != nil { | ||
| c.JSON(404, gin.H{"error": "Debate not found"}) | ||
| return | ||
| } | ||
|
|
||
| // Update debate outcome | ||
| filter := bson.M{"_id": objID} | ||
| update := bson.M{"$set": bson.M{"outcome": "User conceded"}} | ||
|
|
||
| _, err = db.DebateVsBotCollection.UpdateOne(context.Background(), filter, update) | ||
| if err != nil { | ||
| c.JSON(500, gin.H{"error": "Failed to update debate: " + err.Error()}) | ||
| return | ||
| } | ||
|
|
||
| // Get user ID from email | ||
| userCollection := db.GetCollection("users") | ||
| var user models.User | ||
| err = userCollection.FindOne(context.Background(), bson.M{"email": email}).Decode(&user) | ||
| if err != nil { | ||
| // Log error but don't fail the request as the main action succeeded | ||
| log.Printf("Error finding user for concede updates: %v", err) | ||
| c.JSON(200, gin.H{"message": "Debate conceded successfully"}) | ||
| return | ||
| } | ||
|
|
||
| // Save transcript to history | ||
| // We treat concession as a loss | ||
| // Use history from request if available, otherwise fall back to debate record history | ||
| historyToSave := debate.History | ||
| if len(req.History) > 0 { | ||
| historyToSave = req.History | ||
| } | ||
|
|
||
| _ = services.SaveDebateTranscript( | ||
| user.ID, | ||
| email, | ||
| "user_vs_bot", | ||
| debate.Topic, | ||
| debate.BotName, | ||
| "loss", | ||
| historyToSave, | ||
| nil, | ||
| ) | ||
|
|
||
| // Update gamification (score, badges, streaks) | ||
| // Call synchronously but with recover to prevent panics | ||
| func() { | ||
| defer func() { | ||
| if r := recover(); r != nil { | ||
| log.Printf("Panic in updateGamificationAfterBotDebate (concede): %v", r) | ||
| } | ||
| }() | ||
| updateGamificationAfterBotDebate(user.ID, "loss", debate.Topic) | ||
| }() | ||
|
|
||
| c.JSON(200, gin.H{"message": "Debate conceded successfully"}) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add context timeouts and reconsider early return behavior.
Several concerns with the ConcedeDebate handler:
-
Missing context timeouts (Lines 551, 561, 570): All database operations use
context.Background()without timeouts, which could cause requests to hang indefinitely if the database is slow or unavailable. -
Inconsistent success response (Lines 571-576): When user lookup fails, the function returns HTTP 200 with "Debate conceded successfully", but transcript saving and gamification updates are skipped. This creates an inconsistent state where the debate outcome is updated but user history is not. Consider returning an error status or at least a different message indicating partial success.
-
Silent failures in gamification (Lines 599-606): If
updateGamificationAfterBotDebatepanics or errors, it's logged but the user receives a success response, potentially misleading them about their points/badges.
🔎 Suggested improvements
func ConcedeDebate(c *gin.Context) {
token := c.GetHeader("Authorization")
if token == "" {
c.JSON(401, gin.H{"error": "Authorization token required"})
return
}
token = strings.TrimPrefix(token, "Bearer ")
valid, email, err := utils.ValidateTokenAndFetchEmail("./config/config.prod.yml", token, c)
if err != nil || !valid {
c.JSON(401, gin.H{"error": "Invalid or expired token"})
return
}
var req ConcedeRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(400, gin.H{"error": "Invalid request payload: " + err.Error()})
return
}
objID, err := primitive.ObjectIDFromHex(req.DebateId)
if err != nil {
c.JSON(400, gin.H{"error": "Invalid debate ID"})
return
}
// Fetch the debate to get details
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+ defer cancel()
+
var debate models.DebateVsBot
- err = db.DebateVsBotCollection.FindOne(context.Background(), bson.M{"_id": objID}).Decode(&debate)
+ err = db.DebateVsBotCollection.FindOne(ctx, bson.M{"_id": objID}).Decode(&debate)
if err != nil {
c.JSON(404, gin.H{"error": "Debate not found"})
return
}
// Update debate outcome
filter := bson.M{"_id": objID}
update := bson.M{"$set": bson.M{"outcome": "User conceded"}}
- _, err = db.DebateVsBotCollection.UpdateOne(context.Background(), filter, update)
+ _, err = db.DebateVsBotCollection.UpdateOne(ctx, filter, update)
if err != nil {
c.JSON(500, gin.H{"error": "Failed to update debate: " + err.Error()})
return
}
// Get user ID from email
userCollection := db.GetCollection("users")
var user models.User
- err = userCollection.FindOne(context.Background(), bson.M{"email": email}).Decode(&user)
+ err = userCollection.FindOne(ctx, bson.M{"email": email}).Decode(&user)
if err != nil {
- // Log error but don't fail the request as the main action succeeded
log.Printf("Error finding user for concede updates: %v", err)
- c.JSON(200, gin.H{"message": "Debate conceded successfully"})
+ // Return error since we couldn't complete the full concession flow
+ c.JSON(500, gin.H{
+ "error": "Debate outcome updated but failed to save transcript",
+ "message": "Please contact support if your history is not updated",
+ })
return
}
// Save transcript to history
// We treat concession as a loss
// Use history from request if available, otherwise fall back to debate record history
historyToSave := debate.History
if len(req.History) > 0 {
historyToSave = req.History
}
_ = services.SaveDebateTranscript(
user.ID,
email,
"user_vs_bot",
debate.Topic,
debate.BotName,
"loss",
historyToSave,
nil,
)
// Update gamification (score, badges, streaks)
// Call synchronously but with recover to prevent panics
func() {
defer func() {
if r := recover(); r != nil {
log.Printf("Panic in updateGamificationAfterBotDebate (concede): %v", r)
}
}()
updateGamificationAfterBotDebate(user.ID, "loss", debate.Topic)
}()
c.JSON(200, gin.H{"message": "Debate conceded successfully"})
}🤖 Prompt for AI Agents
In backend/controllers/debatevsbot_controller.go (around lines 551–606) the
handler uses context.Background() for DB calls (FindOne/UpdateOne/FindOne on
users), returns 200 when user lookup fails (skipping transcript/gamification),
and swallows gamification failures; fix by using context.WithTimeout (e.g., 5s)
for each DB operation and pass that ctx to FindOne/UpdateOne/FindOne calls,
change the user lookup error path to return a 500 (or a clear partial-success
response) so caller knows the user-related work failed instead of silently
succeeding, ensure the transcript is saved only after a successful user fetch
(or save with best-effort and clearly indicate partial success), and run
updateGamificationAfterBotDebate in a separate goroutine with defer-recover and
proper logging but do not treat gamification panics as full success—capture
errors (via channel or logs) and if gamification fails return or surface a
partial-success response to the client.
| // handleConcede handles concede requests | ||
| func handleConcede(room *Room, conn *websocket.Conn, message Message, client *Client, roomID string) { | ||
| // Broadcast concede message to all clients (including spectators) | ||
| broadcastMessage := Message{ | ||
| Type: "concede", | ||
| Room: roomID, | ||
| Username: client.Username, | ||
| UserID: client.UserID, | ||
| Content: "User conceded the debate", | ||
| } | ||
|
|
||
| // Send to all clients | ||
| for _, r := range snapshotRecipients(room, nil) { | ||
| r.SafeWriteJSON(broadcastMessage) | ||
| } | ||
|
|
||
| // Find opponent | ||
| var opponent *Client | ||
| room.Mutex.Lock() | ||
| for _, c := range room.Clients { | ||
| if !c.IsSpectator && c.UserID != client.UserID { | ||
| opponent = c | ||
| break | ||
| } | ||
| } | ||
| room.Mutex.Unlock() | ||
|
|
||
| if opponent != nil { | ||
| // Update ratings | ||
| // User lost (0.0), Opponent won (1.0) | ||
| userID, _ := primitive.ObjectIDFromHex(client.UserID) | ||
| opponentID, _ := primitive.ObjectIDFromHex(opponent.UserID) | ||
|
|
||
| _, _, err := services.UpdateRatings(userID, opponentID, 0.0, time.Now()) | ||
| if err != nil { | ||
| log.Printf("Error updating ratings after concede: %v", err) | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add validation and improve error handling.
Several issues with the handleConcede function:
-
Missing ObjectID validation (Lines 788-789): If
client.UserIDoropponent.UserIDare not valid hex strings,ObjectIDFromHexwill fail silently (errors ignored with_), and the rating update on line 791 will proceed with zero-value ObjectIDs. -
Rating update errors not reported (Lines 792-794): When
UpdateRatingsfails, only a log is written. Clients (including the conceding user) are not notified that ratings weren't updated. -
Broadcast logic (Line 770): Using
snapshotRecipients(room, nil)sends the concede message back to the sender. While not technically wrong, it's redundant since the sender already knows they conceded.
🔎 Suggested improvements
func handleConcede(room *Room, conn *websocket.Conn, message Message, client *Client, roomID string) {
// Broadcast concede message to all clients (including spectators)
broadcastMessage := Message{
Type: "concede",
Room: roomID,
Username: client.Username,
UserID: client.UserID,
Content: "User conceded the debate",
}
// Send to all clients
- for _, r := range snapshotRecipients(room, nil) {
+ for _, r := range snapshotRecipients(room, conn) {
r.SafeWriteJSON(broadcastMessage)
}
// Find opponent
var opponent *Client
room.Mutex.Lock()
for _, c := range room.Clients {
if !c.IsSpectator && c.UserID != client.UserID {
opponent = c
break
}
}
room.Mutex.Unlock()
if opponent != nil {
// Update ratings
// User lost (0.0), Opponent won (1.0)
- userID, _ := primitive.ObjectIDFromHex(client.UserID)
- opponentID, _ := primitive.ObjectIDFromHex(opponent.UserID)
+ userID, err := primitive.ObjectIDFromHex(client.UserID)
+ if err != nil {
+ log.Printf("Error parsing conceding user ID: %v", err)
+ return
+ }
+ opponentID, err := primitive.ObjectIDFromHex(opponent.UserID)
+ if err != nil {
+ log.Printf("Error parsing opponent user ID: %v", err)
+ return
+ }
_, _, err := services.UpdateRatings(userID, opponentID, 0.0, time.Now())
if err != nil {
log.Printf("Error updating ratings after concede: %v", err)
+ // Notify clients of the rating update failure
+ errorMsg := Message{
+ Type: "error",
+ Content: "Rating update failed",
+ }
+ client.SafeWriteJSON(errorMsg)
+ opponent.SafeWriteJSON(errorMsg)
}
}
}📝 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.
| // handleConcede handles concede requests | |
| func handleConcede(room *Room, conn *websocket.Conn, message Message, client *Client, roomID string) { | |
| // Broadcast concede message to all clients (including spectators) | |
| broadcastMessage := Message{ | |
| Type: "concede", | |
| Room: roomID, | |
| Username: client.Username, | |
| UserID: client.UserID, | |
| Content: "User conceded the debate", | |
| } | |
| // Send to all clients | |
| for _, r := range snapshotRecipients(room, nil) { | |
| r.SafeWriteJSON(broadcastMessage) | |
| } | |
| // Find opponent | |
| var opponent *Client | |
| room.Mutex.Lock() | |
| for _, c := range room.Clients { | |
| if !c.IsSpectator && c.UserID != client.UserID { | |
| opponent = c | |
| break | |
| } | |
| } | |
| room.Mutex.Unlock() | |
| if opponent != nil { | |
| // Update ratings | |
| // User lost (0.0), Opponent won (1.0) | |
| userID, _ := primitive.ObjectIDFromHex(client.UserID) | |
| opponentID, _ := primitive.ObjectIDFromHex(opponent.UserID) | |
| _, _, err := services.UpdateRatings(userID, opponentID, 0.0, time.Now()) | |
| if err != nil { | |
| log.Printf("Error updating ratings after concede: %v", err) | |
| } | |
| } | |
| } | |
| // handleConcede handles concede requests | |
| func handleConcede(room *Room, conn *websocket.Conn, message Message, client *Client, roomID string) { | |
| // Broadcast concede message to all clients (including spectators) | |
| broadcastMessage := Message{ | |
| Type: "concede", | |
| Room: roomID, | |
| Username: client.Username, | |
| UserID: client.UserID, | |
| Content: "User conceded the debate", | |
| } | |
| // Send to all clients | |
| for _, r := range snapshotRecipients(room, conn) { | |
| r.SafeWriteJSON(broadcastMessage) | |
| } | |
| // Find opponent | |
| var opponent *Client | |
| room.Mutex.Lock() | |
| for _, c := range room.Clients { | |
| if !c.IsSpectator && c.UserID != client.UserID { | |
| opponent = c | |
| break | |
| } | |
| } | |
| room.Mutex.Unlock() | |
| if opponent != nil { | |
| // Update ratings | |
| // User lost (0.0), Opponent won (1.0) | |
| userID, err := primitive.ObjectIDFromHex(client.UserID) | |
| if err != nil { | |
| log.Printf("Error parsing conceding user ID: %v", err) | |
| return | |
| } | |
| opponentID, err := primitive.ObjectIDFromHex(opponent.UserID) | |
| if err != nil { | |
| log.Printf("Error parsing opponent user ID: %v", err) | |
| return | |
| } | |
| _, _, err := services.UpdateRatings(userID, opponentID, 0.0, time.Now()) | |
| if err != nil { | |
| log.Printf("Error updating ratings after concede: %v", err) | |
| // Notify clients of the rating update failure | |
| errorMsg := Message{ | |
| Type: "error", | |
| Content: "Rating update failed", | |
| } | |
| client.SafeWriteJSON(errorMsg) | |
| opponent.SafeWriteJSON(errorMsg) | |
| } | |
| } | |
| } |
| const handleConcede = async () => { | ||
| if (window.confirm("Are you sure you want to concede? This will count as a loss.")) { | ||
| try { | ||
| if (debateData.debateId) { | ||
| await concedeDebate(debateData.debateId, state.messages); | ||
| } | ||
|
|
||
| setState(prev => ({ ...prev, isDebateEnded: true })); | ||
| setPopup({ | ||
| show: true, | ||
| message: "You have conceded the debate.", | ||
| isJudging: false | ||
| }); | ||
|
|
||
| setTimeout(() => { | ||
| navigate("/game"); | ||
| }, 2000); | ||
|
|
||
| } catch (error) { | ||
| console.error("Error conceding:", error); | ||
| } | ||
| } | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Improve error handling and state consistency.
The concede flow has a few concerns:
- State inconsistency risk: Line 269 sets
isDebateEnded: truebefore the API call completes. IfconcedeDebatefails (line 266), the local state will show the debate as ended, but the backend won't reflect this change. - Silent error handling: Lines 280-282 catch errors but only log them to console. Users won't know their concede action failed.
- Navigation timing: The 2-second delay (line 276) before navigating away might be too short for users to read the confirmation message.
🔎 Suggested improvements
const handleConcede = async () => {
if (window.confirm("Are you sure you want to concede? This will count as a loss.")) {
try {
if (debateData.debateId) {
await concedeDebate(debateData.debateId, state.messages);
}
+ // Only update state after successful API call
setState(prev => ({ ...prev, isDebateEnded: true }));
setPopup({
show: true,
message: "You have conceded the debate.",
isJudging: false
});
+ // Increase delay to give users more time to read the message
setTimeout(() => {
navigate("/game");
- }, 2000);
+ }, 3000);
} catch (error) {
console.error("Error conceding:", error);
+ // Inform user of the error
+ setPopup({
+ show: true,
+ message: "Failed to concede the debate. Please try again.",
+ isJudging: false
+ });
}
}
};📝 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.
| const handleConcede = async () => { | |
| if (window.confirm("Are you sure you want to concede? This will count as a loss.")) { | |
| try { | |
| if (debateData.debateId) { | |
| await concedeDebate(debateData.debateId, state.messages); | |
| } | |
| setState(prev => ({ ...prev, isDebateEnded: true })); | |
| setPopup({ | |
| show: true, | |
| message: "You have conceded the debate.", | |
| isJudging: false | |
| }); | |
| setTimeout(() => { | |
| navigate("/game"); | |
| }, 2000); | |
| } catch (error) { | |
| console.error("Error conceding:", error); | |
| } | |
| } | |
| }; | |
| const handleConcede = async () => { | |
| if (window.confirm("Are you sure you want to concede? This will count as a loss.")) { | |
| try { | |
| if (debateData.debateId) { | |
| await concedeDebate(debateData.debateId, state.messages); | |
| } | |
| // Only update state after successful API call | |
| setState(prev => ({ ...prev, isDebateEnded: true }}); | |
| setPopup({ | |
| show: true, | |
| message: "You have conceded the debate.", | |
| isJudging: false | |
| }); | |
| // Increase delay to give users more time to read the message | |
| setTimeout(() => { | |
| navigate("/game"); | |
| }, 3000); | |
| } catch (error) { | |
| console.error("Error conceding:", error); | |
| // Inform user of the error | |
| setPopup({ | |
| show: true, | |
| message: "Failed to concede the debate. Please try again.", | |
| isJudging: false | |
| }); | |
| } | |
| } | |
| }; |
🤖 Prompt for AI Agents
In frontend/src/Pages/DebateRoom.tsx around lines 262 to 284, the concede flow
updates local state and navigates before confirming the backend succeeded and
only logs errors; change the flow so the concede API is awaited first and only
on success setState(prev=>({...prev, isDebateEnded:true})), show a success
popup, then navigate (either after a longer timeout like 4s or after user
acknowledgment). In the catch block, replace the console.error with setting a
visible error popup (e.g., setPopup({show:true,message:"Failed to concede.
Please try again.",isError:true})) and do not change isDebateEnded or navigate
when the API fails; ensure all branches handle missing debateId gracefully by
showing an error popup instead of proceeding.
| const handleConcede = useCallback(() => { | ||
| if (window.confirm("Are you sure you want to concede? This will count as a loss.")) { | ||
| if (wsRef.current) { | ||
| wsRef.current.send(JSON.stringify({ | ||
| type: "concede", | ||
| room: roomId, | ||
| userId: currentUserId, | ||
| username: currentUser?.displayName || "User" | ||
| })); | ||
| } | ||
| setDebatePhase(DebatePhase.Finished); | ||
| setPopup({ | ||
| show: true, | ||
| message: "You have conceded the debate.", | ||
| isJudging: false, | ||
| }); | ||
| } | ||
| }, [roomId, currentUserId, currentUser, setDebatePhase, setPopup]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add error handling for WebSocket failures.
The concede flow updates local state without confirming the WebSocket message was sent successfully:
- Line 810 sets
debatePhaseto Finished immediately, but ifwsRef.currentis null or the WebSocket is disconnected, the concede message won't be sent to the opponent. - No verification that the WebSocket send succeeded before updating UI state.
🔎 Suggested improvements
const handleConcede = useCallback(() => {
if (window.confirm("Are you sure you want to concede? This will count as a loss.")) {
- if (wsRef.current) {
+ if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) {
wsRef.current.send(JSON.stringify({
type: "concede",
room: roomId,
userId: currentUserId,
username: currentUser?.displayName || "User"
}));
+ setDebatePhase(DebatePhase.Finished);
+ setPopup({
+ show: true,
+ message: "You have conceded the debate.",
+ isJudging: false,
+ });
+ } else {
+ // WebSocket not connected
+ setPopup({
+ show: true,
+ message: "Connection lost. Unable to concede. Please try again.",
+ isJudging: false,
+ });
+ return;
}
- setDebatePhase(DebatePhase.Finished);
- setPopup({
- show: true,
- message: "You have conceded the debate.",
- isJudging: false,
- });
}
}, [roomId, currentUserId, currentUser, setDebatePhase, setPopup]);📝 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.
| const handleConcede = useCallback(() => { | |
| if (window.confirm("Are you sure you want to concede? This will count as a loss.")) { | |
| if (wsRef.current) { | |
| wsRef.current.send(JSON.stringify({ | |
| type: "concede", | |
| room: roomId, | |
| userId: currentUserId, | |
| username: currentUser?.displayName || "User" | |
| })); | |
| } | |
| setDebatePhase(DebatePhase.Finished); | |
| setPopup({ | |
| show: true, | |
| message: "You have conceded the debate.", | |
| isJudging: false, | |
| }); | |
| } | |
| }, [roomId, currentUserId, currentUser, setDebatePhase, setPopup]); | |
| const handleConcede = useCallback(() => { | |
| if (window.confirm("Are you sure you want to concede? This will count as a loss.")) { | |
| if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) { | |
| wsRef.current.send(JSON.stringify({ | |
| type: "concede", | |
| room: roomId, | |
| userId: currentUserId, | |
| username: currentUser?.displayName || "User" | |
| })); | |
| setDebatePhase(DebatePhase.Finished); | |
| setPopup({ | |
| show: true, | |
| message: "You have conceded the debate.", | |
| isJudging: false, | |
| }); | |
| } else { | |
| // WebSocket not connected | |
| setPopup({ | |
| show: true, | |
| message: "Connection lost. Unable to concede. Please try again.", | |
| isJudging: false, | |
| }); | |
| return; | |
| } | |
| } | |
| }, [roomId, currentUserId, currentUser, setDebatePhase, setPopup]); |
🤖 Prompt for AI Agents
In frontend/src/Pages/OnlineDebateRoom.tsx around lines 800 to 817, the concede
handler updates local state immediately without ensuring the WebSocket message
was actually sent; change the flow to (1) verify wsRef.current exists and
wsRef.current.readyState === WebSocket.OPEN before attempting to send, (2) wrap
the send in a try/catch and only call setDebatePhase(DebatePhase.Finished) and
the success popup after send completes without throwing, and (3) on failure (no
socket, not open, or send error) show an error popup and do not mark the debate
finished (optionally offer a retry). Ensure you include meaningful error text in
the popup/log to aid debugging.
|
looks good |
|
this will end the debae for other user too right? |
|
Yes |
Fixes #154
Also added config.prod.yml to .gitignore so that it devs could not push it accidentally
Video
Screen.Recording.2026-01-08.at.2.11.59.PM.mov
Summary by CodeRabbit
New Features
Chores
✏️ Tip: You can customize this high-level summary in your review settings.