Skip to content

Add participant management features to activities#2

Merged
edu1982 merged 5 commits intomainfrom
accelerate-with-copilot
Feb 10, 2026
Merged

Add participant management features to activities#2
edu1982 merged 5 commits intomainfrom
accelerate-with-copilot

Conversation

@edu1982
Copy link
Copy Markdown
Owner

@edu1982 edu1982 commented Feb 10, 2026

Enhance the API with signup validation for extracurricular activities, a participant section on activity cards, and a feature to delete participants with confirmation. This update improves user experience by displaying current participants and ensuring proper management of enrollments.

Display current participants as a bulleted list on each activity card.
Shows participant emails when enrolled, or a friendly message when empty.
Includes styled divider and matching school theme colors.
- Add DELETE endpoint to unregister participants from activities
- Display delete icon next to each participant
- Remove bullet points from participant list
- Add confirmation dialog before deletion
- Auto-refresh activity list after successful deletion
@edu1982 edu1982 requested a review from Copilot February 10, 2026 11:54
@edu1982 edu1982 merged commit b640271 into main Feb 10, 2026
8 checks passed
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR enhances the activities app with participant management capabilities by exposing an unregister API, adding backend validation for duplicate signups, and updating the frontend to display and remove participants from activity cards.

Changes:

  • Added backend validation to prevent duplicate signups and introduced a new DELETE /activities/{activity_name}/unregister endpoint.
  • Updated the frontend to render a “Current Participants” section per activity card and allow removing a participant with confirmation.
  • Added a pytest-based API test suite and included test dependencies in requirements.txt.

Reviewed changes

Copilot reviewed 5 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/app.py Adds more seeded activities, prevents duplicate signups, and introduces an unregister endpoint.
src/static/app.js Renders participant lists on activity cards, refreshes UI after mutations, and adds delete-with-confirm behavior.
src/static/styles.css Styles the participant section and delete icon for the activity cards.
tests/test_api.py Adds API tests for listing activities, signup flows, and unregister flows.
tests/__init__.py Marks tests as a package (docstring only).
requirements.txt Adds pytest and httpx for running the new test suite.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +88 to +90
// Refresh the activities list
await fetchActivities();

Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fetchActivities() is now called after successful signup/unregister, but it appends <option>s to the activity <select> without clearing existing options. With the new refresh behavior, this will cause duplicate dropdown entries after each signup/delete. Consider resetting activitySelect (and preserving the selected value) before repopulating it.

Copilot uses AI. Check for mistakes.
Comment on lines +23 to 55
// Build participants list
let participantsList = '';
if (details.participants.length > 0) {
participantsList = `
<div class="participants-section">
<strong>Current Participants:</strong>
<ul class="participants-list">
${details.participants.map(email => `
<li>
<span class="participant-email">${email}</span>
<span class="delete-icon" data-activity="${name}" data-email="${email}" title="Remove participant">🗑️</span>
</li>
`).join('')}
</ul>
</div>
`;
} else {
participantsList = `
<div class="participants-section">
<strong>Current Participants:</strong>
<p class="no-participants">No participants yet. Be the first to sign up!</p>
</div>
`;
}

activityCard.innerHTML = `
<h4>${name}</h4>
<p>${details.description}</p>
<p><strong>Schedule:</strong> ${details.schedule}</p>
<p><strong>Availability:</strong> ${spotsLeft} spots left</p>
${participantsList}
`;

Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The participants list HTML interpolates email directly into both text and attribute contexts (data-email="...") via innerHTML. Since emails originate from user input, this enables HTML/attribute injection (XSS) if a crafted value contains quotes or markup. Build these nodes with document.createElement + textContent/dataset, or escape/sanitize values before inserting into the template string.

Suggested change
// Build participants list
let participantsList = '';
if (details.participants.length > 0) {
participantsList = `
<div class="participants-section">
<strong>Current Participants:</strong>
<ul class="participants-list">
${details.participants.map(email => `
<li>
<span class="participant-email">${email}</span>
<span class="delete-icon" data-activity="${name}" data-email="${email}" title="Remove participant">🗑️</span>
</li>
`).join('')}
</ul>
</div>
`;
} else {
participantsList = `
<div class="participants-section">
<strong>Current Participants:</strong>
<p class="no-participants">No participants yet. Be the first to sign up!</p>
</div>
`;
}
activityCard.innerHTML = `
<h4>${name}</h4>
<p>${details.description}</p>
<p><strong>Schedule:</strong> ${details.schedule}</p>
<p><strong>Availability:</strong> ${spotsLeft} spots left</p>
${participantsList}
`;
// Build activity card content (static parts)
activityCard.innerHTML = `
<h4>${name}</h4>
<p>${details.description}</p>
<p><strong>Schedule:</strong> ${details.schedule}</p>
<p><strong>Availability:</strong> ${spotsLeft} spots left</p>
`;
// Build participants section using DOM APIs to avoid injecting untrusted HTML
const participantsSection = document.createElement("div");
participantsSection.className = "participants-section";
const participantsTitle = document.createElement("strong");
participantsTitle.textContent = "Current Participants:";
participantsSection.appendChild(participantsTitle);
if (details.participants.length > 0) {
const participantsUl = document.createElement("ul");
participantsUl.className = "participants-list";
details.participants.forEach((email) => {
const li = document.createElement("li");
const emailSpan = document.createElement("span");
emailSpan.className = "participant-email";
emailSpan.textContent = email;
const deleteSpan = document.createElement("span");
deleteSpan.className = "delete-icon";
deleteSpan.dataset.activity = name;
deleteSpan.dataset.email = email;
deleteSpan.title = "Remove participant";
deleteSpan.textContent = "🗑️";
li.appendChild(emailSpan);
li.appendChild(deleteSpan);
participantsUl.appendChild(li);
});
participantsSection.appendChild(participantsUl);
} else {
const noParticipantsMsg = document.createElement("p");
noParticipantsMsg.className = "no-participants";
noParticipantsMsg.textContent = "No participants yet. Be the first to sign up!";
participantsSection.appendChild(noParticipantsMsg);
}
activityCard.appendChild(participantsSection);

Copilot uses AI. Check for mistakes.
${details.participants.map(email => `
<li>
<span class="participant-email">${email}</span>
<span class="delete-icon" data-activity="${name}" data-email="${email}" title="Remove participant">🗑️</span>
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The delete control is a clickable <span> which is not keyboard-focusable and lacks an accessible name beyond the title tooltip. For accessibility, use a semantic <button type="button"> (or add role="button", tabindex="0", and key handlers) and provide an aria-label so screen readers and keyboard users can remove participants.

Suggested change
<span class="delete-icon" data-activity="${name}" data-email="${email}" title="Remove participant">🗑️</span>
<button type="button" class="delete-icon" data-activity="${name}" data-email="${email}" title="Remove participant" aria-label="Remove participant ${email} from ${name}">🗑️</button>

Copilot uses AI. Check for mistakes.
# Validate student is not already signed up
if email in activity["participants"]:
raise HTTPException(status_code=400, detail="Student is already signed up")

Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

signup_for_activity doesn’t enforce max_participants. As a result, signups can exceed capacity even though the UI displays remaining spots, leading to inconsistent behavior and overbooking. Add a check before appending (e.g., if len(participants) >= max_participants then return a 400/409 with an "Activity is full"-style detail).

Suggested change
# Validate activity capacity
max_participants = activity.get("max_participants")
if max_participants is not None and len(activity["participants"]) >= max_participants:
raise HTTPException(status_code=409, detail="Activity is full")

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants