Skip to content

Commit e9684cb

Browse files
authored
Merge pull request #2 from abalcobia/accelerate-with-copilot
✨ Update app and add extracurricular activities
2 parents 4889525 + 7ad42cd commit e9684cb

File tree

6 files changed

+360
-1
lines changed

6 files changed

+360
-1
lines changed

requirements.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
11
fastapi
22
uvicorn
3+
httpx
4+
pytest
5+
pytest-asyncio

src/app.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,42 @@
3838
"schedule": "Mondays, Wednesdays, Fridays, 2:00 PM - 3:00 PM",
3939
"max_participants": 30,
4040
"participants": ["john@mergington.edu", "olivia@mergington.edu"]
41+
},
42+
"Soccer Team": {
43+
"description": "Competitive soccer training and matches against other schools",
44+
"schedule": "Tuesdays and Thursdays, 4:00 PM - 5:30 PM",
45+
"max_participants": 22,
46+
"participants": ["lucas@mergington.edu", "liam@mergington.edu"]
47+
},
48+
"Swimming Club": {
49+
"description": "Swim training for all skill levels and competitive meets",
50+
"schedule": "Mondays and Wednesdays, 6:00 AM - 7:30 AM",
51+
"max_participants": 20,
52+
"participants": ["mia@mergington.edu", "noah@mergington.edu"]
53+
},
54+
"Art Studio": {
55+
"description": "Explore painting, drawing, and mixed media techniques",
56+
"schedule": "Thursdays, 3:30 PM - 5:00 PM",
57+
"max_participants": 15,
58+
"participants": ["Isabella@mergington.edu", "ava@mergington.edu"]
59+
},
60+
"Drama Club": {
61+
"description": "Act, direct, and produce theatrical performances",
62+
"schedule": "Mondays and Fridays, 4:00 PM - 5:30 PM",
63+
"max_participants": 25,
64+
"participants": ["charlotte@mergington.edu", "amelia@mergington.edu"]
65+
},
66+
"Math Olympiad": {
67+
"description": "Prepare for math competitions and solve advanced problems",
68+
"schedule": "Wednesdays, 3:30 PM - 5:00 PM",
69+
"max_participants": 15,
70+
"participants": ["ethan@mergington.edu", "harper@mergington.edu"]
71+
},
72+
"Science Club": {
73+
"description": "Conduct experiments and explore STEM topics beyond the classroom",
74+
"schedule": "Fridays, 3:30 PM - 5:00 PM",
75+
"max_participants": 20,
76+
"participants": ["james@mergington.edu", "sofia@mergington.edu"]
4177
}
4278
}
4379

@@ -62,6 +98,29 @@ def signup_for_activity(activity_name: str, email: str):
6298
# Get the specific activity
6399
activity = activities[activity_name]
64100

101+
# Validate student is not already signed up
102+
if email in activity["participants"]:
103+
raise HTTPException(status_code=400, detail="Student already signed up for this activity")
104+
65105
# Add student
66106
activity["participants"].append(email)
67107
return {"message": f"Signed up {email} for {activity_name}"}
108+
109+
110+
@app.delete("/activities/{activity_name}/signup")
111+
def unregister_from_activity(activity_name: str, email: str):
112+
"""Unregister a student from an activity"""
113+
# Validate activity exists
114+
if activity_name not in activities:
115+
raise HTTPException(status_code=404, detail="Activity not found")
116+
117+
# Get the specific activity
118+
activity = activities[activity_name]
119+
120+
# Validate student is signed up
121+
if email not in activity["participants"]:
122+
raise HTTPException(status_code=400, detail="Student is not registered for this activity")
123+
124+
# Remove student
125+
activity["participants"].remove(email)
126+
return {"message": f"Unregistered {email} from {activity_name}"}

src/static/app.js

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,42 @@ document.addEventListener("DOMContentLoaded", () => {
2424
<h4>${name}</h4>
2525
<p>${details.description}</p>
2626
<p><strong>Schedule:</strong> ${details.schedule}</p>
27-
<p><strong>Availability:</strong> ${spotsLeft} spots left</p>
27+
<p><strong>Availability:</strong> <span class="spots-badge ${spotsLeft === 0 ? 'spots-full' : spotsLeft <= 3 ? 'spots-low' : 'spots-ok'}">${spotsLeft} spot${spotsLeft !== 1 ? 's' : ''} left</span></p>
28+
<details class="participants-section">
29+
<summary><strong>Participants</strong> <span class="participant-count">${details.participants.length} / ${details.max_participants}</span></summary>
30+
${details.participants.length === 0 ? '<p class="no-participants">No participants yet — be the first!</p>' : ''}
31+
</details>
2832
`;
2933

34+
if (details.participants.length > 0) {
35+
const detailsEl = activityCard.querySelector(".participants-section");
36+
const ul = document.createElement("ul");
37+
ul.className = "participants-list";
38+
details.participants.forEach(p => {
39+
const li = document.createElement("li");
40+
41+
const icon = document.createElement("span");
42+
icon.className = "participant-icon";
43+
icon.textContent = "\u{1F464}";
44+
45+
const emailSpan = document.createElement("span");
46+
emailSpan.className = "participant-email";
47+
emailSpan.textContent = p;
48+
49+
const deleteBtn = document.createElement("button");
50+
deleteBtn.className = "delete-participant-btn";
51+
deleteBtn.innerHTML = "&#128465;";
52+
deleteBtn.title = `Unregister ${p}`;
53+
deleteBtn.addEventListener("click", () => unregisterParticipant(name, p));
54+
55+
li.appendChild(icon);
56+
li.appendChild(emailSpan);
57+
li.appendChild(deleteBtn);
58+
ul.appendChild(li);
59+
});
60+
detailsEl.appendChild(ul);
61+
}
62+
3063
activitiesList.appendChild(activityCard);
3164

3265
// Add option to select dropdown
@@ -41,6 +74,25 @@ document.addEventListener("DOMContentLoaded", () => {
4174
}
4275
}
4376

77+
// Unregister a participant from an activity
78+
async function unregisterParticipant(activityName, email) {
79+
try {
80+
const response = await fetch(
81+
`/activities/${encodeURIComponent(activityName)}/signup?email=${encodeURIComponent(email)}`,
82+
{ method: "DELETE" }
83+
);
84+
const result = await response.json();
85+
if (response.ok) {
86+
fetchActivities();
87+
} else {
88+
alert(result.detail || "Failed to unregister participant.");
89+
}
90+
} catch (error) {
91+
console.error("Error unregistering participant:", error);
92+
alert("Failed to unregister participant.");
93+
}
94+
}
95+
4496
// Handle form submission
4597
signupForm.addEventListener("submit", async (event) => {
4698
event.preventDefault();
@@ -62,6 +114,7 @@ document.addEventListener("DOMContentLoaded", () => {
62114
messageDiv.textContent = result.message;
63115
messageDiv.className = "success";
64116
signupForm.reset();
117+
fetchActivities();
65118
} else {
66119
messageDiv.textContent = result.detail || "An error occurred";
67120
messageDiv.className = "error";

src/static/styles.css

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,124 @@ section h3 {
7474
margin-bottom: 8px;
7575
}
7676

77+
.spots-badge {
78+
display: inline-block;
79+
padding: 2px 8px;
80+
border-radius: 12px;
81+
font-size: 0.85em;
82+
font-weight: bold;
83+
}
84+
85+
.spots-ok {
86+
background-color: #e8f5e9;
87+
color: #2e7d32;
88+
border: 1px solid #a5d6a7;
89+
}
90+
91+
.spots-low {
92+
background-color: #fff3e0;
93+
color: #e65100;
94+
border: 1px solid #ffcc80;
95+
}
96+
97+
.spots-full {
98+
background-color: #ffebee;
99+
color: #c62828;
100+
border: 1px solid #ef9a9a;
101+
}
102+
103+
.participants-section {
104+
margin-top: 12px;
105+
border-top: 1px dashed #ddd;
106+
padding-top: 10px;
107+
}
108+
109+
.participants-section summary {
110+
cursor: pointer;
111+
user-select: none;
112+
list-style: none;
113+
display: flex;
114+
align-items: center;
115+
justify-content: space-between;
116+
padding: 4px 0;
117+
color: #1a237e;
118+
}
119+
120+
.participants-section summary::-webkit-details-marker {
121+
display: none;
122+
}
123+
124+
.participants-section summary::after {
125+
content: '\25BC';
126+
font-size: 0.7em;
127+
transition: transform 0.2s;
128+
color: #888;
129+
}
130+
131+
.participants-section[open] summary::after {
132+
transform: rotate(180deg);
133+
}
134+
135+
.participant-count {
136+
font-size: 0.8em;
137+
background-color: #e8eaf6;
138+
color: #3949ab;
139+
border-radius: 10px;
140+
padding: 1px 7px;
141+
margin-left: 8px;
142+
font-weight: normal;
143+
}
144+
145+
.participants-list {
146+
list-style: none;
147+
margin-top: 10px;
148+
padding: 0;
149+
display: flex;
150+
flex-direction: column;
151+
gap: 6px;
152+
}
153+
154+
.participants-list li {
155+
display: flex;
156+
align-items: center;
157+
gap: 6px;
158+
font-size: 0.9em;
159+
color: #444;
160+
background-color: #f0f4ff;
161+
border-radius: 6px;
162+
padding: 5px 10px;
163+
border: 1px solid #d0d8f0;
164+
}
165+
166+
.participant-icon {
167+
font-size: 1em;
168+
}
169+
170+
.delete-participant-btn {
171+
margin-left: auto;
172+
background: none;
173+
border: none;
174+
cursor: pointer;
175+
font-size: 1em;
176+
padding: 0 2px;
177+
color: #c62828;
178+
opacity: 0.6;
179+
transition: opacity 0.15s, transform 0.15s;
180+
line-height: 1;
181+
}
182+
183+
.delete-participant-btn:hover {
184+
opacity: 1;
185+
transform: scale(1.2);
186+
}
187+
188+
.no-participants {
189+
margin-top: 10px;
190+
font-size: 0.85em;
191+
color: #888;
192+
font-style: italic;
193+
}
194+
77195
.form-group {
78196
margin-bottom: 15px;
79197
}

tests/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)