new first run flow and re-org of wallet area

add changelog

add external service option to get credits page

remove testing code

fix user has existing email and grammar/typos
This commit is contained in:
Jeremy Kauffman 2017-12-07 13:07:30 -05:00
parent 204fd60f95
commit 8c403f640c
40 changed files with 297 additions and 264 deletions

View file

@ -9,9 +9,11 @@ Web UI version numbers should always match the corresponding version of LBRY App
## [Unreleased] ## [Unreleased]
### Added ### Added
* ShapeShift integration * ShapeShift integration
*
### Changed ### Changed
* The first run process for new users has changed substantially. New users can now easily receive one credit.
* The wallet area has been re-organized. Send and Receive are now on the same page. A new page, "Get Credits", explains how users can add LBRY credits to the app.
* The prompt for an insufficient balance is much more user-friendly.
* The credit balance displayed in the main app navigation displays two decimal places instead of one. * The credit balance displayed in the main app navigation displays two decimal places instead of one.
* Moved all redux code into /redux folder * Moved all redux code into /redux folder
* Channel names in pages are highlighted to indicate them being clickable(#814) * Channel names in pages are highlighted to indicate them being clickable(#814)
@ -27,7 +29,7 @@ Web UI version numbers should always match the corresponding version of LBRY App
* *
### Deprecated ### Deprecated
* * We previous two separate modals for insufficient credits. These have been combined.
* *
### Removed ### Removed

View file

@ -3,7 +3,7 @@ import Router from "component/router/index";
import Header from "component/header"; import Header from "component/header";
import Theme from "component/theme"; import Theme from "component/theme";
import ModalRouter from "modal/modalRouter"; import ModalRouter from "modal/modalRouter";
import lbry from "lbry"; import ReactModal from "react-modal";
import throttle from "util/throttle"; import throttle from "util/throttle";
class App extends React.PureComponent { class App extends React.PureComponent {
@ -28,6 +28,8 @@ class App extends React.PureComponent {
const scrollListener = () => recordScroll(this.mainContent.scrollTop); const scrollListener = () => recordScroll(this.mainContent.scrollTop);
this.mainContent.addEventListener("scroll", throttle(scrollListener, 750)); this.mainContent.addEventListener("scroll", throttle(scrollListener, 750));
ReactModal.setAppElement("#window"); //fuck this
} }
componentWillUnmount() { componentWillUnmount() {

View file

@ -69,7 +69,7 @@ export class CreditAmount extends React.PureComponent {
amount: PropTypes.number.isRequired, amount: PropTypes.number.isRequired,
precision: PropTypes.number, precision: PropTypes.number,
isEstimate: PropTypes.bool, isEstimate: PropTypes.bool,
label: PropTypes.bool, label: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
showFree: PropTypes.bool, showFree: PropTypes.bool,
showFullPrice: PropTypes.bool, showFullPrice: PropTypes.bool,
showPlus: PropTypes.bool, showPlus: PropTypes.bool,
@ -106,10 +106,12 @@ export class CreditAmount extends React.PureComponent {
amountText = __("free"); amountText = __("free");
} else { } else {
if (this.props.label) { if (this.props.label) {
amountText = const label =
formattedAmount + typeof this.props.label === "string"
" " + ? this.props.label
(parseFloat(amount) == 1 ? __("credit") : __("credits")); : parseFloat(amount) == 1 ? __("credit") : __("credits");
amountText = formattedAmount + " " + label;
} else { } else {
amountText = formattedAmount; amountText = formattedAmount;
} }

View file

@ -882,6 +882,7 @@ class PublishForm extends React.PureComponent {
!this.state.submitting ? __("Publish") : __("Publishing...") !this.state.submitting ? __("Publish") : __("Publishing...")
} }
disabled={ disabled={
this.props.balance <= 0 ||
this.state.submitting || this.state.submitting ||
(this.state.uri && (this.state.uri &&
this.props.resolvingUris.indexOf(this.state.uri) !== -1) || this.props.resolvingUris.indexOf(this.state.uri) !== -1) ||

View file

@ -20,7 +20,11 @@ const RewardSummary = props => {
)} )}
</div> </div>
<div className="card__actions"> <div className="card__actions">
<Link button="alt" navigate="/rewards" label={__("Learn more")} /> <Link
button="primary"
navigate="/rewards"
label={__("Claim Rewards")}
/>
</div> </div>
</section> </section>
); );

View file

@ -3,8 +3,8 @@ import SettingsPage from "page/settings";
import HelpPage from "page/help"; import HelpPage from "page/help";
import ReportPage from "page/report.js"; import ReportPage from "page/report.js";
import WalletPage from "page/wallet"; import WalletPage from "page/wallet";
import ReceiveCreditsPage from "page/receiveCredits"; import GetCreditsPage from "../../page/getCredits";
import SendCreditsPage from "page/sendCredits"; import SendReceivePage from "page/sendCredits";
import ShowPage from "page/show"; import ShowPage from "page/show";
import PublishPage from "page/publish"; import PublishPage from "page/publish";
import DiscoverPage from "page/discover"; import DiscoverPage from "page/discover";
@ -38,11 +38,11 @@ const Router = props => {
invite: <InvitePage params={params} />, invite: <InvitePage params={params} />,
publish: <PublishPage params={params} />, publish: <PublishPage params={params} />,
published: <FileListPublished params={params} />, published: <FileListPublished params={params} />,
receive: <ReceiveCreditsPage params={params} />, getcredits: <GetCreditsPage params={params} />,
report: <ReportPage params={params} />, report: <ReportPage params={params} />,
rewards: <RewardsPage params={params} />, rewards: <RewardsPage params={params} />,
search: <SearchPage params={params} />, search: <SearchPage params={params} />,
send: <SendCreditsPage params={params} />, send: <SendReceivePage params={params} />,
settings: <SettingsPage params={params} />, settings: <SettingsPage params={params} />,
show: <ShowPage {...params} />, show: <ShowPage {...params} />,
wallet: <WalletPage params={params} />, wallet: <WalletPage params={params} />,

View file

@ -6,10 +6,15 @@ import {
selectEmailNewErrorMessage, selectEmailNewErrorMessage,
} from "redux/selectors/user"; } from "redux/selectors/user";
import UserEmailNew from "./view"; import UserEmailNew from "./view";
import rewards from "rewards";
import { makeSelectRewardAmountByType } from "redux/selectors/rewards";
const select = state => ({ const select = state => ({
isPending: selectEmailNewIsPending(state), isPending: selectEmailNewIsPending(state),
errorMessage: selectEmailNewErrorMessage(state), errorMessage: selectEmailNewErrorMessage(state),
rewardAmount: makeSelectRewardAmountByType()(state, {
reward_type: rewards.TYPE_CONFIRM_EMAIL,
}),
}); });
const perform = dispatch => ({ const perform = dispatch => ({

View file

@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import Link from "component/link"; import { CreditAmount } from "component/common";
import { Form, FormRow, Submit } from "component/form.js"; import { Form, FormRow, Submit } from "component/form.js";
class UserEmailNew extends React.PureComponent { class UserEmailNew extends React.PureComponent {
@ -23,20 +23,24 @@ class UserEmailNew extends React.PureComponent {
} }
render() { render() {
const { errorMessage, isPending } = this.props; const { cancelButton, errorMessage, isPending, rewardAmount } = this.props;
return ( return (
<div>
<p>
Let us know your email and you'll receive{" "}
<CreditAmount amount={rewardAmount} label="LBC" />, the blockchain
token used by LBRY.
</p>
<p>
{__(
"We'll also let you know about LBRY updates, security issues, and great new content."
)}
</p>
<p>
{__("We'll never sell your email, and you can unsubscribe at any time.")}
</p>
<Form onSubmit={this.handleSubmit.bind(this)}> <Form onSubmit={this.handleSubmit.bind(this)}>
<p>
{__(
"This process is required to prevent abuse of the rewards program."
)}
</p>
<p>
{__(
"We will also contact you about updates and new content, but you can unsubscribe at any time."
)}
</p>
<FormRow <FormRow
type="text" type="text"
label="Email" label="Email"
@ -49,9 +53,11 @@ class UserEmailNew extends React.PureComponent {
}} }}
/> />
<div className="form-row-submit"> <div className="form-row-submit">
<Submit label="Next" disabled={isPending} /> <Submit label="Submit" disabled={isPending} />
{cancelButton}
</div> </div>
</Form> </Form>
</div>
); );
} }
} }

View file

@ -7,11 +7,16 @@ import {
selectEmailVerifyErrorMessage, selectEmailVerifyErrorMessage,
} from "redux/selectors/user"; } from "redux/selectors/user";
import UserEmailVerify from "./view"; import UserEmailVerify from "./view";
import rewards from "rewards";
import { makeSelectRewardAmountByType } from "redux/selectors/rewards";
const select = state => ({ const select = state => ({
isPending: selectEmailVerifyIsPending(state), isPending: selectEmailVerifyIsPending(state),
email: selectEmailToVerify(state), email: selectEmailToVerify(state),
errorMessage: selectEmailVerifyErrorMessage(state), errorMessage: selectEmailVerifyErrorMessage(state),
rewardAmount: makeSelectRewardAmountByType()(state, {
reward_type: rewards.TYPE_CONFIRM_EMAIL,
}),
}); });
const perform = dispatch => ({ const perform = dispatch => ({

View file

@ -1,5 +1,6 @@
import React from "react"; import React from "react";
import Link from "component/link"; import Link from "component/link";
import { CreditAmount } from "component/common";
import { Form, FormRow, Submit } from "component/form.js"; import { Form, FormRow, Submit } from "component/form.js";
class UserEmailVerify extends React.PureComponent { class UserEmailVerify extends React.PureComponent {
@ -23,10 +24,16 @@ class UserEmailVerify extends React.PureComponent {
} }
render() { render() {
const { errorMessage, isPending } = this.props; const {
cancelButton,
errorMessage,
email,
isPending,
rewardAmount,
} = this.props;
return ( return (
<Form onSubmit={this.handleSubmit.bind(this)}> <Form onSubmit={this.handleSubmit.bind(this)}>
<p>{__("Please enter the verification code emailed to you.")}</p> <p>Please enter the verification code emailed to {email}.</p>
<FormRow <FormRow
type="text" type="text"
label={__("Verification Code")} label={__("Verification Code")}
@ -41,12 +48,14 @@ class UserEmailVerify extends React.PureComponent {
<div className="form-field__helper"> <div className="form-field__helper">
<p> <p>
{__("Email")}{" "} {__("Email")}{" "}
<Link href="mailto:help@lbry.io" label="help@lbry.io" />{" "} <Link href="mailto:help@lbry.io" label="help@lbry.io" /> or join our{" "}
<Link href="https://chat.lbry.io" label="chat" />{" "}
{__("if you encounter any trouble with your code.")} {__("if you encounter any trouble with your code.")}
</p> </p>
</div> </div>
<div className="form-row-submit form-row-submit--with-footer"> <div className="form-row-submit">
<Submit label={__("Verify")} disabled={this.state.submitting} /> <Submit label={__("Verify")} disabled={isPending} />
{cancelButton}
</div> </div>
</Form> </Form>
); );

View file

@ -79,9 +79,16 @@ class UserVerify extends React.PureComponent {
<div className="card__content"> <div className="card__content">
<p> <p>
{__( {__(
"If you have a YouTube account with subscribers and views, you can sync your account to be granted instant verification." "If you have a YouTube account with subscribers and views, you can sync your account and content to be granted instant verification."
)} )}
</p> </p>
<p>
{__("Some account minimums apply.")}{" "}
<Link
href="https://lbry.io/faq/youtube"
label={__("Read more.")}
/>
</p>
</div> </div>
<div className="card__actions"> <div className="card__actions">
<Link <Link

View file

@ -23,11 +23,12 @@ const WalletBalance = props => {
)} )}
</div> </div>
<div className="card__actions"> <div className="card__actions">
<Link button="alt" navigate="/getcredits" label={__("Get Credits")} />
<Link <Link
button="primary" button="alt"
disabled={balance === 0} disabled={balance === 0}
navigate="/backup" navigate="/backup"
label={__("Backup wallet")} label={__("Backup Wallet")}
/> />
</div> </div>
</section> </section>

View file

@ -6,11 +6,10 @@ export const ERROR = "error";
export const INSUFFICIENT_CREDITS = "insufficient_credits"; export const INSUFFICIENT_CREDITS = "insufficient_credits";
export const UPGRADE = "upgrade"; export const UPGRADE = "upgrade";
export const WELCOME = "welcome"; export const WELCOME = "welcome";
export const EMAIL_COLLECTION = "email_collection";
export const FIRST_REWARD = "first_reward"; export const FIRST_REWARD = "first_reward";
export const AUTHENTICATION_FAILURE = "auth_failure"; export const AUTHENTICATION_FAILURE = "auth_failure";
export const TRANSACTION_FAILED = "transaction_failed"; export const TRANSACTION_FAILED = "transaction_failed";
export const INSUFFICIENT_BALANCE = "insufficient_balance";
export const REWARD_APPROVAL_REQUIRED = "reward_approval_required"; export const REWARD_APPROVAL_REQUIRED = "reward_approval_required";
export const AFFIRM_PURCHASE = "affirm_purchase"; export const AFFIRM_PURCHASE = "affirm_purchase";
export const CREDIT_INTRO = "credit_intro";
export const CONFIRM_CLAIM_REVOKE = "confirmClaimRevoke"; export const CONFIRM_CLAIM_REVOKE = "confirmClaimRevoke";

View file

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

View file

@ -61,7 +61,7 @@ document.addEventListener("click", event => {
if (target.matches("a") || target.matches("button")) { if (target.matches("a") || target.matches("button")) {
// TODO: Look into using accessiblity labels (this would also make the app more accessible) // TODO: Look into using accessiblity labels (this would also make the app more accessible)
let hrefParts = window.location.href.split("#"); let hrefParts = window.location.href.split("#");
let element = target.title || target.text.trim(); let element = target.title || (target.text && target.text.trim());
if (element) { if (element) {
amplitude.getInstance().logEvent("CLICK", { amplitude.getInstance().logEvent("CLICK", {
target: element, target: element,

View file

@ -1,7 +1,7 @@
import React from "react"; import React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { doCloseModal } from "redux/actions/app"; import { doCloseModal } from "redux/actions/app";
import { doAuthNavigate } from "redux/actions/navigation"; import { doNavigate } from "redux/actions/navigation";
import { doSetClientSetting } from "redux/actions/settings"; import { doSetClientSetting } from "redux/actions/settings";
import { selectUserIsRewardApproved } from "redux/selectors/user"; import { selectUserIsRewardApproved } from "redux/selectors/user";
import { selectBalance } from "redux/selectors/wallet"; import { selectBalance } from "redux/selectors/wallet";
@ -25,17 +25,16 @@ const select = (state, props) => {
}; };
const perform = dispatch => () => { const perform = dispatch => () => {
const closeModal = () => {
dispatch(doSetClientSetting(settings.CREDIT_INTRO_ACKNOWLEDGED, true));
dispatch(doCloseModal());
};
return { return {
verifyAccount: () => { addBalance: () => {
closeModal(); dispatch(doSetClientSetting(settings.CREDIT_REQUIRED_ACKNOWLEDGED, true));
dispatch(doAuthNavigate("/discover")); dispatch(doNavigate("/getcredits"));
dispatch(doCloseModal());
},
closeModal: () => {
dispatch(doSetClientSetting(settings.CREDIT_REQUIRED_ACKNOWLEDGED, true));
dispatch(doCloseModal());
}, },
closeModal: closeModal,
}; };
}; };

View file

@ -4,55 +4,42 @@ import { CreditAmount, CurrencySymbol } from "component/common";
import Link from "component/link/index"; import Link from "component/link/index";
const ModalCreditIntro = props => { const ModalCreditIntro = props => {
const { closeModal, totalRewardValue, currentBalance, verifyAccount } = props; const { closeModal, totalRewardValue, currentBalance, addBalance } = props;
const totalRewardRounded = Math.round(totalRewardValue / 10) * 10; const totalRewardRounded = Math.round(totalRewardValue / 10) * 10;
return ( return (
<Modal type="custom" isOpen={true} contentLabel="Welcome to LBRY"> <Modal type="custom" isOpen={true} contentLabel="Welcome to LBRY">
<section> <section>
<h3 className="modal__header">{__("Blockchain 101")}</h3> <h3 className="modal__header">
{__("Computer Wizard Needs Tokens Badly")}
</h3>
<p> <p>
LBRY is controlled and powered by a blockchain asset called{" "} Some actions require{" "} LBRY credits
<em> (<em>
<CurrencySymbol /> <CurrencySymbol />
</em>. <CurrencySymbol />{" "} </em>), the blockchain token that powers the LBRY network.
{__( </p>
"is used to publish content, to have a say in the network rules, and to access paid content." {currentBalance <= 0 && (
<p>
You currently have <CreditAmount amount={currentBalance} />, so the
actions you can take are limited.
</p>
)} )}
</p>
{currentBalance <= 0 ? (
<div>
<p> <p>
You currently have <CreditAmount amount={currentBalance} />, so There are a variety of ways to get credits, including more than{" "}
the actions you can take are limited.
</p>
<p>
However, there are a variety of ways to get credits, including
more than{" "}
{totalRewardValue ? ( {totalRewardValue ? (
<CreditAmount amount={totalRewardRounded} /> <CreditAmount amount={totalRewardRounded} />
) : ( ) : (
<span className="credit-amount">{__("?? credits")}</span> <span className="credit-amount">{__("?? credits")}</span>
)}{" "} )}{" "}
{__( {__(" in free rewards for participating in the LBRY beta.")}
" in rewards available for being a proven human during the LBRY beta."
)}
</p> </p>
</div>
) : (
<div>
<p>
But you probably knew this, since you've already got{" "}
<CreditAmount amount={currentBalance} />.
</p>
</div>
)}
<div className="modal__buttons"> <div className="modal__buttons">
<Link <Link
button="primary" button="primary"
onClick={verifyAccount} onClick={addBalance}
label={__("I'm Totally A Human")} label={__("Get Credits")}
/> />
<Link <Link
button="alt" button="alt"

View file

@ -0,0 +1,21 @@
import React from "react";
import * as settings from "constants/settings";
import { connect } from "react-redux";
import { doCloseModal } from "redux/actions/app";
import { doSetClientSetting } from "redux/actions/settings";
import { selectEmailToVerify, selectUser } from "redux/selectors/user";
import ModalEmailCollection from "./view";
const select = state => ({
email: selectEmailToVerify(state),
user: selectUser(state),
});
const perform = dispatch => () => ({
closeModal: () => {
dispatch(doSetClientSetting(settings.EMAIL_COLLECTION_ACKNOWLEDGED, true));
dispatch(doCloseModal());
},
});
export default connect(select, perform)(ModalEmailCollection);

View file

@ -0,0 +1,45 @@
import React from "react";
import { Modal } from "modal/modal";
import Link from "component/link/index";
import UserEmailNew from "component/userEmailNew";
import UserEmailVerify from "component/userEmailVerify";
class ModalEmailCollection extends React.PureComponent {
renderInner() {
const { closeModal, email, user } = this.props;
const cancelButton = (
<Link button="text" onClick={closeModal} label={__("Not Now")} />
);
if (!user.has_verified_email && !email) {
return <UserEmailNew cancelButton={cancelButton} />;
} else if (!user.has_verified_email) {
return <UserEmailVerify cancelButton={cancelButton} />;
} else {
closeModal();
}
}
render() {
const { user } = this.props;
//this shouldn't happen
if (!user) {
return null;
}
return (
<Modal type="custom" isOpen={true} contentLabel="Email">
<section>
<h3 className="modal__header">
Can We <strike>Touch You</strike> Stay In Touch?
</h3>
{this.renderInner()}
</section>
</Modal>
);
}
}
export default ModalEmailCollection;

View file

@ -32,12 +32,7 @@ class ModalFirstReward extends React.PureComponent {
</p> </p>
<p> <p>
{__( {__(
"No need to understand it all just yet! Try watching or downloading something next." "No need to understand it all just yet! Try watching or publishing something next."
)}
</p>
<p>
{__(
"Finally, please know that LBRY is an early beta and that it earns the name."
)} )}
</p> </p>
</section> </section>

View file

@ -1,17 +0,0 @@
import React from "react";
import { connect } from "react-redux";
import { doCloseModal } from "redux/actions/app";
import { doNavigate } from "redux/actions/navigation";
import ModalInsufficientBalance from "./view";
const select = state => ({});
const perform = dispatch => ({
addBalance: () => {
dispatch(doNavigate("/wallet"));
dispatch(doCloseModal());
},
closeModal: () => dispatch(doCloseModal()),
});
export default connect(select, perform)(ModalInsufficientBalance);

View file

@ -1,26 +0,0 @@
import React from "react";
import { Modal } from "modal/modal";
class ModalInsufficientBalance extends React.PureComponent {
render() {
const { addBalance, closeModal } = this.props;
return (
<Modal
isOpen={true}
type="confirm"
contentLabel={__("Not enough credits")}
confirmButtonLabel={__("Get Credits")}
abortButtonLabel={__("Cancel")}
onAborted={closeModal}
onConfirmed={addBalance}
>
{__(
"Insufficient balance: after this transaction you would have less than 0 LBCs in your wallet."
)}
</Modal>
);
}
}
export default ModalInsufficientBalance;

View file

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

View file

@ -1,28 +0,0 @@
import React from "react";
import { Modal } from "modal/modal";
import { CurrencySymbol } from "component/common";
class ModalInsufficientCredits extends React.PureComponent {
render() {
const { addFunds, closeModal } = this.props;
return (
<Modal
isOpen={true}
type="confirm"
contentLabel={__("Not enough credits")}
confirmButtonLabel={__("Get Credits")}
abortButtonLabel={__("Not Now")}
onAborted={closeModal}
onConfirmed={addFunds}
>
<h3 className="modal__header">{__("More Credits Required")}</h3>
<p>
You'll need more <CurrencySymbol /> to do this.
</p>
</Modal>
);
}
}
export default ModalInsufficientCredits;

View file

@ -6,8 +6,12 @@ import { selectCurrentModal, selectModalProps } from "redux/selectors/app";
import { selectCurrentPage } from "redux/selectors/navigation"; import { selectCurrentPage } from "redux/selectors/navigation";
import { selectCostForCurrentPageUri } from "redux/selectors/cost_info"; import { selectCostForCurrentPageUri } from "redux/selectors/cost_info";
import { makeSelectClientSetting } from "redux/selectors/settings"; import { makeSelectClientSetting } from "redux/selectors/settings";
import { selectUser } from "redux/selectors/user"; import {
selectUser,
selectUserIsVerificationCandidate,
} from "redux/selectors/user";
import { selectBalance } from "redux/selectors/wallet"; import { selectBalance } from "redux/selectors/wallet";
import ModalRouter from "./view"; import ModalRouter from "./view";
const select = (state, props) => ({ const select = (state, props) => ({
@ -16,12 +20,16 @@ const select = (state, props) => ({
modal: selectCurrentModal(state), modal: selectCurrentModal(state),
modalProps: selectModalProps(state), modalProps: selectModalProps(state),
page: selectCurrentPage(state), page: selectCurrentPage(state),
isVerificationCandidate: selectUserIsVerificationCandidate(state),
isCreditIntroAcknowledged: makeSelectClientSetting(
settings.CREDIT_REQUIRED_ACKNOWLEDGED
)(state),
isEmailCollectionAcknowledged: makeSelectClientSetting(
settings.EMAIL_COLLECTION_ACKNOWLEDGED
)(state),
isWelcomeAcknowledged: makeSelectClientSetting( isWelcomeAcknowledged: makeSelectClientSetting(
settings.NEW_USER_ACKNOWLEDGED settings.NEW_USER_ACKNOWLEDGED
)(state), )(state),
isCreditIntroAcknowledged: makeSelectClientSetting(
settings.CREDIT_INTRO_ACKNOWLEDGED
)(state),
user: selectUser(state), user: selectUser(state),
}); });

View file

@ -2,7 +2,6 @@ import React from "react";
import ModalError from "modal/modalError"; import ModalError from "modal/modalError";
import ModalAuthFailure from "modal/modalAuthFailure"; import ModalAuthFailure from "modal/modalAuthFailure";
import ModalDownloading from "modal/modalDownloading"; import ModalDownloading from "modal/modalDownloading";
import ModalInsufficientCredits from "modal/modalInsufficientCredits";
import ModalUpgrade from "modal/modalUpgrade"; import ModalUpgrade from "modal/modalUpgrade";
import ModalWelcome from "modal/modalWelcome"; import ModalWelcome from "modal/modalWelcome";
import ModalFirstReward from "modal/modalFirstReward"; import ModalFirstReward from "modal/modalFirstReward";
@ -10,10 +9,10 @@ import ModalRewardApprovalRequired from "modal/modalRewardApprovalRequired";
import ModalCreditIntro from "modal/modalCreditIntro"; import ModalCreditIntro from "modal/modalCreditIntro";
import ModalRemoveFile from "modal/modalRemoveFile"; import ModalRemoveFile from "modal/modalRemoveFile";
import ModalTransactionFailed from "modal/modalTransactionFailed"; import ModalTransactionFailed from "modal/modalTransactionFailed";
import ModalInsufficientBalance from "modal/modalInsufficientBalance";
import ModalFileTimeout from "modal/modalFileTimeout"; import ModalFileTimeout from "modal/modalFileTimeout";
import ModalAffirmPurchase from "modal/modalAffirmPurchase"; import ModalAffirmPurchase from "modal/modalAffirmPurchase";
import ModalRevokeClaim from "modal/modalRevokeClaim"; import ModalRevokeClaim from "modal/modalRevokeClaim";
import ModalEmailCollection from "../modalEmailCollection";
import * as modals from "constants/modal_types"; import * as modals from "constants/modal_types";
class ModalRouter extends React.PureComponent { class ModalRouter extends React.PureComponent {
@ -43,8 +42,8 @@ class ModalRouter extends React.PureComponent {
const transitionModal = [ const transitionModal = [
this.checkShowWelcome, this.checkShowWelcome,
this.checkShowEmail,
this.checkShowCreditIntro, this.checkShowCreditIntro,
this.checkShowInsufficientCredits,
].reduce((acc, func) => { ].reduce((acc, func) => {
return !acc ? func.bind(this)(props) : acc; return !acc ? func.bind(this)(props) : acc;
}, false); }, false);
@ -74,24 +73,30 @@ class ModalRouter extends React.PureComponent {
} }
} }
checkShowEmail(props) {
const {
isEmailCollectionAcknowledged,
isVerificationCandidate,
user,
} = props;
if (
!isEmailCollectionAcknowledged &&
isVerificationCandidate &&
user &&
!user.has_verified_email
) {
return modals.EMAIL_COLLECTION;
}
}
checkShowCreditIntro(props) { checkShowCreditIntro(props) {
const { page, isCreditIntroAcknowledged, user } = props; const { balance, page, isCreditIntroAcknowledged } = props;
if ( if (
balance <= 0 &&
!isCreditIntroAcknowledged && !isCreditIntroAcknowledged &&
user && (["send", "publish"].includes(page) || this.isPaidShowPage(props))
!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; return modals.INSUFFICIENT_CREDITS;
} }
} }
@ -114,19 +119,15 @@ class ModalRouter extends React.PureComponent {
case modals.FILE_TIMEOUT: case modals.FILE_TIMEOUT:
return <ModalFileTimeout {...modalProps} />; return <ModalFileTimeout {...modalProps} />;
case modals.INSUFFICIENT_CREDITS: case modals.INSUFFICIENT_CREDITS:
return <ModalInsufficientCredits {...modalProps} />; return <ModalCreditIntro {...modalProps} />;
case modals.WELCOME: case modals.WELCOME:
return <ModalWelcome {...modalProps} />; return <ModalWelcome {...modalProps} />;
case modals.FIRST_REWARD: case modals.FIRST_REWARD:
return <ModalFirstReward {...modalProps} />; return <ModalFirstReward {...modalProps} />;
case modals.AUTHENTICATION_FAILURE: case modals.AUTHENTICATION_FAILURE:
return <ModalAuthFailure {...modalProps} />; return <ModalAuthFailure {...modalProps} />;
case modals.CREDIT_INTRO:
return <ModalCreditIntro {...modalProps} />;
case modals.TRANSACTION_FAILED: case modals.TRANSACTION_FAILED:
return <ModalTransactionFailed {...modalProps} />; return <ModalTransactionFailed {...modalProps} />;
case modals.INSUFFICIENT_BALANCE:
return <ModalInsufficientBalance {...modalProps} />;
case modals.REWARD_APPROVAL_REQUIRED: case modals.REWARD_APPROVAL_REQUIRED:
return <ModalRewardApprovalRequired {...modalProps} />; return <ModalRewardApprovalRequired {...modalProps} />;
case modals.CONFIRM_FILE_REMOVE: case modals.CONFIRM_FILE_REMOVE:
@ -135,6 +136,8 @@ class ModalRouter extends React.PureComponent {
return <ModalAffirmPurchase {...modalProps} />; return <ModalAffirmPurchase {...modalProps} />;
case modals.CONFIRM_CLAIM_REVOKE: case modals.CONFIRM_CLAIM_REVOKE:
return <ModalRevokeClaim {...modalProps} />; return <ModalRevokeClaim {...modalProps} />;
case modals.EMAIL_COLLECTION:
return <ModalEmailCollection {...modalProps} />;
default: default:
return null; return null;
} }

View file

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

View file

@ -1,15 +1,28 @@
import React from "react"; import React from "react";
import SubHeader from "component/subHeader"; import SubHeader from "component/subHeader";
import Link from "component/link"; import Link from "component/link";
import WalletAddress from "component/walletAddress"; import RewardSummary from "component/rewardSummary";
import ShapeShift from "component/shapeShift"; import ShapeShift from "component/shapeShift";
const ReceiveCreditsPage = props => { const GetCreditsPage = props => {
return ( return (
<main className="main--single-column"> <main className="main--single-column">
<SubHeader /> <SubHeader />
<WalletAddress /> <RewardSummary />
<ShapeShift /> <ShapeShift />
<section className="card">
<div className="card__title-primary">
<h3>{__("From External Wallet")}</h3>
</div>
<div className="card__actions">
<Link
button="alt"
navigate="/send"
icon="icon-send"
label={__("Send / Receive")}
/>
</div>
</section>
<section className="card"> <section className="card">
<div className="card__title-primary"> <div className="card__title-primary">
<h3>{__("More ways to get LBRY Credits")}</h3> <h3>{__("More ways to get LBRY Credits")}</h3>
@ -33,4 +46,4 @@ const ReceiveCreditsPage = props => {
); );
}; };
export default ReceiveCreditsPage; export default GetCreditsPage;

View file

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

View file

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

View file

@ -1,14 +1,16 @@
import React from "react"; import React from "react";
import SubHeader from "component/subHeader"; import SubHeader from "component/subHeader";
import WalletSend from "component/walletSend"; import WalletSend from "component/walletSend";
import WalletAddress from "component/walletAddress";
const SendCreditsPage = props => { const SendReceivePage = props => {
return ( return (
<main className="main--single-column"> <main className="main--single-column">
<SubHeader /> <SubHeader />
<WalletSend /> <WalletSend />
<WalletAddress />
</main> </main>
); );
}; };
export default SendCreditsPage; export default SendReceivePage;

View file

@ -4,6 +4,7 @@ import lbryio from "lbryio";
import rewards from "rewards"; import rewards from "rewards";
import { selectUnclaimedRewardsByType } from "redux/selectors/rewards"; import { selectUnclaimedRewardsByType } from "redux/selectors/rewards";
import { selectUserIsRewardApproved } from "redux/selectors/user"; import { selectUserIsRewardApproved } from "redux/selectors/user";
import { selectClaimedRewardsById } from "../selectors/rewards";
export function doRewardList() { export function doRewardList() {
return function(dispatch, getState) { return function(dispatch, getState) {
@ -42,7 +43,7 @@ export function doClaimRewardType(rewardType) {
return; return;
} }
if (!userIsRewardApproved) { if (!userIsRewardApproved && rewardType !== rewards.TYPE_CONFIRM_EMAIL) {
return dispatch({ return dispatch({
type: types.OPEN_MODAL, type: types.OPEN_MODAL,
data: { modal: modals.REWARD_APPROVAL_REQUIRED }, data: { modal: modals.REWARD_APPROVAL_REQUIRED },
@ -61,7 +62,7 @@ export function doClaimRewardType(rewardType) {
reward, reward,
}, },
}); });
if (reward.reward_type == rewards.TYPE_NEW_USER) { if (reward.reward_type == rewards.TYPE_CONFIRM_EMAIL) {
dispatch({ dispatch({
type: types.OPEN_MODAL, type: types.OPEN_MODAL,
data: { modal: modals.FIRST_REWARD }, data: { modal: modals.FIRST_REWARD },

View file

@ -61,6 +61,22 @@ export function doUserEmailNew(email) {
type: types.USER_EMAIL_NEW_STARTED, type: types.USER_EMAIL_NEW_STARTED,
email: email, email: email,
}); });
const success = () => {
dispatch({
type: types.USER_EMAIL_NEW_SUCCESS,
data: { email },
});
dispatch(doUserFetch());
}
const failure = error => {
dispatch({
type: types.USER_EMAIL_NEW_FAILURE,
data: { error },
});
}
lbryio lbryio
.call( .call(
"user_email", "user_email",
@ -75,23 +91,11 @@ export function doUserEmailNew(email) {
"resend_token", "resend_token",
{ email: email, only_if_expired: true }, { email: email, only_if_expired: true },
"post" "post"
); ).then(success, failure);
} }
throw error; throw error;
}) })
.then(() => { .then(success, failure);
dispatch({
type: types.USER_EMAIL_NEW_SUCCESS,
data: { email },
});
dispatch(doUserFetch());
})
.catch(error => {
dispatch({
type: types.USER_EMAIL_NEW_FAILURE,
data: { error },
});
});
}; };
} }
@ -118,6 +122,7 @@ export function doUserEmailVerify(verificationToken) {
type: types.USER_EMAIL_VERIFY_SUCCESS, type: types.USER_EMAIL_VERIFY_SUCCESS,
data: { email }, data: { email },
}); });
dispatch(doClaimRewardType(rewards.TYPE_CONFIRM_EMAIL)),
dispatch(doUserFetch()); dispatch(doUserFetch());
} else { } else {
throw new Error("Your email is still not verified."); //shouldn't happen throw new Error("Your email is still not verified."); //shouldn't happen

View file

@ -97,7 +97,7 @@ export function doSendDraftTransaction() {
const amount = selectDraftTransactionAmount(state); const amount = selectDraftTransactionAmount(state);
if (balance - amount <= 0) { if (balance - amount <= 0) {
return dispatch(doOpenModal(modals.INSUFFICIENT_BALANCE)); return dispatch(doOpenModal(modals.INSUFFICIENT_CREDITS));
} }
dispatch({ dispatch({
@ -162,7 +162,7 @@ export function doSendSupport(amount, claim_id, uri) {
const balance = selectBalance(state); const balance = selectBalance(state);
if (balance - amount <= 0) { if (balance - amount <= 0) {
return dispatch(doOpenModal(modals.INSUFFICIENT_BALANCE)); return dispatch(doOpenModal(modals.INSUFFICIENT_CREDITS));
} }
dispatch({ dispatch({

View file

@ -24,9 +24,10 @@ const defaultState = {
settings.NEW_USER_ACKNOWLEDGED, settings.NEW_USER_ACKNOWLEDGED,
false false
), ),
credit_intro_acknowledged: getLocalStorageSetting( email_collection_acknowledged: getLocalStorageSetting(
settings.CREDIT_INTRO_ACKNOWLEDGED settings.EMAIL_COLLECTION_ACKNOWLEDGED
), ),
credit_required_acknowledged: false, //this needs to be re-acknowledged every run
language: getLocalStorageSetting(settings.LANGUAGE, "en"), language: getLocalStorageSetting(settings.LANGUAGE, "en"),
theme: getLocalStorageSetting(settings.THEME, "light"), theme: getLocalStorageSetting(settings.THEME, "light"),
themes: getLocalStorageSetting(settings.THEMES, []), themes: getLocalStorageSetting(settings.THEMES, []),

View file

@ -37,17 +37,17 @@ export const selectHeaderLinks = createSelector(selectCurrentPage, page => {
case "wallet": case "wallet":
case "history": case "history":
case "send": case "send":
case "receive": case "getcredits":
case "invite": case "invite":
case "rewards": case "rewards":
case "backup": case "backup":
return { return {
wallet: __("Overview"), wallet: __("Overview"),
history: __("History"), getcredits: __("Get Credits"),
send: __("Send Credits"), send: __("Send / Receive"),
receive: __("Get Credits"),
rewards: __("Rewards"), rewards: __("Rewards"),
invite: __("Invites"), invite: __("Invites"),
history: __("History"),
}; };
case "downloaded": case "downloaded":
case "published": case "published":
@ -78,8 +78,8 @@ export const selectPageTitle = createSelector(
case "wallet": case "wallet":
return __("Wallet"); return __("Wallet");
case "send": case "send":
return __("Send LBRY Credits"); return __("Send or Receive LBRY Credits");
case "receive": case "getcredits":
return __("Get LBRY Credits"); return __("Get LBRY Credits");
case "backup": case "backup":
return __("Backup Your Wallet"); return __("Backup Your Wallet");

View file

@ -63,9 +63,8 @@ export const selectWunderBarIcon = createSelector(
return "icon-rocket"; return "icon-rocket";
case "invite": case "invite":
return "icon-envelope-open"; return "icon-envelope-open";
case "address": case "getcredits":
case "receive": return "icon-shopping-cart";
return "icon-credit-card";
case "wallet": case "wallet":
case "backup": case "backup":
return "icon-bank"; return "icon-bank";

View file

@ -14,16 +14,17 @@ export const selectUserIsPending = createSelector(
export const selectUser = createSelector(_selectState, state => state.user); export const selectUser = createSelector(_selectState, state => state.user);
export const selectEmailToVerify = createSelector(
_selectState,
state => state.emailToVerify
);
export const selectUserEmail = createSelector( export const selectUserEmail = createSelector(
selectUser, selectUser,
user => (user ? user.primary_email : null) user => (user ? user.primary_email : null)
); );
export const selectEmailToVerify = createSelector(
_selectState,
selectUserEmail,
(state, userEmail) => state.emailToVerify || userEmail
);
export const selectUserIsRewardApproved = createSelector( export const selectUserIsRewardApproved = createSelector(
selectUser, selectUser,
user => user && user.is_reward_approved user => user && user.is_reward_approved

View file

@ -33,10 +33,7 @@ function rewardMessage(type, amount) {
"You earned %s LBC for watching a featured download.", "You earned %s LBC for watching a featured download.",
amount amount
), ),
referral: __( referral: __("You earned %s LBC for referring someone.", amount),
"You earned %s LBC for referring someone.",
amount
),
}[type]; }[type];
} }
@ -44,7 +41,7 @@ const rewards = {};
rewards.TYPE_NEW_DEVELOPER = "new_developer"; rewards.TYPE_NEW_DEVELOPER = "new_developer";
rewards.TYPE_NEW_USER = "new_user"; rewards.TYPE_NEW_USER = "new_user";
rewards.TYPE_CONFIRM_EMAIL = "confirm_email"; rewards.TYPE_CONFIRM_EMAIL = "verified_email";
rewards.TYPE_FIRST_CHANNEL = "new_channel"; rewards.TYPE_FIRST_CHANNEL = "new_channel";
rewards.TYPE_FIRST_STREAM = "first_stream"; rewards.TYPE_FIRST_STREAM = "first_stream";
rewards.TYPE_MANY_DOWNLOADS = "many_downloads"; rewards.TYPE_MANY_DOWNLOADS = "many_downloads";

View file

@ -128,7 +128,7 @@ $text-color: #000;
--card-small-width: $spacing-vertical * 10; --card-small-width: $spacing-vertical * 10;
/* Modal */ /* Modal */
--modal-width: 420px; --modal-width: 440px;
--modal-bg: var(--color-bg); --modal-bg: var(--color-bg);
--modal-overlay-bg: rgba(#F5F5F5, 0.75); // --color-canvas: #F5F5F5 --modal-overlay-bg: rgba(#F5F5F5, 0.75); // --color-canvas: #F5F5F5
--modal-border: 1px solid rgb(204, 204, 204); --modal-border: 1px solid rgb(204, 204, 204);