Skip to content

Commit 1c66265

Browse files
committed
Add participant unregistration feature and update UI for activity participants
1 parent 06a6efa commit 1c66265

3 files changed

Lines changed: 111 additions & 0 deletions

File tree

src/app.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,3 +105,18 @@ def signup_for_activity(activity_name: str, email: str):
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}/signup")
111+
def unregister_from_activity(activity_name: str, email: str):
112+
"""Unregister a student 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+
118+
if email not in activity["participants"]:
119+
raise HTTPException(status_code=400, detail="Student not signed up for this activity")
120+
121+
activity["participants"].remove(email)
122+
return {"message": f"Unregistered {email} from {activity_name}"}

src/static/app.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,24 @@ document.addEventListener("DOMContentLoaded", () => {
2020

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

23+
const participantsList = details.participants
24+
.map(
25+
(p) =>
26+
`<li><span class="participant-email">${p}</span><button class="remove-btn" data-activity="${name}" data-email="${p}" title="Remove ${p}">&#x2715;</button></li>`
27+
)
28+
.join("");
29+
2330
activityCard.innerHTML = `
2431
<h4>${name}</h4>
2532
<p>${details.description}</p>
2633
<p><strong>Schedule:</strong> ${details.schedule}</p>
2734
<p><strong>Availability:</strong> ${spotsLeft} spots left</p>
35+
<div class="participants-section">
36+
<strong>Participants:</strong>
37+
<ul class="participants-list">
38+
${participantsList || "<li class='no-participants'>No participants yet</li>"}
39+
</ul>
40+
</div>
2841
`;
2942

3043
activitiesList.appendChild(activityCard);
@@ -62,6 +75,7 @@ document.addEventListener("DOMContentLoaded", () => {
6275
messageDiv.textContent = result.message;
6376
messageDiv.className = "success";
6477
signupForm.reset();
78+
fetchActivities();
6579
} else {
6680
messageDiv.textContent = result.detail || "An error occurred";
6781
messageDiv.className = "error";
@@ -81,6 +95,41 @@ document.addEventListener("DOMContentLoaded", () => {
8195
}
8296
});
8397

98+
// Handle participant removal
99+
activitiesList.addEventListener("click", async (event) => {
100+
const btn = event.target.closest(".remove-btn");
101+
if (!btn) return;
102+
103+
const activity = btn.dataset.activity;
104+
const email = btn.dataset.email;
105+
106+
try {
107+
const response = await fetch(
108+
`/activities/${encodeURIComponent(activity)}/signup?email=${encodeURIComponent(email)}`,
109+
{ method: "DELETE" }
110+
);
111+
112+
const result = await response.json();
113+
114+
if (response.ok) {
115+
messageDiv.textContent = result.message;
116+
messageDiv.className = "success";
117+
fetchActivities();
118+
} else {
119+
messageDiv.textContent = result.detail || "An error occurred";
120+
messageDiv.className = "error";
121+
}
122+
123+
messageDiv.classList.remove("hidden");
124+
setTimeout(() => messageDiv.classList.add("hidden"), 5000);
125+
} catch (error) {
126+
messageDiv.textContent = "Failed to unregister. Please try again.";
127+
messageDiv.className = "error";
128+
messageDiv.classList.remove("hidden");
129+
console.error("Error unregistering:", error);
130+
}
131+
});
132+
84133
// Initialize app
85134
fetchActivities();
86135
});

src/static/styles.css

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

77+
.participants-section {
78+
margin-top: 10px;
79+
padding-top: 10px;
80+
border-top: 1px solid #e0e0e0;
81+
}
82+
83+
.participants-list {
84+
list-style: none;
85+
padding: 0;
86+
margin-top: 6px;
87+
}
88+
89+
.participants-list li {
90+
padding: 4px 10px;
91+
margin-bottom: 4px;
92+
background-color: #e8eaf6;
93+
color: #1a237e;
94+
border-radius: 12px;
95+
font-size: 14px;
96+
display: inline-flex;
97+
align-items: center;
98+
gap: 6px;
99+
margin-right: 4px;
100+
}
101+
102+
.remove-btn {
103+
background: none;
104+
border: none;
105+
color: #c62828;
106+
cursor: pointer;
107+
font-size: 12px;
108+
padding: 0 2px;
109+
line-height: 1;
110+
border-radius: 50%;
111+
transition: background-color 0.2s;
112+
}
113+
114+
.remove-btn:hover {
115+
background-color: #ffcdd2;
116+
}
117+
118+
.participants-list li.no-participants {
119+
background-color: transparent;
120+
color: #999;
121+
font-style: italic;
122+
}
123+
77124
.form-group {
78125
margin-bottom: 15px;
79126
}

0 commit comments

Comments
 (0)