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;