diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 995a0c34e..7f024a49d 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -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" }, diff --git a/shared/constants/security-provider.ts b/shared/constants/security-provider.ts index 1862e8181..420cecf03 100644 --- a/shared/constants/security-provider.ts +++ b/shared/constants/security-provider.ts @@ -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 = { + [SecurityProvider.Blockaid]: { + tKeyName: 'blockaid', + url: 'https://blockaid.io/', + }, +}; + /** * @typedef {object} SecurityProviderMessageSeverity * @property {0} NOT_MALICIOUS - Indicates message is not malicious diff --git a/ui/components/app/security-provider-banner-alert/__snapshots__/security-provider-banner-alert.test.js.snap b/ui/components/app/security-provider-banner-alert/__snapshots__/security-provider-banner-alert.test.js.snap new file mode 100644 index 000000000..54139024c --- /dev/null +++ b/ui/components/app/security-provider-banner-alert/__snapshots__/security-provider-banner-alert.test.js.snap @@ -0,0 +1,73 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Security Provider Banner Alert should match snapshot 1`] = ` +
+
+ +
+
+ Malicious third party detected +
+

+ This is a description to warn the user of malicious or suspicious transactions. +

+
+
+ +

+ [seeDetails] +

+ +
+
+
    +
  • + List item +
  • +
  • + List item +
  • +
  • + List item +
  • +
+
+
+
+

+ + [securityProviderAdviceBy] +

+
+
+
+`; diff --git a/ui/components/app/security-provider-banner-alert/index.js b/ui/components/app/security-provider-banner-alert/index.js new file mode 100644 index 000000000..f89920127 --- /dev/null +++ b/ui/components/app/security-provider-banner-alert/index.js @@ -0,0 +1 @@ +export { default } from './security-provider-banner-alert'; diff --git a/ui/components/app/security-provider-banner-alert/security-provider-banner-alert.js b/ui/components/app/security-provider-banner-alert/security-provider-banner-alert.js new file mode 100644 index 000000000..f6046d4e9 --- /dev/null +++ b/ui/components/app/security-provider-banner-alert/security-provider-banner-alert.js @@ -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 ( + + {description} + + {details && ( + + {details} + + )} + + + + {t('securityProviderAdviceBy', [ + + {t(SECURITY_PROVIDER_CONFIG[provider].tKeyName)} + , + ])} + + + ); +} + +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 param */ + title: PropTypes.string.isRequired, + + /** + * Optional + */ + + /** Additional details to be displayed under the description */ + details: PropTypes.oneOfType([PropTypes.string, PropTypes.element]), +}; + +export default SecurityProviderBannerAlert; diff --git a/ui/components/app/security-provider-banner-alert/security-provider-banner-alert.stories.js b/ui/components/app/security-provider-banner-alert/security-provider-banner-alert.stories.js new file mode 100644 index 000000000..c78ce3f8f --- /dev/null +++ b/ui/components/app/security-provider-banner-alert/security-provider-banner-alert.stories.js @@ -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{' '} + hyperlink. It can + also contain a toggle to enable progressive disclosure. + +); + +const MockDetailsList = () => ( + +
  • • List item
  • +
  • • List item
  • +
  • • List item
  • +
  • • List item
  • +
    +); + +export default { + title: 'Components/App/SecurityProviderBannerAlert', + argTypes: { + description: { + control: { + type: 'select', + }, + options: ['plainText', 'withLinks'], + mapping: { + plainText: mockPlainText, + withLinks: , + }, + }, + details: { + control: { + type: 'select', + }, + options: ['none', 'plainText', 'withList'], + mapping: { + none: null, + plainText: mockPlainText, + withList: , + }, + }, + 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: , + details: , + provider: SecurityProvider.Blockaid, + }, +}; + +export const DefaultStory = (args) => ( + +); +DefaultStory.storyName = 'Default'; + +export const Danger = (args) => ( + +); + +export const Warning = (args) => ( + +); diff --git a/ui/components/app/security-provider-banner-alert/security-provider-banner-alert.test.js b/ui/components/app/security-provider-banner-alert/security-provider-banner-alert.test.js new file mode 100644 index 000000000..c11a1202e --- /dev/null +++ b/ui/components/app/security-provider-banner-alert/security-provider-banner-alert.test.js @@ -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 = ( +
      +
    • List item
    • +
    • List item
    • +
    • List item
    • +
    +); + +describe('Security Provider Banner Alert', () => { + it('should match snapshot', () => { + const { container } = renderWithProvider( + , + ); + + expect(container).toMatchSnapshot(); + }); + + it('should render', () => { + const { container, getByText } = renderWithProvider( + , + ); + + 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( + , + ); + + expect(container.querySelector('.disclosure')).not.toBeInTheDocument(); + }); +});