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
|
||||
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" />
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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' && (
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
)
|
||||
|
||||
@ -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>
|
||||
</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 */}
|
||||
<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] ?? '—')}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user