diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 3a8795b..83890a5 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,15 +8,15 @@ "name": "cloudhost-frontend", "version": "1.0.0", "dependencies": { - "@heroicons/react": "^2.1.0", "@tanstack/react-query": "^5.17.0", "axios": "^1.6.0", "clsx": "^2.1.0", + "lucide-react": "^1.7.0", "next": "14.1.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-hook-form": "^7.49.0", - "react-hot-toast": "^2.4.1", + "react-toastify": "^11.0.5", "zustand": "^4.5.0" }, "devDependencies": { @@ -141,15 +141,6 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@heroicons/react": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@heroicons/react/-/react-2.2.0.tgz", - "integrity": "sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ==", - "license": "MIT", - "peerDependencies": { - "react": ">= 16 || ^19.0.0-rc" - } - }, "node_modules/@humanwhocodes/config-array": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", @@ -1754,6 +1745,7 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, "license": "MIT", "peer": true }, @@ -2336,6 +2328,7 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -3056,15 +3049,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/goober": { - "version": "2.1.18", - "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.18.tgz", - "integrity": "sha512-2vFqsaDVIT9Gz7N6kAL++pLpp41l3PfDuusHcjnGLfR6+huZkl6ziX+zgVC3ZxpqWhzH6pyDdGrCeDhMIvwaxw==", - "license": "MIT", - "peerDependencies": { - "csstype": "^3.0.10" - } - }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -3924,6 +3908,15 @@ "dev": true, "license": "ISC" }, + "node_modules/lucide-react": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.7.0.tgz", + "integrity": "sha512-yI7BeItCLZJTXikmK4KNUGCKoGzSvbKlfCvw44bU4fXAL6v3gYS4uHD1jzsLkfwODYwI6Drw5Tu9Z5ulDe0TSg==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -4819,23 +4812,6 @@ "react": "^16.8.0 || ^17 || ^18 || ^19" } }, - "node_modules/react-hot-toast": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.6.0.tgz", - "integrity": "sha512-bH+2EBMZ4sdyou/DPrfgIouFpcRLCJ+HoCA32UoAYHn6T3Ur5yfcDCeSr5mwldl6pFOsiocmrXMuoCJ1vV8bWg==", - "license": "MIT", - "dependencies": { - "csstype": "^3.1.3", - "goober": "^2.1.16" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "react": ">=16", - "react-dom": ">=16" - } - }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -4843,6 +4819,19 @@ "dev": true, "license": "MIT" }, + "node_modules/react-toastify": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-11.0.5.tgz", + "integrity": "sha512-EpqHBGvnSTtHYhCPLxML05NLY2ZX0JURbAdNYa6BUkk+amz4wbKBQvoKQAB0ardvSarUBuY4Q4s1sluAzZwkmA==", + "license": "MIT", + "dependencies": { + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": "^18 || ^19", + "react-dom": "^18 || ^19" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 07ac21f..bbed337 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -9,15 +9,15 @@ "lint": "next lint" }, "dependencies": { - "@heroicons/react": "^2.1.0", "@tanstack/react-query": "^5.17.0", "axios": "^1.6.0", "clsx": "^2.1.0", + "lucide-react": "^1.7.0", "next": "14.1.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-hook-form": "^7.49.0", - "react-hot-toast": "^2.4.1", + "react-toastify": "^11.0.5", "zustand": "^4.5.0" }, "devDependencies": { diff --git a/frontend/src/app/dashboard/admin/apps/page.tsx b/frontend/src/app/dashboard/admin/apps/page.tsx index 86313bf..20e23cc 100644 --- a/frontend/src/app/dashboard/admin/apps/page.tsx +++ b/frontend/src/app/dashboard/admin/apps/page.tsx @@ -4,8 +4,9 @@ import { useState } from 'react'; 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 { Search, X, Package, Hexagon, User, Database, Box } from 'lucide-react'; const statusColors: Record = { running: 'badge-green', @@ -130,7 +131,7 @@ export default function AdminAppsPage() { {/* Search */}
- ๐Ÿ” + { setSearch(''); setDebouncedSearch(''); }} - className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 text-sm" + className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600" > - โœ• + )}
{apps.length === 0 ? (
-
๐Ÿ“ฆ
+ {debouncedSearch ? ( <>

No applications found

@@ -188,8 +189,8 @@ export default function AdminAppsPage() { -
- {app.runtime === 'nodejs' ? '๐ŸŸฉ' : '๐ŸŸง'} +
+
@@ -252,8 +253,8 @@ export default function AdminAppsPage() { >
-
- {app.runtime === 'nodejs' ? '๐ŸŸฉ' : '๐ŸŸง'} +
+

{app.name}

@@ -266,14 +267,14 @@ export default function AdminAppsPage() {
{app.user && (
- ๐Ÿ‘ค {app.user.firstName} {app.user.lastName} + {app.user.firstName} {app.user.lastName} โ€ข {app.user.email}
)}
- ๐Ÿ’พ {app.databaseType} - ๐Ÿ“ฆ {app.replicas} replica{app.replicas > 1 ? 's' : ''} + {app.databaseType} + {app.replicas} replica{app.replicas > 1 ? 's' : ''}
); diff --git a/frontend/src/app/dashboard/admin/clusters/page.tsx b/frontend/src/app/dashboard/admin/clusters/page.tsx index 0c4e45f..2387231 100644 --- a/frontend/src/app/dashboard/admin/clusters/page.tsx +++ b/frontend/src/app/dashboard/admin/clusters/page.tsx @@ -3,8 +3,9 @@ import { useState } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import api from '@/lib/api'; -import toast from 'react-hot-toast'; +import { toast } from 'react-toastify'; import type { Cluster, ClusterResources } from '@/types'; +import { Server, CheckCircle, XCircle, BarChart3, Clock, RotateCw, Plug, X } from 'lucide-react'; function ResourcePanel({ clusterId }: { clusterId: string }) { const { data, isLoading, error } = useQuery({ @@ -201,7 +202,7 @@ export default function AdminClustersPage() {

{clusters.length} cluster{clusters.length !== 1 ? 's' : ''} registered

@@ -263,7 +264,7 @@ export default function AdminClustersPage() { disabled={!form.name || !form.apiServer || !form.kubeconfig || createMutation.isPending} className="btn-primary" > - {createMutation.isPending ? '๐Ÿ”„ Verifying connection & adding...' : 'Add Cluster'} + {createMutation.isPending ? <> Verifying connection & adding... : 'Add Cluster'}
)} @@ -283,7 +284,7 @@ export default function AdminClustersPage() {
) : clusters.length === 0 ? (
-
๐Ÿ–ฅ๏ธ
+

No clusters registered yet.

Add a Kubernetes cluster to start deploying applications.

@@ -296,7 +297,7 @@ export default function AdminClustersPage() {
- {cluster.status === 'active' ? 'โœ…' : 'โŒ'} + {cluster.status === 'active' ? : }
@@ -322,14 +323,14 @@ export default function AdminClustersPage() { onClick={() => toggleResources(cluster.id)} className={`btn-ghost text-sm ${expandedResources.has(cluster.id) ? 'bg-purple-50 text-purple-700' : ''}`} > - ๐Ÿ“Š Resources + Resources
@@ -135,8 +136,8 @@ export default function AdminPoolsPage() { value={form.strategy} onChange={(e) => setForm({ ...form, strategy: e.target.value as any })} > - - + +
@@ -223,7 +224,7 @@ export default function AdminPoolsPage() { className="btn-primary disabled:opacity-50" > {createMutation.isPending || updateMutation.isPending - ? 'โณ Saving...' + ? <> Saving... : editingPool ? 'Update Pool' : 'Create Pool'} @@ -254,7 +255,7 @@ export default function AdminPoolsPage() {
) : pools.length === 0 ? (
-
โš–๏ธ
+

No cluster pools created yet

Create a pool to enable load-balanced deployment across multiple clusters @@ -274,8 +275,8 @@ export default function AdminPoolsPage() { {pool.isActive ? 'Active' : 'Inactive'} - - {pool.strategy === 'least-apps' ? '๐Ÿ“Š Least Apps' : '๐Ÿ”„ Round Robin'} + + {pool.strategy === 'least-apps' ? <> Least Apps : <> Round Robin}

{pool.description && ( @@ -293,7 +294,7 @@ export default function AdminPoolsPage() { : 'bg-red-50 text-red-700 border border-red-200' }`} > - {cluster.status === 'active' ? 'โœ…' : 'โŒ'} + {cluster.status === 'active' ? : } {cluster.name} ({cluster.provider || 'N/A'} ยท {cluster.region || 'N/A'}) @@ -314,7 +315,7 @@ export default function AdminPoolsPage() { onClick={() => startEdit(pool)} className="btn-ghost text-sm" > - โœ๏ธ Edit + Edit ))} @@ -132,7 +133,7 @@ export default function AdminTicketsPage() { ) : tickets.length === 0 ? (
- ๐Ÿ“ญ +

No tickets found

) : ( @@ -153,14 +154,14 @@ export default function AdminTicketsPage() { {ticket.priority} - - {ticket.department === 'technical' ? '๐Ÿ”ง Technical' : '๐Ÿ’ผ Sales'} + + {ticket.department === 'technical' ? : } {ticket.department === 'technical' ? 'Technical' : 'Sales'}
{ticket.user && ( - - ๐Ÿ‘ค {ticket.user.firstName} {ticket.user.lastName} ({ticket.user.email}) + + {ticket.user.firstName} {ticket.user.lastName} ({ticket.user.email}) )} โ€ข diff --git a/frontend/src/app/dashboard/admin/users/page.tsx b/frontend/src/app/dashboard/admin/users/page.tsx index 6cdbe97..dc885f2 100644 --- a/frontend/src/app/dashboard/admin/users/page.tsx +++ b/frontend/src/app/dashboard/admin/users/page.tsx @@ -4,8 +4,9 @@ import { useState } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import api from '@/lib/api'; import { useAuthStore } from '@/lib/store'; -import toast from 'react-hot-toast'; +import { toast } from 'react-toastify'; import type { AdminUser } from '@/types'; +import { Users, Search, X, Clock } from 'lucide-react'; export default function AdminUsersPage() { const queryClient = useQueryClient(); @@ -66,7 +67,7 @@ export default function AdminUsersPage() {

{users.length} user{users.length !== 1 ? 's' : ''} registered

@@ -132,7 +133,7 @@ export default function AdminUsersPage() { disabled={!form.email || !form.password || !form.firstName || !form.lastName || createUser.isPending} className="btn-primary disabled:opacity-50" > - {createUser.isPending ? 'โณ Creating...' : 'Create User'} + {createUser.isPending ? <> Creating... : 'Create User'} )} @@ -145,14 +146,7 @@ export default function AdminUsersPage() { value={search} onChange={(e) => setSearch(e.target.value)} /> - - - + {isLoading ? ( @@ -170,7 +164,7 @@ export default function AdminUsersPage() { ) : users.length === 0 ? (
-
๐Ÿ‘ฅ
+

{search ? 'No users found matching your search.' : 'No users yet.'}

) : ( diff --git a/frontend/src/app/dashboard/apps/[id]/page.tsx b/frontend/src/app/dashboard/apps/[id]/page.tsx index 319fa35..9ab04f9 100644 --- a/frontend/src/app/dashboard/apps/[id]/page.tsx +++ b/frontend/src/app/dashboard/apps/[id]/page.tsx @@ -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 = { running: 'badge-green', @@ -293,8 +294,8 @@ export default function AppDetailPage() { {/* Header */}
-
- {app.runtime === 'nodejs' ? '๐ŸŸฉ' : '๐ŸŸง'} +
+
@@ -312,39 +313,39 @@ export default function AppDetailPage() {
{!hasDeployments && ( )} {hasDeployments && ( <> {isStopped ? ( ) : ( )} {isRunning && ( )} {!isInProgress && ( )} {isRunning && ( )} )}
@@ -381,7 +382,7 @@ export default function AppDetailPage() {
Cluster
- ๐Ÿ–ฅ๏ธ {clusters.find((c) => c.id === app.clusterId)?.name || 'Unknown'} + {clusters.find((c) => c.id === app.clusterId)?.name || 'Unknown'}
)} @@ -389,7 +390,7 @@ export default function AppDetailPage() {
Pool
- โš–๏ธ {pools.find((p) => p.id === app.poolId)?.name || 'Unknown'} + {pools.find((p) => p.id === app.poolId)?.name || 'Unknown'}
)} @@ -408,7 +409,7 @@ export default function AppDetailPage() {

Deployment History

{deployments.length === 0 ? (
-
๐Ÿ“ฆ
+

No deployments yet

Upload source code and click Deploy to get started

@@ -423,7 +424,7 @@ export default function AppDetailPage() {

{d.errorMessage && (

- โŒ {d.errorMessage} + {d.errorMessage}

)}
@@ -439,13 +440,13 @@ export default function AppDetailPage() { {/* Source Code Upload */}
-

๐Ÿ“ฆ Source Code

+

Source Code

{app.codePath ? (
- โœ… +

Source code uploaded

@@ -463,20 +464,20 @@ export default function AppDetailPage() {
- ๐Ÿ”— +

Git repository connected

{app.gitUrl}

{app.gitBranch && ( - - ๐ŸŒฟ {app.gitBranch} + + {app.gitBranch} )} {app.gitToken && ( - - ๐Ÿ”‘ Private + + Private )}
@@ -512,7 +513,7 @@ export default function AppDetailPage() { {uploadMutation.isPending ? (
-
โณ
+

Uploading... {uploadProgress}%

) : (
-
๐Ÿ“
+

{app.codePath ? 'Upload new version' : 'Upload your project source code'}

@@ -539,12 +540,12 @@ export default function AppDetailPage() { {/* Resource Monitoring & Scaling */}
-

๐Ÿ“Š Resources & Scaling

+

Resources & Scaling

@@ -574,7 +575,7 @@ export default function AppDetailPage() {

Metrics

- {resourceUsage.metrics.length > 0 ? 'โœ…' : 'โณ'} + {resourceUsage.metrics.length > 0 ? : }

{resourceUsage.metrics.length > 0 ? 'Available' : 'Waiting...'} @@ -599,7 +600,7 @@ export default function AppDetailPage() {

- ๐ŸŸข {metric.name} + {metric.name}

@@ -673,7 +674,7 @@ export default function AppDetailPage() { {pod.status} - {pod.ready ? 'โœ…' : 'โณ'} + {pod.ready ? : } {pod.restarts} ))} @@ -685,7 +686,7 @@ export default function AppDetailPage() { {/* Scaling Controls */}
-

โš™๏ธ Scale Resources

+

Scale Resources

@@ -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 ? <> Applying... : <> Apply Changes}
@@ -771,7 +772,7 @@ export default function AppDetailPage() { {/* Logs โ€” Pod & Build */}
-

๐Ÿ“‹ Logs

+

Logs

{showLogs && logTab === 'pod' && ( @@ -789,7 +790,7 @@ export default function AppDetailPage() { onClick={() => setShowLogs(!showLogs)} className="btn-secondary text-sm" > - {showLogs ? '๐Ÿ”ฝ Hide Logs' : '๐Ÿ“‹ Show Logs'} + {showLogs ? <> Hide Logs : <> Show Logs}
@@ -805,7 +806,7 @@ export default function AppDetailPage() { : 'text-gray-500 hover:text-gray-700' }`} > - ๐Ÿ–ฅ๏ธ Pod Logs + Pod Logs
@@ -834,7 +835,7 @@ export default function AppDetailPage() {
{buildLogsData?.version && (
- ๐Ÿ“Œ {buildLogsData.version} + {buildLogsData.version} {buildLogsData.status} diff --git a/frontend/src/app/dashboard/apps/page.tsx b/frontend/src/app/dashboard/apps/page.tsx index 9d08415..c2167cc 100644 --- a/frontend/src/app/dashboard/apps/page.tsx +++ b/frontend/src/app/dashboard/apps/page.tsx @@ -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 = { running: 'badge-green', @@ -64,17 +65,17 @@ export default function AppsPage() {

{apps.length} application{apps.length !== 1 ? 's' : ''}

- ๐Ÿš€ New Application + New Application
{apps.length === 0 ? (
-
๐Ÿ“ฆ
+

No applications yet

Deploy your first application to get started.

- - ๐Ÿš€ Deploy your first app + + Deploy your first app
) : ( @@ -99,8 +100,8 @@ export default function AppsPage() { -
- {app.runtime === 'nodejs' ? '๐ŸŸฉ' : '๐ŸŸง'} +
+
{app.name} @@ -147,8 +148,8 @@ export default function AppsPage() { >
-
- {app.runtime === 'nodejs' ? '๐ŸŸฉ' : '๐ŸŸง'} +
+

{app.name}

@@ -160,8 +161,8 @@ export default function AppsPage() {
- ๐Ÿ’พ {app.databaseType} - ๐Ÿ“ฆ {app.replicas} replica{app.replicas > 1 ? 's' : ''} + {app.databaseType} + {app.replicas} replica{app.replicas > 1 ? 's' : ''}
); diff --git a/frontend/src/app/dashboard/deploy/page.tsx b/frontend/src/app/dashboard/deploy/page.tsx index bd649a6..1d288da 100644 --- a/frontend/src/app/dashboard/deploy/page.tsx +++ b/frontend/src/app/dashboard/deploy/page.tsx @@ -5,8 +5,9 @@ import { useRouter } from 'next/navigation'; import { useMutation, useQuery } from '@tanstack/react-query'; import api from '@/lib/api'; import { useAuthStore } from '@/lib/store'; -import toast from 'react-hot-toast'; +import { toast } from 'react-toastify'; import type { CreateApplicationDto, ClusterPublic, ClusterPoolPublic } from '@/types'; +import { Hexagon, FolderUp, Link as LinkIcon, CheckCircle, Package, Server, Scale, Home, Target, BarChart3, RotateCw, KeyRound, Rocket, Clock, Upload, XCircle } from 'lucide-react'; const steps = ['Basic Info', 'Runtime & Database', 'Resources', 'Review']; @@ -219,7 +220,7 @@ export default function DeployPage() { : 'border-gray-200 hover:border-gray-300' }`} > - ๐Ÿ“ +

Upload ZIP

@@ -280,7 +281,7 @@ export default function DeployPage() {
- โœ… +

{zipFile.name}

@@ -313,7 +314,7 @@ export default function DeployPage() { }`} >
-
๐Ÿ“ฆ
+

Drag & drop your project ZIP here

@@ -348,8 +349,8 @@ export default function DeployPage() {
{[ - { value: 'nodejs', label: 'Node.js', icon: '๐ŸŸฉ', desc: 'Express, NestJS, Fastify...' }, - { value: 'laravel', label: 'Laravel', icon: '๐ŸŸง', desc: 'PHP 8.3, Composer, Artisan' }, + { value: 'nodejs', label: 'Node.js', icon: , desc: 'Express, NestJS, Fastify...' }, + { value: 'laravel', label: 'Laravel', icon: , desc: 'PHP 8.3, Composer, Artisan' }, ].map((opt) => ( @@ -370,9 +371,9 @@ export default function DeployPage() {
{[ - { value: 'none', label: 'None', icon: 'โŒ' }, - { value: 'postgresql', label: 'PostgreSQL', icon: '๐Ÿ˜' }, - { value: 'mysql', label: 'MySQL', icon: '๐Ÿฌ' }, + { value: 'none', label: 'None', icon: }, + { value: 'postgresql', label: 'PostgreSQL', icon: }, + { value: 'mysql', label: 'MySQL', icon: }, ].map((opt) => ( ))} @@ -413,7 +414,7 @@ export default function DeployPage() { : 'border-gray-200 hover:border-gray-300' }`} > - ๐Ÿ  +

Default

Use default cluster

@@ -429,7 +430,7 @@ export default function DeployPage() { : 'border-gray-200 hover:border-gray-300' }`} > - ๐ŸŽฏ +

Manual

Pick a specific cluster

@@ -445,7 +446,7 @@ export default function DeployPage() { : 'border-gray-200 hover:border-gray-300' }`} > - โš–๏ธ +

Load Balanced

Pick a cluster pool

@@ -470,7 +471,7 @@ export default function DeployPage() { >
- ๐Ÿ–ฅ๏ธ +

{cluster.name} @@ -517,15 +518,15 @@ export default function DeployPage() { >

- โš–๏ธ +

{pool.name}

{pool.description && (

{pool.description}

)}
- - {pool.strategy === 'least-apps' ? '๐Ÿ“Š Least Apps' : '๐Ÿ”„ Round Robin'} + + {pool.strategy === 'least-apps' ? <> Least Apps : <> Round Robin} {pool.clusters.length} cluster{pool.clusters.length !== 1 ? 's' : ''}: @@ -545,7 +546,7 @@ export default function DeployPage() { {clusterMode === 'default' && (
- ๐Ÿ  +

Default cluster will be used

@@ -562,7 +563,7 @@ export default function DeployPage() { ) : (

- ๐Ÿ  +

Cluster Assignment

@@ -686,7 +687,7 @@ export default function DeployPage() { {sourceMethod === 'upload' ? zipFile - ? `๐Ÿ“ ${zipFile.name}` + ? zipFile.name : 'โ€”' : form.gitUrl || 'โ€”'} @@ -700,17 +701,17 @@ export default function DeployPage() { {sourceMethod === 'git' && form.gitToken && (

Auth - ๐Ÿ”‘ Token provided + Token provided
)}
Cluster {isAdmin && clusterMode === 'manual' && form.clusterId - ? `๐ŸŽฏ ${clusters.find((c) => c.id === form.clusterId)?.name || form.clusterId}` + ? `${clusters.find((c) => c.id === form.clusterId)?.name || form.clusterId}` : isAdmin && clusterMode === 'pool' && form.poolId - ? `โš–๏ธ ${pools.find((p) => p.id === form.poolId)?.name || 'Pool'} (Load Balanced)` - : '๐Ÿ  Default Cluster'} + ? `${pools.find((p) => p.id === form.poolId)?.name || 'Pool'} (Load Balanced)` + : 'Default Cluster'}
@@ -764,9 +765,9 @@ export default function DeployPage() { > {createMutation.isPending ? uploadProgress > 0 && uploadProgress < 100 - ? `โณ Uploading... ${uploadProgress}%` - : 'โณ Deploying...' - : '๐Ÿš€ Deploy Application'} + ? `Uploading... ${uploadProgress}%` + : 'Deploying...' + : <> Deploy Application} )}
diff --git a/frontend/src/app/dashboard/layout.tsx b/frontend/src/app/dashboard/layout.tsx index 33dbcde..86b431a 100644 --- a/frontend/src/app/dashboard/layout.tsx +++ b/frontend/src/app/dashboard/layout.tsx @@ -1,38 +1,57 @@ 'use client'; -import { useEffect, useState } from 'react'; +import { useEffect, useState, type ReactNode } from 'react'; import { useRouter, usePathname } from 'next/navigation'; import Link from 'next/link'; import { useQuery } from '@tanstack/react-query'; import { useAuthStore } from '@/lib/store'; import api from '@/lib/api'; +import { + LayoutDashboard, + Package, + Rocket, + Ticket, + Users, + Server, + Scale, + ClipboardList, + Wrench, + Briefcase, + Cloud, + Menu, + X, + LogOut, + Boxes, +} from 'lucide-react'; -const userNavItems = [ - { href: '/dashboard', label: 'Dashboard', icon: '๐Ÿ“Š' }, - { href: '/dashboard/apps', label: 'Applications', icon: '๐Ÿ“ฆ' }, - { href: '/dashboard/deploy', label: 'New Deploy', icon: '๐Ÿš€' }, - { href: '/dashboard/tickets', label: 'Tickets', icon: '๐ŸŽซ' }, +type NavItem = { href: string; label: string; icon: ReactNode }; + +const userNavItems: NavItem[] = [ + { href: '/dashboard', label: 'Dashboard', icon: }, + { href: '/dashboard/apps', label: 'Applications', icon: }, + { href: '/dashboard/deploy', label: 'New Deploy', icon: }, + { href: '/dashboard/tickets', label: 'Tickets', icon: }, ]; -const adminNavItems = [ - { href: '/dashboard/admin/users', label: 'Users', icon: '๐Ÿ‘ฅ' }, - { href: '/dashboard/admin/apps', label: 'All Applications', icon: '๐Ÿ“ฆ' }, - { href: '/dashboard/admin/clusters', label: 'Clusters', icon: '๐Ÿ–ฅ๏ธ' }, - { href: '/dashboard/admin/pools', label: 'Cluster Pools', icon: 'โš–๏ธ' }, - { href: '/dashboard/admin/tickets', label: 'All Tickets', icon: '๐Ÿ“‹' }, +const adminNavItems: NavItem[] = [ + { href: '/dashboard/admin/users', label: 'Users', icon: }, + { href: '/dashboard/admin/apps', label: 'All Applications', icon: }, + { href: '/dashboard/admin/clusters', label: 'Clusters', icon: }, + { href: '/dashboard/admin/pools', label: 'Cluster Pools', icon: }, + { href: '/dashboard/admin/tickets', label: 'All Tickets', icon: }, ]; -const technicalNavItems = [ - { href: '/dashboard/admin/users', label: 'Users', icon: '๐Ÿ‘ฅ' }, - { href: '/dashboard/admin/apps', label: 'All Applications', icon: '๐Ÿ“ฆ' }, - { href: '/dashboard/admin/clusters', label: 'Clusters', icon: '๐Ÿ–ฅ๏ธ' }, - { href: '/dashboard/admin/pools', label: 'Cluster Pools', icon: 'โš–๏ธ' }, - { href: '/dashboard/staff/tickets', label: 'Technical Tickets', icon: '๐Ÿ”ง' }, +const technicalNavItems: NavItem[] = [ + { href: '/dashboard/admin/users', label: 'Users', icon: }, + { href: '/dashboard/admin/apps', label: 'All Applications', icon: }, + { href: '/dashboard/admin/clusters', label: 'Clusters', icon: }, + { href: '/dashboard/admin/pools', label: 'Cluster Pools', icon: }, + { href: '/dashboard/staff/tickets', label: 'Technical Tickets', icon: }, ]; -const salesNavItems = [ - { href: '/dashboard/admin/users', label: 'Users', icon: '๐Ÿ‘ฅ' }, - { href: '/dashboard/staff/tickets', label: 'Sales Tickets', icon: '๐Ÿ’ผ' }, +const salesNavItems: NavItem[] = [ + { href: '/dashboard/admin/users', label: 'Users', icon: }, + { href: '/dashboard/staff/tickets', label: 'Sales Tickets', icon: }, ]; export default function DashboardLayout({ children }: { children: React.ReactNode }) { @@ -65,7 +84,7 @@ export default function DashboardLayout({ children }: { children: React.ReactNod if (!isAuthenticated) return null; - const NavLink = ({ item, badge }: { item: { href: string; label: string; icon: string }; badge?: number }) => { + const NavLink = ({ item, badge }: { item: NavItem; badge?: number }) => { const isActive = pathname === item.href || (item.href !== '/dashboard' && pathname.startsWith(item.href + '/')); return ( - {item.icon} + {item.icon} {item.label} {badge !== undefined && badge > 0 && ( @@ -186,10 +205,10 @@ export default function DashboardLayout({ children }: { children: React.ReactNod >
- โ˜๏ธ CloudHost + CloudHost
@@ -205,12 +224,10 @@ export default function DashboardLayout({ children }: { children: React.ReactNod onClick={() => setSidebarOpen(true)} className="btn-icon lg:hidden" > - - - + - โ˜๏ธ + CloudHost
@@ -233,9 +250,7 @@ export default function DashboardLayout({ children }: { children: React.ReactNod onClick={() => { logout(); router.push('/login'); }} className="btn-ghost text-gray-500 hover:text-red-600" > - - - + Sign out
diff --git a/frontend/src/app/dashboard/page.tsx b/frontend/src/app/dashboard/page.tsx index 5f94b86..2153b26 100644 --- a/frontend/src/app/dashboard/page.tsx +++ b/frontend/src/app/dashboard/page.tsx @@ -2,9 +2,11 @@ import { useQuery } from '@tanstack/react-query'; import Link from 'next/link'; +import type { ReactNode } from 'react'; import api from '@/lib/api'; import { useAuthStore } from '@/lib/store'; import type { Application } from '@/types'; +import { Rocket, Package, Circle, Hexagon } from 'lucide-react'; const statusColors: Record = { running: 'bg-emerald-100 text-emerald-700', @@ -16,9 +18,14 @@ const statusColors: Record = { stopped: 'bg-gray-100 text-gray-600', }; -const statusIcons: Record = { - running: '๐ŸŸข', pending: '๐ŸŸก', building: '๐Ÿ”ต', deploying: '๐Ÿ”ต', - failed: '๐Ÿ”ด', build_failed: '๐Ÿ”ด', stopped: 'โšช', +const statusIcons: Record = { + running: , + pending: , + building: , + deploying: , + failed: , + build_failed: , + stopped: , }; export default function DashboardPage() { @@ -41,7 +48,7 @@ export default function DashboardPage() { {/* Welcome */}

- Welcome back, {user?.firstName}! ๐Ÿ‘‹ + Welcome back, {user?.firstName}!

Here's an overview of your applications. @@ -75,7 +82,7 @@ export default function DashboardPage() {

Recent Applications

- ๐Ÿš€ New Application + New Application
@@ -94,13 +101,13 @@ export default function DashboardPage() {
) : apps.length === 0 ? (
-
๐Ÿ“ฆ
+

No applications yet

Deploy your first application to get started.

- - ๐Ÿš€ Deploy your first app + + Deploy your first app
) : ( @@ -115,8 +122,8 @@ export default function DashboardPage() { className="card-hover flex items-center justify-between group" >
-
- {app.runtime === 'nodejs' ? '๐ŸŸฉ' : '๐ŸŸง'} +
+

@@ -129,7 +136,7 @@ export default function DashboardPage() {

- {statusIcons[status]} + {statusIcons[status]} {status} diff --git a/frontend/src/app/dashboard/staff/tickets/page.tsx b/frontend/src/app/dashboard/staff/tickets/page.tsx index a7722e0..f7d1d18 100644 --- a/frontend/src/app/dashboard/staff/tickets/page.tsx +++ b/frontend/src/app/dashboard/staff/tickets/page.tsx @@ -6,6 +6,7 @@ import Link from 'next/link'; import api from '@/lib/api'; import { useAuthStore } from '@/lib/store'; import type { Ticket, TicketStatus } from '@/types'; +import { Wrench, Briefcase, User, CheckCircle } from 'lucide-react'; const statusColors: Record = { open: 'bg-yellow-100 text-yellow-700', @@ -26,7 +27,8 @@ export default function StaffTicketsPage() { // Determine which department this staff member handles const department = user?.role === 'sales' ? 'sales' : 'technical'; - const departmentLabel = department === 'technical' ? '๐Ÿ”ง Technical' : '๐Ÿ’ผ Sales'; + const departmentLabel = department === 'technical' ? 'Technical' : 'Sales'; + const DeptIcon = department === 'technical' ? Wrench : Briefcase; const { data: tickets = [], isLoading } = useQuery({ queryKey: ['staff-tickets', department, statusFilter], @@ -42,7 +44,7 @@ export default function StaffTicketsPage() { return (
-

{departmentLabel} Tickets

+

{departmentLabel} Tickets

{tickets.length} total tickets

@@ -95,7 +97,7 @@ export default function StaffTicketsPage() {
) : tickets.length === 0 ? (
- โœ… +

No tickets

{statusFilter ? `No ${statusFilter} tickets` : 'No tickets in this department'} @@ -122,8 +124,8 @@ export default function StaffTicketsPage() {

{ticket.user && ( - - ๐Ÿ‘ค {ticket.user.firstName} {ticket.user.lastName} ({ticket.user.email}) + + {ticket.user.firstName} {ticket.user.lastName} ({ticket.user.email}) )} โ€ข diff --git a/frontend/src/app/dashboard/tickets/[id]/page.tsx b/frontend/src/app/dashboard/tickets/[id]/page.tsx index 0cdbf8c..b9dbaf6 100644 --- a/frontend/src/app/dashboard/tickets/[id]/page.tsx +++ b/frontend/src/app/dashboard/tickets/[id]/page.tsx @@ -5,8 +5,9 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useParams, useRouter } from 'next/navigation'; import api from '@/lib/api'; import { useAuthStore } from '@/lib/store'; -import toast from 'react-hot-toast'; +import { toast } from 'react-toastify'; import type { Ticket } from '@/types'; +import { Wrench, Briefcase } from 'lucide-react'; const statusColors: Record = { open: 'bg-yellow-100 text-yellow-700', @@ -83,8 +84,8 @@ export default function TicketDetailPage() { {ticket.status} - - {ticket.department === 'technical' ? '๐Ÿ”ง Technical' : '๐Ÿ’ผ Sales'} + + {ticket.department === 'technical' ? <> Technical : <> Sales} {new Date(ticket.createdAt).toLocaleString()} diff --git a/frontend/src/app/dashboard/tickets/page.tsx b/frontend/src/app/dashboard/tickets/page.tsx index e13ed72..538aa0f 100644 --- a/frontend/src/app/dashboard/tickets/page.tsx +++ b/frontend/src/app/dashboard/tickets/page.tsx @@ -5,8 +5,9 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import Link from 'next/link'; import api from '@/lib/api'; import { useAuthStore } from '@/lib/store'; -import toast from 'react-hot-toast'; +import { toast } from 'react-toastify'; import type { Ticket, CreateTicketDto, TicketDepartment, TicketPriority } from '@/types'; +import { Wrench, Briefcase, Ticket as TicketIcon, X } from 'lucide-react'; const statusColors: Record = { open: 'bg-yellow-100 text-yellow-700', @@ -29,10 +30,10 @@ export default function TicketsPage() { // Staff can only create tickets to other departments const availableDepartments: { value: TicketDepartment; label: string }[] = []; if (user?.role !== 'technical') { - availableDepartments.push({ value: 'technical', label: '๐Ÿ”ง Technical Support' }); + availableDepartments.push({ value: 'technical', label: 'Technical Support' }); } if (user?.role !== 'sales') { - availableDepartments.push({ value: 'sales', label: '๐Ÿ’ผ Sales' }); + availableDepartments.push({ value: 'sales', label: 'Sales' }); } const canCreateTicket = availableDepartments.length > 0; const [form, setForm] = useState({ @@ -72,7 +73,7 @@ export default function TicketsPage() {
{canCreateTicket && ( )}
@@ -115,9 +116,9 @@ export default function TicketsPage() { value={form.priority} onChange={(e) => setForm({ ...form, priority: e.target.value as TicketPriority })} > - - - + + +
@@ -149,7 +150,7 @@ export default function TicketsPage() {
) : tickets.length === 0 ? (
- ๐ŸŽซ +

No tickets yet

Create a ticket if you need help

@@ -173,7 +174,7 @@ export default function TicketsPage() {
- {ticket.department === 'technical' ? '๐Ÿ”ง Technical' : '๐Ÿ’ผ Sales'} + {ticket.department === 'technical' ? <> Technical : <> Sales} โ€ข {new Date(ticket.createdAt).toLocaleDateString('en-US', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' })} โ€ข diff --git a/frontend/src/app/login/page.tsx b/frontend/src/app/login/page.tsx index 1cdb305..2ae0d0a 100644 --- a/frontend/src/app/login/page.tsx +++ b/frontend/src/app/login/page.tsx @@ -4,7 +4,8 @@ import { useState } from 'react'; import { useRouter } from 'next/navigation'; import Link from 'next/link'; import { useAuthStore } from '@/lib/store'; -import toast from 'react-hot-toast'; +import { toast } from 'react-toastify'; +import { Cloud } from 'lucide-react'; export default function LoginPage() { const [email, setEmail] = useState(''); @@ -32,7 +33,7 @@ export default function LoginPage() {
- โ˜๏ธ +

Welcome back

diff --git a/frontend/src/app/register/page.tsx b/frontend/src/app/register/page.tsx index e028292..2a8889f 100644 --- a/frontend/src/app/register/page.tsx +++ b/frontend/src/app/register/page.tsx @@ -4,7 +4,8 @@ import { useState } from 'react'; import { useRouter } from 'next/navigation'; import Link from 'next/link'; import { useAuthStore } from '@/lib/store'; -import toast from 'react-hot-toast'; +import { toast } from 'react-toastify'; +import { Cloud } from 'lucide-react'; export default function RegisterPage() { const [form, setForm] = useState({ email: '', password: '', firstName: '', lastName: '' }); @@ -31,7 +32,7 @@ export default function RegisterPage() {

- โ˜๏ธ +

Create your account

diff --git a/frontend/src/components/providers.tsx b/frontend/src/components/providers.tsx index b9e601b..1198858 100644 --- a/frontend/src/components/providers.tsx +++ b/frontend/src/components/providers.tsx @@ -1,7 +1,8 @@ 'use client'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { Toaster } from 'react-hot-toast'; +import { ToastContainer } from 'react-toastify'; +import 'react-toastify/dist/ReactToastify.css'; import { useEffect, useState } from 'react'; import { useAuthStore } from '@/lib/store'; @@ -28,7 +29,7 @@ export function Providers({ children }: { children: React.ReactNode }) { return ( {children} - + ); }