diff --git a/packages/neuron-ui/src/components/NetworkEditor/hooks.ts b/packages/neuron-ui/src/components/NetworkEditor/hooks.ts index 9a261491d7..428d2a985e 100644 --- a/packages/neuron-ui/src/components/NetworkEditor/hooks.ts +++ b/packages/neuron-ui/src/components/NetworkEditor/hooks.ts @@ -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' @@ -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'), }) } } @@ -126,58 +120,43 @@ 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, @@ -185,13 +164,10 @@ export const useHandleSubmit = ( })(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!, diff --git a/packages/neuron-ui/src/containers/Notification/index.tsx b/packages/neuron-ui/src/containers/Notification/index.tsx index 4be4198f60..4dc283d3c2 100644 --- a/packages/neuron-ui/src/containers/Notification/index.tsx +++ b/packages/neuron-ui/src/containers/Notification/index.tsx @@ -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') => { @@ -24,27 +29,61 @@ const notificationType = (type: 'success' | 'warning' | 'alert') => { } } -const DismissButton = ({ onDismiss }: { onDismiss: React.MouseEventHandler }) => ( +const DismissTopAlertButton = ({ onDismiss }: { onDismiss: React.MouseEventHandler }) => ( ) -const NoticeContent = ({ dispatch }: React.PropsWithoutRef) => { +const TopAlertActions = ({ + count = 0, + dispatch, + onDismiss, +}: { + count: number + dispatch: StateDispatch + onDismiss: MouseEventHandler +}) => ( + + {count > 1 ? ( + { + toggleAllNotificationVisibility()(dispatch) + }} + > + more + + ) : null} + + +) + +export const NoticeContent = ({ dispatch }: React.PropsWithoutRef) => { 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 (
- {notifications.length ? ( + {showTopAlert && notificationsInDesc.length ? ( } + actions={ + + } > {t(notification.content)} ) : null} +
{popups.map(popup => ( @@ -68,6 +110,55 @@ const NoticeContent = ({ dispatch }: React.PropsWithoutRef ))}
+ + + {notificationsInDesc.map(n => { + return ( + + + + {t(`message-types.${n.type}`)} + + + + {t(n.content)} + + ) + })} +
) } diff --git a/packages/neuron-ui/src/locales/en.json b/packages/neuron-ui/src/locales/en.json index e71f09f90c..1bc9305d83 100644 --- a/packages/neuron-ui/src/locales/en.json +++ b/packages/neuron-ui/src/locales/en.json @@ -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", diff --git a/packages/neuron-ui/src/locales/zh.json b/packages/neuron-ui/src/locales/zh.json index 8f091709b3..6ddc2204e1 100644 --- a/packages/neuron-ui/src/locales/zh.json +++ b/packages/neuron-ui/src/locales/zh.json @@ -193,6 +193,14 @@ "cancel": "取消", "save": "保存" }, + "notification-panel": { + "title": "通知中心" + }, + "message-types": { + "warning": "警告", + "alert": "错误", + "success": "成功" + }, "messages": { "at-least-one-address-needed": "需要至少一个地址", "name-required": "缺少名称", diff --git a/packages/neuron-ui/src/states/initStates/app.ts b/packages/neuron-ui/src/states/initStates/app.ts index 60c013c9cf..87cd6293f7 100644 --- a/packages/neuron-ui/src/states/initStates/app.ts +++ b/packages/neuron-ui/src/states/initStates/app.ts @@ -39,6 +39,8 @@ const appState: State.App = { transactionList: false, updateDescription: false, }, + showTopAlert: false, + showAllNotifications: false, } export default appState diff --git a/packages/neuron-ui/src/states/stateProvider/actionCreators/app.ts b/packages/neuron-ui/src/states/stateProvider/actionCreators/app.ts index 45eba47efb..681b15be3f 100644 --- a/packages/neuron-ui/src/states/stateProvider/actionCreators/app.ts +++ b/packages/neuron-ui/src/states/stateProvider/actionCreators/app.ts @@ -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: { @@ -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, } diff --git a/packages/neuron-ui/src/states/stateProvider/reducer.ts b/packages/neuron-ui/src/states/stateProvider/reducer.ts index e031c830f6..1547ce6a5c 100644 --- a/packages/neuron-ui/src/states/stateProvider/reducer.ts +++ b/packages/neuron-ui/src/states/stateProvider/reducer.ts @@ -30,7 +30,7 @@ export enum AppActions { UpdateSendLoading = 'updateSendLoading', UpdateMessage = 'updateMessage', AddNotification = 'addNotification', - RemoveNotification = 'removeNotification', + DismissNotification = 'dismissNotification', ClearNotifications = 'clearNotifications', CleanTransaction = 'cleanTransaction', CleanTransactions = 'cleanTransactions', @@ -43,6 +43,8 @@ export enum AppActions { PopIn = 'popIn', PopOut = 'popOut', + ToggleTopAlertVisibility = 'toggleTopAlertVisibility', + ToggleAllNotificationVisibility = 'toggleAllNotificationVisibility', Ignore = 'ignore', } @@ -404,11 +406,12 @@ export const reducer = ( ...state, app: { ...app, - notifications: [payload], + notifications: [...app.notifications, payload], + showTopAlert: true, }, } } - case AppActions.RemoveNotification: { + case AppActions.DismissNotification: { /** * payload: timstamp */ @@ -420,6 +423,7 @@ export const reducer = ( ...app.messages, }, notifications: app.notifications.filter(({ timestamp }) => timestamp !== payload), + showAllNotifications: app.notifications.length > 1, }, } } @@ -474,6 +478,26 @@ export const reducer = ( }, } } + case AppActions.ToggleTopAlertVisibility: { + const showTopAlert = payload === undefined ? !app.showTopAlert : payload + return { + ...state, + app: { + ...app, + showTopAlert, + notifications: showTopAlert ? app.notifications : app.notifications.slice(0, -1), + }, + } + } + case AppActions.ToggleAllNotificationVisibility: { + return { + ...state, + app: { + ...app, + showAllNotifications: payload === undefined ? !app.showAllNotifications : payload, + }, + } + } default: { return state } diff --git a/packages/neuron-ui/src/types/App/index.d.ts b/packages/neuron-ui/src/types/App/index.d.ts index 95b77c5bc6..0faefcd3f0 100644 --- a/packages/neuron-ui/src/types/App/index.d.ts +++ b/packages/neuron-ui/src/types/App/index.d.ts @@ -87,6 +87,8 @@ declare namespace State { transactionList: boolean updateDescription: boolean } + showTopAlert: boolean + showAllNotifications: boolean } interface NetworkProperty { diff --git a/packages/neuron-ui/src/utils/loadTheme.tsx b/packages/neuron-ui/src/utils/loadTheme.tsx index 41f631d063..e9f7b1e7eb 100644 --- a/packages/neuron-ui/src/utils/loadTheme.tsx +++ b/packages/neuron-ui/src/utils/loadTheme.tsx @@ -5,6 +5,7 @@ import { AddCircle as AddIcon, Alert as AlertIcon, Checkmark as SuccessIcon, + CircleInformation as InfoIcon, Close as DismissIcon, Copy as CopyIcon, Down as ArrowDownIcon, @@ -17,6 +18,7 @@ import { LinkDown as LinkDownIcon, LinkTop as LinkTopIcon, LinkUp as LinkUpIcon, + More as MoreIcon, Nodes as ConnectedIcon, Scan as ScanIcon, Search as SearchIcon, @@ -47,6 +49,7 @@ const { semanticColors } = theme registerIcons({ icons: { + info: , errorbadge: , completed: , MiniCopy: , @@ -69,6 +72,7 @@ registerIcons({ Connected: , Disconnected: , Updating: , + More: , }, })