Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/middleware/with-db.ts
Original file line number Diff line number Diff line change
@@ -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<
{},
Expand Down
139 changes: 139 additions & 0 deletions lib/middleware/with-react.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import { renderToString } from "react-dom/server"
import type { Middleware } from "winterspec/middleware"
import type { ReactNode } from "react"
import { createElement } from "react"

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"
return new Response(
renderToString(
createElement(
"html",
{ lang: "en" },
createElement(
"head",
null,
createElement("script", { src: "https://cdn.tailwindcss.com" }),
createElement("style", {
type: "text/tailwindcss",
dangerouslySetInnerHTML: {
__html: `
.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
}
`,
},
})
),
createElement(
"body",
null,
createElement(
"div",
null,
createElement(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why would you do this instead of making this a tsx file?

"div",
{
className:
"border-b border-gray-300 py-1 flex justify-between items-center",
},
createElement(
"div",
null,
createElement(
"span",
{ className: "px-1 pr-2" },
"admin panel"
),
pathComponents.map((component, index) =>
createElement(
"span",
{ key: index },
createElement(
"span",
{ className: "px-0.5 text-gray-500" },
"/"
),
createElement(
"a",
{
href: `/${pathComponents
.slice(0, index + 1)
.join("/")}`,
},
component
)
)
)
),
createElement(
"div",
{ className: "mr-2 flex items-center" },
createElement(
"div",
{ className: "text-xs text-gray-500 mr-1" },
new Date().toLocaleString()
),
createElement("div", {
dangerouslySetInnerHTML: {
__html: `
<select
id="timezone-select"
class="text-xs"
value="${timezone}"
onchange="document.cookie = 'timezone=' + this.value + ';path=/'; location.reload();"
>
<option ${
timezone === "UTC" ? "selected" : ""
} value="UTC">UTC</option>
<option ${
timezone === "America/Los_Angeles" ? "selected" : ""
} value="America/Los_Angeles">Pacific</option>
<option ${
timezone === "Asia/Kolkata" ? "selected" : ""
} value="Asia/Kolkata">IST</option>
</select>
`,
},
})
)
),
createElement(
"div",
{ className: "flex flex-col text-xs p-1" },
component
)
)
)
)
),
{
headers: {
"Content-Type": "text/html",
},
}
)
}

return await next(req, ctx)
}
4 changes: 3 additions & 1 deletion lib/middleware/with-winter-spec.ts
Original file line number Diff line number Diff line change
@@ -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],
})
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,18 @@
"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"
},
"peerDependencies": {
"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",
Expand Down
75 changes: 75 additions & 0 deletions routes/_fake/admin.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div>
<h2>Things Management</h2>
<div className="flex flex-col gap-4">
<form
action="/things/create"
method="POST"
className="border p-4 rounded"
>
<h3 className="font-bold mb-2">Create New Thing</h3>
<div className="flex flex-col gap-2">
<input
type="text"
name="name"
placeholder="Name"
required
className="border p-2 rounded"
/>
<input
type="text"
name="description"
placeholder="Description"
required
className="border p-2 rounded"
/>
<button type="submit" className="btn">
Create Thing
</button>
</div>
</form>

<div className="border p-4 rounded">
<h3 className="font-bold mb-2">Existing Things</h3>
<div className="grid gap-2">
{things.map((thing) => (
<div
key={thing.thing_id}
className="border p-2 rounded flex justify-between items-center"
>
<div>
<div className="font-bold">{thing.name}</div>
<div className="text-gray-600">{thing.description}</div>
</div>
<div className="text-xs text-gray-500">
ID: {thing.thing_id}
</div>
</div>
))}
</div>
</div>
</div>
</div>
)
}

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(<AdminPage things={things} />)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

incorrect route spec, surprised this works

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is no jsonResponse

})
Loading