diff options
| author | Oskar Nyberg <oskar@mullvad.net> | 2022-02-09 12:52:43 +0100 |
|---|---|---|
| committer | Oskar Nyberg <oskar@mullvad.net> | 2022-02-09 12:52:43 +0100 |
| commit | 98616b7e7ae6c8722b970895a5756d32872a2b79 (patch) | |
| tree | a3878604e9a72ed75bf1c4c1989958c8610b9e46 /gui/src | |
| parent | 5d0be2864ca826669681c0cd3bd818629096225e (diff) | |
| parent | f584c3f3bcdbf508ef7490c5586fc83f3d051805 (diff) | |
| download | mullvadvpn-98616b7e7ae6c8722b970895a5756d32872a2b79.tar.xz mullvadvpn-98616b7e7ae6c8722b970895a5756d32872a2b79.zip | |
Merge branch 'fix-time-added-display'
Diffstat (limited to 'gui/src')
| -rw-r--r-- | gui/src/main/index.ts | 8 | ||||
| -rw-r--r-- | gui/src/renderer/app.tsx | 11 | ||||
| -rw-r--r-- | gui/src/renderer/components/ExpiredAccountAddTime.tsx | 59 | ||||
| -rw-r--r-- | gui/src/renderer/components/RedeemVoucher.tsx | 22 | ||||
| -rw-r--r-- | gui/src/renderer/lib/history.tsx | 28 | ||||
| -rw-r--r-- | gui/src/renderer/lib/routes.ts | 13 | ||||
| -rw-r--r-- | gui/src/renderer/redux/account/actions.ts | 4 | ||||
| -rw-r--r-- | gui/src/renderer/redux/account/reducers.ts | 5 | ||||
| -rw-r--r-- | gui/src/shared/daemon-rpc-types.ts | 1 |
9 files changed, 84 insertions, 67 deletions
diff --git a/gui/src/main/index.ts b/gui/src/main/index.ts index 5b7504e711..bb84d520fe 100644 --- a/gui/src/main/index.ts +++ b/gui/src/main/index.ts @@ -230,13 +230,7 @@ class ApplicationMain { return this.daemonRpc.getAccountData(accountToken); }, (accountData) => { - this.accountData = accountData && { - ...accountData, - previousExpiry: - accountData.expiry !== this.accountData?.expiry - ? this.accountData?.expiry - : this.accountData?.previousExpiry, - }; + this.accountData = accountData; if (this.windowController) { IpcMainEventChannel.account.notify(this.windowController.webContents, this.accountData); diff --git a/gui/src/renderer/app.tsx b/gui/src/renderer/app.tsx index f5134653a7..e9aadedcc4 100644 --- a/gui/src/renderer/app.tsx +++ b/gui/src/renderer/app.tsx @@ -120,7 +120,7 @@ export default class AppRenderer { }); IpcRendererEventChannel.account.listen((newAccountData?: IAccountData) => { - this.setAccountExpiry(newAccountData?.expiry, newAccountData?.previousExpiry); + this.setAccountExpiry(newAccountData?.expiry); }); IpcRendererEventChannel.accountHistory.listen((newAccountHistory?: AccountToken) => { @@ -199,10 +199,7 @@ export default class AppRenderer { initialState.translations.relayLocations, ); - this.setAccountExpiry( - initialState.accountData?.expiry, - initialState.accountData?.previousExpiry, - ); + this.setAccountExpiry(initialState.accountData?.expiry); this.setSettings(initialState.settings); this.handleAccountChange(undefined, initialState.settings.accountToken); this.setAccountHistory(initialState.accountHistory); @@ -834,8 +831,8 @@ export default class AppRenderer { this.reduxActions.settings.updateGuiSettings(guiSettings); } - private setAccountExpiry(expiry?: string, previousExpiry?: string) { - this.reduxActions.account.updateAccountExpiry(expiry, previousExpiry); + private setAccountExpiry(expiry?: string) { + this.reduxActions.account.updateAccountExpiry(expiry); } private storeAutoStart(autoStart: boolean) { diff --git a/gui/src/renderer/components/ExpiredAccountAddTime.tsx b/gui/src/renderer/components/ExpiredAccountAddTime.tsx index 49e8bf8107..4c6c7989d7 100644 --- a/gui/src/renderer/components/ExpiredAccountAddTime.tsx +++ b/gui/src/renderer/components/ExpiredAccountAddTime.tsx @@ -1,16 +1,16 @@ import { useCallback } from 'react'; -import { useSelector } from 'react-redux'; +import { useSelector } from '../redux/store'; import { sprintf } from 'sprintf-js'; import styled from 'styled-components'; import { links, colors } from '../../config.json'; +import { formatDate } from '../../shared/account-expiry'; import { formatRelativeDate } from '../../shared/date-helper'; import { messages } from '../../shared/gettext'; import { useAppContext } from '../context'; import useActions from '../lib/actionsHook'; import { transitions, useHistory } from '../lib/history'; -import { RoutePath } from '../lib/routes'; +import { generateRoutePath, RoutePath } from '../lib/routes'; import account from '../redux/account/actions'; -import { IReduxState } from '../redux/store'; import * as AppButton from './AppButton'; import { AriaDescribed, AriaDescription, AriaDescriptionGroup } from './AriaGroup'; import { bigText } from './common-styles'; @@ -24,6 +24,7 @@ import { RedeemVoucherResponse, RedeemVoucherSubmitButton, } from './RedeemVoucher'; +import { useParams } from 'react-router'; export const StyledHeader = styled(DefaultHeaderBar)({ flex: 0, @@ -82,9 +83,13 @@ export const StyledStatusIcon = styled.div({ export function VoucherInput() { const history = useHistory(); - const onSuccess = useCallback(() => { - history.push(RoutePath.voucherSuccess); - }, [history]); + const onSuccess = useCallback( + (newExpiry: string, secondsAdded: number) => { + const path = generateRoutePath(RoutePath.voucherSuccess, { newExpiry, secondsAdded }); + history.push(path); + }, + [history], + ); const navigateBack = useCallback(() => { history.pop(); @@ -119,23 +124,31 @@ export function VoucherInput() { } export function VoucherVerificationSuccess() { + const { newExpiry, secondsAdded } = useParams<{ newExpiry: string; secondsAdded: string }>(); + return ( - <TimeAdded title={messages.pgettext('connect-view', 'Voucher was successfully redeemed')} /> + <TimeAdded + newExpiry={newExpiry} + secondsAdded={parseInt(secondsAdded)} + title={messages.pgettext('connect-view', 'Voucher was successfully redeemed')} + /> ); } interface ITimeAddedProps { title?: string; + newExpiry?: string; + secondsAdded?: number; } export function TimeAdded(props: ITimeAddedProps) { const history = useHistory(); const finish = useFinishedCallback(); - const accountData = useSelector((state: IReduxState) => state.account); + const expiry = useSelector((state) => state.account.expiry); const isNewAccount = useSelector( - (state: IReduxState) => - state.account.status.type === 'ok' && state.account.status.method === 'new_account', + (state) => state.account.status.type === 'ok' && state.account.status.method === 'new_account', ); + const locale = useSelector((state) => state.userInterface.locale); const navigateToSetupFinished = useCallback(() => { if (isNewAccount) { @@ -146,10 +159,8 @@ export function TimeAdded(props: ITimeAddedProps) { }, [history, finish]); const duration = - (accountData.expiry && - accountData.previousExpiry && - formatRelativeDate(accountData.expiry, accountData.previousExpiry)) ?? - ''; + props.secondsAdded !== undefined ? formatRelativeDate(props.secondsAdded * 1000, 0) : undefined; + const newExpiry = formatDate(props.newExpiry ?? expiry!, locale); return ( <Layout> @@ -164,7 +175,17 @@ export function TimeAdded(props: ITimeAddedProps) { {props.title ?? messages.pgettext('connect-view', 'Time was successfully added')} </StyledTitle> <StyledLabel> - {sprintf(messages.gettext('%(duration)s was added to your account.'), { duration })} + {duration + ? sprintf( + messages.gettext('%(duration)s was added, account paid until %(expiry)s.'), + { + duration, + expiry: newExpiry, + }, + ) + : sprintf(messages.gettext('Account paid until %(expiry)s.'), { + expiry: newExpiry, + })} </StyledLabel> </StyledBody> @@ -238,10 +259,9 @@ export function SetupFinished() { function HeaderBar() { const isNewAccount = useSelector( - (state: IReduxState) => - state.account.status.type === 'ok' && state.account.status.method === 'new_account', + (state) => state.account.status.type === 'ok' && state.account.status.method === 'new_account', ); - const tunnelState = useSelector((state: IReduxState) => state.connection.status); + const tunnelState = useSelector((state) => state.connection.status); const headerBarStyle = isNewAccount ? HeaderBarStyle.default : calculateHeaderBarStyle(tunnelState); @@ -254,8 +274,7 @@ function useFinishedCallback() { const history = useHistory(); const isNewAccount = useSelector( - (state: IReduxState) => - state.account.status.type === 'ok' && state.account.status.method === 'new_account', + (state) => state.account.status.type === 'ok' && state.account.status.method === 'new_account', ); const callback = useCallback(() => { diff --git a/gui/src/renderer/components/RedeemVoucher.tsx b/gui/src/renderer/components/RedeemVoucher.tsx index c7697f2f6f..7810112082 100644 --- a/gui/src/renderer/components/RedeemVoucher.tsx +++ b/gui/src/renderer/components/RedeemVoucher.tsx @@ -1,13 +1,13 @@ import React, { useCallback, useContext, useState } from 'react'; -import { useSelector } from 'react-redux'; +import { useSelector } from '../redux/store'; import { sprintf } from 'sprintf-js'; +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 { useAppContext } from '../context'; import useActions from '../lib/actionsHook'; import accountActions from '../redux/account/actions'; -import { IReduxState } from '../redux/store'; import * as AppButton from './AppButton'; import ImageView from './ImageView'; import { ModalAlert } from './Modal'; @@ -59,7 +59,7 @@ const RedeemVoucherContext = React.createContext<IRedeemVoucherContextValue>({ interface IRedeemVoucherProps { onSubmit?: () => void; - onSuccess?: () => void; + onSuccess?: (newExpiry: string, secondsAdded: number) => void; onFailure?: () => void; children?: React.ReactNode; } @@ -95,7 +95,7 @@ export function RedeemVoucherContainer(props: IRedeemVoucherProps) { setSubmitting(false); setResponse(response); if (response.type === 'success') { - onSuccess?.(); + onSuccess?.(response.newExpiry, response.secondsAdded); } else { onFailure?.(); } @@ -212,15 +212,12 @@ interface IRedeemVoucherAlertProps { export function RedeemVoucherAlert(props: IRedeemVoucherAlertProps) { const { submitting, response } = useContext(RedeemVoucherContext); - const accountData = useSelector((state: IReduxState) => state.account); - - const duration = - (accountData.expiry && - accountData.previousExpiry && - formatRelativeDate(accountData.expiry, accountData.previousExpiry)) ?? - ''; + const locale = useSelector((state) => state.userInterface.locale); if (response?.type === 'success') { + const duration = formatRelativeDate(response.secondsAdded * 1000, 0); + const expiry = formatDate(response.newExpiry, locale); + return ( <ModalAlert buttons={[ @@ -236,8 +233,9 @@ export function RedeemVoucherAlert(props: IRedeemVoucherAlertProps) { {messages.pgettext('redeem-voucher-view', 'Voucher was successfully redeemed.')} </StyledTitle> <StyledLabel> - {sprintf(messages.gettext('%(duration)s was added to your account.'), { + {sprintf(messages.gettext('%(duration)s was added, account paid until %(expiry)s.'), { duration, + expiry, })} </StyledLabel> </ModalAlert> diff --git a/gui/src/renderer/lib/history.tsx b/gui/src/renderer/lib/history.tsx index 4abdcd5fcb..f5bc213112 100644 --- a/gui/src/renderer/lib/history.tsx +++ b/gui/src/renderer/lib/history.tsx @@ -1,7 +1,7 @@ import { Location, Action, LocationDescriptorObject, History as OriginalHistory } from 'history'; import React from 'react'; import { RouteComponentProps, useHistory as useReactRouterHistory, withRouter } from 'react-router'; -import { RoutePath } from './routes'; +import { GeneratedRoutePath, RoutePath } from './routes'; export interface ITransitionSpecification { name: string; @@ -38,7 +38,7 @@ export const transitions: ITransitionMap = { }, }; -type LocationDescriptor<S> = RoutePath | LocationDescriptorObject<S>; +type LocationDescriptor<S> = RoutePath | GeneratedRoutePath | LocationDescriptorObject<S>; type LocationListener<S = unknown> = ( location: Location<S>, @@ -168,7 +168,11 @@ export default class History { } private createLocation(location: LocationDescriptor<S>, state?: S): Location<S> { - if (typeof location === 'object') { + if (typeof location === 'string') { + return this.createLocationFromString(location, state); + } else if ('routePath' in location) { + return this.createLocationFromString(location.routePath, state); + } else { return { pathname: location.pathname ?? this.location.pathname, search: location.search ?? '', @@ -176,17 +180,19 @@ export default class History { state: location.state, key: location.key ?? this.getRandomKey(), }; - } else { - return { - pathname: location, - search: '', - hash: '', - state, - key: this.getRandomKey(), - }; } } + private createLocationFromString(path: string, state?: S): Location<S> { + return { + pathname: path, + search: '', + hash: '', + state, + key: this.getRandomKey(), + }; + } + private getRandomKey() { return Math.random().toString(36).substr(8); } diff --git a/gui/src/renderer/lib/routes.ts b/gui/src/renderer/lib/routes.ts index dee83c2a42..8a63cd6de0 100644 --- a/gui/src/renderer/lib/routes.ts +++ b/gui/src/renderer/lib/routes.ts @@ -1,9 +1,13 @@ +import { generatePath } from 'react-router'; + +export type GeneratedRoutePath = { routePath: string }; + export enum RoutePath { launch = '/', login = '/login', main = '/main', redeemVoucher = '/main/voucher/redeem', - voucherSuccess = '/main/voucher/success', + voucherSuccess = '/main/voucher/success/:newExpiry/:secondsAdded', timeAdded = '/main/time-added', setupFinished = '/main/setup-finished', settings = '/settings', @@ -19,3 +23,10 @@ export enum RoutePath { selectLocation = '/select-location', filterByProvider = '/select-location/filter-by-provider', } + +export function generateRoutePath( + routePath: RoutePath, + parameters: Parameters<typeof generatePath>[1], +): GeneratedRoutePath { + return { routePath: generatePath(routePath, parameters) }; +} diff --git a/gui/src/renderer/redux/account/actions.ts b/gui/src/renderer/redux/account/actions.ts index 4a9790df00..b8fbe94d39 100644 --- a/gui/src/renderer/redux/account/actions.ts +++ b/gui/src/renderer/redux/account/actions.ts @@ -50,7 +50,6 @@ interface IUpdateAccountHistoryAction { interface IUpdateAccountExpiryAction { type: 'UPDATE_ACCOUNT_EXPIRY'; expiry?: string; - previousExpiry?: string; } export type AccountAction = @@ -133,11 +132,10 @@ function updateAccountHistory(accountHistory?: AccountToken): IUpdateAccountHist }; } -function updateAccountExpiry(expiry?: string, previousExpiry?: string): IUpdateAccountExpiryAction { +function updateAccountExpiry(expiry?: string): IUpdateAccountExpiryAction { return { type: 'UPDATE_ACCOUNT_EXPIRY', expiry, - previousExpiry, }; } diff --git a/gui/src/renderer/redux/account/reducers.ts b/gui/src/renderer/redux/account/reducers.ts index 6b68fef9cf..53bc55db1b 100644 --- a/gui/src/renderer/redux/account/reducers.ts +++ b/gui/src/renderer/redux/account/reducers.ts @@ -10,7 +10,6 @@ export interface IAccountReduxState { accountToken?: AccountToken; accountHistory?: AccountToken; expiry?: string; // ISO8601 - previousExpiry?: string; // ISO8601 status: LoginState; } @@ -18,7 +17,6 @@ const initialState: IAccountReduxState = { accountToken: undefined, accountHistory: undefined, expiry: undefined, - previousExpiry: undefined, status: { type: 'none' }, }; @@ -50,7 +48,6 @@ export default function ( status: { type: 'none' }, accountToken: undefined, expiry: undefined, - previousExpiry: undefined, }; case 'RESET_LOGIN_ERROR': return { @@ -73,7 +70,6 @@ export default function ( status: { type: 'ok', method: 'new_account' }, accountToken: action.token, expiry: action.expiry, - previousExpiry: undefined, }; case 'UPDATE_ACCOUNT_TOKEN': return { @@ -89,7 +85,6 @@ export default function ( return { ...state, expiry: action.expiry, - previousExpiry: action.previousExpiry, }; } diff --git a/gui/src/shared/daemon-rpc-types.ts b/gui/src/shared/daemon-rpc-types.ts index 68e61fc3ea..3abde2ee64 100644 --- a/gui/src/shared/daemon-rpc-types.ts +++ b/gui/src/shared/daemon-rpc-types.ts @@ -1,6 +1,5 @@ export interface IAccountData { expiry: string; - previousExpiry?: string; } export type AccountToken = string; export type Ip = string; |
