Skip to content

Commit cadc477

Browse files
feat: account and network selector (#37434)
<!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until the template has been completely filled out, and PR status checks have passed at least once. --> ## **Description** **Add account selector** - Instead of asking user for the _impacted wallet address_, we added an account selector which shows all accounts which the user has in the app. Selecting accounts adds the address to claims payload **Add network selector** - Instead of asking user for the _chainID_, we added an network selector which shows all networks which is covered by shield. Selecting network adds the chainID to claims payload Minor update on the Form UI - add more information and grouping of fields https://www.figma.com/design/HTAO1SrmixV4ppv7qIvLoa/Metamask-Transaction-Shield?node-id=13912-101739&t=9QXWgot1B69O2eJy-4 https://consensyssoftware.atlassian.net/browse/SUBS-657?atlOrigin=eyJpIjoiYWQ3YTk2MjMwMjY5NDRlZDgyMzVkNTZmY2ZlMWMyNDUiLCJwIjoiaiJ9 <!-- Write a short description of the changes included in this pull request, also include relevant motivation and context. Have in mind the following questions: 1. What is the reason for the change? 2. What is the improvement/solution? --> [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/37434?quickstart=1) ## **Changelog** <!-- If this PR is not End-User-Facing and should not show up in the CHANGELOG, you can choose to either: 1. Write `CHANGELOG entry: null` 2. Label with `no-changelog` If this PR is End-User-Facing, please write a short User-Facing description in the past tense like: `CHANGELOG entry: Added a new tab for users to see their NFTs` `CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker` (This helps the Release Engineer do their job more quickly and accurately) --> CHANGELOG entry: Added network and account selector to claims form ## **Related issues** Fixes: ## **Manual testing steps** 1. Login/Create account with shield subscription 2. Go to Menu > Settings > Transaction shield 3. Click on **Submit a claim** 4. Explore the claims form field - more details on the video attached ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> ### **After** **Form UI Update** <img width="650" height="820" alt="Screenshot 2025-11-01 at 3 41 19 AM" src="https://github.com/user-attachments/assets/e2a4bfa5-844e-4f08-9b7d-66454a56bacd" /> **Demo account selector** <video src="https://github.com/user-attachments/assets/49fd3619-d17f-4ab2-bd3c-120983d5c85c" /> **Demo network selector** <video src="https://github.com/user-attachments/assets/70c228ea-ffe5-4a71-adb2-aa37dce84ecb" /> **Demo claims form** <video src="https://github.com/user-attachments/assets/ab2174b3-cef3-4961-8539-912f29efbe1a" /> **Payload when using new account and network selector** <img width="623" height="302" alt="Screenshot 2025-11-01 at 3 48 34 AM" src="https://github.com/user-attachments/assets/68c24953-8ceb-47fa-b638-87a0db4cb6d1" /> <!-- [screenshots/recordings] --> ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Replaces manual chain ID and impacted address inputs with account and network pickers in the Transaction Shield claim form, updating validation, UI sections, tests, styles, and i18n. > > - **Transaction Shield UI**: > - **Selectors**: Add `account-selector` and `network-selector` components and integrate into `submit-claim-form`. > - **Form changes**: Replace manual `chainId` and impacted wallet address inputs with pickers; add "Personal details" and "Incident details" sections; keep file upload; submit handler updated to use selected values. > - **Validation**: Refactor to use i18n message keys (`ERROR_MESSAGE_MAP`, `FIELD_ERROR_MESSAGE_KEY_MAP`); adjust equality check for reimbursement vs impacted address; translate help texts. > - **Tests**: > - Mock new selectors; update tests to validate email/reimbursement address errors and disabled submit; remove impacted address format test. > - **Styles**: > - Add modal hover/selected styles in `transaction-shield-tab/index.scss`. > - **i18n**: > - Add `shieldClaimIncidentDetails*`, `shieldClaimNetwork`, `shieldClaimPersonalDetails*`, `shieldClaimSelectAccount`, `shieldClaimSelectNetwork` and related strings; remove `shieldClaimChainId`. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 715f763. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 54b58db commit cadc477

File tree

10 files changed

+645
-173
lines changed

10 files changed

+645
-173
lines changed

app/_locales/en/messages.json

Lines changed: 21 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/_locales/en_GB/messages.json

Lines changed: 21 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
import React, { useMemo, useState } from 'react';
2+
import {
3+
AvatarAccountSize,
4+
Box,
5+
BoxBorderColor,
6+
FontWeight,
7+
Icon,
8+
IconColor,
9+
IconName,
10+
IconSize,
11+
Text,
12+
TextColor,
13+
TextVariant,
14+
} from '@metamask/design-system-react';
15+
import { useSelector } from 'react-redux';
16+
import classnames from 'classnames';
17+
import {
18+
Modal,
19+
ModalBody,
20+
ModalContent,
21+
ModalContentSize,
22+
ModalHeader,
23+
ModalOverlay,
24+
} from '../../../../components/component-library';
25+
import { PreferredAvatar } from '../../../../components/app/preferred-avatar';
26+
import { AccountSelectorWallet } from '../types';
27+
import { getWalletsWithAccounts } from '../../../../selectors/multichain-accounts/account-tree';
28+
import { shortenAddress } from '../../../../helpers/utils/util';
29+
30+
const AccountSelector = ({
31+
label,
32+
modalTitle,
33+
onAccountSelect,
34+
impactedWalletAddress,
35+
}: {
36+
label: string;
37+
modalTitle: string;
38+
onAccountSelect: (address: string) => void;
39+
impactedWalletAddress: string;
40+
}) => {
41+
const [showAccountListMenu, setShowAccountListMenu] = useState(false);
42+
43+
// Account list
44+
const wallets = useSelector(getWalletsWithAccounts);
45+
46+
// Group recipients by wallet name
47+
const accountsGroupedByWallet: Record<string, AccountSelectorWallet> =
48+
useMemo(() => {
49+
return Object.values(wallets).reduce(
50+
(acc, wallet) => {
51+
const walletName = wallet.metadata.name;
52+
if (!acc[walletName]) {
53+
acc[walletName] = {
54+
id: wallet.id,
55+
name: wallet.metadata.name,
56+
accounts: [],
57+
};
58+
}
59+
Object.values(wallet.groups).forEach((group) => {
60+
// get evm account from group
61+
const evmAccount = group.accounts.find((account) =>
62+
account.type.startsWith('eip155:'),
63+
);
64+
if (evmAccount) {
65+
acc[walletName].accounts.push({
66+
id: group.id,
67+
name: group.metadata.name,
68+
address: evmAccount.address,
69+
type: evmAccount.type,
70+
});
71+
}
72+
});
73+
return acc;
74+
},
75+
{} as Record<string, AccountSelectorWallet>,
76+
);
77+
}, [wallets]);
78+
79+
const selectedAccountInfo = useMemo(() => {
80+
const selectedWallet = Object.values(accountsGroupedByWallet).find(
81+
(wallet) =>
82+
wallet.accounts.some(
83+
(account) =>
84+
account.address.toLowerCase() ===
85+
impactedWalletAddress.toLowerCase(),
86+
),
87+
);
88+
89+
if (selectedWallet) {
90+
return (
91+
selectedWallet.accounts.find(
92+
(account) =>
93+
account.address.toLowerCase() ===
94+
impactedWalletAddress.toLowerCase(),
95+
) ?? null
96+
);
97+
}
98+
return null;
99+
}, [accountsGroupedByWallet, impactedWalletAddress]);
100+
101+
return (
102+
<Box>
103+
<Text
104+
variant={TextVariant.BodyMd}
105+
fontWeight={FontWeight.Medium}
106+
className="mb-2"
107+
>
108+
{label}
109+
</Text>
110+
<Box
111+
asChild
112+
borderColor={BoxBorderColor.BorderDefault}
113+
className="w-full flex items-center gap-2 px-4 h-12 border rounded-lg"
114+
onClick={() => setShowAccountListMenu(true)}
115+
aria-label={modalTitle}
116+
>
117+
<button>
118+
{selectedAccountInfo ? (
119+
<>
120+
<PreferredAvatar
121+
address={selectedAccountInfo?.address ?? ''}
122+
size={AvatarAccountSize.Sm}
123+
/>
124+
<Text>{selectedAccountInfo?.name}</Text>
125+
</>
126+
) : (
127+
<Text color={TextColor.TextAlternative}>{modalTitle}</Text>
128+
)}
129+
130+
<Icon
131+
className="ml-auto"
132+
size={IconSize.Sm}
133+
color={IconColor.IconDefault}
134+
name={IconName.ArrowDown}
135+
/>
136+
</button>
137+
</Box>
138+
139+
<Modal
140+
isOpen={showAccountListMenu}
141+
isClosedOnEscapeKey={true}
142+
isClosedOnOutsideClick={true}
143+
onClose={() => setShowAccountListMenu(false)}
144+
className="account-selector-modal"
145+
data-testid="account-selector-modal"
146+
>
147+
<ModalOverlay />
148+
<ModalContent size={ModalContentSize.Sm}>
149+
<ModalHeader onClose={() => setShowAccountListMenu(false)}>
150+
{modalTitle}
151+
</ModalHeader>
152+
<ModalBody paddingRight={0} paddingLeft={0}>
153+
{Object.entries(accountsGroupedByWallet).map(
154+
([walletName, wallet]) => (
155+
<Box key={walletName}>
156+
<Text
157+
variant={TextVariant.BodyMd}
158+
fontWeight={FontWeight.Medium}
159+
color={TextColor.TextAlternative}
160+
className="px-4 py-2"
161+
>
162+
{walletName}
163+
</Text>
164+
165+
{wallet.accounts.map((account) => (
166+
<Box
167+
asChild
168+
key={account.id}
169+
className={classnames(
170+
'account-selector-modal__account w-full flex items-center gap-4 px-4 py-3',
171+
{
172+
'account-selector-modal__account--selected':
173+
account.address.toLowerCase() ===
174+
selectedAccountInfo?.address.toLowerCase(),
175+
},
176+
)}
177+
onClick={() => {
178+
onAccountSelect(account.address);
179+
setShowAccountListMenu(false);
180+
}}
181+
>
182+
<button>
183+
<PreferredAvatar
184+
address={account.address ?? ''}
185+
size={AvatarAccountSize.Lg}
186+
/>
187+
<Box className="text-left">
188+
<Text
189+
variant={TextVariant.BodyMd}
190+
fontWeight={FontWeight.Medium}
191+
>
192+
{account.name}
193+
</Text>
194+
<Text
195+
variant={TextVariant.BodySm}
196+
fontWeight={FontWeight.Medium}
197+
color={TextColor.TextAlternative}
198+
>
199+
{shortenAddress(account.address)}
200+
</Text>
201+
</Box>
202+
</button>
203+
</Box>
204+
))}
205+
</Box>
206+
),
207+
)}
208+
</ModalBody>
209+
</ModalContent>
210+
</Modal>
211+
</Box>
212+
);
213+
};
214+
215+
export default AccountSelector;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default } from './account-selector';

ui/pages/settings/transaction-shield-tab/index.scss

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,15 @@
5454
}
5555
}
5656
}
57+
58+
.account-selector-modal {
59+
.account-selector-modal__account {
60+
&:hover {
61+
background-color: var(--color-background-hover);
62+
}
63+
}
64+
65+
&__account--selected {
66+
background-color: var(--color-primary-muted);
67+
}
68+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default } from './network-selector';

0 commit comments

Comments
 (0)