Skip to content

Commit f7984b0

Browse files
committed
feat(neuron-ui): add notification panel
show all warnings and alerts in the notification panel
1 parent 2d597ea commit f7984b0

9 files changed

Lines changed: 223 additions & 83 deletions

File tree

packages/neuron-ui/src/components/NetworkEditor/hooks.ts

Lines changed: 32 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useState, useEffect, useMemo, useCallback } from 'react'
22

3-
import { AppActions, StateDispatch } from 'states/stateProvider/reducer'
4-
import { createNetwork, updateNetwork } from 'states/stateProvider/actionCreators'
3+
import { StateDispatch } from 'states/stateProvider/reducer'
4+
import { createNetwork, updateNetwork, addNotification } from 'states/stateProvider/actionCreators'
55

66
import { Message, MAX_NETWORK_NAME_LENGTH } from 'utils/const'
77

@@ -68,15 +68,9 @@ export const useInitialize = (
6868
if (network) {
6969
initialize(network)
7070
} else {
71-
dispatch({
72-
type: AppActions.AddNotification,
73-
payload: {
74-
networks: {
75-
type: 'warning',
76-
timestamp: Date.now(),
77-
content: i18n.t('messages.network-is-not-found'),
78-
},
79-
},
71+
addNotification({
72+
type: 'warning',
73+
content: i18n.t('messages.network-is-not-found'),
8074
})
8175
}
8276
}
@@ -126,72 +120,54 @@ export const useHandleSubmit = (
126120
) =>
127121
useCallback(async () => {
128122
const warning = {
129-
type: 'warning',
123+
type: 'warning' as 'warning',
130124
timestamp: Date.now(),
131125
content: '',
132126
}
133127
if (!name) {
134-
return dispatch({
135-
type: AppActions.AddNotification,
136-
payload: {
137-
...warning,
138-
content: i18n.t(Message.NameRequired),
139-
},
140-
})
128+
return addNotification({
129+
...warning,
130+
content: i18n.t(Message.NameRequired),
131+
})(dispatch)
141132
}
142133
if (name.length > MAX_NETWORK_NAME_LENGTH) {
143-
return dispatch({
144-
type: AppActions.AddNotification,
145-
payload: {
146-
...warning,
147-
content: i18n.t(Message.LengthOfNameShouldBeLessThanOrEqualTo, {
148-
length: MAX_NETWORK_NAME_LENGTH,
149-
}),
150-
},
151-
})
134+
return addNotification({
135+
...warning,
136+
content: i18n.t(Message.LengthOfNameShouldBeLessThanOrEqualTo, {
137+
length: MAX_NETWORK_NAME_LENGTH,
138+
}),
139+
})(dispatch)
152140
}
153141
if (!remote) {
154-
return dispatch({
155-
type: AppActions.AddNotification,
156-
payload: {
157-
...warning,
158-
content: i18n.t(Message.URLRequired),
159-
},
160-
})
142+
return addNotification({
143+
...warning,
144+
content: i18n.t(Message.URLRequired),
145+
})(dispatch)
161146
}
162147
if (!remote.startsWith('http')) {
163-
return dispatch({
164-
type: AppActions.AddNotification,
165-
payload: {
166-
...warning,
167-
content: i18n.t(Message.ProtocolRequired),
168-
},
169-
})
148+
return addNotification({
149+
...warning,
150+
content: i18n.t(Message.ProtocolRequired),
151+
})(dispatch)
170152
}
171153
// verification, for now, only name is unique
172154
if (id === 'new') {
173155
if (networks.some(network => network.name === name)) {
174-
return dispatch({
175-
type: AppActions.AddNotification,
176-
payload: {
177-
...warning,
178-
content: i18n.t(Message.NetworkNameUsed),
179-
},
180-
})
156+
return addNotification({
157+
...warning,
158+
content: i18n.t(Message.NetworkNameUsed),
159+
})(dispatch)
181160
}
182161
return createNetwork({
183162
name,
184163
remote,
185164
})(dispatch, history)
186165
}
187166
if (networks.some(network => network.name === name && network.id !== id)) {
188-
return dispatch({
189-
type: AppActions.AddNotification,
190-
payload: {
191-
...warning,
192-
content: i18n.t(Message.NetworkNameUsed),
193-
},
194-
})
167+
return addNotification({
168+
...warning,
169+
content: i18n.t(Message.NetworkNameUsed),
170+
})(dispatch)
195171
}
196172
return updateNetwork({
197173
networkID: id!,

packages/neuron-ui/src/containers/Notification/index.tsx

Lines changed: 105 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
1-
import React, { useContext, useCallback } from 'react'
1+
import React, { useContext, useMemo, useCallback, MouseEventHandler } from 'react'
22
import { createPortal } from 'react-dom'
33
import { RouteComponentProps } from 'react-router-dom'
44
import { useTranslation } from 'react-i18next'
5-
import { MessageBar, MessageBarType, IconButton } from 'office-ui-fabric-react'
5+
import { Stack, MessageBar, MessageBarType, IconButton, Panel, PanelType, Text } from 'office-ui-fabric-react'
66
import { NeuronWalletContext } from 'states/stateProvider'
7-
import { StateWithDispatch, AppActions } from 'states/stateProvider/reducer'
7+
import { StateWithDispatch, StateDispatch } from 'states/stateProvider/reducer'
8+
import {
9+
toggleAllNotificationVisibility,
10+
toggleTopAlertVisibility,
11+
dismissNotification,
12+
} from 'states/stateProvider/actionCreators'
813
import styles from './Notification.module.scss'
914

1015
const notificationType = (type: 'success' | 'warning' | 'alert') => {
@@ -24,27 +29,61 @@ const notificationType = (type: 'success' | 'warning' | 'alert') => {
2429
}
2530
}
2631

27-
const DismissButton = ({ onDismiss }: { onDismiss: React.MouseEventHandler<HTMLButtonElement> }) => (
32+
const DismissTopAlertButton = ({ onDismiss }: { onDismiss: React.MouseEventHandler<HTMLButtonElement> }) => (
2833
<IconButton iconProps={{ iconName: 'Dismiss' }} onClick={onDismiss} />
2934
)
3035

31-
const NoticeContent = ({ dispatch }: React.PropsWithoutRef<StateWithDispatch & RouteComponentProps>) => {
36+
const TopAlertActions = ({
37+
count = 0,
38+
dispatch,
39+
onDismiss,
40+
}: {
41+
count: number
42+
dispatch: StateDispatch
43+
onDismiss: MouseEventHandler
44+
}) => (
45+
<Stack horizontal verticalAlign="center">
46+
{count > 1 ? (
47+
<IconButton
48+
iconProps={{ iconName: 'More' }}
49+
onClick={() => {
50+
toggleAllNotificationVisibility()(dispatch)
51+
}}
52+
>
53+
more
54+
</IconButton>
55+
) : null}
56+
<DismissTopAlertButton onDismiss={onDismiss} />
57+
</Stack>
58+
)
59+
60+
export const NoticeContent = ({ dispatch }: React.PropsWithoutRef<StateWithDispatch & RouteComponentProps>) => {
3261
const {
33-
app: { notifications = [], popups = [] },
62+
app: { notifications = [], popups = [], showTopAlert = false, showAllNotifications = false },
3463
} = useContext(NeuronWalletContext)
3564
const [t] = useTranslation()
36-
const onDismiss = useCallback(() => {
37-
dispatch({
38-
type: AppActions.ClearNotifications,
39-
payload: null,
40-
})
65+
66+
const notificationsInDesc = useMemo(() => [...notifications].reverse(), [notifications])
67+
const notification: State.Message | undefined = notificationsInDesc[0]
68+
69+
const onTopAlertDismiss = useCallback(() => {
70+
toggleTopAlertVisibility(false)(dispatch)
4171
}, [dispatch])
4272

43-
const notification = notifications[0]
73+
const onPanelDismiss = useCallback(() => {
74+
toggleAllNotificationVisibility()(dispatch)
75+
}, [dispatch])
76+
77+
const onNotificationDismiss = useCallback(
78+
(timestamp: number) => () => {
79+
dismissNotification(timestamp)(dispatch)
80+
},
81+
[dispatch]
82+
)
4483

4584
return (
4685
<div>
47-
{notifications.length ? (
86+
{showTopAlert && notificationsInDesc.length ? (
4887
<MessageBar
4988
messageBarType={notificationType(notification.type)}
5089
styles={{
@@ -56,18 +95,70 @@ const NoticeContent = ({ dispatch }: React.PropsWithoutRef<StateWithDispatch & R
5695
marginRight: '12px',
5796
},
5897
}}
59-
actions={<DismissButton onDismiss={onDismiss} />}
98+
actions={
99+
<TopAlertActions dispatch={dispatch} count={notificationsInDesc.length} onDismiss={onTopAlertDismiss} />
100+
}
60101
>
61102
{t(notification.content)}
62103
</MessageBar>
63104
) : null}
105+
64106
<div className={styles.autoDismissMessages}>
65107
{popups.map(popup => (
66108
<MessageBar key={`${popup.timestamp}`} messageBarType={MessageBarType.success}>
67109
{t(popup.text)}
68110
</MessageBar>
69111
))}
70112
</div>
113+
114+
<Panel
115+
isOpen={showAllNotifications}
116+
type={PanelType.smallFixedFar}
117+
onDismiss={onPanelDismiss}
118+
headerText={t('notification-panel.title')}
119+
isHiddenOnDismiss
120+
isLightDismiss
121+
styles={{
122+
header: {
123+
padding: '0 5px!important',
124+
},
125+
content: {
126+
padding: '0!important',
127+
},
128+
navigation: {
129+
display: 'none',
130+
},
131+
}}
132+
>
133+
{notificationsInDesc.map(n => {
134+
return (
135+
<Stack
136+
key={n.timestamp}
137+
styles={{
138+
root: {
139+
padding: '5px 15px',
140+
boxShadow: '1px 1px 3px rgba(0,0,0,0.1)',
141+
borderLeft: `4px solid`,
142+
borderLeftColor: n.type === 'warning' ? '#fff176ba' : '#ff3d00ba',
143+
margin: '10px 0',
144+
},
145+
}}
146+
>
147+
<Stack horizontal horizontalAlign="space-between" verticalAlign="center">
148+
<Text
149+
styles={{
150+
root: [{ textTransform: 'capitalize' }],
151+
}}
152+
>
153+
{t(`message-types.${n.type}`)}
154+
</Text>
155+
<IconButton iconProps={{ iconName: 'Dismiss' }} onClick={onNotificationDismiss(n.timestamp)} />
156+
</Stack>
157+
<Text as="p">{t(n.content)}</Text>
158+
</Stack>
159+
)
160+
})}
161+
</Panel>
71162
</div>
72163
)
73164
}

packages/neuron-ui/src/locales/en.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,14 @@
193193
"cancel": "Cancel",
194194
"save": "Save"
195195
},
196+
"notification-panel": {
197+
"title": "Notifications"
198+
},
199+
"message-types": {
200+
"warning": "warning",
201+
"alert": "alert",
202+
"success": "success"
203+
},
196204
"messages": {
197205
"at-least-one-address-needed": "At least one address needed",
198206
"name-required": "Name is required",

packages/neuron-ui/src/locales/zh.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,14 @@
193193
"cancel": "取消",
194194
"save": "保存"
195195
},
196+
"notification-panel": {
197+
"title": "通知中心"
198+
},
199+
"message-types": {
200+
"warning": "警告",
201+
"alert": "错误",
202+
"success": "成功"
203+
},
196204
"messages": {
197205
"at-least-one-address-needed": "需要至少一个地址",
198206
"name-required": "缺少名称",

packages/neuron-ui/src/states/initStates/app.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ const appState: State.App = {
3939
transactionList: false,
4040
updateDescription: false,
4141
},
42+
showTopAlert: false,
43+
showAllNotifications: false,
4244
}
4345

4446
export default appState

packages/neuron-ui/src/states/stateProvider/actionCreators/app.ts

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,22 @@ export const initAppState = () => (dispatch: StateDispatch, history: any) => {
7474
})
7575
}
7676

77-
export const addNotification = ({ type, content }: { type: 'alert'; content: string }) => (dispatch: StateDispatch) => {
77+
export const addPopup = (text: string) => (dispatch: StateDispatch) => {
78+
dispatch({
79+
type: AppActions.PopIn,
80+
payload: { text: `messages.${text}`, timestamp: Date.now() },
81+
})
82+
setTimeout(() => {
83+
dispatch({
84+
type: AppActions.PopOut,
85+
payload: null,
86+
})
87+
}, 8000)
88+
}
89+
90+
export const addNotification = ({ type, content }: { type: 'alert' | 'warning'; content: string }) => (
91+
dispatch: StateDispatch
92+
) => {
7893
dispatch({
7994
type: AppActions.AddNotification,
8095
payload: {
@@ -84,22 +99,32 @@ export const addNotification = ({ type, content }: { type: 'alert'; content: str
8499
},
85100
})
86101
}
102+
export const dismissNotification = (timestamp: number) => (dispatch: StateDispatch) => {
103+
dispatch({
104+
type: AppActions.DismissNotification,
105+
payload: timestamp,
106+
})
107+
}
87108

88-
export const addPopup = (text: string) => (dispatch: StateDispatch) => {
109+
export const toggleTopAlertVisibility = (show?: boolean) => (dispatch: StateDispatch) => {
89110
dispatch({
90-
type: AppActions.PopIn,
91-
payload: { text: `messages.${text}`, timestamp: Date.now() },
111+
type: AppActions.ToggleTopAlertVisibility,
112+
payload: show,
113+
})
114+
}
115+
116+
export const toggleAllNotificationVisibility = (show?: boolean) => (dispatch: StateDispatch) => {
117+
dispatch({
118+
type: AppActions.ToggleAllNotificationVisibility,
119+
payload: show,
92120
})
93-
setTimeout(() => {
94-
dispatch({
95-
type: AppActions.PopOut,
96-
payload: null,
97-
})
98-
}, 8000)
99121
}
100122

101123
export default {
102124
initAppState,
103125
addNotification,
104126
addPopup,
127+
dismissNotification,
128+
toggleTopAlertVisibility,
129+
toggleAllNotificationVisibility,
105130
}

0 commit comments

Comments
 (0)