summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorOskar <oskar@mullvad.net>2025-08-27 10:19:59 +0200
committerOskar <oskar@mullvad.net>2025-08-27 10:19:59 +0200
commitd42568406bf37db3a0ba8f27950fdd1ff2cd696f (patch)
treeb1bb5aeaa49d5de626b9d89f6021c6ae494e3768
parent799c442af0301c5341d328f7f44a3e76b5340212 (diff)
parent9c61a6c5961e72c9a3a044a43a2d05802a3571de (diff)
downloadmullvadvpn-d42568406bf37db3a0ba8f27950fdd1ff2cd696f.tar.xz
mullvadvpn-d42568406bf37db3a0ba8f27950fdd1ff2cd696f.zip
Merge branch 'add-error-message-for-voucher-des-24'
-rw-r--r--desktop/packages/mullvad-vpn/locales/messages.pot7
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/components/RedeemVoucher.tsx36
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/components/RedeemVoucherStyles.tsx5
-rw-r--r--desktop/packages/mullvad-vpn/src/shared/utils.ts4
-rw-r--r--desktop/packages/mullvad-vpn/test/unit/account-number.spec.ts26
5 files changed, 70 insertions, 8 deletions
diff --git a/desktop/packages/mullvad-vpn/locales/messages.pot b/desktop/packages/mullvad-vpn/locales/messages.pot
index 75ee0c43ac..0454fb64d1 100644
--- a/desktop/packages/mullvad-vpn/locales/messages.pot
+++ b/desktop/packages/mullvad-vpn/locales/messages.pot
@@ -1841,6 +1841,10 @@ msgctxt "redeem-voucher-view"
msgid "An error occurred."
msgstr ""
+msgctxt "redeem-voucher-view"
+msgid "It looks like you’ve entered an account number instead of a voucher code. If you would like to change the active account, please log out first."
+msgstr ""
+
#. Button label for voucher redemption.
msgctxt "redeem-voucher-view"
msgid "Redeem"
@@ -3097,9 +3101,6 @@ msgstr ""
msgid "It can be useful when you are aware of problems caused by a certain IP version."
msgstr ""
-msgid "It looks like you’ve entered an account number instead of a voucher code. If you would like to change the active account, please log out first."
-msgstr ""
-
msgid "Lets you select apps that should access the Internet directly without going through the VPN tunnel."
msgstr ""
diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/RedeemVoucher.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/RedeemVoucher.tsx
index 9f875f9c04..f9e6fd3e7a 100644
--- a/desktop/packages/mullvad-vpn/src/renderer/components/RedeemVoucher.tsx
+++ b/desktop/packages/mullvad-vpn/src/renderer/components/RedeemVoucher.tsx
@@ -5,12 +5,14 @@ import { formatDate } from '../../shared/account-expiry';
import { VoucherResponse } from '../../shared/daemon-rpc-types';
import { formatRelativeDate } from '../../shared/date-helper';
import { messages } from '../../shared/gettext';
+import { isAccountNumber } from '../../shared/utils';
import { useAppContext } from '../context';
import { Button, ButtonProps, Flex, Spinner } from '../lib/components';
import { IconBadge } from '../lib/icon-badge';
import { useSelector } from '../redux/store';
import { ModalAlert } from './Modal';
import {
+ StyledAccountNumberInfo,
StyledEmptyResponse,
StyledErrorResponse,
StyledInput,
@@ -26,6 +28,7 @@ interface IRedeemVoucherContextValue {
value: string;
setValue: (value: string) => void;
valueValid: boolean;
+ submittedValue: string;
submitting: boolean;
response?: VoucherResponse;
}
@@ -45,6 +48,9 @@ const RedeemVoucherContext = React.createContext<IRedeemVoucherContextValue>({
get valueValid(): boolean {
throw contextProviderMissingError;
},
+ get submittedValue(): string {
+ throw contextProviderMissingError;
+ },
get submitting(): boolean {
throw contextProviderMissingError;
},
@@ -66,6 +72,7 @@ export function RedeemVoucherContainer(props: IRedeemVoucherProps) {
const { submitVoucher } = useAppContext();
const [value, setValue] = useState('');
+ const [submittedValue, setSubmittedValue] = useState('');
const [submitting, setSubmitting] = useState(false);
const [response, setResponse] = useState<VoucherResponse>();
@@ -78,6 +85,7 @@ export function RedeemVoucherContainer(props: IRedeemVoucherProps) {
const submitTimestamp = Date.now();
setSubmitting(true);
+ setSubmittedValue(value);
onSubmit?.();
const response = await submitVoucher(value);
@@ -98,7 +106,15 @@ export function RedeemVoucherContainer(props: IRedeemVoucherProps) {
return (
<RedeemVoucherContext.Provider
- value={{ onSubmit: onSubmitWrapper, value, setValue, valueValid, submitting, response }}>
+ value={{
+ onSubmit: onSubmitWrapper,
+ value,
+ setValue,
+ valueValid,
+ submittedValue,
+ submitting,
+ response,
+ }}>
{props.children}
</RedeemVoucherContext.Provider>
);
@@ -147,7 +163,7 @@ export function RedeemVoucherInput(props: IRedeemVoucherInputProps) {
}
export function RedeemVoucherResponse() {
- const { response, submitting } = useContext(RedeemVoucherContext);
+ const { response, submitting, submittedValue } = useContext(RedeemVoucherContext);
if (submitting) {
return (
@@ -166,9 +182,19 @@ export function RedeemVoucherResponse() {
return <StyledEmptyResponse />;
case 'invalid':
return (
- <StyledErrorResponse>
- {messages.pgettext('redeem-voucher-view', 'Voucher code is invalid.')}
- </StyledErrorResponse>
+ <>
+ <StyledErrorResponse>
+ {messages.pgettext('redeem-voucher-view', 'Voucher code is invalid.')}
+ </StyledErrorResponse>
+ {isAccountNumber(submittedValue) ? (
+ <StyledAccountNumberInfo>
+ {messages.pgettext(
+ 'redeem-voucher-view',
+ 'It looks like you’ve entered an account number instead of a voucher code. If you would like to change the active account, please log out first.',
+ )}
+ </StyledAccountNumberInfo>
+ ) : null}
+ </>
);
case 'already_used':
return (
diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/RedeemVoucherStyles.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/RedeemVoucherStyles.tsx
index b6235a3c7f..aee804c69a 100644
--- a/desktop/packages/mullvad-vpn/src/renderer/components/RedeemVoucherStyles.tsx
+++ b/desktop/packages/mullvad-vpn/src/renderer/components/RedeemVoucherStyles.tsx
@@ -1,5 +1,6 @@
import styled from 'styled-components';
+import { LabelTiny } from '../lib/components';
import { colors } from '../lib/foundations';
import { normalText, smallText, tinyText } from './common-styles';
import FormattableTextInput from './FormattableTextInput';
@@ -49,3 +50,7 @@ export const StyledTitle = styled.span(smallText, {
color: colors.white,
marginBottom: '5px',
});
+
+export const StyledAccountNumberInfo = styled(LabelTiny)({
+ marginTop: '8px',
+});
diff --git a/desktop/packages/mullvad-vpn/src/shared/utils.ts b/desktop/packages/mullvad-vpn/src/shared/utils.ts
index 65bd822009..18eb2140ef 100644
--- a/desktop/packages/mullvad-vpn/src/shared/utils.ts
+++ b/desktop/packages/mullvad-vpn/src/shared/utils.ts
@@ -11,3 +11,7 @@ export function isInRanges(value: number, ranges: [number, number][]): boolean {
export function isNumber(number: unknown): number is number {
return !Number.isNaN(number);
}
+
+export function isAccountNumber(value: string): boolean {
+ return /^\d{10,16}$/.test(value);
+}
diff --git a/desktop/packages/mullvad-vpn/test/unit/account-number.spec.ts b/desktop/packages/mullvad-vpn/test/unit/account-number.spec.ts
new file mode 100644
index 0000000000..ee2a960683
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/test/unit/account-number.spec.ts
@@ -0,0 +1,26 @@
+import { expect } from 'chai';
+
+import { isAccountNumber } from '../../src/shared/utils';
+
+describe('Account number validation', () => {
+ it('Should identify valid account numbers', () => {
+ // Allowed account numbers can range between 10 and 16 digits
+ expect(isAccountNumber('1234567890123456')).to.be.true;
+ expect(isAccountNumber('123456789012')).to.be.true;
+ expect(isAccountNumber('1234567890')).to.be.true;
+ });
+
+ it('Should reject too long account number', () => {
+ expect(isAccountNumber('12345678901234567')).to.be.false;
+ });
+
+ it('Should reject non-digit account numbers', () => {
+ expect(isAccountNumber('123456789012345a')).to.be.false;
+ expect(isAccountNumber('12345678901e')).to.be.false;
+ expect(isAccountNumber('123456789a')).to.be.false;
+ });
+
+ it('Should reject too short account number', () => {
+ expect(isAccountNumber('123456789')).to.be.false;
+ });
+});