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:
parent
112511542f
commit
59102e37b8
7
app/_locales/en/messages.json
generated
7
app/_locales/en/messages.json
generated
@ -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"
|
||||
},
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
`;
|
@ -0,0 +1 @@
|
||||
export { default } from './security-provider-banner-alert';
|
@ -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;
|
@ -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 shouldn’t 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} />
|
||||
);
|
@ -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();
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user