From a43960585f5dbd4bb55d72e45a35002bec98ad70 Mon Sep 17 00:00:00 2001 From: oluapocram Date: Thu, 3 Aug 2017 16:58:29 -0400 Subject: [PATCH 01/65] Update manual-core-to-browser-migration.md Updated documentation to disable development mode after copying Core API Password. --- docs/manual-core-to-browser-migration.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/manual-core-to-browser-migration.md b/docs/manual-core-to-browser-migration.md index 6a7fbd219..b37d9e381 100644 --- a/docs/manual-core-to-browser-migration.md +++ b/docs/manual-core-to-browser-migration.md @@ -23,6 +23,7 @@ associated with it. Copy this address to an easily accessible place. 3. Click on the Blockstack icon in your menu bar. 4. Click "Copy Core API Password" 5. Put this in an easily accessible place. +6. Option-Client on the Blockstack icon in your menu bar to disable. ## Sanity checks From 25cc606b6580aca0ce32c28b9bcefb4b82fe2e32 Mon Sep 17 00:00:00 2001 From: Larry Salibra Date: Sat, 5 Aug 2017 06:52:34 +0800 Subject: [PATCH 02/65] clean lint --- app/js/routes.js | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/app/js/routes.js b/app/js/routes.js index bd8ae4bc1..fea8b70ec 100644 --- a/app/js/routes.js +++ b/app/js/routes.js @@ -1,5 +1,3 @@ -'use strict' - import React from 'react' import { Router, Route, IndexRoute, browserHistory } from 'react-router' @@ -33,11 +31,11 @@ import AuthPage from './auth/AuthPage' import NotFoundPage from './errors/NotFoundPage' export default ( - - - + + + - + @@ -46,9 +44,9 @@ export default ( - + - + @@ -56,15 +54,15 @@ export default ( - + - + - + - - - - + + + + ) From 38d1503af933779e53fe4c46a0176739e39bfe9d Mon Sep 17 00:00:00 2001 From: Larry Salibra Date: Sat, 5 Aug 2017 07:16:51 +0800 Subject: [PATCH 03/65] add page skeletons #669 --- .../profiles/AddUsernameConfirmationPage.js | 43 ++++++++++++++ app/js/profiles/AddUsernamePaymentPage.js | 43 ++++++++++++++ app/js/profiles/AddUsernameSearchPage.js | 43 ++++++++++++++ app/js/profiles/RegisterProfilePage.js | 56 +++++++++---------- app/js/routes.js | 6 ++ 5 files changed, 161 insertions(+), 30 deletions(-) create mode 100644 app/js/profiles/AddUsernameConfirmationPage.js create mode 100644 app/js/profiles/AddUsernamePaymentPage.js create mode 100644 app/js/profiles/AddUsernameSearchPage.js diff --git a/app/js/profiles/AddUsernameConfirmationPage.js b/app/js/profiles/AddUsernameConfirmationPage.js new file mode 100644 index 000000000..788bff327 --- /dev/null +++ b/app/js/profiles/AddUsernameConfirmationPage.js @@ -0,0 +1,43 @@ +import React, { Component, PropTypes } from 'react' +import { bindActionCreators } from 'redux' +import { connect } from 'react-redux' +import { AccountActions } from '../account/store/account' +import { AvailabilityActions } from './store/availability' +import { IdentityActions } from './store/identity' +import { RegistrationActions } from './store/registration' + +import log4js from 'log4js' + +const logger = log4js.getLogger('profiles/AddUsernameSearchPage.js') + +function mapStateToProps(state) { + return { + } +} + +function mapDispatchToProps(dispatch) { + return bindActionCreators(Object.assign({}, + IdentityActions, AccountActions, RegistrationActions, AvailabilityActions), dispatch) +} + +class AddUsernameConfirmationPage extends Component { + static propTypes = { + } + + constructor(props) { + super(props) + + this.state = { + } + } + + render() { + return ( +
+ AddUsernameConfirmationPage +
+ ) + } +} + +export default connect(mapStateToProps, mapDispatchToProps)(AddUsernameConfirmationPage) diff --git a/app/js/profiles/AddUsernamePaymentPage.js b/app/js/profiles/AddUsernamePaymentPage.js new file mode 100644 index 000000000..942cdd5ea --- /dev/null +++ b/app/js/profiles/AddUsernamePaymentPage.js @@ -0,0 +1,43 @@ +import React, { Component, PropTypes } from 'react' +import { bindActionCreators } from 'redux' +import { connect } from 'react-redux' +import { AccountActions } from '../account/store/account' +import { AvailabilityActions } from './store/availability' +import { IdentityActions } from './store/identity' +import { RegistrationActions } from './store/registration' + +import log4js from 'log4js' + +const logger = log4js.getLogger('profiles/AddUsernameSearchPage.js') + +function mapStateToProps(state) { + return { + } +} + +function mapDispatchToProps(dispatch) { + return bindActionCreators(Object.assign({}, + IdentityActions, AccountActions, RegistrationActions, AvailabilityActions), dispatch) +} + +class AddUsernamePaymentPage extends Component { + static propTypes = { + } + + constructor(props) { + super(props) + + this.state = { + } + } + + render() { + return ( +
+ AddUsernamePaymentPage +
+ ) + } +} + +export default connect(mapStateToProps, mapDispatchToProps)(AddUsernamePaymentPage) diff --git a/app/js/profiles/AddUsernameSearchPage.js b/app/js/profiles/AddUsernameSearchPage.js new file mode 100644 index 000000000..8f0ab726c --- /dev/null +++ b/app/js/profiles/AddUsernameSearchPage.js @@ -0,0 +1,43 @@ +import React, { Component, PropTypes } from 'react' +import { bindActionCreators } from 'redux' +import { connect } from 'react-redux' +import { AccountActions } from '../account/store/account' +import { AvailabilityActions } from './store/availability' +import { IdentityActions } from './store/identity' +import { RegistrationActions } from './store/registration' + +import log4js from 'log4js' + +const logger = log4js.getLogger('profiles/AddUsernameSearchPage.js') + +function mapStateToProps(state) { + return { + } +} + +function mapDispatchToProps(dispatch) { + return bindActionCreators(Object.assign({}, + IdentityActions, AccountActions, RegistrationActions, AvailabilityActions), dispatch) +} + +class AddUsernameSearchPage extends Component { + static propTypes = { + } + + constructor(props) { + super(props) + + this.state = { + } + } + + render() { + return ( +
+ AddUsernameSearchPage +
+ ) + } +} + +export default connect(mapStateToProps, mapDispatchToProps)(AddUsernameSearchPage) diff --git a/app/js/profiles/RegisterProfilePage.js b/app/js/profiles/RegisterProfilePage.js index 8735977c1..2e5cacc35 100644 --- a/app/js/profiles/RegisterProfilePage.js +++ b/app/js/profiles/RegisterProfilePage.js @@ -112,9 +112,6 @@ class RegisterPage extends Component { this.props.coreAPIPassword) if (!this.state.storageConnected) { this.displayConnectStorageAlert() - } else if (this.state.zeroBalance) { - logger.debug('Zero balance...displaying alert...') - this.displayZeroBalanceAlert() } } @@ -141,15 +138,12 @@ class RegisterPage extends Component { }) if (!storageConnected) { this.displayConnectStorageAlert() - } else if (zeroBalance) { - this.displayZeroBalanceAlert() } else if (registration.registrationSubmitting || registration.registrationSubmitted || registration.profileUploading || registration.error) { this.displayRegistrationAlerts(registration) - } - else { + } else { this.displayPricingAndAvailabilityAlerts(availability) } @@ -214,8 +208,6 @@ class RegisterPage extends Component { this.props.beforeRegister() // clears any error & resets registration state const username = event.target.value.toLowerCase().replace(/\W+/g, '') - const tld = this.state.tlds[this.state.type] - const domainName = `${username}.${tld}` this.setState({ username @@ -314,9 +306,10 @@ class RegisterPage extends Component { return (
-
+
-
+
+

Search for your username

{ this.state.alerts.map((alert, index) => { return ( @@ -324,28 +317,31 @@ class RegisterPage extends Component { key={index} message={alert.message} status={alert.status} url={alert.url} /> ) - })} -
- -
- - .{tld} -
-
-
+ }) + } +

+ Add a username to save your profile so you can interact with other + people on the decentralized internet. +

+
+ +
+
+
diff --git a/app/js/routes.js b/app/js/routes.js index fea8b70ec..d6d046b13 100644 --- a/app/js/routes.js +++ b/app/js/routes.js @@ -5,6 +5,9 @@ import App from './App' import HomeScreenPage from './HomeScreenPage' import ProfilesApp from './profiles/ProfilesApp' +import AddUsernameSearchPage from './profiles/AddUsernameSearchPage' +import AddUsernamePaymentPage from './profiles/AddUsernamePaymentPage' +import AddUsernameConfirmationPage from './profiles/AddUsernameConfirmationPage' import AllProfilesPage from './profiles/AllProfilesPage' import ViewProfilePage from './profiles/ViewProfilePage' import EditProfilePage from './profiles/EditProfilePage' @@ -42,6 +45,9 @@ export default ( + + + From 02f51879663782a9e58a723768e9aa9825fe9fcc Mon Sep 17 00:00:00 2001 From: Larry Salibra Date: Sat, 5 Aug 2017 19:28:12 +0800 Subject: [PATCH 04/65] start work on add username search page #669 --- app/js/profiles/AddUsernameSearchPage.js | 155 ++++++++++++++++++++- app/js/profiles/ViewProfilePage.js | 2 +- app/js/profiles/components/IdentityItem.js | 4 +- 3 files changed, 157 insertions(+), 4 deletions(-) diff --git a/app/js/profiles/AddUsernameSearchPage.js b/app/js/profiles/AddUsernameSearchPage.js index 8f0ab726c..30a8e2346 100644 --- a/app/js/profiles/AddUsernameSearchPage.js +++ b/app/js/profiles/AddUsernameSearchPage.js @@ -1,17 +1,24 @@ import React, { Component, PropTypes } from 'react' import { bindActionCreators } from 'redux' import { connect } from 'react-redux' + +import Alert from '../components/Alert' import { AccountActions } from '../account/store/account' import { AvailabilityActions } from './store/availability' import { IdentityActions } from './store/identity' import { RegistrationActions } from './store/registration' +import { hasNameBeenPreordered, isABlockstackName } from '../utils/name-utils' import log4js from 'log4js' const logger = log4js.getLogger('profiles/AddUsernameSearchPage.js') +const STORAGE_URL = '/account/storage' + function mapStateToProps(state) { return { + api: state.settings.api, + availability: state.profiles.availability } } @@ -22,19 +29,165 @@ function mapDispatchToProps(dispatch) { class AddUsernameSearchPage extends Component { static propTypes = { + api: PropTypes.object.isRequired, + availability: PropTypes.object.isRequired } constructor(props) { super(props) this.state = { + alerts: [], + username: '', + storageConnected: this.props.api.dropboxAccessToken !== null, + availableDomains: { + id: { + registerUrl: this.props.api.registerUrl, + free: false + }, + 'blockstack.id': { + registerUrl: this.props.api.registerUrl, + free: true + } + } + } + this.onChange = this.onChange.bind(this) + this.search = this.search.bind(this) + this.updateAlert = this.updateAlert.bind(this) + this.displayPricingAndAvailabilityAlerts = this.displayPricingAndAvailabilityAlerts.bind(this) + this.displayConnectStorageAlert = this.displayConnectStorageAlert.bind(this) + } + + componentWillReceiveProps(nextProps) { + logger.trace('componentWillReceiveProps') + // Clear alerts + this.setState({ + alerts: [] + }) + + const storageConnected = this.props.api.dropboxAccessToken !== null + const availability = nextProps.availability + + if (!storageConnected) { + this.displayConnectStorageAlert() + } else { + this.displayPricingAndAvailabilityAlerts(availability) + } + } + + onChange(event) { + const username = event.target.value.toLowerCase().replace(/\W+/g, '') + logger.debug(`onChange: ${username}`) + this.setState({ + username + }) + } + + search(event) { + logger.trace('search') + const username = this.state.useranme + logger.debug(`search: user is searching for ${username}`) + event.preventDefault() + const availableDomainSuffixes = Object.keys(this.availableDomains) + const testDomainName = `${username}.${availableDomainSuffixes[0]}` + if (!isABlockstackName(testDomainName)) { + this.updateAlert('danger', `${username} is notNot valid Blockstack name`) + return } + + } + + updateAlert(alertStatus, alertMessage, url = null) { + logger.trace(`updateAlert: alertStatus: ${alertStatus}, alertMessage ${alertMessage}`) + this.setState({ + alerts: [{ + status: alertStatus, + message: alertMessage, + url + }] + }) + } + + displayPricingAndAvailabilityAlerts(availability) { + let tld = this.state.tlds[this.state.type] + const domainName = `${this.state.username}.${tld}` + + if(domainName === availability.lastNameEntered) { + if(availability.names[domainName].error) { + const error = availability.names[domainName].error + console.error(error) + this.updateAlert('danger', `There was a problem checking on price & availability of ${domainName}`) + } else { + if(availability.names[domainName].checkingAvailability) + this.updateAlert('info', `Checking if ${domainName} available...`) + else if(availability.names[domainName].available) { + if(availability.names[domainName].checkingPrice) { + this.updateAlert('info', `${domainName} is available! Checking price...`) + } else { + const price = availability.names[domainName].price + if(price < this.props.coreWalletBalance) { + const roundedUpPrice = roundTo.up(price, 3) + this.updateAlert('info', `${domainName} costs ~${roundedUpPrice} btc to register.`) + } else { + const shortfall = price - this.props.coreWalletBalance + this.updateAlert('danger', `Your wallet doesn't have enough money to buy ${domainName}. Please send at least ${shortfall} more bitcoin to your wallet.`, WALLET_URL) + } + } + } else { + this.updateAlert('danger', `${domainName} has already been registered.`) + } + } + } + } + + displayConnectStorageAlert() { + this.updateAlert('danger', 'Please go to the Storage app and connect a storage provider.', STORAGE_URL) } render() { return (
- AddUsernameSearchPage +
+
+
+
+

Search for your username

+ { + this.state.alerts.map((alert, index) => { + return ( + + ) + }) + } +

+ Add a username to save your profile so you can interact with other + people on the decentralized internet. +

+
+ + +
+
+ +
+
+
) } diff --git a/app/js/profiles/ViewProfilePage.js b/app/js/profiles/ViewProfilePage.js index 0501068b7..16203af3e 100644 --- a/app/js/profiles/ViewProfilePage.js +++ b/app/js/profiles/ViewProfilePage.js @@ -227,7 +227,7 @@ class ViewProfilePage extends Component { } {!this.hasUsername() ? - Add a username diff --git a/app/js/profiles/components/IdentityItem.js b/app/js/profiles/components/IdentityItem.js index 87ed824ff..430d86761 100644 --- a/app/js/profiles/components/IdentityItem.js +++ b/app/js/profiles/components/IdentityItem.js @@ -35,11 +35,11 @@ class IdentityItem extends Component {
  • {this.props.canAddUsername ? - + Add username : -
    +

    {this.props.pending ? '(pending)' : '\u00A0'}

    From 77c90190e424c06263f8c9f9ac6d7a8636e1affe Mon Sep 17 00:00:00 2001 From: Larry Salibra Date: Mon, 7 Aug 2017 11:09:53 +0800 Subject: [PATCH 05/65] refactor reducer to remember last name entered #664 --- app/js/profiles/store/availability/reducer.js | 2 +- tests/profiles/store/availability/reducer.test.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/js/profiles/store/availability/reducer.js b/app/js/profiles/store/availability/reducer.js index cbbe24375..e8d58fec0 100644 --- a/app/js/profiles/store/availability/reducer.js +++ b/app/js/profiles/store/availability/reducer.js @@ -18,7 +18,7 @@ function AvailabilityReducer(state = initialState, action) { error: null }) }), - lastNameEntered: action.domainName + lastNameEntered: action.domainName.split('.')[0] }) } case types.NAME_AVAILABLE: diff --git a/tests/profiles/store/availability/reducer.test.js b/tests/profiles/store/availability/reducer.test.js index e1a907caa..647d29bf7 100644 --- a/tests/profiles/store/availability/reducer.test.js +++ b/tests/profiles/store/availability/reducer.test.js @@ -26,7 +26,7 @@ describe('Availability Store: AvailabilityReducer', () => { error: null } }, - lastNameEntered: 'satoshi.id' + lastNameEntered: 'satoshi' } const actualState = AvailabilityReducer(undefined, action) assert.deepEqual(actualState, expectedState) From 419baad55c25ead229e2403289b563ccd786322f Mon Sep 17 00:00:00 2001 From: Larry Salibra Date: Tue, 8 Aug 2017 16:13:34 +0800 Subject: [PATCH 06/65] list availability #669 --- app/js/profiles/AddUsernameSearchPage.js | 76 ++++++++++++++++++------ 1 file changed, 57 insertions(+), 19 deletions(-) diff --git a/app/js/profiles/AddUsernameSearchPage.js b/app/js/profiles/AddUsernameSearchPage.js index 30a8e2346..91d2f226d 100644 --- a/app/js/profiles/AddUsernameSearchPage.js +++ b/app/js/profiles/AddUsernameSearchPage.js @@ -36,20 +36,26 @@ class AddUsernameSearchPage extends Component { constructor(props) { super(props) + const availableDomains = { + id: { + registerUrl: this.props.api.registerUrl, + free: false + }, + 'blockstack.id': { + registerUrl: this.props.api.registerUrl, + free: true + } + } + + const nameSuffixes = Object.keys(availableDomains) + this.state = { alerts: [], username: '', + searchingUsername: '', storageConnected: this.props.api.dropboxAccessToken !== null, - availableDomains: { - id: { - registerUrl: this.props.api.registerUrl, - free: false - }, - 'blockstack.id': { - registerUrl: this.props.api.registerUrl, - free: true - } - } + availableDomains, + nameSuffixes } this.onChange = this.onChange.bind(this) this.search = this.search.bind(this) @@ -85,16 +91,19 @@ class AddUsernameSearchPage extends Component { search(event) { logger.trace('search') - const username = this.state.useranme + const username = this.state.username logger.debug(`search: user is searching for ${username}`) event.preventDefault() - const availableDomainSuffixes = Object.keys(this.availableDomains) + const availableDomainSuffixes = Object.keys(this.state.availableDomains) const testDomainName = `${username}.${availableDomainSuffixes[0]}` if (!isABlockstackName(testDomainName)) { - this.updateAlert('danger', `${username} is notNot valid Blockstack name`) + this.updateAlert('danger', `${username} is not a valid Blockstack name`) return } + this.setState({ + searchingUsername: username + }) } updateAlert(alertStatus, alertMessage, url = null) { @@ -109,11 +118,11 @@ class AddUsernameSearchPage extends Component { } displayPricingAndAvailabilityAlerts(availability) { - let tld = this.state.tlds[this.state.type] - const domainName = `${this.state.username}.${tld}` + const username = this.state.username - if(domainName === availability.lastNameEntered) { - if(availability.names[domainName].error) { + if (availability.lastNameEntered && + (username === availability.lastNameEntered.split('.')[0])) { + if (availability.names[domainName].error) { const error = availability.names[domainName].error console.error(error) this.updateAlert('danger', `There was a problem checking on price & availability of ${domainName}`) @@ -141,10 +150,13 @@ class AddUsernameSearchPage extends Component { } displayConnectStorageAlert() { - this.updateAlert('danger', 'Please go to the Storage app and connect a storage provider.', STORAGE_URL) + this.updateAlert('danger', 'Please go to the Storage app and connect a storage provider.', + STORAGE_URL) } render() { + const searchingUsername = this.state.searchingUsername + const availableNames = this.props.availability.names return (
    @@ -184,7 +196,33 @@ class AddUsernameSearchPage extends Component {
    - + {searchingUsername ? + this.state.nameSuffixes.map((nameSuffix) => { + const name = `${searchingUsername}.${nameSuffix}` + const nameAvailabilityObject = availableNames[name] + const searching = !nameAvailabilityObject || + nameAvailabilityObject.checkingAvailability + + const available = nameAvailabilityObject && nameAvailabilityObject.available + return ( +
    + {searching ? +

    Checking {name}...

    + : +
    + {available ? +

    {name} is available!

    + : +

    {name} is already taken.

    + } +
    + } +
    + ) + }) + : + null + }
    From e9b08700adc55186425094fb6af5ce22b32b0faf Mon Sep 17 00:00:00 2001 From: Larry Salibra Date: Tue, 8 Aug 2017 18:09:00 +0800 Subject: [PATCH 07/65] refactor availability & price lookup logic for subdomains #664 --- app/js/account/store/settings/default.js | 5 ++ app/js/profiles/AddUsernameSearchPage.js | 66 +++++-------------- app/js/profiles/store/availability/actions.js | 32 ++++++--- .../store/availability/actions/async.test.js | 40 +++++++++++ 4 files changed, 83 insertions(+), 60 deletions(-) diff --git a/app/js/account/store/settings/default.js b/app/js/account/store/settings/default.js index f00539df4..679cfd6ec 100644 --- a/app/js/account/store/settings/default.js +++ b/app/js/account/store/settings/default.js @@ -23,6 +23,11 @@ const DEFAULT_API = { pgpKeyUrl: 'https://pgp.mit.edu/pks/lookup?search={identifier}&op=vindex&fingerprint=on', btcPriceUrl: 'https://www.bitstamp.net/api/v2/ticker/btcusd/', corePingUrl: 'http://localhost:6270/v1/node/ping', + subdomains: { + 'subdomaintest.id': { + registerUrl: 'http://localhost:6270/v1/names' + } + }, hostedDataLocation: DROPBOX, coreHost: 'localhost', corePort: 6270, diff --git a/app/js/profiles/AddUsernameSearchPage.js b/app/js/profiles/AddUsernameSearchPage.js index 91d2f226d..3e39ca681 100644 --- a/app/js/profiles/AddUsernameSearchPage.js +++ b/app/js/profiles/AddUsernameSearchPage.js @@ -30,22 +30,19 @@ function mapDispatchToProps(dispatch) { class AddUsernameSearchPage extends Component { static propTypes = { api: PropTypes.object.isRequired, - availability: PropTypes.object.isRequired + availability: PropTypes.object.isRequired, + checkNameAvailabilityAndPrice: PropTypes.func.isRequired } constructor(props) { super(props) - - const availableDomains = { - id: { - registerUrl: this.props.api.registerUrl, - free: false + const availableDomains = Object.assign({}, + { + id: { + registerUrl: this.props.api.registerUrl + } }, - 'blockstack.id': { - registerUrl: this.props.api.registerUrl, - free: true - } - } + this.props.api.subdomains) const nameSuffixes = Object.keys(availableDomains) @@ -60,7 +57,6 @@ class AddUsernameSearchPage extends Component { this.onChange = this.onChange.bind(this) this.search = this.search.bind(this) this.updateAlert = this.updateAlert.bind(this) - this.displayPricingAndAvailabilityAlerts = this.displayPricingAndAvailabilityAlerts.bind(this) this.displayConnectStorageAlert = this.displayConnectStorageAlert.bind(this) } @@ -72,12 +68,9 @@ class AddUsernameSearchPage extends Component { }) const storageConnected = this.props.api.dropboxAccessToken !== null - const availability = nextProps.availability if (!storageConnected) { this.displayConnectStorageAlert() - } else { - this.displayPricingAndAvailabilityAlerts(availability) } } @@ -94,16 +87,19 @@ class AddUsernameSearchPage extends Component { const username = this.state.username logger.debug(`search: user is searching for ${username}`) event.preventDefault() - const availableDomainSuffixes = Object.keys(this.state.availableDomains) - const testDomainName = `${username}.${availableDomainSuffixes[0]}` + const nameSuffixes = this.state.nameSuffixes + const testDomainName = `${username}.${nameSuffixes[0]}` if (!isABlockstackName(testDomainName)) { - this.updateAlert('danger', `${username} is not a valid Blockstack name`) + this.updateAlert('danger', `${testDomainName} is not a valid Blockstack name`) return } - this.setState({ searchingUsername: username }) + + nameSuffixes.forEach((nameSuffix) => + this.props.checkNameAvailabilityAndPrice(this.props.api, + `${username}.${nameSuffix}`)) } updateAlert(alertStatus, alertMessage, url = null) { @@ -117,38 +113,6 @@ class AddUsernameSearchPage extends Component { }) } - displayPricingAndAvailabilityAlerts(availability) { - const username = this.state.username - - if (availability.lastNameEntered && - (username === availability.lastNameEntered.split('.')[0])) { - if (availability.names[domainName].error) { - const error = availability.names[domainName].error - console.error(error) - this.updateAlert('danger', `There was a problem checking on price & availability of ${domainName}`) - } else { - if(availability.names[domainName].checkingAvailability) - this.updateAlert('info', `Checking if ${domainName} available...`) - else if(availability.names[domainName].available) { - if(availability.names[domainName].checkingPrice) { - this.updateAlert('info', `${domainName} is available! Checking price...`) - } else { - const price = availability.names[domainName].price - if(price < this.props.coreWalletBalance) { - const roundedUpPrice = roundTo.up(price, 3) - this.updateAlert('info', `${domainName} costs ~${roundedUpPrice} btc to register.`) - } else { - const shortfall = price - this.props.coreWalletBalance - this.updateAlert('danger', `Your wallet doesn't have enough money to buy ${domainName}. Please send at least ${shortfall} more bitcoin to your wallet.`, WALLET_URL) - } - } - } else { - this.updateAlert('danger', `${domainName} has already been registered.`) - } - } - } - } - displayConnectStorageAlert() { this.updateAlert('danger', 'Please go to the Storage app and connect a storage provider.', STORAGE_URL) diff --git a/app/js/profiles/store/availability/actions.js b/app/js/profiles/store/availability/actions.js index 1b53f1683..20f7ec017 100644 --- a/app/js/profiles/store/availability/actions.js +++ b/app/js/profiles/store/availability/actions.js @@ -61,21 +61,35 @@ function namePriceError(domainName, error) { } function checkNameAvailabilityAndPrice(api, domainName) { - logger.trace(`checkNameAvailabilityAndPrice: ${domainName}`) return dispatch => { dispatch(checkingNameAvailability(domainName)) - + logger.debug(`checkNameAvailabilityAndPrice: ${domainName}`) + const nameTokens = domainName.split('.') + const isSubdomain = nameTokens.length === 3 + if (isSubdomain) { + const subdomain = `${nameTokens[1]}.${nameTokens[2]}` + logger.debug(`checkNameAvailabilityAndPrice: subdomain ${subdomain}`) + if (!api.subdomains[subdomain]) { + logger.error(`checkNameAvailabilityAndPrice: no config in for subdomain ${subdomain}`) + dispatch(nameAvailabilityError(domainName, `no config in for subdomain ${subdomain}`)) + } + } return isNameAvailable(api.nameLookupUrl, domainName).then((isAvailable) => { if (isAvailable) { dispatch(nameAvailable(domainName)) dispatch(checkingNamePrice(domainName)) - return getNamePrices(api.priceUrl, domainName).then((prices) => { - const price = prices.total_estimated_cost.btc - dispatch(namePrice(domainName, price)) - }).catch((error) => { - logger.error('checkNameAvailabilityAndPrice: getNamePrices: error', error) - dispatch(namePriceError(domainName, error)) - }) + if (isSubdomain) { + // we only currently support free subdomains + dispatch(namePrice(domainName, 0)) + } else { + return getNamePrices(api.priceUrl, domainName).then((prices) => { + const price = prices.total_estimated_cost.btc + dispatch(namePrice(domainName, price)) + }).catch((error) => { + logger.error('checkNameAvailabilityAndPrice: getNamePrices: error', error) + dispatch(namePriceError(domainName, error)) + }) + } } else { dispatch(nameUnavailable(domainName)) } diff --git a/tests/profiles/store/availability/actions/async.test.js b/tests/profiles/store/availability/actions/async.test.js index 27ba24d95..2f61dfe5d 100644 --- a/tests/profiles/store/availability/actions/async.test.js +++ b/tests/profiles/store/availability/actions/async.test.js @@ -83,6 +83,46 @@ describe('Availability Store: Async Actions', () => { }) }) + it('indicates subdomain name is available and costs 0 btc', () => { + // mock core + nock('http://localhost:6270') + .get('/v1/names/satoshi.subdomaintest.id') + .reply(404, {}, { 'Content-Type': 'application/json' }) + + + const store = mockStore({ + }) + + const mockAPI = Object.assign({}, DEFAULT_API, { + + }) + + return store.dispatch(AvailabilityActions.checkNameAvailabilityAndPrice(mockAPI, + 'satoshi.subdomaintest.id')) + .then(() => { + const expectedActions = [ + { + type: 'CHECKING_NAME_AVAILABILITY', + domainName: 'satoshi.subdomaintest.id' + }, + { + type: 'NAME_AVAILABLE', + domainName: 'satoshi.subdomaintest.id' + }, + { + type: 'CHECKING_NAME_PRICE', + domainName: 'satoshi.subdomaintest.id' + }, + { + type: 'NAME_PRICE', + domainName: 'satoshi.subdomaintest.id', + price: 0 + } + ] + assert.deepEqual(store.getActions(), expectedActions) + }) + }) + it('indicates name is unavailable', () => { // mock core nock('http://localhost:6270') From 08dda6c65aa5e6776fee280f12088cff9c3553fe Mon Sep 17 00:00:00 2001 From: Larry Salibra Date: Tue, 8 Aug 2017 20:08:52 +0800 Subject: [PATCH 08/65] display price for purchase #669 --- app/js/profiles/AddUsernameSearchPage.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/app/js/profiles/AddUsernameSearchPage.js b/app/js/profiles/AddUsernameSearchPage.js index 3e39ca681..b04add0c2 100644 --- a/app/js/profiles/AddUsernameSearchPage.js +++ b/app/js/profiles/AddUsernameSearchPage.js @@ -166,8 +166,14 @@ class AddUsernameSearchPage extends Component { const nameAvailabilityObject = availableNames[name] const searching = !nameAvailabilityObject || nameAvailabilityObject.checkingAvailability + const isSubdomain = nameSuffix.split('.').length > 1 const available = nameAvailabilityObject && nameAvailabilityObject.available + const checkingPrice = nameAvailabilityObject && nameAvailabilityObject.checkingPrice + let price = 0 + if (nameAvailabilityObject) { + price = nameAvailabilityObject.price + } return (
    {searching ? @@ -175,7 +181,20 @@ class AddUsernameSearchPage extends Component { :
    {available ? +

    {name} is available!

    + {isSubdomain ? + Get + : +
    + {checkingPrice ? + Checking price... + : + Buy for {price} + } +
    + } +
    :

    {name} is already taken.

    } From bc96b1c92b5b3eed242f3c573a9406f710a2dcad Mon Sep 17 00:00:00 2001 From: Larry Salibra Date: Tue, 8 Aug 2017 21:34:39 +0800 Subject: [PATCH 09/65] start on payment/confirmation page #669 --- .../profiles/AddUsernameConfirmationPage.js | 43 ---------- app/js/profiles/AddUsernamePaymentPage.js | 43 ---------- app/js/profiles/AddUsernameSearchPage.js | 44 +++++++---- app/js/profiles/AddUsernameSelectPage.js | 78 +++++++++++++++++++ app/js/routes.js | 6 +- 5 files changed, 109 insertions(+), 105 deletions(-) delete mode 100644 app/js/profiles/AddUsernameConfirmationPage.js delete mode 100644 app/js/profiles/AddUsernamePaymentPage.js create mode 100644 app/js/profiles/AddUsernameSelectPage.js diff --git a/app/js/profiles/AddUsernameConfirmationPage.js b/app/js/profiles/AddUsernameConfirmationPage.js deleted file mode 100644 index 788bff327..000000000 --- a/app/js/profiles/AddUsernameConfirmationPage.js +++ /dev/null @@ -1,43 +0,0 @@ -import React, { Component, PropTypes } from 'react' -import { bindActionCreators } from 'redux' -import { connect } from 'react-redux' -import { AccountActions } from '../account/store/account' -import { AvailabilityActions } from './store/availability' -import { IdentityActions } from './store/identity' -import { RegistrationActions } from './store/registration' - -import log4js from 'log4js' - -const logger = log4js.getLogger('profiles/AddUsernameSearchPage.js') - -function mapStateToProps(state) { - return { - } -} - -function mapDispatchToProps(dispatch) { - return bindActionCreators(Object.assign({}, - IdentityActions, AccountActions, RegistrationActions, AvailabilityActions), dispatch) -} - -class AddUsernameConfirmationPage extends Component { - static propTypes = { - } - - constructor(props) { - super(props) - - this.state = { - } - } - - render() { - return ( -
    - AddUsernameConfirmationPage -
    - ) - } -} - -export default connect(mapStateToProps, mapDispatchToProps)(AddUsernameConfirmationPage) diff --git a/app/js/profiles/AddUsernamePaymentPage.js b/app/js/profiles/AddUsernamePaymentPage.js deleted file mode 100644 index 942cdd5ea..000000000 --- a/app/js/profiles/AddUsernamePaymentPage.js +++ /dev/null @@ -1,43 +0,0 @@ -import React, { Component, PropTypes } from 'react' -import { bindActionCreators } from 'redux' -import { connect } from 'react-redux' -import { AccountActions } from '../account/store/account' -import { AvailabilityActions } from './store/availability' -import { IdentityActions } from './store/identity' -import { RegistrationActions } from './store/registration' - -import log4js from 'log4js' - -const logger = log4js.getLogger('profiles/AddUsernameSearchPage.js') - -function mapStateToProps(state) { - return { - } -} - -function mapDispatchToProps(dispatch) { - return bindActionCreators(Object.assign({}, - IdentityActions, AccountActions, RegistrationActions, AvailabilityActions), dispatch) -} - -class AddUsernamePaymentPage extends Component { - static propTypes = { - } - - constructor(props) { - super(props) - - this.state = { - } - } - - render() { - return ( -
    - AddUsernamePaymentPage -
    - ) - } -} - -export default connect(mapStateToProps, mapDispatchToProps)(AddUsernamePaymentPage) diff --git a/app/js/profiles/AddUsernameSearchPage.js b/app/js/profiles/AddUsernameSearchPage.js index b04add0c2..d46a7b447 100644 --- a/app/js/profiles/AddUsernameSearchPage.js +++ b/app/js/profiles/AddUsernameSearchPage.js @@ -18,7 +18,8 @@ const STORAGE_URL = '/account/storage' function mapStateToProps(state) { return { api: state.settings.api, - availability: state.profiles.availability + availability: state.profiles.availability, + walletBalance: state.account.coreWallet.balance } } @@ -31,7 +32,9 @@ class AddUsernameSearchPage extends Component { static propTypes = { api: PropTypes.object.isRequired, availability: PropTypes.object.isRequired, - checkNameAvailabilityAndPrice: PropTypes.func.isRequired + checkNameAvailabilityAndPrice: PropTypes.func.isRequired, + refreshCoreWalletBalance: PropTypes.func.isRequired, + walletBalance: PropTypes.number.isRequired } constructor(props) { @@ -60,6 +63,13 @@ class AddUsernameSearchPage extends Component { this.displayConnectStorageAlert = this.displayConnectStorageAlert.bind(this) } + componentDidMount() { + logger.trace('componentDidMount') + this.props.refreshCoreWalletBalance(this.props.api.addressBalanceUrl, + this.props.api.coreAPIPassword) + } + + componentWillReceiveProps(nextProps) { logger.trace('componentWillReceiveProps') // Clear alerts @@ -121,6 +131,7 @@ class AddUsernameSearchPage extends Component { render() { const searchingUsername = this.state.searchingUsername const availableNames = this.props.availability.names + const walletBalance = this.props.walletBalance return (
    @@ -168,12 +179,15 @@ class AddUsernameSearchPage extends Component { nameAvailabilityObject.checkingAvailability const isSubdomain = nameSuffix.split('.').length > 1 - const available = nameAvailabilityObject && nameAvailabilityObject.available - const checkingPrice = nameAvailabilityObject && nameAvailabilityObject.checkingPrice + const available = nameAvailabilityObject && + nameAvailabilityObject.available + const checkingPrice = nameAvailabilityObject && + nameAvailabilityObject.checkingPrice let price = 0 if (nameAvailabilityObject) { price = nameAvailabilityObject.price } + return (
    {searching ? @@ -182,18 +196,18 @@ class AddUsernameSearchPage extends Component {
    {available ?
    -

    {name} is available!

    - {isSubdomain ? - Get - : -
    - {checkingPrice ? - Checking price... - : - Buy for {price} +

    {name} is available!

    + {isSubdomain ? + Get + : +
    + {checkingPrice ? + Checking price... + : + Buy for {price} + } +
    } -
    - }
    :

    {name} is already taken.

    diff --git a/app/js/profiles/AddUsernameSelectPage.js b/app/js/profiles/AddUsernameSelectPage.js new file mode 100644 index 000000000..ae8e12fba --- /dev/null +++ b/app/js/profiles/AddUsernameSelectPage.js @@ -0,0 +1,78 @@ +import React, { Component, PropTypes } from 'react' +import { bindActionCreators } from 'redux' +import { connect } from 'react-redux' +import { AccountActions } from '../account/store/account' +import { AvailabilityActions } from './store/availability' +import { IdentityActions } from './store/identity' +import { RegistrationActions } from './store/registration' + +import log4js from 'log4js' + +const logger = log4js.getLogger('profiles/AddUsernameSelectPage.js') + +function mapStateToProps(state) { + return { + api: state.settings.api, + availability: state.profiles.availability, + walletBalance: state.account.coreWallet.balance + } +} + +function mapDispatchToProps(dispatch) { + return bindActionCreators(Object.assign({}, + IdentityActions, AccountActions, RegistrationActions, AvailabilityActions), dispatch) +} + +class AddUsernameSelectPage extends Component { + static propTypes = { + routeParams: PropTypes.object.isRequired, + api: PropTypes.object.isRequired, + availability: PropTypes.object.isRequired, + refreshCoreWalletBalance: PropTypes.func.isRequired, + walletBalance: PropTypes.number.isRequired + } + + constructor(props) { + super(props) + const ownerAddress = this.props.routeParams.index + const name = this.props.routeParams.name + + this.state = { + ownerAddress, + name + } + } + + componentDidMount() { + logger.trace('componentDidMount') + this.props.refreshCoreWalletBalance(this.props.api.addressBalanceUrl, + this.props.api.coreAPIPassword) + } + + render() { + const name = this.props.routeParams.name + const availableNames = this.props.availability.names + const nameAvailabilityObject = availableNames[name] + const isSubdomain = name.split('.').length === 3 + let enoughMoney = false + const price = nameAvailabilityObject.price + const walletBalance = this.props.walletBalance + + if (isSubdomain || (walletBalance > price)) { + enoughMoney = true + } + + return ( +
    + AddUsernameSelectPage: {this.state.name} + {enoughMoney ? +
    confirm
    + : +
    deposit {price}
    + } +
    + ) + } +} + +export default connect(mapStateToProps, mapDispatchToProps)(AddUsernameSelectPage) diff --git a/app/js/routes.js b/app/js/routes.js index d6d046b13..ea116a53d 100644 --- a/app/js/routes.js +++ b/app/js/routes.js @@ -6,8 +6,7 @@ import HomeScreenPage from './HomeScreenPage' import ProfilesApp from './profiles/ProfilesApp' import AddUsernameSearchPage from './profiles/AddUsernameSearchPage' -import AddUsernamePaymentPage from './profiles/AddUsernamePaymentPage' -import AddUsernameConfirmationPage from './profiles/AddUsernameConfirmationPage' +import AddUsernameSelectPage from './profiles/AddUsernameSelectPage' import AllProfilesPage from './profiles/AllProfilesPage' import ViewProfilePage from './profiles/ViewProfilePage' import EditProfilePage from './profiles/EditProfilePage' @@ -46,8 +45,7 @@ export default ( - - + From f4bbe4f34247d2de593a93514a0ffebf5662b1a5 Mon Sep 17 00:00:00 2001 From: Larry Salibra Date: Thu, 10 Aug 2017 00:17:23 +0800 Subject: [PATCH 10/65] fix create new profile key generation bug --- app/js/account/store/account/actions.js | 2 +- tests/profiles/store/identity/actions/async.test.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/js/account/store/account/actions.js b/app/js/account/store/account/actions.js index 6f8514218..9e0f8a9f5 100644 --- a/app/js/account/store/account/actions.js +++ b/app/js/account/store/account/actions.js @@ -344,7 +344,7 @@ function newBitcoinAddress() { function newIdentityAddress(newIdentityKeypair) { return { - type: types.NEW_BITCOIN_ADDRESS, + type: types.NEW_IDENTITY_ADDRESS, keypair: newIdentityKeypair } } diff --git a/tests/profiles/store/identity/actions/async.test.js b/tests/profiles/store/identity/actions/async.test.js index b4349f81f..f65cbbba1 100644 --- a/tests/profiles/store/identity/actions/async.test.js +++ b/tests/profiles/store/identity/actions/async.test.js @@ -46,7 +46,7 @@ describe('Availability Store: Async Actions', () => { "keyID": "0359e44963688f2b3e95cb14ce4026f1ea34540ce04260e23c1ed3b8ff4a08da6c", "salt": "0816f31b782a4121f4222b8d0f2a60b025b20f3f40136042d806bdccddfcf300" }, - "type": "NEW_BITCOIN_ADDRESS" + "type": "NEW_IDENTITY_ADDRESS" }, { "type": "INCREMENT_IDENTITY_ADDRESS_INDEX" From 724e551cc6405c2a8c3d37485d6bad2d6fa4db3e Mon Sep 17 00:00:00 2001 From: Larry Salibra Date: Thu, 10 Aug 2017 00:18:50 +0800 Subject: [PATCH 11/65] registration of name with adequate balance --- app/js/profiles/AddUsernameSearchPage.js | 28 ++++-- app/js/profiles/AddUsernameSelectPage.js | 113 +++++++++++++++++++++-- 2 files changed, 127 insertions(+), 14 deletions(-) diff --git a/app/js/profiles/AddUsernameSearchPage.js b/app/js/profiles/AddUsernameSearchPage.js index d46a7b447..6082af734 100644 --- a/app/js/profiles/AddUsernameSearchPage.js +++ b/app/js/profiles/AddUsernameSearchPage.js @@ -1,4 +1,5 @@ import React, { Component, PropTypes } from 'react' +import { Link } from 'react-router' import { bindActionCreators } from 'redux' import { connect } from 'react-redux' @@ -7,7 +8,7 @@ import { AccountActions } from '../account/store/account' import { AvailabilityActions } from './store/availability' import { IdentityActions } from './store/identity' import { RegistrationActions } from './store/registration' -import { hasNameBeenPreordered, isABlockstackName } from '../utils/name-utils' +import { isABlockstackName } from '../utils/name-utils' import log4js from 'log4js' @@ -19,7 +20,8 @@ function mapStateToProps(state) { return { api: state.settings.api, availability: state.profiles.availability, - walletBalance: state.account.coreWallet.balance + walletBalance: state.account.coreWallet.balance, + balanceUrl: state.settings.api.zeroConfBalanceUrl } } @@ -34,7 +36,9 @@ class AddUsernameSearchPage extends Component { availability: PropTypes.object.isRequired, checkNameAvailabilityAndPrice: PropTypes.func.isRequired, refreshCoreWalletBalance: PropTypes.func.isRequired, - walletBalance: PropTypes.number.isRequired + walletBalance: PropTypes.number.isRequired, + routeParams: PropTypes.object.isRequired, + balanceUrl: PropTypes.string.isRequired } constructor(props) { @@ -65,7 +69,7 @@ class AddUsernameSearchPage extends Component { componentDidMount() { logger.trace('componentDidMount') - this.props.refreshCoreWalletBalance(this.props.api.addressBalanceUrl, + this.props.refreshCoreWalletBalance(this.props.balanceUrl, this.props.api.coreAPIPassword) } @@ -131,7 +135,7 @@ class AddUsernameSearchPage extends Component { render() { const searchingUsername = this.state.searchingUsername const availableNames = this.props.availability.names - const walletBalance = this.props.walletBalance + const ownerAddress = this.props.routeParams.index return (
    @@ -189,7 +193,7 @@ class AddUsernameSearchPage extends Component { } return ( -
    +
    {searching ?

    Checking {name}...

    : @@ -198,13 +202,21 @@ class AddUsernameSearchPage extends Component {

    {name} is available!

    {isSubdomain ? - Get + + Get + :
    {checkingPrice ? Checking price... : - Buy for {price} + + Buy for {price} + }
    } diff --git a/app/js/profiles/AddUsernameSelectPage.js b/app/js/profiles/AddUsernameSelectPage.js index ae8e12fba..b65c749f4 100644 --- a/app/js/profiles/AddUsernameSelectPage.js +++ b/app/js/profiles/AddUsernameSelectPage.js @@ -5,6 +5,7 @@ import { AccountActions } from '../account/store/account' import { AvailabilityActions } from './store/availability' import { IdentityActions } from './store/identity' import { RegistrationActions } from './store/registration' +import { hasNameBeenPreordered } from '../utils/name-utils' import log4js from 'log4js' @@ -14,7 +15,12 @@ function mapStateToProps(state) { return { api: state.settings.api, availability: state.profiles.availability, - walletBalance: state.account.coreWallet.balance + walletBalance: state.account.coreWallet.balance, + identityKeypairs: state.account.identityAccount.keypairs, + identityAddresses: state.account.identityAccount.addresses, + registration: state.profiles.registration, + localIdentities: state.profiles.identity.localIdentities, + balanceUrl: state.settings.api.zeroConfBalanceUrl } } @@ -26,47 +32,142 @@ function mapDispatchToProps(dispatch) { class AddUsernameSelectPage extends Component { static propTypes = { routeParams: PropTypes.object.isRequired, + router: PropTypes.object.isRequired, api: PropTypes.object.isRequired, availability: PropTypes.object.isRequired, refreshCoreWalletBalance: PropTypes.func.isRequired, - walletBalance: PropTypes.number.isRequired + walletBalance: PropTypes.number.isRequired, + registerName: PropTypes.func.isRequired, + identityKeypairs: PropTypes.array.isRequired, + identityAddresses: PropTypes.array.isRequired, + registration: PropTypes.object.isRequired, + localIdentities: PropTypes.object.isRequired, + balanceUrl: PropTypes.string.isRequired } constructor(props) { super(props) const ownerAddress = this.props.routeParams.index const name = this.props.routeParams.name + const availableNames = this.props.availability.names + const nameAvailabilityObject = availableNames[name] + if (!nameAvailabilityObject) { + logger.error(`componentDidMount: not sure if ${name} is available.`) + props.router.push(`/profiles/i/add-username/${ownerAddress}/search`) + } this.state = { ownerAddress, - name + name, + registrationInProgress: false } + this.findAddressIndex = this.findAddressIndex.bind(this) + this.register = this.register.bind(this) } componentDidMount() { logger.trace('componentDidMount') - this.props.refreshCoreWalletBalance(this.props.api.addressBalanceUrl, + console.log(this.props.balanceUrl) + this.props.refreshCoreWalletBalance(this.props.balanceUrl, this.props.api.coreAPIPassword) } + componentWillReceiveProps(nextProps) { + const registration = nextProps.registration + + if (this.state.registrationInProgress && registration.registrationSubmitted) { + logger.debug('componentWillReceiveProps: registration submitted! redirecting...') + this.props.router.push('/profiles') // TODO this should go to the status page + } + } + + findAddressIndex(address) { + const identityAddresses = this.props.identityAddresses + for (let i = 0; i < identityAddresses.length; i++) { + if (identityAddresses[i] === address) { + return i + } + } + return null + } + + register(event) { + logger.trace('register') + event.preventDefault() + this.setState({ + registrationInProgress: true + }) + const ownerAddress = this.props.routeParams.index + const name = this.props.routeParams.name + const nameHasBeenPreordered = hasNameBeenPreordered(name, this.props.localIdentities) + + if (nameHasBeenPreordered) { + logger.error(`register: Name ${name} has already been preordered`) + } else { + const addressIndex = this.findAddressIndex(ownerAddress) + + if (!addressIndex) { + logger.error(`register: can't find address ${ownerAddress}`) + } + + logger.debug(`register: ${ownerAddress} index is ${addressIndex}`) + + const address = this.props.identityAddresses[addressIndex] + + if (ownerAddress !== address) { + logger.error(`register: Address ${address} at index ${addressIndex} doesn't match owner address ${ownerAddress}`) + } + + const keypair = this.props.identityKeypairs[addressIndex] + + const nameTokens = name.split('.') + const nameSuffix = name.split(nameTokens[0]) + const isSubdomain = nameTokens.length === 0 + + logger.debug(`register: ${name} has name suffix ${nameSuffix}`) + logger.debug(`register: is ${name} a subdomain? ${isSubdomain}`) + + if (isSubdomain) { + // TODO implement subdomains + } else { + this.props.registerName(this.props.api, name, address, keypair) + } + // + logger.debug(`register: ${name} preordered! Waiting for registration confirmation.`) + } + } + render() { const name = this.props.routeParams.name const availableNames = this.props.availability.names const nameAvailabilityObject = availableNames[name] const isSubdomain = name.split('.').length === 3 let enoughMoney = false - const price = nameAvailabilityObject.price + let price = 0 + if (nameAvailabilityObject) { + price = nameAvailabilityObject.price + } const walletBalance = this.props.walletBalance if (isSubdomain || (walletBalance > price)) { enoughMoney = true } + const registrationInProgress = this.state.registrationInProgress + return (
    AddUsernameSelectPage: {this.state.name} {enoughMoney ? -
    confirm
    +
    + +
    :
    deposit {price}
    } From 11a8e6cd8a4ecc468f68e6e527f905a0421802f0 Mon Sep 17 00:00:00 2001 From: Larry Salibra Date: Thu, 10 Aug 2017 18:48:10 +0800 Subject: [PATCH 12/65] add progress bar #669 --- app/js/profiles/AddUsernameSearchPage.js | 38 ++++++++++++++++++++---- app/styles/app.css | 6 ++++ 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/app/js/profiles/AddUsernameSearchPage.js b/app/js/profiles/AddUsernameSearchPage.js index 6082af734..546bc9fa7 100644 --- a/app/js/profiles/AddUsernameSearchPage.js +++ b/app/js/profiles/AddUsernameSearchPage.js @@ -3,6 +3,7 @@ import { Link } from 'react-router' import { bindActionCreators } from 'redux' import { connect } from 'react-redux' +import Modal from 'react-modal' import Alert from '../components/Alert' import { AccountActions } from '../account/store/account' import { AvailabilityActions } from './store/availability' @@ -156,7 +157,11 @@ class AddUsernameSearchPage extends Component { Add a username to save your profile so you can interact with other people on the decentralized internet.

    -
    + {checkingPrice ? - Checking price... +
    +
    + Checking price... +
    +
    : - - Buy for {price} - +
      +
    • Price: {price} bitcoins
    • +
    • Censorship resistant: Yes!
    • +
    • Arrives in ~2 hours
    • +
    + + Buy {name} + +
    }
    } diff --git a/app/styles/app.css b/app/styles/app.css index 716e79397..5aec019f4 100644 --- a/app/styles/app.css +++ b/app/styles/app.css @@ -505,6 +505,12 @@ div.pro-avatar img{max-width:58px;} .pro-card-body{display:block;margin-bottom:30px;font-size:0.9rem;} .pro-container{padding-top:1.5rem;} +/* 27. Progress bars */ +.progress{font-size: .75rem;line-height: 1rem;text-align: center;} +.progress-bar{height: 1rem;color: #fff;background-color: #2275d7} +.progress-bar-animated{-webkit-animation:progress-bar-stripes 1s linear infinite;-o-animation:progress-bar-stripes 1s linear infinite;animation:progress-bar-stripes 1s linear infinite} +.progress-bar-striped{background-image:-webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);-webkit-background-size: 1rem 1rem;background-size: 1rem 1rem} + /* 28. List group */ .list-group-item:last-child,.list-group-item:first-child{border-radius:0;} .list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{background-image:none;} From 7e9bc5d30f8e5706ee9333ea98191efb5dd678d4 Mon Sep 17 00:00:00 2001 From: Larry Salibra Date: Thu, 10 Aug 2017 19:22:18 +0800 Subject: [PATCH 13/65] purchase confirmation page copy #669 --- app/js/profiles/AddUsernameSearchPage.js | 6 +-- app/js/profiles/AddUsernameSelectPage.js | 60 +++++++++++++++++++----- 2 files changed, 50 insertions(+), 16 deletions(-) diff --git a/app/js/profiles/AddUsernameSearchPage.js b/app/js/profiles/AddUsernameSearchPage.js index 546bc9fa7..6efba7283 100644 --- a/app/js/profiles/AddUsernameSearchPage.js +++ b/app/js/profiles/AddUsernameSearchPage.js @@ -3,13 +3,13 @@ import { Link } from 'react-router' import { bindActionCreators } from 'redux' import { connect } from 'react-redux' -import Modal from 'react-modal' import Alert from '../components/Alert' import { AccountActions } from '../account/store/account' import { AvailabilityActions } from './store/availability' import { IdentityActions } from './store/identity' import { RegistrationActions } from './store/registration' import { isABlockstackName } from '../utils/name-utils' +import roundTo from 'round-to' import log4js from 'log4js' @@ -160,7 +160,7 @@ class AddUsernameSearchPage extends Component { {searching ? diff --git a/app/js/profiles/AddUsernameSelectPage.js b/app/js/profiles/AddUsernameSelectPage.js index b65c749f4..719e3b481 100644 --- a/app/js/profiles/AddUsernameSelectPage.js +++ b/app/js/profiles/AddUsernameSelectPage.js @@ -1,4 +1,5 @@ import React, { Component, PropTypes } from 'react' +import { Link } from 'react-router' import { bindActionCreators } from 'redux' import { connect } from 'react-redux' import { AccountActions } from '../account/store/account' @@ -6,6 +7,8 @@ import { AvailabilityActions } from './store/availability' import { IdentityActions } from './store/identity' import { RegistrationActions } from './store/registration' import { hasNameBeenPreordered } from '../utils/name-utils' +import roundTo from 'round-to' + import log4js from 'log4js' @@ -56,6 +59,12 @@ class AddUsernameSelectPage extends Component { props.router.push(`/profiles/i/add-username/${ownerAddress}/search`) } + const nameHasBeenPreordered = hasNameBeenPreordered(name, props.localIdentities) + if (nameHasBeenPreordered) { + logger.error(`constructor: Name ${name} has already been preordered.`) + props.router.push('/profiles') + } + this.state = { ownerAddress, name, @@ -147,6 +156,7 @@ class AddUsernameSelectPage extends Component { if (nameAvailabilityObject) { price = nameAvailabilityObject.price } + price = roundTo(price, 6) const walletBalance = this.props.walletBalance if (isSubdomain || (walletBalance > price)) { @@ -157,20 +167,44 @@ class AddUsernameSelectPage extends Component { return (
    - AddUsernameSelectPage: {this.state.name} - {enoughMoney ? -
    - +
    +
    +
    +
    + {enoughMoney ? +
    +

    Are you sure you want to buy {name}?

    +

    Purchasing {name} will spend {price} bitcoins + from your wallet.

    +
    + +
    + {registrationInProgress ? + null + : + + Cancel + + } +
    +
    + : +
    deposit {price}
    + } +
    - : -
    deposit {price}
    - }
    ) } From aee07d0d898422ee104f35d1b6e03f84f4efdf31 Mon Sep 17 00:00:00 2001 From: Larry Salibra Date: Thu, 10 Aug 2017 21:44:15 +0800 Subject: [PATCH 14/65] add payment information #669 --- app/js/account/store/account/actions.js | 2 +- app/js/profiles/AddUsernameSelectPage.js | 38 ++++++++++++++++++++++-- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/app/js/account/store/account/actions.js b/app/js/account/store/account/actions.js index 9e0f8a9f5..3fc9412f1 100644 --- a/app/js/account/store/account/actions.js +++ b/app/js/account/store/account/actions.js @@ -196,7 +196,7 @@ function refreshCoreWalletBalance(addressBalanceUrl, coreAPIPassword) { const balance = responseJson.balance.bitcoin logger.debug(`refreshCoreWalletBalance: balance is ${balance}.`) dispatch( - updateCoreWalletBalance(balance) + updateCoreWalletBalance(0) ) }) .catch((error) => { diff --git a/app/js/profiles/AddUsernameSelectPage.js b/app/js/profiles/AddUsernameSelectPage.js index 719e3b481..658d42a24 100644 --- a/app/js/profiles/AddUsernameSelectPage.js +++ b/app/js/profiles/AddUsernameSelectPage.js @@ -8,6 +8,7 @@ import { IdentityActions } from './store/identity' import { RegistrationActions } from './store/registration' import { hasNameBeenPreordered } from '../utils/name-utils' import roundTo from 'round-to' +import { QRCode } from 'react-qr-svg' import log4js from 'log4js' @@ -19,6 +20,7 @@ function mapStateToProps(state) { api: state.settings.api, availability: state.profiles.availability, walletBalance: state.account.coreWallet.balance, + walletAddress: state.account.coreWallet.address, identityKeypairs: state.account.identityAccount.keypairs, identityAddresses: state.account.identityAccount.addresses, registration: state.profiles.registration, @@ -40,6 +42,7 @@ class AddUsernameSelectPage extends Component { availability: PropTypes.object.isRequired, refreshCoreWalletBalance: PropTypes.func.isRequired, walletBalance: PropTypes.number.isRequired, + walletAddress: PropTypes.string.isRequired, registerName: PropTypes.func.isRequired, identityKeypairs: PropTypes.array.isRequired, identityAddresses: PropTypes.array.isRequired, @@ -79,6 +82,7 @@ class AddUsernameSelectPage extends Component { console.log(this.props.balanceUrl) this.props.refreshCoreWalletBalance(this.props.balanceUrl, this.props.api.coreAPIPassword) + } componentWillReceiveProps(nextProps) { @@ -156,7 +160,7 @@ class AddUsernameSelectPage extends Component { if (nameAvailabilityObject) { price = nameAvailabilityObject.price } - price = roundTo(price, 6) + price = roundTo(price, 3) const walletBalance = this.props.walletBalance if (isSubdomain || (walletBalance > price)) { @@ -201,7 +205,37 @@ class AddUsernameSelectPage extends Component {
    : -
    deposit {price}
    +
    +

    Buy {name}

    +

    Send {price} bitcoins to your wallet:
    + {this.props.walletAddress}

    +
    + +
    +
    +
    + Waiting for payment... +
    +
    + + Cancel + +
    +
    +
    }
    From 868b88f816df114eec0f89f921bb2fa688514fda Mon Sep 17 00:00:00 2001 From: Larry Salibra Date: Fri, 11 Aug 2017 20:30:20 +0800 Subject: [PATCH 15/65] fix typo #667 --- docs/manual-core-to-browser-migration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/manual-core-to-browser-migration.md b/docs/manual-core-to-browser-migration.md index b37d9e381..ddc10d813 100644 --- a/docs/manual-core-to-browser-migration.md +++ b/docs/manual-core-to-browser-migration.md @@ -23,7 +23,7 @@ associated with it. Copy this address to an easily accessible place. 3. Click on the Blockstack icon in your menu bar. 4. Click "Copy Core API Password" 5. Put this in an easily accessible place. -6. Option-Client on the Blockstack icon in your menu bar to disable. +6. Option-Click on the Blockstack icon in your menu bar to disable. ## Sanity checks From ae3c5035926dc6fc8ec7c4a5017ae290dc7b4549 Mon Sep 17 00:00:00 2001 From: Amaury Martiny Date: Sat, 12 Aug 2017 11:06:51 +0200 Subject: [PATCH 16/65] Add set default button --- app/js/profiles/components/IdentityItem.js | 14 +++++++++++--- app/styles/app.css | 2 +- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/app/js/profiles/components/IdentityItem.js b/app/js/profiles/components/IdentityItem.js index 87ed824ff..bb70cf24c 100644 --- a/app/js/profiles/components/IdentityItem.js +++ b/app/js/profiles/components/IdentityItem.js @@ -10,7 +10,8 @@ class IdentityItem extends Component { url: PropTypes.string.isRequired, pending: PropTypes.bool.isRequired, ownerAddress: PropTypes.string.isRequired, - canAddUsername: PropTypes.bool.isRequired + canAddUsername: PropTypes.bool.isRequired, + isDefault: PropTypes.bool } constructor(props) { @@ -22,11 +23,11 @@ class IdentityItem extends Component { return (
  • -
    +
    -
    +
    • @@ -46,6 +47,13 @@ class IdentityItem extends Component {

    }
  • +
  • + {this.props.isDefault ? + Default Profile + : + Set as Default Profile + } +
  • diff --git a/app/styles/app.css b/app/styles/app.css index 716e79397..84a5e6ad0 100644 --- a/app/styles/app.css +++ b/app/styles/app.css @@ -494,7 +494,7 @@ a.card:hover{text-decoration:none;} .profile-list-card-title{font-size:15px;color:#19191A;font-weight:400;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;} .profile-list-card-subtitle{font-size:13px;color:#949494;line-height:1.9rem;} .profile-list-card-avatar{display:inline-block;} -.profile-list-avatar{float:left;margin-right:15px;padding:0;} +.profile-list-avatar{padding:0;} div.card-avatar.profile-list-avatar img{width:48px;height:48px;} .profile-card-list{list-style:none;padding:0;} .card-wrapper{list-style:none;padding-left:0;} From 6f980e743ea482d12bd9b9b66a32cb0583fb37b5 Mon Sep 17 00:00:00 2001 From: Amaury Martiny Date: Sat, 12 Aug 2017 11:29:13 +0200 Subject: [PATCH 17/65] Add set default profile in redux --- app/js/profiles/store/identity/actions.js | 8 ++++++++ app/js/profiles/store/identity/reducer.js | 5 +++++ app/js/profiles/store/identity/types.js | 1 + 3 files changed, 14 insertions(+) diff --git a/app/js/profiles/store/identity/actions.js b/app/js/profiles/store/identity/actions.js index ee355cab8..aeb8caa8d 100644 --- a/app/js/profiles/store/identity/actions.js +++ b/app/js/profiles/store/identity/actions.js @@ -29,6 +29,13 @@ function updateCurrentIdentity(domainName, profile, verifications) { } } +function setDefaultIdentity(domainName) { + return { + type: types.SET_DEFAULT, + domainName + } +} + function createNewIdentity(domainName, ownerAddress) { return { type: types.CREATE_NEW, @@ -299,6 +306,7 @@ function fetchCurrentIdentity(lookupUrl, domainName) { const IdentityActions = { calculateLocalIdentities, updateCurrentIdentity, + setDefaultIdentity, createNewIdentity, createNewProfile, updateProfile, diff --git a/app/js/profiles/store/identity/reducer.js b/app/js/profiles/store/identity/reducer.js index 531e96bbc..b1aba478c 100644 --- a/app/js/profiles/store/identity/reducer.js +++ b/app/js/profiles/store/identity/reducer.js @@ -7,6 +7,7 @@ const initialState = { profile: null, verifications: null }, + default: null, localIdentities: {}, namesOwned: [], createProfileError: null @@ -22,6 +23,10 @@ function IdentityReducer(state = initialState, action) { verifications: action.verifications } }) + case types.SET_DEFAULT: + return Object.assign({}, state, { + default: action.domainName + }) case types.CREATE_NEW: return Object.assign({}, state, { localIdentities: Object.assign({}, state.localIdentities, { diff --git a/app/js/profiles/store/identity/types.js b/app/js/profiles/store/identity/types.js index 8a053f903..14804e692 100644 --- a/app/js/profiles/store/identity/types.js +++ b/app/js/profiles/store/identity/types.js @@ -1,4 +1,5 @@ export const UPDATE_CURRENT = 'UPDATE_CURRENT' +export const SET_DEFAULT = 'SET_DEFAULT' export const UPDATE_IDENTITIES = 'UPDATE_IDENTITIES' export const CREATE_NEW = 'CREATE_NEW' export const UPDATE_PROFILE = 'UPDATE_PROFILE' From 250a3cb87aa8bcbedb43a1d6ee075ada5dc7ce3a Mon Sep 17 00:00:00 2001 From: Amaury Martiny Date: Sat, 12 Aug 2017 11:29:30 +0200 Subject: [PATCH 18/65] Set default profile UI --- app/js/profiles/AllProfilesPage.js | 11 +++++++++++ app/js/profiles/components/IdentityItem.js | 12 ++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/app/js/profiles/AllProfilesPage.js b/app/js/profiles/AllProfilesPage.js index 2271c86a5..be85f4019 100644 --- a/app/js/profiles/AllProfilesPage.js +++ b/app/js/profiles/AllProfilesPage.js @@ -16,6 +16,7 @@ const logger = log4js.getLogger('profiles/AllProfilesPage.js') function mapStateToProps(state) { return { localIdentities: state.profiles.identity.localIdentities, + defaultIdentity: state.profiles.identity.default, namesOwned: state.profiles.identity.namesOwned, createProfileError: state.profiles.identity.createProfileError, identityAddresses: state.account.identityAccount.addresses, @@ -32,6 +33,7 @@ function mapDispatchToProps(dispatch) { class IdentityPage extends Component { static propTypes = { localIdentities: PropTypes.object.isRequired, + defaultIdentity: PropTypes.string.isRequired, createNewProfile: PropTypes.func.isRequired, refreshIdentities: PropTypes.func.isRequired, namesOwned: PropTypes.array.isRequired, @@ -39,6 +41,7 @@ class IdentityPage extends Component { identityAddresses: PropTypes.array.isRequired, nextUnusedAddressIndex: PropTypes.number.isRequired, encryptedBackupPhrase: PropTypes.string.isRequired, + setDefaultIdentity: PropTypes.string.isRequired, resetCreateNewProfileError: PropTypes.func.isRequired, createProfileError: PropTypes.string } @@ -56,6 +59,7 @@ class IdentityPage extends Component { console.log(props) this.onValueChange = this.onValueChange.bind(this) + this.setDefaultIdentity = this.setDefaultIdentity.bind(this) this.createNewProfile = this.createNewProfile.bind(this) this.availableIdentityAddresses = this.availableIdentityAddresses.bind(this) this.openPasswordPrompt = this.openPasswordPrompt.bind(this) @@ -101,6 +105,11 @@ class IdentityPage extends Component { }) } + setDefaultIdentity(domainName) { + console.log(domainName) + this.props.setDefaultIdentity(domainName) + } + createNewProfile(event) { logger.trace('createNewProfile') this.setState({ @@ -210,6 +219,8 @@ class IdentityPage extends Component { url={`/profiles/${identity.domainName}/local`} ownerAddress={identity.ownerAddress} canAddUsername={identity.canAddUsername} + isDefault={identity.domainName === this.props.defaultIdentity} + setDefaultIdentity={() => this.setDefaultIdentity(identity.domainName)} /> ) } else { diff --git a/app/js/profiles/components/IdentityItem.js b/app/js/profiles/components/IdentityItem.js index bb70cf24c..5303ba142 100644 --- a/app/js/profiles/components/IdentityItem.js +++ b/app/js/profiles/components/IdentityItem.js @@ -11,12 +11,20 @@ class IdentityItem extends Component { pending: PropTypes.bool.isRequired, ownerAddress: PropTypes.string.isRequired, canAddUsername: PropTypes.bool.isRequired, - isDefault: PropTypes.bool + isDefault: PropTypes.bool, + setDefaultIdentity: PropTypes.func.isRequired } constructor(props) { super(props) this.state = {} + + this.setDefaultIdentity = this.setDefaultIdentity.bind(this) + } + + setDefaultIdentity(event) { + event.preventDefault() + this.props.setDefaultIdentity() } render() { @@ -51,7 +59,7 @@ class IdentityItem extends Component { {this.props.isDefault ? Default Profile : - Set as Default Profile + Set as Default Profile } From 21933402fa4e3e59fd1d0e84a0c48dbe93de6978 Mon Sep 17 00:00:00 2001 From: Amaury Martiny Date: Sat, 12 Aug 2017 11:34:42 +0200 Subject: [PATCH 19/65] Add ui for default profile --- app/js/profiles/components/IdentityItem.js | 2 +- app/styles/app.css | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/js/profiles/components/IdentityItem.js b/app/js/profiles/components/IdentityItem.js index 5303ba142..636a56283 100644 --- a/app/js/profiles/components/IdentityItem.js +++ b/app/js/profiles/components/IdentityItem.js @@ -57,7 +57,7 @@ class IdentityItem extends Component {
  • {this.props.isDefault ? - Default Profile + Default Profile : Set as Default Profile } diff --git a/app/styles/app.css b/app/styles/app.css index 84a5e6ad0..05073d660 100644 --- a/app/styles/app.css +++ b/app/styles/app.css @@ -490,13 +490,13 @@ input[type=radio]{margin-right:1rem;} /* 21. Cards */ .profile-list-card{background-color:#fff;padding:35px;overflow:hidden;/*width:100%;*/border:1px solid #fff;-webkit-box-shadow:0px 0px 25px 0px rgba(0,0,0,0.08);-moz-box-shadow:0px 0px 25px 0px rgba(0,0,0,0.08);box-shadow:0px 0px 25px 0px rgba(0,0,0,0.08);border-radius:0;} a.card:hover{text-decoration:none;} -.profile-card-list{margin-bottom:0;} +.profile-card-list{margin-bottom:0;list-style:none;padding:0;} +.profile-card-list .default{color: #19191A;text-decoration: none;} .profile-list-card-title{font-size:15px;color:#19191A;font-weight:400;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;} .profile-list-card-subtitle{font-size:13px;color:#949494;line-height:1.9rem;} .profile-list-card-avatar{display:inline-block;} .profile-list-avatar{padding:0;} div.card-avatar.profile-list-avatar img{width:48px;height:48px;} -.profile-card-list{list-style:none;padding:0;} .card-wrapper{list-style:none;padding-left:0;} .pro-wrap{font-family:'Lato',sans-serif;max-width:1180px;color:#080809;background-color:#fff;padding:35px;font-weight:300;} .pro-actions-wrap{max-width:1180px;padding:0px;} From 3d40f69da552146f72b775d133fede8d2a1ba4bc Mon Sep 17 00:00:00 2001 From: Amaury Martiny Date: Sat, 12 Aug 2017 13:43:44 +0200 Subject: [PATCH 20/65] Add select profile in authModal --- app/js/auth/components/AuthModal.js | 151 +++++++++++++++++----------- app/styles/app.css | 1 + 2 files changed, 94 insertions(+), 58 deletions(-) diff --git a/app/js/auth/components/AuthModal.js b/app/js/auth/components/AuthModal.js index de961f137..27c456507 100644 --- a/app/js/auth/components/AuthModal.js +++ b/app/js/auth/components/AuthModal.js @@ -20,6 +20,7 @@ const logger = log4js.getLogger('auth/components/AuthModal.js') function mapStateToProps(state) { return { localIdentities: state.profiles.identity.localIdentities, + defaultIdentity: state.profiles.identity.default, identityKeypairs: state.account.identityAccount.keypairs, appManifest: state.auth.appManifest, appManifestLoading: state.auth.appManifestLoading, @@ -42,6 +43,8 @@ class AuthModal extends Component { router: PropTypes.object } static propTypes = { + defaultIdentity: PropTypes.string.isRequired, + localIdentities: PropTypes.object.isRequired, loadAppManifest: PropTypes.func.isRequired, clearSessionToken: PropTypes.func.isRequired, getCoreSessionToken: PropTypes.func.isRequired, @@ -55,6 +58,7 @@ class AuthModal extends Component { super(props) this.state = { + currentIdentity: null, authRequest: null, appManifest: null, coreSessionToken: null, @@ -79,39 +83,54 @@ class AuthModal extends Component { componentWillReceiveProps(nextProps) { const storageConnected = this.props.api.dropboxAccessToken !== null this.setState({ - storageConnected + storageConnected, + currentIdentity: this.state.currentIdentity || nextProps.defaultIdentity }) const appDomain = this.state.decodedToken.payload.domain_name const localIdentities = nextProps.localIdentities const identityKeypairs = nextProps.identityKeypairs - if (appDomain && nextProps.coreSessionTokens[appDomain]) { - logger.trace('componentWillReceiveProps: received coreSessionToken') - if (Object.keys(localIdentities).length > 0) { - let userDomainName = Object.keys(localIdentities)[0] - - let hasUsername = true - if (userDomainName === localIdentities[userDomainName].ownerAddress) { - logger.debug(`login(): this profile ${userDomainName} has no username`) - hasUsername = false - } - const blockchainId = (hasUsername ? userDomainName : null) - const identity = localIdentities[userDomainName] - const profile = identity.profile - const privateKey = identityKeypairs[0].key - const appsNodeKey = identityKeypairs[0].appsNodeKey - const salt = identityKeypairs[0].salt - const appsNode = new AppsNode(HDNode.fromBase58(appsNodeKey), salt) - const appPrivateKey = appsNode.getAppNode(appDomain).getAppPrivateKey() - - // TODO: what if the token is expired? - const authResponse = makeAuthResponse(privateKey, profile, blockchainId, - nextProps.coreSessionTokens[appDomain], appPrivateKey) - - this.props.clearSessionToken(appDomain) - redirectUserToApp(this.state.authRequest, authResponse) - } + if (!appDomain || !nextProps.coreSessionTokens[appDomain]) { + return + } + + // TODO Should put all the following this into side-effects! + + logger.trace('componentWillReceiveProps: received coreSessionToken') + if (!Object.keys(localIdentities).length) { + return + } + // Careful with this.state, as the above this.setState is async. But + // there shouldn't be any problem with the above check. + // TODO: Side-effects to avoid confusion. + const userDomainName = this.state.currentIdentity + + let hasUsername = true + if (userDomainName === localIdentities[userDomainName].ownerAddress) { + logger.debug(`login(): this profile ${userDomainName} has no username`) + hasUsername = false } + + // Get keypair corresponding to the current user identity + const profileSigningKeypair = identityKeypairs.find((keypair) => keypair.address === userDomainName) + + const blockchainId = (hasUsername ? userDomainName : null) + const identity = localIdentities[userDomainName] + const profile = identity.profile + const privateKey = profileSigningKeypair.key + const appsNodeKey = profileSigningKeypair.appsNodeKey + const salt = profileSigningKeypair.salt + const appsNode = new AppsNode(HDNode.fromBase58(appsNodeKey), salt) + const appPrivateKey = appsNode.getAppNode(appDomain).getAppPrivateKey() + + // TODO: what if the token is expired? + const authResponse = makeAuthResponse(privateKey, profile, blockchainId, + nextProps.coreSessionTokens[appDomain], appPrivateKey) + + this.props.clearSessionToken(appDomain) + + logger.trace(`login(): profile ${userDomainName} is logging in`) + redirectUserToApp(this.state.authRequest, authResponse) } closeModal() { @@ -120,35 +139,39 @@ class AuthModal extends Component { login() { this.props.loginToApp() - if (Object.keys(this.props.localIdentities).length > 0) { - const localIdentities = this.props.localIdentities - let userDomainName = Object.keys(localIdentities)[0] - let hasUsername = true - if (userDomainName === localIdentities[userDomainName].ownerAddress) { - logger.debug(`login(): this profile ${userDomainName} has no username`) - hasUsername = false - } - const identity = localIdentities[userDomainName] - const profile = identity.profile - const profileSigningKeypair = this.props.identityKeypairs[0] - const appDomain = this.state.decodedToken.payload.domain_name - const scopes = this.state.decodedToken.payload.scopes - const appsNodeKey = this.props.identityKeypairs[0].appsNodeKey - const salt = this.props.identityKeypairs[0].salt - const appsNode = new AppsNode(HDNode.fromBase58(appsNodeKey), salt) - const appPrivateKey = appsNode.getAppNode(appDomain).getAppPrivateKey() - const blockchainId = (hasUsername ? userDomainName : null) - logger.trace(`login(): Calling setCoreStorageConfig()...`) - setCoreStorageConfig(this.props.api, blockchainId, - localIdentities[userDomainName].profile, profileSigningKeypair) - .then(() => { - logger.trace('login(): Core storage successfully configured.') - logger.trace('login(): Calling getCoreSessionToken()...') - this.props.getCoreSessionToken(this.props.coreHost, - this.props.corePort, this.props.coreAPIPassword, appPrivateKey, - appDomain, this.state.authRequest, blockchainId) - }) + if (!Object.keys(this.props.localIdentities).length) { + return + } + const localIdentities = this.props.localIdentities + const userDomainName = this.state.currentIdentity + + let hasUsername = true + if (userDomainName === localIdentities[userDomainName].ownerAddress) { + logger.debug(`login(): this profile ${userDomainName} has no username`) + hasUsername = false } + + // Get keypair corresponding to the current user identity + const profileSigningKeypair = this.props.identityKeypairs.find((keypair) => keypair.address === userDomainName) + + // TODO Should put this into side-effects! + const appDomain = this.state.decodedToken.payload.domain_name + const scopes = this.state.decodedToken.payload.scopes + const appsNodeKey = profileSigningKeypair.appsNodeKey + const salt = profileSigningKeypair.salt + const appsNode = new AppsNode(HDNode.fromBase58(appsNodeKey), salt) + const appPrivateKey = appsNode.getAppNode(appDomain).getAppPrivateKey() + const blockchainId = (hasUsername ? userDomainName : null) + logger.trace(`login(): Calling setCoreStorageConfig()...`) + setCoreStorageConfig(this.props.api, blockchainId, + localIdentities[userDomainName].profile, profileSigningKeypair) + .then(() => { + logger.trace('login(): Core storage successfully configured.') + logger.trace('login(): Calling getCoreSessionToken()...') + this.props.getCoreSessionToken(this.props.coreHost, + this.props.corePort, this.props.coreAPIPassword, appPrivateKey, + appDomain, this.state.authRequest, blockchainId) + }) } render() { @@ -196,9 +219,21 @@ class AuthModal extends Component {
    { this.state.storageConnected ?
    -

    - Click below to log in. -

    +

    Choose a profile to log in with.

    +
    +
    {registrationInProgress ? - Buying... + null : - Buy + + Cancel + } - -
    - {registrationInProgress ? - null - : - - Cancel - - } +
    +
    + : +
    +

    Are you sure you want to buy {name}?

    +

    Purchasing {name} will spend {price} bitcoins + from your wallet.

    +
    + +
    + {registrationInProgress ? + null + : + + Cancel + + } +
    + }
    :

    Buy {name}

    -

    Send {price} bitcoins to your wallet:
    - {this.props.walletAddress}

    +

    Send {price} bitcoins to your wallet:
    + {this.props.walletAddress} +

    Date: Wed, 16 Aug 2017 17:34:21 +0200 Subject: [PATCH 37/65] Fix bug set default adding username --- app/js/auth/components/AuthModal.js | 4 ++-- app/js/profiles/store/identity/actions.js | 11 ++++++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/app/js/auth/components/AuthModal.js b/app/js/auth/components/AuthModal.js index 68b68d74e..f174778be 100644 --- a/app/js/auth/components/AuthModal.js +++ b/app/js/auth/components/AuthModal.js @@ -110,7 +110,7 @@ class AuthModal extends Component { } // Get keypair corresponding to the current user identity - const profileSigningKeypair = identityKeypairs.find((keypair) => keypair.address === userDomainName) + const profileSigningKeypair = identityKeypairs.find((keypair) => keypair.address === localIdentities[userDomainName].ownerAddress) const blockchainId = (hasUsername ? userDomainName : null) const identity = localIdentities[userDomainName] @@ -150,7 +150,7 @@ class AuthModal extends Component { } // Get keypair corresponding to the current user identity - const profileSigningKeypair = this.props.identityKeypairs.find((keypair) => keypair.address === userDomainName) + const profileSigningKeypair = this.props.identityKeypairs.find((keypair) => keypair.address === localIdentities[userDomainName].ownerAddress) const appDomain = this.state.decodedToken.payload.domain_name const scopes = this.state.decodedToken.payload.scopes diff --git a/app/js/profiles/store/identity/actions.js b/app/js/profiles/store/identity/actions.js index aeb8caa8d..d48c45d52 100644 --- a/app/js/profiles/store/identity/actions.js +++ b/app/js/profiles/store/identity/actions.js @@ -83,7 +83,7 @@ function addUsername(domainName, ownerAddress) { function createNewIdentityFromDomain(domainName, ownerAddress, addingUsername = false) { logger.debug(`createNewIdentityFromDomain: domainName: ${domainName} ownerAddress: ${ownerAddress}`) - return dispatch => { + return (dispatch, getState) => { if (!addingUsername) { logger.trace('createNewIdentityFromDomain: Not adding a username') dispatch(createNewIdentity(domainName, ownerAddress)) @@ -91,6 +91,15 @@ function createNewIdentityFromDomain(domainName, ownerAddress, addingUsername = } else { logger.trace('createNewIdentityFromDomain: adding username to existing profile') dispatch(addUsername(domainName, ownerAddress)) + const state = getState() + if (!state.profiles) { + return + } + // If we are adding a domainName to the default identity, then + // we need to update our default field to the new domainName + if (state.profiles.identity.default === ownerAddress) { + dispatch(setDefaultIdentity(domainName)) + } } } } From b6e4505df277ca845b62b017998fd52510debf83 Mon Sep 17 00:00:00 2001 From: Larry Salibra Date: Thu, 17 Aug 2017 16:49:44 +0800 Subject: [PATCH 38/65] use proper subdomain registrar endpoint #664 --- app/js/account/store/settings/default.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/js/account/store/settings/default.js b/app/js/account/store/settings/default.js index c29828a63..78266c50b 100644 --- a/app/js/account/store/settings/default.js +++ b/app/js/account/store/settings/default.js @@ -25,7 +25,7 @@ const DEFAULT_API = { corePingUrl: 'http://localhost:6270/v1/node/ping', subdomains: { 'foo.id': { - registerUrl: 'http://localhost:7103/v1/names' + registerUrl: 'http://localhost:7103/register' } }, hostedDataLocation: DROPBOX, From 94ad4fce52031c402e1293b70240ff522fabf0f1 Mon Sep 17 00:00:00 2001 From: Larry Salibra Date: Thu, 17 Aug 2017 16:50:15 +0800 Subject: [PATCH 39/65] refactor for subdomain suppor #664 --- app/js/profiles/store/registration/actions.js | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/app/js/profiles/store/registration/actions.js b/app/js/profiles/store/registration/actions.js index adc94ae08..366cb69b9 100644 --- a/app/js/profiles/store/registration/actions.js +++ b/app/js/profiles/store/registration/actions.js @@ -121,16 +121,27 @@ function registerName(api, domainName, ownerAddress, keypair) { logger.debug(`registerName: ${domainName} is not a subdomain`) } - const registrationRequestBody = JSON.stringify({ - name: domainName, - owner_address: ownerAddress, - zonefile: zoneFile, - min_confs: 0, - unsafe: true - }) + let registrationRequestBody = null + + if (nameIsSubdomain) { + registrationRequestBody = JSON.stringify({ + name: domainName.split('.')[0], + owner_address: ownerAddress, + zonefile: zoneFile + }) + } else { + registrationRequestBody = JSON.stringify({ + name: domainName, + owner_address: ownerAddress, + zonefile: zoneFile, + min_confs: 0, + unsafe: true + }) + } dispatch(registrationSubmitting()) - logger.trace(`Submitting registration for ${domainName} to Core node at ${registerUrl}`) + + logger.trace(`Submitting registration for ${domainName} to ${registerUrl}`) const setOwnerKeyUrl = `http://${api.coreHost}:${api.corePort}/v1/wallet/keys/owner` From fe53f920151f21dbfade1521fe65bec75a242d61 Mon Sep 17 00:00:00 2001 From: Larry Salibra Date: Thu, 17 Aug 2017 16:50:31 +0800 Subject: [PATCH 40/65] move name registration into a modal #669 --- app/js/profiles/AddUsernameModal.js | 50 +++++ app/js/profiles/AddUsernameSearchPage.js | 250 ++++++++++++----------- app/js/profiles/AddUsernameSelectPage.js | 173 ++++++++-------- app/js/routes.js | 7 +- app/styles/app.css | 8 + 5 files changed, 277 insertions(+), 211 deletions(-) create mode 100644 app/js/profiles/AddUsernameModal.js diff --git a/app/js/profiles/AddUsernameModal.js b/app/js/profiles/AddUsernameModal.js new file mode 100644 index 000000000..012037c89 --- /dev/null +++ b/app/js/profiles/AddUsernameModal.js @@ -0,0 +1,50 @@ +import React, { Component, PropTypes } from 'react' +import Modal from 'react-modal' + +const customContentStyles = { + marginTop: 0, + width: '615px' +} + +const customOverlayStyles = { + zIndex: 10 +} + +const customStyles = { + content: customContentStyles, + overlay: customOverlayStyles +} + +class AddUsernameModal extends Component { + static propTypes = { + children: PropTypes.object.isRequired, + router: PropTypes.object.isRequired + } + + constructor(props) { + super(props) + this.closeModal = this.closeModal.bind(this) + } + + closeModal() { + this.props.router.push('/profiles') + } + + render() { + return ( + + {this.props.children} + + ) + } +} + +export default AddUsernameModal diff --git a/app/js/profiles/AddUsernameSearchPage.js b/app/js/profiles/AddUsernameSearchPage.js index 5a20313a0..ab52e0e95 100644 --- a/app/js/profiles/AddUsernameSearchPage.js +++ b/app/js/profiles/AddUsernameSearchPage.js @@ -17,6 +17,22 @@ const logger = log4js.getLogger('profiles/AddUsernameSearchPage.js') const STORAGE_URL = '/account/storage' +const listWrapperStyle = { + +} + +const listStyle = { + textAlign: 'left', + paddingLeft: '12em' +} + +const nameResultStyle = { + marginBottom: '3em' +} + +const availabilityHeaderStyle = { + marginBottom: '0.5em' +} function mapStateToProps(state) { return { api: state.settings.api, @@ -139,135 +155,135 @@ class AddUsernameSearchPage extends Component { const ownerAddress = this.props.routeParams.index return (
    -
    -
    -
    -
    -

    Search for your username

    - { - this.state.alerts.map((alert, index) => { - return ( - - ) - }) - } -

    - Add a username to save your profile so you can interact with other - people on the decentralized internet. -

    - Search for your username +
    + { + this.state.alerts.map((alert, index) => { + return ( + + ) + }) + } +

    + Add a username to save your profile so you can interact with other + people on the decentralized internet. +

    + + + - -
    - {searchingUsername ? - this.state.nameSuffixes.map((nameSuffix) => { - const name = `${searchingUsername}.${nameSuffix}` - const nameAvailabilityObject = availableNames[name] - const searching = !nameAvailabilityObject || - nameAvailabilityObject.checkingAvailability - const isSubdomain = nameSuffix.split('.').length > 1 + Search + + +
    + {searchingUsername ? + this.state.nameSuffixes.map((nameSuffix) => { + const name = `${searchingUsername}.${nameSuffix}` + const nameAvailabilityObject = availableNames[name] + const searching = !nameAvailabilityObject || + nameAvailabilityObject.checkingAvailability + const isSubdomain = nameSuffix.split('.').length > 1 - const available = nameAvailabilityObject && - nameAvailabilityObject.available - const checkingPrice = nameAvailabilityObject && - nameAvailabilityObject.checkingPrice - let price = 0 - if (nameAvailabilityObject) { - price = nameAvailabilityObject.price - } - price = roundTo.up(price, 6) - return ( -
    - {searching ? -

    Checking {name}...

    - : -
    - {available ? -
    -

    {name} is available!

    - {isSubdomain ? -
    -
      -
    • Price: Free!
    • -
    • Censorship resistant: Partial!
    • -
    • Arrives in ~30 minutes
    • + const available = nameAvailabilityObject && + nameAvailabilityObject.available + const checkingPrice = nameAvailabilityObject && + nameAvailabilityObject.checkingPrice + let price = 0 + if (nameAvailabilityObject) { + price = nameAvailabilityObject.price + } + price = roundTo.up(price, 6) + return ( +
      + {searching ? +

      Checking {name}...

      + : +
      + {available ? +
      +

      {name} is available!

      + {isSubdomain ? +
      +
        +
      • Price: Free!
      • +
      • Censorship resistant: Partial!
      • +
      • Arrives in ~30 minutes
      • +
      + + Get {name} + +
      + : +
      + {checkingPrice ? +
      +
      + Checking price... +
      +
      + : +
      +
        +
      • Price: {price} bitcoins
      • +
      • Censorship resistant: Yes!
      • +
      • Arrives in ~2 hours
      - Get {name} + Buy {name}
      - : -
      - {checkingPrice ? -
      -
      - Checking price... -
      -
      - : -
      -
        -
      • Price: {price} bitcoins
      • -
      • Censorship resistant: Yes!
      • -
      • Arrives in ~2 hours
      • -
      - - Buy {name} - -
      - } -
      } -
      - : -

      {name} is already taken.

      - } -
      - } +
      + } +
      + : +

      {name} is already taken.

      + }
    - ) - }) - : - null - } -
    + } +
    + ) + }) + : + null + }
    + + Cancel +
    ) diff --git a/app/js/profiles/AddUsernameSelectPage.js b/app/js/profiles/AddUsernameSelectPage.js index 84d48a165..d56236799 100644 --- a/app/js/profiles/AddUsernameSelectPage.js +++ b/app/js/profiles/AddUsernameSelectPage.js @@ -223,106 +223,95 @@ class AddUsernameSelectPage extends Component { return (
    -
    -
    + {enoughMoney ? +
    + {nameIsSubdomain ? +
    +

    Are you sure you want to register {name}?

    +
    + +
    + {registrationInProgress ? + null + : + + Cancel + + } +
    +
    + : +
    +

    Are you sure you want to buy {name}?

    +

    Purchasing {name} will spend {price} bitcoins + from your wallet.

    +
    + +
    + {registrationInProgress ? + null + : + + Cancel + + } +
    +
    + }
    -
    - {enoughMoney ? + : +
    +

    Buy {name}

    +

    Send {price} bitcoins to your wallet:
    + {this.props.walletAddress} +

    +
    +
    - {nameIsSubdomain ? -
    -

    Are you sure you want to register {name}?

    +
    - -
    - {registrationInProgress ? - null - : - - Cancel - - } -
    -
    - : -
    -

    Are you sure you want to buy {name}?

    -

    Purchasing {name} will spend {price} bitcoins - from your wallet.

    -
    - -
    - {registrationInProgress ? - null - : - - Cancel - - } -
    -
    - } -
    - : -
    -

    Buy {name}

    -

    Send {price} bitcoins to your wallet:
    - {this.props.walletAddress} -

    -
    - -
    -
    -
    - Waiting for payment... -
    -
    - - Cancel - + Waiting for payment...
    + + Cancel +
    - } +
    -
    + }
    ) } diff --git a/app/js/routes.js b/app/js/routes.js index ea116a53d..48fbe3517 100644 --- a/app/js/routes.js +++ b/app/js/routes.js @@ -5,6 +5,7 @@ import App from './App' import HomeScreenPage from './HomeScreenPage' import ProfilesApp from './profiles/ProfilesApp' +import AddUsernameModal from './profiles/AddUsernameModal' import AddUsernameSearchPage from './profiles/AddUsernameSearchPage' import AddUsernameSelectPage from './profiles/AddUsernameSelectPage' import AllProfilesPage from './profiles/AllProfilesPage' @@ -44,8 +45,10 @@ export default ( - - + + + + diff --git a/app/styles/app.css b/app/styles/app.css index 5aec019f4..d7b9a3970 100644 --- a/app/styles/app.css +++ b/app/styles/app.css @@ -608,6 +608,8 @@ div.ReactModal__Content.ReactModal__Content--after-open { -webkit-border-radius: 8px; -moz-border-radius: 8px; border-radius: 8px; + transition:all 300ms ease-in-out; + } .modal-heading { font-size: 1.5rem; @@ -663,6 +665,12 @@ form.modal-form > div.form-group { /* Auth Modal */ .auth-modal {text-align: center} +/* Add Username Modal */ +.add-username-modal div div.ReactModal__Content.ReactModal__Content--after-open { + width: 615px; + margin-top: 0; +} + /* Account App */ .item-sidebar-account{font-family:'Source Code Pro',monospace;font-size:0.75rem;line-height:1.5rem;border:none;background-color:transparent;padding-top:0.5625rem;padding-bottom:0.5625rem;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;-ms-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out;} .list-group-item.item-sidebar-account:hover{background-color:rgba(0,0,0,0.1);} From a272495b2197c195f865a800ba1e413dea7f4e49 Mon Sep 17 00:00:00 2001 From: Larry Salibra Date: Thu, 17 Aug 2017 18:53:33 +0800 Subject: [PATCH 41/65] payment page formatting improvements #669 --- app/js/profiles/AddUsernameSelectPage.js | 26 ++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/app/js/profiles/AddUsernameSelectPage.js b/app/js/profiles/AddUsernameSelectPage.js index d56236799..ffe1a1c86 100644 --- a/app/js/profiles/AddUsernameSelectPage.js +++ b/app/js/profiles/AddUsernameSelectPage.js @@ -42,9 +42,10 @@ class AddUsernameSelectPage extends Component { router: PropTypes.object.isRequired, api: PropTypes.object.isRequired, availability: PropTypes.object.isRequired, + getCoreWalletAddress: PropTypes.func.isRequired, refreshCoreWalletBalance: PropTypes.func.isRequired, walletBalance: PropTypes.number.isRequired, - walletAddress: PropTypes.string.isRequired, + walletAddress: PropTypes.string, registerName: PropTypes.func.isRequired, identityKeypairs: PropTypes.array.isRequired, identityAddresses: PropTypes.array.isRequired, @@ -105,6 +106,8 @@ class AddUsernameSelectPage extends Component { componentDidMount() { logger.trace('componentDidMount') + this.props.getCoreWalletAddress(this.props.api.walletPaymentAddressUrl, + this.props.api.coreAPIPassword) this.props.refreshCoreWalletBalance(this.props.balanceUrl, this.props.api.coreAPIPassword) } @@ -219,6 +222,8 @@ class AddUsernameSelectPage extends Component { enoughMoney = true } + const walletAddress = this.props.walletAddress + const registrationInProgress = this.state.registrationInProgress return ( @@ -228,6 +233,7 @@ class AddUsernameSelectPage extends Component { {nameIsSubdomain ?

    Are you sure you want to register {name}?

    +

    {name} is a subdomain that is free to register.

    - + + +
    +
    + :
    - {searchingUsername ? - this.state.nameSuffixes.map((nameSuffix) => { - const name = `${searchingUsername}.${nameSuffix}` - const nameAvailabilityObject = availableNames[name] - const searching = !nameAvailabilityObject || - nameAvailabilityObject.checkingAvailability - const isSubdomain = nameSuffix.split('.').length > 1 +

    Available names

    +
    + {searchingUsername ? + this.state.nameSuffixes.map((nameSuffix) => { + const name = `${searchingUsername}.${nameSuffix}` + const nameAvailabilityObject = availableNames[name] + const searching = !nameAvailabilityObject || + nameAvailabilityObject.checkingAvailability + const isSubdomain = nameSuffix.split('.').length > 1 - const available = nameAvailabilityObject && - nameAvailabilityObject.available - const checkingPrice = nameAvailabilityObject && - nameAvailabilityObject.checkingPrice - let price = 0 - if (nameAvailabilityObject) { - price = nameAvailabilityObject.price - } - price = roundTo.up(price, 6) - return ( -
    - {searching ? -

    Checking {name}...

    - : -
    - {available ? -
    -

    {name} is available!

    - {isSubdomain ? -
    -
      -
    • Price: Free!
    • -
    • Censorship resistant: Partial!
    • -
    • Arrives in ~30 minutes
    • -
    - - Get {name} - -
    - : -
    - {checkingPrice ? -
    -
    - Checking price... -
    -
    - : + const available = nameAvailabilityObject && + nameAvailabilityObject.available + const checkingPrice = nameAvailabilityObject && + nameAvailabilityObject.checkingPrice + let price = 0 + if (nameAvailabilityObject) { + price = nameAvailabilityObject.price + } + price = roundTo.up(price, 6) + return ( +
    + {searching ? +

    Checking {name}...

    + : +
    + {available ? +
    +

    {name}

    + {isSubdomain ?
      -
    • Price: {price} bitcoins
    • -
    • Censorship resistant: Yes!
    • -
    • Arrives in ~2 hours
    • +
    • Price: Free!
    • +
    • Censorship resistant: Partial!
    • +
    • Arrives in ~30 minutes
    - Buy {name} + Get {name} for free
    + : +
    + {checkingPrice ? +
    +
    + Checking price... +
    +
    + : +
    +
      +
    • Price: {price} bitcoins
    • +
    • Censorship resistant: Yes!
    • +
    • Arrives in ~2 hours
    • +
    + + Buy {name} for {price} bitcoins + +
    + } +
    } -
    - } -
    - : -

    {name} is already taken.

    - } +
    + : +

    {name} is already taken.

    + } +
    + }
    - } -
    - ) - }) - : - null - } + ) + }) + : + null + } +
    - - Cancel - -
    + } + + Cancel +
    ) } From b559b91df60141a689a7b7f69f71fbe488d84eda Mon Sep 17 00:00:00 2001 From: Larry Salibra Date: Fri, 18 Aug 2017 18:01:58 +0700 Subject: [PATCH 43/65] simplify results view #689 --- app/js/profiles/AddUsernameSearchPage.js | 78 +++++++++++++----------- 1 file changed, 42 insertions(+), 36 deletions(-) diff --git a/app/js/profiles/AddUsernameSearchPage.js b/app/js/profiles/AddUsernameSearchPage.js index 5a8fc3319..d4e5060f4 100644 --- a/app/js/profiles/AddUsernameSearchPage.js +++ b/app/js/profiles/AddUsernameSearchPage.js @@ -17,20 +17,12 @@ const logger = log4js.getLogger('profiles/AddUsernameSearchPage.js') const STORAGE_URL = '/account/storage' -const listWrapperStyle = { - -} - -const listStyle = { - textAlign: 'left', - paddingLeft: '12em' -} - const nameResultStyle = { marginBottom: '3em' } const availabilityHeaderStyle = { + marginTop: '1em', marginBottom: '0.5em' } function mapStateToProps(state) { @@ -83,6 +75,7 @@ class AddUsernameSearchPage extends Component { this.search = this.search.bind(this) this.updateAlert = this.updateAlert.bind(this) this.displayConnectStorageAlert = this.displayConnectStorageAlert.bind(this) + this.showSearchBox = this.showSearchBox.bind(this) } componentDidMount() { @@ -134,6 +127,16 @@ class AddUsernameSearchPage extends Component { `${username}.${nameSuffix}`)) } + showSearchBox(event) { + logger.trace('showSearchBox') + event.preventDefault() + this.setState({ + username: '', + searchingUsername: '', + showSearchResults: false + }) + } + updateAlert(alertStatus, alertMessage, url = null) { logger.trace(`updateAlert: alertStatus: ${alertStatus}, alertMessage ${alertMessage}`) this.setState({ @@ -200,6 +203,14 @@ class AddUsernameSearchPage extends Component {
    :
    + + < Back + +

    Available names

    {searchingUsername ? @@ -229,19 +240,12 @@ class AddUsernameSearchPage extends Component {

    {name}

    {isSubdomain ? -
    -
      -
    • Price: Free!
    • -
    • Censorship resistant: Partial!
    • -
    • Arrives in ~30 minutes
    • -
    - - Get {name} for free - -
    + + Get {name} for free + :
    {checkingPrice ? @@ -258,25 +262,27 @@ class AddUsernameSearchPage extends Component {
    : -
    -
      -
    • Price: {price} bitcoins
    • -
    • Censorship resistant: Yes!
    • -
    • Arrives in ~2 hours
    • -
    - - Buy {name} for {price} bitcoins - -
    + + Buy {name} for {price} bitcoins + }
    }
    : -

    {name} is already taken.

    +
    +

    {name}

    + +
    + }
    } From 3d4bd7b09e67f8bce52092b3e458854c22508033 Mon Sep 17 00:00:00 2001 From: Larry Salibra Date: Fri, 18 Aug 2017 18:14:55 +0700 Subject: [PATCH 44/65] separate search box into separate component #689 --- app/js/profiles/AddUsernameSearchBox.js | 56 ++++++++++++++++++++++++ app/js/profiles/AddUsernameSearchPage.js | 50 ++++----------------- 2 files changed, 65 insertions(+), 41 deletions(-) create mode 100644 app/js/profiles/AddUsernameSearchBox.js diff --git a/app/js/profiles/AddUsernameSearchBox.js b/app/js/profiles/AddUsernameSearchBox.js new file mode 100644 index 000000000..e05ca0b2a --- /dev/null +++ b/app/js/profiles/AddUsernameSearchBox.js @@ -0,0 +1,56 @@ +import React, { PropTypes } from 'react' +import Alert from '../components/Alert' + +const AddUsernameSearchBox = (props) => + ( +
    +

    Search for your username

    +
    + { + props.alerts.map((alert, index) => + ( + + ) + ) + } +

    + Add a username to save your profile so you can interact with other + people on the decentralized internet. +

    +
    + + +
    +
    +
    + ) + +AddUsernameSearchBox.propTypes = { + alerts: PropTypes.array.isRequired, + search: PropTypes.func.isRequired, + username: PropTypes.string, + onChange: PropTypes.func.isRequired, + disabled: PropTypes.bool.isRequired +} + +export default AddUsernameSearchBox diff --git a/app/js/profiles/AddUsernameSearchPage.js b/app/js/profiles/AddUsernameSearchPage.js index d4e5060f4..a58c5f328 100644 --- a/app/js/profiles/AddUsernameSearchPage.js +++ b/app/js/profiles/AddUsernameSearchPage.js @@ -3,7 +3,8 @@ import { Link } from 'react-router' import { bindActionCreators } from 'redux' import { connect } from 'react-redux' -import Alert from '../components/Alert' +import AddUsernameSearchBox from './AddUsernameSearchBox' + import { AccountActions } from '../account/store/account' import { AvailabilityActions } from './store/availability' import { IdentityActions } from './store/identity' @@ -161,46 +162,13 @@ class AddUsernameSearchPage extends Component { return (
    {!showSearchResults ? -
    -

    Search for your username

    -
    - { - this.state.alerts.map((alert, index) => { - return ( - - ) - }) - } -

    - Add a username to save your profile so you can interact with other - people on the decentralized internet. -

    -
    - - -
    -
    -
    + :
    Date: Fri, 18 Aug 2017 18:37:16 +0700 Subject: [PATCH 45/65] separate search results into component & organize #689 --- app/js/profiles/AddUsernameSearchPage.js | 277 ------------------ ...ddUsernameModal.js => RegistrationPage.js} | 4 +- .../registration/RegistrationSearchBox.js} | 8 +- .../registration/RegistrationSearchResults.js | 119 ++++++++ .../registration/RegistrationSearchView.js | 184 ++++++++++++ .../registration/RegistrationSelectView.js} | 14 +- app/js/routes.js | 12 +- 7 files changed, 323 insertions(+), 295 deletions(-) delete mode 100644 app/js/profiles/AddUsernameSearchPage.js rename app/js/profiles/{AddUsernameModal.js => RegistrationPage.js} (92%) rename app/js/profiles/{AddUsernameSearchBox.js => components/registration/RegistrationSearchBox.js} (88%) create mode 100644 app/js/profiles/components/registration/RegistrationSearchResults.js create mode 100644 app/js/profiles/components/registration/RegistrationSearchView.js rename app/js/profiles/{AddUsernameSelectPage.js => components/registration/RegistrationSelectView.js} (95%) diff --git a/app/js/profiles/AddUsernameSearchPage.js b/app/js/profiles/AddUsernameSearchPage.js deleted file mode 100644 index a58c5f328..000000000 --- a/app/js/profiles/AddUsernameSearchPage.js +++ /dev/null @@ -1,277 +0,0 @@ -import React, { Component, PropTypes } from 'react' -import { Link } from 'react-router' -import { bindActionCreators } from 'redux' -import { connect } from 'react-redux' - -import AddUsernameSearchBox from './AddUsernameSearchBox' - -import { AccountActions } from '../account/store/account' -import { AvailabilityActions } from './store/availability' -import { IdentityActions } from './store/identity' -import { RegistrationActions } from './store/registration' -import { isABlockstackName } from '../utils/name-utils' -import roundTo from 'round-to' - -import log4js from 'log4js' - -const logger = log4js.getLogger('profiles/AddUsernameSearchPage.js') - -const STORAGE_URL = '/account/storage' - -const nameResultStyle = { - marginBottom: '3em' -} - -const availabilityHeaderStyle = { - marginTop: '1em', - marginBottom: '0.5em' -} -function mapStateToProps(state) { - return { - api: state.settings.api, - availability: state.profiles.availability, - walletBalance: state.account.coreWallet.balance, - balanceUrl: state.settings.api.zeroConfBalanceUrl - } -} - -function mapDispatchToProps(dispatch) { - return bindActionCreators(Object.assign({}, - IdentityActions, AccountActions, RegistrationActions, AvailabilityActions), dispatch) -} - -class AddUsernameSearchPage extends Component { - static propTypes = { - api: PropTypes.object.isRequired, - availability: PropTypes.object.isRequired, - checkNameAvailabilityAndPrice: PropTypes.func.isRequired, - refreshCoreWalletBalance: PropTypes.func.isRequired, - walletBalance: PropTypes.number.isRequired, - routeParams: PropTypes.object.isRequired, - balanceUrl: PropTypes.string.isRequired - } - - constructor(props) { - super(props) - const availableDomains = Object.assign({}, - { - id: { - registerUrl: this.props.api.registerUrl - } - }, - this.props.api.subdomains) - - const nameSuffixes = Object.keys(availableDomains) - - this.state = { - alerts: [], - username: '', - searchingUsername: '', - storageConnected: this.props.api.dropboxAccessToken !== null, - availableDomains, - nameSuffixes, - showSearchResults: false - } - this.onChange = this.onChange.bind(this) - this.search = this.search.bind(this) - this.updateAlert = this.updateAlert.bind(this) - this.displayConnectStorageAlert = this.displayConnectStorageAlert.bind(this) - this.showSearchBox = this.showSearchBox.bind(this) - } - - componentDidMount() { - logger.trace('componentDidMount') - this.props.refreshCoreWalletBalance(this.props.balanceUrl, - this.props.api.coreAPIPassword) - } - - - componentWillReceiveProps(nextProps) { - logger.trace('componentWillReceiveProps') - // Clear alerts - this.setState({ - alerts: [] - }) - - const storageConnected = this.props.api.dropboxAccessToken !== null - - if (!storageConnected) { - this.displayConnectStorageAlert() - } - } - - onChange(event) { - const username = event.target.value.toLowerCase().replace(/\W+/g, '') - this.setState({ - username - }) - } - - search(event) { - logger.trace('search') - const username = this.state.username - logger.debug(`search: user is searching for ${username}`) - event.preventDefault() - const nameSuffixes = this.state.nameSuffixes - const testDomainName = `${username}.${nameSuffixes[0]}` - if (!isABlockstackName(testDomainName)) { - this.updateAlert('danger', `${testDomainName} is not a valid Blockstack name`) - return - } - this.setState({ - searchingUsername: username, - showSearchResults: true - }) - - nameSuffixes.forEach((nameSuffix) => - this.props.checkNameAvailabilityAndPrice(this.props.api, - `${username}.${nameSuffix}`)) - } - - showSearchBox(event) { - logger.trace('showSearchBox') - event.preventDefault() - this.setState({ - username: '', - searchingUsername: '', - showSearchResults: false - }) - } - - updateAlert(alertStatus, alertMessage, url = null) { - logger.trace(`updateAlert: alertStatus: ${alertStatus}, alertMessage ${alertMessage}`) - this.setState({ - alerts: [{ - status: alertStatus, - message: alertMessage, - url - }] - }) - } - - displayConnectStorageAlert() { - this.updateAlert('danger', 'Please go to the Storage app and connect a storage provider.', - STORAGE_URL) - } - - render() { - const searchingUsername = this.state.searchingUsername - const availableNames = this.props.availability.names - const ownerAddress = this.props.routeParams.index - const showSearchResults = this.state.showSearchResults - return ( -
    - {!showSearchResults ? - - : -
    - - < Back - -
    -

    Available names

    -
    - {searchingUsername ? - this.state.nameSuffixes.map((nameSuffix) => { - const name = `${searchingUsername}.${nameSuffix}` - const nameAvailabilityObject = availableNames[name] - const searching = !nameAvailabilityObject || - nameAvailabilityObject.checkingAvailability - const isSubdomain = nameSuffix.split('.').length > 1 - - const available = nameAvailabilityObject && - nameAvailabilityObject.available - const checkingPrice = nameAvailabilityObject && - nameAvailabilityObject.checkingPrice - let price = 0 - if (nameAvailabilityObject) { - price = nameAvailabilityObject.price - } - price = roundTo.up(price, 6) - return ( -
    - {searching ? -

    Checking {name}...

    - : -
    - {available ? -
    -

    {name}

    - {isSubdomain ? - - Get {name} for free - - : -
    - {checkingPrice ? -
    -
    - Checking price... -
    -
    - : - - Buy {name} for {price} bitcoins - - } -
    - } -
    - : -
    -

    {name}

    - -
    - - } -
    - } -
    - ) - }) - : - null - } -
    -
    - } - - Cancel - -
    - ) - } -} - -export default connect(mapStateToProps, mapDispatchToProps)(AddUsernameSearchPage) diff --git a/app/js/profiles/AddUsernameModal.js b/app/js/profiles/RegistrationPage.js similarity index 92% rename from app/js/profiles/AddUsernameModal.js rename to app/js/profiles/RegistrationPage.js index 012037c89..43977aed3 100644 --- a/app/js/profiles/AddUsernameModal.js +++ b/app/js/profiles/RegistrationPage.js @@ -15,7 +15,7 @@ const customStyles = { overlay: customOverlayStyles } -class AddUsernameModal extends Component { +class RegistrationPage extends Component { static propTypes = { children: PropTypes.object.isRequired, router: PropTypes.object.isRequired @@ -47,4 +47,4 @@ class AddUsernameModal extends Component { } } -export default AddUsernameModal +export default RegistrationPage diff --git a/app/js/profiles/AddUsernameSearchBox.js b/app/js/profiles/components/registration/RegistrationSearchBox.js similarity index 88% rename from app/js/profiles/AddUsernameSearchBox.js rename to app/js/profiles/components/registration/RegistrationSearchBox.js index e05ca0b2a..adb5a0597 100644 --- a/app/js/profiles/AddUsernameSearchBox.js +++ b/app/js/profiles/components/registration/RegistrationSearchBox.js @@ -1,7 +1,7 @@ import React, { PropTypes } from 'react' -import Alert from '../components/Alert' +import Alert from '../../../components/Alert' -const AddUsernameSearchBox = (props) => +const RegistrationSearchBox = (props) => (

    Search for your username

    @@ -45,7 +45,7 @@ const AddUsernameSearchBox = (props) =>
    ) -AddUsernameSearchBox.propTypes = { +RegistrationSearchBox.propTypes = { alerts: PropTypes.array.isRequired, search: PropTypes.func.isRequired, username: PropTypes.string, @@ -53,4 +53,4 @@ AddUsernameSearchBox.propTypes = { disabled: PropTypes.bool.isRequired } -export default AddUsernameSearchBox +export default RegistrationSearchBox diff --git a/app/js/profiles/components/registration/RegistrationSearchResults.js b/app/js/profiles/components/registration/RegistrationSearchResults.js new file mode 100644 index 000000000..c2ad4c2c9 --- /dev/null +++ b/app/js/profiles/components/registration/RegistrationSearchResults.js @@ -0,0 +1,119 @@ +import React, { PropTypes } from 'react' +import { Link } from 'react-router' +import roundTo from 'round-to' + + +const nameResultStyle = { + marginBottom: '3em' +} + +const availabilityHeaderStyle = { + marginTop: '1em', + marginBottom: '0.5em' +} + +const RegistrationSearchResults = (props) => + ( +
    + + < Back + +
    +

    Available names

    +
    + {props.searchingUsername ? + props.nameSuffixes.map((nameSuffix) => { + const name = `${props.searchingUsername}.${nameSuffix}` + const nameAvailabilityObject = props.availableNames[name] + const searching = !nameAvailabilityObject || + nameAvailabilityObject.checkingAvailability + const isSubdomain = nameSuffix.split('.').length > 1 + + const available = nameAvailabilityObject && + nameAvailabilityObject.available + const checkingPrice = nameAvailabilityObject && + nameAvailabilityObject.checkingPrice + let price = 0 + if (nameAvailabilityObject) { + price = nameAvailabilityObject.price + } + price = roundTo.up(price, 6) + return ( +
    + {searching ? +

    Checking {name}...

    + : +
    + {available ? +
    +

    {name}

    + {isSubdomain ? + + Get {name} for free + + : +
    + {checkingPrice ? +
    +
    + Checking price... +
    +
    + : + + Buy {name} for {price} bitcoins + + } +
    + } +
    + : +
    +

    {name}

    + +
    + + } +
    + } +
    + ) + }) + : + null + } +
    +
    + ) + +RegistrationSearchResults.propTypes = { + showSearchBox: PropTypes.func.isRequired, + searchingUsername: PropTypes.string, + nameSuffixes: PropTypes.array.isRequired, + availableNames: PropTypes.object.isRequired, + ownerAddress: PropTypes.string.isRequired +} + +export default RegistrationSearchResults diff --git a/app/js/profiles/components/registration/RegistrationSearchView.js b/app/js/profiles/components/registration/RegistrationSearchView.js new file mode 100644 index 000000000..2008d3616 --- /dev/null +++ b/app/js/profiles/components/registration/RegistrationSearchView.js @@ -0,0 +1,184 @@ +import React, { Component, PropTypes } from 'react' +import { Link } from 'react-router' +import { bindActionCreators } from 'redux' +import { connect } from 'react-redux' + +import RegistrationSearchBox from './RegistrationSearchBox' +import RegistrationSearchResults from './RegistrationSearchResults' + +import { AccountActions } from '../../../account/store/account' +import { AvailabilityActions } from '../../store/availability' +import { IdentityActions } from '../../store/identity' +import { RegistrationActions } from '../../store/registration' +import { isABlockstackName } from '../../../utils/name-utils' + +import log4js from 'log4js' + +const logger = log4js.getLogger('profiles/AddUsernameSearchPage.js') + +const STORAGE_URL = '/account/storage' + +function mapStateToProps(state) { + return { + api: state.settings.api, + availability: state.profiles.availability, + walletBalance: state.account.coreWallet.balance, + balanceUrl: state.settings.api.zeroConfBalanceUrl + } +} + +function mapDispatchToProps(dispatch) { + return bindActionCreators(Object.assign({}, + IdentityActions, AccountActions, RegistrationActions, AvailabilityActions), dispatch) +} + +class RegistrationSearchView extends Component { + static propTypes = { + api: PropTypes.object.isRequired, + availability: PropTypes.object.isRequired, + checkNameAvailabilityAndPrice: PropTypes.func.isRequired, + refreshCoreWalletBalance: PropTypes.func.isRequired, + walletBalance: PropTypes.number.isRequired, + routeParams: PropTypes.object.isRequired, + balanceUrl: PropTypes.string.isRequired + } + + constructor(props) { + super(props) + const availableDomains = Object.assign({}, + { + id: { + registerUrl: this.props.api.registerUrl + } + }, + this.props.api.subdomains) + + const nameSuffixes = Object.keys(availableDomains) + + this.state = { + alerts: [], + username: '', + searchingUsername: '', + storageConnected: this.props.api.dropboxAccessToken !== null, + availableDomains, + nameSuffixes, + showSearchResults: false + } + this.onChange = this.onChange.bind(this) + this.search = this.search.bind(this) + this.updateAlert = this.updateAlert.bind(this) + this.displayConnectStorageAlert = this.displayConnectStorageAlert.bind(this) + this.showSearchBox = this.showSearchBox.bind(this) + } + + componentDidMount() { + logger.trace('componentDidMount') + this.props.refreshCoreWalletBalance(this.props.balanceUrl, + this.props.api.coreAPIPassword) + } + + + componentWillReceiveProps() { + logger.trace('componentWillReceiveProps') + // Clear alerts + this.setState({ + alerts: [] + }) + + const storageConnected = this.props.api.dropboxAccessToken !== null + + if (!storageConnected) { + this.displayConnectStorageAlert() + } + } + + onChange(event) { + const username = event.target.value.toLowerCase().replace(/\W+/g, '') + this.setState({ + username + }) + } + + search(event) { + logger.trace('search') + const username = this.state.username + logger.debug(`search: user is searching for ${username}`) + event.preventDefault() + const nameSuffixes = this.state.nameSuffixes + const testDomainName = `${username}.${nameSuffixes[0]}` + if (!isABlockstackName(testDomainName)) { + this.updateAlert('danger', `${testDomainName} is not a valid Blockstack name`) + return + } + this.setState({ + searchingUsername: username, + showSearchResults: true + }) + + nameSuffixes.forEach((nameSuffix) => + this.props.checkNameAvailabilityAndPrice(this.props.api, + `${username}.${nameSuffix}`)) + } + + showSearchBox(event) { + logger.trace('showSearchBox') + event.preventDefault() + this.setState({ + username: '', + searchingUsername: '', + showSearchResults: false + }) + } + + updateAlert(alertStatus, alertMessage, url = null) { + logger.trace(`updateAlert: alertStatus: ${alertStatus}, alertMessage ${alertMessage}`) + this.setState({ + alerts: [{ + status: alertStatus, + message: alertMessage, + url + }] + }) + } + + displayConnectStorageAlert() { + this.updateAlert('danger', 'Please go to the Storage app and connect a storage provider.', + STORAGE_URL) + } + + render() { + const searchingUsername = this.state.searchingUsername + const availableNames = this.props.availability.names + const ownerAddress = this.props.routeParams.index + const showSearchResults = this.state.showSearchResults + return ( +
    + {!showSearchResults ? + + : + + } + + Cancel + +
    + ) + } +} + +export default connect(mapStateToProps, mapDispatchToProps)(RegistrationSearchView) diff --git a/app/js/profiles/AddUsernameSelectPage.js b/app/js/profiles/components/registration/RegistrationSelectView.js similarity index 95% rename from app/js/profiles/AddUsernameSelectPage.js rename to app/js/profiles/components/registration/RegistrationSelectView.js index ffe1a1c86..4bf886a0a 100644 --- a/app/js/profiles/AddUsernameSelectPage.js +++ b/app/js/profiles/components/registration/RegistrationSelectView.js @@ -2,11 +2,11 @@ import React, { Component, PropTypes } from 'react' import { Link } from 'react-router' import { bindActionCreators } from 'redux' import { connect } from 'react-redux' -import { AccountActions } from '../account/store/account' -import { AvailabilityActions } from './store/availability' -import { IdentityActions } from './store/identity' -import { RegistrationActions } from './store/registration' -import { hasNameBeenPreordered, isSubdomain } from '../utils/name-utils' +import { AccountActions } from '../../../account/store/account' +import { AvailabilityActions } from '../../store/availability' +import { IdentityActions } from '../../store/identity' +import { RegistrationActions } from '../../store/registration' +import { hasNameBeenPreordered, isSubdomain } from '../../../utils/name-utils' import roundTo from 'round-to' import { QRCode } from 'react-qr-svg' @@ -258,7 +258,9 @@ class AddUsernameSelectPage extends Component {
    :
    -

    Are you sure you want to buy {name}?

    +

    + Are you sure you want to buy {name}? +

    Purchasing {name} will spend {price} bitcoins from your wallet.

    - - - + + + From b8d88673a03c97e016a28328a74d3d80cd936334 Mon Sep 17 00:00:00 2001 From: Larry Salibra Date: Fri, 18 Aug 2017 19:12:39 +0700 Subject: [PATCH 46/65] prevent embedding of browser in frame --- native/blockstackProxy.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/native/blockstackProxy.js b/native/blockstackProxy.js index 1302ebbe1..a3dfba890 100644 --- a/native/blockstackProxy.js +++ b/native/blockstackProxy.js @@ -1,5 +1,5 @@ /* - Quick and dirty server for serving single page apps on localhost. + Quick and dirty server for serving single page apps on localhost. Usage: node blockstackProxy.js @@ -60,7 +60,10 @@ http.createServer(function(request, response) { return; } - response.writeHead(200, {"Content-Type": mimeLookup(filename)}); + response.writeHead(200, { + "Content-Type": mimeLookup(filename), + "X-Frame-Options": "DENY" + }); response.write(file, "binary"); response.end(); }); From 62cfc485c7d22151d480c9d225f9e66881c03003 Mon Sep 17 00:00:00 2001 From: Larry Salibra Date: Sun, 20 Aug 2017 18:29:04 +0700 Subject: [PATCH 47/65] add zone file api endpoint #88 --- app/js/account/store/settings/default.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/js/account/store/settings/default.js b/app/js/account/store/settings/default.js index 78266c50b..80cbf7863 100644 --- a/app/js/account/store/settings/default.js +++ b/app/js/account/store/settings/default.js @@ -23,6 +23,7 @@ const DEFAULT_API = { pgpKeyUrl: 'https://pgp.mit.edu/pks/lookup?search={identifier}&op=vindex&fingerprint=on', btcPriceUrl: 'https://www.bitstamp.net/api/v2/ticker/btcusd/', corePingUrl: 'http://localhost:6270/v1/node/ping', + zoneFileUrl: 'http://localhost:6270/v1/names/{name}/zonefile', subdomains: { 'foo.id': { registerUrl: 'http://localhost:7103/register' From 0caeda2bef8d86802a0d818d3b8f439d9448d30a Mon Sep 17 00:00:00 2001 From: Larry Salibra Date: Sun, 20 Aug 2017 22:07:12 +0700 Subject: [PATCH 48/65] add zone file update page skeleton #88 --- app/js/profiles/ViewProfilePage.js | 14 ++- app/js/profiles/ZoneFilePage.js | 138 +++++++++++++++++++++++++++++ app/js/routes.js | 2 + 3 files changed, 150 insertions(+), 4 deletions(-) create mode 100644 app/js/profiles/ZoneFilePage.js diff --git a/app/js/profiles/ViewProfilePage.js b/app/js/profiles/ViewProfilePage.js index 16203af3e..e088b8404 100644 --- a/app/js/profiles/ViewProfilePage.js +++ b/app/js/profiles/ViewProfilePage.js @@ -221,10 +221,16 @@ class ViewProfilePage extends Component { View Publicly : - - View Publicly - + + + View Publicly + + + Zone File + + } {!this.hasUsername() ? +
    +
    +
    +
    +
    +
    +

    + Update zone file +

    +

    + Updating your zone file is an advanced feature and can break + Blockstack name and profile. It requires broadcasting a + transaction on Bitcoin network and costs Bitcoin. +

    +
    +