Merge branch 'invites_and_rewards'
This commit is contained in:
commit
fa0d2b5b90
96 changed files with 4253 additions and 1032 deletions
11
CHANGELOG.md
11
CHANGELOG.md
|
@ -8,19 +8,24 @@ Web UI version numbers should always match the corresponding version of LBRY App
|
|||
|
||||
## [Unreleased]
|
||||
### 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).
|
||||
* 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 new summary components for rewards and invites to the Wallet landing page.
|
||||
* 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.
|
||||
* Continued to refine first-run process, process for new users, and introducing people to LBRY and LBRY credits.
|
||||
* Changed the default price settings.
|
||||
* 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.
|
||||
* 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)
|
||||
|
|
|
@ -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) {
|
||||
|
|
BIN
ui/dist/font/FontAwesome.otf
vendored
BIN
ui/dist/font/FontAwesome.otf
vendored
Binary file not shown.
BIN
ui/dist/font/fontawesome-webfont.eot
vendored
BIN
ui/dist/font/fontawesome-webfont.eot
vendored
Binary file not shown.
3230
ui/dist/font/fontawesome-webfont.svg
vendored
3230
ui/dist/font/fontawesome-webfont.svg
vendored
File diff suppressed because it is too large
Load diff
Before Width: | Height: | Size: 306 KiB After Width: | Height: | Size: 434 KiB |
BIN
ui/dist/font/fontawesome-webfont.ttf
vendored
BIN
ui/dist/font/fontawesome-webfont.ttf
vendored
Binary file not shown.
BIN
ui/dist/font/fontawesome-webfont.woff
vendored
BIN
ui/dist/font/fontawesome-webfont.woff
vendored
Binary file not shown.
BIN
ui/dist/font/fontawesome-webfont.woff2
vendored
BIN
ui/dist/font/fontawesome-webfont.woff2
vendored
Binary file not shown.
|
@ -26,6 +26,10 @@ const { lbrySettings: config } = require("../../../app/package.json");
|
|||
|
||||
export function doNavigate(path, params = {}, options = {}) {
|
||||
return function(dispatch, getState) {
|
||||
if (!path) {
|
||||
return;
|
||||
}
|
||||
|
||||
let url = path;
|
||||
if (params) url = `${url}?${toQueryString(params)}`;
|
||||
|
||||
|
|
|
@ -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,10 +72,7 @@ export function doClaimReward(reward, saveError = false) {
|
|||
const failure = error => {
|
||||
dispatch({
|
||||
type: types.CLAIM_REWARD_FAILURE,
|
||||
data: {
|
||||
reward,
|
||||
error: saveError ? error : null,
|
||||
},
|
||||
data: { reward, error },
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -83,30 +82,24 @@ export function doClaimReward(reward, saveError = false) {
|
|||
|
||||
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));
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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 },
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
|
@ -22,7 +22,6 @@ const app = {
|
|||
i18n: i18n,
|
||||
logs: logs,
|
||||
log: function(message) {
|
||||
console.log(message);
|
||||
logs.push(message);
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,30 +1,22 @@
|
|||
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 { selectUser } from "selectors/user";
|
||||
import App from "./view";
|
||||
import * as modals from "constants/modal_types";
|
||||
|
||||
const select = (state, props) => ({
|
||||
modal: selectCurrentModal(state),
|
||||
isWelcomeAcknowledged: selectWelcomeModalAcknowledged(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)),
|
||||
|
|
|
@ -165,7 +165,7 @@ class CardVerify extends React.Component {
|
|||
render() {
|
||||
return (
|
||||
<Link
|
||||
button="primary"
|
||||
button="alt"
|
||||
label={this.props.label}
|
||||
icon="icon-lock"
|
||||
disabled={
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -167,18 +167,20 @@ class FileActions extends React.PureComponent {
|
|||
<section className="file-actions">
|
||||
{content}
|
||||
{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"
|
||||
|
|
16
ui/js/component/inviteList/index.js
Normal file
16
ui/js/component/inviteList/index.js
Normal 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);
|
70
ui/js/component/inviteList/view.jsx
Normal file
70
ui/js/component/inviteList/view.jsx
Normal file
|
@ -0,0 +1,70 @@
|
|||
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_FIRST_PUBLISH}
|
||||
/>
|
||||
: <span className="empty">
|
||||
{__("unclaimable")}
|
||||
</span>}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default InviteList;
|
29
ui/js/component/inviteNew/index.js
Normal file
29
ui/js/component/inviteNew/index.js
Normal 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);
|
100
ui/js/component/inviteNew/view.jsx
Normal file
100
ui/js/component/inviteNew/view.jsx
Normal 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;
|
|
@ -1,5 +1,10 @@
|
|||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { doNavigate } from "actions/app";
|
||||
import Link from "./view";
|
||||
|
||||
export default connect(null, null)(Link);
|
||||
const perform = dispatch => ({
|
||||
doNavigate: path => dispatch(doNavigate(path)),
|
||||
});
|
||||
|
||||
export default connect(null, perform)(Link);
|
||||
|
|
|
@ -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;
|
||||
|
|
5
ui/js/component/linkTransaction/index.js
Normal file
5
ui/js/component/linkTransaction/index.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import Link from "./view";
|
||||
|
||||
export default connect(null, null)(Link);
|
14
ui/js/component/linkTransaction/view.jsx
Normal file
14
ui/js/component/linkTransaction/view.jsx
Normal 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;
|
|
@ -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: "",
|
||||
|
@ -318,7 +319,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 +789,6 @@ class PublishForm extends React.PureComponent {
|
|||
ref="bid"
|
||||
type="number"
|
||||
step="0.01"
|
||||
min="0"
|
||||
label={__("Deposit")}
|
||||
postfix="LBC"
|
||||
onChange={event => {
|
||||
|
|
|
@ -6,7 +6,7 @@ import {
|
|||
makeSelectIsRewardClaimPending,
|
||||
} from "selectors/rewards";
|
||||
import { doNavigate } from "actions/app";
|
||||
import { doClaimReward, doClaimRewardClearError } from "actions/rewards";
|
||||
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)),
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
}}
|
||||
|
|
10
ui/js/component/rewardListClaimed/index.js
Normal file
10
ui/js/component/rewardListClaimed/index.js
Normal 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);
|
44
ui/js/component/rewardListClaimed/view.jsx
Normal file
44
ui/js/component/rewardListClaimed/view.jsx
Normal 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;
|
11
ui/js/component/rewardSummary/index.js
Normal file
11
ui/js/component/rewardSummary/index.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { doNavigate } from "actions/app";
|
||||
import { selectUnclaimedRewardValue } from "selectors/rewards";
|
||||
import RewardSummary from "./view";
|
||||
|
||||
const select = state => ({
|
||||
unclaimedRewardAmount: selectUnclaimedRewardValue(state),
|
||||
});
|
||||
|
||||
export default connect(select, null)(RewardSummary);
|
29
ui/js/component/rewardSummary/view.jsx
Normal file
29
ui/js/component/rewardSummary/view.jsx
Normal 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;
|
5
ui/js/component/rewardTile/index.js
Normal file
5
ui/js/component/rewardTile/index.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import RewardTile from "./view";
|
||||
|
||||
export default connect(null, null)(RewardTile);
|
37
ui/js/component/rewardTile/view.jsx
Normal file
37
ui/js/component/rewardTile/view.jsx
Normal 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;
|
|
@ -4,16 +4,20 @@ 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,13 +37,15 @@ 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} />,
|
||||
|
|
|
@ -1,21 +1,5 @@
|
|||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
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 => ({
|
||||
fetchTransactions: () => dispatch(doFetchTransactions()),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(TransactionList);
|
||||
export default connect(null, null)(TransactionList);
|
||||
|
|
|
@ -1,73 +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 } = this.props;
|
||||
|
||||
const rows = [];
|
||||
if (transactionItems.length > 0) {
|
||||
transactionItems.forEach(function(item) {
|
||||
rows.push(
|
||||
<tr key={item.id}>
|
||||
<td>{(item.amount > 0 ? "+" : "") + item.amount}</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>
|
||||
<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>{__("Amount")}</th>
|
||||
<th>{__("Date")}</th>
|
||||
<th>{__("Time")}</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;
|
||||
|
|
23
ui/js/component/transactionListRecent/index.js
Normal file
23
ui/js/component/transactionListRecent/index.js
Normal 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);
|
41
ui/js/component/transactionListRecent/view.jsx
Normal file
41
ui/js/component/transactionListRecent/view.jsx
Normal 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;
|
|
@ -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,
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 published videos, you can sync your account to be granted instant verification."
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="card__actions">
|
||||
<Link
|
||||
href="https://api.lbry.io/yt/sync"
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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."
|
||||
|
|
10
ui/js/component/walletBalance/index.js
Normal file
10
ui/js/component/walletBalance/index.js
Normal 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);
|
43
ui/js/component/walletBalance/view.jsx
Normal file
43
ui/js/component/walletBalance/view.jsx
Normal 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;
|
|
@ -42,15 +42,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>
|
||||
{modal == "insufficientBalance" &&
|
||||
|
|
|
@ -108,6 +108,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
|
||||
|
|
|
@ -7,4 +7,5 @@ export const UPGRADE = "upgrade";
|
|||
export const WELCOME = "welcome";
|
||||
export const FIRST_REWARD = "first_reward";
|
||||
export const AUTHENTICATION_FAILURE = "auth_failure";
|
||||
export const REWARD_APPROVAL_REQUIRED = "reward_approval_required";
|
||||
export const CREDIT_INTRO = "credit_intro";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import React from "react";
|
||||
import rewards from "rewards";
|
||||
import { connect } from "react-redux";
|
||||
import { doCloseModal, doAuthNavigate } from "actions/app";
|
||||
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 +17,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),
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -7,7 +7,7 @@ const select = state => ({});
|
|||
|
||||
const perform = dispatch => ({
|
||||
addFunds: () => {
|
||||
dispatch(doNavigate("/rewards"));
|
||||
dispatch(doNavigate("/wallet"));
|
||||
dispatch(doCloseModal());
|
||||
},
|
||||
closeModal: () => dispatch(doCloseModal()),
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
14
ui/js/modal/modalRewardApprovalRequired/index.js
Normal file
14
ui/js/modal/modalRewardApprovalRequired/index.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { doCloseModal, doAuthNavigate } from "actions/app";
|
||||
import ModalRewardApprovalRequired from "./view";
|
||||
|
||||
const perform = dispatch => ({
|
||||
doAuth: () => {
|
||||
dispatch(doCloseModal());
|
||||
dispatch(doAuthNavigate());
|
||||
},
|
||||
closeModal: () => dispatch(doCloseModal()),
|
||||
});
|
||||
|
||||
export default connect(null, perform)(ModalRewardApprovalRequired);
|
33
ui/js/modal/modalRewardApprovalRequired/view.jsx
Normal file
33
ui/js/modal/modalRewardApprovalRequired/view.jsx
Normal 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;
|
|
@ -1,20 +1,30 @@
|
|||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { selectCurrentModal } from "selectors/app";
|
||||
import { selectCurrentModal, selectCurrentPage } from "selectors/app";
|
||||
import { doOpenModal } from "actions/app";
|
||||
import { selectWelcomeModalAcknowledged } from "selectors/app";
|
||||
import { makeSelectClientSetting } from "selectors/settings";
|
||||
import { selectUser } from "selectors/user";
|
||||
import { selectCostForCurrentPageUri } from "selectors/cost_info";
|
||||
import * as settings from "constants/settings";
|
||||
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);
|
||||
|
|
|
@ -6,31 +6,95 @@ 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 * as modals from "constants/modal_types";
|
||||
import ModalCreditIntro from "modal/modalCreditIntro";
|
||||
|
||||
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;
|
||||
|
||||
|
@ -51,6 +115,8 @@ class ModalRouter extends React.PureComponent {
|
|||
return <ModalAuthFailure />;
|
||||
case modals.CREDIT_INTRO:
|
||||
return <ModalCreditIntro />;
|
||||
case modals.REWARD_APPROVAL_REQUIRED:
|
||||
return <ModalRewardApprovalRequired />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React from "react";
|
||||
import { doNavigate } from "actions/app";
|
||||
import { doAuthNavigate } from "actions/app";
|
||||
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()),
|
||||
});
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
19
ui/js/page/invite/index.js
Normal file
19
ui/js/page/invite/index.js
Normal 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);
|
32
ui/js/page/invite/view.jsx
Normal file
32
ui/js/page/invite/view.jsx
Normal 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;
|
|
@ -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),
|
||||
|
|
5
ui/js/page/receiveCredits/index.js
Normal file
5
ui/js/page/receiveCredits/index.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import ReceiveCreditsPage from "./view";
|
||||
|
||||
export default connect(null, null)(ReceiveCreditsPage);
|
34
ui/js/page/receiveCredits/view.jsx
Normal file
34
ui/js/page/receiveCredits/view.jsx
Normal 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;
|
|
@ -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 { 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),
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
5
ui/js/page/sendCredits/index.js
Normal file
5
ui/js/page/sendCredits/index.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import SendCreditsPage from "./view";
|
||||
|
||||
export default connect(null, null)(SendCreditsPage);
|
14
ui/js/page/sendCredits/view.jsx
Normal file
14
ui/js/page/sendCredits/view.jsx
Normal file
|
@ -0,0 +1,14 @@
|
|||
import React from "react";
|
||||
import SubHeader from "component/subHeader";
|
||||
import WalletSend from "component/walletSend";
|
||||
|
||||
const SendCreditsPage = props => {
|
||||
return (
|
||||
<main className="main--single-column">
|
||||
<SubHeader />
|
||||
<WalletSend />
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
||||
export default SendCreditsPage;
|
|
@ -2,7 +2,7 @@ import React from "react";
|
|||
import lbryuri from "lbryuri";
|
||||
import { BusyMessage } from "component/common";
|
||||
import ChannelPage from "page/channel";
|
||||
import FilePage from "page/filePage";
|
||||
import FilePage from "page/file";
|
||||
|
||||
class ShowPage extends React.PureComponent {
|
||||
componentWillMount() {
|
19
ui/js/page/transactionHistory/index.js
Normal file
19
ui/js/page/transactionHistory/index.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { doFetchTransactions } from "actions/wallet";
|
||||
import {
|
||||
selectTransactionItems,
|
||||
selectIsFetchingTransactions,
|
||||
} from "selectors/wallet";
|
||||
import TransactionHistoryPage from "./view";
|
||||
|
||||
const select = state => ({
|
||||
fetchingTransactions: selectIsFetchingTransactions(state),
|
||||
transactions: selectTransactionItems(state),
|
||||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
fetchTransactions: () => dispatch(doFetchTransactions()),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(TransactionHistoryPage);
|
32
ui/js/page/transactionHistory/view.jsx
Normal file
32
ui/js/page/transactionHistory/view.jsx
Normal file
|
@ -0,0 +1,32 @@
|
|||
import React from "react";
|
||||
import { BusyMessage } from "component/common";
|
||||
import SubHeader from "component/subHeader";
|
||||
import TransactionList from "component/transactionList";
|
||||
|
||||
class TransactionHistoryPage extends React.PureComponent {
|
||||
componentWillMount() {
|
||||
this.props.fetchTransactions();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { fetchingTransactions, transactions } = this.props;
|
||||
return (
|
||||
<main className="main--single-column">
|
||||
<SubHeader />
|
||||
<section className="card">
|
||||
<div className="card__title-primary">
|
||||
<h3>{__("Transaction History")}</h3>
|
||||
</div>
|
||||
<div className="card__content">
|
||||
{fetchingTransactions &&
|
||||
<BusyMessage message={__("Loading transactions")} />}
|
||||
{!fetchingTransactions &&
|
||||
<TransactionList transactions={transactions} />}
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default TransactionHistoryPage;
|
|
@ -1,17 +1,5 @@
|
|||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { doNavigate } from "actions/app";
|
||||
import { selectCurrentPage } from "selectors/app";
|
||||
import { selectBalance } from "selectors/wallet";
|
||||
import WalletPage from "./view";
|
||||
|
||||
const select = state => ({
|
||||
currentPage: selectCurrentPage(state),
|
||||
balance: selectBalance(state),
|
||||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
navigate: path => dispatch(doNavigate(path)),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(WalletPage);
|
||||
export default connect(null, null)(WalletPage);
|
||||
|
|
|
@ -1,36 +1,18 @@
|
|||
import React from "react";
|
||||
import SubHeader from "component/subHeader";
|
||||
import TransactionList from "component/transactionList";
|
||||
import WalletAddress from "component/walletAddress";
|
||||
import WalletSend from "component/walletSend";
|
||||
import Link from "component/link";
|
||||
import { CreditAmount } from "component/common";
|
||||
import WalletBalance from "component/walletBalance";
|
||||
import RewardSummary from "component/rewardSummary";
|
||||
import TransactionListRecent from "component/transactionListRecent";
|
||||
|
||||
const WalletPage = props => {
|
||||
const { balance, currentPage, navigate } = props;
|
||||
|
||||
return (
|
||||
<main className="main--single-column">
|
||||
<main className="main--single-column page--wallet">
|
||||
<SubHeader />
|
||||
<section className="card">
|
||||
<div className="card__title-primary">
|
||||
<h3>{__("Balance")}</h3>
|
||||
</div>
|
||||
<div className="card__content">
|
||||
<CreditAmount amount={balance} precision={8} />
|
||||
</div>
|
||||
<div className="card__content">
|
||||
<div className="help">
|
||||
<Link
|
||||
onClick={() => navigate("/backup")}
|
||||
label={__("Backup Your Wallet")}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{currentPage === "wallet" ? <TransactionList {...props} /> : ""}
|
||||
{currentPage === "send" ? <WalletSend {...props} /> : ""}
|
||||
{currentPage === "receive" ? <WalletAddress /> : ""}
|
||||
<div className="card-grid">
|
||||
<WalletBalance />
|
||||
<RewardSummary />
|
||||
</div>
|
||||
<TransactionListRecent />
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -3,7 +3,8 @@ import * as types from "constants/action_types";
|
|||
const reducers = {};
|
||||
const defaultState = {
|
||||
fetching: false,
|
||||
rewardsByType: {},
|
||||
claimedRewardsById: {}, //id => reward
|
||||
unclaimedRewardsByType: {},
|
||||
claimPendingByType: {},
|
||||
claimErrorsByType: {},
|
||||
};
|
||||
|
@ -17,11 +18,19 @@ reducers[types.FETCH_REWARDS_STARTED] = function(state, action) {
|
|||
reducers[types.FETCH_REWARDS_COMPLETED] = function(state, action) {
|
||||
const { userRewards } = action.data;
|
||||
|
||||
const rewardsByType = {};
|
||||
userRewards.forEach(reward => (rewardsByType[reward.reward_type] = reward));
|
||||
let unclaimedRewards = {},
|
||||
claimedRewards = {};
|
||||
userRewards.forEach(reward => {
|
||||
if (reward.transaction_id) {
|
||||
claimedRewards[reward.id] = reward;
|
||||
} else {
|
||||
unclaimedRewards[reward.reward_type] = reward;
|
||||
}
|
||||
});
|
||||
|
||||
return Object.assign({}, state, {
|
||||
rewardsByType: rewardsByType,
|
||||
claimedRewardsById: claimedRewards,
|
||||
unclaimedRewardsByType: unclaimedRewards,
|
||||
fetching: false,
|
||||
});
|
||||
};
|
||||
|
@ -55,16 +64,22 @@ reducers[types.CLAIM_REWARD_STARTED] = function(state, action) {
|
|||
reducers[types.CLAIM_REWARD_SUCCESS] = function(state, action) {
|
||||
const { reward } = action.data;
|
||||
|
||||
const existingReward = state.rewardsByType[reward.reward_type];
|
||||
let unclaimedRewardsByType = Object.assign({}, state.unclaimedRewardsByType);
|
||||
const existingReward = unclaimedRewardsByType[reward.reward_type];
|
||||
delete state.unclaimedRewardsByType[reward.reward_type];
|
||||
|
||||
const newReward = Object.assign({}, reward, {
|
||||
reward_title: existingReward.reward_title,
|
||||
reward_description: existingReward.reward_description,
|
||||
});
|
||||
const rewardsByType = Object.assign({}, state.rewardsByType);
|
||||
|
||||
rewardsByType[reward.reward_type] = newReward;
|
||||
let claimedRewardsById = Object.assign({}, state.claimedRewardsById);
|
||||
claimedRewardsById[reward.id] = newReward;
|
||||
|
||||
const newState = Object.assign({}, state, { rewardsByType });
|
||||
const newState = Object.assign({}, state, {
|
||||
unclaimedRewardsByType,
|
||||
claimedRewardsById,
|
||||
});
|
||||
|
||||
return setClaimRewardState(newState, newReward, false, "");
|
||||
};
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import * as types from "constants/action_types";
|
||||
import * as settings from "constants/settings";
|
||||
import LANGUAGES from "constants/languages";
|
||||
import lbry from "lbry";
|
||||
|
||||
|
@ -6,7 +7,11 @@ const reducers = {};
|
|||
const defaultState = {
|
||||
clientSettings: {
|
||||
showNsfw: lbry.getClientSetting("showNsfw"),
|
||||
language: lbry.getClientSetting("language"),
|
||||
welcome_acknowledged: lbry.getClientSetting(settings.NEW_USER_ACKNOWLEDGED),
|
||||
credit_intro_acknowledged: lbry.getClientSetting(
|
||||
settings.CREDIT_INTRO_ACKNOWLEDGED
|
||||
),
|
||||
language: lbry.getClientSetting(settings.LANGUAGE),
|
||||
},
|
||||
languages: {},
|
||||
};
|
||||
|
|
|
@ -8,6 +8,11 @@ const defaultState = {
|
|||
emailNewIsPending: false,
|
||||
emailNewErrorMessage: "",
|
||||
emailToVerify: "",
|
||||
inviteNewErrorMessage: "",
|
||||
inviteNewIsPending: false,
|
||||
inviteStatusIsPending: false,
|
||||
invitesRemaining: undefined,
|
||||
invitees: undefined,
|
||||
user: undefined,
|
||||
};
|
||||
|
||||
|
@ -142,6 +147,49 @@ reducers[types.FETCH_ACCESS_TOKEN_SUCCESS] = function(state, action) {
|
|||
});
|
||||
};
|
||||
|
||||
reducers[types.USER_INVITE_STATUS_FETCH_STARTED] = function(state, action) {
|
||||
return Object.assign({}, state, {
|
||||
inviteStatusIsPending: true,
|
||||
});
|
||||
};
|
||||
|
||||
reducers[types.USER_INVITE_STATUS_FETCH_SUCCESS] = function(state, action) {
|
||||
return Object.assign({}, state, {
|
||||
inviteStatusIsPending: false,
|
||||
invitesRemaining: action.data.invitesRemaining,
|
||||
invitees: action.data.invitees,
|
||||
});
|
||||
};
|
||||
|
||||
reducers[types.USER_INVITE_NEW_STARTED] = function(state, action) {
|
||||
return Object.assign({}, state, {
|
||||
inviteNewIsPending: true,
|
||||
inviteNewErrorMessage: "",
|
||||
});
|
||||
};
|
||||
|
||||
reducers[types.USER_INVITE_NEW_SUCCESS] = function(state, action) {
|
||||
return Object.assign({}, state, {
|
||||
inviteNewIsPending: false,
|
||||
inviteNewErrorMessage: "",
|
||||
});
|
||||
};
|
||||
|
||||
reducers[types.USER_INVITE_NEW_FAILURE] = function(state, action) {
|
||||
return Object.assign({}, state, {
|
||||
inviteNewIsPending: false,
|
||||
inviteNewErrorMessage: action.data.error.message,
|
||||
});
|
||||
};
|
||||
|
||||
reducers[types.USER_INVITE_STATUS_FETCH_FAILURE] = function(state, action) {
|
||||
return Object.assign({}, state, {
|
||||
inviteStatusIsPending: false,
|
||||
invitesRemaining: null,
|
||||
invitees: null,
|
||||
});
|
||||
};
|
||||
|
||||
export default function reducer(state = defaultState, action) {
|
||||
const handler = reducers[action.type];
|
||||
if (handler) return handler(state, action);
|
||||
|
|
|
@ -95,6 +95,18 @@ rewards.TYPE_FIRST_STREAM = "first_stream";
|
|||
rewards.TYPE_MANY_DOWNLOADS = "many_downloads";
|
||||
rewards.TYPE_FIRST_PUBLISH = "first_publish";
|
||||
rewards.TYPE_FEATURED_DOWNLOAD = "featured_download";
|
||||
rewards.TYPE_REFERRAL = "referral";
|
||||
rewards.SORT_ORDER = [
|
||||
rewards.TYPE_NEW_USER,
|
||||
rewards.TYPE_CONFIRM_EMAIL,
|
||||
rewards.TYPE_FIRST_STREAM,
|
||||
rewards.TYPE_FIRST_CHANNEL,
|
||||
rewards.TYPE_FIRST_PUBLISH,
|
||||
rewards.TYPE_FEATURED_DOWNLOAD,
|
||||
rewards.TYPE_MANY_DOWNLOADS,
|
||||
rewards.TYPE_REFERRAL,
|
||||
rewards.TYPE_NEW_DEVELOPER,
|
||||
];
|
||||
|
||||
rewards.claimReward = function(type) {
|
||||
function requestReward(resolve, reject, params) {
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { createSelector } from "reselect";
|
||||
import { parseQueryParams, toQueryString } from "util/query_params";
|
||||
import * as settings from "constants/settings.js";
|
||||
import lbry from "lbry";
|
||||
import lbryuri from "lbryuri";
|
||||
|
||||
export const _selectState = state => state.app || {};
|
||||
|
@ -39,13 +38,15 @@ export const selectPageTitle = createSelector(
|
|||
case "wallet":
|
||||
return __("Wallet");
|
||||
case "send":
|
||||
return __("Send");
|
||||
return __("Send Credits");
|
||||
case "receive":
|
||||
return __("Receive");
|
||||
return __("Wallet Address");
|
||||
case "backup":
|
||||
return __("Backup");
|
||||
return __("Backup Your Wallet");
|
||||
case "rewards":
|
||||
return __("Rewards");
|
||||
case "invite":
|
||||
return __("Invites");
|
||||
case "start":
|
||||
return __("Start");
|
||||
case "publish":
|
||||
|
@ -72,8 +73,12 @@ export const selectPageTitle = createSelector(
|
|||
return __("Publishes");
|
||||
case "discover":
|
||||
return __("Home");
|
||||
default:
|
||||
case false:
|
||||
case null:
|
||||
case "":
|
||||
return "";
|
||||
default:
|
||||
return page[0].toUpperCase() + (page.length > 0 ? page.substr(1) : "");
|
||||
}
|
||||
}
|
||||
);
|
||||
|
@ -136,15 +141,19 @@ export const selectHeaderLinks = createSelector(selectCurrentPage, page => {
|
|||
// This contains intentional fall throughs
|
||||
switch (page) {
|
||||
case "wallet":
|
||||
case "history":
|
||||
case "send":
|
||||
case "receive":
|
||||
case "invite":
|
||||
case "rewards":
|
||||
case "backup":
|
||||
return {
|
||||
wallet: __("Overview"),
|
||||
history: __("History"),
|
||||
send: __("Send"),
|
||||
receive: __("Receive"),
|
||||
rewards: __("Rewards"),
|
||||
invite: __("Invites"),
|
||||
};
|
||||
case "downloaded":
|
||||
case "published":
|
||||
|
@ -203,16 +212,6 @@ export const selectSnackBarSnacks = createSelector(
|
|||
snackBar => snackBar.snacks || []
|
||||
);
|
||||
|
||||
export const selectCreditsIntroAcknowledged = createSelector(
|
||||
_selectState,
|
||||
state => lbry.getClientSetting(settings.CREDIT_INTRO_ACKNOWLEDGED)
|
||||
);
|
||||
|
||||
export const selectWelcomeModalAcknowledged = createSelector(
|
||||
_selectState,
|
||||
state => lbry.getClientSetting(settings.FIRST_RUN_ACKNOWLEDGED)
|
||||
);
|
||||
|
||||
export const selectBadgeNumber = createSelector(
|
||||
_selectState,
|
||||
state => state.badgeNumber
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { createSelector } from "reselect";
|
||||
import { selectCurrentParams } from "./app";
|
||||
|
||||
export const _selectState = state => state.costInfo || {};
|
||||
|
||||
|
@ -15,6 +16,13 @@ export const makeSelectCostInfoForUri = () => {
|
|||
return createSelector(selectCostInfoForUri, costInfo => costInfo);
|
||||
};
|
||||
|
||||
export const selectCostForCurrentPageUri = createSelector(
|
||||
selectAllCostInfoByUri,
|
||||
selectCurrentParams,
|
||||
(costInfo, params) =>
|
||||
params.uri && costInfo[params.uri] ? costInfo[params.uri].cost : undefined
|
||||
);
|
||||
|
||||
export const selectFetchingCostInfo = createSelector(
|
||||
_selectState,
|
||||
state => state.fetching || {}
|
||||
|
|
|
@ -1,16 +1,33 @@
|
|||
import { createSelector } from "reselect";
|
||||
import { selectUser } from "selectors/user";
|
||||
import rewards from "rewards";
|
||||
|
||||
const _selectState = state => state.rewards || {};
|
||||
|
||||
export const selectRewardsByType = createSelector(
|
||||
export const selectUnclaimedRewardsByType = createSelector(
|
||||
_selectState,
|
||||
state => state.rewardsByType || {}
|
||||
state => state.unclaimedRewardsByType
|
||||
);
|
||||
|
||||
export const selectRewards = createSelector(
|
||||
selectRewardsByType,
|
||||
byType => Object.values(byType) || []
|
||||
export const selectClaimedRewardsById = createSelector(
|
||||
_selectState,
|
||||
state => state.claimedRewardsById
|
||||
);
|
||||
|
||||
export const selectClaimedRewards = createSelector(
|
||||
selectClaimedRewardsById,
|
||||
byId => Object.values(byId) || []
|
||||
);
|
||||
|
||||
export const selectUnclaimedRewards = createSelector(
|
||||
selectUnclaimedRewardsByType,
|
||||
byType =>
|
||||
Object.values(byType).sort(function(a, b) {
|
||||
return rewards.SORT_ORDER.indexOf(a.reward_type) <
|
||||
rewards.SORT_ORDER.indexOf(b.reward_type)
|
||||
? -1
|
||||
: 1;
|
||||
}) || []
|
||||
);
|
||||
|
||||
export const selectIsRewardEligible = createSelector(
|
||||
|
@ -23,10 +40,12 @@ export const selectFetchingRewards = createSelector(
|
|||
state => !!state.fetching
|
||||
);
|
||||
|
||||
export const selectTotalRewardValue = createSelector(selectRewards, rewards =>
|
||||
rewards.reduce((sum, reward) => {
|
||||
return sum + reward.reward_amount;
|
||||
}, 0)
|
||||
export const selectUnclaimedRewardValue = createSelector(
|
||||
selectUnclaimedRewards,
|
||||
rewards =>
|
||||
rewards.reduce((sum, reward) => {
|
||||
return sum + reward.reward_amount;
|
||||
}, 0)
|
||||
);
|
||||
|
||||
export const selectHasClaimedReward = (state, props) => {
|
||||
|
@ -65,9 +84,16 @@ export const makeSelectClaimRewardError = () => {
|
|||
};
|
||||
|
||||
const selectRewardByType = (state, props) => {
|
||||
return selectRewardsByType(state)[props.reward_type];
|
||||
return selectUnclaimedRewardsByType(state)[props.reward_type];
|
||||
};
|
||||
|
||||
export const makeSelectRewardByType = () => {
|
||||
return createSelector(selectRewardByType, reward => reward);
|
||||
};
|
||||
|
||||
export const makeSelectRewardAmountByType = () => {
|
||||
return createSelector(
|
||||
selectRewardByType,
|
||||
reward => (reward ? reward.reward_amount : 0)
|
||||
);
|
||||
};
|
||||
|
|
|
@ -38,6 +38,8 @@ export const selectWunderBarAddress = createSelector(
|
|||
|
||||
export const selectWunderBarIcon = createSelector(selectCurrentPage, page => {
|
||||
switch (page) {
|
||||
case "auth":
|
||||
return "icon-user";
|
||||
case "search":
|
||||
return "icon-search";
|
||||
case "settings":
|
||||
|
@ -52,22 +54,29 @@ export const selectWunderBarIcon = createSelector(selectCurrentPage, page => {
|
|||
return "icon-folder";
|
||||
case "start":
|
||||
return "icon-file";
|
||||
case "rewards":
|
||||
return "icon-bank";
|
||||
case "wallet":
|
||||
case "history":
|
||||
return "icon-history";
|
||||
case "send":
|
||||
return "icon-send";
|
||||
case "rewards":
|
||||
return "icon-rocket";
|
||||
case "invite":
|
||||
return "icon-envelope-open";
|
||||
case "address":
|
||||
case "receive":
|
||||
return "icon-address-book";
|
||||
case "wallet":
|
||||
case "backup":
|
||||
return "icon-bank";
|
||||
case "show":
|
||||
return "icon-file";
|
||||
case "publish":
|
||||
return "icon-upload";
|
||||
case "developer":
|
||||
return "icon-file";
|
||||
case "developer":
|
||||
return "icon-code";
|
||||
case "discover":
|
||||
return "icon-home";
|
||||
default:
|
||||
return "icon-file";
|
||||
}
|
||||
});
|
||||
|
|
|
@ -12,6 +12,13 @@ export const selectClientSettings = createSelector(
|
|||
state => state.clientSettings || {}
|
||||
);
|
||||
|
||||
export const makeSelectClientSetting = setting => {
|
||||
return createSelector(
|
||||
selectClientSettings,
|
||||
settings => (settings ? settings[setting] : undefined)
|
||||
);
|
||||
};
|
||||
|
||||
export const selectSettingsIsGenerous = createSelector(
|
||||
selectDaemonSettings,
|
||||
settings => settings && settings.is_generous_host
|
||||
|
|
|
@ -68,3 +68,33 @@ export const selectAccessToken = createSelector(
|
|||
_selectState,
|
||||
state => state.accessToken
|
||||
);
|
||||
|
||||
export const selectUserInviteStatusIsPending = createSelector(
|
||||
_selectState,
|
||||
state => state.inviteStatusIsPending
|
||||
);
|
||||
|
||||
export const selectUserInvitesRemaining = createSelector(
|
||||
_selectState,
|
||||
state => state.invitesRemaining
|
||||
);
|
||||
|
||||
export const selectUserInvitees = createSelector(
|
||||
_selectState,
|
||||
state => state.invitees
|
||||
);
|
||||
|
||||
export const selectUserInviteStatusFailed = createSelector(
|
||||
selectUserInvitesRemaining,
|
||||
inviteStatus => selectUserInvitesRemaining === null
|
||||
);
|
||||
|
||||
export const selectUserInviteNewIsPending = createSelector(
|
||||
_selectState,
|
||||
state => state.inviteNewIsPending
|
||||
);
|
||||
|
||||
export const selectUserInviteNewErrorMessage = createSelector(
|
||||
_selectState,
|
||||
state => state.inviteNewErrorMessage
|
||||
);
|
||||
|
|
|
@ -35,6 +35,24 @@ export const selectTransactionItems = createSelector(
|
|||
}
|
||||
);
|
||||
|
||||
export const selectRecentTransactions = createSelector(
|
||||
selectTransactionItems,
|
||||
transactions => {
|
||||
let threshold = new Date();
|
||||
threshold.setDate(threshold.getDate() - 7);
|
||||
return transactions.filter(transaction => {
|
||||
return transaction.date > threshold;
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
export const selectHasTransactions = createSelector(
|
||||
selectTransactionItems,
|
||||
transactions => {
|
||||
return transactions && transactions.length > 0;
|
||||
}
|
||||
);
|
||||
|
||||
export const selectIsFetchingTransactions = createSelector(
|
||||
_selectState,
|
||||
state => state.fetchingTransactions
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
$spacing-vertical: 24px;
|
||||
|
||||
$padding-button: 12px;
|
||||
$padding-text-link: 4px;
|
||||
$padding-button: $spacing-vertical * 2/3;
|
||||
$padding-text-link: 0px;
|
||||
|
||||
$color-primary: #155B4A;
|
||||
$color-primary-light: saturate(lighten($color-primary, 50%), 20%);
|
||||
|
|
|
@ -134,15 +134,6 @@ p
|
|||
}
|
||||
}
|
||||
|
||||
/*should this be here or work this way? had to hack additional rule below*/
|
||||
.icon:only-child {
|
||||
position: relative;
|
||||
top: 0.16em;
|
||||
}
|
||||
.icon-featured > .icon {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.help {
|
||||
font-size: .85em;
|
||||
color: $color-help;
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
@font-face {
|
||||
font-family: 'FontAwesome';
|
||||
src: url('../font/fontawesome-webfont.eot?v=4.3.0');
|
||||
src: url('../font/fontawesome-webfont.eot?#iefix&v=4.3.0') format('embedded-opentype'), url('../font/fontawesome-webfont.woff2?v=4.3.0') format('woff2'), url('../font/fontawesome-webfont.woff?v=4.3.0') format('woff'), url('../font/fontawesome-webfont.ttf?v=4.3.0') format('truetype'), url('../font/fontawesome-webfont.svg?v=4.3.0#fontawesomeregular') format('svg');
|
||||
src: url('../font/fontawesome-webfont.eot?v=4.7.0');
|
||||
src: url('../font/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'), url('../font/fontawesome-webfont.woff2?v=4.7.0') format('woff2'), url('../font/fontawesome-webfont.woff?v=4.7.0') format('woff'), url('../font/fontawesome-webfont.ttf?v=4.7.0') format('truetype'), url('../font/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ $button-focus-shift: 12%;
|
|||
|
||||
+ .button-set-item
|
||||
{
|
||||
margin-left: $padding-button;
|
||||
margin-left: $spacing-vertical;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,10 +23,11 @@ $width-card-small: $spacing-vertical * 10;
|
|||
}
|
||||
.card__title-primary,
|
||||
.card__title-identity,
|
||||
.card__actions,
|
||||
.card__content,
|
||||
.card__subtext {
|
||||
padding: 0 $padding-card-horizontal;
|
||||
.card__subtext,
|
||||
.card__actions {
|
||||
padding-left: $padding-card-horizontal;
|
||||
padding-right: $padding-card-horizontal;
|
||||
}
|
||||
.card--small {
|
||||
.card__title-primary,
|
||||
|
@ -39,6 +40,7 @@ $width-card-small: $spacing-vertical * 10;
|
|||
}
|
||||
.card__title-primary {
|
||||
margin-top: $spacing-vertical * 2/3;
|
||||
margin-bottom: $spacing-vertical * 2/3;
|
||||
}
|
||||
.card__title-identity {
|
||||
margin-top: $spacing-vertical * 1/3;
|
||||
|
@ -46,13 +48,6 @@ $width-card-small: $spacing-vertical * 10;
|
|||
}
|
||||
.card__actions {
|
||||
margin-top: $spacing-vertical * 2/3;
|
||||
}
|
||||
.card__actions--bottom {
|
||||
margin-top: $spacing-vertical * 1/3;
|
||||
margin-bottom: $spacing-vertical * 1/3;
|
||||
}
|
||||
.card__actions--form-submit {
|
||||
margin-top: $spacing-vertical;
|
||||
margin-bottom: $spacing-vertical * 2/3;
|
||||
}
|
||||
.card__content {
|
||||
|
@ -259,4 +254,21 @@ $padding-right-card-hover-hack: 30px;
|
|||
|
||||
.card__icon-featured-content {
|
||||
color: orangered;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
if we keep doing things like this, we should add a real grid system, but I'm going to be a selective dick about it - Jeremy
|
||||
*/
|
||||
.card-grid {
|
||||
$margin-card-grid: $spacing-vertical * 2/3;
|
||||
display:flex;
|
||||
flex-wrap: wrap;
|
||||
> .card {
|
||||
width: $width-page-constrained / 2 - $margin-card-grid / 2;
|
||||
flex-grow:1;
|
||||
}
|
||||
> .card:nth-of-type(2n - 1) {
|
||||
margin-right: $margin-card-grid;
|
||||
}
|
||||
}
|
|
@ -24,6 +24,9 @@ table.table-standard {
|
|||
img {
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
&.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
tr.thead:not(:first-child) th {
|
||||
border-top: 1px solid #e2e2e2;
|
||||
|
@ -49,6 +52,11 @@ table.table-standard {
|
|||
}
|
||||
}
|
||||
}
|
||||
.table-standard--definition-list {
|
||||
th {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
table.table-stretch {
|
||||
width: 100%;
|
||||
|
|
Loading…
Reference in a new issue