diff --git a/package.json b/package.json index 1a7a2f22..e4c3e625 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "react": "^17.0.1", "react-dom": "^17.0.1", "react-dropzone": "^11.4.2", + "react-hot-toast": "^2.1.1", "react-lazyload": "^3.2.0", "react-masonry-css": "^1.0.16", "react-popper": "^2.2.3", diff --git a/src/constants/dom/index.ts b/src/constants/dom/index.ts new file mode 100644 index 00000000..7e58eff9 --- /dev/null +++ b/src/constants/dom/index.ts @@ -0,0 +1 @@ +export const isTablet = () => window.innerWidth < 599; diff --git a/src/containers/App.tsx b/src/containers/App.tsx index ea207916..388670c2 100644 --- a/src/containers/App.tsx +++ b/src/containers/App.tsx @@ -13,6 +13,8 @@ import { SWRConfigProvider } from '~/utils/providers/SWRConfigProvider'; import { observer } from 'mobx-react'; import { useGlobalLoader } from '~/hooks/dom/useGlobalLoader'; import { SearchProvider } from '~/utils/providers/SearchProvider'; +import { Toaster } from 'react-hot-toast'; +import { ToastProvider } from '~/utils/providers/ToastProvider'; const App: VFC = observer(() => { useGlobalLoader(); @@ -25,6 +27,7 @@ const App: VFC = observer(() => { + diff --git a/src/hooks/comments/useCommentFormFormik.ts b/src/hooks/comments/useCommentFormFormik.ts index 84e792f4..3bb869a5 100644 --- a/src/hooks/comments/useCommentFormFormik.ts +++ b/src/hooks/comments/useCommentFormFormik.ts @@ -44,7 +44,6 @@ export const useCommentFormFormik = ( await sendData(values); onSuccess(helpers)(); } catch (error) { - console.log('error', error); onSuccess(helpers)(error); } }, diff --git a/src/hooks/node/useNodeFormFormik.ts b/src/hooks/node/useNodeFormFormik.ts index 00b43af4..0312f67a 100644 --- a/src/hooks/node/useNodeFormFormik.ts +++ b/src/hooks/node/useNodeFormFormik.ts @@ -8,14 +8,13 @@ import { showErrorToast } from '~/utils/errors/showToast'; const validationSchema = object().shape({}); -const afterSubmit = ({ resetForm, setStatus, setSubmitting, setErrors }: FormikHelpers) => ( +const afterSubmit = ({ resetForm, setSubmitting, setErrors }: FormikHelpers) => ( e?: string, errors?: Record ) => { setSubmitting(false); if (e) { - setStatus(e); showErrorToast(e); return; } diff --git a/src/utils/errors/showToast.ts b/src/utils/errors/showToast.ts index 2b2cd335..fa0f6992 100644 --- a/src/utils/errors/showToast.ts +++ b/src/utils/errors/showToast.ts @@ -1,8 +1,21 @@ -const handle = (message: string) => console.warn(message); +import { hideToast, showToastError } from '~/utils/toast'; +import { has, path } from 'ramda'; +import { ERROR_LITERAL, ERRORS } from '~/constants/errors'; + +let toastId = ''; + +const handleUnknown = (message: string) => console.warn(message); +const handleTranslated = (message: string) => { + if (toastId) { + hideToast(toastId); + } + + toastId = showToastError(ERROR_LITERAL[message]); +}; export const showErrorToast = (error: unknown) => { - if (typeof error === 'string') { - handle(error); + if (typeof error === 'string' && has(error, ERROR_LITERAL)) { + handleTranslated(error); return; } @@ -11,5 +24,17 @@ export const showErrorToast = (error: unknown) => { return; } - handle(error.message); + // TODO: Network error + if (error.message === 'Network Error') { + handleTranslated(ERRORS.NETWORK_ERROR); + return; + } + + const messageFromBackend = String(path(['response', 'data', 'error'], error)); + if (messageFromBackend && has(messageFromBackend, ERROR_LITERAL)) { + handleTranslated(messageFromBackend); + return; + } + + handleUnknown(error.message); }; diff --git a/src/utils/providers/ToastProvider.tsx b/src/utils/providers/ToastProvider.tsx new file mode 100644 index 00000000..bc8d46ee --- /dev/null +++ b/src/utils/providers/ToastProvider.tsx @@ -0,0 +1,11 @@ +import { Toaster } from 'react-hot-toast'; +import React from 'react'; + +const containerStyle = { + top: 10, + left: 10, + bottom: 10, + right: 10, +}; + +export const ToastProvider = () => ; diff --git a/src/utils/toast/index.tsx b/src/utils/toast/index.tsx new file mode 100644 index 00000000..a53812de --- /dev/null +++ b/src/utils/toast/index.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import toast from 'react-hot-toast'; +import styles from './styles.module.scss'; +import { ToastOptions } from 'react-hot-toast/dist/core/types'; +import classNames from 'classnames'; +import { isTablet } from '~/constants/dom'; + +const defaultOptions: ToastOptions = { + icon: null, + duration: 3000, + position: isTablet() ? 'top-center' : 'bottom-center', +}; + +export const showToastError = (message: string) => + toast.error(t => toast.dismiss(t.id)}>{message}, { + ...defaultOptions, + className: classNames(styles.toast, styles.error), + }); + +export const hideToast = (id: string) => toast.dismiss(id); diff --git a/src/utils/toast/styles.module.scss b/src/utils/toast/styles.module.scss new file mode 100644 index 00000000..71bfde4e --- /dev/null +++ b/src/utils/toast/styles.module.scss @@ -0,0 +1,14 @@ +@import "src/styles/variables"; + +.toast { + @include outer_shadow; + cursor: pointer; +} + +.error { + font: $font_14_semibold; + background: $red_gradient_alt; + color: white; + user-select: none; + text-transform: uppercase; +} diff --git a/yarn.lock b/yarn.lock index af27d920..7b30944d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5390,6 +5390,11 @@ globule@^1.0.0: lodash "~4.17.10" minimatch "~3.0.2" +goober@^2.0.35: + version "2.1.1" + resolved "https://registry.yarnpkg.com/goober/-/goober-2.1.1.tgz#2328a6dae015c3cd30fc55a70090037a244ad2f6" + integrity sha512-TkGCqHxE4g5DtdpwxFCi53bXRtvw0BoSgCihVSIOioe9kfkqin5wXG8BQKykN0tjzmxZJ81qU2KWinZf5qKVlw== + graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.2: version "4.2.4" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" @@ -9429,6 +9434,13 @@ react-fast-compare@^3.0.1: resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb" integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA== +react-hot-toast@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/react-hot-toast/-/react-hot-toast-2.1.1.tgz#56409ab406b534e9e58274cf98d80355ba0fdda0" + integrity sha512-Odrp4wue0fHh0pOfZt5H+9nWCMtqs3wdlFSzZPp7qsxfzmbE26QmGWIh6hG43CukiPeOjA8WQhBJU8JwtWvWbQ== + dependencies: + goober "^2.0.35" + react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"