summaryrefslogtreecommitdiffhomepage
path: root/gui/src
diff options
context:
space:
mode:
authorOskar Nyberg <oskar@mullvad.net>2023-02-20 13:45:44 +0100
committerOskar Nyberg <oskar@mullvad.net>2023-02-21 18:45:59 +0100
commit934bedf2908013ce78d6f00ed7f0d03ab7acd7c4 (patch)
tree221e9d9035550be21f05e2be45dc633a7cebce59 /gui/src
parent4ecd849bd1004d9b7a8bb5cf57ce2d0e7b58855e (diff)
downloadmullvadvpn-934bedf2908013ce78d6f00ed7f0d03ab7acd7c4.tar.xz
mullvadvpn-934bedf2908013ce78d6f00ed7f0d03ab7acd7c4.zip
Add troubleshoot dialog
Diffstat (limited to 'gui/src')
-rw-r--r--gui/src/renderer/components/NotificationArea.tsx97
-rw-r--r--gui/src/renderer/components/NotificationBanner.tsx37
-rw-r--r--gui/src/shared/notifications/error.ts54
-rw-r--r--gui/src/shared/notifications/notification.ts10
4 files changed, 156 insertions, 42 deletions
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/shared/notifications/error.ts b/gui/src/shared/notifications/error.ts
index 4970fccbaa..9ca0921bf6 100644
--- a/gui/src/shared/notifications/error.ts
+++ b/gui/src/shared/notifications/error.ts
@@ -211,15 +211,17 @@ function getActions(errorState: ErrorState): InAppNotificationAction | void {
if (errorState.cause === ErrorStateCause.setFirewallPolicyError && platform === 'linux') {
return {
- type: 'info-dialog',
- details: messages.pgettext(
- 'troubleshoot',
- 'This can happen because the kernel is old, or if you have removed a kernel.',
- ),
- troubleshoot: [
- messages.pgettext('troubleshoot', 'Update your kernel.'),
- messages.pgettext('troubleshoot', 'Make sure you have NF tables support.'),
- ],
+ 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 = [];
@@ -244,24 +246,28 @@ function getActions(errorState: ErrorState): InAppNotificationAction | void {
}
return {
- type: 'info-dialog',
- details: messages.pgettext(
- 'troubleshoot',
- 'This error can happen when something other than Mullvad is actively updating the DNS.',
- ),
- troubleshoot: troubleshootSteps,
+ 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: 'info-dialog',
- details: messages.pgettext(
- 'troubleshoot',
- 'Unable to communicate with Mullvad kernel driver.',
- ),
- troubleshoot: [
- messages.pgettext('troubleshoot', 'Try reconnecting.'),
- messages.pgettext('troubleshoot', 'Try restarting your device.'),
- ],
+ 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 5264dd7305..88bddfa9f5 100644
--- a/gui/src/shared/notifications/notification.ts
+++ b/gui/src/shared/notifications/notification.ts
@@ -5,12 +5,16 @@ export type NotificationAction = {
withAuth?: boolean;
};
+export interface InAppNotificationTroubleshootInfo {
+ details: string;
+ steps: string[];
+}
+
export type InAppNotificationAction =
| NotificationAction
| {
- type: 'info-dialog';
- details: string;
- troubleshoot: string[];
+ type: 'troubleshoot-dialog';
+ troubleshoot: InAppNotificationTroubleshootInfo;
};
export type InAppNotificationIndicatorType = 'success' | 'warning' | 'error';