1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-22 09:23:21 +01:00

New SecurityProviderBannerAlert component (#19825)

This commit is contained in:
Ariella Vu 2023-07-21 11:04:51 +02:00 committed by GitHub
parent 112511542f
commit 59102e37b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 354 additions and 0 deletions

View File

@ -3579,6 +3579,13 @@
"securityAndPrivacy": {
"message": "Security & privacy"
},
"securityProviderAdviceBy": {
"message": "Security advice by $1",
"description": "The security provider that is providing data"
},
"seeDetails": {
"message": "See details"
},
"seedPhraseConfirm": {
"message": "Confirm Secret Recovery Phrase"
},

View File

@ -1,3 +1,24 @@
export enum SecurityProvider {
Blockaid = 'blockaid',
}
type SecurityProviderConfig = Record<
SecurityProvider,
{
/** translation key for security provider name */
readonly tKeyName: string;
/** URL to securty provider website */
readonly url: string;
}
>;
export const SECURITY_PROVIDER_CONFIG: Readonly<SecurityProviderConfig> = {
[SecurityProvider.Blockaid]: {
tKeyName: 'blockaid',
url: 'https://blockaid.io/',
},
};
/**
* @typedef {object} SecurityProviderMessageSeverity
* @property {0} NOT_MALICIOUS - Indicates message is not malicious

View File

@ -0,0 +1,73 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Security Provider Banner Alert should match snapshot 1`] = `
<div>
<div
class="box mm-banner-base mm-banner-alert mm-banner-alert--severity-danger box--margin-top-4 box--margin-right-4 box--margin-left-4 box--padding-3 box--padding-left-2 box--display-flex box--gap-2 box--flex-direction-row box--background-color-error-muted box--rounded-sm"
>
<span
class="box mm-icon mm-icon--size-lg box--display-inline-block box--flex-direction-row box--color-error-default"
style="mask-image: url('./images/icons/danger.svg');"
/>
<div>
<h5
class="mm-box mm-text mm-banner-base__title mm-text--body-lg-medium mm-box--color-text-default"
data-testid="mm-banner-base-title"
>
Malicious third party detected
</h5>
<p
class="mm-box mm-text mm-text--body-md mm-box--margin-top-2 mm-box--color-text-default"
>
This is a description to warn the user of malicious or suspicious transactions.
</p>
<div
class="disclosure"
>
<details>
<summary
class="disclosure__summary is-arrow"
>
<p
class="mm-box mm-text mm-text--body-md mm-box--color-primary-default"
>
[seeDetails]
</p>
<span
class="box disclosure__summary--icon mm-icon mm-icon--size-sm box--margin-inline-start-2 box--display-inline-block box--flex-direction-row box--color-primary-default"
style="mask-image: url('./images/icons/arrow-up.svg');"
/>
</summary>
<div
class="disclosure__content normal"
>
<ul>
<li>
List item
</li>
<li>
List item
</li>
<li>
List item
</li>
</ul>
</div>
<div
class="disclosure__footer"
/>
</details>
</div>
<p
class="mm-box mm-text mm-text--body-sm mm-box--margin-top-2 mm-box--align-items-center mm-box--color-text-alternative"
>
<span
class="box disclosure__summary--icon mm-icon mm-icon--size-sm box--margin-inline-end-1 box--display-inline-block box--flex-direction-row box--color-primary-default"
style="mask-image: url('./images/icons/security-tick.svg');"
/>
[securityProviderAdviceBy]
</p>
</div>
</div>
</div>
`;

View File

@ -0,0 +1 @@
export { default } from './security-provider-banner-alert';

View File

@ -0,0 +1,104 @@
import React, { useContext } from 'react';
import PropTypes from 'prop-types';
import {
BannerAlert,
ButtonLink,
Icon,
IconName,
IconSize,
Text,
} from '../../component-library';
import Disclosure from '../../ui/disclosure';
import { DisclosureVariant } from '../../ui/disclosure/disclosure.constants';
import { I18nContext } from '../../../contexts/i18n';
import {
AlignItems,
Color,
IconColor,
Severity,
Size,
TextVariant,
} from '../../../helpers/constants/design-system';
import {
SecurityProvider,
SECURITY_PROVIDER_CONFIG,
} from '../../../../shared/constants/security-provider';
function SecurityProviderBannerAlert({
description,
details,
provider,
severity,
title,
}) {
const t = useContext(I18nContext);
return (
<BannerAlert
title={title}
severity={severity}
marginTop={4}
marginRight={4}
marginLeft={4}
>
<Text marginTop={2}>{description}</Text>
{details && (
<Disclosure title={t('seeDetails')} variant={DisclosureVariant.Arrow}>
{details}
</Disclosure>
)}
<Text
marginTop={2}
alignItems={AlignItems.center}
color={Color.textAlternative}
variant={TextVariant.bodySm}
>
<Icon
className="disclosure__summary--icon"
color={IconColor.primaryDefault}
name={IconName.SecurityTick}
size={IconSize.Sm}
marginInlineEnd={1}
/>
{t('securityProviderAdviceBy', [
<ButtonLink
key={`security-provider-button-link-${provider}`}
size={Size.inherit}
href={SECURITY_PROVIDER_CONFIG[provider].url}
externalLink
>
{t(SECURITY_PROVIDER_CONFIG[provider].tKeyName)}
</ButtonLink>,
])}
</Text>
</BannerAlert>
);
}
SecurityProviderBannerAlert.propTypes = {
/** Description content that may be plain text or contain hyperlinks */
description: PropTypes.oneOfType([PropTypes.string, PropTypes.element])
.isRequired,
/** Name of the security provider */
provider: PropTypes.oneOfType(Object.values(SecurityProvider)).isRequired,
/** Severity level */
severity: PropTypes.oneOfType([Severity.Danger, Severity.Warning]).isRequired,
/** Title to be passed as <BannerAlert> param */
title: PropTypes.string.isRequired,
/**
* Optional
*/
/** Additional details to be displayed under the description */
details: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
};
export default SecurityProviderBannerAlert;

View File

@ -0,0 +1,87 @@
import React from 'react';
import { Severity } from '../../../helpers/constants/design-system';
import { ButtonLink, BUTTON_LINK_SIZES, Text } from '../../component-library';
import { SecurityProvider } from '../../../../shared/constants/security-provider';
import SecurityProviderBannerAlert from './security-provider-banner-alert';
const mockPlainText =
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus sapien tellus, elementum sit ' +
'amet laoreet vitae, semper in est. Nulla vel tristique felis. Donec non tellus eget neque cursus malesuada.';
const MockDescriptionWithLinks = () => (
<>
Description shouldnt repeat title. 1-3 lines. Can contain a{' '}
<ButtonLink size={BUTTON_LINK_SIZES.INHERIT}>hyperlink</ButtonLink>. It can
also contain a toggle to enable progressive disclosure.
</>
);
const MockDetailsList = () => (
<Text as="ul">
<li> List item</li>
<li> List item</li>
<li> List item</li>
<li> List item</li>
</Text>
);
export default {
title: 'Components/App/SecurityProviderBannerAlert',
argTypes: {
description: {
control: {
type: 'select',
},
options: ['plainText', 'withLinks'],
mapping: {
plainText: mockPlainText,
withLinks: <MockDescriptionWithLinks />,
},
},
details: {
control: {
type: 'select',
},
options: ['none', 'plainText', 'withList'],
mapping: {
none: null,
plainText: mockPlainText,
withList: <MockDetailsList />,
},
},
provider: {
control: {
type: 'select',
},
options: [Object.values(SecurityProvider)],
},
severity: {
control: {
type: 'select',
},
options: [Severity.Danger, Severity.Warning],
},
title: {
control: 'text',
},
},
args: {
title: 'Title is sentence case no period',
description: <MockDescriptionWithLinks />,
details: <MockDetailsList />,
provider: SecurityProvider.Blockaid,
},
};
export const DefaultStory = (args) => (
<SecurityProviderBannerAlert severity={Severity.Warning} {...args} />
);
DefaultStory.storyName = 'Default';
export const Danger = (args) => (
<SecurityProviderBannerAlert severity={Severity.Danger} {...args} />
);
export const Warning = (args) => (
<SecurityProviderBannerAlert severity={Severity.Warning} {...args} />
);

View File

@ -0,0 +1,61 @@
import React from 'react';
import { Severity } from '../../../helpers/constants/design-system';
import { renderWithProvider } from '../../../../test/lib/render-helpers';
import { SecurityProvider } from '../../../../shared/constants/security-provider';
import SecurityProviderBannerAlert from '.';
const mockTitle = 'Malicious third party detected';
const mockDescription =
'This is a description to warn the user of malicious or suspicious transactions.';
const mockDetails = (
<ul>
<li>List item</li>
<li>List item</li>
<li>List item</li>
</ul>
);
describe('Security Provider Banner Alert', () => {
it('should match snapshot', () => {
const { container } = renderWithProvider(
<SecurityProviderBannerAlert
description={mockDescription}
details={mockDetails}
provider={SecurityProvider.Blockaid}
severity={Severity.Danger}
title={mockTitle}
/>,
);
expect(container).toMatchSnapshot();
});
it('should render', () => {
const { container, getByText } = renderWithProvider(
<SecurityProviderBannerAlert
description={mockDescription}
details={mockDetails}
provider={SecurityProvider.Blockaid}
severity={Severity.Danger}
title={mockTitle}
/>,
);
expect(getByText(mockTitle)).toBeInTheDocument();
expect(getByText(mockDescription)).toBeInTheDocument();
expect(container.querySelector('.disclosure')).toBeInTheDocument();
});
it('should not render disclosure component if no details were provided', () => {
const { container } = renderWithProvider(
<SecurityProviderBannerAlert
description={mockDescription}
provider={SecurityProvider.Blockaid}
severity={Severity.Danger}
title={mockTitle}
/>,
);
expect(container.querySelector('.disclosure')).not.toBeInTheDocument();
});
});