Skip to content

Commit 78b8489

Browse files
committed
Implement participant unregistration feature with UI updates and tests
1 parent 51073bc commit 78b8489

5 files changed

Lines changed: 98 additions & 4 deletions

File tree

requirements.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
1+
pytest
2+
httpx
3+
pytest-asyncio
14
fastapi
25
uvicorn

src/app.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
12
"""
23
High School Management System API
34
@@ -87,6 +88,16 @@ def root():
8788
def get_activities():
8889
return activities
8990

91+
@app.post("/activities/{activity_name}/unregister")
92+
def unregister_participant(activity_name: str, email: str):
93+
"""Remove a participant from an activity"""
94+
if activity_name not in activities:
95+
raise HTTPException(status_code=404, detail="Activity not found")
96+
activity = activities[activity_name]
97+
if email not in activity["participants"]:
98+
raise HTTPException(status_code=404, detail="Participant not found in this activity")
99+
activity["participants"].remove(email)
100+
return {"message": f"Removed {email} from {activity_name}"}
90101

91102
@app.post("/activities/{activity_name}/signup")
92103
def signup_for_activity(activity_name: str, email: str):

src/static/app.js

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,19 @@ document.addEventListener("DOMContentLoaded", () => {
2020

2121
const spotsLeft = details.max_participants - details.participants.length;
2222

23-
// Create participants list HTML
23+
// Create participants list HTML with delete icon to the left
2424
let participantsHTML = "";
2525
if (details.participants.length > 0) {
2626
participantsHTML = `
2727
<div class="participants-section">
2828
<strong>Participants:</strong>
2929
<ul class="participants-list">
30-
${details.participants.map(email => `<li>${email}</li>`).join("")}
30+
${details.participants.map(email => `
31+
<li>
32+
<span class="delete-participant" title="Remove participant" data-activity="${encodeURIComponent(name)}" data-email="${encodeURIComponent(email)}">&#128465;</span>
33+
<span class="participant-email">${email}</span>
34+
</li>
35+
`).join("")}
3136
</ul>
3237
</div>
3338
`;
@@ -56,6 +61,29 @@ document.addEventListener("DOMContentLoaded", () => {
5661
option.textContent = name;
5762
activitySelect.appendChild(option);
5863
});
64+
65+
// Add event listeners for delete icons
66+
document.querySelectorAll(".delete-participant").forEach(icon => {
67+
icon.addEventListener("click", async (e) => {
68+
const activity = decodeURIComponent(icon.getAttribute("data-activity"));
69+
const email = decodeURIComponent(icon.getAttribute("data-email"));
70+
if (confirm(`Remove ${email} from ${activity}?`)) {
71+
try {
72+
const response = await fetch(`/activities/${encodeURIComponent(activity)}/unregister?email=${encodeURIComponent(email)}`, {
73+
method: "POST"
74+
});
75+
const result = await response.json();
76+
if (response.ok) {
77+
fetchActivities();
78+
} else {
79+
alert(result.detail || "Failed to remove participant.");
80+
}
81+
} catch (err) {
82+
alert("Failed to remove participant.");
83+
}
84+
}
85+
});
86+
});
5987
} catch (error) {
6088
activitiesList.innerHTML = "<p>Failed to load activities. Please try again later.</p>";
6189
console.error("Error fetching activities:", error);

src/static/styles.css

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,11 +159,24 @@ footer {
159159
}
160160

161161
.participants-list {
162-
list-style-type: disc;
163-
margin-left: 20px;
162+
list-style-type: none;
163+
margin-left: 0;
164164
margin-bottom: 0;
165165
color: #333;
166166
font-size: 15px;
167+
padding-left: 0;
168+
}
169+
170+
.delete-participant {
171+
color: #c00;
172+
cursor: pointer;
173+
margin-left: 0.5em;
174+
font-size: 1.1em;
175+
vertical-align: middle;
176+
transition: color 0.2s;
177+
}
178+
.delete-participant:hover {
179+
color: #f33;
167180
}
168181

169182
.no-participants {

tests/test_app.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import pytest
2+
from fastapi.testclient import TestClient
3+
from src.app import app
4+
5+
client = TestClient(app)
6+
7+
def test_get_activities():
8+
response = client.get("/activities")
9+
assert response.status_code == 200
10+
data = response.json()
11+
assert isinstance(data, dict)
12+
assert "Chess Club" in data
13+
assert "participants" in data["Chess Club"]
14+
15+
def test_signup_for_activity():
16+
email = "testuser@mergington.edu"
17+
activity = "Chess Club"
18+
# Remove if already present
19+
client.post(f"/activities/{activity}/unregister?email={email}")
20+
response = client.post(f"/activities/{activity}/signup?email={email}")
21+
assert response.status_code == 200
22+
assert f"Signed up {email} for {activity}" in response.json()["message"]
23+
# Try duplicate signup
24+
response2 = client.post(f"/activities/{activity}/signup?email={email}")
25+
assert response2.status_code == 400
26+
# Clean up
27+
client.post(f"/activities/{activity}/unregister?email={email}")
28+
29+
def test_unregister_participant():
30+
email = "removeuser@mergington.edu"
31+
activity = "Programming Class"
32+
# Ensure user is signed up
33+
client.post(f"/activities/{activity}/signup?email={email}")
34+
response = client.post(f"/activities/{activity}/unregister?email={email}")
35+
assert response.status_code == 200
36+
assert f"Removed {email} from {activity}" in response.json()["message"]
37+
# Try removing again
38+
response2 = client.post(f"/activities/{activity}/unregister?email={email}")
39+
assert response2.status_code == 404

0 commit comments

Comments
 (0)