summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorOskar <oskar@mullvad.net>2024-10-02 10:16:14 +0200
committerOskar <oskar@mullvad.net>2024-10-02 10:16:14 +0200
commit833d39b41af37616f15aaf124bf0b76e94f20e63 (patch)
treea63a4704656328d05900a0b8d1f96e95290544af
parent1217112350515255c1933fe2e8192147d660b7f2 (diff)
parent62497011c88d52e80f9b36f84939600d56dfa6e6 (diff)
downloadmullvadvpn-833d39b41af37616f15aaf124bf0b76e94f20e63.tar.xz
mullvadvpn-833d39b41af37616f15aaf124bf0b76e94f20e63.zip
Merge branch 'add-react-hooks-rules-of-hooks-linter-rule-des-1286'
-rw-r--r--gui/eslint.config.mjs4
-rw-r--r--gui/package-lock.json20
-rw-r--r--gui/package.json1
-rw-r--r--gui/src/renderer/app.tsx4
-rw-r--r--gui/src/renderer/components/AppRouter.tsx2
-rw-r--r--gui/src/renderer/components/Login.tsx35
-rw-r--r--gui/src/renderer/containers/LoginPage.tsx42
-rw-r--r--gui/src/renderer/context.tsx19
8 files changed, 62 insertions, 65 deletions
diff --git a/gui/eslint.config.mjs b/gui/eslint.config.mjs
index 5c2b5d848c..75f28f6091 100644
--- a/gui/eslint.config.mjs
+++ b/gui/eslint.config.mjs
@@ -1,6 +1,7 @@
import eslint from '@eslint/js';
import prettier from 'eslint-plugin-prettier/recommended';
import react from 'eslint-plugin-react';
+import reactHooks from 'eslint-plugin-react-hooks';
import simpleImportSort from 'eslint-plugin-simple-import-sort';
import globals from 'globals';
import tseslint from 'typescript-eslint';
@@ -128,6 +129,7 @@ export default tseslint.config(
files: ['**/*.{js,mjs,ts,tsx}'],
plugins: {
'simple-import-sort': simpleImportSort,
+ 'react-hooks': reactHooks,
},
rules: {
quotes: ['error', 'single', { avoidEscape: true }],
@@ -144,6 +146,8 @@ export default tseslint.config(
'@typescript-eslint/ban-ts-comment': ['error', { 'ts-ignore': false }],
'simple-import-sort/imports': 'error',
+ 'react-hooks/rules-of-hooks': 'error',
+
'@typescript-eslint/no-use-before-define': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
diff --git a/gui/package-lock.json b/gui/package-lock.json
index 6ff861c565..3f6bddbe34 100644
--- a/gui/package-lock.json
+++ b/gui/package-lock.json
@@ -56,6 +56,7 @@
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-react": "^7.36.1",
+ "eslint-plugin-react-hooks": "^0.0.0-experimental-2d16326d-20240930",
"eslint-plugin-simple-import-sort": "^12.1.1",
"gettext-extractor": "^3.5.4",
"globals": "^15.9.0",
@@ -5216,6 +5217,18 @@
"eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7"
}
},
+ "node_modules/eslint-plugin-react-hooks": {
+ "version": "0.0.0-experimental-2d16326d-20240930",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-0.0.0-experimental-2d16326d-20240930.tgz",
+ "integrity": "sha512-6SrTdUGjhhQ51U6a695UlDFzR46pFlhz0yYyLrO5f4pXGHA0aE9X0TrqcB1gPyqB7LhSKjDSaoHC5/J48GG2UQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0"
+ }
+ },
"node_modules/eslint-plugin-react/node_modules/resolve": {
"version": "2.0.0-next.5",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz",
@@ -18032,6 +18045,13 @@
}
}
},
+ "eslint-plugin-react-hooks": {
+ "version": "0.0.0-experimental-2d16326d-20240930",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-0.0.0-experimental-2d16326d-20240930.tgz",
+ "integrity": "sha512-6SrTdUGjhhQ51U6a695UlDFzR46pFlhz0yYyLrO5f4pXGHA0aE9X0TrqcB1gPyqB7LhSKjDSaoHC5/J48GG2UQ==",
+ "dev": true,
+ "requires": {}
+ },
"eslint-plugin-simple-import-sort": {
"version": "12.1.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-simple-import-sort/-/eslint-plugin-simple-import-sort-12.1.1.tgz",
diff --git a/gui/package.json b/gui/package.json
index 457a5a97d1..6298c01286 100644
--- a/gui/package.json
+++ b/gui/package.json
@@ -62,6 +62,7 @@
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-react": "^7.36.1",
+ "eslint-plugin-react-hooks": "^0.0.0-experimental-2d16326d-20240930",
"eslint-plugin-simple-import-sort": "^12.1.1",
"gettext-extractor": "^3.5.4",
"globals": "^15.9.0",
diff --git a/gui/src/renderer/app.tsx b/gui/src/renderer/app.tsx
index 940dcc000f..09c84ea673 100644
--- a/gui/src/renderer/app.tsx
+++ b/gui/src/renderer/app.tsx
@@ -437,7 +437,7 @@ export default class AppRenderer {
await this.disconnectTunnel();
};
- public async createNewAccount() {
+ public createNewAccount = async () => {
log.info('Creating account');
const actions = this.reduxActions;
@@ -451,7 +451,7 @@ export default class AppRenderer {
const error = e as Error;
actions.account.createAccountFailed(error);
}
- }
+ };
public fetchDevices = async (accountNumber: AccountNumber): Promise<Array<IDevice>> => {
const devices = await IpcRendererEventChannel.account.listDevices(accountNumber);
diff --git a/gui/src/renderer/components/AppRouter.tsx b/gui/src/renderer/components/AppRouter.tsx
index e1a3d2ab0f..e83b6f55cd 100644
--- a/gui/src/renderer/components/AppRouter.tsx
+++ b/gui/src/renderer/components/AppRouter.tsx
@@ -1,8 +1,8 @@
import { createRef, useCallback, useEffect, useState } from 'react';
import { Route, Switch } from 'react-router';
+import LoginPage from '../components/Login';
import SelectLocation from '../components/select-location/SelectLocationContainer';
-import LoginPage from '../containers/LoginPage';
import { useAppContext } from '../context';
import { ITransitionSpecification, transitions, useHistory } from '../lib/history';
import { RoutePath } from '../lib/routes';
diff --git a/gui/src/renderer/components/Login.tsx b/gui/src/renderer/components/Login.tsx
index 8f17c35651..430772c2c7 100644
--- a/gui/src/renderer/components/Login.tsx
+++ b/gui/src/renderer/components/Login.tsx
@@ -6,7 +6,9 @@ import { AccountDataError, AccountNumber } from '../../shared/daemon-rpc-types';
import { messages } from '../../shared/gettext';
import { useAppContext } from '../context';
import { formatAccountNumber } from '../lib/account';
+import useActions from '../lib/actionsHook';
import { formatHtml } from '../lib/html-formatter';
+import accountActions from '../redux/account/actions';
import { LoginState } from '../redux/account/reducers';
import { useSelector } from '../redux/store';
import Accordion from './Accordion';
@@ -40,6 +42,37 @@ import {
StyledTopInfo,
} from './LoginStyles';
+export default function LoginContainer() {
+ const { openUrl, login, clearAccountHistory, createNewAccount } = useAppContext();
+ const { resetLoginError, updateAccountNumber } = useActions(accountActions);
+
+ const { accountNumber, accountHistory, status } = useSelector((state) => state.account);
+
+ const tunnelState = useSelector((state) => state.connection.status);
+ const blockWhenDisconnected = useSelector((state) => state.settings.blockWhenDisconnected);
+ const showBlockMessage = tunnelState.state === 'error' || blockWhenDisconnected;
+
+ const isPerformingPostUpgrade = useSelector(
+ (state) => state.userInterface.isPerformingPostUpgrade,
+ );
+
+ return (
+ <Login
+ accountNumber={accountNumber}
+ accountHistory={accountHistory}
+ loginState={status}
+ showBlockMessage={showBlockMessage}
+ openExternalLink={openUrl}
+ login={login}
+ resetLoginError={resetLoginError}
+ updateAccountNumber={updateAccountNumber}
+ clearAccountHistory={clearAccountHistory}
+ createNewAccount={createNewAccount}
+ isPerformingPostUpgrade={isPerformingPostUpgrade}
+ />
+ );
+}
+
interface IProps {
accountNumber?: AccountNumber;
accountHistory?: AccountNumber;
@@ -60,7 +93,7 @@ interface IState {
const MIN_ACCOUNT_NUMBER_LENGTH = 10;
-export default class Login extends React.Component<IProps, IState> {
+class Login extends React.Component<IProps, IState> {
public state: IState = {
isActive: true,
};
diff --git a/gui/src/renderer/containers/LoginPage.tsx b/gui/src/renderer/containers/LoginPage.tsx
deleted file mode 100644
index 49fdbba839..0000000000
--- a/gui/src/renderer/containers/LoginPage.tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-import { connect } from 'react-redux';
-import { bindActionCreators } from 'redux';
-
-import Login from '../components/Login';
-import withAppContext, { IAppContext } from '../context';
-import accountActions from '../redux/account/actions';
-import { IReduxState, ReduxDispatch } from '../redux/store';
-
-const mapStateToProps = (state: IReduxState) => {
- const tunnelState = state.connection.status;
- const blockWhenDisconnected = state.settings.blockWhenDisconnected;
- const { accountNumber, accountHistory, status } = state.account;
-
- const showBlockMessage = tunnelState.state === 'error' || blockWhenDisconnected;
-
- const isPerformingPostUpgrade = state.userInterface.isPerformingPostUpgrade;
-
- return {
- accountNumber,
- accountHistory,
- loginState: status,
- showBlockMessage,
- isPerformingPostUpgrade,
- };
-};
-const mapDispatchToProps = (dispatch: ReduxDispatch, props: IAppContext) => {
- const { resetLoginError, updateAccountNumber } = bindActionCreators(accountActions, dispatch);
- return {
- login: (account: string) => {
- void props.app.login(account);
- },
- resetLoginError: () => {
- resetLoginError();
- },
- openExternalLink: (url: string) => props.app.openUrl(url),
- updateAccountNumber,
- clearAccountHistory: () => props.app.clearAccountHistory(),
- createNewAccount: () => void props.app.createNewAccount(),
- };
-};
-
-export default withAppContext(connect(mapStateToProps, mapDispatchToProps)(Login));
diff --git a/gui/src/renderer/context.tsx b/gui/src/renderer/context.tsx
index 3b5fcc14fb..eb1be7a556 100644
--- a/gui/src/renderer/context.tsx
+++ b/gui/src/renderer/context.tsx
@@ -15,25 +15,6 @@ const missingContextError = new Error(
'The context value is empty. Make sure to wrap the component in AppContext.Provider.',
);
-type PropsWithoutAppContext<Props> = Omit<Props, 'app'>;
-
-export default function withAppContext<Props>(
- BaseComponent: React.ComponentType<PropsWithoutAppContext<Props> & IAppContext>,
-) {
- // Exclude the IAppContext from props since those are injected props
- const wrappedComponent = (props: PropsWithoutAppContext<Props>) => {
- const appContext = useAppContext();
- return <BaseComponent app={appContext} {...props} />;
- };
-
- if (window.env.development) {
- wrappedComponent.displayName =
- 'withAppContext(' + (BaseComponent.displayName || BaseComponent.name) + ')';
- }
-
- return wrappedComponent;
-}
-
export function useAppContext(): App {
const appContext = useContext(AppContext);
if (appContext) {