Vote.jsx
1 import React, { Component } from 'react' 2 import PropTypes from 'prop-types' 3 import ReactImageFallback from 'react-image-fallback' 4 import styles from './Vote.module.scss' 5 import sntIcon from '../../common/assets/images/SNT.svg' 6 import CategoriesUtils from '../Categories/Categories.utils' 7 import Categories from '../../common/utils/categories' 8 import icon from '../../common/assets/images/icon.svg' 9 import Modal from '../../common/components/Modal' 10 import { DappModel } from '../../common/utils/models' 11 12 const getCategoryName = category => 13 Categories.find(x => x.key === category).value 14 15 class Vote extends Component { 16 constructor(props) { 17 super(props) 18 this.onClickUpvote = this.onClickUpvote.bind(this) 19 this.onClickDownvote = this.onClickDownvote.bind(this) 20 this.handleChange = this.handleChange.bind(this) 21 this.onClickVote = this.onClickVote.bind(this) 22 } 23 24 onClickUpvote() { 25 const { dapp, onClickUpvote, fetchVoteRating } = this.props 26 let { sntValue } = this.props 27 if (sntValue === '') sntValue = 0 28 onClickUpvote() 29 fetchVoteRating(dapp, true, parseInt(sntValue, 10)) 30 } 31 32 onClickDownvote() { 33 const { dapp, onClickDownvote, fetchVoteRating } = this.props 34 onClickDownvote() 35 fetchVoteRating(dapp, false) 36 } 37 38 handleChange(e) { 39 const { value } = e.target 40 if (value !== '' && /^[1-9][0-9]*$/.test(value) === false) return 41 42 const intValue = value === '' ? 0 : parseInt(value, 10) 43 if (intValue > 1571296) return 44 45 const { dapp, onInputSntValue, fetchVoteRating } = this.props 46 onInputSntValue(value) 47 fetchVoteRating(dapp, true, intValue) 48 } 49 50 onClickVote() { 51 const { dapp, sntValue, isUpvote, upVote, downVote } = this.props 52 if (isUpvote) upVote(dapp, parseInt(sntValue, 10)) 53 else downVote(dapp, sntValue) 54 } 55 56 render() { 57 const { 58 visible, 59 onClickClose, 60 isUpvote, 61 dapp, 62 dapps, 63 sntValue, 64 afterVoteRating, 65 } = this.props 66 67 if (dapp === null) { 68 return <Modal visible={false} onClickClose={onClickClose} /> 69 } 70 71 //const catPosition = dapp.categoryPosition 72 // const upvoteSNTcost = currentSNTamount + parseInt(sntValue, 10) 73 const currentSNTamount = dapp.sntValue 74 const dappsByCategory = dapps.filter( 75 dapp_ => dapp_.category === dapp.category, 76 ) 77 78 let catPosition = dappsByCategory.length 79 for (let i = 0; i < dappsByCategory.length; ++i) { 80 if (dapp.id === dappsByCategory[i].id) { 81 catPosition = i + 1 82 break 83 } 84 } 85 86 let afterVoteCategoryPosition = null 87 if (afterVoteRating !== null) { 88 afterVoteCategoryPosition = 1 89 for (let i = 0; i < dappsByCategory.length; ++i) { 90 if (dappsByCategory[i].id === dapp.id) continue 91 if (dappsByCategory[i].sntValue < dapp.sntValue + afterVoteRating) break 92 afterVoteCategoryPosition++ 93 } 94 } 95 96 return ( 97 <Modal 98 visible={visible && window.location.hash === '#vote'} 99 onClickClose={onClickClose} 100 windowClassName={styles.modalWindow} 101 contentClassName={styles.modalContent} 102 > 103 <div className={styles.tabs}> 104 <button 105 className={isUpvote ? styles.active : ''} 106 type="button" 107 onClick={this.onClickUpvote} 108 > 109 ↑ UPVOTE 110 </button> 111 <button 112 className={!isUpvote ? styles.active : ''} 113 type="button" 114 onClick={this.onClickDownvote} 115 > 116 ↓ DOWNVOTE 117 </button> 118 </div> 119 <div className={styles.dapp}> 120 <ReactImageFallback 121 className={styles.image} 122 src={dapp.image} 123 fallbackImage={icon} 124 alt="App icon" 125 width={24} 126 height={24} 127 /> 128 {dapp.name} 129 </div> 130 <div className={styles.items}> 131 <div className={styles.itemRow}> 132 <span className={styles.item}> 133 <img src={sntIcon} alt="SNT" width="24" height="24" /> 134 {currentSNTamount.toLocaleString()} 135 </span> 136 {isUpvote && afterVoteRating !== null && ( 137 <span className={styles.greenBadge}> 138 {`${(dapp.sntValue + afterVoteRating).toLocaleString()} ↑`} 139 </span> 140 )} 141 {!isUpvote && afterVoteRating !== null && ( 142 <span className={styles.redBadge}> 143 {`${(dapp.sntValue - afterVoteRating).toLocaleString()} ↓`} 144 </span> 145 )} 146 </div> 147 <div className={styles.itemRow}> 148 <span className={styles.item}> 149 <img 150 src={CategoriesUtils(dapp.category)} 151 alt={getCategoryName(dapp.category)} 152 width="24" 153 height="24" 154 /> 155 {`${getCategoryName(dapp.category)} №${catPosition}`} 156 </span> 157 {isUpvote && 158 afterVoteCategoryPosition !== null && 159 afterVoteCategoryPosition !== catPosition && ( 160 <span className={styles.greenBadge}> 161 {`№${afterVoteCategoryPosition} ↑`} 162 </span> 163 )} 164 {!isUpvote && 165 afterVoteCategoryPosition !== null && 166 afterVoteCategoryPosition !== catPosition && ( 167 <span className={styles.redBadge}> 168 {`№${afterVoteCategoryPosition} ↓`} 169 </span> 170 )} 171 </div> 172 </div> 173 {!isUpvote && ( 174 <div 175 className={styles.inputArea} 176 style={{ opacity: sntValue !== '0' ? 1 : 0 }} 177 > 178 <span>{sntValue}</span> 179 </div> 180 )} 181 {isUpvote && ( 182 <div className={`${styles.inputArea} ${styles.inputAreaBorder}`}> 183 <input 184 type="text" 185 value={sntValue} 186 onChange={this.handleChange} 187 style={{ width: `${21 * Math.max(1, sntValue.length)}px` }} 188 /> 189 </div> 190 )} 191 192 <div className={styles.footer}> 193 {isUpvote && ( 194 <p className={styles.disclaimer}> 195 SNT you spend to upvote is locked in the contract and contributes 196 directly to {dapp.name}'s ranking.{' '} 197 <a href="#" target="_blank"> 198 Learn more↗ 199 </a> 200 </p> 201 )} 202 {!isUpvote && ( 203 <p className={styles.disclaimer}> 204 SNT you spend to downvote goes directly back to {dapp.name}. 205 Downvoting moves their DApp down by 1% of the current ranking. The 206 cost is fixed by our unique bonded curve.{' '} 207 <a href="#" target="_blank"> 208 Learn more↗ 209 </a> 210 </p> 211 )} 212 <button 213 type="submit" 214 disabled={(!sntValue || sntValue === '0') && isUpvote} 215 onClick={this.onClickVote} 216 > 217 {isUpvote ? 'Upvote' : 'Downvote'} 218 </button> 219 </div> 220 </Modal> 221 ) 222 } 223 } 224 225 Vote.defaultProps = { 226 dapp: null, 227 afterVoteRating: null, 228 } 229 230 Vote.propTypes = { 231 dapp: PropTypes.shape(DappModel), 232 isUpvote: PropTypes.bool.isRequired, 233 visible: PropTypes.bool.isRequired, 234 sntValue: PropTypes.string.isRequired, 235 afterVoteRating: PropTypes.number, 236 onClickClose: PropTypes.func.isRequired, 237 onClickUpvote: PropTypes.func.isRequired, 238 onClickDownvote: PropTypes.func.isRequired, 239 onInputSntValue: PropTypes.func.isRequired, 240 fetchVoteRating: PropTypes.func.isRequired, 241 upVote: PropTypes.func.isRequired, 242 downVote: PropTypes.func.isRequired, 243 } 244 245 export default Vote