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
51 changes: 49 additions & 2 deletions src/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 activities
"Soccer Team": {
"description": "Join the school soccer team and compete in local leagues",
"schedule": "Tuesdays and Thursdays, 4:00 PM - 5:30 PM",
"max_participants": 22,
"participants": ["lucas@mergington.edu", "mia@mergington.edu"]
},
"Basketball Club": {
"description": "Practice basketball skills and play friendly matches",
"schedule": "Wednesdays, 3:30 PM - 5:00 PM",
"max_participants": 15,
"participants": ["liam@mergington.edu", "ava@mergington.edu"]
},
# Artistic activities
"Art Club": {
"description": "Explore painting, drawing, and other visual arts",
"schedule": "Mondays, 3:30 PM - 5:00 PM",
"max_participants": 18,
"participants": ["noah@mergington.edu", "isabella@mergington.edu"]
},
"Drama Society": {
"description": "Participate in theater productions and acting workshops",
"schedule": "Thursdays, 4:00 PM - 5:30 PM",
"max_participants": 16,
"participants": ["ethan@mergington.edu", "charlotte@mergington.edu"]
},
# Intellectual activities
"Math Club": {
"description": "Solve challenging math problems and prepare for competitions",
"schedule": "Fridays, 2:00 PM - 3:30 PM",
"max_participants": 14,
"participants": ["amelia@mergington.edu", "benjamin@mergington.edu"]
},
"Science Olympiad": {
"description": "Engage in science experiments and academic competitions",
"schedule": "Wednesdays, 4:00 PM - 5:30 PM",
"max_participants": 12,
"participants": ["elijah@mergington.edu", "harper@mergington.edu"]
}
}

Expand All @@ -59,9 +98,17 @@ def signup_for_activity(activity_name: str, email: str):
if activity_name not in activities:
raise HTTPException(status_code=404, detail="Activity not found")

# Get the specificy 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")

# Validate activity is not 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}"}

return {"message": f"{email} signed up for {activity_name}"}
155 changes: 97 additions & 58 deletions src/static/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,83 +4,122 @@ document.addEventListener("DOMContentLoaded", () => {
const signupForm = document.getElementById("signup-form");
const messageDiv = document.getElementById("message");

// Function to fetch activities from API
// Fetch and render activities
let activitiesData = {};

async function fetchActivities() {
try {
const response = await fetch("/activities");
const activities = await response.json();

// Clear loading message
activitiesList.innerHTML = "";

// Populate activities list
Object.entries(activities).forEach(([name, details]) => {
const activityCard = document.createElement("div");
activityCard.className = "activity-card";

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

activityCard.innerHTML = `
<h4>${name}</h4>
<p>${details.description}</p>
<p><strong>Schedule:</strong> ${details.schedule}</p>
<p><strong>Availability:</strong> ${spotsLeft} spots left</p>
`;

activitiesList.appendChild(activityCard);

// Add option to select dropdown
const option = document.createElement("option");
option.value = name;
option.textContent = name;
activitySelect.appendChild(option);
});
} catch (error) {
activitiesList.innerHTML = "<p>Failed to load activities. Please try again later.</p>";
console.error("Error fetching activities:", error);
}
const res = await fetch("/activities");
activitiesData = await res.json();
renderActivities(activitiesData);
}

// Handle form submission
function renderActivities(activities) {
const activitiesList = document.getElementById('activities-list');
activitiesList.innerHTML = '';
Object.entries(activities).forEach(([name, info]) => {
const card = document.createElement('div');
card.className = 'activity-card';

// Activity name
const title = document.createElement('h4');
title.textContent = name;
card.appendChild(title);

// Description
const desc = document.createElement('p');
desc.textContent = info.description;
card.appendChild(desc);

// Schedule
const sched = document.createElement('p');
sched.innerHTML = `<strong>Schedule:</strong> ${info.schedule}`;
card.appendChild(sched);

// Availability
const available = info.max_participants - info.participants.length;
const avail = document.createElement('p');
avail.innerHTML = `<strong>Available spots:</strong> <span class="availability-count">${available}</span>`;
card.appendChild(avail);

// --- Participants section ---
const participantsSection = document.createElement('div');
participantsSection.className = 'participants-section';
const participantsTitle = document.createElement('strong');
participantsTitle.textContent = 'Participants:';
participantsSection.appendChild(participantsTitle);

const participantsList = document.createElement('ul');
participantsList.className = 'participants-list';
if (Array.isArray(info.participants) && info.participants.length > 0) {
info.participants.forEach(email => {
const li = document.createElement('li');
li.textContent = email;
participantsList.appendChild(li);
});
} else {
const li = document.createElement('li');
li.textContent = 'No participants yet';
li.style.color = '#888';
participantsList.appendChild(li);
}
participantsSection.appendChild(participantsList);
card.appendChild(participantsSection);
// --- End participants section ---

activitiesList.appendChild(card);
});
}

// Populate activity dropdown
function populateActivityDropdown(activities) {
activitySelect.innerHTML = "<option value=''>-- Select an activity --</option>";
Object.keys(activities).forEach((name) => {
const option = document.createElement("option");
option.value = name;
option.textContent = name;
activitySelect.appendChild(option);
});
}

// Handle signup form
signupForm.addEventListener("submit", async (event) => {
event.preventDefault();

const email = document.getElementById("email").value;
const email = document.getElementById("email").value.trim();
const activity = document.getElementById("activity").value;
messageDiv.className = "message hidden";
messageDiv.textContent = "";

if (!email || !activity) return;

try {
const response = await fetch(
const res = await fetch(
`/activities/${encodeURIComponent(activity)}/signup?email=${encodeURIComponent(email)}`,
{
method: "POST",
}
);

const result = await response.json();

if (response.ok) {
messageDiv.textContent = result.message;
messageDiv.className = "success";
signupForm.reset();
if (!res.ok) {
const err = await res.json();
messageDiv.className = "message error";
messageDiv.textContent = err.detail || "Signup failed";
} else {
messageDiv.textContent = result.detail || "An error occurred";
messageDiv.className = "error";
const data = await res.json();
messageDiv.className = "message success";
messageDiv.textContent = data.message;
// Refresh activities and dropdown
await fetchActivities();
populateActivityDropdown(activitiesData);
signupForm.reset();
}

messageDiv.classList.remove("hidden");

// Hide message after 5 seconds
setTimeout(() => {
messageDiv.classList.add("hidden");
}, 5000);
} catch (error) {
messageDiv.textContent = "Failed to sign up. Please try again.";
messageDiv.className = "error";
messageDiv.classList.remove("hidden");
console.error("Error signing up:", error);
messageDiv.className = "message error";
messageDiv.textContent = "Network error. Please try again.";
}
});

// Initialize app
fetchActivities();
fetchActivities().then(() => {
populateActivityDropdown(activitiesData);
});
});
12 changes: 12 additions & 0 deletions src/static/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,18 @@ <h3>Available Activities</h3>
<div id="activities-list">
<!-- Activities will be loaded here -->
<p>Loading activities...</p>
<!--
Each activity card should include a participants section like:
<div class="activity-card">
...activity info...
<div class="participants-section">
<strong>Participants:</strong>
<ul class="participants-list">
<!-- List items will be injected here by JS -->
</ul>
</div>
</div>
-->
</div>
</section>

Expand Down
31 changes: 31 additions & 0 deletions src/static/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ section h3 {
border: 1px solid #ddd;
border-radius: 5px;
background-color: #f9f9f9;
/* Add a subtle shadow for depth */
box-shadow: 0 1px 4px rgba(26, 35, 126, 0.06);
}

.activity-card h4 {
Expand All @@ -74,6 +76,35 @@ section h3 {
margin-bottom: 8px;
}

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

.participants-section strong {
color: #3949ab;
font-size: 15px;
display: block;
margin-bottom: 6px;
}

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

.participants-list li {
margin-bottom: 2px;
padding-left: 2px;
word-break: break-all;
}

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