Skip to content

Commit 748e963

Browse files
committed
fix(webapp): surface project rename failures on the form
The two json({ errors: { body } }) returns aren't a conform SubmissionResult, so conform reset the form and discarded them — the last way to fail this form with nothing on screen. Return submission.reply({ formErrors }) and render the rename form's form-level errors, matching the delete form on the same page. Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 9453add commit 748e963

3 files changed

Lines changed: 25 additions & 4 deletions

File tree

.server-changes/fix-project-settings-toast.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ area: webapp
33
type: fix
44
---
55

6-
Renaming a project now keeps you on the project settings page and shows a message explaining the result, instead of silently moving you to the tasks page.
6+
Renaming a project now keeps you on the project settings page and tells you what happened, instead of silently moving you to the tasks page or clearing the form with no explanation.

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings.general/route.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ export const action = dashboardAction(
105105
);
106106

107107
if (membershipResultOrFail.isErr()) {
108-
return json({ errors: { body: membershipResultOrFail.error.type } }, { status: 404 });
108+
return json(submission.reply({ formErrors: ["Project not found"] }), { status: 404 });
109109
}
110110

111111
const { projectId } = membershipResultOrFail.value;
@@ -133,7 +133,9 @@ export const action = dashboardAction(
133133
logger.error("Failed to rename project", {
134134
error: resultOrFail.error,
135135
});
136-
return json({ errors: { body: "Failed to rename project" } }, { status: 400 });
136+
return json(submission.reply({ formErrors: ["Failed to rename project"] }), {
137+
status: 400,
138+
});
137139
}
138140
}
139141
}
@@ -257,6 +259,7 @@ export default function GeneralSettingsPage() {
257259
}}
258260
/>
259261
<FormError id={projectName.errorId}>{projectName.errors}</FormError>
262+
<FormError>{renameForm.errors}</FormError>
260263
</InputGroup>
261264
<FormButtons
262265
confirmButton={

apps/webapp/test/projectSettingsToastRedirect.test.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
// never renders the toast. The general settings action must therefore redirect to a page
55
// that renders.
66

7-
import { okAsync } from "neverthrow";
7+
import { errAsync, okAsync } from "neverthrow";
88
import { describe, expect, it, vi } from "vitest";
99
import { commitSession, getSession, redirectWithErrorMessage } from "~/models/message.server";
1010
import { action as generalSettingsAction } from "~/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings.general/route";
@@ -18,11 +18,16 @@ vi.mock("~/models/organization.server", () => ({
1818
resolveOrgIdFromSlug: vi.fn().mockResolvedValue("org_1"),
1919
}));
2020

21+
const renameFails = { value: false };
22+
2123
vi.mock("~/services/projectSettings.server", () => ({
2224
ProjectSettingsService: class {
2325
verifyProjectMembership() {
2426
return okAsync({ projectId: "proj_1" });
2527
}
28+
renameProject() {
29+
return renameFails.value ? errAsync({ type: "other" as const }) : okAsync(undefined);
30+
}
2631
deleteProject() {
2732
return okAsync(undefined);
2833
}
@@ -105,3 +110,16 @@ describe("general settings redirects target a page that renders", () => {
105110
expect(response.headers.get("Location")).toBe(ORG_PATH);
106111
});
107112
});
113+
114+
describe("general settings failures reach the form", () => {
115+
it("returns a form-level error when the rename fails", async () => {
116+
renameFails.value = true;
117+
const response = await runAction("rename", true);
118+
renameFails.value = false;
119+
120+
expect(response.status).toBe(400);
121+
expect(await response.json()).toMatchObject({
122+
error: { "": ["Failed to rename project"] },
123+
});
124+
});
125+
});

0 commit comments

Comments
 (0)