summaryrefslogtreecommitdiffhomepage
path: root/gui/src
diff options
context:
space:
mode:
authorOskar Nyberg <oskar@mullvad.net>2021-06-16 11:33:06 +0200
committerOskar Nyberg <oskar@mullvad.net>2021-06-28 09:00:49 +0200
commitc006e28144eff8f328c820992b8e4dd82d0ae0cb (patch)
tree803c3a52580a52df99ea9ffb1f73e1e4e44d3d4f /gui/src
parentbaf7ccb26f31164c463ced344c5bc398ececdf89 (diff)
downloadmullvadvpn-c006e28144eff8f328c820992b8e4dd82d0ae0cb.tar.xz
mullvadvpn-c006e28144eff8f328c820992b8e4dd82d0ae0cb.zip
Improve navigation and transitions for out of time view
Diffstat (limited to 'gui/src')
-rw-r--r--gui/src/renderer/components/ExpiredAccountAddTime.tsx38
-rw-r--r--gui/src/renderer/components/ExpiredAccountErrorView.tsx9
-rw-r--r--gui/src/renderer/components/MainView.tsx21
-rw-r--r--gui/src/renderer/containers/ExpiredAccountErrorViewContainer.tsx8
-rw-r--r--gui/src/renderer/lib/transition-rule.ts12
-rw-r--r--gui/src/renderer/routes.tsx6
-rw-r--r--gui/src/renderer/transitions.ts9
7 files changed, 68 insertions, 35 deletions
diff --git a/gui/src/renderer/components/ExpiredAccountAddTime.tsx b/gui/src/renderer/components/ExpiredAccountAddTime.tsx
index 2564fc5b0c..9831430d4e 100644
--- a/gui/src/renderer/components/ExpiredAccountAddTime.tsx
+++ b/gui/src/renderer/components/ExpiredAccountAddTime.tsx
@@ -7,7 +7,9 @@ import { links, colors } from '../../config.json';
import { formatRelativeDate } from '../../shared/date-helper';
import { messages } from '../../shared/gettext';
import { useAppContext } from '../context';
+import useActions from '../lib/actionsHook';
import History from '../lib/history';
+import account from '../redux/account/actions';
import { IReduxState } from '../redux/store';
import * as AppButton from './AppButton';
import { AriaDescribed, AriaDescription, AriaDescriptionGroup } from './AriaGroup';
@@ -127,7 +129,8 @@ interface ITimeAddedProps {
}
export function TimeAdded(props: ITimeAddedProps) {
- const history = useHistory() as History;
+ const history = useHistory();
+ const finish = useFinishedCallback();
const accountData = useSelector((state: IReduxState) => state.account);
const isNewAccount = useSelector(
(state: IReduxState) =>
@@ -138,9 +141,9 @@ export function TimeAdded(props: ITimeAddedProps) {
if (isNewAccount) {
history.push('/main/setup-finished');
} else {
- history.resetTo('/main');
+ finish();
}
- }, [history]);
+ }, [history, finish]);
const duration =
(accountData.expiry &&
@@ -180,13 +183,9 @@ export function TimeAdded(props: ITimeAddedProps) {
}
export function SetupFinished() {
- const history = useHistory() as History;
+ const finish = useFinishedCallback();
const { openUrl } = useAppContext();
- const navigateToMain = useCallback(() => {
- history.resetWith('/main');
- }, [history]);
-
const openPrivacyLink = useCallback(() => openUrl(links.privacyGuide), [openUrl]);
return (
@@ -229,7 +228,7 @@ export function SetupFinished() {
</AppButton.BlueButton>
</AriaDescribed>
</AriaDescriptionGroup>
- <AppButton.GreenButton onClick={navigateToMain}>
+ <AppButton.GreenButton onClick={finish}>
{messages.pgettext('connect-view', 'Start using the app')}
</AppButton.GreenButton>
</AppButton.ButtonGroup>
@@ -252,3 +251,24 @@ function HeaderBar() {
return <StyledHeader barStyle={headerBarStyle} />;
}
+
+function useFinishedCallback() {
+ const { loggedIn } = useActions(account);
+
+ const history = useHistory() as History;
+ const isNewAccount = useSelector(
+ (state: IReduxState) =>
+ state.account.status.type === 'ok' && state.account.status.method === 'new_account',
+ );
+
+ const callback = useCallback(() => {
+ // Changes login method from "new_account" to "existing_account"
+ if (isNewAccount) {
+ loggedIn();
+ }
+
+ history.resetWith('/main');
+ }, [isNewAccount, loggedIn, history]);
+
+ return callback;
+}
diff --git a/gui/src/renderer/components/ExpiredAccountErrorView.tsx b/gui/src/renderer/components/ExpiredAccountErrorView.tsx
index 90e3148bbb..04d9f96156 100644
--- a/gui/src/renderer/components/ExpiredAccountErrorView.tsx
+++ b/gui/src/renderer/components/ExpiredAccountErrorView.tsx
@@ -1,7 +1,6 @@
import * as React from 'react';
import { sprintf } from 'sprintf-js';
import { links } from '../../config.json';
-import { hasExpired } from '../../shared/account-expiry';
import { AccountToken, TunnelState } from '../../shared/daemon-rpc-types';
import { messages } from '../../shared/gettext';
import { LoginState } from '../redux/account/reducers';
@@ -39,10 +38,8 @@ interface IExpiredAccountErrorViewProps {
isBlocked: boolean;
blockWhenDisconnected: boolean;
accountToken?: AccountToken;
- accountExpiry?: string;
loginState: LoginState;
tunnelState: TunnelState;
- hideWelcomeView: () => void;
onExternalLinkWithAuth: (url: string) => Promise<void>;
onDisconnect: () => Promise<void>;
setBlockWhenDisconnected: (value: boolean) => void;
@@ -61,12 +58,6 @@ export default class ExpiredAccountErrorView extends React.Component<
showBlockWhenDisconnectedAlert: false,
};
- public componentDidUpdate() {
- if (this.props.accountExpiry && !hasExpired(this.props.accountExpiry)) {
- this.props.hideWelcomeView();
- }
- }
-
public render() {
const headerBarStyle =
this.props.loginState.type === 'ok' && this.props.loginState.method === 'new_account'
diff --git a/gui/src/renderer/components/MainView.tsx b/gui/src/renderer/components/MainView.tsx
index a9daeb5453..9698321940 100644
--- a/gui/src/renderer/components/MainView.tsx
+++ b/gui/src/renderer/components/MainView.tsx
@@ -1,13 +1,28 @@
-import React from 'react';
+import React, { useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
+import { useHistory } from 'react-router';
import { hasExpired } from '../../shared/account-expiry';
import { IReduxState } from '../redux/store';
import ConnectPage from '../containers/ConnectPage';
import ExpiredAccountErrorViewContainer from '../containers/ExpiredAccountErrorViewContainer';
export default function MainView() {
+ const history = useHistory();
const accountExpiry = useSelector((state: IReduxState) => state.account.expiry);
- const accountExpired = accountExpiry && hasExpired(accountExpiry);
+ const accountHasExpired = accountExpiry && hasExpired(accountExpiry);
+ const isNewAccount = useSelector(
+ (state: IReduxState) =>
+ state.account.status.type === 'ok' && state.account.status.method === 'new_account',
+ );
+ const [showAccountExpired, setShowAccountExpired] = useState(isNewAccount || accountHasExpired);
- return accountExpired ? <ExpiredAccountErrorViewContainer /> : <ConnectPage />;
+ useEffect(() => {
+ if (accountHasExpired) {
+ setShowAccountExpired(true);
+ } else if (showAccountExpired && !accountHasExpired) {
+ history.push('/main/time-added');
+ }
+ }, [showAccountExpired, accountHasExpired]);
+
+ return showAccountExpired ? <ExpiredAccountErrorViewContainer /> : <ConnectPage />;
}
diff --git a/gui/src/renderer/containers/ExpiredAccountErrorViewContainer.tsx b/gui/src/renderer/containers/ExpiredAccountErrorViewContainer.tsx
index 4d3d8c1271..6297a17f42 100644
--- a/gui/src/renderer/containers/ExpiredAccountErrorViewContainer.tsx
+++ b/gui/src/renderer/containers/ExpiredAccountErrorViewContainer.tsx
@@ -1,26 +1,20 @@
import { connect } from 'react-redux';
import { RouteComponentProps, withRouter } from 'react-router';
-import { bindActionCreators } from 'redux';
import log from '../../shared/logging';
import ExpiredAccountErrorView from '../components/ExpiredAccountErrorView';
-import accountActions from '../redux/account/actions';
import withAppContext, { IAppContext } from '../context';
import { IReduxState, ReduxDispatch } from '../redux/store';
const mapStateToProps = (state: IReduxState) => ({
accountToken: state.account.accountToken,
- accountExpiry: state.account.expiry,
loginState: state.account.status,
tunnelState: state.connection.status,
isBlocked: state.connection.isBlocked,
blockWhenDisconnected: state.settings.blockWhenDisconnected,
});
-const mapDispatchToProps = (dispatch: ReduxDispatch, props: RouteComponentProps & IAppContext) => {
- const account = bindActionCreators(accountActions, dispatch);
+const mapDispatchToProps = (_dispatch: ReduxDispatch, props: RouteComponentProps & IAppContext) => {
return {
- // Changes login method from "new_account" to "existing_account"
- hideWelcomeView: () => account.loggedIn(),
onExternalLinkWithAuth: (url: string) => props.app.openLinkWithAuth(url),
onDisconnect: async () => {
try {
diff --git a/gui/src/renderer/lib/transition-rule.ts b/gui/src/renderer/lib/transition-rule.ts
index ae0e2ad5b7..91d5cd6d4c 100644
--- a/gui/src/renderer/lib/transition-rule.ts
+++ b/gui/src/renderer/lib/transition-rule.ts
@@ -1,3 +1,5 @@
+import { Action } from 'history';
+
export interface ITransitionDescriptor {
name: string;
duration: number;
@@ -16,15 +18,19 @@ export interface ITransitionMatch {
export default class TransitionRule {
constructor(private from: string | null, private to: string, private fork: ITransitionFork) {}
- public match(fromRoute: string | null, toRoute: string): ITransitionMatch | null {
- if ((!this.from || this.from === fromRoute) && this.to === toRoute) {
+ public match(
+ fromRoute: string | null,
+ toRoute: string,
+ action?: Action,
+ ): ITransitionMatch | null {
+ if (action !== 'POP' && (!this.from || this.from === fromRoute) && this.to === toRoute) {
return {
direction: 'forward',
descriptor: this.fork.forward,
};
}
- if ((!this.from || this.from === toRoute) && this.to === fromRoute) {
+ if (action !== 'PUSH' && (!this.from || this.from === toRoute) && this.to === fromRoute) {
return {
direction: 'backward',
descriptor: this.fork.backward,
diff --git a/gui/src/renderer/routes.tsx b/gui/src/renderer/routes.tsx
index 2c2325db19..fe49469092 100644
--- a/gui/src/renderer/routes.tsx
+++ b/gui/src/renderer/routes.tsx
@@ -1,3 +1,4 @@
+import { Action } from 'history';
import * as React from 'react';
import { Route, RouteComponentProps, Switch, withRouter } from 'react-router';
import Launch from './components/Launch';
@@ -28,6 +29,7 @@ import {
interface IAppRoutesState {
previousLocation?: RouteComponentProps['location'];
currentLocation: RouteComponentProps['location'];
+ action?: Action;
}
class AppRoutes extends React.Component<RouteComponentProps, IAppRoutesState> {
@@ -47,10 +49,11 @@ class AppRoutes extends React.Component<RouteComponentProps, IAppRoutesState> {
// React throttles updates, so it's impossible to capture the intermediate navigation without
// listening to the history directly.
this.unobserveHistory = (this.props.history as History).listen(
- (location, _action, affectedEntries) => {
+ (location, action, affectedEntries) => {
this.setState({
previousLocation: affectedEntries[0],
currentLocation: location,
+ action,
});
},
);
@@ -67,6 +70,7 @@ class AppRoutes extends React.Component<RouteComponentProps, IAppRoutesState> {
const transitionProps = getTransitionProps(
this.state.previousLocation ? this.state.previousLocation.pathname : null,
location.pathname,
+ this.state.action,
);
return (
diff --git a/gui/src/renderer/transitions.ts b/gui/src/renderer/transitions.ts
index 8dfd2d38ee..cddf8c57a2 100644
--- a/gui/src/renderer/transitions.ts
+++ b/gui/src/renderer/transitions.ts
@@ -1,3 +1,4 @@
+import { Action } from 'history';
import TransitionRule, { ITransitionDescriptor, ITransitionFork } from './lib/transition-rule';
export interface ITransitionGroupProps {
@@ -47,12 +48,13 @@ const transitionRules = [
r('/settings/advanced', '/settings/advanced/wireguard-keys', transitions.push),
r('/settings/advanced', '/settings/advanced/linux-split-tunneling', transitions.push),
r('/settings', '/settings/support', transitions.push),
- r('/main', '/main/time-added', transitions.push),
- r('/main/time-added', '/main/setup-finished', transitions.push),
r('/main', '/main/voucher/redeem', transitions.push),
r('/main/voucher/redeem', '/main/voucher/success', transitions.push),
r('/main/voucher/success', '/main/setup-finished', transitions.push),
r('/main/voucher/success', '/main', transitions.push),
+ r('/main/time-added', '/main/setup-finished', transitions.push),
+ r('/main/time-added', '/main', transitions.push),
+ r('/main', '/main/time-added', transitions.push),
r('/main/setup-finished', '/main', transitions.push),
r(null, '/settings', transitions.slide),
r(null, '/select-location', transitions.slide),
@@ -67,6 +69,7 @@ const transitionRules = [
export function getTransitionProps(
fromRoute: string | null,
toRoute: string,
+ action?: Action,
): ITransitionGroupProps {
// ignore initial transition and transition between the same routes
if (!fromRoute || fromRoute === toRoute) {
@@ -74,7 +77,7 @@ export function getTransitionProps(
}
for (const rule of transitionRules) {
- const match = rule.match(fromRoute, toRoute);
+ const match = rule.match(fromRoute, toRoute, action);
if (match) {
return toTransitionGroupProps(match.descriptor);
}