diff --git a/packages/app/src/components/dialog-custom-provider-form.ts b/packages/app/src/components/dialog-custom-provider-form.ts
index 92d235c3bc..e26dcb0971 100644
--- a/packages/app/src/components/dialog-custom-provider-form.ts
+++ b/packages/app/src/components/dialog-custom-provider-form.ts
@@ -34,7 +34,6 @@ export type FormState = {
apiKey: string
models: ModelRow[]
headers: HeaderRow[]
- saving: boolean
err: {
providerID?: string
name?: string
diff --git a/packages/app/src/components/dialog-custom-provider.test.ts b/packages/app/src/components/dialog-custom-provider.test.ts
index 8cfd78ebeb..07dd26ecd6 100644
--- a/packages/app/src/components/dialog-custom-provider.test.ts
+++ b/packages/app/src/components/dialog-custom-provider.test.ts
@@ -16,7 +16,6 @@ describe("validateCustomProvider", () => {
{ row: "h0", key: " X-Test ", value: " enabled ", err: {} },
{ row: "h1", key: "", value: "", err: {} },
],
- saving: false,
err: {},
},
t,
@@ -60,7 +59,6 @@ describe("validateCustomProvider", () => {
{ row: "h0", key: "Authorization", value: "one", err: {} },
{ row: "h1", key: "authorization", value: "two", err: {} },
],
- saving: false,
err: {},
},
t,
diff --git a/packages/app/src/components/dialog-custom-provider.tsx b/packages/app/src/components/dialog-custom-provider.tsx
index 1d24d66d3b..6f38651e6d 100644
--- a/packages/app/src/components/dialog-custom-provider.tsx
+++ b/packages/app/src/components/dialog-custom-provider.tsx
@@ -3,6 +3,7 @@ import { useDialog } from "@opencode-ai/ui/context/dialog"
import { Dialog } from "@opencode-ai/ui/dialog"
import { IconButton } from "@opencode-ai/ui/icon-button"
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
+import { useMutation } from "@tanstack/solid-query"
import { TextField } from "@opencode-ai/ui/text-field"
import { showToast } from "@opencode-ai/ui/toast"
import { batch, For } from "solid-js"
@@ -31,7 +32,6 @@ export function DialogCustomProvider(props: Props) {
apiKey: "",
models: [modelRow()],
headers: [headerRow()],
- saving: false,
err: {},
})
@@ -116,48 +116,49 @@ export function DialogCustomProvider(props: Props) {
return output.result
}
- const save = async (e: SubmitEvent) => {
- e.preventDefault()
- if (form.saving) return
+ const saveMutation = useMutation(() => ({
+ mutationFn: async (result: NonNullable>) => {
+ const disabledProviders = globalSync.data.config.disabled_providers ?? []
+ const nextDisabled = disabledProviders.filter((id) => id !== result.providerID)
- const result = validate()
- if (!result) return
-
- setForm("saving", true)
-
- const disabledProviders = globalSync.data.config.disabled_providers ?? []
- const nextDisabled = disabledProviders.filter((id) => id !== result.providerID)
-
- const auth = result.key
- ? globalSDK.client.auth.set({
+ if (result.key) {
+ await globalSDK.client.auth.set({
providerID: result.providerID,
auth: {
type: "api",
key: result.key,
},
})
- : Promise.resolve()
+ }
- auth
- .then(() =>
- globalSync.updateConfig({ provider: { [result.providerID]: result.config }, disabled_providers: nextDisabled }),
- )
- .then(() => {
- dialog.close()
- showToast({
- variant: "success",
- icon: "circle-check",
- title: language.t("provider.connect.toast.connected.title", { provider: result.name }),
- description: language.t("provider.connect.toast.connected.description", { provider: result.name }),
- })
+ await globalSync.updateConfig({
+ provider: { [result.providerID]: result.config },
+ disabled_providers: nextDisabled,
})
- .catch((err: unknown) => {
- const message = err instanceof Error ? err.message : String(err)
- showToast({ title: language.t("common.requestFailed"), description: message })
- })
- .finally(() => {
- setForm("saving", false)
+ return result
+ },
+ onSuccess: (result) => {
+ dialog.close()
+ showToast({
+ variant: "success",
+ icon: "circle-check",
+ title: language.t("provider.connect.toast.connected.title", { provider: result.name }),
+ description: language.t("provider.connect.toast.connected.description", { provider: result.name }),
})
+ },
+ onError: (err) => {
+ const message = err instanceof Error ? err.message : String(err)
+ showToast({ title: language.t("common.requestFailed"), description: message })
+ },
+ }))
+
+ const save = (e: SubmitEvent) => {
+ e.preventDefault()
+ if (saveMutation.isPending) return
+
+ const result = validate()
+ if (!result) return
+ saveMutation.mutate(result)
}
return (
@@ -312,8 +313,14 @@ export function DialogCustomProvider(props: Props) {
-
diff --git a/packages/app/src/components/dialog-edit-project.tsx b/packages/app/src/components/dialog-edit-project.tsx
index ec0793c540..eb962f47eb 100644
--- a/packages/app/src/components/dialog-edit-project.tsx
+++ b/packages/app/src/components/dialog-edit-project.tsx
@@ -2,6 +2,7 @@ import { Button } from "@opencode-ai/ui/button"
import { useDialog } from "@opencode-ai/ui/context/dialog"
import { Dialog } from "@opencode-ai/ui/dialog"
import { TextField } from "@opencode-ai/ui/text-field"
+import { useMutation } from "@tanstack/solid-query"
import { Icon } from "@opencode-ai/ui/icon"
import { createMemo, For, Show } from "solid-js"
import { createStore } from "solid-js/store"
@@ -28,7 +29,6 @@ export function DialogEditProject(props: { project: LocalProject }) {
color: props.project.icon?.color || "pink",
iconUrl: props.project.icon?.override || "",
startup: props.project.commands?.start ?? "",
- saving: false,
dragOver: false,
iconHover: false,
})
@@ -71,38 +71,37 @@ export function DialogEditProject(props: { project: LocalProject }) {
setStore("iconUrl", "")
}
- async function handleSubmit(e: SubmitEvent) {
- e.preventDefault()
+ const saveMutation = useMutation(() => ({
+ mutationFn: async () => {
+ const name = store.name.trim() === folderName() ? "" : store.name.trim()
+ const start = store.startup.trim()
- await Promise.resolve()
- .then(async () => {
- setStore("saving", true)
- const name = store.name.trim() === folderName() ? "" : store.name.trim()
- const start = store.startup.trim()
-
- if (props.project.id && props.project.id !== "global") {
- await globalSDK.client.project.update({
- projectID: props.project.id,
- directory: props.project.worktree,
- name,
- icon: { color: store.color, override: store.iconUrl },
- commands: { start },
- })
- globalSync.project.icon(props.project.worktree, store.iconUrl || undefined)
- dialog.close()
- return
- }
-
- globalSync.project.meta(props.project.worktree, {
+ if (props.project.id && props.project.id !== "global") {
+ await globalSDK.client.project.update({
+ projectID: props.project.id,
+ directory: props.project.worktree,
name,
- icon: { color: store.color, override: store.iconUrl || undefined },
- commands: { start: start || undefined },
+ icon: { color: store.color, override: store.iconUrl },
+ commands: { start },
})
+ globalSync.project.icon(props.project.worktree, store.iconUrl || undefined)
dialog.close()
+ return
+ }
+
+ globalSync.project.meta(props.project.worktree, {
+ name,
+ icon: { color: store.color, override: store.iconUrl || undefined },
+ commands: { start: start || undefined },
})
- .finally(() => {
- setStore("saving", false)
- })
+ dialog.close()
+ },
+ }))
+
+ function handleSubmit(e: SubmitEvent) {
+ e.preventDefault()
+ if (saveMutation.isPending) return
+ saveMutation.mutate()
}
return (
@@ -246,8 +245,8 @@ export function DialogEditProject(props: { project: LocalProject }) {