Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
fastapi
uvicorn
httpx
watchfiles
watchfiles
pytest
63 changes: 63 additions & 0 deletions src/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,42 @@
"schedule": "Mondays, Wednesdays, Fridays, 2:00 PM - 3:00 PM",
"max_participants": 30,
"participants": ["john@mergington.edu", "olivia@mergington.edu"]
},
"Basketball Team": {
"description": "Competitive basketball practice and games",
"schedule": "Mondays and Wednesdays, 4:00 PM - 5:30 PM",
"max_participants": 15,
"participants": ["james@mergington.edu"]
},
"Tennis Club": {
"description": "Tennis instruction and match play",
"schedule": "Tuesdays and Thursdays, 4:00 PM - 5:00 PM",
"max_participants": 16,
"participants": ["sarah@mergington.edu", "alex@mergington.edu"]
},
"Drama Club": {
"description": "Perform in plays and musicals",
"schedule": "Wednesdays and Fridays, 3:30 PM - 5:00 PM",
"max_participants": 25,
"participants": ["grace@mergington.edu"]
},
"Art Studio": {
"description": "Painting, drawing, and sculpture techniques",
"schedule": "Mondays and Thursdays, 3:30 PM - 4:45 PM",
"max_participants": 18,
"participants": ["isabella@mergington.edu", "lucas@mergington.edu"]
},
"Debate Team": {
"description": "Develop argumentation and public speaking skills",
"schedule": "Tuesdays, 4:00 PM - 5:30 PM",
"max_participants": 10,
"participants": ["benjamin@mergington.edu"]
},
"Science Club": {
"description": "Explore experiments and scientific discovery",
"schedule": "Fridays, 3:30 PM - 4:45 PM",
"max_participants": 20,
"participants": ["nina@mergington.edu", "ryan@mergington.edu"]
}
}

Expand All @@ -62,6 +98,33 @@ def signup_for_activity(activity_name: str, email: str):
# Get the specific activity
activity = activities[activity_name]

# Check if student is already signed up
if email in activity["participants"]:
raise HTTPException(status_code=400, detail="Student already signed up for this activity")

# Check if activity is full
if len(activity["participants"]) >= activity["max_participants"]:
raise HTTPException(status_code=400, detail="Activity is full")

# Add student
activity["participants"].append(email)
return {"message": f"Signed up {email} for {activity_name}"}


@app.delete("/activities/{activity_name}/participants/{email}")
def remove_participant(activity_name: str, email: str):
"""Remove a participant from an activity"""
# Validate activity exists
if activity_name not in activities:
raise HTTPException(status_code=404, detail="Activity not found")

activity = activities[activity_name]

# Check if participant exists
if email not in activity["participants"]:
raise HTTPException(status_code=404, detail="Participant not found")

# Remove participant
activity["participants"].remove(email)
return {"message": f"Removed {email} from {activity_name}"}

55 changes: 55 additions & 0 deletions src/static/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,19 @@ document.addEventListener("DOMContentLoaded", () => {
<p>${details.description}</p>
<p><strong>Schedule:</strong> ${details.schedule}</p>
<p><strong>Availability:</strong> ${spotsLeft} spots left</p>
<div class="participants-section">
<strong>Participants (${details.participants.length}):</strong>
<ul>
${details.participants.length > 0
? details.participants.map(email => `
<li class="participant-item">
<span>${email}</span>
<button class="delete-participant-btn" data-activity="${name}" data-email="${email}" title="Remove participant">✕</button>
</li>
`).join('')
: '<li class="no-participants">No participants yet</li>'}
</ul>
</div>
`;

activitiesList.appendChild(activityCard);
Expand Down Expand Up @@ -62,6 +75,7 @@ document.addEventListener("DOMContentLoaded", () => {
messageDiv.textContent = result.message;
messageDiv.className = "success";
signupForm.reset();
fetchActivities();
} else {
messageDiv.textContent = result.detail || "An error occurred";
messageDiv.className = "error";
Expand All @@ -81,6 +95,47 @@ document.addEventListener("DOMContentLoaded", () => {
}
});

// Handle delete participant button clicks
document.addEventListener("click", async (event) => {
if (event.target.classList.contains("delete-participant-btn")) {
const activity = event.target.dataset.activity;
const email = event.target.dataset.email;

if (!confirm(`Remove ${email} from ${activity}?`)) {
return;
}

try {
const response = await fetch(
`/activities/${encodeURIComponent(activity)}/participants/${encodeURIComponent(email)}`,
{ method: "DELETE" }
);

if (response.ok) {
// Refresh activities list
fetchActivities();
messageDiv.textContent = `Removed ${email} from ${activity}`;
messageDiv.className = "success";
messageDiv.classList.remove("hidden");

setTimeout(() => {
messageDiv.classList.add("hidden");
}, 5000);
} else {
const result = await response.json();
messageDiv.textContent = result.detail || "Failed to remove participant";
messageDiv.className = "error";
messageDiv.classList.remove("hidden");
}
} catch (error) {
messageDiv.textContent = "Error removing participant";
messageDiv.className = "error";
messageDiv.classList.remove("hidden");
console.error("Error removing participant:", error);
}
}
});

// Initialize app
fetchActivities();
});
58 changes: 58 additions & 0 deletions src/static/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,64 @@ section h3 {
margin-bottom: 8px;
}

.participants-section {
margin-top: 15px;
padding-top: 15px;
border-top: 2px solid #e0e0e0;
}

.participants-section strong {
display: block;
margin-bottom: 8px;
color: #1a237e;
font-size: 14px;
}

.participants-section ul {
list-style: none;
margin-left: 0;
padding-left: 0;
}

.participant-item {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 14px;
color: #555;
margin-bottom: 6px;
padding: 8px;
background-color: #f0f0f0;
border-radius: 3px;
}

.participant-item span {
word-break: break-word;
flex: 1;
}

.delete-participant-btn {
background-color: #d32f2f;
color: white;
border: none;
padding: 4px 8px;
font-size: 14px;
border-radius: 3px;
cursor: pointer;
transition: background-color 0.2s;
margin-left: 10px;
flex-shrink: 0;
}

.delete-participant-btn:hover {
background-color: #b71c1c;
}

.participants-section li.no-participants {
color: #999;
font-style: italic;
}

.form-group {
margin-bottom: 15px;
}
Expand Down
Empty file added tests/__init__.py
Empty file.
Loading