Skip to content

Commit b3e03d4

Browse files
authored
Merge branch 'accelerate-with-copilot' into copilot/sub-pr-2-another-one
2 parents 95ff5b7 + 157cd33 commit b3e03d4

File tree

3 files changed

+108
-72
lines changed

3 files changed

+108
-72
lines changed

src/app.py

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,6 @@
2020
# Mount the static files directory
2121
current_dir = Path(__file__).parent
2222
app.mount("/static", StaticFiles(directory=os.path.join(Path(__file__).parent, "static")), name="static")
23-
# Unregister endpoint
24-
@app.post("/activities/{activity_name}/unregister")
25-
async def unregister_from_activity(activity_name: str, email: str):
26-
# Validate email is not empty or whitespace-only
27-
if not email.strip():
28-
raise HTTPException(status_code=400, detail="Email cannot be empty or whitespace-only")
29-
30-
if activity_name not in activities:
31-
raise HTTPException(status_code=404, detail="Activity not found")
32-
activity = activities[activity_name]
33-
if email not in activity["participants"]:
34-
raise HTTPException(status_code=400, detail="Participant not found")
35-
activity["participants"].remove(email)
36-
return {"message": f"{email} removed from {activity_name}"}
3723

3824
# In-memory activity database
3925
activities = {
@@ -126,3 +112,19 @@ def signup_for_activity(activity_name: str, email: str):
126112
# Add student
127113
activity["participants"].append(email)
128114
return {"message": f"Signed up {email} for {activity_name}"}
115+
116+
117+
@app.post("/activities/{activity_name}/unregister")
118+
async def unregister_from_activity(activity_name: str, email: str):
119+
"""Unregister a student from an activity"""
120+
# Validate email is not empty or whitespace-only
121+
if not email.strip():
122+
raise HTTPException(status_code=400, detail="Email cannot be empty or whitespace-only")
123+
124+
if activity_name not in activities:
125+
raise HTTPException(status_code=404, detail="Activity not found")
126+
activity = activities[activity_name]
127+
if email not in activity["participants"]:
128+
raise HTTPException(status_code=400, detail="Participant not found")
129+
activity["participants"].remove(email)
130+
return {"message": f"{email} removed from {activity_name}"}

src/static/app.js

Lines changed: 92 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -20,67 +20,102 @@ document.addEventListener("DOMContentLoaded", () => {
2020

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

23-
// Create participants list with delete icon
24-
let participantsHTML = "";
25-
if (details.participants.length > 0) {
26-
participantsHTML = `
27-
<div class="participants-section">
28-
<strong>Participants:</strong>
29-
<div class="participants-list">
30-
${details.participants
31-
.map(
32-
(email) =>
33-
`<div class="participant-item">
34-
<span class="participant-email">${email}</span>
35-
<span class="delete-icon" title="Remove participant" data-activity="${name}" data-email="${email}">&#128465;</span>
36-
</div>`
37-
)
38-
.join("")}
39-
</div>
40-
</div>
41-
`;
42-
} else {
43-
participantsHTML = `
44-
<div class="participants-section">
45-
<strong>Participants:</strong>
46-
<p class="participants-none">No participants yet.</p>
47-
</div>
48-
`;
49-
}
23+
// Create title
24+
const title = document.createElement("h4");
25+
title.textContent = name;
26+
activityCard.appendChild(title);
27+
28+
// Create description
29+
const description = document.createElement("p");
30+
description.textContent = details.description;
31+
activityCard.appendChild(description);
32+
33+
// Create schedule
34+
const schedule = document.createElement("p");
35+
const scheduleLabel = document.createElement("strong");
36+
scheduleLabel.textContent = "Schedule: ";
37+
schedule.appendChild(scheduleLabel);
38+
schedule.appendChild(document.createTextNode(details.schedule));
39+
activityCard.appendChild(schedule);
40+
41+
// Create availability
42+
const availability = document.createElement("p");
43+
const availabilityLabel = document.createElement("strong");
44+
availabilityLabel.textContent = "Availability: ";
45+
availability.appendChild(availabilityLabel);
46+
availability.appendChild(document.createTextNode(`${spotsLeft} spots left`));
47+
activityCard.appendChild(availability);
48+
49+
// Create participants section
50+
const participantsSection = document.createElement("div");
51+
participantsSection.className = "participants-section";
52+
53+
const participantsLabel = document.createElement("strong");
54+
participantsLabel.textContent = "Participants:";
55+
participantsSection.appendChild(participantsLabel);
5056

51-
activityCard.innerHTML = `
52-
<h4>${name}</h4>
53-
<p>${details.description}</p>
54-
<p><strong>Schedule:</strong> ${details.schedule}</p>
55-
<p><strong>Availability:</strong> ${spotsLeft} spots left</p>
56-
${participantsHTML}
57-
`;
58-
// Add delete icon event listeners
59-
setTimeout(() => {
60-
const deleteIcons = activityCard.querySelectorAll(".delete-icon");
61-
deleteIcons.forEach((icon) => {
62-
icon.addEventListener("click", async (e) => {
63-
const activityName = icon.getAttribute("data-activity");
64-
const email = icon.getAttribute("data-email");
65-
if (!activityName || !email) return;
66-
if (!confirm(`Remove ${email} from ${activityName}?`)) return;
67-
try {
68-
const response = await fetch(`/activities/${encodeURIComponent(activityName)}/unregister?email=${encodeURIComponent(email)}`, {
69-
method: "POST",
70-
});
71-
if (response.ok) {
72-
fetchActivities();
73-
} else {
74-
const result = await response.json();
75-
alert(result.detail || "Failed to remove participant.");
57+
if (details.participants.length > 0) {
58+
const participantsList = document.createElement("div");
59+
participantsList.className = "participants-list";
60+
61+
details.participants.forEach((email) => {
62+
const participantItem = document.createElement("div");
63+
participantItem.className = "participant-item";
64+
65+
const emailSpan = document.createElement("span");
66+
emailSpan.className = "participant-email";
67+
emailSpan.textContent = email;
68+
participantItem.appendChild(emailSpan);
69+
70+
const deleteIcon = document.createElement("span");
71+
deleteIcon.className = "delete-icon";
72+
deleteIcon.title = "Remove participant";
73+
deleteIcon.textContent = "🗑️";
74+
deleteIcon.setAttribute("role", "button");
75+
deleteIcon.setAttribute("aria-label", "Remove participant");
76+
deleteIcon.setAttribute("tabindex", "0");
77+
78+
const handleDelete = async () => {
79+
// Create a safe confirmation message
80+
const confirmMsg = `Remove participant from activity?\n\nParticipant: ${email}\nActivity: ${name}`;
81+
if (!confirm(confirmMsg)) return;
82+
try {
83+
const response = await fetch(`/activities/${encodeURIComponent(name)}/unregister?email=${encodeURIComponent(email)}`, {
84+
method: "POST",
85+
});
86+
if (response.ok) {
87+
fetchActivities();
88+
} else {
89+
const result = await response.json();
90+
alert(result.detail || "Failed to remove participant.");
91+
}
92+
} catch (error) {
93+
alert("Error removing participant.");
7694
}
77-
} catch (error) {
78-
alert("Error removing participant.");
79-
}
95+
};
96+
97+
// Store data in closure instead of data attributes for better security
98+
deleteIcon.addEventListener("click", handleDelete);
99+
deleteIcon.addEventListener("keydown", (e) => {
100+
if (e.key === "Enter" || e.key === " ") {
101+
e.preventDefault();
102+
handleDelete();
103+
}
104+
});
105+
106+
participantItem.appendChild(deleteIcon);
107+
participantsList.appendChild(participantItem);
80108
});
81-
});
82-
}, 0);
83109

110+
participantsSection.appendChild(participantsList);
111+
} else {
112+
const noParticipants = document.createElement("p");
113+
noParticipants.className = "participants-none";
114+
noParticipants.textContent = "No participants yet.";
115+
participantsSection.appendChild(noParticipants);
116+
}
117+
118+
activityCard.appendChild(participantsSection);
84119
activitiesList.appendChild(activityCard);
85120

86121
// Add option to select dropdown

tests/test_app.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import pytest
21
from fastapi.testclient import TestClient
32
from src.app import app
43

0 commit comments

Comments
 (0)