feat: comprehensive UI polish with responsive design, animations, and modern styling
- Rewrite globals.css with professional utility classes (btn-primary/secondary/danger/ghost, input-field, card/card-hover, badge system, stat-card, skeleton, page-header, animations) - Responsive dashboard layout with mobile hamburger menu, slide-out sidebar, backdrop overlay - Polish login/register pages with gradient backgrounds, slide-up animations, loading spinners - Dashboard home with stat cards grid (2-col mobile, 4-col desktop), skeleton loading, empty states - Apps list with desktop table + mobile card views, proper badges - Deploy wizard with responsive step indicator (checkmarks, progress line), mobile-friendly forms - App detail page with responsive header/action buttons, improved logs tabs, resource scaling - Admin users page with desktop table + mobile cards, skeleton loading - Admin clusters page with responsive layout, animated resource panel - Admin pools page with responsive form grids, improved pool cards - Consistent rounded-2xl cards, rounded-xl buttons/inputs, active:scale feedback throughout
This commit is contained in:
@@ -7,13 +7,18 @@ import { useAuthStore } from '@/lib/store';
|
||||
import type { Application } from '@/types';
|
||||
|
||||
const statusColors: Record<string, string> = {
|
||||
running: 'bg-green-100 text-green-700',
|
||||
pending: 'bg-yellow-100 text-yellow-700',
|
||||
running: 'bg-emerald-100 text-emerald-700',
|
||||
pending: 'bg-amber-100 text-amber-700',
|
||||
building: 'bg-blue-100 text-blue-700',
|
||||
deploying: 'bg-blue-100 text-blue-700',
|
||||
failed: 'bg-red-100 text-red-700',
|
||||
build_failed: 'bg-red-100 text-red-700',
|
||||
stopped: 'bg-gray-100 text-gray-700',
|
||||
stopped: 'bg-gray-100 text-gray-600',
|
||||
};
|
||||
|
||||
const statusIcons: Record<string, string> = {
|
||||
running: '🟢', pending: '🟡', building: '🔵', deploying: '🔵',
|
||||
failed: '🔴', build_failed: '🔴', stopped: '⚪',
|
||||
};
|
||||
|
||||
export default function DashboardPage() {
|
||||
@@ -27,59 +32,79 @@ export default function DashboardPage() {
|
||||
const runningApps = apps.filter(
|
||||
(a) => a.deployments?.some((d) => d.status === 'running'),
|
||||
);
|
||||
const failedApps = apps.filter(
|
||||
(a) => a.deployments?.some((d) => d.status === 'failed' || d.status === 'build_failed'),
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
{/* Welcome */}
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-gray-900">
|
||||
<h1 className="page-title">
|
||||
Welcome back, {user?.firstName}! 👋
|
||||
</h1>
|
||||
<p className="mt-1 text-gray-500">
|
||||
<p className="page-subtitle">
|
||||
Here's an overview of your applications.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Stats */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<div className="card">
|
||||
<p className="text-sm font-medium text-gray-500">Total Apps</p>
|
||||
<p className="mt-2 text-3xl font-bold text-gray-900">{apps.length}</p>
|
||||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<div className="stat-card">
|
||||
<div className="stat-label">Total Apps</div>
|
||||
<div className="stat-value text-gray-900">{isLoading ? '—' : apps.length}</div>
|
||||
</div>
|
||||
<div className="card">
|
||||
<p className="text-sm font-medium text-gray-500">Running</p>
|
||||
<p className="mt-2 text-3xl font-bold text-green-600">{runningApps.length}</p>
|
||||
<div className="stat-card">
|
||||
<div className="stat-label">Running</div>
|
||||
<div className="stat-value text-emerald-600">{isLoading ? '—' : runningApps.length}</div>
|
||||
</div>
|
||||
<div className="card">
|
||||
<p className="text-sm font-medium text-gray-500">Deployments (7d)</p>
|
||||
<p className="mt-2 text-3xl font-bold text-primary-600">
|
||||
{apps.reduce((sum, a) => sum + (a.deployments?.length || 0), 0)}
|
||||
</p>
|
||||
<div className="stat-card">
|
||||
<div className="stat-label">Failed</div>
|
||||
<div className="stat-value text-red-600">{isLoading ? '—' : failedApps.length}</div>
|
||||
</div>
|
||||
<div className="stat-card">
|
||||
<div className="stat-label">Total Deploys</div>
|
||||
<div className="stat-value text-primary-600">
|
||||
{isLoading ? '—' : apps.reduce((sum, a) => sum + (a.deployments?.length || 0), 0)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Recent Applications */}
|
||||
<div>
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<div className="page-header mb-4">
|
||||
<h2 className="text-lg font-semibold text-gray-900">Recent Applications</h2>
|
||||
<Link href="/dashboard/deploy" className="btn-primary text-sm">
|
||||
+ New Application
|
||||
🚀 New Application
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{isLoading ? (
|
||||
<div className="card text-center py-12 text-gray-500">Loading...</div>
|
||||
<div className="space-y-3">
|
||||
{[1, 2, 3].map((i) => (
|
||||
<div key={i} className="card flex items-center gap-4">
|
||||
<div className="skeleton w-11 h-11 rounded-xl" />
|
||||
<div className="flex-1 space-y-2">
|
||||
<div className="skeleton h-4 w-40" />
|
||||
<div className="skeleton h-3 w-64" />
|
||||
</div>
|
||||
<div className="skeleton h-6 w-16 rounded-full" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : apps.length === 0 ? (
|
||||
<div className="card text-center py-12">
|
||||
<p className="text-gray-500 text-lg">No applications yet</p>
|
||||
<p className="text-gray-400 mt-2">
|
||||
<div className="card text-center py-16">
|
||||
<div className="text-5xl mb-4">📦</div>
|
||||
<p className="text-gray-600 text-lg font-medium">No applications yet</p>
|
||||
<p className="text-gray-400 mt-1 text-sm">
|
||||
Deploy your first application to get started.
|
||||
</p>
|
||||
<Link href="/dashboard/deploy" className="btn-primary mt-4 inline-block">
|
||||
Deploy your first app
|
||||
<Link href="/dashboard/deploy" className="btn-primary mt-6 inline-flex">
|
||||
🚀 Deploy your first app
|
||||
</Link>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid gap-4">
|
||||
<div className="grid gap-3">
|
||||
{apps.slice(0, 5).map((app) => {
|
||||
const latestDeploy = app.deployments?.[0];
|
||||
const status = latestDeploy?.status || 'pending';
|
||||
@@ -87,30 +112,39 @@ export default function DashboardPage() {
|
||||
<Link
|
||||
key={app.id}
|
||||
href={`/dashboard/apps/${app.id}`}
|
||||
className="card hover:border-primary-300 transition-colors flex items-center justify-between"
|
||||
className="card-hover flex items-center justify-between group"
|
||||
>
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="w-10 h-10 rounded-lg bg-primary-100 flex items-center justify-center text-lg">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="w-11 h-11 rounded-xl bg-primary-50 flex items-center justify-center text-lg shrink-0">
|
||||
{app.runtime === 'nodejs' ? '🟩' : '🟧'}
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-semibold text-gray-900">{app.name}</h3>
|
||||
<p className="text-sm text-gray-500">
|
||||
<div className="min-w-0">
|
||||
<h3 className="font-semibold text-gray-900 group-hover:text-primary-700 transition-colors truncate">
|
||||
{app.name}
|
||||
</h3>
|
||||
<p className="text-sm text-gray-500 truncate">
|
||||
{app.runtime} · {app.replicas} replica{app.replicas > 1 ? 's' : ''}
|
||||
{app.databaseType !== 'none' && ` · ${app.databaseType}`}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<span
|
||||
className={`px-3 py-1 rounded-full text-xs font-medium ${
|
||||
statusColors[status] || 'bg-gray-100 text-gray-600'
|
||||
}`}
|
||||
>
|
||||
{status}
|
||||
</span>
|
||||
<div className="flex items-center gap-2 shrink-0">
|
||||
<span className="text-sm">{statusIcons[status]}</span>
|
||||
<span className={`badge ${statusColors[status] || 'badge-gray'}`}>
|
||||
{status}
|
||||
</span>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
{apps.length > 5 && (
|
||||
<Link
|
||||
href="/dashboard/apps"
|
||||
className="text-center text-sm text-primary-600 hover:text-primary-700 font-medium py-3"
|
||||
>
|
||||
View all {apps.length} applications →
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user