Skip to content

Commit

Permalink
feat(neuron-ui): add notification panel
Browse files Browse the repository at this point in the history
show all warnings and alerts in the notification panel
  • Loading branch information
Keith-CY committed Jul 31, 2019
1 parent 2d597ea commit f7984b0
Show file tree
Hide file tree
Showing 9 changed files with 223 additions and 83 deletions.
88 changes: 32 additions & 56 deletions packages/neuron-ui/src/components/NetworkEditor/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useState, useEffect, useMemo, useCallback } from 'react'

import { AppActions, StateDispatch } from 'states/stateProvider/reducer'
import { createNetwork, updateNetwork } from 'states/stateProvider/actionCreators'
import { StateDispatch } from 'states/stateProvider/reducer'
import { createNetwork, updateNetwork, addNotification } from 'states/stateProvider/actionCreators'

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

Expand Down Expand Up @@ -68,15 +68,9 @@ export const useInitialize = (
if (network) {
initialize(network)
} else {
dispatch({
type: AppActions.AddNotification,
payload: {
networks: {
type: 'warning',
timestamp: Date.now(),
content: i18n.t('messages.network-is-not-found'),
},
},
addNotification({
type: 'warning',
content: i18n.t('messages.network-is-not-found'),
})
}
}
Expand Down Expand Up @@ -126,72 +120,54 @@ export const useHandleSubmit = (
) =>
useCallback(async () => {
const warning = {
type: 'warning',
type: 'warning' as 'warning',
timestamp: Date.now(),
content: '',
}
if (!name) {
return dispatch({
type: AppActions.AddNotification,
payload: {
...warning,
content: i18n.t(Message.NameRequired),
},
})
return addNotification({
...warning,
content: i18n.t(Message.NameRequired),
})(dispatch)
}
if (name.length > MAX_NETWORK_NAME_LENGTH) {
return dispatch({
type: AppActions.AddNotification,
payload: {
...warning,
content: i18n.t(Message.LengthOfNameShouldBeLessThanOrEqualTo, {
length: MAX_NETWORK_NAME_LENGTH,
}),
},
})
return addNotification({
...warning,
content: i18n.t(Message.LengthOfNameShouldBeLessThanOrEqualTo, {
length: MAX_NETWORK_NAME_LENGTH,
}),
})(dispatch)
}
if (!remote) {
return dispatch({
type: AppActions.AddNotification,
payload: {
...warning,
content: i18n.t(Message.URLRequired),
},
})
return addNotification({
...warning,
content: i18n.t(Message.URLRequired),
})(dispatch)
}
if (!remote.startsWith('http')) {
return dispatch({
type: AppActions.AddNotification,
payload: {
...warning,
content: i18n.t(Message.ProtocolRequired),
},
})
return addNotification({
...warning,
content: i18n.t(Message.ProtocolRequired),
})(dispatch)
}
// verification, for now, only name is unique
if (id === 'new') {
if (networks.some(network => network.name === name)) {
return dispatch({
type: AppActions.AddNotification,
payload: {
...warning,
content: i18n.t(Message.NetworkNameUsed),
},
})
return addNotification({
...warning,
content: i18n.t(Message.NetworkNameUsed),
})(dispatch)
}
return createNetwork({
name,
remote,
})(dispatch, history)
}
if (networks.some(network => network.name === name && network.id !== id)) {
return dispatch({
type: AppActions.AddNotification,
payload: {
...warning,
content: i18n.t(Message.NetworkNameUsed),
},
})
return addNotification({
...warning,
content: i18n.t(Message.NetworkNameUsed),
})(dispatch)
}
return updateNetwork({
networkID: id!,
Expand Down
119 changes: 105 additions & 14 deletions packages/neuron-ui/src/containers/Notification/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import React, { useContext, useCallback } from 'react'
import React, { useContext, useMemo, useCallback, MouseEventHandler } from 'react'
import { createPortal } from 'react-dom'
import { RouteComponentProps } from 'react-router-dom'
import { useTranslation } from 'react-i18next'
import { MessageBar, MessageBarType, IconButton } from 'office-ui-fabric-react'
import { Stack, MessageBar, MessageBarType, IconButton, Panel, PanelType, Text } from 'office-ui-fabric-react'
import { NeuronWalletContext } from 'states/stateProvider'
import { StateWithDispatch, AppActions } from 'states/stateProvider/reducer'
import { StateWithDispatch, StateDispatch } from 'states/stateProvider/reducer'
import {
toggleAllNotificationVisibility,
toggleTopAlertVisibility,
dismissNotification,
} from 'states/stateProvider/actionCreators'
import styles from './Notification.module.scss'

const notificationType = (type: 'success' | 'warning' | 'alert') => {
Expand All @@ -24,27 +29,61 @@ const notificationType = (type: 'success' | 'warning' | 'alert') => {
}
}

const DismissButton = ({ onDismiss }: { onDismiss: React.MouseEventHandler<HTMLButtonElement> }) => (
const DismissTopAlertButton = ({ onDismiss }: { onDismiss: React.MouseEventHandler<HTMLButtonElement> }) => (
<IconButton iconProps={{ iconName: 'Dismiss' }} onClick={onDismiss} />
)

const NoticeContent = ({ dispatch }: React.PropsWithoutRef<StateWithDispatch & RouteComponentProps>) => {
const TopAlertActions = ({
count = 0,
dispatch,
onDismiss,
}: {
count: number
dispatch: StateDispatch
onDismiss: MouseEventHandler
}) => (
<Stack horizontal verticalAlign="center">
{count > 1 ? (
<IconButton
iconProps={{ iconName: 'More' }}
onClick={() => {
toggleAllNotificationVisibility()(dispatch)
}}
>
more
</IconButton>
) : null}
<DismissTopAlertButton onDismiss={onDismiss} />
</Stack>
)

export const NoticeContent = ({ dispatch }: React.PropsWithoutRef<StateWithDispatch & RouteComponentProps>) => {
const {
app: { notifications = [], popups = [] },
app: { notifications = [], popups = [], showTopAlert = false, showAllNotifications = false },
} = useContext(NeuronWalletContext)
const [t] = useTranslation()
const onDismiss = useCallback(() => {
dispatch({
type: AppActions.ClearNotifications,
payload: null,
})

const notificationsInDesc = useMemo(() => [...notifications].reverse(), [notifications])
const notification: State.Message | undefined = notificationsInDesc[0]

const onTopAlertDismiss = useCallback(() => {
toggleTopAlertVisibility(false)(dispatch)
}, [dispatch])

const notification = notifications[0]
const onPanelDismiss = useCallback(() => {
toggleAllNotificationVisibility()(dispatch)
}, [dispatch])

const onNotificationDismiss = useCallback(
(timestamp: number) => () => {
dismissNotification(timestamp)(dispatch)
},
[dispatch]
)

return (
<div>
{notifications.length ? (
{showTopAlert && notificationsInDesc.length ? (
<MessageBar
messageBarType={notificationType(notification.type)}
styles={{
Expand All @@ -56,18 +95,70 @@ const NoticeContent = ({ dispatch }: React.PropsWithoutRef<StateWithDispatch & R
marginRight: '12px',
},
}}
actions={<DismissButton onDismiss={onDismiss} />}
actions={
<TopAlertActions dispatch={dispatch} count={notificationsInDesc.length} onDismiss={onTopAlertDismiss} />
}
>
{t(notification.content)}
</MessageBar>
) : null}

<div className={styles.autoDismissMessages}>
{popups.map(popup => (
<MessageBar key={`${popup.timestamp}`} messageBarType={MessageBarType.success}>
{t(popup.text)}
</MessageBar>
))}
</div>

<Panel
isOpen={showAllNotifications}
type={PanelType.smallFixedFar}
onDismiss={onPanelDismiss}
headerText={t('notification-panel.title')}
isHiddenOnDismiss
isLightDismiss
styles={{
header: {
padding: '0 5px!important',
},
content: {
padding: '0!important',
},
navigation: {
display: 'none',
},
}}
>
{notificationsInDesc.map(n => {
return (
<Stack
key={n.timestamp}
styles={{
root: {
padding: '5px 15px',
boxShadow: '1px 1px 3px rgba(0,0,0,0.1)',
borderLeft: `4px solid`,
borderLeftColor: n.type === 'warning' ? '#fff176ba' : '#ff3d00ba',
margin: '10px 0',
},
}}
>
<Stack horizontal horizontalAlign="space-between" verticalAlign="center">
<Text
styles={{
root: [{ textTransform: 'capitalize' }],
}}
>
{t(`message-types.${n.type}`)}
</Text>
<IconButton iconProps={{ iconName: 'Dismiss' }} onClick={onNotificationDismiss(n.timestamp)} />
</Stack>
<Text as="p">{t(n.content)}</Text>
</Stack>
)
})}
</Panel>
</div>
)
}
Expand Down
8 changes: 8 additions & 0 deletions packages/neuron-ui/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,14 @@
"cancel": "Cancel",
"save": "Save"
},
"notification-panel": {
"title": "Notifications"
},
"message-types": {
"warning": "warning",
"alert": "alert",
"success": "success"
},
"messages": {
"at-least-one-address-needed": "At least one address needed",
"name-required": "Name is required",
Expand Down
8 changes: 8 additions & 0 deletions packages/neuron-ui/src/locales/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,14 @@
"cancel": "取消",
"save": "保存"
},
"notification-panel": {
"title": "通知中心"
},
"message-types": {
"warning": "警告",
"alert": "错误",
"success": "成功"
},
"messages": {
"at-least-one-address-needed": "需要至少一个地址",
"name-required": "缺少名称",
Expand Down
2 changes: 2 additions & 0 deletions packages/neuron-ui/src/states/initStates/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ const appState: State.App = {
transactionList: false,
updateDescription: false,
},
showTopAlert: false,
showAllNotifications: false,
}

export default appState
45 changes: 35 additions & 10 deletions packages/neuron-ui/src/states/stateProvider/actionCreators/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,22 @@ export const initAppState = () => (dispatch: StateDispatch, history: any) => {
})
}

export const addNotification = ({ type, content }: { type: 'alert'; content: string }) => (dispatch: StateDispatch) => {
export const addPopup = (text: string) => (dispatch: StateDispatch) => {
dispatch({
type: AppActions.PopIn,
payload: { text: `messages.${text}`, timestamp: Date.now() },
})
setTimeout(() => {
dispatch({
type: AppActions.PopOut,
payload: null,
})
}, 8000)
}

export const addNotification = ({ type, content }: { type: 'alert' | 'warning'; content: string }) => (
dispatch: StateDispatch
) => {
dispatch({
type: AppActions.AddNotification,
payload: {
Expand All @@ -84,22 +99,32 @@ export const addNotification = ({ type, content }: { type: 'alert'; content: str
},
})
}
export const dismissNotification = (timestamp: number) => (dispatch: StateDispatch) => {
dispatch({
type: AppActions.DismissNotification,
payload: timestamp,
})
}

export const addPopup = (text: string) => (dispatch: StateDispatch) => {
export const toggleTopAlertVisibility = (show?: boolean) => (dispatch: StateDispatch) => {
dispatch({
type: AppActions.PopIn,
payload: { text: `messages.${text}`, timestamp: Date.now() },
type: AppActions.ToggleTopAlertVisibility,
payload: show,
})
}

export const toggleAllNotificationVisibility = (show?: boolean) => (dispatch: StateDispatch) => {
dispatch({
type: AppActions.ToggleAllNotificationVisibility,
payload: show,
})
setTimeout(() => {
dispatch({
type: AppActions.PopOut,
payload: null,
})
}, 8000)
}

export default {
initAppState,
addNotification,
addPopup,
dismissNotification,
toggleTopAlertVisibility,
toggleAllNotificationVisibility,
}
Loading

0 comments on commit f7984b0

Please sign in to comment.