mirror of https://github.com/dap-ps/discover.git
cache + withdraw
This commit is contained in:
parent
554e29a92e
commit
252d9c055f
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"trailingComma": "all",
|
||||
"semi": false,
|
||||
"singleQuote": true
|
||||
}
|
|
@ -31,7 +31,8 @@
|
|||
"redux-thunk": "^2.3.0",
|
||||
"reselect": "^4.0.0",
|
||||
"web3-utils": "^1.0.0-beta.35",
|
||||
"webpack": "4.28.3"
|
||||
"webpack": "4.28.3",
|
||||
"idb": "4.0.3"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
|
|
|
@ -53,6 +53,19 @@ class DiscoverService extends BlockchainService {
|
|||
}
|
||||
}
|
||||
|
||||
async getDAppByIndexWithMetadata(index) {
|
||||
try {
|
||||
const dapp = await DiscoverContract.methods
|
||||
.dapps(index)
|
||||
.call({ from: this.sharedContext.account })
|
||||
|
||||
dapp.metadata = await ipfsSDK.retrieveDAppMetadataByHash(dapp.metadata)
|
||||
return dapp
|
||||
} catch (error) {
|
||||
throw new Error(`Error fetching dapps. Details: ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
async getDAppById(id) {
|
||||
let dapp
|
||||
try {
|
||||
|
|
|
@ -3,20 +3,60 @@ import PropTypes from 'prop-types'
|
|||
import { DappListModel } from '../../utils/models'
|
||||
import DappListItem from '../DappListItem'
|
||||
|
||||
const DappList = props => {
|
||||
const { dapps, isRanked, showActionButtons } = props
|
||||
return (
|
||||
dapps &&
|
||||
dapps.map((dapp, i) => (
|
||||
<DappListItem
|
||||
dapp={dapp}
|
||||
key={dapp.name}
|
||||
isRanked={isRanked}
|
||||
position={i + 1}
|
||||
showActionButtons={showActionButtons}
|
||||
/>
|
||||
))
|
||||
)
|
||||
class DappList extends React.Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {'dappIdsMap': new Set(), 'mounted': false}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { dapps } = this.props
|
||||
const { dappIdsMap } = this.state
|
||||
dapps.forEach(dapp => dappIdsMap.add(dapp.id))
|
||||
this.setState({ dappIdsMap, mounted: true})
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
const { dapps } = this.props
|
||||
const { dappIdsMap } = this.state
|
||||
|
||||
let update = false
|
||||
for (let i = 0; i < dapps.length; i += 1) {
|
||||
if (dappIdsMap.has(dapps[i].id) === false) update = true
|
||||
}
|
||||
if (!update) return
|
||||
|
||||
for (let i = 0; i < dapps.length; i += 1)
|
||||
dappIdsMap.add(dapps[i].id)
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
requestAnimationFrame(() => {
|
||||
this.setState({ dappIdsMap })
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
animate() {
|
||||
|
||||
}
|
||||
|
||||
render() {
|
||||
const { dapps, isRanked, showActionButtons } = this.props
|
||||
const { dappIdsMap, mounted } = this.state
|
||||
return (
|
||||
dapps &&
|
||||
dapps.map((dapp, i) => (
|
||||
<DappListItem
|
||||
dapp={dapp}
|
||||
key={dapp.id}
|
||||
isRanked={isRanked}
|
||||
position={i + 1}
|
||||
visible={!mounted || dappIdsMap.has(dapp.id)}
|
||||
showActionButtons={showActionButtons}
|
||||
/>
|
||||
))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
DappList.defaultProps = {
|
||||
|
|
|
@ -8,8 +8,6 @@ import {
|
|||
import { toggleProfileModalAction } from '../../../modules/Profile/Profile.reducer'
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
onClickUpVote: () => dispatch(showUpVoteAction()),
|
||||
onClickDownVote: () => dispatch(showDownVoteAction()),
|
||||
onClickUpVote: dapp => {
|
||||
dispatch(showUpVoteAction(dapp))
|
||||
dispatch(fetchVoteRatingAction(dapp, true, 0))
|
||||
|
|
|
@ -15,12 +15,12 @@ const DappListItem = props => {
|
|||
onClickDownVote,
|
||||
isRanked,
|
||||
position,
|
||||
category,
|
||||
visible,
|
||||
showActionButtons,
|
||||
onToggleProfileModal,
|
||||
} = props
|
||||
|
||||
const { name, description, url, image } = dapp
|
||||
const { name, desc, url, image } = dapp
|
||||
|
||||
const handleUpVote = () => {
|
||||
onClickUpVote(dapp)
|
||||
|
@ -31,7 +31,7 @@ const DappListItem = props => {
|
|||
}
|
||||
|
||||
return (
|
||||
<div className={isRanked ? styles.rankedListItem : styles.listItem}>
|
||||
<div className={`${styles.dappListItem} ${isRanked ? styles.rankedListItem : styles.listItem} ${visible ? '' : styles.transparent}`}>
|
||||
{isRanked && <div className={styles.position}>{position}</div>}
|
||||
<div
|
||||
className={styles.imgWrapper}
|
||||
|
@ -51,7 +51,7 @@ const DappListItem = props => {
|
|||
className={styles.description}
|
||||
style={{ WebkitBoxOrient: 'vertical' }}
|
||||
>
|
||||
{description}
|
||||
{desc}
|
||||
</p>
|
||||
</div>
|
||||
<a className={styles.url} href={url}>
|
||||
|
@ -84,12 +84,14 @@ const DappListItem = props => {
|
|||
DappListItem.defaultProps = {
|
||||
isRanked: false,
|
||||
showActionButtons: false,
|
||||
visible: true,
|
||||
}
|
||||
|
||||
DappListItem.propTypes = {
|
||||
dapp: PropTypes.shape(DappModel).isRequired,
|
||||
isRanked: PropTypes.bool,
|
||||
showActionButtons: PropTypes.bool,
|
||||
visible: PropTypes.bool,
|
||||
position: PropTypes.number.isRequired,
|
||||
onClickUpVote: PropTypes.func.isRequired,
|
||||
onClickDownVote: PropTypes.func.isRequired,
|
||||
|
|
|
@ -1,5 +1,14 @@
|
|||
@import '../../styles/variables';
|
||||
|
||||
.dappListItem {
|
||||
transition-property: opacity;
|
||||
transition-duration: 1s;
|
||||
}
|
||||
|
||||
.dappListItem.transparent {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.listItem {
|
||||
font-family: $font;
|
||||
background: $background;
|
||||
|
@ -7,6 +16,7 @@
|
|||
height: calculateRem(145);
|
||||
margin: 0 calculateRem(16) calculateRem(11) calculateRem(16);
|
||||
position: relative;
|
||||
|
||||
}
|
||||
|
||||
.rankedListItem {
|
||||
|
|
|
@ -0,0 +1,126 @@
|
|||
import * as Categories from './categories'
|
||||
|
||||
export default class DappModel {
|
||||
constructor() {
|
||||
// blockchain
|
||||
this.id = ''
|
||||
this.sntValue = 0
|
||||
|
||||
// metadata
|
||||
this.name = ''
|
||||
this.image = ''
|
||||
this.desc = ''
|
||||
this.category = ''
|
||||
this.dateAdded = 0
|
||||
}
|
||||
|
||||
clone() {
|
||||
return Object.assign(new DappModel(), this)
|
||||
}
|
||||
|
||||
static instanceFromBlockchainWithMetadata(source) {
|
||||
return Object.assign(new DappModel(), source.metadata, {
|
||||
id: source.id,
|
||||
sntValue: parseInt(source.effectiveBalance, 10),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const RECENTLY_ADDED_SIZE = 50
|
||||
const HIGHEST_RANKED_SIZE = 50
|
||||
|
||||
export class DappState {
|
||||
constructor() {
|
||||
this.loaded = true
|
||||
this.dapps = []
|
||||
this.dappsHightestRanked = null
|
||||
this.dappsRecentlyAdded = null
|
||||
this.categoryMap = new Map()
|
||||
this.categoryMap.set(Categories.EXCHANGES, null)
|
||||
this.categoryMap.set(Categories.MARKETPLACES, null)
|
||||
this.categoryMap.set(Categories.COLLECTIBLES, null)
|
||||
this.categoryMap.set(Categories.GAMES, null)
|
||||
this.categoryMap.set(Categories.SOCIAL_NETWORKS, null)
|
||||
this.categoryMap.set(Categories.UTILITIES, null)
|
||||
this.categoryMap.set(Categories.OTHER, null)
|
||||
}
|
||||
|
||||
clone() {
|
||||
const result = new DappState()
|
||||
result.dapps = [...this.dapps]
|
||||
return result
|
||||
}
|
||||
|
||||
creditDapp(dapp) {
|
||||
for (let i = 0; i < this.dapps.length; i += 1)
|
||||
if (this.dapps[i].id === dapp.id) return this.updateDapp(dapp)
|
||||
return this.addDapp(dapp)
|
||||
}
|
||||
|
||||
addDapp(dapp) {
|
||||
const result = new DappState()
|
||||
let pushed = false
|
||||
for (let i = 0; i < this.dapps.length; i += 1) {
|
||||
if (!pushed && dapp.sntValue > this.dapps[i].sntValue) {
|
||||
result.dapps.push(dapp)
|
||||
pushed = true
|
||||
}
|
||||
result.dapps.push(this.dapps[i].clone())
|
||||
}
|
||||
if (!pushed) result.dapps.push(dapp)
|
||||
return result
|
||||
}
|
||||
|
||||
addDapps(dapps) {
|
||||
const result = new DappState()
|
||||
result.dapps = this.dapps.concat(dapps)
|
||||
result.dapps.sort((a, b) => {
|
||||
return b.sntValue - a.sntValue
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
updateDapp(dapp) {
|
||||
const result = new DappState()
|
||||
for (let i = 0; i < this.dapps.length; i += 1) {
|
||||
if (dapp.id === this.dapps[i].id) result.dapps.push(dapp)
|
||||
else result.dapps.push(this.dapps[i].clone())
|
||||
}
|
||||
result.dapps.sort((a, b) => {
|
||||
return b.sntValue - a.sntValue
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
getDappsByCategory(category) {
|
||||
let filtered = this.categoryMap.get(category)
|
||||
if (filtered === null) {
|
||||
filtered = this.dapps.filter(dapp => dapp.category === category)
|
||||
this.categoryMap.set(category, filtered)
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
getHighestRanked() {
|
||||
if (this.dappsHightestRanked === null)
|
||||
this.dappsHightestRanked = this.dapps.slice(0, HIGHEST_RANKED_SIZE)
|
||||
return this.dappsHightestRanked
|
||||
}
|
||||
|
||||
getRecentlyAdded() {
|
||||
if (this.dappsRecentlyAdded === null) {
|
||||
this.dappsRecentlyAdded = [...this.dapps]
|
||||
.sort((a, b) => {
|
||||
return (
|
||||
new Date().getTime(b.dateAdded) - new Date(a.dateAdded).getTime()
|
||||
)
|
||||
})
|
||||
.slice(0, RECENTLY_ADDED_SIZE)
|
||||
}
|
||||
return this.dappsRecentlyAdded
|
||||
}
|
||||
}
|
||||
|
||||
const dappsInitialState = new DappState()
|
||||
dappsInitialState.loaded = false
|
||||
export { dappsInitialState }
|
|
@ -0,0 +1,35 @@
|
|||
import { openDB } from 'idb'
|
||||
import DappModel from './dapp'
|
||||
|
||||
const DB_NAME = 'status_discover'
|
||||
const DB_STORE_DAPPS = 'store_dapps'
|
||||
|
||||
function open() {
|
||||
return openDB(DB_NAME, 1, {
|
||||
upgrade(db) {
|
||||
console.log('on create')
|
||||
db.createObjectStore(DB_STORE_DAPPS, {
|
||||
keyPath: 'id',
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export default class Database {
|
||||
static async fetchAllDapps() {
|
||||
const result = []
|
||||
const db = await open()
|
||||
let cursor = await db.transaction(DB_STORE_DAPPS).store.openCursor()
|
||||
|
||||
while (cursor) {
|
||||
result.push(Object.assign(new DappModel(), cursor.value))
|
||||
cursor = await cursor.continue()
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
static async creditDapp(dapp) {
|
||||
const db = await open()
|
||||
await db.put(DB_STORE_DAPPS, dapp)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
const withdraw = {
|
||||
visible: false,
|
||||
dapp: null,
|
||||
sntValue: '0',
|
||||
}
|
||||
|
||||
export default withdraw
|
|
@ -9,6 +9,7 @@ import desktopMenu from '../../modules/DesktopMenu/DesktopMenu.reducer'
|
|||
import transactionStatus from '../../modules/TransactionStatus/TransactionStatus.recuder'
|
||||
import alert from '../../modules/Alert/Alert.reducer'
|
||||
import howToSubmit from '../../modules/HowToSubmit/HowToSubmit.reducer'
|
||||
import withdraw from '../../modules/Withdraw/Withdraw.reducer'
|
||||
|
||||
export default history =>
|
||||
combineReducers({
|
||||
|
@ -22,4 +23,5 @@ export default history =>
|
|||
transactionStatus,
|
||||
alert,
|
||||
howToSubmit,
|
||||
withdraw,
|
||||
})
|
||||
|
|
|
@ -4,9 +4,9 @@ export const DappModel = {
|
|||
name: PropTypes.string,
|
||||
url: PropTypes.string,
|
||||
image: PropTypes.string,
|
||||
description: PropTypes.string,
|
||||
desc: PropTypes.string,
|
||||
category: PropTypes.string,
|
||||
dateAdded: PropTypes.string,
|
||||
dateAdded: PropTypes.number,
|
||||
sntValue: PropTypes.number,
|
||||
categoryPosition: PropTypes.number,
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import Terms from '../Terms/Terms'
|
|||
import TransactionStatus from '../TransactionStatus'
|
||||
import Alert from '../Alert'
|
||||
import HowToSubmit from '../HowToSubmit'
|
||||
import Withdraw from '../Withdraw'
|
||||
|
||||
class Router extends React.Component {
|
||||
componentDidMount() {
|
||||
|
@ -27,13 +28,15 @@ class Router extends React.Component {
|
|||
<Route path="/all" component={Dapps} />
|
||||
<Route path="/recently-added" component={RecentlyAdded} />
|
||||
<Route path="/terms" component={Terms} />
|
||||
<Route path="/:dapp_name" component={Profile} />
|
||||
<Route path="/:dapp_name" component={Home} />
|
||||
</Switch>,
|
||||
<Vote key={2} />,
|
||||
<Submit key={3} />,
|
||||
<HowToSubmit key={4} />,
|
||||
<TransactionStatus key={5} />,
|
||||
<Alert key={6} />,
|
||||
<Route key={7} path="/:dapp_name" component={Profile} />,
|
||||
<Withdraw key={8} />
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +1,8 @@
|
|||
import { connect } from 'react-redux'
|
||||
import Dapps from './Dapps'
|
||||
// import selector from './Dapps.selector'
|
||||
import { fetchByCategoryAction } from './Dapps.reducer'
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
dappsCategoryMap: state.dapps.dappsCategoryMap,
|
||||
})
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
fetchByCategory: category => {
|
||||
dispatch(fetchByCategoryAction(category))
|
||||
},
|
||||
dappState: state.dapps,
|
||||
})
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
)(Dapps)
|
||||
export default connect(mapStateToProps)(Dapps)
|
||||
|
|
|
@ -5,6 +5,7 @@ import DappList from '../../common/components/DappList'
|
|||
import CategoryHeader from '../CategoryHeader'
|
||||
import styles from './Dapps.module.scss'
|
||||
import { headerElements, getYPosition } from './Dapps.utils'
|
||||
import { DappState } from '../../common/data/dapp';
|
||||
|
||||
class Dapps extends React.Component {
|
||||
static scanHeaderPositions() {
|
||||
|
@ -25,35 +26,15 @@ class Dapps extends React.Component {
|
|||
componentDidMount() {
|
||||
this.boundScroll = debounce(this.handleScroll.bind(this), 1)
|
||||
window.addEventListener('scroll', this.boundScroll)
|
||||
this.fetchDapps()
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.fetchDapps()
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener('scroll', this.boundScroll)
|
||||
}
|
||||
|
||||
onFetchByCategory(category) {
|
||||
const { fetchByCategory } = this.props
|
||||
fetchByCategory(category)
|
||||
}
|
||||
|
||||
getCategories() {
|
||||
const { dappsCategoryMap } = this.props
|
||||
return [...dappsCategoryMap.keys()]
|
||||
}
|
||||
|
||||
fetchDapps() {
|
||||
const { dappsCategoryMap, fetchByCategory } = this.props
|
||||
|
||||
dappsCategoryMap.forEach((dappState, category) => {
|
||||
if (dappState.canFetch() === false) return
|
||||
if (dappState.items.length >= 1) return
|
||||
fetchByCategory(category)
|
||||
})
|
||||
const { dappState } = this.props
|
||||
return [...dappState.categoryMap.keys()]
|
||||
}
|
||||
|
||||
handleScroll() {
|
||||
|
@ -89,7 +70,7 @@ class Dapps extends React.Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { dappsCategoryMap } = this.props
|
||||
const { dappState } = this.props
|
||||
const categories = this.getCategories()
|
||||
|
||||
return (
|
||||
|
@ -102,15 +83,7 @@ class Dapps extends React.Component {
|
|||
active={this.isCurrentCategory(category)}
|
||||
/>
|
||||
</div>
|
||||
<DappList dapps={dappsCategoryMap.get(category).items} />
|
||||
{dappsCategoryMap.get(category).canFetch() && (
|
||||
<div
|
||||
className={styles.loadMore}
|
||||
onClick={this.onFetchByCategory.bind(this, category)}
|
||||
>
|
||||
Load more dApps from {category}{' '}
|
||||
</div>
|
||||
)}
|
||||
<DappList dapps={dappState.getDappsByCategory(category)} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
@ -118,13 +91,8 @@ class Dapps extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
// Dapps.propTypes = {
|
||||
// categories: PropTypes.arrayOf(
|
||||
// PropTypes.shape({ category: PropTypes.string, dapps: DappListModel }),
|
||||
// ).isRequired,
|
||||
// }
|
||||
Dapps.propTypes = {
|
||||
dappsCategoryMap: PropTypes.instanceOf(Map).isRequired,
|
||||
dappState: PropTypes.instanceOf(DappState).isRequired,
|
||||
fetchByCategory: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
|
|
|
@ -3,14 +3,4 @@
|
|||
.list {
|
||||
margin-top: calculateRem(50);
|
||||
margin-bottom: calculateRem(20);
|
||||
}
|
||||
|
||||
.loadMore {
|
||||
color: $link-color;
|
||||
text-transform: uppercase;
|
||||
font-family: $font;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
margin-left: calculateRem(40 + 16 + 16);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
|
@ -1,165 +1,68 @@
|
|||
// import hardcodedDapps from '../../common/data/dapps'
|
||||
import * as Categories from '../../common/data/categories'
|
||||
import reducerUtil from '../../common/utils/reducer'
|
||||
import { showAlertAction } from '../Alert/Alert.reducer'
|
||||
import BlockchainSDK from '../../common/blockchain'
|
||||
import { TYPE_SUBMIT } from '../TransactionStatus/TransactionStatus.utilities'
|
||||
import DappModel, { dappsInitialState, DappState } from '../../common/data/dapp'
|
||||
import Database from '../../common/data/database';
|
||||
|
||||
const ON_FINISH_FETCH_ALL_DAPPS_ACTION =
|
||||
'DAPPS_ON_FINISH_FETCH_ALL_DAPPS_ACTION'
|
||||
|
||||
const ON_START_FETCH_HIGHEST_RANKED = 'DAPPS_ON_START_FETCH_HIGHEST_RANKED'
|
||||
const ON_FINISH_FETCH_HIGHEST_RANKED = 'DAPPS_ON_FINISH_FETCH_HIGHEST_RANKED'
|
||||
const ON_START_FETCH_RECENTLY_ADDED = 'DAPPS_ON_START_FETCH_RECENTLY_ADDED'
|
||||
const ON_FINISH_FETCH_RECENTLY_ADDED = 'DAPPS_ON_FINISH_FETCH_RECENTLY_ADDED'
|
||||
|
||||
const ON_START_FETCH_BY_CATEGORY = 'DAPPS_ON_START_FETCH_BY_CATEGORY'
|
||||
const ON_FINISH_FETCH_BY_CATEGORY = 'DAPPS_ON_FINISH_FETCH_BY_CATEGORY'
|
||||
|
||||
const ON_UPDATE_DAPPS = 'DAPPS_ON_UPDATE_DAPPS'
|
||||
const ON_UPDATE_DAPP_DATA = 'DAPPS_ON_UPDATE_DAPP_DATA'
|
||||
|
||||
const RECENTLY_ADDED_SIZE = 50
|
||||
const HIGHEST_RANKED_SIZE = 50
|
||||
|
||||
class DappsState {
|
||||
constructor() {
|
||||
this.items = []
|
||||
this.hasMore = true
|
||||
this.fetched = null
|
||||
}
|
||||
|
||||
canFetch() {
|
||||
return this.hasMore && this.fetched !== true
|
||||
}
|
||||
|
||||
setFetched(fetched) {
|
||||
this.fetched = fetched
|
||||
}
|
||||
|
||||
appendItems(items) {
|
||||
const availableNames = new Set()
|
||||
let addedItems = 0
|
||||
for (let i = 0; i < this.items.length; i += 1)
|
||||
availableNames.add(this.items[i].name)
|
||||
for (let i = 0; i < items.length; i += 1) {
|
||||
if (availableNames.has(items[i].name) === false) {
|
||||
addedItems += 1
|
||||
this.items.push(items[i])
|
||||
}
|
||||
}
|
||||
|
||||
this.hasMore = addedItems !== 0
|
||||
}
|
||||
|
||||
cloneWeakItems() {
|
||||
this.items = [...this.items]
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
export const onFinishFetchAllDappsAction = dapps => ({
|
||||
type: ON_FINISH_FETCH_ALL_DAPPS_ACTION,
|
||||
payload: dapps,
|
||||
export const onUpdateDappsAction = dappState => ({
|
||||
type: ON_UPDATE_DAPPS,
|
||||
payload: dappState,
|
||||
})
|
||||
|
||||
export const onStartFetchHighestRankedAction = () => ({
|
||||
type: ON_START_FETCH_HIGHEST_RANKED,
|
||||
payload: null,
|
||||
})
|
||||
|
||||
export const onFinishFetchHighestRankedAction = highestRanked => ({
|
||||
type: ON_FINISH_FETCH_HIGHEST_RANKED,
|
||||
payload: highestRanked,
|
||||
})
|
||||
|
||||
export const onStartFetchRecentlyAddedAction = () => ({
|
||||
type: ON_START_FETCH_RECENTLY_ADDED,
|
||||
payload: null,
|
||||
})
|
||||
|
||||
export const onFinishFetchRecentlyAddedAction = recentlyAdded => ({
|
||||
type: ON_FINISH_FETCH_RECENTLY_ADDED,
|
||||
payload: recentlyAdded,
|
||||
})
|
||||
|
||||
export const onStartFetchByCategoryAction = category => ({
|
||||
type: ON_START_FETCH_BY_CATEGORY,
|
||||
payload: category,
|
||||
})
|
||||
|
||||
export const onFinishFetchByCategoryAction = (category, dapps) => ({
|
||||
type: ON_FINISH_FETCH_BY_CATEGORY,
|
||||
payload: { category, dapps },
|
||||
})
|
||||
|
||||
const fetchAllDappsInState = async (dispatch, getState) => {
|
||||
const state = getState()
|
||||
const { transactionStatus } = state
|
||||
const stateDapps = state.dapps
|
||||
if (stateDapps.dapps === null) {
|
||||
try {
|
||||
const blockchain = await BlockchainSDK.getInstance()
|
||||
let dapps = await blockchain.DiscoverService.getDApps()
|
||||
dapps = dapps.map(dapp => {
|
||||
return Object.assign(dapp.metadata, {
|
||||
id: dapp.id,
|
||||
sntValue: parseInt(dapp.effectiveBalance, 10),
|
||||
})
|
||||
})
|
||||
dapps.sort((a, b) => {
|
||||
return b.sntValue - a.sntValue
|
||||
})
|
||||
if (transactionStatus.type === TYPE_SUBMIT) {
|
||||
for (let i = 0; i < dapps.length; i += 1) {
|
||||
if (dapps[i].id === transactionStatus.dappId) {
|
||||
dapps.splice(i, 1)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dispatch(onFinishFetchAllDappsAction(dapps))
|
||||
return dapps
|
||||
} catch (e) {
|
||||
dispatch(showAlertAction(e.message))
|
||||
dispatch(onFinishFetchAllDappsAction([]))
|
||||
return []
|
||||
}
|
||||
}
|
||||
return stateDapps.dapps
|
||||
}
|
||||
|
||||
export const fetchAllDappsAction = () => {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch(onStartFetchHighestRankedAction())
|
||||
dispatch(onStartFetchRecentlyAddedAction())
|
||||
const state = getState()
|
||||
let dappState = state.dapps
|
||||
|
||||
const dapps = await fetchAllDappsInState(dispatch, getState)
|
||||
const dapps = await Database.fetchAllDapps()
|
||||
dappState = dappState.addDapps(dapps)
|
||||
dispatch(onUpdateDappsAction(dappState))
|
||||
|
||||
const highestRanked = dapps.slice(0, HIGHEST_RANKED_SIZE)
|
||||
let recentlyAdded = [...dapps]
|
||||
recentlyAdded.sort((a, b) => {
|
||||
return new Date().getTime(b.dateAdded) - new Date(a.dateAdded).getTime()
|
||||
})
|
||||
recentlyAdded = recentlyAdded.slice(0, RECENTLY_ADDED_SIZE)
|
||||
try {
|
||||
const blockchain = await BlockchainSDK.getInstance()
|
||||
const discoverService = blockchain.DiscoverService
|
||||
const N = await discoverService.getDAppsCount()
|
||||
if (N === 0) {
|
||||
dispatch(onUpdateDappsAction(dappState.clone()))
|
||||
return
|
||||
}
|
||||
|
||||
dispatch(onFinishFetchHighestRankedAction(highestRanked))
|
||||
dispatch(onFinishFetchRecentlyAddedAction(recentlyAdded))
|
||||
}
|
||||
}
|
||||
|
||||
export const fetchByCategoryAction = category => {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch(onStartFetchByCategoryAction(category))
|
||||
|
||||
const dapps = await fetchAllDappsInState(dispatch, getState)
|
||||
const filteredByCategory = dapps.filter(dapp => dapp.category === category)
|
||||
const dappsCategoryState = getState().dapps.dappsCategoryMap.get(category)
|
||||
const from = dappsCategoryState.items.length
|
||||
const to = Math.min(from + 5, filteredByCategory.length)
|
||||
const dappsCategorySlice = filteredByCategory.slice(from, to)
|
||||
|
||||
dispatch(onFinishFetchByCategoryAction(category, dappsCategorySlice))
|
||||
const { transactionStatus } = state
|
||||
let dappSource = await discoverService.getDAppByIndexWithMetadata(0)
|
||||
let dappModel = DappModel.instanceFromBlockchainWithMetadata(dappSource)
|
||||
dappState = dappState.creditDapp(dappModel)
|
||||
if (
|
||||
dappModel.id !== transactionStatus.dappId ||
|
||||
transactionStatus.type !== TYPE_SUBMIT
|
||||
) {
|
||||
dispatch(onUpdateDappsAction(dappState))
|
||||
Database.creditDapp(dappModel)
|
||||
}
|
||||
for (let i = N - 1; i >= 1; i -= 1) {
|
||||
dappSource = await discoverService.getDAppByIndexWithMetadata(i)
|
||||
dappModel = DappModel.instanceFromBlockchainWithMetadata(dappSource)
|
||||
dappState = dappState.creditDapp(dappModel)
|
||||
if (
|
||||
dappModel.id !== transactionStatus.dappId ||
|
||||
transactionStatus.type !== TYPE_SUBMIT
|
||||
) {
|
||||
dispatch(onUpdateDappsAction(dappState))
|
||||
Database.creditDapp(dappModel)
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('error', e)
|
||||
// setTimeout(() => {
|
||||
// dispatch(showAlertAction(e.message))
|
||||
// }, 1000)
|
||||
// no need to show current blockchain errors, cache is used to there will be any data
|
||||
// dispatch(showAlertAction(e.message))
|
||||
dispatch(onUpdateDappsAction(dappState.clone()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -168,169 +71,18 @@ export const onUpdateDappDataAction = dapp => ({
|
|||
payload: dapp,
|
||||
})
|
||||
|
||||
const onFinishFetchAllDapps = (state, dapps) => {
|
||||
return Object.assign({}, state, { dapps })
|
||||
}
|
||||
|
||||
const onStartFetchHightestRanked = state => {
|
||||
return Object.assign({}, state, {
|
||||
highestRankedFetched: false,
|
||||
})
|
||||
}
|
||||
|
||||
const onFinishFetchHighestRanked = (state, payload) => {
|
||||
return Object.assign({}, state, {
|
||||
highestRanked: payload,
|
||||
highestRankedFetched: true,
|
||||
})
|
||||
}
|
||||
|
||||
const onStartFetchRecentlyAdded = state => {
|
||||
return Object.assign({}, state, {
|
||||
recentlyAddedFetched: false,
|
||||
})
|
||||
}
|
||||
|
||||
const onFinishFetchRecentlyAdded = (state, payload) => {
|
||||
return Object.assign({}, state, {
|
||||
recentlyAdded: payload,
|
||||
recentlyAddedFetched: true,
|
||||
})
|
||||
}
|
||||
|
||||
const onStartFetchByCategory = (state, payload) => {
|
||||
const dappsCategoryMap = new Map()
|
||||
state.dappsCategoryMap.forEach((dappState, category) => {
|
||||
dappsCategoryMap.set(category, dappState.cloneWeakItems())
|
||||
if (category === payload) dappState.setFetched(true)
|
||||
})
|
||||
return Object.assign({}, state, {
|
||||
dappsCategoryMap,
|
||||
})
|
||||
}
|
||||
|
||||
const onFinishFetchByCategory = (state, payload) => {
|
||||
const { category, dapps } = payload
|
||||
|
||||
const dappsCategoryMap = new Map()
|
||||
state.dappsCategoryMap.forEach((dappState, category_) => {
|
||||
dappsCategoryMap.set(category_, dappState)
|
||||
if (category_ === category) {
|
||||
dappState.setFetched(false)
|
||||
dappState.appendItems(dapps)
|
||||
}
|
||||
})
|
||||
return Object.assign({}, state, {
|
||||
dappsCategoryMap,
|
||||
})
|
||||
}
|
||||
|
||||
const insertDappIntoSortedArray = (source, dapp, cmp) => {
|
||||
for (let i = 0; i < source.length; i += 1) {
|
||||
if (cmp(source[i], dapp) === true) {
|
||||
source.splice(i, 0, dapp)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const onUpdateDappData = (state, dapp) => {
|
||||
const dappsCategoryMap = new Map()
|
||||
const { dapps } = state
|
||||
let { highestRanked, recentlyAdded } = state
|
||||
let update = false
|
||||
Database.creditDapp(dapp)
|
||||
return state.creditDapp(dapp)
|
||||
}
|
||||
|
||||
state.dappsCategoryMap.forEach((dappState, category_) => {
|
||||
dappsCategoryMap.set(category_, dappState.cloneWeakItems())
|
||||
})
|
||||
|
||||
for (let i = 0; i < dapps.length; i += 1) {
|
||||
if (dapps[i].id === dapp.id) {
|
||||
dapps[i] = dapp
|
||||
update = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (update === false) {
|
||||
insertDappIntoSortedArray(dapps, dapp, (target, dappItem) => {
|
||||
return target.sntValue < dappItem.sntValue
|
||||
})
|
||||
insertDappIntoSortedArray(highestRanked, dapp, (target, dappItem) => {
|
||||
return target.sntValue < dappItem.sntValue
|
||||
})
|
||||
highestRanked = state.highestRanked.splice(0, HIGHEST_RANKED_SIZE)
|
||||
insertDappIntoSortedArray(recentlyAdded, dapp, (target, dappItem) => {
|
||||
return (
|
||||
new Date().getTime(target.dateAdded) <
|
||||
new Date(dappItem.dateAdded).getTime()
|
||||
)
|
||||
})
|
||||
recentlyAdded = recentlyAdded.splice(0, RECENTLY_ADDED_SIZE)
|
||||
|
||||
const dappState = dappsCategoryMap.get(dapp.category)
|
||||
insertDappIntoSortedArray(dappState.items, dapp, (target, dappItem) => {
|
||||
return target.sntValue < dappItem.sntValue
|
||||
})
|
||||
} else {
|
||||
for (let i = 0; i < highestRanked.length; i += 1) {
|
||||
if (highestRanked[i].id === dapp.id) {
|
||||
highestRanked[i] = dapp
|
||||
break
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < recentlyAdded.length; i += 1) {
|
||||
if (recentlyAdded[i].id === dapp.id) {
|
||||
recentlyAdded[i] = dapp
|
||||
break
|
||||
}
|
||||
}
|
||||
dappsCategoryMap.forEach(dappState => {
|
||||
const dappStateRef = dappState
|
||||
for (let i = 0; i < dappStateRef.items.length; i += 1) {
|
||||
if (dappStateRef.items[i].id === dapp.id) {
|
||||
dappStateRef.items[i] = dapp
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return Object.assign({}, state, {
|
||||
dapps: [...dapps],
|
||||
highestRanked: [...highestRanked],
|
||||
recentlyAdded: [...recentlyAdded],
|
||||
dappsCategoryMap,
|
||||
})
|
||||
const onUpdateDapps = (state, dappState) => {
|
||||
return Object.assign(new DappState(), dappState)
|
||||
}
|
||||
|
||||
const map = {
|
||||
[ON_FINISH_FETCH_ALL_DAPPS_ACTION]: onFinishFetchAllDapps,
|
||||
[ON_START_FETCH_HIGHEST_RANKED]: onStartFetchHightestRanked,
|
||||
[ON_FINISH_FETCH_HIGHEST_RANKED]: onFinishFetchHighestRanked,
|
||||
[ON_START_FETCH_RECENTLY_ADDED]: onStartFetchRecentlyAdded,
|
||||
[ON_FINISH_FETCH_RECENTLY_ADDED]: onFinishFetchRecentlyAdded,
|
||||
[ON_START_FETCH_BY_CATEGORY]: onStartFetchByCategory,
|
||||
[ON_FINISH_FETCH_BY_CATEGORY]: onFinishFetchByCategory,
|
||||
[ON_UPDATE_DAPP_DATA]: onUpdateDappData,
|
||||
}
|
||||
|
||||
const dappsCategoryMap = new Map()
|
||||
dappsCategoryMap.set(Categories.EXCHANGES, new DappsState())
|
||||
dappsCategoryMap.set(Categories.MARKETPLACES, new DappsState())
|
||||
dappsCategoryMap.set(Categories.COLLECTIBLES, new DappsState())
|
||||
dappsCategoryMap.set(Categories.GAMES, new DappsState())
|
||||
dappsCategoryMap.set(Categories.SOCIAL_NETWORKS, new DappsState())
|
||||
dappsCategoryMap.set(Categories.UTILITIES, new DappsState())
|
||||
dappsCategoryMap.set(Categories.OTHER, new DappsState())
|
||||
|
||||
const dappsInitialState = {
|
||||
dapps: null,
|
||||
highestRanked: [],
|
||||
highestRankedFetched: null,
|
||||
recentlyAdded: [],
|
||||
recentlyAddedFetched: null,
|
||||
dappsCategoryMap,
|
||||
[ON_UPDATE_DAPPS]: onUpdateDapps,
|
||||
}
|
||||
|
||||
export default reducerUtil(map, dappsInitialState)
|
||||
|
|
|
@ -1,17 +1,8 @@
|
|||
import { connect } from 'react-redux'
|
||||
import Filtered from './Filtered'
|
||||
import { fetchByCategoryAction } from '../Dapps/Dapps.reducer'
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
dappsCategoryMap: state.dapps.dappsCategoryMap,
|
||||
})
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
fetchByCategory: category => {
|
||||
dispatch(fetchByCategoryAction(category))
|
||||
},
|
||||
dappState: state.dapps,
|
||||
})
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
)(Filtered)
|
||||
export default connect(mapStateToProps)(Filtered)
|
||||
|
|
|
@ -3,69 +3,18 @@ import PropTypes from 'prop-types'
|
|||
import CategorySelector from '../CategorySelector'
|
||||
import DappList from '../../common/components/DappList'
|
||||
import styles from './Filtered.module.scss'
|
||||
|
||||
const getScrollY =
|
||||
window.scrollY !== undefined
|
||||
? () => {
|
||||
return window.scrollY
|
||||
}
|
||||
: () => {
|
||||
return document.documentElement.scrollTop
|
||||
}
|
||||
import { DappState } from '../../common/data/dapp';
|
||||
|
||||
class Filtered extends React.Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.onScroll = this.onScroll.bind(this)
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.fetchDapps()
|
||||
document.addEventListener('scroll', this.onScroll)
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.fetchDapps()
|
||||
}
|
||||
|
||||
onScroll() {
|
||||
this.fetchDapps()
|
||||
}
|
||||
|
||||
getDappList() {
|
||||
const { dappsCategoryMap, match } = this.props
|
||||
const result =
|
||||
match !== undefined ? dappsCategoryMap.get(match.params.id).items : []
|
||||
return result
|
||||
}
|
||||
|
||||
fetchDapps() {
|
||||
const { dappsCategoryMap, match, fetchByCategory } = this.props
|
||||
if (match === undefined) return
|
||||
|
||||
const dappState = dappsCategoryMap.get(match.params.id)
|
||||
if (dappState.canFetch() === false) return
|
||||
|
||||
const root = document.getElementById('root')
|
||||
const bottom = window.innerHeight + getScrollY()
|
||||
const isNearEnd = bottom + window.innerHeight > root.offsetHeight
|
||||
|
||||
if (isNearEnd === false && dappState.items.length >= 10) return
|
||||
|
||||
fetchByCategory(match.params.id)
|
||||
}
|
||||
|
||||
render() {
|
||||
const { match } = this.props
|
||||
const result = this.getDappList()
|
||||
const { match, dappState } = this.props
|
||||
const category = match !== undefined ? match.params.id : undefined
|
||||
|
||||
return (
|
||||
<>
|
||||
<CategorySelector
|
||||
category={match !== undefined ? match.params.id : undefined}
|
||||
/>
|
||||
<CategorySelector category={category} />
|
||||
<div className={styles.list}>
|
||||
<DappList dapps={result} />
|
||||
<DappList dapps={dappState.getDappsByCategory(category)} />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
@ -77,8 +26,7 @@ Filtered.defaultProps = {
|
|||
}
|
||||
|
||||
Filtered.propTypes = {
|
||||
dappsCategoryMap: PropTypes.instanceOf(Map).isRequired,
|
||||
fetchByCategory: PropTypes.func.isRequired,
|
||||
dappState: PropTypes.instanceOf(DappState).isRequired,
|
||||
match: PropTypes.shape({
|
||||
params: PropTypes.shape({
|
||||
id: PropTypes.node,
|
||||
|
|
|
@ -2,7 +2,7 @@ import { connect } from 'react-redux'
|
|||
import HighestRanked from './HighestRanked'
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
dapps: state.dapps.highestRanked,
|
||||
dapps: state.dapps.getHighestRanked(),
|
||||
})
|
||||
|
||||
export default connect(mapStateToProps)(HighestRanked)
|
||||
|
|
|
@ -9,15 +9,15 @@
|
|||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
grid-auto-flow: row;
|
||||
grid-auto-columns: calc(90%);
|
||||
// grid-template-rows: 1fr 1fr 1fr;
|
||||
grid-template-rows: 1fr;
|
||||
overflow-x: scroll;
|
||||
overflow-y: hidden;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
|
||||
@media (min-width: $desktop) {
|
||||
grid-auto-flow: row;
|
||||
// grid-auto-flow: row;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
|
|
@ -18,8 +18,7 @@ class Home extends React.Component {
|
|||
|
||||
render() {
|
||||
const { dapps } = this.props
|
||||
const loaded =
|
||||
dapps.highestRankedFetched === true && dapps.recentlyAddedFetched === true
|
||||
const loaded = dapps.loaded
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -1,15 +1,18 @@
|
|||
import { connect } from 'react-redux'
|
||||
import { withRouter } from 'react-router-dom'
|
||||
|
||||
import Profile from './Profile'
|
||||
import { toggleProfileModalAction } from './Profile.reducer'
|
||||
import { showWithdrawAction } from '../Withdraw/Withdraw.reducer'
|
||||
|
||||
const mapStateToProps = state => state
|
||||
const mapStateToProps = state => ({ dappState: state.dapps })
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
openModal: dapp => dispatch(toggleProfileModalAction(dapp)),
|
||||
onClickWithdraw: dapp => dispatch(showWithdrawAction(dapp)),
|
||||
})
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
)(Profile)
|
||||
export default withRouter(
|
||||
connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
)(Profile),
|
||||
)
|
||||
|
|
|
@ -1,27 +1,22 @@
|
|||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import ReactImageFallback from 'react-image-fallback'
|
||||
import { push } from 'connected-react-router'
|
||||
import Modal from '../../common/components/Modal'
|
||||
import styles from './Profile.module.scss'
|
||||
import icon from '../../common/assets/images/icon.svg'
|
||||
import chat from '../../common/assets/images/chat.svg'
|
||||
|
||||
const DesktopScreen = props => {
|
||||
return <Modal visible={props.visible}>{props.children}</Modal>
|
||||
}
|
||||
|
||||
const MobileScreen = props => {
|
||||
return <>{props.children}</>
|
||||
}
|
||||
import { DappListModel } from '../../common/utils/models';
|
||||
import { DappState } from '../../common/data/dapp';
|
||||
|
||||
const ProfileContent = ({
|
||||
name,
|
||||
url,
|
||||
description,
|
||||
desc,
|
||||
image,
|
||||
position,
|
||||
category,
|
||||
highestRankedPosition,
|
||||
categoryPosition,
|
||||
onClickWithdraw,
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
|
@ -37,13 +32,13 @@ const ProfileContent = ({
|
|||
<div className={styles.information}>
|
||||
<h4 className={styles.header}>{name}</h4>
|
||||
<span className={styles.category}>{category}</span>
|
||||
<a href="#" target="_blank" className={styles.button}>
|
||||
<a href={url} target="_blank" className={styles.button}>
|
||||
Open
|
||||
</a>
|
||||
</div>
|
||||
<div className={styles.description}>
|
||||
<span className={styles.heading}>Description</span>
|
||||
<p>{description}</p>
|
||||
<p>{desc}</p>
|
||||
</div>
|
||||
<div className={styles.chat}>
|
||||
<ReactImageFallback
|
||||
|
@ -70,85 +65,101 @@ const ProfileContent = ({
|
|||
<span className={styles.heading}>Ranking</span>
|
||||
<div className={styles.rank}>
|
||||
<div className={styles.rank_position_1}>
|
||||
<span className={styles.rank_position_span}>{position}</span>
|
||||
<span className={styles.rank_position_span}>{categoryPosition}</span>
|
||||
</div>
|
||||
<span className={styles.rank_position_text}>
|
||||
<span>№</span>
|
||||
{position} in {category}
|
||||
{categoryPosition} in {category}
|
||||
</span>
|
||||
</div>
|
||||
<div className={styles.rank}>
|
||||
<span className={styles.rank_position_2}>
|
||||
<span className={styles.rank_position_span}>{position}</span>
|
||||
<span className={styles.rank_position_span}>{highestRankedPosition}</span>
|
||||
</span>
|
||||
<span className={styles.rank_position_text}>
|
||||
<span>№</span>
|
||||
{position} in highest ranked DApps
|
||||
{highestRankedPosition} in highest ranked DApps
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.actions}>
|
||||
<div className={styles.button}>
|
||||
Edit metadata
|
||||
</div>
|
||||
<div className={styles.button} onClick={onClickWithdraw}>
|
||||
Withdraw SNT
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
class Profile extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
screenSize: 0,
|
||||
visible: true,
|
||||
}
|
||||
this.onClickClose = this.onClickClose.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { innerWidth } = window
|
||||
const { match, openModal } = this.props
|
||||
const { params } = match
|
||||
const { dapp_name } = params
|
||||
if (innerWidth >= 1024) {
|
||||
openModal(dapp_name)
|
||||
}
|
||||
onClickClose() {
|
||||
window.history.back();
|
||||
}
|
||||
|
||||
this.setState({
|
||||
screenSize: innerWidth,
|
||||
visible: true,
|
||||
})
|
||||
onClickWithdraw(dapp) {
|
||||
const { onClickWithdraw } = this.props
|
||||
this.onClickClose()
|
||||
setTimeout(() => {
|
||||
onClickWithdraw(dapp)
|
||||
}, 1)
|
||||
}
|
||||
|
||||
render() {
|
||||
const { match, dapps } = this.props
|
||||
const { match, dappState } = this.props
|
||||
const { dapps } = dappState
|
||||
const { params } = match
|
||||
const { dapp_name } = params
|
||||
let dapp = null
|
||||
let highestRankedPosition = 1
|
||||
let categoryPosition = 1
|
||||
|
||||
const { screenSize, visible } = this.state
|
||||
if (
|
||||
dapps.highestRankedFetched === true &&
|
||||
dapps.recentlyAddedFetched === true
|
||||
) {
|
||||
const dapp = dapps.dapps.find(item =>
|
||||
item.name.toLowerCase() === dapp_name.toLowerCase() ? item : '',
|
||||
)
|
||||
return screenSize >= 1024 ? (
|
||||
<DesktopScreen visible={visible}>
|
||||
<ProfileContent {...dapp} />
|
||||
</DesktopScreen>
|
||||
) : (
|
||||
<MobileScreen {...this.props}>
|
||||
<ProfileContent {...dapp} />
|
||||
</MobileScreen>
|
||||
)
|
||||
for (let i = 0; i < dapps.length; i += 1) {
|
||||
const item = dapps[i]
|
||||
if (item.name.toLowerCase() === dapp_name.toLowerCase()) {
|
||||
highestRankedPosition = i + 1
|
||||
dapp = item
|
||||
break;
|
||||
}
|
||||
}
|
||||
return null
|
||||
|
||||
if (dapp !== null) {
|
||||
const dappsInCategory = dappState.getDappsByCategory(dapp.category)
|
||||
for (let i = 0; i < dappsInCategory.length; i += 1) {
|
||||
const item = dappsInCategory[i]
|
||||
if (item.id === dapp.id) {
|
||||
categoryPosition = i + 1
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
visible={dapp !== null}
|
||||
windowClassName={styles.modalWindow}
|
||||
onClickClose={this.onClickClose}>
|
||||
<ProfileContent {...dapp}
|
||||
highestRankedPosition={highestRankedPosition}
|
||||
categoryPosition={categoryPosition}
|
||||
onClickWithdraw={this.onClickWithdraw.bind(this, dapp)} />
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
}
|
||||
Profile.propTypes = {
|
||||
visible: PropTypes.bool,
|
||||
dapp: PropTypes.object,
|
||||
}
|
||||
|
||||
Profile.defaultProps = {
|
||||
// visible: false,
|
||||
Profile.propTypes = {
|
||||
dappState: PropTypes.instanceOf(DappState),
|
||||
onClickWithdraw: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
export default Profile
|
||||
|
|
|
@ -1,5 +1,15 @@
|
|||
@import '../../common/styles/variables';
|
||||
|
||||
.modalWindow {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@media (min-width: $desktop) {
|
||||
.modalWindow {
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
@ -142,3 +152,28 @@ a {
|
|||
.rank_position_text {
|
||||
margin-top: calculateRem(10);
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex; justify-content: center;
|
||||
border-bottom: 1px solid #eef2f5;
|
||||
box-shadow: inset 0px 1px 0px #eef2f5;
|
||||
padding: calculateRem(10) calculateRem(20);
|
||||
|
||||
.button {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.button:first-child {
|
||||
margin-right: calculateRem(20);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 356px) {
|
||||
|
||||
.actions {
|
||||
.button {
|
||||
font-size:13px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -2,40 +2,16 @@ import profileState from '../../common/data/profile'
|
|||
import reducerUtil from '../../common/utils/reducer'
|
||||
import { history } from '../../common/redux/store'
|
||||
|
||||
const DESKTOP_NAVIGATE = 'DESKTOP_NAVIGATE'
|
||||
const MOBILE_NAVIGATE = 'MOBILE_NAVIGATE'
|
||||
const PROFILE_NAVIGATE = 'PROFILE_NAVIGATE'
|
||||
|
||||
export const toggleProfileModalAction = dapp => {
|
||||
const { innerWidth } = window
|
||||
history.push(`/${dapp.trim()}`, dapp)
|
||||
if (innerWidth <= 1024) {
|
||||
return {
|
||||
type: MOBILE_NAVIGATE,
|
||||
payload: dapp,
|
||||
}
|
||||
}
|
||||
return {
|
||||
type: DESKTOP_NAVIGATE,
|
||||
payload: dapp,
|
||||
type: PROFILE_NAVIGATE,
|
||||
payload: null,
|
||||
}
|
||||
}
|
||||
|
||||
const toggleProfileModal = (state, payload) => {
|
||||
return Object.assign({}, state, {
|
||||
visible: !state.visible,
|
||||
dapp: payload,
|
||||
})
|
||||
}
|
||||
|
||||
const navigateProfile = (state, payload) => {
|
||||
return Object.assign({}, state, {
|
||||
visible: false,
|
||||
dapp: payload,
|
||||
})
|
||||
}
|
||||
const map = {
|
||||
[DESKTOP_NAVIGATE]: toggleProfileModal,
|
||||
[MOBILE_NAVIGATE]: navigateProfile,
|
||||
}
|
||||
const map = {}
|
||||
|
||||
export default reducerUtil(map, profileState)
|
||||
|
|
|
@ -2,7 +2,7 @@ import { connect } from 'react-redux'
|
|||
import RecentlyAdded from './RecentlyAdded'
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
dapps: state.dapps.recentlyAdded,
|
||||
dapps: state.dapps.getRecentlyAdded(),
|
||||
})
|
||||
|
||||
export default connect(mapStateToProps)(RecentlyAdded)
|
||||
|
|
|
@ -9,15 +9,15 @@
|
|||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
grid-auto-flow: row;
|
||||
grid-auto-columns: calc(90%);
|
||||
// grid-template-rows: 1fr 1fr 1fr;
|
||||
grid-template-rows: 1fr;
|
||||
overflow-x: scroll;
|
||||
overflow-y: hidden;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
|
||||
@media (min-width: $desktop) {
|
||||
grid-auto-flow: row;
|
||||
// grid-auto-flow: row;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ import {
|
|||
} from './Submit.reducer'
|
||||
|
||||
const mapStateToProps = state =>
|
||||
Object.assign(state.submit, { dapps: state.dapps.dapps })
|
||||
Object.assign(state.submit, { dappState: state.dapps })
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
onClickClose: () => dispatch(closeSubmitAction()),
|
||||
onInputName: name => dispatch(onInputNameAction(name)),
|
||||
|
|
|
@ -11,6 +11,7 @@ import icon from '../../common/assets/images/icon.svg'
|
|||
import sntIcon from '../../common/assets/images/SNT.svg'
|
||||
import 'rc-slider/assets/index.css'
|
||||
import 'rc-tooltip/assets/bootstrap.css'
|
||||
import { DappState } from '../../common/data/dapp';
|
||||
|
||||
const getCategoryName = category =>
|
||||
Categories.find(x => x.key === category).value
|
||||
|
@ -182,7 +183,7 @@ class Submit extends React.Component {
|
|||
|
||||
render() {
|
||||
const {
|
||||
dapps,
|
||||
dappState,
|
||||
visible_submit,
|
||||
visible_rating,
|
||||
onClickClose,
|
||||
|
@ -212,7 +213,7 @@ class Submit extends React.Component {
|
|||
let afterVoteCategoryPosition = null
|
||||
|
||||
if (visible_rating) {
|
||||
dappsByCategory = dapps.filter(dapp_ => dapp_.category === category)
|
||||
dappsByCategory = dappState.getDappsByCategory(category)
|
||||
|
||||
catPosition = dappsByCategory.length + 1
|
||||
if (sntValue !== '') {
|
||||
|
@ -470,6 +471,7 @@ Submit.propTypes = {
|
|||
onInputSntValue: PropTypes.func.isRequired,
|
||||
onClickTerms: PropTypes.func.isRequired,
|
||||
switchToRating: PropTypes.func.isRequired,
|
||||
dappState: PropTypes.instanceOf(DappState).isRequired,
|
||||
}
|
||||
|
||||
export default Submit
|
||||
|
|
|
@ -38,7 +38,10 @@ export const showSubmitActionAfterCheck = () => {
|
|||
export const showSubmitAction = () => {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState()
|
||||
if (state.transactionStatus.progress) {
|
||||
if (
|
||||
state.transactionStatus.progress &&
|
||||
state.transactionStatus.dappTx !== ''
|
||||
) {
|
||||
dispatch(
|
||||
showAlertAction(
|
||||
'There is an active transaction. Please wait for it to finish and then you could be able to create your Ðapp',
|
||||
|
|
|
@ -38,11 +38,11 @@ class TransactionStatus extends React.Component {
|
|||
<div className={styles.data}>
|
||||
<div className={styles.name}>
|
||||
<div className={styles.nameItself}>{dappName}</div>
|
||||
{!progress && (
|
||||
<div className={styles.close} onClick={hide}>
|
||||
+
|
||||
</div>
|
||||
)}
|
||||
{/* {!progress && ( */}
|
||||
<div className={styles.close} onClick={hide}>
|
||||
+
|
||||
</div>
|
||||
{/* )} */}
|
||||
</div>
|
||||
<div className={styles.info}>{txDesc}</div>
|
||||
{published && <div className={styles.status}>✓ Published</div>}
|
||||
|
|
|
@ -61,7 +61,7 @@ export const checkTransactionStatusAction = tx => {
|
|||
transacationStatus.dappId,
|
||||
)
|
||||
dapp = Object.assign(dapp.metadata, {
|
||||
id: dapp.id,
|
||||
id: transacationStatus.dappId,
|
||||
sntValue: parseInt(dapp.effectiveBalance, 10),
|
||||
})
|
||||
dispatch(onUpdateDappDataAction(dapp))
|
||||
|
@ -72,6 +72,7 @@ export const checkTransactionStatusAction = tx => {
|
|||
}),
|
||||
)
|
||||
}
|
||||
transacationStatus.setTransactionInfo('', '')
|
||||
break
|
||||
case 2:
|
||||
transacationStatus.setProgress(true)
|
||||
|
@ -88,7 +89,6 @@ export const checkTransactionStatusAction = tx => {
|
|||
const hide = state => {
|
||||
const transacationStatus = transactionStatusFetchedInstance()
|
||||
transacationStatus.setDappName('')
|
||||
transacationStatus.setProgress(false)
|
||||
transacationStatus.setType(TYPE_NONE)
|
||||
return Object.assign({}, state, transacationStatus)
|
||||
}
|
||||
|
|
|
@ -4,13 +4,14 @@ export const TYPE_NONE = 0
|
|||
export const TYPE_SUBMIT = 1
|
||||
export const TYPE_UPVOTE = 2
|
||||
export const TYPE_DOWNVOTE = 3
|
||||
export const TYPE_WITHDRAW = 4
|
||||
|
||||
class TransactionStatus {
|
||||
constructor() {
|
||||
this.dappId = ''
|
||||
this.dappTx = ''
|
||||
this.dappId = '' // responsible for background transaction check
|
||||
this.dappTx = '' // responsible for background transaction check
|
||||
this.txDesc = ''
|
||||
this.dappName = ''
|
||||
this.dappName = '' // responsible for UI visibility
|
||||
this.dappImg = ''
|
||||
this.type = TYPE_NONE
|
||||
this.progress = false
|
||||
|
|
|
@ -12,7 +12,7 @@ import {
|
|||
} from './Vote.reducer'
|
||||
|
||||
const mapStateToProps = state =>
|
||||
Object.assign(state.vote, { dapps: state.dapps.dapps })
|
||||
Object.assign(state.vote, { dappState: state.dapps })
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
onClickClose: () => dispatch(closeVoteAction()),
|
||||
onClickUpvote: () => dispatch(switchToUpvoteAction()),
|
||||
|
|
|
@ -8,6 +8,7 @@ import Categories from '../../common/utils/categories'
|
|||
import icon from '../../common/assets/images/icon.svg'
|
||||
import Modal from '../../common/components/Modal'
|
||||
import { DappModel } from '../../common/utils/models'
|
||||
import { DappState } from '../../common/data/dapp';
|
||||
|
||||
const getCategoryName = category =>
|
||||
Categories.find(x => x.key === category).value
|
||||
|
@ -59,7 +60,7 @@ class Vote extends Component {
|
|||
onClickClose,
|
||||
isUpvote,
|
||||
dapp,
|
||||
dapps,
|
||||
dappState,
|
||||
sntValue,
|
||||
afterVoteRating,
|
||||
} = this.props
|
||||
|
@ -71,9 +72,7 @@ class Vote extends Component {
|
|||
//const catPosition = dapp.categoryPosition
|
||||
// const upvoteSNTcost = currentSNTamount + parseInt(sntValue, 10)
|
||||
const currentSNTamount = dapp.sntValue
|
||||
const dappsByCategory = dapps.filter(
|
||||
dapp_ => dapp_.category === dapp.category,
|
||||
)
|
||||
const dappsByCategory = dappState.getDappsByCategory(dapp.category)
|
||||
|
||||
let catPosition = dappsByCategory.length
|
||||
for (let i = 0; i < dappsByCategory.length; ++i) {
|
||||
|
@ -240,6 +239,7 @@ Vote.propTypes = {
|
|||
fetchVoteRating: PropTypes.func.isRequired,
|
||||
upVote: PropTypes.func.isRequired,
|
||||
downVote: PropTypes.func.isRequired,
|
||||
dappState: PropTypes.instanceOf(DappState).isRequired,
|
||||
}
|
||||
|
||||
export default Vote
|
||||
|
|
|
@ -41,7 +41,10 @@ export const showDownVoteActionAfterCheck = dapp => {
|
|||
export const showUpVoteAction = dapp => {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState()
|
||||
if (state.transactionStatus.progress) {
|
||||
if (
|
||||
state.transactionStatus.progress &&
|
||||
state.transactionStatus.dappTx !== ''
|
||||
) {
|
||||
dispatch(
|
||||
showAlertAction(
|
||||
'There is an active transaction. Please wait for it to finish and then you could be able to vote',
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
import { connect } from 'react-redux'
|
||||
import { withRouter } from 'react-router-dom'
|
||||
import Withdraw from './Withdraw'
|
||||
|
||||
import { closeWithdrawAction, onInputSntValueAction, withdrawAction } from './Withdraw.reducer'
|
||||
|
||||
const mapStateToProps = state =>
|
||||
Object.assign(state.withdraw, { dappState: state.dapps })
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
onClickClose: () => dispatch(closeWithdrawAction()),
|
||||
onInputSntValue: sntValue => dispatch(onInputSntValueAction(sntValue)),
|
||||
onWithdraw: (dapp, sntValue) => dispatch(withdrawAction(dapp, sntValue)),
|
||||
})
|
||||
|
||||
export default withRouter(
|
||||
connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
)(Withdraw),
|
||||
)
|
|
@ -0,0 +1,161 @@
|
|||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import ReactImageFallback from 'react-image-fallback'
|
||||
import styles from './Withdraw.module.scss'
|
||||
import Modal from '../../common/components/Modal'
|
||||
import CategoriesUtils from '../Categories/Categories.utils'
|
||||
import Categories from '../../common/utils/categories'
|
||||
import icon from '../../common/assets/images/icon.svg'
|
||||
import sntIcon from '../../common/assets/images/SNT.svg'
|
||||
import 'rc-slider/assets/index.css'
|
||||
import 'rc-tooltip/assets/bootstrap.css'
|
||||
import DappModel from '../../common/data/dapp';
|
||||
|
||||
const getCategoryName = category =>
|
||||
Categories.find(x => x.key === category).value
|
||||
|
||||
class Withdraw extends React.Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.onWithdraw = this.onWithdraw.bind(this)
|
||||
this.handleSNTChange = this.handleSNTChange.bind(this)
|
||||
}
|
||||
|
||||
onWithdraw() {
|
||||
const { dapp, sntValue, onWithdraw } = this.props
|
||||
onWithdraw(dapp, parseInt(sntValue, 10))
|
||||
}
|
||||
|
||||
handleSNTChange(e) {
|
||||
const { dapp } = this.props
|
||||
const { value } = e.target
|
||||
if (value !== '' && /^[1-9][0-9]*$/.test(value) === false) return
|
||||
|
||||
const intValue = value === '' ? 0 : parseInt(value, 10)
|
||||
if (intValue > 1571296) return
|
||||
if (intValue > dapp.sntValue) return
|
||||
|
||||
const { onInputSntValue } = this.props
|
||||
onInputSntValue(value)
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
dappState,
|
||||
dapp,
|
||||
visible,
|
||||
onClickClose,
|
||||
sntValue,
|
||||
} = this.props
|
||||
|
||||
if (dapp === null)
|
||||
return <Modal visible={false} onClickClose={onClickClose} />
|
||||
|
||||
const currentSNTamount = dapp.sntValue
|
||||
const dappsByCategory = dappState.getDappsByCategory(dapp.category)
|
||||
const afterVoteRating = dapp.sntValue - (sntValue !== '' ? parseInt(sntValue, 10) : 0)
|
||||
|
||||
let catPosition = dappsByCategory.length
|
||||
for (let i = 0; i < dappsByCategory.length; ++i) {
|
||||
if (dapp.id === dappsByCategory[i].id) {
|
||||
catPosition = i + 1
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
let afterVoteCategoryPosition = 1
|
||||
for (let i = 0; i < dappsByCategory.length; ++i) {
|
||||
if (dappsByCategory[i].id === dapp.id) continue
|
||||
if (dappsByCategory[i].sntValue < afterVoteRating) break
|
||||
afterVoteCategoryPosition++
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
visible={visible && window.location.hash === '#withdraw'}
|
||||
onClickClose={onClickClose}
|
||||
windowClassName={styles.modalWindow}
|
||||
>
|
||||
<div className={styles.title}>Withdraw</div>
|
||||
<div className={styles.dapp}>
|
||||
<ReactImageFallback
|
||||
className={styles.image}
|
||||
src={dapp.image}
|
||||
fallbackImage={icon}
|
||||
alt="App icon"
|
||||
width={24}
|
||||
height={24}
|
||||
/>
|
||||
{dapp.name}
|
||||
</div>
|
||||
<div className={styles.items}>
|
||||
<div className={styles.itemRow}>
|
||||
<span className={styles.item}>
|
||||
<img src={sntIcon} alt="SNT" width="24" height="24" />
|
||||
{currentSNTamount.toLocaleString()}
|
||||
</span>
|
||||
{afterVoteRating !== null &&
|
||||
afterVoteRating !== currentSNTamount && (
|
||||
<span className={styles.redBadge}>
|
||||
{`${afterVoteRating.toLocaleString()} ↓`}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.itemRow}>
|
||||
<span className={styles.item}>
|
||||
<img
|
||||
src={CategoriesUtils(dapp.category)}
|
||||
alt={getCategoryName(dapp.category)}
|
||||
width="24"
|
||||
height="24"
|
||||
/>
|
||||
{`${getCategoryName(dapp.category)} №${catPosition}`}
|
||||
</span>
|
||||
{afterVoteCategoryPosition !== null &&
|
||||
afterVoteCategoryPosition !== catPosition && (
|
||||
<span className={styles.redBadge}>
|
||||
{`№${afterVoteCategoryPosition} ↓`}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className={`${styles.inputArea} ${styles.inputAreaBorder}`}>
|
||||
<input
|
||||
type="text"
|
||||
value={sntValue}
|
||||
onChange={this.handleSNTChange}
|
||||
style={{ width: `${21 * Math.max(1, sntValue.length)}px` }}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.footer}>
|
||||
<p className={styles.disclaimer}>
|
||||
SNT you spend to rank your DApp is locked in the store. You can
|
||||
earn back through votes, or withdraw, the majority of this SNT
|
||||
at any time.
|
||||
</p>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={!sntValue || sntValue === '0'}
|
||||
onClick={this.onWithdraw}>
|
||||
Withdraw
|
||||
</button>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Withdraw.defaultProps = {
|
||||
dapp: null,
|
||||
}
|
||||
|
||||
Withdraw.propTypes = {
|
||||
visible: PropTypes.bool.isRequired,
|
||||
dapp: PropTypes.instanceOf(DappModel),
|
||||
sntValue: PropTypes.string.isRequired,
|
||||
onClickClose: PropTypes.func.isRequired,
|
||||
onWithdraw: PropTypes.func.isRequired,
|
||||
onInputSntValue: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
export default Withdraw
|
|
@ -0,0 +1,412 @@
|
|||
@import '../../common/styles/variables';
|
||||
|
||||
.modalWindow {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-family: $font;
|
||||
}
|
||||
|
||||
@media (min-width: $desktop) {
|
||||
.modalWindow {
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.modalContentFullScreen {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
@media (min-width: $desktop) {
|
||||
.modalContentFullScreen {
|
||||
height: 512px;
|
||||
}
|
||||
}
|
||||
|
||||
.cntWithImgControl {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.title {
|
||||
line-height: 40px;
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
font-size: 13px;
|
||||
border-bottom: 1px solid #f7f7f7;
|
||||
}
|
||||
|
||||
.withImgControl {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.hasTransaction {
|
||||
text-align: center;
|
||||
padding: 16px 26px;
|
||||
}
|
||||
|
||||
.block {
|
||||
padding: 0 16px 16px 16px;
|
||||
|
||||
.labelRow {
|
||||
height: 42px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
span:nth-child(1) {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
span:nth-child(2) {
|
||||
color: #939ba1;
|
||||
font-size: 12px;
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.input {
|
||||
width: 100%;
|
||||
height: 52px;
|
||||
box-sizing: border-box;
|
||||
padding: 15px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
margin: 0;
|
||||
background: #eef2f5;
|
||||
}
|
||||
|
||||
textarea.input {
|
||||
height: 92px;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.imgCnt {
|
||||
width: 140px;
|
||||
height: 140px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
border-radius: 50%;
|
||||
border: 1px dashed #939ba1;
|
||||
margin: 16px auto;
|
||||
background: #eef2f5;
|
||||
|
||||
span {
|
||||
color: #939ba1;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.imgHolder {
|
||||
width: inherit;
|
||||
height: inherit;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
border-radius: inherit;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
input.uploader {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.imgInfo {
|
||||
line-height: 22px;
|
||||
color: #939ba1;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.categorySelector {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.categorySelector > div {
|
||||
width: 100%;
|
||||
top: auto;
|
||||
bottom: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.categorySelector > button {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
|
||||
transition-property: background;
|
||||
transition-duration: 0.4s;
|
||||
}
|
||||
|
||||
.categorySelectorEmpty > button {
|
||||
background: #939ba1;
|
||||
}
|
||||
}
|
||||
|
||||
.blockSubmit {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 40px 0 16px 0;
|
||||
|
||||
.terms {
|
||||
line-height: 22px;
|
||||
color: #939ba1;
|
||||
font-size: 15px;
|
||||
cursor: pointer;
|
||||
|
||||
a {
|
||||
color: $link-color;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.submitButton {
|
||||
background: $link-color;
|
||||
border-radius: 8px;
|
||||
color: #fff;
|
||||
margin: calculateRem(26) auto 0 auto;
|
||||
border: none;
|
||||
font-family: $font;
|
||||
padding: calculateRem(11) calculateRem(38);
|
||||
font-size: calculateRem(15);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.submitButton:disabled,
|
||||
.submitButton[disabled] {
|
||||
background: $text-color;
|
||||
}
|
||||
}
|
||||
|
||||
.imgControl {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
flex: 1 1 auto;
|
||||
|
||||
.imgCanvasCnt {
|
||||
width: 100%;
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.imgCanvas {
|
||||
width: 160px;
|
||||
height: 160px;
|
||||
border-radius: 50%;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.controls {
|
||||
width: 100%;
|
||||
border-top: 1px solid #eef2f5;
|
||||
|
||||
.slider {
|
||||
height: 74px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
* {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.minZoom {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
flex: 0 0 auto;
|
||||
border: 1px solid #939ba1;
|
||||
border-radius: 3px;
|
||||
margin: auto 30px auto 15px;
|
||||
}
|
||||
|
||||
.maxZoom {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
flex: 0 0 auto;
|
||||
border: 1px solid #939ba1;
|
||||
border-radius: 3px;
|
||||
margin: auto 11px auto 26px;
|
||||
}
|
||||
}
|
||||
|
||||
.actionsCnt {
|
||||
height: 64px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
box-sizing: border-box;
|
||||
padding: 0 16px;
|
||||
|
||||
.button {
|
||||
height: 44px;
|
||||
border-radius: 8px;
|
||||
border: none;
|
||||
border-radius: 22px;
|
||||
font-family: $font;
|
||||
padding: calculateRem(11) calculateRem(38);
|
||||
font-size: calculateRem(15);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.cancelButton {
|
||||
color: $link-color;
|
||||
background: #eceffc;
|
||||
}
|
||||
|
||||
.doneButton {
|
||||
color: #fff;
|
||||
background: $link-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* rating */
|
||||
.dapp {
|
||||
height: 48px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-family: $font;
|
||||
font-weight: 500;
|
||||
padding: 0 calculateRem(12);
|
||||
}
|
||||
|
||||
.dapp .image {
|
||||
max-width: 24px;
|
||||
max-height: 24px;
|
||||
border-radius: 50%;
|
||||
margin-right: calculateRem(12);
|
||||
}
|
||||
|
||||
.items {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-family: $font;
|
||||
}
|
||||
|
||||
.items .itemRow {
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 calculateRem(12);
|
||||
}
|
||||
|
||||
.items .item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.items .item img {
|
||||
margin-right: calculateRem(12);
|
||||
}
|
||||
|
||||
.badge {
|
||||
border-radius: 24px;
|
||||
color: #ffffff;
|
||||
font-family: $font;
|
||||
font-size: calculateRem(15);
|
||||
margin-right: calculateRem(16);
|
||||
margin-left: auto;
|
||||
padding: calculateRem(3) calculateRem(10);
|
||||
}
|
||||
|
||||
.greenBadge {
|
||||
@extend .badge;
|
||||
background: #44d058;
|
||||
}
|
||||
|
||||
.redBadge {
|
||||
@extend .badge;
|
||||
background: #f00;
|
||||
}
|
||||
|
||||
.inputArea {
|
||||
width: calc(100% - 2 * 16px);
|
||||
height: 64px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.inputArea.inputAreaBorder {
|
||||
border-bottom: 1px solid #eef2f5;
|
||||
}
|
||||
|
||||
.inputArea input {
|
||||
width: 19px;
|
||||
border: none;
|
||||
text-align: center;
|
||||
font-size: calculateRem(32);
|
||||
line-height: calculateRem(28);
|
||||
font-family: $font;
|
||||
margin-right: calculateRem(6);
|
||||
}
|
||||
|
||||
.inputArea input:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.inputArea::after {
|
||||
transition: all 0.05s ease-in-out;
|
||||
content: 'SNT';
|
||||
color: #939ba1;
|
||||
font-size: calculateRem(32);
|
||||
line-height: calculateRem(28);
|
||||
font-family: $font;
|
||||
}
|
||||
|
||||
.inputArea span {
|
||||
font-size: calculateRem(32);
|
||||
line-height: calculateRem(28);
|
||||
font-family: $font;
|
||||
margin-right: calculateRem(6);
|
||||
}
|
||||
|
||||
.footer {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.footer button {
|
||||
background: $link-color;
|
||||
border-radius: 8px;
|
||||
color: #fff;
|
||||
margin: calculateRem(10) auto;
|
||||
border: none;
|
||||
font-family: $font;
|
||||
padding: calculateRem(11) calculateRem(38);
|
||||
font-size: calculateRem(15);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.footer button:disabled,
|
||||
.footer button[disabled] {
|
||||
background: $text-color;
|
||||
}
|
||||
|
||||
.footer .disclaimer {
|
||||
font-size: calculateRem(15);
|
||||
color: $text-color;
|
||||
line-height: 22px;
|
||||
font-family: $font;
|
||||
padding: calculateRem(16);
|
||||
border-bottom: 1px solid #eef2f5;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.footer .disclaimer a {
|
||||
text-decoration: none;
|
||||
color: $link-color;
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
import withdrawInitialState from '../../common/data/withdraw'
|
||||
import reducerUtil from '../../common/utils/reducer'
|
||||
import {
|
||||
onReceiveTransactionInfoAction,
|
||||
checkTransactionStatusAction,
|
||||
onStartProgressAction,
|
||||
hideAction,
|
||||
} from '../TransactionStatus/TransactionStatus.recuder'
|
||||
import { TYPE_WITHDRAW } from '../TransactionStatus/TransactionStatus.utilities'
|
||||
import { showAlertAction } from '../Alert/Alert.reducer'
|
||||
|
||||
import BlockchainSDK from '../../common/blockchain'
|
||||
|
||||
const SHOW_WITHDRAW_AFTER_CHECK = 'WITHDRAW_SHOW_WITHDRAW_AFTER_CHECK'
|
||||
const CLOSE_WITHDRAW = 'WITHDRAW_CLOSE_WITHDRAW'
|
||||
const ON_INPUT_SNT_VALUE = 'WITHDRAW_ON_INPUT_SNT_VALUE'
|
||||
|
||||
export const showWithdrawAfterCheckAction = dapp => {
|
||||
window.location.hash = 'withdraw'
|
||||
return {
|
||||
type: SHOW_WITHDRAW_AFTER_CHECK,
|
||||
payload: dapp,
|
||||
}
|
||||
}
|
||||
|
||||
export const showWithdrawAction = dapp => {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState()
|
||||
if (
|
||||
state.transactionStatus.progress &&
|
||||
state.transactionStatus.dappTx !== ''
|
||||
) {
|
||||
dispatch(
|
||||
showAlertAction(
|
||||
'There is an active transaction. Please wait for it to finish and then you could be able to create your Ðapp',
|
||||
),
|
||||
)
|
||||
} else dispatch(showWithdrawAfterCheckAction(dapp))
|
||||
}
|
||||
}
|
||||
|
||||
export const closeWithdrawAction = () => {
|
||||
window.history.back()
|
||||
return {
|
||||
type: CLOSE_WITHDRAW,
|
||||
payload: null,
|
||||
}
|
||||
}
|
||||
|
||||
export const withdrawAction = (dapp, sntValue) => {
|
||||
return async dispatch => {
|
||||
dispatch(closeWithdrawAction())
|
||||
dispatch(
|
||||
onStartProgressAction(
|
||||
dapp.name,
|
||||
dapp.image,
|
||||
'Status is an open source mobile DApp browser and messenger build for #Etherium',
|
||||
TYPE_WITHDRAW,
|
||||
),
|
||||
)
|
||||
try {
|
||||
const blockchain = await BlockchainSDK.getInstance()
|
||||
const tx = await blockchain.DiscoverService.withdraw(dapp.id, sntValue)
|
||||
dispatch(onReceiveTransactionInfoAction(dapp.id, tx))
|
||||
dispatch(checkTransactionStatusAction(tx))
|
||||
} catch (e) {
|
||||
dispatch(hideAction())
|
||||
dispatch(showAlertAction(e.message))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const onInputSntValueAction = sntValue => ({
|
||||
type: ON_INPUT_SNT_VALUE,
|
||||
payload: sntValue,
|
||||
})
|
||||
|
||||
const showWithdrawAfterCheck = (state, dapp) => {
|
||||
return Object.assign({}, state, {
|
||||
visible: true,
|
||||
dapp,
|
||||
sntValue: dapp.sntValue.toString(),
|
||||
})
|
||||
}
|
||||
|
||||
const closeWithdraw = state => {
|
||||
return Object.assign({}, state, {
|
||||
visible: false,
|
||||
dapp: null,
|
||||
})
|
||||
}
|
||||
|
||||
const onInputSntValue = (state, sntValue) => {
|
||||
return Object.assign({}, state, {
|
||||
sntValue,
|
||||
})
|
||||
}
|
||||
|
||||
const map = {
|
||||
[SHOW_WITHDRAW_AFTER_CHECK]: showWithdrawAfterCheck,
|
||||
[CLOSE_WITHDRAW]: closeWithdraw,
|
||||
[ON_INPUT_SNT_VALUE]: onInputSntValue,
|
||||
}
|
||||
|
||||
export default reducerUtil(map, withdrawInitialState)
|
|
@ -0,0 +1,3 @@
|
|||
import Withdraw from './Withdraw.container'
|
||||
|
||||
export default Withdraw
|
Loading…
Reference in New Issue