diff options
| author | Oskar <oskar@mullvad.net> | 2025-08-27 10:19:59 +0200 |
|---|---|---|
| committer | Oskar <oskar@mullvad.net> | 2025-08-27 10:19:59 +0200 |
| commit | d42568406bf37db3a0ba8f27950fdd1ff2cd696f (patch) | |
| tree | b1bb5aeaa49d5de626b9d89f6021c6ae494e3768 | |
| parent | 799c442af0301c5341d328f7f44a3e76b5340212 (diff) | |
| parent | 9c61a6c5961e72c9a3a044a43a2d05802a3571de (diff) | |
| download | mullvadvpn-d42568406bf37db3a0ba8f27950fdd1ff2cd696f.tar.xz mullvadvpn-d42568406bf37db3a0ba8f27950fdd1ff2cd696f.zip | |
Merge branch 'add-error-message-for-voucher-des-24'
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; + }); +}); |
