feat: auto email confirmation #2169
12 changed files with 84 additions and 114 deletions
|
@ -52,7 +52,7 @@
|
|||
"hast-util-sanitize": "^1.1.2",
|
||||
"keytar": "^4.2.1",
|
||||
"lbry-redux": "lbryio/lbry-redux#820b6eeadf9e58c1e5027fd4d92808ba817b410e",
|
||||
"lbryinc": "lbryio/lbryinc#68c8ce6b7608dabc9180fff9b4841d10e1c62284",
|
||||
"lbryinc": "lbryio/lbryinc#b3bb8c67745235ef54baea95accaedaa4bb86d4d",
|
||||
"localforage": "^1.7.1",
|
||||
"mammoth": "^1.4.6",
|
||||
"mime": "^2.3.1",
|
||||
|
|
|
@ -69,12 +69,7 @@ class UserEmailNew extends React.PureComponent<Props, State> {
|
|||
<div className="card__actions">
|
||||
<Submit label="Submit" disabled={isPending} />
|
||||
{cancelButton}
|
||||
<br /><br />
|
||||
</div>
|
||||
|
||||
<p className="card__content help">
|
||||
{`${__('Your email may be used to sync usage data across devices.')} `}
|
||||
</p>
|
||||
</Form>
|
||||
</span>
|
||||
);
|
||||
|
|
|
@ -1,24 +1,22 @@
|
|||
import { connect } from 'react-redux';
|
||||
import {
|
||||
selectEmailVerifyIsPending,
|
||||
selectEmailToVerify,
|
||||
selectEmailVerifyErrorMessage,
|
||||
doUserEmailVerify,
|
||||
doUserEmailVerifyFailure,
|
||||
doUserResendVerificationEmail,
|
||||
doUserCheckEmailVerified,
|
||||
selectUser,
|
||||
} from 'lbryinc';
|
||||
import { doNavigate } from 'redux/actions/navigation';
|
||||
import UserEmailVerify from './view';
|
||||
|
||||
const select = state => ({
|
||||
isPending: selectEmailVerifyIsPending(state),
|
||||
email: selectEmailToVerify(state),
|
||||
errorMessage: selectEmailVerifyErrorMessage(state),
|
||||
user: selectUser(state),
|
||||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
verifyUserEmail: (code, recaptcha) => dispatch(doUserEmailVerify(code, recaptcha)),
|
||||
verifyUserEmailFailure: error => dispatch(doUserEmailVerifyFailure(error)),
|
||||
resendVerificationEmail: email => dispatch(doUserResendVerificationEmail(email)),
|
||||
checkEmailVerified: () => dispatch(doUserCheckEmailVerified()),
|
||||
navigate: path => dispatch(doNavigate(path)),
|
||||
});
|
||||
|
||||
export default connect(
|
||||
|
|
|
@ -1,48 +1,37 @@
|
|||
// @flow
|
||||
import * as React from 'react';
|
||||
import Button from 'component/button';
|
||||
import { Form, FormField, FormRow, Submit } from 'component/common/form';
|
||||
|
||||
type Props = {
|
||||
cancelButton: React.Node,
|
||||
errorMessage: ?string,
|
||||
email: string,
|
||||
isPending: boolean,
|
||||
onModal?: boolean,
|
||||
verifyUserEmail: (string, string) => void,
|
||||
verifyUserEmailFailure: string => void,
|
||||
resendVerificationEmail: string => void,
|
||||
checkEmailVerified: () => void,
|
||||
user: {
|
||||
has_verified_email: boolean,
|
||||
},
|
||||
};
|
||||
|
||||
type State = {
|
||||
code: string,
|
||||
};
|
||||
|
||||
class UserEmailVerify extends React.PureComponent<Props, State> {
|
||||
class UserEmailVerify extends React.PureComponent<Props> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
code: '',
|
||||
};
|
||||
|
||||
(this: any).handleSubmit = this.handleSubmit.bind(this);
|
||||
this.emailVerifyCheckInterval = null;
|
||||
(this: any).handleResendVerificationEmail = this.handleResendVerificationEmail.bind(this);
|
||||
}
|
||||
|
||||
handleCodeChanged(event: SyntheticInputEvent<*>) {
|
||||
this.setState({
|
||||
code: String(event.target.value).trim(),
|
||||
});
|
||||
componentDidMount() {
|
||||
this.emailVerifyCheckInterval = setInterval(() => this.checkIfVerified(), 5000);
|
||||
}
|
||||
|
||||
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');
|
||||
componentDidUpdate() {
|
||||
if (this.emailVerifyCheckInterval && this.props.user.has_verified_email) {
|
||||
clearInterval(this.emailVerifyCheckInterval);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.emailVerifyCheckInterval) {
|
||||
clearInterval(this.emailVerifyCheckInterval);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -50,55 +39,38 @@ class UserEmailVerify extends React.PureComponent<Props, State> {
|
|||
this.props.resendVerificationEmail(this.props.email);
|
||||
}
|
||||
|
||||
checkIfVerified() {
|
||||
const { checkEmailVerified } = this.props;
|
||||
checkEmailVerified();
|
||||
Yeah I figured that would be less hammering on the server. I agree. Yeah I figured that would be less hammering on the server. I agree.
|
||||
}
|
||||
|
||||
emailVerifyCheckInterval: ?IntervalID;
|
||||
|
||||
render() {
|
||||
const { cancelButton, errorMessage, email, isPending, onModal } = this.props;
|
||||
const { cancelButton, email } = this.props;
|
||||
|
||||
return (
|
||||
<span>
|
||||
<p>Please enter the verification code emailed to {email}.</p>
|
||||
<div>
|
||||
<p>
|
||||
{__('An email was sent to')} {email}.{' '}
|
||||
{__('Follow the link and you will be good to go. This will update automatically.')}
|
||||
</p>
|
||||
|
||||
<Form onSubmit={this.handleSubmit}>
|
||||
<FormRow padded>
|
||||
<FormField
|
||||
stretch
|
||||
name="code"
|
||||
type="text"
|
||||
placeholder="eyJyZWNhcHRjaGEiOiIw..."
|
||||
label={__('Verification Code')}
|
||||
error={errorMessage}
|
||||
value={this.state.code}
|
||||
onChange={event => this.handleCodeChanged(event)}
|
||||
/>
|
||||
</FormRow>
|
||||
<div className="card__actions">
|
||||
<Button
|
||||
button="primary"
|
||||
label={__('Resend verification email')}
|
||||
onClick={this.handleResendVerificationEmail}
|
||||
/>
|
||||
{cancelButton}
|
||||
</div>
|
||||
|
||||
<div className="card__actions">
|
||||
<Submit label={__('Verify')} disabled={isPending} />
|
||||
{cancelButton}
|
||||
{!onModal && (
|
||||
<Button
|
||||
button="link"
|
||||
label={__('Resend verification email')}
|
||||
onClick={this.handleResendVerificationEmail}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<p className="help">
|
||||
{__('Email')} <Button button="link" href="mailto:help@lbry.io" label="help@lbry.io" />{' '}
|
||||
or join our <Button button="link" href="https://chat.lbry.io" label="chat" />{' '}
|
||||
{__('if you encounter any trouble with your code.')}
|
||||
</p>
|
||||
{onModal && (
|
||||
<div className="card__actions help">
|
||||
<Button
|
||||
button="link"
|
||||
label={__('Resend verification email')}
|
||||
onClick={this.handleResendVerificationEmail}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Form>
|
||||
</span>
|
||||
<p className="help">
|
||||
{__('Email')} <Button button="link" href="mailto:help@lbry.io" label="help@lbry.io" /> or
|
||||
join our <Button button="link" href="https://chat.lbry.io" label="chat" />{' '}
|
||||
{__('if you encounter any trouble verifying.')}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ import {
|
|||
import { doToast, doBlackListedOutpointsSubscribe, isURIValid } from 'lbry-redux';
|
||||
import { doNavigate } from 'redux/actions/navigation';
|
||||
import { doDownloadLanguages, doUpdateIsNightAsync } from 'redux/actions/settings';
|
||||
import { doUserEmailVerify, doAuthenticate, Lbryio, rewards } from 'lbryinc';
|
||||
import { doAuthenticate, Lbryio, rewards } from 'lbryinc';
|
||||
import 'scss/all.scss';
|
||||
import store from 'store';
|
||||
import pjson from 'package.json';
|
||||
|
@ -33,6 +33,10 @@ const APPPAGEURL = 'lbry://?';
|
|||
|
||||
autoUpdater.logger = remote.require('electron-log');
|
||||
|
||||
if (process.env.LBRY_API_URL) {
|
||||
Lbryio.setLocalApi(process.env.LBRY_API_URL);
|
||||
}
|
||||
|
||||
// We need to override Lbryio for getting/setting the authToken
|
||||
// We interect with ipcRenderer to get the auth key from a users keyring
|
||||
// We keep a local variable for authToken beacuse `ipcRenderer.send` does not
|
||||
|
@ -95,23 +99,8 @@ rewards.setCallback('claimRewardSuccess', () => {
|
|||
|
||||
ipcRenderer.on('open-uri-requested', (event, uri, newSession) => {
|
||||
if (uri && uri.startsWith('lbry://')) {
|
||||
if (uri.startsWith('lbry://?verify=')) {
|
||||
let verification = {};
|
||||
try {
|
||||
verification = JSON.parse(atob(uri.substring(15)));
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
if (verification.token && verification.recaptcha) {
|
||||
app.store.dispatch(doConditionalAuthNavigate(newSession));
|
||||
app.store.dispatch(doUserEmailVerify(verification.token, verification.recaptcha));
|
||||
} else {
|
||||
app.store.dispatch(
|
||||
doToast({
|
||||
message: 'Invalid Verification URI',
|
||||
})
|
||||
);
|
||||
}
|
||||
if (uri.startsWith('lbry://?verify')) {
|
||||
app.store.dispatch(doConditionalAuthNavigate(newSession));
|
||||
} else if (uri.startsWith(APPPAGEURL)) {
|
||||
const navpage = uri.replace(APPPAGEURL, '').toLowerCase();
|
||||
app.store.dispatch(doNavigate(`/${navpage}`));
|
||||
|
|
|
@ -67,7 +67,9 @@ export class Modal extends React.PureComponent<ModalProps> {
|
|||
![null, undefined, ''].includes(overlayClassName) ? overlayClassName : 'modal-overlay'
|
||||
}
|
||||
>
|
||||
<h1 className="card__title">{title}</h1>
|
||||
<header className="card__header">
|
||||
<h2 className="card__title">{title}</h2>
|
||||
</header>
|
||||
<div className="card__content">{children}</div>
|
||||
{type === 'custom' ? null : ( // custom modals define their own buttons
|
||||
<div className="card__actions">
|
||||
|
|
|
@ -16,7 +16,7 @@ class ModalAffirmPurchase extends React.PureComponent<Props> {
|
|||
constructor() {
|
||||
super();
|
||||
|
||||
this.onAffirmPurchase = this.onAffirmPurchase.bind(this);
|
||||
(this: any).onAffirmPurchase = this.onAffirmPurchase.bind(this);
|
||||
}
|
||||
|
||||
onAffirmPurchase() {
|
||||
|
|
|
@ -9,8 +9,12 @@ type Props = {
|
|||
declineAutoUpdate: () => any,
|
||||
};
|
||||
|
||||
class ModalAutoUpdateDownloaded extends React.PureComponent<Props> {
|
||||
constructor(props: ModalProps) {
|
||||
type State = {
|
||||
disabled: boolean,
|
||||
};
|
||||
|
||||
class ModalAutoUpdateDownloaded extends React.PureComponent<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
|
|
|
@ -12,6 +12,15 @@ type Props = {
|
|||
};
|
||||
|
||||
class ModalEmailCollection extends React.PureComponent<Props> {
|
||||
getTitle() {
|
||||
const { user } = this.props;
|
||||
if (user && !user.has_verified_email) {
|
||||
return __('Awaiting Confirmation');
|
||||
}
|
||||
|
||||
return __('Can We Stay In Touch?');
|
||||
}
|
||||
|
||||
renderInner() {
|
||||
const { closeModal, email, user } = this.props;
|
||||
|
||||
|
@ -34,7 +43,7 @@ class ModalEmailCollection extends React.PureComponent<Props> {
|
|||
}
|
||||
|
||||
return (
|
||||
<Modal type="custom" isOpen contentLabel="Email" title={__('Can We Stay In Touch?')}>
|
||||
<Modal type="custom" isOpen contentLabel="Email" title={this.getTitle()}>
|
||||
<section className="card__content">{this.renderInner()}</section>
|
||||
</Modal>
|
||||
);
|
||||
|
|
|
@ -31,8 +31,8 @@ import ModalWalletUnlock from 'modal/modalWalletUnlock';
|
|||
import ModalRewardCode from 'modal/modalRewardCode';
|
||||
|
||||
type Props = {
|
||||
notification: { id: string },
|
||||
notificationProps: {},
|
||||
modal: { id: string, modalProps: {} },
|
||||
error: string | { message: string },
|
||||
};
|
||||
|
||||
class ModalRouter extends React.PureComponent<Props> {
|
||||
|
@ -80,6 +80,7 @@ class ModalRouter extends React.PureComponent<Props> {
|
|||
|
||||
checkShowWelcome(props) {
|
||||
const { isWelcomeAcknowledged, user } = props;
|
||||
|
||||
if (!isWelcomeAcknowledged && user && !user.is_reward_approved && !user.is_identity_verified) {
|
||||
return MODALS.WELCOME;
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ class AuthPage extends React.PureComponent<Props> {
|
|||
if (isPending || (user && !user.has_verified_email && !email)) {
|
||||
return __('Human Proofing');
|
||||
} else if (user && !user.has_verified_email) {
|
||||
return __('Confirm Email');
|
||||
return __('Awaiting Confirmation');
|
||||
} else if (user && !user.is_identity_verified && !user.is_reward_approved) {
|
||||
return __('Final Verification');
|
||||
}
|
||||
|
|
|
@ -5683,9 +5683,9 @@ lbry-redux@lbryio/lbry-redux#84b7d396934d57a37802aadbef71db91230a9404:
|
|||
reselect "^3.0.0"
|
||||
uuid "^3.3.2"
|
||||
|
||||
lbryinc@lbryio/lbryinc#68c8ce6b7608dabc9180fff9b4841d10e1c62284:
|
||||
lbryinc@lbryio/lbryinc#cee70d482cf352f2e66215fa109f4178406228ca:
|
||||
version "0.0.1"
|
||||
resolved "https://codeload.github.com/lbryio/lbryinc/tar.gz/68c8ce6b7608dabc9180fff9b4841d10e1c62284"
|
||||
resolved "https://codeload.github.com/lbryio/lbryinc/tar.gz/cee70d482cf352f2e66215fa109f4178406228ca"
|
||||
dependencies:
|
||||
lbry-redux lbryio/lbry-redux#84b7d396934d57a37802aadbef71db91230a9404
|
||||
reselect "^3.0.0"
|
||||
|
|
Loading…
Reference in a new issue
Five seconds? Seems a bit long, we should look into doing this with websockets instead of polling.