diff --git a/webapp/package.json b/webapp/package.json index 32a15a963..479bb5e20 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -123,4 +123,4 @@ "type": "git", "url": "https://github.com/decentraland/marketplace.git" } -} +} \ No newline at end of file diff --git a/webapp/src/components/AccountPage/AccountBanner/AccountBanner.container.tsx b/webapp/src/components/AccountPage/AccountBanner/AccountBanner.container.tsx index 408023e6b..ed1026919 100644 --- a/webapp/src/components/AccountPage/AccountBanner/AccountBanner.container.tsx +++ b/webapp/src/components/AccountPage/AccountBanner/AccountBanner.container.tsx @@ -1,6 +1,7 @@ import { connect } from 'react-redux' import { Dispatch } from 'redux' import { isLoadingType } from 'decentraland-dapps/dist/modules/loading/selectors' +import { getAddress as getAddressFromUrl } from '../../../modules/account/selectors' import { RootState } from '../../../modules/reducer' import { goBack } from '../../../modules/routing/actions' import { getViewAsGuest } from '../../../modules/routing/selectors' @@ -8,14 +9,12 @@ import { fetchStoreRequest, FETCH_STORE_REQUEST } from '../../../modules/store/a import { getStoresByOwner, getLocalStore, getLoading as getStoreLoading } from '../../../modules/store/selectors' import { Store } from '../../../modules/store/types' import { getAddress as getAddressFromWallet } from '../../../modules/wallet/selectors' -import { getAccountUrlParams } from '../../../utils/routing' import AccountBanner from './AccountBanner' import { MapStateProps, MapDispatchProps } from './AccountBanner.types' const mapState = (state: RootState): MapStateProps => { const viewAsGuest = getViewAsGuest(state) - const addressFromUrl = getAccountUrlParams()?.address - const address = addressFromUrl || getAddressFromWallet(state) + const address = getAddressFromUrl(state) || getAddressFromWallet(state) const isLoading = isLoadingType(getStoreLoading(state), FETCH_STORE_REQUEST) let store: Store | undefined = address ? getStoresByOwner(state)[address] : undefined diff --git a/webapp/src/components/AssetList/utils.ts b/webapp/src/components/AssetList/utils.ts index 055d4c71f..03b1320de 100644 --- a/webapp/src/components/AssetList/utils.ts +++ b/webapp/src/components/AssetList/utils.ts @@ -1,5 +1,9 @@ +import { matchPath } from 'react-router-dom' import { locations } from '../../modules/routing/locations' -import { matchAppRoute } from '../../utils/routing' + +export function matchAppRoute>(path: string, route: string) { + return matchPath(path, { path: route, strict: true, exact: true }) +} export function getLastVisitedElementId(currentLocation: string, lastVisitedLocation: string) { const matchLands = matchAppRoute(currentLocation, locations.lands()) diff --git a/webapp/src/components/AssetProvider/AssetProvider.container.ts b/webapp/src/components/AssetProvider/AssetProvider.container.ts index d2a532430..91d4971fd 100644 --- a/webapp/src/components/AssetProvider/AssetProvider.container.ts +++ b/webapp/src/components/AssetProvider/AssetProvider.container.ts @@ -8,10 +8,22 @@ import { Asset, AssetType } from '../../modules/asset/types' import { getContract } from '../../modules/contract/selectors' import { isLoadingFeatureFlags as getIsLoadingFeatureFlags } from '../../modules/features/selectors' import { clearItemErrors, fetchItemRequest } from '../../modules/item/actions' -import { isFetchingItem, getError as getItemsError, getData as getItems } from '../../modules/item/selectors' +import { + getContractAddress as getItemContractAddress, + getTokenId as getItemTokenId, + isFetchingItem, + getError as getItemsError, + getData as getItems +} from '../../modules/item/selectors' import { getItem } from '../../modules/item/utils' import { fetchNFTRequest, FETCH_NFT_REQUEST, clearNFTErrors } from '../../modules/nft/actions' -import { getLoading as getNFTLoading, getError as getNFTError, getData as getNFTs } from '../../modules/nft/selectors' +import { + getContractAddress as getNFTContractAddress, + getTokenId as getNFTTokenId, + getLoading as getNFTLoading, + getError as getNFTError, + getData as getNFTs +} from '../../modules/nft/selectors' import { getNFT } from '../../modules/nft/utils' import { getData as getOrders } from '../../modules/order/selectors' import { getActiveOrder } from '../../modules/order/utils' @@ -21,7 +33,6 @@ import { getOpenRentalId } from '../../modules/rental/utils' import { FetchOneOptions } from '../../modules/vendor' import { ContractName } from '../../modules/vendor/decentraland' import { convertToOutputString } from '../../utils/output' -import { getItemContractAddressFromUrl, getItemIdFromUrl, getNFTContractAddressFromUrl, getNFTTokenIdFromUrl } from '../../utils/routing' import AssetProvider from './AssetProvider' import { MapDispatch, MapDispatchProps, MapStateProps, OwnProps } from './AssetProvider.types' @@ -36,8 +47,8 @@ const mapState = (state: RootState, ownProps: OwnProps): MapStateProps => { switch (ownProps.type) { case AssetType.NFT: { const nfts = getNFTs(state) - contractAddress = contractAddress || getNFTContractAddressFromUrl() - tokenId = tokenId || getNFTTokenIdFromUrl() + contractAddress = contractAddress || getNFTContractAddress(state) + tokenId = tokenId || getNFTTokenId(state) asset = getNFT(contractAddress, tokenId, nfts) isLoading = isLoadingType(getNFTLoading(state), FETCH_NFT_REQUEST) error = getNFTError(state) @@ -45,8 +56,8 @@ const mapState = (state: RootState, ownProps: OwnProps): MapStateProps => { } case AssetType.ITEM: { const items = getItems(state) - contractAddress = contractAddress || getItemContractAddressFromUrl() - tokenId = tokenId || getItemIdFromUrl() + contractAddress = contractAddress || getItemContractAddress(state) + tokenId = tokenId || getItemTokenId(state) asset = getItem(contractAddress, tokenId, items) isLoading = isFetchingItem(state, contractAddress!, tokenId!) error = getItemsError(state) diff --git a/webapp/src/components/CollectionPage/CollectionPage.container.ts b/webapp/src/components/CollectionPage/CollectionPage.container.ts index 146c842f7..14a5ff575 100644 --- a/webapp/src/components/CollectionPage/CollectionPage.container.ts +++ b/webapp/src/components/CollectionPage/CollectionPage.container.ts @@ -1,5 +1,6 @@ import { connect } from 'react-redux' import { Dispatch } from 'redux' +import { getContractAddress } from '../../modules/collection/selectors' import { RootState } from '../../modules/reducer' import { goBack } from '../../modules/routing/actions' import { getAddress } from '../../modules/wallet/selectors' @@ -7,6 +8,7 @@ import CollectionPage from './CollectionPage' import { MapDispatchProps, MapStateProps } from './CollectionPage.types' const mapState = (state: RootState): MapStateProps => ({ + contractAddress: getContractAddress(state), currentAddress: getAddress(state) }) diff --git a/webapp/src/components/CollectionPage/CollectionPage.tsx b/webapp/src/components/CollectionPage/CollectionPage.tsx index 2de3ee010..d3c1b49c5 100644 --- a/webapp/src/components/CollectionPage/CollectionPage.tsx +++ b/webapp/src/components/CollectionPage/CollectionPage.tsx @@ -4,7 +4,6 @@ import { t } from 'decentraland-dapps/dist/modules/translation/utils' import { Back, Column, Page, Row, Section, Header, Badge, Icon, Color, Button, Loader, useMobileMediaQuery } from 'decentraland-ui' import { formatWeiMANA } from '../../lib/mana' import { getBuilderCollectionDetailUrl } from '../../modules/collection/utils' -import { getCollectionContractAddressFromUrl } from '../../utils/routing' import CollectionProvider from '../CollectionProvider' import { Mana } from '../Mana' import AssetCell from '../OnSaleOrRentList/AssetCell' @@ -19,9 +18,7 @@ const WEARABLES_TAB = 'wearables' const EMOTES_TAB = 'emotes' const CollectionPage = (props: Props) => { - const { currentAddress, onBack } = props - const contractAddress = getCollectionContractAddressFromUrl() - + const { currentAddress, contractAddress, onBack } = props const isMobile = useMobileMediaQuery() const tabList = [ diff --git a/webapp/src/components/CollectionPage/CollectionPage.types.ts b/webapp/src/components/CollectionPage/CollectionPage.types.ts index 61a1df6fc..98bab14e1 100644 --- a/webapp/src/components/CollectionPage/CollectionPage.types.ts +++ b/webapp/src/components/CollectionPage/CollectionPage.types.ts @@ -1,7 +1,8 @@ export type Props = { currentAddress?: string + contractAddress: string | null onBack: () => void } -export type MapStateProps = Pick +export type MapStateProps = Pick export type MapDispatchProps = Pick diff --git a/webapp/src/modules/account/selectors.ts b/webapp/src/modules/account/selectors.ts index b3ced1475..dc4d9bc06 100644 --- a/webapp/src/modules/account/selectors.ts +++ b/webapp/src/modules/account/selectors.ts @@ -1,6 +1,8 @@ +import { createMatchSelector } from 'connected-react-router' import { createSelector } from 'reselect' import { Network } from '@dcl/schemas' import { RootState } from '../reducer' +import { locations } from '../routing/locations' import { AccountMetrics } from './types' import { sumAccountMetrics } from './utils' @@ -12,6 +14,13 @@ export const getMetricsByNetworkByAddress = (state: RootState) => getState(state export const getLoading = (state: RootState) => getState(state).loading export const getError = (state: RootState) => getState(state).error +const accountMatchSelector = createMatchSelector(locations.account(':address')) + +export const getAddress = createSelector, string | undefined>( + accountMatchSelector, + match => match?.params.address?.toLowerCase() +) + export const getMetricsByAddressByNetwork = createSelector(getMetricsByNetworkByAddress, metrics => { const addresses = new Set([...Object.keys(metrics.ETHEREUM), ...Object.keys(metrics.MATIC)]) diff --git a/webapp/src/modules/collection/selectors.ts b/webapp/src/modules/collection/selectors.ts index f78eec025..e07d59d6a 100644 --- a/webapp/src/modules/collection/selectors.ts +++ b/webapp/src/modules/collection/selectors.ts @@ -1,7 +1,9 @@ +import { createMatchSelector } from 'connected-react-router' import { AnyAction } from 'redux' import { createSelector } from 'reselect' import { Collection } from '@dcl/schemas' import { RootState } from '../reducer' +import { locations } from '../routing/locations' import { FETCH_COLLECTIONS_REQUEST, FETCH_SINGLE_COLLECTION_REQUEST, @@ -43,3 +45,10 @@ export const getCollectionsByAddress = createSelector ) ) + +const CollectionDetailMatchSelector = createMatchSelector(locations.collection(':contractAddress')) + +export const getContractAddress = createSelector, string | null>( + CollectionDetailMatchSelector, + match => match?.params.contractAddress.toLowerCase() || null +) diff --git a/webapp/src/modules/item/selectors.ts b/webapp/src/modules/item/selectors.ts index a3a80f7a1..1a76ce3de 100644 --- a/webapp/src/modules/item/selectors.ts +++ b/webapp/src/modules/item/selectors.ts @@ -1,9 +1,11 @@ +import { createMatchSelector } from 'connected-react-router' import { AnyAction } from 'redux' import { createSelector } from 'reselect' import { Item } from '@dcl/schemas' import { AuthorizationStepStatus } from 'decentraland-dapps/dist/containers/withAuthorizedAction/AuthorizationModal' import { isLoadingType } from 'decentraland-dapps/dist/modules/loading/selectors' import { RootState } from '../reducer' +import { locations } from '../routing/locations' import { BUY_ITEM_REQUEST, FETCH_COLLECTION_ITEMS_REQUEST, @@ -44,6 +46,24 @@ export const isFetchingItemsOfCollection = (state: RootState, contractAddress: s export const getItems = createSelector, Item[]>(getData, itemsById => Object.values(itemsById)) +const ItemDetailMatchSelector = createMatchSelector< + RootState, + { + contractAddress: string + tokenId: string + } +>(locations.item(':contractAddress', ':tokenId')) + +export const getContractAddress = createSelector, string | null>( + ItemDetailMatchSelector, + match => match?.params.contractAddress.toLowerCase() || null +) + +export const getTokenId = createSelector, string | null>( + ItemDetailMatchSelector, + match => match?.params.tokenId || null +) + export const getItemsByContractAddress = createSelector(getItems, items => items.reduce( (acc, item) => { diff --git a/webapp/src/modules/nft/selectors.ts b/webapp/src/modules/nft/selectors.ts index df04e8219..35fd40559 100644 --- a/webapp/src/modules/nft/selectors.ts +++ b/webapp/src/modules/nft/selectors.ts @@ -1,8 +1,9 @@ +import { createMatchSelector } from 'connected-react-router' import { AnyAction } from 'redux' import { createSelector } from 'reselect' import { getAddress } from 'decentraland-dapps/dist/modules/wallet/selectors' -import { getNFTContractAddressFromUrl, getNFTTokenIdFromUrl } from '../../utils/routing' import { RootState } from '../reducer' +import { locations } from '../routing/locations' import { View } from '../ui/types' import { FETCH_NFTS_REQUEST, FetchNFTsRequestAction } from './actions' import { NFTState } from './reducer' @@ -14,18 +15,34 @@ export const getData = (state: RootState) => getState(state).data export const getLoading = (state: RootState) => getState(state).loading export const getError = (state: RootState) => getState(state).error +const nftDetailMatchSelector = createMatchSelector< + RootState, + { + contractAddress: string + tokenId: string + } +>(locations.nft(':contractAddress', ':tokenId')) + const isFetchNftsRequestAction = (action: AnyAction): action is FetchNFTsRequestAction => action.type === FETCH_NFTS_REQUEST export const isLoadingNftsByView = (state: RootState, view: View | undefined) => getLoading(state).filter((action: AnyAction) => isFetchNftsRequestAction(action) && action.payload?.options?.view === view) -export const getCurrentNFT = createSelector( +export const getContractAddress = createSelector, string | null>( + nftDetailMatchSelector, + match => match?.params.contractAddress.toLowerCase() || null +) + +export const getTokenId = createSelector, string | null>( + nftDetailMatchSelector, + match => match?.params.tokenId || null +) + +export const getCurrentNFT = createSelector( + state => getContractAddress(state), + state => getTokenId(state), state => getData(state), - nfts => { - const contractAddress = getNFTContractAddressFromUrl() - const tokenId = getNFTTokenIdFromUrl() - return getNFT(contractAddress, tokenId, nfts) - } + (contractAddress, tokenId, nfts) => getNFT(contractAddress, tokenId, nfts) ) export const getNFTsByOwner = createSelector>( diff --git a/webapp/src/modules/routing/selectors.ts b/webapp/src/modules/routing/selectors.ts index 96ea18553..000c9174b 100644 --- a/webapp/src/modules/routing/selectors.ts +++ b/webapp/src/modules/routing/selectors.ts @@ -5,7 +5,7 @@ import { EmotePlayMode, GenderFilterOption, Network, Rarity } from '@dcl/schemas import { t } from 'decentraland-dapps/dist/modules/translation/utils' import { isOfEnumType } from '../../utils/enums' import { AssetStatusFilter } from '../../utils/filters' -import { getAccountAddressFromUrl } from '../../utils/routing' +import { getAddress as getAccountAddress } from '../account/selectors' import { AssetType } from '../asset/types' import { RootState } from '../reducer' import { getView } from '../ui/browse/selectors' @@ -325,16 +325,17 @@ export const getEmoteHasGeometry = createSelector( search => getURLParam(search, 'emoteHasGeometry') === 'true' ) -export const getCurrentLocationAddress = createSelector( +export const getCurrentLocationAddress = createSelector( getPathName, getWalletAddress, - (pathname, walletAddress) => { + getAccountAddress, + (pathname, walletAddress, accountAddress) => { let address: string | undefined if (pathname === locations.currentAccount()) { address = walletAddress } else { - address = getAccountAddressFromUrl() + address = accountAddress } return address ? address.toLowerCase() : undefined diff --git a/webapp/src/utils/routing.ts b/webapp/src/utils/routing.ts deleted file mode 100644 index 2ed4a823e..000000000 --- a/webapp/src/utils/routing.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { matchPath } from 'react-router-dom' -import { locations } from '../modules/routing/locations' - -export function matchAppRoute>(path: string, route: string) { - return matchPath(path, { path: route, strict: true, exact: true }) -} - -export const getAccountUrlParams = () => matchAppRoute<{ address: string }>(window.location.pathname, locations.account())?.params -export const getAccountAddressFromUrl = () => getAccountUrlParams()?.address.toLowerCase() - -export const getCollectionDetailUrlParams = () => - matchAppRoute<{ contractAddress: string }>(window.location.pathname, locations.collection())?.params -export const getCollectionContractAddressFromUrl = () => getCollectionDetailUrlParams()?.contractAddress.toLowerCase() || null - -export const getItemUrlParams = () => - matchAppRoute<{ contractAddress: string; itemId: string }>(window.location.pathname, locations.item())?.params -export const getItemContractAddressFromUrl = () => getItemUrlParams()?.contractAddress.toLowerCase() || null -export const getItemIdFromUrl = () => getItemUrlParams()?.itemId || null - -export const getNFTUrlParams = () => - matchAppRoute<{ contractAddress: string; tokenId: string }>(window.location.pathname, locations.nft())?.params -export const getNFTContractAddressFromUrl = () => getNFTUrlParams()?.contractAddress.toLowerCase() || null -export const getNFTTokenIdFromUrl = () => getNFTUrlParams()?.tokenId || null