mirror of
https://github.com/elyby/accounts-frontend.git
synced 2024-12-23 05:29:56 +05:30
Change prettier rules
This commit is contained in:
parent
73f0c37a6a
commit
f85b9d8d35
10
.eslintrc.js
10
.eslintrc.js
@ -65,10 +65,7 @@ module.exports = {
|
||||
'sessionStorage', // we have our own localStorage module
|
||||
'event',
|
||||
],
|
||||
'id-length': [
|
||||
'error',
|
||||
{ min: 2, exceptions: ['x', 'y', 'i', 'k', 'l', 'm', 'n', '$', '_'] },
|
||||
],
|
||||
'id-length': ['error', { min: 2, exceptions: ['x', 'y', 'i', 'k', 'l', 'm', 'n', '$', '_'] }],
|
||||
'guard-for-in': ['error'],
|
||||
'no-var': ['error'],
|
||||
'prefer-const': ['error'],
|
||||
@ -124,10 +121,7 @@ module.exports = {
|
||||
'react/jsx-boolean-value': 'warn',
|
||||
'react/jsx-closing-bracket-location': 'off', // can not configure for our code style
|
||||
'react/jsx-curly-spacing': 'warn',
|
||||
'react/jsx-handler-names': [
|
||||
'warn',
|
||||
{ eventHandlerPrefix: 'on', eventHandlerPropPrefix: 'on' },
|
||||
],
|
||||
'react/jsx-handler-names': ['warn', { eventHandlerPrefix: 'on', eventHandlerPropPrefix: 'on' }],
|
||||
'react/jsx-key': 'warn',
|
||||
'react/jsx-max-props-per-line': 'off',
|
||||
'react/jsx-no-bind': 'off',
|
||||
|
@ -2,5 +2,7 @@
|
||||
"trailingComma": "all",
|
||||
"singleQuote": true,
|
||||
"proseWrap": "always",
|
||||
"endOfLine": "lf"
|
||||
"endOfLine": "lf",
|
||||
"tabWidth": 4,
|
||||
"printWidth": 120
|
||||
}
|
||||
|
@ -2,10 +2,7 @@ import React from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { Channel } from '@storybook/channels';
|
||||
import { setIntlConfig } from 'storybook-addon-intl';
|
||||
import {
|
||||
EVENT_SET_LOCALE_ID,
|
||||
EVENT_GET_LOCALE_ID,
|
||||
} from 'storybook-addon-intl/dist/shared';
|
||||
import { EVENT_SET_LOCALE_ID, EVENT_GET_LOCALE_ID } from 'storybook-addon-intl/dist/shared';
|
||||
import { SUPPORTED_LANGUAGES, DEFAULT_LANGUAGE } from 'app/services/i18n';
|
||||
import { setLocale } from 'app/components/i18n/actions';
|
||||
|
||||
|
6
@types/cwordin-api.d.ts
vendored
6
@types/cwordin-api.d.ts
vendored
@ -69,11 +69,7 @@ declare module 'crowdin-api' {
|
||||
type FilesList = Record<string, string | ReadableStream>;
|
||||
|
||||
export default class CrowdinApi {
|
||||
constructor(params: {
|
||||
apiKey: string;
|
||||
projectName: string;
|
||||
baseUrl?: string;
|
||||
});
|
||||
constructor(params: { apiKey: string; projectName: string; baseUrl?: string });
|
||||
projectInfo(): Promise<ProjectInfoResponse>;
|
||||
languageStatus(language: string): Promise<LanguageStatusResponse>;
|
||||
exportFile(
|
||||
|
5
@types/multi-progress.d.ts
vendored
5
@types/multi-progress.d.ts
vendored
@ -1,10 +1,7 @@
|
||||
declare module 'multi-progress' {
|
||||
export default class MultiProgress {
|
||||
constructor(stream?: string);
|
||||
newBar(
|
||||
schema: string,
|
||||
options: ProgressBar.ProgressBarOptions,
|
||||
): ProgressBar;
|
||||
newBar(schema: string, options: ProgressBar.ProgressBarOptions): ProgressBar;
|
||||
terminate(): void;
|
||||
move(index: number): void;
|
||||
tick(index: number, value?: number, options?: any): void;
|
||||
|
16
@types/prompt.d.ts
vendored
16
@types/prompt.d.ts
vendored
@ -2,10 +2,7 @@
|
||||
// Project: https://github.com/flatiron/prompt
|
||||
|
||||
declare module 'prompt' {
|
||||
type PropertiesType =
|
||||
| Array<string>
|
||||
| prompt.PromptSchema
|
||||
| Array<prompt.PromptPropertyOptions>;
|
||||
type PropertiesType = Array<string> | prompt.PromptSchema | Array<prompt.PromptPropertyOptions>;
|
||||
|
||||
namespace prompt {
|
||||
interface PromptSchema {
|
||||
@ -40,19 +37,12 @@ declare module 'prompt' {
|
||||
: T extends PromptSchema
|
||||
? Record<keyof T['properties'], string>
|
||||
: T extends Array<PromptPropertyOptions>
|
||||
? Record<
|
||||
T[number]['name'] extends string ? T[number]['name'] : number,
|
||||
string
|
||||
>
|
||||
? Record<T[number]['name'] extends string ? T[number]['name'] : number, string>
|
||||
: never,
|
||||
) => void,
|
||||
): void;
|
||||
|
||||
export function addProperties(
|
||||
obj: any,
|
||||
properties: PropertiesType,
|
||||
callback: (err: Error) => void,
|
||||
): void;
|
||||
export function addProperties(obj: any, properties: PropertiesType, callback: (err: Error) => void): void;
|
||||
|
||||
export function history(propertyName: string): any;
|
||||
|
||||
|
16
@types/redux-localstorage.d.ts
vendored
16
@types/redux-localstorage.d.ts
vendored
@ -7,19 +7,9 @@ declare module 'redux-localstorage' {
|
||||
key: string;
|
||||
merge?: any;
|
||||
slicer?: any;
|
||||
serialize?: (
|
||||
value: any,
|
||||
replacer?: (key: string, value: any) => any,
|
||||
space?: string | number,
|
||||
) => string;
|
||||
deserialize?: (
|
||||
text: string,
|
||||
reviver?: (key: any, value: any) => any,
|
||||
) => any;
|
||||
serialize?: (value: any, replacer?: (key: string, value: any) => any, space?: string | number) => string;
|
||||
deserialize?: (text: string, reviver?: (key: any, value: any) => any) => any;
|
||||
}
|
||||
|
||||
export default function persistState(
|
||||
paths: string | string[],
|
||||
config: ConfigRS,
|
||||
): () => any;
|
||||
export default function persistState(paths: string | string[], config: ConfigRS): () => any;
|
||||
}
|
||||
|
13
@types/unexpected.d.ts
vendored
13
@types/unexpected.d.ts
vendored
@ -12,11 +12,7 @@ declare module 'unexpected' {
|
||||
/**
|
||||
* @see http://unexpected.js.org/api/expect/
|
||||
*/
|
||||
<A extends Array<unknown> = []>(
|
||||
subject: unknown,
|
||||
assertionName: string,
|
||||
...args: A
|
||||
): EnchantedPromise<any>;
|
||||
<A extends Array<unknown> = []>(subject: unknown, assertionName: string, ...args: A): EnchantedPromise<any>;
|
||||
|
||||
it<A extends Array<unknown> = []>(
|
||||
assertionName: string,
|
||||
@ -71,12 +67,7 @@ declare module 'unexpected' {
|
||||
identify(value: unknown): value is T;
|
||||
base?: string;
|
||||
equal?(a: T, b: T, equal: (a: unknown, b: unknown) => boolean): boolean;
|
||||
inspect?(
|
||||
value: T,
|
||||
depth: number,
|
||||
output: any,
|
||||
inspect: (value: unknown, depth: number) => any,
|
||||
): void;
|
||||
inspect?(value: T, depth: number, output: any, inspect: (value: unknown, depth: number) => any): void;
|
||||
}
|
||||
}
|
||||
|
||||
|
18
README.md
18
README.md
@ -3,8 +3,7 @@
|
||||
[![Build Status](https://travis-ci.org/elyby/accounts-frontend.svg?branch=master)](https://travis-ci.org/elyby/accounts-frontend)
|
||||
[![Ely.by translation on Crowdin](https://d322cqt584bo4o.cloudfront.net/elyby/localized.svg)](https://translate.ely.by/project/elyby)
|
||||
|
||||
Web interface for Ely.by Accounts service. Developed using ReactJS and Flow
|
||||
typing.
|
||||
Web interface for Ely.by Accounts service. Developed using ReactJS and Flow typing.
|
||||
|
||||
## Development
|
||||
|
||||
@ -19,16 +18,15 @@ cd accounts-frontend
|
||||
yarn install
|
||||
```
|
||||
|
||||
After that you need to copy `config/template.env.js` into `config/env.js` and
|
||||
adjust it for yourself. Then you can start the application in dev mode:
|
||||
After that you need to copy `config/template.env.js` into `config/env.js` and adjust it for yourself. Then you can start
|
||||
the application in dev mode:
|
||||
|
||||
```bash
|
||||
yarn start
|
||||
```
|
||||
|
||||
This will start the dev server on port 8080, which will automatically apply all
|
||||
changes in project files, as well as proxy all requests to the backend on the
|
||||
domain specified in `env.js`.
|
||||
This will start the dev server on port 8080, which will automatically apply all changes in project files, as well as
|
||||
proxy all requests to the backend on the domain specified in `env.js`.
|
||||
|
||||
To run the tests execute:
|
||||
|
||||
@ -42,8 +40,7 @@ yarn test
|
||||
|
||||
2. Place your code in a separate branch `git checkout -b <your_branch_name>`.
|
||||
|
||||
3. Add your fork as a remote
|
||||
`git remote add fork https://github.com/<your_username>/accounts-frontend.git`.
|
||||
3. Add your fork as a remote `git remote add fork https://github.com/<your_username>/accounts-frontend.git`.
|
||||
|
||||
4. Push to your fork repository `git push -u fork <your_branch_name>`.
|
||||
|
||||
@ -52,5 +49,4 @@ yarn test
|
||||
## Translating
|
||||
|
||||
Ely.by translation is done through the [Crowdin](https://crowdin.com) service.
|
||||
[Click here](https://translate.ely.by/project/elyby/invite) to participate in
|
||||
the translation of the project.
|
||||
[Click here](https://translate.ely.by/project/elyby/invite) to participate in the translation of the project.
|
||||
|
@ -33,8 +33,7 @@ export default class MeasureHeight extends React.PureComponent<
|
||||
} & React.HTMLAttributes<HTMLDivElement>
|
||||
> {
|
||||
static defaultProps = {
|
||||
shouldMeasure: (prevState: ChildState, newState: ChildState) =>
|
||||
prevState !== newState,
|
||||
shouldMeasure: (prevState: ChildState, newState: ChildState) => prevState !== newState,
|
||||
onMeasure: () => {},
|
||||
};
|
||||
|
||||
|
@ -41,13 +41,7 @@ export class AccountSwitcher extends React.Component<Props> {
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
accounts,
|
||||
skin,
|
||||
allowAdd,
|
||||
allowLogout,
|
||||
highlightActiveAccount,
|
||||
} = this.props;
|
||||
const { accounts, skin, allowAdd, allowLogout, highlightActiveAccount } = this.props;
|
||||
const activeAccount = getActiveAccount({ accounts });
|
||||
|
||||
if (!activeAccount) {
|
||||
@ -57,43 +51,25 @@ export class AccountSwitcher extends React.Component<Props> {
|
||||
let { available } = accounts;
|
||||
|
||||
if (highlightActiveAccount) {
|
||||
available = available.filter(
|
||||
(account) => account.id !== activeAccount.id,
|
||||
);
|
||||
available = available.filter((account) => account.id !== activeAccount.id);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
styles.accountSwitcher,
|
||||
styles[`${skin}AccountSwitcher`],
|
||||
)}
|
||||
className={clsx(styles.accountSwitcher, styles[`${skin}AccountSwitcher`])}
|
||||
data-testid="account-switcher"
|
||||
>
|
||||
{highlightActiveAccount && (
|
||||
<div className={styles.item} data-testid="active-account">
|
||||
<div
|
||||
className={clsx(
|
||||
styles.accountIcon,
|
||||
styles.activeAccountIcon,
|
||||
styles.accountIcon1,
|
||||
)}
|
||||
/>
|
||||
<div className={clsx(styles.accountIcon, styles.activeAccountIcon, styles.accountIcon1)} />
|
||||
<div className={styles.activeAccountInfo}>
|
||||
<div className={styles.activeAccountUsername}>
|
||||
{activeAccount.username}
|
||||
</div>
|
||||
<div
|
||||
className={clsx(styles.accountEmail, styles.activeAccountEmail)}
|
||||
>
|
||||
<div className={styles.activeAccountUsername}>{activeAccount.username}</div>
|
||||
<div className={clsx(styles.accountEmail, styles.activeAccountEmail)}>
|
||||
{activeAccount.email}
|
||||
</div>
|
||||
<div className={styles.links}>
|
||||
<div className={styles.link}>
|
||||
<a
|
||||
href={`http://ely.by/u${activeAccount.id}`}
|
||||
target="_blank"
|
||||
>
|
||||
<a href={`http://ely.by/u${activeAccount.id}`} target="_blank">
|
||||
<Message {...messages.goToEly} />
|
||||
</a>
|
||||
</div>
|
||||
@ -122,9 +98,7 @@ export class AccountSwitcher extends React.Component<Props> {
|
||||
<div
|
||||
className={clsx(
|
||||
styles.accountIcon,
|
||||
styles[
|
||||
`accountIcon${(index % 7) + (highlightActiveAccount ? 2 : 1)}`
|
||||
],
|
||||
styles[`accountIcon${(index % 7) + (highlightActiveAccount ? 2 : 1)}`],
|
||||
)}
|
||||
/>
|
||||
|
||||
|
@ -4,28 +4,16 @@ import { browserHistory } from 'app/services/history';
|
||||
import { InternalServerError } from 'app/services/request';
|
||||
import { sessionStorage } from 'app/services/localStorage';
|
||||
import * as authentication from 'app/services/api/authentication';
|
||||
import {
|
||||
authenticate,
|
||||
revoke,
|
||||
logoutAll,
|
||||
logoutStrangers,
|
||||
} from 'app/components/accounts/actions';
|
||||
import {
|
||||
add,
|
||||
activate,
|
||||
remove,
|
||||
reset,
|
||||
} from 'app/components/accounts/actions/pure-actions';
|
||||
import { authenticate, revoke, logoutAll, logoutStrangers } from 'app/components/accounts/actions';
|
||||
import { add, activate, remove, reset } from 'app/components/accounts/actions/pure-actions';
|
||||
import { updateUser, setUser } from 'app/components/user/actions';
|
||||
import { setLogin, setAccountSwitcher } from 'app/components/auth/actions';
|
||||
import { Dispatch, RootState } from 'app/reducers';
|
||||
|
||||
import { Account } from './reducer';
|
||||
|
||||
const token =
|
||||
'eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJlbHl8MSJ9.pRJ7vakt2eIscjqwG__KhSxKb3qwGsdBBeDbBffJs_I';
|
||||
const legacyToken =
|
||||
'eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOjF9.cRF-sQNrwWQ94xCb3vWioVdjxAZeefEE7GMGwh7708o';
|
||||
const token = 'eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJlbHl8MSJ9.pRJ7vakt2eIscjqwG__KhSxKb3qwGsdBBeDbBffJs_I';
|
||||
const legacyToken = 'eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOjF9.cRF-sQNrwWQ94xCb3vWioVdjxAZeefEE7GMGwh7708o';
|
||||
|
||||
const account = {
|
||||
id: 1,
|
||||
@ -63,9 +51,7 @@ describe('components/accounts/actions', () => {
|
||||
user: {},
|
||||
});
|
||||
|
||||
sinon
|
||||
.stub(authentication, 'validateToken')
|
||||
.named('authentication.validateToken');
|
||||
sinon.stub(authentication, 'validateToken').named('authentication.validateToken');
|
||||
sinon.stub(browserHistory, 'push').named('browserHistory.push');
|
||||
sinon.stub(authentication, 'logout').named('authentication.logout');
|
||||
|
||||
@ -96,29 +82,13 @@ describe('components/accounts/actions', () => {
|
||||
));
|
||||
|
||||
it('should request user by extracting id from token', () =>
|
||||
authenticate({ token } as Account)(
|
||||
dispatch,
|
||||
getState,
|
||||
undefined,
|
||||
).then(() =>
|
||||
expect(authentication.validateToken, 'to have a call satisfying', [
|
||||
1,
|
||||
token,
|
||||
undefined,
|
||||
]),
|
||||
authenticate({ token } as Account)(dispatch, getState, undefined).then(() =>
|
||||
expect(authentication.validateToken, 'to have a call satisfying', [1, token, undefined]),
|
||||
));
|
||||
|
||||
it('should request user by extracting id from legacy token', () =>
|
||||
authenticate({ token: legacyToken } as Account)(
|
||||
dispatch,
|
||||
getState,
|
||||
undefined,
|
||||
).then(() =>
|
||||
expect(authentication.validateToken, 'to have a call satisfying', [
|
||||
1,
|
||||
legacyToken,
|
||||
undefined,
|
||||
]),
|
||||
authenticate({ token: legacyToken } as Account)(dispatch, getState, undefined).then(() =>
|
||||
expect(authentication.validateToken, 'to have a call satisfying', [1, legacyToken, undefined]),
|
||||
));
|
||||
|
||||
it(`dispatches accounts:add action`, () =>
|
||||
@ -133,33 +103,22 @@ describe('components/accounts/actions', () => {
|
||||
|
||||
it(`dispatches i18n:setLocale action`, () =>
|
||||
authenticate(account)(dispatch, getState, undefined).then(() =>
|
||||
expect(dispatch, 'to have a call satisfying', [
|
||||
{ type: 'i18n:setLocale', payload: { locale: 'be' } },
|
||||
]),
|
||||
expect(dispatch, 'to have a call satisfying', [{ type: 'i18n:setLocale', payload: { locale: 'be' } }]),
|
||||
));
|
||||
|
||||
it('should update user state', () =>
|
||||
authenticate(account)(dispatch, getState, undefined).then(() =>
|
||||
expect(dispatch, 'to have a call satisfying', [
|
||||
updateUser({ ...user, isGuest: false }),
|
||||
]),
|
||||
expect(dispatch, 'to have a call satisfying', [updateUser({ ...user, isGuest: false })]),
|
||||
));
|
||||
|
||||
it('resolves with account', () =>
|
||||
authenticate(account)(dispatch, getState, undefined).then((resp) =>
|
||||
expect(resp, 'to equal', account),
|
||||
));
|
||||
authenticate(account)(dispatch, getState, undefined).then((resp) => expect(resp, 'to equal', account)));
|
||||
|
||||
it('rejects when bad auth data', () => {
|
||||
(authentication.validateToken as any).returns(Promise.reject({}));
|
||||
|
||||
return expect(
|
||||
authenticate(account)(dispatch, getState, undefined),
|
||||
'to be rejected',
|
||||
).then(() => {
|
||||
expect(dispatch, 'to have a call satisfying', [
|
||||
setLogin(account.email),
|
||||
]);
|
||||
return expect(authenticate(account)(dispatch, getState, undefined), 'to be rejected').then(() => {
|
||||
expect(dispatch, 'to have a call satisfying', [setLogin(account.email)]);
|
||||
|
||||
expect(browserHistory.push, 'to have a call satisfying', ['/login']);
|
||||
});
|
||||
@ -170,14 +129,8 @@ describe('components/accounts/actions', () => {
|
||||
|
||||
(authentication.validateToken as any).rejects(resp);
|
||||
|
||||
return expect(
|
||||
authenticate(account)(dispatch, getState, undefined),
|
||||
'to be rejected with',
|
||||
resp,
|
||||
).then(() =>
|
||||
expect(dispatch, 'to have no calls satisfying', [
|
||||
{ payload: { isGuest: true } },
|
||||
]),
|
||||
return expect(authenticate(account)(dispatch, getState, undefined), 'to be rejected with', resp).then(() =>
|
||||
expect(dispatch, 'to have no calls satisfying', [{ payload: { isGuest: true } }]),
|
||||
);
|
||||
});
|
||||
|
||||
@ -215,9 +168,7 @@ describe('components/accounts/actions', () => {
|
||||
|
||||
it('should dispatch setAccountSwitcher', () =>
|
||||
authenticate(account)(dispatch, getState, undefined).then(() =>
|
||||
expect(dispatch, 'to have a call satisfying', [
|
||||
setAccountSwitcher(false),
|
||||
]),
|
||||
expect(dispatch, 'to have a call satisfying', [setAccountSwitcher(false)]),
|
||||
));
|
||||
});
|
||||
|
||||
@ -236,17 +187,11 @@ describe('components/accounts/actions', () => {
|
||||
});
|
||||
|
||||
it('should activate account before auth api call', () => {
|
||||
(authentication.validateToken as any).returns(
|
||||
Promise.reject({ error: 'foo' }),
|
||||
);
|
||||
(authentication.validateToken as any).returns(Promise.reject({ error: 'foo' }));
|
||||
|
||||
return expect(
|
||||
authenticate(account)(dispatch, getState, undefined),
|
||||
'to be rejected with',
|
||||
{ error: 'foo' },
|
||||
).then(() =>
|
||||
expect(dispatch, 'to have a call satisfying', [activate(account)]),
|
||||
);
|
||||
return expect(authenticate(account)(dispatch, getState, undefined), 'to be rejected with', {
|
||||
error: 'foo',
|
||||
}).then(() => expect(dispatch, 'to have a call satisfying', [activate(account)]));
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -273,17 +218,12 @@ describe('components/accounts/actions', () => {
|
||||
|
||||
it('should call logout api method in background', () =>
|
||||
revoke(account)(dispatch, getState, undefined).then(() =>
|
||||
expect(authentication.logout, 'to have a call satisfying', [
|
||||
account.token,
|
||||
]),
|
||||
expect(authentication.logout, 'to have a call satisfying', [account.token]),
|
||||
));
|
||||
|
||||
it('should update user state', () =>
|
||||
revoke(account)(dispatch, getState, undefined).then(
|
||||
() =>
|
||||
expect(dispatch, 'to have a call satisfying', [
|
||||
setUser({ isGuest: true }),
|
||||
]),
|
||||
() => expect(dispatch, 'to have a call satisfying', [setUser({ isGuest: true })]),
|
||||
// expect(dispatch, 'to have calls satisfying', [
|
||||
// [remove(account)],
|
||||
// [expect.it('to be a function')]
|
||||
@ -317,9 +257,7 @@ describe('components/accounts/actions', () => {
|
||||
|
||||
it('should call logout api method in background', () =>
|
||||
revoke(account2)(dispatch, getState, undefined).then(() =>
|
||||
expect(authentication.logout, 'to have a call satisfying', [
|
||||
account2.token,
|
||||
]),
|
||||
expect(authentication.logout, 'to have a call satisfying', [account2.token]),
|
||||
));
|
||||
});
|
||||
});
|
||||
@ -343,10 +281,7 @@ describe('components/accounts/actions', () => {
|
||||
it('should call logout api method for each account', () => {
|
||||
logoutAll()(dispatch, getState, undefined);
|
||||
|
||||
expect(authentication.logout, 'to have calls satisfying', [
|
||||
[account.token],
|
||||
[account2.token],
|
||||
]);
|
||||
expect(authentication.logout, 'to have calls satisfying', [[account.token], [account2.token]]);
|
||||
});
|
||||
|
||||
it('should dispatch reset', () => {
|
||||
@ -437,9 +372,7 @@ describe('components/accounts/actions', () => {
|
||||
user,
|
||||
});
|
||||
|
||||
return logoutStrangers()(dispatch, getState, undefined).then(() =>
|
||||
expect(dispatch, 'was not called'),
|
||||
);
|
||||
return logoutStrangers()(dispatch, getState, undefined).then(() => expect(dispatch, 'was not called'));
|
||||
});
|
||||
|
||||
describe('when all accounts are strangers', () => {
|
||||
@ -464,9 +397,7 @@ describe('components/accounts/actions', () => {
|
||||
[foreignAccount2.token],
|
||||
]);
|
||||
|
||||
expect(dispatch, 'to have a call satisfying', [
|
||||
setUser({ isGuest: true }),
|
||||
]);
|
||||
expect(dispatch, 'to have a call satisfying', [setUser({ isGuest: true })]);
|
||||
|
||||
expect(dispatch, 'to have a call satisfying', [reset()]);
|
||||
});
|
||||
@ -486,9 +417,7 @@ describe('components/accounts/actions', () => {
|
||||
});
|
||||
|
||||
it('should not log out', () =>
|
||||
expect(dispatch, 'not to have calls satisfying', [
|
||||
{ payload: foreignAccount },
|
||||
]));
|
||||
expect(dispatch, 'not to have calls satisfying', [{ payload: foreignAccount }]));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,27 +1,14 @@
|
||||
import { getJwtPayloads } from 'app/functions';
|
||||
import { sessionStorage } from 'app/services/localStorage';
|
||||
import {
|
||||
validateToken,
|
||||
requestToken,
|
||||
logout,
|
||||
} from 'app/services/api/authentication';
|
||||
import {
|
||||
relogin as navigateToLogin,
|
||||
setAccountSwitcher,
|
||||
} from 'app/components/auth/actions';
|
||||
import { validateToken, requestToken, logout } from 'app/services/api/authentication';
|
||||
import { relogin as navigateToLogin, setAccountSwitcher } from 'app/components/auth/actions';
|
||||
import { updateUser, setGuest } from 'app/components/user/actions';
|
||||
import { setLocale } from 'app/components/i18n/actions';
|
||||
import logger from 'app/services/logger';
|
||||
import { ThunkAction } from 'app/reducers';
|
||||
|
||||
import { getActiveAccount, Account } from './reducer';
|
||||
import {
|
||||
add,
|
||||
remove,
|
||||
activate,
|
||||
reset,
|
||||
updateToken,
|
||||
} from './actions/pure-actions';
|
||||
import { add, remove, activate, reset, updateToken } from './actions/pure-actions';
|
||||
|
||||
export { updateToken, activate, remove };
|
||||
|
||||
@ -52,9 +39,7 @@ export function authenticate(
|
||||
accountId = findAccountIdFromToken(token);
|
||||
}
|
||||
|
||||
const knownAccount = getState().accounts.available.find(
|
||||
(item) => item.id === accountId,
|
||||
);
|
||||
const knownAccount = getState().accounts.available.find((item) => item.id === accountId);
|
||||
|
||||
if (knownAccount) {
|
||||
// this account is already available
|
||||
@ -63,11 +48,11 @@ export function authenticate(
|
||||
}
|
||||
|
||||
try {
|
||||
const {
|
||||
token: newToken,
|
||||
refreshToken: newRefreshToken,
|
||||
user,
|
||||
} = await validateToken(accountId, token, refreshToken);
|
||||
const { token: newToken, refreshToken: newRefreshToken, user } = await validateToken(
|
||||
accountId,
|
||||
token,
|
||||
refreshToken,
|
||||
);
|
||||
const { auth } = getState();
|
||||
const newAccount: Account = {
|
||||
id: user.id,
|
||||
@ -204,11 +189,9 @@ export function recoverFromTokenError(
|
||||
|
||||
if (activeAccount && activeAccount.refreshToken) {
|
||||
if (
|
||||
[
|
||||
'Token expired',
|
||||
'Incorrect token',
|
||||
'You are requesting with an invalid credential.',
|
||||
].includes(error.message)
|
||||
['Token expired', 'Incorrect token', 'You are requesting with an invalid credential.'].includes(
|
||||
error.message,
|
||||
)
|
||||
) {
|
||||
// request token and retry
|
||||
return dispatch(requestNewToken());
|
||||
@ -341,13 +324,10 @@ export function logoutStrangers(): ThunkAction<Promise<void>> {
|
||||
} = getState();
|
||||
const activeAccount = getActiveAccount(getState());
|
||||
|
||||
const isStranger = ({ refreshToken, id }: Account) =>
|
||||
!refreshToken && !sessionStorage.getItem(`stranger${id}`);
|
||||
const isStranger = ({ refreshToken, id }: Account) => !refreshToken && !sessionStorage.getItem(`stranger${id}`);
|
||||
|
||||
if (available.some(isStranger)) {
|
||||
const accountToReplace = available.find(
|
||||
(account) => !isStranger(account),
|
||||
);
|
||||
const accountToReplace = available.find((account) => !isStranger(account));
|
||||
|
||||
if (accountToReplace) {
|
||||
available.filter(isStranger).forEach((account) => {
|
||||
|
@ -59,9 +59,4 @@ export function updateToken(token: string): UpdateTokenAction {
|
||||
};
|
||||
}
|
||||
|
||||
export type Action =
|
||||
| AddAction
|
||||
| RemoveAction
|
||||
| ActivateAction
|
||||
| ResetAction
|
||||
| UpdateTokenAction;
|
||||
export type Action = AddAction | RemoveAction | ActivateAction | ResetAction | UpdateTokenAction;
|
||||
|
@ -55,16 +55,9 @@ describe('Accounts reducer', () => {
|
||||
token: 'newToken',
|
||||
};
|
||||
|
||||
expect(
|
||||
accounts(
|
||||
{ ...initial, available: [outdatedAccount] },
|
||||
add(updatedAccount),
|
||||
),
|
||||
'to satisfy',
|
||||
{
|
||||
expect(accounts({ ...initial, available: [outdatedAccount] }, add(updatedAccount)), 'to satisfy', {
|
||||
available: [updatedAccount],
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should sort accounts by username', () => {
|
||||
@ -74,13 +67,9 @@ describe('Accounts reducer', () => {
|
||||
username: 'abc',
|
||||
};
|
||||
|
||||
expect(
|
||||
accounts({ ...initial, available: [account] }, add(newAccount)),
|
||||
'to satisfy',
|
||||
{
|
||||
expect(accounts({ ...initial, available: [account] }, add(newAccount)), 'to satisfy', {
|
||||
available: [newAccount, account],
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('throws, when account is invalid', () => {
|
||||
@ -99,11 +88,7 @@ describe('Accounts reducer', () => {
|
||||
|
||||
describe('accounts:remove', () => {
|
||||
it('should remove an account', () =>
|
||||
expect(
|
||||
accounts({ ...initial, available: [account] }, remove(account)),
|
||||
'to equal',
|
||||
initial,
|
||||
));
|
||||
expect(accounts({ ...initial, available: [account] }, remove(account)), 'to equal', initial));
|
||||
|
||||
it('throws, when account is invalid', () => {
|
||||
expect(
|
||||
@ -121,24 +106,14 @@ describe('Accounts reducer', () => {
|
||||
|
||||
describe('actions:reset', () => {
|
||||
it('should reset accounts state', () =>
|
||||
expect(
|
||||
accounts({ ...initial, available: [account] }, reset()),
|
||||
'to equal',
|
||||
initial,
|
||||
));
|
||||
expect(accounts({ ...initial, available: [account] }, reset()), 'to equal', initial));
|
||||
});
|
||||
|
||||
describe('accounts:updateToken', () => {
|
||||
it('should update token', () => {
|
||||
const newToken = 'newToken';
|
||||
|
||||
expect(
|
||||
accounts(
|
||||
{ active: account.id, available: [account] },
|
||||
updateToken(newToken),
|
||||
),
|
||||
'to satisfy',
|
||||
{
|
||||
expect(accounts({ active: account.id, available: [account] }, updateToken(newToken)), 'to satisfy', {
|
||||
active: account.id,
|
||||
available: [
|
||||
{
|
||||
@ -146,8 +121,7 @@ describe('Accounts reducer', () => {
|
||||
token: newToken,
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -16,14 +16,10 @@ export type State = {
|
||||
export function getActiveAccount(state: { accounts: State }): Account | null {
|
||||
const accountId = state.accounts.active;
|
||||
|
||||
return (
|
||||
state.accounts.available.find((account) => account.id === accountId) || null
|
||||
);
|
||||
return state.accounts.available.find((account) => account.id === accountId) || null;
|
||||
}
|
||||
|
||||
export function getAvailableAccounts(state: {
|
||||
accounts: State;
|
||||
}): Array<Account> {
|
||||
export function getAvailableAccounts(state: { accounts: State }): Array<Account> {
|
||||
return state.accounts.available;
|
||||
}
|
||||
|
||||
@ -42,9 +38,7 @@ export default function accounts(
|
||||
|
||||
const { payload } = action;
|
||||
|
||||
state.available = state.available
|
||||
.filter((account) => account.id !== payload.id)
|
||||
.concat(payload);
|
||||
state.available = state.available.filter((account) => account.id !== payload.id).concat(payload);
|
||||
|
||||
state.available.sort((account1, account2) => {
|
||||
if (account1.username === account2.username) {
|
||||
@ -91,9 +85,7 @@ export default function accounts(
|
||||
|
||||
return {
|
||||
...state,
|
||||
available: state.available.filter(
|
||||
(account) => account.id !== payload.id,
|
||||
),
|
||||
available: state.available.filter((account) => account.id !== payload.id),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1,26 +1,9 @@
|
||||
import React, {
|
||||
CSSProperties,
|
||||
MouseEventHandler,
|
||||
ReactElement,
|
||||
ReactNode,
|
||||
} from 'react';
|
||||
import React, { CSSProperties, MouseEventHandler, ReactElement, ReactNode } from 'react';
|
||||
import { AccountsState } from 'app/components/accounts';
|
||||
import { User } from 'app/components/user';
|
||||
import { connect } from 'react-redux';
|
||||
import {
|
||||
TransitionMotion,
|
||||
spring,
|
||||
PlainStyle,
|
||||
Style,
|
||||
TransitionStyle,
|
||||
TransitionPlainStyle,
|
||||
} from 'react-motion';
|
||||
import {
|
||||
Panel,
|
||||
PanelBody,
|
||||
PanelFooter,
|
||||
PanelHeader,
|
||||
} from 'app/components/ui/Panel';
|
||||
import { TransitionMotion, spring, PlainStyle, Style, TransitionStyle, TransitionPlainStyle } from 'react-motion';
|
||||
import { Panel, PanelBody, PanelFooter, PanelHeader } from 'app/components/ui/Panel';
|
||||
import { Form } from 'app/components/ui/form';
|
||||
import MeasureHeight from 'app/components/MeasureHeight';
|
||||
import panelStyles from 'app/components/ui/panel.scss';
|
||||
@ -71,11 +54,7 @@ if (process.env.NODE_ENV !== 'production') {
|
||||
contexts.reduce((acc, context) => {
|
||||
context.forEach((panel) => {
|
||||
if (acc[panel]) {
|
||||
throw new Error(
|
||||
`Panel ${panel} is already exists in context ${JSON.stringify(
|
||||
acc[panel],
|
||||
)}`,
|
||||
);
|
||||
throw new Error(`Panel ${panel} is already exists in context ${JSON.stringify(acc[panel])}`);
|
||||
}
|
||||
|
||||
acc[panel] = context;
|
||||
@ -161,10 +140,8 @@ class PanelTransition extends React.PureComponent<Props, State> {
|
||||
timerIds: Array<number> = []; // this is a list of a probably running timeouts to clean on unmount
|
||||
|
||||
componentDidUpdate(prevProps: Props) {
|
||||
const nextPanel: PanelId =
|
||||
this.props.Body && (this.props.Body.type as any).panelId;
|
||||
const prevPanel: PanelId =
|
||||
prevProps.Body && (prevProps.Body.type as any).panelId;
|
||||
const nextPanel: PanelId = this.props.Body && (this.props.Body.type as any).panelId;
|
||||
const prevPanel: PanelId = prevProps.Body && (prevProps.Body.type as any).panelId;
|
||||
|
||||
if (nextPanel !== prevPanel) {
|
||||
const direction = this.getDirection(nextPanel, prevPanel);
|
||||
@ -198,17 +175,7 @@ class PanelTransition extends React.PureComponent<Props, State> {
|
||||
render() {
|
||||
const { contextHeight, forceHeight } = this.state;
|
||||
|
||||
const {
|
||||
Title,
|
||||
Body,
|
||||
Footer,
|
||||
Links,
|
||||
auth,
|
||||
user,
|
||||
clearErrors,
|
||||
resolve,
|
||||
reject,
|
||||
} = this.props;
|
||||
const { Title, Body, Footer, Links, auth, user, clearErrors, resolve, reject } = this.props;
|
||||
|
||||
if (this.props.children) {
|
||||
return this.props.children;
|
||||
@ -273,9 +240,7 @@ class PanelTransition extends React.PureComponent<Props, State> {
|
||||
|
||||
const contentHeight = {
|
||||
overflow: 'hidden',
|
||||
height: forceHeight
|
||||
? common.style.switchContextHeightSpring
|
||||
: 'auto',
|
||||
height: forceHeight ? common.style.switchContextHeightSpring : 'auto',
|
||||
};
|
||||
|
||||
this.tryToAutoFocus(panels.length);
|
||||
@ -293,9 +258,7 @@ class PanelTransition extends React.PureComponent<Props, State> {
|
||||
isLoading={this.props.auth.isLoading}
|
||||
>
|
||||
<Panel>
|
||||
<PanelHeader>
|
||||
{panels.map((config) => this.getHeader(config))}
|
||||
</PanelHeader>
|
||||
<PanelHeader>{panels.map((config) => this.getHeader(config))}</PanelHeader>
|
||||
<div style={contentHeight}>
|
||||
<MeasureHeight
|
||||
state={this.shouldMeasureHeight()}
|
||||
@ -306,16 +269,11 @@ class PanelTransition extends React.PureComponent<Props, State> {
|
||||
{panels.map((config) => this.getBody(config))}
|
||||
</div>
|
||||
</PanelBody>
|
||||
<PanelFooter>
|
||||
{panels.map((config) => this.getFooter(config))}
|
||||
</PanelFooter>
|
||||
<PanelFooter>{panels.map((config) => this.getFooter(config))}</PanelFooter>
|
||||
</MeasureHeight>
|
||||
</div>
|
||||
</Panel>
|
||||
<div
|
||||
className={helpLinksStyles}
|
||||
data-testid="auth-controls-secondary"
|
||||
>
|
||||
<div className={helpLinksStyles} data-testid="auth-controls-secondary">
|
||||
{panels.map((config) => this.getLinks(config))}
|
||||
</div>
|
||||
</Form>
|
||||
@ -334,8 +292,7 @@ class PanelTransition extends React.PureComponent<Props, State> {
|
||||
}
|
||||
};
|
||||
|
||||
onFormInvalid = (errors: Record<string, ValidationError>): void =>
|
||||
this.props.setErrors(errors);
|
||||
onFormInvalid = (errors: Record<string, ValidationError>): void => this.props.setErrors(errors);
|
||||
|
||||
willEnter = (config: TransitionStyle): PlainStyle => {
|
||||
const transform = this.getTransformForPanel(config.key);
|
||||
@ -368,9 +325,7 @@ class PanelTransition extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
let sign =
|
||||
prevPanelId &&
|
||||
panelId &&
|
||||
currentContext.indexOf(panelId) > currentContext.indexOf(prevPanelId)
|
||||
prevPanelId && panelId && currentContext.indexOf(panelId) > currentContext.indexOf(prevPanelId)
|
||||
? fromRight
|
||||
: fromLeft;
|
||||
|
||||
@ -444,12 +399,7 @@ class PanelTransition extends React.PureComponent<Props, State> {
|
||||
return acc + item.type;
|
||||
}, '') as string;
|
||||
|
||||
return [
|
||||
errorString,
|
||||
isHeightDirty,
|
||||
user.lang,
|
||||
accounts.available.length,
|
||||
].join('');
|
||||
return [errorString, isHeightDirty, user.lang, accounts.available.length].join('');
|
||||
}
|
||||
|
||||
getHeader({ key, style, data }: TransitionPlainStyle): ReactElement {
|
||||
@ -463,10 +413,7 @@ class PanelTransition extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
const transitionStyle = {
|
||||
...this.getDefaultTransitionStyles(
|
||||
key,
|
||||
(style as unknown) as AnimationStyle,
|
||||
),
|
||||
...this.getDefaultTransitionStyles(key, (style as unknown) as AnimationStyle),
|
||||
opacity: 1, // reset default
|
||||
};
|
||||
|
||||
@ -512,10 +459,7 @@ class PanelTransition extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
const transitionStyle: CSSProperties = {
|
||||
...this.getDefaultTransitionStyles(
|
||||
key,
|
||||
(style as unknown) as AnimationStyle,
|
||||
),
|
||||
...this.getDefaultTransitionStyles(key, (style as unknown) as AnimationStyle),
|
||||
top: 'auto', // reset default
|
||||
[verticalOrigin]: 0,
|
||||
...transform,
|
||||
@ -541,10 +485,7 @@ class PanelTransition extends React.PureComponent<Props, State> {
|
||||
getFooter({ key, style, data }: TransitionPlainStyle): ReactElement {
|
||||
const { Footer } = data as AnimationData;
|
||||
|
||||
const transitionStyle = this.getDefaultTransitionStyles(
|
||||
key,
|
||||
(style as unknown) as AnimationStyle,
|
||||
);
|
||||
const transitionStyle = this.getDefaultTransitionStyles(key, (style as unknown) as AnimationStyle);
|
||||
|
||||
return (
|
||||
<div key={`footer/${key}`} style={transitionStyle}>
|
||||
@ -556,10 +497,7 @@ class PanelTransition extends React.PureComponent<Props, State> {
|
||||
getLinks({ key, style, data }: TransitionPlainStyle): ReactElement {
|
||||
const { Links } = data as AnimationData;
|
||||
|
||||
const transitionStyle = this.getDefaultTransitionStyles(
|
||||
key,
|
||||
(style as unknown) as AnimationStyle,
|
||||
);
|
||||
const transitionStyle = this.getDefaultTransitionStyles(key, (style as unknown) as AnimationStyle);
|
||||
|
||||
return (
|
||||
<div key={`links/${key}`} style={transitionStyle}>
|
||||
@ -589,11 +527,7 @@ class PanelTransition extends React.PureComponent<Props, State> {
|
||||
};
|
||||
}
|
||||
|
||||
translate(
|
||||
value: number,
|
||||
direction: 'X' | 'Y' = 'X',
|
||||
unit: '%' | 'px' = '%',
|
||||
): CSSProperties {
|
||||
translate(value: number, direction: 'X' | 'Y' = 'X', unit: '%' | 'px' = '%'): CSSProperties {
|
||||
return {
|
||||
WebkitTransform: `translate${direction}(${value}${unit})`,
|
||||
transform: `translate${direction}(${value}${unit})`,
|
||||
|
@ -5,13 +5,10 @@ To add new panel you need to:
|
||||
- create panel component at `components/auth/[panelId]`
|
||||
- add new context in `components/auth/PanelTransition`
|
||||
- connect component to router in `pages/auth/AuthPage`
|
||||
- add new state to `services/authFlow` and coresponding test to
|
||||
`tests/services/authFlow`
|
||||
- add new state to `services/authFlow` and coresponding test to `tests/services/authFlow`
|
||||
- connect state to `authFlow`. Update `services/authFlow/AuthFlow.test` and
|
||||
`services/authFlow/AuthFlow.functional.test` (the last one for some complex
|
||||
flow)
|
||||
- add new actions to `components/auth/actions` and api endpoints to
|
||||
`services/api`
|
||||
`services/authFlow/AuthFlow.functional.test` (the last one for some complex flow)
|
||||
- add new actions to `components/auth/actions` and api endpoints to `services/api`
|
||||
- whatever else you need
|
||||
|
||||
Commit id with example implementation: f4d315c
|
||||
|
@ -9,11 +9,7 @@ interface Props {
|
||||
label: MessageDescriptor;
|
||||
}
|
||||
|
||||
const RejectionLink: ComponentType<Props> = ({
|
||||
isAvailable,
|
||||
payload,
|
||||
label,
|
||||
}) => {
|
||||
const RejectionLink: ComponentType<Props> = ({ isAvailable, payload, label }) => {
|
||||
const context = useContext(Context);
|
||||
|
||||
if (isAvailable && !isAvailable(context)) {
|
||||
|
@ -31,21 +31,14 @@ describe('components/auth/actions', () => {
|
||||
const dispatch = sinon.stub().named('store.dispatch');
|
||||
const getState = sinon.stub().named('store.getState');
|
||||
|
||||
function callThunk<A extends Array<any>, F extends (...args: A) => any>(
|
||||
fn: F,
|
||||
...args: A
|
||||
): Promise<void> {
|
||||
function callThunk<A extends Array<any>, F extends (...args: A) => any>(fn: F, ...args: A): Promise<void> {
|
||||
const thunk = fn(...args);
|
||||
|
||||
return thunk(dispatch, getState);
|
||||
}
|
||||
|
||||
function expectDispatchCalls(calls: Array<Array<ReduxAction>>) {
|
||||
expect(dispatch, 'to have calls satisfying', [
|
||||
[setLoadingState(true)],
|
||||
...calls,
|
||||
[setLoadingState(false)],
|
||||
]);
|
||||
expect(dispatch, 'to have calls satisfying', [[setLoadingState(true)], ...calls, [setLoadingState(false)]]);
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
@ -84,10 +77,7 @@ describe('components/auth/actions', () => {
|
||||
|
||||
it('should send get request to an api', () =>
|
||||
callThunk(oAuthValidate, oauthData).then(() => {
|
||||
expect(request.get, 'to have a call satisfying', [
|
||||
'/api/oauth2/v1/validate',
|
||||
{},
|
||||
]);
|
||||
expect(request.get, 'to have a call satisfying', ['/api/oauth2/v1/validate', {}]);
|
||||
}));
|
||||
|
||||
it('should dispatch setClient, setOAuthRequest and setScopes', () =>
|
||||
|
@ -4,10 +4,7 @@ import logger from 'app/services/logger';
|
||||
import localStorage from 'app/services/localStorage';
|
||||
import * as loader from 'app/services/loader';
|
||||
import history from 'app/services/history';
|
||||
import {
|
||||
updateUser,
|
||||
acceptRules as userAcceptRules,
|
||||
} from 'app/components/user/actions';
|
||||
import { updateUser, acceptRules as userAcceptRules } from 'app/components/user/actions';
|
||||
import { authenticate, logoutAll } from 'app/components/accounts/actions';
|
||||
import { getActiveAccount } from 'app/components/accounts/reducer';
|
||||
import {
|
||||
@ -118,18 +115,10 @@ export function login({
|
||||
}
|
||||
|
||||
export function acceptRules() {
|
||||
return wrapInLoader((dispatch) =>
|
||||
dispatch(userAcceptRules()).catch(validationErrorsHandler(dispatch)),
|
||||
);
|
||||
return wrapInLoader((dispatch) => dispatch(userAcceptRules()).catch(validationErrorsHandler(dispatch)));
|
||||
}
|
||||
|
||||
export function forgotPassword({
|
||||
login = '',
|
||||
captcha = '',
|
||||
}: {
|
||||
login: string;
|
||||
captcha: string;
|
||||
}) {
|
||||
export function forgotPassword({ login = '', captcha = '' }: { login: string; captcha: string }) {
|
||||
return wrapInLoader((dispatch, getState) =>
|
||||
forgotPasswordEndpoint(login, captcha)
|
||||
.then(({ data = {} }) =>
|
||||
@ -200,11 +189,7 @@ export function register({
|
||||
);
|
||||
}
|
||||
|
||||
export function activate({
|
||||
key = '',
|
||||
}: {
|
||||
key: string;
|
||||
}): ThunkAction<Promise<Account>> {
|
||||
export function activate({ key = '' }: { key: string }): ThunkAction<Promise<Account>> {
|
||||
return wrapInLoader((dispatch) =>
|
||||
activateEndpoint(key)
|
||||
.then(authHandler(dispatch))
|
||||
@ -212,13 +197,7 @@ export function activate({
|
||||
);
|
||||
}
|
||||
|
||||
export function resendActivation({
|
||||
email = '',
|
||||
captcha,
|
||||
}: {
|
||||
email: string;
|
||||
captcha: string;
|
||||
}) {
|
||||
export function resendActivation({ email = '', captcha }: { email: string; captcha: string }) {
|
||||
return wrapInLoader((dispatch) =>
|
||||
resendActivationEndpoint(email, captcha)
|
||||
.then((resp) => {
|
||||
@ -263,8 +242,7 @@ export function setLogin(login: string | null): SetCredentialsAction {
|
||||
export function relogin(login: string | null): ThunkAction {
|
||||
return (dispatch, getState) => {
|
||||
const credentials = getCredentials(getState());
|
||||
const returnUrl =
|
||||
credentials.returnUrl || location.pathname + location.search;
|
||||
const returnUrl = credentials.returnUrl || location.pathname + location.search;
|
||||
|
||||
dispatch(
|
||||
setCredentials({
|
||||
@ -325,9 +303,7 @@ interface SetErrorAction extends ReduxAction {
|
||||
error: boolean;
|
||||
}
|
||||
|
||||
export function setErrors(
|
||||
errors: Record<string, ValidationError> | null,
|
||||
): SetErrorAction {
|
||||
export function setErrors(errors: Record<string, ValidationError> | null): SetErrorAction {
|
||||
return {
|
||||
type: 'auth:error',
|
||||
payload: errors,
|
||||
@ -373,12 +349,8 @@ export function oAuthValidate(oauthData: OauthData) {
|
||||
.validate(oauthData)
|
||||
.then((resp) => {
|
||||
const { scopes } = resp.session;
|
||||
const invalidScopes = scopes.filter(
|
||||
(scope) => !KNOWN_SCOPES.includes(scope),
|
||||
);
|
||||
let prompt = (oauthData.prompt || 'none')
|
||||
.split(',')
|
||||
.map((item) => item.trim());
|
||||
const invalidScopes = scopes.filter((scope) => !KNOWN_SCOPES.includes(scope));
|
||||
let prompt = (oauthData.prompt || 'none').split(',').map((item) => item.trim());
|
||||
|
||||
if (prompt.includes('none')) {
|
||||
prompt = ['none'];
|
||||
@ -505,16 +477,7 @@ export type ClientAction = SetClientAction;
|
||||
|
||||
interface SetOauthAction extends ReduxAction {
|
||||
type: 'set_oauth';
|
||||
payload: Pick<
|
||||
OAuthState,
|
||||
| 'clientId'
|
||||
| 'redirectUrl'
|
||||
| 'responseType'
|
||||
| 'scope'
|
||||
| 'prompt'
|
||||
| 'loginHint'
|
||||
| 'state'
|
||||
>;
|
||||
payload: Pick<OAuthState, 'clientId' | 'redirectUrl' | 'responseType' | 'scope' | 'prompt' | 'loginHint' | 'state'>;
|
||||
}
|
||||
|
||||
// Input data is coming right from the query string, so the names
|
||||
@ -551,11 +514,7 @@ interface SetOAuthResultAction extends ReduxAction {
|
||||
|
||||
export const SET_OAUTH_RESULT = 'set_oauth_result'; // TODO: remove
|
||||
|
||||
export function setOAuthCode(payload: {
|
||||
success: boolean;
|
||||
code: string;
|
||||
displayCode: boolean;
|
||||
}): SetOAuthResultAction {
|
||||
export function setOAuthCode(payload: { success: boolean; code: string; displayCode: boolean }): SetOAuthResultAction {
|
||||
return {
|
||||
type: 'set_oauth_result',
|
||||
payload,
|
||||
@ -601,10 +560,7 @@ export function requirePermissionsAccept(): RequestPermissionsAcceptAction {
|
||||
};
|
||||
}
|
||||
|
||||
export type OAuthAction =
|
||||
| SetOauthAction
|
||||
| SetOAuthResultAction
|
||||
| RequestPermissionsAcceptAction;
|
||||
export type OAuthAction = SetOauthAction | SetOAuthResultAction | RequestPermissionsAcceptAction;
|
||||
|
||||
interface SetScopesAction extends ReduxAction {
|
||||
type: 'set_scopes';
|
||||
@ -700,12 +656,7 @@ function validationErrorsHandler(
|
||||
Object.assign(firstErrorObj.payload, resp.data);
|
||||
}
|
||||
|
||||
if (
|
||||
['error.key_not_exists', 'error.key_expire'].includes(
|
||||
firstErrorObj.type,
|
||||
) &&
|
||||
repeatUrl
|
||||
) {
|
||||
if (['error.key_not_exists', 'error.key_expire'].includes(firstErrorObj.type) && repeatUrl) {
|
||||
// TODO: this should be formatted on backend
|
||||
firstErrorObj.payload.repeatUrl = repeatUrl;
|
||||
}
|
||||
|
@ -12,8 +12,7 @@ export default class ActivationBody extends BaseAuthBody {
|
||||
static displayName = 'ActivationBody';
|
||||
static panelId = 'activation';
|
||||
|
||||
autoFocusField =
|
||||
this.props.match.params && this.props.match.params.key ? null : 'key';
|
||||
autoFocusField = this.props.match.params && this.props.match.params.key ? null : 'key';
|
||||
|
||||
render() {
|
||||
const { key } = this.props.match.params;
|
||||
|
@ -17,9 +17,7 @@ export default class AppInfo extends React.Component<{
|
||||
return (
|
||||
<div className={styles.appInfo}>
|
||||
<div className={styles.logoContainer}>
|
||||
<h2 className={styles.logo}>
|
||||
{name ? name : <Message {...messages.appName} />}
|
||||
</h2>
|
||||
<h2 className={styles.logo}>{name ? name : <Message {...messages.appName} />}</h2>
|
||||
</div>
|
||||
<div className={styles.descriptionContainer}>
|
||||
{description ? (
|
||||
|
@ -21,12 +21,7 @@ const AuthError: ComponentType<Props> = ({ error, onClose }) => {
|
||||
useEffect(() => {
|
||||
resetTimeout();
|
||||
|
||||
if (
|
||||
onClose &&
|
||||
typeof error !== 'string' &&
|
||||
error.payload &&
|
||||
error.payload.canRepeatIn
|
||||
) {
|
||||
if (onClose && typeof error !== 'string' && error.payload && error.payload.canRepeatIn) {
|
||||
const msLeft = error.payload.canRepeatIn * 1000;
|
||||
// 1500 to let the user see, that time is elapsed
|
||||
setTimeout(onClose, msLeft - Date.now() + 1500);
|
||||
|
@ -25,12 +25,7 @@ interface FactoryParams {
|
||||
links?: RejectionLinkProps | Array<RejectionLinkProps>;
|
||||
}
|
||||
|
||||
export default function ({
|
||||
title,
|
||||
body,
|
||||
footer,
|
||||
links,
|
||||
}: FactoryParams): Factory {
|
||||
export default function ({ title, body, footer, links }: FactoryParams): Factory {
|
||||
return () => ({
|
||||
Title: () => <AuthTitle title={title} />,
|
||||
Body: body,
|
||||
@ -40,10 +35,7 @@ export default function ({
|
||||
<span>
|
||||
{([] as Array<RejectionLinkProps>)
|
||||
.concat(links)
|
||||
.map((link, index) => [
|
||||
index ? ' | ' : '',
|
||||
<RejectionLink {...link} key={index} />,
|
||||
])}
|
||||
.map((link, index) => [index ? ' | ' : '', <RejectionLink {...link} key={index} />])}
|
||||
</span>
|
||||
) : null,
|
||||
});
|
||||
|
@ -50,12 +50,7 @@ class Finish extends React.Component<Props> {
|
||||
<div className={styles.codeContainer}>
|
||||
<div className={styles.code}>{code}</div>
|
||||
</div>
|
||||
<Button
|
||||
color="green"
|
||||
small
|
||||
label={messages.copy}
|
||||
onClick={this.onCopyClick}
|
||||
/>
|
||||
<Button color="green" small label={messages.copy} onClick={this.onCopyClick} />
|
||||
</div>
|
||||
) : (
|
||||
<div className={styles.description}>
|
||||
|
@ -51,11 +51,7 @@ export default class ForgotPasswordBody extends BaseAuthBody {
|
||||
<div data-testid="forgot-password-login">
|
||||
<div className={styles.login}>
|
||||
{login}
|
||||
<span
|
||||
className={styles.editLogin}
|
||||
onClick={this.onClickEdit}
|
||||
data-testid="edit-login"
|
||||
/>
|
||||
<span className={styles.editLogin} onClick={this.onClickEdit} data-testid="edit-login" />
|
||||
</div>
|
||||
<p className={styles.descriptionText}>
|
||||
<Message {...messages.pleasePressButton} />
|
||||
|
@ -20,12 +20,7 @@ export default class LoginBody extends BaseAuthBody {
|
||||
<div>
|
||||
{this.renderErrors()}
|
||||
|
||||
<Input
|
||||
{...this.bindField('login')}
|
||||
icon="envelope"
|
||||
required
|
||||
placeholder={messages.emailOrUsername}
|
||||
/>
|
||||
<Input {...this.bindField('login')} icon="envelope" required placeholder={messages.emailOrUsername} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -23,11 +23,7 @@ export default class PasswordBody extends BaseAuthBody {
|
||||
|
||||
<div className={styles.miniProfile}>
|
||||
<div className={styles.avatar}>
|
||||
{user.avatar ? (
|
||||
<img src={user.avatar} />
|
||||
) : (
|
||||
<span className={icons.user} />
|
||||
)}
|
||||
{user.avatar ? <img src={user.avatar} /> : <span className={icons.user} />}
|
||||
</div>
|
||||
<div className={styles.email}>{user.email || user.username}</div>
|
||||
</div>
|
||||
@ -41,11 +37,7 @@ export default class PasswordBody extends BaseAuthBody {
|
||||
/>
|
||||
|
||||
<div className={authStyles.checkboxInput}>
|
||||
<Checkbox
|
||||
{...this.bindField('rememberMe')}
|
||||
defaultChecked
|
||||
label={messages.rememberMe}
|
||||
/>
|
||||
<Checkbox {...this.bindField('rememberMe')} defaultChecked label={messages.rememberMe} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -22,11 +22,7 @@ export default class PermissionsBody extends BaseAuthBody {
|
||||
<PanelBodyHeader>
|
||||
<div className={styles.authInfo}>
|
||||
<div className={styles.authInfoAvatar}>
|
||||
{user.avatar ? (
|
||||
<img src={user.avatar} />
|
||||
) : (
|
||||
<span className={icons.user} />
|
||||
)}
|
||||
{user.avatar ? <img src={user.avatar} /> : <span className={icons.user} />}
|
||||
</div>
|
||||
<div className={styles.authInfoTitle}>
|
||||
<Message {...messages.youAuthorizedAs} />
|
||||
@ -50,9 +46,7 @@ export default class PermissionsBody extends BaseAuthBody {
|
||||
{message ? (
|
||||
<Message {...message} />
|
||||
) : (
|
||||
scope.replace(/^\w|_/g, (match) =>
|
||||
match.replace('_', ' ').toUpperCase(),
|
||||
)
|
||||
scope.replace(/^\w|_/g, (match) => match.replace('_', ' ').toUpperCase())
|
||||
)}
|
||||
</li>
|
||||
);
|
||||
|
@ -15,10 +15,7 @@ export default class RecoverPasswordBody extends BaseAuthBody {
|
||||
static panelId = 'recoverPassword';
|
||||
static hasGoBack = true;
|
||||
|
||||
autoFocusField =
|
||||
this.props.match.params && this.props.match.params.key
|
||||
? 'newPassword'
|
||||
: 'key';
|
||||
autoFocusField = this.props.match.params && this.props.match.params.key ? 'newPassword' : 'key';
|
||||
|
||||
render() {
|
||||
const { user } = this.context;
|
||||
|
@ -7,13 +7,9 @@ describe('components/auth/reducer', () => {
|
||||
it('should set login', () => {
|
||||
const expectedLogin = 'foo';
|
||||
|
||||
expect(
|
||||
auth(undefined, setLogin(expectedLogin)).credentials,
|
||||
'to satisfy',
|
||||
{
|
||||
expect(auth(undefined, setLogin(expectedLogin)).credentials, 'to satisfy', {
|
||||
login: expectedLogin,
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -63,10 +63,7 @@ export interface State {
|
||||
scopes: Scopes;
|
||||
}
|
||||
|
||||
const error: Reducer<State['error'], ErrorAction> = (
|
||||
state = null,
|
||||
{ type, payload },
|
||||
) => {
|
||||
const error: Reducer<State['error'], ErrorAction> = (state = null, { type, payload }) => {
|
||||
if (type === 'auth:error') {
|
||||
return payload;
|
||||
}
|
||||
@ -74,10 +71,7 @@ const error: Reducer<State['error'], ErrorAction> = (
|
||||
return state;
|
||||
};
|
||||
|
||||
const credentials: Reducer<State['credentials'], CredentialsAction> = (
|
||||
state = {},
|
||||
{ type, payload },
|
||||
) => {
|
||||
const credentials: Reducer<State['credentials'], CredentialsAction> = (state = {}, { type, payload }) => {
|
||||
if (type === 'auth:setCredentials') {
|
||||
if (payload) {
|
||||
return {
|
||||
@ -91,10 +85,10 @@ const credentials: Reducer<State['credentials'], CredentialsAction> = (
|
||||
return state;
|
||||
};
|
||||
|
||||
const isSwitcherEnabled: Reducer<
|
||||
State['isSwitcherEnabled'],
|
||||
AccountSwitcherAction
|
||||
> = (state = true, { type, payload }) => {
|
||||
const isSwitcherEnabled: Reducer<State['isSwitcherEnabled'], AccountSwitcherAction> = (
|
||||
state = true,
|
||||
{ type, payload },
|
||||
) => {
|
||||
if (type === 'auth:setAccountSwitcher') {
|
||||
return payload;
|
||||
}
|
||||
@ -102,10 +96,7 @@ const isSwitcherEnabled: Reducer<
|
||||
return state;
|
||||
};
|
||||
|
||||
const isLoading: Reducer<State['isLoading'], LoadingAction> = (
|
||||
state = false,
|
||||
{ type, payload },
|
||||
) => {
|
||||
const isLoading: Reducer<State['isLoading'], LoadingAction> = (state = false, { type, payload }) => {
|
||||
if (type === 'set_loading_state') {
|
||||
return payload;
|
||||
}
|
||||
@ -113,10 +104,7 @@ const isLoading: Reducer<State['isLoading'], LoadingAction> = (
|
||||
return state;
|
||||
};
|
||||
|
||||
const client: Reducer<State['client'], ClientAction> = (
|
||||
state = null,
|
||||
{ type, payload },
|
||||
) => {
|
||||
const client: Reducer<State['client'], ClientAction> = (state = null, { type, payload }) => {
|
||||
if (type === 'set_client') {
|
||||
return payload;
|
||||
}
|
||||
@ -143,10 +131,7 @@ const oauth: Reducer<State['oauth'], OAuthAction> = (state = null, action) => {
|
||||
}
|
||||
};
|
||||
|
||||
const scopes: Reducer<State['scopes'], ScopesAction> = (
|
||||
state = [],
|
||||
{ type, payload },
|
||||
) => {
|
||||
const scopes: Reducer<State['scopes'], ScopesAction> = (state = [], { type, payload }) => {
|
||||
if (type === 'set_scopes') {
|
||||
return payload;
|
||||
}
|
||||
@ -164,9 +149,7 @@ export default combineReducers<State>({
|
||||
scopes,
|
||||
});
|
||||
|
||||
export function getLogin(
|
||||
state: RootState | Pick<RootState, 'auth'>,
|
||||
): string | null {
|
||||
export function getLogin(state: RootState | Pick<RootState, 'auth'>): string | null {
|
||||
return state.auth.credentials.login || null;
|
||||
}
|
||||
|
||||
|
@ -28,12 +28,7 @@ describe('ContactForm', () => {
|
||||
|
||||
expect(screen.getAllByRole('textbox').length, 'to be greater than', 1);
|
||||
|
||||
expect(
|
||||
screen.getByRole('button', { name: /Send/ }),
|
||||
'to have property',
|
||||
'type',
|
||||
'submit',
|
||||
);
|
||||
expect(screen.getByRole('button', { name: /Send/ }), 'to have property', 'type', 'submit');
|
||||
|
||||
[
|
||||
{
|
||||
@ -49,12 +44,7 @@ describe('ContactForm', () => {
|
||||
name: 'message',
|
||||
},
|
||||
].forEach((el) => {
|
||||
expect(
|
||||
screen.getByLabelText(el.label, { exact: false }),
|
||||
'to have property',
|
||||
'name',
|
||||
el.name,
|
||||
);
|
||||
expect(screen.getByLabelText(el.label, { exact: false }), 'to have property', 'name', el.name);
|
||||
});
|
||||
});
|
||||
|
||||
@ -114,11 +104,7 @@ describe('ContactForm', () => {
|
||||
]);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByText('Your message was received', { exact: false }),
|
||||
'to be a',
|
||||
HTMLElement,
|
||||
);
|
||||
expect(screen.getByText('Your message was received', { exact: false }), 'to be a', HTMLElement);
|
||||
});
|
||||
|
||||
expect(screen.getByText(user.email), 'to be a', HTMLElement);
|
||||
@ -159,12 +145,7 @@ describe('ContactForm', () => {
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Send' }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByRole('alert'),
|
||||
'to have property',
|
||||
'innerHTML',
|
||||
'E‑mail is invalid',
|
||||
);
|
||||
expect(screen.getByRole('alert'), 'to have property', 'innerHTML', 'E‑mail is invalid');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -2,14 +2,7 @@ import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import clsx from 'clsx';
|
||||
import { FormattedMessage as Message } from 'react-intl';
|
||||
import {
|
||||
Input,
|
||||
TextArea,
|
||||
Button,
|
||||
Form,
|
||||
FormModel,
|
||||
Dropdown,
|
||||
} from 'app/components/ui/form';
|
||||
import { Input, TextArea, Button, Form, FormModel, Dropdown } from 'app/components/ui/form';
|
||||
import feedback from 'app/services/api/feedback';
|
||||
import icons from 'app/components/ui/icons.scss';
|
||||
import popupStyles from 'app/components/ui/popup/popup.scss';
|
||||
@ -57,12 +50,7 @@ export class ContactForm extends React.Component<
|
||||
const { onClose } = this.props;
|
||||
|
||||
return (
|
||||
<div
|
||||
data-testid="feedbackPopup"
|
||||
className={
|
||||
isSuccessfullySent ? styles.successState : styles.contactForm
|
||||
}
|
||||
>
|
||||
<div data-testid="feedbackPopup" className={isSuccessfullySent ? styles.successState : styles.contactForm}>
|
||||
<div className={popupStyles.popup}>
|
||||
<div className={popupStyles.header}>
|
||||
<h2 className={popupStyles.headerTitle}>
|
||||
@ -100,12 +88,7 @@ export class ContactForm extends React.Component<
|
||||
|
||||
<div className={styles.pairInputRow}>
|
||||
<div className={styles.pairInput}>
|
||||
<Input
|
||||
{...form.bindField('subject')}
|
||||
required
|
||||
label={messages.subject}
|
||||
skin="light"
|
||||
/>
|
||||
<Input {...form.bindField('subject')} required label={messages.subject} skin="light" />
|
||||
</div>
|
||||
|
||||
<div className={styles.pairInput}>
|
||||
@ -140,12 +123,7 @@ export class ContactForm extends React.Component<
|
||||
</div>
|
||||
|
||||
<div className={styles.footer}>
|
||||
<Button
|
||||
label={messages.send}
|
||||
block
|
||||
type="submit"
|
||||
disabled={isLoading}
|
||||
/>
|
||||
<Button label={messages.send} block type="submit" disabled={isLoading} />
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
@ -166,12 +144,7 @@ export class ContactForm extends React.Component<
|
||||
</div>
|
||||
|
||||
<div className={styles.footer}>
|
||||
<Button
|
||||
label={messages.close}
|
||||
block
|
||||
onClick={onClose}
|
||||
data-testid="feedback-popup-close-button"
|
||||
/>
|
||||
<Button label={messages.close} block onClick={onClose} data-testid="feedback-popup-close-button" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -70,15 +70,7 @@ export default class ApplicationsIndex extends React.Component<Props> {
|
||||
}
|
||||
|
||||
getContent() {
|
||||
const {
|
||||
displayForGuest,
|
||||
applications,
|
||||
isLoading,
|
||||
resetApp,
|
||||
deleteApp,
|
||||
clientId,
|
||||
resetClientId,
|
||||
} = this.props;
|
||||
const { displayForGuest, applications, isLoading, resetApp, deleteApp, clientId, resetClientId } = this.props;
|
||||
|
||||
if (applications.length > 0) {
|
||||
return (
|
||||
@ -103,10 +95,7 @@ export default class ApplicationsIndex extends React.Component<Props> {
|
||||
function Loader({ noApps }: { noApps: boolean }) {
|
||||
return (
|
||||
<div className={styles.emptyState} data-e2e={noApps ? 'noApps' : 'loading'}>
|
||||
<img
|
||||
src={noApps ? cubeIcon : loadingCubeIcon}
|
||||
className={styles.emptyStateIcon}
|
||||
/>
|
||||
<img src={noApps ? cubeIcon : loadingCubeIcon} className={styles.emptyStateIcon} />
|
||||
|
||||
<div
|
||||
className={clsx(styles.noAppsContainer, {
|
||||
|
@ -18,10 +18,7 @@ export function setAppsList(apps: Array<OauthAppResponse>): SetAvailableAction {
|
||||
};
|
||||
}
|
||||
|
||||
export function getApp(
|
||||
state: { apps: Apps },
|
||||
clientId: string,
|
||||
): OauthAppResponse | null {
|
||||
export function getApp(state: { apps: Apps }, clientId: string): OauthAppResponse | null {
|
||||
return state.apps.available.find((app) => app.clientId === clientId) || null;
|
||||
}
|
||||
|
||||
@ -46,10 +43,7 @@ function addApp(app: OauthAppResponse): AddAppAction {
|
||||
}
|
||||
|
||||
export function fetchAvailableApps() {
|
||||
return async (
|
||||
dispatch: Dispatch<any>,
|
||||
getState: () => { user: User },
|
||||
): Promise<void> => {
|
||||
return async (dispatch: Dispatch<any>, getState: () => { user: User }): Promise<void> => {
|
||||
const { id } = getState().user;
|
||||
|
||||
if (!id) {
|
||||
@ -84,10 +78,7 @@ function createDeleteAppAction(clientId: string): DeleteAppAction {
|
||||
};
|
||||
}
|
||||
|
||||
export function resetApp(
|
||||
clientId: string,
|
||||
resetSecret: boolean,
|
||||
): ThunkAction<Promise<void>> {
|
||||
export function resetApp(clientId: string, resetSecret: boolean): ThunkAction<Promise<void>> {
|
||||
return async (dispatch) => {
|
||||
const { data: app } = await oauth.reset(clientId, resetSecret);
|
||||
|
||||
|
@ -7,10 +7,7 @@ import { ApplicationType } from 'app/components/dev/apps';
|
||||
import { Form, FormModel, Button } from 'app/components/ui/form';
|
||||
import { BackButton } from 'app/components/profile/ProfileForm';
|
||||
import { COLOR_GREEN } from 'app/components/ui';
|
||||
import {
|
||||
TYPE_APPLICATION,
|
||||
TYPE_MINECRAFT_SERVER,
|
||||
} from 'app/components/dev/apps';
|
||||
import { TYPE_APPLICATION, TYPE_MINECRAFT_SERVER } from 'app/components/dev/apps';
|
||||
import styles from 'app/components/profile/profileForm.scss';
|
||||
import logger from 'app/services/logger';
|
||||
import messages from './ApplicationForm.intl.json';
|
||||
@ -40,13 +37,14 @@ const typeToForm: TypeToForm = {
|
||||
|
||||
type TypeToLabel = Record<ApplicationType, MessageDescriptor>;
|
||||
|
||||
const typeToLabel: TypeToLabel = ((Object.keys(typeToForm) as unknown) as Array<
|
||||
ApplicationType
|
||||
>).reduce((result, key) => {
|
||||
const typeToLabel: TypeToLabel = ((Object.keys(typeToForm) as unknown) as Array<ApplicationType>).reduce(
|
||||
(result, key) => {
|
||||
result[key] = typeToForm[key].label;
|
||||
|
||||
return result;
|
||||
}, {} as TypeToLabel);
|
||||
},
|
||||
{} as TypeToLabel,
|
||||
);
|
||||
|
||||
export default class ApplicationForm extends React.Component<{
|
||||
app: OauthAppResponse;
|
||||
@ -72,11 +70,7 @@ export default class ApplicationForm extends React.Component<{
|
||||
|
||||
<div className={styles.form}>
|
||||
<div className={styles.formBody}>
|
||||
<Message
|
||||
{...(isUpdate
|
||||
? messages.updatingApplication
|
||||
: messages.creatingApplication)}
|
||||
>
|
||||
<Message {...(isUpdate ? messages.updatingApplication : messages.creatingApplication)}>
|
||||
{(pageTitle: string) => (
|
||||
<h3 className={styles.title}>
|
||||
<Helmet title={pageTitle} />
|
||||
@ -100,9 +94,7 @@ export default class ApplicationForm extends React.Component<{
|
||||
) : (
|
||||
<div className={styles.formRow}>
|
||||
<p className={styles.description}>
|
||||
<Message
|
||||
{...messages.toDisplayRegistrationFormChooseType}
|
||||
/>
|
||||
<Message {...messages.toDisplayRegistrationFormChooseType} />
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
@ -113,11 +105,7 @@ export default class ApplicationForm extends React.Component<{
|
||||
<Button
|
||||
color={COLOR_GREEN}
|
||||
block
|
||||
label={
|
||||
isUpdate
|
||||
? messages.updateApplication
|
||||
: messages.createApplication
|
||||
}
|
||||
label={isUpdate ? messages.updateApplication : messages.createApplication}
|
||||
type="submit"
|
||||
/>
|
||||
)}
|
||||
|
@ -12,14 +12,9 @@ interface Props {
|
||||
setType: (type: ApplicationType) => void;
|
||||
}
|
||||
|
||||
const ApplicationTypeSwitcher: ComponentType<Props> = ({
|
||||
appTypes,
|
||||
selectedType,
|
||||
setType,
|
||||
}) => (
|
||||
const ApplicationTypeSwitcher: ComponentType<Props> = ({ appTypes, selectedType, setType }) => (
|
||||
<div>
|
||||
{((Object.keys(appTypes) as unknown) as Array<ApplicationType>).map(
|
||||
(type) => (
|
||||
{((Object.keys(appTypes) as unknown) as Array<ApplicationType>).map((type) => (
|
||||
<div className={styles.radioContainer} key={type}>
|
||||
<Radio
|
||||
onChange={() => setType(type)}
|
||||
@ -29,8 +24,7 @@ const ApplicationTypeSwitcher: ComponentType<Props> = ({
|
||||
checked={selectedType === type}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
)}
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
|
@ -40,10 +40,7 @@ export default class ApplicationItem extends React.Component<
|
||||
application: OauthAppResponse;
|
||||
expand: boolean;
|
||||
onTileClick: (clientId: string) => void;
|
||||
onResetSubmit: (
|
||||
clientId: string,
|
||||
resetClientSecret: boolean,
|
||||
) => Promise<void>;
|
||||
onResetSubmit: (clientId: string, resetClientSecret: boolean) => Promise<void>;
|
||||
onDeleteSubmit: (clientId: string) => Promise<void>;
|
||||
},
|
||||
State
|
||||
@ -93,15 +90,9 @@ export default class ApplicationItem extends React.Component<
|
||||
</div>
|
||||
|
||||
<Collapse isOpened={expand} onRest={this.onCollapseRest}>
|
||||
<div
|
||||
className={styles.appDetailsContainer}
|
||||
style={{ transform: `translateY(-${translateY}px)` }}
|
||||
>
|
||||
<div className={styles.appDetailsContainer} style={{ transform: `translateY(-${translateY}px)` }}>
|
||||
<div className={styles.appDetailsInfoField}>
|
||||
<Link
|
||||
to={`/dev/applications/${app.clientId}`}
|
||||
className={styles.editAppLink}
|
||||
>
|
||||
<Link to={`/dev/applications/${app.clientId}`} className={styles.editAppLink}>
|
||||
<Message
|
||||
{...messages.editDescription}
|
||||
values={{
|
||||
@ -109,13 +100,7 @@ export default class ApplicationItem extends React.Component<
|
||||
}}
|
||||
/>
|
||||
</Link>
|
||||
<Input
|
||||
label="Client ID:"
|
||||
skin={SKIN_LIGHT}
|
||||
disabled
|
||||
value={app.clientId}
|
||||
copy
|
||||
/>
|
||||
<Input label="Client ID:" skin={SKIN_LIGHT} disabled value={app.clientId} copy />
|
||||
</div>
|
||||
|
||||
<div className={styles.appDetailsInfoField}>
|
||||
@ -130,9 +115,7 @@ export default class ApplicationItem extends React.Component<
|
||||
</div>
|
||||
|
||||
<div className={styles.appDetailsDescription}>
|
||||
<Message
|
||||
{...messages.ifYouSuspectingThatSecretHasBeenCompromised}
|
||||
/>
|
||||
<Message {...messages.ifYouSuspectingThatSecretHasBeenCompromised} />
|
||||
</div>
|
||||
|
||||
<div className={styles.appActionsButtons}>
|
||||
@ -191,9 +174,7 @@ export default class ApplicationItem extends React.Component<
|
||||
) : (
|
||||
<div
|
||||
className={styles.continueActionLink}
|
||||
onClick={this.onResetSubmit(
|
||||
selectedAction === ACTION_RESET_SECRET,
|
||||
)}
|
||||
onClick={this.onResetSubmit(selectedAction === ACTION_RESET_SECRET)}
|
||||
>
|
||||
<Message {...messages.continue} />
|
||||
</div>
|
||||
|
@ -80,21 +80,12 @@ export default class ApplicationsList extends React.Component<Props, State> {
|
||||
const { applications, clientId } = this.props;
|
||||
const { expandedApp } = this.state;
|
||||
|
||||
if (
|
||||
clientId &&
|
||||
expandedApp !== clientId &&
|
||||
applications.some((app) => app.clientId === clientId)
|
||||
) {
|
||||
requestAnimationFrame(() =>
|
||||
this.onTileClick(clientId, { noReset: true }),
|
||||
);
|
||||
if (clientId && expandedApp !== clientId && applications.some((app) => app.clientId === clientId)) {
|
||||
requestAnimationFrame(() => this.onTileClick(clientId, { noReset: true }));
|
||||
}
|
||||
}
|
||||
|
||||
onTileClick = (
|
||||
clientId: string,
|
||||
{ noReset = false }: { noReset?: boolean } = {},
|
||||
) => {
|
||||
onTileClick = (clientId: string, { noReset = false }: { noReset?: boolean } = {}) => {
|
||||
const { clientId: initialClientId, resetClientId } = this.props;
|
||||
const expandedApp = this.state.expandedApp === clientId ? null : clientId;
|
||||
|
||||
|
@ -21,9 +21,7 @@ export default function apps(state: Apps = defaults, action: Action): Apps {
|
||||
case 'apps:addApp': {
|
||||
const { payload } = action;
|
||||
const available = [...state.available];
|
||||
let index = available.findIndex(
|
||||
(app) => app.clientId === payload.clientId,
|
||||
);
|
||||
let index = available.findIndex((app) => app.clientId === payload.clientId);
|
||||
|
||||
if (index === -1) {
|
||||
index = available.length;
|
||||
@ -40,9 +38,7 @@ export default function apps(state: Apps = defaults, action: Action): Apps {
|
||||
case 'apps:deleteApp':
|
||||
return {
|
||||
...state,
|
||||
available: state.available.filter(
|
||||
(app) => app.clientId !== action.payload,
|
||||
),
|
||||
available: state.available.filter((app) => app.clientId !== action.payload),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -3,10 +3,9 @@ import { storiesOf } from '@storybook/react';
|
||||
|
||||
import FooterMenu from './FooterMenu';
|
||||
|
||||
const PreviewWrapper: ComponentType<{ style?: CSSProperties }> = ({
|
||||
style,
|
||||
children,
|
||||
}) => <div style={{ padding: '25px', ...style }}>{children}</div>;
|
||||
const PreviewWrapper: ComponentType<{ style?: CSSProperties }> = ({ style, children }) => (
|
||||
<div style={{ padding: '25px', ...style }}>{children}</div>
|
||||
);
|
||||
|
||||
storiesOf('Components', module).add('FooterMenu', () => (
|
||||
<div style={{ display: 'flex' }}>
|
||||
|
@ -11,9 +11,7 @@ import messages from './footerMenu.intl.json';
|
||||
|
||||
const FooterMenu: ComponentType = () => {
|
||||
const dispatch = useDispatch();
|
||||
const onLanguageSwitcherClick = useCallback<
|
||||
MouseEventHandler<HTMLAnchorElement>
|
||||
>(
|
||||
const onLanguageSwitcherClick = useCallback<MouseEventHandler<HTMLAnchorElement>>(
|
||||
(event) => {
|
||||
event.preventDefault();
|
||||
dispatch(createPopup({ Popup: LanguageSwitcher }));
|
||||
@ -34,11 +32,7 @@ const FooterMenu: ComponentType = () => {
|
||||
</Link>
|
||||
|
||||
<div className={styles.langTriggerContainer}>
|
||||
<a
|
||||
href="#"
|
||||
className={styles.langTrigger}
|
||||
onClick={onLanguageSwitcherClick}
|
||||
>
|
||||
<a href="#" className={styles.langTrigger} onClick={onLanguageSwitcherClick}>
|
||||
<span className={styles.langTriggerIcon} />
|
||||
<Message {...messages.siteLanguage} />
|
||||
</a>
|
||||
|
@ -6,9 +6,7 @@ import { RootState } from 'app/reducers';
|
||||
|
||||
const IntlProvider: ComponentType = ({ children }) => {
|
||||
const [intl, setIntl] = useState<IntlShape>(i18n.getIntl());
|
||||
const locale = useSelector(
|
||||
({ i18n: i18nState }: RootState) => i18nState.locale,
|
||||
);
|
||||
const locale = useSelector(({ i18n: i18nState }: RootState) => i18nState.locale);
|
||||
|
||||
useEffect(() => {
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
|
@ -14,9 +14,7 @@ const SUPPORTED_LANGUAGES: string[] = Object.keys(supportedLocales);
|
||||
|
||||
export default {
|
||||
getCountryList(): string[] {
|
||||
return SUPPORTED_LANGUAGES.map(
|
||||
(locale) => localeToCountryCode[locale] || locale,
|
||||
);
|
||||
return SUPPORTED_LANGUAGES.map((locale) => localeToCountryCode[locale] || locale);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -29,9 +27,7 @@ export default {
|
||||
*/
|
||||
getIconUrl(locale: string): string {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const mod = require(`flag-icon-css/flags/4x3/${
|
||||
localeToCountryCode[locale] || locale
|
||||
}.svg`);
|
||||
const mod = require(`flag-icon-css/flags/4x3/${localeToCountryCode[locale] || locale}.svg`);
|
||||
|
||||
return mod.default || mod;
|
||||
},
|
||||
|
@ -10,10 +10,7 @@ const defaultState: State = {
|
||||
locale: i18n.detectLanguage(),
|
||||
};
|
||||
|
||||
export default function (
|
||||
state: State = defaultState,
|
||||
{ type, payload }: Action,
|
||||
): State {
|
||||
export default function (state: State = defaultState, { type, payload }: Action): State {
|
||||
if (type === 'i18n:setLocale') {
|
||||
return payload;
|
||||
}
|
||||
|
@ -79,15 +79,11 @@ export default class LanguageList extends React.Component<{
|
||||
})}
|
||||
style={{
|
||||
height:
|
||||
isListEmpty && this.emptyListStateInner
|
||||
? this.emptyListStateInner.clientHeight
|
||||
: 0,
|
||||
isListEmpty && this.emptyListStateInner ? this.emptyListStateInner.clientHeight : 0,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
ref={(elem: HTMLDivElement | null) =>
|
||||
(this.emptyListStateInner = elem)
|
||||
}
|
||||
ref={(elem: HTMLDivElement | null) => (this.emptyListStateInner = elem)}
|
||||
className={styles.emptyLanguagesList}
|
||||
>
|
||||
<img
|
||||
|
@ -72,19 +72,13 @@ class LanguageSwitcher extends React.Component<
|
||||
<h2 className={popupStyles.headerTitle}>
|
||||
<Message {...messages.siteLanguage} />
|
||||
</h2>
|
||||
<span
|
||||
className={clsx(icons.close, popupStyles.close)}
|
||||
onClick={onClose}
|
||||
/>
|
||||
<span className={clsx(icons.close, popupStyles.close)} onClick={onClose} />
|
||||
</div>
|
||||
|
||||
<div className={styles.languageSwitcherBody}>
|
||||
<div className={styles.searchBox}>
|
||||
<input
|
||||
className={clsx(
|
||||
formStyles.lightTextField,
|
||||
formStyles.greenTextField,
|
||||
)}
|
||||
className={clsx(formStyles.lightTextField, formStyles.greenTextField)}
|
||||
placeholder={intl.formatMessage(messages.startTyping)}
|
||||
onChange={this.onFilterUpdate}
|
||||
onKeyPress={this.onFilterKeyPress()}
|
||||
|
@ -10,9 +10,7 @@ interface Props {
|
||||
locale: LocaleData;
|
||||
}
|
||||
|
||||
const LocaleItem: ComponentType<Props> = ({
|
||||
locale: { code, name, englishName, progress, isReleased },
|
||||
}) => {
|
||||
const LocaleItem: ComponentType<Props> = ({ locale: { code, name, englishName, progress, isReleased } }) => {
|
||||
let progressLabel: ReactNode;
|
||||
|
||||
if (progress !== 100) {
|
||||
|
@ -30,9 +30,7 @@ const LanguageLink: ComponentType = () => {
|
||||
<span
|
||||
className={styles.languageIcon}
|
||||
style={{
|
||||
backgroundImage: `url('${localeFlags.getIconUrl(
|
||||
localeDefinition.code,
|
||||
)}')`,
|
||||
backgroundImage: `url('${localeFlags.getIconUrl(localeDefinition.code)}')`,
|
||||
}}
|
||||
/>
|
||||
{localeDefinition.name}
|
||||
|
@ -3,10 +3,7 @@ import { FormModel } from 'app/components/ui/form';
|
||||
|
||||
export interface ProfileContext {
|
||||
userId: number;
|
||||
onSubmit: (options: {
|
||||
form: FormModel;
|
||||
sendData: () => Promise<any>;
|
||||
}) => Promise<void>;
|
||||
onSubmit: (options: { form: FormModel; sendData: () => Promise<any> }) => Promise<void>;
|
||||
goToProfile: () => Promise<void>;
|
||||
}
|
||||
|
||||
|
@ -94,11 +94,7 @@ class Profile extends React.PureComponent<Props> {
|
||||
<Message
|
||||
{...messages.changedAt}
|
||||
values={{
|
||||
at: (
|
||||
<RelativeTime
|
||||
timestamp={user.passwordChangedAt * 1000}
|
||||
/>
|
||||
),
|
||||
at: <RelativeTime timestamp={user.passwordChangedAt * 1000} />,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
@ -117,9 +113,7 @@ class Profile extends React.PureComponent<Props> {
|
||||
locale: user.lang,
|
||||
participateInTheTranslation: (
|
||||
<a href="http://ely.by/translate" target="_blank">
|
||||
<Message
|
||||
{...messages.participateInTheTranslation}
|
||||
/>
|
||||
<Message {...messages.participateInTheTranslation} />
|
||||
</a>
|
||||
),
|
||||
}}
|
||||
|
@ -33,19 +33,13 @@ function ProfileField({
|
||||
<div className={styles.paramValue}>{value}</div>
|
||||
|
||||
{Action && (
|
||||
<Action
|
||||
to={link}
|
||||
className={styles.paramAction}
|
||||
data-testid="profile-action"
|
||||
>
|
||||
<Action to={link} className={styles.paramAction} data-testid="profile-action">
|
||||
<span className={styles.paramEditIcon} />
|
||||
</Action>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{warningMessage && (
|
||||
<div className={styles.paramMessage}>{warningMessage}</div>
|
||||
)}
|
||||
{warningMessage && <div className={styles.paramMessage}>{warningMessage}</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -3,13 +3,7 @@ import { FormattedMessage as Message } from 'react-intl';
|
||||
import { Helmet } from 'react-helmet-async';
|
||||
import { SlideMotion } from 'app/components/ui/motion';
|
||||
import { ScrollIntoView } from 'app/components/ui/scroll';
|
||||
import {
|
||||
Input,
|
||||
Button,
|
||||
Form,
|
||||
FormModel,
|
||||
FormError,
|
||||
} from 'app/components/ui/form';
|
||||
import { Input, Button, Form, FormModel, FormError } from 'app/components/ui/form';
|
||||
import { BackButton } from 'app/components/profile/ProfileForm';
|
||||
import styles from 'app/components/profile/profileForm.scss';
|
||||
import helpLinks from 'app/components/auth/helpLinks.scss';
|
||||
@ -63,8 +57,7 @@ export default class ChangeEmail extends React.Component<Props, State> {
|
||||
|
||||
static getDerivedStateFromProps(props: Props, state: State) {
|
||||
return {
|
||||
activeStep:
|
||||
typeof props.step === 'number' ? props.step : state.activeStep,
|
||||
activeStep: typeof props.step === 'number' ? props.step : state.activeStep,
|
||||
code: props.code || state.code,
|
||||
};
|
||||
}
|
||||
@ -74,15 +67,8 @@ export default class ChangeEmail extends React.Component<Props, State> {
|
||||
const form = this.props.stepForms[activeStep];
|
||||
|
||||
return (
|
||||
<Form
|
||||
form={form}
|
||||
onSubmit={this.onFormSubmit}
|
||||
onInvalid={() => this.forceUpdate()}
|
||||
>
|
||||
<div
|
||||
className={styles.contentWithBackButton}
|
||||
data-testid="change-email"
|
||||
>
|
||||
<Form form={form} onSubmit={this.onFormSubmit} onInvalid={() => this.forceUpdate()}>
|
||||
<div className={styles.contentWithBackButton} data-testid="change-email">
|
||||
<BackButton />
|
||||
|
||||
<div className={styles.form}>
|
||||
@ -105,11 +91,7 @@ export default class ChangeEmail extends React.Component<Props, State> {
|
||||
</div>
|
||||
|
||||
<div className={styles.stepper}>
|
||||
<Stepper
|
||||
color="violet"
|
||||
totalSteps={STEPS_TOTAL}
|
||||
activeStep={activeStep}
|
||||
/>
|
||||
<Stepper color="violet" totalSteps={STEPS_TOTAL} activeStep={activeStep} />
|
||||
</div>
|
||||
|
||||
<div className={styles.form}>
|
||||
@ -121,11 +103,7 @@ export default class ChangeEmail extends React.Component<Props, State> {
|
||||
color="violet"
|
||||
type="submit"
|
||||
block
|
||||
label={
|
||||
this.isLastStep()
|
||||
? messages.changeEmailButton
|
||||
: messages.sendEmailButton
|
||||
}
|
||||
label={this.isLastStep() ? messages.changeEmailButton : messages.sendEmailButton}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -194,13 +172,7 @@ export default class ChangeEmail extends React.Component<Props, State> {
|
||||
);
|
||||
}
|
||||
|
||||
renderStep1({
|
||||
email,
|
||||
form,
|
||||
code,
|
||||
isCodeSpecified,
|
||||
isActiveStep,
|
||||
}: FormStepParams): ReactNode {
|
||||
renderStep1({ email, form, code, isCodeSpecified, isActiveStep }: FormStepParams): ReactNode {
|
||||
return (
|
||||
<div key="step1" data-testid="step2" className={styles.formBody}>
|
||||
<div className={styles.formRow}>
|
||||
@ -247,12 +219,7 @@ export default class ChangeEmail extends React.Component<Props, State> {
|
||||
);
|
||||
}
|
||||
|
||||
renderStep2({
|
||||
form,
|
||||
code,
|
||||
isCodeSpecified,
|
||||
isActiveStep,
|
||||
}: FormStepParams): ReactNode {
|
||||
renderStep2({ form, code, isCodeSpecified, isActiveStep }: FormStepParams): ReactNode {
|
||||
const { newEmail } = this.state;
|
||||
|
||||
return (
|
||||
|
@ -14,12 +14,8 @@ describe('<ChangePassword />', () => {
|
||||
</TestContextProvider>,
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.getByLabelText('New password', { exact: false }),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByLabelText('Repeat the password', { exact: false }),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByLabelText('New password', { exact: false })).toBeInTheDocument();
|
||||
expect(screen.getByLabelText('Repeat the password', { exact: false })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should call onSubmit if passwords entered', async () => {
|
||||
@ -37,20 +33,13 @@ describe('<ChangePassword />', () => {
|
||||
},
|
||||
});
|
||||
|
||||
fireEvent.change(
|
||||
screen.getByLabelText('Repeat the password', { exact: false }),
|
||||
{
|
||||
fireEvent.change(screen.getByLabelText('Repeat the password', { exact: false }), {
|
||||
target: {
|
||||
value: '123',
|
||||
},
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
fireEvent.click(
|
||||
screen
|
||||
.getByRole('button', { name: 'Change password' })
|
||||
.closest('button') as HTMLButtonElement,
|
||||
);
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Change password' }).closest('button') as HTMLButtonElement);
|
||||
|
||||
uxpect(onSubmit, 'was called');
|
||||
});
|
||||
|
@ -1,13 +1,7 @@
|
||||
import React from 'react';
|
||||
import { FormattedMessage as Message } from 'react-intl';
|
||||
import { Helmet } from 'react-helmet-async';
|
||||
import {
|
||||
Input,
|
||||
Button,
|
||||
Checkbox,
|
||||
Form,
|
||||
FormModel,
|
||||
} from 'app/components/ui/form';
|
||||
import { Input, Button, Checkbox, Form, FormModel } from 'app/components/ui/form';
|
||||
|
||||
import { BackButton } from '../ProfileForm';
|
||||
import styles from '../profileForm.scss';
|
||||
@ -90,12 +84,7 @@ export default class ChangePassword extends React.Component<Props> {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
color="green"
|
||||
block
|
||||
label={messages.changePasswordButton}
|
||||
type="submit"
|
||||
/>
|
||||
<Button color="green" block label={messages.changePasswordButton} type="submit" />
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
|
@ -63,12 +63,7 @@ export default class ChangeUsername extends React.Component<Props> {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
color="green"
|
||||
block
|
||||
label={messages.changeUsernameButton}
|
||||
type="submit"
|
||||
/>
|
||||
<Button color="green" block label={messages.changeUsernameButton} type="submit" />
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
|
@ -26,11 +26,7 @@ export default class MfaDisable extends React.Component<
|
||||
render() {
|
||||
const { showForm } = this.state;
|
||||
|
||||
return showForm ? (
|
||||
<MfaDisableForm onSubmit={this.onSubmit} />
|
||||
) : (
|
||||
<MfaStatus onProceed={this.onProceed} />
|
||||
);
|
||||
return showForm ? <MfaDisableForm onSubmit={this.onSubmit} /> : <MfaStatus onProceed={this.onProceed} />;
|
||||
}
|
||||
|
||||
onProceed = () => this.setState({ showForm: true });
|
||||
|
@ -82,8 +82,7 @@ export default class MfaEnable extends React.PureComponent<Props, State> {
|
||||
},
|
||||
{
|
||||
buttonLabel: messages.enable,
|
||||
buttonAction: () =>
|
||||
this.confirmationFormEl && this.confirmationFormEl.submit(),
|
||||
buttonAction: () => this.confirmationFormEl && this.confirmationFormEl.submit(),
|
||||
},
|
||||
];
|
||||
|
||||
@ -100,13 +99,7 @@ export default class MfaEnable extends React.PureComponent<Props, State> {
|
||||
|
||||
{this.renderStepForms()}
|
||||
|
||||
<Button
|
||||
color="green"
|
||||
onClick={buttonAction}
|
||||
loading={isLoading}
|
||||
block
|
||||
label={buttonLabel}
|
||||
/>
|
||||
<Button color="green" onClick={buttonAction} loading={isLoading} block label={buttonLabel} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -17,13 +17,7 @@ class MultiFactorAuth extends React.Component<{
|
||||
onChangeStep: (nextStep: number) => void;
|
||||
}> {
|
||||
render() {
|
||||
const {
|
||||
step,
|
||||
onSubmit,
|
||||
onComplete,
|
||||
onChangeStep,
|
||||
isMfaEnabled,
|
||||
} = this.props;
|
||||
const { step, onSubmit, onComplete, onChangeStep, isMfaEnabled } = this.props;
|
||||
|
||||
return (
|
||||
<div className={styles.contentWithBackButton}>
|
||||
@ -47,18 +41,11 @@ class MultiFactorAuth extends React.Component<{
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isMfaEnabled && (
|
||||
<MfaDisable onSubmit={onSubmit} onComplete={onComplete} />
|
||||
)}
|
||||
{isMfaEnabled && <MfaDisable onSubmit={onSubmit} onComplete={onComplete} />}
|
||||
</div>
|
||||
|
||||
{isMfaEnabled || (
|
||||
<MfaEnable
|
||||
step={step}
|
||||
onSubmit={onSubmit}
|
||||
onChangeStep={onChangeStep}
|
||||
onComplete={onComplete}
|
||||
/>
|
||||
<MfaEnable step={step} onSubmit={onSubmit} onChangeStep={onChangeStep} onComplete={onComplete} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
@ -16,34 +16,28 @@ const linksByOs: {
|
||||
searchLink: 'https://play.google.com/store/search?q=totp%20authenticator',
|
||||
featured: [
|
||||
{
|
||||
link:
|
||||
'https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2',
|
||||
link: 'https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2',
|
||||
label: 'Google Authenticator',
|
||||
},
|
||||
{
|
||||
link:
|
||||
'https://play.google.com/store/apps/details?id=org.fedorahosted.freeotp',
|
||||
link: 'https://play.google.com/store/apps/details?id=org.fedorahosted.freeotp',
|
||||
label: 'FreeOTP Authenticator',
|
||||
},
|
||||
{
|
||||
link:
|
||||
'https://play.google.com/store/apps/details?id=com.authenticator.authservice',
|
||||
link: 'https://play.google.com/store/apps/details?id=com.authenticator.authservice',
|
||||
label: 'TOTP Authenticator',
|
||||
},
|
||||
],
|
||||
},
|
||||
ios: {
|
||||
searchLink:
|
||||
'https://linkmaker.itunes.apple.com/en-us?mediaType=ios_apps&term=authenticator',
|
||||
searchLink: 'https://linkmaker.itunes.apple.com/en-us?mediaType=ios_apps&term=authenticator',
|
||||
featured: [
|
||||
{
|
||||
link:
|
||||
'https://itunes.apple.com/ru/app/google-authenticator/id388497605',
|
||||
link: 'https://itunes.apple.com/ru/app/google-authenticator/id388497605',
|
||||
label: 'Google Authenticator',
|
||||
},
|
||||
{
|
||||
link:
|
||||
'https://itunes.apple.com/us/app/otp-auth-two-factor-authentication-for-pros/id659877384',
|
||||
link: 'https://itunes.apple.com/us/app/otp-auth-two-factor-authentication-for-pros/id659877384',
|
||||
label: 'OTP Auth',
|
||||
},
|
||||
{
|
||||
@ -53,22 +47,18 @@ const linksByOs: {
|
||||
],
|
||||
},
|
||||
windows: {
|
||||
searchLink:
|
||||
'https://www.microsoft.com/be-by/store/search/apps?devicetype=mobile&q=authenticator',
|
||||
searchLink: 'https://www.microsoft.com/be-by/store/search/apps?devicetype=mobile&q=authenticator',
|
||||
featured: [
|
||||
{
|
||||
link:
|
||||
'https://www.microsoft.com/en-us/store/p/microsoft-authenticator/9nblgggzmcj6',
|
||||
link: 'https://www.microsoft.com/en-us/store/p/microsoft-authenticator/9nblgggzmcj6',
|
||||
label: 'Microsoft Authenticator',
|
||||
},
|
||||
{
|
||||
link:
|
||||
'https://www.microsoft.com/en-us/store/p/authenticator/9nblggh08h54',
|
||||
link: 'https://www.microsoft.com/en-us/store/p/authenticator/9nblggh08h54',
|
||||
label: 'Authenticator+',
|
||||
},
|
||||
{
|
||||
link:
|
||||
'https://www.microsoft.com/en-us/store/p/authenticator-for-windows/9nblggh4n8mx',
|
||||
link: 'https://www.microsoft.com/en-us/store/p/authenticator-for-windows/9nblggh4n8mx',
|
||||
label: 'Authenticator for Windows',
|
||||
},
|
||||
],
|
||||
@ -77,11 +67,7 @@ const linksByOs: {
|
||||
|
||||
export default function OsInstruction({ os }: { os: OS }) {
|
||||
return (
|
||||
<div
|
||||
className={styles.osInstruction}
|
||||
data-testid="os-instruction"
|
||||
data-os={os}
|
||||
>
|
||||
<div className={styles.osInstruction} data-testid="os-instruction" data-os={os}>
|
||||
<h3 className={styles.instructionTitle}>
|
||||
<Message {...messages.installOnOfTheApps} />
|
||||
</h3>
|
||||
|
@ -15,11 +15,7 @@ export default function OsInstruction({
|
||||
onClick: (event: React.MouseEvent<any>) => void;
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
className={clsx(styles.osTile, className)}
|
||||
onClick={onClick}
|
||||
data-testid="os-tile"
|
||||
>
|
||||
<div className={clsx(styles.osTile, className)} onClick={onClick} data-testid="os-tile">
|
||||
<img className={styles.osLogo} src={logo} alt={label} />
|
||||
<div className={styles.osName}>{label}</div>
|
||||
</div>
|
||||
|
@ -7,13 +7,7 @@ import profileForm from 'app/components/profile/profileForm.scss';
|
||||
import messages from '../MultiFactorAuth.intl.json';
|
||||
import styles from './key-form.scss';
|
||||
|
||||
export default function KeyForm({
|
||||
secret,
|
||||
qrCodeSrc,
|
||||
}: {
|
||||
secret: string;
|
||||
qrCodeSrc: string;
|
||||
}) {
|
||||
export default function KeyForm({ secret, qrCodeSrc }: { secret: string; qrCodeSrc: string }) {
|
||||
const formattedSecret = formatSecret(secret || new Array(24).join('X'));
|
||||
|
||||
return (
|
||||
|
@ -14,10 +14,7 @@ interface Props {
|
||||
}
|
||||
|
||||
const PasswordRequestForm: ComponentType<Props> = ({ form, onSubmit }) => (
|
||||
<div
|
||||
className={styles.requestPasswordForm}
|
||||
data-testid="password-request-form"
|
||||
>
|
||||
<div className={styles.requestPasswordForm} data-testid="password-request-form">
|
||||
<div className={popupStyles.popup}>
|
||||
<Form onSubmit={onSubmit} form={form}>
|
||||
<div className={popupStyles.header}>
|
||||
|
@ -5,11 +5,7 @@ import { omit } from 'app/functions';
|
||||
import styles from './panel.scss';
|
||||
import icons from './icons.scss';
|
||||
|
||||
export function Panel(props: {
|
||||
title?: string;
|
||||
icon?: string;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
export function Panel(props: { title?: string; icon?: string; children: React.ReactNode }) {
|
||||
const { title: titleText, icon: iconType } = props;
|
||||
let icon: React.ReactElement | undefined;
|
||||
let title: React.ReactElement | undefined;
|
||||
|
@ -10,9 +10,7 @@ function RelativeTime({ timestamp }: { timestamp: number }) {
|
||||
value={value}
|
||||
unit={unit}
|
||||
numeric="auto"
|
||||
updateIntervalInSeconds={
|
||||
['seconds', 'minute', 'hour'].includes(unit) ? 1 : undefined
|
||||
}
|
||||
updateIntervalInSeconds={['seconds', 'minute', 'hour'].includes(unit) ? 1 : undefined}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -27,10 +27,7 @@ const BSoD: ComponentType<Props> = ({ lastEventId }) => {
|
||||
|
||||
return (
|
||||
<div className={styles.body}>
|
||||
<canvas
|
||||
className={styles.canvas}
|
||||
ref={(el: HTMLCanvasElement | null) => el && new BoxesField(el)}
|
||||
/>
|
||||
<canvas className={styles.canvas} ref={(el: HTMLCanvasElement | null) => el && new BoxesField(el)} />
|
||||
|
||||
<div className={styles.wrapper}>
|
||||
<div className={styles.title}>
|
||||
|
@ -11,13 +11,7 @@ export default class Box {
|
||||
private _size: number;
|
||||
private _halfSize: number;
|
||||
|
||||
constructor(
|
||||
size: number,
|
||||
position: Point,
|
||||
startRotate: number,
|
||||
color: string,
|
||||
shadowColor: string,
|
||||
) {
|
||||
constructor(size: number, position: Point, startRotate: number, color: string, shadowColor: string) {
|
||||
this.size = size;
|
||||
this.position = position;
|
||||
this.color = color;
|
||||
|
@ -35,14 +35,7 @@ export default class BoxesField {
|
||||
backgroundColor: '#233d49',
|
||||
lightColor: '#28555b',
|
||||
shadowColor: '#274451',
|
||||
boxColors: [
|
||||
'#207e5c',
|
||||
'#5b9aa9',
|
||||
'#e66c69',
|
||||
'#6b5b8c',
|
||||
'#8b5d79',
|
||||
'#dd8650',
|
||||
],
|
||||
boxColors: ['#207e5c', '#5b9aa9', '#e66c69', '#6b5b8c', '#8b5d79', '#dd8650'],
|
||||
},
|
||||
) {
|
||||
this.elem = elem;
|
||||
@ -70,8 +63,7 @@ export default class BoxesField {
|
||||
this.boxes.push(
|
||||
new Box(
|
||||
Math.floor(
|
||||
Math.random() * (this.params.boxMaxSize - this.params.boxMinSize) +
|
||||
this.params.boxMinSize,
|
||||
Math.random() * (this.params.boxMaxSize - this.params.boxMinSize) + this.params.boxMinSize,
|
||||
),
|
||||
{
|
||||
x: Math.floor(Math.random() * elem.width + 1),
|
||||
@ -92,23 +84,13 @@ export default class BoxesField {
|
||||
}
|
||||
|
||||
drawLight(light: Point): void {
|
||||
const greaterSize =
|
||||
window.screen.width > window.screen.height
|
||||
? window.screen.width
|
||||
: window.screen.height;
|
||||
const greaterSize = window.screen.width > window.screen.height ? window.screen.width : window.screen.height;
|
||||
// еее, теорема пифагора и описывание окружности вокруг квадрата, не зря в универ ходил!!!
|
||||
const lightRadius = greaterSize * Math.sqrt(2);
|
||||
|
||||
this.ctx.beginPath();
|
||||
this.ctx.arc(light.x, light.y, lightRadius, 0, 2 * Math.PI);
|
||||
const gradient = this.ctx.createRadialGradient(
|
||||
light.x,
|
||||
light.y,
|
||||
0,
|
||||
light.x,
|
||||
light.y,
|
||||
lightRadius,
|
||||
);
|
||||
const gradient = this.ctx.createRadialGradient(light.x, light.y, 0, light.x, light.y, lightRadius);
|
||||
gradient.addColorStop(0, this.params.lightColor);
|
||||
gradient.addColorStop(1, this.params.backgroundColor);
|
||||
this.ctx.fillStyle = gradient;
|
||||
@ -171,8 +153,6 @@ export default class BoxesField {
|
||||
}
|
||||
|
||||
getRandomColor(): string {
|
||||
return this.params.boxColors[
|
||||
Math.floor(Math.random() * this.params.boxColors.length)
|
||||
];
|
||||
return this.params.boxColors[Math.floor(Math.random() * this.params.boxColors.length)];
|
||||
}
|
||||
}
|
||||
|
@ -15,15 +15,10 @@ describe('BsodMiddleware', () => {
|
||||
|
||||
const middleware = new BsodMiddleware(dispatchBsod, logger as any);
|
||||
|
||||
return expect(middleware.catch(resp), 'to be rejected with', resp).then(
|
||||
() => {
|
||||
return expect(middleware.catch(resp), 'to be rejected with', resp).then(() => {
|
||||
expect(dispatchBsod, 'was called');
|
||||
expect(logger.warn, 'to have a call satisfying', [
|
||||
'Unexpected response (BSoD)',
|
||||
{ resp },
|
||||
]);
|
||||
},
|
||||
);
|
||||
expect(logger.warn, 'to have a call satisfying', ['Unexpected response (BSoD)', { resp }]);
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
@ -38,12 +33,10 @@ describe('BsodMiddleware', () => {
|
||||
|
||||
const middleware = new BsodMiddleware(dispatchBsod, logger as any);
|
||||
|
||||
return expect(middleware.catch(resp), 'to be rejected with', resp).then(
|
||||
() => {
|
||||
return expect(middleware.catch(resp), 'to be rejected with', resp).then(() => {
|
||||
expect(dispatchBsod, 'was not called');
|
||||
expect(logger.warn, 'was not called');
|
||||
},
|
||||
);
|
||||
});
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
@ -15,15 +15,12 @@ class BsodMiddleware implements Middleware {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
async catch<T extends Resp<any>>(
|
||||
resp?: T | InternalServerError | Error,
|
||||
): Promise<T> {
|
||||
async catch<T extends Resp<any>>(resp?: T | InternalServerError | Error): Promise<T> {
|
||||
const { originalResponse } = (resp || {}) as InternalServerError;
|
||||
|
||||
if (
|
||||
resp &&
|
||||
((resp instanceof InternalServerError &&
|
||||
(resp.error as any).code !== ABORT_ERR) ||
|
||||
((resp instanceof InternalServerError && (resp.error as any).code !== ABORT_ERR) ||
|
||||
(originalResponse && /5\d\d/.test(originalResponse.status)))
|
||||
) {
|
||||
this.dispatchBsod();
|
||||
|
@ -11,10 +11,7 @@ let injectedStore: Store;
|
||||
let injectedHistory: History<any>;
|
||||
let onBsod: undefined | (() => void);
|
||||
|
||||
export default function dispatchBsod(
|
||||
store = injectedStore,
|
||||
history = injectedHistory,
|
||||
) {
|
||||
export default function dispatchBsod(store = injectedStore, history = injectedHistory) {
|
||||
store.dispatch(bsod());
|
||||
onBsod && onBsod();
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
@import '~app/components/ui/colors.scss';
|
||||
|
||||
$font-family-monospaced: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Roboto Mono',
|
||||
monospace;
|
||||
$font-family-monospaced: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Roboto Mono', monospace;
|
||||
|
||||
.body {
|
||||
height: 100%;
|
||||
|
@ -43,14 +43,8 @@ export default class Collapse extends Component<Props, State> {
|
||||
const { height, wasInitialized } = this.state;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={styles.overflow}
|
||||
style={wasInitialized ? {} : { height: 0 }}
|
||||
>
|
||||
<MeasureHeight
|
||||
state={this.shouldMeasureHeight()}
|
||||
onMeasure={this.onUpdateHeight}
|
||||
>
|
||||
<div className={styles.overflow} style={wasInitialized ? {} : { height: 0 }}>
|
||||
<MeasureHeight state={this.shouldMeasureHeight()} onMeasure={this.onUpdateHeight}>
|
||||
<Motion
|
||||
style={{
|
||||
top: wasInitialized ? spring(isOpened ? 0 : -height) : -height,
|
||||
|
@ -6,32 +6,25 @@ import Button from './Button';
|
||||
storiesOf('UI/Form', module).add('Button', () => (
|
||||
<>
|
||||
<div>
|
||||
<Button label="Green Button" />{' '}
|
||||
<Button label="Blue Button" color="blue" />{' '}
|
||||
<Button label="DarkBlue Button" color="darkBlue" />{' '}
|
||||
<Button label="Violet Button" color="violet" />{' '}
|
||||
<Button label="LightViolet Button" color="lightViolet" />{' '}
|
||||
<Button label="Orange Button" color="orange" />{' '}
|
||||
<Button label="Red Button" color="red" />{' '}
|
||||
<Button label="Black Button" color="black" />{' '}
|
||||
<Button label="Green Button" /> <Button label="Blue Button" color="blue" />{' '}
|
||||
<Button label="DarkBlue Button" color="darkBlue" /> <Button label="Violet Button" color="violet" />{' '}
|
||||
<Button label="LightViolet Button" color="lightViolet" /> <Button label="Orange Button" color="orange" />{' '}
|
||||
<Button label="Red Button" color="red" /> <Button label="Black Button" color="black" />{' '}
|
||||
<Button label="White Button" color="white" />
|
||||
</div>
|
||||
<div>
|
||||
<h2>Disabled buttons</h2>
|
||||
<Button disabled label="Green Button" />{' '}
|
||||
<Button disabled label="Blue Button" color="blue" />{' '}
|
||||
<Button disabled label="Green Button" /> <Button disabled label="Blue Button" color="blue" />{' '}
|
||||
<Button disabled label="DarkBlue Button" color="darkBlue" />{' '}
|
||||
<Button disabled label="Violet Button" color="violet" />{' '}
|
||||
<Button disabled label="LightViolet Button" color="lightViolet" />{' '}
|
||||
<Button disabled label="Orange Button" color="orange" />{' '}
|
||||
<Button disabled label="Red Button" color="red" />{' '}
|
||||
<Button disabled label="Orange Button" color="orange" /> <Button disabled label="Red Button" color="red" />{' '}
|
||||
<Button disabled label="Black Button" color="black" />{' '}
|
||||
<Button disabled label="White Button" color="white" />
|
||||
</div>
|
||||
<div>
|
||||
<h2>Button sizes</h2>
|
||||
<Button label="Default button" /> <Button label="Small button" small />{' '}
|
||||
<br />
|
||||
<Button label="Default button" /> <Button label="Small button" small /> <br />
|
||||
<br />
|
||||
<Button label="Block button" block />
|
||||
<br />
|
||||
@ -39,15 +32,12 @@ storiesOf('UI/Form', module).add('Button', () => (
|
||||
</div>
|
||||
<div>
|
||||
<h2>Loading button</h2>
|
||||
<Button loading label="Green Button" />{' '}
|
||||
<Button loading label="Blue Button" color="blue" />{' '}
|
||||
<Button loading label="Green Button" /> <Button loading label="Blue Button" color="blue" />{' '}
|
||||
<Button loading label="DarkBlue Button" color="darkBlue" />{' '}
|
||||
<Button loading label="Violet Button" color="violet" />{' '}
|
||||
<Button loading label="LightViolet Button" color="lightViolet" />{' '}
|
||||
<Button loading label="Orange Button" color="orange" />{' '}
|
||||
<Button loading label="Red Button" color="red" />{' '}
|
||||
<Button loading label="Black Button" color="black" />{' '}
|
||||
<Button loading label="White Button" color="white" />
|
||||
<Button loading label="Orange Button" color="orange" /> <Button loading label="Red Button" color="red" />{' '}
|
||||
<Button loading label="Black Button" color="black" /> <Button loading label="White Button" color="white" />
|
||||
</div>
|
||||
</>
|
||||
));
|
||||
|
@ -48,9 +48,7 @@ export default class Button extends FormComponent<
|
||||
disabled={disabled}
|
||||
{...restProps}
|
||||
>
|
||||
{typeof label === 'object' && React.isValidElement(label)
|
||||
? label
|
||||
: this.formatMessage(label)}
|
||||
{typeof label === 'object' && React.isValidElement(label) ? label : this.formatMessage(label)}
|
||||
</ComponentProp>
|
||||
);
|
||||
}
|
||||
|
@ -56,10 +56,7 @@ export default class Captcha extends FormInputComponent<
|
||||
<ComponentLoader />
|
||||
</div>
|
||||
|
||||
<div
|
||||
ref={this.elRef}
|
||||
className={clsx(styles.captcha, styles[`${skin}Captcha`])}
|
||||
/>
|
||||
<div ref={this.elRef} className={clsx(styles.captcha, styles[`${skin}Captcha`])} />
|
||||
|
||||
{this.renderError()}
|
||||
</div>
|
||||
|
@ -30,19 +30,9 @@ export default class Checkbox extends FormInputComponent<
|
||||
const props = omit(this.props, ['color', 'skin', 'label']);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
styles[`${color}MarkableRow`],
|
||||
styles[`${skin}MarkableRow`],
|
||||
)}
|
||||
>
|
||||
<div className={clsx(styles[`${color}MarkableRow`], styles[`${skin}MarkableRow`])}>
|
||||
<label className={styles.markableContainer}>
|
||||
<input
|
||||
ref={this.elRef}
|
||||
className={styles.markableInput}
|
||||
type="checkbox"
|
||||
{...props}
|
||||
/>
|
||||
<input ref={this.elRef} className={styles.markableInput} type="checkbox" {...props} />
|
||||
<div className={styles.checkbox} />
|
||||
{label}
|
||||
</label>
|
||||
|
@ -56,9 +56,7 @@ export default class Dropdown extends FormInputComponent<Props, State> {
|
||||
delete restProps.label;
|
||||
|
||||
const activeItem = this.getActiveItem();
|
||||
const label = React.isValidElement(activeItem.label)
|
||||
? activeItem.label
|
||||
: this.formatMessage(activeItem.label);
|
||||
const label = React.isValidElement(activeItem.label) ? activeItem.label : this.formatMessage(activeItem.label);
|
||||
|
||||
return (
|
||||
<div>
|
||||
@ -78,11 +76,7 @@ export default class Dropdown extends FormInputComponent<Props, State> {
|
||||
|
||||
<div className={styles.menu}>
|
||||
{Object.entries(items).map(([value, label]) => (
|
||||
<div
|
||||
className={styles.menuItem}
|
||||
key={value}
|
||||
onClick={this.onSelectItem({ value, label })}
|
||||
>
|
||||
<div className={styles.menuItem} key={value} onClick={this.onSelectItem({ value, label })}>
|
||||
{label}
|
||||
</div>
|
||||
))}
|
||||
|
@ -64,10 +64,7 @@ export default class Form extends React.Component<Props, State> {
|
||||
static getDerivedStateFromProps(props: Props, state: State) {
|
||||
const patch: Partial<State> = {};
|
||||
|
||||
if (
|
||||
typeof props.isLoading !== 'undefined' &&
|
||||
props.isLoading !== state.isLoading
|
||||
) {
|
||||
if (typeof props.isLoading !== 'undefined' && props.isLoading !== state.isLoading) {
|
||||
patch.isLoading = props.isLoading;
|
||||
}
|
||||
|
||||
@ -152,9 +149,7 @@ export default class Form extends React.Component<Props, State> {
|
||||
.finally(() => this.mounted && this.setState({ isLoading: false }));
|
||||
}
|
||||
} else {
|
||||
const invalidEls: NodeListOf<InputElement> = form.querySelectorAll(
|
||||
':invalid',
|
||||
);
|
||||
const invalidEls: NodeListOf<InputElement> = form.querySelectorAll(':invalid');
|
||||
const errors: Record<string, string> = {};
|
||||
invalidEls[0].focus(); // focus on first error
|
||||
|
||||
|
@ -8,13 +8,8 @@ interface Props {
|
||||
error?: Parameters<typeof resolveError>[0] | MessageDescriptor | null;
|
||||
}
|
||||
|
||||
function isMessageDescriptor(
|
||||
message: Props['error'],
|
||||
): message is MessageDescriptor {
|
||||
return (
|
||||
typeof message === 'object' &&
|
||||
typeof (message as MessageDescriptor).id !== 'undefined'
|
||||
);
|
||||
function isMessageDescriptor(message: Props['error']): message is MessageDescriptor {
|
||||
return typeof message === 'object' && typeof (message as MessageDescriptor).id !== 'undefined';
|
||||
}
|
||||
|
||||
const FormError: ComponentType<Props> = ({ error }) => {
|
||||
|
@ -83,9 +83,7 @@ export default class FormModel {
|
||||
*/
|
||||
focus(fieldId: string): void {
|
||||
if (!this.fields[fieldId]) {
|
||||
throw new Error(
|
||||
`Can not focus. The field with an id ${fieldId} does not exists`,
|
||||
);
|
||||
throw new Error(`Can not focus. The field with an id ${fieldId} does not exists`);
|
||||
}
|
||||
|
||||
this.fields[fieldId].focus();
|
||||
@ -102,9 +100,7 @@ export default class FormModel {
|
||||
const field = this.fields[fieldId];
|
||||
|
||||
if (!field) {
|
||||
throw new Error(
|
||||
`Can not get value. The field with an id ${fieldId} does not exists`,
|
||||
);
|
||||
throw new Error(`Can not get value. The field with an id ${fieldId} does not exists`);
|
||||
}
|
||||
|
||||
if (!field.getValue) {
|
||||
|
@ -40,14 +40,7 @@ export default class Input extends FormInputComponent<
|
||||
elRef = React.createRef<HTMLInputElement>();
|
||||
|
||||
render() {
|
||||
const {
|
||||
color,
|
||||
skin,
|
||||
center,
|
||||
icon: iconType,
|
||||
copy: showCopyIcon,
|
||||
placeholder: placeholderText,
|
||||
} = this.props;
|
||||
const { color, skin, center, icon: iconType, copy: showCopyIcon, placeholder: placeholderText } = this.props;
|
||||
let { label: labelContent } = this.props;
|
||||
const { wasCopied } = this.state;
|
||||
let placeholder: string | undefined;
|
||||
@ -57,16 +50,7 @@ export default class Input extends FormInputComponent<
|
||||
type: 'text',
|
||||
...this.props,
|
||||
},
|
||||
[
|
||||
'label',
|
||||
'placeholder',
|
||||
'error',
|
||||
'skin',
|
||||
'color',
|
||||
'center',
|
||||
'icon',
|
||||
'copy',
|
||||
],
|
||||
['label', 'placeholder', 'error', 'skin', 'color', 'center', 'icon', 'copy'],
|
||||
);
|
||||
|
||||
let label: React.ReactElement | null = null;
|
||||
@ -117,13 +101,9 @@ export default class Input extends FormInputComponent<
|
||||
<div className={styles.textFieldContainer}>
|
||||
<input
|
||||
ref={this.elRef}
|
||||
className={clsx(
|
||||
styles[`${skin}TextField`],
|
||||
styles[`${color}TextField`],
|
||||
{
|
||||
className={clsx(styles[`${skin}TextField`], styles[`${color}TextField`], {
|
||||
[styles.textFieldCenter]: center,
|
||||
},
|
||||
)}
|
||||
})}
|
||||
placeholder={placeholder}
|
||||
{...props}
|
||||
/>
|
||||
@ -161,10 +141,7 @@ export default class Input extends FormInputComponent<
|
||||
|
||||
try {
|
||||
clearTimeout(copiedStateTimeout);
|
||||
copiedStateTimeout = setTimeout(
|
||||
() => this.setState({ wasCopied: false }),
|
||||
2000,
|
||||
);
|
||||
copiedStateTimeout = setTimeout(() => this.setState({ wasCopied: false }), 2000);
|
||||
|
||||
await copy(value);
|
||||
this.setState({ wasCopied: true });
|
||||
|
@ -9,10 +9,5 @@ type LinkProps = React.ComponentProps<typeof Link>;
|
||||
export default function LinkButton(props: ButtonProps & LinkProps) {
|
||||
const { to, ...restProps } = props;
|
||||
|
||||
return (
|
||||
<Button
|
||||
component={(linkProps) => <Link {...linkProps} to={to} />}
|
||||
{...(restProps as ButtonProps)}
|
||||
/>
|
||||
);
|
||||
return <Button component={(linkProps) => <Link {...linkProps} to={to} />} {...(restProps as ButtonProps)} />;
|
||||
}
|
||||
|
@ -31,19 +31,9 @@ export default class Radio extends FormInputComponent<
|
||||
const props = omit(this.props, ['color', 'skin', 'label']);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
styles[`${color}MarkableRow`],
|
||||
styles[`${skin}MarkableRow`],
|
||||
)}
|
||||
>
|
||||
<div className={clsx(styles[`${color}MarkableRow`], styles[`${skin}MarkableRow`])}>
|
||||
<label className={styles.markableContainer}>
|
||||
<input
|
||||
ref={this.elRef}
|
||||
className={styles.markableInput}
|
||||
type="radio"
|
||||
{...props}
|
||||
/>
|
||||
<input ref={this.elRef} className={styles.markableInput} type="radio" {...props} />
|
||||
<div className={styles.radio} />
|
||||
{label}
|
||||
</label>
|
||||
|
@ -1,8 +1,6 @@
|
||||
import React from 'react';
|
||||
import { MessageDescriptor } from 'react-intl';
|
||||
import TextareaAutosize, {
|
||||
TextareaAutosizeProps,
|
||||
} from 'react-textarea-autosize';
|
||||
import TextareaAutosize, { TextareaAutosizeProps } from 'react-textarea-autosize';
|
||||
import clsx from 'clsx';
|
||||
import { uniqueId, omit } from 'app/functions';
|
||||
import { SKIN_DARK, COLOR_GREEN, Skin, Color } from 'app/components/ui';
|
||||
@ -17,9 +15,7 @@ interface OwnProps {
|
||||
color: Color;
|
||||
}
|
||||
|
||||
export default class TextArea extends FormInputComponent<
|
||||
OwnProps & Omit<TextareaAutosizeProps, keyof OwnProps>
|
||||
> {
|
||||
export default class TextArea extends FormInputComponent<OwnProps & Omit<TextareaAutosizeProps, keyof OwnProps>> {
|
||||
static defaultProps = {
|
||||
color: COLOR_GREEN,
|
||||
skin: SKIN_DARK,
|
||||
@ -28,12 +24,7 @@ export default class TextArea extends FormInputComponent<
|
||||
elRef = React.createRef<HTMLTextAreaElement>();
|
||||
|
||||
render() {
|
||||
const {
|
||||
color,
|
||||
skin,
|
||||
label: labelText,
|
||||
placeholder: placeholderText,
|
||||
} = this.props;
|
||||
const { color, skin, label: labelText, placeholder: placeholderText } = this.props;
|
||||
let label: React.ReactElement | undefined;
|
||||
let placeholder: string | undefined;
|
||||
|
||||
@ -67,11 +58,7 @@ export default class TextArea extends FormInputComponent<
|
||||
<div className={styles.textAreaContainer}>
|
||||
<TextareaAutosize
|
||||
inputRef={this.elRef}
|
||||
className={clsx(
|
||||
styles.textArea,
|
||||
styles[`${skin}TextField`],
|
||||
styles[`${color}TextField`],
|
||||
)}
|
||||
className={clsx(styles.textArea, styles[`${skin}TextField`], styles[`${color}TextField`])}
|
||||
placeholder={placeholder}
|
||||
{...props}
|
||||
/>
|
||||
|
@ -10,16 +10,4 @@ import Dropdown from './Dropdown';
|
||||
import Captcha from './Captcha';
|
||||
import FormError from './FormError';
|
||||
|
||||
export {
|
||||
Input,
|
||||
TextArea,
|
||||
Button,
|
||||
LinkButton,
|
||||
Checkbox,
|
||||
Radio,
|
||||
Form,
|
||||
FormModel,
|
||||
Dropdown,
|
||||
Captcha,
|
||||
FormError,
|
||||
};
|
||||
export { Input, TextArea, Button, LinkButton, Checkbox, Radio, Form, FormModel, Dropdown, Captcha, FormError };
|
||||
|
@ -1,13 +1,4 @@
|
||||
export type Color =
|
||||
| 'green'
|
||||
| 'blue'
|
||||
| 'darkBlue'
|
||||
| 'violet'
|
||||
| 'lightViolet'
|
||||
| 'orange'
|
||||
| 'red'
|
||||
| 'black'
|
||||
| 'white';
|
||||
export type Color = 'green' | 'blue' | 'darkBlue' | 'violet' | 'lightViolet' | 'orange' | 'red' | 'black' | 'white';
|
||||
|
||||
export type Skin = 'dark' | 'light';
|
||||
|
||||
|
@ -12,15 +12,10 @@ interface Props {
|
||||
}
|
||||
|
||||
const ComponentLoader: ComponentType<Props> = ({ skin = 'dark' }) => (
|
||||
<div
|
||||
className={clsx(styles.componentLoader, styles[`${skin}ComponentLoader`])}
|
||||
>
|
||||
<div className={clsx(styles.componentLoader, styles[`${skin}ComponentLoader`])}>
|
||||
<div className={styles.spins}>
|
||||
{new Array(5).fill(0).map((_, index) => (
|
||||
<div
|
||||
className={clsx(styles.spin, styles[`spin${index}`])}
|
||||
key={index}
|
||||
/>
|
||||
<div className={clsx(styles.spin, styles[`spin${index}`])} key={index} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -13,12 +13,7 @@ interface Props {
|
||||
onLoad?: () => void;
|
||||
}
|
||||
|
||||
const ImageLoader: ComponentType<Props> = ({
|
||||
src,
|
||||
alt,
|
||||
ratio,
|
||||
onLoad = () => {},
|
||||
}) => {
|
||||
const ImageLoader: ComponentType<Props> = ({ src, alt, ratio, onLoad = () => {} }) => {
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -52,9 +52,7 @@ describe('<PopupStack />', () => {
|
||||
fireEvent.click(screen.getByRole('button', { name: 'close' }));
|
||||
|
||||
uxpect(destroy, 'was called once');
|
||||
uxpect(destroy, 'to have a call satisfying', [
|
||||
uxpect.it('to be', props.popups[0]),
|
||||
]);
|
||||
uxpect(destroy, 'to have a call satisfying', [uxpect.it('to be', props.popups[0])]);
|
||||
|
||||
rerender(<PopupStack popups={[]} destroy={destroy} />);
|
||||
|
||||
@ -141,9 +139,7 @@ describe('<PopupStack />', () => {
|
||||
document.dispatchEvent(event);
|
||||
|
||||
uxpect(props.destroy, 'was called once');
|
||||
uxpect(props.destroy, 'to have a call satisfying', [
|
||||
uxpect.it('to be', props.popups[1]),
|
||||
]);
|
||||
uxpect(props.destroy, 'to have a call satisfying', [uxpect.it('to be', props.popups[1])]);
|
||||
});
|
||||
|
||||
it('should NOT hide popup on esc pressed if disableOverlayClose', () => {
|
||||
|
@ -46,11 +46,7 @@ export class PopupStack extends React.Component<Props> {
|
||||
}}
|
||||
timeout={500}
|
||||
>
|
||||
<div
|
||||
className={styles.overlay}
|
||||
role="dialog"
|
||||
onClick={this.onOverlayClick(popup)}
|
||||
>
|
||||
<div className={styles.overlay} role="dialog" onClick={this.onOverlayClick(popup)}>
|
||||
<Popup onClose={this.onClose(popup)} />
|
||||
</div>
|
||||
</CSSTransition>
|
||||
|
@ -98,8 +98,7 @@ export function scrollTo(y: number) {
|
||||
* @returns {number}
|
||||
*/
|
||||
function normalizeScrollPosition(y: number): number {
|
||||
const contentHeight =
|
||||
(document.documentElement && document.documentElement.scrollHeight) || 0;
|
||||
const contentHeight = (document.documentElement && document.documentElement.scrollHeight) || 0;
|
||||
const windowHeight: number = window.innerHeight;
|
||||
const maxY = contentHeight - windowHeight;
|
||||
|
||||
|
@ -47,8 +47,7 @@
|
||||
|
||||
background: #aaa;
|
||||
border: 2px solid #aaa;
|
||||
transition: background 0.4s ease,
|
||||
border-color 0.4s cubic-bezier(0.19, 1, 0.22, 1); // easeOutExpo
|
||||
transition: background 0.4s ease, border-color 0.4s cubic-bezier(0.19, 1, 0.22, 1); // easeOutExpo
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,4 @@
|
||||
import {
|
||||
changeLang as changeLangEndpoint,
|
||||
acceptRules as acceptRulesEndpoint,
|
||||
} from 'app/services/api/accounts';
|
||||
import { changeLang as changeLangEndpoint, acceptRules as acceptRulesEndpoint } from 'app/services/api/accounts';
|
||||
import { setLocale } from 'app/components/i18n/actions';
|
||||
import { ThunkAction } from 'app/reducers';
|
||||
|
||||
@ -75,9 +72,7 @@ export function acceptRules(): ThunkAction<Promise<{ success: boolean }>> {
|
||||
const { id } = getState().user;
|
||||
|
||||
if (!id) {
|
||||
throw new Error(
|
||||
'user id is should be set at the moment when this action is called',
|
||||
);
|
||||
throw new Error('user id is should be set at the moment when this action is called');
|
||||
}
|
||||
|
||||
return acceptRulesEndpoint(id).then((resp) => {
|
||||
|
@ -24,9 +24,7 @@ describe('refreshTokenMiddleware', () => {
|
||||
const email = 'test@email.com';
|
||||
|
||||
beforeEach(() => {
|
||||
sinon
|
||||
.stub(authentication, 'requestToken')
|
||||
.named('authentication.requestToken');
|
||||
sinon.stub(authentication, 'requestToken').named('authentication.requestToken');
|
||||
sinon.stub(authentication, 'logout').named('authentication.logout');
|
||||
sinon.stub(browserHistory, 'push');
|
||||
|
||||
@ -58,8 +56,7 @@ describe('refreshTokenMiddleware', () => {
|
||||
expect(browserHistory.push, 'to have a call satisfying', ['/login']);
|
||||
}
|
||||
|
||||
it('must be till 2100 to test with validToken', () =>
|
||||
expect(new Date().getFullYear(), 'to be less than', 2100));
|
||||
it('must be till 2100 to test with validToken', () => expect(new Date().getFullYear(), 'to be less than', 2100));
|
||||
|
||||
describe('#before', () => {
|
||||
describe('when token expired', () => {
|
||||
@ -90,16 +87,12 @@ describe('refreshTokenMiddleware', () => {
|
||||
},
|
||||
};
|
||||
|
||||
(authentication.requestToken as any).returns(
|
||||
Promise.resolve(validToken),
|
||||
);
|
||||
(authentication.requestToken as any).returns(Promise.resolve(validToken));
|
||||
|
||||
return middleware.before!(data).then((resp) => {
|
||||
expect(resp, 'to satisfy', data);
|
||||
|
||||
expect(authentication.requestToken, 'to have a call satisfying', [
|
||||
refreshToken,
|
||||
]);
|
||||
expect(authentication.requestToken, 'to have a call satisfying', [refreshToken]);
|
||||
});
|
||||
});
|
||||
|
||||
@ -138,14 +131,10 @@ describe('refreshTokenMiddleware', () => {
|
||||
},
|
||||
};
|
||||
|
||||
(authentication.requestToken as any).returns(
|
||||
Promise.resolve(validToken),
|
||||
);
|
||||
(authentication.requestToken as any).returns(Promise.resolve(validToken));
|
||||
|
||||
return middleware.before!(data).then(() =>
|
||||
expect(dispatch, 'to have a call satisfying', [
|
||||
updateToken(validToken),
|
||||
]),
|
||||
expect(dispatch, 'to have a call satisfying', [updateToken(validToken)]),
|
||||
);
|
||||
});
|
||||
|
||||
@ -184,10 +173,9 @@ describe('refreshTokenMiddleware', () => {
|
||||
it('should relogin if token request failed', () => {
|
||||
(authentication.requestToken as any).returns(Promise.reject());
|
||||
|
||||
return expect(
|
||||
middleware.before!({ url: 'foo', options: { headers: {} } }),
|
||||
'to be rejected',
|
||||
).then(() => assertRelogin());
|
||||
return expect(middleware.before!({ url: 'foo', options: { headers: {} } }), 'to be rejected').then(() =>
|
||||
assertRelogin(),
|
||||
);
|
||||
});
|
||||
|
||||
it('should not logout if request failed with 5xx', () => {
|
||||
@ -199,11 +187,7 @@ describe('refreshTokenMiddleware', () => {
|
||||
middleware.before!({ url: 'foo', options: { headers: {} } }),
|
||||
'to be rejected with',
|
||||
resp,
|
||||
).then(() =>
|
||||
expect(dispatch, 'to have no calls satisfying', [
|
||||
{ payload: { isGuest: true } },
|
||||
]),
|
||||
);
|
||||
).then(() => expect(dispatch, 'to have no calls satisfying', [{ payload: { isGuest: true } }]));
|
||||
});
|
||||
});
|
||||
|
||||
@ -279,40 +263,26 @@ describe('refreshTokenMiddleware', () => {
|
||||
});
|
||||
|
||||
function assertNewTokenRequest() {
|
||||
expect(authentication.requestToken, 'to have a call satisfying', [
|
||||
refreshToken,
|
||||
]);
|
||||
expect(authentication.requestToken, 'to have a call satisfying', [refreshToken]);
|
||||
expect(restart, 'was called');
|
||||
expect(dispatch, 'was called');
|
||||
}
|
||||
|
||||
it('should request new token if expired', () =>
|
||||
expect(
|
||||
middleware.catch!(
|
||||
expiredResponse,
|
||||
{ url: '', options: { headers: {} } },
|
||||
restart,
|
||||
),
|
||||
middleware.catch!(expiredResponse, { url: '', options: { headers: {} } }, restart),
|
||||
'to be fulfilled',
|
||||
).then(assertNewTokenRequest));
|
||||
|
||||
it('should request new token if invalid credential', () =>
|
||||
expect(
|
||||
middleware.catch!(
|
||||
badTokenReponse,
|
||||
{ url: '', options: { headers: {} } },
|
||||
restart,
|
||||
),
|
||||
middleware.catch!(badTokenReponse, { url: '', options: { headers: {} } }, restart),
|
||||
'to be fulfilled',
|
||||
).then(assertNewTokenRequest));
|
||||
|
||||
it('should request new token if token is incorrect', () =>
|
||||
expect(
|
||||
middleware.catch!(
|
||||
incorrectTokenReponse,
|
||||
{ url: '', options: { headers: {} } },
|
||||
restart,
|
||||
),
|
||||
middleware.catch!(incorrectTokenReponse, { url: '', options: { headers: {} } }, restart),
|
||||
'to be fulfilled',
|
||||
).then(assertNewTokenRequest));
|
||||
|
||||
@ -335,11 +305,7 @@ describe('refreshTokenMiddleware', () => {
|
||||
});
|
||||
|
||||
return expect(
|
||||
middleware.catch!(
|
||||
incorrectTokenReponse,
|
||||
{ url: '', options: { headers: {} } },
|
||||
restart,
|
||||
),
|
||||
middleware.catch!(incorrectTokenReponse, { url: '', options: { headers: {} } }, restart),
|
||||
'to be rejected',
|
||||
).then(() => {
|
||||
assertRelogin();
|
||||
@ -359,12 +325,10 @@ describe('refreshTokenMiddleware', () => {
|
||||
restart,
|
||||
);
|
||||
|
||||
return expect(promise, 'to be rejected with', expiredResponse).then(
|
||||
() => {
|
||||
return expect(promise, 'to be rejected with', expiredResponse).then(() => {
|
||||
expect(restart, 'was not called');
|
||||
expect(authentication.requestToken, 'was not called');
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should pass the rest of failed requests through', () => {
|
||||
|
@ -1,7 +1,4 @@
|
||||
import {
|
||||
ensureToken,
|
||||
recoverFromTokenError,
|
||||
} from 'app/components/accounts/actions';
|
||||
import { ensureToken, recoverFromTokenError } from 'app/components/accounts/actions';
|
||||
import { getActiveAccount } from 'app/components/accounts/reducer';
|
||||
import { Store } from 'app/reducers';
|
||||
import { Middleware } from 'app/services/request';
|
||||
@ -15,15 +12,11 @@ import { Middleware } from 'app/services/request';
|
||||
*
|
||||
* @returns {object} - request middleware
|
||||
*/
|
||||
export default function refreshTokenMiddleware({
|
||||
dispatch,
|
||||
getState,
|
||||
}: Store): Middleware {
|
||||
export default function refreshTokenMiddleware({ dispatch, getState }: Store): Middleware {
|
||||
return {
|
||||
async before(req) {
|
||||
const activeAccount = getActiveAccount(getState());
|
||||
const disableMiddleware =
|
||||
!!req.options.token || req.options.token === null;
|
||||
const disableMiddleware = !!req.options.token || req.options.token === null;
|
||||
|
||||
const isRefreshTokenRequest = req.url.includes('refresh-token');
|
||||
|
||||
@ -34,13 +27,8 @@ export default function refreshTokenMiddleware({
|
||||
return dispatch(ensureToken()).then(() => req);
|
||||
},
|
||||
|
||||
async catch(
|
||||
resp: { status: number; message: string },
|
||||
req,
|
||||
restart,
|
||||
): Promise<any> {
|
||||
const disableMiddleware =
|
||||
!!req.options.token || req.options.token === null;
|
||||
async catch(resp: { status: number; message: string }, req, restart): Promise<any> {
|
||||
const disableMiddleware = !!req.options.token || req.options.token === null;
|
||||
|
||||
if (disableMiddleware) {
|
||||
return Promise.reject(resp);
|
||||
|
@ -42,10 +42,7 @@ const defaults: User = {
|
||||
isGuest: true,
|
||||
};
|
||||
|
||||
export default function user(
|
||||
state: User = defaults,
|
||||
{ type, payload }: { type: string; payload: any },
|
||||
) {
|
||||
export default function user(state: User = defaults, { type, payload }: { type: string; payload: any }) {
|
||||
switch (type) {
|
||||
case CHANGE_LANG:
|
||||
if (!payload || !payload.lang) {
|
||||
|
@ -48,20 +48,14 @@ export default class LoggedInPanel extends React.Component<
|
||||
[styles.activeAccountExpanded]: isAccountSwitcherActive,
|
||||
})}
|
||||
>
|
||||
<button
|
||||
className={styles.activeAccountButton}
|
||||
onClick={this.onExpandAccountSwitcher}
|
||||
>
|
||||
<button className={styles.activeAccountButton} onClick={this.onExpandAccountSwitcher}>
|
||||
<span className={styles.userIcon} />
|
||||
<span className={styles.userName}>{username}</span>
|
||||
<span className={styles.expandIcon} />
|
||||
</button>
|
||||
|
||||
<div className={clsx(styles.accountSwitcherContainer)}>
|
||||
<AccountSwitcher
|
||||
skin="light"
|
||||
onAfterAction={this.onToggleAccountSwitcher}
|
||||
/>
|
||||
<AccountSwitcher skin="light" onAfterAction={this.onToggleAccountSwitcher} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user