diff --git a/dist/img/warning.png b/dist/img/warning.png new file mode 100644 index 000000000..c15cc246a Binary files /dev/null and b/dist/img/warning.png differ diff --git a/dist/index.html b/dist/index.html index 3ecc0a4d2..2bc0d6e83 100644 --- a/dist/index.html +++ b/dist/index.html @@ -20,8 +20,9 @@
- - + + + @@ -32,6 +33,7 @@ + diff --git a/js/app.js b/js/app.js index 1306ae5c7..f4fc8adad 100644 --- a/js/app.js +++ b/js/app.js @@ -1,4 +1,13 @@ var App = React.createClass({ + _error_key_labels: { + connectionString: 'API connection string', + method: 'Method', + params: 'Parameters', + code: 'Error code', + message: 'Error message', + data: 'Error data', + }, + getInitialState: function() { // For now, routes are in format ?page or ?page=args var match, param, val, viewingPage, @@ -11,35 +20,42 @@ var App = React.createClass({ viewingPage: viewingPage, drawerOpen: drawerOpenRaw !== null ? JSON.parse(drawerOpenRaw) : true, pageArgs: val, + errorInfo: null, + modal: null, + startNotice: null, + updateUrl: null, + isOldOSX: null, }; }, componentDidMount: function() { lbry.getStartNotice(function(notice) { if (notice) { - alert(notice); + this.setState({ + modal: 'startNotice', + startNotice: notice + }); } }); }, componentWillMount: function() { - lbry.checkNewVersionAvailable(function(isAvailable) { + document.addEventListener('unhandledError', (event) => { + this.alertError(event.detail); + }); + lbry.checkNewVersionAvailable((isAvailable) => { if (!isAvailable || sessionStorage.getItem('upgradeSkipped')) { return; } - var message = 'The version of LBRY you\'re using is not up to date.\n\n' + - 'Choose "OK" to download the latest version.'; - - lbry.getVersionInfo(function(versionInfo) { + lbry.getVersionInfo((versionInfo) => { + var isOldOSX = false; if (versionInfo.os_system == 'Darwin') { var updateUrl = 'https://lbry.io/get/lbry.dmg'; var maj, min, patch; [maj, min, patch] = versionInfo.lbrynet_version.split('.'); if (maj == 0 && min <= 2 && patch <= 2) { - // On OS X with version <= 0.2.2, we need to notify user to close manually close LBRY - message += '\n\nBefore installing the new version, make sure to exit LBRY, if you started the app ' + - 'click that LBRY icon in your status bar and choose "Quit."'; + isOldOSX = true; } } else if (versionInfo.os_system == 'Linux') { var updateUrl = 'https://lbry.io/get/lbry.deb'; @@ -49,13 +65,11 @@ var App = React.createClass({ var updateUrl = 'https://lbry.io/get'; } - if (window.confirm(message)) - { - lbry.stop(); - window.location = updateUrl; - } else { - sessionStorage.setItem('upgradeSkipped', true); - }; + this.setState({ + modal: 'upgrade', + isOldOSX: isOldOSX, + updateUrl: updateUrl, + }) }); }); }, @@ -67,12 +81,40 @@ var App = React.createClass({ sessionStorage.setItem('drawerOpen', false); this.setState({ drawerOpen: false }); }, + closeModal: function() { + this.setState({ + modal: null, + }); + }, + handleUpgradeClicked: function() { + lbry.stop(); + window.location = this.state.updateUrl; + }, + handleSkipClicked: function() { + sessionStorage.setItem('upgradeSkipped', true); + this.setState({ + modal: null, + }); + }, onSearch: function(term) { this.setState({ viewingPage: 'discover', pageArgs: term }); }, + alertError: function(error) { + var errorInfoList = []; + for (let key of Object.keys(error)) { + let val = typeof error[key] == 'string' ? error[key] : JSON.stringify(error[key]); + let label = this._error_key_labels[key]; + errorInfoList.push(
  • {label}: {val}
  • ); + } + + this.setState({ + modal: 'error', + errorInfo: , + }); + }, getHeaderLinks: function() { switch(this.state.viewingPage) @@ -151,6 +193,29 @@ var App = React.createClass({
    {mainContent} + + {this.state.startNotice} + + +

    The version of LBRY you're using is not up to date. Choose "Upgrade" to get the latest version.

    + {this.state.isOldOSX + ?

    Before installing the new version, make sure to exit LBRY. If you started the app, click the LBRY icon in your status bar and choose "Quit."

    + : null} + +
    + +

    Error

    + +
    +
    +

    We're sorry that LBRY has encountered an error. This has been reported and we will investigate the problem.

    +
    + {this.state.errorInfo} +
    + +
    +
    ); } diff --git a/js/component/common.js b/js/component/common.js index a751d4d8c..41a10c97b 100644 --- a/js/component/common.js +++ b/js/component/common.js @@ -113,3 +113,36 @@ var Address = React.createClass({ ); } }); + +var Thumbnail = React.createClass({ + _defaultImageUri: '/img/default-thumb.svg', + _maxLoadTime: 10000, + + propTypes: { + src: React.PropTypes.string.isRequired, + }, + handleError: function() { + if (this.state.imageUrl != this._defaultImageUri) { + this.setState({ + imageUri: this._defaultImageUri, + }); + } + }, + getInitialState: function() { + return { + imageUri: this.props.src || this._defaultImageUri, + }; + }, + componentDidMount: function() { + setTimeout(() => { + if (!this.refs.img.complete) { + this.setState({ + imageUri: this._defaultImageUri, + }); + } + }, this._maxLoadTime); + }, + render: function() { + return + }, +}); diff --git a/js/component/drawer.js b/js/component/drawer.js index a821c274f..e3ca457d4 100644 --- a/js/component/drawer.js +++ b/js/component/drawer.js @@ -1,6 +1,12 @@ var DrawerItem = React.createClass({ + getDefaultProps: function() { + return { + subPages: [], + }; + }, render: function() { - var isSelected = this.props.viewingPage == this.props.href.substr(2); + var isSelected = (this.props.viewingPage == this.props.href.substr(2) || + this.props.subPages.indexOf(this.props.viewingPage) != -1); return } }); @@ -32,8 +38,8 @@ var Drawer = React.createClass({ - - + + {isLinux ? : null} diff --git a/js/component/link.js b/js/component/link.js index 0243d5bed..8f9b8cde7 100644 --- a/js/component/link.js +++ b/js/component/link.js @@ -93,13 +93,22 @@ var DownloadLink = React.createClass({ getInitialState: function() { return { downloading: false, + filePath: null, + modal: null, } }, + closeModal: function() { + this.setState({ + modal: null, + }) + }, handleClick: function() { lbry.getCostEstimate(this.props.streamName, (amount) => { lbry.getBalance((balance) => { if (amount > balance) { - alert("You don't have enough LBRY credits to pay for this stream."); + this.setState({ + modal: 'notEnoughCredits', + }); } else { this.startDownload(); } @@ -113,15 +122,27 @@ var DownloadLink = React.createClass({ }); lbry.getStream(this.props.streamName, (streamInfo) => { - alert('Downloading to ' + streamInfo.path); - console.log(streamInfo); + this.setState({ + modal: 'downloadStarted', + filePath: streamInfo.path, + }); }); } }, render: function() { var label = (!this.state.downloading ? this.props.label : this.props.downloadingLabel); - return ; + return ( + + + + Downloading to {this.state.filePath} + + + You don't have enough LBRY credits to pay for this stream. + + + ); } }); @@ -138,22 +159,40 @@ var WatchLink = React.createClass({ lbry.getCostEstimate(this.props.streamName, (amount) => { lbry.getBalance((balance) => { if (amount > balance) { - alert("You don't have enough LBRY credits to pay for this stream."); + this.setState({ + modal: 'notEnoughCredits', + }); } else { window.location = '?watch=' + this.props.streamName; } }); }); }, + getInitialState: function() { + return { + modal: null, + }; + }, + closeModal: function() { + this.setState({ + modal: null, + }); + }, getDefaultProps: function() { return { icon: 'icon-play', label: 'Watch', } }, - render: function() { - return ; + return ( + + + + You don't have enough LBRY credits to pay for this stream. + + + ); } }); \ No newline at end of file diff --git a/js/component/modal.js b/js/component/modal.js new file mode 100644 index 000000000..b1bb5065f --- /dev/null +++ b/js/component/modal.js @@ -0,0 +1,57 @@ +var Modal = React.createClass({ + propTypes: { + type: React.PropTypes.oneOf(['alert', 'confirm', 'custom']), + onConfirmed: React.PropTypes.func, + onAborted: React.PropTypes.func, + confirmButtonLabel: React.PropTypes.string, + abortButtonLabel: React.PropTypes.string, + confirmButtonDisabled: React.PropTypes.bool, + abortButtonDisabled: React.PropTypes.bool, + }, + getDefaultProps: function() { + return { + type: 'alert', + confirmButtonLabel: 'OK', + abortButtonLabel: 'Cancel', + confirmButtonDisabled: false, + abortButtonDisabled: false, + }; + }, + render: function() { + var props = Object.assign({}, this.props); + + if (typeof props.className == 'undefined') { + props.className = 'modal'; + } else { + props.className += ' modal'; + } + + if (typeof props.overlayClassName == 'undefined') { + props.overlayClassName = 'modal-overlay'; + } else { + props.overlayClassName += ' modal-overlay'; + } + + props.onCloseRequested = props.onAborted || props.onConfirmed; + + if (this.props.type == 'custom') { + var buttons = null; + } else { + var buttons = ( +
    + {this.props.type == 'confirm' + ? + : null} + +
    + ); + } + + return ( + + {this.props.children} + {buttons} + + ); + } +}); diff --git a/js/lbry.js b/js/lbry.js index d1216b6f1..e39980e82 100644 --- a/js/lbry.js +++ b/js/lbry.js @@ -10,14 +10,38 @@ var lbry = { } }; -lbry.jsonrpc_call = function (connectionString, method, params, callback, errorCallback, connectFailedCallback) { +lbry.jsonrpc_call = function (connectionString, method, params, callback, errorCallback, connectFailedCallback, timeout) { var xhr = new XMLHttpRequest; + if (typeof connectFailedCallback !== 'undefined') { + if (timeout) { + xhr.timeout = timeout; + } + + xhr.addEventListener('error', function (e) { + connectFailedCallback(e); + }); + xhr.addEventListener('timeout', function() { + connectFailedCallback(new Error('XMLHttpRequest connection timed out')); + }) + } xhr.addEventListener('load', function() { var response = JSON.parse(xhr.responseText); if (response.error) { if (errorCallback) { errorCallback(response.error); + } else { + var errorEvent = new CustomEvent('unhandledError', { + detail: { + connectionString: connectionString, + method: method, + params: params, + code: response.error.code, + message: response.error.message, + data: response.error.data + } + }); + document.dispatchEvent(errorEvent) } } else if (callback) { callback(response.result); @@ -25,8 +49,21 @@ lbry.jsonrpc_call = function (connectionString, method, params, callback, errorC }); if (connectFailedCallback) { - xhr.addEventListener('error', function (e) { - connectFailedCallback(e); + xhr.addEventListener('error', function (event) { + connectFailedCallback(event); + }); + } else { + xhr.addEventListener('error', function (event) { + var errorEvent = new CustomEvent('unhandledError', { + detail: { + connectionString: connectionString, + method: method, + params: params, + code: xhr.status, + message: 'Connection to API server failed' + } + }); + document.dispatchEvent(errorEvent); }); } @@ -122,11 +159,6 @@ lbry.sendToAddress = function(amount, address, callback, errorCallback) lbry.call("send_amount_to_address", { "amount" : amount, "address": address }, callback, errorCallback); } -lbry.search = function(query, callback) -{ - lbry.lighthouse.call('search', [query], callback); -} - lbry.resolveName = function(name, callback) { lbry.call('resolve_name', { 'name': name }, callback, () => { // For now, assume any error means the name was not resolved diff --git a/js/lighthouse.js b/js/lighthouse.js index e72322486..93cea4ceb 100644 --- a/js/lighthouse.js +++ b/js/lighthouse.js @@ -1,4 +1,7 @@ lbry.lighthouse = { + _search_timeout: 5000, + _max_search_tries: 5, + servers: [ 'http://lighthouse1.lbry.io:50005', 'http://lighthouse2.lbry.io:50005', @@ -6,9 +9,28 @@ lbry.lighthouse = { ], path: '/', - call: function(method, params, callback, errorCallback, connectFailedCallback) { - lbry.jsonrpc_call(this.server + this.path, method, params, callback, errorCallback, connectFailedCallback); + call: function(method, params, callback, errorCallback, connectFailedCallback, timeout) { + lbry.jsonrpc_call(this.server + this.path, method, params, callback, errorCallback, connectFailedCallback, timeout); }, + + search: function(query, callback) { + let handleSearchFailed = function(tryNum=0) { + if (tryNum > lbry.lighthouse._max_search_tries) { + throw new Error(`Could not connect to Lighthouse server. Last server attempted: ${lbry.lighthouse.server}`); + } else { + // Randomly choose one of the other search servers to switch to + let otherServers = lbry.lighthouse.servers.slice(); + otherServers.splice(otherServers.indexOf(lbry.lighthouse.server), 1); + lbry.lighthouse.server = otherServers[Math.round(Math.random() * (otherServers.length - 1))]; + + lbry.lighthouse.call('search', [query], callback, undefined, function() { + handleSearchFailed(tryNum + 1); + }, lbry.lighthouse._search_timeout); + } + } + + lbry.lighthouse.call('search', [query], callback, undefined, function() { handleSearchFailed() }, lbry.lighthouse._search_timeout); + } }; lbry.lighthouse.server = lbry.lighthouse.servers[Math.round(Math.random() * (lbry.lighthouse.servers.length - 1))]; diff --git a/js/page/claim_code.js b/js/page/claim_code.js index d904703a9..1655d37da 100644 --- a/js/page/claim_code.js +++ b/js/page/claim_code.js @@ -14,6 +14,10 @@ var ClaimCodePage = React.createClass({ getInitialState: function() { return { submitting: false, + modal: null, + referralCredits: null, + activationCredits: null, + failureReason: null, } }, handleSubmit: function(event) { @@ -22,15 +26,19 @@ var ClaimCodePage = React.createClass({ } if (!this.refs.code.value) { - alert('Please enter an invitation code or choose "Skip."'); + this.setState({ + modal: 'missingCode', + }); return; } else if (!this.refs.email.value) { - alert('Please enter an email address or choose "Skip."'); + this.setState({ + modal: 'missingEmail', + }); return; } this.setState({ - submitting: true + submitting: true, }); lbry.getNewAddress((address) => { @@ -42,33 +50,25 @@ var ClaimCodePage = React.createClass({ var response = JSON.parse(xhr.responseText); if (response.success) { - var redeemMessage = 'Your invite code has been redeemed. '; - if (response.referralCredits > 0) { - redeemMessage += 'You have also earned ' + response.referralCredits + ' credits from referrals. A total of ' + - (response.activationCredits + response.referralCredits) + ' will be added to your balance shortly.'; - } else if(response.activationCredits > 0) { - redeemMessage += response.activationCredits + ' credits will be added to your balance shortly.'; - } else { - redeemMessage += 'The credits will be added to your balance shortly.'; - } - alert(redeemMessage); - localStorage.setItem('claimCodeDone', true); - window.location = '?home'; - } else { - alert(response.reason); this.setState({ - submitting: false + modal: 'codeRedeemed', + referralCredits: response.referralCredits, + activationCredits: response.activationCredits, + }); + } else { + this.setState({ + submitting: false, + modal: 'codeRedeemFailed', + failureReason: response.reason, }); } }); xhr.addEventListener('error', () => { this.setState({ - submitting: false + submitting: false, + modal: 'couldNotConnect', }); - alert('LBRY couldn\'t connect to our servers to confirm your invitation code. Please check your ' + - 'internet connection. If you continue to have problems, you can still browse LBRY and ' + - 'visit the Settings page to redeem your code later.'); }); xhr.open('POST', 'https://invites.lbry.io', true); @@ -78,10 +78,19 @@ var ClaimCodePage = React.createClass({ }); }, handleSkip: function() { - alert('Welcome to LBRY! You can visit the Wallet page to redeem an invite code at any time.'); + this.setState({ + modal: 'skipped', + }); + }, + handleFinished: function() { localStorage.setItem('claimCodeDone', true); window.location = '?home'; }, + closeModal: function() { + this.setState({ + modal: null, + }); + }, render: function() { return (
    @@ -105,6 +114,31 @@ var ClaimCodePage = React.createClass({ + + Please enter an invitation code or choose "Skip." + + + Please enter an email address or choose "Skip." + + + {this.state.failureReason} + + + Your invite code has been redeemed. + {this.state.referralCredits > 0 + ? `You have also earned {referralCredits} credits from referrals. A total of {activationCredits + referralCredits} + will be added to your balance shortly.` + : (this.state.activationCredits > 0 + ? `{this.state.activationCredits} credits will be added to your balance shortly.` + : 'The credits will be added to your balance shortly.')} + + + Welcome to LBRY! You can visit the Wallet page to redeem an invite code at any time. + + +

    LBRY couldn't connect to our servers to confirm your invitation code. Please check your internet connection.

    + If you continue to have problems, you can still browse LBRY and visit the Settings page to redeem your code later. +
    ); } diff --git a/js/page/discover.js b/js/page/discover.js index 8445414af..e8344d35a 100644 --- a/js/page/discover.js +++ b/js/page/discover.js @@ -113,7 +113,7 @@ var SearchResultRow = React.createClass({
    - {'Photo +
    @@ -179,7 +179,7 @@ var FeaturedContentItem = React.createClass({ componentDidMount: function() { this.resolveSearch = true; - lbry.search(this.props.name, function(results) { + lbry.lighthouse.search(this.props.name, function(results) { var result = results[0]; var metadata = result.value; if (this.resolveSearch) @@ -201,7 +201,7 @@ var FeaturedContentItem = React.createClass({ } return (
    -
    ); @@ -257,7 +257,7 @@ var DiscoverPage = React.createClass({ query: this.props.query, }); - lbry.search(this.props.query, this.searchCallback); + lbry.lighthouse.search(this.props.query, this.searchCallback); }, componentDidMount: function() { diff --git a/js/page/my_files.js b/js/page/my_files.js index fe60cbaa2..587498bb7 100644 --- a/js/page/my_files.js +++ b/js/page/my_files.js @@ -5,31 +5,48 @@ var moreMenuStyle = { right: '13px', }; var MyFilesRowMoreMenu = React.createClass({ - onRevealClicked: function() { + propTypes: { + title: React.PropTypes.string.isRequired, + path: React.PropTypes.string.isRequired, + completed: React.PropTypes.bool.isRequired, + lbryUri: React.PropTypes.string.isRequired, + }, + handleRevealClicked: function() { lbry.revealFile(this.props.path); }, - onRemoveClicked: function() { + handleRemoveClicked: function() { lbry.deleteFile(this.props.lbryUri, false); }, - onDeleteClicked: function() { - var alertText = 'Are you sure you\'d like to delete "' + this.props.title + '?" This will ' + - (this.completed ? ' stop the download and ' : '') + - 'permanently remove the file from your system.'; - - if (confirm(alertText)) { - lbry.deleteFile(this.props.lbryUri); - } + handleDeleteClicked: function() { + this.setState({ + modal: 'confirmDelete', + }); + }, + handleDeleteConfirmed: function() { + lbry.deleteFile(this.props.lbryUri); + lbry.setState({ + modal: null, + }); + }, + getInitialState: function() { + return { + modal: null, + }; }, render: function() { return (
    - {/* @TODO: Switch to OS specific wording */} - - + {/* @TODO: Switch to OS specific wording */} + +
    + + Are you sure you'd like to delete {this.props.title}? This will {this.props.completed ? ' stop the download and ' : ''} + permanently remove the file from your system. +
    ); } @@ -98,7 +115,7 @@ var MyFilesRow = React.createClass({
    - {'Photo +

    {this.props.pending ? this.props.title : {this.props.title}}

    @@ -123,8 +140,8 @@ var MyFilesRow = React.createClass({
    + completed={this.props.completed} lbryUri={this.props.lbryUri} + fileName={this.props.fileName} path={this.props.path}/>
    }
    @@ -208,7 +225,11 @@ var MyFilesPage = React.createClass({ for (let fileInfo of filesInfo) { let name = fileInfo.lbry_uri; - lbry.search(name, (results) => { + if (name === null) { + continue; + } + + lbry.lighthouse.search(name, (results) => { var result = results[0]; var available = result.name == name && result.available; diff --git a/js/page/publish.js b/js/page/publish.js index a21d6516a..03cda29cf 100644 --- a/js/page/publish.js +++ b/js/page/publish.js @@ -81,14 +81,8 @@ var PublishPage = React.createClass({ console.log(publishArgs); lbry.publish(publishArgs, (message) => { this.handlePublishStarted(); - this.setState({ - submitting: false, - }); }, null, (error) => { this.handlePublishError(error); - this.setState({ - submitting: false, - }); }); }; @@ -125,18 +119,26 @@ var PublishPage = React.createClass({ otherLicenseUrl: '', uploadProgress: 0.0, uploaded: false, + errorMessage: null, tempFileReady: false, submitting: false, + modal: null, }; }, handlePublishStarted: function() { - alert(`Your file ${this.refs.meta_title.getValue()} has been published to LBRY at the address lbry://${this.state.name}!\n\n` + - `You will now be taken to your My Files page, where your newly published file will be listed. Your file will take a few minutes to appear for other LBRY users; until then it will be listed as "pending."`); + this.setState({ + modal: 'publishStarted', + }); + }, + handlePublishStartedConfirmed: function() { window.location = "?published"; }, handlePublishError: function(error) { - alert(`The following error occurred when attempting to publish your file:\n\n` + - error.message); + this.setState({ + submitting: false, + modal: 'error', + errorMessage: error.message, + }); }, handleNameChange: function(event) { var rawName = event.target.value; @@ -335,7 +337,7 @@ var PublishPage = React.createClass({ (!this.state.name ? '' : (! this.state.nameResolved ? The name {this.state.name} is available. : (this.state.myClaimExists ? You already have a claim on the name {this.state.name}. You can use this page to update your claim. - : The name {this.state.name} is currently claimed for {lbry.formatCredits(this.state.topClaimValue)} credits.))) + : The name {this.state.name} is currently claimed for {this.state.topClaimValue} {this.state.topClaimValue == 1 ? 'credit' : 'credits'}.))) }
    What LBRY name would you like to claim for this file?
    @@ -356,13 +358,13 @@ var PublishPage = React.createClass({

    Bid Amount

    - Credits + Credits
    How much would you like to bid for this name? { !this.state.nameResolved ? Since this name is not currently resolved, you may bid as low as you want, but higher bids help prevent others from claiming your name. - : (this.state.topClaimIsMine ? You currently control this name with a bid of {lbry.formatCredits(this.state.myClaimValue)} credits. - : (this.state.myClaimExists ? You have a non-winning bid on this name for {lbry.formatCredits(this.state.myClaimValue)} credits. - To control this name, you'll need to increase your bid to at least {lbry.formatCredits(this.state.myClaimValue)} credits. - : You must bid over {lbry.formatCredits(this.state.topClaimValue)} credits to claim this name.)) } + : (this.state.topClaimIsMine ? You currently control this name with a bid of {this.state.myClaimValue} {this.state.myClaimValue == 1 ? 'credit' : 'credits'}. + : (this.state.myClaimExists ? You have a non-winning bid on this name for {this.state.myClaimValue} {this.state.myClaimValue == 1 ? 'credit' : 'credits'}. + To control this name, you'll need to increase your bid to more than {this.state.topClaimValue} {this.state.topClaimValue == 1 ? 'credit' : 'credits'}. + : You must bid over {this.state.topClaimValue} {this.state.topClaimValue == 1 ? 'credit' : 'credits'} to claim this name.)) }
    @@ -463,6 +465,14 @@ var PublishPage = React.createClass({
    + + +

    Your file has been published to LBRY at the address lbry://{this.state.name}!

    + You will now be taken to your My Files page, where your newly published file will be listed. The file will take a few minutes to appear for other LBRY users; until then it will be listed as "pending." +
    + + The following error occurred when attempting to publish your file: {this.state.errorMessage} + ); } diff --git a/js/page/referral.js b/js/page/referral.js index 0c7337d1d..4cbcf930d 100644 --- a/js/page/referral.js +++ b/js/page/referral.js @@ -14,6 +14,9 @@ var ReferralPage = React.createClass({ getInitialState: function() { return { submitting: false, + modal: null, + referralCredits: null, + failureReason: null, } }, handleSubmit: function(event) { @@ -22,15 +25,17 @@ var ReferralPage = React.createClass({ } if (!this.refs.code.value) { - alert('Please enter a referral code.'); - return; + this.setState({ + modal: 'missingCode', + }); } else if (!this.refs.email.value) { - alert('Please enter an email address.'); - return; + this.setState({ + modal: 'missingEmail', + }); } this.setState({ - submitting: true + submitting: true, }); lbry.getNewAddress((address) => { @@ -42,29 +47,24 @@ var ReferralPage = React.createClass({ var response = JSON.parse(xhr.responseText); if (response.success) { - if (response.referralCredits > 0) { - alert('You have earned ' + response.referralCredits + ' credits from referrals. ' + - 'We will credit your account shortly. Thanks!'); - } else { - alert('You have not earned any new referral credits since the last time you checked. ' + - 'Please check back in a week or two.'); - } - - window.location = '?home'; - } else { - alert(response.reason); this.setState({ - submitting: false + modal: 'referralInfo', + referralCredits: response.referralCredits, + }); + } else { + this.setState({ + submitting: false, + modal: 'lookupFailed', + failureReason: response.reason, }); } }); xhr.addEventListener('error', () => { this.setState({ - submitting: false + submitting: false, + modal: 'couldNotConnect', }); - alert('LBRY couldn\'t connect to our servers to confirm your referral code. Please check your ' + - 'internet connection.'); }); xhr.open('POST', 'https://invites.lbry.io/check', true); @@ -73,6 +73,15 @@ var ReferralPage = React.createClass({ '&email=' + encodeURIComponent(email)); }); }, + closeModal: function() { + this.setState({ + modal: null, + }); + }, + handleFinished: function() { + localStorage.setItem('claimCodeDone', true); + window.location = '?home'; + }, render: function() { return (
    @@ -94,6 +103,17 @@ var ReferralPage = React.createClass({
    + + {this.state.referralCredits > 0 + ? `You have earned {response.referralCredits} credits from referrals. We will credit your account shortly. Thanks!` + : 'You have not earned any new referral credits since the last time you checked. Please check back in a week or two.'} + + + {this.state.failureReason} + + + LBRY couldn't connect to our servers to confirm your referral code. Please check your internet connection. + ); } diff --git a/js/page/report.js b/js/page/report.js index af10ae71c..ac9751857 100644 --- a/js/page/report.js +++ b/js/page/report.js @@ -6,9 +6,9 @@ var ReportPage = React.createClass({ }); lbry.reportBug(this._messageArea.value, () => { this.setState({ - submitting: false + submitting: false, + modal: 'submitted', }); - alert("Your bug report has been submitted! Thank you for your feedback."); }); this._messageArea.value = ''; } @@ -16,9 +16,15 @@ var ReportPage = React.createClass({ componentDidMount: function() { document.title = "Report an Issue"; }, + closeModal: function() { + this.setState({ + modal: null, + }) + }, getInitialState: function() { return { submitting: false, + modal: null, } }, render: function() { @@ -38,6 +44,9 @@ var ReportPage = React.createClass({

    Developer?

    You can also . + + Your bug report has been submitted! Thank you for your feedback. + ); } diff --git a/js/page/show.js b/js/page/show.js index a7c28030c..3c99ea4bb 100644 --- a/js/page/show.js +++ b/js/page/show.js @@ -31,7 +31,7 @@ var FormatItem = React.createClass({ return (
    - {'Photo +

    {description}

    @@ -117,7 +117,7 @@ var DetailPage = React.createClass({ componentWillMount: function() { document.title = 'lbry://' + this.props.name; - lbry.search(this.props.name, (results) => { + lbry.lighthouse.search(this.props.name, (results) => { var result = results[0]; if (result.name != this.props.name) { diff --git a/js/page/wallet.js b/js/page/wallet.js index a6eaa690c..7f5c536cc 100644 --- a/js/page/wallet.js +++ b/js/page/wallet.js @@ -17,6 +17,7 @@ var AddressSection = React.createClass({ getInitialState: function() { return { address: null, + modal: null, } }, componentWillMount: function() { @@ -58,7 +59,9 @@ var SendToAddressSection = React.createClass({ if ((this.state.balance - this.state.amount) < 1) { - alert("Insufficient balance: after this transaction you would have less than 1 LBC in your wallet.") + this.setState({ + modal: 'insufficientBalance', + }); return; } @@ -85,6 +88,11 @@ var SendToAddressSection = React.createClass({ }) }); }, + closeModal: function() { + this.setState({ + modal: null, + }); + }, getInitialState: function() { return { address: "", @@ -136,6 +144,9 @@ var SendToAddressSection = React.createClass({ : '' } + + Insufficient balance: after this transaction you would have less than 1 LBC in your wallet. + ); } @@ -168,7 +179,7 @@ var TransactionList = React.createClass({ { transactionItems.push({ id: txid, - date: new Date(parseInt(tx["timestamp"]) * 1000), + date: tx["timestamp"] ? (new Date(parseInt(tx["timestamp"]) * 1000)) : null, amount: condensedTransactions[txid] }); delete condensedTransactions[txid]; @@ -187,8 +198,8 @@ var TransactionList = React.createClass({ rows.push( { (item.amount > 0 ? '+' : '' ) + item.amount } - { item.date.toLocaleDateString() } - { item.date.toLocaleTimeString() } + { item.date ? item.date.toLocaleDateString() : (Transaction pending) } + { item.date ? item.date.toLocaleTimeString() : (Transaction pending) } {item.id.substr(0, 7)} diff --git a/scss/_gui.scss b/scss/_gui.scss index 09a1b950a..184bf68b5 100644 --- a/scss/_gui.scss +++ b/scss/_gui.scss @@ -78,6 +78,11 @@ label { display: block; } +code { + font: 0.8em Consolas, 'Lucida Console', 'Source Sans', monospace; + background-color: #eee; +} + p { margin-bottom: 0.8em; @@ -132,6 +137,13 @@ input[type="text"], input[type="search"] } } +.button-container { + + .button-container + { + margin-left: 12px; + } +} + .button-block { cursor: pointer; @@ -143,10 +155,6 @@ input[type="text"], input[type="search"] text-align: center; border-radius: 2px; text-transform: uppercase; - + .button-block - { - margin-left: 12px; - } .icon { top: 0em; @@ -225,3 +233,73 @@ input[type="text"], input[type="search"] margin-top: $spacing-vertical; } } + + +.modal-overlay { + position: fixed; + display: flex; + justify-content: center; + align-items: center; + + top: 0px; + left: 0px; + right: 0px; + bottom: 0px; + background-color: rgba(255, 255, 255, 0.74902); + z-index: 9999; +} + +.modal { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + + border: 1px solid rgb(204, 204, 204); + background: rgb(255, 255, 255); + overflow: auto; + border-radius: 4px; + outline: none; + padding: 36px; + max-width: 250px; +} + +.modal__header { + margin-bottom: 5px; +} + +.modal__buttons { + display: flex; + flex-direction: row; + justify-content: center; + margin-top: 15px; +} + +.modal__button { + margin: 0px 6px; +} + +.error-modal__error-list { + border: 1px solid #eee; + padding: 8px; + list-style: none; +} + +.error-modal-overlay { + background: rgba(#000, .88); +} + +.error-modal { + max-width: none; + width: 400px; +} + +.error-modal__content { + display: flex; + padding: 0px 8px 10px 10px; +} + +.error-modal__warning-symbol { + margin-top: 6px; + margin-right: 7px; +} \ No newline at end of file