mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 14:16:12 +00:00
fix(custom-metrics): say "name taken" only for a name clash
The form read every 409 as "a metric with this name already exists". Now that it keeps one idempotency key across taps, a second tap while the first create is still running also answers 409, and the form told the person their name was taken. The create and rename routes send `customMetric.name_taken` with the clash, and the form matches the code.
This commit is contained in:
1 parent
671245c782
commit
706af61cdd
6 files changed
+14
-3
No files matched your search
@@ -225,6 +225,7 @@ describe("PATCH /api/custom-metrics/[id]", () => {
|
||||
|
||||
const res = await PATCH(patchReq({ name: "Other" }), params("cm-1"));
|
||||
expect(res.status).toBe(409);
|
||||
expect((await res.json()).meta.errorCode).toBe("customMetric.name_taken");
|
||||
expect(prisma.customMetric.update).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -111,7 +111,9 @@ export const PATCH = apiHandler(
|
||||
select: { id: true },
|
||||
});
|
||||
if (clash) {
|
||||
return apiError("A custom metric with this name already exists", 409);
|
||||
return apiError("A custom metric with this name already exists", 409, {
|
||||
errorCode: "customMetric.name_taken",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -134,6 +134,9 @@ describe("POST /api/custom-metrics", () => {
|
||||
|
||||
const res = await POST(postReq({ name: "Grip strength", unit: "kg" }));
|
||||
expect(res.status).toBe(409);
|
||||
// The form names the clash by this code: a 409 without it can be the
|
||||
// first save still running under the same key.
|
||||
expect((await res.json()).meta.errorCode).toBe("customMetric.name_taken");
|
||||
expect(prisma.customMetric.create).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
||||
@@ -100,7 +100,9 @@ async function postCustomMetric(request: NextRequest) {
|
||||
select: { id: true, deletedAt: true },
|
||||
});
|
||||
if (existing && existing.deletedAt === null) {
|
||||
return apiError("A custom metric with this name already exists", 409);
|
||||
return apiError("A custom metric with this name already exists", 409, {
|
||||
errorCode: "customMetric.name_taken",
|
||||
});
|
||||
}
|
||||
|
||||
// Field-by-field assignment — never spread `parsed.data`.
|
||||
|
||||
@@ -132,8 +132,10 @@ export function CustomMetricForm({
|
||||
);
|
||||
onSuccess?.(saved);
|
||||
} catch (err) {
|
||||
// By its code: a 409 can also be the first save still running.
|
||||
setError(
|
||||
err instanceof ApiError && err.status === 409
|
||||
err instanceof ApiError &&
|
||||
err.meta?.errorCode === "customMetric.name_taken"
|
||||
? t("customMetrics.form.nameTaken")
|
||||
: localizedApiError(err, t, "customMetrics.form.saveError"),
|
||||
);
|
||||
|
||||
@@ -140,6 +140,7 @@ export const ERROR_CODE_CATALOGUE: Readonly<Record<string, readonly string[]>> =
|
||||
"coach-prefs.body.invalid_shape",
|
||||
],
|
||||
consent: ["consent.ai.confirmRequired", "consent.ai.required"],
|
||||
customMetric: ["customMetric.name_taken"],
|
||||
cycle: [
|
||||
"cycle.bulk.invalid",
|
||||
"cycle.bulk.too_large",
|
||||
|
||||
Reference in new issue
Block a user