import { useCallback, useState } from 'react'; import { useSelector } from 'react-redux'; import styled from 'styled-components'; import { colors } from '../../config.json'; import { messages } from '../../shared/gettext'; import log from '../../shared/logging'; import { NewDeviceNotificationProvider } from '../../shared/notifications/new-device'; import { BlockWhenDisconnectedNotificationProvider, CloseToAccountExpiryNotificationProvider, ConnectingNotificationProvider, ErrorNotificationProvider, InAppNotificationAction, InAppNotificationProvider, InAppNotificationTroubleshootInfo, InconsistentVersionNotificationProvider, ReconnectingNotificationProvider, UnsupportedVersionNotificationProvider, UpdateAvailableNotificationProvider, } from '../../shared/notifications/notification'; import { useAppContext } from '../context'; import useActions from '../lib/actionsHook'; import { transitions, useHistory } from '../lib/history'; import { formatHtml } from '../lib/html-formatter'; import { RoutePath } from '../lib/routes'; import accountActions from '../redux/account/actions'; import { IReduxState } from '../redux/store'; import * as AppButton from './AppButton'; import { ModalAlert, ModalAlertType, ModalMessage } from './Modal'; import { NotificationActions, NotificationBanner, NotificationCloseAction, NotificationContent, NotificationIndicator, NotificationOpenLinkAction, NotificationSubtitle, NotificationTitle, NotificationTroubleshootDialogAction, } from './NotificationBanner'; interface IProps { className?: string; } export default function NotificationArea(props: IProps) { const account = useSelector((state: IReduxState) => state.account); const locale = useSelector((state: IReduxState) => state.userInterface.locale); const tunnelState = useSelector((state: IReduxState) => state.connection.status); const version = useSelector((state: IReduxState) => state.version); const blockWhenDisconnected = useSelector( (state: IReduxState) => state.settings.blockWhenDisconnected, ); const hasExcludedApps = useSelector( (state: IReduxState) => state.settings.splitTunneling && state.settings.splitTunnelingApplications.length > 0, ); const { hideNewDeviceBanner } = useActions(accountActions); const notificationProviders: InAppNotificationProvider[] = [ new ConnectingNotificationProvider({ tunnelState }), new ReconnectingNotificationProvider(tunnelState), new BlockWhenDisconnectedNotificationProvider({ tunnelState, blockWhenDisconnected, hasExcludedApps, }), new ErrorNotificationProvider({ tunnelState, hasExcludedApps }), new InconsistentVersionNotificationProvider({ consistent: version.consistent }), new UnsupportedVersionNotificationProvider(version), ]; if (account.expiry) { notificationProviders.push( new CloseToAccountExpiryNotificationProvider({ accountExpiry: account.expiry, locale }), ); } notificationProviders.push( new NewDeviceNotificationProvider({ shouldDisplay: account.status.type === 'ok' && account.status.newDeviceBanner, deviceName: account.deviceName ?? '', close: hideNewDeviceBanner, }), new UpdateAvailableNotificationProvider(version), ); const notificationProvider = notificationProviders.find((notification) => notification.mayDisplay(), ); if (notificationProvider) { const notification = notificationProvider.getInAppNotification(); if (notification) { return ( {notification.title} {formatHtml(notification.subtitle ?? '')} {notification.action && } ); } else { log.error( `Notification providers mayDisplay() returned true but getInAppNotification() returned undefined for ${notificationProvider.constructor.name}`, ); } } return ; } const TroubleshootList = styled.ul({ listStyle: 'disc outside', paddingLeft: '20px', color: colors.white80, }); interface INotificationActionWrapperProps { action: InAppNotificationAction; } function NotificationActionWrapper(props: INotificationActionWrapperProps) { const history = useHistory(); const { openLinkWithAuth, openUrl } = useAppContext(); const [troubleshootInfo, setTroubleshootInfo] = useState(); const handleClick = useCallback(() => { if (props.action) { switch (props.action.type) { case 'open-url': if (props.action.withAuth) { return openLinkWithAuth(props.action.url); } else { return openUrl(props.action.url); } case 'troubleshoot-dialog': setTroubleshootInfo(props.action.troubleshoot); break; case 'close': props.action.close(); break; } } return Promise.resolve(); }, [props.action]); const goToProblemReport = useCallback(() => { setTroubleshootInfo(undefined); history.push(RoutePath.problemReport, { transition: transitions.show }); }, []); const closeTroubleshootInfo = useCallback(() => setTroubleshootInfo(undefined), []); let actionComponent: React.ReactElement | undefined; if (props.action) { switch (props.action.type) { case 'open-url': actionComponent = ; break; case 'troubleshoot-dialog': actionComponent = ( <> ); break; case 'close': actionComponent = ; } } return ( <> {actionComponent} {messages.pgettext('in-app-notifications', 'Send problem report')} , {messages.gettext('Back')} , ]} close={closeTroubleshootInfo}> {troubleshootInfo?.details} {troubleshootInfo?.steps.map((step) => (
  • {step}
  • ))}
    {messages.pgettext( 'troubleshoot', 'If these steps do not work please send a problem report.', )}
    ); }