Merge branch 'master' into tipping_button

This commit is contained in:
hackrush 2017-09-01 14:02:35 +05:30
commit 5b49915ff6
134 changed files with 4719 additions and 1555 deletions

View file

@ -1,5 +1,5 @@
[bumpversion] [bumpversion]
current_version = 0.14.3 current_version = 0.15.0
commit = True commit = True
tag = True tag = True
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)((?P<release>[a-z]+)(?P<candidate>\d+))? parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)((?P<release>[a-z]+)(?P<candidate>\d+))?

View file

@ -9,40 +9,72 @@ Web UI version numbers should always match the corresponding version of LBRY App
## [Unreleased] ## [Unreleased]
### Added ### Added
* Added a tipping button to send LBRY Credits to the publisher * Added a tipping button to send LBRY Credits to the publisher
* Added a forward button and improved history behavior. Back/forward disable when unusable. *
* Added a new component, `FormFieldPrice` which is now used in Publish and Settings.
* Added wallet backup guide reference.
### Changed ### Changed
* Updated to daemon [0.15](https://github.com/lbryio/lbry/releases). Most relevant changes for app are improved announcing of content and a fix for the daemon getting stuck running. *
* Continued to refine first-run process, process for new users, and introducing people to LBRY and LBRY credits. *
* Changed the default price settings.
* When an "Open" button is clicked on a show page, if the file fails to open, the app will try to open the file's folder.
* Some form field refactoring as we take baby steps towards form sanity.
* Replaced confusing placeholder text from email input.
* Refactored modal and settings logic.
* Updated several packages and fixed warnings in build process (all but the [fsevents warning](https://github.com/yarnpkg/yarn/issues/3738), which is a rather dramatic debate)
### Fixed ### Fixed
* Tiles will no longer be blurry on hover (Windows only bug) *
* Removed placeholder values from price selection form fields, which was causing confusion that these were real values (#426) *
* Fixed showing "other currency" help tip in publish form, which was caused due to not "setting" state for price
* Public page now properly checks for all required fields are filled
* Fixed pagination styling for pages > 5 (#416)
* Fixed sizing on squat videos (#419)
* Support claims no longer show up on Published page (#384)
* Fixed rendering of small prices (#461)
* Fixed incorrect URI in Downloads/Published page (#460)
* Fixed menu bug (#503)
### Deprecated ### Deprecated
* *
* *
### Removed ### Removed
* Removed the label "Max Purchase Price" from settings page. It was redundant.
* Unused old files from previous commit(9c3d633)
* *
*
## [0.15.0] - 2017-08-31
### Added
* Added an Invites area inside of the Wallet. This allows users to invite others and shows the status of all past invites (including all invite data from the past year). Up to one referral reward can now be claimed, but only if both users have passed the humanity test.
* Added new summary components for rewards and invites to the Wallet landing page.
* Added a forward button and improved history behavior. Back/forward disable when unusable.
* Added past history of rewards to the rewards page.
* Added wallet backup guide reference.
* Added a new widget for setting prices (`FormFieldPrice`), used in Publish and Settings.
### Changed
* Updated to daemon [0.15](https://github.com/lbryio/lbry/releases). Most relevant changes for app are improved announcing of content and a fix for the daemon getting stuck running.
* Significant refinements to first-run process, process for new users, and introducing people to LBRY and LBRY credits.
* Changed Wallet landing page to summarize status of other areas. Refactored wallet and transaction logic.
* Added icons to missing page, improved icon and title logic.
* Changed the default price settings for priced publishes.
* When an "Open" button is clicked on a show page, if the file fails to open, the app will try to open the file's folder.
* Updated several packages and fixed warnings in build process (all but the [fsevents warning](https://github.com/yarnpkg/yarn/issues/3738), which is a rather dramatic debate)
* Some form field refactoring as we take baby steps towards form sanity.
* Replaced confusing placeholder text from email input.
* Refactored modal and settings logic.
* Refactored history and navigation logic.
### Removed
* Removed the label "Max Purchase Price" from settings page. It was redundant.
* Unused old files from previous commit(9c3d633)
### Fixed
* Tiles will no longer be blurry on hover (Windows only bug)
* Removed placeholder values from price selection form fields, which was causing confusion that these were real values (#426)
* Fixed showing "other currency" help tip in publish form, which was caused due to not "setting" state for price
* Publish page now properly checks for all required fields are filled
* Fixed pagination styling for pages > 5 (#416)
* Fixed sizing on squat videos (#419)
* Support claims no longer show up on Published page (#384)
* Fixed rendering of small prices (#461)
* Fixed incorrect URI in Downloads/Published page (#460)
* Fixed menu bug (#503)
* Fixed incorrect URLs on some channel content (#505)
* Fixed video sizing for squat videos (#492)
* Fixed issues with small prices (#461)
* Fixed issues with negative values not being stopped by app on entry (#441)
* Fixed source file error when editing existing claim (#467)
## [0.14.3] - 2017-08-03 ## [0.14.3] - 2017-08-03

View file

@ -51,7 +51,7 @@ let daemonStopRequested = false;
let readyToQuit = false; let readyToQuit = false;
// If we receive a URI to open from an external app but there's no window to // If we receive a URI to open from an external app but there's no window to
// send it to, it's cached in this variable. // sendCredits it to, it's cached in this variable.
let openUri = null; let openUri = null;
function processRequestedUri(uri) { function processRequestedUri(uri) {

View file

@ -1,6 +1,6 @@
{ {
"name": "LBRY", "name": "LBRY",
"version": "0.14.3", "version": "0.15.0",
"main": "main.js", "main": "main.js",
"description": "A browser for the LBRY network, a digital marketplace controlled by its users.", "description": "A browser for the LBRY network, a digital marketplace controlled by its users.",
"author": { "author": {

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load diff

Before

Width:  |  Height:  |  Size: 306 KiB

After

Width:  |  Height:  |  Size: 434 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -5,18 +5,10 @@ import {
selectUpgradeDownloadPath, selectUpgradeDownloadPath,
selectUpgradeDownloadItem, selectUpgradeDownloadItem,
selectUpgradeFilename, selectUpgradeFilename,
selectPageTitle,
selectCurrentPage,
selectCurrentParams,
selectHistoryBack,
selectHistoryForward,
} from "selectors/app"; } from "selectors/app";
import { doSearch } from "actions/search";
import { doFetchDaemonSettings } from "actions/settings"; import { doFetchDaemonSettings } from "actions/settings";
import { doAuthenticate } from "actions/user"; import { doAuthenticate } from "actions/user";
import { doFileList } from "actions/file_info"; import { doFileList } from "actions/file_info";
import { toQueryString } from "util/query_params";
import { parseQueryParams } from "util/query_params";
const { remote, ipcRenderer, shell } = require("electron"); const { remote, ipcRenderer, shell } = require("electron");
const path = require("path"); const path = require("path");
@ -24,126 +16,6 @@ const { download } = remote.require("electron-dl");
const fs = remote.require("fs"); const fs = remote.require("fs");
const { lbrySettings: config } = require("../../../app/package.json"); const { lbrySettings: config } = require("../../../app/package.json");
export function doNavigate(path, params = {}, options = {}) {
return function(dispatch, getState) {
let url = path;
if (params) url = `${url}?${toQueryString(params)}`;
dispatch(doChangePath(url));
const state = getState();
const pageTitle = selectPageTitle(state);
const historyState = history.state;
dispatch(
doHistoryPush({ params, page: historyState.page + 1 }, pageTitle, url)
);
};
}
export function doAuthNavigate(pathAfterAuth = null, params = {}) {
return function(dispatch, getState) {
if (pathAfterAuth) {
dispatch({
type: types.CHANGE_AFTER_AUTH_PATH,
data: {
path: `${pathAfterAuth}?${toQueryString(params)}`,
},
});
}
dispatch(doNavigate("/auth"));
};
}
export function doChangePath(path, options = {}) {
return function(dispatch, getState) {
dispatch({
type: types.CHANGE_PATH,
data: {
path,
},
});
const state = getState();
const pageTitle = selectPageTitle(state);
const scrollY = options.scrollY;
window.document.title = pageTitle;
if (scrollY) window.scrollTo(0, scrollY);
else window.scrollTo(0, 0);
const currentPage = selectCurrentPage(state);
if (currentPage === "search") {
const params = selectCurrentParams(state);
dispatch(doSearch(params.query));
}
};
}
export function doHistoryBack() {
return function(dispatch, getState) {
// Get back history from stack
const back = selectHistoryBack(getState());
if (back) {
// Set location
dispatch(doChangePath(back.location));
dispatch({
type: types.HISTORY_NAVIGATE,
data: { page: back },
});
}
};
}
export function doHistoryForward() {
return function(dispatch, getState) {
// Get forward history from stack
const forward = selectHistoryForward(getState());
if (forward) {
// Set location
dispatch(doChangePath(forward.location));
dispatch({
type: types.HISTORY_NAVIGATE,
data: { page: forward },
});
}
};
}
export function doHistoryPush(currentState, title, relativeUrl) {
return function(dispatch, getState) {
title += " - LBRY";
history.pushState(currentState, title, `#${relativeUrl}`);
dispatch({
type: types.HISTORY_NAVIGATE,
data: {
location: relativeUrl,
},
});
};
}
export function doRecordScroll(scroll) {
return function(dispatch, getState) {
const state = getState();
const historyState = history.state;
if (!historyState) return;
historyState.scrollY = scroll;
history.replaceState(
historyState,
document.title,
`#${state.app.currentPath}`
);
};
}
export function doOpenModal(modal) { export function doOpenModal(modal) {
return { return {
type: types.OPEN_MODAL, type: types.OPEN_MODAL,
@ -301,25 +173,8 @@ export function doAlertError(errorList) {
export function doDaemonReady() { export function doDaemonReady() {
return function(dispatch, getState) { return function(dispatch, getState) {
const path = window.location.hash || "#/discover";
const params = parseQueryParams(path.split("?")[1] || "");
// Get first page
const page = {
index: 0,
location: path.replace(/^#/, ""),
};
history.replaceState(
{ params, is_first_page: true, page: 1 },
document.title,
`${path}`
);
dispatch(doAuthenticate()); dispatch(doAuthenticate());
dispatch({ dispatch({ type: types.DAEMON_READY });
type: types.DAEMON_READY,
data: { page },
});
dispatch(doFetchDaemonSettings()); dispatch(doFetchDaemonSettings());
dispatch(doFileList()); dispatch(doFileList());
}; };

View file

@ -20,6 +20,8 @@ import * as modals from "constants/modal_types";
const { ipcRenderer } = require("electron"); const { ipcRenderer } = require("electron");
const DOWNLOAD_POLL_INTERVAL = 250;
export function doResolveUri(uri) { export function doResolveUri(uri) {
return function(dispatch, getState) { return function(dispatch, getState) {
uri = lbryuri.normalize(uri); uri = lbryuri.normalize(uri);
@ -175,7 +177,7 @@ export function doUpdateLoadStatus(uri, outpoint) {
// download hasn't started yet // download hasn't started yet
setTimeout(() => { setTimeout(() => {
dispatch(doUpdateLoadStatus(uri, outpoint)); dispatch(doUpdateLoadStatus(uri, outpoint));
}, 250); }, DOWNLOAD_POLL_INTERVAL);
} else if (fileInfo.completed) { } else if (fileInfo.completed) {
// TODO this isn't going to get called if they reload the client before // TODO this isn't going to get called if they reload the client before
// the download finished // the download finished
@ -221,7 +223,7 @@ export function doUpdateLoadStatus(uri, outpoint) {
setTimeout(() => { setTimeout(() => {
dispatch(doUpdateLoadStatus(uri, outpoint)); dispatch(doUpdateLoadStatus(uri, outpoint));
}, 250); }, DOWNLOAD_POLL_INTERVAL);
} }
}); });
}; };
@ -303,6 +305,7 @@ export function doLoadVideo(uri) {
} }
}) })
.catch(error => { .catch(error => {
console.log(error);
dispatch({ dispatch({
type: types.LOADING_VIDEO_FAILED, type: types.LOADING_VIDEO_FAILED,
data: { uri }, data: { uri },

View file

@ -35,7 +35,11 @@ export function doFetchCostInfoForUri(uri) {
if (isGenerous && claim) { if (isGenerous && claim) {
let cost; let cost;
const fee = claim.value.stream.metadata.fee; const fee = claim.value &&
claim.value.stream &&
claim.value.stream.metadata
? claim.value.stream.metadata.fee
: undefined;
if (fee === undefined) { if (fee === undefined) {
resolve({ cost: 0, includesData: true }); resolve({ cost: 0, includesData: true });
} else if (fee.currency == "LBC") { } else if (fee.currency == "LBC") {

View file

@ -12,7 +12,8 @@ import {
selectUrisLoading, selectUrisLoading,
selectTotalDownloadProgress, selectTotalDownloadProgress,
} from "selectors/file_info"; } from "selectors/file_info";
import { doCloseModal, doHistoryBack } from "actions/app"; import { doCloseModal } from "actions/app";
import { doHistoryBack } from "actions/navigation";
import setProgressBar from "util/setProgressBar"; import setProgressBar from "util/setProgressBar";
import batchActions from "util/batchActions"; import batchActions from "util/batchActions";

104
ui/js/actions/navigation.js Normal file
View file

@ -0,0 +1,104 @@
import * as types from "constants/action_types";
import {
selectPageTitle,
selectCurrentPage,
selectCurrentParams,
selectHistoryStack,
selectHistoryIndex,
} from "selectors/navigation";
import { doSearch } from "actions/search";
import { toQueryString } from "util/query_params";
export function doNavigate(path, params = {}, options = {}) {
return function(dispatch, getState) {
if (!path) {
return;
}
let url = path;
if (params && Object.values(params).length) {
url += "?" + toQueryString(params);
}
dispatch(doChangePath(url, options));
const pageTitle = selectPageTitle(getState()) + " - LBRY";
dispatch({
type: types.HISTORY_NAVIGATE,
data: { url, index: options.index },
});
};
}
export function doAuthNavigate(pathAfterAuth = null, params = {}) {
return function(dispatch, getState) {
if (pathAfterAuth) {
dispatch({
type: types.CHANGE_AFTER_AUTH_PATH,
data: {
path: `${pathAfterAuth}?${toQueryString(params)}`,
},
});
}
dispatch(doNavigate("/auth"));
};
}
export function doChangePath(path, options = {}) {
return function(dispatch, getState) {
dispatch({
type: types.CHANGE_PATH,
data: {
path,
},
});
const state = getState();
const scrollY = options.scrollY;
//I wasn't seeing it scroll to the proper position without this -- possibly because the page isn't fully rendered? Not sure - Jeremy
setTimeout(() => {
window.scrollTo(0, scrollY ? scrollY : 0);
}, 100);
const currentPage = selectCurrentPage(state);
if (currentPage === "search") {
const params = selectCurrentParams(state);
dispatch(doSearch(params.query));
}
};
}
export function doHistoryTraverse(dispatch, state, modifier) {
const stack = selectHistoryStack(state),
index = selectHistoryIndex(state) + modifier;
if (index >= 0 && index < stack.length) {
const historyItem = stack[index];
return dispatch(
doNavigate(historyItem.path, {}, { scrollY: historyItem.scrollY, index })
);
}
}
export function doHistoryBack() {
return function(dispatch, getState) {
return doHistoryTraverse(dispatch, getState(), -1);
};
}
export function doHistoryForward() {
return function(dispatch, getState) {
return doHistoryTraverse(dispatch, getState(), 1);
};
}
export function doRecordScroll(scroll) {
return function(dispatch, getState) {
dispatch({
type: types.WINDOW_SCROLLED,
data: { scrollY: scroll },
});
};
}

View file

@ -2,7 +2,8 @@ import * as types from "constants/action_types";
import * as modals from "constants/modal_types"; import * as modals from "constants/modal_types";
import lbryio from "lbryio"; import lbryio from "lbryio";
import rewards from "rewards"; import rewards from "rewards";
import { selectRewardsByType } from "selectors/rewards"; import { selectUnclaimedRewardsByType } from "selectors/rewards";
import { selectUserIsRewardApproved } from "selectors/user";
export function doRewardList() { export function doRewardList() {
return function(dispatch, getState) { return function(dispatch, getState) {
@ -13,7 +14,7 @@ export function doRewardList() {
}); });
lbryio lbryio
.call("reward", "list", {}) .call("reward", "list", { multiple_rewards_per_type: true })
.then(userRewards => { .then(userRewards => {
dispatch({ dispatch({
type: types.FETCH_REWARDS_COMPLETED, type: types.FETCH_REWARDS_COMPLETED,
@ -31,22 +32,23 @@ export function doRewardList() {
export function doClaimRewardType(rewardType) { export function doClaimRewardType(rewardType) {
return function(dispatch, getState) { return function(dispatch, getState) {
const rewardsByType = selectRewardsByType(getState()), const state = getState(),
reward = rewardsByType[rewardType]; rewardsByType = selectUnclaimedRewardsByType(state),
reward = rewardsByType[rewardType],
userIsRewardApproved = selectUserIsRewardApproved(state);
if (reward) {
dispatch(doClaimReward(reward));
}
};
}
export function doClaimReward(reward, saveError = false) {
return function(dispatch, getState) {
if (reward.transaction_id) { if (reward.transaction_id) {
//already claimed, do nothing //already claimed, do nothing
return; return;
} }
if (!userIsRewardApproved) {
return dispatch({
type: types.OPEN_MODAL,
data: { modal: modals.REWARD_APPROVAL_REQUIRED },
});
}
dispatch({ dispatch({
type: types.CLAIM_REWARD_STARTED, type: types.CLAIM_REWARD_STARTED,
data: { reward }, data: { reward },
@ -70,43 +72,34 @@ export function doClaimReward(reward, saveError = false) {
const failure = error => { const failure = error => {
dispatch({ dispatch({
type: types.CLAIM_REWARD_FAILURE, type: types.CLAIM_REWARD_FAILURE,
data: { data: { reward, error },
reward,
error: saveError ? error : null,
},
}); });
}; };
rewards.claimReward(reward.reward_type).then(success, failure); rewards.claimReward(rewardType).then(success, failure);
}; };
} }
export function doClaimEligiblePurchaseRewards() { export function doClaimEligiblePurchaseRewards() {
return function(dispatch, getState) { return function(dispatch, getState) {
if (!lbryio.enabled) { const state = getState(),
rewardsByType = selectUnclaimedRewardsByType(state),
userIsRewardApproved = selectUserIsRewardApproved(state);
if (!userIsRewardApproved || !lbryio.enabled) {
return; return;
} }
const rewardsByType = selectRewardsByType(getState()); if (rewardsByType[rewards.TYPE_FIRST_STREAM]) {
dispatch(doClaimRewardType(rewards.TYPE_FIRST_STREAM));
let types = {}; } else {
[
types[rewards.TYPE_FIRST_STREAM] = false; rewards.TYPE_MANY_DOWNLOADS,
types[rewards.TYPE_FEATURED_DOWNLOAD] = false; rewards.TYPE_FEATURED_DOWNLOAD,
types[rewards.TYPE_MANY_DOWNLOADS] = false; ].forEach(type => {
Object.values(rewardsByType).forEach(reward => { dispatch(doClaimRewardType(type));
if (types[reward.reward_type] === false && reward.transaction_id) { });
types[reward.reward_type] = true;
}
});
let unclaimedType = Object.keys(types).find(type => {
return types[type] === false && type !== rewards.TYPE_FEATURED_DOWNLOAD; //handled below
});
if (unclaimedType) {
dispatch(doClaimRewardType(unclaimedType));
} }
dispatch(doClaimRewardType(rewards.TYPE_FEATURED_DOWNLOAD));
}; };
} }

View file

@ -2,8 +2,8 @@ import * as types from "constants/action_types";
import lbryuri from "lbryuri"; import lbryuri from "lbryuri";
import lighthouse from "lighthouse"; import lighthouse from "lighthouse";
import { doResolveUri } from "actions/content"; import { doResolveUri } from "actions/content";
import { doNavigate, doHistoryPush } from "actions/app"; import { doNavigate } from "actions/navigation";
import { selectCurrentPage } from "selectors/app"; import { selectCurrentPage } from "selectors/navigation";
import batchActions from "util/batchActions"; import batchActions from "util/batchActions";
export function doSearch(query) { export function doSearch(query) {

View file

@ -101,6 +101,9 @@ export function doDownloadLanguage(langFile) {
export function doDownloadLanguages() { export function doDownloadLanguages() {
return function(dispatch, getState) { return function(dispatch, getState) {
//temporarily disable i18n so I can get a working build out -- Jeremy
return;
if (!fs.existsSync(app.i18n.directory)) { if (!fs.existsSync(app.i18n.directory)) {
fs.mkdirSync(app.i18n.directory); fs.mkdirSync(app.i18n.directory);
} }

View file

@ -1,7 +1,7 @@
import * as types from "constants/action_types"; import * as types from "constants/action_types";
import * as modals from "constants/modal_types"; import * as modals from "constants/modal_types";
import lbryio from "lbryio"; import lbryio from "lbryio";
import { doOpenModal } from "actions/app"; import { doOpenModal, doShowSnackBar } from "actions/app";
import { doRewardList, doClaimRewardType } from "actions/rewards"; import { doRewardList, doClaimRewardType } from "actions/rewards";
import { selectEmailToVerify, selectUser } from "selectors/user"; import { selectEmailToVerify, selectUser } from "selectors/user";
import rewards from "rewards"; import rewards from "rewards";
@ -19,6 +19,7 @@ export function doAuthenticate() {
data: { user }, data: { user },
}); });
dispatch(doRewardList()); dispatch(doRewardList());
dispatch(doFetchInviteStatus());
}) })
.catch(error => { .catch(error => {
dispatch(doOpenModal(modals.AUTHENTICATION_FAILURE)); dispatch(doOpenModal(modals.AUTHENTICATION_FAILURE));
@ -172,3 +173,62 @@ export function doFetchAccessToken() {
lbryio.getAuthToken().then(success); lbryio.getAuthToken().then(success);
}; };
} }
export function doFetchInviteStatus() {
return function(dispatch, getState) {
dispatch({
type: types.USER_INVITE_STATUS_FETCH_STARTED,
});
lbryio
.call("user", "invite_status")
.then(status => {
dispatch({
type: types.USER_INVITE_STATUS_FETCH_SUCCESS,
data: {
invitesRemaining: status.invites_remaining
? status.invites_remaining
: 0,
invitees: status.invitees,
},
});
})
.catch(error => {
dispatch({
type: types.USER_INVITE_STATUS_FETCH_FAILURE,
data: { error },
});
});
};
}
export function doUserInviteNew(email) {
return function(dispatch, getState) {
dispatch({
type: types.USER_INVITE_NEW_STARTED,
});
lbryio
.call("user", "invite", { email: email }, "post")
.then(invite => {
dispatch({
type: types.USER_INVITE_NEW_SUCCESS,
data: { email },
});
dispatch(
doShowSnackBar({
message: __("Invite sent to %s", email),
})
);
dispatch(doFetchInviteStatus());
})
.catch(error => {
dispatch({
type: types.USER_INVITE_NEW_FAILURE,
data: { error },
});
});
};
}

View file

@ -22,7 +22,6 @@ const app = {
i18n: i18n, i18n: i18n,
logs: logs, logs: logs,
log: function(message) { log: function(message) {
console.log(message);
logs.push(message); logs.push(message);
}, },
}; };

View file

@ -1,30 +1,21 @@
import React from "react"; import React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { selectCurrentModal } from "selectors/app"; import { selectPageTitle } from "selectors/navigation";
import {
doCheckUpgradeAvailable,
doOpenModal,
doAlertError,
doRecordScroll,
} from "actions/app";
import { doFetchRewardedContent } from "actions/content";
import { doUpdateBalance } from "actions/wallet";
import { selectWelcomeModalAcknowledged } from "selectors/app";
import { selectUser } from "selectors/user"; import { selectUser } from "selectors/user";
import { doCheckUpgradeAvailable, doAlertError } from "actions/app";
import { doRecordScroll } from "actions/navigation";
import { doFetchRewardedContent } from "actions/content";
import { doUpdateBalance } from "actions/wallet";
import App from "./view"; import App from "./view";
import * as modals from "constants/modal_types";
const select = (state, props) => ({ const select = (state, props) => ({
modal: selectCurrentModal(state), pageTitle: selectPageTitle(state),
isWelcomeAcknowledged: selectWelcomeModalAcknowledged(state),
user: selectUser(state), user: selectUser(state),
}); });
const perform = dispatch => ({ const perform = dispatch => ({
alertError: errorList => dispatch(doAlertError(errorList)), alertError: errorList => dispatch(doAlertError(errorList)),
checkUpgradeAvailable: () => dispatch(doCheckUpgradeAvailable()), checkUpgradeAvailable: () => dispatch(doCheckUpgradeAvailable()),
openWelcomeModal: () => dispatch(doOpenModal(modals.WELCOME)),
updateBalance: balance => dispatch(doUpdateBalance(balance)), updateBalance: balance => dispatch(doUpdateBalance(balance)),
fetchRewardedContent: () => dispatch(doFetchRewardedContent()), fetchRewardedContent: () => dispatch(doFetchRewardedContent()),
recordScroll: scrollPosition => dispatch(doRecordScroll(scrollPosition)), recordScroll: scrollPosition => dispatch(doRecordScroll(scrollPosition)),

View file

@ -30,12 +30,22 @@ class App extends React.PureComponent {
this.scrollListener = () => this.props.recordScroll(window.scrollY); this.scrollListener = () => this.props.recordScroll(window.scrollY);
window.addEventListener("scroll", this.scrollListener); window.addEventListener("scroll", this.scrollListener);
this.setTitleFromProps(this.props);
} }
componentWillUnmount() { componentWillUnmount() {
window.removeEventListener("scroll", this.scrollListener); window.removeEventListener("scroll", this.scrollListener);
} }
componentWillReceiveProps(props) {
this.setTitleFromProps(props);
}
setTitleFromProps(props) {
window.document.title = props.pageTitle;
}
render() { render() {
return ( return (
<div id="window"> <div id="window">

View file

@ -165,7 +165,7 @@ class CardVerify extends React.Component {
render() { render() {
return ( return (
<Link <Link
button="primary" button="alt"
label={this.props.label} label={this.props.label}
icon="icon-lock" icon="icon-lock"
disabled={ disabled={

View file

@ -69,15 +69,18 @@ export class CreditAmount extends React.PureComponent {
label: React.PropTypes.bool, label: React.PropTypes.bool,
showFree: React.PropTypes.bool, showFree: React.PropTypes.bool,
showFullPrice: React.PropTypes.bool, showFullPrice: React.PropTypes.bool,
showPlus: React.PropTypes.bool,
look: React.PropTypes.oneOf(["indicator", "plain"]), look: React.PropTypes.oneOf(["indicator", "plain"]),
}; };
static defaultProps = { static defaultProps = {
precision: 2, precision: 2,
label: true, label: true,
showFree: false,
look: "indicator", look: "indicator",
showFree: false, showFree: false,
showFullPrice: false, showFullPrice: false,
showPlus: false,
}; };
render() { render() {
@ -98,13 +101,18 @@ export class CreditAmount extends React.PureComponent {
let amountText; let amountText;
if (this.props.showFree && parseFloat(this.props.amount) === 0) { if (this.props.showFree && parseFloat(this.props.amount) === 0) {
amountText = __("free"); amountText = __("free");
} else if (this.props.label) {
amountText =
formattedAmount +
" " +
(parseFloat(amount) == 1 ? __("credit") : __("credits"));
} else { } else {
amountText = formattedAmount; if (this.props.label) {
amountText =
formattedAmount +
" " +
(parseFloat(amount) == 1 ? __("credit") : __("credits"));
} else {
amountText = formattedAmount;
}
if (this.props.showPlus && amount > 0) {
amountText = "+" + amountText;
}
} }
return ( return (

View file

@ -1,13 +1,12 @@
import React from "react"; import React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { selectPlatform } from "selectors/app"; import { selectPlatform, selectCurrentModal } from "selectors/app";
import { import {
makeSelectFileInfoForUri, makeSelectFileInfoForUri,
makeSelectDownloadingForUri, makeSelectDownloadingForUri,
makeSelectLoadingForUri, makeSelectLoadingForUri,
} from "selectors/file_info"; } from "selectors/file_info";
import { makeSelectIsAvailableForUri } from "selectors/availability"; import { makeSelectIsAvailableForUri } from "selectors/availability";
import { selectCurrentModal } from "selectors/app";
import { makeSelectCostInfoForUri } from "selectors/cost_info"; import { makeSelectCostInfoForUri } from "selectors/cost_info";
import { doCloseModal, doOpenModal } from "actions/app"; import { doCloseModal, doOpenModal } from "actions/app";
import { doFetchAvailability } from "actions/availability"; import { doFetchAvailability } from "actions/availability";

View file

@ -178,18 +178,20 @@ class FileActions extends React.PureComponent {
onClick={this.handleSupportButtonClicked.bind(this)} onClick={this.handleSupportButtonClicked.bind(this)}
/> />
{showMenu {showMenu
? <DropDownMenu> ? <div className="button-set-item">
<DropDownMenuItem <DropDownMenu>
key={0} <DropDownMenuItem
onClick={() => openInFolder(fileInfo)} key={0}
label={openInFolderMessage} onClick={() => openInFolder(fileInfo)}
/> label={openInFolderMessage}
<DropDownMenuItem />
key={1} <DropDownMenuItem
onClick={() => openModal(modals.CONFIRM_FILE_REMOVE)} key={1}
label={__("Remove...")} onClick={() => openModal(modals.CONFIRM_FILE_REMOVE)}
/> label={__("Remove...")}
</DropDownMenu> />
</DropDownMenu>
</div>
: ""} : ""}
<Modal <Modal
type="confirm" type="confirm"

View file

@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { doNavigate } from "actions/app"; import { doNavigate } from "actions/navigation";
import { doResolveUri } from "actions/content"; import { doResolveUri } from "actions/content";
import { selectShowNsfw } from "selectors/settings"; import { selectShowNsfw } from "selectors/settings";
import { import {

View file

@ -6,7 +6,7 @@ import {
selectCurrentSearchResults, selectCurrentSearchResults,
selectSearchQuery, selectSearchQuery,
} from "selectors/search"; } from "selectors/search";
import { doNavigate } from "actions/app"; import { doNavigate } from "actions/navigation";
import FileListSearch from "./view"; import FileListSearch from "./view";
const select = state => ({ const select = state => ({

View file

@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { doNavigate } from "actions/app"; import { doNavigate } from "actions/navigation";
import { doResolveUri } from "actions/content"; import { doResolveUri } from "actions/content";
import { import {
makeSelectClaimForUri, makeSelectClaimForUri,

View file

@ -1,9 +1,16 @@
import React from "react"; import React from "react";
import { formatCredits } from "util/formatCredits"; import { formatCredits } from "util/formatCredits";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { selectIsBackDisabled, selectIsForwardDisabled } from "selectors/app"; import {
selectIsBackDisabled,
selectIsForwardDisabled,
} from "selectors/navigation";
import { selectBalance } from "selectors/wallet"; import { selectBalance } from "selectors/wallet";
import { doNavigate, doHistoryBack, doHistoryForward } from "actions/app"; import {
doNavigate,
doHistoryBack,
doHistoryForward,
} from "actions/navigation";
import Header from "./view"; import Header from "./view";
const select = state => ({ const select = state => ({

View file

@ -0,0 +1,16 @@
import React from "react";
import { connect } from "react-redux";
import {
selectUserInvitees,
selectUserInviteStatusIsPending,
} from "selectors/user";
import InviteList from "./view";
const select = state => ({
invitees: selectUserInvitees(state),
isPending: selectUserInviteStatusIsPending(state),
});
const perform = dispatch => ({});
export default connect(select, perform)(InviteList);

View file

@ -0,0 +1,77 @@
import React from "react";
import { Icon } from "component/common";
import RewardLink from "component/rewardLink";
import rewards from "rewards.js";
class InviteList extends React.PureComponent {
render() {
const { invitees } = this.props;
if (!invitees) {
return null;
}
return (
<section className="card">
<div className="card__title-primary">
<h3>{__("Invite History")}</h3>
</div>
<div className="card__content">
{invitees.length === 0 &&
<span className="empty">{__("You haven't invited anyone.")} </span>}
{invitees.length > 0 &&
<table className="table-standard table-stretch">
<thead>
<tr>
<th>
{__("Invitee Email")}
</th>
<th className="text-center">
{__("Invite Status")}
</th>
<th className="text-center">
{__("Reward")}
</th>
</tr>
</thead>
<tbody>
{invitees.map((invitee, index) => {
return (
<tr key={index}>
<td>{invitee.email}</td>
<td className="text-center">
{invitee.invite_accepted
? <Icon icon="icon-check" />
: <span className="empty">{__("unused")}</span>}
</td>
<td className="text-center">
{invitee.invite_reward_claimed
? <Icon icon="icon-check" />
: invitee.invite_accepted
? <RewardLink
label={__("Claim")}
reward_type={rewards.TYPE_REFERRAL}
/>
: <span className="empty">
{__("unclaimable")}
</span>}
</td>
</tr>
);
})}
</tbody>
</table>}
</div>
<div className="card__content">
<div className="help">
{__(
"The maximum number of invite rewards is currently limited. Invite reward can only be claimed if the invitee passes the humanness test."
)}
</div>
</div>
</section>
);
}
}
export default InviteList;

View file

@ -0,0 +1,29 @@
import React from "react";
import { connect } from "react-redux";
import InviteNew from "./view";
import {
selectUserInvitesRemaining,
selectUserInviteNewIsPending,
selectUserInviteNewErrorMessage,
} from "selectors/user";
import rewards from "rewards";
import { makeSelectRewardAmountByType } from "selectors/rewards";
import { doUserInviteNew } from "actions/user";
const select = state => {
const selectReward = makeSelectRewardAmountByType();
return {
errorMessage: selectUserInviteNewErrorMessage(state),
invitesRemaining: selectUserInvitesRemaining(state),
isPending: selectUserInviteNewIsPending(state),
rewardAmount: selectReward(state, { reward_type: rewards.TYPE_REFERRAL }),
};
};
const perform = dispatch => ({
inviteNew: email => dispatch(doUserInviteNew(email)),
});
export default connect(select, perform)(InviteNew);

View file

@ -0,0 +1,100 @@
import React from "react";
import { BusyMessage, CreditAmount } from "component/common";
import Link from "component/link";
import { FormRow } from "component/form.js";
class FormInviteNew extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
email: "",
};
}
handleEmailChanged(event) {
this.setState({
email: event.target.value,
});
}
handleSubmit(event) {
event.preventDefault();
this.props.inviteNew(this.state.email);
}
render() {
const { errorMessage, isPending } = this.props;
return (
<form>
<FormRow
type="text"
label="Email"
placeholder="youremail@example.org"
name="email"
value={this.state.email}
errorMessage={errorMessage}
onChange={event => {
this.handleEmailChanged(event);
}}
/>
<div className="form-row-submit">
<Link
button="primary"
label={__("Send Invite")}
disabled={isPending}
onClick={event => {
this.handleSubmit(event);
}}
/>
</div>
</form>
);
}
}
class InviteNew extends React.PureComponent {
render() {
const {
errorMessage,
invitesRemaining,
inviteNew,
inviteStatusIsPending,
isPending,
rewardAmount,
} = this.props;
return (
<section className="card">
<div className="card__title-primary">
<CreditAmount amount={rewardAmount} />
<h3>
{__("Invite a Friend")}
</h3>
</div>
{/*
<div className="card__content">
{invitesRemaining > 0 &&
<p>{__("You have %s invites remaining.", invitesRemaining)}</p>}
{invitesRemaining <= 0 &&
<p className="empty">{__("You have no invites.")}</p>}
</div> */}
<div className="card__content">
<p>
{__(
"Or an enemy. Or your cousin Jerry, who you're kind of unsure about."
)}
</p>
<FormInviteNew
errorMessage={errorMessage}
inviteNew={inviteNew}
isPending={isPending}
/>
</div>
</section>
);
}
}
export default InviteNew;

View file

@ -1,5 +1,10 @@
import React from "react"; import React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { doNavigate } from "actions/navigation";
import Link from "./view"; import Link from "./view";
export default connect(null, null)(Link); const perform = dispatch => ({
doNavigate: path => dispatch(doNavigate(path)),
});
export default connect(null, perform)(Link);

View file

@ -5,7 +5,6 @@ const Link = props => {
const { const {
href, href,
title, title,
onClick,
style, style,
label, label,
icon, icon,
@ -13,6 +12,8 @@ const Link = props => {
button, button,
disabled, disabled,
children, children,
navigate,
doNavigate,
} = props; } = props;
const className = const className =
@ -21,6 +22,12 @@ const Link = props => {
(button ? " button-block button-" + button + " button-set-item" : "") + (button ? " button-block button-" + button + " button-set-item" : "") +
(disabled ? " disabled" : ""); (disabled ? " disabled" : "");
const onClick = !props.onClick && navigate
? () => {
doNavigate(navigate);
}
: props.onClick;
let content; let content;
if (children) { if (children) {
content = children; content = children;

View file

@ -0,0 +1,5 @@
import React from "react";
import { connect } from "react-redux";
import Link from "./view";
export default connect(null, null)(Link);

View file

@ -0,0 +1,14 @@
import React from "react";
import Link from "component/link";
const LinkTransaction = props => {
const { id } = props;
const linkProps = Object.assign({}, props);
linkProps.href = "https://explorer.lbry.io/#!/transaction/" + id;
linkProps.label = id.substr(0, 7);
return <Link {...linkProps} />;
};
export default LinkTransaction;

View file

@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { doNavigate } from "actions/app"; import { doNavigate } from "actions/navigation";
import NsfwOverlay from "./view"; import NsfwOverlay from "./view";
const perform = dispatch => ({ const perform = dispatch => ({

View file

@ -16,6 +16,7 @@ class PublishForm extends React.PureComponent {
this._requiredFields = ["name", "bid", "meta_title", "tosAgree"]; this._requiredFields = ["name", "bid", "meta_title", "tosAgree"];
this._defaultCopyrightNotice = "All rights reserved."; this._defaultCopyrightNotice = "All rights reserved.";
this._defaultPaidPrice = 0.01;
this.state = { this.state = {
rawName: "", rawName: "",
@ -46,6 +47,7 @@ class PublishForm extends React.PureComponent {
modal: null, modal: null,
isFee: false, isFee: false,
customUrl: false, customUrl: false,
source: null,
}; };
} }
@ -115,8 +117,12 @@ class PublishForm extends React.PureComponent {
: {}), : {}),
}; };
const { source } = this.state;
if (this.refs.file.getValue() !== "") { if (this.refs.file.getValue() !== "") {
publishArgs.file_path = this.refs.file.getValue(); publishArgs.file_path = this.refs.file.getValue();
} else if (source) {
publishArgs.sources = source;
} }
const success = claim => {}; const success = claim => {};
@ -257,6 +263,7 @@ class PublishForm extends React.PureComponent {
handlePrefillClicked() { handlePrefillClicked() {
const claimInfo = this.myClaimInfo(); const claimInfo = this.myClaimInfo();
const { source } = claimInfo.value.stream;
const { const {
license, license,
licenseUrl, licenseUrl,
@ -275,6 +282,7 @@ class PublishForm extends React.PureComponent {
meta_nsfw: nsfw, meta_nsfw: nsfw,
prefillDone: true, prefillDone: true,
bid: claimInfo.amount, bid: claimInfo.amount,
source,
}; };
if (license == this._defaultCopyrightNotice) { if (license == this._defaultCopyrightNotice) {
@ -318,7 +326,9 @@ class PublishForm extends React.PureComponent {
handleFeePrefChange(feeEnabled) { handleFeePrefChange(feeEnabled) {
this.setState({ this.setState({
isFee: feeEnabled, isFee: feeEnabled,
feeAmount: this.state.feeAmount == "" ? "5.00" : this.state.feeAmount, feeAmount: this.state.feeAmount == ""
? this._defaultPaidPrice
: this.state.feeAmount,
}); });
} }
@ -786,7 +796,6 @@ class PublishForm extends React.PureComponent {
ref="bid" ref="bid"
type="number" type="number"
step="0.01" step="0.01"
min="0"
label={__("Deposit")} label={__("Deposit")}
postfix="LBC" postfix="LBC"
onChange={event => { onChange={event => {

View file

@ -5,8 +5,8 @@ import {
makeSelectRewardByType, makeSelectRewardByType,
makeSelectIsRewardClaimPending, makeSelectIsRewardClaimPending,
} from "selectors/rewards"; } from "selectors/rewards";
import { doNavigate } from "actions/app"; import { doNavigate } from "actions/navigation";
import { doClaimReward, doClaimRewardClearError } from "actions/rewards"; import { doClaimRewardType, doClaimRewardClearError } from "actions/rewards";
import RewardLink from "./view"; import RewardLink from "./view";
const makeSelect = () => { const makeSelect = () => {
@ -24,7 +24,7 @@ const makeSelect = () => {
}; };
const perform = dispatch => ({ const perform = dispatch => ({
claimReward: reward => dispatch(doClaimReward(reward, true)), claimReward: reward => dispatch(doClaimRewardType(reward.reward_type, true)),
clearError: reward => dispatch(doClaimRewardClearError(reward)), clearError: reward => dispatch(doClaimRewardClearError(reward)),
navigate: path => dispatch(doNavigate(path)), navigate: path => dispatch(doNavigate(path)),
}); });

View file

@ -9,15 +9,18 @@ const RewardLink = props => {
claimReward, claimReward,
clearError, clearError,
errorMessage, errorMessage,
label,
isPending, isPending,
} = props; } = props;
return ( return (
<div className="reward-link"> <div className="reward-link">
<Link <Link
button={button ? button : "alt"} button={button}
disabled={isPending} disabled={isPending}
label={isPending ? __("Claiming...") : __("Claim Reward")} label={
isPending ? __("Claiming...") : label ? label : __("Claim Reward")
}
onClick={() => { onClick={() => {
claimReward(reward); claimReward(reward);
}} }}

View file

@ -0,0 +1,10 @@
import React from "react";
import { connect } from "react-redux";
import { selectClaimedRewards } from "selectors/rewards";
import RewardListClaimed from "./view";
const select = state => ({
rewards: selectClaimedRewards(state),
});
export default connect(select, null)(RewardListClaimed);

View file

@ -0,0 +1,44 @@
import React from "react";
import LinkTransaction from "component/linkTransaction";
const RewardListClaimed = props => {
const { rewards } = props;
if (!rewards || !rewards.length) {
return null;
}
return (
<section className="card">
<div className="card__title-identity"><h3>Claimed Rewards</h3></div>
<div className="card__content">
<table className="table-standard table-stretch">
<thead>
<tr>
<th>{__("Title")}</th>
<th>{__("Amount")}</th>
<th>{__("Transaction")}</th>
<th>{__("Date")}</th>
</tr>
</thead>
<tbody>
{rewards.map(reward => {
return (
<tr key={reward.id}>
<td>{reward.reward_title}</td>
<td>{reward.reward_amount}</td>
<td><LinkTransaction id={reward.transaction_id} /></td>
<td>
{reward.created_at.replace("Z", " ").replace("T", " ")}
</td>
</tr>
);
})}
</tbody>
</table>
</div>
</section>
);
};
export default RewardListClaimed;

View file

@ -0,0 +1,10 @@
import React from "react";
import { connect } from "react-redux";
import { selectUnclaimedRewardValue } from "selectors/rewards";
import RewardSummary from "./view";
const select = state => ({
unclaimedRewardAmount: selectUnclaimedRewardValue(state),
});
export default connect(select, null)(RewardSummary);

View file

@ -0,0 +1,29 @@
import React from "react";
import Link from "component/link";
import { CreditAmount } from "component/common";
const RewardSummary = props => {
const { balance, unclaimedRewardAmount } = props;
return (
<section className="card">
<div className="card__title-primary">
<h3>{__("Rewards")}</h3>
</div>
<div className="card__content">
{unclaimedRewardAmount > 0 &&
<p>
You have{" "}
<CreditAmount amount={unclaimedRewardAmount} precision={8} /> in
unclaimed rewards.
</p>}
</div>
<div className="card__actions card__actions--bottom">
<Link button="text" navigate="/rewards" label={__("Rewards")} />
<Link button="text" navigate="/invite" label={__("Invites")} />
</div>
</section>
);
};
export default RewardSummary;

View file

@ -0,0 +1,5 @@
import React from "react";
import { connect } from "react-redux";
import RewardTile from "./view";
export default connect(null, null)(RewardTile);

View file

@ -0,0 +1,37 @@
import React from "react";
import { CreditAmount, Icon } from "component/common";
import RewardLink from "component/rewardLink";
import Link from "component/link";
import rewards from "rewards";
const RewardTile = props => {
const { reward } = props;
const claimed = !!reward.transaction_id;
return (
<section className="card">
<div className="card__inner">
<div className="card__title-primary">
<CreditAmount amount={reward.reward_amount} />
<h3>{reward.reward_title}</h3>
</div>
<div className="card__content">{reward.reward_description}</div>
<div className="card__actions card__actions--bottom ">
{reward.reward_type == rewards.TYPE_REFERRAL &&
<Link
button="alt"
navigate="/invite"
label={__("Go To Invites")}
/>}
{reward.reward_type !== rewards.TYPE_REFERRAL &&
(claimed
? <span><Icon icon="icon-check" /> {__("Reward claimed.")}</span>
: <RewardLink button="alt" reward_type={reward.reward_type} />)}
</div>
</div>
</section>
);
};
export default RewardTile;

View file

@ -1,7 +1,10 @@
import React from "react"; import React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import Router from "./view.jsx"; import Router from "./view.jsx";
import { selectCurrentPage, selectCurrentParams } from "selectors/app.js"; import {
selectCurrentPage,
selectCurrentParams,
} from "selectors/navigation.js";
const select = state => ({ const select = state => ({
params: selectCurrentParams(state), params: selectCurrentParams(state),

View file

@ -2,18 +2,21 @@ import React from "react";
import SettingsPage from "page/settings"; import SettingsPage from "page/settings";
import HelpPage from "page/help"; import HelpPage from "page/help";
import ReportPage from "page/report.js"; import ReportPage from "page/report.js";
import StartPage from "page/start.js";
import WalletPage from "page/wallet"; import WalletPage from "page/wallet";
import ShowPage from "page/showPage"; import ReceiveCreditsPage from "page/receiveCredits";
import SendCreditsPage from "page/sendCredits";
import ShowPage from "page/show";
import PublishPage from "page/publish"; import PublishPage from "page/publish";
import DiscoverPage from "page/discover"; import DiscoverPage from "page/discover";
import DeveloperPage from "page/developer.js"; import DeveloperPage from "page/developer.js";
import RewardsPage from "page/rewards"; import RewardsPage from "page/rewards";
import FileListDownloaded from "page/fileListDownloaded"; import FileListDownloaded from "page/fileListDownloaded";
import FileListPublished from "page/fileListPublished"; import FileListPublished from "page/fileListPublished";
import TransactionHistoryPage from "page/transactionHistory";
import ChannelPage from "page/channel"; import ChannelPage from "page/channel";
import SearchPage from "page/search"; import SearchPage from "page/search";
import AuthPage from "page/auth"; import AuthPage from "page/auth";
import InvitePage from "page/invite";
import BackupPage from "page/backup"; import BackupPage from "page/backup";
const route = (page, routesMap) => { const route = (page, routesMap) => {
@ -33,16 +36,17 @@ const Router = props => {
discover: <DiscoverPage params={params} />, discover: <DiscoverPage params={params} />,
downloaded: <FileListDownloaded params={params} />, downloaded: <FileListDownloaded params={params} />,
help: <HelpPage params={params} />, help: <HelpPage params={params} />,
history: <TransactionHistoryPage params={params} />,
invite: <InvitePage params={params} />,
publish: <PublishPage params={params} />, publish: <PublishPage params={params} />,
published: <FileListPublished params={params} />, published: <FileListPublished params={params} />,
receive: <WalletPage params={params} />, receive: <ReceiveCreditsPage params={params} />,
report: <ReportPage params={params} />, report: <ReportPage params={params} />,
rewards: <RewardsPage params={params} />, rewards: <RewardsPage params={params} />,
search: <SearchPage params={params} />, search: <SearchPage params={params} />,
send: <WalletPage params={params} />, send: <SendCreditsPage params={params} />,
settings: <SettingsPage params={params} />, settings: <SettingsPage params={params} />,
show: <ShowPage params={params} />, show: <ShowPage params={params} />,
start: <StartPage params={params} />,
wallet: <WalletPage params={params} />, wallet: <WalletPage params={params} />,
}); });
}; };

View file

@ -1,11 +1,10 @@
import React from "react"; import React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { doNavigate, doRemoveSnackBarSnack } from "actions/app"; import { doRemoveSnackBarSnack } from "actions/app";
import { selectSnackBarSnacks } from "selectors/app"; import { selectSnackBarSnacks } from "selectors/app";
import SnackBar from "./view"; import SnackBar from "./view";
const perform = dispatch => ({ const perform = dispatch => ({
navigate: path => dispatch(doNavigate(path)),
removeSnack: () => dispatch(doRemoveSnackBarSnack()), removeSnack: () => dispatch(doRemoveSnackBarSnack()),
}); });

View file

@ -10,7 +10,7 @@ class SnackBar extends React.PureComponent {
} }
render() { render() {
const { navigate, snacks, removeSnack } = this.props; const { snacks, removeSnack } = this.props;
if (!snacks.length) { if (!snacks.length) {
this._hideTimeout = null; //should be unmounting anyway, but be safe? this._hideTimeout = null; //should be unmounting anyway, but be safe?
@ -33,7 +33,7 @@ class SnackBar extends React.PureComponent {
{linkText && {linkText &&
linkTarget && linkTarget &&
<Link <Link
onClick={() => navigate(linkTarget)} navigate={linkTarget}
className="snack-bar__action" className="snack-bar__action"
label={linkText} label={linkText}
/>} />}

View file

@ -1,7 +1,7 @@
import React from "react"; import React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { selectCurrentPage, selectHeaderLinks } from "selectors/app"; import { selectCurrentPage, selectHeaderLinks } from "selectors/navigation";
import { doNavigate } from "actions/app"; import { doNavigate } from "actions/navigation";
import SubHeader from "./view"; import SubHeader from "./view";
const select = (state, props) => ({ const select = (state, props) => ({

View file

@ -1,23 +1,5 @@
import React from "react"; import React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { doNavigate } from "actions/app";
import { doFetchTransactions } from "actions/wallet";
import {
selectBalance,
selectTransactionItems,
selectIsFetchingTransactions,
} from "selectors/wallet";
import TransactionList from "./view"; import TransactionList from "./view";
const select = state => ({ export default connect(null, null)(TransactionList);
fetchingTransactions: selectIsFetchingTransactions(state),
transactionItems: selectTransactionItems(state),
});
const perform = dispatch => ({
navigate: (path, params) => dispatch(doNavigate(path, params)),
fetchTransactions: () => dispatch(doFetchTransactions()),
});
export default connect(select, perform)(TransactionList);

View file

@ -1,103 +1,57 @@
import React from "react"; import React from "react";
import { Address, BusyMessage, CreditAmount } from "component/common"; import LinkTransaction from "component/linkTransaction";
import { CreditAmount } from "component/common";
class TransactionList extends React.PureComponent { const TransactionList = props => {
componentWillMount() { const { emptyMessage, transactions } = props;
this.props.fetchTransactions();
}
render() {
const { fetchingTransactions, transactionItems, navigate } = this.props;
function findTypeOfTx(type, is_tip) {
if (is_tip && type === "Support") return "Tip";
else return type;
}
function getClaimLink(claim_name, claim_id) {
if (claim_id !== "" && claim_name !== "") {
let uri = `lbry://${claim_name}#${claim_id}`;
return (
<td>
<a
className="button-text"
onClick={() => navigate("/show", { uri })}
>
{claim_name}
</a>
</td>
);
}
return <td>{__("N/A")}</td>;
}
const rows = [];
if (transactionItems.length > 0) {
transactionItems.forEach(function(item) {
rows.push(
<tr key={item.id}>
<td>{findTypeOfTx(item.type, item.is_tip)}</td>
<td>{(item.amount > 0 ? "+" : "") + item.amount}</td>
<td>{item.fee}</td>
<td>
{item.date
? item.date.toLocaleDateString()
: <span className="empty">{__("(Transaction pending)")}</span>}
</td>
<td>
{item.date
? item.date.toLocaleTimeString()
: <span className="empty">{__("(Transaction pending)")}</span>}
</td>
{getClaimLink(item.claim_name, item.claim_id)}
<td>
<a
className="button-text"
href={"https://explorer.lbry.io/#!/transaction/" + item.id}
>
{item.id.substr(0, 7)}
</a>
</td>
</tr>
);
});
}
if (!transactions || !transactions.length) {
return ( return (
<section className="card"> <div className="empty">
<div className="card__title-primary"> {emptyMessage || __("No transactions to list.")}
<h3>{__("Transaction History")}</h3> </div>
</div>
<div className="card__content">
{fetchingTransactions &&
<BusyMessage message={__("Loading transactions")} />}
{!fetchingTransactions && rows.length === 0
? <div className="empty">{__("You have no transactions.")}</div>
: ""}
{rows.length > 0
? <table className="table-standard table-stretch">
<thead>
<tr>
<th>{__("Type")}</th>
<th>{__("Amount")}</th>
<th>{__("Fee")}</th>
<th>{__("Date")}</th>
<th>{__("Time")}</th>
<th>{__("Claim")}</th>
<th>{__("Transaction")}</th>
</tr>
</thead>
<tbody>
{rows}
</tbody>
</table>
: ""}
</div>
</section>
); );
} }
}
return (
<table className="table-standard table-stretch">
<thead>
<tr>
<th>{__("Date")}</th>
<th>{__("Amount")}</th>
<th>{__("Transaction")}</th>
</tr>
</thead>
<tbody>
{transactions.map(item => {
return (
<tr key={item.id}>
<td>
{item.date
? item.date.toLocaleDateString() +
" " +
item.date.toLocaleTimeString()
: <span className="empty">
{__("(Transaction pending)")}
</span>}
</td>
<td>
<CreditAmount
amount={item.amount}
look="plain"
showPlus={true}
precision={8}
/>{" "}
</td>
<td>
<LinkTransaction id={item.id} />
</td>
</tr>
);
})}
</tbody>
</table>
);
};
export default TransactionList; export default TransactionList;

View file

@ -0,0 +1,23 @@
import React from "react";
import { connect } from "react-redux";
import { doFetchTransactions } from "actions/wallet";
import {
selectBalance,
selectRecentTransactions,
selectHasTransactions,
selectIsFetchingTransactions,
} from "selectors/wallet";
import TransactionListRecent from "./view";
const select = state => ({
fetchingTransactions: selectIsFetchingTransactions(state),
transactions: selectRecentTransactions(state),
hasTransactions: selectHasTransactions(state),
});
const perform = dispatch => ({
fetchTransactions: () => dispatch(doFetchTransactions()),
});
export default connect(select, perform)(TransactionListRecent);

View file

@ -0,0 +1,41 @@
import React from "react";
import { BusyMessage } from "component/common";
import Link from "component/link";
import TransactionList from "component/transactionList";
class TransactionListRecent extends React.PureComponent {
componentWillMount() {
this.props.fetchTransactions();
}
render() {
const { fetchingTransactions, hasTransactions, transactions } = this.props;
return (
<section className="card">
<div className="card__title-primary">
<h3>{__("Recent Transactions")}</h3>
</div>
<div className="card__content">
{fetchingTransactions &&
<BusyMessage message={__("Loading transactions")} />}
{!fetchingTransactions &&
<TransactionList
transactions={transactions}
emptyMessage={__("You have no recent transactions.")}
/>}
</div>
{hasTransactions &&
<div className="card__actions card__actions--bottom">
<Link
navigate="/history"
label={__("See Full History")}
button="text"
/>
</div>}
</section>
);
}
}
export default TransactionListRecent;

View file

@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { doUserEmailNew } from "actions/user"; import { doUserEmailNew, doUserInviteNew } from "actions/user";
import { import {
selectEmailNewIsPending, selectEmailNewIsPending,
selectEmailNewErrorMessage, selectEmailNewErrorMessage,

View file

@ -31,6 +31,16 @@ class UserEmailNew extends React.PureComponent {
this.handleSubmit(event); this.handleSubmit(event);
}} }}
> >
<p>
{__(
"This process is required to prevent abuse of the rewards program."
)}
</p>
<p>
{__(
"We will also contact you about updates and new content, but you can unsubscribe at any time."
)}
</p>
<FormRow <FormRow
type="text" type="text"
label="Email" label="Email"

View file

@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { doNavigate } from "actions/app"; import { doNavigate } from "actions/navigation";
import { doUserIdentityVerify } from "actions/user"; import { doUserIdentityVerify } from "actions/user";
import rewards from "rewards"; import rewards from "rewards";
import { makeSelectRewardByType } from "selectors/rewards"; import { makeSelectRewardByType } from "selectors/rewards";

View file

@ -26,37 +26,125 @@ class UserVerify extends React.PureComponent {
const { errorMessage, isPending, navigate } = this.props; const { errorMessage, isPending, navigate } = this.props;
return ( return (
<div> <div>
<p> <section className="card card--form">
{__( <div className="card__title-primary">
"To ensure you are a real person, we require a valid credit or debit card." <h1>{__("Final Human Proof")}</h1>
) + </div>
" " + <div className="card__content">
__("There is no charge at all, now or in the future.") + <p>
" "} Finally, please complete <strong>one and only one</strong> of the
<Link options below.
href="https://lbry.io/faq/identity-requirements" </p>
label={__("Read More")} </div>
/> </section>
</p> <section className="card card--form">
{errorMessage && <p className="form-field__error">{errorMessage}</p>} <div className="card__title-primary">
<p> <h3>{__("1) Proof via Credit")}</h3>
<CardVerify </div>
label={__("Link Card and Finish")} <div className="card__content">
disabled={isPending} {__(
token={this.onToken.bind(this)} "If you have a valid credit or debit card, you can use it to instantly prove your humanity."
stripeKey={lbryio.getStripeToken()} ) +
/> " " +
</p> __("There is no charge at all for this, now or in the future.") +
<p> " "}
{__( </div>
"You can continue without this step, but you will not be eligible to earn rewards." <div className="card__actions">
)} {errorMessage &&
</p> <p className="form-field__error">{errorMessage}</p>}
<Link <CardVerify
onClick={() => navigate("/discover")} label={__("Perform Card Verification")}
button="alt" disabled={isPending}
label={__("Skip Rewards")} token={this.onToken.bind(this)}
/> stripeKey={lbryio.getStripeToken()}
/>
</div>
<div className="card__content">
<div className="meta">
{__(
"A $1 authorization may temporarily appear with your provider."
)}{" "}
{" "}
<Link
href="https://lbry.io/faq/identity-requirements"
label={__("Read more about why we do this.")}
/>
</div>
</div>
</section>
<section className="card card--form">
<div className="card__title-primary">
<h3>{__("2) Proof via YouTube")}</h3>
</div>
<div className="card__content">
<p>
{__(
"If you have a YouTube account with subscribers and views, you can sync your account to be granted instant verification."
)}
</p>
</div>
<div className="card__actions">
<Link
href="https://api.lbry.io/yt/connect"
button="alt"
icon="icon-youtube"
label={__("YouTube Account Sync")}
/>
</div>
<div className="card__content">
<div className="meta">
This will not automatically refresh after approval. Once you have
synced your account, just navigate away or click
{" "} <Link navigate="/rewards" label="here" />.
</div>
</div>
</section>
<section className="card card--form">
<div className="card__title-primary">
<h3>{__("3) Proof via Chat")}</h3>
</div>
<div className="card__content">
<p>
{__(
"A moderator capable of approving you is typically available in the #verification channel of our chat room."
)}
</p>
<p>
{__(
"This process will likely involve providing proof of a stable and established online or real-life identity."
)}
</p>
</div>
<div className="card__actions">
<Link
href="https://slack.lbry.io"
button="alt"
icon="icon-slack"
label={__("Join LBRY Chat")}
/>
</div>
</section>
<section className="card card--form">
<div className="card__title-primary">
<h5>{__("Or, Skip It Entirely")}</h5>
</div>
<div className="card__content">
<p className="meta">
{__(
"You can continue without this step, but you will not be eligible to earn rewards."
)}
</p>
</div>
<div className="card__actions">
<Link
onClick={() => navigate("/discover")}
button="alt"
label={__("Skip Rewards")}
/>
</div>
</section>
</div> </div>
); );
} }

View file

@ -1,7 +1,7 @@
import React from "react"; import React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { doCloseModal } from "actions/app"; import { doCloseModal } from "actions/app";
import { doNavigate, doChangeVolume } from "actions/app"; import { doChangeVolume } from "actions/app";
import { selectCurrentModal, selectVolume } from "selectors/app"; import { selectCurrentModal, selectVolume } from "selectors/app";
import { doPurchaseUri, doLoadVideo } from "actions/content"; import { doPurchaseUri, doLoadVideo } from "actions/content";
import { import {

View file

@ -5,7 +5,7 @@ import {
selectReceiveAddress, selectReceiveAddress,
selectGettingNewAddress, selectGettingNewAddress,
} from "selectors/wallet"; } from "selectors/wallet";
import WalletPage from "./view"; import WalletAddress from "./view";
const select = state => ({ const select = state => ({
receiveAddress: selectReceiveAddress(state), receiveAddress: selectReceiveAddress(state),
@ -17,4 +17,4 @@ const perform = dispatch => ({
getNewAddress: () => dispatch(doGetNewAddress()), getNewAddress: () => dispatch(doGetNewAddress()),
}); });
export default connect(select, perform)(WalletPage); export default connect(select, perform)(WalletAddress);

View file

@ -16,6 +16,11 @@ class WalletAddress extends React.PureComponent {
<h3>{__("Wallet Address")}</h3> <h3>{__("Wallet Address")}</h3>
</div> </div>
<div className="card__content"> <div className="card__content">
<p>
{__(
"Use this address to receive credits sent by another user (or yourself)."
)}
</p>
<Address address={receiveAddress} /> <Address address={receiveAddress} />
</div> </div>
<div className="card__actions"> <div className="card__actions">
@ -29,11 +34,6 @@ class WalletAddress extends React.PureComponent {
</div> </div>
<div className="card__content"> <div className="card__content">
<div className="help"> <div className="help">
<p>
{__(
'Other LBRY users may send credits to you by entering this address on the "Send" page.'
)}
</p>
<p> <p>
{__( {__(
"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." "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."

View file

@ -0,0 +1,10 @@
import React from "react";
import { connect } from "react-redux";
import { selectBalance } from "selectors/wallet";
import WalletBalance from "./view";
const select = state => ({
balance: selectBalance(state),
});
export default connect(select, null)(WalletBalance);

View file

@ -0,0 +1,43 @@
import React from "react";
import Link from "component/link";
import { CreditAmount } from "component/common";
const WalletBalance = props => {
const { balance, navigate } = props;
/*
<div className="help">
<Link
onClick={() => navigate("/backup")}
label={__("Backup Your Wallet")}
/>
</div>
*/
return (
<section className="card">
<div className="card__title-primary">
<h3>{__("Balance")}</h3>
</div>
<div className="card__content">
{(balance || balance === 0) &&
<CreditAmount amount={balance} precision={8} />}
</div>
<div className="card__actions card__actions--bottom">
<Link
button="text"
navigate="/send"
disabled={balance === 0}
label={__("Send")}
/>
<Link button="text" navigate="/receive" label={__("Receive")} />
<Link
button="text"
disabled={balance === 0}
navigate="/backup"
label={__("Backup")}
/>
</div>
</section>
);
};
export default WalletBalance;

View file

@ -34,15 +34,15 @@ const WalletSend = props => {
onChange={setAddress} onChange={setAddress}
value={address} value={address}
/> />
</div> <div className="form-row-submit">
<div className="card__actions card__actions--form-submit"> <Link
<Link button="primary"
button="primary" label={__("Send")}
label={__("Send")} onClick={sendToAddress}
onClick={sendToAddress} disabled={!(parseFloat(amount) > 0.0) || !address}
disabled={!(parseFloat(amount) > 0.0) || !address} />
/> <input type="submit" className="hidden" />
<input type="submit" className="hidden" /> </div>
</div> </div>
</form> </form>
</section> </section>

View file

@ -2,7 +2,7 @@ import React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import lbryuri from "lbryuri.js"; import lbryuri from "lbryuri.js";
import { selectWunderBarAddress, selectWunderBarIcon } from "selectors/search"; import { selectWunderBarAddress, selectWunderBarIcon } from "selectors/search";
import { doNavigate } from "actions/app"; import { doNavigate } from "actions/navigation";
import Wunderbar from "./view"; import Wunderbar from "./view";
const select = state => ({ const select = state => ({

View file

@ -1,16 +1,19 @@
export const CHANGE_PATH = "CHANGE_PATH";
export const OPEN_MODAL = "OPEN_MODAL"; export const OPEN_MODAL = "OPEN_MODAL";
export const CLOSE_MODAL = "CLOSE_MODAL"; export const CLOSE_MODAL = "CLOSE_MODAL";
export const HISTORY_NAVIGATE = "HISTORY_NAVIGATE";
export const SHOW_SNACKBAR = "SHOW_SNACKBAR"; export const SHOW_SNACKBAR = "SHOW_SNACKBAR";
export const REMOVE_SNACKBAR_SNACK = "REMOVE_SNACKBAR_SNACK"; export const REMOVE_SNACKBAR_SNACK = "REMOVE_SNACKBAR_SNACK";
export const WINDOW_FOCUSED = "WINDOW_FOCUSED"; export const WINDOW_FOCUSED = "WINDOW_FOCUSED";
export const CHANGE_AFTER_AUTH_PATH = "CHANGE_AFTER_AUTH_PATH";
export const DAEMON_READY = "DAEMON_READY"; export const DAEMON_READY = "DAEMON_READY";
export const DAEMON_VERSION_MATCH = "DAEMON_VERSION_MATCH"; export const DAEMON_VERSION_MATCH = "DAEMON_VERSION_MATCH";
export const DAEMON_VERSION_MISMATCH = "DAEMON_VERSION_MISMATCH"; export const DAEMON_VERSION_MISMATCH = "DAEMON_VERSION_MISMATCH";
export const VOLUME_CHANGED = "VOLUME_CHANGED"; export const VOLUME_CHANGED = "VOLUME_CHANGED";
// Navigation
export const CHANGE_PATH = "CHANGE_PATH";
export const CHANGE_AFTER_AUTH_PATH = "CHANGE_AFTER_AUTH_PATH";
export const WINDOW_SCROLLED = "WINDOW_SCROLLED";
export const HISTORY_NAVIGATE = "HISTORY_NAVIGATE";
// Upgrades // Upgrades
export const UPGRADE_CANCELLED = "UPGRADE_CANCELLED"; export const UPGRADE_CANCELLED = "UPGRADE_CANCELLED";
export const DOWNLOAD_UPGRADE = "DOWNLOAD_UPGRADE"; export const DOWNLOAD_UPGRADE = "DOWNLOAD_UPGRADE";
@ -108,6 +111,15 @@ export const USER_IDENTITY_VERIFY_FAILURE = "USER_IDENTITY_VERIFY_FAILURE";
export const USER_FETCH_STARTED = "USER_FETCH_STARTED"; export const USER_FETCH_STARTED = "USER_FETCH_STARTED";
export const USER_FETCH_SUCCESS = "USER_FETCH_SUCCESS"; export const USER_FETCH_SUCCESS = "USER_FETCH_SUCCESS";
export const USER_FETCH_FAILURE = "USER_FETCH_FAILURE"; export const USER_FETCH_FAILURE = "USER_FETCH_FAILURE";
export const USER_INVITE_STATUS_FETCH_STARTED =
"USER_INVITE_STATUS_FETCH_STARTED";
export const USER_INVITE_STATUS_FETCH_SUCCESS =
"USER_INVITE_STATUS_FETCH_SUCCESS";
export const USER_INVITE_STATUS_FETCH_FAILURE =
"USER_INVITE_STATUS_FETCH_FAILURE";
export const USER_INVITE_NEW_STARTED = "USER_INVITE_NEW_STARTED";
export const USER_INVITE_NEW_SUCCESS = "USER_INVITE_NEW_SUCCESS";
export const USER_INVITE_NEW_FAILURE = "USER_INVITE_NEW_FAILURE";
export const FETCH_ACCESS_TOKEN_SUCCESS = "FETCH_ACCESS_TOKEN_SUCCESS"; export const FETCH_ACCESS_TOKEN_SUCCESS = "FETCH_ACCESS_TOKEN_SUCCESS";
// Rewards // Rewards

View file

@ -9,4 +9,5 @@ export const FIRST_REWARD = "first_reward";
export const AUTHENTICATION_FAILURE = "auth_failure"; export const AUTHENTICATION_FAILURE = "auth_failure";
export const TRANSACTION_FAILED = "transaction_failed"; export const TRANSACTION_FAILED = "transaction_failed";
export const INSUFFICIENT_BALANCE = "insufficient_balance"; export const INSUFFICIENT_BALANCE = "insufficient_balance";
export const REWARD_APPROVAL_REQUIRED = "reward_approval_required";
export const CREDIT_INTRO = "credit_intro"; export const CREDIT_INTRO = "credit_intro";

View file

@ -1,5 +1,8 @@
/*hardcoded names still exist for these in reducers/settings.js - only discovered when debugging*/
/*Many settings are stored in the localStorage by their name -
be careful about changing the value of a settings constant, as doing so can invalidate existing settings*/
export const CREDIT_INTRO_ACKNOWLEDGED = "credit_intro_acknowledged"; export const CREDIT_INTRO_ACKNOWLEDGED = "credit_intro_acknowledged";
export const FIRST_RUN_ACKNOWLEDGED = "welcome_acknowledged"; export const NEW_USER_ACKNOWLEDGED = "welcome_acknowledged";
export const LANGUAGE = "language"; export const LANGUAGE = "language";
export const SHOW_NSFW = "showNsfw"; export const SHOW_NSFW = "showNsfw";
export const SHOW_UNAVAILABLE = "showUnavailable"; export const SHOW_UNAVAILABLE = "showUnavailable";

View file

@ -6,9 +6,9 @@ import SnackBar from "component/snackBar";
import { Provider } from "react-redux"; import { Provider } from "react-redux";
import store from "store.js"; import store from "store.js";
import SplashScreen from "component/splash"; import SplashScreen from "component/splash";
import { doChangePath, doNavigate, doDaemonReady } from "actions/app"; import { doDaemonReady } from "actions/app";
import { doNavigate } from "actions/navigation";
import { doDownloadLanguages } from "actions/settings"; import { doDownloadLanguages } from "actions/settings";
import { toQueryString } from "util/query_params";
import * as types from "constants/action_types"; import * as types from "constants/action_types";
const env = ENV; const env = ENV;
@ -28,23 +28,6 @@ window.addEventListener("contextmenu", event => {
event.preventDefault(); event.preventDefault();
}); });
window.addEventListener("popstate", (event, param) => {
event.preventDefault();
const hash = document.location.hash;
let action;
if (hash !== "") {
const url = hash.replace(/^#/, "");
const { params, scrollY } = event.state || {};
const queryString = toQueryString(params);
app.store.dispatch(doChangePath(`${url}?${queryString}`, { scrollY }));
} else {
app.store.dispatch(doChangePath("/discover"));
}
});
ipcRenderer.on("open-uri-requested", (event, uri) => { ipcRenderer.on("open-uri-requested", (event, uri) => {
if (uri && uri.startsWith("lbry://")) { if (uri && uri.startsWith("lbry://")) {
app.store.dispatch(doNavigate("/show", { uri })); app.store.dispatch(doNavigate("/show", { uri }));
@ -87,13 +70,6 @@ win.on("focus", () => {
dock.setBadge(""); dock.setBadge("");
}); });
const updateProgress = () => {
const state = app.store.getState();
const progress = selectTotalDownloadProgress(state);
win.setProgressBar(progress || -1);
};
const initialState = app.store.getState(); const initialState = app.store.getState();
var init = function() { var init = function() {

View file

@ -1,13 +1,14 @@
import React from "react"; import React from "react";
import rewards from "rewards";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { doCloseModal, doAuthNavigate } from "actions/app"; import { doCloseModal } from "actions/app";
import { doAuthNavigate } from "actions/navigation";
import { doSetClientSetting } from "actions/settings"; import { doSetClientSetting } from "actions/settings";
import { selectUserIsRewardApproved } from "selectors/user"; import { selectUserIsRewardApproved } from "selectors/user";
import { selectBalance } from "selectors/wallet";
import { import {
makeSelectHasClaimedReward, makeSelectHasClaimedReward,
makeSelectRewardByType, makeSelectRewardByType,
selectTotalRewardValue, selectUnclaimedRewardValue,
} from "selectors/rewards"; } from "selectors/rewards";
import * as settings from "constants/settings"; import * as settings from "constants/settings";
import ModalCreditIntro from "./view"; import ModalCreditIntro from "./view";
@ -17,9 +18,9 @@ const select = (state, props) => {
selectReward = makeSelectRewardByType(); selectReward = makeSelectRewardByType();
return { return {
currentBalance: selectBalance(state),
isRewardApproved: selectUserIsRewardApproved(state), isRewardApproved: selectUserIsRewardApproved(state),
reward: selectReward(state, { reward_type: rewards.TYPE_NEW_USER }), totalRewardValue: selectUnclaimedRewardValue(state),
totalRewardValue: selectTotalRewardValue(state),
}; };
}; };

View file

@ -1,40 +1,64 @@
import React from "react"; import React from "react";
import { Modal } from "modal/modal"; import { Modal } from "modal/modal";
import { CreditAmount } from "component/common"; import { CreditAmount, CurrencySymbol } from "component/common";
import Link from "component/link/index"; import Link from "component/link/index";
import { formatCredits } from "util/formatCredits";
const ModalCreditIntro = props => { const ModalCreditIntro = props => {
const { closeModal, totalRewardValue, verifyAccount } = props; const { closeModal, currentBalance, totalRewardValue, verifyAccount } = props;
const totalRewardRounded = Math.round(totalRewardValue / 10) * 10; const totalRewardRounded = Math.round(totalRewardValue / 10) * 10;
return ( return (
<Modal type="custom" isOpen={true} contentLabel="Welcome to LBRY"> <Modal type="custom" isOpen={true} contentLabel="Welcome to LBRY">
<section> <section>
<h3 className="modal__header">{__("Claim Your Credits")}</h3> <h3 className="modal__header">{__("Blockchain 101")}</h3>
<p>
The LBRY network is controlled and powered by credits called{" "}
<em>LBC</em>, a blockchain asset.
</p>
<p>
{__("New patrons receive ")} {" "}
{totalRewardValue
? <CreditAmount amount={totalRewardRounded} />
: <span className="credit-amount">{__("credits")}</span>}
{" "} {__("in rewards for usage and influence of the network.")}
</p>
<p> <p>
LBRY is controlled and powered by a blockchain asset called {" "}
<em><CurrencySymbol /></em>.{" "}
<CurrencySymbol />{" "}
{__( {__(
"You'll also earn weekly bonuses for checking out the greatest new stuff." "is used to publish content, to have a say in the network rules, and to access paid content."
)} )}
</p> </p>
{currentBalance <= 0
? <div>
<p>
You currently have <CreditAmount amount={currentBalance} />, so
the actions you can take are limited.
</p>
<p>
However, there are a variety of ways to get credits, including
more than {" "}
{totalRewardValue
? <CreditAmount amount={totalRewardRounded} />
: <span className="credit-amount">{__("?? credits")}</span>}
{" "}{" "}
{__(
" in rewards available for being a proven human during the LBRY beta."
)}
</p>
</div>
: <div>
<p>
But you probably knew this, since you've already got{" "}
<CreditAmount amount={currentBalance} />.
</p>
</div>}
<div className="modal__buttons"> <div className="modal__buttons">
<Link <Link
button="primary" button="primary"
onClick={verifyAccount} onClick={verifyAccount}
label={__("You Had Me At Free LBC")} label={__("I'm Totally A Human")}
/>
<Link
button="alt"
onClick={closeModal}
label={
currentBalance <= 0 ? __("Use Without LBC") : __("Meh, Not Now")
}
/> />
<Link button="alt" onClick={closeModal} label={__("I Burn Money")} />
</div> </div>
</section> </section>
</Modal> </Modal>

View file

@ -1,13 +1,14 @@
import React from "react"; import React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { doCloseModal, doNavigate } from "actions/app"; import { doCloseModal } from "actions/app";
import { doNavigate } from "actions/navigation";
import ModalInsufficientCredits from "./view"; import ModalInsufficientCredits from "./view";
const select = state => ({}); const select = state => ({});
const perform = dispatch => ({ const perform = dispatch => ({
addFunds: () => { addFunds: () => {
dispatch(doNavigate("/rewards")); dispatch(doNavigate("/wallet"));
dispatch(doCloseModal()); dispatch(doCloseModal());
}, },
closeModal: () => dispatch(doCloseModal()), closeModal: () => dispatch(doCloseModal()),

View file

@ -1,5 +1,6 @@
import React from "react"; import React from "react";
import { Modal } from "modal/modal"; import { Modal } from "modal/modal";
import { CurrencySymbol } from "component/common";
class ModalInsufficientCredits extends React.PureComponent { class ModalInsufficientCredits extends React.PureComponent {
render() { render() {
@ -11,11 +12,12 @@ class ModalInsufficientCredits extends React.PureComponent {
type="confirm" type="confirm"
contentLabel={__("Not enough credits")} contentLabel={__("Not enough credits")}
confirmButtonLabel={__("Get Credits")} confirmButtonLabel={__("Get Credits")}
abortButtonLabel={__("Cancel")} abortButtonLabel={__("Not Now")}
onAborted={closeModal} onAborted={closeModal}
onConfirmed={addFunds} onConfirmed={addFunds}
> >
{__("More LBRY credits are required to purchase this.")} <h3 className="modal__header">{__("More Credits Required")}</h3>
<p>You'll need more <CurrencySymbol /> to do this.</p>
</Modal> </Modal>
); );
} }

View file

@ -1,9 +1,8 @@
import React from "react"; import React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { doCloseModal, doHistoryBack } from "actions/app"; import { doCloseModal } from "actions/app";
import { doDeleteFileAndGoBack } from "actions/file_info"; import { doDeleteFileAndGoBack } from "actions/file_info";
import { makeSelectClaimForUriIsMine } from "selectors/claims"; import { makeSelectClaimForUriIsMine } from "selectors/claims";
import batchActions from "util/batchActions";
import ModalRemoveFile from "./view"; import ModalRemoveFile from "./view";

View file

@ -0,0 +1,15 @@
import React from "react";
import { connect } from "react-redux";
import { doCloseModal } from "actions/app";
import { doAuthNavigate } from "actions/navigation";
import ModalRewardApprovalRequired from "./view";
const perform = dispatch => ({
doAuth: () => {
dispatch(doCloseModal());
dispatch(doAuthNavigate());
},
closeModal: () => dispatch(doCloseModal()),
});
export default connect(null, perform)(ModalRewardApprovalRequired);

View file

@ -0,0 +1,33 @@
import React from "react";
import { Modal } from "modal/modal";
class ModalRewardApprovalRequired extends React.PureComponent {
render() {
const { closeModal, doAuth } = this.props;
return (
<Modal
isOpen={true}
contentLabel={__("Human Verification Required")}
onConfirmed={doAuth}
onAborted={closeModal}
type="confirm"
confirmButtonLabel={__("I'm Totally Real")}
abortButtonLabel={__("Never Mind")}
>
<section>
<h3 className="modal__header">
{__("This is awkward. Are you real?")}
</h3>
<p>
{__(
"Before we can give you any credits, we need to perform a brief check to make sure you're a new and unique person."
)}
</p>
</section>
</Modal>
);
}
}
export default ModalRewardApprovalRequired;

View file

@ -1,20 +1,31 @@
import React from "react"; import React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { selectCurrentModal } from "selectors/app";
import { doOpenModal } from "actions/app"; import { doOpenModal } from "actions/app";
import { selectWelcomeModalAcknowledged } from "selectors/app"; import * as settings from "constants/settings";
import { selectCurrentModal } from "selectors/app";
import { selectCurrentPage } from "selectors/navigation";
import { selectCostForCurrentPageUri } from "selectors/cost_info";
import { makeSelectClientSetting } from "selectors/settings";
import { selectUser } from "selectors/user"; import { selectUser } from "selectors/user";
import { selectBalance } from "selectors/wallet";
import ModalRouter from "./view"; import ModalRouter from "./view";
import * as modals from "constants/modal_types";
const select = (state, props) => ({ const select = (state, props) => ({
balance: selectBalance(state),
showPageCost: selectCostForCurrentPageUri(state),
modal: selectCurrentModal(state), modal: selectCurrentModal(state),
isWelcomeAcknowledged: selectWelcomeModalAcknowledged(state), page: selectCurrentPage(state),
isWelcomeAcknowledged: makeSelectClientSetting(
settings.NEW_USER_ACKNOWLEDGED
)(state),
isCreditIntroAcknowledged: makeSelectClientSetting(
settings.CREDIT_INTRO_ACKNOWLEDGED
)(state),
user: selectUser(state), user: selectUser(state),
}); });
const perform = dispatch => ({ const perform = dispatch => ({
openWelcomeModal: () => dispatch(doOpenModal(modals.WELCOME)), openModal: modal => dispatch(doOpenModal(modal)),
}); });
export default connect(select, perform)(ModalRouter); export default connect(select, perform)(ModalRouter);

View file

@ -6,33 +6,97 @@ import ModalInsufficientCredits from "modal/modalInsufficientCredits";
import ModalUpgrade from "modal/modalUpgrade"; import ModalUpgrade from "modal/modalUpgrade";
import ModalWelcome from "modal/modalWelcome"; import ModalWelcome from "modal/modalWelcome";
import ModalFirstReward from "modal/modalFirstReward"; import ModalFirstReward from "modal/modalFirstReward";
import ModalRewardApprovalRequired from "modal/modalRewardApprovalRequired";
import ModalCreditIntro from "modal/modalCreditIntro"; import ModalCreditIntro from "modal/modalCreditIntro";
import ModalTransactionFailed from "modal/modalTransactionFailed"; import ModalTransactionFailed from "modal/modalTransactionFailed";
import ModalInsufficientBalance from "modal/modalInsufficientBalance"; import ModalInsufficientBalance from "modal/modalInsufficientBalance";
import * as modals from "constants/modal_types"; import * as modals from "constants/modal_types";
class ModalRouter extends React.PureComponent { class ModalRouter extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
lastTransitionModal: null,
lastTransitionPage: null,
};
}
componentWillMount() { componentWillMount() {
this.showWelcome(this.props); this.showTransitionModals(this.props);
} }
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
this.showWelcome(nextProps); this.showTransitionModals(nextProps);
} }
showWelcome(props) { showTransitionModals(props) {
const { isWelcomeAcknowledged, openWelcomeModal, user } = props; const { modal, openModal, page } = props;
if (modal) {
return;
}
const transitionModal = [
this.checkShowWelcome,
this.checkShowCreditIntro,
this.checkShowInsufficientCredits,
].reduce((acc, func) => {
return !acc ? func.bind(this)(props) : acc;
}, false);
if (
transitionModal &&
(transitionModal != this.state.lastTransitionModal ||
page != this.state.lastTransitionPage)
) {
openModal(transitionModal);
this.setState({
lastTransitionModal: transitionModal,
lastTransitionPage: page,
});
}
}
checkShowWelcome(props) {
const { isWelcomeAcknowledged, user } = props;
if ( if (
!isWelcomeAcknowledged && !isWelcomeAcknowledged &&
user && user &&
!user.is_reward_approved && !user.is_reward_approved &&
!user.is_identity_verified !user.is_identity_verified
) { ) {
openWelcomeModal(); return modals.WELCOME;
} }
} }
checkShowCreditIntro(props) {
const { page, isCreditIntroAcknowledged, user } = props;
if (
!isCreditIntroAcknowledged &&
user &&
!user.is_reward_approved &&
(["rewards", "send", "receive", "publish", "wallet"].includes(page) ||
this.isPaidShowPage(props))
) {
return modals.CREDIT_INTRO;
}
}
checkShowInsufficientCredits(props) {
const { balance, page } = props;
if (balance <= 0 && ["send", "publish"].includes(page)) {
return modals.INSUFFICIENT_CREDITS;
}
}
isPaidShowPage(props) {
const { page, showPageCost } = props;
return page === "show" && showPageCost > 0;
}
render() { render() {
const { modal } = this.props; const { modal } = this.props;
@ -57,6 +121,8 @@ class ModalRouter extends React.PureComponent {
return <ModalTransactionFailed />; return <ModalTransactionFailed />;
case modals.INSUFFICIENT_BALANCE: case modals.INSUFFICIENT_BALANCE:
return <ModalInsufficientBalance />; return <ModalInsufficientBalance />;
case modals.REWARD_APPROVAL_REQUIRED:
return <ModalRewardApprovalRequired />;
default: default:
return null; return null;
} }

View file

@ -8,9 +8,8 @@ import ModalWelcome from "./view";
const perform = dispatch => () => ({ const perform = dispatch => () => ({
closeModal: () => { closeModal: () => {
dispatch(doSetClientSetting(settings.FIRST_RUN_ACKNOWLEDGED, true)); dispatch(doSetClientSetting(settings.NEW_USER_ACKNOWLEDGED, true));
dispatch(doCloseModal()); dispatch(doCloseModal());
dispatch(doOpenModal(modals.CREDIT_INTRO));
}, },
}); });

View file

@ -22,7 +22,11 @@ const ModalWelcome = props => {
)} )}
</p> </p>
<div className="modal__buttons"> <div className="modal__buttons">
<Link button="primary" onClick={closeModal} label={__("Continue")} /> <Link
button="primary"
onClick={closeModal}
label={__("Blockchain Centaurs? I'm In")}
/>
</div> </div>
</section> </section>
</Modal> </Modal>

View file

@ -1,7 +1,7 @@
import React from "react"; import React from "react";
import { doNavigate } from "actions/app"; import { doNavigate } from "actions/navigation";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { selectPathAfterAuth } from "selectors/app"; import { selectPathAfterAuth } from "selectors/navigation";
import { import {
selectAuthenticationIsPending, selectAuthenticationIsPending,
selectEmailToVerify, selectEmailToVerify,

View file

@ -30,11 +30,11 @@ export class AuthPage extends React.PureComponent {
const { email, isPending, isVerificationCandidate, user } = this.props; const { email, isPending, isVerificationCandidate, user } = this.props;
if (isPending || (user && !user.has_verified_email && !email)) { if (isPending || (user && !user.has_verified_email && !email)) {
return __("Welcome to LBRY"); return __("Human Proofing");
} else if (user && !user.has_verified_email) { } else if (user && !user.has_verified_email) {
return __("Confirm Email"); return __("Confirm Email");
} else if (user && !user.is_identity_verified && !user.is_reward_approved) { } else if (user && !user.is_identity_verified && !user.is_reward_approved) {
return __("Confirm Identity"); return __("Final Verification");
} else { } else {
return __("Welcome to LBRY"); return __("Welcome to LBRY");
} }
@ -44,51 +44,45 @@ export class AuthPage extends React.PureComponent {
const { email, isPending, isVerificationCandidate, user } = this.props; const { email, isPending, isVerificationCandidate, user } = this.props;
if (isPending) { if (isPending) {
return <BusyMessage message={__("Authenticating")} />; return [<BusyMessage message={__("Authenticating")} />, true];
} else if (user && !user.has_verified_email && !email) { } else if (user && !user.has_verified_email && !email) {
return <UserEmailNew />; return [<UserEmailNew />, true];
} else if (user && !user.has_verified_email) { } else if (user && !user.has_verified_email) {
return <UserEmailVerify />; return [<UserEmailVerify />, true];
} else if (user && !user.is_identity_verified) { } else if (user && !user.is_identity_verified) {
return <UserVerify />; return [<UserVerify />, false];
} else { } else {
return <span className="empty">{__("No further steps.")}</span>; return [<span className="empty">{__("No further steps.")}</span>, true];
} }
} }
render() { render() {
const { email, user, isPending, navigate } = this.props; const { email, user, isPending, navigate } = this.props;
const [innerContent, useTemplate] = this.renderMain();
return ( return useTemplate
<main className=""> ? <main>
<section className="card card--form"> <section className="card card--form">
<div className="card__title-primary"> <div className="card__title-primary">
<h1>{this.getTitle()}</h1> <h1>{this.getTitle()}</h1>
</div>
<div className="card__content">
{!isPending &&
!email &&
user &&
!user.has_verified_email &&
<p>
{__("Create a verified identity and receive LBC rewards.")}
</p>}
{this.renderMain()}
</div>
<div className="card__content">
<div className="help">
{__(
"This information is disclosed only to LBRY, Inc. and not to the LBRY network. It is only required to earn LBRY rewards."
) + " "}
<Link
onClick={() => navigate("/discover")}
label={__("Return home")}
/>.
</div> </div>
</div> <div className="card__content">
</section> {innerContent}
</main> </div>
); <div className="card__content">
<div className="help">
{__(
"This information is disclosed only to LBRY, Inc. and not to the LBRY network. It is only required to earn LBRY rewards."
) + " "}
<Link
onClick={() => navigate("/discover")}
label={__("Return home")}
/>.
</div>
</div>
</section>
</main>
: innerContent;
} }
} }

View file

@ -9,8 +9,8 @@ import {
makeSelectClaimsInChannelForCurrentPage, makeSelectClaimsInChannelForCurrentPage,
makeSelectFetchingChannelClaims, makeSelectFetchingChannelClaims,
} from "selectors/claims"; } from "selectors/claims";
import { selectCurrentParams } from "selectors/app"; import { selectCurrentParams } from "selectors/navigation";
import { doNavigate } from "actions/app"; import { doNavigate } from "actions/navigation";
import { makeSelectTotalPagesForChannel } from "selectors/content"; import { makeSelectTotalPagesForChannel } from "selectors/content";
import ChannelPage from "./view"; import ChannelPage from "./view";

View file

@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { doNavigate } from "actions/app"; import { doNavigate } from "actions/navigation";
import { doFetchFileInfo } from "actions/file_info"; import { doFetchFileInfo } from "actions/file_info";
import { makeSelectFileInfoForUri } from "selectors/file_info"; import { makeSelectFileInfoForUri } from "selectors/file_info";
import { selectRewardContentClaimIds } from "selectors/content"; import { selectRewardContentClaimIds } from "selectors/content";

View file

@ -10,7 +10,7 @@ import {
selectIsFetchingClaimListMine, selectIsFetchingClaimListMine,
} from "selectors/claims"; } from "selectors/claims";
import { doFetchClaimListMine } from "actions/content"; import { doFetchClaimListMine } from "actions/content";
import { doNavigate } from "actions/app"; import { doNavigate } from "actions/navigation";
import { doCancelAllResolvingUris } from "actions/content"; import { doCancelAllResolvingUris } from "actions/content";
import FileListDownloaded from "./view"; import FileListDownloaded from "./view";

View file

@ -7,7 +7,7 @@ import {
selectIsFetchingClaimListMine, selectIsFetchingClaimListMine,
} from "selectors/claims"; } from "selectors/claims";
import { doClaimRewardType } from "actions/rewards"; import { doClaimRewardType } from "actions/rewards";
import { doNavigate } from "actions/app"; import { doNavigate } from "actions/navigation";
import { doCancelAllResolvingUris } from "actions/content"; import { doCancelAllResolvingUris } from "actions/content";
import FileListPublished from "./view"; import FileListPublished from "./view";

View file

@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import { doNavigate } from "actions/app"; import { doAuthNavigate } from "actions/navigation";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { doFetchAccessToken } from "actions/user"; import { doFetchAccessToken } from "actions/user";
import { selectAccessToken, selectUser } from "selectors/user"; import { selectAccessToken, selectUser } from "selectors/user";
@ -11,7 +11,7 @@ const select = state => ({
}); });
const perform = dispatch => ({ const perform = dispatch => ({
navigate: (path, params) => dispatch(doNavigate(path, params)), doAuth: () => dispatch(doAuthNavigate("/help")),
fetchAccessToken: () => dispatch(doFetchAccessToken()), fetchAccessToken: () => dispatch(doFetchAccessToken()),
}); });

View file

@ -3,7 +3,7 @@ import React from "react";
import lbry from "lbry.js"; import lbry from "lbry.js";
import Link from "component/link"; import Link from "component/link";
import SubHeader from "component/subHeader"; import SubHeader from "component/subHeader";
import { BusyMessage } from "component/common"; import { BusyMessage, Icon } from "component/common";
class HelpPage extends React.PureComponent { class HelpPage extends React.PureComponent {
constructor(props) { constructor(props) {
@ -50,7 +50,7 @@ class HelpPage extends React.PureComponent {
render() { render() {
let ver, osName, platform, newVerLink; let ver, osName, platform, newVerLink;
const { navigate, user } = this.props; const { doAuth, user } = this.props;
if (this.state.versionInfo) { if (this.state.versionInfo) {
ver = this.state.versionInfo; ver = this.state.versionInfo;
@ -119,7 +119,7 @@ class HelpPage extends React.PureComponent {
<p>{__("Did you find something wrong?")}</p> <p>{__("Did you find something wrong?")}</p>
<p> <p>
<Link <Link
onClick={() => navigate("report")} navigate="/report"
label={__("Submit a Bug Report")} label={__("Submit a Bug Report")}
icon="icon-bug" icon="icon-bug"
button="alt" button="alt"
@ -143,7 +143,7 @@ class HelpPage extends React.PureComponent {
</p> </p>
: <p>{__("Your copy of LBRY is up to date.")}</p>} : <p>{__("Your copy of LBRY is up to date.")}</p>}
{this.state.uiVersion && ver {this.state.uiVersion && ver
? <table className="table-standard"> ? <table className="table-standard table-stretch table-standard--definition-list">
<tbody> <tbody>
<tr> <tr>
<th>{__("App")}</th> <th>{__("App")}</th>
@ -162,7 +162,21 @@ class HelpPage extends React.PureComponent {
<td> <td>
{user && user.primary_email {user && user.primary_email
? user.primary_email ? user.primary_email
: <span className="empty">{__("none")}</span>} : <span>
<span className="empty">{__("none")} </span>
(<Link
onClick={() => doAuth()}
label={__("set email")}
/>)
</span>}
</td>
</tr>
<tr>
<th>{__("Reward Eligible")}</th>
<td>
{user && user.is_reward_approved
? <Icon icon="icon-check" />
: <Icon icon="icon-ban" />}
</td> </td>
</tr> </tr>
<tr> <tr>

View file

@ -0,0 +1,19 @@
import React from "react";
import { connect } from "react-redux";
import InvitePage from "./view";
import { doFetchInviteStatus } from "actions/user";
import {
selectUserInviteStatusFailed,
selectUserInviteStatusIsPending,
} from "selectors/user";
const select = state => ({
isFailed: selectUserInviteStatusFailed(state),
isPending: selectUserInviteStatusIsPending(state),
});
const perform = dispatch => ({
fetchInviteStatus: () => dispatch(doFetchInviteStatus()),
});
export default connect(select, perform)(InvitePage);

View file

@ -0,0 +1,32 @@
import React from "react";
import { BusyMessage } from "component/common";
import SubHeader from "component/subHeader";
import InviteNew from "component/inviteNew";
import InviteList from "component/inviteList";
class InvitePage extends React.PureComponent {
componentWillMount() {
this.props.fetchInviteStatus();
}
render() {
const { isPending, isFailed } = this.props;
return (
<main className="main--single-column">
<SubHeader />
{isPending &&
<BusyMessage message={__("Checking your invite status")} />}
{!isPending &&
isFailed &&
<span className="empty">
{__("Failed to retrieve invite status.")}
</span>}
{!isPending && !isFailed && <InviteNew />}
{!isPending && !isFailed && <InviteList />}
</main>
);
}
}
export default InvitePage;

View file

@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { doNavigate, doHistoryBack } from "actions/app"; import { doNavigate, doHistoryBack } from "actions/navigation";
import { doClaimRewardType } from "actions/rewards"; import { doClaimRewardType } from "actions/rewards";
import { import {
selectMyClaims, selectMyClaims,
@ -16,10 +16,12 @@ import {
doCreateChannel, doCreateChannel,
doPublish, doPublish,
} from "actions/content"; } from "actions/content";
import { selectBalance } from "selectors/wallet";
import rewards from "rewards"; import rewards from "rewards";
import PublishPage from "./view"; import PublishPage from "./view";
const select = state => ({ const select = state => ({
balance: selectBalance(state),
myClaims: selectMyClaims(state), myClaims: selectMyClaims(state),
fetchingChannels: selectFetchingMyChannels(state), fetchingChannels: selectFetchingMyChannels(state),
channels: selectMyChannelClaims(state), channels: selectMyChannelClaims(state),

View file

@ -0,0 +1,5 @@
import React from "react";
import { connect } from "react-redux";
import ReceiveCreditsPage from "./view";
export default connect(null, null)(ReceiveCreditsPage);

View file

@ -0,0 +1,34 @@
import React from "react";
import SubHeader from "component/subHeader";
import Link from "component/link";
import WalletAddress from "component/walletAddress";
const ReceiveCreditsPage = props => {
return (
<main className="main--single-column">
<SubHeader />
<WalletAddress />
<section className="card">
<div className="card__title-primary">
<h3>{__("Where To Find Credits")}</h3>
</div>
<div className="card__content">
<p>
{
"LBRY credits can be purchased on exchanges, earned for contributions, for mining, and more."
}
</p>
</div>
<div className="card__actions">
<Link
button="alt"
href="https://lbry.io/faq/earn-credits"
label={__("Read More")}
/>
</div>
</section>
</main>
);
};
export default ReceiveCreditsPage;

View file

@ -1,23 +1,18 @@
import React from "react"; import React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { import {
makeSelectRewardByType,
selectFetchingRewards, selectFetchingRewards,
selectRewards, selectUnclaimedRewards,
} from "selectors/rewards"; } from "selectors/rewards";
import { selectUser } from "selectors/user"; import { selectUser } from "selectors/user";
import { doAuthNavigate, doNavigate } from "actions/app"; import { doAuthNavigate, doNavigate } from "actions/navigation";
import { doRewardList } from "actions/rewards"; import { doRewardList } from "actions/rewards";
import rewards from "rewards";
import RewardsPage from "./view"; import RewardsPage from "./view";
const select = (state, props) => { const select = (state, props) => {
const selectReward = makeSelectRewardByType();
return { return {
fetching: selectFetchingRewards(state), fetching: selectFetchingRewards(state),
rewards: selectRewards(state), rewards: selectUnclaimedRewards(state),
newUserReward: selectReward(state, { reward_type: rewards.TYPE_NEW_USER }),
user: selectUser(state), user: selectUser(state),
}; };
}; };

View file

@ -1,31 +1,9 @@
import React from "react"; import React from "react";
import { BusyMessage, CreditAmount, Icon } from "component/common"; import { BusyMessage } from "component/common";
import RewardListClaimed from "component/rewardListClaimed";
import RewardTile from "component/rewardTile";
import SubHeader from "component/subHeader"; import SubHeader from "component/subHeader";
import Link from "component/link"; import Link from "component/link";
import RewardLink from "component/rewardLink";
const RewardTile = props => {
const { reward } = props;
const claimed = !!reward.transaction_id;
return (
<section className="card">
<div className="card__inner">
<div className="card__title-primary">
<CreditAmount amount={reward.reward_amount} />
<h3>{reward.reward_title}</h3>
</div>
<div className="card__actions">
{claimed
? <span><Icon icon="icon-check" /> {__("Reward claimed.")}</span>
: <RewardLink reward_type={reward.reward_type} />}
</div>
<div className="card__content">{reward.reward_description}</div>
</div>
</section>
);
};
class RewardsPage extends React.PureComponent { class RewardsPage extends React.PureComponent {
componentDidMount() { componentDidMount() {
@ -44,32 +22,8 @@ class RewardsPage extends React.PureComponent {
} }
} }
render() { renderPageHeader() {
const { doAuth, fetching, navigate, rewards, user } = this.props; const { doAuth, navigate, user } = this.props;
let content, cardHeader;
if (fetching) {
content = (
<div className="card__content">
<BusyMessage message={__("Fetching rewards")} />
</div>
);
} else if (rewards.length > 0) {
content = (
<div>
{rewards.map(reward =>
<RewardTile key={reward.reward_type} reward={reward} />
)}
</div>
);
} else {
content = (
<div className="card__content empty">
{__("Failed to load rewards.")}
</div>
);
}
if (user && !user.is_reward_approved) { if (user && !user.is_reward_approved) {
if ( if (
@ -77,20 +31,27 @@ class RewardsPage extends React.PureComponent {
!user.has_verified_email || !user.has_verified_email ||
!user.is_identity_verified !user.is_identity_verified
) { ) {
cardHeader = ( return (
<div> <section className="card">
<div className="card__title-primary">
<h3>{__("Humans Only")}</h3>
</div>
<div className="card__content empty"> <div className="card__content empty">
<p> <p>
{__("Only verified accounts are eligible to earn rewards.")} {__("Rewards are for human beings only.")}
{" "}
{__(
"You'll have to prove you're one of us before you can claim any rewards."
)}
</p> </p>
</div> </div>
<div className="card__content"> <div className="card__content">
<Link onClick={doAuth} button="primary" label="Become Verified" /> <Link onClick={doAuth} button="primary" label="Prove Humanity" />
</div> </div>
</div> </section>
); );
} else { } else {
cardHeader = ( return (
<div className="card__content"> <div className="card__content">
<p> <p>
{__( {__(
@ -122,25 +83,52 @@ class RewardsPage extends React.PureComponent {
</div> </div>
); );
} }
}
}
renderUnclaimedRewards() {
const { fetching, rewards, user } = this.props;
if (fetching) {
return (
<div className="card__content">
<BusyMessage message={__("Fetching rewards")} />
</div>
);
} else if (user === null) { } else if (user === null) {
cardHeader = ( return (
<div> <div className="card__content empty">
<div className="card__content empty"> <p>
<p> {__(
{__( "This application is unable to earn rewards due to an authentication failure."
"This application is unable to earn rewards due to an authentication failure." )}
)} </p>
</p> </div>
</div> );
} else if (!rewards || rewards.length <= 0) {
return (
<div className="card__content empty">
{__("Failed to load rewards.")}
</div>
);
} else {
return (
<div className="card-grid">
{rewards.map(reward =>
<RewardTile key={reward.reward_type} reward={reward} />
)}
</div> </div>
); );
} }
}
render() {
return ( return (
<main className="main--single-column"> <main className="main--single-column">
<SubHeader /> <SubHeader />
{cardHeader && <section className="card">{cardHeader}</section>} {this.renderPageHeader()}
{content} {this.renderUnclaimedRewards()}
{<RewardListClaimed />}
</main> </main>
); );
} }

View file

@ -5,7 +5,7 @@ import {
selectSearchQuery, selectSearchQuery,
selectCurrentSearchResults, selectCurrentSearchResults,
} from "selectors/search"; } from "selectors/search";
import { doNavigate } from "actions/app"; import { doNavigate } from "actions/navigation";
import SearchPage from "./view"; import SearchPage from "./view";
const select = state => ({ const select = state => ({

Some files were not shown because too many files have changed in this diff Show more