Skip to content

Commit

Permalink
Feat/my bid (#1695)
Browse files Browse the repository at this point in the history
* fix: removed actual nft from table

* feat: added bids table

* feat: bids

* fix: tests

* fix: imported react on utils file

* fix: warning
  • Loading branch information
flobarreto authored and juanmahidalgo committed Jun 1, 2023
1 parent 1536f72 commit 2c22389
Show file tree
Hide file tree
Showing 28 changed files with 687 additions and 23 deletions.
2 changes: 1 addition & 1 deletion webapp/src/components/AssetCard/AssetCard.css
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ a.ui.card:hover,
.AssetCard .wrapBigText {
display: flex;
flex-direction: column;
align-items: start;
align-items: flex-start;
}

.AssetCard .wrapBigText > span {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ describe('Best Buying Option', () => {
tokenId: '1',
owner: '0x92712b730b9a474f99a47bb8b1750190d5959a2b',
buyer: null,
price: '10',
price: '100000000000000000000',
status: ListingStatus.OPEN,
expiresAt: 1671033414000,
createdAt: 1671033414000,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ const BestBuyingOption = ({ asset, tableRef }: Props) => {
'1'
)
.then(response => {
setMostExpensiveBid(response[0])
setMostExpensiveBid(response.data[0])
})
.finally(() => setIsLoading(false))
.catch(error => {
Expand Down
21 changes: 21 additions & 0 deletions webapp/src/components/AssetPage/BidsTable/BidsTable.container.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { isLoadingType } from 'decentraland-dapps/dist/modules/loading/selectors'
import { connect } from 'react-redux'
import { RootState } from '../../../modules/reducer'
import { getAddress, getLoading } from '../../../modules/wallet/selectors'
import {
ACCEPT_BID_REQUEST,
acceptBidRequest
} from '../../../modules/bid/actions'
import { MapDispatch, MapDispatchProps, MapStateProps } from './BidsTable.types'
import BidsTable from './BidsTable'

const mapState = (state: RootState): MapStateProps => ({
address: getAddress(state),
isAcceptingBid: isLoadingType(getLoading(state), ACCEPT_BID_REQUEST)
})

const mapDispatch = (dispatch: MapDispatch): MapDispatchProps => ({
onAccept: bid => dispatch(acceptBidRequest(bid))
})

export default connect(mapState, mapDispatch)(BidsTable)
82 changes: 82 additions & 0 deletions webapp/src/components/AssetPage/BidsTable/BidsTable.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
.BidsTable {
position: relative;
}

.linkedProfileRow {
color: var(--primary) !important;
}

.BidsTable .headerMargin {
padding: 20px !important;
}

.issuedIdContainer {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
font-size: 14px;
}

.BidsTable .issuedId {
font-size: 15px;
font-weight: 700;
}

.row {
display: flex;
flex-direction: row;
gap: 10px;
justify-content: space-between;
align-items: center;
}

.badgeContainer :global(.dcl.badge) {
color: white;
background-color: transparent !important;
}

.badgeContainer {
display: flex;
flex-direction: row;
gap: 10px;
}

.BidsTable .goToNFT {
color: white;
padding-right: 25px;
}

.emptyTable {
width: 100%;
height: 350px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 10px;
font-size: 20px;
}

.manaField {
display: flex;
align-items: center;
font-size: 14px;
}

.manaField :global(.ui.header:last-child) {
font-size: 14px;
}

.viewListingContainer {
display: flex;
flex-direction: row;
justify-content: space-between;
margin-right: 10px;
}

@media (max-width: 768px) {
.linkedProfileRow {
margin-left: unset;
}
}
162 changes: 162 additions & 0 deletions webapp/src/components/AssetPage/BidsTable/BidsTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import { ethers } from 'ethers'
import React, { useEffect, useState } from 'react'
import { Bid, BidSortBy } from '@dcl/schemas'
import { Mana } from 'decentraland-ui'
import { T, t } from 'decentraland-dapps/dist/modules/translation/utils'
import { bidAPI } from '../../../modules/vendor/decentraland'
import { formatWeiMANA } from '../../../lib/mana'
import { TableContent } from '../../Table/TableContent'
import { DataTableType } from '../../Table/TableContent/TableContent.types'
import TableContainer from '../../Table/TableContainer'
import { AssetType } from '../../../modules/asset/types'
import { getAssetName } from '../../../modules/asset/utils'
import { AssetProvider } from '../../AssetProvider'
import { ConfirmInputValueModal } from '../../ConfirmInputValueModal'
import { formatDataToTable } from './utils'
import { Props } from './BidsTable.types'

export const ROWS_PER_PAGE = 6
const INITIAL_PAGE = 1

const BidsTable = (props: Props) => {
const { nft, address, isAcceptingBid, onAccept } = props

const tabList = [
{
value: 'offers_table',
displayValue: t('offers_table.offers')
}
]

const sortByList = [
{
text: t('offers_table.most_expensive'),
value: BidSortBy.MOST_EXPENSIVE
},
{
text: t('offers_table.recenty_offered'),
value: BidSortBy.RECENTLY_OFFERED
},
{
text: t('offers_table.recently_updated'),
value: BidSortBy.RECENTLY_UPDATED
}
]

const [bids, setBids] = useState<DataTableType[]>([])
const [total, setTotal] = useState(0)
const [page, setPage] = useState(INITIAL_PAGE)
const [totalPages, setTotalPages] = useState<number>(0)
const [isLoading, setIsLoading] = useState(false)
const [sortBy, setSortBy] = useState<BidSortBy>(BidSortBy.MOST_EXPENSIVE)
const [showConfirmationModal, setShowConfirmationModal] = useState<{
display: boolean
bid: Bid | null
}>({
display: false,
bid: null
})

// We're doing this outside of redux to avoid having to store all orders when we only care about the first ROWS_PER_PAGE
useEffect(() => {
if (nft) {
setIsLoading(true)
bidAPI
.fetchByNFT(
nft.contractAddress,
nft.tokenId,
null,
sortBy,
ROWS_PER_PAGE.toString(),
((page - 1) * ROWS_PER_PAGE).toString()
)
.then(response => {
setTotal(response.total)
setBids(
formatDataToTable(
response.data.filter(bid => bid.bidder !== address),
bid => setShowConfirmationModal({ display: true, bid: bid }),
address
)
)
setTotalPages(Math.ceil(response.total / ROWS_PER_PAGE) | 0)
})
.finally(() => setIsLoading(false))
.catch(error => {
console.error(error)
})
}
}, [nft, setIsLoading, setBids, page, sortBy, address])

return bids.length > 0 ? (
<>
<TableContainer
children={
<TableContent
data={bids}
activePage={page}
isLoading={isLoading}
setPage={setPage}
totalPages={totalPages}
empty={() => null}
total={total}
hasHeaders
/>
}
tabsList={tabList}
sortbyList={sortByList}
handleSortByChange={(value: string) => setSortBy(value as BidSortBy)}
sortBy={sortBy}
/>
{showConfirmationModal.bid && showConfirmationModal.display ? (
<AssetProvider
type={AssetType.NFT}
contractAddress={showConfirmationModal.bid.contractAddress}
tokenId={showConfirmationModal.bid.tokenId}
>
{nft =>
nft &&
showConfirmationModal.bid && (
<ConfirmInputValueModal
open={showConfirmationModal.display}
headerTitle={t('bid_page.confirm.title')}
content={
<>
<T
id="bid_page.confirm.accept_bid_line_one"
values={{
name: <b>{getAssetName(nft)}</b>,
amount: (
<Mana showTooltip network={nft.network} inline>
{formatWeiMANA(showConfirmationModal.bid.price)}
</Mana>
)
}}
/>
<br />
<T id="bid_page.confirm.accept_bid_line_two" />
</>
}
onConfirm={() => {
showConfirmationModal.bid &&
onAccept(showConfirmationModal.bid)
}}
valueToConfirm={ethers.utils.formatEther(
showConfirmationModal.bid.price
)}
network={nft.network}
onCancel={() =>
setShowConfirmationModal({ display: false, bid: null })
}
loading={isAcceptingBid}
disabled={isAcceptingBid}
/>
)
}
</AssetProvider>
) : null}
</>
) : null
}

export default React.memo(BidsTable)
17 changes: 17 additions & 0 deletions webapp/src/components/AssetPage/BidsTable/BidsTable.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Dispatch } from 'redux'
import { Bid } from '@dcl/schemas'
import { VendorName } from '../../../modules/vendor'
import { NFT } from '../../../modules/nft/types'
import { AcceptBidRequestAction } from '../../../modules/bid/actions'

export type Props = {
nft: NFT<VendorName.DECENTRALAND> | null
address?: string
onAccept: (bid: Bid) => void
isAcceptingBid: boolean
}

export type MapStateProps = Pick<Props, 'address' | 'isAcceptingBid'>

export type MapDispatchProps = Pick<Props, 'onAccept'>
export type MapDispatch = Dispatch<AcceptBidRequestAction>
2 changes: 2 additions & 0 deletions webapp/src/components/AssetPage/BidsTable/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import BidsTable from './BidsTable.container'
export { BidsTable }
53 changes: 53 additions & 0 deletions webapp/src/components/AssetPage/BidsTable/utils.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Bid } from '@dcl/schemas'
import { t } from 'decentraland-dapps/dist/modules/translation/utils'
import { Button, Mana } from 'decentraland-ui'
import { formatDistanceToNow, getDateAndMonthName } from '../../../lib/date'
import { formatWeiMANA } from '../../../lib/mana'
import { LinkedProfile } from '../../LinkedProfile'
import { ManaToFiat } from '../../ManaToFiat'
import { DataTableType } from '../../Table/TableContent/TableContent.types'
import styles from './BidsTable.module.css'

export const formatDataToTable = (
bids: Bid[],
setShowConfirmationModal: (bid: Bid) => void,
address?: string | null
): DataTableType[] => {
return bids.reduce((accumulator: DataTableType[], bid: Bid) => {
const value: DataTableType = {
[t('offers_table.from')]: (
<LinkedProfile
className={styles.linkedProfileRow}
address={bid.bidder}
/>
),
[t('offers_table.published_date')]: getDateAndMonthName(bid.createdAt),
[t('offers_table.expiration_date')]: formatDistanceToNow(+bid.expiresAt, {
addSuffix: true
}),
[t('listings_table.price')]: (
<div className={styles.viewListingContainer}>
<div className={styles.manaField}>
<Mana className="manaField" network={bid.network}>
{formatWeiMANA(bid.price)}
</Mana>{' '}
&nbsp;
{'('}
<ManaToFiat mana={bid.price} />
{')'}
</div>
{address === bid.seller ? (
<Button
primary
onClick={() => setShowConfirmationModal(bid)}
size="small"
>
{t('offers_table.accept')}
</Button>
) : null}
</div>
)
}
return [...accumulator, value]
}, [])
}
6 changes: 4 additions & 2 deletions webapp/src/components/AssetPage/EmoteDetail/EmoteDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import CampaignBadge from '../../Campaign/CampaignBadge'
import TableContainer from '../../Table/TableContainer'
import RarityBadge from '../../RarityBadge'
import BaseDetail from '../BaseDetail'
import { BidList } from '../BidList'
import { BidsTable } from '../BidsTable'
import { YourOffer } from '../YourOffer'
import Collection from '../Collection'
import { Description } from '../Description'
import IconBadge from '../IconBadge'
Expand Down Expand Up @@ -98,7 +99,8 @@ const EmoteDetail = ({ nft }: Props) => {
actions={<SaleActionBox asset={nft} />}
below={
<>
<BidList nft={nft} />
<YourOffer nft={nft} />
<BidsTable nft={nft} />
<TransactionHistory asset={nft} />
<TableContainer
tabsList={tabList}
Expand Down
Loading

0 comments on commit 2c22389

Please sign in to comment.