diff --git a/packages/desktop-client/package.json b/packages/desktop-client/package.json index 358eee53548..19b8078aceb 100644 --- a/packages/desktop-client/package.json +++ b/packages/desktop-client/package.json @@ -24,7 +24,6 @@ "@types/react-dom": "^18.2.1", "@types/react-grid-layout": "^1", "@types/react-modal": "^3.16.0", - "@types/react-redux": "^7.1.25", "@types/uuid": "^9.0.2", "@types/webpack-bundle-analyzer": "^4.6.3", "@use-gesture/react": "^10.3.0", @@ -61,15 +60,13 @@ "react-i18next": "^14.1.2", "react-markdown": "^8.0.7", "react-modal": "3.16.1", - "react-redux": "7.2.9", + "react-redux": "^9.2.0", "react-router-dom": "6.21.3", "react-simple-pull-to-refresh": "^1.3.3", "react-spring": "^9.7.3", "react-stately": "^3.33.0", "react-virtualized-auto-sizer": "^1.0.21", "recharts": "^2.10.4", - "redux": "^4.2.1", - "redux-thunk": "^2.4.2", "remark-gfm": "^3.0.1", "rollup-plugin-visualizer": "^5.12.0", "sass": "^1.70.0", diff --git a/packages/desktop-client/src/auth/AuthProvider.tsx b/packages/desktop-client/src/auth/AuthProvider.tsx index e0d5903783d..987184f6b20 100644 --- a/packages/desktop-client/src/auth/AuthProvider.tsx +++ b/packages/desktop-client/src/auth/AuthProvider.tsx @@ -1,9 +1,7 @@ import React, { createContext, useContext, type ReactNode } from 'react'; -import { useSelector } from 'react-redux'; - -import { type State } from 'loot-core/client/state-types'; import { useServerURL } from '../components/ServerContext'; +import { useSelector } from '../redux'; import { type Permissions } from './types'; @@ -18,7 +16,7 @@ type AuthProviderProps = { }; export const AuthProvider = ({ children }: AuthProviderProps) => { - const userData = useSelector((state: State) => state.user.data); + const userData = useSelector(state => state.user.data); const serverUrl = useServerURL(); const hasPermission = (permission?: Permissions) => { diff --git a/packages/desktop-client/src/auth/ProtectedRoute.tsx b/packages/desktop-client/src/auth/ProtectedRoute.tsx index 5dacd055782..0dad71a1c38 100644 --- a/packages/desktop-client/src/auth/ProtectedRoute.tsx +++ b/packages/desktop-client/src/auth/ProtectedRoute.tsx @@ -1,10 +1,10 @@ import { useEffect, useState, type ReactElement } from 'react'; -import { useSelector } from 'react-redux'; import { type RemoteFile, type SyncedLocalFile } from 'loot-core/types/file'; import { View } from '../components/common/View'; import { useMetadataPref } from '../hooks/useMetadataPref'; +import { useSelector } from '../redux'; import { useAuth } from './AuthProvider'; import { type Permissions } from './types'; diff --git a/packages/desktop-client/src/components/App.tsx b/packages/desktop-client/src/components/App.tsx index acc0f361c87..f914d04f7c1 100644 --- a/packages/desktop-client/src/components/App.tsx +++ b/packages/desktop-client/src/components/App.tsx @@ -9,7 +9,6 @@ import { } from 'react-error-boundary'; import { HotkeysProvider } from 'react-hotkeys-hook'; import { useTranslation } from 'react-i18next'; -import { useDispatch, useSelector } from 'react-redux'; import { BrowserRouter } from 'react-router-dom'; import { @@ -20,7 +19,6 @@ import { sync, } from 'loot-core/client/actions'; import { SpreadsheetProvider } from 'loot-core/client/SpreadsheetProvider'; -import { type State } from 'loot-core/client/state-types'; import * as Platform from 'loot-core/src/client/platform'; import { init as initConnection, @@ -30,6 +28,7 @@ import { import { useActions } from '../hooks/useActions'; import { useMetadataPref } from '../hooks/useMetadataPref'; import { installPolyfills } from '../polyfills'; +import { useDispatch, useSelector } from '../redux'; import { styles, hasHiddenScrollbars, ThemeStyle, useTheme } from '../style'; import { ExposeNavigate } from '../util/router-tools'; @@ -51,7 +50,7 @@ function AppInner() { const { t } = useTranslation(); const { showBoundary: showErrorBoundary } = useErrorBoundary(); const dispatch = useDispatch(); - const userData = useSelector((state: State) => state.user.data); + const userData = useSelector(state => state.user.data); const { signOut, addNotification } = useActions(); const maybeUpdate = async (cb?: () => T): Promise => { diff --git a/packages/desktop-client/src/components/AppBackground.tsx b/packages/desktop-client/src/components/AppBackground.tsx index 9d57bafdde8..423412e31e4 100644 --- a/packages/desktop-client/src/components/AppBackground.tsx +++ b/packages/desktop-client/src/components/AppBackground.tsx @@ -1,10 +1,10 @@ import React from 'react'; -import { useSelector } from 'react-redux'; import { useTransition, animated } from 'react-spring'; import { css } from '@emotion/css'; import { AnimatedLoading } from '../icons/AnimatedLoading'; +import { useSelector } from '../redux'; import { theme } from '../style'; import { Background } from './Background'; diff --git a/packages/desktop-client/src/components/BankSyncStatus.tsx b/packages/desktop-client/src/components/BankSyncStatus.tsx index 52130e0c198..843b62d3e3f 100644 --- a/packages/desktop-client/src/components/BankSyncStatus.tsx +++ b/packages/desktop-client/src/components/BankSyncStatus.tsx @@ -1,10 +1,8 @@ import React from 'react'; import { Trans } from 'react-i18next'; -import { useSelector } from 'react-redux'; import { useTransition, animated } from 'react-spring'; -import { type State } from 'loot-core/src/client/state-types'; - +import { useSelector } from '../redux'; import { theme, styles } from '../style'; import { AnimatedRefresh } from './AnimatedRefresh'; @@ -12,9 +10,7 @@ import { Text } from './common/Text'; import { View } from './common/View'; export function BankSyncStatus() { - const accountsSyncing = useSelector( - (state: State) => state.account.accountsSyncing, - ); + const accountsSyncing = useSelector(state => state.account.accountsSyncing); const accountsSyncingCount = accountsSyncing.length; const count = accountsSyncingCount; diff --git a/packages/desktop-client/src/components/FinancesApp.tsx b/packages/desktop-client/src/components/FinancesApp.tsx index 8b987636ee3..a1074fbf3e2 100644 --- a/packages/desktop-client/src/components/FinancesApp.tsx +++ b/packages/desktop-client/src/components/FinancesApp.tsx @@ -1,7 +1,6 @@ // @ts-strict-ignore import React, { type ReactElement, useEffect, useRef } from 'react'; import { useTranslation } from 'react-i18next'; -import { useDispatch, useSelector } from 'react-redux'; import { Route, Routes, @@ -11,7 +10,6 @@ import { } from 'react-router-dom'; import { addNotification, sync } from 'loot-core/client/actions'; -import { type State } from 'loot-core/src/client/state-types'; import * as undo from 'loot-core/src/platform/client/undo'; import { ProtectedRoute } from '../auth/ProtectedRoute'; @@ -20,6 +18,7 @@ import { useAccounts } from '../hooks/useAccounts'; import { useLocalPref } from '../hooks/useLocalPref'; import { useMetaThemeColor } from '../hooks/useMetaThemeColor'; import { useNavigate } from '../hooks/useNavigate'; +import { useSelector, useDispatch } from '../redux'; import { theme } from '../style'; import { getIsOutdated, getLatestVersion } from '../util/versions'; @@ -90,9 +89,7 @@ export function FinancesApp() { const { t } = useTranslation(); const accounts = useAccounts(); - const accountsLoaded = useSelector( - (state: State) => state.queries.accountsLoaded, - ); + const accountsLoaded = useSelector(state => state.queries.accountsLoaded); const [lastUsedVersion, setLastUsedVersion] = useLocalPref( 'flags.updateNotificationShownForVersion', diff --git a/packages/desktop-client/src/components/HelpMenu.tsx b/packages/desktop-client/src/components/HelpMenu.tsx index e0e790dc74b..11c292add30 100644 --- a/packages/desktop-client/src/components/HelpMenu.tsx +++ b/packages/desktop-client/src/components/HelpMenu.tsx @@ -1,7 +1,6 @@ import { forwardRef, useRef } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; import { Trans, useTranslation } from 'react-i18next'; -import { useDispatch } from 'react-redux'; import { useLocation } from 'react-router-dom'; import { useToggle } from 'usehooks-ts'; @@ -11,6 +10,7 @@ import { pushModal } from 'loot-core/client/actions/modals'; import { useFeatureFlag } from '../hooks/useFeatureFlag'; import { SvgHelp } from '../icons/v2/Help'; +import { useDispatch } from '../redux'; import { Button } from './common/Button2'; import { Menu } from './common/Menu'; diff --git a/packages/desktop-client/src/components/LoggedInUser.tsx b/packages/desktop-client/src/components/LoggedInUser.tsx index 0c0662d3855..0e715c5a107 100644 --- a/packages/desktop-client/src/components/LoggedInUser.tsx +++ b/packages/desktop-client/src/components/LoggedInUser.tsx @@ -1,10 +1,8 @@ import React, { useState, useEffect, useRef, type CSSProperties } from 'react'; import { Trans, useTranslation } from 'react-i18next'; -import { useDispatch, useSelector } from 'react-redux'; import { useLocation } from 'react-router-dom'; import { closeBudget, getUserData, signOut } from 'loot-core/client/actions'; -import { type State } from 'loot-core/src/client/state-types'; import { type RemoteFile, type SyncedLocalFile } from 'loot-core/types/file'; import { type TransObjectLiteral } from 'loot-core/types/util'; @@ -12,6 +10,7 @@ import { useAuth } from '../auth/AuthProvider'; import { Permissions } from '../auth/types'; import { useMetadataPref } from '../hooks/useMetadataPref'; import { useNavigate } from '../hooks/useNavigate'; +import { useSelector, useDispatch } from '../redux'; import { theme, styles } from '../style'; import { Button } from './common/Button2'; @@ -36,7 +35,7 @@ export function LoggedInUser({ const { t } = useTranslation(); const dispatch = useDispatch(); const navigate = useNavigate(); - const userData = useSelector((state: State) => state.user.data); + const userData = useSelector(state => state.user.data); const [loading, setLoading] = useState(true); const [menuOpen, setMenuOpen] = useState(false); const serverUrl = useServerURL(); @@ -51,7 +50,7 @@ export function LoggedInUser({ f => f.state === 'remote' || f.state === 'synced' || f.state === 'detached', ) as (SyncedLocalFile | RemoteFile)[]; const currentFile = remoteFiles.find(f => f.cloudFileId === cloudFileId); - const hasSyncedPrefs = useSelector((state: State) => state.prefs.synced); + const hasSyncedPrefs = useSelector(state => state.prefs.synced); useEffect(() => { async function init() { diff --git a/packages/desktop-client/src/components/ManageRules.tsx b/packages/desktop-client/src/components/ManageRules.tsx index 36643423ba5..7f3b27e9b84 100644 --- a/packages/desktop-client/src/components/ManageRules.tsx +++ b/packages/desktop-client/src/components/ManageRules.tsx @@ -8,7 +8,6 @@ import React, { type Dispatch, } from 'react'; import { useTranslation } from 'react-i18next'; -import { useDispatch } from 'react-redux'; import { useSchedules } from 'loot-core/client/data-hooks/schedules'; import { q } from 'loot-core/shared/query'; @@ -25,6 +24,7 @@ import { useAccounts } from '../hooks/useAccounts'; import { useCategories } from '../hooks/useCategories'; import { usePayees } from '../hooks/usePayees'; import { useSelected, SelectedProvider } from '../hooks/useSelected'; +import { useDispatch } from '../redux'; import { theme } from '../style'; import { Button } from './common/Button2'; diff --git a/packages/desktop-client/src/components/Modals.tsx b/packages/desktop-client/src/components/Modals.tsx index 013d4672fe0..4b4919cfbf5 100644 --- a/packages/desktop-client/src/components/Modals.tsx +++ b/packages/desktop-client/src/components/Modals.tsx @@ -1,7 +1,6 @@ // @ts-strict-ignore import React, { useEffect } from 'react'; import { useTranslation } from 'react-i18next'; -import { useDispatch } from 'react-redux'; import { useLocation } from 'react-router-dom'; import { closeModal } from 'loot-core/client/actions'; @@ -10,6 +9,7 @@ import * as monthUtils from 'loot-core/src/shared/months'; import { useMetadataPref } from '../hooks/useMetadataPref'; import { useModalState } from '../hooks/useModalState'; +import { useDispatch } from '../redux'; import { ModalTitle, ModalHeader } from './common/Modal'; import { AccountAutocompleteModal } from './modals/AccountAutocompleteModal'; diff --git a/packages/desktop-client/src/components/Notifications.tsx b/packages/desktop-client/src/components/Notifications.tsx index 59bd7adcb7f..dde728d7d9d 100644 --- a/packages/desktop-client/src/components/Notifications.tsx +++ b/packages/desktop-client/src/components/Notifications.tsx @@ -7,16 +7,15 @@ import React, { type CSSProperties, } from 'react'; import { useTranslation } from 'react-i18next'; -import { useDispatch, useSelector } from 'react-redux'; import { css } from '@emotion/css'; import { removeNotification } from 'loot-core/client/actions'; -import { type State } from 'loot-core/src/client/state-types'; import type { NotificationWithId } from 'loot-core/src/client/state-types/notifications'; import { AnimatedLoading } from '../icons/AnimatedLoading'; import { SvgDelete } from '../icons/v0'; +import { useSelector, useDispatch } from '../redux'; import { styles, theme } from '../style'; import { Button, ButtonWithLoading } from './common/Button2'; @@ -265,12 +264,8 @@ function Notification({ export function Notifications({ style }: { style?: CSSProperties }) { const dispatch = useDispatch(); const { isNarrowWidth } = useResponsive(); - const notifications = useSelector( - (state: State) => state.notifications.notifications, - ); - const notificationInset = useSelector( - (state: State) => state.notifications.inset, - ); + const notifications = useSelector(state => state.notifications.notifications); + const notificationInset = useSelector(state => state.notifications.inset); return ( state.app.updateInfo); + const updateInfo = useSelector(state => state.app.updateInfo); const showUpdateNotification = useSelector( - (state: State) => state.app.showUpdateNotification, + state => state.app.showUpdateNotification, ); const dispatch = useDispatch(); diff --git a/packages/desktop-client/src/components/accounts/Account.tsx b/packages/desktop-client/src/components/accounts/Account.tsx index 4be9f28866b..4ef75e82894 100644 --- a/packages/desktop-client/src/components/accounts/Account.tsx +++ b/packages/desktop-client/src/components/accounts/Account.tsx @@ -8,7 +8,6 @@ import React, { useEffect, } from 'react'; import { Trans } from 'react-i18next'; -import { useSelector } from 'react-redux'; import { Navigate, useParams, useLocation } from 'react-router-dom'; import { debounce } from 'debounce'; @@ -29,6 +28,7 @@ import { type PagedQuery, } from 'loot-core/src/client/query-helpers'; import { send, listen } from 'loot-core/src/platform/client/fetch'; +import * as undo from 'loot-core/src/platform/client/undo'; import { currentDay } from 'loot-core/src/shared/months'; import { q, type Query } from 'loot-core/src/shared/query'; import { @@ -68,6 +68,7 @@ import { } from '../../hooks/useSplitsExpanded'; import { useSyncedPref } from '../../hooks/useSyncedPref'; import { useTransactionBatchActions } from '../../hooks/useTransactionBatchActions'; +import { useSelector } from '../../redux'; import { styles, theme } from '../../style'; import { Button } from '../common/Button2'; import { Text } from '../common/Text'; @@ -261,8 +262,6 @@ type AccountInternalProps = { showExtraBalances?: boolean; setShowExtraBalances: (newValue: boolean) => void; modalShowing?: boolean; - setLastUndoState: (state: null) => void; - lastUndoState: { current: UndoState | null }; accounts: AccountEntity[]; getPayees: () => Promise; updateAccount: (newAccount: AccountEntity) => void; @@ -412,7 +411,7 @@ class AccountInternal extends PureComponent< } } - this.props.setLastUndoState(null); + undo.setUndoState('undoEvent', null); }; const unlistens = [listen('undo-event', onUndo)]; @@ -428,8 +427,9 @@ class AccountInternal extends PureComponent< // If there is a pending undo, apply it immediately (this happens // when an undo changes the location to this page) - if (this.props.lastUndoState && this.props.lastUndoState.current) { - onUndo(this.props.lastUndoState.current); + const lastUndoEvent = undo.getUndoState('undoEvent'); + if (lastUndoEvent) { + onUndo(lastUndoEvent); } } @@ -1884,7 +1884,6 @@ export function Account() { ); const modalShowing = useSelector(state => state.modals.modalStack.length > 0); const accountsSyncing = useSelector(state => state.account.accountsSyncing); - const lastUndoState = useSelector(state => state.app.lastUndoState); const filterConditions = location?.state?.filterConditions || []; const savedFiters = useFilters(); @@ -1923,7 +1922,6 @@ export function Account() { payees={payees} modalShowing={modalShowing} accountsSyncing={accountsSyncing} - lastUndoState={lastUndoState} filterConditions={filterConditions} categoryGroups={categoryGroups} {...actionCreators} diff --git a/packages/desktop-client/src/components/accounts/AccountSyncCheck.tsx b/packages/desktop-client/src/components/accounts/AccountSyncCheck.tsx index 489b6e92e19..f7ec29db71c 100644 --- a/packages/desktop-client/src/components/accounts/AccountSyncCheck.tsx +++ b/packages/desktop-client/src/components/accounts/AccountSyncCheck.tsx @@ -1,6 +1,5 @@ import React, { useCallback, useRef, useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; -import { useDispatch, useSelector } from 'react-redux'; import { useParams } from 'react-router-dom'; import { unlinkAccount } from 'loot-core/client/actions'; @@ -8,7 +7,9 @@ import { type AccountEntity } from 'loot-core/types/models'; import { authorizeBank } from '../../gocardless'; import { useAccounts } from '../../hooks/useAccounts'; +import { useFailedAccounts } from '../../hooks/useFailedAccounts'; import { SvgExclamationOutline } from '../../icons/v1'; +import { useDispatch } from '../../redux'; import { theme } from '../../style'; import { Button } from '../common/Button2'; import { Link } from '../common/Link'; @@ -82,7 +83,7 @@ function useErrorMessage() { export function AccountSyncCheck() { const accounts = useAccounts(); - const failedAccounts = useSelector(state => state.account.failedAccounts); + const failedAccounts = useFailedAccounts(); const dispatch = useDispatch(); const { id } = useParams(); const [open, setOpen] = useState(false); diff --git a/packages/desktop-client/src/components/autocomplete/PayeeAutocomplete.test.tsx b/packages/desktop-client/src/components/autocomplete/PayeeAutocomplete.test.tsx index cd9f20a122c..b5b3aa925e7 100644 --- a/packages/desktop-client/src/components/autocomplete/PayeeAutocomplete.test.tsx +++ b/packages/desktop-client/src/components/autocomplete/PayeeAutocomplete.test.tsx @@ -3,11 +3,11 @@ import userEvent from '@testing-library/user-event'; import { vi } from 'vitest'; import { generateAccount } from 'loot-core/src/mocks'; -import { TestProvider } from 'loot-core/src/mocks/redux'; import type { AccountEntity, PayeeEntity } from 'loot-core/types/models'; import { AuthProvider } from '../../auth/AuthProvider'; import { useCommonPayees } from '../../hooks/usePayees'; +import { TestProvider } from '../../redux/mock'; import { ResponsiveProvider } from '../responsive/ResponsiveProvider'; import { diff --git a/packages/desktop-client/src/components/autocomplete/PayeeAutocomplete.tsx b/packages/desktop-client/src/components/autocomplete/PayeeAutocomplete.tsx index b1f02818a4e..997219bb474 100644 --- a/packages/desktop-client/src/components/autocomplete/PayeeAutocomplete.tsx +++ b/packages/desktop-client/src/components/autocomplete/PayeeAutocomplete.tsx @@ -12,7 +12,6 @@ import React, { type CSSProperties, } from 'react'; import { Trans, useTranslation } from 'react-i18next'; -import { useDispatch } from 'react-redux'; import { css, cx } from '@emotion/css'; @@ -27,6 +26,7 @@ import { import { useAccounts } from '../../hooks/useAccounts'; import { useCommonPayees, usePayees } from '../../hooks/usePayees'; import { SvgAdd, SvgBookmark } from '../../icons/v1'; +import { useDispatch } from '../../redux'; import { theme, styles } from '../../style'; import { Button } from '../common/Button'; import { TextOneLine } from '../common/TextOneLine'; diff --git a/packages/desktop-client/src/components/budget/index.tsx b/packages/desktop-client/src/components/budget/index.tsx index 0012f46f0a7..f7b61070445 100644 --- a/packages/desktop-client/src/components/budget/index.tsx +++ b/packages/desktop-client/src/components/budget/index.tsx @@ -1,7 +1,6 @@ // @ts-strict-ignore import React, { memo, useMemo, useState, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; -import { useDispatch } from 'react-redux'; import { addNotification, @@ -26,6 +25,7 @@ import { useGlobalPref } from '../../hooks/useGlobalPref'; import { useLocalPref } from '../../hooks/useLocalPref'; import { useNavigate } from '../../hooks/useNavigate'; import { useSyncedPref } from '../../hooks/useSyncedPref'; +import { useDispatch } from '../../redux'; import { styles } from '../../style'; import { View } from '../common/View'; import { NamespaceContext } from '../spreadsheet/NamespaceContext'; diff --git a/packages/desktop-client/src/components/manager/BudgetList.tsx b/packages/desktop-client/src/components/manager/BudgetList.tsx index 03b8ab1300e..860a4db0c7b 100644 --- a/packages/desktop-client/src/components/manager/BudgetList.tsx +++ b/packages/desktop-client/src/components/manager/BudgetList.tsx @@ -6,7 +6,6 @@ import React, { useCallback, } from 'react'; import { Trans, useTranslation } from 'react-i18next'; -import { useDispatch, useSelector } from 'react-redux'; import { closeAndDownloadBudget, @@ -43,6 +42,7 @@ import { SvgUserGroup, } from '../../icons/v1'; import { SvgCloudUnknown, SvgKey, SvgRefreshArrow } from '../../icons/v2'; +import { useSelector, useDispatch } from '../../redux'; import { styles, theme } from '../../style'; import { tokens } from '../../tokens'; import { Button } from '../common/Button2'; diff --git a/packages/desktop-client/src/components/manager/ConfigServer.tsx b/packages/desktop-client/src/components/manager/ConfigServer.tsx index 09511e249e7..a8020f083dc 100644 --- a/packages/desktop-client/src/components/manager/ConfigServer.tsx +++ b/packages/desktop-client/src/components/manager/ConfigServer.tsx @@ -1,7 +1,6 @@ // @ts-strict-ignore import React, { useState, useEffect, useCallback } from 'react'; import { Trans, useTranslation } from 'react-i18next'; -import { useDispatch } from 'react-redux'; import { createBudget, loggedIn, signOut } from 'loot-core/client/actions'; import { @@ -11,6 +10,7 @@ import { import { useGlobalPref } from '../../hooks/useGlobalPref'; import { useNavigate } from '../../hooks/useNavigate'; +import { useDispatch } from '../../redux'; import { theme } from '../../style'; import { Button, ButtonWithLoading } from '../common/Button2'; import { BigInput } from '../common/Input'; diff --git a/packages/desktop-client/src/components/manager/ManagementApp.tsx b/packages/desktop-client/src/components/manager/ManagementApp.tsx index 05f22b26dd9..872bf98363a 100644 --- a/packages/desktop-client/src/components/manager/ManagementApp.tsx +++ b/packages/desktop-client/src/components/manager/ManagementApp.tsx @@ -1,5 +1,4 @@ import React, { useEffect } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; import { Navigate, Route, Routes } from 'react-router-dom'; import { loggedIn, setAppState } from 'loot-core/client/actions'; @@ -7,6 +6,7 @@ import { loggedIn, setAppState } from 'loot-core/client/actions'; import { ProtectedRoute } from '../../auth/ProtectedRoute'; import { Permissions } from '../../auth/types'; import { useMetaThemeColor } from '../../hooks/useMetaThemeColor'; +import { useSelector, useDispatch } from '../../redux'; import { theme } from '../../style'; import { tokens } from '../../tokens'; import { diff --git a/packages/desktop-client/src/components/manager/WelcomeScreen.tsx b/packages/desktop-client/src/components/manager/WelcomeScreen.tsx index 9866f16d32a..7586402a78b 100644 --- a/packages/desktop-client/src/components/manager/WelcomeScreen.tsx +++ b/packages/desktop-client/src/components/manager/WelcomeScreen.tsx @@ -1,9 +1,9 @@ import React from 'react'; import { Trans, useTranslation } from 'react-i18next'; -import { useDispatch } from 'react-redux'; import { createBudget, pushModal } from 'loot-core/client/actions'; +import { useDispatch } from '../../redux'; import { styles, theme } from '../../style'; import { Button } from '../common/Button2'; import { Link } from '../common/Link'; diff --git a/packages/desktop-client/src/components/manager/subscribe/Bootstrap.tsx b/packages/desktop-client/src/components/manager/subscribe/Bootstrap.tsx index 3e2d48b199d..d78561dbfd5 100644 --- a/packages/desktop-client/src/components/manager/subscribe/Bootstrap.tsx +++ b/packages/desktop-client/src/components/manager/subscribe/Bootstrap.tsx @@ -1,12 +1,12 @@ // @ts-strict-ignore import React, { useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; -import { useDispatch } from 'react-redux'; import { createBudget } from 'loot-core/src/client/actions/budgets'; import { send } from 'loot-core/src/platform/client/fetch'; import { useNavigate } from '../../../hooks/useNavigate'; +import { useDispatch } from '../../../redux'; import { theme } from '../../../style'; import { Button } from '../../common/Button2'; import { Link } from '../../common/Link'; diff --git a/packages/desktop-client/src/components/manager/subscribe/Login.tsx b/packages/desktop-client/src/components/manager/subscribe/Login.tsx index df8aefaf423..b9f117fd647 100644 --- a/packages/desktop-client/src/components/manager/subscribe/Login.tsx +++ b/packages/desktop-client/src/components/manager/subscribe/Login.tsx @@ -1,7 +1,6 @@ // @ts-strict-ignore import React, { useState, useEffect } from 'react'; import { Trans, useTranslation } from 'react-i18next'; -import { useDispatch } from 'react-redux'; import { useSearchParams } from 'react-router-dom'; import { isElectron } from 'loot-core/shared/environment'; @@ -11,6 +10,7 @@ import { type OpenIdConfig } from 'loot-core/types/models/openid'; import { useNavigate } from '../../../hooks/useNavigate'; import { AnimatedLoading } from '../../../icons/AnimatedLoading'; +import { useDispatch } from '../../../redux'; import { styles, theme } from '../../../style'; import { Button, ButtonWithLoading } from '../../common/Button2'; import { BigInput } from '../../common/Input'; diff --git a/packages/desktop-client/src/components/manager/subscribe/OpenIdCallback.ts b/packages/desktop-client/src/components/manager/subscribe/OpenIdCallback.ts index 02928047eab..a14871a035b 100644 --- a/packages/desktop-client/src/components/manager/subscribe/OpenIdCallback.ts +++ b/packages/desktop-client/src/components/manager/subscribe/OpenIdCallback.ts @@ -1,9 +1,10 @@ import { useEffect } from 'react'; -import { useDispatch } from 'react-redux'; import { loggedIn } from 'loot-core/src/client/actions/user'; import { send } from 'loot-core/src/platform/client/fetch'; +import { useDispatch } from '../../../redux'; + export function OpenIdCallback() { const dispatch = useDispatch(); useEffect(() => { diff --git a/packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx b/packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx index 3b6c6780312..50d2456a467 100644 --- a/packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx +++ b/packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx @@ -5,7 +5,6 @@ import React, { useMemo, useState, } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; import { collapseModals, @@ -38,6 +37,7 @@ import { useAccountPreviewTransactions } from '../../../hooks/useAccountPreviewT import { useDateFormat } from '../../../hooks/useDateFormat'; import { useFailedAccounts } from '../../../hooks/useFailedAccounts'; import { useNavigate } from '../../../hooks/useNavigate'; +import { useSelector, useDispatch } from '../../../redux'; import { styles, theme } from '../../../style'; import { Button } from '../../common/Button2'; import { Text } from '../../common/Text'; diff --git a/packages/desktop-client/src/components/mobile/accounts/Accounts.tsx b/packages/desktop-client/src/components/mobile/accounts/Accounts.tsx index 3a2668004cd..16cb2f958dd 100644 --- a/packages/desktop-client/src/components/mobile/accounts/Accounts.tsx +++ b/packages/desktop-client/src/components/mobile/accounts/Accounts.tsx @@ -1,6 +1,5 @@ import React, { type CSSProperties, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; -import { useDispatch, useSelector } from 'react-redux'; import { css } from '@emotion/css'; @@ -13,6 +12,7 @@ import { useFailedAccounts } from '../../../hooks/useFailedAccounts'; import { useNavigate } from '../../../hooks/useNavigate'; import { useSyncedPref } from '../../../hooks/useSyncedPref'; import { SvgAdd, SvgCheveronRight } from '../../../icons/v1'; +import { useDispatch, useSelector } from '../../../redux'; import { theme, styles } from '../../../style'; import { makeAmountFullStyle } from '../../budget/util'; import { Button } from '../../common/Button2'; diff --git a/packages/desktop-client/src/components/mobile/budget/BudgetTable.jsx b/packages/desktop-client/src/components/mobile/budget/BudgetTable.jsx index c2bf9cd50b2..c3976b39103 100644 --- a/packages/desktop-client/src/components/mobile/budget/BudgetTable.jsx +++ b/packages/desktop-client/src/components/mobile/budget/BudgetTable.jsx @@ -1,6 +1,5 @@ import React, { memo, useCallback, useRef } from 'react'; import { useTranslation } from 'react-i18next'; -import { useDispatch } from 'react-redux'; import { css } from '@emotion/css'; import { AutoTextSize } from 'auto-text-size'; @@ -31,6 +30,7 @@ import { SvgCheveronRight, } from '../../../icons/v1'; import { SvgViewShow } from '../../../icons/v2'; +import { useDispatch } from '../../../redux'; import { theme, styles } from '../../../style'; import { BalanceWithCarryover } from '../../budget/BalanceWithCarryover'; import { makeAmountGrey, makeBalanceAmountStyle } from '../../budget/util'; diff --git a/packages/desktop-client/src/components/mobile/budget/CategoryTransactions.tsx b/packages/desktop-client/src/components/mobile/budget/CategoryTransactions.tsx index 4eee5f6e2cb..290f8c25fac 100644 --- a/packages/desktop-client/src/components/mobile/budget/CategoryTransactions.tsx +++ b/packages/desktop-client/src/components/mobile/budget/CategoryTransactions.tsx @@ -1,5 +1,4 @@ import React, { useCallback, useEffect, useState } from 'react'; -import { useDispatch } from 'react-redux'; import { getPayees } from 'loot-core/client/actions'; import { @@ -18,6 +17,7 @@ import { import { useDateFormat } from '../../../hooks/useDateFormat'; import { useNavigate } from '../../../hooks/useNavigate'; +import { useDispatch } from '../../../redux'; import { TextOneLine } from '../../common/TextOneLine'; import { View } from '../../common/View'; import { MobilePageHeader, Page } from '../../Page'; diff --git a/packages/desktop-client/src/components/mobile/budget/index.tsx b/packages/desktop-client/src/components/mobile/budget/index.tsx index 20d233753e9..9ff327e8853 100644 --- a/packages/desktop-client/src/components/mobile/budget/index.tsx +++ b/packages/desktop-client/src/components/mobile/budget/index.tsx @@ -1,6 +1,5 @@ // @ts-strict-ignore import React, { useCallback, useEffect, useState } from 'react'; -import { useDispatch } from 'react-redux'; import { applyBudgetAction, @@ -25,6 +24,7 @@ import { useCategories } from '../../../hooks/useCategories'; import { useLocalPref } from '../../../hooks/useLocalPref'; import { useSyncedPref } from '../../../hooks/useSyncedPref'; import { AnimatedLoading } from '../../../icons/AnimatedLoading'; +import { useDispatch } from '../../../redux'; import { theme } from '../../../style'; import { prewarmMonth } from '../../budget/util'; import { View } from '../../common/View'; diff --git a/packages/desktop-client/src/components/mobile/transactions/TransactionEdit.jsx b/packages/desktop-client/src/components/mobile/transactions/TransactionEdit.jsx index 3e0abc54898..ef3066469eb 100644 --- a/packages/desktop-client/src/components/mobile/transactions/TransactionEdit.jsx +++ b/packages/desktop-client/src/components/mobile/transactions/TransactionEdit.jsx @@ -8,7 +8,6 @@ import React, { useCallback, } from 'react'; import { useTranslation } from 'react-i18next'; -import { useDispatch, useSelector } from 'react-redux'; import { useLocation, useParams } from 'react-router-dom'; import { @@ -55,6 +54,7 @@ import { import { SvgSplit } from '../../../icons/v0'; import { SvgAdd, SvgPiggyBank, SvgTrash } from '../../../icons/v1'; import { SvgPencilWriteAlternate } from '../../../icons/v2'; +import { useSelector, useDispatch } from '../../../redux'; import { styles, theme } from '../../../style'; import { Button } from '../../common/Button'; import { Text } from '../../common/Text'; diff --git a/packages/desktop-client/src/components/mobile/transactions/TransactionList.jsx b/packages/desktop-client/src/components/mobile/transactions/TransactionList.jsx index f577126fdb8..cf555dba3bb 100644 --- a/packages/desktop-client/src/components/mobile/transactions/TransactionList.jsx +++ b/packages/desktop-client/src/components/mobile/transactions/TransactionList.jsx @@ -7,7 +7,6 @@ import React, { } from 'react'; import { ListBox, Section, Header, Collection } from 'react-aria-components'; import { useTranslation } from 'react-i18next'; -import { useDispatch } from 'react-redux'; import { setNotificationInset } from 'loot-core/client/actions'; import { groupById, integerToCurrency } from 'loot-core/shared/util'; @@ -27,6 +26,7 @@ import { useUndo } from '../../../hooks/useUndo'; import { AnimatedLoading } from '../../../icons/AnimatedLoading'; import { SvgDelete } from '../../../icons/v0'; import { SvgDotsHorizontalTriple } from '../../../icons/v1'; +import { useDispatch } from '../../../redux'; import { styles, theme } from '../../../style'; import { Button } from '../../common/Button2'; import { Menu } from '../../common/Menu'; diff --git a/packages/desktop-client/src/components/mobile/transactions/TransactionListItem.tsx b/packages/desktop-client/src/components/mobile/transactions/TransactionListItem.tsx index db898ec9ef8..dee30eddf0d 100644 --- a/packages/desktop-client/src/components/mobile/transactions/TransactionListItem.tsx +++ b/packages/desktop-client/src/components/mobile/transactions/TransactionListItem.tsx @@ -4,7 +4,6 @@ import React, { } from 'react'; import { mergeProps } from 'react-aria'; import { ListBoxItem } from 'react-aria-components'; -import { useSelector } from 'react-redux'; import { PressResponder, @@ -25,6 +24,7 @@ import { SvgCheckCircle1, SvgLockClosed, } from '../../../icons/v2'; +import { useSelector } from '../../../redux'; import { styles, theme } from '../../../style'; import { makeAmountFullStyle } from '../../budget/util'; import { Button } from '../../common/Button2'; diff --git a/packages/desktop-client/src/components/modals/BudgetListModal.tsx b/packages/desktop-client/src/components/modals/BudgetListModal.tsx index 10e4e5e5829..1d1d5fc77f5 100644 --- a/packages/desktop-client/src/components/modals/BudgetListModal.tsx +++ b/packages/desktop-client/src/components/modals/BudgetListModal.tsx @@ -1,8 +1,8 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; -import { useSelector } from 'react-redux'; import { useMetadataPref } from '../../hooks/useMetadataPref'; +import { useSelector } from '../../redux'; import { Modal, ModalHeader, ModalCloseButton } from '../common/Modal'; import { Text } from '../common/Text'; import { View } from '../common/View'; diff --git a/packages/desktop-client/src/components/modals/CloseAccountModal.tsx b/packages/desktop-client/src/components/modals/CloseAccountModal.tsx index 2b476bd37d1..98a606bc0a9 100644 --- a/packages/desktop-client/src/components/modals/CloseAccountModal.tsx +++ b/packages/desktop-client/src/components/modals/CloseAccountModal.tsx @@ -2,7 +2,6 @@ import React, { type FormEvent, useState, type CSSProperties } from 'react'; import { Form } from 'react-aria-components'; import { useTranslation, Trans } from 'react-i18next'; -import { useDispatch } from 'react-redux'; import { closeAccount, @@ -15,6 +14,7 @@ import { type TransObjectLiteral } from 'loot-core/types/util'; import { useAccounts } from '../../hooks/useAccounts'; import { useCategories } from '../../hooks/useCategories'; +import { useDispatch } from '../../redux'; import { styles, theme } from '../../style'; import { AccountAutocomplete } from '../autocomplete/AccountAutocomplete'; import { CategoryAutocomplete } from '../autocomplete/CategoryAutocomplete'; diff --git a/packages/desktop-client/src/components/modals/CoverModal.tsx b/packages/desktop-client/src/components/modals/CoverModal.tsx index 30a94eb76f5..8123d80f8f4 100644 --- a/packages/desktop-client/src/components/modals/CoverModal.tsx +++ b/packages/desktop-client/src/components/modals/CoverModal.tsx @@ -1,11 +1,11 @@ import React, { useCallback, useMemo, useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; -import { useDispatch } from 'react-redux'; import { pushModal } from 'loot-core/client/actions'; import { type CategoryEntity } from 'loot-core/src/types/models'; import { useCategories } from '../../hooks/useCategories'; +import { useDispatch } from '../../redux'; import { styles } from '../../style'; import { addToBeBudgetedGroup, diff --git a/packages/desktop-client/src/components/modals/CreateAccountModal.tsx b/packages/desktop-client/src/components/modals/CreateAccountModal.tsx index 85d438a65fb..64597e2a009 100644 --- a/packages/desktop-client/src/components/modals/CreateAccountModal.tsx +++ b/packages/desktop-client/src/components/modals/CreateAccountModal.tsx @@ -1,7 +1,6 @@ import React, { useEffect, useState } from 'react'; import { DialogTrigger } from 'react-aria-components'; import { Trans, useTranslation } from 'react-i18next'; -import { useDispatch } from 'react-redux'; import { pushModal } from 'loot-core/client/actions'; import { send } from 'loot-core/src/platform/client/fetch'; @@ -13,6 +12,7 @@ import { useGoCardlessStatus } from '../../hooks/useGoCardlessStatus'; import { useSimpleFinStatus } from '../../hooks/useSimpleFinStatus'; import { useSyncServerStatus } from '../../hooks/useSyncServerStatus'; import { SvgDotsHorizontalTriple } from '../../icons/v1'; +import { useDispatch } from '../../redux'; import { theme } from '../../style'; import { Warning } from '../alerts'; import { Button, ButtonWithLoading } from '../common/Button2'; diff --git a/packages/desktop-client/src/components/modals/CreateEncryptionKeyModal.tsx b/packages/desktop-client/src/components/modals/CreateEncryptionKeyModal.tsx index 77ec1b9eb71..1e9c3bbcba2 100644 --- a/packages/desktop-client/src/components/modals/CreateEncryptionKeyModal.tsx +++ b/packages/desktop-client/src/components/modals/CreateEncryptionKeyModal.tsx @@ -2,7 +2,6 @@ import React, { useState } from 'react'; import { Form } from 'react-aria-components'; import { useTranslation, Trans } from 'react-i18next'; -import { useDispatch } from 'react-redux'; import { css } from '@emotion/css'; @@ -10,6 +9,7 @@ import { loadAllFiles, loadGlobalPrefs, sync } from 'loot-core/client/actions'; import { send } from 'loot-core/src/platform/client/fetch'; import { getCreateKeyError } from 'loot-core/src/shared/errors'; +import { useDispatch } from '../../redux'; import { styles, theme } from '../../style'; import { ButtonWithLoading } from '../common/Button2'; import { InitialFocus } from '../common/InitialFocus'; diff --git a/packages/desktop-client/src/components/modals/CreateLocalAccountModal.tsx b/packages/desktop-client/src/components/modals/CreateLocalAccountModal.tsx index d2147150b84..72e2e386192 100644 --- a/packages/desktop-client/src/components/modals/CreateLocalAccountModal.tsx +++ b/packages/desktop-client/src/components/modals/CreateLocalAccountModal.tsx @@ -2,13 +2,13 @@ import { type FormEvent, useState } from 'react'; import { Form } from 'react-aria-components'; import { useTranslation } from 'react-i18next'; -import { useDispatch } from 'react-redux'; import { closeModal, createAccount } from 'loot-core/client/actions'; import { toRelaxedNumber } from 'loot-core/src/shared/util'; import * as useAccounts from '../../hooks/useAccounts'; import { useNavigate } from '../../hooks/useNavigate'; +import { useDispatch } from '../../redux'; import { theme } from '../../style'; import { Button } from '../common/Button2'; import { FormError } from '../common/FormError'; diff --git a/packages/desktop-client/src/components/modals/EditRuleModal.jsx b/packages/desktop-client/src/components/modals/EditRuleModal.jsx index 78599bb261d..f70aae87093 100644 --- a/packages/desktop-client/src/components/modals/EditRuleModal.jsx +++ b/packages/desktop-client/src/components/modals/EditRuleModal.jsx @@ -1,6 +1,5 @@ import React, { useState, useEffect, useRef, useMemo } from 'react'; import { useTranslation, Trans } from 'react-i18next'; -import { useDispatch } from 'react-redux'; import { css } from '@emotion/css'; import { v4 as uuid } from 'uuid'; @@ -37,6 +36,7 @@ import { useFeatureFlag } from '../../hooks/useFeatureFlag'; import { useSelected, SelectedProvider } from '../../hooks/useSelected'; import { SvgDelete, SvgAdd, SvgSubtract } from '../../icons/v0'; import { SvgAlignLeft, SvgCode, SvgInformationOutline } from '../../icons/v1'; +import { useDispatch } from '../../redux'; import { styles, theme } from '../../style'; import { Button } from '../common/Button2'; import { Menu } from '../common/Menu'; diff --git a/packages/desktop-client/src/components/modals/EnvelopeBudgetSummaryModal.tsx b/packages/desktop-client/src/components/modals/EnvelopeBudgetSummaryModal.tsx index 8c9b247da3b..b22ed2a4573 100644 --- a/packages/desktop-client/src/components/modals/EnvelopeBudgetSummaryModal.tsx +++ b/packages/desktop-client/src/components/modals/EnvelopeBudgetSummaryModal.tsx @@ -1,6 +1,5 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; -import { useDispatch } from 'react-redux'; import { collapseModals, pushModal } from 'loot-core/client/actions'; import { envelopeBudget } from 'loot-core/client/queries'; @@ -9,6 +8,7 @@ import { format, sheetForMonth, prevMonth } from 'loot-core/src/shared/months'; import { useCategories } from '../../hooks/useCategories'; import { useUndo } from '../../hooks/useUndo'; +import { useDispatch } from '../../redux'; import { styles } from '../../style'; import { ToBudgetAmount } from '../budget/envelope/budgetsummary/ToBudgetAmount'; import { TotalsList } from '../budget/envelope/budgetsummary/TotalsList'; diff --git a/packages/desktop-client/src/components/modals/GoCardlessExternalMsgModal.tsx b/packages/desktop-client/src/components/modals/GoCardlessExternalMsgModal.tsx index 886fff3c88e..7d119703815 100644 --- a/packages/desktop-client/src/components/modals/GoCardlessExternalMsgModal.tsx +++ b/packages/desktop-client/src/components/modals/GoCardlessExternalMsgModal.tsx @@ -1,7 +1,6 @@ // @ts-strict-ignore import React, { useEffect, useState, useRef } from 'react'; import { Trans, useTranslation } from 'react-i18next'; -import { useDispatch } from 'react-redux'; import { pushModal } from 'loot-core/src/client/actions/modals'; import { sendCatch } from 'loot-core/src/platform/client/fetch'; @@ -12,6 +11,7 @@ import { import { useGoCardlessStatus } from '../../hooks/useGoCardlessStatus'; import { AnimatedLoading } from '../../icons/AnimatedLoading'; +import { useDispatch } from '../../redux'; import { theme } from '../../style'; import { Error, Warning } from '../alerts'; import { Autocomplete } from '../autocomplete/Autocomplete'; diff --git a/packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx b/packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx index 530b2d10324..9c1092ef96a 100644 --- a/packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx +++ b/packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.jsx @@ -1,6 +1,5 @@ import React, { useState, useEffect, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; -import { useDispatch } from 'react-redux'; import deepEqual from 'deep-equal'; @@ -14,6 +13,7 @@ import { amountToInteger } from 'loot-core/src/shared/util'; import { useDateFormat } from '../../../hooks/useDateFormat'; import { useSyncedPrefs } from '../../../hooks/useSyncedPrefs'; +import { useDispatch } from '../../../redux'; import { theme } from '../../../style'; import { Button, ButtonWithLoading } from '../../common/Button2'; import { Input } from '../../common/Input'; diff --git a/packages/desktop-client/src/components/modals/LoadBackupModal.tsx b/packages/desktop-client/src/components/modals/LoadBackupModal.tsx index 0be83b372c1..19bbb62fac2 100644 --- a/packages/desktop-client/src/components/modals/LoadBackupModal.tsx +++ b/packages/desktop-client/src/components/modals/LoadBackupModal.tsx @@ -1,12 +1,12 @@ import React, { useState, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; -import { useDispatch } from 'react-redux'; import { loadBackup, makeBackup } from 'loot-core/client/actions'; import { type Backup } from 'loot-core/server/backups'; import { send, listen, unlisten } from 'loot-core/src/platform/client/fetch'; import { useMetadataPref } from '../../hooks/useMetadataPref'; +import { useDispatch } from '../../redux'; import { theme } from '../../style'; import { Block } from '../common/Block'; import { Button } from '../common/Button2'; diff --git a/packages/desktop-client/src/components/modals/MergeUnusedPayeesModal.tsx b/packages/desktop-client/src/components/modals/MergeUnusedPayeesModal.tsx index acd5ae9b388..4a22fbfefdd 100644 --- a/packages/desktop-client/src/components/modals/MergeUnusedPayeesModal.tsx +++ b/packages/desktop-client/src/components/modals/MergeUnusedPayeesModal.tsx @@ -1,12 +1,12 @@ import React, { useState, useRef, useEffect, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; -import { useDispatch, useSelector } from 'react-redux'; import { replaceModal } from 'loot-core/src/client/actions/modals'; import { send } from 'loot-core/src/platform/client/fetch'; import { type PayeeEntity } from 'loot-core/types/models'; import { usePayees } from '../../hooks/usePayees'; +import { useSelector, useDispatch } from '../../redux'; import { theme } from '../../style'; import { Information } from '../alerts'; import { Button } from '../common/Button2'; diff --git a/packages/desktop-client/src/components/modals/OutOfSyncMigrationsModal.tsx b/packages/desktop-client/src/components/modals/OutOfSyncMigrationsModal.tsx index 60bd9fc054c..2a904f26345 100644 --- a/packages/desktop-client/src/components/modals/OutOfSyncMigrationsModal.tsx +++ b/packages/desktop-client/src/components/modals/OutOfSyncMigrationsModal.tsx @@ -1,9 +1,9 @@ import React from 'react'; import { Trans, useTranslation } from 'react-i18next'; -import { useDispatch } from 'react-redux'; import { closeBudget } from 'loot-core/client/actions'; +import { useDispatch } from '../../redux'; import { Button } from '../common/Button2'; import { Link } from '../common/Link'; import { Modal, ModalHeader, ModalTitle } from '../common/Modal'; diff --git a/packages/desktop-client/src/components/modals/SelectLinkedAccountsModal.jsx b/packages/desktop-client/src/components/modals/SelectLinkedAccountsModal.jsx index 98eaf9a8839..d72bd8e8cf0 100644 --- a/packages/desktop-client/src/components/modals/SelectLinkedAccountsModal.jsx +++ b/packages/desktop-client/src/components/modals/SelectLinkedAccountsModal.jsx @@ -1,6 +1,5 @@ import React, { useState } from 'react'; import { useTranslation, Trans } from 'react-i18next'; -import { useDispatch } from 'react-redux'; import { closeModal, @@ -10,6 +9,7 @@ import { } from 'loot-core/client/actions'; import { useAccounts } from '../../hooks/useAccounts'; +import { useDispatch } from '../../redux'; import { theme } from '../../style'; import { Autocomplete } from '../autocomplete/Autocomplete'; import { Button } from '../common/Button2'; diff --git a/packages/desktop-client/src/components/modals/TransferModal.tsx b/packages/desktop-client/src/components/modals/TransferModal.tsx index 2abd515a618..9cf1a7fa64b 100644 --- a/packages/desktop-client/src/components/modals/TransferModal.tsx +++ b/packages/desktop-client/src/components/modals/TransferModal.tsx @@ -1,11 +1,11 @@ import React, { useMemo, useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; -import { useDispatch } from 'react-redux'; import { pushModal } from 'loot-core/client/actions'; import { type CategoryEntity } from 'loot-core/types/models'; import { useCategories } from '../../hooks/useCategories'; +import { useDispatch } from '../../redux'; import { styles } from '../../style'; import { addToBeBudgetedGroup, diff --git a/packages/desktop-client/src/components/modals/TransferOwnership.tsx b/packages/desktop-client/src/components/modals/TransferOwnership.tsx index bb2a198c48c..d1619964ffd 100644 --- a/packages/desktop-client/src/components/modals/TransferOwnership.tsx +++ b/packages/desktop-client/src/components/modals/TransferOwnership.tsx @@ -1,9 +1,7 @@ import { useEffect, useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; -import { useDispatch, useSelector } from 'react-redux'; import { addNotification, closeAndLoadBudget } from 'loot-core/client/actions'; -import { type State } from 'loot-core/client/state-types'; import { send } from 'loot-core/platform/client/fetch'; import { getUserAccessErrors } from 'loot-core/shared/errors'; import { type Budget } from 'loot-core/types/budget'; @@ -12,6 +10,7 @@ import { type Handlers } from 'loot-core/types/handlers'; import { useActions } from '../../hooks/useActions'; import { useMetadataPref } from '../../hooks/useMetadataPref'; +import { useDispatch, useSelector } from '../../redux'; import { styles, theme } from '../../style'; import { Button } from '../common/Button2'; import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal'; @@ -30,7 +29,7 @@ export function TransferOwnership({ }: TransferOwnershipProps) { const { t } = useTranslation(); - const userData = useSelector((state: State) => state.user.data); + const userData = useSelector(state => state.user.data); const actions = useActions(); const [userId, setUserId] = useState(''); const [error, setError] = useState(null); diff --git a/packages/desktop-client/src/components/modals/manager/ConfirmChangeDocumentDir.tsx b/packages/desktop-client/src/components/modals/manager/ConfirmChangeDocumentDir.tsx index 23e81a4f23e..adf3e5dfe35 100644 --- a/packages/desktop-client/src/components/modals/manager/ConfirmChangeDocumentDir.tsx +++ b/packages/desktop-client/src/components/modals/manager/ConfirmChangeDocumentDir.tsx @@ -1,10 +1,10 @@ import React, { useCallback, useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; -import { useDispatch } from 'react-redux'; import { addNotification } from 'loot-core/client/actions'; import { useGlobalPref } from '../../../hooks/useGlobalPref'; +import { useDispatch } from '../../../redux'; import { theme, styles } from '../../../style'; import { Information } from '../../alerts'; import { Button, ButtonWithLoading } from '../../common/Button2'; diff --git a/packages/desktop-client/src/components/modals/manager/DeleteFileModal.tsx b/packages/desktop-client/src/components/modals/manager/DeleteFileModal.tsx index 3b93804fb86..2524c0e4d1d 100644 --- a/packages/desktop-client/src/components/modals/manager/DeleteFileModal.tsx +++ b/packages/desktop-client/src/components/modals/manager/DeleteFileModal.tsx @@ -1,10 +1,10 @@ import React, { useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; -import { useDispatch } from 'react-redux'; import { deleteBudget } from 'loot-core/client/actions'; import { type File } from 'loot-core/src/types/file'; +import { useDispatch } from '../../../redux'; import { theme } from '../../../style'; import { ButtonWithLoading } from '../../common/Button2'; import { Modal, ModalCloseButton, ModalHeader } from '../../common/Modal'; diff --git a/packages/desktop-client/src/components/modals/manager/DuplicateFileModal.tsx b/packages/desktop-client/src/components/modals/manager/DuplicateFileModal.tsx index a614b97c6d3..224aaa0f08f 100644 --- a/packages/desktop-client/src/components/modals/manager/DuplicateFileModal.tsx +++ b/packages/desktop-client/src/components/modals/manager/DuplicateFileModal.tsx @@ -1,6 +1,5 @@ import React, { useEffect, useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; -import { useDispatch } from 'react-redux'; import { addNotification, @@ -10,6 +9,7 @@ import { } from 'loot-core/client/actions'; import { type File } from 'loot-core/src/types/file'; +import { useDispatch } from '../../../redux'; import { theme } from '../../../style'; import { Button, ButtonWithLoading } from '../../common/Button2'; import { FormError } from '../../common/FormError'; diff --git a/packages/desktop-client/src/components/modals/manager/FilesSettingsModal.tsx b/packages/desktop-client/src/components/modals/manager/FilesSettingsModal.tsx index 55d1e7bee68..ee3c1107def 100644 --- a/packages/desktop-client/src/components/modals/manager/FilesSettingsModal.tsx +++ b/packages/desktop-client/src/components/modals/manager/FilesSettingsModal.tsx @@ -1,11 +1,11 @@ import React, { useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; -import { useDispatch } from 'react-redux'; import { loadAllFiles, pushModal } from 'loot-core/client/actions'; import { useGlobalPref } from '../../../hooks/useGlobalPref'; import { SvgPencil1 } from '../../../icons/v2'; +import { useDispatch } from '../../../redux'; import { theme, styles } from '../../../style'; import { Button } from '../../common/Button2'; import { Modal, ModalCloseButton, ModalHeader } from '../../common/Modal'; diff --git a/packages/desktop-client/src/components/modals/manager/ImportActualModal.tsx b/packages/desktop-client/src/components/modals/manager/ImportActualModal.tsx index ac31b5757b2..995154b13e7 100644 --- a/packages/desktop-client/src/components/modals/manager/ImportActualModal.tsx +++ b/packages/desktop-client/src/components/modals/manager/ImportActualModal.tsx @@ -1,11 +1,11 @@ // @ts-strict-ignore import React, { useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; -import { useDispatch } from 'react-redux'; import { importBudget } from 'loot-core/src/client/actions/budgets'; import { useNavigate } from '../../../hooks/useNavigate'; +import { useDispatch } from '../../../redux'; import { styles, theme } from '../../../style'; import { Block } from '../../common/Block'; import { ButtonWithLoading } from '../../common/Button2'; diff --git a/packages/desktop-client/src/components/modals/manager/ImportModal.tsx b/packages/desktop-client/src/components/modals/manager/ImportModal.tsx index 3ced17d7093..ef4d1824f05 100644 --- a/packages/desktop-client/src/components/modals/manager/ImportModal.tsx +++ b/packages/desktop-client/src/components/modals/manager/ImportModal.tsx @@ -1,9 +1,9 @@ import React, { useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; -import { useDispatch } from 'react-redux'; import { pushModal } from 'loot-core/client/actions'; +import { useDispatch } from '../../../redux'; import { styles, theme } from '../../../style'; import { Block } from '../../common/Block'; import { Button } from '../../common/Button2'; diff --git a/packages/desktop-client/src/components/modals/manager/ImportYNAB4Modal.tsx b/packages/desktop-client/src/components/modals/manager/ImportYNAB4Modal.tsx index 2c0a52b9318..49a80721bf9 100644 --- a/packages/desktop-client/src/components/modals/manager/ImportYNAB4Modal.tsx +++ b/packages/desktop-client/src/components/modals/manager/ImportYNAB4Modal.tsx @@ -1,11 +1,11 @@ // @ts-strict-ignore import React, { useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; -import { useDispatch } from 'react-redux'; import { importBudget } from 'loot-core/src/client/actions/budgets'; import { useNavigate } from '../../../hooks/useNavigate'; +import { useDispatch } from '../../../redux'; import { styles, theme } from '../../../style'; import { Block } from '../../common/Block'; import { ButtonWithLoading } from '../../common/Button2'; diff --git a/packages/desktop-client/src/components/modals/manager/ImportYNAB5Modal.tsx b/packages/desktop-client/src/components/modals/manager/ImportYNAB5Modal.tsx index 98ee2f49fac..8945278e80a 100644 --- a/packages/desktop-client/src/components/modals/manager/ImportYNAB5Modal.tsx +++ b/packages/desktop-client/src/components/modals/manager/ImportYNAB5Modal.tsx @@ -1,11 +1,11 @@ // @ts-strict-ignore import React, { useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; -import { useDispatch } from 'react-redux'; import { importBudget } from 'loot-core/src/client/actions/budgets'; import { useNavigate } from '../../../hooks/useNavigate'; +import { useDispatch } from '../../../redux'; import { styles, theme } from '../../../style'; import { Block } from '../../common/Block'; import { ButtonWithLoading } from '../../common/Button2'; diff --git a/packages/desktop-client/src/components/payees/ManagePayeesWithData.tsx b/packages/desktop-client/src/components/payees/ManagePayeesWithData.tsx index 064023fbad5..f7940295f5e 100644 --- a/packages/desktop-client/src/components/payees/ManagePayeesWithData.tsx +++ b/packages/desktop-client/src/components/payees/ManagePayeesWithData.tsx @@ -1,18 +1,18 @@ import React, { useState, useEffect, useCallback } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; import { getPayees, initiallyLoadPayees, pushModal, - setLastUndoState, } from 'loot-core/client/actions'; import { type UndoState } from 'loot-core/server/undo'; import { send, listen } from 'loot-core/src/platform/client/fetch'; +import * as undo from 'loot-core/src/platform/client/undo'; import { applyChanges, type Diff } from 'loot-core/src/shared/util'; import { type NewRuleEntity, type PayeeEntity } from 'loot-core/types/models'; import { usePayees } from '../../hooks/usePayees'; +import { useDispatch } from '../../redux'; import { ManagePayees } from './ManagePayees'; @@ -24,7 +24,6 @@ export function ManagePayeesWithData({ initialSelectedIds, }: ManagePayeesWithDataProps) { const payees = usePayees(); - const lastUndoState = useSelector(state => state.app.lastUndoState); const dispatch = useDispatch(); const [ruleCounts, setRuleCounts] = useState({ value: new Map() }); @@ -80,15 +79,16 @@ export function ManagePayeesWithData({ await refetchRuleCounts(); } - await dispatch(setLastUndoState(null)); + undo.setUndoState('undoEvent', null); } - if (lastUndoState.current) { - onUndo(lastUndoState.current); + const lastUndoEvent = undo.getUndoState('undoEvent'); + if (lastUndoEvent) { + onUndo(lastUndoEvent); } return listen('undo-event', onUndo); - }, [dispatch, lastUndoState, refetchRuleCounts, refetchOrphanedPayees]); + }, [dispatch, refetchRuleCounts, refetchOrphanedPayees]); function onViewRules(id: PayeeEntity['id']) { dispatch(pushModal('manage-rules', { payeeId: id })); diff --git a/packages/desktop-client/src/components/reports/Overview.tsx b/packages/desktop-client/src/components/reports/Overview.tsx index 8178454a9b7..5c13de11b9a 100644 --- a/packages/desktop-client/src/components/reports/Overview.tsx +++ b/packages/desktop-client/src/components/reports/Overview.tsx @@ -2,7 +2,6 @@ import React, { useMemo, useRef, useState } from 'react'; import { Responsive, WidthProvider, type Layout } from 'react-grid-layout'; import { useHotkeys } from 'react-hotkeys-hook'; import { Trans, useTranslation } from 'react-i18next'; -import { useDispatch } from 'react-redux'; import { useLocation } from 'react-router-dom'; import { @@ -22,6 +21,7 @@ import { import { useAccounts } from '../../hooks/useAccounts'; import { useNavigate } from '../../hooks/useNavigate'; import { useSyncedPref } from '../../hooks/useSyncedPref'; +import { useDispatch } from '../../redux'; import { breakpoints } from '../../tokens'; import { Button } from '../common/Button2'; import { Menu } from '../common/Menu'; diff --git a/packages/desktop-client/src/components/reports/reports/Calendar.tsx b/packages/desktop-client/src/components/reports/reports/Calendar.tsx index f064616fbc7..d4a723f6716 100644 --- a/packages/desktop-client/src/components/reports/reports/Calendar.tsx +++ b/packages/desktop-client/src/components/reports/reports/Calendar.tsx @@ -7,7 +7,6 @@ import React, { useCallback, } from 'react'; import { Trans, useTranslation } from 'react-i18next'; -import { useDispatch } from 'react-redux'; import { useParams, useSearchParams } from 'react-router-dom'; import { useSpring, animated, config } from 'react-spring'; @@ -48,6 +47,7 @@ import { SvgCheveronDown, SvgCheveronUp, } from '../../../icons/v1'; +import { useDispatch } from '../../../redux'; import { styles, theme } from '../../../style'; import { Button } from '../../common/Button2'; import { View } from '../../common/View'; diff --git a/packages/desktop-client/src/components/reports/reports/CashFlow.tsx b/packages/desktop-client/src/components/reports/reports/CashFlow.tsx index e7debf2df02..9ec6af671fc 100644 --- a/packages/desktop-client/src/components/reports/reports/CashFlow.tsx +++ b/packages/desktop-client/src/components/reports/reports/CashFlow.tsx @@ -1,6 +1,5 @@ import React, { useState, useEffect, useMemo } from 'react'; import { Trans, useTranslation } from 'react-i18next'; -import { useDispatch } from 'react-redux'; import { useParams } from 'react-router-dom'; import * as d from 'date-fns'; @@ -18,6 +17,7 @@ import { import { useFilters } from '../../../hooks/useFilters'; import { useNavigate } from '../../../hooks/useNavigate'; +import { useDispatch } from '../../../redux'; import { theme } from '../../../style'; import { AlignedText } from '../../common/AlignedText'; import { Block } from '../../common/Block'; diff --git a/packages/desktop-client/src/components/reports/reports/CustomReportListCards.tsx b/packages/desktop-client/src/components/reports/reports/CustomReportListCards.tsx index cc0a6e14468..b30a4dd1515 100644 --- a/packages/desktop-client/src/components/reports/reports/CustomReportListCards.tsx +++ b/packages/desktop-client/src/components/reports/reports/CustomReportListCards.tsx @@ -1,6 +1,5 @@ import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { useDispatch } from 'react-redux'; import { send, sendCatch } from 'loot-core/platform/client/fetch/index'; import { addNotification } from 'loot-core/src/client/actions'; @@ -13,6 +12,7 @@ import { useCategories } from '../../../hooks/useCategories'; import { usePayees } from '../../../hooks/usePayees'; import { useSyncedPref } from '../../../hooks/useSyncedPref'; import { SvgExclamationSolid } from '../../../icons/v1'; +import { useDispatch } from '../../../redux'; import { styles } from '../../../style/index'; import { theme } from '../../../style/theme'; import { Text } from '../../common/Text'; diff --git a/packages/desktop-client/src/components/reports/reports/NetWorth.tsx b/packages/desktop-client/src/components/reports/reports/NetWorth.tsx index ce0344486f9..150436fe1f1 100644 --- a/packages/desktop-client/src/components/reports/reports/NetWorth.tsx +++ b/packages/desktop-client/src/components/reports/reports/NetWorth.tsx @@ -1,6 +1,5 @@ import React, { useState, useEffect, useMemo } from 'react'; import { Trans, useTranslation } from 'react-i18next'; -import { useDispatch } from 'react-redux'; import { useParams } from 'react-router-dom'; import * as d from 'date-fns'; @@ -15,6 +14,7 @@ import { type TimeFrame, type NetWorthWidget } from 'loot-core/types/models'; import { useAccounts } from '../../../hooks/useAccounts'; import { useFilters } from '../../../hooks/useFilters'; import { useNavigate } from '../../../hooks/useNavigate'; +import { useDispatch } from '../../../redux'; import { theme, styles } from '../../../style'; import { Button } from '../../common/Button2'; import { Paragraph } from '../../common/Paragraph'; diff --git a/packages/desktop-client/src/components/reports/reports/Spending.tsx b/packages/desktop-client/src/components/reports/reports/Spending.tsx index d7ae60e8da6..a97fc15a2e5 100644 --- a/packages/desktop-client/src/components/reports/reports/Spending.tsx +++ b/packages/desktop-client/src/components/reports/reports/Spending.tsx @@ -1,6 +1,5 @@ import React, { useState, useMemo, useEffect } from 'react'; import { Trans, useTranslation } from 'react-i18next'; -import { useDispatch } from 'react-redux'; import { useParams } from 'react-router-dom'; import * as d from 'date-fns'; @@ -15,6 +14,7 @@ import { type RuleConditionEntity } from 'loot-core/types/models/rule'; import { useFilters } from '../../../hooks/useFilters'; import { useNavigate } from '../../../hooks/useNavigate'; +import { useDispatch } from '../../../redux'; import { theme, styles } from '../../../style'; import { AlignedText } from '../../common/AlignedText'; import { Block } from '../../common/Block'; diff --git a/packages/desktop-client/src/components/reports/reports/Summary.tsx b/packages/desktop-client/src/components/reports/reports/Summary.tsx index 95be1b5b465..1d7e5200edf 100644 --- a/packages/desktop-client/src/components/reports/reports/Summary.tsx +++ b/packages/desktop-client/src/components/reports/reports/Summary.tsx @@ -1,6 +1,5 @@ import React, { useState, useEffect, useMemo, type CSSProperties } from 'react'; import { Trans, useTranslation } from 'react-i18next'; -import { useDispatch } from 'react-redux'; import { useParams } from 'react-router-dom'; import { parseISO } from 'date-fns'; @@ -22,6 +21,7 @@ import { SvgEquals } from '../../../icons/v1'; import { SvgCloseParenthesis } from '../../../icons/v2/CloseParenthesis'; import { SvgOpenParenthesis } from '../../../icons/v2/OpenParenthesis'; import { SvgSum } from '../../../icons/v2/Sum'; +import { useDispatch } from '../../../redux'; import { theme } from '../../../style'; import { Button } from '../../common/Button2'; import { Text } from '../../common/Text'; diff --git a/packages/desktop-client/src/components/schedules/PostsOfflineNotification.tsx b/packages/desktop-client/src/components/schedules/PostsOfflineNotification.tsx index 2dd79ea0efb..47e9e70c4f3 100644 --- a/packages/desktop-client/src/components/schedules/PostsOfflineNotification.tsx +++ b/packages/desktop-client/src/components/schedules/PostsOfflineNotification.tsx @@ -1,6 +1,5 @@ import React from 'react'; import { Trans, useTranslation } from 'react-i18next'; -import { useDispatch } from 'react-redux'; import { useLocation } from 'react-router-dom'; import { popModal } from 'loot-core/client/actions'; @@ -8,6 +7,7 @@ import { send } from 'loot-core/src/platform/client/fetch'; import { type PayeeEntity } from 'loot-core/types/models'; import { useFormatList } from '../../hooks/useFormatList'; +import { useDispatch } from '../../redux'; import { theme } from '../../style'; import { Button } from '../common/Button2'; import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal'; diff --git a/packages/desktop-client/src/components/schedules/ScheduleDetails.tsx b/packages/desktop-client/src/components/schedules/ScheduleDetails.tsx index 6d02f34d1ed..bb917358457 100644 --- a/packages/desktop-client/src/components/schedules/ScheduleDetails.tsx +++ b/packages/desktop-client/src/components/schedules/ScheduleDetails.tsx @@ -1,7 +1,6 @@ // @ts-strict-ignore import React, { useEffect, useReducer } from 'react'; import { Trans, useTranslation } from 'react-i18next'; -import { useDispatch } from 'react-redux'; import { t } from 'i18next'; @@ -22,6 +21,7 @@ import { import { useDateFormat } from '../../hooks/useDateFormat'; import { usePayees } from '../../hooks/usePayees'; import { useSelected, SelectedProvider } from '../../hooks/useSelected'; +import { useDispatch } from '../../redux'; import { theme } from '../../style'; import { AccountAutocomplete } from '../autocomplete/AccountAutocomplete'; import { PayeeAutocomplete } from '../autocomplete/PayeeAutocomplete'; diff --git a/packages/desktop-client/src/components/schedules/ScheduleLink.tsx b/packages/desktop-client/src/components/schedules/ScheduleLink.tsx index a787fe9fe6e..773d460c05d 100644 --- a/packages/desktop-client/src/components/schedules/ScheduleLink.tsx +++ b/packages/desktop-client/src/components/schedules/ScheduleLink.tsx @@ -1,7 +1,6 @@ // @ts-strict-ignore import React, { useMemo, useRef, useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; -import { useDispatch } from 'react-redux'; import { pushModal } from 'loot-core/client/actions'; import { useSchedules } from 'loot-core/src/client/data-hooks/schedules'; @@ -13,6 +12,7 @@ import { } from 'loot-core/src/types/models'; import { SvgAdd } from '../../icons/v0'; +import { useDispatch } from '../../redux'; import { Button } from '../common/Button2'; import { InitialFocus } from '../common/InitialFocus'; import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal'; diff --git a/packages/desktop-client/src/components/schedules/index.tsx b/packages/desktop-client/src/components/schedules/index.tsx index 02db32b2a6e..e3f39cd1e92 100644 --- a/packages/desktop-client/src/components/schedules/index.tsx +++ b/packages/desktop-client/src/components/schedules/index.tsx @@ -1,6 +1,5 @@ import React, { useCallback, useMemo, useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; -import { useDispatch } from 'react-redux'; import { pushModal } from 'loot-core/client/actions'; import { q } from 'loot-core/shared/query'; @@ -8,6 +7,7 @@ import { useSchedules } from 'loot-core/src/client/data-hooks/schedules'; import { send } from 'loot-core/src/platform/client/fetch'; import { type ScheduleEntity } from 'loot-core/src/types/models'; +import { useDispatch } from '../../redux'; import { theme } from '../../style'; import { Button } from '../common/Button2'; import { Search } from '../common/Search'; diff --git a/packages/desktop-client/src/components/settings/AuthSettings.tsx b/packages/desktop-client/src/components/settings/AuthSettings.tsx index 229a5ed92b4..83af5bffdce 100644 --- a/packages/desktop-client/src/components/settings/AuthSettings.tsx +++ b/packages/desktop-client/src/components/settings/AuthSettings.tsx @@ -1,10 +1,10 @@ import React from 'react'; import { Trans, useTranslation } from 'react-i18next'; -import { useDispatch } from 'react-redux'; import { pushModal } from 'loot-core/client/actions'; import { useFeatureFlag } from '../../hooks/useFeatureFlag'; +import { useDispatch } from '../../redux'; import { theme } from '../../style'; import { Button } from '../common/Button2'; import { Label } from '../common/Label'; diff --git a/packages/desktop-client/src/components/settings/Encryption.tsx b/packages/desktop-client/src/components/settings/Encryption.tsx index 8a35bf9f738..fa65538415a 100644 --- a/packages/desktop-client/src/components/settings/Encryption.tsx +++ b/packages/desktop-client/src/components/settings/Encryption.tsx @@ -1,10 +1,10 @@ import React from 'react'; import { Trans } from 'react-i18next'; -import { useDispatch } from 'react-redux'; import { pushModal } from 'loot-core/client/actions'; import { useMetadataPref } from '../../hooks/useMetadataPref'; +import { useDispatch } from '../../redux'; import { theme } from '../../style'; import { Button } from '../common/Button2'; import { Link } from '../common/Link'; diff --git a/packages/desktop-client/src/components/settings/Reset.tsx b/packages/desktop-client/src/components/settings/Reset.tsx index f698d99544e..a9ef6e293b9 100644 --- a/packages/desktop-client/src/components/settings/Reset.tsx +++ b/packages/desktop-client/src/components/settings/Reset.tsx @@ -1,11 +1,11 @@ import React, { useState } from 'react'; import { Trans } from 'react-i18next'; -import { useDispatch } from 'react-redux'; import { resetSync } from 'loot-core/client/actions'; import { send } from 'loot-core/src/platform/client/fetch'; import { useMetadataPref } from '../../hooks/useMetadataPref'; +import { useDispatch } from '../../redux'; import { ButtonWithLoading } from '../common/Button2'; import { Text } from '../common/Text'; diff --git a/packages/desktop-client/src/components/settings/index.tsx b/packages/desktop-client/src/components/settings/index.tsx index e5ada574711..491ed850e60 100644 --- a/packages/desktop-client/src/components/settings/index.tsx +++ b/packages/desktop-client/src/components/settings/index.tsx @@ -1,6 +1,5 @@ import React, { type ReactNode, useEffect } from 'react'; import { useTranslation, Trans } from 'react-i18next'; -import { useDispatch } from 'react-redux'; import { css } from '@emotion/css'; @@ -11,6 +10,7 @@ import { listen } from 'loot-core/src/platform/client/fetch'; import { useGlobalPref } from '../../hooks/useGlobalPref'; import { useIsOutdated, useLatestVersion } from '../../hooks/useLatestVersion'; import { useMetadataPref } from '../../hooks/useMetadataPref'; +import { useDispatch } from '../../redux'; import { theme } from '../../style'; import { tokens } from '../../tokens'; import { Button } from '../common/Button2'; diff --git a/packages/desktop-client/src/components/sidebar/Account.tsx b/packages/desktop-client/src/components/sidebar/Account.tsx index d11549f2a04..bc72d78c2f3 100644 --- a/packages/desktop-client/src/components/sidebar/Account.tsx +++ b/packages/desktop-client/src/components/sidebar/Account.tsx @@ -1,6 +1,5 @@ // @ts-strict-ignore import React, { type CSSProperties, useRef, useState } from 'react'; -import { useDispatch } from 'react-redux'; import { css, cx } from '@emotion/css'; @@ -14,6 +13,7 @@ import { type AccountEntity } from 'loot-core/src/types/models'; import { useContextMenu } from '../../hooks/useContextMenu'; import { useNotes } from '../../hooks/useNotes'; +import { useDispatch } from '../../redux'; import { styles, theme } from '../../style'; import { AlignedText } from '../common/AlignedText'; import { InitialFocus } from '../common/InitialFocus'; diff --git a/packages/desktop-client/src/components/sidebar/Accounts.tsx b/packages/desktop-client/src/components/sidebar/Accounts.tsx index 62dae517333..b260c155553 100644 --- a/packages/desktop-client/src/components/sidebar/Accounts.tsx +++ b/packages/desktop-client/src/components/sidebar/Accounts.tsx @@ -1,10 +1,8 @@ import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { useDispatch, useSelector } from 'react-redux'; import { moveAccount } from 'loot-core/src/client/actions'; import * as queries from 'loot-core/src/client/queries'; -import { type State } from 'loot-core/src/client/state-types'; import { type AccountEntity } from 'loot-core/types/models'; import { useAccounts } from '../../hooks/useAccounts'; @@ -14,6 +12,7 @@ import { useLocalPref } from '../../hooks/useLocalPref'; import { useOffBudgetAccounts } from '../../hooks/useOffBudgetAccounts'; import { useOnBudgetAccounts } from '../../hooks/useOnBudgetAccounts'; import { useUpdatedAccounts } from '../../hooks/useUpdatedAccounts'; +import { useSelector, useDispatch } from '../../redux'; import { theme } from '../../style'; import { View } from '../common/View'; @@ -32,9 +31,7 @@ export function Accounts() { const offbudgetAccounts = useOffBudgetAccounts(); const onBudgetAccounts = useOnBudgetAccounts(); const closedAccounts = useClosedAccounts(); - const syncingAccountIds = useSelector( - (state: State) => state.account.accountsSyncing, - ); + const syncingAccountIds = useSelector(state => state.account.accountsSyncing); const getAccountPath = (account: AccountEntity) => `/accounts/${account.id}`; @@ -120,8 +117,8 @@ export function Accounts() { account={account} connected={!!account.bank} pending={syncingAccountIds.includes(account.id)} - failed={failedAccounts?.has(account.id)} - updated={updatedAccounts?.includes(account.id)} + failed={failedAccounts.has(account.id)} + updated={updatedAccounts.includes(account.id)} to={getAccountPath(account)} query={queries.accountBalance(account)} onDragChange={onDragChange} @@ -150,8 +147,8 @@ export function Accounts() { account={account} connected={!!account.bank} pending={syncingAccountIds.includes(account.id)} - failed={failedAccounts?.has(account.id)} - updated={updatedAccounts?.includes(account.id)} + failed={failedAccounts.has(account.id)} + updated={updatedAccounts.includes(account.id)} to={getAccountPath(account)} query={queries.accountBalance(account)} onDragChange={onDragChange} diff --git a/packages/desktop-client/src/components/sidebar/BudgetName.tsx b/packages/desktop-client/src/components/sidebar/BudgetName.tsx index 3201d976769..ca7cb9bad9a 100644 --- a/packages/desktop-client/src/components/sidebar/BudgetName.tsx +++ b/packages/desktop-client/src/components/sidebar/BudgetName.tsx @@ -1,6 +1,5 @@ import React, { type ReactNode, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { useDispatch } from 'react-redux'; import { closeBudget } from 'loot-core/src/client/actions'; import * as Platform from 'loot-core/src/client/platform'; @@ -9,6 +8,7 @@ import { useContextMenu } from '../../hooks/useContextMenu'; import { useMetadataPref } from '../../hooks/useMetadataPref'; import { useNavigate } from '../../hooks/useNavigate'; import { SvgExpandArrow } from '../../icons/v0'; +import { useDispatch } from '../../redux'; import { theme } from '../../style'; import { Button } from '../common/Button2'; import { InitialFocus } from '../common/InitialFocus'; diff --git a/packages/desktop-client/src/components/sidebar/Sidebar.tsx b/packages/desktop-client/src/components/sidebar/Sidebar.tsx index 9f9d4c4953c..d7d5394424b 100644 --- a/packages/desktop-client/src/components/sidebar/Sidebar.tsx +++ b/packages/desktop-client/src/components/sidebar/Sidebar.tsx @@ -1,6 +1,5 @@ import React, { type CSSProperties, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { useDispatch } from 'react-redux'; import { css } from '@emotion/css'; import { Resizable } from 're-resizable'; @@ -12,6 +11,7 @@ import { useGlobalPref } from '../../hooks/useGlobalPref'; import { useLocalPref } from '../../hooks/useLocalPref'; import { useResizeObserver } from '../../hooks/useResizeObserver'; import { SvgAdd } from '../../icons/v1'; +import { useDispatch } from '../../redux'; import { styles, theme } from '../../style'; import { View } from '../common/View'; import { useResponsive } from '../responsive/ResponsiveProvider'; diff --git a/packages/desktop-client/src/components/table.tsx b/packages/desktop-client/src/components/table.tsx index a253acd89cf..d62de7ed99f 100644 --- a/packages/desktop-client/src/components/table.tsx +++ b/packages/desktop-client/src/components/table.tsx @@ -4,7 +4,6 @@ import React, { useState, useCallback, useRef, - useEffect, useLayoutEffect, useImperativeHandle, useMemo, @@ -17,9 +16,9 @@ import React, { type MutableRefObject, type CSSProperties, } from 'react'; -import { useStore } from 'react-redux'; import AutoSizer from 'react-virtualized-auto-sizer'; +import { useModalState } from '../hooks/useModalState'; import { AvoidRefocusScrollProvider, useProperFocus, @@ -1229,8 +1228,8 @@ export function useTableNavigator( const containerRef = useRef(); // See `onBlur` for why we need this - const store = useStore(); - const modalStackLength = useRef(0); + const modalState = useModalState(); + const modalStackLength = useRef(modalState.modalStack.length); // onEdit is passed to children, so make sure it maintains identity const onEdit = useCallback((id: T['id'], field?: string) => { @@ -1238,10 +1237,6 @@ export function useTableNavigator( setFocusedField(id ? field : null); }, []); - useEffect(() => { - modalStackLength.current = store.getState().modals.modalStack.length; - }, []); - function flashInput() { // Force the container to be focused which suppresses the "space // pages down" behavior. If we don't do this and the user presses @@ -1396,7 +1391,7 @@ export function useTableNavigator( // modal just opened. This way the field still shows an // input, and it will be refocused when the modal closes. const prevNumModals = modalStackLength.current; - const numModals = store.getState().modals.modalStack.length; + const numModals = modalState.modalStack.length; if ( document.hasFocus() && diff --git a/packages/desktop-client/src/components/transactions/SelectedTransactionsButton.tsx b/packages/desktop-client/src/components/transactions/SelectedTransactionsButton.tsx index fa1c85836ee..f11325572a8 100644 --- a/packages/desktop-client/src/components/transactions/SelectedTransactionsButton.tsx +++ b/packages/desktop-client/src/components/transactions/SelectedTransactionsButton.tsx @@ -1,7 +1,6 @@ import { useMemo } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; import { useTranslation } from 'react-i18next'; -import { useDispatch } from 'react-redux'; import { pushModal } from 'loot-core/client/actions'; import { isPreviewId } from 'loot-core/shared/transactions'; @@ -9,6 +8,7 @@ import { validForTransfer } from 'loot-core/src/client/transfer'; import { type TransactionEntity } from 'loot-core/types/models'; import { useSelectedItems } from '../../hooks/useSelected'; +import { useDispatch } from '../../redux'; import { Menu } from '../common/Menu'; import { SelectedItemsButton } from '../table'; diff --git a/packages/desktop-client/src/components/transactions/TransactionList.jsx b/packages/desktop-client/src/components/transactions/TransactionList.jsx index 849c6daf497..89b67068750 100644 --- a/packages/desktop-client/src/components/transactions/TransactionList.jsx +++ b/packages/desktop-client/src/components/transactions/TransactionList.jsx @@ -1,5 +1,4 @@ import React, { useRef, useCallback, useLayoutEffect } from 'react'; -import { useDispatch } from 'react-redux'; import { pushModal } from 'loot-core/client/actions'; import { send } from 'loot-core/src/platform/client/fetch'; @@ -13,6 +12,7 @@ import { import { getChangedValues, applyChanges } from 'loot-core/src/shared/util'; import { useNavigate } from '../../hooks/useNavigate'; +import { useDispatch } from '../../redux'; import { theme } from '../../style'; import { TransactionTable } from './TransactionsTable'; diff --git a/packages/desktop-client/src/components/transactions/TransactionMenu.tsx b/packages/desktop-client/src/components/transactions/TransactionMenu.tsx index e2723014d8f..1d87d30a4df 100644 --- a/packages/desktop-client/src/components/transactions/TransactionMenu.tsx +++ b/packages/desktop-client/src/components/transactions/TransactionMenu.tsx @@ -1,11 +1,11 @@ import React, { type ComponentPropsWithoutRef } from 'react'; import { useTranslation } from 'react-i18next'; -import { useDispatch } from 'react-redux'; import { pushModal } from 'loot-core/client/actions'; import { isPreviewId } from 'loot-core/shared/transactions'; import { type TransactionEntity } from 'loot-core/types/models'; +import { useDispatch } from '../../redux'; import { Menu } from '../common/Menu'; type BalanceMenuProps = Omit< diff --git a/packages/desktop-client/src/components/transactions/TransactionsTable.jsx b/packages/desktop-client/src/components/transactions/TransactionsTable.jsx index adcdaa70672..2a4949a8b10 100644 --- a/packages/desktop-client/src/components/transactions/TransactionsTable.jsx +++ b/packages/desktop-client/src/components/transactions/TransactionsTable.jsx @@ -12,7 +12,6 @@ import React, { } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; import { useTranslation } from 'react-i18next'; -import { useDispatch } from 'react-redux'; import { css } from '@emotion/css'; import { @@ -60,6 +59,7 @@ import { SvgCalendar, SvgHyperlink2, } from '../../icons/v2'; +import { useDispatch } from '../../redux'; import { styles, theme } from '../../style'; import { AccountAutocomplete } from '../autocomplete/AccountAutocomplete'; import { CategoryAutocomplete } from '../autocomplete/CategoryAutocomplete'; diff --git a/packages/desktop-client/src/components/transactions/TransactionsTable.test.jsx b/packages/desktop-client/src/components/transactions/TransactionsTable.test.jsx index 5f5ed4c4170..3acca829459 100644 --- a/packages/desktop-client/src/components/transactions/TransactionsTable.test.jsx +++ b/packages/desktop-client/src/components/transactions/TransactionsTable.test.jsx @@ -12,7 +12,6 @@ import { generateAccount, generateCategoryGroups, } from 'loot-core/src/mocks'; -import { TestProvider } from 'loot-core/src/mocks/redux'; import { initServer } from 'loot-core/src/platform/client/fetch'; import { addSplitTransaction, @@ -25,6 +24,7 @@ import { integerToCurrency } from 'loot-core/src/shared/util'; import { AuthProvider } from '../../auth/AuthProvider'; import { SelectedProviderWithItems } from '../../hooks/useSelected'; import { SplitsExpandedProvider } from '../../hooks/useSplitsExpanded'; +import { TestProvider } from '../../redux/mock'; import { ResponsiveProvider } from '../responsive/ResponsiveProvider'; import { TransactionTable } from './TransactionsTable'; diff --git a/packages/desktop-client/src/components/util/GenericInput.jsx b/packages/desktop-client/src/components/util/GenericInput.jsx index f718cdcb412..3ed5d8ca7d9 100644 --- a/packages/desktop-client/src/components/util/GenericInput.jsx +++ b/packages/desktop-client/src/components/util/GenericInput.jsx @@ -1,5 +1,4 @@ import React from 'react'; -import { useSelector } from 'react-redux'; import { useReports } from 'loot-core/client/data-hooks/reports'; import { getMonthYearFormat } from 'loot-core/src/shared/months'; @@ -7,6 +6,7 @@ import { integerToAmount, amountToInteger } from 'loot-core/src/shared/util'; import { useCategories } from '../../hooks/useCategories'; import { useDateFormat } from '../../hooks/useDateFormat'; +import { useSelector } from '../../redux'; import { AccountAutocomplete } from '../autocomplete/AccountAutocomplete'; import { Autocomplete } from '../autocomplete/Autocomplete'; import { CategoryAutocomplete } from '../autocomplete/CategoryAutocomplete'; diff --git a/packages/desktop-client/src/global-events.ts b/packages/desktop-client/src/global-events.ts index 619f673b259..8d18609d4b1 100644 --- a/packages/desktop-client/src/global-events.ts +++ b/packages/desktop-client/src/global-events.ts @@ -75,7 +75,7 @@ export function handleGlobalEvents(actions: BoundActions, store: Store) { if (tagged) { Promise.all(promises).then(() => { - actions.setLastUndoState(undoState); + undo.setUndoState('undoEvent', undoState); // If a modal has been tagged, open it instead of navigating if (tagged.openModal) { diff --git a/packages/desktop-client/src/gocardless.ts b/packages/desktop-client/src/gocardless.ts index b9861626bcd..dc4d48b6332 100644 --- a/packages/desktop-client/src/gocardless.ts +++ b/packages/desktop-client/src/gocardless.ts @@ -1,10 +1,10 @@ -import { type Dispatch } from 'loot-core/client/actions/types'; +import { type AppDispatch } from 'loot-core/client/store'; import { pushModal } from 'loot-core/src/client/actions/modals'; import { send } from 'loot-core/src/platform/client/fetch'; import { type GoCardlessToken } from 'loot-core/src/types/models'; function _authorize( - dispatch: Dispatch, + dispatch: AppDispatch, upgradingAccountId: string | undefined, { onSuccess, @@ -40,7 +40,7 @@ function _authorize( } export async function authorizeBank( - dispatch: Dispatch, + dispatch: AppDispatch, { upgradingAccountId }: { upgradingAccountId?: string } = {}, ) { _authorize(dispatch, upgradingAccountId, { diff --git a/packages/desktop-client/src/hooks/useAccounts.ts b/packages/desktop-client/src/hooks/useAccounts.ts index 4c44e9cec93..06b279a0b10 100644 --- a/packages/desktop-client/src/hooks/useAccounts.ts +++ b/packages/desktop-client/src/hooks/useAccounts.ts @@ -1,14 +1,12 @@ import { useEffect } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; import { getAccounts } from 'loot-core/src/client/actions'; -import { type State } from 'loot-core/src/client/state-types'; + +import { useSelector, useDispatch } from '../redux'; export function useAccounts() { const dispatch = useDispatch(); - const accountsLoaded = useSelector( - (state: State) => state.queries.accountsLoaded, - ); + const accountsLoaded = useSelector(state => state.queries.accountsLoaded); useEffect(() => { if (!accountsLoaded) { diff --git a/packages/desktop-client/src/hooks/useActions.ts b/packages/desktop-client/src/hooks/useActions.ts index 2952eaf8a0d..f1ed715c7ee 100644 --- a/packages/desktop-client/src/hooks/useActions.ts +++ b/packages/desktop-client/src/hooks/useActions.ts @@ -1,5 +1,4 @@ import { useMemo } from 'react'; -import { useDispatch } from 'react-redux'; import { bindActionCreators } from 'redux'; import { type ThunkAction } from 'redux-thunk'; @@ -7,6 +6,8 @@ import { type ThunkAction } from 'redux-thunk'; import * as actions from 'loot-core/src/client/actions'; import type { Action, State } from 'loot-core/src/client/state-types'; +import { useDispatch } from '../redux'; + type ActionReturnType unknown> = ReturnType extends ThunkAction ? ReturnType @@ -21,7 +22,7 @@ export type BoundActions = { // https://react-redux.js.org/api/hooks#recipe-useactions /** - * @deprecated please use actions directly with `useDispatch` + * @deprecated please use actions directly with `useAppDispatch` * @see https://github.com/reduxjs/react-redux/issues/1252#issuecomment-488160930 **/ export function useActions() { diff --git a/packages/desktop-client/src/hooks/useCategories.ts b/packages/desktop-client/src/hooks/useCategories.ts index 4c85fdfaff2..6963c06f725 100644 --- a/packages/desktop-client/src/hooks/useCategories.ts +++ b/packages/desktop-client/src/hooks/useCategories.ts @@ -1,14 +1,12 @@ import { useEffect } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; import { getCategories } from 'loot-core/src/client/actions'; -import { type State } from 'loot-core/src/client/state-types'; + +import { useSelector, useDispatch } from '../redux'; export function useCategories() { const dispatch = useDispatch(); - const categoriesLoaded = useSelector( - (state: State) => state.queries.categoriesLoaded, - ); + const categoriesLoaded = useSelector(state => state.queries.categoriesLoaded); useEffect(() => { if (!categoriesLoaded) { diff --git a/packages/desktop-client/src/hooks/useFailedAccounts.ts b/packages/desktop-client/src/hooks/useFailedAccounts.ts index 86aeb89959d..86b20b948fc 100644 --- a/packages/desktop-client/src/hooks/useFailedAccounts.ts +++ b/packages/desktop-client/src/hooks/useFailedAccounts.ts @@ -1,7 +1,11 @@ -import { useSelector } from 'react-redux'; +import { useMemo } from 'react'; -import { type State } from 'loot-core/src/client/state-types'; +import { useSelector } from '../redux'; export function useFailedAccounts() { - return useSelector((state: State) => state.account.failedAccounts); + const failedAccounts = useSelector(state => state.account.failedAccounts); + return useMemo( + () => new Map(Object.entries(failedAccounts)), + [failedAccounts], + ); } diff --git a/packages/desktop-client/src/hooks/useGlobalPref.ts b/packages/desktop-client/src/hooks/useGlobalPref.ts index 4f686a2aeff..22e2c1ef806 100644 --- a/packages/desktop-client/src/hooks/useGlobalPref.ts +++ b/packages/desktop-client/src/hooks/useGlobalPref.ts @@ -1,10 +1,10 @@ import { useCallback } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; import { saveGlobalPrefs } from 'loot-core/src/client/actions'; -import { type State } from 'loot-core/src/client/state-types'; import { type GlobalPrefs } from 'loot-core/src/types/prefs'; +import { useSelector, useDispatch } from '../redux'; + type SetGlobalPrefAction = ( value: GlobalPrefs[K], ) => void; @@ -28,7 +28,7 @@ export function useGlobalPref( [prefName, dispatch, onSaveGlobalPrefs], ); const globalPref = useSelector( - (state: State) => state.prefs.global?.[prefName] as GlobalPrefs[K], + state => state.prefs.global?.[prefName] as GlobalPrefs[K], ); return [globalPref, setGlobalPref]; diff --git a/packages/desktop-client/src/hooks/useMetadataPref.ts b/packages/desktop-client/src/hooks/useMetadataPref.ts index 57d4b7902b7..7a16605996e 100644 --- a/packages/desktop-client/src/hooks/useMetadataPref.ts +++ b/packages/desktop-client/src/hooks/useMetadataPref.ts @@ -1,10 +1,10 @@ import { useCallback } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; import { savePrefs } from 'loot-core/client/actions'; -import { type State } from 'loot-core/client/state-types'; import { type MetadataPrefs } from 'loot-core/types/prefs'; +import { useSelector, useDispatch } from '../redux'; + type SetMetadataPrefAction = ( value: MetadataPrefs[K], ) => void; @@ -19,9 +19,7 @@ export function useMetadataPref( }, [prefName, dispatch], ); - const localPref = useSelector( - (state: State) => state.prefs.local?.[prefName], - ); + const localPref = useSelector(state => state.prefs.local?.[prefName]); return [localPref, setLocalPref]; } diff --git a/packages/desktop-client/src/hooks/useModalState.ts b/packages/desktop-client/src/hooks/useModalState.ts index 1810ba7fa6c..9b01288eea2 100644 --- a/packages/desktop-client/src/hooks/useModalState.ts +++ b/packages/desktop-client/src/hooks/useModalState.ts @@ -1,10 +1,10 @@ import { useCallback } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; import { popModal } from 'loot-core/client/actions'; -import { type State } from 'loot-core/client/state-types'; import { type Modal } from 'loot-core/client/state-types/modals'; +import { useSelector, useDispatch } from '../redux'; + type ModalState = { onClose: () => void; modalStack: Modal[]; @@ -14,8 +14,8 @@ type ModalState = { }; export function useModalState(): ModalState { - const modalStack = useSelector((state: State) => state.modals.modalStack); - const isHidden = useSelector((state: State) => state.modals.isHidden); + const modalStack = useSelector(state => state.modals.modalStack); + const isHidden = useSelector(state => state.modals.isHidden); const dispatch = useDispatch(); const popModalCallback = useCallback(() => { diff --git a/packages/desktop-client/src/hooks/usePayees.ts b/packages/desktop-client/src/hooks/usePayees.ts index cffa9df0673..91732a6d406 100644 --- a/packages/desktop-client/src/hooks/usePayees.ts +++ b/packages/desktop-client/src/hooks/usePayees.ts @@ -1,13 +1,13 @@ import { useEffect } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; import { getCommonPayees, getPayees } from 'loot-core/src/client/actions'; -import { type State } from 'loot-core/src/client/state-types'; + +import { useSelector, useDispatch } from '../redux'; export function useCommonPayees() { const dispatch = useDispatch(); const commonPayeesLoaded = useSelector( - (state: State) => state.queries.commonPayeesLoaded, + state => state.queries.commonPayeesLoaded, ); useEffect(() => { @@ -21,9 +21,7 @@ export function useCommonPayees() { export function usePayees() { const dispatch = useDispatch(); - const payeesLoaded = useSelector( - (state: State) => state.queries.payeesLoaded, - ); + const payeesLoaded = useSelector(state => state.queries.payeesLoaded); useEffect(() => { if (!payeesLoaded) { diff --git a/packages/desktop-client/src/hooks/useSelected.tsx b/packages/desktop-client/src/hooks/useSelected.tsx index ceab8ce5603..d26e8c96b70 100644 --- a/packages/desktop-client/src/hooks/useSelected.tsx +++ b/packages/desktop-client/src/hooks/useSelected.tsx @@ -10,9 +10,7 @@ import React, { type ReactElement, type ReactNode, } from 'react'; -import { useSelector } from 'react-redux'; -import { type State } from 'loot-core/src/client/state-types'; import { listen } from 'loot-core/src/platform/client/fetch'; import * as undo from 'loot-core/src/platform/client/undo'; import { type UndoState } from 'loot-core/src/server/undo'; @@ -208,8 +206,6 @@ export function useSelected( return () => undo.setUndoState('selectedItems', prevState); }, [state.selectedItems]); - const lastUndoState = useSelector((state: State) => state.app.lastUndoState); - useEffect(() => { function onUndo({ messages, undoTag }: UndoState) { const tagged = undo.getTaggedState(undoTag); @@ -231,8 +227,9 @@ export function useSelected( } } - if (lastUndoState && lastUndoState.current) { - onUndo(lastUndoState.current); + const lastUndoEvent = undo.getUndoState('undoEvent'); + if (lastUndoEvent) { + onUndo(lastUndoEvent); } return listen('undo-event', onUndo); diff --git a/packages/desktop-client/src/hooks/useSplitsExpanded.tsx b/packages/desktop-client/src/hooks/useSplitsExpanded.tsx index 25386d2dcec..2d178e152d1 100644 --- a/packages/desktop-client/src/hooks/useSplitsExpanded.tsx +++ b/packages/desktop-client/src/hooks/useSplitsExpanded.tsx @@ -6,13 +6,15 @@ import React, { useReducer, type Dispatch, type ReactNode, + useRef, } from 'react'; -import { useSelector, useDispatch } from 'react-redux'; -import { - type SplitMode, - type SplitState, -} from 'loot-core/client/state-types/app'; +type SplitMode = 'collapse' | 'expand'; +type SplitState = { + ids: Set; + mode: SplitMode; + transitionId: string | null; +}; type ToggleSplitAction = { type: 'toggle-split'; @@ -59,7 +61,7 @@ type SplitsStateContext = { const SplitsExpandedContext = createContext({ state: { mode: 'collapse', - ids: new Set(), + ids: new Set(), transitionId: null, }, dispatch: () => { @@ -73,10 +75,11 @@ export function useSplitsExpanded() { return useMemo( () => ({ ...data, - isExpanded: (id: string) => - data.state.mode === 'collapse' + isExpanded: (id: string) => { + return data.state.mode === 'collapse' ? !data.state.ids.has(id) - : data.state.ids.has(id), + : data.state.ids.has(id); + }, }), [data], ); @@ -91,8 +94,7 @@ export function SplitsExpandedProvider({ children, initialMode = 'expand', }: SplitsExpandedProviderProps) { - const cachedState = useSelector(state => state.app.lastSplitState); - const reduxDispatch = useDispatch(); + const previousState = useRef(null); const [state, dispatch] = useReducer( (state: SplitState, action: Actions): SplitState => { @@ -152,7 +154,7 @@ export function SplitsExpandedProvider({ return { ...state, transitionId: null }; } }, - cachedState.current || { + previousState.current || { ids: new Set(), mode: initialMode, transitionId: null, @@ -171,9 +173,9 @@ export function SplitsExpandedProvider({ useEffect(() => { // In a finished state, cache the state if (state.transitionId == null) { - reduxDispatch({ type: 'SET_LAST_SPLIT_STATE', splitState: state }); + previousState.current = state; } - }, [reduxDispatch, state]); + }, [state]); const value = useMemo(() => ({ state, dispatch }), [state, dispatch]); diff --git a/packages/desktop-client/src/hooks/useSyncServerStatus.ts b/packages/desktop-client/src/hooks/useSyncServerStatus.ts index f9a2351d3fd..d3c1c733b16 100644 --- a/packages/desktop-client/src/hooks/useSyncServerStatus.ts +++ b/packages/desktop-client/src/hooks/useSyncServerStatus.ts @@ -1,14 +1,11 @@ -import { useSelector } from 'react-redux'; - -import { type State } from 'loot-core/src/client/state-types'; - import { useServerURL } from '../components/ServerContext'; +import { useSelector } from '../redux'; type SyncServerStatus = 'offline' | 'no-server' | 'online'; export function useSyncServerStatus(): SyncServerStatus { const serverUrl = useServerURL(); - const userData = useSelector((state: State) => state.user.data); + const userData = useSelector(state => state.user.data); if (!serverUrl) { return 'no-server'; diff --git a/packages/desktop-client/src/hooks/useSyncedPref.ts b/packages/desktop-client/src/hooks/useSyncedPref.ts index 67808eeb97a..310328a836a 100644 --- a/packages/desktop-client/src/hooks/useSyncedPref.ts +++ b/packages/desktop-client/src/hooks/useSyncedPref.ts @@ -1,10 +1,10 @@ import { useCallback } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; import { saveSyncedPrefs } from 'loot-core/client/actions'; -import { type State } from 'loot-core/client/state-types'; import { type SyncedPrefs } from 'loot-core/src/types/prefs'; +import { useSelector, useDispatch } from '../redux'; + type SetSyncedPrefAction = ( value: SyncedPrefs[K], ) => void; @@ -19,7 +19,7 @@ export function useSyncedPref( }, [prefName, dispatch], ); - const pref = useSelector((state: State) => state.prefs.synced[prefName]); + const pref = useSelector(state => state.prefs.synced[prefName]); return [pref, setPref]; } diff --git a/packages/desktop-client/src/hooks/useSyncedPrefs.ts b/packages/desktop-client/src/hooks/useSyncedPrefs.ts index 57493794892..cdef01a4e9d 100644 --- a/packages/desktop-client/src/hooks/useSyncedPrefs.ts +++ b/packages/desktop-client/src/hooks/useSyncedPrefs.ts @@ -1,10 +1,10 @@ import { useCallback } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; import { saveSyncedPrefs } from 'loot-core/client/actions'; -import { type State } from 'loot-core/client/state-types'; import { type SyncedPrefs } from 'loot-core/src/types/prefs'; +import { useSelector, useDispatch } from '../redux'; + type SetSyncedPrefsAction = (value: Partial) => void; /** @deprecated: please use `useSyncedPref` (singular) */ @@ -16,7 +16,7 @@ export function useSyncedPrefs(): [SyncedPrefs, SetSyncedPrefsAction] { }, [dispatch], ); - const prefs = useSelector((state: State) => state.prefs.synced); + const prefs = useSelector(state => state.prefs.synced); return [prefs, setPrefs]; } diff --git a/packages/desktop-client/src/hooks/useTransactionBatchActions.ts b/packages/desktop-client/src/hooks/useTransactionBatchActions.ts index a49c1efbf53..ff612ac4c83 100644 --- a/packages/desktop-client/src/hooks/useTransactionBatchActions.ts +++ b/packages/desktop-client/src/hooks/useTransactionBatchActions.ts @@ -1,5 +1,3 @@ -import { useDispatch } from 'react-redux'; - import { pushModal } from 'loot-core/client/actions'; import { runQuery } from 'loot-core/client/query-helpers'; import { send } from 'loot-core/platform/client/fetch'; @@ -19,6 +17,8 @@ import { type TransactionEntity, } from 'loot-core/types/models'; +import { useDispatch } from '../redux'; + type BatchEditProps = { name: keyof TransactionEntity; ids: Array; diff --git a/packages/desktop-client/src/hooks/useUndo.ts b/packages/desktop-client/src/hooks/useUndo.ts index 0ab580f44f2..2e5f8871892 100644 --- a/packages/desktop-client/src/hooks/useUndo.ts +++ b/packages/desktop-client/src/hooks/useUndo.ts @@ -1,10 +1,10 @@ import { useCallback } from 'react'; -import { useDispatch } from 'react-redux'; import { undo, redo, addNotification } from 'loot-core/client/actions'; import { type Notification } from 'loot-core/client/state-types/notifications'; import { useResponsive } from '../components/responsive/ResponsiveProvider'; +import { useDispatch } from '../redux'; type UndoActions = { undo: () => void; diff --git a/packages/desktop-client/src/hooks/useUpdatedAccounts.ts b/packages/desktop-client/src/hooks/useUpdatedAccounts.ts index 483d22c9b5d..25a7ec729d1 100644 --- a/packages/desktop-client/src/hooks/useUpdatedAccounts.ts +++ b/packages/desktop-client/src/hooks/useUpdatedAccounts.ts @@ -1,7 +1,5 @@ -import { useSelector } from 'react-redux'; - -import { type State } from 'loot-core/src/client/state-types'; +import { useSelector } from '../redux'; export function useUpdatedAccounts() { - return useSelector((state: State) => state.queries.updatedAccounts); + return useSelector(state => state.queries.updatedAccounts); } diff --git a/packages/desktop-client/src/index.tsx b/packages/desktop-client/src/index.tsx index 296e6f07ee1..9612414cc79 100644 --- a/packages/desktop-client/src/index.tsx +++ b/packages/desktop-client/src/index.tsx @@ -10,20 +10,12 @@ import './i18n'; import React from 'react'; import { Provider } from 'react-redux'; +import { bindActionCreators } from '@reduxjs/toolkit'; import { createRoot } from 'react-dom/client'; -import { - createStore, - combineReducers, - applyMiddleware, - bindActionCreators, -} from 'redux'; -import thunk from 'redux-thunk'; import * as actions from 'loot-core/src/client/actions'; -import * as constants from 'loot-core/src/client/constants'; import { runQuery } from 'loot-core/src/client/query-helpers'; -import { reducers } from 'loot-core/src/client/reducers'; -import { initialState as initialAppState } from 'loot-core/src/client/reducers/app'; +import { store } from 'loot-core/src/client/store'; import { send } from 'loot-core/src/platform/client/fetch'; import { q } from 'loot-core/src/shared/query'; @@ -37,34 +29,6 @@ import { type BoundActions } from './hooks/useActions'; // focus outline appear from keyboard events. import 'focus-visible'; -const appReducer = combineReducers(reducers); -function rootReducer(state, action) { - if (action.type === constants.CLOSE_BUDGET) { - // Reset the state and only keep around things intentionally. This - // blows away everything else - state = { - budgets: state.budgets, - user: state.user, - prefs: { local: null, global: state.prefs.global }, - app: { - ...initialAppState, - updateInfo: state.updateInfo, - showUpdateNotification: state.showUpdateNotification, - managerHasInitialized: state.app.managerHasInitialized, - loadingText: state.app.loadingText, - }, - }; - } - - return appReducer(state, action); -} - -const compose = window['__REDUX_DEVTOOLS_EXTENSION_COMPOSE__'] || (f => f); -const store = createStore( - rootReducer, - undefined, - compose(applyMiddleware(thunk)), -); const boundActions = bindActionCreators( actions, store.dispatch, diff --git a/packages/desktop-client/src/redux/index.ts b/packages/desktop-client/src/redux/index.ts new file mode 100644 index 00000000000..5cb89bbd39b --- /dev/null +++ b/packages/desktop-client/src/redux/index.ts @@ -0,0 +1,9 @@ +import { + useDispatch as useReduxDispatch, + useSelector as useReduxSelector, +} from 'react-redux'; + +import { type AppDispatch, type RootState } from 'loot-core/client/store'; + +export const useDispatch = useReduxDispatch.withTypes(); +export const useSelector = useReduxSelector.withTypes(); diff --git a/packages/desktop-client/src/redux/mock.tsx b/packages/desktop-client/src/redux/mock.tsx new file mode 100644 index 00000000000..138259bdc2a --- /dev/null +++ b/packages/desktop-client/src/redux/mock.tsx @@ -0,0 +1,8 @@ +import React, { type ReactNode } from 'react'; +import { Provider } from 'react-redux'; + +import { mockStore } from 'loot-core/client/store/mock'; + +export function TestProvider({ children }: { children: ReactNode }) { + return {children}; +} diff --git a/packages/desktop-client/src/setupTests.js b/packages/desktop-client/src/setupTests.js index 0975add586c..a645eae5bf2 100644 --- a/packages/desktop-client/src/setupTests.js +++ b/packages/desktop-client/src/setupTests.js @@ -1,4 +1,4 @@ -import { resetStore } from 'loot-core/src/mocks/redux'; +import { resetMockStore } from 'loot-core/src/client/store/mock'; import { installPolyfills } from './polyfills'; @@ -16,7 +16,7 @@ vi.mock('react-virtualized-auto-sizer', () => ({ global.Date.now = () => 123456789; global.__resetWorld = () => { - resetStore(); + resetMockStore(); }; process.on('unhandledRejection', reason => { diff --git a/packages/loot-core/package.json b/packages/loot-core/package.json index cd8257cba33..a5b50a07bbd 100644 --- a/packages/loot-core/package.json +++ b/packages/loot-core/package.json @@ -18,6 +18,7 @@ "license": "ISC", "dependencies": { "@jlongster/sql.js": "^1.6.7", + "@reduxjs/toolkit": "^2.5.0", "@rschedule/core": "^1.5.0", "@rschedule/json-tools": "^1.5.0", "@rschedule/standard-date-adapter": "^1.5.0", @@ -49,7 +50,6 @@ "@types/jest": "^27.5.2", "@types/jlongster__sql.js": "npm:@types/sql.js@latest", "@types/pegjs": "^0.10.3", - "@types/react-redux": "^7.1.25", "@types/uuid": "^9.0.2", "@types/webpack": "^5.28.5", "@types/webpack-bundle-analyzer": "^4.6.3", diff --git a/packages/loot-core/src/client/actions/account.ts b/packages/loot-core/src/client/actions/account.ts index 579366b38b5..e7dd9b69918 100644 --- a/packages/loot-core/src/client/actions/account.ts +++ b/packages/loot-core/src/client/actions/account.ts @@ -10,10 +10,10 @@ import type { SetLastTransactionAction, UpdateNewTransactionsAction, } from '../state-types/queries'; +import { type AppDispatch, type GetRootState } from '../store'; import { addNotification } from './notifications'; import { getPayees, getAccounts } from './queries'; -import type { Dispatch, GetState } from './types'; export function setAccountsSyncing( ids: SetAccountsSyncingAction['ids'], @@ -48,7 +48,7 @@ export function markAccountSuccess( } export function unlinkAccount(id: string) { - return async (dispatch: Dispatch) => { + return async (dispatch: AppDispatch) => { await send('account-unlink', { id }); dispatch(markAccountSuccess(id)); dispatch(getAccounts()); @@ -61,7 +61,7 @@ export function linkAccount( upgradingId?: string, offBudget?: boolean, ) { - return async (dispatch: Dispatch) => { + return async (dispatch: AppDispatch) => { await send('gocardless-accounts-link', { requisitionId, account, @@ -78,7 +78,7 @@ export function linkAccountSimpleFin( upgradingId?: string, offBudget?: boolean, ) { - return async (dispatch: Dispatch) => { + return async (dispatch: AppDispatch) => { await send('simplefin-accounts-link', { externalAccount, upgradingId, @@ -139,7 +139,7 @@ function handleSyncResponse( } export function syncAccounts(id?: string) { - return async (dispatch: Dispatch, getState: GetState) => { + return async (dispatch: AppDispatch, getState: GetRootState) => { // Disallow two parallel sync operations if (getState().account.accountsSyncing.length > 0) { return false; @@ -257,7 +257,7 @@ export function parseTransactions(filepath, options) { } export function importPreviewTransactions(id: string, transactions) { - return async (dispatch: Dispatch): Promise => { + return async (dispatch: AppDispatch): Promise => { const { errors = [], updatedPreview } = await send('transactions-import', { accountId: id, transactions, @@ -278,7 +278,7 @@ export function importPreviewTransactions(id: string, transactions) { } export function importTransactions(id: string, transactions, reconcile = true) { - return async (dispatch: Dispatch): Promise => { + return async (dispatch: AppDispatch): Promise => { if (!reconcile) { await send('api/transactions-add', { accountId: id, @@ -333,7 +333,7 @@ export function markAccountRead(accountId): MarkAccountReadAction { } export function moveAccount(id, targetId) { - return async (dispatch: Dispatch) => { + return async (dispatch: AppDispatch) => { await send('account-move', { id, targetId }); dispatch(getAccounts()); dispatch(getPayees()); diff --git a/packages/loot-core/src/client/actions/app.ts b/packages/loot-core/src/client/actions/app.ts index b2a707ffb26..bc199564039 100644 --- a/packages/loot-core/src/client/actions/app.ts +++ b/packages/loot-core/src/client/actions/app.ts @@ -1,13 +1,8 @@ // @ts-strict-ignore import { send } from '../../platform/client/fetch'; import * as constants from '../constants'; -import type { - AppState, - SetAppStateAction, - SetLastUndoStateAction, -} from '../state-types/app'; - -import type { Dispatch } from './types'; +import type { AppState, SetAppStateAction } from '../state-types/app'; +import { type AppDispatch } from '../store'; export function setAppState(state: Partial): SetAppStateAction { return { @@ -17,21 +12,12 @@ export function setAppState(state: Partial): SetAppStateAction { } export function updateApp() { - return async (dispatch: Dispatch) => { + return async (dispatch: AppDispatch) => { await global.Actual.applyAppUpdate(); dispatch(setAppState({ updateInfo: null })); }; } -export function setLastUndoState( - undoState: SetLastUndoStateAction['undoState'], -): SetLastUndoStateAction { - return { - type: constants.SET_LAST_UNDO_STATE, - undoState, - }; -} - // This is only used in the fake web version where everything runs in // the browser. It's a way to send a file to the backend to be // imported into the virtual filesystem. diff --git a/packages/loot-core/src/client/actions/backups.ts b/packages/loot-core/src/client/actions/backups.ts index 92218a8f434..51e5100a7b5 100644 --- a/packages/loot-core/src/client/actions/backups.ts +++ b/packages/loot-core/src/client/actions/backups.ts @@ -1,13 +1,13 @@ // @ts-strict-ignore import { send } from '../../platform/client/fetch'; +import { type AppDispatch, type GetRootState } from '../store'; import { closeBudget, loadBudget } from './budgets'; -import type { Dispatch, GetState } from './types'; // Take in the budget id so that backups can be loaded when a budget // isn't opened export function loadBackup(budgetId, backupId) { - return async (dispatch: Dispatch, getState: GetState) => { + return async (dispatch: AppDispatch, getState: GetRootState) => { const prefs = getState().prefs.local; if (prefs && prefs.id) { await dispatch(closeBudget()); @@ -19,7 +19,7 @@ export function loadBackup(budgetId, backupId) { } export function makeBackup() { - return async (dispatch: Dispatch, getState: GetState) => { + return async (dispatch: AppDispatch, getState: GetRootState) => { const prefs = getState().prefs.local; if (prefs && prefs.id) { await send('backup-make', { id: prefs.id }); diff --git a/packages/loot-core/src/client/actions/budgets.ts b/packages/loot-core/src/client/actions/budgets.ts index 8e0a5bfebd1..4a36f1f49f4 100644 --- a/packages/loot-core/src/client/actions/budgets.ts +++ b/packages/loot-core/src/client/actions/budgets.ts @@ -5,14 +5,14 @@ import { send } from '../../platform/client/fetch'; import { getDownloadError, getSyncError } from '../../shared/errors'; import type { Handlers } from '../../types/handlers'; import * as constants from '../constants'; +import { type AppDispatch, type GetRootState } from '../store'; import { setAppState } from './app'; import { closeModal, pushModal } from './modals'; import { loadPrefs, loadGlobalPrefs } from './prefs'; -import type { Dispatch, GetState } from './types'; export function loadBudgets() { - return async (dispatch: Dispatch) => { + return async (dispatch: AppDispatch) => { const budgets = await send('get-budgets'); dispatch({ @@ -23,7 +23,7 @@ export function loadBudgets() { } export function loadRemoteFiles() { - return async (dispatch: Dispatch) => { + return async (dispatch: AppDispatch) => { const files = await send('get-remote-files'); dispatch({ @@ -34,7 +34,7 @@ export function loadRemoteFiles() { } export function loadAllFiles() { - return async (dispatch: Dispatch, getState: GetState) => { + return async (dispatch: AppDispatch, getState: GetRootState) => { const budgets = await send('get-budgets'); const files = await send('get-remote-files'); @@ -49,7 +49,7 @@ export function loadAllFiles() { } export function loadBudget(id: string, options = {}) { - return async (dispatch: Dispatch) => { + return async (dispatch: AppDispatch) => { dispatch(setAppState({ loadingText: t('Loading...') })); // Loading a budget may fail @@ -90,7 +90,7 @@ export function loadBudget(id: string, options = {}) { } export function closeBudget() { - return async (dispatch: Dispatch, getState: GetState) => { + return async (dispatch: AppDispatch, getState: GetRootState) => { const prefs = getState().prefs.local; if (prefs && prefs.id) { // This clears out all the app state so the user starts fresh @@ -107,7 +107,7 @@ export function closeBudget() { } export function closeBudgetUI() { - return async (dispatch: Dispatch, getState: GetState) => { + return async (dispatch: AppDispatch, getState: GetRootState) => { const prefs = getState().prefs.local; if (prefs && prefs.id) { dispatch({ type: constants.CLOSE_BUDGET }); @@ -116,14 +116,14 @@ export function closeBudgetUI() { } export function deleteBudget(id?: string, cloudFileId?: string) { - return async (dispatch: Dispatch) => { + return async (dispatch: AppDispatch) => { await send('delete-budget', { id, cloudFileId }); await dispatch(loadAllFiles()); }; } export function createBudget({ testMode = false, demoMode = false } = {}) { - return async (dispatch: Dispatch) => { + return async (dispatch: AppDispatch) => { dispatch( setAppState({ loadingText: @@ -180,7 +180,7 @@ export function duplicateBudget({ */ cloudSync?: boolean; }) { - return async (dispatch: Dispatch) => { + return async (dispatch: AppDispatch) => { try { dispatch( setAppState({ @@ -219,7 +219,7 @@ export function importBudget( filepath: string, type: Parameters[0]['type'], ) { - return async (dispatch: Dispatch) => { + return async (dispatch: AppDispatch) => { const { error } = await send('import-budget', { filepath, type }); if (error) { throw new Error(error); @@ -232,7 +232,7 @@ export function importBudget( } export function uploadBudget(id: string) { - return async (dispatch: Dispatch) => { + return async (dispatch: AppDispatch) => { const { error } = await send('upload-budget', { id }); if (error) { return { error }; @@ -244,21 +244,21 @@ export function uploadBudget(id: string) { } export function closeAndLoadBudget(fileId: string) { - return async (dispatch: Dispatch) => { + return async (dispatch: AppDispatch) => { await dispatch(closeBudget()); await dispatch(loadBudget(fileId)); }; } export function closeAndDownloadBudget(cloudFileId: string) { - return async (dispatch: Dispatch) => { + return async (dispatch: AppDispatch) => { await dispatch(closeBudget()); dispatch(downloadBudget(cloudFileId, { replace: true })); }; } export function downloadBudget(cloudFileId: string, { replace = false } = {}) { - return async (dispatch: Dispatch) => { + return async (dispatch: AppDispatch) => { dispatch( setAppState({ loadingText: t('Downloading...'), diff --git a/packages/loot-core/src/client/actions/prefs.ts b/packages/loot-core/src/client/actions/prefs.ts index 21cd7a5d556..a777f390466 100644 --- a/packages/loot-core/src/client/actions/prefs.ts +++ b/packages/loot-core/src/client/actions/prefs.ts @@ -6,12 +6,12 @@ import { type SyncedPrefs, } from '../../types/prefs'; import * as constants from '../constants'; +import { type AppDispatch, type GetRootState } from '../store'; import { closeModal } from './modals'; -import type { Dispatch, GetState } from './types'; export function loadPrefs() { - return async (dispatch: Dispatch, getState: GetState) => { + return async (dispatch: AppDispatch, getState: GetRootState) => { const prefs = await send('load-prefs'); // Remove any modal state if switching between budgets @@ -45,7 +45,7 @@ export function loadPrefs() { } export function savePrefs(prefs: MetadataPrefs) { - return async (dispatch: Dispatch) => { + return async (dispatch: AppDispatch) => { await send('save-prefs', prefs); dispatch({ type: constants.MERGE_LOCAL_PREFS, @@ -55,7 +55,7 @@ export function savePrefs(prefs: MetadataPrefs) { } export function loadGlobalPrefs() { - return async (dispatch: Dispatch, getState: GetState) => { + return async (dispatch: AppDispatch, getState: GetRootState) => { const globalPrefs = await send('load-global-prefs'); dispatch({ type: constants.SET_PREFS, @@ -71,7 +71,7 @@ export function saveGlobalPrefs( prefs: GlobalPrefs, onSaveGlobalPrefs?: () => void, ) { - return async (dispatch: Dispatch) => { + return async (dispatch: AppDispatch) => { await send('save-global-prefs', prefs); dispatch({ type: constants.MERGE_GLOBAL_PREFS, @@ -82,7 +82,7 @@ export function saveGlobalPrefs( } export function saveSyncedPrefs(prefs: SyncedPrefs) { - return async (dispatch: Dispatch) => { + return async (dispatch: AppDispatch) => { await Promise.all( Object.entries(prefs).map(([prefName, value]) => send('preferences/save', { diff --git a/packages/loot-core/src/client/actions/queries.ts b/packages/loot-core/src/client/actions/queries.ts index 25401608a52..fa36f4efcf2 100644 --- a/packages/loot-core/src/client/actions/queries.ts +++ b/packages/loot-core/src/client/actions/queries.ts @@ -5,13 +5,13 @@ import throttle from 'throttleit'; import { send } from '../../platform/client/fetch'; import { type AccountEntity } from '../../types/models'; import * as constants from '../constants'; +import { type AppDispatch, type GetRootState } from '../store'; import { pushModal } from './modals'; import { addNotification, addGenericErrorNotification } from './notifications'; -import type { Dispatch, GetState } from './types'; export function applyBudgetAction(month, type, args) { - return async (dispatch: Dispatch) => { + return async (dispatch: AppDispatch) => { switch (type) { case 'budget-amount': await send('budget/budget-amount', { @@ -145,7 +145,7 @@ export function applyBudgetAction(month, type, args) { } export function getCategories() { - return async (dispatch: Dispatch) => { + return async (dispatch: AppDispatch) => { const categories = await send('get-categories'); dispatch({ type: constants.LOAD_CATEGORIES, @@ -161,7 +161,7 @@ export function createCategory( isIncome: boolean, hidden: boolean, ) { - return async (dispatch: Dispatch) => { + return async (dispatch: AppDispatch) => { const id = await send('category-create', { name, groupId, @@ -174,7 +174,7 @@ export function createCategory( } export function deleteCategory(id: string, transferId?: string) { - return async (dispatch: Dispatch) => { + return async (dispatch: AppDispatch) => { const { error } = await send('category-delete', { id, transferId }); if (error) { @@ -204,28 +204,28 @@ export function deleteCategory(id: string, transferId?: string) { } export function updateCategory(category) { - return async (dispatch: Dispatch) => { + return async (dispatch: AppDispatch) => { await send('category-update', category); dispatch(getCategories()); }; } export function moveCategory(id, groupId, targetId) { - return async (dispatch: Dispatch) => { + return async (dispatch: AppDispatch) => { await send('category-move', { id, groupId, targetId }); await dispatch(getCategories()); }; } export function moveCategoryGroup(id, targetId) { - return async (dispatch: Dispatch) => { + return async (dispatch: AppDispatch) => { await send('category-group-move', { id, targetId }); await dispatch(getCategories()); }; } export function createGroup(name) { - return async (dispatch: Dispatch) => { + return async (dispatch: AppDispatch) => { const id = await send('category-group-create', { name }); dispatch(getCategories()); return id; @@ -254,7 +254,7 @@ export function deleteGroup(id, transferId?) { } export function getPayees() { - return async (dispatch: Dispatch) => { + return async (dispatch: AppDispatch) => { const payees = await send('payees-get'); dispatch({ type: constants.LOAD_PAYEES, @@ -265,7 +265,7 @@ export function getPayees() { } export function getCommonPayees() { - return async (dispatch: Dispatch) => { + return async (dispatch: AppDispatch) => { const payees = await send('common-payees-get'); dispatch({ type: constants.LOAD_COMMON_PAYEES, @@ -276,7 +276,7 @@ export function getCommonPayees() { } export function initiallyLoadPayees() { - return async (dispatch: Dispatch, getState: GetState) => { + return async (dispatch: AppDispatch, getState: GetRootState) => { if (getState().queries.payees.length === 0) { return dispatch(getPayees()); } @@ -284,7 +284,7 @@ export function initiallyLoadPayees() { } export function createPayee(name: string) { - return async (dispatch: Dispatch) => { + return async (dispatch: AppDispatch) => { const id = await send('payee-create', { name: name.trim() }); dispatch(getPayees()); return id; @@ -292,7 +292,7 @@ export function createPayee(name: string) { } export function getAccounts() { - return async (dispatch: Dispatch) => { + return async (dispatch: AppDispatch) => { const accounts = await send('accounts-get'); dispatch({ type: constants.LOAD_ACCOUNTS, accounts }); return accounts; @@ -300,14 +300,14 @@ export function getAccounts() { } export function updateAccount(account: AccountEntity) { - return async (dispatch: Dispatch) => { + return async (dispatch: AppDispatch) => { dispatch({ type: constants.UPDATE_ACCOUNT, account }); await send('account-update', account); }; } export function createAccount(name, balance, offBudget) { - return async (dispatch: Dispatch) => { + return async (dispatch: AppDispatch) => { const id = await send('account-create', { name, balance, offBudget }); await dispatch(getAccounts()); await dispatch(getPayees()); @@ -316,7 +316,7 @@ export function createAccount(name, balance, offBudget) { } export function openAccountCloseModal(accountId) { - return async (dispatch: Dispatch, getState: GetState) => { + return async (dispatch: AppDispatch, getState: GetRootState) => { const { balance, numTransactions } = await send('account-properties', { id: accountId, }); @@ -340,7 +340,7 @@ export function closeAccount( categoryId: string, forced?: boolean, ) { - return async (dispatch: Dispatch) => { + return async (dispatch: AppDispatch) => { await send('account-close', { id: accountId, transferAccountId, @@ -352,7 +352,7 @@ export function closeAccount( } export function reopenAccount(accountId) { - return async (dispatch: Dispatch) => { + return async (dispatch: AppDispatch) => { await send('account-reopen', { id: accountId }); dispatch(getAccounts()); }; diff --git a/packages/loot-core/src/client/actions/sync.ts b/packages/loot-core/src/client/actions/sync.ts index 0d55026f9eb..ca7c341aeb7 100644 --- a/packages/loot-core/src/client/actions/sync.ts +++ b/packages/loot-core/src/client/actions/sync.ts @@ -1,13 +1,13 @@ import { send } from '../../platform/client/fetch'; import { getUploadError } from '../../shared/errors'; +import { type AppDispatch, type GetRootState } from '../store'; import { syncAccounts } from './account'; import { pushModal } from './modals'; import { loadPrefs } from './prefs'; -import type { Dispatch, GetState } from './types'; export function resetSync() { - return async (dispatch: Dispatch) => { + return async (dispatch: AppDispatch) => { const { error } = await send('sync-reset'); if (error) { @@ -36,7 +36,7 @@ export function resetSync() { } export function sync() { - return async (dispatch: Dispatch, getState: GetState) => { + return async (dispatch: AppDispatch, getState: GetRootState) => { const prefs = getState().prefs.local; if (prefs && prefs.id) { const result = await send('sync'); @@ -53,7 +53,7 @@ export function sync() { } export function syncAndDownload(accountId?: string) { - return async (dispatch: Dispatch) => { + return async (dispatch: AppDispatch) => { // It is *critical* that we sync first because of transaction // reconciliation. We want to get all transactions that other // clients have already made, so that imported transactions can be diff --git a/packages/loot-core/src/client/actions/types.d.ts b/packages/loot-core/src/client/actions/types.d.ts deleted file mode 100644 index ad9e5ee358a..00000000000 --- a/packages/loot-core/src/client/actions/types.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -import type { ThunkDispatch } from 'redux-thunk'; - -import type { State, Action } from '../state-types'; - -export type Dispatch = ThunkDispatch; -export type GetState = () => State; diff --git a/packages/loot-core/src/client/actions/user.ts b/packages/loot-core/src/client/actions/user.ts index 6084b4e5487..5136f6cf6a1 100644 --- a/packages/loot-core/src/client/actions/user.ts +++ b/packages/loot-core/src/client/actions/user.ts @@ -1,12 +1,12 @@ import { send } from '../../platform/client/fetch'; import * as constants from '../constants'; +import { type AppDispatch } from '../store'; import { loadAllFiles, closeBudget } from './budgets'; import { loadGlobalPrefs } from './prefs'; -import type { Dispatch } from './types'; export function getUserData() { - return async (dispatch: Dispatch) => { + return async (dispatch: AppDispatch) => { const data = await send('subscribe-get-user'); dispatch({ @@ -18,7 +18,7 @@ export function getUserData() { } export function loggedIn() { - return async (dispatch: Dispatch) => { + return async (dispatch: AppDispatch) => { await dispatch(getUserData()); // We want to be careful about how we call loadAllFiles. It will @@ -40,7 +40,7 @@ export function loggedIn() { } export function signOut() { - return async (dispatch: Dispatch) => { + return async (dispatch: AppDispatch) => { await send('subscribe-sign-out'); dispatch(getUserData()); diff --git a/packages/loot-core/src/client/constants.ts b/packages/loot-core/src/client/constants.ts index e4f968a22e5..40406878536 100644 --- a/packages/loot-core/src/client/constants.ts +++ b/packages/loot-core/src/client/constants.ts @@ -25,8 +25,6 @@ export const ADD_NOTIFICATION = 'ADD_NOTIFICATION'; export const REMOVE_NOTIFICATION = 'REMOVE_NOTIFICATION'; export const SET_NOTIFICATION_INSET = 'SET_NOTIFICATION_INSET'; export const GET_USER_DATA = 'GET_USER_DATA'; -export const SET_LAST_UNDO_STATE = 'SET_LAST_UNDO_STATE'; -export const SET_LAST_SPLIT_STATE = 'SET_LAST_SPLIT_STATE'; export const SET_ACCOUNTS_SYNCING = 'SET_ACCOUNTS_SYNCING'; export const ACCOUNT_SYNC_STATUS = 'ACCOUNT_SYNC_STATUS'; export const SIGN_OUT = 'SIGN_OUT'; diff --git a/packages/loot-core/src/client/reducers/account.ts b/packages/loot-core/src/client/reducers/account.ts index 37387c5e52b..ef9e74121cb 100644 --- a/packages/loot-core/src/client/reducers/account.ts +++ b/packages/loot-core/src/client/reducers/account.ts @@ -2,8 +2,8 @@ import * as constants from '../constants'; import type { Action } from '../state-types'; import type { AccountState } from '../state-types/account'; -const initialState: AccountState = { - failedAccounts: new Map(), +export const initialState: AccountState = { + failedAccounts: {}, accountsSyncing: [], }; @@ -15,14 +15,14 @@ export function update(state = initialState, action: Action): AccountState { accountsSyncing: action.ids, }; case constants.ACCOUNT_SYNC_STATUS: { - const failedAccounts = new Map(state.failedAccounts); + const failedAccounts = { ...state.failedAccounts }; if (action.failed) { - failedAccounts.set(action.id, { + failedAccounts[action.id] = { type: action.errorType, code: action.errorCode, - }); + }; } else { - failedAccounts.delete(action.id); + delete failedAccounts[action.id]; } return { ...state, failedAccounts }; diff --git a/packages/loot-core/src/client/reducers/app.ts b/packages/loot-core/src/client/reducers/app.ts index 19d4f73d6bd..db1161f12c1 100644 --- a/packages/loot-core/src/client/reducers/app.ts +++ b/packages/loot-core/src/client/reducers/app.ts @@ -7,8 +7,6 @@ export const initialState: AppState = { updateInfo: null, showUpdateNotification: true, managerHasInitialized: false, - lastUndoState: { current: null }, - lastSplitState: { current: null }, }; export function update(state = initialState, action: Action): AppState { @@ -18,17 +16,6 @@ export function update(state = initialState, action: Action): AppState { ...state, ...action.state, }; - case constants.SET_LAST_UNDO_STATE: - // Intentionally mutate it. Components should never rerender - // looking at this, so we put it in a "box" like a ref. They - // only ever need to look at this on mount. - state.lastUndoState.current = action.undoState; - return state; - - case constants.SET_LAST_SPLIT_STATE: - state.lastSplitState.current = action.splitState; - return state; - default: } return state; diff --git a/packages/loot-core/src/client/reducers/budgets.ts b/packages/loot-core/src/client/reducers/budgets.ts index 70d9f535f11..fad8eb9e190 100644 --- a/packages/loot-core/src/client/reducers/budgets.ts +++ b/packages/loot-core/src/client/reducers/budgets.ts @@ -129,7 +129,7 @@ function reconcileFiles( .concat(sorted.filter(f => f.state === 'broken')); } -const initialState: BudgetsState = { +export const initialState: BudgetsState = { budgets: [], remoteFiles: null, allFiles: null, diff --git a/packages/loot-core/src/client/reducers/modals.ts b/packages/loot-core/src/client/reducers/modals.ts index d0e6840fb08..89f882f49c2 100644 --- a/packages/loot-core/src/client/reducers/modals.ts +++ b/packages/loot-core/src/client/reducers/modals.ts @@ -2,7 +2,7 @@ import * as constants from '../constants'; import type { Action } from '../state-types'; import type { ModalsState } from '../state-types/modals'; -const initialState: ModalsState = { +export const initialState: ModalsState = { modalStack: [], isHidden: false, }; diff --git a/packages/loot-core/src/client/reducers/notifications.ts b/packages/loot-core/src/client/reducers/notifications.ts index b1dd868d9b7..a42efea911a 100644 --- a/packages/loot-core/src/client/reducers/notifications.ts +++ b/packages/loot-core/src/client/reducers/notifications.ts @@ -3,7 +3,7 @@ import * as constants from '../constants'; import type { Action } from '../state-types'; import type { NotificationsState } from '../state-types/notifications'; -const initialState = { +export const initialState = { notifications: [], inset: {}, }; diff --git a/packages/loot-core/src/client/reducers/prefs.ts b/packages/loot-core/src/client/reducers/prefs.ts index 108c9eb551d..dd2c8659930 100644 --- a/packages/loot-core/src/client/reducers/prefs.ts +++ b/packages/loot-core/src/client/reducers/prefs.ts @@ -2,7 +2,7 @@ import * as constants from '../constants'; import type { Action } from '../state-types'; import type { PrefsState } from '../state-types/prefs'; -const initialState: PrefsState = { +export const initialState: PrefsState = { local: {}, global: {}, synced: {}, diff --git a/packages/loot-core/src/client/reducers/queries.ts b/packages/loot-core/src/client/reducers/queries.ts index b95214d0b02..0ef1d87107c 100644 --- a/packages/loot-core/src/client/reducers/queries.ts +++ b/packages/loot-core/src/client/reducers/queries.ts @@ -7,7 +7,7 @@ import * as constants from '../constants'; import type { Action } from '../state-types'; import type { QueriesState } from '../state-types/queries'; -const initialState: QueriesState = { +export const initialState: QueriesState = { newTransactions: [], matchedTransactions: [], lastTransaction: null, diff --git a/packages/loot-core/src/client/reducers/user.ts b/packages/loot-core/src/client/reducers/user.ts index fe715884bec..03c17460343 100644 --- a/packages/loot-core/src/client/reducers/user.ts +++ b/packages/loot-core/src/client/reducers/user.ts @@ -1,7 +1,7 @@ import * as constants from '../constants'; import type { UserActions, UserState } from '../state-types/user'; -const initialState: UserState = { +export const initialState: UserState = { data: null, }; diff --git a/packages/loot-core/src/client/state-types/account.d.ts b/packages/loot-core/src/client/state-types/account.d.ts index 5abad2a6df2..87b16a8698e 100644 --- a/packages/loot-core/src/client/state-types/account.d.ts +++ b/packages/loot-core/src/client/state-types/account.d.ts @@ -1,7 +1,7 @@ import type * as constants from '../constants'; export type AccountState = { - failedAccounts: Map; + failedAccounts: { [key: string]: { type: string; code: string } }; accountsSyncing: string[]; }; diff --git a/packages/loot-core/src/client/state-types/app.d.ts b/packages/loot-core/src/client/state-types/app.d.ts index fd4e4f34414..c0923f568cb 100644 --- a/packages/loot-core/src/client/state-types/app.d.ts +++ b/packages/loot-core/src/client/state-types/app.d.ts @@ -1,13 +1,5 @@ -import type { UndoState } from '../../server/undo'; import type * as constants from '../constants'; -export type SplitMode = 'collapse' | 'expand'; -export type SplitState = { - ids: Set; - mode: SplitMode; - transitionId: string | null; -}; - export type AppState = { loadingText: string | null; updateInfo: { @@ -17,8 +9,6 @@ export type AppState = { } | null; showUpdateNotification: boolean; managerHasInitialized: boolean; - lastUndoState: { current: UndoState | null }; - lastSplitState: { current: SplitState | null }; }; export type SetAppStateAction = { @@ -26,17 +16,4 @@ export type SetAppStateAction = { state: Partial; }; -export type SetLastUndoStateAction = { - type: typeof constants.SET_LAST_UNDO_STATE; - undoState: UndoState | null; -}; - -export type SetLastSplitStateAction = { - type: typeof constants.SET_LAST_SPLIT_STATE; - splitState: SplitState | null; -}; - -export type AppActions = - | SetAppStateAction - | SetLastUndoStateAction - | SetLastSplitStateAction; +export type AppActions = SetAppStateAction; diff --git a/packages/loot-core/src/client/state-types/index.d.ts b/packages/loot-core/src/client/state-types/index.d.ts index 382bbcf83fe..ffc435f33ab 100644 --- a/packages/loot-core/src/client/state-types/index.d.ts +++ b/packages/loot-core/src/client/state-types/index.d.ts @@ -34,8 +34,3 @@ export type State = { queries: QueriesState; user: UserState; }; - -declare module 'react-redux' { - // eslint-disable-next-line @typescript-eslint/no-empty-object-type, @typescript-eslint/consistent-type-definitions - export interface DefaultRootState extends State {} -} diff --git a/packages/loot-core/src/client/store/index.ts b/packages/loot-core/src/client/store/index.ts new file mode 100644 index 00000000000..12f1f2e4d3b --- /dev/null +++ b/packages/loot-core/src/client/store/index.ts @@ -0,0 +1,53 @@ +import { combineReducers, configureStore } from '@reduxjs/toolkit'; + +import * as constants from '../constants'; +import { reducers } from '../reducers'; +import { initialState as initialAccountState } from '../reducers/account'; +import { initialState as initialAppState } from '../reducers/app'; +import { initialState as initialBudgetsState } from '../reducers/budgets'; +import { initialState as initialModalsState } from '../reducers/modals'; +import { initialState as initialNotificationsState } from '../reducers/notifications'; +import { initialState as initialPrefsState } from '../reducers/prefs'; +import { initialState as initialQueriesState } from '../reducers/queries'; +import { initialState as initialUserState } from '../reducers/user'; + +const appReducer = combineReducers(reducers); +const rootReducer: typeof appReducer = (state, action) => { + if (action.type === constants.CLOSE_BUDGET) { + // Reset the state and only keep around things intentionally. This + // blows away everything else + state = { + account: initialAccountState, + modals: initialModalsState, + notifications: initialNotificationsState, + queries: initialQueriesState, + budgets: state?.budgets || initialBudgetsState, + user: state?.user || initialUserState, + prefs: { + local: initialPrefsState.local, + global: state?.prefs?.global || initialPrefsState.global, + synced: initialPrefsState.synced, + }, + app: { + ...initialAppState, + managerHasInitialized: state?.app?.managerHasInitialized || false, + loadingText: state?.app?.loadingText || null, + }, + }; + } + + return appReducer(state, action); +}; + +export const store = configureStore({ + reducer: rootReducer, + middleware: getDefaultMiddleware => + getDefaultMiddleware({ + // TODO: Fix this in a separate PR. Remove non-serializable states in the store. + serializableCheck: false, + }), +}); + +export type RootState = ReturnType; +export type AppDispatch = typeof store.dispatch; +export type GetRootState = typeof store.getState; diff --git a/packages/loot-core/src/client/store/mock.ts b/packages/loot-core/src/client/store/mock.ts new file mode 100644 index 00000000000..a232a9c5662 --- /dev/null +++ b/packages/loot-core/src/client/store/mock.ts @@ -0,0 +1,17 @@ +import { configureStore, combineReducers } from '@reduxjs/toolkit'; + +import { reducers } from '../reducers'; + +import { type store as realStore } from './index'; + +const appReducer = combineReducers(reducers); + +export let mockStore: typeof realStore = configureStore({ + reducer: appReducer, +}); + +export function resetMockStore() { + mockStore = configureStore({ + reducer: appReducer, + }); +} diff --git a/packages/loot-core/src/mocks/redux.tsx b/packages/loot-core/src/mocks/redux.tsx deleted file mode 100644 index 0722bbff90b..00000000000 --- a/packages/loot-core/src/mocks/redux.tsx +++ /dev/null @@ -1,21 +0,0 @@ -// @ts-strict-ignore -import React from 'react'; -import { Provider } from 'react-redux'; - -import { createStore, combineReducers, applyMiddleware } from 'redux'; -import thunk from 'redux-thunk'; - -import { reducers } from '../client/reducers'; - -const appReducer = combineReducers(reducers); -let store = null; - -export function resetStore() { - store = createStore(appReducer, undefined, applyMiddleware(thunk /* log */)); -} - -resetStore(); - -export function TestProvider({ children }) { - return {children}; -} diff --git a/packages/loot-core/src/platform/client/undo/index.d.ts b/packages/loot-core/src/platform/client/undo/index.d.ts index 18090c47d93..759474e48bc 100644 --- a/packages/loot-core/src/platform/client/undo/index.d.ts +++ b/packages/loot-core/src/platform/client/undo/index.d.ts @@ -1,14 +1,17 @@ -// eslint-disable-next-line import/no-unresolved -import { type ModalType } from '../../client/state-types/modals'; +import { OptionlessModal } from '../../../client/state-types/modals'; +import { UndoState as ServerUndoState } from '../../../server/undo'; export type UndoState = { - id?: string; - url: unknown; - openModal: ModalType; + url: string | null; + // Right now, only the payees page uses this. It's only being set to + // `manage-rules` which is an optionless modal. Do we want to also + // support modals with options? + openModal: OptionlessModal | null; selectedItems: { name: string; items: Set; } | null; + undoEvent: ServerUndoState | null; }; export function setUndoState>( diff --git a/packages/loot-core/src/platform/client/undo/index.web.ts b/packages/loot-core/src/platform/client/undo/index.web.ts index 15c3300b076..39dde9ac0c0 100644 --- a/packages/loot-core/src/platform/client/undo/index.web.ts +++ b/packages/loot-core/src/platform/client/undo/index.web.ts @@ -2,15 +2,20 @@ import { v4 as uuidv4 } from 'uuid'; import type * as T from '.'; +type UndoStateWithId = T.UndoState & { + id?: ReturnType; +}; + // List of recently used states. We don't use a true MRU structure // because our needs are simple and we also do some custom reordering. const HISTORY_SIZE = 40; -let UNDO_STATE_MRU: T.UndoState[] = []; +let UNDO_STATE_MRU: UndoStateWithId[] = []; -const currentUndoState: T.UndoState = { +const currentUndoState: UndoStateWithId = { url: null, openModal: null, selectedItems: null, + undoEvent: null, }; export const setUndoState: T.SetUndoState = function (name, value) { diff --git a/upcoming-release-notes/4000.md b/upcoming-release-notes/4000.md new file mode 100644 index 00000000000..97d68b9b6b6 --- /dev/null +++ b/upcoming-release-notes/4000.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [joel-jeremy] +--- + +Phase 1 - Migrate to modern redux toolkit APIs diff --git a/yarn.lock b/yarn.lock index 5f72026d305..5f60a120657 100644 --- a/yarn.lock +++ b/yarn.lock @@ -77,7 +77,6 @@ __metadata: "@types/react-dom": "npm:^18.2.1" "@types/react-grid-layout": "npm:^1" "@types/react-modal": "npm:^3.16.0" - "@types/react-redux": "npm:^7.1.25" "@types/uuid": "npm:^9.0.2" "@types/webpack-bundle-analyzer": "npm:^4.6.3" "@use-gesture/react": "npm:^10.3.0" @@ -114,15 +113,13 @@ __metadata: react-i18next: "npm:^14.1.2" react-markdown: "npm:^8.0.7" react-modal: "npm:3.16.1" - react-redux: "npm:7.2.9" + react-redux: "npm:^9.2.0" react-router-dom: "npm:6.21.3" react-simple-pull-to-refresh: "npm:^1.3.3" react-spring: "npm:^9.7.3" react-stately: "npm:^3.33.0" react-virtualized-auto-sizer: "npm:^1.0.21" recharts: "npm:^2.10.4" - redux: "npm:^4.2.1" - redux-thunk: "npm:^2.4.2" remark-gfm: "npm:^3.0.1" rollup-plugin-visualizer: "npm:^5.12.0" sass: "npm:^1.70.0" @@ -1461,7 +1458,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.14.8, @babel/runtime@npm:^7.15.4, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.9.2": +"@babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.14.8, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.9.2": version: 7.25.6 resolution: "@babel/runtime@npm:7.25.6" dependencies: @@ -4590,6 +4587,26 @@ __metadata: languageName: node linkType: hard +"@reduxjs/toolkit@npm:^2.5.0": + version: 2.5.0 + resolution: "@reduxjs/toolkit@npm:2.5.0" + dependencies: + immer: "npm:^10.0.3" + redux: "npm:^5.0.1" + redux-thunk: "npm:^3.1.0" + reselect: "npm:^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 + checksum: 10/b24ee7e89bd00c5f3ee5df12cbc1b6395784ff215b9bf75d2c1b211d8494af220057ced666f49c6e815b723252bc3eb704f7be4a3c261ab06ee7b776f4565d4b + languageName: node + linkType: hard + "@remix-run/router@npm:1.14.2": version: 1.14.2 resolution: "@remix-run/router@npm:1.14.2" @@ -5571,16 +5588,6 @@ __metadata: languageName: node linkType: hard -"@types/hoist-non-react-statics@npm:^3.3.0": - version: 3.3.1 - resolution: "@types/hoist-non-react-statics@npm:3.3.1" - dependencies: - "@types/react": "npm:*" - hoist-non-react-statics: "npm:^3.3.0" - checksum: 10/071e6d75a0ed9aa0e9ca2cc529a8c15bf7ac3e4a37aac279772ea6036fd0bf969b67fb627b65cfce65adeab31fec1e9e95b4dcdefeab075b580c0c7174206f63 - languageName: node - linkType: hard - "@types/http-cache-semantics@npm:*": version: 4.0.1 resolution: "@types/http-cache-semantics@npm:4.0.1" @@ -5794,18 +5801,6 @@ __metadata: languageName: node linkType: hard -"@types/react-redux@npm:^7.1.20, @types/react-redux@npm:^7.1.25": - version: 7.1.33 - resolution: "@types/react-redux@npm:7.1.33" - dependencies: - "@types/hoist-non-react-statics": "npm:^3.3.0" - "@types/react": "npm:*" - hoist-non-react-statics: "npm:^3.3.0" - redux: "npm:^4.0.0" - checksum: 10/65f4e0a3f0e532bbbe44ae6522d1fce91bfcb3bacc90904c35d3f819e77932cc489b6945988acb4a2320f6e78c57dd1c149556aa241a68efc51de15a2cd73fc0 - languageName: node - linkType: hard - "@types/react@npm:*, @types/react@npm:^18.2.0": version: 18.2.0 resolution: "@types/react@npm:18.2.0" @@ -5882,6 +5877,13 @@ __metadata: languageName: node linkType: hard +"@types/use-sync-external-store@npm:^0.0.6": + version: 0.0.6 + resolution: "@types/use-sync-external-store@npm:0.0.6" + checksum: 10/a95ce330668501ad9b1c5b7f2b14872ad201e552a0e567787b8f1588b22c7040c7c3d80f142cbb9f92d13c4ea41c46af57a20f2af4edf27f224d352abcfe4049 + languageName: node + linkType: hard + "@types/uuid@npm:^9.0.2": version: 9.0.2 resolution: "@types/uuid@npm:9.0.2" @@ -11501,7 +11503,7 @@ __metadata: languageName: node linkType: hard -"hoist-non-react-statics@npm:^3.3.0, hoist-non-react-statics@npm:^3.3.2": +"hoist-non-react-statics@npm:^3.3.2": version: 3.3.2 resolution: "hoist-non-react-statics@npm:3.3.2" dependencies: @@ -11745,6 +11747,13 @@ __metadata: languageName: node linkType: hard +"immer@npm:^10.0.3": + version: 10.1.1 + resolution: "immer@npm:10.1.1" + checksum: 10/9dacf1e8c201d69191ccd88dc5d733bafe166cd45a5a360c5d7c88f1de0dff974a94114d72b35f3106adfe587fcfb131c545856184a2247d89d735ad25589863 + languageName: node + linkType: hard + "immutable@npm:^4.0.0": version: 4.3.0 resolution: "immutable@npm:4.3.0" @@ -13806,6 +13815,7 @@ __metadata: "@actual-app/api": "npm:*" "@actual-app/crdt": "npm:*" "@jlongster/sql.js": "npm:^1.6.7" + "@reduxjs/toolkit": "npm:^2.5.0" "@rschedule/core": "npm:^1.5.0" "@rschedule/json-tools": "npm:^1.5.0" "@rschedule/standard-date-adapter": "npm:^1.5.0" @@ -13817,7 +13827,6 @@ __metadata: "@types/jest": "npm:^27.5.2" "@types/jlongster__sql.js": "npm:@types/sql.js@latest" "@types/pegjs": "npm:^0.10.3" - "@types/react-redux": "npm:^7.1.25" "@types/uuid": "npm:^9.0.2" "@types/webpack": "npm:^5.28.5" "@types/webpack-bundle-analyzer": "npm:^4.6.3" @@ -16513,24 +16522,22 @@ __metadata: languageName: node linkType: hard -"react-redux@npm:7.2.9": - version: 7.2.9 - resolution: "react-redux@npm:7.2.9" +"react-redux@npm:^9.2.0": + version: 9.2.0 + resolution: "react-redux@npm:9.2.0" dependencies: - "@babel/runtime": "npm:^7.15.4" - "@types/react-redux": "npm:^7.1.20" - hoist-non-react-statics: "npm:^3.3.2" - loose-envify: "npm:^1.4.0" - prop-types: "npm:^15.7.2" - react-is: "npm:^17.0.2" + "@types/use-sync-external-store": "npm:^0.0.6" + use-sync-external-store: "npm:^1.4.0" peerDependencies: - react: ^16.8.3 || ^17 || ^18 + "@types/react": ^18.2.25 || ^19 + react: ^18.0 || ^19 + redux: ^5.0.0 peerDependenciesMeta: - react-dom: + "@types/react": optional: true - react-native: + redux: optional: true - checksum: 10/1c3018bd2601e6d18339281867910b583dcbb3d8856403086e08c00abf0dfe467a94c0d1356bafa8cdf107bf1e2c9899a28486e4778e85c8bc4dfed2076b116f + checksum: 10/b3d2f89f469169475ab0a9f8914d54a336ac9bc6a31af6e8dcfe9901e6fe2cfd8c1a3f6ce7a2f7f3e0928a93fbab833b668804155715598b7f2ad89927d3ff50 languageName: node linkType: hard @@ -16812,16 +16819,16 @@ __metadata: languageName: node linkType: hard -"redux-thunk@npm:^2.4.2": - version: 2.4.2 - resolution: "redux-thunk@npm:2.4.2" +"redux-thunk@npm:^3.1.0": + version: 3.1.0 + resolution: "redux-thunk@npm:3.1.0" peerDependencies: - redux: ^4 - checksum: 10/9bcb1193835128ecebf1e1a1b1a37bc15e8dfbdf6b6ee1b5566dd4c8e4ca05a81175f0c6dda34ab47f87053cd13b74d9f881d59446691d7b192831852b5d7a72 + redux: ^5.0.0 + checksum: 10/38c563db5f0bbec90d2e65cc27f3c870c1b6102e0c071258734fac41cb0e51d31d894125815c2f4133b20aff231f51f028ad99bccc05a7e3249f1a5d5a959ed3 languageName: node linkType: hard -"redux@npm:^4.0.0, redux@npm:^4.2.0, redux@npm:^4.2.1": +"redux@npm:^4.2.0": version: 4.2.1 resolution: "redux@npm:4.2.1" dependencies: @@ -16830,6 +16837,13 @@ __metadata: languageName: node linkType: hard +"redux@npm:^5.0.1": + version: 5.0.1 + resolution: "redux@npm:5.0.1" + checksum: 10/a373f9ed65693ead58bea5ef61c1d6bef39da9f2706db3be6f84815f3a1283230ecd1184efb1b3daa7f807d8211b0181564ca8f336fc6ee0b1e2fa0ba06737c2 + languageName: node + linkType: hard + "reflect.getprototypeof@npm:^1.0.8": version: 1.0.8 resolution: "reflect.getprototypeof@npm:1.0.8" @@ -17018,6 +17032,13 @@ __metadata: languageName: node linkType: hard +"reselect@npm:^5.1.0": + version: 5.1.1 + resolution: "reselect@npm:5.1.1" + checksum: 10/1fdae11a39ed9c8d85a24df19517c8372ee24fefea9cce3fae9eaad8e9cefbba5a3d4940c6fe31296b6addf76e035588c55798f7e6e147e1b7c0855f119e7fa5 + languageName: node + linkType: hard + "resize-observer-polyfill@npm:^1.5.1": version: 1.5.1 resolution: "resize-observer-polyfill@npm:1.5.1" @@ -19450,6 +19471,15 @@ __metadata: languageName: node linkType: hard +"use-sync-external-store@npm:^1.4.0": + version: 1.4.0 + resolution: "use-sync-external-store@npm:1.4.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + checksum: 10/08bf581a8a2effaefc355e9d18ed025d436230f4cc973db2f593166df357cf63e47b9097b6e5089b594758bde322e1737754ad64905e030d70f8ff7ee671fd01 + languageName: node + linkType: hard + "usehooks-ts@npm:^3.0.1": version: 3.0.1 resolution: "usehooks-ts@npm:3.0.1"