diff --git a/packages/user/src/components/Invitation/InvitationForm.tsx b/packages/user/src/components/Invitation/InvitationForm.tsx index eaf6b9a1c..fb9df5c3b 100644 --- a/packages/user/src/components/Invitation/InvitationForm.tsx +++ b/packages/user/src/components/Invitation/InvitationForm.tsx @@ -10,6 +10,7 @@ import { toast } from "react-toastify"; import * as zod from "zod"; import { addInvitation } from "@/api/invitation"; +import { INVITATION_ERRORS, SOMETHING_WRONG_ERROR } from "@/constants"; import { useConfig } from "@/hooks"; import { InvitationFormFields } from "./InvitationFormFields"; @@ -26,7 +27,7 @@ interface Properties { apps?: InvitationAppOption[]; expiryDateField?: InvitationExpiryDateField; onCancel?: () => void; - onSubmitted?: (response: AddInvitationResponse) => void; // afterSubmit + onSubmitted?: (response: AddInvitationResponse) => void; // eslint-disable-next-line @typescript-eslint/no-explicit-any prepareData?: (rawFormData: any) => any; roles?: InvitationRoleOption[]; @@ -42,11 +43,17 @@ export const InvitationForm = ({ roles, }: Properties) => { const { t, i18n } = useTranslation("invitations"); - const config = useConfig(); const [submitting, setSubmitting] = useState(false); - const [error, setError] = useState(false); + + // Stores the error code string (e.g. "USER_ALREADY_EXISTS_ERROR") + const [error, setError] = useState(null); + + // Stores dynamic values (email, role, app name) for the translation + const [errorParameters, setErrorParameters] = useState< + Record + >({}); const getDefaultValues = useCallback(() => { // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -56,7 +63,6 @@ export const InvitationForm = ({ if (apps?.length === 1) { const app = apps[0]; - defaultValues.app = app; filteredRoles = app.supportedRoles; } @@ -107,27 +113,69 @@ export const InvitationForm = ({ return parsedData; }; + const getErrorMessage = useCallback(() => { + switch (error) { + case INVITATION_ERRORS.INVALID_EMAIL: + return t("errors.invalidEmail", { + email: errorParameters?.email, + ns: "errors", + }); + + case INVITATION_ERRORS.INVITATION_ALREADY_EXISTS: + return t("errors.invitationAlreadyExists", { ns: "errors" }); + + case INVITATION_ERRORS.ROLE_NOT_FOUND: + return t("errors.roleNotFound", { + ns: "errors", + role: errorParameters?.role, + }); + + case INVITATION_ERRORS.ROLE_NOT_SUPPORTED: + return t("errors.roleNotSupported", { + app: errorParameters?.app, + ns: "errors", + }); + + case INVITATION_ERRORS.USER_ALREADY_EXISTS: + return t("errors.userAlreadyExists", { + email: errorParameters?.email, + ns: "errors", + }); + + default: + return t("errors.somethingWrong", { ns: "errors" }); + } + }, [error, errorParameters, t]); + // eslint-disable-next-line @typescript-eslint/no-explicit-any const onSubmit = (data: any) => { setSubmitting(true); + setError(null); const invitationData = prepareData ? prepareData(data) : getFormData(data); addInvitation(invitationData, config.apiBaseUrl) .then((response) => { - if ("data" in response && response.data.status === "ERROR") { - // TODO better handle errors - setError(true); - } else { - toast.success(t("messages.invite.success")); - - if (onSubmitted) { - onSubmitted(response); - } + // Success case + toast.success(t("messages.invite.success")); + + if (onSubmitted) { + onSubmitted(response); } }) - .catch(() => { - setError(true); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .catch((error: any) => { + const code = error?.response?.data?.code || SOMETHING_WRONG_ERROR; + + const selectedApp = apps?.find((app) => app.id === data.app)?.name; + + setErrorParameters({ + email: data.email, + role: data.role, + app: selectedApp || data.app, + }); + + setError(code); }) .finally(() => { setSubmitting(false); @@ -180,9 +228,9 @@ export const InvitationForm = ({ <> {error && ( { - setError(false); + setError(null); }} severity="danger" /> diff --git a/packages/user/src/constants.ts b/packages/user/src/constants.ts index 95116a657..b07084f8c 100644 --- a/packages/user/src/constants.ts +++ b/packages/user/src/constants.ts @@ -1,3 +1,5 @@ +export const SOMETHING_WRONG_ERROR = "SOMETHING_WRONG"; + export const SUPERTOKENS_API_BASE_PATH_DEFAULT = "/auth"; export enum DEFAULT_PATHS { @@ -18,6 +20,14 @@ export enum DEFAULT_PATHS { PROFILE = "/profile", } +export enum INVITATION_ERRORS { + INVALID_EMAIL = "INVALID_EMAIL_ERROR", + INVITATION_ALREADY_EXISTS = "INVITATION_ALREADY_EXISTS_ERROR", + ROLE_NOT_FOUND = "ROLE_NOT_FOUND_ERROR", + ROLE_NOT_SUPPORTED = "ROLE_NOT_SUPPORTED_ERROR", + USER_ALREADY_EXISTS = "USER_ALREADY_EXISTS_ERROR", +} + export enum EMAIL_VERIFICATION { EMAIL_ALREADY_VERIFIED = "EMAIL_ALREADY_VERIFIED", ERROR = "ERROR", diff --git a/packages/user/src/locales/en/errors.json b/packages/user/src/locales/en/errors.json index b3965bae6..fcded0349 100644 --- a/packages/user/src/locales/en/errors.json +++ b/packages/user/src/locales/en/errors.json @@ -2,7 +2,13 @@ "errors": { "401": "Invalid credentials. Please check your email or password and try again.", "emailAlreadyExists": "This email already exists. Please sign in instead.", - "incorrectPassword": "The current password you entered is incorrect.", - "otherErrors": "Oops! Something went wrong." + "incorrectPassword": "The current password you entered is incorrect", + "invalidEmail": "The provided email {{email}} is invalid.", + "invitationAlreadyExists": "Invitation already exists for this email.", + "otherErrors": "Oops! Something went wrong.", + "roleNotFound": "Role {{role}} does not exist", + "roleNotSupported": "App {{app}} does not support the role.", + "somethingWrong": "Something went wrong. Please try again.", + "userAlreadyExists": "User with email {{email}} already exists" } } diff --git a/packages/user/src/locales/fr/errors.json b/packages/user/src/locales/fr/errors.json index c8cf561fa..2158b462e 100644 --- a/packages/user/src/locales/fr/errors.json +++ b/packages/user/src/locales/fr/errors.json @@ -3,6 +3,12 @@ "401": "Invalid credentials. Please check your email or password and try again. (fr)", "emailAlreadyExists": "This email already exists. Please sign in instead. (fr)", "incorrectPassword": "The current password you entered is incorrect. (fr)", - "otherErrors": "Oops! Something went wrong (fr)." + "invalidEmail": "The provided email {{email}} is invalid. (fr)", + "invitationAlreadyExists": "Invitation already exists for this email. (fr)", + "otherErrors": "Oops! Something went wrong (fr).", + "roleNotFound": "Role {{role}} does not exist. (fr)", + "roleNotSupported": "App {{app}} does not support the role. (fr)", + "somethingWrong": "Something went wrong. Please try again. (fr)", + "userAlreadyExists": "User with email {{email}} already exists. (fr)" } }