Skip to content

Commit

Permalink
fix: Collection details UI (#2537)
Browse files Browse the repository at this point in the history
* fix: Button actions font color

* feat: Remove create items notice

* fix: Collection cards empty state

* feat: Fetch forum post replies

* feat: Get collection forum post reply stats

* feat: Add unread posts badge

* feat: Update toggle on sale modal texts

* feat: Add on sale badge next to the name

* feat: Update collection details publish button text

* feat: Update collection details header margin bottom

* feat: Add tooltip to mint button

* feat: Reorder collections page action buttons

* feat: Move fetch forum post reply to collection details page

* feat: Set default sort order: Created At desc in Collections Page

* feat: Add reducer tests for collection forum post reply

* feat: Update tests

* feat: Update forum post reply topic id type

* feat: Update publish collection success modal icon

* fix: Add loading state for put on sale button

* feat: Set CREATED_AT Desc sort order by default in Curations Page

* feat: Refactor rendering forum replies badge condition
  • Loading branch information
cyaiox authored Feb 7, 2023
1 parent dbbdab8 commit 34fc771
Show file tree
Hide file tree
Showing 28 changed files with 445 additions and 110 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ import { getData as getWallet } from 'decentraland-dapps/dist/modules/wallet/sel
import { RootState } from 'modules/common/types'
import { getCollectionId } from 'modules/location/selectors'
import { getCollection, isOnSaleLoading, getLoading as getLoadingCollection, getStatusByCollectionId } from 'modules/collection/selectors'
import { DELETE_COLLECTION_REQUEST } from 'modules/collection/actions'
import { DELETE_COLLECTION_REQUEST, SET_COLLECTION_MINTERS_REQUEST } from 'modules/collection/actions'
import { openModal } from 'modules/modal/actions'
import { getCollectionItems } from 'modules/item/selectors'
import { ItemType } from 'modules/item/types'
import { fetchCollectionForumPostReplyRequest, FETCH_COLLECTION_FORUM_POST_REPLY_REQUEST } from 'modules/forum/actions'
import { MapStateProps, MapDispatchProps, MapDispatch } from './CollectionDetailPage.types'
import CollectionDetailPage from './CollectionDetailPage'

Expand All @@ -22,16 +23,19 @@ const mapState = (state: RootState): MapStateProps => {
tab: tab ? (tab as ItemType) : undefined,
wallet: getWallet(state)!,
collection,
isOnSaleLoading: isOnSaleLoading(state),
isOnSaleLoading: isOnSaleLoading(state) || isLoadingType(getLoadingCollection(state), SET_COLLECTION_MINTERS_REQUEST),
items: getCollectionItems(state, collectionId),
status: statusByCollectionId[collectionId],
isLoading: isLoadingType(getLoadingCollection(state), DELETE_COLLECTION_REQUEST)
isLoading:
isLoadingType(getLoadingCollection(state), DELETE_COLLECTION_REQUEST) ||
isLoadingType(getLoadingCollection(state), FETCH_COLLECTION_FORUM_POST_REPLY_REQUEST)
}
}

const mapDispatch = (dispatch: MapDispatch): MapDispatchProps => ({
onNavigate: (path, locationState) => dispatch(push(path, locationState)),
onOpenModal: (name, metadata) => dispatch(openModal(name, metadata))
onOpenModal: (name, metadata) => dispatch(openModal(name, metadata)),
onFetchCollectionForumPostReply: id => dispatch(fetchCollectionForumPostReplyRequest(id))
})

export default connect(mapState, mapDispatch)(CollectionDetailPage)
26 changes: 25 additions & 1 deletion src/components/CollectionDetailPage/CollectionDetailPage.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
.CollectionDetailPage > .ui.container > .dcl.section {
margin-bottom: 48px;
}

.CollectionDetailPage .header-column {
flex: none;
width: calc(100% - 300px);
Expand All @@ -24,6 +28,14 @@
display: inline-block;
}

.CollectionDetailPage .ui.circular.label.badge-on-sale {
margin-left: 1em;
padding: 0.5em 0.667em !important;
color: var(--text);
background-color: var(--primary);
text-transform: uppercase;
}

.CollectionDetailPage .section:not(.is-published) .header-row:hover {
cursor: pointer;
}
Expand Down Expand Up @@ -255,10 +267,17 @@
padding: 7px 20px;
}

.CollectionDetailPage .dcl.tabs .secondary-actions.tab .button span {
.CollectionDetailPage .dcl.tabs .secondary-actions.tab .button span,
.CollectionDetailPage .dcl.narrow .secondary-actions .button span {
color: var(--primary);
}

.CollectionDetailPage .badge-forum-unread-posts {
margin-left: 8px;
color: var(--text);
background-color: var(--primary);
}

.unsynced-collection.container {
display: flex;
padding: 24px 30px;
Expand Down Expand Up @@ -324,3 +343,8 @@
height: 48px;
width: 48px;
}

.CollectionDetailPage.popup-mint,
.CollectionDetailPage.popup-mint:before {
background-color: var(--smart-grey) !important;
}
94 changes: 68 additions & 26 deletions src/components/CollectionDetailPage/CollectionDetailPage.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import * as React from 'react'
import classNames from 'classnames'
import { Link } from 'react-router-dom'
import { Network } from '@dcl/schemas'
import { Section, Row, Narrow, Column, Header, Button, Popup, Tabs, Table } from 'decentraland-ui'
import { Section, Row, Narrow, Column, Header, Button, Popup, Tabs, Table, Label } from 'decentraland-ui'
import { NetworkCheck } from 'decentraland-dapps/dist/containers'
import { t, T } from 'decentraland-dapps/dist/modules/translation/utils'
import { t } from 'decentraland-dapps/dist/modules/translation/utils'
import { locations } from 'routing/locations'
import { FromParam } from 'modules/location/types'
import {
Expand All @@ -20,7 +19,6 @@ import { CollectionType } from 'modules/collection/types'
import CollectionProvider from 'components/CollectionProvider'
import { Item, ItemType, SyncStatus } from 'modules/item/types'
import LoggedInDetailPage from 'components/LoggedInDetailPage'
import Notice from 'components/Notice'
import NotFound from 'components/NotFound'
import BuilderIcon from 'components/Icon'
import Back from 'components/Back'
Expand All @@ -32,13 +30,33 @@ import CollectionItem from './CollectionItem'

import './CollectionDetailPage.css'

const STORAGE_KEY = 'dcl-collection-notice'

export default class CollectionDetailPage extends React.PureComponent<Props, State> {
state: State = {
tab: this.props.tab || ItemType.WEARABLE
}

componentDidMount(): void {
const { collection } = this.props
if (collection) {
this.fetchCollectionForumPostReply()
}
}

componentDidUpdate(prevProps: Props): void {
const { collection } = this.props
if (!prevProps.collection && collection) {
this.fetchCollectionForumPostReply()
}
}

fetchCollectionForumPostReply() {
const { collection, onFetchCollectionForumPostReply } = this.props
// Only fetch the forum post replies if the collection has a forum link and there's no other fetch process in progress
if (collection && collection.isPublished && !collection.isApproved && collection.forumLink) {
onFetchCollectionForumPostReply(collection.id)
}
}

handleMintItems = () => {
const { collection, onOpenModal } = this.props
onOpenModal('MintItemsModal', { collectionId: collection!.id })
Expand All @@ -58,9 +76,8 @@ export default class CollectionDetailPage extends React.PureComponent<Props, Sta

handleOnSaleChange = () => {
const { collection, wallet, onOpenModal } = this.props
const toggleIsOnSale = !isCollectionOnSale(collection!, wallet)
if (collection) {
onOpenModal('SellCollectionModal', { collectionId: collection.id, isOnSale: toggleIsOnSale })
onOpenModal('SellCollectionModal', { collectionId: collection.id, isOnSale: isCollectionOnSale(collection, wallet) })
}
}

Expand Down Expand Up @@ -164,21 +181,37 @@ export default class CollectionDetailPage extends React.PureComponent<Props, Sta
onClick={this.handleOnSaleChange}
>
<span className="text">
{isOnSale ? t('collection_detail_page.remove_from_marketplace') : t('collection_detail_page.on_sale')}
{isOnSale ? t('collection_detail_page.remove_from_marketplace') : t('collection_detail_page.put_for_sale')}
</span>
</Button>
)}
</NetworkCheck>
)
}

renderForumRepliesBadge() {
const { collection } = this.props
if (collection?.forumPostReply) {
const { highest_post_number, last_read_post_number } = collection.forumPostReply
const unreadPosts = highest_post_number - (last_read_post_number ?? 0)
return (
<Label className="badge-forum-unread-posts" circular size="tiny">
{unreadPosts}
</Label>
)
}

return null
}

renderPage(items: Item[]) {
const { tab } = this.state
const { status, wallet } = this.props
const collection = this.props.collection!

const canMint = canMintCollectionItems(collection, wallet.address)
const isLocked = isCollectionLocked(collection)
const isOnSale = isCollectionOnSale(collection, wallet)
const hasEmotes = items.some(item => item.type === ItemType.EMOTE)
const hasWearables = items.some(item => item.type === ItemType.WEARABLE)
const isEmoteMissingPrice = hasEmotes ? items.some(item => item.type === ItemType.EMOTE && !item.price) : false
Expand Down Expand Up @@ -209,21 +242,39 @@ export default class CollectionDetailPage extends React.PureComponent<Props, Sta
{collection.name}
</Header>
<BuilderIcon name="edit" className="edit-collection-name" />
{isOnSale ? (
<Label className="badge-on-sale" size="small" circular>
{t('collection_detail_page.on_sale')}
</Label>
) : null}
</Row>
)}
</Column>
<Column align="right" className="actions-container" shrink={false} grow={false}>
<Row className="actions">
{collection.isPublished && collection.isApproved ? (
<Button basic className="action-button" disabled={!canMint} onClick={this.handleMintItems}>
<span className="text">{t('collection_detail_page.mint_items')}</span>
</Button>
<Popup
content={t('collection_detail_page.can_mint')}
className="CollectionDetailPage popup-mint"
position="top center"
trigger={
<Button basic className="action-button" disabled={!canMint} onClick={this.handleMintItems}>
<span className="text">{t('collection_detail_page.mint_items')}</span>
</Button>
}
on="hover"
inverted
flowing
/>
) : null}
{collection.isPublished && collection.forumLink && !collection.isApproved ? (
<>
<Button basic onClick={this.handleNavigateToForum}>
<span>{t('collection_context_menu.forum_post')}</span>
</Button>
{collection?.forumPostReply ? this.renderForumRepliesBadge() : null}
</>
) : null}
{collection.isPublished && collection.forumLink && !collection.isApproved && (
<Button basic onClick={this.handleNavigateToForum}>
<span>{t('collection_context_menu.forum_post')}</span>
</Button>
)}
{!(collection.isPublished && collection.isApproved) && status !== SyncStatus.UNSYNCED ? (
<CollectionPublishButton collection={collection} />
) : null}
Expand All @@ -238,15 +289,6 @@ export default class CollectionDetailPage extends React.PureComponent<Props, Sta
</Row>
</Section>
<Narrow>
<Notice storageKey={STORAGE_KEY}>
<T
id="collection_detail_page.notice"
values={{
editor_link: <Link to={getCollectionEditorURL(collection, items)}>{t('global.click_here')}</Link>
}}
/>
</Notice>

{status === SyncStatus.UNSYNCED ? (
<div className="unsynced-collection container">
<i className="unsynced-collection alert-icon" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Wallet } from 'decentraland-dapps/dist/modules/wallet/types'
import { openModal, OpenModalAction } from 'modules/modal/actions'
import { Collection } from 'modules/collection/types'
import { Item, ItemType, SyncStatus } from 'modules/item/types'
import { fetchCollectionForumPostReplyRequest, FetchCollectionForumPostReplyRequestAction } from 'modules/forum/actions'

export type Props = {
tab?: ItemType
Expand All @@ -15,6 +16,7 @@ export type Props = {
status: SyncStatus
onNavigate: (path: string, prop?: CollectionDetailLocationState) => void
onOpenModal: typeof openModal
onFetchCollectionForumPostReply: typeof fetchCollectionForumPostReplyRequest
}

export type CollectionDetailLocationState = {
Expand All @@ -26,5 +28,5 @@ export type State = {
}

export type MapStateProps = Pick<Props, 'wallet' | 'collection' | 'isOnSaleLoading' | 'isLoading' | 'items' | 'tab' | 'status'>
export type MapDispatchProps = Pick<Props, 'onNavigate' | 'onOpenModal'>
export type MapDispatch = Dispatch<CallHistoryMethodAction | OpenModalAction>
export type MapDispatchProps = Pick<Props, 'onNavigate' | 'onOpenModal' | 'onFetchCollectionForumPostReply'>
export type MapDispatch = Dispatch<CallHistoryMethodAction | OpenModalAction | FetchCollectionForumPostReplyRequestAction>
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ const CollectionPublishButton = (props: Props) => {
} else {
const publishButton = (
<NetworkButton disabled={isPublishDisabled} primary compact onClick={handlePublish} network={Network.MATIC}>
{t('global.publish')}
{t('collection_detail_page.publish')}
</NetworkButton>
)

Expand Down
7 changes: 4 additions & 3 deletions src/components/CollectionImage/CollectionImage.css
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,16 @@
height: 100%;
}

.CollectionImage .item-row.empty {
.CollectionImage > .item-row.empty {
display: flex;
flex-direction: column;
justify-content: center;
height: 100%;
color: var(--secondary-text);
border: 1px solid var(--divider);
border-radius: 5px;
border: 1px solid var(--secondary-text);
text-transform: uppercase;
margin-top: 0;
padding: 0;
}

.CollectionImage .item {
Expand Down
7 changes: 6 additions & 1 deletion src/components/CollectionsPage/CollectionsPage.css
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,12 @@
}

.CollectionsPage .filters .dcl.row.actions .ui.button.open-editor {
padding: 6px 13px;
display: flex;
align-items: center;
}

.CollectionsPage .filters .dcl.row.actions .ui.button.open-editor .Icon {
margin-right: 6px;
}

.CollectionsPage .filters .dcl.row.actions .ui.button.open-editor:hover {
Expand Down
35 changes: 11 additions & 24 deletions src/components/CollectionsPage/CollectionsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,15 @@ const PAGE_SIZE = 20
export default class CollectionsPage extends React.PureComponent<Props> {
state = {
currentTab: TABS.COLLECTIONS,
sort: CurationSortOptions.MOST_RELEVANT,
sort: CurationSortOptions.CREATED_AT_DESC,
page: 1
}

componentDidMount() {
const { address, hasUserOrphanItems, onFetchCollections, onFetchOrphanItem } = this.props
// fetch if already connected
if (address) {
onFetchCollections(address, { page: 1, limit: PAGE_SIZE, sort: CurationSortOptions.MOST_RELEVANT })
onFetchCollections(address, { page: 1, limit: PAGE_SIZE, sort: CurationSortOptions.CREATED_AT_DESC })
// TODO: Remove this call when there are no users with orphan items
if (hasUserOrphanItems === undefined) {
onFetchOrphanItem(address)
Expand Down Expand Up @@ -186,31 +186,18 @@ export default class CollectionsPage extends React.PureComponent<Props> {
return (
<Column align="right">
<Row className="actions">
{this.isCollectionTabActive() && (
<>
<Dropdown
trigger={
<Button basic className="create-item">
<Icon name="add-active" />
</Button>
}
inline
direction="left"
>
<Dropdown.Menu>
<>
<Dropdown.Item text={t('collections_page.new_collection')} onClick={this.handleNewCollection} />
{isThirdPartyManager && (
<Dropdown.Item text={t('collections_page.new_third_party_collection')} onClick={this.handleNewThirdPartyCollection} />
)}
</>
</Dropdown.Menu>
</Dropdown>
</>
{isThirdPartyManager && (
<Button className="action-button" size="small" basic onClick={this.handleNewThirdPartyCollection}>
{t('collections_page.new_third_party_collection')}
</Button>
)}
<Button className="open-editor" primary onClick={this.handleOpenEditor} size="tiny">
<Button className="action-button open-editor" size="small" basic onClick={this.handleOpenEditor}>
<Icon name="cube" />
{t('item_editor.open')}
</Button>
<Button className="action-button" size="small" primary onClick={this.handleNewCollection}>
{t('collections_page.new_collection')}
</Button>
</Row>
</Column>
)
Expand Down
2 changes: 1 addition & 1 deletion src/components/CurationPage/CurationPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ const CAMPAIGN_TAG = 'HolidaySeason'

export default class CurationPage extends React.PureComponent<Props, State> {
state: State = {
sortBy: CurationSortOptions.MOST_RELEVANT,
sortBy: CurationSortOptions.CREATED_AT_DESC,
filterByStatus: CurationExtraStatuses.ALL_STATUS,
filterByTags: [],
filterByType: CollectionExtraTypes.ALL_TYPES,
Expand Down
Loading

1 comment on commit 34fc771

@vercel
Copy link

@vercel vercel bot commented on 34fc771 Feb 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

builder – ./

builder-decentraland1.vercel.app
builder-git-master-decentraland1.vercel.app

Please sign in to comment.