db server and front end comms work card render basic works
This commit is contained in:
+18
-33
@@ -1,39 +1,24 @@
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { APITester } from "./APITester";
|
||||
import "./index.css";
|
||||
import { useEffect, useState } from "react";
|
||||
import StepCard from "./components/ui/StepCard";
|
||||
import type { FlowStep } from "./types"; // types only
|
||||
|
||||
import logo from "./logo.svg";
|
||||
import reactLogo from "./react.svg";
|
||||
export default function App() {
|
||||
const [steps, setSteps] = useState<FlowStep[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
fetch("http://localhost:3001/steps")
|
||||
.then(res => res.json())
|
||||
.then(data => setSteps(data))
|
||||
.catch(err => console.error("Failed to fetch steps:", err));
|
||||
}, []);
|
||||
|
||||
export function App() {
|
||||
return (
|
||||
<div className="container mx-auto p-8 text-center relative z-10">
|
||||
<div className="flex justify-center items-center gap-8 mb-8">
|
||||
<img
|
||||
src={logo}
|
||||
alt="Bun Logo"
|
||||
className="h-36 p-6 transition-all duration-300 hover:drop-shadow-[0_0_2em_#646cffaa] scale-120"
|
||||
/>
|
||||
<img
|
||||
src={reactLogo}
|
||||
alt="React Logo"
|
||||
className="h-36 p-6 transition-all duration-300 hover:drop-shadow-[0_0_2em_#61dafbaa] [animation:spin_20s_linear_infinite]"
|
||||
/>
|
||||
</div>
|
||||
<Card>
|
||||
<CardHeader className="gap-4">
|
||||
<CardTitle className="text-3xl font-bold">Bun + React</CardTitle>
|
||||
<CardDescription>
|
||||
Edit <code className="rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono">src/App.tsx</code> and save to
|
||||
test HMR
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<APITester />
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className="p-4 space-y-4">
|
||||
{steps.length === 0 ? (
|
||||
<p>Loading steps...</p>
|
||||
) : (
|
||||
steps.map(step => <StepCard key={step.id} step={step} />)
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import { useState } from "react";
|
||||
|
||||
// --- Types ---
|
||||
export type FieldType = {
|
||||
id: string;
|
||||
label: string;
|
||||
type: "textarea" | "array";
|
||||
};
|
||||
|
||||
export type Item = {
|
||||
id?: number;
|
||||
text?: string;
|
||||
followUp?: string;
|
||||
comment?: string;
|
||||
flagged?: boolean;
|
||||
};
|
||||
|
||||
// --- Props ---
|
||||
type DynamicArrayFieldProps = {
|
||||
field: FieldType;
|
||||
stepId: string; // keep for future use
|
||||
items: Item[];
|
||||
onAdd: (item: Item) => void;
|
||||
};
|
||||
|
||||
export default function DynamicArrayField({ field, stepId: _stepId, items, onAdd }: DynamicArrayFieldProps) {
|
||||
const [input, setInput] = useState("");
|
||||
|
||||
const handleAdd = () => {
|
||||
if (!input) return;
|
||||
onAdd({ text: input });
|
||||
setInput("");
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<label className="block font-medium">{field.label}</label>
|
||||
{items.map((item, index) => (
|
||||
<div key={index} className="p-1 border rounded">
|
||||
{item.text || item.comment}
|
||||
</div>
|
||||
))}
|
||||
<div className="flex space-x-2">
|
||||
<input
|
||||
type="text"
|
||||
value={input}
|
||||
onChange={(e) => setInput(e.target.value)}
|
||||
placeholder={`Add ${field.label}`}
|
||||
className="border rounded px-2 py-1 flex-1"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleAdd}
|
||||
className="bg-blue-500 text-white px-3 py-1 rounded"
|
||||
>
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import type{ FlowStep } from "../../types";
|
||||
|
||||
type StepCardProps = {
|
||||
step: FlowStep;
|
||||
};
|
||||
|
||||
export default function StepCard({ step }: StepCardProps) {
|
||||
return (
|
||||
<div className="border rounded-lg p-4 bg-white shadow-sm">
|
||||
<h2 className="font-bold text-lg">{step.stepNumber}: {step.description}</h2>
|
||||
<p className="text-sm opacity-70">
|
||||
Stage: {step.stage} | Role: {step.role} | Handoff: {step.handoffTo} via {step.handoffChannel}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -176,4 +176,3 @@ export function getConcern(stepId: string): Concern | null {
|
||||
};
|
||||
}
|
||||
|
||||
//export default db;
|
||||
|
||||
+4
-5
@@ -1,13 +1,12 @@
|
||||
<!doctype html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="icon" type="image/svg+xml" href="./logo.svg" />
|
||||
<title>Bun + React</title>
|
||||
<script type="module" src="./frontend.tsx" async></script>
|
||||
<title>StepCard Test</title>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</head>
|
||||
<body>
|
||||
<body class="bg-gray-100">
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import React from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import App from "./App";
|
||||
|
||||
const container = document.getElementById("root");
|
||||
if (!container) throw new Error("Root container not found");
|
||||
|
||||
// Only create root if it doesn't already exist (HMR safe)
|
||||
const root = (window as any)._root || createRoot(container);
|
||||
(window as any)._root = root;
|
||||
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
);
|
||||
@@ -0,0 +1,19 @@
|
||||
export const intakeStepSchema = [
|
||||
{
|
||||
id: "extendedDesc",
|
||||
label: "Extended Description",
|
||||
type: "textarea"
|
||||
},
|
||||
{
|
||||
id: "questions",
|
||||
label: "Questions",
|
||||
type: "array",
|
||||
itemLabel: "Question"
|
||||
},
|
||||
{
|
||||
id: "concerns",
|
||||
label: "Concerns",
|
||||
type: "array",
|
||||
itemLabel: "Concern"
|
||||
}
|
||||
] as const;
|
||||
+5
-61
@@ -1,4 +1,3 @@
|
||||
// src/server.ts
|
||||
import { serve } from "bun";
|
||||
import {
|
||||
createStep,
|
||||
@@ -10,7 +9,7 @@ import {
|
||||
getQuestions,
|
||||
flagConcern,
|
||||
getConcern
|
||||
} from "./db.ts";
|
||||
} from "./db.ts"; // server-only
|
||||
|
||||
const corsHeaders = {
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
@@ -27,79 +26,24 @@ async function getJSON(req: Request) {
|
||||
}
|
||||
|
||||
serve({
|
||||
port: 3000,
|
||||
port: 3001,
|
||||
fetch: async (req: Request) => {
|
||||
const { method } = req;
|
||||
const path = new URL(req.url).pathname;
|
||||
|
||||
if (method === "OPTIONS") {
|
||||
return new Response(null, { headers: corsHeaders });
|
||||
}
|
||||
if (method === "OPTIONS") return new Response(null, { headers: corsHeaders });
|
||||
|
||||
// --- Steps ---
|
||||
if (path === "/steps" && method === "GET") {
|
||||
if (path === "/steps" && method === "GET")
|
||||
return new Response(JSON.stringify(getAllSteps()), { headers: { ...corsHeaders, "Content-Type": "application/json" } });
|
||||
}
|
||||
|
||||
if (path.startsWith("/steps/") && method === "GET") {
|
||||
const stepId = String(path.split("/")[2]);
|
||||
return new Response(JSON.stringify(getStep(stepId)), { headers: { ...corsHeaders, "Content-Type": "application/json" } });
|
||||
}
|
||||
|
||||
if (path === "/steps" && method === "POST") {
|
||||
const data = await getJSON(req);
|
||||
createStep({
|
||||
id: String(data.id),
|
||||
stepNumber: String(data.stepNumber),
|
||||
caseCode: String(data.caseCode),
|
||||
subStep: String(data.subStep),
|
||||
stage: String(data.stage),
|
||||
role: String(data.role),
|
||||
handoffTo: String(data.handoffTo),
|
||||
handoffChannel: String(data.handoffChannel),
|
||||
description: String(data.description),
|
||||
});
|
||||
return new Response(JSON.stringify({ success: true }), { headers: { ...corsHeaders, "Content-Type": "application/json" } });
|
||||
}
|
||||
|
||||
// --- Extended Descriptions ---
|
||||
if (path === "/descriptions" && method === "POST") {
|
||||
const { stepId, content } = await getJSON(req);
|
||||
saveExtendedDescription(String(stepId), String(content));
|
||||
return new Response(JSON.stringify({ success: true }), { headers: { ...corsHeaders, "Content-Type": "application/json" } });
|
||||
}
|
||||
|
||||
if (path.startsWith("/descriptions/") && method === "GET") {
|
||||
const stepId = String(path.split("/")[2]);
|
||||
return new Response(JSON.stringify({ stepId, content: getExtendedDescription(stepId) }), { headers: { ...corsHeaders, "Content-Type": "application/json" } });
|
||||
}
|
||||
|
||||
// --- Questions ---
|
||||
if (path === "/questions" && method === "POST") {
|
||||
const { stepId, question, followUp } = await getJSON(req);
|
||||
addQuestion(String(stepId), String(question), followUp ? String(followUp) : null);
|
||||
return new Response(JSON.stringify({ success: true }), { headers: { ...corsHeaders, "Content-Type": "application/json" } });
|
||||
}
|
||||
|
||||
if (path.startsWith("/questions/") && method === "GET") {
|
||||
const stepId = String(path.split("/")[2]);
|
||||
return new Response(JSON.stringify(getQuestions(stepId)), { headers: { ...corsHeaders, "Content-Type": "application/json" } });
|
||||
}
|
||||
|
||||
// --- Concerns ---
|
||||
if (path === "/concerns" && method === "POST") {
|
||||
const { stepId, flagged, comment } = await getJSON(req);
|
||||
flagConcern(String(stepId), Boolean(flagged), String(comment));
|
||||
return new Response(JSON.stringify({ success: true }), { headers: { ...corsHeaders, "Content-Type": "application/json" } });
|
||||
}
|
||||
|
||||
if (path.startsWith("/concerns/") && method === "GET") {
|
||||
const stepId = String(path.split("/")[2]);
|
||||
return new Response(JSON.stringify(getConcern(stepId)), { headers: { ...corsHeaders, "Content-Type": "application/json" } });
|
||||
}
|
||||
|
||||
return new Response("Not Found", { status: 404 });
|
||||
}
|
||||
});
|
||||
|
||||
console.log("Server running at http://localhost:3000");
|
||||
console.log("Server running at http://localhost:3001");
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
// src/types.ts
|
||||
export type FlowStep = {
|
||||
id: string;
|
||||
stepNumber: string;
|
||||
caseCode: string;
|
||||
subStep: string;
|
||||
stage: string;
|
||||
role: string;
|
||||
handoffTo: string;
|
||||
handoffChannel: string;
|
||||
description: string;
|
||||
createdAt: string;
|
||||
};
|
||||
|
||||
export type Question = {
|
||||
id: number;
|
||||
stepId: string;
|
||||
question: string;
|
||||
followUp: string | null;
|
||||
createdAt: string;
|
||||
};
|
||||
|
||||
export type Concern = {
|
||||
stepId: string;
|
||||
flagged: boolean;
|
||||
comment: string;
|
||||
createdAt: string;
|
||||
};
|
||||
@@ -1,81 +0,0 @@
|
||||
// test-server.ts
|
||||
async function test() {
|
||||
const base = "http://localhost:3000";
|
||||
|
||||
// --- Create a step ---
|
||||
const stepData = {
|
||||
id: "step1",
|
||||
stepNumber: "0.1",
|
||||
caseCode: "caseA",
|
||||
subStep: "a",
|
||||
stage: "Pre-Intake",
|
||||
role: "Estimator",
|
||||
handoffTo: "Office Manager",
|
||||
handoffChannel: "Phone",
|
||||
description: "Initial ITB received",
|
||||
};
|
||||
|
||||
console.log("Creating step...");
|
||||
let res = await fetch(`${base}/steps`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(stepData),
|
||||
});
|
||||
console.log(await res.json());
|
||||
|
||||
// --- Get the step ---
|
||||
console.log("Getting step...");
|
||||
res = await fetch(`${base}/steps/${stepData.id}`);
|
||||
console.log(await res.json());
|
||||
|
||||
// --- Add extended description ---
|
||||
console.log("Adding extended description...");
|
||||
res = await fetch(`${base}/descriptions`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ stepId: stepData.id, content: "Extended info about step1" }),
|
||||
});
|
||||
console.log(await res.json());
|
||||
|
||||
// --- Get extended description ---
|
||||
console.log("Getting extended description...");
|
||||
res = await fetch(`${base}/descriptions/${stepData.id}`);
|
||||
console.log(await res.json());
|
||||
|
||||
// --- Add a question ---
|
||||
console.log("Adding question...");
|
||||
res = await fetch(`${base}/questions`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ stepId: stepData.id, question: "Follow up question?", followUp: "Yes, clarify details" }),
|
||||
});
|
||||
console.log(await res.json());
|
||||
|
||||
// --- Get questions ---
|
||||
console.log("Getting questions...");
|
||||
res = await fetch(`${base}/questions/${stepData.id}`);
|
||||
console.log(await res.json());
|
||||
|
||||
// --- Add a concern ---
|
||||
console.log("Adding concern...");
|
||||
res = await fetch(`${base}/concerns`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ stepId: stepData.id, flagged: true, comment: "Check this step for errors" }),
|
||||
});
|
||||
console.log(await res.json());
|
||||
|
||||
// --- Get concern ---
|
||||
console.log("Getting concern...");
|
||||
res = await fetch(`${base}/concerns/${stepData.id}`);
|
||||
console.log(await res.json());
|
||||
|
||||
// --- Get all steps ---
|
||||
console.log("Getting all steps...");
|
||||
res = await fetch(`${base}/steps`);
|
||||
console.log(await res.json());
|
||||
|
||||
console.log("Test complete.");
|
||||
}
|
||||
|
||||
test();
|
||||
|
||||
Reference in New Issue
Block a user