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} -
- + {withConfirm && ( + + )}
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; }