diff --git a/.github/scripts/label-prs.ts b/.github/scripts/label-prs.ts new file mode 100644 index 000000000000..ef4e790dba0d --- /dev/null +++ b/.github/scripts/label-prs.ts @@ -0,0 +1,173 @@ +import * as core from '@actions/core'; +import { context, getOctokit } from '@actions/github'; +import { GitHub } from '@actions/github/lib/utils'; + +main().catch((error: Error): void => { + console.error(error); + process.exit(1); +}); + +async function main(): Promise { + const token = process.env.GITHUB_TOKEN; + + if (!token) { + core.setFailed('GITHUB_TOKEN not found'); + process.exit(1); + } + + const octokit = getOctokit(token); + + const headRef = context.payload.pull_request?.head.ref || ''; + + let issueNumber = await getIssueNumberFromPullRequestBody(); + if (issueNumber === "") { + bailIfIsBranchNameInvalid(headRef); + bailIfIsNotFeatureBranch(headRef); + issueNumber = getIssueNumberFromBranchName(headRef); + } + + await updateLabels(octokit, issueNumber); +} + +async function getIssueNumberFromPullRequestBody(): Promise { + console.log("Checking if the PR's body references an issue..."); + + let ISSUE_LINK_IN_PR_DESCRIPTION_REGEX = + /(close|closes|closed|fix|fixes|fixed|resolve|resolves|resolved)\s#\d+/gi; + + const prBody = await getPullRequestBody(); + + let matches = prBody.match(ISSUE_LINK_IN_PR_DESCRIPTION_REGEX); + if (!matches || matches?.length === 0) { + console.log( + 'No direct link can be drawn between the PR and an issue from the PR body because no issue number was referenced.', + ); + return ""; + } + + if (matches?.length > 1) { + console.log( + 'No direct link can be drawn between the PR and an issue from the PR body because more than one issue number was referenced.', + ); + return ""; + } + + const ISSUE_NUMBER_REGEX = /\d+/; + const issueNumber = matches[0].match(ISSUE_NUMBER_REGEX)?.[0] || ''; + + console.log(`Found issue number ${issueNumber} in PR body.`); + + return issueNumber; +} + +async function getPullRequestBody(): Promise { + if (context.eventName !== 'pull_request') { + console.log('This action should only run on pull_request events.'); + process.exit(1); + } + + const prBody = context.payload.pull_request?.body || ''; + return prBody; +} + +function bailIfIsBranchNameInvalid(branchName: string): void { + const BRANCH_REGEX = + /^(main|develop|(ci|chore|docs|feat|feature|fix|perf|refactor|revert|style)\/\d*(?:[-](?![-])\w*)*|Version-v\d+\.\d+\.\d+)$/; + const isValidBranchName = new RegExp(BRANCH_REGEX).test(branchName); + + if (!isValidBranchName) { + console.log('This branch name does not follow the convention.'); + console.log( + 'Here are some example branch names that are accepted: "fix/123-description", "feat/123-longer-description", "feature/123", "main", "develop", "Version-v10.24.2".', + ); + console.log( + 'No issue could be linked to this PR, so no labels were copied', + ); + + process.exit(0); + } +} + +function bailIfIsNotFeatureBranch(branchName: string): void { + if ( + branchName === 'main' || + branchName === 'develop' || + branchName.startsWith('Version-v') + ) { + console.log(`${branchName} is not a feature branch.`); + console.log( + 'No issue could be linked to this PR, so no labels were copied', + ); + process.exit(0); + } +} + +async function updateLabels(octokit: InstanceType, issueNumber: string): Promise { + interface ILabel { + name: string; + }; + + const owner = context.repo.owner; + const repo = context.repo.repo; + + const issue = await octokit.rest.issues.get({ + owner: owner, + repo: repo, + issue_number: Number(issueNumber), + }); + + const getNameFromLabel = (label: ILabel): string => label.name + + const issueLabels = issue.data.labels.map(label => getNameFromLabel(label as ILabel)); + + const prNumber = context.payload.number; + + const pr = await octokit.rest.issues.get({ + owner: owner, + repo: repo, + issue_number: prNumber, + }); + + const startingPRLabels = pr.data.labels.map(label => getNameFromLabel(label as ILabel)); + + const dedupedFinalPRLabels = [ + ...new Set([...startingPRLabels, ...issueLabels]), + ]; + + const hasIssueAdditionalLabels = !sortedArrayEqual( + startingPRLabels, + dedupedFinalPRLabels, + ); + if (hasIssueAdditionalLabels) { + await octokit.rest.issues.update({ + owner, + repo, + issue_number: prNumber, + labels: dedupedFinalPRLabels, + }); + } +} + +function getIssueNumberFromBranchName(branchName: string): string { + console.log('Checking if the branch name references an issue...'); + + let issueNumber: string; + if (branchName.split('/').length > 1) { + issueNumber = branchName.split('/')[1].split('-')[0]; + } else { + issueNumber = branchName.split('-')[0]; + } + + console.log(`Found issue number ${issueNumber} in branch name.`); + + return issueNumber; +} + +function sortedArrayEqual(array1: string[], array2: string[]): boolean { + const lengthsAreEqual = array1.length === array2.length; + const everyElementMatchesByIndex = array1.every( + (value: string, index: number): boolean => value === array2[index], + ); + + return lengthsAreEqual && everyElementMatchesByIndex; +} diff --git a/.github/workflows/label-prs.yml b/.github/workflows/label-prs.yml new file mode 100644 index 000000000000..f915a881abc9 --- /dev/null +++ b/.github/workflows/label-prs.yml @@ -0,0 +1,32 @@ +name: Label PR + +on: + pull_request: + types: [assigned, opened, edited, synchronize, reopened] + +jobs: + label-pr: + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Use Node.js + uses: actions/setup-node@v3 + with: + node-version: '16' + + - name: Install Yarn + run: npm install -g yarn + + - name: Install dependencies + run: yarn + + - name: Run PR labelling script + run: npm run label-prs + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.husky/pre-push b/.husky/pre-push new file mode 100755 index 000000000000..bbf5399ef918 --- /dev/null +++ b/.husky/pre-push @@ -0,0 +1,4 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +yarn validate-branch-name \ No newline at end of file diff --git a/.validate-branch-namerc.js b/.validate-branch-namerc.js new file mode 100644 index 000000000000..8685701d15df --- /dev/null +++ b/.validate-branch-namerc.js @@ -0,0 +1,11 @@ +const BRANCH_REGEX = + /^(main|develop|(ci|chore|docs|feat|feature|fix|perf|refactor|revert|style)\/\d*(?:[-](?![-])\w*)*|Version-v\d+\.\d+\.\d+)$/; + +const ERROR_MSG = + 'This branch name does not follow our conventions.' + + '\n' + + 'Rename it with "git branch -m "' + + '\n' + + 'Here are some example branch names that are accepted: "fix/123-description", "feat/123-longer-description", "feature/123", "main", "develop", "Version-v10.24.2".'; + +module.exports = { pattern: BRANCH_REGEX, errorMsg: ERROR_MSG }; diff --git a/app/_locales/de/messages.json b/app/_locales/de/messages.json index b8b8c19df271..fceaa5ed5fb3 100644 --- a/app/_locales/de/messages.json +++ b/app/_locales/de/messages.json @@ -1570,9 +1570,6 @@ "history": { "message": "Verlauf" }, - "holdToReveal": { - "message": "Halten, um GWP anzuzeigen" - }, "holdToRevealContent1": { "message": "Ihre geheime Wiederherstellungsphrase bietet $1", "description": "$1 is a bolded text with the message from 'holdToRevealContent2'" @@ -1593,9 +1590,6 @@ "message": "Betrüger aber schon.", "description": "The text link in 'holdToRevealContent3'" }, - "holdToRevealTitle": { - "message": "Bewahren Sie Ihre GWP sicher auf" - }, "ignoreAll": { "message": "Alle ignorieren" }, diff --git a/app/_locales/el/messages.json b/app/_locales/el/messages.json index 06ebced86cd0..d177839254a8 100644 --- a/app/_locales/el/messages.json +++ b/app/_locales/el/messages.json @@ -1570,9 +1570,6 @@ "history": { "message": "Ιστορικό" }, - "holdToReveal": { - "message": "Κρατήστε πατημένο για αποκάλυψη της ΜΦΑ" - }, "holdToRevealContent1": { "message": "Η Μυστική σας Φράση Ανάκτησης παρέχει $1", "description": "$1 is a bolded text with the message from 'holdToRevealContent2'" @@ -1593,9 +1590,6 @@ "message": "αλλά οι απατεώνες μπορεί να το κάνουν.", "description": "The text link in 'holdToRevealContent3'" }, - "holdToRevealTitle": { - "message": "Κρατήστε τη ΜΦΑ σας ασφαλή" - }, "ignoreAll": { "message": "Αγνόηση όλων" }, diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 088fe8444aec..7572190c18bc 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -1754,9 +1754,6 @@ "history": { "message": "History" }, - "holdToReveal": { - "message": "Hold to reveal SRP" - }, "holdToRevealContent1": { "message": "Your Secret Recovery Phrase provides $1", "description": "$1 is a bolded text with the message from 'holdToRevealContent2'" @@ -1777,9 +1774,28 @@ "message": "but phishers might.", "description": "The text link in 'holdToRevealContent3'" }, - "holdToRevealTitle": { + "holdToRevealContentPrivateKey1": { + "message": "Your Private Key provides $1", + "description": "$1 is a bolded text with the message from 'holdToRevealContentPrivateKey2'" + }, + "holdToRevealContentPrivateKey2": { + "message": "full access to your wallet and funds.", + "description": "Is the bolded text in 'holdToRevealContentPrivateKey2'" + }, + "holdToRevealLockedLabel": { "message": "hold to reveal circle locked" }, + "holdToRevealPrivateKey": { + "message": "Hold to reveal Private Key" + }, + "holdToRevealPrivateKeyTitle": { + "message": "Keep your private key safe" + }, + "holdToRevealSRP": { + "message": "Hold to reveal SRP" + }, + "holdToRevealSRPTitle": { "message": "Keep your SRP safe" }, + "holdToRevealUnlockedLabel": { "message": "hold to reveal circle unlocked" }, "id": { "message": "Id" }, diff --git a/app/_locales/es/messages.json b/app/_locales/es/messages.json index 67a3b012ad9b..0661e837ace3 100644 --- a/app/_locales/es/messages.json +++ b/app/_locales/es/messages.json @@ -1570,9 +1570,6 @@ "history": { "message": "Historial" }, - "holdToReveal": { - "message": "Mantenga presionado para mostrar la SRP" - }, "holdToRevealContent1": { "message": "Su frase secreta de recuperación proporciona $1", "description": "$1 is a bolded text with the message from 'holdToRevealContent2'" @@ -1593,9 +1590,6 @@ "message": "pero los defraudadores sí.", "description": "The text link in 'holdToRevealContent3'" }, - "holdToRevealTitle": { - "message": "Mantenga segura su SRP" - }, "ignoreAll": { "message": "Ignorar todo" }, diff --git a/app/_locales/fr/messages.json b/app/_locales/fr/messages.json index b543b2114f00..920ff11a7d39 100644 --- a/app/_locales/fr/messages.json +++ b/app/_locales/fr/messages.json @@ -1570,9 +1570,6 @@ "history": { "message": "Historique" }, - "holdToReveal": { - "message": "Appuyez longuement pour révéler la PSR" - }, "holdToRevealContent1": { "message": "Votre phrase secrète de récupération donne $1", "description": "$1 is a bolded text with the message from 'holdToRevealContent2'" @@ -1593,9 +1590,6 @@ "message": "mais les hameçonneurs pourraient le faire.", "description": "The text link in 'holdToRevealContent3'" }, - "holdToRevealTitle": { - "message": "Conservez votre PSR en lieu sûr" - }, "ignoreAll": { "message": "Ignorer tout" }, diff --git a/app/_locales/hi/messages.json b/app/_locales/hi/messages.json index e9781a768b1c..7861fd19e0ee 100644 --- a/app/_locales/hi/messages.json +++ b/app/_locales/hi/messages.json @@ -1570,9 +1570,6 @@ "history": { "message": "इतिहास" }, - "holdToReveal": { - "message": "SRP देखने के लिए होल्ड करें" - }, "holdToRevealContent1": { "message": "आपका सीक्रेट रिकवरी फ्रेज $1 प्रदान करता है", "description": "$1 is a bolded text with the message from 'holdToRevealContent2'" @@ -1593,9 +1590,6 @@ "message": "लेकिन फिशर कर सकते हैं।", "description": "The text link in 'holdToRevealContent3'" }, - "holdToRevealTitle": { - "message": "अपने SRP को सुरक्षित रखें" - }, "ignoreAll": { "message": "सभी को अनदेखा करें" }, diff --git a/app/_locales/id/messages.json b/app/_locales/id/messages.json index 97df69b6ff68..dd49e11ed34c 100644 --- a/app/_locales/id/messages.json +++ b/app/_locales/id/messages.json @@ -1570,9 +1570,6 @@ "history": { "message": "Riwayat" }, - "holdToReveal": { - "message": "Tahan untuk mengungkap FPR" - }, "holdToRevealContent1": { "message": "Frasa Pemulihan Rahasia memberikan $1", "description": "$1 is a bolded text with the message from 'holdToRevealContent2'" @@ -1593,9 +1590,6 @@ "message": "tetapi penipu akan mencoba memintanya.", "description": "The text link in 'holdToRevealContent3'" }, - "holdToRevealTitle": { - "message": "Jaga keamanan FPR Anda" - }, "ignoreAll": { "message": "Abaikan semua" }, diff --git a/app/_locales/ja/messages.json b/app/_locales/ja/messages.json index a3c0dd28e11b..6121b09dde46 100644 --- a/app/_locales/ja/messages.json +++ b/app/_locales/ja/messages.json @@ -1570,9 +1570,6 @@ "history": { "message": "履歴" }, - "holdToReveal": { - "message": "長押しして SRP を表示" - }, "holdToRevealContent1": { "message": "秘密のリカバリーフレーズは$1を提供します。", "description": "$1 is a bolded text with the message from 'holdToRevealContent2'" @@ -1593,9 +1590,6 @@ "message": "もし尋ねられた場合はフィッシング詐欺の可能性があります。", "description": "The text link in 'holdToRevealContent3'" }, - "holdToRevealTitle": { - "message": "SRP は安全に保管してください" - }, "ignoreAll": { "message": "すべて無視" }, diff --git a/app/_locales/ko/messages.json b/app/_locales/ko/messages.json index c4de64fc44df..61a439b52abf 100644 --- a/app/_locales/ko/messages.json +++ b/app/_locales/ko/messages.json @@ -1570,9 +1570,6 @@ "history": { "message": "기록" }, - "holdToReveal": { - "message": "눌러서 SRP 확인" - }, "holdToRevealContent1": { "message": "비밀 복구 구문이 있으면 $1 기능을 사용할 수 있습니다", "description": "$1 is a bolded text with the message from 'holdToRevealContent2'" @@ -1593,9 +1590,6 @@ "message": "오히려 피싱 사기꾼들이 요구할 수 있으니 주의가 필요합니다.", "description": "The text link in 'holdToRevealContent3'" }, - "holdToRevealTitle": { - "message": "SRP를 안전하게 보관하세요" - }, "ignoreAll": { "message": "모두 무시" }, diff --git a/app/_locales/pt/messages.json b/app/_locales/pt/messages.json index d52ed995db38..93adcfea89f4 100644 --- a/app/_locales/pt/messages.json +++ b/app/_locales/pt/messages.json @@ -1570,9 +1570,6 @@ "history": { "message": "Histórico" }, - "holdToReveal": { - "message": "Segure para revelar a FRS" - }, "holdToRevealContent1": { "message": "Sua Frase de Recuperação Secreta concede $1", "description": "$1 is a bolded text with the message from 'holdToRevealContent2'" @@ -1593,9 +1590,6 @@ "message": "mas os phishers talvez solicitem.", "description": "The text link in 'holdToRevealContent3'" }, - "holdToRevealTitle": { - "message": "Mantenha sua FRS em segurança" - }, "ignoreAll": { "message": "Ignorar tudo" }, diff --git a/app/_locales/ru/messages.json b/app/_locales/ru/messages.json index 1b9a1a6e83c6..03d546df99e0 100644 --- a/app/_locales/ru/messages.json +++ b/app/_locales/ru/messages.json @@ -1570,9 +1570,6 @@ "history": { "message": "История" }, - "holdToReveal": { - "message": "Удерживайте для отображения СВФ" - }, "holdToRevealContent1": { "message": "Ваша секретная фраза для восстановления дает $1", "description": "$1 is a bolded text with the message from 'holdToRevealContent2'" @@ -1593,9 +1590,6 @@ "message": "но злоумышленники-фишеры могут.", "description": "The text link in 'holdToRevealContent3'" }, - "holdToRevealTitle": { - "message": "Обеспечьте безопасность своей СВФ" - }, "ignoreAll": { "message": "Игнорировать все" }, diff --git a/app/_locales/tl/messages.json b/app/_locales/tl/messages.json index 479683256066..bb47c161fed5 100644 --- a/app/_locales/tl/messages.json +++ b/app/_locales/tl/messages.json @@ -1570,9 +1570,6 @@ "history": { "message": "History" }, - "holdToReveal": { - "message": "I-hold para ipakita ang SRP" - }, "holdToRevealContent1": { "message": "Ang iyong Secret Recovery Phrase ay nagbibigay ng $1", "description": "$1 is a bolded text with the message from 'holdToRevealContent2'" @@ -1593,9 +1590,6 @@ "message": "ngunit maaring hingin ng mga phisher.", "description": "The text link in 'holdToRevealContent3'" }, - "holdToRevealTitle": { - "message": "Ingatan ang iyong SRP" - }, "ignoreAll": { "message": "Huwag pansinin ang lahat" }, diff --git a/app/_locales/tr/messages.json b/app/_locales/tr/messages.json index 1479da53fe0d..ebabab1c7855 100644 --- a/app/_locales/tr/messages.json +++ b/app/_locales/tr/messages.json @@ -1570,9 +1570,6 @@ "history": { "message": "Geçmiş" }, - "holdToReveal": { - "message": "GKİ'yi göstermek için basılı tut" - }, "holdToRevealContent1": { "message": "Gizli Kurtarma İfadeniz: $1", "description": "$1 is a bolded text with the message from 'holdToRevealContent2'" @@ -1593,9 +1590,6 @@ "message": "ancak dolandırıcılar talep edilebilir.", "description": "The text link in 'holdToRevealContent3'" }, - "holdToRevealTitle": { - "message": "GKİ'nizi güvende tutun" - }, "ignoreAll": { "message": "Tümünü yoksay" }, diff --git a/app/_locales/vi/messages.json b/app/_locales/vi/messages.json index 4bb67b89e33e..120efa397a7e 100644 --- a/app/_locales/vi/messages.json +++ b/app/_locales/vi/messages.json @@ -1570,9 +1570,6 @@ "history": { "message": "Lịch sử" }, - "holdToReveal": { - "message": "Giữ để hiển thị Cụm từ khôi phục bí mật" - }, "holdToRevealContent1": { "message": "Cụm từ khôi phục bí mật của bạn cung cấp $1", "description": "$1 is a bolded text with the message from 'holdToRevealContent2'" @@ -1593,9 +1590,6 @@ "message": "nhưng những kẻ lừa đảo qua mạng thì có.", "description": "The text link in 'holdToRevealContent3'" }, - "holdToRevealTitle": { - "message": "Đảm bảo an toàn cho Cụm từ khôi phục bí mật của bạn" - }, "ignoreAll": { "message": "Bỏ qua tất cả" }, diff --git a/app/_locales/zh_CN/messages.json b/app/_locales/zh_CN/messages.json index 3bbbf016536a..ffe567145ead 100644 --- a/app/_locales/zh_CN/messages.json +++ b/app/_locales/zh_CN/messages.json @@ -1570,9 +1570,6 @@ "history": { "message": "历史记录" }, - "holdToReveal": { - "message": "按住以显示 SRP" - }, "holdToRevealContent1": { "message": "您的助记词提供 $1", "description": "$1 is a bolded text with the message from 'holdToRevealContent2'" @@ -1593,9 +1590,6 @@ "message": "但网络钓鱼者可能会。", "description": "The text link in 'holdToRevealContent3'" }, - "holdToRevealTitle": { - "message": "确保 SRP 的安全" - }, "ignoreAll": { "message": "忽略所有" }, diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 48e18e5a3278..60dae31308e6 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -63,6 +63,7 @@ import { ///: END:ONLY_INCLUDE_IN import { SignatureController } from '@metamask/signature-controller'; +import { ApprovalType } from '@metamask/controller-utils'; import { AssetType, TransactionStatus, @@ -94,7 +95,6 @@ import { UI_NOTIFICATIONS } from '../../shared/notifications'; import { MILLISECOND, SECOND } from '../../shared/constants/time'; import { ORIGIN_METAMASK, - MESSAGE_TYPE, ///: BEGIN:ONLY_INCLUDE_IN(snaps) SNAP_DIALOG_TYPES, ///: END:ONLY_INCLUDE_IN @@ -256,9 +256,13 @@ export default class MetamaskController extends EventEmitter { }), showApprovalRequest: opts.showUserConfirmation, typesExcludedFromRateLimiting: [ - MESSAGE_TYPE.ETH_SIGN, - MESSAGE_TYPE.PERSONAL_SIGN, - MESSAGE_TYPE.ETH_SIGN_TYPED_DATA, + ApprovalType.EthSign, + ApprovalType.PersonalSign, + ApprovalType.EthSignTypedData, + ApprovalType.Transaction, + ApprovalType.WatchAsset, + ApprovalType.EthGetEncryptionPublicKey, + ApprovalType.EthDecrypt, ], }); diff --git a/package.json b/package.json index afaf7c54acff..c33842858205 100644 --- a/package.json +++ b/package.json @@ -93,7 +93,9 @@ "test-storybook:ci": "concurrently -k -s first -n \"SB,TEST\" -c \"magenta,blue\" \"yarn storybook:build && npx http-server storybook-build --port 6006 \" \"wait-on tcp:6006 && yarn test-storybook --maxWorkers=2\"", "githooks:install": "husky install", "fitness-functions": "ts-node development/fitness-functions/index.ts", - "generate-beta-commit": "node ./development/generate-beta-commit.js" + "generate-beta-commit": "node ./development/generate-beta-commit.js", + "validate-branch-name": "validate-branch-name", + "label-prs": "ts-node ./.github/scripts/label-prs.ts" }, "resolutions": { "analytics-node/axios": "^0.21.2", @@ -211,6 +213,8 @@ "request@^2.85.0": "patch:request@npm%3A2.88.2#./.yarn/patches/request-npm-2.88.2-f4a57c72c4.patch" }, "dependencies": { + "@actions/core": "^1.10.0", + "@actions/github": "^5.1.1", "@babel/runtime": "^7.5.5", "@download/blockies": "^1.0.3", "@ensdomains/content-hash": "^2.5.6", @@ -317,6 +321,7 @@ "fuse.js": "^3.2.0", "globalthis": "^1.0.1", "human-standard-token-abi": "^2.0.0", + "husky": "^8.0.3", "immer": "^9.0.6", "is-retry-allowed": "^2.2.0", "jest-junit": "^14.0.1", @@ -360,6 +365,7 @@ "unicode-confusables": "^0.1.1", "uuid": "^8.3.2", "valid-url": "^1.0.9", + "validate-branch-name": "^1.3.0", "web3-stream-provider": "^4.0.0", "zxcvbn": "^4.4.2" }, diff --git a/ui/components/app/hold-to-reveal-button/hold-to-reveal-button.js b/ui/components/app/hold-to-reveal-button/hold-to-reveal-button.js index fe2b422d4745..b12443ccd7ed 100644 --- a/ui/components/app/hold-to-reveal-button/hold-to-reveal-button.js +++ b/ui/components/app/hold-to-reveal-button/hold-to-reveal-button.js @@ -121,7 +121,7 @@ export default function HoldToRevealButton({ buttonText, onLongPressed }) {
diff --git a/ui/components/app/hold-to-reveal-button/hold-to-reveal-button.test.js b/ui/components/app/hold-to-reveal-button/hold-to-reveal-button.test.js index 16c6c09170a4..2fd23e8eff8b 100644 --- a/ui/components/app/hold-to-reveal-button/hold-to-reveal-button.test.js +++ b/ui/components/app/hold-to-reveal-button/hold-to-reveal-button.test.js @@ -1,20 +1,21 @@ import React from 'react'; import { render, fireEvent, waitFor } from '@testing-library/react'; +import configureMockState from 'redux-mock-store'; +import thunk from 'redux-thunk'; +import { renderWithProvider } from '../../../../test/lib/render-helpers'; +import mockState from '../../../../test/data/mock-state.json'; import { MetaMetricsEventCategory, MetaMetricsEventKeyType, MetaMetricsEventName, } from '../../../../shared/constants/metametrics'; +import { MetaMetricsContext } from '../../../contexts/metametrics'; import HoldToRevealButton from './hold-to-reveal-button'; const mockTrackEvent = jest.fn(); -jest.mock('react', () => ({ - ...jest.requireActual('react'), - useContext: () => mockTrackEvent, -})); - describe('HoldToRevealButton', () => { + const mockStore = configureMockState([thunk])(mockState); let props = {}; beforeEach(() => { @@ -51,21 +52,24 @@ describe('HoldToRevealButton', () => { }); it('should show the locked padlock when a button is long pressed and then should show it after it was lifted off before the animation concludes', async () => { - const { getByText, queryByLabelText } = render( - , + const { getByText, queryByLabelText } = renderWithProvider( + + + , + mockStore, ); const button = getByText('Hold to reveal SRP'); fireEvent.mouseDown(button); - const circleLocked = queryByLabelText('circle-locked'); + const circleLocked = queryByLabelText('hold to reveal circle locked'); await waitFor(() => { expect(circleLocked).toBeInTheDocument(); }); fireEvent.mouseUp(button); - const circleUnlocked = queryByLabelText('circle-unlocked'); + const circleUnlocked = queryByLabelText('hold to reveal circle unlocked'); await waitFor(() => { expect(circleUnlocked).not.toBeInTheDocument(); @@ -73,37 +77,40 @@ describe('HoldToRevealButton', () => { }); it('should show the unlocked padlock when a button is long pressed for the duration of the animation', async () => { - const { getByText, queryByLabelText } = render( - , + const { getByText, queryByLabelText, getByLabelText } = renderWithProvider( + + + , + mockStore, ); const button = getByText('Hold to reveal SRP'); fireEvent.mouseDown(button); - const circleLocked = queryByLabelText('circle-locked'); + const circleLocked = getByLabelText('hold to reveal circle locked'); fireEvent.transitionEnd(circleLocked); - const circleUnlocked = queryByLabelText('circle-unlocked'); + const circleUnlocked = queryByLabelText('hold to reveal circle unlocked'); fireEvent.animationEnd(circleUnlocked); await waitFor(() => { expect(circleUnlocked).toBeInTheDocument(); - expect(mockTrackEvent).toHaveBeenNthCalledWith(2, { + expect(mockTrackEvent).toHaveBeenNthCalledWith(1, { category: MetaMetricsEventCategory.Keys, event: MetaMetricsEventName.SrpHoldToRevealClickStarted, properties: { key_type: MetaMetricsEventKeyType.Srp, }, }); - expect(mockTrackEvent).toHaveBeenNthCalledWith(5, { + expect(mockTrackEvent).toHaveBeenNthCalledWith(2, { category: MetaMetricsEventCategory.Keys, event: MetaMetricsEventName.SrpHoldToRevealCompleted, properties: { key_type: MetaMetricsEventKeyType.Srp, }, }); - expect(mockTrackEvent).toHaveBeenNthCalledWith(6, { + expect(mockTrackEvent).toHaveBeenNthCalledWith(3, { category: MetaMetricsEventCategory.Keys, event: MetaMetricsEventName.SrpRevealViewed, properties: { diff --git a/ui/components/app/modals/export-private-key-modal/__snapshots__/export-private-key-modal.test.js.snap b/ui/components/app/modals/export-private-key-modal/__snapshots__/export-private-key-modal.test.js.snap index f275c351e6e3..be40a2b069b1 100644 --- a/ui/components/app/modals/export-private-key-modal/__snapshots__/export-private-key-modal.test.js.snap +++ b/ui/components/app/modals/export-private-key-modal/__snapshots__/export-private-key-modal.test.js.snap @@ -59,57 +59,76 @@ exports[`Export PrivateKey Modal should match snapshot 1`] = ` class="account-modal__close" />
0x0DCD5D886577d5081B0c52e242Ef29E70Be3E7bc
- Show Private Keys - +

- Type your MetaMask password - - +
+ > + +
+

- Warning: Never disclose this key. Anyone with your private keys can steal any assets held in your account. + +
+

+ Warning: Never disclose this key. Anyone with your private keys can steal any assets held in your account. +

+
diff --git a/ui/components/app/modals/export-private-key-modal/export-private-key-modal.component.js b/ui/components/app/modals/export-private-key-modal/export-private-key-modal.component.js index b50c3de852d5..49c4f7be2aa1 100644 --- a/ui/components/app/modals/export-private-key-modal/export-private-key-modal.component.js +++ b/ui/components/app/modals/export-private-key-modal/export-private-key-modal.component.js @@ -1,139 +1,188 @@ import log from 'loglevel'; +import React, { useContext, useEffect, useState } from 'react'; import PropTypes from 'prop-types'; -import React, { Component } from 'react'; - -import copyToClipboard from 'copy-to-clipboard'; -import Button from '../../../ui/button'; -import AccountModalContainer from '../account-modal-container'; +import withModalProps from '../../../../helpers/higher-order-components/with-modal-props'; +import Box from '../../../ui/box'; import { - toChecksumHexAddress, - stripHexPrefix, -} from '../../../../../shared/modules/hexstring-utils'; + BUTTON_SIZES, + BUTTON_VARIANT, + BannerAlert, + Button, + Text, +} from '../../../component-library'; +import AccountModalContainer from '../account-modal-container'; +import { toChecksumHexAddress } from '../../../../../shared/modules/hexstring-utils'; import { MetaMetricsEventCategory, MetaMetricsEventKeyType, MetaMetricsEventName, } from '../../../../../shared/constants/metametrics'; - -export default class ExportPrivateKeyModal extends Component { - static contextTypes = { - t: PropTypes.func, - trackEvent: PropTypes.func, - }; - - static defaultProps = { - warning: null, - previousModalState: null, - }; - - static propTypes = { - exportAccount: PropTypes.func.isRequired, - selectedIdentity: PropTypes.object.isRequired, - warning: PropTypes.node, - showAccountDetailModal: PropTypes.func.isRequired, - hideModal: PropTypes.func.isRequired, - hideWarning: PropTypes.func.isRequired, - clearAccountDetails: PropTypes.func.isRequired, - previousModalState: PropTypes.string, - }; - - state = { - password: '', - privateKey: null, - showWarning: true, - }; - - componentWillUnmount() { - this.props.clearAccountDetails(); - this.props.hideWarning(); - } - - exportAccountAndGetPrivateKey = (password, address) => { - const { exportAccount } = this.props; - - exportAccount(password, address) - .then((privateKey) => { - this.context.trackEvent({ +import HoldToRevealModal from '../hold-to-reveal-modal/hold-to-reveal-modal'; +import { MetaMetricsContext } from '../../../../contexts/metametrics'; +import { useI18nContext } from '../../../../hooks/useI18nContext'; +import { + BLOCK_SIZES, + BorderColor, + BorderStyle, + Color, + DISPLAY, + FLEX_DIRECTION, + FONT_WEIGHT, + JustifyContent, + TextVariant, +} from '../../../../helpers/constants/design-system'; +import PrivateKeyDisplay from './private-key'; +import PasswordInput from './password-input'; + +const ExportPrivateKeyModal = ({ + clearAccountDetails, + hideWarning, + exportAccount, + selectedIdentity, + showAccountDetailModal, + hideModal, + warning = null, + previousModalState, +}) => { + const [password, setPassword] = useState(''); + const [privateKey, setPrivateKey] = useState(null); + const [showWarning, setShowWarning] = useState(true); + const [showHoldToReveal, setShowHoldToReveal] = useState(false); + const trackEvent = useContext(MetaMetricsContext); + const t = useI18nContext(); + + useEffect(() => { + return () => { + clearAccountDetails(); + hideWarning(); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const exportAccountAndGetPrivateKey = async (passwordInput, address) => { + try { + const privateKeyRetrieved = await exportAccount(passwordInput, address); + trackEvent( + { category: MetaMetricsEventCategory.Keys, event: MetaMetricsEventName.KeyExportRevealed, properties: { key_type: MetaMetricsEventKeyType.Pkey, }, - }); - - this.setState({ - privateKey, - showWarning: false, - }); - }) - .catch((e) => { - this.context.trackEvent({ + }, + {}, + ); + setPrivateKey(privateKeyRetrieved); + setShowWarning(false); + setShowHoldToReveal(true); + } catch (e) { + trackEvent( + { category: MetaMetricsEventCategory.Keys, event: MetaMetricsEventName.KeyExportFailed, properties: { key_type: MetaMetricsEventKeyType.Pkey, reason: 'incorrect_password', }, - }); + }, + {}, + ); - log.error(e); - }); + log.error(e); + } }; - renderPasswordLabel(privateKey) { - return ( - - {privateKey - ? this.context.t('copyPrivateKey') - : this.context.t('typePassword')} - - ); - } - - renderPasswordInput(privateKey) { - const plainKey = privateKey && stripHexPrefix(privateKey); - - if (!privateKey) { - return ( - this.setState({ password: event.target.value })} - /> - ); - } + const { name, address } = selectedIdentity; + if (showHoldToReveal) { return ( -
{ - copyToClipboard(plainKey); - this.context.trackEvent({ - category: MetaMetricsEventCategory.Keys, - event: MetaMetricsEventName.KeyExportCopied, - properties: { - key_type: MetaMetricsEventKeyType.Pkey, - copy_method: 'clipboard', - }, - }); - }} + showAccountDetailModal()} > - {plainKey} -
+ setShowHoldToReveal(false)} + willHide={false} + holdToRevealType="PrivateKey" + /> + ); } - renderButtons(privateKey, address, hideModal) { - return ( -
+ return ( + showAccountDetailModal()} + > + + {name} + + + {toChecksumHexAddress(address)} + + + + {t('showPrivateKeys')} + + {privateKey ? ( + + ) : ( + + )} + {showWarning && ( + + {warning} + + )} + + {t('privateKeyWarning')} + + {!privateKey && ( )} {privateKey ? ( ) : ( )} -
- ); - } - - render() { - const { - selectedIdentity, - warning, - showAccountDetailModal, - hideModal, - previousModalState, - } = this.props; - const { name, address } = selectedIdentity; - - const { privateKey, showWarning } = this.state; - - return ( - showAccountDetailModal()} - > - {name} -
- {toChecksumHexAddress(address)} -
-
- - {this.context.t('showPrivateKeys')} - -
- {this.renderPasswordLabel(privateKey)} - {this.renderPasswordInput(privateKey)} - {showWarning && warning ? ( - - {warning} - - ) : null} -
-
- {this.context.t('privateKeyWarning')} -
- {this.renderButtons(privateKey, address, hideModal)} - - ); - } -} + + + ); +}; + +ExportPrivateKeyModal.propTypes = { + exportAccount: PropTypes.func.isRequired, + selectedIdentity: PropTypes.object.isRequired, + warning: PropTypes.node, + showAccountDetailModal: PropTypes.func.isRequired, + hideModal: PropTypes.func.isRequired, + hideWarning: PropTypes.func.isRequired, + clearAccountDetails: PropTypes.func.isRequired, + previousModalState: PropTypes.string, +}; + +export default withModalProps(ExportPrivateKeyModal); diff --git a/ui/components/app/modals/export-private-key-modal/export-private-key-modal.component.test.js b/ui/components/app/modals/export-private-key-modal/export-private-key-modal.component.test.js new file mode 100644 index 000000000000..f4a9132808b4 --- /dev/null +++ b/ui/components/app/modals/export-private-key-modal/export-private-key-modal.component.test.js @@ -0,0 +1,130 @@ +import { fireEvent, waitFor } from '@testing-library/react'; +import configureMockStore from 'redux-mock-store'; +import React from 'react'; +import thunk from 'redux-thunk'; +import { renderWithProvider } from '../../../../../test/lib/render-helpers'; +import ExportPrivateKeyModal from '.'; + +const mockAddress = '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc'; +const mockPrivateKey = 'mock private key'; +const mockExportAccount = jest.fn().mockResolvedValue(mockPrivateKey); +const mockClearAccountDetail = jest.fn(); +const mockHideWarning = jest.fn(); + +jest.mock('../../../../store/actions', () => ({ + exportAccount: () => mockExportAccount, + clearAccountDetails: () => mockClearAccountDetail, + hideWarning: () => mockHideWarning, +})); + +describe('Export Private Key Modal', () => { + const state = { + metamask: { + selectedAddress: mockAddress, + identities: { + '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc': { + address: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc', + name: 'Test Account', + }, + }, + providerConfig: { + type: 'rpc', + chainId: '0x5', + ticker: 'ETH', + id: 'testNetworkConfigurationId', + }, + }, + appState: { + warning: null, + previousModalState: { + name: null, + }, + isLoading: false, + accountDetail: { + privateKey: null, + }, + modal: { + modalState: {}, + previousModalState: { + name: null, + }, + }, + }, + }; + const mockStore = configureMockStore([thunk])(state); + + it('renders export private key modal', () => { + const { queryByText } = renderWithProvider( + , + mockStore, + ); + + const title = queryByText('Show Private Keys'); + expect(title).toBeInTheDocument(); + + const warning = queryByText( + 'Warning: Never disclose this key. Anyone with your private keys can steal any assets held in your account.', + ); + expect(warning).toBeInTheDocument(); + expect(queryByText(mockPrivateKey)).not.toBeInTheDocument(); + }); + + it('renders hold to reveal after entering password', async () => { + const { queryByText, getByPlaceholderText } = renderWithProvider( + , + mockStore, + ); + + const nextButton = queryByText('Confirm'); + expect(nextButton).toBeInTheDocument(); + + const input = getByPlaceholderText('Enter password'); + + fireEvent.change(input, { + target: { value: 'password' }, + }); + + fireEvent.click(nextButton); + + await waitFor(() => { + expect(mockExportAccount).toHaveBeenCalled(); + expect(queryByText('Keep your private key safe')).toBeInTheDocument(); + }); + }); + + it('provides password after passing hold to reveal', async () => { + const { queryByText, getByLabelText, getByText, getByPlaceholderText } = + renderWithProvider(, mockStore); + + const nextButton = queryByText('Confirm'); + expect(nextButton).toBeInTheDocument(); + + const input = getByPlaceholderText('Enter password'); + fireEvent.change(input, { + target: { value: 'password' }, + }); + + fireEvent.click(nextButton); + + await waitFor(() => { + expect(mockExportAccount).toHaveBeenCalled(); + expect(queryByText('Keep your private key safe')).toBeInTheDocument(); + }); + + const holdButton = getByText('Hold to reveal Private Key'); + expect(holdButton).toBeInTheDocument(); + + fireEvent.mouseDown(holdButton); + + const circle = getByLabelText('hold to reveal circle locked'); + fireEvent.transitionEnd(circle); + const circleUnlocked = getByLabelText('hold to reveal circle unlocked'); + fireEvent.animationEnd(circleUnlocked); + + await waitFor(() => { + expect(queryByText('Show Private Keys')).toBeInTheDocument(); + expect(queryByText('Done')).toBeInTheDocument(); + expect(queryByText(mockPrivateKey)).toBeInTheDocument(); + }); + }); +}); diff --git a/ui/components/app/modals/export-private-key-modal/export-private-key-modal.stories.js b/ui/components/app/modals/export-private-key-modal/export-private-key-modal.stories.js index 39af8761f99f..36ba01b89c4c 100644 --- a/ui/components/app/modals/export-private-key-modal/export-private-key-modal.stories.js +++ b/ui/components/app/modals/export-private-key-modal/export-private-key-modal.stories.js @@ -1,10 +1,32 @@ import React from 'react'; -import ExportPrivateKeyModal from '.'; +import { Provider } from 'react-redux'; +import testData from '../../../../../.storybook/test-data'; +import configureStore from '../../../../store/store'; +import ExportPrivateKeyModal from './export-private-key-modal.component'; + +// Using Test Data For Redux +const store = configureStore(testData); export default { title: 'Components/App/Modals/ExportPrivateKeyModal', + decorators: [(story) => {story()}], + argsTypes: { + exportAccount: { action: 'exportAccount' }, + }, }; -export const DefaultStory = () => ; +export const DefaultStory = () => { + return ( + { + return 'mockPrivateKey'; + }} + selectedIdentity={ + testData.metamask.identities[testData.metamask.selectedAddress] + } + /> + ); +}; DefaultStory.storyName = 'Default'; diff --git a/ui/components/app/modals/export-private-key-modal/export-private-key-modal.test.js b/ui/components/app/modals/export-private-key-modal/export-private-key-modal.test.js index bc5e38df2447..873be62b615f 100644 --- a/ui/components/app/modals/export-private-key-modal/export-private-key-modal.test.js +++ b/ui/components/app/modals/export-private-key-modal/export-private-key-modal.test.js @@ -59,7 +59,8 @@ describe('Export PrivateKey Modal', () => { mockStore, ); - const passwordInput = queryByTestId('password-input'); + const passwordInput = + queryByTestId('password-input').querySelector('input'); const passwordInputEvent = { target: { diff --git a/ui/components/app/modals/export-private-key-modal/index.scss b/ui/components/app/modals/export-private-key-modal/index.scss index 46e12a102b22..8edde5e39eab 100644 --- a/ui/components/app/modals/export-private-key-modal/index.scss +++ b/ui/components/app/modals/export-private-key-modal/index.scss @@ -1,11 +1,4 @@ .export-private-key-modal { - &__body-title { - @include H4; - - margin-top: 16px; - margin-bottom: 16px; - } - &__divider { width: 100%; height: 1px; @@ -13,93 +6,18 @@ background-color: var(--color-border-default); } - &__account-name { - @include H4; - - margin-top: 9px; - } - - &__password { - display: flex; - flex-direction: column; - } - - &__password-label, - &__password--error { - @include H6; - - color: var(--color-text-default); - margin-bottom: 10px; - } - - &__password--error { - color: var(--color-error-default); - margin-bottom: 0; - } - - &__password-input { - @include Paragraph; - - padding: 10px 0 13px 17px; - width: 291px; - height: 44px; - background: var(--color-background-default); - color: var(--color-text-default); - border: 1px solid var(--color-border-default); - } - &__password::-webkit-input-placeholder { color: var(--color-text-muted); } - &__password--warning { - @include H7; - - border-radius: 8px; - background-color: var(--color-error-muted); - font-weight: 500; - color: var(--color-text-default); - border: 1px solid var(--color-error-default); - width: 292px; - padding: 9px 15px; - margin-top: 18px; - } - &__private-key-display { - @include Paragraph; - height: 80px; width: 291px; - border: 1px solid var(--color-border-default); - border-radius: 2px; - color: var(--color-error-default); - padding: 9px 13px 8px; overflow: hidden; overflow-wrap: break-word; } - &__buttons { - display: flex; - flex-direction: row; - justify-content: center; - width: 100%; - padding: 0 25px; - } - - &__button { - margin-top: 17px; - width: 141px; - min-width: initial; - } - - &__button--cancel { - margin-right: 15px; - } - .ellip-address-wrapper { - border: 1px solid var(--color-border-default); - padding: 5px 10px; - margin-top: 7px; max-width: 286px; direction: ltr; overflow: hidden; diff --git a/ui/components/app/modals/export-private-key-modal/password-input.js b/ui/components/app/modals/export-private-key-modal/password-input.js new file mode 100644 index 000000000000..4892055bde0b --- /dev/null +++ b/ui/components/app/modals/export-private-key-modal/password-input.js @@ -0,0 +1,50 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { + BLOCK_SIZES, + FLEX_DIRECTION, + DISPLAY, + AlignItems, + Color, + TextVariant, +} from '../../../../helpers/constants/design-system'; +import Box from '../../../ui/box'; +import { useI18nContext } from '../../../../hooks/useI18nContext'; +import { Label, TEXT_FIELD_TYPES, TextField } from '../../../component-library'; + +const PasswordInput = ({ setPassword }) => { + const t = useI18nContext(); + + return ( + + + setPassword(event.target.value)} + data-testid="password-input" + /> + + ); +}; + +PasswordInput.propTypes = { + setPassword: PropTypes.func.isRequired, +}; + +export default PasswordInput; diff --git a/ui/components/app/modals/export-private-key-modal/private-key.js b/ui/components/app/modals/export-private-key-modal/private-key.js new file mode 100644 index 000000000000..c2200ed4723d --- /dev/null +++ b/ui/components/app/modals/export-private-key-modal/private-key.js @@ -0,0 +1,81 @@ +import copyToClipboard from 'copy-to-clipboard'; +import { stripHexPrefix } from 'ethereumjs-util'; +import React, { useContext } from 'react'; +import PropTypes from 'prop-types'; +import Box from '../../../ui/box'; +import { useI18nContext } from '../../../../hooks/useI18nContext'; +import { + MetaMetricsEventCategory, + MetaMetricsEventName, + MetaMetricsEventKeyType, +} from '../../../../../shared/constants/metametrics'; +import { MetaMetricsContext } from '../../../../contexts/metametrics'; +import { + BLOCK_SIZES, + BorderStyle, + BorderColor, + BorderRadius, + AlignItems, + DISPLAY, + Color, + FLEX_DIRECTION, + TextVariant, +} from '../../../../helpers/constants/design-system'; +import { Label } from '../../../component-library'; + +const PrivateKeyDisplay = ({ privateKey }) => { + const trackEvent = useContext(MetaMetricsContext); + const t = useI18nContext(); + const plainKey = stripHexPrefix(privateKey); + + return ( + + + { + copyToClipboard(plainKey); + trackEvent( + { + category: MetaMetricsEventCategory.Keys, + event: MetaMetricsEventName.KeyExportCopied, + properties: { + key_type: MetaMetricsEventKeyType.Pkey, + copy_method: 'clipboard', + }, + }, + {}, + ); + }} + > + {plainKey} + + + ); +}; + +PrivateKeyDisplay.propTypes = { + privateKey: PropTypes.string.isRequired, +}; + +export default PrivateKeyDisplay; diff --git a/ui/components/app/modals/hold-to-reveal-modal/hold-to-reveal-modal.js b/ui/components/app/modals/hold-to-reveal-modal/hold-to-reveal-modal.js index 4a4f79581714..3377127c76e4 100644 --- a/ui/components/app/modals/hold-to-reveal-modal/hold-to-reveal-modal.js +++ b/ui/components/app/modals/hold-to-reveal-modal/hold-to-reveal-modal.js @@ -5,6 +5,7 @@ import Box from '../../../ui/box'; import { Text, Button, + BUTTON_SIZES, BUTTON_VARIANT, ButtonIcon, IconName, @@ -27,52 +28,80 @@ import { MetaMetricsEventName, } from '../../../../../shared/constants/metametrics'; -const HoldToRevealModal = ({ onLongPressed, hideModal }) => { +const HoldToRevealModal = ({ + onLongPressed, + hideModal, + willHide = true, + holdToRevealType = 'SRP', +}) => { const t = useI18nContext(); + const holdToRevealTitle = + holdToRevealType === 'SRP' + ? 'holdToRevealSRPTitle' + : 'holdToRevealPrivateKeyTitle'; + + const holdToRevealButton = + holdToRevealType === 'SRP' ? 'holdToRevealSRP' : 'holdToRevealPrivateKey'; const trackEvent = useContext(MetaMetricsContext); const unlock = () => { onLongPressed(); - hideModal(); + if (willHide) { + hideModal(); + } }; const handleCancel = () => { hideModal(); }; - return ( - + const renderHoldToRevealPrivateKeyContent = () => { + return ( - {t('holdToRevealTitle')} - { - trackEvent({ - category: MetaMetricsEventCategory.Keys, - event: MetaMetricsEventName.SrpHoldToRevealCloseClicked, - properties: { - key_type: MetaMetricsEventKeyType.Srp, - }, - }); - handleCancel(); - }} - ariaLabel={t('close')} - /> + + {t('holdToRevealContentPrivateKey1', [ + + {t('holdToRevealContentPrivateKey2')} + , + ])} + + + {t('holdToRevealContent3', [ + + {t('holdToRevealContent4')} + , + , + ])} + + ); + }; + + const renderHoldToRevealSRPContent = () => { + return ( { ])} + ); + }; + + return ( + + + {t(holdToRevealTitle)} + {willHide && ( + { + trackEvent({ + category: MetaMetricsEventCategory.Keys, + event: MetaMetricsEventName.SrpHoldToRevealCloseClicked, + properties: { + key_type: MetaMetricsEventKeyType.Srp, + }, + }); + handleCancel(); + }} + ariaLabel={t('close')} + /> + )} + + {holdToRevealType === 'SRP' + ? renderHoldToRevealSRPContent() + : renderHoldToRevealPrivateKeyContent()} { const onLongPressStub = jest.fn(); const hideModalStub = jest.fn(); - global.platform = { openTab: jest.fn() }; - afterEach(() => { jest.resetAllMocks(); }); @@ -36,6 +34,7 @@ describe('Hold to Reveal Modal', () => { , mockStore, ); @@ -43,7 +42,7 @@ describe('Hold to Reveal Modal', () => { const holdButton = getByText('Hold to reveal SRP'); expect(holdButton).toBeInTheDocument(); - const warningTitle = getByText(holdToRevealTitle.message); + const warningTitle = getByText(holdToRevealSRPTitle.message); expect(warningTitle).toBeInTheDocument(); const warningText1 = getByText( holdToRevealContent1.message.replace(' $1', ''), @@ -83,12 +82,12 @@ describe('Hold to Reveal Modal', () => { ); const holdButton = getByText('Hold to reveal SRP'); - const circleLocked = queryByLabelText('circle-locked'); + const circleLocked = queryByLabelText('hold to reveal circle locked'); fireEvent.mouseDown(holdButton); fireEvent.transitionEnd(circleLocked); - const circleUnlocked = queryByLabelText('circle-unlocked'); + const circleUnlocked = queryByLabelText('hold to reveal circle unlocked'); fireEvent.animationEnd(circleUnlocked); await waitFor(() => { @@ -112,8 +111,8 @@ describe('Hold to Reveal Modal', () => { fireEvent.click(holdButton); - const circleLocked = queryByLabelText('circle-locked'); - const circleUnlocked = queryByLabelText('circle-unlocked'); + const circleLocked = queryByLabelText('hold to reveal circle locked'); + const circleUnlocked = queryByLabelText('hold to reveal circle unlocked'); await waitFor(() => { expect(circleLocked).toBeInTheDocument(); @@ -163,12 +162,12 @@ describe('Hold to Reveal Modal', () => { ); const holdButton = getByText('Hold to reveal SRP'); - const circleLocked = queryByLabelText('circle-locked'); + const circleLocked = queryByLabelText('hold to reveal circle locked'); fireEvent.mouseDown(holdButton); fireEvent.transitionEnd(circleLocked); - const circleUnlocked = queryByLabelText('circle-unlocked'); + const circleUnlocked = queryByLabelText('hold to reveal circle unlocked'); fireEvent.animationEnd(circleUnlocked); await waitFor(() => { diff --git a/ui/components/ui/slider/slider.component.js b/ui/components/ui/slider/slider.component.js index 2dc67e08b722..58166506235e 100644 --- a/ui/components/ui/slider/slider.component.js +++ b/ui/components/ui/slider/slider.component.js @@ -4,13 +4,12 @@ import MaterialSlider from '@material-ui/core/Slider'; import { withStyles } from '@material-ui/core/styles'; import { - Color, - FONT_WEIGHT, - TypographyVariant, + TextColor, + TextVariant, } from '../../../helpers/constants/design-system'; import InfoTooltip from '../info-tooltip/info-tooltip'; -import Typography from '../typography/typography'; +import { Text } from '../../component-library'; const styles = { root: { @@ -68,34 +67,24 @@ const Slider = ({
{titleText && ( - + {titleText} - + )} {tooltipText && ( )} {valueText && ( - + {valueText} - + )}
{titleDetail && (
- + {titleDetail} - +
)}
@@ -103,12 +92,9 @@ const Slider = ({
{infoText && ( - + {infoText} - + )}
diff --git a/ui/pages/keychains/reveal-seed.js b/ui/pages/keychains/reveal-seed.js index 81c264abcac4..0fa7aa9489bc 100644 --- a/ui/pages/keychains/reveal-seed.js +++ b/ui/pages/keychains/reveal-seed.js @@ -91,6 +91,7 @@ const RevealSeedPage = () => { setCompletedLongPress(true); setScreen(REVEAL_SEED_SCREEN); }, + holdToRevealType: 'SRP', }), ); }) diff --git a/ui/pages/keychains/reveal-seed.test.js b/ui/pages/keychains/reveal-seed.test.js index aec802384f79..58baa750329b 100644 --- a/ui/pages/keychains/reveal-seed.test.js +++ b/ui/pages/keychains/reveal-seed.test.js @@ -214,12 +214,12 @@ describe('Reveal Seed Page', () => { }); const holdButton = getByText('Hold to reveal SRP'); - const circleLocked = queryByLabelText('circle-locked'); + const circleLocked = queryByLabelText('hold to reveal circle locked'); fireEvent.mouseDown(holdButton); fireEvent.transitionEnd(circleLocked); - const circleUnlocked = queryByLabelText('circle-unlocked'); + const circleUnlocked = queryByLabelText('hold to reveal circle unlocked'); fireEvent.animationEnd(circleUnlocked); await waitFor(() => { diff --git a/yarn.lock b/yarn.lock index 39656d679494..69484f285799 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5,6 +5,37 @@ __metadata: version: 6 cacheKey: 8 +"@actions/core@npm:^1.10.0": + version: 1.10.0 + resolution: "@actions/core@npm:1.10.0" + dependencies: + "@actions/http-client": ^2.0.1 + uuid: ^8.3.2 + checksum: 0a75621e007ab20d887434cdd165f0b9036f14c22252a2faed33543d8b9d04ec95d823e69ca636a25245574e4585d73e1e9e47a845339553c664f9f2c9614669 + languageName: node + linkType: hard + +"@actions/github@npm:^5.1.1": + version: 5.1.1 + resolution: "@actions/github@npm:5.1.1" + dependencies: + "@actions/http-client": ^2.0.1 + "@octokit/core": ^3.6.0 + "@octokit/plugin-paginate-rest": ^2.17.0 + "@octokit/plugin-rest-endpoint-methods": ^5.13.0 + checksum: 2210bd7f8e1e8b407b7df74a259523dc4c63f4ad3a6bfcc0d7867b6e9c3499bd3e25d7de7a9a1bbd0de3be441a8832d5c0b5c0cff3036cd477378c0ec5502434 + languageName: node + linkType: hard + +"@actions/http-client@npm:^2.0.1": + version: 2.1.0 + resolution: "@actions/http-client@npm:2.1.0" + dependencies: + tunnel: ^0.0.6 + checksum: 25a72a952cc95fb4b3ab086da73a5754dd0957c206637cace69be2e16f018cc1b3d3c40d3bcf89ffd8a5929d5e8445594b498b50db306a50ad7536023f8e3800 + languageName: node + linkType: hard + "@ampproject/remapping@npm:^2.1.0": version: 2.2.0 resolution: "@ampproject/remapping@npm:2.2.0" @@ -4692,6 +4723,116 @@ __metadata: languageName: node linkType: hard +"@octokit/auth-token@npm:^2.4.4": + version: 2.5.0 + resolution: "@octokit/auth-token@npm:2.5.0" + dependencies: + "@octokit/types": ^6.0.3 + checksum: 45949296c09abcd6beb4c3f69d45b0c1f265f9581d2a9683cf4d1800c4cf8259c2f58d58e44c16c20bffb85a0282a176c0d51f4af300e428b863f27b910e6297 + languageName: node + linkType: hard + +"@octokit/core@npm:^3.6.0": + version: 3.6.0 + resolution: "@octokit/core@npm:3.6.0" + dependencies: + "@octokit/auth-token": ^2.4.4 + "@octokit/graphql": ^4.5.8 + "@octokit/request": ^5.6.3 + "@octokit/request-error": ^2.0.5 + "@octokit/types": ^6.0.3 + before-after-hook: ^2.2.0 + universal-user-agent: ^6.0.0 + checksum: f81160129037bd8555d47db60cd5381637b7e3602ad70735a7bdf8f3d250c7b7114a666bb12ef7a8746a326a5d72ed30a1b8f8a5a170007f7285c8e217bef1f0 + languageName: node + linkType: hard + +"@octokit/endpoint@npm:^6.0.1": + version: 6.0.12 + resolution: "@octokit/endpoint@npm:6.0.12" + dependencies: + "@octokit/types": ^6.0.3 + is-plain-object: ^5.0.0 + universal-user-agent: ^6.0.0 + checksum: b48b29940af11c4b9bca41cf56809754bb8385d4e3a6122671799d27f0238ba575b3fde86d2d30a84f4dbbc14430940de821e56ecc6a9a92d47fc2b29a31479d + languageName: node + linkType: hard + +"@octokit/graphql@npm:^4.5.8": + version: 4.8.0 + resolution: "@octokit/graphql@npm:4.8.0" + dependencies: + "@octokit/request": ^5.6.0 + "@octokit/types": ^6.0.3 + universal-user-agent: ^6.0.0 + checksum: f68afe53f63900d4a16a0a733f2f500df2695b731f8ed32edb728d50edead7f5011437f71d069c2d2f6d656227703d0c832a3c8af58ecf82bd5dcc051f2d2d74 + languageName: node + linkType: hard + +"@octokit/openapi-types@npm:^12.11.0": + version: 12.11.0 + resolution: "@octokit/openapi-types@npm:12.11.0" + checksum: 8a7d4bd6288cc4085cabe0ca9af2b87c875c303af932cb138aa1b2290eb69d32407759ac23707bb02776466e671244a902e9857896903443a69aff4b6b2b0e3b + languageName: node + linkType: hard + +"@octokit/plugin-paginate-rest@npm:^2.17.0": + version: 2.21.3 + resolution: "@octokit/plugin-paginate-rest@npm:2.21.3" + dependencies: + "@octokit/types": ^6.40.0 + peerDependencies: + "@octokit/core": ">=2" + checksum: acf31de2ba4021bceec7ff49c5b0e25309fc3c009d407f153f928ddf436ab66cd4217344138378d5523f5fb233896e1db58c9c7b3ffd9612a66d760bc5d319ed + languageName: node + linkType: hard + +"@octokit/plugin-rest-endpoint-methods@npm:^5.13.0": + version: 5.16.2 + resolution: "@octokit/plugin-rest-endpoint-methods@npm:5.16.2" + dependencies: + "@octokit/types": ^6.39.0 + deprecation: ^2.3.1 + peerDependencies: + "@octokit/core": ">=3" + checksum: 30fcc50c335d1093f03573d9fa3a4b7d027fc98b215c43e07e82ee8dabfa0af0cf1b963feb542312ae32d897a2f68dc671577206f30850215517bebedc5a2c73 + languageName: node + linkType: hard + +"@octokit/request-error@npm:^2.0.5, @octokit/request-error@npm:^2.1.0": + version: 2.1.0 + resolution: "@octokit/request-error@npm:2.1.0" + dependencies: + "@octokit/types": ^6.0.3 + deprecation: ^2.0.0 + once: ^1.4.0 + checksum: baec2b5700498be01b4d958f9472cb776b3f3b0ea52924323a07e7a88572e24cac2cdf7eb04a0614031ba346043558b47bea2d346e98f0e8385b4261f138ef18 + languageName: node + linkType: hard + +"@octokit/request@npm:^5.6.0, @octokit/request@npm:^5.6.3": + version: 5.6.3 + resolution: "@octokit/request@npm:5.6.3" + dependencies: + "@octokit/endpoint": ^6.0.1 + "@octokit/request-error": ^2.1.0 + "@octokit/types": ^6.16.1 + is-plain-object: ^5.0.0 + node-fetch: ^2.6.7 + universal-user-agent: ^6.0.0 + checksum: c0b4542eb4baaf880d673c758d3e0b5c4a625a4ae30abf40df5548b35f1ff540edaac74625192b1aff42a79ac661e774da4ab7d5505f1cb4ef81239b1e8510c5 + languageName: node + linkType: hard + +"@octokit/types@npm:^6.0.3, @octokit/types@npm:^6.16.1, @octokit/types@npm:^6.39.0, @octokit/types@npm:^6.40.0": + version: 6.41.0 + resolution: "@octokit/types@npm:6.41.0" + dependencies: + "@octokit/openapi-types": ^12.11.0 + checksum: fd6f75e0b19b90d1a3d244d2b0c323ed8f2f05e474a281f60a321986683548ef2e0ec2b3a946aa9405d6092e055344455f69f58957c60f58368c8bdda5b7d2ab + languageName: node + linkType: hard + "@oozcitak/dom@npm:1.15.10": version: 1.15.10 resolution: "@oozcitak/dom@npm:1.15.10" @@ -9913,6 +10054,13 @@ __metadata: languageName: node linkType: hard +"babel-plugin-add-module-exports@npm:^0.2.1": + version: 0.2.1 + resolution: "babel-plugin-add-module-exports@npm:0.2.1" + checksum: 0d40e7b970161a10960fbbd0492565ae2f3f4872b4da089f8f5bb874cfac4e38e088e4d96bc33cef22a8963774abe84622feeebbbe049ad95c4ee33c907b277d + languageName: node + linkType: hard + "babel-plugin-add-react-displayname@npm:^0.0.5": version: 0.0.5 resolution: "babel-plugin-add-react-displayname@npm:0.0.5" @@ -10351,6 +10499,13 @@ __metadata: languageName: node linkType: hard +"before-after-hook@npm:^2.2.0": + version: 2.2.3 + resolution: "before-after-hook@npm:2.2.3" + checksum: a1a2430976d9bdab4cd89cb50d27fa86b19e2b41812bf1315923b0cba03371ebca99449809226425dd3bcef20e010db61abdaff549278e111d6480034bebae87 + languageName: node + linkType: hard + "better-opn@npm:^2.1.1": version: 2.1.1 resolution: "better-opn@npm:2.1.1" @@ -13247,6 +13402,17 @@ __metadata: languageName: node linkType: hard +"current-git-branch@npm:^1.1.0": + version: 1.1.0 + resolution: "current-git-branch@npm:1.1.0" + dependencies: + babel-plugin-add-module-exports: ^0.2.1 + execa: ^0.6.1 + is-git-repository: ^1.0.0 + checksum: 57042d5c9fc608a951e81310da1caa3bdf917d0fede06996bfd7eaab5bdee7d29ced3440c3c73e5d90e503e689e2084e1906b1a17723e35684b1579d1bb5a54f + languageName: node + linkType: hard + "cwd@npm:^0.10.0": version: 0.10.0 resolution: "cwd@npm:0.10.0" @@ -13774,6 +13940,13 @@ __metadata: languageName: node linkType: hard +"deprecation@npm:^2.0.0, deprecation@npm:^2.3.1": + version: 2.3.1 + resolution: "deprecation@npm:2.3.1" + checksum: f56a05e182c2c195071385455956b0c4106fe14e36245b00c689ceef8e8ab639235176a96977ba7c74afb173317fac2e0ec6ec7a1c6d1e6eaa401c586c714132 + languageName: node + linkType: hard + "deps-regex@npm:^0.1.4": version: 0.1.4 resolution: "deps-regex@npm:0.1.4" @@ -16173,6 +16346,21 @@ __metadata: languageName: node linkType: hard +"execa@npm:^0.6.1": + version: 0.6.3 + resolution: "execa@npm:0.6.3" + dependencies: + cross-spawn: ^5.0.1 + get-stream: ^3.0.0 + is-stream: ^1.1.0 + npm-run-path: ^2.0.0 + p-finally: ^1.0.0 + signal-exit: ^3.0.0 + strip-eof: ^1.0.0 + checksum: 2c66177731273a7c0a4c031af81b486b67ec1eeeb8f353ebc68e0cfe7f63aca9ebc1e6fe03ba10f130f2bd179c0ac69b35668fe2bfc1ceb68fbf5291d0783457 + languageName: node + linkType: hard + "execa@npm:^0.7.0": version: 0.7.0 resolution: "execa@npm:0.7.0" @@ -20036,6 +20224,16 @@ __metadata: languageName: node linkType: hard +"is-git-repository@npm:^1.0.0": + version: 1.1.1 + resolution: "is-git-repository@npm:1.1.1" + dependencies: + execa: ^0.6.1 + path-is-absolute: ^1.0.1 + checksum: 2873d41da9ae5771a9118bdd743f32fa868301c57e8e4d8e255d4e14c04267112294a29f2824531fa554696042d8d87185811cff2de2a06381cff7d61d9ac22d + languageName: node + linkType: hard + "is-glob@npm:^2.0.0, is-glob@npm:^2.0.1": version: 2.0.1 resolution: "is-glob@npm:2.0.1" @@ -23951,6 +24149,8 @@ __metadata: version: 0.0.0-use.local resolution: "metamask-crx@workspace:." dependencies: + "@actions/core": ^1.10.0 + "@actions/github": ^5.1.1 "@babel/code-frame": ^7.12.13 "@babel/core": ^7.12.1 "@babel/eslint-parser": ^7.13.14 @@ -24280,6 +24480,7 @@ __metadata: unicode-confusables: ^0.1.1 uuid: ^8.3.2 valid-url: ^1.0.9 + validate-branch-name: ^1.3.0 vinyl: ^2.2.1 vinyl-buffer: ^1.0.1 vinyl-source-stream: ^2.0.0 @@ -33597,6 +33798,13 @@ __metadata: languageName: node linkType: hard +"universal-user-agent@npm:^6.0.0": + version: 6.0.0 + resolution: "universal-user-agent@npm:6.0.0" + checksum: 5092bbc80dd0d583cef0b62c17df0043193b74f425112ea6c1f69bc5eda21eeec7a08d8c4f793a277eb2202ffe9b44bec852fa3faff971234cd209874d1b79ef + languageName: node + linkType: hard + "universalify@npm:^0.1.0, universalify@npm:^0.1.2": version: 0.1.2 resolution: "universalify@npm:0.1.2" @@ -33956,6 +34164,19 @@ __metadata: languageName: node linkType: hard +"validate-branch-name@npm:^1.3.0": + version: 1.3.0 + resolution: "validate-branch-name@npm:1.3.0" + dependencies: + commander: ^8.3.0 + cosmiconfig: ^7.0.1 + current-git-branch: ^1.1.0 + bin: + validate-branch-name: cli.js + checksum: be82c1e39bfe0519fa02f01670b5ef928903a19289249559c9148c0fd20df356f373f2490fbfd54d018868a6348cc2ceb82fb67d5f5c48c15ecb48735b9b87fb + languageName: node + linkType: hard + "validate-npm-package-license@npm:^3.0.1": version: 3.0.4 resolution: "validate-npm-package-license@npm:3.0.4"