Initial MobilityOps build pack (docs, contracts, scaffold)
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
FROM node:22-alpine AS build
|
||||
WORKDIR /app
|
||||
COPY package.json tsconfig.json vite.config.ts index.html ./
|
||||
COPY src ./src
|
||||
RUN npm install && npm run build
|
||||
|
||||
FROM nginx:1.27-alpine
|
||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||
COPY --from=build /app/dist /usr/share/nginx/html
|
||||
EXPOSE 80
|
||||
@@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="description" content="MobilityOps synthetic-data operations proof of concept" />
|
||||
<title>MobilityOps</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,22 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://api:8000;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
location /health {
|
||||
proxy_pass http://api:8000/health;
|
||||
}
|
||||
|
||||
location / {
|
||||
try_files $uri /index.html;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "mobilityops-web",
|
||||
"private": true,
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --host 0.0.0.0",
|
||||
"build": "tsc -b && vite build",
|
||||
"preview": "vite preview --host 0.0.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vitejs/plugin-react": "latest",
|
||||
"vite": "latest",
|
||||
"typescript": "latest",
|
||||
"react": "latest",
|
||||
"react-dom": "latest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "latest",
|
||||
"@types/react-dom": "latest"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
const API = import.meta.env.VITE_API_BASE_URL ?? "";
|
||||
|
||||
type Status = {
|
||||
service: string;
|
||||
environment: string;
|
||||
demo_mode: boolean;
|
||||
knowledge_provider: string;
|
||||
scaffold: boolean;
|
||||
};
|
||||
|
||||
export function App() {
|
||||
const [status, setStatus] = useState<Status | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
fetch(`${API}/api/v1/system/status`)
|
||||
.then((response) => {
|
||||
if (!response.ok) throw new Error(`API returned ${response.status}`);
|
||||
return response.json();
|
||||
})
|
||||
.then(setStatus)
|
||||
.catch((reason: unknown) => setError(reason instanceof Error ? reason.message : "Unknown error"));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<main className="shell">
|
||||
<section className="hero">
|
||||
<p className="eyebrow">Synthetic proof of concept</p>
|
||||
<h1>MobilityOps</h1>
|
||||
<p>Connected operations for vehicle rental and service teams.</p>
|
||||
</section>
|
||||
<section className="panel" aria-live="polite">
|
||||
<h2>Scaffold status</h2>
|
||||
{error && <p className="error">API unavailable: {error}</p>}
|
||||
{!error && !status && <p>Connecting to the MobilityOps API…</p>}
|
||||
{status && (
|
||||
<dl>
|
||||
<div><dt>Service</dt><dd>{status.service}</dd></div>
|
||||
<div><dt>Environment</dt><dd>{status.environment}</dd></div>
|
||||
<div><dt>Knowledge provider</dt><dd>{status.knowledge_provider}</dd></div>
|
||||
</dl>
|
||||
)}
|
||||
<p className="note">This is the bootable project scaffold. Claude must replace it with the complete scoped application described in the build pack.</p>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { App } from "./App";
|
||||
import "./styles.css";
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
);
|
||||
@@ -0,0 +1,19 @@
|
||||
:root {
|
||||
font-family: Inter, ui-sans-serif, system-ui, sans-serif;
|
||||
color: #172033;
|
||||
background: #f3f6fa;
|
||||
font-synthesis: none;
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
body { margin: 0; min-width: 320px; }
|
||||
.shell { width: min(920px, calc(100% - 32px)); margin: 0 auto; padding: 64px 0; }
|
||||
.hero { margin-bottom: 28px; }
|
||||
.eyebrow { color: #315d73; font-weight: 700; text-transform: uppercase; letter-spacing: .08em; font-size: .8rem; }
|
||||
h1 { margin: 6px 0; font-size: clamp(2.4rem, 7vw, 4.8rem); line-height: 1; }
|
||||
.hero > p:last-child { color: #526173; font-size: 1.2rem; }
|
||||
.panel { background: white; border: 1px solid #dce3eb; border-radius: 18px; padding: 24px; box-shadow: 0 10px 30px rgba(24,40,64,.06); }
|
||||
dl { display: grid; gap: 12px; }
|
||||
dl div { display: flex; justify-content: space-between; gap: 24px; border-bottom: 1px solid #edf1f5; padding-bottom: 10px; }
|
||||
dt { color: #607084; } dd { margin: 0; font-weight: 700; }
|
||||
.note { margin-top: 24px; color: #607084; }
|
||||
.error { color: #9a2530; }
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||
"allowJs": false,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": ["src"],
|
||||
"references": []
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { defineConfig } from "vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
});
|
||||
Reference in New Issue
Block a user