From 34393be872920e355985f1d391e81c87fd6db2ff Mon Sep 17 00:00:00 2001 From: ValiDM <44869004+ValiDM@users.noreply.github.com> Date: Fri, 19 Dec 2025 16:42:21 +0000 Subject: [PATCH 1/3] Add styles for the participants section and list to make it visually appealing and consistent. --- src/static/app.js | 20 ++++++++++++++++++++ src/static/styles.css | 27 +++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/src/static/app.js b/src/static/app.js index dcc1e38..0e00942 100644 --- a/src/static/app.js +++ b/src/static/app.js @@ -20,11 +20,31 @@ document.addEventListener("DOMContentLoaded", () => { const spotsLeft = details.max_participants - details.participants.length; + // Participants section + let participantsSection = ""; + if (details.participants.length > 0) { + participantsSection = ` +
+ Participants: + +
+ `; + } else { + participantsSection = ` +
+ No participants yet. +
+ `; + } + activityCard.innerHTML = `

${name}

${details.description}

Schedule: ${details.schedule}

Availability: ${spotsLeft} spots left

+ ${participantsSection} `; activitiesList.appendChild(activityCard); diff --git a/src/static/styles.css b/src/static/styles.css index a533b32..515fad3 100644 --- a/src/static/styles.css +++ b/src/static/styles.css @@ -74,6 +74,33 @@ section h3 { margin-bottom: 8px; } +/* Add some spacing for the participants section */ +.participants-section { + margin-top: 12px; + padding: 10px; + background-color: #eef2fa; + border-radius: 4px; +} + +.participants-section strong { + color: #3949ab; + display: block; + margin-bottom: 6px; +} + +.participants-list { + margin-left: 18px; + margin-bottom: 0; + color: #333; +} + +.participants-section.no-participants { + background-color: #f5f5f5; + color: #888; + font-style: italic; + padding: 8px 10px; +} + .form-group { margin-bottom: 15px; } From 04d75ac3d9dea2400816ad20fcc0820c3232b92d Mon Sep 17 00:00:00 2001 From: ValiDM <44869004+ValiDM@users.noreply.github.com> Date: Fri, 19 Dec 2025 17:02:02 +0000 Subject: [PATCH 2/3] feat: add participant delete icon, hide bullets, and auto-refresh activities Added a delete icon next to each participant for unregistering from activities Hid bullet points in the participant list for cleaner UI Implemented auto-refresh of activities after participant registration or deletion Added missing backend endpoints to support these features --- src/app.py | 22 ++++++++++++++++++++++ src/static/app.js | 32 +++++++++++++++++++++++++++++++- src/static/styles.css | 28 +++++++++++++++++++++++++++- 3 files changed, 80 insertions(+), 2 deletions(-) diff --git a/src/app.py b/src/app.py index cab7d58..ebebb88 100644 --- a/src/app.py +++ b/src/app.py @@ -10,10 +10,21 @@ from fastapi.responses import RedirectResponse import os from pathlib import Path +from fastapi.responses import JSONResponse + app = FastAPI(title="Mergington High School API", description="API for viewing and signing up for extracurricular activities") +# Redirect root URL to static index.html +@app.get("/") +def root(): + return RedirectResponse(url="/static/index.html") + +@app.get("/activities") +def get_activities(): + return JSONResponse(content=activities) + # Mount the static files directory current_dir = Path(__file__).parent app.mount("/static", StaticFiles(directory=os.path.join(Path(__file__).parent, @@ -83,6 +94,17 @@ } } +@app.delete("/activities/{activity_name}/unregister") +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="Participant not found in this activity") + activity["participants"].remove(email) + return {"message": f"Removed {email} from {activity_name}"} + @app.post("/activities/{activity_name}/signup") def signup_for_activity(activity_name: str, email: str): """Sign up a student for an activity""" diff --git a/src/static/app.js b/src/static/app.js index 0e00942..c71a87c 100644 --- a/src/static/app.js +++ b/src/static/app.js @@ -27,7 +27,12 @@ document.addEventListener("DOMContentLoaded", () => {
Participants:
`; @@ -49,6 +54,30 @@ document.addEventListener("DOMContentLoaded", () => { activitiesList.appendChild(activityCard); + // Add event listeners for delete buttons (after DOM insertion) + setTimeout(() => { + activityCard.querySelectorAll('.delete-participant-btn').forEach(btn => { + btn.addEventListener('click', async (e) => { + const activity = btn.getAttribute('data-activity'); + const email = btn.getAttribute('data-email'); + if (!confirm(`Remove ${email} from ${activity}?`)) return; + try { + const response = await fetch(`/activities/${encodeURIComponent(activity)}/unregister?email=${encodeURIComponent(email)}`, { + method: 'DELETE', + }); + const result = await response.json(); + if (response.ok) { + fetchActivities(); + } else { + alert(result.detail || 'Failed to remove participant.'); + } + } catch (err) { + alert('Error removing participant.'); + } + }); + }); + }, 0); + // Add option to select dropdown const option = document.createElement("option"); option.value = name; @@ -82,6 +111,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"; diff --git a/src/static/styles.css b/src/static/styles.css index 515fad3..edc0c04 100644 --- a/src/static/styles.css +++ b/src/static/styles.css @@ -89,9 +89,35 @@ section h3 { } .participants-list { - margin-left: 18px; + margin-left: 0; margin-bottom: 0; color: #333; + list-style: none; + padding-left: 0; + /* Remove default bullets */ +} + +.participant-item { + list-style: none; + display: flex; + align-items: center; + gap: 6px; +} + +.delete-participant-btn { + background: none; + border: none; + color: #c62828; + cursor: pointer; + font-size: 18px; + line-height: 1; + padding: 0 4px; + margin-left: 4px; + transition: color 0.2s; +} +.delete-participant-btn:hover { + color: #ff1744; +} } .participants-section.no-participants { From 4bc1e8279735fd9e5cfd7baf237b3b4ccba440dd Mon Sep 17 00:00:00 2001 From: ValiDM <44869004+ValiDM@users.noreply.github.com> Date: Fri, 19 Dec 2025 17:08:03 +0000 Subject: [PATCH 3/3] test: add FastAPI API tests with pytest Added test_app.py with basic FastAPI endpoint tests using pytest and httpx Updated requirements.txt to include pytest and httpx for testing Ensured tests directory structure for future test expansion --- requirements.txt | 4 ++++ src/static/styles.css | 1 - tests/test_app.py | 40 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 tests/test_app.py diff --git a/requirements.txt b/requirements.txt index 97dc7cd..9078b9f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,6 @@ +# For testing +pytest +httpx +pytest-asyncio fastapi uvicorn diff --git a/src/static/styles.css b/src/static/styles.css index edc0c04..49c2509 100644 --- a/src/static/styles.css +++ b/src/static/styles.css @@ -118,7 +118,6 @@ section h3 { .delete-participant-btn:hover { color: #ff1744; } -} .participants-section.no-participants { background-color: #f5f5f5; diff --git a/tests/test_app.py b/tests/test_app.py new file mode 100644 index 0000000..81c8b46 --- /dev/null +++ b/tests/test_app.py @@ -0,0 +1,40 @@ +import pytest +from fastapi.testclient import TestClient +from src.app import app + +client = TestClient(app) + +def test_get_activities(): + response = client.get("/activities") + assert response.status_code == 200 + data = response.json() + assert isinstance(data, dict) + assert "Chess Club" in data + +def test_signup_and_unregister(): + test_email = "testuser@mergington.edu" + activity = "Chess Club" + # Ensure user is not already signed up + client.delete(f"/activities/{activity}/unregister", params={"email": test_email}) + # Sign up + response = client.post(f"/activities/{activity}/signup", params={"email": test_email}) + assert response.status_code == 200 + assert f"Signed up {test_email}" in response.json()["message"] + # Unregister + response = client.delete(f"/activities/{activity}/unregister", params={"email": test_email}) + assert response.status_code == 200 + assert f"Removed {test_email}" in response.json()["message"] + +def test_signup_duplicate(): + test_email = "duplicate@mergington.edu" + activity = "Chess Club" + # Clean up + client.delete(f"/activities/{activity}/unregister", params={"email": test_email}) + # First signup + client.post(f"/activities/{activity}/signup", params={"email": test_email}) + # Duplicate signup + response = client.post(f"/activities/{activity}/signup", params={"email": test_email}) + assert response.status_code == 400 + assert "already signed up" in response.json()["detail"] + # Clean up + client.delete(f"/activities/{activity}/unregister", params={"email": test_email})