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
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# For testing
pytest
fastapi
uvicorn
httpx
Expand Down
104 changes: 76 additions & 28 deletions src/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,61 @@

# In-memory activity database
activities = {
"Chess Club": {
"description": "Learn strategies and compete in chess tournaments",
"schedule": "Fridays, 3:30 PM - 5:00 PM",
"max_participants": 12,
"participants": ["michael@mergington.edu", "daniel@mergington.edu"]
},
"Programming Class": {
"description": "Learn programming fundamentals and build software projects",
"schedule": "Tuesdays and Thursdays, 3:30 PM - 4:30 PM",
"max_participants": 20,
"participants": ["emma@mergington.edu", "sophia@mergington.edu"]
},
"Gym Class": {
"description": "Physical education and sports activities",
"schedule": "Mondays, Wednesdays, Fridays, 2:00 PM - 3:00 PM",
"max_participants": 30,
"participants": ["john@mergington.edu", "olivia@mergington.edu"]
}
"Chess Club": {
"description": "Learn strategies and compete in chess tournaments",
"schedule": "Fridays, 3:30 PM - 5:00 PM",
"max_participants": 12,
"participants": ["michael@mergington.edu", "daniel@mergington.edu"]
},
"Programming Class": {
"description": "Learn programming fundamentals and build software projects",
"schedule": "Tuesdays and Thursdays, 3:30 PM - 4:30 PM",
"max_participants": 20,
"participants": ["emma@mergington.edu", "sophia@mergington.edu"]
},
"Gym Class": {
"description": "Physical education and sports activities",
"schedule": "Mondays, Wednesdays, Fridays, 2:00 PM - 3:00 PM",
"max_participants": 30,
"participants": ["john@mergington.edu", "olivia@mergington.edu"]
},
"Soccer Team": {
"description": "Practice soccer skills and compete in inter-school matches",
"schedule": "Mondays and Wednesdays, 4:00 PM - 5:30 PM",
"max_participants": 22,
"participants": ["liam@mergington.edu", "noah@mergington.edu"]
},
"Basketball Club": {
"description": "Develop teamwork and basketball fundamentals",
"schedule": "Tuesdays and Fridays, 4:00 PM - 5:30 PM",
"max_participants": 15,
"participants": ["ava@mergington.edu", "isabella@mergington.edu"]
},
"Drama Club": {
"description": "Explore acting, stage presence, and theater production",
"schedule": "Thursdays, 3:30 PM - 5:00 PM",
"max_participants": 18,
"participants": ["mia@mergington.edu", "charlotte@mergington.edu"]
},
"Painting Workshop": {
"description": "Learn painting techniques and create visual art projects",
"schedule": "Wednesdays, 3:30 PM - 5:00 PM",
"max_participants": 16,
"participants": ["amelia@mergington.edu", "harper@mergington.edu"]
},
"Debate Team": {
"description": "Build public speaking and critical thinking through debates",
"schedule": "Mondays, 3:30 PM - 5:00 PM",
"max_participants": 14,
"participants": ["elijah@mergington.edu", "james@mergington.edu"]
},
"Math Olympiad": {
"description": "Solve advanced math problems and prepare for competitions",
"schedule": "Fridays, 3:30 PM - 5:00 PM",
"max_participants": 12,
"participants": ["lucas@mergington.edu", "benjamin@mergington.edu"]
}

}


Expand All @@ -52,16 +89,27 @@ def get_activities():
return activities



# POST: Register participant
@app.post("/activities/{activity_name}/signup")
def signup_for_activity(activity_name: str, email: str):
"""Sign up a student for an activity"""
# Validate activity exists
if activity_name not in activities:
raise HTTPException(status_code=404, detail="Activity not found")

# Get the specific activity
activity = activities[activity_name]
"""Sign up a student for an activity"""
if activity_name not in activities:
raise HTTPException(status_code=404, detail="Activity not found")
activity = activities[activity_name]
if email in activity["participants"]:
raise HTTPException(status_code=400, detail="Student already signed up for this activity")
activity["participants"].append(email)
return {"message": f"Signed up {email} for {activity_name}"}

# Add student
activity["participants"].append(email)
return {"message": f"Signed up {email} for {activity_name}"}
# DELETE: Unregister participant
@app.delete("/activities/{activity_name}/signup")
def unregister_from_activity(activity_name: str, email: str):
"""Remove a student from an activity"""
if activity_name not in activities:
raise HTTPException(status_code=404, detail="Activity not found")
activity = activities[activity_name]
if email not in activity["participants"]:
raise HTTPException(status_code=404, detail="Student not registered for this activity")
activity["participants"].remove(email)
return {"message": f"Removed {email} from {activity_name}"}
54 changes: 54 additions & 0 deletions src/static/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,67 @@ document.addEventListener("DOMContentLoaded", () => {

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

// Create participants list HTML
let participantsHTML = "";
if (details.participants && details.participants.length > 0) {
participantsHTML = `
<div class="participants-section">
<strong>Participants:</strong>
<ul class="participants-list no-bullets">
${details.participants.map(p => `
<li class="participant-item">
<span class="participant-email">${p}</span>
<button class="delete-participant" title="Remove participant" data-activity="${name}" data-email="${p}">
<span aria-hidden="true">&times;</span>
</button>
</li>
`).join("")}
</ul>
</div>
`;
} else {
participantsHTML = `
<div class="participants-section empty">
<em>No participants yet</em>
</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>
${participantsHTML}
`;

activitiesList.appendChild(activityCard);
activitiesList.appendChild(activityCard);

// Add delete event listeners after card is in DOM
const deleteButtons = activityCard.querySelectorAll('.delete-participant');
deleteButtons.forEach(btn => {
btn.addEventListener('click', async (e) => {
e.preventDefault();
e.stopPropagation();
const email = btn.getAttribute('data-email');
const activity = btn.getAttribute('data-activity');
if (!email || !activity) return;
try {
const response = await fetch(`/activities/${encodeURIComponent(activity)}/signup?email=${encodeURIComponent(email)}`, {
method: 'DELETE',
});
if (response.ok) {
fetchActivities(); // Refresh list
} else {
const result = await response.json();
alert(result.detail || 'Failed to remove participant.');
}
} catch (error) {
alert('Failed to remove participant.');
}
});
});

// Add option to select dropdown
const option = document.createElement("option");
Expand Down Expand Up @@ -62,6 +115,7 @@ document.addEventListener("DOMContentLoaded", () => {
messageDiv.textContent = result.message;
messageDiv.className = "success";
signupForm.reset();
fetchActivities(); // Refresh activities list after successful signup
} else {
messageDiv.textContent = result.detail || "An error occurred";
messageDiv.className = "error";
Expand Down
63 changes: 63 additions & 0 deletions src/static/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,69 @@ section h3 {
margin-bottom: 8px;
}

.participants-section {
margin-top: 10px;
padding: 10px;
background-color: #eef3fa;
border-radius: 4px;
border: 1px solid #c5cae9;
}

.participants-section strong {
color: #3949ab;
font-size: 15px;
}

.participants-list {
margin: 8px 0 0 0;
padding-left: 0;
list-style-type: none;
color: #333;
font-size: 15px;
}

.no-bullets {
list-style-type: none;
padding-left: 0;
}

.participant-item {
display: flex;
align-items: center;
margin-bottom: 4px;
}

.participant-email {
flex: 1;
word-break: break-all;
}

.delete-participant {
background: none;
border: none;
color: #c62828;
font-size: 20px;
margin-left: 8px;
cursor: pointer;
padding: 2px 6px;
border-radius: 50%;
transition: background 0.2s;
line-height: 1;
}

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

.participants-section.empty {
background: none;
border: none;
color: #888;
font-style: italic;
padding-left: 0;
}

.form-group {
margin-bottom: 15px;
}
Expand Down
Loading