diff --git a/app/soapbox/features/ui/containers/notifications_container.tsx b/app/soapbox/features/ui/containers/notifications_container.tsx index 9cbcc6e98..566acffbb 100644 --- a/app/soapbox/features/ui/containers/notifications_container.tsx +++ b/app/soapbox/features/ui/containers/notifications_container.tsx @@ -1,57 +1,73 @@ import React from 'react'; import { useIntl } from 'react-intl'; import { NotificationStack, NotificationObject, StyleFactoryFn } from 'react-notification'; -import { Link } from 'react-router-dom'; +import { useHistory } from 'react-router-dom'; +import { dismissAlert } from 'soapbox/actions/alerts'; import { Button } from 'soapbox/components/ui'; import { useAppSelector, useAppDispatch } from 'soapbox/hooks'; -import { dismissAlert } from '../../../actions/alerts'; -import { getAlerts } from '../../../selectors'; - -const defaultBarStyleFactory: StyleFactoryFn = (index, style, _notification) => { - return Object.assign( - {}, - style, - { bottom: `${14 + index * 12 + index * 42}px` }, - ); -}; +import type { Alert } from 'soapbox/reducers/alerts'; +/** Portal for snackbar alerts. */ const SnackbarContainer: React.FC = () => { const intl = useIntl(); + const history = useHistory(); const dispatch = useAppDispatch(); - const notifications = useAppSelector(getAlerts); + const alerts = useAppSelector(state => state.alerts); - notifications.forEach(notification => { - ['title', 'message', 'actionLabel'].forEach(key => { - // @ts-ignore - const value = notification[key]; - - if (typeof value === 'object') { - // @ts-ignore - notification[key] = intl.formatMessage(value); - } - }); - - if (notification.action) { - const { action } = notification; - notification.action = ( - - ); - } else if (notification.actionLabel) { - notification.action = ( - - {notification.actionLabel} - - ); + /** Apply i18n to the message if it's an object. */ + const maybeFormatMessage = (message: any): string => { + switch (typeof message) { + case 'string': return message; + case 'object': return intl.formatMessage(message); + default: return ''; } - }); + }; + + /** Convert a reducer Alert into a react-notification object. */ + const buildAlert = (item: Alert): NotificationObject => { + // Backwards-compatibility + if (item.actionLink) { + item = item.set('action', () => history.push(item.actionLink)); + } + + const alert: NotificationObject = { + message: maybeFormatMessage(item.message), + title: maybeFormatMessage(item.title), + key: item.key, + className: `notification-bar-${item.severity}`, + activeClassName: 'snackbar--active', + dismissAfter: 6000, + style: false, + }; + + if (item.action && item.actionLabel) { + // HACK: it's a JSX.Element instead of a string! + // react-notification displays it just fine. + alert.action = ( + + ) as any; + } + + return alert; + }; const onDismiss = (alert: NotificationObject) => { dispatch(dismissAlert(alert)); }; + const defaultBarStyleFactory: StyleFactoryFn = (index, style, _notification) => { + return Object.assign( + {}, + style, + { bottom: `${14 + index * 12 + index * 42}px` }, + ); + }; + + const notifications = alerts.toArray().map(buildAlert); + return (