From b3de4ceee9d2100175758af1ad91e56de574f2d8 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Sat, 15 Jul 2017 14:44:50 -0400 Subject: [PATCH 01/11] beginnings of new flow --- ui/js/actions/app.js | 6 ++++ ui/js/actions/content.js | 4 +-- ui/js/component/app/view.jsx | 12 ++++---- ui/js/component/fileActions/view.jsx | 7 ----- .../modalInsufficientCredits/index.js | 16 ++++++++++ .../modalInsufficientCredits/view.jsx | 24 +++++++++++++++ ui/js/component/modalUpgrade/view.jsx | 1 - ui/js/component/modalWelcome/index.js | 21 ++++++++++---- ui/js/component/modalWelcome/view.jsx | 29 ++++++++++++++----- .../component/video/internal/play-button.jsx | 7 ----- ui/js/constants/modal_types.js | 6 +++- ui/js/main.js | 16 +--------- ui/js/selectors/app.js | 5 ++++ ui/package.json | 3 +- 14 files changed, 104 insertions(+), 53 deletions(-) create mode 100644 ui/js/component/modalInsufficientCredits/index.js create mode 100644 ui/js/component/modalInsufficientCredits/view.jsx diff --git a/ui/js/actions/app.js b/ui/js/actions/app.js index 35049d435..d812172b6 100644 --- a/ui/js/actions/app.js +++ b/ui/js/actions/app.js @@ -8,11 +8,13 @@ import { selectPageTitle, selectCurrentPage, selectCurrentParams, + selectWelcomeModalAcknowledged, } from "selectors/app"; import { doSearch } from "actions/search"; import { doFetchDaemonSettings } from "actions/settings"; import { doAuthenticate } from "actions/user"; import { doFileList } from "actions/file_info"; +import * as modals from "constants/modal_types"; const { remote, ipcRenderer, shell } = require("electron"); const path = require("path"); @@ -219,12 +221,16 @@ export function doAlertError(errorList) { export function doDaemonReady() { return function(dispatch, getState) { + const showWelcome = !selectWelcomeModalAcknowledged(getState()); dispatch(doAuthenticate()); dispatch({ type: types.DAEMON_READY, }); dispatch(doFetchDaemonSettings()); dispatch(doFileList()); + if (showWelcome) { + dispatch(doOpenModal(modals.WELCOME)); + } }; } diff --git a/ui/js/actions/content.js b/ui/js/actions/content.js index f46a1fa33..9d6f55408 100644 --- a/ui/js/actions/content.js +++ b/ui/js/actions/content.js @@ -15,8 +15,8 @@ import { selectBadgeNumber } from "selectors/app"; import { selectTotalDownloadProgress } from "selectors/file_info"; import setBadge from "util/setBadge"; import setProgressBar from "util/setProgressBar"; -import { doFileList } from "actions/file_info"; import batchActions from "util/batchActions"; +import * as modals from "constants/modal_types"; const { ipcRenderer } = require("electron"); @@ -293,7 +293,7 @@ export function doPurchaseUri(uri, purchaseModalName) { } if (cost > balance) { - dispatch(doOpenModal("notEnoughCredits")); + dispatch(doOpenModal(modals.INSUFFICIENT_CREDITS)); } else { dispatch(doOpenModal(purchaseModalName)); } diff --git a/ui/js/component/app/view.jsx b/ui/js/component/app/view.jsx index bc8264f21..8f4c54cb0 100644 --- a/ui/js/component/app/view.jsx +++ b/ui/js/component/app/view.jsx @@ -3,10 +3,11 @@ import Router from "component/router"; import Header from "component/header"; import ModalError from "component/modalError"; import ModalDownloading from "component/modalDownloading"; +import ModalInsufficientCredits from "component/modalInsufficientCredits"; import ModalUpgrade from "component/modalUpgrade"; import ModalWelcome from "component/modalWelcome"; import lbry from "lbry"; -import { Line } from "rc-progress"; +import * as modals from "constants/modal_types"; class App extends React.PureComponent { componentWillMount() { @@ -32,10 +33,11 @@ class App extends React.PureComponent {
- {modal == "upgrade" && } - {modal == "downloading" && } - {modal == "error" && } - {modal == "welcome" && } + {modal == modals.UPGRADE && } + {modal == modals.DOWNLOADING && } + {modal == modals.ERROR && } + {modal == modals.INSUFFICIENT_CREDITS && } + {modal == modals.WELCOME && } ); } diff --git a/ui/js/component/fileActions/view.jsx b/ui/js/component/fileActions/view.jsx index 53188a1f5..0d4dbbd6b 100644 --- a/ui/js/component/fileActions/view.jsx +++ b/ui/js/component/fileActions/view.jsx @@ -176,13 +176,6 @@ class FileActions extends React.PureComponent { {" "} {__("credits")}. - - {__("You don't have enough LBRY credits to pay for this stream.")} - ({}); + +const perform = dispatch => ({ + addFunds: () => { + dispatch(doNavigate("/rewards")); + dispatch(doCloseModal()); + }, + closeModal: () => dispatch(doCloseModal()), +}); + +export default connect(select, perform)(ModalInsufficientCredits); diff --git a/ui/js/component/modalInsufficientCredits/view.jsx b/ui/js/component/modalInsufficientCredits/view.jsx new file mode 100644 index 000000000..fd214cd11 --- /dev/null +++ b/ui/js/component/modalInsufficientCredits/view.jsx @@ -0,0 +1,24 @@ +import React from "react"; +import { Modal } from "component/modal"; + +class ModalInsufficientCredits extends React.PureComponent { + render() { + const { addFunds, closeModal } = this.props; + + return ( + + {__("More LBRY credits are required to purchase this.")} + + ); + } +} + +export default ModalInsufficientCredits; diff --git a/ui/js/component/modalUpgrade/view.jsx b/ui/js/component/modalUpgrade/view.jsx index 544fd96b7..2d364bd31 100644 --- a/ui/js/component/modalUpgrade/view.jsx +++ b/ui/js/component/modalUpgrade/view.jsx @@ -1,6 +1,5 @@ import React from "react"; import { Modal } from "component/modal"; -import { downloadUpgrade, skipUpgrade } from "actions/app"; class ModalUpgrade extends React.PureComponent { render() { diff --git a/ui/js/component/modalWelcome/index.js b/ui/js/component/modalWelcome/index.js index 12ec5c0be..5ad4b0bc9 100644 --- a/ui/js/component/modalWelcome/index.js +++ b/ui/js/component/modalWelcome/index.js @@ -1,11 +1,11 @@ import React from "react"; import rewards from "rewards"; import { connect } from "react-redux"; -import { doCloseModal } from "actions/app"; +import { doCloseModal, doNavigate } from "actions/app"; +import { doSetClientSetting } from "actions/settings"; import { selectUserIsRewardApproved } from "selectors/user"; import { makeSelectHasClaimedReward, - makeSelectClaimRewardError, makeSelectRewardByType, } from "selectors/rewards"; import ModalWelcome from "./view"; @@ -21,8 +21,19 @@ const select = (state, props) => { }; }; -const perform = dispatch => ({ - closeModal: () => dispatch(doCloseModal()), -}); +const perform = dispatch => () => { + const closeModal = () => { + dispatch(doSetClientSetting("welcome_acknowledged", true)); + dispatch(doCloseModal()); + }; + + return { + verifyAccount: () => { + closeModal(); + dispatch(doNavigate("/rewards")); + }, + closeModal: closeModal, + }; +}; export default connect(select, perform)(ModalWelcome); diff --git a/ui/js/component/modalWelcome/view.jsx b/ui/js/component/modalWelcome/view.jsx index 82448c3ae..cfd3d2c67 100644 --- a/ui/js/component/modalWelcome/view.jsx +++ b/ui/js/component/modalWelcome/view.jsx @@ -6,7 +6,13 @@ import RewardLink from "component/rewardLink"; class ModalWelcome extends React.PureComponent { render() { - const { closeModal, hasClaimed, isRewardApproved, reward } = this.props; + const { + closeModal, + hasClaimed, + isRewardApproved, + reward, + verifyAccount, + } = this.props; return !hasClaimed ? @@ -29,13 +35,20 @@ class ModalWelcome extends React.PureComponent { {" "}{isRewardApproved ? __("Here's a nickel, kid.") : ""}

- {isRewardApproved - ? - : } + {isRewardApproved && + } + {!isRewardApproved && + } + {!isRewardApproved && + }
diff --git a/ui/js/component/video/internal/play-button.jsx b/ui/js/component/video/internal/play-button.jsx index d104fcdeb..f5223231c 100644 --- a/ui/js/component/video/internal/play-button.jsx +++ b/ui/js/component/video/internal/play-button.jsx @@ -78,13 +78,6 @@ class VideoPlayButton extends React.PureComponent { icon={icon} onClick={this.onWatchClick.bind(this)} /> - - {__("You don't have enough LBRY credits to pay for this stream.")} - { const initialState = app.store.getState(); -// import whyDidYouUpdate from "why-did-you-update"; -// if (env === "development") { -// /* -// https://github.com/garbles/why-did-you-update -// "A function that monkey patches React and notifies you in the console when -// potentially unnecessary re-renders occur." -// -// Just checks if props change between updates. Can be fixed by manually -// adding a check in shouldComponentUpdate or using React.PureComponent -// */ -// whyDidYouUpdate(React); -// } - var init = function() { function onDaemonReady() { window.sessionStorage.setItem("loaded", "y"); //once we've made it here once per session, we don't need to show splash again @@ -117,7 +103,7 @@ var init = function() { ReactDOM.render( -
+
, canvas ); diff --git a/ui/js/selectors/app.js b/ui/js/selectors/app.js index f6acd6d07..209b428c1 100644 --- a/ui/js/selectors/app.js +++ b/ui/js/selectors/app.js @@ -187,6 +187,11 @@ export const selectSnackBarSnacks = createSelector( snackBar => snackBar.snacks || [] ); +export const selectWelcomeModalAcknowledged = createSelector( + _selectState, + state => lbry.getClientSetting("welcome_acknowledged") +); + export const selectBadgeNumber = createSelector( _selectState, state => state.badgeNumber diff --git a/ui/package.json b/ui/package.json index 4c4986ec2..7e7491ed9 100644 --- a/ui/package.json +++ b/ui/package.json @@ -72,8 +72,7 @@ "webpack": "^2.6.1", "webpack-dev-server": "^2.4.4", "webpack-notifier": "^1.5.0", - "webpack-target-electron-renderer": "^0.4.0", - "why-did-you-update": "0.0.8" + "webpack-target-electron-renderer": "^0.4.0" }, "lint-staged": { "gitDir": "../", -- 2.45.3 From 16abedbf3a27c039ea6b5642a4d10e05aea2ec60 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Sun, 16 Jul 2017 12:29:46 -0400 Subject: [PATCH 02/11] moderate progress --- ui/js/actions/app.js | 8 +-- ui/js/component/app/index.js | 32 +++++++-- ui/js/component/app/view.jsx | 32 ++++++++- ui/js/component/auth/index.js | 2 + ui/js/component/auth/view.jsx | 9 ++- ui/js/component/authOverlay/view.jsx | 2 +- ui/js/component/common.js | 5 +- ui/js/component/header/index.js | 4 +- ui/js/component/modalWelcome/view.jsx | 17 +++-- .../userEmailNew/{index.jsx => index.js} | 0 ui/js/component/userEmailNew/view.jsx | 1 - .../userEmailVerify/{index.jsx => index.js} | 0 ui/js/component/userEmailVerify/view.jsx | 1 - ui/js/component/userVerify/index.js | 21 ++++++ ui/js/component/userVerify/view.jsx | 69 +++++++++++++++++++ ui/js/lbry.js | 5 -- ui/js/page/rewards/index.js | 27 +++++--- ui/js/page/rewards/view.jsx | 56 +++++++++------ ui/js/selectors/user.js | 7 +- ui/js/utils.js | 4 ++ ui/scss/component/_form-field.scss | 4 -- 21 files changed, 228 insertions(+), 78 deletions(-) rename ui/js/component/userEmailNew/{index.jsx => index.js} (100%) rename ui/js/component/userEmailVerify/{index.jsx => index.js} (100%) create mode 100644 ui/js/component/userVerify/index.js create mode 100644 ui/js/component/userVerify/view.jsx diff --git a/ui/js/actions/app.js b/ui/js/actions/app.js index d812172b6..309e21b01 100644 --- a/ui/js/actions/app.js +++ b/ui/js/actions/app.js @@ -8,13 +8,11 @@ import { selectPageTitle, selectCurrentPage, selectCurrentParams, - selectWelcomeModalAcknowledged, } from "selectors/app"; import { doSearch } from "actions/search"; import { doFetchDaemonSettings } from "actions/settings"; import { doAuthenticate } from "actions/user"; import { doFileList } from "actions/file_info"; -import * as modals from "constants/modal_types"; const { remote, ipcRenderer, shell } = require("electron"); const path = require("path"); @@ -220,17 +218,13 @@ export function doAlertError(errorList) { } export function doDaemonReady() { - return function(dispatch, getState) { - const showWelcome = !selectWelcomeModalAcknowledged(getState()); + return function(dispatch) { dispatch(doAuthenticate()); dispatch({ type: types.DAEMON_READY, }); dispatch(doFetchDaemonSettings()); dispatch(doFileList()); - if (showWelcome) { - dispatch(doOpenModal(modals.WELCOME)); - } }; } diff --git a/ui/js/component/app/index.js b/ui/js/component/app/index.js index 4c966cb56..0d4e1c093 100644 --- a/ui/js/component/app/index.js +++ b/ui/js/component/app/index.js @@ -1,18 +1,40 @@ import React from "react"; import { connect } from "react-redux"; - import { selectCurrentModal } from "selectors/app"; -import { doCheckUpgradeAvailable, doAlertError } from "actions/app"; +import { + doCheckUpgradeAvailable, + doOpenModal, + doAlertError, +} from "actions/app"; import { doUpdateBalance } from "actions/wallet"; +import { selectWelcomeModalAcknowledged } from "selectors/app"; +import rewards from "rewards"; +import { + selectFetchingRewards, + makeSelectHasClaimedReward, +} from "selectors/rewards"; +import { selectUser } from "selectors/user"; import App from "./view"; +import * as modals from "constants/modal_types"; -const select = state => ({ - modal: selectCurrentModal(state), -}); +const select = (state, props) => { + const selectHasClaimed = makeSelectHasClaimedReward(); + + return { + modal: selectCurrentModal(state), + isWelcomeAcknowledged: selectWelcomeModalAcknowledged(state), + isFetchingRewards: selectFetchingRewards(state), + isWelcomeRewardClaimed: selectHasClaimed(state, { + reward_type: rewards.TYPE_NEW_USER, + }), + user: selectUser(state), + }; +}; const perform = dispatch => ({ alertError: errorList => dispatch(doAlertError(errorList)), checkUpgradeAvailable: () => dispatch(doCheckUpgradeAvailable()), + openWelcomeModal: () => dispatch(doOpenModal(modals.WELCOME)), updateBalance: balance => dispatch(doUpdateBalance(balance)), }); diff --git a/ui/js/component/app/view.jsx b/ui/js/component/app/view.jsx index 8f4c54cb0..7c6b17eca 100644 --- a/ui/js/component/app/view.jsx +++ b/ui/js/component/app/view.jsx @@ -11,17 +11,43 @@ import * as modals from "constants/modal_types"; class App extends React.PureComponent { componentWillMount() { + const { alertError, checkUpgradeAvailable, updateBalance } = this.props; + document.addEventListener("unhandledError", event => { - this.props.alertError(event.detail); + alertError(event.detail); }); if (!this.props.upgradeSkipped) { - this.props.checkUpgradeAvailable(); + checkUpgradeAvailable(); } lbry.balanceSubscribe(balance => { - this.props.updateBalance(balance); + updateBalance(balance); }); + + this.showWelcome(this.props); + } + + componentWillReceiveProps(nextProps) { + this.showWelcome(nextProps); + } + + showWelcome(props) { + const { + isFetchingRewards, + isWelcomeAcknowledged, + isWelcomeRewardClaimed, + openWelcomeModal, + user, + } = props; + + if ( + !isWelcomeAcknowledged && + user && + (!isFetchingRewards || !isWelcomeRewardClaimed) + ) { + openWelcomeModal(); + } } render() { diff --git a/ui/js/component/auth/index.js b/ui/js/component/auth/index.js index 37af9f90f..f448d6e82 100644 --- a/ui/js/component/auth/index.js +++ b/ui/js/component/auth/index.js @@ -4,12 +4,14 @@ import { selectAuthenticationIsPending, selectEmailToVerify, selectUserIsVerificationCandidate, + selectUser, } from "selectors/user"; import Auth from "./view"; const select = state => ({ isPending: selectAuthenticationIsPending(state), email: selectEmailToVerify(state), + user: selectUser(state), isVerificationCandidate: selectUserIsVerificationCandidate(state), }); diff --git a/ui/js/component/auth/view.jsx b/ui/js/component/auth/view.jsx index 551113ffa..1c49c513f 100644 --- a/ui/js/component/auth/view.jsx +++ b/ui/js/component/auth/view.jsx @@ -2,17 +2,20 @@ 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 { isPending, email, isVerificationCandidate } = this.props; + const { email, isPending, isVerificationCandidate, user } = this.props; if (isPending) { return ; - } else if (!email) { + } else if (user && !user.has_verified_email && !email) { return ; - } else if (isVerificationCandidate) { + } else if (user && !user.has_verified_email) { return ; + } else if (user && !user.is_identity_verified) { + return ; } else { return {__("No further steps.")}; } diff --git a/ui/js/component/authOverlay/view.jsx b/ui/js/component/authOverlay/view.jsx index 753fe2fe4..fe8b9151d 100644 --- a/ui/js/component/authOverlay/view.jsx +++ b/ui/js/component/authOverlay/view.jsx @@ -54,7 +54,7 @@ export class AuthOverlay extends React.PureComponent { ? "" :
{!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." diff --git a/ui/js/component/common.js b/ui/js/component/common.js index 38dbf83fd..8e7279248 100644 --- a/ui/js/component/common.js +++ b/ui/js/component/common.js @@ -1,4 +1,5 @@ import React from "react"; +import { formatCredits } from "utils"; import lbry from "../lbry.js"; //component/icon.js @@ -78,7 +79,7 @@ export class CreditAmount extends React.PureComponent { }; render() { - const formattedAmount = lbry.formatCredits( + const formattedAmount = formatCredits( this.props.amount, this.props.precision ); @@ -140,7 +141,7 @@ export class Address extends React.PureComponent { }} style={addressStyle} readOnly="readonly" - value={this.props.address} + value={this.props.address || ""} /> ); } diff --git a/ui/js/component/header/index.js b/ui/js/component/header/index.js index d05e7800b..eda8923d3 100644 --- a/ui/js/component/header/index.js +++ b/ui/js/component/header/index.js @@ -1,12 +1,12 @@ import React from "react"; -import lbry from "lbry"; +import { formatCredits } from "utils"; import { connect } from "react-redux"; import { selectBalance } from "selectors/wallet"; import { doNavigate, doHistoryBack } from "actions/app"; import Header from "./view"; const select = state => ({ - balance: lbry.formatCredits(selectBalance(state), 1), + balance: formatCredits(selectBalance(state), 1), publish: __("Publish"), }); diff --git a/ui/js/component/modalWelcome/view.jsx b/ui/js/component/modalWelcome/view.jsx index cfd3d2c67..3dec74cfa 100644 --- a/ui/js/component/modalWelcome/view.jsx +++ b/ui/js/component/modalWelcome/view.jsx @@ -31,8 +31,11 @@ class ModalWelcome extends React.PureComponent { )}

- {__("Thank you for making content freedom possible!")} - {" "}{isRewardApproved ? __("Here's a nickel, kid.") : ""} + {__("Please have")} {" "} + {reward && + } + {!reward && {__("??")}} + {" "} {__("as a thank you for building content freedom.")}

{isRewardApproved && @@ -40,15 +43,11 @@ class ModalWelcome extends React.PureComponent { {!isRewardApproved && } {!isRewardApproved && - } + }
diff --git a/ui/js/component/userEmailNew/index.jsx b/ui/js/component/userEmailNew/index.js similarity index 100% rename from ui/js/component/userEmailNew/index.jsx rename to ui/js/component/userEmailNew/index.js diff --git a/ui/js/component/userEmailNew/view.jsx b/ui/js/component/userEmailNew/view.jsx index 5391bdb3f..cc553f63e 100644 --- a/ui/js/component/userEmailNew/view.jsx +++ b/ui/js/component/userEmailNew/view.jsx @@ -27,7 +27,6 @@ class UserEmailNew extends React.PureComponent { return (
{ this.handleSubmit(event); }} diff --git a/ui/js/component/userEmailVerify/index.jsx b/ui/js/component/userEmailVerify/index.js similarity index 100% rename from ui/js/component/userEmailVerify/index.jsx rename to ui/js/component/userEmailVerify/index.js diff --git a/ui/js/component/userEmailVerify/view.jsx b/ui/js/component/userEmailVerify/view.jsx index 5aa656a61..de9cda5c3 100644 --- a/ui/js/component/userEmailVerify/view.jsx +++ b/ui/js/component/userEmailVerify/view.jsx @@ -27,7 +27,6 @@ class UserEmailVerify extends React.PureComponent { return ( { this.handleSubmit(event); }} diff --git a/ui/js/component/userVerify/index.js b/ui/js/component/userVerify/index.js new file mode 100644 index 000000000..b24dbdb8f --- /dev/null +++ b/ui/js/component/userVerify/index.js @@ -0,0 +1,21 @@ +import React from "react"; +import { connect } from "react-redux"; +import { doUserEmailVerify } from "actions/user"; +import { + selectEmailVerifyIsPending, + selectEmailToVerify, + selectEmailVerifyErrorMessage, +} from "selectors/user"; +import UserVerify from "./view"; + +const select = state => ({ + isPending: selectEmailVerifyIsPending(state), + email: selectEmailToVerify(state), + errorMessage: selectEmailVerifyErrorMessage(state), +}); + +const perform = dispatch => ({ + verifyUserEmail: code => dispatch(doUserEmailVerify(code)), +}); + +export default connect(select, perform)(UserVerify); diff --git a/ui/js/component/userVerify/view.jsx b/ui/js/component/userVerify/view.jsx new file mode 100644 index 000000000..4ff9e1192 --- /dev/null +++ b/ui/js/component/userVerify/view.jsx @@ -0,0 +1,69 @@ +import React from "react"; +import Link from "component/link"; +import { FormRow } from "component/form.js"; + +class UserVerify extends React.PureComponent { + constructor(props) { + super(props); + + this.state = { + code: "", + }; + } + + handleCodeChanged(event) { + this.setState({ + code: event.target.value, + }); + } + + handleSubmit(event) { + event.preventDefault(); + this.props.verifyUserEmail(this.state.code); + } + + render() { + const { errorMessage, isPending } = this.props; + return

VERIFY

; + return ( + { + this.handleSubmit(event); + }} + > + zzzzzzzzzzzzzzzzzzzzzzzzzzzzz + { + this.handleCodeChanged(event); + }} + 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); + }} + /> +
+ + ); + } +} + +export default UserVerify; diff --git a/ui/js/lbry.js b/ui/js/lbry.js index 7409bdde4..6b4730762 100644 --- a/ui/js/lbry.js +++ b/ui/js/lbry.js @@ -288,11 +288,6 @@ lbry.setClientSetting = function(setting, value) { return localStorage.setItem("setting_" + setting, JSON.stringify(value)); }; -//utilities -lbry.formatCredits = function(amount, precision) { - return amount.toFixed(precision || 1).replace(/\.?0+$/, ""); -}; - lbry.formatName = function(name) { // Converts LBRY name to standard format (all lower case, no special characters, spaces replaced by dashes) name = name.replace("/s+/g", "-"); diff --git a/ui/js/page/rewards/index.js b/ui/js/page/rewards/index.js index 8e7abb1fc..14029ff26 100644 --- a/ui/js/page/rewards/index.js +++ b/ui/js/page/rewards/index.js @@ -1,22 +1,31 @@ import React from "react"; import { connect } from "react-redux"; -import { doNavigate } from "actions/app"; -import { selectFetchingRewards, selectRewards } from "selectors/rewards"; +import { + makeSelectRewardByType, + selectFetchingRewards, + selectRewards, +} from "selectors/rewards"; import { selectUserIsRewardEligible, selectUserHasEmail, selectUserIsVerificationCandidate, } from "selectors/user"; import { doRewardList } from "actions/rewards"; +import rewards from "rewards"; import RewardsPage from "./view"; -const select = state => ({ - fetching: selectFetchingRewards(state), - rewards: selectRewards(state), - hasEmail: selectUserHasEmail(state), - isEligible: selectUserIsRewardEligible(state), - isVerificationCandidate: selectUserIsVerificationCandidate(state), -}); +const select = (state, props) => { + const selectReward = makeSelectRewardByType(); + + return { + fetching: selectFetchingRewards(state), + rewards: selectRewards(state), + hasEmail: selectUserHasEmail(state), + isEligible: selectUserIsRewardEligible(state), + isVerificationCandidate: selectUserIsVerificationCandidate(state), + newUserReward: selectReward(state, { reward_type: rewards.TYPE_NEW_USER }), + }; +}; const perform = dispatch => ({ fetchRewards: () => dispatch(doRewardList()), diff --git a/ui/js/page/rewards/view.jsx b/ui/js/page/rewards/view.jsx index 237994667..67c8b1a27 100644 --- a/ui/js/page/rewards/view.jsx +++ b/ui/js/page/rewards/view.jsx @@ -1,9 +1,7 @@ import React from "react"; -import lbryio from "lbryio"; 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 => { @@ -41,7 +39,9 @@ class RewardsPage extends React.PureComponent { fetchRewards(props) { const { fetching, rewards, fetchRewards } = props; - if (!fetching && Object.keys(rewards).length < 1) fetchRewards(); + if (!fetching && (!rewards || !rewards.length)) { + fetchRewards(); + } } render() { @@ -51,6 +51,7 @@ class RewardsPage extends React.PureComponent { isVerificationCandidate, hasEmail, rewards, + newUserReward, } = this.props; let content, @@ -59,42 +60,55 @@ class RewardsPage extends React.PureComponent { if (!hasEmail || isVerificationCandidate) { content = (
-

- {__( - "Additional information is required to be eligible for the rewards program." - )} -

- +
+ {newUserReward && + } +

Welcome to LBRY

+
+
+

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

+
+
); isCard = true; } else if (!isEligible) { isCard = true; content = ( -
+

{__("You are not eligible to claim rewards.")}

); } else if (fetching) { - content = ; + content = ( +
+ +
+ ); } else if (rewards.length > 0) { - content = rewards.map(reward => - + content = ( +
+ {rewards.map(reward => + + )} +
); } else { - content =
{__("Failed to load rewards.")}
; + content = ( +
+ {__("Failed to load rewards.")} +
+ ); } return (
- {isCard - ?
-
- {content} -
-
- : content} + {isCard ?
{content}
: content}
); } diff --git a/ui/js/selectors/user.js b/ui/js/selectors/user.js index d17485aa6..a7ff1dc9f 100644 --- a/ui/js/selectors/user.js +++ b/ui/js/selectors/user.js @@ -12,10 +12,7 @@ export const selectUserIsPending = createSelector( state => state.userIsPending ); -export const selectUser = createSelector( - _selectState, - state => state.user || {} -); +export const selectUser = createSelector(_selectState, state => state.user); export const selectEmailToVerify = createSelector( _selectState, @@ -65,7 +62,7 @@ export const selectEmailVerifyErrorMessage = createSelector( export const selectUserIsVerificationCandidate = createSelector( selectUser, - user => user && !user.has_verified_email + user => user && (!user.has_verified_email || !user.is_identity_verified) ); export const selectUserIsAuthRequested = createSelector( diff --git a/ui/js/utils.js b/ui/js/utils.js index 783f85113..b41a2e0d4 100644 --- a/ui/js/utils.js +++ b/ui/js/utils.js @@ -29,3 +29,7 @@ export function getSession(key, fallback = undefined) { export function setSession(key, value) { sessionStorage.setItem(key, JSON.stringify(value)); } + +export function formatCredits(amount, precision) { + return amount.toFixed(precision || 1).replace(/\.?0+$/, ""); +} diff --git a/ui/scss/component/_form-field.scss b/ui/scss/component/_form-field.scss index f701ebe06..8f918cc7d 100644 --- a/ui/scss/component/_form-field.scss +++ b/ui/scss/component/_form-field.scss @@ -3,10 +3,6 @@ $width-input-border: 2px; $width-input-text: 330px; -.form-input-width { - width: $width-input-text -} - .form-row-submit { margin-top: $spacing-vertical; -- 2.45.3 From ab9150fb27cdaffa44650834519445432a0e1c3c Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Tue, 18 Jul 2017 19:00:13 -0400 Subject: [PATCH 03/11] 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 { -- 2.45.3 From a63222c7dd4a45ba4eec10168042790d363c4288 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Tue, 18 Jul 2017 19:00:55 -0400 Subject: [PATCH 04/11] remove test code --- ui/js/actions/user.js | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/ui/js/actions/user.js b/ui/js/actions/user.js index 0ca9bc8ee..799bc8cbc 100644 --- a/ui/js/actions/user.js +++ b/ui/js/actions/user.js @@ -159,22 +159,22 @@ export function doUserIdentityVerify(stripeToken) { } }) .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() }, - // }); + // 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() }, + }); }); }; } -- 2.45.3 From d7fdaa08e2b1974a1d1afd4b4c8e183bb076683b Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Wed, 19 Jul 2017 11:09:40 -0400 Subject: [PATCH 05/11] more progress --- ui/js/actions/app.js | 14 ++++++++++++++ ui/js/actions/user.js | 32 ++++++++++++++++---------------- ui/js/component/auth/index.js | 18 ------------------ ui/js/component/auth/view.jsx | 0 ui/js/constants/action_types.js | 2 +- ui/js/page/auth/index.js | 4 +++- ui/js/page/auth/view.jsx | 20 +------------------- ui/js/page/rewards/index.js | 6 ++++-- ui/js/reducers/app.js | 7 +++++++ ui/js/selectors/app.js | 5 +++++ 10 files changed, 51 insertions(+), 57 deletions(-) delete mode 100644 ui/js/component/auth/index.js delete mode 100644 ui/js/component/auth/view.jsx diff --git a/ui/js/actions/app.js b/ui/js/actions/app.js index 309e21b01..f50ea9467 100644 --- a/ui/js/actions/app.js +++ b/ui/js/actions/app.js @@ -37,6 +37,20 @@ export function doNavigate(path, params = {}) { }; } +export function doAuthNavigate(pathAfterAuth = null, params = {}) { + return function(dispatch, getState) { + if (pathAfterAuth) { + dispatch({ + type: types.CHANGE_AFTER_AUTH_PATH, + data: { + path: `${pathAfterAuth}?${queryStringFromParams(params)}`, + }, + }); + } + dispatch(doNavigate("/auth")); + }; +} + export function doChangePath(path) { return function(dispatch, getState) { dispatch({ diff --git a/ui/js/actions/user.js b/ui/js/actions/user.js index 799bc8cbc..0ca9bc8ee 100644 --- a/ui/js/actions/user.js +++ b/ui/js/actions/user.js @@ -159,22 +159,22 @@ export function doUserIdentityVerify(stripeToken) { } }) .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() }, - }); + 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/index.js b/ui/js/component/auth/index.js deleted file mode 100644 index f448d6e82..000000000 --- a/ui/js/component/auth/index.js +++ /dev/null @@ -1,18 +0,0 @@ -import React from "react"; -import { connect } from "react-redux"; -import { - selectAuthenticationIsPending, - selectEmailToVerify, - selectUserIsVerificationCandidate, - selectUser, -} from "selectors/user"; -import Auth from "./view"; - -const select = state => ({ - isPending: selectAuthenticationIsPending(state), - email: selectEmailToVerify(state), - user: selectUser(state), - isVerificationCandidate: selectUserIsVerificationCandidate(state), -}); - -export default connect(select, null)(Auth); diff --git a/ui/js/component/auth/view.jsx b/ui/js/component/auth/view.jsx deleted file mode 100644 index e69de29bb..000000000 diff --git a/ui/js/constants/action_types.js b/ui/js/constants/action_types.js index 0a20d0758..24e9a5017 100644 --- a/ui/js/constants/action_types.js +++ b/ui/js/constants/action_types.js @@ -5,7 +5,7 @@ export const HISTORY_BACK = "HISTORY_BACK"; export const SHOW_SNACKBAR = "SHOW_SNACKBAR"; export const REMOVE_SNACKBAR_SNACK = "REMOVE_SNACKBAR_SNACK"; export const WINDOW_FOCUSED = "WINDOW_FOCUSED"; - +export const CHANGE_AFTER_AUTH_PATH = "CHANGE_AFTER_AUTH_PATH"; export const DAEMON_READY = "DAEMON_READY"; // Upgrades diff --git a/ui/js/page/auth/index.js b/ui/js/page/auth/index.js index 430955b1c..063c5aad6 100644 --- a/ui/js/page/auth/index.js +++ b/ui/js/page/auth/index.js @@ -1,6 +1,7 @@ import React from "react"; import { doNavigate } from "actions/app"; import { connect } from "react-redux"; +import { selectPathAfterAuth } from "selectors/app"; import { selectAuthenticationIsPending, selectUserHasEmail, @@ -19,12 +20,13 @@ const select = state => ({ selectIdentityVerifyIsPending(state), email: selectEmailToVerify(state), hasEmail: selectUserHasEmail(state), + pathAfterAuth: selectPathAfterAuth(state), user: selectUser(state), isVerificationCandidate: selectUserIsVerificationCandidate(state), }); const perform = dispatch => ({ - onAuthComplete: () => dispatch(doNavigate("/discover")), + navigate: path => dispatch(doNavigate(path)), }); export default connect(select, perform)(AuthPage); diff --git a/ui/js/page/auth/view.jsx b/ui/js/page/auth/view.jsx index f55bebdb5..5e87dfbf6 100644 --- a/ui/js/page/auth/view.jsx +++ b/ui/js/page/auth/view.jsx @@ -5,41 +5,23 @@ 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(); + props.navigate(props.pathAfterAuth); } } diff --git a/ui/js/page/rewards/index.js b/ui/js/page/rewards/index.js index 3dd8bdc68..52e18250a 100644 --- a/ui/js/page/rewards/index.js +++ b/ui/js/page/rewards/index.js @@ -10,7 +10,7 @@ import { selectUserHasEmail, selectUserIsVerificationCandidate, } from "selectors/user"; -import { doNavigate } from "actions/app"; +import { doAuthNavigate } from "actions/app"; import { doRewardList } from "actions/rewards"; import rewards from "rewards"; import RewardsPage from "./view"; @@ -30,7 +30,9 @@ const select = (state, props) => { const perform = dispatch => ({ fetchRewards: () => dispatch(doRewardList()), - doAuth: () => dispatch(doNavigate("/auth")), + doAuth: () => { + dispatch(doAuthNavigate("/rewards")); + }, }); export default connect(select, perform)(RewardsPage); diff --git a/ui/js/reducers/app.js b/ui/js/reducers/app.js index fe8c9adae..bc24ac85e 100644 --- a/ui/js/reducers/app.js +++ b/ui/js/reducers/app.js @@ -15,6 +15,7 @@ const reducers = {}; const defaultState = { isLoaded: false, currentPath: currentPath(), + pathAfterAuth: "/discover", platform: process.platform, upgradeSkipped: sessionStorage.getItem("upgradeSkipped"), daemonReady: false, @@ -34,6 +35,12 @@ reducers[types.CHANGE_PATH] = function(state, action) { }); }; +reducers[types.CHANGE_AFTER_AUTH_PATH] = function(state, action) { + return Object.assign({}, state, { + pathAfterAuth: action.data.path, + }); +}; + reducers[types.UPGRADE_CANCELLED] = function(state, action) { return Object.assign({}, state, { downloadProgress: null, diff --git a/ui/js/selectors/app.js b/ui/js/selectors/app.js index 209b428c1..b70fbbdce 100644 --- a/ui/js/selectors/app.js +++ b/ui/js/selectors/app.js @@ -196,3 +196,8 @@ export const selectBadgeNumber = createSelector( _selectState, state => state.badgeNumber ); + +export const selectPathAfterAuth = createSelector( + _selectState, + state => state.pathAfterAuth +); -- 2.45.3 From ed2cbc05550596bf8a6f2278dea0c844cd5931a8 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Thu, 20 Jul 2017 15:03:01 -0400 Subject: [PATCH 06/11] minor cleanup, update api field name --- ui/js/lbryio.js | 2 +- ui/js/page/auth/index.js | 2 -- ui/js/page/auth/view.jsx | 4 ++-- ui/js/page/help/index.js | 7 ++++++- ui/js/page/help/view.jsx | 18 +++++++++++++----- ui/js/page/rewards/index.js | 6 +----- ui/js/page/rewards/view.jsx | 2 +- ui/js/reducers/user.js | 4 ++-- ui/js/selectors/user.js | 17 +---------------- 9 files changed, 27 insertions(+), 35 deletions(-) diff --git a/ui/js/lbryio.js b/ui/js/lbryio.js index 475e9e7e2..8d785ee2a 100644 --- a/ui/js/lbryio.js +++ b/ui/js/lbryio.js @@ -136,7 +136,7 @@ lbryio.authenticate = function() { resolve({ id: 1, language: "en", - has_email: true, + primary_email: "disabled@lbry.io", has_verified_email: true, is_identity_verified: true, is_reward_approved: false, diff --git a/ui/js/page/auth/index.js b/ui/js/page/auth/index.js index 063c5aad6..541504dbb 100644 --- a/ui/js/page/auth/index.js +++ b/ui/js/page/auth/index.js @@ -4,7 +4,6 @@ import { connect } from "react-redux"; import { selectPathAfterAuth } from "selectors/app"; import { selectAuthenticationIsPending, - selectUserHasEmail, selectEmailToVerify, selectUserIsVerificationCandidate, selectUser, @@ -19,7 +18,6 @@ const select = state => ({ selectUserIsPending(state) || selectIdentityVerifyIsPending(state), email: selectEmailToVerify(state), - hasEmail: selectUserHasEmail(state), pathAfterAuth: selectPathAfterAuth(state), user: selectUser(state), isVerificationCandidate: selectUserIsVerificationCandidate(state), diff --git a/ui/js/page/auth/view.jsx b/ui/js/page/auth/view.jsx index 5e87dfbf6..b43c7298c 100644 --- a/ui/js/page/auth/view.jsx +++ b/ui/js/page/auth/view.jsx @@ -56,7 +56,7 @@ export class AuthPage extends React.PureComponent { } render() { - const { email, hasEmail, isPending } = this.props; + const { email, user, isPending } = this.props; return (
@@ -67,7 +67,7 @@ export class AuthPage extends React.PureComponent {
{!isPending && !email && - !hasEmail && + !user.has_verified_email &&

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

} diff --git a/ui/js/page/help/index.js b/ui/js/page/help/index.js index 31bb1ed51..c3d4086a3 100644 --- a/ui/js/page/help/index.js +++ b/ui/js/page/help/index.js @@ -2,9 +2,14 @@ import React from "react"; import { doNavigate } from "actions/app"; import { connect } from "react-redux"; import HelpPage from "./view"; +import { selectUser } from "selectors/user"; + +const select = state => ({ + user: selectUser(state), +}); const perform = dispatch => ({ navigate: (path, params) => dispatch(doNavigate(path, params)), }); -export default connect(null, perform)(HelpPage); +export default connect(select, perform)(HelpPage); diff --git a/ui/js/page/help/view.jsx b/ui/js/page/help/view.jsx index b8a59a619..0d2d42378 100644 --- a/ui/js/page/help/view.jsx +++ b/ui/js/page/help/view.jsx @@ -41,7 +41,7 @@ class HelpPage extends React.PureComponent { render() { let ver, osName, platform, newVerLink; - const { navigate } = this.props; + const { navigate, user } = this.props; if (this.state.versionInfo) { ver = this.state.versionInfo; @@ -136,16 +136,24 @@ class HelpPage extends React.PureComponent { ? - + + + + + - + - - + + diff --git a/ui/js/page/rewards/index.js b/ui/js/page/rewards/index.js index ce24ce6e7..8a7a29d98 100644 --- a/ui/js/page/rewards/index.js +++ b/ui/js/page/rewards/index.js @@ -5,11 +5,7 @@ import { selectFetchingRewards, selectRewards, } from "selectors/rewards"; -import { - selectUser, - selectUserHasEmail, - selectUserIsVerificationCandidate, -} from "selectors/user"; +import { selectUser } from "selectors/user"; import { doAuthNavigate, doNavigate } from "actions/app"; import { doRewardList } from "actions/rewards"; import rewards from "rewards"; diff --git a/ui/js/page/rewards/view.jsx b/ui/js/page/rewards/view.jsx index e590d5895..abb7a3879 100644 --- a/ui/js/page/rewards/view.jsx +++ b/ui/js/page/rewards/view.jsx @@ -73,7 +73,7 @@ class RewardsPage extends React.PureComponent { if ( user && - (!user.has_email || + (!user.primary_email || !user.has_verified_email || !user.is_identity_verified) ) { diff --git a/ui/js/reducers/user.js b/ui/js/reducers/user.js index 6c624c053..4f78d3dc2 100644 --- a/ui/js/reducers/user.js +++ b/ui/js/reducers/user.js @@ -73,7 +73,7 @@ reducers[types.USER_EMAIL_NEW_STARTED] = function(state, action) { reducers[types.USER_EMAIL_NEW_SUCCESS] = function(state, action) { let user = Object.assign({}, state.user); - user.has_email = true; + user.primary_email = action.data.email; return Object.assign({}, state, { emailToVerify: action.data.email, emailNewIsPending: false, @@ -105,7 +105,7 @@ reducers[types.USER_EMAIL_VERIFY_STARTED] = function(state, action) { reducers[types.USER_EMAIL_VERIFY_SUCCESS] = function(state, action) { let user = Object.assign({}, state.user); - user.has_email = true; + user.primary_email = action.data.email; return Object.assign({}, state, { emailToVerify: "", emailVerifyIsPending: false, diff --git a/ui/js/selectors/user.js b/ui/js/selectors/user.js index 6ade78efe..55108d335 100644 --- a/ui/js/selectors/user.js +++ b/ui/js/selectors/user.js @@ -21,13 +21,7 @@ export const selectEmailToVerify = createSelector( export const selectUserEmail = createSelector( selectUser, - user => (user && user.email ? user.email : "fake@lbry.io") -); - -export const selectUserHasEmail = createSelector( - selectUser, - selectEmailToVerify, - (user, email) => (user && user.has_email) || !!email + user => (user ? user.primary_email : null) ); export const selectUserIsRewardApproved = createSelector( @@ -74,12 +68,3 @@ export const selectUserIsVerificationCandidate = createSelector( selectUser, user => user && (!user.has_verified_email || !user.is_identity_verified) ); - -export const selectUserIsAuthRequested = createSelector( - selectEmailNewDeclined, - selectAuthenticationIsPending, - selectUserIsVerificationCandidate, - selectUserHasEmail, - (isEmailDeclined, isPending, isVerificationCandidate, hasEmail) => - !isEmailDeclined && (isPending || !hasEmail || isVerificationCandidate) -); -- 2.45.3 From 8313d06346c8154767eaf7a3095fa99ce407c2ef Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Thu, 20 Jul 2017 15:37:25 -0400 Subject: [PATCH 07/11] add missing reward text --- ui/js/rewards.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ui/js/rewards.js b/ui/js/rewards.js index 64f8a6f65..fd952032c 100644 --- a/ui/js/rewards.js +++ b/ui/js/rewards.js @@ -30,6 +30,10 @@ function rewardMessage(type, amount) { "You earned %s LBC for making your first publication.", amount ), + featured_download: __( + "You earned %s LBC for watching a featured download.", + amount + ), }[type]; } -- 2.45.3 From 57a56c5313664c152181119dc9f6339d5bc41083 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Thu, 20 Jul 2017 17:35:56 -0400 Subject: [PATCH 08/11] remove unused logic --- ui/js/actions/app.js | 1 - ui/js/actions/rewards.js | 4 +--- ui/js/component/rewardLink/index.js | 3 --- ui/js/component/rewardLink/view.jsx | 20 ++++++++------------ 4 files changed, 9 insertions(+), 19 deletions(-) diff --git a/ui/js/actions/app.js b/ui/js/actions/app.js index c3f23a3a5..9c7ed27fe 100644 --- a/ui/js/actions/app.js +++ b/ui/js/actions/app.js @@ -4,7 +4,6 @@ import { selectUpdateUrl, selectUpgradeDownloadPath, selectUpgradeDownloadItem, - selectUpgradeFilename, selectPageTitle, selectCurrentPage, selectCurrentParams, diff --git a/ui/js/actions/rewards.js b/ui/js/actions/rewards.js index 9c5d5143a..751bd79df 100644 --- a/ui/js/actions/rewards.js +++ b/ui/js/actions/rewards.js @@ -99,9 +99,7 @@ export function doClaimEligiblePurchaseRewards() { if (unclaimedType) { dispatch(doClaimRewardType(unclaimedType)); } - if (types[rewards.TYPE_FEATURED_DOWNLOAD] === false) { - dispatch(doClaimRewardType(rewards.TYPE_FEATURED_DOWNLOAD)); - } + dispatch(doClaimRewardType(rewards.TYPE_FEATURED_DOWNLOAD)); }; } diff --git a/ui/js/component/rewardLink/index.js b/ui/js/component/rewardLink/index.js index 81b01488b..cf53b2367 100644 --- a/ui/js/component/rewardLink/index.js +++ b/ui/js/component/rewardLink/index.js @@ -1,7 +1,6 @@ import React from "react"; import { connect } from "react-redux"; import { - makeSelectHasClaimedReward, makeSelectClaimRewardError, makeSelectRewardByType, makeSelectIsRewardClaimPending, @@ -11,13 +10,11 @@ import { doClaimReward, doClaimRewardClearError } from "actions/rewards"; import RewardLink from "./view"; const makeSelect = () => { - const selectHasClaimedReward = makeSelectHasClaimedReward(); const selectIsPending = makeSelectIsRewardClaimPending(); const selectReward = makeSelectRewardByType(); const selectError = makeSelectClaimRewardError(); const select = (state, props) => ({ - isClaimed: selectHasClaimedReward(state, props), errorMessage: selectError(state, props), isPending: selectIsPending(state, props), reward: selectReward(state, props), diff --git a/ui/js/component/rewardLink/view.jsx b/ui/js/component/rewardLink/view.jsx index be3a72f35..36a504d3e 100644 --- a/ui/js/component/rewardLink/view.jsx +++ b/ui/js/component/rewardLink/view.jsx @@ -1,5 +1,4 @@ import React from "react"; -import { Icon } from "component/common"; import Modal from "component/modal"; import Link from "component/link"; @@ -10,22 +9,19 @@ const RewardLink = props => { claimReward, clearError, errorMessage, - isClaimed, isPending, } = props; return (
- {isClaimed - ? Reward claimed. - : { - claimReward(reward); - }} - />} + { + claimReward(reward); + }} + /> {errorMessage ? Date: Fri, 21 Jul 2017 18:06:39 -0400 Subject: [PATCH 09/11] fix modal showing for existing users, fix previous merge, remove console --- ui/js/actions/app.js | 4 +--- ui/js/component/app/view.jsx | 2 +- ui/js/component/userEmailVerify/view.jsx | 2 -- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/ui/js/actions/app.js b/ui/js/actions/app.js index a5110babe..26425aad5 100644 --- a/ui/js/actions/app.js +++ b/ui/js/actions/app.js @@ -39,7 +39,7 @@ export function doAuthNavigate(pathAfterAuth = null, params = {}) { dispatch({ type: types.CHANGE_AFTER_AUTH_PATH, data: { - path: `${pathAfterAuth}?${queryStringFromParams(params)}`, + path: `${pathAfterAuth}?${toQueryString(params)}`, }, }); } @@ -248,8 +248,6 @@ export function doCheckDaemonVersion() { export function doAlertError(errorList) { return function(dispatch, getState) { const state = getState(); - console.log("do alert error"); - console.log(errorList); dispatch({ type: types.OPEN_MODAL, data: { diff --git a/ui/js/component/app/view.jsx b/ui/js/component/app/view.jsx index c4b857a8b..5447580af 100644 --- a/ui/js/component/app/view.jsx +++ b/ui/js/component/app/view.jsx @@ -48,7 +48,7 @@ class App extends React.PureComponent { if ( !isWelcomeAcknowledged && user && - (!isFetchingRewards || !isWelcomeRewardClaimed) + (isFetchingRewards === false && isWelcomeRewardClaimed === false) ) { openWelcomeModal(); } diff --git a/ui/js/component/userEmailVerify/view.jsx b/ui/js/component/userEmailVerify/view.jsx index 6e75ac0f7..d0c5581df 100644 --- a/ui/js/component/userEmailVerify/view.jsx +++ b/ui/js/component/userEmailVerify/view.jsx @@ -24,8 +24,6 @@ class UserEmailVerify extends React.PureComponent { render() { const { errorMessage, isPending } = this.props; - console.log("user email verify render"); - console.log(this.props); return (
{ -- 2.45.3 From 5ea4bffe9dd8206a469d9b8b2498be6f38ea5ca6 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Fri, 21 Jul 2017 18:13:13 -0400 Subject: [PATCH 10/11] production key --- ui/js/component/userVerify/view.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/js/component/userVerify/view.jsx b/ui/js/component/userVerify/view.jsx index 705ee424c..d09a97d34 100644 --- a/ui/js/component/userVerify/view.jsx +++ b/ui/js/component/userVerify/view.jsx @@ -38,7 +38,7 @@ class UserVerify extends React.PureComponent { label={__("Link Card and Finish")} disabled={isPending} token={this.onToken.bind(this)} - stripeKey="pk_test_NoL1JWL7i1ipfhVId5KfDZgo" + stripeKey="pk_live_e8M4dRNnCCbmpZzduEUZBgJO" />
); -- 2.45.3 From 29553bc391e594c0d918179345cf1e33a38c9c40 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Mon, 24 Jul 2017 18:59:26 -0400 Subject: [PATCH 11/11] auth flow fixes --- ui/js/actions/rewards.js | 7 ++ ui/js/actions/user.js | 16 +-- ui/js/component/app/view.jsx | 2 + ui/js/component/modalFirstReward/index.js | 20 ++++ ui/js/component/modalFirstReward/view.jsx | 50 +++++++++ ui/js/component/modalWelcome/index.js | 1 - ui/js/component/modalWelcome/view.jsx | 118 +++++++--------------- ui/js/constants/modal_types.js | 1 + ui/js/page/auth/view.jsx | 2 +- ui/js/page/rewards/view.jsx | 9 +- 10 files changed, 129 insertions(+), 97 deletions(-) create mode 100644 ui/js/component/modalFirstReward/index.js create mode 100644 ui/js/component/modalFirstReward/view.jsx diff --git a/ui/js/actions/rewards.js b/ui/js/actions/rewards.js index 751bd79df..95ae3b25b 100644 --- a/ui/js/actions/rewards.js +++ b/ui/js/actions/rewards.js @@ -1,4 +1,5 @@ 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"; @@ -58,6 +59,12 @@ export function doClaimReward(reward, saveError = false) { reward, }, }); + if (reward.reward_type == rewards.TYPE_NEW_USER) { + dispatch({ + type: types.OPEN_MODAL, + data: { modal: modals.FIRST_REWARD }, + }); + } }; const failure = error => { diff --git a/ui/js/actions/user.js b/ui/js/actions/user.js index dad5e7624..e2fa9b917 100644 --- a/ui/js/actions/user.js +++ b/ui/js/actions/user.js @@ -1,8 +1,9 @@ import * as types from "constants/action_types"; import lbryio from "lbryio"; import { setLocal } from "utils"; -import { doRewardList } from "actions/rewards"; +import { doRewardList, doClaimRewardType } from "actions/rewards"; import { selectEmailToVerify, selectUser } from "selectors/user"; +import rewards from "rewards"; export function doAuthenticate() { return function(dispatch, getState) { @@ -152,6 +153,7 @@ export function doUserIdentityVerify(stripeToken) { type: types.USER_IDENTITY_VERIFY_SUCCESS, data: { user }, }); + dispatch(doClaimRewardType(rewards.TYPE_NEW_USER)); } else { throw new Error( "Your identity is still not verified. This should not happen." @@ -159,18 +161,6 @@ export function doUserIdentityVerify(stripeToken) { } }) .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/app/view.jsx b/ui/js/component/app/view.jsx index 5447580af..e364c0db0 100644 --- a/ui/js/component/app/view.jsx +++ b/ui/js/component/app/view.jsx @@ -6,6 +6,7 @@ import ModalDownloading from "component/modalDownloading"; import ModalInsufficientCredits from "component/modalInsufficientCredits"; import ModalUpgrade from "component/modalUpgrade"; import ModalWelcome from "component/modalWelcome"; +import ModalFirstReward from "component/modalFirstReward"; import lbry from "lbry"; import * as modals from "constants/modal_types"; @@ -72,6 +73,7 @@ class App extends React.PureComponent { {modal == modals.ERROR && } {modal == modals.INSUFFICIENT_CREDITS && } {modal == modals.WELCOME && } + {modal == modals.FIRST_REWARD && } ); } diff --git a/ui/js/component/modalFirstReward/index.js b/ui/js/component/modalFirstReward/index.js new file mode 100644 index 000000000..5993a990e --- /dev/null +++ b/ui/js/component/modalFirstReward/index.js @@ -0,0 +1,20 @@ +import React from "react"; +import rewards from "rewards"; +import { connect } from "react-redux"; +import { doCloseModal } from "actions/app"; +import { makeSelectRewardByType } from "selectors/rewards"; +import ModalFirstReward from "./view"; + +const select = (state, props) => { + const selectReward = makeSelectRewardByType(); + + return { + reward: selectReward(state, { reward_type: rewards.TYPE_NEW_USER }), + }; +}; + +const perform = dispatch => ({ + closeModal: () => dispatch(doCloseModal()), +}); + +export default connect(select, perform)(ModalFirstReward); diff --git a/ui/js/component/modalFirstReward/view.jsx b/ui/js/component/modalFirstReward/view.jsx new file mode 100644 index 000000000..fc0aead0f --- /dev/null +++ b/ui/js/component/modalFirstReward/view.jsx @@ -0,0 +1,50 @@ +import React from "react"; +import { Modal } from "component/modal"; +import { CreditAmount } from "component/common"; + +class ModalFirstReward extends React.PureComponent { + render() { + const { closeModal, reward } = this.props; + + return ( + +
+

{__("About Your Reward")}

+

+ {__("You earned a reward of")} + {" "} + {" "}{__("LBRY credits, or")} {__("LBC")}. +

+

+ {__( + "This reward will show in your Wallet momentarily, shown in the top right, probably while you are reading this message." + )} +

+

+ {__( + "LBC is used to compensate creators, to publish, and to have say in how the network works." + )} +

+

+ {__( + "No need to understand it all just yet! Try watching or downloading something next." + )} +

+

+ {__( + "Finally, pleaseh know that LBRY is an early beta and that it earns the name." + )} +

+
+
+ ); + } +} + +export default ModalFirstReward; diff --git a/ui/js/component/modalWelcome/index.js b/ui/js/component/modalWelcome/index.js index 00a50c86b..4d8962a70 100644 --- a/ui/js/component/modalWelcome/index.js +++ b/ui/js/component/modalWelcome/index.js @@ -15,7 +15,6 @@ const select = (state, props) => { selectReward = makeSelectRewardByType(); return { - hasClaimed: selectHasClaimed(state, { reward_type: rewards.TYPE_NEW_USER }), isRewardApproved: selectUserIsRewardApproved(state), reward: selectReward(state, { reward_type: rewards.TYPE_NEW_USER }), }; diff --git a/ui/js/component/modalWelcome/view.jsx b/ui/js/component/modalWelcome/view.jsx index 3dec74cfa..c87df9da7 100644 --- a/ui/js/component/modalWelcome/view.jsx +++ b/ui/js/component/modalWelcome/view.jsx @@ -6,87 +6,45 @@ import RewardLink from "component/rewardLink"; class ModalWelcome extends React.PureComponent { render() { - const { - closeModal, - hasClaimed, - isRewardApproved, - reward, - verifyAccount, - } = this.props; + const { closeModal, isRewardApproved, reward, verifyAccount } = this.props; - return !hasClaimed - ? -
-

{__("Welcome to LBRY.")}

-

- {__( - "Using LBRY is like dating a centaur. Totally normal up top, and" - )} - {" "}{__("way different")} {__("underneath.")} -

-

{__("Up top, LBRY is similar to popular media sites.")}

-

- {__( - "Below, LBRY is controlled by users -- you -- via blockchain and decentralization." - )} -

-

- {__("Please have")} {" "} - {reward && - } - {!reward && {__("??")}} - {" "} {__("as a thank you for building content freedom.")} -

-
- {isRewardApproved && - } - {!isRewardApproved && - } - {!isRewardApproved && - } -
-
-
- : -
-

{__("About Your Reward")}

-

- {__("You earned a reward of")} - {" "} - {" "}{__("LBRY credits, or")} {__("LBC")}. -

-

- {__( - "This reward will show in your Wallet momentarily, probably while you are reading this message." - )} -

-

- {__( - "LBC is used to compensate creators, to publish, and to have say in how the network works." - )} -

-

- {__( - "No need to understand it all just yet! Try watching or downloading something next." - )} -

-

- {__( - "Finally, know that LBRY is an early beta and that it earns the name." - )} -

-
-
; + return ( + +
+

{__("Welcome to LBRY.")}

+

+ {__( + "Using LBRY is like dating a centaur. Totally normal up top, and" + )} + {" "}{__("way different")} {__("underneath.")} +

+

{__("Up top, LBRY is similar to popular media sites.")}

+

+ {__( + "Below, LBRY is controlled by users -- you -- via blockchain and decentralization." + )} +

+

+ {__("Please have")} {" "} + {reward && + } + {!reward && {__("??")}} + {" "} {__("as a thank you for building content freedom.")} +

+
+ {isRewardApproved && + } + {!isRewardApproved && + } + +
+
+
+ ); } } diff --git a/ui/js/constants/modal_types.js b/ui/js/constants/modal_types.js index 6befad274..13db30f3d 100644 --- a/ui/js/constants/modal_types.js +++ b/ui/js/constants/modal_types.js @@ -5,3 +5,4 @@ export const ERROR = "error"; export const INSUFFICIENT_CREDITS = "insufficient_credits"; export const UPGRADE = "upgrade"; export const WELCOME = "welcome"; +export const FIRST_REWARD = "first_reward"; diff --git a/ui/js/page/auth/view.jsx b/ui/js/page/auth/view.jsx index b43c7298c..fac6e13a0 100644 --- a/ui/js/page/auth/view.jsx +++ b/ui/js/page/auth/view.jsx @@ -76,7 +76,7 @@ export class AuthPage extends React.PureComponent {
{__( - "This information is disclosed only to LBRY, Inc. and not to the LBRY network. It is collected to provide communication and prevent abuse." + "This information is disclosed only to LBRY, Inc. and not to the LBRY network. It is optional and only collected to provide communication and prevent abuse. You may use LBRY without providing this information." )}
diff --git a/ui/js/page/rewards/view.jsx b/ui/js/page/rewards/view.jsx index abb7a3879..f69f3c61b 100644 --- a/ui/js/page/rewards/view.jsx +++ b/ui/js/page/rewards/view.jsx @@ -97,11 +97,16 @@ class RewardsPage extends React.PureComponent { {" "} {__("This can take anywhere from several minutes to several days.")}

+

- {__("You will receive an email when this process is complete.")} + {__( + "We apologize for this inconvenience, but have added this additional step to prevent fraud." + )}

- {__("Please enjoy free content in the meantime!")} + {__("You will receive an email when this process is complete.") + + " " + + __("Please enjoy free content in the meantime!")}

{__("daemon (lbrynet)")}{__("App")}{this.state.uiVersion}
{__("Daemon (lbrynet)")} {ver.lbrynet_version}
{__("wallet (lbryum)")}{__("Wallet (lbryum)")} {ver.lbryum_version}
{__("interface")}{this.state.uiVersion}{__("Connected Email")} + {user && user.primary_email + ? user.primary_email + : {__("none")}} +
{__("Platform")}