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;
+ },
};