Skip to content

Commit

Permalink
Merge pull request #2 from suren-atoyan/notifications
Browse files Browse the repository at this point in the history
Integrate notifications
  • Loading branch information
suren-atoyan authored Jun 27, 2020
2 parents 3813e73 + c690f85 commit fef9447
Show file tree
Hide file tree
Showing 18 changed files with 221 additions and 18 deletions.
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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 🎉
68 changes: 59 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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

Expand Down Expand Up @@ -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.

<img src="./public/images/readme/build.png" width="300" title="build">
<img src="./public/images/readme/build.png" width="300" title="Build">

## Usage

Expand Down Expand Up @@ -110,7 +160,7 @@ The last one will build your project (`yarn build`) and start express server (`y

## Structure

<img src="./public/images/readme/structure.png" width="200" title="index.js file">
<img src="./public/images/readme/structure.png" width="200" title="Structure">

Initial files:

Expand All @@ -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/)

<img src="./public/images/readme/audit.png" width="500" title="audit results">
<img src="./public/images/readme/audit.png" width="500" title="Audit results">

## TODOs

Expand Down
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-pwa",
"version": "0.1.0",
"version": "1.0.0",
"private": true,
"dependencies": {
"@material-ui/core": "^4.10.2",
Expand All @@ -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",
Expand All @@ -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",
Expand Down
Binary file modified public/images/readme/build.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified public/images/readme/layout.sc.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified public/images/readme/structure.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions src/config/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { isMobile } from 'utils';

/* set your data here */
const email = '[email protected]';
const domain = 'your-project-domain.com'
Expand Down Expand Up @@ -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,
Expand All @@ -78,5 +91,6 @@ export {
repository,
title,
themePair,
notifications,
themes,
};
2 changes: 1 addition & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand Down
2 changes: 2 additions & 0 deletions src/sections/Layout/Component.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -13,6 +14,7 @@ function Layout() {

return (
<>
<Notifications />
<Navigation />
<Box component="main" className={classes.wrapper}>
<Box className={classes.spacer} />
Expand Down
6 changes: 4 additions & 2 deletions src/sections/Navigation/Component.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = () => {
Expand All @@ -20,4 +20,6 @@ export default function Navigation() {
<AppBar isMenuOpen={isMenuOpen} onMenuOpen={handleMenuOpen} />
</>
);
};
}

export default Navigation;
17 changes: 17 additions & 0 deletions src/sections/Notifications/Component.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React from 'react';

import { SnackbarProvider } from 'notistack';

import Notifier from './Notifier';

import { notifications } from 'config';

function Notifications() {
return (
<SnackbarProvider maxSnack={notifications.maxSnack}>
<Notifier />
</SnackbarProvider>
);
}

export default Notifications;
49 changes: 49 additions & 0 deletions src/sections/Notifications/Notifier/Component.js
Original file line number Diff line number Diff line change
@@ -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;
3 changes: 3 additions & 0 deletions src/sections/Notifications/Notifier/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import Component from './Component';

export default Component;
3 changes: 3 additions & 0 deletions src/sections/Notifications/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import Component from './Component';

export default Component;
32 changes: 31 additions & 1 deletion src/store/actions/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { themePair } from 'config';
import { themePair, notifications as notificationsDefoults } from 'config';

const theme = {
toggle({ effects, state }) {
Expand All @@ -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,
};
5 changes: 4 additions & 1 deletion src/store/effects/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { resetApp } from 'utils';
import { v1 as uuidv1 } from 'uuid';

const SW = {}; // don't keep it in the store

Expand Down Expand Up @@ -26,4 +27,6 @@ const theme = {
},
};

export { sw, theme };
const genUUID = uuidv1;

export { sw, theme, genUUID };
1 change: 1 addition & 0 deletions src/store/state/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const initialState = {
isUpdated: false,
registration: null,
},
notifications: [],
};

export { initialState };
Loading

0 comments on commit fef9447

Please sign in to comment.