#397: ensure, that recaptcha.render will be called with an existing el ref

This commit is contained in:
SleepWalker 2018-04-18 22:49:10 +03:00
parent cc50dab0e4
commit fde1d36287
6 changed files with 59 additions and 50 deletions

View File

@ -1,25 +1,21 @@
// @flow
import type { MessageDescriptor } from 'react-intl';
import React from 'react';
import classNames from 'classnames';
import buttons from 'components/ui/buttons.scss';
import { COLOR_GREEN } from 'components/ui';
import type { Color } from 'components/ui';
import FormComponent from './FormComponent';
import type { Color } from 'components/ui';
export default class Button extends FormComponent {
props: {
label: string | {id: string},
block: bool,
small: bool,
loading: bool,
className: string,
color: Color
};
export default class Button extends FormComponent<{
label: string | MessageDescriptor,
block: bool,
small: bool,
loading: bool,
className: string,
color: Color
}> {
static defaultProps = {
color: COLOR_GREEN
};

View File

@ -1,35 +1,41 @@
import PropTypes from 'prop-types';
// @flow
import React from 'react';
import classNames from 'classnames';
import type { CaptchaID } from 'services/captcha';
import type { Skin } from 'components/ui';
import captcha from 'services/captcha';
import logger from 'services/logger';
import { skins, SKIN_DARK } from 'components/ui';
import { ComponentLoader } from 'components/ui/loader';
import styles from './form.scss';
import FormInputComponent from './FormInputComponent';
export default class Captcha extends FormInputComponent {
static displayName = 'Captcha';
static propTypes = {
skin: PropTypes.oneOf(skins),
delay: PropTypes.number
};
export default class Captcha extends FormInputComponent<{
delay: number,
skin: Skin,
}, {
code: string,
}> {
el: ?HTMLDivElement;
captchaId: CaptchaID;
static defaultProps = {
skin: SKIN_DARK,
skin: 'dark',
delay: 0
};
componentDidMount() {
setTimeout(() => {
captcha.render(this.el, {
this.el && captcha.render(this.el, {
skin: this.props.skin,
onSetCode: this.setCode
}).then((captchaId) => this.captchaId = captchaId, (error) => logger.error('Error rendering captcha', { error }));
})
.then((captchaId) => {this.captchaId = captchaId;})
.catch((error) => {
logger.error('Failed rendering captcha', {
error
});
});
}, this.props.delay);
}
@ -64,5 +70,5 @@ export default class Captcha extends FormInputComponent {
this.reset();
}
setCode = (code) => this.setState({code});
setCode = (code: string) => this.setState({code});
}

View File

@ -1,10 +1,9 @@
// @flow
import type { MessageDescriptor } from 'react-intl';
import { Component } from 'react';
import { intlShape } from 'react-intl';
export default class FormComponent extends Component {
static displayName = 'FormComponent';
export default class FormComponent<P, S = void> extends Component<P, S> {
static contextTypes = {
intl: intlShape.isRequired
};
@ -16,7 +15,7 @@ export default class FormComponent extends Component {
*
* @return {string}
*/
formatMessage(message) {
formatMessage(message: string | MessageDescriptor) {
if (message && message.id && this.context && this.context.intl) {
message = this.context.intl.formatMessage(message);
}

View File

@ -1,15 +1,18 @@
import PropTypes from 'prop-types';
// @flow
import type { MessageDescriptor } from 'react-intl';
import React from 'react';
import FormComponent from './FormComponent';
import FormError from './FormError';
export default class FormInputComponent extends FormComponent {
static displayName = 'FormInputComponent';
type Error = string | MessageDescriptor;
static propTypes = {
error: PropTypes.string
};
export default class FormInputComponent<P, S = void> extends FormComponent<P & {
error?: Error,
}, S & {
error?: Error,
}> {
el: ?HTMLDivElement;
componentWillReceiveProps() {
if (this.state && this.state.error) {
@ -19,7 +22,7 @@ export default class FormInputComponent extends FormComponent {
}
}
setEl = (el) => {
setEl = (el: ?HTMLDivElement) => {
this.el = el;
};
@ -29,7 +32,7 @@ export default class FormInputComponent extends FormComponent {
return <FormError error={error} />;
}
setError(error) {
setError(error: Error) {
this.setState({error});
}
}

View File

@ -32,7 +32,7 @@ export default class FormModel {
const props: Object = {
name,
ref: (el: ?FormInputComponent) => {
ref: (el: ?FormInputComponent<any>) => {
if (el) {
if (!(el instanceof FormInputComponent)) {
throw new Error('Expected FormInputComponent component');

View File

@ -1,3 +1,4 @@
// @flow
import { loadScript } from 'functions';
import options from 'services/api/options';
@ -5,6 +6,8 @@ let readyPromise;
let lang = 'en';
let sitekey;
export opaque type CaptchaID = string;
export default {
/**
* @param {DOMNode|string} el - dom node or id of element where to render captcha
@ -14,7 +17,10 @@ export default {
*
* @return {Promise} - resolves to captchaId
*/
render(el, {skin: theme, onSetCode: callback}) {
render(el: HTMLElement, {skin: theme, onSetCode: callback}: {
skin: 'dark' | 'light',
onSetCode: (string) => void,
}): Promise<CaptchaID> {
return this.loadApi().then(() =>
window.grecaptcha.render(el, {
sitekey,
@ -27,7 +33,7 @@ export default {
/**
* @param {string} captchaId - captcha id, returned from render promise
*/
reset(captchaId) {
reset(captchaId: CaptchaID) {
this.loadApi().then(() => window.grecaptcha.reset(captchaId));
},
@ -36,7 +42,7 @@ export default {
*
* @see https://developers.google.com/recaptcha/docs/language
*/
setLang(newLang) {
setLang(newLang: string) {
lang = newLang;
},
@ -45,7 +51,7 @@ export default {
*
* @see http://www.google.com/recaptcha/admin
*/
setApiKey(apiKey) {
setApiKey(apiKey: string) {
sitekey = apiKey;
},
@ -54,14 +60,14 @@ export default {
*
* @return {Promise}
*/
loadApi() {
loadApi(): Promise<void> {
if (!readyPromise) {
readyPromise = Promise.all([
new Promise((resolve) => {
window.onReCaptchaReady = resolve;
}),
options.get().then((resp) => this.setApiKey(resp.reCaptchaPublicKey))
]);
]).then(() => {});
loadScript(`https://recaptcha.net/recaptcha/api.js?onload=onReCaptchaReady&render=explicit&hl=${lang}`);
}
@ -69,4 +75,3 @@ export default {
return readyPromise;
}
};