Skip to content

Commit 8aec05d

Browse files
committed
Implement participant removal feature and enhance activity display
1 parent 8a5e38b commit 8aec05d

3 files changed

Lines changed: 127 additions & 3 deletions

File tree

src/app.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,10 +98,24 @@ def signup_for_activity(activity_name: str, email: str):
9898
# Get the specific activity
9999
activity = activities[activity_name]
100100

101-
# Validate student is not already signed up
102-
if email not in activity["participants"]:
101+
# Validate student is not already signed up
102+
if email in activity["participants"]:
103103
raise HTTPException(status_code=400, detail="Student already signed up for this activity")
104104

105105
# Add student
106106
activity["participants"].append(email)
107107
return {"message": f"Signed up {email} for {activity_name}"}
108+
109+
110+
@app.delete("/activities/{activity_name}/participants/{email}")
111+
def remove_participant(activity_name: str, email: str):
112+
"""Remove a participant from an activity"""
113+
if activity_name not in activities:
114+
raise HTTPException(status_code=404, detail="Activity not found")
115+
116+
activity = activities[activity_name]
117+
if email not in activity["participants"]:
118+
raise HTTPException(status_code=404, detail="Participant not found")
119+
120+
activity["participants"].remove(email)
121+
return {"message": f"Removed {email} from {activity_name}"}

src/static/app.js

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ document.addEventListener("DOMContentLoaded", () => {
1010
const response = await fetch("/activities");
1111
const activities = await response.json();
1212

13-
// Clear loading message
13+
// Clear loading message and reset dropdown
1414
activitiesList.innerHTML = "";
15+
activitySelect.innerHTML = '<option value="">-- Select an activity --</option>';
1516

1617
// Populate activities list
1718
Object.entries(activities).forEach(([name, details]) => {
@@ -20,15 +21,74 @@ document.addEventListener("DOMContentLoaded", () => {
2021

2122
const spotsLeft = details.max_participants - details.participants.length;
2223

24+
// Generate participants list
25+
let participantsList = '';
26+
if (details.participants.length > 0) {
27+
participantsList = `
28+
<h5>Participants:</h5>
29+
<ul class="participants-list">
30+
${details.participants.map(email => `
31+
<li>
32+
<span>${email}</span>
33+
<button
34+
type="button"
35+
class="delete-participant"
36+
data-activity="${name}"
37+
data-email="${email}"
38+
aria-label="Remove ${email}">
39+
&times;
40+
</button>
41+
</li>
42+
`).join('')}
43+
</ul>
44+
`;
45+
}
46+
2347
activityCard.innerHTML = `
2448
<h4>${name}</h4>
2549
<p>${details.description}</p>
2650
<p><strong>Schedule:</strong> ${details.schedule}</p>
2751
<p><strong>Availability:</strong> ${spotsLeft} spots left</p>
52+
${participantsList}
2853
`;
2954

3055
activitiesList.appendChild(activityCard);
3156

57+
activityCard.addEventListener("click", async (event) => {
58+
const button = event.target.closest(".delete-participant");
59+
if (!button) return;
60+
61+
const activityName = button.dataset.activity;
62+
const email = button.dataset.email;
63+
64+
try {
65+
const response = await fetch(
66+
`/activities/${encodeURIComponent(activityName)}/participants/${encodeURIComponent(email)}`,
67+
{ method: "DELETE" }
68+
);
69+
70+
const result = await response.json();
71+
72+
if (response.ok) {
73+
messageDiv.textContent = result.message;
74+
messageDiv.className = "success";
75+
fetchActivities();
76+
} else {
77+
messageDiv.textContent = result.detail || "Unable to remove participant";
78+
messageDiv.className = "error";
79+
}
80+
} catch (error) {
81+
messageDiv.textContent = "Failed to remove participant. Please try again.";
82+
messageDiv.className = "error";
83+
console.error("Error removing participant:", error);
84+
}
85+
86+
messageDiv.classList.remove("hidden");
87+
setTimeout(() => {
88+
messageDiv.classList.add("hidden");
89+
}, 5000);
90+
});
91+
3292
// Add option to select dropdown
3393
const option = document.createElement("option");
3494
option.value = name;
@@ -62,6 +122,7 @@ document.addEventListener("DOMContentLoaded", () => {
62122
messageDiv.textContent = result.message;
63123
messageDiv.className = "success";
64124
signupForm.reset();
125+
fetchActivities();
65126
} else {
66127
messageDiv.textContent = result.detail || "An error occurred";
67128
messageDiv.className = "error";

src/static/styles.css

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,55 @@ section h3 {
7474
margin-bottom: 8px;
7575
}
7676

77+
.activity-card h5 {
78+
margin-top: 15px;
79+
margin-bottom: 8px;
80+
font-size: 14px;
81+
color: #333;
82+
font-weight: bold;
83+
}
84+
85+
.activity-card ul {
86+
list-style: none;
87+
margin-left: 0;
88+
padding-left: 0;
89+
}
90+
91+
.participants-list li {
92+
display: flex;
93+
justify-content: space-between;
94+
align-items: center;
95+
margin-bottom: 6px;
96+
padding: 6px 0;
97+
border-bottom: 1px solid #eee;
98+
}
99+
100+
.participants-list li:last-child {
101+
border-bottom: none;
102+
}
103+
104+
.participants-list span {
105+
color: #555;
106+
font-size: 14px;
107+
}
108+
109+
.delete-participant {
110+
background: transparent;
111+
border: none;
112+
color: #c62828;
113+
cursor: pointer;
114+
font-size: 18px;
115+
line-height: 1;
116+
padding: 0 6px;
117+
border-radius: 4px;
118+
transition: background-color 0.2s;
119+
}
120+
121+
.delete-participant:hover,
122+
.delete-participant:focus {
123+
background-color: rgba(198, 40, 40, 0.1);
124+
}
125+
77126
.form-group {
78127
margin-bottom: 15px;
79128
}

0 commit comments

Comments
 (0)