T1/Phase 1: scaffold Vite SPA + Hono API + Bun worker
- apps/web: Vite + React 19 + TanStack Router/Query + shadcn/ui - apps/api: Hono + Bun on :3001 with /api/health, /api/auth/*, /mcp stubs - apps/worker: Bun worker stub, DB connection, graceful SIGTERM - apps/web-legacy/: old Next.js code moved aside (preserved for T2-T8 reference) - Dockerfiles: api (Bun), worker (Bun), spa (multi-stage Caddy) - Caddyfile: serves dist + reverse-proxies /api/* + /mcp to api - docker-compose.yml: 4-service target (api, spa, db, worker) - packages/db/src/client.ts: shared Drizzle client for api + worker - db/client.ts: root-level alias for convenience Parent: t_e1cbd87d -> t_24c9c3fd (T0)
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
import { createRootRoute, Outlet } from "@tanstack/react-router";
|
||||
|
||||
export const Route = createRootRoute({
|
||||
component: () => (
|
||||
<div className="min-h-screen bg-background text-foreground">
|
||||
<Outlet />
|
||||
</div>
|
||||
),
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
import { createRoute } from "@tanstack/react-router";
|
||||
import { Route as rootRoute } from "./__root";
|
||||
|
||||
function HomePage() {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center min-h-screen p-8">
|
||||
<h1 className="text-4xl font-bold mb-4">Project E — UI Redesign</h1>
|
||||
<p className="text-lg text-muted-foreground">Phase 1 Scaffold</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const Route = createRoute({
|
||||
getParentRoute: () => rootRoute,
|
||||
path: "/",
|
||||
component: HomePage,
|
||||
});
|
||||
@@ -0,0 +1,76 @@
|
||||
import { createRoute, useNavigate } from "@tanstack/react-router";
|
||||
import { Route as rootRoute } from "./__root";
|
||||
import { useState } from "react";
|
||||
|
||||
function LoginPage() {
|
||||
const navigate = useNavigate();
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [error, setError] = useState("");
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setError("");
|
||||
try {
|
||||
const res = await fetch("/api/auth/credentials", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ email, password }),
|
||||
});
|
||||
if (!res.ok) {
|
||||
const data = await res.json();
|
||||
setError(data.message || "Login failed");
|
||||
return;
|
||||
}
|
||||
navigate({ to: "/" });
|
||||
} catch {
|
||||
setError("Network error");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-screen">
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
className="w-full max-w-sm p-8 space-y-4 border rounded-lg"
|
||||
>
|
||||
<h1 className="text-2xl font-bold text-center">Sign In</h1>
|
||||
{error && (
|
||||
<p className="text-sm text-destructive text-center">{error}</p>
|
||||
)}
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">Email</label>
|
||||
<input
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
className="w-full px-3 py-2 border rounded-md bg-background"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">Password</label>
|
||||
<input
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
className="w-full px-3 py-2 border rounded-md bg-background"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
className="w-full py-2 px-4 bg-primary text-primary-foreground rounded-md hover:opacity-90"
|
||||
>
|
||||
Sign In
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const Route = createRoute({
|
||||
getParentRoute: () => rootRoute,
|
||||
path: "/login",
|
||||
component: LoginPage,
|
||||
});
|
||||
Reference in New Issue
Block a user