diff --git a/.eslintrc.js b/.eslintrc.js index 698666bb8c9..0c2caca16c2 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -97,6 +97,7 @@ module.exports = { // ], 'no-var': 'warn', + 'react/jsx-curly-brace-presence': 'warn', 'object-shorthand': ['warn', 'properties'], 'import/extensions': [ @@ -161,7 +162,7 @@ module.exports = { // Rules disable during TS migration '@typescript-eslint/no-var-requires': 'off', - 'prefer-const': 'off', + 'prefer-const': 'warn', 'prefer-spread': 'off', '@typescript-eslint/no-empty-function': 'off', }, @@ -237,54 +238,6 @@ module.exports = { 'no-restricted-imports': ['off', { patterns: restrictedImportColors }], }, }, - // TODO: Remove this override once we addressed all warnings and enable the rule globally. - { - files: [ - './packages/api/*', - './packages/api/app/**/*', - './packages/crdt/**/*', - './packages/desktop-client/src/*', - './packages/desktop-client/src/components/*', - './packages/desktop-client/src/components/accounts/**/*', - './packages/desktop-client/src/components/autocomplete/**/*', - './packages/desktop-client/src/components/budget/**/*', - './packages/desktop-client/src/components/common/**/*', - './packages/desktop-client/src/components/filters/**/*', - './packages/desktop-client/src/components/gocardless/**/*', - './packages/desktop-client/src/components/manager/**/*', - './packages/desktop-client/src/components/mobile/**/*', - './packages/desktop-client/src/components/modals/**/*', - './packages/desktop-client/src/components/payees/**/*', - './packages/desktop-client/src/components/reports/**/*', - './packages/desktop-client/src/components/responsive/**/*', - './packages/desktop-client/src/components/rules/**/*', - './packages/desktop-client/src/components/schedules/**/*', - './packages/desktop-client/src/components/select/**/*', - './packages/desktop-client/src/components/settings/**/*', - './packages/desktop-client/src/components/sidebar/**/*', - './packages/desktop-client/src/components/spreadsheet/**/*', - './packages/desktop-client/src/components/transactions/**/*', - './packages/desktop-client/src/components/util/**/*', - './packages/desktop-client/src/hooks/**/*', - './packages/desktop-client/src/icons/**/*', - './packages/desktop-client/src/style/**/*', - './packages/desktop-client/src/types/**/*', - './packages/desktop-client/src/util/**/*', - './packages/desktop-electron/**/*', - './packages/eslint-plugin-actual/**/*', - './packages/loot-core/*', - './packages/loot-core/src/client/**/*', - './packages/loot-core/src/mocks/**/*', - './packages/loot-core/src/platform/**/*', - './packages/loot-core/src/server/**/*', - './packages/loot-core/src/shared/**/*', - './packages/loot-core/src/types/**/*', - './packages/loot-core/webpack/**/*', - ], - rules: { - 'prefer-const': 'warn', - }, - }, ], settings: { 'import/resolver': { diff --git a/packages/desktop-client/e2e/budget.test.js b/packages/desktop-client/e2e/budget.test.js index 95404837f5b..630d6d87a76 100644 --- a/packages/desktop-client/e2e/budget.test.js +++ b/packages/desktop-client/e2e/budget.test.js @@ -59,8 +59,8 @@ test.describe('Budget', () => { }); test('clicking on spent amounts opens a transaction page', async () => { - let categoryName = await budgetPage.getCategoryNameForRow(1); - let accountPage = await budgetPage.clickOnSpentAmountForRow(1); + const categoryName = await budgetPage.getCategoryNameForRow(1); + const accountPage = await budgetPage.clickOnSpentAmountForRow(1); expect(page.url()).toContain('/accounts'); expect(await accountPage.accountName.textContent()).toMatch( new RegExp(String.raw`${categoryName} \(\w+ \d+\)`), diff --git a/packages/desktop-client/e2e/mobile.test.js b/packages/desktop-client/e2e/mobile.test.js index fa53ecd5152..b67a03e4243 100644 --- a/packages/desktop-client/e2e/mobile.test.js +++ b/packages/desktop-client/e2e/mobile.test.js @@ -83,6 +83,8 @@ test.describe('Mobile', () => { await expect(transactionEntryPage.header).toHaveText('New Transaction'); await transactionEntryPage.amountField.fill('12.34'); + // Click anywhere to cancel active edit. + await transactionEntryPage.header.click(); await transactionEntryPage.fillField( page.getByTestId('payee-field'), 'Kroger', @@ -114,6 +116,8 @@ test.describe('Mobile', () => { await expect(page).toMatchThemeScreenshots(); await transactionEntryPage.amountField.fill('12.34'); + // Click anywhere to cancel active edit. + await transactionEntryPage.header.click(); await transactionEntryPage.fillField( page.getByTestId('payee-field'), 'Kroger', diff --git a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-from-accounts-id-page-1-chromium-linux.png b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-from-accounts-id-page-1-chromium-linux.png index 109397af4d8..71c34be4250 100644 Binary files a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-from-accounts-id-page-1-chromium-linux.png and b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-from-accounts-id-page-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-from-accounts-id-page-2-chromium-linux.png b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-from-accounts-id-page-2-chromium-linux.png index d0990083a99..885ad0401f9 100644 Binary files a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-from-accounts-id-page-2-chromium-linux.png and b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-from-accounts-id-page-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-1-chromium-linux.png b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-1-chromium-linux.png index 98d8921bfd4..f66759c3186 100644 Binary files a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-1-chromium-linux.png and b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-2-chromium-linux.png b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-2-chromium-linux.png index ff9a2444f99..3f118cdc0ac 100644 Binary files a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-2-chromium-linux.png and b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-3-chromium-linux.png b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-3-chromium-linux.png index 27df8c3e591..3404a4f5b32 100644 Binary files a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-3-chromium-linux.png and b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-4-chromium-linux.png b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-4-chromium-linux.png index ff627b5a0a6..6bb639fa3de 100644 Binary files a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-4-chromium-linux.png and b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-4-chromium-linux.png differ diff --git a/packages/desktop-client/src/components/Modals.tsx b/packages/desktop-client/src/components/Modals.tsx index 6c9238faf2a..5da4049a2bb 100644 --- a/packages/desktop-client/src/components/Modals.tsx +++ b/packages/desktop-client/src/components/Modals.tsx @@ -9,6 +9,8 @@ import useCategories from '../hooks/useCategories'; import useSyncServerStatus from '../hooks/useSyncServerStatus'; import { type CommonModalProps } from '../types/modals'; +import CategoryGroupMenu from './modals/CategoryGroupMenu'; +import CategoryMenu from './modals/CategoryMenu'; import CloseAccount from './modals/CloseAccount'; import ConfirmCategoryDelete from './modals/ConfirmCategoryDelete'; import ConfirmTransactionEdit from './modals/ConfirmTransactionEdit'; @@ -24,6 +26,7 @@ import ImportTransactions from './modals/ImportTransactions'; import LoadBackup from './modals/LoadBackup'; import ManageRulesModal from './modals/ManageRulesModal'; import MergeUnusedPayees from './modals/MergeUnusedPayees'; +import Notes from './modals/Notes'; import PlaidExternalMsg from './modals/PlaidExternalMsg'; import ReportBudgetSummary from './modals/ReportBudgetSummary'; import RolloverBudgetSummary from './modals/RolloverBudgetSummary'; @@ -232,6 +235,7 @@ export default function Modals() { modalProps={modalProps} name={options.name} onSubmit={options.onSubmit} + onClose={options.onClose} /> ); @@ -240,7 +244,7 @@ export default function Modals() { ); + case 'category-menu': + return ( + + ); + + case 'category-group-menu': + return ( + + ); + + case 'notes': + return ( + + ); + default: console.error('Unknown modal:', name); return null; diff --git a/packages/desktop-client/src/components/Notes.tsx b/packages/desktop-client/src/components/Notes.tsx new file mode 100644 index 00000000000..818d77a0188 --- /dev/null +++ b/packages/desktop-client/src/components/Notes.tsx @@ -0,0 +1,133 @@ +import React, { useEffect, useRef } from 'react'; +import ReactMarkdown from 'react-markdown'; + +import { css } from 'glamor'; +import remarkGfm from 'remark-gfm'; + +import { useResponsive } from '../ResponsiveProvider'; +import { type CSSProperties, theme } from '../style'; +import { remarkBreaks, sequentialNewlinesPlugin } from '../util/markdown'; + +import Text from './common/Text'; + +const remarkPlugins = [sequentialNewlinesPlugin, remarkGfm, remarkBreaks]; + +const markdownStyles = css({ + display: 'block', + maxWidth: 350, + padding: 8, + overflowWrap: 'break-word', + '& p': { + margin: 0, + ':not(:first-child)': { + marginTop: '0.25rem', + }, + }, + '& ul, & ol': { + listStylePosition: 'inside', + margin: 0, + paddingLeft: 0, + }, + '&>* ul, &>* ol': { + marginLeft: '1.5rem', + }, + '& li>p': { + display: 'contents', + }, + '& blockquote': { + paddingLeft: '0.75rem', + borderLeft: '3px solid ' + theme.markdownDark, + margin: 0, + }, + '& hr': { + borderTop: 'none', + borderLeft: 'none', + borderRight: 'none', + borderBottom: '1px solid ' + theme.markdownNormal, + }, + '& code': { + backgroundColor: theme.markdownLight, + padding: '0.1rem 0.5rem', + borderRadius: '0.25rem', + }, + '& pre': { + padding: '0.5rem', + backgroundColor: theme.markdownLight, + borderRadius: '0.5rem', + margin: 0, + ':not(:first-child)': { + marginTop: '0.25rem', + }, + '& code': { + background: 'inherit', + padding: 0, + borderRadius: 0, + }, + }, + '& table, & th, & td': { + border: '1px solid ' + theme.markdownNormal, + }, + '& table': { + borderCollapse: 'collapse', + wordBreak: 'break-word', + }, + '& td': { + padding: '0.25rem 0.75rem', + }, +}); + +type NotesProps = { + notes: string; + editable?: boolean; + focused?: boolean; + onChange?: (value: string) => void; + onBlur?: (value: string) => void; + getStyle?: (editable: boolean) => CSSProperties; +}; + +export default function Notes({ + notes, + editable, + focused, + onChange, + onBlur, + getStyle, +}: NotesProps) { + const { isNarrowWidth } = useResponsive(); + const _onChange = value => { + onChange?.(value); + }; + + const textAreaRef = useRef(); + + useEffect(() => { + if (focused && editable) { + textAreaRef.current.focus(); + } + }, [focused, editable]); + + return editable ? ( +