diff --git a/src/components/Modals/DeployModal/DeployToWorld_WorldsForEnsOwnersFeature/DeployToWorld.container.ts b/src/components/Modals/DeployModal/DeployToWorld_WorldsForEnsOwnersFeature/DeployToWorld.container.ts index 3ca6111045..5d256361d3 100644 --- a/src/components/Modals/DeployModal/DeployToWorld_WorldsForEnsOwnersFeature/DeployToWorld.container.ts +++ b/src/components/Modals/DeployModal/DeployToWorld_WorldsForEnsOwnersFeature/DeployToWorld.container.ts @@ -3,7 +3,6 @@ import { push, replace } from 'connected-react-router' import { RootState } from 'modules/common/types' import { getCurrentProject } from 'modules/project/selectors' import { getENSByWallet, getExternalNamesForConnectedWallet } from 'modules/ens/selectors' -import { fetchExternalNamesRequest } from 'modules/ens/actions' import { deployToWorldRequest } from 'modules/deployment/actions' import { getCurrentMetrics } from 'modules/scene/selectors' import { recordMediaRequest } from 'modules/media/actions' @@ -32,8 +31,7 @@ const mapDispatch = (dispatch: MapDispatch): MapDispatchProps => ({ }, onRecord: () => dispatch(recordMediaRequest()), onNavigate: path => dispatch(push(path)), - onReplace: (path, locationState) => dispatch(replace(path, locationState)), - onFetchExternalNames: () => dispatch(fetchExternalNamesRequest()) + onReplace: (path, locationState) => dispatch(replace(path, locationState)) }) export default connect(mapState, mapDispatch)(DeployToWorld) diff --git a/src/components/Modals/DeployModal/DeployToWorld_WorldsForEnsOwnersFeature/DeployToWorld.tsx b/src/components/Modals/DeployModal/DeployToWorld_WorldsForEnsOwnersFeature/DeployToWorld.tsx index b187114aa1..5be8e0c51b 100644 --- a/src/components/Modals/DeployModal/DeployToWorld_WorldsForEnsOwnersFeature/DeployToWorld.tsx +++ b/src/components/Modals/DeployModal/DeployToWorld_WorldsForEnsOwnersFeature/DeployToWorld.tsx @@ -37,8 +37,7 @@ export default function DeployToWorld({ onNavigate, onReplace, onClose, - onBack, - onFetchExternalNames + onBack }: Props) { const analytics = getAnalytics() @@ -86,10 +85,6 @@ export default function DeployToWorld({ } }, [claimedName, analytics]) - useEffect(() => { - onFetchExternalNames() - }, [onFetchExternalNames]) - const handlePublish = useCallback(() => { if (world) { onPublish(project.id, world) diff --git a/src/components/Modals/DeployModal/DeployToWorld_WorldsForEnsOwnersFeature/DeployToWorld.types.ts b/src/components/Modals/DeployModal/DeployToWorld_WorldsForEnsOwnersFeature/DeployToWorld.types.ts index 73f49c5d84..db3a029072 100644 --- a/src/components/Modals/DeployModal/DeployToWorld_WorldsForEnsOwnersFeature/DeployToWorld.types.ts +++ b/src/components/Modals/DeployModal/DeployToWorld_WorldsForEnsOwnersFeature/DeployToWorld.types.ts @@ -28,14 +28,13 @@ export type Props = { onRecord: typeof recordMediaRequest onNavigate: (path: string) => void onReplace: (path: string, locationState?: DeployToWorldLocationStateProps) => void - onFetchExternalNames: () => void } export type MapStateProps = Pick< Props, 'ensList' | 'externalNames' | 'project' | 'metrics' | 'deployments' | 'deploymentProgress' | 'error' | 'isLoading' > -export type MapDispatchProps = Pick +export type MapDispatchProps = Pick export type MapDispatch = Dispatch< DeployToWorldRequestAction | CallHistoryMethodAction | RecordMediaRequestAction | FetchExternalNamesRequestAction > diff --git a/src/components/WorldListPage_WorldsForEnsOwnersFeature/WorldListPage.container.ts b/src/components/WorldListPage_WorldsForEnsOwnersFeature/WorldListPage.container.ts index a2c01b7b62..c8968015bf 100644 --- a/src/components/WorldListPage_WorldsForEnsOwnersFeature/WorldListPage.container.ts +++ b/src/components/WorldListPage_WorldsForEnsOwnersFeature/WorldListPage.container.ts @@ -2,37 +2,42 @@ import { connect } from 'react-redux' import { push } from 'connected-react-router' import { isLoadingType } from 'decentraland-dapps/dist/modules/loading/selectors' import { RootState } from 'modules/common/types' -import { FETCH_ENS_LIST_REQUEST } from 'modules/ens/actions' -import { getENSByWallet, getError as getENSError, getLoading } from 'modules/ens/selectors' +import { FETCH_ENS_LIST_REQUEST, FETCH_EXTERNAL_NAMES_REQUEST } from 'modules/ens/actions' +import { + getENSByWallet, + getError as getENSError, + getExternalNamesForConnectedWallet, + getLoading as getLoadingENS +} from 'modules/ens/selectors' import { FETCH_WORLD_DEPLOYMENTS_REQUEST } from 'modules/deployment/actions' import { getDeploymentsByWorlds, getError as getDeploymentsError, getLoading as getDeploymentsLoading } from 'modules/deployment/selectors' import { FETCH_LANDS_REQUEST } from 'modules/land/actions' import { getLoading as getLandsLoading } from 'modules/land/selectors' import { isLoggingIn, isLoggedIn } from 'modules/identity/selectors' import { getProjects } from 'modules/ui/dashboard/selectors' -import { fetchWorldsWalletStatsRequest } from 'modules/worlds/actions' import { getConnectedWalletStats, getLoading as getLoadingWorlds } from 'modules/worlds/selectors' import { MapStateProps, MapDispatchProps, MapDispatch } from './WorldListPage.types' import WorldListPage from './WorldListPage' const mapState = (state: RootState): MapStateProps => ({ ensList: getENSByWallet(state), + externalNames: getExternalNamesForConnectedWallet(state), deploymentsByWorlds: getDeploymentsByWorlds(state), projects: getProjects(state), error: getENSError(state)?.message ?? getDeploymentsError(state) ?? undefined, isLoading: - isLoadingType(getLoading(state), FETCH_ENS_LIST_REQUEST) || + isLoadingType(getLoadingENS(state), FETCH_ENS_LIST_REQUEST) || isLoadingType(getDeploymentsLoading(state), FETCH_WORLD_DEPLOYMENTS_REQUEST) || isLoadingType(getLandsLoading(state), FETCH_LANDS_REQUEST) || isLoadingType(getLoadingWorlds(state), FETCH_WORLD_DEPLOYMENTS_REQUEST) || + isLoadingType(getLoadingENS(state), FETCH_EXTERNAL_NAMES_REQUEST) || isLoggingIn(state), isLoggedIn: isLoggedIn(state), worldsWalletStats: getConnectedWalletStats(state) }) const mapDispatch = (dispatch: MapDispatch): MapDispatchProps => ({ - onNavigate: path => dispatch(push(path)), - onFetchWorldsWalletStats: () => dispatch(fetchWorldsWalletStatsRequest()) + onNavigate: path => dispatch(push(path)) }) export default connect(mapState, mapDispatch)(WorldListPage) diff --git a/src/components/WorldListPage_WorldsForEnsOwnersFeature/WorldListPage.css b/src/components/WorldListPage_WorldsForEnsOwnersFeature/WorldListPage.css index e99c193f90..a7ee75991a 100644 --- a/src/components/WorldListPage_WorldsForEnsOwnersFeature/WorldListPage.css +++ b/src/components/WorldListPage_WorldsForEnsOwnersFeature/WorldListPage.css @@ -166,3 +166,7 @@ .WorldListPage .ui.dropdown { margin-right: 20px; } + +.WorldListPage .worlds-storage { + margin-bottom: 24px; +} diff --git a/src/components/WorldListPage_WorldsForEnsOwnersFeature/WorldListPage.tsx b/src/components/WorldListPage_WorldsForEnsOwnersFeature/WorldListPage.tsx index e93b678093..ab993b60aa 100644 --- a/src/components/WorldListPage_WorldsForEnsOwnersFeature/WorldListPage.tsx +++ b/src/components/WorldListPage_WorldsForEnsOwnersFeature/WorldListPage.tsx @@ -16,6 +16,7 @@ import { } from 'decentraland-ui' import { config } from 'config' import { isDevelopment } from 'lib/environment' +import { WorldsWalletStats } from 'lib/api/worlds' import { ENS } from 'modules/ens/types' import { locations } from 'routing/locations' import CopyToClipboard from 'components/CopyToClipboard/CopyToClipboard' @@ -25,6 +26,8 @@ import { NavigationTab } from 'components/Navigation/Navigation.types' import { Props, SortBy } from './WorldListPage.types' import NameTabs from './NameTabs' import WorldsStorage from './WorldsStorage' +import { TabType, useCurrentlySelectedTab } from './hooks' +import { fromBytesToMegabytes } from './utils' import './WorldListPage.css' const EXPLORER_URL = config.get('EXPLORER_URL', '') @@ -32,9 +35,10 @@ const WORLDS_CONTENT_SERVER_URL = config.get('WORLDS_CONTENT_SERVER', '') const PAGE_SIZE = 12 const WorldListPage: React.FC = props => { - const { ensList, error, deploymentsByWorlds, isLoading, projects, worldsWalletStats, onNavigate, onFetchWorldsWalletStats } = props + const { ensList, externalNames, error, deploymentsByWorlds, isLoading, projects, worldsWalletStats, onNavigate } = props const [sortBy, setSortBy] = useState(SortBy.ASC) const [page, setPage] = useState(1) + const { tab } = useCurrentlySelectedTab() const isWorldDeployed = (ens: ENS) => { if (ens.worldStatus?.healthy) { @@ -81,7 +85,9 @@ const WorldListPage: React.FC = props => { } const paginate = () => { - return ensList + const list = tab === TabType.DCL ? ensList : externalNames + + return list .sort((a: ENS, b: ENS) => { switch (sortBy) { case SortBy.ASC: { @@ -144,8 +150,16 @@ const WorldListPage: React.FC = props => { ) } - const renderEnsList = () => { - const total = ensList.length + const renderWorldSize = (ens: ENS, stats?: WorldsWalletStats) => { + const names = tab === TabType.DCL ? stats?.dclNames : stats?.ensNames + const bytes = names?.find(dclName => dclName.name === ens.subdomain)?.size + const suffix = tab === TabType.ENS ? ' / 25' : '' + + return !bytes ? '-' : fromBytesToMegabytes(Number(bytes)).toFixed(2) + suffix + } + + const renderList = () => { + const total = tab === TabType.DCL ? ensList.length : externalNames.length const totalPages = Math.ceil(total / PAGE_SIZE) const paginatedItems = paginate() @@ -184,6 +198,9 @@ const WorldListPage: React.FC = props => { {t('worlds_list_page.table.name')} {t('worlds_list_page.table.url')} {t('worlds_list_page.table.published_scene')} + + {t('worlds_list_page.table.size')} + {t('worlds_list_page.table.status')} @@ -196,6 +213,9 @@ const WorldListPage: React.FC = props => { {ens.name} {renderWorldUrl(ens)} {renderPublishSceneButton(ens)} + + {renderWorldSize(ens, worldsWalletStats)} + {renderWorldStatus(ens)} @@ -233,9 +253,30 @@ const WorldListPage: React.FC = props => { ) } + const renderDCLNamesView = () => { + return ( +
+ {worldsWalletStats ? ( + + ) : null} + {ensList.length > 0 ? renderList() : renderEmptyPage()} +
+ ) + } + + const renderENSNamesView = () => { + return
{externalNames.length > 0 ? renderList() : renderEmptyPage()}
+ } + + // Reset values when changing tab. useEffect(() => { - onFetchWorldsWalletStats() - }, [onFetchWorldsWalletStats]) + setSortBy(SortBy.ASC) + setPage(1) + }, [tab]) return ( = props => { isLoading={isLoading} isPageFullscreen={true} > - {/** The following elements are just for show until the feature is complete, disregard the layout, just preview the components */}

Worlds

-
- {worldsWalletStats ? ( - - ) : null} -
+ {tab === TabType.DCL ? renderDCLNamesView() : renderENSNamesView()}
- {/** Old ens list which will be removed or replaced with the new worlds for ens owners feature */} - {ensList.length > 0 ? renderEnsList() : renderEmptyPage()}
) } diff --git a/src/components/WorldListPage_WorldsForEnsOwnersFeature/WorldListPage.types.ts b/src/components/WorldListPage_WorldsForEnsOwnersFeature/WorldListPage.types.ts index c4cb04272b..81d41f85ac 100644 --- a/src/components/WorldListPage_WorldsForEnsOwnersFeature/WorldListPage.types.ts +++ b/src/components/WorldListPage_WorldsForEnsOwnersFeature/WorldListPage.types.ts @@ -12,13 +12,13 @@ export enum SortBy { export type Props = { error?: string ensList: ENS[] + externalNames: ENS[] deploymentsByWorlds: Record projects: Project[] isLoggedIn: boolean isLoading: boolean worldsWalletStats?: WorldsWalletStats onNavigate: (path: string) => void - onFetchWorldsWalletStats: () => void } export type State = { @@ -28,7 +28,7 @@ export type State = { export type MapStateProps = Pick< Props, - 'ensList' | 'deploymentsByWorlds' | 'isLoading' | 'error' | 'projects' | 'isLoggedIn' | 'worldsWalletStats' + 'ensList' | 'externalNames' | 'deploymentsByWorlds' | 'isLoading' | 'error' | 'projects' | 'isLoggedIn' | 'worldsWalletStats' > -export type MapDispatchProps = Pick +export type MapDispatchProps = Pick export type MapDispatch = Dispatch diff --git a/src/components/WorldListPage_WorldsForEnsOwnersFeature/WorldsStorage/WorldsStorage.spec.tsx b/src/components/WorldListPage_WorldsForEnsOwnersFeature/WorldsStorage/WorldsStorage.spec.tsx index 1fd8b1a88b..b2ff4c9873 100644 --- a/src/components/WorldListPage_WorldsForEnsOwnersFeature/WorldsStorage/WorldsStorage.spec.tsx +++ b/src/components/WorldListPage_WorldsForEnsOwnersFeature/WorldsStorage/WorldsStorage.spec.tsx @@ -1,10 +1,22 @@ import { render, screen } from '@testing-library/react' -import WorldsStorage, { CURRENT_MBS_TEST_ID, PROGRESS_TEST_ID } from './WorldsStorage' +import WorldsStorage, { CURRENT_MBS_TEST_ID, PROGRESS_TEST_ID, WORLDS_STORAGE_TEST_ID } from './WorldsStorage' describe('when rendering the worlds storage component', () => { + let currentBytes: number + let maxBytes: number + let className: string + + beforeEach(() => { + currentBytes = 100 + maxBytes = 100 + }) + describe('when the provided current bytes is 50550000 and the max bytes is 100000000', () => { beforeEach(() => { - render() + currentBytes = 50550000 + maxBytes = 100000000 + + render() }) it('should render 50.55/100.00mb', () => { @@ -15,4 +27,24 @@ describe('when rendering the worlds storage component', () => { expect(screen.getByTestId(PROGRESS_TEST_ID).children[0].getAttribute('style')).toEqual('width: 50%;') }) }) + + describe('when providing a classname as prop', () => { + beforeEach(() => { + className = 'some-class' + render() + }) + it('should set the classname to the root element', () => { + expect(screen.getByTestId(WORLDS_STORAGE_TEST_ID).className).toEqual(`worldsStorage ${className}`) + }) + }) + + describe('when a classname is not provided', () => { + beforeEach(() => { + render() + }) + + it('should set the classname to the root element', () => { + expect(screen.getByTestId(WORLDS_STORAGE_TEST_ID).className).toEqual('worldsStorage') + }) + }) }) diff --git a/src/components/WorldListPage_WorldsForEnsOwnersFeature/WorldsStorage/WorldsStorage.tsx b/src/components/WorldListPage_WorldsForEnsOwnersFeature/WorldsStorage/WorldsStorage.tsx index d2802f4d4d..58719cf1f5 100644 --- a/src/components/WorldListPage_WorldsForEnsOwnersFeature/WorldsStorage/WorldsStorage.tsx +++ b/src/components/WorldListPage_WorldsForEnsOwnersFeature/WorldsStorage/WorldsStorage.tsx @@ -1,19 +1,22 @@ import React from 'react' +import classnames from 'classnames' import { Progress } from 'decentraland-ui' import { t } from 'decentraland-dapps/dist/modules/translation/utils' import { Props } from './WorldsStorage.types' import styles from './WorldsStorage.module.css' +import { fromBytesToMegabytes } from '../utils' export const CURRENT_MBS_TEST_ID = 'worlds-storage-current-mbs' export const PROGRESS_TEST_ID = 'worlds-storage-bar-front' +export const WORLDS_STORAGE_TEST_ID = 'worlds-storage' -const WorldsStorage = ({ maxBytes, currentBytes }: Props) => { - const maxMbs = maxBytes / 1000000 - const currentMbs = currentBytes / 1000000 +const WorldsStorage = ({ maxBytes, currentBytes, className }: Props) => { + const maxMbs = fromBytesToMegabytes(maxBytes) + const currentMbs = fromBytesToMegabytes(currentBytes) const usedPercentage = (currentMbs * 100) / maxMbs return ( -
+
{t('worlds_list_page.worlds_storage.space_used')}
diff --git a/src/components/WorldListPage_WorldsForEnsOwnersFeature/WorldsStorage/WorldsStorage.types.ts b/src/components/WorldListPage_WorldsForEnsOwnersFeature/WorldsStorage/WorldsStorage.types.ts index af1d5b8dd1..738d69d90f 100644 --- a/src/components/WorldListPage_WorldsForEnsOwnersFeature/WorldsStorage/WorldsStorage.types.ts +++ b/src/components/WorldListPage_WorldsForEnsOwnersFeature/WorldsStorage/WorldsStorage.types.ts @@ -1,4 +1,5 @@ export type Props = { maxBytes: number currentBytes: number + className?: string } diff --git a/src/components/WorldListPage_WorldsForEnsOwnersFeature/utils.ts b/src/components/WorldListPage_WorldsForEnsOwnersFeature/utils.ts new file mode 100644 index 0000000000..a59e57539b --- /dev/null +++ b/src/components/WorldListPage_WorldsForEnsOwnersFeature/utils.ts @@ -0,0 +1,3 @@ +export const fromBytesToMegabytes = (bytes: number) => { + return bytes / 1000000 +} diff --git a/src/modules/deployment/sagas.spec.ts b/src/modules/deployment/sagas.spec.ts index 6a7a202f29..0375ee0524 100644 --- a/src/modules/deployment/sagas.spec.ts +++ b/src/modules/deployment/sagas.spec.ts @@ -2,6 +2,7 @@ import { CatalystClient, createCatalystClient, createContentClient } from 'dcl-c import { expectSaga } from 'redux-saga-test-plan' import * as matchers from 'redux-saga-test-plan/matchers' import { buildEntity } from 'dcl-catalyst-client/dist/client/utils/DeploymentBuilder' +import { getAddress } from 'decentraland-dapps/dist/modules/wallet/selectors' import { BuilderAPI } from 'lib/api/builder' import { getCatalystContentUrl } from 'lib/api/peer' import { isLoggedIn } from 'modules/identity/selectors' @@ -16,7 +17,6 @@ import { deployToWorldRequest, fetchWorldDeploymentsRequest, fetchWorldDeploymen import { deploymentSaga } from './sagas' import { makeContentFiles } from './contentUtils' import { Deployment } from './types' -import { getAddress } from 'decentraland-dapps/dist/modules/wallet/selectors' let builderAPI: BuilderAPI let catalystClient: CatalystClient diff --git a/src/modules/deployment/sagas.ts b/src/modules/deployment/sagas.ts index 6eaa856da3..f47c6186a2 100644 --- a/src/modules/deployment/sagas.ts +++ b/src/modules/deployment/sagas.ts @@ -27,6 +27,8 @@ import { getCurrentProject, getData as getProjects } from 'modules/project/selec import { Project } from 'modules/project/types' import { getSceneByProjectId } from 'modules/scene/utils' import { Scene } from 'modules/scene/types' +import { store } from 'modules/common/store' // PREVENTS IMPORT UNDEFINED +import { getParcelOrientation } from 'modules/project/utils' import { DEPLOY_TO_POOL_REQUEST, deployToPoolFailure, @@ -59,8 +61,6 @@ import { import { makeContentFiles } from './contentUtils' import { getEmptyDeployment, getThumbnail, UNPUBLISHED_PROJECT_ID } from './utils' import { ProgressStage } from './types' -import { store } from 'modules/common/store' // PREVENTS IMPORT UNDEFINED -import { getParcelOrientation } from 'modules/project/utils' type UnwrapPromise = T extends PromiseLike ? U : T diff --git a/src/modules/ens/actions.ts b/src/modules/ens/actions.ts index f3f66d46a8..e9370692d8 100644 --- a/src/modules/ens/actions.ts +++ b/src/modules/ens/actions.ts @@ -165,7 +165,7 @@ export const FETCH_EXTERNAL_NAMES_SUCCESS = '[Success] Fetch External Names' export const FETCH_EXTERNAL_NAMES_FAILURE = '[Failure] Fetch External Names' export const fetchExternalNamesRequest = (owner?: string) => action(FETCH_EXTERNAL_NAMES_REQUEST, { owner }) -export const fetchExternalNamesSuccess = (owner: string, names: string[]) => action(FETCH_EXTERNAL_NAMES_SUCCESS, { owner, names }) +export const fetchExternalNamesSuccess = (owner: string, names: ENS[]) => action(FETCH_EXTERNAL_NAMES_SUCCESS, { owner, names }) export const fetchExternalNamesFailure = (error: ENSError, owner?: string) => action(FETCH_EXTERNAL_NAMES_FAILURE, { owner, error }) export type FetchExternalNamesRequestAction = ReturnType diff --git a/src/modules/ens/reducer.spec.ts b/src/modules/ens/reducer.spec.ts index 9d66f54533..2867e3f061 100644 --- a/src/modules/ens/reducer.spec.ts +++ b/src/modules/ens/reducer.spec.ts @@ -1,6 +1,6 @@ import { FETCH_EXTERNAL_NAMES_REQUEST, fetchExternalNamesFailure, fetchExternalNamesRequest, fetchExternalNamesSuccess } from './actions' import { ENSState, INITIAL_STATE, ensReducer } from './reducer' -import { ENSError } from './types' +import { ENS, ENSError } from './types' let state: ENSState @@ -44,18 +44,11 @@ describe('when handling the fetch external names actions', () => { }) describe('when handling the fetch external names success action', () => { - let names: string[] + let names: ENS[] beforeEach(() => { - names = ['name1.eth', 'name2.eth'] - state.loading = [{ type: FETCH_EXTERNAL_NAMES_REQUEST }] - }) - - it('should update the external names property in the state', () => { - const action = fetchExternalNamesSuccess(owner, names) - const newState = ensReducer(state, action) - expect(newState.externalNames).toEqual({ - 'name1.eth': { + names = [ + { subdomain: 'name1.eth', nftOwnerAddress: owner, name: 'name1.eth', @@ -64,7 +57,7 @@ describe('when handling the fetch external names actions', () => { resolver: '', tokenId: '' }, - 'name2.eth': { + { subdomain: 'name2.eth', nftOwnerAddress: owner, name: 'name2.eth', @@ -73,6 +66,16 @@ describe('when handling the fetch external names actions', () => { resolver: '', tokenId: '' } + ] + state.loading = [{ type: FETCH_EXTERNAL_NAMES_REQUEST }] + }) + + it('should update the external names property in the state', () => { + const action = fetchExternalNamesSuccess(owner, names) + const newState = ensReducer(state, action) + expect(newState.externalNames).toEqual({ + 'name1.eth': names[0], + 'name2.eth': names[1] }) }) diff --git a/src/modules/ens/reducer.ts b/src/modules/ens/reducer.ts index 8c49c0fb31..c92572c206 100644 --- a/src/modules/ens/reducer.ts +++ b/src/modules/ens/reducer.ts @@ -228,21 +228,9 @@ export function ensReducer(state: ENSState = INITIAL_STATE, action: ENSReducerAc } } case FETCH_EXTERNAL_NAMES_SUCCESS: { - const { owner, names } = action.payload + const { names } = action.payload - const externalNames: ENS[] = names.map(name => { - return { - subdomain: name, - nftOwnerAddress: owner, - content: '', - ensOwnerAddress: '', - name, - resolver: '', - tokenId: '' - } - }) - - const externalNamesByDomain = externalNames.reduce((obj, ens) => { + const externalNamesByDomain = names.reduce((obj, ens) => { obj[ens.subdomain] = ens return obj }, {} as Record) diff --git a/src/modules/ens/sagas.spec.ts b/src/modules/ens/sagas.spec.ts index 48fb71f519..3a62c06892 100644 --- a/src/modules/ens/sagas.spec.ts +++ b/src/modules/ens/sagas.spec.ts @@ -2,19 +2,22 @@ import * as matchers from 'redux-saga-test-plan/matchers' import { throwError } from 'redux-saga-test-plan/providers' import { expectSaga } from 'redux-saga-test-plan' import { call, select } from 'redux-saga/effects' +import { ethers } from 'ethers' import { BuilderClient } from '@dcl/builder-client' import { ChainId, Network } from '@dcl/schemas' import { ERC20__factory, ERC20, DCLController__factory, DCLRegistrar__factory, ENS__factory } from 'contracts' import { getChainIdByNetwork, getSigner } from 'decentraland-dapps/dist/lib/eth' import { getAddress } from 'decentraland-dapps/dist/modules/wallet/selectors' -import { ethers } from 'ethers' +import { connectWalletSuccess } from 'decentraland-dapps/dist/modules/wallet/actions' +import { Wallet } from 'decentraland-dapps/dist/modules/wallet/types' import { CONTROLLER_V2_ADDRESS, ENS_ADDRESS, MANA_ADDRESS, REGISTRAR_ADDRESS } from 'modules/common/contracts' +import { fetchWorldDeploymentsRequest } from 'modules/deployment/actions' import { DclListsAPI } from 'lib/api/lists' import { WorldInfo, WorldsAPI, content } from 'lib/api/worlds' import { MarketplaceAPI } from 'lib/api/marketplace' import { ENSApi } from 'lib/api/ens' import { getLands } from 'modules/land/selectors' -import { getWallet } from 'modules/wallet/utils' +import { getAddressOrWaitConnection, getWallet } from 'modules/wallet/utils' import { allowClaimManaRequest, claimNameRequest, @@ -29,8 +32,9 @@ import { fetchExternalNamesSuccess } from './actions' import { ensSaga } from './sagas' -import { ENS, ENSError } from './types' +import { ENS, ENSError, WorldStatus } from './types' import { getENSBySubdomain, getExternalNames } from './selectors' +import { addWorldStatusToEachENS } from './utils' jest.mock('@dcl/builder-client') @@ -172,36 +176,34 @@ describe('when handling the fetching of external ens names for an owner', () => const MOCK_ADDRESS = '0x123' describe('when the owner is not provided in the action', () => { - let storedAddress: string | undefined + let getAddressOrWaitConnectionResult: string | undefined - describe('and the wallet address can be obtained from the store', () => { + describe('and the wallet address is obtained from the get address or wait connection function', () => { beforeEach(() => { - storedAddress = MOCK_ADDRESS + getAddressOrWaitConnectionResult = MOCK_ADDRESS }) - it('should call the ens api with the store wallet address', async () => { + it('should call the ens api with the address obtained from the get address or wait connection function', async () => { await expectSaga(ensSaga, builderClient, ensApi) .provide([ - [select(getAddress), storedAddress], - [call([ensApi, ensApi.fetchExternalNames], storedAddress!), []] + [call(getAddressOrWaitConnection), getAddressOrWaitConnectionResult], + [call([ensApi, ensApi.fetchExternalNames], getAddressOrWaitConnectionResult!), []] ]) - .put(fetchExternalNamesSuccess(storedAddress!, [])) + .put(fetchWorldDeploymentsRequest([])) + .put(fetchExternalNamesSuccess(getAddressOrWaitConnectionResult!, [])) .dispatch(fetchExternalNamesRequest()) .silentRun() }) }) - describe('and the wallet address cannot be obtained from the store', () => { - let ensError: ENSError - + describe('and the wallet address is not obtained from the get address or wait connection function', () => { beforeEach(() => { - storedAddress = undefined - ensError = { message: 'No owner address provided' } + getAddressOrWaitConnectionResult = undefined }) it('should dispatch an error action with undefined as the owner and the error', async () => { await expectSaga(ensSaga, builderClient, ensApi) - .provide([[select(getAddress), storedAddress]]) - .put(fetchExternalNamesFailure(ensError, undefined)) + .provide([[call(getAddressOrWaitConnection), getAddressOrWaitConnectionResult]]) + .put(fetchExternalNamesFailure({ message: 'No owner address provided' }, undefined)) .dispatch(fetchExternalNamesRequest()) .silentRun() }) @@ -234,16 +236,42 @@ describe('when handling the fetching of external ens names for an owner', () => }) describe('when fetchENSList returns an array of names', () => { - let names: string[] - - beforeEach(() => { - names = ['name1.eth', 'name2.eth'] - }) - it('should dispatch a success action with the owner and the names', async () => { + const enss: ENS[] = [ + { + subdomain: 'name1.eth', + nftOwnerAddress: owner, + name: 'name1.eth', + content: '', + ensOwnerAddress: '', + resolver: '', + tokenId: '' + }, + { + subdomain: 'name2.eth', + nftOwnerAddress: owner, + name: 'name2.eth', + content: '', + ensOwnerAddress: '', + resolver: '', + tokenId: '' + } + ] + + const withWorldStatus: ENS[] = enss.map(ens => ({ + ...ens, + worldStatus: {} as WorldStatus + })) + + const worlds = withWorldStatus.map(ens => ens.subdomain) + await expectSaga(ensSaga, builderClient, ensApi) - .provide([[call([ensApi, ensApi.fetchExternalNames], owner), names]]) - .put(fetchExternalNamesSuccess(owner, names)) + .provide([ + [call([ensApi, ensApi.fetchExternalNames], owner), worlds], + [call(addWorldStatusToEachENS, enss), withWorldStatus] + ]) + .put(fetchWorldDeploymentsRequest(worlds)) + .put(fetchExternalNamesSuccess(owner, withWorldStatus)) .dispatch(fetchExternalNamesRequest(owner)) .silentRun() }) @@ -362,3 +390,13 @@ describe('when handling the fetch ens world status request', () => { }) }) }) + +describe('when handling the wallet connection', () => { + it('should put the request action to fetch external names', async () => { + const address = '0x123' + await expectSaga(ensSaga, builderClient, ensApi) + .put(fetchExternalNamesRequest(address)) + .dispatch(connectWalletSuccess({ address } as Wallet)) + .silentRun() + }) +}) diff --git a/src/modules/ens/sagas.ts b/src/modules/ens/sagas.ts index c06d2c59d0..cadffba7b0 100644 --- a/src/modules/ens/sagas.ts +++ b/src/modules/ens/sagas.ts @@ -7,6 +7,7 @@ import { BuilderClient, LandCoords, LandHashes } from '@dcl/builder-client' import { ContractName, getContract } from 'decentraland-transactions' import { getChainIdByNetwork, getNetworkProvider, getSigner } from 'decentraland-dapps/dist/lib/eth' import { getAddress } from 'decentraland-dapps/dist/modules/wallet/selectors' +import { CONNECT_WALLET_SUCCESS, ConnectWalletSuccessAction } from 'decentraland-dapps/dist/modules/wallet/actions' import { Wallet } from 'decentraland-dapps/dist/modules/wallet/types' import { getCurrentLocale } from 'decentraland-dapps/dist/modules/translation/utils' import { waitForTx } from 'decentraland-dapps/dist/modules/transaction/utils' @@ -17,7 +18,7 @@ import { DCLRegistrar__factory } from 'contracts/factories/DCLRegistrar__factory import { DCLController__factory } from 'contracts/factories/DCLController__factory' import { ERC20__factory } from 'contracts/factories/ERC20__factory' import { ENS_ADDRESS, ENS_RESOLVER_ADDRESS, CONTROLLER_V2_ADDRESS, MANA_ADDRESS, REGISTRAR_ADDRESS } from 'modules/common/contracts' -import { getWallet } from 'modules/wallet/utils' +import { getAddressOrWaitConnection, getWallet } from 'modules/wallet/utils' import { getCenter, getSelection } from 'modules/land/utils' import { fetchWorldDeploymentsRequest } from 'modules/deployment/actions' import { getLands } from 'modules/land/selectors' @@ -72,11 +73,12 @@ import { FETCH_EXTERNAL_NAMES_REQUEST, FetchExternalNamesRequestAction, fetchExternalNamesSuccess, - fetchExternalNamesFailure + fetchExternalNamesFailure, + fetchExternalNamesRequest } from './actions' import { getENSBySubdomain, getExternalNames } from './selectors' import { ENS, ENSOrigin, ENSError, Authorization } from './types' -import { getDomainFromName, isExternalName } from './utils' +import { addWorldStatusToEachENS, getDomainFromName, isExternalName } from './utils' export function* ensSaga(builderClient: BuilderClient, ensApi: ENSApi) { yield takeLatest(FETCH_LANDS_SUCCESS, handleFetchLandsSuccess) @@ -90,6 +92,7 @@ export function* ensSaga(builderClient: BuilderClient, ensApi: ENSApi) { yield takeEvery(ALLOW_CLAIM_MANA_REQUEST, handleApproveClaimManaRequest) yield takeEvery(RECLAIM_NAME_REQUEST, handleReclaimNameRequest) yield takeEvery(FETCH_EXTERNAL_NAMES_REQUEST, handleFetchExternalNamesRequest) + yield takeEvery(CONNECT_WALLET_SUCCESS, handleConnectWallet) function* handleFetchLandsSuccess() { yield put(fetchENSAuthorizationRequest()) @@ -505,7 +508,7 @@ export function* ensSaga(builderClient: BuilderClient, ensApi: ENSApi) { } function* handleFetchExternalNamesRequest(action: FetchExternalNamesRequestAction) { - const owner = action.payload.owner ?? (yield select(getAddress)) + const owner = action.payload.owner ?? (yield call(getAddressOrWaitConnection)) try { if (!owner) { @@ -513,10 +516,31 @@ export function* ensSaga(builderClient: BuilderClient, ensApi: ENSApi) { } const names: string[] = yield call([ensApi, ensApi.fetchExternalNames], owner) - yield put(fetchExternalNamesSuccess(owner, names)) + + const enss: ENS[] = names.map(name => { + return { + subdomain: name, + nftOwnerAddress: owner, + content: '', + ensOwnerAddress: '', + name, + resolver: '', + tokenId: '' + } + }) + + const enssWithWorldStatus: ENS[] = yield call(addWorldStatusToEachENS, enss) + + yield put(fetchWorldDeploymentsRequest(enssWithWorldStatus.filter(ens => ens.worldStatus).map(ens => ens.subdomain))) + + yield put(fetchExternalNamesSuccess(owner, enssWithWorldStatus)) } catch (error) { const ensError: ENSError = { message: error.message } yield put(fetchExternalNamesFailure(ensError, owner)) } } + + function* handleConnectWallet(action: ConnectWalletSuccessAction) { + yield put(fetchExternalNamesRequest(action.payload.wallet.address)) + } } diff --git a/src/modules/ens/selectors.ts b/src/modules/ens/selectors.ts index 3f048fc413..5390b321fa 100644 --- a/src/modules/ens/selectors.ts +++ b/src/modules/ens/selectors.ts @@ -42,6 +42,11 @@ export const getExternalNamesForConnectedWallet = createSelector(getExternalName return Object.values(externalNames).filter(externalName => isEqual(externalName.nftOwnerAddress, address)) }) +export const getExternalNamesForWallet = (wallet: string) => (state: RootState) => { + const externalNames = getExternalNames(state) + return Object.values(externalNames).filter(externalName => isEqual(externalName.nftOwnerAddress, wallet)) +} + export const getAuthorizationByWallet = createSelector< RootState, ENSState['authorizations'], diff --git a/src/modules/ens/utils.spec.ts b/src/modules/ens/utils.spec.ts index 969e851c72..b2ae9d40f0 100644 --- a/src/modules/ens/utils.spec.ts +++ b/src/modules/ens/utils.spec.ts @@ -1,4 +1,7 @@ -import { isExternalName } from './utils' +import { WorldInfo, content } from 'lib/api/worlds' +import { extractEntityId } from 'lib/urn' +import { ENS } from './types' +import { addWorldStatusToEachENS, isExternalName } from './utils' describe('when checking if a subdomain is an external subdomain', () => { let subdomain: string @@ -23,3 +26,64 @@ describe('when checking if a subdomain is an external subdomain', () => { }) }) }) + +describe('when adding the world status to each ens', () => { + let enss: ENS[] + + describe('when providing a list of enss', () => { + let response: WorldInfo | null + + beforeEach(() => { + enss = [ + { + subdomain: 'name.dcl.eth' + } + ] as ENS[] + }) + + describe('when the fetch world request returns null', () => { + beforeEach(() => { + response = null + + jest.spyOn(content, 'fetchWorld').mockResolvedValue(response) + }) + + it('should return the enss with the world status set to null', async () => { + expect(await addWorldStatusToEachENS(enss)).toEqual([{ ...enss[0], worldStatus: null }]) + }) + }) + + describe('when the fetch world request returns the world info', () => { + beforeEach(() => { + response = { + healthy: true, + configurations: { + scenesUrn: [ + 'urn:decentraland:entity:bafkreiavjamezigndx7rq5ggwd3tg3c5hzrnvla5euukqs25ew2hs4gtte?=&baseUrl=https://worlds-content-server.decentraland.zone/contents/' + ] + } + } as WorldInfo + + jest.spyOn(content, 'fetchWorld').mockResolvedValue(response) + }) + + it('should return the enss with the world status set to null', async () => { + const healthy = response!.healthy + const urn = response!.configurations.scenesUrn[0] + + expect(await addWorldStatusToEachENS(enss)).toEqual([ + { + ...enss[0], + worldStatus: { + healthy, + scene: { + urn, + entityId: extractEntityId(urn) + } + } + } + ]) + }) + }) + }) +}) diff --git a/src/modules/ens/utils.ts b/src/modules/ens/utils.ts index 3142f45d92..8914dedcc3 100644 --- a/src/modules/ens/utils.ts +++ b/src/modules/ens/utils.ts @@ -2,10 +2,12 @@ import { ethers } from 'ethers' import { Entity } from '@dcl/schemas' import { getSigner } from 'decentraland-dapps/dist/lib/eth' import { PEER_URL, getCatalystContentUrl } from 'lib/api/peer' +import { extractEntityId } from 'lib/urn' +import { WorldInfo, content } from 'lib/api/worlds' import { DCLRegistrar__factory } from 'contracts/factories/DCLRegistrar__factory' import { Land } from 'modules/land/types' import { REGISTRAR_ADDRESS } from 'modules/common/contracts' -import { ENS } from './types' +import { ENS, WorldStatus } from './types' export const PRICE_IN_WEI = '100000000000000000000' // 100 MANA export const PRICE = ethers.utils.formatEther(PRICE_IN_WEI) @@ -86,3 +88,35 @@ export function isEnoughClaimMana(mana: string) { export function isExternalName(subdomain: string) { return !subdomain.endsWith('dcl.eth') } + +export async function addWorldStatusToEachENS(enss: ENS[]) { + const enssWithWorldStatus: ENS[] = [] + + // This will be slow for users with plenty of ens names. + // Same happens with dcl names as it uses a similar logic of fetching world info 1 by 1. + for (const ens of enss) { + let worldStatus: WorldStatus | null = null + + const world: WorldInfo | null = await content.fetchWorld(ens.subdomain) + + if (world) { + const { healthy, configurations } = world + const entityId = extractEntityId(configurations.scenesUrn[0]) + + worldStatus = { + healthy, + scene: { + urn: configurations.scenesUrn[0], + entityId + } + } + } + + enssWithWorldStatus.push({ + ...ens, + worldStatus + }) + } + + return enssWithWorldStatus +} diff --git a/src/modules/translation/languages/en.json b/src/modules/translation/languages/en.json index 47014538fb..1d602c8ab7 100644 --- a/src/modules/translation/languages/en.json +++ b/src/modules/translation/languages/en.json @@ -602,7 +602,8 @@ "status_active": "Active", "status_inactive": "Inactive", "actions": "Actions", - "empty_url": "To activate this world you need to publish a scene" + "empty_url": "To activate this world you need to publish a scene", + "size": "Size mb" }, "empty_list": { "title": "Get a free World when you own a NAME", diff --git a/src/modules/translation/languages/es.json b/src/modules/translation/languages/es.json index 7c2af62e62..22e51292bc 100644 --- a/src/modules/translation/languages/es.json +++ b/src/modules/translation/languages/es.json @@ -604,7 +604,8 @@ "status_active": "Activo", "status_inactive": "Inactivo", "actions": "Acciones", - "empty_url": "Para activar este mundo, necesitas publicar una escena" + "empty_url": "Para activar este mundo, necesitas publicar una escena", + "size": "Peso mb" }, "empty_list": { "title": "Obtenga un mundo gratis cuando tenga un nombre", diff --git a/src/modules/translation/languages/zh.json b/src/modules/translation/languages/zh.json index f10f9ba74e..9d710bd2b7 100644 --- a/src/modules/translation/languages/zh.json +++ b/src/modules/translation/languages/zh.json @@ -606,7 +606,8 @@ "status_active": "积极的", "status_inactive": "不活动", "actions": "动作", - "empty_url": "要激活这个世界,您需要发布一个场景" + "empty_url": "要激活这个世界,您需要发布一个场景", + "size": "大小 兆字节" }, "empty_list": { "title": "当您拥有一个名字时获得一个自由的世界", diff --git a/src/modules/wallet/utils.spec.ts b/src/modules/wallet/utils.spec.ts new file mode 100644 index 0000000000..dab0cf26a6 --- /dev/null +++ b/src/modules/wallet/utils.spec.ts @@ -0,0 +1,60 @@ +import { expectSaga } from 'redux-saga-test-plan' +import { select, take } from 'redux-saga/effects' +import { getAddress } from 'decentraland-dapps/dist/modules/wallet/selectors' +import { + CONNECT_WALLET_FAILURE, + CONNECT_WALLET_SUCCESS, + connectWalletFailure, + connectWalletSuccess +} from 'decentraland-dapps/dist/modules/wallet/actions' +import { Wallet } from 'decentraland-dapps/dist/modules/wallet/types' +import { getAddressOrWaitConnection } from './utils' + +describe('when calling the get address or wait for connection function', () => { + let connectedAddress: string | undefined + + describe('when there is a wallet already connected', () => { + beforeEach(() => { + connectedAddress = '0x123' + }) + + it('should return the address from the connected wallet', () => { + return expectSaga(getAddressOrWaitConnection) + .provide([[select(getAddress), connectedAddress]]) + .returns(connectedAddress) + .silentRun() + }) + }) + + describe('when there is no wallet already connected', () => { + beforeEach(() => { + connectedAddress = undefined + }) + + describe('and the wallet connects successfully', () => { + it('should return the address from the connected wallet', () => { + const connectionSuccessAddress = '0x123' + + return expectSaga(getAddressOrWaitConnection) + .provide([ + [select(getAddress), connectedAddress], + [take(CONNECT_WALLET_SUCCESS), connectWalletSuccess({ address: connectionSuccessAddress } as Wallet)] + ]) + .returns(connectionSuccessAddress) + .silentRun() + }) + }) + + describe('and the wallet fails to connect', () => { + it('should return undefined', () => { + return expectSaga(getAddressOrWaitConnection) + .provide([ + [select(getAddress), connectedAddress], + [take(CONNECT_WALLET_FAILURE), connectWalletFailure('some error')] + ]) + .returns(undefined) + .silentRun() + }) + }) + }) +}) diff --git a/src/modules/wallet/utils.ts b/src/modules/wallet/utils.ts index a59e19f398..59648ac97e 100644 --- a/src/modules/wallet/utils.ts +++ b/src/modules/wallet/utils.ts @@ -1,9 +1,15 @@ -import { select } from 'redux-saga/effects' +import { race, select, take } from 'redux-saga/effects' import { ethers } from 'ethers' import { getConnectedProvider } from 'decentraland-dapps/dist/lib/eth' import { Wallet, Provider } from 'decentraland-dapps/dist/modules/wallet/types' -import { getData as getBaseWallet } from 'decentraland-dapps/dist/modules/wallet/selectors' +import { getAddress, getData as getBaseWallet } from 'decentraland-dapps/dist/modules/wallet/selectors' import { config } from 'config' +import { + CONNECT_WALLET_FAILURE, + CONNECT_WALLET_SUCCESS, + ConnectWalletFailureAction, + ConnectWalletSuccessAction +} from 'decentraland-dapps/dist/modules/wallet/actions' export const TRANSACTIONS_API_URL = config.get('TRANSACTIONS_API_URL') @@ -30,3 +36,23 @@ export async function getMethodData(populatedTransactionPromise: Promise = yield select(getAddress) + + if (address) { + return address + } + + const { + success + }: { + success: ConnectWalletSuccessAction + failure: ConnectWalletFailureAction + } = yield race({ + success: take(CONNECT_WALLET_SUCCESS), + failure: take(CONNECT_WALLET_FAILURE) + }) + + return success ? success.payload.wallet.address : undefined +} diff --git a/src/modules/worlds/sagas.spec.ts b/src/modules/worlds/sagas.spec.ts index 5379301008..3aff7ef6b2 100644 --- a/src/modules/worlds/sagas.spec.ts +++ b/src/modules/worlds/sagas.spec.ts @@ -1,20 +1,16 @@ import { expectSaga } from 'redux-saga-test-plan' -import { call, select, take } from 'redux-saga/effects' +import { call } from 'redux-saga/effects' import { throwError } from 'redux-saga-test-plan/providers' -import { getAddress } from 'decentraland-dapps/dist/modules/wallet/selectors' +import { connectWalletSuccess } from 'decentraland-dapps/dist/modules/wallet/actions' import { Wallet } from 'decentraland-dapps/dist/modules/wallet/types' -import { - CONNECT_WALLET_FAILURE, - CONNECT_WALLET_SUCCESS, - connectWalletFailure, - connectWalletSuccess -} from 'decentraland-dapps/dist/modules/wallet/actions' import { WorldsWalletStats, content } from 'lib/api/worlds' +import { getAddressOrWaitConnection } from 'modules/wallet/utils' import { fetchWorldsWalletStatsFailure, fetchWorldsWalletStatsRequest, fetchWorldsWalletStatsSuccess } from './actions' import { worldsSaga } from './sagas' +let address: string | undefined + describe('when handling the request action to fetch worlds stats for a wallet', () => { - let address: string | undefined let stats: WorldsWalletStats describe('when the address is provided in the action', () => { @@ -70,93 +66,66 @@ describe('when handling the request action to fetch worlds stats for a wallet', }) describe('when the address is not provided in the action', () => { - let addressInStore: string | undefined + let getAddressOrWaitConnectionResult: string | undefined beforeEach(() => { address = undefined }) - describe('and there is an address available in the store', () => { + describe('and the get address or wait for connection function returns an address', () => { beforeEach(() => { - addressInStore = '0x123' + getAddressOrWaitConnectionResult = '0x123' }) - describe('and the request to fetch wallet stats responds with the worlds wallet stats', () => { + describe('and the request to fetch wallet stats responds with the wallet stats', () => { beforeEach(() => { stats = { dclNames: [], ensNames: [], maxAllowedSpace: '', usedSpace: '', - wallet: addressInStore! + wallet: getAddressOrWaitConnectionResult! } }) - it('should put the success action with the address in the store and the stats', () => { + it('should put the success action with the address from the get address or wait for connection function and the stats', () => { return expectSaga(worldsSaga) .provide([ - [select(getAddress), addressInStore], - [call([content, content.fetchWalletStats], addressInStore!), stats] + [call(getAddressOrWaitConnection), getAddressOrWaitConnectionResult], + [call([content, content.fetchWalletStats], getAddressOrWaitConnectionResult!), stats] ]) - .put(fetchWorldsWalletStatsSuccess(addressInStore!, stats)) + .put(fetchWorldsWalletStatsSuccess(getAddressOrWaitConnectionResult!, stats)) .dispatch(fetchWorldsWalletStatsRequest(address)) .silentRun() }) }) }) - describe('and there is no address available in the store', () => { - let connectProvide: any - let connectionAddress: string - + describe('and the get address or wait for connection function does not return an address', () => { beforeEach(() => { - addressInStore = undefined + getAddressOrWaitConnectionResult = undefined }) - describe('and the wallet connection fails', () => { - beforeEach(() => { - connectProvide = [take(CONNECT_WALLET_FAILURE), connectWalletFailure('some error')] - }) - - it('should put a failure action with an undefined address and the error message', () => { - return expectSaga(worldsSaga) - .provide([[select(getAddress), addressInStore], connectProvide]) - .put(fetchWorldsWalletStatsFailure('An address is required', addressInStore)) - .dispatch(fetchWorldsWalletStatsRequest(address)) - .silentRun() - }) + it('should put a failure action with an undefined address and the error message', () => { + return expectSaga(worldsSaga) + .provide([[call(getAddressOrWaitConnection), getAddressOrWaitConnectionResult]]) + .put(fetchWorldsWalletStatsFailure('An address is required', getAddressOrWaitConnectionResult)) + .dispatch(fetchWorldsWalletStatsRequest(address)) + .silentRun() }) + }) + }) +}) - describe('and the wallet connection does not fail', () => { - beforeEach(() => { - connectionAddress = '0x123' - connectProvide = [take(CONNECT_WALLET_SUCCESS), connectWalletSuccess({ address: connectionAddress } as Wallet)] - }) +describe('when the wallet connects', () => { + beforeEach(() => { + address = '0x123' + }) - describe('and the request to fetch wallet stats responds with the worlds wallet stats', () => { - beforeEach(() => { - stats = { - dclNames: [], - ensNames: [], - maxAllowedSpace: '', - usedSpace: '', - wallet: connectionAddress - } - }) - - it('should put the success action with the connection address and the stats', () => { - return expectSaga(worldsSaga) - .provide([ - [select(getAddress), addressInStore], - connectProvide, - [call([content, content.fetchWalletStats], connectionAddress), stats] - ]) - .put(fetchWorldsWalletStatsSuccess(connectionAddress, stats)) - .dispatch(fetchWorldsWalletStatsRequest(address)) - .silentRun() - }) - }) - }) - }) + it('should put the action to fetch worlds stats for the connected wallet address', () => { + return expectSaga(worldsSaga) + .put(fetchWorldsWalletStatsRequest(address)) + .dispatch(connectWalletSuccess({ address: address! } as Wallet)) + .silentRun() }) }) diff --git a/src/modules/worlds/sagas.ts b/src/modules/worlds/sagas.ts index 950353f3b5..e698a1237b 100644 --- a/src/modules/worlds/sagas.ts +++ b/src/modules/worlds/sagas.ts @@ -1,53 +1,26 @@ -import { call, put, race, select, take, takeEvery } from 'redux-saga/effects' -import { getAddress } from 'decentraland-dapps/dist/modules/wallet/selectors' -import { - CONNECT_WALLET_FAILURE, - CONNECT_WALLET_SUCCESS, - ConnectWalletFailureAction, - ConnectWalletSuccessAction -} from 'decentraland-dapps/dist/modules/wallet/actions' +import { call, put, takeEvery } from 'redux-saga/effects' +import { CONNECT_WALLET_SUCCESS, ConnectWalletSuccessAction } from 'decentraland-dapps/dist/modules/wallet/actions' +import { getAddressOrWaitConnection } from 'modules/wallet/utils' import { WorldsWalletStats, content as WorldsAPIContent } from 'lib/api/worlds' import { FETCH_WORLDS_WALLET_STATS_REQUEST, FetchWalletWorldsStatsRequestAction, fetchWorldsWalletStatsFailure, + fetchWorldsWalletStatsRequest, fetchWorldsWalletStatsSuccess } from './actions' export function* worldsSaga() { yield takeEvery(FETCH_WORLDS_WALLET_STATS_REQUEST, handlefetchWorldsWalletStatsRequest) + yield takeEvery(CONNECT_WALLET_SUCCESS, handleConnectWallet) } -/** - * Handle the fetch of the provided or current user's wallet stats. - * If no address is provided through the action, the wallet address from the currently connected wallet will be used. - * This saga is intended to be used without providing an address only if a wallet is connected or connecting. - * If called without a connected wallet and providing no address, it will get stuck until a wallet is connected. - */ function* handlefetchWorldsWalletStatsRequest(action: FetchWalletWorldsStatsRequestAction) { - let address = action.payload.address - - if (!address) { - address = yield select(getAddress) - } + const address = action.payload.address ?? (yield call(getAddressOrWaitConnection)) try { if (!address) { - const { - success - }: { - success: ConnectWalletSuccessAction - failure: ConnectWalletFailureAction - } = yield race({ - success: take(CONNECT_WALLET_SUCCESS), - failure: take(CONNECT_WALLET_FAILURE) - }) - - if (success) { - address = success.payload.wallet.address - } else { - throw new Error('An address is required') - } + throw new Error('An address is required') } const stats: WorldsWalletStats | null = yield call([WorldsAPIContent, WorldsAPIContent.fetchWalletStats], address) @@ -61,3 +34,7 @@ function* handlefetchWorldsWalletStatsRequest(action: FetchWalletWorldsStatsRequ yield put(fetchWorldsWalletStatsFailure(e.message, address)) } } + +function* handleConnectWallet(action: ConnectWalletSuccessAction) { + yield put(fetchWorldsWalletStatsRequest(action.payload.wallet.address)) +}