From 6a7bf34dc39de0ea441675d2e43853878f562480 Mon Sep 17 00:00:00 2001 From: Joel Jeremy Marquez Date: Mon, 12 Feb 2024 07:07:44 -0800 Subject: [PATCH] Hooks for frequently made operations (#2293) * Hooks for frequently made operations * Release notes * Fix typecheck errors * Remove useGlobalPrefs * Add null checks * Fix showCleared pref * Add loaded flag for categories, accounts and payees state * Refactor to reduce unnecessary states * Fix eslint errors * Fix hooks deps * Add useEffect * Fix typecheck error * Set local and global pref hooks * Fix lint error * VRT * Fix typecheck error * Remove eager loading * Fix typecheck error * Fix typo * Fix typecheck error * Update useTheme * Typecheck errors * Typecheck error * defaultValue * Explicitly check undefined * Remove useGlobalPref and useLocalPref defaults * Fix default prefs * Default value * Fix lint error * Set default theme * Default date format in Account * Update packages/desktop-client/src/style/theme.tsx Co-authored-by: Matiss Janis Aboltins --------- Co-authored-by: Matiss Janis Aboltins --- ...with-budgeted-amounts-1-chromium-linux.png | Bin 25638 -> 24585 bytes ...with-budgeted-amounts-2-chromium-linux.png | Bin 26212 -> 24829 bytes .../desktop-client/src/components/App.tsx | 47 +-- .../src/components/BankSyncStatus.tsx | 7 +- .../src/components/FinancesApp.tsx | 41 +-- .../src/components/LoggedInUser.tsx | 7 +- .../src/components/ManageRules.tsx | 23 +- .../src/components/MobileWebMessage.tsx | 20 +- .../desktop-client/src/components/Modals.tsx | 39 +- .../src/components/Notifications.tsx | 11 +- .../src/components/PrivacyFilter.tsx | 3 +- .../src/components/ThemeSelector.tsx | 11 +- .../src/components/Titlebar.tsx | 36 +- .../src/components/UpdateNotification.tsx | 12 +- .../src/components/accounts/Account.jsx | 57 ++- .../components/accounts/AccountSyncCheck.jsx | 3 +- .../src/components/accounts/Header.jsx | 7 +- .../src/components/accounts/MobileAccount.jsx | 34 +- .../components/accounts/MobileAccounts.jsx | 37 +- .../autocomplete/AccountAutocomplete.tsx | 4 +- .../autocomplete/PayeeAutocomplete.tsx | 10 +- .../components/budget/BudgetCategories.jsx | 68 +++- .../src/components/budget/BudgetTable.jsx | 70 ++-- .../components/budget/DynamicBudgetTable.tsx | 14 +- .../src/components/budget/MobileBudget.tsx | 26 +- .../components/budget/MobileBudgetTable.jsx | 40 +- .../components/budget/MonthCountSelector.tsx | 2 +- .../src/components/budget/index.tsx | 347 +++++------------- .../budget/rollover/RolloverContext.tsx | 3 - .../src/components/filters/AppliedFilters.tsx | 2 +- .../src/components/filters/FiltersMenu.jsx | 8 +- .../src/components/modals/CloseAccount.tsx | 13 +- .../modals/ConfirmCategoryDelete.tsx | 16 +- .../src/components/modals/EditField.jsx | 12 +- .../src/components/modals/EditRule.jsx | 7 +- .../components/modals/ImportTransactions.jsx | 9 +- .../src/components/modals/LoadBackup.jsx | 13 +- .../components/modals/MergeUnusedPayees.jsx | 7 +- .../modals/SelectLinkedAccounts.jsx | 3 +- .../components/modals/SwitchBudgetType.tsx | 9 +- .../payees/ManagePayeesWithData.jsx | 3 +- .../src/components/reports/Overview.jsx | 4 +- .../components/reports/graphs/AreaGraph.tsx | 2 +- .../components/reports/graphs/BarGraph.tsx | 2 +- .../reports/graphs/CashFlowGraph.tsx | 2 +- .../reports/graphs/StackedBarGraph.tsx | 2 +- .../reports/reports/CustomReport.jsx | 30 +- .../components/reports/reports/NetWorth.jsx | 4 +- .../reports/spreadsheets/filterEmptyRows.ts | 2 +- .../spreadsheets/grouped-spreadsheet.ts | 2 +- .../src/components/rules/ScheduleValue.tsx | 9 +- .../src/components/rules/Value.tsx | 19 +- .../schedules/DiscoverSchedules.tsx | 8 +- .../components/schedules/ScheduleDetails.jsx | 12 +- .../components/schedules/SchedulesTable.tsx | 21 +- .../src/components/select/DateSelect.tsx | 14 +- .../select/RecurringSchedulePicker.jsx | 18 +- .../src/components/settings/Encryption.tsx | 9 +- .../src/components/settings/Experimental.tsx | 20 +- .../src/components/settings/Export.tsx | 12 +- .../src/components/settings/Format.tsx | 44 +-- .../src/components/settings/Global.tsx | 13 +- .../src/components/settings/Reset.tsx | 9 +- .../src/components/settings/Themes.tsx | 10 +- .../src/components/settings/index.tsx | 22 +- .../src/components/sidebar/Accounts.tsx | 86 ++--- .../src/components/sidebar/Sidebar.tsx | 208 +++++++---- .../components/sidebar/SidebarProvider.tsx | 52 +++ .../components/sidebar/SidebarWithData.tsx | 181 --------- .../src/components/sidebar/index.tsx | 77 +--- .../transactions/MobileTransaction.jsx | 16 +- .../transactions/SimpleTransactionsTable.jsx | 18 +- .../src/components/util/DisplayId.tsx | 55 ++- .../src/components/util/GenericInput.jsx | 5 +- .../desktop-client/src/hooks/useAccount.ts | 8 + .../desktop-client/src/hooks/useAccounts.ts | 20 + .../src/hooks/useBudgetedAccounts.ts | 14 + .../desktop-client/src/hooks/useCategories.ts | 23 +- .../src/hooks/useClosedAccounts.ts | 11 + .../desktop-client/src/hooks/useDateFormat.ts | 7 + .../src/hooks/useFailedAccounts.ts | 7 + .../src/hooks/useFeatureFlag.ts | 17 +- .../desktop-client/src/hooks/useGlobalPref.ts | 27 ++ .../desktop-client/src/hooks/useLocalPref.ts | 27 ++ .../desktop-client/src/hooks/useLocalPrefs.ts | 7 + .../src/hooks/useOffBudgetAccounts.ts | 14 + packages/desktop-client/src/hooks/usePayee.ts | 8 + .../desktop-client/src/hooks/usePayees.ts | 20 + .../src/hooks/usePrivacyMode.ts | 9 + .../desktop-client/src/hooks/useSelected.tsx | 7 +- .../src/hooks/useSyncServerStatus.ts | 7 +- .../src/hooks/useUpdatedAccounts.ts | 7 + packages/desktop-client/src/style/theme.tsx | 16 +- .../loot-core/src/client/actions/account.ts | 7 + .../loot-core/src/client/actions/prefs.ts | 4 +- .../src/client/data-hooks/accounts.tsx | 36 -- .../src/client/data-hooks/payees.tsx | 36 -- packages/loot-core/src/client/privacy.ts | 5 - .../loot-core/src/client/reducers/queries.ts | 6 + .../src/client/state-types/modals.d.ts | 2 +- .../src/client/state-types/queries.d.ts | 3 + packages/loot-core/src/shared/categories.ts | 106 ------ packages/loot-core/src/types/prefs.d.ts | 1 + upcoming-release-notes/2293.md | 6 + 104 files changed, 1045 insertions(+), 1492 deletions(-) create mode 100644 packages/desktop-client/src/components/sidebar/SidebarProvider.tsx delete mode 100644 packages/desktop-client/src/components/sidebar/SidebarWithData.tsx create mode 100644 packages/desktop-client/src/hooks/useAccount.ts create mode 100644 packages/desktop-client/src/hooks/useAccounts.ts create mode 100644 packages/desktop-client/src/hooks/useBudgetedAccounts.ts create mode 100644 packages/desktop-client/src/hooks/useClosedAccounts.ts create mode 100644 packages/desktop-client/src/hooks/useDateFormat.ts create mode 100644 packages/desktop-client/src/hooks/useFailedAccounts.ts create mode 100644 packages/desktop-client/src/hooks/useGlobalPref.ts create mode 100644 packages/desktop-client/src/hooks/useLocalPref.ts create mode 100644 packages/desktop-client/src/hooks/useLocalPrefs.ts create mode 100644 packages/desktop-client/src/hooks/useOffBudgetAccounts.ts create mode 100644 packages/desktop-client/src/hooks/usePayee.ts create mode 100644 packages/desktop-client/src/hooks/usePayees.ts create mode 100644 packages/desktop-client/src/hooks/usePrivacyMode.ts create mode 100644 packages/desktop-client/src/hooks/useUpdatedAccounts.ts delete mode 100644 packages/loot-core/src/client/data-hooks/accounts.tsx delete mode 100644 packages/loot-core/src/client/data-hooks/payees.tsx delete mode 100644 packages/loot-core/src/client/privacy.ts delete mode 100644 packages/loot-core/src/shared/categories.ts create mode 100644 upcoming-release-notes/2293.md diff --git a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-loads-the-budget-page-with-budgeted-amounts-1-chromium-linux.png b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-loads-the-budget-page-with-budgeted-amounts-1-chromium-linux.png index fa4ff40ee97ecb8e257683496581ccbe298620d7..e1ed15d720db70723d09bf378709c32a6c0559ee 100644 GIT binary patch literal 24585 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 E0Q$GZ!2kdN literal 25638 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 NWkq#`GI_J$e*^BQvSa`N diff --git a/packages/desktop-client/src/components/App.tsx b/packages/desktop-client/src/components/App.tsx index 696c7633ea1..8323f724721 100644 --- a/packages/desktop-client/src/components/App.tsx +++ b/packages/desktop-client/src/components/App.tsx @@ -7,17 +7,15 @@ import { } from 'react-error-boundary'; import { useSelector } from 'react-redux'; -import { type State } from 'loot-core/client/state-types'; -import { type AppState } from 'loot-core/client/state-types/app'; -import { type PrefsState } from 'loot-core/client/state-types/prefs'; import * as Platform from 'loot-core/src/client/platform'; +import { type State } from 'loot-core/src/client/state-types'; import { init as initConnection, send, } from 'loot-core/src/platform/client/fetch'; -import { type GlobalPrefs } from 'loot-core/src/types/prefs'; import { useActions } from '../hooks/useActions'; +import { useLocalPref } from '../hooks/useLocalPref'; import { installPolyfills } from '../polyfills'; import { ResponsiveProvider } from '../ResponsiveProvider'; import { styles, hasHiddenScrollbars, ThemeStyle } from '../style'; @@ -34,26 +32,13 @@ import { UpdateNotification } from './UpdateNotification'; type AppInnerProps = { budgetId: string; cloudFileId: string; - loadingText: string; - loadBudget: ( - id: string, - loadingText?: string, - options?: object, - ) => Promise; - closeBudget: () => Promise; - loadGlobalPrefs: () => Promise; }; -function AppInner({ - budgetId, - cloudFileId, - loadingText, - loadBudget, - closeBudget, - loadGlobalPrefs, -}: AppInnerProps) { +function AppInner({ budgetId, cloudFileId }: AppInnerProps) { const [initializing, setInitializing] = useState(true); const { showBoundary: showErrorBoundary } = useErrorBoundary(); + const loadingText = useSelector((state: State) => state.app.loadingText); + const { loadBudget, closeBudget, loadGlobalPrefs } = useActions(); async function init() { const socketName = await global.Actual.getServerSocket(); @@ -126,16 +111,9 @@ function ErrorFallback({ error }: FallbackProps) { } export function App() { - const budgetId = useSelector( - state => state.prefs.local && state.prefs.local.id, - ); - const cloudFileId = useSelector( - state => state.prefs.local && state.prefs.local.cloudFileId, - ); - const loadingText = useSelector( - state => state.app.loadingText, - ); - const { loadBudget, closeBudget, loadGlobalPrefs, sync } = useActions(); + const [budgetId] = useLocalPref('id'); + const [cloudFileId] = useLocalPref('cloudFileId'); + const { sync } = useActions(); const [hiddenScrollbars, setHiddenScrollbars] = useState( hasHiddenScrollbars(), ); @@ -184,14 +162,7 @@ export function App() { {process.env.REACT_APP_REVIEW_ID && !Platform.isPlaywright && ( )} - + diff --git a/packages/desktop-client/src/components/BankSyncStatus.tsx b/packages/desktop-client/src/components/BankSyncStatus.tsx index 3fde066fa4c..f49b9200749 100644 --- a/packages/desktop-client/src/components/BankSyncStatus.tsx +++ b/packages/desktop-client/src/components/BankSyncStatus.tsx @@ -2,8 +2,7 @@ import React from 'react'; import { useSelector } from 'react-redux'; import { useTransition, animated } from 'react-spring'; -import { type State } from 'loot-core/client/state-types'; -import { type AccountState } from 'loot-core/client/state-types/account'; +import { type State } from 'loot-core/src/client/state-types'; import { theme, styles } from '../style'; @@ -12,8 +11,8 @@ import { Text } from './common/Text'; import { View } from './common/View'; export function BankSyncStatus() { - const accountsSyncing = useSelector( - state => state.account.accountsSyncing, + const accountsSyncing = useSelector( + (state: State) => state.account.accountsSyncing, ); const name = accountsSyncing diff --git a/packages/desktop-client/src/components/FinancesApp.tsx b/packages/desktop-client/src/components/FinancesApp.tsx index 29c241a14da..8024afe7957 100644 --- a/packages/desktop-client/src/components/FinancesApp.tsx +++ b/packages/desktop-client/src/components/FinancesApp.tsx @@ -2,6 +2,7 @@ import React, { type ReactElement, useEffect, useMemo } from 'react'; import { DndProvider } from 'react-dnd'; import { HTML5Backend as Backend } from 'react-dnd-html5-backend'; +import { useSelector } from 'react-redux'; import { Route, Routes, @@ -13,12 +14,12 @@ import { import hotkeys from 'hotkeys-js'; -import { AccountsProvider } from 'loot-core/src/client/data-hooks/accounts'; -import { PayeesProvider } from 'loot-core/src/client/data-hooks/payees'; import { SpreadsheetProvider } from 'loot-core/src/client/SpreadsheetProvider'; +import { type State } from 'loot-core/src/client/state-types'; import { checkForUpdateNotification } from 'loot-core/src/client/update-notification'; import * as undo from 'loot-core/src/platform/client/undo'; +import { useAccounts } from '../hooks/useAccounts'; import { useActions } from '../hooks/useActions'; import { useNavigate } from '../hooks/useNavigate'; import { useResponsive } from '../ResponsiveProvider'; @@ -39,7 +40,8 @@ import { Reports } from './reports'; import { NarrowAlternate, WideComponent } from './responsive'; import { ScrollProvider } from './ScrollProvider'; import { Settings } from './settings'; -import { FloatableSidebar, SidebarProvider } from './sidebar'; +import { FloatableSidebar } from './sidebar'; +import { SidebarProvider } from './sidebar/SidebarProvider'; import { Titlebar, TitlebarProvider } from './Titlebar'; import { TransactionEdit } from './transactions/MobileTransaction'; @@ -71,18 +73,19 @@ function WideNotSupported({ children, redirectTo = '/budget' }) { return isNarrowWidth ? children : null; } -function RouterBehaviors({ getAccounts }) { +function RouterBehaviors() { const navigate = useNavigate(); + const accounts = useAccounts(); + const accountsLoaded = useSelector( + (state: State) => state.queries.accountsLoaded, + ); useEffect(() => { - // Get the accounts and check if any exist. If there are no - // accounts, we want to redirect the user to the All Accounts - // screen which will prompt them to add an account - getAccounts().then(accounts => { - if (accounts.length === 0) { - navigate('/accounts'); - } - }); - }, []); + // If there are no accounts, we want to redirect the user to + // the All Accounts screen which will prompt them to add an account + if (accountsLoaded && accounts.length === 0) { + navigate('/accounts'); + } + }, [accountsLoaded, accounts]); const location = useLocation(); const href = useHref(location); @@ -116,7 +119,7 @@ function FinancesAppWithoutContext() { return ( - + @@ -265,13 +268,9 @@ export function FinancesApp() { - - - - {app} - - - + + {app} + diff --git a/packages/desktop-client/src/components/LoggedInUser.tsx b/packages/desktop-client/src/components/LoggedInUser.tsx index bfd037ad437..f1043b764db 100644 --- a/packages/desktop-client/src/components/LoggedInUser.tsx +++ b/packages/desktop-client/src/components/LoggedInUser.tsx @@ -2,8 +2,7 @@ import React, { useState, useEffect } from 'react'; import { useSelector } from 'react-redux'; -import { type State } from 'loot-core/client/state-types'; -import { type UserState } from 'loot-core/client/state-types/user'; +import { type State } from 'loot-core/src/client/state-types'; import { useActions } from '../hooks/useActions'; import { theme, styles, type CSSProperties } from '../style'; @@ -25,9 +24,7 @@ export function LoggedInUser({ style, color, }: LoggedInUserProps) { - const userData = useSelector( - state => state.user.data, - ); + const userData = useSelector((state: State) => state.user.data); const { getUserData, signOut, closeBudget } = useActions(); const [loading, setLoading] = useState(true); const [menuOpen, setMenuOpen] = useState(false); diff --git a/packages/desktop-client/src/components/ManageRules.tsx b/packages/desktop-client/src/components/ManageRules.tsx index 38634a9899a..2459fcb44a1 100644 --- a/packages/desktop-client/src/components/ManageRules.tsx +++ b/packages/desktop-client/src/components/ManageRules.tsx @@ -7,10 +7,8 @@ import React, { type SetStateAction, type Dispatch, } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; +import { useDispatch } from 'react-redux'; -import { type State } from 'loot-core/client/state-types'; -import { type QueriesState } from 'loot-core/client/state-types/queries'; import { pushModal } from 'loot-core/src/client/actions/modals'; import { initiallyLoadPayees } from 'loot-core/src/client/actions/queries'; import { send } from 'loot-core/src/platform/client/fetch'; @@ -19,7 +17,9 @@ import { mapField, friendlyOp } from 'loot-core/src/shared/rules'; import { describeSchedule } from 'loot-core/src/shared/schedules'; import { type RuleEntity } from 'loot-core/src/types/models'; +import { useAccounts } from '../hooks/useAccounts'; import { useCategories } from '../hooks/useCategories'; +import { usePayees } from '../hooks/usePayees'; import { useSelected, SelectedProvider } from '../hooks/useSelected'; import { theme } from '../style'; @@ -105,18 +105,13 @@ function ManageRulesContent({ const { data: schedules } = SchedulesQuery.useQuery(); const { list: categories } = useCategories(); - const state = useSelector< - State, - { - payees: QueriesState['payees']; - accounts: QueriesState['accounts']; - schedules: ReturnType<(typeof SchedulesQuery)['useQuery']>; - } - >(state => ({ - payees: state.queries.payees, - accounts: state.queries.accounts, + const payees = usePayees(); + const accounts = useAccounts(); + const state = { + payees, + accounts, schedules, - })); + }; const filterData = useMemo( () => ({ ...state, diff --git a/packages/desktop-client/src/components/MobileWebMessage.tsx b/packages/desktop-client/src/components/MobileWebMessage.tsx index bc3e0c7ac93..c54da6eedf1 100644 --- a/packages/desktop-client/src/components/MobileWebMessage.tsx +++ b/packages/desktop-client/src/components/MobileWebMessage.tsx @@ -1,10 +1,6 @@ import React, { useState } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; - -import { savePrefs } from 'loot-core/src/client/actions'; -import { type State } from 'loot-core/src/client/state-types'; -import { type PrefsState } from 'loot-core/src/client/state-types/prefs'; +import { useLocalPref } from '../hooks/useLocalPref'; import { useResponsive } from '../ResponsiveProvider'; import { theme, styles } from '../style'; @@ -16,30 +12,24 @@ import { Checkbox } from './forms'; const buttonStyle = { border: 0, fontSize: 15, padding: '10px 13px' }; export function MobileWebMessage() { - const hideMobileMessagePref = useSelector< - State, - PrefsState['local']['hideMobileMessage'] - >(state => { - return (state.prefs.local && state.prefs.local.hideMobileMessage) || true; - }); + const [hideMobileMessage = true, setHideMobileMessagePref] = + useLocalPref('hideMobileMessage'); const { isNarrowWidth } = useResponsive(); const [show, setShow] = useState( isNarrowWidth && - !hideMobileMessagePref && + !hideMobileMessage && !document.cookie.match(/hideMobileMessage=true/), ); const [requestDontRemindMe, setRequestDontRemindMe] = useState(false); - const dispatch = useDispatch(); - function onTry() { setShow(false); if (requestDontRemindMe) { // remember the pref indefinitely - dispatch(savePrefs({ hideMobileMessage: true })); + setHideMobileMessagePref(true); } else { // Set a cookie for 5 minutes const d = new Date(); diff --git a/packages/desktop-client/src/components/Modals.tsx b/packages/desktop-client/src/components/Modals.tsx index 93ae5559218..56b599861d5 100644 --- a/packages/desktop-client/src/components/Modals.tsx +++ b/packages/desktop-client/src/components/Modals.tsx @@ -4,16 +4,10 @@ import { useSelector } from 'react-redux'; import { useLocation } from 'react-router-dom'; import { type State } from 'loot-core/src/client/state-types'; -import { - type ModalsState, - type PopModalAction, -} from 'loot-core/src/client/state-types/modals'; -import { type PrefsState } from 'loot-core/src/client/state-types/prefs'; -import { type QueriesState } from 'loot-core/src/client/state-types/queries'; +import { type PopModalAction } from 'loot-core/src/client/state-types/modals'; import { send } from 'loot-core/src/platform/client/fetch'; import { useActions } from '../hooks/useActions'; -import { useCategories } from '../hooks/useCategories'; import { useSyncServerStatus } from '../hooks/useSyncServerStatus'; import { CategoryGroupMenu } from './modals/CategoryGroupMenu'; @@ -56,19 +50,8 @@ export type CommonModalProps = { }; export function Modals() { - const modalStack = useSelector( - state => state.modals.modalStack, - ); - const isHidden = useSelector( - state => state.modals.isHidden, - ); - const accounts = useSelector( - state => state.queries.accounts, - ); - const { grouped: categoryGroups, list: categories } = useCategories(); - const budgetId = useSelector( - state => state.prefs.local && state.prefs.local.id, - ); + const modalStack = useSelector((state: State) => state.modals.modalStack); + const isHidden = useSelector((state: State) => state.modals.isHidden); const actions = useActions(); const location = useLocation(); @@ -118,8 +101,6 @@ export function Modals() { account={options.account} balance={options.balance} canDelete={options.canDelete} - accounts={accounts.filter(acct => acct.closed === 0)} - categoryGroups={categoryGroups} actions={actions} /> ); @@ -130,7 +111,6 @@ export function Modals() { modalProps={modalProps} externalAccounts={options.accounts} requisitionId={options.requisitionId} - localAccounts={accounts.filter(acct => acct.closed === 0)} actions={actions} syncSource={options.syncSource} /> @@ -140,15 +120,8 @@ export function Modals() { return ( c.id === options.category) - } - group={ - 'group' in options && - categoryGroups.find(g => g.id === options.group) - } - categoryGroups={categoryGroups} + category={options.category} + group={options.group} onDelete={options.onDelete} /> ); @@ -166,7 +139,7 @@ export function Modals() { return ( ( - state => state.notifications.notifications, + const notifications = useSelector( + (state: State) => state.notifications.notifications, ); return ( (state => state.prefs.local?.isPrivacyEnabled); - const { savePrefs } = useActions(); + const [isPrivacyEnabled, setPrivacyEnabledPref] = + useLocalPref('isPrivacyEnabled'); const privacyIconStyle = { width: 15, height: 15 }; @@ -132,7 +128,7 @@ function PrivacyButton({ style }: PrivacyButtonProps) { )} - {(show3Cols || !showBudgetedCol) && ( + {(show3Cols || showSpentColumn) && ( @@ -125,7 +130,7 @@ export function LoadBackup({ ) : ( actions.loadBackup(budgetId, id)} + onSelect={id => actions.loadBackup(budgetIdToLoad, id)} /> )} diff --git a/packages/desktop-client/src/components/modals/MergeUnusedPayees.jsx b/packages/desktop-client/src/components/modals/MergeUnusedPayees.jsx index 5e36e4f9d99..33ca3d70328 100644 --- a/packages/desktop-client/src/components/modals/MergeUnusedPayees.jsx +++ b/packages/desktop-client/src/components/modals/MergeUnusedPayees.jsx @@ -4,6 +4,7 @@ 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 { usePayees } from '../../hooks/usePayees'; import { theme } from '../../style'; import { Information } from '../alerts'; import { Button } from '../common/Button'; @@ -15,10 +16,8 @@ import { View } from '../common/View'; const highlightStyle = { color: theme.pageTextPositive }; export function MergeUnusedPayees({ modalProps, payeeIds, targetPayeeId }) { - const { payees: allPayees, modalStack } = useSelector(state => ({ - payees: state.queries.payees, - modalStack: state.modals.modalStack, - })); + const allPayees = usePayees(); + const modalStack = useSelector(state => state.modals.modalStack); const isEditingRule = !!modalStack.find(m => m.name === 'edit-rule'); const dispatch = useDispatch(); const [shouldCreateRule, setShouldCreateRule] = useState(true); diff --git a/packages/desktop-client/src/components/modals/SelectLinkedAccounts.jsx b/packages/desktop-client/src/components/modals/SelectLinkedAccounts.jsx index 8d8d82475bc..8f74791de53 100644 --- a/packages/desktop-client/src/components/modals/SelectLinkedAccounts.jsx +++ b/packages/desktop-client/src/components/modals/SelectLinkedAccounts.jsx @@ -1,5 +1,6 @@ import React, { useState } from 'react'; +import { useAccounts } from '../../hooks/useAccounts'; import { theme } from '../../style'; import { Autocomplete } from '../autocomplete/Autocomplete'; import { Button } from '../common/Button'; @@ -14,10 +15,10 @@ export function SelectLinkedAccounts({ modalProps, requisitionId, externalAccounts, - localAccounts, actions, syncSource, }) { + const localAccounts = useAccounts().filter(a => a.closed === 0); const [chosenAccounts, setChosenAccounts] = useState(() => { return Object.fromEntries( localAccounts diff --git a/packages/desktop-client/src/components/modals/SwitchBudgetType.tsx b/packages/desktop-client/src/components/modals/SwitchBudgetType.tsx index 80aada7a8d5..eb04d7e86fe 100644 --- a/packages/desktop-client/src/components/modals/SwitchBudgetType.tsx +++ b/packages/desktop-client/src/components/modals/SwitchBudgetType.tsx @@ -1,10 +1,7 @@ // @ts-strict-ignore import React from 'react'; -import { useSelector } from 'react-redux'; - -import { type State } from 'loot-core/src/client/state-types'; -import { type PrefsState } from 'loot-core/src/client/state-types/prefs'; +import { useLocalPref } from '../../hooks/useLocalPref'; import { Button } from '../common/Button'; import { ExternalLink } from '../common/ExternalLink'; import { Modal } from '../common/Modal'; @@ -21,9 +18,7 @@ export function SwitchBudgetType({ modalProps, onSwitch, }: SwitchBudgetTypeProps) { - const budgetType = useSelector( - state => state.prefs.local.budgetType, - ); + const [budgetType] = useLocalPref('budgetType'); return ( {() => ( diff --git a/packages/desktop-client/src/components/payees/ManagePayeesWithData.jsx b/packages/desktop-client/src/components/payees/ManagePayeesWithData.jsx index a828c5e73f2..55ea8bc78ae 100644 --- a/packages/desktop-client/src/components/payees/ManagePayeesWithData.jsx +++ b/packages/desktop-client/src/components/payees/ManagePayeesWithData.jsx @@ -6,11 +6,12 @@ import { applyChanges } from 'loot-core/src/shared/util'; import { useActions } from '../../hooks/useActions'; import { useCategories } from '../../hooks/useCategories'; +import { usePayees } from '../../hooks/usePayees'; import { ManagePayees } from './ManagePayees'; export function ManagePayeesWithData({ initialSelectedIds }) { - const initialPayees = useSelector(state => state.queries.payees); + const initialPayees = usePayees(); const lastUndoState = useSelector(state => state.app.lastUndoState); const { grouped: categoryGroups } = useCategories(); diff --git a/packages/desktop-client/src/components/reports/Overview.jsx b/packages/desktop-client/src/components/reports/Overview.jsx index e0b8163d6da..539aeda890f 100644 --- a/packages/desktop-client/src/components/reports/Overview.jsx +++ b/packages/desktop-client/src/components/reports/Overview.jsx @@ -1,8 +1,8 @@ import React from 'react'; -import { useSelector } from 'react-redux'; import { useReports } from 'loot-core/src/client/data-hooks/reports'; +import { useAccounts } from '../../hooks/useAccounts'; import { useFeatureFlag } from '../../hooks/useFeatureFlag'; import { styles } from '../../style'; import { View } from '../common/View'; @@ -18,7 +18,7 @@ export function Overview() { const customReportsFeatureFlag = useFeatureFlag('customReports'); - const accounts = useSelector(state => state.queries.accounts); + const accounts = useAccounts(); return ( state.prefs.local?.reportsViewLegend) || false; - const viewSummary = - useSelector(state => state.prefs.local?.reportsViewSummary) || false; - const viewLabels = - useSelector(state => state.prefs.local?.reportsViewLabel) || false; - const { savePrefs } = useActions(); + const [viewLegend = false, setViewLegendPref] = + useLocalPref('reportsViewLegend'); + const [viewSummary = false, setViewSummaryPref] = + useLocalPref('reportsViewSummary'); + const [viewLabels = false, setViewLabelsPref] = + useLocalPref('reportsViewLabel'); const { filters, @@ -126,8 +124,8 @@ export function CustomReport() { }, []); const balanceTypeOp = ReportOptions.balanceTypeMap.get(balanceType); - const payees = useCachedPayees(); - const accounts = useCachedAccounts(); + const payees = usePayees(); + const accounts = useAccounts(); const getGroupData = useMemo(() => { return createGroupedSpreadsheet({ @@ -235,13 +233,13 @@ export function CustomReport() { const onChangeViews = (viewType, status) => { if (viewType === 'viewLegend') { - savePrefs({ reportsViewLegend: status ?? !viewLegend }); + setViewLegendPref(status ?? !viewLegend); } if (viewType === 'viewSummary') { - savePrefs({ reportsViewSummary: !viewSummary }); + setViewSummaryPref(!viewSummary); } if (viewType === 'viewLabels') { - savePrefs({ reportsViewLabel: !viewLabels }); + setViewLabelsPref(!viewLabels); } }; diff --git a/packages/desktop-client/src/components/reports/reports/NetWorth.jsx b/packages/desktop-client/src/components/reports/reports/NetWorth.jsx index 5bdd779b24f..4dbec9a8e05 100644 --- a/packages/desktop-client/src/components/reports/reports/NetWorth.jsx +++ b/packages/desktop-client/src/components/reports/reports/NetWorth.jsx @@ -1,5 +1,4 @@ import React, { useState, useEffect, useMemo } from 'react'; -import { useSelector } from 'react-redux'; import * as d from 'date-fns'; @@ -7,6 +6,7 @@ import { send } from 'loot-core/src/platform/client/fetch'; import * as monthUtils from 'loot-core/src/shared/months'; import { integerToCurrency } from 'loot-core/src/shared/util'; +import { useAccounts } from '../../../hooks/useAccounts'; import { useFilters } from '../../../hooks/useFilters'; import { theme, styles } from '../../../style'; import { Paragraph } from '../../common/Paragraph'; @@ -20,7 +20,7 @@ import { useReport } from '../useReport'; import { fromDateRepr } from '../util'; export function NetWorth() { - const accounts = useSelector(state => state.queries.accounts); + const accounts = useAccounts(); const { filters, saved, diff --git a/packages/desktop-client/src/components/reports/spreadsheets/filterEmptyRows.ts b/packages/desktop-client/src/components/reports/spreadsheets/filterEmptyRows.ts index 45efef476f4..c0b6e8f3443 100644 --- a/packages/desktop-client/src/components/reports/spreadsheets/filterEmptyRows.ts +++ b/packages/desktop-client/src/components/reports/spreadsheets/filterEmptyRows.ts @@ -1,5 +1,5 @@ // @ts-strict-ignore -import { type GroupedEntity } from 'loot-core/types/models/reports'; +import { type GroupedEntity } from 'loot-core/src/types/models/reports'; export function filterEmptyRows( showEmpty: boolean, diff --git a/packages/desktop-client/src/components/reports/spreadsheets/grouped-spreadsheet.ts b/packages/desktop-client/src/components/reports/spreadsheets/grouped-spreadsheet.ts index 0adc9b11936..f44cddf8ca4 100644 --- a/packages/desktop-client/src/components/reports/spreadsheets/grouped-spreadsheet.ts +++ b/packages/desktop-client/src/components/reports/spreadsheets/grouped-spreadsheet.ts @@ -3,7 +3,7 @@ import { runQuery } from 'loot-core/src/client/query-helpers'; import { send } from 'loot-core/src/platform/client/fetch'; import * as monthUtils from 'loot-core/src/shared/months'; import { integerToAmount } from 'loot-core/src/shared/util'; -import { type GroupedEntity } from 'loot-core/types/models/reports'; +import { type GroupedEntity } from 'loot-core/src/types/models/reports'; import { categoryLists } from '../ReportOptions'; diff --git a/packages/desktop-client/src/components/rules/ScheduleValue.tsx b/packages/desktop-client/src/components/rules/ScheduleValue.tsx index 036d0a2ed37..fb05b0b8f2d 100644 --- a/packages/desktop-client/src/components/rules/ScheduleValue.tsx +++ b/packages/desktop-client/src/components/rules/ScheduleValue.tsx @@ -1,12 +1,11 @@ import React from 'react'; -import { useSelector } from 'react-redux'; -import { type State } from 'loot-core/client/state-types'; -import { type QueriesState } from 'loot-core/client/state-types/queries'; import { getPayeesById } from 'loot-core/src/client/reducers/queries'; import { describeSchedule } from 'loot-core/src/shared/schedules'; import { type ScheduleEntity } from 'loot-core/src/types/models'; +import { usePayees } from '../../hooks/usePayees'; + import { SchedulesQuery } from './SchedulesQuery'; import { Value } from './Value'; @@ -15,9 +14,7 @@ type ScheduleValueProps = { }; export function ScheduleValue({ value }: ScheduleValueProps) { - const payees = useSelector( - state => state.queries.payees, - ); + const payees = usePayees(); const byId = getPayeesById(payees); const { data: schedules } = SchedulesQuery.useQuery(); diff --git a/packages/desktop-client/src/components/rules/Value.tsx b/packages/desktop-client/src/components/rules/Value.tsx index fe9ccc73c2f..d8b184b39d5 100644 --- a/packages/desktop-client/src/components/rules/Value.tsx +++ b/packages/desktop-client/src/components/rules/Value.tsx @@ -1,17 +1,16 @@ // @ts-strict-ignore import React, { useState } from 'react'; -import { useSelector } from 'react-redux'; import { format as formatDate, parseISO } from 'date-fns'; -import { type State } from 'loot-core/client/state-types'; -import { type PrefsState } from 'loot-core/client/state-types/prefs'; -import { type QueriesState } from 'loot-core/client/state-types/queries'; import { getMonthYearFormat } from 'loot-core/src/shared/months'; import { getRecurringDescription } from 'loot-core/src/shared/schedules'; import { integerToCurrency } from 'loot-core/src/shared/util'; +import { useAccounts } from '../../hooks/useAccounts'; import { useCategories } from '../../hooks/useCategories'; +import { useDateFormat } from '../../hooks/useDateFormat'; +import { usePayees } from '../../hooks/usePayees'; import { type CSSProperties, theme } from '../../style'; import { LinkButton } from '../common/LinkButton'; import { Text } from '../common/Text'; @@ -36,16 +35,10 @@ export function Value({ describe = x => x.name, style, }: ValueProps) { - const dateFormat = useSelector( - state => state.prefs.local.dateFormat || 'MM/dd/yyyy', - ); - const payees = useSelector( - state => state.queries.payees, - ); + const dateFormat = useDateFormat() || 'MM/dd/yyyy'; + const payees = usePayees(); const { list: categories } = useCategories(); - const accounts = useSelector( - state => state.queries.accounts, - ); + const accounts = useAccounts(); const valueStyle = { color: theme.pageTextPositive, ...style, diff --git a/packages/desktop-client/src/components/schedules/DiscoverSchedules.tsx b/packages/desktop-client/src/components/schedules/DiscoverSchedules.tsx index 03a0aebaf00..1f62f40c8f4 100644 --- a/packages/desktop-client/src/components/schedules/DiscoverSchedules.tsx +++ b/packages/desktop-client/src/components/schedules/DiscoverSchedules.tsx @@ -1,9 +1,6 @@ // @ts-strict-ignore import React, { useState } from 'react'; -import { useSelector } from 'react-redux'; -import { type State } from 'loot-core/client/state-types'; -import { type PrefsState } from 'loot-core/client/state-types/prefs'; import { runQuery } from 'loot-core/src/client/query-helpers'; import { send } from 'loot-core/src/platform/client/fetch'; import { q } from 'loot-core/src/shared/query'; @@ -11,6 +8,7 @@ import { getRecurringDescription } from 'loot-core/src/shared/schedules'; import type { DiscoverScheduleEntity } from 'loot-core/src/types/models'; import type { BoundActions } from '../../hooks/useActions'; +import { useDateFormat } from '../../hooks/useDateFormat'; import { useSelected, useSelectedDispatch, @@ -41,9 +39,7 @@ function DiscoverSchedulesTable({ }) { const selectedItems = useSelectedItems(); const dispatchSelected = useSelectedDispatch(); - const dateFormat = useSelector( - state => state.prefs.local.dateFormat || 'MM/dd/yyyy', - ); + const dateFormat = useDateFormat() || 'MM/dd/yyyy'; function renderItem({ item }: { item: DiscoverScheduleEntity }) { const selected = selectedItems.has(item.id); diff --git a/packages/desktop-client/src/components/schedules/ScheduleDetails.jsx b/packages/desktop-client/src/components/schedules/ScheduleDetails.jsx index 85446055b92..56d20b44265 100644 --- a/packages/desktop-client/src/components/schedules/ScheduleDetails.jsx +++ b/packages/desktop-client/src/components/schedules/ScheduleDetails.jsx @@ -1,14 +1,15 @@ import React, { useEffect, useReducer } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; +import { useDispatch } from 'react-redux'; import { pushModal } from 'loot-core/src/client/actions/modals'; -import { useCachedPayees } from 'loot-core/src/client/data-hooks/payees'; import { runQuery, liveQuery } from 'loot-core/src/client/query-helpers'; import { send, sendCatch } from 'loot-core/src/platform/client/fetch'; import * as monthUtils from 'loot-core/src/shared/months'; import { q } from 'loot-core/src/shared/query'; import { extractScheduleConds } from 'loot-core/src/shared/schedules'; +import { useDateFormat } from '../../hooks/useDateFormat'; +import { usePayees } from '../../hooks/usePayees'; import { useSelected, SelectedProvider } from '../../hooks/useSelected'; import { theme } from '../../style'; import { AccountAutocomplete } from '../autocomplete/AccountAutocomplete'; @@ -70,11 +71,10 @@ function updateScheduleConditions(schedule, fields) { export function ScheduleDetails({ modalProps, actions, id, transaction }) { const adding = id == null; const fromTrans = transaction != null; - const payees = useCachedPayees({ idKey: true }); + const payees = usePayees({ idKey: true }); const globalDispatch = useDispatch(); - const dateFormat = useSelector(state => { - return state.prefs.local.dateFormat || 'MM/dd/yyyy'; - }); + const dateFormat = useDateFormat() || 'MM/dd/yyyy'; + const [state, dispatch] = useReducer( (state, action) => { switch (action.type) { diff --git a/packages/desktop-client/src/components/schedules/SchedulesTable.tsx b/packages/desktop-client/src/components/schedules/SchedulesTable.tsx index b2ec6ea021c..ba34e2df104 100644 --- a/packages/desktop-client/src/components/schedules/SchedulesTable.tsx +++ b/packages/desktop-client/src/components/schedules/SchedulesTable.tsx @@ -1,11 +1,6 @@ // @ts-strict-ignore import React, { useState, useMemo, type CSSProperties } from 'react'; -import { useSelector } from 'react-redux'; -import { type State } from 'loot-core/client/state-types'; -import { type PrefsState } from 'loot-core/client/state-types/prefs'; -import { useCachedAccounts } from 'loot-core/src/client/data-hooks/accounts'; -import { useCachedPayees } from 'loot-core/src/client/data-hooks/payees'; import { type ScheduleStatusType, type ScheduleStatuses, @@ -15,6 +10,9 @@ import { getScheduledAmount } from 'loot-core/src/shared/schedules'; import { integerToCurrency } from 'loot-core/src/shared/util'; import { type ScheduleEntity } from 'loot-core/src/types/models'; +import { useAccounts } from '../../hooks/useAccounts'; +import { useDateFormat } from '../../hooks/useDateFormat'; +import { usePayees } from '../../hooks/usePayees'; import { SvgDotsHorizontalTriple } from '../../icons/v1'; import { SvgCheck } from '../../icons/v2'; import { theme } from '../../style'; @@ -196,16 +194,11 @@ export function SchedulesTable({ onAction, tableStyle, }: SchedulesTableProps) { - const dateFormat = useSelector( - state => { - return state.prefs.local.dateFormat || 'MM/dd/yyyy'; - }, - ); - + const dateFormat = useDateFormat() || 'MM/dd/yyyy'; const [showCompleted, setShowCompleted] = useState(false); - const payees = useCachedPayees(); - const accounts = useCachedAccounts(); + const payees = usePayees(); + const accounts = useAccounts(); const filteredSchedules = useMemo(() => { if (!filter) { @@ -240,7 +233,7 @@ export function SchedulesTable({ filterIncludes(dateStr) ); }); - }, [schedules, filter, statuses]); + }, [payees, accounts, schedules, filter, statuses]); const items: SchedulesTableItem[] = useMemo(() => { const unCompletedSchedules = filteredSchedules.filter(s => !s.completed); diff --git a/packages/desktop-client/src/components/select/DateSelect.tsx b/packages/desktop-client/src/components/select/DateSelect.tsx index a6b83fc65f9..6893edc8d26 100644 --- a/packages/desktop-client/src/components/select/DateSelect.tsx +++ b/packages/desktop-client/src/components/select/DateSelect.tsx @@ -10,15 +10,12 @@ import React, { type MutableRefObject, type KeyboardEvent, } from 'react'; -import { useSelector } from 'react-redux'; import { parse, parseISO, format, subDays, addDays, isValid } from 'date-fns'; import Pikaday from 'pikaday'; import 'pikaday/css/pikaday.css'; -import { type State } from 'loot-core/client/state-types'; -import { type PrefsState } from 'loot-core/client/state-types/prefs'; import { getDayMonthFormat, getDayMonthRegex, @@ -28,6 +25,7 @@ import { } from 'loot-core/src/shared/months'; import { stringToInteger } from 'loot-core/src/shared/util'; +import { useLocalPref } from '../../hooks/useLocalPref'; import { type CSSProperties, theme } from '../../style'; import { Input, type InputProps } from '../common/Input'; import { View, type ViewProps } from '../common/View'; @@ -233,14 +231,8 @@ export function DateSelect({ const [selectedValue, setSelectedValue] = useState(value); const userSelectedValue = useRef(selectedValue); - const firstDayOfWeekIdx = useSelector< - State, - PrefsState['local']['firstDayOfWeekIdx'] - >(state => - state.prefs.local?.firstDayOfWeekIdx - ? state.prefs.local.firstDayOfWeekIdx - : '0', - ); + const [_firstDayOfWeekIdx] = useLocalPref('firstDayOfWeekIdx'); + const firstDayOfWeekIdx = _firstDayOfWeekIdx || '0'; useEffect(() => { userSelectedValue.current = value; diff --git a/packages/desktop-client/src/components/select/RecurringSchedulePicker.jsx b/packages/desktop-client/src/components/select/RecurringSchedulePicker.jsx index df10237168f..17ee82e87c2 100644 --- a/packages/desktop-client/src/components/select/RecurringSchedulePicker.jsx +++ b/packages/desktop-client/src/components/select/RecurringSchedulePicker.jsx @@ -1,10 +1,10 @@ import React, { useEffect, useReducer, useState } from 'react'; -import { useSelector } from 'react-redux'; import { sendCatch } from 'loot-core/src/platform/client/fetch'; import * as monthUtils from 'loot-core/src/shared/months'; import { getRecurringDescription } from 'loot-core/src/shared/schedules'; +import { useDateFormat } from '../../hooks/useDateFormat'; import { SvgAdd, SvgSubtract } from '../../icons/v0'; import { theme } from '../../style'; import { Button } from '../common/Button'; @@ -159,11 +159,9 @@ function reducer(state, action) { } function SchedulePreview({ previewDates }) { - const dateFormat = useSelector(state => - (state.prefs.local.dateFormat || 'MM/dd/yyyy') - .replace('MM', 'M') - .replace('dd', 'd'), - ); + const dateFormat = (useDateFormat() || 'MM/dd/yyyy') + .replace('MM', 'M') + .replace('dd', 'd'); if (!previewDates) { return null; @@ -281,9 +279,7 @@ function RecurringScheduleTooltip({ config: currentConfig, onClose, onSave }) { const skipWeekend = state.config.hasOwnProperty('skipWeekend') ? state.config.skipWeekend : false; - const dateFormat = useSelector( - state => state.prefs.local.dateFormat || 'MM/dd/yyyy', - ); + const dateFormat = useDateFormat() || 'MM/dd/yyyy'; useEffect(() => { dispatch({ @@ -481,9 +477,7 @@ function RecurringScheduleTooltip({ config: currentConfig, onClose, onSave }) { export function RecurringSchedulePicker({ value, buttonStyle, onChange }) { const { isOpen, close, getOpenEvents } = useTooltip(); - const dateFormat = useSelector( - state => state.prefs.local.dateFormat || 'MM/dd/yyyy', - ); + const dateFormat = useDateFormat() || 'MM/dd/yyyy'; function onSave(config) { onChange(config); diff --git a/packages/desktop-client/src/components/settings/Encryption.tsx b/packages/desktop-client/src/components/settings/Encryption.tsx index cfe994b13a3..97a7660ce2f 100644 --- a/packages/desktop-client/src/components/settings/Encryption.tsx +++ b/packages/desktop-client/src/components/settings/Encryption.tsx @@ -1,11 +1,8 @@ // @ts-strict-ignore import React from 'react'; -import { useSelector } from 'react-redux'; - -import { type State } from 'loot-core/client/state-types'; -import { type PrefsState } from 'loot-core/client/state-types/prefs'; import { useActions } from '../../hooks/useActions'; +import { useLocalPref } from '../../hooks/useLocalPref'; import { theme } from '../../style'; import { Button } from '../common/Button'; import { ExternalLink } from '../common/ExternalLink'; @@ -17,9 +14,7 @@ import { Setting } from './UI'; export function EncryptionSettings() { const { pushModal } = useActions(); const serverURL = useServerURL(); - const encryptKeyId = useSelector( - state => state.prefs.local.encryptKeyId, - ); + const [encryptKeyId] = useLocalPref('encryptKeyId'); const missingCryptoAPI = !(window.crypto && crypto.subtle); diff --git a/packages/desktop-client/src/components/settings/Experimental.tsx b/packages/desktop-client/src/components/settings/Experimental.tsx index fc1a942c521..15f20989fec 100644 --- a/packages/desktop-client/src/components/settings/Experimental.tsx +++ b/packages/desktop-client/src/components/settings/Experimental.tsx @@ -1,12 +1,9 @@ import { type ReactNode, useState } from 'react'; -import { useSelector } from 'react-redux'; -import { type State } from 'loot-core/src/client/state-types'; -import { type PrefsState } from 'loot-core/src/client/state-types/prefs'; import type { FeatureFlag } from 'loot-core/src/types/prefs'; -import { useActions } from '../../hooks/useActions'; import { useFeatureFlag } from '../../hooks/useFeatureFlag'; +import { useLocalPref } from '../../hooks/useLocalPref'; import { theme } from '../../style'; import { LinkButton } from '../common/LinkButton'; import { Text } from '../common/Text'; @@ -23,23 +20,20 @@ type FeatureToggleProps = { }; function FeatureToggle({ - flag, + flag: flagName, disableToggle = false, error, children, }: FeatureToggleProps) { - const { savePrefs } = useActions(); - const enabled = useFeatureFlag(flag); + const enabled = useFeatureFlag(flagName); + const [_, setFlagPref] = useLocalPref(`flags.${flagName}`); return ( ); } diff --git a/packages/desktop-client/src/components/transactions/MobileTransaction.jsx b/packages/desktop-client/src/components/transactions/MobileTransaction.jsx index bda688058a8..bbce43aee6d 100644 --- a/packages/desktop-client/src/components/transactions/MobileTransaction.jsx +++ b/packages/desktop-client/src/components/transactions/MobileTransaction.jsx @@ -46,9 +46,12 @@ import { groupById, } from 'loot-core/src/shared/util'; +import { useAccounts } from '../../hooks/useAccounts'; import { useActions } from '../../hooks/useActions'; import { useCategories } from '../../hooks/useCategories'; +import { useDateFormat } from '../../hooks/useDateFormat'; import { useNavigate } from '../../hooks/useNavigate'; +import { usePayees } from '../../hooks/usePayees'; import { useSetThemeColor } from '../../hooks/useSetThemeColor'; import { SingleActiveEditFormProvider, @@ -939,11 +942,6 @@ function TransactionEditUnconnected(props) { useSetThemeColor(theme.mobileViewTheme); useEffect(() => { - // May as well update categories / accounts when transaction ID changes - props.getCategories(); - props.getAccounts(); - props.getPayees(); - async function fetchTransaction() { // Query for the transaction based on the ID with grouped splits. // @@ -1110,12 +1108,10 @@ function TransactionEditUnconnected(props) { export const TransactionEdit = props => { const { list: categories } = useCategories(); - const payees = useSelector(state => state.queries.payees); + const payees = usePayees(); const lastTransaction = useSelector(state => state.queries.lastTransaction); - const accounts = useSelector(state => state.queries.accounts); - const dateFormat = useSelector( - state => state.prefs.local.dateFormat || 'MM/dd/yyyy', - ); + const accounts = useAccounts(); + const dateFormat = useDateFormat() || 'MM/dd/yyyy'; const actions = useActions(); return ( diff --git a/packages/desktop-client/src/components/transactions/SimpleTransactionsTable.jsx b/packages/desktop-client/src/components/transactions/SimpleTransactionsTable.jsx index ea43f6adbd6..736df89d280 100644 --- a/packages/desktop-client/src/components/transactions/SimpleTransactionsTable.jsx +++ b/packages/desktop-client/src/components/transactions/SimpleTransactionsTable.jsx @@ -1,5 +1,4 @@ -import React, { memo, useCallback, useMemo } from 'react'; -import { useSelector } from 'react-redux'; +import React, { memo, useMemo, useCallback } from 'react'; import { format as formatDate, @@ -13,8 +12,11 @@ import { } from 'loot-core/src/client/reducers/queries'; import { integerToCurrency } from 'loot-core/src/shared/util'; +import { useAccounts } from '../../hooks/useAccounts'; import { useCategories } from '../../hooks/useCategories'; -import { useSelectedDispatch, useSelectedItems } from '../../hooks/useSelected'; +import { useDateFormat } from '../../hooks/useDateFormat'; +import { usePayees } from '../../hooks/usePayees'; +import { useSelectedItems, useSelectedDispatch } from '../../hooks/useSelected'; import { SvgArrowsSynchronize } from '../../icons/v2'; import { styles, theme } from '../../style'; import { Cell, Field, Row, SelectCell, Table } from '../table'; @@ -141,13 +143,9 @@ export function SimpleTransactionsTable({ style, }) { const { grouped: categories } = useCategories(); - const { payees, accounts, dateFormat } = useSelector(state => { - return { - payees: state.queries.payees, - accounts: state.queries.accounts, - dateFormat: state.prefs.local.dateFormat || 'MM/dd/yyyy', - }; - }); + const payees = usePayees(); + const accounts = useAccounts(); + const dateFormat = useDateFormat() || 'MM/dd/yyyy'; const selectedItems = useSelectedItems(); const dispatchSelected = useSelectedDispatch(); const memoFields = useMemo(() => fields, [JSON.stringify(fields)]); diff --git a/packages/desktop-client/src/components/util/DisplayId.tsx b/packages/desktop-client/src/components/util/DisplayId.tsx index db3982cbeac..34c5f593acc 100644 --- a/packages/desktop-client/src/components/util/DisplayId.tsx +++ b/packages/desktop-client/src/components/util/DisplayId.tsx @@ -1,9 +1,8 @@ // @ts-strict-ignore import React from 'react'; -import { CachedAccounts } from 'loot-core/src/client/data-hooks/accounts'; -import { CachedPayees } from 'loot-core/src/client/data-hooks/payees'; - +import { useAccount } from '../../hooks/useAccount'; +import { usePayee } from '../../hooks/usePayee'; import { theme } from '../../style'; import { Text } from '../common/Text'; @@ -18,33 +17,33 @@ export function DisplayId({ id, noneColor = theme.pageTextSubdued, }: DisplayIdProps) { - let DataComponent; - - switch (type) { - case 'payees': - DataComponent = CachedPayees; - break; - case 'accounts': - DataComponent = CachedAccounts; - break; - default: - throw new Error('DisplayId: unknown object type: ' + type); - } + return type === 'accounts' ? ( + + ) : ( + + ); +} +function AccountDisplayId({ id, noneColor }) { + const account = useAccount(id); return ( - - {data => { - const item = data[id]; + + {account ? account.name : 'None'} + + ); +} - return ( - - {item ? item.name : 'None'} - - ); - }} - +function PayeeDisplayId({ id, noneColor }) { + const payee = usePayee(id); + return ( + + {payee ? payee.name : 'None'} + ); } diff --git a/packages/desktop-client/src/components/util/GenericInput.jsx b/packages/desktop-client/src/components/util/GenericInput.jsx index b42826e01ba..bf2e3c53d30 100644 --- a/packages/desktop-client/src/components/util/GenericInput.jsx +++ b/packages/desktop-client/src/components/util/GenericInput.jsx @@ -4,6 +4,7 @@ import { useSelector } from 'react-redux'; import { getMonthYearFormat } from 'loot-core/src/shared/months'; import { useCategories } from '../../hooks/useCategories'; +import { useDateFormat } from '../../hooks/useDateFormat'; import { AccountAutocomplete } from '../autocomplete/AccountAutocomplete'; import { Autocomplete } from '../autocomplete/Autocomplete'; import { CategoryAutocomplete } from '../autocomplete/CategoryAutocomplete'; @@ -27,9 +28,7 @@ export function GenericInput({ }) { const { grouped: categoryGroups } = useCategories(); const saved = useSelector(state => state.queries.saved); - const dateFormat = useSelector( - state => state.prefs.local.dateFormat || 'MM/dd/yyyy', - ); + const dateFormat = useDateFormat() || 'MM/dd/yyyy'; // This makes the UI more resilient in case of faulty data if (multi && !Array.isArray(value)) { diff --git a/packages/desktop-client/src/hooks/useAccount.ts b/packages/desktop-client/src/hooks/useAccount.ts new file mode 100644 index 00000000000..e3da8f35e6b --- /dev/null +++ b/packages/desktop-client/src/hooks/useAccount.ts @@ -0,0 +1,8 @@ +import { useMemo } from 'react'; + +import { useAccounts } from './useAccounts'; + +export function useAccount(id: string) { + const accounts = useAccounts(); + return useMemo(() => accounts.find(a => a.id === id), [id, accounts]); +} diff --git a/packages/desktop-client/src/hooks/useAccounts.ts b/packages/desktop-client/src/hooks/useAccounts.ts new file mode 100644 index 00000000000..4c44e9cec93 --- /dev/null +++ b/packages/desktop-client/src/hooks/useAccounts.ts @@ -0,0 +1,20 @@ +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'; + +export function useAccounts() { + const dispatch = useDispatch(); + const accountsLoaded = useSelector( + (state: State) => state.queries.accountsLoaded, + ); + + useEffect(() => { + if (!accountsLoaded) { + dispatch(getAccounts()); + } + }, []); + + return useSelector(state => state.queries.accounts); +} diff --git a/packages/desktop-client/src/hooks/useBudgetedAccounts.ts b/packages/desktop-client/src/hooks/useBudgetedAccounts.ts new file mode 100644 index 00000000000..dbd8e1f53db --- /dev/null +++ b/packages/desktop-client/src/hooks/useBudgetedAccounts.ts @@ -0,0 +1,14 @@ +import { useMemo } from 'react'; + +import { useAccounts } from './useAccounts'; + +export function useBudgetedAccounts() { + const accounts = useAccounts(); + return useMemo( + () => + accounts.filter( + account => account.closed === 0 && account.offbudget === 0, + ), + [accounts], + ); +} diff --git a/packages/desktop-client/src/hooks/useCategories.ts b/packages/desktop-client/src/hooks/useCategories.ts index 8e273091541..4c85fdfaff2 100644 --- a/packages/desktop-client/src/hooks/useCategories.ts +++ b/packages/desktop-client/src/hooks/useCategories.ts @@ -1,25 +1,20 @@ import { useEffect } from 'react'; -import { useSelector } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; -import { type State } from 'loot-core/client/state-types'; -import { type QueriesState } from 'loot-core/client/state-types/queries'; - -import { useActions } from './useActions'; +import { getCategories } from 'loot-core/src/client/actions'; +import { type State } from 'loot-core/src/client/state-types'; export function useCategories() { - const { getCategories } = useActions(); - - const categories = useSelector( - state => state.queries.categories.list, + const dispatch = useDispatch(); + const categoriesLoaded = useSelector( + (state: State) => state.queries.categoriesLoaded, ); useEffect(() => { - if (categories.length === 0) { - getCategories(); + if (!categoriesLoaded) { + dispatch(getCategories()); } }, []); - return useSelector( - state => state.queries.categories, - ); + return useSelector(state => state.queries.categories); } diff --git a/packages/desktop-client/src/hooks/useClosedAccounts.ts b/packages/desktop-client/src/hooks/useClosedAccounts.ts new file mode 100644 index 00000000000..85aa5b92040 --- /dev/null +++ b/packages/desktop-client/src/hooks/useClosedAccounts.ts @@ -0,0 +1,11 @@ +import { useMemo } from 'react'; + +import { useAccounts } from './useAccounts'; + +export function useClosedAccounts() { + const accounts = useAccounts(); + return useMemo( + () => accounts.filter(account => account.closed === 1), + [accounts], + ); +} diff --git a/packages/desktop-client/src/hooks/useDateFormat.ts b/packages/desktop-client/src/hooks/useDateFormat.ts new file mode 100644 index 00000000000..258f156e1fc --- /dev/null +++ b/packages/desktop-client/src/hooks/useDateFormat.ts @@ -0,0 +1,7 @@ +import { useSelector } from 'react-redux'; + +import { type State } from 'loot-core/src/client/state-types'; + +export function useDateFormat() { + return useSelector((state: State) => state.prefs.local?.dateFormat); +} diff --git a/packages/desktop-client/src/hooks/useFailedAccounts.ts b/packages/desktop-client/src/hooks/useFailedAccounts.ts new file mode 100644 index 00000000000..86aeb89959d --- /dev/null +++ b/packages/desktop-client/src/hooks/useFailedAccounts.ts @@ -0,0 +1,7 @@ +import { useSelector } from 'react-redux'; + +import { type State } from 'loot-core/src/client/state-types'; + +export function useFailedAccounts() { + return useSelector((state: State) => state.account.failedAccounts); +} diff --git a/packages/desktop-client/src/hooks/useFeatureFlag.ts b/packages/desktop-client/src/hooks/useFeatureFlag.ts index 40b79b84dba..5550cfed2cd 100644 --- a/packages/desktop-client/src/hooks/useFeatureFlag.ts +++ b/packages/desktop-client/src/hooks/useFeatureFlag.ts @@ -1,8 +1,7 @@ // @ts-strict-ignore import { useSelector } from 'react-redux'; -import { type State } from 'loot-core/client/state-types'; -import { type PrefsState } from 'loot-core/client/state-types/prefs'; +import { type State } from 'loot-core/src/client/state-types'; import type { FeatureFlag } from 'loot-core/src/types/prefs'; const DEFAULT_FEATURE_FLAG_STATE: Record = { @@ -15,13 +14,11 @@ const DEFAULT_FEATURE_FLAG_STATE: Record = { }; export function useFeatureFlag(name: FeatureFlag): boolean { - return useSelector( - state => { - const value = state.prefs.local[`flags.${name}`]; + return useSelector((state: State) => { + const value = state.prefs.local[`flags.${name}`]; - return value === undefined - ? DEFAULT_FEATURE_FLAG_STATE[name] || false - : value; - }, - ); + return value === undefined + ? DEFAULT_FEATURE_FLAG_STATE[name] || false + : value; + }); } diff --git a/packages/desktop-client/src/hooks/useGlobalPref.ts b/packages/desktop-client/src/hooks/useGlobalPref.ts new file mode 100644 index 00000000000..02d5773ac62 --- /dev/null +++ b/packages/desktop-client/src/hooks/useGlobalPref.ts @@ -0,0 +1,27 @@ +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'; + +type SetGlobalPrefAction = ( + value: GlobalPrefs[K], +) => void; + +export function useGlobalPref( + prefName: K, +): [GlobalPrefs[K], SetGlobalPrefAction] { + const dispatch = useDispatch(); + const setGlobalPref = useCallback>( + value => { + dispatch(saveGlobalPrefs({ [prefName]: value } as GlobalPrefs)); + }, + [prefName, dispatch], + ); + const globalPref = useSelector( + (state: State) => state.prefs.global?.[prefName] as GlobalPrefs[K], + ); + + return [globalPref, setGlobalPref]; +} diff --git a/packages/desktop-client/src/hooks/useLocalPref.ts b/packages/desktop-client/src/hooks/useLocalPref.ts new file mode 100644 index 00000000000..70a50cb5545 --- /dev/null +++ b/packages/desktop-client/src/hooks/useLocalPref.ts @@ -0,0 +1,27 @@ +import { useCallback } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; + +import { savePrefs } from 'loot-core/src/client/actions'; +import { type State } from 'loot-core/src/client/state-types'; +import { type LocalPrefs } from 'loot-core/src/types/prefs'; + +type SetLocalPrefAction = ( + value: LocalPrefs[K], +) => void; + +export function useLocalPref( + prefName: K, +): [LocalPrefs[K], SetLocalPrefAction] { + const dispatch = useDispatch(); + const setLocalPref = useCallback>( + value => { + dispatch(savePrefs({ [prefName]: value } as LocalPrefs)); + }, + [prefName, dispatch], + ); + const localPref = useSelector( + (state: State) => state.prefs.local?.[prefName] as LocalPrefs[K], + ); + + return [localPref, setLocalPref]; +} diff --git a/packages/desktop-client/src/hooks/useLocalPrefs.ts b/packages/desktop-client/src/hooks/useLocalPrefs.ts new file mode 100644 index 00000000000..870bef808e3 --- /dev/null +++ b/packages/desktop-client/src/hooks/useLocalPrefs.ts @@ -0,0 +1,7 @@ +import { useSelector } from 'react-redux'; + +import { type State } from 'loot-core/src/client/state-types'; + +export function useLocalPrefs() { + return useSelector((state: State) => state.prefs.local); +} diff --git a/packages/desktop-client/src/hooks/useOffBudgetAccounts.ts b/packages/desktop-client/src/hooks/useOffBudgetAccounts.ts new file mode 100644 index 00000000000..71a5db919bf --- /dev/null +++ b/packages/desktop-client/src/hooks/useOffBudgetAccounts.ts @@ -0,0 +1,14 @@ +import { useMemo } from 'react'; + +import { useAccounts } from './useAccounts'; + +export function useOffBudgetAccounts() { + const accounts = useAccounts(); + return useMemo( + () => + accounts.filter( + account => account.closed === 0 && account.offbudget === 1, + ), + [accounts], + ); +} diff --git a/packages/desktop-client/src/hooks/usePayee.ts b/packages/desktop-client/src/hooks/usePayee.ts new file mode 100644 index 00000000000..2606c60a875 --- /dev/null +++ b/packages/desktop-client/src/hooks/usePayee.ts @@ -0,0 +1,8 @@ +import { useMemo } from 'react'; + +import { usePayees } from './usePayees'; + +export function usePayee(id: string) { + const payees = usePayees(); + return useMemo(() => payees.find(p => p.id === id), [id, payees]); +} diff --git a/packages/desktop-client/src/hooks/usePayees.ts b/packages/desktop-client/src/hooks/usePayees.ts new file mode 100644 index 00000000000..cf51d6b9a7f --- /dev/null +++ b/packages/desktop-client/src/hooks/usePayees.ts @@ -0,0 +1,20 @@ +import { useEffect } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; + +import { getPayees } from 'loot-core/src/client/actions'; +import { type State } from 'loot-core/src/client/state-types'; + +export function usePayees() { + const dispatch = useDispatch(); + const payeesLoaded = useSelector( + (state: State) => state.queries.payeesLoaded, + ); + + useEffect(() => { + if (!payeesLoaded) { + dispatch(getPayees()); + } + }, []); + + return useSelector(state => state.queries.payees); +} diff --git a/packages/desktop-client/src/hooks/usePrivacyMode.ts b/packages/desktop-client/src/hooks/usePrivacyMode.ts new file mode 100644 index 00000000000..ffa633bc04f --- /dev/null +++ b/packages/desktop-client/src/hooks/usePrivacyMode.ts @@ -0,0 +1,9 @@ +import { useSelector } from 'react-redux'; + +import { type State } from 'loot-core/src/client/state-types'; + +export function usePrivacyMode() { + return useSelector( + (state: State) => state.prefs.local?.isPrivacyEnabled ?? false, + ); +} diff --git a/packages/desktop-client/src/hooks/useSelected.tsx b/packages/desktop-client/src/hooks/useSelected.tsx index 398350e0dd1..63a0437a815 100644 --- a/packages/desktop-client/src/hooks/useSelected.tsx +++ b/packages/desktop-client/src/hooks/useSelected.tsx @@ -12,8 +12,7 @@ import React, { } from 'react'; import { useSelector } from 'react-redux'; -import { type State } from 'loot-core/client/state-types'; -import { type AppState } from 'loot-core/client/state-types/app'; +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'; @@ -210,9 +209,7 @@ export function useSelected( return () => undo.setUndoState('selectedItems', prevState); }, [state.selectedItems]); - const lastUndoState = useSelector( - state => state.app.lastUndoState, - ); + const lastUndoState = useSelector((state: State) => state.app.lastUndoState); useEffect(() => { function onUndo({ messages, undoTag }: UndoState) { diff --git a/packages/desktop-client/src/hooks/useSyncServerStatus.ts b/packages/desktop-client/src/hooks/useSyncServerStatus.ts index 2b48a0c6f57..d788340bee9 100644 --- a/packages/desktop-client/src/hooks/useSyncServerStatus.ts +++ b/packages/desktop-client/src/hooks/useSyncServerStatus.ts @@ -1,7 +1,6 @@ import { useSelector } from 'react-redux'; -import { type State } from 'loot-core/client/state-types'; -import { type UserState } from 'loot-core/client/state-types/user'; +import { type State } from 'loot-core/src/client/state-types'; import { useServerURL } from '../components/ServerContext'; @@ -9,9 +8,7 @@ export type SyncServerStatus = 'offline' | 'no-server' | 'online'; export function useSyncServerStatus(): SyncServerStatus { const serverUrl = useServerURL(); - const userData = useSelector( - state => state.user.data, - ); + const userData = useSelector((state: State) => state.user.data); if (!serverUrl) { return 'no-server'; diff --git a/packages/desktop-client/src/hooks/useUpdatedAccounts.ts b/packages/desktop-client/src/hooks/useUpdatedAccounts.ts new file mode 100644 index 00000000000..483d22c9b5d --- /dev/null +++ b/packages/desktop-client/src/hooks/useUpdatedAccounts.ts @@ -0,0 +1,7 @@ +import { useSelector } from 'react-redux'; + +import { type State } from 'loot-core/src/client/state-types'; + +export function useUpdatedAccounts() { + return useSelector((state: State) => state.queries.updatedAccounts); +} diff --git a/packages/desktop-client/src/style/theme.tsx b/packages/desktop-client/src/style/theme.tsx index 7cd3b5751fd..fe6b2374d14 100644 --- a/packages/desktop-client/src/style/theme.tsx +++ b/packages/desktop-client/src/style/theme.tsx @@ -1,12 +1,11 @@ // @ts-strict-ignore import { useEffect, useState } from 'react'; -import { useSelector } from 'react-redux'; -import { type State } from 'loot-core/client/state-types'; -import { type PrefsState } from 'loot-core/client/state-types/prefs'; import { isNonProductionEnvironment } from 'loot-core/src/shared/environment'; import type { Theme } from 'loot-core/src/types/prefs'; +import { useGlobalPref } from '../hooks/useGlobalPref'; + import * as darkTheme from './themes/dark'; import * as developmentTheme from './themes/development'; import * as lightTheme from './themes/light'; @@ -24,16 +23,13 @@ export const themeOptions = Object.entries(themes).map( ([key, { name }]) => [key, name] as [Theme, string], ); -export function useTheme(): Theme { - return ( - useSelector( - state => state.prefs.global?.theme, - ) || 'light' - ); +export function useTheme() { + const [theme = 'light', setThemePref] = useGlobalPref('theme'); + return [theme, setThemePref] as const; } export function ThemeStyle() { - const theme = useTheme(); + const [theme] = useTheme(); const [themeColors, setThemeColors] = useState< typeof lightTheme | typeof darkTheme | undefined >(undefined); diff --git a/packages/loot-core/src/client/actions/account.ts b/packages/loot-core/src/client/actions/account.ts index c8349d84c57..e7f7f269b4b 100644 --- a/packages/loot-core/src/client/actions/account.ts +++ b/packages/loot-core/src/client/actions/account.ts @@ -263,3 +263,10 @@ export function markAccountRead(accountId): MarkAccountReadAction { accountId, }; } + +export function moveAccount(id, targetId) { + return async (dispatch: Dispatch) => { + await send('account-move', { id, targetId }); + dispatch(getAccounts()); + }; +} diff --git a/packages/loot-core/src/client/actions/prefs.ts b/packages/loot-core/src/client/actions/prefs.ts index b93a38eb389..5dbca103f50 100644 --- a/packages/loot-core/src/client/actions/prefs.ts +++ b/packages/loot-core/src/client/actions/prefs.ts @@ -26,7 +26,7 @@ export function loadPrefs() { }; } -export function savePrefs(prefs: Partial) { +export function savePrefs(prefs: prefs.LocalPrefs) { return async (dispatch: Dispatch) => { await send('save-prefs', prefs); dispatch({ @@ -48,7 +48,7 @@ export function loadGlobalPrefs() { }; } -export function saveGlobalPrefs(prefs: Partial) { +export function saveGlobalPrefs(prefs: prefs.GlobalPrefs) { return async (dispatch: Dispatch) => { await send('save-global-prefs', prefs); dispatch({ diff --git a/packages/loot-core/src/client/data-hooks/accounts.tsx b/packages/loot-core/src/client/data-hooks/accounts.tsx deleted file mode 100644 index c2460477fa6..00000000000 --- a/packages/loot-core/src/client/data-hooks/accounts.tsx +++ /dev/null @@ -1,36 +0,0 @@ -// @ts-strict-ignore -import React, { createContext, useContext } from 'react'; - -import { q } from '../../shared/query'; -import { type AccountEntity } from '../../types/models'; -import { useLiveQuery } from '../query-hooks'; -import { getAccountsById } from '../reducers/queries'; - -function useAccounts(): AccountEntity[] { - return useLiveQuery(() => q('accounts').select('*'), []); -} - -const AccountsContext = createContext(null); - -export function AccountsProvider({ children }) { - const data = useAccounts(); - return ( - {children} - ); -} - -export function CachedAccounts({ children, idKey }) { - const data = useCachedAccounts({ idKey }); - return children(data); -} - -export function useCachedAccounts(): AccountEntity[]; -export function useCachedAccounts({ - idKey, -}: { - idKey: boolean; -}): Record; -export function useCachedAccounts({ idKey }: { idKey?: boolean } = {}) { - const data = useContext(AccountsContext); - return idKey && data ? getAccountsById(data) : data; -} diff --git a/packages/loot-core/src/client/data-hooks/payees.tsx b/packages/loot-core/src/client/data-hooks/payees.tsx deleted file mode 100644 index 4299aa9a31f..00000000000 --- a/packages/loot-core/src/client/data-hooks/payees.tsx +++ /dev/null @@ -1,36 +0,0 @@ -// @ts-strict-ignore -import React, { createContext, useContext } from 'react'; - -import { q } from '../../shared/query'; -import { type PayeeEntity } from '../../types/models'; -import { useLiveQuery } from '../query-hooks'; -import { getPayeesById } from '../reducers/queries'; - -function usePayees(): PayeeEntity[] { - return useLiveQuery(() => q('payees').select('*'), []); -} - -const PayeesContext = createContext(null); - -export function PayeesProvider({ children }) { - const data = usePayees(); - return ( - {children} - ); -} - -export function CachedPayees({ children, idKey }) { - const data = useCachedPayees({ idKey }); - return children(data); -} - -export function useCachedPayees(): PayeeEntity[]; -export function useCachedPayees({ - idKey, -}: { - idKey: boolean; -}): Record; -export function useCachedPayees({ idKey }: { idKey?: boolean } = {}) { - const data = useContext(PayeesContext); - return idKey && data ? getPayeesById(data) : data; -} diff --git a/packages/loot-core/src/client/privacy.ts b/packages/loot-core/src/client/privacy.ts deleted file mode 100644 index 295bba5e510..00000000000 --- a/packages/loot-core/src/client/privacy.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { useSelector } from 'react-redux'; - -export function usePrivacyMode() { - return useSelector(state => state.prefs?.local?.isPrivacyEnabled ?? false); -} diff --git a/packages/loot-core/src/client/reducers/queries.ts b/packages/loot-core/src/client/reducers/queries.ts index 470c3591692..b0c38817a24 100644 --- a/packages/loot-core/src/client/reducers/queries.ts +++ b/packages/loot-core/src/client/reducers/queries.ts @@ -13,11 +13,14 @@ const initialState: QueriesState = { lastTransaction: null, updatedAccounts: [], accounts: [], + accountsLoaded: false, categories: { grouped: [], list: [], }, + categoriesLoaded: false, payees: [], + payeesLoaded: false, earliestTransaction: null, }; @@ -56,6 +59,7 @@ export function update(state = initialState, action: Action): QueriesState { return { ...state, accounts: action.accounts, + accountsLoaded: true, }; case constants.UPDATE_ACCOUNT: { return { @@ -72,11 +76,13 @@ export function update(state = initialState, action: Action): QueriesState { return { ...state, categories: action.categories, + categoriesLoaded: true, }; case constants.LOAD_PAYEES: return { ...state, payees: action.payees, + payeesLoaded: true, }; default: } diff --git a/packages/loot-core/src/client/state-types/modals.d.ts b/packages/loot-core/src/client/state-types/modals.d.ts index e25e38feb32..72cd5dbc54b 100644 --- a/packages/loot-core/src/client/state-types/modals.d.ts +++ b/packages/loot-core/src/client/state-types/modals.d.ts @@ -42,7 +42,7 @@ type FinanceModals = { syncSource?: AccountSyncSource; }; - 'confirm-category-delete': { onDelete: () => void } & ( + 'confirm-category-delete': { onDelete: (categoryId: string) => void } & ( | { category: string } | { group: string } ); diff --git a/packages/loot-core/src/client/state-types/queries.d.ts b/packages/loot-core/src/client/state-types/queries.d.ts index a7f18526fe0..0d511868b39 100644 --- a/packages/loot-core/src/client/state-types/queries.d.ts +++ b/packages/loot-core/src/client/state-types/queries.d.ts @@ -8,8 +8,11 @@ export type QueriesState = { lastTransaction: unknown | null; updatedAccounts: string[]; accounts: AccountEntity[]; + accountsLoaded: boolean; categories: Awaited>; + categoriesLoaded: boolean; payees: Awaited>; + payeesLoaded: boolean; earliestTransaction: unknown | null; }; diff --git a/packages/loot-core/src/shared/categories.ts b/packages/loot-core/src/shared/categories.ts deleted file mode 100644 index 9e2c4c190aa..00000000000 --- a/packages/loot-core/src/shared/categories.ts +++ /dev/null @@ -1,106 +0,0 @@ -// @ts-strict-ignore -export function addCategory(categoryGroups, cat) { - return categoryGroups.map(group => { - if (group.id === cat.cat_group) { - group.categories = [cat, ...group.categories]; - } - return { ...group }; - }); -} - -export function updateCategory(categoryGroups, category) { - return categoryGroups.map(group => { - if (group.id === category.cat_group) { - group.categories = group.categories.map(c => { - if (c.id === category.id) { - return { ...c, ...category }; - } - return c; - }); - } - return group; - }); -} - -export function moveCategory(categoryGroups, id, groupId, targetId) { - if (id === targetId) { - return categoryGroups; - } - - let moveCat = categoryGroups.reduce((value, group) => { - return value || group.categories.find(cat => cat.id === id); - }, null); - - // Update the group id on the category - moveCat = { ...moveCat, cat_group: groupId }; - - return categoryGroups.map(group => { - if (group.id === groupId) { - group.categories = group.categories.reduce((cats, cat) => { - if (cat.id === targetId) { - cats.push(moveCat); - cats.push(cat); - } else if (cat.id !== id) { - cats.push(cat); - } - return cats; - }, []); - - if (!targetId) { - group.categories.push(moveCat); - } - } else { - group.categories = group.categories.filter(cat => cat.id !== id); - } - - return { ...group }; - }); -} - -export function moveCategoryGroup(categoryGroups, id, targetId) { - if (id === targetId) { - return categoryGroups; - } - - const moveGroup = categoryGroups.find(g => g.id === id); - - categoryGroups = categoryGroups.reduce((groups, group) => { - if (group.id === targetId) { - groups.push(moveGroup); - groups.push(group); - } else if (group.id !== id) { - groups.push(group); - } - return groups; - }, []); - - if (!targetId) { - categoryGroups.push(moveGroup); - } - - return categoryGroups; -} - -export function deleteCategory(categoryGroups, id) { - return categoryGroups.map(group => { - group.categories = group.categories.filter(c => c.id !== id); - return group; - }); -} - -export function addGroup(categoryGroups, group) { - return [...categoryGroups, group]; -} - -export function updateGroup(categoryGroups, group) { - return categoryGroups.map(g => { - if (g.id === group.id) { - return { ...g, ...group }; - } - return g; - }); -} - -export function deleteGroup(categoryGroups, id) { - return categoryGroups.filter(g => g.id !== id); -} diff --git a/packages/loot-core/src/types/prefs.d.ts b/packages/loot-core/src/types/prefs.d.ts index df63517dd8d..251b6e5dd6a 100644 --- a/packages/loot-core/src/types/prefs.d.ts +++ b/packages/loot-core/src/types/prefs.d.ts @@ -53,6 +53,7 @@ export type LocalPrefs = Partial< reportsViewLegend: boolean; reportsViewSummary: boolean; reportsViewLabel: boolean; + 'mobile.showSpentColumn': boolean; } & Record<`flags.${FeatureFlag}`, boolean> >; diff --git a/upcoming-release-notes/2293.md b/upcoming-release-notes/2293.md new file mode 100644 index 00000000000..d65818af8a0 --- /dev/null +++ b/upcoming-release-notes/2293.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [joel-jeremy] +--- + +Add hooks for frequently-made operations in the codebase.