From 2e3cc0b8e60d2de0d6ca6df593f9bd53359287ce Mon Sep 17 00:00:00 2001 From: Pranav Bhat Date: Mon, 30 Sep 2024 22:30:42 +0530 Subject: [PATCH 1/3] fix issue #45: enhance error messages for user feedback updated error toast notifications to display specific user feedback based on backend response --- .../components/categories/CategoryDialog.tsx | 5 +- .../content-types/ContentTypeDialog.tsx | 4 +- .../src/components/contents/ContentDialog.tsx | 4 +- .../context-vars/ContextVarDialog.tsx | 4 +- frontend/src/components/roles/RoleDialog.tsx | 4 +- frontend/src/components/roles/index.tsx | 4 +- .../src/components/translations/index.tsx | 4 +- frontend/src/hooks/useToast.ts | 69 ++++++++++--------- frontend/src/i18n/en/translation.json | 1 + frontend/src/i18n/fr/translation.json | 1 + 10 files changed, 55 insertions(+), 45 deletions(-) diff --git a/frontend/src/components/categories/CategoryDialog.tsx b/frontend/src/components/categories/CategoryDialog.tsx index 9aa81e3..053a28a 100644 --- a/frontend/src/components/categories/CategoryDialog.tsx +++ b/frontend/src/components/categories/CategoryDialog.tsx @@ -35,8 +35,9 @@ export const CategoryDialog: FC = ({ const { t } = useTranslation(); const { toast } = useToast(); const { mutateAsync: createCategory } = useCreate(EntityType.CATEGORY, { - onError: () => { - toast.error(t("message.internal_server_error")); + onError: (error) => { + console.log(error); + toast.error(error); }, onSuccess: () => { closeDialog(); diff --git a/frontend/src/components/content-types/ContentTypeDialog.tsx b/frontend/src/components/content-types/ContentTypeDialog.tsx index bf914cd..41902c0 100644 --- a/frontend/src/components/content-types/ContentTypeDialog.tsx +++ b/frontend/src/components/content-types/ContentTypeDialog.tsx @@ -50,8 +50,8 @@ export const ContentTypeDialog: FC = ({ const { mutateAsync: createContentType } = useCreate( EntityType.CONTENT_TYPE, { - onError: () => { - toast.error(t("message.internal_server_error")); + onError: (error) => { + toast.error(error); }, onSuccess: () => { closeDialog(); diff --git a/frontend/src/components/contents/ContentDialog.tsx b/frontend/src/components/contents/ContentDialog.tsx index 3bf6f08..6cfd303 100644 --- a/frontend/src/components/contents/ContentDialog.tsx +++ b/frontend/src/components/contents/ContentDialog.tsx @@ -178,8 +178,8 @@ export const ContentDialog: FC = ({ createContent( { ...params, entity: contentType.id }, { - onError: () => { - toast.error(t("message.internal_server_error")); + onError: (error) => { + toast.error(error); }, onSuccess: () => { closeDialog(); diff --git a/frontend/src/components/context-vars/ContextVarDialog.tsx b/frontend/src/components/context-vars/ContextVarDialog.tsx index 5a39618..5d6499e 100644 --- a/frontend/src/components/context-vars/ContextVarDialog.tsx +++ b/frontend/src/components/context-vars/ContextVarDialog.tsx @@ -35,8 +35,8 @@ export const ContextVarDialog: FC = ({ const { t } = useTranslation(); const { toast } = useToast(); const { mutateAsync: createContextVar } = useCreate(EntityType.CONTEXT_VAR, { - onError: () => { - toast.error(t("message.internal_server_error")); + onError: (error) => { + toast.error(error); }, onSuccess() { closeDialog(); diff --git a/frontend/src/components/roles/RoleDialog.tsx b/frontend/src/components/roles/RoleDialog.tsx index 95ea303..86f154f 100644 --- a/frontend/src/components/roles/RoleDialog.tsx +++ b/frontend/src/components/roles/RoleDialog.tsx @@ -34,8 +34,8 @@ export const RoleDialog: FC = ({ const { t } = useTranslation(); const { toast } = useToast(); const { mutateAsync: createRole } = useCreate(EntityType.ROLE, { - onError: () => { - toast.error(t("message.internal_server_error")); + onError: (error) => { + toast.error(error); }, onSuccess() { closeDialog(); diff --git a/frontend/src/components/roles/index.tsx b/frontend/src/components/roles/index.tsx index 380728f..c9225c6 100644 --- a/frontend/src/components/roles/index.tsx +++ b/frontend/src/components/roles/index.tsx @@ -57,8 +57,8 @@ export const Roles = () => { }, ); const { mutateAsync: deleteRole } = useDelete(EntityType.ROLE, { - onError: () => { - toast.error(t("message.internal_server_error")); + onError: (error) => { + toast.error(error); }, onSuccess() { deleteDialogCtl.closeDialog(); diff --git a/frontend/src/components/translations/index.tsx b/frontend/src/components/translations/index.tsx index 7f44a62..f1651c2 100644 --- a/frontend/src/components/translations/index.tsx +++ b/frontend/src/components/translations/index.tsx @@ -56,8 +56,8 @@ export const Translations = () => { }, ); const { mutateAsync: deleteTranslation } = useDelete(EntityType.TRANSLATION, { - onError: () => { - toast.error(t("message.internal_server_error")); + onError: (error) => { + toast.error(error); }, onSuccess() { deleteDialogCtl.closeDialog(); diff --git a/frontend/src/hooks/useToast.ts b/frontend/src/hooks/useToast.ts index 6e0201b..64bd304 100644 --- a/frontend/src/hooks/useToast.ts +++ b/frontend/src/hooks/useToast.ts @@ -1,18 +1,11 @@ -/* - * Copyright © 2024 Hexastack. All rights reserved. - * - * Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms: - * 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission. - * 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file). - * 3. SaaS Restriction: This software, or any derivative of it, may not be used to offer a competing product or service (SaaS) without prior written consent from Hexastack. Offering the software as a service or using it in a commercial cloud environment without express permission is strictly prohibited. - */ - import { OptionsObject, enqueueSnackbar, SnackbarProvider as ToastProvider, } from "notistack"; +import { useTranslation } from "react-i18next"; + export { ToastProvider }; const TOAST_COMMON_STYLE = { @@ -39,25 +32,39 @@ const TOAST_WARNING_STYLE = { backgroundColor: "#fdf6ec", }; -export const useToast = () => ({ - toast: { - error: (message: string, options?: OptionsObject<"error">) => - enqueueSnackbar(message, { - variant: "error", - ...options, - style: { ...TOAST_ERROR_STYLE, ...options?.style }, - }), - success: (message: string, options?: OptionsObject<"success">) => - enqueueSnackbar(message, { - variant: "success", - ...options, - style: { ...TOAST_SUCCESS_STYLE, ...options?.style }, - }), - warning: (message: string, options?: OptionsObject<"warning">) => - enqueueSnackbar(message, { - variant: "warning", - ...options, - style: { ...TOAST_WARNING_STYLE, ...options?.style }, - }), - }, -}); +export const useToast = () => { + const { t } = useTranslation(); + + const extractErrorMessage = (error: any) => { + if (error?.statusCode == 409) { + return t("message.duplicate_error"); + } + + return error?.message || t("message.internal_server_error"); + }; + + return { + toast: { + error: (error: any, options?: OptionsObject<"error">) => { + const errorMessage = extractErrorMessage(error); + enqueueSnackbar(errorMessage, { + variant: "error", + ...options, + style: { ...TOAST_ERROR_STYLE, ...options?.style }, + }); + }, + success: (message: string, options?: OptionsObject<"success">) => + enqueueSnackbar(message, { + variant: "success", + ...options, + style: { ...TOAST_SUCCESS_STYLE, ...options?.style }, + }), + warning: (message: string, options?: OptionsObject<"warning">) => + enqueueSnackbar(message, { + variant: "warning", + ...options, + style: { ...TOAST_WARNING_STYLE, ...options?.style }, + }), + }, + }; +}; diff --git a/frontend/src/i18n/en/translation.json b/frontend/src/i18n/en/translation.json index fc34769..b1d356f 100644 --- a/frontend/src/i18n/en/translation.json +++ b/frontend/src/i18n/en/translation.json @@ -1,5 +1,6 @@ { "message": { + "duplicate_error": "Duplicate entry. Please choose a unique value.", "bad_request": "400 BAD REQUEST", "unable_to_process_request": "Unable to process request", "not_found": "404 NOT FOUND", diff --git a/frontend/src/i18n/fr/translation.json b/frontend/src/i18n/fr/translation.json index d61fc43..583099c 100644 --- a/frontend/src/i18n/fr/translation.json +++ b/frontend/src/i18n/fr/translation.json @@ -1,5 +1,6 @@ { "message": { + "duplicate_error" : "Entrée en double. Veuillez choisir une valeur unique.", "bad_request": "400 MAUVAISE REQUÊTE", "unable_to_process_request": "Impossible de traiter la requête", "not_found": "404 RESSOURCE INTROUVABLE", From b24464974a2ef3bde9f7fcac1f4afe11b9d56703 Mon Sep 17 00:00:00 2001 From: Pranav Bhat Date: Tue, 1 Oct 2024 00:23:38 +0530 Subject: [PATCH 2/3] Address reviewer feedback --- frontend/src/components/categories/CategoryDialog.tsx | 1 - frontend/src/hooks/useToast.ts | 7 +++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/categories/CategoryDialog.tsx b/frontend/src/components/categories/CategoryDialog.tsx index 12b8174..7e8a43c 100644 --- a/frontend/src/components/categories/CategoryDialog.tsx +++ b/frontend/src/components/categories/CategoryDialog.tsx @@ -35,7 +35,6 @@ export const CategoryDialog: FC = ({ const { toast } = useToast(); const { mutateAsync: createCategory } = useCreate(EntityType.CATEGORY, { onError: (error) => { - console.log(error); toast.error(error); }, onSuccess: () => { diff --git a/frontend/src/hooks/useToast.ts b/frontend/src/hooks/useToast.ts index 64bd304..1f8f82c 100644 --- a/frontend/src/hooks/useToast.ts +++ b/frontend/src/hooks/useToast.ts @@ -1,3 +1,10 @@ +/* + * Copyright © 2024 Hexastack. All rights reserved. + * + * Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms: + * 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission. + * 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file). + */ import { OptionsObject, enqueueSnackbar, From 3c715a593374638d278bb850db7e43f34505c141 Mon Sep 17 00:00:00 2001 From: Pranav Bhat <63770398+PranavBhatP@users.noreply.github.com> Date: Tue, 1 Oct 2024 20:01:40 +0530 Subject: [PATCH 3/3] Update frontend/src/components/roles/index.tsx Co-authored-by: Yassine <95612053+yassinedorbozgithub@users.noreply.github.com> --- frontend/src/components/roles/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/components/roles/index.tsx b/frontend/src/components/roles/index.tsx index c9225c6..be3434e 100644 --- a/frontend/src/components/roles/index.tsx +++ b/frontend/src/components/roles/index.tsx @@ -4,7 +4,6 @@ * Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms: * 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission. * 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file). - * 3. SaaS Restriction: This software, or any derivative of it, may not be used to offer a competing product or service (SaaS) without prior written consent from Hexastack. Offering the software as a service or using it in a commercial cloud environment without express permission is strictly prohibited. */ import { faUniversalAccess } from "@fortawesome/free-solid-svg-icons";