1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-10-22 19:26:13 +02:00
metamask-extension/ui/pages/swaps/loading-swaps-quotes/loading-swaps-quotes.js
Elliot Winkler 1304ec7af5
Convert shared/constants/metametrics to TS (#18353)
We want to convert NetworkController to TypeScript in order to be able
to compare differences in the controller between in this repo and the
core repo. To do this, however, we need to convert the dependencies of
the controller to TypeScript.

As a part of this effort, this commit converts
`shared/constants/metametrics` to TypeScript. Note that simple objects
have been largely replaced with enums. There are some cases where I even
split up some of these objects into multiple enums.

Co-authored-by: Mark Stacey <markjstacey@gmail.com>
2023-04-03 09:31:04 -06:00

177 lines
6.2 KiB
JavaScript

import EventEmitter from 'events';
import React, { useState, useEffect, useRef, useContext } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import PropTypes from 'prop-types';
import { shuffle } from 'lodash';
import { useHistory } from 'react-router-dom';
import isEqual from 'lodash/isEqual';
import {
navigateBackToBuildQuote,
getFetchParams,
getQuotesFetchStartTime,
getSmartTransactionsOptInStatus,
getSmartTransactionsEnabled,
getCurrentSmartTransactionsEnabled,
} from '../../../ducks/swaps/swaps';
import {
isHardwareWallet,
getHardwareWalletType,
} from '../../../selectors/selectors';
import { I18nContext } from '../../../contexts/i18n';
import { MetaMetricsContext } from '../../../contexts/metametrics';
import Mascot from '../../../components/ui/mascot';
import { MetaMetricsEventCategory } from '../../../../shared/constants/metametrics';
import SwapsFooter from '../swaps-footer';
import BackgroundAnimation from './background-animation';
export default function LoadingSwapsQuotes({
aggregatorMetadata,
loadingComplete,
onDone,
}) {
const t = useContext(I18nContext);
const trackEvent = useContext(MetaMetricsContext);
const dispatch = useDispatch();
const history = useHistory();
const animationEventEmitter = useRef(new EventEmitter());
const fetchParams = useSelector(getFetchParams, isEqual);
const quotesFetchStartTime = useSelector(getQuotesFetchStartTime);
const hardwareWalletUsed = useSelector(isHardwareWallet);
const hardwareWalletType = useSelector(getHardwareWalletType);
const smartTransactionsOptInStatus = useSelector(
getSmartTransactionsOptInStatus,
);
const smartTransactionsEnabled = useSelector(getSmartTransactionsEnabled);
const currentSmartTransactionsEnabled = useSelector(
getCurrentSmartTransactionsEnabled,
);
const quotesRequestCancelledEventConfig = {
event: 'Quotes Request Cancelled',
category: MetaMetricsEventCategory.Swaps,
sensitiveProperties: {
token_from: fetchParams?.sourceTokenInfo?.symbol,
token_from_amount: fetchParams?.value,
request_type: fetchParams?.balanceError,
token_to: fetchParams?.destinationTokenInfo?.symbol,
slippage: fetchParams?.slippage,
custom_slippage: fetchParams?.slippage !== 2,
response_time: Date.now() - quotesFetchStartTime,
is_hardware_wallet: hardwareWalletUsed,
hardware_wallet_type: hardwareWalletType,
stx_enabled: smartTransactionsEnabled,
current_stx_enabled: currentSmartTransactionsEnabled,
stx_user_opt_in: smartTransactionsOptInStatus,
},
};
const [aggregatorNames] = useState(() =>
shuffle(Object.keys(aggregatorMetadata)),
);
const numberOfQuotes = aggregatorNames.length;
const mascotContainer = useRef();
const currentMascotContainer = mascotContainer.current;
const [quoteCount, updateQuoteCount] = useState(0);
const [midPointTarget, setMidpointTarget] = useState(null);
useEffect(() => {
let timeoutLength;
// The below logic simulates a sequential loading of the aggregator quotes, even though we are fetching them all with a single call.
// This is to give the user a sense of progress. The callback passed to `setTimeout` updates the quoteCount and therefore causes
// a new logo to be shown, the fox to look at that logo, the logo bar and aggregator name to update.
if (loadingComplete) {
// If loading is complete, but the quoteCount is not, we quickly display the remaining logos/names/fox looks. 0.2s each
timeoutLength = 20;
} else {
// If loading is not complete, we display remaining logos/names/fox looks at random intervals between 0.5s and 2s, to simulate the
// sort of loading a user would experience in most async scenarios
timeoutLength = 500 + Math.floor(Math.random() * 1500);
}
const quoteCountTimeout = setTimeout(() => {
if (quoteCount < numberOfQuotes) {
updateQuoteCount(quoteCount + 1);
} else if (quoteCount === numberOfQuotes && loadingComplete) {
onDone();
}
}, timeoutLength);
return function cleanup() {
clearTimeout(quoteCountTimeout);
};
}, [quoteCount, loadingComplete, onDone, numberOfQuotes]);
useEffect(() => {
if (currentMascotContainer) {
const { top, left, width, height } =
currentMascotContainer.getBoundingClientRect();
const center = { x: left + width / 2, y: top + height / 2 };
setMidpointTarget(center);
}
}, [currentMascotContainer]);
return (
<div className="loading-swaps-quotes">
<div className="loading-swaps-quotes__content">
<>
<div className="loading-swaps-quotes__quote-counter">
<span>
{t('swapFetchingQuoteNofN', [
Math.min(quoteCount + 1, numberOfQuotes),
numberOfQuotes,
])}
</span>
</div>
<div className="loading-swaps-quotes__quote-name-check">
<span>{t('swapFetchingQuotes')}</span>
</div>
<div className="loading-swaps-quotes__loading-bar-container">
<div
className="loading-swaps-quotes__loading-bar"
style={{
width: `${(100 / numberOfQuotes) * quoteCount}%`,
}}
/>
</div>
</>
<div className="loading-swaps-quotes__animation">
<BackgroundAnimation />
<div
className="loading-swaps-quotes__mascot-container"
ref={mascotContainer}
>
<Mascot
animationEventEmitter={animationEventEmitter.current}
width="90"
height="90"
lookAtTarget={midPointTarget}
/>
</div>
</div>
</div>
<SwapsFooter
submitText={t('back')}
onSubmit={async () => {
trackEvent(quotesRequestCancelledEventConfig);
await dispatch(navigateBackToBuildQuote(history));
}}
hideCancel
/>
</div>
);
}
LoadingSwapsQuotes.propTypes = {
loadingComplete: PropTypes.bool.isRequired,
onDone: PropTypes.func.isRequired,
aggregatorMetadata: PropTypes.objectOf(
PropTypes.shape({
title: PropTypes.string,
color: PropTypes.string,
icon: PropTypes.string,
}),
),
};