-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix ability to have duplicate account names #3527
Changes from 3 commits
3738bde
8c87837
a54c1c9
c1011f1
8a6b13e
4d7a0f7
7a3529d
a52d173
b6df9c5
2642df5
4ac887a
67d1719
1103b4c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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'; | ||
|
@@ -67,6 +69,7 @@ export function AccountHeader({ | |
onCreateReconciliationTransaction, | ||
onToggleExtraBalances, | ||
onSaveName, | ||
saveNameError, | ||
onExposeName, | ||
onSync, | ||
onImport, | ||
|
@@ -89,8 +92,6 @@ export function AccountHeader({ | |
onMakeAsSplitTransaction, | ||
onMakeAsNonSplitTransactions, | ||
}) { | ||
const { t } = useTranslation(); | ||
|
||
const [menuOpen, setMenuOpen] = useState(false); | ||
const searchInput = useRef(null); | ||
const triggerRef = useRef(null); | ||
|
@@ -176,99 +177,21 @@ export function AccountHeader({ | |
}} | ||
> | ||
{!!account?.bank && ( | ||
<View | ||
style={{ | ||
backgroundColor: accountsSyncing.includes(account.id) | ||
? theme.sidebarItemBackgroundPending | ||
: failedAccounts.has(account.id) | ||
? theme.sidebarItemBackgroundFailed | ||
: theme.sidebarItemBackgroundPositive, | ||
marginRight: '4px', | ||
width: 8, | ||
height: 8, | ||
borderRadius: 8, | ||
}} | ||
<AccountSyncSidebar | ||
account={account} | ||
failedAccount={failedAccounts} | ||
accountsSyncing={accountsSyncing} | ||
qedi-r marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/> | ||
)} | ||
{editingName ? ( | ||
<InitialFocus> | ||
<Input | ||
defaultValue={accountName} | ||
onEnter={e => 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', | ||
}} | ||
/> | ||
</InitialFocus> | ||
) : isNameEditable ? ( | ||
<View | ||
style={{ | ||
flexDirection: 'row', | ||
alignItems: 'center', | ||
gap: 3, | ||
'& .hover-visible': { | ||
opacity: 0, | ||
transition: 'opacity .25s', | ||
}, | ||
'&:hover .hover-visible': { | ||
opacity: 1, | ||
}, | ||
}} | ||
> | ||
<View | ||
style={{ | ||
fontSize: 25, | ||
fontWeight: 500, | ||
marginRight: 5, | ||
marginBottom: -1, | ||
}} | ||
data-testid="account-name" | ||
> | ||
{account && account.closed | ||
? t('Closed: {{ accountName }}', { accountName }) | ||
: accountName} | ||
</View> | ||
|
||
{account && ( | ||
<NotesButton | ||
id={`account-${account.id}`} | ||
defaultColor={theme.pageTextSubdued} | ||
/> | ||
)} | ||
<Button | ||
variant="bare" | ||
aria-label={t('Edit account name')} | ||
className="hover-visible" | ||
onPress={() => onExposeName(true)} | ||
> | ||
<SvgPencil1 | ||
style={{ | ||
width: 11, | ||
height: 11, | ||
color: theme.pageTextSubdued, | ||
}} | ||
/> | ||
</Button> | ||
</View> | ||
) : ( | ||
<View | ||
style={{ fontSize: 25, fontWeight: 500, marginBottom: -1 }} | ||
data-testid="account-name" | ||
> | ||
{account && account.closed | ||
? t('Closed: {{ accountName }}', { accountName }) | ||
: accountName} | ||
</View> | ||
)} | ||
<AccountNameField | ||
account={account} | ||
accountName={accountName} | ||
isNameEditable={isNameEditable} | ||
editingName={editingName} | ||
saveNameError={saveNameError} | ||
onSaveName={onSaveName} | ||
onExposeName={onExposeName} | ||
/> | ||
</View> | ||
</View> | ||
|
||
|
@@ -471,6 +394,127 @@ export function AccountHeader({ | |
); | ||
} | ||
|
||
function AccountSyncSidebar({ account, failedAccounts, accountsSyncing }) { | ||
return ( | ||
<View | ||
style={{ | ||
backgroundColor: accountsSyncing.includes(account.id) | ||
? theme.sidebarItemBackgroundPending | ||
: failedAccounts.has(account.id) | ||
? theme.sidebarItemBackgroundFailed | ||
: theme.sidebarItemBackgroundPositive, | ||
marginRight: '4px', | ||
width: 8, | ||
height: 8, | ||
borderRadius: 8, | ||
}} | ||
/> | ||
); | ||
} | ||
|
||
function AccountNameField({ | ||
account, | ||
accountName, | ||
isNameEditable, | ||
editingName, | ||
saveNameError, | ||
onSaveName, | ||
onExposeName, | ||
}) { | ||
if (editingName) { | ||
return ( | ||
<Fragment> | ||
<InitialFocus> | ||
<Input | ||
defaultValue={accountName} | ||
onEnter={e => 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', | ||
}} | ||
/> | ||
</InitialFocus> | ||
{saveNameError && ( | ||
<View style={{ color: theme.warningText }}>{saveNameError}</View> | ||
)} | ||
</Fragment> | ||
); | ||
} else { | ||
if (isNameEditable) { | ||
return ( | ||
<View | ||
style={{ | ||
flexDirection: 'row', | ||
alignItems: 'center', | ||
gap: 3, | ||
'& .hover-visible': { | ||
opacity: 0, | ||
transition: 'opacity .25s', | ||
}, | ||
'&:hover .hover-visible': { | ||
opacity: 1, | ||
}, | ||
}} | ||
> | ||
Comment on lines
+454
to
+465
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Refactor inline styles to support pseudo-classes The current inline styles for hover effects use pseudo-classes and nested selectors, which are not supported in React's inline style objects. This may cause the hover effects to not work as intended. Consider refactoring these styles using a CSS-in-JS solution like import styled from 'styled-components';
const HoverContainer = styled.div`
display: flex;
flex-direction: row;
align-items: center;
gap: 3px;
.hover-visible {
opacity: 0;
transition: opacity 0.25s;
}
&:hover .hover-visible {
opacity: 1;
}
`;
// Then in your component:
<HoverContainer>
{/* ... existing content ... */}
</HoverContainer> This approach will ensure that the hover effects work as expected while maintaining the component's styling logic. |
||
<View | ||
style={{ | ||
fontSize: 25, | ||
fontWeight: 500, | ||
marginRight: 5, | ||
marginBottom: -1, | ||
}} | ||
data-testid="account-name" | ||
> | ||
{account && account.closed | ||
? t('Closed: {{ accountName }}', { accountName }) | ||
qedi-r marked this conversation as resolved.
Show resolved
Hide resolved
|
||
: accountName} | ||
</View> | ||
|
||
{account && ( | ||
<NotesButton | ||
id={`account-${account.id}`} | ||
defaultColor={theme.pageTextSubdued} | ||
/> | ||
)} | ||
<Button | ||
variant="bare" | ||
aria-label={t('Edit account name')} | ||
className="hover-visible" | ||
onPress={() => onExposeName(true)} | ||
> | ||
<SvgPencil1 | ||
style={{ | ||
width: 11, | ||
height: 11, | ||
color: theme.pageTextSubdued, | ||
}} | ||
/> | ||
</Button> | ||
</View> | ||
); | ||
} else { | ||
return ( | ||
<View | ||
style={{ fontSize: 25, fontWeight: 500, marginBottom: -1 }} | ||
data-testid="account-name" | ||
> | ||
{account && account.closed | ||
? t('Closed: {{ accountName }}', { accountName }) | ||
qedi-r marked this conversation as resolved.
Show resolved
Hide resolved
|
||
: accountName} | ||
</View> | ||
); | ||
} | ||
} | ||
} | ||
|
||
function AccountMenu({ | ||
account, | ||
canSync, | ||
|
@@ -483,8 +527,6 @@ function AccountMenu({ | |
onReconcile, | ||
onMenuSelect, | ||
}) { | ||
const { t } = useTranslation(); | ||
|
||
const [tooltip, setTooltip] = useState('default'); | ||
const syncServerStatus = useSyncServerStatus(); | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider using the
useTranslation
hook instead of importingt
directly.Importing
t
directly fromi18next
might not properly handle re-renders or language changes within React components. Using theuseTranslation
hook fromreact-i18next
ensures better integration with React's lifecycle and updates when the language changes.Apply this diff to use the
useTranslation
hook:There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was going to ask about this one - it looks like it's in use like this in other places in the product, so I thought this was acceptable. Maybe clarifying statements should be made on https://actualbudget.org/docs/contributing/i18n/?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@qedi-r, thank you for pointing that out. Since importing
t
directly fromi18next
is used elsewhere in the codebase, it's acceptable in this context. Updating the contributing guidelines to clarify this practice would indeed be helpful.(_/)
(•_•)
(/|) That's a great observation!
✏️ Learnings added
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would suggest keeping the
useTranslation
here, there is extra logic to make it well-behaved on language changes and such. We really should use it everywhere but one step at a time.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.