diff --git a/src/hooks/AccountHooks.ts b/src/hooks/AccountHooks.ts index 8209300..aaeba2f 100644 --- a/src/hooks/AccountHooks.ts +++ b/src/hooks/AccountHooks.ts @@ -285,14 +285,14 @@ export function useDeleteAccount(accountId: string){ } -export function UseRemoveAccountFromRaidGroup(raidGroupId?: string, accountId?: string){ +export function useRemoveAccountFromRaidGroup(raidGroupId?: string, accountId?: string){ const queryClient = useQueryClient(); return useMutation({ mutationKey: ["removeAccountFromRaidGroup", raidGroupId, accountId], mutationFn: async () => { - const response = await api.delete(`/account/raidGroup/${raidGroupId}/permissions/${accountId}`); + const response = await api.delete(`/account/${accountId}/raidGroup/${raidGroupId}/permission`); if(response.status !== 200){ throw new Error("Failed to remove account from raid group"); diff --git a/src/hooks/ClassGroupHooks.ts b/src/hooks/ClassGroupHooks.ts index ca7cbac..3b0aaff 100644 --- a/src/hooks/ClassGroupHooks.ts +++ b/src/hooks/ClassGroupHooks.ts @@ -109,7 +109,6 @@ export function useUpdateClassGroup(raidGroupId: string){ return useMutation({ mutationKey: ["updateClassGroup"], mutationFn: async ({classGroup, gameClassIds}:{classGroup: ClassGroup; gameClassIds: string[];}) => { - console.log("Hit"); const response = await api.put(`/raidGroup/${raidGroupId}/classGroup/${classGroup.classGroupId}`, { classGroup, diff --git a/src/hooks/RaidGroupRequestHooks.ts b/src/hooks/RaidGroupRequestHooks.ts new file mode 100644 index 0000000..0697a8a --- /dev/null +++ b/src/hooks/RaidGroupRequestHooks.ts @@ -0,0 +1,124 @@ +import { RaidGroupPermissionType } from "@/interface/RaidGroup"; +import { RaidGroupRequest } from "@/interface/RaidGroupRequest"; +import { api } from "@/util/AxiosUtil"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; + + +export function useGetRaidGroupRequests(raidGroupId: string, page: number, pageSize: number, searchTerm?: string){ + const searchParams = new URLSearchParams(); + searchParams.append("page", page.toString()); + searchParams.append("pageSize", pageSize.toString()); + if(searchTerm){ + searchParams.append("searchTerm", searchTerm); + } + + return useQuery({ + queryKey: ["raidGroupRequest", raidGroupId, {page, pageSize, searchTerm}], + queryFn: async () => { + const response = await api.get(`/raidGroup/${raidGroupId}/raidGroupRequest?${searchParams}`); + + if(response.status !== 200){ + throw new Error("Failed to get raid group requests"); + } + else if(response.data.errors){ + throw new Error(response.data.errors.join(", ")); + } + + return response.data as RaidGroupRequest[]; + }, + enabled: !!raidGroupId && raidGroupId !== "" + }); +} + +export function useGetRaidGroupRequestCount(raidGroupId?: string, searchTerm?: string){ + const searchParams = new URLSearchParams(); + if(searchTerm){ + searchParams.append("searchTerm", searchTerm); + } + + return useQuery({ + queryKey: ["raidGroupRequest", raidGroupId, "count", searchTerm], + queryFn: async () => { + const response = await api.get(`/raidGroup/${raidGroupId}/raidGroupRequest/count?${searchParams}`); + + if(response.status !== 200){ + throw new Error("Failed to get raid group request count"); + } + else if(response.data.errors){ + throw new Error(response.data.errors.join(", ")); + } + + return response.data.count as number; + }, + enabled: !!raidGroupId && raidGroupId !== "" + }); +} + + +export function useCreateRaidGroupRequest(raidGroupId: string){ + const queryClient = useQueryClient(); + + return useMutation({ + mutationKey: ["raidGroupRequest", raidGroupId], + mutationFn: async (requestMessage: string) => { + const response = await api.post(`/raidGroup/${raidGroupId}/raidGroupRequest`, { + requestMessage + }); + + if(response.status !== 200){ + throw new Error("Failed to create raid group request"); + } + else if(response.data.errors){ + throw new Error(response.data.errors.join(", ")); + } + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["raidGroupRequest", raidGroupId] }); + } + }); +} + +export function useResolveRaidGroupRequest(raidGroupId: string, raidGroupRequestId: string){ + const queryClient = useQueryClient(); + + return useMutation({ + mutationKey: ["raidGroupRequest", raidGroupId, raidGroupRequestId], + mutationFn: async (permission: RaidGroupPermissionType) => { + const response = await api.put(`/raidGroup/${raidGroupId}/raidGroupRequest/${raidGroupRequestId}/resolve`, { + resolution: permission + }); + + if(response.status !== 200){ + throw new Error("Failed to resolve raid group request"); + } + else if(response.data.errors){ + throw new Error(response.data.errors.join(", ")); + } + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["raidGroupRequest", raidGroupId] }); + } + }); + +} + +export function useDeleteRaidGroupRequest(raidGroupId: string, raidGroupRequestId: string){ + const queryClient = useQueryClient(); + + return useMutation({ + mutationKey: ["raidGroupRequest", raidGroupId, raidGroupRequestId], + mutationFn: async () => { + const response = await api.delete(`/raidGroup/${raidGroupId}/raidGroupRequest/${raidGroupRequestId}`); + + if(response.status !== 200){ + throw new Error("Failed to delete raid group request"); + } + else if(response.data.errors){ + throw new Error(response.data.errors.join(", ")); + } + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["raidGroupRequest", raidGroupId] }); + } + }); +} diff --git a/src/interface/RaidGroupRequest.ts b/src/interface/RaidGroupRequest.ts new file mode 100644 index 0000000..952e30c --- /dev/null +++ b/src/interface/RaidGroupRequest.ts @@ -0,0 +1,8 @@ +export interface RaidGroupRequest { + raidGroupRequestId?: string; + raidGroupId: string; + accountId: string; + requestMessage: string; + + username: string; +} diff --git a/src/pages/protected/RaidGroupPage.tsx b/src/pages/protected/RaidGroupPage.tsx index 5ed229c..da9e5f3 100644 --- a/src/pages/protected/RaidGroupPage.tsx +++ b/src/pages/protected/RaidGroupPage.tsx @@ -6,6 +6,7 @@ import RaidGroupCalendarDisplay from "@/ui/calendar/RaidGroupCalendarDisplay"; import RaidGroupHeader from "@/ui/calendar/RaidGroupHeader"; import ClassGroupsTab from "@/ui/classGroup/ClassGroupsTab"; import PersonTab from "@/ui/person/PersonTab"; +import RaidGroupRequestTab from "@/ui/raidGroupRequest/RaidGroupRequestTab"; import RaidInstancesTab from "@/ui/raidInstances/RaidInstancesTab"; import RaidLayoutTab from "@/ui/raidLayout/RaidLayoutTab"; import { useEffect, useState } from "react"; @@ -65,13 +66,17 @@ export default function RaidGroupPage(){ { tabHeader: "Users", tabContent: + }, + { + tabHeader: "Requests", + tabContent: } ]; return (
{ - console.log("Fetching token"); + //console.log("Fetching token"); try{ const response = await api.get("/auth/refresh"); //If the token is retrieved diff --git a/src/ui/account/AccountsList.tsx b/src/ui/account/AccountsList.tsx index 182271b..81c8507 100644 --- a/src/ui/account/AccountsList.tsx +++ b/src/ui/account/AccountsList.tsx @@ -15,16 +15,13 @@ import RevokeRefreshTokenModal from "./modals/RevokeRefreshTokenModal"; import RaidGroupAccountAdminButtons from "./RaidGroupAccountAdminButtons"; -export interface AccountsListProps { +export default function AccountsList({ + accounts, + raidGroup +}:{ accounts: Account[]; raidGroup?: RaidGroup; -} - - -export default function AccountsList(props: AccountsListProps){ - const { accounts, raidGroup } = props; - - +}){ const [ selectedAccount, setSelectedAccount ] = useState(undefined); const [ displayForcePasswordResetModal, setDisplayForcePasswordResetModal ] = useState(false); const [ displayAccountPasswordSetModal, setDisplayAccountPasswordSetModal ] = useState(false); @@ -175,6 +172,7 @@ export default function AccountsList(props: AccountsListProps){ display={displayRemoveAccountFromRaidGroupModal} close={() => {setDisplayRemoveAccountFromRaidGroupModal(false); setSelectedAccount(undefined);}} account={selectedAccount} + raidGroup={raidGroup} /> ); diff --git a/src/ui/account/RaidGroupAccountsTab.tsx b/src/ui/account/RaidGroupAccountsTab.tsx index e5c66b1..e8bf6f6 100644 --- a/src/ui/account/RaidGroupAccountsTab.tsx +++ b/src/ui/account/RaidGroupAccountsTab.tsx @@ -51,7 +51,7 @@ export default function RaidGroupAccountsTab({  
 
@@ -72,7 +72,7 @@ export default function RaidGroupAccountsTab({ {/* Pagination */} diff --git a/src/ui/account/modals/RemoveAccountFromRaidGroupModal.tsx b/src/ui/account/modals/RemoveAccountFromRaidGroupModal.tsx index d652f58..7ab7d96 100644 --- a/src/ui/account/modals/RemoveAccountFromRaidGroupModal.tsx +++ b/src/ui/account/modals/RemoveAccountFromRaidGroupModal.tsx @@ -1,7 +1,7 @@ import DangerButton from "@/components/button/DangerButton"; import SecondaryButton from "@/components/button/SecondaryButton"; import RaidBuilderModal from "@/components/modal/RaidBuilderModal"; -import { UseRemoveAccountFromRaidGroup } from "@/hooks/AccountHooks"; +import { useRemoveAccountFromRaidGroup } from "@/hooks/AccountHooks"; import { Account } from "@/interface/Account"; import { RaidGroup } from "@/interface/RaidGroup"; import { useTimedModal } from "@/providers/TimedModalProvider"; @@ -19,7 +19,7 @@ export default function RemoveAccountFromRaidGroupModal({ account?: Account; raidGroup?: RaidGroup; }){ - const removeAccountFromRaidGroupQuery = UseRemoveAccountFromRaidGroup(raidGroup?.raidGroupId ?? "", account?.accountId ?? ""); + const removeAccountFromRaidGroupQuery = useRemoveAccountFromRaidGroup(raidGroup?.raidGroupId ?? "", account?.accountId ?? ""); const { addSuccessMessage, addErrorMessage } = useTimedModal(); diff --git a/src/ui/raidGroupRequest/RaidGroupRequestButtons.tsx b/src/ui/raidGroupRequest/RaidGroupRequestButtons.tsx new file mode 100644 index 0000000..a22ed30 --- /dev/null +++ b/src/ui/raidGroupRequest/RaidGroupRequestButtons.tsx @@ -0,0 +1,38 @@ +import { ButtonProps } from "@/components/button/Button"; +import DangerButton from "@/components/button/DangerButton"; +import PrimaryButton from "@/components/button/PrimaryButton"; +import { BsPencilFill, BsTrash3 } from "react-icons/bs"; + + +export default function RaidGroupRequestButtons({ + buttonProps, + showRaidGroupRequestModal, + showDeleteRaidGroupRequestModal +}:{ + buttonProps: ButtonProps; + showRaidGroupRequestModal: () => void; + showDeleteRaidGroupRequestModal: () => void; +}){ + return ( +
+ + + + + + +
+ ); +} diff --git a/src/ui/raidGroupRequest/RaidGroupRequestList.tsx b/src/ui/raidGroupRequest/RaidGroupRequestList.tsx new file mode 100644 index 0000000..b03c27c --- /dev/null +++ b/src/ui/raidGroupRequest/RaidGroupRequestList.tsx @@ -0,0 +1,81 @@ +import { ButtonProps } from "@/components/button/Button"; +import Table from "@/components/table/Table"; +import { RaidGroup } from "@/interface/RaidGroup"; +import { RaidGroupRequest } from "@/interface/RaidGroupRequest"; +import { useState } from "react"; +import DeleteRaidGroupRequestModal from "./modal/DeleteRaidGroupRequestModal"; +import RaidGroupRequestModal from "./modal/RaidGroupRequestModal"; +import RaidGroupRequestButtons from "./RaidGroupRequestButtons"; + + +export default function RaidGroupRequestList({ + raidGroupRequests, + raidGroup +}:{ + raidGroupRequests: RaidGroupRequest[]; + raidGroup: RaidGroup; +}){ + const [ selectedRequest, setSelectedRequest ] = useState(); + const [ displayRequestModal, setDisplayRequestModal ] = useState(false); + const [ displayDeleteRequestModal, setDisplayDeleteRequestModal ] = useState(false); + + + const buttonProps: ButtonProps = { + variant: "ghost", + size: "md", + shape: "square" + }; + + + const headElements: React.ReactNode[] = [ +
+ Username +
, +
+ Actions +
+ ]; + const bodyElements: React.ReactNode[][] = raidGroupRequests.map((request) => [ +
+ {request.username} +
, +
+
+   +
+ { + raidGroup && + { setSelectedRequest(request); setDisplayRequestModal(true); }} + showDeleteRaidGroupRequestModal={() => { setSelectedRequest(request); setDisplayDeleteRequestModal(true); }} + /> + } +
+ ]); + + + return ( + <> + + {setDisplayRequestModal(false); setSelectedRequest(undefined);}} + raidGroupRequest={selectedRequest} + raidGroupId={raidGroup.raidGroupId ?? ""} + /> + {setDisplayDeleteRequestModal(false); setSelectedRequest(undefined);}} + raidGroupRequest={selectedRequest} + /> + + ); +} diff --git a/src/ui/raidGroupRequest/RaidGroupRequestListSkeleton.tsx b/src/ui/raidGroupRequest/RaidGroupRequestListSkeleton.tsx new file mode 100644 index 0000000..36e58f3 --- /dev/null +++ b/src/ui/raidGroupRequest/RaidGroupRequestListSkeleton.tsx @@ -0,0 +1,10 @@ +export default function RaidGroupRequestListSkeleton(){ + //TODO: + + + return ( +
+ Raid Group Request List Skeleton +
+ ); +} diff --git a/src/ui/raidGroupRequest/RaidGroupRequestLoader.tsx b/src/ui/raidGroupRequest/RaidGroupRequestLoader.tsx new file mode 100644 index 0000000..0c48dfd --- /dev/null +++ b/src/ui/raidGroupRequest/RaidGroupRequestLoader.tsx @@ -0,0 +1,36 @@ +import DangerMessage from "@/components/message/DangerMessage"; +import { useGetRaidGroupRequests } from "@/hooks/RaidGroupRequestHooks"; +import { RaidGroup } from "@/interface/RaidGroup"; +import RaidGroupRequestList from "./RaidGroupRequestList"; +import RaidGroupRequestListSkeleton from "./RaidGroupRequestListSkeleton"; + + +export default function RaidGroupRequestLoader({ + page, + pageSize, + searchTerm, + raidGroup +}:{ + page: number; + pageSize: number; + searchTerm?: string; + raidGroup: RaidGroup; +}){ + const raidGroupRequestsQuery = useGetRaidGroupRequests(raidGroup.raidGroupId!, page - 1, pageSize, searchTerm); + + + if(raidGroupRequestsQuery.status === "pending"){ + return + } + else if(raidGroupRequestsQuery.status === "error"){ + return Error: {raidGroupRequestsQuery.error.message} + } + else{ + return ( + + ); + } +} diff --git a/src/ui/raidGroupRequest/RaidGroupRequestTab.tsx b/src/ui/raidGroupRequest/RaidGroupRequestTab.tsx new file mode 100644 index 0000000..28a3305 --- /dev/null +++ b/src/ui/raidGroupRequest/RaidGroupRequestTab.tsx @@ -0,0 +1,89 @@ +import TextInput from "@/components/input/TextInput"; +import Pagination from "@/components/pagination/Pagination"; +import { useGetRaidGroupRequestCount } from "@/hooks/RaidGroupRequestHooks"; +import { RaidGroup } from "@/interface/RaidGroup"; +import { useEffect, useState } from "react"; +import { useDebouncedCallback } from "use-debounce"; +import RaidGroupRequestLoader from "./RaidGroupRequestLoader"; + + +export default function RaidGroupRequestTab({ + raidGroup +}:{ + raidGroup: RaidGroup; +}){ + const [ page, setPage ] = useState(1); + const [ totalPages, setTotalPages ] = useState(1); + const [ searchTerm, setSearchTerm ] = useState(""); + const [ sentSearchTerm, setSentSearchTerm ] = useState(); + const pageSize = 10; + const modalId = crypto.randomUUID().replaceAll("-", ""); + + + const raidGroupRequestsCountQuery = useGetRaidGroupRequestCount(raidGroup.raidGroupId); + + const updateSearchTerm = useDebouncedCallback((newSearchTerm: string) => { + setSentSearchTerm(newSearchTerm.length ? newSearchTerm : undefined); + }, 1000); + + + useEffect(() => { + updateSearchTerm(searchTerm ?? ""); + }, [ searchTerm, updateSearchTerm ]); + + + useEffect(() => { + if(raidGroupRequestsCountQuery.status === "success"){ + setTotalPages(Math.ceil(raidGroupRequestsCountQuery.data / pageSize)); + } + }, [ raidGroupRequestsCountQuery ]); + + + return ( + <> +
+
+   +
+
+   +
+
+
+ setSearchTerm(e.target.value)} + placeholder="Search" + /> +
+
+
+ {/* Requests List */} + + {/* Pagination */} +
+ +
+ + ); +} diff --git a/src/ui/raidGroupRequest/modal/DeleteRaidGroupRequestModal.tsx b/src/ui/raidGroupRequest/modal/DeleteRaidGroupRequestModal.tsx new file mode 100644 index 0000000..522348f --- /dev/null +++ b/src/ui/raidGroupRequest/modal/DeleteRaidGroupRequestModal.tsx @@ -0,0 +1,63 @@ +import DangerButton from "@/components/button/DangerButton"; +import SecondaryButton from "@/components/button/SecondaryButton"; +import RaidBuilderModal from "@/components/modal/RaidBuilderModal"; +import { useDeleteRaidGroupRequest } from "@/hooks/RaidGroupRequestHooks"; +import { RaidGroupRequest } from "@/interface/RaidGroupRequest"; +import { useTimedModal } from "@/providers/TimedModalProvider"; +import { useEffect } from "react"; + + +export default function DeleteRaidGroupRequestModal({ + display, + close, + raidGroupRequest +}:{ + display: boolean; + close: () => void; + raidGroupRequest?: RaidGroupRequest; +}){ + const deleteRaidGroupRequestMutate = useDeleteRaidGroupRequest(raidGroupRequest?.raidGroupId ?? "", raidGroupRequest?.raidGroupRequestId ?? ""); + const { addSuccessMessage, addErrorMessage } = useTimedModal(); + + + const deleteRaidGroupRequest = () => { + deleteRaidGroupRequestMutate.mutate(); + } + + useEffect(() => { + if(deleteRaidGroupRequestMutate.status === "success"){ + deleteRaidGroupRequestMutate.reset(); + addSuccessMessage("Raid Group Request deleted successfully"); + close(); + } + else if(deleteRaidGroupRequestMutate.status === "error"){ + deleteRaidGroupRequestMutate.reset(); + addErrorMessage(`Error deleting Raid Group Request: ${deleteRaidGroupRequestMutate.error.message}`); + console.log(deleteRaidGroupRequestMutate.error); + } + }); + + + return( + + + Delete + + + Cancel + + + } + /> + ); +} diff --git a/src/ui/raidGroupRequest/modal/RaidGroupRequestModal.tsx b/src/ui/raidGroupRequest/modal/RaidGroupRequestModal.tsx new file mode 100644 index 0000000..bd74fbe --- /dev/null +++ b/src/ui/raidGroupRequest/modal/RaidGroupRequestModal.tsx @@ -0,0 +1,127 @@ +import PrimaryButton from "@/components/button/PrimaryButton"; +import SecondaryButton from "@/components/button/SecondaryButton"; +import TextInput from "@/components/input/TextInput"; +import RaidBuilderModal from "@/components/modal/RaidBuilderModal"; +import RaidGroupPermissionSelector from "@/components/raidGroup/RaidGroupPermissionSelector"; +import { useCreateRaidGroupRequest, useResolveRaidGroupRequest } from "@/hooks/RaidGroupRequestHooks"; +import { Account } from "@/interface/Account"; +import { RaidGroupPermissionType } from "@/interface/RaidGroup"; +import { RaidGroupRequest } from "@/interface/RaidGroupRequest"; +import { useTimedModal } from "@/providers/TimedModalProvider"; +import { useEffect, useState } from "react"; + + +export default function RaidGroupRequestModal({ + display, + close, + raidGroupId, + raidGroupRequest, + account +}:{ + display: boolean; + close: () => void; + raidGroupId: string; + raidGroupRequest?: RaidGroupRequest; + account?: Account; +}){ + const [ currentPermission, setCurrentPermission ] = useState(RaidGroupPermissionType.RAIDER); + const [ requestMessage, setRequestMessage ] = useState(raidGroupRequest?.requestMessage ?? ""); + const modalId = crypto.randomUUID().replaceAll("-", ""); + + + useEffect(() => { + setCurrentPermission(RaidGroupPermissionType.RAIDER); + setRequestMessage(raidGroupRequest?.requestMessage ?? ""); + }, [ display, raidGroupRequest ]); + + + const createRaidGroupRequestMutate = useCreateRaidGroupRequest(raidGroupId); + const resolveRaidGroupRequestMutate = useResolveRaidGroupRequest(raidGroupId, raidGroupRequest?.raidGroupRequestId ?? ""); + const { addSuccessMessage, addErrorMessage } = useTimedModal(); + + + useEffect(() => { + if(createRaidGroupRequestMutate.status === "success"){ + createRaidGroupRequestMutate.reset(); + addSuccessMessage("Raid Group Request Created"); + close(); + } + else if(createRaidGroupRequestMutate.status === "error"){ + createRaidGroupRequestMutate.reset(); + addErrorMessage(`Error creating raid group request: ${createRaidGroupRequestMutate.error.message}`); + console.log(createRaidGroupRequestMutate.error); + } + else if(resolveRaidGroupRequestMutate.status === "success"){ + resolveRaidGroupRequestMutate.reset(); + addSuccessMessage("Raid Group Request Resolved"); + close(); + } + else if(resolveRaidGroupRequestMutate.status === "error"){ + resolveRaidGroupRequestMutate.reset(); + addErrorMessage(`Error resolving raid group request: ${resolveRaidGroupRequestMutate.error.message}`); + console.log(resolveRaidGroupRequestMutate.error); + } + }); + + + const createRaidGroupRequest = () => { + createRaidGroupRequestMutate.mutate(requestMessage); + } + + const resolveRaidGroupRequest = () => { + resolveRaidGroupRequestMutate.mutate(currentPermission); + } + + + return ( + +
+ { + raidGroupRequest && + raidGroupRequest?.username + } + { + account && + account?.username + } +
+ setRequestMessage(e.target.value)} + placeholder="Message" + disabled={!!raidGroupRequest} + /> + { + raidGroupRequest && + setCurrentPermission(e.target.value as RaidGroupPermissionType)} + /> + } + + } + modalFooter={ + <> + + {raidGroupRequest ? "Resolve" : "Request"} + + + Cancel + + + } + /> + ); +} diff --git a/src/ui/raidLayout/modal/RaidLayoutModal.tsx b/src/ui/raidLayout/modal/RaidLayoutModal.tsx index 9236493..d5b74a6 100644 --- a/src/ui/raidLayout/modal/RaidLayoutModal.tsx +++ b/src/ui/raidLayout/modal/RaidLayoutModal.tsx @@ -38,22 +38,17 @@ export default function RaidLayoutModal({ }, [ raidLayout, selectedClassGroups ]); const updateRaidLayoutSize = (newLayoutSize: number) => { - console.log("newLayoutSize = " + newLayoutSize + ", " + classGroups.length); setRaidLayoutSize(newLayoutSize); if(newLayoutSize < classGroups.length){ - console.log("Removing"); setClassGroups(classGroups.slice(0, newLayoutSize)); } else if(newLayoutSize > classGroups.length){ - console.log("Adding"); setClassGroups([ ...classGroups, null ]); } } - console.log("classGroups"); - console.log(classGroups); const createRaidLayoutMutate = useCreateRaidLayout(raidGroup.raidGroupId ?? "");