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;