Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: display bridge quotes #28031

Merged
merged 8 commits into from
Nov 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions app/_locales/en/messages.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions test/jest/mock-store.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { CHAIN_IDS, CURRENCY_SYMBOLS } from '../../shared/constants/network';
import { KeyringType } from '../../shared/constants/keyring';
import { ETH_EOA_METHODS } from '../../shared/constants/eth-methods';
import { mockNetworkState } from '../stub/networks';
import { DEFAULT_BRIDGE_CONTROLLER_STATE } from '../../app/scripts/controllers/bridge/constants';

export const createGetSmartTransactionFeesApiResponse = () => {
return {
Expand Down Expand Up @@ -726,6 +727,8 @@ export const createBridgeMockStore = (
destNetworkAllowlist: [],
...featureFlagOverrides,
},
quotes: DEFAULT_BRIDGE_CONTROLLER_STATE.quotes,
quoteRequest: DEFAULT_BRIDGE_CONTROLLER_STATE.quoteRequest,
...bridgeStateOverrides,
},
},
Expand Down
2 changes: 1 addition & 1 deletion ui/ducks/bridge/selectors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@ describe('Bridge selectors', () => {
const state = createBridgeMockStore();
const result = getToAmount(state as never);

expect(result).toStrictEqual('0');
expect(result).toStrictEqual(undefined);
});
});

Expand Down
37 changes: 35 additions & 2 deletions ui/ducks/bridge/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
NetworkState,
} from '@metamask/network-controller';
import { uniqBy } from 'lodash';
import { createSelector } from 'reselect';
import {
getNetworkConfigurationsByChainId,
getIsBridgeEnabled,
Expand All @@ -19,6 +20,10 @@ import {
import { createDeepEqualSelector } from '../../selectors/util';
import { getProviderConfig } from '../metamask/metamask';
import { SwapsTokenObject } from '../../../shared/constants/swaps';
import { calcTokenAmount } from '../../../shared/lib/transactions-controller-utils';
// TODO: Remove restricted import
// eslint-disable-next-line import/no-restricted-paths
import { RequestStatus } from '../../../app/scripts/controllers/bridge/constants';
import { BridgeState } from './bridge';

type BridgeAppState = {
Expand Down Expand Up @@ -124,10 +129,38 @@ export const getToToken = (

export const getFromAmount = (state: BridgeAppState): string | null =>
state.bridge.fromTokenInputValue;
export const getToAmount = (_state: BridgeAppState) => {
return '0';

export const getBridgeQuotes = (state: BridgeAppState) => {
return {
quotes: state.metamask.bridgeState.quotes,
quotesLastFetchedMs: state.metamask.bridgeState.quotesLastFetched,
isLoading:
state.metamask.bridgeState.quotesLoadingStatus === RequestStatus.LOADING,
};
};

export const getRecommendedQuote = createSelector(
getBridgeQuotes,
({ quotes }) => {
// TODO implement sorting
micaelae marked this conversation as resolved.
Show resolved Hide resolved
return quotes[0];
},
);

export const getQuoteRequest = (state: BridgeAppState) => {
const { quoteRequest } = state.metamask.bridgeState;
return quoteRequest;
};

export const getToAmount = createSelector(getRecommendedQuote, (quote) =>
quote
? calcTokenAmount(
quote.quote.destTokenAmount,
quote.quote.destAsset.decimals,
)
: undefined,
);

export const getIsBridgeTx = createDeepEqualSelector(
getFromChain,
getToChain,
Expand Down
34 changes: 34 additions & 0 deletions ui/hooks/bridge/useCountdownTimer.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { renderHookWithProvider } from '../../../test/lib/render-helpers';
import { createBridgeMockStore } from '../../../test/jest/mock-store';
import { flushPromises } from '../../../test/lib/timer-helpers';
import { useCountdownTimer } from './useCountdownTimer';

jest.useFakeTimers();
const renderUseCountdownTimer = (mockStoreState: object) =>
renderHookWithProvider(() => useCountdownTimer(), mockStoreState);

describe('useCountdownTimer', () => {
beforeEach(() => {
jest.clearAllMocks();
jest.clearAllTimers();
});

it('returns time remaining', async () => {
const quotesLastFetched = Date.now();
const { result } = renderUseCountdownTimer(
createBridgeMockStore({}, {}, { quotesLastFetched }),
);

let i = 0;
while (i <= 30) {
const secondsLeft = Math.min(30, 30 - i + 1);
expect(result.current).toStrictEqual(
`0:${secondsLeft < 10 ? '0' : ''}${secondsLeft}`,
);
i += 10;
jest.advanceTimersByTime(10000);
await flushPromises();
}
expect(result.current).toStrictEqual('0:00');
});
});
37 changes: 37 additions & 0 deletions ui/hooks/bridge/useCountdownTimer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Duration } from 'luxon';
import { useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
import { getBridgeQuotes } from '../../ducks/bridge/selectors';
// TODO: Remove restricted import
// eslint-disable-next-line import/no-restricted-paths
import { REFRESH_INTERVAL_MS } from '../../../app/scripts/controllers/bridge/constants';
import { SECOND } from '../../../shared/constants/time';

/**
* Custom hook that provides a countdown timer based on the last fetched quotes timestamp.
*
* This hook calculates the remaining time until the next refresh interval and updates every second.
*
* @returns The formatted remaining time in 'm:ss' format.
*/
export const useCountdownTimer = () => {
const [timeRemaining, setTimeRemaining] = useState(REFRESH_INTERVAL_MS);
const { quotesLastFetchedMs } = useSelector(getBridgeQuotes);

useEffect(() => {
if (quotesLastFetchedMs) {
setTimeRemaining(
REFRESH_INTERVAL_MS - (Date.now() - quotesLastFetchedMs),
);
}
}, [quotesLastFetchedMs]);

useEffect(() => {
const interval = setInterval(() => {
setTimeRemaining(Math.max(0, timeRemaining - SECOND));
}, SECOND);
return () => clearInterval(interval);
}, [timeRemaining]);

return Duration.fromMillis(timeRemaining).toFormat('m:ss');
};
2 changes: 1 addition & 1 deletion ui/pages/bridge/__snapshots__/index.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ exports[`Bridge renders the component with initial props 1`] = `
data-theme="light"
disabled=""
>
Select token
Select token and amount
</button>
</div>
</div>
Expand Down
2 changes: 2 additions & 0 deletions ui/pages/bridge/index.scss
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
@use "design-system";

@import 'prepare/index';
@import 'quotes/index';


.bridge {
max-height: 100vh;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ exports[`PrepareBridgePage should render the component, with initial state 1`] =
class="mm-box prepare-bridge-page__content"
>
<div
class="mm-box prepare-bridge-page__from"
class="mm-box bridge-box"
>
<div
class="mm-box prepare-bridge-page__input-row"
Expand Down Expand Up @@ -130,7 +130,7 @@ exports[`PrepareBridgePage should render the component, with initial state 1`] =
</button>
</div>
<div
class="mm-box prepare-bridge-page__to"
class="mm-box bridge-box"
>
<div
class="mm-box prepare-bridge-page__input-row"
Expand Down Expand Up @@ -213,7 +213,7 @@ exports[`PrepareBridgePage should render the component, with inputs set 1`] = `
class="mm-box prepare-bridge-page__content"
>
<div
class="mm-box prepare-bridge-page__from"
class="mm-box bridge-box"
>
<div
class="mm-box prepare-bridge-page__input-row"
Expand Down Expand Up @@ -340,7 +340,7 @@ exports[`PrepareBridgePage should render the component, with inputs set 1`] = `
</button>
</div>
<div
class="mm-box prepare-bridge-page__to"
class="mm-box bridge-box"
>
<div
class="mm-box prepare-bridge-page__input-row"
Expand Down
Loading