#14: add captcha support for resend-activation

This commit is contained in:
SleepWalker 2016-07-31 16:53:16 +03:00
parent 4f7f1d2273
commit 4716c679b9
11 changed files with 153 additions and 16 deletions

View File

@ -103,9 +103,9 @@ export function activate({key = ''}) {
);
}
export function resendActivation({email = ''}) {
export function resendActivation({email = '', captcha}) {
return wrapInLoader((dispatch) =>
signup.resendActivation({email})
signup.resendActivation({email, captcha})
.then((resp) => {
dispatch(updateUser({
email

View File

@ -2,7 +2,7 @@ import React from 'react';
import { FormattedMessage as Message } from 'react-intl';
import { Input } from 'components/ui/form';
import { Input, Captcha } from 'components/ui/form';
import registerMessages from 'components/auth/register/Register.intl.json';
import BaseAuthBody from 'components/auth/BaseAuthBody';
@ -25,16 +25,16 @@ export default class ResendActivation extends BaseAuthBody {
<Message {...messages.specifyYourEmail} />
</div>
<div className={styles.formRow}>
<Input {...this.bindField('email')}
icon="envelope"
color="blue"
type="email"
required
placeholder={registerMessages.yourEmail}
defaultValue={this.context.user.email}
/>
</div>
<Input {...this.bindField('email')}
icon="envelope"
color="blue"
type="email"
required
placeholder={registerMessages.yourEmail}
defaultValue={this.context.user.email}
/>
<Captcha {...this.bindField('captcha')} />
</div>
);
}

View File

@ -0,0 +1,38 @@
import React, { PropTypes } from 'react';
import captcha from 'services/captcha';
import { skins, SKIN_DARK } from 'components/ui';
import styles from './form.scss';
import FormInputComponent from './FormInputComponent';
export default class Captcha extends FormInputComponent {
static displayName = 'Captcha';
static propTypes = {
skin: PropTypes.oneOf(skins)
};
static defaultProps = {
skin: SKIN_DARK
};
componentDidMount() {
captcha.render(this.el, {
skin: this.props.skin,
onSetCode: this.setCode
});
}
render() {
return (
<div ref={this.setEl} className={styles.captcha} />
);
}
getValue() {
return this.state && this.state.code;
}
setCode = (code) => this.setState({code});
}

View File

@ -295,6 +295,15 @@
}
}
.captcha {
width: 302px;
height: 76px;
// minimum captcha width is 302px, which can not be changed
// using transform to scale down to 296px
transform-origin: 0;
transform: scaleX(0.98);
}
/**
* Form validation
*/

View File

@ -5,6 +5,7 @@ import Button from './Button';
import Form from './Form';
import FormModel from './FormModel';
import Dropdown from './Dropdown';
import Captcha from './Captcha';
import FormError from './FormError';
export {
@ -15,5 +16,6 @@ export {
Form,
FormModel,
Dropdown,
Captcha,
FormError
};

View File

@ -1,6 +1,7 @@
import { routeActions } from 'react-router-redux';
import request from 'services/request';
import captcha from 'services/captcha';
import accounts from 'services/api/accounts';
import authentication from 'services/api/authentication';
import { setLocale } from 'components/i18n/actions';
@ -27,6 +28,9 @@ export function changeLang(lang) {
accounts.changeLang(lang);
}
// TODO: probably should be moved from here, because it is side effect
captcha.setLang(lang);
dispatch({
type: CHANGE_LANG,
payload: {

View File

@ -3,7 +3,7 @@ import { authenticate, changeLang } from 'components/user/actions';
/**
* Initializes User state with the fresh data
*
* @param {Object} store - redux store
* @param {object} store - redux store
*
* @return {Promise} a promise, that resolves in User state
*/

View File

@ -18,3 +18,25 @@ export function omit(obj, keys) {
return newObj;
}
/**
* Asynchronously loads script
*
* @param {string} src
*
* @return {Promise}
*/
export function loadScript(src) {
const script = document.createElement('script');
script.async = true;
script.defer = true;
script.src = src;
return new Promise((resolve, reject) => {
script.onlaod = resolve;
script.onerror = reject;
document.body.appendChild(script);
});
}

View File

@ -11,6 +11,9 @@ import { IntlProvider } from 'components/i18n';
import routesFactory from 'routes';
import storeFactory from 'storeFactory';
import bsodFactory from 'components/ui/bsod/factory';
import captcha from 'services/captcha';
captcha.setApiKey('6LdUZiYTAAAAAEjDGi9kEu0MRKYHYWskPFNXSYOV'); // TODO
const store = storeFactory();

View File

@ -22,10 +22,10 @@ export default {
);
},
resendActivation({email = ''}) {
resendActivation({email = '', captcha}) {
return request.post(
'/api/signup/repeat-message',
{email}
{email, captcha}
);
}
};

59
src/services/captcha.js Normal file
View File

@ -0,0 +1,59 @@
import { loadScript } from 'functions';
let readyPromise;
let lang = 'en';
let sitekey;
export default {
/**
* @param {DOMNode|string} el - dom node or id of element where to render captcha
* @param {string} options.skin - skin color (dark|light)
* @param {function} options.onSetCode - the callback, that will be called with
* captcha verification code, after user successfully solves captcha
*
* @return {Promise}
*/
render(el, {skin: theme, onSetCode: callback}) {
if (!sitekey) {
throw new Error('Site key is required to render captcha');
}
return loadApi().then(() =>
window.grecaptcha.render(el, {
sitekey,
theme,
callback
})
);
},
/**
* @param {stirng} newLang
*
* @see https://developers.google.com/recaptcha/docs/language
*/
setLang(newLang) {
lang = newLang;
},
/**
* @param {string} apiKey
*
* @see http://www.google.com/recaptcha/admin
*/
setApiKey(apiKey) {
sitekey = apiKey;
}
};
function loadApi() {
if (!readyPromise) {
readyPromise = new Promise((resolve) => {
window.onReCaptchaReady = resolve;
});
loadScript(`https://www.google.com/recaptcha/api.js?onload=onReCaptchaReady&render=explicit&hl=${lang}`);
}
return readyPromise;
}