summaryrefslogtreecommitdiffhomepage
path: root/gui/src
diff options
context:
space:
mode:
authorOskar Nyberg <oskar@mullvad.net>2020-06-15 20:11:24 +0200
committerOskar Nyberg <oskar@mullvad.net>2020-06-24 11:23:07 +0200
commit1f54942ca8da86efeb64b174dcceecdd9e48a85c (patch)
treef1187dd93e4e6cb1f6ee1c91e87e101a7031694b /gui/src
parent63193938c1c5719e06eae51f8d213af55125a2de (diff)
downloadmullvadvpn-1f54942ca8da86efeb64b174dcceecdd9e48a85c.tar.xz
mullvadvpn-1f54942ca8da86efeb64b174dcceecdd9e48a85c.zip
Refactor AccountExpiry
Diffstat (limited to 'gui/src')
-rw-r--r--gui/src/main/index.ts11
-rw-r--r--gui/src/renderer/components/Account.tsx8
-rw-r--r--gui/src/renderer/components/Connect.tsx6
-rw-r--r--gui/src/renderer/components/ExpiredAccountErrorView.tsx6
-rw-r--r--gui/src/renderer/components/NotificationArea.tsx10
-rw-r--r--gui/src/renderer/components/Settings.tsx11
-rw-r--r--gui/src/renderer/containers/ConnectPage.tsx5
-rw-r--r--gui/src/renderer/containers/ExpiredAccountErrorViewContainer.tsx5
-rw-r--r--gui/src/shared/account-expiry.ts81
-rw-r--r--gui/src/shared/notifications/accountExpiry.ts17
10 files changed, 71 insertions, 89 deletions
diff --git a/gui/src/main/index.ts b/gui/src/main/index.ts
index b09d83e292..407a348ca9 100644
--- a/gui/src/main/index.ts
+++ b/gui/src/main/index.ts
@@ -4,7 +4,7 @@ import log from 'electron-log';
import mkdirp from 'mkdirp';
import * as path from 'path';
import * as uuid from 'uuid';
-import AccountExpiry from '../shared/account-expiry';
+import { hasExpired } from '../shared/account-expiry';
import BridgeSettingsBuilder from '../shared/bridge-settings-builder';
import {
AccountToken,
@@ -1147,10 +1147,7 @@ class ApplicationMain {
}
private async autoConnect() {
- if (
- !this.accountData ||
- !new AccountExpiry(this.accountData.expiry, this.locale).hasExpired()
- ) {
+ if (!this.accountData || !hasExpired(this.accountData.expiry)) {
try {
log.info('Auto-connecting the tunnel');
await this.daemonRpc.connectTunnel();
@@ -1221,9 +1218,9 @@ class ApplicationMain {
private notifyOfAccountExpiry() {
if (this.accountData) {
- const accountExpiry = new AccountExpiry(this.accountData.expiry, this.locale);
const notificationProvider = new AccountExpiryNotificationProvider({
- accountExpiry,
+ accountExpiry: this.accountData.expiry,
+ locale: this.locale,
tooSoon: this.accountExpiryNotificationTimeout !== undefined,
});
if (notificationProvider.mayDisplay()) {
diff --git a/gui/src/renderer/components/Account.tsx b/gui/src/renderer/components/Account.tsx
index 352ed1dad3..3d344887bd 100644
--- a/gui/src/renderer/components/Account.tsx
+++ b/gui/src/renderer/components/Account.tsx
@@ -1,5 +1,5 @@
import * as React from 'react';
-import AccountExpiry from '../../shared/account-expiry';
+import { formatDate, hasExpired } from '../../shared/account-expiry';
import { messages } from '../../shared/gettext';
import {
AccountContainer,
@@ -97,14 +97,12 @@ export default class Account extends React.Component<IProps> {
function FormattedAccountExpiry(props: { expiry?: string; locale: string }) {
if (props.expiry) {
- const expiry = new AccountExpiry(props.expiry, props.locale);
-
- if (expiry.hasExpired()) {
+ if (hasExpired(props.expiry)) {
return (
<AccountOutOfTime>{messages.pgettext('account-view', 'OUT OF TIME')}</AccountOutOfTime>
);
} else {
- return <AccountRowValue>{expiry.formattedDate()}</AccountRowValue>;
+ return <AccountRowValue>{formatDate(props.expiry, props.locale)}</AccountRowValue>;
}
} else {
return (
diff --git a/gui/src/renderer/components/Connect.tsx b/gui/src/renderer/components/Connect.tsx
index b57c63a273..636f445bc1 100644
--- a/gui/src/renderer/components/Connect.tsx
+++ b/gui/src/renderer/components/Connect.tsx
@@ -1,7 +1,7 @@
import * as React from 'react';
import { Component, Styles, View } from 'reactxp';
import styled from 'styled-components';
-import AccountExpiry from '../../shared/account-expiry';
+import { hasExpired } from '../../shared/account-expiry';
import ExpiredAccountErrorViewContainer from '../containers/ExpiredAccountErrorViewContainer';
import NotificationArea from '../components/NotificationArea';
import { AuthFailureKind, parseAuthFailure } from '../../shared/auth-failure';
@@ -17,7 +17,7 @@ import TunnelControl from './TunnelControl';
interface IProps {
connection: IConnectionReduxState;
loginState: LoginState;
- accountExpiry?: AccountExpiry;
+ accountExpiry?: string;
blockWhenDisconnected: boolean;
selectedRelayName: string;
onSettings: () => void;
@@ -135,7 +135,7 @@ export default class Connect extends Component<IProps, IState> {
// Use the account expiry to deduce the account state
if (this.props.accountExpiry) {
- return this.props.accountExpiry.hasExpired();
+ return hasExpired(this.props.accountExpiry);
}
// Do not assume that the account hasn't expired if the expiry is not available at the moment
diff --git a/gui/src/renderer/components/ExpiredAccountErrorView.tsx b/gui/src/renderer/components/ExpiredAccountErrorView.tsx
index 61140016ea..0854f0ace2 100644
--- a/gui/src/renderer/components/ExpiredAccountErrorView.tsx
+++ b/gui/src/renderer/components/ExpiredAccountErrorView.tsx
@@ -2,7 +2,7 @@ import * as React from 'react';
import { Component, Text, View } from 'reactxp';
import { sprintf } from 'sprintf-js';
import { links } from '../../config.json';
-import AccountExpiry from '../../shared/account-expiry';
+import { hasExpired } from '../../shared/account-expiry';
import { AccountToken } from '../../shared/daemon-rpc-types';
import { messages } from '../../shared/gettext';
import { LoginState } from '../redux/account/reducers';
@@ -29,7 +29,7 @@ interface IExpiredAccountErrorViewProps {
isBlocked: boolean;
blockWhenDisconnected: boolean;
accountToken?: AccountToken;
- accountExpiry?: AccountExpiry;
+ accountExpiry?: string;
loginState: LoginState;
hideWelcomeView: () => void;
onExternalLinkWithAuth: (url: string) => Promise<void>;
@@ -52,7 +52,7 @@ export default class ExpiredAccountErrorView extends Component<
};
public componentDidUpdate() {
- if (this.props.accountExpiry && !this.props.accountExpiry.hasExpired()) {
+ if (this.props.accountExpiry && !hasExpired(this.props.accountExpiry)) {
this.props.hideWelcomeView();
}
}
diff --git a/gui/src/renderer/components/NotificationArea.tsx b/gui/src/renderer/components/NotificationArea.tsx
index bc76ba0885..89d742df0a 100644
--- a/gui/src/renderer/components/NotificationArea.tsx
+++ b/gui/src/renderer/components/NotificationArea.tsx
@@ -3,7 +3,6 @@ import log from 'electron-log';
import React, { useCallback } from 'react';
import { useSelector } from 'react-redux';
import { Types } from 'reactxp';
-import AccountExpiry from '../../shared/account-expiry';
import {
AccountExpiryNotificationProvider,
BlockWhenDisconnectedNotificationProvider,
@@ -33,11 +32,8 @@ interface IProps {
}
export default function NotificationArea(props: IProps) {
- const accountExpiry = useSelector((state: IReduxState) =>
- state.account.expiry
- ? new AccountExpiry(state.account.expiry, state.userInterface.locale)
- : undefined,
- );
+ const accountExpiry = useSelector((state: IReduxState) => state.account.expiry);
+ const locale = useSelector((state: IReduxState) => state.userInterface.locale);
const tunnelState = useSelector((state: IReduxState) => state.connection.status);
const version = useSelector((state: IReduxState) => state.version);
const blockWhenDisconnected = useSelector(
@@ -55,7 +51,7 @@ export default function NotificationArea(props: IProps) {
];
if (accountExpiry) {
- notificationProviders.push(new AccountExpiryNotificationProvider({ accountExpiry }));
+ notificationProviders.push(new AccountExpiryNotificationProvider({ accountExpiry, locale }));
}
const notificationProvider = notificationProviders.find((notification) =>
diff --git a/gui/src/renderer/components/Settings.tsx b/gui/src/renderer/components/Settings.tsx
index 4a7684f405..f01bfd7036 100644
--- a/gui/src/renderer/components/Settings.tsx
+++ b/gui/src/renderer/components/Settings.tsx
@@ -1,7 +1,7 @@
import * as React from 'react';
import { Component, Text, View } from 'reactxp';
import { colors, links } from '../../config.json';
-import AccountExpiry from '../../shared/account-expiry';
+import { hasExpired, formatRemainingTime } from '../../shared/account-expiry';
import { messages } from '../../shared/gettext';
import * as AppButton from './AppButton';
import * as Cell from './Cell';
@@ -102,11 +102,10 @@ export default class Settings extends Component<IProps> {
return null;
}
- const expiry = this.props.accountExpiry
- ? new AccountExpiry(this.props.accountExpiry, this.props.expiryLocale)
- : null;
- const isOutOfTime = expiry ? expiry.hasExpired() : false;
- const formattedExpiry = expiry ? expiry.remainingTime().toUpperCase() : '';
+ const isOutOfTime = this.props.accountExpiry ? hasExpired(this.props.accountExpiry) : false;
+ const formattedExpiry = this.props.accountExpiry
+ ? formatRemainingTime(this.props.accountExpiry, this.props.expiryLocale).toUpperCase()
+ : '';
const outOfTimeMessage = messages.pgettext('settings-view', 'OUT OF TIME');
diff --git a/gui/src/renderer/containers/ConnectPage.tsx b/gui/src/renderer/containers/ConnectPage.tsx
index 4ba47f7511..57f550d988 100644
--- a/gui/src/renderer/containers/ConnectPage.tsx
+++ b/gui/src/renderer/containers/ConnectPage.tsx
@@ -3,7 +3,6 @@ import log from 'electron-log';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { sprintf } from 'sprintf-js';
-import AccountExpiry from '../../shared/account-expiry';
import { messages } from '../../shared/gettext';
import Connect from '../components/Connect';
import withAppContext, { IAppContext } from '../context';
@@ -65,9 +64,7 @@ function getRelayName(
const mapStateToProps = (state: IReduxState) => {
return {
- accountExpiry: state.account.expiry
- ? new AccountExpiry(state.account.expiry, state.userInterface.locale)
- : undefined,
+ accountExpiry: state.account.expiry,
loginState: state.account.status,
blockWhenDisconnected: state.settings.blockWhenDisconnected,
selectedRelayName: getRelayName(state.settings.relaySettings, state.settings.relayLocations),
diff --git a/gui/src/renderer/containers/ExpiredAccountErrorViewContainer.tsx b/gui/src/renderer/containers/ExpiredAccountErrorViewContainer.tsx
index 2c4ca2e6dd..dcdc862355 100644
--- a/gui/src/renderer/containers/ExpiredAccountErrorViewContainer.tsx
+++ b/gui/src/renderer/containers/ExpiredAccountErrorViewContainer.tsx
@@ -1,7 +1,6 @@
import log from 'electron-log';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
-import AccountExpiry from '../../shared/account-expiry';
import ExpiredAccountErrorView from '../components/ExpiredAccountErrorView';
import accountActions from '../redux/account/actions';
@@ -10,9 +9,7 @@ import { IReduxState, ReduxDispatch } from '../redux/store';
const mapStateToProps = (state: IReduxState) => ({
accountToken: state.account.accountToken,
- accountExpiry: state.account.expiry
- ? new AccountExpiry(state.account.expiry, state.userInterface.locale)
- : undefined,
+ accountExpiry: state.account.expiry,
loginState: state.account.status,
isBlocked: state.connection.isBlocked,
blockWhenDisconnected: state.settings.blockWhenDisconnected,
diff --git a/gui/src/shared/account-expiry.ts b/gui/src/shared/account-expiry.ts
index d6acb0de91..4511797f3c 100644
--- a/gui/src/shared/account-expiry.ts
+++ b/gui/src/shared/account-expiry.ts
@@ -2,57 +2,52 @@ import moment from 'moment';
import { sprintf } from 'sprintf-js';
import { messages } from './gettext';
-export default class AccountExpiry {
- private expiry: moment.Moment;
+type DateArgument = string | Date | moment.Moment;
- constructor(isoString: string, locale: string) {
- this.expiry = moment(isoString).locale(locale);
- }
-
- public hasExpired(): boolean {
- return this.willHaveExpiredAt(new Date());
- }
-
- public willHaveExpiredAt(date: Date): boolean {
- return this.expiry.isSameOrBefore(date);
- }
+export function hasExpired(expiry: DateArgument): boolean {
+ return moment(expiry).isSameOrBefore(new Date());
+}
- public formattedDate(): string {
- return this.expiry.format('lll');
- }
+export function formatDate(date: DateArgument, locale: string): string {
+ return moment(date).locale(locale).format('lll');
+}
- public durationUntilExpiry(): string {
- const daysDiff = this.expiry.diff(new Date(), 'days');
+export function formatDurationUntilExpiry(expiry: DateArgument, locale: string): string {
+ const expiryMoment = moment(expiry).locale(locale);
+ const daysDiff = expiryMoment.diff(new Date(), 'days');
- // Below three months we want to show the duration in days. Moments fromNow() method starts
- // measuring duration in months from 26 days and up.
- // https://momentjs.com/docs/#/displaying/fromnow/
- if (daysDiff >= 26 && daysDiff <= 90) {
- return sprintf(
- // TRANSLATORS: The remaining time left on the account measured in days.
- // TRANSLATORS: Available placeholders:
- // TRANSLATORS: %(duration)s - The remaining time measured in days.
- messages.pgettext('account-expiry', '%(duration)s days'),
- { duration: daysDiff },
- );
- } else {
- return this.expiry.fromNow(true);
- }
+ // Below three months we want to show the duration in days. Moments fromNow() method starts
+ // measuring duration in months from 26 days and up.
+ // https://momentjs.com/docs/#/displaying/fromnow/
+ if (daysDiff >= 26 && daysDiff <= 90) {
+ return sprintf(
+ // TRANSLATORS: The remaining time left on the account measured in days.
+ // TRANSLATORS: Available placeholders:
+ // TRANSLATORS: %(duration)s - The remaining time measured in days.
+ messages.pgettext('account-expiry', '%(duration)s days'),
+ { duration: daysDiff },
+ );
+ } else {
+ return expiryMoment.fromNow(true);
}
+}
- public remainingTime(shouldCapitalizeFirstLetter?: boolean): string {
- const duration = this.durationUntilExpiry();
+export function formatRemainingTime(
+ expiry: DateArgument,
+ locale: string,
+ shouldCapitalizeFirstLetter?: boolean,
+): string {
+ const duration = formatDurationUntilExpiry(expiry, locale);
- const remaining = sprintf(
- // TRANSLATORS: The remaining time left on the account displayed across the app.
- // TRANSLATORS: Available placeholders:
- // TRANSLATORS: %(duration)s - a localized remaining time (in minutes, hours, or days) until the account expiry
- messages.pgettext('account-expiry', '%(duration)s left'),
- { duration },
- );
+ const remaining = sprintf(
+ // TRANSLATORS: The remaining time left on the account displayed across the app.
+ // TRANSLATORS: Available placeholders:
+ // TRANSLATORS: %(duration)s - a localized remaining time (in minutes, hours, or days) until the account expiry
+ messages.pgettext('account-expiry', '%(duration)s left'),
+ { duration },
+ );
- return shouldCapitalizeFirstLetter ? capitalizeFirstLetter(remaining) : remaining;
- }
+ return shouldCapitalizeFirstLetter ? capitalizeFirstLetter(remaining) : remaining;
}
function capitalizeFirstLetter(inputString: string): string {
diff --git a/gui/src/shared/notifications/accountExpiry.ts b/gui/src/shared/notifications/accountExpiry.ts
index b749fb55c1..db536c7405 100644
--- a/gui/src/shared/notifications/accountExpiry.ts
+++ b/gui/src/shared/notifications/accountExpiry.ts
@@ -2,7 +2,7 @@ import moment from 'moment';
import { sprintf } from 'sprintf-js';
import { links } from '../../config.json';
import { messages } from '../../shared/gettext';
-import AccountExpiry from '../account-expiry';
+import { hasExpired, formatRemainingTime } from '../account-expiry';
import {
InAppNotification,
InAppNotificationProvider,
@@ -11,7 +11,8 @@ import {
} from './notification';
interface AccountExpiryContext {
- accountExpiry: AccountExpiry;
+ accountExpiry: string;
+ locale: string;
tooSoon?: boolean;
}
@@ -20,10 +21,12 @@ export class AccountExpiryNotificationProvider
public constructor(private context: AccountExpiryContext) {}
public mayDisplay() {
+ const willHaveExpiredInThreeDays = moment(this.context.accountExpiry).isSameOrBefore(
+ moment().add(3, 'days'),
+ );
+
return (
- !this.context.accountExpiry.hasExpired() &&
- this.context.accountExpiry.willHaveExpiredAt(moment().add(3, 'days').toDate()) &&
- !this.context.tooSoon
+ !hasExpired(this.context.accountExpiry) && willHaveExpiredInThreeDays && !this.context.tooSoon
);
}
@@ -34,7 +37,7 @@ export class AccountExpiryNotificationProvider
// TRANSLATORS: %(duration)s - remaining time, e.g. "2 days"
messages.pgettext('notifications', 'Account credit expires in %(duration)s'),
{
- duration: this.context.accountExpiry.remainingTime(),
+ duration: formatRemainingTime(this.context.accountExpiry, this.context.locale),
},
);
@@ -49,7 +52,7 @@ export class AccountExpiryNotificationProvider
return {
indicator: 'warning',
title: messages.pgettext('in-app-notifications', 'ACCOUNT CREDIT EXPIRES SOON'),
- subtitle: this.context.accountExpiry.remainingTime(true),
+ subtitle: formatRemainingTime(this.context.accountExpiry, this.context.locale, true),
action: { type: 'open-url', url: links.purchase, withAuth: true },
};
}