Skip to content
Open
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
6 changes: 5 additions & 1 deletion src/components/TeamCard/styles/TeamCard.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@


.teamMemberImg {
width: fit-content;
border-radius: 10px 10px 0 0;
width: 100%;
height: 100%;
object-fit: cover;
object-position: center;
}

.ImgDiv{
Expand All @@ -27,6 +28,9 @@
background-color: #aeaeae;
border-radius: 10px 10px 0 0;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
}
.ImgDiv span{
margin-left: 0;
Expand Down
101 changes: 86 additions & 15 deletions src/features/Modals/Event/TeamDetailsModal/TeamDetailsModal.jsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import React, { useState, useEffect } from "react";
import React, { useState, useEffect, useContext } from "react";
import PropTypes from "prop-types";
import { MdClose, MdGroups, MdPerson, MdEmail, MdSchool, MdCalendarToday } from "react-icons/md";
import { MdClose, MdGroups, MdPerson, MdEmail, MdSchool, MdCalendarToday, MdPersonRemove } from "react-icons/md";
import { FaCopy, FaCheck } from "react-icons/fa";
import { api } from "../../../../services";
import { Alert, MicroLoading } from "../../../../microInteraction";
import AuthContext from "../../../../context/AuthContext";
import styles from "./style/TeamDetailsModal.module.scss";

const TeamDetailsModal = ({ isOpen, onClose, formId, eventTitle }) => {
const [teamDetails, setTeamDetails] = useState(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null);
const [copiedCode, setCopiedCode] = useState(false);
const [removingMember, setRemovingMember] = useState(null);
const authCtx = useContext(AuthContext);

useEffect(() => {
if (isOpen && formId) {
Expand Down Expand Up @@ -61,7 +64,7 @@ const TeamDetailsModal = ({ isOpen, onClose, formId, eventTitle }) => {
await navigator.clipboard.writeText(teamDetails.teamCode);
setCopiedCode(true);
setTimeout(() => setCopiedCode(false), 2000);

Alert({
type: "success",
message: "Team code copied to clipboard!",
Expand All @@ -74,6 +77,59 @@ const TeamDetailsModal = ({ isOpen, onClose, formId, eventTitle }) => {
}
};

const handleRemoveMember = async (memberEmail, memberName) => {
// Confirm removal
const confirmed = window.confirm(
`Are you sure you want to remove ${memberName} from the team?`
);

if (!confirmed) return;

try {
setRemovingMember(memberEmail);

const response = await api.delete(`/api/form/removeMember/${formId}`, {
data: { memberEmail },
headers: {
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
});

if (response.status === 200) {
Alert({
type: "success",
message: "Member removed successfully!",
position: "bottom-right",
duration: 3000,
});

// Update team details with the response data
setTeamDetails(response.data.data);
} else {
setError(response.data.message || "Failed to remove member");
}
} catch (err) {
console.error("Error removing member:", err);
setError(
err.response?.data?.message || "Failed to remove member. Please try again."
);
} finally {
setRemovingMember(null);
}
};

// Check if current user is team creator or admin
const canRemoveMembers = () => {
if (!teamDetails || !authCtx.user) return false;

// Get the team creator email (first member who registered)
const teamCreatorEmail = teamDetails.members[0]?.email;
const currentUserEmail = authCtx.user.email;
const isAdmin = authCtx.user.access === "ADMIN";

return currentUserEmail === teamCreatorEmail || isAdmin;
};

const handleBackdropClick = (e) => {
if (e.target === e.currentTarget) {
onClose();
Expand Down Expand Up @@ -109,27 +165,27 @@ const TeamDetailsModal = ({ isOpen, onClose, formId, eventTitle }) => {
<MdGroups size={24} color="#f97507" />
<h4>Team Information</h4>
</div>

<div className={styles.teamDetails}>
<div className={styles.teamDetailItem}>
<span className={styles.label}>Team Name:</span>
<span className={styles.value}>{teamDetails.teamName}</span>
</div>

<div className={styles.teamDetailItem}>
<span className={styles.label}>Team Code:</span>
<div className={styles.teamCodeContainer}>
<span className={styles.teamCode}>{teamDetails.teamCode}</span>
<button
className={styles.copyButton}
<button
className={styles.copyButton}
onClick={copyTeamCode}
title="Copy team code"
>
{copiedCode ? <FaCheck size={16} /> : <FaCopy size={16} />}
</button>
</div>
</div>

<div className={styles.teamDetailItem}>
<span className={styles.label}>Team Size:</span>
<span className={styles.value}>
Expand All @@ -144,10 +200,10 @@ const TeamDetailsModal = ({ isOpen, onClose, formId, eventTitle }) => {
<MdPerson size={24} color="#f97507" />
<h4>Team Members ({teamDetails.members.length})</h4>
</div>

<div className={styles.membersList}>
{teamDetails.members.map((member, index) => (
<div key={member.id} className={styles.memberCard}>
<div key={member.id || member.email} className={styles.memberCard}>
<div className={styles.memberAvatar}>
{member.img ? (
<img src={member.img} alt={member.name} />
Expand All @@ -157,30 +213,30 @@ const TeamDetailsModal = ({ isOpen, onClose, formId, eventTitle }) => {
</div>
)}
</div>

<div className={styles.memberInfo}>
<h5 className={styles.memberName}>{member.name || "Unknown"}</h5>

<div className={styles.memberDetails}>
<div className={styles.memberDetail}>
<MdEmail size={16} />
<span>{member.email}</span>
</div>

{member.rollNumber && (
<div className={styles.memberDetail}>
<MdPerson size={16} />
<span>Roll: {member.rollNumber}</span>
</div>
)}

{member.college && (
<div className={styles.memberDetail}>
<MdSchool size={16} />
<span>{member.college}</span>
</div>
)}

{member.year && (
<div className={styles.memberDetail}>
<MdCalendarToday size={16} />
Expand All @@ -189,6 +245,21 @@ const TeamDetailsModal = ({ isOpen, onClose, formId, eventTitle }) => {
)}
</div>
</div>

{canRemoveMembers() && teamDetails.members.length > 1 && (
<button
className={styles.removeMemberBtn}
onClick={() => handleRemoveMember(member.email, member.name)}
disabled={removingMember === member.email}
title="Remove member from team"
>
{removingMember === member.email ? (
<MicroLoading customStyles={{ width: "20px", height: "20px" }} />
) : (
<MdPersonRemove size={20} />
)}
</button>
)}
</div>
))}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
opacity: 0;
transform: translateY(-20px) scale(0.95);
}

to {
opacity: 1;
transform: translateY(0) scale(1);
Expand Down Expand Up @@ -57,7 +58,7 @@
border: none;
color: rgb(0, 0, 0);
cursor: pointer;
padding: 8px ;
padding: 8px;
border-radius: 50%;
transition: background-color 0.2s;
display: flex;
Expand Down Expand Up @@ -97,12 +98,10 @@

h3 {
margin: 0;
background: linear-gradient(
rgba(244, 43, 3, 0.84) 0%,
rgba(255, 190, 11, 0.84) 100%
);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background: linear-gradient(rgba(244, 43, 3, 0.84) 0%,
rgba(255, 190, 11, 0.84) 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
font-size: 1.25rem;
font-weight: 600;
}
Expand Down Expand Up @@ -227,12 +226,45 @@
border-radius: 8px;
border: 1px solid #e27100;
transition: box-shadow 0.2s;
position: relative;

&:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
}

.removeMemberBtn {
position: absolute;
top: 12px;
right: 12px;
background: #dc2626;
color: white;
border: none;
padding: 8px;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s;
display: flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;

&:hover:not(:disabled) {
background: #b91c1c;
transform: scale(1.05);
}

&:active:not(:disabled) {
transform: scale(0.95);
}

&:disabled {
opacity: 0.6;
cursor: not-allowed;
}
}

.memberAvatar {
flex-shrink: 0;
width: 60px;
Expand Down