diff --git a/webapp/src/components/AssetBrowse/AssetBrowse.container.ts b/webapp/src/components/AssetBrowse/AssetBrowse.container.ts index 7688338343..6a1eb3a935 100644 --- a/webapp/src/components/AssetBrowse/AssetBrowse.container.ts +++ b/webapp/src/components/AssetBrowse/AssetBrowse.container.ts @@ -8,8 +8,7 @@ import { getSection, getOnlySmart, getOnlyOnRent, - getIsFullscreen, - getVisitedLocations + getIsFullscreen } from '../../modules/routing/selectors' import { isMapSet } from '../../modules/routing/utils' import { setView } from '../../modules/ui/actions' @@ -28,8 +27,7 @@ const mapState = (state: RootState): MapStateProps => { assetType: getAssetType(state), viewInState: getView(state), onlySmart: getOnlySmart(state), - onlyOnRent: getOnlyOnRent(state), - visitedLocations: getVisitedLocations(state) + onlyOnRent: getOnlyOnRent(state) } } diff --git a/webapp/src/components/AssetBrowse/AssetBrowse.tsx b/webapp/src/components/AssetBrowse/AssetBrowse.tsx index a881f15482..2e63c96716 100644 --- a/webapp/src/components/AssetBrowse/AssetBrowse.tsx +++ b/webapp/src/components/AssetBrowse/AssetBrowse.tsx @@ -6,6 +6,7 @@ import { BackToTopButton, Mobile, NotMobile, Page, Tabs } from 'decentraland-ui' import { usePagination } from '../../lib/pagination' import { locations } from '../../modules/routing/locations' import { BrowseOptions } from '../../modules/routing/types' +import { ExtendedHistory } from '../../modules/types' import { View } from '../../modules/ui/types' import { getPersistedIsMapProperty, isAccountView, isListsSection } from '../../modules/ui/utils' import { Section as DecentralandSection } from '../../modules/vendor/decentraland' @@ -43,15 +44,15 @@ const AssetBrowse = (props: Props) => { onlyOnSale, onlySmart, viewInState, - onlyOnRent, - visitedLocations + onlyOnRent } = props const location = useLocation() - const history = useHistory() + const history = useHistory() as ExtendedHistory const { changeFilter } = usePagination() // Prevent fetching more than once while browsing + const visitedLocations = history.getLastVisitedLocations() const lastLocation = visitedLocations[visitedLocations.length - 2] const [hasFetched, setHasFetched] = useState( history.action === 'POP' && @@ -78,6 +79,17 @@ const AssetBrowse = (props: Props) => { const isMapPropertyPersisted = getPersistedIsMapProperty() + useEffect(() => { + const cancelListener = history.listen((_location, action) => { + if (action === 'POP') { + setHasFetched(false) + } + }) + return () => { + cancelListener() + } + }, [history]) + useEffect(() => { if (section === DecentralandSection.LAND && !isAccountView(view) && isMapPropertyPersisted === false && isMap) { // To prevent the map view from being displayed when the user clicks on the Land navigation tab. diff --git a/webapp/src/components/AssetBrowse/AssetBrowse.types.ts b/webapp/src/components/AssetBrowse/AssetBrowse.types.ts index b291e37136..c50dbb8085 100644 --- a/webapp/src/components/AssetBrowse/AssetBrowse.types.ts +++ b/webapp/src/components/AssetBrowse/AssetBrowse.types.ts @@ -1,4 +1,3 @@ -import { RouterLocation } from 'connected-react-router' import { Dispatch } from 'redux' import { AssetType } from '../../modules/asset/types' import { browse, BrowseAction, fetchAssetsFromRoute, FetchAssetsFromRouteAction } from '../../modules/routing/actions' @@ -24,12 +23,11 @@ export type Props = { onlyOnSale?: boolean onlySmart?: boolean onlyOnRent?: boolean - visitedLocations: RouterLocation[] } export type MapStateProps = Pick< Props, - 'isMap' | 'isFullscreen' | 'onlyOnSale' | 'viewInState' | 'section' | 'assetType' | 'onlySmart' | 'onlyOnRent' | 'visitedLocations' + 'isMap' | 'isFullscreen' | 'onlyOnSale' | 'viewInState' | 'section' | 'assetType' | 'onlySmart' | 'onlyOnRent' > export type MapDispatchProps = Pick export type MapDispatch = Dispatch diff --git a/webapp/src/components/AssetCard/AssetCard.container.ts b/webapp/src/components/AssetCard/AssetCard.container.ts index 8cc86f4c33..f5007b6701 100644 --- a/webapp/src/components/AssetCard/AssetCard.container.ts +++ b/webapp/src/components/AssetCard/AssetCard.container.ts @@ -1,5 +1,4 @@ import { connect } from 'react-redux' -import { getLocation } from 'connected-react-router' import { RentalListing } from '@dcl/schemas' import { getAssetPrice, isNFT } from '../../modules/asset/utils' import { getData } from '../../modules/order/selectors' @@ -7,7 +6,6 @@ import { getActiveOrder } from '../../modules/order/utils' import { RootState } from '../../modules/reducer' import { getRentalById } from '../../modules/rental/selectors' import { getOpenRentalId } from '../../modules/rental/utils' -import { locations } from '../../modules/routing/locations' import { getPageName, getSortBy, getWearablesUrlParams } from '../../modules/routing/selectors' import { PageName } from '../../modules/routing/types' import { isClaimingBackLandTransactionPending } from '../../modules/ui/browse/selectors' @@ -31,11 +29,11 @@ const mapState = (state: RootState, ownProps: OwnProps): MapStateProps => { return { price, - showListedTag: pageName === PageName.ACCOUNT && Boolean(price) && getLocation(state).pathname !== locations.root(), isClaimingBackLandTransactionPending: isNFT(asset) ? isClaimingBackLandTransactionPending(state, asset) : false, rental: rentalOfNFT, showRentalChip: rentalOfNFT !== null && pageName === PageName.ACCOUNT, sortBy: getSortBy(state), + pageName, appliedFilters: { minPrice, maxPrice diff --git a/webapp/src/components/AssetCard/AssetCard.spec.tsx b/webapp/src/components/AssetCard/AssetCard.spec.tsx index 4c01d4ed97..3b64b2a4d6 100644 --- a/webapp/src/components/AssetCard/AssetCard.spec.tsx +++ b/webapp/src/components/AssetCard/AssetCard.spec.tsx @@ -3,7 +3,7 @@ import { screen } from '@testing-library/react' import { BodyShape, ChainId, Network, NFTCategory, Rarity, WearableCategory } from '@dcl/schemas' import { Asset } from '../../modules/asset/types' import { INITIAL_STATE } from '../../modules/favorites/reducer' -import { SortBy } from '../../modules/routing/types' +import { PageName, SortBy } from '../../modules/routing/types' import { renderWithProviders } from '../../utils/test' import AssetCard from './AssetCard' import { Props as AssetCardProps } from './AssetCard.types' @@ -20,6 +20,7 @@ function renderAssetCard(props: Partial = {}) { rental={null} sortBy={SortBy.RECENTLY_LISTED} appliedFilters={{ maxPrice: '100', minPrice: '1' }} + pageName={PageName.ACCOUNT} {...props} />, { diff --git a/webapp/src/components/AssetCard/AssetCard.tsx b/webapp/src/components/AssetCard/AssetCard.tsx index 81d81a9b76..5bb8fc6fe4 100644 --- a/webapp/src/components/AssetCard/AssetCard.tsx +++ b/webapp/src/components/AssetCard/AssetCard.tsx @@ -1,6 +1,6 @@ import React, { useCallback, useMemo } from 'react' import { useInView } from 'react-intersection-observer' -import { Link } from 'react-router-dom' +import { Link, useLocation } from 'react-router-dom' import { Item, Network, RentalListing } from '@dcl/schemas' import { Profile } from 'decentraland-dapps/dist/containers' import { t } from 'decentraland-dapps/dist/modules/translation/utils' @@ -16,7 +16,8 @@ import { isRentalListingExecuted, isRentalListingOpen } from '../../modules/rental/utils' -import { SortBy } from '../../modules/routing/types' +import { locations } from '../../modules/routing/locations' +import { PageName, SortBy } from '../../modules/routing/types' import { AssetImage } from '../AssetImage' import { FavoritesCounter } from '../FavoritesCounter' import { Mana } from '../Mana' @@ -80,7 +81,7 @@ const AssetCard = (props: Props) => { asset, isManager, price, - showListedTag, + pageName, showRentalChip: showRentalBubble, onClick, isClaimingBackLandTransactionPending, @@ -91,6 +92,8 @@ const AssetCard = (props: Props) => { const { ref, inView } = useInView() const isMobile = useMobileMediaQuery() + const location = useLocation() + const showListedTag = pageName === PageName.ACCOUNT && Boolean(price) && location.pathname !== locations.root() const title = getAssetName(asset) const { parcel, estate, wearable, emote, ens } = asset.data diff --git a/webapp/src/components/AssetCard/AssetCard.types.ts b/webapp/src/components/AssetCard/AssetCard.types.ts index fb304c21bc..339f90cd38 100644 --- a/webapp/src/components/AssetCard/AssetCard.types.ts +++ b/webapp/src/components/AssetCard/AssetCard.types.ts @@ -1,13 +1,13 @@ import { Order, RentalListing } from '@dcl/schemas' import { Asset } from '../../modules/asset/types' -import { BrowseOptions } from '../../modules/routing/types' +import { BrowseOptions, PageName } from '../../modules/routing/types' export type Props = { asset: Asset price: string | null order?: Order isManager?: boolean - showListedTag?: boolean + pageName: PageName onClick?: () => void isClaimingBackLandTransactionPending: boolean showRentalChip: boolean @@ -18,6 +18,6 @@ export type Props = { export type MapStateProps = Pick< Props, - 'showListedTag' | 'price' | 'showRentalChip' | 'rental' | 'isClaimingBackLandTransactionPending' | 'sortBy' | 'appliedFilters' + 'pageName' | 'price' | 'showRentalChip' | 'rental' | 'isClaimingBackLandTransactionPending' | 'sortBy' | 'appliedFilters' > export type OwnProps = Pick diff --git a/webapp/src/components/AssetList/AssetList.container.ts b/webapp/src/components/AssetList/AssetList.container.ts index 3c92fbd297..55cb8aab7f 100644 --- a/webapp/src/components/AssetList/AssetList.container.ts +++ b/webapp/src/components/AssetList/AssetList.container.ts @@ -8,15 +8,7 @@ import { FETCH_NFTS_REQUEST } from '../../modules/nft/actions' import { isLoadingNftsByView } from '../../modules/nft/selectors' import { RootState } from '../../modules/reducer' import { browse, clearFilters } from '../../modules/routing/actions' -import { - getVendor, - getPageNumber, - getAssetType, - getSection, - getSearch, - hasFiltersEnabled, - getVisitedLocations -} from '../../modules/routing/selectors' +import { getVendor, getPageNumber, getAssetType, getSection, getSearch, hasFiltersEnabled } from '../../modules/routing/selectors' import { getBrowseAssets, getCount, getView } from '../../modules/ui/browse/selectors' import AssetList from './AssetList' import { MapStateProps, MapDispatch, MapDispatchProps } from './AssetList.types' @@ -40,8 +32,7 @@ const mapState = (state: RootState): MapStateProps => { assetType === AssetType.ITEM ? isLoadingType(getLoadingItems(state), FETCH_ITEMS_REQUEST) || isLoadingFavoritedItems(state) : isLoadingType(loadingState, FETCH_NFTS_REQUEST), - hasFiltersEnabled: hasFiltersEnabled(state), - visitedLocations: getVisitedLocations(state) + hasFiltersEnabled: hasFiltersEnabled(state) } } diff --git a/webapp/src/components/AssetList/AssetList.tsx b/webapp/src/components/AssetList/AssetList.tsx index d1da056bf9..ef56201d04 100644 --- a/webapp/src/components/AssetList/AssetList.tsx +++ b/webapp/src/components/AssetList/AssetList.tsx @@ -1,11 +1,12 @@ import React, { useCallback, useMemo, useEffect } from 'react' -import { Link, useLocation } from 'react-router-dom' +import { Link, useHistory, useLocation } from 'react-router-dom' import { NFTCategory } from '@dcl/schemas' import { getAnalytics } from 'decentraland-dapps/dist/modules/analytics/utils' import { t, T } from 'decentraland-dapps/dist/modules/translation/utils' import { Card, Loader } from 'decentraland-ui' import { locations } from '../../modules/routing/locations' import { getCategoryFromSection } from '../../modules/routing/search' +import { ExtendedHistory } from '../../modules/types' import { getMaxQuerySize, MAX_PAGE } from '../../modules/vendor/api' import * as events from '../../utils/events' import { AssetCard } from '../AssetCard' @@ -15,23 +16,12 @@ import { Props } from './AssetList.types' import './AssetList.css' const AssetList = (props: Props) => { - const { - vendor, - section, - assetType, - assets, - page, - count, - search, - isLoading, - hasFiltersEnabled, - visitedLocations, - onBrowse, - isManager, - onClearFilters - } = props + const { vendor, section, assetType, assets, page, count, search, isLoading, hasFiltersEnabled, onBrowse, isManager, onClearFilters } = + props const location = useLocation() + const history = useHistory() as ExtendedHistory + const visitedLocations = history.getLastVisitedLocations() useEffect(() => { if (visitedLocations.length > 1) { diff --git a/webapp/src/components/AssetList/AssetList.types.ts b/webapp/src/components/AssetList/AssetList.types.ts index 9e33e3acbf..4ba2da3203 100644 --- a/webapp/src/components/AssetList/AssetList.types.ts +++ b/webapp/src/components/AssetList/AssetList.types.ts @@ -1,4 +1,3 @@ -import { RouterLocation } from 'connected-react-router' import { Dispatch } from 'redux' import { Asset, AssetType } from '../../modules/asset/types' import { browse, BrowseAction, clearFilters, ClearFiltersAction } from '../../modules/routing/actions' @@ -18,12 +17,11 @@ export type Props = { onBrowse: typeof browse onClearFilters: typeof clearFilters search: string - visitedLocations: RouterLocation[] } export type MapStateProps = Pick< Props, - 'vendor' | 'section' | 'assets' | 'page' | 'count' | 'isLoading' | 'assetType' | 'search' | 'hasFiltersEnabled' | 'visitedLocations' + 'vendor' | 'section' | 'assets' | 'page' | 'count' | 'isLoading' | 'assetType' | 'search' | 'hasFiltersEnabled' > export type MapDispatchProps = Pick export type MapDispatch = Dispatch diff --git a/webapp/src/components/Routes/Routes.container.ts b/webapp/src/components/Routes/Routes.container.ts index 0f78a8af28..6dee180067 100644 --- a/webapp/src/components/Routes/Routes.container.ts +++ b/webapp/src/components/Routes/Routes.container.ts @@ -1,14 +1,18 @@ import { connect } from 'react-redux' -import { withRouter } from 'react-router' +import { withRouter } from 'react-router-dom' +import { Dispatch } from 'redux' +import { closeAllModals } from 'decentraland-dapps/dist/modules/modal/actions' import { getIsMaintenanceEnabled } from '../../modules/features/selectors' import { RootState } from '../../modules/reducer' import Routes from './Routes' -import { MapStateProps } from './Routes.types' +import { MapDispatchProps, MapStateProps } from './Routes.types' const mapState = (state: RootState): MapStateProps => ({ inMaintenance: getIsMaintenanceEnabled(state) }) -const mapDispatch = (_: any) => ({}) +const mapDispatch = (dispatch: Dispatch): MapDispatchProps => ({ + onLocationChanged: () => dispatch(closeAllModals()) +}) export default withRouter(connect(mapState, mapDispatch)(Routes)) diff --git a/webapp/src/components/Routes/Routes.tsx b/webapp/src/components/Routes/Routes.tsx index 75718d31c2..5e6e4b821f 100644 --- a/webapp/src/components/Routes/Routes.tsx +++ b/webapp/src/components/Routes/Routes.tsx @@ -1,5 +1,5 @@ -import { useCallback } from 'react' -import { Switch, Route, Redirect, RouteComponentProps } from 'react-router-dom' +import { useCallback, useEffect } from 'react' +import { Switch, Route, Redirect, RouteComponentProps, useLocation } from 'react-router-dom' import Intercom from 'decentraland-dapps/dist/components/Intercom' import { usePageTracking } from 'decentraland-dapps/dist/hooks/usePageTracking' import { t } from 'decentraland-dapps/dist/modules/translation/utils' @@ -33,8 +33,13 @@ import { SuccessPage } from '../SuccessPage' import { TransferPage } from '../TransferPage' import { Props } from './Routes.types' -const Routes = ({ inMaintenance }: Props) => { +const Routes = ({ inMaintenance, onLocationChanged }: Props) => { usePageTracking() + const location = useLocation() + + useEffect(() => { + onLocationChanged() + }, [location]) const APP_ID = config.get('INTERCOM_APP_ID') const renderItemAssetPage = useCallback(() => , []) diff --git a/webapp/src/components/Routes/Routes.types.ts b/webapp/src/components/Routes/Routes.types.ts index d27a712df2..00752a8059 100644 --- a/webapp/src/components/Routes/Routes.types.ts +++ b/webapp/src/components/Routes/Routes.types.ts @@ -1,10 +1,13 @@ -import { RouteComponentProps } from 'react-router' +import { RouteComponentProps } from 'react-router-dom' +import { closeAllModals } from 'decentraland-dapps/dist/modules/modal/actions' export type Props = RouteComponentProps & { inMaintenance: boolean + onLocationChanged: typeof closeAllModals } export type MapStateProps = Pick +export type MapDispatchProps = Pick export type State = { hasError: boolean diff --git a/webapp/src/components/SuccessPage/SuccessPage.container.ts b/webapp/src/components/SuccessPage/SuccessPage.container.ts index 342b85596b..c6e2810106 100644 --- a/webapp/src/components/SuccessPage/SuccessPage.container.ts +++ b/webapp/src/components/SuccessPage/SuccessPage.container.ts @@ -1,5 +1,4 @@ import { connect } from 'react-redux' -import { getSearch } from 'connected-react-router' import { Dispatch } from 'redux' import { ChainId } from '@dcl/schemas' import { openModal } from 'decentraland-dapps/dist/modules/modal/actions' @@ -13,7 +12,7 @@ import { getTokenIdFromLogs } from './utils' import { MapDispatchProps, MapStateProps } from './SuccessPage.types' const mapState = (state: RootState): MapStateProps => { - const search = new URLSearchParams(getSearch(state)) + const search = new URLSearchParams(window.location.search) const transaction = getTransaction(state, search.get('txHash') || '') const address = getAddress(state) const isLoadingTx = Boolean(transaction && transaction.status !== TransactionStatus.CONFIRMED) diff --git a/webapp/src/modules/modal/sagas.ts b/webapp/src/modules/modal/sagas.ts index 0dd447d533..9a68ea8766 100644 --- a/webapp/src/modules/modal/sagas.ts +++ b/webapp/src/modules/modal/sagas.ts @@ -1,5 +1,4 @@ -import { LOCATION_CHANGE } from 'connected-react-router' -import { delay, put, select, takeEvery } from 'redux-saga/effects' +import { put, select, takeEvery } from 'redux-saga/effects' import { closeModal, closeAllModals, openModal } from 'decentraland-dapps/dist/modules/modal/actions' import { getOpenModals } from 'decentraland-dapps/dist/modules/modal/selectors' import { @@ -19,7 +18,6 @@ import { } from '../rental/actions' export function* modalSaga() { - yield takeEvery(LOCATION_CHANGE, handleLocationChange) yield takeEvery( [ CLAIM_ASSET_SUCCESS, @@ -37,14 +35,6 @@ export function* modalSaga() { yield takeEvery(ACCEPT_RENTAL_LISTING_SUCCESS, handleOpenRentConfirmationModal) } -function* handleLocationChange() { - const openModals = (yield select(getOpenModals)) as ReturnType - if (Object.keys(openModals).length > 0) { - yield delay(100) - yield handleCloseAllModals() - } -} - function* handleCloseAllModals() { yield put(closeAllModals()) } diff --git a/webapp/src/modules/reducer.ts b/webapp/src/modules/reducer.ts index c7ccf726b4..0ab17518f7 100644 --- a/webapp/src/modules/reducer.ts +++ b/webapp/src/modules/reducer.ts @@ -26,7 +26,6 @@ import { nftReducer as nft } from './nft/reducer' import { orderReducer as order } from './order/reducer' import { proximityReducer as proximity } from './proximity/reducer' import { rentalReducer as rental } from './rental/reducer' -import { routingReducer as routing } from './routing/reducer' import { saleReducer as sale } from './sale/reducer' import { storeReducer as store } from './store/reducer' import { tileReducer as tile } from './tile/reducer' @@ -44,7 +43,6 @@ export const createRootReducer = (history: History) => rental, profile, proximity, - routing, storage, tile, toast, diff --git a/webapp/src/modules/routing/reducer.ts b/webapp/src/modules/routing/reducer.ts deleted file mode 100644 index 051317ff9b..0000000000 --- a/webapp/src/modules/routing/reducer.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { LocationChangeAction, LOCATION_CHANGE, RouterLocation } from 'connected-react-router' - -export type RoutingState = { - isLoadMore: boolean - visitedLocations: RouterLocation[] -} - -const INITIAL_STATE: RoutingState = { - isLoadMore: false, - visitedLocations: [] -} - -type RoutingReducerAction = LocationChangeAction - -export function routingReducer(state: RoutingState = INITIAL_STATE, action: RoutingReducerAction) { - switch (action.type) { - case LOCATION_CHANGE: { - return { - ...state, - visitedLocations: [action.payload.location, ...state.visitedLocations].slice(0, 2) // only save last 2 locations - } - } - default: - return state - } -} diff --git a/webapp/src/modules/routing/sagas.spec.ts b/webapp/src/modules/routing/sagas.spec.ts index 0e0b05d016..a14c8d4ba9 100644 --- a/webapp/src/modules/routing/sagas.spec.ts +++ b/webapp/src/modules/routing/sagas.spec.ts @@ -1,4 +1,3 @@ -import { LOCATION_CHANGE, LocationChangeAction, RouterLocation } from 'connected-react-router' import { BigNumber, ethers } from 'ethers' import { call, getContext, select } from 'redux-saga/effects' import { expectSaga } from 'redux-saga-test-plan' @@ -36,7 +35,7 @@ import { Section } from '../vendor/decentraland' import { browse, clearFilters, fetchAssetsFromRoute as fetchAssetsFromRouteAction } from './actions' import { locations } from './locations' import { fetchAssetsFromRoute, getNewBrowseOptions, routingSaga } from './sagas' -import { getCurrentBrowseOptions, getLatestVisitedLocation, getSection } from './selectors' +import { getCurrentBrowseOptions, getSection } from './selectors' import { BrowseOptions, SortBy } from './types' import { buildBrowseURL } from './utils' @@ -1113,109 +1112,6 @@ describe('when handling the browse action', () => { }) }) -describe('when handling the location change action', () => { - let browseOptions: BrowseOptions, location: RouterLocation, locationChangeAction: LocationChangeAction - beforeEach(() => { - browseOptions = { - view: View.MARKET, - address: '0x...' - } - location = { - pathname: 'aPathName', - search: 'aSearch', - hash: 'aHash', - state: {}, - key: 'aKey', - query: {} - } - locationChangeAction = { - type: LOCATION_CHANGE, - payload: { - isFirstRendering: false, - location, - action: 'POP' - } - } - }) - describe('and the location action is a POP, meaning going back', () => { - describe("and the current pathname doesn't match the browse", () => { - beforeEach(() => { - locationChangeAction.payload.location.pathname = 'anotherPathName' - }) - it('should not call the fetchAssetFromRoute', () => { - return expectSaga(routingSaga) - .provide([ - [ - select(getLatestVisitedLocation), - { - pathname: locations.browse() - } - ], - [select(getCurrentBrowseOptions), browseOptions], - [select(getSection), Section.WEARABLES], - [select(getPage), 1] - ]) - .not.put(fetchAssetsFromRouteAction(browseOptions)) - .dispatch(locationChangeAction) - .run({ silenceTimeout: true }) - }) - }) - describe('and its coming from the browse', () => { - beforeEach(() => { - locationChangeAction.payload.location.pathname = locations.browse() - }) - it('should dispatch the fetchAssetFromRoute action', () => { - return expectSaga(routingSaga) - .provide([ - [ - select(getLatestVisitedLocation), - { - pathname: locations.browse() - } - ], - [select(getCurrentBrowseOptions), browseOptions], - [select(getSection), Section.WEARABLES], - [select(getPage), 1] - ]) - .put(fetchAssetsFromRouteAction(browseOptions)) - .dispatch(locationChangeAction) - .run({ silenceTimeout: true }) - }) - }) - describe('and its coming from another route', () => { - it('should not dispatch the fetchAssetFromRoute action', () => { - return expectSaga(routingSaga) - .provide([ - [ - select(getLatestVisitedLocation), - { - pathname: 'aNotBrowsePath' - } - ], - [select(getCurrentBrowseOptions), browseOptions], - [select(getSection), Section.WEARABLES], - [select(getPage), 1] - ]) - .not.put(fetchAssetsFromRouteAction(browseOptions)) - .dispatch(locationChangeAction) - .run({ silenceTimeout: true }) - }) - }) - }) - describe('and the location action is not a POP', () => { - beforeEach(() => { - locationChangeAction.payload.action = 'PUSH' - }) - it('should not dispatch the fetchAssetFromRoute action', () => { - return expectSaga(routingSaga) - .provide([[select(getCurrentBrowseOptions), browseOptions]]) - .not.put(fetchAssetsFromRouteAction(browseOptions)) - .dispatch(locationChangeAction) - .run({ silenceTimeout: true }) - }) - }) -}) - describe('handleRedirectToSuccessPage saga', () => { let searchParams: { txHash: string diff --git a/webapp/src/modules/routing/sagas.ts b/webapp/src/modules/routing/sagas.ts index 0fa77b46ec..06c35ba572 100644 --- a/webapp/src/modules/routing/sagas.ts +++ b/webapp/src/modules/routing/sagas.ts @@ -1,7 +1,5 @@ -import { matchPath } from 'react-router-dom' -import { getLocation, LOCATION_CHANGE, LocationChangeAction } from 'connected-react-router' import { History } from 'history' -import { takeEvery, put, select, call, take, delay, race, spawn, getContext } from 'redux-saga/effects' +import { takeEvery, put, select, call, take, race, spawn, getContext } from 'redux-saga/effects' import { CatalogFilters, CatalogSortBy, NFTCategory, RentalStatus, Sale, SaleSortBy, SaleType } from '@dcl/schemas' import { getSigner } from 'decentraland-dapps/dist/lib/eth' import { TRANSACTION_ACTION_FLAG } from 'decentraland-dapps/dist/modules/transaction/types' @@ -43,7 +41,6 @@ import { getMinPrice, getStatus, getEmotePlayMode, - getLatestVisitedLocation, getRarities, getWearableGenders, getContracts, @@ -64,16 +61,7 @@ import { MAX_PAGE, PAGE_SIZE, getMaxQuerySize, MAX_QUERY_SIZE } from '../vendor/ import { Section } from '../vendor/decentraland' import { VendorName } from '../vendor/types' import { getFilters } from '../vendor/utils' -import { - BROWSE, - BrowseAction, - FETCH_ASSETS_FROM_ROUTE, - fetchAssetsFromRoute as fetchAssetsFromRouteAction, - FetchAssetsFromRouteAction, - CLEAR_FILTERS, - GO_BACK, - GoBackAction -} from './actions' +import { BROWSE, BrowseAction, FETCH_ASSETS_FROM_ROUTE, FetchAssetsFromRouteAction, CLEAR_FILTERS, GO_BACK, GoBackAction } from './actions' import { locations } from './locations' import { getCategoryFromSection, @@ -88,7 +76,6 @@ import { BrowseOptions } from './types' import { getClearedBrowseOptions, isCatalogView, rentalFilters, SALES_PER_PAGE, sellFilters, buildBrowseURL } from './utils' export function* routingSaga() { - yield takeEvery(LOCATION_CHANGE, handleLocationChange) yield takeEvery(FETCH_ASSETS_FROM_ROUTE, handleFetchAssetsFromRoute) yield takeEvery(BROWSE, handleBrowse) yield takeEvery(CLEAR_FILTERS, handleClearFilters) @@ -105,20 +92,6 @@ export function* routingSaga() { yield takeEvery(CLAIM_NAME_TRANSACTION_SUBMITTED, handleRedirectClaimingNameToSuccessPage) } -function* handleLocationChange(action: LocationChangeAction) { - // Re-triggers fetchAssetsFromRoute action when the user goes back - if (action.payload.action === 'POP' && matchPath(action.payload.location.pathname, { path: locations.browse() })) { - const latestVisitedLocation = (yield select(getLatestVisitedLocation)) as ReturnType - const isComingFromBrowse = !!matchPath(latestVisitedLocation?.pathname, { - path: locations.browse() - }) - if (isComingFromBrowse) { - const options = (yield select(getCurrentBrowseOptions)) as ReturnType - yield put(fetchAssetsFromRouteAction(options)) - } - } -} - function* handleFetchAssetsFromRoute(action: FetchAssetsFromRouteAction) { const newOptions = (yield call(getNewBrowseOptions, action.payload.options)) as BrowseOptions yield call(fetchAssetsFromRoute, newOptions) @@ -153,14 +126,9 @@ function* handleGoBack(action: GoBackAction) { const history: History = yield getContext('history') const { defaultLocation } = action.payload - history.goBack() - - const { timeout }: { timeout?: boolean } = (yield race({ - changed: take(LOCATION_CHANGE), - timeout: delay(250) - })) as { changed: LocationChangeAction; timeout: boolean } - - if (timeout) { + if (history.length) { + history.goBack() + } else { history.replace(defaultLocation || locations.root()) } } diff --git a/webapp/src/modules/routing/selectors.spec.ts b/webapp/src/modules/routing/selectors.spec.ts index 881dec68f3..49aa9d28ad 100644 --- a/webapp/src/modules/routing/selectors.spec.ts +++ b/webapp/src/modules/routing/selectors.spec.ts @@ -1,4 +1,3 @@ -import { RouterLocation } from 'connected-react-router' import { EmotePlayMode, GenderFilterOption, Network, Rarity } from '@dcl/schemas' import { AssetStatusFilter } from '../../utils/filters' import { AssetType } from '../asset/types' @@ -25,31 +24,10 @@ import { getSortByOptions, getStatus, hasFiltersEnabled, - getLatestVisitedLocation, getContracts } from './selectors' import { PageName, Sections, SortBy } from './types' -describe('when getting the latest visited location', () => { - describe('and there is no previous location', () => { - it('should return undefined', () => { - expect(getLatestVisitedLocation.resultFunc([])).toBe(undefined) - }) - }) - - describe('and there is a previous location', () => { - let prevLocation: RouterLocation - beforeEach(() => { - prevLocation = { - pathname: '/browse' - } as RouterLocation - }) - it('should return the location', () => { - expect(getLatestVisitedLocation.resultFunc([{ ...prevLocation, pathname: 'an oldest location' }, prevLocation])).toBe(prevLocation) - }) - }) -}) - describe('when getting if the are filters set', () => { describe('when the search filter is set', () => { it('should return false', () => { diff --git a/webapp/src/modules/routing/selectors.ts b/webapp/src/modules/routing/selectors.ts index 33e6f88206..000c9174b4 100644 --- a/webapp/src/modules/routing/selectors.ts +++ b/webapp/src/modules/routing/selectors.ts @@ -19,15 +19,6 @@ import { locations } from './locations' import { getDefaultOptionsByView, getURLParamArray, getURLParam, getURLParamArrayNonStandard, SEARCH_ARRAY_PARAM_SEPARATOR } from './search' import { BrowseOptions, PageName, SortBy, SortByOption } from './types' -export const getState = (state: RootState) => state.routing - -export const getVisitedLocations = (state: RootState) => getState(state).visitedLocations - -export const getLatestVisitedLocation = createSelector[], ReturnType>( - getVisitedLocations, - visitedLocations => visitedLocations[visitedLocations.length - 1] -) - const getPathName = createSelector, string>(getLocation, location => location.pathname) export const getVendor = createSelector(getRouterSearch, search => { diff --git a/webapp/src/modules/store.ts b/webapp/src/modules/store.ts index 124cc05e11..798bf45646 100644 --- a/webapp/src/modules/store.ts +++ b/webapp/src/modules/store.ts @@ -1,5 +1,5 @@ import { routerMiddleware } from 'connected-react-router' -import { createMemoryHistory, createBrowserHistory, History } from 'history' +import { createMemoryHistory, createBrowserHistory, History, Location } from 'history' import { Action, applyMiddleware, compose, createStore, Middleware } from 'redux' import { createLogger } from 'redux-logger' import createSagasMiddleware from 'redux-saga' @@ -16,11 +16,33 @@ import { getCurrentIdentity } from './identity/selectors' import { createRootReducer, RootState } from './reducer' import { rootSaga } from './sagas' import { fetchTilesRequest } from './tile/actions' +import { ExtendedHistory } from './types' import { SET_IS_TRYING_ON } from './ui/preview/actions' const basename = /^decentraland.(zone|org|today)$/.test(window.location.host) ? '/marketplace' : undefined -export const createHistory = () => createBrowserHistory({ basename }) +export const createHistory = () => { + const history = createBrowserHistory({ basename }) as ExtendedHistory + const locations: Location[] = [] + + history.listen((location, action) => { + if (action === 'PUSH') { + locations.push(location) + if (locations.length > 5) { + locations.shift() + } + } + }) + + history.getLastVisitedLocations = (n?: number) => { + if (n) { + return locations.slice(-n) + } + return locations + } + + return history +} export function initStore(history: History) { const anyWindow = window as unknown as Window & { __REDUX_DEVTOOLS_EXTENSION_COMPOSE__: any; getState: any } diff --git a/webapp/src/modules/types.ts b/webapp/src/modules/types.ts index c28462ce6b..2515e7131c 100644 --- a/webapp/src/modules/types.ts +++ b/webapp/src/modules/types.ts @@ -1,3 +1,5 @@ +import { createBrowserHistory, Location } from 'history' + export type Await = T extends { then(onfulfilled?: (value: infer U) => unknown): unknown } @@ -5,3 +7,7 @@ export type Await = T extends { : T export type AwaitFn any> = Await> + +export interface ExtendedHistory extends ReturnType { + getLastVisitedLocations: (n?: number) => Location[] +}