diff --git a/package-lock.json b/package-lock.json
index 39e9d49f..0904ca7a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,6 +9,7 @@
"version": "0.21.2",
"dependencies": {
"@hathor/wallet-lib": "1.10.0",
+ "@reduxjs/toolkit": "2.5.1",
"@unleash/proxy-client-react": "1.0.4",
"axios": "1.7.2",
"bootstrap": "5.3.3",
@@ -29,10 +30,9 @@
"react-gtm-module": "2.0.11",
"react-loading": "2.0.3",
"react-paginate": "8.2.0",
- "react-redux": "8.1.3",
+ "react-redux": "9.2.0",
"react-router-dom": "6.29.0",
"react-scripts": "3.4.4",
- "redux": "4.2.1",
"sass": "1.77.8",
"unleash-proxy-client": "1.11.0",
"viz.js": "2.1.2"
@@ -5011,6 +5011,40 @@
"integrity": "sha512-bRBcb2T+I88aG74LMVHaKms2p/T8aQd8+BZ7LuuzXlRfog1bMWWn/C5i0HVuvW4RPtXQYgIlGiXVDy9Ir1So/w==",
"peer": true
},
+ "node_modules/@reduxjs/toolkit": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.5.1.tgz",
+ "integrity": "sha512-UHhy3p0oUpdhnSxyDjaRDYaw8Xra75UiLbCiRozVPHjfDwNYkh0TsVm/1OmTW8Md+iDAJmYPWUKMvsMc2GtpNg==",
+ "license": "MIT",
+ "dependencies": {
+ "immer": "^10.0.3",
+ "redux": "^5.0.1",
+ "redux-thunk": "^3.1.0",
+ "reselect": "^5.1.0"
+ },
+ "peerDependencies": {
+ "react": "^16.9.0 || ^17.0.0 || ^18 || ^19",
+ "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0"
+ },
+ "peerDependenciesMeta": {
+ "react": {
+ "optional": true
+ },
+ "react-redux": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@reduxjs/toolkit/node_modules/immer": {
+ "version": "10.1.1",
+ "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz",
+ "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/immer"
+ }
+ },
"node_modules/@remix-run/router": {
"version": "1.22.0",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.22.0.tgz",
@@ -5273,15 +5307,6 @@
"@types/node": "*"
}
},
- "node_modules/@types/hoist-non-react-statics": {
- "version": "3.3.5",
- "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz",
- "integrity": "sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==",
- "dependencies": {
- "@types/react": "*",
- "hoist-non-react-statics": "^3.3.0"
- }
- },
"node_modules/@types/istanbul-lib-coverage": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
@@ -5334,34 +5359,20 @@
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz",
"integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw=="
},
- "node_modules/@types/prop-types": {
- "version": "15.7.12",
- "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz",
- "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q=="
- },
"node_modules/@types/q": {
"version": "1.5.8",
"resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.8.tgz",
"integrity": "sha512-hroOstUScF6zhIi+5+x0dzqrHA1EJi+Irri6b1fxolMTqqHIV/Cg77EtnQcZqZCu8hR3mX2BzIxN4/GzI68Kfw=="
},
- "node_modules/@types/react": {
- "version": "18.3.3",
- "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz",
- "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==",
- "dependencies": {
- "@types/prop-types": "*",
- "csstype": "^3.0.2"
- }
- },
"node_modules/@types/stack-utils": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz",
"integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw=="
},
"node_modules/@types/use-sync-external-store": {
- "version": "0.0.3",
- "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz",
- "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA=="
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
+ "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg=="
},
"node_modules/@types/yargs": {
"version": "13.0.12",
@@ -9155,11 +9166,6 @@
"cssom": "0.3.x"
}
},
- "node_modules/csstype": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
- "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
- },
"node_modules/cyclist": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.2.tgz",
@@ -12255,14 +12261,6 @@
"minimalistic-crypto-utils": "^1.0.1"
}
},
- "node_modules/hoist-non-react-statics": {
- "version": "3.3.2",
- "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
- "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
- "dependencies": {
- "react-is": "^16.7.0"
- }
- },
"node_modules/hosted-git-info": {
"version": "2.8.9",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
@@ -21174,48 +21172,27 @@
}
},
"node_modules/react-redux": {
- "version": "8.1.3",
- "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.1.3.tgz",
- "integrity": "sha512-n0ZrutD7DaX/j9VscF+uTALI3oUPa/pO4Z3soOBIjuRn/FzVu6aehhysxZCLi6y7duMf52WNZGMl7CtuK5EnRw==",
+ "version": "9.2.0",
+ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
+ "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
"dependencies": {
- "@babel/runtime": "^7.12.1",
- "@types/hoist-non-react-statics": "^3.3.1",
- "@types/use-sync-external-store": "^0.0.3",
- "hoist-non-react-statics": "^3.3.2",
- "react-is": "^18.0.0",
- "use-sync-external-store": "^1.0.0"
+ "@types/use-sync-external-store": "^0.0.6",
+ "use-sync-external-store": "^1.4.0"
},
"peerDependencies": {
- "@types/react": "^16.8 || ^17.0 || ^18.0",
- "@types/react-dom": "^16.8 || ^17.0 || ^18.0",
- "react": "^16.8 || ^17.0 || ^18.0",
- "react-dom": "^16.8 || ^17.0 || ^18.0",
- "react-native": ">=0.59",
- "redux": "^4 || ^5.0.0-beta.0"
+ "@types/react": "^18.2.25 || ^19",
+ "react": "^18.0 || ^19",
+ "redux": "^5.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
- "@types/react-dom": {
- "optional": true
- },
- "react-dom": {
- "optional": true
- },
- "react-native": {
- "optional": true
- },
"redux": {
"optional": true
}
}
},
- "node_modules/react-redux/node_modules/react-is": {
- "version": "18.3.1",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
- "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="
- },
"node_modules/react-refresh": {
"version": "0.14.0",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz",
@@ -21760,11 +21737,18 @@
}
},
"node_modules/redux": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
- "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
- "dependencies": {
- "@babel/runtime": "^7.9.2"
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
+ "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
+ "license": "MIT"
+ },
+ "node_modules/redux-thunk": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz",
+ "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "redux": "^5.0.0"
}
},
"node_modules/reflect.getprototypeof": {
@@ -22048,6 +22032,12 @@
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="
},
+ "node_modules/reselect": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
+ "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==",
+ "license": "MIT"
+ },
"node_modules/resolve": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.0.tgz",
@@ -26337,11 +26327,11 @@
}
},
"node_modules/use-sync-external-store": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz",
- "integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==",
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz",
+ "integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==",
"peerDependencies": {
- "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/util": {
diff --git a/package.json b/package.json
index 050049d2..e4db50c5 100644
--- a/package.json
+++ b/package.json
@@ -8,6 +8,7 @@
"private": true,
"dependencies": {
"@hathor/wallet-lib": "1.10.0",
+ "@reduxjs/toolkit": "2.5.1",
"@unleash/proxy-client-react": "1.0.4",
"axios": "1.7.2",
"bootstrap": "5.3.3",
@@ -28,10 +29,9 @@
"react-gtm-module": "2.0.11",
"react-loading": "2.0.3",
"react-paginate": "8.2.0",
- "react-redux": "8.1.3",
+ "react-redux": "9.2.0",
"react-router-dom": "6.29.0",
"react-scripts": "3.4.4",
- "redux": "4.2.1",
"sass": "1.77.8",
"unleash-proxy-client": "1.11.0",
"viz.js": "2.1.2"
diff --git a/src/App.js b/src/App.js
index d6c13101..2ba4eda5 100644
--- a/src/App.js
+++ b/src/App.js
@@ -39,7 +39,7 @@ import {
dashboardUpdate,
isVersionAllowedUpdate,
updateServerInfo,
-} from './actions/index';
+} from './store/rootSlice';
import versionApi from './api/version';
import helpers from './utils/helpers';
import { BASE_URL } from './constants';
@@ -142,10 +142,6 @@ function Root() {
path="/token_balances"
element={}
/>
- }
- />
} />
} />
} />
diff --git a/src/actions/index.js b/src/actions/index.js
deleted file mode 100644
index 4218f022..00000000
--- a/src/actions/index.js
+++ /dev/null
@@ -1,29 +0,0 @@
-/**
- * Copyright (c) Hathor Labs and its affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- */
-
-import store from '../store/index';
-import themeUtils from '../utils/theme';
-
-export const dashboardUpdate = data => ({ type: 'dashboard_update', payload: data });
-
-export const isVersionAllowedUpdate = data => ({
- type: 'is_version_allowed_update',
- payload: data,
-});
-
-export const apiLoadErrorUpdate = data => ({ type: 'api_load_error_update', payload: data });
-
-export const updateServerInfo = data => ({ type: 'update_server_info', payload: data });
-
-export const toggleTheme = () => {
- const state = store.getState();
- const currentTheme = state.theme === 'light' ? 'dark' : 'light';
-
- themeUtils.applyTheme(currentTheme);
-
- return { type: 'toggle_theme', payload: currentTheme };
-};
diff --git a/src/components/Navigation.js b/src/components/Navigation.js
index f71da807..bab6de48 100644
--- a/src/components/Navigation.js
+++ b/src/components/Navigation.js
@@ -28,7 +28,7 @@ import {
UNLEASH_TOKEN_BALANCES_FEATURE_FLAG,
REACT_APP_NETWORK,
} from '../constants';
-import { toggleTheme } from '../actions';
+import { toggleTheme } from '../store/rootSlice';
import NewHathorAlert from './NewHathorAlert';
function Navigation() {
diff --git a/src/components/ThemeSwitch.js b/src/components/ThemeSwitch.js
index 0ef0aa2d..7d9b04ee 100644
--- a/src/components/ThemeSwitch.js
+++ b/src/components/ThemeSwitch.js
@@ -7,7 +7,7 @@
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
-import { toggleTheme } from '../actions';
+import { toggleTheme } from '../store/rootSlice';
export const ThemeSwitch = () => {
const theme = useSelector(state => state.theme);
diff --git a/src/screens/VersionError.js b/src/screens/VersionError.js
index fd990944..de5d565d 100644
--- a/src/screens/VersionError.js
+++ b/src/screens/VersionError.js
@@ -10,7 +10,7 @@ import { useDispatch } from 'react-redux';
import { MIN_API_VERSION } from '../constants';
import versionApi from '../api/version';
import helpers from '../utils/helpers';
-import { isVersionAllowedUpdate } from '../actions/index';
+import { isVersionAllowedUpdate } from '../store/rootSlice';
import logo from '../assets/images/hathor-white-logo.png';
import Version from '../components/Version';
diff --git a/src/store/index.js b/src/store/index.js
index 51e5f452..c413c5f9 100644
--- a/src/store/index.js
+++ b/src/store/index.js
@@ -5,9 +5,9 @@
* LICENSE file in the root directory of this source tree.
*/
-import { createStore } from 'redux';
-import rootReducer from '../reducers/index';
+import { configureStore } from '@reduxjs/toolkit';
+import rootReducer from './rootSlice';
-const store = createStore(rootReducer);
+const store = configureStore({ reducer: rootReducer });
export default store;
diff --git a/src/reducers/index.js b/src/store/rootSlice.js
similarity index 59%
rename from src/reducers/index.js
rename to src/store/rootSlice.js
index ed252c4b..13eecff6 100644
--- a/src/reducers/index.js
+++ b/src/store/rootSlice.js
@@ -1,3 +1,7 @@
+/* eslint-disable no-param-reassign */
+// Disabling no-param-reassign because ImmerJS uses it as a default pattern
+// See https://github.com/immerjs/immer/issues/189
+
/**
* Copyright (c) Hathor Labs and its affiliates.
*
@@ -5,8 +9,8 @@
* LICENSE file in the root directory of this source tree.
*/
+import { createSlice } from '@reduxjs/toolkit';
import { constants } from '@hathor/wallet-lib';
-import { cloneDeep } from 'lodash';
import themeUtils from '../utils/theme';
/**
@@ -49,6 +53,7 @@ import themeUtils from '../utils/theme';
* @property {boolean} isVersionAllowed - if the backend API version is allowed for this admin.
* @property {ServerInfo} serverInfo - server info from version api.
* @property {boolean} apiLoadError - If we had an error while loading the initial data from the server.
+ * @property {'dark'|'light'} theme - current theme of the app.
*/
/**
@@ -66,41 +71,39 @@ const initialState = {
theme: themeUtils.initializeTheme(),
};
-const rootReducer = (state = initialState, action) => {
- switch (action.type) {
- case 'dashboard_update':
- return { ...state, data: action.payload };
- case 'is_version_allowed_update':
- return { ...state, isVersionAllowed: action.payload.allowed };
- case 'api_load_error_update':
- return { ...state, apiLoadError: action.payload.apiLoadError };
- case 'update_server_info':
- return setServerInfo(state, action);
- case 'toggle_theme':
- return { ...state, theme: action.payload };
- default:
- return state;
- }
-};
-
-/**
- * Set the server info a.k.a '/version' data on storage.
- * Will update keys based on default values.
- *
- * @param {ReduxStore} state - Current store state.
- * @param {payload} ServerInfo - Server info to save on storage.
- * @returns {ReduxStore} New state for the store.
- */
-const setServerInfo = (state, { payload }) => {
- const serverInfo = cloneDeep(payload);
- // Default values
- serverInfo.decimal_places = serverInfo.decimal_places ?? constants.DECIMAL_PLACES;
- serverInfo.native_token = serverInfo.native_token ?? constants.DEFAULT_NATIVE_TOKEN_CONFIG;
+const rootSlice = createSlice({
+ name: 'root',
+ initialState,
+ reducers: {
+ dashboardUpdate(state, action) {
+ state.data = action.payload;
+ },
+ isVersionAllowedUpdate(state, action) {
+ state.isVersionAllowed = action.payload.allowed;
+ },
+ apiLoadErrorUpdate(state, action) {
+ state.apiLoadError = action.payload.apiLoadError;
+ },
+ updateServerInfo(state, action) {
+ const serverInfo = { ...action.payload };
+ serverInfo.decimal_places = serverInfo.decimal_places ?? constants.DECIMAL_PLACES;
+ serverInfo.native_token = serverInfo.native_token ?? constants.DEFAULT_NATIVE_TOKEN_CONFIG;
+ state.serverInfo = serverInfo;
+ },
+ toggleTheme(state) {
+ const currentTheme = state.theme === 'light' ? 'dark' : 'light';
+ themeUtils.applyTheme(currentTheme);
+ state.theme = currentTheme;
+ },
+ },
+});
- return {
- ...state,
- serverInfo,
- };
-};
+export const {
+ dashboardUpdate,
+ isVersionAllowedUpdate,
+ apiLoadErrorUpdate,
+ updateServerInfo,
+ toggleTheme,
+} = rootSlice.actions;
-export default rootReducer;
+export default rootSlice.reducer;