Skip to content

Commit

Permalink
Merge pull request #1764 from gettakaro/feature/gameserver-table
Browse files Browse the repository at this point in the history
Feat: gameservers table view
  • Loading branch information
emielvanseveren authored Nov 8, 2024
2 parents a753df2 + e55ac88 commit 7834422
Show file tree
Hide file tree
Showing 13 changed files with 460 additions and 352 deletions.
136 changes: 21 additions & 115 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions packages/web-main/src/components/Navbar/GameServerNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,10 @@ export const GameServerNav: FC = () => {

return (
<Nav data-testid="server-nav">
{gameServerId && gameServerId !== '' && gameservers && gameservers.length > 0 ? (
{gameServerId && gameServerId !== '' && gameservers && gameservers.data.length > 0 ? (
<>
<h3>Game Server</h3>
{gameservers.length > 1 && <GlobalGameServerSelect currentSelectedGameServerId={gameServerId} />}
{gameservers.data.length > 1 && <GlobalGameServerSelect currentSelectedGameServerId={gameServerId} />}
{gameServerLinks.map((link) => renderLink(link))}
</>
) : (
Expand Down
24 changes: 15 additions & 9 deletions packages/web-main/src/components/Player.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Avatar, Chip, getInitials } from '@takaro/lib-components';
import { Avatar, Chip, getInitials, Tooltip } from '@takaro/lib-components';
import { playerQueryOptions } from 'queries/player';
import { FC } from 'react';
import { Link } from '@tanstack/react-router';
Expand Down Expand Up @@ -46,13 +46,19 @@ export const Player: FC<PlayerProps> = ({ playerId, name, avatarUrl, showAvatar,
);

return (
<Link
to={'/player/$playerId'}
params={{ playerId }}
style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', width: 'fit-content' }}
>
{showAvatar && avatar}
<span>{name}</span>
</Link>
<Tooltip>
<Tooltip.Trigger>
<Link
className="underline"
to={'/player/$playerId'}
params={{ playerId }}
style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', width: 'fit-content' }}
>
{showAvatar && avatar}
<span>{name}</span>
</Link>
</Tooltip.Trigger>
<Tooltip.Content>Open player profile</Tooltip.Content>
</Tooltip>
);
};
136 changes: 5 additions & 131 deletions packages/web-main/src/components/cards/GameServerCard/index.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,14 @@
import { FC, MouseEvent, useEffect, useState } from 'react';
import {
Button,
Chip,
Dialog,
Dropdown,
IconButton,
Tooltip,
Card,
Skeleton,
useTheme,
ValueConfirmationField,
} from '@takaro/lib-components';
import { EventOutputDTO, GameServerOutputDTO, PERMISSIONS } from '@takaro/apiclient';
import { FC, useEffect } from 'react';
import { Chip, Tooltip, Card, Skeleton } from '@takaro/lib-components';
import { EventOutputDTO, GameServerOutputDTO } from '@takaro/apiclient';
import { useNavigate } from '@tanstack/react-router';
import {
AiOutlineMenu as MenuIcon,
AiOutlineDelete as DeleteIcon,
AiOutlineEdit as EditIcon,
AiOutlineLineChart as DashboardIcon,
AiOutlineCopy as CopyIcon,
AiOutlineFunction as ModulesIcon,
AiOutlineSetting as SettingsIcon,
} from 'react-icons/ai';

import { Header, TitleContainer, DetailsContainer } from './style';
import { useGameServerRemove } from 'queries/gameserver';
import { PermissionsGuard } from 'components/PermissionsGuard';
import { InnerBody } from '../style';
import { useSocket } from 'hooks/useSocket';
import { playersOnGameServersQueryOptions } from 'queries/pog';
import { useQuery } from '@tanstack/react-query';
import { GameServerActions } from 'routes/_auth/_global/gameservers';

const StatusChip: FC<{ reachable: boolean; enabled: boolean }> = ({ reachable, enabled }) => {
if (!enabled) return <Chip label="disabled" color="warning" variant="outline" />;
Expand All @@ -38,11 +17,7 @@ const StatusChip: FC<{ reachable: boolean; enabled: boolean }> = ({ reachable, e
};

export const GameServerCard: FC<GameServerOutputDTO> = ({ id, name, type, reachable, enabled }) => {
const [openDeleteDialog, setOpenDeleteDialog] = useState<boolean>(false);
const [valid, setValid] = useState<boolean>(false);
const navigate = useNavigate();
const theme = useTheme();
const { mutate, isPending: isDeleting } = useGameServerRemove();
const { socket } = useSocket();
const {
data: onlinePogs,
Expand All @@ -68,28 +43,6 @@ export const GameServerCard: FC<GameServerOutputDTO> = ({ id, name, type, reacha
};
}, []);

const handleOnEditClick = (e: MouseEvent): void => {
e.stopPropagation();
navigate({ to: '/gameservers/update/$gameServerId', params: { gameServerId: id } });
};
const handleOnDeleteClick = (e: MouseEvent) => {
e.stopPropagation();
if (e.shiftKey) {
handleOnDelete();
} else {
setOpenDeleteDialog(true);
}
};

const handleOnDelete = () => {
mutate({ gameServerId: id });
};

const handleOnCopyClick = (e: MouseEvent) => {
e.stopPropagation();
navigator.clipboard.writeText(id);
};

return (
<>
<Card
Expand All @@ -106,61 +59,7 @@ export const GameServerCard: FC<GameServerOutputDTO> = ({ id, name, type, reacha
<InnerBody>
<Header>
<StatusChip reachable={reachable} enabled={enabled} />
<PermissionsGuard requiredPermissions={[[PERMISSIONS.ManageGameservers]]}>
<Dropdown>
<Dropdown.Trigger asChild>
<IconButton icon={<MenuIcon />} ariaLabel="Settings" />
</Dropdown.Trigger>
<Dropdown.Menu>
<Dropdown.Menu.Group label="Actions">
<Dropdown.Menu.Item icon={<CopyIcon />} onClick={handleOnCopyClick} label="Copy gameserverID" />
<Dropdown.Menu.Item icon={<EditIcon />} onClick={handleOnEditClick} label="Edit gameserver" />
<Dropdown.Menu.Item
icon={<DeleteIcon fill={theme.colors.error} />}
onClick={handleOnDeleteClick}
label="Delete gameserver"
/>
</Dropdown.Menu.Group>
<Dropdown.Menu.Group label="Navigation">
<Dropdown.Menu.Item
icon={<DashboardIcon />}
onClick={(e) => {
e.stopPropagation();
navigate({
to: '/gameserver/$gameServerId/dashboard/overview',
params: { gameServerId: id },
});
}}
label="go to dashboard"
/>
<Dropdown.Menu.Item
icon={<ModulesIcon />}
onClick={(e) => {
e.stopPropagation();
navigate({ to: '/gameserver/$gameServerId/modules', params: { gameServerId: id } });
}}
label="go to modules"
/>
<Dropdown.Menu.Item
icon={<ModulesIcon />}
onClick={(e) => {
e.stopPropagation();
navigate({ to: '/gameserver/$gameServerId/shop', params: { gameServerId: id } });
}}
label="go to shop"
/>
<Dropdown.Menu.Item
icon={<SettingsIcon />}
onClick={(e) => {
e.stopPropagation();
navigate({ to: '/gameserver/$gameServerId/settings', params: { gameServerId: id } });
}}
label="go to settings"
/>
</Dropdown.Menu.Group>
</Dropdown.Menu>
</Dropdown>
</PermissionsGuard>
<GameServerActions gameServerName={name} gameServerId={id} />
</Header>
<DetailsContainer>
<TitleContainer>
Expand All @@ -183,31 +82,6 @@ export const GameServerCard: FC<GameServerOutputDTO> = ({ id, name, type, reacha
</InnerBody>
</Card.Body>
</Card>
<Dialog open={openDeleteDialog} onOpenChange={setOpenDeleteDialog}>
<Dialog.Content>
<Dialog.Heading>delete: gameserver</Dialog.Heading>
<Dialog.Body size="medium">
<p>
Are you sure you want to delete the gameserver? To confirm, type <strong>{name}</strong> in the field
below.
</p>
<ValueConfirmationField
value={name}
onValidChange={(v) => setValid(v)}
label="Game server name"
id="deleteGameServerConfirmation"
/>
<Button
isLoading={isDeleting}
onClick={() => handleOnDelete()}
disabled={!valid}
fullWidth
text="Delete gameserver"
color="error"
/>
</Dialog.Body>
</Dialog.Content>
</Dialog>
</>
);
};
6 changes: 3 additions & 3 deletions packages/web-main/src/queries/gameserver.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ const defaultGameServerErrorMessages: Partial<ErrorMessageMapping> = {
};

export const gameServersQueryOptions = (queryParams: GameServerSearchInputDTO = {}) => {
return queryOptions<GameServerOutputDTO[], AxiosError<GameServerOutputArrayDTOAPI>>({
queryKey: [...gameServerKeys.list(), ...queryParamsToArray(queryParams)],
queryFn: async () => (await getApiClient().gameserver.gameServerControllerSearch(queryParams)).data.data,
return queryOptions<GameServerOutputArrayDTOAPI, AxiosError<GameServerOutputArrayDTOAPI>>({
queryKey: [...gameServerKeys.list(), 'table', ...queryParamsToArray(queryParams)],
queryFn: async () => (await getApiClient().gameserver.gameServerControllerSearch(queryParams)).data,
});
};

Expand Down
4 changes: 2 additions & 2 deletions packages/web-main/src/routes/_auth/_global.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ export const Route = createFileRoute('/_auth/_global')({
const canManageGameServers = hasPermission(session, ['MANAGE_GAMESERVERS']);
if (location.pathname == '/' && !canManageGameServers) {
const gameservers = await context.queryClient.ensureQueryData(gameServersQueryOptions());
if (gameservers.length > 0) {
throw redirect({ to: '/gameserver/$gameServerId/shop', params: { gameServerId: gameservers[0].id } });
if (gameservers.data.length > 0) {
throw redirect({ to: '/gameserver/$gameServerId/shop', params: { gameServerId: gameservers.data[0].id } });
}
}
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { useNavigate, Outlet } from '@tanstack/react-router';
import { gameServersInfiniteQueryOptions } from 'queries/gameserver';
import { PermissionsGuard } from 'components/PermissionsGuard';
import { AddCard, CardList, GameServerCard } from 'components/cards';
import { useInfiniteQuery } from '@tanstack/react-query';
import { Button, Empty, EmptyPage, InfiniteScroll, Skeleton } from '@takaro/lib-components';
import { Fragment } from 'react';
import { PERMISSIONS } from '@takaro/apiclient';

export const GameServersCardView = () => {
const navigate = useNavigate();

const {
data: gameServers,
isFetching,
hasNextPage,
fetchNextPage,
isLoading,
isFetchingNextPage,
} = useInfiniteQuery({
...gameServersInfiniteQueryOptions(),
});

if (isLoading) {
return (
<CardList>
<Skeleton variant="rectangular" height="100%" width="100%" />
<Skeleton variant="rectangular" height="100%" width="100%" />
<Skeleton variant="rectangular" height="100%" width="100%" />
<Skeleton variant="rectangular" height="100%" width="100%" />
</CardList>
);
}

if (!gameServers || gameServers.pages.length === 0) {
return (
<EmptyPage>
<Empty
header="Bro, what are you waiting on?"
description="Create a game server to really get started with Takaro."
actions={[
<Button
key="gameservers-create"
text="Create a game server"
onClick={() => navigate({ to: '/gameservers/create' })}
/>,
]}
/>
<Outlet />
</EmptyPage>
);
}

return (
<Fragment>
<CardList>
{gameServers.pages
.flatMap((page) => page.data)
.map((gameServer) => (
<GameServerCard key={gameServer.id} {...gameServer} />
))}
<PermissionsGuard requiredPermissions={[PERMISSIONS.ManageGameservers]}>
<AddCard title="Gameserver" onClick={() => navigate({ to: '/gameservers/create' })} />
</PermissionsGuard>
<PermissionsGuard requiredPermissions={[PERMISSIONS.ManageGameservers]}>
<AddCard title="Import from CSMM" onClick={() => navigate({ to: '/gameservers/create/import' })} />
</PermissionsGuard>
</CardList>
<InfiniteScroll
isFetching={isFetching}
hasNextPage={hasNextPage}
fetchNextPage={fetchNextPage}
isFetchingNextPage={isFetchingNextPage}
/>
</Fragment>
);
};
Loading

0 comments on commit 7834422

Please sign in to comment.