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:
@@ -140,25 +140,37 @@ export default function DeployPage() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="max-w-2xl mx-auto space-y-8">
|
||||
<div className="max-w-2xl mx-auto space-y-8 animate-fade-in">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-gray-900">Deploy New Application</h1>
|
||||
<p className="mt-1 text-gray-500">Follow the steps to deploy your app to the cloud.</p>
|
||||
<h1 className="page-title">Deploy New Application</h1>
|
||||
<p className="page-subtitle">Follow the steps to deploy your app to the cloud.</p>
|
||||
</div>
|
||||
|
||||
{/* Step indicator */}
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="flex items-center justify-between">
|
||||
{steps.map((label, i) => (
|
||||
<div key={label} className="flex items-center">
|
||||
<div className={`flex items-center justify-center w-8 h-8 rounded-full text-sm font-medium ${
|
||||
i <= step ? 'bg-primary-600 text-white' : 'bg-gray-200 text-gray-500'
|
||||
}`}>
|
||||
{i + 1}
|
||||
<div key={label} className="flex items-center flex-1 last:flex-none">
|
||||
<div className="flex flex-col items-center">
|
||||
<div className={`flex items-center justify-center w-9 h-9 rounded-full text-sm font-bold transition-all duration-300 ${
|
||||
i < step
|
||||
? 'bg-emerald-500 text-white shadow-md'
|
||||
: i === step
|
||||
? 'bg-primary-600 text-white shadow-lg ring-4 ring-primary-100'
|
||||
: 'bg-gray-200 text-gray-500'
|
||||
}`}>
|
||||
{i < step ? '✓' : i + 1}
|
||||
</div>
|
||||
<span className={`mt-1.5 text-xs font-medium hidden sm:block ${
|
||||
i <= step ? 'text-gray-900' : 'text-gray-400'
|
||||
}`}>
|
||||
{label}
|
||||
</span>
|
||||
</div>
|
||||
<span className={`ml-2 text-sm ${i <= step ? 'text-gray-900 font-medium' : 'text-gray-400'}`}>
|
||||
{label}
|
||||
</span>
|
||||
{i < steps.length - 1 && <div className="w-8 h-0.5 bg-gray-200 mx-3" />}
|
||||
{i < steps.length - 1 && (
|
||||
<div className={`flex-1 h-0.5 mx-2 sm:mx-3 rounded-full transition-colors duration-300 ${
|
||||
i < step ? 'bg-emerald-400' : 'bg-gray-200'
|
||||
}`} />
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -166,8 +178,8 @@ export default function DeployPage() {
|
||||
<div className="card">
|
||||
{/* Step 0: Basic Info */}
|
||||
{step === 0 && (
|
||||
<div className="space-y-4">
|
||||
<h2 className="text-lg font-semibold">Basic Information</h2>
|
||||
<div className="space-y-5">
|
||||
<h2 className="text-lg font-semibold text-gray-900">Basic Information</h2>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Application Name</label>
|
||||
<input
|
||||
@@ -326,10 +338,10 @@ export default function DeployPage() {
|
||||
{/* Step 1: Runtime & Database */}
|
||||
{step === 1 && (
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-lg font-semibold">Runtime & Database</h2>
|
||||
<h2 className="text-lg font-semibold text-gray-900">Runtime & Database</h2>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-3">Application Runtime</label>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
{[
|
||||
{ value: 'nodejs', label: 'Node.js', icon: '🟩', desc: 'Express, NestJS, Fastify...' },
|
||||
{ value: 'laravel', label: 'Laravel', icon: '🟧', desc: 'PHP 8.3, Composer, Artisan' },
|
||||
@@ -351,7 +363,7 @@ export default function DeployPage() {
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-3">Database</label>
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<div className="grid grid-cols-3 gap-3 sm:gap-4">
|
||||
{[
|
||||
{ value: 'none', label: 'None', icon: '❌' },
|
||||
{ value: 'postgresql', label: 'PostgreSQL', icon: '🐘' },
|
||||
@@ -377,12 +389,12 @@ export default function DeployPage() {
|
||||
{/* Step 2: Resources */}
|
||||
{step === 2 && (
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-lg font-semibold">Resources & Configuration</h2>
|
||||
<h2 className="text-lg font-semibold text-gray-900">Resources & Configuration</h2>
|
||||
|
||||
{/* Cluster Assignment Mode */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">Cluster Assignment</label>
|
||||
<div className="grid grid-cols-3 gap-3 mb-4">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3 mb-4">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
@@ -542,7 +554,7 @@ export default function DeployPage() {
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">CPU Request</label>
|
||||
<select className="input-field" value={form.cpuRequest} onChange={(e) => setForm({ ...form, cpuRequest: e.target.value })}>
|
||||
@@ -580,7 +592,7 @@ export default function DeployPage() {
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Replicas</label>
|
||||
<input
|
||||
@@ -606,7 +618,7 @@ export default function DeployPage() {
|
||||
{/* Environment Variables */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">Environment Variables</label>
|
||||
<div className="flex space-x-2 mb-3">
|
||||
<div className="flex flex-col sm:flex-row gap-2 mb-3">
|
||||
<input
|
||||
className="input-field flex-1"
|
||||
placeholder="KEY"
|
||||
@@ -619,7 +631,7 @@ export default function DeployPage() {
|
||||
value={envVal}
|
||||
onChange={(e) => setEnvVal(e.target.value)}
|
||||
/>
|
||||
<button type="button" onClick={addEnvVar} className="btn-secondary">Add</button>
|
||||
<button type="button" onClick={addEnvVar} className="btn-secondary shrink-0">Add</button>
|
||||
</div>
|
||||
{Object.entries(form.envVars || {}).map(([key, value]) => (
|
||||
<div key={key} className="flex items-center justify-between bg-gray-50 rounded-lg px-3 py-2 mb-2">
|
||||
@@ -636,8 +648,8 @@ export default function DeployPage() {
|
||||
{/* Step 3: Review */}
|
||||
{step === 3 && (
|
||||
<div className="space-y-4">
|
||||
<h2 className="text-lg font-semibold">Review & Deploy</h2>
|
||||
<div className="bg-gray-50 rounded-xl p-6 space-y-3">
|
||||
<h2 className="text-lg font-semibold text-gray-900">Review & Deploy</h2>
|
||||
<div className="bg-gray-50 rounded-xl p-5 sm:p-6 space-y-3">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-sm text-gray-500">Name</span>
|
||||
<span className="text-sm font-medium">{form.name}</span>
|
||||
@@ -709,11 +721,11 @@ export default function DeployPage() {
|
||||
)}
|
||||
|
||||
{/* Navigation */}
|
||||
<div className="flex justify-between mt-8 pt-6 border-t border-gray-200">
|
||||
<div className="flex justify-between mt-8 pt-6 border-t border-gray-100">
|
||||
<button
|
||||
onClick={() => setStep(step - 1)}
|
||||
disabled={step === 0}
|
||||
className="btn-secondary disabled:opacity-30"
|
||||
className="btn-ghost disabled:opacity-0 disabled:pointer-events-none"
|
||||
>
|
||||
← Back
|
||||
</button>
|
||||
@@ -721,7 +733,7 @@ export default function DeployPage() {
|
||||
<button
|
||||
onClick={() => setStep(step + 1)}
|
||||
disabled={!canNext()}
|
||||
className="btn-primary"
|
||||
className="btn-primary disabled:opacity-50"
|
||||
>
|
||||
Next →
|
||||
</button>
|
||||
@@ -729,12 +741,12 @@ export default function DeployPage() {
|
||||
<button
|
||||
onClick={handleSubmit}
|
||||
disabled={createMutation.isPending}
|
||||
className="btn-primary"
|
||||
className="btn-primary disabled:opacity-50"
|
||||
>
|
||||
{createMutation.isPending
|
||||
? uploadProgress > 0 && uploadProgress < 100
|
||||
? `Uploading... ${uploadProgress}%`
|
||||
: 'Deploying...'
|
||||
? `⏳ Uploading... ${uploadProgress}%`
|
||||
: '⏳ Deploying...'
|
||||
: '🚀 Deploy Application'}
|
||||
</button>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user