Skip to content

Commit

Permalink
Merge 0f7a555 into 961da55
Browse files Browse the repository at this point in the history
  • Loading branch information
mariano-aguero committed Oct 1, 2018
2 parents 961da55 + 0f7a555 commit 39dc54d
Show file tree
Hide file tree
Showing 12 changed files with 184 additions and 13 deletions.
4 changes: 4 additions & 0 deletions public/metadata/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
# Ignore everything
*

# But not these files...
!.gitignore
!reservedTokenTemplate.csv
4 changes: 4 additions & 0 deletions public/metadata/reservedTokenTemplate.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
0x66f537cCD03f21c58172602B919884A442A3c313,percentage,80
0x665772109Eb2dc9F5B7c28F987Ec58949d4Eeb87,percentage,10
0x693d436da2c3C11341149522E5F6d0390B363197,tokens,1788191146
0x2bD96eA633e8BcB468732c68B2CD632BfF4D79Db,tokens,758413595.514329
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/assets/stylesheets/styles.css

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
.sw-ReservedTokensListControls {
display: flex;
flex-direction: column;
justify-content: flex-end;

@media (min-width: $breakpoint-md) {
flex-direction: row;
}

@media (min-width: $breakpoint-xl) {
margin-bottom: -20px;
}
Expand All @@ -22,11 +27,20 @@
font-size: 16px;
font-weight: 600;
line-height: 30px;
margin: 0 38px 0 0;
margin-bottom: 25px;
padding: 0 0 0 25px;

&:last-child {
margin-right: 0;
margin-bottom: 0;
}

@media (min-width: $breakpoint-md) {
margin-bottom: 0;
margin-right: 38px;

&:last-child {
margin-right: 0;
}
}

&.sw-ReservedTokensListControls_Button-clearall {
Expand All @@ -37,8 +51,12 @@
background-image: url('#{ $base-images-path }/ReservedTokensListControls/ico-upload.svg');
}

&.sw-ReservedTokensListControls_Button-downloadcsv {
background-image: url('#{ $base-images-path }/ReservedTokensListControls/ico-download.svg');
}

&.m-r-0 {
margin-right: 0;
}

}
}
33 changes: 30 additions & 3 deletions src/components/Common/ReservedTokensInputBlock.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ import {
reservedTokensImported,
noMoreReservedSlotAvailableCSV,
clearingReservedTokens,
noMoreReservedSlotAvailable
noMoreReservedSlotAvailable,
reservedTokenErrorImportingCSV
} from '../../utils/alerts'
import processReservedTokens from '../../utils/processReservedTokens'
import { processReservedTokens, errorsCsv } from '../../utils/processReservedTokens'
import logdown from 'logdown'
import { downloadFile } from '../../utils/utils'

const logger = logdown('TW:ReservedTokensInputBlock')
const { VALID, INVALID } = VALIDATION_TYPES
Expand Down Expand Up @@ -174,6 +176,13 @@ export class ReservedTokensInputBlock extends Component {
Papa.parse(file, {
skipEmptyLines: true,
complete: results => {
const { data } = results
const { errorRowLength, errorAddress } = errorsCsv(data)

if ((errorRowLength && errorRowLength.length > 0) || (errorAddress && errorAddress.length > 0)) {
return reservedTokenErrorImportingCSV(errorRowLength, errorAddress)
}

const { called, reservedTokenLengthError } = processReservedTokens(
{
rows: results.data,
Expand Down Expand Up @@ -203,6 +212,18 @@ export class ReservedTokensInputBlock extends Component {
return result
}

downloadCSV = async () => {
try {
const response = await fetch(`/metadata/reservedTokenTemplate.csv`)
const text = await response.text()

// See RFC for csv MIME type http://tools.ietf.org/html/rfc4180
downloadFile(text, 'template.csv', 'text/csv')
} catch (err) {
logger.log('Error fetching file when download template csv')
}
}

render() {
const reservedTokensElements = <ReservedTokensTable extraClassName="sw-BorderedBlock_Row2Column1" {...this.props} />
const tokensListEmpty = this.props.reservedTokenStore.tokens.length === 0
Expand Down Expand Up @@ -280,10 +301,16 @@ export class ReservedTokensInputBlock extends Component {
</div>
)}
<Dropzone onDrop={this.onDrop} accept=".csv" style={dropzoneStyle}>
<div className="sw-ReservedTokensListControls_Button sw-ReservedTokensListControls_Button-uploadcsv m-r-0">
<div className="sw-ReservedTokensListControls_Button sw-ReservedTokensListControls_Button-uploadcsv">
Upload CSV
</div>
</Dropzone>
<div
className="sw-ReservedTokensListControls_Button sw-ReservedTokensListControls_Button-downloadcsv m-r-0"
onClick={this.downloadCSV}
>
Download CSV template
</div>
</div>
</div>
</div>
Expand Down
38 changes: 38 additions & 0 deletions src/utils/alerts.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import sweetAlert2 from 'sweetalert2'
import { weiToGwei } from './utils'
import { DEPLOYMENT_VALUES, LIMIT_RESERVED_ADDRESSES, LIMIT_WHITELISTED_ADDRESSES } from './constants'

export function noMetaMaskAlert(cb) {
sweetAlert2({
title: 'Warning',
Expand Down Expand Up @@ -271,6 +272,43 @@ export function whitelistImported(count) {
})
}

export function reservedTokenErrorImportingCSV(lines, address) {
let message = 'There was an error importing the file.'

if (lines && lines.length > 0) {
const messageHeader = `<div class="text-left">The following lines have an erroneous amount of columns: </div>`

let list = '<ul class="text-left">'
for (let error of lines) {
const { line, columns } = error
const columnLabel = columns > 1 ? 'columns' : 'column'
let item = `<li>The line number ${line} have ${columns} ${columnLabel}, must have 3 columns</li>`
list = `${list}${item}`
}
list = `${list}</ul>`
message = `${messageHeader} ${list}`
}

if (address && address.length > 0) {
const messageHeader = `<div class="text-left">The following address are wrong: </div>`

let list = '<ul class="text-left">'
for (let error of address) {
const { line, address } = error
let item = `<li>The line number ${line} has an incorrect address: ${address}</li>`
list = `${list}${item}`
}
list = `${list}</ul>`
message = `${message} ${messageHeader} ${list}`
}

return sweetAlert2({
title: 'Error importing the file',
html: message,
type: 'error'
})
}

export function reservedTokensImported(count) {
return sweetAlert2({
title: 'Reserved tokens imported',
Expand Down
33 changes: 32 additions & 1 deletion src/utils/processReservedTokens.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { composeValidators, isAddress, isDecimalPlacesNotGreaterThan, isPositive, isRequired } from './validations'
import { reservedTokenStore } from '../stores'
import logdown from 'logdown'
import Web3 from 'web3'

const logger = logdown('TW:processReservedTokens')

Expand All @@ -14,7 +15,7 @@ const logger = logdown('TW:processReservedTokens')
* @param {Function} cb The function to be called with each valid item
* @returns {Object} Object with a `called` property, indicating the number of times the callback was called
*/
export default function({ rows, decimals }, cb) {
export const processReservedTokens = ({ rows, decimals }, cb) => {
let called = 0
let reservedTokenLengthError = false
for (let row of rows) {
Expand Down Expand Up @@ -61,3 +62,33 @@ export default function({ rows, decimals }, cb) {
reservedTokenLengthError: reservedTokenLengthError
}
}

export const errorsCsv = data => {
let errorRowLength = []
let errorAddress = []

for (let i = 0; i < data.length; i++) {
const row = data[i]

if (row.length < 3) {
const errorRow = {
line: i + 1,
columns: row.length
}
errorRowLength.push(errorRow)
}

if (row[0] && !Web3.utils.isAddress(row[0])) {
const errorRow = {
line: i + 1,
address: row[0]
}
errorAddress.push(errorRow)
}
}

return {
errorRowLength: errorRowLength,
errorAddress: errorAddress
}
}
26 changes: 26 additions & 0 deletions src/utils/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -260,3 +260,29 @@ export const navigateTo = (history, location, params = '') => {

return true
}

export const downloadFile = (data, filename, mimetype) => {
if (!data) {
return
}

const blob = data.constructor !== Blob ? new Blob([data], { type: mimetype || 'application/octet-stream' }) : data

if (navigator.msSaveBlob) {
navigator.msSaveBlob(blob, filename)
return
}

const lnk = document.createElement('a')
const url = window.URL
let objectURL

if (mimetype) {
lnk.type = mimetype
}

lnk.download = filename || 'untitled'
lnk.href = objectURL = url.createObjectURL(blob)
lnk.dispatchEvent(new MouseEvent('click'))
setTimeout(url.revokeObjectURL.bind(url, objectURL))
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`percentage 1`] = `
<inject-ReservedTokensInputBlock-with-reservedTokenStore-tokenStore
<inject-ReservedTokensInputBlock-with-reservedTokenStore
reservedTokenStore={
ReservedTokenStore {
"tokens": Array [],
Expand Down Expand Up @@ -345,15 +345,21 @@ exports[`percentage 1`] = `
Dropzone
</span>
</Component>
<div
className="sw-ReservedTokensListControls_Button sw-ReservedTokensListControls_Button-downloadcsv m-r-0"
onClick={[Function]}
>
Download CSV template
</div>
</div>
</div>
</div>
</ReservedTokensInputBlock>
</inject-ReservedTokensInputBlock-with-reservedTokenStore-tokenStore>
</inject-ReservedTokensInputBlock-with-reservedTokenStore>
`;

exports[`tokens 1`] = `
<inject-ReservedTokensInputBlock-with-reservedTokenStore-tokenStore
<inject-ReservedTokensInputBlock-with-reservedTokenStore
reservedTokenStore={
ReservedTokenStore {
"tokens": Array [],
Expand Down Expand Up @@ -698,9 +704,15 @@ exports[`tokens 1`] = `
Dropzone
</span>
</Component>
<div
className="sw-ReservedTokensListControls_Button sw-ReservedTokensListControls_Button-downloadcsv m-r-0"
onClick={[Function]}
>
Download CSV template
</div>
</div>
</div>
</div>
</ReservedTokensInputBlock>
</inject-ReservedTokensInputBlock-with-reservedTokenStore-tokenStore>
</inject-ReservedTokensInputBlock-with-reservedTokenStore>
`;
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,12 @@ exports[`StepTwoForm should render StepTwoForm for Minted Capped Crowdsale 1`] =
<span>
Dropzone
</span>
<div
className="sw-ReservedTokensListControls_Button sw-ReservedTokensListControls_Button-downloadcsv m-r-0"
onClick={[Function]}
>
Download CSV template
</div>
</div>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion test/utils/processReservedTokens.spec.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import processReservedTokens from '../../src/utils/processReservedTokens'
import { processReservedTokens } from '../../src/utils/processReservedTokens'

describe('processReservedTokens function', () => {
it('should call the callback for each valid reserved tokens item', () => {
Expand Down

0 comments on commit 39dc54d

Please sign in to comment.