diff --git a/src/components/settings-modal/account-settings/__tests__/account-settings.test.tsx b/src/components/settings-modal/account-settings/__tests__/account-settings.test.tsx index 0b206aa3..7f684e62 100644 --- a/src/components/settings-modal/account-settings/__tests__/account-settings.test.tsx +++ b/src/components/settings-modal/account-settings/__tests__/account-settings.test.tsx @@ -7,6 +7,7 @@ import AccountSettings from '../account-settings'; (globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true; const act = (React as { act?: (cb: () => void | Promise) => void | Promise }).act as (cb: () => void | Promise) => void | Promise; +const IMPORTED_ACCOUNT_ADDRESSES_STORAGE_KEY = 'importedAccountAddresses'; const hookMocks = vi.hoisted(() => ({ deleteAccount: vi.fn(), @@ -177,6 +178,34 @@ describe('AccountSettings', () => { expect(buttonTexts.some((text) => text.includes('delete_account'))).toBe(true); }); + it('shows generated-account copy for app-created accounts', () => { + render(); + + const infoText = container.querySelector('select')?.parentElement?.querySelector('div')?.textContent ?? ''; + expect(infoText).toContain('account_auto_generated'); + expect(infoText).toContain('stored_locally'); + }); + + it('shows only the stored-locally copy for imported accounts', () => { + localStorage.setItem(IMPORTED_ACCOUNT_ADDRESSES_STORAGE_KEY, JSON.stringify(['0x123'])); + + render(); + + const infoText = container.querySelector('select')?.parentElement?.querySelector('div')?.textContent ?? ''; + expect(infoText).toContain('stored_locally'); + expect(infoText).not.toContain('account_auto_generated'); + }); + + it('treats the legacy imported account storage key as imported state', () => { + localStorage.setItem('importedAccountAddress', '0x123'); + + render(); + + const infoText = container.querySelector('select')?.parentElement?.querySelector('div')?.textContent ?? ''; + expect(infoText).toContain('stored_locally'); + expect(infoText).not.toContain('account_auto_generated'); + }); + it('deletes the account only after both confirmations succeed', async () => { confirmSpy.mockReturnValueOnce(true).mockReturnValueOnce(true); @@ -314,6 +343,7 @@ describe('AccountSettings', () => { expect(hookMocks.importAccount).toHaveBeenCalledOnce(); const importedPayload = JSON.parse(hookMocks.importAccount.mock.calls[0][0]); expect(importedPayload.account.subscriptions).toEqual(['business.eth', 'music-posting.bso']); + expect(localStorage.getItem(IMPORTED_ACCOUNT_ADDRESSES_STORAGE_KEY)).toBe(JSON.stringify(['0x999'])); expect(localStorage.getItem('importedAccountAddress')).toBe('0x999'); expect(hookMocks.setActiveAccount).toHaveBeenCalledWith('Imported'); expect(alertSpy).toHaveBeenCalledWith('Imported Imported'); diff --git a/src/components/settings-modal/account-settings/account-settings.tsx b/src/components/settings-modal/account-settings/account-settings.tsx index e2c63afc..9ff4c1ac 100644 --- a/src/components/settings-modal/account-settings/account-settings.tsx +++ b/src/components/settings-modal/account-settings/account-settings.tsx @@ -6,6 +6,8 @@ import { Capacitor } from '@capacitor/core'; import { useLocation, useNavigate } from 'react-router-dom'; const isAndroid = Capacitor.getPlatform() === 'android'; +const IMPORTED_ACCOUNT_ADDRESSES_STORAGE_KEY = 'importedAccountAddresses'; +const IMPORTED_ACCOUNT_ADDRESS_LEGACY_STORAGE_KEY = 'importedAccountAddress'; const safeParseJSON = (value: string): T | null => { try { @@ -24,6 +26,32 @@ const withErrorHandling = async (fn: () => Promise, onError: (e: unknown) } }; +const readImportedAccountAddresses = (): string[] => { + try { + const storedAddresses = localStorage.getItem(IMPORTED_ACCOUNT_ADDRESSES_STORAGE_KEY); + const parsedAddresses = storedAddresses ? safeParseJSON(storedAddresses) : null; + const normalizedAddresses = Array.isArray(parsedAddresses) + ? parsedAddresses.filter((address): address is string => typeof address === 'string' && address.length > 0) + : []; + const legacyAddress = localStorage.getItem(IMPORTED_ACCOUNT_ADDRESS_LEGACY_STORAGE_KEY); + return [...new Set(legacyAddress ? [...normalizedAddresses, legacyAddress] : normalizedAddresses)]; + } catch (error) { + console.warn('Failed to read imported account addresses from localStorage:', error); + return []; + } +}; + +const rememberImportedAccountAddress = (address: string) => { + try { + const importedAddresses = readImportedAccountAddresses(); + const nextImportedAddresses = [...new Set([...importedAddresses, address])]; + localStorage.setItem(IMPORTED_ACCOUNT_ADDRESSES_STORAGE_KEY, JSON.stringify(nextImportedAddresses)); + localStorage.setItem(IMPORTED_ACCOUNT_ADDRESS_LEGACY_STORAGE_KEY, address); + } catch (error) { + console.warn('Failed to save imported account address to localStorage:', error); + } +}; + // Inner component keyed by account id so state resets when user switches account const AccountSettingsEditor = ({ account, @@ -125,7 +153,7 @@ const AccountSettingsEditor = ({ async () => { await importAccount(modifiedAccountJson); if (accountData.account?.author?.address) { - localStorage.setItem('importedAccountAddress', accountData.account.author.address); + rememberImportedAccountAddress(accountData.account.author.address); } if (accountData.account?.name) { await setActiveAccount(accountData.account.name); @@ -163,6 +191,10 @@ const AccountSettingsEditor = ({ )); const host = window.electronApi?.isElectron ? 'this desktop app' : isAndroid ? 'this mobile app' : window.location.hostname; + const isImportedAccount = typeof account?.author?.address === 'string' && readImportedAccountAddresses().includes(account.author.address); + const accountStorageInfo = isImportedAccount + ? t('stored_locally', { location: host, interpolation: { escapeValue: false } }) + : `${t('account_auto_generated')} ${t('stored_locally', { location: host, interpolation: { escapeValue: false } })}`; return (
@@ -172,9 +204,7 @@ const AccountSettingsEditor = ({ {' '} {' '} -
- {t('account_auto_generated')} {t('stored_locally', { location: host, interpolation: { escapeValue: false } })} -
+
{accountStorageInfo}