diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..f455182
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,14 @@
+### Releases
+
+## v1.0.0
+###### *June 19, 2020*
+
+It's the first stable version of the template. It's going to become a solid foundation for your next React project.
+
+- features: add notifications 🎉
+- readme: update bundle size, fix image titles, add section for notifications, add examples
+
+## v0.1.0
+###### *June 19, 2020*
+
+🎉 First release 🎉
diff --git a/README.md b/README.md
index 69e34d6..494ca39 100644
--- a/README.md
+++ b/README.md
@@ -20,8 +20,9 @@ By the same philosophy, there are other routines above the basic configuration,
* [CRA](#cra)
* [React Router](#react-router)
* [Material UI](#material-ui)
-* [Theme](#theme)
* [Store](#store)
+* [Theme](#theme)
+* [Notifications](#notifications)
* [Error Handling](#error-handling)
* [Service Worker](#service-worker)
* [SEO](#seo)
@@ -38,15 +39,64 @@ The latest version of `react-router-dom` is integrated. Routes are defined in [/
#### Material UI
-The latest version of `Material-UI` is integrated. The whole layout of the application is made by `Material-UI` components. In the demonstrated components/sections you can notice how MUI components can be customized. The styling system is also inherited from MUI.
+The latest version of [Material-UI](https://material-ui.com/) is integrated. The whole layout of the application is made by `Material-UI` components. In the demonstrated components/sections you can notice how MUI components can be customized. The styling system is also inherited from MUI.
+
+#### Store
+
+For store management [overmind](https://github.com/cerebral/overmind) has been used. It's a simple store management tool. Here you can find its [implementation and integration](https://github.com/suren-atoyan/react-pwa/tree/master/src/store).
+
+You can use `useStore` hook exported from `store`. It'll give you `state`, `actions` and `effects`, which you can use.
+
+```js
+// ...
+import { useStore } from 'store';
+
+function SomeCoolComponent() {
+ const { state, actions, effects } = useStore();
+
+ // ...
+}
+```
#### Theme
-The [theme system](https://github.com/suren-atoyan/react-pwa/blob/master/src/theme/ThemeProvider.js) is based on MUI theme. There are two themes' styles that are defined in the [config file](https://github.com/suren-atoyan/react-pwa/blob/master/src/config/index.js). The theme provider, which is based on MUI is integrated with app and store.
+The [theme system](https://github.com/suren-atoyan/react-pwa/blob/master/src/theme/ThemeProvider.js) is based on MUI theme. It's integrated with store, so, see how you can use it:
-#### Store
+```js
+// ...
+import { useStore } from 'store';
-For store management `overmind` has been used. It's a simple store management tool. Here you can find its [implementation and integration](https://github.com/suren-atoyan/react-pwa/tree/master/src/store).
+function SomeCoolComponent() {
+ const { state, actions } = useStore();
+
+ // let's see what is the current theme mode
+ console.log(state.theme.mode);
+
+ // and if you want to change the theme, call appropriate action
+ function toggleTheme() {
+ actions.theme.toggle();
+ }
+}
+```
+
+Also you can modify predefined theme parameters in [config](https://github.com/suren-atoyan/react-pwa/blob/master/src/config/index.js#L29) file.
+
+#### Notifications
+
+Here we've used [notistack](https://github.com/iamhosseindhv/notistack). It's integrated with store and to show a notification you just need to call the appropriate action; look how you can do it:
+
+```js
+// ...
+import { useStore } from 'store';
+
+function SomeCoolComponent() {
+ const { actions } = useStore();
+
+ function showNiceWarning() {
+ actions.notifications.push({ message: 'Heeeeey, something went wrong I guess' });
+ }
+}
+```
#### Error Handling
@@ -76,9 +126,9 @@ There is a simple express server `/hoster/server`, which plays the role of a sta
## Size
-After all these integrations the biggest bundle size is **~59KB**. It means even first load will be pretty fast (in my case it's 1.1s), further loads (already cached by service worker and workbox) will take ~0.25s.
+After all these integrations the biggest bundle size is **~66KB**. It means even first load will be pretty fast (in my case it's 1.1s), further loads (already cached by service worker and workbox) will take ~0.25s.
-
+
## Usage
@@ -110,7 +160,7 @@ The last one will build your project (`yarn build`) and start express server (`y
## Structure
-
+
Initial files:
@@ -137,7 +187,7 @@ Initial files:
NOTE: The performance is not 100 because of demo server.
Check the results in the [live demo](https://react-pwa.surenatoyan.com/)
-
+
## TODOs
diff --git a/package.json b/package.json
index 5bc492a..2e6b326 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "react-pwa",
- "version": "0.1.0",
+ "version": "1.0.0",
"private": true,
"dependencies": {
"@material-ui/core": "^4.10.2",
@@ -9,6 +9,7 @@
"@testing-library/user-event": "^7.1.2",
"dayjs": "^1.8.28",
"is-mobile": "^2.2.1",
+ "notistack": "^0.9.17",
"overmind": "^24.1.1",
"overmind-react": "^25.1.1",
"react": "^16.13.1",
@@ -17,7 +18,8 @@
"react-helmet": "^6.1.0",
"react-icons": "^3.10.0",
"react-router-dom": "^5.2.0",
- "react-scripts": "3.4.1"
+ "react-scripts": "3.4.1",
+ "uuid": "^8.2.0"
},
"scripts": {
"start": "react-scripts start",
diff --git a/public/images/readme/build.png b/public/images/readme/build.png
index ef7d19b..1c07cc9 100644
Binary files a/public/images/readme/build.png and b/public/images/readme/build.png differ
diff --git a/public/images/readme/layout.sc.png b/public/images/readme/layout.sc.png
index e837d91..9808fd2 100644
Binary files a/public/images/readme/layout.sc.png and b/public/images/readme/layout.sc.png differ
diff --git a/public/images/readme/structure.png b/public/images/readme/structure.png
index d2b8cb3..4945cee 100644
Binary files a/public/images/readme/structure.png and b/public/images/readme/structure.png differ
diff --git a/src/config/index.js b/src/config/index.js
index 5ae8a07..7195333 100644
--- a/src/config/index.js
+++ b/src/config/index.js
@@ -1,3 +1,5 @@
+import { isMobile } from 'utils';
+
/* set your data here */
const email = 'super-email-of-the-auther@gmail.com';
const domain = 'your-project-domain.com'
@@ -68,6 +70,17 @@ const title = 'React PWA';
const themePair = ['dark', 'light'];
+const notifications = {
+ options: {
+ anchorOrigin: {
+ vertical: 'bottom',
+ horizontal: 'left',
+ },
+ autoHideDuration: 3000,
+ },
+ maxSnack: isMobile ? 3 : 4,
+};
+
export {
messages,
cancelationMessage,
@@ -78,5 +91,6 @@ export {
repository,
title,
themePair,
+ notifications,
themes,
};
diff --git a/src/index.js b/src/index.js
index 8402afd..139b9de 100644
--- a/src/index.js
+++ b/src/index.js
@@ -8,7 +8,7 @@ if (!document.ie) { // check for ie
Promise.all([
import('react'),
import('react-dom'),
- import('./App'),
+ import('App'),
]).then(([
{ default: React },
{ default: ReactDOM },
diff --git a/src/sections/Layout/Component.js b/src/sections/Layout/Component.js
index 2d5cb23..252041b 100644
--- a/src/sections/Layout/Component.js
+++ b/src/sections/Layout/Component.js
@@ -5,6 +5,7 @@ import Box from '@material-ui/core/Box';
import Content from 'sections/Content';
import Copyright from 'sections/Copyright';
import Navigation from 'sections/Navigation';
+import Notifications from 'sections/Notifications';
import useStyles from './styles';
@@ -13,6 +14,7 @@ function Layout() {
return (
<>
+
diff --git a/src/sections/Navigation/Component.js b/src/sections/Navigation/Component.js
index fad1b93..bf517b1 100644
--- a/src/sections/Navigation/Component.js
+++ b/src/sections/Navigation/Component.js
@@ -3,7 +3,7 @@ import React, { useState } from 'react';
import AppBar from 'sections/AppBar';
import Menu from 'sections/Menu';
-export default function Navigation() {
+function Navigation() {
const [isMenuOpen, setIsMenuOpen] = useState(false);
const handleMenuOpen = () => {
@@ -20,4 +20,6 @@ export default function Navigation() {
>
);
-};
+}
+
+export default Navigation;
diff --git a/src/sections/Notifications/Component.js b/src/sections/Notifications/Component.js
new file mode 100644
index 0000000..5fc4f1f
--- /dev/null
+++ b/src/sections/Notifications/Component.js
@@ -0,0 +1,17 @@
+import React from 'react';
+
+import { SnackbarProvider } from 'notistack';
+
+import Notifier from './Notifier';
+
+import { notifications } from 'config';
+
+function Notifications() {
+ return (
+
+
+
+ );
+}
+
+export default Notifications;
diff --git a/src/sections/Notifications/Notifier/Component.js b/src/sections/Notifications/Notifier/Component.js
new file mode 100644
index 0000000..444f8dd
--- /dev/null
+++ b/src/sections/Notifications/Notifier/Component.js
@@ -0,0 +1,49 @@
+import { useEffect, useRef } from 'react';
+
+import { useSnackbar } from 'notistack';
+
+import { useStore } from 'store';
+
+function Notifier() {
+ const { state: { notifications }, actions } = useStore();
+ const { enqueueSnackbar, closeSnackbar } = useSnackbar();
+ const displayed = useRef([]);
+
+ function storeDisplayed(key) {
+ displayed.current = [...displayed.current, key];
+ }
+
+ function removeDisplayed(key) {
+ displayed.current = [...displayed.current.filter(_key => key !== _key)];
+ }
+
+ useEffect(_ => {
+ notifications.forEach(({ message, options, dismissed }) => {
+ if (dismissed) {
+ // dismiss snackbar using notistack
+ closeSnackbar(options.key);
+ return;
+ }
+
+ // do nothing if snackbar is already displayed
+ if (displayed.current.includes(options.key)) return;
+
+ // display snackbar using notistack
+ enqueueSnackbar(message, {
+ ...options,
+ onExited(event, key) {
+ // removen this snackbar from the store
+ actions.notifications.remove(key);
+ removeDisplayed(key);
+ },
+ });
+
+ // keep track of snackbars that we've displayed
+ storeDisplayed(options.key);
+ });
+ });
+
+ return null;
+}
+
+export default Notifier;
diff --git a/src/sections/Notifications/Notifier/index.js b/src/sections/Notifications/Notifier/index.js
new file mode 100644
index 0000000..bf109b8
--- /dev/null
+++ b/src/sections/Notifications/Notifier/index.js
@@ -0,0 +1,3 @@
+import Component from './Component';
+
+export default Component;
diff --git a/src/sections/Notifications/index.js b/src/sections/Notifications/index.js
new file mode 100644
index 0000000..bf109b8
--- /dev/null
+++ b/src/sections/Notifications/index.js
@@ -0,0 +1,3 @@
+import Component from './Component';
+
+export default Component;
diff --git a/src/store/actions/index.js b/src/store/actions/index.js
index a53a24f..eeacb08 100644
--- a/src/store/actions/index.js
+++ b/src/store/actions/index.js
@@ -1,4 +1,4 @@
-import { themePair } from 'config';
+import { themePair, notifications as notificationsDefoults } from 'config';
const theme = {
toggle({ effects, state }) {
@@ -21,7 +21,37 @@ const sw = {
},
};
+const notifications = {
+ push({ state, effects }, notification) {
+ state.notifications.push({
+ ...notification,
+ dismissed: false,
+ options: {
+ ...notificationsDefoults.options,
+ ...notification.options,
+ key: effects.genUUID(),
+ },
+ });
+ },
+
+ close({ state }, key, dismissAll = !key) {
+
+ state.notifications = state.notifications.map(
+ notification => (dismissAll || notification.options.key === key)
+ ? { ...notification, dismissed: true }
+ : { ...notification }
+ );
+ },
+
+ remove({ state }, key) {
+ state.notifications = state.notifications.filter(
+ notification => notification.options.key !== key,
+ );
+ },
+};
+
export {
theme,
sw,
+ notifications,
};
diff --git a/src/store/effects/index.js b/src/store/effects/index.js
index 2c75dc2..927dc99 100644
--- a/src/store/effects/index.js
+++ b/src/store/effects/index.js
@@ -1,4 +1,5 @@
import { resetApp } from 'utils';
+import { v1 as uuidv1 } from 'uuid';
const SW = {}; // don't keep it in the store
@@ -26,4 +27,6 @@ const theme = {
},
};
-export { sw, theme };
+const genUUID = uuidv1;
+
+export { sw, theme, genUUID };
diff --git a/src/store/state/index.js b/src/store/state/index.js
index 9b20ebd..f08361c 100644
--- a/src/store/state/index.js
+++ b/src/store/state/index.js
@@ -7,6 +7,7 @@ const initialState = {
isUpdated: false,
registration: null,
},
+ notifications: [],
};
export { initialState };
diff --git a/yarn.lock b/yarn.lock
index fc45796..b76b000 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3008,7 +3008,7 @@ clone-deep@^4.0.1:
kind-of "^6.0.2"
shallow-clone "^3.0.0"
-clsx@^1.0.4:
+clsx@^1.0.4, clsx@^1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188"
integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==
@@ -5102,7 +5102,7 @@ hmac-drbg@^1.0.0:
minimalistic-assert "^1.0.0"
minimalistic-crypto-utils "^1.0.1"
-hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.2:
+hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
@@ -7257,6 +7257,14 @@ normalize-url@^3.0.0:
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559"
integrity sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==
+notistack@^0.9.17:
+ version "0.9.17"
+ resolved "https://registry.yarnpkg.com/notistack/-/notistack-0.9.17.tgz#dcb827f268013356f198ee7143ffda9f48a0f06f"
+ integrity sha512-nypTN6sEe+q98wMaxF/UwatA1yAq948+bZOo9JKYR+tU65DW0ipWyx8DseJ3UJYvb6VDD+Fqo83qwayQ46bEEA==
+ dependencies:
+ clsx "^1.1.0"
+ hoist-non-react-statics "^3.3.0"
+
npm-run-path@^2.0.0:
version "2.0.2"
resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f"
@@ -10654,6 +10662,11 @@ uuid@^3.0.1, uuid@^3.3.2:
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
+uuid@^8.2.0:
+ version "8.2.0"
+ resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.2.0.tgz#cb10dd6b118e2dada7d0cd9730ba7417c93d920e"
+ integrity sha512-CYpGiFTUrmI6OBMkAdjSDM0k5h8SkkiTP4WAjQgDgNB1S3Ou9VBEvr6q0Kv2H1mMk7IWfxYGpMH5sd5AvcIV2Q==
+
v8-compile-cache@^2.0.3:
version "2.1.0"
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz#e14de37b31a6d194f5690d67efc4e7f6fc6ab30e"