From 025c021896ce52a0c577ea41918d7b2f65d89283 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=20Jurmanovi=C4=87?= Date: Sat, 21 Mar 2026 21:12:50 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20restyle=20dashboard=20=E2=80=94=20card?= =?UTF-8?q?=20grid=20(desktop),=20row=20list=20(mobile),=20status=20badges?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/dashboard/page.tsx | 43 +----- src/components/server/ServerCard.tsx | 140 +++++++++--------- .../server/ServerListWithActions.tsx | 60 ++++++-- src/components/server/ServerRow.tsx | 86 +++++++++++ src/components/ui/SteamCMDNotification.tsx | 10 +- 5 files changed, 210 insertions(+), 129 deletions(-) create mode 100644 src/components/server/ServerRow.tsx diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx index 9ccfd1c..18acf6d 100755 --- a/src/app/dashboard/page.tsx +++ b/src/app/dashboard/page.tsx @@ -1,53 +1,16 @@ import { requireAuth } from '@/lib/auth/server'; import { getServers } from '@/lib/api/server/servers'; -import { hasPermission } from '@/lib/schemas'; -import Link from 'next/link'; import { ServerListWithActions } from '@/components/server/ServerListWithActions'; import { SteamCMDNotification } from '@/components/ui/SteamCMDNotification'; -import LogoutButton from '@/components/ui/LogoutButton'; export default async function DashboardPage() { const session = await requireAuth(); const servers = await getServers(session.token!); return ( -
-
-
-

ACC Server Manager

-
- {hasPermission(session.user!, 'membership.view') && ( - - - - - Users - - )} - -
-
-
- +
- -
- -
-
+ + ); } diff --git a/src/components/server/ServerCard.tsx b/src/components/server/ServerCard.tsx index 8a68653..a49bcdb 100755 --- a/src/components/server/ServerCard.tsx +++ b/src/components/server/ServerCard.tsx @@ -1,17 +1,31 @@ +'use client'; + import Link from 'next/link'; import { useTransition } from 'react'; -import { Server, ServiceStatus, getStatusColor, serviceStatusToString } from '@/lib/schemas'; +import { Server, ServiceStatus, serviceStatusToString } from '@/lib/schemas'; import { startServerEventAction, restartServerEventAction, stopServerEventAction } from '@/lib/actions/servers'; import { useRouter } from 'next/navigation'; +import { StatusDot } from '@/components/ui/StatusDot'; +import { Badge } from '@/components/ui/Badge'; +import { GhButton } from '@/components/ui/GhButton'; interface ServerCardProps { server: Server; } +function statusBadgeVariant(status: ServiceStatus): 'green' | 'yellow' | 'gray' { + if (status === ServiceStatus.Running) return 'green'; + if ( + [ServiceStatus.Starting, ServiceStatus.Restarting, ServiceStatus.Stopping].includes(status) + ) + return 'yellow'; + return 'gray'; +} + export function ServerCard({ server }: ServerCardProps) { const [isPending, startTransition] = useTransition(); const router = useRouter(); @@ -36,82 +50,68 @@ export function ServerCard({ server }: ServerCardProps) { ServiceStatus.Restarting, ServiceStatus.Starting, ServiceStatus.Stopping, - ServiceStatus.Unknown + ServiceStatus.Unknown, ].includes(server.status); return ( - <> -
- -
-
-
-

{server.name}

-
- - - {serviceStatusToString(server.status)} - -
-
-
-
- - - -
-
-
+
+ +
+ {server.name} + +
+
+ {server.state?.track || 'No track'} · {server.state?.playerCount ?? 0} players +
-
-
- Track: - {server.state?.track || 'N/A'} -
-
- Players: - {server.state?.playerCount || 0} -
+
+
+
Players
+
+ {server.state?.playerCount ?? 0} +
+
+
+
Track
+
+ {server.state?.track || 'N/A'}
- - -
- - - - -
+ +
+ + {serviceStatusToString(server.status)} + +
+ + +
+ + Start + + + Restart + + + Stop +
- +
); } diff --git a/src/components/server/ServerListWithActions.tsx b/src/components/server/ServerListWithActions.tsx index 08a2e75..9afecd6 100755 --- a/src/components/server/ServerListWithActions.tsx +++ b/src/components/server/ServerListWithActions.tsx @@ -1,12 +1,15 @@ 'use client'; import { useCallback, useState } from 'react'; -import { Server } from '@/lib/schemas/server'; +import { Server, ServiceStatus } from '@/lib/schemas/server'; import { User, hasPermission } from '@/lib/schemas/user'; import { ServerCard } from './ServerCard'; +import { ServerRow } from './ServerRow'; import { CreateServerModal } from './CreateServerModal'; import RefreshButton from '@/components/ui/RefreshButton'; import { useSteamCMD } from '@/lib/context/SteamCMDContext'; +import { GhButton } from '@/components/ui/GhButton'; +import { Badge } from '@/components/ui/Badge'; interface ServerListWithActionsProps { servers: Server[]; @@ -20,29 +23,58 @@ export function ServerListWithActions({ servers, user }: ServerListWithActionsPr const handleOnClose = useCallback(() => setIsCreateModalOpen(false), []); const canCreateServer = hasPermission(user, 'server.create'); + const runningCount = servers.filter((s) => s.status === ServiceStatus.Running).length; + const offlineCount = servers.filter((s) => s.status === ServiceStatus.Stopped).length; + return ( <> -
-

Your Servers

-
+ {/* Topbar */} +
+
+ Servers + {servers.length} total +
+
+ {canCreateServer && ( - + + New Server + )} - +
+
+ + {/* Status summary bar */} +
+
+ {runningCount > 0 && ● {runningCount} running} + {offlineCount > 0 && {offlineCount} offline}
-
- {servers.map((server) => ( - - ))} + {/* Responsive server list */} +
+ {/* Card grid — desktop only */} +
+ {servers.map((server) => ( + + ))} +
+ {/* Row list — mobile only */} +
+ {servers.map((server) => ( + + ))} +
diff --git a/src/components/server/ServerRow.tsx b/src/components/server/ServerRow.tsx new file mode 100644 index 0000000..800fe18 --- /dev/null +++ b/src/components/server/ServerRow.tsx @@ -0,0 +1,86 @@ +'use client'; + +import { useTransition } from 'react'; +import { useRouter } from 'next/navigation'; +import Link from 'next/link'; +import { Server, ServiceStatus } from '@/lib/schemas'; +import { + startServerEventAction, + restartServerEventAction, + stopServerEventAction +} from '@/lib/actions/servers'; +import { StatusDot } from '@/components/ui/StatusDot'; +import { GhButton } from '@/components/ui/GhButton'; + +interface ServerRowProps { + server: Server; +} + +export function ServerRow({ server }: ServerRowProps) { + const [isPending, startTransition] = useTransition(); + const router = useRouter(); + + const startServer = () => + startTransition(async () => { + await startServerEventAction(server.id); + router.refresh(); + }); + const restartServer = () => + startTransition(async () => { + await restartServerEventAction(server.id); + router.refresh(); + }); + const stopServer = () => + startTransition(async () => { + await stopServerEventAction(server.id); + router.refresh(); + }); + + const transitioning = [ + ServiceStatus.Restarting, + ServiceStatus.Starting, + ServiceStatus.Stopping, + ServiceStatus.Unknown, + ].includes(server.status); + const disabled = transitioning || isPending; + + return ( +
+ + +
+
{server.name}
+
+ {server.state?.track ?? 'No track'} · {server.state?.playerCount ?? 0} players +
+
+ +
+ {server.status === ServiceStatus.Stopped ? ( + + Start + + ) : ( + <> + + Restart + + + Stop + + + )} +
+
+ ); +} diff --git a/src/components/ui/SteamCMDNotification.tsx b/src/components/ui/SteamCMDNotification.tsx index 1dfddc4..8739055 100755 --- a/src/components/ui/SteamCMDNotification.tsx +++ b/src/components/ui/SteamCMDNotification.tsx @@ -12,11 +12,11 @@ export function SteamCMDNotification() { const serverCount = runningSteamServers.size; return ( -
+
@@ -28,7 +28,7 @@ export function SteamCMDNotification() {
-

+

SteamCMD is currently running for {serverCount} server{serverCount !== 1 ? 's' : ''}. Server actions are temporarily disabled to prevent conflicts. This will automatically resolve when the download completes. @@ -36,8 +36,8 @@ export function SteamCMDNotification() {

-
- Downloading... +
+ Downloading...