summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorOskar <oskar@mullvad.net>2025-08-28 09:50:03 +0200
committerOskar <oskar@mullvad.net>2025-08-29 07:38:40 +0200
commit55dd829f370156e9e20bbe76a35fde791740f40d (patch)
tree594cf7810eb25d92b076095841483bf49a488ee4
parentfc7b1ee2321a69622fb8d83f23d13bded0191e6f (diff)
downloadmullvadvpn-55dd829f370156e9e20bbe76a35fde791740f40d.tar.xz
mullvadvpn-55dd829f370156e9e20bbe76a35fde791740f40d.zip
Add confirmation dialog when clearing account history
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/components/views/login/ClearAccountHistoryDialog.tsx48
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/components/views/login/Login.tsx125
2 files changed, 121 insertions, 52 deletions
diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/login/ClearAccountHistoryDialog.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/views/login/ClearAccountHistoryDialog.tsx
new file mode 100644
index 0000000000..b2c3e370d5
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/login/ClearAccountHistoryDialog.tsx
@@ -0,0 +1,48 @@
+import { messages } from '../../../../shared/gettext';
+import { Button } from '../../../lib/components';
+import { ModalAlert, ModalAlertType, ModalMessage } from '../../Modal';
+
+interface Props {
+ visible: boolean;
+ onConfirm: () => void;
+ onHide: () => void;
+}
+
+export default function ClearAccountHistoryDialog(props: Props) {
+ return (
+ <ModalAlert
+ isOpen={props.visible}
+ type={ModalAlertType.caution}
+ buttons={[
+ <Button variant="destructive" key="confirm" onClick={props.onConfirm}>
+ <Button.Text>
+ {
+ // TRANSLATORS: Button label in confirmation dialog that confirms a remove action.
+ messages.gettext('Remove')
+ }
+ </Button.Text>
+ </Button>,
+ <Button key="back" onClick={props.onHide}>
+ <Button.Text>{messages.gettext('Cancel')}</Button.Text>
+ </Button>,
+ ]}
+ close={props.onHide}>
+ <ModalMessage>
+ {
+ // TRANSLATORS: Text that informs the user about the consequences of clearing the saved
+ // TRANSLATORS: account number.
+ messages.pgettext(
+ 'login-view',
+ 'Removing the saved account number from this device cannot be undone.',
+ )
+ }
+ </ModalMessage>
+ <ModalMessage>
+ {
+ // TRANSLATORS: Text that asks the user if they really want to remove the saved account.
+ messages.pgettext('login-view', 'Do you want to remove the saved account number?')
+ }
+ </ModalMessage>
+ </ModalAlert>
+ );
+}
diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/login/Login.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/views/login/Login.tsx
index 4446ae96ff..395467ac14 100644
--- a/desktop/packages/mullvad-vpn/src/renderer/components/views/login/Login.tsx
+++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/login/Login.tsx
@@ -27,6 +27,7 @@ import { useSelector } from '../../../redux/store';
import Accordion from '../../Accordion';
import { AppMainHeader } from '../../app-main-header';
import { Container, Layout } from '../../Layout';
+import ClearAccountHistoryDialog from './ClearAccountHistoryDialog';
import {
StyledAccountDropdownContainer,
StyledAccountDropdownItem,
@@ -96,6 +97,7 @@ interface IProps {
interface IState {
isActive: boolean;
+ clearAccountHistoryDialogVisible: boolean;
}
const MIN_ACCOUNT_NUMBER_LENGTH = 10;
@@ -103,6 +105,7 @@ const MIN_ACCOUNT_NUMBER_LENGTH = 10;
class Login extends React.Component<IProps, IState> {
public state: IState = {
isActive: true,
+ clearAccountHistoryDialogVisible: false,
};
private accountInput = React.createRef<HTMLInputElement>();
@@ -299,9 +302,18 @@ class Login extends React.Component<IProps, IState> {
};
private onClearAccountHistory = () => {
+ this.setState({ clearAccountHistoryDialogVisible: true });
+ };
+
+ private onConfirmClearAccountHistory = () => {
+ this.hideClearAccountHistoryDialog();
void this.clearAccountHistory();
};
+ private hideClearAccountHistoryDialog = () => {
+ this.setState({ clearAccountHistoryDialogVisible: false });
+ };
+
private async clearAccountHistory() {
try {
await this.props.clearAccountHistory();
@@ -321,59 +333,68 @@ class Login extends React.Component<IProps, IState> {
this.props.loginState.method === 'existing_account';
return (
- <Flex $flexDirection="column" $gap="small">
- <Label htmlFor={inputId} data-testid="subtitle">
- {this.formSubtitle()}
- </Label>
- <StyledAccountInputGroup
- $active={allowInteraction && this.state.isActive}
- $editable={allowInteraction}
- $error={hasError}
- onSubmit={this.onSubmit}>
- <StyledAccountInputBackdrop>
- <StyledInput
- id={inputId}
- allowedCharacters="[0-9]"
- separator=" "
- groupLength={4}
- placeholder="0000 0000 0000 0000"
- value={this.props.accountNumber || ''}
- disabled={!allowInteraction}
- onFocus={this.onFocus}
- onBlur={this.onBlur}
- handleChange={this.onInputChange}
- autoFocus={true}
- ref={this.accountInput}
- aria-autocomplete="list"
- />
- <StyledInputButton
- type="submit"
- $visible={allowLogin}
- disabled={!allowLogin}
- aria-label={
- // TRANSLATORS: This is used by screenreaders to communicate the login button.
- messages.pgettext('accessibility', 'Login')
- }>
- <StyledInputSubmitIcon
- $visible={
- this.props.loginState.type !== 'logging in' && !this.props.isPerformingPostUpgrade
- }
- icon="chevron-right"
- size="large"
+ <>
+ <Flex $flexDirection="column" $gap="small">
+ <Label htmlFor={inputId} data-testid="subtitle">
+ {this.formSubtitle()}
+ </Label>
+ <StyledAccountInputGroup
+ $active={allowInteraction && this.state.isActive}
+ $editable={allowInteraction}
+ $error={hasError}
+ onSubmit={this.onSubmit}>
+ <StyledAccountInputBackdrop>
+ <StyledInput
+ id={inputId}
+ allowedCharacters="[0-9]"
+ separator=" "
+ groupLength={4}
+ placeholder="0000 0000 0000 0000"
+ value={this.props.accountNumber || ''}
+ disabled={!allowInteraction}
+ onFocus={this.onFocus}
+ onBlur={this.onBlur}
+ handleChange={this.onInputChange}
+ autoFocus={true}
+ ref={this.accountInput}
+ aria-autocomplete="list"
/>
- </StyledInputButton>
- </StyledAccountInputBackdrop>
- <Accordion expanded={this.shouldShowAccountHistory()}>
- <StyledAccountDropdownContainer>
- <AccountDropdown
- item={this.props.accountHistory}
- onSelect={this.onSelectAccountFromHistory}
- onRemove={this.onClearAccountHistory}
- />
- </StyledAccountDropdownContainer>
- </Accordion>
- </StyledAccountInputGroup>
- </Flex>
+ <StyledInputButton
+ type="submit"
+ $visible={allowLogin}
+ disabled={!allowLogin}
+ aria-label={
+ // TRANSLATORS: This is used by screenreaders to communicate the login button.
+ messages.pgettext('accessibility', 'Login')
+ }>
+ <StyledInputSubmitIcon
+ $visible={
+ this.props.loginState.type !== 'logging in' &&
+ !this.props.isPerformingPostUpgrade
+ }
+ icon="chevron-right"
+ size="large"
+ />
+ </StyledInputButton>
+ </StyledAccountInputBackdrop>
+ <Accordion expanded={this.shouldShowAccountHistory()}>
+ <StyledAccountDropdownContainer>
+ <AccountDropdown
+ item={this.props.accountHistory}
+ onSelect={this.onSelectAccountFromHistory}
+ onRemove={this.onClearAccountHistory}
+ />
+ </StyledAccountDropdownContainer>
+ </Accordion>
+ </StyledAccountInputGroup>
+ </Flex>
+
+ <ClearAccountHistoryDialog
+ visible={this.state.clearAccountHistoryDialogVisible}
+ onConfirm={this.onConfirmClearAccountHistory}
+ onHide={this.hideClearAccountHistoryDialog}
+ />
+ </>
);
}