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:
keyhan
2026-04-06 17:20:56 +03:30
parent 5d281b85d6
commit 3c3e0e48fa
18 changed files with 248 additions and 229 deletions
+36 -35
View File
@@ -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>
+12 -11
View File
@@ -3,8 +3,9 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import Link from 'next/link';
import api from '@/lib/api';
import toast from 'react-hot-toast';
import { toast } from 'react-toastify';
import type { Application } from '@/types';
import { Rocket, Package, Hexagon, Database, Box } from 'lucide-react';
const statusColors: Record<string, string> = {
running: 'badge-green',
@@ -64,17 +65,17 @@ export default function AppsPage() {
<p className="page-subtitle">{apps.length} application{apps.length !== 1 ? 's' : ''}</p>
</div>
<Link href="/dashboard/deploy" className="btn-primary">
🚀 New Application
<Rocket className="w-4 h-4 mr-1 inline" /> New Application
</Link>
</div>
{apps.length === 0 ? (
<div className="card text-center py-16">
<div className="text-5xl mb-4">📦</div>
<Package className="w-12 h-12 mx-auto mb-4 text-gray-300" />
<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-6 inline-flex">
🚀 Deploy your first app
<Link href="/dashboard/deploy" className="btn-primary mt-6 inline-flex items-center gap-1">
<Rocket className="w-4 h-4" /> Deploy your first app
</Link>
</div>
) : (
@@ -99,8 +100,8 @@ export default function AppsPage() {
<tr key={app.id} className="hover:bg-gray-50/50 transition-colors">
<td className="px-6 py-4">
<Link href={`/dashboard/apps/${app.id}`} className="flex items-center gap-3 group">
<div className="w-9 h-9 rounded-lg bg-primary-50 flex items-center justify-center text-base shrink-0">
{app.runtime === 'nodejs' ? '🟩' : '🟧'}
<div className="w-9 h-9 rounded-lg bg-primary-50 flex items-center justify-center shrink-0">
<Hexagon className={`w-5 h-5 ${app.runtime === 'nodejs' ? 'text-green-600' : 'text-orange-500'}`} />
</div>
<span className="font-semibold text-gray-900 group-hover:text-primary-600 transition-colors">
{app.name}
@@ -147,8 +148,8 @@ export default function AppsPage() {
>
<div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-xl bg-primary-50 flex items-center justify-center text-lg">
{app.runtime === 'nodejs' ? '🟩' : '🟧'}
<div className="w-10 h-10 rounded-xl bg-primary-50 flex items-center justify-center">
<Hexagon className={`w-5 h-5 ${app.runtime === 'nodejs' ? 'text-green-600' : 'text-orange-500'}`} />
</div>
<div>
<h3 className="font-semibold text-gray-900">{app.name}</h3>
@@ -160,8 +161,8 @@ export default function AppsPage() {
</span>
</div>
<div className="flex items-center gap-4 text-xs text-gray-500">
<span>💾 {app.databaseType}</span>
<span>📦 {app.replicas} replica{app.replicas > 1 ? 's' : ''}</span>
<span className="flex items-center gap-1"><Database className="w-3 h-3" /> {app.databaseType}</span>
<span className="flex items-center gap-1"><Box className="w-3 h-3" /> {app.replicas} replica{app.replicas > 1 ? 's' : ''}</span>
</div>
</Link>
);