diff --git a/webapp/src/components/AssetBrowse/AssetBrowse.tsx b/webapp/src/components/AssetBrowse/AssetBrowse.tsx index f07caad41a..15127182c4 100644 --- a/webapp/src/components/AssetBrowse/AssetBrowse.tsx +++ b/webapp/src/components/AssetBrowse/AssetBrowse.tsx @@ -9,8 +9,7 @@ import { Sections } from '../../modules/vendor/routing/types' import { BrowseOptions } from '../../modules/routing/types' import { getPersistedIsMapProperty, - isAccountView, - isListsSection + isAccountView } from '../../modules/ui/utils' import { locations } from '../../modules/routing/locations' import { AccountSidebar } from '../AccountSidebar' @@ -154,7 +153,7 @@ const AssetBrowse = (props: Props) => { visitedLocations ]) - const left = isListsSection(section) ? null : ( + const left = ( <> {isAccountOrCurrentAccount ? ( diff --git a/webapp/src/components/AssetList/AssetList.container.ts b/webapp/src/components/AssetList/AssetList.container.ts index c36d02bb0b..4e48f7f097 100644 --- a/webapp/src/components/AssetList/AssetList.container.ts +++ b/webapp/src/components/AssetList/AssetList.container.ts @@ -15,28 +15,25 @@ import { } from '../../modules/routing/selectors' import { getLoading as getLoadingNFTs } from '../../modules/nft/selectors' import { getLoading as getLoadingItems } from '../../modules/item/selectors' -import { isLoadingFavoritedItems } from '../../modules/favorites/selectors' import { FETCH_ITEMS_REQUEST } from '../../modules/item/actions' import { AssetType } from '../../modules/asset/types' import { MapStateProps, MapDispatch, MapDispatchProps } from './AssetList.types' import AssetList from './AssetList' const mapState = (state: RootState): MapStateProps => { - const section = getSection(state) const page = getPageNumber(state) const assetType = getAssetType(state) return { vendor: getVendor(state), assetType, section: getSection(state), - assets: getBrowseAssets(state, section, assetType), + assets: getBrowseAssets(state, assetType), page, count: getCount(state), search: getSearch(state), isLoading: assetType === AssetType.ITEM - ? isLoadingType(getLoadingItems(state), FETCH_ITEMS_REQUEST) || - isLoadingFavoritedItems(state) + ? isLoadingType(getLoadingItems(state), FETCH_ITEMS_REQUEST) : isLoadingType(getLoadingNFTs(state), FETCH_NFTS_REQUEST), hasFiltersEnabled: hasFiltersEnabled(state), visitedLocations: getVisitedLocations(state) diff --git a/webapp/src/components/AssetTopbar/AssetTopbar.tsx b/webapp/src/components/AssetTopbar/AssetTopbar.tsx index 2c0985e1bd..4ae472bba9 100644 --- a/webapp/src/components/AssetTopbar/AssetTopbar.tsx +++ b/webapp/src/components/AssetTopbar/AssetTopbar.tsx @@ -20,7 +20,6 @@ import { import { isAccountView, isLandSection, - isListsSection, persistIsMapProperty } from '../../modules/ui/utils' import trash from '../../images/trash.png' @@ -114,7 +113,7 @@ export const AssetTopbar = ({ [styles.searchMap]: isMap })} > - {!isMap && !isListsSection(section) && ( + {!isMap && ( {!isLoading ? ( - isListsSection(section) && !count ? null : ( -
-

- {count && isCatalogView(view) - ? t( +

+

+ {count && isCatalogView(view) + ? t( + search + ? 'nft_filters.query_results' + : 'nft_filters.results', + { + count: count.toLocaleString(), search - ? 'nft_filters.query_results' - : 'nft_filters.results', - { - count: count.toLocaleString(), - search - } - ) - : getCountText(count, search)} -

-
- ) - ) : null} - {!isListsSection(section) ? ( -
- - {isMobile ? ( - - ) : null} + } + ) + : getCountText(count, search)} +

) : null} +
+ + {isMobile ? ( + + ) : null} +
)} {!isMap && hasFiltersEnabled ? ( diff --git a/webapp/src/components/ListPage/ListPage.container.ts b/webapp/src/components/ListPage/ListPage.container.ts index 21dd39942d..fad49758af 100644 --- a/webapp/src/components/ListPage/ListPage.container.ts +++ b/webapp/src/components/ListPage/ListPage.container.ts @@ -10,12 +10,15 @@ import { import { GET_LIST_REQUEST, deleteListStart, + fetchFavoritedItemsRequest, getListRequest } from '../../modules/favorites/actions' import { RootState } from '../../modules/reducer' import { getWallet, isConnecting } from '../../modules/wallet/selectors' import { openModal } from '../../modules/modal/actions' import { locations } from '../../modules/routing/locations' +import { isLoadingFavoritedItems } from '../../modules/favorites/selectors' +import { getItemsPickedByUserOrCreator } from '../../modules/ui/browse/selectors' import { MapStateProps, MapDispatch, @@ -32,7 +35,9 @@ const mapState = (state: RootState, ownProps: OwnProps): MapStateProps => { wallet: getWallet(state), listId, list: listId ? getList(state, listId) : null, - isLoading: isLoadingType(getLoading(state), GET_LIST_REQUEST), + isLoadingList: isLoadingType(getLoading(state), GET_LIST_REQUEST), + isLoadingItems: isLoadingFavoritedItems(state), + items: getItemsPickedByUserOrCreator(state), error: getError(state), isListV1Enabled: getIsListsV1Enabled(state) } @@ -43,7 +48,9 @@ const mapDispatch = (dispatch: MapDispatch): MapDispatchProps => ({ onFetchList: listId => dispatch(getListRequest(listId)), onEditList: list => dispatch(openModal('CreateOrEditListModal', { list })), onShareList: list => dispatch(openModal('ShareListModal', { list })), - onDeleteList: list => dispatch(deleteListStart(list)) + onDeleteList: list => dispatch(deleteListStart(list)), + onFetchFavoritedItems: (options: any, force?) => + dispatch(fetchFavoritedItemsRequest(options, force)) }) export default connect(mapState, mapDispatch)(ListPage) diff --git a/webapp/src/components/ListPage/ListPage.module.css b/webapp/src/components/ListPage/ListPage.module.css index a7491b039b..e1311868fb 100644 --- a/webapp/src/components/ListPage/ListPage.module.css +++ b/webapp/src/components/ListPage/ListPage.module.css @@ -5,7 +5,7 @@ } .header:global(.ui.header) { - margin: 0 24px; + margin: 0; display: flex; align-items: center; column-gap: 19px; @@ -166,6 +166,7 @@ .assetBrowseContainer { flex-grow: 1; + margin-top: 30px; } .empty { @@ -193,6 +194,31 @@ margin-top: 20px; } +.count { + font-size: 17px; + margin-bottom: 20px +} + +.emptyState { + flex-grow: 1; +} + +.cardsGroup { + display: grid; + grid-gap: 12px; + gap: 12px; + grid-template-columns: repeat(auto-fill, 290px); +} + +.overlay { + height: 100%; + width: 100%; + position: absolute; + background: var(--background); + opacity: 0.6; + z-index: 3; +} + @media (max-width: 768px) { .header:global(.ui.header) { margin-left: 16px; @@ -206,6 +232,10 @@ } } -.emptyState { - flex-grow: 1; +@media (min-width: 768px) { + .transparentOverlay { + height: 100%; + width: 100%; + position: absolute; + } } diff --git a/webapp/src/components/ListPage/ListPage.tsx b/webapp/src/components/ListPage/ListPage.tsx index 18134b42df..4190a30cc0 100644 --- a/webapp/src/components/ListPage/ListPage.tsx +++ b/webapp/src/components/ListPage/ListPage.tsx @@ -15,14 +15,16 @@ import { getAnalytics } from 'decentraland-dapps/dist/modules/analytics/utils' import { formatDistanceToNow } from '../../lib/date' import { locations } from '../../modules/routing/locations' import { Section } from '../../modules/vendor/decentraland' -import { VendorName } from '../../modules/vendor' -import { View } from '../../modules/ui/types' import { DEFAULT_FAVORITES_LIST_ID } from '../../modules/vendor/decentraland/favorites' import * as events from '../../utils/events' +import { usePagination } from '../../lib/pagination' +import { Sections } from '../../modules/routing/types' +import { MAX_PAGE, PAGE_SIZE } from '../../modules/vendor/api' import { NavigationTab } from '../Navigation/Navigation.types' -import { AssetBrowse } from '../AssetBrowse' import { PageLayout } from '../PageLayout' import { LinkedProfile } from '../LinkedProfile' +import { InfiniteScroll } from '../InfiniteScroll' +import { AssetCard } from '../AssetCard' import { PrivateTag } from '../PrivateTag' import { Props } from './ListPage.types' import styles from './ListPage.module.css' @@ -45,24 +47,29 @@ import { const LIST_NOT_FOUND = 'list was not found' -const ListPage = (props: Props) => { - const { - isConnecting, - wallet, - listId, - list, - isLoading, - error, - onFetchList, - onBack, - onEditList, - onDeleteList, - onShareList, - isListV1Enabled - } = props +const ListPage = ({ + isConnecting, + wallet, + listId, + items, + list, + isLoadingList, + isLoadingItems, + error, + onFetchList, + onBack, + onEditList, + onDeleteList, + onShareList, + onFetchFavoritedItems, + isListV1Enabled +}: Props) => { const hasFetchedOnce = useRef(false) const { pathname, search } = useLocation() + const { page, first, offset, goToNextPage } = usePagination() + const isLoading = isLoadingList || isLoadingItems + // Fetching list const fetchList = useCallback(() => { if (listId && !isLoading && !hasFetchedOnce.current) { onFetchList(listId) @@ -142,6 +149,22 @@ const ListPage = (props: Props) => { if (!isConnecting) fetchList() }, [fetchList, isConnecting]) + useEffect(() => { + if (list) { + onFetchFavoritedItems({ + view: Section.LISTS, + section: Sections.decentraland.LISTS, + page, + filters: { first, skip: offset } + }) + } + }, [first, list, offset, onFetchFavoritedItems, page]) + + const hasMorePages = + Boolean(list) && + (items.length !== list?.itemsCount || list?.itemsCount === PAGE_SIZE) && + page <= MAX_PAGE + if (!isConnecting && !wallet && list?.isPrivate) { return } @@ -149,9 +172,19 @@ const ListPage = (props: Props) => { return ( {isLoading || isConnecting ? ( - + <> +
+
+ +
+ ) : null} - {!isLoading && !isConnecting && listId && list && !error ? ( + {!isConnecting && listId && list && !error ? (
{(!isPublicView || list.id === DEFAULT_FAVORITES_LIST_ID) && @@ -257,12 +290,28 @@ const ListPage = (props: Props) => { data-testid={ASSET_BROWSE_TEST_ID} className={styles.assetBrowseContainer} > +
+ {list.itemsCount + ? t('lists_page.subtitle', { count: list.itemsCount }) + : null} +
{list.itemsCount ? ( - + <> +
+ {items.map((item, index) => ( + + ))} +
+ + {null} + + ) : (
diff --git a/webapp/src/components/ListPage/ListPage.types.ts b/webapp/src/components/ListPage/ListPage.types.ts index 150dc04cbc..63d282615f 100644 --- a/webapp/src/components/ListPage/ListPage.types.ts +++ b/webapp/src/components/ListPage/ListPage.types.ts @@ -1,13 +1,16 @@ import { Dispatch } from 'redux' import { CallHistoryMethodAction } from 'connected-react-router' import { RouteComponentProps } from 'react-router-dom' +import { Item } from '@dcl/schemas' import { Wallet } from 'decentraland-dapps/dist/modules/wallet/types' import { GoBackAction } from '../../modules/routing/actions' import { List } from '../../modules/favorites/types' import { DeleteListStartAction, + FetchFavoritedItemsRequestAction, GetListRequestAction, deleteListStart, + fetchFavoritedItemsRequest, getListRequest } from '../../modules/favorites/actions' import { OpenModalAction, openModal } from '../../modules/modal/actions' @@ -19,13 +22,16 @@ export type Props = { wallet: Wallet | null listId?: string list: List | null - isLoading: boolean + items: Item[] + isLoadingList: boolean + isLoadingItems: boolean error: string | null onFetchList: typeof getListRequest onBack: () => void onEditList: (list: List) => ReturnType onDeleteList: typeof deleteListStart onShareList?: (list: List) => ReturnType + onFetchFavoritedItems: typeof fetchFavoritedItemsRequest isListV1Enabled: boolean } & RouteComponentProps @@ -35,14 +41,21 @@ export type MapStateProps = Pick< | 'wallet' | 'listId' | 'list' - | 'isLoading' + | 'items' + | 'isLoadingList' + | 'isLoadingItems' | 'error' | 'isListV1Enabled' > export type MapDispatchProps = Pick< Props, - 'onBack' | 'onFetchList' | 'onEditList' | 'onDeleteList' | 'onShareList' + | 'onBack' + | 'onFetchList' + | 'onEditList' + | 'onDeleteList' + | 'onShareList' + | 'onFetchFavoritedItems' > export type MapDispatch = Dispatch< | CallHistoryMethodAction @@ -50,5 +63,6 @@ export type MapDispatch = Dispatch< | GetListRequestAction | OpenModalAction | DeleteListStartAction + | FetchFavoritedItemsRequestAction > export type OwnProps = RouteComponentProps diff --git a/webapp/src/components/ListsPage/ListCard/ListCard.tsx b/webapp/src/components/ListsPage/ListCard/ListCard.tsx index 4d5f015735..a804239ebc 100644 --- a/webapp/src/components/ListsPage/ListCard/ListCard.tsx +++ b/webapp/src/components/ListsPage/ListCard/ListCard.tsx @@ -4,11 +4,6 @@ import { Card, Dropdown, Icon } from 'decentraland-ui' import { t } from 'decentraland-dapps/dist/modules/translation/utils' import { locations } from '../../../modules/routing/locations' import { DEFAULT_FAVORITES_LIST_ID } from '../../../modules/vendor/decentraland/favorites/api' -import { AssetType } from '../../../modules/asset/types' -import { Section } from '../../../modules/vendor/decentraland' -import { View } from '../../../modules/ui/types' -import { VendorName } from '../../../modules/vendor' -import { SortBy } from '../../../modules/routing/types' import { AssetImage } from '../../AssetImage' import { PrivateTag } from '../../PrivateTag' import { Props } from './ListCard.types' @@ -33,18 +28,7 @@ const ListCard = (props: Props) => { ) return ( - +
{list.isPrivate ? ( { } = props const [hasCopiedAddress, setHasCopied] = useTimer(1200) - const listLink = locations.list(list.id, { - assetType: AssetType.ITEM, - page: 1, - section: Section.LISTS, - view: View.LISTS, - vendor: VendorName.DECENTRALAND, - sortBy: SortBy.NEWEST - }) + const listLink = locations.list(list.id) const handleClose = useCallback(() => { onClose() diff --git a/webapp/src/modules/routing/sagas.ts b/webapp/src/modules/routing/sagas.ts index 70f84e9d2c..3aeb826a95 100644 --- a/webapp/src/modules/routing/sagas.ts +++ b/webapp/src/modules/routing/sagas.ts @@ -119,7 +119,6 @@ import { } from '../bid/actions' import { getData } from '../event/selectors' import { getPage } from '../ui/browse/selectors' -import { fetchFavoritedItemsRequest } from '../favorites/actions' import { AssetStatusFilter } from '../../utils/filters' import { buildBrowseURL } from './utils' @@ -298,16 +297,6 @@ export function* fetchAssetsFromRoute(options: BrowseOptions) { search ) break - case Section.LISTS: - yield put( - fetchFavoritedItemsRequest({ - view, - section, - page, - filters: { first, skip } - }) - ) - break default: const isWearableHead = section === Sections[VendorName.DECENTRALAND].WEARABLES_HEAD @@ -560,8 +549,6 @@ function* deriveCurrentOptions( section: current.section || previous.section } - if (newOptions.section === Section.LISTS) return newOptions - newOptions = { ...newOptions, onlyOnRent: current.hasOwnProperty('onlyOnRent') diff --git a/webapp/src/modules/routing/search.ts b/webapp/src/modules/routing/search.ts index 0c2db3f01d..d86ff06ac8 100644 --- a/webapp/src/modules/routing/search.ts +++ b/webapp/src/modules/routing/search.ts @@ -23,8 +23,6 @@ export function getDefaultOptionsByView( view?: View, section?: Section ): BrowseOptions { - if (section === Section.LISTS) return {} - let defaultOptions: Partial = { onlyOnSale: view && isAccountView(view) ? false : undefined, sortBy: diff --git a/webapp/src/modules/routing/selectors.ts b/webapp/src/modules/routing/selectors.ts index f1d44301f6..4f337054a1 100644 --- a/webapp/src/modules/routing/selectors.ts +++ b/webapp/src/modules/routing/selectors.ts @@ -21,7 +21,7 @@ import { RootState } from '../reducer' import { AssetType } from '../asset/types' import { getAddress as getWalletAddress } from '../wallet/selectors' import { getAddress as getAccountAddress } from '../account/selectors' -import { isLandSection, isListsSection } from '../ui/utils' +import { isLandSection } from '../ui/utils' import { getDefaultOptionsByView, getURLParamArray, @@ -608,8 +608,6 @@ export const hasFiltersEnabled = createSelector< } = browseOptions const isLand = isLandSection(section as Section) - if (isListsSection(section as Section)) return false - if (isLand) { const hasOnSaleFilter = onlyOnSale === true const hasOnRentFilter = onlyOnRent === true diff --git a/webapp/src/modules/toast/toasts.tsx b/webapp/src/modules/toast/toasts.tsx index e0ec73d7f4..3387235f5e 100644 --- a/webapp/src/modules/toast/toasts.tsx +++ b/webapp/src/modules/toast/toasts.tsx @@ -15,16 +15,11 @@ import { undoUnpickingItemAsFavoriteRequest, unpickItemAsFavoriteRequest } from '../favorites/actions' -import { AssetType } from '../asset/types' -import { Section } from '../vendor/decentraland' -import { View } from '../ui/types' import { List } from '../favorites/types' import { ListOfLists, UpdateOrCreateList } from '../vendor/decentraland/favorites/types' -import { SortBy } from '../routing/types' -import { VendorName } from '../vendor' import { toastDispatchableActionsChannel } from './utils' import { BulkPickUnpickMessageType, @@ -328,32 +323,14 @@ function buildBulkPickItemBodyMessage( count: lists.length, first_list_name: ( - + {lists[0]?.name ?? ''} ), second_list_name: ( - + {lists[1]?.name ?? ''} diff --git a/webapp/src/modules/translation/locales/en.json b/webapp/src/modules/translation/locales/en.json index 7293e49b9c..4bc8e14648 100644 --- a/webapp/src/modules/translation/locales/en.json +++ b/webapp/src/modules/translation/locales/en.json @@ -1325,6 +1325,7 @@ }, "list_page": { "default_title": "Wishlist", + "subtitle": "{count} {count, plural, one {item} other {items}}", "edit_list": "Edit List", "delete_list": "Delete List", "last_updated_at": "Last updated", diff --git a/webapp/src/modules/translation/locales/es.json b/webapp/src/modules/translation/locales/es.json index b55830bfd8..4fa2b61359 100644 --- a/webapp/src/modules/translation/locales/es.json +++ b/webapp/src/modules/translation/locales/es.json @@ -1316,6 +1316,7 @@ }, "list_page": { "default_title": "Lista de deseos", + "subtitle": "{count} {count, plural, one {item} other {items}}", "edit_list": "Editar Lista", "delete_list": "Eliminar Lista", "last_updated_at": "Última actualización", diff --git a/webapp/src/modules/translation/locales/zh.json b/webapp/src/modules/translation/locales/zh.json index cf600b8675..052549d53b 100644 --- a/webapp/src/modules/translation/locales/zh.json +++ b/webapp/src/modules/translation/locales/zh.json @@ -1321,6 +1321,7 @@ }, "list_page": { "default_title": "心愿单", + "subtitle": "{count} {count, plural, one {项} other {项目}}", "edit_list": "编辑列表", "delete_list": "删除列表", "last_updated_at": "最后更新于", diff --git a/webapp/src/modules/ui/browse/selectors.ts b/webapp/src/modules/ui/browse/selectors.ts index 482a2309f9..e0db023ac0 100644 --- a/webapp/src/modules/ui/browse/selectors.ts +++ b/webapp/src/modules/ui/browse/selectors.ts @@ -30,7 +30,6 @@ import { ItemState } from '../../item/reducer' import { VendorName } from '../../vendor' import { getAddress, getWallet } from '../../wallet/selectors' import { getTransactionsByType } from '../../transaction/selectors' -import { Section, Sections } from '../../vendor/routing/types' import { Asset, AssetType } from '../../asset/types' import { View } from '../types' import { OnRentNFT, OnSaleElement, OnSaleNFT } from './types' @@ -60,15 +59,6 @@ const getItems = createSelector< browse.itemIds.map(id => itemsById[id]) ) -// export const getCatalogItems = createSelector< -// RootState, -// BrowseUIState, -// CatalogState['data'], -// CatalogItem[] -// >(getState, getCatalogData, (browse, catalogsById) => -// browse.catalogIds.map(id => catalogsById[id]) -// ) - export const getOnSaleItems = createSelector< RootState, ReturnType, @@ -82,16 +72,9 @@ export const getOnSaleItems = createSelector< export const getBrowseAssets = ( state: RootState, - section: Section, assetType: AssetType ): Asset[] => { - if (assetType === AssetType.ITEM) { - return section === Sections.decentraland.LISTS - ? getItemsPickedByUserOrCreator(state) - : getItems(state) - } else { - return getNFTs(state) - } + return assetType === AssetType.ITEM ? getItems(state) : getNFTs(state) } export const getBrowseLists = createSelector< diff --git a/webapp/src/modules/ui/utils.spec.ts b/webapp/src/modules/ui/utils.spec.ts deleted file mode 100644 index 39c0806aba..0000000000 --- a/webapp/src/modules/ui/utils.spec.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Section } from '../vendor/decentraland' -import { isListsSection } from './utils' - -describe('when getting if the section is lists', () => { - it('should return true when it is', () => { - expect(isListsSection(Section.LISTS)).toBe(true) - }) - - it('should return true when it is not', () => { - expect(isListsSection(Section.COLLECTIONS)).toBe(false) - }) -}) diff --git a/webapp/src/modules/ui/utils.ts b/webapp/src/modules/ui/utils.ts index c574a53f6e..256a095e92 100644 --- a/webapp/src/modules/ui/utils.ts +++ b/webapp/src/modules/ui/utils.ts @@ -11,8 +11,6 @@ const landSections = new Set
([ ]) export const isAccountView = (view: View) => accountViews.has(view) -export const isListsSection = (section?: Section) => - Sections.decentraland.LISTS === section export const isLandSection = (section?: Section) => !!section && landSections.has(section)