summaryrefslogtreecommitdiffhomepage
path: root/gui
diff options
context:
space:
mode:
authorOskar Nyberg <oskar@mullvad.net>2022-07-15 17:43:46 +0200
committerOskar Nyberg <oskar@mullvad.net>2022-07-22 14:36:05 +0200
commit566b5a118bebb5633593523badea643971c96b97 (patch)
tree12d0849640b8888e95e25c9e0ad43f12cc9730cf /gui
parent33a452faefd260b31fc84ea4569735e465feed96 (diff)
downloadmullvadvpn-566b5a118bebb5633593523badea643971c96b97.tar.xz
mullvadvpn-566b5a118bebb5633593523badea643971c96b97.zip
Refactor Settings.tsx
Diffstat (limited to 'gui')
-rw-r--r--gui/src/renderer/components/AppRouter.tsx4
-rw-r--r--gui/src/renderer/components/Settings.tsx411
-rw-r--r--gui/src/renderer/components/SettingsStyles.tsx6
-rw-r--r--gui/src/renderer/containers/SettingsPage.tsx36
-rw-r--r--gui/src/renderer/lib/routes.ts2
5 files changed, 203 insertions, 256 deletions
diff --git a/gui/src/renderer/components/AppRouter.tsx b/gui/src/renderer/components/AppRouter.tsx
index e9355775b2..75892805e9 100644
--- a/gui/src/renderer/components/AppRouter.tsx
+++ b/gui/src/renderer/components/AppRouter.tsx
@@ -8,7 +8,6 @@ import OpenVPNSettingsPage from '../containers/OpenVPNSettingsPage';
import ProblemReportPage from '../containers/ProblemReportPage';
import SelectLanguagePage from '../containers/SelectLanguagePage';
import SelectLocationPage from '../containers/SelectLocationPage';
-import SettingsPage from '../containers/SettingsPage';
import WireguardSettingsPage from '../containers/WireguardSettingsPage';
import withAppContext, { IAppContext } from '../context';
import { IHistoryProps, ITransitionSpecification, transitions, withHistory } from '../lib/history';
@@ -25,6 +24,7 @@ import Focus, { IFocusHandle } from './Focus';
import InterfaceSettings from './InterfaceSettings';
import Launch from './Launch';
import MainView from './MainView';
+import Settings from './Settings';
import SplitTunnelingSettings from './SplitTunnelingSettings';
import Support from './Support';
import TooManyDevices from './TooManyDevices';
@@ -87,7 +87,7 @@ class AppRouter extends React.Component<IHistoryProps & IAppContext, IAppRoutesS
<Route exact path={RoutePath.voucherSuccess} component={VoucherVerificationSuccess} />
<Route exact path={RoutePath.timeAdded} component={TimeAdded} />
<Route exact path={RoutePath.setupFinished} component={SetupFinished} />
- <Route exact path={RoutePath.settings} component={SettingsPage} />
+ <Route exact path={RoutePath.settings} component={Settings} />
<Route exact path={RoutePath.selectLanguage} component={SelectLanguagePage} />
<Route exact path={RoutePath.accountSettings} component={AccountPage} />
<Route exact path={RoutePath.interfaceSettings} component={InterfaceSettings} />
diff --git a/gui/src/renderer/components/Settings.tsx b/gui/src/renderer/components/Settings.tsx
index 2deb1ee0e7..812deef142 100644
--- a/gui/src/renderer/components/Settings.tsx
+++ b/gui/src/renderer/components/Settings.tsx
@@ -1,10 +1,12 @@
-import * as React from 'react';
+import { useCallback, useEffect } from 'react';
import { colors, links } from '../../config.json';
import { formatRemainingTime, hasExpired } from '../../shared/account-expiry';
import { messages } from '../../shared/gettext';
-import History from '../lib/history';
-import { LoginState } from '../redux/account/reducers';
+import { useAppContext } from '../context';
+import { useHistory } from '../lib/history';
+import { RoutePath } from '../lib/routes';
+import { useSelector } from '../redux/store';
import { AriaDescribed, AriaDescription, AriaDescriptionGroup } from './AriaGroup';
import * as Cell from './cell';
import { BackAction } from './KeyboardNavigation';
@@ -13,7 +15,6 @@ import { NavigationBar, NavigationContainer, NavigationItems, TitleBarItem } fro
import SettingsHeader, { HeaderTitle } from './SettingsHeader';
import {
StyledCellIcon,
- StyledCellSpacer,
StyledContainer,
StyledContent,
StyledNavigationScrollbars,
@@ -22,238 +23,226 @@ import {
StyledSettingsContent,
} from './SettingsStyles';
-export interface IProps {
- preferredLocaleDisplayName: string;
- loginState: LoginState;
- connectedToDaemon: boolean;
- accountExpiry?: string;
- appVersion: string;
- consistentVersion: boolean;
- upToDateVersion: boolean;
- suggestedIsBeta: boolean;
- isOffline: boolean;
- onQuit: () => void;
- onClose: () => void;
- onViewSelectLanguage: () => void;
- onViewAccount: () => void;
- onViewSupport: () => void;
- onViewPreferences: () => void;
- onViewAdvancedSettings: () => void;
- onExternalLink: (url: string) => void;
- updateAccountData: () => void;
- history: History;
-}
+export default function Support() {
+ const history = useHistory();
+ const { updateAccountData } = useAppContext();
-export default class Settings extends React.Component<IProps> {
- public componentDidMount() {
- if (this.props.history.action === 'PUSH') {
- this.props.updateAccountData();
+ useEffect(() => {
+ if (history.action === 'PUSH') {
+ updateAccountData();
}
- }
+ }, []);
+
+ const loginState = useSelector((state) => state.account.status);
+ const connectedToDaemon = useSelector((state) => state.userInterface.connectedToDaemon);
+
+ const showLargeTitle = loginState.type !== 'ok';
+ const showSubSettings = loginState.type === 'ok' && connectedToDaemon;
- public render() {
- const showLargeTitle = this.props.loginState.type !== 'ok';
+ return (
+ <BackAction icon="close" action={history.dismiss}>
+ <Layout>
+ <StyledContainer>
+ <NavigationContainer>
+ <NavigationBar alwaysDisplayBarTitle={!showLargeTitle}>
+ <NavigationItems>
+ <TitleBarItem>
+ {
+ // TRANSLATORS: Title label in navigation bar
+ messages.pgettext('navigation-bar', 'Settings')
+ }
+ </TitleBarItem>
+ </NavigationItems>
+ </NavigationBar>
- return (
- <BackAction icon="close" action={this.props.onClose}>
- <Layout>
- <StyledContainer>
- <NavigationContainer>
- <NavigationBar alwaysDisplayBarTitle={!showLargeTitle}>
- <NavigationItems>
- <TitleBarItem>
- {
- // TRANSLATORS: Title label in navigation bar
- messages.pgettext('navigation-bar', 'Settings')
- }
- </TitleBarItem>
- </NavigationItems>
- </NavigationBar>
+ <StyledNavigationScrollbars fillContainer>
+ <StyledContent>
+ {showLargeTitle && (
+ <SettingsHeader>
+ <HeaderTitle>{messages.pgettext('navigation-bar', 'Settings')}</HeaderTitle>
+ </SettingsHeader>
+ )}
- <StyledNavigationScrollbars fillContainer>
- <StyledContent>
- {showLargeTitle && (
- <SettingsHeader>
- <HeaderTitle>{messages.pgettext('navigation-bar', 'Settings')}</HeaderTitle>
- </SettingsHeader>
+ <StyledSettingsContent>
+ {showSubSettings && (
+ <>
+ <Cell.Group>
+ <AccountButton />
+ <InterfaceSettingsButton />
+ <VpnSettingsButton />
+ </Cell.Group>
+
+ {(window.env.platform === 'linux' || window.env.platform === 'win32') && (
+ <Cell.Group>
+ <SplitTunnelingButton />
+ </Cell.Group>
+ )}
+ </>
)}
- <StyledSettingsContent>
- {this.renderTopButtons()}
- {this.renderMiddleButtons()}
- {this.renderBottomButtons()}
- </StyledSettingsContent>
- </StyledContent>
+ <Cell.Group>
+ <SupportButton />
+ <AppVersionButton />
+ </Cell.Group>
+ </StyledSettingsContent>
+ </StyledContent>
- {this.renderQuitButton()}
- </StyledNavigationScrollbars>
- </NavigationContainer>
- </StyledContainer>
- </Layout>
- </BackAction>
- );
- }
+ <QuitButton />
+ </StyledNavigationScrollbars>
+ </NavigationContainer>
+ </StyledContainer>
+ </Layout>
+ </BackAction>
+ );
+}
- private openDownloadLink = () =>
- this.props.onExternalLink(this.props.suggestedIsBeta ? links.betaDownload : links.download);
- private openFaqLink = () => this.props.onExternalLink(links.faq);
+function AccountButton() {
+ const history = useHistory();
+ const navigate = useCallback(() => history.push(RoutePath.accountSettings), [history]);
- private renderQuitButton() {
- return (
- <StyledQuitButton onClick={this.props.onQuit}>
- {messages.pgettext('settings-view', 'Quit app')}
- </StyledQuitButton>
- );
- }
+ const accountExpiry = useSelector((state) => state.account.expiry);
+ const isOutOfTime = accountExpiry ? hasExpired(accountExpiry) : false;
+ const formattedExpiry = accountExpiry ? formatRemainingTime(accountExpiry).toUpperCase() : '';
+ const outOfTimeMessage = messages.pgettext('settings-view', 'OUT OF TIME');
- private renderTopButtons() {
- const isLoggedIn = this.props.loginState.type === 'ok';
- if (!isLoggedIn || !this.props.connectedToDaemon) {
- return null;
- }
+ return (
+ <Cell.CellNavigationButton onClick={navigate}>
+ <Cell.Label>
+ {
+ // TRANSLATORS: Navigation button to the 'Account' view
+ messages.pgettext('settings-view', 'Account')
+ }
+ </Cell.Label>
+ <StyledOutOfTimeSubText isOutOfTime={isOutOfTime}>
+ {isOutOfTime ? outOfTimeMessage : formattedExpiry}
+ </StyledOutOfTimeSubText>
+ </Cell.CellNavigationButton>
+ );
+}
- const isOutOfTime = this.props.accountExpiry ? hasExpired(this.props.accountExpiry) : false;
- const formattedExpiry = this.props.accountExpiry
- ? formatRemainingTime(this.props.accountExpiry).toUpperCase()
- : '';
+function InterfaceSettingsButton() {
+ const history = useHistory();
+ const navigate = useCallback(() => history.push(RoutePath.interfaceSettings), [history]);
- const outOfTimeMessage = messages.pgettext('settings-view', 'OUT OF TIME');
+ return (
+ <Cell.CellNavigationButton onClick={navigate}>
+ <Cell.Label>
+ {
+ // TRANSLATORS: Navigation button to the 'Interface settings' view
+ messages.pgettext('settings-view', 'Interface settings')
+ }
+ </Cell.Label>
+ </Cell.CellNavigationButton>
+ );
+}
- return (
- <>
- <Cell.CellButton onClick={this.props.onViewAccount}>
- <Cell.Label>
- {
- // TRANSLATORS: Navigation button to the 'Account' view
- messages.pgettext('settings-view', 'Account')
- }
- </Cell.Label>
- <StyledOutOfTimeSubText isOutOfTime={isOutOfTime}>
- {isOutOfTime ? outOfTimeMessage : formattedExpiry}
- </StyledOutOfTimeSubText>
- <Cell.Icon height={12} width={7} source="icon-chevron" />
- </Cell.CellButton>
+function VpnSettingsButton() {
+ const history = useHistory();
+ const navigate = useCallback(() => history.push(RoutePath.vpnSettings), [history]);
- <Cell.CellButton onClick={this.props.onViewPreferences}>
- <Cell.Label>
- {
- // TRANSLATORS: Navigation button to the 'Preferences' view
- messages.pgettext('settings-view', 'Preferences')
- }
- </Cell.Label>
- <Cell.Icon height={12} width={7} source="icon-chevron" />
- </Cell.CellButton>
+ return (
+ <Cell.CellNavigationButton onClick={navigate}>
+ <Cell.Label>
+ {
+ // TRANSLATORS: Navigation button to the 'VPN settings' view
+ messages.pgettext('settings-view', 'VPN settings')
+ }
+ </Cell.Label>
+ </Cell.CellNavigationButton>
+ );
+}
- <Cell.CellButton onClick={this.props.onViewAdvancedSettings}>
- <Cell.Label>
- {
- // TRANSLATORS: Navigation button to the 'Advanced' settings view
- messages.pgettext('settings-view', 'Advanced')
- }
- </Cell.Label>
- <Cell.Icon height={12} width={7} source="icon-chevron" />
- </Cell.CellButton>
- <StyledCellSpacer />
- </>
- );
- }
+function SplitTunnelingButton() {
+ const history = useHistory();
+ const navigate = useCallback(() => history.push(RoutePath.splitTunneling), [history]);
+
+ return (
+ <Cell.CellNavigationButton onClick={navigate}>
+ <Cell.Label>
+ {
+ // TRANSLATORS: Navigation button to the 'Split tunneling' view
+ messages.pgettext('settings-view', 'Split tunneling')
+ }
+ </Cell.Label>
+ </Cell.CellNavigationButton>
+ );
+}
- private renderMiddleButtons() {
- let icon;
- let footer;
- if (!this.props.consistentVersion || !this.props.upToDateVersion) {
- const inconsistentVersionMessage = messages.pgettext(
- 'settings-view',
- 'App is out of sync. Please quit and restart.',
- );
+function AppVersionButton() {
+ const appVersion = useSelector((state) => state.version.current);
+ const consistentVersion = useSelector((state) => state.version.consistent);
+ const upToDateVersion = useSelector((state) => (state.version.suggestedUpgrade ? false : true));
+ const suggestedIsBeta = useSelector((state) => state.version.suggestedIsBeta ?? false);
+ const isOffline = useSelector((state) => state.connection.isBlocked);
- const updateAvailableMessage = messages.pgettext(
- 'settings-view',
- 'Update available. Install the latest app version to stay up to date.',
- );
+ const { openUrl } = useAppContext();
+ const openDownloadLink = useCallback(
+ () => openUrl(suggestedIsBeta ? links.betaDownload : links.download),
+ [openUrl, suggestedIsBeta],
+ );
- const message = !this.props.consistentVersion
- ? inconsistentVersionMessage
- : updateAvailableMessage;
+ let icon;
+ let footer;
+ if (!consistentVersion || !upToDateVersion) {
+ const inconsistentVersionMessage = messages.pgettext(
+ 'settings-view',
+ 'App is out of sync. Please quit and restart.',
+ );
- icon = <StyledCellIcon source="icon-alert" width={18} tintColor={colors.red} />;
- footer = (
- <Cell.Footer>
- <Cell.FooterText>{message}</Cell.FooterText>
- </Cell.Footer>
- );
- } else {
- footer = <StyledCellSpacer />;
- }
+ const updateAvailableMessage = messages.pgettext(
+ 'settings-view',
+ 'Update available. Install the latest app version to stay up to date.',
+ );
- return (
- <AriaDescriptionGroup>
- <AriaDescribed>
- <Cell.CellButton disabled={this.props.isOffline} onClick={this.openDownloadLink}>
- {icon}
- <Cell.Label>{messages.pgettext('settings-view', 'App version')}</Cell.Label>
- <Cell.SubText>{this.props.appVersion}</Cell.SubText>
- <AriaDescription>
- <Cell.Icon
- height={16}
- width={16}
- source="icon-extLink"
- aria-label={messages.pgettext('accessibility', 'Opens externally')}
- />
- </AriaDescription>
- </Cell.CellButton>
- </AriaDescribed>
- {footer}
- </AriaDescriptionGroup>
+ const message = !consistentVersion ? inconsistentVersionMessage : updateAvailableMessage;
+
+ icon = <StyledCellIcon source="icon-alert" width={18} tintColor={colors.red} />;
+ footer = (
+ <Cell.Footer>
+ <Cell.FooterText>{message}</Cell.FooterText>
+ </Cell.Footer>
);
}
- private renderBottomButtons() {
- return (
- <>
- <Cell.CellButton onClick={this.props.onViewSupport}>
- <Cell.Label>
- {
- // TRANSLATORS: Navigation button to the 'Report a problem' help view
- messages.pgettext('settings-view', 'Report a problem')
- }
- </Cell.Label>
- <Cell.Icon height={12} width={7} source="icon-chevron" />
+ return (
+ <AriaDescriptionGroup>
+ <AriaDescribed>
+ <Cell.CellButton disabled={isOffline} onClick={openDownloadLink}>
+ {icon}
+ <Cell.Label>{messages.pgettext('settings-view', 'App version')}</Cell.Label>
+ <Cell.SubText>{appVersion}</Cell.SubText>
+ <AriaDescription>
+ <Cell.Icon
+ height={16}
+ width={16}
+ source="icon-extLink"
+ aria-label={messages.pgettext('accessibility', 'Opens externally')}
+ />
+ </AriaDescription>
</Cell.CellButton>
+ </AriaDescribed>
+ {footer}
+ </AriaDescriptionGroup>
+ );
+}
- <AriaDescriptionGroup>
- <AriaDescribed>
- <Cell.CellButton disabled={this.props.isOffline} onClick={this.openFaqLink}>
- <Cell.Label>
- {
- // TRANSLATORS: Link to the webpage
- messages.pgettext('settings-view', 'FAQs & Guides')
- }
- </Cell.Label>
- <AriaDescription>
- <Cell.Icon
- height={16}
- width={16}
- source="icon-extLink"
- aria-label={messages.pgettext('accessibility', 'Opens externally')}
- />
- </AriaDescription>
- </Cell.CellButton>
- </AriaDescribed>
- </AriaDescriptionGroup>
+function SupportButton() {
+ const history = useHistory();
+ const navigate = useCallback(() => history.push(RoutePath.support), [history]);
- <Cell.CellButton onClick={this.props.onViewSelectLanguage}>
- <StyledCellIcon width={24} height={24} source="icon-language" />
- <Cell.Label>
- {
- // TRANSLATORS: Navigation button to the 'Language' settings view
- messages.pgettext('settings-view', 'Language')
- }
- </Cell.Label>
- <Cell.SubText>{this.props.preferredLocaleDisplayName}</Cell.SubText>
- <Cell.Icon height={12} width={7} source="icon-chevron" />
- </Cell.CellButton>
- </>
- );
- }
+ return (
+ <Cell.CellNavigationButton onClick={navigate}>
+ <Cell.Label>{messages.pgettext('settings-view', 'Support')}</Cell.Label>
+ </Cell.CellNavigationButton>
+ );
+}
+
+function QuitButton() {
+ const { quit } = useAppContext();
+
+ return (
+ <StyledQuitButton onClick={quit}>
+ {messages.pgettext('settings-view', 'Quit app')}
+ </StyledQuitButton>
+ );
}
diff --git a/gui/src/renderer/components/SettingsStyles.tsx b/gui/src/renderer/components/SettingsStyles.tsx
index 88a4c8258a..687b448424 100644
--- a/gui/src/renderer/components/SettingsStyles.tsx
+++ b/gui/src/renderer/components/SettingsStyles.tsx
@@ -34,12 +34,6 @@ export const StyledSettingsContent = styled.div({
flexDirection: 'column',
});
-export const StyledCellSpacer = styled.div({
- height: '20px',
- minHeight: '20px',
- flex: 0,
-});
-
export const StyledQuitButton = styled(AppButton.RedButton)({
margin: '20px 22px 22px',
});
diff --git a/gui/src/renderer/containers/SettingsPage.tsx b/gui/src/renderer/containers/SettingsPage.tsx
deleted file mode 100644
index e771541673..0000000000
--- a/gui/src/renderer/containers/SettingsPage.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import { connect } from 'react-redux';
-
-import Settings from '../components/Settings';
-import withAppContext, { IAppContext } from '../context';
-import { IHistoryProps, withHistory } from '../lib/history';
-import { RoutePath } from '../lib/routes';
-import { IReduxState, ReduxDispatch } from '../redux/store';
-
-const mapStateToProps = (state: IReduxState, props: IAppContext) => ({
- preferredLocaleDisplayName: props.app.getPreferredLocaleDisplayName(
- state.settings.guiSettings.preferredLocale,
- ),
- loginState: state.account.status,
- connectedToDaemon: state.userInterface.connectedToDaemon,
- accountExpiry: state.account.expiry,
- appVersion: state.version.current,
- consistentVersion: state.version.consistent,
- upToDateVersion: state.version.suggestedUpgrade ? false : true,
- suggestedIsBeta: state.version.suggestedIsBeta ?? false,
- isOffline: state.connection.isBlocked,
-});
-const mapDispatchToProps = (_dispatch: ReduxDispatch, props: IHistoryProps & IAppContext) => {
- return {
- onQuit: () => props.app.quit(),
- onClose: () => props.history.dismiss(),
- onViewSelectLanguage: () => props.history.push(RoutePath.selectLanguage),
- onViewAccount: () => props.history.push(RoutePath.accountSettings),
- onViewSupport: () => props.history.push(RoutePath.problemReport),
- onViewPreferences: () => props.history.push(RoutePath.preferences),
- onViewAdvancedSettings: () => props.history.push(RoutePath.advancedSettings),
- onExternalLink: (url: string) => props.app.openUrl(url),
- updateAccountData: () => props.app.updateAccountData(),
- };
-};
-
-export default withAppContext(withHistory(connect(mapStateToProps, mapDispatchToProps)(Settings)));
diff --git a/gui/src/renderer/lib/routes.ts b/gui/src/renderer/lib/routes.ts
index a997a97cd3..9c8eb4c493 100644
--- a/gui/src/renderer/lib/routes.ts
+++ b/gui/src/renderer/lib/routes.ts
@@ -19,7 +19,7 @@ export enum RoutePath {
vpnSettings = '/settings/vpn',
wireguardSettings = '/settings/advanced/wireguard',
openVpnSettings = '/settings/advanced/openvpn',
- splitTunneling = '/settings/advanced/split-tunneling',
+ splitTunneling = '/settings/split-tunneling',
support = '/settings/support',
problemReport = '/settings/support/problem-report',
selectLocation = '/select-location',