diff options
| author | Oskar Nyberg <oskar@mullvad.net> | 2021-06-16 11:33:06 +0200 |
|---|---|---|
| committer | Oskar Nyberg <oskar@mullvad.net> | 2021-06-28 09:00:49 +0200 |
| commit | c006e28144eff8f328c820992b8e4dd82d0ae0cb (patch) | |
| tree | 803c3a52580a52df99ea9ffb1f73e1e4e44d3d4f | |
| parent | baf7ccb26f31164c463ced344c5bc398ececdf89 (diff) | |
| download | mullvadvpn-c006e28144eff8f328c820992b8e4dd82d0ae0cb.tar.xz mullvadvpn-c006e28144eff8f328c820992b8e4dd82d0ae0cb.zip | |
Improve navigation and transitions for out of time view
| -rw-r--r-- | gui/src/renderer/components/ExpiredAccountAddTime.tsx | 38 | ||||
| -rw-r--r-- | gui/src/renderer/components/ExpiredAccountErrorView.tsx | 9 | ||||
| -rw-r--r-- | gui/src/renderer/components/MainView.tsx | 21 | ||||
| -rw-r--r-- | gui/src/renderer/containers/ExpiredAccountErrorViewContainer.tsx | 8 | ||||
| -rw-r--r-- | gui/src/renderer/lib/transition-rule.ts | 12 | ||||
| -rw-r--r-- | gui/src/renderer/routes.tsx | 6 | ||||
| -rw-r--r-- | gui/src/renderer/transitions.ts | 9 |
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); } |
