diff --git a/src/router/routes.tsx b/src/router/routes.tsx
index f9bd39c..c71a5cc 100644
--- a/src/router/routes.tsx
+++ b/src/router/routes.tsx
@@ -101,7 +101,20 @@ export const routes: RouteObject[] = [
},
{
path: 'apps',
- element:
,
+ children: [
+ {
+ index: true,
+ element:
,
+ },
+ {
+ path: 'available',
+ element:
,
+ },
+ {
+ path: 'installed',
+ element:
,
+ },
+ ],
},
{
path: 'advanced',
diff --git a/wild-web-app/src/components/apps/appUtils.tsx b/wild-web-app/src/components/apps/appUtils.tsx
new file mode 100644
index 0000000..c2d7125
--- /dev/null
+++ b/wild-web-app/src/components/apps/appUtils.tsx
@@ -0,0 +1,111 @@
+import { useState } from 'react';
+import {
+ AppWindow,
+ Database,
+ Globe,
+ Shield,
+ BarChart3,
+ MessageSquare,
+ CheckCircle,
+ AlertCircle,
+ Download,
+ Loader2,
+ Settings,
+} from 'lucide-react';
+import { Badge } from '../ui/badge';
+import type { App } from '../../services/api';
+
+export interface MergedApp extends App {
+ deploymentStatus?: 'added' | 'deployed';
+ url?: string;
+}
+
+export function getStatusIcon(status?: string) {
+ switch (status) {
+ case 'running':
+ return
;
+ case 'error':
+ return
;
+ case 'deploying':
+ return
;
+ case 'stopped':
+ return
;
+ case 'added':
+ return
;
+ case 'deployed':
+ return
;
+ case 'available':
+ return
;
+ default:
+ return null;
+ }
+}
+
+export function getStatusBadge(app: MergedApp) {
+ // Determine status: runtime status > deployment status > available
+ const status = app.status?.status || app.deploymentStatus || 'available';
+
+ const variants: Record
= {
+ available: 'secondary',
+ added: 'outline',
+ deploying: 'default',
+ running: 'success',
+ error: 'destructive',
+ stopped: 'warning',
+ deployed: 'outline',
+ };
+
+ const labels: Record = {
+ available: 'Available',
+ added: 'Added',
+ deploying: 'Deploying',
+ running: 'Running',
+ error: 'Error',
+ stopped: 'Stopped',
+ deployed: 'Deployed',
+ };
+
+ return (
+
+ {labels[status] || status}
+
+ );
+}
+
+export function getCategoryIcon(category?: string) {
+ switch (category) {
+ case 'database':
+ return ;
+ case 'web':
+ return ;
+ case 'security':
+ return ;
+ case 'monitoring':
+ return ;
+ case 'communication':
+ return ;
+ case 'storage':
+ return ;
+ default:
+ return ;
+ }
+}
+
+export function AppIcon({ app }: { app: MergedApp }) {
+ const [imageError, setImageError] = useState(false);
+
+ return (
+
+ {app.icon && !imageError ? (
+

setImageError(true)}
+ />
+ ) : (
+ getCategoryIcon(app.category)
+ )}
+
+ );
+}