diff --git a/app/client/components/rooms/JoinLockedRoomDialog.tsx b/app/client/components/rooms/JoinLockedRoomDialog.tsx index 7f947b88..5b97a0c5 100644 --- a/app/client/components/rooms/JoinLockedRoomDialog.tsx +++ b/app/client/components/rooms/JoinLockedRoomDialog.tsx @@ -1,7 +1,8 @@ import { Input } from "@headlessui/react"; +import { Context } from "@reactivated"; import { UseMutationResult } from "@tanstack/react-query"; import { AxiosResponse } from "axios"; -import React from "react"; +import React, { useContext } from "react"; import { CustomDialog } from "../CustomDialog"; import { LoadingContentSpinner } from "../LoadingContentSpinner"; import { ApiErrorMessage } from "./api/ApiErrorMessage"; @@ -11,7 +12,7 @@ interface JoinLockedRoomDialogProps { joinRoomMutation: UseMutationResult< AxiosResponse, Error, - string | undefined, + { userId: number; password?: string | undefined }, unknown >; dialogOpen: boolean; @@ -24,13 +25,17 @@ export const JoinLockedRoomDialog = ({ dialogOpen, closeDialog, }: JoinLockedRoomDialogProps) => { + const { user } = useContext(Context); const [typedPassword, setTypedPassword] = React.useState(""); const handleSubmit = (event: React.FormEvent) => { event.preventDefault(); if (typedPassword !== "") { - joinRoomMutation.mutate(typedPassword, { onSuccess: closeDialog }); + joinRoomMutation.mutate( + { userId: user.id, password: typedPassword }, + { onSuccess: closeDialog }, + ); } }; diff --git a/app/client/components/rooms/admin/RoomEditDialog.tsx b/app/client/components/rooms/admin/RoomEditDialog.tsx index 64d89111..f5017a7f 100644 --- a/app/client/components/rooms/admin/RoomEditDialog.tsx +++ b/app/client/components/rooms/admin/RoomEditDialog.tsx @@ -2,6 +2,7 @@ import { RoomData } from "@client/utils/roomData"; import { Tab, TabGroup, TabList, TabPanel, TabPanels } from "@headlessui/react"; import React from "react"; import { CustomDialog } from "../../CustomDialog"; +import { RoomMembersEdit } from "./RoomMembersEdit"; import { RoomPropertiesForm } from "./RoomPropertiesForm"; interface RoomEditDialogProps { @@ -32,7 +33,9 @@ export const RoomEditDialog = ({ submitButtonLabel={title} /> - Members + + + diff --git a/app/client/components/rooms/admin/RoomMembersAdd.tsx b/app/client/components/rooms/admin/RoomMembersAdd.tsx new file mode 100644 index 00000000..e0fb9189 --- /dev/null +++ b/app/client/components/rooms/admin/RoomMembersAdd.tsx @@ -0,0 +1,97 @@ +import { RoomMember } from "@client/utils/roomData"; +import { UserApiData } from "@client/utils/userData"; +import { zosiaApi, zosiaApiRoutes } from "@client/utils/zosiaApi"; +import { + Combobox, + ComboboxButton, + ComboboxInput, + ComboboxOption, + ComboboxOptions, +} from "@headlessui/react"; +import { CheckIcon, ChevronDownIcon } from "@heroicons/react/24/solid"; +import { useQuery } from "@tanstack/react-query"; +import clsx from "clsx"; +import React, { useState } from "react"; + +interface RoomMembersAddProps { + members: RoomMember[]; + addMember: (memberId: number) => void; +} + +export const RoomMembersAdd = ({ members, addMember }: RoomMembersAddProps) => { + const [searchQuery, setSearchQuery] = useState(""); + const { isPending, isError, data, error } = useQuery({ + queryKey: ["users"], + queryFn: async () => { + const res = await zosiaApi.get<[UserApiData]>(zosiaApiRoutes.users); + return res.data; + }, + }); + + if (isPending) { + return ; + } + + if (isError) { + return
Error: {error.message}
; + } + + const filteredUsers = + searchQuery === "" + ? data + : data.filter((user) => + `${user.first_name} ${user.last_name}` + .toLowerCase() + .includes(searchQuery.toLowerCase()), + ); + + const isUserAlreadyMember = (userId: number) => { + return members.some((member) => member.id === userId); + }; + + const onChange = (user?: UserApiData) => { + if (user) { + addMember(user.id); + } + setSearchQuery(""); + }; + + return ( + setSearchQuery("")}> +
+ setSearchQuery(event.target.value)} + className="input input-bordered w-full" + /> + + + +
+ + {filteredUsers.map((user) => ( + + {user.first_name} {user.last_name} + + + ))} + +
+ ); +}; diff --git a/app/client/components/rooms/admin/RoomMembersEdit.tsx b/app/client/components/rooms/admin/RoomMembersEdit.tsx new file mode 100644 index 00000000..653fa751 --- /dev/null +++ b/app/client/components/rooms/admin/RoomMembersEdit.tsx @@ -0,0 +1,53 @@ +import { AdminTable } from "@client/components/admin/tables/AdminTable"; +import { RoomMember } from "@client/utils/roomData"; +import { TrashIcon } from "@heroicons/react/24/solid"; +import React from "react"; +import { useRoomMutations } from "../api/RoomMutations"; +import { RoomMembersAdd } from "./RoomMembersAdd"; + +interface RoomMembersEditProps { + roomID: number; + members: RoomMember[]; +} + +export const RoomMembersEdit = ({ roomID, members }: RoomMembersEditProps) => { + const { joinRoomMutation, leaveRoomMutation } = useRoomMutations(roomID); + + const deleteMember = (memberId: number) => { + leaveRoomMutation.mutate(memberId); + }; + + const addMember = (memberId: number) => { + joinRoomMutation.mutate({ userId: memberId }); + }; + + return ( +
+

Add user to room

+ + + + +

Users currently in room

+ + {members.map((member) => ( + + + {member.firstName} {member.lastName} + + + + + + + ))} + +
+ ); +}; diff --git a/app/client/components/rooms/api/RoomMutations.tsx b/app/client/components/rooms/api/RoomMutations.tsx index f452a3da..56805a3c 100644 --- a/app/client/components/rooms/api/RoomMutations.tsx +++ b/app/client/components/rooms/api/RoomMutations.tsx @@ -14,7 +14,7 @@ import React, { useContext } from "react"; import { showCustomToast } from "../../CustomToast"; import { ApiErrorMessage } from "./ApiErrorMessage"; -export const useRoomMutations = (roomId: number, roomName: string) => { +export const useRoomMutations = (roomId: number, roomName?: string) => { const { user } = useContext(Context); const queryClient = useQueryClient(); @@ -52,31 +52,41 @@ export const useRoomMutations = (roomId: number, roomName: string) => { }; const joinRoomMutation = useMutation({ - mutationFn: async (password?: string) => { + mutationFn: async (params: { userId: number; password?: string }) => { return await zosiaApi.post( zosiaApiRoutes.roomMember(roomId), { - user: user.id, - password: password, + user: params.userId, + password: params.password, }, ); }, - onSuccess: (data) => - onMutationSuccess(`You've joined room ${data.data.name}.`, data), + onSuccess: (data, { userId }) => + onMutationSuccess( + userId === user.id + ? `You've joined room ${data.data.name}.` + : `You've added user to room ${data.data.name}.`, + data, + ), onError: onMutationError, }); const leaveRoomMutation = useMutation({ - mutationFn: async () => { + mutationFn: async (userId: number) => { return await zosiaApi.delete( zosiaApiRoutes.roomMember(roomId), { - data: { user: user.id }, + data: { user: userId }, }, ); }, - onSuccess: (data) => - onMutationSuccess(`You've left room ${data.data.name}.`, data), + onSuccess: (data, userId) => + onMutationSuccess( + userId === user.id + ? `You've left room ${data.data.name}.` + : `You've removed user from room ${data.data.name}.`, + data, + ), onError: onMutationError, }); diff --git a/app/client/components/rooms/card/RoomCard.tsx b/app/client/components/rooms/card/RoomCard.tsx index f4174ecb..6b14a9ab 100644 --- a/app/client/components/rooms/card/RoomCard.tsx +++ b/app/client/components/rooms/card/RoomCard.tsx @@ -66,10 +66,10 @@ export const RoomCard = ({ if (isLocked) { setRoomPasswordDialogOpen(true); } else { - joinRoomMutation.mutate(""); + joinRoomMutation.mutate({ userId: user.id }); } }; - const leaveRoom = () => leaveRoomMutation.mutate(); + const leaveRoom = () => leaveRoomMutation.mutate(user.id); const lockRoom = () => lockRoomMutation.mutate(); const unlockRoom = () => unlockRoomMutation.mutate(); const deleteRoom = () => setRoomDeleteConfirmationDialogOpen(true); diff --git a/app/client/utils/userData.ts b/app/client/utils/userData.ts new file mode 100644 index 00000000..499caf46 --- /dev/null +++ b/app/client/utils/userData.ts @@ -0,0 +1,9 @@ +export interface UserApiData { + id: number; + email: string; + first_name: string; + last_name: string; + is_active: boolean; + is_staff: boolean; + date_joined: string; +} diff --git a/app/client/utils/zosiaApi.ts b/app/client/utils/zosiaApi.ts index 25dec36e..d80dc5f4 100644 --- a/app/client/utils/zosiaApi.ts +++ b/app/client/utils/zosiaApi.ts @@ -15,6 +15,8 @@ export const zosiaApiRoutes = { organizations: "api/v1/users/organizations/", addLectureDurations: reverse("load_durations"), + users: "api/v1/users/", + rooms: "api/v2/rooms/", room: (id: number) => `api/v2/rooms/${id}/`, roomMember: (roomId: number) => `api/v2/rooms/${roomId}/member/`,