diff --git a/frontend/react/package-lock.json b/frontend/react/package-lock.json
index 1cc4111..e0e4030 100644
--- a/frontend/react/package-lock.json
+++ b/frontend/react/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "react",
- "version": "0.0.0",
+ "version": "0.1.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "react",
- "version": "0.0.0",
+ "version": "0.1.3",
"dependencies": {
"antd": "^5.27.0",
"react": "^19.1.0",
diff --git a/frontend/react/src/assets/images/backgrounds/modal-background-sprite.svg b/frontend/react/src/assets/images/backgrounds/modal-background-sprite.svg
index 394e51c..d5cb11e 100644
--- a/frontend/react/src/assets/images/backgrounds/modal-background-sprite.svg
+++ b/frontend/react/src/assets/images/backgrounds/modal-background-sprite.svg
@@ -78,4 +78,8 @@
+
+
diff --git a/frontend/react/src/assets/images/icons/icons-sprite.svg b/frontend/react/src/assets/images/icons/icons-sprite.svg
index f5f833f..9623566 100644
--- a/frontend/react/src/assets/images/icons/icons-sprite.svg
+++ b/frontend/react/src/assets/images/icons/icons-sprite.svg
@@ -41,5 +41,9 @@
+
+
+
+
\ No newline at end of file
diff --git a/frontend/react/src/components/common/icon-button/types.ts b/frontend/react/src/components/common/icon-button/types.ts
index 727c11c..4035665 100644
--- a/frontend/react/src/components/common/icon-button/types.ts
+++ b/frontend/react/src/components/common/icon-button/types.ts
@@ -1,6 +1,13 @@
import type { ButtonHTMLAttributes } from "react";
-export type IconName = "copy" | "edit" | "save" | "info" | "link" | "cross";
+export type IconName =
+ | "copy"
+ | "edit"
+ | "save"
+ | "info"
+ | "link"
+ | "cross"
+ | "trash";
export type IconColor = "green" | "white";
export type IconButtonProps = {
diff --git a/frontend/react/src/components/common/modals/modal/Modal.scss b/frontend/react/src/components/common/modals/modal/Modal.scss
index 11d0e91..139f581 100644
--- a/frontend/react/src/components/common/modals/modal/Modal.scss
+++ b/frontend/react/src/components/common/modals/modal/Modal.scss
@@ -17,6 +17,7 @@
padding-left: 86px;
text-align: left;
background: left center / 74px 74px no-repeat;
+ min-height: 74px;
&--presents {
background-image: url("../../../../assets/images/backgrounds/modal-background-sprite.svg#presents");
@@ -33,6 +34,10 @@
&--car {
background-image: url("../../../../assets/images/backgrounds/modal-background-sprite.svg#car");
}
+
+ &--trash {
+ background-image: url("../../../../assets/images/backgrounds/modal-background-sprite.svg#trash");
+ }
}
&__title {
@@ -57,8 +62,10 @@
right: 44px;
}
- &__back-button {
+ &__action-buttons {
+ display: flex;
+ justify-content: flex-end;
+ gap: 10px;
margin-top: 48px;
- text-align: right;
}
}
diff --git a/frontend/react/src/components/common/modals/modal/Modal.tsx b/frontend/react/src/components/common/modals/modal/Modal.tsx
index 5c619e3..774997b 100644
--- a/frontend/react/src/components/common/modals/modal/Modal.tsx
+++ b/frontend/react/src/components/common/modals/modal/Modal.tsx
@@ -15,6 +15,8 @@ const Modal = ({
onClose,
onConfirm,
children,
+ withConfirm = false,
+ isLoading = false,
}: ModalProps) => {
useEffect(() => {
document.body.style.overflow = isOpen ? "hidden" : "";
@@ -38,15 +40,35 @@ const Modal = ({
-
+ {} : onClose}
+ disabled={isLoading}
+ />
{children}
-
-
diff --git a/frontend/react/src/components/common/modals/modal/types.ts b/frontend/react/src/components/common/modals/modal/types.ts
index f9f40c5..916cb02 100644
--- a/frontend/react/src/components/common/modals/modal/types.ts
+++ b/frontend/react/src/components/common/modals/modal/types.ts
@@ -5,6 +5,7 @@ export const ICON_NAMES = {
COOKIE: "cookie",
NOTE: "note",
CAR: "car",
+ TRASH: "trash",
} as const;
type IconName = (typeof ICON_NAMES)[keyof typeof ICON_NAMES];
@@ -18,4 +19,6 @@ export interface ModalProps {
onClose: () => void;
onConfirm: () => void;
children: ReactNode;
+ withConfirm?: boolean;
+ isLoading?: boolean;
}
diff --git a/frontend/react/src/components/common/participant-card/ParticipantCard.tsx b/frontend/react/src/components/common/participant-card/ParticipantCard.tsx
index 1cbdb5e..44f4aaf 100644
--- a/frontend/react/src/components/common/participant-card/ParticipantCard.tsx
+++ b/frontend/react/src/components/common/participant-card/ParticipantCard.tsx
@@ -3,6 +3,7 @@ import InfoButton from "../info-button/InfoButton";
import ItemCard from "../item-card/ItemCard";
import { type ParticipantCardProps } from "./types";
import "./ParticipantCard.scss";
+import DeleteButton from "../delete-button/DeleteButton";
const ParticipantCard = ({
firstName,
@@ -13,6 +14,7 @@ const ParticipantCard = ({
adminInfo = "",
participantLink = "",
onInfoButtonClick,
+ onDeleteUserButtonClick,
}: ParticipantCardProps) => {
return (
@@ -39,6 +41,10 @@ const ParticipantCard = ({
{!isCurrentUser && isAdmin ? (
) : null}
+
+ {isCurrentUserAdmin && !isAdmin ? (
+
+ ) : null}
);
diff --git a/frontend/react/src/components/common/participant-card/types.ts b/frontend/react/src/components/common/participant-card/types.ts
index da2f24a..39e5212 100644
--- a/frontend/react/src/components/common/participant-card/types.ts
+++ b/frontend/react/src/components/common/participant-card/types.ts
@@ -7,4 +7,5 @@ export interface ParticipantCardProps {
adminInfo?: string;
participantLink?: string;
onInfoButtonClick?: () => void;
+ onDeleteUserButtonClick?: () => void;
}
diff --git a/frontend/react/src/components/room-page/RoomPage.tsx b/frontend/react/src/components/room-page/RoomPage.tsx
index 9797c78..6b4c0f9 100644
--- a/frontend/react/src/components/room-page/RoomPage.tsx
+++ b/frontend/react/src/components/room-page/RoomPage.tsx
@@ -87,6 +87,7 @@ const RoomPage = () => {
participants={participants ?? []}
roomDetails={roomDetails ?? ({} as GetRoomResponse)}
onDrawNames={() => fetchRandomize()}
+ onRefetchParticipants={fetchParticipants}
/>
);
diff --git a/frontend/react/src/components/room-page/participants-list/ParticipantsList.tsx b/frontend/react/src/components/room-page/participants-list/ParticipantsList.tsx
index ab55a17..44f9fa8 100644
--- a/frontend/react/src/components/room-page/participants-list/ParticipantsList.tsx
+++ b/frontend/react/src/components/room-page/participants-list/ParticipantsList.tsx
@@ -4,16 +4,24 @@ import ParticipantCard from "@components/common/participant-card/ParticipantCard
import ParticipantDetailsModal from "@components/common/modals/participant-details-modal/ParticipantDetailsModal";
import type { Participant } from "@types/api";
import {
+ BASE_API_URL,
MAX_PARTICIPANTS_NUMBER,
generateParticipantLink,
} from "@utils/general";
import { type ParticipantsListProps, type PersonalInformation } from "./types";
import "./ParticipantsList.scss";
+import DeleteUserModal from "@components/common/modals/delete-user-modal/DeleteUserModal";
-const ParticipantsList = ({ participants }: ParticipantsListProps) => {
+const ParticipantsList = ({
+ participants,
+ onParticipantDeleted,
+}: ParticipantsListProps) => {
const { userCode } = useParams();
const [selectedParticipant, setSelectedParticipant] =
useState(null);
+ const [selectedParticipantToDelete, setSelectedParticipantToDelete] =
+ useState(null);
+ const [isDeleting, setIsDeleting] = useState(false);
const admin = participants?.find((participant) => participant?.isAdmin);
const restParticipants = participants?.filter(
@@ -36,6 +44,42 @@ const ParticipantsList = ({ participants }: ParticipantsListProps) => {
const handleModalClose = () => setSelectedParticipant(null);
+ const handleDeleteUserButtonClick = (participant: Participant) => {
+ setSelectedParticipantToDelete(participant);
+ };
+
+ const handleDeleteUserCancel = () => {
+ if (isDeleting) return;
+ setSelectedParticipantToDelete(null);
+ };
+
+ const handleDeleteUserConfirm = async (participant: Participant) => {
+ if (!participant || isDeleting) return;
+
+ setIsDeleting(true);
+ try {
+ const response = await fetch(
+ `${BASE_API_URL}/api/users/${participant.id}?userCode=${userCode}`,
+ {
+ method: "DELETE",
+ },
+ );
+
+ if (!response.ok) {
+ throw new Error("Failed to delete participant");
+ }
+
+ onParticipantDeleted?.();
+
+ setSelectedParticipantToDelete(null);
+ } catch (error) {
+ console.error("Error deleting participant:", error);
+ alert("Failed to delete participant. Please try again.");
+ } finally {
+ setIsDeleting(false);
+ }
+ };
+
return (
{
? () => handleInfoButtonClick(user)
: undefined
}
+ onDeleteUserButtonClick={() => handleDeleteUserButtonClick(user)}
/>
))}
@@ -93,6 +138,16 @@ const ParticipantsList = ({ participants }: ParticipantsListProps) => {
personalInfoData={selectedParticipant}
/>
) : null}
+
+ {selectedParticipantToDelete ? (
+
+ ) : null}
);
diff --git a/frontend/react/src/components/room-page/participants-list/types.ts b/frontend/react/src/components/room-page/participants-list/types.ts
index cfa8f12..68fb400 100644
--- a/frontend/react/src/components/room-page/participants-list/types.ts
+++ b/frontend/react/src/components/room-page/participants-list/types.ts
@@ -2,6 +2,7 @@ import type { GetParticipantsResponse } from "@types/api.ts";
export interface ParticipantsListProps {
participants: GetParticipantsResponse;
+ onParticipantDeleted?: () => void;
}
export interface PersonalInformation {
diff --git a/frontend/react/src/components/room-page/room-page-content/RoomPageContent.tsx b/frontend/react/src/components/room-page/room-page-content/RoomPageContent.tsx
index a838e32..10f362b 100644
--- a/frontend/react/src/components/room-page/room-page-content/RoomPageContent.tsx
+++ b/frontend/react/src/components/room-page/room-page-content/RoomPageContent.tsx
@@ -20,6 +20,7 @@ const RoomPageContent = ({
participants,
roomDetails,
onDrawNames,
+ onRefetchParticipants,
}: RoomPageContentProps) => {
const { userCode } = useParams();
const [isUserDetailsModalOpen, setIsUserDetailsModalOpen] = useState(false);
@@ -102,7 +103,10 @@ const RoomPageContent = ({
-
+
{isAdmin || (!isAdmin && isRandomized) ? (
diff --git a/frontend/react/src/components/room-page/room-page-content/types.ts b/frontend/react/src/components/room-page/room-page-content/types.ts
index 354bbb1..0cd3529 100644
--- a/frontend/react/src/components/room-page/room-page-content/types.ts
+++ b/frontend/react/src/components/room-page/room-page-content/types.ts
@@ -4,4 +4,5 @@ export interface RoomPageContentProps {
participants: GetParticipantsResponse;
roomDetails: GetRoomResponse;
onDrawNames: () => void;
+ onRefetchParticipants?: () => void | Promise;
}