Change prettier rules

This commit is contained in:
ErickSkrauch 2020-05-24 02:08:24 +03:00
parent 73f0c37a6a
commit f85b9d8d35
382 changed files with 24137 additions and 26046 deletions

View File

@ -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',

View File

@ -2,5 +2,7 @@
"trailingComma": "all",
"singleQuote": true,
"proseWrap": "always",
"endOfLine": "lf"
"endOfLine": "lf",
"tabWidth": 4,
"printWidth": 120
}

View File

@ -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';

View File

@ -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(

View File

@ -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
View File

@ -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;

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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.

View File

@ -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: () => {},
};

View File

@ -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)}`],
)}
/>

View File

@ -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 }]));
});
});
});

View File

@ -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) => {

View File

@ -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;

View File

@ -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,
},
],
},
);
});
});
});
});

View File

@ -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),
};
}

View File

@ -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})`,

View File

@ -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

View File

@ -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)) {

View File

@ -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', () =>

View File

@ -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;
}

View File

@ -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;

View File

@ -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 ? (

View File

@ -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);

View File

@ -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,
});

View File

@ -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}>

View File

@ -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} />

View File

@ -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>
);
}

View File

@ -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>
);

View File

@ -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>
);

View File

@ -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;

View File

@ -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,
},
);
});
});
});

View File

@ -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;
}

View File

@ -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',
'Email is invalid',
);
expect(screen.getByRole('alert'), 'to have property', 'innerHTML', 'Email is invalid');
});
});
});

View File

@ -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>
);

View File

@ -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, {

View File

@ -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);

View File

@ -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"
/>
)}

View File

@ -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>
);

View File

@ -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>

View File

@ -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;

View File

@ -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),
};
}

View File

@ -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' }}>

View File

@ -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>

View File

@ -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') {

View File

@ -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;
},

View File

@ -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;
}

View File

@ -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

View File

@ -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()}

View File

@ -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) {

View File

@ -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}

View File

@ -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>;
}

View File

@ -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>
),
}}

View File

@ -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>
);
}

View File

@ -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 (

View File

@ -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');
});

View File

@ -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>

View File

@ -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>

View File

@ -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 });

View File

@ -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>
);

View File

@ -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>
);

View File

@ -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>

View File

@ -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>

View File

@ -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 (

View File

@ -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}>

View File

@ -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;

View File

@ -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}
/>
);
}

View File

@ -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}>

View File

@ -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;

View File

@ -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)];
}
}

View File

@ -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');
},
);
});
}),
);
});

View File

@ -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();

View File

@ -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();

View File

@ -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%;

View File

@ -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,

View File

@ -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>
</>
));

View File

@ -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>
);
}

View File

@ -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>

View File

@ -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>

View File

@ -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>
))}

View File

@ -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

View File

@ -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 }) => {

View File

@ -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) {

View File

@ -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 });

View File

@ -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)} />;
}

View File

@ -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>

View File

@ -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}
/>

View File

@ -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 };

View File

@ -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';

View File

@ -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>

View File

@ -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(() => {

View File

@ -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', () => {

View File

@ -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>

View File

@ -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;

View File

@ -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
}
}

View File

@ -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) => {

View File

@ -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', () => {

View File

@ -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);

View File

@ -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) {

View File

@ -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