From 37ae87fe9baddf7ca4ed1304021215d689331f61 Mon Sep 17 00:00:00 2001 From: Adrien de Peretti Date: Thu, 23 May 2024 12:36:45 +0200 Subject: [PATCH] fix(admin-next, utils, medusa): Handle db errors properly and fix form (#7416) * fix(admin-next, utils, medusa): Handle db errors properly and fix form * fix(admin-next, utils, medusa): Handle db errors properly and fix form * fix(admin-next, utils, medusa): Handle db errors properly and fix form * fix(admin-next, utils, medusa): Handle db errors properly and fix form --- .../dashboard/src/lib/is-fetch-error.ts | 5 +++ .../invite-user-form/invite-user-form.tsx | 33 ++++++++++++++----- packages/core/js-sdk/src/client.ts | 12 +++++-- packages/core/js-sdk/src/index.ts | 1 + .../src/dal/mikro-orm/db-error-mapper.ts | 2 +- .../medusa/src/api/admin/invites/route.ts | 8 ++++- 6 files changed, 47 insertions(+), 14 deletions(-) create mode 100644 packages/admin-next/dashboard/src/lib/is-fetch-error.ts diff --git a/packages/admin-next/dashboard/src/lib/is-fetch-error.ts b/packages/admin-next/dashboard/src/lib/is-fetch-error.ts new file mode 100644 index 0000000000..fb33246ede --- /dev/null +++ b/packages/admin-next/dashboard/src/lib/is-fetch-error.ts @@ -0,0 +1,5 @@ +import { FetchError } from "@medusajs/js-sdk" + +export const isFetchError = (error: any): error is FetchError => { + return error instanceof FetchError +} diff --git a/packages/admin-next/dashboard/src/routes/users/user-invite/components/invite-user-form/invite-user-form.tsx b/packages/admin-next/dashboard/src/routes/users/user-invite/components/invite-user-form/invite-user-form.tsx index 564930cc0e..3cc36668c0 100644 --- a/packages/admin-next/dashboard/src/routes/users/user-invite/components/invite-user-form/invite-user-form.tsx +++ b/packages/admin-next/dashboard/src/routes/users/user-invite/components/invite-user-form/invite-user-form.tsx @@ -2,6 +2,7 @@ import { zodResolver } from "@hookform/resolvers/zod" import { ArrowPath, Trash } from "@medusajs/icons" import { InviteDTO } from "@medusajs/types" import { + Alert, Button, Container, Heading, @@ -29,6 +30,7 @@ import { } from "../../../../../hooks/api/invites" import { DataTable } from "../../../../../components/table/data-table" import { useUserInviteTableQuery } from "../../../../../hooks/table/query/use-user-invite-table-query" +import { isFetchError } from "../../../../../lib/is-fetch-error.ts" const InviteUserSchema = zod.object({ email: zod.string().email(), @@ -74,16 +76,18 @@ export const InviteUserForm = () => { const { mutateAsync, isPending } = useCreateInvite() const handleSubmit = form.handleSubmit(async (values) => { - await mutateAsync( - { - email: values.email, - }, - { - onSuccess: () => { - form.reset() - }, + try { + await mutateAsync({ email: values.email }) + form.reset() + } catch (error) { + if (isFetchError(error) && error.status === 400) { + form.setError("root", { + type: "manual", + message: error.message, + }) + return } - ) + } }) if (isError) { @@ -106,6 +110,17 @@ export const InviteUserForm = () => { {t("users.inviteUserHint")} + + {form.formState.errors.root && ( + + {form.formState.errors.root.message} + + )} +
{ if (resp.status >= 300) { - const error = new FetchError(resp.statusText, resp.status) - throw error + const jsonError = await resp.json().catch(() => ({})) as { message?: string } + throw new FetchError( + jsonError.message ?? resp.statusText, + resp.statusText, + resp.status + ) } // If we requested JSON, we try to parse the response. Otherwise, we return the raw response. @@ -56,9 +60,11 @@ const normalizeResponse = async (resp: Response, reqHeaders: Headers) => { export class FetchError extends Error { status: number | undefined + statusText: string | undefined - constructor(message: string, status?: number) { + constructor(message: string, statusText?: string, status?: number) { super(message) + this.statusText = statusText this.status = status } } diff --git a/packages/core/js-sdk/src/index.ts b/packages/core/js-sdk/src/index.ts index 03592cdf6e..9ad987dadb 100644 --- a/packages/core/js-sdk/src/index.ts +++ b/packages/core/js-sdk/src/index.ts @@ -21,3 +21,4 @@ class Medusa { } export default Medusa +export { FetchError } from "./client" diff --git a/packages/core/utils/src/dal/mikro-orm/db-error-mapper.ts b/packages/core/utils/src/dal/mikro-orm/db-error-mapper.ts index e93a68f51f..7d2ff301fe 100644 --- a/packages/core/utils/src/dal/mikro-orm/db-error-mapper.ts +++ b/packages/core/utils/src/dal/mikro-orm/db-error-mapper.ts @@ -74,7 +74,7 @@ const getConstraintInfo = (err: any) => { return null } - const [keys, values] = detail.match(/\([^\(.]*\)/g) || [] + const [keys, values] = detail.match(/\([^\(]*\)/g) || [] if (!keys || !values) { return null diff --git a/packages/medusa/src/api/admin/invites/route.ts b/packages/medusa/src/api/admin/invites/route.ts index 21941782ad..aa8399a3ba 100644 --- a/packages/medusa/src/api/admin/invites/route.ts +++ b/packages/medusa/src/api/admin/invites/route.ts @@ -45,9 +45,15 @@ export const POST = async ( input: { invites: [req.validatedBody], }, + throwOnError: false, + } + + const { result, errors } = await workflow.run(input) + + if (errors?.length) { + throw errors[0].error } - const { result } = await workflow.run(input) const invite = await refetchInvite( result[0].id, req.scope,