diff --git a/code/frontend/src/components/ParticipantsList.jsx b/code/frontend/src/components/ParticipantsList.jsx new file mode 100644 index 0000000..15122f6 --- /dev/null +++ b/code/frontend/src/components/ParticipantsList.jsx @@ -0,0 +1,150 @@ +import { useState } from "react"; +import { Collapse } from "@mui/material"; +import { activityService } from "../services/activityService"; + +const ParticipantsList = ({ activityId, onError }) => { + const [participants, setParticipants] = useState(null); + const [isExpanded, setIsExpanded] = useState(false); + const [isLoading, setIsLoading] = useState(false); + + const fetchParticipants = async () => { + if (participants) return; // Already loaded + + setIsLoading(true); + try { + const response = await activityService.getActivityParticipants(activityId); + setParticipants(response.content || []); + } catch (error) { + console.error("Failed to fetch participants:", error); + onError?.("Failed to load participants"); + } finally { + setIsLoading(false); + } + }; + + const toggleExpand = async () => { + const wasExpanded = isExpanded; + setIsExpanded(!wasExpanded); + + if (!wasExpanded && !participants) { + await fetchParticipants(); + } + }; + + return ( + <> + + +
+

Participants:

+ {isLoading ? ( +
Loading participants...
+ ) : participants && participants.length > 0 ? ( +
+ {participants.map((participant, index) => ( +
+
+ {participant.username.charAt(0).toUpperCase()} +
+
+ {participant.username} + {participant.roleType === "ADMIN" && ( + Admin + )} +
+
+ ))} +
+ ) : participants && participants.length === 0 ? ( +
No participants found
+ ) : null} +
+
+ + ); +}; + +const styles = { + participantsButton: { + backgroundColor: "#6b7280", + color: "white", + border: "none", + padding: "0.75rem 1rem", + borderRadius: "0.5rem", + fontSize: "0.85rem", + fontWeight: "600", + cursor: "pointer", + transition: "background-color 0.2s", + flex: "1", + whiteSpace: "nowrap", + }, + participantsSection: { + marginTop: "1rem", + padding: "1rem", + backgroundColor: "#f9fafb", + borderRadius: "0.5rem", + border: "1px solid #e5e7eb", + }, + participantsTitle: { + margin: "0 0 0.75rem 0", + color: "#374151", + fontSize: "1rem", + fontWeight: "600", + }, + participantsList: { + display: "flex", + flexDirection: "column", + gap: "0.5rem", + }, + participantItem: { + display: "flex", + alignItems: "center", + gap: "0.75rem", + padding: "0.5rem", + backgroundColor: "white", + borderRadius: "0.375rem", + border: "1px solid #e5e7eb", + }, + participantAvatar: { + width: "32px", + height: "32px", + backgroundColor: "#3b82f6", + color: "white", + borderRadius: "50%", + display: "flex", + alignItems: "center", + justifyContent: "center", + fontWeight: "bold", + fontSize: "0.875rem", + }, + participantInfo: { + display: "flex", + alignItems: "center", + gap: "0.5rem", + flex: "1", + }, + participantName: { + color: "#1f2937", + fontSize: "0.9rem", + fontWeight: "500", + }, + adminBadge: { + backgroundColor: "#fbbf24", + color: "#92400e", + padding: "0.125rem 0.5rem", + borderRadius: "0.25rem", + fontSize: "0.75rem", + fontWeight: "600", + }, + loadingText: { + color: "#6b7280", + fontSize: "0.875rem", + fontStyle: "italic", + textAlign: "center", + padding: "1rem", + }, +}; + +export default ParticipantsList; \ No newline at end of file diff --git a/code/frontend/src/pages/Home.jsx b/code/frontend/src/pages/Home.jsx index 617e315..b7aa5de 100644 --- a/code/frontend/src/pages/Home.jsx +++ b/code/frontend/src/pages/Home.jsx @@ -6,6 +6,7 @@ import { styled } from "@mui/material/styles"; import LogoutIcon from "@mui/icons-material/Logout"; import { activityService } from "../services/activityService"; import AvatarUpload from "../components/Avator"; +import ParticipantsList from "../components/ParticipantsList"; const StyledTab = styled(Tab)({ textTransform: "none", @@ -388,14 +389,24 @@ export default function Home() { {activity.location} - +
+ setNotification({ + open: true, + message, + severity: "error", + })} + /> + +
); })} @@ -643,18 +654,28 @@ const styles = { metaIcon: { fontSize: "1rem", }, + activityActions: { + display: "flex", + flexDirection: "column", + gap: "0.5rem", + marginTop: "auto", + alignItems: "stretch", + }, joinButton: { backgroundColor: "#3b82f6", color: "white", border: "none", - padding: "0.75rem 1.5rem", + padding: "0.5rem 0.75rem", borderRadius: "0.5rem", - fontSize: "0.9rem", + fontSize: "0.95rem", fontWeight: "600", cursor: "pointer", transition: "background-color 0.2s", width: "100%", + minWidth: "120px", + maxWidth: "200px", alignSelf: "center", - marginTop: "auto", + margin: "0 auto", + boxSizing: "border-box", }, }; diff --git a/code/frontend/src/services/activityService.js b/code/frontend/src/services/activityService.js index 4562ac8..7f494b5 100644 --- a/code/frontend/src/services/activityService.js +++ b/code/frontend/src/services/activityService.js @@ -52,4 +52,10 @@ export const activityService = { const response = await api.post("/activity", activityData); return response.data; }, + getActivityParticipants: async (activityId, page = 0, size = 20) => { + const response = await api.get(`/activities/${activityId}/participants`, { + params: { page, size } + }); + return response.data.data; + }, };