import browser from 'webextension-polyfill' import React, { useState, useContext, useEffect, useRef } from 'react' import { useNavigate } from 'react-router-dom' import { toast } from 'react-toastify' import hideStringMiddle from '../helpers/hideStringMiddle' import ImportAccountModal from '../modals/ImportAccountModal' import DeriveAccountModal from '../modals/DeriveAccountModal' import MainContext from '../contexts/MainContext' import { getSessionVault, setSessionVault } from '../common' const Accounts = () => { const { accounts, defaultAccount, updateDefaultAccount } = useContext(MainContext) const [isDropdownOpen, setIsDropdownOpen] = useState(false) const [showImportAccountModal, setShowImportAccountModal] = useState(false) const [showDeriveAccount, setShowDeriveAccount] = useState(false) const dropdownRef = useRef(null) const navigate = useNavigate() // Close dropdown on click outside useEffect(() => { const handleClickOutside = (event) => { if (dropdownRef.current && !dropdownRef.current.contains(event.target)) { setIsDropdownOpen(false) } } const handleEscape = (event) => { if (event.key === 'Escape') { setIsDropdownOpen(false) } } if (isDropdownOpen) { document.addEventListener('mousedown', handleClickOutside) document.addEventListener('keydown', handleEscape) } return () => { document.removeEventListener('mousedown', handleClickOutside) document.removeEventListener('keydown', handleEscape) } }, [isDropdownOpen]) const changeDefaultAccount = async (prvKey) => { const vault = await getSessionVault() if (!vault) return // Find the account being switched to const targetAccount = accounts.find(acc => acc.prvKey === prvKey) const accountName = targetAccount?.name || (targetAccount?.type === 'derived' ? `Account ${targetAccount.index}` : `Imported ${targetAccount?.index}`) vault.accountDefault = prvKey // Encrypt via background (key stays in background memory) const response = await browser.runtime.sendMessage({ type: 'ENCRYPT_VAULT', data: vault }) if (response.success) { await browser.storage.local.set({ encryptedVault: response.encryptedData }) } await setSessionVault(vault) await updateDefaultAccount() setIsDropdownOpen(false) // Show confirmation toast toast.success(`Switched to ${accountName}`, { position: 'bottom-center', autoClose: 2000, hideProgressBar: true }) navigate('/vault') } const toggleDropdown = () => { setIsDropdownOpen(prev => !prev) } const lockVault = async () => { // Send lock request to background (clears key from memory) await browser.runtime.sendMessage({ type: 'LOCK_VAULT' }) } return ( <>
{ e.preventDefault(); toggleDropdown() }}>
{!defaultAccount.type ? 'Loading...' : defaultAccount.type === "derived" ? (defaultAccount.name || `Account ${defaultAccount.index}`) : (defaultAccount.name || `Imported ${defaultAccount.index}`)}
{isDropdownOpen && (
Accounts
{ e.preventDefault(); lockVault() }} title="Lock now"> Lock
{accounts.length === 0 ? (
Loading accounts...
) : accounts.map((account) => { const isCurrentAccount = account.prvKey === defaultAccount.prvKey const displayName = account.type === "derived" ? (account.name || `Account ${account.index}`) : (account.name || `Imported ${account.index}`) return (
{ e.preventDefault(); changeDefaultAccount(account.prvKey) }}>
{isCurrentAccount && ( )}
{displayName} {account.type === "derived" && Derived} {account.type === "imported" && Imported}
{account.npub ? hideStringMiddle(account.npub, 8, 6) : '...'}
) })}
)} setShowImportAccountModal(false)} onClose={() => setShowImportAccountModal(false)} /> setShowDeriveAccount(false)} onClose={() => setShowDeriveAccount(false)} />
) } export default Accounts