summaryrefslogtreecommitdiffhomepage
path: root/gui/src/renderer/components
diff options
context:
space:
mode:
authorOskar Nyberg <oskar@mullvad.net>2021-11-26 14:19:08 +0100
committerOskar Nyberg <oskar@mullvad.net>2022-03-14 13:58:44 +0100
commitebfdf9289aff8d85266161b17f490bc0a661ffcf (patch)
tree07cd905c535564dfc3b35a63856ec21359aad658 /gui/src/renderer/components
parent9916f3444c1d15a6d9ad5caba3555e1c0e1a704c (diff)
downloadmullvadvpn-ebfdf9289aff8d85266161b17f490bc0a661ffcf.tar.xz
mullvadvpn-ebfdf9289aff8d85266161b17f490bc0a661ffcf.zip
Add log out confirmation dialog
Diffstat (limited to 'gui/src/renderer/components')
-rw-r--r--gui/src/renderer/components/Account.tsx92
-rw-r--r--gui/src/renderer/components/AccountStyles.tsx7
-rw-r--r--gui/src/renderer/components/TooManyDevices.tsx70
3 files changed, 135 insertions, 34 deletions
diff --git a/gui/src/renderer/components/Account.tsx b/gui/src/renderer/components/Account.tsx
index a612f2a07e..4a84e3c110 100644
--- a/gui/src/renderer/components/Account.tsx
+++ b/gui/src/renderer/components/Account.tsx
@@ -10,6 +10,7 @@ import {
AccountRows,
AccountRowValue,
DeviceRowValue,
+ StyledSpinnerContainer,
StyledBuyCreditButton,
StyledContainer,
StyledRedeemVoucherButton,
@@ -18,10 +19,12 @@ import AccountTokenLabel from './AccountTokenLabel';
import * as AppButton from './AppButton';
import { AriaDescribed, AriaDescription, AriaDescriptionGroup } from './AriaGroup';
import { Layout } from './Layout';
+import { ModalAlert, ModalAlertType, ModalMessage } from './Modal';
import { NavigationBar, NavigationItems, TitleBarItem } from './NavigationBar';
import SettingsHeader, { HeaderTitle } from './SettingsHeader';
-import { AccountToken } from '../../shared/daemon-rpc-types';
+import { AccountToken, IDevice } from '../../shared/daemon-rpc-types';
+import ImageView from './ImageView';
import { BackAction } from './KeyboardNavigation';
interface IProps {
@@ -30,13 +33,22 @@ interface IProps {
accountExpiry?: string;
expiryLocale: string;
isOffline: boolean;
+ prepareLogout: () => void;
+ cancelLogout: () => void;
onLogout: () => void;
onClose: () => void;
onBuyMore: () => Promise<void>;
updateAccountData: () => void;
+ getDevice: () => Promise<IDevice | undefined>;
}
-export default class Account extends React.Component<IProps> {
+interface IState {
+ logoutDialogState: 'hidden' | 'checking-ports' | 'confirm';
+}
+
+export default class Account extends React.Component<IProps, IState> {
+ public state: IState = { logoutDialogState: 'hidden' };
+
public componentDidMount() {
this.props.updateAccountData();
}
@@ -65,7 +77,7 @@ export default class Account extends React.Component<IProps> {
<AccountRows>
<AccountRow>
<AccountRowLabel>
- {messages.pgettext('account-view', 'Device name')}
+ {messages.pgettext('device-management', 'Device name')}
</AccountRowLabel>
<DeviceRowValue>{this.props.deviceName}</DeviceRowValue>
</AccountRow>
@@ -114,16 +126,88 @@ export default class Account extends React.Component<IProps> {
<StyledRedeemVoucherButton />
- <AppButton.RedButton onClick={this.props.onLogout}>
+ <AppButton.RedButton onClick={this.onTryLogout}>
{messages.pgettext('account-view', 'Log out')}
</AppButton.RedButton>
</AccountFooter>
</AccountContainer>
</StyledContainer>
+
+ {this.renderLogoutDialog()}
</Layout>
</BackAction>
);
}
+
+ private renderLogoutDialog() {
+ const modalType =
+ this.state.logoutDialogState === 'checking-ports' ? undefined : ModalAlertType.warning;
+
+ const message =
+ this.state.logoutDialogState === 'checking-ports' ? (
+ <StyledSpinnerContainer>
+ <ImageView source="icon-spinner" width={60} height={60} />
+ </StyledSpinnerContainer>
+ ) : (
+ <ModalMessage>
+ {
+ // TRANSLATORS: This is is a further explanation of what happens when logging out.
+ messages.pgettext(
+ 'device-management',
+ 'The ports forwarded to this device will be deleted if you log out.',
+ )
+ }
+ </ModalMessage>
+ );
+
+ const buttons =
+ this.state.logoutDialogState === 'checking-ports'
+ ? []
+ : [
+ <AppButton.RedButton key="logout" onClick={this.props.onLogout}>
+ {
+ // TRANSLATORS: Confirmation button when logging out
+ messages.pgettext('device-management', 'Log out anyway')
+ }
+ </AppButton.RedButton>,
+ <AppButton.BlueButton key="back" onClick={this.cancelLogout}>
+ {messages.gettext('Back')}
+ </AppButton.BlueButton>,
+ ];
+
+ return (
+ <ModalAlert
+ isOpen={this.state.logoutDialogState !== 'hidden'}
+ type={modalType}
+ buttons={buttons}>
+ {message}
+ </ModalAlert>
+ );
+ }
+
+ private onTryLogout = async () => {
+ this.setState({ logoutDialogState: 'checking-ports' });
+ this.props.prepareLogout();
+
+ const device = await this.props.getDevice();
+ if (device === undefined) {
+ this.onHideLogoutConfirmationDialog();
+ } else if (device.ports !== undefined && device.ports.length > 0) {
+ this.setState({ logoutDialogState: 'confirm' });
+ } else {
+ this.props.onLogout();
+ this.onHideLogoutConfirmationDialog();
+ }
+ };
+
+ private cancelLogout = () => {
+ this.props.cancelLogout();
+ this.onHideLogoutConfirmationDialog();
+ };
+
+ private onHideLogoutConfirmationDialog = () => {
+ this.setState({ logoutDialogState: 'hidden' });
+ };
}
function FormattedAccountExpiry(props: { expiry?: string; locale: string }) {
diff --git a/gui/src/renderer/components/AccountStyles.tsx b/gui/src/renderer/components/AccountStyles.tsx
index 34ce600fde..549b0cda7d 100644
--- a/gui/src/renderer/components/AccountStyles.tsx
+++ b/gui/src/renderer/components/AccountStyles.tsx
@@ -52,6 +52,13 @@ export const AccountOutOfTime = styled(AccountRowValue)({
color: colors.red,
});
+export const StyledSpinnerContainer = styled.div({
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ padding: '8px 0',
+});
+
export const AccountFooter = styled.div({
display: 'flex',
flexDirection: 'column',
diff --git a/gui/src/renderer/components/TooManyDevices.tsx b/gui/src/renderer/components/TooManyDevices.tsx
index 56ecbd99c1..b743dd2aec 100644
--- a/gui/src/renderer/components/TooManyDevices.tsx
+++ b/gui/src/renderer/components/TooManyDevices.tsx
@@ -4,10 +4,12 @@ import styled from 'styled-components';
import { colors } from '../../config.json';
import { IDevice } from '../../shared/daemon-rpc-types';
import { messages } from '../../shared/gettext';
+import { capitalizeEveryWord } from '../../shared/string-helpers';
import { useAppContext } from '../context';
import { transitions, useHistory } from '../lib/history';
import { RoutePath } from '../lib/routes';
import { useBoolean } from '../lib/utilityHooks';
+import { formatMarkdown } from '../markdown-formatter';
import { useSelector } from '../redux/store';
import * as AppButton from './AppButton';
import * as Cell from './cell';
@@ -192,6 +194,8 @@ function Device(props: IDeviceProps) {
setDeleting();
}, [props.onRemove, props.device.id, hideConfirmation, setDeleting]);
+ const capitalizedDeviceName = capitalizeEveryWord(props.device.name);
+
return (
<>
<StyledCellContainer>
@@ -213,36 +217,42 @@ function Device(props: IDeviceProps) {
/>
</StyledRemoveDeviceButton>
</StyledCellContainer>
- {confirmationVisible && (
- <ModalAlert
- type={ModalAlertType.warning}
- iconColor={colors.red}
- buttons={[
- <AppButton.RedButton key="remove" onClick={onRemove} disabled={deleting}>
- {
- // TRANSLATORS: Confirmation button when logging out other device.
- messages.pgettext('device-management', 'Yes, log out device')
- }
- </AppButton.RedButton>,
- <AppButton.BlueButton key="back" onClick={hideConfirmation} disabled={deleting}>
- {messages.gettext('Back')}
- </AppButton.BlueButton>,
- ]}
- close={hideConfirmation}>
- {deleting ? (
- <ImageView source="icon-spinner" />
- ) : (
- <>
- <ModalMessage>
- {sprintf(
+ <ModalAlert
+ isOpen={confirmationVisible}
+ type={ModalAlertType.warning}
+ iconColor={colors.red}
+ buttons={[
+ <AppButton.RedButton key="remove" onClick={onRemove} disabled={deleting}>
+ {
+ // TRANSLATORS: Confirmation button when logging out other device.
+ messages.pgettext('device-management', 'Yes, log out device')
+ }
+ </AppButton.RedButton>,
+ <AppButton.BlueButton key="back" onClick={hideConfirmation} disabled={deleting}>
+ {messages.gettext('Back')}
+ </AppButton.BlueButton>,
+ ]}
+ close={hideConfirmation}>
+ {deleting ? (
+ <ImageView source="icon-spinner" />
+ ) : (
+ <>
+ <ModalMessage>
+ {formatMarkdown(
+ sprintf(
// TRANSLATORS: Text displayed above button which logs out another device.
+ // TRANSLATORS: The text enclosed in "**" will appear bold.
+ // TRANSLATORS: Available placeholders:
+ // TRANSLATORS: %(deviceName)s - The name of the device to log out.
messages.pgettext(
'device-management',
- 'Are you sure you want to log out of %(deviceName)s?',
+ 'Are you sure you want to log out of **%(deviceName)s**?',
),
- { deviceName: props.device.name },
- )}
- </ModalMessage>
+ { deviceName: capitalizedDeviceName },
+ ),
+ )}
+ </ModalMessage>
+ {props.device.ports && props.device.ports.length > 0 && (
<ModalMessage>
{
// TRANSLATORS: Further information about consequences of logging out device.
@@ -252,10 +262,10 @@ function Device(props: IDeviceProps) {
)
}
</ModalMessage>
- </>
- )}
- </ModalAlert>
- )}
+ )}
+ </>
+ )}
+ </ModalAlert>
</>
);
}