From cad52b5c37e0044506cf9203d34291d0042edf76 Mon Sep 17 00:00:00 2001 From: 1aerostorm Date: Sun, 14 Jan 2024 18:44:04 +0000 Subject: [PATCH] HF 30 - NFT auction --- app/components/all.scss | 1 + app/components/cards/TransferHistoryRow.jsx | 14 + app/components/elements/Expandable.jsx | 6 + .../elements/forms/AmountAssetField.jsx | 22 +- app/components/elements/forms/AmountField.jsx | 6 +- app/components/elements/nft/NFTTokenItem.jsx | 13 +- app/components/elements/nft/NFTTokenItem.scss | 2 +- app/components/modules/nft/NFTPlaceBet.jsx | 257 ++++++++++++++++++ app/components/modules/nft/NFTPlaceBet.scss | 9 + app/components/modules/nft/NFTTokens.jsx | 27 +- app/components/pages/nft/NFTTokenPage.jsx | 192 ++++++++++++- app/components/pages/nft/NFTTokenPage.scss | 10 + app/locales/en.json | 9 +- app/locales/ru-RU.json | 9 +- app/redux/FetchDataSaga.js | 89 +++++- app/redux/GlobalReducer.js | 2 + 16 files changed, 630 insertions(+), 38 deletions(-) create mode 100644 app/components/modules/nft/NFTPlaceBet.jsx create mode 100644 app/components/modules/nft/NFTPlaceBet.scss diff --git a/app/components/all.scss b/app/components/all.scss index e48581c..f5a86f0 100644 --- a/app/components/all.scss +++ b/app/components/all.scss @@ -74,6 +74,7 @@ @import "./modules/nft/CreateNFTCollection"; @import "./modules/nft/IssueNFTToken"; @import "./modules/nft/NFTTokens"; +@import "./modules/nft/NFTPlaceBet"; @import "./modules/nft/NFTTokenSell"; @import "./modules/nft/NFTTokenTransfer"; diff --git a/app/components/cards/TransferHistoryRow.jsx b/app/components/cards/TransferHistoryRow.jsx index fdbc8ff..7711906 100644 --- a/app/components/cards/TransferHistoryRow.jsx +++ b/app/components/cards/TransferHistoryRow.jsx @@ -398,6 +398,20 @@ class TransferHistoryRow extends React.Component { linkExternal3 = true description_end = tt('transferhistoryrow_jsx.for') description_end += Asset(data.price).floatString + } else if (type === 'nft_buy') { + link = data.buyer + description_middle = tt('nft_token_page_jsx.placed_bet') + tt('nft_token_page_jsx.on') + const { tokenTitle, tokenLink } = getToken(data.token_id) + link2 = tokenLink + linkExternal2 = true + description_middle2 = tt('nft_token_page_jsx.selled2m') + description_middle2 += Asset(data.price).floatString + } else if (type === 'nft_cancel_order') { + link = data.owner + description_middle = tt('nft_token_page_jsx.canceled_bet') + tt('nft_token_page_jsx.on') + const { tokenTitle, tokenLink } = getToken(data.token_id) + link2 = tokenLink + linkExternal2 = true } else { code_key = JSON.stringify({type, ...data}, null, 2); } diff --git a/app/components/elements/Expandable.jsx b/app/components/elements/Expandable.jsx index 222bb1f..e3ec881 100644 --- a/app/components/elements/Expandable.jsx +++ b/app/components/elements/Expandable.jsx @@ -6,6 +6,12 @@ class Expandable extends Component { opened: false, }; + componentDidMount() { + this.setState({ + opened: !!this.props.opened + }) + } + onToggleExpander = () => { this.setState({ opened: !this.state.opened, diff --git a/app/components/elements/forms/AmountAssetField.jsx b/app/components/elements/forms/AmountAssetField.jsx index 4eb5782..449fbd1 100644 --- a/app/components/elements/forms/AmountAssetField.jsx +++ b/app/components/elements/forms/AmountAssetField.jsx @@ -8,23 +8,19 @@ class AmountAssetField extends React.Component { } onChange = (e) => { - const { amountField, values, setFieldValue, assets } = this.props + const { amountField, onChange, values, setFieldValue, assets } = this.props const value = e.target.value const asset = assets[value] if (asset) { const { supply } = asset - const oldValue = values[amountField].asset - setFieldValue(amountField, AssetEditor(oldValue.amount, - supply.precision, supply.symbol)) - - if (!values.author) { // if not edit mode - if (asset.allow_override_transfer || value === 'GBG') { - if (values.tip_cost) - setFieldValue('tip_cost', false) - setFieldValue('disable_tip', true) - } else { - setFieldValue('disable_tip', false) - } + if (amountField) { + const oldValue = values[amountField].asset + setFieldValue(amountField, AssetEditor(oldValue.amount, + supply.precision, supply.symbol)) + } + + if (onChange) { + onChange(e, asset) } } } diff --git a/app/components/elements/forms/AmountField.jsx b/app/components/elements/forms/AmountField.jsx index f9b5ef2..b91fc86 100644 --- a/app/components/elements/forms/AmountField.jsx +++ b/app/components/elements/forms/AmountField.jsx @@ -11,13 +11,13 @@ class AmountField extends React.Component { // TODO: is it right to pass all props to input const { placeholder, name, ...rest } = this.props const { value, } = field - const { values, setFieldValue } = form + const { values, setFieldValue, setFieldTouched } = form return this.onChange(e, values, setFieldValue)} + {...rest} onChange={(e) => this.onChange(e, values, setFieldValue, setFieldTouched)} /> } - onChange = (e, values, setFieldValue) => { + onChange = (e, values, setFieldValue, setFieldTouched) => { const { name } = this.props const newAmount = values[name].withChange(e.target.value) if (newAmount.hasChange && newAmount.asset.amount >= 0) { diff --git a/app/components/elements/nft/NFTTokenItem.jsx b/app/components/elements/nft/NFTTokenItem.jsx index d546e8f..3561193 100644 --- a/app/components/elements/nft/NFTTokenItem.jsx +++ b/app/components/elements/nft/NFTTokenItem.jsx @@ -126,6 +126,12 @@ class NFTTokenItem extends Component { }, value: tt('g.transfer') }) } + // if (!isMy && last_price) { + // kebabItems.unshift({ link: '#', onClick: e => { + // this.props.showPlaceBet(e, tokenIdx) + // }, value: tt('nft_tokens_jsx.place_bet') }) + // } + const isCollection = page === 'collection' const isMarket = page === 'market' @@ -137,7 +143,7 @@ class NFTTokenItem extends Component { : null} {isMy && !selling && } - {isMy && selling && } {!isMy && selling && } - {isMy && } + {isMy && } {kebabItems.length > 1 ? : null} @@ -164,6 +170,9 @@ class NFTTokenItem extends Component { {!isMy && {'@' + token.owner} } + {token.has_bets && + {tt('nft_tokens_jsx.has_bets')} + }
{data.title}
diff --git a/app/components/elements/nft/NFTTokenItem.scss b/app/components/elements/nft/NFTTokenItem.scss index 0782900..889be57 100644 --- a/app/components/elements/nft/NFTTokenItem.scss +++ b/app/components/elements/nft/NFTTokenItem.scss @@ -51,7 +51,7 @@ .button:hover { background-color: #016aad !important; } - .button.hollow { + .button.hollow.noborder { border: 0px; } .button.hollow:hover { diff --git a/app/components/modules/nft/NFTPlaceBet.jsx b/app/components/modules/nft/NFTPlaceBet.jsx new file mode 100644 index 0000000..4ec5a90 --- /dev/null +++ b/app/components/modules/nft/NFTPlaceBet.jsx @@ -0,0 +1,257 @@ +import React, { Component, } from 'react' +import tt from 'counterpart' +import { connect, } from 'react-redux' +import CloseButton from 'react-foundation-components/lib/global/close-button' +import { Formik, Form, Field, ErrorMessage, } from 'formik' +import { api } from 'golos-lib-js' +import { validateAccountName, Asset, AssetEditor } from 'golos-lib-js/lib/utils' + +import AssetBalance from 'app/components/elements/AssetBalance' +import AmountField from 'app/components/elements/forms/AmountField' +import AmountAssetField from 'app/components/elements/forms/AmountAssetField' +import LoadingIndicator from 'app/components/elements/LoadingIndicator' +import NFTSmallIcon from 'app/components/elements/nft/NFTSmallIcon' +import transaction from 'app/redux/Transaction' +import { generateOrderID } from 'app/utils/market/utils' +import session from 'app/utils/session' + +class NFTPlaceBet extends Component { + state = { + order: { + price: AssetEditor('0.000 GOLOS') + } + } + + async componentDidMount() { + const isHidden = (sym) => { + return ($STM_Config.hidden_assets && $STM_Config.hidden_assets[sym]) + } + try { + let assets = {} + let currentBalance + const username = session.load().currentName + if (username) { + let bals = await api.getAccountsBalances([username], { system: true }) + bals = bals[0] + if (bals['GOLOS']) { + assets['GOLOS'] = { supply: Asset(bals['GOLOS'].balance) } + } + if (bals['GBG']) { + assets['GBG'] = { supply: Asset(bals['GBG'].balance) } + } + for (const [sym, obj] of Object.entries(bals)) { + if (!isHidden(sym) && sym !== 'GOLOS' && sym !== 'GBG') { + assets[sym] = { supply: Asset(obj.balance) } + } + } + for (const [sym, obj] of Object.entries(assets)) { + currentBalance = obj.supply.clone() + break + } + } + this.setState({ + assets, + currentBalance + }) + } catch (err) { + console.error(err) + } + } + + validate = (values) => { + const errors = {} + const { price } = values + const { currentBalance } = this.state + if (price.asset.eq(0)) { + errors.price = tt('nft_token_sell_jsx.fill_price') + } else if (currentBalance && price.asset.gt(currentBalance)) { + errors.price = tt('g.invalid_amount') + } + return errors + } + + setSubmitting = (submitting) => { + this.setState({ submitting }) + } + + getToken = () => { + const { nft_tokens, tokenIdx } = this.props + if (tokenIdx !== undefined) { + return nft_tokens.toJS().data[tokenIdx] + } + return this.props.token + } + + _onSubmit = async (values) => { + this.setSubmitting(true) + this.setState({ + errorMessage: '' + }) + + const { currentUser, onClose, } = this.props + const token = this.getToken() + const { token_id, name } = token + + const username = currentUser.get('username') + + await this.props.placeBet(token_id, values.price, name, username, () => { + this.props.onClose() + this.setSubmitting(false) + this.doNotRender = true + this.props.refetch() + }, (err) => { + console.error(err) + this.setSubmitting(false) + this.setState({ + errorMessage: err.toString() + }) + }) + } + + onCancelMouseDown = (e) => { + e.preventDefault() + this.setState({ + cancelMouseDown: true + }) + } + + onCancelMouseUp = (e) => { + e.preventDefault() + if (this.state.cancelMouseDown) { + this.props.onClose() + this.setState({ + cancelMouseDown: false + }) + } + } + + onMouseUp = (e) => { + e.preventDefault() + if (this.state.cancelMouseDown) { + this.props.onClose() + } + } + + onAssetChange = (e, asset) => { + this.setState({ + currentBalance: asset.supply.clone() + }) + } + + _renderSubmittingIndicator = () => { + const { submitting } = this.state + + return submitting ? + + : null + } + + render() { + const { assets } = this.state + + if (this.doNotRender || !assets) { + return + } + + const { onClose, } = this.props + + const token = this.getToken() + + const { json_metadata, image } = token + + let data + if (json_metadata) { + data = JSON.parse(json_metadata) + } + data = data || {} // node allows to use '', null, object, or array + + const { errorMessage, submitting, currentBalance, } = this.state + + return
+ +

{tt('nft_tokens_jsx.place_bet')}

+
+ + {data.title} +
+ + {({ + handleSubmit, isValid, values, errors, touched, setFieldValue, handleChange, + }) => { + return ( +
+
+ {tt('g.price')} +
+
+
+
+ + + + +
+ {errors.price &&
{errors.price}
} +
+
+ {currentBalance && } + {(errorMessage && errorMessage !== 'Canceled') ?
+
+
{errorMessage}
+
+
: null} +
+
+ + + {this._renderSubmittingIndicator()} +
+
+ + )}}
+
+ } +} + +export default connect( + // mapStateToProps + (state, ownProps) => { + return { ...ownProps, + nft_tokens: state.global.get('nft_tokens'), + } + }, + + dispatch => ({ + placeBet: ( + token_id, price, collectionName, username, successCallback, errorCallback + ) => { + const operation = { + buyer: username, + name: collectionName, + token_id, + order_id: generateOrderID(), + price: price.asset.toString() + } + + dispatch(transaction.actions.broadcastOperation({ + type: 'nft_buy', + username, + operation, + successCallback, + errorCallback + })) + } + }) +)(NFTPlaceBet) diff --git a/app/components/modules/nft/NFTPlaceBet.scss b/app/components/modules/nft/NFTPlaceBet.scss new file mode 100644 index 0000000..feff4b1 --- /dev/null +++ b/app/components/modules/nft/NFTPlaceBet.scss @@ -0,0 +1,9 @@ +.NFTPlaceBet { + .column { + padding-left: 0rem; + padding-right: 0rem; + } + .error { + margin-bottom: 0px; + } +} diff --git a/app/components/modules/nft/NFTTokens.jsx b/app/components/modules/nft/NFTTokens.jsx index 0186c7c..6e42937 100644 --- a/app/components/modules/nft/NFTTokens.jsx +++ b/app/components/modules/nft/NFTTokens.jsx @@ -12,6 +12,7 @@ import LoadingIndicator from 'app/components/elements/LoadingIndicator' import NFTTokenItem from 'app/components/elements/nft/NFTTokenItem' import NFTTokenTransfer from 'app/components/modules/nft/NFTTokenTransfer' import NFTTokenSell from 'app/components/modules/nft/NFTTokenSell' +import NFTPlaceBet from 'app/components/modules/nft/NFTPlaceBet' import g from 'app/redux/GlobalReducer' class NFTTokens extends Component { @@ -59,6 +60,20 @@ class NFTTokens extends Component { }) } + showPlaceBet = (e, tokenIdx) => { + e.preventDefault() + this.setState({ + showPlaceBet: true, + tokenIdx, + }) + } + + hidePlaceBet = () => { + this.setState({ + showPlaceBet: false, + }) + } + sortOrder = (e, sort, sortReversed) => { e.preventDefault() this.sort = sort @@ -91,11 +106,12 @@ class NFTTokens extends Component { assets={assets} showTransfer={this.showTransfer} showSell={this.showSell} + showPlaceBet={this.showPlaceBet} refetch={this.refetch} />) } } - const { showTransfer, showSell, tokenIdx } = this.state + const { showTransfer, showSell, showPlaceBet, tokenIdx } = this.state const sortItems = [ { link: '#', onClick: e => { @@ -169,6 +185,15 @@ class NFTTokens extends Component { refetch={this.refetch} /> + + + +
) } } diff --git a/app/components/pages/nft/NFTTokenPage.jsx b/app/components/pages/nft/NFTTokenPage.jsx index cd986c1..6539797 100644 --- a/app/components/pages/nft/NFTTokenPage.jsx +++ b/app/components/pages/nft/NFTTokenPage.jsx @@ -11,6 +11,7 @@ import LoadingIndicator from 'app/components/elements/LoadingIndicator' import TimeAgoWrapper from 'app/components/elements/TimeAgoWrapper' import NFTTokenTransfer from 'app/components/modules/nft/NFTTokenTransfer' import NFTTokenSell from 'app/components/modules/nft/NFTTokenSell' +import NFTPlaceBet from 'app/components/modules/nft/NFTPlaceBet' import NotFound from 'app/components/pages/NotFound' import { msgsHost, msgsLink } from 'app/utils/ExtLinkUtils' import { getAssetMeta } from 'app/utils/market/utils' @@ -71,28 +72,52 @@ class NFTTokenPage extends Component { } async loadOps() { - const { nft_token, } = this.props - const token = nft_token.toJS() - const { token_id } = token - const ops = await api.getNftTokenOps({ - token_ids: [ token_id ], - from: 0, - limit: 500 - }) - this.setState({ - ops: ops[token_id] - }) + try { + const { nft_token, } = this.props + const token = nft_token.toJS() + const { token_id } = token + const ops = await api.getNftTokenOps({ + token_ids: [ token_id ], + from: 0, + limit: 500 + }) + this.setState({ + ops: ops[token_id] + }) + } catch (err) { + console.error('loadOps', err) + } + } + + async loadBets() { + try { + const { nft_token, } = this.props + const token = nft_token.toJS() + const { token_id } = token + const bets = await api.getNftOrdersAsync({ + select_token_ids: [ token_id ], + limit: 100, + type: 'buying' + }) + this.setState({ + bets + }) + } catch (err) { + console.error('loadBets', err) + } } async componentDidMount() { if (this.props.nft_token) { await this.loadOps() + await this.loadBets() } } async componentDidUpdate(prevProps) { if (this.props.nft_token && !prevProps.nft_token) { await this.loadOps() + await this.loadBets() } } @@ -122,6 +147,23 @@ class NFTTokenPage extends Component { }) } + showPlaceBet = (e) => { + e.preventDefault() + this.setState({ + showPlaceBet: true, + }) + } + + hidePlaceBet = () => { + this.setState({ + showPlaceBet: false, + }) + } + + isBets = () => { + return window.location.hash === '#bets' + } + _renderOp = (trx, i) => { const [ opType, op ] = trx.op @@ -173,6 +215,15 @@ class NFTTokenPage extends Component { {tt('nft_token_page_jsx.bought')} {!price.eq(0) ? (tt('nft_token_page_jsx.selled2m') + price.floatString) : ''} + } else if (op.token_id) { + content =
+ {accLink(op.buyer)} + {tt('nft_token_page_jsx.placed_bet')} + {tt('nft_token_page_jsx.selled2m')} + {price.floatString} +
+ } else { + return null } } return @@ -196,6 +247,49 @@ class NFTTokenPage extends Component { } + _renderBets = (token, isMy) => { + let bets + if (isMy) { + const betObjs = this.state.bets + if (betObjs && betObjs.length) { + const sellToken = async (e, bet) => { + e.preventDefault() + await this.props.sellToken(bet, this.props.currentUser, () => { + this.props.fetchState() + }, (err) => { + if (!err || err.toString() === 'Canceled') return + console.error(err) + alert(err.toString()) + }) + } + + const rows = [] + for (let i = 0; i < betObjs.length; ++i) { + const bet = betObjs[i] + rows.push( + {Asset(bet.price).floatString} + + + ) + } + bets = + {rows} +
+ } else { + return null + } + + const opened = this.isBets() + + bets = + {bets} + + } + return bets + } + render() { if (!this.props.nft_token_loaded) { return @@ -261,6 +355,31 @@ class NFTTokenPage extends Component { const isMy = currentUser && currentUser.get('username') === token.owner + let my_bet = token.my_bet ? Asset(token.my_bet.price) : null + if (my_bet) { + const asset = assets[my_bet.symbol] + let imageUrl + if (asset) { + imageUrl = getAssetMeta(asset).image_url + } + const cancelBet = (e) => { + this.props.cancelBet(e, token.my_bet, () => { + this.props.fetchState() + }, (err) => { + if (!err || err.toString() === 'Canceled') return + console.error(err) + alert(err.toString()) + }) + } + my_bet =
+ {imageUrl && {''}} + {tt('nft_tokens_jsx.you_bet_is') + my_bet.floatString} + +
+ } + return
@@ -297,6 +416,7 @@ class NFTTokenPage extends Component { {this._renderOps(ops)} + {this._renderBets(token, isMy)} {!description ?
: null} {isMy ?
@@ -305,7 +425,7 @@ class NFTTokenPage extends Component { {tt('nft_tokens_jsx.cancel')} } - {!selling && } {!selling && } + {!my_bet && } {!selling && msgsHost() && {tt('nft_token_page_jsx.msg')}}
} + {my_bet}
@@ -343,6 +467,15 @@ class NFTTokenPage extends Component { refetch={this.props.fetchState} /> + + + + } } @@ -425,6 +558,41 @@ module.exports = { successCallback, errorCallback })) + }, + cancelBet: (e, order, successCallback, errorCallback) => { + e.preventDefault() + + const operation = { + owner: order.owner, + order_id: order.order_id + } + + dispatch(transaction.actions.broadcastOperation({ + type: 'nft_cancel_order', + confirm: tt('g.are_you_sure'), + username: order.owner, + operation, + successCallback, + errorCallback + })) + }, + sellToken: (bet, currentUser, successCallback, errorCallback) => { + const username = currentUser.get('username') + const operation = { + seller: username, + token_id: bet.token_id, + buyer: bet.owner, + order_id: bet.order_id, + price: bet.price + } + + dispatch(transaction.actions.broadcastOperation({ + type: 'nft_sell', + username, + operation, + successCallback, + errorCallback + })) } }) )(NFTTokenPage) diff --git a/app/components/pages/nft/NFTTokenPage.scss b/app/components/pages/nft/NFTTokenPage.scss index f85c008..0f6e488 100644 --- a/app/components/pages/nft/NFTTokenPage.scss +++ b/app/components/pages/nft/NFTTokenPage.scss @@ -12,6 +12,16 @@ display: flex; width: 100%; } + .my_bet { + padding-left: 5px; + padding-top: 5px; + + span { + vertical-align: middle; + margin-left: 2px; + margin-right: 2px; + } + } img { max-width: 320px; diff --git a/app/locales/en.json b/app/locales/en.json index 924efce..7e094fc 100644 --- a/app/locales/en.json +++ b/app/locales/en.json @@ -778,13 +778,17 @@ "sort_name": "By collection name", "cancel": "Cancel", "cancel_hint": "Cancel selling", + "place_bet": "Place a bet", "selling_for": "Selling for ", + "you_bet_is": "Your bet is ", + "has_bets": "Bets", "buy": "Buy", "buy2": "Buy for ", "buy_confirm": "Are you sure you want to buy ", "buy_confirm2": " for ", "your_token": "Your token", - "owner": "Owner" + "owner": "Owner", + "bets": "Bets" }, "all_nft_page_jsx": { "title": "All NFT on the Golos", @@ -803,6 +807,9 @@ "selled2": " selled token to ", "selled2m": " for ", "bought": " bought token ", + "placed_bet": " placed bet", + "canceled_bet": " canceled bet", + "on": " on ", "owner_is": "Owner is ", "burnt": "This NFT-токен was existing sometime.", "burnt2": "But it was burnt.", diff --git a/app/locales/ru-RU.json b/app/locales/ru-RU.json index 9d9dcd7..93ec882 100644 --- a/app/locales/ru-RU.json +++ b/app/locales/ru-RU.json @@ -1132,13 +1132,17 @@ "sort_name": "По имени коллекции", "cancel": "Отменить", "cancel_hint": "Отменить продажу", + "place_bet": "Сделать ставку", "selling_for": "Продается за ", + "you_bet_is": "Ваша ставка - ", + "has_bets": "Ставки", "buy": "Купить", "buy2": "Купить за ", "buy_confirm": "Вы уверены, что хотите купить ", "buy_confirm2": " за ", "your_token": "Ваш токен", - "owner": "Владелец" + "owner": "Владелец", + "bets": "Ставки" }, "all_nft_page_jsx": { "title": "Все NFT на Golos", @@ -1157,6 +1161,9 @@ "selled2": " продал токен ", "selled2m": " за ", "bought": " купил токен", + "placed_bet": " сделал ставку", + "canceled_bet": " отменил ставку", + "on": " на ", "owner_is": "Владелец - ", "burnt": "Этот NFT-токен существовал когда-то.", "burnt2": "Но он был сожжен.", diff --git a/app/redux/FetchDataSaga.js b/app/redux/FetchDataSaga.js index eb463d0..7eb9a54 100644 --- a/app/redux/FetchDataSaga.js +++ b/app/redux/FetchDataSaga.js @@ -13,6 +13,7 @@ import { reveseTag, getFilterTags } from 'app/utils/tags'; import { CATEGORIES, SELECT_TAGS_KEY, DEBT_TOKEN_SHORT, LIQUID_TICKER } from 'app/client_config'; import { getAllPairs } from 'app/utils/market/utils' import { parseNFTImage, NFTImageStub } from 'app/utils/NFTUtils' +import session from 'app/utils/session' function* fillNftCollectionImages(nft_collections) { const noImgColls = {} @@ -54,6 +55,35 @@ function* loadNftAssets(nft_assets, syms) { } } +function* fillNftTokenOrders(select_token_ids, tokens_by_id, onlyMy = true) { + try { + if (!select_token_ids.length) return + + const acc = session.load().currentName + + if (acc) { + const nft_orders = yield call([api, api.getNftOrdersAsync], { + select_token_ids, + owner: onlyMy ? acc : undefined, + limit: onlyMy ? 100 : 1, + type: 'buying' + }) + for (const order of nft_orders) { + const { token_id } = order + const token = tokens_by_id[token_id] + if (!token) continue + if (onlyMy) { + token.my_bet = order + } else { + token.has_bets = true + } + } + } + } catch (err) { + console.error(err) + } +} + export function* fetchDataWatches () { yield fork(watchLocationChange); yield fork(watchDataRequests); @@ -219,21 +249,47 @@ export function* fetchState(location_change_action) { state.witnesses[uname] = yield call([api, api.getWitnessByAccountAsync], uname) break case 'nft-history': - const nftHistory = yield call([api, api.getAccountHistoryAsync], uname, -1, 1000, {select_ops: ['nft_token', 'nft_transfer', 'nft_token_sold']}) + const nftHistory = yield call([api, api.getAccountHistoryAsync], uname, -1, 1000, {select_ops: ['nft_token', 'nft_transfer', 'nft_buy', 'nft_cancel_order', 'nft_token_sold']}) account.nft_history = [] state.cprops = yield call([api, api.getChainPropertiesAsync]) const nft_token_ids = new Set() + const addTokenId = (operation) => { + const { token_id } = operation[1].op[1] + if (token_id !== 0) nft_token_ids.add(token_id) + } + const pushHistory = (operation) => { + state.accounts[uname].nft_history.push(operation) + } + + const nft_order_tokens = {} + nftHistory.forEach(operation => { switch (operation[1].op[0]) { case 'nft_token': case 'nft_transfer': + case 'nft_buy': { + const { token_id, order_id, name } = operation[1].op[1] + if (!name || !token_id) break + addTokenId(operation) + pushHistory(operation) + nft_order_tokens[order_id] = token_id + break + } + case 'nft_cancel_order': { + const { order_id } = operation[1].op[1] + const token_id = nft_order_tokens[order_id] + if (token_id) { + operation[1].op[1].token_id = token_id + pushHistory(operation) + } + break + } case 'nft_token_sold': - const { token_id } = operation[1].op[1] - if (token_id !== 0) nft_token_ids.add(token_id) - state.accounts[uname].nft_history.push(operation) + addTokenId(operation) + pushHistory(operation) break default: @@ -308,6 +364,20 @@ export function* fetchState(location_change_action) { state.nft_token = state.nft_token[0] state.nft_token_loaded = true + const select_token_ids = [] + const tokens_by_id = {} + + try { + if (state.nft_token) { + select_token_ids.push(state.nft_token.token_id) + tokens_by_id[state.nft_token.token_id] = state.nft_token + } + + yield fillNftTokenOrders(select_token_ids, tokens_by_id) + } catch (err) { + console.error(err) + } + try { const syms = new Set() @@ -318,6 +388,10 @@ export function* fetchState(location_change_action) { const price = Asset(state.nft_token.order.price) syms.add(price.symbol) } + if (state.nft_token.my_bet) { + const price = Asset(state.nft_token.my_bet.price) + syms.add(price.symbol) + } } state.nft_assets = {} @@ -555,6 +629,8 @@ export function* fetchNftTokens({ payload: { account, start_token_id, sort, reve } let nft_assets + const select_token_ids = [] + const tokens_by_id = {} try { const syms = new Set() @@ -566,6 +642,9 @@ export function* fetchNftTokens({ payload: { account, start_token_id, sort, reve const price = Asset(no.order.price) syms.add(price.symbol) } + + select_token_ids.push(no.token_id) + tokens_by_id[no.token_id] = no } nft_assets = {} @@ -574,6 +653,8 @@ export function* fetchNftTokens({ payload: { account, start_token_id, sort, reve console.error(err) } + yield fillNftTokenOrders(select_token_ids, tokens_by_id, false) + yield put(GlobalReducer.actions.receiveNftTokens({nft_tokens, start_token_id, next_from, nft_assets})) } catch (err) { console.error('fetchNftTokens', err) diff --git a/app/redux/GlobalReducer.js b/app/redux/GlobalReducer.js index e37e2fa..9c4e3c3 100644 --- a/app/redux/GlobalReducer.js +++ b/app/redux/GlobalReducer.js @@ -105,6 +105,8 @@ export default createModule({ let res = state if (res.has('nft_collections')) res = res.delete('nft_collections') + if (res.has('nft_token')) + res = res.deleteIn(['nft_token', 'my_bet']) res = res.mergeDeep(payload) if (!payload.has('nft_tokens')) { if (!window.location.pathname.endsWith('/nft-tokens')) {