From 9d041aaa7a1c93f50c642119d03066ef1c258c8a Mon Sep 17 00:00:00 2001 From: Trevor Farlow <trevdor@users.noreply.github.com> Date: Fri, 23 Jun 2023 12:40:59 -0600 Subject: [PATCH] react-router 6 upgrade (#1066) Co-authored-by: Jed Fox <git@jedfox.com> --- packages/desktop-client/package.json | 4 +- .../src/components/FinancesApp.js | 348 +++++++--------- .../src/components/FloatableSidebar.js | 11 +- .../src/components/GlobalKeys.js | 12 +- .../src/components/LoggedInUser.js | 9 +- .../desktop-client/src/components/Modals.js | 391 +++++++++--------- .../desktop-client/src/components/Page.js | 6 +- .../src/components/SidebarWithData.js | 28 +- .../desktop-client/src/components/Titlebar.js | 58 +-- .../src/components/accounts/Account.js | 20 +- .../src/components/accounts/MobileAccount.js | 3 +- .../src/components/accounts/MobileAccounts.js | 2 +- .../components/accounts/TransactionList.js | 13 +- .../src/components/budget/index.js | 8 +- .../desktop-client/src/components/common.tsx | 49 +-- .../src/components/manager/ConfigServer.js | 16 +- .../src/components/manager/ManagementApp.js | 96 ++--- .../src/components/manager/Modals.js | 13 +- .../manager/subscribe/ChangePassword.tsx | 8 +- .../components/manager/subscribe/Error.tsx | 6 +- .../components/manager/subscribe/common.tsx | 12 +- .../components/modals/CreateLocalAccount.js | 6 +- .../src/components/modals/EditRule.js | 1 - .../components/modals/MergeUnusedPayees.js | 1 - .../src/components/reports/Overview.js | 1 - .../src/components/reports/index.js | 16 +- .../components/schedules/DiscoverSchedules.js | 16 +- .../src/components/schedules/EditSchedule.js | 8 +- .../src/components/schedules/LinkSchedule.js | 6 +- .../schedules/PostsOfflineNotification.js | 8 +- .../src/components/schedules/index.js | 10 +- .../desktop-client/src/components/sidebar.js | 16 +- packages/desktop-client/src/global-events.js | 12 +- .../desktop-client/src/util/location-state.js | 12 - .../desktop-client/src/util/router-tools.tsx | 71 ++++ packages/desktop-electron/menu.js | 4 +- .../loot-core/src/client/actions/budgets.ts | 2 +- packages/loot-core/typings/window.d.ts | 5 +- upcoming-release-notes/1066.md | 6 + yarn.lock | 162 +------- 40 files changed, 678 insertions(+), 798 deletions(-) delete mode 100644 packages/desktop-client/src/util/location-state.js create mode 100644 packages/desktop-client/src/util/router-tools.tsx create mode 100644 upcoming-release-notes/1066.md diff --git a/packages/desktop-client/package.json b/packages/desktop-client/package.json index 7e14e925d..a6c58d236 100644 --- a/packages/desktop-client/package.json +++ b/packages/desktop-client/package.json @@ -48,9 +48,7 @@ "react-merge-refs": "^1.1.0", "react-modal": "3.16.1", "react-redux": "7.2.1", - "react-router": "5.2.0", - "react-router-dom": "5.2.0", - "react-router-dom-v5-compat": "^6.4.1", + "react-router-dom": "6.11.2", "react-scripts": "^5.0.1", "react-spring": "^9.7.1", "react-virtualized-auto-sizer": "^1.0.2", diff --git a/packages/desktop-client/src/components/FinancesApp.js b/packages/desktop-client/src/components/FinancesApp.js index c82f1524f..8d6c3bc5c 100644 --- a/packages/desktop-client/src/components/FinancesApp.js +++ b/packages/desktop-client/src/components/FinancesApp.js @@ -1,18 +1,17 @@ -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useEffect, useMemo } from 'react'; import { DndProvider } from 'react-dnd'; import Backend from 'react-dnd-html5-backend'; import { connect } from 'react-redux'; import { - Router, Route, - Redirect, - Switch, - useLocation, + Routes, + Navigate, NavLink, + useNavigate, + BrowserRouter, + useParams, } from 'react-router-dom'; -import { CompatRouter } from 'react-router-dom-v5-compat'; -import { createBrowserHistory } from 'history'; import hotkeys from 'hotkeys-js'; import * as actions from 'loot-core/src/client/actions'; @@ -20,20 +19,18 @@ 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 checkForUpdateNotification from 'loot-core/src/client/update-notification'; -import * as undo from 'loot-core/src/platform/client/undo'; import Cog from '../icons/v1/Cog'; import PiggyBank from '../icons/v1/PiggyBank'; import Wallet from '../icons/v1/Wallet'; import { useResponsive } from '../ResponsiveProvider'; import { colors, styles } from '../style'; -import { getLocationState, makeLocationState } from '../util/location-state'; +import { ExposeNavigate, StackedRoutes } from '../util/router-tools'; import { getIsOutdated, getLatestVersion } from '../util/versions'; import Account from './accounts/Account'; import MobileAccount from './accounts/MobileAccount'; import MobileAccounts from './accounts/MobileAccounts'; -import { ActiveLocationProvider } from './ActiveLocation'; import BankSyncStatus from './BankSyncStatus'; import Budget from './budget'; import { BudgetMonthCountProvider } from './budget/BudgetMonthCountContext'; @@ -45,7 +42,6 @@ import { ManageRulesPage } from './ManageRulesPage'; import Modals from './Modals'; import NordigenLink from './nordigen/NordigenLink'; import Notifications from './Notifications'; -import { PageTypeProvider } from './Page'; import { ManagePayeesPage } from './payees/ManagePayeesPage'; import Reports from './reports'; import Schedules from './schedules'; @@ -58,125 +54,126 @@ import Titlebar, { TitlebarProvider } from './Titlebar'; function NarrowNotSupported({ children, redirectTo = '/budget' }) { const { isNarrowWidth } = useResponsive(); - return isNarrowWidth ? <Redirect to={redirectTo} /> : children; + const navigate = useNavigate(); + useEffect(() => { + if (isNarrowWidth) { + navigate(redirectTo); + } + }, [isNarrowWidth, navigate, redirectTo]); + return isNarrowWidth ? null : children; } -function Routes({ location }) { +function StackedRoutesInner({ location }) { const { isNarrowWidth } = useResponsive(); return ( - <Switch location={location}> - <Route path="/" exact render={() => <Redirect to="/budget" />} /> + <Routes location={location}> + <Route path="/" element={<Navigate to="/budget" replace />} /> + + <Route + path="/reports/*" + element={ + <NarrowNotSupported> + <Reports /> + </NarrowNotSupported> + } + /> - <Route path="/reports"> - <NarrowNotSupported> - <Reports /> - </NarrowNotSupported> - </Route> + <Route + path="/budget" + element={isNarrowWidth ? <MobileBudget /> : <Budget />} + /> - <Route path="/budget"> - {isNarrowWidth ? <MobileBudget /> : <Budget />} - </Route> + <Route + path="/schedules" + element={ + <NarrowNotSupported> + <Schedules /> + </NarrowNotSupported> + } + /> - <Route path="/schedules" exact> - <NarrowNotSupported> - <Schedules /> - </NarrowNotSupported> - </Route> - <Route path="/schedule/edit" exact> - <NarrowNotSupported> - <EditSchedule /> - </NarrowNotSupported> - </Route> - <Route path="/schedule/edit/:id"> - <NarrowNotSupported> - <EditSchedule /> - </NarrowNotSupported> - </Route> - <Route path="/schedule/link"> - <NarrowNotSupported> - <LinkSchedule /> - </NarrowNotSupported> - </Route> - <Route path="/schedule/discover"> - <NarrowNotSupported> - <DiscoverSchedules /> - </NarrowNotSupported> - </Route> - <Route path="/schedule/posts-offline-notification"> - <PostsOfflineNotification /> - </Route> + <Route + path="/schedule/edit" + element={ + <NarrowNotSupported> + <EditSchedule /> + </NarrowNotSupported> + } + /> + <Route + path="/schedule/edit/:id" + element={ + <NarrowNotSupported> + <EditSchedule /> + </NarrowNotSupported> + } + /> + <Route + path="/schedule/link" + element={ + <NarrowNotSupported> + <LinkSchedule /> + </NarrowNotSupported> + } + /> + <Route + path="/schedule/discover" + element={ + <NarrowNotSupported> + <DiscoverSchedules /> + </NarrowNotSupported> + } + /> - <Route path="/payees" exact> - <ManagePayeesPage /> - </Route> - <Route path="/rules" exact> - <ManageRulesPage /> - </Route> - <Route path="/settings"> - <Settings /> - </Route> - <Route path="/nordigen/link" exact> - <NarrowNotSupported> - <NordigenLink /> - </NarrowNotSupported> - </Route> + <Route + path="/schedule/posts-offline-notification" + element={<PostsOfflineNotification />} + /> - <Route path="/accounts/:id" exact> - {props => { - const AcctCmp = isNarrowWidth ? MobileAccount : Account; - return ( - props.match && <AcctCmp key={props.match.params.id} {...props} /> - ); - }} - </Route> - <Route path="/accounts" exact> - {isNarrowWidth ? <MobileAccounts /> : <Account />} - </Route> - </Switch> - ); -} + <Route path="/payees" element={<ManagePayeesPage />} /> + <Route path="/rules" element={<ManageRulesPage />} /> + <Route path="/settings" element={<Settings />} /> + <Route + path="/nordigen/link" + element={ + <NarrowNotSupported> + <NordigenLink /> + </NarrowNotSupported> + } + /> -function StackedRoutes() { - let location = useLocation(); - let locationPtr = getLocationState(location, 'locationPtr'); + <Route + path="/accounts/:id" + element={<AccountCmp isNarrowWidth={isNarrowWidth} />} + /> - let locations = [location]; - while (locationPtr) { - locations.unshift(locationPtr); - locationPtr = getLocationState(locationPtr, 'locationPtr'); - } + <Route + path="/accounts" + element={isNarrowWidth ? <MobileAccounts /> : <Account />} + /> + </Routes> + ); +} - let base = locations[0]; - let stack = locations.slice(1); +// Needed to re-mount the component for each account ID change +function AccountCmp(props) { + const { id } = useParams(); + const Component = props.isNarrowWidth ? MobileAccount : Account; - return ( - <ActiveLocationProvider location={locations[locations.length - 1]}> - <Routes location={base} /> - {stack.map((location, idx) => ( - <PageTypeProvider - key={location.key} - type="modal" - current={idx === stack.length - 1} - > - <Routes location={location} /> - </PageTypeProvider> - ))} - </ActiveLocationProvider> - ); + return <Component key={id} {...props} />; } function NavTab({ icon: TabIcon, name, path }) { return ( <NavLink to={path} - style={{ + style={({ isActive }) => ({ alignItems: 'center', - color: '#8E8E8F', + color: isActive ? colors.p5 : '#8E8E8F', display: 'flex', flexDirection: 'column', textDecoration: 'none', - }} - activeStyle={{ color: colors.p5 }} + })} > <TabIcon width={22} @@ -211,49 +208,22 @@ function MobileNavTabs() { ); } -function FinancesApp(props) { - const [patchedHistory] = useState(() => createBrowserHistory()); - +function Redirector({ getAccounts }) { + let navigate = useNavigate(); useEffect(() => { - let oldPush = patchedHistory.push; - patchedHistory.push = (to, state) => { - let newState = makeLocationState(to.state || state); - if (typeof to === 'object') { - return oldPush.call(patchedHistory, { ...to, state: newState }); - } else { - return oldPush.call(patchedHistory, to, newState); - } - }; - - // I'm not sure if this is the best approach but we need this to - // globally. We could instead move various workflows inside global - // React components, but that's for another day. - window.__history = patchedHistory; - - undo.setUndoState('url', window.location.href); - - const cleanup = patchedHistory.listen(location => { - undo.setUndoState('url', window.location.href); - }); - - return cleanup; - }, []); - - useEffect(() => { - // TODO: quick hack fix for showing the demo - if (patchedHistory.location.pathname === '/subscribe') { - patchedHistory.push('/'); - } - // 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 - props.getAccounts().then(accounts => { + getAccounts().then(accounts => { if (accounts.length === 0) { - patchedHistory.push('/accounts'); + navigate('/accounts'); } }); + }, []); +} +function FinancesApp(props) { + useEffect(() => { // The default key handler scope hotkeys.setScope('app'); @@ -273,61 +243,59 @@ function FinancesApp(props) { }, []); return ( - <Router history={patchedHistory}> - <CompatRouter> - <View style={{ height: '100%', backgroundColor: colors.n10 }}> - <GlobalKeys /> - - <View style={{ flexDirection: 'row', flex: 1 }}> - <FloatableSidebar /> - - <View + <BrowserRouter> + <Redirector getAccounts={props.getAccounts} /> + <ExposeNavigate /> + + <View style={{ height: '100%', backgroundColor: colors.n10 }}> + <GlobalKeys /> + + <View style={{ flexDirection: 'row', flex: 1 }}> + <FloatableSidebar /> + + <View + style={{ + flex: 1, + overflow: 'hidden', + width: '100%', + }} + > + <Titlebar + style={{ + WebkitAppRegion: 'drag', + position: 'absolute', + top: 0, + left: 0, + right: 0, + zIndex: 1000, + }} + /> + <div style={{ flex: 1, - overflow: 'hidden', - width: '100%', + display: 'flex', + overflow: 'auto', + position: 'relative', }} > - <Titlebar - style={{ - WebkitAppRegion: 'drag', - position: 'absolute', - top: 0, - left: 0, - right: 0, - zIndex: 1000, - }} + <Notifications /> + <BankSyncStatus /> + <StackedRoutes + render={location => <StackedRoutesInner location={location} />} /> - <div - style={{ - flex: 1, - display: 'flex', - overflow: 'auto', - position: 'relative', - }} - > - <Notifications /> - <BankSyncStatus /> - <StackedRoutes /> - <Modals history={patchedHistory} /> - </div> - - <Switch> - <Route path="/budget"> - <MobileNavTabs /> - </Route> - <Route path="/accounts"> - <MobileNavTabs /> - </Route> - <Route path="/settings"> - <MobileNavTabs /> - </Route> - </Switch> - </View> + <Modals /> + </div> + + <Routes> + <Route path="/budget" element={<MobileNavTabs />} /> + <Route path="/accounts" element={<MobileNavTabs />} /> + <Route path="/settings" element={<MobileNavTabs />} /> + <Route path="*" element={null} /> + </Routes> </View> </View> - </CompatRouter> - </Router> + </View> + </BrowserRouter> ); } diff --git a/packages/desktop-client/src/components/FloatableSidebar.js b/packages/desktop-client/src/components/FloatableSidebar.js index 444ee866e..bdc9583c5 100644 --- a/packages/desktop-client/src/components/FloatableSidebar.js +++ b/packages/desktop-client/src/components/FloatableSidebar.js @@ -1,6 +1,5 @@ import React, { createContext, useState, useContext, useMemo } from 'react'; import { connect, useSelector } from 'react-redux'; -import { withRouter } from 'react-router-dom'; import * as actions from 'loot-core/src/client/actions'; @@ -84,9 +83,7 @@ function Sidebar({ floatingSidebar }) { ); } -export default withRouter( - connect( - state => ({ floatingSidebar: state.prefs.global.floatingSidebar }), - actions, - )(Sidebar), -); +export default connect( + state => ({ floatingSidebar: state.prefs.global.floatingSidebar }), + actions, +)(Sidebar); diff --git a/packages/desktop-client/src/components/GlobalKeys.js b/packages/desktop-client/src/components/GlobalKeys.js index 63b6a58fc..a75c07635 100644 --- a/packages/desktop-client/src/components/GlobalKeys.js +++ b/packages/desktop-client/src/components/GlobalKeys.js @@ -1,10 +1,10 @@ import { useEffect } from 'react'; -import { useHistory } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import * as Platform from 'loot-core/src/client/platform'; export default function GlobalKeys() { - let history = useHistory(); + let navigate = useNavigate(); useEffect(() => { const handleKeys = e => { if (Platform.isBrowser) { @@ -14,17 +14,17 @@ export default function GlobalKeys() { if (e.metaKey) { switch (e.key) { case '1': - history.push('/budget'); + navigate('/budget'); break; case '2': - history.push('/reports'); + navigate('/reports'); break; case '3': - history.push('/accounts'); + navigate('/accounts'); break; case ',': if (Platform.OS === 'mac') { - history.push('/settings'); + navigate('/settings'); } break; default: diff --git a/packages/desktop-client/src/components/LoggedInUser.js b/packages/desktop-client/src/components/LoggedInUser.js index 9b9b68aaa..ba41e9cbe 100644 --- a/packages/desktop-client/src/components/LoggedInUser.js +++ b/packages/desktop-client/src/components/LoggedInUser.js @@ -1,6 +1,5 @@ import React, { useState, useEffect } from 'react'; import { connect } from 'react-redux'; -import { withRouter } from 'react-router'; import { css } from 'glamor'; @@ -35,7 +34,7 @@ function LoggedInUser({ async function onChangePassword() { await closeBudget(); - window.__history.push('/change-password'); + window.__navigate('/change-password'); } async function onMenuSelect(type) { @@ -47,14 +46,14 @@ function LoggedInUser({ break; case 'sign-in': await closeBudget(); - window.__history.push('/login'); + window.__navigate('/login'); break; case 'sign-out': signOut(); break; case 'config-server': await closeBudget(); - window.__history.push('/config-server'); + window.__navigate('/config-server'); break; default: } @@ -132,4 +131,4 @@ function LoggedInUser({ export default connect( state => ({ userData: state.user.data }), actions, -)(withRouter(LoggedInUser)); +)(LoggedInUser); diff --git a/packages/desktop-client/src/components/Modals.js b/packages/desktop-client/src/components/Modals.js index c18eeb859..c09a581d1 100644 --- a/packages/desktop-client/src/components/Modals.js +++ b/packages/desktop-client/src/components/Modals.js @@ -1,8 +1,6 @@ import React from 'react'; import { connect } from 'react-redux'; -import { Route, Switch } from 'react-router-dom'; -import { createLocation } from 'history'; import { bindActionCreators } from 'redux'; import * as actions from 'loot-core/src/client/actions'; @@ -31,13 +29,11 @@ import PlaidExternalMsg from './modals/PlaidExternalMsg'; import SelectLinkedAccounts from './modals/SelectLinkedAccounts'; function Modals({ - history, modalStack, isHidden, accounts, categoryGroups, categories, - payees, budgetId, actions, }) { @@ -45,190 +41,208 @@ function Modals({ const syncServerStatus = useSyncServerStatus(); - return modalStack.map(({ name, options = {} }, idx) => { - const modalProps = { - onClose: actions.popModal, - onBack: actions.popModal, - showBack: idx > 0, - isCurrent: idx === modalStack.length - 1, - isHidden, - stackIndex: idx, - }; - - let location = createLocation('/' + name); - return ( - <Switch key={name} location={location}> - <Route path="/import-transactions"> - <ImportTransactions modalProps={modalProps} options={options} /> - </Route> - - <Route path="/add-account"> - <CreateAccount - modalProps={modalProps} - actions={actions} - syncServerStatus={syncServerStatus} - /> - </Route> - - <Route path="/add-local-account"> - <CreateLocalAccount - modalProps={modalProps} - actions={actions} - history={history} - /> - </Route> - - <Route path="/close-account"> - <CloseAccount - modalProps={modalProps} - account={options.account} - balance={options.balance} - canDelete={options.canDelete} - accounts={accounts.filter(acct => acct.closed === 0)} - categoryGroups={categoryGroups} - actions={actions} - /> - </Route> - - <Route path="/select-linked-accounts"> - <SelectLinkedAccounts - modalProps={modalProps} - externalAccounts={options.accounts} - requisitionId={options.requisitionId} - localAccounts={accounts.filter(acct => acct.closed === 0)} - upgradingAccountId={options.upgradingAccountId} - actions={actions} - /> - </Route> - - <Route path="/configure-linked-accounts"> - <ConfigureLinkedAccounts - modalProps={modalProps} - institution={options.institution} - publicToken={options.publicToken} - accounts={options.accounts} - upgradingId={options.upgradingId} - actions={actions} - /> - </Route> - - <Route path="/confirm-category-delete"> - <ConfirmCategoryDelete - modalProps={modalProps} - actions={actions} - category={categories.find(c => c.id === options.category)} - group={categoryGroups.find(g => g.id === options.group)} - categoryGroups={categoryGroups} - onDelete={options.onDelete} - /> - </Route> - - <Route path="/load-backup"> - <LoadBackup - watchUpdates - budgetId={budgetId} - modalProps={modalProps} - actions={actions} - /> - </Route> - - <Route path="/manage-rules"> - <ManageRulesModal - history={history} - modalProps={modalProps} - payeeId={options.payeeId} - /> - </Route> - - <Route path="/edit-rule"> - <EditRule - history={history} - modalProps={modalProps} - defaultRule={options.rule} - onSave={options.onSave} - /> - </Route> - - <Route path="/merge-unused-payees"> - <MergeUnusedPayees - history={history} - modalProps={modalProps} - payeeIds={options.payeeIds} - targetPayeeId={options.targetPayeeId} - /> - </Route> - - <Route path="/plaid-external-msg"> - <PlaidExternalMsg - modalProps={modalProps} - actions={actions} - onMoveExternal={options.onMoveExternal} - onClose={() => { - options.onClose && options.onClose(); - send('poll-web-token-stop'); - }} - onSuccess={options.onSuccess} - /> - </Route> - <Route path="/nordigen-init"> - <NordigenInitialise - modalProps={modalProps} - onSuccess={options.onSuccess} - /> - </Route> - <Route path="/nordigen-external-msg"> - <NordigenExternalMsg - modalProps={modalProps} - actions={actions} - onMoveExternal={options.onMoveExternal} - onClose={() => { - options.onClose && options.onClose(); - send('nordigen-poll-web-token-stop'); - }} - onSuccess={options.onSuccess} - /> - </Route> - - <Route path="/create-encryption-key"> - <CreateEncryptionKey - key={name} - modalProps={modalProps} - actions={actions} - options={options} - /> - </Route> - - <Route path="/fix-encryption-key"> - <FixEncryptionKey - key={name} - modalProps={modalProps} - actions={actions} - options={options} - /> - </Route> - - <Route path="/edit-field"> - <EditField - key={name} - modalProps={modalProps} - actions={actions} - name={options.name} - onSubmit={options.onSubmit} - /> - </Route> - - <Route path="/budget-summary"> - <BudgetSummary - key={name} - modalProps={modalProps} - month={options.month} - actions={actions} - isGoalTemplatesEnabled={isGoalTemplatesEnabled} - /> - </Route> - </Switch> - ); - }); + return modalStack + .map(({ name, options = {} }, idx) => { + const modalProps = { + onClose: actions.popModal, + onBack: actions.popModal, + showBack: idx > 0, + isCurrent: idx === modalStack.length - 1, + isHidden, + stackIndex: idx, + }; + + switch (name) { + case 'import-transactions': + return ( + <ImportTransactions modalProps={modalProps} options={options} /> + ); + + case 'add-account': + return ( + <CreateAccount + modalProps={modalProps} + actions={actions} + syncServerStatus={syncServerStatus} + /> + ); + + case 'add-local-account': + return ( + <CreateLocalAccount modalProps={modalProps} actions={actions} /> + ); + + case 'close-account': + return ( + <CloseAccount + modalProps={modalProps} + account={options.account} + balance={options.balance} + canDelete={options.canDelete} + accounts={accounts.filter(acct => acct.closed === 0)} + categoryGroups={categoryGroups} + actions={actions} + /> + ); + + case 'select-linked-accounts': + return ( + <SelectLinkedAccounts + modalProps={modalProps} + externalAccounts={options.accounts} + requisitionId={options.requisitionId} + localAccounts={accounts.filter(acct => acct.closed === 0)} + upgradingAccountId={options.upgradingAccountId} + actions={actions} + /> + ); + + case 'configure-linked-accounts': + return ( + <ConfigureLinkedAccounts + modalProps={modalProps} + institution={options.institution} + publicToken={options.publicToken} + accounts={options.accounts} + upgradingId={options.upgradingId} + actions={actions} + /> + ); + + case 'confirm-category-delete': + return ( + <ConfirmCategoryDelete + modalProps={modalProps} + actions={actions} + category={categories.find(c => c.id === options.category)} + group={categoryGroups.find(g => g.id === options.group)} + categoryGroups={categoryGroups} + onDelete={options.onDelete} + /> + ); + + case 'load-backup': + return ( + <LoadBackup + watchUpdates + budgetId={budgetId} + modalProps={modalProps} + actions={actions} + /> + ); + + case 'manage-rules': + return ( + <ManageRulesModal + modalProps={modalProps} + payeeId={options.payeeId} + /> + ); + + case 'edit-rule': + return ( + <EditRule + modalProps={modalProps} + defaultRule={options.rule} + onSave={options.onSave} + /> + ); + + case 'merge-unused-payees': + return ( + <MergeUnusedPayees + modalProps={modalProps} + payeeIds={options.payeeIds} + targetPayeeId={options.targetPayeeId} + /> + ); + + case 'plaid-external-msg': + return ( + <PlaidExternalMsg + modalProps={modalProps} + actions={actions} + onMoveExternal={options.onMoveExternal} + onClose={() => { + options.onClose && options.onClose(); + send('poll-web-token-stop'); + }} + onSuccess={options.onSuccess} + /> + ); + + case 'nordigen-init': + return ( + <NordigenInitialise + modalProps={modalProps} + onSuccess={options.onSuccess} + /> + ); + + case 'nordigen-external-msg': + return ( + <NordigenExternalMsg + modalProps={modalProps} + actions={actions} + onMoveExternal={options.onMoveExternal} + onClose={() => { + options.onClose && options.onClose(); + send('nordigen-poll-web-token-stop'); + }} + onSuccess={options.onSuccess} + /> + ); + + case 'create-encryption-key': + return ( + <CreateEncryptionKey + key={name} + modalProps={modalProps} + actions={actions} + options={options} + /> + ); + + case 'fix-encryption-key': + return ( + <FixEncryptionKey + key={name} + modalProps={modalProps} + actions={actions} + options={options} + /> + ); + + case 'edit-field': + return ( + <EditField + key={name} + modalProps={modalProps} + actions={actions} + name={options.name} + onSubmit={options.onSubmit} + /> + ); + + case 'budget-summary': + return ( + <BudgetSummary + key={name} + modalProps={modalProps} + month={options.month} + actions={actions} + isGoalTemplatesEnabled={isGoalTemplatesEnabled} + /> + ); + + default: + console.error('Unknown modal:', name); + return null; + } + }) + .map((modal, idx) => ( + <React.Fragment key={modalStack[idx].name}>{modal}</React.Fragment> + )); } export default connect( @@ -238,7 +252,6 @@ export default connect( accounts: state.queries.accounts, categoryGroups: state.queries.categories.grouped, categories: state.queries.categories.list, - payees: state.queries.payees, budgetId: state.prefs.local && state.prefs.local.id, }), dispatch => ({ actions: bindActionCreators(actions, dispatch) }), diff --git a/packages/desktop-client/src/components/Page.js b/packages/desktop-client/src/components/Page.js index ccacf653b..8038a87db 100644 --- a/packages/desktop-client/src/components/Page.js +++ b/packages/desktop-client/src/components/Page.js @@ -1,5 +1,5 @@ import React, { createContext, useContext } from 'react'; -import { useHistory } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import { useResponsive } from '../ResponsiveProvider'; import { colors, styles } from '../style'; @@ -65,7 +65,7 @@ function PageTitle({ name, style }) { export function Page({ title, modalSize, children, titleStyle }) { let { type, current } = usePageType(); - let history = useHistory(); + let navigate = useNavigate(); let { isNarrowWidth } = useResponsive(); let HORIZONTAL_PADDING = isNarrowWidth ? 10 : 20; @@ -81,7 +81,7 @@ export function Page({ title, modalSize, children, titleStyle }) { title={title} isCurrent={current} size={size} - onClose={() => history.goBack()} + onClose={() => navigate(-1)} > {children} </Modal> diff --git a/packages/desktop-client/src/components/SidebarWithData.js b/packages/desktop-client/src/components/SidebarWithData.js index d38be20d0..0acca9e5c 100644 --- a/packages/desktop-client/src/components/SidebarWithData.js +++ b/packages/desktop-client/src/components/SidebarWithData.js @@ -1,6 +1,6 @@ import React, { useState, useEffect } from 'react'; import { connect, useDispatch } from 'react-redux'; -import { withRouter, useHistory } from 'react-router'; +import { useNavigate } from 'react-router'; import { bindActionCreators } from 'redux'; @@ -18,7 +18,7 @@ import { Sidebar } from './sidebar'; function EditableBudgetName({ prefs, savePrefs }) { let dispatch = useDispatch(); - let history = useHistory(); + let navigate = useNavigate(); const [editing, setEditing] = useState(false); const [menuOpen, setMenuOpen] = useState(false); @@ -30,7 +30,7 @@ function EditableBudgetName({ prefs, savePrefs }) { setEditing(true); break; case 'settings': - history.push('/settings'); + navigate('/settings'); break; case 'help': window.open('https://actualbudget.org/docs/', '_blank'); @@ -155,15 +155,13 @@ function SidebarWithData({ ); } -export default withRouter( - connect( - state => ({ - accounts: state.queries.accounts, - failedAccounts: state.account.failedAccounts, - updatedAccounts: state.queries.updatedAccounts, - prefs: state.prefs.local, - floatingSidebar: state.prefs.global.floatingSidebar, - }), - dispatch => bindActionCreators(actions, dispatch), - )(SidebarWithData), -); +export default connect( + state => ({ + accounts: state.queries.accounts, + failedAccounts: state.account.failedAccounts, + updatedAccounts: state.queries.updatedAccounts, + prefs: state.prefs.local, + floatingSidebar: state.prefs.global.floatingSidebar, + }), + dispatch => bindActionCreators(actions, dispatch), +)(SidebarWithData); diff --git a/packages/desktop-client/src/components/Titlebar.js b/packages/desktop-client/src/components/Titlebar.js index 35093383e..4bd3af0f2 100644 --- a/packages/desktop-client/src/components/Titlebar.js +++ b/packages/desktop-client/src/components/Titlebar.js @@ -6,7 +6,7 @@ import React, { useContext, } from 'react'; import { connect } from 'react-redux'; -import { Switch, Route, useLocation, useHistory } from 'react-router-dom'; +import { Routes, Route, useLocation, useNavigate } from 'react-router-dom'; import { css, media } from 'glamor'; @@ -268,7 +268,7 @@ function Titlebar({ style, sync, }) { - let history = useHistory(); + let navigate = useNavigate(); let location = useLocation(); let sidebar = useSidebar(); let { isNarrowWidth } = useResponsive(); @@ -320,32 +320,38 @@ function Titlebar({ </Button> )} - <Switch> - <Route path="/accounts" exact> - {location.state?.goBack ? ( - <Button onClick={() => history.goBack()} bare> - <ArrowLeft - width={10} - height={10} - style={{ marginRight: 5, color: 'currentColor' }} - />{' '} - Back - </Button> - ) : null} - </Route> + <Routes> + <Route + path="/accounts" + element={ + location.state?.goBack ? ( + <Button onClick={() => navigate(-1)} bare> + <ArrowLeft + width={10} + height={10} + style={{ marginRight: 5, color: 'currentColor' }} + />{' '} + Back + </Button> + ) : null + } + /> - <Route path="/accounts/:id" exact> - <AccountSyncCheck /> - </Route> + <Route path="/accounts/:id" element={<AccountSyncCheck />} /> - <Route path="/budget" exact> - <BudgetTitlebar - globalPrefs={globalPrefs} - saveGlobalPrefs={saveGlobalPrefs} - localPrefs={localPrefs} - /> - </Route> - </Switch> + <Route + path="/budget" + element={ + <BudgetTitlebar + globalPrefs={globalPrefs} + saveGlobalPrefs={saveGlobalPrefs} + localPrefs={localPrefs} + /> + } + /> + + <Route path="*" element={null} /> + </Routes> <View style={{ flex: 1 }} /> <UncategorizedButton /> {serverURL ? ( diff --git a/packages/desktop-client/src/components/accounts/Account.js b/packages/desktop-client/src/components/accounts/Account.js index 26c60b416..23ee740d6 100644 --- a/packages/desktop-client/src/components/accounts/Account.js +++ b/packages/desktop-client/src/components/accounts/Account.js @@ -7,7 +7,12 @@ import React, { useMemo, } from 'react'; import { useSelector, useDispatch } from 'react-redux'; -import { Redirect, useParams, useHistory, useLocation } from 'react-router-dom'; +import { + Navigate, + useParams, + useNavigate, + useLocation, +} from 'react-router-dom'; import { debounce } from 'debounce'; import { bindActionCreators } from 'redux'; @@ -521,7 +526,8 @@ function SelectedTransactionsButton({ onScheduleAction, }) { let selectedItems = useSelectedItems(); - let history = useHistory(); + let navigate = useNavigate(); + let location = useLocation(); let types = useMemo(() => { let items = [...selectedItems]; @@ -633,14 +639,14 @@ function SelectedTransactionsButton({ } if (scheduleId) { - history.push(`/schedule/edit/${scheduleId}`, { - locationPtr: history.location, + navigate(`/schedule/edit/${scheduleId}`, { + locationPtr: location, }); } break; case 'link-schedule': - history.push(`/schedule/link`, { - locationPtr: history.location, + navigate(`/schedule/link`, { + locationPtr: location, transactionIds: [...selectedItems], }); break; @@ -1841,7 +1847,7 @@ class AccountInternal extends PureComponent { if (!accountName && !loading) { // This is probably an account that was deleted, so redirect to // all accounts - return <Redirect to="/accounts" />; + return <Navigate to="/accounts" replace />; } let showEmptyMessage = !loading && !accountId && accounts.length === 0; diff --git a/packages/desktop-client/src/components/accounts/MobileAccount.js b/packages/desktop-client/src/components/accounts/MobileAccount.js index 2db1f2ca3..212e397b5 100644 --- a/packages/desktop-client/src/components/accounts/MobileAccount.js +++ b/packages/desktop-client/src/components/accounts/MobileAccount.js @@ -1,7 +1,6 @@ import React, { useEffect, useMemo, useState } from 'react'; import { connect, useDispatch, useSelector } from 'react-redux'; -import { useParams } from 'react-router-dom'; -import { useNavigate } from 'react-router-dom-v5-compat'; +import { useParams, useNavigate } from 'react-router-dom'; import debounce from 'debounce'; import memoizeOne from 'memoize-one'; diff --git a/packages/desktop-client/src/components/accounts/MobileAccounts.js b/packages/desktop-client/src/components/accounts/MobileAccounts.js index d76baefee..2aba8819c 100644 --- a/packages/desktop-client/src/components/accounts/MobileAccounts.js +++ b/packages/desktop-client/src/components/accounts/MobileAccounts.js @@ -1,6 +1,6 @@ import React, { Component, useEffect, useState } from 'react'; import { connect } from 'react-redux'; -import { useNavigate } from 'react-router-dom-v5-compat'; +import { useNavigate } from 'react-router-dom'; import * as actions from 'loot-core/src/client/actions'; import * as queries from 'loot-core/src/client/queries'; diff --git a/packages/desktop-client/src/components/accounts/TransactionList.js b/packages/desktop-client/src/components/accounts/TransactionList.js index 96c1e3096..28cd4d657 100644 --- a/packages/desktop-client/src/components/accounts/TransactionList.js +++ b/packages/desktop-client/src/components/accounts/TransactionList.js @@ -1,5 +1,5 @@ import React, { useRef, useCallback, useLayoutEffect } from 'react'; -import { useHistory } from 'react-router'; +import { useNavigate } from 'react-router-dom'; import { send } from 'loot-core/src/platform/client/fetch'; import { @@ -80,7 +80,7 @@ export default function TransactionList({ onCreatePayee, }) { let transactionsLatest = useRef(); - let history = useHistory(); + let navigate = useNavigate(); useLayoutEffect(() => { transactionsLatest.current = transactions; @@ -145,12 +145,9 @@ export default function TransactionList({ return newTransaction; }, []); - let onManagePayees = useCallback( - id => { - history.push('/payees', { selectedPayee: id }); - }, - [history], - ); + let onManagePayees = useCallback(id => { + navigate('/payees', { selectedPayee: id }); + }); return ( <TransactionTable diff --git a/packages/desktop-client/src/components/budget/index.js b/packages/desktop-client/src/components/budget/index.js index 7c5e5d3af..c14af8eae 100644 --- a/packages/desktop-client/src/components/budget/index.js +++ b/packages/desktop-client/src/components/budget/index.js @@ -1,6 +1,6 @@ import React, { memo, PureComponent, useContext, useMemo } from 'react'; import { connect } from 'react-redux'; -import { useHistory } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import * as actions from 'loot-core/src/client/actions'; import { useSpreadsheet } from 'loot-core/src/client/SpreadsheetProvider'; @@ -332,7 +332,7 @@ class Budget extends PureComponent { }; onShowActivity = (categoryName, categoryId, month) => { - this.props.history.push({ + this.props.navigate({ pathname: '/accounts', state: { goBack: true, @@ -520,7 +520,7 @@ const RolloverBudgetSummary = memo(props => { function BudgetWrapper(props) { let spreadsheet = useSpreadsheet(); let titlebar = useContext(TitlebarContext); - let history = useHistory(); + let navigate = useNavigate(); let reportComponents = useMemo( () => ({ @@ -565,7 +565,7 @@ function BudgetWrapper(props) { rolloverComponents={rolloverComponents} spreadsheet={spreadsheet} titlebar={titlebar} - history={history} + navigate={navigate} /> </View> ); diff --git a/packages/desktop-client/src/components/common.tsx b/packages/desktop-client/src/components/common.tsx index 183994882..255e88fb9 100644 --- a/packages/desktop-client/src/components/common.tsx +++ b/packages/desktop-client/src/components/common.tsx @@ -13,8 +13,7 @@ import React, { createElement, cloneElement, } from 'react'; -import type { RouteComponentProps } from 'react-router'; -import { Route, NavLink, withRouter, useRouteMatch } from 'react-router-dom'; +import { NavLink, useMatch, useNavigate } from 'react-router-dom'; import { ListboxInput, @@ -156,7 +155,6 @@ export function Link({ style, children, ...nativeProps }: LinkProps) { type AnchorLinkProps = { to: string; - exact: boolean; style?: CSSProperties; activeStyle?: CSSProperties; children?: ReactNode; @@ -164,17 +162,15 @@ type AnchorLinkProps = { export function AnchorLink({ to, - exact, style, activeStyle, children, }: AnchorLinkProps) { - let match = useRouteMatch({ path: to, exact: true }); + let match = useMatch({ path: to }); return ( <NavLink to={to} - exact={exact} {...css([styles.smallText, style, match ? activeStyle : null])} > {children} @@ -221,40 +217,33 @@ export const ExternalLink = forwardRef<HTMLElement, ExternalLinkProps>( }, ); -type ButtonLinkProps = ComponentProps<typeof Button> & - RouteComponentProps & { - to: string; - activeStyle?: CSSProperties; - }; -function ButtonLink_({ - history, - staticContext, +type ButtonLinkProps = ComponentProps<typeof Button> & { + to: string; + activeStyle?: CSSProperties; +}; +export function ButtonLink({ to, style, activeStyle, - match, - location, ...props }: ButtonLinkProps) { + const navigate = useNavigate(); + const match = useMatch({ path: to }); return ( - <Route - path={to} - children={({ match }) => ( - <Button - style={[style, match ? activeStyle : null]} - {...props} - onClick={e => { - props.onClick && props.onClick(e); - history.push(to); - }} - /> - )} + <Button + style={{ + ...style, + ...(match ? activeStyle : {}), + }} + {...props} + onClick={e => { + props.onClick && props.onClick(e); + navigate(to); + }} /> ); } -export const ButtonLink = withRouter(ButtonLink_); - type InputWithContentProps = ComponentProps<typeof Input> & { leftContent: ReactNode; rightContent: ReactNode; diff --git a/packages/desktop-client/src/components/manager/ConfigServer.js b/packages/desktop-client/src/components/manager/ConfigServer.js index a4e7a1235..50f08f6a4 100644 --- a/packages/desktop-client/src/components/manager/ConfigServer.js +++ b/packages/desktop-client/src/components/manager/ConfigServer.js @@ -1,6 +1,6 @@ import React, { useState, useEffect } from 'react'; import { useDispatch } from 'react-redux'; -import { useHistory } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import { createBudget } from 'loot-core/src/client/actions/budgets'; import { signOut, loggedIn } from 'loot-core/src/client/actions/user'; @@ -19,7 +19,7 @@ import { Title, Input } from './subscribe/common'; export default function ConfigServer() { useSetThemeColor(colors.p5); let dispatch = useDispatch(); - let history = useHistory(); + let navigate = useNavigate(); let [url, setUrl] = useState(''); let currentUrl = useServerURL(); let setServerUrl = useSetServerURL(); @@ -58,7 +58,7 @@ export default function ConfigServer() { setError(error); } else { await dispatch(signOut()); - history.push('/'); + navigate('/'); } setLoading(false); } else if (error) { @@ -67,7 +67,7 @@ export default function ConfigServer() { } else { setLoading(false); await dispatch(signOut()); - history.push('/'); + navigate('/'); } } @@ -78,13 +78,13 @@ export default function ConfigServer() { async function onSkip() { await setServerUrl(null); await dispatch(loggedIn()); - history.push('/'); + navigate('/'); } async function onCreateTestFile() { await setServerUrl(null); await dispatch(createBudget({ testMode: true })); - window.__history.push('/'); + window.__navigate('/'); } return ( @@ -135,7 +135,7 @@ export default function ConfigServer() { <Input autoFocus={true} placeholder={'https://example.com'} - value={url} + value={url || ''} onChange={e => setUrl(e.target.value)} style={{ flex: 1, marginRight: 10 }} /> @@ -147,7 +147,7 @@ export default function ConfigServer() { bare type="button" style={{ fontSize: 15, marginLeft: 10 }} - onClick={() => history.goBack()} + onClick={() => navigate(-1)} > Cancel </Button> diff --git a/packages/desktop-client/src/components/manager/ManagementApp.js b/packages/desktop-client/src/components/manager/ManagementApp.js index 6e8d52e5f..4d7038f26 100644 --- a/packages/desktop-client/src/components/manager/ManagementApp.js +++ b/packages/desktop-client/src/components/manager/ManagementApp.js @@ -1,13 +1,12 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect } from 'react'; import { connect } from 'react-redux'; -import { Switch, Redirect, Router, Route } from 'react-router-dom'; - -import { createBrowserHistory } from 'history'; +import { Navigate, BrowserRouter, Route, Routes } from 'react-router-dom'; import * as actions from 'loot-core/src/client/actions'; import { colors } from '../../style'; import tokens from '../../tokens'; +import { ExposeNavigate } from '../../util/router-tools'; import { View, Text } from '../common'; import LoggedInUser from '../LoggedInUser'; import Notifications from '../Notifications'; @@ -58,9 +57,6 @@ function ManagementApp({ getUserData, loadAllFiles, }) { - const [history] = useState(createBrowserHistory); - window.__history = history; - // runs on mount only useEffect(() => { // An action may have been triggered from outside, and we don't @@ -107,7 +103,8 @@ function ManagementApp({ } return ( - <Router history={history}> + <BrowserRouter> + <ExposeNavigate /> <View style={{ height: '100%' }}> <View style={{ @@ -150,25 +147,18 @@ function ManagementApp({ > {userData && files ? ( <> - <Switch> - <Route exact path="/config-server"> - <ConfigServer /> - </Route> - <Route exact path="/change-password"> - <ChangePassword /> - </Route> + <Routes> + <Route path="/config-server" element={<ConfigServer />} /> + + <Route path="/change-password" element={<ChangePassword />} /> {files && files.length > 0 ? ( - <Route exact path="/"> - <BudgetList /> - </Route> + <Route path="/" element={<BudgetList />} /> ) : ( - <Route exact path="/"> - <WelcomeScreen /> - </Route> + <Route path="/" element={<WelcomeScreen />} /> )} {/* Redirect all other pages to this route */} - <Route path="/" render={() => <Redirect to="/" />} /> - </Switch> + <Route path="/*" element={<Navigate to="/" />} /> + </Routes> <View style={{ @@ -179,48 +169,44 @@ function ManagementApp({ zIndex: 4000, }} > - <Switch> - <Route exact path="/config-server" children={null} /> - <Route exact path="/"> - <LoggedInUser - hideIfNoServer - style={{ padding: '4px 7px' }} - /> - </Route> - </Switch> + <Routes> + <Route path="/config-server" element={null} /> + <Route + path="/*" + element={ + <LoggedInUser + hideIfNoServer + style={{ padding: '4px 7px' }} + /> + } + /> + </Routes> </View> </> ) : ( - <Switch> - <Route exact path="/login"> - <Login /> - </Route> - <Route exact path="/error"> - <Error /> - </Route> - <Route exact path="/config-server"> - <ConfigServer /> - </Route> - <Route exact path="/bootstrap"> - <Bootstrap /> - </Route> + <Routes> + <Route path="/login" element={<Login />} /> + <Route path="/error" element={<Error />} /> + <Route path="/config-server" element={<ConfigServer />} /> + <Route path="/bootstrap" element={<Bootstrap />} /> {/* Redirect all other pages to this route */} - <Route path="/" render={() => <Redirect to="/bootstrap" />} /> - </Switch> + <Route + path="/*" + element={<Navigate to="/bootstrap" replace />} + /> + </Routes> )} </View> )} - <Switch> - <Route exact path="/config-server" children={null} /> - <Route path="/"> - <ServerURL /> - </Route> - </Switch> + <Routes> + <Route path="/config-server" element={null} /> + <Route path="/*" element={<ServerURL />} /> + </Routes> <Version /> </View> - <Modals history={history} /> - </Router> + <Modals /> + </BrowserRouter> ); } diff --git a/packages/desktop-client/src/components/manager/Modals.js b/packages/desktop-client/src/components/manager/Modals.js index bbc467122..d945e7369 100644 --- a/packages/desktop-client/src/components/manager/Modals.js +++ b/packages/desktop-client/src/components/manager/Modals.js @@ -16,15 +16,7 @@ import ImportActual from './ImportActual'; import ImportYNAB4 from './ImportYNAB4'; import ImportYNAB5 from './ImportYNAB5'; -function Modals({ - modalStack, - isHidden, - allFiles, - availableImports, - globalPrefs, - isLoggedIn, - actions, -}) { +function Modals({ modalStack, isHidden, availableImports, actions }) { let stack = modalStack.map(({ name, options }, idx) => { const modalProps = { onClose: actions.popModal, @@ -111,9 +103,6 @@ export default connect( isHidden: state.modals.isHidden, budgets: state.budgets.budgets, availableImports: state.budgets.availableImports, - globalPrefs: state.prefs.global, - allFiles: state.budgets.allFiles, - isLoggedIn: !!state.user.data, }), dispatch => ({ actions: bindActionCreators(actions, dispatch) }), )(Modals); diff --git a/packages/desktop-client/src/components/manager/subscribe/ChangePassword.tsx b/packages/desktop-client/src/components/manager/subscribe/ChangePassword.tsx index 36a7965b4..b8ffeb0df 100644 --- a/packages/desktop-client/src/components/manager/subscribe/ChangePassword.tsx +++ b/packages/desktop-client/src/components/manager/subscribe/ChangePassword.tsx @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import { useHistory } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import { send } from 'loot-core/src/platform/client/fetch'; @@ -10,7 +10,7 @@ import { Title } from './common'; import { ConfirmPasswordForm } from './ConfirmPasswordForm'; export default function ChangePassword() { - let history = useHistory(); + let navigate = useNavigate(); let [error, setError] = useState(null); let [msg, setMessage] = useState(null); @@ -36,7 +36,7 @@ export default function ChangePassword() { } else { setMessage('Password successfully changed'); await send('subscribe-sign-in', { password }); - history.push('/'); + navigate('/'); } } @@ -86,7 +86,7 @@ export default function ChangePassword() { bare type="button" style={{ fontSize: 15, marginRight: 10 }} - onClick={() => history.push('/')} + onClick={() => navigate('/')} > Cancel </Button> diff --git a/packages/desktop-client/src/components/manager/subscribe/Error.tsx b/packages/desktop-client/src/components/manager/subscribe/Error.tsx index b83a3f5e0..6a4ff3b05 100644 --- a/packages/desktop-client/src/components/manager/subscribe/Error.tsx +++ b/packages/desktop-client/src/components/manager/subscribe/Error.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { useHistory, useLocation } from 'react-router-dom'; +import { useNavigate, useLocation } from 'react-router-dom'; import { colors } from '../../../style'; import { View, Text, Button } from '../../common'; @@ -14,12 +14,12 @@ function getErrorMessage(reason) { } export default function Error() { - let history = useHistory(); + let navigate = useNavigate(); let location = useLocation(); let { error } = (location.state || {}) as { error? }; function onTryAgain() { - history.push('/'); + navigate('/'); } return ( diff --git a/packages/desktop-client/src/components/manager/subscribe/common.tsx b/packages/desktop-client/src/components/manager/subscribe/common.tsx index 73b2c58b0..f7fbc44f1 100644 --- a/packages/desktop-client/src/components/manager/subscribe/common.tsx +++ b/packages/desktop-client/src/components/manager/subscribe/common.tsx @@ -4,7 +4,7 @@ import React, { useEffect, useState, } from 'react'; -import { useHistory, useLocation } from 'react-router-dom'; +import { useNavigate, useLocation } from 'react-router-dom'; import { send } from 'loot-core/src/platform/client/fetch'; @@ -23,7 +23,7 @@ import { useSetServerURL } from '../../ServerContext'; // do any checks. export function useBootstrapped() { let [checked, setChecked] = useState(false); - let history = useHistory(); + let navigate = useNavigate(); let location = useLocation(); let setServerURL = useSetServerURL(); @@ -31,7 +31,7 @@ export function useBootstrapped() { async function run() { let ensure = url => { if (location.pathname !== url) { - history.push(url); + navigate(url); } else { setChecked(true); } @@ -47,7 +47,7 @@ export function useBootstrapped() { }); if ('error' in result || !result.hasServer) { console.log('error' in result && result.error); - history.push('/config-server'); + navigate('/config-server'); return; } @@ -61,7 +61,7 @@ export function useBootstrapped() { } else { let result = await send('subscribe-needs-bootstrap'); if ('error' in result) { - history.push('/error', { error: result.error }); + navigate('/error', { state: { error: result.error } }); } else if (result.bootstrapped) { ensure('/login'); } else { @@ -70,7 +70,7 @@ export function useBootstrapped() { } } run(); - }, [history, location]); + }, [location]); return { checked }; } diff --git a/packages/desktop-client/src/components/modals/CreateLocalAccount.js b/packages/desktop-client/src/components/modals/CreateLocalAccount.js index 2e4ec4a25..d4b8efcdf 100644 --- a/packages/desktop-client/src/components/modals/CreateLocalAccount.js +++ b/packages/desktop-client/src/components/modals/CreateLocalAccount.js @@ -1,4 +1,5 @@ import React from 'react'; +import { useNavigate } from 'react-router-dom'; import { Formik } from 'formik'; @@ -17,7 +18,8 @@ import { Text, } from '../common'; -function CreateLocalAccount({ modalProps, actions, history }) { +function CreateLocalAccount({ modalProps, actions }) { + let navigate = useNavigate(); return ( <Modal title="Create Local Account" {...modalProps} showBack={false}> {() => ( @@ -43,7 +45,7 @@ function CreateLocalAccount({ modalProps, actions, history }) { toRelaxedNumber(values.balance), values.offbudget, ); - history.push('/accounts/' + id); + navigate('/accounts/' + id); } }} render={({ diff --git a/packages/desktop-client/src/components/modals/EditRule.js b/packages/desktop-client/src/components/modals/EditRule.js index 06cc775e6..f0ce39494 100644 --- a/packages/desktop-client/src/components/modals/EditRule.js +++ b/packages/desktop-client/src/components/modals/EditRule.js @@ -581,7 +581,6 @@ let conditionFields = [ ]); export default function EditRule({ - history, modalProps, defaultRule, onSave: originalOnSave, diff --git a/packages/desktop-client/src/components/modals/MergeUnusedPayees.js b/packages/desktop-client/src/components/modals/MergeUnusedPayees.js index bbccf33a9..62cd244ef 100644 --- a/packages/desktop-client/src/components/modals/MergeUnusedPayees.js +++ b/packages/desktop-client/src/components/modals/MergeUnusedPayees.js @@ -11,7 +11,6 @@ import { View, Text, Modal, ModalButtons, Button, P } from '../common'; let highlightStyle = { color: colors.p5 }; export default function MergeUnusedPayees({ - history, modalProps, payeeIds, targetPayeeId, diff --git a/packages/desktop-client/src/components/reports/Overview.js b/packages/desktop-client/src/components/reports/Overview.js index 264180009..31a5f14bd 100644 --- a/packages/desktop-client/src/components/reports/Overview.js +++ b/packages/desktop-client/src/components/reports/Overview.js @@ -49,7 +49,6 @@ function Card({ flex, to, style, children }) { return ( <AnchorLink to={to} - exact style={[{ textDecoration: 'none', flex }, containerProps]} > {content} diff --git a/packages/desktop-client/src/components/reports/index.js b/packages/desktop-client/src/components/reports/index.js index 75af78499..b0ccfe63c 100644 --- a/packages/desktop-client/src/components/reports/index.js +++ b/packages/desktop-client/src/components/reports/index.js @@ -1,5 +1,5 @@ import React from 'react'; -import { Route } from 'react-router-dom'; +import { Route, Routes } from 'react-router-dom'; import { View } from '../common'; @@ -10,15 +10,11 @@ import Overview from './Overview'; export default function Reports() { return ( <View style={{ flex: 1 }} data-testid="reports-page"> - <Route path="/reports" exact> - <Overview /> - </Route> - <Route path="/reports/net-worth" exact> - <NetWorth /> - </Route> - <Route path="/reports/cash-flow" exact> - <CashFlow /> - </Route> + <Routes> + <Route path="/" element={<Overview />} /> + <Route path="/net-worth" element={<NetWorth />} /> + <Route path="/cash-flow" element={<CashFlow />} /> + </Routes> </View> ); } diff --git a/packages/desktop-client/src/components/schedules/DiscoverSchedules.js b/packages/desktop-client/src/components/schedules/DiscoverSchedules.js index 4978e64bc..bea585f1e 100644 --- a/packages/desktop-client/src/components/schedules/DiscoverSchedules.js +++ b/packages/desktop-client/src/components/schedules/DiscoverSchedules.js @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import { useHistory } from 'react-router-dom'; +import { Navigate, useLocation, useNavigate } from 'react-router-dom'; import q, { runQuery } from 'loot-core/src/client/query-helpers'; import { send } from 'loot-core/src/platform/client/fetch'; @@ -12,6 +12,7 @@ import useSelected, { } from '../../hooks/useSelected'; import useSendPlatformRequest from '../../hooks/useSendPlatformRequest'; import { colors } from '../../style'; +import { getParent } from '../../util/router-tools'; import { View, Stack, ButtonWithLoading, P } from '../common'; import { Page, usePageType } from '../Page'; import { Table, TableHeader, Row, Field, SelectCell } from '../table'; @@ -109,13 +110,20 @@ function DiscoverSchedulesTable({ schedules, loading }) { export default function DiscoverSchedules() { let pageType = usePageType(); - let history = useHistory(); - let { data: schedules = [], isLoading } = + let navigate = useNavigate(); + let { data: schedules, isLoading } = useSendPlatformRequest('schedule/discover'); + if (!schedules) schedules = []; + let [creating, setCreating] = useState(false); let selectedInst = useSelected('discover-schedules', schedules, []); + let location = useLocation(); + if (!getParent(location)) { + return <Navigate to="/schedules" replace />; + } + async function onCreate() { let selected = schedules.filter(s => selectedInst.items.has(s.id)); setCreating(true); @@ -144,7 +152,7 @@ export default function DiscoverSchedules() { } setCreating(false); - history.goBack(); + navigate(-1); } return ( diff --git a/packages/desktop-client/src/components/schedules/EditSchedule.js b/packages/desktop-client/src/components/schedules/EditSchedule.js index 23c6c4b89..32bd37231 100644 --- a/packages/desktop-client/src/components/schedules/EditSchedule.js +++ b/packages/desktop-client/src/components/schedules/EditSchedule.js @@ -1,6 +1,6 @@ import React, { useEffect, useReducer } from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import { useParams, useHistory } from 'react-router-dom'; +import { useParams, useNavigate } from 'react-router-dom'; import { pushModal } from 'loot-core/src/client/actions/modals'; import { useCachedPayees } from 'loot-core/src/client/data-hooks/payees'; @@ -81,7 +81,7 @@ export default function ScheduleDetails() { let { id, initialFields } = useParams(); let adding = id == null; let payees = useCachedPayees({ idKey: true }); - let history = useHistory(); + let navigate = useNavigate(); let globalDispatch = useDispatch(); let dateFormat = useSelector(state => { return state.prefs.local.dateFormat || 'MM/dd/yyyy'; @@ -379,7 +379,7 @@ export default function ScheduleDetails() { if (adding) { await onLinkTransactions([...selectedInst.items], res.data); } - history.goBack(); + navigate(-1); } } @@ -769,7 +769,7 @@ export default function ScheduleDetails() { style={{ marginTop: 20 }} > {state.error && <Text style={{ color: colors.r4 }}>{state.error}</Text>} - <Button style={{ marginRight: 10 }} onClick={() => history.goBack()}> + <Button style={{ marginRight: 10 }} onClick={() => navigate(-1)}> Cancel </Button> <Button primary onClick={onSave}> diff --git a/packages/desktop-client/src/components/schedules/LinkSchedule.js b/packages/desktop-client/src/components/schedules/LinkSchedule.js index e27d20df6..0ab090f6c 100644 --- a/packages/desktop-client/src/components/schedules/LinkSchedule.js +++ b/packages/desktop-client/src/components/schedules/LinkSchedule.js @@ -1,5 +1,5 @@ import React, { useCallback, useState } from 'react'; -import { useLocation, useHistory } from 'react-router-dom'; +import { useLocation, useNavigate } from 'react-router-dom'; import { useSchedules } from 'loot-core/src/client/data-hooks/schedules'; import { send } from 'loot-core/src/platform/client/fetch'; @@ -11,7 +11,7 @@ import { SchedulesTable } from './SchedulesTable'; export default function ScheduleLink() { let location = useLocation(); - let history = useHistory(); + let navigate = useNavigate(); let scheduleData = useSchedules( useCallback(query => query.filter({ completed: false }), []), ); @@ -32,7 +32,7 @@ export default function ScheduleLink() { updated: ids.map(id => ({ id, schedule: scheduleId })), }); } - history.goBack(); + navigate(-1); } return ( diff --git a/packages/desktop-client/src/components/schedules/PostsOfflineNotification.js b/packages/desktop-client/src/components/schedules/PostsOfflineNotification.js index e770b0a18..43a6b19f2 100644 --- a/packages/desktop-client/src/components/schedules/PostsOfflineNotification.js +++ b/packages/desktop-client/src/components/schedules/PostsOfflineNotification.js @@ -1,5 +1,5 @@ import React from 'react'; -import { useLocation, useHistory } from 'react-router-dom'; +import { useLocation, useNavigate } from 'react-router-dom'; import { send } from 'loot-core/src/platform/client/fetch'; @@ -10,18 +10,18 @@ import DisplayId from '../util/DisplayId'; export default function PostsOfflineNotification() { let location = useLocation(); - let history = useHistory(); + let navigate = useNavigate(); let payees = (location.state && location.state.payees) || []; let plural = payees.length > 1; function onClose() { - history.goBack(); + navigate(-1); } async function onPost() { await send('schedule/force-run-service'); - history.goBack(); + navigate(-1); } return ( diff --git a/packages/desktop-client/src/components/schedules/index.js b/packages/desktop-client/src/components/schedules/index.js index e54490884..90bc38270 100644 --- a/packages/desktop-client/src/components/schedules/index.js +++ b/packages/desktop-client/src/components/schedules/index.js @@ -1,16 +1,16 @@ import React, { useState } from 'react'; -import { useHistory } from 'react-router-dom'; import { useSchedules } from 'loot-core/src/client/data-hooks/schedules'; import { send } from 'loot-core/src/platform/client/fetch'; +import { usePushModal } from '../../util/router-tools'; import { View, Button, Search } from '../common'; import { Page } from '../Page'; import { SchedulesTable, ROW_HEIGHT } from './SchedulesTable'; export default function Schedules() { - let history = useHistory(); + let pushModal = usePushModal(); let [filter, setFilter] = useState(''); @@ -23,15 +23,15 @@ export default function Schedules() { let { schedules, statuses } = scheduleData; function onEdit(id) { - history.push(`/schedule/edit/${id}`, { locationPtr: history.location }); + pushModal(`/schedule/edit/${id}`); } function onAdd() { - history.push(`/schedule/edit`, { locationPtr: history.location }); + pushModal('/schedule/edit'); } function onDiscover() { - history.push(`/schedule/discover`, { locationPtr: history.location }); + pushModal('/schedule/discover'); } async function onAction(name, id) { diff --git a/packages/desktop-client/src/components/sidebar.js b/packages/desktop-client/src/components/sidebar.js index db1cd3c12..5c3efed88 100644 --- a/packages/desktop-client/src/components/sidebar.js +++ b/packages/desktop-client/src/components/sidebar.js @@ -30,7 +30,6 @@ const fontWeight = 600; function ItemContent({ style, to, - exact, onClick, activeStyle, forceActive, @@ -57,7 +56,6 @@ function ItemContent({ ) : ( <AnchorLink to={to} - exact={exact} style={style} activeStyle={activeStyle} forceActive={forceActive} @@ -74,7 +72,6 @@ function Item({ style, indent = 0, to, - exact, onClick, button, forceHover = false, @@ -122,7 +119,6 @@ function Item({ <ItemContent style={linkStyle} to={to} - exact={exact} onClick={onClick} activeStyle={activeStyle} forceActive={forceActive} @@ -134,16 +130,7 @@ function Item({ ); } -function SecondaryItem({ - Icon, - title, - style, - to, - exact, - onClick, - bold, - indent = 0, -}) { +function SecondaryItem({ Icon, title, style, to, onClick, bold, indent = 0 }) { const hoverStyle = { backgroundColor: colors.n2, }; @@ -183,7 +170,6 @@ function SecondaryItem({ <ItemContent style={linkStyle} to={to} - exact={exact} onClick={onClick} activeStyle={activeStyle} > diff --git a/packages/desktop-client/src/global-events.js b/packages/desktop-client/src/global-events.js index d50a41b39..059745440 100644 --- a/packages/desktop-client/src/global-events.js +++ b/packages/desktop-client/src/global-events.js @@ -36,11 +36,13 @@ export function handleGlobalEvents(actions, store) { }); listen('schedules-offline', ({ payees }) => { - let history = window.__history; - if (history) { - history.push(`/schedule/posts-offline-notification`, { - locationPtr: history.location, - payees, + let navigate = window.__navigate; + if (navigate) { + navigate(`/schedule/posts-offline-notification`, { + state: { + locationPtr: navigate.location, + payees, + }, }); } }); diff --git a/packages/desktop-client/src/util/location-state.js b/packages/desktop-client/src/util/location-state.js deleted file mode 100644 index 246d034df..000000000 --- a/packages/desktop-client/src/util/location-state.js +++ /dev/null @@ -1,12 +0,0 @@ -let VERSION = Date.now(); - -export function makeLocationState(state) { - return { ...state, _version: VERSION }; -} - -export function getLocationState(location, subfield) { - if (location.state && location.state._version === VERSION) { - return subfield ? location.state[subfield] : location.state; - } - return null; -} diff --git a/packages/desktop-client/src/util/router-tools.tsx b/packages/desktop-client/src/util/router-tools.tsx new file mode 100644 index 000000000..a8fc7ea7d --- /dev/null +++ b/packages/desktop-client/src/util/router-tools.tsx @@ -0,0 +1,71 @@ +import { type ReactNode, useCallback, useLayoutEffect } from 'react'; +import { + type Location, + type To, + useLocation, + useNavigate, +} from 'react-router-dom'; + +import { ActiveLocationProvider } from '../components/ActiveLocation'; +import { PageTypeProvider } from '../components/Page'; + +let VERSION = Date.now(); + +export function ExposeNavigate() { + let navigate = useNavigate(); + useLayoutEffect(() => { + window.__navigate = navigate; + }, [navigate]); + return null; +} + +export function usePushModal() { + let navigate = useNavigate(); + let location = useLocation(); + + return useCallback( + (path: To) => + navigate(path, { state: { parent: location, _version: VERSION } }), + [navigate, location], + ); +} + +export function getParent(location: Location): Location | null { + if (location.state?._version !== VERSION) { + return null; + } + return location.state?.parent || null; +} + +export function StackedRoutes({ + render, +}: { + render: (loc: Location) => ReactNode; +}) { + let location = useLocation(); + let parent = getParent(location); + + let locations = [location]; + while (parent) { + locations.unshift(parent); + parent = getParent(parent); + } + + let base = locations[0]; + let stack = locations.slice(1); + + return ( + <ActiveLocationProvider location={locations[locations.length - 1]}> + {render(base)} + {stack.map((location, idx) => ( + <PageTypeProvider + key={location.key} + type="modal" + current={idx === stack.length - 1} + > + {render(location)} + </PageTypeProvider> + ))} + </ActiveLocationProvider> + ); +} diff --git a/packages/desktop-electron/menu.js b/packages/desktop-electron/menu.js index f6a23e037..f47249599 100644 --- a/packages/desktop-electron/menu.js +++ b/packages/desktop-electron/menu.js @@ -131,7 +131,9 @@ function getMenu(isDev, createWindow) { enabled: false, click: function (menuItem, focusedWin) { focusedWin.webContents.executeJavaScript( - '__history && __history.push("/schedule/discover", { locationPtr: __history.location })', + // TODO: fix + // '__navigate && __history.push("/schedule/discover", { locationPtr: __history.location })', + 'alert("Not implemented")', ); }, }, diff --git a/packages/loot-core/src/client/actions/budgets.ts b/packages/loot-core/src/client/actions/budgets.ts index 5eff493d1..d9b6f18df 100644 --- a/packages/loot-core/src/client/actions/budgets.ts +++ b/packages/loot-core/src/client/actions/budgets.ts @@ -165,7 +165,7 @@ export function importBudget(filepath, type) { dispatch(closeModal()); await dispatch(loadPrefs()); - window.__history.push('/budget'); + window.__navigate('/budget'); }; } diff --git a/packages/loot-core/typings/window.d.ts b/packages/loot-core/typings/window.d.ts index 57e0efae4..3eea72bc6 100644 --- a/packages/loot-core/typings/window.d.ts +++ b/packages/loot-core/typings/window.d.ts @@ -8,9 +8,6 @@ declare global { openURLInBrowser: (url: string) => void; }; - __history?: { - location; - push(url: string, opts?: unknown): void; - }; + __navigate?: ReturnType<import('react-router')['useNavigate']>; } } diff --git a/upcoming-release-notes/1066.md b/upcoming-release-notes/1066.md new file mode 100644 index 000000000..d4af039d4 --- /dev/null +++ b/upcoming-release-notes/1066.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [trevdor] +--- + +Upgrade to react-router v6 and adopt v6 routing conventions. diff --git a/yarn.lock b/yarn.lock index ecc5f02a1..0bfe01471 100644 --- a/yarn.lock +++ b/yarn.lock @@ -111,9 +111,7 @@ __metadata: react-merge-refs: ^1.1.0 react-modal: 3.16.1 react-redux: 7.2.1 - react-router: 5.2.0 - react-router-dom: 5.2.0 - react-router-dom-v5-compat: ^6.4.1 + react-router-dom: 6.11.2 react-scripts: ^5.0.1 react-spring: ^9.7.1 react-virtualized-auto-sizer: ^1.0.2 @@ -1723,7 +1721,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.14.8, @babel/runtime@npm:^7.16.3, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.9.2": +"@babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.14.8, @babel/runtime@npm:^7.16.3, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.9.2": version: 7.22.5 resolution: "@babel/runtime@npm:7.22.5" dependencies: @@ -3276,10 +3274,10 @@ __metadata: languageName: node linkType: hard -"@remix-run/router@npm:1.6.1": - version: 1.6.1 - resolution: "@remix-run/router@npm:1.6.1" - checksum: 4ca65d9c7d6fa277227ad8fd4ef53bebab99460b714d835b609c998f9a7e7c33a964ce2b8af853b50025a60d9113968f256abc5f71f451939ff14a5187d327fe +"@remix-run/router@npm:1.6.2": + version: 1.6.2 + resolution: "@remix-run/router@npm:1.6.2" + checksum: 5969d313bff6ba5c75917910090cebafda84b9d3b4b453fae6b3d60fea9f938078578ffca769c532ab7ce252cd4a207b78d1024d7c727ab80dd572e62fd3b3f2 languageName: node linkType: hard @@ -9851,30 +9849,7 @@ __metadata: languageName: node linkType: hard -"history@npm:^4.9.0": - version: 4.10.1 - resolution: "history@npm:4.10.1" - dependencies: - "@babel/runtime": ^7.1.2 - loose-envify: ^1.2.0 - resolve-pathname: ^3.0.0 - tiny-invariant: ^1.0.2 - tiny-warning: ^1.0.0 - value-equal: ^1.0.1 - checksum: addd84bc4683929bae4400419b5af132ff4e4e9b311a0d4e224579ea8e184a6b80d7f72c55927e4fa117f69076a9e47ce082d8d0b422f1a9ddac7991490ca1d0 - languageName: node - linkType: hard - -"history@npm:^5.3.0": - version: 5.3.0 - resolution: "history@npm:5.3.0" - dependencies: - "@babel/runtime": ^7.7.6 - checksum: d73c35df49d19ac172f9547d30a21a26793e83f16a78386d99583b5bf1429cc980799fcf1827eb215d31816a6600684fba9686ce78104e23bd89ec239e7c726f - languageName: node - linkType: hard - -"hoist-non-react-statics@npm:^3.1.0, hoist-non-react-statics@npm:^3.3.0": +"hoist-non-react-statics@npm:^3.3.0": version: 3.3.2 resolution: "hoist-non-react-statics@npm:3.3.2" dependencies: @@ -10768,13 +10743,6 @@ __metadata: languageName: node linkType: hard -"isarray@npm:0.0.1": - version: 0.0.1 - resolution: "isarray@npm:0.0.1" - checksum: 49191f1425681df4a18c2f0f93db3adb85573bcdd6a4482539d98eac9e705d8961317b01175627e860516a2fc45f8f9302db26e5a380a97a520e272e2a40a8d4 - languageName: node - linkType: hard - "isarray@npm:^2.0.5": version: 2.0.5 resolution: "isarray@npm:2.0.5" @@ -12175,7 +12143,7 @@ __metadata: languageName: node linkType: hard -"loose-envify@npm:^1.0.0, loose-envify@npm:^1.1.0, loose-envify@npm:^1.2.0, loose-envify@npm:^1.3.1, loose-envify@npm:^1.4.0": +"loose-envify@npm:^1.0.0, loose-envify@npm:^1.1.0, loose-envify@npm:^1.4.0": version: 1.4.0 resolution: "loose-envify@npm:1.4.0" dependencies: @@ -12562,19 +12530,6 @@ __metadata: languageName: node linkType: hard -"mini-create-react-context@npm:^0.4.0": - version: 0.4.1 - resolution: "mini-create-react-context@npm:0.4.1" - dependencies: - "@babel/runtime": ^7.12.1 - tiny-warning: ^1.0.3 - peerDependencies: - prop-types: ^15.0.0 - react: ^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - checksum: f8cb2c7738aac355fe9ce7e8425f371b7fa90daddd5133edda4ccfdc18c49043b2ec04be6f3abf09b60a0f52549d54f158d5bfd81cdfb1a658531e5b9fe7bc6a - languageName: node - linkType: hard - "mini-css-extract-plugin@npm:^2.4.5": version: 2.7.5 resolution: "mini-css-extract-plugin@npm:2.7.5" @@ -13591,15 +13546,6 @@ __metadata: languageName: node linkType: hard -"path-to-regexp@npm:^1.7.0": - version: 1.8.0 - resolution: "path-to-regexp@npm:1.8.0" - dependencies: - isarray: 0.0.1 - checksum: 709f6f083c0552514ef4780cb2e7e4cf49b0cc89a97439f2b7cc69a608982b7690fb5d1720a7473a59806508fc2dae0be751ba49f495ecf89fd8fbc62abccbcd - languageName: node - linkType: hard - "path-type@npm:^2.0.0": version: 2.0.0 resolution: "path-type@npm:2.0.0" @@ -14783,7 +14729,7 @@ __metadata: languageName: node linkType: hard -"prop-types@npm:^15.5.10, prop-types@npm:^15.6.2, prop-types@npm:^15.7.2, prop-types@npm:^15.8.1": +"prop-types@npm:^15.5.10, prop-types@npm:^15.7.2, prop-types@npm:^15.8.1": version: 15.8.1 resolution: "prop-types@npm:15.8.1" dependencies: @@ -15046,7 +14992,7 @@ __metadata: languageName: node linkType: hard -"react-is@npm:^16.13.1, react-is@npm:^16.6.0, react-is@npm:^16.7.0, react-is@npm:^16.9.0": +"react-is@npm:^16.13.1, react-is@npm:^16.7.0, react-is@npm:^16.9.0": version: 16.13.1 resolution: "react-is@npm:16.13.1" checksum: f7a19ac3496de32ca9ae12aa030f00f14a3d45374f1ceca0af707c831b2a6098ef0d6bdae51bd437b0a306d7f01d4677fcc8de7c0d331eb47ad0f46130e53c5f @@ -15131,65 +15077,27 @@ __metadata: languageName: node linkType: hard -"react-router-dom-v5-compat@npm:^6.4.1": - version: 6.11.1 - resolution: "react-router-dom-v5-compat@npm:6.11.1" +"react-router-dom@npm:6.11.2": + version: 6.11.2 + resolution: "react-router-dom@npm:6.11.2" dependencies: - history: ^5.3.0 - react-router: 6.11.1 + "@remix-run/router": 1.6.2 + react-router: 6.11.2 peerDependencies: react: ">=16.8" react-dom: ">=16.8" - react-router-dom: 4 || 5 - checksum: 56d79c3a14c82f57bdc564f425a48311a91cbb4bcf8e6cdfa7adb59a260074606cf59073d830381111464e345642323063c25a800e287ff527b1035381811e13 - languageName: node - linkType: hard - -"react-router-dom@npm:5.2.0": - version: 5.2.0 - resolution: "react-router-dom@npm:5.2.0" - dependencies: - "@babel/runtime": ^7.1.2 - history: ^4.9.0 - loose-envify: ^1.3.1 - prop-types: ^15.6.2 - react-router: 5.2.0 - tiny-invariant: ^1.0.2 - tiny-warning: ^1.0.0 - peerDependencies: - react: ">=15" - checksum: 98d2d35f9540ac4a3c14dc023623fc8411a6a6338e95d726370e07b27c3bc6e854516537c8e3f9ad2483c4bbd579ba28cce9aff843a19fe8ebff663318886335 - languageName: node - linkType: hard - -"react-router@npm:5.2.0": - version: 5.2.0 - resolution: "react-router@npm:5.2.0" - dependencies: - "@babel/runtime": ^7.1.2 - history: ^4.9.0 - hoist-non-react-statics: ^3.1.0 - loose-envify: ^1.3.1 - mini-create-react-context: ^0.4.0 - path-to-regexp: ^1.7.0 - prop-types: ^15.6.2 - react-is: ^16.6.0 - tiny-invariant: ^1.0.2 - tiny-warning: ^1.0.0 - peerDependencies: - react: ">=15" - checksum: 6fc908729110a65a5676a9e41333e0f511a3c0ff84c93c0dc704330cf3e02124c93aaeab8031b0e2c71712390d9278fff848eeebfbdda36dca3201142f309973 + checksum: ba44ff37f2956bc18991f2eedb32a3fa46d35832f61ded6c5d167e853ca289868fca6635467866280c73bc3da00dce8437dbbec57da100c0a3e3e3850af00b83 languageName: node linkType: hard -"react-router@npm:6.11.1": - version: 6.11.1 - resolution: "react-router@npm:6.11.1" +"react-router@npm:6.11.2": + version: 6.11.2 + resolution: "react-router@npm:6.11.2" dependencies: - "@remix-run/router": 1.6.1 + "@remix-run/router": 1.6.2 peerDependencies: react: ">=16.8" - checksum: c5cafbaac13564d0e325f84ce6e4cbc42de5c381b0f619209f3b101d2b6eae4a8f9ee87b492875e869909dd9bb549d05d2f677085708f79622b872bd45d14bbb + checksum: e47f875dca70033a3b42704cb5ec076b60f9629a5cdc3be613707f3d5a5706123fb80301037455c285c6d5a1011b443e1784e0103969ebfac7071648d360c413 languageName: node linkType: hard @@ -15600,13 +15508,6 @@ __metadata: languageName: node linkType: hard -"resolve-pathname@npm:^3.0.0": - version: 3.0.0 - resolution: "resolve-pathname@npm:3.0.0" - checksum: 6147241ba42c423dbe83cb067a2b4af4f60908c3af57e1ea567729cc71416c089737fe2a73e9e79e7a60f00f66c91e4b45ad0d37cd4be2d43fec44963ef14368 - languageName: node - linkType: hard - "resolve-url-loader@npm:^4.0.0": version: 4.0.0 resolution: "resolve-url-loader@npm:4.0.0" @@ -17143,20 +17044,6 @@ __metadata: languageName: node linkType: hard -"tiny-invariant@npm:^1.0.2": - version: 1.3.1 - resolution: "tiny-invariant@npm:1.3.1" - checksum: 872dbd1ff20a21303a2fd20ce3a15602cfa7fcf9b228bd694a52e2938224313b5385a1078cb667ed7375d1612194feaca81c4ecbe93121ca1baebe344de4f84c - languageName: node - linkType: hard - -"tiny-warning@npm:^1.0.0, tiny-warning@npm:^1.0.3": - version: 1.0.3 - resolution: "tiny-warning@npm:1.0.3" - checksum: da62c4acac565902f0624b123eed6dd3509bc9a8d30c06e017104bedcf5d35810da8ff72864400ad19c5c7806fc0a8323c68baf3e326af7cb7d969f846100d71 - languageName: node - linkType: hard - "tmp-promise@npm:^3.0.2": version: 3.0.3 resolution: "tmp-promise@npm:3.0.3" @@ -17817,13 +17704,6 @@ __metadata: languageName: node linkType: hard -"value-equal@npm:^1.0.1": - version: 1.0.1 - resolution: "value-equal@npm:1.0.1" - checksum: bb7ae1facc76b5cf8071aeb6c13d284d023fdb370478d10a5d64508e0e6e53bb459c4bbe34258df29d82e6f561f874f0105eba38de0e61fe9edd0bdce07a77a2 - languageName: node - linkType: hard - "vary@npm:~1.1.2": version: 1.1.2 resolution: "vary@npm:1.1.2" -- GitLab