diff --git a/ui/js/component/reward-link.js b/ui/js/component/reward-link.js
deleted file mode 100644
index 93486f406..000000000
--- a/ui/js/component/reward-link.js
+++ /dev/null
@@ -1,109 +0,0 @@
-import React from "react";
-import lbry from "lbry";
-import { Icon } from "component/common";
-import Modal from "component/modal";
-import rewards from "rewards";
-import Link from "component/link";
-
-export class RewardLink extends React.PureComponent {
- static propTypes = {
- type: React.PropTypes.string.isRequired,
- claimed: React.PropTypes.bool,
- onRewardClaim: React.PropTypes.func,
- onRewardFailure: React.PropTypes.func,
- };
-
- constructor(props) {
- super(props);
-
- this.state = {
- claimable: true,
- pending: false,
- errorMessage: null,
- };
- }
-
- refreshClaimable() {
- switch (this.props.type) {
- case "new_user":
- this.setState({ claimable: true });
- return;
-
- case "first_publish":
- lbry.claim_list_mine().then(list => {
- this.setState({
- claimable: list.length > 0,
- });
- });
- return;
- }
- }
-
- componentWillMount() {
- this.refreshClaimable();
- }
-
- claimReward() {
- this.setState({
- pending: true,
- });
-
- rewards
- .claimReward(this.props.type)
- .then(reward => {
- this.setState({
- pending: false,
- errorMessage: null,
- });
- if (this.props.onRewardClaim) {
- this.props.onRewardClaim(reward);
- }
- })
- .catch(error => {
- this.setState({
- errorMessage: error.message,
- pending: false,
- });
- });
- }
-
- clearError() {
- if (this.props.onRewardFailure) {
- this.props.onRewardFailure();
- }
- this.setState({
- errorMessage: null,
- });
- }
-
- render() {
- return (
-
- {this.props.claimed
- ? {__("Reward claimed.")}
- : {
- this.claimReward();
- }}
- />}
- {this.state.errorMessage
- ? {
- this.clearError();
- }}
- >
- {this.state.errorMessage}
-
- : ""}
-
- );
- }
-}
diff --git a/ui/js/component/rewardLink/index.js b/ui/js/component/rewardLink/index.js
new file mode 100644
index 000000000..81b01488b
--- /dev/null
+++ b/ui/js/component/rewardLink/index.js
@@ -0,0 +1,35 @@
+import React from "react";
+import { connect } from "react-redux";
+import {
+ makeSelectHasClaimedReward,
+ makeSelectClaimRewardError,
+ makeSelectRewardByType,
+ makeSelectIsRewardClaimPending,
+} from "selectors/rewards";
+import { doNavigate } from "actions/app";
+import { doClaimReward, doClaimRewardClearError } from "actions/rewards";
+import RewardLink from "./view";
+
+const makeSelect = () => {
+ const selectHasClaimedReward = makeSelectHasClaimedReward();
+ const selectIsPending = makeSelectIsRewardClaimPending();
+ const selectReward = makeSelectRewardByType();
+ const selectError = makeSelectClaimRewardError();
+
+ const select = (state, props) => ({
+ isClaimed: selectHasClaimedReward(state, props),
+ errorMessage: selectError(state, props),
+ isPending: selectIsPending(state, props),
+ reward: selectReward(state, props),
+ });
+
+ return select;
+};
+
+const perform = dispatch => ({
+ claimReward: reward => dispatch(doClaimReward(reward, true)),
+ clearError: reward => dispatch(doClaimRewardClearError(reward)),
+ navigate: path => dispatch(doNavigate(path)),
+});
+
+export default connect(makeSelect, perform)(RewardLink);
diff --git a/ui/js/component/rewardLink/view.jsx b/ui/js/component/rewardLink/view.jsx
new file mode 100644
index 000000000..6ebd50a20
--- /dev/null
+++ b/ui/js/component/rewardLink/view.jsx
@@ -0,0 +1,44 @@
+import React from "react";
+import { Icon } from "component/common";
+import Modal from "component/modal";
+import Link from "component/link";
+
+const RewardLink = props => {
+ const {
+ reward,
+ button,
+ claimReward,
+ clearError,
+ errorMessage,
+ isClaimed,
+ isPending,
+ } = props;
+
+ return (
+
+ {isClaimed
+ ? Reward claimed.
+ : {
+ claimReward(reward);
+ }}
+ />}
+ {errorMessage
+ ? {
+ clearError(reward);
+ }}
+ >
+ {errorMessage}
+
+ : ""}
+
+ );
+};
+export default RewardLink;
diff --git a/ui/js/component/router/view.jsx b/ui/js/component/router/view.jsx
index 131f222e6..1890992e1 100644
--- a/ui/js/component/router/view.jsx
+++ b/ui/js/component/router/view.jsx
@@ -7,9 +7,8 @@ import WalletPage from "page/wallet";
import ShowPage from "page/showPage";
import PublishPage from "page/publish";
import DiscoverPage from "page/discover";
-import SplashScreen from "component/splash.js";
import DeveloperPage from "page/developer.js";
-import RewardsPage from "page/rewards.js";
+import RewardsPage from "page/rewards";
import FileListDownloaded from "page/fileListDownloaded";
import FileListPublished from "page/fileListPublished";
import ChannelPage from "page/channel";
diff --git a/ui/js/component/userEmailNew/index.jsx b/ui/js/component/userEmailNew/index.jsx
new file mode 100644
index 000000000..0b5e93d3e
--- /dev/null
+++ b/ui/js/component/userEmailNew/index.jsx
@@ -0,0 +1,23 @@
+import React from 'react'
+import {
+ connect
+} from 'react-redux'
+import {
+ doUserEmailNew
+} from 'actions/user'
+import {
+ selectEmailNewIsPending,
+ selectEmailNewErrorMessage,
+} from 'selectors/user'
+import UserEmailNew from './view'
+
+const select = (state) => ({
+ isPending: selectEmailNewIsPending(state),
+ errorMessage: selectEmailNewErrorMessage(state),
+})
+
+const perform = (dispatch) => ({
+ addUserEmail: (email) => dispatch(doUserEmailNew(email))
+})
+
+export default connect(select, perform)(UserEmailNew)
diff --git a/ui/js/component/userEmailNew/view.jsx b/ui/js/component/userEmailNew/view.jsx
new file mode 100644
index 000000000..5391bdb3f
--- /dev/null
+++ b/ui/js/component/userEmailNew/view.jsx
@@ -0,0 +1,61 @@
+import React from "react";
+import Link from "component/link";
+import { FormRow } from "component/form.js";
+
+class UserEmailNew extends React.PureComponent {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ email: "",
+ };
+ }
+
+ handleEmailChanged(event) {
+ this.setState({
+ email: event.target.value,
+ });
+ }
+
+ handleSubmit(event) {
+ event.preventDefault();
+ this.props.addUserEmail(this.state.email);
+ }
+
+ render() {
+ const { errorMessage, isPending } = this.props;
+
+ return (
+
+ );
+ }
+}
+
+export default UserEmailNew;
diff --git a/ui/js/component/userEmailVerify/index.jsx b/ui/js/component/userEmailVerify/index.jsx
new file mode 100644
index 000000000..f2ae56c86
--- /dev/null
+++ b/ui/js/component/userEmailVerify/index.jsx
@@ -0,0 +1,21 @@
+import React from "react";
+import { connect } from "react-redux";
+import { doUserEmailVerify } from "actions/user";
+import {
+ selectEmailVerifyIsPending,
+ selectEmailToVerify,
+ selectEmailVerifyErrorMessage,
+} from "selectors/user";
+import UserEmailVerify from "./view";
+
+const select = state => ({
+ isPending: selectEmailVerifyIsPending(state),
+ email: selectEmailToVerify(state),
+ errorMessage: selectEmailVerifyErrorMessage(state),
+});
+
+const perform = dispatch => ({
+ verifyUserEmail: code => dispatch(doUserEmailVerify(code)),
+});
+
+export default connect(select, perform)(UserEmailVerify);
diff --git a/ui/js/component/userEmailVerify/view.jsx b/ui/js/component/userEmailVerify/view.jsx
new file mode 100644
index 000000000..c6cc65f34
--- /dev/null
+++ b/ui/js/component/userEmailVerify/view.jsx
@@ -0,0 +1,68 @@
+import React from "react";
+import Link from "component/link";
+import { FormRow } from "component/form.js";
+
+class UserEmailVerify extends React.PureComponent {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ code: "",
+ };
+ }
+
+ handleCodeChanged(event) {
+ this.setState({
+ code: event.target.value,
+ });
+ }
+
+ handleSubmit(event) {
+ event.preventDefault();
+ this.props.verifyUserEmail(this.state.code);
+ }
+
+ render() {
+ const { errorMessage, isPending } = this.props;
+
+ return (
+
+ );
+ }
+}
+
+export default UserEmailVerify;
diff --git a/ui/js/component/welcomeModal/index.jsx b/ui/js/component/welcomeModal/index.jsx
new file mode 100644
index 000000000..bbfc5b6a7
--- /dev/null
+++ b/ui/js/component/welcomeModal/index.jsx
@@ -0,0 +1,28 @@
+import React from "react";
+import rewards from "rewards";
+import { connect } from "react-redux";
+import { doCloseModal } from "actions/app";
+import { selectUserIsRewardApproved } from "selectors/user";
+import {
+ makeSelectHasClaimedReward,
+ makeSelectClaimRewardError,
+ makeSelectRewardByType,
+} from "selectors/rewards";
+import WelcomeModal from "./view";
+
+const select = (state, props) => {
+ const selectHasClaimed = makeSelectHasClaimedReward(),
+ selectReward = makeSelectRewardByType();
+
+ return {
+ hasClaimed: selectHasClaimed(state, { reward_type: rewards.TYPE_NEW_USER }),
+ isRewardApproved: selectUserIsRewardApproved(state),
+ reward: selectReward(state, { reward_type: rewards.TYPE_NEW_USER }),
+ };
+};
+
+const perform = dispatch => ({
+ closeModal: () => dispatch(doCloseModal()),
+});
+
+export default connect(select, perform)(WelcomeModal);
diff --git a/ui/js/component/welcomeModal/view.jsx b/ui/js/component/welcomeModal/view.jsx
new file mode 100644
index 000000000..13e0cd5f1
--- /dev/null
+++ b/ui/js/component/welcomeModal/view.jsx
@@ -0,0 +1,75 @@
+import React from "react";
+import { Modal } from "component/modal";
+import { CreditAmount } from "component/common";
+import Link from "component/link";
+import RewardLink from "component/rewardLink";
+
+class WelcomeModal extends React.PureComponent {
+ render() {
+ const { closeModal, hasClaimed, isRewardApproved, reward } = this.props;
+
+ return !hasClaimed
+ ?
+
+ 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!
+ {" "}{isRewardApproved ? __("Here's a nickel, kid.") : ""}
+
+
+ {isRewardApproved
+ ?
+ : }
+
+
+
+ :
+
+ 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.
+
+
+ ;
+ }
+}
+
+export default WelcomeModal;
diff --git a/ui/js/constants/action_types.js b/ui/js/constants/action_types.js
index 9e6a168a4..3450c1c1a 100644
--- a/ui/js/constants/action_types.js
+++ b/ui/js/constants/action_types.js
@@ -69,3 +69,27 @@ 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";
+export const USER_EMAIL_DECLINE = "USER_EMAIL_DECLINE";
+export const USER_EMAIL_NEW_STARTED = "USER_EMAIL_NEW_STARTED";
+export const USER_EMAIL_NEW_SUCCESS = "USER_EMAIL_NEW_SUCCESS";
+export const USER_EMAIL_NEW_EXISTS = "USER_EMAIL_NEW_EXISTS";
+export const USER_EMAIL_NEW_FAILURE = "USER_EMAIL_NEW_FAILURE";
+export const USER_EMAIL_VERIFY_STARTED = "USER_EMAIL_VERIFY_STARTED";
+export const USER_EMAIL_VERIFY_SUCCESS = "USER_EMAIL_VERIFY_SUCCESS";
+export const USER_EMAIL_VERIFY_FAILURE = "USER_EMAIL_VERIFY_FAILURE";
+export const USER_FETCH_STARTED = "USER_FETCH_STARTED";
+export const USER_FETCH_SUCCESS = "USER_FETCH_SUCCESS";
+export const USER_FETCH_FAILURE = "USER_FETCH_FAILURE";
+
+// Rewards
+export const FETCH_REWARDS_STARTED = "FETCH_REWARDS_STARTED";
+export const FETCH_REWARDS_COMPLETED = "FETCH_REWARDS_COMPLETED";
+export const CLAIM_REWARD_STARTED = "CLAIM_REWARD_STARTED";
+export const CLAIM_REWARD_SUCCESS = "CLAIM_REWARD_SUCCESS";
+export const CLAIM_REWARD_FAILURE = "CLAIM_REWARD_FAILURE";
+export const CLAIM_REWARD_CLEAR_ERROR = "CLAIM_REWARD_CLEAR_ERROR";
diff --git a/ui/js/constants/modal_types.js b/ui/js/constants/modal_types.js
new file mode 100644
index 000000000..b34bb9afb
--- /dev/null
+++ b/ui/js/constants/modal_types.js
@@ -0,0 +1 @@
+export const WELCOME = "welcome";
diff --git a/ui/js/lbryio.js b/ui/js/lbryio.js
index b23697d15..11416fbd6 100644
--- a/ui/js/lbryio.js
+++ b/ui/js/lbryio.js
@@ -1,206 +1,192 @@
-import { getSession, setSession } from './utils.js';
-import lbry from './lbry.js';
+import { getSession, setSession, setLocal } from "./utils.js";
+import lbry from "./lbry.js";
-const querystring = require('querystring');
+const querystring = require("querystring");
const lbryio = {
- _accessToken: getSession('accessToken'),
- _authenticationPromise: null,
- _user: null,
- enabled: true
+ _accessToken: getSession("accessToken"),
+ _authenticationPromise: null,
+ enabled: true,
};
const CONNECTION_STRING = process.env.LBRY_APP_API_URL
- ? process.env.LBRY_APP_API_URL.replace(/\/*$/, '/') // exactly one slash at the end
- : 'https://api.lbry.io/';
+ ? process.env.LBRY_APP_API_URL.replace(/\/*$/, "/") // exactly one slash at the end
+ : "https://api.lbry.io/";
const EXCHANGE_RATE_TIMEOUT = 20 * 60 * 1000;
lbryio._exchangePromise = null;
lbryio._exchangeLastFetched = null;
lbryio.getExchangeRates = function() {
- if (
- !lbryio._exchangeLastFetched ||
- Date.now() - lbryio._exchangeLastFetched > EXCHANGE_RATE_TIMEOUT
- ) {
- lbryio._exchangePromise = new Promise((resolve, reject) => {
- lbryio
- .call('lbc', 'exchange_rate', {}, 'get', true)
- .then(({ lbc_usd, lbc_btc, btc_usd }) => {
- const rates = { lbc_usd, lbc_btc, btc_usd };
- resolve(rates);
- })
- .catch(reject);
- });
- lbryio._exchangeLastFetched = Date.now();
- }
- return lbryio._exchangePromise;
+ if (
+ !lbryio._exchangeLastFetched ||
+ Date.now() - lbryio._exchangeLastFetched > EXCHANGE_RATE_TIMEOUT
+ ) {
+ lbryio._exchangePromise = new Promise((resolve, reject) => {
+ lbryio
+ .call("lbc", "exchange_rate", {}, "get", true)
+ .then(({ lbc_usd, lbc_btc, btc_usd }) => {
+ const rates = { lbc_usd, lbc_btc, btc_usd };
+ resolve(rates);
+ })
+ .catch(reject);
+ });
+ lbryio._exchangeLastFetched = Date.now();
+ }
+ return lbryio._exchangePromise;
};
-lbryio.call = function(
- resource,
- action,
- params = {},
- method = 'get',
- evenIfDisabled = false
-) {
- // evenIfDisabled is just for development, when we may have some calls working and some not
- return new Promise((resolve, reject) => {
- if (
- !lbryio.enabled &&
- !evenIfDisabled &&
- (resource != 'discover' || action != 'list')
- ) {
- console.log(__('Internal API disabled'));
- reject(new Error(__('LBRY internal API is disabled')));
- return;
- }
+lbryio.call = function(resource, action, params = {}, method = "get") {
+ return new Promise((resolve, reject) => {
+ if (!lbryio.enabled && (resource != "discover" || action != "list")) {
+ console.log(__("Internal API disabled"));
+ reject(new Error(__("LBRY internal API is disabled")));
+ return;
+ }
- const xhr = new XMLHttpRequest();
+ const xhr = new XMLHttpRequest();
- xhr.addEventListener('error', function(event) {
- reject(
- new Error(__('Something went wrong making an internal API call.'))
- );
- });
+ xhr.addEventListener("error", function(event) {
+ reject(
+ new Error(__("Something went wrong making an internal API call."))
+ );
+ });
- xhr.addEventListener('timeout', function() {
- reject(new Error(__('XMLHttpRequest connection timed out')));
- });
+ xhr.addEventListener("timeout", function() {
+ reject(new Error(__("XMLHttpRequest connection timed out")));
+ });
- xhr.addEventListener('load', function() {
- const response = JSON.parse(xhr.responseText);
+ xhr.addEventListener("load", function() {
+ const response = JSON.parse(xhr.responseText);
- if (!response.success) {
- if (reject) {
- let error = new Error(response.error);
- error.xhr = xhr;
- reject(error);
- } else {
- document.dispatchEvent(
- new CustomEvent('unhandledError', {
- detail: {
- connectionString: connectionString,
- method: action,
- params: params,
- message: response.error.message,
- ...(response.error.data ? { data: response.error.data } : {})
- }
- })
- );
- }
- } else {
- resolve(response.data);
- }
- });
+ if (!response.success) {
+ if (reject) {
+ let error = new Error(response.error);
+ error.xhr = xhr;
+ reject(error);
+ } else {
+ document.dispatchEvent(
+ new CustomEvent("unhandledError", {
+ detail: {
+ connectionString: connectionString,
+ method: action,
+ params: params,
+ message: response.error.message,
+ ...(response.error.data ? { data: response.error.data } : {}),
+ },
+ })
+ );
+ }
+ } else {
+ resolve(response.data);
+ }
+ });
- // For social media auth:
- //const accessToken = localStorage.getItem('accessToken');
- //const fullParams = {...params, ... accessToken ? {access_token: accessToken} : {}};
+ // For social media auth:
+ //const accessToken = localStorage.getItem('accessToken');
+ //const fullParams = {...params, ... accessToken ? {access_token: accessToken} : {}};
- // Temp app ID based auth:
- const fullParams = { app_id: lbryio.getAccessToken(), ...params };
+ // Temp app ID based auth:
+ const fullParams = { app_id: lbryio.getAccessToken(), ...params };
- if (method == 'get') {
- xhr.open(
- 'get',
- CONNECTION_STRING +
- resource +
- '/' +
- action +
- '?' +
- querystring.stringify(fullParams),
- true
- );
- xhr.send();
- } else if (method == 'post') {
- xhr.open('post', CONNECTION_STRING + resource + '/' + action, true);
- xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
- xhr.send(querystring.stringify(fullParams));
- } else {
- reject(new Error(__('Invalid method')));
- }
- });
+ if (method == "get") {
+ xhr.open(
+ "get",
+ CONNECTION_STRING +
+ resource +
+ "/" +
+ action +
+ "?" +
+ querystring.stringify(fullParams),
+ true
+ );
+ xhr.send();
+ } else if (method == "post") {
+ xhr.open("post", CONNECTION_STRING + resource + "/" + action, true);
+ xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
+ xhr.send(querystring.stringify(fullParams));
+ } else {
+ reject(new Error(__("Invalid method")));
+ }
+ });
};
lbryio.getAccessToken = () => {
- const token = getSession('accessToken');
- return token ? token.toString().trim() : token;
+ const token = getSession("accessToken");
+ return token ? token.toString().trim() : token;
};
lbryio.setAccessToken = token => {
- setSession('accessToken', token ? token.toString().trim() : token);
+ setSession("accessToken", token ? token.toString().trim() : token);
+};
+
+lbryio.setCurrentUser = (resolve, reject) => {
+ lbryio
+ .call("user", "me")
+ .then(data => {
+ resolve(data);
+ })
+ .catch(function(err) {
+ lbryio.setAccessToken(null);
+ reject(err);
+ });
};
lbryio.authenticate = function() {
- if (!lbryio.enabled) {
- return new Promise((resolve, reject) => {
- resolve({
- id: 1,
- has_verified_email: true
- });
- });
- }
- if (lbryio._authenticationPromise === null) {
- lbryio._authenticationPromise = new Promise((resolve, reject) => {
- lbry
- .status()
- .then(response => {
- let installation_id = response.installation_id;
+ if (!lbryio.enabled) {
+ return new Promise((resolve, reject) => {
+ resolve({
+ id: 1,
+ language: "en",
+ has_email: true,
+ has_verified_email: true,
+ is_reward_approved: false,
+ is_reward_eligible: false,
+ });
+ });
+ }
+ if (lbryio._authenticationPromise === null) {
+ lbryio._authenticationPromise = new Promise((resolve, reject) => {
+ lbry
+ .status()
+ .then(response => {
+ let installation_id = response.installation_id;
- function setCurrentUser() {
- lbryio
- .call('user', 'me')
- .then(data => {
- lbryio.user = data;
- resolve(data);
- })
- .catch(function(err) {
- lbryio.setAccessToken(null);
- if (!getSession('reloadedOnFailedAuth')) {
- setSession('reloadedOnFailedAuth', true);
- window.location.reload();
- } else {
- reject(err);
- }
- });
- }
-
- if (!lbryio.getAccessToken()) {
- lbryio
- .call(
- 'user',
- 'new',
- {
- language: 'en',
- app_id: installation_id
- },
- 'post'
- )
- .then(function(responseData) {
- if (!responseData.id) {
- reject(
- new Error(__('Received invalid authentication response.'))
- );
- }
- lbryio.setAccessToken(installation_id);
- setCurrentUser();
- })
- .catch(function(error) {
- /*
- until we have better error code format, assume all errors are duplicate application id
- if we're wrong, this will be caught by later attempts to make a valid call
- */
- lbryio.setAccessToken(installation_id);
- setCurrentUser();
- });
- } else {
- setCurrentUser();
- }
- })
- .catch(reject);
- });
- }
- return lbryio._authenticationPromise;
+ if (!lbryio.getAccessToken()) {
+ lbryio
+ .call(
+ "user",
+ "new",
+ {
+ language: "en",
+ app_id: installation_id,
+ },
+ "post"
+ )
+ .then(function(responseData) {
+ if (!responseData.id) {
+ reject(
+ new Error("Received invalid authentication response.")
+ );
+ }
+ lbryio.setAccessToken(installation_id);
+ lbryio.setCurrentUser(resolve, reject);
+ })
+ .catch(function(error) {
+ /*
+ until we have better error code format, assume all errors are duplicate application id
+ if we're wrong, this will be caught by later attempts to make a valid call
+ */
+ lbryio.setAccessToken(installation_id);
+ lbryio.setCurrentUser(resolve, reject);
+ });
+ } else {
+ lbryio.setCurrentUser(resolve, reject);
+ }
+ })
+ .catch(reject);
+ });
+ }
+ return lbryio._authenticationPromise;
};
export default lbryio;
diff --git a/ui/js/main.js b/ui/js/main.js
index 0c7c465e1..741c441da 100644
--- a/ui/js/main.js
+++ b/ui/js/main.js
@@ -1,18 +1,13 @@
import React from "react";
import ReactDOM from "react-dom";
-import whyDidYouUpdate from "why-did-you-update";
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";
import store from "store.js";
import SplashScreen from "component/splash.js";
-import { AuthOverlay } from "component/auth.js";
+import AuthOverlay from "component/authOverlay";
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 env = ENV;
@@ -57,7 +52,10 @@ ipcRenderer.on("open-uri-requested", (event, uri) => {
document.addEventListener("click", event => {
var target = event.target;
while (target && target !== document) {
- if (target.matches('a[href^="http"]')) {
+ if (
+ target.matches('a[href^="http"]') ||
+ target.matches('a[href^="mailto"]')
+ ) {
event.preventDefault();
shell.openExternal(target.href);
return;
@@ -68,31 +66,27 @@ document.addEventListener("click", event => {
const initialState = app.store.getState();
-if (env === "development") {
- /*
- https://github.com/garbles/why-did-you-update
- "A function that monkey patches React and notifies you in the console when
- potentially unnecessary re-renders occur."
-
- Just checks if props change between updates. Can be fixed by manually
- adding a check in shouldComponentUpdate or using React.PureComponent
- */
- whyDidYouUpdate(React);
-}
+// import whyDidYouUpdate from "why-did-you-update";
+// if (env === "development") {
+// /*
+// https://github.com/garbles/why-did-you-update
+// "A function that monkey patches React and notifies you in the console when
+// potentially unnecessary re-renders occur."
+//
+// Just checks if props change between updates. Can be fixed by manually
+// adding a check in shouldComponentUpdate or using React.PureComponent
+// */
+// whyDidYouUpdate(React);
+// }
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());
ReactDOM.render(
-
+
,
canvas
);
diff --git a/ui/js/page/fileListPublished/index.js b/ui/js/page/fileListPublished/index.js
index 41ef8a73f..948b81c7e 100644
--- a/ui/js/page/fileListPublished/index.js
+++ b/ui/js/page/fileListPublished/index.js
@@ -1,10 +1,12 @@
import React from "react";
+import rewards from "rewards";
import { connect } from "react-redux";
import { doFetchFileInfosAndPublishedClaims } from "actions/file_info";
import {
selectFileInfosPublished,
selectFileListDownloadedOrPublishedIsPending,
} from "selectors/file_info";
+import { doClaimRewardType } from "actions/rewards";
import { doNavigate } from "actions/app";
import FileListPublished from "./view";
@@ -16,6 +18,8 @@ const select = state => ({
const perform = dispatch => ({
navigate: path => dispatch(doNavigate(path)),
fetchFileListPublished: () => dispatch(doFetchFileInfosAndPublishedClaims()),
+ claimFirstPublishReward: () =>
+ dispatch(doClaimRewardType(rewards.TYPE_FIRST_PUBLISH)),
});
export default connect(select, perform)(FileListPublished);
diff --git a/ui/js/page/fileListPublished/view.jsx b/ui/js/page/fileListPublished/view.jsx
index 19ac43ec4..90c7aad47 100644
--- a/ui/js/page/fileListPublished/view.jsx
+++ b/ui/js/page/fileListPublished/view.jsx
@@ -16,24 +16,7 @@ class FileListPublished extends React.PureComponent {
}
componentDidUpdate() {
- if (this.props.fileInfos.length > 0) this._requestPublishReward();
- }
-
- _requestPublishReward() {
- // TODO this is throwing an error now
- // Error: LBRY internal API is disabled
- //
- // lbryio.call('reward', 'list', {}).then(function(userRewards) {
- // //already rewarded
- // if (userRewards.filter(function (reward) {
- // return reward.reward_type == rewards.TYPE_FIRST_PUBLISH && reward.transaction_id
- // }).length) {
- // return
- // }
- // else {
- // rewards.claimReward(rewards.TYPE_FIRST_PUBLISH).catch(() => {})
- // }
- // })
+ if (this.props.fileInfos.length > 0) this.props.claimFirstPublishReward();
}
render() {
diff --git a/ui/js/page/publish/index.js b/ui/js/page/publish/index.js
index 4b20b9032..abe8137df 100644
--- a/ui/js/page/publish/index.js
+++ b/ui/js/page/publish/index.js
@@ -1,7 +1,9 @@
import React from "react";
import { connect } from "react-redux";
import { doNavigate, doHistoryBack } from "actions/app";
+import { doClaimRewardType } from "actions/rewards";
import { selectMyClaims } from "selectors/claims";
+import rewards from "rewards";
import PublishPage from "./view";
const select = state => ({
@@ -11,6 +13,8 @@ const select = state => ({
const perform = dispatch => ({
back: () => dispatch(doHistoryBack()),
navigate: path => dispatch(doNavigate(path)),
+ claimFirstChannelReward: () =>
+ dispatch(doClaimRewardType(rewards.TYPE_FIRST_CHANNEL)),
});
export default connect(select, perform)(PublishPage);
diff --git a/ui/js/page/publish/view.jsx b/ui/js/page/publish/view.jsx
index 89db3c109..bf31a3b5d 100644
--- a/ui/js/page/publish/view.jsx
+++ b/ui/js/page/publish/view.jsx
@@ -44,7 +44,7 @@ class PublishPage extends React.PureComponent {
// Calls API to update displayed list of channels. If a channel name is provided, will select
// that channel at the same time (used immediately after creating a channel)
lbry.channel_list_mine().then(channels => {
- rewards.claimReward(rewards.TYPE_FIRST_CHANNEL).then(() => {}, () => {});
+ this.props.claimFirstChannelReward();
this.setState({
channels: channels,
...(channel ? { channel } : {}),
diff --git a/ui/js/page/rewards.js b/ui/js/page/rewards.js
deleted file mode 100644
index 1199ed0df..000000000
--- a/ui/js/page/rewards.js
+++ /dev/null
@@ -1,100 +0,0 @@
-import React from "react";
-import lbryio from "lbryio";
-import { CreditAmount, Icon } from "component/common.js";
-import SubHeader from "component/subHeader";
-import { RewardLink } from "component/reward-link";
-
-export class RewardTile extends React.PureComponent {
- static propTypes = {
- 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() {
- return (
-
-
-
-
-
{this.props.title}
-
-
- {this.props.claimed
- ? {__("Reward claimed.")}
- : }
-
-
{this.props.description}
-
-
- );
- }
-}
-
-export class RewardsPage extends React.PureComponent {
- constructor(props) {
- super(props);
-
- this.state = {
- userRewards: null,
- failed: null,
- };
- }
-
- componentWillMount() {
- this.loadRewards();
- }
-
- loadRewards() {
- lbryio.call("reward", "list", {}).then(
- userRewards => {
- this.setState({
- userRewards: userRewards,
- });
- },
- () => {
- this.setState({ failed: true });
- }
- );
- }
-
- render() {
- return (
-
-
-
- {!this.state.userRewards
- ? this.state.failed
- ?
{__("Failed to load rewards.")}
- : ""
- : this.state.userRewards.map(
- ({
- reward_type,
- reward_title,
- reward_description,
- transaction_id,
- reward_amount,
- }) => {
- return (
-
- );
- }
- )}
-
-
- );
- }
-}
-
-export default RewardsPage;
diff --git a/ui/js/page/rewards/index.js b/ui/js/page/rewards/index.js
new file mode 100644
index 000000000..61d0515e9
--- /dev/null
+++ b/ui/js/page/rewards/index.js
@@ -0,0 +1,20 @@
+import React from "react";
+import { connect } from "react-redux";
+import { doNavigate } from "actions/app";
+import { selectFetchingRewards, selectRewards } from "selectors/rewards";
+import {
+ selectUserIsRewardEligible,
+ selectUserHasEmail,
+ selectUserIsVerificationCandidate,
+} from "selectors/user";
+import RewardsPage from "./view";
+
+const select = state => ({
+ fetching: selectFetchingRewards(state),
+ rewards: selectRewards(state),
+ hasEmail: selectUserHasEmail(state),
+ isEligible: selectUserIsRewardEligible(state),
+ isVerificationCandidate: selectUserIsVerificationCandidate(state),
+});
+
+export default connect(select, null)(RewardsPage);
diff --git a/ui/js/page/rewards/view.jsx b/ui/js/page/rewards/view.jsx
new file mode 100644
index 000000000..6f78a6490
--- /dev/null
+++ b/ui/js/page/rewards/view.jsx
@@ -0,0 +1,92 @@
+import React from "react";
+import lbryio from "lbryio";
+import { BusyMessage, CreditAmount, Icon } from "component/common";
+import SubHeader from "component/subHeader";
+import Auth from "component/auth";
+import Link from "component/link";
+import RewardLink from "component/rewardLink";
+
+const RewardTile = props => {
+ const { reward } = props;
+
+ const claimed = !!reward.transaction_id;
+
+ return (
+
+
+
+
+
{reward.reward_title}
+
+
+ {claimed
+ ? Reward claimed.
+ : }
+
+
{reward.reward_description}
+
+
+ );
+};
+
+const RewardsPage = props => {
+ const {
+ fetching,
+ isEligible,
+ isVerificationCandidate,
+ hasEmail,
+ rewards,
+ } = props;
+
+ let content,
+ isCard = false;
+
+ if (!hasEmail || isVerificationCandidate) {
+ content = (
+
+
+ {__(
+ "Additional information is required to be eligible for the rewards program."
+ )}
+
+
+
+ );
+ isCard = true;
+ } else if (!isEligible) {
+ isCard = true;
+ content = (
+
+
{__("You are not eligible to claim rewards.")}
+
+ To become eligible, email
+ {" "} with a
+ link to a public social media profile.
+
+
+ );
+ } else if (fetching) {
+ content =
;
+ } else if (rewards.length > 0) {
+ content = rewards.map(reward =>
+
+ );
+ } else {
+ content =
{__("Failed to load rewards.")}
;
+ }
+
+ return (
+
+
+ {isCard
+ ?
+ : content}
+
+ );
+};
+
+export default RewardsPage;
diff --git a/ui/js/page/settings/view.jsx b/ui/js/page/settings/view.jsx
index f1476d5d3..b5139119e 100644
--- a/ui/js/page/settings/view.jsx
+++ b/ui/js/page/settings/view.jsx
@@ -229,25 +229,32 @@ class SettingsPage extends React.PureComponent {
- {/*}