Skip to content

Commit

Permalink
Merge 4b92d85 into 6baa35e
Browse files Browse the repository at this point in the history
  • Loading branch information
meelrossi committed May 14, 2024
2 parents 6baa35e + 4b92d85 commit 62b6d36
Show file tree
Hide file tree
Showing 31 changed files with 738 additions and 197 deletions.
8 changes: 4 additions & 4 deletions package-lock.json

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
"decentraland-ecs": "6.12.4-7784644013.commit-f770b3e",
"decentraland-experiments": "^1.0.2",
"decentraland-transactions": "^2.6.1",
"decentraland-ui": "^5.23.2",
"decentraland-ui": "^5.23.3",
"ethers": "^5.6.8",
"file-saver": "^2.0.1",
"graphql": "^15.8.0",
Expand Down
10 changes: 9 additions & 1 deletion src/components/WorldListPage/NameTabs/NameTabs.container.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
import { connect } from 'react-redux'
import { push } from 'connected-react-router'
import { getIsWorldContributorEnabled } from 'modules/features/selectors'
import { RootState } from 'modules/common/types'
import NameTabs from './NameTabs'
import { MapDispatch, MapDispatchProps } from './NameTabs.types'

const mapState = (state: RootState) => {
return {
isWorldContributorEnabled: getIsWorldContributorEnabled(state)
}
}

const mapDispatch = (dispatch: MapDispatch): MapDispatchProps => {
return {
onNavigate: to => dispatch(push(to))
}
}

export default connect(null, mapDispatch)(NameTabs)
export default connect(mapState, mapDispatch)(NameTabs)
79 changes: 66 additions & 13 deletions src/components/WorldListPage/NameTabs/NameTabs.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ const mockUseCurrentlySelectedTab = useCurrentlySelectedTab as jest.Mock
const mockMobile = Mobile as jest.Mock
const mockNotMobile = NotMobile as jest.Mock

const dclTabText = 'Decentraland NAMEs'
const ensTabText = 'ENS Domains'
const contributorTabText = 'Contributor'

describe('when rendering the name tabs component', () => {
let useCurrentlySelectedTabResult: UseCurrentlySelectedTabResult
let onNavigate: jest.Mock
Expand All @@ -33,7 +37,7 @@ describe('when rendering the name tabs component', () => {
})

it('should call the on navigate prop with the pathname, url search params and an added tab query param with dcl as value', () => {
render(<NameTabs onNavigate={onNavigate} />)
render(<NameTabs onNavigate={onNavigate} isWorldContributorEnabled />)
expect(onNavigate).toHaveBeenCalledWith(
`${useCurrentlySelectedTabResult.pathname}?${useCurrentlySelectedTabResult.urlSearchParams.toString()}&${TAB_QUERY_PARAM_KEY}=${
TabType.DCL
Expand All @@ -42,24 +46,20 @@ describe('when rendering the name tabs component', () => {
})
})

describe('when the tab param is returned as dcl or ens bu the currently selected tab hook', () => {
let dclTabText: string
let ensTabText: string

describe('when the tab param is returned as dcl, ens or contributor by the currently selected tab hook', () => {
beforeEach(() => {
useCurrentlySelectedTabResult.tab = TabType.DCL
dclTabText = 'Decentraland NAMEs'
ensTabText = 'ENS Domains'

mockMobile.mockImplementation(({ children }) => children as ReactNode)
mockNotMobile.mockImplementation(() => null)
})

it('should render both the ens and dcl tabs name tabs', () => {
render(<NameTabs onNavigate={onNavigate} />)
it('should render both the ens, dcl and contributor tabs name tabs', () => {
render(<NameTabs onNavigate={onNavigate} isWorldContributorEnabled />)

expect(screen.getByText(dclTabText)).toBeInTheDocument()
expect(screen.getByText(ensTabText)).toBeInTheDocument()
expect(screen.getByText(contributorTabText)).toBeInTheDocument()
})

describe('when the tab param is ens', () => {
Expand All @@ -68,9 +68,10 @@ describe('when rendering the name tabs component', () => {
})

it('should add the active css class to the ens tab', () => {
render(<NameTabs onNavigate={onNavigate} />)
render(<NameTabs onNavigate={onNavigate} isWorldContributorEnabled />)

expect(screen.getByText(ensTabText)).toHaveClass('active')
expect(screen.getByText(contributorTabText)).not.toHaveClass('active')
expect(screen.getByText(dclTabText)).not.toHaveClass('active')
})
})
Expand All @@ -81,16 +82,31 @@ describe('when rendering the name tabs component', () => {
})

it('should add the active css class to the dcl tab', () => {
render(<NameTabs onNavigate={onNavigate} />)
render(<NameTabs onNavigate={onNavigate} isWorldContributorEnabled />)

expect(screen.getByText(ensTabText)).not.toHaveClass('active')
expect(screen.getByText(contributorTabText)).not.toHaveClass('active')
expect(screen.getByText(dclTabText)).toHaveClass('active')
})
})

describe('when the tab param is contributor', () => {
beforeEach(() => {
useCurrentlySelectedTabResult.tab = TabType.CONTRIBUTOR
})

it('should add the active css class to the contributor tab', () => {
render(<NameTabs onNavigate={onNavigate} isWorldContributorEnabled />)

expect(screen.getByText(ensTabText)).not.toHaveClass('active')
expect(screen.getByText(contributorTabText)).toHaveClass('active')
expect(screen.getByText(dclTabText)).not.toHaveClass('active')
})
})

describe('when the dcl tab is clicked', () => {
it('should call the on navigate prop with the current pathname + the tab query param with dcl as value', () => {
render(<NameTabs onNavigate={onNavigate} />)
render(<NameTabs onNavigate={onNavigate} isWorldContributorEnabled />)

screen.getByText(dclTabText).click()

Expand All @@ -104,7 +120,7 @@ describe('when rendering the name tabs component', () => {

describe('when the ens tab is clicked', () => {
it('should call the on navigate prop with the current pathname + the tab query param with ens as value', () => {
render(<NameTabs onNavigate={onNavigate} />)
render(<NameTabs onNavigate={onNavigate} isWorldContributorEnabled />)

screen.getByText(ensTabText).click()

Expand All @@ -115,5 +131,42 @@ describe('when rendering the name tabs component', () => {
)
})
})

describe('when the contributor tab is clicked', () => {
it('should call the on navigate prop with the current pathname + the tab query param with contributor as value', () => {
render(<NameTabs onNavigate={onNavigate} isWorldContributorEnabled />)

screen.getByText(contributorTabText).click()

expect(onNavigate).toHaveBeenCalledWith(
`${useCurrentlySelectedTabResult.pathname}?${useCurrentlySelectedTabResult.urlSearchParams.toString()}&${TAB_QUERY_PARAM_KEY}=${
TabType.CONTRIBUTOR
}`
)
})
})
})
})

describe('when isWorldContributorEnabled is false', () => {
let useCurrentlySelectedTabResult: UseCurrentlySelectedTabResult
let onNavigate: jest.Mock

beforeEach(() => {
useCurrentlySelectedTabResult = {
tab: TabType.DCL,
pathname: '/pathname',
urlSearchParams: new URLSearchParams('?foo=bar')
} as UseCurrentlySelectedTabResult

mockUseCurrentlySelectedTab.mockReturnValueOnce(useCurrentlySelectedTabResult)

onNavigate = jest.fn()
})
it('should not show contributor tab', () => {
render(<NameTabs onNavigate={onNavigate} isWorldContributorEnabled={false} />)
expect(screen.getByText(dclTabText)).toBeInTheDocument()
expect(screen.getByText(ensTabText)).toBeInTheDocument()
expect(screen.queryByText(contributorTabText)).not.toBeInTheDocument()
})
})
9 changes: 7 additions & 2 deletions src/components/WorldListPage/NameTabs/NameTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { t } from 'decentraland-dapps/dist/modules/translation/utils'
import { Props } from './NameTabs.types'
import { TAB_QUERY_PARAM_KEY, TabType, useCurrentlySelectedTab } from '../hooks'

const NameTabs = ({ onNavigate }: Props) => {
const NameTabs = ({ isWorldContributorEnabled, onNavigate }: Props) => {
const { tab, pathname, urlSearchParams } = useCurrentlySelectedTab()

const navigateToTab = (tab: TabType) => {
Expand All @@ -13,7 +13,7 @@ const NameTabs = ({ onNavigate }: Props) => {
onNavigate(`${pathname}?${urlSearchParamsCopy.toString()}`)
}

if (!tab) {
if (!tab || (tab === TabType.CONTRIBUTOR && !isWorldContributorEnabled)) {
navigateToTab(TabType.DCL)
return null
}
Expand All @@ -26,6 +26,11 @@ const NameTabs = ({ onNavigate }: Props) => {
<Tabs.Tab active={tab === TabType.ENS} onClick={() => navigateToTab(TabType.ENS)}>
{t('worlds_list_page.name_tabs.ens_names')}
</Tabs.Tab>
{isWorldContributorEnabled && (
<Tabs.Tab active={tab === TabType.CONTRIBUTOR} onClick={() => navigateToTab(TabType.CONTRIBUTOR)}>
{t('worlds_list_page.name_tabs.contributor_names')}
</Tabs.Tab>
)}
</Tabs>
)
}
Expand Down
2 changes: 2 additions & 0 deletions src/components/WorldListPage/NameTabs/NameTabs.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import { CallHistoryMethodAction } from 'connected-react-router'
import { Dispatch } from 'react'

export type Props = {
isWorldContributorEnabled: boolean
onNavigate: (to: string) => void
}

export type MapStateProps = Pick<Props, 'isWorldContributorEnabled'>
export type MapDispatchProps = Pick<Props, 'onNavigate'>
export type MapDispatch = Dispatch<CallHistoryMethodAction>
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { connect } from 'react-redux'
import { push } from 'connected-react-router'
import { isLoadingType } from 'decentraland-dapps/dist/modules/loading'
import { getContributableNamesList, getLoading as getLoadingENS, getContributableNamesError } from 'modules/ens/selectors'
import { RootState } from 'modules/common/types'
import { getDeploymentsByWorlds, getLoading as getDeploymentLoading } from 'modules/deployment/selectors'
import { getProjects } from 'modules/ui/dashboard/selectors'
import { FETCH_WORLD_DEPLOYMENTS_REQUEST, clearDeploymentRequest } from 'modules/deployment/actions'
import { FETCH_CONTRIBUTABLE_NAMES_REQUEST } from 'modules/ens/actions'
import WorldContributorTab from './WorldContributorTab'
import { MapStateProp, MapDispatch, MapDispatchProps } from './WorldContributorTab.types'

const mapState = (state: RootState): MapStateProp => ({
items: getContributableNamesList(state),
deploymentsByWorlds: getDeploymentsByWorlds(state),
projects: getProjects(state),
error: getContributableNamesError(state),
loading:
isLoadingType(getLoadingENS(state), FETCH_CONTRIBUTABLE_NAMES_REQUEST) ||
isLoadingType(getDeploymentLoading(state), FETCH_WORLD_DEPLOYMENTS_REQUEST)
})

const mapDispatch = (dispatch: MapDispatch): MapDispatchProps => ({
onNavigate: path => dispatch(push(path)),
onUnpublishWorld: deploymentId => dispatch(clearDeploymentRequest(deploymentId))
})

export default connect(mapState, mapDispatch)(WorldContributorTab)
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { t } from 'decentraland-dapps/dist/modules/translation'
import { Loader, Message, Table, Empty, Button } from 'decentraland-ui'
import { formatNumber } from 'decentraland-dapps/dist/lib'
import { useCallback } from 'react'
import { config } from 'config'
import { locations } from 'routing/locations'
import Profile from 'components/Profile'
import { ENS } from 'modules/ens/types'
import { fromBytesToMegabytes, renderPublishSceneButton, renderWorldUrl } from '../utils'
import { Props } from './WorldContributorTab.types'

export default function WorldContributorTab({ items, deploymentsByWorlds, projects, loading, error, onNavigate, onUnpublishWorld }: Props) {
const handlePublishScene = useCallback(() => {
onNavigate(locations.scenes())
}, [onNavigate])

const handleEditScene = useCallback(
(ens: ENS) => {
const { projectId } = deploymentsByWorlds[ens.subdomain]
onNavigate(locations.sceneDetail(projectId as string))
},
[deploymentsByWorlds, locations, onNavigate]
)

const handleUnpublishScene = useCallback(
(ens: ENS) => {
const deploymentId = deploymentsByWorlds[ens.subdomain]?.id
if (deploymentId) {
onUnpublishWorld(deploymentId)
}
},
[deploymentsByWorlds, onUnpublishWorld]
)

if (error) {
return <Message error size="tiny" visible content={error.message} header={t('worlds_list_page.error_title')} />
}

if (loading) {
return <Loader active size="big" />
}

if (!items.length) {
return (
<Empty className="empty-names-container" height={500}>
<div className="empty-icon dcl-icon" />
<div className="empty-title">{t('worlds_list_page.empty_contributor_list.title')}</div>
<div className="empty-description">
{t('worlds_list_page.empty_contributor_list.description', { b: (text: string) => <b>{text}</b> })}
</div>
<Button as="a" href={`${config.get('MARKETPLACE_WEB_URL')}/names/mint`} target="_blank" className="empty-action" primary>
{t('worlds_list_page.empty_list.cta')}
</Button>
</Empty>
)
}

return (
<Table basic="very">
<Table.Header>
<Table.Row>
<Table.HeaderCell width="1">{t('worlds_list_page.table.name')}</Table.HeaderCell>
<Table.HeaderCell width="1">{t('worlds_list_page.table.owner')}</Table.HeaderCell>
<Table.HeaderCell width="2">{t('worlds_list_page.table.url')}</Table.HeaderCell>
<Table.HeaderCell width="1">{t('worlds_list_page.table.published_scene')}</Table.HeaderCell>
<Table.HeaderCell width="1">{t('worlds_list_page.table.size')}</Table.HeaderCell>
<Table.HeaderCell width="1">{t('worlds_list_page.table.permissions')}</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{items.map((ens: ENS, index) => {
const canUserDeploy = ens.userPermissions?.includes('deployment')
const userPermissions = ens.userPermissions
?.map(permission => t(`worlds_list_page.table.user_permissions.${permission}`))
.join('/')
return (
<Table.Row className="TableRow" key={index}>
<Table.Cell width={1}>{ens.name}</Table.Cell>
<Table.Cell width={1}>{<Profile address={ens.nftOwnerAddress || ''} />}</Table.Cell>
<Table.Cell width={2}>{renderWorldUrl(deploymentsByWorlds, ens)}</Table.Cell>
<Table.Cell width={1}>
{renderPublishSceneButton({
deploymentsByWorlds,
ens,
projects,
onEditScene: canUserDeploy ? handleEditScene : undefined,
onPublishScene: canUserDeploy ? handlePublishScene : undefined,
onUnpublishScene: canUserDeploy ? handleUnpublishScene : undefined
})}
</Table.Cell>
<Table.Cell width={1}>{formatNumber(fromBytesToMegabytes(Number(ens.size) || 0))}</Table.Cell>
<Table.Cell width={1}>{userPermissions}</Table.Cell>
</Table.Row>
)
})}
</Table.Body>
</Table>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Dispatch } from 'redux'
import { Deployment } from 'modules/deployment/types'
import { ENS, ENSError } from 'modules/ens/types'
import { Project } from 'modules/project/types'
import { clearDeploymentRequest } from 'modules/deployment/actions'

export type Props = {
items: ENS[]
projects: Project[]
deploymentsByWorlds: Record<string, Deployment>
loading: boolean
error: ENSError | null
onNavigate: (path: string) => void
onUnpublishWorld: typeof clearDeploymentRequest
}

export type MapStateProp = Pick<Props, 'items' | 'deploymentsByWorlds' | 'projects' | 'loading' | 'error'>

export type MapDispatchProps = Pick<Props, 'onNavigate' | 'onUnpublishWorld'>
export type MapDispatch = Dispatch
3 changes: 3 additions & 0 deletions src/components/WorldListPage/WorldContributorTab/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import WorldContributorTab from './WorldContributorTab.container'

export default WorldContributorTab
Loading

0 comments on commit 62b6d36

Please sign in to comment.