diff --git a/ui/js/app.js b/ui/js/app.js index 03d508f81..424fb8533 100644 --- a/ui/js/app.js +++ b/ui/js/app.js @@ -2,22 +2,18 @@ import React from 'react'; import {Line} from 'rc-progress'; import lbry from './lbry.js'; -import lbryio from './lbryio.js'; import EmailPage from './page/email.js'; import SettingsPage from './page/settings.js'; import HelpPage from './page/help.js'; import WatchPage from './page/watch.js'; import ReportPage from './page/report.js'; import StartPage from './page/start.js'; -import ClaimCodePage from './page/claim_code.js'; -import ReferralPage from './page/referral.js'; import RewardsPage from './page/rewards.js'; import RewardPage from './page/reward.js'; import WalletPage from './page/wallet.js'; import ShowPage from './page/show.js'; import PublishPage from './page/publish.js'; import DiscoverPage from './page/discover.js'; -import SplashScreen from './component/splash.js'; import DeveloperPage from './page/developer.js'; import {FileListDownloaded, FileListPublished} from './page/file-list.js'; import Drawer from './component/drawer.js'; @@ -229,15 +225,11 @@ var App = React.createClass({ case 'wallet': case 'send': case 'receive': - case 'claim': - case 'referral': case 'rewards': return { '?wallet': 'Overview', '?send': 'Send', '?receive': 'Receive', - '?claim': 'Claim Beta Code', - '?referral': 'Check Referral Credit', '?rewards': 'Rewards', }; case 'downloaded': @@ -268,14 +260,8 @@ var App = React.createClass({ return ; case 'start': return ; - case 'claim': - return ; - case 'referral': - return ; case 'rewards': - return ; - case 'reward': - return ; + return ; case 'wallet': case 'send': case 'receive': diff --git a/ui/js/component/auth.js b/ui/js/component/auth.js index 2b7e6d02b..83edd2c9f 100644 --- a/ui/js/component/auth.js +++ b/ui/js/component/auth.js @@ -1,10 +1,11 @@ import React from 'react'; import lbryio from '../lbryio.js'; +import Modal from './modal.js'; import ModalPage from './modal-page.js'; -import {Link} from '../component/link.js'; -import FormField from '../component/form.js'; -import Notice from '../component/notice.js' +import {Link, RewardLink} from '../component/link.js'; +import {FormField, FormRow} from '../component/form.js'; +import rewards from '../rewards.js'; const SubmitEmailStage = React.createClass({ @@ -29,8 +30,8 @@ const SubmitEmailStage = React.createClass({ lbryio.call('user_email', 'new', {email: this.state.email}, 'post').then(() => { this.props.onEmailSaved(); }, (error) => { - if (this._emailField) { - this._emailField.showError(error.message) + if (this._emailRow) { + this._emailRow.showError(error.message) } this.setState({ submitting: false }); }); @@ -39,10 +40,10 @@ const SubmitEmailStage = React.createClass({ return (
- { this._emailField = field }} type="text" label="Email" placeholder="webmaster@toplbryfan.com" + { this._emailRow = ref }} type="text" label="Email" placeholder="webmaster@toplbryfan.com" name="email" value={this.state.email} onChange={this.handleEmailChanged} /> -
+
@@ -71,27 +72,29 @@ const ConfirmEmailStage = React.createClass({ submitting: true, }); - lbryio.call('user_email', 'confirm', {verification_token: this.state.code}, 'post').then(() => { - rewards.claimReward('confirm_email').then(() => { - this.props.onDone(); - }, (err) => {l - this.props.onDone(); - }); - }, (error) => { - if (this._codeField) { - this._codeField.showError(error.message) - this.setState({ submitting: false }) + const onSubmitError = function(error) { + if (this._codeRow) { + this._codeRow.showError(error.message) } - }); + this.setState({ submitting: false }); + }.bind(this) + + lbryio.call('user_email', 'confirm', {verification_token: this.state.code}, 'post').then((userEmail) => { + if (userEmail.IsVerified) { + this.props.onEmailConfirmed(); + } else { + onSubmitError(new Error("Your email is still not verified.")) //shouldn't happen? + } + }, onSubmitError); }, render: function() { return (
- { this._codeField = field }} type="text" + { this._codeRow = ref }} type="text" name="code" placeholder="a94bXXXXXXXXXXXXXX" value={this.state.code} onChange={this.handleCodeChanged} helper="A verification code is required to access this version."/> -
+
@@ -100,9 +103,29 @@ const ConfirmEmailStage = React.createClass({ } }); +const WelcomeStage = React.createClass({ + onRewardClaim: function() { + console.log('omg'); + }, + render: function() { + return ( +
+

Welcome to LBRY.

+

LBRY is the first community controlled content marketplace.

+

Since you're new here, we'll toss you some credits.

+
+ +
+

LBC is blah blah blah.

+

And remember, LBRY is a beta and be safe!

+
+ ); + } +}); + + const ErrorStage = React.createClass({ render: function() { - //
return (

An error was encountered that we cannot continue from.

@@ -129,68 +152,66 @@ export const AuthOverlay = React.createClass({ error: ErrorStage, email: SubmitEmailStage, confirm: ConfirmEmailStage, + welcome: WelcomeStage, }, - propTypes: { - // onDone: React.PropTypes.func.isRequired, - }, + getInitialState: function() { return { - stage: "pending", + stage: "welcome", stageProps: {} }; }, - componentWillMount: function() { - lbryio.authenticate().then(function(user) { - console.log(user); - if (!user.HasVerifiedEmail) { - this.setState({ - stage: "email", - stageProps: { - onEmailSaved: function() { - this.setState({ - stage: "confirm" - }) - }.bind(this) - } - }) - } else { - this.setState({ stage: null }) - } - }.bind(this)).catch((err) => { - this.setState({ - stage: "error", - stageProps: { errorText: err.message } - }) - document.dispatchEvent(new CustomEvent('unhandledError', { - detail: { - message: err.message, - data: err.stack - } - })); - }) + endAuth: function() { + this.setState({ + stage: null + }); + }, + componentWillMount: function() { + // lbryio.authenticate().then(function(user) { + // if (!user.HasVerifiedEmail) { //oops I fucked this up + // this.setState({ + // stage: "email", + // stageProps: { + // onEmailSaved: function() { + // this.setState({ + // stage: "confirm", + // stageProps: { + // onEmailConfirmed: function() { this.setState({ stage: "welcome"}) }.bind(this) + // } + // }) + // }.bind(this) + // } + // }) + // } else { + // this.endAuth() + // } + // }.bind(this)).catch((err) => { + // this.setState({ + // stage: "error", + // stageProps: { errorText: err.message } + // }) + // document.dispatchEvent(new CustomEvent('unhandledError', { + // detail: { + // message: err.message, + // data: err.stack + // } + // })); + // }) }, - // handleStageDone: function() { - // if (this.state.stageNum >= this._stages.length - 1) { - // this.props.onDone(); - // } else { - // this.setState({ - // stageNum: this.state.stageNum + 1, - // }); - // } - // }, - - // render: function() { - console.log(lbryio.user); if (!this.state.stage || lbryio.user && lbryio.user.HasVerifiedEmail) { return null; } const StageContent = this._stages[this.state.stage]; return ( - -

LBRY Early Access

- -
+ this.state.stage != "welcome" ? + +

LBRY Early Access

+ +
: + + + ); } }); \ No newline at end of file diff --git a/ui/js/component/drawer.js b/ui/js/component/drawer.js index eaf11506b..e719af073 100644 --- a/ui/js/component/drawer.js +++ b/ui/js/component/drawer.js @@ -55,7 +55,7 @@ var Drawer = React.createClass({ - + diff --git a/ui/js/component/file-actions.js b/ui/js/component/file-actions.js index 1a535099a..715133bd8 100644 --- a/ui/js/component/file-actions.js +++ b/ui/js/component/file-actions.js @@ -3,7 +3,7 @@ import lbry from '../lbry.js'; import {Link} from '../component/link.js'; import {Icon} from '../component/common.js'; import Modal from './modal.js'; -import FormField from './form.js'; +import {FormField} from './form.js'; import {ToolTip} from '../component/tooltip.js'; import {DropDownMenu, DropDownMenuItem} from './menu.js'; @@ -53,7 +53,7 @@ let WatchLink = React.createClass({ render: function() { return (
- + You don't have enough LBRY credits to pay for this stream. diff --git a/ui/js/component/file-tile.js b/ui/js/component/file-tile.js index b65434bac..bb9483374 100644 --- a/ui/js/component/file-tile.js +++ b/ui/js/component/file-tile.js @@ -47,7 +47,7 @@ let FilePrice = React.createClass({ } return ( - + ); @@ -131,8 +131,8 @@ export let FileTileStream = React.createClass({ const title = isConfirmed ? metadata.title : lbryUri; const obscureNsfw = this.props.obscureNsfw && isConfirmed && metadata.nsfw; return ( -
-
+
+
@@ -140,24 +140,139 @@ export let FileTileStream = React.createClass({ { !this.props.hidePrice ? : null} - -

+ +
+ +
+
+

+ + {isConfirmed + ? metadata.description + : This file is pending confirmation.} + +

+
+

+
+ {this.state.showNsfwHelp + ?
+

+ This content is Not Safe For Work. + To view adult content, please change your . +

+
+ : null} +
+ ); + } +}); + +export let FileCardStream = React.createClass({ + _fileInfoSubscribeId: null, + _isMounted: null, + + propTypes: { + metadata: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.object]), + outpoint: React.PropTypes.string, + hideOnRemove: React.PropTypes.bool, + hidePrice: React.PropTypes.bool, + obscureNsfw: React.PropTypes.bool + }, + getInitialState: function() { + return { + showNsfwHelp: false, + isHidden: false, + available: null, + } + }, + getDefaultProps: function() { + return { + obscureNsfw: !lbry.getClientSetting('showNsfw'), + hidePrice: false + } + }, + componentDidMount: function() { + this._isMounted = true; + if (this.props.hideOnRemove) { + this._fileInfoSubscribeId = lbry.fileInfoSubscribe(this.props.outpoint, this.onFileInfoUpdate); + } + }, + componentWillUnmount: function() { + if (this._fileInfoSubscribeId) { + lbry.fileInfoUnsubscribe(this.props.outpoint, this._fileInfoSubscribeId); + } + }, + onFileInfoUpdate: function(fileInfo) { + if (!fileInfo && this._isMounted && this.props.hideOnRemove) { + this.setState({ + isHidden: true + }); + } + }, + handleMouseOver: function() { + if (this.props.obscureNsfw && this.props.metadata && this.props.metadata.nsfw) { + this.setState({ + showNsfwHelp: true, + }); + } + }, + handleMouseOut: function() { + if (this.state.showNsfwHelp) { + this.setState({ + showNsfwHelp: false, + }); + } + }, + render: function() { + if (this.state.isHidden) { + return null; + } + + const lbryUri = uri.normalizeLbryUri(this.props.uri); + const metadata = this.props.metadata; + const isConfirmed = typeof metadata == 'object'; + const title = isConfirmed ? metadata.title : lbryUri; + const obscureNsfw = this.props.obscureNsfw && isConfirmed && metadata.nsfw; + return ( +
+
+ - -

- + hasSignature={this.props.hasSignature} signatureIsValid={this.props.signatureIsValid} /> + +

+ +
+ { !this.props.hidePrice + ? + : null} +
+ {isConfirmed - ? metadata.description - : This file is pending confirmation.} + ? metadata.description + : This file is pending confirmation.} -

+
+
+
{this.state.showNsfwHelp diff --git a/ui/js/component/form.js b/ui/js/component/form.js index 176f3334f..b33fcddd5 100644 --- a/ui/js/component/form.js +++ b/ui/js/component/form.js @@ -1,31 +1,30 @@ import React from 'react'; import {Icon} from './common.js'; -var requiredFieldWarningStyle = { - color: '#cc0000', - transition: 'opacity 400ms ease-in', -}; +var formFieldCounter = 0, + formFieldNestedLabelTypes = ['radio', 'checkbox']; -var formFieldCounter = 0; +function formFieldId() { + return "form-field-" + (++formFieldCounter); +} -var FormField = React.createClass({ +export let FormField = React.createClass({ _fieldRequiredText: 'This field is required', _type: null, _element: null, propTypes: { type: React.PropTypes.string.isRequired, - row: React.PropTypes.bool, - hidden: React.PropTypes.bool, + hasError: React.PropTypes.bool }, getInitialState: function() { return { - errorState: 'hidden', - adviceText: null, + isError: null, + errorMessage: null, } }, componentWillMount: function() { - if (['text', 'radio', 'checkbox', 'file'].includes(this.props.type)) { + if (['text', 'number', 'radio', 'checkbox', 'file'].includes(this.props.type)) { this._element = 'input'; this._type = this.props.type; } else if (this.props.type == 'text-number') { @@ -38,22 +37,11 @@ var FormField = React.createClass({ }, showError: function(text) { this.setState({ - errorState: 'shown', - adviceText: text, + isError: true, + errorMessage: text, }); - - // setTimeout(() => { - // this.setState({ - // errorState: 'fading', - // }); - // setTimeout(() => { - // this.setState({ - // errorState: 'hidden', - // }); - // }, 450); - // }, 5000); }, - warnRequired: function() { + showRequiredError: function() { this.showError(this._fieldRequiredText); }, focus: function() { @@ -74,33 +62,27 @@ var FormField = React.createClass({ render: function() { // Pass all unhandled props to the field element const otherProps = Object.assign({}, this.props), - hasError = this.state.errorState != 'hidden'; + isError = this.state.isError !== null ? this.state.isError : this.props.hasError, + elementId = this.props.id ? this.props.id : formFieldId(), + renderElementInsideLabel = this.props.label && formFieldNestedLabelTypes.includes(this.props.type); + delete otherProps.type; - delete otherProps.hidden; delete otherProps.label; - delete otherProps.row; - delete otherProps.helper; + delete otherProps.hasError; - ++formFieldCounter; - const elementId = "form-field-" + formFieldCounter + const element = + {this.props.children} + ; - if (this.props.hidden) { - return null; - } - - const field =
- { this.props.label ? -
- -
: '' - } - - {this.props.children} - - { !hasError && this.props.helper ?
{this.props.helper}
: '' } - { hasError ?
{this.state.adviceText}
: '' } + return
+ { renderElementInsideLabel ? + : element } + { isError ?
{this.state.errorMessage}
: '' }
return ( this.props.row ? @@ -108,6 +90,57 @@ var FormField = React.createClass({ field ); } -}); +}) -export default FormField; +export let FormRow = React.createClass({ + propTypes: { + label: React.PropTypes.string, + // helper: React.PropTypes.html, + }, + getValue: function() { + if (this.props.type == 'checkbox') { + return this.refs.field.checked; + } else if (this.props.type == 'file') { + return this.refs.field.files[0].path; + } else { + return this.refs.field.value; + } + }, + getInitialState: function() { + return { + isError: false, + errorMessage: null, + } + }, + showError: function(text) { + this.setState({ + isError: true, + errorMessage: text, + }); + }, + getValue: function() { + return this.refs.field.getValue(); + }, + render: function() { + const fieldProps = Object.assign({}, this.props), + elementId = formFieldId(), + renderLabelInFormField = formFieldNestedLabelTypes.includes(this.props.type); + + if (!renderLabelInFormField) { + delete fieldProps.label; + } + delete fieldProps.helper; + + return
+ { this.props.label && !renderLabelInFormField ? +
+ +
: '' } + + { !this.state.isError && this.props.helper ?
{this.props.helper}
: '' } + { this.state.isError ?
{this.state.errorMessage}
: '' } +
+ } +}) diff --git a/ui/js/component/header.js b/ui/js/component/header.js index 6e186cc44..463042cff 100644 --- a/ui/js/component/header.js +++ b/ui/js/component/header.js @@ -1,6 +1,6 @@ import React from 'react'; import {Link} from './link.js'; -import NotificationBar from './notification-bar.js'; +import {Icon} from './common.js'; var Header = React.createClass({ getInitialState: function() { @@ -53,6 +53,7 @@ var Header = React.createClass({

{ this.state.title }

+
@@ -62,7 +63,6 @@ var Header = React.createClass({ : '' } - ); } diff --git a/ui/js/component/link.js b/ui/js/component/link.js index 8a4d76f76..8bcaddd48 100644 --- a/ui/js/component/link.js +++ b/ui/js/component/link.js @@ -1,5 +1,7 @@ import React from 'react'; import {Icon} from './common.js'; +import Modal from '../component/modal.js'; +import rewards from '../rewards.js'; export let Link = React.createClass({ propTypes: { @@ -52,4 +54,76 @@ export let Link = React.createClass({ ); } +}); + +export let RewardLink = React.createClass({ + propTypes: { + type: React.PropTypes.string.isRequired, + claimed: React.PropTypes.bool, + onRewardClaim: React.PropTypes.func + }, + refreshClaimable: function() { + switch(this.props.type) { + case 'new_user': + this.setState({ claimable: true }); + return; + + case 'first_publish': + lbry.claim_list_mine().then(function(list) { + this.setState({ + claimable: list.length > 0 + }) + }.bind(this)); + return; + } + }, + componentWillMount: function() { + this.refreshClaimable(); + }, + getInitialState: function() { + return { + claimable: true, + pending: false, + errorMessage: null + } + }, + claimReward: function() { + this.setState({ + pending: true + }) + rewards.claimReward(this.props.type).then(function(reward) { + console.log(reward); + this.setState({ + pending: false, + errorMessage: null + }) + if (this.props.onRewardClaim) { + this.props.onRewardClaim(); + } + }.bind(this)).catch(function(error) { + this.setState({ + errorMessage: error.message, + pending: false + }) + }.bind(this)) + }, + clearError: function() { + this.setState({ + errorMessage: null + }) + }, + render: function() { + return ( +
+ {this.props.claimed + ? Reward claimed. + : } + {this.state.errorMessage ? + + {this.state.errorMessage} + + : ''} +
+ ); + } }); \ No newline at end of file diff --git a/ui/js/component/notification-bar.js b/ui/js/component/notification-bar.js deleted file mode 100644 index f6662b552..000000000 --- a/ui/js/component/notification-bar.js +++ /dev/null @@ -1,53 +0,0 @@ -import React from 'react'; -import lbry from '../lbry.js'; -import Notice from '../component/notice.js'; - -export const NotificationBar = React.createClass({ - _displayTime: 8, // in seconds - - _hideTimeout: null, - - getInitialState: function() { - return { - message: null, - isError: null, - } - }, - handleNoticeReceived: function(event) { - if (this._hideTimeout) { - clearTimeout(this._hideTimeout); - } - - const {detail: {message, isError}} = event; - this.setState({ - message: message, - isError: isError, - }); - - this._hideTimeout = setTimeout(() => { - this.setState({ - message: null, - isError: null, - }); - }, this._displayTime * 1000); - }, - componentWillMount: function() { - document.addEventListener('globalNotice', this.handleNoticeReceived); - }, - componentWillUnmount: function() { - document.removeEventListener('globalNotice', this.handleNoticeReceived); - }, - render: function() { - if (!this.state.message) { - return null; - } - - return ( - - {this.state.message} - - ); - }, -}); - -export default NotificationBar; \ No newline at end of file diff --git a/ui/js/component/snack-bar.js b/ui/js/component/snack-bar.js new file mode 100644 index 000000000..e1ddb01b0 --- /dev/null +++ b/ui/js/component/snack-bar.js @@ -0,0 +1,58 @@ +import React from 'react'; +import lbry from '../lbry.js'; + +export const SnackBar = React.createClass({ + + _displayTime: 5, // in seconds + + _hideTimeout: null, + + getInitialState: function() { + return { + snacks: [] + } + }, + handleSnackReceived: function(event) { + // console.log(event); + // if (this._hideTimeout) { + // clearTimeout(this._hideTimeout); + // } + + let snacks = this.state.snacks; + snacks.push(event.detail); + this.setState({ snacks: snacks}); + }, + componentWillMount: function() { + document.addEventListener('globalNotice', this.handleSnackReceived); + }, + componentWillUnmount: function() { + document.removeEventListener('globalNotice', this.handleSnackReceived); + }, + render: function() { + if (!this.state.snacks.length) { + this._hideTimeout = null; //should be unmounting anyway, but be safe? + return null; + } + + let snack = this.state.snacks[0]; + + if (this._hideTimeout === null) { + this._hideTimeout = setTimeout(function() { + this._hideTimeout = null; + let snacks = this.state.snacks; + snacks.shift(); + this.setState({ snacks: snacks }); + }.bind(this), this._displayTime * 1000); + } + + return ( +
+ {snack.message} + {snack.linkText && snack.linkTarget ? + {snack.linkText} : ''} +
+ ); + }, +}); + +export default SnackBar; \ No newline at end of file diff --git a/ui/js/component/welcome.js b/ui/js/component/welcome.js deleted file mode 100644 index 36036abe1..000000000 --- a/ui/js/component/welcome.js +++ /dev/null @@ -1,156 +0,0 @@ -import React from 'react'; -import lbryio from '../lbryio.js'; - -import ModalPage from './modal-page.js'; -import {Link} from '../component/link.js'; -import FormField from '../component/form.js'; -import Notice from '../component/notice.js' - - -const SubmitEmailStage = React.createClass({ - getInitialState: function() { - return { - rewardType: null, - email: '', - submitting: false, - errorMessage: null, - }; - }, - handleEmailChanged: function(event) { - this.setState({ - email: event.target.value, - }); - }, - handleSubmit: function(event) { - event.preventDefault(); - - this.setState({ - submitting: true, - }); - lbryio.call('user_email', 'new', {email: this.state.email}, 'post').then(() => { - this.props.onDone(); - }, (error) => { - this.setState({ - submitting: false, - errorMessage: error.message, - }); - }); - }, - render: function() { - return ( -
-

Welcome to LBRY

- {this.state.errorMessage - ? - {this.state.errorMessage} - - : null} -

Copy here explaining what we do with your email, and the reward.

-
-
Email
-
-
-
- ); - } -}); - -const ConfirmEmailStage = React.createClass({ - getInitialState: function() { - return { - rewardType: null, - code: '', - submitting: false, - errorMessage: null, - }; - }, - handleCodeChanged: function(event) { - this.setState({ - code: event.target.value, - }); - }, - handleSubmit: function(event) { - event.preventDefault(); - this.setState({ - submitting: true, - }); - - lbryio.call('user_email', 'confirm', {verification_token: this.state.code}, 'post').then(() => { - rewards.claimReward('confirm_email').then(() => { - console.log('succeeded'); - this.props.onDone(); - }, (err) => { - console.log('failed'); - this.props.onDone(); - }); - }, (error) => { - this.setState({ - submitting: false, - errorMessage: error.message, - }); - }); - }, - render: function() { - return ( -
-

Confirm Your Email Address

- {this.state.errorMessage - ? - {this.state.errorMessage} - - : null} -

Please enter your verification code to confirm your email address.

-
-
-
-
-
- ); - } -}); - -const FinalMessageStage = React.createClass({ - render: function() { - return ( -
-

Email verified

-

Text here about what happens next

-
-
- ); - } -}); - -export const Welcome = React.createClass({ - _stages: [ - SubmitEmailStage, - ConfirmEmailStage, - FinalMessageStage, - ], - propTypes: { - onDone: React.PropTypes.func.isRequired, - }, - getInitialState: function() { - return { - stageNum: 0, - }; - }, - handleStageDone: function() { - if (this.state.stageNum >= this._stages.length - 1) { - this.props.onDone(); - } else { - this.setState({ - stageNum: this.state.stageNum + 1, - }); - } - }, - render: function() { - const Content = this._stages[this.state.stageNum]; - return ( - - - - ); - } -}); - diff --git a/ui/js/lbry.js b/ui/js/lbry.js index 4d227248e..8fbfdb04d 100644 --- a/ui/js/lbry.js +++ b/ui/js/lbry.js @@ -282,29 +282,6 @@ lbry.getCostInfo = function(lbryUri, callback, errorCallback) { }); } -lbry.getFeaturedDiscoverNames = function(callback) { - return new Promise(function(resolve, reject) { - var xhr = new XMLHttpRequest; - xhr.open('GET', 'https://api.lbry.io/discover/list', true); - xhr.onload = () => { - if (xhr.status === 200) { - var responseData = JSON.parse(xhr.responseText); - if (responseData.data) //new signature, once api.lbry.io is updated - { - resolve(responseData.data); - } - else - { - resolve(responseData); - } - } else { - reject(Error('Failed to fetch featured names.')); - } - }; - xhr.send(); - }); -} - lbry.getMyClaims = function(callback) { lbry.call('get_name_claims', {}, callback); } diff --git a/ui/js/lbryio.js b/ui/js/lbryio.js index 582592b71..9c6d37f03 100644 --- a/ui/js/lbryio.js +++ b/ui/js/lbryio.js @@ -1,4 +1,4 @@ -import {getLocal, setLocal} from './utils.js'; +import {getLocal, getSession, setSession, setLocal} from './utils.js'; import lbry from './lbry.js'; const querystring = require('querystring'); @@ -20,18 +20,7 @@ const mocks = { value: 50, claimed: false, }; - }, - 'reward_type.list': () => { - return [ - { - name: 'link_github', - title: 'Link your GitHub account', - description: 'Link LBRY to your GitHub account', - value: 50, - claimed: false, - }, - ]; - }, + } }; lbryio.call = function(resource, action, params={}, method='get') { @@ -103,7 +92,7 @@ lbryio.setAccessToken = (token) => { lbryio._accessToken = token } -lbryio.authenticate = () => { +lbryio.authenticate = function() { if (lbryio._authenticationPromise === null) { lbryio._authenticationPromise = new Promise((resolve, reject) => { lbry.status().then(({installation_id}) => { @@ -117,7 +106,12 @@ lbryio.authenticate = () => { resolve(data) }).catch(function(err) { lbryio.setAccessToken(null); - reject(err); + if (!getSession('reloadedOnFailedAuth')) { + setSession('reloadedOnFailedAuth', true) + window.location.reload(); + } else { + reject(err); + } }) } diff --git a/ui/js/main.js b/ui/js/main.js index 12dee5d92..544ebb0a2 100644 --- a/ui/js/main.js +++ b/ui/js/main.js @@ -5,8 +5,8 @@ import lbryio from './lbryio.js'; import lighthouse from './lighthouse.js'; import App from './app.js'; import SplashScreen from './component/splash.js'; +import SnackBar from './component/snack-bar.js'; import {AuthOverlay} from './component/auth.js'; -import {Welcome} from './component/welcome.js'; const {remote} = require('electron'); const contextMenu = remote.require('./menu/context-menu'); @@ -28,12 +28,12 @@ let init = function() { lbryio.authenticate() //start auth process as soon as soon as we can get an install ID }) - async function onDaemonReady() { + function onDaemonReady() { window.sessionStorage.setItem('loaded', 'y'); //once we've made it here once per session, we don't need to show splash again - ReactDOM.render(
, canvas) + ReactDOM.render(
, canvas) } - if (window.sessionStorage.getItem('loaded') == 'y' && false) { + if (window.sessionStorage.getItem('loaded') == 'y') { onDaemonReady(); } else { ReactDOM.render(, canvas); diff --git a/ui/js/page/claim_code.js b/ui/js/page/claim_code.js deleted file mode 100644 index 7a9976824..000000000 --- a/ui/js/page/claim_code.js +++ /dev/null @@ -1,158 +0,0 @@ -import React from 'react'; -import lbry from '../lbry.js'; -import Modal from '../component/modal.js'; -import {Link} from '../component/link.js'; - -var claimCodeContentStyle = { - display: 'inline-block', - textAlign: 'left', - width: '600px', -}, claimCodeLabelStyle = { - display: 'inline-block', - cursor: 'default', - width: '130px', - textAlign: 'right', - marginRight: '6px', -}; - -var ClaimCodePage = React.createClass({ - getInitialState: function() { - return { - submitting: false, - modal: null, - referralCredits: null, - activationCredits: null, - failureReason: null, - } - }, - handleSubmit: function(event) { - if (typeof event !== 'undefined') { - event.preventDefault(); - } - - if (!this.refs.code.value) { - this.setState({ - modal: 'missingCode', - }); - return; - } else if (!this.refs.email.value) { - this.setState({ - modal: 'missingEmail', - }); - return; - } - - this.setState({ - submitting: true, - }); - - lbry.getUnusedAddress((address) => { - var code = this.refs.code.value; - var email = this.refs.email.value; - - var xhr = new XMLHttpRequest; - xhr.addEventListener('load', () => { - var response = JSON.parse(xhr.responseText); - - if (response.success) { - this.setState({ - 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, - modal: 'couldNotConnect', - }); - }); - - xhr.open('POST', 'https://invites.lbry.io', true); - xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); - xhr.send('code=' + encodeURIComponent(code) + '&address=' + encodeURIComponent(address) + - '&email=' + encodeURIComponent(email)); - }); - }, - handleSkip: function() { - this.setState({ - modal: 'skipped', - }); - }, - handleFinished: function() { - localStorage.setItem('claimCodeDone', true); - window.location = '?home'; - }, - closeModal: function() { - this.setState({ - modal: null, - }); - }, - render: function() { - return ( -
-
-
-

Claim your beta invitation code

-
-

Thanks for beta testing LBRY! Enter your invitation code and email address below to receive your initial - LBRY credits.

-

You will be added to our mailing list (if you're not already on it) and will be eligible for future rewards for beta testers.

-
-
-
-
-
-
- - - -
-
-
- - 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. -
-
- ); - } -}); - -export default ClaimCodePage; diff --git a/ui/js/page/developer.js b/ui/js/page/developer.js index 93eb1cc11..377204852 100644 --- a/ui/js/page/developer.js +++ b/ui/js/page/developer.js @@ -1,6 +1,6 @@ import lbry from '../lbry.js'; import React from 'react'; -import FormField from '../component/form.js'; +import {FormField} from '../component/form.js'; import {Link} from '../component/link.js'; const fs = require('fs'); diff --git a/ui/js/page/discover.js b/ui/js/page/discover.js index 993481d07..7751db2db 100644 --- a/ui/js/page/discover.js +++ b/ui/js/page/discover.js @@ -1,5 +1,6 @@ import React from 'react'; import lbry from '../lbry.js'; +import lbryio from '../lbryio.js'; import lighthouse from '../lighthouse.js'; import {FileTile} from '../component/file-tile.js'; import {Link} from '../component/link.js'; @@ -58,45 +59,44 @@ var SearchResults = React.createClass({ } }); -var featuredContentLegendStyle = { - fontSize: '12px', - color: '#aaa', - verticalAlign: '15%', -}; +const communityCategoryToolTipText = ('Community Content is a public space where anyone can share content with the ' + +'rest of the LBRY community. Bid on the names "one," "two," "three," "four" and ' + +'"five" to put your content here!'); + +var FeaturedCategory = React.createClass({ + render: function() { + return (
+ { this.props.category ? +

{this.props.category} + { this.props.category == "community" ? + + : '' }

+ : '' } + { this.props.names.map((name) => { return }) } +
) + } +}) var FeaturedContent = React.createClass({ getInitialState: function() { return { - featuredNames: [], + featuredNames: {}, }; }, componentWillMount: function() { - lbry.getFeaturedDiscoverNames().then((featuredNames) => { + lbryio.call('discover', 'list', { version: "early-access" } ).then((featuredNames) => { this.setState({ featuredNames: featuredNames }); }); }, render: function() { - const toolTipText = ('Community Content is a public space where anyone can share content with the ' + - 'rest of the LBRY community. Bid on the names "one," "two," "three," "four" and ' + - '"five" to put your content here!'); - + console.log(this.state.featuredNames); return ( -
-
-

Featured Content

- { this.state.featuredNames.map(name => ) } -
-
-

- Community Content - -

- - - - - -
+
+ { + Object.keys(this.state.featuredNames).map(function(category) { + return + }.bind(this)) + }
); } @@ -173,12 +173,12 @@ var DiscoverPage = React.createClass({ }, render: function() { - //{ !this.props.query && !this.state.searching ? : null } return (
{ this.state.searching ? : null } { !this.state.searching && this.props.query && this.state.results.length ? : null } { !this.state.searching && this.props.query && !this.state.results.length ? : null } + { !this.props.query && !this.state.searching ? : null }
); } diff --git a/ui/js/page/email.js b/ui/js/page/email.js index b7f31cd49..76b031737 100644 --- a/ui/js/page/email.js +++ b/ui/js/page/email.js @@ -1,7 +1,7 @@ import React from 'react'; import lbryio from '../lbryio.js'; import {getLocal, setLocal} from '../utils.js'; -import FormField from '../component/form.js' +import {FormField} from '../component/form.js' import {Link} from '../component/link.js' import rewards from '../rewards.js'; @@ -12,7 +12,7 @@ const EmailPage = React.createClass({ } if (!this.state.email) { - this._emailField.warnRequired(); + this._emailField.showRequiredError(); } }, componentWillMount: function() { diff --git a/ui/js/page/file-list.js b/ui/js/page/file-list.js index ba91835e7..518bb85d6 100644 --- a/ui/js/page/file-list.js +++ b/ui/js/page/file-list.js @@ -2,7 +2,7 @@ import React from 'react'; import lbry from '../lbry.js'; import uri from '../uri.js'; import {Link} from '../component/link.js'; -import FormField from '../component/form.js'; +import {FormField} from '../component/form.js'; import {FileTileStream} from '../component/file-tile.js'; import {BusyMessage, Thumbnail} from '../component/common.js'; diff --git a/ui/js/page/publish.js b/ui/js/page/publish.js index 6e0799263..4f28dc567 100644 --- a/ui/js/page/publish.js +++ b/ui/js/page/publish.js @@ -1,7 +1,7 @@ import React from 'react'; import lbry from '../lbry.js'; import uri from '../uri.js'; -import FormField from '../component/form.js'; +import {FormField, FormRow} from '../component/form.js'; import {Link} from '../component/link.js'; import Modal from '../component/modal.js'; @@ -36,7 +36,7 @@ var PublishPage = React.createClass({ for (let fieldName of checkFields) { var field = this.refs[fieldName]; if (field.getValue() === '') { - field.warnRequired(); + field.showRequiredError(); if (!missingFieldFound) { field.focus(); missingFieldFound = true; @@ -84,7 +84,7 @@ var PublishPage = React.createClass({ if (this.refs.file.getValue() !== '') { publishArgs.file_path = this.refs.file.getValue(); } - + lbry.publish(publishArgs, (message) => { this.handlePublishStarted(); }, null, (error) => { @@ -344,9 +344,12 @@ var PublishPage = React.createClass({
-

LBRY Name

-
- +
+

LBRY Name

+
+
+ What LBRY name would you like to claim for this file? .
)} /> { (!this.state.name ? null @@ -356,7 +359,6 @@ var PublishPage = React.createClass({ ? 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 {this.state.topClaimValue} {this.state.topClaimValue == 1 ? 'credit' : 'credits'}.))) } -
What LBRY name would you like to claim for this file?
@@ -381,9 +383,11 @@ var PublishPage = React.createClass({
-

Choose File

- - { this.state.myClaimExists ?
If you don't choose a file, the file from your existing claim will be used.
: null } +

Choose File

+
+ + { this.state.myClaimExists ?
If you don't choose a file, the file from your existing claim will be used.
: null } +
diff --git a/ui/js/page/referral.js b/ui/js/page/referral.js deleted file mode 100644 index 1f98e49ff..000000000 --- a/ui/js/page/referral.js +++ /dev/null @@ -1,130 +0,0 @@ -import React from 'react'; -import lbry from '../lbry.js'; -import {Link} from '../component/link.js'; -import Modal from '../component/modal.js'; - -var referralCodeContentStyle = { - display: 'inline-block', - textAlign: 'left', - width: '600px', -}, referralCodeLabelStyle = { - display: 'inline-block', - cursor: 'default', - width: '130px', - textAlign: 'right', - marginRight: '6px', -}; - -var ReferralPage = React.createClass({ - getInitialState: function() { - return { - submitting: false, - modal: null, - referralCredits: null, - failureReason: null, - } - }, - handleSubmit: function(event) { - if (typeof event !== 'undefined') { - event.preventDefault(); - } - - if (!this.refs.code.value) { - this.setState({ - modal: 'missingCode', - }); - } else if (!this.refs.email.value) { - this.setState({ - modal: 'missingEmail', - }); - } - - this.setState({ - submitting: true, - }); - - lbry.getUnusedAddress((address) => { - var code = this.refs.code.value; - var email = this.refs.email.value; - - var xhr = new XMLHttpRequest; - xhr.addEventListener('load', () => { - var response = JSON.parse(xhr.responseText); - - if (response.success) { - this.setState({ - modal: 'referralInfo', - referralCredits: response.referralCredits, - }); - } else { - this.setState({ - submitting: false, - modal: 'lookupFailed', - failureReason: response.reason, - }); - } - }); - - xhr.addEventListener('error', () => { - this.setState({ - submitting: false, - modal: 'couldNotConnect', - }); - }); - - xhr.open('POST', 'https://invites.lbry.io/check', true); - xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); - xhr.send('code=' + encodeURIComponent(code) + '&address=' + encodeURIComponent(address) + - '&email=' + encodeURIComponent(email)); - }); - }, - closeModal: function() { - this.setState({ - modal: null, - }); - }, - handleFinished: function() { - localStorage.setItem('claimCodeDone', true); - window.location = '?home'; - }, - render: function() { - return ( -
- -
-

Check your referral credits

-
-

Have you referred others to LBRY? Enter your referral code and email address below to check how many credits you've earned!

-

As a reminder, your referral code is the same as your LBRY invitation code.

-
-
-
-
-
-
- - -
-
- - - {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. - -
- ); - } -}); - -export default ReferralPage; diff --git a/ui/js/page/reward.js b/ui/js/page/reward.js index 773e21893..2fb5b3e64 100644 --- a/ui/js/page/reward.js +++ b/ui/js/page/reward.js @@ -3,124 +3,124 @@ import lbryio from '../lbryio.js'; import {Link} from '../component/link.js'; import Notice from '../component/notice.js'; import {CreditAmount} from '../component/common.js'; - -const {shell} = require('electron'); -const querystring = require('querystring'); - -const GITHUB_CLIENT_ID = '6baf581d32bad60519'; - -const LinkGithubReward = React.createClass({ - propTypes: { - onClaimed: React.PropTypes.func, - }, - _launchLinkPage: function() { - /* const githubAuthParams = { - client_id: GITHUB_CLIENT_ID, - redirect_uri: 'https://lbry.io/', - scope: 'user:email,public_repo', - allow_signup: false, - } - shell.openExternal('https://github.com/login/oauth/authorize?' + querystring.stringify(githubAuthParams)); */ - shell.openExternal('https://lbry.io'); - }, - handleConfirmClicked: function() { - this.setState({ - confirming: true, - }); - - lbry.get_new_address().then((address) => { - lbryio.call('reward', 'new', { - reward_type: 'new_developer', - access_token: '**access token here**', - wallet_address: address, - }, 'post').then((response) => { - console.log('response:', response); - - this.props.onClaimed(); // This will trigger another API call to show that we succeeded - - this.setState({ - confirming: false, - error: null, - }); - }, (error) => { - console.log('failed with error:', error); - this.setState({ - confirming: false, - error: error, - }); - }); - }); - }, - getInitialState: function() { - return { - confirming: false, - error: null, - }; - }, - render: function() { - return ( -
-

-
-

This will open a browser window where you can authorize GitHub to link your account to LBRY. This will record your email (no spam) and star the LBRY repo.

-

Once you're finished, you may confirm you've linked the account to receive your reward.

-
- {this.state.error - ? - {this.state.error.message} - - : null} - - -
- ); - } -}); - -const RewardPage = React.createClass({ - propTypes: { - name: React.PropTypes.string.isRequired, - }, - _getRewardType: function() { - lbryio.call('reward_type', 'get', this.props.name).then((rewardType) => { - this.setState({ - rewardType: rewardType, - }); - }); - }, - getInitialState: function() { - return { - rewardType: null, - }; - }, - componentWillMount: function() { - this._getRewardType(); - }, - render: function() { - if (!this.state.rewardType) { - return null; - } - - let Reward; - if (this.props.name == 'link_github') { - Reward = LinkGithubReward; - } - - const {title, description, value} = this.state.rewardType; - return ( -
-
-

{title}

- -

{this.state.rewardType.claimed - ? This reward has been claimed. - : description}

- -
-
- ); - } -}); - -export default RewardPage; +// +// const {shell} = require('electron'); +// const querystring = require('querystring'); +// +// const GITHUB_CLIENT_ID = '6baf581d32bad60519'; +// +// const LinkGithubReward = React.createClass({ +// propTypes: { +// onClaimed: React.PropTypes.func, +// }, +// _launchLinkPage: function() { +// /* const githubAuthParams = { +// client_id: GITHUB_CLIENT_ID, +// redirect_uri: 'https://lbry.io/', +// scope: 'user:email,public_repo', +// allow_signup: false, +// } +// shell.openExternal('https://github.com/login/oauth/authorize?' + querystring.stringify(githubAuthParams)); */ +// shell.openExternal('https://lbry.io'); +// }, +// handleConfirmClicked: function() { +// this.setState({ +// confirming: true, +// }); +// +// lbry.get_new_address().then((address) => { +// lbryio.call('reward', 'new', { +// reward_type: 'new_developer', +// access_token: '**access token here**', +// wallet_address: address, +// }, 'post').then((response) => { +// console.log('response:', response); +// +// this.props.onClaimed(); // This will trigger another API call to show that we succeeded +// +// this.setState({ +// confirming: false, +// error: null, +// }); +// }, (error) => { +// console.log('failed with error:', error); +// this.setState({ +// confirming: false, +// error: error, +// }); +// }); +// }); +// }, +// getInitialState: function() { +// return { +// confirming: false, +// error: null, +// }; +// }, +// render: function() { +// return ( +//
+//

+//
+//

This will open a browser window where you can authorize GitHub to link your account to LBRY. This will record your email (no spam) and star the LBRY repo.

+//

Once you're finished, you may confirm you've linked the account to receive your reward.

+//
+// {this.state.error +// ? +// {this.state.error.message} +// +// : null} +// +// +//
+// ); +// } +// }); +// +// const RewardPage = React.createClass({ +// propTypes: { +// name: React.PropTypes.string.isRequired, +// }, +// _getRewardType: function() { +// lbryio.call('reward_type', 'get', this.props.name).then((rewardType) => { +// this.setState({ +// rewardType: rewardType, +// }); +// }); +// }, +// getInitialState: function() { +// return { +// rewardType: null, +// }; +// }, +// componentWillMount: function() { +// this._getRewardType(); +// }, +// render: function() { +// if (!this.state.rewardType) { +// return null; +// } +// +// let Reward; +// if (this.props.name == 'link_github') { +// Reward = LinkGithubReward; +// } +// +// const {title, description, value} = this.state.rewardType; +// return ( +//
+//
+//

{title}

+// +//

{this.state.rewardType.claimed +// ? This reward has been claimed. +// : description}

+// +//
+//
+// ); +// } +// }); +// +// export default RewardPage; diff --git a/ui/js/page/rewards.js b/ui/js/page/rewards.js index 52f705035..62c5d0497 100644 --- a/ui/js/page/rewards.js +++ b/ui/js/page/rewards.js @@ -1,28 +1,34 @@ import React from 'react'; import lbry from '../lbry.js'; import lbryio from '../lbryio.js'; -import {CreditAmount} from '../component/common.js'; +import {CreditAmount, Icon} from '../component/common.js'; +import rewards from '../rewards.js'; import Modal from '../component/modal.js'; -import {Link} from '../component/link.js'; +import {RewardLink} from '../component/link.js'; const RewardTile = React.createClass({ propTypes: { - name: React.PropTypes.string.isRequired, + type: React.PropTypes.string.isRequired, title: React.PropTypes.string.isRequired, description: React.PropTypes.string.isRequired, claimed: React.PropTypes.bool.isRequired, value: React.PropTypes.number.isRequired, + onRewardClaim: React.PropTypes.func }, render: function() { return (
-
-

- -
{this.props.description}
- {this.props.claimed - ? This reward has been claimed. - : } +
+
+ +

{this.props.title}

+
+
+ {this.props.claimed + ? Reward claimed. + : } +
+
{this.props.description}
); @@ -31,29 +37,29 @@ const RewardTile = React.createClass({ var RewardsPage = React.createClass({ componentWillMount: function() { - lbryio.call('reward_type', 'list', {}).then((rewardTypes) => { - this.setState({ - rewardTypes: rewardTypes, - }); - }); + this.loadRewards() }, getInitialState: function() { return { - rewardTypes: null, + userRewards: null, }; }, + loadRewards: function() { + lbryio.call('reward', 'list', {}).then((userRewards) => { + this.setState({ + userRewards: userRewards, + }); + }); + }, render: function() { return (
-
-

Rewards

- {!this.state.rewardTypes - ? null - : this.state.rewardTypes.map(({name, title, description, claimed, value}) => { - return ; - })} -
+ {!this.state.userRewards + ? null + : this.state.userRewards.map(({RewardType, RewardTitle, RewardDescription, TransactionID, RewardAmount}) => { + return ; + })}
); diff --git a/ui/js/page/settings.js b/ui/js/page/settings.js index 508a8a84d..c278741b4 100644 --- a/ui/js/page/settings.js +++ b/ui/js/page/settings.js @@ -1,21 +1,7 @@ import React from 'react'; +import {FormField, FormRow} from '../component/form.js'; import lbry from '../lbry.js'; -var settingsRadioOptionStyles = { - display: 'block', - marginLeft: '13px' -}, settingsCheckBoxOptionStyles = { - display: 'block', - marginLeft: '13px' -}, settingsNumberFieldStyles = { - width: '40px' -}, downloadDirectoryLabelStyles = { - fontSize: '.9em', - marginLeft: '13px' -}, downloadDirectoryFieldStyles= { - width: '300px' -}; - var SettingsPage = React.createClass({ onRunOnStartChange: function (event) { lbry.setDaemonSetting('run_on_startup', event.target.checked); @@ -81,29 +67,54 @@ var SettingsPage = React.createClass({ return (
-

Run on Startup

- -
-
-

Download Directory

-
Where would you like the files you download from LBRY to be saved?
- -
-
-

Bandwidth Limits

-
-

Max Upload

- - +
+

Run on Startup

-
+
+ +
+
+
+
+

Download Directory

+
+
+ +
+
+
+
+

Bandwidth Limits

+
+
+

Max Upload

+ + + { this.state.isMaxUpload ? + : '' + } +
+

Max Download

-

Content

-
- -
- NSFW content may include nudity, intense sexuality, profanity, or other adult content. - By displaying NSFW content, you are affirming you are of legal age to view mature content in your country or jurisdiction. -
+
+

Content

+
+
+ +
+
+
-

Search

-
-
- Would you like search results to include items that are not currently available for download? +
+

Share Diagnostic Data

- +
+
-
-

Share Diagnostic Data

- -
); } }); +/* + +
+

Search

+
+
+ Would you like search results to include items that are not currently available for download? +
+ +
+
+
+

Share Diagnostic Data

+ +
+ */ export default SettingsPage; diff --git a/ui/js/page/wallet.js b/ui/js/page/wallet.js index 2ace64c27..fd68345cb 100644 --- a/ui/js/page/wallet.js +++ b/ui/js/page/wallet.js @@ -2,12 +2,9 @@ import React from 'react'; import lbry from '../lbry.js'; import {Link} from '../component/link.js'; import Modal from '../component/modal.js'; +import {FormField, FormRow} from '../component/form.js'; import {Address, BusyMessage, CreditAmount} from '../component/common.js'; - -var addressRefreshButtonStyle = { - fontSize: '11pt', -}; var AddressSection = React.createClass({ _refreshAddress: function(event) { if (typeof event !== 'undefined') { @@ -27,12 +24,12 @@ var AddressSection = React.createClass({ event.preventDefault(); } - lbry.getNewAddress((address) => { + lbry.wallet_new_address().then((address) => { window.localStorage.setItem('wallet_address', address); this.setState({ address: address, }); - }); + }.bind(this)); }, getInitialState: function() { @@ -60,12 +57,20 @@ var AddressSection = React.createClass({ render: function() { return (
-

Wallet Address

-
- -
-

Other LBRY users may send credits to you by entering this address on the "Send" page.

- You can generate a new address at any time, and any previous addresses will continue to work. Using multiple addresses can be helpful for keeping track of incoming payments from multiple sources. +
+

Wallet Address

+
+
+
+
+
+ +
+
+
+

Other LBRY users may send credits to you by entering this address on the "Send" page.

+

You can generate a new address at any time, and any previous addresses will continue to work. Using multiple addresses can be helpful for keeping track of incoming payments from multiple sources.

+
); @@ -143,27 +148,26 @@ var SendToAddressSection = React.createClass({ return (
-

Send Credits

-
- - +
+

Send Credits

-
- - +
+
-
+
+ +
+
0.0) || this.state.address == ""} />
- { - this.state.results ? -
-

Results

- {this.state.results} -
- : '' - } + { + this.state.results ? +
+

Results

+ {this.state.results} +
: '' + } @@ -231,25 +235,29 @@ var TransactionList = React.createClass({ } return (
-

Transaction History

- { this.state.transactionItems === null ? : '' } - { this.state.transactionItems && rows.length === 0 ?
You have no transactions.
: '' } - { this.state.transactionItems && rows.length > 0 ? - - - - - - - - - - - {rows} - -
AmountDateTimeTransaction
+
+

Transaction History

+
+
+ { this.state.transactionItems === null ? : '' } + { this.state.transactionItems && rows.length === 0 ?
You have no transactions.
: '' } + { this.state.transactionItems && rows.length > 0 ? + + + + + + + + + + + {rows} + +
AmountDateTimeTransaction
: '' - } + } +
); } @@ -290,9 +298,13 @@ var WalletPage = React.createClass({ return (
-

Balance

- { this.state.balance === null ? : ''} - { this.state.balance !== null ? : '' } +
+

Balance

+
+
+ { this.state.balance === null ? : ''} + { this.state.balance !== null ? : '' } +
{ this.props.viewingPage === 'wallet' ? : '' } { this.props.viewingPage === 'send' ? : '' } diff --git a/ui/js/rewards.js b/ui/js/rewards.js index 4310b18b8..29f43058c 100644 --- a/ui/js/rewards.js +++ b/ui/js/rewards.js @@ -1,11 +1,14 @@ import lbry from './lbry.js'; import lbryio from './lbryio.js'; -const MESSAGES = { - new_developer: "Your reward has been confirmed for registering as a new developer.", - confirm_email: "Your reward has been confirmed for verifying your email address.", - first_publish: "Your reward has been confirmed for making your first publication.", -}; +function rewardMessage(type, amount) { + return { + new_developer: "Your reward has been confirmed for registering as a new developer.", + new_user: `You received ${amount} LBC new user reward.`, + confirm_email: "Your reward has been confirmed for verifying your email address.", + first_publish: "Your reward has been confirmed for making your first publication.", + }[type]; +} const rewards = {}; @@ -13,22 +16,25 @@ rewards.claimReward = function(type) { return new Promise((resolve, reject) => { console.log('top of promise body') lbry.get_new_address().then((address) => { - console.log('top of get_new_address') const params = { reward_type: type, wallet_address: address, }; lbryio.call('reward', 'new', params, 'post').then(({RewardAmount}) => { - const result = { - type: type, - amount: RewardAmount, - message: MESSAGES[type], - }; + const + message = rewardMessage(type, RewardAmount), + result = { + type: type, + amount: RewardAmount, + message: message + }; // Display global notice document.dispatchEvent(new CustomEvent('globalNotice', { detail: { - message: MESSAGES[type], + message: message, + linkText: "Show All", + linkTarget: "?rewards", isError: false, }, })); @@ -36,16 +42,7 @@ rewards.claimReward = function(type) { // Add more events here to display other places resolve(result); - }, (error) => { - document.dispatchEvent(new CustomEvent('globalNotice', { - detail: { - message: `Failed to claim reward: ${error.message}`, - isError: true, - }, - })); - document.dispatchEvent(new CustomEvent('rewardFailed', error)); - reject(error); - }); + }, reject); }); }); } diff --git a/ui/js/utils.js b/ui/js/utils.js index 290a0f54e..e9472a6a4 100644 --- a/ui/js/utils.js +++ b/ui/js/utils.js @@ -12,4 +12,20 @@ export function getLocal(key) { */ export function setLocal(key, value) { localStorage.setItem(key, JSON.stringify(value)); +} + +/** + * Thin wrapper around localStorage.getItem(). Parses JSON and returns undefined if the value + * is not set yet. + */ +export function getSession(key) { + const itemRaw = sessionStorage.getItem(key); + return itemRaw === null ? undefined : JSON.parse(itemRaw); +} + +/** + * Thin wrapper around localStorage.setItem(). Converts value to JSON. + */ +export function setSession(key, value) { + sessionStorage.setItem(key, JSON.stringify(value)); } \ No newline at end of file diff --git a/ui/scss/_canvas.scss b/ui/scss/_canvas.scss index a5082d0d9..300ecb75b 100644 --- a/ui/scss/_canvas.scss +++ b/ui/scss/_canvas.scss @@ -60,6 +60,11 @@ $drawer-width: 240px; text-align: center; } +#window +{ + position: relative; /*window has it's own z-index inside of it*/ + z-index: 1; +} #window.drawer-closed { #drawer { display: none } @@ -100,12 +105,28 @@ $drawer-width: 240px; .header-search { margin-left: 60px; + $padding-adjust: 36px; text-align: center; + .icon { + position: absolute; + top: $spacing-vertical * 1.5 / 2 + 2px; //hacked + margin-left: -$padding-adjust + 14px; //hacked + } input[type="search"] { + position: relative; + left: -$padding-adjust; background: rgba(255, 255, 255, 0.3); color: white; width: 400px; + height: $spacing-vertical * 1.5; + line-height: $spacing-vertical * 1.5; + padding-left: $padding-adjust + 3; + padding-right: 3px; + @include border-radius(2px); @include placeholder-color(#e8e8e8); + &:focus { + box-shadow: $focus-box-shadow; + } } } @@ -159,26 +180,6 @@ nav.sub-header { padding: $spacing-vertical; } - h2 - { - margin-bottom: $spacing-vertical; - } - h3, h4 - { - margin-bottom: $spacing-vertical / 2; - margin-top: $spacing-vertical; - &:first-child - { - margin-top: 0; - } - } - .meta - { - + h2, + h3, + h4 - { - margin-top: 0; - } - } } $header-icon-size: 1.5em; @@ -197,48 +198,6 @@ $header-icon-size: 1.5em; padding: 0 6px 0 18px; } -.card { - margin-left: auto; - margin-right: auto; - max-width: 800px; - padding: $spacing-vertical; - background: $color-bg; - box-shadow: $default-box-shadow; - border-radius: 2px; -} -.card-obscured -{ - position: relative; -} -.card-obscured .card-content { - -webkit-filter: blur($blur-intensity); - -moz-filter: blur($blur-intensity); - -o-filter: blur($blur-intensity); - -ms-filter: blur($blur-intensity); - filter: blur($blur-intensity); -} -.card-overlay { - position: absolute; - left: 0px; - right: 0px; - top: 0px; - bottom: 0px; - padding: 20px; - background-color: rgba(128, 128, 128, 0.8); - color: #fff; - display: flex; - align-items: center; - font-weight: 600; -} - -.card-series-submit -{ - margin-left: auto; - margin-right: auto; - max-width: 800px; - padding: $spacing-vertical / 2; -} - .full-screen { width: 100%; diff --git a/ui/scss/_form.scss b/ui/scss/_form.scss deleted file mode 100644 index c54343c4a..000000000 --- a/ui/scss/_form.scss +++ /dev/null @@ -1,66 +0,0 @@ -@import "global"; - -/* this probably shouldn't exist but it does so here we are - Jeremy */ - - -textarea, -select, -input[type="text"], -input[type="password"], -input[type="email"], -input[type="number"], -input[type="search"], -input[type="date"] { - @include placeholder { - color: lighten($color-text-dark, 60%); - } - transition: all $transition-standard; - cursor: pointer; - padding-left: 1px; - padding-right: 1px; - box-sizing: border-box; - -webkit-appearance: none; - &[readonly] { - background-color: #bbb; - } -} - -input[type="text"], -input[type="password"], -input[type="email"], -input[type="number"], -input[type="search"], -input[type="date"] { - border-bottom: 2px solid $color-form-border; - line-height: $spacing-vertical - 4; - height: $spacing-vertical * 1.5; - &.form-field__input--error { - border-color: $color-error; - } -} - -textarea:focus, -input[type="text"]:focus, -input[type="password"]:focus, -input[type="email"]:focus, -input[type="number"]:focus, -input[type="search"]:focus, -input[type="date"]:focus { - border-color: $color-primary; -} - -textarea { - border: 2px solid $color-form-border; -} - -.form-row -{ - + .form-row - { - margin-top: $spacing-vertical; - } - + .form-row-submit - { - margin-top: $spacing-vertical; - } -} \ No newline at end of file diff --git a/ui/scss/_global.scss b/ui/scss/_global.scss index 7d3249888..b203e7d18 100644 --- a/ui/scss/_global.scss +++ b/ui/scss/_global.scss @@ -6,8 +6,10 @@ $padding-button: 12px; $padding-text-link: 4px; $color-primary: #155B4A; +$color-primary-light: saturate(lighten($color-primary, 50%), 20%); $color-light-alt: hsl(hue($color-primary), 15, 85); $color-text-dark: #000; +$color-black-transparent: rgba(32,32,32,0.9); $color-help: rgba(0,0,0,.6); $color-notice: #8a6d3b; $color-error: #a94442; @@ -30,7 +32,7 @@ $height-header: $spacing-vertical * 2.5; $height-button: $spacing-vertical * 1.5; $default-box-shadow: 0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12); -//$focus-box-shadow: 2px 4px 4px 0 rgba(0,0,0,.14),2px 5px 3px -2px rgba(0,0,0,.2),2px 3px 7px 0 rgba(0,0,0,.12); +$focus-box-shadow: 2px 4px 4px 0 rgba(0,0,0,.14),2px 5px 3px -2px rgba(0,0,0,.2),2px 3px 7px 0 rgba(0,0,0,.12); $transition-standard: .225s ease; diff --git a/ui/scss/_gui.scss b/ui/scss/_gui.scss index 691ccb328..6c97b5fbc 100644 --- a/ui/scss/_gui.scss +++ b/ui/scss/_gui.scss @@ -38,6 +38,7 @@ text-align: center; } +/* section { margin-bottom: $spacing-vertical; @@ -46,10 +47,10 @@ section margin-bottom: 0; } &:only-child { - /* If it's an only child, assume it's part of a React layout that will handle the last child condition on its own */ margin-bottom: $spacing-vertical; } } +*/ main h1 { font-size: 2.0em; @@ -178,7 +179,8 @@ p background: rgb(255, 255, 255); overflow: auto; border-radius: 4px; - padding: 36px; + padding: $spacing-vertical; + box-shadow: $default-box-shadow; max-width: 250px; } diff --git a/ui/scss/_reset.scss b/ui/scss/_reset.scss index 8e0db2623..e951875a8 100644 --- a/ui/scss/_reset.scss +++ b/ui/scss/_reset.scss @@ -7,6 +7,10 @@ body, div, dl, dt, dd, ul, ol, li, h1, h2, h3, h4, h5, h6, pre, code, form, fiel { outline: 0; } +input::-webkit-search-cancel-button { + /* Remove default */ + -webkit-appearance: none; +} table { border-collapse: collapse; diff --git a/ui/scss/all.scss b/ui/scss/all.scss index 3c28c011c..18a9ec720 100644 --- a/ui/scss/all.scss +++ b/ui/scss/all.scss @@ -1,12 +1,12 @@ @import "_reset"; @import "_grid"; @import "_icons"; -@import "_form"; @import "_mediaelement"; @import "_canvas"; @import "_gui"; @import "component/_table"; @import "component/_button.scss"; +@import "component/_card.scss"; @import "component/_file-actions.scss"; @import "component/_file-tile.scss"; @import "component/_form-field.scss"; @@ -16,7 +16,7 @@ @import "component/_channel-indicator.scss"; @import "component/_notice.scss"; @import "component/_modal-page.scss"; -@import "component/_notification-bar.scss"; +@import "component/_snack-bar.scss"; @import "page/_developer.scss"; @import "page/_watch.scss"; @import "page/_reward.scss"; diff --git a/ui/scss/component/_card.scss b/ui/scss/component/_card.scss new file mode 100644 index 000000000..af42624b2 --- /dev/null +++ b/ui/scss/component/_card.scss @@ -0,0 +1,122 @@ +@import "../global"; + +$padding-card-horizontal: $spacing-vertical * 2/3; + +.card { + margin-left: auto; + margin-right: auto; + max-width: 800px; + background: $color-bg; + box-shadow: $default-box-shadow; + border-radius: 2px; + margin-bottom: $spacing-vertical * 2/3; + overflow: auto; +} +.card--obscured +{ + position: relative; +} +.card--obscured .card__inner { + -webkit-filter: blur($blur-intensity); + -moz-filter: blur($blur-intensity); + -o-filter: blur($blur-intensity); + -ms-filter: blur($blur-intensity); + filter: blur($blur-intensity); +} +.card__title-primary { + padding: 0 $padding-card-horizontal; + margin-top: $spacing-vertical; +} +.card__title-identity { + padding: 0 $padding-card-horizontal; + margin-top: $spacing-vertical * 1/3; + margin-bottom: $spacing-vertical * 1/3; +} +.card__actions { + padding: 0 $padding-card-horizontal; +} +.card__actions { + margin-top: $spacing-vertical * 2/3; +} +.card__actions--bottom { + margin-top: $spacing-vertical * 1/3; + margin-bottom: $spacing-vertical * 1/3; +} +.card__actions--form-submit { + margin-top: $spacing-vertical; + margin-bottom: $spacing-vertical * 2/3; +} +.card__content { + margin-top: $spacing-vertical * 2/3; + margin-bottom: $spacing-vertical * 2/3; + padding: 0 $padding-card-horizontal; +} +.card__subtext { + color: #444; + margin-top: 12px; + font-size: 0.9em; + margin-top: $spacing-vertical * 2/3; + margin-bottom: $spacing-vertical * 2/3; + padding: 0 $padding-card-horizontal; +} +.card__subtext--two-lines { + height: $font-size * 0.9 * $font-line-height * 2; +} +.card-overlay { + position: absolute; + left: 0px; + right: 0px; + top: 0px; + bottom: 0px; + padding: 20px; + background-color: rgba(128, 128, 128, 0.8); + color: #fff; + display: flex; + align-items: center; + font-weight: 600; +} + +.card__media img { + max-width: 100%;; + display: block; + margin-left: auto; + margin-right: auto; +} + +$width-card-small: $spacing-vertical * 12; +.card--small { + width: $width-card-small; +} +.card--small .card__media { + max-height: $width-card-small * 9 / 16; + img { + max-height: $width-card-small * 9 / 16; + } +} + +.card__subtitle { + color: $color-meta-light; + font-size: 0.85em; +} + +.card-series-submit +{ + margin-left: auto; + margin-right: auto; + max-width: 800px; + padding: $spacing-vertical / 2; +} + +.card-row { + > .card { + vertical-align: top; + display: inline-block; + margin-right: $spacing-vertical / 3; + } + + .card-row { + margin-top: $spacing-vertical * 1/3; + } +} +.card-row__header { + margin-bottom: $spacing-vertical / 3; +} \ No newline at end of file diff --git a/ui/scss/component/_file-tile.scss b/ui/scss/component/_file-tile.scss index a5c73a175..1f4b463b8 100644 --- a/ui/scss/component/_file-tile.scss +++ b/ui/scss/component/_file-tile.scss @@ -2,10 +2,10 @@ .file-tile__row { height: $spacing-vertical * 7; -} -.file-tile__row--unavailable { - opacity: 0.5; + .file-price { + float: right; + } } .file-tile__thumbnail { @@ -20,10 +20,6 @@ font-weight: bold; } -.file-tile__cost { - float: right; -} - .file-tile__description { color: #444; margin-top: 12px; diff --git a/ui/scss/component/_form-field.scss b/ui/scss/component/_form-field.scss index db2d168e0..3f22a9af2 100644 --- a/ui/scss/component/_form-field.scss +++ b/ui/scss/component/_form-field.scss @@ -1,14 +1,88 @@ @import "../global"; -.form-field { - display: inline-block; +.form-row-submit +{ + margin-top: $spacing-vertical; } -.form-field__label { +$height-input: $spacing-vertical * 1.5; + +.form-row__label-row { margin-top: $spacing-vertical * 2/3; margin-bottom: $spacing-vertical * 1/3; line-height: 1; } +.form-row__label-row--prefix { + float: left; + margin-right: 5px; + line-height: $height-input; +} + +.form-field { + display: inline-block; + + input[type="checkbox"], + input[type="radio"] { + cursor: pointer; + } + + textarea, + select, + input[type="text"], + input[type="password"], + input[type="email"], + input[type="number"], + input[type="search"], + input[type="date"] { + @include placeholder { + color: lighten($color-text-dark, 60%); + } + transition: all $transition-standard; + cursor: pointer; + padding-left: 1px; + padding-right: 1px; + box-sizing: border-box; + -webkit-appearance: none; + &[readonly] { + background-color: #bbb; + } + } + + input[type="text"], + input[type="password"], + input[type="email"], + input[type="number"], + input[type="search"], + input[type="date"] { + border-bottom: 2px solid $color-form-border; + line-height: $spacing-vertical - 4; + height: $height-input; + &.form-field__input--error { + border-color: $color-error; + } + } + + textarea:focus, + input[type="text"]:focus, + input[type="password"]:focus, + input[type="email"]:focus, + input[type="number"]:focus, + input[type="search"]:focus, + input[type="date"]:focus { + border-color: $color-primary; + } + + textarea { + border: 2px solid $color-form-border; + } +} + +.form-field__label { + &[for] { cursor: pointer; } + > input[type="checkbox"], input[type="radio"] { + margin-right: 6px; + } +} .form-field__label--error { color: $color-error; @@ -18,8 +92,8 @@ width: 330px; } -.form-field__input-text-number { - width: 50px; +.form-field__input-number { + width: 100px; } .form-field__error, .form-field__helper { diff --git a/ui/scss/component/_modal-page.scss b/ui/scss/component/_modal-page.scss index 5c56e1ad3..2b86c5cad 100644 --- a/ui/scss/component/_modal-page.scss +++ b/ui/scss/component/_modal-page.scss @@ -19,14 +19,16 @@ justify-content: center; align-items: center; + border: 1px solid rgb(204, 204, 204); + background: rgb(255, 255, 255); + overflow: auto; +} + +.modal-page--full { left: 0; right: 0; top: 0; bottom: 0; - - border: 1px solid rgb(204, 204, 204); - background: rgb(255, 255, 255); - overflow: auto; } /* diff --git a/ui/scss/component/_notification-bar.scss b/ui/scss/component/_notification-bar.scss deleted file mode 100644 index 2f9959f94..000000000 --- a/ui/scss/component/_notification-bar.scss +++ /dev/null @@ -1,6 +0,0 @@ -@import "../global"; - -.notification-bar { - margin-top: 5px; - margin-right: 10px; -} diff --git a/ui/scss/component/_snack-bar.scss b/ui/scss/component/_snack-bar.scss new file mode 100644 index 000000000..c3df3ab92 --- /dev/null +++ b/ui/scss/component/_snack-bar.scss @@ -0,0 +1,42 @@ +@import "../global"; + +$padding-snack-horizontal: $spacing-vertical; + +.snack-bar { + $height-snack: $spacing-vertical * 2; + $padding-snack-vertical: $spacing-vertical / 4; + + line-height: $height-snack - $padding-snack-vertical * 2; + padding: $padding-snack-vertical $padding-snack-horizontal; + position: fixed; + top: $spacing-vertical; + left: 0; + right: 0; + margin-left: auto; + margin-right: auto; + min-width: 300px; + max-width: 500px; + background: $color-black-transparent; + color: #f0f0f0; + + display: flex; + justify-content: space-between; + align-items: center; + + border-radius: 2px; + + transition: all $transition-standard; + + z-index: 10000; /*hack to get it over react modal */ +} + +.snack-bar__action { + display: inline-block; + text-transform: uppercase; + color: $color-primary-light; + margin: 0px 0px 0px $padding-snack-horizontal; + min-width: min-content; + &:hover { + text-decoration: underline; + } +}