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

View File

@ -1,35 +1,41 @@
import PropTypes from 'prop-types'; // @flow
import React from 'react'; import React from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import type { CaptchaID } from 'services/captcha';
import type { Skin } from 'components/ui';
import captcha from 'services/captcha'; import captcha from 'services/captcha';
import logger from 'services/logger'; import logger from 'services/logger';
import { skins, SKIN_DARK } from 'components/ui';
import { ComponentLoader } from 'components/ui/loader'; import { ComponentLoader } from 'components/ui/loader';
import styles from './form.scss'; import styles from './form.scss';
import FormInputComponent from './FormInputComponent'; import FormInputComponent from './FormInputComponent';
export default class Captcha extends FormInputComponent { export default class Captcha extends FormInputComponent<{
static displayName = 'Captcha'; delay: number,
skin: Skin,
static propTypes = { }, {
skin: PropTypes.oneOf(skins), code: string,
delay: PropTypes.number }> {
}; el: ?HTMLDivElement;
captchaId: CaptchaID;
static defaultProps = { static defaultProps = {
skin: SKIN_DARK, skin: 'dark',
delay: 0 delay: 0
}; };
componentDidMount() { componentDidMount() {
setTimeout(() => { setTimeout(() => {
captcha.render(this.el, { this.el && captcha.render(this.el, {
skin: this.props.skin, skin: this.props.skin,
onSetCode: this.setCode 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); }, this.props.delay);
} }
@ -64,5 +70,5 @@ export default class Captcha extends FormInputComponent {
this.reset(); 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 { Component } from 'react';
import { intlShape } from 'react-intl'; import { intlShape } from 'react-intl';
export default class FormComponent extends Component { export default class FormComponent<P, S = void> extends Component<P, S> {
static displayName = 'FormComponent';
static contextTypes = { static contextTypes = {
intl: intlShape.isRequired intl: intlShape.isRequired
}; };
@ -16,7 +15,7 @@ export default class FormComponent extends Component {
* *
* @return {string} * @return {string}
*/ */
formatMessage(message) { formatMessage(message: string | MessageDescriptor) {
if (message && message.id && this.context && this.context.intl) { if (message && message.id && this.context && this.context.intl) {
message = this.context.intl.formatMessage(message); 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 React from 'react';
import FormComponent from './FormComponent'; import FormComponent from './FormComponent';
import FormError from './FormError'; import FormError from './FormError';
export default class FormInputComponent extends FormComponent { type Error = string | MessageDescriptor;
static displayName = 'FormInputComponent';
static propTypes = { export default class FormInputComponent<P, S = void> extends FormComponent<P & {
error: PropTypes.string error?: Error,
}; }, S & {
error?: Error,
}> {
el: ?HTMLDivElement;
componentWillReceiveProps() { componentWillReceiveProps() {
if (this.state && this.state.error) { if (this.state && this.state.error) {
@ -19,7 +22,7 @@ export default class FormInputComponent extends FormComponent {
} }
} }
setEl = (el) => { setEl = (el: ?HTMLDivElement) => {
this.el = el; this.el = el;
}; };
@ -29,7 +32,7 @@ export default class FormInputComponent extends FormComponent {
return <FormError error={error} />; return <FormError error={error} />;
} }
setError(error) { setError(error: Error) {
this.setState({error}); this.setState({error});
} }
} }

View File

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

View File

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