diff --git a/generators/app/templates/package-lock.json b/generators/app/templates/package-lock.json index 0de47c94..e721f62b 100644 --- a/generators/app/templates/package-lock.json +++ b/generators/app/templates/package-lock.json @@ -1855,14 +1855,6 @@ "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.6.6.tgz", "integrity": "sha512-ojhgxzUHZ7am3D2jHkMzPpsBAiB005GF5YU4ea+8DNPybMk01JJUM9V9YRlF/GE95tcOm8DxQvWA2jq19bGalQ==" }, - "@emotion/is-prop-valid": { - "version": "0.6.8", - "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.6.8.tgz", - "integrity": "sha512-IMSL7ekYhmFlILXcouA6ket3vV7u9BqStlXzbKOF9HBtpUPMMlHU+bBxrLOa2NvleVwNIxeq/zL8LafLbeUXcA==", - "requires": { - "@emotion/memoize": "^0.6.6" - } - }, "@emotion/memoize": { "version": "0.6.6", "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.6.6.tgz", @@ -5036,14 +5028,6 @@ "stylis-rule-sheet": "^0.0.10" } }, - "create-emotion-styled": { - "version": "9.2.8", - "resolved": "https://registry.npmjs.org/create-emotion-styled/-/create-emotion-styled-9.2.8.tgz", - "integrity": "sha512-2LrNM5MREWzI5hZK+LyiBHglwE18WE3AEbBQgpHQ1+zmyLSm/dJsUZBeFAwuIMb+TjNZP0KsMZlV776ufOtFdg==", - "requires": { - "@emotion/is-prop-valid": "^0.6.1" - } - }, "create-hash": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", @@ -12560,15 +12544,6 @@ "scheduler": "^0.13.4" } }, - "react-emotion": { - "version": "9.2.12", - "resolved": "https://registry.npmjs.org/react-emotion/-/react-emotion-9.2.12.tgz", - "integrity": "sha512-qt7XbxnEKX5sZ73rERJ92JMbEOoyOwG3BuCRFRkXrsJhEe+rFBRTljRw7yOLHZUCQC4GBObZhjXIduQ8S0ZpYw==", - "requires": { - "babel-plugin-emotion": "^9.2.11", - "create-emotion-styled": "^9.2.8" - } - }, "react-error-overlay": { "version": "5.1.6", "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-5.1.6.tgz", @@ -13841,14 +13816,15 @@ "dom-testing-library": "^3.19.0" } }, - "react-toast-notifications": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/react-toast-notifications/-/react-toast-notifications-1.4.0.tgz", - "integrity": "sha512-O9D76tEwDpJRqted8z+2ukCHbbzPaV3Lfvr+IKOY43go64F1oLdX0Vj3yRk6zwuq5+hOB1ExgR2iqTJ5hi1oEw==", + "react-toastify": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-5.3.0.tgz", + "integrity": "sha512-01Tc1ZfeAgmbUp+CWj0J/G+6KqCGpo2XKdBSZ9/fpDw0Qmvne6fIrEzg7bGx2Uz7at5GDgd/ii4ek/6TY/T+6Q==", "requires": { - "emotion": "^9.1.1", - "react-emotion": "^9.1.3", - "react-transition-group": "^2.3.1" + "@babel/runtime": "^7.4.2", + "classnames": "^2.2.6", + "prop-types": "^15.7.2", + "react-transition-group": "^2.6.1" } }, "react-transition-group": { diff --git a/generators/app/templates/package.json b/generators/app/templates/package.json index 85c3f3d9..0033e680 100644 --- a/generators/app/templates/package.json +++ b/generators/app/templates/package.json @@ -33,7 +33,7 @@ "react-i18next": "^10.1.2", "react-router-dom": "^4.3.1", "react-scripts": "^3.0.1", - "react-toast-notifications": "^1.3.1", + "react-toastify": "^5.3.0", "solid-auth-client": "^2.3.0", "styled-components": "^4.1.3" }, diff --git a/generators/app/templates/src/App.js b/generators/app/templates/src/App.js index 378c99a5..5a334de4 100644 --- a/generators/app/templates/src/App.js +++ b/generators/app/templates/src/App.js @@ -1,32 +1,41 @@ import React, { Fragment, Suspense } from 'react'; -import { ToastProvider } from 'react-toast-notifications'; -import { ToasterNotification, Loader } from '@util-components'; +import { toast, Slide } from 'react-toastify'; +import { Loader } from '@util-components'; import { ThemeProvider } from 'styled-components'; import { library } from '@fortawesome/fontawesome-svg-core'; import { fas } from '@fortawesome/free-solid-svg-icons'; import { faGithub } from '@fortawesome/free-brands-svg-icons'; import Routes from './routes'; import theme from './utils/theme'; +import 'react-toastify/dist/ReactToastify.css'; import 'flag-icon-css/css/flag-icon.min.css'; import 'normalize.css'; import './index.css'; import '@inrupt/solid-style-guide'; +import { Toaster } from './App.styled'; library.add(fas); library.add(faGithub); const App = () => ( }> - - - - - + + + + ); diff --git a/generators/app/templates/src/App.styled.js b/generators/app/templates/src/App.styled.js new file mode 100644 index 00000000..0ddf83ba --- /dev/null +++ b/generators/app/templates/src/App.styled.js @@ -0,0 +1,40 @@ +import { ToastContainer } from 'react-toastify'; +import styled from 'styled-components'; + +export const Toaster = styled(ToastContainer)` + &.solid-toaster-container { + min-width: 410px; + margin-left: 0; + transform: translateX(-50%); + padding: 0; + & .toaster-error { + color: #fff; + background: rgba(213, 0, 0, 0.9); + } + + & .toaster-success { + color: #fff; + background: rgba(124, 77, 255, 0.9); + } + } + + & .solid-toaster { + border-radius: 4px; + min-width: 410px; + color: #fff; + display: flex; + align-content: center; + min-height: 72px; + margin: 0; + + & > .solid-toaster-body { + margin: 0; + display: flex; + } + + & > button { + color: #fff; + opacity: 0.8; + } + } +`; diff --git a/generators/app/templates/src/components/AuthNavBar/auth-nav-bar.container.js b/generators/app/templates/src/components/AuthNavBar/auth-nav-bar.container.js index 96980a4f..ddf3eedd 100644 --- a/generators/app/templates/src/components/AuthNavBar/auth-nav-bar.container.js +++ b/generators/app/templates/src/components/AuthNavBar/auth-nav-bar.container.js @@ -2,8 +2,8 @@ import React, { Component } from 'react'; import { UpdateContext, withWebId } from '@inrupt/solid-react-components'; import { withTranslation } from 'react-i18next'; import data from '@solid/query-ldflex'; -import { withToastManager } from 'react-toast-notifications'; import AuthNavBar from './auth-nav-bar.component'; +import { errorToaster } from '@utils'; let beforeContext = {}; @@ -46,11 +46,7 @@ class AuthNavBarContainer extends Component { image }); } catch (error) { - const { toastManager } = this.props; - toastManager.add(['Error', error.message], { - appearance: 'error', - autoDismiss: false - }); + errorToaster(error.message, 'Error'); } }; @@ -61,4 +57,4 @@ class AuthNavBarContainer extends Component { } AuthNavBarContainer.contextType = UpdateContext; -export default withTranslation()(withToastManager(withWebId(AuthNavBarContainer))); +export default withTranslation()(withWebId(AuthNavBarContainer)); diff --git a/generators/app/templates/src/components/Utils/LanguageDropdown/language-dropdown.component.js b/generators/app/templates/src/components/Utils/LanguageDropdown/language-dropdown.component.js index 31a1ae3e..255d8fec 100644 --- a/generators/app/templates/src/components/Utils/LanguageDropdown/language-dropdown.component.js +++ b/generators/app/templates/src/components/Utils/LanguageDropdown/language-dropdown.component.js @@ -1,6 +1,6 @@ import React, { Component } from 'react'; import { Dropdown } from '@util-components'; -import { withToastManager } from 'react-toast-notifications'; +import { toast } from 'react-toastify'; const languages = { en: { @@ -19,7 +19,6 @@ const languages = { type Props = { i18n: Object, - toastManager: Object, t: Function }; @@ -32,8 +31,8 @@ class LanguageDropdown extends Component { getLanguage = () => localStorage.getItem('i18nextLng') || 'en'; onLanguageSelect = nextLanguage => { - const { i18n, toastManager } = this.props; - toastManager.toasts.forEach(toast => toastManager.remove(toast.id)); + const { i18n } = this.props; + toast.dismiss(); i18n.changeLanguage(nextLanguage); this.setState({ language: this.getLanguage() @@ -69,4 +68,4 @@ class LanguageDropdown extends Component { } } -export default withToastManager(LanguageDropdown); +export default LanguageDropdown; diff --git a/generators/app/templates/src/components/Utils/ToasterNotification/index.js b/generators/app/templates/src/components/Utils/ToasterNotification/index.js index d83d8b60..db48f2ff 100644 --- a/generators/app/templates/src/components/Utils/ToasterNotification/index.js +++ b/generators/app/templates/src/components/Utils/ToasterNotification/index.js @@ -1,3 +1,3 @@ -import ToasterNotification from './toaster-notification.component'; +import Toaster from './toaster.component'; -export default ToasterNotification; +export default Toaster; diff --git a/generators/app/templates/src/components/Utils/ToasterNotification/toaster-notification.component.js b/generators/app/templates/src/components/Utils/ToasterNotification/toaster-notification.component.js deleted file mode 100644 index 0b4a1f0e..00000000 --- a/generators/app/templates/src/components/Utils/ToasterNotification/toaster-notification.component.js +++ /dev/null @@ -1,32 +0,0 @@ -import React from 'react'; -import { ToastConsumer } from 'react-toast-notifications'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { ToasterWrapper } from './toaster-notification.style'; - -type Props = { - appearance: String, - children: Node, - onDismiss: () => void -}; - -const ToasterNotification = (props: Props) => { - const { appearance, children, onDismiss } = props; - const isArray = Array.isArray(children); - const title = isArray ? children[0] : children; - const content = isArray && children[1]; - return ( - -
- {title &&

{title}

} - {content &&

{content}

} -
-
- - {() => } - -
-
- ); -}; - -export default ToasterNotification; diff --git a/generators/app/templates/src/components/Utils/ToasterNotification/toaster-notification.style.js b/generators/app/templates/src/components/Utils/ToasterNotification/toaster-notification.style.js deleted file mode 100644 index 8161cc06..00000000 --- a/generators/app/templates/src/components/Utils/ToasterNotification/toaster-notification.style.js +++ /dev/null @@ -1,29 +0,0 @@ -import styled from 'styled-components'; - -import { media } from '../../../utils'; - -export const ToasterWrapper = styled.section` - //Temporary fix to a z-index problem with fixed headers - margin-top: 60px; - min-width: 310px; - &.toaster-wrap--primary { - box-sizing: border-box; - } - &.error { - background-color: rgba(213, 0, 0, 1); - - &:hover { - background-color: rgba(213, 0, 0, 0.8); - } - } - &.warning { - background-color: rgba(255, 234, 0, 1); - - &:hover { - background-color: rgba(213, 0, 0, 0.8); - } - } - ${media.tablet` - min-width: 410px; - `} -`; diff --git a/generators/app/templates/src/components/Utils/ToasterNotification/toaster-notification.test.js b/generators/app/templates/src/components/Utils/ToasterNotification/toaster-notification.test.js deleted file mode 100644 index 2002e137..00000000 --- a/generators/app/templates/src/components/Utils/ToasterNotification/toaster-notification.test.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable react/no-children-prop */ -import React from 'react'; - -import { render, cleanup } from 'react-testing-library'; -import ToasterNotification from './toaster-notification.component'; - -afterAll(cleanup); - -const { container } = render(); - -describe('Toaster Notification Component', () => { - it('Toaster Notification renders without crashing', () => { - expect(container).toBeTruthy(); - }); - - it('render title and message when children prop comes', () => { - const title = document.querySelector('.content__title'); - const message = document.querySelector('.content__message'); - - expect(title).toBeTruthy(); - expect(message).toBeTruthy(); - }); -}); diff --git a/generators/app/templates/src/components/Utils/ToasterNotification/toaster.component.js b/generators/app/templates/src/components/Utils/ToasterNotification/toaster.component.js new file mode 100644 index 00000000..e1dbad21 --- /dev/null +++ b/generators/app/templates/src/components/Utils/ToasterNotification/toaster.component.js @@ -0,0 +1,37 @@ +import React from 'react'; +import styled from 'styled-components'; + +type Props = { + title: String, + content: String +}; + +const Toastr = styled.div` + display: flex; + flex-direction: column; + justify-content: space-evenly; + flex: 1 1 auto; + + & > span { + font-weight: bold; + text-transform: uppercase; + } + + & > p { + margin: 0; + padding: 2px 0; + color: inherit; + } +`; + +const Toaster = (props: Props) => { + const { title, content } = props; + return ( + + {title} +

{content}

+
+ ); +}; + +export default Toaster; diff --git a/generators/app/templates/src/components/Utils/ToasterNotification/toaster.test.js b/generators/app/templates/src/components/Utils/ToasterNotification/toaster.test.js new file mode 100644 index 00000000..db59ef11 --- /dev/null +++ b/generators/app/templates/src/components/Utils/ToasterNotification/toaster.test.js @@ -0,0 +1,27 @@ +/* eslint-disable react/no-children-prop */ +import React from 'react'; + +import { render, cleanup } from 'react-testing-library'; +import Toaster from './toaster.component'; + +afterAll(cleanup); + +const defaultTitle = 'error'; + +const defaultContent = 'Something happened'; + +const { container, getByText } = render(); + +describe('Toaster Notification Component', () => { + it('Toaster Notification renders without crashing', () => { + expect(container).toBeTruthy(); + }); + + it('render title', () => { + expect(getByText(defaultTitle).textContent).toBe(defaultTitle); + }); + + it('render content', () => { + expect(getByText(defaultContent).textContent).toBe(defaultContent); + }); +}); diff --git a/generators/app/templates/src/components/Utils/index.js b/generators/app/templates/src/components/Utils/index.js index 2dfcaef3..e4e73d8f 100644 --- a/generators/app/templates/src/components/Utils/index.js +++ b/generators/app/templates/src/components/Utils/index.js @@ -8,6 +8,7 @@ import Loader from './Loader'; import ToasterNotification from './ToasterNotification'; import Input from './Input'; import LanguageDropdown from './LanguageDropdown'; +import Toaster from './ToasterNotification/toaster.component'; export { GradientBackground, @@ -19,5 +20,6 @@ export { Badge, Loader, Input, - LanguageDropdown + LanguageDropdown, + Toaster }; diff --git a/generators/app/templates/src/containers/Profile/components/Image/image.component.js b/generators/app/templates/src/containers/Profile/components/Image/image.component.js index 67cecbe3..e4d79708 100644 --- a/generators/app/templates/src/containers/Profile/components/Image/image.component.js +++ b/generators/app/templates/src/containers/Profile/components/Image/image.component.js @@ -4,13 +4,13 @@ import { Uploader, useLiveUpdate } from '@inrupt/solid-react-components'; import { useTranslation } from 'react-i18next'; import { namedNode } from '@rdfjs/data-model'; import { ImageProfile } from '@components'; +import { successToaster, errorToaster } from '@utils'; type Props = { - webId: String, - toastManager: String + webId: String }; -export const Image = ({ webId, toastManager, defaultProfilePhoto }: Props) => { +export const Image = ({ webId, defaultProfilePhoto }: Props) => { const [image, setImage] = useState(''); const latestUpdate = useLiveUpdate(); @@ -37,10 +37,7 @@ export const Image = ({ webId, toastManager, defaultProfilePhoto }: Props) => { setImage(image && image.value); } } catch (error) { - toastManager.add(['Error', error.message], { - appearance: 'error', - autoDismiss: false - }); + errorToaster(error.message, 'Error 500'); } }; @@ -57,17 +54,10 @@ export const Image = ({ webId, toastManager, defaultProfilePhoto }: Props) => { const updatePhoto = async (uri: String) => { try { const { user } = data; - await user.vcard_hasPhoto.set(namedNode(uri)); - - toastManager.add(['', t('profile.uploadSuccess')], { - appearance: 'success' - }); + successToaster(t('profile.uploadSuccess')); } catch (error) { - toastManager.add(['Error', error.message], { - appearance: 'error', - autoDismiss: false - }); + errorToaster(error.message, 'Error'); } }; const limit = 2100000; @@ -87,10 +77,7 @@ export const Image = ({ webId, toastManager, defaultProfilePhoto }: Props) => { }, onError: error => { if (error && error.statusText) { - toastManager.add(['Error', error.statusText], { - appearance: 'error', - autoDismiss: false - }); + errorToaster(error.statusText); } }, onComplete: uploadedFiles => { diff --git a/generators/app/templates/src/containers/Profile/profile.container.js b/generators/app/templates/src/containers/Profile/profile.container.js index 1cd475e0..fe74b8ba 100644 --- a/generators/app/templates/src/containers/Profile/profile.container.js +++ b/generators/app/templates/src/containers/Profile/profile.container.js @@ -1,8 +1,8 @@ import React, { Fragment } from 'react'; import { useTranslation } from 'react-i18next'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { withToastManager } from 'react-toast-notifications'; import { useWebId, ShexFormBuilder } from '@inrupt/solid-react-components'; +import { successToaster, errorToaster } from '@utils'; import { Header, ProfileContainer, @@ -23,24 +23,20 @@ const defaultProfilePhoto = '/img/icon/empty-profile.svg'; * for more information please go to: https://github.com/solid/query-ldflex */ -const Profile = ({ toastManager }) => { +const Profile = () => { const webId = useWebId(); const { t, i18n } = useTranslation(); const successCallback = () => { - toastManager.add([t('profile.successTitle'), t('profile.successCallback')], { - appearance: 'success' - }); + successToaster(t('profile.successCallback'), t('profile.successTitle')); }; const errorCallback = e => { const code = e.code || e.status; const messageError = code ? `profile.errors.${code}` : 'profile.errors.default'; - if (code && code !== 200) - toastManager.add(['Error', t(messageError)], { - appearance: 'error', - autoDismiss: false - }); + if (code && code !== 200) { + errorToaster(t(messageError), 'Error'); + } }; return ( @@ -52,8 +48,7 @@ const Profile = ({ toastManager }) => { @@ -112,4 +107,4 @@ const Profile = ({ toastManager }) => { ); }; -export default withToastManager(Profile); +export default Profile; diff --git a/generators/app/templates/src/containers/Welcome/welcome.component.js b/generators/app/templates/src/containers/Welcome/welcome.component.js index fd6adc4f..59eb920d 100644 --- a/generators/app/templates/src/containers/Welcome/welcome.component.js +++ b/generators/app/templates/src/containers/Welcome/welcome.component.js @@ -10,8 +10,8 @@ import { WelcomeDetail, ImageWrapper } from './welcome.style'; -import { withToastManager } from 'react-toast-notifications'; import { ImageProfile } from '@components'; +import { errorToaster } from '@utils'; /** * Welcome Page UI component, containing the styled components for the Welcome Page @@ -19,7 +19,7 @@ import { ImageProfile } from '@components'; * @param props */ const WelcomePageContent = props => { - const { webId, image, updatePhoto, toastManager, name, t } = props; + const { webId, image, updatePhoto, name, t } = props; const limit = 2100000; return ( @@ -47,10 +47,7 @@ const WelcomePageContent = props => { }, onError: error => { if (error && error.statusText) { - toastManager.add(['', error.statusText], { - appearance: 'error', - autoDismiss: false - }); + errorToaster(error.statusText); } }, onComplete: uploadedFiles => { @@ -185,4 +182,4 @@ const WelcomePageContent = props => { }; export { WelcomePageContent }; -export default withTranslation()(isLoading(withToastManager(WelcomePageContent))); +export default withTranslation()(isLoading(WelcomePageContent)); diff --git a/generators/app/templates/src/containers/Welcome/welcome.container.js b/generators/app/templates/src/containers/Welcome/welcome.container.js index 6e365834..cf4113bc 100644 --- a/generators/app/templates/src/containers/Welcome/welcome.container.js +++ b/generators/app/templates/src/containers/Welcome/welcome.container.js @@ -1,9 +1,9 @@ import React, { Component } from 'react'; import { withWebId } from '@inrupt/solid-react-components'; import data from '@solid/query-ldflex'; -import { withToastManager } from 'react-toast-notifications'; import { namedNode } from '@rdfjs/data-model'; import WelcomePageContent from './welcome.component'; +import { successToaster, errorToaster } from '@utils'; const defaultProfilePhoto = '/img/icon/empty-profile.svg'; @@ -82,20 +82,13 @@ class WelcomeComponent extends Component { */ updatePhoto = async (uri: String, message) => { const { hasImage } = this.state; - const { toastManager } = this.props; try { const { user } = data; if (hasImage) await user.vcard_hasPhoto.set(namedNode(uri)); else await user.vcard_hasPhoto.add(namedNode(uri)); - - toastManager.add(['', message], { - appearance: 'success' - }); + successToaster(message); } catch (error) { - toastManager.add(['Error', error.message], { - appearance: 'error', - autoDismiss: false - }); + errorToaster(error.message, 'Error'); } }; @@ -108,4 +101,4 @@ class WelcomeComponent extends Component { } } -export default withWebId(withToastManager(WelcomeComponent)); +export default withWebId(WelcomeComponent); diff --git a/generators/app/templates/src/layouts/PrivateLayout/private.layout.js b/generators/app/templates/src/layouts/PrivateLayout/private.layout.js index 19600b26..4ed05b57 100644 --- a/generators/app/templates/src/layouts/PrivateLayout/private.layout.js +++ b/generators/app/templates/src/layouts/PrivateLayout/private.layout.js @@ -7,6 +7,7 @@ import styled from 'styled-components'; const Container = styled.div` min-height: 100%; position: relative; + padding-top: 60px; `; const FooterContainer = styled.div` diff --git a/generators/app/templates/src/utils/index.js b/generators/app/templates/src/utils/index.js index d83f87c5..0497508c 100644 --- a/generators/app/templates/src/utils/index.js +++ b/generators/app/templates/src/utils/index.js @@ -1,5 +1,6 @@ import { media } from './styledComponents'; import { expandedProperty } from './context'; +import { successToaster, errorToaster } from './toaster'; function* entries(obj) { for (const key of Object.keys(obj)) { @@ -7,4 +8,4 @@ function* entries(obj) { } } -export { media, expandedProperty, entries }; +export { media, expandedProperty, entries, successToaster, errorToaster }; diff --git a/generators/app/templates/src/utils/toaster.js b/generators/app/templates/src/utils/toaster.js new file mode 100644 index 00000000..c7ffd4f2 --- /dev/null +++ b/generators/app/templates/src/utils/toaster.js @@ -0,0 +1,16 @@ +import React from 'react'; +import { toast } from 'react-toastify'; +import { Toaster } from '@util-components'; + +export const errorToaster = (content: String, title: String = null) => + toast(, { + autoClose: false, + className: 'solid-toaster toaster-error', + type: 'error' + }); + +export const successToaster = (content: String, title: String = null) => + toast(, { + className: 'solid-toaster toaster-success', + type: 'success' + });