Skip to content

Commit 617296a

Browse files
committed
fix(webapp): scope the settings form error to the submitted form
Both conform forms on the project general settings page read the same action result, and a SubmissionResult carries no form identity, so a failed rename rendered its form-level error under the delete box too. Echo the submitted intent back as `formAction` and gate each form's lastResult on it, matching the test task page. Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 748e963 commit 617296a

2 files changed

Lines changed: 39 additions & 8 deletions

File tree

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

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,17 @@ function createSchema(
6060
]);
6161
}
6262

63+
type FormAction = "rename" | "delete";
64+
65+
export function submissionFor(lastSubmission: unknown, formAction: FormAction) {
66+
return lastSubmission &&
67+
typeof lastSubmission === "object" &&
68+
"formAction" in lastSubmission &&
69+
lastSubmission.formAction === formAction
70+
? lastSubmission
71+
: undefined;
72+
}
73+
6374
const Params = z.object({
6475
organizationSlug: z.string(),
6576
projectParam: z.string(),
@@ -85,6 +96,7 @@ export const action = dashboardAction(
8596
);
8697

8798
const formData = await request.formData();
99+
const formAction = formData.get("action") as FormAction;
88100

89101
const schema = createSchema({
90102
getSlugMatch: (slug) => {
@@ -94,7 +106,7 @@ export const action = dashboardAction(
94106
const submission = parseWithZod(formData, { schema });
95107

96108
if (submission.status !== "success") {
97-
return json(submission.reply());
109+
return json({ ...submission.reply(), formAction });
98110
}
99111

100112
const projectSettingsService = new ProjectSettingsService();
@@ -105,7 +117,10 @@ export const action = dashboardAction(
105117
);
106118

107119
if (membershipResultOrFail.isErr()) {
108-
return json(submission.reply({ formErrors: ["Project not found"] }), { status: 404 });
120+
return json(
121+
{ ...submission.reply({ formErrors: ["Project not found"] }), formAction },
122+
{ status: 404 }
123+
);
109124
}
110125

111126
const { projectId } = membershipResultOrFail.value;
@@ -133,9 +148,10 @@ export const action = dashboardAction(
133148
logger.error("Failed to rename project", {
134149
error: resultOrFail.error,
135150
});
136-
return json(submission.reply({ formErrors: ["Failed to rename project"] }), {
137-
status: 400,
138-
});
151+
return json(
152+
{ ...submission.reply({ formErrors: ["Failed to rename project"] }), formAction },
153+
{ status: 400 }
154+
);
139155
}
140156
}
141157
}
@@ -194,7 +210,7 @@ export default function GeneralSettingsPage() {
194210
const [renameForm, { projectName }] = useForm({
195211
id: "rename-project",
196212
// TODO: type this
197-
lastResult: lastSubmission as any,
213+
lastResult: submissionFor(lastSubmission, "rename") as any,
198214
shouldRevalidate: "onSubmit",
199215
onValidate({ formData }) {
200216
return parseWithZod(formData, {
@@ -210,7 +226,7 @@ export default function GeneralSettingsPage() {
210226
const [deleteForm, { projectSlug }] = useForm({
211227
id: "delete-project",
212228
// TODO: type this
213-
lastResult: lastSubmission as any,
229+
lastResult: submissionFor(lastSubmission, "delete") as any,
214230
shouldValidate: "onInput",
215231
shouldRevalidate: "onSubmit",
216232
onValidate({ formData }) {

apps/webapp/test/projectSettingsToastRedirect.test.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@
77
import { errAsync, okAsync } from "neverthrow";
88
import { describe, expect, it, vi } from "vitest";
99
import { commitSession, getSession, redirectWithErrorMessage } from "~/models/message.server";
10-
import { action as generalSettingsAction } from "~/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings.general/route";
10+
import {
11+
action as generalSettingsAction,
12+
submissionFor,
13+
} from "~/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings.general/route";
1114

1215
vi.mock("~/services/routeBuilders/dashboardBuilder", () => ({
1316
dashboardAction: (_options: unknown, handler: unknown) => handler,
@@ -122,4 +125,16 @@ describe("general settings failures reach the form", () => {
122125
error: { "": ["Failed to rename project"] },
123126
});
124127
});
128+
129+
// A SubmissionResult carries no form identity, so both forms would otherwise show it.
130+
it("scopes the rename failure to the rename form", async () => {
131+
renameFails.value = true;
132+
const response = await runAction("rename", true);
133+
renameFails.value = false;
134+
135+
const result = await response.json();
136+
137+
expect(submissionFor(result, "rename")).toEqual(result);
138+
expect(submissionFor(result, "delete")).toBeUndefined();
139+
});
125140
});

0 commit comments

Comments
 (0)