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]
current_version = 0.14.3
current_version = 0.15.0
commit = True
tag = True
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]
### Added
* 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
* 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
* 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
*
*
### 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

View file

@ -51,7 +51,7 @@ let daemonStopRequested = false;
let readyToQuit = false;
// 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;
function processRequestedUri(uri) {

View file

@ -1,6 +1,6 @@
{
"name": "LBRY",
"version": "0.14.3",
"version": "0.15.0",
"main": "main.js",
"description": "A browser for the LBRY network, a digital marketplace controlled by its users.",
"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,
selectUpgradeDownloadItem,
selectUpgradeFilename,
selectPageTitle,
selectCurrentPage,
selectCurrentParams,
selectHistoryBack,
selectHistoryForward,
} from "selectors/app";
import { doSearch } from "actions/search";
import { doFetchDaemonSettings } from "actions/settings";
import { doAuthenticate } from "actions/user";
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 path = require("path");
@ -24,126 +16,6 @@ const { download } = remote.require("electron-dl");
const fs = remote.require("fs");
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) {
return {
type: types.OPEN_MODAL,
@ -301,25 +173,8 @@ export function doAlertError(errorList) {
export function doDaemonReady() {
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({
type: types.DAEMON_READY,
data: { page },
});
dispatch({ type: types.DAEMON_READY });
dispatch(doFetchDaemonSettings());
dispatch(doFileList());
};

View file

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

View file

@ -35,7 +35,11 @@ export function doFetchCostInfoForUri(uri) {
if (isGenerous && claim) {
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) {
resolve({ cost: 0, includesData: true });
} else if (fee.currency == "LBC") {

View file

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

View file

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

View file

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

View file

@ -1,7 +1,7 @@
import * as types from "constants/action_types";
import * as modals from "constants/modal_types";
import lbryio from "lbryio";
import { doOpenModal } from "actions/app";
import { doOpenModal, doShowSnackBar } from "actions/app";
import { doRewardList, doClaimRewardType } from "actions/rewards";
import { selectEmailToVerify, selectUser } from "selectors/user";
import rewards from "rewards";
@ -19,6 +19,7 @@ export function doAuthenticate() {
data: { user },
});
dispatch(doRewardList());
dispatch(doFetchInviteStatus());
})
.catch(error => {
dispatch(doOpenModal(modals.AUTHENTICATION_FAILURE));
@ -172,3 +173,62 @@ export function doFetchAccessToken() {
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,
logs: logs,
log: function(message) {
console.log(message);
logs.push(message);
},
};

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,9 +1,16 @@
import React from "react";
import { formatCredits } from "util/formatCredits";
import { connect } from "react-redux";
import { selectIsBackDisabled, selectIsForwardDisabled } from "selectors/app";
import {
selectIsBackDisabled,
selectIsForwardDisabled,
} from "selectors/navigation";
import { selectBalance } from "selectors/wallet";
import { doNavigate, doHistoryBack, doHistoryForward } from "actions/app";
import {
doNavigate,
doHistoryBack,
doHistoryForward,
} from "actions/navigation";
import Header from "./view";
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 { connect } from "react-redux";
import { doNavigate } from "actions/navigation";
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 {
href,
title,
onClick,
style,
label,
icon,
@ -13,6 +12,8 @@ const Link = props => {
button,
disabled,
children,
navigate,
doNavigate,
} = props;
const className =
@ -21,6 +22,12 @@ const Link = props => {
(button ? " button-block button-" + button + " button-set-item" : "") +
(disabled ? " disabled" : "");
const onClick = !props.onClick && navigate
? () => {
doNavigate(navigate);
}
: props.onClick;
let content;
if (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 { connect } from "react-redux";
import { doNavigate } from "actions/app";
import { doNavigate } from "actions/navigation";
import NsfwOverlay from "./view";
const perform = dispatch => ({

View file

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

View file

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

View file

@ -9,15 +9,18 @@ const RewardLink = props => {
claimReward,
clearError,
errorMessage,
label,
isPending,
} = props;
return (
<div className="reward-link">
<Link
button={button ? button : "alt"}
button={button}
disabled={isPending}
label={isPending ? __("Claiming...") : __("Claim Reward")}
label={
isPending ? __("Claiming...") : label ? label : __("Claim Reward")
}
onClick={() => {
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 { connect } from "react-redux";
import Router from "./view.jsx";
import { selectCurrentPage, selectCurrentParams } from "selectors/app.js";
import {
selectCurrentPage,
selectCurrentParams,
} from "selectors/navigation.js";
const select = state => ({
params: selectCurrentParams(state),

View file

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

View file

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

View file

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

View file

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

View file

@ -1,23 +1,5 @@
import React from "react";
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";
const select = state => ({
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);
export default connect(null, null)(TransactionList);

View file

@ -1,103 +1,57 @@
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 {
componentWillMount() {
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>
);
});
}
const TransactionList = props => {
const { emptyMessage, transactions } = props;
if (!transactions || !transactions.length) {
return (
<section className="card">
<div className="card__title-primary">
<h3>{__("Transaction History")}</h3>
</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>
<div className="empty">
{emptyMessage || __("No transactions to list.")}
</div>
);
}
}
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;

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 { connect } from "react-redux";
import { doUserEmailNew } from "actions/user";
import { doUserEmailNew, doUserInviteNew } from "actions/user";
import {
selectEmailNewIsPending,
selectEmailNewErrorMessage,

View file

@ -31,6 +31,16 @@ class UserEmailNew extends React.PureComponent {
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
type="text"
label="Email"

View file

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

View file

@ -26,37 +26,125 @@ class UserVerify extends React.PureComponent {
const { errorMessage, isPending, navigate } = this.props;
return (
<div>
<p>
{__(
"To ensure you are a real person, we require a valid credit or debit card."
) +
" " +
__("There is no charge at all, now or in the future.") +
" "}
<Link
href="https://lbry.io/faq/identity-requirements"
label={__("Read More")}
/>
</p>
{errorMessage && <p className="form-field__error">{errorMessage}</p>}
<p>
<CardVerify
label={__("Link Card and Finish")}
disabled={isPending}
token={this.onToken.bind(this)}
stripeKey={lbryio.getStripeToken()}
/>
</p>
<p>
{__(
"You can continue without this step, but you will not be eligible to earn rewards."
)}
</p>
<Link
onClick={() => navigate("/discover")}
button="alt"
label={__("Skip Rewards")}
/>
<section className="card card--form">
<div className="card__title-primary">
<h1>{__("Final Human Proof")}</h1>
</div>
<div className="card__content">
<p>
Finally, please complete <strong>one and only one</strong> of the
options below.
</p>
</div>
</section>
<section className="card card--form">
<div className="card__title-primary">
<h3>{__("1) Proof via Credit")}</h3>
</div>
<div className="card__content">
{__(
"If you have a valid credit or debit card, you can use it to instantly prove your humanity."
) +
" " +
__("There is no charge at all for this, now or in the future.") +
" "}
</div>
<div className="card__actions">
{errorMessage &&
<p className="form-field__error">{errorMessage}</p>}
<CardVerify
label={__("Perform Card Verification")}
disabled={isPending}
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>
);
}

View file

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

View file

@ -5,7 +5,7 @@ import {
selectReceiveAddress,
selectGettingNewAddress,
} from "selectors/wallet";
import WalletPage from "./view";
import WalletAddress from "./view";
const select = state => ({
receiveAddress: selectReceiveAddress(state),
@ -17,4 +17,4 @@ const perform = dispatch => ({
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>
</div>
<div className="card__content">
<p>
{__(
"Use this address to receive credits sent by another user (or yourself)."
)}
</p>
<Address address={receiveAddress} />
</div>
<div className="card__actions">
@ -29,11 +34,6 @@ class WalletAddress extends React.PureComponent {
</div>
<div className="card__content">
<div className="help">
<p>
{__(
'Other LBRY users may send credits to you by entering this address on the "Send" page.'
)}
</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."

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}
value={address}
/>
</div>
<div className="card__actions card__actions--form-submit">
<Link
button="primary"
label={__("Send")}
onClick={sendToAddress}
disabled={!(parseFloat(amount) > 0.0) || !address}
/>
<input type="submit" className="hidden" />
<div className="form-row-submit">
<Link
button="primary"
label={__("Send")}
onClick={sendToAddress}
disabled={!(parseFloat(amount) > 0.0) || !address}
/>
<input type="submit" className="hidden" />
</div>
</div>
</form>
</section>

View file

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

View file

@ -1,16 +1,19 @@
export const CHANGE_PATH = "CHANGE_PATH";
export const OPEN_MODAL = "OPEN_MODAL";
export const CLOSE_MODAL = "CLOSE_MODAL";
export const HISTORY_NAVIGATE = "HISTORY_NAVIGATE";
export const SHOW_SNACKBAR = "SHOW_SNACKBAR";
export const REMOVE_SNACKBAR_SNACK = "REMOVE_SNACKBAR_SNACK";
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_VERSION_MATCH = "DAEMON_VERSION_MATCH";
export const DAEMON_VERSION_MISMATCH = "DAEMON_VERSION_MISMATCH";
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
export const UPGRADE_CANCELLED = "UPGRADE_CANCELLED";
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_SUCCESS = "USER_FETCH_SUCCESS";
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";
// Rewards

View file

@ -9,4 +9,5 @@ export const FIRST_REWARD = "first_reward";
export const AUTHENTICATION_FAILURE = "auth_failure";
export const TRANSACTION_FAILED = "transaction_failed";
export const INSUFFICIENT_BALANCE = "insufficient_balance";
export const REWARD_APPROVAL_REQUIRED = "reward_approval_required";
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 FIRST_RUN_ACKNOWLEDGED = "welcome_acknowledged";
export const NEW_USER_ACKNOWLEDGED = "welcome_acknowledged";
export const LANGUAGE = "language";
export const SHOW_NSFW = "showNsfw";
export const SHOW_UNAVAILABLE = "showUnavailable";

View file

@ -6,9 +6,9 @@ import SnackBar from "component/snackBar";
import { Provider } from "react-redux";
import store from "store.js";
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 { toQueryString } from "util/query_params";
import * as types from "constants/action_types";
const env = ENV;
@ -28,23 +28,6 @@ window.addEventListener("contextmenu", event => {
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) => {
if (uri && uri.startsWith("lbry://")) {
app.store.dispatch(doNavigate("/show", { uri }));
@ -87,13 +70,6 @@ win.on("focus", () => {
dock.setBadge("");
});
const updateProgress = () => {
const state = app.store.getState();
const progress = selectTotalDownloadProgress(state);
win.setProgressBar(progress || -1);
};
const initialState = app.store.getState();
var init = function() {

View file

@ -1,13 +1,14 @@
import React from "react";
import rewards from "rewards";
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 { selectUserIsRewardApproved } from "selectors/user";
import { selectBalance } from "selectors/wallet";
import {
makeSelectHasClaimedReward,
makeSelectRewardByType,
selectTotalRewardValue,
selectUnclaimedRewardValue,
} from "selectors/rewards";
import * as settings from "constants/settings";
import ModalCreditIntro from "./view";
@ -17,9 +18,9 @@ const select = (state, props) => {
selectReward = makeSelectRewardByType();
return {
currentBalance: selectBalance(state),
isRewardApproved: selectUserIsRewardApproved(state),
reward: selectReward(state, { reward_type: rewards.TYPE_NEW_USER }),
totalRewardValue: selectTotalRewardValue(state),
totalRewardValue: selectUnclaimedRewardValue(state),
};
};

View file

@ -1,40 +1,64 @@
import React from "react";
import { Modal } from "modal/modal";
import { CreditAmount } from "component/common";
import { CreditAmount, CurrencySymbol } from "component/common";
import Link from "component/link/index";
import { formatCredits } from "util/formatCredits";
const ModalCreditIntro = props => {
const { closeModal, totalRewardValue, verifyAccount } = props;
const { closeModal, currentBalance, totalRewardValue, verifyAccount } = props;
const totalRewardRounded = Math.round(totalRewardValue / 10) * 10;
return (
<Modal type="custom" isOpen={true} contentLabel="Welcome to LBRY">
<section>
<h3 className="modal__header">{__("Claim Your Credits")}</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>
<h3 className="modal__header">{__("Blockchain 101")}</h3>
<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>
{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">
<Link
button="primary"
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>
</section>
</Modal>

View file

@ -1,13 +1,14 @@
import React from "react";
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";
const select = state => ({});
const perform = dispatch => ({
addFunds: () => {
dispatch(doNavigate("/rewards"));
dispatch(doNavigate("/wallet"));
dispatch(doCloseModal());
},
closeModal: () => dispatch(doCloseModal()),

View file

@ -1,5 +1,6 @@
import React from "react";
import { Modal } from "modal/modal";
import { CurrencySymbol } from "component/common";
class ModalInsufficientCredits extends React.PureComponent {
render() {
@ -11,11 +12,12 @@ class ModalInsufficientCredits extends React.PureComponent {
type="confirm"
contentLabel={__("Not enough credits")}
confirmButtonLabel={__("Get Credits")}
abortButtonLabel={__("Cancel")}
abortButtonLabel={__("Not Now")}
onAborted={closeModal}
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>
);
}

View file

@ -1,9 +1,8 @@
import React from "react";
import { connect } from "react-redux";
import { doCloseModal, doHistoryBack } from "actions/app";
import { doCloseModal } from "actions/app";
import { doDeleteFileAndGoBack } from "actions/file_info";
import { makeSelectClaimForUriIsMine } from "selectors/claims";
import batchActions from "util/batchActions";
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 { connect } from "react-redux";
import { selectCurrentModal } from "selectors/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 { selectBalance } from "selectors/wallet";
import ModalRouter from "./view";
import * as modals from "constants/modal_types";
const select = (state, props) => ({
balance: selectBalance(state),
showPageCost: selectCostForCurrentPageUri(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),
});
const perform = dispatch => ({
openWelcomeModal: () => dispatch(doOpenModal(modals.WELCOME)),
openModal: modal => dispatch(doOpenModal(modal)),
});
export default connect(select, perform)(ModalRouter);

View file

@ -6,33 +6,97 @@ import ModalInsufficientCredits from "modal/modalInsufficientCredits";
import ModalUpgrade from "modal/modalUpgrade";
import ModalWelcome from "modal/modalWelcome";
import ModalFirstReward from "modal/modalFirstReward";
import ModalRewardApprovalRequired from "modal/modalRewardApprovalRequired";
import ModalCreditIntro from "modal/modalCreditIntro";
import ModalTransactionFailed from "modal/modalTransactionFailed";
import ModalInsufficientBalance from "modal/modalInsufficientBalance";
import * as modals from "constants/modal_types";
class ModalRouter extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
lastTransitionModal: null,
lastTransitionPage: null,
};
}
componentWillMount() {
this.showWelcome(this.props);
this.showTransitionModals(this.props);
}
componentWillReceiveProps(nextProps) {
this.showWelcome(nextProps);
this.showTransitionModals(nextProps);
}
showWelcome(props) {
const { isWelcomeAcknowledged, openWelcomeModal, user } = props;
showTransitionModals(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 (
!isWelcomeAcknowledged &&
user &&
!user.is_reward_approved &&
!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() {
const { modal } = this.props;
@ -57,6 +121,8 @@ class ModalRouter extends React.PureComponent {
return <ModalTransactionFailed />;
case modals.INSUFFICIENT_BALANCE:
return <ModalInsufficientBalance />;
case modals.REWARD_APPROVAL_REQUIRED:
return <ModalRewardApprovalRequired />;
default:
return null;
}

View file

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

View file

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

View file

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

View file

@ -30,11 +30,11 @@ export class AuthPage extends React.PureComponent {
const { email, isPending, isVerificationCandidate, user } = this.props;
if (isPending || (user && !user.has_verified_email && !email)) {
return __("Welcome to LBRY");
return __("Human Proofing");
} else if (user && !user.has_verified_email) {
return __("Confirm Email");
} else if (user && !user.is_identity_verified && !user.is_reward_approved) {
return __("Confirm Identity");
return __("Final Verification");
} else {
return __("Welcome to LBRY");
}
@ -44,51 +44,45 @@ export class AuthPage extends React.PureComponent {
const { email, isPending, isVerificationCandidate, user } = this.props;
if (isPending) {
return <BusyMessage message={__("Authenticating")} />;
return [<BusyMessage message={__("Authenticating")} />, true];
} else if (user && !user.has_verified_email && !email) {
return <UserEmailNew />;
return [<UserEmailNew />, true];
} else if (user && !user.has_verified_email) {
return <UserEmailVerify />;
return [<UserEmailVerify />, true];
} else if (user && !user.is_identity_verified) {
return <UserVerify />;
return [<UserVerify />, false];
} else {
return <span className="empty">{__("No further steps.")}</span>;
return [<span className="empty">{__("No further steps.")}</span>, true];
}
}
render() {
const { email, user, isPending, navigate } = this.props;
const [innerContent, useTemplate] = this.renderMain();
return (
<main className="">
<section className="card card--form">
<div className="card__title-primary">
<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")}
/>.
return useTemplate
? <main>
<section className="card card--form">
<div className="card__title-primary">
<h1>{this.getTitle()}</h1>
</div>
</div>
</section>
</main>
);
<div className="card__content">
{innerContent}
</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,
makeSelectFetchingChannelClaims,
} from "selectors/claims";
import { selectCurrentParams } from "selectors/app";
import { doNavigate } from "actions/app";
import { selectCurrentParams } from "selectors/navigation";
import { doNavigate } from "actions/navigation";
import { makeSelectTotalPagesForChannel } from "selectors/content";
import ChannelPage from "./view";

View file

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

View file

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

View file

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

View file

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

View file

@ -3,7 +3,7 @@ import React from "react";
import lbry from "lbry.js";
import Link from "component/link";
import SubHeader from "component/subHeader";
import { BusyMessage } from "component/common";
import { BusyMessage, Icon } from "component/common";
class HelpPage extends React.PureComponent {
constructor(props) {
@ -50,7 +50,7 @@ class HelpPage extends React.PureComponent {
render() {
let ver, osName, platform, newVerLink;
const { navigate, user } = this.props;
const { doAuth, user } = this.props;
if (this.state.versionInfo) {
ver = this.state.versionInfo;
@ -119,7 +119,7 @@ class HelpPage extends React.PureComponent {
<p>{__("Did you find something wrong?")}</p>
<p>
<Link
onClick={() => navigate("report")}
navigate="/report"
label={__("Submit a Bug Report")}
icon="icon-bug"
button="alt"
@ -143,7 +143,7 @@ class HelpPage extends React.PureComponent {
</p>
: <p>{__("Your copy of LBRY is up to date.")}</p>}
{this.state.uiVersion && ver
? <table className="table-standard">
? <table className="table-standard table-stretch table-standard--definition-list">
<tbody>
<tr>
<th>{__("App")}</th>
@ -162,7 +162,21 @@ class HelpPage extends React.PureComponent {
<td>
{user && 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>
</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 { connect } from "react-redux";
import { doNavigate, doHistoryBack } from "actions/app";
import { doNavigate, doHistoryBack } from "actions/navigation";
import { doClaimRewardType } from "actions/rewards";
import {
selectMyClaims,
@ -16,10 +16,12 @@ import {
doCreateChannel,
doPublish,
} from "actions/content";
import { selectBalance } from "selectors/wallet";
import rewards from "rewards";
import PublishPage from "./view";
const select = state => ({
balance: selectBalance(state),
myClaims: selectMyClaims(state),
fetchingChannels: selectFetchingMyChannels(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 { connect } from "react-redux";
import {
makeSelectRewardByType,
selectFetchingRewards,
selectRewards,
selectUnclaimedRewards,
} from "selectors/rewards";
import { selectUser } from "selectors/user";
import { doAuthNavigate, doNavigate } from "actions/app";
import { doAuthNavigate, doNavigate } from "actions/navigation";
import { doRewardList } from "actions/rewards";
import rewards from "rewards";
import RewardsPage from "./view";
const select = (state, props) => {
const selectReward = makeSelectRewardByType();
return {
fetching: selectFetchingRewards(state),
rewards: selectRewards(state),
newUserReward: selectReward(state, { reward_type: rewards.TYPE_NEW_USER }),
rewards: selectUnclaimedRewards(state),
user: selectUser(state),
};
};

View file

@ -1,31 +1,9 @@
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 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 {
componentDidMount() {
@ -44,32 +22,8 @@ class RewardsPage extends React.PureComponent {
}
}
render() {
const { doAuth, fetching, navigate, rewards, 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>
);
}
renderPageHeader() {
const { doAuth, navigate, user } = this.props;
if (user && !user.is_reward_approved) {
if (
@ -77,20 +31,27 @@ class RewardsPage extends React.PureComponent {
!user.has_verified_email ||
!user.is_identity_verified
) {
cardHeader = (
<div>
return (
<section className="card">
<div className="card__title-primary">
<h3>{__("Humans Only")}</h3>
</div>
<div className="card__content empty">
<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>
</div>
<div className="card__content">
<Link onClick={doAuth} button="primary" label="Become Verified" />
<Link onClick={doAuth} button="primary" label="Prove Humanity" />
</div>
</div>
</section>
);
} else {
cardHeader = (
return (
<div className="card__content">
<p>
{__(
@ -122,25 +83,52 @@ class RewardsPage extends React.PureComponent {
</div>
);
}
}
}
renderUnclaimedRewards() {
const { fetching, rewards, user } = this.props;
if (fetching) {
return (
<div className="card__content">
<BusyMessage message={__("Fetching rewards")} />
</div>
);
} else if (user === null) {
cardHeader = (
<div>
<div className="card__content empty">
<p>
{__(
"This application is unable to earn rewards due to an authentication failure."
)}
</p>
</div>
return (
<div className="card__content empty">
<p>
{__(
"This application is unable to earn rewards due to an authentication failure."
)}
</p>
</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>
);
}
}
render() {
return (
<main className="main--single-column">
<SubHeader />
{cardHeader && <section className="card">{cardHeader}</section>}
{content}
{this.renderPageHeader()}
{this.renderUnclaimedRewards()}
{<RewardListClaimed />}
</main>
);
}

View file

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

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