Skip to content

Commit 802de7f

Browse files
committed
feat(web): scaffold dashboard app with realtime job UI
1 parent 5892313 commit 802de7f

20 files changed

Lines changed: 2258 additions & 5 deletions

apps/web/index.html

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<meta name="description" content="StackForge — AI-powered full-stack project scaffolding. Describe your idea, pick your stack, and watch agents build your blueprint in real time." />
7+
<title>StackForge — AI Project Scaffolding</title>
8+
<link rel="preconnect" href="https://fonts.googleapis.com" />
9+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
10+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap" rel="stylesheet" />
11+
</head>
12+
<body>
13+
<div id="root"></div>
14+
<script type="module" src="/src/main.tsx"></script>
15+
</body>
16+
</html>

apps/web/package.json

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,24 @@
44
"private": true,
55
"type": "module",
66
"scripts": {
7-
"dev": "echo 'web: not implemented yet'",
8-
"build": "echo 'web: not implemented yet'",
9-
"lint": "echo 'web: not implemented yet'"
7+
"dev": "vite",
8+
"build": "tsc -b && vite build",
9+
"typecheck": "tsc --noEmit",
10+
"preview": "vite preview"
1011
},
1112
"dependencies": {
12-
"@stackforge/shared": "workspace:*"
13+
"@stackforge/ui": "workspace:*",
14+
"react": "^19.0.0",
15+
"react-dom": "^19.0.0",
16+
"react-router-dom": "^7.1.0"
17+
},
18+
"devDependencies": {
19+
"@tailwindcss/vite": "^4.1.0",
20+
"@types/react": "^19.0.0",
21+
"@types/react-dom": "^19.0.0",
22+
"@vitejs/plugin-react": "^4.3.0",
23+
"tailwindcss": "^4.1.0",
24+
"typescript": "^5.7.3",
25+
"vite": "^6.2.0"
1326
}
1427
}

apps/web/src/App.tsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import React from "react";
2+
import { BrowserRouter, Routes, Route } from "react-router-dom";
3+
import { ToastProvider } from "@stackforge/ui";
4+
import { Layout } from "./components/Layout";
5+
import { Home } from "./pages/Home";
6+
import { JobPage } from "./pages/JobPage";
7+
8+
export function App() {
9+
return (
10+
<BrowserRouter>
11+
<ToastProvider>
12+
<Layout>
13+
<Routes>
14+
<Route path="/" element={<Home />} />
15+
<Route path="/jobs/:jobId" element={<JobPage />} />
16+
</Routes>
17+
</Layout>
18+
</ToastProvider>
19+
</BrowserRouter>
20+
);
21+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import React from "react";
2+
import { Badge, Card, Spinner } from "@stackforge/ui";
3+
import type { AgentState } from "../hooks/useJobStream";
4+
5+
const AGENT_META: Record<string, { icon: string; label: string; description: string }> = {
6+
planner: { icon: "📋", label: "Planner", description: "Defines project structure and tech stack" },
7+
schema: { icon: "🗄️", label: "Schema", description: "Designs database entities and relationships" },
8+
api: { icon: "⚡", label: "API", description: "Plans API routes and endpoints" },
9+
frontend: { icon: "🎨", label: "Frontend", description: "Creates frontend pages and components" },
10+
devops: { icon: "🚀", label: "DevOps", description: "Sets up CI/CD, Docker, and deployment" },
11+
reviewer: { icon: "🔍", label: "Reviewer", description: "Reviews the entire blueprint for quality" },
12+
};
13+
14+
export function AgentCard({ agent }: { agent: AgentState }) {
15+
const meta = AGENT_META[agent.name] ?? { icon: "🤖", label: agent.name, description: "" };
16+
17+
const borderColor =
18+
agent.status === "running"
19+
? "rgba(56, 189, 248, 0.3)"
20+
: agent.status === "completed"
21+
? "rgba(52, 211, 153, 0.2)"
22+
: agent.status === "failed"
23+
? "rgba(244, 63, 94, 0.2)"
24+
: "#23232f";
25+
26+
return (
27+
<Card
28+
hover={false}
29+
style={{
30+
padding: "18px 20px",
31+
borderColor,
32+
borderRadius: "14px",
33+
animation: agent.status === "running" ? "pulse-glow 2s ease-in-out infinite" : undefined,
34+
transition: "all 350ms cubic-bezier(0.4, 0, 0.2, 1)",
35+
}}
36+
>
37+
<div style={{ display: "flex", alignItems: "center", gap: "14px" }}>
38+
{/* Icon */}
39+
<div
40+
style={{
41+
width: "42px",
42+
height: "42px",
43+
borderRadius: "12px",
44+
background:
45+
agent.status === "running"
46+
? "rgba(56, 189, 248, 0.1)"
47+
: agent.status === "completed"
48+
? "rgba(52, 211, 153, 0.1)"
49+
: "rgba(92, 92, 111, 0.1)",
50+
display: "flex",
51+
alignItems: "center",
52+
justifyContent: "center",
53+
fontSize: "20px",
54+
flexShrink: 0,
55+
}}
56+
>
57+
{agent.status === "running" ? <Spinner size={18} color="#38bdf8" /> : meta.icon}
58+
</div>
59+
60+
{/* Info */}
61+
<div style={{ flex: 1, minWidth: 0 }}>
62+
<div style={{ display: "flex", alignItems: "center", gap: "10px", marginBottom: "4px" }}>
63+
<span style={{ fontSize: "15px", fontWeight: 600 }}>{meta.label}</span>
64+
<Badge status={agent.status} />
65+
</div>
66+
<p style={{ fontSize: "13px", color: "#9898a8", margin: 0 }}>{meta.description}</p>
67+
</div>
68+
69+
{/* Duration / details */}
70+
<div style={{ textAlign: "right", flexShrink: 0 }}>
71+
{agent.durationMs != null && (
72+
<span style={{ fontSize: "13px", color: "#9898a8", fontVariantNumeric: "tabular-nums" }}>
73+
{(agent.durationMs / 1000).toFixed(1)}s
74+
</span>
75+
)}
76+
{agent.totalTokens != null && (
77+
<div style={{ fontSize: "11px", color: "#5c5c6f", marginTop: "2px" }}>
78+
{agent.totalTokens.toLocaleString()} tokens
79+
</div>
80+
)}
81+
{agent.error && (
82+
<span style={{ fontSize: "12px", color: "#f43f5e" }}>{agent.error}</span>
83+
)}
84+
</div>
85+
</div>
86+
</Card>
87+
);
88+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import React from "react";
2+
import { AgentCard } from "./AgentCard";
3+
import type { AgentState } from "../hooks/useJobStream";
4+
5+
export function AgentTimeline({ agents }: { agents: AgentState[] }) {
6+
return (
7+
<div style={{ position: "relative" }}>
8+
{/* Vertical connector line */}
9+
<div
10+
style={{
11+
position: "absolute",
12+
left: "39px",
13+
top: "24px",
14+
bottom: "24px",
15+
width: "2px",
16+
background: "linear-gradient(to bottom, #6366f1, #8b5cf6, #23232f)",
17+
borderRadius: "1px",
18+
zIndex: 0,
19+
}}
20+
/>
21+
22+
<div style={{ display: "flex", flexDirection: "column", gap: "12px", position: "relative", zIndex: 1 }}>
23+
{agents.map((agent, i) => (
24+
<div
25+
key={agent.name}
26+
className="animate-fade-in"
27+
style={{ animationDelay: `${i * 60}ms`, animationFillMode: "both" }}
28+
>
29+
<AgentCard agent={agent} />
30+
</div>
31+
))}
32+
</div>
33+
</div>
34+
);
35+
}

0 commit comments

Comments
 (0)