Skip to content

Commit

Permalink
feat: support multiple accounts in Wallet UI (#506)
Browse files Browse the repository at this point in the history
* refactor: add account discover service

* feat: support multiple account in SNAP

* feat: watch the SNAP current account

* fix: lint

* feat: support multiple account

---------

Co-authored-by: khanti42 <[email protected]>
  • Loading branch information
stanleyyconsensys and khanti42 authored Feb 6, 2025
1 parent c129f1c commit a9fcd27
Show file tree
Hide file tree
Showing 32 changed files with 1,138 additions and 586 deletions.
1 change: 1 addition & 0 deletions packages/wallet-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
"@metamask/eslint-config-jest": "^12.1.0",
"@metamask/eslint-config-nodejs": "^12.1.0",
"@metamask/eslint-config-typescript": "^12.1.0",
"@metamask/utils": "^11.0.1",
"@storybook/addon-actions": "^6.5.3",
"@storybook/addon-essentials": "^6.5.3",
"@storybook/addon-interactions": "^6.5.3",
Expand Down
19 changes: 8 additions & 11 deletions packages/wallet-ui/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,12 @@ import 'toastr2/dist/toastr.min.css';
import { NoMetamaskModal } from 'components/ui/organism/NoMetamaskModal';
import { MinVersionModal } from './components/ui/organism/MinVersionModal';
import { useHasMetamask } from 'hooks/useHasMetamask';
import { DUMMY_ADDRESS } from 'utils/constants';
import { DeployModal } from 'components/ui/organism/DeployModal';

library.add(fas, far);

function App() {
const { initSnap, getWalletData, checkConnection, loadLocale } =
const { initSnap, initWalletData, checkConnection, loadLocale } =
useStarkNetSnap();
const { connected, forceReconnect, provider } = useAppSelector(
(state) => state.wallet,
Expand All @@ -38,11 +37,10 @@ function App() {
} = useAppSelector((state) => state.modals);
const { loader } = useAppSelector((state) => state.UI);
const networks = useAppSelector((state) => state.networks);
const { accounts } = useAppSelector((state) => state.wallet);
const { currentAccount } = useAppSelector((state) => state.wallet);
const { hasMetamask } = useHasMetamask();

const address =
accounts?.length > 0 ? (accounts[0] as unknown as string) : DUMMY_ADDRESS;
const chainId = networks.items?.[networks.activeNetwork]?.chainId;
const address = currentAccount.address;

useEffect(() => {
if (!provider) {
Expand All @@ -58,12 +56,11 @@ function App() {
}, [connected, forceReconnect, hasMetamask, provider]);

useEffect(() => {
if (provider && networks.items.length > 0) {
const chainId = networks.items[networks.activeNetwork].chainId;
getWalletData(chainId);
if (provider && networks.items.length > 0 && chainId) {
initWalletData({ chainId });
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [networks.activeNetwork, provider]);
}, [networks.activeNetwork, provider, chainId]);

useEffect(() => {
if (connected) {
Expand Down Expand Up @@ -106,7 +103,7 @@ function App() {
>
<DeployModal address={address} />
</PopIn>
<Home address={address} />
<Home />
<PopIn isOpen={loading}>
{loading && (
<LoadingBackdrop>{loader.loadingMessage}</LoadingBackdrop>
Expand Down
9 changes: 9 additions & 0 deletions packages/wallet-ui/src/assets/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@
"aboutThisSnap": {
"message": "About this snap"
},
"account": {
"message": "Compte"
},
"accounts": {
"message": "Comptes"
},
"accountDeployedSuccessfully": {
"message": "Account deployed successfully."
},
Expand Down Expand Up @@ -297,6 +303,9 @@
},
"whatIsASnap": {
"message": "What is a snap?"
},
"youCannotHideLastAccount": {
"message": "You cannot hide the last remaining account."
}
}
}
9 changes: 9 additions & 0 deletions packages/wallet-ui/src/assets/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@
"aboutThisSnap": {
"message": "À propos de ce snap"
},
"account": {
"message": "Compte"
},
"accounts": {
"message": "Comptes"
},
"accountDeployedSuccessfully": {
"message": "Compte déployé avec succès."
},
Expand Down Expand Up @@ -291,6 +297,9 @@
},
"whatIsASnap": {
"message": "Qu'est-ce qu'un snap ?"
},
"youCannotHideLastAccount": {
"message": "Vous ne pouvez pas masquer le dernier compte restant."
}
}
}
9 changes: 4 additions & 5 deletions packages/wallet-ui/src/components/pages/Home/Home.view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,20 @@ import { SideBar } from 'components/ui/organism/SideBar';
import { RightPart, Wrapper, NoTransactions } from './Home.style';
import { useAppSelector } from 'hooks/redux';
import { useMultiLanguage } from 'services';
interface Props {
address: string;
}

export const HomeView = ({ address }: Props) => {
export const HomeView = () => {
const { erc20TokenBalanceSelected, transactions } = useAppSelector(
(state) => state.wallet,
);
const loader = useAppSelector((state) => state.UI.loader);
const currentAccount = useAppSelector((state) => state.wallet.currentAccount);
const address = currentAccount.address;
const { upgradeModalVisible } = useAppSelector((state) => state.modals);
const { translate } = useMultiLanguage();

return (
<Wrapper>
<SideBar address={address} />
<SideBar />
<RightPart>
{!upgradeModalVisible &&
Object.keys(erc20TokenBalanceSelected).length > 0 && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export const PopperTooltipView = ({
});

return (
<>
<div style={{ zIndex: 1 }}>
<Wrapper
ref={setTriggerRef}
onClick={handleOnClick}
Expand All @@ -91,6 +91,6 @@ export const PopperTooltipView = ({
<ToolTipContent style={contentStyle}>{content}</ToolTipContent>
</PopperContainer>
)}
</>
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,23 @@ export const TransactionsListView = ({ transactions }: Props) => {
const networks = useAppSelector((state) => state.networks);
const wallet = useAppSelector((state) => state.wallet);
const timeoutHandle = useRef(setTimeout(() => {}));
const chainId = networks.items[networks.activeNetwork]?.chainId;
const {
currentAccount,
erc20TokenBalanceSelected,
transactions: walletTransactions,
} = wallet;

useEffect(() => {
const chain = networks.items[networks.activeNetwork]?.chainId;
const address = wallet.accounts?.[0] as unknown as string;
if (chain && address) {
if (chainId && erc20TokenBalanceSelected.address) {
clearTimeout(timeoutHandle.current); // cancel the timeout that was in-flight
timeoutHandle.current = setTimeout(
() =>
getTransactions(
address,
wallet.erc20TokenBalanceSelected.address,
currentAccount.address,
erc20TokenBalanceSelected.address,
10,
chain,
chainId,
false,
true,
),
Expand All @@ -37,30 +41,29 @@ export const TransactionsListView = ({ transactions }: Props) => {
return () => clearTimeout(timeoutHandle.current);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [wallet.transactions]);
}, [walletTransactions]);

useEffect(
() => {
const chain = networks.items[networks.activeNetwork]?.chainId;
const address = wallet.accounts?.[0] as unknown as string;
if (chain && address) {
if (chainId && erc20TokenBalanceSelected.address) {
clearTimeout(timeoutHandle.current); // cancel the timeout that was in-flight
getTransactions(
address,
wallet.erc20TokenBalanceSelected.address,
currentAccount.address,
erc20TokenBalanceSelected.address,
10,
chain,
chainId,
);
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[
// eslint-disable-next-line react-hooks/exhaustive-deps
wallet.erc20TokenBalanceSelected.address,
erc20TokenBalanceSelected.address,
// eslint-disable-next-line react-hooks/exhaustive-deps
wallet.erc20TokenBalanceSelected.chainId,
erc20TokenBalanceSelected.chainId,
// eslint-disable-next-line react-hooks/exhaustive-deps
wallet.accounts?.[0],
currentAccount,
chainId,
],
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,7 @@ export default {
component: AccountDetailsModalView,
} as Meta;

const address =
'0x683ec5da50476f84a5d47e822cd4dd35ae3a63c6c1f0725bf28526290d1ee13';

export const ContentOnly = () => <AccountDetailsModalView address={address} />;
export const ContentOnly = () => <AccountDetailsModalView />;

export const WithModal = () => {
let [isOpen, setIsOpen] = useState(false);
Expand All @@ -24,7 +21,7 @@ export const WithModal = () => {
showClose={false}
style={{ backgroundColor: 'transparent' }}
>
<AccountDetailsModalView address={address} />
<AccountDetailsModalView />
</PopIn>
</>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,25 @@ import { openExplorerTab } from 'utils/utils';
import { useAppSelector } from 'hooks/redux';
import { useMultiLanguage, useStarkNetSnap } from 'services';

interface Props {
address: string;
}

export const AccountDetailsModalView = ({ address }: Props) => {
export const AccountDetailsModalView = () => {
const networks = useAppSelector((state) => state.networks);
const currentAccount = useAppSelector((state) => state.wallet.currentAccount);
const { getPrivateKeyFromAddress } = useStarkNetSnap();
const { translate } = useMultiLanguage();

const chainId = networks?.items[networks.activeNetwork]?.chainId;
const address = currentAccount.address;
const addressIndex = currentAccount?.addressIndex ?? 0;
return (
<div>
<AccountImageDiv>
<AccountImageStyled size={64} address={address} />
</AccountImageDiv>
<Wrapper>
<TitleDiv>
<Title>{translate('myAccount')}</Title>
<Title>
{translate('account')} {addressIndex + 1}
</Title>
{/* <ModifyIcon /> */}
</TitleDiv>
<AddressQrCode value={address} />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Meta } from '@storybook/react';
import { AccountSwitchModalView } from './AccountSwitchModal.view';

export default {
title: 'Molecule/AccountAddress',
component: AccountSwitchModalView,
} as Meta;

const wrapperStyle = {
backgroundColor: 'white',
height: '300px',
alignItems: 'center',
display: 'flex',
justifyContent: 'center',
};

export const Default = () => (
<div style={wrapperStyle}>
<AccountSwitchModalView></AccountSwitchModalView>
</div>
);

export const Full = () => (
<div style={wrapperStyle}>
<AccountSwitchModalView full></AccountSwitchModalView>
</div>
);

export const DarkerBackground = () => (
<div style={{ ...wrapperStyle, backgroundColor: 'grey' }}>
<AccountSwitchModalView full></AccountSwitchModalView>
</div>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { AccountImage } from 'components/ui/atom/AccountImage';
import { Button } from 'components/ui/atom/Button';
import styled from 'styled-components';

export const MenuSection = styled.div`
padding: 0px 10px;
display: flex;
flex-direction: column;
height: 202;
overflow-y: auto;
`;

export const Wrapper = styled(Button).attrs((props) => ({
fontSize: props.theme.typography.c1.fontSize,
upperCaseOnly: false,
textStyle: {
fontWeight: props.theme.typography.p1.fontWeight,
fontFamily: props.theme.typography.p1.fontFamily,
},
iconStyle: {
fontSize: props.theme.typography.i1.fontSize,
color: props.theme.palette.grey.grey1,
},
}))`
padding: 4px 5px;
height: 25px;
color: ${(props) => props.theme.palette.grey.black};
border-radius: 24px;
border: 1px solid ${(props) => props.theme.palette.grey.grey3};
:hover {
background-color: ${(props) => props.theme.palette.grey.grey4};
border: none;
}
`;

export const Normal = styled.div`
font-size: ${(props) => props.theme.typography.p1.fontSize};
`;

export const AccountSwitchMenuItem = styled.div`
cursor: pointer;
display: flex;
align-items: center;
padding: 14px;
justify-content: space-between;
`;

export const AccountImageStyled = styled(AccountImage)`
margin-left: ${(props) => props.theme.spacing.small};
cursor: pointer;
`;

export const Container = styled.div`
display: flex;
alignitems: center;
`;

export const MenuItemText = styled.span`
${(props) => props.theme.typography.p2};
`;
Loading

0 comments on commit a9fcd27

Please sign in to comment.