mirror of
https://github.com/elyby/accounts-frontend.git
synced 2024-12-29 00:10:54 +05:30
Merge remote-tracking branch 'origin/develop' into 22-oauth-app-managment
This commit is contained in:
commit
bd77abcdd8
@ -1,9 +1,8 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { FormattedMessage as Message } from 'react-intl';
|
import { FormattedMessage as Message } from 'react-intl';
|
||||||
|
|
||||||
import { IntlProvider } from 'components/i18n';
|
import { IntlProvider } from 'components/i18n';
|
||||||
|
import logger from 'services/logger';
|
||||||
import appInfo from 'components/auth/appInfo/AppInfo.intl.json';
|
import appInfo from 'components/auth/appInfo/AppInfo.intl.json';
|
||||||
|
|
||||||
import styles from './styles.scss';
|
import styles from './styles.scss';
|
||||||
@ -12,32 +11,64 @@ import messages from './BSoD.intl.json';
|
|||||||
|
|
||||||
// TODO: probably it is better to render this view from the App view
|
// TODO: probably it is better to render this view from the App view
|
||||||
// to remove dependencies from store and IntlProvider
|
// to remove dependencies from store and IntlProvider
|
||||||
export default function BSoD({store}: {store: *}) {
|
export default class BSoD extends React.Component<{
|
||||||
return (
|
store: Object
|
||||||
<IntlProvider store={store}>
|
}, {
|
||||||
<div className={styles.body}>
|
lastEventId?: string,
|
||||||
<canvas className={styles.canvas}
|
}> {
|
||||||
ref={(el: ?HTMLCanvasElement) => el && new BoxesField(el)}
|
state = {};
|
||||||
/>
|
|
||||||
|
|
||||||
<div className={styles.wrapper}>
|
componentDidMount() {
|
||||||
<div className={styles.title}>
|
// poll for event id
|
||||||
<Message {...appInfo.appName} />
|
const timer = setInterval(() => {
|
||||||
</div>
|
if (!logger.getLastEventId()) {
|
||||||
<div className={styles.lineWithMargin}>
|
return;
|
||||||
<Message {...messages.criticalErrorHappened} />
|
}
|
||||||
</div>
|
|
||||||
<div className={styles.line}>
|
clearInterval(timer);
|
||||||
<Message {...messages.reloadPageOrContactUs} />
|
|
||||||
</div>
|
this.setState({
|
||||||
<a href="mailto:support@ely.by" className={styles.support}>
|
lastEventId: logger.getLastEventId()
|
||||||
support@ely.by
|
});
|
||||||
</a>
|
}, 500);
|
||||||
<div className={styles.easterEgg}>
|
}
|
||||||
<Message {...messages.alsoYouCanInteractWithBackground}/>
|
|
||||||
|
render() {
|
||||||
|
const {store} = this.props;
|
||||||
|
const {lastEventId} = this.state;
|
||||||
|
|
||||||
|
let emailUrl = 'mailto:support@ely.by';
|
||||||
|
|
||||||
|
if (lastEventId) {
|
||||||
|
emailUrl += `?subject=Bug report for #${lastEventId}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IntlProvider store={store}>
|
||||||
|
<div className={styles.body}>
|
||||||
|
<canvas className={styles.canvas}
|
||||||
|
ref={(el: ?HTMLCanvasElement) => el && new BoxesField(el)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className={styles.wrapper}>
|
||||||
|
<div className={styles.title}>
|
||||||
|
<Message {...appInfo.appName} />
|
||||||
|
</div>
|
||||||
|
<div className={styles.lineWithMargin}>
|
||||||
|
<Message {...messages.criticalErrorHappened} />
|
||||||
|
</div>
|
||||||
|
<div className={styles.line}>
|
||||||
|
<Message {...messages.reloadPageOrContactUs} />
|
||||||
|
</div>
|
||||||
|
<a href={emailUrl} className={styles.support}>
|
||||||
|
support@ely.by
|
||||||
|
</a>
|
||||||
|
<div className={styles.easterEgg}>
|
||||||
|
<Message {...messages.alsoYouCanInteractWithBackground}/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</IntlProvider>
|
||||||
</IntlProvider>
|
);
|
||||||
);
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,27 +1,24 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import React from 'react';
|
import type { MessageDescriptor } from 'react-intl';
|
||||||
import type { ComponentType } from 'react';
|
import type { ComponentType } from 'react';
|
||||||
|
import type { Color } from 'components/ui';
|
||||||
|
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 FormComponent from './FormComponent';
|
import FormComponent from './FormComponent';
|
||||||
|
|
||||||
import type { Color } from 'components/ui';
|
|
||||||
import type { MessageDescriptor } from 'react-intl';
|
|
||||||
|
|
||||||
export default class Button extends FormComponent<{
|
export default class Button extends FormComponent<{
|
||||||
label: string | MessageDescriptor,
|
label: string | MessageDescriptor,
|
||||||
block?: bool,
|
block?: bool,
|
||||||
small?: bool,
|
small?: bool,
|
||||||
loading?: bool,
|
loading?: bool,
|
||||||
className?: string,
|
className?: string,
|
||||||
color?: Color,
|
color: Color,
|
||||||
disabled?: bool,
|
disabled?: bool,
|
||||||
component?: string | ComponentType<any>,
|
component: string | ComponentType<any>,
|
||||||
} | HTMLButtonElement> {
|
}> {
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
color: COLOR_GREEN,
|
color: COLOR_GREEN,
|
||||||
component: 'button',
|
component: 'button',
|
||||||
|
@ -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});
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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');
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
// @flow
|
||||||
|
import type {User} from 'components/user';
|
||||||
import Raven from 'raven-js';
|
import Raven from 'raven-js';
|
||||||
|
|
||||||
import abbreviate from './abbreviate';
|
import abbreviate from './abbreviate';
|
||||||
@ -5,8 +7,8 @@ import abbreviate from './abbreviate';
|
|||||||
const isTest = process.env.__TEST__; // eslint-disable-line
|
const isTest = process.env.__TEST__; // eslint-disable-line
|
||||||
const isProduction = process.env.__PROD__; // eslint-disable-line
|
const isProduction = process.env.__PROD__; // eslint-disable-line
|
||||||
|
|
||||||
const logger = {
|
class Logger {
|
||||||
init({sentryCdn}) {
|
init({ sentryCdn }: { sentryCdn: string }) {
|
||||||
if (sentryCdn) {
|
if (sentryCdn) {
|
||||||
Raven.config(sentryCdn, {
|
Raven.config(sentryCdn, {
|
||||||
logger: 'accounts-js-app',
|
logger: 'accounts-js-app',
|
||||||
@ -37,54 +39,67 @@ const logger = {
|
|||||||
message = '';
|
message = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info(`Unhandled rejection${message}`, {
|
this.info(`Unhandled rejection${message}`, {
|
||||||
error,
|
error,
|
||||||
event
|
event
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
setUser(user) {
|
setUser(user: User) {
|
||||||
Raven.setUserContext({
|
Raven.setUserContext({
|
||||||
username: user.username,
|
username: user.username,
|
||||||
email: user.email,
|
email: user.email,
|
||||||
id: user.id
|
id: user.id
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
[
|
error(message: string | Error, context: Object) {
|
||||||
// 'fatal',
|
log('error', message, context);
|
||||||
'error',
|
}
|
||||||
'warning',
|
|
||||||
'info',
|
|
||||||
'debug'
|
|
||||||
].forEach((level) => {
|
|
||||||
const method = level === 'warning' ? 'warn' : level;
|
|
||||||
|
|
||||||
logger[method] = (message, context) => {
|
info(message: string | Error, context: Object) {
|
||||||
if (isTest) {
|
log('info', message, context);
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof context !== 'object') {
|
warn(message: string | Error, context: Object) {
|
||||||
// it would better to always have an object here
|
log('warning', message, context);
|
||||||
context = {
|
}
|
||||||
message: context
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
prepareContext(context).then((context) => {
|
getLastEventId(): string | void {
|
||||||
console[method](message, context); // eslint-disable-line
|
return Raven.lastEventId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Raven.captureException(message, {
|
function log(
|
||||||
level,
|
level: 'error' | 'warning' | 'info' | 'debug',
|
||||||
extra: context
|
message: string | Error,
|
||||||
});
|
context: Object
|
||||||
|
) {
|
||||||
|
const method: 'error' | 'warn' | 'info' | 'debug' = level === 'warning' ? 'warn' : level;
|
||||||
|
|
||||||
|
if (isTest) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof context !== 'object') {
|
||||||
|
// it would better to always have an object here
|
||||||
|
context = {
|
||||||
|
message: context
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
prepareContext(context).then((context) => {
|
||||||
|
console[method](message, context); // eslint-disable-line
|
||||||
|
|
||||||
|
Raven.captureException(message, {
|
||||||
|
level,
|
||||||
|
extra: context,
|
||||||
|
...(typeof message === 'string' ? { fingerprint: [message] } : {}),
|
||||||
});
|
});
|
||||||
};
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* prepare data for JSON.stringify
|
* prepare data for JSON.stringify
|
||||||
@ -93,7 +108,7 @@ const logger = {
|
|||||||
*
|
*
|
||||||
* @return {Promise}
|
* @return {Promise}
|
||||||
*/
|
*/
|
||||||
function prepareContext(context) {
|
function prepareContext(context: any) {
|
||||||
if (context instanceof Response) {
|
if (context instanceof Response) {
|
||||||
// TODO: rewrite abbreviate to use promises and recursively find Response
|
// TODO: rewrite abbreviate to use promises and recursively find Response
|
||||||
return context.json()
|
return context.json()
|
||||||
@ -120,4 +135,4 @@ function prepareContext(context) {
|
|||||||
return Promise.resolve(abbreviate(context));
|
return Promise.resolve(abbreviate(context));
|
||||||
}
|
}
|
||||||
|
|
||||||
export default logger;
|
export default new Logger();
|
||||||
|
Loading…
Reference in New Issue
Block a user