Skip to content

Commit

Permalink
HF 30 - NFT auction
Browse files Browse the repository at this point in the history
  • Loading branch information
1aerostorm committed Jan 16, 2024
1 parent f0e8615 commit cad52b5
Show file tree
Hide file tree
Showing 16 changed files with 630 additions and 38 deletions.
1 change: 1 addition & 0 deletions app/components/all.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down
14 changes: 14 additions & 0 deletions app/components/cards/TransferHistoryRow.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
6 changes: 6 additions & 0 deletions app/components/elements/Expandable.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ class Expandable extends Component {
opened: false,
};

componentDidMount() {
this.setState({
opened: !!this.props.opened
})
}

onToggleExpander = () => {
this.setState({
opened: !this.state.opened,
Expand Down
22 changes: 9 additions & 13 deletions app/components/elements/forms/AmountAssetField.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions app/components/elements/forms/AmountField.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 <input type='text' value={value.amountStr} placeholder={placeholder}
{...rest} onChange={(e) => 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) {
Expand Down
13 changes: 11 additions & 2 deletions app/components/elements/nft/NFTTokenItem.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand All @@ -137,7 +143,7 @@ class NFTTokenItem extends Component {
<Icon name='new/more' size='0_95x' />
</DropdownMenu> : null}
{isMy && !selling && <button className='button slim float-right' onClick={e => this.props.showSell(e, tokenIdx)}>{tt('g.sell')}</button>}
{isMy && selling && <button className='button slim alert hollow float-right' title={tt('nft_tokens_jsx.cancel_hint')}
{isMy && selling && <button className='button slim alert hollow noborder float-right' title={tt('nft_tokens_jsx.cancel_hint')}
onClick={e => this.cancelOrder(e, tokenIdx)}>
{tt('nft_tokens_jsx.cancel')}</button>}
{!isMy && selling && <button className='button slim float-right' title={tt('nft_tokens_jsx.buy2') + price.floatString}
Expand All @@ -148,7 +154,7 @@ class NFTTokenItem extends Component {
buttons = <div>
{!isMy && <React.Fragment>&nbsp;</React.Fragment>}
{isMy && <button className='button slim' onClick={e => this.props.showSell(e, tokenIdx)}>{tt('g.sell')}</button>}
{isMy && <button className='button slim hollow' onClick={e => this.props.showTransfer(e, tokenIdx)}>{tt('g.transfer')}</button>}
{isMy && <button className='button slim hollow noborder' onClick={e => this.props.showTransfer(e, tokenIdx)}>{tt('g.transfer')}</button>}
{kebabItems.length > 1 ? <DropdownMenu className='float-right' el='div' items={kebabItems}>
<Icon name='new/more' size='0_95x' />
</DropdownMenu> : null}
Expand All @@ -164,6 +170,9 @@ class NFTTokenItem extends Component {
{!isMy && <Link to={'/@' + token.owner} className='token-owner' title={tt('nft_tokens_jsx.owner')}>
{'@' + token.owner}
</Link>}
{token.has_bets && <a href={link + '#bets'} target='_blank' rel='noopener noreferrer' className='token-owner'>
{tt('nft_tokens_jsx.has_bets')}
</a>}
<div>
<h5 className='token-title'>{data.title}</h5>
<span className='token-coll secondary'>
Expand Down
2 changes: 1 addition & 1 deletion app/components/elements/nft/NFTTokenItem.scss
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
.button:hover {
background-color: #016aad !important;
}
.button.hollow {
.button.hollow.noborder {
border: 0px;
}
.button.hollow:hover {
Expand Down
257 changes: 257 additions & 0 deletions app/components/modules/nft/NFTPlaceBet.jsx
Original file line number Diff line number Diff line change
@@ -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 ? <span className='submitter'>
<LoadingIndicator type='circle' />
</span> : null
}

render() {
const { assets } = this.state

if (this.doNotRender || !assets) {
return <LoadingIndicator type='circle' />
}

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 <div className='NFTPlaceBet'>
<CloseButton onClick={onClose} />
<h4>{tt('nft_tokens_jsx.place_bet')}</h4>
<div style={{ marginBottom: '0.5rem' }}>
<NFTSmallIcon image={image} />
<span style={{ display: 'inline-block', marginTop: '13px', marginLeft: '0.5rem' }}>{data.title}</span>
</div>
<Formik
initialValues={this.state.order}
enableReinitialize={true}
validate={this.validate}
validateOnMount={true}
onSubmit={this._onSubmit}
>
{({
handleSubmit, isValid, values, errors, touched, setFieldValue, handleChange,
}) => {
return (
<Form onMouseUp={this.onMouseUp}>
<div>
{tt('g.price')}
</div>
<div className='row'>
<div className='column small-12'>
<div className='input-group' style={{marginBottom: 5}}>
<AmountField name='price' autoFocus />
<span className="input-group-label" style={{paddingLeft: 0, paddingRight: 0}}>
<AmountAssetField amountField='price' setFieldValue={setFieldValue} values={values} assets={assets}
onChange={this.onAssetChange}/>
</span>
</div>
{errors.price && <div className='error'>{errors.price}</div>}
</div>
</div>
{currentBalance && <AssetBalance balanceValue={currentBalance.floatString} />}
{(errorMessage && errorMessage !== 'Canceled') ? <div className='row'>
<div className='column small-12'>
<div className='error' style={{marginBottom:'0px'}}>{errorMessage}</div>
</div>
</div> : null}
<div className='row' style={{ marginTop: '0.5rem' }}>
<div className='column small-8'>
<button type='submit' disabled={!isValid || submitting} className='button'>
{tt('nft_tokens_jsx.place_bet')}
</button>
<button type='button' disabled={submitting} className='button hollow'
onMouseDown={this.onCancelMouseDown} onMouseUp={this.onCancelMouseUp}>
{tt('g.cancel')}
</button>
{this._renderSubmittingIndicator()}
</div>
</div>
</Form>
)}}</Formik>
</div>
}
}

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)
Loading

0 comments on commit cad52b5

Please sign in to comment.