From f58df6fe913bbafeb7d3a36d07be57cc58b2fdd2 Mon Sep 17 00:00:00 2001 From: Sidiq Abiodun <33093903+Muhammadulawal@users.noreply.github.com> Date: Tue, 2 Dec 2025 02:27:14 +0000 Subject: [PATCH 1/3] initialize FastAPI application structure --- src/app.py | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/app.py b/src/app.py index 4ebb1d9..3b353c3 100644 --- a/src/app.py +++ b/src/app.py @@ -38,6 +38,45 @@ "schedule": "Mondays, Wednesdays, Fridays, 2:00 PM - 3:00 PM", "max_participants": 30, "participants": ["john@mergington.edu", "olivia@mergington.edu"] + }, + # Sports related activities + "Soccer Team": { + "description": "Join the school soccer team and compete in local matches", + "schedule": "Wednesdays, 4:00 PM - 5:30 PM", + "max_participants": 18, + "participants": ["lucas@mergington.edu", "mia@mergington.edu"] + }, + "Basketball Club": { + "description": "Practice basketball skills and play friendly games", + "schedule": "Mondays, 3:30 PM - 5:00 PM", + "max_participants": 15, + "participants": ["liam@mergington.edu", "ava@mergington.edu"] + }, + # Artistic activities + "Art Workshop": { + "description": "Explore painting, drawing, and sculpture techniques", + "schedule": "Thursdays, 4:00 PM - 5:30 PM", + "max_participants": 16, + "participants": ["ella@mergington.edu", "noah@mergington.edu"] + }, + "Drama Club": { + "description": "Act, direct, and produce school plays and performances", + "schedule": "Tuesdays, 3:30 PM - 5:00 PM", + "max_participants": 20, + "participants": ["isabella@mergington.edu", "jack@mergington.edu"] + }, + # Intellectual activities + "Math Olympiad": { + "description": "Prepare for math competitions and solve challenging problems", + "schedule": "Fridays, 4:00 PM - 5:30 PM", + "max_participants": 10, + "participants": ["ethan@mergington.edu", "charlotte@mergington.edu"] + }, + "Science Club": { + "description": "Conduct experiments and explore scientific concepts", + "schedule": "Wednesdays, 3:30 PM - 5:00 PM", + "max_participants": 14, + "participants": ["benjamin@mergington.edu", "amelia@mergington.edu"] } } @@ -62,6 +101,10 @@ def signup_for_activity(activity_name: str, email: str): # Get the specific activity activity = activities[activity_name] +# Validate student is not already signed up + if email in activity["participants"]: + raise HTTPException(status_code=400, detail="Student already signed up for this activity") + # Add student activity["participants"].append(email) return {"message": f"Signed up {email} for {activity_name}"} From 5e5d65fea879725324141cb69ac0cffcd17f1540 Mon Sep 17 00:00:00 2001 From: Sidiq Abiodun <33093903+Muhammadulawal@users.noreply.github.com> Date: Tue, 2 Dec 2025 02:34:57 +0000 Subject: [PATCH 2/3] Add participant list display and styling to activity cards --- src/static/app.js | 34 ++++++++++++++++++++++++++++------ src/static/styles.css | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 6 deletions(-) diff --git a/src/static/app.js b/src/static/app.js index dcc1e38..2a0b05c 100644 --- a/src/static/app.js +++ b/src/static/app.js @@ -20,12 +20,34 @@ document.addEventListener("DOMContentLoaded", () => { const spotsLeft = details.max_participants - details.participants.length; - activityCard.innerHTML = ` -

${name}

-

${details.description}

-

Schedule: ${details.schedule}

-

Availability: ${spotsLeft} spots left

- `; + + // Create participants list HTML + let participantsHTML = ''; + if (details.participants.length > 0) { + participantsHTML = ` +
+ Participants: + +
+ `; + } else { + participantsHTML = ` +
+ Participants: +

No one has signed up yet.

+
+ `; + } + + activityCard.innerHTML = ` +

${name}

+

${details.description}

+

Schedule: ${details.schedule}

+

Availability: ${spotsLeft} spots left

+ ${participantsHTML} + `; activitiesList.appendChild(activityCard); diff --git a/src/static/styles.css b/src/static/styles.css index a533b32..04b5f2a 100644 --- a/src/static/styles.css +++ b/src/static/styles.css @@ -63,6 +63,8 @@ section h3 { border: 1px solid #ddd; border-radius: 5px; background-color: #f9f9f9; + box-shadow: 0 2px 8px rgba(0, 102, 204, 0.08); + transition: box-shadow 0.2s; } .activity-card h4 { @@ -74,6 +76,43 @@ section h3 { margin-bottom: 8px; } +.participants-section { + margin-top: 12px; + padding: 10px 12px; + background: #e3f2fd; + border-radius: 4px; + border: 1px solid #bbdefb; +} + +.participants-section strong { + color: #1976d2; + font-size: 15px; + display: block; + margin-bottom: 6px; +} + +.participants-section ul { + list-style-type: disc; + margin-left: 20px; + margin-bottom: 0; +} + +.participants-section li { + margin-bottom: 4px; + color: #333; + font-size: 14px; +} + +.participants-section.empty { + background: #fffde7; + border: 1px solid #ffe082; +} +.participants-section.empty p { + color: #bdb76b; + font-style: italic; + margin-bottom: 0; +} + .form-group { margin-bottom: 15px; } From cc4232f4cfbc02a53edf66000ffe238b3189a9a0 Mon Sep 17 00:00:00 2001 From: Sidiq Abiodun <33093903+Muhammadulawal@users.noreply.github.com> Date: Tue, 2 Dec 2025 02:43:11 +0000 Subject: [PATCH 3/3] Implement participant unregistration feature and update UI for activity cards --- requirements.txt | 2 ++ src/app.py | 15 ++++++++++ src/static/app.js | 68 +++++++++++++++++++++++++++++++------------ src/static/styles.css | 28 +++++++++++++++--- tests/test_app.py | 61 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 152 insertions(+), 22 deletions(-) create mode 100644 tests/test_app.py diff --git a/requirements.txt b/requirements.txt index 97dc7cd..8d75658 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,4 @@ fastapi +pytest +httpx uvicorn diff --git a/src/app.py b/src/app.py index 3b353c3..dbfefde 100644 --- a/src/app.py +++ b/src/app.py @@ -108,3 +108,18 @@ def signup_for_activity(activity_name: str, email: str): # Add student activity["participants"].append(email) return {"message": f"Signed up {email} for {activity_name}"} + + +# Unregister a participant from an activity +from fastapi import Query + +@app.delete("/activities/{activity_name}/unregister") +def unregister_from_activity(activity_name: str, email: str = Query(...)): + """Remove a participant 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=400, detail="Participant not found in this activity") + activity["participants"].remove(email) + return {"message": f"Unregistered {email} from {activity_name}"} diff --git a/src/static/app.js b/src/static/app.js index 2a0b05c..17b8772 100644 --- a/src/static/app.js +++ b/src/static/app.js @@ -22,24 +22,29 @@ document.addEventListener("DOMContentLoaded", () => { // Create participants list HTML - let participantsHTML = ''; - if (details.participants.length > 0) { - participantsHTML = ` -
- Participants: - -
- `; - } else { - participantsHTML = ` -
- Participants: -

No one has signed up yet.

-
- `; - } + let participantsHTML = ''; + if (details.participants.length > 0) { + participantsHTML = ` +
+ Participants: + +
+ `; + } else { + participantsHTML = ` +
+ Participants: +

No one has signed up yet.

+
+ `; + } activityCard.innerHTML = `

${name}

@@ -51,6 +56,33 @@ document.addEventListener("DOMContentLoaded", () => { activitiesList.appendChild(activityCard); + // Add event listeners for delete icons + activityCard.querySelectorAll('.delete-participant').forEach(icon => { + icon.addEventListener('click', async (e) => { + const activityName = icon.getAttribute('data-activity'); + const email = icon.getAttribute('data-email'); + if (confirm(`Unregister ${email} from ${activityName}?`)) { + try { + const response = await fetch(`/activities/${encodeURIComponent(activityName)}/unregister?email=${encodeURIComponent(email)}`, { + method: 'DELETE', + }); + const result = await response.json(); + if (response.ok) { + fetchActivities(); // Refresh list + messageDiv.textContent = result.message; + messageDiv.style.color = 'green'; + } else { + messageDiv.textContent = result.detail || 'Failed to unregister.'; + messageDiv.style.color = 'red'; + } + } catch (err) { + messageDiv.textContent = 'Error unregistering participant.'; + messageDiv.style.color = 'red'; + } + } + }); + }); + // Add option to select dropdown const option = document.createElement("option"); option.value = name; diff --git a/src/static/styles.css b/src/static/styles.css index 04b5f2a..88510e6 100644 --- a/src/static/styles.css +++ b/src/static/styles.css @@ -91,18 +91,38 @@ section h3 { margin-bottom: 6px; } -.participants-section ul { - list-style-type: disc; - margin-left: 20px; + +.participants-list { + list-style-type: none; + margin-left: 0; margin-bottom: 0; + padding-left: 0; } -.participants-section li { +.participant-item { + display: flex; + align-items: center; margin-bottom: 4px; color: #333; font-size: 14px; } +.participant-email { + flex: 1; +} + +.delete-participant { + cursor: pointer; + color: #d32f2f; + margin-left: 10px; + font-size: 16px; + transition: color 0.2s; +} +.delete-participant:hover { + color: #b71c1c; + text-shadow: 0 0 2px #d32f2f; +} + .participants-section.empty { background: #fffde7; border: 1px solid #ffe082; diff --git a/tests/test_app.py b/tests/test_app.py new file mode 100644 index 0000000..30b25b0 --- /dev/null +++ b/tests/test_app.py @@ -0,0 +1,61 @@ +import pytest +from fastapi.testclient import TestClient +from src.app import app + +client = TestClient(app) + +# Test GET /activities +def test_get_activities(): + response = client.get("/activities") + assert response.status_code == 200 + data = response.json() + assert "Chess Club" in data + assert "Programming Class" in data + assert "Gym Class" in data + +# Test POST /activities/{activity_name}/signup +@pytest.mark.parametrize("activity,email", [ + ("Chess Club", "newstudent@mergington.edu"), + ("Programming Class", "newcoder@mergington.edu"), +]) +def test_signup_for_activity(activity, email): + response = client.post(f"/activities/{activity}/signup?email={email}") + assert response.status_code == 200 + assert f"Signed up {email} for {activity}" in response.json()["message"] + +# Test duplicate signup +def test_duplicate_signup(): + activity = "Chess Club" + email = "michael@mergington.edu" + response = client.post(f"/activities/{activity}/signup?email={email}") + assert response.status_code == 400 + assert "already signed up" in response.json()["detail"] + +# Test DELETE /activities/{activity_name}/unregister +@pytest.mark.parametrize("activity,email", [ + ("Chess Club", "daniel@mergington.edu"), + ("Programming Class", "emma@mergington.edu"), +]) +def test_unregister_from_activity(activity, email): + response = client.delete(f"/activities/{activity}/unregister?email={email}") + assert response.status_code == 200 + assert f"Unregistered {email} from {activity}" in response.json()["message"] + +# Test unregister non-existent participant +def test_unregister_nonexistent(): + activity = "Chess Club" + email = "notfound@mergington.edu" + response = client.delete(f"/activities/{activity}/unregister?email={email}") + assert response.status_code == 400 + assert "Participant not found" in response.json()["detail"] + +# Test activity not found +@pytest.mark.parametrize("endpoint", [ + "/activities/Unknown/signup?email=test@mergington.edu", + "/activities/Unknown/unregister?email=test@mergington.edu", +]) +def test_activity_not_found(endpoint): + method = "post" if "signup" in endpoint else "delete" + response = getattr(client, method)(endpoint) + assert response.status_code == 404 + assert "Activity not found" in response.json()["detail"]