mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-22 09:57:02 +01:00
[FLASK] Improve snaps connect flow (#19461)
* add todo comments * add snaps-connect component * added new messages * added component scss files to main scss files * remove dead code and add snap-connect-cell * update snaps connect * updated messages and styling * update messages and css * update css * moved snaps privacy warning into snaps connect, moved snaps connect error into snap install * added story and removed unused import * fix style linting and move snaps connect error css * removed unused message * ran lavamoat policy generation * fix fencing * some more css changes * Fix scrolling and box shadow * added comment, fixed quote * Align more with Figma * Regen LavaMoat policies * bring back privacy logic to permission page container * Revert scrolling changes + fix snaps icon * fix linting, reintroduced dedupe logic and additionally addressed a corner case * made some fixes * Fix scrolling with multiple snaps * add dedupe logic to snaps connect and fix spacing issue * policy regen * lint fix * fix fencing * replaced with new icon design, trimmed origin urls in certain places * remove unused imports * badge icon size * Revert LM policy changes * Use SnapAvatar for snaps-connect * Use InstallError for connection failed * Delete unused CSS file * Remove unused CSS * Use useOriginMetadata * addressed PR comments * fix linting errors * add explicit condition * fix fencing * fix some more fencing * fix util fencing issue * fix storybook file, prevent null destructuring * Fix storybook origin URLs * Fix wrong prop name --------- Co-authored-by: Frederik Bolding <frederik.bolding@gmail.com> Co-authored-by: Guillaume Roux <guillaumeroux123@gmail.com> Co-authored-by: Erik Nilsson <eriks@mail.se>
This commit is contained in:
parent
95d57b254f
commit
ff36e32fb0
22
app/_locales/en/messages.json
generated
22
app/_locales/en/messages.json
generated
@ -739,6 +739,10 @@
|
|||||||
"connectManually": {
|
"connectManually": {
|
||||||
"message": "Manually connect to current site"
|
"message": "Manually connect to current site"
|
||||||
},
|
},
|
||||||
|
"connectSnap": {
|
||||||
|
"message": "Connect $1",
|
||||||
|
"description": "$1 is the snap for which a connection is being requested."
|
||||||
|
},
|
||||||
"connectTo": {
|
"connectTo": {
|
||||||
"message": "Connect to $1",
|
"message": "Connect to $1",
|
||||||
"description": "$1 is the name/origin of a web3 site/application that the user can connect to metamask"
|
"description": "$1 is the name/origin of a web3 site/application that the user can connect to metamask"
|
||||||
@ -804,6 +808,16 @@
|
|||||||
"connectionError": {
|
"connectionError": {
|
||||||
"message": "Connection error"
|
"message": "Connection error"
|
||||||
},
|
},
|
||||||
|
"connectionFailed": {
|
||||||
|
"message": "Connection failed"
|
||||||
|
},
|
||||||
|
"connectionFailedDescription": {
|
||||||
|
"message": "Fetching of $1 failed, check your network and try again.",
|
||||||
|
"description": "$1 is the name of the snap being fetched."
|
||||||
|
},
|
||||||
|
"connectionRequest": {
|
||||||
|
"message": "Connection request"
|
||||||
|
},
|
||||||
"contactUs": {
|
"contactUs": {
|
||||||
"message": "Contact us"
|
"message": "Contact us"
|
||||||
},
|
},
|
||||||
@ -2265,6 +2279,10 @@
|
|||||||
"moreComingSoon": {
|
"moreComingSoon": {
|
||||||
"message": "More coming soon..."
|
"message": "More coming soon..."
|
||||||
},
|
},
|
||||||
|
"multipleSnapConnectionWarning": {
|
||||||
|
"message": "$1 wants to connect with $2 snaps. Only proceed if you trust this website.",
|
||||||
|
"description": "$1 is the dapp and $2 is the number of snaps it wants to connect to."
|
||||||
|
},
|
||||||
"mustSelectOne": {
|
"mustSelectOne": {
|
||||||
"message": "Must select at least 1 token."
|
"message": "Must select at least 1 token."
|
||||||
},
|
},
|
||||||
@ -3764,6 +3782,10 @@
|
|||||||
"smartTransaction": {
|
"smartTransaction": {
|
||||||
"message": "Smart transaction"
|
"message": "Smart transaction"
|
||||||
},
|
},
|
||||||
|
"snapConnectionWarning": {
|
||||||
|
"message": "$1 wants to connect to $2. Only continue if you trust this website.",
|
||||||
|
"description": "$2 is the snap and $1 is the dapp requesting connection to the snap."
|
||||||
|
},
|
||||||
"snapContent": {
|
"snapContent": {
|
||||||
"message": "This content is coming from $1",
|
"message": "This content is coming from $1",
|
||||||
"description": "This is shown when a snap shows transaction insight information in the confirmation UI. $1 is a link to the snap's settings page with the link text being the name of the snap."
|
"description": "This is shown when a snap shows transaction insight information in the confirmation UI. $1 is a link to the snap's settings page with the link text being the name of the snap."
|
||||||
|
@ -2,7 +2,6 @@ import PropTypes from 'prop-types';
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { isEqual } from 'lodash';
|
import { isEqual } from 'lodash';
|
||||||
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
||||||
import { isObject } from '@metamask/utils';
|
|
||||||
import {
|
import {
|
||||||
SnapCaveatType,
|
SnapCaveatType,
|
||||||
WALLET_SNAP_PERMISSION_KEY,
|
WALLET_SNAP_PERMISSION_KEY,
|
||||||
@ -14,6 +13,7 @@ import PermissionsConnectFooter from '../permissions-connect-footer';
|
|||||||
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
||||||
import { RestrictedMethods } from '../../../../shared/constants/permissions';
|
import { RestrictedMethods } from '../../../../shared/constants/permissions';
|
||||||
import SnapPrivacyWarning from '../snaps/snap-privacy-warning';
|
import SnapPrivacyWarning from '../snaps/snap-privacy-warning';
|
||||||
|
import { getDedupedSnaps } from '../../../helpers/utils/util';
|
||||||
///: END:ONLY_INCLUDE_IN
|
///: END:ONLY_INCLUDE_IN
|
||||||
import { PermissionPageContainerContent } from '.';
|
import { PermissionPageContainerContent } from '.';
|
||||||
|
|
||||||
@ -86,29 +86,20 @@ export default class PermissionPageContainer extends Component {
|
|||||||
|
|
||||||
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
||||||
getDedupedSnapPermissions() {
|
getDedupedSnapPermissions() {
|
||||||
const permission =
|
const { request, currentPermissions } = this.props;
|
||||||
this.props.request.permissions[WALLET_SNAP_PERMISSION_KEY];
|
const snapKeys = getDedupedSnaps(request, currentPermissions);
|
||||||
const requestedSnaps = permission?.caveats[0].value;
|
const permission = request?.permissions?.[WALLET_SNAP_PERMISSION_KEY] || {};
|
||||||
const currentSnaps =
|
|
||||||
this.props.currentPermissions[WALLET_SNAP_PERMISSION_KEY]?.caveats[0]
|
|
||||||
.value;
|
|
||||||
|
|
||||||
if (!isObject(currentSnaps)) {
|
|
||||||
return permission;
|
|
||||||
}
|
|
||||||
|
|
||||||
const requestedSnapKeys = requestedSnaps ? Object.keys(requestedSnaps) : [];
|
|
||||||
const currentSnapKeys = currentSnaps ? Object.keys(currentSnaps) : [];
|
|
||||||
const dedupedCaveats = requestedSnapKeys.reduce((acc, snapId) => {
|
|
||||||
if (!currentSnapKeys.includes(snapId)) {
|
|
||||||
acc[snapId] = {};
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...permission,
|
...permission,
|
||||||
caveats: [{ type: SnapCaveatType.SnapIds, value: dedupedCaveats }],
|
caveats: [
|
||||||
|
{
|
||||||
|
type: SnapCaveatType.SnapIds,
|
||||||
|
value: snapKeys.reduce((caveatValue, snapId) => {
|
||||||
|
caveatValue[snapId] = {};
|
||||||
|
return caveatValue;
|
||||||
|
}, {}),
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,16 +40,18 @@ const InstallError = ({ title, error, description, iconName }) => {
|
|||||||
{title}
|
{title}
|
||||||
</Text>
|
</Text>
|
||||||
{description && <Text textAlign={TextAlign.Center}>{description}</Text>}
|
{description && <Text textAlign={TextAlign.Center}>{description}</Text>}
|
||||||
<Box padding={2}>
|
{error && (
|
||||||
<ActionableMessage type="danger" message={error} />
|
<Box padding={2}>
|
||||||
</Box>
|
<ActionableMessage type="danger" message={error} />
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
InstallError.propTypes = {
|
InstallError.propTypes = {
|
||||||
title: PropTypes.node.isRequired,
|
title: PropTypes.node.isRequired,
|
||||||
error: PropTypes.string.isRequired,
|
error: PropTypes.string,
|
||||||
description: PropTypes.string,
|
description: PropTypes.string,
|
||||||
iconName: PropTypes.string,
|
iconName: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
@ -8,7 +8,6 @@ import {
|
|||||||
AlignItems,
|
AlignItems,
|
||||||
DISPLAY,
|
DISPLAY,
|
||||||
JustifyContent,
|
JustifyContent,
|
||||||
Size,
|
|
||||||
BackgroundColor,
|
BackgroundColor,
|
||||||
} from '../../../../helpers/constants/design-system';
|
} from '../../../../helpers/constants/design-system';
|
||||||
import { getSnapName } from '../../../../helpers/utils/util';
|
import { getSnapName } from '../../../../helpers/utils/util';
|
||||||
@ -23,7 +22,13 @@ import {
|
|||||||
} from '../../../component-library';
|
} from '../../../component-library';
|
||||||
import { getTargetSubjectMetadata } from '../../../../selectors';
|
import { getTargetSubjectMetadata } from '../../../../selectors';
|
||||||
|
|
||||||
const SnapAvatar = ({ snapId, className }) => {
|
const SnapAvatar = ({
|
||||||
|
snapId,
|
||||||
|
badgeSize = IconSize.Sm,
|
||||||
|
avatarSize = IconSize.Lg,
|
||||||
|
borderWidth = 2,
|
||||||
|
className,
|
||||||
|
}) => {
|
||||||
const subjectMetadata = useSelector((state) =>
|
const subjectMetadata = useSelector((state) =>
|
||||||
getTargetSubjectMetadata(state, snapId),
|
getTargetSubjectMetadata(state, snapId),
|
||||||
);
|
);
|
||||||
@ -40,12 +45,12 @@ const SnapAvatar = ({ snapId, className }) => {
|
|||||||
badge={
|
badge={
|
||||||
<AvatarIcon
|
<AvatarIcon
|
||||||
iconName={IconName.Snaps}
|
iconName={IconName.Snaps}
|
||||||
size={IconSize.Sm}
|
size={badgeSize}
|
||||||
backgroundColor={IconColor.infoDefault}
|
backgroundColor={IconColor.infoDefault}
|
||||||
borderColor={BackgroundColor.backgroundDefault}
|
borderColor={BackgroundColor.backgroundDefault}
|
||||||
borderWidth={2}
|
borderWidth={borderWidth}
|
||||||
iconProps={{
|
iconProps={{
|
||||||
size: IconSize.Sm,
|
size: badgeSize,
|
||||||
color: IconColor.infoInverse,
|
color: IconColor.infoInverse,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -53,10 +58,10 @@ const SnapAvatar = ({ snapId, className }) => {
|
|||||||
position={BadgeWrapperPosition.bottomRight}
|
position={BadgeWrapperPosition.bottomRight}
|
||||||
>
|
>
|
||||||
{iconUrl ? (
|
{iconUrl ? (
|
||||||
<AvatarFavicon size={Size.LG} src={iconUrl} name={friendlyName} />
|
<AvatarFavicon size={avatarSize} src={iconUrl} name={friendlyName} />
|
||||||
) : (
|
) : (
|
||||||
<AvatarBase
|
<AvatarBase
|
||||||
size={Size.LG}
|
size={avatarSize}
|
||||||
display={DISPLAY.FLEX}
|
display={DISPLAY.FLEX}
|
||||||
alignItems={AlignItems.center}
|
alignItems={AlignItems.center}
|
||||||
justifyContent={JustifyContent.center}
|
justifyContent={JustifyContent.center}
|
||||||
@ -75,6 +80,9 @@ SnapAvatar.propTypes = {
|
|||||||
* The id of the snap
|
* The id of the snap
|
||||||
*/
|
*/
|
||||||
snapId: PropTypes.string,
|
snapId: PropTypes.string,
|
||||||
|
badgeSize: PropTypes.string,
|
||||||
|
avatarSize: PropTypes.string,
|
||||||
|
borderWidth: PropTypes.number,
|
||||||
/**
|
/**
|
||||||
* The className of the SnapAvatar
|
* The className of the SnapAvatar
|
||||||
*/
|
*/
|
||||||
|
1
ui/components/app/snaps/snap-connect-cell/index.js
Normal file
1
ui/components/app/snaps/snap-connect-cell/index.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default } from './snap-connect-cell';
|
@ -0,0 +1,74 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import Box from '../../../ui/box';
|
||||||
|
import {
|
||||||
|
IconColor,
|
||||||
|
AlignItems,
|
||||||
|
Display,
|
||||||
|
FontWeight,
|
||||||
|
} from '../../../../helpers/constants/design-system';
|
||||||
|
import { getSnapName } from '../../../../helpers/utils/util';
|
||||||
|
import {
|
||||||
|
Icon,
|
||||||
|
IconName,
|
||||||
|
IconSize,
|
||||||
|
Text,
|
||||||
|
ValidTag,
|
||||||
|
} from '../../../component-library';
|
||||||
|
import Tooltip from '../../../ui/tooltip/tooltip';
|
||||||
|
import { useI18nContext } from '../../../../hooks/useI18nContext';
|
||||||
|
import SnapAvatar from '../snap-avatar/snap-avatar';
|
||||||
|
import { getTargetSubjectMetadata } from '../../../../selectors';
|
||||||
|
|
||||||
|
export default function SnapConnectCell({ origin, snapId }) {
|
||||||
|
const t = useI18nContext();
|
||||||
|
const snapMetadata = useSelector((state) =>
|
||||||
|
getTargetSubjectMetadata(state, snapId),
|
||||||
|
);
|
||||||
|
const friendlyName = getSnapName(snapId, snapMetadata);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
alignItems={AlignItems.center}
|
||||||
|
paddingTop={2}
|
||||||
|
paddingBottom={2}
|
||||||
|
display={Display.Flex}
|
||||||
|
>
|
||||||
|
<SnapAvatar snapId={snapId} />
|
||||||
|
<Box width="full" marginLeft={4} marginRight={4}>
|
||||||
|
<Text>
|
||||||
|
{t('connectSnap', [
|
||||||
|
<Text as={ValidTag.Span} key="1" fontWeight={FontWeight.Bold}>
|
||||||
|
{friendlyName}
|
||||||
|
</Text>,
|
||||||
|
])}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Tooltip
|
||||||
|
html={
|
||||||
|
<div>
|
||||||
|
{t('snapConnectionWarning', [
|
||||||
|
<b key="0">{origin}</b>,
|
||||||
|
<b key="1">{friendlyName}</b>,
|
||||||
|
])}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
position="bottom"
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
color={IconColor.iconMuted}
|
||||||
|
name={IconName.Info}
|
||||||
|
size={IconSize.Sm}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
SnapConnectCell.propTypes = {
|
||||||
|
origin: PropTypes.string.isRequired,
|
||||||
|
snapId: PropTypes.string.isRequired,
|
||||||
|
};
|
@ -0,0 +1,16 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import SnapConnectCell from '.';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'Components/App/Snaps/SnapConnectCell',
|
||||||
|
component: SnapConnectCell,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DefaultStory = (args) => <SnapConnectCell {...args} />;
|
||||||
|
|
||||||
|
DefaultStory.storyName = 'Default';
|
||||||
|
|
||||||
|
DefaultStory.args = {
|
||||||
|
origin: 'aave.com',
|
||||||
|
snapId: 'npm:@metamask/example-snap',
|
||||||
|
};
|
@ -44,6 +44,7 @@ const TOKEN_DETAILS = '/token-details';
|
|||||||
const CONNECT_ROUTE = '/connect';
|
const CONNECT_ROUTE = '/connect';
|
||||||
const CONNECT_CONFIRM_PERMISSIONS_ROUTE = '/confirm-permissions';
|
const CONNECT_CONFIRM_PERMISSIONS_ROUTE = '/confirm-permissions';
|
||||||
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
||||||
|
const CONNECT_SNAPS_CONNECT_ROUTE = '/snaps-connect';
|
||||||
const CONNECT_SNAP_INSTALL_ROUTE = '/snap-install';
|
const CONNECT_SNAP_INSTALL_ROUTE = '/snap-install';
|
||||||
const CONNECT_SNAP_UPDATE_ROUTE = '/snap-update';
|
const CONNECT_SNAP_UPDATE_ROUTE = '/snap-update';
|
||||||
const CONNECT_SNAP_RESULT_ROUTE = '/snap-install-result';
|
const CONNECT_SNAP_RESULT_ROUTE = '/snap-install-result';
|
||||||
@ -238,6 +239,7 @@ export {
|
|||||||
CONNECT_ROUTE,
|
CONNECT_ROUTE,
|
||||||
CONNECT_CONFIRM_PERMISSIONS_ROUTE,
|
CONNECT_CONFIRM_PERMISSIONS_ROUTE,
|
||||||
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
||||||
|
CONNECT_SNAPS_CONNECT_ROUTE,
|
||||||
CONNECT_SNAP_INSTALL_ROUTE,
|
CONNECT_SNAP_INSTALL_ROUTE,
|
||||||
CONNECT_SNAP_UPDATE_ROUTE,
|
CONNECT_SNAP_UPDATE_ROUTE,
|
||||||
CONNECT_SNAP_RESULT_ROUTE,
|
CONNECT_SNAP_RESULT_ROUTE,
|
||||||
|
@ -9,6 +9,8 @@ import * as lodash from 'lodash';
|
|||||||
import bowser from 'bowser';
|
import bowser from 'bowser';
|
||||||
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
||||||
import { getSnapPrefix } from '@metamask/snaps-utils';
|
import { getSnapPrefix } from '@metamask/snaps-utils';
|
||||||
|
import { WALLET_SNAP_PERMISSION_KEY } from '@metamask/rpc-methods';
|
||||||
|
import { isObject } from '@metamask/utils';
|
||||||
///: END:ONLY_INCLUDE_IN
|
///: END:ONLY_INCLUDE_IN
|
||||||
import { CHAIN_IDS, NETWORK_TYPES } from '../../../shared/constants/network';
|
import { CHAIN_IDS, NETWORK_TYPES } from '../../../shared/constants/network';
|
||||||
import {
|
import {
|
||||||
@ -566,6 +568,25 @@ export const getSnapName = (snapId, subjectMetadata) => {
|
|||||||
return subjectMetadata?.name ?? removeSnapIdPrefix(snapId);
|
return subjectMetadata?.name ?? removeSnapIdPrefix(snapId);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getDedupedSnaps = (request, permissions) => {
|
||||||
|
const permission = request?.permissions?.[WALLET_SNAP_PERMISSION_KEY];
|
||||||
|
const requestedSnaps = permission?.caveats[0].value;
|
||||||
|
const currentSnaps =
|
||||||
|
permissions?.[WALLET_SNAP_PERMISSION_KEY]?.caveats[0].value;
|
||||||
|
|
||||||
|
if (!isObject(currentSnaps) && requestedSnaps) {
|
||||||
|
return Object.keys(requestedSnaps);
|
||||||
|
}
|
||||||
|
|
||||||
|
const requestedSnapKeys = requestedSnaps ? Object.keys(requestedSnaps) : [];
|
||||||
|
const currentSnapKeys = currentSnaps ? Object.keys(currentSnaps) : [];
|
||||||
|
const dedupedSnaps = requestedSnapKeys.filter(
|
||||||
|
(snapId) => !currentSnapKeys.includes(snapId),
|
||||||
|
);
|
||||||
|
|
||||||
|
return dedupedSnaps.length > 0 ? dedupedSnaps : requestedSnapKeys;
|
||||||
|
};
|
||||||
|
|
||||||
///: END:ONLY_INCLUDE_IN
|
///: END:ONLY_INCLUDE_IN
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
@import 'snaps/snap-install/index';
|
@import 'snaps/snap-install/index';
|
||||||
@import 'snaps/snap-update/index';
|
@import 'snaps/snap-update/index';
|
||||||
@import 'snaps/snap-result/index';
|
@import 'snaps/snap-result/index';
|
||||||
|
@import 'snaps/snaps-connect/index';
|
||||||
@import 'redirect/index';
|
@import 'redirect/index';
|
||||||
|
|
||||||
.permissions-connect {
|
.permissions-connect {
|
||||||
|
@ -13,6 +13,7 @@ import { Icon, IconName, IconSize } from '../../components/component-library';
|
|||||||
import ChooseAccount from './choose-account';
|
import ChooseAccount from './choose-account';
|
||||||
import PermissionsRedirect from './redirect';
|
import PermissionsRedirect from './redirect';
|
||||||
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
||||||
|
import SnapsConnect from './snaps/snaps-connect';
|
||||||
import SnapInstall from './snaps/snap-install';
|
import SnapInstall from './snaps/snap-install';
|
||||||
import SnapUpdate from './snaps/snap-update';
|
import SnapUpdate from './snaps/snap-update';
|
||||||
import SnapResult from './snaps/snap-result';
|
import SnapResult from './snaps/snap-result';
|
||||||
@ -40,6 +41,7 @@ export default class PermissionConnect extends Component {
|
|||||||
confirmPermissionPath: PropTypes.string.isRequired,
|
confirmPermissionPath: PropTypes.string.isRequired,
|
||||||
requestType: PropTypes.string.isRequired,
|
requestType: PropTypes.string.isRequired,
|
||||||
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
||||||
|
snapsConnectPath: PropTypes.string.isRequired,
|
||||||
snapInstallPath: PropTypes.string.isRequired,
|
snapInstallPath: PropTypes.string.isRequired,
|
||||||
snapUpdatePath: PropTypes.string.isRequired,
|
snapUpdatePath: PropTypes.string.isRequired,
|
||||||
snapResultPath: PropTypes.string.isRequired,
|
snapResultPath: PropTypes.string.isRequired,
|
||||||
@ -105,6 +107,7 @@ export default class PermissionConnect extends Component {
|
|||||||
connectPath,
|
connectPath,
|
||||||
confirmPermissionPath,
|
confirmPermissionPath,
|
||||||
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
||||||
|
snapsConnectPath,
|
||||||
snapInstallPath,
|
snapInstallPath,
|
||||||
snapUpdatePath,
|
snapUpdatePath,
|
||||||
snapResultPath,
|
snapResultPath,
|
||||||
@ -140,6 +143,9 @@ export default class PermissionConnect extends Component {
|
|||||||
case 'wallet_installSnapResult':
|
case 'wallet_installSnapResult':
|
||||||
history.replace(snapResultPath);
|
history.replace(snapResultPath);
|
||||||
break;
|
break;
|
||||||
|
case 'wallet_connectSnaps':
|
||||||
|
history.replace(snapsConnectPath);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
///: END:ONLY_INCLUDE_IN
|
///: END:ONLY_INCLUDE_IN
|
||||||
history.replace(confirmPermissionPath);
|
history.replace(confirmPermissionPath);
|
||||||
@ -183,6 +189,7 @@ export default class PermissionConnect extends Component {
|
|||||||
confirmPermissionPath,
|
confirmPermissionPath,
|
||||||
requestType,
|
requestType,
|
||||||
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
||||||
|
snapsConnectPath,
|
||||||
snapInstallPath,
|
snapInstallPath,
|
||||||
snapUpdatePath,
|
snapUpdatePath,
|
||||||
snapResultPath,
|
snapResultPath,
|
||||||
@ -204,6 +211,9 @@ export default class PermissionConnect extends Component {
|
|||||||
case 'wallet_installSnapResult':
|
case 'wallet_installSnapResult':
|
||||||
this.props.history.push(snapResultPath);
|
this.props.history.push(snapResultPath);
|
||||||
break;
|
break;
|
||||||
|
case 'wallet_connectSnaps':
|
||||||
|
this.props.history.replace(snapsConnectPath);
|
||||||
|
break;
|
||||||
///: END:ONLY_INCLUDE_IN
|
///: END:ONLY_INCLUDE_IN
|
||||||
default:
|
default:
|
||||||
this.props.history.push(confirmPermissionPath);
|
this.props.history.push(confirmPermissionPath);
|
||||||
@ -299,6 +309,7 @@ export default class PermissionConnect extends Component {
|
|||||||
confirmPermissionPath,
|
confirmPermissionPath,
|
||||||
hideTopBar,
|
hideTopBar,
|
||||||
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
||||||
|
snapsConnectPath,
|
||||||
snapInstallPath,
|
snapInstallPath,
|
||||||
snapUpdatePath,
|
snapUpdatePath,
|
||||||
snapResultPath,
|
snapResultPath,
|
||||||
@ -381,6 +392,35 @@ export default class PermissionConnect extends Component {
|
|||||||
{
|
{
|
||||||
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
||||||
}
|
}
|
||||||
|
<Route
|
||||||
|
path={snapsConnectPath}
|
||||||
|
exact
|
||||||
|
render={() => (
|
||||||
|
<SnapsConnect
|
||||||
|
request={permissionsRequest || {}}
|
||||||
|
approveConnection={(...args) => {
|
||||||
|
approvePermissionsRequest(...args);
|
||||||
|
this.redirect(true);
|
||||||
|
}}
|
||||||
|
rejectConnection={(requestId) =>
|
||||||
|
this.cancelPermissionsRequest(requestId)
|
||||||
|
}
|
||||||
|
targetSubjectMetadata={targetSubjectMetadata}
|
||||||
|
snapsInstallPrivacyWarningShown={
|
||||||
|
snapsInstallPrivacyWarningShown
|
||||||
|
}
|
||||||
|
setSnapsInstallPrivacyWarningShownStatus={
|
||||||
|
setSnapsInstallPrivacyWarningShownStatus
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{
|
||||||
|
///: END:ONLY_INCLUDE_IN
|
||||||
|
}
|
||||||
|
{
|
||||||
|
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
||||||
|
}
|
||||||
<Route
|
<Route
|
||||||
path={snapInstallPath}
|
path={snapInstallPath}
|
||||||
exact
|
exact
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
import { SubjectType } from '@metamask/subject-metadata-controller';
|
import { SubjectType } from '@metamask/subject-metadata-controller';
|
||||||
|
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
||||||
|
import { WALLET_SNAP_PERMISSION_KEY } from '@metamask/rpc-methods';
|
||||||
|
///: END:ONLY_INCLUDE_IN
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import {
|
import {
|
||||||
@ -32,6 +35,7 @@ import {
|
|||||||
CONNECT_ROUTE,
|
CONNECT_ROUTE,
|
||||||
CONNECT_CONFIRM_PERMISSIONS_ROUTE,
|
CONNECT_CONFIRM_PERMISSIONS_ROUTE,
|
||||||
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
||||||
|
CONNECT_SNAPS_CONNECT_ROUTE,
|
||||||
CONNECT_SNAP_INSTALL_ROUTE,
|
CONNECT_SNAP_INSTALL_ROUTE,
|
||||||
CONNECT_SNAP_UPDATE_ROUTE,
|
CONNECT_SNAP_UPDATE_ROUTE,
|
||||||
CONNECT_SNAP_RESULT_ROUTE,
|
CONNECT_SNAP_RESULT_ROUTE,
|
||||||
@ -75,10 +79,21 @@ const mapStateToProps = (state, ownProps) => {
|
|||||||
subjectType: SubjectType.Unknown,
|
subjectType: SubjectType.Unknown,
|
||||||
};
|
};
|
||||||
|
|
||||||
const requestType = getRequestType(state, permissionsRequestId);
|
let requestType = getRequestType(state, permissionsRequestId);
|
||||||
|
|
||||||
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
||||||
const requestState = getRequestState(state, permissionsRequestId);
|
// We want to only assign the wallet_connectSnaps request type (i.e. only show
|
||||||
|
// SnapsConnect) if and only if we get a singular wallet_snap permission request.
|
||||||
|
// Any other request gets pushed to the normal permission connect flow.
|
||||||
|
if (
|
||||||
|
permissionsRequest &&
|
||||||
|
Object.keys(permissionsRequest.permissions || {}).length === 1 &&
|
||||||
|
permissionsRequest.permissions?.[WALLET_SNAP_PERMISSION_KEY]
|
||||||
|
) {
|
||||||
|
requestType = 'wallet_connectSnaps';
|
||||||
|
}
|
||||||
|
|
||||||
|
const requestState = getRequestState(state, permissionsRequestId) || {};
|
||||||
///: END:ONLY_INCLUDE_IN
|
///: END:ONLY_INCLUDE_IN
|
||||||
|
|
||||||
const accountsWithLabels = getAccountsWithLabels(state);
|
const accountsWithLabels = getAccountsWithLabels(state);
|
||||||
@ -96,6 +111,7 @@ const mapStateToProps = (state, ownProps) => {
|
|||||||
const connectPath = `${CONNECT_ROUTE}/${permissionsRequestId}`;
|
const connectPath = `${CONNECT_ROUTE}/${permissionsRequestId}`;
|
||||||
const confirmPermissionPath = `${CONNECT_ROUTE}/${permissionsRequestId}${CONNECT_CONFIRM_PERMISSIONS_ROUTE}`;
|
const confirmPermissionPath = `${CONNECT_ROUTE}/${permissionsRequestId}${CONNECT_CONFIRM_PERMISSIONS_ROUTE}`;
|
||||||
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
||||||
|
const snapsConnectPath = `${CONNECT_ROUTE}/${permissionsRequestId}${CONNECT_SNAPS_CONNECT_ROUTE}`;
|
||||||
const snapInstallPath = `${CONNECT_ROUTE}/${permissionsRequestId}${CONNECT_SNAP_INSTALL_ROUTE}`;
|
const snapInstallPath = `${CONNECT_ROUTE}/${permissionsRequestId}${CONNECT_SNAP_INSTALL_ROUTE}`;
|
||||||
const snapUpdatePath = `${CONNECT_ROUTE}/${permissionsRequestId}${CONNECT_SNAP_UPDATE_ROUTE}`;
|
const snapUpdatePath = `${CONNECT_ROUTE}/${permissionsRequestId}${CONNECT_SNAP_UPDATE_ROUTE}`;
|
||||||
const snapResultPath = `${CONNECT_ROUTE}/${permissionsRequestId}${CONNECT_SNAP_RESULT_ROUTE}`;
|
const snapResultPath = `${CONNECT_ROUTE}/${permissionsRequestId}${CONNECT_SNAP_RESULT_ROUTE}`;
|
||||||
@ -119,6 +135,8 @@ const mapStateToProps = (state, ownProps) => {
|
|||||||
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
||||||
} else if (isSnapInstallOrUpdateOrResult) {
|
} else if (isSnapInstallOrUpdateOrResult) {
|
||||||
page = isRequestingAccounts ? '3' : '2';
|
page = isRequestingAccounts ? '3' : '2';
|
||||||
|
} else if (pathname === snapsConnectPath) {
|
||||||
|
page = 1;
|
||||||
///: END:ONLY_INCLUDE_IN
|
///: END:ONLY_INCLUDE_IN
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Incorrect path for permissions-connect component');
|
throw new Error('Incorrect path for permissions-connect component');
|
||||||
@ -128,6 +146,7 @@ const mapStateToProps = (state, ownProps) => {
|
|||||||
isRequestingAccounts,
|
isRequestingAccounts,
|
||||||
requestType,
|
requestType,
|
||||||
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
||||||
|
snapsConnectPath,
|
||||||
snapInstallPath,
|
snapInstallPath,
|
||||||
snapUpdatePath,
|
snapUpdatePath,
|
||||||
snapResultPath,
|
snapResultPath,
|
||||||
|
@ -18,7 +18,6 @@ import {
|
|||||||
} from '../../../../helpers/constants/design-system';
|
} from '../../../../helpers/constants/design-system';
|
||||||
import { getSnapInstallWarnings } from '../util';
|
import { getSnapInstallWarnings } from '../util';
|
||||||
import PulseLoader from '../../../../components/ui/pulse-loader/pulse-loader';
|
import PulseLoader from '../../../../components/ui/pulse-loader/pulse-loader';
|
||||||
import InstallError from '../../../../components/app/snaps/install-error/install-error';
|
|
||||||
import SnapAuthorshipHeader from '../../../../components/app/snaps/snap-authorship-header';
|
import SnapAuthorshipHeader from '../../../../components/app/snaps/snap-authorship-header';
|
||||||
import {
|
import {
|
||||||
AvatarIcon,
|
AvatarIcon,
|
||||||
@ -29,6 +28,9 @@ import {
|
|||||||
import { getSnapName } from '../../../../helpers/utils/util';
|
import { getSnapName } from '../../../../helpers/utils/util';
|
||||||
import SnapPermissionsList from '../../../../components/app/snaps/snap-permissions-list';
|
import SnapPermissionsList from '../../../../components/app/snaps/snap-permissions-list';
|
||||||
import { useScrollRequired } from '../../../../hooks/useScrollRequired';
|
import { useScrollRequired } from '../../../../hooks/useScrollRequired';
|
||||||
|
import SiteOrigin from '../../../../components/ui/site-origin/site-origin';
|
||||||
|
import InstallError from '../../../../components/app/snaps/install-error/install-error';
|
||||||
|
import { useOriginMetadata } from '../../../../hooks/useOriginMetadata';
|
||||||
|
|
||||||
export default function SnapInstall({
|
export default function SnapInstall({
|
||||||
request,
|
request,
|
||||||
@ -38,7 +40,8 @@ export default function SnapInstall({
|
|||||||
targetSubjectMetadata,
|
targetSubjectMetadata,
|
||||||
}) {
|
}) {
|
||||||
const t = useI18nContext();
|
const t = useI18nContext();
|
||||||
|
const siteMetadata = useOriginMetadata(request?.metadata?.dappOrigin) || {};
|
||||||
|
const { origin, iconUrl, name } = siteMetadata;
|
||||||
const [isShowingWarning, setIsShowingWarning] = useState(false);
|
const [isShowingWarning, setIsShowingWarning] = useState(false);
|
||||||
|
|
||||||
const { isScrollable, isScrolledToBottom, scrollToBottom, ref, onScroll } =
|
const { isScrollable, isScrolledToBottom, scrollToBottom, ref, onScroll } =
|
||||||
@ -78,6 +81,15 @@ export default function SnapInstall({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getFooterMessage = () => {
|
||||||
|
if (hasError) {
|
||||||
|
return 'ok';
|
||||||
|
} else if (isLoading) {
|
||||||
|
return 'connect';
|
||||||
|
}
|
||||||
|
return 'install';
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
className="page-container snap-install"
|
className="page-container snap-install"
|
||||||
@ -86,14 +98,31 @@ export default function SnapInstall({
|
|||||||
borderStyle={BorderStyle.none}
|
borderStyle={BorderStyle.none}
|
||||||
flexDirection={FLEX_DIRECTION.COLUMN}
|
flexDirection={FLEX_DIRECTION.COLUMN}
|
||||||
>
|
>
|
||||||
<SnapAuthorshipHeader snapId={targetSubjectMetadata.origin} />
|
{isLoading || hasError ? (
|
||||||
|
<Box
|
||||||
|
width="full"
|
||||||
|
alignItems={AlignItems.center}
|
||||||
|
justifyContent={JustifyContent.center}
|
||||||
|
paddingTop={4}
|
||||||
|
>
|
||||||
|
<SiteOrigin
|
||||||
|
chip
|
||||||
|
siteOrigin={origin}
|
||||||
|
title={origin}
|
||||||
|
iconSrc={iconUrl}
|
||||||
|
iconName={name}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<SnapAuthorshipHeader snapId={targetSubjectMetadata.origin} />
|
||||||
|
)}
|
||||||
<Box
|
<Box
|
||||||
ref={ref}
|
ref={ref}
|
||||||
onScroll={onScroll}
|
onScroll={onScroll}
|
||||||
className="snap-install__content"
|
className="snap-install__content"
|
||||||
style={{
|
style={{
|
||||||
overflowY: 'auto',
|
overflowY: 'auto',
|
||||||
flex: !isLoading && '1',
|
flex: !isLoading && !hasError && '1',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{isLoading && (
|
{isLoading && (
|
||||||
@ -107,7 +136,16 @@ export default function SnapInstall({
|
|||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
{hasError && (
|
{hasError && (
|
||||||
<InstallError error={requestState.error} title={t('requestFailed')} />
|
<InstallError
|
||||||
|
iconName={IconName.Warning}
|
||||||
|
title={t('connectionFailed')}
|
||||||
|
description={t('connectionFailedDescription', [
|
||||||
|
<Text as={ValidTag.Span} key="1" fontWeight={FontWeight.Medium}>
|
||||||
|
{snapName}
|
||||||
|
</Text>,
|
||||||
|
])}
|
||||||
|
error={requestState.error}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
{!hasError && !isLoading && (
|
{!hasError && !isLoading && (
|
||||||
<>
|
<>
|
||||||
@ -172,7 +210,7 @@ export default function SnapInstall({
|
|||||||
onCancel={onCancel}
|
onCancel={onCancel}
|
||||||
cancelText={t('cancel')}
|
cancelText={t('cancel')}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
submitText={t(hasError ? 'ok' : 'install')}
|
submitText={t(getFooterMessage())}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
{isShowingWarning && (
|
{isShowingWarning && (
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
export { default } from './snaps-connect';
|
@ -0,0 +1,9 @@
|
|||||||
|
.snaps-connect {
|
||||||
|
overflow-y: hidden;
|
||||||
|
|
||||||
|
.page-container__footer {
|
||||||
|
border-top: 0;
|
||||||
|
margin-top: auto;
|
||||||
|
box-shadow: var(--shadow-size-lg) var(--color-shadow-default);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,251 @@
|
|||||||
|
import React, { useCallback, useState } from 'react';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { useI18nContext } from '../../../../hooks/useI18nContext';
|
||||||
|
import Box from '../../../../components/ui/box';
|
||||||
|
import SiteOrigin from '../../../../components/ui/site-origin';
|
||||||
|
import {
|
||||||
|
IconSize,
|
||||||
|
Text,
|
||||||
|
ValidTag,
|
||||||
|
} from '../../../../components/component-library';
|
||||||
|
import {
|
||||||
|
FlexDirection,
|
||||||
|
TextVariant,
|
||||||
|
JustifyContent,
|
||||||
|
AlignItems,
|
||||||
|
TextAlign,
|
||||||
|
Display,
|
||||||
|
FontWeight,
|
||||||
|
BlockSize,
|
||||||
|
} from '../../../../helpers/constants/design-system';
|
||||||
|
import { PageContainerFooter } from '../../../../components/ui/page-container';
|
||||||
|
import SnapConnectCell from '../../../../components/app/snaps/snap-connect-cell/snap-connect-cell';
|
||||||
|
import { getDedupedSnaps, getSnapName } from '../../../../helpers/utils/util';
|
||||||
|
import PulseLoader from '../../../../components/ui/pulse-loader/pulse-loader';
|
||||||
|
import SnapPrivacyWarning from '../../../../components/app/snaps/snap-privacy-warning/snap-privacy-warning';
|
||||||
|
import {
|
||||||
|
getPermissions,
|
||||||
|
getTargetSubjectMetadata,
|
||||||
|
} from '../../../../selectors';
|
||||||
|
import SnapAvatar from '../../../../components/app/snaps/snap-avatar/snap-avatar';
|
||||||
|
import { useOriginMetadata } from '../../../../hooks/useOriginMetadata';
|
||||||
|
|
||||||
|
export default function SnapsConnect({
|
||||||
|
request,
|
||||||
|
approveConnection,
|
||||||
|
rejectConnection,
|
||||||
|
targetSubjectMetadata,
|
||||||
|
snapsInstallPrivacyWarningShown,
|
||||||
|
setSnapsInstallPrivacyWarningShownStatus,
|
||||||
|
}) {
|
||||||
|
const t = useI18nContext();
|
||||||
|
const { origin, iconUrl, name } = targetSubjectMetadata;
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [isShowingSnapsPrivacyWarning, setIsShowingSnapsPrivacyWarning] =
|
||||||
|
useState(!snapsInstallPrivacyWarningShown);
|
||||||
|
const currentPermissions = useSelector((state) =>
|
||||||
|
getPermissions(state, request?.metadata?.origin),
|
||||||
|
);
|
||||||
|
|
||||||
|
const onCancel = useCallback(() => {
|
||||||
|
rejectConnection(request.metadata.id);
|
||||||
|
}, [request, rejectConnection]);
|
||||||
|
|
||||||
|
const onConnect = useCallback(() => {
|
||||||
|
try {
|
||||||
|
setIsLoading(true);
|
||||||
|
approveConnection(request);
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
}, [request, approveConnection]);
|
||||||
|
|
||||||
|
const snaps = getDedupedSnaps(request, currentPermissions);
|
||||||
|
|
||||||
|
const singularConnectSnapMetadata = useSelector((state) =>
|
||||||
|
getTargetSubjectMetadata(state, snaps?.[0]),
|
||||||
|
);
|
||||||
|
|
||||||
|
const SnapsConnectContent = () => {
|
||||||
|
const { hostname: trimmedOrigin } = useOriginMetadata(origin) || {};
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
className="snap-connect__loader-container"
|
||||||
|
flexDirection={FlexDirection.Column}
|
||||||
|
alignItems={AlignItems.center}
|
||||||
|
justifyContent={JustifyContent.center}
|
||||||
|
>
|
||||||
|
<PulseLoader />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (snaps?.length > 1) {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
className="snaps-connect__content"
|
||||||
|
flexDirection={FlexDirection.Column}
|
||||||
|
justifyContent={JustifyContent.center}
|
||||||
|
alignItems={AlignItems.center}
|
||||||
|
paddingLeft={4}
|
||||||
|
paddingRight={4}
|
||||||
|
paddingTop={8}
|
||||||
|
width={BlockSize.Full}
|
||||||
|
style={{ overflowY: 'hidden' }}
|
||||||
|
>
|
||||||
|
<Text paddingBottom={2} variant={TextVariant.headingLg}>
|
||||||
|
{t('connectionRequest')}
|
||||||
|
</Text>
|
||||||
|
<Text variant={TextVariant.bodyMd} textAlign={TextAlign.Center}>
|
||||||
|
{t('multipleSnapConnectionWarning', [
|
||||||
|
<Text
|
||||||
|
as={ValidTag.Span}
|
||||||
|
key="1"
|
||||||
|
variant={TextVariant.bodyMd}
|
||||||
|
fontWeight={FontWeight.Medium}
|
||||||
|
>
|
||||||
|
{trimmedOrigin}
|
||||||
|
</Text>,
|
||||||
|
<Text
|
||||||
|
as={ValidTag.Span}
|
||||||
|
key="2"
|
||||||
|
variant={TextVariant.bodyMd}
|
||||||
|
fontWeight={FontWeight.Medium}
|
||||||
|
>
|
||||||
|
{snaps?.length}
|
||||||
|
</Text>,
|
||||||
|
])}
|
||||||
|
</Text>
|
||||||
|
<Box
|
||||||
|
className="snaps-connect__content__snaps-list"
|
||||||
|
flexDirection={FlexDirection.Column}
|
||||||
|
display={Display.Flex}
|
||||||
|
marginTop={4}
|
||||||
|
width={BlockSize.Full}
|
||||||
|
style={{ overflowY: 'auto', flex: 1 }}
|
||||||
|
>
|
||||||
|
{snaps.map((snap) => (
|
||||||
|
// TODO(hbmalik88): add in the iconUrl prop when we have access to a snap's icons pre-installation
|
||||||
|
<SnapConnectCell
|
||||||
|
key={`snaps-connect-${snap}`}
|
||||||
|
snapId={snap}
|
||||||
|
origin={trimmedOrigin}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
} else if (snaps?.length === 1) {
|
||||||
|
const snapId = snaps[0];
|
||||||
|
const snapName = getSnapName(snapId, singularConnectSnapMetadata);
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
className="snaps-connect__content"
|
||||||
|
flexDirection={FlexDirection.Column}
|
||||||
|
justifyContent={JustifyContent.center}
|
||||||
|
alignItems={AlignItems.center}
|
||||||
|
height={BlockSize.Full}
|
||||||
|
paddingLeft={4}
|
||||||
|
paddingRight={4}
|
||||||
|
>
|
||||||
|
<Box paddingBottom={2}>
|
||||||
|
<SnapAvatar
|
||||||
|
snapId={snaps[0]}
|
||||||
|
badgeSize={IconSize.Md}
|
||||||
|
avatarSize={IconSize.Xl}
|
||||||
|
borderWidth={3}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Text paddingBottom={2} variant={TextVariant.headingLg}>
|
||||||
|
{t('connectionRequest')}
|
||||||
|
</Text>
|
||||||
|
<Text
|
||||||
|
variant={TextVariant.bodyMd}
|
||||||
|
textAlign={TextAlign.Center}
|
||||||
|
padding={[0, 4]}
|
||||||
|
>
|
||||||
|
{t('snapConnectionWarning', [
|
||||||
|
<Text
|
||||||
|
as={ValidTag.Span}
|
||||||
|
key="1"
|
||||||
|
variant={TextVariant.bodyMd}
|
||||||
|
fontWeight={FontWeight.Medium}
|
||||||
|
>
|
||||||
|
{trimmedOrigin}
|
||||||
|
</Text>,
|
||||||
|
<Text
|
||||||
|
as={ValidTag.Span}
|
||||||
|
key="2"
|
||||||
|
variant={TextVariant.bodyMd}
|
||||||
|
fontWeight={FontWeight.Medium}
|
||||||
|
>
|
||||||
|
{snapName}
|
||||||
|
</Text>,
|
||||||
|
])}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
className="page-container snaps-connect"
|
||||||
|
flexDirection={FlexDirection.Column}
|
||||||
|
alignItems={AlignItems.center}
|
||||||
|
>
|
||||||
|
{isShowingSnapsPrivacyWarning && (
|
||||||
|
<SnapPrivacyWarning
|
||||||
|
onAccepted={() => {
|
||||||
|
setIsShowingSnapsPrivacyWarning(false);
|
||||||
|
setSnapsInstallPrivacyWarningShownStatus(true);
|
||||||
|
}}
|
||||||
|
onCanceled={onCancel}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Box
|
||||||
|
className="snaps-connect__header"
|
||||||
|
flexDirection={FlexDirection.Column}
|
||||||
|
alignItems={AlignItems.center}
|
||||||
|
paddingLeft={4}
|
||||||
|
paddingRight={4}
|
||||||
|
>
|
||||||
|
<SiteOrigin
|
||||||
|
chip
|
||||||
|
siteOrigin={origin}
|
||||||
|
title={origin}
|
||||||
|
iconSrc={iconUrl}
|
||||||
|
iconName={name}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<SnapsConnectContent />
|
||||||
|
<PageContainerFooter
|
||||||
|
footerClassName="snaps-connect__footer"
|
||||||
|
cancelButtonType="default"
|
||||||
|
hideCancel={false}
|
||||||
|
disabled={isLoading}
|
||||||
|
onCancel={onCancel}
|
||||||
|
cancelText={t('cancel')}
|
||||||
|
onSubmit={onConnect}
|
||||||
|
submitText={t('connect')}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
SnapsConnect.propTypes = {
|
||||||
|
request: PropTypes.object.isRequired,
|
||||||
|
approveConnection: PropTypes.func.isRequired,
|
||||||
|
rejectConnection: PropTypes.func.isRequired,
|
||||||
|
targetSubjectMetadata: PropTypes.shape({
|
||||||
|
extensionId: PropTypes.string,
|
||||||
|
iconUrl: PropTypes.string,
|
||||||
|
name: PropTypes.string,
|
||||||
|
origin: PropTypes.string,
|
||||||
|
subjectType: PropTypes.string,
|
||||||
|
}),
|
||||||
|
snapsInstallPrivacyWarningShown: PropTypes.bool.isRequired,
|
||||||
|
setSnapsInstallPrivacyWarningShownStatus: PropTypes.func,
|
||||||
|
};
|
@ -0,0 +1,102 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
|
||||||
|
import configureStore from '../../../../store/store';
|
||||||
|
import mockState from '../../../../../test/data/mock-state.json';
|
||||||
|
import SnapsConnect from '.';
|
||||||
|
|
||||||
|
const store = configureStore(mockState);
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'Pages/Snaps/SnapConnect',
|
||||||
|
|
||||||
|
component: SnapsConnect,
|
||||||
|
argTypes: {},
|
||||||
|
decorators: [(story) => <Provider store={store}>{story()}</Provider>],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DefaultStory = (args) => <SnapsConnect {...args} />;
|
||||||
|
|
||||||
|
DefaultStory.storyName = 'Default';
|
||||||
|
|
||||||
|
DefaultStory.args = {
|
||||||
|
request: {
|
||||||
|
metadata: {
|
||||||
|
id: 'foo',
|
||||||
|
},
|
||||||
|
permissions: {
|
||||||
|
wallet_snap: {
|
||||||
|
caveats: [
|
||||||
|
{
|
||||||
|
value: {
|
||||||
|
'npm:@metamask/test-snap-bip44': {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
targetSubjectMetadata: {
|
||||||
|
origin: 'https://metamask.io',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const MultiStory = (args) => <SnapsConnect {...args} />;
|
||||||
|
|
||||||
|
MultiStory.storyName = 'Multi';
|
||||||
|
|
||||||
|
MultiStory.args = {
|
||||||
|
request: {
|
||||||
|
metadata: {
|
||||||
|
id: 'foo',
|
||||||
|
},
|
||||||
|
permissions: {
|
||||||
|
wallet_snap: {
|
||||||
|
caveats: [
|
||||||
|
{
|
||||||
|
value: {
|
||||||
|
'npm:@metamask/test-snap-bip44': {},
|
||||||
|
'npm:@metamask/test-snap-bip32': {},
|
||||||
|
'npm:@metamask/test-snap-getEntropy': {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
targetSubjectMetadata: {
|
||||||
|
origin: 'https://metamask.io',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ScrollingStory = (args) => <SnapsConnect {...args} />;
|
||||||
|
|
||||||
|
ScrollingStory.storyName = 'Scrolling';
|
||||||
|
|
||||||
|
ScrollingStory.args = {
|
||||||
|
request: {
|
||||||
|
metadata: {
|
||||||
|
id: 'foo',
|
||||||
|
},
|
||||||
|
permissions: {
|
||||||
|
wallet_snap: {
|
||||||
|
caveats: [
|
||||||
|
{
|
||||||
|
value: {
|
||||||
|
'npm:@metamask/test-snap-bip44': {},
|
||||||
|
'npm:@metamask/test-snap-bip32': {},
|
||||||
|
'npm:@metamask/test-snap-getEntropy': {},
|
||||||
|
'npm:@metamask/test-snap-networkAccess': {},
|
||||||
|
'npm:@metamask/test-snap-wasm': {},
|
||||||
|
'npm:@metamask/test-snap-notify': {},
|
||||||
|
'npm:@metamask/test-snap-dialog': {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
targetSubjectMetadata: {
|
||||||
|
origin: 'https://metamask.io',
|
||||||
|
},
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user