Files
opencode/packages/console/app/src/routes/workspace/settings-section.tsx
2025-10-06 17:13:19 -04:00

125 lines
4.0 KiB
TypeScript

import { json, action, useParams, useSubmission, createAsync, query } from "@solidjs/router"
import { createEffect, Show } from "solid-js"
import { createStore } from "solid-js/store"
import { withActor } from "~/context/auth.withActor"
import { Workspace } from "@opencode-ai/console-core/workspace.js"
import styles from "./settings-section.module.css"
import { Database, eq } from "@opencode-ai/console-core/drizzle/index.js"
import { WorkspaceTable } from "@opencode-ai/console-core/schema/workspace.sql.js"
const getWorkspaceInfo = query(async (workspaceID: string) => {
"use server"
return withActor(
() =>
Database.use((tx) =>
tx
.select({
id: WorkspaceTable.id,
name: WorkspaceTable.name,
slug: WorkspaceTable.slug,
})
.from(WorkspaceTable)
.where(eq(WorkspaceTable.id, workspaceID))
.then((rows) => rows[0] || null),
),
workspaceID,
)
}, "workspace.get")
const updateWorkspace = action(async (form: FormData) => {
"use server"
const name = form.get("name")?.toString().trim()
if (!name) return { error: "Workspace name is required." }
if (name.length > 255) return { error: "Name must be 255 characters or less." }
const workspaceID = form.get("workspaceID")?.toString()
if (!workspaceID) return { error: "Workspace ID is required." }
return json(
await withActor(
() =>
Workspace.update({ name })
.then(() => ({ error: undefined }))
.catch((e) => ({ error: e.message as string })),
workspaceID,
),
)
}, "workspace.update")
export function SettingsSection() {
const params = useParams()
const workspaceInfo = createAsync(() => getWorkspaceInfo(params.id))
const submission = useSubmission(updateWorkspace)
const [store, setStore] = createStore({ show: false })
let input: HTMLInputElement
createEffect(() => {
if (!submission.pending && submission.result && !submission.result.error) {
hide()
}
})
function show() {
while (true) {
submission.clear()
if (!submission.result) break
}
setStore("show", true)
input.focus()
}
function hide() {
setStore("show", false)
}
return (
<section class={styles.root}>
<div data-slot="section-title">
<h2>Settings</h2>
<p>Update your workspace name and preferences.</p>
</div>
<div data-slot="section-content">
<div data-slot="setting">
<div data-slot="setting-info">
<h3>Workspace Name</h3>
<p data-slot="current-value">{workspaceInfo()?.name}</p>
</div>
<Show
when={!store.show}
fallback={
<form action={updateWorkspace} method="post" data-slot="create-form">
<div data-slot="input-container">
<input
required
ref={(r) => (input = r)}
data-component="input"
name="name"
type="text"
placeholder="Workspace name"
value={workspaceInfo()?.name ?? "Default"}
/>
<Show when={submission.result && submission.result.error}>
{(err) => <div data-slot="form-error">{err()}</div>}
</Show>
</div>
<input type="hidden" name="workspaceID" value={params.id} />
<div data-slot="form-actions">
<button type="reset" data-color="ghost" onClick={() => hide()}>
Cancel
</button>
<button type="submit" data-color="primary" disabled={submission.pending}>
{submission.pending ? "Updating..." : "Update"}
</button>
</div>
</form>
}
>
<button data-color="primary" onClick={() => show()}>
Edit Name
</button>
</Show>
</div>
</div>
</section>
)
}