Skip to content

Commit

Permalink
Merge 045299b into 5ea5bba
Browse files Browse the repository at this point in the history
  • Loading branch information
meelrossi committed Nov 8, 2023
2 parents 5ea5bba + 045299b commit 92b167d
Show file tree
Hide file tree
Showing 24 changed files with 249 additions and 31 deletions.
15 changes: 14 additions & 1 deletion webapp/src/components/AssetImage/AssetImage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ const AssetImage = (props: Props) => {
wearableController,
isTryingOn,
isPlayingEmote,
showUpdatedDateWarning,
onSetIsTryingOn,
onSetWearablePreviewController,
onPlaySmartWearableVideoShowcase,
Expand Down Expand Up @@ -186,7 +187,13 @@ const AssetImage = (props: Props) => {
setHasSound(sound)
})
}
}, [wearableController, asset.category, isDraggable, hasSound, isLoadingWearablePreview])
}, [
wearableController,
asset.category,
isDraggable,
hasSound,
isLoadingWearablePreview
])

const estateSelection = useMemo(() => (estate ? getSelection(estate) : []), [
estate
Expand Down Expand Up @@ -257,6 +264,9 @@ const AssetImage = (props: Props) => {
showForRent={false}
showOnSale={false}
showOwned={false}
lastUpdated={
showUpdatedDateWarning ? new Date(asset.updatedAt) : undefined
}
>
{hasBadges && children}
</Atlas>
Expand All @@ -278,6 +288,9 @@ const AssetImage = (props: Props) => {
showOnSale={false}
showOwned={false}
isEstate
lastUpdated={
showUpdatedDateWarning ? new Date(asset.updatedAt) : undefined
}
>
{hasBadges && children}
</Atlas>
Expand Down
1 change: 1 addition & 0 deletions webapp/src/components/AssetImage/AssetImage.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export type Props = {
className?: string
isDraggable?: boolean
withNavigation?: boolean
showUpdatedDateWarning?: boolean
hasPopup?: boolean
zoom?: number
isSmall?: boolean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ const EstateDetail = ({ nft, order, rental }: Props) => {
isDraggable
withNavigation
hasPopup
showUpdatedDateWarning
/>
{estate.size === 0 && (
<div className="dissolved-wrapper">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,13 @@ const ParcelDetail = ({ nft, order, rental }: Props) => {
asset={nft}
rental={rental ?? undefined}
assetImage={
<AssetImage asset={nft} isDraggable withNavigation hasPopup />
<AssetImage
asset={nft}
isDraggable
withNavigation
hasPopup
showUpdatedDateWarning
/>
}
showDetails={isLand(nft)}
isOnSale={!!nft.activeOrderId}
Expand Down
3 changes: 2 additions & 1 deletion webapp/src/components/Atlas/Atlas.container.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { connect } from 'react-redux'
import { push } from 'connected-react-router'
import { getTiles, getTilesByEstateId } from '../../modules/tile/selectors'
import { getLastModifiedDate, getTiles, getTilesByEstateId } from '../../modules/tile/selectors'
import { getOnRentNFTsByLessor } from '../../modules/ui/browse/selectors'
import { RootState } from '../../modules/reducer'
import { getWalletNFTs } from '../../modules/nft/selectors'
Expand All @@ -19,6 +19,7 @@ const mapState = (state: RootState): MapStateProps => {
nfts: getWalletNFTs(state),
nftsOnRent,
tilesByEstateId: getTilesByEstateId(state),
lastAtlasModifiedDate: getLastModifiedDate(state),
getContract: (query: Partial<Contract>) => getContract(state, query)
}
}
Expand Down
5 changes: 5 additions & 0 deletions webapp/src/components/Atlas/Atlas.css
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@
outline: 1px solid white;
}

.atlas-wrapper .atlas-warning-banner {
position: absolute;
bottom: 0;
}

.atlas-wrapper .atlas-info-button .info-icon {
background-image: url('../../images/info.svg');
width: 16px;
Expand Down
15 changes: 14 additions & 1 deletion webapp/src/components/Atlas/Atlas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { VendorName } from '../../modules/vendor'
import { NFT } from '../../modules/nft/types'
import Popup from './Popup'
import './Atlas.css'
import ErrorBanner from '../ErrorBanner'

const getCoords = (x: number | string, y: number | string) => `${x},${y}`

Expand All @@ -35,8 +36,10 @@ const Atlas: React.FC<Props> = (props: Props) => {
tilesByEstateId,
withMapColorsInfo,
withZoomControls,
lastAtlasModifiedDate,
getContract,
children
children,
lastUpdated
} = props

const [showPopup, setShowPopup] = useState(false)
Expand Down Expand Up @@ -375,6 +378,16 @@ const Atlas: React.FC<Props> = (props: Props) => {
layers={layers}
withZoomControls={withZoomControls}
/>
{lastAtlasModifiedDate &&
lastUpdated &&
lastUpdated > lastAtlasModifiedDate ? (
<ErrorBanner
className="atlas-warning-banner"
info={t('atlas_updated_warning.info', {
strong: (text: string) => <strong>{text}</strong>
})}
/>
) : null}
{hoveredTile ? (
<Popup
x={x}
Expand Down
4 changes: 3 additions & 1 deletion webapp/src/components/Atlas/Atlas.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,16 @@ export type Props = Partial<AtlasProps> & {
showOwned?: boolean
withMapColorsInfo?: boolean
withZoomControls?: boolean
lastUpdated?: Date
lastAtlasModifiedDate: Date | null
getContract: (query: Partial<Contract>) => ReturnType<typeof getContract>
onNavigate: (path: string) => void
children?: React.ReactNode
}

export type MapStateProps = Pick<
Props,
'tiles' | 'nfts' | 'nftsOnRent' | 'tilesByEstateId' | 'getContract'
'tiles' | 'nfts' | 'nftsOnRent' | 'tilesByEstateId' | 'getContract' | 'lastAtlasModifiedDate'
>
export type MapDispatchProps = Pick<Props, 'onNavigate'>
export type MapDispatch = Dispatch<CallHistoryMethodAction>
12 changes: 9 additions & 3 deletions webapp/src/components/BidPage/BidModal/BidModal.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useState, useCallback } from 'react'
import { ethers } from 'ethers'
import { Contract } from '@dcl/schemas'
import { Contract, NFTCategory } from '@dcl/schemas'
import { Header, Form, Field, Button } from 'decentraland-ui'
import { t, T } from 'decentraland-dapps/dist/modules/translation/utils'
import { withAuthorizedAction } from 'decentraland-dapps/dist/containers'
Expand Down Expand Up @@ -28,6 +28,7 @@ import { ConfirmInputValueModal } from '../../ConfirmInputValueModal'
import { Mana } from '../../Mana'
import { Props } from './BidModal.types'
import './BidModal.css'
import ErrorBanner from '../../ErrorBanner'

const BidModal = (props: Props) => {
const {
Expand All @@ -44,7 +45,7 @@ const BidModal = (props: Props) => {
const [price, setPrice] = useState('')
const [expiresAt, setExpiresAt] = useState(getDefaultExpirationDate())

const [fingerprint, isLoading] = useFingerprint(nft)
const [fingerprint, isLoading, contractFingerprint] = useFingerprint(nft)

const [showConfirmationModal, setShowConfirmationModal] = useState(false)

Expand Down Expand Up @@ -106,7 +107,9 @@ const BidModal = (props: Props) => {
isInvalidDate ||
hasInsufficientMANA ||
isLoading ||
isPlacingBid
isPlacingBid ||
(!fingerprint && nft.category === NFTCategory.ESTATE) ||
contractFingerprint !== fingerprint

return (
<AssetAction asset={nft}>
Expand Down Expand Up @@ -161,6 +164,9 @@ const BidModal = (props: Props) => {
error={isInvalidDate}
message={isInvalidDate ? t('bid_page.invalid_date') : undefined}
/>
{contractFingerprint !== fingerprint ? (
<ErrorBanner info={t('atlas_updated_warning.fingerprint_missmatch')} />
) : null}
</div>
<div className="buttons">
<Button
Expand Down
20 changes: 12 additions & 8 deletions webapp/src/components/BuyPage/BuyNFTModal/BuyNFTModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { PartiallySupportedNetworkCard } from '../PartiallySupportedNetworkCard'
import { NotEnoughMana } from '../NotEnoughMana'
import { PriceHasChanged } from '../PriceHasChanged'
import { Props } from './BuyNFTModal.types'
import ErrorBanner from '../../ErrorBanner'

const BuyNFTModal = (props: Props) => {
const {
Expand All @@ -51,7 +52,11 @@ const BuyNFTModal = (props: Props) => {
onClearOrderErrors
} = props

const [fingerprint, isFingerprintLoading] = useFingerprint(nft)
const [
fingerprint,
isFingerprintLoading,
contractFingerprint
] = useFingerprint(nft)
const analytics = getAnalytics()

const handleExecuteOrder = useCallback(
Expand Down Expand Up @@ -121,7 +126,9 @@ const BuyNFTModal = (props: Props) => {
!order ||
isOwner ||
(hasInsufficientMANA && !isBuyWithCardPage) ||
(!fingerprint && nft.category === NFTCategory.ESTATE)
(!fingerprint && nft.category === NFTCategory.ESTATE) ||
isFingerprintLoading ||
contractFingerprint !== fingerprint

const name = <Name asset={nft} />

Expand All @@ -140,12 +147,6 @@ const BuyNFTModal = (props: Props) => {
subtitle = (
<T id={`${translationPageDescriptorId}.not_for_sale`} values={{ name }} />
)
} else if (
!fingerprint &&
nft.category === NFTCategory.ESTATE &&
!isFingerprintLoading
) {
subtitle = <T id={`${translationPageDescriptorId}.no_fingerprint`} />
} else if (isOwner) {
subtitle = (
<T id={`${translationPageDescriptorId}.is_owner`} values={{ name }} />
Expand Down Expand Up @@ -202,6 +203,9 @@ const BuyNFTModal = (props: Props) => {
<PriceTooLow chainId={nft.chainId} network={nft.network} />
) : null}
<PartiallySupportedNetworkCard asset={nft} />
{contractFingerprint !== fingerprint ? (
<ErrorBanner info={t('atlas_updated_warning.fingerprint_missmatch')} />
) : null}
<div
className={classNames('buttons', isWearableOrEmote(nft) && 'with-mana')}
>
Expand Down
28 changes: 28 additions & 0 deletions webapp/src/components/ErrorBanner/ErrorBanner.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
.container {
background: var(--dropdown);
display: flex;
padding: 20px;
gap: 10px;
}

.icon {
background: var(--oj-not-simpson);
border-radius: 50%;
min-width: 25px;
height: 25px;
text-align: center;
margin: 0;
}

.icon :global(.icon) {
position: relative;
top: 2px;
margin: 0;
}

.info {
display: flex;
flex-direction: column;
gap: 5px;
text-align: left;
}
17 changes: 17 additions & 0 deletions webapp/src/components/ErrorBanner/ErrorBanner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import classNames from 'classnames'
import { Icon } from 'decentraland-ui'
import { Props } from './ErrorBanner.types'
import styles from './ErrorBanner.module.css'

export default function ErrorBanner({ info, className }: Props) {
return (
<div className={classNames(styles.container, className)}>
<div className={styles.icon}>
<Icon name="exclamation triangle" />
</div>
<span className={styles.info}>
{info}
</span>
</div>
)
}
4 changes: 4 additions & 0 deletions webapp/src/components/ErrorBanner/ErrorBanner.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export type Props = {
info: string;
className?: string;
}
2 changes: 2 additions & 0 deletions webapp/src/components/ErrorBanner/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import ErrorBanner from "./ErrorBanner";
export default ErrorBanner
53 changes: 52 additions & 1 deletion webapp/src/modules/nft/estate/utils.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import {
getSigner,
getConnectedProvider
getConnectedProvider,
getNetworkProvider
} from 'decentraland-dapps/dist/lib/eth'
import { EstateRegistry__factory } from '../../../contracts'
import { Contract } from '../../vendor/services'
import { NFT } from '../types'
import { ethers } from 'ethers'

export const getSelection = (estate: NFT['data']['estate']) => {
return estate!.parcels.map(pair => ({
Expand All @@ -21,6 +23,55 @@ export const getCenter = (selection: { x: number; y: number }[]) => {
return [x, y]
}

export async function generateFingerprint(
estateId: string,
parcels: { x: number; y: number }[],
landContract: Contract
) {
const provider = await getNetworkProvider(landContract.chainId)
const contract = new ethers.Contract(
landContract.address,
[
{
constant: true,
inputs: [
{ name: 'x', type: 'int256' },
{ name: 'y', type: 'int256' }
],
name: 'encodeTokenId',
outputs: [{ name: '', type: 'uint256' }],
payable: false,
stateMutability: 'pure',
type: 'function'
}
],
new ethers.providers.Web3Provider(provider)
)

const estateTokenIds = []

for (const parcel of parcels) {
estateTokenIds.push(
(await contract.encodeTokenId(parcel.x, parcel.y)).toString()
)
}

let fingerprint = BigInt(
ethers.utils.solidityKeccak256(
['string', 'uint256'],
['estateId', estateId]
)
)

for (const tokenId of estateTokenIds) {
fingerprint ^= BigInt(
ethers.utils.solidityKeccak256(['uint256'], [tokenId])
)
}

return fingerprint.toString(16)
}

export async function getFingerprint(
estateId: string,
estateContract: Contract
Expand Down
Loading

0 comments on commit 92b167d

Please sign in to comment.