This commit is contained in:
rmoren97 2026-02-07 15:55:48 -08:00
parent e3e02acbc5
commit c3db15827d
7 changed files with 100 additions and 30 deletions

View File

@ -108,7 +108,7 @@ export default function DashboardPage() {
<a
key={server._id}
href={`/servers/${server._id}`}
className="flex items-center justify-between px-6 py-4 hover:bg-gray-800 transition-colors"
className="flex items-center justify-between px-4 sm:px-6 py-4 hover:bg-gray-800 transition-colors gap-3"
>
<div className="flex items-center gap-4">
<Server size={20} className="text-gray-500" />

View File

@ -20,7 +20,7 @@ export default function AppLayout({ children }: { children: React.ReactNode }) {
return (
<div className="flex h-screen bg-gray-950">
<Sidebar />
<main className="flex-1 overflow-y-auto p-6">
<main className="flex-1 overflow-y-auto p-4 pt-18 lg:p-6 lg:pt-6">
{children}
</main>
</div>

View File

@ -196,7 +196,7 @@ export default function ServerDetailPage() {
description={`${server.type} · ${server.version} · Port ${server.port}`}
icon={Server}
actions={
<div className="flex items-center gap-2">
<div className="flex flex-wrap items-center gap-2">
<ServerStatusBadge status={server.status} size="md" />
{hasPermission('servers:start') && server.status === 'offline' && (

View File

@ -88,7 +88,7 @@ export default function ConsoleViewer({ serverId, readOnly = false }: ConsoleVie
{/* Console Output */}
<div
ref={containerRef}
className="flex-1 overflow-y-auto p-4 font-mono text-sm text-gray-300 space-y-0.5 min-h-[400px]"
className="flex-1 overflow-y-auto p-4 font-mono text-xs sm:text-sm text-gray-300 space-y-0.5 min-h-[300px] sm:min-h-[400px]"
>
{lines.length === 0 ? (
<p className="text-gray-600 italic">Waiting for server output...</p>

View File

@ -9,16 +9,16 @@ interface PageHeaderProps {
export default function PageHeader({ title, description, icon: Icon, actions }: PageHeaderProps) {
return (
<div className="bg-gray-900/80 backdrop-blur-lg rounded-lg shadow-lg border border-gray-700/50 p-6">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
{Icon && <Icon size={28} className="text-gray-400" />}
<div>
<h1 className="text-2xl font-bold text-gray-100">{title}</h1>
{description && <p className="text-sm text-gray-400 mt-1">{description}</p>}
<div className="bg-gray-900/80 backdrop-blur-lg rounded-lg shadow-lg border border-gray-700/50 p-4 sm:p-6">
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div className="flex items-center gap-3 min-w-0">
{Icon && <Icon size={28} className="text-gray-400 flex-shrink-0" />}
<div className="min-w-0">
<h1 className="text-xl sm:text-2xl font-bold text-gray-100 truncate">{title}</h1>
{description && <p className="text-sm text-gray-400 mt-1 truncate">{description}</p>}
</div>
</div>
{actions && <div className="flex items-center gap-2">{actions}</div>}
{actions && <div className="flex flex-wrap items-center gap-2">{actions}</div>}
</div>
</div>
)

View File

@ -1,6 +1,6 @@
'use client'
import { useState } from 'react'
import { useState, useEffect } from 'react'
import { usePathname } from 'next/navigation'
import Link from 'next/link'
import { useAuth } from '@/contexts/AuthContext'
@ -14,6 +14,8 @@ import {
ChevronLeft,
ChevronRight,
Gamepad2,
Menu,
X,
} from 'lucide-react'
interface NavItem {
@ -33,6 +35,7 @@ const navItems: NavItem[] = [
export default function Sidebar() {
const [collapsed, setCollapsed] = useState(false)
const [mobileOpen, setMobileOpen] = useState(false)
const pathname = usePathname()
const { user, logout, hasPermission } = useAuth()
@ -40,18 +43,41 @@ export default function Sidebar() {
item => !item.permission || hasPermission(item.permission)
)
return (
<aside
className={`flex flex-col bg-gray-900 border-r border-gray-700/50 transition-all duration-300 ${
collapsed ? 'w-16' : 'w-64'
}`}
>
// Close mobile sidebar on route change
useEffect(() => {
setMobileOpen(false)
}, [pathname])
// Prevent body scroll when mobile sidebar is open
useEffect(() => {
if (mobileOpen) {
document.body.style.overflow = 'hidden'
} else {
document.body.style.overflow = ''
}
return () => {
document.body.style.overflow = ''
}
}, [mobileOpen])
const sidebarContent = (
<>
{/* Logo */}
<div className="flex items-center gap-3 px-4 h-16 border-b border-gray-700/50">
<Gamepad2 size={28} className="text-cyan-500 flex-shrink-0" />
{!collapsed && (
{(!collapsed || mobileOpen) && (
<span className="text-lg font-bold text-gray-100 truncate">MC-Manager</span>
)}
{/* Mobile close button */}
{mobileOpen && (
<button
onClick={() => setMobileOpen(false)}
className="ml-auto p-1.5 rounded-lg text-gray-400 hover:bg-gray-800 hover:text-gray-200 transition-colors lg:hidden"
aria-label="Close menu"
>
<X size={20} />
</button>
)}
</div>
{/* Navigation */}
@ -71,7 +97,7 @@ export default function Sidebar() {
}`}
>
<Icon size={20} className="flex-shrink-0" />
{!collapsed && <span>{item.label}</span>}
{(!collapsed || mobileOpen) && <span>{item.label}</span>}
</Link>
)
})}
@ -80,7 +106,7 @@ export default function Sidebar() {
{/* Footer */}
<div className="border-t border-gray-700/50 p-2 space-y-1">
{/* User Info */}
{user && !collapsed && (
{user && (!collapsed || mobileOpen) && (
<div className="px-3 py-2">
<p className="text-sm font-medium text-gray-200 truncate">{user.username}</p>
<p className="text-xs text-gray-500 truncate">{user.email}</p>
@ -93,18 +119,62 @@ export default function Sidebar() {
className="flex items-center gap-3 px-3 py-2.5 rounded-lg text-gray-400 hover:bg-red-500/10 hover:text-red-400 transition-colors text-sm font-medium w-full"
>
<LogOut size={20} className="flex-shrink-0" />
{!collapsed && <span>Logout</span>}
{(!collapsed || mobileOpen) && <span>Logout</span>}
</button>
{/* Collapse Toggle */}
{/* Collapse Toggle — desktop only */}
<button
onClick={() => setCollapsed(prev => !prev)}
className="flex items-center justify-center gap-3 px-3 py-2.5 rounded-lg text-gray-500 hover:bg-gray-800 hover:text-gray-300 transition-colors w-full"
className="hidden lg:flex items-center justify-center gap-3 px-3 py-2.5 rounded-lg text-gray-500 hover:bg-gray-800 hover:text-gray-300 transition-colors w-full"
>
{collapsed ? <ChevronRight size={20} /> : <ChevronLeft size={20} />}
{!collapsed && <span className="text-sm">Collapse</span>}
</button>
</div>
</>
)
return (
<>
{/* Mobile top bar */}
<div className="lg:hidden fixed top-0 left-0 right-0 z-30 flex items-center gap-3 px-4 h-14 bg-gray-900 border-b border-gray-700/50">
<button
onClick={() => setMobileOpen(true)}
className="p-2 -ml-2 rounded-lg text-gray-400 hover:bg-gray-800 hover:text-gray-200 transition-colors"
aria-label="Open menu"
>
<Menu size={22} />
</button>
<Gamepad2 size={24} className="text-cyan-500" />
<span className="text-base font-bold text-gray-100">MC-Manager</span>
</div>
{/* Mobile overlay */}
{mobileOpen && (
<div
className="fixed inset-0 z-40 bg-black/60 backdrop-blur-sm lg:hidden"
onClick={() => setMobileOpen(false)}
/>
)}
{/* Mobile sidebar drawer */}
<aside
className={`fixed inset-y-0 left-0 z-50 flex flex-col bg-gray-900 border-r border-gray-700/50 w-72
transform transition-transform duration-300 ease-in-out lg:hidden ${
mobileOpen ? 'translate-x-0' : '-translate-x-full'
}`}
>
{sidebarContent}
</aside>
{/* Desktop sidebar */}
<aside
className={`hidden lg:flex flex-col bg-gray-900 border-r border-gray-700/50 transition-all duration-300 ${
collapsed ? 'w-16' : 'w-64'
}`}
>
{sidebarContent}
</aside>
</>
)
}

View File

@ -133,9 +133,9 @@ export default function DataManagementTemplate<T extends Record<string, any>>({
{/* Search & Filters Bar */}
<div className="bg-gray-900/80 backdrop-blur-lg rounded-lg border border-gray-700/50 p-4">
<div className="flex flex-wrap items-center gap-4">
<div className="flex flex-col sm:flex-row sm:flex-wrap items-stretch sm:items-center gap-3 sm:gap-4">
{/* Search */}
<div className="relative flex-1 min-w-[200px]">
<div className="relative flex-1 min-w-0 sm:min-w-[200px]">
<Search size={16} className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-500" />
<input
type="text"
@ -196,7 +196,7 @@ export default function DataManagementTemplate<T extends Record<string, any>>({
{columns.map(col => (
<th
key={col.key}
className={`px-6 py-3 text-left text-xs font-medium text-gray-400 uppercase tracking-wider ${
className={`px-4 sm:px-6 py-3 text-left text-xs font-medium text-gray-400 uppercase tracking-wider ${
col.sortable ? 'cursor-pointer select-none hover:text-gray-200' : ''
}`}
onClick={() => col.sortable && handleSort(col.key)}
@ -221,7 +221,7 @@ export default function DataManagementTemplate<T extends Record<string, any>>({
}`}
>
{columns.map(col => (
<td key={col.key} className="px-6 py-4 whitespace-nowrap text-sm text-gray-300">
<td key={col.key} className="px-4 sm:px-6 py-3 sm:py-4 whitespace-nowrap text-sm text-gray-300">
{col.render
? col.render(item)
: String(item[col.key] ?? '—')}