diff options
| author | Oskar Nyberg <oskar@mullvad.net> | 2023-02-21 18:46:12 +0100 |
|---|---|---|
| committer | Oskar Nyberg <oskar@mullvad.net> | 2023-02-21 18:46:12 +0100 |
| commit | 26a81c00ba35e12af86d48985de0cfc98032a3fe (patch) | |
| tree | 6a742e4f548772b107f5b9ce71b2847244cbbca6 | |
| parent | 68b96d792092acf0e5127684201a85d7628c0632 (diff) | |
| parent | 16d8551c7119aa14364e90253b051b7a26ed7653 (diff) | |
| download | mullvadvpn-26a81c00ba35e12af86d48985de0cfc98032a3fe.tar.xz mullvadvpn-26a81c00ba35e12af86d48985de0cfc98032a3fe.zip | |
Merge branch 'improve-in-app-notifications'
| -rw-r--r-- | CHANGELOG.md | 1 | ||||
| -rw-r--r-- | gui/locales/messages.pot | 53 | ||||
| -rw-r--r-- | gui/src/renderer/components/KeyboardNavigation.tsx | 41 | ||||
| -rw-r--r-- | gui/src/renderer/components/NavigationBar.tsx | 11 | ||||
| -rw-r--r-- | gui/src/renderer/components/NotificationArea.tsx | 97 | ||||
| -rw-r--r-- | gui/src/renderer/components/NotificationBanner.tsx | 37 | ||||
| -rw-r--r-- | gui/src/renderer/components/Settings.tsx | 2 | ||||
| -rw-r--r-- | gui/src/renderer/components/select-location/SelectLocation.tsx | 2 | ||||
| -rw-r--r-- | gui/src/shared/localization-contexts.ts | 3 | ||||
| -rw-r--r-- | gui/src/shared/notifications/error.ts | 74 | ||||
| -rw-r--r-- | gui/src/shared/notifications/notification.ts | 14 |
11 files changed, 278 insertions, 57 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index bd070d284b..5839eba589 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ Line wrap the file at 100 chars. Th - Add Kyber1024 KEM algorithm into the Post-Quantum secure key exchange algorithm. This means the Quantum-resistant-tunnels feature now mixes both Classic McEliece and Kyber for added protection. - Add notification dot to tray icon and system notification throttling. +- Add troubleshooting information to some in-app notifications. ### Changed - Update the Post-Quantum secure key exchange gRPC client to use the stabilized diff --git a/gui/locales/messages.pot b/gui/locales/messages.pot index 3fe9fc4570..da6569a449 100644 --- a/gui/locales/messages.pot +++ b/gui/locales/messages.pot @@ -215,6 +215,9 @@ msgstr "" msgid "This setting increases latency. Use only if needed." msgstr "" +msgid "Troubleshoot" +msgstr "" + msgid "Try a different search." msgstr "" @@ -602,6 +605,10 @@ msgctxt "in-app-notifications" msgid "Please quit and restart the app." msgstr "" +msgctxt "in-app-notifications" +msgid "Send problem report" +msgstr "" + #. The in-app banner displayed to the user when the app beta update is #. available. #. Available placeholders: @@ -779,7 +786,7 @@ msgid "Lockdown mode active, connection blocked" msgstr "" msgctxt "notifications" -msgid "No servers in your selected location match your settings." +msgid "No servers match your settings, try changing server or other settings." msgstr "" msgctxt "notifications" @@ -795,7 +802,7 @@ msgid "Unable to apply firewall rules." msgstr "" msgctxt "notifications" -msgid "Unable to apply firewall rules. Try disabling any third-party antivirus or security software." +msgid "Unable to apply firewall rules. Try temporarily disabling any third-party antivirus or security software." msgstr "" msgctxt "notifications" @@ -807,7 +814,7 @@ msgid "Unable to block all network traffic. Please troubleshoot or send a proble msgstr "" msgctxt "notifications" -msgid "Unable to block all network traffic. Try disabling any third-party antivirus or security software or send a problem report." +msgid "Unable to block all network traffic. Try temporarily disabling any third-party antivirus or security software or send a problem report." msgstr "" msgctxt "notifications" @@ -1231,6 +1238,46 @@ msgctxt "tray-icon-tooltip" msgid "Connecting. %(location)s" msgstr "" +msgctxt "troubleshoot" +msgid "If these steps do not work please send a problem report." +msgstr "" + +msgctxt "troubleshoot" +msgid "Make sure you have NF tables support." +msgstr "" + +msgctxt "troubleshoot" +msgid "This can happen because the kernel is old, or if you have removed a kernel." +msgstr "" + +msgctxt "troubleshoot" +msgid "This error can happen when something other than Mullvad is actively updating the DNS." +msgstr "" + +msgctxt "troubleshoot" +msgid "Try reconnecting." +msgstr "" + +msgctxt "troubleshoot" +msgid "Try restarting your device." +msgstr "" + +msgctxt "troubleshoot" +msgid "Try to turn Wi-Fi Calling off in the FaceTime app settings and restart the Mac." +msgstr "" + +msgctxt "troubleshoot" +msgid "Unable to communicate with Mullvad kernel driver." +msgstr "" + +msgctxt "troubleshoot" +msgid "Uninstall or disable other DNS, networking and ads/website blocking apps." +msgstr "" + +msgctxt "troubleshoot" +msgid "Update your kernel." +msgstr "" + msgctxt "tunnel-control" msgid "Secure my connection" msgstr "" diff --git a/gui/src/renderer/components/KeyboardNavigation.tsx b/gui/src/renderer/components/KeyboardNavigation.tsx index 8e8bae5faf..b98f57b4ce 100644 --- a/gui/src/renderer/components/KeyboardNavigation.tsx +++ b/gui/src/renderer/components/KeyboardNavigation.tsx @@ -11,7 +11,7 @@ interface IKeyboardNavigationProps { // Listens for and handles keyboard shortcuts export default function KeyboardNavigation(props: IKeyboardNavigationProps) { const history = useHistory(); - const [backAction, setBackAction] = useState<IBackActionConfiguration>(); + const [backAction, setBackAction] = useState<BackActionFn>(); const location = useLocation(); const handleKeyDown = useCallback( @@ -22,7 +22,7 @@ export default function KeyboardNavigation(props: IKeyboardNavigationProps) { if (event.shiftKey) { history.pop(true); } else { - backAction?.action(); + backAction?.(); } } } @@ -38,18 +38,12 @@ export default function KeyboardNavigation(props: IKeyboardNavigationProps) { return <BackActionTracker registerBackAction={setBackAction}>{props.children}</BackActionTracker>; } -type BackActionIcon = 'back' | 'close'; type BackActionFn = () => void; -interface IBackActionConfiguration { - icon: BackActionIcon; - action: BackActionFn; -} - interface IBackActionContext { - parentBackAction?: IBackActionConfiguration; - registerBackAction: (backAction: IBackActionConfiguration) => void; - removeBackAction: (backAction: IBackActionConfiguration) => void; + parentBackAction?: BackActionFn; + registerBackAction: (backAction: BackActionFn) => void; + removeBackAction: (backAction: BackActionFn) => void; } export const BackActionContext = React.createContext<IBackActionContext>({ @@ -63,7 +57,6 @@ export const BackActionContext = React.createContext<IBackActionContext>({ interface IBackActionProps { disabled?: boolean; - icon?: BackActionIcon; action: BackActionFn; children: React.ReactNode; } @@ -72,13 +65,9 @@ interface IBackActionProps { // either by pressing the back button in the navigation bar or by pressing escape. export function BackAction(props: IBackActionProps) { const backActionContext = useContext(BackActionContext); - const [childrenBackAction, setChildrenBackAction] = useState<IBackActionConfiguration>(); + const [childrenBackAction, setChildrenBackAction] = useState<BackActionFn>(); - const parentBackAction = useMemo<IBackActionConfiguration>( - () => ({ icon: props.icon ?? 'back', action: props.action }), - [props.icon, props.action], - ); - const backActionConfiguration = childrenBackAction ?? parentBackAction; + const backActionConfiguration = childrenBackAction ?? props.action; // Every time the action or the disabled property changes the action needs to be reregistered. useEffect((): (() => void) | void => { @@ -91,29 +80,27 @@ export function BackAction(props: IBackActionProps) { // Every back action keeps track of the back actions in its subtree. This makes it possible to // always use the action furthest down in the tree. return ( - <BackActionTracker - registerBackAction={setChildrenBackAction} - parentBackAction={parentBackAction}> + <BackActionTracker registerBackAction={setChildrenBackAction} parentBackAction={props.action}> {props.children} </BackActionTracker> ); } interface IBackActionTracker { - parentBackAction?: IBackActionConfiguration; - registerBackAction: (backAction: IBackActionConfiguration | undefined) => void; + parentBackAction?: BackActionFn; + registerBackAction: (backAction: BackActionFn | undefined) => void; children: React.ReactNode; } // This component keeps track of all registered back actions in it's subtree and reports one of them // to it's parent. function BackActionTracker(props: IBackActionTracker) { - const [backActions, setBackActions] = useState<Array<IBackActionConfiguration>>([]); + const [backActions, setBackActions] = useState<Array<BackActionFn>>([]); - const registerBackAction = useCallback((backAction: IBackActionConfiguration) => { + const registerBackAction = useCallback((backAction: BackActionFn) => { setBackActions((backActions) => [...backActions, backAction]); }, []); - const removeBackAction = useCallback((backAction: IBackActionConfiguration) => { + const removeBackAction = useCallback((backAction: BackActionFn) => { setBackActions((backActions) => backActions.filter((action) => action !== backAction)); }, []); const backActionContext = useMemo( @@ -121,7 +108,7 @@ function BackActionTracker(props: IBackActionTracker) { [backActions], ); - useEffect(() => props.registerBackAction(backActions.at(0)), [backActions]); + useEffect(() => props.registerBackAction(() => backActions.at(0)), [backActions]); return ( <BackActionContext.Provider value={backActionContext}> diff --git a/gui/src/renderer/components/NavigationBar.tsx b/gui/src/renderer/components/NavigationBar.tsx index 317a9bd26e..56c3d2b8a3 100644 --- a/gui/src/renderer/components/NavigationBar.tsx +++ b/gui/src/renderer/components/NavigationBar.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useContext, useEffect, useLayoutEffect, useRef } from 'react'; +import React, { useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef } from 'react'; import { colors } from '../../config.json'; import { messages } from '../../shared/gettext'; @@ -184,13 +184,14 @@ export const TitleBarItem = React.memo(function TitleBarItemT(props: ITitleBarIt }); export function BackBarItem() { + const history = useHistory(); + const backIcon = useMemo(() => history.length > 2, []); const { parentBackAction } = useContext(BackActionContext); - const iconSource = parentBackAction?.icon === 'back' ? 'icon-back' : 'icon-close-down'; - const ariaLabel = - parentBackAction?.icon === 'back' ? messages.gettext('Back') : messages.gettext('Close'); + const iconSource = backIcon ? 'icon-back' : 'icon-close-down'; + const ariaLabel = backIcon ? messages.gettext('Back') : messages.gettext('Close'); return ( - <StyledBackBarItemButton aria-label={ariaLabel} onClick={parentBackAction?.action}> + <StyledBackBarItemButton aria-label={ariaLabel} onClick={parentBackAction}> <StyledBackBarItemIcon source={iconSource} tintColor={colors.white40} width={24} /> </StyledBackBarItemButton> ); diff --git a/gui/src/renderer/components/NotificationArea.tsx b/gui/src/renderer/components/NotificationArea.tsx index 317cc1e923..4923bacc7c 100644 --- a/gui/src/renderer/components/NotificationArea.tsx +++ b/gui/src/renderer/components/NotificationArea.tsx @@ -1,21 +1,29 @@ -import { useCallback } from 'react'; +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 { BlockWhenDisconnectedNotificationProvider, CloseToAccountExpiryNotificationProvider, ConnectingNotificationProvider, ErrorNotificationProvider, + InAppNotificationAction, InAppNotificationProvider, + InAppNotificationTroubleshootInfo, InconsistentVersionNotificationProvider, - NotificationAction, ReconnectingNotificationProvider, UnsupportedVersionNotificationProvider, UpdateAvailableNotificationProvider, } from '../../shared/notifications/notification'; import { useAppContext } from '../context'; +import { transitions, useHistory } from '../lib/history'; +import { RoutePath } from '../lib/routes'; import { IReduxState } from '../redux/store'; +import * as AppButton from './AppButton'; +import { ModalAlert, ModalAlertType, ModalMessage } from './Modal'; import { NotificationActions, NotificationBanner, @@ -24,6 +32,7 @@ import { NotificationOpenLinkAction, NotificationSubtitle, NotificationTitle, + NotificationTroubleshootDialogAction, } from './NotificationBanner'; interface IProps { @@ -92,24 +101,92 @@ export default function NotificationArea(props: IProps) { return <NotificationBanner className={props.className} aria-hidden={true} />; } +const TroubleshootList = styled.ul({ + listStyle: 'disc outside', + paddingLeft: '20px', + color: colors.white80, +}); + interface INotificationActionWrapperProps { - action: NotificationAction; + action: InAppNotificationAction; } function NotificationActionWrapper(props: INotificationActionWrapperProps) { + const history = useHistory(); const { openLinkWithAuth, openUrl } = useAppContext(); + const [troubleshootInfo, setTroubleshootInfo] = useState<InAppNotificationTroubleshootInfo>(); const handleClick = useCallback(() => { - if (props.action.withAuth) { - return openLinkWithAuth(props.action.url); - } else { - return openUrl(props.action.url); + 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; + } } + + 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 = <NotificationOpenLinkAction onClick={handleClick} />; + break; + case 'troubleshoot-dialog': + actionComponent = ( + <> + <NotificationTroubleshootDialogAction onClick={handleClick} /> + </> + ); + break; + } + } + return ( - <NotificationActions> - <NotificationOpenLinkAction onClick={handleClick} /> - </NotificationActions> + <> + <NotificationActions>{actionComponent}</NotificationActions> + <ModalAlert + isOpen={troubleshootInfo !== undefined} + type={ModalAlertType.info} + buttons={[ + <AppButton.GreenButton key="problem-report" onClick={goToProblemReport}> + {messages.pgettext('in-app-notifications', 'Send problem report')} + </AppButton.GreenButton>, + <AppButton.BlueButton key="back" onClick={closeTroubleshootInfo}> + {messages.gettext('Back')} + </AppButton.BlueButton>, + ]} + close={closeTroubleshootInfo}> + <ModalMessage>{troubleshootInfo?.details}</ModalMessage> + <ModalMessage> + <TroubleshootList> + {troubleshootInfo?.steps.map((step) => ( + <li key={step}>{step}</li> + ))} + </TroubleshootList> + </ModalMessage> + <ModalMessage> + {messages.pgettext( + 'troubleshoot', + 'If these steps do not work please send a problem report.', + )} + </ModalMessage> + </ModalAlert> + </> ); } diff --git a/gui/src/renderer/components/NotificationBanner.tsx b/gui/src/renderer/components/NotificationBanner.tsx index 9f8c63e4b2..4f20d315c8 100644 --- a/gui/src/renderer/components/NotificationBanner.tsx +++ b/gui/src/renderer/components/NotificationBanner.tsx @@ -26,7 +26,7 @@ export function NotificationSubtitle(props: INotificationSubtitleProps) { return React.Children.count(props.children) > 0 ? <NotificationSubtitleText {...props} /> : null; } -export const NotificationOpenLinkActionButton = styled(AppButton.SimpleButton)({ +export const NotificationActionButton = styled(AppButton.SimpleButton)({ flex: 1, justifyContent: 'center', cursor: 'default', @@ -36,20 +36,25 @@ export const NotificationOpenLinkActionButton = styled(AppButton.SimpleButton)({ }); export const NotificationOpenLinkActionIcon = styled(ImageView)({ - [NotificationOpenLinkActionButton + ':hover &']: { + [NotificationActionButton + ':hover &']: { + backgroundColor: colors.white80, + }, +}); + +export const NotificationTroubleshootDialogActionIcon = styled(ImageView)({ + [NotificationActionButton + ':hover &']: { backgroundColor: colors.white80, }, }); interface INotifcationOpenLinkActionProps { onClick: () => Promise<void>; - children?: React.ReactNode; } export function NotificationOpenLinkAction(props: INotifcationOpenLinkActionProps) { return ( <AppButton.BlockingButton onClick={props.onClick}> - <NotificationOpenLinkActionButton + <NotificationActionButton aria-describedby={NOTIFICATION_AREA_ID} aria-label={messages.gettext('Open URL')}> <NotificationOpenLinkActionIcon @@ -58,11 +63,33 @@ export function NotificationOpenLinkAction(props: INotifcationOpenLinkActionProp tintColor={colors.white60} source="icon-extLink" /> - </NotificationOpenLinkActionButton> + </NotificationActionButton> </AppButton.BlockingButton> ); } +interface INotifcationTroubleshootDialogActionProps { + onClick: () => Promise<void>; +} + +export function NotificationTroubleshootDialogAction( + props: INotifcationTroubleshootDialogActionProps, +) { + return ( + <NotificationActionButton + aria-describedby={NOTIFICATION_AREA_ID} + aria-label={messages.gettext('Troubleshoot')} + onClick={props.onClick}> + <NotificationOpenLinkActionIcon + height={12} + width={12} + tintColor={colors.white60} + source="icon-info" + /> + </NotificationActionButton> + ); +} + export const NotificationContent = styled.div.attrs({ id: NOTIFICATION_AREA_ID })({ display: 'flex', flexDirection: 'column', diff --git a/gui/src/renderer/components/Settings.tsx b/gui/src/renderer/components/Settings.tsx index 76b4d6b4fd..1a316f81bd 100644 --- a/gui/src/renderer/components/Settings.tsx +++ b/gui/src/renderer/components/Settings.tsx @@ -38,7 +38,7 @@ export default function Support() { const showSubSettings = loginState.type === 'ok' && connectedToDaemon; return ( - <BackAction icon="close" action={history.pop}> + <BackAction action={history.pop}> <Layout> <SettingsContainer> <NavigationContainer> diff --git a/gui/src/renderer/components/select-location/SelectLocation.tsx b/gui/src/renderer/components/select-location/SelectLocation.tsx index 9e9c60b7a5..bc397506a8 100644 --- a/gui/src/renderer/components/select-location/SelectLocation.tsx +++ b/gui/src/renderer/components/select-location/SelectLocation.tsx @@ -117,7 +117,7 @@ export default function SelectLocation() { const showProvidersFilter = providers.length > 0; const showFilters = showOwnershipFilter || showProvidersFilter; return ( - <BackAction icon="close" action={onClose}> + <BackAction action={onClose}> <Layout> <SettingsContainer> <NavigationContainer> diff --git a/gui/src/shared/localization-contexts.ts b/gui/src/shared/localization-contexts.ts index 8e8c3930be..d342a4210e 100644 --- a/gui/src/shared/localization-contexts.ts +++ b/gui/src/shared/localization-contexts.ts @@ -33,4 +33,5 @@ export type LocalizationContexts = | 'support-view' | 'select-language-nav' | 'tray-icon-context-menu' - | 'tray-icon-tooltip'; + | 'tray-icon-tooltip' + | 'troubleshoot'; diff --git a/gui/src/shared/notifications/error.ts b/gui/src/shared/notifications/error.ts index 67267a494e..9ca0921bf6 100644 --- a/gui/src/shared/notifications/error.ts +++ b/gui/src/shared/notifications/error.ts @@ -11,6 +11,7 @@ import { import { messages } from '../gettext'; import { InAppNotification, + InAppNotificationAction, InAppNotificationProvider, SystemNotification, SystemNotificationCategory, @@ -77,6 +78,7 @@ export class ErrorNotificationProvider ? messages.pgettext('in-app-notifications', 'NETWORK TRAFFIC MIGHT BE LEAKING') : messages.pgettext('in-app-notifications', 'BLOCKING INTERNET'), subtitle, + action: getActions(this.context.tunnelState.details) ?? undefined, }; } else { return undefined; @@ -91,7 +93,7 @@ function getMessage(errorState: ErrorState): string { case 'win32': return messages.pgettext( 'notifications', - 'Unable to block all network traffic. Try disabling any third-party antivirus or security software or send a problem report.', + 'Unable to block all network traffic. Try temporarily disabling any third-party antivirus or security software or send a problem report.', ); case 'linux': return messages.pgettext( @@ -141,7 +143,7 @@ function getMessage(errorState: ErrorState): string { case 'win32': return messages.pgettext( 'notifications', - 'Unable to apply firewall rules. Try disabling any third-party antivirus or security software.', + 'Unable to apply firewall rules. Try temporarily disabling any third-party antivirus or security software.', ); case 'linux': return messages.pgettext( @@ -184,7 +186,7 @@ function getTunnelParameterMessage(error: TunnelParameterError): string { case TunnelParameterError.noMatchingRelay: return messages.pgettext( 'notifications', - 'No servers in your selected location match your settings.', + 'No servers match your settings, try changing server or other settings.', ); case TunnelParameterError.noWireguardKey: return sprintf( @@ -203,3 +205,69 @@ function getTunnelParameterMessage(error: TunnelParameterError): string { ); } } + +function getActions(errorState: ErrorState): InAppNotificationAction | void { + const platform = process.platform ?? window.env.platform; + + if (errorState.cause === ErrorStateCause.setFirewallPolicyError && platform === 'linux') { + return { + type: 'troubleshoot-dialog', + troubleshoot: { + details: messages.pgettext( + 'troubleshoot', + 'This can happen because the kernel is old, or if you have removed a kernel.', + ), + steps: [ + messages.pgettext('troubleshoot', 'Update your kernel.'), + messages.pgettext('troubleshoot', 'Make sure you have NF tables support.'), + ], + }, + }; + } else if (errorState.cause === ErrorStateCause.setDnsError) { + const troubleshootSteps = []; + if (platform === 'darwin') { + troubleshootSteps.push( + messages.pgettext( + 'troubleshoot', + 'Try to turn Wi-Fi Calling off in the FaceTime app settings and restart the Mac.', + ), + messages.pgettext( + 'troubleshoot', + 'Uninstall or disable other DNS, networking and ads/website blocking apps.', + ), + ); + } else if (platform === 'win32') { + troubleshootSteps.push( + messages.pgettext( + 'troubleshoot', + 'Uninstall or disable other DNS, networking and ads/website blocking apps.', + ), + ); + } + + return { + type: 'troubleshoot-dialog', + troubleshoot: { + details: messages.pgettext( + 'troubleshoot', + 'This error can happen when something other than Mullvad is actively updating the DNS.', + ), + steps: troubleshootSteps, + }, + }; + } else if (errorState.cause === ErrorStateCause.splitTunnelError) { + return { + type: 'troubleshoot-dialog', + troubleshoot: { + details: messages.pgettext( + 'troubleshoot', + 'Unable to communicate with Mullvad kernel driver.', + ), + steps: [ + messages.pgettext('troubleshoot', 'Try reconnecting.'), + messages.pgettext('troubleshoot', 'Try restarting your device.'), + ], + }, + }; + } +} diff --git a/gui/src/shared/notifications/notification.ts b/gui/src/shared/notifications/notification.ts index 10b5b21fc8..88bddfa9f5 100644 --- a/gui/src/shared/notifications/notification.ts +++ b/gui/src/shared/notifications/notification.ts @@ -5,6 +5,18 @@ export type NotificationAction = { withAuth?: boolean; }; +export interface InAppNotificationTroubleshootInfo { + details: string; + steps: string[]; +} + +export type InAppNotificationAction = + | NotificationAction + | { + type: 'troubleshoot-dialog'; + troubleshoot: InAppNotificationTroubleshootInfo; + }; + export type InAppNotificationIndicatorType = 'success' | 'warning' | 'error'; export enum SystemNotificationSeverityType { @@ -39,7 +51,7 @@ export interface InAppNotification { indicator?: InAppNotificationIndicatorType; title: string; subtitle?: string; - action?: NotificationAction; + action?: InAppNotificationAction; } export interface SystemNotificationProvider extends NotificationProvider { |
