1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-22 01:47:00 +01:00

[MMI] Add-compliance-details component (#18575)

Co-authored-by: Antonio Regadas <antonio.regadas@consensys.net>
Co-authored-by: Ariella Vu <20778143+digiwand@users.noreply.github.com>
This commit is contained in:
Albert Olivé 2023-04-21 20:28:26 +02:00 committed by GitHub
parent d2779c9ef1
commit 4f64e3d6c3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 459 additions and 0 deletions

View File

@ -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"
},

View File

@ -0,0 +1,144 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ComplianceDetails should render correctly 1`] = `
<div>
<div
class="box compliance-details box--padding-right-4 box--padding-left-4 box--display-flex box--flex-direction-column"
>
<div
class="box compliance-details__row box--padding-top-4 box--padding-bottom-4 box--display-flex box--flex-direction-column box--justify-content-center box--height-2/3"
>
<p
class="box mm-text mm-text--body-md box--flex-direction-row box--color-text-default"
>
Address
</p>
<p
class="box mm-text mm-text--body-xs box--flex-direction-row box--color-text-default"
>
0xAddress
</p>
</div>
<div
class="box compliance-details__row box--padding-top-4 box--padding-bottom-4 box--display-flex box--flex-direction-column box--justify-content-center box--height-2/3"
>
<div
class="box box--margin-bottom-1 box--display-flex box--flex-direction-row box--align-items-center box--color-text-alternative"
>
<p
class="box mm-text mm-text--body-md box--margin-right-2 box--flex-direction-row box--color-text-default"
>
Risk rating
</p>
<div
class="info-tooltip"
>
<div>
<div
aria-describedby="tippy-tooltip-1"
class="info-tooltip__tooltip-container"
data-original-title="null"
data-tooltipped=""
style="display: inline;"
tabindex="0"
>
<svg
viewBox="0 0 10 10"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M5 0C2.2 0 0 2.2 0 5s2.2 5 5 5 5-2.2 5-5-2.2-5-5-5zm0 2c.4 0 .7.3.7.7s-.3.7-.7.7-.7-.2-.7-.6.3-.8.7-.8zm.7 6H4.3V4.3h1.5V8z"
fill="var(--color-icon-alternative)"
/>
</svg>
</div>
</div>
</div>
</div>
<div
class="box compliance-row__column-risk compliance-row__column-risk--green box--flex-direction-row"
>
<p
class="box mm-text mm-text--body-md box--flex-direction-row box--color-text-default"
>
low
</p>
</div>
</div>
<div
class="box compliance-details__row box--padding-top-4 box--padding-bottom-4 box--display-flex box--flex-direction-column box--justify-content-center box--height-2/3"
>
<div
class="box box--display-flex box--flex-direction-row box--align-items-center box--color-text-alternative"
>
<p
class="box mm-text mm-text--body-md box--margin-right-2 box--flex-direction-row box--color-text-default"
>
Report last run
</p>
<div
class="info-tooltip"
>
<div>
<div
aria-describedby="tippy-tooltip-2"
class="info-tooltip__tooltip-container"
data-original-title="null"
data-tooltipped=""
style="display: inline;"
tabindex="0"
>
<svg
viewBox="0 0 10 10"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M5 0C2.2 0 0 2.2 0 5s2.2 5 5 5 5-2.2 5-5-2.2-5-5-5zm0 2c.4 0 .7.3.7.7s-.3.7-.7.7-.7-.2-.7-.6.3-.8.7-.8zm.7 6H4.3V4.3h1.5V8z"
fill="var(--color-icon-alternative)"
/>
</svg>
</div>
</div>
</div>
</div>
<p
class="box mm-text mm-text--body-md box--flex-direction-row box--color-text-default"
/>
</div>
<div
class="box box--flex-direction-row"
>
<div
class="swaps-footer"
>
<div
class="swaps-footer__buttons swaps-footer__buttons--border"
>
<div
class="page-container__footer swaps-footer__custom-page-container-footer-class"
>
<footer>
<button
class="button btn--rounded btn-secondary page-container__footer-button page-container__footer-button__cancel swaps-footer__custom-page-container-footer-button-class"
data-testid="page-container-footer-cancel"
role="button"
tabindex="0"
>
Show report
</button>
<button
class="button btn--rounded btn-primary page-container__footer-button swaps-footer__custom-page-container-footer-button-class"
data-testid="page-container-footer-next"
role="button"
tabindex="0"
>
Run report
</button>
</footer>
</div>
</div>
</div>
</div>
</div>
</div>
`;

View File

@ -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 (
<Box
display={DISPLAY.FLEX}
flexDirection={FLEX_DIRECTION.COLUMN}
paddingLeft={4}
paddingRight={4}
className="compliance-details"
>
<Box
display={DISPLAY.FLEX}
flexDirection={FLEX_DIRECTION.COLUMN}
justifyContent={JustifyContent.center}
height={BLOCK_SIZES.TWO_THIRDS}
paddingTop={4}
paddingBottom={4}
className="compliance-details__row"
>
<Text>{t('address')}</Text>
<Text variant={TextVariant.bodyXs}>{address}</Text>
</Box>
<Box
display={DISPLAY.FLEX}
flexDirection={FLEX_DIRECTION.COLUMN}
justifyContent={JustifyContent.center}
height={BLOCK_SIZES.TWO_THIRDS}
paddingTop={4}
paddingBottom={4}
className="compliance-details__row"
>
<Box
display={DISPLAY.FLEX}
alignItems={AlignItems.center}
marginBottom={1}
color={TextColor.textAlternative}
>
<Text marginRight={2}>{t('riskRating')}</Text>
<InfoTooltip
position="bottom"
contentText={<span>{t('riskRatingTooltip')}</span>}
/>
</Box>
<Box
className={classnames('compliance-row__column-risk', {
'compliance-row__column-risk--green': lastReport?.risk === 'low',
'compliance-row__column-risk--yellow':
lastReport?.risk === 'medium',
'compliance-row__column-risk--orange': lastReport?.risk === 'high',
'compliance-row__column-risk--red':
lastReport?.risk === 'unacceptable',
})}
>
<Text>{lastReport ? lastReport.risk : t('noReport')}</Text>
</Box>
</Box>
<Box
display={DISPLAY.FLEX}
flexDirection={FLEX_DIRECTION.COLUMN}
justifyContent={JustifyContent.center}
height={BLOCK_SIZES.TWO_THIRDS}
paddingTop={4}
paddingBottom={4}
className="compliance-details__row"
>
<Box
display={DISPLAY.FLEX}
alignItems={AlignItems.center}
color={TextColor.textAlternative}
>
<Text marginRight={2}>{t('reportLastRun')}</Text>
<InfoTooltip
position="bottom"
contentText={<span>{t('reportLastRunTooltip')}</span>}
/>
</Box>
<Text color={TextColor.textDefault}>
{lastReport
? formatDate(new Date(lastReport.createTime).getTime())
: 'N/A'}
</Text>
</Box>
<Box>
<SwapsFooter
onSubmit={() => {
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
/>
</Box>
</Box>
);
};
ComplianceDetails.propTypes = {
address: PropTypes.string,
onClose: PropTypes.func,
onGenerate: PropTypes.func,
};
export default ComplianceDetails;

View File

@ -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) => <Provider store={store}>{story()}</Provider>],
component: ComplianceDetails,
args: {
address: '0xAddress',
onClose: () => undefined,
onGenerate: () => undefined,
},
argTypes: {
onClick: {
action: 'onClick',
},
},
};
export const DefaultStory = (args) => <ComplianceDetails {...args} />;
DefaultStory.storyName = 'ComplianceDetails';

View File

@ -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(
<ComplianceDetails
address={props.address}
onClose={props.onClose}
onGenerate={props.onGenerate}
/>,
store,
);
expect(container).toMatchSnapshot();
});
it('runs onGenerate fuction', () => {
renderWithProvider(
<ComplianceDetails
address={props.address}
onClose={props.onClose}
onGenerate={props.onGenerate}
/>,
store,
);
fireEvent.click(screen.queryByTestId('page-container-footer-next'));
expect(props.onGenerate).toHaveBeenCalledTimes(1);
expect(props.onGenerate).toHaveBeenCalledWith(props.address);
});
});

View File

@ -0,0 +1 @@
export { default } from './compliance-details';

View File

@ -0,0 +1,5 @@
.compliance-details {
&__row {
border-top: 1px solid var(--color-border-muted);
}
}

View File

@ -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';

View File

@ -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';