From 29f323e721107ce1acbcdfadeae1d8dd195334a5 Mon Sep 17 00:00:00 2001 From: Stefan Hall Date: Wed, 29 Nov 2023 08:19:18 +1300 Subject: [PATCH 01/21] Validates minimum node version to 18.12.0 for @actual-app/api npm package (#1980) --- packages/api/index.js | 3 +++ packages/api/package.json | 1 + packages/api/validateNodeVersion.js | 16 ++++++++++++++++ upcoming-release-notes/1980.md | 6 ++++++ yarn.lock | 8 ++++++++ 5 files changed, 34 insertions(+) create mode 100644 packages/api/validateNodeVersion.js create mode 100644 upcoming-release-notes/1980.md diff --git a/packages/api/index.js b/packages/api/index.js index 3c6043f77b4..aebc7c061d4 100644 --- a/packages/api/index.js +++ b/packages/api/index.js @@ -3,6 +3,7 @@ // eslint-disable-next-line import/extensions import * as bundle from './app/bundle.api.js'; import * as injected from './injected'; +import { validateNodeVersion } from './validateNodeVersion'; let actualApp; export const internal = bundle.lib; @@ -18,6 +19,8 @@ export async function init(config = {}) { return; } + validateNodeVersion(); + global.fetch = (...args) => import('node-fetch').then(({ default: fetch }) => fetch(...args)); diff --git a/packages/api/package.json b/packages/api/package.json index a2f07411c52..6f876dc3383 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -20,6 +20,7 @@ }, "dependencies": { "better-sqlite3": "^9.1.1", + "compare-versions": "^6.1.0", "node-fetch": "^3.3.2", "uuid": "^9.0.0" }, diff --git a/packages/api/validateNodeVersion.js b/packages/api/validateNodeVersion.js new file mode 100644 index 00000000000..d2bfb828a4f --- /dev/null +++ b/packages/api/validateNodeVersion.js @@ -0,0 +1,16 @@ +import { satisfies } from 'compare-versions'; + +import * as packageJson from './package.json'; + +export function validateNodeVersion() { + if (process?.versions?.node) { + const nodeVersion = process?.versions?.node; + const minimumNodeVersion = packageJson.engines.node; + + if (!satisfies(nodeVersion, minimumNodeVersion)) { + throw new Error( + `@actual-app/api requires a node version ${minimumNodeVersion}. Found that you are using: ${nodeVersion}. Please upgrade to a higher version`, + ); + } + } +} diff --git a/upcoming-release-notes/1980.md b/upcoming-release-notes/1980.md new file mode 100644 index 00000000000..799c980f7b5 --- /dev/null +++ b/upcoming-release-notes/1980.md @@ -0,0 +1,6 @@ +--- +category: Enhancements +authors: [Marethyu1] +--- + +Validates minimum node version to 18.12.0 for @actual-app/api npm package diff --git a/yarn.lock b/yarn.lock index 5752137fd62..1dd1f7c5915 100644 --- a/yarn.lock +++ b/yarn.lock @@ -25,6 +25,7 @@ __metadata: dependencies: "@types/uuid": "npm:^9.0.2" better-sqlite3: "npm:^9.1.1" + compare-versions: "npm:^6.1.0" node-fetch: "npm:^3.3.2" typescript: "npm:^5.0.2" uuid: "npm:^9.0.0" @@ -7169,6 +7170,13 @@ __metadata: languageName: node linkType: hard +"compare-versions@npm:^6.1.0": + version: 6.1.0 + resolution: "compare-versions@npm:6.1.0" + checksum: 20f349e7f8ad784704c68265f4e660e2abbe2c3d5c75793184fccb85f0c5c0263260e01fdd4488376f6b74b0f069e16c9684463f7316b075716fb1581eb36b77 + languageName: node + linkType: hard + "compressible@npm:~2.0.16": version: 2.0.18 resolution: "compressible@npm:2.0.18" From 5a81a25b7e3e037a56a5c792d6c7aa50f568179f Mon Sep 17 00:00:00 2001 From: youngcw Date: Thu, 30 Nov 2023 09:50:31 -0700 Subject: [PATCH 02/21] fix broken schedule amount sign in firefox (#1992) * fix broken schedule amount sign in firefox * note * delete line --- packages/desktop-client/src/components/util/AmountInput.tsx | 1 - upcoming-release-notes/1992.md | 6 ++++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 upcoming-release-notes/1992.md diff --git a/packages/desktop-client/src/components/util/AmountInput.tsx b/packages/desktop-client/src/components/util/AmountInput.tsx index 3fa30ad5e5d..4c830ce4096 100644 --- a/packages/desktop-client/src/components/util/AmountInput.tsx +++ b/packages/desktop-client/src/components/util/AmountInput.tsx @@ -89,7 +89,6 @@ export function AmountInput({ ) : ( - - diff --git a/packages/loot-core/src/client/actions/budgets.ts b/packages/loot-core/src/client/actions/budgets.ts index c35ca100ee9..9a5321cf3dc 100644 --- a/packages/loot-core/src/client/actions/budgets.ts +++ b/packages/loot-core/src/client/actions/budgets.ts @@ -110,10 +110,7 @@ export function closeBudgetUI() { }; } -export function deleteBudget( - id: string | undefined, - cloudFileId: string | undefined, -) { +export function deleteBudget(id?: string, cloudFileId?: string) { return async (dispatch: Dispatch) => { await send('delete-budget', { id, cloudFileId }); await dispatch(loadAllFiles()); 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 517f1778aaa..1c12c19f09d 100644 --- a/packages/loot-core/src/client/state-types/modals.d.ts +++ b/packages/loot-core/src/client/state-types/modals.d.ts @@ -1,8 +1,8 @@ +import { type File } from '../../types/file'; import type { AccountEntity, GoCardlessToken } from '../../types/models'; import type { RuleEntity } from '../../types/models/rule'; import type { EmptyObject, StripNever } from '../../types/util'; import type * as constants from '../constants'; - export type ModalType = keyof FinanceModals; export type OptionlessModal = { @@ -70,6 +70,16 @@ type FinanceModals = { onSuccess: (data: GoCardlessToken) => Promise; }; + 'delete-budget': { file: File }; + + import: null; + + 'import-ynab4': null; + + 'import-ynab5': null; + + 'import-actual': null; + 'create-encryption-key': { recreate?: boolean }; 'fix-encryption-key': { hasExistingKey?: boolean; diff --git a/packages/loot-core/src/types/file.d.ts b/packages/loot-core/src/types/file.d.ts index 394adee0718..30301b1fb14 100644 --- a/packages/loot-core/src/types/file.d.ts +++ b/packages/loot-core/src/types/file.d.ts @@ -11,11 +11,13 @@ export type FileState = export type LocalFile = Omit & { state: 'local'; }; + export type SyncableLocalFile = Budget & { cloudFileId: string; groupId: string; state: 'broken' | 'unknown'; }; + export type SyncedLocalFile = Budget & { cloudFileId: string; groupId: string; @@ -23,6 +25,7 @@ export type SyncedLocalFile = Budget & { hasKey: boolean; state: 'synced' | 'detached'; }; + export type RemoteFile = { cloudFileId: string; groupId: string; diff --git a/upcoming-release-notes/2004.md b/upcoming-release-notes/2004.md new file mode 100644 index 00000000000..0ff9d19d9bf --- /dev/null +++ b/upcoming-release-notes/2004.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [MikesGlitch] +--- + +Convert BudgetTotals, GoCardlessLink, Import, WelcomeScreen components to Typescript. From 6bfd9586e0ac99e65cfc385bfe8ae97dc44ca886 Mon Sep 17 00:00:00 2001 From: Ameek Singh Date: Sat, 2 Dec 2023 17:43:56 -0500 Subject: [PATCH 09/21] [Maintenance] Converting dateRange and useReport to tsx (#2007) --- .../reports/{DateRange.js => DateRange.tsx} | 20 +++++++++++++------ .../reports/{useReport.js => useReport.tsx} | 11 +++++++--- upcoming-release-notes/2007.md | 6 ++++++ 3 files changed, 28 insertions(+), 9 deletions(-) rename packages/desktop-client/src/components/reports/{DateRange.js => DateRange.tsx} (60%) rename packages/desktop-client/src/components/reports/{useReport.js => useReport.tsx} (61%) create mode 100644 upcoming-release-notes/2007.md diff --git a/packages/desktop-client/src/components/reports/DateRange.js b/packages/desktop-client/src/components/reports/DateRange.tsx similarity index 60% rename from packages/desktop-client/src/components/reports/DateRange.js rename to packages/desktop-client/src/components/reports/DateRange.tsx index 2e2ba1234e5..df3d6eb50ec 100644 --- a/packages/desktop-client/src/components/reports/DateRange.js +++ b/packages/desktop-client/src/components/reports/DateRange.tsx @@ -1,16 +1,24 @@ -import React from 'react'; +import React, { type ReactElement } from 'react'; import * as d from 'date-fns'; import { theme } from '../../style'; import Block from '../common/Block'; -function DateRange({ start, end }) { - start = d.parseISO(start); - end = d.parseISO(end); +type DateRangeProps = { + start: string; + end: string; +}; - let content; - if (start.getYear() !== end.getYear()) { +function DateRange({ + start: startProp, + end: endProp, +}: DateRangeProps): ReactElement { + const start = d.parseISO(startProp); + const end = d.parseISO(endProp); + + let content: string | ReactElement; + if (start.getFullYear() !== end.getFullYear()) { content = (
{d.format(start, 'MMM yyyy')} - {d.format(end, 'MMM yyyy')} diff --git a/packages/desktop-client/src/components/reports/useReport.js b/packages/desktop-client/src/components/reports/useReport.tsx similarity index 61% rename from packages/desktop-client/src/components/reports/useReport.js rename to packages/desktop-client/src/components/reports/useReport.tsx index 15d5a3fb986..0e010d13517 100644 --- a/packages/desktop-client/src/components/reports/useReport.js +++ b/packages/desktop-client/src/components/reports/useReport.tsx @@ -1,8 +1,14 @@ -import { useState, useEffect } from 'react'; +import { useState, useEffect, type SetStateAction } from 'react'; import { useSpreadsheet } from 'loot-core/src/client/SpreadsheetProvider'; -function useReport(sheetName, getData) { +function useReport( + sheetName: string, + getData: ( + spreadsheet: ReturnType, + setData: (results: unknown) => SetStateAction, + ) => Promise, +) { const spreadsheet = useSpreadsheet(); const [results, setResults] = useState(null); @@ -15,7 +21,6 @@ function useReport(sheetName, getData) { cleanup?.(); }; }, [getData]); - return results; } diff --git a/upcoming-release-notes/2007.md b/upcoming-release-notes/2007.md new file mode 100644 index 00000000000..cb0aa92f926 --- /dev/null +++ b/upcoming-release-notes/2007.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [ameekSinghUniversityAcc] +--- + +Migrating the DateRange and UseReport files to typescript From 5f528018698906bef812b34660394b89d2c14154 Mon Sep 17 00:00:00 2001 From: Michael Clark <5285928+MikesGlitch@users.noreply.github.com> Date: Mon, 4 Dec 2023 19:31:38 +0000 Subject: [PATCH 10/21] [Maintenance] Adding Aria labels for accessibility (#2025) --- packages/desktop-client/src/components/NotesButton.tsx | 1 + packages/desktop-client/src/components/Notifications.tsx | 1 + packages/desktop-client/src/components/ThemeSelector.tsx | 1 + packages/desktop-client/src/components/Titlebar.tsx | 2 ++ .../desktop-client/src/components/UpdateNotification.tsx | 1 + packages/desktop-client/src/components/accounts/Header.js | 1 + .../desktop-client/src/components/budget/BudgetTotals.tsx | 1 + .../src/components/budget/MobileBudgetTable.js | 5 +++++ .../budget/report/budgetsummary/BudgetSummary.tsx | 3 ++- .../budget/rollover/budgetsummary/BudgetSummary.tsx | 3 ++- .../desktop-client/src/components/manager/BudgetList.js | 2 ++ .../src/components/schedules/SchedulesTable.tsx | 1 + .../src/components/select/RecurringSchedulePicker.js | 2 ++ .../desktop-client/src/components/sidebar/ToggleButton.tsx | 7 ++++++- .../src/components/transactions/TransactionsTable.js | 1 + .../desktop-client/src/components/util/AmountInput.tsx | 1 + upcoming-release-notes/2025.md | 6 ++++++ 17 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 upcoming-release-notes/2025.md diff --git a/packages/desktop-client/src/components/NotesButton.tsx b/packages/desktop-client/src/components/NotesButton.tsx index 76d4c9a9aca..59ab1112259 100644 --- a/packages/desktop-client/src/components/NotesButton.tsx +++ b/packages/desktop-client/src/components/NotesButton.tsx @@ -188,6 +188,7 @@ export default function NotesButton({ > + ); +} diff --git a/packages/desktop-client/src/components/Page.tsx b/packages/desktop-client/src/components/Page.tsx index 6f666400f7d..d9bd447f9c2 100644 --- a/packages/desktop-client/src/components/Page.tsx +++ b/packages/desktop-client/src/components/Page.tsx @@ -1,4 +1,4 @@ -import React, { type ReactNode } from 'react'; +import React, { type ComponentPropsWithoutRef, type ReactNode } from 'react'; import { useResponsive } from '../ResponsiveProvider'; import { theme, styles, type CSSProperties } from '../style'; @@ -7,18 +7,24 @@ import Text from './common/Text'; import View from './common/View'; type PageHeaderProps = { - name: ReactNode; + title: ReactNode; + titleContainerProps?: ComponentPropsWithoutRef; style?: CSSProperties; + leftContentContainerProps?: ComponentPropsWithoutRef; leftContent?: ReactNode; + rightContentContainerProps?: ComponentPropsWithoutRef; rightContent?: ReactNode; }; const HEADER_HEIGHT = 50; function PageHeader({ - name, + title, + titleContainerProps, style, + leftContentContainerProps, leftContent, + rightContentContainerProps, rightContent, }: PageHeaderProps) { const { isNarrowWidth } = useResponsive(); @@ -28,8 +34,8 @@ function PageHeader({ {leftContent} - {name} + {title} {rightContent} @@ -80,53 +94,88 @@ function PageHeader({ ...style, }} > - {name} + {title} ); } type PageProps = { - title: ReactNode; - titleStyle?: CSSProperties; - headerLeftContent?: ReactNode; - headerRightContent?: ReactNode; + titleContainerProps?: PageHeaderProps['titleContainerProps']; + title: PageHeaderProps['title']; + headerStyle?: CSSProperties; + headerLeftContentContainerProps?: PageHeaderProps['leftContentContainerProps']; + headerLeftContent?: PageHeaderProps['leftContent']; + headerRightContentContainerProps?: PageHeaderProps['rightContentContainerProps']; + headerRightContent?: PageHeaderProps['rightContent']; + style?: CSSProperties; + padding?: number; + childrenContainerProps?: ComponentPropsWithoutRef; children: ReactNode; + footer?: ReactNode; }; export function Page({ + titleContainerProps, title, - titleStyle, + headerStyle, + headerLeftContentContainerProps, headerLeftContent, + headerRightContentContainerProps, headerRightContent, + style, + padding, + childrenContainerProps, children, + footer, }: PageProps) { - let { isNarrowWidth } = useResponsive(); - let HORIZONTAL_PADDING = isNarrowWidth ? 10 : 20; + const { isNarrowWidth } = useResponsive(); + const _padding = padding != null ? padding : isNarrowWidth ? 10 : 20; return ( - + - - {children} - + {isNarrowWidth ? ( + + {children} + + ) : ( + + {children} + + )} + {footer} ); } diff --git a/packages/desktop-client/src/components/Titlebar.tsx b/packages/desktop-client/src/components/Titlebar.tsx index 5c50871ec2b..cf594d1af91 100644 --- a/packages/desktop-client/src/components/Titlebar.tsx +++ b/packages/desktop-client/src/components/Titlebar.tsx @@ -130,7 +130,7 @@ type SyncButtonProps = { style?: CSSProperties; isMobile?: boolean; }; -export function SyncButton({ style, isMobile = false }: SyncButtonProps) { +function SyncButton({ style, isMobile = false }: SyncButtonProps) { let cloudFileId = useSelector(state => state.prefs.local.cloudFileId); let { sync } = useActions(); diff --git a/packages/desktop-client/src/components/accounts/MobileAccountDetails.js b/packages/desktop-client/src/components/accounts/MobileAccountDetails.js index 0f91f4fd0d2..b20832df9fd 100644 --- a/packages/desktop-client/src/components/accounts/MobileAccountDetails.js +++ b/packages/desktop-client/src/components/accounts/MobileAccountDetails.js @@ -1,17 +1,15 @@ import React, { useState, useMemo } from 'react'; -import { Link } from 'react-router-dom'; import { useActions } from '../../hooks/useActions'; import Add from '../../icons/v1/Add'; -import CheveronLeft from '../../icons/v1/CheveronLeft'; import SearchAlternate from '../../icons/v2/SearchAlternate'; -import { theme, styles } from '../../style'; -import Button from '../common/Button'; +import { theme } from '../../style'; import ButtonLink from '../common/ButtonLink'; import InputWithContent from '../common/InputWithContent'; import Label from '../common/Label'; -import Text from '../common/Text'; import View from '../common/View'; +import MobileBackButton from '../MobileBackButton'; +import { Page } from '../Page'; import PullToRefresh from '../responsive/PullToRefresh'; import CellValue from '../spreadsheet/CellValue'; import { TransactionList } from '../transactions/MobileTransaction'; @@ -63,9 +61,6 @@ function TransactionSearchInput({ accountName, onSearch }) { ); } -const LEFT_RIGHT_FLEX_WIDTH = 70; -const BUDGET_HEADER_HEIGHT = 50; - export default function AccountDetails({ account, prependTransactions, @@ -90,24 +85,42 @@ export default function AccountDetails({ }; return ( - } + headerRightContent={ + + + + } + padding={0} style={{ flex: 1, backgroundColor: theme.mobilePageBackground, - overflowY: 'hidden', - flexGrow: 1, }} > - - - - - ); -} - -function AccountDetailsHeader({ account }) { - return ( - - - - - - - {account.name} - - - - - - - - - + ); } diff --git a/packages/desktop-client/src/components/accounts/MobileAccounts.js b/packages/desktop-client/src/components/accounts/MobileAccounts.js index 59c9db5d172..2df04b5ac84 100644 --- a/packages/desktop-client/src/components/accounts/MobileAccounts.js +++ b/packages/desktop-client/src/components/accounts/MobileAccounts.js @@ -158,32 +158,28 @@ function AccountList({ }; return ( - - - - - } - > - {accounts.length === 0 && } - + + + + } + padding={0} + style={{ flex: 1, backgroundColor: theme.mobilePageBackground }} + > + {accounts.length === 0 && } + + {budgetedAccounts.length > 0 && ( )} @@ -213,9 +209,9 @@ function AccountList({ onSelect={onSelectAccount} /> ))} - - - + + + ); } diff --git a/packages/desktop-client/src/components/budget/MobileBudgetTable.js b/packages/desktop-client/src/components/budget/MobileBudgetTable.js index a02840ce508..0190840d578 100644 --- a/packages/desktop-client/src/components/budget/MobileBudgetTable.js +++ b/packages/desktop-client/src/components/budget/MobileBudgetTable.js @@ -19,13 +19,12 @@ import Label from '../common/Label'; import Menu from '../common/Menu'; import Text from '../common/Text'; import View from '../common/View'; +import { Page } from '../Page'; import PullToRefresh from '../responsive/PullToRefresh'; -import { useServerURL } from '../ServerContext'; import CellValue from '../spreadsheet/CellValue'; import NamespaceContext from '../spreadsheet/NamespaceContext'; import useFormat from '../spreadsheet/useFormat'; import useSheetValue from '../spreadsheet/useSheetValue'; -import { SyncButton } from '../Titlebar'; import { Tooltip, useTooltip } from '../tooltips'; import { AmountInput } from '../util/AmountInput'; // import { @@ -1695,7 +1694,6 @@ export function BudgetTable(props) { const show3Cols = width >= 360; // let editMode = false; // neuter editMode -- sorry, not rewriting drag-n-drop right now - let currentMonth = monthUtils.currentMonth(); let format = useFormat(); const mobileShowBudgetedColPref = useSelector(state => { @@ -1724,27 +1722,62 @@ export function BudgetTable(props) { borderRadius: 'unset', }; + const _onSwitchBudgetType = () => { + pushModal('switch-budget-type', { + onSwitch: onSwitchBudgetType, + }); + }; + + const onToggleHiddenCategories = () => { + savePrefs({ + 'budget.showHiddenCategories': !showHiddenCategories, + }); + }; + return ( - - + + } + headerRightContent={ + !editMode ? ( + + ) : ( + + ) + } + style={{ flex: 1 }} + > {type === 'report' ? ( = currentMonth} + projected={month >= monthUtils.currentMonth()} onClick={onShowBudgetSummary} /> ) : ( @@ -1873,123 +1906,95 @@ export function BudgetTable(props) { /> - - - {!editMode ? ( - // (this.list = el)} - // keyboardShouldPersistTaps="always" - // refreshControl={refreshControl} - // style={{ backgroundColor: colors.n10 }} - // automaticallyAdjustContentInsets={false} - // > - - - - ) : ( - // - // - // {({ - // dragging, - // onGestureEvent, - // onHandlerStateChange, - // scrollRef, - // onScroll - // }) => ( - - - + + {!editMode ? ( + // (this.list = el)} + // keyboardShouldPersistTaps="always" + // refreshControl={refreshControl} + // style={{ backgroundColor: colors.n10 }} + // automaticallyAdjustContentInsets={false} + // > + + + + ) : ( + // + // + // {({ + // dragging, + // onGestureEvent, + // onHandlerStateChange, + // scrollRef, + // onScroll + // }) => ( + + + - // - // - )} - - - + // + // + )} + + ); } -const LEFT_RIGHT_FLEX_WIDTH = 80; -const BUDGET_HEADER_HEIGHT = 50; - -function BudgetHeader({ - currentMonth, - monthBounds, - onPrevMonth, - onNextMonth, - editMode, +function BudgetMenu({ onEditMode, - showHiddenCategories, - savePrefs, - pushModal, + onToggleHiddenCategories, onSwitchBudgetType, }) { - let serverURL = useServerURL(); - - let prevEnabled = currentMonth > monthBounds.start; - let nextEnabled = currentMonth < monthUtils.subMonths(monthBounds.end, 1); - - let buttonStyle = { - padding: 10, - margin: 2, - }; - - let toggleHiddenCategories = () => { - savePrefs({ - 'budget.showHiddenCategories': !showHiddenCategories, - }); - }; - let tooltip = useTooltip(); let isReportBudgetEnabled = useFeatureFlag('reportBudget'); @@ -2000,181 +2005,123 @@ function BudgetHeader({ onEditMode?.(true); break; case 'toggle-hidden-categories': - toggleHiddenCategories(); + onToggleHiddenCategories?.(); break; case 'switch-budget-type': - pushModal('switch-budget-type', { - onSwitch: onSwitchBudgetType, - }); + onSwitchBudgetType?.(); break; default: throw new Error(`Unrecognized menu option: ${name}`); } }; + return ( + <> + + {tooltip.isOpen && ( + +