Skip to content

Commit

Permalink
chore: src picker working
Browse files Browse the repository at this point in the history
  • Loading branch information
micaelae committed Jun 27, 2024
1 parent b798225 commit b37f80c
Show file tree
Hide file tree
Showing 8 changed files with 283 additions and 26 deletions.
3 changes: 2 additions & 1 deletion shared/constants/bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ export const ALLOWED_BRIDGE_CHAIN_IDS = [

const BRIDGE_DEV_API_BASE_URL = 'https://bridge.dev-api.cx.metamask.io';
const BRIDGE_PROD_API_BASE_URL = 'https://bridge.api.cx.metamask.io';
export const BRIDGE_API_BASE_URL = process.env.BRIDGE_USE_DEV_APIS
export const BRIDGE_API_BASE_URL_ = process.env.BRIDGE_USE_DEV_APIS
? BRIDGE_DEV_API_BASE_URL
: BRIDGE_PROD_API_BASE_URL;

export const BRIDGE_CLIENT_ID = 'extension';
export const BRIDGE_API_BASE_URL = 'http://localhost:4000';
8 changes: 4 additions & 4 deletions ui/ducks/bridge/actions.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { swapsSlice } from '../swaps/swaps';
import { bridgeSlice } from './bridge';

// Bridge actions

// eslint-disable-next-line no-empty-pattern
const {} = swapsSlice.actions;
// Proxied swaps actions
export const { setFromToken, setToToken, setFromTokenInputValue } =
swapsSlice.actions;

// Bridge actions
export const { setToChain } = bridgeSlice.actions;
30 changes: 28 additions & 2 deletions ui/ducks/bridge/selectors.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,35 @@
import { createSelector } from 'reselect';
import { getProviderConfig } from '../metamask/metamask';
import { getIsBridgeEnabled } from '../../selectors';
import { getIsBridgeEnabled, getSwapsDefaultToken } from '../../selectors';
import * as swapsSlice from '../swaps/swaps';
import { ALLOWED_BRIDGE_CHAIN_IDS } from '../../../shared/constants/bridge';

export const getFromChain = (state: any) => getProviderConfig(state);
export const getToChain = (state: any) => state.bridge.toChain;
export const getToChain = (state: any) => {
return state.bridge.toChain;
};
// TODO read from feature flags and return ProviderConfig/RPCDefinition
export const getFromChains = (state: any) => ALLOWED_BRIDGE_CHAIN_IDS;
// TODO read from feature flags and return ProviderConfig/RPCDefinition
export const getToChains = (state: any) => ALLOWED_BRIDGE_CHAIN_IDS;

export const getFromToken = (state: any) => {
const swapsFromToken = swapsSlice.getFromToken(state);
if (!swapsFromToken?.symbol) {
return getSwapsDefaultToken(state);
}
return swapsFromToken;
};
export const getToToken = (state: any) => {
return swapsSlice.getToToken(state);
};

export const getFromAmount = (state: any) =>
swapsSlice.getFromTokenInputValue(state);
export const getToAmount = (state: any) => {
// TODO get toAmount from best or selected bridge controller quote
return '0';
};

export const getIsBridgeTx = createSelector(
getFromChain,
Expand Down
2 changes: 2 additions & 0 deletions ui/hooks/useTokensWithFiltering.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ export const useTokensWithFiltering = <T extends TokenDetails>(
const conversionRate = useSelector(getConversionRate);
const currentCurrency = useSelector(getCurrentCurrency);

// TODO use chainId if defined
const tokenList = useSelector(getTokenList) as Record<string, T>;
// TODO use chainId if defined
const topTokens = useSelector(getTopAssets, isEqual);
const usersTokens = uniqBy([...tokensWithBalances, ...tokens], 'address');

Expand Down
142 changes: 142 additions & 0 deletions ui/pages/bridge/components/multichain-token-picker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import React, { useState } from 'react';
import {
Box,
Button,
Modal,
ModalContent,
ModalHeader,
ModalOverlay,
Text,
TextFieldSearch,
} from '../../../components/component-library';
import { useI18nContext } from '../../../hooks/useI18nContext';
import { useSelector } from 'react-redux';
import {
BlockSize,
Display,
FlexDirection,
TextAlign,
TextVariant,
} from '../../../helpers/constants/design-system';
import ItemList from '../../swaps/searchable-item-list/item-list/item-list.component';
import { getNonTestNetworks, getSwapsDefaultToken } from '../../../selectors';
import { isEqual } from 'lodash';
import { useTokensWithFiltering } from '../../../hooks/useTokensWithFiltering';

// TODO import from token-api
// TODO write type validator for token
type BridgeToken = {};
export const MultiChainTokenPicker = function <T = BridgeToken>({
selectedNetwork,
selectedToken,
networks,
onTokenChange,
onNetworkChange,
}: {
selectedToken: T;
onTokenChange: (token: T) => void;
networks: any[];
selectedNetwork?: any;
onNetworkChange: (chainId: string, id?: string) => void;
}) {
const t = useI18nContext();

const [isOpen, setIsOpen] = useState(false);
const [searchQuery, setSearchQuery] = useState('');

const defaultSwapsToken = useSelector(getSwapsDefaultToken, isEqual);

// TODO use multichain token list - tokensChainsCache ?
const filteredTokens = useTokensWithFiltering(
// TODO handle dest chain native token - use generic default token selector instead?
defaultSwapsToken,
searchQuery,
{},
() => {
// TODO disable blocked tokens
return false;
},
selectedNetwork?.chainId,
);

// TODO does this work for networks the user hasn't added?
const nonTestNetworks = useSelector(getNonTestNetworks);
const onModalClose = () => {
setSearchQuery('');
setIsOpen(false);
};

return (
<Box className="multichain-asset-picker">
<div className="multichain-asset-picker__asset">
<Modal isOpen={isOpen} onClose={onModalClose}>
<ModalOverlay />
<ModalContent modalDialogProps={{ padding: 0 }}>
<ModalHeader onClose={onModalClose}>
<Text
variant={TextVariant.headingSm}
textAlign={TextAlign.Center}
>
{t('bridge')}
</Text>
</ModalHeader>
<Box className="network-list" width={BlockSize.Full} tabIndex="0">
<Box
style={{ gridColumnStart: 1, gridColumnEnd: 3 }}
display={Display.Flex}
flexDirection={FlexDirection.Row}
>
{nonTestNetworks
?.filter(({ chainId }) => networks.includes(chainId))
.map(({ chainId, id, nickname }) => (
<Button onClick={() => onNetworkChange(chainId, id)}>
{nickname}
</Button>
))}
</Box>
</Box>
<Box
className="list-with-search"
width={BlockSize.Full}
tabIndex="0"
>
<Box
style={{ gridColumnStart: 1, gridColumnEnd: 3 }}
display={Display.Flex}
flexDirection={FlexDirection.Column}
>
<TextFieldSearch
id="multichain-asset-picker__asset-search"
marginBottom={4}
onChange={(e) => setSearchQuery(e.target.value)}
clearButtonOnClick={() => setSearchQuery('')}
value={searchQuery}
placeholder={t('enterTokenNameOrAddress')}
inputProps={{ marginRight: 0 }}
className="list-with-search__text-search"
autoFocus
tabIndex="0"
/>
</Box>
{filteredTokens?.length > 0 && (
<ItemList
searchQuery={searchQuery}
results={filteredTokens}
onClickItem={(t) => {
onTokenChange(t);
setIsOpen(false);
}}
/>
)}
</Box>
</ModalContent>
</Modal>

<Button onClick={() => setIsOpen(true)}>
{selectedToken?.symbol ?? 'Select token'}
</Button>
</div>
<div className="multichain-asset-picker__amount"></div>
</Box>
);
};
5 changes: 4 additions & 1 deletion ui/pages/bridge/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ import {
IconColor,
FRACTIONS,
} from '../../helpers/constants/design-system';
import { PrepareBridgePage } from './prepare/prepare-bridge-page';
import useUpdateSwapsState from '../../hooks/useUpdateSwapsState';
import PrepareBridgePage from './prepare/prepare-bridge-page';

const CrossChainSwap = () => {
const t = useContext(I18nContext);
Expand All @@ -45,6 +46,8 @@ const CrossChainSwap = () => {
const isPrepareSwapRoute = pathname === PREPARE_SWAP_ROUTE;
const swapsEnabled = useSelector(getSwapsFeatureIsLive);

useUpdateSwapsState();

const redirectToDefaultRoute = async () => {
history.push({
pathname: DEFAULT_ROUTE,
Expand Down
118 changes: 100 additions & 18 deletions ui/pages/bridge/prepare/prepare-bridge-page.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,108 @@
import React from 'react';
import { useSelector, shallowEqual } from 'react-redux';
import { isEqual, shuffle } from 'lodash';
import PrepareSwapPage from '../../swaps/prepare-swap-page/prepare-swap-page';
import { getSelectedAccount, getTokenList } from '../../../selectors';
import { useSelector, useDispatch } from 'react-redux';
import {
setFromToken,
setFromTokenInputValue,
setToChain,
setToToken,
} from '../../../ducks/bridge/actions';
import {
getFromAmount,
getFromChain,
getFromChains,
getFromToken,
getToAmount,
getToChain,
getToChains,
getToToken,
} from '../../../ducks/bridge/selectors';
import { MultiChainTokenPicker } from '../components/multichain-token-picker';
import { Box, TextField } from '../../../components/component-library';
import {
BlockSize,
FlexDirection,
} from '../../../helpers/constants/design-system';
import { setActiveNetwork } from '../../../store/actions';

export const PrepareBridgePage = () => {
const selectedAccount = useSelector(getSelectedAccount, shallowEqual);
const { balance: ethBalance, address: selectedAccountAddress } =
selectedAccount;
const PrepareBridgePage = () => {
const dispatch = useDispatch();

const tokenList = useSelector(getTokenList, isEqual);
const shuffledTokensList = shuffle(Object.values(tokenList));
const fromToken = useSelector(getFromToken);
const toToken = useSelector(getToToken);

const fromChains = useSelector(getFromChains);
const toChains = useSelector(getToChains);
const fromChain = useSelector(getFromChain);
const toChain = useSelector(getToChain);

const fromAmount = useSelector(getFromAmount);
const toAmount = useSelector(getToAmount);

// TODO copy in effects from PrepareSwapPage as needed
// TODO implement new UI
return (
<div>
<PrepareSwapPage
ethBalance={ethBalance}
selectedAccountAddress={selectedAccountAddress}
shuffledTokensList={shuffledTokensList}
/>
<div className="prepare-bridge-page">
<Box
className="prepare-bridge-page__content"
width={BlockSize.Full}
flexDirection={FlexDirection.Column}
>
<Box
className="from-input"
width={BlockSize.Full}
flexDirection={FlexDirection.Row}
>
{`BRIDGE FROM ${fromChain?.chainId}:${fromToken?.symbol} ${fromAmount}`}
<MultiChainTokenPicker
selectedNetwork={fromChain}
selectedToken={fromToken}
networks={fromChains}
onTokenChange={(token) => dispatch(setFromToken(token))}
onNetworkChange={(chainId, id) => {
dispatch(setActiveNetwork(id));
// TODO emit metric
// trackEvent({
// event: MetaMetricsEventName.NavNetworkSwitched,
// category: MetaMetricsEventCategory.Network,
// properties: {
// location: 'Network Menu',
// chain_id: currentChainId,
// from_network: currentChainId,
// to_network: network.chainId,
// },
// });
// TODO refresh token list if it doesnt happen automatically
}}
/>
<TextField
value={fromAmount}
onChange={(e) => {
// TODO validate input
dispatch(setFromTokenInputValue(e.target.value));
}}
/>
</Box>

<Box
className="to-input"
width={BlockSize.Full}
flexDirection={FlexDirection.Row}
>
{`BRIDGE TO ${toChain?.chainId}:${toToken?.symbol} ${toAmount}`}
<MultiChainTokenPicker
selectedNetwork={toChain}
selectedToken={toToken}
networks={toChains}
onTokenChange={(token) => dispatch(setToToken(token))}
onNetworkChange={(chainId, id) => {
dispatch(setToChain(chainId));
console.log('TODO implement bridge action for this');
// TODO refresh token list if it doesnt happen automatically
}}
/>
<TextField value={fromAmount} readOnly />
</Box>
</Box>
</div>
);
};

export default PrepareBridgePage;
1 change: 1 addition & 0 deletions ui/selectors/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -1305,6 +1305,7 @@ export function getIsSwapsChain(state) {
}

export function getIsBridgeChain(state) {
console.log('====', state);
const chainId = getCurrentChainId(state);
return ALLOWED_BRIDGE_CHAIN_IDS.includes(chainId);
}
Expand Down

0 comments on commit b37f80c

Please sign in to comment.