From 153f2381a9a87076fc8db60f727e91e45f013767 Mon Sep 17 00:00:00 2001 From: cmarcelo Date: Mon, 22 Apr 2019 16:58:19 +0100 Subject: [PATCH 01/18] Adding status field to MyAsset --- decentraland/constants/MyAssetStatus.js | 7 +++++++ decentraland/helpers/index.js | 8 ++++++++ decentraland/redux/actions.js | 8 ++++++++ decentraland/redux/reducers.js | 19 +++++++++++++++++-- decentraland/screens/BuyLandScreen.js | 11 ++++++++++- decentraland/screens/MyAssetsScreen.js | 5 ++++- 6 files changed, 54 insertions(+), 4 deletions(-) create mode 100644 decentraland/constants/MyAssetStatus.js diff --git a/decentraland/constants/MyAssetStatus.js b/decentraland/constants/MyAssetStatus.js new file mode 100644 index 00000000..17a369e5 --- /dev/null +++ b/decentraland/constants/MyAssetStatus.js @@ -0,0 +1,7 @@ +export const BUYING = "BUYING"; +export const BOUGHT = "BOUGHT"; + +export default { + BUYING, + BOUGHT, +}; diff --git a/decentraland/helpers/index.js b/decentraland/helpers/index.js index adb00d15..5c7c1d6b 100644 --- a/decentraland/helpers/index.js +++ b/decentraland/helpers/index.js @@ -171,6 +171,13 @@ export const listsAreEqual = (first, second) => { ); }; +// Update any list having id as key field +export const updateListItem = (list, toUpdateId, entriesToUpdate) => { + return list.map(item => { + return item.id === toUpdateId ? { ...item, ...entriesToUpdate } : item; + }); +}; + const loadConfig = () => { const tasitSdkConfig = require("../config/current.js"); ConfigLoader.setConfig(tasitSdkConfig); @@ -255,4 +262,5 @@ export default { getNetworkName, buildBlockchainUrlFromActionId, restoreCreationStateOfAccountFromBlockchain, + updateListItem, }; diff --git a/decentraland/redux/actions.js b/decentraland/redux/actions.js index 3426995f..2a9768ff 100644 --- a/decentraland/redux/actions.js +++ b/decentraland/redux/actions.js @@ -13,6 +13,7 @@ export const ADD_TO_MY_ASSETS_LIST = "ADD_TO_MY_ASSETS_LIST"; export const REMOVE_MY_ASSET_FROM_LIST = "REMOVE_MY_ASSET_FROM_LIST"; export const SET_MY_ASSETS_LIST = "SET_MY_ASSETS_LIST"; export const SET_ACTION_ID_FOR_MY_ASSET = "SET_ACTION_ID_FOR_MY_ASSET"; +export const UPDATE_MY_ASSET_STATUS = "UPDATE_MY_ASSET_STATUS"; export function setAccount(account) { return { type: SET_ACCOUNT, account }; @@ -71,3 +72,10 @@ export function setActionIdForMyAsset(myAssetId, actionId) { myAssetAndActionIds: { myAssetId, actionId }, }; } + +export function updateMyAssetStatus(myAssetId, status) { + return { + type: UPDATE_MY_ASSET_STATUS, + myAssetAndStatus: { myAssetId, status }, + }; +} diff --git a/decentraland/redux/reducers.js b/decentraland/redux/reducers.js index c399d417..bef1466e 100644 --- a/decentraland/redux/reducers.js +++ b/decentraland/redux/reducers.js @@ -13,8 +13,9 @@ import { REMOVE_MY_ASSET_FROM_LIST, SET_MY_ASSETS_LIST, SET_ACTION_ID_FOR_MY_ASSET, + UPDATE_MY_ASSET_STATUS, } from "./actions"; -import { removeFromList } from "@helpers"; +import { removeFromList, updateListItem } from "@helpers"; import AccountCreationStatus from "@constants/AccountCreationStatus"; const { NOT_STARTED } = AccountCreationStatus; @@ -83,7 +84,13 @@ function assetsForSale(state = { list: [], loadingInProgress: true }, action) { } function myAssets(state = { list: [] }, action) { - const { type, myAsset, myAssets, myAssetAndActionIds } = action; + const { + type, + myAsset, + myAssets, + myAssetAndActionIds, + myAssetAndStatus, + } = action; switch (type) { case ADD_TO_MY_ASSETS_LIST: return { ...state, list: [myAsset, ...state.list] }; @@ -99,12 +106,20 @@ function myAssets(state = { list: [] }, action) { case SET_ACTION_ID_FOR_MY_ASSET: { const { myAssetId: toUpdateId, actionId } = myAssetAndActionIds; const { list: myAssets } = state; + // TODO: Use updateListItem const list = myAssets.map(asset => { if (asset.id === toUpdateId) return { ...asset, actionId }; else return asset; }); return { ...state, list }; } + case UPDATE_MY_ASSET_STATUS: { + const { myAssetId: toUpdateId, status } = myAssetAndStatus; + const { list: myAssets } = state; + const entriesToUpdate = { status }; + const list = updateListItem(myAssets, toUpdateId, entriesToUpdate); + return { ...state, list }; + } default: return state; } diff --git a/decentraland/screens/BuyLandScreen.js b/decentraland/screens/BuyLandScreen.js index 796cb684..575ba419 100644 --- a/decentraland/screens/BuyLandScreen.js +++ b/decentraland/screens/BuyLandScreen.js @@ -6,12 +6,15 @@ import { removeMyAssetFromList, addToMyAssetsList, setActionIdForMyAsset, + updateMyAssetStatus, } from "../redux/actions"; import BuyLand from "@presentational/BuyLand"; import PropTypes from "prop-types"; import { showError, showInfo, getContracts } from "@helpers"; import AssetTypes from "@constants/AssetTypes"; const { ESTATE, PARCEL } = AssetTypes; +import MyAssetStatus from "@constants/MyAssetStatus"; +const { BUYING, BOUGHT } = MyAssetStatus; // TODO: Go deep on gas handling. // Without that, VM returns a revert error instead of out of gas error. @@ -50,6 +53,7 @@ export class BuyLandScreen extends React.Component { removeMyAssetFromList, addToMyAssetsList, setActionIdForMyAsset, + updateMyAssetStatus, } = props; const { account } = accountInfo; const { asset } = landForSale; @@ -64,6 +68,8 @@ export class BuyLandScreen extends React.Component { // that catches the safeExecuteOrder successful event. await action.waitForNonceToUpdate(); + updateMyAssetStatus(assetId, BOUGHT); + showInfo(`${typeDescription} bought successfully.`); }; @@ -80,7 +86,7 @@ export class BuyLandScreen extends React.Component { // Optimistic UI update removeLandForSale(landForSale); - addToMyAssetsList(asset); + addToMyAssetsList({ ...asset, status: BUYING }); navigation.navigate("ListLandForSaleScreen"); @@ -101,6 +107,7 @@ export class BuyLandScreen extends React.Component { type === ESTATE ? estateContract.getAddress() : landContract.getAddress(); + // LANDRegistry contract doesn't implement getFingerprint function const fingerprint = type === ESTATE ? await estateContract.getFingerprint(assetId) : "0x"; @@ -148,6 +155,7 @@ BuyLandScreen.propTypes = { removeMyAssetFromList: PropTypes.func.isRequired, addToMyAssetsList: PropTypes.func.isRequired, setActionIdForMyAsset: PropTypes.func.isRequired, + updateMyAssetStatus: PropTypes.func.isRequired, }; const mapStateToProps = state => { @@ -162,6 +170,7 @@ const mapDispatchToProps = { addToMyAssetsList, removeMyAssetFromList, setActionIdForMyAsset, + updateMyAssetStatus, }; export default connect( diff --git a/decentraland/screens/MyAssetsScreen.js b/decentraland/screens/MyAssetsScreen.js index bd88357b..fbe86c41 100644 --- a/decentraland/screens/MyAssetsScreen.js +++ b/decentraland/screens/MyAssetsScreen.js @@ -7,6 +7,8 @@ import MyAssetsListItem from "@presentational/MyAssetsListItem"; import { listsAreEqual, getContracts } from "@helpers"; import { generateAssetFromId } from "@helpers/decentraland"; import DecentralandUtils from "tasit-sdk/dist/helpers/DecentralandUtils"; +import MyAssetStatus from "@constants/MyAssetStatus"; +const { BOUGHT } = MyAssetStatus; export class MyAssetsScreen extends React.Component { componentDidMount = async () => { @@ -45,6 +47,7 @@ export class MyAssetsScreen extends React.Component { for (let land of listOfLand) { const generateAsset = async land => { const { id: assetId, nftAddress, transactionHash: actionId } = land; + const status = BOUGHT; const asset = await generateAssetFromId( estateContract, @@ -53,7 +56,7 @@ export class MyAssetsScreen extends React.Component { nftAddress ); - return { ...asset, actionId }; + return { ...asset, actionId, status }; }; const assetPromise = generateAsset(land); From 7c21ad42026301effc11b4fb705c52f37e3d65c8 Mon Sep 17 00:00:00 2001 From: cmarcelo Date: Mon, 22 Apr 2019 19:59:59 +0100 Subject: [PATCH 02/18] Improve myassets updating from blockchain --- decentraland/redux/actions.js | 17 +++++++++------ decentraland/redux/middlewares.js | 18 ++++++++++++---- decentraland/redux/reducers.js | 11 ++++++---- decentraland/screens/BuyLandScreen.js | 22 ++++++++++---------- decentraland/screens/BuyLandScreen.test.js | 8 ++++---- decentraland/screens/MyAssetsScreen.js | 24 ++++++++++++++-------- 6 files changed, 63 insertions(+), 37 deletions(-) diff --git a/decentraland/redux/actions.js b/decentraland/redux/actions.js index 2a9768ff..d43c02d6 100644 --- a/decentraland/redux/actions.js +++ b/decentraland/redux/actions.js @@ -9,8 +9,9 @@ export const APPEND_LAND_FOR_SALE_TO_LIST = "APPEND_LAND_FOR_SALE_TO_LIST"; export const PREPEND_LAND_FOR_SALE_TO_LIST = "PREPEND_LAND_FOR_SALE_TO_LIST"; export const SET_LOADING_ASSETS_FOR_SALE_IN_PROGRESS = "SET_LOADING_ASSETS_FOR_SALE_IN_PROGRESS"; -export const ADD_TO_MY_ASSETS_LIST = "ADD_TO_MY_ASSETS_LIST"; -export const REMOVE_MY_ASSET_FROM_LIST = "REMOVE_MY_ASSET_FROM_LIST"; +export const PREPEND_TO_MY_ASSETS_LIST = "PREPEND_TO_MY_ASSETS_LIST"; +export const APPEND_TO_MY_ASSETS_LIST = "APPEND_TO_MY_ASSETS_LIST"; +export const REMOVE_FROM_MY_ASSETS_LIST = "REMOVE_FROM_MY_ASSETS_LIST"; export const SET_MY_ASSETS_LIST = "SET_MY_ASSETS_LIST"; export const SET_ACTION_ID_FOR_MY_ASSET = "SET_ACTION_ID_FOR_MY_ASSET"; export const UPDATE_MY_ASSET_STATUS = "UPDATE_MY_ASSET_STATUS"; @@ -54,12 +55,16 @@ export function removeLandForSale(landForSale) { return { type: REMOVE_LAND_FOR_SALE, landForSale }; } -export function addToMyAssetsList(myAsset) { - return { type: ADD_TO_MY_ASSETS_LIST, myAsset }; +export function prependToMyAssetsList(myAsset) { + return { type: PREPEND_TO_MY_ASSETS_LIST, myAsset }; } -export function removeMyAssetFromList(myAsset) { - return { type: REMOVE_MY_ASSET_FROM_LIST, myAsset }; +export function appendToMyAssetsList(myAsset) { + return { type: APPEND_TO_MY_ASSETS_LIST, myAsset }; +} + +export function removeFromMyAssetsList(myAsset) { + return { type: REMOVE_FROM_MY_ASSETS_LIST, myAsset }; } export function setMyAssetsList(myAssets) { diff --git a/decentraland/redux/middlewares.js b/decentraland/redux/middlewares.js index a0f14da7..dd18d315 100644 --- a/decentraland/redux/middlewares.js +++ b/decentraland/redux/middlewares.js @@ -4,10 +4,12 @@ import { SET_ACCOUNT_CREATION_STATUS, UPDATE_ACTION_FOR_ACCOUNT_CREATION_STATUS, SET_ACCOUNT_CREATION_ACTIONS, - ADD_TO_MY_ASSETS_LIST, - REMOVE_MY_ASSET_FROM_LIST, + PREPEND_TO_MY_ASSETS_LIST, + APPEND_TO_MY_ASSETS_LIST, + REMOVE_FROM_MY_ASSETS_LIST, SET_MY_ASSETS_LIST, SET_ACTION_ID_FOR_MY_ASSET, + UPDATE_MY_ASSET_STATUS, } from "./actions"; import { storeAccount, @@ -41,11 +43,15 @@ const storer = store => next => async action => { await storeAccountCreationActions(creationActions); break; } - case ADD_TO_MY_ASSETS_LIST: { + case PREPEND_TO_MY_ASSETS_LIST: { await storeMyAssets(myAssetsList); break; } - case REMOVE_MY_ASSET_FROM_LIST: { + case APPEND_TO_MY_ASSETS_LIST: { + await storeMyAssets(myAssetsList); + break; + } + case REMOVE_FROM_MY_ASSETS_LIST: { await storeMyAssets(myAssetsList); break; } @@ -57,6 +63,10 @@ const storer = store => next => async action => { await storeMyAssets(myAssetsList); break; } + case UPDATE_MY_ASSET_STATUS: { + await storeMyAssets(myAssetsList); + break; + } } return result; diff --git a/decentraland/redux/reducers.js b/decentraland/redux/reducers.js index bef1466e..f4d9bdab 100644 --- a/decentraland/redux/reducers.js +++ b/decentraland/redux/reducers.js @@ -9,8 +9,9 @@ import { UPDATE_ACTION_ID_FOR_ACCOUNT_CREATION_STATUS, SET_ACCOUNT_CREATION_ACTIONS, SET_LOADING_ASSETS_FOR_SALE_IN_PROGRESS, - ADD_TO_MY_ASSETS_LIST, - REMOVE_MY_ASSET_FROM_LIST, + PREPEND_TO_MY_ASSETS_LIST, + APPEND_TO_MY_ASSETS_LIST, + REMOVE_FROM_MY_ASSETS_LIST, SET_MY_ASSETS_LIST, SET_ACTION_ID_FOR_MY_ASSET, UPDATE_MY_ASSET_STATUS, @@ -92,9 +93,11 @@ function myAssets(state = { list: [] }, action) { myAssetAndStatus, } = action; switch (type) { - case ADD_TO_MY_ASSETS_LIST: + case PREPEND_TO_MY_ASSETS_LIST: return { ...state, list: [myAsset, ...state.list] }; - case REMOVE_MY_ASSET_FROM_LIST: { + case APPEND_TO_MY_ASSETS_LIST: + return { ...state, list: [...state.list, myAsset] }; + case REMOVE_FROM_MY_ASSETS_LIST: { const { list: myAssets } = state; const list = removeFromList(myAssets, myAsset); return { ...state, list }; diff --git a/decentraland/screens/BuyLandScreen.js b/decentraland/screens/BuyLandScreen.js index 575ba419..6e404430 100644 --- a/decentraland/screens/BuyLandScreen.js +++ b/decentraland/screens/BuyLandScreen.js @@ -3,8 +3,8 @@ import { connect } from "react-redux"; import { removeLandForSale, prependLandForSaleToList, - removeMyAssetFromList, - addToMyAssetsList, + removeFromMyAssetsList, + prependToMyAssetsList, setActionIdForMyAsset, updateMyAssetStatus, } from "../redux/actions"; @@ -50,8 +50,8 @@ export class BuyLandScreen extends React.Component { accountInfo, removeLandForSale, prependLandForSaleToList, - removeMyAssetFromList, - addToMyAssetsList, + removeFromMyAssetsList, + prependToMyAssetsList, setActionIdForMyAsset, updateMyAssetStatus, } = props; @@ -76,7 +76,7 @@ export class BuyLandScreen extends React.Component { const onError = (assetForSale, message) => { const { asset } = assetForSale; showError(message); - removeMyAssetFromList(asset); + removeFromMyAssetsList(asset); prependLandForSaleToList(assetForSale); }; @@ -86,9 +86,9 @@ export class BuyLandScreen extends React.Component { // Optimistic UI update removeLandForSale(landForSale); - addToMyAssetsList({ ...asset, status: BUYING }); + prependToMyAssetsList({ ...asset, status: BUYING }); - navigation.navigate("ListLandForSaleScreen"); + navigation.navigate("MyAssetsScreen"); const actionId = await action.getId(); setActionIdForMyAsset(assetId, actionId); @@ -152,8 +152,8 @@ BuyLandScreen.propTypes = { myAssets: PropTypes.array.isRequired, removeLandForSale: PropTypes.func.isRequired, prependLandForSaleToList: PropTypes.func.isRequired, - removeMyAssetFromList: PropTypes.func.isRequired, - addToMyAssetsList: PropTypes.func.isRequired, + removeFromMyAssetsList: PropTypes.func.isRequired, + prependToMyAssetsList: PropTypes.func.isRequired, setActionIdForMyAsset: PropTypes.func.isRequired, updateMyAssetStatus: PropTypes.func.isRequired, }; @@ -167,8 +167,8 @@ const mapStateToProps = state => { const mapDispatchToProps = { removeLandForSale, prependLandForSaleToList, - addToMyAssetsList, - removeMyAssetFromList, + prependToMyAssetsList, + removeFromMyAssetsList, setActionIdForMyAsset, updateMyAssetStatus, }; diff --git a/decentraland/screens/BuyLandScreen.test.js b/decentraland/screens/BuyLandScreen.test.js index 4c6363f2..7c111dcd 100644 --- a/decentraland/screens/BuyLandScreen.test.js +++ b/decentraland/screens/BuyLandScreen.test.js @@ -14,8 +14,8 @@ describe("BuyLandScreen", () => { }; const navigation = () => {}; const removeLandForSale = () => {}; - const addToMyAssetsList = () => {}; - const removeMyAssetFromList = () => {}; + const prependToMyAssetsList = () => {}; + const removeFromMyAssetsList = () => {}; const prependLandForSaleToList = () => {}; const setActionIdForMyAsset = () => {}; const myAssets = []; @@ -28,8 +28,8 @@ describe("BuyLandScreen", () => { myAssets={myAssets} selectedLandToBuy={estateForSale} removeLandForSale={removeLandForSale} - addToMyAssetsList={addToMyAssetsList} - removeMyAssetFromList={removeMyAssetFromList} + prependToMyAssetsList={prependToMyAssetsList} + removeFromMyAssetsList={removeFromMyAssetsList} prependLandForSaleToList={prependLandForSaleToList} setActionIdForMyAsset={setActionIdForMyAsset} /> diff --git a/decentraland/screens/MyAssetsScreen.js b/decentraland/screens/MyAssetsScreen.js index fbe86c41..00193ffe 100644 --- a/decentraland/screens/MyAssetsScreen.js +++ b/decentraland/screens/MyAssetsScreen.js @@ -1,6 +1,6 @@ import React from "react"; import { connect } from "react-redux"; -import { setMyAssetsList } from "../redux/actions"; +import { removeFromMyAssetsList, appendToMyAssetsList } from "../redux/actions"; import PropTypes from "prop-types"; import MyAssetsList from "@presentational/MyAssetsList"; import MyAssetsListItem from "@presentational/MyAssetsListItem"; @@ -12,16 +12,23 @@ const { BOUGHT } = MyAssetStatus; export class MyAssetsScreen extends React.Component { componentDidMount = async () => { - const { account, myAssets: assetsFromState, setMyAssetsList } = this.props; + const { + account, + myAssets: assetsFromState, + removeFromMyAssetsList, + appendToMyAssetsList, + } = this.props; if (account) { const { address } = account; + const boughtAssets = assetsFromState + .map(asset => (!asset.status ? { ...asset, status: BOUGHT } : asset)) + .filter(asset => asset.status === BOUGHT); + const assetsFromBlockchain = await this._getAssetsFromBlockchain(address); - const shouldUpdate = !listsAreEqual( - assetsFromBlockchain, - assetsFromState - ); + const shouldUpdate = !listsAreEqual(assetsFromBlockchain, boughtAssets); if (shouldUpdate) { - setMyAssetsList(assetsFromBlockchain); + boughtAssets.forEach(removeFromMyAssetsList); + assetsFromBlockchain.forEach(appendToMyAssetsList); } } }; @@ -92,7 +99,8 @@ const mapStateToProps = state => { }; const mapDispatchToProps = { - setMyAssetsList, + removeFromMyAssetsList, + appendToMyAssetsList, }; export default connect( From bb12f7b3a4fd12756f8b59634ac3c86b94e6a750 Mon Sep 17 00:00:00 2001 From: cmarcelo Date: Mon, 22 Apr 2019 20:28:51 +0100 Subject: [PATCH 03/18] Adjusting navigation --- decentraland/helpers/index.js | 11 +++++++++-- decentraland/redux/actions.js | 8 ++++---- decentraland/redux/reducers.js | 20 +++++++++++--------- decentraland/screens/BuyLandScreen.js | 3 +++ decentraland/screens/MyAssetsScreen.js | 6 ++++-- 5 files changed, 31 insertions(+), 17 deletions(-) diff --git a/decentraland/helpers/index.js b/decentraland/helpers/index.js index 5c7c1d6b..3ffecc7c 100644 --- a/decentraland/helpers/index.js +++ b/decentraland/helpers/index.js @@ -160,8 +160,14 @@ export const formatNumber = number => { return formattedNumber; }; -export const removeFromList = (list, toRemove) => - list.filter(e => e !== toRemove); +export const toListIfNot = itemOrList => + Array.isArray(itemOrList) ? itemOrList : [itemOrList]; + +export const removeFromList = (list, toRemove) => { + const elementsToRemove = toListIfNot(toRemove); + const idsToRemove = elementsToRemove.map(e => e.id); + return list.filter(e => !idsToRemove.includes(e.id)); +}; export const listsAreEqual = (first, second) => { if (first.length !== second.length) return false; @@ -263,4 +269,5 @@ export default { buildBlockchainUrlFromActionId, restoreCreationStateOfAccountFromBlockchain, updateListItem, + toListIfNot, }; diff --git a/decentraland/redux/actions.js b/decentraland/redux/actions.js index d43c02d6..93c9b328 100644 --- a/decentraland/redux/actions.js +++ b/decentraland/redux/actions.js @@ -59,12 +59,12 @@ export function prependToMyAssetsList(myAsset) { return { type: PREPEND_TO_MY_ASSETS_LIST, myAsset }; } -export function appendToMyAssetsList(myAsset) { - return { type: APPEND_TO_MY_ASSETS_LIST, myAsset }; +export function appendToMyAssetsList(itemOrList) { + return { type: APPEND_TO_MY_ASSETS_LIST, itemOrList }; } -export function removeFromMyAssetsList(myAsset) { - return { type: REMOVE_FROM_MY_ASSETS_LIST, myAsset }; +export function removeFromMyAssetsList(itemOrList) { + return { type: REMOVE_FROM_MY_ASSETS_LIST, itemOrList }; } export function setMyAssetsList(myAssets) { diff --git a/decentraland/redux/reducers.js b/decentraland/redux/reducers.js index f4d9bdab..de625d70 100644 --- a/decentraland/redux/reducers.js +++ b/decentraland/redux/reducers.js @@ -16,7 +16,7 @@ import { SET_ACTION_ID_FOR_MY_ASSET, UPDATE_MY_ASSET_STATUS, } from "./actions"; -import { removeFromList, updateListItem } from "@helpers"; +import { removeFromList, updateListItem, toListIfNot } from "@helpers"; import AccountCreationStatus from "@constants/AccountCreationStatus"; const { NOT_STARTED } = AccountCreationStatus; @@ -91,15 +91,20 @@ function myAssets(state = { list: [] }, action) { myAssets, myAssetAndActionIds, myAssetAndStatus, + itemOrList, } = action; + switch (type) { case PREPEND_TO_MY_ASSETS_LIST: return { ...state, list: [myAsset, ...state.list] }; - case APPEND_TO_MY_ASSETS_LIST: - return { ...state, list: [...state.list, myAsset] }; + case APPEND_TO_MY_ASSETS_LIST: { + const toAppend = toListIfNot(itemOrList); + return { ...state, list: [...state.list, ...toAppend] }; + } case REMOVE_FROM_MY_ASSETS_LIST: { const { list: myAssets } = state; - const list = removeFromList(myAssets, myAsset); + const toRemove = itemOrList; + const list = removeFromList(myAssets, toRemove); return { ...state, list }; } case SET_MY_ASSETS_LIST: { @@ -109,11 +114,8 @@ function myAssets(state = { list: [] }, action) { case SET_ACTION_ID_FOR_MY_ASSET: { const { myAssetId: toUpdateId, actionId } = myAssetAndActionIds; const { list: myAssets } = state; - // TODO: Use updateListItem - const list = myAssets.map(asset => { - if (asset.id === toUpdateId) return { ...asset, actionId }; - else return asset; - }); + const entriesToUpdate = { actionId }; + const list = updateListItem(myAssets, toUpdateId, entriesToUpdate); return { ...state, list }; } case UPDATE_MY_ASSET_STATUS: { diff --git a/decentraland/screens/BuyLandScreen.js b/decentraland/screens/BuyLandScreen.js index 6e404430..16f86afd 100644 --- a/decentraland/screens/BuyLandScreen.js +++ b/decentraland/screens/BuyLandScreen.js @@ -1,5 +1,6 @@ import React from "react"; import { connect } from "react-redux"; +import { StackActions, NavigationActions } from "react-navigation"; import { removeLandForSale, prependLandForSaleToList, @@ -88,6 +89,8 @@ export class BuyLandScreen extends React.Component { removeLandForSale(landForSale); prependToMyAssetsList({ ...asset, status: BUYING }); + // Back to top of current Stack before navigate + navigation.dispatch(StackActions.popToTop()); navigation.navigate("MyAssetsScreen"); const actionId = await action.getId(); diff --git a/decentraland/screens/MyAssetsScreen.js b/decentraland/screens/MyAssetsScreen.js index 00193ffe..686c3e95 100644 --- a/decentraland/screens/MyAssetsScreen.js +++ b/decentraland/screens/MyAssetsScreen.js @@ -20,6 +20,8 @@ export class MyAssetsScreen extends React.Component { } = this.props; if (account) { const { address } = account; + + // Note: If an stored asset hasn't a status, assuming 'bought'. const boughtAssets = assetsFromState .map(asset => (!asset.status ? { ...asset, status: BOUGHT } : asset)) .filter(asset => asset.status === BOUGHT); @@ -27,8 +29,8 @@ export class MyAssetsScreen extends React.Component { const assetsFromBlockchain = await this._getAssetsFromBlockchain(address); const shouldUpdate = !listsAreEqual(assetsFromBlockchain, boughtAssets); if (shouldUpdate) { - boughtAssets.forEach(removeFromMyAssetsList); - assetsFromBlockchain.forEach(appendToMyAssetsList); + removeFromMyAssetsList(boughtAssets); + appendToMyAssetsList(assetsFromBlockchain); } } }; From cff706bf7fcb79e23f7d6e38a66f1935da753461 Mon Sep 17 00:00:00 2001 From: cmarcelo Date: Mon, 22 Apr 2019 20:29:55 +0100 Subject: [PATCH 04/18] Updating test --- decentraland/screens/BuyLandScreen.test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/decentraland/screens/BuyLandScreen.test.js b/decentraland/screens/BuyLandScreen.test.js index 7c111dcd..b8876419 100644 --- a/decentraland/screens/BuyLandScreen.test.js +++ b/decentraland/screens/BuyLandScreen.test.js @@ -18,6 +18,7 @@ describe("BuyLandScreen", () => { const removeFromMyAssetsList = () => {}; const prependLandForSaleToList = () => {}; const setActionIdForMyAsset = () => {}; + const updateMyAssetStatus = () => {}; const myAssets = []; expect( @@ -32,6 +33,7 @@ describe("BuyLandScreen", () => { removeFromMyAssetsList={removeFromMyAssetsList} prependLandForSaleToList={prependLandForSaleToList} setActionIdForMyAsset={setActionIdForMyAsset} + updateMyAssetStatus={updateMyAssetStatus} /> ) ).toMatchSnapshot(); From 766f5a4e8386e76ac4b7ec1e4a701e8bd67adfad Mon Sep 17 00:00:00 2001 From: cmarcelo Date: Mon, 22 Apr 2019 20:32:17 +0100 Subject: [PATCH 05/18] Fixing lint --- decentraland/screens/BuyLandScreen.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/decentraland/screens/BuyLandScreen.js b/decentraland/screens/BuyLandScreen.js index 16f86afd..018e804b 100644 --- a/decentraland/screens/BuyLandScreen.js +++ b/decentraland/screens/BuyLandScreen.js @@ -1,6 +1,6 @@ import React from "react"; import { connect } from "react-redux"; -import { StackActions, NavigationActions } from "react-navigation"; +import { StackActions } from "react-navigation"; import { removeLandForSale, prependLandForSaleToList, From aaa0aa03342db13f5ab86920520a1f759bbe990e Mon Sep 17 00:00:00 2001 From: Paul Cowgill Date: Mon, 22 Apr 2019 15:17:12 -0500 Subject: [PATCH 06/18] Grammar --- decentraland/helpers/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/decentraland/helpers/index.js b/decentraland/helpers/index.js index 3ffecc7c..8b84bfd1 100644 --- a/decentraland/helpers/index.js +++ b/decentraland/helpers/index.js @@ -177,7 +177,7 @@ export const listsAreEqual = (first, second) => { ); }; -// Update any list having id as key field +// Update item from any list of objects having id as key field export const updateListItem = (list, toUpdateId, entriesToUpdate) => { return list.map(item => { return item.id === toUpdateId ? { ...item, ...entriesToUpdate } : item; From 85f1ad1118cb7b751e191d1a47dae60abe24e22b Mon Sep 17 00:00:00 2001 From: Paul Cowgill Date: Mon, 22 Apr 2019 15:17:21 -0500 Subject: [PATCH 07/18] Grammar --- decentraland/screens/MyAssetsScreen.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/decentraland/screens/MyAssetsScreen.js b/decentraland/screens/MyAssetsScreen.js index 686c3e95..502b0ceb 100644 --- a/decentraland/screens/MyAssetsScreen.js +++ b/decentraland/screens/MyAssetsScreen.js @@ -21,7 +21,7 @@ export class MyAssetsScreen extends React.Component { if (account) { const { address } = account; - // Note: If an stored asset hasn't a status, assuming 'bought'. + // Note: If a stored asset doesn't have a status, assuming 'bought'. const boughtAssets = assetsFromState .map(asset => (!asset.status ? { ...asset, status: BOUGHT } : asset)) .filter(asset => asset.status === BOUGHT); From 718241da5268e78c51d8d13b1b4585454a8eb5c7 Mon Sep 17 00:00:00 2001 From: cmarcelo Date: Mon, 22 Apr 2019 21:48:46 +0100 Subject: [PATCH 08/18] Refactoring myAssets reducer --- decentraland/redux/reducers.js | 92 ++++++++++++++++++++-------------- 1 file changed, 55 insertions(+), 37 deletions(-) diff --git a/decentraland/redux/reducers.js b/decentraland/redux/reducers.js index de625d70..df8ac7e8 100644 --- a/decentraland/redux/reducers.js +++ b/decentraland/redux/reducers.js @@ -85,51 +85,69 @@ function assetsForSale(state = { list: [], loadingInProgress: true }, action) { } function myAssets(state = { list: [] }, action) { - const { - type, - myAsset, - myAssets, - myAssetAndActionIds, - myAssetAndStatus, - itemOrList, - } = action; + const { type } = action; switch (type) { case PREPEND_TO_MY_ASSETS_LIST: - return { ...state, list: [myAsset, ...state.list] }; - case APPEND_TO_MY_ASSETS_LIST: { - const toAppend = toListIfNot(itemOrList); - return { ...state, list: [...state.list, ...toAppend] }; - } - case REMOVE_FROM_MY_ASSETS_LIST: { - const { list: myAssets } = state; - const toRemove = itemOrList; - const list = removeFromList(myAssets, toRemove); - return { ...state, list }; - } - case SET_MY_ASSETS_LIST: { - const list = myAssets === null ? [] : myAssets; - return { ...state, list }; - } - case SET_ACTION_ID_FOR_MY_ASSET: { - const { myAssetId: toUpdateId, actionId } = myAssetAndActionIds; - const { list: myAssets } = state; - const entriesToUpdate = { actionId }; - const list = updateListItem(myAssets, toUpdateId, entriesToUpdate); - return { ...state, list }; - } - case UPDATE_MY_ASSET_STATUS: { - const { myAssetId: toUpdateId, status } = myAssetAndStatus; - const { list: myAssets } = state; - const entriesToUpdate = { status }; - const list = updateListItem(myAssets, toUpdateId, entriesToUpdate); - return { ...state, list }; - } + return prependToMyAssetsList(state, action); + case APPEND_TO_MY_ASSETS_LIST: + return appendToMyAssetsList(state, action); + case REMOVE_FROM_MY_ASSETS_LIST: + return removeFromMyAssetsList(state, action); + case SET_MY_ASSETS_LIST: + return setMyAssetsList(state, action); + case SET_ACTION_ID_FOR_MY_ASSET: + return setActionIdForMyAsset(state, action); + case UPDATE_MY_ASSET_STATUS: + return updateMyAssetStatus(state, action); default: return state; } } +const prependToMyAssetsList = (state, action) => { + const { myAsset } = action; + return { ...state, list: [myAsset, ...state.list] }; +}; + +const appendToMyAssetsList = (state, action) => { + const { itemOrList } = action; + const toAppend = toListIfNot(itemOrList); + return { ...state, list: [...state.list, ...toAppend] }; +}; + +const removeFromMyAssetsList = (state, action) => { + const { itemOrList } = action; + const { list: myAssets } = state; + const toRemove = itemOrList; + const list = removeFromList(myAssets, toRemove); + return { ...state, list }; +}; + +const setMyAssetsList = (state, action) => { + const { myAssets } = action; + const list = myAssets === null ? [] : myAssets; + return { ...state, list }; +}; + +const setActionIdForMyAsset = (state, action) => { + const { myAssetAndActionIds } = action; + const { myAssetId: toUpdateId, actionId } = myAssetAndActionIds; + const { list: myAssets } = state; + const entriesToUpdate = { actionId }; + const list = updateListItem(myAssets, toUpdateId, entriesToUpdate); + return { ...state, list }; +}; + +const updateMyAssetStatus = (state, action) => { + const { myAssetAndStatus } = action; + const { myAssetId: toUpdateId, status } = myAssetAndStatus; + const { list: myAssets } = state; + const entriesToUpdate = { status }; + const list = updateListItem(myAssets, toUpdateId, entriesToUpdate); + return { ...state, list }; +}; + const decentralandApp = combineReducers({ accountInfo, selectedLandToBuy, From f29344be14087ccfc54ccdb1d69e592ed112823f Mon Sep 17 00:00:00 2001 From: cmarcelo Date: Tue, 23 Apr 2019 14:42:09 +0100 Subject: [PATCH 09/18] Refactoring reducers --- decentraland/redux/reducers.js | 178 +++++++++++++++++++-------------- 1 file changed, 104 insertions(+), 74 deletions(-) diff --git a/decentraland/redux/reducers.js b/decentraland/redux/reducers.js index df8ac7e8..39343e05 100644 --- a/decentraland/redux/reducers.js +++ b/decentraland/redux/reducers.js @@ -21,90 +21,105 @@ import { removeFromList, updateListItem, toListIfNot } from "@helpers"; import AccountCreationStatus from "@constants/AccountCreationStatus"; const { NOT_STARTED } = AccountCreationStatus; -function accountInfo( - state = { +// Reducing boilerplate from reducers +// Refs: https://redux.js.org/recipes/structuring-reducers/refactoring-reducer-example#reducing-boilerplate +function createReducer(initialState, handlers) { + return function reducer(state = initialState, action) { + if (handlers.hasOwnProperty(action.type)) { + return handlers[action.type](state, action); + } else { + return state; + } + }; +} + +// +// accountInfo reducer +// +const setAccount = (state, action) => { + const { account } = action; + return { ...state, account }; +}; +const setAccountCreationStatus = (state, action) => { + const { creationStatus } = action; + return { ...state, creationStatus }; +}; +const updateActionIdForAccountCreationStatus = (state, action) => { + const { creationStatusAction } = action; + const { status, actionId } = creationStatusAction; + let { creationActions } = state; + creationActions = { ...creationActions, [status]: actionId }; + return { ...state, creationActions }; +}; +const setAccountCreationActions = (state, action) => { + const { creationActions } = action; + return { ...state, creationActions }; +}; + +const accountInfo = createReducer( + { account: null, creationStatus: NOT_STARTED, creationActions: {}, }, - action -) { - const { - type, - account, - creationStatus, - creationStatusAction, - creationActions, - } = action; - switch (type) { - case SET_ACCOUNT: - return { ...state, account }; - case SET_ACCOUNT_CREATION_STATUS: - return { ...state, creationStatus }; - case UPDATE_ACTION_ID_FOR_ACCOUNT_CREATION_STATUS: { - const { status, actionId } = creationStatusAction; - let { creationActions } = state; - creationActions = { ...creationActions, [status]: actionId }; - return { ...state, creationActions }; - } - case SET_ACCOUNT_CREATION_ACTIONS: { - return { ...state, creationActions }; - } - default: - return state; + { + [SET_ACCOUNT]: setAccount, + [SET_ACCOUNT_CREATION_STATUS]: setAccountCreationStatus, + [UPDATE_ACTION_ID_FOR_ACCOUNT_CREATION_STATUS]: updateActionIdForAccountCreationStatus, + [SET_ACCOUNT_CREATION_ACTIONS]: setAccountCreationActions, } -} +); -function selectedLandToBuy(state = null, action) { - const { type, landForSale } = action; - switch (type) { - case SELECT_LAND_TO_BUY: - return landForSale; - default: - return state; - } -} +// +// selectedLandToBuy reducer +// +const selectLandToBuy = (state, action) => { + const { landForSale } = action; + return landForSale; +}; -function assetsForSale(state = { list: [], loadingInProgress: true }, action) { - const { type, landForSale, loadingInProgress } = action; - switch (type) { - case PREPEND_LAND_FOR_SALE_TO_LIST: - return { ...state, list: [landForSale, ...state.list] }; - case APPEND_LAND_FOR_SALE_TO_LIST: - return { ...state, list: [...state.list, landForSale] }; - case REMOVE_LAND_FOR_SALE: { - let { list: assetsForSale } = state; - const list = removeFromList(assetsForSale, landForSale); - return { ...state, list }; - } - case SET_LOADING_ASSETS_FOR_SALE_IN_PROGRESS: - return { ...state, loadingInProgress }; - default: - return state; - } -} +const selectedLandToBuy = createReducer(null, { + [SELECT_LAND_TO_BUY]: selectLandToBuy, +}); -function myAssets(state = { list: [] }, action) { - const { type } = action; - - switch (type) { - case PREPEND_TO_MY_ASSETS_LIST: - return prependToMyAssetsList(state, action); - case APPEND_TO_MY_ASSETS_LIST: - return appendToMyAssetsList(state, action); - case REMOVE_FROM_MY_ASSETS_LIST: - return removeFromMyAssetsList(state, action); - case SET_MY_ASSETS_LIST: - return setMyAssetsList(state, action); - case SET_ACTION_ID_FOR_MY_ASSET: - return setActionIdForMyAsset(state, action); - case UPDATE_MY_ASSET_STATUS: - return updateMyAssetStatus(state, action); - default: - return state; +// +// assetsForSale reducer +// +const prependLandForSaleToList = (state, action) => { + const { landForSale } = action; + return { ...state, list: [landForSale, ...state.list] }; +}; + +const appendLandForSaleToList = (state, action) => { + const { landForSale } = action; + return { ...state, list: [...state.list, landForSale] }; +}; + +const removeLandForSale = (state, action) => { + const { landForSale } = action; + let { list: assetsForSale } = state; + const list = removeFromList(assetsForSale, landForSale); + return { ...state, list }; +}; + +const setLoadingAssetsForSaleInProgress = (state, action) => { + const { loadingInProgress } = action; + return { ...state, loadingInProgress }; +}; + +const assetsForSale = createReducer( + { list: [], loadingInProgress: true }, + { + [PREPEND_LAND_FOR_SALE_TO_LIST]: prependLandForSaleToList, + [APPEND_LAND_FOR_SALE_TO_LIST]: appendLandForSaleToList, + [REMOVE_LAND_FOR_SALE]: removeLandForSale, + [SET_LOADING_ASSETS_FOR_SALE_IN_PROGRESS]: setLoadingAssetsForSaleInProgress, } -} +); +// +// myAssets reducer +// const prependToMyAssetsList = (state, action) => { const { myAsset } = action; return { ...state, list: [myAsset, ...state.list] }; @@ -148,6 +163,21 @@ const updateMyAssetStatus = (state, action) => { return { ...state, list }; }; +const myAssets = createReducer( + { list: [] }, + { + [PREPEND_TO_MY_ASSETS_LIST]: prependToMyAssetsList, + [APPEND_TO_MY_ASSETS_LIST]: appendToMyAssetsList, + [REMOVE_FROM_MY_ASSETS_LIST]: removeFromMyAssetsList, + [SET_MY_ASSETS_LIST]: setMyAssetsList, + [SET_ACTION_ID_FOR_MY_ASSET]: setActionIdForMyAsset, + [UPDATE_MY_ASSET_STATUS]: updateMyAssetStatus, + } +); + +// +// All all reducers +// const decentralandApp = combineReducers({ accountInfo, selectedLandToBuy, From b27e8ba8b99c90f916e783b56b7ac766fbb1dc9f Mon Sep 17 00:00:00 2001 From: cmarcelo Date: Tue, 23 Apr 2019 16:10:01 +0100 Subject: [PATCH 10/18] Review request --- decentraland/redux/reducers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/decentraland/redux/reducers.js b/decentraland/redux/reducers.js index 39343e05..46dbb2fd 100644 --- a/decentraland/redux/reducers.js +++ b/decentraland/redux/reducers.js @@ -176,7 +176,7 @@ const myAssets = createReducer( ); // -// All all reducers +// All reducers // const decentralandApp = combineReducers({ accountInfo, From 4fece53ac5a26fdf38c56531e142ebf868bda88c Mon Sep 17 00:00:00 2001 From: cmarcelo Date: Tue, 23 Apr 2019 17:10:35 +0100 Subject: [PATCH 11/18] Review request --- decentraland/helpers/index.js | 2 ++ decentraland/screens/MyAssetsScreen.js | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/decentraland/helpers/index.js b/decentraland/helpers/index.js index 8b84bfd1..13fd5542 100644 --- a/decentraland/helpers/index.js +++ b/decentraland/helpers/index.js @@ -133,6 +133,7 @@ export const showFatalError = msg => console.error(msg); export const showError = msg => showToast(`ERROR: ${msg}`); export const showWarn = msg => showToast(`WARN: ${msg}`); export const showInfo = msg => showToast(`${msg}`); +export const logWarn = msg => console.warn(msg); export const checkBlockchain = async () => { loadConfig(); @@ -257,6 +258,7 @@ export default { showError, showWarn, showInfo, + logWarn, fundAccountWithEthers, fundAccountWithMana, getContracts, diff --git a/decentraland/screens/MyAssetsScreen.js b/decentraland/screens/MyAssetsScreen.js index 502b0ceb..ae0326b9 100644 --- a/decentraland/screens/MyAssetsScreen.js +++ b/decentraland/screens/MyAssetsScreen.js @@ -4,7 +4,7 @@ import { removeFromMyAssetsList, appendToMyAssetsList } from "../redux/actions"; import PropTypes from "prop-types"; import MyAssetsList from "@presentational/MyAssetsList"; import MyAssetsListItem from "@presentational/MyAssetsListItem"; -import { listsAreEqual, getContracts } from "@helpers"; +import { listsAreEqual, getContracts, logWarn } from "@helpers"; import { generateAssetFromId } from "@helpers/decentraland"; import DecentralandUtils from "tasit-sdk/dist/helpers/DecentralandUtils"; import MyAssetStatus from "@constants/MyAssetStatus"; @@ -29,6 +29,10 @@ export class MyAssetsScreen extends React.Component { const assetsFromBlockchain = await this._getAssetsFromBlockchain(address); const shouldUpdate = !listsAreEqual(assetsFromBlockchain, boughtAssets); if (shouldUpdate) { + // TODO: Add some UI indication that something unexpected happened + logWarn( + `Accounts' assets from blockchain aren't the same as the stored on the app.` + ); removeFromMyAssetsList(boughtAssets); appendToMyAssetsList(assetsFromBlockchain); } From e56a93a776c2ded329617b60e1ba94bba2e80a5f Mon Sep 17 00:00:00 2001 From: cmarcelo Date: Tue, 23 Apr 2019 17:18:23 +0100 Subject: [PATCH 12/18] Renaming reducer selectedLandToBuy to landToBuy --- decentraland/redux/reducers.js | 6 +++--- decentraland/screens/BuyLandScreen.js | 8 ++++---- decentraland/screens/BuyLandScreen.test.js | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/decentraland/redux/reducers.js b/decentraland/redux/reducers.js index 46dbb2fd..221b0813 100644 --- a/decentraland/redux/reducers.js +++ b/decentraland/redux/reducers.js @@ -71,14 +71,14 @@ const accountInfo = createReducer( ); // -// selectedLandToBuy reducer +// landToBuy reducer // const selectLandToBuy = (state, action) => { const { landForSale } = action; return landForSale; }; -const selectedLandToBuy = createReducer(null, { +const landToBuy = createReducer(null, { [SELECT_LAND_TO_BUY]: selectLandToBuy, }); @@ -180,7 +180,7 @@ const myAssets = createReducer( // const decentralandApp = combineReducers({ accountInfo, - selectedLandToBuy, + landToBuy, assetsForSale, myAssets, }); diff --git a/decentraland/screens/BuyLandScreen.js b/decentraland/screens/BuyLandScreen.js index 018e804b..8aa613e6 100644 --- a/decentraland/screens/BuyLandScreen.js +++ b/decentraland/screens/BuyLandScreen.js @@ -135,7 +135,7 @@ export class BuyLandScreen extends React.Component { }; render() { - const { selectedLandToBuy: landForSale, accountInfo } = this.props; + const { landToBuy: landForSale, accountInfo } = this.props; const { creationStatus, creationActions } = accountInfo; return ( @@ -151,7 +151,7 @@ export class BuyLandScreen extends React.Component { BuyLandScreen.propTypes = { accountInfo: PropTypes.object, - selectedLandToBuy: PropTypes.object.isRequired, + landToBuy: PropTypes.object.isRequired, myAssets: PropTypes.array.isRequired, removeLandForSale: PropTypes.func.isRequired, prependLandForSaleToList: PropTypes.func.isRequired, @@ -162,9 +162,9 @@ BuyLandScreen.propTypes = { }; const mapStateToProps = state => { - const { accountInfo, selectedLandToBuy, myAssets } = state; + const { accountInfo, landToBuy, myAssets } = state; const { list: myAssetsList } = myAssets; - return { accountInfo, selectedLandToBuy, myAssets: myAssetsList }; + return { accountInfo, landToBuy, myAssets: myAssetsList }; }; const mapDispatchToProps = { diff --git a/decentraland/screens/BuyLandScreen.test.js b/decentraland/screens/BuyLandScreen.test.js index b8876419..7fa007f3 100644 --- a/decentraland/screens/BuyLandScreen.test.js +++ b/decentraland/screens/BuyLandScreen.test.js @@ -27,7 +27,7 @@ describe("BuyLandScreen", () => { navigation={navigation} accountInfo={accountInfo} myAssets={myAssets} - selectedLandToBuy={estateForSale} + landToBuy={estateForSale} removeLandForSale={removeLandForSale} prependToMyAssetsList={prependToMyAssetsList} removeFromMyAssetsList={removeFromMyAssetsList} From be85215bf97edb9c9ec4a201f2183f829b9f6a28 Mon Sep 17 00:00:00 2001 From: Paul Cowgill Date: Tue, 23 Apr 2019 11:56:41 -0500 Subject: [PATCH 13/18] Grammar --- decentraland/screens/MyAssetsScreen.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/decentraland/screens/MyAssetsScreen.js b/decentraland/screens/MyAssetsScreen.js index ae0326b9..dd147214 100644 --- a/decentraland/screens/MyAssetsScreen.js +++ b/decentraland/screens/MyAssetsScreen.js @@ -31,7 +31,7 @@ export class MyAssetsScreen extends React.Component { if (shouldUpdate) { // TODO: Add some UI indication that something unexpected happened logWarn( - `Accounts' assets from blockchain aren't the same as the stored on the app.` + `Account's assets from blockchain aren't the same as the ones stored on the app.` ); removeFromMyAssetsList(boughtAssets); appendToMyAssetsList(assetsFromBlockchain); From 341feec07956960b5384e973a91515c74da1fc36 Mon Sep 17 00:00:00 2001 From: cmarcelo Date: Tue, 23 Apr 2019 18:48:50 +0100 Subject: [PATCH 14/18] Log assets inconsistency --- decentraland/screens/MyAssetsScreen.js | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/decentraland/screens/MyAssetsScreen.js b/decentraland/screens/MyAssetsScreen.js index ae0326b9..3f046e5a 100644 --- a/decentraland/screens/MyAssetsScreen.js +++ b/decentraland/screens/MyAssetsScreen.js @@ -29,16 +29,32 @@ export class MyAssetsScreen extends React.Component { const assetsFromBlockchain = await this._getAssetsFromBlockchain(address); const shouldUpdate = !listsAreEqual(assetsFromBlockchain, boughtAssets); if (shouldUpdate) { - // TODO: Add some UI indication that something unexpected happened - logWarn( - `Accounts' assets from blockchain aren't the same as the stored on the app.` - ); + this._logAssetsInconsistency(assetsFromBlockchain, boughtAssets); removeFromMyAssetsList(boughtAssets); appendToMyAssetsList(assetsFromBlockchain); } } }; + _logAssetsInconsistency = (fromBlockchain, fromState) => { + const fromBlockchainIds = fromBlockchain.map(asset => asset.id); + const fromStateIds = fromState.map(asset => asset.id); + + const addedIds = fromBlockchainIds.filter(id => !fromStateIds.includes(id)); + const removedIds = fromStateIds.filter( + id => !fromBlockchainIds.includes(id) + ); + + // TODO: Add some UI indication that something unexpected happened + logWarn(`Assets from blockchain aren't the same as the stored on the app.`); + + if (addedIds.length > 0) + logWarn(`Some assets added to MyLandScreen. IDs: [${addedIds}]`); + + if (removedIds.length > 0) + logWarn(`Some assets removed from MyLandScreen. IDs: [${removedIds}]`); + }; + _getAssetsFromBlockchain = async address => { const listOfPromises = await this._getAssetsOf(address); const fromBlockchain = await Promise.all([...listOfPromises]); From 78e319c61dc664212f517f7a975fb57ee5827ccf Mon Sep 17 00:00:00 2001 From: cmarcelo Date: Tue, 23 Apr 2019 18:51:49 +0100 Subject: [PATCH 15/18] log assets inconsistency details --- decentraland/screens/MyAssetsScreen.js | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/decentraland/screens/MyAssetsScreen.js b/decentraland/screens/MyAssetsScreen.js index 6cb6f496..5ae8d8d6 100644 --- a/decentraland/screens/MyAssetsScreen.js +++ b/decentraland/screens/MyAssetsScreen.js @@ -29,14 +29,8 @@ export class MyAssetsScreen extends React.Component { const assetsFromBlockchain = await this._getAssetsFromBlockchain(address); const shouldUpdate = !listsAreEqual(assetsFromBlockchain, boughtAssets); if (shouldUpdate) { -<<<<<<< HEAD - this._logAssetsInconsistency(assetsFromBlockchain, boughtAssets); -======= // TODO: Add some UI indication that something unexpected happened - logWarn( - `Account's assets from blockchain aren't the same as the ones stored on the app.` - ); ->>>>>>> be85215bf97edb9c9ec4a201f2183f829b9f6a28 + this._logAssetsInconsistency(assetsFromBlockchain, boughtAssets); removeFromMyAssetsList(boughtAssets); appendToMyAssetsList(assetsFromBlockchain); } @@ -52,8 +46,9 @@ export class MyAssetsScreen extends React.Component { id => !fromBlockchainIds.includes(id) ); - // TODO: Add some UI indication that something unexpected happened - logWarn(`Assets from blockchain aren't the same as the stored on the app.`); + logWarn( + `Account's assets from blockchain aren't the same as the ones stored on the app.` + ); if (addedIds.length > 0) logWarn(`Some assets added to MyLandScreen. IDs: [${addedIds}]`); From b196afa57df1acd2397beee19f466de08230ffee Mon Sep 17 00:00:00 2001 From: cmarcelo Date: Tue, 23 Apr 2019 22:20:27 +0100 Subject: [PATCH 16/18] userActions reducer --- decentraland/App.js | 18 +++++ .../components/presentational/MyAsset.js | 15 ++-- .../components/presentational/MyAsset.test.js | 16 +++- .../components/presentational/MyAssetsList.js | 18 +++-- .../presentational/MyAssetsList.test.js | 13 ++-- .../presentational/MyAssetsListItem.js | 5 +- .../presentational/MyAssetsListItem.test.js | 8 +- .../__snapshots__/MyAsset.test.js.snap | 11 ++- .../__snapshots__/MyAssetsList.test.js.snap | 15 +++- .../MyAssetsListItem.test.js.snap | 7 ++ decentraland/constants/AssetTypes.js | 4 +- decentraland/constants/MyAssetStatus.js | 7 -- decentraland/constants/UserActionStatus.js | 17 +++++ decentraland/helpers/storage.js | 15 ++++ decentraland/helpers/testHelpers.js | 21 +++++- decentraland/redux/actions.js | 18 ++--- decentraland/redux/middlewares.js | 15 ++-- decentraland/redux/reducers.js | 47 ++++++------ decentraland/screens/BuyLandScreen.js | 31 ++++---- decentraland/screens/BuyLandScreen.test.js | 8 +- decentraland/screens/MyAssetsScreen.js | 75 ++++++++++++++----- decentraland/screens/MyAssetsScreen.test.js | 15 ++-- .../__snapshots__/MyAssetsScreen.test.js.snap | 14 +++- 23 files changed, 284 insertions(+), 129 deletions(-) delete mode 100644 decentraland/constants/MyAssetStatus.js create mode 100644 decentraland/constants/UserActionStatus.js diff --git a/decentraland/App.js b/decentraland/App.js index 300c56b1..c1719400 100644 --- a/decentraland/App.js +++ b/decentraland/App.js @@ -11,6 +11,7 @@ import { setMyAssetsList, setAccountCreationStatus, setAccountCreationActions, + addUserAction, } from "./redux/actions"; import applyMiddleware from "./redux/middlewares"; import { Action } from "tasit-sdk"; @@ -28,9 +29,11 @@ import { retrieveAccountCreationActions, storeIsFirstUse, retrieveIsFirstUse, + retrieveUserActions, } from "@helpers/storage"; import { Ionicons } from "@expo/vector-icons"; import { Root } from "native-base"; +import { SUCCESSFUL } from "@constants/UserActionStatus"; const store = createStore(decentralandApp, applyMiddleware); @@ -99,6 +102,21 @@ Is the config file correct?`; async _loadMyAssets() { const myAssets = await retrieveMyAssets(); + const userActions = await retrieveUserActions(); + + // Handling with actions stored before + // the introduction of userAction state (<= 0.0.17) + myAssets.forEach(asset => { + const { id: assetId, actionId } = asset; + let userAction = userActions.find(action => action.assetId === assetId); + + if (!userAction && !!actionId) { + userAction = { actionId, assetId, status: SUCCESSFUL }; + userActions.push(userAction); + } + }); + + if (userActions) store.dispatch(addUserAction(userActions)); if (myAssets) store.dispatch(setMyAssetsList(myAssets)); } diff --git a/decentraland/components/presentational/MyAsset.js b/decentraland/components/presentational/MyAsset.js index c3322180..4187b6e6 100644 --- a/decentraland/components/presentational/MyAsset.js +++ b/decentraland/components/presentational/MyAsset.js @@ -4,13 +4,11 @@ import PropTypes from "prop-types"; import { responsiveWidth } from "react-native-responsive-dimensions"; import Estate from "./Estate"; import Parcel from "./Parcel"; -import AssetTypes from "@constants/AssetTypes"; +import { ESTATE, PARCEL } from "@constants/AssetTypes"; import LinkToBlockchain from "./LinkToBlockchain"; import AssetName from "./AssetName"; -const { ESTATE, PARCEL } = AssetTypes; - -export function MyAsset({ asset }) { +export function MyAsset({ asset, userAction }) { const { type } = asset; return ( @@ -23,17 +21,19 @@ export function MyAsset({ asset }) { return ; } })()} - + ); } MyAsset.propTypes = { asset: PropTypes.object.isRequired, + userAction: PropTypes.object.isRequired, }; -export function MyAssetInfo({ asset }) { - const { actionId, name } = asset; +export function MyAssetInfo({ asset, userAction }) { + const { name } = asset; + const { actionId } = userAction; return ( @@ -49,6 +49,7 @@ export function MyAssetInfo({ asset }) { MyAssetInfo.propTypes = { asset: PropTypes.object.isRequired, + userAction: PropTypes.object.isRequired, }; const styles = StyleSheet.create({ diff --git a/decentraland/components/presentational/MyAsset.test.js b/decentraland/components/presentational/MyAsset.test.js index e24bf86e..14203471 100644 --- a/decentraland/components/presentational/MyAsset.test.js +++ b/decentraland/components/presentational/MyAsset.test.js @@ -1,16 +1,26 @@ import React from "react"; import { shallow } from "enzyme"; import { MyAsset, MyAssetInfo } from "./MyAsset"; -import { parcel } from "@helpers/testHelpers"; +import { parcel, parcelUserAction } from "@helpers/testHelpers"; describe("MyAsset", () => { it("renders the component", async () => { - expect(shallow()).toMatchSnapshot(); + const asset = parcel; + const userAction = parcelUserAction; + + expect( + shallow() + ).toMatchSnapshot(); }); describe("MyAssetInfo", () => { it("renders the component", async () => { - expect(shallow()).toMatchSnapshot(); + const asset = parcel; + const userAction = parcelUserAction; + + expect( + shallow() + ).toMatchSnapshot(); }); }); }); diff --git a/decentraland/components/presentational/MyAssetsList.js b/decentraland/components/presentational/MyAssetsList.js index 2afdd5f8..947e9ce9 100644 --- a/decentraland/components/presentational/MyAssetsList.js +++ b/decentraland/components/presentational/MyAssetsList.js @@ -11,19 +11,26 @@ import LargeText from "@presentational/LargeText"; // https://medium.com/groww-engineering/stateless-component-vs-pure-component-d2af88a1200b export default class MyAssetsList extends React.PureComponent { render() { - const { myAssetsList, renderItem } = this.props; - const { length: listAmount } = myAssetsList; + const { myAssets, userActions, renderItem } = this.props; + const { length: listAmount } = myAssets; + + const dataList = myAssets.map(asset => { + let userAction = userActions.find(action => action.assetId === asset.id); + return { asset, userAction }; + }); + const withoutAssets = listAmount === 0; + return withoutAssets ? ( {`You haven't bought any land yet.`} ) : ( item.id} + keyExtractor={item => item.asset.id} /> ); } @@ -31,7 +38,8 @@ export default class MyAssetsList extends React.PureComponent { MyAssetsList.propTypes = { renderItem: PropTypes.func.isRequired, - myAssetsList: PropTypes.array.isRequired, + myAssets: PropTypes.array.isRequired, + userActions: PropTypes.array.isRequired, }; const styles = StyleSheet.create({ diff --git a/decentraland/components/presentational/MyAssetsList.test.js b/decentraland/components/presentational/MyAssetsList.test.js index e7a9fa6d..415625f1 100644 --- a/decentraland/components/presentational/MyAssetsList.test.js +++ b/decentraland/components/presentational/MyAssetsList.test.js @@ -1,31 +1,34 @@ import React from "react"; import { shallow } from "enzyme"; import MyAssetsList from "./MyAssetsList"; -import { estate } from "@helpers/testHelpers"; +import { estate, userActions } from "@helpers/testHelpers"; describe("MyAssetsList", () => { describe("renders the component", () => { const myAssetRenderer = () => {}; it("without assets", async () => { - const myAssetsList = []; + const myAssets = []; + expect( shallow( ) ).toMatchSnapshot(); }); it("with assets", async () => { - const myAssetsList = [estate]; + const myAssets = [estate]; expect( shallow( ) ).toMatchSnapshot(); diff --git a/decentraland/components/presentational/MyAssetsListItem.js b/decentraland/components/presentational/MyAssetsListItem.js index d51d80ae..49f61ad8 100644 --- a/decentraland/components/presentational/MyAssetsListItem.js +++ b/decentraland/components/presentational/MyAssetsListItem.js @@ -14,11 +14,11 @@ import MyAsset from "./MyAsset"; // https://medium.com/groww-engineering/stateless-component-vs-pure-component-d2af88a1200b export default class MyAssetsListItem extends React.PureComponent { render() { - const { asset } = this.props; + const { asset, userAction } = this.props; return ( - + ); @@ -27,6 +27,7 @@ export default class MyAssetsListItem extends React.PureComponent { MyAssetsListItem.propTypes = { asset: PropTypes.object.isRequired, + userAction: PropTypes.object.isRequired, }; const styles = StyleSheet.create({ diff --git a/decentraland/components/presentational/MyAssetsListItem.test.js b/decentraland/components/presentational/MyAssetsListItem.test.js index 1ea567c8..ba0f3d15 100644 --- a/decentraland/components/presentational/MyAssetsListItem.test.js +++ b/decentraland/components/presentational/MyAssetsListItem.test.js @@ -1,10 +1,14 @@ import React from "react"; import { shallow } from "enzyme"; import MyAssetsListItem from "./MyAssetsListItem"; -import { parcel } from "@helpers/testHelpers"; +import { parcel, parcelUserAction } from "@helpers/testHelpers"; describe("MyAssetsListItem", () => { it("renders the component", async () => { - expect(shallow()).toMatchSnapshot(); + const asset = parcel; + const userAction = parcelUserAction; + expect( + shallow() + ).toMatchSnapshot(); }); }); diff --git a/decentraland/components/presentational/__snapshots__/MyAsset.test.js.snap b/decentraland/components/presentational/__snapshots__/MyAsset.test.js.snap index ab63fbd1..6b9f7336 100644 --- a/decentraland/components/presentational/__snapshots__/MyAsset.test.js.snap +++ b/decentraland/components/presentational/__snapshots__/MyAsset.test.js.snap @@ -29,7 +29,9 @@ exports[`MyAsset MyAssetInfo renders the component 1`] = ` } } > - + `; @@ -61,6 +63,13 @@ exports[`MyAsset renders the component 1`] = ` "type": "PARCEL", } } + userAction={ + Object { + "actionId": "0x1234567890123456789012345678901234567890", + "assetId": "0123456789", + "status": "SUCCESSFUL", + } + } /> `; diff --git a/decentraland/components/presentational/__snapshots__/MyAssetsList.test.js.snap b/decentraland/components/presentational/__snapshots__/MyAssetsList.test.js.snap index 27a63ec3..41dd3166 100644 --- a/decentraland/components/presentational/__snapshots__/MyAssetsList.test.js.snap +++ b/decentraland/components/presentational/__snapshots__/MyAssetsList.test.js.snap @@ -5,10 +5,17 @@ exports[`MyAssetsList renders the component with assets 1`] = ` data={ Array [ Object { - "id": "123", - "img": "https://api.decentraland.org/v1/estates/1/map.png", - "name": "Sample Estate", - "type": "ESTATE", + "asset": Object { + "id": "123", + "img": "https://api.decentraland.org/v1/estates/1/map.png", + "name": "Sample Estate", + "type": "ESTATE", + }, + "userAction": Object { + "actionId": "0x987654321098765432109876543210987654321", + "assetId": "123", + "status": "SUCCESSFUL", + }, }, ] } diff --git a/decentraland/components/presentational/__snapshots__/MyAssetsListItem.test.js.snap b/decentraland/components/presentational/__snapshots__/MyAssetsListItem.test.js.snap index bb980b2c..841bdfb5 100644 --- a/decentraland/components/presentational/__snapshots__/MyAssetsListItem.test.js.snap +++ b/decentraland/components/presentational/__snapshots__/MyAssetsListItem.test.js.snap @@ -28,6 +28,13 @@ exports[`MyAssetsListItem renders the component 1`] = ` "type": "PARCEL", } } + userAction={ + Object { + "actionId": "0x1234567890123456789012345678901234567890", + "assetId": "0123456789", + "status": "SUCCESSFUL", + } + } /> diff --git a/decentraland/constants/AssetTypes.js b/decentraland/constants/AssetTypes.js index dbecb521..ac9f2c15 100644 --- a/decentraland/constants/AssetTypes.js +++ b/decentraland/constants/AssetTypes.js @@ -1,5 +1,5 @@ -const ESTATE = "ESTATE"; -const PARCEL = "PARCEL"; +export const ESTATE = "ESTATE"; +export const PARCEL = "PARCEL"; export default { ESTATE, diff --git a/decentraland/constants/MyAssetStatus.js b/decentraland/constants/MyAssetStatus.js deleted file mode 100644 index 17a369e5..00000000 --- a/decentraland/constants/MyAssetStatus.js +++ /dev/null @@ -1,7 +0,0 @@ -export const BUYING = "BUYING"; -export const BOUGHT = "BOUGHT"; - -export default { - BUYING, - BOUGHT, -}; diff --git a/decentraland/constants/UserActionStatus.js b/decentraland/constants/UserActionStatus.js new file mode 100644 index 00000000..2778af02 --- /dev/null +++ b/decentraland/constants/UserActionStatus.js @@ -0,0 +1,17 @@ +// To be used for meta-tx +export const BROADCASTED = "BROADCASTED"; + +export const PENDING = "PENDING"; +export const FAILED = "FAILED"; + +// Note: We are using only 'SUCCESSFUL' for now +// When we'll tracking confirmations that will be used for >= 7 +// and 'PROBABLY_SUCCESSFUL' to < 7 confirmations. +export const PROBABLY_SUCCESSFUL = "PROBABLY_SUCCESSFUL"; +export const SUCCESSFUL = "SUCCESSFUL"; + +export default { + PENDING, + FAILED, + SUCCESSFUL, +}; diff --git a/decentraland/helpers/storage.js b/decentraland/helpers/storage.js index 5737d1fe..1229aa47 100644 --- a/decentraland/helpers/storage.js +++ b/decentraland/helpers/storage.js @@ -8,6 +8,19 @@ const MY_ASSETS_LIST = "MY_ASSETS_LIST"; const EPHEMERAL_ACCOUNT_CREATION_STATUS = "EPHEMERAL_ACCOUNT_CREATION_STATUS"; const EPHEMERAL_ACCOUNT_CREATION_ACTIONS = "EPHEMERAL_ACCOUNT_CREATION_ACTIONS"; const IS_FIRST_APP_USE = "IS_FIRST_APP_USE"; +const USER_ACTIONS = "USER_ACTIONS"; + +export const storeUserActions = async userActions => { + const strUserActions = _toString(userActions); + await _storeData(USER_ACTIONS, strUserActions, false); +}; + +export const retrieveUserActions = async () => { + const strUserActions = await _retrieveData(USER_ACTIONS); + const userActions = _fromString(strUserActions); + if (userActions === null) return []; + return userActions; +}; export const storeIsFirstUse = async isFirstUse => { const strIsFirstUse = _toString(isFirstUse); @@ -127,4 +140,6 @@ export default { retrieveAccountCreationActions, storeIsFirstUse, retrieveIsFirstUse, + retrieveUserActions, + storeUserActions, }; diff --git a/decentraland/helpers/testHelpers.js b/decentraland/helpers/testHelpers.js index 6e2179e0..58278eca 100644 --- a/decentraland/helpers/testHelpers.js +++ b/decentraland/helpers/testHelpers.js @@ -1,6 +1,6 @@ -import AssetTypes from "@constants/AssetTypes"; +import { ESTATE, PARCEL } from "@constants/AssetTypes"; import { DONE, PENDING, MISSING } from "@constants/ActionStatus"; -const { ESTATE, PARCEL } = AssetTypes; +import { SUCCESSFUL } from "@constants/UserActionStatus"; export const parcel = { type: PARCEL, @@ -56,6 +56,20 @@ export const accountCreationSteps = [ }, ]; +export const parcelUserAction = { + actionId: "0x1234567890123456789012345678901234567890", + assetId: "0123456789", + status: SUCCESSFUL, +}; + +export const estateUserAction = { + actionId: "0x987654321098765432109876543210987654321", + assetId: "123", + status: SUCCESSFUL, +}; + +export const userActions = [parcelUserAction, estateUserAction]; + export default { estateForSale, parcelForSale, @@ -63,4 +77,7 @@ export default { estate, anAction, estateWithoutName, + parcelUserAction, + estateUserAction, + userActions, }; diff --git a/decentraland/redux/actions.js b/decentraland/redux/actions.js index 93c9b328..97a6ad17 100644 --- a/decentraland/redux/actions.js +++ b/decentraland/redux/actions.js @@ -13,8 +13,8 @@ export const PREPEND_TO_MY_ASSETS_LIST = "PREPEND_TO_MY_ASSETS_LIST"; export const APPEND_TO_MY_ASSETS_LIST = "APPEND_TO_MY_ASSETS_LIST"; export const REMOVE_FROM_MY_ASSETS_LIST = "REMOVE_FROM_MY_ASSETS_LIST"; export const SET_MY_ASSETS_LIST = "SET_MY_ASSETS_LIST"; -export const SET_ACTION_ID_FOR_MY_ASSET = "SET_ACTION_ID_FOR_MY_ASSET"; -export const UPDATE_MY_ASSET_STATUS = "UPDATE_MY_ASSET_STATUS"; +export const ADD_USER_ACTION = "ADD_USER_ACTION"; +export const UPDATE_USER_ACTION_STATUS = "UPDATE_USER_ACTION_STATUS"; export function setAccount(account) { return { type: SET_ACCOUNT, account }; @@ -71,16 +71,10 @@ export function setMyAssetsList(myAssets) { return { type: SET_MY_ASSETS_LIST, myAssets }; } -export function setActionIdForMyAsset(myAssetId, actionId) { - return { - type: SET_ACTION_ID_FOR_MY_ASSET, - myAssetAndActionIds: { myAssetId, actionId }, - }; +export function addUserAction(itemOrList) { + return { type: ADD_USER_ACTION, itemOrList }; } -export function updateMyAssetStatus(myAssetId, status) { - return { - type: UPDATE_MY_ASSET_STATUS, - myAssetAndStatus: { myAssetId, status }, - }; +export function updateUserActionStatus(actionIdAndStatus) { + return { type: UPDATE_USER_ACTION_STATUS, actionIdAndStatus }; } diff --git a/decentraland/redux/middlewares.js b/decentraland/redux/middlewares.js index dd18d315..60e8d459 100644 --- a/decentraland/redux/middlewares.js +++ b/decentraland/redux/middlewares.js @@ -8,21 +8,22 @@ import { APPEND_TO_MY_ASSETS_LIST, REMOVE_FROM_MY_ASSETS_LIST, SET_MY_ASSETS_LIST, - SET_ACTION_ID_FOR_MY_ASSET, - UPDATE_MY_ASSET_STATUS, + ADD_USER_ACTION, + UPDATE_USER_ACTION_STATUS, } from "./actions"; import { storeAccount, storeAccountCreationStatus, storeAccountCreationActions, storeMyAssets, + storeUserActions, } from "@helpers/storage"; const storer = store => next => async action => { const { type } = action; const result = next(action); const nextState = store.getState(); - const { accountInfo, myAssets } = nextState; + const { accountInfo, myAssets, userActions } = nextState; const { account, creationStatus, creationActions } = accountInfo; const { list: myAssetsList } = myAssets; @@ -59,12 +60,12 @@ const storer = store => next => async action => { await storeMyAssets(myAssetsList); break; } - case SET_ACTION_ID_FOR_MY_ASSET: { - await storeMyAssets(myAssetsList); + case ADD_USER_ACTION: { + await storeUserActions(userActions); break; } - case UPDATE_MY_ASSET_STATUS: { - await storeMyAssets(myAssetsList); + case UPDATE_USER_ACTION_STATUS: { + await storeUserActions(userActions); break; } } diff --git a/decentraland/redux/reducers.js b/decentraland/redux/reducers.js index 221b0813..b1b260be 100644 --- a/decentraland/redux/reducers.js +++ b/decentraland/redux/reducers.js @@ -13,8 +13,8 @@ import { APPEND_TO_MY_ASSETS_LIST, REMOVE_FROM_MY_ASSETS_LIST, SET_MY_ASSETS_LIST, - SET_ACTION_ID_FOR_MY_ASSET, - UPDATE_MY_ASSET_STATUS, + ADD_USER_ACTION, + UPDATE_USER_ACTION_STATUS, } from "./actions"; import { removeFromList, updateListItem, toListIfNot } from "@helpers"; @@ -145,24 +145,6 @@ const setMyAssetsList = (state, action) => { return { ...state, list }; }; -const setActionIdForMyAsset = (state, action) => { - const { myAssetAndActionIds } = action; - const { myAssetId: toUpdateId, actionId } = myAssetAndActionIds; - const { list: myAssets } = state; - const entriesToUpdate = { actionId }; - const list = updateListItem(myAssets, toUpdateId, entriesToUpdate); - return { ...state, list }; -}; - -const updateMyAssetStatus = (state, action) => { - const { myAssetAndStatus } = action; - const { myAssetId: toUpdateId, status } = myAssetAndStatus; - const { list: myAssets } = state; - const entriesToUpdate = { status }; - const list = updateListItem(myAssets, toUpdateId, entriesToUpdate); - return { ...state, list }; -}; - const myAssets = createReducer( { list: [] }, { @@ -170,11 +152,31 @@ const myAssets = createReducer( [APPEND_TO_MY_ASSETS_LIST]: appendToMyAssetsList, [REMOVE_FROM_MY_ASSETS_LIST]: removeFromMyAssetsList, [SET_MY_ASSETS_LIST]: setMyAssetsList, - [SET_ACTION_ID_FOR_MY_ASSET]: setActionIdForMyAsset, - [UPDATE_MY_ASSET_STATUS]: updateMyAssetStatus, } ); +// +// userActions reducer +// +const addUserAction = (state, action) => { + const { itemOrList } = action; + const toAppend = toListIfNot(itemOrList); + return [...state, ...toAppend]; +}; + +const updateUserActionStatus = (state, action) => { + const { actionIdAndStatus } = action; + const { actionId: toUpdateId, status } = actionIdAndStatus; + const entriesToUpdate = { status }; + const list = updateListItem(state, toUpdateId, entriesToUpdate); + return list; +}; + +const userActions = createReducer([], { + [ADD_USER_ACTION]: addUserAction, + [UPDATE_USER_ACTION_STATUS]: updateUserActionStatus, +}); + // // All reducers // @@ -183,6 +185,7 @@ const decentralandApp = combineReducers({ landToBuy, assetsForSale, myAssets, + userActions, }); export default decentralandApp; diff --git a/decentraland/screens/BuyLandScreen.js b/decentraland/screens/BuyLandScreen.js index 8aa613e6..7fe806ca 100644 --- a/decentraland/screens/BuyLandScreen.js +++ b/decentraland/screens/BuyLandScreen.js @@ -6,16 +6,14 @@ import { prependLandForSaleToList, removeFromMyAssetsList, prependToMyAssetsList, - setActionIdForMyAsset, - updateMyAssetStatus, + addUserAction, + updateUserActionStatus, } from "../redux/actions"; import BuyLand from "@presentational/BuyLand"; import PropTypes from "prop-types"; import { showError, showInfo, getContracts } from "@helpers"; -import AssetTypes from "@constants/AssetTypes"; -const { ESTATE, PARCEL } = AssetTypes; -import MyAssetStatus from "@constants/MyAssetStatus"; -const { BUYING, BOUGHT } = MyAssetStatus; +import { ESTATE, PARCEL } from "@constants/AssetTypes"; +import { PENDING, SUCCESSFUL } from "@constants/UserActionStatus"; // TODO: Go deep on gas handling. // Without that, VM returns a revert error instead of out of gas error. @@ -53,8 +51,8 @@ export class BuyLandScreen extends React.Component { prependLandForSaleToList, removeFromMyAssetsList, prependToMyAssetsList, - setActionIdForMyAsset, - updateMyAssetStatus, + addUserAction, + updateUserActionStatus, } = props; const { account } = accountInfo; const { asset } = landForSale; @@ -68,8 +66,9 @@ export class BuyLandScreen extends React.Component { // TODO: This function should be called inside of the eventListener // that catches the safeExecuteOrder successful event. await action.waitForNonceToUpdate(); + const actionId = await action.getId(); - updateMyAssetStatus(assetId, BOUGHT); + updateUserActionStatus({ actionId, status: SUCCESSFUL }); showInfo(`${typeDescription} bought successfully.`); }; @@ -87,14 +86,16 @@ export class BuyLandScreen extends React.Component { // Optimistic UI update removeLandForSale(landForSale); - prependToMyAssetsList({ ...asset, status: BUYING }); + prependToMyAssetsList(asset); // Back to top of current Stack before navigate navigation.dispatch(StackActions.popToTop()); navigation.navigate("MyAssetsScreen"); const actionId = await action.getId(); - setActionIdForMyAsset(assetId, actionId); + const userAction = { actionId, status: PENDING, assetId }; + + addUserAction(userAction); onSuccess(); }; @@ -157,8 +158,8 @@ BuyLandScreen.propTypes = { prependLandForSaleToList: PropTypes.func.isRequired, removeFromMyAssetsList: PropTypes.func.isRequired, prependToMyAssetsList: PropTypes.func.isRequired, - setActionIdForMyAsset: PropTypes.func.isRequired, - updateMyAssetStatus: PropTypes.func.isRequired, + addUserAction: PropTypes.func.isRequired, + updateUserActionStatus: PropTypes.func.isRequired, }; const mapStateToProps = state => { @@ -172,8 +173,8 @@ const mapDispatchToProps = { prependLandForSaleToList, prependToMyAssetsList, removeFromMyAssetsList, - setActionIdForMyAsset, - updateMyAssetStatus, + addUserAction, + updateUserActionStatus, }; export default connect( diff --git a/decentraland/screens/BuyLandScreen.test.js b/decentraland/screens/BuyLandScreen.test.js index 7fa007f3..53f55659 100644 --- a/decentraland/screens/BuyLandScreen.test.js +++ b/decentraland/screens/BuyLandScreen.test.js @@ -17,8 +17,8 @@ describe("BuyLandScreen", () => { const prependToMyAssetsList = () => {}; const removeFromMyAssetsList = () => {}; const prependLandForSaleToList = () => {}; - const setActionIdForMyAsset = () => {}; - const updateMyAssetStatus = () => {}; + const addUserAction = () => {}; + const updateUserActionStatus = () => {}; const myAssets = []; expect( @@ -32,8 +32,8 @@ describe("BuyLandScreen", () => { prependToMyAssetsList={prependToMyAssetsList} removeFromMyAssetsList={removeFromMyAssetsList} prependLandForSaleToList={prependLandForSaleToList} - setActionIdForMyAsset={setActionIdForMyAsset} - updateMyAssetStatus={updateMyAssetStatus} + addUserAction={addUserAction} + updateUserActionStatus={updateUserActionStatus} /> ) ).toMatchSnapshot(); diff --git a/decentraland/screens/MyAssetsScreen.js b/decentraland/screens/MyAssetsScreen.js index 5ae8d8d6..4e03e710 100644 --- a/decentraland/screens/MyAssetsScreen.js +++ b/decentraland/screens/MyAssetsScreen.js @@ -1,42 +1,76 @@ import React from "react"; import { connect } from "react-redux"; -import { removeFromMyAssetsList, appendToMyAssetsList } from "../redux/actions"; +import { + removeFromMyAssetsList, + appendToMyAssetsList, + addUserAction, +} from "../redux/actions"; import PropTypes from "prop-types"; import MyAssetsList from "@presentational/MyAssetsList"; import MyAssetsListItem from "@presentational/MyAssetsListItem"; import { listsAreEqual, getContracts, logWarn } from "@helpers"; import { generateAssetFromId } from "@helpers/decentraland"; import DecentralandUtils from "tasit-sdk/dist/helpers/DecentralandUtils"; -import MyAssetStatus from "@constants/MyAssetStatus"; -const { BOUGHT } = MyAssetStatus; +import { SUCCESSFUL } from "@constants/UserActionStatus"; export class MyAssetsScreen extends React.Component { componentDidMount = async () => { const { account, - myAssets: assetsFromState, removeFromMyAssetsList, appendToMyAssetsList, + addUserAction, } = this.props; if (account) { const { address } = account; - // Note: If a stored asset doesn't have a status, assuming 'bought'. - const boughtAssets = assetsFromState - .map(asset => (!asset.status ? { ...asset, status: BOUGHT } : asset)) - .filter(asset => asset.status === BOUGHT); + const boughtAssets = this._getBoughtAssetsFromState(); + + const { + assetsFromBlockchain, + actionsFromBlockchain, + } = await this.__getAssetsAndActionsFromBlockchain(address); - const assetsFromBlockchain = await this._getAssetsFromBlockchain(address); const shouldUpdate = !listsAreEqual(assetsFromBlockchain, boughtAssets); if (shouldUpdate) { // TODO: Add some UI indication that something unexpected happened this._logAssetsInconsistency(assetsFromBlockchain, boughtAssets); + removeFromMyAssetsList(boughtAssets); appendToMyAssetsList(assetsFromBlockchain); + addUserAction(actionsFromBlockchain); } } }; + _getAssetsAndActionsFromBlockchain = async address => { + const assetsFromBlockchain = await this._getAssetsFromBlockchain(address); + + const actionsFromBlockchain = assetsFromBlockchain.map(asset => { + const { actionId, id: assetId } = asset; + const status = SUCCESSFUL; + return { actionId, assetId, status }; + }); + + assetsFromBlockchain.forEach(asset => delete asset.actionId); + + return { assetsFromBlockchain, actionsFromBlockchain }; + }; + + _getBoughtAssetsFromState = () => { + const { myAssets: assetsFromState, userActions } = this.props; + + const boughtAssets = assetsFromState.filter(asset => { + const { id: assetId } = asset; + const assetAction = userActions.find( + action => action.assetId === assetId + ); + return assetAction && assetAction.status === SUCCESSFUL; + }); + + return boughtAssets; + }; + _logAssetsInconsistency = (fromBlockchain, fromState) => { const fromBlockchainIds = fromBlockchain.map(asset => asset.id); const fromStateIds = fromState.map(asset => asset.id); @@ -78,7 +112,6 @@ export class MyAssetsScreen extends React.Component { for (let land of listOfLand) { const generateAsset = async land => { const { id: assetId, nftAddress, transactionHash: actionId } = land; - const status = BOUGHT; const asset = await generateAssetFromId( estateContract, @@ -87,7 +120,7 @@ export class MyAssetsScreen extends React.Component { nftAddress ); - return { ...asset, actionId, status }; + return { ...asset, actionId }; }; const assetPromise = generateAsset(land); @@ -97,15 +130,21 @@ export class MyAssetsScreen extends React.Component { return assets; }; - _renderItem = ({ item: asset }) => { - return ; + _renderItem = ({ item: assetAndUserAction }) => { + let { asset, userAction } = assetAndUserAction; + if (!userAction) userAction = {}; + return ; }; render() { - const { myAssets } = this.props; + const { myAssets, userActions } = this.props; return ( - + ); } } @@ -113,18 +152,20 @@ export class MyAssetsScreen extends React.Component { MyAssetsScreen.propTypes = { myAssets: PropTypes.array.isRequired, account: PropTypes.object, + userActions: PropTypes.array.isRequired, }; const mapStateToProps = state => { - const { myAssets, accountInfo } = state; + const { myAssets, accountInfo, userActions } = state; const { account } = accountInfo; const { list: myAssetsList } = myAssets; - return { myAssets: myAssetsList, account }; + return { myAssets: myAssetsList, account, userActions }; }; const mapDispatchToProps = { removeFromMyAssetsList, appendToMyAssetsList, + addUserAction, }; export default connect( diff --git a/decentraland/screens/MyAssetsScreen.test.js b/decentraland/screens/MyAssetsScreen.test.js index a70490b1..66f49622 100644 --- a/decentraland/screens/MyAssetsScreen.test.js +++ b/decentraland/screens/MyAssetsScreen.test.js @@ -1,30 +1,25 @@ import React from "react"; import { shallow } from "enzyme"; import { MyAssetsScreen } from "./MyAssetsScreen"; -import { parcel } from "@helpers/testHelpers"; +import { parcel, parcelUserAction } from "@helpers/testHelpers"; describe("ListLandForSaleScreen", () => { describe("renders the component", () => { - const setMyAssetsList = () => {}; it("without assets", async () => { const myAssets = []; + const userActions = []; expect( shallow( - + ) ).toMatchSnapshot(); }); it("with assets", async () => { const myAssets = [parcel]; + const userActions = [parcelUserAction]; expect( shallow( - + ) ).toMatchSnapshot(); }); diff --git a/decentraland/screens/__snapshots__/MyAssetsScreen.test.js.snap b/decentraland/screens/__snapshots__/MyAssetsScreen.test.js.snap index 86acdb5f..c74857ca 100644 --- a/decentraland/screens/__snapshots__/MyAssetsScreen.test.js.snap +++ b/decentraland/screens/__snapshots__/MyAssetsScreen.test.js.snap @@ -2,7 +2,7 @@ exports[`ListLandForSaleScreen renders the component with assets 1`] = ` `; exports[`ListLandForSaleScreen renders the component without assets 1`] = ` `; From edb45201c90a443512e3e92005ba9b63bad15494 Mon Sep 17 00:00:00 2001 From: cmarcelo Date: Tue, 23 Apr 2019 22:25:09 +0100 Subject: [PATCH 17/18] Review request --- decentraland/.eslintrc.js | 2 +- decentraland/helpers/index.js | 5 +++++ decentraland/screens/MyAssetsScreen.js | 8 ++++---- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/decentraland/.eslintrc.js b/decentraland/.eslintrc.js index cb7356c1..b6d4940d 100644 --- a/decentraland/.eslintrc.js +++ b/decentraland/.eslintrc.js @@ -15,7 +15,7 @@ module.exports = { "plugin:jest/recommended", ], rules: { - "no-console": ["error", { allow: ["warn", "error"] }], + "no-console": ["error", { allow: ["warn", "error", "info"] }], "react-native/no-raw-text": ["error", { skip: ["LargeText"] }], "react/prop-types": ["error", { ignore: ["navigation"] }], "prettier/prettier": "error", diff --git a/decentraland/helpers/index.js b/decentraland/helpers/index.js index 13fd5542..0091edba 100644 --- a/decentraland/helpers/index.js +++ b/decentraland/helpers/index.js @@ -133,7 +133,10 @@ export const showFatalError = msg => console.error(msg); export const showError = msg => showToast(`ERROR: ${msg}`); export const showWarn = msg => showToast(`WARN: ${msg}`); export const showInfo = msg => showToast(`${msg}`); + +export const logInfo = msg => console.info(msg); export const logWarn = msg => console.warn(msg); +export const logError = msg => console.error(msg); export const checkBlockchain = async () => { loadConfig(); @@ -258,7 +261,9 @@ export default { showError, showWarn, showInfo, + logInfo, logWarn, + logError, fundAccountWithEthers, fundAccountWithMana, getContracts, diff --git a/decentraland/screens/MyAssetsScreen.js b/decentraland/screens/MyAssetsScreen.js index 4e03e710..79e6a4f2 100644 --- a/decentraland/screens/MyAssetsScreen.js +++ b/decentraland/screens/MyAssetsScreen.js @@ -8,7 +8,7 @@ import { import PropTypes from "prop-types"; import MyAssetsList from "@presentational/MyAssetsList"; import MyAssetsListItem from "@presentational/MyAssetsListItem"; -import { listsAreEqual, getContracts, logWarn } from "@helpers"; +import { listsAreEqual, getContracts, logInfo } from "@helpers"; import { generateAssetFromId } from "@helpers/decentraland"; import DecentralandUtils from "tasit-sdk/dist/helpers/DecentralandUtils"; import { SUCCESSFUL } from "@constants/UserActionStatus"; @@ -80,15 +80,15 @@ export class MyAssetsScreen extends React.Component { id => !fromBlockchainIds.includes(id) ); - logWarn( + logInfo( `Account's assets from blockchain aren't the same as the ones stored on the app.` ); if (addedIds.length > 0) - logWarn(`Some assets added to MyLandScreen. IDs: [${addedIds}]`); + logInfo(`Some assets added to MyLandScreen. IDs: [${addedIds}]`); if (removedIds.length > 0) - logWarn(`Some assets removed from MyLandScreen. IDs: [${removedIds}]`); + logInfo(`Some assets removed from MyLandScreen. IDs: [${removedIds}]`); }; _getAssetsFromBlockchain = async address => { From 22799c55da646ec8034938feb0272381be9d0c0a Mon Sep 17 00:00:00 2001 From: Paul Cowgill Date: Tue, 23 Apr 2019 20:35:08 -0500 Subject: [PATCH 18/18] Grammar --- decentraland/constants/UserActionStatus.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/decentraland/constants/UserActionStatus.js b/decentraland/constants/UserActionStatus.js index 2778af02..6c5c39d2 100644 --- a/decentraland/constants/UserActionStatus.js +++ b/decentraland/constants/UserActionStatus.js @@ -5,7 +5,7 @@ export const PENDING = "PENDING"; export const FAILED = "FAILED"; // Note: We are using only 'SUCCESSFUL' for now -// When we'll tracking confirmations that will be used for >= 7 +// When we're tracking confirmations that will be used for >= 7 // and 'PROBABLY_SUCCESSFUL' to < 7 confirmations. export const PROBABLY_SUCCESSFUL = "PROBABLY_SUCCESSFUL"; export const SUCCESSFUL = "SUCCESSFUL";