diff --git a/src/hooks/RaidGroupHooks.ts b/src/hooks/RaidGroupHooks.ts index 39a6e3a..80ebd70 100644 --- a/src/hooks/RaidGroupHooks.ts +++ b/src/hooks/RaidGroupHooks.ts @@ -102,6 +102,56 @@ export function useGetRaidGroupsByGameCount(gameId: string, searchTerm?: string) }); } +export function useGetRaidGroupsByAccount(accountId: string, page: number, pageSize: number, searchTerm?: string){ + return useQuery({ + queryKey: ["raidGroups", accountId, { page, pageSize, searchTerm }], + queryFn: async () => { + const params = new URLSearchParams(); + params.append("page", page.toString()); + params.append("pageSize", pageSize.toString()); + if(searchTerm){ + params.append("searchTerm", searchTerm); + } + + const response = await api.get(`/raidGroup/account/${accountId}?${params}`); + + if(response.status !== 200){ + throw new Error("Failed to get raid groups"); + } + else if(response.data.errors){ + throw new Error(response.data.errors.join(", ")); + } + + return response.data as RaidGroup[]; + } + }); +} + +export function useGetRaidGroupsCountByAccount(accountId: string, searchTerm?: string){ + const searchParams = new URLSearchParams(); + if(searchTerm){ + searchParams.append("searchTerm", searchTerm); + } + + + return useQuery({ + queryKey: ["raidGroups", accountId, "count", searchTerm], + queryFn: async () => { + const response = await api.get(`/raidGroup/account/${accountId}/count?${searchParams}`); + + if(response.status !== 200){ + throw new Error("Failed to get raid groups count"); + } + else if(response.data.errors){ + throw new Error(response.data.errors.join(", ")); + } + + return response.data.count as number; + } + }); + +} + export function useGetRaidGroupCalendar(raidGroupId: string){ return useQuery({ queryKey: ["raidGroups", raidGroupId, "calendar"], diff --git a/src/pages/protected/GamesPage.tsx b/src/pages/protected/GamesPage.tsx index 764bfbf..67d85f5 100644 --- a/src/pages/protected/GamesPage.tsx +++ b/src/pages/protected/GamesPage.tsx @@ -3,8 +3,19 @@ import AllGamesDisplay from "@/ui/game/AllGamesDisplay"; export default function GamesPage(){ return ( -
- +
+

+ Games +

+
+ +
); } diff --git a/src/pages/protected/RaidGroupsPage.tsx b/src/pages/protected/RaidGroupsPage.tsx index b49169b..00cb9c1 100644 --- a/src/pages/protected/RaidGroupsPage.tsx +++ b/src/pages/protected/RaidGroupsPage.tsx @@ -1,10 +1,27 @@ +import { useAuth } from "@/providers/AuthProvider"; +import RaidGroupsByAccountDisplay from "@/ui/raidGroup/RaidGroupsByAccountDisplay"; + + export default function RaidGroupsPage(){ - //TODO: + const { accountId } = useAuth(); return ( -
- Raid Groups Page -
+
+

+ Raid Groups +

+
+ +
+
); } diff --git a/src/providers/AuthProvider.tsx b/src/providers/AuthProvider.tsx index 411d83a..fd2aa6d 100644 --- a/src/providers/AuthProvider.tsx +++ b/src/providers/AuthProvider.tsx @@ -14,13 +14,15 @@ type AuthProviderState = { setJwt: (token: string | null) => void; expiration: Date | null; setExpiration: (expiration: Date | null) => void; + accountId: string | null; } const initialState: AuthProviderState = { jwt: null, setJwt: () => null, expiration: null, - setExpiration: () => null + setExpiration: () => null, + accountId: null } const AuthContext = createContext(initialState); @@ -32,6 +34,7 @@ export function AuthProvider({ const [ jwt, setJwt ] = useState(null); const [ expiration, setExpiration ] = useState(null); const [ firstFetch, setFirstFetch ] = useState(true); + const [ accountId, setAccountId ] = useState(null); const fetchToken = useCallback(async () => { @@ -41,8 +44,12 @@ export function AuthProvider({ //If the token is retrieved if((response.status === 200) && (!response.data.errors)){ setJwt(response.data.token); - setExpiration(new Date(JSON.parse(atob(response.data.token.split(".")[1])).exp * 1000)); + const decodedToken = JSON.parse(atob(response.data.token.split(".")[1])); + //console.log("decodedToken = "); + //console.log(decodedToken); + setExpiration(new Date(decodedToken.exp * 1000)); setFirstFetch(false); + setAccountId(decodedToken.accountId); return response.data.token; } //If the token cannot be retrieved @@ -90,8 +97,10 @@ export function AuthProvider({ jwt, setJwt, expiration, - setExpiration - }), [ jwt, setJwt, expiration, setExpiration ]); + setExpiration, + accountId + }), [ jwt, setJwt, expiration, setExpiration, accountId ]); + //TODO: Return a spinner while the first token is being fetched if(firstFetch){ diff --git a/src/ui/raidGroup/RaidGroupsByAccountDisplay.tsx b/src/ui/raidGroup/RaidGroupsByAccountDisplay.tsx new file mode 100644 index 0000000..ace936b --- /dev/null +++ b/src/ui/raidGroup/RaidGroupsByAccountDisplay.tsx @@ -0,0 +1,103 @@ +import PrimaryButton from "@/components/button/PrimaryButton"; +import TextInput from "@/components/input/TextInput"; +import Pagination from "@/components/pagination/Pagination"; +import { useGetRaidGroupsCountByAccount } from "@/hooks/RaidGroupHooks"; +import { useEffect, useState } from "react"; +import { useDebouncedCallback } from "use-debounce"; +import RaidGroupsByAccountLoader from "./RaidGroupsByAccountLoader"; +import RaidGroupModal from "./modals/RaidGroupModal"; + + +export default function RaidGroupsByAccountDisplay({ + accountId +}:{ + accountId: string; +}){ + const [ displayCreateAccountModal, setDisplayCreateAccountModal ] = useState(false); + 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 raidGroupsCountQuery = useGetRaidGroupsCountByAccount(accountId, sentSearchTerm); + + + const updateSearchTerm = useDebouncedCallback((newSearchTerm: string) => { + setSentSearchTerm(newSearchTerm.length ? newSearchTerm : undefined); + }, 1000); + + + useEffect(() => { + updateSearchTerm(searchTerm ?? ""); + }, [ searchTerm, updateSearchTerm ]); + + + useEffect(() => { + if(raidGroupsCountQuery.status === "success"){ + setTotalPages(Math.ceil(raidGroupsCountQuery.data / pageSize)); + } + }, [ raidGroupsCountQuery ]); + + + return ( + <> +
+
+   +
+ {/* Add Raid Group Button */} +
+ setDisplayCreateAccountModal(true)} + > + Create Raid Group + + setDisplayCreateAccountModal(false)} + raidGroup={undefined} + /> +
+ {/* Raid Group Search Box */} +
+
+ setSearchTerm(e.target.value)} + placeholder="Search" + /> +
+
+
+ {/* Raid Groups List */} + + {/* Pagination */} +
+ +
+ + ); +} diff --git a/src/ui/raidGroup/RaidGroupsByAccountLoader.tsx b/src/ui/raidGroup/RaidGroupsByAccountLoader.tsx new file mode 100644 index 0000000..c982e3d --- /dev/null +++ b/src/ui/raidGroup/RaidGroupsByAccountLoader.tsx @@ -0,0 +1,33 @@ +import DangerMessage from "@/components/message/DangerMessage"; +import { useGetRaidGroupsByAccount } from "@/hooks/RaidGroupHooks"; +import RaidGroupsList from "./RaidGroupsList"; +import RaidGroupsListSkeleton from "./RaidGroupsListSkeleton"; + +export default function RaidGroupsByAccountLoader({ + accountId, + page, + pageSize, + searchTerm +}:{ + accountId: string; + page: number; + pageSize: number; + searchTerm?: string; +}){ + const raidGroupsQuery = useGetRaidGroupsByAccount(accountId, page - 1, pageSize, searchTerm); + + + if(raidGroupsQuery.status === "pending"){ + return + } + else if(raidGroupsQuery.status === "error"){ + return Error {raidGroupsQuery.error.message} + } + else{ + return ( + + ); + } +} diff --git a/src/ui/raidGroup/RaidGroupsList.tsx b/src/ui/raidGroup/RaidGroupsList.tsx index 112e25b..968a50e 100644 --- a/src/ui/raidGroup/RaidGroupsList.tsx +++ b/src/ui/raidGroup/RaidGroupsList.tsx @@ -2,6 +2,7 @@ import { ButtonProps } from "@/components/button/Button"; import Table from "@/components/table/Table"; import { RaidGroup } from "@/interface/RaidGroup"; import { useState } from "react"; +import { Link } from "react-router"; import DeleteRaidGroupModal from "./modals/DeleteRaidGroupModal"; import RaidGroupModal from "./modals/RaidGroupModal"; import RaidGroupAdminButtons from "./RaidGroupAdminButtons"; @@ -55,9 +56,11 @@ export default function RaidGroupsList({ }   , -
+ {raidGroup.raidGroupName} -
, + ,
diff --git a/src/ui/raidGroup/RaidGroupsListSkeleton.tsx b/src/ui/raidGroup/RaidGroupsListSkeleton.tsx index 88e46c3..d13df02 100644 --- a/src/ui/raidGroup/RaidGroupsListSkeleton.tsx +++ b/src/ui/raidGroup/RaidGroupsListSkeleton.tsx @@ -1,7 +1,71 @@ +import { ButtonShape, ButtonSizeType, ButtonVariant } from "@/components/button/Button"; +import Table from "@/components/table/Table"; +import { elementBg } from "@/util/SkeletonUtil"; +import RaidGroupAdminButtons from "./RaidGroupAdminButtons"; + + export default function RaidGroupsListSkeleton(){ - return ( -
- Raid Group List Skeleton -
- ); + const headElements: React.ReactNode[] = [ +
+ Icon +
, +
+ Name +
, +
+ Actions +
+ ]; + const bodyElements: React.ReactNode[][] = [ + RaidGroupSkeleton(), + RaidGroupSkeleton(), + RaidGroupSkeleton(), + RaidGroupSkeleton(), + RaidGroupSkeleton(), + RaidGroupSkeleton(), + RaidGroupSkeleton(), + RaidGroupSkeleton(), + RaidGroupSkeleton(), + RaidGroupSkeleton(), + ]; + + + return ( + + ); +} + + +function RaidGroupSkeleton(): React.ReactNode[]{ + const buttonsProps = { + buttonProps: { + variant: "ghost" as ButtonVariant, + size: "md" as ButtonSizeType, + shape: "square" as ButtonShape, + disabled: true + }, + showEditRaidGroupModal: () => {}, + showDeleteRaidGroupModal: () => {} + } + const elements: React.ReactNode[] = [ +
, +
, +
+
 
+ +
+ ]; + + return elements; }