diff --git a/ui/js/actions/app.js b/ui/js/actions/app.js index 871607778..e6d5879f8 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, @@ -35,6 +34,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}?${toQueryString(params)}`, + }, + }); + } + dispatch(doNavigate("/auth")); + }; +} + export function doChangePath(path, options = {}) { return function(dispatch, getState) { dispatch({ @@ -237,8 +250,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/actions/content.js b/ui/js/actions/content.js index 6c2052aca..b0e40ffa8 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"); @@ -294,7 +294,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/actions/rewards.js b/ui/js/actions/rewards.js index 9c5d5143a..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 => { @@ -99,9 +106,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/actions/user.js b/ui/js/actions/user.js index a2db27c9f..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 { selectEmailToVerify } from "selectors/user"; +import { doRewardList, doClaimRewardType } from "actions/rewards"; +import { selectEmailToVerify, selectUser } from "selectors/user"; +import rewards from "rewards"; export function doAuthenticate() { return function(dispatch, getState) { @@ -137,6 +138,37 @@ 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 }, + }); + dispatch(doClaimRewardType(rewards.TYPE_NEW_USER)); + } else { + throw new Error( + "Your identity is still not verified. This should not happen." + ); //shouldn't happen + } + }) + .catch(error => { + dispatch({ + type: types.USER_IDENTITY_VERIFY_FAILURE, + data: { error: error.toString() }, + }); + }); + }; +} + export function doFetchAccessToken() { return function(dispatch, getState) { const success = token => diff --git a/ui/js/component/app/index.js b/ui/js/component/app/index.js index 3749741bb..ff4940f88 100644 --- a/ui/js/component/app/index.js +++ b/ui/js/component/app/index.js @@ -1,23 +1,41 @@ import React from "react"; import { connect } from "react-redux"; - import { selectCurrentModal } from "selectors/app"; import { doCheckUpgradeAvailable, + doOpenModal, doAlertError, doRecordScroll, - doCheckDaemonVersion, } 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)), recordScroll: scrollPosition => dispatch(doRecordScroll(scrollPosition)), }); diff --git a/ui/js/component/app/view.jsx b/ui/js/component/app/view.jsx index 857bd7cf6..e364c0db0 100644 --- a/ui/js/component/app/view.jsx +++ b/ui/js/component/app/view.jsx @@ -3,30 +3,58 @@ 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 ModalFirstReward from "component/modalFirstReward"; import lbry from "lbry"; -import { Line } from "rc-progress"; +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); + this.scrollListener = () => this.props.recordScroll(window.scrollY); window.addEventListener("scroll", this.scrollListener); } + componentWillReceiveProps(nextProps) { + this.showWelcome(nextProps); + } + + showWelcome(props) { + const { + isFetchingRewards, + isWelcomeAcknowledged, + isWelcomeRewardClaimed, + openWelcomeModal, + user, + } = props; + + if ( + !isWelcomeAcknowledged && + user && + (isFetchingRewards === false && isWelcomeRewardClaimed === false) + ) { + openWelcomeModal(); + } + } + componentWillUnmount() { window.removeEventListener("scroll", this.scrollListener); } @@ -40,10 +68,12 @@ 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 && } + {modal == modals.FIRST_REWARD && } ); } diff --git a/ui/js/component/auth/index.js b/ui/js/component/auth/index.js deleted file mode 100644 index 37af9f90f..000000000 --- a/ui/js/component/auth/index.js +++ /dev/null @@ -1,16 +0,0 @@ -import React from "react"; -import { connect } from "react-redux"; -import { - selectAuthenticationIsPending, - selectEmailToVerify, - selectUserIsVerificationCandidate, -} from "selectors/user"; -import Auth from "./view"; - -const select = state => ({ - isPending: selectAuthenticationIsPending(state), - email: selectEmailToVerify(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 551113ffa..000000000 --- a/ui/js/component/auth/view.jsx +++ /dev/null @@ -1,22 +0,0 @@ -import React from "react"; -import { BusyMessage } from "component/common"; -import UserEmailNew from "component/userEmailNew"; -import UserEmailVerify from "component/userEmailVerify"; - -export class Auth extends React.PureComponent { - render() { - const { isPending, email, isVerificationCandidate } = this.props; - - if (isPending) { - return ; - } else if (!email) { - return ; - } else if (isVerificationCandidate) { - 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 753fe2fe4..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/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/fileActions/view.jsx b/ui/js/component/fileActions/view.jsx index ef72463c5..9ff0fbaa1 100644 --- a/ui/js/component/fileActions/view.jsx +++ b/ui/js/component/fileActions/view.jsx @@ -181,13 +181,6 @@ class FileActions extends React.PureComponent { {" "} {__("credits")}. - - {__("You don't have enough LBRY credits to pay for this stream.")} - ({ - balance: lbry.formatCredits(selectBalance(state), 1), + balance: formatCredits(selectBalance(state), 1), publish: __("Publish"), }); diff --git a/ui/js/component/link/view.jsx b/ui/js/component/link/view.jsx index 2f9c7478e..a39e9947c 100644 --- a/ui/js/component/link/view.jsx +++ b/ui/js/component/link/view.jsx @@ -11,7 +11,6 @@ const Link = props => { icon, badge, button, - hidden, disabled, children, } = props; diff --git a/ui/js/component/modal-page.js b/ui/js/component/modal-page.js deleted file mode 100644 index f63d120f5..000000000 --- a/ui/js/component/modal-page.js +++ /dev/null @@ -1,21 +0,0 @@ -import React from "react"; -import ReactModal from "react-modal"; - -export class ModalPage extends React.PureComponent { - render() { - return ( - -
- {this.props.children} -
-
- ); - } -} - -export default ModalPage; 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/modalInsufficientCredits/index.js b/ui/js/component/modalInsufficientCredits/index.js new file mode 100644 index 000000000..0ece4f1fe --- /dev/null +++ b/ui/js/component/modalInsufficientCredits/index.js @@ -0,0 +1,16 @@ +import React from "react"; +import { connect } from "react-redux"; +import { doCloseModal, doNavigate } from "actions/app"; +import ModalInsufficientCredits from "./view"; + +const select = state => ({}); + +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..4d8962a70 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, doAuthNavigate } from "actions/app"; +import { doSetClientSetting } from "actions/settings"; import { selectUserIsRewardApproved } from "selectors/user"; import { makeSelectHasClaimedReward, - makeSelectClaimRewardError, makeSelectRewardByType, } from "selectors/rewards"; import ModalWelcome from "./view"; @@ -15,14 +15,24 @@ 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 }), }; }; -const perform = dispatch => ({ - closeModal: () => dispatch(doCloseModal()), -}); +const perform = dispatch => () => { + const closeModal = () => { + dispatch(doSetClientSetting("welcome_acknowledged", true)); + dispatch(doCloseModal()); + }; + + return { + verifyAccount: () => { + closeModal(); + dispatch(doAuthNavigate("/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..c87df9da7 100644 --- a/ui/js/component/modalWelcome/view.jsx +++ b/ui/js/component/modalWelcome/view.jsx @@ -6,75 +6,45 @@ import RewardLink from "component/rewardLink"; class ModalWelcome extends React.PureComponent { render() { - const { closeModal, hasClaimed, isRewardApproved, reward } = 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." - )} -

-

- {__("Thank you for making content freedom possible!")} - {" "}{isRewardApproved ? __("Here's a nickel, kid.") : ""} -

-
- {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/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 ? { 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/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 2570eba29..e1bcfdfd1 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,14 +24,13 @@ class UserEmailVerify extends React.PureComponent { render() { const { errorMessage, isPending } = this.props; - return ( { this.handleSubmit(event); }} > +

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

{ + const selectReward = makeSelectRewardByType(); + + return { + isPending: selectIdentityVerifyIsPending(state), + errorMessage: selectIdentityVerifyErrorMessage(state), + reward: selectReward(state, { reward_type: rewards.TYPE_NEW_USER }), + }; +}; + +const perform = dispatch => ({ + 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 new file mode 100644 index 000000000..d09a97d34 --- /dev/null +++ b/ui/js/component/userVerify/view.jsx @@ -0,0 +1,48 @@ +import React from "react"; +import { CreditAmount } from "component/common"; +import CardVerify from "component/cardVerify"; + +class UserVerify extends React.PureComponent { + constructor(props) { + super(props); + + this.state = { + code: "", + }; + } + + handleCodeChanged(event) { + this.setState({ + code: event.target.value, + }); + } + + onToken(data) { + this.props.verifyUserIdentity(data.id); + } + + render() { + const { errorMessage, isPending, reward } = this.props; + return ( +
+ {(!reward || !reward.transaction_id) && +

+ 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}

} + +
+ ); + } +} + +export default UserVerify; 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 +102,7 @@ var init = function() { ReactDOM.render( -
+
, canvas ); diff --git a/ui/js/page/auth/index.js b/ui/js/page/auth/index.js new file mode 100644 index 000000000..541504dbb --- /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 { selectPathAfterAuth } from "selectors/app"; +import { + selectAuthenticationIsPending, + 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), + pathAfterAuth: selectPathAfterAuth(state), + user: selectUser(state), + isVerificationCandidate: selectUserIsVerificationCandidate(state), +}); + +const perform = dispatch => ({ + 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 new file mode 100644 index 000000000..fac6e13a0 --- /dev/null +++ b/ui/js/page/auth/view.jsx @@ -0,0 +1,89 @@ +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 { + componentWillMount() { + this.navigateIfAuthenticated(this.props); + } + + componentWillReceiveProps(nextProps) { + this.navigateIfAuthenticated(nextProps); + } + + navigateIfAuthenticated(props) { + const { isPending, user } = props; + if ( + !isPending && + user && + user.has_verified_email && + user.is_identity_verified + ) { + props.navigate(props.pathAfterAuth); + } + } + + 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, user, isPending } = this.props; + + return ( +
+
+
+

{this.getTitle()}

+
+
+ {!isPending && + !email && + !user.has_verified_email && +

+ {__("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 optional and only collected to provide communication and prevent abuse. You may use LBRY without providing this information." + )} +
+
+
+
+ ); + } +} + +export default AuthPage; diff --git a/ui/js/page/help/index.js b/ui/js/page/help/index.js index f5378405e..c4ea548b2 100644 --- a/ui/js/page/help/index.js +++ b/ui/js/page/help/index.js @@ -2,10 +2,11 @@ import React from "react"; import { doNavigate } from "actions/app"; import { connect } from "react-redux"; import { doFetchAccessToken } from "actions/user"; -import { selectAccessToken } from "selectors/user"; +import { selectAccessToken, selectUser } from "selectors/user"; import HelpPage from "./view"; const select = state => ({ + user: selectUser(state), accessToken: selectAccessToken(state), }); diff --git a/ui/js/page/help/view.jsx b/ui/js/page/help/view.jsx index a805a92d9..f56d058e8 100644 --- a/ui/js/page/help/view.jsx +++ b/ui/js/page/help/view.jsx @@ -50,7 +50,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; @@ -146,16 +146,24 @@ class HelpPage extends React.PureComponent { ? - + + + + + - + - - + + diff --git a/ui/js/page/rewards/index.js b/ui/js/page/rewards/index.js index 8e7abb1fc..8a7a29d98 100644 --- a/ui/js/page/rewards/index.js +++ b/ui/js/page/rewards/index.js @@ -1,25 +1,33 @@ import React from "react"; import { connect } from "react-redux"; -import { doNavigate } from "actions/app"; -import { selectFetchingRewards, selectRewards } from "selectors/rewards"; import { - selectUserIsRewardEligible, - selectUserHasEmail, - selectUserIsVerificationCandidate, -} from "selectors/user"; + makeSelectRewardByType, + selectFetchingRewards, + selectRewards, +} from "selectors/rewards"; +import { selectUser } from "selectors/user"; +import { doAuthNavigate, doNavigate } from "actions/app"; import { doRewardList } from "actions/rewards"; +import rewards from "rewards"; import RewardsPage from "./view"; -const select = state => ({ - 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), + newUserReward: selectReward(state, { reward_type: rewards.TYPE_NEW_USER }), + user: selectUser(state), + }; +}; const perform = dispatch => ({ fetchRewards: () => dispatch(doRewardList()), + navigate: path => dispatch(doNavigate(path)), + doAuth: () => { + dispatch(doAuthNavigate("/rewards")); + }, }); export default connect(select, perform)(RewardsPage); diff --git a/ui/js/page/rewards/view.jsx b/ui/js/page/rewards/view.jsx index 237994667..f69f3c61b 100644 --- a/ui/js/page/rewards/view.jsx +++ b/ui/js/page/rewards/view.jsx @@ -1,8 +1,6 @@ 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"; @@ -41,60 +39,91 @@ 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() { - const { - fetching, - isEligible, - isVerificationCandidate, - hasEmail, - rewards, - } = this.props; + const { doAuth, fetching, navigate, rewards, user } = this.props; - let content, - isCard = false; + let content, cardHeader; - if (!hasEmail || isVerificationCandidate) { + if (fetching) { + content = ( +
+ +
+ ); + } else if (rewards.length > 0) { content = (
-

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

- + {rewards.map(reward => + + )}
); - isCard = true; - } else if (!isEligible) { - isCard = true; - content = ( -
-

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

-
- ); - } else if (fetching) { - content = ; - } else if (rewards.length > 0) { - content = rewards.map(reward => - - ); } else { - content =
{__("Failed to load rewards.")}
; + content = ( +
+ {__("Failed to load rewards.")} +
+ ); + } + + if ( + user && + (!user.primary_email || + !user.has_verified_email || + !user.is_identity_verified) + ) { + cardHeader = ( +
+
+

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

+
+
+ +
+
+ ); + } else if (user && !user.is_reward_approved) { + cardHeader = ( +
+

+ {__( + "This account must undergo review before you can participate in the rewards program." + )} + {" "} + {__("This can take anywhere from several minutes to several days.")} +

+ +

+ {__( + "We apologize for this inconvenience, but have added this additional step to prevent fraud." + )} +

+

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

+

+ navigate("/discover")} + button="primary" + label="Return Home" + /> +

+
+ ); } return (
- {isCard - ?
-
- {content} -
-
- : content} + {cardHeader &&
{cardHeader}
} + {content}
); } diff --git a/ui/js/reducers/app.js b/ui/js/reducers/app.js index cf0849504..6ee007b86 100644 --- a/ui/js/reducers/app.js +++ b/ui/js/reducers/app.js @@ -16,6 +16,7 @@ const reducers = {}; const defaultState = { isLoaded: false, currentPath: currentPath(), + pathAfterAuth: "/discover", platform: process.platform, upgradeSkipped: sessionStorage.getItem("upgradeSkipped"), daemonVersionMatched: null, @@ -49,6 +50,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/reducers/user.js b/ui/js/reducers/user.js index ef7602193..56720b2f7 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, @@ -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, + }); +}; + reducers[types.FETCH_ACCESS_TOKEN_SUCCESS] = function(state, action) { const { token } = action.data; 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]; } diff --git a/ui/js/selectors/app.js b/ui/js/selectors/app.js index 7cb83a4d7..3951a8216 100644 --- a/ui/js/selectors/app.js +++ b/ui/js/selectors/app.js @@ -192,7 +192,17 @@ 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 ); + +export const selectPathAfterAuth = createSelector( + _selectState, + state => state.pathAfterAuth +); diff --git a/ui/js/selectors/user.js b/ui/js/selectors/user.js index 265e9c936..68554d825 100644 --- a/ui/js/selectors/user.js +++ b/ui/js/selectors/user.js @@ -12,25 +12,16 @@ 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, state => state.emailToVerify ); -export const selectUserHasEmail = createSelector( +export const selectUserEmail = createSelector( selectUser, - selectEmailToVerify, - (user, email) => (user && user.has_email) || !!email -); - -export const selectUserIsRewardEligible = createSelector( - selectUser, - user => user && user.is_reward_eligible + user => (user ? user.primary_email : null) ); export const selectUserIsRewardApproved = createSelector( @@ -63,18 +54,19 @@ export const selectEmailVerifyErrorMessage = createSelector( state => state.emailVerifyErrorMessage ); -export const selectUserIsVerificationCandidate = createSelector( - selectUser, - user => user && !user.has_verified_email +export const selectIdentityVerifyIsPending = createSelector( + _selectState, + state => state.identityVerifyIsPending ); -export const selectUserIsAuthRequested = createSelector( - selectEmailNewDeclined, - selectAuthenticationIsPending, - selectUserIsVerificationCandidate, - selectUserHasEmail, - (isEmailDeclined, isPending, isVerificationCandidate, hasEmail) => - !isEmailDeclined && (isPending || !hasEmail || isVerificationCandidate) +export const selectIdentityVerifyErrorMessage = createSelector( + _selectState, + state => state.identityVerifyErrorMessage +); + +export const selectUserIsVerificationCandidate = createSelector( + selectUser, + user => user && (!user.has_verified_email || !user.is_identity_verified) ); export const selectAccessToken = createSelector( diff --git a/ui/js/utils.js b/ui/js/utils.js index b366954d1..18e4ca21e 100644 --- a/ui/js/utils.js +++ b/ui/js/utils.js @@ -31,3 +31,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/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": "../", 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/all.scss b/ui/scss/all.scss index 6b421196a..61612d7df 100644 --- a/ui/scss/all.scss +++ b/ui/scss/all.scss @@ -15,7 +15,6 @@ @import "component/_channel-indicator.scss"; @import "component/_notice.scss"; @import "component/_modal.scss"; -@import "component/_modal-page.scss"; @import "component/_snack-bar.scss"; @import "component/_video.scss"; @import "page/_developer.scss"; diff --git a/ui/scss/component/_card.scss b/ui/scss/component/_card.scss index b6a902f89..d8416d226 100644 --- a/ui/scss/component/_card.scss +++ b/ui/scss/component/_card.scss @@ -164,6 +164,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 f701ebe06..9dee7240b 100644 --- a/ui/scss/component/_form-field.scss +++ b/ui/scss/component/_form-field.scss @@ -1,11 +1,6 @@ @import "../global"; $width-input-border: 2px; -$width-input-text: 330px; - -.form-input-width { - width: $width-input-text -} .form-row-submit { diff --git a/ui/scss/component/_modal-page.scss b/ui/scss/component/_modal-page.scss deleted file mode 100644 index ada366f61..000000000 --- a/ui/scss/component/_modal-page.scss +++ /dev/null @@ -1,54 +0,0 @@ -@import "../global"; - -.modal-page { - position: fixed; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - - border: 1px solid rgb(204, 204, 204); - background: rgb(255, 255, 255); - overflow: auto; -} - -.modal-page--full { - left: 0; - right: 0; - top: 0; - bottom: 0; - .modal-page__content { - max-width: 500px; - } -} - -/* -.modal-page { - position: fixed; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - - border: 1px solid rgb(204, 204, 204); - background: rgb(255, 255, 255); - overflow: auto; - border-radius: 4px; - outline: none; - padding: 36px; - - top: 25px; - left: 25px; - right: 25px; - bottom: 25px; -} -*/ - -.modal-page__content { - h1, h2 { - margin-bottom: $spacing-vertical / 2; - } - h3, h4 { - margin-bottom: $spacing-vertical / 4; - } -} \ No newline at end of file
{__("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")}