From c667118f10cee4b3f50e0a543cfffbe275927c3c Mon Sep 17 00:00:00 2001 From: Jed Fox <git@jedfox.com> Date: Tue, 8 Aug 2023 19:35:22 -0700 Subject: [PATCH] Remove page-based modals in favor of existing state-based modal logic (#1270) Co-authored-by: Trevor Farlow <trevdor@users.noreply.github.com> --- .../src/components/ActiveLocation.js | 13 -- .../src/components/FinancesApp.tsx | 190 +++++++----------- .../desktop-client/src/components/Modals.tsx | 42 ++++ .../desktop-client/src/components/Page.tsx | 52 +---- .../src/components/accounts/Account.js | 12 +- .../src/components/accounts/Header.js | 2 + .../accounts/MobileAccountDetails.js | 2 + .../src/components/manager/Modals.js | 2 +- .../src/components/responsive/wide.ts | 3 - .../components/schedules/DiscoverSchedules.js | 31 ++- .../src/components/schedules/EditSchedule.js | 52 ++--- .../src/components/schedules/LinkSchedule.js | 19 +- .../schedules/PostsOfflineNotification.js | 19 +- .../src/components/schedules/index.js | 11 +- .../transactions/SelectedTransactions.js | 7 +- .../transactions/TransactionList.js | 5 +- packages/desktop-client/src/global-events.js | 5 +- .../desktop-client/src/util/router-tools.tsx | 71 +------ packages/desktop-electron/menu.js | 2 +- .../src/client/state-types/modals.d.ts | 8 + packages/loot-core/typings/window.d.ts | 3 - upcoming-release-notes/1270.md | 6 + 22 files changed, 206 insertions(+), 351 deletions(-) delete mode 100644 packages/desktop-client/src/components/ActiveLocation.js create mode 100644 upcoming-release-notes/1270.md diff --git a/packages/desktop-client/src/components/ActiveLocation.js b/packages/desktop-client/src/components/ActiveLocation.js deleted file mode 100644 index 65b8a050b..000000000 --- a/packages/desktop-client/src/components/ActiveLocation.js +++ /dev/null @@ -1,13 +0,0 @@ -import React, { createContext, useContext } from 'react'; - -let ActiveLocationContext = createContext(null); - -export function ActiveLocationProvider({ location, children }) { - return ( - <ActiveLocationContext.Provider value={location} children={children} /> - ); -} - -export function useActiveLocation() { - return useContext(ActiveLocationContext); -} diff --git a/packages/desktop-client/src/components/FinancesApp.tsx b/packages/desktop-client/src/components/FinancesApp.tsx index 4f5b0f8d2..e161aec5a 100644 --- a/packages/desktop-client/src/components/FinancesApp.tsx +++ b/packages/desktop-client/src/components/FinancesApp.tsx @@ -26,7 +26,7 @@ import PiggyBank from '../icons/v1/PiggyBank'; import Wallet from '../icons/v1/Wallet'; import { useResponsive } from '../ResponsiveProvider'; import { theme, styles } from '../style'; -import { ExposeNavigate, StackedRoutes } from '../util/router-tools'; +import { ExposeNavigate } from '../util/router-tools'; import { getIsOutdated, getLatestVersion } from '../util/versions'; import BankSyncStatus from './BankSyncStatus'; @@ -40,7 +40,6 @@ import Notifications from './Notifications'; import { ManagePayeesPage } from './payees/ManagePayeesPage'; import Reports from './reports'; import { NarrowAlternate, WideComponent } from './responsive'; -import PostsOfflineNotification from './schedules/PostsOfflineNotification'; import Settings from './settings'; import Titlebar, { TitlebarProvider } from './Titlebar'; import { TransactionEdit } from './transactions/MobileTransaction'; @@ -73,120 +72,6 @@ function WideNotSupported({ children, redirectTo = '/budget' }) { return isNarrowWidth ? children : null; } -function StackedRoutesInner({ location }) { - return ( - <Routes location={location}> - <Route path="/" element={<Navigate to="/budget" replace />} /> - - <Route - path="/reports/*" - element={ - <NarrowNotSupported> - {/* Has its own lazy loading logic */} - <Reports /> - </NarrowNotSupported> - } - /> - - <Route path="/budget" element={<NarrowAlternate name="Budget" />} /> - - <Route - path="/schedules" - element={ - <NarrowNotSupported> - <WideComponent name="Schedules" /> - </NarrowNotSupported> - } - /> - - <Route - path="/schedule/edit" - element={ - <NarrowNotSupported> - <WideComponent name="EditSchedule" /> - </NarrowNotSupported> - } - /> - <Route - path="/schedule/edit/:id" - element={ - <NarrowNotSupported> - <WideComponent name="EditSchedule" /> - </NarrowNotSupported> - } - /> - <Route - path="/schedule/link" - element={ - <NarrowNotSupported> - <WideComponent name="LinkSchedule" /> - </NarrowNotSupported> - } - /> - <Route - path="/schedule/discover" - element={ - <NarrowNotSupported> - <WideComponent name="DiscoverSchedules" /> - </NarrowNotSupported> - } - /> - - <Route - path="/schedule/posts-offline-notification" - element={<PostsOfflineNotification />} - /> - - <Route path="/payees" element={<ManagePayeesPage />} /> - <Route path="/rules" element={<ManageRulesPage />} /> - <Route path="/settings" element={<Settings />} /> - - {/* TODO: remove Nordigen route after v23.8.0 */} - <Route - path="/nordigen/link" - element={ - <NarrowNotSupported> - <WideComponent name="GoCardlessLink" /> - </NarrowNotSupported> - } - /> - <Route - path="/gocardless/link" - element={ - <NarrowNotSupported> - <WideComponent name="GoCardlessLink" /> - </NarrowNotSupported> - } - /> - - <Route path="/accounts" element={<NarrowAlternate name="Accounts" />} /> - - <Route - path="/accounts/:id" - element={<NarrowAlternate name="Account" />} - /> - - <Route - path="/accounts/:id/transactions/:transactionId" - element={ - <WideNotSupported> - <TransactionEdit /> - </WideNotSupported> - } - /> - - <Route - path="/accounts/:id/transactions/new" - element={ - <WideNotSupported> - <TransactionEdit /> - </WideNotSupported> - } - /> - </Routes> - ); -} - function NavTab({ icon: TabIcon, name, path }) { return ( <NavLink @@ -311,9 +196,76 @@ function FinancesApp() { /> <Notifications /> <BankSyncStatus /> - <StackedRoutes - render={location => <StackedRoutesInner location={location} />} - /> + + <Routes> + <Route path="/" element={<Navigate to="/budget" replace />} /> + + <Route + path="/reports/*" + element={ + <NarrowNotSupported> + {/* Has its own lazy loading logic */} + <Reports /> + </NarrowNotSupported> + } + /> + + <Route + path="/budget" + element={<NarrowAlternate name="Budget" />} + /> + + <Route + path="/schedules" + element={ + <NarrowNotSupported> + <WideComponent name="Schedules" /> + </NarrowNotSupported> + } + /> + + <Route path="/payees" element={<ManagePayeesPage />} /> + <Route path="/rules" element={<ManageRulesPage />} /> + <Route path="/settings" element={<Settings />} /> + + <Route + path="/gocardless/link" + element={ + <NarrowNotSupported> + <WideComponent name="GoCardlessLink" /> + </NarrowNotSupported> + } + /> + + <Route + path="/accounts" + element={<NarrowAlternate name="Accounts" />} + /> + + <Route + path="/accounts/:id" + element={<NarrowAlternate name="Account" />} + /> + + <Route + path="/accounts/:id/transactions/:transactionId" + element={ + <WideNotSupported> + <TransactionEdit /> + </WideNotSupported> + } + /> + + <Route + path="/accounts/:id/transactions/new" + element={ + <WideNotSupported> + <TransactionEdit /> + </WideNotSupported> + } + /> + </Routes> + <Modals /> </div> diff --git a/packages/desktop-client/src/components/Modals.tsx b/packages/desktop-client/src/components/Modals.tsx index f37aa1043..43a8c4c7a 100644 --- a/packages/desktop-client/src/components/Modals.tsx +++ b/packages/desktop-client/src/components/Modals.tsx @@ -23,6 +23,10 @@ import ManageRulesModal from './modals/ManageRulesModal'; import MergeUnusedPayees from './modals/MergeUnusedPayees'; import PlaidExternalMsg from './modals/PlaidExternalMsg'; import SelectLinkedAccounts from './modals/SelectLinkedAccounts'; +import DiscoverSchedules from './schedules/DiscoverSchedules'; +import ScheduleDetails from './schedules/EditSchedule'; +import ScheduleLink from './schedules/LinkSchedule'; +import PostsOfflineNotification from './schedules/PostsOfflineNotification'; export default function Modals() { const modalStack = useSelector(state => state.modals.modalStack); @@ -218,6 +222,44 @@ export default function Modals() { /> ); + case 'schedule-edit': + return ( + <ScheduleDetails + key={name} + modalProps={modalProps} + id={options?.id || null} + actions={actions} + /> + ); + + case 'schedule-link': + return ( + <ScheduleLink + key={name} + modalProps={modalProps} + actions={actions} + transactionIds={options?.transactionIds} + /> + ); + + case 'schedules-discover': + return ( + <DiscoverSchedules + key={name} + modalProps={modalProps} + actions={actions} + /> + ); + + case 'schedule-posts-offline-notification': + return ( + <PostsOfflineNotification + key={name} + modalProps={modalProps} + actions={actions} + /> + ); + default: console.error('Unknown modal:', name); return null; diff --git a/packages/desktop-client/src/components/Page.tsx b/packages/desktop-client/src/components/Page.tsx index ae638f952..8a2068bfc 100644 --- a/packages/desktop-client/src/components/Page.tsx +++ b/packages/desktop-client/src/components/Page.tsx @@ -1,30 +1,20 @@ -import React, { createContext, type ReactNode, useContext } from 'react'; -import { useNavigate } from 'react-router-dom'; +import React, { type ReactNode } from 'react'; import { type CSSProperties } from 'glamor'; import { useResponsive } from '../ResponsiveProvider'; import { colors, styles } from '../style'; -import Modal from './common/Modal'; import Text from './common/Text'; import View from './common/View'; -let PageTypeContext = createContext({ type: 'page', current: undefined }); - -export function PageTypeProvider({ type, current, children }) { - return ( - <PageTypeContext.Provider value={{ type, current }}> - {children} - </PageTypeContext.Provider> - ); -} - -export function usePageType() { - return useContext(PageTypeContext); -} - -function PageTitle({ name, style }) { +function PageTitle({ + name, + style, +}: { + name: ReactNode; + style?: CSSProperties; +}) { const { isNarrowWidth } = useResponsive(); if (isNarrowWidth) { @@ -69,40 +59,16 @@ function PageTitle({ name, style }) { export function Page({ title, - modalSize, children, titleStyle, }: { - title: string; - modalSize?: string | { width: number; height?: number }; + title: ReactNode; children: ReactNode; titleStyle?: CSSProperties; }) { - let { type, current } = usePageType(); - let navigate = useNavigate(); let { isNarrowWidth } = useResponsive(); let HORIZONTAL_PADDING = isNarrowWidth ? 10 : 20; - if (type === 'modal') { - let size = - typeof modalSize === 'string' - ? modalSize === 'medium' - ? { width: 750, height: 600 } - : { width: 600 } - : modalSize; - - return ( - <Modal - title={title} - isCurrent={current} - size={size} - onClose={() => navigate(-1)} - > - {children} - </Modal> - ); - } - return ( <View style={isNarrowWidth ? undefined : styles.page}> <PageTitle diff --git a/packages/desktop-client/src/components/accounts/Account.js b/packages/desktop-client/src/components/accounts/Account.js index 638159a16..53bbe6761 100644 --- a/packages/desktop-client/src/components/accounts/Account.js +++ b/packages/desktop-client/src/components/accounts/Account.js @@ -28,7 +28,6 @@ import { applyChanges, groupById } from 'loot-core/src/shared/util'; import { authorizeBank } from '../../gocardless'; import { SelectedProviderWithItems } from '../../hooks/useSelected'; import { styles, colors } from '../../style'; -import { useActiveLocation } from '../ActiveLocation'; import Button from '../common/Button'; import Text from '../common/Text'; import View from '../common/View'; @@ -1144,6 +1143,7 @@ class AccountInternal extends PureComponent { hideFraction, addNotification, accountsSyncing, + pushModal, replaceModal, showExtraBalances, accountId, @@ -1226,6 +1226,7 @@ class AccountInternal extends PureComponent { filters={this.state.filters} conditionsOp={this.state.conditionsOp} savePrefs={this.props.savePrefs} + pushModal={this.props.pushModal} onSearch={this.onSearch} onShowTransactions={this.onShowTransactions} onMenuSelect={this.onMenuSelect} @@ -1301,6 +1302,7 @@ class AccountInternal extends PureComponent { </View> ) : null } + pushModal={pushModal} onSort={this.onSort} sortField={this.state.sort.field} ascDesc={this.state.sort.ascDesc} @@ -1342,7 +1344,6 @@ function AccountHack(props) { export default function Account() { let params = useParams(); let location = useLocation(); - let activeLocation = useActiveLocation(); let state = useSelector(state => ({ newTransactions: state.queries.newTransactions, @@ -1403,12 +1404,9 @@ export default function Account() { <AccountHack {...state} {...actionCreators} - modalShowing={ - state.modalShowing || - !!(activeLocation.state && activeLocation.state.parent) - } + modalShowing={state.modalShowing} accountId={params.id} - categoryId={activeLocation?.state?.filter?.category} + categoryId={location?.state?.filter?.category} location={location} filtersList={filtersList} /> diff --git a/packages/desktop-client/src/components/accounts/Header.js b/packages/desktop-client/src/components/accounts/Header.js index a22a232ed..b58ec8f4f 100644 --- a/packages/desktop-client/src/components/accounts/Header.js +++ b/packages/desktop-client/src/components/accounts/Header.js @@ -52,6 +52,7 @@ export function AccountHeader({ filters, conditionsOp, savePrefs, + pushModal, onSearch, onAddTransaction, onShowTransactions, @@ -259,6 +260,7 @@ export function AccountHeader({ onUnlink={onBatchUnlink} onCreateRule={onCreateRule} onScheduleAction={onScheduleAction} + pushModal={pushModal} /> )} <Button diff --git a/packages/desktop-client/src/components/accounts/MobileAccountDetails.js b/packages/desktop-client/src/components/accounts/MobileAccountDetails.js index 88281b6f0..4d95e5c4f 100644 --- a/packages/desktop-client/src/components/accounts/MobileAccountDetails.js +++ b/packages/desktop-client/src/components/accounts/MobileAccountDetails.js @@ -74,6 +74,7 @@ export default function AccountDetails({ onLoadMore, onSearch, onSelectTransaction, + pushModal, // refreshControl }) { let allTransactions = useMemo(() => { @@ -170,6 +171,7 @@ export default function AccountDetails({ // refreshControl={refreshControl} onLoadMore={onLoadMore} onSelect={onSelectTransaction} + pushModal={pushModal} /> </View> ); diff --git a/packages/desktop-client/src/components/manager/Modals.js b/packages/desktop-client/src/components/manager/Modals.js index 2980749df..fa6451658 100644 --- a/packages/desktop-client/src/components/manager/Modals.js +++ b/packages/desktop-client/src/components/manager/Modals.js @@ -18,7 +18,7 @@ export default function Modals() { let isHidden = useSelector(state => state.modals.isHidden); let actions = useActions(); - let stack = modalStack.map(({ name, options }, idx) => { + let stack = modalStack.map(({ name, options = {} }, idx) => { const modalProps = { onClose: actions.popModal, onPush: actions.pushModal, diff --git a/packages/desktop-client/src/components/responsive/wide.ts b/packages/desktop-client/src/components/responsive/wide.ts index cca8e725e..827a4203c 100644 --- a/packages/desktop-client/src/components/responsive/wide.ts +++ b/packages/desktop-client/src/components/responsive/wide.ts @@ -1,9 +1,6 @@ export { default as Budget } from '../budget'; export { default as Schedules } from '../schedules'; -export { default as EditSchedule } from '../schedules/EditSchedule'; -export { default as LinkSchedule } from '../schedules/LinkSchedule'; -export { default as DiscoverSchedules } from '../schedules/DiscoverSchedules'; export { default as GoCardlessLink } from '../gocardless/GoCardlessLink'; diff --git a/packages/desktop-client/src/components/schedules/DiscoverSchedules.js b/packages/desktop-client/src/components/schedules/DiscoverSchedules.js index de1a600bb..aab542cea 100644 --- a/packages/desktop-client/src/components/schedules/DiscoverSchedules.js +++ b/packages/desktop-client/src/components/schedules/DiscoverSchedules.js @@ -1,5 +1,4 @@ import React, { useState } from 'react'; -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,12 +11,11 @@ import useSelected, { } from '../../hooks/useSelected'; import useSendPlatformRequest from '../../hooks/useSendPlatformRequest'; import { colors } from '../../style'; -import { getParent } from '../../util/router-tools'; import { ButtonWithLoading } from '../common/Button'; +import Modal from '../common/Modal'; import Paragraph from '../common/Paragraph'; import Stack from '../common/Stack'; import View from '../common/View'; -import { Page, usePageType } from '../Page'; import { Table, TableHeader, Row, Field, SelectCell } from '../table'; import DisplayId from '../util/DisplayId'; @@ -26,7 +24,6 @@ import { ScheduleAmountCell } from './SchedulesTable'; let ROW_HEIGHT = 43; function DiscoverSchedulesTable({ schedules, loading }) { - let pageType = usePageType(); let selectedItems = useSelectedItems(); let dispatchSelected = useSelectedDispatch(); @@ -95,11 +92,10 @@ function DiscoverSchedulesTable({ schedules, loading }) { <Table rowHeight={ROW_HEIGHT} version="v2" - backgroundColor={pageType.type === 'modal' ? 'transparent' : undefined} + backgroundColor="transparent" style={{ flex: 1, - backgroundColor: - pageType.type === 'modal' ? 'transparent' : undefined, + backgroundColor: 'transparent', }} items={schedules} loading={loading} @@ -111,9 +107,7 @@ function DiscoverSchedulesTable({ schedules, loading }) { ); } -export default function DiscoverSchedules() { - let pageType = usePageType(); - let navigate = useNavigate(); +export default function DiscoverSchedules({ modalProps, actions }) { let { data: schedules, isLoading } = useSendPlatformRequest('schedule/discover'); if (!schedules) schedules = []; @@ -122,11 +116,6 @@ export default function DiscoverSchedules() { 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); @@ -155,11 +144,15 @@ export default function DiscoverSchedules() { } setCreating(false); - navigate(-1); + actions.popModal(); } return ( - <Page title="Found schedules" modalSize={{ width: 850, height: 650 }}> + <Modal + title="Found schedules" + size={{ width: 850, height: 650 }} + {...modalProps} + > <Paragraph> We found some possible schedules in your current transactions. Select the ones you want to create. @@ -180,7 +173,7 @@ export default function DiscoverSchedules() { justify="flex-end" style={{ paddingTop: 20, - paddingBottom: pageType.type === 'modal' ? 0 : 20, + paddingBottom: 0, }} > <ButtonWithLoading @@ -192,6 +185,6 @@ export default function DiscoverSchedules() { Create schedules </ButtonWithLoading> </Stack> - </Page> + </Modal> ); } diff --git a/packages/desktop-client/src/components/schedules/EditSchedule.js b/packages/desktop-client/src/components/schedules/EditSchedule.js index 9548eb55d..528dc372a 100644 --- a/packages/desktop-client/src/components/schedules/EditSchedule.js +++ b/packages/desktop-client/src/components/schedules/EditSchedule.js @@ -1,6 +1,5 @@ import React, { useEffect, useReducer } from 'react'; import { useDispatch, useSelector } from 'react-redux'; -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'; @@ -14,12 +13,12 @@ import { colors } from '../../style'; import AccountAutocomplete from '../autocomplete/AccountAutocomplete'; import PayeeAutocomplete from '../autocomplete/PayeeAutocomplete'; import Button from '../common/Button'; +import Modal from '../common/Modal'; import Stack from '../common/Stack'; import Text from '../common/Text'; import View from '../common/View'; import { FormField, FormLabel, Checkbox } from '../forms'; import { OpSelect } from '../modals/EditRule'; -import { Page } from '../Page'; import DateSelect from '../select/DateSelect'; import RecurringSchedulePicker from '../select/RecurringSchedulePicker'; import { SelectedItemsButton } from '../table'; @@ -27,19 +26,6 @@ import SimpleTransactionsTable from '../transactions/SimpleTransactionsTable'; import { AmountInput, BetweenAmountInput } from '../util/AmountInput'; import GenericInput from '../util/GenericInput'; -function mergeFields(defaults, initial) { - let res = { ...defaults }; - if (initial) { - // Only merge in fields from `initial` that exist in `defaults` - Object.keys(initial).forEach(key => { - if (key in defaults) { - res[key] = initial[key]; - } - }); - } - return res; -} - function updateScheduleConditions(schedule, fields) { let conds = extractScheduleConds(schedule._conditions); @@ -80,11 +66,9 @@ function updateScheduleConditions(schedule, fields) { }; } -export default function ScheduleDetails() { - let { id, initialFields } = useParams(); +export default function ScheduleDetails({ modalProps, actions, id }) { let adding = id == null; let payees = useCachedPayees({ idKey: true }); - let navigate = useNavigate(); let globalDispatch = useDispatch(); let dateFormat = useSelector(state => { return state.prefs.local.dateFormat || 'MM/dd/yyyy'; @@ -193,18 +177,15 @@ export default function ScheduleDetails() { schedule: null, upcomingDates: null, error: null, - fields: mergeFields( - { - payee: null, - account: null, - amount: null, - amountOp: null, - date: null, - posts_transaction: false, - name: null, - }, - initialFields, - ), + fields: { + payee: null, + account: null, + amount: null, + amountOp: null, + date: null, + posts_transaction: false, + name: null, + }, transactions: [], transactionsMode: adding ? 'matched' : 'linked', }, @@ -382,7 +363,7 @@ export default function ScheduleDetails() { if (adding) { await onLinkTransactions([...selectedInst.items], res.data); } - navigate(-1); + actions.popModal(); } } @@ -432,9 +413,10 @@ export default function ScheduleDetails() { let repeats = state.fields.date ? !!state.fields.date.frequency : false; return ( - <Page + <Modal title={payee ? `Schedule: ${payee.name}` : 'Schedule'} - modalSize="medium" + size="medium" + {...modalProps} > <Stack direction="row" style={{ marginTop: 10 }}> <FormField style={{ flex: 1 }}> @@ -772,13 +754,13 @@ export default function ScheduleDetails() { style={{ marginTop: 20 }} > {state.error && <Text style={{ color: colors.r4 }}>{state.error}</Text>} - <Button style={{ marginRight: 10 }} onClick={() => navigate(-1)}> + <Button style={{ marginRight: 10 }} onClick={actions.popModal}> Cancel </Button> <Button type="primary" onClick={onSave}> {adding ? 'Add' : 'Save'} </Button> </Stack> - </Page> + </Modal> ); } diff --git a/packages/desktop-client/src/components/schedules/LinkSchedule.js b/packages/desktop-client/src/components/schedules/LinkSchedule.js index b33696755..af9724ae8 100644 --- a/packages/desktop-client/src/components/schedules/LinkSchedule.js +++ b/packages/desktop-client/src/components/schedules/LinkSchedule.js @@ -1,19 +1,20 @@ import React, { useCallback, useState } from 'react'; -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'; +import Modal from '../common/Modal'; import Search from '../common/Search'; import Text from '../common/Text'; import View from '../common/View'; -import { Page } from '../Page'; import { SchedulesTable } from './SchedulesTable'; -export default function ScheduleLink() { - let location = useLocation(); - let navigate = useNavigate(); +export default function ScheduleLink({ + modalProps, + actions, + transactionIds: ids, +}) { let scheduleData = useSchedules( useCallback(query => query.filter({ completed: false }), []), ); @@ -27,18 +28,16 @@ export default function ScheduleLink() { let { schedules, statuses } = scheduleData; async function onSelect(scheduleId) { - let { state } = location; - let ids = state.transactionIds; if (ids && ids.length > 0) { await send('transactions-batch-update', { updated: ids.map(id => ({ id, schedule: scheduleId })), }); } - navigate(-1); + actions.popModal(); } return ( - <Page title="Link Schedule" modalSize="medium"> + <Modal title="Link Schedule" size="medium" {...modalProps}> <View style={{ flexDirection: 'row', marginBottom: 20, alignItems: 'center' }} > @@ -61,6 +60,6 @@ export default function ScheduleLink() { onSelect={onSelect} tableStyle={{ marginInline: -20 }} /> - </Page> + </Modal> ); } diff --git a/packages/desktop-client/src/components/schedules/PostsOfflineNotification.js b/packages/desktop-client/src/components/schedules/PostsOfflineNotification.js index 1c292825d..1261f938b 100644 --- a/packages/desktop-client/src/components/schedules/PostsOfflineNotification.js +++ b/packages/desktop-client/src/components/schedules/PostsOfflineNotification.js @@ -1,34 +1,29 @@ import React from 'react'; -import { useLocation, useNavigate } from 'react-router-dom'; +import { useLocation } from 'react-router-dom'; import { send } from 'loot-core/src/platform/client/fetch'; import { colors } from '../../style'; import Button from '../common/Button'; +import Modal from '../common/Modal'; import Paragraph from '../common/Paragraph'; import Stack from '../common/Stack'; import Text from '../common/Text'; -import { Page } from '../Page'; import DisplayId from '../util/DisplayId'; -export default function PostsOfflineNotification() { +export default function PostsOfflineNotification({ modalProps, actions }) { let location = useLocation(); - let navigate = useNavigate(); let payees = (location.state && location.state.payees) || []; let plural = payees.length > 1; - function onClose() { - navigate(-1); - } - async function onPost() { await send('schedule/force-run-service'); - navigate(-1); + actions.popModal(); } return ( - <Page title="Post transactions?" modalSize="small"> + <Modal title="Post transactions?" size="small" {...modalProps}> <Paragraph> {payees.length > 0 ? ( <Text> @@ -73,11 +68,11 @@ export default function PostsOfflineNotification() { style={{ marginTop: 20 }} spacing={2} > - <Button onClick={onClose}>Decide later</Button> + <Button onClick={actions.popModal}>Decide later</Button> <Button type="primary" onClick={onPost}> Post transactions </Button> </Stack> - </Page> + </Modal> ); } diff --git a/packages/desktop-client/src/components/schedules/index.js b/packages/desktop-client/src/components/schedules/index.js index 05f6a2198..7ca41bf82 100644 --- a/packages/desktop-client/src/components/schedules/index.js +++ b/packages/desktop-client/src/components/schedules/index.js @@ -3,7 +3,7 @@ import React, { useState } from 'react'; 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 { useActions } from '../../hooks/useActions'; import Button from '../common/Button'; import Search from '../common/Search'; import View from '../common/View'; @@ -12,8 +12,7 @@ import { Page } from '../Page'; import { SchedulesTable, ROW_HEIGHT } from './SchedulesTable'; export default function Schedules() { - let pushModal = usePushModal(); - + let { pushModal } = useActions(); let [filter, setFilter] = useState(''); let scheduleData = useSchedules(); @@ -25,15 +24,15 @@ export default function Schedules() { let { schedules, statuses } = scheduleData; function onEdit(id) { - pushModal(`/schedule/edit/${id}`); + pushModal('schedule-edit', { id }); } function onAdd() { - pushModal('/schedule/edit'); + pushModal('schedule-edit'); } function onDiscover() { - pushModal('/schedule/discover'); + pushModal('schedules-discover'); } async function onAction(name, id) { diff --git a/packages/desktop-client/src/components/transactions/SelectedTransactions.js b/packages/desktop-client/src/components/transactions/SelectedTransactions.js index dc1bd5c0f..d29843896 100644 --- a/packages/desktop-client/src/components/transactions/SelectedTransactions.js +++ b/packages/desktop-client/src/components/transactions/SelectedTransactions.js @@ -1,7 +1,6 @@ import React, { useMemo } from 'react'; import { useSelectedItems } from '../../hooks/useSelected'; -import { usePushModal } from '../../util/router-tools'; import Menu from '../common/Menu'; import { SelectedItemsButton } from '../table'; @@ -16,8 +15,8 @@ export function SelectedTransactionsButton({ onUnlink, onCreateRule, onScheduleAction, + pushModal, }) { - let pushModal = usePushModal(); let selectedItems = useSelectedItems(); let types = useMemo(() => { @@ -130,11 +129,11 @@ export function SelectedTransactionsButton({ } if (scheduleId) { - pushModal(`/schedule/edit/${scheduleId}`); + pushModal('schedule-edit', { id: scheduleId }); } break; case 'link-schedule': - pushModal('/schedule/link', { + pushModal('schedule-link', { transactionIds: [...selectedItems], }); break; diff --git a/packages/desktop-client/src/components/transactions/TransactionList.js b/packages/desktop-client/src/components/transactions/TransactionList.js index 908d6f025..c3eba6d14 100644 --- a/packages/desktop-client/src/components/transactions/TransactionList.js +++ b/packages/desktop-client/src/components/transactions/TransactionList.js @@ -12,7 +12,6 @@ import { import { getChangedValues, applyChanges } from 'loot-core/src/shared/util'; import { theme } from '../../style'; -import { usePushModal } from '../../util/router-tools'; import { TransactionTable } from './TransactionsTable'; @@ -78,6 +77,7 @@ export default function TransactionList({ dateFormat, hideFraction, addNotification, + pushModal, renderEmpty, onSort, sortField, @@ -89,7 +89,6 @@ export default function TransactionList({ }) { let transactionsLatest = useRef(); let navigate = useNavigate(); - let pushModal = usePushModal(); useLayoutEffect(() => { transactionsLatest.current = transactions; @@ -163,7 +162,7 @@ export default function TransactionList({ }); let onNavigateToSchedule = useCallback(scheduleId => { - pushModal(`/schedule/edit/${scheduleId}`); + pushModal('schedule-edit', { id: scheduleId }); }); return ( diff --git a/packages/desktop-client/src/global-events.js b/packages/desktop-client/src/global-events.js index b6e533b01..258e13ea2 100644 --- a/packages/desktop-client/src/global-events.js +++ b/packages/desktop-client/src/global-events.js @@ -36,10 +36,7 @@ export function handleGlobalEvents(actions, store) { }); listen('schedules-offline', ({ payees }) => { - let pushModal = window.__pushModal; - if (pushModal) { - pushModal(`/schedule/posts-offline-notification`, { payees }); - } + actions.pushModal(`schedule-posts-offline-notification`, { payees }); }); // This is experimental: we sync data locally automatically when diff --git a/packages/desktop-client/src/util/router-tools.tsx b/packages/desktop-client/src/util/router-tools.tsx index f370d90d5..21e89c061 100644 --- a/packages/desktop-client/src/util/router-tools.tsx +++ b/packages/desktop-client/src/util/router-tools.tsx @@ -1,75 +1,10 @@ -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(); +import { useLayoutEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; export function ExposeNavigate() { let navigate = useNavigate(); - let pushModal = usePushModal(); useLayoutEffect(() => { window.__navigate = navigate; - window.__pushModal = pushModal; - }, [navigate, pushModal]); + }, [navigate]); return null; } - -export function usePushModal() { - let navigate = useNavigate(); - let location = useLocation(); - - return useCallback( - (path: To, stateProps: Record<string, unknown> = {}) => - navigate(path, { - state: { parent: location, _version: VERSION, ...stateProps }, - }), - [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 44cf69c6d..09d8fc9d7 100644 --- a/packages/desktop-electron/menu.js +++ b/packages/desktop-electron/menu.js @@ -131,7 +131,7 @@ function getMenu(isDev, createWindow) { enabled: false, click: function (menuItem, focusedWin) { focusedWin.webContents.executeJavaScript( - '__pushModal && __pushModal("/schedule/discover")', + 'window.__actionsForMenu && window.__actionsForMenu.pushModal("schedules-discover")', ); }, }, 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 c4f1fac44..e44deedd3 100644 --- a/packages/loot-core/src/client/state-types/modals.d.ts +++ b/packages/loot-core/src/client/state-types/modals.d.ts @@ -85,6 +85,14 @@ type FinanceModals = { 'budget-summary': { month: string; }; + + 'schedule-edit': { id: string } | null; + + 'schedule-link': { transactionIds: string[] } | null; + + 'schedules-discover': null; + + 'schedule-posts-offline-notification': null; }; export type PushModalAction = { diff --git a/packages/loot-core/typings/window.d.ts b/packages/loot-core/typings/window.d.ts index 00401f267..da199f9b0 100644 --- a/packages/loot-core/typings/window.d.ts +++ b/packages/loot-core/typings/window.d.ts @@ -1,5 +1,3 @@ -import { usePushModal } from '../../desktop-client/src/util/router-tools'; - export {}; declare global { @@ -19,6 +17,5 @@ declare global { }; __navigate?: import('react-router').NavigateFunction; - __pushModal?: ReturnType<typeof usePushModal>; } } diff --git a/upcoming-release-notes/1270.md b/upcoming-release-notes/1270.md new file mode 100644 index 000000000..9fd440282 --- /dev/null +++ b/upcoming-release-notes/1270.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [j-f1] +--- + +Remove second modal implementation -- GitLab