refactor: migrate from react-hot-toast to react-toastify and replace all emoji icons with Lucide React
- Replaced react-hot-toast with react-toastify (ToastContainer in providers.tsx) - Removed @heroicons/react dependency - Added lucide-react for consistent SVG icon system - Migrated all 18 frontend pages from emoji icons to Lucide components - Updated sidebar navigation, login/register, dashboard, apps, deploy, tickets, admin users/clusters/pools pages - All emoji indicators (status, runtime, actions) now use proper SVG icons - Build passes with zero TypeScript errors
This commit is contained in:
@@ -3,9 +3,10 @@
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import api from '@/lib/api';
|
||||
import toast from 'react-hot-toast';
|
||||
import { toast } from 'react-toastify';
|
||||
import type { Application, Deployment, ResourceUsage, ClusterPublic, ClusterPoolPublic } from '@/types';
|
||||
import { useState, useRef, useCallback, useEffect } from 'react';
|
||||
import { Hexagon, Rocket, Play, Square, RotateCw, Globe, Package, Server, Scale, CheckCircle, Clock, XCircle, AlertCircle, Link, GitBranch, KeyRound, FolderUp, BarChart3, ChevronDown, FileText, Monitor, Hammer, Settings, RefreshCw, Upload, Circle, Pin } from 'lucide-react';
|
||||
|
||||
const statusColors: Record<string, string> = {
|
||||
running: 'badge-green',
|
||||
@@ -293,8 +294,8 @@ export default function AppDetailPage() {
|
||||
{/* Header */}
|
||||
<div className="flex flex-col sm:flex-row sm:items-center gap-4">
|
||||
<div className="flex items-center gap-4 flex-1 min-w-0">
|
||||
<div className="w-14 h-14 rounded-2xl bg-primary-50 flex items-center justify-center text-2xl shrink-0">
|
||||
{app.runtime === 'nodejs' ? '🟩' : '🟧'}
|
||||
<div className="w-14 h-14 rounded-2xl bg-primary-50 flex items-center justify-center shrink-0">
|
||||
<Hexagon className={`w-7 h-7 ${app.runtime === 'nodejs' ? 'text-green-500' : 'text-orange-500'}`} />
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
@@ -312,39 +313,39 @@ export default function AppDetailPage() {
|
||||
<div className="flex flex-wrap gap-2 shrink-0">
|
||||
{!hasDeployments && (
|
||||
<button onClick={() => deployMutation.mutate()} disabled={deployMutation.isPending || (!app.codePath && !app.gitUrl)} className="btn-primary text-sm disabled:opacity-50">
|
||||
{deployMutation.isPending ? '⏳ Deploying...' : '🚀 Deploy'}
|
||||
{deployMutation.isPending ? <><Clock className="w-4 h-4 inline animate-spin" /> Deploying...</> : <><Rocket className="w-4 h-4 inline" /> Deploy</>}
|
||||
</button>
|
||||
)}
|
||||
{hasDeployments && (
|
||||
<>
|
||||
{isStopped ? (
|
||||
<button onClick={() => startMutation.mutate()} disabled={startMutation.isPending} className="btn-primary text-sm disabled:opacity-50">
|
||||
{startMutation.isPending ? '⏳...' : '▶️ Start'}
|
||||
{startMutation.isPending ? <><Clock className="w-3 h-3 inline animate-spin" /></> : <><Play className="w-3 h-3 inline" /> Start</>}
|
||||
</button>
|
||||
) : (
|
||||
<button onClick={() => stopMutation.mutate()} disabled={stopMutation.isPending || isInProgress} className="btn-secondary text-sm disabled:opacity-50">
|
||||
{stopMutation.isPending ? '⏳...' : '⏹️ Stop'}
|
||||
{stopMutation.isPending ? <><Clock className="w-3 h-3 inline animate-spin" /></> : <><Square className="w-3 h-3 inline" /> Stop</>}
|
||||
</button>
|
||||
)}
|
||||
{isRunning && (
|
||||
<button onClick={() => restartMutation.mutate()} disabled={restartMutation.isPending} className="btn-secondary text-sm disabled:opacity-50">
|
||||
{restartMutation.isPending ? '⏳...' : '🔄 Restart'}
|
||||
{restartMutation.isPending ? <><Clock className="w-3 h-3 inline animate-spin" /></> : <><RotateCw className="w-3 h-3 inline" /> Restart</>}
|
||||
</button>
|
||||
)}
|
||||
{!isInProgress && (
|
||||
<button onClick={() => redeployMutation.mutate()} disabled={redeployMutation.isPending} className="btn-primary text-sm disabled:opacity-50" title="Rebuild from latest source code">
|
||||
{redeployMutation.isPending ? '⏳...' : '🔄 Redeploy'}
|
||||
{redeployMutation.isPending ? <><Clock className="w-3 h-3 inline animate-spin" /></> : <><RefreshCw className="w-3 h-3 inline" /> Redeploy</>}
|
||||
</button>
|
||||
)}
|
||||
{isRunning && (
|
||||
<button onClick={() => previewMutation.mutate()} disabled={previewMutation.isPending} className="text-sm px-4 py-2 rounded-xl font-medium bg-emerald-50 text-emerald-700 hover:bg-emerald-100 border border-emerald-200 disabled:opacity-50 transition-all active:scale-[0.98]">
|
||||
{previewMutation.isPending ? '⏳...' : '🌐 Preview'}
|
||||
{previewMutation.isPending ? <><Clock className="w-3 h-3 inline animate-spin" /></> : <><Globe className="w-3 h-3 inline" /> Preview</>}
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<button onClick={handleDelete} disabled={deleteMutation.isPending} className="btn-danger text-sm disabled:opacity-50">
|
||||
{deleteMutation.isPending ? '⏳...' : 'Delete'}
|
||||
{deleteMutation.isPending ? <><Clock className="w-3 h-3 inline animate-spin" /></> : 'Delete'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -381,7 +382,7 @@ export default function AppDetailPage() {
|
||||
<div className="flex justify-between">
|
||||
<dt className="text-sm text-gray-500">Cluster</dt>
|
||||
<dd className="text-sm font-medium text-gray-900">
|
||||
🖥️ {clusters.find((c) => c.id === app.clusterId)?.name || 'Unknown'}
|
||||
<Server className="w-4 h-4 inline text-gray-400" /> {clusters.find((c) => c.id === app.clusterId)?.name || 'Unknown'}
|
||||
</dd>
|
||||
</div>
|
||||
)}
|
||||
@@ -389,7 +390,7 @@ export default function AppDetailPage() {
|
||||
<div className="flex justify-between">
|
||||
<dt className="text-sm text-gray-500">Pool</dt>
|
||||
<dd className="text-sm font-medium text-gray-900">
|
||||
⚖️ {pools.find((p) => p.id === app.poolId)?.name || 'Unknown'}
|
||||
<Scale className="w-4 h-4 inline text-gray-400" /> {pools.find((p) => p.id === app.poolId)?.name || 'Unknown'}
|
||||
</dd>
|
||||
</div>
|
||||
)}
|
||||
@@ -408,7 +409,7 @@ export default function AppDetailPage() {
|
||||
<h2 className="text-lg font-semibold text-gray-900 mb-4">Deployment History</h2>
|
||||
{deployments.length === 0 ? (
|
||||
<div className="text-center py-8">
|
||||
<div className="text-3xl mb-2">📦</div>
|
||||
<Package className="w-8 h-8 mx-auto text-gray-300 mb-2" />
|
||||
<p className="text-gray-500 text-sm">No deployments yet</p>
|
||||
<p className="text-gray-400 text-xs mt-1">Upload source code and click Deploy to get started</p>
|
||||
</div>
|
||||
@@ -423,7 +424,7 @@ export default function AppDetailPage() {
|
||||
</p>
|
||||
{d.errorMessage && (
|
||||
<p className="text-xs text-red-500 mt-1 truncate" title={d.errorMessage}>
|
||||
❌ {d.errorMessage}
|
||||
<XCircle className="w-3 h-3 inline" /> {d.errorMessage}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
@@ -439,13 +440,13 @@ export default function AppDetailPage() {
|
||||
|
||||
{/* Source Code Upload */}
|
||||
<div className="card">
|
||||
<h2 className="text-lg font-semibold text-gray-900 mb-4">📦 Source Code</h2>
|
||||
<h2 className="text-lg font-semibold text-gray-900 mb-4 flex items-center gap-2"><Package className="w-5 h-5" /> Source Code</h2>
|
||||
|
||||
{app.codePath ? (
|
||||
<div className="flex items-center justify-between p-4 bg-green-50 border border-green-200 rounded-xl mb-4">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="w-10 h-10 bg-green-100 rounded-lg flex items-center justify-center text-green-600">
|
||||
✅
|
||||
<CheckCircle className="w-5 h-5" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-green-800">Source code uploaded</p>
|
||||
@@ -463,20 +464,20 @@ export default function AppDetailPage() {
|
||||
<div className="flex items-center justify-between p-4 bg-blue-50 border border-blue-200 rounded-xl mb-4">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="w-10 h-10 bg-blue-100 rounded-lg flex items-center justify-center text-blue-600">
|
||||
🔗
|
||||
<Link className="w-5 h-5" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-blue-800">Git repository connected</p>
|
||||
<p className="text-xs text-blue-600 font-mono">{app.gitUrl}</p>
|
||||
<div className="flex items-center space-x-3 mt-1">
|
||||
{app.gitBranch && (
|
||||
<span className="text-xs text-blue-500">
|
||||
🌿 {app.gitBranch}
|
||||
<span className="text-xs text-blue-500 flex items-center gap-1">
|
||||
<GitBranch className="w-3 h-3" /> {app.gitBranch}
|
||||
</span>
|
||||
)}
|
||||
{app.gitToken && (
|
||||
<span className="text-xs text-green-600">
|
||||
🔑 Private
|
||||
<span className="text-xs text-green-600 flex items-center gap-1">
|
||||
<KeyRound className="w-3 h-3" /> Private
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
@@ -512,7 +513,7 @@ export default function AppDetailPage() {
|
||||
|
||||
{uploadMutation.isPending ? (
|
||||
<div className="space-y-3">
|
||||
<div className="text-3xl">⏳</div>
|
||||
<Upload className="w-8 h-8 mx-auto text-gray-400 animate-pulse" />
|
||||
<p className="text-sm font-medium text-gray-700">Uploading... {uploadProgress}%</p>
|
||||
<div className="w-48 mx-auto bg-gray-200 rounded-full h-2">
|
||||
<div
|
||||
@@ -523,7 +524,7 @@ export default function AppDetailPage() {
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
<div className="text-3xl">📁</div>
|
||||
<FolderUp className="w-8 h-8 mx-auto text-gray-400" />
|
||||
<p className="text-sm font-medium text-gray-700">
|
||||
{app.codePath ? 'Upload new version' : 'Upload your project source code'}
|
||||
</p>
|
||||
@@ -539,12 +540,12 @@ export default function AppDetailPage() {
|
||||
{/* Resource Monitoring & Scaling */}
|
||||
<div className="card">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h2 className="text-lg font-semibold text-gray-900">📊 Resources & Scaling</h2>
|
||||
<h2 className="text-lg font-semibold text-gray-900 flex items-center gap-2"><BarChart3 className="w-5 h-5" /> Resources & Scaling</h2>
|
||||
<button
|
||||
onClick={() => setShowResources(!showResources)}
|
||||
className="btn-secondary text-sm"
|
||||
>
|
||||
{showResources ? '🔽 Hide' : '📊 Monitor'}
|
||||
{showResources ? <><ChevronDown className="w-4 h-4 inline" /> Hide</> : <><BarChart3 className="w-4 h-4 inline" /> Monitor</>}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -574,7 +575,7 @@ export default function AppDetailPage() {
|
||||
<div className="bg-purple-50 rounded-xl p-4 text-center">
|
||||
<p className="text-xs text-purple-500 font-medium">Metrics</p>
|
||||
<p className="text-2xl font-bold text-purple-700">
|
||||
{resourceUsage.metrics.length > 0 ? '✅' : '⏳'}
|
||||
{resourceUsage.metrics.length > 0 ? <CheckCircle className="w-6 h-6 mx-auto text-purple-700" /> : <Clock className="w-6 h-6 mx-auto text-purple-400" />}
|
||||
</p>
|
||||
<p className="text-xs text-purple-400">
|
||||
{resourceUsage.metrics.length > 0 ? 'Available' : 'Waiting...'}
|
||||
@@ -599,7 +600,7 @@ export default function AppDetailPage() {
|
||||
<div key={metric.name} className="bg-gray-50 rounded-xl p-4 space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-xs font-mono text-gray-600 truncate max-w-[250px]" title={metric.name}>
|
||||
🟢 {metric.name}
|
||||
<Circle className="w-2 h-2 inline fill-green-500 text-green-500" /> {metric.name}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -673,7 +674,7 @@ export default function AppDetailPage() {
|
||||
{pod.status}
|
||||
</span>
|
||||
</td>
|
||||
<td className="py-2">{pod.ready ? '✅' : '⏳'}</td>
|
||||
<td className="py-2">{pod.ready ? <CheckCircle className="w-4 h-4 text-green-500" /> : <Clock className="w-4 h-4 text-yellow-500" />}</td>
|
||||
<td className="py-2">{pod.restarts}</td>
|
||||
</tr>
|
||||
))}
|
||||
@@ -685,7 +686,7 @@ export default function AppDetailPage() {
|
||||
|
||||
{/* Scaling Controls */}
|
||||
<div className="border-t pt-4">
|
||||
<h3 className="text-sm font-semibold text-gray-700 mb-3">⚙️ Scale Resources</h3>
|
||||
<h3 className="text-sm font-semibold text-gray-700 mb-3 flex items-center gap-1"><Settings className="w-4 h-4" /> Scale Resources</h3>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label className="block text-xs text-gray-500 mb-1">CPU Request</label>
|
||||
@@ -751,7 +752,7 @@ export default function AppDetailPage() {
|
||||
disabled={scaleMutation.isPending}
|
||||
className="btn-primary text-sm w-full disabled:opacity-50"
|
||||
>
|
||||
{scaleMutation.isPending ? '⏳ Applying...' : '🔄 Apply Changes'}
|
||||
{scaleMutation.isPending ? <><Clock className="w-3 h-3 inline animate-spin" /> Applying...</> : <><RefreshCw className="w-3 h-3 inline" /> Apply Changes</>}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -771,7 +772,7 @@ export default function AppDetailPage() {
|
||||
{/* Logs — Pod & Build */}
|
||||
<div className="card">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h2 className="text-lg font-semibold text-gray-900">📋 Logs</h2>
|
||||
<h2 className="text-lg font-semibold text-gray-900 flex items-center gap-2"><FileText className="w-5 h-5" /> Logs</h2>
|
||||
<div className="flex items-center space-x-3">
|
||||
{showLogs && logTab === 'pod' && (
|
||||
<span className="text-xs text-gray-400 flex items-center space-x-1">
|
||||
@@ -789,7 +790,7 @@ export default function AppDetailPage() {
|
||||
onClick={() => setShowLogs(!showLogs)}
|
||||
className="btn-secondary text-sm"
|
||||
>
|
||||
{showLogs ? '🔽 Hide Logs' : '📋 Show Logs'}
|
||||
{showLogs ? <><ChevronDown className="w-4 h-4 inline" /> Hide Logs</> : <><FileText className="w-4 h-4 inline" /> Show Logs</>}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -805,7 +806,7 @@ export default function AppDetailPage() {
|
||||
: 'text-gray-500 hover:text-gray-700'
|
||||
}`}
|
||||
>
|
||||
🖥️ Pod Logs
|
||||
<Monitor className="w-4 h-4 inline" /> Pod Logs
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setLogTab('build')}
|
||||
@@ -815,7 +816,7 @@ export default function AppDetailPage() {
|
||||
: 'text-gray-500 hover:text-gray-700'
|
||||
}`}
|
||||
>
|
||||
🔨 Build Logs
|
||||
<Hammer className="w-4 h-4 inline" /> Build Logs
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -834,7 +835,7 @@ export default function AppDetailPage() {
|
||||
<div>
|
||||
{buildLogsData?.version && (
|
||||
<div className="flex items-center gap-3 mb-2 text-xs text-gray-500">
|
||||
<span>📌 {buildLogsData.version}</span>
|
||||
<span><Pin className="w-3 h-3 inline" /> {buildLogsData.version}</span>
|
||||
<span className={`badge ${statusColors[buildLogsData.status] || 'badge-gray'}`}>
|
||||
{buildLogsData.status}
|
||||
</span>
|
||||
|
||||
Reference in New Issue
Block a user