diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json
index e02d7e061..841ddef6f 100644
--- a/app/_locales/en/messages.json
+++ b/app/_locales/en/messages.json
@@ -2402,6 +2402,9 @@
"noNFTs": {
"message": "No NFTs yet"
},
+ "noReport": {
+ "message": "No Report"
+ },
"noSnaps": {
"message": "You don't have any snaps installed."
},
@@ -3245,6 +3248,12 @@
"replace": {
"message": "replace"
},
+ "reportLastRun": {
+ "message": "Report last run"
+ },
+ "reportLastRunTooltip": {
+ "message": "The date and time of when the last AML/CFT report was run"
+ },
"requestFailed": {
"message": "Request failed"
},
@@ -3374,9 +3383,18 @@
"revokeSpendingCapTooltipText": {
"message": "This third party will be unable to spend any more of your current or future tokens."
},
+ "riskRating": {
+ "message": "Risk rating"
+ },
+ "riskRatingTooltip": {
+ "message": "The risk rating of the address you are interacting with based on your risk settings"
+ },
"rpcUrl": {
"message": "New RPC URL"
},
+ "runReport": {
+ "message": "Run report"
+ },
"safeTransferFrom": {
"message": "Safe transfer from"
},
@@ -3591,6 +3609,9 @@
"showPrivateKeys": {
"message": "Show Private Keys"
},
+ "showReport": {
+ "message": "Show report"
+ },
"showTestnetNetworks": {
"message": "Show test networks"
},
diff --git a/ui/components/institutional/compliance-details/__snapshots__/compliance-details.test.js.snap b/ui/components/institutional/compliance-details/__snapshots__/compliance-details.test.js.snap
new file mode 100644
index 000000000..2cd5c29ec
--- /dev/null
+++ b/ui/components/institutional/compliance-details/__snapshots__/compliance-details.test.js.snap
@@ -0,0 +1,144 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`ComplianceDetails should render correctly 1`] = `
+
+
+
+
+ Address
+
+
+ 0xAddress
+
+
+
+
+
+
+ Report last run
+
+
+
+
+
+
+
+
+
+
+`;
diff --git a/ui/components/institutional/compliance-details/compliance-details.js b/ui/components/institutional/compliance-details/compliance-details.js
new file mode 100644
index 000000000..c195dbd3c
--- /dev/null
+++ b/ui/components/institutional/compliance-details/compliance-details.js
@@ -0,0 +1,159 @@
+import React, { useContext, useEffect, useState } from 'react';
+import { useSelector, useDispatch } from 'react-redux';
+import PropTypes from 'prop-types';
+import classnames from 'classnames';
+import { I18nContext } from '../../../contexts/i18n';
+import InfoTooltip from '../../ui/info-tooltip';
+import SwapsFooter from '../../../pages/swaps/swaps-footer';
+import {
+ fetchHistoricalReports,
+ getComplianceHistoricalReportsByAddress,
+ getComplianceTenantSubdomain,
+} from '../../../ducks/institutional/institutional';
+import { formatDate } from '../../../helpers/utils/util';
+import Box from '../../ui/box';
+import { Text } from '../../component-library';
+import {
+ TextColor,
+ TextVariant,
+ JustifyContent,
+ AlignItems,
+ BLOCK_SIZES,
+ DISPLAY,
+ FLEX_DIRECTION,
+} from '../../../helpers/constants/design-system';
+
+const ComplianceDetails = ({ address, onClose, onGenerate }) => {
+ const t = useContext(I18nContext);
+ const dispatch = useDispatch();
+
+ useEffect(() => {
+ dispatch(fetchHistoricalReports(address));
+ }, [address, dispatch]);
+
+ const [lastReport, setLastReport] = useState(null);
+ const historicalReports = useSelector(
+ getComplianceHistoricalReportsByAddress(address),
+ );
+
+ useEffect(() => {
+ if (historicalReports && historicalReports.length) {
+ setLastReport(
+ historicalReports.reduce((prev, cur) =>
+ prev.createTime > cur.createTime ? prev : cur,
+ ),
+ );
+ }
+ }, [historicalReports]);
+
+ const complianceTenantSubdomain = useSelector(getComplianceTenantSubdomain);
+
+ return (
+
+
+ {t('address')}
+ {address}
+
+
+
+ {t('riskRating')}
+ {t('riskRatingTooltip')}}
+ />
+
+
+ {lastReport ? lastReport.risk : t('noReport')}
+
+
+
+
+ {t('reportLastRun')}
+ {t('reportLastRunTooltip')}}
+ />
+
+
+ {lastReport
+ ? formatDate(new Date(lastReport.createTime).getTime())
+ : 'N/A'}
+
+
+
+ {
+ onGenerate(address);
+ onClose();
+ }}
+ submitText={t('runReport')}
+ onCancel={() =>
+ global.platform.openTab({
+ url: `https://${complianceTenantSubdomain}.compliance.codefi.network/app/kyt/addresses/${lastReport.address}/${lastReport.reportId}`,
+ })
+ }
+ cancelText={t('showReport')}
+ hideCancel={!lastReport}
+ approveActive={lastReport}
+ showTopBorder
+ />
+
+
+ );
+};
+
+ComplianceDetails.propTypes = {
+ address: PropTypes.string,
+ onClose: PropTypes.func,
+ onGenerate: PropTypes.func,
+};
+
+export default ComplianceDetails;
diff --git a/ui/components/institutional/compliance-details/compliance-details.stories.js b/ui/components/institutional/compliance-details/compliance-details.stories.js
new file mode 100644
index 000000000..b4ead4450
--- /dev/null
+++ b/ui/components/institutional/compliance-details/compliance-details.stories.js
@@ -0,0 +1,48 @@
+import React from 'react';
+import { Provider } from 'react-redux';
+import configureStore from '../../../store/store';
+import testData from '../../../../.storybook/test-data';
+import ComplianceDetails from '.';
+
+const customData = {
+ ...testData,
+ metamask: {
+ institutionalFeatures: {
+ complianceProjectId: '',
+ complianceClientId: '',
+ reportsInProgress: {},
+ historicalReports: {
+ '0xAddress': [
+ {
+ reportId: 'reportId',
+ address: '0xAddress',
+ risk: 'low',
+ creatTime: new Date(),
+ },
+ ],
+ },
+ },
+ },
+};
+
+const store = configureStore(customData);
+
+export default {
+ title: 'Components/Institutional/ComplianceDetails',
+ decorators: [(story) => {story()}],
+ component: ComplianceDetails,
+ args: {
+ address: '0xAddress',
+ onClose: () => undefined,
+ onGenerate: () => undefined,
+ },
+ argTypes: {
+ onClick: {
+ action: 'onClick',
+ },
+ },
+};
+
+export const DefaultStory = (args) => ;
+
+DefaultStory.storyName = 'ComplianceDetails';
diff --git a/ui/components/institutional/compliance-details/compliance-details.test.js b/ui/components/institutional/compliance-details/compliance-details.test.js
new file mode 100644
index 000000000..670d8a1b4
--- /dev/null
+++ b/ui/components/institutional/compliance-details/compliance-details.test.js
@@ -0,0 +1,67 @@
+import React from 'react';
+import configureStore from 'redux-mock-store';
+import { fireEvent, screen } from '@testing-library/react';
+import thunk from 'redux-thunk';
+import { renderWithProvider } from '../../../../test/lib/render-helpers';
+import ComplianceDetails from './compliance-details';
+
+const initState = {
+ metamask: {
+ institutionalFeatures: {
+ complianceProjectId: '',
+ complianceClientId: '',
+ reportsInProgress: {},
+ historicalReports: {
+ '0xAddress': [
+ {
+ reportId: 'reportId',
+ address: '0xAddress',
+ risk: 'low',
+ creatTime: new Date(),
+ },
+ ],
+ },
+ },
+ },
+};
+const middlewares = [thunk];
+const mockStore = configureStore(middlewares);
+
+describe('ComplianceDetails', () => {
+ const props = {
+ address: '0xAddress',
+ onClose: jest.fn(),
+ onGenerate: jest.fn(),
+ };
+
+ const store = mockStore(initState);
+
+ it('should render correctly', () => {
+ const { container } = renderWithProvider(
+ ,
+ store,
+ );
+
+ expect(container).toMatchSnapshot();
+ });
+
+ it('runs onGenerate fuction', () => {
+ renderWithProvider(
+ ,
+ store,
+ );
+
+ fireEvent.click(screen.queryByTestId('page-container-footer-next'));
+
+ expect(props.onGenerate).toHaveBeenCalledTimes(1);
+ expect(props.onGenerate).toHaveBeenCalledWith(props.address);
+ });
+});
diff --git a/ui/components/institutional/compliance-details/index.js b/ui/components/institutional/compliance-details/index.js
new file mode 100644
index 000000000..4cccdb1cd
--- /dev/null
+++ b/ui/components/institutional/compliance-details/index.js
@@ -0,0 +1 @@
+export { default } from './compliance-details';
diff --git a/ui/components/institutional/compliance-details/index.scss b/ui/components/institutional/compliance-details/index.scss
new file mode 100644
index 000000000..d342c36ff
--- /dev/null
+++ b/ui/components/institutional/compliance-details/index.scss
@@ -0,0 +1,5 @@
+.compliance-details {
+ &__row {
+ border-top: 1px solid var(--color-border-muted);
+ }
+}
diff --git a/ui/components/institutional/institutional-components.scss b/ui/components/institutional/institutional-components.scss
new file mode 100644
index 000000000..8ae612682
--- /dev/null
+++ b/ui/components/institutional/institutional-components.scss
@@ -0,0 +1,11 @@
+/**
+* Please import your styles in order of atomicity.
+* The most atomic styles should be imported first.
+* This will help improve specificity and reduce the chance of
+* unintended overrides.
+**/
+@import 'compliance-details/index';
+@import 'compliance-settings/index';
+@import 'jwt-dropdown/jwt-dropdown';
+@import 'jwt-url-form/jwt-url-form';
+@import 'note-to-trader/index';
diff --git a/ui/css/index.scss b/ui/css/index.scss
index a26c7ae09..c97d9573a 100644
--- a/ui/css/index.scss
+++ b/ui/css/index.scss
@@ -11,6 +11,9 @@
@import '../components/component-library/component-library-components.scss';
@import '../components/app/app-components';
@import '../components/ui/ui-components';
+///: BEGIN:ONLY_INCLUDE_IN(mmi)
+@import '../components/institutional/institutional-components';
+///: END:ONLY_INCLUDE_IN
@import '../components/multichain/multichain-components.scss';
@import '../pages/pages';
@import './errors.scss';