Add support for Smart Transactions (#12676)
parent
d2c292bead
commit
2585f45bde
After Width: | Height: | Size: 119 KiB |
After Width: | Height: | Size: 7.1 KiB |
After Width: | Height: | Size: 6.2 KiB |
@ -0,0 +1,13 @@ |
||||
diff --git a/node_modules/@metamask/smart-transactions-controller/node_modules/fast-json-patch/commonjs/helpers.js b/node_modules/@metamask/smart-transactions-controller/node_modules/fast-json-patch/commonjs/helpers.js
|
||||
index 0ac28b4..d048c0a 100644
|
||||
--- a/node_modules/@metamask/smart-transactions-controller/node_modules/fast-json-patch/commonjs/helpers.js
|
||||
+++ b/node_modules/@metamask/smart-transactions-controller/node_modules/fast-json-patch/commonjs/helpers.js
|
||||
@@ -21,7 +21,7 @@ var _hasOwnProperty = Object.prototype.hasOwnProperty;
|
||||
function hasOwnProperty(obj, key) {
|
||||
return _hasOwnProperty.call(obj, key);
|
||||
}
|
||||
-exports.hasOwnProperty = hasOwnProperty;
|
||||
+Object.defineProperty(exports, "hasOwnProperty", { value: hasOwnProperty });
|
||||
function _objectKeys(obj) {
|
||||
if (Array.isArray(obj)) {
|
||||
var keys = new Array(obj.length);
|
@ -0,0 +1,90 @@ |
||||
import React, { useState } from 'react'; |
||||
import PropTypes from 'prop-types'; |
||||
import { useDispatch } from 'react-redux'; |
||||
import ListItem from '../../ui/list-item'; |
||||
import TransactionStatus from '../transaction-status/transaction-status.component'; |
||||
import TransactionIcon from '../transaction-icon'; |
||||
import { useI18nContext } from '../../../hooks/useI18nContext'; |
||||
import { formatDateWithYearContext } from '../../../helpers/utils/util'; |
||||
import { |
||||
TRANSACTION_GROUP_CATEGORIES, |
||||
TRANSACTION_GROUP_STATUSES, |
||||
SMART_TRANSACTION_STATUSES, |
||||
} from '../../../../shared/constants/transaction'; |
||||
|
||||
import CancelButton from '../cancel-button'; |
||||
import { cancelSwapsSmartTransaction } from '../../../ducks/swaps/swaps'; |
||||
|
||||
export default function SmartTransactionListItem({ |
||||
smartTransaction, |
||||
isEarliestNonce = false, |
||||
}) { |
||||
const dispatch = useDispatch(); |
||||
const t = useI18nContext(); |
||||
const [cancelSwapLinkClicked, setCancelSwapLinkClicked] = useState(false); |
||||
const { |
||||
sourceTokenSymbol, |
||||
destinationTokenSymbol, |
||||
time, |
||||
status, |
||||
} = smartTransaction; |
||||
const category = TRANSACTION_GROUP_CATEGORIES.SWAP; |
||||
const title = t('swapTokenToToken', [ |
||||
sourceTokenSymbol, |
||||
destinationTokenSymbol, |
||||
]); |
||||
const subtitle = 'metamask'; |
||||
const date = formatDateWithYearContext(time); |
||||
let displayedStatusKey; |
||||
if (status === SMART_TRANSACTION_STATUSES.PENDING) { |
||||
displayedStatusKey = TRANSACTION_GROUP_STATUSES.PENDING; |
||||
} else if (status?.startsWith(SMART_TRANSACTION_STATUSES.CANCELLED)) { |
||||
displayedStatusKey = TRANSACTION_GROUP_STATUSES.CANCELLED; |
||||
} |
||||
const showCancelSwapLink = |
||||
smartTransaction.cancellable && !cancelSwapLinkClicked; |
||||
const className = 'transaction-list-item transaction-list-item--unconfirmed'; |
||||
return ( |
||||
<> |
||||
<ListItem |
||||
className={className} |
||||
title={title} |
||||
icon={ |
||||
<TransactionIcon category={category} status={displayedStatusKey} /> |
||||
} |
||||
subtitle={ |
||||
<h3> |
||||
<TransactionStatus |
||||
isPending |
||||
isEarliestNonce={isEarliestNonce} |
||||
date={date} |
||||
status={displayedStatusKey} |
||||
/> |
||||
<span className="transaction-list-item__origin" title={subtitle}> |
||||
{subtitle} |
||||
</span> |
||||
</h3> |
||||
} |
||||
> |
||||
{displayedStatusKey === TRANSACTION_GROUP_STATUSES.PENDING && |
||||
showCancelSwapLink && ( |
||||
<div className="transaction-list-item__pending-actions"> |
||||
<CancelButton |
||||
transaction={smartTransaction.uuid} |
||||
cancelTransaction={(e) => { |
||||
e?.preventDefault(); |
||||
dispatch(cancelSwapsSmartTransaction(smartTransaction.uuid)); |
||||
setCancelSwapLinkClicked(true); |
||||
}} |
||||
/> |
||||
</div> |
||||
)} |
||||
</ListItem> |
||||
</> |
||||
); |
||||
} |
||||
|
||||
SmartTransactionListItem.propTypes = { |
||||
smartTransaction: PropTypes.object.isRequired, |
||||
isEarliestNonce: PropTypes.bool, |
||||
}; |
@ -0,0 +1,18 @@ |
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP |
||||
|
||||
exports[`ArrowIcon renders the ArrowIcon component 1`] = ` |
||||
<div> |
||||
<svg |
||||
fill="none" |
||||
height="13" |
||||
viewBox="0 0 15 13" |
||||
width="15" |
||||
xmlns="http://www.w3.org/2000/svg" |
||||
> |
||||
<path |
||||
d="M14.4946 6.14779C14.4863 5.93594 14.3991 5.69108 14.2539 5.53666L9.51334 0.49958C9.1921 0.21091 8.57613 0.0917437 8.21709 0.425509C7.86344 0.754237 7.87396 1.39178 8.22627 1.72181L11.5595 5.25889L1.1618 5.25889C0.670919 5.25889 0.272949 5.65687 0.272949 6.14779C0.272949 6.6387 0.670919 7.03668 1.1618 7.03668L11.5595 7.03668L8.22627 10.5738C7.92297 10.8776 7.86691 11.5376 8.21709 11.8701C8.56718 12.2025 9.20529 12.0963 9.51334 11.796L14.2539 6.75891C14.4161 6.58653 14.4952 6.38428 14.4946 6.14779Z" |
||||
fill="#D6D9DC" |
||||
/> |
||||
</svg> |
||||
</div> |
||||
`; |
@ -0,0 +1,24 @@ |
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP |
||||
|
||||
exports[`CanceledIcon renders the CanceledIcon component 1`] = ` |
||||
<div> |
||||
<svg |
||||
fill="none" |
||||
height="39" |
||||
viewBox="0 0 41 39" |
||||
width="41" |
||||
xmlns="http://www.w3.org/2000/svg" |
||||
> |
||||
<path |
||||
d="M34.5805 6.55143C36.1394 8.11033 37.3994 9.91222 38.3237 11.8681C38.7066 12.6782 39.0318 13.5148 39.2968 14.3714L39.3242 14.4606C39.4816 14.9785 39.6171 15.5037 39.73 16.0347C40.6511 20.3654 40.0335 24.9029 37.9511 28.8509C35.6001 33.308 31.5749 36.6487 26.7609 38.138C24.3773 38.8754 21.8717 39.1361 19.3873 38.9052C16.9029 38.6743 14.4883 37.9563 12.2813 36.7922C10.2608 35.7264 8.52045 34.3588 7.08333 32.7636C5.61245 31.1448 4.43054 29.2824 3.59178 27.2574C3.32329 26.6092 2.89685 25.2767 2.89685 25.2767H6.89583C7.68667 27.4068 8.96033 29.362 10.6288 30.9609L10.6318 30.96C11.5096 31.8005 12.501 32.5516 13.595 33.1786C15.8742 34.483 18.4736 35.1864 21.1455 35.1864C25.1768 35.1864 29.0429 33.585 31.8935 30.7345C34.744 27.8839 36.3455 24.0177 36.3455 19.9865C36.3455 18.7267 36.1891 17.483 35.8871 16.2814C35.8206 16.017 35.7471 15.7546 35.6666 15.4945C34.4752 11.6432 31.8027 8.42306 28.237 6.54231C24.6713 4.66156 20.5045 4.27429 16.6533 5.46571C12.8837 6.63189 9.71868 9.21706 7.82253 12.6699L12.8961 15.307L3.28331 18.3447L0.245605 8.73191L4.45005 10.9171C6.81537 6.56278 10.7905 3.30177 15.5303 1.83546C17.3658 1.26761 19.2587 0.986653 21.1455 0.986694C26.1846 0.986694 31.0173 2.98824 34.5805 6.55143Z" |
||||
fill="#037DD6" |
||||
/> |
||||
<path |
||||
clip-rule="evenodd" |
||||
d="M18.1252 22.1036L14.7107 18.689L12.0926 21.3257L18.1252 27.3584L30.2322 15.2515L27.614 12.6148L18.1252 22.1036Z" |
||||
fill="#037DD6" |
||||
fill-rule="evenodd" |
||||
/> |
||||
</svg> |
||||
</div> |
||||
`; |
@ -0,0 +1,22 @@ |
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP |
||||
|
||||
exports[`RevertedIcon renders the RevertedIcon component 1`] = ` |
||||
<div> |
||||
<svg |
||||
fill="none" |
||||
height="39" |
||||
viewBox="0 0 41 39" |
||||
width="41" |
||||
xmlns="http://www.w3.org/2000/svg" |
||||
> |
||||
<path |
||||
d="M34.6806 6.55143C36.2395 8.11033 37.4995 9.91222 38.4238 11.8681C38.8067 12.6782 39.1319 13.5148 39.3969 14.3714L39.4243 14.4606C39.5817 14.9785 39.7172 15.5037 39.8301 16.0347C40.7512 20.3654 40.1336 24.9029 38.0512 28.8509C35.7002 33.308 31.675 36.6487 26.861 38.138C24.4773 38.8754 21.9718 39.1361 19.4874 38.9052C17.003 38.6743 14.5884 37.9563 12.3814 36.7922C10.3609 35.7264 8.62055 34.3588 7.18343 32.7636C5.71254 31.1448 4.53064 29.2824 3.69188 27.2574C3.42339 26.6092 2.99695 25.2767 2.99695 25.2767H6.99592C7.78677 27.4068 9.06042 29.362 10.7289 30.9609L10.7319 30.96C11.6097 31.8005 12.6011 32.5516 13.6951 33.1786C15.9743 34.483 18.5737 35.1864 21.2456 35.1864C25.2769 35.1864 29.143 33.585 31.9936 30.7345C34.8441 27.8839 36.4456 24.0177 36.4456 19.9865C36.4456 18.7267 36.2892 17.483 35.9872 16.2814C35.9207 16.017 35.8471 15.7546 35.7667 15.4945C34.5753 11.6432 31.9028 8.42306 28.3371 6.54231C24.7714 4.66156 20.6046 4.27429 16.7534 5.46571C12.9838 6.63189 9.81878 9.21706 7.92263 12.6699L12.9962 15.307L3.3834 18.3447L0.345703 8.73191L4.55015 10.9171C6.91547 6.56278 10.8906 3.30177 15.6304 1.83546C17.4659 1.26761 19.3588 0.986653 21.2456 0.986694C26.2847 0.986694 31.1174 2.98824 34.6806 6.55143Z" |
||||
fill="#D73A49" |
||||
/> |
||||
<path |
||||
d="M18.5849 19.9869L15.1454 23.4264L17.9845 26.2655L21.424 22.826L24.8635 26.2655L27.7026 23.4264L24.2631 19.9869L27.7026 16.5473L24.8635 13.7082L21.424 17.1478L17.9845 13.7082L15.1454 16.5473L18.5849 19.9869Z" |
||||
fill="#D73A49" |
||||
/> |
||||
</svg> |
||||
</div> |
||||
`; |
@ -0,0 +1,18 @@ |
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP |
||||
|
||||
exports[`SuccessIcon renders the SuccessIcon component 1`] = ` |
||||
<div> |
||||
<svg |
||||
fill="none" |
||||
height="39" |
||||
viewBox="0 0 39 39" |
||||
width="39" |
||||
xmlns="http://www.w3.org/2000/svg" |
||||
> |
||||
<path |
||||
d="M34.5457 19.8983C34.5457 23.9296 32.9443 27.7958 30.0937 30.6463C27.2432 33.4969 23.377 35.0983 19.3457 35.0983C15.3144 35.0983 11.4482 33.4969 8.59768 30.6463C5.74713 27.7958 4.1457 23.9296 4.1457 19.8983C4.1457 15.867 5.74713 12.0008 8.59768 9.15029C11.4482 6.29974 15.3144 4.69832 19.3457 4.69832C20.7897 4.69832 22.1957 4.90732 23.5257 5.28732L26.5087 2.30432C24.3047 1.39232 21.8917 0.898315 19.3457 0.898315C16.8506 0.898315 14.3799 1.38977 12.0747 2.3446C9.76953 3.29944 7.67499 4.69897 5.91067 6.46329C2.34748 10.0265 0.345703 14.8592 0.345703 19.8983C0.345703 24.9374 2.34748 29.7702 5.91067 33.3333C7.67499 35.0977 9.76953 36.4972 12.0747 37.452C14.3799 38.4069 16.8506 38.8983 19.3457 38.8983C24.3848 38.8983 29.2175 36.8965 32.7807 33.3333C36.3439 29.7702 38.3457 24.9374 38.3457 19.8983H34.5457ZM11.5747 16.2503L8.8957 18.9483L17.4457 27.4983L36.4457 8.49832L33.7667 5.80032L17.4457 22.1213L11.5747 16.2503Z" |
||||
fill="#28A745" |
||||
/> |
||||
</svg> |
||||
</div> |
||||
`; |
@ -0,0 +1,18 @@ |
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP |
||||
|
||||
exports[`TimerIcon renders the TimerIcon component 1`] = ` |
||||
<div> |
||||
<svg |
||||
fill="none" |
||||
height="14" |
||||
viewBox="0 0 15 14" |
||||
width="15" |
||||
xmlns="http://www.w3.org/2000/svg" |
||||
> |
||||
<path |
||||
d="M7.36621 0.0810547C3.62012 0.0810547 0.584961 3.11621 0.584961 6.8623C0.584961 10.6084 3.62012 13.6436 7.36621 13.6436C11.1123 13.6436 14.1475 10.6084 14.1475 6.8623C14.1475 3.11621 11.1123 0.0810547 7.36621 0.0810547ZM7.36621 12.3311C4.33105 12.3311 1.89746 9.89746 1.89746 6.8623C1.89746 3.85449 4.33105 1.39355 7.36621 1.39355C10.374 1.39355 12.835 3.85449 12.835 6.8623C12.835 9.89746 10.374 12.3311 7.36621 12.3311ZM9.03418 9.4873C9.19824 9.59668 9.38965 9.56934 9.49902 9.40527L10.0186 8.72168C10.1279 8.55762 10.1006 8.36621 9.93652 8.25684L8.13184 6.91699V3.03418C8.13184 2.87012 7.96777 2.70605 7.80371 2.70605H6.92871C6.7373 2.70605 6.60059 2.87012 6.60059 3.03418V7.5459C6.60059 7.62793 6.62793 7.7373 6.70996 7.79199L9.03418 9.4873Z" |
||||
fill="#037DD6" |
||||
/> |
||||
</svg> |
||||
</div> |
||||
`; |
@ -0,0 +1,25 @@ |
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP |
||||
|
||||
exports[`UnknownIcon renders the UnknownIcon component 1`] = ` |
||||
<div> |
||||
<svg |
||||
fill="none" |
||||
height="39" |
||||
viewBox="0 0 39 39" |
||||
width="39" |
||||
xmlns="http://www.w3.org/2000/svg" |
||||
> |
||||
<circle |
||||
cx="19.1533" |
||||
cy="19.2715" |
||||
r="17.1" |
||||
stroke="#037DD6" |
||||
stroke-width="3.8" |
||||
/> |
||||
<path |
||||
d="M16.712 21.5034C16.712 20.7834 16.88 20.1834 17.216 19.7034C17.568 19.2074 18.08 18.7274 18.752 18.2634C19.008 18.0874 19.264 17.9274 19.52 17.7834C19.776 17.6394 20 17.4874 20.192 17.3274C20.4 17.1514 20.56 16.9674 20.672 16.7754C20.8 16.5674 20.864 16.3194 20.864 16.0314C20.864 15.5354 20.688 15.1594 20.336 14.9034C20 14.6474 19.592 14.5194 19.112 14.5194C18.616 14.5194 18.16 14.6794 17.744 14.9994C17.344 15.3034 17.144 15.7674 17.144 16.3914H13.52C13.52 15.6394 13.664 14.9354 13.952 14.2794C14.24 13.6074 14.632 13.0314 15.128 12.5514C15.64 12.0554 16.232 11.6634 16.904 11.3754C17.592 11.0874 18.328 10.9434 19.112 10.9434C19.896 10.9434 20.616 11.0794 21.272 11.3514C21.944 11.6234 22.512 11.9834 22.976 12.4314C23.456 12.8794 23.824 13.3994 24.08 13.9914C24.352 14.5674 24.488 15.1754 24.488 15.8154C24.488 16.2314 24.456 16.6234 24.392 16.9914C24.344 17.3594 24.24 17.7114 24.08 18.0474C23.936 18.3834 23.728 18.7114 23.456 19.0314C23.184 19.3514 22.84 19.6634 22.424 19.9674C21.784 20.4474 21.272 20.8234 20.888 21.0954C20.52 21.3674 20.336 21.6474 20.336 21.9354V21.9594H16.712V21.5034ZM18.512 28.4634C18.16 28.4634 17.824 28.3994 17.504 28.2714C17.2 28.1274 16.928 27.9434 16.688 27.7194C16.464 27.4794 16.28 27.2074 16.136 26.9034C16.008 26.5834 15.944 26.2474 15.944 25.8954C15.944 25.5434 16.008 25.2154 16.136 24.9114C16.28 24.5914 16.464 24.3194 16.688 24.0954C16.928 23.8554 17.2 23.6714 17.504 23.5434C17.824 23.3994 18.16 23.3274 18.512 23.3274C18.864 23.3274 19.192 23.3994 19.496 23.5434C19.816 23.6714 20.088 23.8554 20.312 24.0954C20.552 24.3194 20.736 24.5914 20.864 24.9114C21.008 25.2154 21.08 25.5434 21.08 25.8954C21.08 26.2474 21.008 26.5834 20.864 26.9034C20.736 27.2074 20.552 27.4794 20.312 27.7194C20.088 27.9434 19.816 28.1274 19.496 28.2714C19.192 28.3994 18.864 28.4634 18.512 28.4634Z" |
||||
fill="#037DD6" |
||||
/> |
||||
</svg> |
||||
</div> |
||||
`; |
@ -0,0 +1,18 @@ |
||||
import React from 'react'; |
||||
|
||||
export default function ArrowIcon() { |
||||
return ( |
||||
<svg |
||||
width="15" |
||||
height="13" |
||||
viewBox="0 0 15 13" |
||||
fill="none" |
||||
xmlns="http://www.w3.org/2000/svg" |
||||
> |
||||
<path |
||||
d="M14.4946 6.14779C14.4863 5.93594 14.3991 5.69108 14.2539 5.53666L9.51334 0.49958C9.1921 0.21091 8.57613 0.0917437 8.21709 0.425509C7.86344 0.754237 7.87396 1.39178 8.22627 1.72181L11.5595 5.25889L1.1618 5.25889C0.670919 5.25889 0.272949 5.65687 0.272949 6.14779C0.272949 6.6387 0.670919 7.03668 1.1618 7.03668L11.5595 7.03668L8.22627 10.5738C7.92297 10.8776 7.86691 11.5376 8.21709 11.8701C8.56718 12.2025 9.20529 12.0963 9.51334 11.796L14.2539 6.75891C14.4161 6.58653 14.4952 6.38428 14.4946 6.14779Z" |
||||
fill="#D6D9DC" |
||||
/> |
||||
</svg> |
||||
); |
||||
} |
@ -0,0 +1,11 @@ |
||||
import React from 'react'; |
||||
|
||||
import { renderWithProvider } from '../../../../test/jest'; |
||||
import ArrowIcon from './arrow-icon'; |
||||
|
||||
describe('ArrowIcon', () => { |
||||
it('renders the ArrowIcon component', () => { |
||||
const { container } = renderWithProvider(<ArrowIcon />); |
||||
expect(container).toMatchSnapshot(); |
||||
}); |
||||
}); |
@ -0,0 +1,24 @@ |
||||
import React from 'react'; |
||||
|
||||
export default function CanceledIcon() { |
||||
return ( |
||||
<svg |
||||
width="41" |
||||
height="39" |
||||
viewBox="0 0 41 39" |
||||
fill="none" |
||||
xmlns="http://www.w3.org/2000/svg" |
||||
> |
||||
<path |
||||
d="M34.5805 6.55143C36.1394 8.11033 37.3994 9.91222 38.3237 11.8681C38.7066 12.6782 39.0318 13.5148 39.2968 14.3714L39.3242 14.4606C39.4816 14.9785 39.6171 15.5037 39.73 16.0347C40.6511 20.3654 40.0335 24.9029 37.9511 28.8509C35.6001 33.308 31.5749 36.6487 26.7609 38.138C24.3773 38.8754 21.8717 39.1361 19.3873 38.9052C16.9029 38.6743 14.4883 37.9563 12.2813 36.7922C10.2608 35.7264 8.52045 34.3588 7.08333 32.7636C5.61245 31.1448 4.43054 29.2824 3.59178 27.2574C3.32329 26.6092 2.89685 25.2767 2.89685 25.2767H6.89583C7.68667 27.4068 8.96033 29.362 10.6288 30.9609L10.6318 30.96C11.5096 31.8005 12.501 32.5516 13.595 33.1786C15.8742 34.483 18.4736 35.1864 21.1455 35.1864C25.1768 35.1864 29.0429 33.585 31.8935 30.7345C34.744 27.8839 36.3455 24.0177 36.3455 19.9865C36.3455 18.7267 36.1891 17.483 35.8871 16.2814C35.8206 16.017 35.7471 15.7546 35.6666 15.4945C34.4752 11.6432 31.8027 8.42306 28.237 6.54231C24.6713 4.66156 20.5045 4.27429 16.6533 5.46571C12.8837 6.63189 9.71868 9.21706 7.82253 12.6699L12.8961 15.307L3.28331 18.3447L0.245605 8.73191L4.45005 10.9171C6.81537 6.56278 10.7905 3.30177 15.5303 1.83546C17.3658 1.26761 19.2587 0.986653 21.1455 0.986694C26.1846 0.986694 31.0173 2.98824 34.5805 6.55143Z" |
||||
fill="#037DD6" |
||||
/> |
||||
<path |
||||
fillRule="evenodd" |
||||
clipRule="evenodd" |
||||
d="M18.1252 22.1036L14.7107 18.689L12.0926 21.3257L18.1252 27.3584L30.2322 15.2515L27.614 12.6148L18.1252 22.1036Z" |
||||
fill="#037DD6" |
||||
/> |
||||
</svg> |
||||
); |
||||
} |
@ -0,0 +1,11 @@ |
||||
import React from 'react'; |
||||
|
||||
import { renderWithProvider } from '../../../../test/jest'; |
||||
import CanceledIcon from './canceled-icon'; |
||||
|
||||
describe('CanceledIcon', () => { |
||||
it('renders the CanceledIcon component', () => { |
||||
const { container } = renderWithProvider(<CanceledIcon />); |
||||
expect(container).toMatchSnapshot(); |
||||
}); |
||||
}); |
@ -0,0 +1 @@ |
||||
export { default } from './smart-transaction-status'; |
@ -0,0 +1,84 @@ |
||||
@keyframes shift { |
||||
to { |
||||
background-position: 100% 0; |
||||
} |
||||
} |
||||
|
||||
.smart-transaction-status { |
||||
display: flex; |
||||
flex-flow: column; |
||||
align-items: center; |
||||
flex: 1; |
||||
width: 100%; |
||||
|
||||
&__loading-bar-container { |
||||
height: 3px; |
||||
background: var(--Grey-100); |
||||
display: flex; |
||||
margin-top: 12px; |
||||
margin-bottom: 28px; |
||||
} |
||||
|
||||
&__loading-bar { |
||||
height: 3px; |
||||
background: var(--Blue-500); |
||||
transition: width 0.5s linear; |
||||
} |
||||
|
||||
div { |
||||
text-align: center; |
||||
} |
||||
|
||||
&__content { |
||||
flex-flow: column; |
||||
width: 100%; |
||||
} |
||||
|
||||
&__background-animation { |
||||
position: relative; |
||||
left: -88px; |
||||
background-repeat: repeat; |
||||
background-position: 0 0; |
||||
|
||||
&--top { |
||||
width: 1634px; |
||||
height: 54px; |
||||
background-size: 817px 54px; |
||||
background-image: url('/images/transaction-background-top.svg'); |
||||
animation: shift 19s linear infinite; |
||||
} |
||||
|
||||
&--bottom { |
||||
width: 1600px; |
||||
height: 62px; |
||||
background-size: 800px 62px; |
||||
background-image: url('/images/transaction-background-bottom.svg'); |
||||
animation: shift 22s linear infinite; |
||||
} |
||||
} |
||||
|
||||
a { |
||||
color: var(--Blue-500); |
||||
} |
||||
|
||||
&__support-link { |
||||
color: var(--Blue-500); |
||||
margin-top: 24px; |
||||
cursor: pointer; |
||||
} |
||||
|
||||
&__cancel-swap-link { |
||||
font-size: $font-size-h7; |
||||
} |
||||
|
||||
&__swaps-footer { |
||||
.btn-secondary { |
||||
color: var(--ui-4); |
||||
border: 1px solid var(--ui-4); |
||||
} |
||||
} |
||||
|
||||
&__remaining-time { |
||||
font-variant-numeric: tabular-nums; |
||||
} |
||||
} |
@ -0,0 +1,22 @@ |
||||
import React from 'react'; |
||||
|
||||
export default function RevertedIcon() { |
||||
return ( |
||||
<svg |
||||
width="41" |
||||
height="39" |
||||
viewBox="0 0 41 39" |
||||
fill="none" |
||||
xmlns="http://www.w3.org/2000/svg" |
||||
> |
||||
<path |
||||
d="M34.6806 6.55143C36.2395 8.11033 37.4995 9.91222 38.4238 11.8681C38.8067 12.6782 39.1319 13.5148 39.3969 14.3714L39.4243 14.4606C39.5817 14.9785 39.7172 15.5037 39.8301 16.0347C40.7512 20.3654 40.1336 24.9029 38.0512 28.8509C35.7002 33.308 31.675 36.6487 26.861 38.138C24.4773 38.8754 21.9718 39.1361 19.4874 38.9052C17.003 38.6743 14.5884 37.9563 12.3814 36.7922C10.3609 35.7264 8.62055 34.3588 7.18343 32.7636C5.71254 31.1448 4.53064 29.2824 3.69188 27.2574C3.42339 26.6092 2.99695 25.2767 2.99695 25.2767H6.99592C7.78677 27.4068 9.06042 29.362 10.7289 30.9609L10.7319 30.96C11.6097 31.8005 12.6011 32.5516 13.6951 33.1786C15.9743 34.483 18.5737 35.1864 21.2456 35.1864C25.2769 35.1864 29.143 33.585 31.9936 30.7345C34.8441 27.8839 36.4456 24.0177 36.4456 19.9865C36.4456 18.7267 36.2892 17.483 35.9872 16.2814C35.9207 16.017 35.8471 15.7546 35.7667 15.4945C34.5753 11.6432 31.9028 8.42306 28.3371 6.54231C24.7714 4.66156 20.6046 4.27429 16.7534 5.46571C12.9838 6.63189 9.81878 9.21706 7.92263 12.6699L12.9962 15.307L3.3834 18.3447L0.345703 8.73191L4.55015 10.9171C6.91547 6.56278 10.8906 3.30177 15.6304 1.83546C17.4659 1.26761 19.3588 0.986653 21.2456 0.986694C26.2847 0.986694 31.1174 2.98824 34.6806 6.55143Z" |
||||
fill="#D73A49" |
||||
/> |
||||
<path |
||||
d="M18.5849 19.9869L15.1454 23.4264L17.9845 26.2655L21.424 22.826L24.8635 26.2655L27.7026 23.4264L24.2631 19.9869L27.7026 16.5473L24.8635 13.7082L21.424 17.1478L17.9845 13.7082L15.1454 16.5473L18.5849 19.9869Z" |
||||
fill="#D73A49" |
||||
/> |
||||
</svg> |
||||
); |
||||
} |
@ -0,0 +1,11 @@ |
||||
import React from 'react'; |
||||
|
||||
import { renderWithProvider } from '../../../../test/jest'; |
||||
import RevertedIcon from './reverted-icon'; |
||||
|
||||
describe('RevertedIcon', () => { |
||||
it('renders the RevertedIcon component', () => { |
||||
const { container } = renderWithProvider(<RevertedIcon />); |
||||
expect(container).toMatchSnapshot(); |
||||
}); |
||||
}); |
@ -0,0 +1,409 @@ |
||||
import React, { useContext, useEffect, useState } from 'react'; |
||||
import { useDispatch, useSelector } from 'react-redux'; |
||||
import { useHistory } from 'react-router-dom'; |
||||
|
||||
import { I18nContext } from '../../../contexts/i18n'; |
||||
import { useNewMetricEvent } from '../../../hooks/useMetricEvent'; |
||||
import { |
||||
getFetchParams, |
||||
prepareToLeaveSwaps, |
||||
getCurrentSmartTransactions, |
||||
getSelectedQuote, |
||||
getTopQuote, |
||||
getSmartTransactionsOptInStatus, |
||||
getSmartTransactionsEnabled, |
||||
getCurrentSmartTransactionsEnabled, |
||||
getSwapsRefreshStates, |
||||
cancelSwapsSmartTransaction, |
||||
} from '../../../ducks/swaps/swaps'; |
||||
import { |
||||
isHardwareWallet, |
||||
getHardwareWalletType, |
||||
} from '../../../selectors/selectors'; |
||||
import { |
||||
DEFAULT_ROUTE, |
||||
BUILD_QUOTE_ROUTE, |
||||
} from '../../../helpers/constants/routes'; |
||||
import Typography from '../../../components/ui/typography'; |
||||
import Box from '../../../components/ui/box'; |
||||
import UrlIcon from '../../../components/ui/url-icon'; |
||||
import { |
||||
BLOCK_SIZES, |
||||
COLORS, |
||||
TYPOGRAPHY, |
||||
JUSTIFY_CONTENT, |
||||
DISPLAY, |
||||
FONT_WEIGHT, |
||||
ALIGN_ITEMS, |
||||
} from '../../../helpers/constants/design-system'; |
||||
import { |
||||
stopPollingForQuotes, |
||||
setBackgroundSwapRouteState, |
||||
} from '../../../store/actions'; |
||||
import { SMART_TRANSACTION_STATUSES } from '../../../../shared/constants/transaction'; |
||||
|
||||
import SwapsFooter from '../swaps-footer'; |
||||
import { calcTokenAmount } from '../../../helpers/utils/token-util'; |
||||
import { showRemainingTimeInMinAndSec } from '../swaps.util'; |
||||
import SuccessIcon from './success-icon'; |
||||
import RevertedIcon from './reverted-icon'; |
||||
import CanceledIcon from './canceled-icon'; |
||||
import UnknownIcon from './unknown-icon'; |
||||
import ArrowIcon from './arrow-icon'; |
||||
import TimerIcon from './timer-icon'; |
||||
|
||||
export default function SmartTransactionStatus() { |
||||
const [cancelSwapLinkClicked, setCancelSwapLinkClicked] = useState(false); |
||||
const t = useContext(I18nContext); |
||||
const history = useHistory(); |
||||
const dispatch = useDispatch(); |
||||
const fetchParams = useSelector(getFetchParams) || {}; |
||||
const { destinationTokenInfo = {}, sourceTokenInfo = {} } = |
||||
fetchParams?.metaData || {}; |
||||
const hardwareWalletUsed = useSelector(isHardwareWallet); |
||||
const hardwareWalletType = useSelector(getHardwareWalletType); |
||||
const needsTwoConfirmations = true; |
||||
const selectedQuote = useSelector(getSelectedQuote); |
||||
const topQuote = useSelector(getTopQuote); |
||||
const usedQuote = selectedQuote || topQuote; |
||||
const currentSmartTransactions = useSelector(getCurrentSmartTransactions); |
||||
const smartTransactionsOptInStatus = useSelector( |
||||
getSmartTransactionsOptInStatus, |
||||
); |
||||
const swapsRefreshRates = useSelector(getSwapsRefreshStates); |
||||
const smartTransactionsEnabled = useSelector(getSmartTransactionsEnabled); |
||||
const currentSmartTransactionsEnabled = useSelector( |
||||
getCurrentSmartTransactionsEnabled, |
||||
); |
||||
let smartTransactionStatus = SMART_TRANSACTION_STATUSES.PENDING; |
||||
let latestSmartTransaction = {}; |
||||
let latestSmartTransactionUuid; |
||||
|
||||
if (currentSmartTransactions && currentSmartTransactions.length > 0) { |
||||
latestSmartTransaction = |
||||
currentSmartTransactions[currentSmartTransactions.length - 1]; |
||||
latestSmartTransactionUuid = latestSmartTransaction?.uuid; |
||||
smartTransactionStatus = |
||||
latestSmartTransaction?.status || SMART_TRANSACTION_STATUSES.PENDING; |
||||
} |
||||
|
||||
const [timeLeftForPendingStxInSec, setTimeLeftForPendingStxInSec] = useState( |
||||
swapsRefreshRates.stxStatusDeadline, |
||||
); |
||||
|
||||
const sensitiveProperties = { |
||||
needs_two_confirmations: needsTwoConfirmations, |
||||
token_from: sourceTokenInfo?.symbol, |
||||
token_from_amount: fetchParams?.value, |
||||
token_to: destinationTokenInfo?.symbol, |
||||
request_type: fetchParams?.balanceError ? 'Quote' : 'Order', |
||||
slippage: fetchParams?.slippage, |
||||
custom_slippage: fetchParams?.slippage === 2, |
||||
is_hardware_wallet: hardwareWalletUsed, |
||||
hardware_wallet_type: hardwareWalletType, |
||||
stx_uuid: latestSmartTransactionUuid, |
||||
stx_enabled: smartTransactionsEnabled, |
||||
current_stx_enabled: currentSmartTransactionsEnabled, |
||||
stx_user_opt_in: smartTransactionsOptInStatus, |
||||
}; |
||||
|
||||
let destinationValue; |
||||
if (usedQuote?.destinationAmount) { |
||||
destinationValue = calcTokenAmount( |
||||
usedQuote?.destinationAmount, |
||||
destinationTokenInfo.decimals, |
||||
).toPrecision(8); |
||||
} |
||||
|
||||
const stxStatusPageLoadedEvent = useNewMetricEvent({ |
||||
event: 'STX Status Page Loaded', |
||||
category: 'swaps', |
||||
sensitiveProperties, |
||||
}); |
||||
|
||||
const cancelSmartTransactionEvent = useNewMetricEvent({ |
||||
event: 'Cancel STX', |
||||
category: 'swaps', |
||||
sensitiveProperties, |
||||
}); |
||||
|
||||
const isSmartTransactionPending = |
||||
smartTransactionStatus === SMART_TRANSACTION_STATUSES.PENDING; |
||||
const showCloseButtonOnly = |
||||
isSmartTransactionPending || |
||||
smartTransactionStatus === SMART_TRANSACTION_STATUSES.SUCCESS; |
||||
|
||||
useEffect(() => { |
||||
stxStatusPageLoadedEvent(); |
||||
// eslint-disable-next-line
|
||||
}, []); |
||||
|
||||
useEffect(() => { |
||||
let intervalId; |
||||
if (isSmartTransactionPending && latestSmartTransactionUuid) { |
||||
const calculateRemainingTime = () => { |
||||
const secondsAfterStxSubmission = Math.round( |
||||
(Date.now() - latestSmartTransaction.time) / 1000, |
||||
); |
||||
if (secondsAfterStxSubmission > swapsRefreshRates.stxStatusDeadline) { |
||||
setTimeLeftForPendingStxInSec(0); |
||||
clearInterval(intervalId); |
||||
return; |
||||
} |
||||
setTimeLeftForPendingStxInSec( |
||||
swapsRefreshRates.stxStatusDeadline - secondsAfterStxSubmission, |
||||
); |
||||
}; |
||||
intervalId = setInterval(calculateRemainingTime, 1000); |
||||
calculateRemainingTime(); |
||||
} |
||||
|
||||
return () => clearInterval(intervalId); |
||||
}, [ |
||||
dispatch, |
||||
isSmartTransactionPending, |
||||
latestSmartTransactionUuid, |
||||
latestSmartTransaction.time, |
||||
swapsRefreshRates.stxStatusDeadline, |
||||
]); |
||||
|
||||
useEffect(() => { |
||||
dispatch(setBackgroundSwapRouteState('smartTransactionStatus')); |
||||
setTimeout(() => { |
||||
// We don't need to poll for quotes on the status page.
|
||||
dispatch(stopPollingForQuotes()); |
||||
}, 1000); // Stop polling for quotes after 1s.
|
||||
}, [dispatch]); |
||||
|
||||
let headerText = t('stxPendingOptimizingGas'); |
||||
let description; |
||||
let subDescription; |
||||
let icon; |
||||
if (isSmartTransactionPending) { |
||||
if (timeLeftForPendingStxInSec < 120) { |
||||
headerText = t('stxPendingFinalizing'); |
||||
} else if (timeLeftForPendingStxInSec < 150) { |
||||
headerText = t('stxPendingPrivatelySubmitting'); |
||||
} |
||||
} |
||||
if (smartTransactionStatus === SMART_TRANSACTION_STATUSES.SUCCESS) { |
||||
headerText = t('stxSuccess'); |
||||
description = t('stxSuccessDescription', [destinationTokenInfo?.symbol]); |
||||
icon = <SuccessIcon />; |
||||
} else if (smartTransactionStatus === 'cancelled_user_cancelled') { |
||||
headerText = t('stxUserCancelled'); |
||||
description = t('stxUserCancelledDescription'); |
||||
icon = <CanceledIcon />; |
||||
} else if ( |
||||
smartTransactionStatus.startsWith('cancelled') || |
||||
smartTransactionStatus.includes('deadline_missed') |
||||
) { |
||||
headerText = t('stxCancelled'); |
||||
description = t('stxCancelledDescription'); |
||||
subDescription = t('stxCancelledSubDescription'); |
||||
icon = <CanceledIcon />; |
||||
} else if (smartTransactionStatus === 'unknown') { |
||||
headerText = t('stxUnknown'); |
||||
description = t('stxUnknownDescription'); |
||||
icon = <UnknownIcon />; |
||||
} else if (smartTransactionStatus === 'reverted') { |
||||
headerText = t('stxFailure'); |
||||
description = t('stxFailureDescription', [ |
||||
<a |
||||
className="smart-transaction-status__support-link" |
||||
key="smart-transaction-status-support-link" |
||||
href="https://support.metamask.io" |
||||
target="_blank" |
||||
rel="noopener noreferrer" |
||||
> |
||||
{t('customerSupport')} |
||||
</a>, |
||||
]); |
||||
icon = <RevertedIcon />; |
||||
} |
||||
|
||||
const showCancelSwapLink = |
||||
latestSmartTransaction.cancellable && !cancelSwapLinkClicked; |
||||
|
||||
const CancelSwap = () => { |
||||
return ( |
||||
<Box marginBottom={0}> |
||||
<a |
||||
className="smart-transaction-status__cancel-swap-link" |
||||
href="#" |
||||
onClick={(e) => { |
||||
e?.preventDefault(); |
||||
setCancelSwapLinkClicked(true); // We want to hide it after a user clicks on it.
|
||||
cancelSmartTransactionEvent(); |
||||
dispatch(cancelSwapsSmartTransaction(latestSmartTransactionUuid)); |
||||
}} |
||||
> |
||||
{t('cancelSwap')} |
||||
</a> |
||||
</Box> |
||||
); |
||||
}; |
||||
|
||||
return ( |
||||
<div className="smart-transaction-status"> |
||||
<Box |
||||
paddingLeft={8} |
||||
paddingRight={8} |
||||
height={BLOCK_SIZES.FULL} |
||||
justifyContent={JUSTIFY_CONTENT.START} |
||||
display={DISPLAY.FLEX} |
||||
className="smart-transaction-status__content" |
||||
> |
||||
<Box |
||||
marginTop={10} |
||||
marginBottom={0} |
||||
display={DISPLAY.FLEX} |
||||
justifyContent={JUSTIFY_CONTENT.CENTER} |
||||
alignItems={ALIGN_ITEMS.CENTER} |
||||
> |
||||
<Typography color={COLORS.UI4} variant={TYPOGRAPHY.H6}> |
||||
{`${fetchParams?.value && Number(fetchParams.value).toFixed(5)} `} |
||||
</Typography> |
||||
<Typography |
||||
color={COLORS.UI4} |
||||
variant={TYPOGRAPHY.H6} |
||||
fontWeight={FONT_WEIGHT.BOLD} |
||||
boxProps={{ marginLeft: 1, marginRight: 2 }} |
||||
> |
||||
{sourceTokenInfo?.symbol} |
||||
</Typography> |
||||
<UrlIcon |
||||
url={sourceTokenInfo.iconUrl} |
||||
className="main-quote-summary__icon" |
||||
name={sourceTokenInfo.symbol} |
||||
fallbackClassName="main-quote-summary__icon-fallback" |
||||
/> |
||||
<Box display={DISPLAY.BLOCK} marginLeft={2} marginRight={2}> |
||||
<ArrowIcon /> |
||||
</Box> |
||||
<UrlIcon |
||||
url={destinationTokenInfo.iconUrl} |
||||
className="main-quote-summary__icon" |
||||
name={destinationTokenInfo.symbol} |
||||
fallbackClassName="main-quote-summary__icon-fallback" |
||||
/> |
||||
<Typography |
||||
color={COLORS.UI4} |
||||
variant={TYPOGRAPHY.H6} |
||||
boxProps={{ marginLeft: 2 }} |
||||
> |
||||
{`~${destinationValue && Number(destinationValue).toFixed(5)} `} |
||||
</Typography> |
||||
<Typography |
||||
color={COLORS.UI4} |
||||
variant={TYPOGRAPHY.H6} |
||||
fontWeight={FONT_WEIGHT.BOLD} |
||||
boxProps={{ marginLeft: 1 }} |
||||
> |
||||
{destinationTokenInfo?.symbol} |
||||
</Typography> |
||||
</Box> |
||||
<Box |
||||
marginTop={3} |
||||
className="smart-transaction-status__background-animation smart-transaction-status__background-animation--top" |
||||
></Box> |
||||
{icon && ( |
||||
<Box marginTop={3} marginBottom={2}> |
||||
{icon} |
||||
</Box> |
||||
)} |
||||
{isSmartTransactionPending && ( |
||||
<Box |
||||
marginTop={7} |
||||
marginBottom={1} |
||||
display={DISPLAY.FLEX} |
||||
justifyContent={JUSTIFY_CONTENT.CENTER} |
||||
alignItems={ALIGN_ITEMS.CENTER} |
||||
> |
||||
<TimerIcon /> |
||||
<Typography |
||||
color={COLORS.UI4} |
||||
variant={TYPOGRAPHY.H6} |
||||
boxProps={{ marginLeft: 1 }} |
||||
> |
||||
{`${t('swapCompleteIn')} `} |
||||
</Typography> |
||||
<Typography |
||||
color={COLORS.UI4} |
||||
variant={TYPOGRAPHY.H6} |
||||
fontWeight={FONT_WEIGHT.BOLD} |
||||
boxProps={{ marginLeft: 1 }} |
||||
className="smart-transaction-status__remaining-time" |
||||
> |
||||
{showRemainingTimeInMinAndSec(timeLeftForPendingStxInSec)} |
||||
</Typography> |
||||
</Box> |
||||
)} |
||||
<Typography |
||||
color={COLORS.BLACK} |
||||
variant={TYPOGRAPHY.H4} |
||||
fontWeight={FONT_WEIGHT.BOLD} |
||||
> |
||||
{headerText} |
||||
</Typography> |
||||
{isSmartTransactionPending && ( |
||||
<div className="smart-transaction-status__loading-bar-container"> |
||||
<div |
||||
className="smart-transaction-status__loading-bar" |
||||
style={{ |
||||
width: `${ |
||||
(100 / swapsRefreshRates.stxStatusDeadline) * |
||||
(swapsRefreshRates.stxStatusDeadline - |
||||
timeLeftForPendingStxInSec) |
||||
}%`,
|
||||
}} |
||||
/> |
||||
</div> |
||||
)} |
||||
{description && ( |
||||
<Typography |
||||
variant={TYPOGRAPHY.H6} |
||||
boxProps={{ marginTop: 0 }} |
||||
color={COLORS.UI4} |
||||
> |
||||
{description} |
||||
</Typography> |
||||
)} |
||||
<Box |
||||
marginTop={3} |
||||
className="smart-transaction-status__background-animation smart-transaction-status__background-animation--bottom" |
||||
></Box> |
||||
{subDescription && ( |
||||
<Typography |
||||
variant={TYPOGRAPHY.H7} |
||||
boxProps={{ marginTop: 8 }} |
||||
color={COLORS.UI4} |
||||
> |
||||
{subDescription} |
||||
</Typography> |
||||
)} |
||||
</Box> |
||||
{showCancelSwapLink && |
||||
latestSmartTransactionUuid && |
||||
isSmartTransactionPending && <CancelSwap />} |
||||
<SwapsFooter |
||||
onSubmit={async () => { |
||||
if (showCloseButtonOnly) { |
||||
await dispatch(prepareToLeaveSwaps()); |
||||
history.push(DEFAULT_ROUTE); |
||||
} else { |
||||
history.push(BUILD_QUOTE_ROUTE); |
||||
} |
||||
}} |
||||
onCancel={async () => { |
||||
await dispatch(prepareToLeaveSwaps()); |
||||
history.push(DEFAULT_ROUTE); |
||||
}} |
||||
submitText={showCloseButtonOnly ? t('close') : t('tryAgain')} |
||||
hideCancel={showCloseButtonOnly} |
||||
cancelText={t('close')} |
||||
className="smart-transaction-status__swaps-footer" |
||||
/> |
||||
</div> |
||||
); |
||||
} |
@ -0,0 +1,10 @@ |
||||
import React from 'react'; |
||||
import SmartTransactionStatus from './smart-transaction-status'; |
||||
|
||||
export default { |
||||
title: 'SmartTransactionStatus', |
||||
}; |
||||
|
||||
export const SmartTransactionStatusComponent = () => { |
||||
return <SmartTransactionStatus />; |
||||
}; |
@ -0,0 +1,25 @@ |
||||
import React from 'react'; |
||||
import configureMockStore from 'redux-mock-store'; |
||||
import thunk from 'redux-thunk'; |
||||
|
||||
import { |
||||
renderWithProvider, |
||||
createSwapsMockStore, |
||||
setBackgroundConnection, |
||||
} from '../../../../test/jest'; |
||||
import SmartTransactionStatus from '.'; |
||||
|
||||
const middleware = [thunk]; |
||||
setBackgroundConnection({ |
||||
stopPollingForQuotes: jest.fn(), |
||||
setBackgroundSwapRouteState: jest.fn(), |
||||
}); |
||||
|
||||
describe('SmartTransactionStatus', () => { |
||||
it('renders the component with initial props', () => { |
||||
const store = configureMockStore(middleware)(createSwapsMockStore()); |
||||
const { getByText } = renderWithProvider(<SmartTransactionStatus />, store); |
||||
expect(getByText('Optimizing gas...')).toBeInTheDocument(); |
||||
expect(getByText('Close')).toBeInTheDocument(); |
||||
}); |
||||
}); |
@ -0,0 +1,18 @@ |
||||
import React from 'react'; |
||||
|
||||
export default function SuccessIcon() { |
||||
return ( |
||||
<svg |
||||
width="39" |
||||
height="39" |
||||
viewBox="0 0 39 39" |
||||
fill="none" |
||||
xmlns="http://www.w3.org/2000/svg" |
||||
> |
||||
<path |
||||
d="M34.5457 19.8983C34.5457 23.9296 32.9443 27.7958 30.0937 30.6463C27.2432 33.4969 23.377 35.0983 19.3457 35.0983C15.3144 35.0983 11.4482 33.4969 8.59768 30.6463C5.74713 27.7958 4.1457 23.9296 4.1457 19.8983C4.1457 15.867 5.74713 12.0008 8.59768 9.15029C11.4482 6.29974 15.3144 4.69832 19.3457 4.69832C20.7897 4.69832 22.1957 4.90732 23.5257 5.28732L26.5087 2.30432C24.3047 1.39232 21.8917 0.898315 19.3457 0.898315C16.8506 0.898315 14.3799 1.38977 12.0747 2.3446C9.76953 3.29944 7.67499 4.69897 5.91067 6.46329C2.34748 10.0265 0.345703 14.8592 0.345703 19.8983C0.345703 24.9374 2.34748 29.7702 5.91067 33.3333C7.67499 35.0977 9.76953 36.4972 12.0747 37.452C14.3799 38.4069 16.8506 38.8983 19.3457 38.8983C24.3848 38.8983 29.2175 36.8965 32.7807 33.3333C36.3439 29.7702 38.3457 24.9374 38.3457 19.8983H34.5457ZM11.5747 16.2503L8.8957 18.9483L17.4457 27.4983L36.4457 8.49832L33.7667 5.80032L17.4457 22.1213L11.5747 16.2503Z" |
||||
fill="#28A745" |
||||
/> |
||||
</svg> |
||||
); |
||||
} |
@ -0,0 +1,11 @@ |
||||
import React from 'react'; |
||||
|
||||
import { renderWithProvider } from '../../../../test/jest'; |
||||
import SuccessIcon from './success-icon'; |
||||
|
||||
describe('SuccessIcon', () => { |
||||
it('renders the SuccessIcon component', () => { |
||||
const { container } = renderWithProvider(<SuccessIcon />); |
||||
expect(container).toMatchSnapshot(); |
||||
}); |
||||
}); |
@ -0,0 +1,18 @@ |
||||
import React from 'react'; |
||||
|
||||
export default function TimerIcon() { |
||||
return ( |
||||
<svg |
||||
width="15" |
||||
height="14" |
||||
viewBox="0 0 15 14" |
||||
fill="none" |
||||
xmlns="http://www.w3.org/2000/svg" |
||||
> |
||||
<path |
||||
d="M7.36621 0.0810547C3.62012 0.0810547 0.584961 3.11621 0.584961 6.8623C0.584961 10.6084 3.62012 13.6436 7.36621 13.6436C11.1123 13.6436 14.1475 10.6084 14.1475 6.8623C14.1475 3.11621 11.1123 0.0810547 7.36621 0.0810547ZM7.36621 12.3311C4.33105 12.3311 1.89746 9.89746 1.89746 6.8623C1.89746 3.85449 4.33105 1.39355 7.36621 1.39355C10.374 1.39355 12.835 3.85449 12.835 6.8623C12.835 9.89746 10.374 12.3311 7.36621 12.3311ZM9.03418 9.4873C9.19824 9.59668 9.38965 9.56934 9.49902 9.40527L10.0186 8.72168C10.1279 8.55762 10.1006 8.36621 9.93652 8.25684L8.13184 6.91699V3.03418C8.13184 2.87012 7.96777 2.70605 7.80371 2.70605H6.92871C6.7373 2.70605 6.60059 2.87012 6.60059 3.03418V7.5459C6.60059 7.62793 6.62793 7.7373 6.70996 7.79199L9.03418 9.4873Z" |
||||
fill="#037DD6" |
||||
/> |
||||
</svg> |
||||
); |
||||
} |
@ -0,0 +1,11 @@ |
||||
import React from 'react'; |
||||
|
||||
import { renderWithProvider } from '../../../../test/jest'; |
||||
import TimerIcon from './timer-icon'; |
||||
|
||||
describe('TimerIcon', () => { |
||||
it('renders the TimerIcon component', () => { |
||||
const { container } = renderWithProvider(<TimerIcon />); |
||||
expect(container).toMatchSnapshot(); |
||||
}); |
||||
}); |
@ -0,0 +1,25 @@ |
||||
import React from 'react'; |
||||
|
||||
export default function UnknownIcon() { |
||||
return ( |
||||
<svg |
||||
width="39" |
||||
height="39" |
||||
viewBox="0 0 39 39" |
||||
fill="none" |
||||
xmlns="http://www.w3.org/2000/svg" |
||||
> |
||||
<circle |
||||
cx="19.1533" |
||||
cy="19.2715" |
||||
r="17.1" |
||||
stroke="#037DD6" |
||||
strokeWidth="3.8" |
||||
/> |
||||
<path |
||||
d="M16.712 21.5034C16.712 20.7834 16.88 20.1834 17.216 19.7034C17.568 19.2074 18.08 18.7274 18.752 18.2634C19.008 18.0874 19.264 17.9274 19.52 17.7834C19.776 17.6394 20 17.4874 20.192 17.3274C20.4 17.1514 20.56 16.9674 20.672 16.7754C20.8 16.5674 20.864 16.3194 20.864 16.0314C20.864 15.5354 20.688 15.1594 20.336 14.9034C20 14.6474 19.592 14.5194 19.112 14.5194C18.616 14.5194 18.16 14.6794 17.744 14.9994C17.344 15.3034 17.144 15.7674 17.144 16.3914H13.52C13.52 15.6394 13.664 14.9354 13.952 14.2794C14.24 13.6074 14.632 13.0314 15.128 12.5514C15.64 12.0554 16.232 11.6634 16.904 11.3754C17.592 11.0874 18.328 10.9434 19.112 10.9434C19.896 10.9434 20.616 11.0794 21.272 11.3514C21.944 11.6234 22.512 11.9834 22.976 12.4314C23.456 12.8794 23.824 13.3994 24.08 13.9914C24.352 14.5674 24.488 15.1754 24.488 15.8154C24.488 16.2314 24.456 16.6234 24.392 16.9914C24.344 17.3594 24.24 17.7114 24.08 18.0474C23.936 18.3834 23.728 18.7114 23.456 19.0314C23.184 19.3514 22.84 19.6634 22.424 19.9674C21.784 20.4474 21.272 20.8234 20.888 21.0954C20.52 21.3674 20.336 21.6474 20.336 21.9354V21.9594H16.712V21.5034ZM18.512 28.4634C18.16 28.4634 17.824 28.3994 17.504 28.2714C17.2 28.1274 16.928 27.9434 16.688 27.7194C16.464 27.4794 16.28 27.2074 16.136 26.9034C16.008 26.5834 15.944 26.2474 15.944 25.8954C15.944 25.5434 16.008 25.2154 16.136 24.9114C16.28 24.5914 16.464 24.3194 16.688 24.0954C16.928 23.8554 17.2 23.6714 17.504 23.5434C17.824 23.3994 18.16 23.3274 18.512 23.3274C18.864 23.3274 19.192 23.3994 19.496 23.5434C19.816 23.6714 20.088 23.8554 20.312 24.0954C20.552 24.3194 20.736 24.5914 20.864 24.9114C21.008 25.2154 21.08 25.5434 21.08 25.8954C21.08 26.2474 21.008 26.5834 20.864 26.9034C20.736 27.2074 20.552 27.4794 20.312 27.7194C20.088 27.9434 19.816 28.1274 19.496 28.2714C19.192 28.3994 18.864 28.4634 18.512 28.4634Z" |
||||
fill="#037DD6" |
||||
/> |
||||
</svg> |
||||
); |
||||
} |
@ -0,0 +1,11 @@ |
||||
import React from 'react'; |
||||
|
||||
import { renderWithProvider } from '../../../../test/jest'; |
||||
import UnknownIcon from './unknown-icon'; |
||||
|
||||
describe('UnknownIcon', () => { |
||||
it('renders the UnknownIcon component', () => { |
||||
const { container } = renderWithProvider(<UnknownIcon />); |
||||
expect(container).toMatchSnapshot(); |
||||
}); |
||||
}); |
Loading…
Reference in new issue