Added phone verification functionality
This commit is contained in:
parent
b9b7af2bbd
commit
4409315353
15 changed files with 225 additions and 59 deletions
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { doUserFieldNew, doUserInviteNew } from 'redux/actions/user';
|
||||
import { doUserEmailNew, doUserPhoneNew, doUserInviteNew } from 'redux/actions/user';
|
||||
import { selectEmailNewIsPending, selectEmailNewErrorMessage } from 'redux/selectors/user';
|
||||
import UserFieldNew from './view';
|
||||
|
||||
|
@ -10,7 +10,8 @@ const select = state => ({
|
|||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
addUserEmail: email => dispatch(doUserFieldNew(email)),
|
||||
addUserEmail: email => dispatch(doUserEmailNew(email)),
|
||||
addUserPhone: phone => dispatch(doUserPhoneNew(phone)),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(UserFieldNew);
|
||||
|
|
|
@ -6,25 +6,58 @@ class UserFieldNew extends React.PureComponent {
|
|||
super(props);
|
||||
|
||||
this.state = {
|
||||
phone: '',
|
||||
email: '',
|
||||
};
|
||||
}
|
||||
|
||||
handleEmailChanged(event) {
|
||||
handleChanged(event, fieldType) {
|
||||
console.log({
|
||||
[fieldType]: event.target.value,
|
||||
});
|
||||
this.setState({
|
||||
email: event.target.value,
|
||||
[fieldType]: event.target.value,
|
||||
});
|
||||
}
|
||||
|
||||
handleSubmit() {
|
||||
const { email } = this.state;
|
||||
this.props.addUserEmail(email);
|
||||
const { email, phone } = this.state;
|
||||
if (phone) {
|
||||
this.props.addUserPhone(phone);
|
||||
} else {
|
||||
this.props.addUserEmail(email);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { cancelButton, errorMessage, isPending } = this.props;
|
||||
const { cancelButton, errorMessage, isPending, fieldType } = this.props;
|
||||
|
||||
return (
|
||||
return fieldType === 'phone' ? (
|
||||
<div>
|
||||
<p>
|
||||
{__(
|
||||
'Enter your phone number and we will send you a verification code. We will not share your phone number with third parties.'
|
||||
)}
|
||||
</p>
|
||||
<Form onSubmit={this.handleSubmit.bind(this)}>
|
||||
<FormRow
|
||||
type="text"
|
||||
label="Phone"
|
||||
placeholder="(555) 555-5555"
|
||||
name="phone"
|
||||
value={this.state.phone}
|
||||
errorMessage={errorMessage}
|
||||
onChange={event => {
|
||||
this.handleChanged(event, 'phone');
|
||||
}}
|
||||
/>
|
||||
<div className="form-row-submit">
|
||||
<Submit label="Submit" disabled={isPending} />
|
||||
{cancelButton}
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<p>
|
||||
{__("We'll let you know about LBRY updates, security issues, and great new content.")}
|
||||
|
@ -39,7 +72,7 @@ class UserFieldNew extends React.PureComponent {
|
|||
value={this.state.email}
|
||||
errorMessage={errorMessage}
|
||||
onChange={event => {
|
||||
this.handleEmailChanged(event);
|
||||
this.handleChanged(event, 'email');
|
||||
}}
|
||||
/>
|
||||
<div className="form-row-submit">
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { doUserFieldVerify, doUserFieldVerifyFailure } from 'redux/actions/user';
|
||||
import { doUserEmailVerify, doUserPhoneVerify, doUserEmailVerifyFailure } from 'redux/actions/user';
|
||||
import {
|
||||
selectEmailVerifyIsPending,
|
||||
selectEmailToVerify,
|
||||
selectPhoneToVerify,
|
||||
selectEmailVerifyErrorMessage,
|
||||
} from 'redux/selectors/user';
|
||||
import UserFieldVerify from './view';
|
||||
|
@ -11,12 +12,14 @@ import UserFieldVerify from './view';
|
|||
const select = state => ({
|
||||
isPending: selectEmailVerifyIsPending(state),
|
||||
email: selectEmailToVerify(state),
|
||||
phone: selectPhoneToVerify(state),
|
||||
errorMessage: selectEmailVerifyErrorMessage(state),
|
||||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
verifyUserEmail: (code, recaptcha) => dispatch(doUserFieldVerify(code, recaptcha)),
|
||||
verifyUserEmailFailure: error => dispatch(doUserFieldVerifyFailure(error)),
|
||||
verifyUserEmail: (code, recaptcha) => dispatch(doUserEmailVerify(code, recaptcha)),
|
||||
verifyUserPhone: code => dispatch(doUserPhoneVerify(code)),
|
||||
verifyUserEmailFailure: error => dispatch(doUserEmailVerifyFailure(error)),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(UserFieldVerify);
|
||||
|
|
|
@ -19,19 +19,24 @@ class UserFieldVerify extends React.PureComponent {
|
|||
|
||||
handleSubmit() {
|
||||
const { code } = this.state;
|
||||
try {
|
||||
const verification = JSON.parse(atob(code));
|
||||
this.props.verifyUserEmail(verification.token, verification.recaptcha);
|
||||
} catch (error) {
|
||||
this.props.verifyUserEmailFailure('Invalid Verification Token');
|
||||
const { fieldType } = this.props;
|
||||
if (fieldType === 'phone') {
|
||||
this.props.verifyUserPhone(code);
|
||||
} else {
|
||||
try {
|
||||
const verification = JSON.parse(atob(code));
|
||||
this.props.verifyUserEmail(verification.token, verification.recaptcha);
|
||||
} catch (error) {
|
||||
this.props.verifyUserEmailFailure('Invalid Verification Token');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { cancelButton, errorMessage, email, isPending } = this.props;
|
||||
const { cancelButton, errorMessage, email, isPending, phone } = this.props;
|
||||
return (
|
||||
<Form onSubmit={this.handleSubmit.bind(this)}>
|
||||
<p>Please enter the verification code emailed to {email}.</p>
|
||||
<p>Please enter the verification code sent to {phone || email}.</p>
|
||||
<FormRow
|
||||
type="text"
|
||||
label={__('Verification Code')}
|
||||
|
|
|
@ -9,6 +9,9 @@ import {
|
|||
selectIdentityVerifyErrorMessage,
|
||||
} from 'redux/selectors/user';
|
||||
import UserVerify from './view';
|
||||
import { selectCurrentModal } from 'redux/selectors/app';
|
||||
import { doOpenModal } from 'redux/actions/app';
|
||||
import { PHONE_COLLECTION } from 'constants/modal_types';
|
||||
|
||||
const select = (state, props) => {
|
||||
const selectReward = makeSelectRewardByType();
|
||||
|
@ -17,12 +20,14 @@ const select = (state, props) => {
|
|||
isPending: selectIdentityVerifyIsPending(state),
|
||||
errorMessage: selectIdentityVerifyErrorMessage(state),
|
||||
reward: selectReward(state, { reward_type: rewards.TYPE_NEW_USER }),
|
||||
modal: selectCurrentModal(state),
|
||||
};
|
||||
};
|
||||
|
||||
const perform = dispatch => ({
|
||||
navigate: uri => dispatch(doNavigate(uri)),
|
||||
verifyUserIdentity: token => dispatch(doUserIdentityVerify(token)),
|
||||
verifyPhone: () => dispatch(doOpenModal(PHONE_COLLECTION)),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(UserVerify);
|
||||
|
|
|
@ -23,7 +23,7 @@ class UserVerify extends React.PureComponent {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { errorMessage, isPending, navigate } = this.props;
|
||||
const { errorMessage, isPending, navigate, verifyPhone, modal } = this.props;
|
||||
return (
|
||||
<div>
|
||||
<section className="card card--form">
|
||||
|
@ -74,12 +74,13 @@ class UserVerify extends React.PureComponent {
|
|||
)}`}
|
||||
</div>
|
||||
<div className="card__actions">
|
||||
{errorMessage && <p className="form-field__error">{errorMessage}</p>}
|
||||
<CardVerify
|
||||
<Link
|
||||
onClick={() => {
|
||||
verifyPhone();
|
||||
}}
|
||||
button="alt"
|
||||
icon="icon-phone"
|
||||
label={__('Submit Phone Number')}
|
||||
disabled={isPending}
|
||||
token={this.onToken.bind(this)}
|
||||
stripeKey={lbryio.getStripeToken()}
|
||||
/>
|
||||
</div>
|
||||
<div className="card__content">
|
||||
|
|
|
@ -108,6 +108,14 @@ 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_PHONE_DECLINE = 'USER_PHONE_DECLINE';
|
||||
export const USER_PHONE_NEW_STARTED = 'USER_PHONE_NEW_STARTED';
|
||||
export const USER_PHONE_NEW_SUCCESS = 'USER_PHONE_NEW_SUCCESS';
|
||||
export const USER_PHONE_NEW_EXISTS = 'USER_PHONE_NEW_EXISTS';
|
||||
export const USER_PHONE_NEW_FAILURE = 'USER_PHONE_NEW_FAILURE';
|
||||
export const USER_PHONE_VERIFY_STARTED = 'USER_PHONE_VERIFY_STARTED';
|
||||
export const USER_PHONE_VERIFY_SUCCESS = 'USER_PHONE_VERIFY_SUCCESS';
|
||||
export const USER_PHONE_VERIFY_FAILURE = 'USER_PHONE_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';
|
||||
|
|
|
@ -6,7 +6,7 @@ export const ERROR = 'error';
|
|||
export const INSUFFICIENT_CREDITS = 'insufficient_credits';
|
||||
export const UPGRADE = 'upgrade';
|
||||
export const WELCOME = 'welcome';
|
||||
export const EMAIL_COLLECTION = 'email_collection';
|
||||
export const PHONE_COLLECTION = 'phone_collection';
|
||||
export const FIRST_REWARD = 'first_reward';
|
||||
export const AUTHENTICATION_FAILURE = 'auth_failure';
|
||||
export const TRANSACTION_FAILED = 'transaction_failed';
|
||||
|
|
|
@ -12,7 +12,7 @@ import { Provider } from 'react-redux';
|
|||
import { doConditionalAuthNavigate, doDaemonReady, doShowSnackBar } from 'redux/actions/app';
|
||||
import { doNavigate } from 'redux/actions/navigation';
|
||||
import { doDownloadLanguages } from 'redux/actions/settings';
|
||||
import { doUserFieldVerify } from 'redux/actions/user';
|
||||
import { doUserEmailVerify } from 'redux/actions/user';
|
||||
import 'scss/all.scss';
|
||||
import store from 'store';
|
||||
import app from './app';
|
||||
|
@ -35,7 +35,7 @@ ipcRenderer.on('open-uri-requested', (event, uri, newSession) => {
|
|||
}
|
||||
if (verification.token && verification.recaptcha) {
|
||||
app.store.dispatch(doConditionalAuthNavigate(newSession));
|
||||
app.store.dispatch(doUserFieldVerify(verification.token, verification.recaptcha));
|
||||
app.store.dispatch(doUserEmailVerify(verification.token, verification.recaptcha));
|
||||
} else {
|
||||
app.store.dispatch(doShowSnackBar({ message: 'Invalid Verification URI' }));
|
||||
}
|
||||
|
|
|
@ -3,18 +3,19 @@ import * as settings from 'constants/settings';
|
|||
import { connect } from 'react-redux';
|
||||
import { doCloseModal } from 'redux/actions/app';
|
||||
import { doSetClientSetting } from 'redux/actions/settings';
|
||||
import { selectEmailToVerify, selectUser } from 'redux/selectors/user';
|
||||
import { selectPhoneToVerify, selectUser } from 'redux/selectors/user';
|
||||
import ModalPhoneCollection from './view';
|
||||
import { doNavigate } from 'redux/actions/navigation';
|
||||
|
||||
const select = state => ({
|
||||
email: selectEmailToVerify(state),
|
||||
phone: selectPhoneToVerify(state),
|
||||
user: selectUser(state),
|
||||
});
|
||||
|
||||
const perform = dispatch => () => ({
|
||||
closeModal: () => {
|
||||
dispatch(doSetClientSetting(settings.EMAIL_COLLECTION_ACKNOWLEDGED, true));
|
||||
dispatch(doCloseModal());
|
||||
dispatch(doNavigate('/rewards'));
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -6,14 +6,14 @@ import UserFieldVerify from 'component/userFieldVerify';
|
|||
|
||||
class ModalPhoneCollection extends React.PureComponent {
|
||||
renderInner() {
|
||||
const { closeModal, email, user } = this.props;
|
||||
const { closeModal, phone, user } = this.props;
|
||||
|
||||
const cancelButton = <Link button="text" onClick={closeModal} label={__('Not Now')} />;
|
||||
|
||||
if (!user.has_verified_email && !email) {
|
||||
return <UserFieldNew cancelButton={cancelButton} />;
|
||||
} else if (!user.has_verified_email) {
|
||||
return <UserFieldVerify cancelButton={cancelButton} />;
|
||||
if (!user.phone_number && !phone) {
|
||||
return <UserFieldNew cancelButton={cancelButton} fieldType="phone" />;
|
||||
} else if (!user.phone_number) {
|
||||
return <UserFieldVerify cancelButton={cancelButton} fieldType="phone" />;
|
||||
}
|
||||
closeModal();
|
||||
}
|
||||
|
|
|
@ -40,11 +40,10 @@ class ModalRouter extends React.PureComponent {
|
|||
return;
|
||||
}
|
||||
|
||||
const transitionModal = [
|
||||
this.checkShowWelcome,
|
||||
this.checkShowEmail,
|
||||
this.checkShowCreditIntro,
|
||||
].reduce((acc, func) => (!acc ? func.bind(this)(props) : acc), false);
|
||||
const transitionModal = [this.checkShowWelcome, this.checkShowCreditIntro].reduce(
|
||||
(acc, func) => (!acc ? func.bind(this)(props) : acc),
|
||||
false
|
||||
);
|
||||
|
||||
if (
|
||||
transitionModal &&
|
||||
|
@ -65,18 +64,6 @@ class ModalRouter extends React.PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
checkShowEmail(props) {
|
||||
const { isEmailCollectionAcknowledged, isVerificationCandidate, user } = props;
|
||||
if (
|
||||
!isEmailCollectionAcknowledged &&
|
||||
isVerificationCandidate &&
|
||||
user &&
|
||||
!user.has_verified_email
|
||||
) {
|
||||
return modals.EMAIL_COLLECTION;
|
||||
}
|
||||
}
|
||||
|
||||
checkShowCreditIntro(props) {
|
||||
const { balance, page, isCreditIntroAcknowledged } = props;
|
||||
|
||||
|
@ -124,7 +111,7 @@ class ModalRouter extends React.PureComponent {
|
|||
return <ModalAffirmPurchase {...modalProps} />;
|
||||
case modals.CONFIRM_CLAIM_REVOKE:
|
||||
return <ModalRevokeClaim {...modalProps} />;
|
||||
case modals.EMAIL_COLLECTION:
|
||||
case modals.PHONE_COLLECTION:
|
||||
return <ModalPhoneCollection {...modalProps} />;
|
||||
default:
|
||||
return null;
|
||||
|
|
|
@ -3,7 +3,7 @@ import * as MODALS from 'constants/modal_types';
|
|||
import Lbryio from 'lbryio';
|
||||
import { doOpenModal, doShowSnackBar } from 'redux/actions/app';
|
||||
import { doClaimRewardType, doRewardList } from 'redux/actions/rewards';
|
||||
import { selectEmailToVerify } from 'redux/selectors/user';
|
||||
import { selectEmailToVerify, selectPhoneToVerify } from 'redux/selectors/user';
|
||||
import rewards from 'rewards';
|
||||
|
||||
export function doFetchInviteStatus() {
|
||||
|
@ -78,7 +78,72 @@ export function doUserFetch() {
|
|||
};
|
||||
}
|
||||
|
||||
export function doUserFieldNew(email) {
|
||||
export function doUserPhoneNew(phone) {
|
||||
return dispatch => {
|
||||
dispatch({
|
||||
type: ACTIONS.USER_PHONE_NEW_STARTED,
|
||||
phone,
|
||||
});
|
||||
|
||||
const success = () => {
|
||||
dispatch({
|
||||
type: ACTIONS.USER_PHONE_NEW_SUCCESS,
|
||||
data: { phone },
|
||||
});
|
||||
};
|
||||
|
||||
const failure = error => {
|
||||
dispatch({
|
||||
type: ACTIONS.USER_PHONE_NEW_FAILURE,
|
||||
data: { error },
|
||||
});
|
||||
};
|
||||
|
||||
Lbryio.call('user_phone', 'new', { phone_number: phone, country_code: 1 }, 'post').then(
|
||||
success,
|
||||
failure
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export function doUserPhoneVerifyFailure(error) {
|
||||
return {
|
||||
type: ACTIONS.USER_PHONE_VERIFY_FAILURE,
|
||||
data: { error },
|
||||
};
|
||||
}
|
||||
|
||||
export function doUserPhoneVerify(verificationCode) {
|
||||
return (dispatch, getState) => {
|
||||
const phone_number = selectPhoneToVerify(getState());
|
||||
|
||||
dispatch({
|
||||
type: ACTIONS.USER_PHONE_VERIFY_STARTED,
|
||||
code: verificationCode,
|
||||
});
|
||||
|
||||
Lbryio.call(
|
||||
'user_phone',
|
||||
'confirm',
|
||||
{
|
||||
verification_code: verificationCode,
|
||||
phone_number,
|
||||
country_code: '1',
|
||||
},
|
||||
'post'
|
||||
)
|
||||
.then(userEmail => {
|
||||
dispatch({
|
||||
type: ACTIONS.USER_PHONE_VERIFY_SUCCESS,
|
||||
data: { phone_number },
|
||||
});
|
||||
dispatch(doUserFetch());
|
||||
})
|
||||
.catch(error => dispatch(doUserPhoneVerifyFailure(error)));
|
||||
};
|
||||
}
|
||||
|
||||
export function doUserEmailNew(email) {
|
||||
return dispatch => {
|
||||
dispatch({
|
||||
type: ACTIONS.USER_EMAIL_NEW_STARTED,
|
||||
|
@ -116,14 +181,14 @@ export function doUserFieldNew(email) {
|
|||
};
|
||||
}
|
||||
|
||||
export function doUserFieldVerifyFailure(error) {
|
||||
export function doUserEmailVerifyFailure(error) {
|
||||
return {
|
||||
type: ACTIONS.USER_EMAIL_VERIFY_FAILURE,
|
||||
data: { error },
|
||||
};
|
||||
}
|
||||
|
||||
export function doUserFieldVerify(verificationToken, recaptcha) {
|
||||
export function doUserEmailVerify(verificationToken, recaptcha) {
|
||||
return (dispatch, getState) => {
|
||||
const email = selectEmailToVerify(getState());
|
||||
|
||||
|
@ -154,7 +219,7 @@ export function doUserFieldVerify(verificationToken, recaptcha) {
|
|||
throw new Error('Your email is still not verified.'); // shouldn't happen
|
||||
}
|
||||
})
|
||||
.catch(error => dispatch(doUserFieldVerifyFailure(error)));
|
||||
.catch(error => dispatch(doUserEmailVerifyFailure(error)));
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -55,6 +55,52 @@ reducers[ACTIONS.USER_FETCH_FAILURE] = state =>
|
|||
user: null,
|
||||
});
|
||||
|
||||
reducers[ACTIONS.USER_PHONE_NEW_STARTED] = state =>
|
||||
Object.assign({}, state, {
|
||||
phoneNewIsPending: true,
|
||||
phoneNewErrorMessage: '',
|
||||
});
|
||||
|
||||
reducers[ACTIONS.USER_PHONE_NEW_SUCCESS] = (state, action) =>
|
||||
Object.assign({}, state, {
|
||||
phoneToVerify: action.data.phone,
|
||||
phoneNewIsPending: false,
|
||||
});
|
||||
|
||||
reducers[ACTIONS.USER_PHONE_NEW_EXISTS] = (state, action) =>
|
||||
Object.assign({}, state, {
|
||||
phoneToVerify: action.data.phone,
|
||||
phoneNewIsPending: false,
|
||||
});
|
||||
|
||||
reducers[ACTIONS.USER_PHONE_NEW_FAILURE] = (state, action) =>
|
||||
Object.assign({}, state, {
|
||||
phoneNewIsPending: false,
|
||||
phoneNewErrorMessage: action.data.error,
|
||||
});
|
||||
|
||||
reducers[ACTIONS.USER_PHONE_VERIFY_STARTED] = state =>
|
||||
Object.assign({}, state, {
|
||||
phoneVerifyIsPending: true,
|
||||
phoneVerifyErrorMessage: '',
|
||||
});
|
||||
|
||||
reducers[ACTIONS.USER_PHONE_VERIFY_SUCCESS] = (state, action) => {
|
||||
const user = Object.assign({}, state.user);
|
||||
user.phone_number = action.data.phone_number;
|
||||
return Object.assign({}, state, {
|
||||
phoneToVerify: '',
|
||||
phoneVerifyIsPending: false,
|
||||
user,
|
||||
});
|
||||
};
|
||||
|
||||
reducers[ACTIONS.USER_PHONE_VERIFY_FAILURE] = (state, action) =>
|
||||
Object.assign({}, state, {
|
||||
phoneVerifyIsPending: false,
|
||||
phoneVerifyErrorMessage: action.data.error,
|
||||
});
|
||||
|
||||
reducers[ACTIONS.USER_EMAIL_NEW_STARTED] = state =>
|
||||
Object.assign({}, state, {
|
||||
emailNewIsPending: true,
|
||||
|
|
|
@ -16,12 +16,23 @@ export const selectUserEmail = createSelector(
|
|||
user => (user ? user.primary_email : null)
|
||||
);
|
||||
|
||||
export const selectUserPhone = createSelector(
|
||||
selectUser,
|
||||
user => (user ? user.phone_number : null)
|
||||
);
|
||||
|
||||
export const selectEmailToVerify = createSelector(
|
||||
selectState,
|
||||
selectUserEmail,
|
||||
(state, userEmail) => state.emailToVerify || userEmail
|
||||
);
|
||||
|
||||
export const selectPhoneToVerify = createSelector(
|
||||
selectState,
|
||||
selectUserPhone,
|
||||
(state, userPhone) => state.phoneToVerify || userPhone
|
||||
);
|
||||
|
||||
export const selectUserIsRewardApproved = createSelector(
|
||||
selectUser,
|
||||
user => user && user.is_reward_approved
|
||||
|
|
Loading…
Add table
Reference in a new issue