diff --git a/package-lock.json b/package-lock.json index 46769d8b4..0db110917 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3587,6 +3587,14 @@ "csstype": "^2.2.0" } }, + "@types/react-alert": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@types/react-alert/-/react-alert-5.2.0.tgz", + "integrity": "sha512-1cyu/vQ4X0CJVC3X4CrJHRBc/9BvTqOF9FH6iy/4TLwPng++GC4ftxS/UaM91FqsKjao7pPtqxUlW1cRafNHLw==", + "requires": { + "@types/react": "*" + } + }, "@types/react-dom": { "version": "16.9.4", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.4.tgz", @@ -11582,8 +11590,7 @@ }, "ansi-regex": { "version": "2.1.1", - "bundled": true, - "optional": true + "bundled": true }, "aproba": { "version": "1.2.0", @@ -11601,13 +11608,11 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true, - "optional": true + "bundled": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -11620,18 +11625,15 @@ }, "code-point-at": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "concat-map": { "version": "0.0.1", - "bundled": true, - "optional": true + "bundled": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "core-util-is": { "version": "1.0.2", @@ -11734,8 +11736,7 @@ }, "inherits": { "version": "2.0.3", - "bundled": true, - "optional": true + "bundled": true }, "ini": { "version": "1.3.5", @@ -11745,7 +11746,6 @@ "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -11758,20 +11758,17 @@ "minimatch": { "version": "3.0.4", "bundled": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.8", - "bundled": true, - "optional": true + "bundled": true }, "minipass": { "version": "2.3.5", "bundled": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -11788,7 +11785,6 @@ "mkdirp": { "version": "0.5.1", "bundled": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -11861,8 +11857,7 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true, - "optional": true + "bundled": true }, "object-assign": { "version": "4.1.1", @@ -11872,7 +11867,6 @@ "once": { "version": "1.4.0", "bundled": true, - "optional": true, "requires": { "wrappy": "1" } @@ -11948,8 +11942,7 @@ }, "safe-buffer": { "version": "5.1.2", - "bundled": true, - "optional": true + "bundled": true }, "safer-buffer": { "version": "2.1.2", @@ -11979,7 +11972,6 @@ "string-width": { "version": "1.0.2", "bundled": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -11997,7 +11989,6 @@ "strip-ansi": { "version": "3.0.1", "bundled": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -12036,13 +12027,11 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true, - "optional": true + "bundled": true }, "yallist": { "version": "3.0.3", - "bundled": true, - "optional": true + "bundled": true } } }, @@ -17687,6 +17676,15 @@ "prop-types": "^15.6.2" } }, + "react-alert": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/react-alert/-/react-alert-7.0.1.tgz", + "integrity": "sha512-pHtMtP8gFQ4hLVQ/j7HIaayjvVNoMgoZHCag8YYBUpsLcamj0+435PJdZR7oh7AVbW0C2JPvNmS5+Md1Y59oew==", + "requires": { + "prop-types": "^15.7.2", + "react-transition-group": "^4.3.0" + } + }, "react-apollo": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/react-apollo/-/react-apollo-3.1.5.tgz", diff --git a/package.json b/package.json index 9c25281c6..7bb7e173a 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "moment-timezone": "^0.5.26", "qs": "^6.9.0", "react": "^16.12.0", + "react-alert": "^7.0.1", "react-apollo": "^3.1.4", "react-dom": "^16.9.0", "react-dropzone": "^8.2.0", @@ -100,6 +101,7 @@ "@types/react-sortable-hoc": "^0.7.1", "@types/react-sortable-tree": "^0.3.6", "@types/react-test-renderer": "^16.8.2", + "@types/react-alert": "^5.2.0", "@types/semver-compare": "^1.0.1", "@types/storybook__addon-storyshots": "^3.4.9", "@types/storybook__react": "^4.0.2", diff --git a/src/components/messages/MessageManager.tsx b/src/components/messages/MessageManager.tsx index a09ccf52b..cfe811814 100644 --- a/src/components/messages/MessageManager.tsx +++ b/src/components/messages/MessageManager.tsx @@ -1,91 +1,37 @@ import Button from "@material-ui/core/Button"; import IconButton from "@material-ui/core/IconButton"; -import Snackbar from "@material-ui/core/Snackbar"; +import SnackbarContent from "@material-ui/core/SnackbarContent"; import Typography from "@material-ui/core/Typography"; import CloseIcon from "@material-ui/icons/Close"; import classNames from "classnames"; import React, { useState } from "react"; +import { AlertComponentPropsWithStyle } from "react-alert"; import { FormattedMessage } from "react-intl"; -import { IMessage, MessageContext } from "./"; +import { IMessage } from "./"; import { useStyles } from "./styles"; -interface Message extends IMessage { - key: string; +export interface IMessageManagerProps extends AlertComponentPropsWithStyle { + message: IMessage; } -export const MessageManager = props => { - const { children } = props; +export const MessageManager: React.FC = props => { + const { + close, + options: { timeout }, + message: { actionBtn, expandText, status, title, text, onUndo } + } = props; - const [message, setMessage] = useState({ - key: "0", - onUndo: undefined, - status: "info", - text: "" - }); - const [opened, setOpened] = useState(false); const [expand, setExpand] = useState(false); const classes = useStyles({}); - const { - actionBtn, - autohide = 9000, - expandText, - title, - text, - key, - onUndo, - status = "info" - } = message; - - const queue = []; - - const handleClose = (_, reason) => { - if (reason === "clickaway") { - return; - } - setOpened(false); - }; - - const processQueue = () => { - if (queue.length > 0) { - setMessage(queue.shift()); - setOpened(true); - } - }; - - const handleExited = () => { - processQueue(); - }; - - const pushMessage = (message: IMessage) => { - queue.push({ - key: new Date().getTime(), - ...message - }); - - if (opened) { - setOpened(false); - } else { - processQueue(); - } - }; return ( - <> - + { } - title={title} action={[ !!expandText ? (
{ key="undo" color="default" size="small" - onClick={handleClose as any} + onClick={close} data-tc="button-undo" > { key="close" aria-label="Close" color="inherit" - onClick={handleClose as any} + onClick={close} className={classNames(classes.closeBtn, { [classes.closeBtnInfo]: status === "info" })} @@ -186,20 +132,17 @@ export const MessageManager = props => {
]} /> - - {children} - - +
); }; diff --git a/src/components/messages/styles.ts b/src/components/messages/styles.ts index e86c2ec10..04301a84f 100644 --- a/src/components/messages/styles.ts +++ b/src/components/messages/styles.ts @@ -36,13 +36,15 @@ export const useStyles = makeStyles( "& > div": { "& button span": { color: "#fff" - }, + } + }, + "& > div:first-child": { "&:before": { backgroundImage: `url(${errorIcon})` - }, - backgroundColor: theme.palette.error.main, - color: "#fff" - } + } + }, + backgroundColor: theme.palette.error.main, + color: "#fff" }, expandBtn: { "&:before": { @@ -122,6 +124,9 @@ export const useStyles = makeStyles( }, snackbar: { "& > div": { + paddingLeft: 60 + }, + "& > div:first-child": { "&:before": { backgroundImage: `url(${infoIcon})`, backgroundRepeat: "no-repeat", @@ -134,22 +139,30 @@ export const useStyles = makeStyles( top: 13, width: 32 }, - paddingLeft: 60, + paddingTop: 10, position: "relative" - } + }, + borderRadius: 4 + }, + snackbarContainer: { + borderRadius: 4, + display: "block", + margin: theme.spacing(2, 2, 0, 0), + maxWidth: 480, + pointerEvents: "all", + position: "relative" }, success: { - "& > div": { - "& button span": { - color: "#fff" - }, + "& > div:first-child": { "&:before": { backgroundImage: `url(${successIcon})` - }, - - backgroundColor: successColor, + } + }, + "& button span": { color: "#fff" - } + }, + backgroundColor: successColor, + color: "#fff" }, text: { color: "#fff", @@ -159,16 +172,16 @@ export const useStyles = makeStyles( paddingTop: 5 }, warning: { - "& > div": { - "& button span": { - color: "#fff" - }, + "& > div:first-child": { "&:before": { backgroundImage: `url(${warningIcon})` - }, - backgroundColor: warningColor, + } + }, + "& button span": { color: "#fff" - } + }, + backgroundColor: warningColor, + color: "#fff" } }), { name: "MessageManager" } diff --git a/src/hooks/useNotifier.ts b/src/hooks/useNotifier.ts index af927991f..d5e3ed55f 100644 --- a/src/hooks/useNotifier.ts +++ b/src/hooks/useNotifier.ts @@ -1,9 +1,12 @@ -import { IMessageContext, MessageContext } from "@saleor/components/messages"; -import { useContext } from "react"; +import { IMessage } from "@saleor/components/messages"; +import { useAlert } from "react-alert"; -export type UseNotifierResult = IMessageContext; +export type UseNotifierResult = (options: IMessage) => void; function useNotifier(): UseNotifierResult { - const notify = useContext(MessageContext); + const alert = useAlert(); + const notify = (options: IMessage) => { + alert.show(options, options.autohide && { timeout: options.autohide }); + }; return notify; } export default useNotifier; diff --git a/src/index.tsx b/src/index.tsx index 2c30b3571..3ca1ada8a 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,65 +1,66 @@ -import Navigator from "@saleor/components/Navigator"; -import useAppState from "@saleor/hooks/useAppState"; -import { defaultDataIdFromObject, InMemoryCache } from "apollo-cache-inmemory"; +import { API_URI, APP_MOUNT_URI, GTM_ID } from "./config"; +import { Provider as AlertProvider, positions } from "react-alert"; +import Auth, { getAuthToken, removeAuthToken } from "./auth"; +import { BrowserRouter, Route, Switch } from "react-router-dom"; +import ConfigurationSection, { createConfigurationMenu } from "./configuration"; +import { ErrorResponse, onError } from "apollo-link-error"; +import { InMemoryCache, defaultDataIdFromObject } from "apollo-cache-inmemory"; + import { ApolloClient } from "apollo-client"; import { ApolloLink } from "apollo-link"; -import { BatchHttpLink } from "apollo-link-batch-http"; -import { setContext } from "apollo-link-context"; -import { ErrorResponse, onError } from "apollo-link-error"; -import { createUploadLink } from "apollo-upload-client"; -import React from "react"; import { ApolloProvider } from "react-apollo"; -import { render } from "react-dom"; -import ErrorBoundary from "react-error-boundary"; -import TagManager from "react-gtm-module"; -import { useIntl } from "react-intl"; -import { BrowserRouter, Route, Switch } from "react-router-dom"; - +import AppLayout from "./components/AppLayout"; +import AppStateProvider from "./containers/AppState"; import AttributeSection from "./attributes"; -import { attributeSection } from "./attributes/urls"; -import Auth, { getAuthToken, removeAuthToken } from "./auth"; import AuthProvider from "./auth/AuthProvider"; -import LoginLoading from "./auth/components/LoginLoading/LoginLoading"; -import SectionRoute from "./auth/components/SectionRoute"; -import { isJwtError } from "./auth/errors"; -import { hasPermission } from "./auth/misc"; +import BackgroundTasksProvider from "./containers/BackgroundTasks"; +import { BatchHttpLink } from "apollo-link-batch-http"; import CategorySection from "./categories"; import CollectionSection from "./collections"; -import AppLayout from "./components/AppLayout"; -import { DateProvider } from "./components/Date"; -import { LocaleProvider } from "./components/Locale"; -import { MessageManager } from "./components/messages"; -import { ShopProvider } from "./components/Shop"; -import ThemeProvider from "./components/Theme"; -import { WindowTitle } from "./components/WindowTitle"; -import { API_URI, APP_MOUNT_URI, GTM_ID } from "./config"; -import ConfigurationSection, { createConfigurationMenu } from "./configuration"; -import AppStateProvider from "./containers/AppState"; -import BackgroundTasksProvider from "./containers/BackgroundTasks"; import { CustomerSection } from "./customers"; +import { DateProvider } from "./components/Date"; import DiscountSection from "./discounts"; +import ErrorBoundary from "react-error-boundary"; import HomePage from "./home"; -import { commonMessages } from "./intl"; +import { LocaleProvider } from "./components/Locale"; +import LoginLoading from "./auth/components/LoginLoading/LoginLoading"; +import { MessageManager } from "./components/messages"; import NavigationSection from "./navigation"; -import { navigationSection } from "./navigation/urls"; +import Navigator from "@saleor/components/Navigator"; import { NotFound } from "./NotFound"; import OrdersSection from "./orders"; import PageSection from "./pages"; +import { PermissionEnum } from "./types/globalTypes"; import PermissionGroupSection from "./permissionGroups"; import PluginsSection from "./plugins"; import ProductSection from "./products"; import ProductTypesSection from "./productTypes"; +import React from "react"; +import SectionRoute from "./auth/components/SectionRoute"; import ServiceSection from "./services"; -import { serviceSection } from "./services/urls"; import ShippingSection from "./shipping"; +import { ShopProvider } from "./components/Shop"; import SiteSettingsSection from "./siteSettings"; import StaffSection from "./staff"; +import TagManager from "react-gtm-module"; import TaxesSection from "./taxes"; +import ThemeProvider from "./components/Theme"; import TranslationsSection from "./translations"; -import { PermissionEnum } from "./types/globalTypes"; import WarehouseSection from "./warehouses"; -import { warehouseSection } from "./warehouses/urls"; import WebhooksSection from "./webhooks"; +import { WindowTitle } from "./components/WindowTitle"; +import { attributeSection } from "./attributes/urls"; +import { commonMessages } from "./intl"; +import { createUploadLink } from "apollo-upload-client"; +import { hasPermission } from "./auth/misc"; +import { isJwtError } from "./auth/errors"; +import { navigationSection } from "./navigation/urls"; +import { render } from "react-dom"; +import { serviceSection } from "./services/urls"; +import { setContext } from "apollo-link-context"; +import useAppState from "@saleor/hooks/useAppState"; +import { useIntl } from "react-intl"; +import { warehouseSection } from "./warehouses/urls"; interface ResponseError extends ErrorResponse { networkError?: Error & { @@ -126,6 +127,15 @@ const apolloClient = new ApolloClient({ link: invalidTokenLink.concat(authLink.concat(link)) }); +const notificationOptions = { + containerStyle: { + zIndex: 1000 + }, + offset: "20px", + position: positions.TOP_RIGHT, + timeout: 3000 +}; + const App: React.FC = () => { const isDark = localStorage.getItem("theme") === "true"; @@ -135,7 +145,7 @@ const App: React.FC = () => { - + @@ -143,7 +153,7 @@ const App: React.FC = () => { - + diff --git a/src/storybook/Decorator.tsx b/src/storybook/Decorator.tsx index 4819962cb..fb3f019c2 100644 --- a/src/storybook/Decorator.tsx +++ b/src/storybook/Decorator.tsx @@ -7,6 +7,16 @@ import { MessageManager } from "../components/messages"; import ThemeProvider from "../components/Theme"; import { TimezoneProvider } from "../components/Timezone"; +const messageProps = { + close: () => undefined, + id: "id", + message: { + text: "Test" + }, + options: {}, + style: {} +}; + export const Decorator = storyFn => ( ( - +
"0 6px 10px 0px rgba(0, 0, 0, 0.15), 0 1px 18px 0px rgba(0, 0, 0, 0.12), 0 3px 5px -1px rgba(0, 0, 0, 0.10)", color: colors.font.default, display: "block", - maxWidth: 480 + maxWidth: 480, + padding: 0 } }, MuiSwitch: {