diff --git a/app/browser/api/ledger.js b/app/browser/api/ledger.js index 1192b545e79..4854d693c52 100644 --- a/app/browser/api/ledger.js +++ b/app/browser/api/ledger.js @@ -15,10 +15,10 @@ const qr = require('qr-image') const underscore = require('underscore') const tldjs = require('tldjs') const urlFormat = require('url').format -const queryString = require('querystring') const levelUp = require('level') const random = require('random-lib') const uuid = require('uuid') +const BigNumber = require('bignumber.js') // Actions const appActions = require('../../../js/actions/appActions') @@ -50,8 +50,8 @@ let visitsByPublisher = {} let bootP let quitP const _internal = { - verboseP: process.env.LEDGER_VERBOSE || true, - debugP: process.env.LEDGER_DEBUG || true, + verboseP: process.env.LEDGER_VERBOSE || false, + debugP: process.env.LEDGER_DEBUG || false, ruleset: { raw: [], cooked: [] @@ -63,7 +63,6 @@ let ledgerPublisher let ledgerClient let client let synopsis -let ledgerBalance // Timers let balanceTimeoutId = false @@ -91,7 +90,8 @@ const clientOptions = { rulesTestP: process.env.LEDGER_RULES_TESTING, verboseP: process.env.LEDGER_VERBOSE, server: process.env.LEDGER_SERVER_URL, - createWorker: electron.app.createWorker + createWorker: electron.app.createWorker, + version: 'v2' } const fileTypes = { bmp: new Buffer([0x42, 0x4d]), @@ -101,7 +101,7 @@ const fileTypes = { png: new Buffer([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]) } const minimumVisitTimeDefault = 8 * 1000 -const nextAddFoundsTime = 3 * miliseconds.day +const nextAddFundsTime = 3 * miliseconds.day let signatureMax = 0 underscore.keys(fileTypes).forEach((fileType) => { if (signatureMax < fileTypes[fileType].length) signatureMax = fileTypes[fileType].length @@ -112,8 +112,8 @@ signatureMax = Math.ceil(signatureMax * 1.5) const sufficientBalanceToReconcile = (state) => { const balance = Number(ledgerState.getInfoProp(state, 'balance') || 0) const unconfirmed = Number(ledgerState.getInfoProp(state, 'unconfirmed') || 0) - const btc = ledgerState.getInfoProp(state, 'btc') - return btc && (balance + unconfirmed > 0.9 * Number(btc)) + const bat = ledgerState.getInfoProp(state, 'bat') + return bat && (balance + unconfirmed > 0.9 * Number(bat)) } const hasFunds = (state) => { const balance = getSetting(settings.PAYMENTS_ENABLED) @@ -152,24 +152,34 @@ const notifications = { notifications.timeout = setInterval((state) => { notifications.onInterval(state) }, notifications.pollingInterval, state) - - // Show relevant browser notifications on launch - notifications.onLaunch(state) }, onLaunch: (state) => { - if (!getSetting(settings.PAYMENTS_NOTIFICATIONS)) { + if (!getSetting(settings.PAYMENTS_ENABLED)) { return } - // Show one-time BAT conversion message: - // - if payments are enabled - // - user has a positive balance - // - this is an existing profile (new profiles will have firstRunTimestamp matching btcToBatTimestamp) - // - notification has not already been shown yet - // (see https://github.com/brave/browser-laptop/issues/11021) + + // One time conversion of wallet + const isNewInstall = state.get('firstRunTimestamp') === state.getIn(['migrations', 'batMercuryTimestamp']) + const hasUpgradedWallet = state.getIn(['migrations', 'batMercuryTimestamp']) !== state.getIn(['migrations', 'btcToBatTimestamp']) + if (!isNewInstall && !hasUpgradedWallet) { + module.exports.transitionWalletToBat(state) + } + if (hasFunds(state)) { - const isNewInstall = state.get('firstRunTimestamp') === state.getIn(['migrations', 'btcToBatTimestamp']) - const hasBeenNotified = state.getIn(['migrations', 'btcToBatTimestamp']) !== state.getIn(['migrations', 'btcToBatNotifiedTimestamp']) - if (!isNewInstall && !hasBeenNotified) { + // Don't bother processing the rest, which are only notifications. + if (!getSetting(settings.PAYMENTS_NOTIFICATIONS)) { + return + } + + // Show one-time BAT conversion message: + // - if payments are enabled + // - user has a positive balance + // - this is an existing profile (new profiles will have firstRunTimestamp matching batMercuryTimestamp) + // - wallet has been transitioned + // - notification has not already been shown yet + // (see https://github.com/brave/browser-laptop/issues/11021) + const hasBeenNotified = state.getIn(['migrations', 'batMercuryTimestamp']) !== state.getIn(['migrations', 'btcToBatNotifiedTimestamp']) + if (!isNewInstall && hasUpgradedWallet && !hasBeenNotified) { notifications.showBraveWalletUpdated() } } @@ -307,7 +317,7 @@ const notifications = { }) }, showAddFunds: () => { - const nextTime = new Date().getTime() + nextAddFoundsTime + const nextTime = new Date().getTime() + nextAddFundsTime appActions.changeSetting(settings.PAYMENTS_NOTIFICATION_ADD_FUNDS_TIMESTAMP, nextTime) appActions.showNotification({ @@ -500,7 +510,7 @@ const logError = (state, err, caller) => { } const loadKeysFromBackupFile = (state, filePath) => { - let keys = null + let recoveryKey = null const fs = require('fs') let data = fs.readFileSync(filePath) @@ -512,19 +522,10 @@ const loadKeysFromBackupFile = (state, filePath) => { let messageLines = recoveryFileContents.split(os.EOL) - let paymentIdLine = '' || messageLines[3] - let passphraseLine = '' || messageLines[4] - - const paymentIdPattern = new RegExp([locale.translation('ledgerBackupText3'), '([^ ]+)'].join(' ')) - const paymentId = (paymentIdLine.match(paymentIdPattern) || [])[1] + let passphraseLine = '' || messageLines[3] const passphrasePattern = new RegExp([locale.translation('ledgerBackupText4'), '(.+)$'].join(' ')) - const passphrase = (passphraseLine.match(passphrasePattern) || [])[1] - - keys = { - paymentId, - passphrase - } + recoveryKey = (passphraseLine.match(passphrasePattern) || [])[1] } catch (exc) { state = logError(state, exc, 'recoveryWallet') } @@ -532,7 +533,7 @@ const loadKeysFromBackupFile = (state, filePath) => { return { state, - keys + recoveryKey } } @@ -607,7 +608,7 @@ const roundToTarget = (l, target, property) => { // TODO we should convert this function and all related ones into immutable // TODO merge publishers and publisherData that is created in getPublisherData // so that we don't need to create new Map every single time -const synopsisNormalizer = (state, changedPublisher) => { +const synopsisNormalizer = (state, changedPublisher, returnState) => { let dataPinned = [] // change to list let dataUnPinned = [] // change to list let dataExcluded = [] // change to list @@ -627,7 +628,11 @@ const synopsisNormalizer = (state, changedPublisher) => { } if (results.length === 0) { - return ledgerState.saveAboutSynopsis(state, Immutable.List()) + if (returnState) { + return ledgerState.saveAboutSynopsis(state, Immutable.List()) + } + + return [] } results = underscore.sortBy(results, (entry) => -entry.scores[scorekeeper]) @@ -718,7 +723,11 @@ const synopsisNormalizer = (state, changedPublisher) => { state = ledgerState.setPublishersProp(state, publisherKey, 'pinPercentage', pinPercentage) }) - return ledgerState.saveAboutSynopsis(state, newData) + if (returnState) { + return ledgerState.saveAboutSynopsis(state, newData) + } + + return newData } const updatePublisherInfo = (state, changedPublisher) => { @@ -727,7 +736,7 @@ const updatePublisherInfo = (state, changedPublisher) => { } // const options = synopsis.options - state = synopsisNormalizer(state, changedPublisher) + state = synopsisNormalizer(state, changedPublisher, true) return state } @@ -1130,14 +1139,12 @@ const pageDataChanged = (state) => { const backupKeys = (state, backupAction) => { const date = moment().format('L') - const paymentId = ledgerState.getInfoProp(state, 'paymentId') const passphrase = ledgerState.getInfoProp(state, 'passphrase') const messageLines = [ locale.translation('ledgerBackupText1'), [locale.translation('ledgerBackupText2'), date].join(' '), '', - [locale.translation('ledgerBackupText3'), paymentId].join(' '), [locale.translation('ledgerBackupText4'), passphrase].join(' '), '', locale.translation('ledgerBackupText5') @@ -1162,8 +1169,8 @@ const backupKeys = (state, backupAction) => { }) } -const recoverKeys = (state, useRecoveryKeyFile, firstKey, secondKey) => { - let firstRecoveryKey, secondRecoveryKey +const recoverKeys = (state, useRecoveryKeyFile, key) => { + let recoveryKey if (useRecoveryKeyFile) { let recoveryKeyFile = promptForRecoveryKeyFile() @@ -1174,35 +1181,23 @@ const recoverKeys = (state, useRecoveryKeyFile, firstKey, secondKey) => { if (recoveryKeyFile) { const result = loadKeysFromBackupFile(state, recoveryKeyFile) - const keys = result.keys || {} + recoveryKey = result.recoveryKey || '' state = result.state - - if (keys) { - firstRecoveryKey = keys.paymentId - secondRecoveryKey = keys.passphrase - } } } - if (!firstRecoveryKey || !secondRecoveryKey) { - firstRecoveryKey = firstKey - secondRecoveryKey = secondKey + if (!recoveryKey) { + recoveryKey = key } - const UUID_REGEX = /^[0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12}$/ - if ( - typeof firstRecoveryKey !== 'string' || - !firstRecoveryKey.match(UUID_REGEX) || - typeof secondRecoveryKey !== 'string' || - !secondRecoveryKey.match(UUID_REGEX) - ) { + if (typeof recoveryKey !== 'string') { // calling logError sets the error object state = logError(state, true, 'recoverKeys') state = ledgerState.setRecoveryStatus(state, false) return state } - client.recoverWallet(firstRecoveryKey, secondRecoveryKey, (err, result) => { + client.recoverWallet(null, recoveryKey, (err, result) => { appActions.onWalletRecovery(err, result) }) @@ -1210,14 +1205,11 @@ const recoverKeys = (state, useRecoveryKeyFile, firstKey, secondKey) => { } const onWalletRecovery = (state, error, result) => { - let existingLedgerError = ledgerState.getInfoProp(state, 'error') - if (error) { // we reset ledgerInfo.error to what it was before (likely null) // if ledgerInfo.error is not null, the wallet info will not display in UI // logError sets ledgerInfo.error, so we must we clear it or UI will show an error - state = logError(error, 'recoveryWallet') - state = ledgerState.setInfoProp(state, 'error', existingLedgerError) + state = logError(state, error.toString(), 'recoveryWallet') state = ledgerState.setRecoveryStatus(state, false) } else { callback(error, result) @@ -1319,7 +1311,7 @@ const enable = (state, paymentsEnabled) => { } if (!ledgerPublisher) { - ledgerPublisher = require('ledger-publisher') + ledgerPublisher = require('bat-publisher') } synopsis = new (ledgerPublisher.Synopsis)() const stateSynopsis = ledgerState.getSynopsis(state) @@ -1434,7 +1426,7 @@ const cacheRuleSet = (state, ruleset) => { } const clientprep = () => { - if (!ledgerClient) ledgerClient = require('ledger-client') + if (!ledgerClient) ledgerClient = require('bat-client') _internal.debugP = ledgerClient.prototype.boolion(process.env.LEDGER_PUBLISHER_DEBUG) _internal.verboseP = ledgerClient.prototype.boolion(process.env.LEDGER_PUBLISHER_VERBOSE) } @@ -1450,8 +1442,12 @@ const roundtrip = (params, options, callback) => { underscore.omit(params, ['headers', 'payload', 'timeout'])) // TBD: let the user configure this via preferences [MTR] - if (parts.hostname === 'ledger.brave.com' && params.useProxy) { - parts.hostname = 'ledger-proxy.privateinternetaccess.com' + if (params.useProxy) { + if (parts.hostname === 'ledger.brave.com') { + parts.hostname = 'ledger-proxy.privateinternetaccess.com' + } else if (parts.hostname === 'ledger.mercury.basicattentiontoken.org') { + parts.hostname = 'mercury-proxy.privateinternetaccess.com' + } } const i = parts.path.indexOf('?') @@ -1484,16 +1480,18 @@ const roundtrip = (params, options, callback) => { console.log('>>> ' + (body || '').split('\n').join('\n>>> ')) } - if (err) return callback(err) + if (err) return callback(err, response) if (Math.floor(response.statusCode / 100) !== 2) { - return callback(new Error('HTTP response ' + response.statusCode) + ' for ' + params.method + ' ' + params.path) + return callback( + new Error('HTTP response ' + response.statusCode + ' for ' + params.method + ' ' + params.path), + response) } try { payload = rawP ? body : (response.statusCode !== 204) ? JSON.parse(body) : null } catch (err) { - return callback(err) + return callback(err, response) } try { @@ -1513,28 +1511,6 @@ const roundtrip = (params, options, callback) => { if (options.payload) console.log('<<< ' + JSON.stringify(params.payload, null, 2).split('\n').join('\n<<< ')) } -const updateLedgerInfo = (state) => { - const ledgerInfo = ledgerState.getInfoProps(state) - const now = new Date().getTime() - - if (ledgerInfo.get('buyURLExpires') > now) { - state = ledgerState.setInfoProp(state, 'buyMaximumUSD', 6) - } - if (typeof process.env.ADDFUNDS_URL !== 'undefined') { - state = ledgerState.setInfoProp(state, 'buyURLFrame', true) - const buyURL = process.env.ADDFUNDS_URL + '?' + - queryString.stringify({ - currency: ledgerInfo.get('currency'), - amount: getSetting(settings.PAYMENTS_CONTRIBUTION_AMOUNT), - address: ledgerInfo.get('address') - }) - state = ledgerState.setInfoProp(state, 'buyURL', buyURL) - state = ledgerState.setInfoProp(state, 'buyMaximumUSD', false) - } - - return state -} - const observeTransactions = (state, transactions) => { const current = ledgerState.getInfoProp(state, 'transactions') if (current && current.size === transactions.length) { @@ -1558,9 +1534,27 @@ const getStateInfo = (state, parsedData) => { return state } + if (!ledgerClient) { + ledgerClient = require('bat-client') + } + + if (parsedData.properties && parsedData.properties.wallet && parsedData.properties.wallet.keyinfo) { + let seed = parsedData.properties.wallet.keyinfo.seed + if (!(seed instanceof Uint8Array)) { + seed = new Uint8Array(Object.values(seed)) + } + + parsedData.properties.wallet.keyinfo.seed = seed + } + + let passphrase = ledgerClient.prototype.getWalletPassphrase(parsedData) + if (passphrase) { + passphrase = passphrase.join(' ') + } + const newInfo = { paymentId: parsedData.properties.wallet.paymentId, - passphrase: parsedData.properties.wallet.keychains.passphrase, + passphrase: passphrase, created: !!parsedData.properties.wallet, creating: !parsedData.properties.wallet, reconcileFrequency: parsedData.properties.days, @@ -1576,7 +1570,7 @@ const getStateInfo = (state, parsedData) => { let transactions = [] if (!parsedData.transactions) { - return updateLedgerInfo(state) + return state } for (let i = parsedData.transactions.length - 1; i >= 0; i--) { @@ -1599,29 +1593,48 @@ const getStateInfo = (state, parsedData) => { } observeTransactions(state, transactions) - state = ledgerState.setInfoProp(state, 'transactions', Immutable.fromJS(transactions)) - return updateLedgerInfo(state) + return ledgerState.setInfoProp(state, 'transactions', Immutable.fromJS(transactions)) } const generatePaymentData = (state) => { const ledgerInfo = ledgerState.getInfoProps(state) - const paymentURL = `bitcoin:${ledgerInfo.get('address')}?amount=${ledgerInfo.get('btc')}&label=${encodeURI('Brave Software')}` - if (ledgerInfo.get('paymentURL') !== paymentURL) { - state = ledgerState.setInfoProp(state, 'paymentURL', paymentURL) + const addresses = ledgerInfo.get('addresses') || Immutable.List() + + addresses.forEach((address, index) => { + if (ledgerInfo.hasIn(['walletQR', index])) { + return + } + + let url = null + switch (index) { + case 'BAT': + case 'ETH': + url = `ethereum:${address}` + break + case 'BTC': + url = `bitcoin:${address}` + break + case 'LTC': + url = `litecoin:${address}` + break + default: + return + } + try { let chunks = [] - qr.image(paymentURL, {type: 'png'}) + qr.image(url, {type: 'png'}) .on('data', (chunk) => { chunks.push(chunk) }) .on('end', () => { const paymentIMG = 'data:image/png;base64,' + Buffer.concat(chunks).toString('base64') - state = ledgerState.setInfoProp(state, 'paymentIMG', paymentIMG) + appActions.onLedgerQRGenerated(index, paymentIMG) }) } catch (ex) { - console.error('qr.imageSync error: ' + ex.toString()) + console.error('qr.imageSync (for url ' + url + ') error: ' + ex.toString()) } - } + }) return state } @@ -1643,7 +1656,6 @@ const getPaymentInfo = (state) => { client.getWalletProperties(amount, currency, function (err, body) { if (err) { - logError(err, 'getWalletProperties') return } @@ -1657,33 +1669,68 @@ const getPaymentInfo = (state) => { } const onWalletProperties = (state, body) => { - let newInfo = { - buyURL: body.get('buyURL'), - buyURLExpires: body.get('buyURLExpires'), - balance: body.get('balance'), - unconfirmed: body.get('unconfirmed'), - satoshis: body.get('satoshis') - } + // Addresses + state = ledgerState.setInfoProp(state, 'addresses', body.get('addresses')) - if (client) { - newInfo.address = client.getWalletAddress() + // Balance + const balance = parseFloat(body.get('balance')) + if (balance >= 0) { + state = ledgerState.setInfoProp(state, 'balance', balance) } - state = ledgerState.mergeInfoProp(state, newInfo) + // Rates + const rates = body.get('rates') + if (rates != null) { + state = ledgerState.setInfoProp(state, 'rates', rates) + } + // Current currency const info = ledgerState.getInfoProps(state) + const infoRates = info.get('rates') + const currency = 'USD' // TODO for now it's fixed + let rate = infoRates.get(currency) + + if (rate) { + state = ledgerState.setInfoProp(state, 'currentRate', rate) + } + + // Probi + const probi = parseFloat(body.get('probi')) + if (probi > 0) { + state = ledgerState.setInfoProp(state, 'probi', probi) - const amount = info.getIn(['bravery', 'fee', 'amount']) - const currency = info.getIn(['bravery', 'fee', 'currency']) + const amount = info.get('balance') - if (amount && currency) { - const bodyCurrency = body.getIn(['rates', 'currency']) - if (bodyCurrency) { - const btc = (amount / bodyCurrency).toFixed(8) - state = ledgerState.setInfoProp(state, 'btc', btc) + if (amount && rate) { + const bigProbi = new BigNumber(probi.toString()).dividedBy('1e18') + const bigRate = new BigNumber(rate.toString()) + const converted = bigProbi.times(bigRate).toFormat(2) + state = ledgerState.setInfoProp(state, 'converted', converted) } } + // unconfirmed amount + const unconfirmed = parseFloat(body.get('unconfirmed')) + if (unconfirmed > 0) { + const result = (unconfirmed / 1e8).toFixed(4) + if (ledgerState.getInfoProp(state, 'unconfirmed') === result) { + return state + } + + state = ledgerState.setInfoProp(state, 'unconfirmed', result) + if (clientOptions.verboseP) { + console.log('\ngetBalance refreshes ledger info: ' + ledgerState.getInfoProp(state, 'unconfirmed')) + } + return state + } + + if (ledgerState.getInfoProp(state, 'unconfirmed') === '0.0000') { + return state + } + + if (clientOptions.verboseP) { + console.log('\nWalletProperties refreshes payment info') + } state = generatePaymentData(state) return state @@ -1706,7 +1753,7 @@ const setPaymentInfo = (amount) => { amount = parseInt(amount, 10) if (isNaN(amount) || (amount <= 0)) return - underscore.extend(bravery.fee, {amount: amount}) + underscore.extend(bravery.fee, { amount: amount, currency: client.getWalletAddresses().BAT ? 'BAT' : 'USD' }) client.setBraveryProperties(bravery, (err, result) => { if (err) { err = err.toString() @@ -1728,7 +1775,7 @@ const onBraveryProperties = (state, error, result) => { } if (result) { - muonWriter(pathName(statePath), result) + muonWriter(result) } return state @@ -1737,46 +1784,8 @@ const onBraveryProperties = (state, error, result) => { const getBalance = (state) => { if (!client) return - const address = ledgerState.getInfoProp(state, 'address') const balanceFn = getBalance.bind(null, state) balanceTimeoutId = setTimeout(balanceFn, 1 * miliseconds.minute) - if (!address) { - return - } - - if (!ledgerBalance) ledgerBalance = require('ledger-balance') - ledgerBalance.getBalance(address, underscore.extend({balancesP: true}, client.options), - (err, provider, result) => { - if (err) { - return console.warn('ledger balance warning: ' + JSON.stringify(err, null, 2)) - } - appActions.onLedgerBalanceReceived(result.unconfirmed) - }) -} - -const balanceReceived = (state, unconfirmed) => { - if (typeof unconfirmed === 'undefined') { - return state - } - - if (unconfirmed > 0) { - const result = (unconfirmed / 1e8).toFixed(4) - if (ledgerState.getInfoProp(state, 'unconfirmed') === result) { - return state - } - - state = ledgerState.setInfoProp(state, 'unconfirmed', result) - if (clientOptions.verboseP) { - console.log('\ngetBalance refreshes ledger info: ' + ledgerState.getInfoProp(state, 'unconfirmed')) - } - return updateLedgerInfo(state) - } - - if (ledgerState.getInfoProp(state, 'unconfirmed') === '0.0000') { - return state - } - - if (clientOptions.verboseP) console.log('\ngetBalance refreshes payment info') return getPaymentInfo(state) } @@ -1803,7 +1812,8 @@ const onCallback = (state, result, delayTime) => { let entries = client && client.report() if (!result) { - return run(state, delayTime) + run(state, delayTime) + return state } const regularResults = result.toJS() @@ -1820,7 +1830,7 @@ const onCallback = (state, result, delayTime) => { state = cacheRuleSet(state, regularResults.ruleset) if (result.has('rulesetV2')) { results = regularResults.rulesetV2 // TODO optimize if possible - result = result.delete('rulesetV2') + delete regularResults.rulesetV2 entries = [] results.forEach((entry) => { @@ -1851,7 +1861,7 @@ const onCallback = (state, result, delayTime) => { if (result.has('publishersV2')) { results = regularResults.publishersV2 // TODO optimize if possible - result = result.delete('publishersV2') + delete regularResults.publishersV2 entries = [] results.forEach((entry) => { @@ -1874,7 +1884,7 @@ const onCallback = (state, result, delayTime) => { }) } - muonWriter(pathName(statePath), regularResults) + muonWriter(regularResults) run(state, delayTime) return state @@ -1896,7 +1906,7 @@ const initialize = (state, paymentsEnabled) => { return state } - if (!ledgerPublisher) ledgerPublisher = require('ledger-publisher') + if (!ledgerPublisher) ledgerPublisher = require('bat-publisher') let ruleset = [] ledgerPublisher.ruleset.forEach(rule => { if (rule.consequent) ruleset.push(rule) @@ -1942,8 +1952,16 @@ const onInitRead = (state, parsedData) => { try { let timeUntilReconcile clientprep() + + const options = Object.assign({}, clientOptions) + try { + if (parsedData.properties.wallet.keychains.user) { + options.version = 'v1' + } + } catch (ex) {} + client = ledgerClient(parsedData.personaId, - underscore.extend(parsedData.options, {roundtrip: roundtrip}, clientOptions), + underscore.extend(parsedData.options, {roundtrip: roundtrip}, options), parsedData) // Scenario: User enables Payments, disables it, waits 30+ days, then @@ -1966,7 +1984,7 @@ const onInitRead = (state, parsedData) => { }) } } catch (ex) { - console.error('ledger client creation error: ', ex) + console.error('ledger client creation error(1): ', ex) return state } @@ -1988,12 +2006,15 @@ const onInitRead = (state, parsedData) => { setPaymentInfo(getSetting(settings.PAYMENTS_CONTRIBUTION_AMOUNT)) getBalance(state) + // Show relevant browser notifications on launch + notifications.onLaunch(state) + return state } const onTimeUntilReconcile = (state, stateResult) => { - state = getStateInfo(stateResult) - muonWriter(pathName(statePath), stateResult) + state = getStateInfo(state, stateResult.toJS()) // TODO optimize + muonWriter(stateResult) return state } @@ -2078,7 +2099,7 @@ const run = (state, delayTime) => { const result = client.vote(winner) if (result) stateData = result }) - if (stateData) muonWriter(pathName(statePath), stateData) + if (stateData) muonWriter(stateData) } catch (ex) { console.log('ledger client error(2): ' + ex.toString() + (ex.stack ? ('\n' + ex.stack) : '')) } @@ -2146,7 +2167,8 @@ const onNetworkConnected = (state) => { balanceTimeoutId = setTimeout(newBalance, 5 * miliseconds.second) } -const muonWriter = (path, payload) => { +const muonWriter = (payload) => { + const path = pathName(statePath) muon.file.writeImportant(path, JSON.stringify(payload, null, 2), (success) => { if (!success) return console.error('write error: ' + path) @@ -2196,7 +2218,7 @@ const migration = (state) => { } // for synopsis variable handling only -const deleteSynopsis = (publisherKey) => { +const deleteSynopsisPublisher = (publisherKey) => { delete synopsis.publishers[publisherKey] } @@ -2210,6 +2232,56 @@ const savePublisherOption = (publisherKey, prop, value) => { } } +const deleteSynopsis = () => { + synopsis.publishers = {} +} + +let newClient = null +const transitionWalletToBat = (state) => { + let newPaymentId, result + + if (newClient === true) return + + if (!newClient) { + try { + clientprep() + newClient = ledgerClient(null, underscore.extend({roundtrip: roundtrip}, clientOptions), null) + } catch (ex) { + return console.error('ledger client creation error(2): ', ex) + } + } + + newPaymentId = newClient.getPaymentId() + if (!newPaymentId) { + newClient.sync((err, result, delayTime) => { + if (err) { + return console.log('ledger client error(3): ' + JSON.stringify(err, null, 2) + (err.stack ? ('\n' + err.stack) : '')) + } + + if (typeof delayTime === 'undefined') delayTime = random.randomInt({ min: 1, max: 500 }) + + setTimeout(() => transitionWalletToBat(state), delayTime) + }) + return + } + + try { + client.transition(newPaymentId, (err, properties) => { + if (err) { + console.error('ledger client transition error: ', err) + } else { + result = newClient.transitioned(properties) + client = newClient + newClient = true + appActions.onLedgerCallback(result, random.randomInt({ min: miliseconds.minute, max: 10 * miliseconds.minute })) + appActions.onBitcoinToBatTransitioned() + } + }) + } catch (ex) { + console.error('exception during ledger client transition: ', ex) + } +} + module.exports = { backupKeys, recoverKeys, @@ -2224,7 +2296,6 @@ module.exports = { verifiedP, boot, onBootStateFile, - balanceReceived, onWalletProperties, paymentPresent, addFoundClosed, @@ -2232,7 +2303,7 @@ module.exports = { onBraveryProperties, onLedgerFirstSync, onCallback, - deleteSynopsis, + deleteSynopsisPublisher, saveOptionSynopsis, savePublisherOption, onTimeUntilReconcile, @@ -2240,5 +2311,7 @@ module.exports = { onNetworkConnected, migration, onInitRead, - notifications + notifications, + deleteSynopsis, + transitionWalletToBat } diff --git a/app/browser/reducers/ledgerReducer.js b/app/browser/reducers/ledgerReducer.js index 5191a0c1544..019156b0eae 100644 --- a/app/browser/reducers/ledgerReducer.js +++ b/app/browser/reducers/ledgerReducer.js @@ -45,8 +45,7 @@ const ledgerReducer = (state, action, immutableAction) => { state = ledgerApi.recoverKeys( state, action.get('useRecoveryKeyFile'), - action.get('firstRecoveryKey'), - action.get('secondRecoveryKey') + action.get('recoveryKey') ) break } @@ -62,6 +61,7 @@ const ledgerReducer = (state, action, immutableAction) => { const clearData = defaults ? defaults.merge(temp) : temp if (clearData.get('browserHistory') && !getSetting(settings.PAYMENTS_ENABLED)) { state = ledgerState.resetSynopsis(state) + ledgerApi.deleteSynopsis() } break } @@ -130,7 +130,7 @@ const ledgerReducer = (state, action, immutableAction) => { case 'ledgerPaymentsShown': { if (action.get('value') === false) { - ledgerApi.deleteSynopsis(publisherKey) + ledgerApi.deleteSynopsisPublisher(publisherKey) state = ledgerState.deletePublishers(state, publisherKey) state = ledgerApi.updatePublisherInfo(state) } @@ -246,11 +246,6 @@ const ledgerReducer = (state, action, immutableAction) => { state = ledgerApi.onBootStateFile(state) break } - case appConstants.APP_ON_LEDGER_BALANCE_RECEIVED: - { - state = ledgerApi.balanceReceived(state, action.get('unconfirmed')) - break - } case appConstants.APP_ON_WALLET_PROPERTIES: { state = ledgerApi.onWalletProperties(state, action.get('body')) @@ -266,6 +261,11 @@ const ledgerReducer = (state, action, immutableAction) => { ledgerApi.addFoundClosed(state) break } + case appConstants.APP_ON_CHANGE_ADD_FUNDS_DIALOG_STEP: + { + state = ledgerState.saveWizardData(state, action.get('page'), action.get('currency')) + break + } case appConstants.APP_ON_WALLET_RECOVERY: { state = ledgerApi.onWalletRecovery(state, action.get('error'), action.get('result')) @@ -304,6 +304,7 @@ const ledgerReducer = (state, action, immutableAction) => { case appConstants.APP_ON_RESET_RECOVERY_STATUS: { state = ledgerState.setRecoveryStatus(state, null) + state = ledgerState.setInfoProp(state, 'error', null) break } case appConstants.APP_ON_LEDGER_INIT_READ: @@ -316,6 +317,16 @@ const ledgerReducer = (state, action, immutableAction) => { state = state.setIn(['migrations', 'btcToBatNotifiedTimestamp'], new Date().getTime()) break } + case appConstants.APP_ON_BTC_TO_BAT_TRANSITIONED: + { + state = state.setIn(['migrations', 'btcToBatTimestamp'], new Date().getTime()) + break + } + case appConstants.APP_ON_LEDGER_QR_GENERATED: + { + state = ledgerState.saveQRCode(state, action.get('currency'), action.get('image')) + break + } } return state } diff --git a/app/browser/tabs.js b/app/browser/tabs.js index d7917f8f44b..417af3cbf03 100644 --- a/app/browser/tabs.js +++ b/app/browser/tabs.js @@ -164,7 +164,8 @@ const updateAboutDetails = (tab, tabValue) => { // TODO(bridiver) - refactor these to use state helpers const ledgerInfo = ledgerState.getInfoProps(appState) - const synopsis = appState.getIn(['ledger', 'about']) + const synopsis = ledgerState.getAboutData(appState) + const wizardData = ledgerState.geWizardData(appState) const preferencesData = appState.getIn(['about', 'preferences']) const appSettings = appState.get('settings') let allSiteSettings = appState.get('siteSettings') @@ -188,20 +189,23 @@ const updateAboutDetails = (tab, tabValue) => { // TODO save this into values into the sate so that we don't call this app action on every state change // this should be saved in app state when windows will be refactored #11151 + /* if (url === 'about:preferences#payments') { tab.on('destroyed', () => { appActions.ledgerPaymentsPresent(tabValue.get('tabId'), false) }) - appActions.ledgerPaymentsPresent(tabValue.get('tabId'), false) + appActions.ledgerPaymentsPresent(tabValue.get('tabId'), true) } else { appActions.ledgerPaymentsPresent(tabValue.get('tabId'), false) } */ + if (location === 'about:preferences' || location === 'about:contributions' || location === aboutUrls.get('about:contributions')) { const ledgerData = ledgerInfo .merge(synopsis) .merge(preferencesData) + .set('wizardData', wizardData) tab.send(messages.LEDGER_UPDATED, ledgerData.toJS()) tab.send(messages.SETTINGS_UPDATED, appSettings.toJS()) tab.send(messages.SITE_SETTINGS_UPDATED, allSiteSettings.toJS()) diff --git a/app/common/lib/ledgerUtil.js b/app/common/lib/ledgerUtil.js index 7857321b487..4aadb987b8d 100644 --- a/app/common/lib/ledgerUtil.js +++ b/app/common/lib/ledgerUtil.js @@ -6,6 +6,7 @@ const Immutable = require('immutable') const moment = require('moment') +const BigNumber = require('bignumber.js') // State const siteSettingsState = require('../state/siteSettingsState') @@ -62,32 +63,36 @@ const shouldTrackView = (view, responseList) => { return false } -const btcToCurrencyString = (btc, ledgerData) => { - const balance = Number(btc || 0) +const batToCurrencyString = (bat, ledgerData) => { + const balance = Number(bat || 0) const currency = (ledgerData && ledgerData.get('currency')) || 'USD' - if (balance === 0) { + if (balance === 0 || ledgerData == null) { return `0 ${currency}` } - if (ledgerData && ledgerData.get('btc') && typeof ledgerData.get('amount') === 'number') { - const btcValue = ledgerData.get('btc') / ledgerData.get('amount') - const fiatValue = (balance / btcValue).toFixed(2) - let roundedValue = Math.floor(fiatValue) - const diff = fiatValue - roundedValue - - if (diff > 0.74) { - roundedValue += 0.75 - } else if (diff > 0.49) { - roundedValue += 0.50 - } else if (diff > 0.24) { - roundedValue += 0.25 - } + const rate = ledgerData.get('currentRate') || 0 + const converted = new BigNumber(new BigNumber(rate.toString())).times(balance).toFixed(2) + return `${converted} ${currency}` +} - return `${roundedValue.toFixed(2)} ${currency}` +const formatCurrentBalance = (ledgerData) => { + let currency = 'USD' + let balance = 0 + let converted = 0 + let hasRate = false + + if (ledgerData != null) { + currency = ledgerData.get('currency') || 'USD' + balance = Number(ledgerData.get('balance') || 0) + converted = Number.parseFloat(ledgerData.get('converted')) || 0 + hasRate = ledgerData.has('currentRate') } - return `${balance} BTC` + balance = balance.toFixed(2) + converted = converted.toFixed(2) + + return `${balance} BAT${hasRate ? ` (${converted} ${currency})` : ''}` } const formattedTimeFromNow = (timestamp) => { @@ -110,11 +115,11 @@ const walletStatus = (ledgerData) => { const pendingFunds = Number(ledgerData.get('unconfirmed') || 0) if (pendingFunds + Number(ledgerData.get('balance') || 0) < - 0.9 * Number(ledgerData.get('btc') || 0)) { + 0.9 * Number(ledgerData.get('bat') || 0)) { status.id = 'insufficientFundsStatus' } else if (pendingFunds > 0) { status.id = 'pendingFundsStatus' - status.args = {funds: btcToCurrencyString(pendingFunds, ledgerData)} + status.args = {funds: batToCurrencyString(pendingFunds, ledgerData)} } else if (transactions && transactions.size > 0) { status.id = 'defaultWalletStatus' } else { @@ -206,7 +211,7 @@ const stickyP = (state, publisherKey) => { module.exports = { shouldTrackView, - btcToCurrencyString, + batToCurrencyString, formattedTimeFromNow, formattedDateFromTimestamp, walletStatus, @@ -214,5 +219,6 @@ module.exports = { contributeP, visibleP, eligibleP, - stickyP + stickyP, + formatCurrentBalance } diff --git a/app/common/state/ledgerState.js b/app/common/state/ledgerState.js index 04bfff9dbdd..d14322f4f48 100644 --- a/app/common/state/ledgerState.js +++ b/app/common/state/ledgerState.js @@ -259,6 +259,15 @@ const ledgerState = { return state.setIn(['ledger', 'info'], Immutable.Map()) }, + saveQRCode: (state, currency, image) => { + state = validateState(state) + if (currency == null) { + return state + } + + return state.setIn(['ledger', 'info', 'walletQR', currency], image) + }, + /** * OTHERS */ @@ -275,9 +284,10 @@ const ledgerState = { return state.setIn(['ledger', 'info', 'error'], null) } - return state - .setIn(['ledger', 'info', 'error', 'caller'], caller) - .setIn(['ledger', 'info', 'error', 'error'], error) + return state.setIn(['ledger', 'info', 'error'], Immutable.fromJS({ + caller: caller, + error: error + })) }, changePinnedValues: (state, publishers) => { @@ -298,6 +308,7 @@ const ledgerState = { return state }, + // About page // TODO (optimization) don't have two almost identical object in state (synopsi->publishers and about->synopsis) saveAboutSynopsis: (state, publishers) => { state = validateState(state) @@ -310,6 +321,23 @@ const ledgerState = { state = validateState(state) return state .setIn(['ledger', 'about', 'synopsisOptions'], ledgerState.getSynopsisOptions(state)) + }, + + getAboutData: (state) => { + return state.getIn(['ledger', 'about']) || Immutable.Map() + }, + + saveWizardData: (state, page, currency) => { + state = validateState(state) + return state.mergeIn(['ledger', 'wizardData'], { + currentPage: page, + currency: currency + }) + }, + + geWizardData: (state) => { + state = validateState(state) + return state.getIn(['ledger', 'wizardData']) || Immutable.Map() } } diff --git a/app/extensions.js b/app/extensions.js index 43251873f62..08ffb271108 100644 --- a/app/extensions.js +++ b/app/extensions.js @@ -170,7 +170,7 @@ let generateBraveManifest = () => { 'form-action': '\'none\'', 'style-src': '\'self\' \'unsafe-inline\'', 'img-src': '* data: file://*', - 'frame-src': '\'self\' https://buy.coinbase.com https://brave.com' + 'frame-src': '\'self\' https://brave.com' } if (process.env.NODE_ENV === 'development') { diff --git a/app/extensions/brave/img/ledger/cryptoIcons/BAT_icon.svg b/app/extensions/brave/img/ledger/cryptoIcons/BAT_icon.svg new file mode 100644 index 00000000000..2154658f32b --- /dev/null +++ b/app/extensions/brave/img/ledger/cryptoIcons/BAT_icon.svg @@ -0,0 +1,22 @@ + + + + +BAT_icon + + + + + + + + + + diff --git a/app/extensions/brave/img/ledger/cryptoIcons/BTC_icon.svg b/app/extensions/brave/img/ledger/cryptoIcons/BTC_icon.svg new file mode 100644 index 00000000000..04079be30bc --- /dev/null +++ b/app/extensions/brave/img/ledger/cryptoIcons/BTC_icon.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/app/extensions/brave/img/ledger/cryptoIcons/ETH_icon.svg b/app/extensions/brave/img/ledger/cryptoIcons/ETH_icon.svg new file mode 100644 index 00000000000..f02465a5f7c --- /dev/null +++ b/app/extensions/brave/img/ledger/cryptoIcons/ETH_icon.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + diff --git a/app/extensions/brave/img/ledger/cryptoIcons/LTC_icon.svg b/app/extensions/brave/img/ledger/cryptoIcons/LTC_icon.svg new file mode 100644 index 00000000000..3150b4e0d94 --- /dev/null +++ b/app/extensions/brave/img/ledger/cryptoIcons/LTC_icon.svg @@ -0,0 +1,16 @@ + + + + +Litecoin + + + + diff --git a/app/extensions/brave/img/ledger/fakeQRcode.png b/app/extensions/brave/img/ledger/fakeQRcode.png new file mode 100644 index 00000000000..6b2ebdd0acc Binary files /dev/null and b/app/extensions/brave/img/ledger/fakeQRcode.png differ diff --git a/app/extensions/brave/img/ledger/uphold-logo.png b/app/extensions/brave/img/ledger/uphold-logo.png new file mode 100644 index 00000000000..c4f4bef4392 Binary files /dev/null and b/app/extensions/brave/img/ledger/uphold-logo.png differ diff --git a/app/extensions/brave/img/ledger/wallet_icon.svg b/app/extensions/brave/img/ledger/wallet_icon.svg new file mode 100644 index 00000000000..c73d949cd29 --- /dev/null +++ b/app/extensions/brave/img/ledger/wallet_icon.svg @@ -0,0 +1,12 @@ + + + + + + diff --git a/app/extensions/brave/img/preferences/browser_prefs_payments.svg b/app/extensions/brave/img/preferences/browser_prefs_payments.svg deleted file mode 100644 index 5ebfd7fb651..00000000000 --- a/app/extensions/brave/img/preferences/browser_prefs_payments.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - diff --git a/app/extensions/brave/img/preferences/browser_prefs_payments_off.svg b/app/extensions/brave/img/preferences/browser_prefs_payments_off.svg new file mode 100644 index 00000000000..dee1e95e051 --- /dev/null +++ b/app/extensions/brave/img/preferences/browser_prefs_payments_off.svg @@ -0,0 +1,10 @@ + + + + + + diff --git a/app/extensions/brave/img/preferences/browser_prefs_payments_on.svg b/app/extensions/brave/img/preferences/browser_prefs_payments_on.svg new file mode 100644 index 00000000000..463273b7ba5 --- /dev/null +++ b/app/extensions/brave/img/preferences/browser_prefs_payments_on.svg @@ -0,0 +1,20 @@ + + + + +BAT_logo_color + + + + + + + + + diff --git a/app/extensions/brave/locales/en-US/app.properties b/app/extensions/brave/locales/en-US/app.properties index 2ef96b77aad..58c02724e46 100644 --- a/app/extensions/brave/locales/en-US/app.properties +++ b/app/extensions/brave/locales/en-US/app.properties @@ -144,11 +144,10 @@ flashInstalled=Flash is already installed and can be enabled in Preferences > Se goToPrefs=Open Preferences goToAdobe=Reinstall Flash allowFlashPlayer=Allow {{origin}} to run Flash Player? -ledgerBackupText1=Brave Wallet Recovery Keys +ledgerBackupText1=Brave Wallet Recovery Key ledgerBackupText2=Date created: -ledgerBackupText3=Recovery Key 1: -ledgerBackupText4=Recovery Key 2: -ledgerBackupText5=Note: These keys are not stored on Brave servers. These keys are your only method of recovering your Brave wallet. Save these keys in a safe place, separate from your Brave browser. +ledgerBackupText4=Recovery Key: +ledgerBackupText5=Note: This key is not stored on Brave servers. This key is your only method of recovering your Brave wallet. Save this key in a safe place, separate from your Brave browser. Make sure you keep this key private, or else your wallet will be compromised. allowWidevine=Allow {{origin}} to run Google Widevine? error=Error @@ -239,7 +238,7 @@ viewCertificate=View Certificate walletConvertedBackup=Back up your new wallet walletConvertedDismiss=Later walletConvertedLearnMore=Learn More -walletConvertedToBat=Your Brave Payments BTC wallet has been converted to a BAT wallet. +walletConvertedToBat=Your BTC will be converted to BAT and will appear in your Brave wallet within approximately 30 minutes. windowCaptionButtonMinimize=Minimize windowCaptionButtonMaximize=Maximize windowCaptionButtonRestore=Restore Down diff --git a/app/extensions/brave/locales/en-US/preferences.properties b/app/extensions/brave/locales/en-US/preferences.properties index 00bad4fe52c..a0e7fd1922f 100644 --- a/app/extensions/brave/locales/en-US/preferences.properties +++ b/app/extensions/brave/locales/en-US/preferences.properties @@ -81,9 +81,6 @@ defaultWalletStatus=Thanks for helping support your favorite websites! tableEmptyText=No table data. notificationEmptyText=Top publisher visits bitcoin=Bitcoin -bitcoinAdd=Use your existing Bitcoin wallet/account -bitcoinAddDescription=Use any BTC wallet that can transfer Bitcoin to your Brave wallet. -bitcoinBuy=Buy Bitcoin lastContribution=Last contribution: nextContribution=Next contribution: noPaymentHistory=No previous contributions @@ -112,14 +109,8 @@ contributionStatementFooterNoteBoxBody2=Brave Payments uses a statistical model contributionStatementCopyrightFooter=©2016 - {{currentYear}} Brave Software. Brave is a registered trademark of Brave Software. Site names may be trademarks or registered trademarks of the site owner. contributionStatements=Contribution statements listOfContributionStatements=List of contribution statements -bitcoinPaymentURL=Your Brave wallet address -bitcoinQRImg.title=Brave wallet QR code -bitcoinQR=Your Brave wallet QR code paymentHistoryTitle=Your Payment History -bitcoinCopyAddress=Copy Bitcoin address to clipboard -bitcoinVisitAccount=Transfer BTC bitcoinBalance=Please transfer:  -bitcoinWalletNotAvailable=Wallet information not available. :( usd=$ cancel=Cancel done=Done @@ -129,27 +120,15 @@ ok=Ok autoSuggestSites=auto-include notifications=Show notifications nonVerifiedPublishers=Allow contributions to non-verified sites -moneyAdd=Use your debit/credit card -moneyAddSubTitle=No Bitcoin needed! -outsideUSAPayment=Buy Bitcoin at our recommended source -coinbaseNotAvailable=Sorry! Adding funds with a credit/debit card is available only for contributions of $5/month at the moment. add=Fund with debit/credit -transferTime=Transfer may take up to 40 minutes -fundingDisabled1=Card funding is temporarily unavailable. -fundingDisabled2=We apologize for the inconvenience. -fundingDisabled3=Learn more... addFundsTitle=Add funds… addFunds=Three ways to add funds to your Brave Wallet copy=Copy +copied=Copied! firstKey=Key 1 -secondKey=Key 2 firstRecoveryKey=Recovery Key 1 -secondRecoveryKey=Recovery Key 2 addFundsAlternate=Add funds to your Brave Wallet copyToClipboard=Copy to clipboard -smartphoneTitle=Use your smartphone app to transfer Bitcoin -displayQRCode=Display QR code -coinbaseMessage=debit/credit funding powered by coinbase date=Date totalAmount=Total Amount receiptLink=Receipt Link @@ -207,7 +186,7 @@ blockCanvasFingerprinting=Fingerprinting Protection (may break some sites) advancedSettingsTitle=Advanced Settings for Brave Payments advancedSettingsIcon.title=Advanced Settings ledgerRecoveryTitle=Recover your Brave wallet -ledgerRecoverySubtitle=Enter your recovery keys below +ledgerRecoverySubtitle=Enter your recovery key below ledgerRecoverySucceeded=Success! ledgerRecoveryFailedTitle=Recovery Failed ledgerRecoveryFailedMessage=Please re-enter keys or try different keys. @@ -215,7 +194,6 @@ ledgerRecoveryNetworkFailedTitle=Network Error ledgerRecoveryNetworkFailedMessage=Please check your internet connection and try again. ledgerRecoveryContent=Your previous wallet will now be used. Your new wallet will be discarded. ledgerBackupTitle=Backup your Brave wallet -ledgerBackupContent=Below, you will find the anonymized recovery keys that are required if you ever lose access to this computer. minimumPageTimeSetting=Minimum page time before logging a visit minimumVisitsSetting=Minimum visits for publisher relevancy minimumPageTimeLow=5 seconds @@ -228,8 +206,6 @@ backupLedger=Backup your wallet balanceRecovered={{balance}} was recovered and transferred to your Brave wallet. recoverLedger=Recover your wallet recover=Recover -recoverFromFile=Import recovery keys -printKeys=Print keys saveRecoveryFile=Save recovery file… advancedPrivacySettings=Advanced Privacy Settings: braveryDefaults=Default Shields: All Sites @@ -387,3 +363,29 @@ tabPreviewTiming=Time to wait before previewing a tab long=Long normal=Normal short=Short +addFundsHeader=Add funds to your Brave Wallet +uphold=All transactions are processed by Uphold. +learnMore=Learn more... +backWithArrow=⟨ Previous +nextWithArrow=Next ⟩ +balance=Balance: +helloBat=Hello, and thank you for using Brave Payments! +helloBatText1=Brave Payments allows you to make anonymous, monthly
contributions to the publishers you visit on the internet. +helloBatText2=Please note that your Brave Payments Wallet is unidirectional, and the money you transfer from outside accounts can not be retrieved or refunded. The wallet’s primary purpose is to send your contributions to publishers each month, based on your control and advisement. +batContributionTitle=Introducing BAT Contribution Matching! +batContributionText1=To say thanks to our early adopters, Brave is matching the next 5.00 USD of BATs that you add to your wallet. +batContributionText2=Just transfer any amount from your crypto-currency wallet and we’ll match the next 5.00 USD. +batContributionText3= Note: No need to do anything to participate after transferring funds. Your Brave wallet balance just increases by up to 5.00 USD. This promotion is available for a limited time +addFundsWizardMainHeader=Brave makes it easy to transfer funds from your existing crypto-currency wallet to your Brave BAT Wallet. +addFundsWizardMainOptions=Select one of the currencies below to begin a new transfer. +addFundsWizardMainReminder=Reminder: The Brave Wallet is unidirectional and BAT flows to publisher sites. For more information about Brave Payments, please visit +theFAQ=the FAQ. +addFundsWizardAddressHeader=Go to your external {{currencyName}} account and send {{currency}} to your Brave wallet address below: +addFundsWizardAddressInputNote=Note: We recommend starting with enough {{currency}} to cover your first contribution of {{funds}}. +addFundsWizardAddressFooter=Your {{currency}} will be converted to BAT and appear in your Brave wallet in minutes. +addFundsWizardAddressFooterBAT=Your additional BAT will appear in your Brave wallet after a few minutes. +or=or +qrCodeVersion=QR Code Version +recoverFromFile=Import recovery key +recoveryKey=Recovery Key +ledgerBackupContent=Below, you will find the anonymized recovery key that is required if you ever lose access to this computer.
Make sure you keep this key private, or else your wallet will be compromised. diff --git a/app/locale.js b/app/locale.js index 02d2bbfaf57..5c746c545eb 100644 --- a/app/locale.js +++ b/app/locale.js @@ -211,7 +211,6 @@ var rendererIdentifiers = function () { 'turnOffNotifications', 'copyToClipboard', 'smartphoneTitle', - 'displayQRCode', 'updateLater', 'updateHello', // notifications diff --git a/app/renderer/components/common/clipboardButton.js b/app/renderer/components/common/clipboardButton.js index c7ddead3f4f..0729e9d1f17 100644 --- a/app/renderer/components/common/clipboardButton.js +++ b/app/renderer/components/common/clipboardButton.js @@ -36,6 +36,8 @@ class ClipboardButton extends React.Component { return : null) + subTitle = (this.props.subTitle + ? : null) } return
{title} + {subTitle} {close} @@ -108,7 +122,7 @@ class ModalOverlay extends ImmutableComponent { } render () { - return
} @@ -90,7 +91,11 @@ const styles = StyleSheet.create({ }, aboutPageSectionTitle: { - display: 'inline-block' + display: 'inline-block', + whiteSpace: 'nowrap' + }, + aboutPageSectionTitle_canWrap: { + whiteSpace: 'normal' }, aboutPageSectionSubTitle: { fontSize: '16px', diff --git a/app/renderer/components/common/textbox.js b/app/renderer/components/common/textbox.js index 6de92ffa90d..93b7c40f7f1 100644 --- a/app/renderer/components/common/textbox.js +++ b/app/renderer/components/common/textbox.js @@ -17,8 +17,7 @@ class Textbox extends ImmutableComponent { styles.textbox, (this.props.readonly || this.props.readOnly) ? styles.readOnly : styles.outlineable, this.props['data-isCommonForm'] && commonStyles.isCommonForm, - this.props['data-isSettings'] && styles.isSettings, - this.props['data-isRecoveryKeyTextbox'] && styles.recoveryKeys + this.props['data-isSettings'] && styles.isSettings ) return @@ -31,15 +30,32 @@ class FormTextbox extends ImmutableComponent { } } -class SettingTextbox extends ImmutableComponent { +class GroupedFormTextbox extends ImmutableComponent { render () { - return + return ( +
+ +
+ {this.props.groupedItem} +
+
+ ) } } -class RecoveryKeyTextbox extends ImmutableComponent { +class SettingTextbox extends ImmutableComponent { render () { - return + return } } @@ -93,14 +109,49 @@ const styles = StyleSheet.create({ }, isDefault: { fontSize: globalStyles.spacing.textAreaFontSize // Issue #6851 + }, + + groupedFormTextBox: { + display: 'flex', + flex: 1 + }, + + groupedFormTextBox__firstGroupedItem: { + boxSizing: 'border-box', + background: '#fff', + borderTopLeftRadius: '4px', + borderBottomLeftRadius: '4px', + borderTopRightRadius: 0, + borderBottomRightRadius: 0, + boxShadow: 'inset 0 1px 1px rgba(0, 0, 0, 0.1)', + display: 'block', + border: 'solid 1px rgba(0, 0, 0, 0.2)', + fontSize: '14.5px', + height: '2.25em', + outline: 'none', + padding: '0.4em', + width: '100%', + color: 'rgb(68, 68, 68)' + }, + + groupedFormTextBox__lastGroupedItem: { + border: 'solid 1px rgba(0, 0, 0, 0.2)', + borderTopLeftRadius: 0, + borderBottomLeftRadius: 0, + borderTopRightRadius: '4px', + borderBottomRightRadius: '4px', + width: '30px', + display: 'flex', + alignItems: 'center', + justifyContent: 'center' } }) module.exports = { Textbox, FormTextbox, + GroupedFormTextbox, SettingTextbox, - RecoveryKeyTextbox, TextArea, DefaultTextArea } diff --git a/app/renderer/components/preferences/payment/addFundsDialog/addFundsDialog.js b/app/renderer/components/preferences/payment/addFundsDialog/addFundsDialog.js new file mode 100644 index 00000000000..6a939eeca17 --- /dev/null +++ b/app/renderer/components/preferences/payment/addFundsDialog/addFundsDialog.js @@ -0,0 +1,69 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const React = require('react') + +// Components +const {BatWelcomeScreen, BatContribMatching} = require('./steps/addFundsBatScreen') +const AddFundsWizardMain = require('./steps/addFundsWizardMain') +const AddFundsWizardAddress = require('./steps/addFundsWizardAddress') + +class AddFundsDialog extends React.Component { + get currentPage () { + return this.props.addFundsDialog.get('currentPage') + } + + get currency () { + return this.props.addFundsDialog.get('currency') + } + + get currencyQRCode () { + const walletQR = this.props.walletQR + + if (walletQR != null) { + return walletQR.get(this.currency) + } + } + + get currencyAddress () { + const addresses = this.props.addresses + + if (addresses != null) { + return addresses.get(this.currency) + } + } + + get currentView () { + switch (this.currentPage) { + case 'batContribMatching': + return + case 'addFundsWizardMain': + return + case 'addFundsWizardAddress': + return ( + + ) + default: + return + } + } + + render () { + return ( +
+ { + this.currentView + } +
+ ) + } +} + +module.exports = AddFundsDialog diff --git a/app/renderer/components/preferences/payment/addFundsDialog/addFundsDialogFooter.js b/app/renderer/components/preferences/payment/addFundsDialog/addFundsDialogFooter.js new file mode 100644 index 00000000000..3b8ae33b6d8 --- /dev/null +++ b/app/renderer/components/preferences/payment/addFundsDialog/addFundsDialogFooter.js @@ -0,0 +1,178 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Components +const React = require('react') +const BrowserButton = require('../../../common/browserButton') + +// Actions +const appActions = require('../../../../../../js/actions/appActions') + +// Styles +const {StyleSheet, css} = require('aphrodite') +const upholdLogo = require('../../../../../extensions/brave/img/ledger/uphold-logo.png') + +class AddFundsDialogFooter extends React.Component { + constructor (props) { + super(props) + this.onBack = this.onBack.bind(this) + this.onNext = this.onNext.bind(this) + this.onDone = this.onDone.bind(this) + } + + get currentPage () { + return this.props.addFundsDialog.get('currentPage') + } + + onBack () { + switch (this.currentPage) { + case 'batContribMatching': + case 'addFundsWizardMain': + // TODO: add the below comment under 'batContribMatching' + // when it is publicly available + // appActions.onChangeAddFundsDialogStep('batContribMatching') + // break + appActions.onChangeAddFundsDialogStep('batWelcomeScreen') + break + case 'addFundsWizardAddress': + appActions.onChangeAddFundsDialogStep('addFundsWizardMain') + break + default: + break + } + } + + onNext () { + switch (this.currentPage) { + // TODO: replace 'batWelcomeScreen' with 'batContribMatching' + // once latter is available + case 'batWelcomeScreen': + // case 'batContribMatching': + appActions.onChangeAddFundsDialogStep('addFundsWizardMain') + break + case 'addFundsWizardMain': + appActions.onChangeAddFundsDialogStep('addFundsWizardAddress') + break + default: + // TODO: enable again once 'batContribMatching' is available + // and remove the current + // appActions.onChangeAddFundsDialogStep('batContribMatching') + appActions.onChangeAddFundsDialogStep('addFundsWizardMain') + break + } + } + + onDone () { + // close the dialog and set default page + // to add funds wizard to avoid + // user seeing welcome greetings all the time + this.props.onHide() + appActions.onChangeAddFundsDialogStep('addFundsWizardMain') + } + + get showBackButton () { + return ( + this.currentPage != null && + this.currentPage !== 'batWelcomeScreen' && + // Should users be allowed to go back once in the wizard? + // for now they can't + this.currentPage !== 'addFundsWizardMain' + ) + } + + get showNextButton () { + return ( + this.currentPage !== 'addFundsWizardMain' && + this.currentPage !== 'addFundsWizardAddress' + ) + } + + get showDoneButton () { + return ( + this.currentPage === 'addFundsWizardMain' || + this.currentPage === 'addFundsWizardAddress' + ) + } + + render () { + return ( +
+
+ +
+ + +
+
+
+ { + this.showBackButton + ? + : null + } + { + this.showNextButton + ? + : null + } + { + this.showDoneButton + ? + : null + } +
+
+ ) + } +} + +const styles = StyleSheet.create({ + footer: { + display: 'flex', + flex: 1, + justifyContent: 'space-between', + alignItems: 'center' + }, + + footer__start: { + display: 'flex', + alignItems: 'center' + }, + + footer__start__uphold_logo: { + width: '120px', + hestart: '35px' + }, + + footer__start__uphold_text: { + display: 'flex', + flexDirection: 'column', + fontSize: 'small', + fontStyle: 'italic', + margin: '0 10px' + } +}) + +module.exports = AddFundsDialogFooter diff --git a/app/renderer/components/preferences/payment/addFundsDialog/steps/addFundsBatScreen.js b/app/renderer/components/preferences/payment/addFundsDialog/steps/addFundsBatScreen.js new file mode 100644 index 00000000000..f66211c8ae6 --- /dev/null +++ b/app/renderer/components/preferences/payment/addFundsDialog/steps/addFundsBatScreen.js @@ -0,0 +1,91 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Components +const React = require('react') +const {AboutPageSectionTitle} = require('../../../../common/sectionTitle') + +// Styles +const {StyleSheet, css} = require('aphrodite') +const {addFundsDialogMinHeight} = require('../../../../styles/global').spacing +const batIcon = require('../../../../../../extensions/brave/img/ledger/cryptoIcons/BAT_icon.svg') + +class BatWelcomeScreen extends React.Component { + render () { + return ( +
+ +

+

+

+ ) + } +} + +class BatContribMatching extends React.Component { + render () { + return ( +
+
+ +

+

+

+

+

+ ) + } +} + +const styles = StyleSheet.create({ + batScreen: { + position: 'relative', + display: 'flex', + flexDirection: 'column', + justifyContent: 'space-between', + paddingLeft: '60px', + minHeight: addFundsDialogMinHeight, + + '::before': { + position: 'absolute', + top: 0, + left: 0, + content: '""', + backgroundRepeat: 'no-repeat', + backgroundSize: 'contain', + backgroundImage: `url(${batIcon})`, + width: '40px', + height: '40px' + } + }, + + batScreen__text: { + margin: '20px 0' + }, + + batScreen__text_small: { + fontSize: 'small' + } +}) + +module.exports = { + BatWelcomeScreen, + BatContribMatching +} diff --git a/app/renderer/components/preferences/payment/addFundsDialog/steps/addFundsWizardAddress.js b/app/renderer/components/preferences/payment/addFundsDialog/steps/addFundsWizardAddress.js new file mode 100644 index 00000000000..5a6b9065d0a --- /dev/null +++ b/app/renderer/components/preferences/payment/addFundsDialog/steps/addFundsWizardAddress.js @@ -0,0 +1,249 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const React = require('react') +const {StyleSheet, css} = require('aphrodite') + +// Components +const {GroupedFormTextbox} = require('../../../../common/textbox') +const ClipboardButton = require('../../../../common/clipboardButton') + +// Actions +const appActions = require('../../../../../../../js/actions/appActions') + +// Styles +const globalStyles = require('../../../../styles/global') +const {addFundsDialogMinHeight} = require('../../../../styles/global').spacing +const ethIcon = require('../../../../../../extensions/brave/img/ledger/cryptoIcons/ETH_icon.svg') +const btcIcon = require('../../../../../../extensions/brave/img/ledger/cryptoIcons/BTC_icon.svg') +const ltcIcon = require('../../../../../../extensions/brave/img/ledger/cryptoIcons/LTC_icon.svg') +const batIcon = require('../../../../../../extensions/brave/img/ledger/cryptoIcons/BAT_icon.svg') + +class AddFundsWizardAddress extends React.Component { + constructor (props) { + super(props) + this.onCopy = this.onCopy.bind(this) + } + + get currency () { + return this.props.currency + } + + get currencyName () { + switch (this.currency) { + case 'ETH': + return 'Ethereum' + case 'BTC': + return 'Bitcoin' + case 'LTC': + return 'Litecoin' + // defaults to BAT + default: + return this.currency + } + } + + get copyToClipboardButton () { + return ( + + ) + } + + // Input note for BAT is different to avoid repetition + get footerNote () { + return this.currency === 'BAT' + ? 'addFundsWizardAddressFooterBAT' + : 'addFundsWizardAddressFooter' + } + + onCopy () { + if (!this.addressInputNode) { + return + } + appActions.clipboardTextCopied(this.addressInputNode.value) + } + + componentDidMount () { + if (!this.addressInputNode) { + return + } + this.addressInputNode.focus() + this.addressInputNode.select() + } + + render () { + const testId = ['addFundsWizardAddress', this.currency].join('') + return ( +
+
+
+
+
+ { this.addressInputNode = node }} + value={this.props.address} + groupedItem={this.copyToClipboardButton} + groupedItemTitle='copyToClipboard' + /> +

+

+

+
+ +
+
+ +
+
+ ) + } +} + +const styles = StyleSheet.create({ + wizardAddress: { + position: 'relative', + display: 'flex', + flexDirection: 'column', + justifyContent: 'space-between', + paddingLeft: '60px', + minHeight: addFundsDialogMinHeight, + + '::before': { + position: 'absolute', + top: 0, + left: 0, + content: '""', + backgroundRepeat: 'no-repeat', + backgroundSize: 'contain', + width: '40px', + height: '40px' + } + }, + + wizardAddress_bat: { + '::before': { + backgroundImage: `url(${batIcon})` + } + }, + + wizardAddress_eth: { + '::before': { + backgroundImage: `url(${ethIcon})` + } + }, + + wizardAddress_btc: { + '::before': { + backgroundImage: `url(${btcIcon})` + } + }, + + wizardAddress_ltc: { + '::before': { + backgroundImage: `url(${ltcIcon})` + } + }, + + wizardAddress__main: { + display: 'flex', + flex: 1, + justifyContent: 'space-between', + margin: '15px 0' + }, + + wizardAddress__text_note: { + fontSize: 'small', + margin: '10px 0' + }, + + wizardAddress__text_small: { + fontSize: 'small' + }, + + wizardAddress__inputBox: { + display: 'flex', + flex: 1, + alignItems: 'center', + alignSelf: 'center', + justifyContent: 'space-between', + height: '120px' + }, + + wizardAddress__fancyDivider: { + display: 'flex', + width: '40px', + height: '100%', + whiteSpace: 'nowrap', + margin: '0 20px', + // new law: if something can be done in CSS, it will be done in CSS. + backgroundImage: 'linear-gradient(black 33%, rgba(255,255,255,0) 0%)', + backgroundPosition: 'center', + backgroundSize: '2px 6px', + backgroundRepeat: 'repeat-y' + }, + + wizardAddress__fancyDivider__text: { + display: 'flex', + background: 'white', + margin: 'auto', + padding: '8px', + color: '#000' + }, + + wizardAddress__qrCode: { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + padding: '15px 0' + }, + + wizardAddress__qrCode__text: { + color: '#777', + margin: '5px 0' + }, + + wizardAddress__qrCode__image: { + maxWidth: '100px' + } +}) + +module.exports = AddFundsWizardAddress diff --git a/app/renderer/components/preferences/payment/addFundsDialog/steps/addFundsWizardMain.js b/app/renderer/components/preferences/payment/addFundsDialog/steps/addFundsWizardMain.js new file mode 100644 index 00000000000..49b176029b4 --- /dev/null +++ b/app/renderer/components/preferences/payment/addFundsDialog/steps/addFundsWizardMain.js @@ -0,0 +1,214 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Components +const React = require('react') +const BrowserButton = require('../../../../common/browserButton') + +// Actions +const appActions = require('../../../../../../../js/actions/appActions') + +// Styles +const {StyleSheet, css} = require('aphrodite') +const {addFundsDialogMinHeight} = require('../../../../styles/global').spacing +const walletIcon = require('../../../../../../extensions/brave/img/ledger/wallet_icon.svg') +const ethIcon = require('../../../../../../extensions/brave/img/ledger/cryptoIcons/ETH_icon.svg') +const btcIcon = require('../../../../../../extensions/brave/img/ledger/cryptoIcons/BTC_icon.svg') +const ltcIcon = require('../../../../../../extensions/brave/img/ledger/cryptoIcons/LTC_icon.svg') +const batIcon = require('../../../../../../extensions/brave/img/ledger/cryptoIcons/BAT_icon.svg') + +class AddFundsWizardMain extends React.Component { + constructor (props) { + super(props) + this.onClickETH = this.onClickETH.bind(this) + this.onClickBTC = this.onClickBTC.bind(this) + this.onClickLTC = this.onClickLTC.bind(this) + this.onClickBAT = this.onClickBAT.bind(this) + } + + onClickBTC () { + appActions.onChangeAddFundsDialogStep('addFundsWizardAddress', 'BTC') + } + + onClickETH () { + appActions.onChangeAddFundsDialogStep('addFundsWizardAddress', 'ETH') + } + + onClickBAT () { + appActions.onChangeAddFundsDialogStep('addFundsWizardAddress', 'BAT') + } + + onClickLTC () { + appActions.onChangeAddFundsDialogStep('addFundsWizardAddress', 'LTC') + } + + render () { + return ( +
+ ) + } +} + +const styles = StyleSheet.create({ + wizardMain: { + position: 'relative', + display: 'flex', + flexDirection: 'column', + justifyContent: 'space-between', + paddingLeft: '60px', + minHeight: addFundsDialogMinHeight, + + '::before': { + position: 'absolute', + top: 0, + left: 0, + content: '""', + backgroundRepeat: 'no-repeat', + backgroundImage: `url(${walletIcon})`, + backgroundSize: 'contain', + width: '40px', + height: '40px' + } + }, + + wizardMain__text: { + margin: '20px 0' + }, + + wizardMain__text_bold: { + fontWeight: 600 + }, + + wizardMain__text_small: { + fontSize: 'small' + }, + + // but this inside a pseudo-state + // otherwise you can't have a gradient background + wizardMain__currencyIcon: { + position: 'relative', + width: '100px', + height: '80px', + + // our icon relies here + '::before': { + content: '""', + position: 'absolute', + left: 0, + top: 0, + bottom: 0, + right: 0, + margin: 'auto', + backgroundRepeat: 'no-repeat', + backgroundPosition: 'center center', + backgroundSize: 'contain', + width: '70%', + height: '70%' + }, + + // here's the currency abbr + // 'content' is defined per icon + '::after': { + position: 'absolute', + right: 0, + top: 0, + margin: '5px', + fontWeight: 600, + textTransform: 'uppercase', + fontSize: 'xx-small' + } + }, + + wizardMain__currencyIcon_btc: { + '::before': { + backgroundImage: `url(${btcIcon})` + }, + + '::after': { + content: '"btc"' + } + }, + + wizardMain__currencyIcon_eth: { + '::before': { + backgroundImage: `url(${ethIcon})` + }, + + '::after': { + content: '"eth"' + } + }, + + wizardMain__currencyIcon_bat: { + '::before': { + backgroundImage: `url(${batIcon})` + }, + + '::after': { + content: '"bat"' + } + }, + + wizardMain__currencyIcon_ltc: { + '::before': { + backgroundImage: `url(${ltcIcon})` + }, + + '::after': { + content: '"ltc"' + } + } +}) + +module.exports = AddFundsWizardMain diff --git a/app/renderer/components/preferences/payment/bitcoinDashboard.js b/app/renderer/components/preferences/payment/bitcoinDashboard.js deleted file mode 100644 index 8c084034eb7..00000000000 --- a/app/renderer/components/preferences/payment/bitcoinDashboard.js +++ /dev/null @@ -1,685 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -const React = require('react') -const {StyleSheet, css} = require('aphrodite/no-important') - -// components -const BrowserButton = require('../../common/browserButton') -const cx = require('../../../../../js/lib/classSet') -const ModalOverlay = require('../../common/modalOverlay') -const ImmutableComponent = require('../../immutableComponent') - -// styles -const globalStyles = require('../../styles/global') -const commonStyles = require('../../styles/commonStyles') - -const CoinBase = require('../../../../extensions/brave/img/coinbase_logo.png') -const Andorid = require('../../../../extensions/brave/img/android_download.svg') -const IOS = require('../../../../extensions/brave/img/ios_download.svg') - -// other -const coinbaseCountries = require('../../../../../js/constants/coinbaseCountries') -const config = require('../../../../../js/constants/config') -const getSetting = require('../../../../../js/settings').getSetting -const settings = require('../../../../../js/constants/settings') -const aboutActions = require('../../../../../js/about/aboutActions') - -class BitcoinDashboard extends ImmutableComponent { - constructor () { - super() - this.buyCompleted = false - this.openBuyURLTab = this.openBuyURLTab.bind(this) - } - - get currency () { - return this.props.ledgerData.get('currency') || 'USD' - } - - get amount () { - return getSetting(settings.PAYMENTS_CONTRIBUTION_AMOUNT, this.props.settings) || 0 - } - - get canUseCoinbase () { - if (!this.props.ledgerData.get('buyMaximumUSD')) return true - - return this.currency === 'USD' && this.amount < this.props.ledgerData.get('buyMaximumUSD') - } - - get userInAmerica () { - const countryCode = this.props.ledgerData.get('countryCode') - return !(countryCode && countryCode !== 'US') - } - - openBuyURLTab () { - // close parent dialog - this.props.hideParentOverlay() - } - - faCreditCard () { - return - } - faBitcoin () { - return - - - - } - faSmartphone () { - return - } - - bitcoinPurchaseButton (options) { - const disabled = options && options.disabled - const buttonAttrs = { - l10nId: 'add', - testId: 'bitcoinPurchaseButton', - primaryColor: true, - panelItem: true, - disabled: disabled - } - const hrefAttrs = { - href: this.props.ledgerData.get('buyURL'), - target: '_blank' - } - - if (disabled) { - buttonAttrs.disabled = 'disabled' - hrefAttrs.disabled = 'disabled' - } else { - hrefAttrs.onClick = this.openBuyURLTab - } - - if (!this.props.ledgerData.get('buyURLFrame')) { - if (!disabled) { - buttonAttrs.onClick = this.props.showOverlay.bind(this) - } - return - } - - return - - - } - - coinbaseAvailability () { - const disabled = true - if (this.canUseCoinbase) { - return
- {this.bitcoinPurchaseButton({disabled})} - { - disabled - ?
- } else { - return
-
-
- } - } - - coinbasePanel () { - return
-
-
-
- {this.faCreditCard()} -
-
-
-
-
-
-
- {this.coinbaseAvailability()} -
- } - exchangePanel () { - const url = this.props.ledgerData.getIn(['exchangeInfo', 'exchangeURL']) - const name = this.props.ledgerData.getIn(['exchangeInfo', 'exchangeName']) - // Call worldWidePanel if we don't have the URL or Name - if (!url || !name) { - return this.worldWidePanel() - } else { - return
-
- } - } - worldWidePanel () { - return
-
-
-
- {this.faCreditCard()} -
-
-
-
-
-
-
- - - -
-
- } - - bitcoinPanel () { - const ledgerData = this.props.ledgerData - - return
-
-
-
- {this.faBitcoin()} -
-
-
-
-
-
-
- { - ledgerData.get('address') - ?
- { - ledgerData.get('hasBitcoinHandler') && ledgerData.get('paymentURL') - ?
- - - -
- : null - } -
-
{ledgerData.get('address')}
- -
- :
-
-
- } -
- } - - smartphonePanel () { - return
-
-
-
- {this.faSmartphone()} -
-
-
-
-
-
-
- -
-
- } - - qrcodeOverlayContent () { - return
- -
-
- } - qrcodeOverlayFooter () { - if (coinbaseCountries.indexOf(this.props.ledgerData.get('countryCode')) > -1) { - return
-
- } - return null - } - - bitcoinOverlayContent () { - return