diff options
| author | Oskar Nyberg <oskar@mullvad.net> | 2020-06-15 20:11:24 +0200 |
|---|---|---|
| committer | Oskar Nyberg <oskar@mullvad.net> | 2020-06-24 11:23:07 +0200 |
| commit | 1f54942ca8da86efeb64b174dcceecdd9e48a85c (patch) | |
| tree | f1187dd93e4e6cb1f6ee1c91e87e101a7031694b | |
| parent | 63193938c1c5719e06eae51f8d213af55125a2de (diff) | |
| download | mullvadvpn-1f54942ca8da86efeb64b174dcceecdd9e48a85c.tar.xz mullvadvpn-1f54942ca8da86efeb64b174dcceecdd9e48a85c.zip | |
Refactor AccountExpiry
| -rw-r--r-- | gui/src/main/index.ts | 11 | ||||
| -rw-r--r-- | gui/src/renderer/components/Account.tsx | 8 | ||||
| -rw-r--r-- | gui/src/renderer/components/Connect.tsx | 6 | ||||
| -rw-r--r-- | gui/src/renderer/components/ExpiredAccountErrorView.tsx | 6 | ||||
| -rw-r--r-- | gui/src/renderer/components/NotificationArea.tsx | 10 | ||||
| -rw-r--r-- | gui/src/renderer/components/Settings.tsx | 11 | ||||
| -rw-r--r-- | gui/src/renderer/containers/ConnectPage.tsx | 5 | ||||
| -rw-r--r-- | gui/src/renderer/containers/ExpiredAccountErrorViewContainer.tsx | 5 | ||||
| -rw-r--r-- | gui/src/shared/account-expiry.ts | 81 | ||||
| -rw-r--r-- | gui/src/shared/notifications/accountExpiry.ts | 17 |
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 }, }; } |
