From 3738bde9c3d0e811ae5a3b7007ee2c0e4773dec9 Mon Sep 17 00:00:00 2001 From: qedi-r <1435081+qedi-r@users.noreply.github.com> Date: Sat, 28 Sep 2024 11:55:53 -0400 Subject: [PATCH 01/10] add duplicate name validation to sidebar, and both narrow/wide Account pages --- .../src/components/accounts/Account.tsx | 20 +- .../src/components/accounts/Header.jsx | 235 ++++++++++-------- .../mobile/accounts/AccountTransactions.tsx | 1 + .../components/modals/AccountMenuModal.tsx | 39 ++- .../modals/CreateLocalAccountModal.tsx | 43 +++- .../src/components/sidebar/Account.tsx | 10 - .../src/components/util/accountValidation.ts | 23 ++ 7 files changed, 238 insertions(+), 133 deletions(-) create mode 100644 packages/desktop-client/src/components/util/accountValidation.ts diff --git a/packages/desktop-client/src/components/accounts/Account.tsx b/packages/desktop-client/src/components/accounts/Account.tsx index 05e30623b77..1c8d20bdbbb 100644 --- a/packages/desktop-client/src/components/accounts/Account.tsx +++ b/packages/desktop-client/src/components/accounts/Account.tsx @@ -69,6 +69,7 @@ import { Button } from '../common/Button2'; import { Text } from '../common/Text'; import { View } from '../common/View'; import { TransactionList } from '../transactions/TransactionList'; +import { validateAccountName } from '../util/accountValidation'; import { AccountHeader } from './Header'; @@ -296,6 +297,7 @@ type AccountInternalState = { prevShowCleared?: boolean; showReconciled: boolean; editingName: boolean; + nameError: string; isAdding: boolean; modalShowing?: boolean; sort: { @@ -343,6 +345,7 @@ class AccountInternal extends PureComponent< showCleared: props.showCleared, showReconciled: props.showReconciled, editingName: false, + nameError: null, isAdding: false, sort: null, filteredAmount: null, @@ -703,13 +706,19 @@ class AccountInternal extends PureComponent< }; onSaveName = (name: string) => { - if (name.trim().length) { - const accountId = this.props.accountId; + const accountNameError = validateAccountName( + name, + this.props.accountId, + this.props.accounts, + ); + if (accountNameError) { + this.setState({ nameError: accountNameError }); + } else { const account = this.props.accounts.find( - account => account.id === accountId, - )!; + account => account.id === this.props.accountId, + ); this.props.updateAccount({ ...account, name }); - this.setState({ editingName: false }); + this.setState({ editingName: false, nameError: null }); } }; @@ -1704,6 +1713,7 @@ class AccountInternal extends PureComponent< onAddTransaction={this.onAddTransaction} onToggleExtraBalances={this.onToggleExtraBalances} onSaveName={this.onSaveName} + saveNameError={this.state.nameError} onExposeName={this.onExposeName} onReconcile={this.onReconcile} onDoneReconciling={this.onDoneReconciling} diff --git a/packages/desktop-client/src/components/accounts/Header.jsx b/packages/desktop-client/src/components/accounts/Header.jsx index f8aa6b492d8..950d29f1b3a 100644 --- a/packages/desktop-client/src/components/accounts/Header.jsx +++ b/packages/desktop-client/src/components/accounts/Header.jsx @@ -1,6 +1,8 @@ -import React, { useState, useRef } from 'react'; +import React, { useState, useRef, Fragment } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; -import { Trans, useTranslation } from 'react-i18next'; +import { Trans } from 'react-i18next'; + +import { t } from 'i18next'; import { useLocalPref } from '../../hooks/useLocalPref'; import { useSplitsExpanded } from '../../hooks/useSplitsExpanded'; @@ -14,6 +16,7 @@ import { SvgPencil1, } from '../../icons/v2'; import { theme, styles } from '../../style'; +import { Warning } from '../alerts'; import { AnimatedRefresh } from '../AnimatedRefresh'; import { Button } from '../common/Button2'; import { InitialFocus } from '../common/InitialFocus'; @@ -67,6 +70,7 @@ export function AccountHeader({ onCreateReconciliationTransaction, onToggleExtraBalances, onSaveName, + saveNameError, onExposeName, onSync, onImport, @@ -89,8 +93,6 @@ export function AccountHeader({ onMakeAsSplitTransaction, onMakeAsNonSplitTransactions, }) { - const { t } = useTranslation(); - const [menuOpen, setMenuOpen] = useState(false); const searchInput = useRef(null); const triggerRef = useRef(null); @@ -176,99 +178,21 @@ export function AccountHeader({ }} > {!!account?.bank && ( - )} - {editingName ? ( - - onSaveName(e.target.value)} - onBlur={e => onSaveName(e.target.value)} - onEscape={() => onExposeName(false)} - style={{ - fontSize: 25, - fontWeight: 500, - marginTop: -3, - marginBottom: -4, - marginLeft: -6, - paddingTop: 2, - paddingBottom: 2, - width: Math.max(20, accountName.length) + 'ch', - }} - /> - - ) : isNameEditable ? ( - - - {account && account.closed - ? t('Closed: {{ accountName }}', { accountName }) - : accountName} - - - {account && ( - - )} - - - ) : ( - - {account && account.closed - ? t('Closed: {{ accountName }}', { accountName }) - : accountName} - - )} + @@ -471,6 +395,125 @@ export function AccountHeader({ ); } +function AccountSyncSidebar({ account, failedAccounts, accountsSyncing }) { + return ( + + ); +} + +function AccountNameField({ + account, + accountName, + isNameEditable, + editingName, + saveNameError, + onSaveName, + onExposeName, +}) { + if (editingName) { + return ( + + + onSaveName(e.target.value)} + onBlur={e => onSaveName(e.target.value)} + onEscape={() => onExposeName(false)} + style={{ + fontSize: 25, + fontWeight: 500, + marginTop: -3, + marginBottom: -4, + marginLeft: -6, + paddingTop: 2, + paddingBottom: 2, + width: Math.max(20, accountName.length) + 'ch', + }} + /> + + {saveNameError && {saveNameError}} + + ); + } else { + if (isNameEditable) { + return ( + + + {account && account.closed + ? t('Closed: {{ accountName }}', { accountName }) + : accountName} + + + {account && ( + + )} + + + ); + } else { + return ( + + {account && account.closed + ? t('Closed: {{ accountName }}', { accountName }) + : accountName} + + ); + } + } +} + function AccountMenu({ account, canSync, @@ -483,8 +526,6 @@ function AccountMenu({ onReconcile, onMenuSelect, }) { - const { t } = useTranslation(); - const [tooltip, setTooltip] = useState('default'); const syncServerStatus = useSyncServerStatus(); diff --git a/packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx b/packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx index eaf49332fb9..453212917d7 100644 --- a/packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx +++ b/packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx @@ -43,6 +43,7 @@ import { Button } from '../../common/Button2'; import { Text } from '../../common/Text'; import { View } from '../../common/View'; import { MobilePageHeader, Page } from '../../Page'; +import { validateAccountName } from '../../util/accountValidation'; import { MobileBackButton } from '../MobileBackButton'; import { AddTransactionButton } from '../transactions/AddTransactionButton'; import { TransactionListWithBalances } from '../transactions/TransactionListWithBalances'; diff --git a/packages/desktop-client/src/components/modals/AccountMenuModal.tsx b/packages/desktop-client/src/components/modals/AccountMenuModal.tsx index f07a6915c44..7106dc5bc7f 100644 --- a/packages/desktop-client/src/components/modals/AccountMenuModal.tsx +++ b/packages/desktop-client/src/components/modals/AccountMenuModal.tsx @@ -1,8 +1,9 @@ -import React, { type ComponentProps, useRef, useState } from 'react'; +import React, { type ComponentProps, Fragment, useRef, useState } from 'react'; import { type AccountEntity } from 'loot-core/types/models'; import { useAccount } from '../../hooks/useAccount'; +import { useAccounts } from '../../hooks/useAccounts'; import { useNotes } from '../../hooks/useNotes'; import { SvgClose, SvgDotsHorizontalTriple, SvgLockOpen } from '../../icons/v1'; import { SvgNotesPaper } from '../../icons/v2'; @@ -18,6 +19,7 @@ import { import { Popover } from '../common/Popover'; import { View } from '../common/View'; import { Notes } from '../Notes'; +import { validateAccountName } from '../util/accountValidation'; type AccountMenuModalProps = { accountId: string; @@ -37,7 +39,9 @@ export function AccountMenuModal({ onClose, }: AccountMenuModalProps) { const account = useAccount(accountId); + const accounts = useAccounts(); const originalNotes = useNotes(`account-${accountId}`); + const [accountNameError, setAccountNameError] = useState(null); const onRename = (newName: string) => { if (!account) { @@ -45,10 +49,20 @@ export function AccountMenuModal({ } if (newName !== account.name) { - onSave?.({ - ...account, - name: newName, - }); + const renameAccountError = validateAccountName( + newName, + accountId, + accounts, + ); + if (renameAccountError) { + setAccountNameError(renameAccountError); + } else { + setAccountNameError(null); + onSave?.({ + ...account, + name: newName, + }); + } } }; @@ -93,11 +107,16 @@ export function AccountMenuModal({ /> } title={ - + + + + {accountNameError} + + } rightContent={} /> diff --git a/packages/desktop-client/src/components/modals/CreateLocalAccountModal.tsx b/packages/desktop-client/src/components/modals/CreateLocalAccountModal.tsx index 7c737d30615..2f46b080091 100644 --- a/packages/desktop-client/src/components/modals/CreateLocalAccountModal.tsx +++ b/packages/desktop-client/src/components/modals/CreateLocalAccountModal.tsx @@ -1,11 +1,14 @@ // @ts-strict-ignore -import React, { type FormEvent, useState } from 'react'; +import { type FormEvent, useState } from 'react'; import { Form } from 'react-aria-components'; +import { useTranslation } from 'react-i18next'; import { useDispatch } from 'react-redux'; import { closeModal, createAccount } from 'loot-core/client/actions'; import { toRelaxedNumber } from 'loot-core/src/shared/util'; +import { type AccountEntity } from 'loot-core/types/models'; +import * as useAccounts from '../../hooks/useAccounts'; import { useNavigate } from '../../hooks/useNavigate'; import { theme } from '../../style'; import { Button } from '../common/Button2'; @@ -25,23 +28,46 @@ import { Text } from '../common/Text'; import { View } from '../common/View'; import { Checkbox } from '../forms'; +function accountNameIsInUse(accountName: string, accounts: AccountEntity[]) { + return accounts.map(a => a.name).includes(accountName); +} + export function CreateLocalAccountModal() { const navigate = useNavigate(); const dispatch = useDispatch(); + const accounts = useAccounts.useAccounts(); const [name, setName] = useState(''); const [offbudget, setOffbudget] = useState(false); const [balance, setBalance] = useState('0'); - const [nameError, setNameError] = useState(false); + const [nameError, setNameError] = useState(null); const [balanceError, setBalanceError] = useState(false); const validateBalance = balance => !isNaN(parseFloat(balance)); + const { t } = useTranslation(); + + const validateName = (name: string): boolean => { + if (!name) { + setNameError(t('Name is required')); + return false; + } else if (accountNameIsInUse(name, accounts)) { + setNameError(t('Name {{ name }} must be unique.', { name })); + return false; + } + return true; + }; + + const validateAndSetName = (name: string) => { + if (validateName(name)) { + setName(name); + setNameError(null); + } + }; const onSubmit = async (event: FormEvent) => { event.preventDefault(); - const nameError = !name; - setNameError(nameError); + validateName(name); const balanceError = !validateBalance(balance); setBalanceError(balanceError); @@ -72,19 +98,14 @@ export function CreateLocalAccountModal() { onChange={event => setName(event.target.value)} onBlur={event => { const name = event.target.value.trim(); - setName(name); - if (name && nameError) { - setNameError(false); - } + validateAndSetName(name); }} style={{ flex: 1 }} /> {nameError && ( - - Name is required - + {nameError} )} >({ padding: 10, }} > - - {name} - {accountNote && ( ({ diff --git a/packages/desktop-client/src/components/util/accountValidation.ts b/packages/desktop-client/src/components/util/accountValidation.ts new file mode 100644 index 00000000000..ddbb25c8269 --- /dev/null +++ b/packages/desktop-client/src/components/util/accountValidation.ts @@ -0,0 +1,23 @@ +import { t } from 'i18next'; + +import { type AccountEntity } from 'loot-core/types/models'; + +export function validateAccountName( + newAccountName: string, + accountId: string, + accounts: AccountEntity[], +) { + newAccountName = newAccountName.trim(); + if (newAccountName.length) { + const duplicateNamedAccounts = accounts.filter( + account => account.name === newAccountName && account.id !== accountId, + ); + if (duplicateNamedAccounts.length) { + return t('Name {{ newAccountName }} must be unique.', { newAccountName }); + } else { + return null; + } + } else { + return t('Name cannot be blank.'); + } +} From 8c8783716c5e228aa1c46aa1d4f3258b79185b88 Mon Sep 17 00:00:00 2001 From: qedi-r <1435081+qedi-r@users.noreply.github.com> Date: Sat, 28 Sep 2024 12:31:49 -0400 Subject: [PATCH 02/10] unify error messages and styles --- .../desktop-client/src/components/accounts/Header.jsx | 5 +++-- .../components/mobile/accounts/AccountTransactions.tsx | 1 - .../src/components/modals/CreateLocalAccountModal.tsx | 10 ++++++++-- .../desktop-client/src/components/sidebar/Account.tsx | 10 ++++++++++ 4 files changed, 21 insertions(+), 5 deletions(-) diff --git a/packages/desktop-client/src/components/accounts/Header.jsx b/packages/desktop-client/src/components/accounts/Header.jsx index 950d29f1b3a..657a8a408bf 100644 --- a/packages/desktop-client/src/components/accounts/Header.jsx +++ b/packages/desktop-client/src/components/accounts/Header.jsx @@ -16,7 +16,6 @@ import { SvgPencil1, } from '../../icons/v2'; import { theme, styles } from '../../style'; -import { Warning } from '../alerts'; import { AnimatedRefresh } from '../AnimatedRefresh'; import { Button } from '../common/Button2'; import { InitialFocus } from '../common/InitialFocus'; @@ -443,7 +442,9 @@ function AccountNameField({ }} /> - {saveNameError && {saveNameError}} + {saveNameError && ( + {saveNameError} + )} ); } else { diff --git a/packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx b/packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx index 453212917d7..eaf49332fb9 100644 --- a/packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx +++ b/packages/desktop-client/src/components/mobile/accounts/AccountTransactions.tsx @@ -43,7 +43,6 @@ import { Button } from '../../common/Button2'; import { Text } from '../../common/Text'; import { View } from '../../common/View'; import { MobilePageHeader, Page } from '../../Page'; -import { validateAccountName } from '../../util/accountValidation'; import { MobileBackButton } from '../MobileBackButton'; import { AddTransactionButton } from '../transactions/AddTransactionButton'; import { TransactionListWithBalances } from '../transactions/TransactionListWithBalances'; diff --git a/packages/desktop-client/src/components/modals/CreateLocalAccountModal.tsx b/packages/desktop-client/src/components/modals/CreateLocalAccountModal.tsx index 2f46b080091..0dafff3084c 100644 --- a/packages/desktop-client/src/components/modals/CreateLocalAccountModal.tsx +++ b/packages/desktop-client/src/components/modals/CreateLocalAccountModal.tsx @@ -27,6 +27,7 @@ import { import { Text } from '../common/Text'; import { View } from '../common/View'; import { Checkbox } from '../forms'; +import { validateAccountName } from '../util/accountValidation'; function accountNameIsInUse(accountName: string, accounts: AccountEntity[]) { return accounts.map(a => a.name).includes(accountName); @@ -58,7 +59,10 @@ export function CreateLocalAccountModal() { }; const validateAndSetName = (name: string) => { - if (validateName(name)) { + const nameError = validateAccountName(name, '', accounts); + if (nameError) { + setNameError(nameError); + } else { setName(name); setNameError(null); } @@ -105,7 +109,9 @@ export function CreateLocalAccountModal() { {nameError && ( - {nameError} + + {nameError} + )} >({ padding: 10, }} > + + {name} + {accountNote && ( ({ From a54c1c93670e2d8a0ec7965f8ed9fe67c3fac308 Mon Sep 17 00:00:00 2001 From: qedi-r <1435081+qedi-r@users.noreply.github.com> Date: Sat, 28 Sep 2024 12:47:06 -0400 Subject: [PATCH 03/10] keep state for the current account name on the mobile account modal --- .../desktop-client/src/components/modals/AccountMenuModal.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/desktop-client/src/components/modals/AccountMenuModal.tsx b/packages/desktop-client/src/components/modals/AccountMenuModal.tsx index 7106dc5bc7f..afe68897663 100644 --- a/packages/desktop-client/src/components/modals/AccountMenuModal.tsx +++ b/packages/desktop-client/src/components/modals/AccountMenuModal.tsx @@ -42,8 +42,10 @@ export function AccountMenuModal({ const accounts = useAccounts(); const originalNotes = useNotes(`account-${accountId}`); const [accountNameError, setAccountNameError] = useState(null); + const [currentAccountName, setCurrentAccountName] = useState(account.name); const onRename = (newName: string) => { + setCurrentAccountName(newName); if (!account) { return; } @@ -110,7 +112,7 @@ export function AccountMenuModal({ From c1011f176d8998a0b23edc1782cb72c4a17b706b Mon Sep 17 00:00:00 2001 From: qedi-r <1435081+qedi-r@users.noreply.github.com> Date: Sat, 28 Sep 2024 12:52:41 -0400 Subject: [PATCH 04/10] add release notes --- upcoming-release-notes/3527.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 upcoming-release-notes/3527.md diff --git a/upcoming-release-notes/3527.md b/upcoming-release-notes/3527.md new file mode 100644 index 00000000000..6e476c8276d --- /dev/null +++ b/upcoming-release-notes/3527.md @@ -0,0 +1,6 @@ +--- +category: Bugfix +authors: [qedi-r] +--- + +Updates UI to disallow non-unique account names. From 8a6b13e65644f385681576d62f9a07733a6baafe Mon Sep 17 00:00:00 2001 From: qedi-r <1435081+qedi-r@users.noreply.github.com> Date: Sat, 28 Sep 2024 13:13:57 -0400 Subject: [PATCH 05/10] remove extra validation function --- .../modals/CreateLocalAccountModal.tsx | 20 +------------------ 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/packages/desktop-client/src/components/modals/CreateLocalAccountModal.tsx b/packages/desktop-client/src/components/modals/CreateLocalAccountModal.tsx index 0dafff3084c..9b93c54a37c 100644 --- a/packages/desktop-client/src/components/modals/CreateLocalAccountModal.tsx +++ b/packages/desktop-client/src/components/modals/CreateLocalAccountModal.tsx @@ -1,12 +1,10 @@ // @ts-strict-ignore import { type FormEvent, useState } from 'react'; import { Form } from 'react-aria-components'; -import { useTranslation } from 'react-i18next'; import { useDispatch } from 'react-redux'; import { closeModal, createAccount } from 'loot-core/client/actions'; import { toRelaxedNumber } from 'loot-core/src/shared/util'; -import { type AccountEntity } from 'loot-core/types/models'; import * as useAccounts from '../../hooks/useAccounts'; import { useNavigate } from '../../hooks/useNavigate'; @@ -29,10 +27,6 @@ import { View } from '../common/View'; import { Checkbox } from '../forms'; import { validateAccountName } from '../util/accountValidation'; -function accountNameIsInUse(accountName: string, accounts: AccountEntity[]) { - return accounts.map(a => a.name).includes(accountName); -} - export function CreateLocalAccountModal() { const navigate = useNavigate(); const dispatch = useDispatch(); @@ -45,18 +39,6 @@ export function CreateLocalAccountModal() { const [balanceError, setBalanceError] = useState(false); const validateBalance = balance => !isNaN(parseFloat(balance)); - const { t } = useTranslation(); - - const validateName = (name: string): boolean => { - if (!name) { - setNameError(t('Name is required')); - return false; - } else if (accountNameIsInUse(name, accounts)) { - setNameError(t('Name {{ name }} must be unique.', { name })); - return false; - } - return true; - }; const validateAndSetName = (name: string) => { const nameError = validateAccountName(name, '', accounts); @@ -71,7 +53,7 @@ export function CreateLocalAccountModal() { const onSubmit = async (event: FormEvent) => { event.preventDefault(); - validateName(name); + const nameError = validateAccountName(name, '', accounts); const balanceError = !validateBalance(balance); setBalanceError(balanceError); From 4d7a0f770d2d5da3ff8b6c4f9c91f8900f866166 Mon Sep 17 00:00:00 2001 From: qedi-r <1435081+qedi-r@users.noreply.github.com> Date: Sat, 28 Sep 2024 13:16:19 -0400 Subject: [PATCH 06/10] fix typo in AccountSyncSidebar params --- packages/desktop-client/src/components/accounts/Header.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/desktop-client/src/components/accounts/Header.jsx b/packages/desktop-client/src/components/accounts/Header.jsx index 657a8a408bf..ef8ce343621 100644 --- a/packages/desktop-client/src/components/accounts/Header.jsx +++ b/packages/desktop-client/src/components/accounts/Header.jsx @@ -179,7 +179,7 @@ export function AccountHeader({ {!!account?.bank && ( )} From 7a3529d73d3c378417513677f4bbecca86b374df Mon Sep 17 00:00:00 2001 From: qedi-r <1435081+qedi-r@users.noreply.github.com> Date: Sat, 28 Sep 2024 13:33:12 -0400 Subject: [PATCH 07/10] don't set current account name to empty string and prevent further changes on AccountMenuModal --- .../src/components/accounts/Account.tsx | 4 ++-- .../components/modals/AccountMenuModal.tsx | 24 +++++++++++++------ .../src/components/util/accountValidation.ts | 4 ++-- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/packages/desktop-client/src/components/accounts/Account.tsx b/packages/desktop-client/src/components/accounts/Account.tsx index 1c8d20bdbbb..f06f6fb83d0 100644 --- a/packages/desktop-client/src/components/accounts/Account.tsx +++ b/packages/desktop-client/src/components/accounts/Account.tsx @@ -345,7 +345,7 @@ class AccountInternal extends PureComponent< showCleared: props.showCleared, showReconciled: props.showReconciled, editingName: false, - nameError: null, + nameError: '', isAdding: false, sort: null, filteredAmount: null, @@ -718,7 +718,7 @@ class AccountInternal extends PureComponent< account => account.id === this.props.accountId, ); this.props.updateAccount({ ...account, name }); - this.setState({ editingName: false, nameError: null }); + this.setState({ editingName: false, nameError: '' }); } }; diff --git a/packages/desktop-client/src/components/modals/AccountMenuModal.tsx b/packages/desktop-client/src/components/modals/AccountMenuModal.tsx index afe68897663..0737cf30b1f 100644 --- a/packages/desktop-client/src/components/modals/AccountMenuModal.tsx +++ b/packages/desktop-client/src/components/modals/AccountMenuModal.tsx @@ -20,6 +20,7 @@ import { Popover } from '../common/Popover'; import { View } from '../common/View'; import { Notes } from '../Notes'; import { validateAccountName } from '../util/accountValidation'; +import { t } from 'i18next'; type AccountMenuModalProps = { accountId: string; @@ -41,14 +42,21 @@ export function AccountMenuModal({ const account = useAccount(accountId); const accounts = useAccounts(); const originalNotes = useNotes(`account-${accountId}`); - const [accountNameError, setAccountNameError] = useState(null); - const [currentAccountName, setCurrentAccountName] = useState(account.name); + const [accountNameError, setAccountNameError] = useState(''); + const [currentAccountName, setCurrentAccountName] = useState( + account?.name || t('New Account'), + ); const onRename = (newName: string) => { - setCurrentAccountName(newName); + newName = newName.trim(); if (!account) { return; } + if (!newName) { + setCurrentAccountName(t('Account')); + } else { + setCurrentAccountName(newName); + } if (newName !== account.name) { const renameAccountError = validateAccountName( @@ -59,7 +67,7 @@ export function AccountMenuModal({ if (renameAccountError) { setAccountNameError(renameAccountError); } else { - setAccountNameError(null); + setAccountNameError(''); onSave?.({ ...account, name: newName, @@ -115,9 +123,11 @@ export function AccountMenuModal({ title={currentAccountName} onTitleUpdate={onRename} /> - - {accountNameError} - + {accountNameError && ( + + {accountNameError} + + )} } rightContent={} diff --git a/packages/desktop-client/src/components/util/accountValidation.ts b/packages/desktop-client/src/components/util/accountValidation.ts index ddbb25c8269..f825e4b796f 100644 --- a/packages/desktop-client/src/components/util/accountValidation.ts +++ b/packages/desktop-client/src/components/util/accountValidation.ts @@ -6,7 +6,7 @@ export function validateAccountName( newAccountName: string, accountId: string, accounts: AccountEntity[], -) { +): string { newAccountName = newAccountName.trim(); if (newAccountName.length) { const duplicateNamedAccounts = accounts.filter( @@ -15,7 +15,7 @@ export function validateAccountName( if (duplicateNamedAccounts.length) { return t('Name {{ newAccountName }} must be unique.', { newAccountName }); } else { - return null; + return ''; } } else { return t('Name cannot be blank.'); From a52d1730a65c61ae6bea4ae10a20159e7c4fa4b4 Mon Sep 17 00:00:00 2001 From: qedi-r <1435081+qedi-r@users.noreply.github.com> Date: Sat, 28 Sep 2024 13:39:20 -0400 Subject: [PATCH 08/10] lint imports --- .../desktop-client/src/components/modals/AccountMenuModal.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/desktop-client/src/components/modals/AccountMenuModal.tsx b/packages/desktop-client/src/components/modals/AccountMenuModal.tsx index 0737cf30b1f..8dbfa8b720d 100644 --- a/packages/desktop-client/src/components/modals/AccountMenuModal.tsx +++ b/packages/desktop-client/src/components/modals/AccountMenuModal.tsx @@ -1,5 +1,7 @@ import React, { type ComponentProps, Fragment, useRef, useState } from 'react'; +import { t } from 'i18next'; + import { type AccountEntity } from 'loot-core/types/models'; import { useAccount } from '../../hooks/useAccount'; @@ -20,7 +22,6 @@ import { Popover } from '../common/Popover'; import { View } from '../common/View'; import { Notes } from '../Notes'; import { validateAccountName } from '../util/accountValidation'; -import { t } from 'i18next'; type AccountMenuModalProps = { accountId: string; From b6df9c57f756373f734ac62cc41d53e11c11a2f8 Mon Sep 17 00:00:00 2001 From: qedi-r <1435081+qedi-r@users.noreply.github.com> Date: Thu, 3 Oct 2024 08:23:56 +0200 Subject: [PATCH 09/10] update error message to Name x already exists --- .../desktop-client/src/components/util/accountValidation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/desktop-client/src/components/util/accountValidation.ts b/packages/desktop-client/src/components/util/accountValidation.ts index f825e4b796f..86ec7fe94b6 100644 --- a/packages/desktop-client/src/components/util/accountValidation.ts +++ b/packages/desktop-client/src/components/util/accountValidation.ts @@ -13,7 +13,7 @@ export function validateAccountName( account => account.name === newAccountName && account.id !== accountId, ); if (duplicateNamedAccounts.length) { - return t('Name {{ newAccountName }} must be unique.', { newAccountName }); + return t('Name {{ newAccountName }} already exists.', { newAccountName }); } else { return ''; } From 67d17190888c2ff02c62cfb0f527a76fe013a156 Mon Sep 17 00:00:00 2001 From: qedi-r <1435081+qedi-r@users.noreply.github.com> Date: Thu, 10 Oct 2024 18:01:16 -0400 Subject: [PATCH 10/10] use proper translation functions --- .../desktop-client/src/components/accounts/Header.jsx | 8 +++++--- .../src/components/modals/AccountMenuModal.tsx | 6 +++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/desktop-client/src/components/accounts/Header.jsx b/packages/desktop-client/src/components/accounts/Header.jsx index ef8ce343621..24e9fc6dc66 100644 --- a/packages/desktop-client/src/components/accounts/Header.jsx +++ b/packages/desktop-client/src/components/accounts/Header.jsx @@ -1,8 +1,6 @@ import React, { useState, useRef, Fragment } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; -import { Trans } from 'react-i18next'; - -import { t } from 'i18next'; +import { Trans, useTranslation } from 'react-i18next'; import { useLocalPref } from '../../hooks/useLocalPref'; import { useSplitsExpanded } from '../../hooks/useSplitsExpanded'; @@ -92,6 +90,7 @@ export function AccountHeader({ onMakeAsSplitTransaction, onMakeAsNonSplitTransactions, }) { + const { t } = useTranslation(); const [menuOpen, setMenuOpen] = useState(false); const searchInput = useRef(null); const triggerRef = useRef(null); @@ -421,6 +420,8 @@ function AccountNameField({ onSaveName, onExposeName, }) { + const { t } = useTranslation(); + if (editingName) { return ( @@ -527,6 +528,7 @@ function AccountMenu({ onReconcile, onMenuSelect, }) { + const { t } = useTranslation(); const [tooltip, setTooltip] = useState('default'); const syncServerStatus = useSyncServerStatus(); diff --git a/packages/desktop-client/src/components/modals/AccountMenuModal.tsx b/packages/desktop-client/src/components/modals/AccountMenuModal.tsx index 8dbfa8b720d..c65fea01f3b 100644 --- a/packages/desktop-client/src/components/modals/AccountMenuModal.tsx +++ b/packages/desktop-client/src/components/modals/AccountMenuModal.tsx @@ -1,6 +1,5 @@ -import React, { type ComponentProps, Fragment, useRef, useState } from 'react'; - -import { t } from 'i18next'; +import { type ComponentProps, Fragment, useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { type AccountEntity } from 'loot-core/types/models'; @@ -40,6 +39,7 @@ export function AccountMenuModal({ onEditNotes, onClose, }: AccountMenuModalProps) { + const { t } = useTranslation(); const account = useAccount(accountId); const accounts = useAccounts(); const originalNotes = useNotes(`account-${accountId}`);