Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add lists page #1747

Merged
merged 18 commits into from
Jun 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions webapp/src/components/AssetList/AssetList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,10 @@ const AssetList = (props: Props) => {
return (
<div className="empty">
<div className="logo"></div>
<h1 className="title">{t('my_lists.empty.title')}</h1>
<p className="subtitle">{t('my_lists.empty.subtitle')}</p>
<h1 className="title">{t('list_page.empty.title')}</h1>
<p className="subtitle">{t('list_page.empty.subtitle')}</p>
<Button primary as={Link} to={locations.browse()}>
{t('my_lists.empty.action')}
{t('list_page.empty.action')}
</Button>
</div>
)
Expand Down
27 changes: 27 additions & 0 deletions webapp/src/components/ListPage/ListPage.container.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { connect } from 'react-redux'
import { replace } from 'connected-react-router'
import { RootState } from '../../modules/reducer'
import { getWallet, isConnecting } from '../../modules/wallet/selectors'
import {
MapStateProps,
MapDispatch,
MapDispatchProps,
OwnProps
} from './ListPage.types'
import ListPage from './ListPage'

const mapState = (state: RootState, ownProps: OwnProps): MapStateProps => {
const { listId } = ownProps.match.params

return {
wallet: getWallet(state),
isConnecting: isConnecting(state),
listId
}
}

const mapDispatch = (dispatch: MapDispatch): MapDispatchProps => ({
onRedirect: path => dispatch(replace(path))
})

export default connect(mapState, mapDispatch)(ListPage)
29 changes: 29 additions & 0 deletions webapp/src/components/ListPage/ListPage.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
.page {
min-height: 100vh;
}

.header:global(.ui.header) {
margin-left: 24px;
}

@media (max-width: 768px) {
.header:global(.ui.header) {
margin-left: 16px;
}
}

.flexContainer {
display: flex;
flex-direction: column;
/* Height of the page minus the sizes of the elements before the footer */
/* Navbar, Navigation, Header (with paddings) */
min-height: calc(100vh - 64px - 65px - 42px - 28px - 28px);
}

.emptyState {
flex-grow: 1;
}

.footer {
flex: 0;
}
53 changes: 53 additions & 0 deletions webapp/src/components/ListPage/ListPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React, { useEffect } from 'react'
import { useLocation } from 'react-router-dom'
import classNames from 'classnames'
import { Header } from 'decentraland-ui'
import { t } from 'decentraland-dapps/dist/modules/translation/utils'
import { locations } from '../../modules/routing/locations'
import { View } from '../../modules/ui/types'
import { VendorName } from '../../modules/vendor'
import { Section } from '../../modules/vendor/decentraland'
import { Navbar } from '../Navbar'
import { Footer } from '../Footer'
import { Navigation } from '../Navigation'
import { AssetBrowse } from '../AssetBrowse'
import { NavigationTab } from '../Navigation/Navigation.types'
import { Props } from './ListPage.types'
import styles from './ListPage.module.css'

const ListPage = ({ wallet, isConnecting, onRedirect }: Props) => {
// Redirect to signIn if trying to access current account without a wallet
const { pathname, search } = useLocation()

useEffect(() => {
if (!isConnecting && !wallet) {
onRedirect(locations.signIn(`${pathname}${search}`))
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isConnecting, wallet, onRedirect])
kevinszuchet marked this conversation as resolved.
Show resolved Hide resolved

return (
<div className={styles.page}>
<Navbar isFullscreen />
<Navigation activeTab={NavigationTab.MY_LISTS} />
<Header className={styles.header} size="large">
{/* TODO: use the name of the selected list */}
{t('list_page.default_title')}
</Header>
<div className={classNames(wallet ? null : styles.flexContainer)}>
{wallet ? (
<AssetBrowse
view={View.LISTS}
section={Section.LISTS}
vendor={VendorName.DECENTRALAND}
/>
) : (
<div className={styles.emptyState}></div>
)}
<Footer className={styles.footer} />
</div>
</div>
)
}

export default React.memo(ListPage)
19 changes: 19 additions & 0 deletions webapp/src/components/ListPage/ListPage.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Dispatch } from 'redux'
import { CallHistoryMethodAction } from 'connected-react-router'
import { RouteComponentProps } from 'react-router-dom'
import { Wallet } from 'decentraland-dapps/dist/modules/wallet/types'

type Params = { listId?: string }

export type Props = {
wallet: Wallet | null
isConnecting: boolean
listId?: string
onRedirect: (path: string) => void
} & RouteComponentProps<Params>

export type MapStateProps = Pick<Props, 'wallet' | 'isConnecting' | 'listId'>

export type MapDispatchProps = Pick<Props, 'onRedirect'>
export type MapDispatch = Dispatch<CallHistoryMethodAction>
export type OwnProps = RouteComponentProps<Params>
2 changes: 2 additions & 0 deletions webapp/src/components/ListPage/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import ListPage from './ListPage.container'
export { ListPage }
39 changes: 20 additions & 19 deletions webapp/src/components/ListsPage/ListsPage.container.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,28 @@
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import { replace } from 'connected-react-router'
import { openModal } from 'decentraland-dapps/dist/modules/modal/actions'
import { RootState } from '../../modules/reducer'
import { getWallet, isConnecting } from '../../modules/wallet/selectors'
import {
MapStateProps,
MapDispatch,
MapDispatchProps,
OwnProps
} from './ListsPage.types'
import AccountPage from './ListsPage'

const mapState = (state: RootState, ownProps: OwnProps): MapStateProps => {
const { listId } = ownProps.match.params
import { getBrowseLists, getCount } from '../../modules/ui/browse/selectors'
import { isLoadingLists } from '../../modules/favorites/selectors'
import { fetchListsRequest } from '../../modules/favorites/actions'
import { MapStateProps, MapDispatch, MapDispatchProps } from './ListsPage.types'
import ListsPage from './ListsPage'

const mapState = (state: RootState): MapStateProps => {
return {
wallet: getWallet(state),
isConnecting: isConnecting(state),
listId
isLoading: isLoadingLists(state),
lists: getBrowseLists(state),
count: getCount(state)
}
}

const mapDispatch = (dispatch: MapDispatch): MapDispatchProps => ({
onRedirect: path => dispatch(replace(path))
})
const mapDispatch = (dispatch: MapDispatch): MapDispatchProps =>
bindActionCreators(
kevinszuchet marked this conversation as resolved.
Show resolved Hide resolved
{
onFetchLists: fetchListsRequest,
onCreateList: () => openModal('CreateListModal')
},
dispatch
)

export default connect(mapState, mapDispatch)(AccountPage)
export default connect(mapState, mapDispatch)(ListsPage)
32 changes: 17 additions & 15 deletions webapp/src/components/ListsPage/ListsPage.module.css
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
.page {
min-height: 100vh;
}

.header:global(.ui.header) {
margin-left: 24px;
display: flex;
flex-direction: column;
}

@media (max-width: 768px) {
Expand All @@ -12,18 +9,23 @@
}
}

.flexContainer {
.subHeader {
display: flex;
flex-direction: column;
/* Height of the page minus the sizes of the elements before the footer */
/* Navbar, Navigation, Header (with paddings) */
min-height: calc(100vh - 64px - 65px - 42px - 28px - 28px);
justify-content: space-between;
margin-bottom: 46px;
}

.subHeader .right {
display: inline-flex;
gap: 10px;
align-items: center;
}

.emptyState {
flex-grow: 1;
.subHeader .left {
font-size: 17px;
}

.footer {
flex: 0;
}
.cards:global(.ui.cards) {
flex: 1 1 auto;
margin: 0px;
}
126 changes: 92 additions & 34 deletions webapp/src/components/ListsPage/ListsPage.tsx
Original file line number Diff line number Diff line change
@@ -1,52 +1,110 @@
import React, { useEffect } from 'react'
import { useLocation } from 'react-router-dom'
import classNames from 'classnames'
import { Header } from 'decentraland-ui'
import React, { useCallback, useEffect, useMemo } from 'react'
import { Button, Card, Dropdown, Header, Icon } from 'decentraland-ui'
import { t } from 'decentraland-dapps/dist/modules/translation/utils'
import { locations } from '../../modules/routing/locations'
import { View } from '../../modules/ui/types'
import { VendorName } from '../../modules/vendor'
import { Section } from '../../modules/vendor/decentraland'
import { Navbar } from '../Navbar'
import { Footer } from '../Footer'
import { Navigation } from '../Navigation'
import { AssetBrowse } from '../AssetBrowse'
import { usePagination } from '../../lib/pagination'
import { ListsBrowseSortBy } from '../../modules/favorites/types'
import { PAGE_SIZE } from '../../modules/vendor/api'
import { getParameter } from '../../lib/enum'
import { InfiniteScroll } from '../InfiniteScroll'
import { NavigationTab } from '../Navigation/Navigation.types'
import { PageLayout } from '../PageLayout'
import { Props } from './ListsPage.types'
import styles from './ListsPage.module.css'

const ListsPage = ({ wallet, isConnecting, onRedirect }: Props) => {
// Redirect to signIn if trying to access current account without a wallet
const { pathname, search } = useLocation()
const ListsPage = ({
count,
lists,
isLoading,
onFetchLists,
onCreateList
}: Props) => {
const { page, first, sortBy, goToNextPage, changeSorting } = usePagination()
const selectedSortBy = useMemo(
() =>
getParameter<ListsBrowseSortBy>(
Object.values(ListsBrowseSortBy),
sortBy,
ListsBrowseSortBy.RECENTLY_UPDATED
),
[sortBy]
)

useEffect(() => {
if (!isConnecting && !wallet) {
onRedirect(locations.signIn(`${pathname}${search}`))
let skip: number | undefined = undefined
let first: number = PAGE_SIZE
// Check if this is a fresh load of the site trying to load a page that's not the first one
if (!count && page !== 1) {
skip = 0
// Load all pages up to the last one
first = page * PAGE_SIZE
}
onFetchLists({ page, first, skip, sortBy: selectedSortBy })
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isConnecting, wallet, onRedirect])
}, [first, page, selectedSortBy, onFetchLists])

const handleSortChange = useCallback(
(_e, data) => changeSorting(data.value),
[changeSorting]
)

const hasMorePages = lists.length < (count ?? 0)

return (
<div className={styles.page}>
<Navbar isFullscreen />
<Navigation activeTab={NavigationTab.MY_LISTS} />
<PageLayout activeTab={NavigationTab.MY_LISTS}>
<Header className={styles.header} size="large">
{/* TODO: use the name of the selected list */}
{t('lists_page.default_title')}
{t('lists_page.title')}
</Header>
<div className={classNames(wallet ? null : styles.flexContainer)}>
{wallet ? (
<AssetBrowse
view={View.LISTS}
section={Section.LISTS}
vendor={VendorName.DECENTRALAND}
<div className={styles.subHeader}>
<div className={styles.left}>
{count ? t('lists_page.subtitle', { count }) : null}
</div>
<div className={styles.right}>
{t('filters.sort_by')}
<Dropdown
options={[
{
value: ListsBrowseSortBy.RECENTLY_UPDATED,
text: t('filters.recently_updated')
},
{
value: ListsBrowseSortBy.NAME_ASC,
text: t('filters.name_asc')
},
{
value: ListsBrowseSortBy.NAME_DESC,
text: t('filters.name_desc')
},
{ value: ListsBrowseSortBy.NEWEST, text: t('filters.newest') },
{ value: ListsBrowseSortBy.OLDEST, text: t('filters.oldest') }
]}
value={selectedSortBy}
onChange={handleSortChange}
/>
) : (
<div className={styles.emptyState}></div>
)}
<Footer className={styles.footer} />
<Button size="small" primary onClick={onCreateList}>
<Icon name="plus" />
{t('lists_page.create_list')}
</Button>
</div>
</div>
</div>
<InfiniteScroll
page={page}
hasMorePages={hasMorePages}
onLoadMore={goToNextPage}
isLoading={isLoading}
maxScrollPages={3}
>
<Card.Group className={styles.cards}>
{lists.map(list => (
<Card key={list.id}>
<Card.Content>
<Card.Header>{list.name}</Card.Header>
<Card.Meta>x Items</Card.Meta>
kevinszuchet marked this conversation as resolved.
Show resolved Hide resolved
</Card.Content>
</Card>
))}
</Card.Group>
</InfiniteScroll>
</PageLayout>
)
}

Expand Down
Loading