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 { useEffect, useState } from "react";
|
||||||
import { APITester } from "./APITester";
|
import StepCard from "./components/ui/StepCard";
|
||||||
import "./index.css";
|
import type { FlowStep } from "./types"; // types only
|
||||||
|
|
||||||
import logo from "./logo.svg";
|
export default function App() {
|
||||||
import reactLogo from "./react.svg";
|
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 (
|
return (
|
||||||
<div className="container mx-auto p-8 text-center relative z-10">
|
<div className="p-4 space-y-4">
|
||||||
<div className="flex justify-center items-center gap-8 mb-8">
|
{steps.length === 0 ? (
|
||||||
<img
|
<p>Loading steps...</p>
|
||||||
src={logo}
|
) : (
|
||||||
alt="Bun Logo"
|
steps.map(step => <StepCard key={step.id} step={step} />)
|
||||||
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>
|
</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">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<link rel="icon" type="image/svg+xml" href="./logo.svg" />
|
<title>StepCard Test</title>
|
||||||
<title>Bun + React</title>
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
<script type="module" src="./frontend.tsx" async></script>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body class="bg-gray-100">
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</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 { serve } from "bun";
|
||||||
import {
|
import {
|
||||||
createStep,
|
createStep,
|
||||||
@@ -10,7 +9,7 @@ import {
|
|||||||
getQuestions,
|
getQuestions,
|
||||||
flagConcern,
|
flagConcern,
|
||||||
getConcern
|
getConcern
|
||||||
} from "./db.ts";
|
} from "./db.ts"; // server-only
|
||||||
|
|
||||||
const corsHeaders = {
|
const corsHeaders = {
|
||||||
"Access-Control-Allow-Origin": "*",
|
"Access-Control-Allow-Origin": "*",
|
||||||
@@ -27,79 +26,24 @@ async function getJSON(req: Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
serve({
|
serve({
|
||||||
port: 3000,
|
port: 3001,
|
||||||
fetch: async (req: Request) => {
|
fetch: async (req: Request) => {
|
||||||
const { method } = req;
|
const { method } = req;
|
||||||
const path = new URL(req.url).pathname;
|
const path = new URL(req.url).pathname;
|
||||||
|
|
||||||
if (method === "OPTIONS") {
|
if (method === "OPTIONS") return new Response(null, { headers: corsHeaders });
|
||||||
return new Response(null, { headers: corsHeaders });
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Steps ---
|
// --- Steps ---
|
||||||
if (path === "/steps" && method === "GET") {
|
if (path === "/steps" && method === "GET")
|
||||||
return new Response(JSON.stringify(getAllSteps()), { headers: { ...corsHeaders, "Content-Type": "application/json" } });
|
return new Response(JSON.stringify(getAllSteps()), { headers: { ...corsHeaders, "Content-Type": "application/json" } });
|
||||||
}
|
|
||||||
|
|
||||||
if (path.startsWith("/steps/") && method === "GET") {
|
if (path.startsWith("/steps/") && method === "GET") {
|
||||||
const stepId = String(path.split("/")[2]);
|
const stepId = String(path.split("/")[2]);
|
||||||
return new Response(JSON.stringify(getStep(stepId)), { headers: { ...corsHeaders, "Content-Type": "application/json" } });
|
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 });
|
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