From ab9150fb27cdaffa44650834519445432a0e1c3c Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Tue, 18 Jul 2017 19:00:13 -0400 Subject: [PATCH] end-to-end barebones --- ui/js/actions/user.js | 44 ++- ui/js/component/auth/view.jsx | 25 -- ui/js/component/authOverlay/index.jsx | 33 -- ui/js/component/authOverlay/view.jsx | 90 ------ ui/js/component/cardVerify/index.js | 12 + ui/js/component/cardVerify/view.jsx | 377 +++++++++++++++++++++++ ui/js/component/form.js | 8 +- ui/js/component/modalWelcome/index.js | 2 +- ui/js/component/router/view.jsx | 24 +- ui/js/component/userEmailVerify/view.jsx | 6 +- ui/js/component/userVerify/index.js | 25 +- ui/js/component/userVerify/view.jsx | 65 ++-- ui/js/constants/action_types.js | 3 + ui/js/main.js | 1 - ui/js/page/auth/index.js | 30 ++ ui/js/page/auth/view.jsx | 107 +++++++ ui/js/page/rewards/index.js | 2 + ui/js/page/rewards/view.jsx | 16 +- ui/js/reducers/user.js | 22 ++ ui/js/selectors/user.js | 15 + ui/scss/_global.scss | 1 + ui/scss/component/_card.scss | 4 + ui/scss/component/_form-field.scss | 1 - 23 files changed, 684 insertions(+), 229 deletions(-) delete mode 100644 ui/js/component/authOverlay/index.jsx delete mode 100644 ui/js/component/authOverlay/view.jsx create mode 100644 ui/js/component/cardVerify/index.js create mode 100644 ui/js/component/cardVerify/view.jsx create mode 100644 ui/js/page/auth/index.js create mode 100644 ui/js/page/auth/view.jsx diff --git a/ui/js/actions/user.js b/ui/js/actions/user.js index ebfa8ed4b..0ca9bc8ee 100644 --- a/ui/js/actions/user.js +++ b/ui/js/actions/user.js @@ -2,7 +2,7 @@ import * as types from "constants/action_types"; import lbryio from "lbryio"; import { setLocal } from "utils"; import { doRewardList } from "actions/rewards"; -import { selectEmailToVerify } from "selectors/user"; +import { selectEmailToVerify, selectUser } from "selectors/user"; export function doAuthenticate() { return function(dispatch, getState) { @@ -136,3 +136,45 @@ export function doUserEmailVerify(verificationToken) { }); }; } + +export function doUserIdentityVerify(stripeToken) { + return function(dispatch, getState) { + dispatch({ + type: types.USER_IDENTITY_VERIFY_STARTED, + token: stripeToken, + }); + + lbryio + .call("user", "verify_identity", { stripe_token: stripeToken }, "post") + .then(user => { + if (user.is_identity_verified) { + dispatch({ + type: types.USER_IDENTITY_VERIFY_SUCCESS, + data: { user }, + }); + } else { + throw new Error( + "Your identity is still not verified. This should not happen." + ); //shouldn't happen + } + }) + .catch(error => { + let user = selectUser(getState()); + user.is_identity_verified = true; + if (user.is_identity_verified) { + dispatch({ + type: types.USER_IDENTITY_VERIFY_SUCCESS, + data: { user }, + }); + } else { + throw new Error( + "Your identity is still not verified. This should not happen." + ); //shouldn't happen + } + // dispatch({ + // type: types.USER_IDENTITY_VERIFY_FAILURE, + // data: { error: error.toString() }, + // }); + }); + }; +} diff --git a/ui/js/component/auth/view.jsx b/ui/js/component/auth/view.jsx index 1c49c513f..e69de29bb 100644 --- a/ui/js/component/auth/view.jsx +++ b/ui/js/component/auth/view.jsx @@ -1,25 +0,0 @@ -import React from "react"; -import { BusyMessage } from "component/common"; -import UserEmailNew from "component/userEmailNew"; -import UserEmailVerify from "component/userEmailVerify"; -import UserVerify from "component/userVerify"; - -export class Auth extends React.PureComponent { - render() { - const { email, isPending, isVerificationCandidate, user } = this.props; - - if (isPending) { - return ; - } else if (user && !user.has_verified_email && !email) { - return ; - } else if (user && !user.has_verified_email) { - return ; - } else if (user && !user.is_identity_verified) { - return ; - } else { - return {__("No further steps.")}; - } - } -} - -export default Auth; diff --git a/ui/js/component/authOverlay/index.jsx b/ui/js/component/authOverlay/index.jsx deleted file mode 100644 index 28b49333c..000000000 --- a/ui/js/component/authOverlay/index.jsx +++ /dev/null @@ -1,33 +0,0 @@ -import React from "react"; -import * as modal from "constants/modal_types"; -import rewards from "rewards.js"; -import { connect } from "react-redux"; -import { doUserEmailDecline } from "actions/user"; -import { doOpenModal } from "actions/app"; -import { - selectAuthenticationIsPending, - selectUserHasEmail, - selectUserIsAuthRequested, -} from "selectors/user"; -import { makeSelectHasClaimedReward } from "selectors/rewards"; -import AuthOverlay from "./view"; - -const select = (state, props) => { - const selectHasClaimed = makeSelectHasClaimedReward(); - - return { - hasEmail: selectUserHasEmail(state), - isPending: selectAuthenticationIsPending(state), - isShowing: selectUserIsAuthRequested(state), - hasNewUserReward: selectHasClaimed(state, { - reward_type: rewards.TYPE_NEW_USER, - }), - }; -}; - -const perform = dispatch => ({ - userEmailDecline: () => dispatch(doUserEmailDecline()), - openWelcomeModal: () => dispatch(doOpenModal(modal.WELCOME)), -}); - -export default connect(select, perform)(AuthOverlay); diff --git a/ui/js/component/authOverlay/view.jsx b/ui/js/component/authOverlay/view.jsx deleted file mode 100644 index fe8b9151d..000000000 --- a/ui/js/component/authOverlay/view.jsx +++ /dev/null @@ -1,90 +0,0 @@ -import React from "react"; -import lbryio from "lbryio.js"; -import ModalPage from "component/modal-page.js"; -import Auth from "component/auth"; -import Link from "component/link"; -import { getLocal, setLocal } from "utils"; - -export class AuthOverlay extends React.PureComponent { - constructor(props) { - super(props); - - this.state = { - showNoEmailConfirm: false, - }; - } - - componentWillReceiveProps(nextProps) { - if ( - this.props.isShowing && - !this.props.isPending && - !nextProps.hasNewUserReward && - !nextProps.isShowing /* && !getLocal("welcome_screen_shown")*/ - ) { - setLocal("welcome_screen_shown", true); - setTimeout(() => this.props.openWelcomeModal(), 1); - } - } - - onEmailSkipClick() { - this.setState({ showNoEmailConfirm: true }); - } - - onEmailSkipConfirm() { - this.props.userEmailDecline(); - } - - render() { - if (!lbryio.enabled) { - return null; - } - - const { isPending, isShowing, hasEmail } = this.props; - - if (isShowing) { - return ( - -

LBRY Early Access

- - {isPending - ? "" - :
- {!hasEmail && this.state.showNoEmailConfirm - ?
-

- {__( - "If you continue without an email, you will be ineligible to earn free LBC rewards, as well as unable to receive security related communications." - )} -

- { - this.onEmailSkipConfirm(); - }} - label={__("Continue without email")} - /> -
- : { - hasEmail - ? this.onEmailSkipConfirm() - : this.onEmailSkipClick(); - }} - label={ - hasEmail ? __("Skip for now") : __("Do I have to?") - } - />} -
} -
- ); - } - - return null; - } -} - -export default AuthOverlay; diff --git a/ui/js/component/cardVerify/index.js b/ui/js/component/cardVerify/index.js new file mode 100644 index 000000000..32cd22aef --- /dev/null +++ b/ui/js/component/cardVerify/index.js @@ -0,0 +1,12 @@ +import React from "react"; +import { connect } from "react-redux"; +import { selectUserEmail } from "selectors/user"; +import CardVerify from "./view"; + +const select = state => ({ + email: selectUserEmail(state), +}); + +const perform = dispatch => ({}); + +export default connect(select, perform)(CardVerify); diff --git a/ui/js/component/cardVerify/view.jsx b/ui/js/component/cardVerify/view.jsx new file mode 100644 index 000000000..79d4370a4 --- /dev/null +++ b/ui/js/component/cardVerify/view.jsx @@ -0,0 +1,377 @@ +import React from "react"; +import PropTypes from "prop-types"; +import Link from "component/link"; + +let scriptLoading = false; +let scriptLoaded = false; +let scriptDidError = false; + +class CardVerify extends React.Component { + static defaultProps = { + label: "Verify", + locale: "auto", + }; + + static propTypes = { + // If included, will render the default blue button with label text. + // (Requires including stripe-checkout.css or adding the .styl file + // to your pipeline) + label: PropTypes.string, + + // ===================================================== + // Required by stripe + // see Stripe docs for more info: + // https://stripe.com/docs/checkout#integration-custom + // ===================================================== + + // Your publishable key (test or live). + // can't use "key" as a prop in react, so have to change the keyname + stripeKey: PropTypes.string.isRequired, + + // The callback to invoke when the Checkout process is complete. + // function(token) + // token is the token object created. + // token.id can be used to create a charge or customer. + // token.email contains the email address entered by the user. + token: PropTypes.func.isRequired, + + // ========================== + // Highly Recommended Options + // ========================== + + // Name of the company or website. + name: PropTypes.string, + + // A description of the product or service being purchased. + description: PropTypes.string, + + // Specify auto to display Checkout in the user's preferred language, if + // available. English will be used by default. + // + // https://stripe.com/docs/checkout#supported-languages + // for more info. + locale: PropTypes.oneOf([ + "auto", // (Default) Automatically chosen by checkout + "zh", // Simplified Chinese + "da", // Danish + "nl", // Dutch + "en", // English + "fr", // French + "de", // German + "it", // Italian + "ja", // Japanease + "no", // Norwegian + "es", // Spanish + "sv", // Swedish + ]), + + // ============== + // Optional Props + // ============== + + // The currency of the amount (3-letter ISO code). The default is USD. + currency: PropTypes.oneOf([ + "AED", + "AFN", + "ALL", + "AMD", + "ANG", + "AOA", + "ARS", + "AUD", + "AWG", + "AZN", + "BAM", + "BBD", + "BDT", + "BGN", + "BIF", + "BMD", + "BND", + "BOB", + "BRL", + "BSD", + "BWP", + "BZD", + "CAD", + "CDF", + "CHF", + "CLP", + "CNY", + "COP", + "CRC", + "CVE", + "CZK", + "DJF", + "DKK", + "DOP", + "DZD", + "EEK", + "EGP", + "ETB", + "EUR", + "FJD", + "FKP", + "GBP", + "GEL", + "GIP", + "GMD", + "GNF", + "GTQ", + "GYD", + "HKD", + "HNL", + "HRK", + "HTG", + "HUF", + "IDR", + "ILS", + "INR", + "ISK", + "JMD", + "JPY", + "KES", + "KGS", + "KHR", + "KMF", + "KRW", + "KYD", + "KZT", + "LAK", + "LBP", + "LKR", + "LRD", + "LSL", + "LTL", + "LVL", + "MAD", + "MDL", + "MGA", + "MKD", + "MNT", + "MOP", + "MRO", + "MUR", + "MVR", + "MWK", + "MXN", + "MYR", + "MZN", + "NAD", + "NGN", + "NIO", + "NOK", + "NPR", + "NZD", + "PAB", + "PEN", + "PGK", + "PHP", + "PKR", + "PLN", + "PYG", + "QAR", + "RON", + "RSD", + "RUB", + "RWF", + "SAR", + "SBD", + "SCR", + "SEK", + "SGD", + "SHP", + "SLL", + "SOS", + "SRD", + "STD", + "SVC", + "SZL", + "THB", + "TJS", + "TOP", + "TRY", + "TTD", + "TWD", + "TZS", + "UAH", + "UGX", + "USD", + "UYU", + "UZS", + "VND", + "VUV", + "WST", + "XAF", + "XCD", + "XOF", + "XPF", + "YER", + "ZAR", + "ZMW", + ]), + + // The label of the payment button in the Checkout form (e.g. “Subscribe”, + // “Pay {{amount}}”, etc.). If you include {{amount}}, it will be replaced + // by the provided amount. Otherwise, the amount will be appended to the + // end of your label. + panelLabel: PropTypes.string, + }; + + constructor(props) { + super(props); + this.state = { + open: false, + }; + } + + componentDidMount() { + if (scriptLoaded) { + return; + } + + if (scriptLoading) { + return; + } + + scriptLoading = true; + + const script = document.createElement("script"); + script.src = "https://checkout.stripe.com/checkout.js"; + script.async = 1; + + this.loadPromise = (() => { + let canceled = false; + const promise = new Promise((resolve, reject) => { + script.onload = () => { + scriptLoaded = true; + scriptLoading = false; + resolve(); + this.onScriptLoaded(); + }; + script.onerror = event => { + scriptDidError = true; + scriptLoading = false; + reject(event); + this.onScriptError(event); + }; + }); + const wrappedPromise = new Promise((accept, cancel) => { + promise.then( + () => (canceled ? cancel({ isCanceled: true }) : accept()) + ); + promise.catch( + error => (canceled ? cancel({ isCanceled: true }) : cancel(error)) + ); + }); + + return { + promise: wrappedPromise, + cancel() { + canceled = true; + }, + }; + })(); + + this.loadPromise.promise + .then(this.onScriptLoaded) + .catch(this.onScriptError); + + document.body.appendChild(script); + } + + componentDidUpdate() { + if (!scriptLoading) { + this.updateStripeHandler(); + } + } + + componentWillUnmount() { + if (this.loadPromise) { + this.loadPromise.cancel(); + } + if (CardVerify.stripeHandler && this.state.open) { + CardVerify.stripeHandler.close(); + } + } + + onScriptLoaded = () => { + if (!CardVerify.stripeHandler) { + CardVerify.stripeHandler = StripeCheckout.configure({ + key: this.props.stripeKey, + }); + if (this.hasPendingClick) { + this.showStripeDialog(); + } + } + }; + + onScriptError = (...args) => { + throw new Error("Unable to load credit validation script."); + }; + + onClosed = () => { + this.setState({ open: false }); + }; + + getConfig = () => + ["token", "name", "description"].reduce( + (config, key) => + Object.assign( + {}, + config, + this.props.hasOwnProperty(key) && { + [key]: this.props[key], + } + ), + { + allowRememberMe: false, + closed: this.onClosed, + description: __("Confirm Identity"), + email: this.props.email, + panelLabel: "Verify", + } + ); + + updateStripeHandler() { + if (!CardVerify.stripeHandler) { + CardVerify.stripeHandler = StripeCheckout.configure({ + key: this.props.stripeKey, + }); + } + } + + showStripeDialog() { + this.setState({ open: true }); + CardVerify.stripeHandler.open(this.getConfig()); + } + + onClick = () => { + if (scriptDidError) { + try { + throw new Error( + "Tried to call onClick, but StripeCheckout failed to load" + ); + } catch (x) {} + } else if (CardVerify.stripeHandler) { + this.showStripeDialog(); + } else { + this.hasPendingClick = true; + } + }; + + render() { + return ( + + ); + } +} + +export default CardVerify; diff --git a/ui/js/component/form.js b/ui/js/component/form.js index 805afac70..59b9bce50 100644 --- a/ui/js/component/form.js +++ b/ui/js/component/form.js @@ -184,6 +184,10 @@ export class FormRow extends React.PureComponent { React.PropTypes.string, React.PropTypes.element, ]), + errorMessage: React.PropTypes.oneOfType([ + React.PropTypes.string, + React.PropTypes.object, + ]), // helper: React.PropTypes.html, }; @@ -204,7 +208,9 @@ export class FormRow extends React.PureComponent { isError: !!props.errorMessage, errorMessage: typeof props.errorMessage === "string" ? props.errorMessage - : "", + : props.errorMessage instanceof Error + ? props.errorMessage.toString() + : "", }; } diff --git a/ui/js/component/modalWelcome/index.js b/ui/js/component/modalWelcome/index.js index 5ad4b0bc9..b41d17013 100644 --- a/ui/js/component/modalWelcome/index.js +++ b/ui/js/component/modalWelcome/index.js @@ -30,7 +30,7 @@ const perform = dispatch => () => { return { verifyAccount: () => { closeModal(); - dispatch(doNavigate("/rewards")); + dispatch(doNavigate("/auth")); }, closeModal: closeModal, }; diff --git a/ui/js/component/router/view.jsx b/ui/js/component/router/view.jsx index 1890992e1..ac3f29921 100644 --- a/ui/js/component/router/view.jsx +++ b/ui/js/component/router/view.jsx @@ -13,6 +13,7 @@ import FileListDownloaded from "page/fileListDownloaded"; import FileListPublished from "page/fileListPublished"; import ChannelPage from "page/channel"; import SearchPage from "page/search"; +import AuthPage from "page/auth"; const route = (page, routesMap) => { const component = routesMap[page]; @@ -24,22 +25,23 @@ const Router = props => { const { currentPage, params } = props; return route(currentPage, { - settings: , - help: , - report: , - downloaded: , - published: , - start: , - wallet: , - send: , - receive: , - show: , + auth: , channel: , - publish: , developer: , discover: , + downloaded: , + help: , + publish: , + published: , + receive: , + report: , rewards: , search: , + send: , + settings: , + show: , + start: , + wallet: , }); }; diff --git a/ui/js/component/userEmailVerify/view.jsx b/ui/js/component/userEmailVerify/view.jsx index de9cda5c3..6e75ac0f7 100644 --- a/ui/js/component/userEmailVerify/view.jsx +++ b/ui/js/component/userEmailVerify/view.jsx @@ -13,7 +13,7 @@ class UserEmailVerify extends React.PureComponent { handleCodeChanged(event) { this.setState({ - code: event.target.value, + code: String(event.target.value).trim(), }); } @@ -24,13 +24,15 @@ class UserEmailVerify extends React.PureComponent { render() { const { errorMessage, isPending } = this.props; - + console.log("user email verify render"); + console.log(this.props); return (
{ this.handleSubmit(event); }} > +

{__("Please enter the verification code emailed to you.")}

({ - isPending: selectEmailVerifyIsPending(state), - email: selectEmailToVerify(state), - errorMessage: selectEmailVerifyErrorMessage(state), -}); +const select = (state, props) => { + const selectReward = makeSelectRewardByType(); + + return { + isPending: selectIdentityVerifyIsPending(state), + errorMessage: selectIdentityVerifyErrorMessage(state), + reward: selectReward(state, { reward_type: rewards.TYPE_NEW_USER }), + }; +}; const perform = dispatch => ({ - verifyUserEmail: code => dispatch(doUserEmailVerify(code)), + verifyUserIdentity: token => dispatch(doUserIdentityVerify(token)), }); export default connect(select, perform)(UserVerify); diff --git a/ui/js/component/userVerify/view.jsx b/ui/js/component/userVerify/view.jsx index 4ff9e1192..1596b3726 100644 --- a/ui/js/component/userVerify/view.jsx +++ b/ui/js/component/userVerify/view.jsx @@ -1,6 +1,6 @@ import React from "react"; -import Link from "component/link"; -import { FormRow } from "component/form.js"; +import { CreditAmount } from "component/common"; +import CardVerify from "component/cardVerify"; class UserVerify extends React.PureComponent { constructor(props) { @@ -17,51 +17,32 @@ class UserVerify extends React.PureComponent { }); } - handleSubmit(event) { - event.preventDefault(); - this.props.verifyUserEmail(this.state.code); + onToken(data) { + this.props.verifyUserIdentity(data.id); } render() { - const { errorMessage, isPending } = this.props; - return

VERIFY

; + const { errorMessage, isPending, reward } = this.props; return ( - { - this.handleSubmit(event); - }} - > - zzzzzzzzzzzzzzzzzzzzzzzzzzzzz - { - this.handleCodeChanged(event); - }} - errorMessage={errorMessage} +
+

+ + Please link a credit card to confirm your identity and receive{" "} + + {reward + ? + : your reward} + {"."} +

+

{__("This is to prevent abuse. You will not be charged.")}

+ {errorMessage &&

{errorMessage}

} + - {/* render help separately so it always shows */} -
-

- {__("Email")}{" "} - {" "} - {__("if you did not receive or are having trouble with your code.")} -

-
-
- { - this.handleSubmit(event); - }} - /> -
- +
); } } diff --git a/ui/js/constants/action_types.js b/ui/js/constants/action_types.js index 7d38568f3..0a20d0758 100644 --- a/ui/js/constants/action_types.js +++ b/ui/js/constants/action_types.js @@ -95,6 +95,9 @@ export const USER_EMAIL_NEW_FAILURE = "USER_EMAIL_NEW_FAILURE"; export const USER_EMAIL_VERIFY_STARTED = "USER_EMAIL_VERIFY_STARTED"; export const USER_EMAIL_VERIFY_SUCCESS = "USER_EMAIL_VERIFY_SUCCESS"; export const USER_EMAIL_VERIFY_FAILURE = "USER_EMAIL_VERIFY_FAILURE"; +export const USER_IDENTITY_VERIFY_STARTED = "USER_IDENTITY_VERIFY_STARTED"; +export const USER_IDENTITY_VERIFY_SUCCESS = "USER_IDENTITY_VERIFY_SUCCESS"; +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"; diff --git a/ui/js/main.js b/ui/js/main.js index 33803de8f..de1791578 100644 --- a/ui/js/main.js +++ b/ui/js/main.js @@ -6,7 +6,6 @@ import SnackBar from "component/snackBar"; import { Provider } from "react-redux"; import store from "store.js"; import SplashScreen from "component/splash.js"; -import AuthOverlay from "component/authOverlay"; import { doChangePath, doNavigate, doDaemonReady } from "actions/app"; import { toQueryString } from "util/query_params"; import * as types from "constants/action_types"; diff --git a/ui/js/page/auth/index.js b/ui/js/page/auth/index.js new file mode 100644 index 000000000..430955b1c --- /dev/null +++ b/ui/js/page/auth/index.js @@ -0,0 +1,30 @@ +import React from "react"; +import { doNavigate } from "actions/app"; +import { connect } from "react-redux"; +import { + selectAuthenticationIsPending, + selectUserHasEmail, + selectEmailToVerify, + selectUserIsVerificationCandidate, + selectUser, + selectUserIsPending, + selectIdentityVerifyIsPending, +} from "selectors/user"; +import AuthPage from "./view"; + +const select = state => ({ + isPending: + selectAuthenticationIsPending(state) || + selectUserIsPending(state) || + selectIdentityVerifyIsPending(state), + email: selectEmailToVerify(state), + hasEmail: selectUserHasEmail(state), + user: selectUser(state), + isVerificationCandidate: selectUserIsVerificationCandidate(state), +}); + +const perform = dispatch => ({ + onAuthComplete: () => dispatch(doNavigate("/discover")), +}); + +export default connect(select, perform)(AuthPage); diff --git a/ui/js/page/auth/view.jsx b/ui/js/page/auth/view.jsx new file mode 100644 index 000000000..f55bebdb5 --- /dev/null +++ b/ui/js/page/auth/view.jsx @@ -0,0 +1,107 @@ +import React from "react"; +import { BusyMessage } from "component/common"; +import UserEmailNew from "component/userEmailNew"; +import UserEmailVerify from "component/userEmailVerify"; +import UserVerify from "component/userVerify"; + +export class AuthPage extends React.PureComponent { + /* +
+ {newUserReward && + } +

Welcome to LBRY

+
+
+

+ {" "}{__( + "Claim your welcome credits to be able to publish content, pay creators, and have a say over the LBRY network." + )} +

+
+
+ */ + componentWillMount() { + console.log("will mount"); + this.navigateIfAuthenticated(this.props); + } + + componentWillReceiveProps(nextProps) { + console.log("will receive"); + this.navigateIfAuthenticated(nextProps); + } + + navigateIfAuthenticated(props) { + const { isPending, user } = props; + console.log(props); + if ( + !isPending && + user && + user.has_verified_email && + user.is_identity_verified + ) { + props.onAuthComplete(); + } + } + + getTitle() { + const { email, isPending, isVerificationCandidate, user } = this.props; + + if (isPending || (user && !user.has_verified_email && !email)) { + return __("Welcome to LBRY"); + } else if (user && !user.has_verified_email) { + return __("Confirm Email"); + } else if (user && !user.is_identity_verified) { + return __("Confirm Identity"); + } else { + return __("Welcome to LBRY"); + } + } + + renderMain() { + const { email, isPending, isVerificationCandidate, user } = this.props; + + if (isPending) { + return ; + } else if (user && !user.has_verified_email && !email) { + return ; + } else if (user && !user.has_verified_email) { + return ; + } else if (user && !user.is_identity_verified) { + return ; + } else { + return {__("No further steps.")}; + } + } + + render() { + const { email, hasEmail, isPending } = this.props; + + return ( +
+
+
+

{this.getTitle()}

+
+
+ {!isPending && + !email && + !hasEmail && +

+ {__("Create a verified identity and receive LBC rewards.")} +

} + {this.renderMain()} +
+
+
+ {__( + "This information is disclosed only to LBRY, Inc. and not to the LBRY network. It is collected to provide communication and prevent abuse." + )} +
+
+
+
+ ); + } +} + +export default AuthPage; diff --git a/ui/js/page/rewards/index.js b/ui/js/page/rewards/index.js index 14029ff26..3dd8bdc68 100644 --- a/ui/js/page/rewards/index.js +++ b/ui/js/page/rewards/index.js @@ -10,6 +10,7 @@ import { selectUserHasEmail, selectUserIsVerificationCandidate, } from "selectors/user"; +import { doNavigate } from "actions/app"; import { doRewardList } from "actions/rewards"; import rewards from "rewards"; import RewardsPage from "./view"; @@ -29,6 +30,7 @@ const select = (state, props) => { const perform = dispatch => ({ fetchRewards: () => dispatch(doRewardList()), + doAuth: () => dispatch(doNavigate("/auth")), }); export default connect(select, perform)(RewardsPage); diff --git a/ui/js/page/rewards/view.jsx b/ui/js/page/rewards/view.jsx index 67c8b1a27..842f90685 100644 --- a/ui/js/page/rewards/view.jsx +++ b/ui/js/page/rewards/view.jsx @@ -1,7 +1,7 @@ import React from "react"; import { BusyMessage, CreditAmount, Icon } from "component/common"; import SubHeader from "component/subHeader"; -import Auth from "component/auth"; +import Link from "component/link"; import RewardLink from "component/rewardLink"; const RewardTile = props => { @@ -46,6 +46,7 @@ class RewardsPage extends React.PureComponent { render() { const { + doAuth, fetching, isEligible, isVerificationCandidate, @@ -60,19 +61,12 @@ class RewardsPage extends React.PureComponent { if (!hasEmail || isVerificationCandidate) { content = (
-
- {newUserReward && - } -

Welcome to LBRY

+
+

{__("Only verified accounts are eligible to earn rewards.")}

-

- {" "}{__( - "Claim your welcome credits to be able to publish content, pay creators, and have a say over the LBRY network." - )} -

+
-
); isCard = true; diff --git a/ui/js/reducers/user.js b/ui/js/reducers/user.js index 4e4ffda40..6c624c053 100644 --- a/ui/js/reducers/user.js +++ b/ui/js/reducers/user.js @@ -120,6 +120,28 @@ reducers[types.USER_EMAIL_VERIFY_FAILURE] = function(state, action) { }); }; +reducers[types.USER_IDENTITY_VERIFY_STARTED] = function(state, action) { + return Object.assign({}, state, { + identityVerifyIsPending: true, + identityVerifyErrorMessage: "", + }); +}; + +reducers[types.USER_IDENTITY_VERIFY_SUCCESS] = function(state, action) { + return Object.assign({}, state, { + identityVerifyIsPending: false, + identityVerifyErrorMessage: "", + user: action.data.user, + }); +}; + +reducers[types.USER_IDENTITY_VERIFY_FAILURE] = function(state, action) { + return Object.assign({}, state, { + identityVerifyIsPending: false, + identityVerifyErrorMessage: action.data.error, + }); +}; + export default function reducer(state = defaultState, action) { const handler = reducers[action.type]; if (handler) return handler(state, action); diff --git a/ui/js/selectors/user.js b/ui/js/selectors/user.js index a7ff1dc9f..a4f266ff0 100644 --- a/ui/js/selectors/user.js +++ b/ui/js/selectors/user.js @@ -19,6 +19,11 @@ export const selectEmailToVerify = createSelector( state => state.emailToVerify ); +export const selectUserEmail = createSelector( + selectUser, + user => (user && user.email ? user.email : "fake@lbry.io") +); + export const selectUserHasEmail = createSelector( selectUser, selectEmailToVerify, @@ -60,6 +65,16 @@ export const selectEmailVerifyErrorMessage = createSelector( state => state.emailVerifyErrorMessage ); +export const selectIdentityVerifyIsPending = createSelector( + _selectState, + state => state.identityVerifyIsPending +); + +export const selectIdentityVerifyErrorMessage = createSelector( + _selectState, + state => state.identityVerifyErrorMessage +); + export const selectUserIsVerificationCandidate = createSelector( selectUser, user => user && (!user.has_verified_email || !user.is_identity_verified) diff --git a/ui/scss/_global.scss b/ui/scss/_global.scss index 74dc364c1..d30ae6dc5 100644 --- a/ui/scss/_global.scss +++ b/ui/scss/_global.scss @@ -29,6 +29,7 @@ $max-content-width: 1000px; $max-text-width: 660px; $width-page-constrained: 800px; +$width-input-text: 330px; $height-header: $spacing-vertical * 2.5; $height-button: $spacing-vertical * 1.5; diff --git a/ui/scss/component/_card.scss b/ui/scss/component/_card.scss index da73b21bd..e5126d6e7 100644 --- a/ui/scss/component/_card.scss +++ b/ui/scss/component/_card.scss @@ -156,6 +156,10 @@ $height-card-small: $spacing-vertical * 15; height: $width-card-small * 9 / 16; } +.card--form { + width: $width-input-text + $padding-card-horizontal * 2; +} + .card__subtitle { color: $color-help; font-size: 0.85em; diff --git a/ui/scss/component/_form-field.scss b/ui/scss/component/_form-field.scss index 8f918cc7d..9dee7240b 100644 --- a/ui/scss/component/_form-field.scss +++ b/ui/scss/component/_form-field.scss @@ -1,7 +1,6 @@ @import "../global"; $width-input-border: 2px; -$width-input-text: 330px; .form-row-submit {