diff --git a/ICO_wizard_state_example.json b/ICO_wizard_state_example.json index a613d825b..6ce896dd3 100644 --- a/ICO_wizard_state_example.json +++ b/ICO_wizard_state_example.json @@ -2520,11 +2520,6 @@ "_store": {} } ], - "whiteListInput": { - "addr": "", - "min": "", - "max": "" - }, "tier": "Tier 1", "updatable": "on", "whitelistdisabled": "no" @@ -2535,10 +2530,9 @@ "updatable": "on", "whitelist": [], "whiteListElements": [], - "whiteListInput": {}, "startTime": "2017-10-03T00:05:00", "endTime": "2017-10-07T00:05:00" } ], "children": [] -} \ No newline at end of file +} diff --git a/package-lock.json b/package-lock.json index 2d0ab18ab..f3e205ad5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -445,6 +445,14 @@ "integrity": "sha1-GcenYEc3dEaPILLS0DNyrX1Mv10=", "dev": true }, + "attr-accept": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-1.1.2.tgz", + "integrity": "sha512-NUj0itVSnpFkUYCj3XKSRCZ7N9gPwWcyX/tF7HosqyDBPMSygALivvJIGI8VvlPcunns5khMkpxoNshvmhy/ZQ==", + "requires": { + "core-js": "2.5.3" + } + }, "autolinker": { "version": "0.15.3", "resolved": "https://registry.npmjs.org/autolinker/-/autolinker-0.15.3.tgz", @@ -10668,6 +10676,11 @@ "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.6.tgz", "integrity": "sha512-lQe48YPsMJAig+yngZ87Lus+NF+3mtu7DVOBu6b/gHO1YpKwIj5AWjZ/TOS7i46HD/UixzWb1zeWDZfGZ3iYcg==" }, + "papaparse": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-4.3.7.tgz", + "integrity": "sha1-7R5xgzINHgg53+n3GGGFz8UJe40=" + }, "param-case": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz", @@ -12643,6 +12656,15 @@ "prop-types": "15.6.0" } }, + "react-dropzone": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-4.2.8.tgz", + "integrity": "sha512-L/q6ySfhdG9Md3P21jFumzlm92TxRT0FtYX6G793Nf8bt7Fzpwx6gJsPk0idV094koj/Y5vRpp0q9+e0bdsjxw==", + "requires": { + "attr-accept": "1.1.2", + "prop-types": "15.6.0" + } + }, "react-error-overlay": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-1.0.10.tgz", diff --git a/package.json b/package.json index 0c1f0adc8..b8f39b6e6 100755 --- a/package.json +++ b/package.json @@ -69,6 +69,7 @@ "mobx-react": "^4.3.3", "moment": "^2.20.1", "object-assign": "4.1.1", + "papaparse": "^4.3.7", "path": "^0.12.7", "postcss-flexbugs-fixes": "3.0.0", "postcss-loader": "2.0.6", @@ -81,6 +82,7 @@ "react-countdown-clock": "^2.0.0", "react-dev-utils": "^3.0.2", "react-dom": "^15.6.1", + "react-dropzone": "^4.2.8", "react-error-overlay": "^1.0.9", "react-router-dom": "^4.1.2", "solc": "^0.4.14", diff --git a/src/components/Common/WhitelistInputBlock.js b/src/components/Common/WhitelistInputBlock.js index b5337cbfc..28025424b 100644 --- a/src/components/Common/WhitelistInputBlock.js +++ b/src/components/Common/WhitelistInputBlock.js @@ -1,11 +1,13 @@ import React from 'react' import Web3 from 'web3'; import update from 'immutability-helper'; +import Dropzone from 'react-dropzone'; +import Papa from 'papaparse' import '../../assets/stylesheets/application.css'; import { InputField } from './InputField' -import { TEXT_FIELDS, defaultState, VALIDATION_TYPES } from '../../utils/constants' +import { TEXT_FIELDS, VALIDATION_TYPES } from '../../utils/constants' +import { validateAddress } from '../../utils/utils' import { WhitelistItem } from './WhitelistItem' -import { getOldState } from '../../utils/utils' import { inject, observer } from 'mobx-react' const { ADDRESS, MIN, MAX } = TEXT_FIELDS const {VALID, INVALID} = VALIDATION_TYPES; @@ -15,22 +17,23 @@ const {VALID, INVALID} = VALIDATION_TYPES; export class WhitelistInputBlock extends React.Component { constructor (props) { super(props) - let oldState = getOldState(props, defaultState) - this.state = Object.assign({}, oldState, { + this.state = { + addr: '', + min: '', + max: '', validation: { address: { pristine: true, valid: INVALID } } - }) + } } addWhitelistItem = () => { const { tierStore } = this.props const crowdsaleNum = this.props.num - const tier = tierStore.tiers[crowdsaleNum] - const { addr, min, max } = tier.whitelistInput + const { addr, min, max } = this.state this.setState(update(this.state, { validation: { @@ -44,51 +47,26 @@ export class WhitelistInputBlock extends React.Component { return } - this.setState(update(this.state, { + this.setState({ + addr: '', + min: '', + max: '', validation: { address: { - $set: { - pristine: true, - valid: INVALID - } + pristine: true, + valid: INVALID } } - })) - - this.clearWhiteListInputs() - - const whitelist = tier.whitelist.slice() - - const isAdded = whitelist.find(item => item.addr === addr && !item.deleted) - - if (isAdded) return - - const whitelistElements = tier.whitelistElements.slice() - const whitelistNum = whitelistElements.length - - whitelistElements.push({ addr, min, max, whitelistNum, crowdsaleNum }) - whitelist.push({ addr, min, max }) - - tierStore.setTierProperty(whitelistElements, 'whitelistElements', crowdsaleNum) - tierStore.setTierProperty(whitelist, 'whitelist', crowdsaleNum) - } + }) - clearWhiteListInputs = () => { - const whitelistInput = { - addr: '', - min: '', - max: '' - } - this.props.tierStore.setTierProperty(whitelistInput, 'whitelistInput', this.props.num) + tierStore.addWhitelistItem({ addr, min, max }, crowdsaleNum) } - handleAddressChange = e => { - this.props.onChange(e, 'crowdsale', this.props.num, 'whitelist_addr') - - const address = e.target.value + handleAddressChange = address => { const isAddressValid = Web3.utils.isAddress(address) ? VALID : INVALID; const newState = update(this.state, { + addr: { $set: address }, validation: { address: { $set: { @@ -102,20 +80,61 @@ export class WhitelistInputBlock extends React.Component { this.setState(newState) } + isAddress = (address) => validateAddress(address) + isNumber = (number) => !isNaN(parseFloat(number)) + + onDrop = (acceptedFiles, rejectedFiles) => { + acceptedFiles.forEach(file => { + Papa.parse(file, { + skipEmptyLines: true, + complete: results => { + results.data.forEach((row) => { + if (row.length !== 3) return + + const [addr, min, max] = row + + if (!this.isAddress(addr) || !this.isNumber(min) || !this.isNumber(max)) return + + this.props.tierStore.addWhitelistItem({ addr, min, max }, this.props.num) + }) + } + }) + }) + } + + render () { const { num } = this.props - const { whitelistInput, whitelistElements } = this.props.tierStore.tiers[num] + const { whitelistElements } = this.props.tierStore.tiers[num] + + const dropzoneStyle = { + position: 'relative', + marginTop: '-15px', + marginBottom: '15px' + } + const uploadCSVStyle = { + textDecoration: 'underline', + cursor: 'pointer' + } return (
+ + Upload CSV + +
this.handleAddressChange(e)} + value={this.state.addr} + onChange={e => this.handleAddressChange(e.target.value)} description={`Address of a whitelisted account. Whitelists are inherited. E.g., if an account whitelisted on Tier 1 and didn't buy max cap on Tier 1, he can buy on Tier 2, and following tiers.`} pristine={this.state.validation.address.pristine} valid={this.state.validation.address.valid} @@ -125,16 +144,16 @@ export class WhitelistInputBlock extends React.Component { side='white-list-input-property white-list-input-property-middle' type='number' title={MIN} - value={whitelistInput && whitelistInput.min} - onChange={e => this.props.onChange(e, 'crowdsale', num, 'whitelist_min')} + value={this.state.min} + onChange={e => this.setState({ min: e.target.value })} description={`Minimum amount tokens to buy. Not a minimal size of a transaction. If minCap is 1 and user bought 1 token in a previous transaction and buying 0.1 token it will allow him to buy.`} /> this.props.onChange(e, 'crowdsale', num, 'whitelist_max')} + value={this.state.max} + onChange={e => this.setState({ max: e.target.value })} description={`Maximum is the hard limit.`} />
diff --git a/src/components/Common/WhitelistItem.js b/src/components/Common/WhitelistItem.js index 6a96292c5..0b357055a 100644 --- a/src/components/Common/WhitelistItem.js +++ b/src/components/Common/WhitelistItem.js @@ -7,7 +7,7 @@ import { inject, observer } from 'mobx-react' export class WhitelistItem extends React.Component { removeItem () { const { tierStore, crowdsaleNum, whitelistNum } = this.props - tierStore.removeWhiteListItem(whitelistNum, crowdsaleNum) + tierStore.removeWhitelistItem(whitelistNum, crowdsaleNum) } render () { diff --git a/src/components/manage/index.js b/src/components/manage/index.js index d5ce0cb7a..e1e6e2a06 100644 --- a/src/components/manage/index.js +++ b/src/components/manage/index.js @@ -370,14 +370,6 @@ export class Manage extends Component { }) } - changeState = (event, parent, key, property) => { - const { tierStore } = this.props - const whitelistInputProps = { ...tierStore.tiers[key].whitelistInput } - const prop = property.split('_')[1] - whitelistInputProps[prop] = event.target.value - tierStore.setTierProperty(whitelistInputProps, 'whitelistInput', key) - } - clickedWhiteListInputBlock = e => { if (e.target.classList.contains('button_fill_plus')) { this.setState({ formPristine: false }) @@ -389,7 +381,6 @@ export class Manage extends Component { this.changeState(e, contract, num, prop)} /> ) } diff --git a/src/components/manage/utils.js b/src/components/manage/utils.js index 739ce6b76..5fff889dd 100644 --- a/src/components/manage/utils.js +++ b/src/components/manage/utils.js @@ -202,12 +202,7 @@ export const processTier = (crowdsaleAddress, crowdsaleNum) => { const newTier = { whitelist: [], - whitelistElements: [], - whitelistInput: { - addr: '', - min: '', - max: '' - } + whitelistElements: [] } const initialValues = {} diff --git a/src/components/stepFour/utils.js b/src/components/stepFour/utils.js index 6ffaf6076..85743f3e2 100644 --- a/src/components/stepFour/utils.js +++ b/src/components/stepFour/utils.js @@ -402,7 +402,6 @@ export const addWhitelist = () => { for (let i = 0; i <= round; i++) { const tier = tierStore.tiers[i] - const whitelistInput = tier.whitelistInput for (let j = 0; j < tier.whitelist.length; j++) { let itemIsAdded = false @@ -418,25 +417,6 @@ export const addWhitelist = () => { whitelist.push.apply(whitelist, tier.whitelist) } } - - if (whitelistInput.addr && whitelistInput.min && whitelistInput.max) { - let itemIsAdded = false - - for (let k = 0; k < whitelist.length; k++) { - if (whitelist[k].addr === whitelistInput.addr) { - itemIsAdded = true - break - } - } - - if (!itemIsAdded) { - whitelist.push({ - 'addr': whitelistInput.addr, - 'min': whitelistInput.min, - 'max': whitelistInput.max - }) - } - } } console.log('whitelist:', whitelist) diff --git a/src/components/stepThree/CrowdsaleBlock.js b/src/components/stepThree/CrowdsaleBlock.js index d21b715ef..86002e726 100644 --- a/src/components/stepThree/CrowdsaleBlock.js +++ b/src/components/stepThree/CrowdsaleBlock.js @@ -18,16 +18,6 @@ export class CrowdsaleBlock extends React.Component { tierStore.setTierProperty(startTime, "startTime", num); tierStore.setTierProperty(endTime, "endTime", num); } - changeState = (event, parent, key, property) => { - if (property.indexOf("whitelist_") === 0) { - const { tierStore } = this.props; - const whitelistInputProps = { ...tierStore.tiers[key].whitelistInput }; - const prop = property.split("_")[1]; - - whitelistInputProps[prop] = event.target.value; - tierStore.setTierProperty(whitelistInputProps, "whitelistInput", key); - } - }; updateTierStore = (event, property) => { const { tierStore, num } = this.props; @@ -43,7 +33,7 @@ export class CrowdsaleBlock extends React.Component {

Whitelist

- this.changeState(e, cntrct, num, prop)} /> +
); return ( diff --git a/src/components/stepThree/index.js b/src/components/stepThree/index.js index c59647082..0ef2f926b 100644 --- a/src/components/stepThree/index.js +++ b/src/components/stepThree/index.js @@ -63,17 +63,6 @@ export class stepThree extends React.Component { this.props.tierStore.invalidateToken(); }; - changeState = (event, parent, key, property) => { - if (property.indexOf("whitelist_") === 0) { - const { tierStore } = this.props; - const whitelistInputProps = { ...tierStore.tiers[key].whitelistInput }; - const prop = property.split("_")[1]; - - whitelistInputProps[prop] = event.target.value; - tierStore.setTierProperty(whitelistInputProps, "whitelistInput", key); - } - }; - addCrowdsale() { const { crowdsaleBlockListStore, tierStore } = this.props; let num = crowdsaleBlockListStore.blockList.length + 1; @@ -83,8 +72,7 @@ export class stepThree extends React.Component { rate: 0, updatable: "off", whitelist: [], - whitelistElements: [], - whitelistInput: {} + whitelistElements: [] }; const newTierValidations = { @@ -347,7 +335,7 @@ export class stepThree extends React.Component {

Whitelist

- this.changeState(e, cntrct, 0, prop)} /> +
); return ( diff --git a/src/stores/TierStore.js b/src/stores/TierStore.js index 39b3540a2..b172e6fd4 100644 --- a/src/stores/TierStore.js +++ b/src/stores/TierStore.js @@ -168,7 +168,26 @@ class TierStore { }); } - @action removeWhiteListItem = (whitelistNum, crowdsaleNum) => { + @action addWhitelistItem = ({ addr, min, max }, crowdsaleNum) => { + const tier = this.tiers[crowdsaleNum] + + const whitelist = tier.whitelist.slice() + + const isAdded = whitelist.find(item => item.addr === addr && !item.deleted) + + if (isAdded) return + + const whitelistElements = tier.whitelistElements.slice() + const whitelistNum = whitelistElements.length + + whitelistElements.push({ addr, min, max, whitelistNum, crowdsaleNum }) + whitelist.push({ addr, min, max }) + + this.setTierProperty(whitelistElements, 'whitelistElements', crowdsaleNum) + this.setTierProperty(whitelist, 'whitelist', crowdsaleNum) + } + + @action removeWhitelistItem = (whitelistNum, crowdsaleNum) => { let whitelist = this.tiers[crowdsaleNum].whitelist.slice() whitelist[whitelistNum].deleted = true this.setTierProperty(whitelist, 'whitelist', crowdsaleNum) diff --git a/src/utils/constants.js b/src/utils/constants.js index 67481ad74..348962ccf 100644 --- a/src/utils/constants.js +++ b/src/utils/constants.js @@ -1,52 +1,10 @@ -export const defaultState = { - contracts: { - token: {}, - crowdsale: {addr:[], abiConstructor:[]}, - pricingStrategy: {addr:[], abiConstructor:[]}, - multisig: {}, - nullFinalizeAgent: {addr:[], abiConstructor:[]}, - finalizeAgent: {addr:[], abiConstructor:[]}, - tokenTransferProxy: {} - }, - token: { - name: '', - ticker: '', - supply: 0, - decimals: '', - reservedTokens: [], - reservedTokensElements: [], - reservedTokensInput: {dim: "tokens"} - }, - crowdsale: [{ - startTime: '', - endTime: '', - walletAddress: '', - supply: '', - whitelist: [], - whitelistElements: [], - whitelistInput: {} - }], - pricingStrategy: [{rate: ''}], - blockTimeGeneration: 17, - compilerVersion: "0.4.11", - optimized: true, - contractName: "MintedTokenCappedCrowdsaleExt", - contractType: "white-list-with-cap", - contractTypes: { - standard: "standard", - capped: "capped", - whitelistwithcap: "white-list-with-cap" - } -} - export const defaultTiers = [{ startTime: '', endTime: '', walletAddress: '', supply: '', whitelist: [], - whitelistElements: [], - whitelistInput: {} + whitelistElements: [] }] export const CONTRACT_TYPES = { diff --git a/src/utils/utils.js b/src/utils/utils.js index 927f794a8..85b240668 100644 --- a/src/utils/utils.js +++ b/src/utils/utils.js @@ -68,8 +68,6 @@ if (!Math.floor10) { const getTimeAsNumber = (time) => new Date(time).getTime() -export const getOldState = (props, defaultState) => (props && props.location && props.location.query && props.location.query.state) || defaultState - export const getStepClass = (step, activeStep) => step === activeStep ? "step-navigation step-navigation_active" : "step-navigation" export const stepsAreValid = (steps) => {