import { json, query, action, useParams, createAsync, useSubmission } from "@solidjs/router" import { createEffect, createSignal, For, Show } from "solid-js" import { withActor } from "~/context/auth.withActor" import { createStore } from "solid-js/store" import { formatDateUTC, formatDateForTable } from "./common" import styles from "./member-section.module.css" import { and, Database, eq, sql } from "@opencode/console-core/drizzle/index.js" import { UserTable, UserRole } from "@opencode/console-core/schema/user.sql.js" import { Identifier } from "@opencode/console-core/identifier.js" const removeMember = action(async (form: FormData) => { "use server" const id = form.get("id")?.toString() if (!id) return { error: "ID is required" } const workspaceID = form.get("workspaceID")?.toString() if (!workspaceID) return { error: "Workspace ID is required" } return json( await withActor( () => Database.use((tx) => tx .update(UserTable) .set({ timeDeleted: sql`now()` }) .where(and(eq(UserTable.id, id), eq(UserTable.workspaceID, workspaceID))), ), workspaceID, ), { revalidate: listMembers.key }, ) }, "member.remove") const inviteMember = action(async (form: FormData) => { "use server" const email = form.get("email")?.toString().trim() if (!email) return { error: "Email is required" } const workspaceID = form.get("workspaceID")?.toString() if (!workspaceID) return { error: "Workspace ID is required" } const role = form.get("role")?.toString() as (typeof UserRole)[number] if (!role) return { error: "Role is required" } return json( await withActor( () => Database.use((tx) => tx .insert(UserTable) .values({ id: Identifier.create("user"), name: "", email, workspaceID, role, }) .then((data) => ({ error: undefined, data })) .catch((e) => ({ error: e.message as string })), ), workspaceID, ), { revalidate: listMembers.key }, ) }, "member.create") const listMembers = query(async (workspaceID: string) => { "use server" return withActor( () => Database.use((tx) => tx.select().from(UserTable).where(eq(UserTable.workspaceID, workspaceID))), workspaceID, ) }, "member.list") export function MemberCreateForm() { const params = useParams() const submission = useSubmission(inviteMember) const [store, setStore] = createStore({ show: false }) let input: HTMLInputElement createEffect(() => { if (!submission.pending && submission.result && !submission.result.error) { hide() } }) function show() { // submission.clear() does not clear the result in some cases, ie. // 1. Create key with empty name => error shows // 2. Put in a key name and creates the key => form hides // 3. Click add key button again => form shows with the same error if // submission.clear() is called only once while (true) { submission.clear() if (!submission.result) break } setStore("show", true) input.focus() } function hide() { setStore("show", false) } return ( show()}> Invite Member } > (input = r)} data-component="input" name="email" type="text" placeholder="Enter email" /> Admin Can manage models, members, and billing Member Can only generate API keys for themselves {(err) => {err()}} hide()}> Cancel {submission.pending ? "Inviting..." : "Invite"} ) } export function MemberSection() { const params = useParams() const members = createAsync(() => listMembers(params.id)) return ( Members Manage your members for accessing opencode services. Invite a member to your workspace } > Email Role Joined {(member) => { return ( {member.email} {member.role} invited}> {formatDateForTable(member.timeSeen!)} Delete ) }} ) }
Can manage models, members, and billing
Can only generate API keys for themselves
Manage your members for accessing opencode services.
Invite a member to your workspace