diff --git a/bun.lockb b/bun.lockb
index 557d79c..5044326 100755
Binary files a/bun.lockb and b/bun.lockb differ
diff --git a/lib/middleware/with-db.ts b/lib/middleware/with-db.ts
index 5ae5826..5c22ae3 100644
--- a/lib/middleware/with-db.ts
+++ b/lib/middleware/with-db.ts
@@ -1,6 +1,6 @@
import type { DbClient } from "lib/db/db-client"
import { createDatabase } from "lib/db/db-client"
-import type { Middleware } from "winterspec"
+import type { Middleware } from "winterspec/middleware"
export const withDb: Middleware<
{},
diff --git a/lib/middleware/with-react.tsx b/lib/middleware/with-react.tsx
new file mode 100644
index 0000000..bbac2ce
--- /dev/null
+++ b/lib/middleware/with-react.tsx
@@ -0,0 +1,96 @@
+import React from "react"
+import { renderToString } from "react-dom/server"
+import type { Middleware } from "winterspec/middleware"
+import type { ReactNode } from "react"
+import { Fragment } from "react"
+
+const tailwindStyle = `
+.btn {
+ @apply text-white visited:text-white m-1 bg-blue-500 hover:bg-blue-700 font-bold py-2 px-4 rounded;
+}
+a {
+ @apply underline text-blue-600 hover:text-blue-800 visited:text-purple-800 m-1;
+}
+h2 {
+ @apply text-xl font-bold my-2;
+}
+input, select {
+ @apply border border-gray-300 rounded p-1 ml-0.5;
+}
+form {
+ @apply inline-flex flex-col gap-2 border border-gray-300 rounded p-2 m-2 text-xs;
+}
+button {
+ @apply bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded;
+}
+`
+
+const renderBreadcrumbs = (pathComponents: string[]) =>
+ pathComponents.map((component, index) => (
+
+ /
+
+ {component}
+
+
+ ))
+
+const renderTimezoneDropdown = (timezone: string) => (
+
+)
+
+export const withReact: Middleware<
+ {},
+ { react: (component: ReactNode) => Response }
+> = async (req, ctx, next) => {
+ ctx.react = (component: ReactNode) => {
+ const pathComponents = new URL(req.url).pathname.split("/").filter(Boolean)
+ const timezone = req.headers.get("X-Timezone") || "UTC"
+
+ const html = renderToString(
+
+
+
+
+
+
+
+
+
+ admin panel
+ {renderBreadcrumbs(pathComponents)}
+
+
+
+ {new Date().toLocaleString()}
+
+ {renderTimezoneDropdown(timezone)}
+
+
+
{component}
+
+
+
+ )
+
+ return new Response(html, {
+ headers: {
+ "Content-Type": "text/html",
+ },
+ })
+ }
+
+ return await next(req, ctx)
+}
diff --git a/lib/middleware/with-winter-spec.ts b/lib/middleware/with-winter-spec.ts
index a7bfd4b..2bacbf6 100644
--- a/lib/middleware/with-winter-spec.ts
+++ b/lib/middleware/with-winter-spec.ts
@@ -1,10 +1,12 @@
import { createWithWinterSpec } from "winterspec"
+import type { Middleware } from "winterspec/middleware"
import { withDb } from "./with-db"
+import { withReact } from "./with-react"
export const withRouteSpec = createWithWinterSpec({
apiName: "tscircuit Debug API",
productionServerUrl: "https://debug-api.tscircuit.com",
- beforeAuthMiddleware: [],
+ beforeAuthMiddleware: [withReact],
authMiddleware: {},
afterAuthMiddleware: [withDb],
})
diff --git a/package.json b/package.json
index d03438f..01bea02 100644
--- a/package.json
+++ b/package.json
@@ -5,7 +5,8 @@
"devDependencies": {
"@biomejs/biome": "^1.8.3",
"@types/bun": "latest",
- "@types/react": "18.3.4",
+ "@types/react": "^18.3.4",
+ "@types/react-dom": "^19.1.3",
"next": "^14.2.5",
"redaxios": "^0.5.1"
},
@@ -13,6 +14,9 @@
"typescript": "^5.0.0"
},
"dependencies": {
+ "openapi3-ts": "^4.4.0",
+ "react": "^19.1.0",
+ "react-dom": "^19.1.0",
"winterspec": "^0.0.86",
"zod": "^3.23.8",
"zustand": "^4.5.5",
diff --git a/routes/_fake/admin.tsx b/routes/_fake/admin.tsx
new file mode 100644
index 0000000..e3980cd
--- /dev/null
+++ b/routes/_fake/admin.tsx
@@ -0,0 +1,75 @@
+import React from "react"
+import { withRouteSpec } from "lib/middleware/with-winter-spec"
+import { z } from "zod"
+
+const AdminPage = ({ things }: { things: any[] }) => {
+ return (
+
+
Things Management
+
+
+
+
+
Existing Things
+
+ {things.map((thing) => (
+
+
+
{thing.name}
+
{thing.description}
+
+
+ ID: {thing.thing_id}
+
+
+ ))}
+
+
+
+
+ )
+}
+
+export default withRouteSpec({
+ methods: ["GET"],
+ jsonResponse: z.object({
+ things: z.array(
+ z.object({
+ thing_id: z.string(),
+ name: z.string(),
+ description: z.string(),
+ })
+ ),
+ }),
+})(async (req, ctx) => {
+ const things = ctx.db.things
+ return ctx.react()
+})