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)