From 75d6eb8bbc86cd3a38c1d892be5a69d35c8ee4fc Mon Sep 17 00:00:00 2001 From: Mike Johnston Date: Fri, 30 Jan 2026 19:45:22 -0500 Subject: [PATCH] test leaving challange invited to --- backend/src/routes/challenges.js | 37 ++++++++++++++++++++++++++ frontend/src/api.js | 6 +++++ frontend/src/pages/ChallengeDetail.jsx | 27 ++++++++++++++++++- 3 files changed, 69 insertions(+), 1 deletion(-) diff --git a/backend/src/routes/challenges.js b/backend/src/routes/challenges.js index 4fc3ca2..55ad064 100644 --- a/backend/src/routes/challenges.js +++ b/backend/src/routes/challenges.js @@ -213,6 +213,43 @@ router.post('/:id/respond', authMiddleware, asyncHandler(async (req, res) => { res.json({ status }); })); +// Leave a challenge (participants only, not creator) +router.post('/:id/leave', authMiddleware, asyncHandler(async (req, res) => { + const challengeId = req.params.id; + + // Get challenge to verify it exists and user is not creator + const challenges = await query( + 'SELECT * FROM challenges WHERE id = ?', + [challengeId] + ); + + if (challenges.length === 0) { + throw new AppError('Challenge not found', 404); + } + + const challenge = challenges[0]; + + if (challenge.created_by === req.user.userId) { + throw new AppError('Challenge creator cannot leave. Delete the challenge instead.', 403); + } + + // Check if user is a participant + const participation = await query( + 'SELECT * FROM challenge_participants WHERE challenge_id = ? AND user_id = ?', + [challengeId, req.user.userId] + ); + + if (participation.length === 0) { + throw new AppError('You are not a participant of this challenge', 404); + } + + // Delete user's participation and predictions + await query('DELETE FROM predictions WHERE challenge_id = ? AND user_id = ?', [challengeId, req.user.userId]); + await query('DELETE FROM challenge_participants WHERE challenge_id = ? AND user_id = ?', [challengeId, req.user.userId]); + + res.json({ success: true, message: 'Left challenge successfully' }); +})); + // Delete a challenge (only creator can delete) router.delete('/:id', authMiddleware, asyncHandler(async (req, res) => { const challengeId = req.params.id; diff --git a/frontend/src/api.js b/frontend/src/api.js index 33d3e8f..40c94ac 100644 --- a/frontend/src/api.js +++ b/frontend/src/api.js @@ -148,6 +148,12 @@ class API { }); } + async leaveChallenge(challengeId) { + return this.request(`/challenges/${challengeId}/leave`, { + method: 'POST' + }); + } + // TMDB async searchShows(query) { return this.request(`/tmdb/search?q=${encodeURIComponent(query)}`); diff --git a/frontend/src/pages/ChallengeDetail.jsx b/frontend/src/pages/ChallengeDetail.jsx index b435ccb..0ebed71 100644 --- a/frontend/src/pages/ChallengeDetail.jsx +++ b/frontend/src/pages/ChallengeDetail.jsx @@ -25,6 +25,7 @@ export default function ChallengeDetail() { const [deleting, setDeleting] = useState(false); const [friends, setFriends] = useState([]); const [inviting, setInviting] = useState(null); + const [leaving, setLeaving] = useState(false); const searchRef = useRef(null); useClickOutside(searchRef, () => setSearchResults([])); @@ -235,6 +236,22 @@ export default function ChallengeDetail() { } }; + const handleLeave = async () => { + if (!confirm('Are you sure you want to leave this challenge? Your predictions will be removed.')) { + return; + } + + setLeaving(true); + try { + await api.leaveChallenge(id); + toast.success('Left challenge successfully'); + navigate('/challenges'); + } catch (err) { + toast.error('Failed to leave challenge: ' + err.message); + setLeaving(false); + } + }; + if (loading) { return
Loading challenge...
; } @@ -271,7 +288,7 @@ export default function ChallengeDetail() { > {showInvite ? 'Cancel' : 'Invite Friends'} - {challenge.challenge.created_by === user.id && ( + {challenge.challenge.created_by === user.id ? ( + ) : ( + )} -- 2.49.1