Skip to content

Commit b825027

Browse files
committed
ui: add toast notification components and actions
1 parent fa3e703 commit b825027

File tree

8 files changed

+159
-2
lines changed

8 files changed

+159
-2
lines changed

app/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"react-dom": "^16.13.1",
3838
"react-i18next": "11.4.0",
3939
"react-scripts": "3.4.1",
40+
"react-toastify": "6.0.5",
4041
"react-virtualized": "^9.21.2"
4142
},
4243
"devDependencies": {

app/src/App.scss

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
// rc-tooltip component styles
1818
@import '../node_modules/rc-tooltip/assets/bootstrap_white.css';
1919

20+
// react-toastify styles
21+
@import '../node_modules/react-toastify/dist/ReactToastify.css';
22+
2023
@font-face {
2124
font-family: 'OpenSans Light';
2225
src: url('./assets/fonts/OpenSans-Light.ttf') format('truetype');

app/src/App.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from 'react';
22
import './App.scss';
33
import { createStore, StoreProvider } from 'store';
4+
import AlertContainer from 'components/common/AlertContainer';
45
import { Layout } from 'components/layout';
56
import Pages from 'components/Pages';
67
import { ThemeProvider } from 'components/theme';
@@ -13,6 +14,7 @@ const App = () => {
1314
<ThemeProvider>
1415
<Layout>
1516
<Pages />
17+
<AlertContainer />
1618
</Layout>
1719
</ThemeProvider>
1820
</StoreProvider>
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import React, { useEffect } from 'react';
2+
import { toast, ToastContainer } from 'react-toastify';
3+
import { values } from 'mobx';
4+
import { observer } from 'mobx-react-lite';
5+
import { Alert } from 'types/state';
6+
import { useStore } from 'store';
7+
import { styled } from 'components/theme';
8+
import { Close } from './icons';
9+
10+
const Styled = {
11+
Body: styled.div`
12+
margin-right: 10px;
13+
`,
14+
Title: styled.div`
15+
font-family: ${props => props.theme.fonts.open.semiBold};
16+
font-size: ${props => props.theme.sizes.xs};
17+
text-transform: uppercase;
18+
`,
19+
Message: styled.div`
20+
font-size: ${props => props.theme.sizes.xs};
21+
`,
22+
CloseIcon: styled.div`
23+
display: flex;
24+
align-items: center;
25+
justify-content: center;
26+
min-width: 18px;
27+
height: 18px;
28+
border: 1px solid ${props => props.theme.colors.offWhite};
29+
border-radius: 18px;
30+
transition: background-color 0.3s;
31+
32+
&:hover {
33+
color: ${props => props.theme.colors.blue};
34+
background-color: ${props => props.theme.colors.offWhite};
35+
}
36+
37+
svg {
38+
width: 12px;
39+
height: 12px;
40+
padding: 0;
41+
}
42+
`,
43+
Container: styled(ToastContainer)`
44+
.Toastify__toast {
45+
border-radius: 4px;
46+
}
47+
.Toastify__toast--error {
48+
color: ${props => props.theme.colors.offWhite};
49+
background-color: ${props => props.theme.colors.pink};
50+
}
51+
`,
52+
};
53+
54+
interface AlertToastProps {
55+
alert: Alert;
56+
onClose: (id: number) => void;
57+
}
58+
59+
/**
60+
* The content to be rendered inside of the toast
61+
*/
62+
const AlertToast: React.FC<AlertToastProps> = ({ alert, onClose }) => {
63+
// use useEffect to only run the side-effect one time
64+
useEffect(() => {
65+
const { id, type, message, title } = alert;
66+
// create a component to display inside of the toast
67+
const { Body, Title, Message } = Styled;
68+
const body = (
69+
<Body>
70+
{title && <Title>{title}</Title>}
71+
<Message>{message}</Message>
72+
</Body>
73+
);
74+
// display the toast popup containing the styled body
75+
toast(body, { type, onClose: () => onClose(id) });
76+
}, [alert, onClose]);
77+
78+
// do not render anything to the dom. the toast() func will display the content
79+
return null;
80+
};
81+
82+
/**
83+
* A wrapper around the ToastContainer to add custom styling. Also renders
84+
* each toast message based on the alerts in the mobx store
85+
*/
86+
const AlertContainer: React.FC = () => {
87+
const { uiStore } = useStore();
88+
89+
const { Container, CloseIcon } = Styled;
90+
const closeButton = (
91+
<CloseIcon>
92+
<Close />
93+
</CloseIcon>
94+
);
95+
return (
96+
<>
97+
{values(uiStore.alerts).map(n => (
98+
<AlertToast key={n.id} alert={n} onClose={uiStore.clearAlert} />
99+
))}
100+
<Container position="top-right" autoClose={5 * 1000} closeButton={closeButton} />
101+
</>
102+
);
103+
};
104+
105+
export default observer(AlertContainer);

app/src/components/common/icons.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ interface IconProps {
2222
const Icon = styled.span<IconProps>`
2323
display: inline-block;
2424
padding: 6px;
25+
transition: all 0.3s;
2526
2627
${props =>
2728
props.onClick &&

app/src/store/stores/uiStore.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { action, observable } from 'mobx';
1+
import { action, observable, toJS } from 'mobx';
2+
import { Alert } from 'types/state';
23
import { Store } from 'store';
34

45
type PageName = 'loop' | 'history' | 'settings';
@@ -14,6 +15,8 @@ export default class UiStore {
1415
@observable processingSwapsVisible = false;
1516
/** the selected setting on the Settings page */
1617
@observable selectedSetting: SettingName = 'general';
18+
/** a collection of alerts to display as toasts */
19+
@observable alerts = observable.map<number, Alert>();
1720

1821
constructor(store: Store) {
1922
this._store = store;
@@ -51,5 +54,21 @@ export default class UiStore {
5154
@action.bound
5255
showSettings(name: SettingName) {
5356
this.selectedSetting = name;
57+
this._store.log.info('Switch to Setting screen', name);
58+
}
59+
60+
/** adds a alert to the store */
61+
@action.bound
62+
notify(message: string, title?: string) {
63+
const alert: Alert = { id: Date.now(), type: 'error', message, title };
64+
this.alerts.set(alert.id, alert);
65+
this._store.log.info('Added alert', message, toJS(this.alerts));
66+
}
67+
68+
/** removes an existing alert */
69+
@action.bound
70+
clearAlert(id: number) {
71+
this.alerts.delete(id);
72+
this._store.log.info('Cleared alert', id, toJS(this.alerts));
5473
}
5574
}

app/src/types/state.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,10 @@ export enum BuildSwapSteps {
2727
ReviewQuote = 3,
2828
Processing = 4,
2929
}
30+
31+
export interface Alert {
32+
id: number;
33+
type: 'info' | 'success' | 'warning' | 'error' | 'default';
34+
title?: string;
35+
message: string;
36+
}

app/yarn.lock

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5610,7 +5610,7 @@ dom-converter@^0.2:
56105610
dependencies:
56115611
utila "~0.4"
56125612

5613-
dom-helpers@^5.0.0:
5613+
dom-helpers@^5.0.0, dom-helpers@^5.0.1:
56145614
version "5.1.4"
56155615
resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.1.4.tgz#4609680ab5c79a45f2531441f1949b79d6587f4b"
56165616
integrity sha512-TjMyeVUvNEnOnhzs6uAn9Ya47GmMo3qq7m+Lr/3ON0Rs5kHvb8I+SQYjLUSYn7qhEm0QjW0yrBkvz9yOrwwz1A==
@@ -12188,6 +12188,25 @@ react-textarea-autosize@^7.1.0:
1218812188
"@babel/runtime" "^7.1.2"
1218912189
prop-types "^15.6.0"
1219012190

12191+
react-toastify@6.0.5:
12192+
version "6.0.5"
12193+
resolved "https://registry.yarnpkg.com/react-toastify/-/react-toastify-6.0.5.tgz#6435b2bf6a298863bc71342dcc88e8283cdb4630"
12194+
integrity sha512-1YXSb6Jr478c1TJEyVpxLHFvtmeXGMvdpZc0fke/7lK+MoLBC+NFgB74bq+C2SZe6LdK+K1voEURJoY88WqWvA==
12195+
dependencies:
12196+
classnames "^2.2.6"
12197+
prop-types "^15.7.2"
12198+
react-transition-group "^4.4.1"
12199+
12200+
react-transition-group@^4.4.1:
12201+
version "4.4.1"
12202+
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.1.tgz#63868f9325a38ea5ee9535d828327f85773345c9"
12203+
integrity sha512-Djqr7OQ2aPUiYurhPalTrVy9ddmFCCzwhqQmtN+J3+3DzLO209Fdr70QrN8Z3DsglWql6iY1lDWAfpFiBtuKGw==
12204+
dependencies:
12205+
"@babel/runtime" "^7.5.5"
12206+
dom-helpers "^5.0.1"
12207+
loose-envify "^1.4.0"
12208+
prop-types "^15.6.2"
12209+
1219112210
react-virtualized@^9.21.2:
1219212211
version "9.21.2"
1219312212
resolved "https://registry.yarnpkg.com/react-virtualized/-/react-virtualized-9.21.2.tgz#02e6df65c1e020c8dbf574ec4ce971652afca84e"

0 commit comments

Comments
 (0)