mirror of
https://github.com/rmoren97/mc-manager.git
synced 2026-02-10 17:40:30 -08:00
updates
This commit is contained in:
parent
e3e02acbc5
commit
c3db15827d
@ -108,7 +108,7 @@ export default function DashboardPage() {
|
|||||||
<a
|
<a
|
||||||
key={server._id}
|
key={server._id}
|
||||||
href={`/servers/${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">
|
<div className="flex items-center gap-4">
|
||||||
<Server size={20} className="text-gray-500" />
|
<Server size={20} className="text-gray-500" />
|
||||||
|
|||||||
@ -20,7 +20,7 @@ export default function AppLayout({ children }: { children: React.ReactNode }) {
|
|||||||
return (
|
return (
|
||||||
<div className="flex h-screen bg-gray-950">
|
<div className="flex h-screen bg-gray-950">
|
||||||
<Sidebar />
|
<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}
|
{children}
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -196,7 +196,7 @@ export default function ServerDetailPage() {
|
|||||||
description={`${server.type} · ${server.version} · Port ${server.port}`}
|
description={`${server.type} · ${server.version} · Port ${server.port}`}
|
||||||
icon={Server}
|
icon={Server}
|
||||||
actions={
|
actions={
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
<ServerStatusBadge status={server.status} size="md" />
|
<ServerStatusBadge status={server.status} size="md" />
|
||||||
|
|
||||||
{hasPermission('servers:start') && server.status === 'offline' && (
|
{hasPermission('servers:start') && server.status === 'offline' && (
|
||||||
|
|||||||
@ -88,7 +88,7 @@ export default function ConsoleViewer({ serverId, readOnly = false }: ConsoleVie
|
|||||||
{/* Console Output */}
|
{/* Console Output */}
|
||||||
<div
|
<div
|
||||||
ref={containerRef}
|
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 ? (
|
{lines.length === 0 ? (
|
||||||
<p className="text-gray-600 italic">Waiting for server output...</p>
|
<p className="text-gray-600 italic">Waiting for server output...</p>
|
||||||
|
|||||||
@ -9,16 +9,16 @@ interface PageHeaderProps {
|
|||||||
|
|
||||||
export default function PageHeader({ title, description, icon: Icon, actions }: PageHeaderProps) {
|
export default function PageHeader({ title, description, icon: Icon, actions }: PageHeaderProps) {
|
||||||
return (
|
return (
|
||||||
<div className="bg-gray-900/80 backdrop-blur-lg rounded-lg shadow-lg border border-gray-700/50 p-6">
|
<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 items-center justify-between">
|
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3 min-w-0">
|
||||||
{Icon && <Icon size={28} className="text-gray-400" />}
|
{Icon && <Icon size={28} className="text-gray-400 flex-shrink-0" />}
|
||||||
<div>
|
<div className="min-w-0">
|
||||||
<h1 className="text-2xl font-bold text-gray-100">{title}</h1>
|
<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">{description}</p>}
|
{description && <p className="text-sm text-gray-400 mt-1 truncate">{description}</p>}
|
||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { useState } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import { usePathname } from 'next/navigation'
|
import { usePathname } from 'next/navigation'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import { useAuth } from '@/contexts/AuthContext'
|
import { useAuth } from '@/contexts/AuthContext'
|
||||||
@ -14,6 +14,8 @@ import {
|
|||||||
ChevronLeft,
|
ChevronLeft,
|
||||||
ChevronRight,
|
ChevronRight,
|
||||||
Gamepad2,
|
Gamepad2,
|
||||||
|
Menu,
|
||||||
|
X,
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
|
|
||||||
interface NavItem {
|
interface NavItem {
|
||||||
@ -33,6 +35,7 @@ const navItems: NavItem[] = [
|
|||||||
|
|
||||||
export default function Sidebar() {
|
export default function Sidebar() {
|
||||||
const [collapsed, setCollapsed] = useState(false)
|
const [collapsed, setCollapsed] = useState(false)
|
||||||
|
const [mobileOpen, setMobileOpen] = useState(false)
|
||||||
const pathname = usePathname()
|
const pathname = usePathname()
|
||||||
const { user, logout, hasPermission } = useAuth()
|
const { user, logout, hasPermission } = useAuth()
|
||||||
|
|
||||||
@ -40,18 +43,41 @@ export default function Sidebar() {
|
|||||||
item => !item.permission || hasPermission(item.permission)
|
item => !item.permission || hasPermission(item.permission)
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
// Close mobile sidebar on route change
|
||||||
<aside
|
useEffect(() => {
|
||||||
className={`flex flex-col bg-gray-900 border-r border-gray-700/50 transition-all duration-300 ${
|
setMobileOpen(false)
|
||||||
collapsed ? 'w-16' : 'w-64'
|
}, [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 */}
|
{/* Logo */}
|
||||||
<div className="flex items-center gap-3 px-4 h-16 border-b border-gray-700/50">
|
<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" />
|
<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>
|
<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>
|
</div>
|
||||||
|
|
||||||
{/* Navigation */}
|
{/* Navigation */}
|
||||||
@ -71,7 +97,7 @@ export default function Sidebar() {
|
|||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<Icon size={20} className="flex-shrink-0" />
|
<Icon size={20} className="flex-shrink-0" />
|
||||||
{!collapsed && <span>{item.label}</span>}
|
{(!collapsed || mobileOpen) && <span>{item.label}</span>}
|
||||||
</Link>
|
</Link>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
@ -80,7 +106,7 @@ export default function Sidebar() {
|
|||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
<div className="border-t border-gray-700/50 p-2 space-y-1">
|
<div className="border-t border-gray-700/50 p-2 space-y-1">
|
||||||
{/* User Info */}
|
{/* User Info */}
|
||||||
{user && !collapsed && (
|
{user && (!collapsed || mobileOpen) && (
|
||||||
<div className="px-3 py-2">
|
<div className="px-3 py-2">
|
||||||
<p className="text-sm font-medium text-gray-200 truncate">{user.username}</p>
|
<p className="text-sm font-medium text-gray-200 truncate">{user.username}</p>
|
||||||
<p className="text-xs text-gray-500 truncate">{user.email}</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"
|
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" />
|
<LogOut size={20} className="flex-shrink-0" />
|
||||||
{!collapsed && <span>Logout</span>}
|
{(!collapsed || mobileOpen) && <span>Logout</span>}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* Collapse Toggle */}
|
{/* Collapse Toggle — desktop only */}
|
||||||
<button
|
<button
|
||||||
onClick={() => setCollapsed(prev => !prev)}
|
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 ? <ChevronRight size={20} /> : <ChevronLeft size={20} />}
|
||||||
{!collapsed && <span className="text-sm">Collapse</span>}
|
{!collapsed && <span className="text-sm">Collapse</span>}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</>
|
||||||
|
)
|
||||||
|
|
||||||
|
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>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -133,9 +133,9 @@ export default function DataManagementTemplate<T extends Record<string, any>>({
|
|||||||
|
|
||||||
{/* Search & Filters Bar */}
|
{/* Search & Filters Bar */}
|
||||||
<div className="bg-gray-900/80 backdrop-blur-lg rounded-lg border border-gray-700/50 p-4">
|
<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 */}
|
{/* 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" />
|
<Search size={16} className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-500" />
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
@ -196,7 +196,7 @@ export default function DataManagementTemplate<T extends Record<string, any>>({
|
|||||||
{columns.map(col => (
|
{columns.map(col => (
|
||||||
<th
|
<th
|
||||||
key={col.key}
|
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' : ''
|
col.sortable ? 'cursor-pointer select-none hover:text-gray-200' : ''
|
||||||
}`}
|
}`}
|
||||||
onClick={() => col.sortable && handleSort(col.key)}
|
onClick={() => col.sortable && handleSort(col.key)}
|
||||||
@ -221,7 +221,7 @@ export default function DataManagementTemplate<T extends Record<string, any>>({
|
|||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{columns.map(col => (
|
{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
|
||||||
? col.render(item)
|
? col.render(item)
|
||||||
: String(item[col.key] ?? '—')}
|
: String(item[col.key] ?? '—')}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user