mirror of
https://github.com/dap-ps/discover.git
synced 2025-02-07 15:05:07 +00:00
Merge with master
This commit is contained in:
commit
f52e24b345
5
.prettierrc
Normal file
5
.prettierrc
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"trailingComma": "all",
|
||||
"semi": false,
|
||||
"singleQuote": true
|
||||
}
|
@ -78,7 +78,7 @@ module.exports = {
|
||||
instanceOf: 'MiniMeToken',
|
||||
address: '0x2764b5da3696E3613Ef9864E9B4613f9fA478E75',
|
||||
},
|
||||
Discover: { address: '0x9591a20b9B601651eDF1072A1Dda994C0B1a5bBf' },
|
||||
Discover: { address: '0x74B32E54A50DDB18aB278EafA905e9cb595331B7' },
|
||||
// SNT: {
|
||||
// instanceOf: 'MiniMeToken',
|
||||
// args: [
|
||||
|
@ -1,2 +1,2 @@
|
||||
module.exports.mnemonic =
|
||||
''
|
||||
'erupt point century seek certain escape solution flee elegant hard please pen'
|
||||
|
@ -32,7 +32,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",
|
||||
|
BIN
public/images/learn-more-curve.png
Normal file
BIN
public/images/learn-more-curve.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 70 KiB |
@ -1,175 +1,188 @@
|
||||
/* global web3 */
|
||||
import { broadcastContractFn } from '../helpers';
|
||||
import MetadataClient from '../../../clients/metadata-client';
|
||||
import { broadcastContractFn } from '../helpers'
|
||||
import MetadataClient from '../../../clients/metadata-client'
|
||||
|
||||
import BlockchainService from '../blockchain-service';
|
||||
import BlockchainService from '../blockchain-service'
|
||||
|
||||
import DiscoverValidator from './discover-validator';
|
||||
import DiscoverContract from '../../../../embarkArtifacts/contracts/Discover';
|
||||
import DiscoverValidator from './discover-validator'
|
||||
import DiscoverContract from '../../../../embarkArtifacts/contracts/Discover'
|
||||
|
||||
class DiscoverService extends BlockchainService {
|
||||
constructor(sharedContext) {
|
||||
super(sharedContext, DiscoverContract, DiscoverValidator);
|
||||
super(sharedContext, DiscoverContract, DiscoverValidator)
|
||||
}
|
||||
|
||||
// View methods
|
||||
async upVoteEffect(id, amount) {
|
||||
await this.validator.validateUpVoteEffect(id, amount);
|
||||
await this.validator.validateUpVoteEffect(id, amount)
|
||||
|
||||
return DiscoverContract.methods
|
||||
.upvoteEffect(id, amount)
|
||||
.call({ from: this.sharedContext.account });
|
||||
.call({ from: this.sharedContext.account })
|
||||
}
|
||||
|
||||
async downVoteCost(id) {
|
||||
const dapp = await this.getDAppById(id);
|
||||
const dapp = await this.getDAppById(id)
|
||||
return DiscoverContract.methods
|
||||
.downvoteCost(dapp.id)
|
||||
.call({ from: this.sharedContext.account });
|
||||
.call({ from: this.sharedContext.account })
|
||||
}
|
||||
|
||||
async getDAppsCount() {
|
||||
return DiscoverContract.methods
|
||||
.getDAppsCount()
|
||||
.call({ from: this.sharedContext.account });
|
||||
.call({ from: this.sharedContext.account })
|
||||
}
|
||||
|
||||
async getDAppByIndexWithMetadata(index) {
|
||||
try {
|
||||
const dapp = await DiscoverContract.methods
|
||||
.dapps(index)
|
||||
.call({ from: this.sharedContext.account })
|
||||
|
||||
dapp.metadata = await MetadataClient.retrieveMetadata(dapp.metadata)
|
||||
return dapp
|
||||
} catch (error) {
|
||||
throw new Error(`Error fetching dapps. Details: ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
async getDAppById(id) {
|
||||
let dapp;
|
||||
let dapp
|
||||
try {
|
||||
const dappId = await DiscoverContract.methods
|
||||
.id2index(id)
|
||||
.call({ from: this.sharedContext.account });
|
||||
.call({ from: this.sharedContext.account })
|
||||
|
||||
dapp = await DiscoverContract.methods
|
||||
.dapps(dappId)
|
||||
.call({ from: this.sharedContext.account });
|
||||
.call({ from: this.sharedContext.account })
|
||||
} catch (error) {
|
||||
throw new Error('Searching DApp does not exists');
|
||||
throw new Error('Searching DApp does not exists')
|
||||
}
|
||||
|
||||
if (dapp.id != id) {
|
||||
throw new Error('Error fetching correct data from contract');
|
||||
throw new Error('Error fetching correct data from contract')
|
||||
}
|
||||
|
||||
return dapp;
|
||||
return dapp
|
||||
}
|
||||
|
||||
async getDAppDataById(id) {
|
||||
const dapp = await this.getDAppById(id);
|
||||
const dapp = await this.getDAppById(id)
|
||||
|
||||
try {
|
||||
const dappMetadata = await MetadataClient.retrieveMetadata(dapp.metadata);
|
||||
dapp.metadata = dappMetadata.details;
|
||||
dapp.metadata.status = dappMetadata.status;
|
||||
const dappMetadata = await MetadataClient.retrieveMetadata(dapp.metadata)
|
||||
dapp.metadata = dappMetadata.details
|
||||
dapp.metadata.status = dappMetadata.status
|
||||
|
||||
return dapp;
|
||||
return dapp
|
||||
} catch (error) {
|
||||
throw new Error('Error fetching correct data');
|
||||
throw new Error('Error fetching correct data')
|
||||
}
|
||||
}
|
||||
|
||||
async safeMax() {
|
||||
return DiscoverContract.methods
|
||||
.safeMax()
|
||||
.call({ from: this.sharedContext.account });
|
||||
.call({ from: this.sharedContext.account })
|
||||
}
|
||||
|
||||
async isDAppExists(id) {
|
||||
return DiscoverContract.methods
|
||||
.existingIDs(id)
|
||||
.call({ from: this.sharedContext.account });
|
||||
.call({ from: this.sharedContext.account })
|
||||
}
|
||||
|
||||
async checkIfCreatorOfDApp(id) {
|
||||
const dapp = this.getDAppById(id);
|
||||
this.sharedContext.account = await super.getAccount();
|
||||
const dapp = this.getDAppById(id)
|
||||
this.sharedContext.account = await super.getAccount()
|
||||
|
||||
return dapp.developer == this.sharedContext.account;
|
||||
return dapp.developer == this.sharedContext.account
|
||||
}
|
||||
|
||||
// Transaction methods
|
||||
async createDApp(amount, metadata) {
|
||||
await super.__unlockServiceAccount();
|
||||
await super.__unlockServiceAccount()
|
||||
|
||||
const dappMetadata = JSON.parse(JSON.stringify(metadata));
|
||||
dappMetadata.uploader = this.sharedContext.account;
|
||||
const dappMetadata = JSON.parse(JSON.stringify(metadata))
|
||||
dappMetadata.uploader = this.sharedContext.account
|
||||
|
||||
const dappId = web3.utils.keccak256(JSON.stringify(dappMetadata));
|
||||
await this.validator.validateDAppCreation(dappId, amount);
|
||||
const dappId = web3.utils.keccak256(JSON.stringify(dappMetadata))
|
||||
await this.validator.validateDAppCreation(dappId, amount)
|
||||
|
||||
const uploadedMetadata = await MetadataClient.upload(dappMetadata);
|
||||
const uploadedMetadata = await MetadataClient.upload(dappMetadata)
|
||||
|
||||
const callData = DiscoverContract.methods
|
||||
.createDApp(dappId, amount, uploadedMetadata)
|
||||
.encodeABI();
|
||||
.encodeABI()
|
||||
|
||||
const createdTx = await this.sharedContext.SNTService.approveAndCall(
|
||||
this.contract,
|
||||
amount,
|
||||
callData
|
||||
);
|
||||
callData,
|
||||
)
|
||||
|
||||
return { tx: createdTx, id: dappId };
|
||||
return { tx: createdTx, id: dappId }
|
||||
}
|
||||
|
||||
async upVote(id, amount) {
|
||||
await this.validator.validateUpVoting(id, amount);
|
||||
await this.validator.validateUpVoting(id, amount)
|
||||
|
||||
const callData = DiscoverContract.methods.upvote(id, amount).encodeABI();
|
||||
const callData = DiscoverContract.methods.upvote(id, amount).encodeABI()
|
||||
return this.sharedContext.SNTService.approveAndCall(
|
||||
this.contract,
|
||||
amount,
|
||||
callData
|
||||
);
|
||||
callData,
|
||||
)
|
||||
}
|
||||
|
||||
async downVote(id) {
|
||||
const dapp = await this.getDAppById(id);
|
||||
const amount = (await this.downVoteCost(dapp.id)).c;
|
||||
const dapp = await this.getDAppById(id)
|
||||
const amount = (await this.downVoteCost(dapp.id)).c
|
||||
|
||||
const callData = DiscoverContract.methods
|
||||
.downvote(dapp.id, amount)
|
||||
.encodeABI();
|
||||
.encodeABI()
|
||||
return this.sharedContext.SNTService.approveAndCall(
|
||||
this.contract,
|
||||
amount,
|
||||
callData
|
||||
);
|
||||
callData,
|
||||
)
|
||||
}
|
||||
|
||||
async withdraw(id, amount) {
|
||||
await super.__unlockServiceAccount();
|
||||
await this.validator.validateWithdrawing(id, amount);
|
||||
await super.__unlockServiceAccount()
|
||||
await this.validator.validateWithdrawing(id, amount)
|
||||
|
||||
try {
|
||||
return broadcastContractFn(
|
||||
DiscoverContract.methods.withdraw(id, amount).send,
|
||||
this.sharedContext.account
|
||||
);
|
||||
this.sharedContext.account,
|
||||
)
|
||||
} catch (error) {
|
||||
throw new Error(`Transfer on withdraw failed. Details: ${error.message}`);
|
||||
throw new Error(`Transfer on withdraw failed. Details: ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
async setMetadata(id, metadata) {
|
||||
await super.__unlockServiceAccount();
|
||||
await this.validator.validateMetadataSet(id);
|
||||
await super.__unlockServiceAccount()
|
||||
await this.validator.validateMetadataSet(id)
|
||||
|
||||
const dappMetadata = JSON.parse(JSON.stringify(metadata));
|
||||
dappMetadata.uploader = this.sharedContext.account;
|
||||
const dappMetadata = JSON.parse(JSON.stringify(metadata))
|
||||
dappMetadata.uploader = this.sharedContext.account
|
||||
|
||||
const uploadedMetadata = await MetadataClient.upload(dappMetadata);
|
||||
const uploadedMetadata = await MetadataClient.upload(dappMetadata)
|
||||
|
||||
try {
|
||||
return broadcastContractFn(
|
||||
DiscoverContract.methods.setMetadata(id, uploadedMetadata).send,
|
||||
this.sharedContext.account
|
||||
);
|
||||
this.sharedContext.account,
|
||||
)
|
||||
} catch (error) {
|
||||
throw new Error(`Uploading metadata failed. Details: ${error.message}`);
|
||||
throw new Error(`Uploading metadata failed. Details: ${error.message}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default DiscoverService;
|
||||
export default DiscoverService
|
||||
|
@ -1,45 +1,45 @@
|
||||
import HTTPClient from '../http-client';
|
||||
import HTTPClient from '../http-client'
|
||||
|
||||
import * as helpers from '../../utils/metadata-utils';
|
||||
import metadataClientEndpoints from '../endpoints/metadata-client-endpoints';
|
||||
import * as helpers from '../../utils/metadata-utils'
|
||||
import metadataClientEndpoints from '../endpoints/metadata-client-endpoints'
|
||||
|
||||
class APIClient {
|
||||
async upload(metadata) {
|
||||
const uploadedDataResponse = await HTTPClient.postRequest(
|
||||
metadataClientEndpoints.UPLOAD,
|
||||
{ details: metadata }
|
||||
);
|
||||
metadata,
|
||||
)
|
||||
|
||||
return helpers.getBytes32FromIpfsHash(uploadedDataResponse.data.hash);
|
||||
return helpers.getBytes32FromIpfsHash(uploadedDataResponse.data.hash)
|
||||
}
|
||||
|
||||
async retrieveMetadata(metadataBytes32) {
|
||||
const convertedHash = helpers.getIpfsHashFromBytes32(metadataBytes32);
|
||||
const convertedHash = helpers.getIpfsHashFromBytes32(metadataBytes32)
|
||||
const retrievedMetadataResponse = await HTTPClient.getRequest(
|
||||
`${metadataClientEndpoints.RETRIEVE_METADATA}/${convertedHash}`
|
||||
);
|
||||
`${metadataClientEndpoints.RETRIEVE_METADATA}/${convertedHash}`,
|
||||
)
|
||||
|
||||
return retrievedMetadataResponse.data;
|
||||
return retrievedMetadataResponse.data
|
||||
}
|
||||
|
||||
async retrieveAllDappsMetadata() {
|
||||
const retrievedDAppsMetadataResponse = await HTTPClient.getRequest(
|
||||
`${metadataClientEndpoints.RETRIEVE_ALL_METADATA}`
|
||||
);
|
||||
`${metadataClientEndpoints.RETRIEVE_ALL_METADATA}`,
|
||||
)
|
||||
|
||||
const formatedDappsMetadata = {};
|
||||
const metadataHashes = Object.keys(retrievedDAppsMetadataResponse.data);
|
||||
const formatedDappsMetadata = {}
|
||||
const metadataHashes = Object.keys(retrievedDAppsMetadataResponse.data)
|
||||
for (let i = 0; i < metadataHashes.length; i++) {
|
||||
const convertedDappMetadataHash = helpers.getBytes32FromIpfsHash(
|
||||
metadataHashes[i]
|
||||
);
|
||||
metadataHashes[i],
|
||||
)
|
||||
|
||||
formatedDappsMetadata[convertedDappMetadataHash] =
|
||||
retrievedDAppsMetadataResponse.data[metadataHashes[i]];
|
||||
retrievedDAppsMetadataResponse.data[metadataHashes[i]]
|
||||
}
|
||||
|
||||
return formatedDappsMetadata;
|
||||
return formatedDappsMetadata
|
||||
}
|
||||
}
|
||||
|
||||
export default APIClient;
|
||||
export default APIClient
|
||||
|
@ -3,20 +3,57 @@ 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,11 @@ 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 +55,7 @@ const DappListItem = props => {
|
||||
className={styles.description}
|
||||
style={{ WebkitBoxOrient: 'vertical' }}
|
||||
>
|
||||
{description}
|
||||
{desc}
|
||||
</p>
|
||||
</div>
|
||||
<a className={styles.url} href={url}>
|
||||
@ -84,12 +88,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;
|
||||
|
126
src/common/data/dapp.js
Normal file
126
src/common/data/dapp.js
Normal file
@ -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 }
|
@ -1,633 +0,0 @@
|
||||
import * as Categories from './categories'
|
||||
|
||||
const Dapps = [
|
||||
{
|
||||
metadata: {
|
||||
name: 'Airswap',
|
||||
url: 'https://instant.airswap.io/',
|
||||
image: '/images/dapps/airswap.png',
|
||||
description: 'Meet the future of trading',
|
||||
category: Categories.EXCHANGES,
|
||||
dateAdded: '2019-05-05',
|
||||
categoryPosition: 13,
|
||||
},
|
||||
rate: 45,
|
||||
},
|
||||
{
|
||||
metadata: {
|
||||
name: 'Bancor',
|
||||
url: 'https://www.bancor.network/',
|
||||
image: '/images/dapps/bancor.png',
|
||||
description: 'Bancor is a decentralized liquidity network',
|
||||
category: Categories.EXCHANGES,
|
||||
dateAdded: '2019-03-05',
|
||||
categoryPosition: 12,
|
||||
},
|
||||
rate: 345,
|
||||
},
|
||||
{
|
||||
metadata: {
|
||||
name: 'Kyber',
|
||||
url: 'https://web3.kyber.network',
|
||||
description:
|
||||
'On-chain, instant and liquid platform for exchange and payment',
|
||||
image: '/images/dapps/kyber.png',
|
||||
category: Categories.EXCHANGES,
|
||||
dateAdded: '2019-04-05',
|
||||
categoryPosition: 11,
|
||||
},
|
||||
rate: 2345,
|
||||
},
|
||||
{
|
||||
metadata: {
|
||||
name: 'Uniswap',
|
||||
url: 'https://uniswap.exchange/',
|
||||
description:
|
||||
'Seamlessly exchange ERC20 tokens, or use a formalized model to pool liquidity reserves',
|
||||
image: '/images/dapps/uniswap.png',
|
||||
category: Categories.EXCHANGES,
|
||||
dateAdded: '2019-04-23',
|
||||
categoryPosition: 10,
|
||||
},
|
||||
rate: 12345,
|
||||
},
|
||||
{
|
||||
metadata: {
|
||||
name: 'DAI by MakerDao',
|
||||
url: 'https://dai.makerdao.com',
|
||||
description: 'Stability for the blockchain',
|
||||
image: '/images/dapps/dai.png',
|
||||
category: Categories.EXCHANGES,
|
||||
dateAdded: '2019-04-05',
|
||||
categoryPosition: 9,
|
||||
},
|
||||
rate: 22345,
|
||||
},
|
||||
{
|
||||
metadata: {
|
||||
name: 'Augur',
|
||||
url: 'https://augur.net',
|
||||
description:
|
||||
'A prediction market protocol owned and operated by the people that use it',
|
||||
image: '/images/dapps/augur.svg',
|
||||
category: Categories.EXCHANGES,
|
||||
dateAdded: '2019-04-11',
|
||||
categoryPosition: 8,
|
||||
},
|
||||
rate: 32345,
|
||||
},
|
||||
{
|
||||
metadata: {
|
||||
name: 'LocalEthereum',
|
||||
url: 'https://localethereum.com/',
|
||||
description: 'The smartest way to buy and sell Ether',
|
||||
image: '/images/dapps/local-ethereum.png',
|
||||
category: Categories.EXCHANGES,
|
||||
dateAdded: '2019-04-05',
|
||||
categoryPosition: 7,
|
||||
},
|
||||
rate: 42345,
|
||||
},
|
||||
{
|
||||
metadata: {
|
||||
name: 'Eth2phone',
|
||||
url: 'https://eth2.io',
|
||||
description: 'Send Ether by phone number',
|
||||
image: '/images/dapps/eth2phone.png',
|
||||
category: Categories.EXCHANGES,
|
||||
dateAdded: '2019-04-05',
|
||||
categoryPosition: 6,
|
||||
},
|
||||
rate: 52345,
|
||||
},
|
||||
{
|
||||
metadata: {
|
||||
name: 'DDEX',
|
||||
url: 'https://ddex.io/',
|
||||
description:
|
||||
'Instant, real-time order matching with secure on-chain settlement',
|
||||
image: '/images/dapps/ddex.png',
|
||||
category: Categories.EXCHANGES,
|
||||
dateAdded: '2019-04-05',
|
||||
categoryPosition: 5,
|
||||
},
|
||||
rate: 62345,
|
||||
},
|
||||
{
|
||||
metadata: {
|
||||
name: 'Nuo',
|
||||
url: 'https://app.nuo.network/lend/',
|
||||
description:
|
||||
'The non-custodial way to lend, borrow or margin trade cryptocurrency',
|
||||
image: '/images/dapps/nuo.png',
|
||||
category: Categories.EXCHANGES,
|
||||
dateAdded: '2019-04-05',
|
||||
categoryPosition: 4,
|
||||
},
|
||||
rate: 72345,
|
||||
},
|
||||
{
|
||||
metadata: {
|
||||
name: 'EasyTrade',
|
||||
url: 'https://easytrade.io',
|
||||
description: 'One exchange for every token',
|
||||
image: '/images/dapps/easytrade.png',
|
||||
category: Categories.EXCHANGES,
|
||||
dateAdded: '2019-04-05',
|
||||
categoryPosition: 3,
|
||||
},
|
||||
rate: 82345,
|
||||
},
|
||||
{
|
||||
metadata: {
|
||||
name: 'slow.trade',
|
||||
url: 'https://slow.trade/',
|
||||
description:
|
||||
'Trade fairly priced crypto assets on the first platform built with the DutchX protocol',
|
||||
image: '/images/dapps/slowtrade.png',
|
||||
category: Categories.EXCHANGES,
|
||||
dateAdded: '2019-04-05',
|
||||
categoryPosition: 2,
|
||||
},
|
||||
rate: 92345,
|
||||
},
|
||||
{
|
||||
metadata: {
|
||||
name: 'Expo Trading',
|
||||
url: 'https://expotrading.com/trade/',
|
||||
description: 'The simplest way to margin trade cryptocurrency',
|
||||
image: '/images/dapps/expotrading.png',
|
||||
category: Categories.EXCHANGES,
|
||||
dateAdded: '2019-04-11',
|
||||
categoryPosition: 1,
|
||||
},
|
||||
rate: 102345,
|
||||
},
|
||||
{
|
||||
metadata: {
|
||||
name: 'Bidali',
|
||||
url: 'https://commerce.bidali.com/dapp',
|
||||
description: 'Buy from top brands with crypto',
|
||||
image: '/images/dapps/bidali.png',
|
||||
category: Categories.MARKETPLACES,
|
||||
dateAdded: '2019-05-01',
|
||||
},
|
||||
rate: 10246,
|
||||
},
|
||||
{
|
||||
metadata: {
|
||||
name: 'blockimmo',
|
||||
url: 'https://blockimmo.ch',
|
||||
description:
|
||||
'blockimmo is a blockchain powered, regulated platform enabling shared property investments and ownership',
|
||||
image: '/images/dapps/blockimmo.png',
|
||||
category: Categories.MARKETPLACES,
|
||||
dateAdded: '2019-04-05',
|
||||
categoryPosition: 2,
|
||||
},
|
||||
rate: 12345,
|
||||
},
|
||||
{
|
||||
metadata: {
|
||||
name: 'CryptoCribs',
|
||||
url: 'https://cryptocribs.com',
|
||||
description: 'Travel the globe. Pay in crypto',
|
||||
image: '/images/dapps/cryptocribs.png',
|
||||
category: Categories.MARKETPLACES,
|
||||
dateAdded: '2019-04-05',
|
||||
categoryPosition: 2,
|
||||
},
|
||||
rate: 12345,
|
||||
},
|
||||
{
|
||||
metadata: {
|
||||
name: 'Ethlance',
|
||||
url: 'https://ethlance.com',
|
||||
description:
|
||||
'The future of work is now. Hire people or work yourself in return for ETH',
|
||||
image: '/images/dapps/ethlance.png',
|
||||
category: Categories.MARKETPLACES,
|
||||
dateAdded: '2019-04-05',
|
||||
categoryPosition: 2,
|
||||
},
|
||||
rate: 12345,
|
||||
},
|
||||
{
|
||||
metadata: {
|
||||
name: 'OpenSea',
|
||||
url: 'https://opensea.io',
|
||||
description: 'The largest decentralized marketplace for cryptogoods',
|
||||
image: '/images/dapps/opensea.png',
|
||||
category: Categories.MARKETPLACES,
|
||||
dateAdded: '2019-04-05',
|
||||
categoryPosition: 2,
|
||||
},
|
||||
rate: 12345,
|
||||
},
|
||||
{
|
||||
metadata: {
|
||||
name: 'KnownOrigin',
|
||||
url: 'https://dapp.knownorigin.io/gallery',
|
||||
description: 'Discover, buy and collect digital artwork',
|
||||
image: '/images/dapps/knownorigin.png',
|
||||
category: Categories.MARKETPLACES,
|
||||
dateAdded: '2019-04-11',
|
||||
categoryPosition: 2,
|
||||
},
|
||||
rate: 12345,
|
||||
},
|
||||
{
|
||||
metadata: {
|
||||
name: 'dBay',
|
||||
url: 'https://dbay.ai',
|
||||
description: 'Buy from all your favorite DApps in one place',
|
||||
image: '/images/dapps/dBay.png',
|
||||
category: Categories.MARKETPLACES,
|
||||
dateAdded: '2019-04-23',
|
||||
categoryPosition: 2,
|
||||
},
|
||||
rate: 12345,
|
||||
},
|
||||
{
|
||||
metadata: {
|
||||
name: 'Name Bazaar',
|
||||
url: 'https://namebazaar.io',
|
||||
description: 'ENS name marketplace',
|
||||
image: '/images/dapps/name-bazaar.png',
|
||||
category: Categories.MARKETPLACES,
|
||||
dateAdded: '2019-04-05',
|
||||
categoryPosition: 2,
|
||||
},
|
||||
rate: 12345,
|
||||
},
|
||||
{
|
||||
metadata: {
|
||||
name: 'The Bounties Network',
|
||||
url: 'https://bounties.network/',
|
||||
description: 'Bounties on any task, paid in any token',
|
||||
image: '/images/dapps/bounties-network.png',
|
||||
category: Categories.MARKETPLACES,
|
||||
dateAdded: '2019-04-05',
|
||||
categoryPosition: 2,
|
||||
},
|
||||
rate: 12345,
|
||||
},
|
||||
{
|
||||
metadata: {
|
||||
name: 'Emoon',
|
||||
url: 'https://www.emoon.io/',
|
||||
description:
|
||||
'A decentralized marketplace for buying & selling crypto assets',
|
||||
image: '/images/dapps/emoon.png',
|
||||
category: Categories.MARKETPLACES,
|
||||
dateAdded: '2019-04-05',
|
||||
categoryPosition: 2,
|
||||
},
|
||||
rate: 12345,
|
||||
},
|
||||
{
|
||||
metadata: {
|
||||
name: 'Astro Ledger',
|
||||
url: 'https://www.astroledger.org/#/onSale',
|
||||
description: 'Funding space grants with blockchain star naming',
|
||||
image: '/images/dapps/astroledger.svg',
|
||||
category: Categories.MARKETPLACES,
|
||||
dateAdded: '2019-04-11',
|
||||
categoryPosition: 2,
|
||||
},
|
||||
rate: 12345,
|
||||
},
|
||||
{
|
||||
metadata: {
|
||||
name: 'SuperRare',
|
||||
url: 'https://superrare.co/market',
|
||||
description:
|
||||
'Buy, sell and collect unique digital creations by artists around the world',
|
||||
image: '/images/dapps/superrare.png',
|
||||
category: Categories.MARKETPLACES,
|
||||
dateAdded: '2019-04-05',
|
||||
categoryPosition: 2,
|
||||
},
|
||||
rate: 12345,
|
||||
},
|
||||
{
|
||||
metadata: {
|
||||
name: 'CryptoCare',
|
||||
url: 'https://cryptocare.tech',
|
||||
description:
|
||||
'Give your Ether some heart! Collectibles that make the world a better place',
|
||||
image: '/images/dapps/cryptocare.jpg',
|
||||
category: Categories.COLLECTIBLES,
|
||||
dateAdded: '2019-04-11',
|
||||
categoryPosition: 2,
|
||||
},
|
||||
rate: 12345,
|
||||
},
|
||||
{
|
||||
metadata: {
|
||||
name: 'CryptoKitties',
|
||||
url: 'https://www.cryptokitties.co',
|
||||
description: 'Collect and breed adorable digital cats',
|
||||
image: '/images/dapps/cryptokitties.png',
|
||||
category: Categories.COLLECTIBLES,
|
||||
dateAdded: '2019-04-05',
|
||||
categoryPosition: 2,
|
||||
},
|
||||
rate: 12345,
|
||||
},
|
||||
{
|
||||
metadata: {
|
||||
name: 'Cryptographics',
|
||||
url: 'https://cryptographics.app/',
|
||||
description:
|
||||
'A digital art hub for creation, trading, and collecting unique items',
|
||||
image: '/images/dapps/cryptographics.png',
|
||||
category: Categories.COLLECTIBLES,
|
||||
dateAdded: '2019-04-05',
|
||||
categoryPosition: 2,
|
||||
},
|
||||
rate: 12345,
|
||||
},
|
||||
{
|
||||
metadata: {
|
||||
name: 'CryptoPunks',
|
||||
url: 'https://www.larvalabs.com/cryptopunks',
|
||||
description: '10,000 unique collectible punks',
|
||||
image: '/images/dapps/cryptopunks.png',
|
||||
category: Categories.COLLECTIBLES,
|
||||
dateAdded: '2019-04-05',
|
||||
categoryPosition: 2,
|
||||
},
|
||||
rate: 12345,
|
||||
},
|
||||
{
|
||||
metadata: {
|
||||
name: 'Crypto Takeovers',
|
||||
url: 'https://cryptotakeovers.com/',
|
||||
description: 'Predict and conquer the world. Make a crypto fortune',
|
||||
image: '/images/dapps/cryptotakeovers.png',
|
||||
category: Categories.GAMES,
|
||||
dateAdded: '2019-04-05',
|
||||
categoryPosition: 2,
|
||||
},
|
||||
rate: 12345,
|
||||
},
|
||||
{
|
||||
metadata: {
|
||||
name: 'CryptoFighters',
|
||||
url: 'https://cryptofighters.io',
|
||||
description: 'Collect train and fight digital fighters',
|
||||
image: '/images/dapps/cryptofighters.png',
|
||||
category: Categories.GAMES,
|
||||
dateAdded: '2019-04-11',
|
||||
categoryPosition: 2,
|
||||
},
|
||||
rate: 12345,
|
||||
},
|
||||
{
|
||||
metadata: {
|
||||
name: 'Decentraland',
|
||||
url: 'https://market.decentraland.org/',
|
||||
description:
|
||||
'A virtual reality platform powered by the Ethereum blockchain',
|
||||
image: '/images/dapps/decentraland.png',
|
||||
category: Categories.GAMES,
|
||||
dateAdded: '2019-04-05',
|
||||
categoryPosition: 2,
|
||||
},
|
||||
rate: 12345,
|
||||
},
|
||||
{
|
||||
metadata: {
|
||||
name: 'Dragonereum',
|
||||
url: 'https://dapp.dragonereum.io',
|
||||
description: 'Own and trade dragons, fight with other players',
|
||||
image: '/images/dapps/dragonereum.png',
|
||||
category: Categories.GAMES,
|
||||
dateAdded: '2019-04-05',
|
||||
categoryPosition: 2,
|
||||
},
|
||||
rate: 12345,
|
||||
},
|
||||
{
|
||||
metadata: {
|
||||
name: 'Etherbots',
|
||||
url: 'https://etherbots.io/',
|
||||
description: 'Robot wars on Ethereum',
|
||||
image: '/images/dapps/etherbots.png',
|
||||
category: Categories.GAMES,
|
||||
dateAdded: '2019-04-05',
|
||||
categoryPosition: 2,
|
||||
},
|
||||
rate: 12345,
|
||||
},
|
||||
{
|
||||
metadata: {
|
||||
name: 'Etheremon',
|
||||
url: 'https://www.etheremon.com/',
|
||||
description: 'Decentralized World of Ether Monsters',
|
||||
image: '/images/dapps/etheremon.png',
|
||||
category: Categories.GAMES,
|
||||
dateAdded: '2019-04-05',
|
||||
categoryPosition: 2,
|
||||
},
|
||||
rate: 12345,
|
||||
},
|
||||
{
|
||||
metadata: {
|
||||
name: 'CryptoStrikers',
|
||||
url: 'https://www.cryptostrikers.com/',
|
||||
description: 'The Beautiful (card) Game',
|
||||
image: '/images/dapps/cryptostrikers.png',
|
||||
category: Categories.GAMES,
|
||||
dateAdded: '2019-04-05',
|
||||
categoryPosition: 2,
|
||||
},
|
||||
rate: 12345,
|
||||
},
|
||||
// {
|
||||
// metadata: {
|
||||
// name: 'FairHouse',
|
||||
// url: 'https://fairhouse.io',
|
||||
// description: 'Fair and transparent entertainment games.',
|
||||
// image: '/images/dapps/fairhouse.png',
|
||||
// category: Categories.GAMES,
|
||||
// dateAdded: '2019-04-11',
|
||||
// categoryPosition: 2,
|
||||
// },
|
||||
// rate: 12345,
|
||||
// },
|
||||
{
|
||||
metadata: {
|
||||
name: 'Cent',
|
||||
url: 'https://beta.cent.co/',
|
||||
description: 'Get wisdom, get money',
|
||||
image: '/images/dapps/cent.png',
|
||||
category: Categories.SOCIAL_NETWORKS,
|
||||
dateAdded: '2019-04-05',
|
||||
categoryPosition: 2,
|
||||
},
|
||||
rate: 12345,
|
||||
},
|
||||
{
|
||||
metadata: {
|
||||
name: 'Kickback',
|
||||
url: 'https://kickback.events/',
|
||||
description:
|
||||
'Event no shows? No problem. Kickback asks event attendees to put skin in the game with Ethereum',
|
||||
image: '/images/dapps/kickback.png',
|
||||
category: Categories.SOCIAL_NETWORKS,
|
||||
dateAdded: '2019-04-05',
|
||||
categoryPosition: 2,
|
||||
},
|
||||
rate: 12345,
|
||||
},
|
||||
{
|
||||
metadata: {
|
||||
name: 'Peepeth',
|
||||
url: 'https://peepeth.com/',
|
||||
description: 'Blockchain-powered microblogging',
|
||||
image: '/images/dapps/peepeth.png',
|
||||
category: Categories.SOCIAL_NETWORKS,
|
||||
dateAdded: '2019-04-05',
|
||||
categoryPosition: 2,
|
||||
},
|
||||
rate: 12345,
|
||||
},
|
||||
{
|
||||
metadata: {
|
||||
name: 'livepeer.tv',
|
||||
url: 'http://livepeer.tv/',
|
||||
description: 'Decentralized video broadcasting',
|
||||
image: '/images/dapps/livepeer.png',
|
||||
category: Categories.OTHER,
|
||||
dateAdded: '2019-04-05',
|
||||
categoryPosition: 2,
|
||||
},
|
||||
rate: 12345,
|
||||
},
|
||||
{
|
||||
metadata: {
|
||||
name: 'Aragon',
|
||||
url: 'https://mainnet.aragon.org/',
|
||||
description: 'Build unstoppable organizations on Ethereum',
|
||||
image: '/images/dapps/aragon.png',
|
||||
category: Categories.UTILITIES,
|
||||
dateAdded: '2019-04-05',
|
||||
categoryPosition: 2,
|
||||
},
|
||||
rate: 12345,
|
||||
},
|
||||
{
|
||||
metadata: {
|
||||
name: 'Compound Finance',
|
||||
url: 'https://app.compound.finance/',
|
||||
description:
|
||||
'An open-source protocol for algorithmic, efficient Money Markets on Ethereum',
|
||||
image: '/images/dapps/compoundfinance.png',
|
||||
category: Categories.UTILITIES,
|
||||
dateAdded: '2019-04-11',
|
||||
categoryPosition: 2,
|
||||
},
|
||||
rate: 12345,
|
||||
},
|
||||
{
|
||||
metadata: {
|
||||
name: 'InstaDApp',
|
||||
url: 'https://instadapp.io/',
|
||||
description: 'Decentralized Banking',
|
||||
image: '/images/dapps/instadapp.jpg',
|
||||
category: Categories.UTILITIES,
|
||||
dateAdded: '2019-04-11',
|
||||
categoryPosition: 2,
|
||||
},
|
||||
rate: 12345,
|
||||
},
|
||||
{
|
||||
metadata: {
|
||||
name: 'Livepeer',
|
||||
url: 'https://explorer.livepeer.org/',
|
||||
description: 'Decentralized video broadcasting',
|
||||
image: '/images/dapps/livepeer.png',
|
||||
category: Categories.UTILITIES,
|
||||
dateAdded: '2019-04-11',
|
||||
categoryPosition: 2,
|
||||
},
|
||||
rate: 12345,
|
||||
},
|
||||
{
|
||||
metadata: {
|
||||
name: 'ETHLend',
|
||||
url: 'https://app.ethlend.io',
|
||||
description: 'Decentralized lending on Ethereum',
|
||||
image: '/images/dapps/ethlend.png',
|
||||
category: Categories.UTILITIES,
|
||||
dateAdded: '2019-04-05',
|
||||
categoryPosition: 2,
|
||||
},
|
||||
rate: 12345,
|
||||
},
|
||||
{
|
||||
metadata: {
|
||||
name: 'Civitas',
|
||||
url: 'https://communities.colu.com/',
|
||||
description: 'Blockchain-powered local communities',
|
||||
image: '/images/dapps/civitas.png',
|
||||
category: Categories.UTILITIES,
|
||||
dateAdded: '2019-04-11',
|
||||
categoryPosition: 2,
|
||||
},
|
||||
rate: 12345,
|
||||
},
|
||||
{
|
||||
metadata: {
|
||||
name: '3Box',
|
||||
url: 'https://3box.io/',
|
||||
description: 'Create and manage your Ethereum Profile',
|
||||
image: '/images/dapps/3Box.png',
|
||||
category: Categories.UTILITIES,
|
||||
dateAdded: '2019-04-11',
|
||||
categoryPosition: 2,
|
||||
},
|
||||
rate: 12345,
|
||||
},
|
||||
{
|
||||
metadata: {
|
||||
name: 'Hexel',
|
||||
url: 'https://www.onhexel.com/',
|
||||
description: 'Create your own cryptocurrency',
|
||||
image: '/images/dapps/hexel.png',
|
||||
category: Categories.UTILITIES,
|
||||
dateAdded: '2019-04-11',
|
||||
categoryPosition: 2,
|
||||
},
|
||||
rate: 12345,
|
||||
},
|
||||
{
|
||||
metadata: {
|
||||
name: 'Smartz',
|
||||
url: 'https://smartz.io',
|
||||
description: 'Easy smart contract management',
|
||||
image: '/images/dapps/smartz.png',
|
||||
category: Categories.UTILITIES,
|
||||
dateAdded: '2019-04-05',
|
||||
categoryPosition: 2,
|
||||
},
|
||||
rate: 12345,
|
||||
},
|
||||
{
|
||||
metadata: {
|
||||
name: 'SNT Voting DApp',
|
||||
url: 'https://vote.status.im',
|
||||
description:
|
||||
'Let your SNT be heard! Vote on decisions exclusive to SNT holders, or create a poll of your own.',
|
||||
image: '/images/dapps/snt-voting.png',
|
||||
category: Categories.UTILITIES,
|
||||
dateAdded: '2019-04-05',
|
||||
categoryPosition: 2,
|
||||
},
|
||||
rate: 12345,
|
||||
},
|
||||
]
|
||||
|
||||
export default Dapps
|
35
src/common/data/database.js
Normal file
35
src/common/data/database.js
Normal file
@ -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)
|
||||
}
|
||||
}
|
@ -4,6 +4,8 @@ const vote = {
|
||||
isUpvote: false,
|
||||
sntValue: '0',
|
||||
afterVoteRating: null,
|
||||
learnMoreUpVote: false,
|
||||
learnMoreDownVote: false,
|
||||
}
|
||||
|
||||
export default vote
|
||||
|
7
src/common/data/withdraw.js
Normal file
7
src/common/data/withdraw.js
Normal file
@ -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,
|
||||
}
|
||||
|
||||
|
@ -4,13 +4,3 @@
|
||||
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,336 +1,88 @@
|
||||
// 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 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_UPDATE_DAPPS = 'DAPPS_ON_UPDATE_DAPPS'
|
||||
const ON_UPDATE_DAPP_DATA = 'DAPPS_ON_UPDATE_DAPP_DATA'
|
||||
|
||||
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_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 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 onUpdateDappsAction = dappState => ({
|
||||
type: ON_UPDATE_DAPPS,
|
||||
payload: dappState,
|
||||
})
|
||||
|
||||
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()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const onUpdateDappDataAction = dapp => ({
|
||||
type: ON_UPDATE_DAPP_DATA,
|
||||
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;
|
||||
}
|
||||
}
|
||||
};
|
||||
payload: dapp,
|
||||
})
|
||||
|
||||
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
|
||||
};
|
||||
[ON_UPDATE_DAPP_DATA]: onUpdateDappData,
|
||||
[ON_UPDATE_DAPPS]: onUpdateDapps,
|
||||
}
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
export default reducerUtil(map, dappsInitialState);
|
||||
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 (
|
||||
<>
|
||||
|
@ -98,7 +98,8 @@ class HowToSubmit extends React.Component {
|
||||
<ol>
|
||||
<li>Malicious code injection</li>
|
||||
<li>
|
||||
Violation of <a>Status' principles</a>
|
||||
Violation of{' '}
|
||||
<a href="https://status.im/about/">Status' principles</a>
|
||||
</li>
|
||||
<li>Lack of usability (especially on mobile)</li>
|
||||
<li>Vote manipulation.</li>
|
||||
@ -110,8 +111,8 @@ class HowToSubmit extends React.Component {
|
||||
UI choices for the same underlying contract. Note that
|
||||
Discover is not affiliated with Status directly, we have
|
||||
simply chosen to use SNT as a token of value, to abide by{' '}
|
||||
<a>Status’ principles</a>, and to take a mobile-first approach
|
||||
to development.
|
||||
<a href="https://status.im/about/">Status’ principles</a>, and
|
||||
to take a mobile-first approach to development.
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
|
@ -1,15 +1,20 @@
|
||||
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'
|
||||
import { showSubmitAction } from '../Submit/Submit.reducer'
|
||||
|
||||
const mapStateToProps = state => state
|
||||
const mapStateToProps = state => ({ dappState: state.dapps })
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
openModal: dapp => dispatch(toggleProfileModalAction(dapp)),
|
||||
onClickWithdraw: dapp => dispatch(showWithdrawAction(dapp)),
|
||||
onClickUpdateMetadata: dapp => dispatch(showSubmitAction(dapp)),
|
||||
})
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
)(Profile)
|
||||
export default withRouter(
|
||||
connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
)(Profile),
|
||||
)
|
||||
|
@ -1,27 +1,23 @@
|
||||
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,
|
||||
onClickUpdateMetadata,
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
@ -37,13 +33,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,23 +66,35 @@ 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} onClick={onClickUpdateMetadata}>
|
||||
Edit metadata
|
||||
</div>
|
||||
<div className={styles.button} onClick={onClickWithdraw}>
|
||||
Withdraw SNT
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
@ -95,60 +103,80 @@ const ProfileContent = ({
|
||||
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)
|
||||
}
|
||||
|
||||
onClickUpdateMetadata(dapp) {
|
||||
const { onClickUpdateMetadata } = this.props
|
||||
this.onClickClose()
|
||||
setTimeout(() => {
|
||||
onClickUpdateMetadata(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)}
|
||||
onClickUpdateMetadata={this.onClickUpdateMetadata.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,
|
||||
onClickUpdateMetadata: 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,27 @@ 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;
|
||||
}
|
||||
|
@ -17,10 +17,11 @@ import {
|
||||
submitAction,
|
||||
switchToRatingAction,
|
||||
onInputSntValueAction,
|
||||
updateAction,
|
||||
} 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)),
|
||||
@ -33,6 +34,7 @@ const mapDispatchToProps = dispatch => ({
|
||||
onImgCancel: () => dispatch(onImgCancelAction()),
|
||||
onImgDone: imgBase64 => dispatch(onImgDoneAction(imgBase64)),
|
||||
onSubmit: (dapp, sntValue) => dispatch(submitAction(dapp, sntValue)),
|
||||
onUpdate: (dappId, metadata) => dispatch(updateAction(dappId, metadata)),
|
||||
onClickTerms: () => dispatch(push('/terms')),
|
||||
switchToRating: () => dispatch(switchToRatingAction()),
|
||||
onInputSntValue: sntValue => dispatch(onInputSntValueAction(sntValue)),
|
||||
|
@ -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
|
||||
@ -150,8 +151,18 @@ class Submit extends React.Component {
|
||||
}
|
||||
|
||||
onSubmit() {
|
||||
const { onSubmit, name, desc, url, img, category, sntValue } = this.props
|
||||
const dapp = {
|
||||
const {
|
||||
onSubmit,
|
||||
onUpdate,
|
||||
id,
|
||||
name,
|
||||
desc,
|
||||
url,
|
||||
img,
|
||||
category,
|
||||
sntValue,
|
||||
} = this.props
|
||||
const metadata = {
|
||||
name,
|
||||
url,
|
||||
img,
|
||||
@ -159,7 +170,8 @@ class Submit extends React.Component {
|
||||
desc,
|
||||
}
|
||||
|
||||
onSubmit(dapp, parseInt(sntValue, 10))
|
||||
if (id === '') onSubmit(metadata, parseInt(sntValue, 10))
|
||||
else onUpdate(id, metadata)
|
||||
}
|
||||
|
||||
handleSNTChange(e) {
|
||||
@ -182,10 +194,11 @@ class Submit extends React.Component {
|
||||
|
||||
render() {
|
||||
const {
|
||||
dapps,
|
||||
dappState,
|
||||
visible_submit,
|
||||
visible_rating,
|
||||
onClickClose,
|
||||
id,
|
||||
name,
|
||||
desc,
|
||||
url,
|
||||
@ -212,7 +225,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 !== '') {
|
||||
@ -315,7 +328,7 @@ class Submit extends React.Component {
|
||||
className={styles.submitButton}
|
||||
type="submit"
|
||||
disabled={!canSubmit}
|
||||
onClick={switchToRating}
|
||||
onClick={id === '' ? switchToRating : this.onSubmit}
|
||||
>
|
||||
Continue
|
||||
</button>
|
||||
@ -467,9 +480,11 @@ Submit.propTypes = {
|
||||
onImgCancel: PropTypes.func.isRequired,
|
||||
onImgDone: PropTypes.func.isRequired,
|
||||
onSubmit: PropTypes.func.isRequired,
|
||||
onUpdate: PropTypes.func.isRequired,
|
||||
onInputSntValue: PropTypes.func.isRequired,
|
||||
onClickTerms: PropTypes.func.isRequired,
|
||||
switchToRating: PropTypes.func.isRequired,
|
||||
dappState: PropTypes.instanceOf(DappState).isRequired,
|
||||
}
|
||||
|
||||
export default Submit
|
||||
|
@ -1,199 +1,247 @@
|
||||
import submitInitialState from '../../common/data/submit';
|
||||
import reducerUtil from '../../common/utils/reducer';
|
||||
import submitInitialState from '../../common/data/submit'
|
||||
import reducerUtil from '../../common/utils/reducer'
|
||||
import {
|
||||
onReceiveTransactionInfoAction,
|
||||
checkTransactionStatusAction,
|
||||
onStartProgressAction,
|
||||
hideAction
|
||||
} from '../TransactionStatus/TransactionStatus.recuder';
|
||||
import { TYPE_SUBMIT } from '../TransactionStatus/TransactionStatus.utilities';
|
||||
import { showAlertAction } from '../Alert/Alert.reducer';
|
||||
hideAction,
|
||||
} from '../TransactionStatus/TransactionStatus.recuder'
|
||||
import {
|
||||
TYPE_SUBMIT,
|
||||
TYPE_UPDATE,
|
||||
} from '../TransactionStatus/TransactionStatus.utilities'
|
||||
import { showAlertAction } from '../Alert/Alert.reducer'
|
||||
|
||||
import BlockchainSDK from '../../common/blockchain';
|
||||
import BlockchainSDK from '../../common/blockchain'
|
||||
|
||||
const SHOW_SUBMIT_AFTER_CHECK = 'SUBMIT_SHOW_SUBMIT_AFTER_CHECK';
|
||||
const CLOSE_SUBMIT = 'SUBMIT_CLOSE_SUBMIT';
|
||||
const ON_INPUT_NAME = 'SUBMIT_ON_INPUT_NAME';
|
||||
const ON_INPUT_DESC = 'SUBMIT_ON_INPUT_DESC';
|
||||
const ON_INPUT_URL = 'SUBMIT_ON_INPUT_URL';
|
||||
const ON_SELECT_CATEGORY = 'SUBMIT_ON_SELECT_CATEGORY';
|
||||
const ON_IMG_READ = 'SUBMIT_ON_IMG_READ';
|
||||
const ON_IMG_ZOOM = 'SUBMIT_ON_IMG_ZOOM';
|
||||
const ON_IMG_MOVE_CONTROL = 'SUBMIT_ON_IMG_MOVE_CONTROL';
|
||||
const ON_IMG_MOVE = 'SUBMIT_ON_IMG_MOVE';
|
||||
const ON_IMG_CANCEL = 'SUBMIT_ON_IMG_CANCEL';
|
||||
const ON_IMG_DONE = 'SUBMIT_ON_IMG_DONE';
|
||||
const SHOW_SUBMIT_AFTER_CHECK = 'SUBMIT_SHOW_SUBMIT_AFTER_CHECK'
|
||||
const CLOSE_SUBMIT = 'SUBMIT_CLOSE_SUBMIT'
|
||||
const ON_INPUT_NAME = 'SUBMIT_ON_INPUT_NAME'
|
||||
const ON_INPUT_DESC = 'SUBMIT_ON_INPUT_DESC'
|
||||
const ON_INPUT_URL = 'SUBMIT_ON_INPUT_URL'
|
||||
const ON_SELECT_CATEGORY = 'SUBMIT_ON_SELECT_CATEGORY'
|
||||
const ON_IMG_READ = 'SUBMIT_ON_IMG_READ'
|
||||
const ON_IMG_ZOOM = 'SUBMIT_ON_IMG_ZOOM'
|
||||
const ON_IMG_MOVE_CONTROL = 'SUBMIT_ON_IMG_MOVE_CONTROL'
|
||||
const ON_IMG_MOVE = 'SUBMIT_ON_IMG_MOVE'
|
||||
const ON_IMG_CANCEL = 'SUBMIT_ON_IMG_CANCEL'
|
||||
const ON_IMG_DONE = 'SUBMIT_ON_IMG_DONE'
|
||||
|
||||
const SWITCH_TO_RATING = 'SUBMIT_SWITCH_TO_RATING';
|
||||
const ON_INPUT_SNT_VALUE = 'SUBMIT_ON_INPUT_SNT_VALUE';
|
||||
const SWITCH_TO_RATING = 'SUBMIT_SWITCH_TO_RATING'
|
||||
const ON_INPUT_SNT_VALUE = 'SUBMIT_ON_INPUT_SNT_VALUE'
|
||||
|
||||
export const showSubmitActionAfterCheck = () => {
|
||||
window.location.hash = 'submit';
|
||||
const showSubmitAfterCheckAction = dapp => {
|
||||
window.location.hash = 'submit'
|
||||
return {
|
||||
type: SHOW_SUBMIT_AFTER_CHECK,
|
||||
payload: null
|
||||
};
|
||||
};
|
||||
payload: dapp,
|
||||
}
|
||||
}
|
||||
|
||||
export const showSubmitAction = () => {
|
||||
export const showSubmitAction = dapp => {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
if (state.transactionStatus.progress) {
|
||||
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(showSubmitActionAfterCheck());
|
||||
};
|
||||
};
|
||||
'There is an active transaction. Please wait for it to finish and then you could be able to create your Ðapp',
|
||||
),
|
||||
)
|
||||
} else if (dapp !== undefined) {
|
||||
// convert dapp's image from url ot base64
|
||||
const toDataUrl = (url, callback) => {
|
||||
const xhr = new XMLHttpRequest()
|
||||
xhr.onload = () => {
|
||||
const reader = new FileReader()
|
||||
reader.onloadend = () => {
|
||||
callback(reader.result)
|
||||
}
|
||||
reader.readAsDataURL(xhr.response)
|
||||
}
|
||||
xhr.open('GET', url)
|
||||
xhr.responseType = 'blob'
|
||||
xhr.send()
|
||||
}
|
||||
toDataUrl(dapp.image, base64 => {
|
||||
dapp.image = base64
|
||||
dispatch(showSubmitAfterCheckAction(dapp))
|
||||
})
|
||||
} else dispatch(showSubmitAfterCheckAction(dapp))
|
||||
}
|
||||
}
|
||||
|
||||
export const closeSubmitAction = () => {
|
||||
window.history.back();
|
||||
window.history.back()
|
||||
return {
|
||||
type: CLOSE_SUBMIT,
|
||||
payload: null
|
||||
};
|
||||
};
|
||||
payload: null,
|
||||
}
|
||||
}
|
||||
|
||||
export const onInputNameAction = name => ({
|
||||
type: ON_INPUT_NAME,
|
||||
payload: name
|
||||
});
|
||||
payload: name,
|
||||
})
|
||||
|
||||
export const onInputDescAction = desc => ({
|
||||
type: ON_INPUT_DESC,
|
||||
payload: desc.substring(0, 140)
|
||||
});
|
||||
payload: desc.substring(0, 140),
|
||||
})
|
||||
|
||||
export const onInputUrlAction = url => ({
|
||||
type: ON_INPUT_URL,
|
||||
payload: url
|
||||
});
|
||||
payload: url,
|
||||
})
|
||||
|
||||
export const onSelectCategoryAction = category => ({
|
||||
type: ON_SELECT_CATEGORY,
|
||||
payload: category
|
||||
});
|
||||
payload: category,
|
||||
})
|
||||
|
||||
export const onImgReadAction = imgBase64 => ({
|
||||
type: ON_IMG_READ,
|
||||
payload: imgBase64
|
||||
});
|
||||
payload: imgBase64,
|
||||
})
|
||||
|
||||
export const onImgZoomAction = zoom => ({
|
||||
type: ON_IMG_ZOOM,
|
||||
payload: zoom
|
||||
});
|
||||
payload: zoom,
|
||||
})
|
||||
|
||||
export const onImgMoveControlAction = move => ({
|
||||
type: ON_IMG_MOVE_CONTROL,
|
||||
payload: move
|
||||
});
|
||||
payload: move,
|
||||
})
|
||||
|
||||
export const onImgMoveAction = (x, y) => ({
|
||||
type: ON_IMG_MOVE,
|
||||
payload: { x, y }
|
||||
});
|
||||
payload: { x, y },
|
||||
})
|
||||
|
||||
export const onImgCancelAction = () => ({
|
||||
type: ON_IMG_CANCEL,
|
||||
payload: null
|
||||
});
|
||||
payload: null,
|
||||
})
|
||||
|
||||
export const onImgDoneAction = imgBase64 => ({
|
||||
type: ON_IMG_DONE,
|
||||
payload: imgBase64
|
||||
});
|
||||
payload: imgBase64,
|
||||
})
|
||||
|
||||
export const submitAction = (dapp, sntValue) => {
|
||||
return async dispatch => {
|
||||
dispatch(closeSubmitAction());
|
||||
dispatch(closeSubmitAction())
|
||||
dispatch(
|
||||
onStartProgressAction(
|
||||
dapp.name,
|
||||
dapp.img,
|
||||
'Status is an open source mobile DApp browser and messenger build for #Etherium',
|
||||
TYPE_SUBMIT
|
||||
)
|
||||
);
|
||||
TYPE_SUBMIT,
|
||||
),
|
||||
)
|
||||
try {
|
||||
const blockchain = await BlockchainSDK.getInstance();
|
||||
const blockchain = await BlockchainSDK.getInstance()
|
||||
const { tx, id } = await blockchain.DiscoverService.createDApp(sntValue, {
|
||||
name: dapp.name,
|
||||
url: dapp.url,
|
||||
description: dapp.desc,
|
||||
category: dapp.category,
|
||||
image: dapp.img,
|
||||
dateAdded: Date.now()
|
||||
});
|
||||
dispatch(onReceiveTransactionInfoAction(id, tx));
|
||||
dispatch(checkTransactionStatusAction(tx));
|
||||
dateAdded: Date.now(),
|
||||
})
|
||||
dispatch(onReceiveTransactionInfoAction(id, tx))
|
||||
dispatch(checkTransactionStatusAction(tx))
|
||||
} catch (e) {
|
||||
dispatch(hideAction());
|
||||
dispatch(showAlertAction(e.message));
|
||||
dispatch(hideAction())
|
||||
dispatch(showAlertAction(e.message))
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const updateAction = (dappId, metadata) => {
|
||||
return async dispatch => {
|
||||
dispatch(closeSubmitAction())
|
||||
dispatch(
|
||||
onStartProgressAction(
|
||||
metadata.name,
|
||||
metadata.img,
|
||||
'Status is an open source mobile DApp browser and messenger build for #Etherium',
|
||||
TYPE_UPDATE,
|
||||
),
|
||||
)
|
||||
try {
|
||||
const blockchain = await BlockchainSDK.getInstance()
|
||||
const tx = await blockchain.DiscoverService.setMetadata(dappId, metadata)
|
||||
dispatch(onReceiveTransactionInfoAction(dappId, tx))
|
||||
dispatch(checkTransactionStatusAction(tx))
|
||||
} catch (e) {
|
||||
dispatch(hideAction())
|
||||
dispatch(showAlertAction(e.message))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const switchToRatingAction = () => ({
|
||||
type: SWITCH_TO_RATING,
|
||||
paylaod: null
|
||||
});
|
||||
paylaod: null,
|
||||
})
|
||||
|
||||
export const onInputSntValueAction = sntValue => ({
|
||||
type: ON_INPUT_SNT_VALUE,
|
||||
payload: sntValue
|
||||
});
|
||||
payload: sntValue,
|
||||
})
|
||||
|
||||
const showSubmitAfterCheck = state => {
|
||||
const showSubmitAfterCheck = (state, dapp) => {
|
||||
return Object.assign({}, state, {
|
||||
visible_submit: true,
|
||||
visible_rating: false,
|
||||
id: '',
|
||||
name: '',
|
||||
desc: '',
|
||||
url: '',
|
||||
img: '',
|
||||
category: '',
|
||||
id: dapp !== undefined ? dapp.id : '',
|
||||
name: dapp !== undefined ? dapp.name : '',
|
||||
desc: dapp !== undefined ? dapp.desc : '',
|
||||
url: dapp !== undefined ? dapp.url : '',
|
||||
img: dapp !== undefined ? dapp.image : '',
|
||||
category: dapp !== undefined ? dapp.category : '',
|
||||
imgControl: false,
|
||||
imgControlZoom: 0,
|
||||
imgControlMove: false,
|
||||
imgControlX: 0,
|
||||
imgControlY: 0,
|
||||
sntValue: '0'
|
||||
});
|
||||
};
|
||||
sntValue: '0',
|
||||
})
|
||||
}
|
||||
|
||||
const closeSubmit = state => {
|
||||
return Object.assign({}, state, {
|
||||
visible: false
|
||||
});
|
||||
};
|
||||
visible: false,
|
||||
})
|
||||
}
|
||||
|
||||
const onInputName = (state, name) => {
|
||||
return Object.assign({}, state, {
|
||||
name
|
||||
});
|
||||
};
|
||||
name,
|
||||
})
|
||||
}
|
||||
|
||||
const onInputDesc = (state, desc) => {
|
||||
return Object.assign({}, state, {
|
||||
desc
|
||||
});
|
||||
};
|
||||
desc,
|
||||
})
|
||||
}
|
||||
|
||||
const onInputUrl = (state, url) => {
|
||||
return Object.assign({}, state, {
|
||||
url
|
||||
});
|
||||
};
|
||||
url,
|
||||
})
|
||||
}
|
||||
|
||||
const onSelectCategory = (state, category) => {
|
||||
return Object.assign({}, state, {
|
||||
category
|
||||
});
|
||||
};
|
||||
category,
|
||||
})
|
||||
}
|
||||
|
||||
const onImgRead = (state, imgBase64) => {
|
||||
return Object.assign({}, state, {
|
||||
@ -202,55 +250,55 @@ const onImgRead = (state, imgBase64) => {
|
||||
imgControlZoom: 0,
|
||||
imgControlMove: false,
|
||||
imgControlX: 0,
|
||||
imgControlY: 0
|
||||
});
|
||||
};
|
||||
imgControlY: 0,
|
||||
})
|
||||
}
|
||||
|
||||
const onImgZoom = (state, zoom) => {
|
||||
return Object.assign({}, state, {
|
||||
imgControlZoom: zoom
|
||||
});
|
||||
};
|
||||
imgControlZoom: zoom,
|
||||
})
|
||||
}
|
||||
|
||||
const onImgMoveControl = (state, move) => {
|
||||
return Object.assign({}, state, {
|
||||
imgControlMove: move
|
||||
});
|
||||
};
|
||||
imgControlMove: move,
|
||||
})
|
||||
}
|
||||
|
||||
const onImgMove = (state, payload) => {
|
||||
return Object.assign({}, state, {
|
||||
imgControlX: payload.x,
|
||||
imgControlY: payload.y
|
||||
});
|
||||
};
|
||||
imgControlY: payload.y,
|
||||
})
|
||||
}
|
||||
|
||||
const onImgCancel = state => {
|
||||
return Object.assign({}, state, {
|
||||
img: '',
|
||||
imgControl: false
|
||||
});
|
||||
};
|
||||
imgControl: false,
|
||||
})
|
||||
}
|
||||
|
||||
const onImgDone = (state, imgBase64) => {
|
||||
return Object.assign({}, state, {
|
||||
img: imgBase64,
|
||||
imgControl: false
|
||||
});
|
||||
};
|
||||
imgControl: false,
|
||||
})
|
||||
}
|
||||
|
||||
const switchToRating = state => {
|
||||
return Object.assign({}, state, {
|
||||
visible_submit: false,
|
||||
visible_rating: true
|
||||
});
|
||||
};
|
||||
visible_rating: true,
|
||||
})
|
||||
}
|
||||
|
||||
const onInputSntValue = (state, sntValue) => {
|
||||
return Object.assign({}, state, {
|
||||
sntValue
|
||||
});
|
||||
};
|
||||
sntValue,
|
||||
})
|
||||
}
|
||||
|
||||
const map = {
|
||||
[SHOW_SUBMIT_AFTER_CHECK]: showSubmitAfterCheck,
|
||||
@ -266,7 +314,7 @@ const map = {
|
||||
[ON_IMG_CANCEL]: onImgCancel,
|
||||
[ON_IMG_DONE]: onImgDone,
|
||||
[SWITCH_TO_RATING]: switchToRating,
|
||||
[ON_INPUT_SNT_VALUE]: onInputSntValue
|
||||
};
|
||||
[ON_INPUT_SNT_VALUE]: onInputSntValue,
|
||||
}
|
||||
|
||||
export default reducerUtil(map, submitInitialState);
|
||||
export default reducerUtil(map, submitInitialState)
|
||||
|
@ -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,15 @@ 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
|
||||
export const TYPE_UPDATE = 5
|
||||
|
||||
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
|
||||
|
@ -9,10 +9,13 @@ import {
|
||||
fetchVoteRatingAction,
|
||||
upVoteAction,
|
||||
downVoteAction,
|
||||
learnMoreUpVoteAction,
|
||||
learnMoreDownVoteAction,
|
||||
closeLearnMoreAction,
|
||||
} 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()),
|
||||
@ -23,6 +26,9 @@ const mapDispatchToProps = dispatch => ({
|
||||
},
|
||||
upVote: (dapp, amount) => dispatch(upVoteAction(dapp, amount)),
|
||||
downVote: (dapp, amount) => dispatch(downVoteAction(dapp, amount)),
|
||||
onClickLearnMoreUpVote: () => dispatch(learnMoreUpVoteAction()),
|
||||
onClickLearnMoreDownVote: () => dispatch(learnMoreDownVoteAction()),
|
||||
onClickCloseLearnMore: () => dispatch(closeLearnMoreAction()),
|
||||
})
|
||||
|
||||
export default withRouter(
|
||||
|
@ -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,9 +60,14 @@ class Vote extends Component {
|
||||
onClickClose,
|
||||
isUpvote,
|
||||
dapp,
|
||||
dapps,
|
||||
dappState,
|
||||
sntValue,
|
||||
afterVoteRating,
|
||||
learnMoreUpVote,
|
||||
learnMoreDownVote,
|
||||
onClickLearnMoreUpVote,
|
||||
onClickLearnMoreDownVote,
|
||||
onClickCloseLearnMore,
|
||||
} = this.props
|
||||
|
||||
if (dapp === null) {
|
||||
@ -71,9 +77,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) {
|
||||
@ -100,123 +104,200 @@ class Vote extends Component {
|
||||
windowClassName={styles.modalWindow}
|
||||
contentClassName={styles.modalContent}
|
||||
>
|
||||
<div className={styles.tabs}>
|
||||
<button
|
||||
className={isUpvote ? styles.active : ''}
|
||||
type="button"
|
||||
onClick={this.onClickUpvote}
|
||||
>
|
||||
↑ UPVOTE
|
||||
</button>
|
||||
<button
|
||||
className={!isUpvote ? styles.active : ''}
|
||||
type="button"
|
||||
onClick={this.onClickDownvote}
|
||||
>
|
||||
↓ DOWNVOTE
|
||||
</button>
|
||||
</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>
|
||||
{isUpvote && afterVoteRating !== null && (
|
||||
<span className={styles.greenBadge}>
|
||||
{`${(dapp.sntValue + afterVoteRating).toLocaleString()} ↑`}
|
||||
</span>
|
||||
)}
|
||||
{!isUpvote && afterVoteRating !== null && (
|
||||
<span className={styles.redBadge}>
|
||||
{`${(dapp.sntValue - 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"
|
||||
{!learnMoreUpVote && !learnMoreDownVote && (
|
||||
<>
|
||||
<div className={styles.tabs}>
|
||||
<button
|
||||
className={isUpvote ? styles.active : ''}
|
||||
type="button"
|
||||
onClick={this.onClickUpvote}
|
||||
>
|
||||
↑ UPVOTE
|
||||
</button>
|
||||
<button
|
||||
className={!isUpvote ? styles.active : ''}
|
||||
type="button"
|
||||
onClick={this.onClickDownvote}
|
||||
>
|
||||
↓ DOWNVOTE
|
||||
</button>
|
||||
</div>
|
||||
<div className={styles.dapp}>
|
||||
<ReactImageFallback
|
||||
className={styles.image}
|
||||
src={dapp.image}
|
||||
fallbackImage={icon}
|
||||
alt="App icon"
|
||||
width={24}
|
||||
height={24}
|
||||
/>
|
||||
{`${getCategoryName(dapp.category)} №${catPosition}`}
|
||||
</span>
|
||||
{isUpvote &&
|
||||
afterVoteCategoryPosition !== null &&
|
||||
afterVoteCategoryPosition !== catPosition && (
|
||||
<span className={styles.greenBadge}>
|
||||
{`№${afterVoteCategoryPosition} ↑`}
|
||||
{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>
|
||||
)}
|
||||
{!isUpvote &&
|
||||
afterVoteCategoryPosition !== null &&
|
||||
afterVoteCategoryPosition !== catPosition && (
|
||||
<span className={styles.redBadge}>
|
||||
{`№${afterVoteCategoryPosition} ↓`}
|
||||
{isUpvote && afterVoteRating !== null && (
|
||||
<span className={styles.greenBadge}>
|
||||
{`${(dapp.sntValue + afterVoteRating).toLocaleString()} ↑`}
|
||||
</span>
|
||||
)}
|
||||
{!isUpvote && afterVoteRating !== null && (
|
||||
<span className={styles.redBadge}>
|
||||
{`${(dapp.sntValue - 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>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{!isUpvote && (
|
||||
<div
|
||||
className={styles.inputArea}
|
||||
style={{ opacity: sntValue !== '0' ? 1 : 0 }}
|
||||
>
|
||||
<span>{sntValue}</span>
|
||||
</div>
|
||||
)}
|
||||
{isUpvote && (
|
||||
<div className={`${styles.inputArea} ${styles.inputAreaBorder}`}>
|
||||
<input
|
||||
type="text"
|
||||
value={sntValue}
|
||||
onChange={this.handleChange}
|
||||
style={{ width: `${21 * Math.max(1, sntValue.length)}px` }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{isUpvote &&
|
||||
afterVoteCategoryPosition !== null &&
|
||||
afterVoteCategoryPosition !== catPosition && (
|
||||
<span className={styles.greenBadge}>
|
||||
{`№${afterVoteCategoryPosition} ↑`}
|
||||
</span>
|
||||
)}
|
||||
{!isUpvote &&
|
||||
afterVoteCategoryPosition !== null &&
|
||||
afterVoteCategoryPosition !== catPosition && (
|
||||
<span className={styles.redBadge}>
|
||||
{`№${afterVoteCategoryPosition} ↓`}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{!isUpvote && (
|
||||
<div
|
||||
className={styles.inputArea}
|
||||
style={{ opacity: sntValue !== '0' ? 1 : 0 }}
|
||||
>
|
||||
<span>{sntValue}</span>
|
||||
</div>
|
||||
)}
|
||||
{isUpvote && (
|
||||
<div className={`${styles.inputArea} ${styles.inputAreaBorder}`}>
|
||||
<input
|
||||
type="text"
|
||||
value={sntValue}
|
||||
onChange={this.handleChange}
|
||||
style={{ width: `${21 * Math.max(1, sntValue.length)}px` }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className={styles.footer}>
|
||||
{isUpvote && (
|
||||
<p className={styles.disclaimer}>
|
||||
SNT you spend to upvote is locked in the contract and contributes
|
||||
directly to {dapp.name}'s ranking.{' '}
|
||||
<a href="#" target="_blank">
|
||||
Learn more↗
|
||||
</a>
|
||||
</p>
|
||||
)}
|
||||
{!isUpvote && (
|
||||
<p className={styles.disclaimer}>
|
||||
SNT you spend to downvote goes directly back to {dapp.name}.
|
||||
Downvoting moves their DApp down by 1% of the current ranking. The
|
||||
cost is fixed by our unique bonded curve.{' '}
|
||||
<a href="#" target="_blank">
|
||||
Learn more↗
|
||||
</a>
|
||||
</p>
|
||||
)}
|
||||
<button
|
||||
type="submit"
|
||||
disabled={(!sntValue || sntValue === '0') && isUpvote}
|
||||
onClick={this.onClickVote}
|
||||
>
|
||||
{isUpvote ? 'Upvote' : 'Downvote'}
|
||||
</button>
|
||||
</div>
|
||||
<div className={styles.footer}>
|
||||
{isUpvote && (
|
||||
<p className={styles.disclaimer}>
|
||||
SNT you spend to upvote is locked in the contract and
|
||||
contributes directly to {dapp.name}'s ranking.{' '}
|
||||
<a onClick={onClickLearnMoreUpVote}>Learn more↗</a>
|
||||
</p>
|
||||
)}
|
||||
{!isUpvote && (
|
||||
<p className={styles.disclaimer}>
|
||||
SNT you spend to downvote goes directly back to {dapp.name}.
|
||||
Downvoting moves their DApp down by 1% of the current ranking.
|
||||
The cost is fixed by our unique bonded curve.{' '}
|
||||
<a onClick={onClickLearnMoreDownVote}>Learn more↗</a>
|
||||
</p>
|
||||
)}
|
||||
<button
|
||||
type="submit"
|
||||
disabled={(!sntValue || sntValue === '0') && isUpvote}
|
||||
onClick={this.onClickVote}
|
||||
>
|
||||
{isUpvote ? 'Upvote' : 'Downvote'}
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{learnMoreUpVote && (
|
||||
<div className={styles.learnMoreCnt}>
|
||||
<div className={styles.title}>How to submit a ÐApp</div>
|
||||
<div className={styles.spacing}>
|
||||
<img src="/images/learn-more-curve.png" />
|
||||
<p>
|
||||
This is what the curve you're using really looks like. The more
|
||||
SNT staked on a DApp, the cheaper it becomes for anyone to
|
||||
downvote it.
|
||||
</p>
|
||||
<p>
|
||||
However, you can upvote this DApp by any amount of SNT you wish.
|
||||
SNT you spend is sent directly to the contract and locked up
|
||||
there. It does not
|
||||
</p>
|
||||
<p>
|
||||
go to Status, the developer of the DApp, or any other middleman.
|
||||
There are no fees, but once the SNT is spent, you cannot get it
|
||||
back.
|
||||
</p>
|
||||
<p>
|
||||
What you spend is added directly to the DApp's balance, and the
|
||||
effect this will have on it's ranking is shown in the previous
|
||||
screen.
|
||||
</p>
|
||||
<p>Welcome to the future of decentralised curation!</p>
|
||||
</div>
|
||||
<div className={styles.backButtonCnt}>
|
||||
<button
|
||||
type="submit"
|
||||
onClick={onClickCloseLearnMore}
|
||||
className={styles.backButton}
|
||||
>
|
||||
Back
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{learnMoreDownVote && (
|
||||
<div className={styles.learnMoreCnt}>
|
||||
<div className={styles.title}>How to submit a ÐApp</div>
|
||||
<div className={styles.spacing}>
|
||||
<img src="/images/learn-more-curve.png" />
|
||||
<p>
|
||||
This is what the curve you're using really looks like. The more
|
||||
SNT staked on a DApp, the cheaper it becomes for anyone to
|
||||
downvote it.
|
||||
</p>
|
||||
<p>
|
||||
You can downvote this DApp, and each downvote will move it 1% of
|
||||
its current value down the rankings.{' '}
|
||||
</p>
|
||||
<p>
|
||||
SNT you spend is sent directly to the developer of the DApp, so
|
||||
we can be sure you aren't just trolling them and that, even if
|
||||
you are, you are required to pay for the privilege. It does not
|
||||
go to Status or any other middleman. There are no fees, but once
|
||||
the SNT is spent, you cannot get it back.
|
||||
</p>
|
||||
<p>
|
||||
What you spend is dictated by how much SNT the DApp has already
|
||||
staked, and the exact numerical effect of moving 1% down in the
|
||||
rankings is shown in the previous screen.
|
||||
</p>
|
||||
<p>Welcome to the future of decentralised curation!</p>
|
||||
</div>
|
||||
<div className={styles.backButtonCnt}>
|
||||
<button
|
||||
type="submit"
|
||||
onClick={onClickCloseLearnMore}
|
||||
className={styles.backButton}
|
||||
>
|
||||
Back
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
@ -233,6 +314,8 @@ Vote.propTypes = {
|
||||
visible: PropTypes.bool.isRequired,
|
||||
sntValue: PropTypes.string.isRequired,
|
||||
afterVoteRating: PropTypes.number,
|
||||
learnMoreDownVote: PropTypes.bool.isRequired,
|
||||
learnMoreUpVote: PropTypes.bool.isRequired,
|
||||
onClickClose: PropTypes.func.isRequired,
|
||||
onClickUpvote: PropTypes.func.isRequired,
|
||||
onClickDownvote: PropTypes.func.isRequired,
|
||||
@ -240,6 +323,10 @@ Vote.propTypes = {
|
||||
fetchVoteRating: PropTypes.func.isRequired,
|
||||
upVote: PropTypes.func.isRequired,
|
||||
downVote: PropTypes.func.isRequired,
|
||||
dappState: PropTypes.instanceOf(DappState).isRequired,
|
||||
onClickLearnMoreUpVote: PropTypes.func.isRequired,
|
||||
onClickLearnMoreDownVote: PropTypes.func.isRequired,
|
||||
onClickCloseLearnMore: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
export default Vote
|
||||
|
@ -213,4 +213,62 @@
|
||||
.footer .disclaimer a {
|
||||
text-decoration: none;
|
||||
color: $link-color;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.learnMoreCnt {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-family: $font;
|
||||
|
||||
.title {
|
||||
line-height: 40px;
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
font-weight: 600;
|
||||
font-size: 17px;
|
||||
border-bottom: 1px solid #f7f7f7;
|
||||
}
|
||||
|
||||
.spacing {
|
||||
margin: 14px 16px;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
p {
|
||||
line-height: 24px;
|
||||
text-indent: 32px;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.backButtonCnt {
|
||||
height: 64px;
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-top: 1px solid #f7f7f7;
|
||||
margin-top: auto;
|
||||
|
||||
.backButton {
|
||||
background: $link-color;
|
||||
border-radius: 8px;
|
||||
color: #fff;
|
||||
border: none;
|
||||
font-family: $font;
|
||||
padding: calculateRem(11) calculateRem(38);
|
||||
font-size: calculateRem(15);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: $desktop) {
|
||||
.learnMoreCnt {
|
||||
min-height: 512px;
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,9 @@ const SWITCH_TO_DOWNVOTE = 'VOTE_SWITCH_TO_DOWNVOTE'
|
||||
const ON_INPUT_SNT_VALUE = 'VOTE_ON_INPUT_SNT_VALUE'
|
||||
const UPDATE_AFTER_UP_VOTING_VALUES = 'VOTE_UPDATE_AFTER_UP_VOTING_VALUES'
|
||||
const UPDATE_AFTER_DOWN_VOTING_VALUES = 'VOTE_UPDATE_AFTER_DOWN_VOTING_VALUES'
|
||||
const LEARN_MORE_UPVOTE = 'VOTE_LEARN_MORE_UPVOTE'
|
||||
const LEARN_MORE_DOWNVOTE = 'VOTE_LEARN_MORE_DOWNVOTE'
|
||||
const CLOSE_LEARN_MORE = 'VOTE_CLOSE_LEARN_MORE'
|
||||
|
||||
export const showUpVoteActionAfterCheck = dapp => {
|
||||
window.location.hash = 'vote'
|
||||
@ -41,7 +44,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',
|
||||
@ -54,7 +60,10 @@ export const showUpVoteAction = dapp => {
|
||||
export const showDownVoteAction = 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',
|
||||
@ -187,6 +196,21 @@ export const downVoteAction = (dapp, amount) => {
|
||||
}
|
||||
}
|
||||
|
||||
export const learnMoreUpVoteAction = () => ({
|
||||
type: LEARN_MORE_UPVOTE,
|
||||
payload: null,
|
||||
})
|
||||
|
||||
export const learnMoreDownVoteAction = () => ({
|
||||
type: LEARN_MORE_DOWNVOTE,
|
||||
payload: null,
|
||||
})
|
||||
|
||||
export const closeLearnMoreAction = () => ({
|
||||
type: CLOSE_LEARN_MORE,
|
||||
payload: null,
|
||||
})
|
||||
|
||||
const showUpVoteAfterCheck = (state, dapp) => {
|
||||
return Object.assign({}, state, {
|
||||
visible: true,
|
||||
@ -194,6 +218,8 @@ const showUpVoteAfterCheck = (state, dapp) => {
|
||||
sntValue: '0',
|
||||
isUpvote: true,
|
||||
afterVoteRating: null,
|
||||
learnMoreUpVote: false,
|
||||
learnMoreDownVote: false,
|
||||
})
|
||||
}
|
||||
|
||||
@ -204,6 +230,8 @@ const showDownVoteAfterCheck = (state, dapp) => {
|
||||
sntValue: '0',
|
||||
isUpvote: false,
|
||||
afterVoteRating: null,
|
||||
learnMoreUpVote: false,
|
||||
learnMoreDownVote: false,
|
||||
})
|
||||
}
|
||||
|
||||
@ -211,6 +239,8 @@ const closeVote = state => {
|
||||
return Object.assign({}, state, {
|
||||
visible: false,
|
||||
dapp: null,
|
||||
learnMoreUpVote: false,
|
||||
learnMoreDownVote: false,
|
||||
})
|
||||
}
|
||||
|
||||
@ -250,6 +280,27 @@ const updateAfterDownVotingValues = (state, payload) => {
|
||||
})
|
||||
}
|
||||
|
||||
const learnMoreUpVote = state => {
|
||||
return Object.assign({}, state, {
|
||||
learnMoreUpVote: true,
|
||||
learnMoreDownVote: false,
|
||||
})
|
||||
}
|
||||
|
||||
const learnMoreDownVote = state => {
|
||||
return Object.assign({}, state, {
|
||||
learnMoreUpVote: false,
|
||||
learnMoreDownVote: true,
|
||||
})
|
||||
}
|
||||
|
||||
const closeLearnMore = state => {
|
||||
return Object.assign({}, state, {
|
||||
learnMoreUpVote: false,
|
||||
learnMoreDownVote: false,
|
||||
})
|
||||
}
|
||||
|
||||
const map = {
|
||||
[SHOW_UP_VOTE_AFTER_CHECK]: showUpVoteAfterCheck,
|
||||
[SHOW_DOWN_VOTE_AFTER_CHEECK]: showDownVoteAfterCheck,
|
||||
@ -259,6 +310,9 @@ const map = {
|
||||
[ON_INPUT_SNT_VALUE]: onInputSntValue,
|
||||
[UPDATE_AFTER_UP_VOTING_VALUES]: updateAfterUpVotingValues,
|
||||
[UPDATE_AFTER_DOWN_VOTING_VALUES]: updateAfterDownVotingValues,
|
||||
[LEARN_MORE_UPVOTE]: learnMoreUpVote,
|
||||
[LEARN_MORE_DOWNVOTE]: learnMoreDownVote,
|
||||
[CLOSE_LEARN_MORE]: closeLearnMore,
|
||||
}
|
||||
|
||||
export default reducerUtil(map, voteInitialState)
|
||||
|
24
src/modules/Withdraw/Withdraw.container.js
Normal file
24
src/modules/Withdraw/Withdraw.container.js
Normal file
@ -0,0 +1,24 @@
|
||||
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),
|
||||
)
|
157
src/modules/Withdraw/Withdraw.jsx
Normal file
157
src/modules/Withdraw/Withdraw.jsx
Normal file
@ -0,0 +1,157 @@
|
||||
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
|
412
src/modules/Withdraw/Withdraw.module.scss
Normal file
412
src/modules/Withdraw/Withdraw.module.scss
Normal file
@ -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;
|
||||
}
|
105
src/modules/Withdraw/Withdraw.reducer.js
Normal file
105
src/modules/Withdraw/Withdraw.reducer.js
Normal file
@ -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)
|
3
src/modules/Withdraw/index.js
Normal file
3
src/modules/Withdraw/index.js
Normal file
@ -0,0 +1,3 @@
|
||||
import Withdraw from './Withdraw.container'
|
||||
|
||||
export default Withdraw
|
Loading…
x
Reference in New Issue
Block a user