diff --git a/ui/js/actions/app.js b/ui/js/actions/app.js
index 709d85ffb..1454f8d2c 100644
--- a/ui/js/actions/app.js
+++ b/ui/js/actions/app.js
@@ -9,7 +9,18 @@ import {
selectCurrentPage,
selectCurrentParams,
} from "selectors/app";
-import { doSearch } from "actions/search";
+import {
+ doSearch,
+} from 'actions/search'
+import {
+ doFetchDaemonSettings
+} from 'actions/settings'
+import {
+ doAuthenticate
+} from 'actions/user'
+import {
+ doFileList
+} from 'actions/file_info'
const { remote, ipcRenderer, shell } = require("electron");
const path = require("path");
@@ -216,9 +227,15 @@ export function doAlertError(errorList) {
}
export function doDaemonReady() {
- return {
- type: types.DAEMON_READY,
- };
+ return function(dispatch, getState) {
+ dispatch({
+ type: types.DAEMON_READY
+ })
+ dispatch(doAuthenticate())
+ dispatch(doChangePath('/discover'))
+ dispatch(doFetchDaemonSettings())
+ dispatch(doFileList())
+ }
}
export function doShowSnackBar(data) {
diff --git a/ui/js/actions/user.js b/ui/js/actions/user.js
new file mode 100644
index 000000000..d616c3ac4
--- /dev/null
+++ b/ui/js/actions/user.js
@@ -0,0 +1,21 @@
+import * as types from 'constants/action_types'
+import lbryio from 'lbryio'
+
+export function doAuthenticate() {
+ return function(dispatch, getState) {
+ dispatch({
+ type: types.AUTHENTICATION_STARTED,
+ })
+ lbryio.authenticate().then((user) => {
+ dispatch({
+ type: types.AUTHENTICATION_SUCCESS,
+ data: { user }
+ })
+ }).catch((error) => {
+ dispatch({
+ type: types.AUTHENTICATION_FAILURE,
+ data: { error }
+ })
+ })
+ }
+}
\ No newline at end of file
diff --git a/ui/js/component/authOverlay/index.jsx b/ui/js/component/authOverlay/index.jsx
new file mode 100644
index 000000000..7e08a5ef9
--- /dev/null
+++ b/ui/js/component/authOverlay/index.jsx
@@ -0,0 +1,21 @@
+import React from 'react'
+import {
+ connect
+} from 'react-redux'
+import {
+ doStartUpgrade,
+ doCancelUpgrade,
+} from 'actions/app'
+import {
+ selectAuthenticationIsPending,
+} from 'selectors/user'
+import AuthOverlay from './view'
+
+const select = (state) => ({
+ isPending: selectAuthenticationIsPending(state)
+})
+
+const perform = (dispatch) => ({
+})
+
+export default connect(select, perform)(AuthOverlay)
diff --git a/ui/js/component/authOverlay/view.jsx b/ui/js/component/authOverlay/view.jsx
new file mode 100644
index 000000000..20025820a
--- /dev/null
+++ b/ui/js/component/authOverlay/view.jsx
@@ -0,0 +1,350 @@
+import React from "react";
+import lbry from "lbry.js";
+import lbryio from "lbryio.js";
+import Modal from "component/modal.js";
+import ModalPage from "component/modal-page.js";
+import Link from "component/link"
+import {BusyMessage} from "component/common"
+import {RewardLink} from 'component/reward-link';
+import {FormRow} from "component/form.js";
+import {CreditAmount, Address} from "component/common.js";
+import {getLocal, setLocal} from 'utils.js';
+import rewards from 'rewards'
+
+
+class SubmitEmailStage extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ rewardType: null,
+ email: '',
+ showNoEmailConfirm: false,
+ submitting: false
+ };
+ }
+
+ handleEmailChanged(event) {
+ this.setState({
+ email: event.target.value,
+ });
+ }
+
+ onEmailSaved(email) {
+ this.props.setStage("confirm", { email: email })
+ }
+
+ onEmailSkipClick() {
+ this.setState({ showNoEmailConfirm: true })
+ }
+
+ onEmailSkipConfirm() {
+ setLocal('auth_bypassed', true);
+ this.props.setStage(null)
+ }
+
+ handleSubmit(event) {
+ event.preventDefault();
+
+ this.setState({
+ submitting: true,
+ });
+ lbryio.call('user_email', 'new', {email: this.state.email}, 'post').then(() => {
+ this.onEmailSaved(this.state.email);
+ }, (error) => {
+ if (error.xhr && (error.xhr.status == 409 || error.message == "This email is already in use")) {
+ this.onEmailSaved(this.state.email);
+ return;
+ } else if (this._emailRow) {
+ this._emailRow.showError(error.message)
+ }
+ this.setState({ submitting: false });
+ });
+ }
+
+ render() {
+ return (
+
+ );
+ }
+}
+
+class ConfirmEmailStage extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ rewardType: null,
+ code: '',
+ submitting: false,
+ errorMessage: null,
+ };
+ }
+
+ handleCodeChanged(event) {
+ this.setState({
+ code: event.target.value,
+ });
+ }
+
+ handleSubmit(event) {
+ event.preventDefault();
+ this.setState({
+ submitting: true,
+ });
+
+ const onSubmitError = (error) => {
+ if (this._codeRow) {
+ this._codeRow.showError(error.message)
+ }
+ this.setState({ submitting: false });
+ };
+
+ lbryio.call('user_email', 'confirm', {verification_token: this.state.code, email: this.props.email}, 'post').then((userEmail) => {
+ if (userEmail.is_verified) {
+ this.props.setStage("welcome")
+ } else {
+ onSubmitError(new Error("Your email is still not verified.")) //shouldn't happen?
+ }
+ }, onSubmitError);
+ }
+
+ render() {
+ return (
+
+ );
+ }
+}
+
+class WelcomeStage extends React.Component {
+ static propTypes = {
+ endAuth: React.PropTypes.func,
+ }
+
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ hasReward: false,
+ rewardAmount: null,
+ };
+ }
+
+ onRewardClaim(reward) {
+ this.setState({
+ hasReward: true,
+ rewardAmount: reward.amount
+ })
+ }
+
+ render() {
+ return (
+ !this.state.hasReward ?
+
+
+ Welcome to LBRY.
+ Using LBRY is like dating a centaur. Totally normal up top, and way different underneath.
+ Up top, LBRY is similar to popular media sites.
+ Below, LBRY is controlled by users -- you -- via blockchain and decentralization.
+ Thank you for making content freedom possible! Here's a nickel, kid.
+
+ { this.onRewardClaim(event) }} onRewardFailure={() => this.props.setStage(null)} onConfirmed={() => { this.props.setStage(null) }} />
+
+
+ :
+ { this.props.setStage(null) }}>
+
+ About Your Reward
+ You earned a reward of LBRY credits, or LBC.
+ This reward will show in your Wallet momentarily, probably while you are reading this message.
+ LBC is used to compensate creators, to publish, and to have say in how the network works.
+ No need to understand it all just yet! Try watching or downloading something next.
+ Finally, know that LBRY is an early beta and that it earns the name.
+
+
+ );
+ }
+}
+
+const ErrorStage = (props) => {
+ return
+ An error was encountered that we cannot continue from.
+ At least we're earning the name beta.
+ { props.errorText ? Message: {props.errorText}
: '' }
+ { window.location.reload() } } />
+
+}
+
+const PendingStage = (props) => {
+ return
+}
+
+
+class CodeRequiredStage extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this._balanceSubscribeId = null
+
+ this.state = {
+ balance: 0,
+ address: getLocal('wallet_address')
+ };
+ }
+
+ componentWillMount() {
+ this._balanceSubscribeId = lbry.balanceSubscribe((balance) => {
+ this.setState({
+ balance: balance
+ });
+ })
+
+ if (!this.state.address) {
+ lbry.wallet_unused_address().then((address) => {
+ setLocal('wallet_address', address);
+ this.setState({ address: address });
+ });
+ }
+ }
+
+ componentWillUnmount() {
+ if (this._balanceSubscribeId) {
+ lbry.balanceUnsubscribe(this._balanceSubscribeId)
+ }
+ }
+
+ render() {
+ const disabled = this.state.balance < 1;
+ return (
+
+
+ Early access to LBRY is restricted as we build and scale the network.
+ There are two ways in.
+ Own LBRY Credits
+ If you own at least 1 LBC, you can get in right now.
+ { setLocal('auth_bypassed', true); this.props.setStage(null); }}
+ disabled={disabled} label="Let Me In" button={ disabled ? "alt" : "primary" } />
+ Your balance is . To increase your balance, send credits to this address:
+
+ If you don't understand how to send credits, then...
+
+
+ Wait For A Code
+ If you provide your email, you'll automatically receive a notification when the system is open.
+ { this.props.setStage("email"); }} label="Return" />
+
+
+ );
+ }
+}
+
+
+export class AuthOverlay extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this._stages = {
+ pending: PendingStage,
+ error: ErrorStage,
+ nocode: CodeRequiredStage,
+ email: SubmitEmailStage,
+ confirm: ConfirmEmailStage,
+ welcome: WelcomeStage
+ }
+ }
+
+ setStage(stage, stageProps = {}) {
+ this.setState({
+ stage: stage,
+ stageProps: stageProps
+ })
+ }
+
+ componentWillMount() {
+ // lbryio.authenticate().then((user) => {
+ // if (!user.has_verified_email) {
+ // if (getLocal('auth_bypassed')) {
+ // this.setStage(null)
+ // } else {
+ // this.setStage("email", {})
+ // }
+ // } else {
+ // lbryio.call('reward', 'list', {}).then((userRewards) => {
+ // userRewards.filter(function(reward) {
+ // return reward.reward_type == rewards.TYPE_NEW_USER && reward.transaction_id;
+ // }).length ?
+ // this.setStage(null) :
+ // this.setStage("welcome")
+ // });
+ // }
+ // }).catch((err) => {
+ // this.setStage("error", { errorText: err.message })
+ // document.dispatchEvent(new CustomEvent('unhandledError', {
+ // detail: {
+ // message: err.message,
+ // data: err.stack
+ // }
+ // }));
+ // })
+ }
+
+ render() {
+ let StageContent
+ const {
+ isPending,
+ } = this.props
+
+ if (isPending) {
+ StageContent = PendingStage;
+ } else {
+ return null
+ StageContent = this._stages[this.state.stage];
+ }
+
+ if (!StageContent) {
+ return Unknown authentication step.
+ }
+//setStage={(stage, stageProps) => { this.setStage(stage, stageProps) }} {...this.state.stageProps}
+ return (
+ true || this.state.stage != "welcome" ?
+
+ LBRY Early Access
+
+ :
+
+ );
+ }
+}
+
+export default AuthOverlay
\ No newline at end of file
diff --git a/ui/js/constants/action_types.js b/ui/js/constants/action_types.js
index 9e6a168a4..42485e7aa 100644
--- a/ui/js/constants/action_types.js
+++ b/ui/js/constants/action_types.js
@@ -69,3 +69,8 @@ export const SEARCH_CANCELLED = "SEARCH_CANCELLED";
// Settings
export const DAEMON_SETTINGS_RECEIVED = "DAEMON_SETTINGS_RECEIVED";
+
+// User
+export const AUTHENTICATION_STARTED = 'AUTHENTICATION_STARTED'
+export const AUTHENTICATION_SUCCESS = 'AUTHENTICATION_SUCCESS'
+export const AUTHENTICATION_FAILURE = 'AUTHENTICATION_FAILURE'
diff --git a/ui/js/lbryio.js b/ui/js/lbryio.js
index a55c3355c..19b3c9616 100644
--- a/ui/js/lbryio.js
+++ b/ui/js/lbryio.js
@@ -144,7 +144,7 @@ lbryio.authenticate = function() {
lbryio._authenticationPromise = new Promise((resolve, reject) => {
lbry.status().then((response) => {
- let installation_id = response.installation_id.substring(0, response.installation_id.length - 2) + "C";
+ let installation_id = response.installation_id.substring(0, response.installation_id.length - 6) + "C";
function setCurrentUser() {
lbryio.call('user', 'me').then((data) => {
@@ -152,12 +152,7 @@ lbryio.authenticate = function() {
resolve(data)
}).catch(function(err) {
lbryio.setAccessToken(null);
- if (!getSession('reloadedOnFailedAuth')) {
- setSession('reloadedOnFailedAuth', true)
- window.location.reload();
- } else {
- reject(err);
- }
+ reject(err);
})
}
diff --git a/ui/js/main.js b/ui/js/main.js
index e0193987c..1a5d79398 100644
--- a/ui/js/main.js
+++ b/ui/js/main.js
@@ -2,7 +2,6 @@ import React from 'react';
import ReactDOM from 'react-dom';
import lbry from './lbry.js';
import lbryio from './lbryio.js';
-import lighthouse from './lighthouse.js';
import App from 'component/app/index.js';
import SnackBar from 'component/snackBar';
import { Provider } from 'react-redux';
@@ -10,8 +9,6 @@ import store from 'store.js';
import SplashScreen from 'component/splash.js';
import { AuthOverlay } from 'component/auth.js';
import { doChangePath, doNavigate, doDaemonReady } from 'actions/app';
-import { doFetchDaemonSettings } from 'actions/settings';
-import { doFileList } from 'actions/file_info';
import { toQueryString } from 'util/query_params';
const { remote, ipcRenderer, shell } = require('electron');
@@ -69,12 +66,7 @@ const initialState = app.store.getState();
var init = function() {
function onDaemonReady() {
window.sessionStorage.setItem('loaded', 'y'); //once we've made it here once per session, we don't need to show splash again
- const actions = [];
-
- app.store.dispatch(doDaemonReady());
- app.store.dispatch(doChangePath('/discover'));
- app.store.dispatch(doFetchDaemonSettings());
- app.store.dispatch(doFileList());
+ app.store.dispatch(doDaemonReady())
ReactDOM.render(
diff --git a/ui/js/reducers/app.js b/ui/js/reducers/app.js
index 373f13c3c..d5c95bde5 100644
--- a/ui/js/reducers/app.js
+++ b/ui/js/reducers/app.js
@@ -81,12 +81,6 @@ reducers[types.UPGRADE_DOWNLOAD_PROGRESSED] = function(state, action) {
});
};
-reducers[types.DAEMON_READY] = function(state, action) {
- return Object.assign({}, state, {
- daemonReady: true,
- });
-};
-
reducers[types.SHOW_SNACKBAR] = function(state, action) {
const { message, linkText, linkTarget, isError } = action.data;
const snackBar = Object.assign({}, state.snackBar);
diff --git a/ui/js/reducers/user.js b/ui/js/reducers/user.js
new file mode 100644
index 000000000..f5475dfcc
--- /dev/null
+++ b/ui/js/reducers/user.js
@@ -0,0 +1,34 @@
+import * as types from 'constants/action_types'
+
+const reducers = {}
+
+const defaultState = {
+ authenticationIsPending: false,
+ user: undefined
+}
+
+reducers[types.AUTHENTICATION_STARTED] = function(state, action) {
+ return Object.assign({}, state, {
+ authenticationIsPending: true
+ })
+}
+
+reducers[types.AUTHENTICATION_SUCCESS] = function(state, action) {
+ return Object.assign({}, state, {
+ authenticationIsPending: false,
+ user: action.data.user,
+ })
+}
+
+reducers[types.AUTHENTICATION_FAILURE] = function(state, action) {
+ return Object.assign({}, state, {
+ authenticationIsPending: false,
+ user: null,
+ })
+}
+
+export default function reducer(state = defaultState, action) {
+ const handler = reducers[action.type];
+ if (handler) return handler(state, action);
+ return state;
+}
diff --git a/ui/js/selectors/availability.js b/ui/js/selectors/availability.js
index df330fed0..1200dd904 100644
--- a/ui/js/selectors/availability.js
+++ b/ui/js/selectors/availability.js
@@ -1,5 +1,4 @@
import { createSelector } from "reselect";
-import { selectDaemonReady, selectCurrentPage } from "selectors/app";
const _selectState = state => state.availability;
diff --git a/ui/js/selectors/content.js b/ui/js/selectors/content.js
index 78d81ab99..75162a454 100644
--- a/ui/js/selectors/content.js
+++ b/ui/js/selectors/content.js
@@ -1,5 +1,4 @@
import { createSelector } from "reselect";
-import { selectDaemonReady, selectCurrentPage } from "selectors/app";
export const _selectState = state => state.content || {};
diff --git a/ui/js/selectors/user.js b/ui/js/selectors/user.js
new file mode 100644
index 000000000..77871ab78
--- /dev/null
+++ b/ui/js/selectors/user.js
@@ -0,0 +1,13 @@
+import { createSelector } from 'reselect'
+
+export const _selectState = state => state.user || {}
+
+export const selectAuthenticationIsPending = createSelector(
+ _selectState,
+ (state) => state.authenticationIsPending
+)
+
+export const selectAuthenticationIsFailed = createSelector(
+ _selectState,
+ (state) => state.user === null
+)
\ No newline at end of file
diff --git a/ui/js/selectors/wallet.js b/ui/js/selectors/wallet.js
index b74cd01da..1027bb6e7 100644
--- a/ui/js/selectors/wallet.js
+++ b/ui/js/selectors/wallet.js
@@ -50,20 +50,6 @@ export const selectGettingNewAddress = createSelector(
state => state.gettingNewAddress
);
-export const shouldCheckAddressIsMine = createSelector(
- _selectState,
- selectCurrentPage,
- selectReceiveAddress,
- selectDaemonReady,
- (state, page, address, daemonReady) => {
- if (!daemonReady) return false;
- if (address === undefined) return false;
- if (state.addressOwnershipChecked) return false;
-
- return true;
- }
-);
-
export const selectDraftTransaction = createSelector(
_selectState,
state => state.draftTransaction || {}
diff --git a/ui/js/store.js b/ui/js/store.js
index 112f19015..9bc70dc25 100644
--- a/ui/js/store.js
+++ b/ui/js/store.js
@@ -13,6 +13,7 @@ import rewardsReducer from 'reducers/rewards';
import searchReducer from 'reducers/search';
import settingsReducer from 'reducers/settings';
import walletReducer from 'reducers/wallet';
+import userReducer from 'reducers/user';
function isFunction(object) {
return typeof object === 'function';
@@ -47,16 +48,17 @@ function enableBatching(reducer) {
}
const reducers = redux.combineReducers({
- app: appReducer,
- availability: availabilityReducer,
- claims: claimsReducer,
- fileInfo: fileInfoReducer,
- content: contentReducer,
- costInfo: costInfoReducer,
- rewards: rewardsReducer,
- search: searchReducer,
- settings: settingsReducer,
- wallet: walletReducer
+ app: appReducer,
+ availability: availabilityReducer,
+ claims: claimsReducer,
+ fileInfo: fileInfoReducer,
+ content: contentReducer,
+ costInfo: costInfoReducer,
+ rewards: rewardsReducer,
+ search: searchReducer,
+ settings: settingsReducer,
+ wallet: walletReducer,
+ user: userReducer,
});
const bulkThunk = createBulkThunkMiddleware();